Merge commit '34be23373b9e73694c3b214ba857283bad65aedb' into HEAD

Bug: b/154865520
Change-Id: I68ccde01a704e66fdfcc9bf0dec9d2c40167ab8a
diff --git a/.gitignore b/.gitignore
index 196c63c..b2af56e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,3 +29,6 @@
 # C-Lion
 /.idea/
 /cmake-build-*/
+
+# VSCode
+/.vscode/*
diff --git a/Android.bp b/Android.bp
index 6f0448e..e99ea8f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -59,8 +59,8 @@
     ],
     tool_files: ["utils/generate_language_headers.py"],
     cmd: 
-        "$(location) --extinst-name=DebugInfo --extinst-grammar=$(location source/extinst.debuginfo.grammar.json) --extinst-output-base=$$(dirname $(location DebugInfo.h))/DebugInfo; "+
-        "$(location) --extinst-name=OpenCLDebugInfo100 --extinst-grammar=$(location source/extinst.opencl.debuginfo.100.grammar.json) --extinst-output-base=$$(dirname $(location OpenCLDebugInfo100.h))/OpenCLDebugInfo100; "
+        "$(location) --extinst-grammar=$(location source/extinst.debuginfo.grammar.json) --extinst-output-path=$(location DebugInfo.h); "+
+        "$(location) --extinst-grammar=$(location source/extinst.opencl.debuginfo.100.grammar.json) --extinst-output-path=$(location OpenCLDebugInfo100.h); "
 }
 
 genrule {
diff --git a/BUILD.gn b/BUILD.gn
index 5ed85d9..d3107fd 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -37,6 +37,8 @@
 
     sources = [
       core_json_file,
+      debuginfo_insts_file,
+      cldebuginfo100_insts_file,
     ]
     outputs = [
       core_insts_file,
@@ -184,21 +186,19 @@
     script = "utils/generate_language_headers.py"
 
     name = invoker.name
-    extinst_output_base = "${target_gen_dir}/${name}"
+    extinst_output_path = "${target_gen_dir}/${name}.h"
 
     args = [
-      "--extinst-name",
-      "${name}",
       "--extinst-grammar",
       rebase_path(invoker.grammar_file, root_build_dir),
-      "--extinst-output-base",
-      rebase_path(extinst_output_base, root_build_dir),
+      "--extinst-output-path",
+      rebase_path(extinst_output_path, root_build_dir),
     ]
     inputs = [
       invoker.grammar_file,
     ]
     outputs = [
-      "${extinst_output_base}.h",
+      "${extinst_output_path}",
     ]
   }
 }
@@ -324,14 +324,6 @@
   }
 }
 
-source_set("spv_headers") {
-  sources = [
-    "$spirv_headers/include/spirv/1.2/GLSL.std.450.h",
-    "$spirv_headers/include/spirv/unified1/OpenCL.std.h",
-    "$spirv_headers/include/spirv/unified1/spirv.h",
-  ]
-}
-
 source_set("spvtools_headers") {
   sources = [
     "include/spirv-tools/instrument.hpp",
@@ -424,9 +416,9 @@
   ]
 
   public_deps = [
-    ":spv_headers",
     ":spvtools_core_enums_unified1",
     ":spvtools_headers",
+    "${spirv_headers}:spv_headers",
   ]
 
   if (build_with_chromium) {
@@ -490,6 +482,8 @@
 
   deps = [
     ":spvtools",
+    ":spvtools_language_header_cldebuginfo100",
+    ":spvtools_language_header_debuginfo",
   ]
   public_deps = [
     ":spvtools_headers",
@@ -596,6 +590,8 @@
     "source/opt/inst_bindless_check_pass.h",
     "source/opt/inst_buff_addr_check_pass.cpp",
     "source/opt/inst_buff_addr_check_pass.h",
+    "source/opt/inst_debug_printf_pass.cpp",
+    "source/opt/inst_debug_printf_pass.h",
     "source/opt/instruction.cpp",
     "source/opt/instruction.h",
     "source/opt/instruction_list.cpp",
@@ -717,6 +713,8 @@
 
   deps = [
     ":spvtools",
+    ":spvtools_language_header_cldebuginfo100",
+    ":spvtools_language_header_debuginfo",
     ":spvtools_vendor_tables_spv-amd-shader-ballot",
   ]
   public_deps = [
diff --git a/CHANGES b/CHANGES
index 2f2968e..fe6641e 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,7 +1,59 @@
 Revision history for SPIRV-Tools
 
-v2020.1-dev 2019-12-11
- - Start v2020.1-dev
+v2020.3-dev 2020-03-26
+ - Start v2020.3-dev
+
+v2020.2 2020-03-26
+ - General:
+   - Support extended instructions in the vscode language server
+   - Make spvOpcodeString part of the public API (#3174)
+   - Added guide to writing a spirv-fuzz fuzzer pass (#3190)
+   - Add support for KHR_ray_{query,tracing} extensions (#3235)
+ - Optimizer
+   - Debug Printf support (#3215)
+   - Add data structure for DebugScope, DebugDeclare in spirv-opt (#3183)
+   - Fix identification of Vulkan images and buffers (#3253)
+ - Validator
+   - Add support for SPV_AMD_shader_image_load_store_lod (#3186)
+   - Add validation rules for OpenCL.DebugInfo.100 extension (#3133)
+   - Adding WebGPU specific Workgroup scope rule (#3204)
+   - Disallow phis of images, samplers and sampled images (#3246)
+ - Reduce
+ - Fuzz
+   - Fuzzer passes to add local and global variables (#3175)
+   - Add fuzzer passes to add loads/stores (#3176)
+   - Fuzzer pass to add function calls (#3178)
+   - Fuzzer pass that adds access chains (#3182)
+   - Fuzzer pass to add equation instructions (#3202)
+   - Add swap commutable operands transformation (#3205)
+   - Add fuzzer pass to permute function parameters (#3212)
+   - Allow OpPhi operand to be replaced with a composite synonym (#3221)
+ - Linker
+
+v2020.1 2020-02-03
+ - General:
+   - Add support for SPV_KHR_non_semantic_info (#3110)
+   - Support OpenCL.DebugInfo.100 extended instruction set (#3080)
+   - Added support for Vulkan 1.2
+   - Add API function to better handle getting the necessary environment (#3142)
+   - Clarify mapping of target env to SPIR-V version (#3150)
+   - Implement constant folding for many transcendentals (#3166)
+ - 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)
+   - Handle TimeAMD in AmdExtensionToKhrPass. (#3168)
+ - Validator
+   - Fix structured exit validation (#3141)
+ - Reduce
+ - Fuzz
+   - Fuzzer pass to merge blocks (#3097)
+   - Transformation to add a new function to a module (#3114)
+   - Add fuzzer pass to perform module donation (#3117)
+   - Fuzzer passes to create and branch to new dead blocks (#3135)
+   - Fuzzer pass to add composite types (#3171)
+ - Linker:
+   - Remove names and decorations of imported symbols (#3081)
 
 v2019.5 2019-12-11
  - General:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6ed56a8..9d0eb8b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -249,7 +249,7 @@
 
 # Precompiled header macro. Parameters are source file list and filename for pch cpp file.
 macro(spvtools_pch SRCS PCHPREFIX)
-  if(MSVC AND CMAKE_GENERATOR MATCHES "^Visual Studio")
+  if(MSVC AND CMAKE_GENERATOR MATCHES "^Visual Studio" AND NOT "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
     set(PCH_NAME "$(IntDir)\\${PCHPREFIX}.pch")
     # make source files use/depend on PCH_NAME
     set_source_files_properties(${${SRCS}} PROPERTIES COMPILE_FLAGS "/Yu${PCHPREFIX}.h /FI${PCHPREFIX}.h /Fp${PCH_NAME} /Zm300" OBJECT_DEPENDS "${PCH_NAME}")
@@ -300,7 +300,7 @@
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
 endif()
 
-set(SPIRV_LIBRARIES "-lSPIRV-Tools -lSPIRV-Tools-link -lSPIRV-Tools-opt")
+set(SPIRV_LIBRARIES "-lSPIRV-Tools-opt -lSPIRV-Tools -lSPIRV-Tools-link")
 set(SPIRV_SHARED_LIBRARIES "-lSPIRV-Tools-shared")
 
 # Build pkg-config file
diff --git a/DEPS b/DEPS
index 311b7ed..c3e78a2 100644
--- a/DEPS
+++ b/DEPS
@@ -6,7 +6,7 @@
   'effcee_revision': 'cd25ec17e9382f99a895b9ef53ff3c277464d07d',
   'googletest_revision': 'f2fb48c3b3d79a75a88a99fba6576b25d42ec528',
   're2_revision': '5bd613749fd530b576b890283bfb6bc6ea6246cb',
-  'spirv_headers_revision': 'af64a9e826bf5bb5fcd2434dd71be1e41e922563',
+  'spirv_headers_revision': 'f8bf11a0253a32375c32cad92c841237b96696c0',
 }
 
 deps = {
diff --git a/build_defs.bzl b/build_defs.bzl
index 5d913a1..15b70c7 100644
--- a/build_defs.bzl
+++ b/build_defs.bzl
@@ -167,16 +167,16 @@
 def generate_extinst_lang_headers(name, grammar = None):
     if not grammar:
         fail("Must specify grammar", "grammar")
-    fmtargs = [name]
+    outs = [name + ".h"]
+    fmtargs = outs
     native.genrule(
         name = "gen_extinst_lang_headers_" + name,
         srcs = [grammar],
-        outs = [name + ".h"],
+        outs = outs,
         cmd = (
             "$(location :generate_language_headers) " +
-            "--extinst-name={0} " +
             "--extinst-grammar=$< " +
-            "--extinst-output-base=$(@D)/{0}"
+            "--extinst-output-path=$(location {0})"
         ).format(*fmtargs),
         tools = [":generate_language_headers"],
         visibility = ["//visibility:private"],
diff --git a/docs/spirv-fuzz.md b/docs/spirv-fuzz.md
new file mode 100644
index 0000000..e5439e3
--- /dev/null
+++ b/docs/spirv-fuzz.md
@@ -0,0 +1,83 @@
+# Guide to writing a spirv-fuzz fuzzer pass
+
+Writing a spirv-fuzz fuzzer pass usually requires two main contributions:
+
+- A *transformation*, capturing a small semantics-preserving change that can be made to a SPIR-V module.  This requires adding a protobuf message representing the transformation, and a corresponding class that implements the `Transformation` interface.
+- A new *fuzzer pass* class, implementing the `FuzzerPass` interface, that knows how to walk a SPIR-V module and apply the new transformation in a randomized fashion.
+
+In some cases, more than one kind of transformation is required for a single fuzzer pass, and in some cases the transformations that a new fuzzer pass requires have already been introduced by existing passes.  But the most common case is to introduce a transformation and fuzzer pass together.
+
+As an example, let's consider the `TransformationSetSelectionControl` transformation.  In SPIR-V, an `OpSelectionMerge` instruction (which intuitively indicates the start of an `if` or `switch` statement in a function) has a *selection control* mask, that can be one of `None`, `Flatten` or `DontFlatten`.  The details of these do not matter much for this little tutorial, but in brief, this parameter provides a hint to the shader compiler as to whether it would be profitable to attempt to flatten a piece of conditional code so that all of its statements are executed in a predicated fashion.
+
+As the selection control mask is just a hint, changing the value of this mask should have no semantic impact on the module.  The `TransformationSelectionControl` transformation specifies a new value for a given selection control mask.
+
+## Adding a new protobuf message
+
+Take a look at the `Transformation` message in `spvtoolsfuzz.proto`.  This has a `oneof` field that can be any one of the different spirv-fuzz transformations.  Observe that one of the options is `TransformationSetSelectionControl`.  When adding a transformation you first need to add an option for your transformation to the end of the `oneof` declaration.
+
+Now look at the `TransformationSetSelectionControl` message.  If adding your own transformation you need to add a new message for your transformation, and it should be placed alphabetically with respect to other transformations.
+
+The fields of `TransformationSetSelectionControl` provide just enough information to (a) determine whether a given example of this transformation is actually applicable, and (b) apply the transformation in the case that it is applicable.  The details of the transformation message will vary a lot between transformations.  In this case, the message has a `block_id` field, specifying a block that must end with `OpSelectionMerge`, and a `selection_control` field, which is the new value for the selection control mask of the `OpSelectionMerge` instruction.
+
+## Adding a new transformation class
+
+If your transformation is called `TransformationSomeThing`, you need to add `transformation_some_thing.h` and `transformation_some_thing.cpp` to `source/fuzz` and the corresponding `CMakeLists.txt` file.  So for `TransformationSetSelectionControl` we have `transformation_selection_control.h` and `transformation_selection_control.cpp`, and we will use this as an example to illustrate the expected contents of these files.
+
+The header file contains the specification of a class, `TransformationSetSelectionControl`, that implements the `Transformation` interface (from `transformation.h`).
+
+A transformation class should always have a single field, which should be the associated protobuf message; in our case:
+
+```
+ private:
+  protobufs::TransformationSetSelectionControl message_;
+```
+
+and two public constructors, one that takes a protobuf message; in our case:
+
+```
+  explicit TransformationSetSelectionControl(
+      const protobufs::TransformationSetSelectionControl& message);
+```
+
+and one that takes a parameter for each protobuf message field; in our case:
+
+```
+  TransformationSetSelectionControl(uint32_t block_id);
+```
+
+The first constructor allows an instance of the class to be created from a corresponding protobuf message.  The second should provide the ingredients necessary to populate a protobuf message.
+
+The class should also override the `IsApplicable`, `Apply` and `ToMessage` methods from `Transformation`.
+
+See `transformation_set_selection_control.h` for an example.
+
+The `IsApplicable` method should have a comment in the header file describing the conditions for applicability in simple terms.  These conditions should be implemented in the body of this method in the `.cpp` file.
+
+In the case of `TransformationSetSelectionControl`, `IsApplicable` involves checking that `block_id` is indeed the id of a block that has an `OpSelectoinMerge` instruction, and that `selection_control` is a valid selection mask.
+
+The `Apply` method should have a comment in the header file summarising the result of applying the transformation.  It should be implemented in the `.cpp` file, and you should assume that `IsApplicable` holds whenever `Apply` is invoked.
+
+## Writing tests for the transformation class
+
+Whenever you add a transformation class, `TransformationSomeThing`, you should add an associated test file, `transformation_some_thing_test.cpp`, under `test/fuzz`, adding it to the associated `CMakeLists.txt` file.
+
+For example `test/fuzz/transformation_set_selection_control_test.cpp` contains tests for `TransformationSetSelectionControl`.  Your tests should aim to cover one example from each scenario where the transformation is inapplicable, and check that it is indeed deemed inapplicable, and then check that the transformation does the right thing when applied in a few different ways.
+
+For example, the tests for `TransformationSetSelectionControl` check that a transformation of this kind is inapplicable if the `block_id` field of the transformation is not a block, or does not end in `OpSelectionMerge`, or if the `selection_control` mask has an illegal value.  It also checks that applying a sequence of valid transformations to a SPIR-V shader leads to a shader with appropriately modified selection controls.
+
+## Adding a new fuzzer pass class
+
+A *fuzzer pass* traverses a SPIR-V module looking for places to apply a certain kind of transformation, and randomly decides at which of these points to actually apply the transformation.  It might be necessary to apply other transformations in order to apply a given transformation (for example, if a transformation requires a certain type to be present in the module, said type can be added if not already present via another transformation).
+
+A fuzzer pass implements the `FuzzerPass` interface, and overrides its `Apply` method.  If your fuzzer pass is named `FuzzerPassSomeThing` then it should be represented by `fuzzer_pass_some_thing.h` and `fuzzer_pass_some_thing.cpp`, under `source/fuzz`; these should be added to the associated `CMakeLists.txt` file.
+
+Have a look at the source filed for `FuzzerPassAdjustSelectionControls`.  This pass considers every block that ends with `OpSelectionMerge`.  It decides randomly whether to adjust the selection control of this merge instruction via:
+
+```
+if (!GetFuzzerContext()->ChoosePercentage(
+        GetFuzzerContext()->GetChanceOfAdjustingSelectionControl())) {
+  continue;
+}
+```
+
+The `GetChanceOfAddingSelectionControl()` method has been added to `FuzzerContext` specifically to support this pass, and returns a percentage between 0 and 100.  It returns the `chance_of_adjusting_selection_control_` of `FuzzerContext`, which is randomly initialized to lie with the interval defined by `kChanceOfAdjustingSelectionControl` in `fuzzer_context.cpp`.  For any pass you write, you will need to add an analogous `GetChanceOf...` method to `FuzzerContext`, backed by an appropriate field, and you will need to decide on lower and upper bounds for this field and specify these via a `kChanceOf...` constant.
diff --git a/examples/cpp-interface/CMakeLists.txt b/examples/cpp-interface/CMakeLists.txt
index d050b07..7887ee7 100644
--- a/examples/cpp-interface/CMakeLists.txt
+++ b/examples/cpp-interface/CMakeLists.txt
@@ -16,4 +16,4 @@
   TARGET spirv-tools-cpp-example
   SRCS main.cpp
   LIBS SPIRV-Tools-opt
-)
\ No newline at end of file
+)
diff --git a/include/spirv-tools/instrument.hpp b/include/spirv-tools/instrument.hpp
index 2dcb333..d3180e4 100644
--- a/include/spirv-tools/instrument.hpp
+++ b/include/spirv-tools/instrument.hpp
@@ -208,6 +208,9 @@
 // The binding for the input buffer read by InstBuffAddrCheckPass.
 static const int kDebugInputBindingBuffAddr = 2;
 
+// This is the output buffer written by InstDebugPrintfPass.
+static const int kDebugOutputPrintfStream = 3;
+
 // Bindless Validation Input Buffer Format
 //
 // An input buffer for bindless validation consists of a single array of
diff --git a/include/spirv-tools/libspirv.h b/include/spirv-tools/libspirv.h
index e14e2e7..03c7d1b 100644
--- a/include/spirv-tools/libspirv.h
+++ b/include/spirv-tools/libspirv.h
@@ -1,4 +1,6 @@
-// Copyright (c) 2015-2016 The Khronos Group Inc.
+// Copyright (c) 2015-2020 The Khronos Group 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.
@@ -165,6 +167,12 @@
   SPV_OPERAND_TYPE_KERNEL_ENQ_FLAGS,              // SPIR-V Sec 3.29
   SPV_OPERAND_TYPE_KERNEL_PROFILING_INFO,         // SPIR-V Sec 3.30
   SPV_OPERAND_TYPE_CAPABILITY,                    // SPIR-V Sec 3.31
+  SPV_OPERAND_TYPE_RAY_FLAGS,                     // SPIR-V Sec 3.RF
+  SPV_OPERAND_TYPE_RAY_QUERY_INTERSECTION,        // SPIR-V Sec 3.RQIntersection
+  SPV_OPERAND_TYPE_RAY_QUERY_COMMITTED_INTERSECTION_TYPE,  // SPIR-V Sec
+                                                           // 3.RQCommitted
+  SPV_OPERAND_TYPE_RAY_QUERY_CANDIDATE_INTERSECTION_TYPE,  // SPIR-V Sec
+                                                           // 3.RQCandidate
 
   // Set 5:  Operands that are a single word bitmask.
   // Sometimes a set bit indicates the instruction requires still more operands.
@@ -419,8 +427,17 @@
 SPIRV_TOOLS_EXPORT const char* spvSoftwareVersionDetailsString(void);
 
 // Certain target environments impose additional restrictions on SPIR-V, so it's
-// often necessary to specify which one applies.  SPV_ENV_UNIVERSAL means
+// often necessary to specify which one applies.  SPV_ENV_UNIVERSAL_* implies an
 // environment-agnostic SPIR-V.
+//
+// When an API method needs to derive a SPIR-V version from a target environment
+// (from the spv_context object), the method will choose the highest version of
+// SPIR-V supported by the target environment.  Examples:
+//    SPV_ENV_VULKAN_1_0           ->  SPIR-V 1.0
+//    SPV_ENV_VULKAN_1_1           ->  SPIR-V 1.3
+//    SPV_ENV_VULKAN_1_1_SPIRV_1_4 ->  SPIR-V 1.4
+//    SPV_ENV_VULKAN_1_2           ->  SPIR-V 1.5
+// Consult the description of API entry points for specific rules.
 typedef enum {
   SPV_ENV_UNIVERSAL_1_0,  // SPIR-V 1.0 latest revision, no other restrictions.
   SPV_ENV_VULKAN_1_0,     // Vulkan 1.0 latest revision.
@@ -448,7 +465,10 @@
   SPV_ENV_VULKAN_1_1,     // Vulkan 1.1 latest revision.
   SPV_ENV_WEBGPU_0,       // Work in progress WebGPU 1.0.
   SPV_ENV_UNIVERSAL_1_4,  // SPIR-V 1.4 latest revision, no other restrictions.
-  SPV_ENV_VULKAN_1_1_SPIRV_1_4,  // Vulkan 1.1 with SPIR-V 1.4 binary.
+
+  // Vulkan 1.1 with VK_KHR_spirv_1_4, i.e. SPIR-V 1.4 binary.
+  SPV_ENV_VULKAN_1_1_SPIRV_1_4,
+
   SPV_ENV_UNIVERSAL_1_5,  // SPIR-V 1.5 latest revision, no other restrictions.
   SPV_ENV_VULKAN_1_2,     // Vulkan 1.2 latest revision.
 } spv_target_env;
@@ -473,7 +493,24 @@
 // false and sets *env to SPV_ENV_UNIVERSAL_1_0.
 SPIRV_TOOLS_EXPORT bool spvParseTargetEnv(const char* s, spv_target_env* env);
 
-// Creates a context object.  Returns null if env is invalid.
+// Determines the target env value with the least features but which enables
+// the given Vulkan and SPIR-V versions. If such a target is supported, returns
+// true and writes the value to |env|, otherwise returns false.
+//
+// The Vulkan version is given as an unsigned 32-bit number as specified in
+// Vulkan section "29.2.1 Version Numbers": the major version number appears
+// in bits 22 to 21, and the minor version is in bits 12 to 21.  The SPIR-V
+// version is given in the SPIR-V version header word: major version in bits
+// 16 to 23, and minor version in bits 8 to 15.
+SPIRV_TOOLS_EXPORT bool spvParseVulkanEnv(uint32_t vulkan_ver,
+                                          uint32_t spirv_ver,
+                                          spv_target_env* env);
+
+// Creates a context object for most of the SPIRV-Tools API.
+// Returns null if env is invalid.
+//
+// See specific API calls for how the target environment is interpeted
+// (particularly assembly and validation).
 SPIRV_TOOLS_EXPORT spv_context spvContextCreate(spv_target_env env);
 
 // Destroys the given context object.
@@ -654,6 +691,8 @@
 // be stored into *binary. Any error will be written into *diagnostic if
 // diagnostic is non-null, otherwise the context's message consumer will be
 // used. The generated binary is independent of the context and may outlive it.
+// The SPIR-V binary version is set to the highest version of SPIR-V supported
+// by the context's target environment.
 SPIRV_TOOLS_EXPORT spv_result_t spvTextToBinary(const spv_const_context context,
                                                 const char* text,
                                                 const size_t length,
@@ -691,6 +730,12 @@
 // Validates a SPIR-V binary for correctness. Any errors will be written into
 // *diagnostic if diagnostic is non-null, otherwise the context's message
 // consumer will be used.
+//
+// Validate for SPIR-V spec rules for the SPIR-V version named in the
+// binary's header (at word offset 1).  Additionally, if the context target
+// environment is a client API (such as Vulkan 1.1), then validate for that
+// client API version, to the extent that it is verifiable from data in the
+// binary itself.
 SPIRV_TOOLS_EXPORT spv_result_t spvValidate(const spv_const_context context,
                                             const spv_const_binary binary,
                                             spv_diagnostic* diagnostic);
@@ -698,6 +743,12 @@
 // Validates a SPIR-V binary for correctness. Uses the provided Validator
 // options. Any errors will be written into *diagnostic if diagnostic is
 // non-null, otherwise the context's message consumer will be used.
+//
+// Validate for SPIR-V spec rules for the SPIR-V version named in the
+// binary's header (at word offset 1).  Additionally, if the context target
+// environment is a client API (such as Vulkan 1.1), then validate for that
+// client API version, to the extent that it is verifiable from data in the
+// binary itself, or in the validator options.
 SPIRV_TOOLS_EXPORT spv_result_t spvValidateWithOptions(
     const spv_const_context context, const spv_const_validator_options options,
     const spv_const_binary binary, spv_diagnostic* diagnostic);
@@ -723,6 +774,9 @@
 SPIRV_TOOLS_EXPORT spv_result_t
 spvDiagnosticPrint(const spv_diagnostic diagnostic);
 
+// Gets the name of an instruction, without the "Op" prefix.
+SPIRV_TOOLS_EXPORT const char* spvOpcodeString(const uint32_t opcode);
+
 // The binary parser interface.
 
 // A pointer to a function that accepts a parsed SPIR-V header.
diff --git a/include/spirv-tools/libspirv.hpp b/include/spirv-tools/libspirv.hpp
index ceadef8..5e1819e 100644
--- a/include/spirv-tools/libspirv.hpp
+++ b/include/spirv-tools/libspirv.hpp
@@ -36,6 +36,9 @@
  public:
   // Constructs a context targeting the given environment |env|.
   //
+  // See specific API calls for how the target environment is interpeted
+  // (particularly assembly and validation).
+  //
   // The constructed instance will have an empty message consumer, which just
   // ignores all messages from the library. Use SetMessageConsumer() to supply
   // one if messages are of concern.
@@ -279,16 +282,20 @@
   // Assembles the given assembly |text| and writes the result to |binary|.
   // Returns true on successful assembling. |binary| will be kept untouched if
   // assembling is unsuccessful.
+  // The SPIR-V binary version is set to the highest version of SPIR-V supported
+  // by the target environment with which this SpirvTools object was created.
   bool Assemble(const std::string& text, std::vector<uint32_t>* binary,
                 uint32_t options = kDefaultAssembleOption) const;
   // |text_size| specifies the number of bytes in |text|. A terminating null
   // character is not required to present in |text| as long as |text| is valid.
+  // The SPIR-V binary version is set to the highest version of SPIR-V supported
+  // by the target environment with which this SpirvTools object was created.
   bool Assemble(const char* text, size_t text_size,
                 std::vector<uint32_t>* binary,
                 uint32_t options = kDefaultAssembleOption) const;
 
   // Disassembles the given SPIR-V |binary| with the given |options| and writes
-  // the assembly to |text|. Returns ture on successful disassembling. |text|
+  // the assembly to |text|. Returns true on successful disassembling. |text|
   // will be kept untouched if diassembling is unsuccessful.
   bool Disassemble(const std::vector<uint32_t>& binary, std::string* text,
                    uint32_t options = kDefaultDisassembleOption) const;
@@ -300,10 +307,26 @@
   // Validates the given SPIR-V |binary|. Returns true if no issues are found.
   // Otherwise, returns false and communicates issues via the message consumer
   // registered.
+  // Validates for SPIR-V spec rules for the SPIR-V version named in the
+  // binary's header (at word offset 1).  Additionally, if the target
+  // environment is a client API (such as Vulkan 1.1), then validate for that
+  // client API version, to the extent that it is verifiable from data in the
+  // binary itself.
   bool Validate(const std::vector<uint32_t>& binary) const;
+  // Like the previous overload, but provides the binary as a pointer and size:
   // |binary_size| specifies the number of words in |binary|.
+  // Validates for SPIR-V spec rules for the SPIR-V version named in the
+  // binary's header (at word offset 1).  Additionally, if the target
+  // environment is a client API (such as Vulkan 1.1), then validate for that
+  // client API version, to the extent that it is verifiable from data in the
+  // binary itself.
   bool Validate(const uint32_t* binary, size_t binary_size) const;
   // Like the previous overload, but takes an options object.
+  // Validates for SPIR-V spec rules for the SPIR-V version named in the
+  // binary's header (at word offset 1).  Additionally, if the target
+  // environment is a client API (such as Vulkan 1.1), then validate for that
+  // client API version, to the extent that it is verifiable from data in the
+  // binary itself, or in the validator options.
   bool Validate(const uint32_t* binary, size_t binary_size,
                 spv_validator_options options) const;
 
diff --git a/include/spirv-tools/optimizer.hpp b/include/spirv-tools/optimizer.hpp
index ba8cbaf..b904923 100644
--- a/include/spirv-tools/optimizer.hpp
+++ b/include/spirv-tools/optimizer.hpp
@@ -65,9 +65,9 @@
   // Constructs an instance with the given target |env|, which is used to decode
   // the binaries to be optimized later.
   //
-  // The constructed instance will have an empty message consumer, which just
-  // ignores all messages from the library. Use SetMessageConsumer() to supply
-  // one if messages are of concern.
+  // The instance will have an empty message consumer, which ignores all
+  // messages from the library. Use SetMessageConsumer() to supply a consumer
+  // if messages are of concern.
   //
   // For collections of passes that are meant to transform the input into
   // another execution environment, then the source environment should be
@@ -164,17 +164,26 @@
   bool FlagHasValidForm(const std::string& flag) const;
 
   // Allows changing, after creation time, the target environment to be
-  // optimized for.  Should be called before calling Run().
+  // optimized for and validated.  Should be called before calling Run().
   void SetTargetEnv(const spv_target_env env);
 
   // Optimizes the given SPIR-V module |original_binary| and writes the
-  // optimized binary into |optimized_binary|.
+  // optimized binary into |optimized_binary|. The optimized binary uses
+  // the same SPIR-V version as the original binary.
+  //
   // Returns true on successful optimization, whether or not the module is
   // modified. Returns false if |original_binary| fails to validate or if errors
   // occur when processing |original_binary| using any of the registered passes.
   // In that case, no further passes are executed and the contents in
   // |optimized_binary| may be invalid.
   //
+  // By default, the binary is validated before any transforms are performed,
+  // and optionally after each transform.  Validation uses SPIR-V spec rules
+  // for the SPIR-V version named in the binary's header (at word offset 1).
+  // Additionally, if the target environment is a client API (such as
+  // Vulkan 1.1), then validate for that client API version, to the extent
+  // that it is verifiable from data in the binary itself.
+  //
   // It's allowed to alias |original_binary| to the start of |optimized_binary|.
   bool Run(const uint32_t* original_binary, size_t original_binary_size,
            std::vector<uint32_t>* optimized_binary) const;
@@ -190,6 +199,14 @@
 
   // Same as above, except it takes an options object.  See the documentation
   // for |OptimizerOptions| to see which options can be set.
+  //
+  // By default, the binary is validated before any transforms are performed,
+  // and optionally after each transform.  Validation uses SPIR-V spec rules
+  // for the SPIR-V version named in the binary's header (at word offset 1).
+  // Additionally, if the target environment is a client API (such as
+  // Vulkan 1.1), then validate for that client API version, to the extent
+  // that it is verifiable from data in the binary itself, or from the
+  // validator options set on the optimizer options.
   bool Run(const uint32_t* original_binary, const size_t original_binary_size,
            std::vector<uint32_t>* optimized_binary,
            const spv_optimizer_options opt_options) const;
@@ -774,6 +791,18 @@
                                                  uint32_t shader_id,
                                                  uint32_t version = 2);
 
+// Create a pass to instrument OpDebugPrintf instructions.
+// This pass replaces all OpDebugPrintf instructions with instructions to write
+// a record containing the string id and the all specified values into a special
+// printf output buffer (if space allows). This pass is designed to support
+// the printf validation in the Vulkan validation layers.
+//
+// The instrumentation will write buffers in debug descriptor set |desc_set|.
+// It will write |shader_id| in each output record to identify the shader
+// module which generated the record.
+Optimizer::PassToken CreateInstDebugPrintfPass(uint32_t desc_set,
+                                               uint32_t shader_id);
+
 // Create a pass to upgrade to the VulkanKHR memory model.
 // This pass upgrades the Logical GLSL450 memory model to Logical VulkanKHR.
 // Additionally, it modifies memory, image, atomic and barrier operations to
diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt
index 4e7e10c..708ca84 100644
--- a/source/CMakeLists.txt
+++ b/source/CMakeLists.txt
@@ -126,13 +126,11 @@
 endmacro(spvtools_vendor_tables)
 
 macro(spvtools_extinst_lang_headers NAME GRAMMAR_FILE)
-  set(OUTBASE ${spirv-tools_BINARY_DIR}/${NAME})
-  set(OUT_H ${OUTBASE}.h)
+  set(OUT_H ${spirv-tools_BINARY_DIR}/${NAME}.h)
   add_custom_command(OUTPUT ${OUT_H}
     COMMAND ${PYTHON_EXECUTABLE} ${LANG_HEADER_PROCESSING_SCRIPT}
-      --extinst-name=${NAME}
       --extinst-grammar=${GRAMMAR_FILE}
-      --extinst-output-base=${OUTBASE}
+      --extinst-output-path=${OUT_H}
     DEPENDS ${LANG_HEADER_PROCESSING_SCRIPT} ${GRAMMAR_FILE}
     COMMENT "Generate language specific header for ${NAME}.")
   add_custom_target(spirv-tools-header-${NAME} DEPENDS ${OUT_H})
diff --git a/source/binary.cpp b/source/binary.cpp
index 0463061..f16bf52 100644
--- a/source/binary.cpp
+++ b/source/binary.cpp
@@ -1,4 +1,6 @@
-// Copyright (c) 2015-2016 The Khronos Group Inc.
+// Copyright (c) 2015-2020 The Khronos Group 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.
@@ -633,6 +635,10 @@
     case SPV_OPERAND_TYPE_GROUP_OPERATION:
     case SPV_OPERAND_TYPE_KERNEL_ENQ_FLAGS:
     case SPV_OPERAND_TYPE_KERNEL_PROFILING_INFO:
+    case SPV_OPERAND_TYPE_RAY_FLAGS:
+    case SPV_OPERAND_TYPE_RAY_QUERY_INTERSECTION:
+    case SPV_OPERAND_TYPE_RAY_QUERY_COMMITTED_INTERSECTION_TYPE:
+    case SPV_OPERAND_TYPE_RAY_QUERY_CANDIDATE_INTERSECTION_TYPE:
     case SPV_OPERAND_TYPE_DEBUG_BASE_TYPE_ATTRIBUTE_ENCODING:
     case SPV_OPERAND_TYPE_DEBUG_COMPOSITE_TYPE:
     case SPV_OPERAND_TYPE_DEBUG_TYPE_QUALIFIER:
diff --git a/source/disassemble.cpp b/source/disassemble.cpp
index 4b3972b..af30ce0 100644
--- a/source/disassemble.cpp
+++ b/source/disassemble.cpp
@@ -1,4 +1,6 @@
-// Copyright (c) 2015-2016 The Khronos Group Inc.
+// Copyright (c) 2015-2020 The Khronos Group 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.
@@ -277,6 +279,10 @@
     case SPV_OPERAND_TYPE_GROUP_OPERATION:
     case SPV_OPERAND_TYPE_KERNEL_ENQ_FLAGS:
     case SPV_OPERAND_TYPE_KERNEL_PROFILING_INFO:
+    case SPV_OPERAND_TYPE_RAY_FLAGS:
+    case SPV_OPERAND_TYPE_RAY_QUERY_INTERSECTION:
+    case SPV_OPERAND_TYPE_RAY_QUERY_COMMITTED_INTERSECTION_TYPE:
+    case SPV_OPERAND_TYPE_RAY_QUERY_CANDIDATE_INTERSECTION_TYPE:
     case SPV_OPERAND_TYPE_DEBUG_BASE_TYPE_ATTRIBUTE_ENCODING:
     case SPV_OPERAND_TYPE_DEBUG_COMPOSITE_TYPE:
     case SPV_OPERAND_TYPE_DEBUG_TYPE_QUALIFIER:
diff --git a/source/enum_set.h b/source/enum_set.h
index 2e7046d..d4d31e3 100644
--- a/source/enum_set.h
+++ b/source/enum_set.h
@@ -93,6 +93,10 @@
   // enum value is already in the set.
   void Add(EnumType c) { AddWord(ToWord(c)); }
 
+  // Removes the given enum value from the set.  This has no effect if the
+  // enum value is not in the set.
+  void Remove(EnumType c) { RemoveWord(ToWord(c)); }
+
   // Returns true if this enum value is in the set.
   bool Contains(EnumType c) const { return ContainsWord(ToWord(c)); }
 
@@ -141,6 +145,17 @@
     }
   }
 
+  // Removes the given enum value (as a 32-bit word) from the set.  This has no
+  // effect if the enum value is not in the set.
+  void RemoveWord(uint32_t word) {
+    if (auto new_bits = AsMask(word)) {
+      mask_ &= ~new_bits;
+    } else {
+      auto itr = Overflow().find(word);
+      if (itr != Overflow().end()) Overflow().erase(itr);
+    }
+  }
+
   // Returns true if the enum represented as a 32-bit word is in the set.
   bool ContainsWord(uint32_t word) const {
     // We shouldn't call Overflow() since this is a const method.
diff --git a/source/ext_inst.cpp b/source/ext_inst.cpp
index 53ebb3c..e69c3c9 100644
--- a/source/ext_inst.cpp
+++ b/source/ext_inst.cpp
@@ -138,6 +138,14 @@
   return false;
 }
 
+bool spvExtInstIsDebugInfo(const spv_ext_inst_type_t type) {
+  if (type == SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100 ||
+      type == SPV_EXT_INST_TYPE_DEBUGINFO) {
+    return true;
+  }
+  return false;
+}
+
 spv_result_t spvExtInstTableNameLookup(const spv_ext_inst_table table,
                                        const spv_ext_inst_type_t type,
                                        const char* name,
diff --git a/source/ext_inst.h b/source/ext_inst.h
index b42d82b..aff6e30 100644
--- a/source/ext_inst.h
+++ b/source/ext_inst.h
@@ -24,6 +24,9 @@
 // Returns true if the extended instruction set is non-semantic
 bool spvExtInstIsNonSemantic(const spv_ext_inst_type_t type);
 
+// Returns true if the extended instruction set is debug info
+bool spvExtInstIsDebugInfo(const spv_ext_inst_type_t type);
+
 // Finds the named extented instruction of the given type in the given extended
 // instruction table. On success, returns SPV_SUCCESS and writes a handle of
 // the instruction entry into *entry.
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index ea5e216..0f0581f 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -29,6 +29,7 @@
   )
 
   set(SPIRV_TOOLS_FUZZ_SOURCES
+        call_graph.h
         data_descriptor.h
         equivalence_relation.h
         fact_manager.h
@@ -36,10 +37,18 @@
         fuzzer.h
         fuzzer_context.h
         fuzzer_pass.h
+        fuzzer_pass_add_access_chains.h
+        fuzzer_pass_add_composite_types.h
         fuzzer_pass_add_dead_blocks.h
         fuzzer_pass_add_dead_breaks.h
         fuzzer_pass_add_dead_continues.h
+        fuzzer_pass_add_equation_instructions.h
+        fuzzer_pass_add_function_calls.h
+        fuzzer_pass_add_global_variables.h
+        fuzzer_pass_add_loads.h
+        fuzzer_pass_add_local_variables.h
         fuzzer_pass_add_no_contraction_decorations.h
+        fuzzer_pass_add_stores.h
         fuzzer_pass_add_useful_constructs.h
         fuzzer_pass_adjust_function_controls.h
         fuzzer_pass_adjust_loop_controls.h
@@ -53,7 +62,10 @@
         fuzzer_pass_obfuscate_constants.h
         fuzzer_pass_outline_functions.h
         fuzzer_pass_permute_blocks.h
+        fuzzer_pass_permute_function_parameters.h
         fuzzer_pass_split_blocks.h
+        fuzzer_pass_swap_commutable_operands.h
+        fuzzer_pass_toggle_access_chain_instruction.h
         fuzzer_util.h
         id_use_descriptor.h
         instruction_descriptor.h
@@ -64,8 +76,10 @@
         replayer.h
         shrinker.h
         transformation.h
+        transformation_access_chain.h
         transformation_add_constant_boolean.h
         transformation_add_constant_composite.h
+        transformation_add_constant_null.h
         transformation_add_constant_scalar.h
         transformation_add_dead_block.h
         transformation_add_dead_break.h
@@ -73,6 +87,7 @@
         transformation_add_function.h
         transformation_add_global_undef.h
         transformation_add_global_variable.h
+        transformation_add_local_variable.h
         transformation_add_no_contraction_decoration.h
         transformation_add_type_array.h
         transformation_add_type_boolean.h
@@ -85,10 +100,15 @@
         transformation_add_type_vector.h
         transformation_composite_construct.h
         transformation_composite_extract.h
+        transformation_context.h
         transformation_copy_object.h
+        transformation_equation_instruction.h
+        transformation_function_call.h
+        transformation_load.h
         transformation_merge_blocks.h
         transformation_move_block_down.h
         transformation_outline_function.h
+        transformation_permute_function_parameters.h
         transformation_replace_boolean_constant_with_constant_binary.h
         transformation_replace_constant_with_uniform.h
         transformation_replace_id_with_synonym.h
@@ -97,20 +117,32 @@
         transformation_set_memory_operands_mask.h
         transformation_set_selection_control.h
         transformation_split_block.h
+        transformation_store.h
+        transformation_swap_commutable_operands.h
+        transformation_toggle_access_chain_instruction.h
         transformation_vector_shuffle.h
         uniform_buffer_element_descriptor.h
         ${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.h
 
+        call_graph.cpp
         data_descriptor.cpp
         fact_manager.cpp
         force_render_red.cpp
         fuzzer.cpp
         fuzzer_context.cpp
         fuzzer_pass.cpp
+        fuzzer_pass_add_access_chains.cpp
+        fuzzer_pass_add_composite_types.cpp
         fuzzer_pass_add_dead_blocks.cpp
         fuzzer_pass_add_dead_breaks.cpp
         fuzzer_pass_add_dead_continues.cpp
+        fuzzer_pass_add_equation_instructions.cpp
+        fuzzer_pass_add_function_calls.cpp
+        fuzzer_pass_add_global_variables.cpp
+        fuzzer_pass_add_loads.cpp
+        fuzzer_pass_add_local_variables.cpp
         fuzzer_pass_add_no_contraction_decorations.cpp
+        fuzzer_pass_add_stores.cpp
         fuzzer_pass_add_useful_constructs.cpp
         fuzzer_pass_adjust_function_controls.cpp
         fuzzer_pass_adjust_loop_controls.cpp
@@ -124,7 +156,10 @@
         fuzzer_pass_obfuscate_constants.cpp
         fuzzer_pass_outline_functions.cpp
         fuzzer_pass_permute_blocks.cpp
+        fuzzer_pass_permute_function_parameters.cpp
         fuzzer_pass_split_blocks.cpp
+        fuzzer_pass_swap_commutable_operands.cpp
+        fuzzer_pass_toggle_access_chain_instruction.cpp
         fuzzer_util.cpp
         id_use_descriptor.cpp
         instruction_descriptor.cpp
@@ -134,8 +169,10 @@
         replayer.cpp
         shrinker.cpp
         transformation.cpp
+        transformation_access_chain.cpp
         transformation_add_constant_boolean.cpp
         transformation_add_constant_composite.cpp
+        transformation_add_constant_null.cpp
         transformation_add_constant_scalar.cpp
         transformation_add_dead_block.cpp
         transformation_add_dead_break.cpp
@@ -143,6 +180,7 @@
         transformation_add_function.cpp
         transformation_add_global_undef.cpp
         transformation_add_global_variable.cpp
+        transformation_add_local_variable.cpp
         transformation_add_no_contraction_decoration.cpp
         transformation_add_type_array.cpp
         transformation_add_type_boolean.cpp
@@ -155,10 +193,15 @@
         transformation_add_type_vector.cpp
         transformation_composite_construct.cpp
         transformation_composite_extract.cpp
+        transformation_context.cpp
         transformation_copy_object.cpp
+        transformation_equation_instruction.cpp
+        transformation_function_call.cpp
+        transformation_load.cpp
         transformation_merge_blocks.cpp
         transformation_move_block_down.cpp
         transformation_outline_function.cpp
+        transformation_permute_function_parameters.cpp
         transformation_replace_boolean_constant_with_constant_binary.cpp
         transformation_replace_constant_with_uniform.cpp
         transformation_replace_id_with_synonym.cpp
@@ -167,6 +210,9 @@
         transformation_set_memory_operands_mask.cpp
         transformation_set_selection_control.cpp
         transformation_split_block.cpp
+        transformation_store.cpp
+        transformation_swap_commutable_operands.cpp
+        transformation_toggle_access_chain_instruction.cpp
         transformation_vector_shuffle.cpp
         uniform_buffer_element_descriptor.cpp
         ${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.cc
diff --git a/source/fuzz/call_graph.cpp b/source/fuzz/call_graph.cpp
new file mode 100644
index 0000000..15416fe
--- /dev/null
+++ b/source/fuzz/call_graph.cpp
@@ -0,0 +1,81 @@
+// 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 <queue>
+
+namespace spvtools {
+namespace fuzz {
+
+CallGraph::CallGraph(opt::IRContext* context) {
+  // Initialize function in-degree and call graph edges to 0 and empty.
+  for (auto& function : *context->module()) {
+    function_in_degree_[function.result_id()] = 0;
+    call_graph_edges_[function.result_id()] = std::set<uint32_t>();
+  }
+
+  // Consider every function.
+  for (auto& function : *context->module()) {
+    // Avoid considering the same callee of this function multiple times by
+    // recording known callees.
+    std::set<uint32_t> known_callees;
+    // Consider every function call instruction in every block.
+    for (auto& block : function) {
+      for (auto& instruction : block) {
+        if (instruction.opcode() != SpvOpFunctionCall) {
+          continue;
+        }
+        // Get the id of the function being called.
+        uint32_t callee = instruction.GetSingleWordInOperand(0);
+        if (known_callees.count(callee)) {
+          // We have already considered a call to this function - ignore it.
+          continue;
+        }
+        // Increase the callee's in-degree and add an edge to the call graph.
+        function_in_degree_[callee]++;
+        call_graph_edges_[function.result_id()].insert(callee);
+        // Mark the callee as 'known'.
+        known_callees.insert(callee);
+      }
+    }
+  }
+}
+
+void CallGraph::PushDirectCallees(uint32_t function_id,
+                                  std::queue<uint32_t>* queue) const {
+  for (auto callee : GetDirectCallees(function_id)) {
+    queue->push(callee);
+  }
+}
+
+std::set<uint32_t> CallGraph::GetIndirectCallees(uint32_t function_id) const {
+  std::set<uint32_t> result;
+  std::queue<uint32_t> queue;
+  PushDirectCallees(function_id, &queue);
+
+  while (!queue.empty()) {
+    auto next = queue.front();
+    queue.pop();
+    if (result.count(next)) {
+      continue;
+    }
+    result.insert(next);
+    PushDirectCallees(next, &queue);
+  }
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/call_graph.h b/source/fuzz/call_graph.h
new file mode 100644
index 0000000..14cd23b
--- /dev/null
+++ b/source/fuzz/call_graph.h
@@ -0,0 +1,62 @@
+// 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_CALL_GRAPH_H_
+#define SOURCE_FUZZ_CALL_GRAPH_H_
+
+#include <map>
+#include <set>
+
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Represents the acyclic call graph of a SPIR-V module.
+class CallGraph {
+ public:
+  // Creates a call graph corresponding to the given SPIR-V module.
+  explicit CallGraph(opt::IRContext* context);
+
+  // Returns a mapping from each function to its number of distinct callers.
+  const std::map<uint32_t, uint32_t>& GetFunctionInDegree() const {
+    return function_in_degree_;
+  }
+
+  // Returns the ids of the functions that |function_id| directly invokes.
+  const std::set<uint32_t>& GetDirectCallees(uint32_t function_id) const {
+    return call_graph_edges_.at(function_id);
+  }
+
+  // Returns the ids of the functions that |function_id| directly or indirectly
+  // invokes.
+  std::set<uint32_t> GetIndirectCallees(uint32_t function_id) const;
+
+ private:
+  // Pushes the direct callees of |function_id| on to |queue|.
+  void PushDirectCallees(uint32_t function_id,
+                         std::queue<uint32_t>* queue) const;
+
+  // Maps each function id to the ids of its immediate callees.
+  std::map<uint32_t, std::set<uint32_t>> call_graph_edges_;
+
+  // For each function id, stores the number of distinct functions that call
+  // the function.
+  std::map<uint32_t, uint32_t> function_in_degree_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_CALL_GRAPH_H_
diff --git a/source/fuzz/equivalence_relation.h b/source/fuzz/equivalence_relation.h
index 046536f..6d0b63e 100644
--- a/source/fuzz/equivalence_relation.h
+++ b/source/fuzz/equivalence_relation.h
@@ -68,27 +68,14 @@
 template <typename T, typename PointerHashT, typename PointerEqualsT>
 class EquivalenceRelation {
  public:
-  // Merges the equivalence classes associated with |value1| and |value2|.
-  // If any of these values was not previously in the equivalence relation, it
-  // is added to the pool of values known to be in the relation.
+  // Requires that |value1| and |value2| are already registered in the
+  // equivalence relation.  Merges the equivalence classes associated with
+  // |value1| and |value2|.
   void MakeEquivalent(const T& value1, const T& value2) {
-    // Register each value if necessary.
-    for (auto value : {value1, value2}) {
-      if (!Exists(value)) {
-        // Register the value in the equivalence relation.  This relies on
-        // T having a copy constructor.
-        auto unique_pointer_to_value = MakeUnique<T>(value);
-        auto pointer_to_value = unique_pointer_to_value.get();
-        owned_values_.push_back(std::move(unique_pointer_to_value));
-        value_set_.insert(pointer_to_value);
-
-        // Initially say that the value is its own parent and that it has no
-        // children.
-        assert(pointer_to_value && "Representatives should never be null.");
-        parent_[pointer_to_value] = pointer_to_value;
-        children_[pointer_to_value] = std::vector<const T*>();
-      }
-    }
+    assert(Exists(value1) &&
+           "Precondition: value1 must already be registered.");
+    assert(Exists(value2) &&
+           "Precondition: value2 must already be registered.");
 
     // Look up canonical pointers to each of the values in the value pool.
     const T* value1_ptr = *value_set_.find(&value1);
@@ -112,6 +99,27 @@
     }
   }
 
+  // Requires that |value| is not known to the equivalence relation. Registers
+  // it in its own equivalence class and returns a pointer to the equivalence
+  // class representative.
+  const T* Register(const T& value) {
+    assert(!Exists(value));
+
+    // This relies on T having a copy constructor.
+    auto unique_pointer_to_value = MakeUnique<T>(value);
+    auto pointer_to_value = unique_pointer_to_value.get();
+    owned_values_.push_back(std::move(unique_pointer_to_value));
+    value_set_.insert(pointer_to_value);
+
+    // Initially say that the value is its own parent and that it has no
+    // children.
+    assert(pointer_to_value && "Representatives should never be null.");
+    parent_[pointer_to_value] = pointer_to_value;
+    children_[pointer_to_value] = std::vector<const T*>();
+
+    return pointer_to_value;
+  }
+
   // Returns exactly one representative per equivalence class.
   std::vector<const T*> GetEquivalenceClassRepresentatives() const {
     std::vector<const T*> result;
@@ -168,7 +176,6 @@
     return value_set_.find(&value) != value_set_.end();
   }
 
- private:
   // Returns the representative of the equivalence class of |value|, which must
   // already be known to the equivalence relation.  This is the 'Find' operation
   // in a classic union-find data structure.
@@ -207,6 +214,7 @@
     return result;
   }
 
+ private:
   // Maps every value to a parent.  The representative of an equivalence class
   // is its own parent.  A value's representative can be found by walking its
   // chain of ancestors.
diff --git a/source/fuzz/fact_manager.cpp b/source/fuzz/fact_manager.cpp
index 5c0814a..7901341 100644
--- a/source/fuzz/fact_manager.cpp
+++ b/source/fuzz/fact_manager.cpp
@@ -28,24 +28,13 @@
 
 namespace {
 
-std::string ToString(const protobufs::Fact& fact) {
-  assert(fact.fact_case() == protobufs::Fact::kConstantUniformFact &&
-         "Right now this is the only fact we know how to stringify.");
+std::string ToString(const protobufs::FactConstantUniform& fact) {
   std::stringstream stream;
-  stream << "("
-         << fact.constant_uniform_fact()
-                .uniform_buffer_element_descriptor()
-                .descriptor_set()
-         << ", "
-         << fact.constant_uniform_fact()
-                .uniform_buffer_element_descriptor()
-                .binding()
-         << ")[";
+  stream << "(" << fact.uniform_buffer_element_descriptor().descriptor_set()
+         << ", " << fact.uniform_buffer_element_descriptor().binding() << ")[";
 
   bool first = true;
-  for (auto index : fact.constant_uniform_fact()
-                        .uniform_buffer_element_descriptor()
-                        .index()) {
+  for (auto index : fact.uniform_buffer_element_descriptor().index()) {
     if (first) {
       first = false;
     } else {
@@ -57,7 +46,7 @@
   stream << "] == [";
 
   first = true;
-  for (auto constant_word : fact.constant_uniform_fact().constant_word()) {
+  for (auto constant_word : fact.constant_word()) {
     if (first) {
       first = false;
     } else {
@@ -70,6 +59,36 @@
   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
 
 //=======================
@@ -331,16 +350,71 @@
 //==============================
 
 //==============================
-// Data synonym 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.
-class FactManager::DataSynonymFacts {
+// 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,
       opt::IRContext* context) const;
@@ -355,11 +429,12 @@
                     opt::IRContext* context) const;
 
  private:
-  // Adds |fact| to the set of managed facts, and recurses into sub-components
-  // of the data descriptors referenced in |fact|, if they are composites, to
+  // 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 AddFactRecursive(const protobufs::FactDataSynonym& fact,
-                        opt::IRContext* context);
+  void AddDataSynonymFactRecursive(const protobufs::DataDescriptor& dd1,
+                                   const protobufs::DataDescriptor& dd2,
+                                   opt::IRContext* context);
 
   // 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
@@ -370,12 +445,32 @@
   // compute the closure only when a data synonym fact is *queried*.
   void ComputeClosureOfFacts(opt::IRContext* context) const;
 
+  // Records the fact that |dd1| and |dd2| are equivalent, and merges the sets
+  // of equations that are known about them.
+  //
+  // This is a const method, despite the fact that it mutates the (mutable)
+  // set of facts about data descriptors because it is invoked in a lazy fashion
+  // when querying facts.
+  void MakeEquivalent(const protobufs::DataDescriptor& dd1,
+                      const protobufs::DataDescriptor& dd2) const;
+
   // Returns true if and only if |dd1| and |dd2| are valid data descriptors
-  // whose associated data have the same type.
+  // whose associated data have the same type (modulo integer signedness).
   bool DataDescriptorsAreWellFormedAndComparable(
       opt::IRContext* context, const protobufs::DataDescriptor& dd1,
       const protobufs::DataDescriptor& dd2) 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.
   //
@@ -396,31 +491,249 @@
   // whether a new fact has been added since the last time such a computation
   // was performed.
   //
-  // It is mutable so faciliate having const methods, that provide answers to
+  // It is mutable to facilitate having const methods, that provide answers to
   // questions about data synonym facts, triggering closure computation on
   // demand.
-  mutable bool closure_computation_required = false;
+  mutable 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.
+  //
+  // It is mutable because a closure computation can be triggered from a const
+  // method, and when a closure computation detects that two data descriptors
+  // are equivalent it is necessary to merge the equation facts for those data
+  // descriptors.
+  mutable std::unordered_map<
+      const protobufs::DataDescriptor*,
+      std::unordered_set<Operation, OperationHash, OperationEquals>>
+      id_equations_;
 };
 
-void FactManager::DataSynonymFacts::AddFact(
+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.
-  AddFactRecursive(fact, context);
+  AddDataSynonymFactRecursive(fact.data1(), fact.data2(), context);
 }
 
-void FactManager::DataSynonymFacts::AddFactRecursive(
-    const protobufs::FactDataSynonym& fact, opt::IRContext* context) {
-  assert(DataDescriptorsAreWellFormedAndComparable(context, fact.data1(),
-                                                   fact.data2()));
+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);
+}
+
+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,
+         std::unordered_set<Operation, OperationHash, OperationEquals>()});
+  }
+
+  {
+    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 SpvOpIAdd: {
+      // Equation form: "a = b + c"
+      {
+        auto existing_first_operand_equations = id_equations_.find(rhs_dds[0]);
+        if (existing_first_operand_equations != id_equations_.end()) {
+          for (auto equation : existing_first_operand_equations->second) {
+            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);
+              }
+            }
+          }
+        }
+      }
+      {
+        auto existing_second_operand_equations = id_equations_.find(rhs_dds[1]);
+        if (existing_second_operand_equations != id_equations_.end()) {
+          for (auto equation : existing_second_operand_equations->second) {
+            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"
+      {
+        auto existing_first_operand_equations = id_equations_.find(rhs_dds[0]);
+        if (existing_first_operand_equations != id_equations_.end()) {
+          for (auto equation : existing_first_operand_equations->second) {
+            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);
+              }
+            }
+          }
+        }
+      }
+
+      {
+        auto existing_second_operand_equations = id_equations_.find(rhs_dds[1]);
+        if (existing_second_operand_equations != id_equations_.end()) {
+          for (auto equation : existing_second_operand_equations->second) {
+            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"
+      auto existing_equations = id_equations_.find(rhs_dds[0]);
+      if (existing_equations != id_equations_.end()) {
+        for (auto equation : existing_equations->second) {
+          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.
-  synonymous_.MakeEquivalent(fact.data1(), fact.data2());
-  // 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; it will be performed next time a method answering
-  // a data synonym fact-related question is invoked.
-  closure_computation_required = true;
+  MakeEquivalent(dd1, dd2);
 
   // We now check whether this is a synonym about composite objects.  If it is,
   // we can recursively add synonym facts about their associated sub-components.
@@ -428,9 +741,8 @@
   // 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(fact.data1().object())->type_id(),
-      fact.data1().index());
+      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 &&
@@ -460,21 +772,19 @@
   //   obj_1[a_1, ..., a_m, i] == obj_2[b_1, ..., b_n, i]
   for (uint32_t i = 0; i < num_composite_elements; i++) {
     std::vector<uint32_t> extended_indices1 =
-        fuzzerutil::RepeatedFieldToVector(fact.data1().index());
+        fuzzerutil::RepeatedFieldToVector(dd1.index());
     extended_indices1.push_back(i);
     std::vector<uint32_t> extended_indices2 =
-        fuzzerutil::RepeatedFieldToVector(fact.data2().index());
+        fuzzerutil::RepeatedFieldToVector(dd2.index());
     extended_indices2.push_back(i);
-    protobufs::FactDataSynonym extended_data_synonym_fact;
-    *extended_data_synonym_fact.mutable_data1() =
-        MakeDataDescriptor(fact.data1().object(), std::move(extended_indices1));
-    *extended_data_synonym_fact.mutable_data2() =
-        MakeDataDescriptor(fact.data2().object(), std::move(extended_indices2));
-    AddFactRecursive(extended_data_synonym_fact, context);
+    AddDataSynonymFactRecursive(
+        MakeDataDescriptor(dd1.object(), std::move(extended_indices1)),
+        MakeDataDescriptor(dd2.object(), std::move(extended_indices2)),
+        context);
   }
 }
 
-void FactManager::DataSynonymFacts::ComputeClosureOfFacts(
+void FactManager::DataSynonymAndIdEquationFacts::ComputeClosureOfFacts(
     opt::IRContext* context) const {
   // 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
@@ -551,10 +861,10 @@
 
   // 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) {
+  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;
+    closure_computation_required_ = false;
 
     // Consider each class in the equivalence relation.
     for (auto representative :
@@ -735,10 +1045,7 @@
             // synonymous.
             assert(DataDescriptorsAreWellFormedAndComparable(
                 context, dd1_prefix, dd2_prefix));
-            synonymous_.MakeEquivalent(dd1_prefix, dd2_prefix);
-            // As we have added a new synonym fact, we might benefit from doing
-            // another pass over the equivalence relation.
-            closure_computation_required = true;
+            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.
@@ -750,20 +1057,134 @@
   }
 }
 
-bool FactManager::DataSynonymFacts::DataDescriptorsAreWellFormedAndComparable(
-    opt::IRContext* context, const protobufs::DataDescriptor& dd1,
+void FactManager::DataSynonymAndIdEquationFacts::MakeEquivalent(
+    const protobufs::DataDescriptor& dd1,
     const protobufs::DataDescriptor& dd2) const {
-  auto end_type_1 = fuzzerutil::WalkCompositeTypeIndices(
+  // 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,
+           std::unordered_set<Operation, OperationHash, OperationEquals>()});
+    }
+    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_2 = fuzzerutil::WalkCompositeTypeIndices(
+  auto end_type_id_2 = fuzzerutil::WalkCompositeTypeIndices(
       context, context->get_def_use_mgr()->GetDef(dd2.object())->type_id(),
       dd2.index());
-  return end_type_1 && end_type_1 == end_type_2;
+  // 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::DataSynonymFacts::GetSynonymsForDataDescriptor(
+FactManager::DataSynonymAndIdEquationFacts::GetSynonymsForDataDescriptor(
     const protobufs::DataDescriptor& data_descriptor,
     opt::IRContext* context) const {
   ComputeClosureOfFacts(context);
@@ -774,7 +1195,7 @@
 }
 
 std::vector<uint32_t>
-FactManager::DataSynonymFacts ::GetIdsForWhichSynonymsAreKnown(
+FactManager::DataSynonymAndIdEquationFacts ::GetIdsForWhichSynonymsAreKnown(
     opt::IRContext* context) const {
   ComputeClosureOfFacts(context);
   std::vector<uint32_t> result;
@@ -786,12 +1207,12 @@
   return result;
 }
 
-bool FactManager::DataSynonymFacts::IsSynonymous(
+bool FactManager::DataSynonymAndIdEquationFacts::IsSynonymous(
     const protobufs::DataDescriptor& data_descriptor1,
     const protobufs::DataDescriptor& data_descriptor2,
     opt::IRContext* context) const {
-  const_cast<FactManager::DataSynonymFacts*>(this)->ComputeClosureOfFacts(
-      context);
+  const_cast<FactManager::DataSynonymAndIdEquationFacts*>(this)
+      ->ComputeClosureOfFacts(context);
   return synonymous_.Exists(data_descriptor1) &&
          synonymous_.Exists(data_descriptor2) &&
          synonymous_.IsEquivalent(data_descriptor1, data_descriptor2);
@@ -801,7 +1222,7 @@
 //==============================
 
 //==============================
-// Dead id facts
+// Dead block facts
 
 // The purpose of this class is to group the fields and data used to represent
 // facts about data blocks.
@@ -829,10 +1250,74 @@
 // 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 pointee value facts
+
+// The purpose of this class is to group the fields and data used to represent
+// facts about pointers whose pointee values are irrelevant.
+class FactManager::IrrelevantPointeeValueFacts {
+ 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.
+  bool PointeeValueIsIrrelevant(uint32_t pointer_id) const;
+
+ private:
+  std::set<uint32_t> pointers_to_irrelevant_pointees_ids_;
+};
+
+void FactManager::IrrelevantPointeeValueFacts::AddFact(
+    const protobufs::FactPointeeValueIsIrrelevant& fact) {
+  pointers_to_irrelevant_pointees_ids_.insert(fact.pointer_id());
+}
+
+bool FactManager::IrrelevantPointeeValueFacts::PointeeValueIsIrrelevant(
+    uint32_t pointer_id) const {
+  return pointers_to_irrelevant_pointees_ids_.count(pointer_id) != 0;
+}
+
+// End of arbitrarily-valued variable facts
+//==============================
+
 FactManager::FactManager()
     : uniform_constant_facts_(MakeUnique<ConstantUniformFacts>()),
-      data_synonym_facts_(MakeUnique<DataSynonymFacts>()),
-      dead_block_facts_(MakeUnique<DeadBlockFacts>()) {}
+      data_synonym_and_id_equation_facts_(
+          MakeUnique<DataSynonymAndIdEquationFacts>()),
+      dead_block_facts_(MakeUnique<DeadBlockFacts>()),
+      livesafe_function_facts_(MakeUnique<LivesafeFunctionFacts>()),
+      irrelevant_pointee_value_facts_(
+          MakeUnique<IrrelevantPointeeValueFacts>()) {}
 
 FactManager::~FactManager() = default;
 
@@ -855,11 +1340,15 @@
       return uniform_constant_facts_->AddFact(fact.constant_uniform_fact(),
                                               context);
     case protobufs::Fact::kDataSynonymFact:
-      data_synonym_facts_->AddFact(fact.data_synonym_fact(), context);
+      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;
@@ -872,7 +1361,7 @@
   protobufs::FactDataSynonym fact;
   *fact.mutable_data1() = data1;
   *fact.mutable_data2() = data2;
-  data_synonym_facts_->AddFact(fact, context);
+  data_synonym_and_id_equation_facts_->AddFact(fact, context);
 }
 
 std::vector<uint32_t> FactManager::GetConstantsAvailableFromUniformsForType(
@@ -907,15 +1396,16 @@
 
 std::vector<uint32_t> FactManager::GetIdsForWhichSynonymsAreKnown(
     opt::IRContext* context) const {
-  return data_synonym_facts_->GetIdsForWhichSynonymsAreKnown(context);
+  return data_synonym_and_id_equation_facts_->GetIdsForWhichSynonymsAreKnown(
+      context);
 }
 
 std::vector<const protobufs::DataDescriptor*>
 FactManager::GetSynonymsForDataDescriptor(
     const protobufs::DataDescriptor& data_descriptor,
     opt::IRContext* context) const {
-  return data_synonym_facts_->GetSynonymsForDataDescriptor(data_descriptor,
-                                                           context);
+  return data_synonym_and_id_equation_facts_->GetSynonymsForDataDescriptor(
+      data_descriptor, context);
 }
 
 std::vector<const protobufs::DataDescriptor*> FactManager::GetSynonymsForId(
@@ -927,8 +1417,8 @@
     const protobufs::DataDescriptor& data_descriptor1,
     const protobufs::DataDescriptor& data_descriptor2,
     opt::IRContext* context) const {
-  return data_synonym_facts_->IsSynonymous(data_descriptor1, data_descriptor2,
-                                           context);
+  return data_synonym_and_id_equation_facts_->IsSynonymous(
+      data_descriptor1, data_descriptor2, context);
 }
 
 bool FactManager::BlockIsDead(uint32_t block_id) const {
@@ -941,5 +1431,37 @@
   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_pointee_value_facts_->PointeeValueIsIrrelevant(pointer_id);
+}
+
+void FactManager::AddFactValueOfPointeeIsIrrelevant(uint32_t pointer_id) {
+  protobufs::FactPointeeValueIsIrrelevant fact;
+  fact.set_pointer_id(pointer_id);
+  irrelevant_pointee_value_facts_->AddFact(fact);
+}
+
+void FactManager::AddFactIdEquation(uint32_t lhs_id, SpvOp opcode,
+                                    const std::vector<uint32_t>& rhs_id,
+                                    opt::IRContext* context) {
+  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);
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/fact_manager.h b/source/fuzz/fact_manager.h
index f035fcc..f80d677 100644
--- a/source/fuzz/fact_manager.h
+++ b/source/fuzz/fact_manager.h
@@ -61,6 +61,21 @@
   // Records the fact that |block_id| is dead.
   void AddFactBlockIsDead(uint32_t block_id);
 
+  // Records the fact that |function_id| is livesafe.
+  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.
+  void AddFactValueOfPointeeIsIrrelevant(uint32_t pointer_id);
+
+  // Records the fact that |lhs_id| is defined by the equation:
+  //
+  //   |lhs_id| = |opcode| |rhs_id[0]| ... |rhs_id[N-1]|
+  //
+  void AddFactIdEquation(uint32_t lhs_id, SpvOp opcode,
+                         const std::vector<uint32_t>& rhs_id,
+                         opt::IRContext* context);
+
   // The fact manager is responsible for managing a few distinct categories of
   // facts. In principle there could be different fact managers for each kind
   // of fact, but in practice providing one 'go to' place for facts is
@@ -143,6 +158,26 @@
   // End of dead block facts
   //==============================
 
+  //==============================
+  // Querying facts about livesafe function
+
+  // Returns true if and ony if |function_id| is the id of a function known
+  // to be livesafe.
+  bool FunctionIsLivesafe(uint32_t function_id) const;
+
+  // End of dead livesafe function facts
+  //==============================
+
+  //==============================
+  // Querying facts about pointers with irrelevant pointee values
+
+  // Returns true if and ony if the value of the pointee associated with
+  // |pointer_id| is irrelevant.
+  bool PointeeValueIsIrrelevant(uint32_t pointer_id) const;
+
+  // End of irrelevant pointee value facts
+  //==============================
+
  private:
   // For each distinct kind of fact to be managed, we use a separate opaque
   // class type.
@@ -152,13 +187,24 @@
   std::unique_ptr<ConstantUniformFacts>
       uniform_constant_facts_;  // Unique pointer to internal data.
 
-  class DataSynonymFacts;  // Opaque class for management of data synonym facts.
-  std::unique_ptr<DataSynonymFacts>
-      data_synonym_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 IrrelevantPointeeValueFacts;  // Opaque class for management of
+  // facts about pointers whose pointee values do not matter.
+  std::unique_ptr<IrrelevantPointeeValueFacts>
+      irrelevant_pointee_value_facts_;  // Unique pointer to internal data.
 };
 
 }  // namespace fuzz
diff --git a/source/fuzz/force_render_red.cpp b/source/fuzz/force_render_red.cpp
index 46e23e8..5bf2879 100644
--- a/source/fuzz/force_render_red.cpp
+++ b/source/fuzz/force_render_red.cpp
@@ -17,6 +17,7 @@
 #include "source/fuzz/fact_manager.h"
 #include "source/fuzz/instruction_descriptor.h"
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/transformation_context.h"
 #include "source/fuzz/transformation_replace_constant_with_uniform.h"
 #include "source/fuzz/uniform_buffer_element_descriptor.h"
 #include "source/opt/build_module.h"
@@ -159,7 +160,8 @@
 }  // namespace
 
 bool ForceRenderRed(
-    const spv_target_env& target_env, const std::vector<uint32_t>& binary_in,
+    const spv_target_env& target_env, spv_validator_options validator_options,
+    const std::vector<uint32_t>& binary_in,
     const spvtools::fuzz::protobufs::FactSequence& initial_facts,
     std::vector<uint32_t>* binary_out) {
   auto message_consumer = spvtools::utils::CLIMessageConsumer;
@@ -171,7 +173,7 @@
   }
 
   // Initial binary should be valid.
-  if (!tools.Validate(&binary_in[0], binary_in.size())) {
+  if (!tools.Validate(&binary_in[0], binary_in.size(), validator_options)) {
     message_consumer(SPV_MSG_ERROR, nullptr, {},
                      "Initial binary is invalid; stopping.");
     return false;
@@ -187,6 +189,8 @@
   for (auto& fact : initial_facts.fact()) {
     fact_manager.AddFact(fact, ir_context.get());
   }
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   auto entry_point_function =
       FindFragmentShaderEntryPoint(ir_context.get(), message_consumer);
@@ -355,8 +359,9 @@
     for (auto& replacement : {first_greater_then_operand_replacement.get(),
                               second_greater_then_operand_replacement.get()}) {
       if (replacement) {
-        assert(replacement->IsApplicable(ir_context.get(), fact_manager));
-        replacement->Apply(ir_context.get(), &fact_manager);
+        assert(replacement->IsApplicable(ir_context.get(),
+                                         transformation_context));
+        replacement->Apply(ir_context.get(), &transformation_context);
       }
     }
   }
diff --git a/source/fuzz/force_render_red.h b/source/fuzz/force_render_red.h
index 2484d27..b51c72b 100644
--- a/source/fuzz/force_render_red.h
+++ b/source/fuzz/force_render_red.h
@@ -38,7 +38,8 @@
 // instead become: 'u > v', where 'u' and 'v' are pieces of uniform data for
 // which it is known that 'u < v' holds.
 bool ForceRenderRed(
-    const spv_target_env& target_env, const std::vector<uint32_t>& binary_in,
+    const spv_target_env& target_env, spv_validator_options validator_options,
+    const std::vector<uint32_t>& binary_in,
     const spvtools::fuzz::protobufs::FactSequence& initial_facts,
     std::vector<uint32_t>* binary_out);
 
diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp
index a624c11..6524c21 100644
--- a/source/fuzz/fuzzer.cpp
+++ b/source/fuzz/fuzzer.cpp
@@ -21,10 +21,18 @@
 #include "fuzzer_pass_adjust_memory_operands_masks.h"
 #include "source/fuzz/fact_manager.h"
 #include "source/fuzz/fuzzer_context.h"
+#include "source/fuzz/fuzzer_pass_add_access_chains.h"
+#include "source/fuzz/fuzzer_pass_add_composite_types.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_loads.h"
+#include "source/fuzz/fuzzer_pass_add_local_variables.h"
 #include "source/fuzz/fuzzer_pass_add_no_contraction_decorations.h"
+#include "source/fuzz/fuzzer_pass_add_stores.h"
 #include "source/fuzz/fuzzer_pass_add_useful_constructs.h"
 #include "source/fuzz/fuzzer_pass_adjust_function_controls.h"
 #include "source/fuzz/fuzzer_pass_adjust_loop_controls.h"
@@ -37,9 +45,13 @@
 #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_split_blocks.h"
+#include "source/fuzz/fuzzer_pass_swap_commutable_operands.h"
+#include "source/fuzz/fuzzer_pass_toggle_access_chain_instruction.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"
 #include "source/util/make_unique.h"
@@ -55,19 +67,19 @@
 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|, |fact_manager|, |fuzzer_context| and
-// |transformation_sequence_out| as parameters.  Extra arguments can be provided
-// via |extra_args|.
+// 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, FactManager* fact_manager,
+    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, fact_manager, fuzzer_context,
-                                    transformation_sequence_out,
+    passes->push_back(MakeUnique<T>(ir_context, transformation_context,
+                                    fuzzer_context, transformation_sequence_out,
                                     std::forward<Args>(extra_args)...));
   }
 }
@@ -75,26 +87,31 @@
 }  // namespace
 
 struct Fuzzer::Impl {
-  explicit Impl(spv_target_env env, uint32_t random_seed,
-                bool validate_after_each_pass)
+  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) {}
+        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.
-  MessageConsumer consumer;  // Message consumer.
+                                         // 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,
-               bool validate_after_each_fuzzer_pass)
-    : impl_(MakeUnique<Impl>(env, seed, validate_after_each_fuzzer_pass)) {}
+               bool validate_after_each_fuzzer_pass,
+               spv_validator_options validator_options)
+    : impl_(MakeUnique<Impl>(env, seed, validate_after_each_fuzzer_pass,
+                             validator_options)) {}
 
 Fuzzer::~Fuzzer() = default;
 
@@ -109,7 +126,8 @@
   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())) {
+    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.");
@@ -138,7 +156,8 @@
   }
 
   // Initial binary should be valid.
-  if (!tools.Validate(&binary_in[0], binary_in.size())) {
+  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;
@@ -164,11 +183,13 @@
 
   FactManager fact_manager;
   fact_manager.AddFacts(impl_->consumer, initial_facts, ir_context.get());
+  TransformationContext transformation_context(&fact_manager,
+                                               impl_->validator_options);
 
   // Add some essential ingredients to the module if they are not already
   // present, such as boolean constants.
   FuzzerPassAddUsefulConstructs add_useful_constructs(
-      ir_context.get(), &fact_manager, &fuzzer_context,
+      ir_context.get(), &transformation_context, &fuzzer_context,
       transformation_sequence_out);
   if (!impl_->ApplyPassAndCheckValidity(&add_useful_constructs, *ir_context,
                                         tools)) {
@@ -178,42 +199,69 @@
   // Apply some semantics-preserving passes.
   std::vector<std::unique_ptr<FuzzerPass>> passes;
   while (passes.empty()) {
-    MaybeAddPass<FuzzerPassAddDeadBlocks>(&passes, ir_context.get(),
-                                          &fact_manager, &fuzzer_context,
-                                          transformation_sequence_out);
-    MaybeAddPass<FuzzerPassAddDeadBreaks>(&passes, ir_context.get(),
-                                          &fact_manager, &fuzzer_context,
-                                          transformation_sequence_out);
-    MaybeAddPass<FuzzerPassAddDeadContinues>(&passes, ir_context.get(),
-                                             &fact_manager, &fuzzer_context,
-                                             transformation_sequence_out);
-    MaybeAddPass<FuzzerPassApplyIdSynonyms>(&passes, ir_context.get(),
-                                            &fact_manager, &fuzzer_context,
-                                            transformation_sequence_out);
-    MaybeAddPass<FuzzerPassConstructComposites>(&passes, ir_context.get(),
-                                                &fact_manager, &fuzzer_context,
-                                                transformation_sequence_out);
-    MaybeAddPass<FuzzerPassCopyObjects>(&passes, ir_context.get(),
-                                        &fact_manager, &fuzzer_context,
-                                        transformation_sequence_out);
+    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<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<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<FuzzerPassAddStores>(&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(), &fact_manager, &fuzzer_context,
+        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
         transformation_sequence_out, donor_suppliers);
-    MaybeAddPass<FuzzerPassMergeBlocks>(&passes, ir_context.get(),
-                                        &fact_manager, &fuzzer_context,
-                                        transformation_sequence_out);
-    MaybeAddPass<FuzzerPassObfuscateConstants>(&passes, ir_context.get(),
-                                               &fact_manager, &fuzzer_context,
-                                               transformation_sequence_out);
-    MaybeAddPass<FuzzerPassOutlineFunctions>(&passes, ir_context.get(),
-                                             &fact_manager, &fuzzer_context,
-                                             transformation_sequence_out);
-    MaybeAddPass<FuzzerPassPermuteBlocks>(&passes, ir_context.get(),
-                                          &fact_manager, &fuzzer_context,
-                                          transformation_sequence_out);
-    MaybeAddPass<FuzzerPassSplitBlocks>(&passes, ir_context.get(),
-                                        &fact_manager, &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<FuzzerPassSplitBlocks>(
+        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
+        transformation_sequence_out);
   }
 
   bool is_first = true;
@@ -234,19 +282,25 @@
   // as they do not unlock other passes.
   std::vector<std::unique_ptr<FuzzerPass>> final_passes;
   MaybeAddPass<FuzzerPassAdjustFunctionControls>(
-      &final_passes, ir_context.get(), &fact_manager, &fuzzer_context,
+      &final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
       transformation_sequence_out);
-  MaybeAddPass<FuzzerPassAdjustLoopControls>(&final_passes, ir_context.get(),
-                                             &fact_manager, &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(), &fact_manager, &fuzzer_context,
+      &final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
       transformation_sequence_out);
   MaybeAddPass<FuzzerPassAdjustSelectionControls>(
-      &final_passes, ir_context.get(), &fact_manager, &fuzzer_context,
+      &final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
       transformation_sequence_out);
   MaybeAddPass<FuzzerPassAddNoContractionDecorations>(
-      &final_passes, ir_context.get(), &fact_manager, &fuzzer_context,
+      &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);
   for (auto& pass : final_passes) {
     if (!impl_->ApplyPassAndCheckValidity(pass.get(), *ir_context, tools)) {
diff --git a/source/fuzz/fuzzer.h b/source/fuzz/fuzzer.h
index 3ac73a1..6c3ef71 100644
--- a/source/fuzz/fuzzer.h
+++ b/source/fuzz/fuzzer.h
@@ -41,8 +41,9 @@
   // seed for pseudo-random number generation.
   // |validate_after_each_fuzzer_pass| controls whether the validator will be
   // invoked after every fuzzer pass is applied.
-  explicit Fuzzer(spv_target_env env, uint32_t seed,
-                  bool validate_after_each_fuzzer_pass);
+  Fuzzer(spv_target_env env, uint32_t seed,
+         bool validate_after_each_fuzzer_pass,
+         spv_validator_options validator_options);
 
   // Disables copy/move constructor/assignment operations.
   Fuzzer(const Fuzzer&) = delete;
diff --git a/source/fuzz/fuzzer_context.cpp b/source/fuzz/fuzzer_context.cpp
index 559aecb..2f9fc5a 100644
--- a/source/fuzz/fuzzer_context.cpp
+++ b/source/fuzz/fuzzer_context.cpp
@@ -23,11 +23,23 @@
 // 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> kChanceOfAddingAccessChain = {5, 50};
+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> kChanceOfAddingDeadBlock = {20, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfAddingDeadBreak = {5, 80};
 const std::pair<uint32_t, uint32_t> kChanceOfAddingDeadContinue = {5, 80};
+const std::pair<uint32_t, uint32_t> kChanceOfAddingEquationInstruction = {5,
+                                                                          90};
+const std::pair<uint32_t, uint32_t> kChanceOfAddingGlobalVariable = {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> kChanceOfAddingMatrixType = {20, 70};
 const std::pair<uint32_t, uint32_t> kChanceOfAddingNoContractionDecoration = {
     5, 70};
+const std::pair<uint32_t, uint32_t> kChanceOfAddingStore = {5, 50};
+const std::pair<uint32_t, uint32_t> kChanceOfAddingVectorType = {20, 70};
 const std::pair<uint32_t, uint32_t> kChanceOfAdjustingFunctionControl = {20,
                                                                          70};
 const std::pair<uint32_t, uint32_t> kChanceOfAdjustingLoopControl = {20, 90};
@@ -35,20 +47,31 @@
                                                                             90};
 const std::pair<uint32_t, uint32_t> kChanceOfAdjustingSelectionControl = {20,
                                                                           90};
+const std::pair<uint32_t, uint32_t> kChanceOfCallingFunction = {1, 10};
+const std::pair<uint32_t, uint32_t> kChanceOfChoosingStructTypeVsArrayType = {
+    20, 80};
 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> kChanceOfDonatingAdditionalModule = {5, 50};
+const std::pair<uint32_t, uint32_t> kChanceOfGoingDeeperWhenMakingAccessChain =
+    {50, 95};
+const std::pair<uint32_t, uint32_t> kChanceOfMakingDonorLivesafe = {40, 60};
 const std::pair<uint32_t, uint32_t> kChanceOfMergingBlocks = {20, 95};
 const std::pair<uint32_t, uint32_t> kChanceOfMovingBlockDown = {20, 50};
 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> kChanceOfPermutingParameters = {30, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfReplacingIdWithSynonym = {10, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfSplittingBlock = {40, 95};
+const std::pair<uint32_t, uint32_t> kChanceOfTogglingAccessChainInstruction = {
+    20, 90};
 
 // Default limits for various quantities that are chosen during fuzzing.
 // Keep them in alphabetical order.
 const uint32_t kDefaultMaxLoopControlPartialCount = 100;
 const uint32_t kDefaultMaxLoopControlPeelCount = 100;
+const uint32_t kDefaultMaxLoopLimit = 20;
+const uint32_t kDefaultMaxNewArraySizeLimit = 100;
 
 // Default functions for controlling how deep to go during recursive
 // generation/transformation. Keep them in alphabetical order.
@@ -66,16 +89,38 @@
                              uint32_t min_fresh_id)
     : random_generator_(random_generator),
       next_fresh_id_(min_fresh_id),
+      max_loop_control_partial_count_(kDefaultMaxLoopControlPartialCount),
+      max_loop_control_peel_count_(kDefaultMaxLoopControlPeelCount),
+      max_loop_limit_(kDefaultMaxLoopLimit),
+      max_new_array_size_limit_(kDefaultMaxNewArraySizeLimit),
       go_deeper_in_constant_obfuscation_(
           kDefaultGoDeeperInConstantObfuscation) {
+  chance_of_adding_access_chain_ =
+      ChooseBetweenMinAndMax(kChanceOfAddingAccessChain);
+  chance_of_adding_another_struct_field_ =
+      ChooseBetweenMinAndMax(kChanceOfAddingAnotherStructField);
+  chance_of_adding_array_or_struct_type_ =
+      ChooseBetweenMinAndMax(kChanceOfAddingArrayOrStructType);
   chance_of_adding_dead_block_ =
       ChooseBetweenMinAndMax(kChanceOfAddingDeadBlock);
   chance_of_adding_dead_break_ =
       ChooseBetweenMinAndMax(kChanceOfAddingDeadBreak);
   chance_of_adding_dead_continue_ =
       ChooseBetweenMinAndMax(kChanceOfAddingDeadContinue);
+  chance_of_adding_equation_instruction_ =
+      ChooseBetweenMinAndMax(kChanceOfAddingEquationInstruction);
+  chance_of_adding_global_variable_ =
+      ChooseBetweenMinAndMax(kChanceOfAddingGlobalVariable);
+  chance_of_adding_load_ = ChooseBetweenMinAndMax(kChanceOfAddingLoad);
+  chance_of_adding_local_variable_ =
+      ChooseBetweenMinAndMax(kChanceOfAddingLocalVariable);
+  chance_of_adding_matrix_type_ =
+      ChooseBetweenMinAndMax(kChanceOfAddingMatrixType);
   chance_of_adding_no_contraction_decoration_ =
       ChooseBetweenMinAndMax(kChanceOfAddingNoContractionDecoration);
+  chance_of_adding_store_ = ChooseBetweenMinAndMax(kChanceOfAddingStore);
+  chance_of_adding_vector_type_ =
+      ChooseBetweenMinAndMax(kChanceOfAddingVectorType);
   chance_of_adjusting_function_control_ =
       ChooseBetweenMinAndMax(kChanceOfAdjustingFunctionControl);
   chance_of_adjusting_loop_control_ =
@@ -84,11 +129,19 @@
       ChooseBetweenMinAndMax(kChanceOfAdjustingMemoryOperandsMask);
   chance_of_adjusting_selection_control_ =
       ChooseBetweenMinAndMax(kChanceOfAdjustingSelectionControl);
+  chance_of_calling_function_ =
+      ChooseBetweenMinAndMax(kChanceOfCallingFunction);
+  chance_of_choosing_struct_type_vs_array_type_ =
+      ChooseBetweenMinAndMax(kChanceOfChoosingStructTypeVsArrayType);
   chance_of_constructing_composite_ =
       ChooseBetweenMinAndMax(kChanceOfConstructingComposite);
   chance_of_copying_object_ = ChooseBetweenMinAndMax(kChanceOfCopyingObject);
   chance_of_donating_additional_module_ =
       ChooseBetweenMinAndMax(kChanceOfDonatingAdditionalModule);
+  chance_of_going_deeper_when_making_access_chain_ =
+      ChooseBetweenMinAndMax(kChanceOfGoingDeeperWhenMakingAccessChain);
+  chance_of_making_donor_livesafe_ =
+      ChooseBetweenMinAndMax(kChanceOfMakingDonorLivesafe);
   chance_of_merging_blocks_ = ChooseBetweenMinAndMax(kChanceOfMergingBlocks);
   chance_of_moving_block_down_ =
       ChooseBetweenMinAndMax(kChanceOfMovingBlockDown);
@@ -96,11 +149,13 @@
       ChooseBetweenMinAndMax(kChanceOfObfuscatingConstant);
   chance_of_outlining_function_ =
       ChooseBetweenMinAndMax(kChanceOfOutliningFunction);
+  chance_of_permuting_parameters_ =
+      ChooseBetweenMinAndMax(kChanceOfPermutingParameters);
   chance_of_replacing_id_with_synonym_ =
       ChooseBetweenMinAndMax(kChanceOfReplacingIdWithSynonym);
   chance_of_splitting_block_ = ChooseBetweenMinAndMax(kChanceOfSplittingBlock);
-  max_loop_control_partial_count_ = kDefaultMaxLoopControlPartialCount;
-  max_loop_control_peel_count_ = kDefaultMaxLoopControlPeelCount;
+  chance_of_toggling_access_chain_instruction_ =
+      ChooseBetweenMinAndMax(kChanceOfTogglingAccessChainInstruction);
 }
 
 FuzzerContext::~FuzzerContext() = default;
diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h
index 7cfe15b..1529705 100644
--- a/source/fuzz/fuzzer_context.h
+++ b/source/fuzz/fuzzer_context.h
@@ -46,26 +46,96 @@
   // method, and which must be non-empty.  Typically 'HasSizeMethod' will be an
   // std::vector.
   template <typename HasSizeMethod>
-  uint32_t RandomIndex(const HasSizeMethod& sequence) {
+  uint32_t RandomIndex(const HasSizeMethod& sequence) const {
     assert(sequence.size() > 0);
     return random_generator_->RandomUint32(
         static_cast<uint32_t>(sequence.size()));
   }
 
+  // Selects a random index into |sequence|, removes the element at that index
+  // and returns it.
+  template <typename T>
+  T RemoveAtRandomIndex(std::vector<T>* sequence) const {
+    uint32_t index = RandomIndex(*sequence);
+    T result = sequence->at(index);
+    sequence->erase(sequence->begin() + index);
+    return result;
+  }
+
+  // Randomly shuffles a |sequence| between |lo| and |hi| indices inclusively.
+  // |lo| and |hi| must be valid indices to the |sequence|
+  template <typename T>
+  void Shuffle(std::vector<T>* sequence, size_t lo, size_t hi) const {
+    auto& array = *sequence;
+
+    if (array.empty()) {
+      return;
+    }
+
+    assert(lo <= hi && hi < array.size() && "lo and/or hi indices are invalid");
+
+    // i > lo to account for potential infinite loop when lo == 0
+    for (size_t i = hi; i > lo; --i) {
+      auto index =
+          random_generator_->RandomUint32(static_cast<uint32_t>(i - lo + 1));
+
+      if (lo + index != i) {
+        // Introduce std::swap to the scope but don't use it
+        // directly since there might be a better overload
+        using std::swap;
+        swap(array[lo + index], array[i]);
+      }
+    }
+  }
+
+  // Ramdomly shuffles a |sequence|
+  template <typename T>
+  void Shuffle(std::vector<T>* sequence) const {
+    if (!sequence->empty()) {
+      Shuffle(sequence, 0, sequence->size() - 1);
+    }
+  }
+
   // Yields an id that is guaranteed not to be used in the module being fuzzed,
   // or to have been issued before.
   uint32_t GetFreshId();
 
   // Probabilities associated with applying various transformations.
   // Keep them in alphabetical order.
+  uint32_t GetChanceOfAddingAccessChain() {
+    return chance_of_adding_access_chain_;
+  }
+  uint32_t GetChanceOfAddingAnotherStructField() {
+    return chance_of_adding_another_struct_field_;
+  }
+  uint32_t GetChanceOfAddingArrayOrStructType() {
+    return chance_of_adding_array_or_struct_type_;
+  }
   uint32_t GetChanceOfAddingDeadBlock() { return chance_of_adding_dead_block_; }
   uint32_t GetChanceOfAddingDeadBreak() { return chance_of_adding_dead_break_; }
   uint32_t GetChanceOfAddingDeadContinue() {
     return chance_of_adding_dead_continue_;
   }
+  uint32_t GetChanceOfAddingEquationInstruction() {
+    return chance_of_adding_equation_instruction_;
+  }
+  uint32_t GetChanceOfAddingGlobalVariable() {
+    return chance_of_adding_global_variable_;
+  }
+  uint32_t GetChanceOfAddingLoad() { return chance_of_adding_load_; }
+  uint32_t GetChanceOfAddingLocalVariable() {
+    return chance_of_adding_local_variable_;
+  }
+  uint32_t GetChanceOfAddingMatrixType() {
+    return chance_of_adding_matrix_type_;
+  }
   uint32_t GetChanceOfAddingNoContractionDecoration() {
     return chance_of_adding_no_contraction_decoration_;
   }
+  uint32_t GetChanceOfAddingStore() { return chance_of_adding_store_; }
+  uint32_t GetChanceOfAddingVectorType() {
+    return chance_of_adding_vector_type_;
+  }
   uint32_t GetChanceOfAdjustingFunctionControl() {
     return chance_of_adjusting_function_control_;
   }
@@ -78,6 +148,10 @@
   uint32_t GetChanceOfAdjustingSelectionControl() {
     return chance_of_adjusting_selection_control_;
   }
+  uint32_t GetChanceOfCallingFunction() { return chance_of_calling_function_; }
+  uint32_t GetChanceOfChoosingStructTypeVsArrayType() {
+    return chance_of_choosing_struct_type_vs_array_type_;
+  }
   uint32_t GetChanceOfConstructingComposite() {
     return chance_of_constructing_composite_;
   }
@@ -85,6 +159,12 @@
   uint32_t GetChanceOfDonatingAdditionalModule() {
     return chance_of_donating_additional_module_;
   }
+  uint32_t GetChanceOfGoingDeeperWhenMakingAccessChain() {
+    return chance_of_going_deeper_when_making_access_chain_;
+  }
+  uint32_t ChanceOfMakingDonorLivesafe() {
+    return chance_of_making_donor_livesafe_;
+  }
   uint32_t GetChanceOfMergingBlocks() { return chance_of_merging_blocks_; }
   uint32_t GetChanceOfMovingBlockDown() { return chance_of_moving_block_down_; }
   uint32_t GetChanceOfObfuscatingConstant() {
@@ -93,19 +173,35 @@
   uint32_t GetChanceOfOutliningFunction() {
     return chance_of_outlining_function_;
   }
+  uint32_t GetChanceOfPermutingParameters() {
+    return chance_of_permuting_parameters_;
+  }
   uint32_t GetChanceOfReplacingIdWithSynonym() {
     return chance_of_replacing_id_with_synonym_;
   }
   uint32_t GetChanceOfSplittingBlock() { return chance_of_splitting_block_; }
+  uint32_t GetChanceOfTogglingAccessChainInstruction() {
+    return chance_of_toggling_access_chain_instruction_;
+  }
   uint32_t GetRandomLoopControlPeelCount() {
     return random_generator_->RandomUint32(max_loop_control_peel_count_);
   }
   uint32_t GetRandomLoopControlPartialCount() {
     return random_generator_->RandomUint32(max_loop_control_partial_count_);
   }
+  uint32_t GetRandomLoopLimit() {
+    return random_generator_->RandomUint32(max_loop_limit_);
+  }
+  uint32_t GetRandomSizeForNewArray() {
+    // Ensure that the array size is non-zero.
+    return random_generator_->RandomUint32(max_new_array_size_limit_ - 1) + 1;
+  }
 
-  // Functions to control how deeply to recurse.
-  // Keep them in alphabetical order.
+  // Other functions to control transformations. Keep them in alphabetical
+  // order.
+  uint32_t GetRandomIndexForAccessChain(uint32_t composite_size_bound) {
+    return random_generator_->RandomUint32(composite_size_bound);
+  }
   bool GoDeeperInConstantObfuscation(uint32_t depth) {
     return go_deeper_in_constant_obfuscation_(depth, random_generator_);
   }
@@ -118,29 +214,47 @@
 
   // Probabilities associated with applying various transformations.
   // Keep them in alphabetical order.
+  uint32_t chance_of_adding_access_chain_;
+  uint32_t chance_of_adding_another_struct_field_;
+  uint32_t chance_of_adding_array_or_struct_type_;
   uint32_t chance_of_adding_dead_block_;
   uint32_t chance_of_adding_dead_break_;
   uint32_t chance_of_adding_dead_continue_;
+  uint32_t chance_of_adding_equation_instruction_;
+  uint32_t chance_of_adding_global_variable_;
+  uint32_t chance_of_adding_load_;
+  uint32_t chance_of_adding_local_variable_;
+  uint32_t chance_of_adding_matrix_type_;
   uint32_t chance_of_adding_no_contraction_decoration_;
+  uint32_t chance_of_adding_store_;
+  uint32_t chance_of_adding_vector_type_;
   uint32_t chance_of_adjusting_function_control_;
   uint32_t chance_of_adjusting_loop_control_;
   uint32_t chance_of_adjusting_memory_operands_mask_;
   uint32_t chance_of_adjusting_selection_control_;
+  uint32_t chance_of_calling_function_;
+  uint32_t chance_of_choosing_struct_type_vs_array_type_;
   uint32_t chance_of_constructing_composite_;
   uint32_t chance_of_copying_object_;
   uint32_t chance_of_donating_additional_module_;
+  uint32_t chance_of_going_deeper_when_making_access_chain_;
+  uint32_t chance_of_making_donor_livesafe_;
   uint32_t chance_of_merging_blocks_;
   uint32_t chance_of_moving_block_down_;
   uint32_t chance_of_obfuscating_constant_;
   uint32_t chance_of_outlining_function_;
+  uint32_t chance_of_permuting_parameters_;
   uint32_t chance_of_replacing_id_with_synonym_;
   uint32_t chance_of_splitting_block_;
+  uint32_t chance_of_toggling_access_chain_instruction_;
 
   // Limits associated with various quantities for which random values are
   // chosen during fuzzing.
   // Keep them in alphabetical order.
   uint32_t max_loop_control_partial_count_;
   uint32_t max_loop_control_peel_count_;
+  uint32_t max_loop_limit_;
+  uint32_t max_new_array_size_limit_;
 
   // Functions to determine with what probability to go deeper when generating
   // or mutating constructs recursively.
diff --git a/source/fuzz/fuzzer_pass.cpp b/source/fuzz/fuzzer_pass.cpp
index 1da53f4..dbe5143 100644
--- a/source/fuzz/fuzzer_pass.cpp
+++ b/source/fuzz/fuzzer_pass.cpp
@@ -14,26 +14,41 @@
 
 #include "source/fuzz/fuzzer_pass.h"
 
+#include <set>
+
+#include "source/fuzz/fuzzer_util.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_scalar.h"
+#include "source/fuzz/transformation_add_global_undef.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"
+#include "source/fuzz/transformation_add_type_int.h"
+#include "source/fuzz/transformation_add_type_matrix.h"
+#include "source/fuzz/transformation_add_type_pointer.h"
+#include "source/fuzz/transformation_add_type_vector.h"
 
 namespace spvtools {
 namespace fuzz {
 
-FuzzerPass::FuzzerPass(opt::IRContext* ir_context, FactManager* fact_manager,
+FuzzerPass::FuzzerPass(opt::IRContext* ir_context,
+                       TransformationContext* transformation_context,
                        FuzzerContext* fuzzer_context,
                        protobufs::TransformationSequence* transformations)
     : ir_context_(ir_context),
-      fact_manager_(fact_manager),
+      transformation_context_(transformation_context),
       fuzzer_context_(fuzzer_context),
       transformations_(transformations) {}
 
 FuzzerPass::~FuzzerPass() = default;
 
 std::vector<opt::Instruction*> FuzzerPass::FindAvailableInstructions(
-    const opt::Function& function, opt::BasicBlock* block,
-    opt::BasicBlock::iterator inst_it,
+    opt::Function* function, opt::BasicBlock* block,
+    const opt::BasicBlock::iterator& inst_it,
     std::function<bool(opt::IRContext*, opt::Instruction*)>
-        instruction_is_relevant) {
+        instruction_is_relevant) const {
   // TODO(afd) The following is (relatively) simple, but may end up being
   //  prohibitively inefficient, as it walks the whole dominator tree for
   //  every instruction that is considered.
@@ -46,6 +61,14 @@
     }
   }
 
+  // Consider all function parameters
+  function->ForEachParam(
+      [this, &instruction_is_relevant, &result](opt::Instruction* param) {
+        if (instruction_is_relevant(GetIRContext(), param)) {
+          result.push_back(param);
+        }
+      });
+
   // Consider all previous instructions in this block
   for (auto prev_inst_it = block->begin(); prev_inst_it != inst_it;
        ++prev_inst_it) {
@@ -56,7 +79,7 @@
 
   // Walk the dominator tree to consider all instructions from dominating
   // blocks
-  auto dominator_analysis = GetIRContext()->GetDominatorAnalysis(&function);
+  auto dominator_analysis = GetIRContext()->GetDominatorAnalysis(function);
   for (auto next_dominator = dominator_analysis->ImmediateDominator(block);
        next_dominator != nullptr;
        next_dominator =
@@ -70,12 +93,12 @@
   return result;
 }
 
-void FuzzerPass::MaybeAddTransformationBeforeEachInstruction(
+void FuzzerPass::ForEachInstructionWithInstructionDescriptor(
     std::function<
-        void(const opt::Function& function, opt::BasicBlock* block,
+        void(opt::Function* function, opt::BasicBlock* block,
              opt::BasicBlock::iterator inst_it,
              const protobufs::InstructionDescriptor& instruction_descriptor)>
-        maybe_apply_transformation) {
+        action) {
   // Consider every block in every function.
   for (auto& function : *GetIRContext()->module()) {
     for (auto& block : function) {
@@ -113,11 +136,10 @@
         const SpvOp opcode = inst_it->opcode();
 
         // Invoke the provided function, which might apply a transformation.
-        maybe_apply_transformation(
-            function, &block, inst_it,
-            MakeInstructionDescriptor(
-                base, opcode,
-                skip_count.count(opcode) ? skip_count.at(opcode) : 0));
+        action(&function, &block, inst_it,
+               MakeInstructionDescriptor(
+                   base, opcode,
+                   skip_count.count(opcode) ? skip_count.at(opcode) : 0));
 
         if (!inst_it->HasResultId()) {
           skip_count[opcode] =
@@ -128,5 +150,356 @@
   }
 }
 
+uint32_t FuzzerPass::FindOrCreateBoolType() {
+  opt::analysis::Bool bool_type;
+  auto existing_id = GetIRContext()->get_type_mgr()->GetId(&bool_type);
+  if (existing_id) {
+    return existing_id;
+  }
+  auto result = GetFuzzerContext()->GetFreshId();
+  ApplyTransformation(TransformationAddTypeBoolean(result));
+  return result;
+}
+
+uint32_t FuzzerPass::FindOrCreate32BitIntegerType(bool is_signed) {
+  opt::analysis::Integer int_type(32, is_signed);
+  auto existing_id = GetIRContext()->get_type_mgr()->GetId(&int_type);
+  if (existing_id) {
+    return existing_id;
+  }
+  auto result = GetFuzzerContext()->GetFreshId();
+  ApplyTransformation(TransformationAddTypeInt(result, 32, is_signed));
+  return result;
+}
+
+uint32_t FuzzerPass::FindOrCreate32BitFloatType() {
+  opt::analysis::Float float_type(32);
+  auto existing_id = GetIRContext()->get_type_mgr()->GetId(&float_type);
+  if (existing_id) {
+    return existing_id;
+  }
+  auto result = GetFuzzerContext()->GetFreshId();
+  ApplyTransformation(TransformationAddTypeFloat(result, 32));
+  return result;
+}
+
+uint32_t FuzzerPass::FindOrCreateFunctionType(
+    uint32_t return_type_id, const std::vector<uint32_t>& argument_id) {
+  // FindFunctionType has a sigle argument for OpTypeFunction operands
+  // so we will have to copy them all in this vector
+  std::vector<uint32_t> type_ids(argument_id.size() + 1);
+  type_ids[0] = return_type_id;
+  std::copy(argument_id.begin(), argument_id.end(), type_ids.begin() + 1);
+
+  // Check if type exists
+  auto existing_id = fuzzerutil::FindFunctionType(GetIRContext(), type_ids);
+  if (existing_id) {
+    return existing_id;
+  }
+
+  auto result = GetFuzzerContext()->GetFreshId();
+  ApplyTransformation(
+      TransformationAddTypeFunction(result, return_type_id, argument_id));
+  return result;
+}
+
+uint32_t FuzzerPass::FindOrCreateVectorType(uint32_t component_type_id,
+                                            uint32_t component_count) {
+  assert(component_count >= 2 && component_count <= 4 &&
+         "Precondition: component count must be in range [2, 4].");
+  opt::analysis::Type* component_type =
+      GetIRContext()->get_type_mgr()->GetType(component_type_id);
+  assert(component_type && "Precondition: the component type must exist.");
+  opt::analysis::Vector vector_type(component_type, component_count);
+  auto existing_id = GetIRContext()->get_type_mgr()->GetId(&vector_type);
+  if (existing_id) {
+    return existing_id;
+  }
+  auto result = GetFuzzerContext()->GetFreshId();
+  ApplyTransformation(
+      TransformationAddTypeVector(result, component_type_id, component_count));
+  return result;
+}
+
+uint32_t FuzzerPass::FindOrCreateMatrixType(uint32_t column_count,
+                                            uint32_t row_count) {
+  assert(column_count >= 2 && column_count <= 4 &&
+         "Precondition: column count must be in range [2, 4].");
+  assert(row_count >= 2 && row_count <= 4 &&
+         "Precondition: row count must be in range [2, 4].");
+  uint32_t column_type_id =
+      FindOrCreateVectorType(FindOrCreate32BitFloatType(), row_count);
+  opt::analysis::Type* column_type =
+      GetIRContext()->get_type_mgr()->GetType(column_type_id);
+  opt::analysis::Matrix matrix_type(column_type, column_count);
+  auto existing_id = GetIRContext()->get_type_mgr()->GetId(&matrix_type);
+  if (existing_id) {
+    return existing_id;
+  }
+  auto result = GetFuzzerContext()->GetFreshId();
+  ApplyTransformation(
+      TransformationAddTypeMatrix(result, column_type_id, column_count));
+  return result;
+}
+
+uint32_t FuzzerPass::FindOrCreatePointerType(uint32_t base_type_id,
+                                             SpvStorageClass storage_class) {
+  // We do not use the type manager here, due to problems related to isomorphic
+  // but distinct structs not being regarded as different.
+  auto existing_id = fuzzerutil::MaybeGetPointerType(
+      GetIRContext(), base_type_id, storage_class);
+  if (existing_id) {
+    return existing_id;
+  }
+  auto result = GetFuzzerContext()->GetFreshId();
+  ApplyTransformation(
+      TransformationAddTypePointer(result, storage_class, base_type_id));
+  return result;
+}
+
+uint32_t FuzzerPass::FindOrCreatePointerTo32BitIntegerType(
+    bool is_signed, SpvStorageClass storage_class) {
+  return FindOrCreatePointerType(FindOrCreate32BitIntegerType(is_signed),
+                                 storage_class);
+}
+
+uint32_t FuzzerPass::FindOrCreate32BitIntegerConstant(uint32_t word,
+                                                      bool is_signed) {
+  auto uint32_type_id = FindOrCreate32BitIntegerType(is_signed);
+  opt::analysis::IntConstant int_constant(
+      GetIRContext()->get_type_mgr()->GetType(uint32_type_id)->AsInteger(),
+      {word});
+  auto existing_constant =
+      GetIRContext()->get_constant_mgr()->FindConstant(&int_constant);
+  if (existing_constant) {
+    return GetIRContext()
+        ->get_constant_mgr()
+        ->GetDefiningInstruction(existing_constant)
+        ->result_id();
+  }
+  auto result = GetFuzzerContext()->GetFreshId();
+  ApplyTransformation(
+      TransformationAddConstantScalar(result, uint32_type_id, {word}));
+  return result;
+}
+
+uint32_t FuzzerPass::FindOrCreate32BitFloatConstant(uint32_t word) {
+  auto float_type_id = FindOrCreate32BitFloatType();
+  opt::analysis::FloatConstant float_constant(
+      GetIRContext()->get_type_mgr()->GetType(float_type_id)->AsFloat(),
+      {word});
+  auto existing_constant =
+      GetIRContext()->get_constant_mgr()->FindConstant(&float_constant);
+  if (existing_constant) {
+    return GetIRContext()
+        ->get_constant_mgr()
+        ->GetDefiningInstruction(existing_constant)
+        ->result_id();
+  }
+  auto result = GetFuzzerContext()->GetFreshId();
+  ApplyTransformation(
+      TransformationAddConstantScalar(result, float_type_id, {word}));
+  return result;
+}
+
+uint32_t FuzzerPass::FindOrCreateBoolConstant(bool value) {
+  auto bool_type_id = FindOrCreateBoolType();
+  opt::analysis::BoolConstant bool_constant(
+      GetIRContext()->get_type_mgr()->GetType(bool_type_id)->AsBool(), value);
+  auto existing_constant =
+      GetIRContext()->get_constant_mgr()->FindConstant(&bool_constant);
+  if (existing_constant) {
+    return GetIRContext()
+        ->get_constant_mgr()
+        ->GetDefiningInstruction(existing_constant)
+        ->result_id();
+  }
+  auto result = GetFuzzerContext()->GetFreshId();
+  ApplyTransformation(TransformationAddConstantBoolean(result, value));
+  return result;
+}
+
+uint32_t FuzzerPass::FindOrCreateGlobalUndef(uint32_t type_id) {
+  for (auto& inst : GetIRContext()->types_values()) {
+    if (inst.opcode() == SpvOpUndef && inst.type_id() == type_id) {
+      return inst.result_id();
+    }
+  }
+  auto result = GetFuzzerContext()->GetFreshId();
+  ApplyTransformation(TransformationAddGlobalUndef(result, type_id));
+  return result;
+}
+
+std::pair<std::vector<uint32_t>, std::map<uint32_t, std::vector<uint32_t>>>
+FuzzerPass::GetAvailableBasicTypesAndPointers(
+    SpvStorageClass storage_class) const {
+  // Records all of the basic types available in the module.
+  std::set<uint32_t> basic_types;
+
+  // For each basic type, records all the associated pointer types that target
+  // the basic type and that have |storage_class| as their storage class.
+  std::map<uint32_t, std::vector<uint32_t>> basic_type_to_pointers;
+
+  for (auto& inst : GetIRContext()->types_values()) {
+    // For each basic type that we come across, record type, and the fact that
+    // we cannot yet have seen any pointers that use the basic type as its
+    // pointee type.
+    //
+    // For pointer types with basic pointee types, associate the pointer type
+    // with the basic type.
+    switch (inst.opcode()) {
+      case SpvOpTypeBool:
+      case SpvOpTypeFloat:
+      case SpvOpTypeInt:
+      case SpvOpTypeMatrix:
+      case SpvOpTypeVector:
+        // These are all basic types.
+        basic_types.insert(inst.result_id());
+        basic_type_to_pointers.insert({inst.result_id(), {}});
+        break;
+      case SpvOpTypeArray:
+        // An array type is basic if its base type is basic.
+        if (basic_types.count(inst.GetSingleWordInOperand(0))) {
+          basic_types.insert(inst.result_id());
+          basic_type_to_pointers.insert({inst.result_id(), {}});
+        }
+        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;
+          }
+        }
+        if (all_members_are_basic_types) {
+          basic_types.insert(inst.result_id());
+          basic_type_to_pointers.insert({inst.result_id(), {}});
+        }
+        break;
+      }
+      case SpvOpTypePointer: {
+        // We are interested in the pointer if its pointee type is basic and it
+        // has the right storage class.
+        auto pointee_type = inst.GetSingleWordInOperand(1);
+        if (inst.GetSingleWordInOperand(0) == storage_class &&
+            basic_types.count(pointee_type)) {
+          // The pointer has the desired storage class, and its pointee type is
+          // a basic type, so we are interested in it.  Associate it with its
+          // basic type.
+          basic_type_to_pointers.at(pointee_type).push_back(inst.result_id());
+        }
+        break;
+      }
+      default:
+        break;
+    }
+  }
+  return {{basic_types.begin(), basic_types.end()}, basic_type_to_pointers};
+}
+
+uint32_t FuzzerPass::FindOrCreateZeroConstant(
+    uint32_t scalar_or_composite_type_id) {
+  auto type_instruction =
+      GetIRContext()->get_def_use_mgr()->GetDef(scalar_or_composite_type_id);
+  assert(type_instruction && "The type instruction must exist.");
+  switch (type_instruction->opcode()) {
+    case SpvOpTypeBool:
+      return FindOrCreateBoolConstant(false);
+    case SpvOpTypeFloat:
+      return FindOrCreate32BitFloatConstant(0);
+    case SpvOpTypeInt:
+      return FindOrCreate32BitIntegerConstant(
+          0, type_instruction->GetSingleWordInOperand(1) != 0);
+    case SpvOpTypeArray: {
+      return GetZeroConstantForHomogeneousComposite(
+          *type_instruction, type_instruction->GetSingleWordInOperand(0),
+          fuzzerutil::GetArraySize(*type_instruction, GetIRContext()));
+    }
+    case SpvOpTypeMatrix:
+    case SpvOpTypeVector: {
+      return GetZeroConstantForHomogeneousComposite(
+          *type_instruction, type_instruction->GetSingleWordInOperand(0),
+          type_instruction->GetSingleWordInOperand(1));
+    }
+    case SpvOpTypeStruct: {
+      std::vector<const opt::analysis::Constant*> field_zero_constants;
+      std::vector<uint32_t> field_zero_ids;
+      for (uint32_t index = 0; index < type_instruction->NumInOperands();
+           index++) {
+        uint32_t field_constant_id = FindOrCreateZeroConstant(
+            type_instruction->GetSingleWordInOperand(index));
+        field_zero_ids.push_back(field_constant_id);
+        field_zero_constants.push_back(
+            GetIRContext()->get_constant_mgr()->FindDeclaredConstant(
+                field_constant_id));
+      }
+      return FindOrCreateCompositeConstant(
+          *type_instruction, field_zero_constants, field_zero_ids);
+    }
+    default:
+      assert(false && "Unknown type.");
+      return 0;
+  }
+}
+
+uint32_t FuzzerPass::FindOrCreateCompositeConstant(
+    const opt::Instruction& composite_type_instruction,
+    const std::vector<const opt::analysis::Constant*>& constants,
+    const std::vector<uint32_t>& constant_ids) {
+  assert(constants.size() == constant_ids.size() &&
+         "Precondition: |constants| and |constant_ids| must be in "
+         "correspondence.");
+
+  opt::analysis::Type* composite_type = GetIRContext()->get_type_mgr()->GetType(
+      composite_type_instruction.result_id());
+  std::unique_ptr<opt::analysis::Constant> composite_constant;
+  if (composite_type->AsArray()) {
+    composite_constant = MakeUnique<opt::analysis::ArrayConstant>(
+        composite_type->AsArray(), constants);
+  } else if (composite_type->AsMatrix()) {
+    composite_constant = MakeUnique<opt::analysis::MatrixConstant>(
+        composite_type->AsMatrix(), constants);
+  } else if (composite_type->AsStruct()) {
+    composite_constant = MakeUnique<opt::analysis::StructConstant>(
+        composite_type->AsStruct(), constants);
+  } else if (composite_type->AsVector()) {
+    composite_constant = MakeUnique<opt::analysis::VectorConstant>(
+        composite_type->AsVector(), constants);
+  } else {
+    assert(false &&
+           "Precondition: |composite_type| must declare a composite type.");
+    return 0;
+  }
+
+  uint32_t existing_constant =
+      GetIRContext()->get_constant_mgr()->FindDeclaredConstant(
+          composite_constant.get(), composite_type_instruction.result_id());
+  if (existing_constant) {
+    return existing_constant;
+  }
+  uint32_t result = GetFuzzerContext()->GetFreshId();
+  ApplyTransformation(TransformationAddConstantComposite(
+      result, composite_type_instruction.result_id(), constant_ids));
+  return result;
+}
+
+uint32_t FuzzerPass::GetZeroConstantForHomogeneousComposite(
+    const opt::Instruction& composite_type_instruction,
+    uint32_t component_type_id, uint32_t num_components) {
+  std::vector<const opt::analysis::Constant*> zero_constants;
+  std::vector<uint32_t> zero_ids;
+  uint32_t zero_component = FindOrCreateZeroConstant(component_type_id);
+  const opt::analysis::Constant* registered_zero_component =
+      GetIRContext()->get_constant_mgr()->FindDeclaredConstant(zero_component);
+  for (uint32_t i = 0; i < num_components; i++) {
+    zero_constants.push_back(registered_zero_component);
+    zero_ids.push_back(zero_component);
+  }
+  return FindOrCreateCompositeConstant(composite_type_instruction,
+                                       zero_constants, zero_ids);
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass.h b/source/fuzz/fuzzer_pass.h
index e3bf8e8..94b8dfa 100644
--- a/source/fuzz/fuzzer_pass.h
+++ b/source/fuzz/fuzzer_pass.h
@@ -18,9 +18,9 @@
 #include <functional>
 #include <vector>
 
-#include "source/fuzz/fact_manager.h"
 #include "source/fuzz/fuzzer_context.h"
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/transformation_context.h"
 #include "source/opt/ir_context.h"
 
 namespace spvtools {
@@ -29,22 +29,25 @@
 // Interface for applying a pass of transformations to a module.
 class FuzzerPass {
  public:
-  FuzzerPass(opt::IRContext* ir_context, FactManager* fact_manager,
+  FuzzerPass(opt::IRContext* ir_context,
+             TransformationContext* transformation_context,
              FuzzerContext* fuzzer_context,
              protobufs::TransformationSequence* transformations);
 
   virtual ~FuzzerPass();
 
   // Applies the pass to the module |ir_context_|, assuming and updating
-  // facts from |fact_manager_|, and using |fuzzer_context_| to guide the
-  // process.  Appends to |transformations_| all transformations that were
-  // applied during the pass.
+  // information from |transformation_context_|, and using |fuzzer_context_| to
+  // guide the process.  Appends to |transformations_| all transformations that
+  // were applied during the pass.
   virtual void Apply() = 0;
 
  protected:
   opt::IRContext* GetIRContext() const { return ir_context_; }
 
-  FactManager* GetFactManager() const { return fact_manager_; }
+  TransformationContext* GetTransformationContext() const {
+    return transformation_context_;
+  }
 
   FuzzerContext* GetFuzzerContext() const { return fuzzer_context_; }
 
@@ -61,47 +64,180 @@
   // |instruction_is_relevant| predicate.  This, for instance, could ignore all
   // instructions that have a particular decoration.
   std::vector<opt::Instruction*> FindAvailableInstructions(
-      const opt::Function& function, opt::BasicBlock* block,
-      opt::BasicBlock::iterator inst_it,
+      opt::Function* function, opt::BasicBlock* block,
+      const opt::BasicBlock::iterator& inst_it,
       std::function<bool(opt::IRContext*, opt::Instruction*)>
-          instruction_is_relevant);
+          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.
   //
-  // The code to manipulate the instruction descriptor is a bit fiddly, and the
+  // 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
   // transformation passes.
   //
-  // The function |maybe_apply_transformation| is invoked for each instruction
-  // |inst_it| in block |block| of function |function| that is encountered.  The
+  // The function |action| is invoked for each instruction |inst_it| in block
+  // |block| of function |function| that is encountered.  The
   // |instruction_descriptor| parameter to the function object allows |inst_it|
   // to be identified.
   //
-  // The job of |maybe_apply_transformation| is to randomly decide whether to
-  // try to apply some transformation, and then - if selected - to attempt to
-  // apply it.
-  void MaybeAddTransformationBeforeEachInstruction(
+  // In most intended use cases, the job of |action| is to randomly decide
+  // whether to try to apply some transformation, and then - if selected - to
+  // attempt to apply it.
+  void ForEachInstructionWithInstructionDescriptor(
       std::function<
-          void(const opt::Function& function, opt::BasicBlock* block,
+          void(opt::Function* function, opt::BasicBlock* block,
                opt::BasicBlock::iterator inst_it,
                const protobufs::InstructionDescriptor& instruction_descriptor)>
-          maybe_apply_transformation);
+          action);
 
-  // A generic helper for applying a transforamtion that should be appplicable
+  // A generic helper for applying a transformation that should be applicable
   // by construction, and adding it to the sequence of applied transformations.
   template <typename TransformationType>
   void ApplyTransformation(const TransformationType& transformation) {
-    assert(transformation.IsApplicable(GetIRContext(), *GetFactManager()) &&
+    assert(transformation.IsApplicable(GetIRContext(),
+                                       *GetTransformationContext()) &&
            "Transformation should be applicable by construction.");
-    transformation.Apply(GetIRContext(), GetFactManager());
+    transformation.Apply(GetIRContext(), GetTransformationContext());
     *GetTransformations()->add_transformation() = transformation.ToMessage();
   }
 
+  // Returns the id of an OpTypeBool instruction.  If such an instruction does
+  // not exist, a transformation is applied to add it.
+  uint32_t FindOrCreateBoolType();
+
+  // Returns the id of an OpTypeInt instruction, with width 32 and signedness
+  // specified by |is_signed|.  If such an instruction does not exist, a
+  // transformation is applied to add it.
+  uint32_t FindOrCreate32BitIntegerType(bool is_signed);
+
+  // Returns the id of an OpTypeFloat instruction, with width 32.  If such an
+  // instruction does not exist, a transformation is applied to add it.
+  uint32_t FindOrCreate32BitFloatType();
+
+  // Returns the id of an OpTypeFunction %<return_type_id> %<...argument_id>
+  // instruction. If such an instruction doesn't exist, a transformation
+  // is applied to create a new one.
+  uint32_t FindOrCreateFunctionType(uint32_t return_type_id,
+                                    const std::vector<uint32_t>& argument_id);
+
+  // Returns the id of an OpTypeVector instruction, with |component_type_id|
+  // (which must already exist) as its base type, and |component_count|
+  // elements (which must be in the range [2, 4]).  If such an instruction does
+  // not exist, a transformation is applied to add it.
+  uint32_t FindOrCreateVectorType(uint32_t component_type_id,
+                                  uint32_t component_count);
+
+  // Returns the id of an OpTypeMatrix instruction, with |column_count| columns
+  // and |row_count| rows (each of which must be in the range [2, 4]).  If the
+  // float and vector types required to build this matrix type or the matrix
+  // type itself do not exist, transformations are applied to add them.
+  uint32_t FindOrCreateMatrixType(uint32_t column_count, uint32_t row_count);
+
+  // Returns the id of a pointer type with base type |base_type_id| (which must
+  // already exist) and storage class |storage_class|.  A transformation is
+  // applied to add the pointer if it does not already exist.
+  uint32_t FindOrCreatePointerType(uint32_t base_type_id,
+                                   SpvStorageClass storage_class);
+
+  // Returns the id of an OpTypePointer instruction, with a 32-bit integer base
+  // type of signedness specified by |is_signed|.  If the pointer type or
+  // required integer base type do not exist, transformations are applied to add
+  // them.
+  uint32_t FindOrCreatePointerTo32BitIntegerType(bool is_signed,
+                                                 SpvStorageClass storage_class);
+
+  // Returns the id of an OpConstant instruction, with 32-bit integer type of
+  // signedness specified by |is_signed|, with |word| as its value.  If either
+  // the required integer type or the constant do not exist, transformations are
+  // applied to add them.
+  uint32_t FindOrCreate32BitIntegerConstant(uint32_t word, bool is_signed);
+
+  // Returns the id of an OpConstant instruction, with 32-bit floating-point
+  // type, with |word| as its value.  If either the required floating-point type
+  // or the constant do not exist, transformations are applied to add them.
+  uint32_t FindOrCreate32BitFloatConstant(uint32_t word);
+
+  // Returns the id of an OpConstantTrue or OpConstantFalse instruction,
+  // according to |value|.  If either the required instruction or the bool
+  // type do not exist, transformations are applied to add them.
+  uint32_t FindOrCreateBoolConstant(bool value);
+
+  // Returns the result id of an instruction of the form:
+  //   %id = OpUndef %|type_id|
+  // If no such instruction exists, a transformation is applied to add it.
+  uint32_t FindOrCreateGlobalUndef(uint32_t type_id);
+
+  // Define a *basic type* to be an integer, boolean or floating-point type,
+  // or a matrix, vector, struct or fixed-size array built from basic types.  In
+  // particular, a basic type cannot contain an opaque type (such as an image),
+  // or a runtime-sized array.
+  //
+  // Yields a pair, (basic_type_ids, basic_type_ids_to_pointers), such that:
+  // - basic_type_ids captures every basic type declared in the module.
+  // - basic_type_ids_to_pointers maps every such basic type to the sequence
+  //   of all pointer types that have storage class |storage_class| and the
+  //   given basic type as their pointee type.  The sequence may be empty for
+  //   some basic types if no pointers to those types are defined for the given
+  //   storage class, and the sequence will have multiple elements if there are
+  //   repeated pointer declarations for the same basic type and storage class.
+  std::pair<std::vector<uint32_t>, std::map<uint32_t, std::vector<uint32_t>>>
+  GetAvailableBasicTypesAndPointers(SpvStorageClass storage_class) const;
+
+  // Given a type id, |scalar_or_composite_type_id|, which must correspond to
+  // some scalar or composite type, returns the result id of an instruction
+  // defining a constant of the given type that is zero or false at everywhere.
+  // If such an instruction does not yet exist, transformations are applied to
+  // add it.
+  //
+  // Examples:
+  // --------------+-------------------------------
+  //   TYPE        | RESULT is id corresponding to
+  // --------------+-------------------------------
+  //   bool        | false
+  // --------------+-------------------------------
+  //   bvec4       | (false, false, false, false)
+  // --------------+-------------------------------
+  //   float       | 0.0
+  // --------------+-------------------------------
+  //   vec2        | (0.0, 0.0)
+  // --------------+-------------------------------
+  //   int[3]      | [0, 0, 0]
+  // --------------+-------------------------------
+  //   struct S {  |
+  //     int i;    | S(0, false, (0u, 0u))
+  //     bool b;   |
+  //     uint2 u;  |
+  //   }           |
+  // --------------+-------------------------------
+  uint32_t FindOrCreateZeroConstant(uint32_t scalar_or_composite_type_id);
+
  private:
+  // Array, matrix and vector are *homogeneous* composite types in the sense
+  // that every component of one of these types has the same type.  Given a
+  // homogeneous composite type instruction, |composite_type_instruction|,
+  // returns the id of a composite constant instruction for which every element
+  // is zero/false.  If such an instruction does not yet exist, transformations
+  // are applied to add it.
+  uint32_t GetZeroConstantForHomogeneousComposite(
+      const opt::Instruction& composite_type_instruction,
+      uint32_t component_type_id, uint32_t num_components);
+
+  // Helper to find an existing composite constant instruction of the given
+  // composite type with the given constant components, or to apply
+  // transformations to create such an instruction if it does not yet exist.
+  // Parameter |composite_type_instruction| must be a composite type
+  // instruction.  The parameters |constants| and |constant_ids| must have the
+  // same size, and it must be the case that for each i, |constant_ids[i]| is
+  // the result id of an instruction that defines |constants[i]|.
+  uint32_t FindOrCreateCompositeConstant(
+      const opt::Instruction& composite_type_instruction,
+      const std::vector<const opt::analysis::Constant*>& constants,
+      const std::vector<uint32_t>& constant_ids);
+
   opt::IRContext* ir_context_;
-  FactManager* fact_manager_;
+  TransformationContext* transformation_context_;
   FuzzerContext* fuzzer_context_;
   protobufs::TransformationSequence* transformations_;
 };
diff --git a/source/fuzz/fuzzer_pass_add_access_chains.cpp b/source/fuzz/fuzzer_pass_add_access_chains.cpp
new file mode 100644
index 0000000..b9c1eed
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_access_chains.cpp
@@ -0,0 +1,170 @@
+// 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_access_chains.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/transformation_access_chain.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassAddAccessChains::FuzzerPassAddAccessChains(
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassAddAccessChains::~FuzzerPassAddAccessChains() = default;
+
+void FuzzerPassAddAccessChains::Apply() {
+  ForEachInstructionWithInstructionDescriptor(
+      [this](opt::Function* function, opt::BasicBlock* block,
+             opt::BasicBlock::iterator inst_it,
+             const protobufs::InstructionDescriptor& instruction_descriptor)
+          -> void {
+        assert(inst_it->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");
+
+        // Check whether it is legitimate to insert an access chain
+        // instruction before this instruction.
+        if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpAccessChain,
+                                                          inst_it)) {
+          return;
+        }
+
+        // Randomly decide whether to try inserting a load here.
+        if (!GetFuzzerContext()->ChoosePercentage(
+                GetFuzzerContext()->GetChanceOfAddingAccessChain())) {
+          return;
+        }
+
+        // Get all of the pointers that are currently in scope, excluding
+        // explicitly null and undefined pointers.
+        std::vector<opt::Instruction*> relevant_pointer_instructions =
+            FindAvailableInstructions(
+                function, block, inst_it,
+                [](opt::IRContext* context,
+                   opt::Instruction* instruction) -> bool {
+                  if (!instruction->result_id() || !instruction->type_id()) {
+                    // A pointer needs both a result and type id.
+                    return false;
+                  }
+                  switch (instruction->opcode()) {
+                    case SpvOpConstantNull:
+                    case SpvOpUndef:
+                      // Do not allow making an access chain from a null or
+                      // undefined pointer.  (We can eliminate these cases
+                      // before actually checking that the instruction is a
+                      // pointer.)
+                      return false;
+                    default:
+                      break;
+                  }
+                  // If the instruction has pointer type, we can legitimately
+                  // make an access chain from it.
+                  return context->get_def_use_mgr()
+                             ->GetDef(instruction->type_id())
+                             ->opcode() == SpvOpTypePointer;
+                });
+
+        // At this point, |relevant_instructions| contains all the pointers
+        // we might think of making an access chain from.
+        if (relevant_pointer_instructions.empty()) {
+          return;
+        }
+
+        auto chosen_pointer =
+            relevant_pointer_instructions[GetFuzzerContext()->RandomIndex(
+                relevant_pointer_instructions)];
+        std::vector<uint32_t> index_ids;
+        auto pointer_type = GetIRContext()->get_def_use_mgr()->GetDef(
+            chosen_pointer->type_id());
+        uint32_t subobject_type_id = pointer_type->GetSingleWordInOperand(1);
+        while (true) {
+          auto subobject_type =
+              GetIRContext()->get_def_use_mgr()->GetDef(subobject_type_id);
+          if (!spvOpcodeIsComposite(subobject_type->opcode())) {
+            break;
+          }
+          if (!GetFuzzerContext()->ChoosePercentage(
+                  GetFuzzerContext()
+                      ->GetChanceOfGoingDeeperWhenMakingAccessChain())) {
+            break;
+          }
+          uint32_t bound;
+          switch (subobject_type->opcode()) {
+            case SpvOpTypeArray:
+              bound = fuzzerutil::GetArraySize(*subobject_type, GetIRContext());
+              break;
+            case SpvOpTypeMatrix:
+            case SpvOpTypeVector:
+              bound = subobject_type->GetSingleWordInOperand(1);
+              break;
+            case SpvOpTypeStruct:
+              bound = fuzzerutil::GetNumberOfStructMembers(*subobject_type);
+              break;
+            default:
+              assert(false && "Not a composite type opcode.");
+              // Set the bound to a value in order to keep release compilers
+              // happy.
+              bound = 0;
+              break;
+          }
+          if (bound == 0) {
+            // It is possible for a composite type to legitimately have zero
+            // sub-components, at least in the case of a struct, which
+            // can have no fields.
+            break;
+          }
+
+          // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3179) We
+          //  could allow non-constant indices when looking up non-structs,
+          //  using clamping to ensure they are in-bounds.
+          uint32_t index_value =
+              GetFuzzerContext()->GetRandomIndexForAccessChain(bound);
+          index_ids.push_back(FindOrCreate32BitIntegerConstant(
+              index_value, GetFuzzerContext()->ChooseEven()));
+          switch (subobject_type->opcode()) {
+            case SpvOpTypeArray:
+            case SpvOpTypeMatrix:
+            case SpvOpTypeVector:
+              subobject_type_id = subobject_type->GetSingleWordInOperand(0);
+              break;
+            case SpvOpTypeStruct:
+              subobject_type_id =
+                  subobject_type->GetSingleWordInOperand(index_value);
+              break;
+            default:
+              assert(false && "Not a composite type opcode.");
+          }
+        }
+        // The transformation we are about to create will only apply if a
+        // pointer suitable for the access chain's result type exists, so we
+        // create one if it does not.
+        FindOrCreatePointerType(subobject_type_id,
+                                static_cast<SpvStorageClass>(
+                                    pointer_type->GetSingleWordInOperand(0)));
+        // Apply the transformation to add an access chain.
+        ApplyTransformation(TransformationAccessChain(
+            GetFuzzerContext()->GetFreshId(), chosen_pointer->result_id(),
+            index_ids, instruction_descriptor));
+      });
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_add_access_chains.h b/source/fuzz/fuzzer_pass_add_access_chains.h
new file mode 100644
index 0000000..8649296
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_access_chains.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_ADD_ACCESS_CHAINS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_ADD_ACCESS_CHAINS_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Fuzzer pass that randomly adds access chains based on pointers available in
+// the module.  Other passes can use these access chains, e.g. by loading from
+// them.
+class FuzzerPassAddAccessChains : public FuzzerPass {
+ public:
+  FuzzerPassAddAccessChains(opt::IRContext* ir_context,
+                            TransformationContext* transformation_context,
+                            FuzzerContext* fuzzer_context,
+                            protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassAddAccessChains();
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_ADD_ACCESS_CHAINS_H_
diff --git a/source/fuzz/fuzzer_pass_add_composite_types.cpp b/source/fuzz/fuzzer_pass_add_composite_types.cpp
new file mode 100644
index 0000000..9b0dda8
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_composite_types.cpp
@@ -0,0 +1,139 @@
+// 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_types.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/transformation_add_type_array.h"
+#include "source/fuzz/transformation_add_type_struct.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassAddCompositeTypes::FuzzerPassAddCompositeTypes(
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassAddCompositeTypes::~FuzzerPassAddCompositeTypes() = default;
+
+void FuzzerPassAddCompositeTypes::Apply() {
+  MaybeAddMissingVectorTypes();
+  MaybeAddMissingMatrixTypes();
+
+  // Randomly interleave between adding struct and array composite types
+  while (GetFuzzerContext()->ChoosePercentage(
+      GetFuzzerContext()->GetChanceOfAddingArrayOrStructType())) {
+    if (GetFuzzerContext()->ChoosePercentage(
+            GetFuzzerContext()->GetChanceOfChoosingStructTypeVsArrayType())) {
+      AddNewStructType();
+    } else {
+      AddNewArrayType();
+    }
+  }
+}
+
+void FuzzerPassAddCompositeTypes::MaybeAddMissingVectorTypes() {
+  // Functions to lazily supply scalar base types on demand if we decide to
+  // create vectors with the relevant base types.
+  std::function<uint32_t()> bool_type_supplier = [this]() -> uint32_t {
+    return FindOrCreateBoolType();
+  };
+  std::function<uint32_t()> float_type_supplier = [this]() -> uint32_t {
+    return FindOrCreate32BitFloatType();
+  };
+  std::function<uint32_t()> int_type_supplier = [this]() -> uint32_t {
+    return FindOrCreate32BitIntegerType(true);
+  };
+  std::function<uint32_t()> uint_type_supplier = [this]() -> uint32_t {
+    return FindOrCreate32BitIntegerType(false);
+  };
+
+  // Consider each of the base types with which we can make vectors.
+  for (auto& base_type_supplier : {bool_type_supplier, float_type_supplier,
+                                   int_type_supplier, uint_type_supplier}) {
+    // Consider each valid vector size.
+    for (uint32_t size = 2; size <= 4; size++) {
+      // Randomly decide whether to create (if it does not already exist) a
+      // vector with this size and base type.
+      if (GetFuzzerContext()->ChoosePercentage(
+              GetFuzzerContext()->GetChanceOfAddingVectorType())) {
+        FindOrCreateVectorType(base_type_supplier(), size);
+      }
+    }
+  }
+}
+
+void FuzzerPassAddCompositeTypes::MaybeAddMissingMatrixTypes() {
+  // Consider every valid matrix dimension.
+  for (uint32_t columns = 2; columns <= 4; columns++) {
+    for (uint32_t rows = 2; rows <= 4; rows++) {
+      // Randomly decide whether to create (if it does not already exist) a
+      // matrix with these dimensions.  As matrices can only have floating-point
+      // base type, we do not need to consider multiple base types as in the
+      // case for vectors.
+      if (GetFuzzerContext()->ChoosePercentage(
+              GetFuzzerContext()->GetChanceOfAddingMatrixType())) {
+        FindOrCreateMatrixType(columns, rows);
+      }
+    }
+  }
+}
+
+void FuzzerPassAddCompositeTypes::AddNewArrayType() {
+  ApplyTransformation(TransformationAddTypeArray(
+      GetFuzzerContext()->GetFreshId(), ChooseScalarOrCompositeType(),
+      FindOrCreate32BitIntegerConstant(
+          GetFuzzerContext()->GetRandomSizeForNewArray(), false)));
+}
+
+void FuzzerPassAddCompositeTypes::AddNewStructType() {
+  std::vector<uint32_t> field_type_ids;
+  do {
+    field_type_ids.push_back(ChooseScalarOrCompositeType());
+  } while (GetFuzzerContext()->ChoosePercentage(
+      GetFuzzerContext()->GetChanceOfAddingAnotherStructField()));
+  ApplyTransformation(TransformationAddTypeStruct(
+      GetFuzzerContext()->GetFreshId(), field_type_ids));
+}
+
+uint32_t FuzzerPassAddCompositeTypes::ChooseScalarOrCompositeType() {
+  // Gather up all the possibly-relevant types.
+  std::vector<uint32_t> candidates;
+  for (auto& inst : GetIRContext()->types_values()) {
+    switch (inst.opcode()) {
+      case SpvOpTypeArray:
+      case SpvOpTypeBool:
+      case SpvOpTypeFloat:
+      case SpvOpTypeInt:
+      case SpvOpTypeMatrix:
+      case SpvOpTypeStruct:
+      case SpvOpTypeVector:
+        candidates.push_back(inst.result_id());
+        break;
+      default:
+        break;
+    }
+  }
+  assert(!candidates.empty() &&
+         "This function should only be called if there is at least one scalar "
+         "or composite type available.");
+  // Return one of these types at random.
+  return candidates[GetFuzzerContext()->RandomIndex(candidates)];
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_add_composite_types.h b/source/fuzz/fuzzer_pass_add_composite_types.h
new file mode 100644
index 0000000..87bc0ff
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_composite_types.h
@@ -0,0 +1,61 @@
+// 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_TYPES_H_
+#define SOURCE_FUZZ_FUZZER_PASS_ADD_COMPOSITE_TYPES_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Fuzzer pass that randomly adds missing vector and matrix types, and new
+// array and struct types, to the module.
+class FuzzerPassAddCompositeTypes : public FuzzerPass {
+ public:
+  FuzzerPassAddCompositeTypes(
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassAddCompositeTypes();
+
+  void Apply() override;
+
+ private:
+  // Creates an array of a random size with a random existing base type and adds
+  // it to the module.
+  void AddNewArrayType();
+
+  // Creates a struct with fields of random existing types and adds it to the
+  // module.
+  void AddNewStructType();
+
+  // For each vector type not already present in the module, randomly decides
+  // whether to add it to the module.
+  void MaybeAddMissingVectorTypes();
+
+  // For each matrix type not already present in the module, randomly decides
+  // whether to add it to the module.
+  void MaybeAddMissingMatrixTypes();
+
+  // Returns the id of a scalar or composite type declared in the module,
+  // chosen randomly.
+  uint32_t ChooseScalarOrCompositeType();
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_ADD_COMPOSITE_TYPES_H_
diff --git a/source/fuzz/fuzzer_pass_add_dead_blocks.cpp b/source/fuzz/fuzzer_pass_add_dead_blocks.cpp
index c9bc9c4..4e9db1f 100644
--- a/source/fuzz/fuzzer_pass_add_dead_blocks.cpp
+++ b/source/fuzz/fuzzer_pass_add_dead_blocks.cpp
@@ -21,10 +21,11 @@
 namespace fuzz {
 
 FuzzerPassAddDeadBlocks::FuzzerPassAddDeadBlocks(
-    opt::IRContext* ir_context, FactManager* fact_manager,
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
     protobufs::TransformationSequence* transformations)
-    : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
 
 FuzzerPassAddDeadBlocks::~FuzzerPassAddDeadBlocks() = default;
 
@@ -53,8 +54,9 @@
   }
   // Apply all those transformations that are in fact applicable.
   for (auto& transformation : candidate_transformations) {
-    if (transformation.IsApplicable(GetIRContext(), *GetFactManager())) {
-      transformation.Apply(GetIRContext(), GetFactManager());
+    if (transformation.IsApplicable(GetIRContext(),
+                                    *GetTransformationContext())) {
+      transformation.Apply(GetIRContext(), GetTransformationContext());
       *GetTransformations()->add_transformation() = transformation.ToMessage();
     }
   }
diff --git a/source/fuzz/fuzzer_pass_add_dead_blocks.h b/source/fuzz/fuzzer_pass_add_dead_blocks.h
index 01e3843..d78f088 100644
--- a/source/fuzz/fuzzer_pass_add_dead_blocks.h
+++ b/source/fuzz/fuzzer_pass_add_dead_blocks.h
@@ -24,7 +24,8 @@
 // passes can then manipulate such blocks.
 class FuzzerPassAddDeadBlocks : public FuzzerPass {
  public:
-  FuzzerPassAddDeadBlocks(opt::IRContext* ir_context, FactManager* fact_manager,
+  FuzzerPassAddDeadBlocks(opt::IRContext* ir_context,
+                          TransformationContext* transformation_context,
                           FuzzerContext* fuzzer_context,
                           protobufs::TransformationSequence* transformations);
 
diff --git a/source/fuzz/fuzzer_pass_add_dead_breaks.cpp b/source/fuzz/fuzzer_pass_add_dead_breaks.cpp
index fa6b098..6b171dc 100644
--- a/source/fuzz/fuzzer_pass_add_dead_breaks.cpp
+++ b/source/fuzz/fuzzer_pass_add_dead_breaks.cpp
@@ -13,7 +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"
 
@@ -21,10 +21,11 @@
 namespace fuzz {
 
 FuzzerPassAddDeadBreaks::FuzzerPassAddDeadBreaks(
-    opt::IRContext* ir_context, FactManager* fact_manager,
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
     protobufs::TransformationSequence* transformations)
-    : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
 
 FuzzerPassAddDeadBreaks::~FuzzerPassAddDeadBreaks() = default;
 
@@ -34,11 +35,16 @@
   // We consider each function separately.
   for (auto& function : *GetIRContext()->module()) {
     // For a given function, we find all the merge blocks in that function.
-    std::vector<uint32_t> merge_block_ids;
+    std::vector<opt::BasicBlock*> merge_blocks;
     for (auto& block : function) {
       auto maybe_merge_id = block.MergeBlockIdIfAny();
       if (maybe_merge_id) {
-        merge_block_ids.push_back(maybe_merge_id);
+        auto merge_block =
+            fuzzerutil::MaybeFindBlock(GetIRContext(), maybe_merge_id);
+
+        assert(merge_block && "Merge block can't be null");
+
+        merge_blocks.push_back(merge_block);
       }
     }
     // We rather aggressively consider the possibility of adding a break from
@@ -46,14 +52,36 @@
     // inapplicable as they would be illegal.  That's OK - we later discard the
     // ones that turn out to be no good.
     for (auto& block : function) {
-      for (auto merge_block_id : merge_block_ids) {
-        // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2856): right
-        //  now we completely ignore OpPhi instructions at merge blocks.  This
-        //  will lead to interesting opportunities being missed.
+      for (auto* merge_block : merge_blocks) {
+        // Populate this vector with ids that are available at the branch point
+        // of this basic block. We will use these ids to update OpPhi
+        // instructions later.
+        std::vector<uint32_t> phi_ids;
+
+        // Determine how we need to adjust OpPhi instructions' operands
+        // for this transformation to be valid.
+        //
+        // If |block| has a branch to |merge_block|, the latter must have all of
+        // its OpPhi instructions set up correctly - we don't need to adjust
+        // anything.
+        if (!block.IsSuccessor(merge_block)) {
+          merge_block->ForEachPhiInst([this, &phi_ids](opt::Instruction* phi) {
+            // Add an additional operand for OpPhi instruction.
+            //
+            // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3177):
+            // If we have a way to communicate to the fact manager
+            // that a specific id use is irrelevant and could be replaced with
+            // something else, we should add such a fact about the zero
+            // provided as an OpPhi operand
+            phi_ids.push_back(FindOrCreateZeroConstant(phi->type_id()));
+          });
+        }
+
         auto candidate_transformation = TransformationAddDeadBreak(
-            block.id(), merge_block_id, GetFuzzerContext()->ChooseEven(), {});
-        if (candidate_transformation.IsApplicable(GetIRContext(),
-                                                  *GetFactManager())) {
+            block.id(), merge_block->id(), GetFuzzerContext()->ChooseEven(),
+            std::move(phi_ids));
+        if (candidate_transformation.IsApplicable(
+                GetIRContext(), *GetTransformationContext())) {
           // Only consider a transformation as a candidate if it is applicable.
           candidate_transformations.push_back(
               std::move(candidate_transformation));
@@ -82,10 +110,11 @@
     candidate_transformations.erase(candidate_transformations.begin() + index);
     // Probabilistically decide whether to try to apply it vs. ignore it, in the
     // case that it is applicable.
-    if (transformation.IsApplicable(GetIRContext(), *GetFactManager()) &&
+    if (transformation.IsApplicable(GetIRContext(),
+                                    *GetTransformationContext()) &&
         GetFuzzerContext()->ChoosePercentage(
             GetFuzzerContext()->GetChanceOfAddingDeadBreak())) {
-      transformation.Apply(GetIRContext(), GetFactManager());
+      transformation.Apply(GetIRContext(), GetTransformationContext());
       *GetTransformations()->add_transformation() = transformation.ToMessage();
     }
   }
diff --git a/source/fuzz/fuzzer_pass_add_dead_breaks.h b/source/fuzz/fuzzer_pass_add_dead_breaks.h
index 12a5095..c379eed 100644
--- a/source/fuzz/fuzzer_pass_add_dead_breaks.h
+++ b/source/fuzz/fuzzer_pass_add_dead_breaks.h
@@ -23,7 +23,8 @@
 // A fuzzer pass for adding dead break edges to the module.
 class FuzzerPassAddDeadBreaks : public FuzzerPass {
  public:
-  FuzzerPassAddDeadBreaks(opt::IRContext* ir_context, FactManager* fact_manager,
+  FuzzerPassAddDeadBreaks(opt::IRContext* ir_context,
+                          TransformationContext* transformation_context,
                           FuzzerContext* fuzzer_context,
                           protobufs::TransformationSequence* transformations);
 
diff --git a/source/fuzz/fuzzer_pass_add_dead_continues.cpp b/source/fuzz/fuzzer_pass_add_dead_continues.cpp
index 51bcb91..b8f07fb 100644
--- a/source/fuzz/fuzzer_pass_add_dead_continues.cpp
+++ b/source/fuzz/fuzzer_pass_add_dead_continues.cpp
@@ -13,7 +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"
 
@@ -21,10 +21,11 @@
 namespace fuzz {
 
 FuzzerPassAddDeadContinues::FuzzerPassAddDeadContinues(
-    opt::IRContext* ir_context, FactManager* fact_manager,
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
     protobufs::TransformationSequence* transformations)
-    : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
 
 FuzzerPassAddDeadContinues::~FuzzerPassAddDeadContinues() = default;
 
@@ -32,22 +33,54 @@
   // Consider every block in every function.
   for (auto& function : *GetIRContext()->module()) {
     for (auto& block : function) {
+      // Get the label id of the continue target of the innermost loop.
+      auto continue_block_id =
+          block.IsLoopHeader()
+              ? block.ContinueBlockId()
+              : GetIRContext()->GetStructuredCFGAnalysis()->LoopContinueBlock(
+                    block.id());
+
+      // This transformation is not applicable if current block is not inside a
+      // loop.
+      if (continue_block_id == 0) {
+        continue;
+      }
+
+      auto* continue_block =
+          fuzzerutil::MaybeFindBlock(GetIRContext(), continue_block_id);
+      assert(continue_block && "Continue block is null");
+
+      // Analyze return type of each OpPhi instruction in the continue target
+      // and provide an id for the transformation if needed.
+      std::vector<uint32_t> phi_ids;
+      // Check whether current block has an edge to the continue target.
+      // 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.
+          //
+          // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3177):
+          // If we have a way to communicate to the fact manager
+          // that a specific id use is irrelevant and could be replaced with
+          // something else, we should add such a fact about the zero
+          // provided as an OpPhi operand
+          phi_ids.push_back(FindOrCreateZeroConstant(phi->type_id()));
+        });
+      }
+
       // Make a transformation to add a dead continue from this node; if the
       // node turns out to be inappropriate (e.g. by not being in a loop) the
       // precondition for the transformation will fail and it will be ignored.
-      //
-      // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2856): right
-      //  now we completely ignore OpPhi instructions at continue targets.
-      //  This will lead to interesting opportunities being missed.
       auto candidate_transformation = TransformationAddDeadContinue(
-          block.id(), GetFuzzerContext()->ChooseEven(), {});
+          block.id(), GetFuzzerContext()->ChooseEven(), std::move(phi_ids));
       // Probabilistically decide whether to apply the transformation in the
       // case that it is applicable.
       if (candidate_transformation.IsApplicable(GetIRContext(),
-                                                *GetFactManager()) &&
+                                                *GetTransformationContext()) &&
           GetFuzzerContext()->ChoosePercentage(
               GetFuzzerContext()->GetChanceOfAddingDeadContinue())) {
-        candidate_transformation.Apply(GetIRContext(), GetFactManager());
+        candidate_transformation.Apply(GetIRContext(),
+                                       GetTransformationContext());
         *GetTransformations()->add_transformation() =
             candidate_transformation.ToMessage();
       }
diff --git a/source/fuzz/fuzzer_pass_add_dead_continues.h b/source/fuzz/fuzzer_pass_add_dead_continues.h
index d067f1c..b2acb93 100644
--- a/source/fuzz/fuzzer_pass_add_dead_continues.h
+++ b/source/fuzz/fuzzer_pass_add_dead_continues.h
@@ -24,7 +24,7 @@
 class FuzzerPassAddDeadContinues : public FuzzerPass {
  public:
   FuzzerPassAddDeadContinues(
-      opt::IRContext* ir_context, FactManager* fact_manager,
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
       protobufs::TransformationSequence* transformations);
 
diff --git a/source/fuzz/fuzzer_pass_add_equation_instructions.cpp b/source/fuzz/fuzzer_pass_add_equation_instructions.cpp
new file mode 100644
index 0000000..49c4a8a
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_equation_instructions.cpp
@@ -0,0 +1,239 @@
+// 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_equation_instructions.h"
+
+#include <vector>
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/transformation_equation_instruction.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassAddEquationInstructions::FuzzerPassAddEquationInstructions(
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassAddEquationInstructions::~FuzzerPassAddEquationInstructions() =
+    default;
+
+void FuzzerPassAddEquationInstructions::Apply() {
+  ForEachInstructionWithInstructionDescriptor(
+      [this](opt::Function* function, opt::BasicBlock* block,
+             opt::BasicBlock::iterator inst_it,
+             const protobufs::InstructionDescriptor& instruction_descriptor) {
+        if (!GetFuzzerContext()->ChoosePercentage(
+                GetFuzzerContext()->GetChanceOfAddingEquationInstruction())) {
+          return;
+        }
+
+        // Check that it is OK to add an equation instruction before the given
+        // instruction in principle - e.g. check that this does not lead to
+        // inserting before an OpVariable or OpPhi instruction.  We use OpIAdd
+        // as an example opcode for this check, to be representative of *some*
+        // opcode that defines an equation, even though we may choose a
+        // different opcode below.
+        if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpIAdd, inst_it)) {
+          return;
+        }
+
+        // Get all available instructions with result ids and types that are not
+        // OpUndef.
+        std::vector<opt::Instruction*> available_instructions =
+            FindAvailableInstructions(
+                function, block, inst_it,
+                [](opt::IRContext*, opt::Instruction* instruction) -> bool {
+                  return instruction->result_id() && instruction->type_id() &&
+                         instruction->opcode() != SpvOpUndef;
+                });
+
+        // Try the opcodes for which we know how to make ids at random until
+        // something works.
+        std::vector<SpvOp> candidate_opcodes = {SpvOpIAdd, SpvOpISub,
+                                                SpvOpLogicalNot, SpvOpSNegate};
+        do {
+          auto opcode =
+              GetFuzzerContext()->RemoveAtRandomIndex(&candidate_opcodes);
+          switch (opcode) {
+            case SpvOpIAdd:
+            case SpvOpISub: {
+              // Instructions of integer (scalar or vector) result type are
+              // suitable for these opcodes.
+              auto integer_instructions =
+                  GetIntegerInstructions(available_instructions);
+              if (!integer_instructions.empty()) {
+                // There is at least one such instruction, so pick one at random
+                // for the LHS of an equation.
+                auto lhs = integer_instructions.at(
+                    GetFuzzerContext()->RandomIndex(integer_instructions));
+
+                // For the RHS, we can use any instruction with an integer
+                // scalar/vector result type of the same number of components
+                // and the same bit-width for the underlying integer type.
+
+                // Work out the element count and bit-width.
+                auto lhs_type =
+                    GetIRContext()->get_type_mgr()->GetType(lhs->type_id());
+                uint32_t lhs_element_count;
+                uint32_t lhs_bit_width;
+                if (lhs_type->AsVector()) {
+                  lhs_element_count = lhs_type->AsVector()->element_count();
+                  lhs_bit_width = lhs_type->AsVector()
+                                      ->element_type()
+                                      ->AsInteger()
+                                      ->width();
+                } else {
+                  lhs_element_count = 1;
+                  lhs_bit_width = lhs_type->AsInteger()->width();
+                }
+
+                // Get all the instructions that match on element count and
+                // bit-width.
+                auto candidate_rhs_instructions = RestrictToElementBitWidth(
+                    RestrictToVectorWidth(integer_instructions,
+                                          lhs_element_count),
+                    lhs_bit_width);
+
+                // Choose a RHS instruction at random; there is guaranteed to
+                // be at least one choice as the LHS will be available.
+                auto rhs = candidate_rhs_instructions.at(
+                    GetFuzzerContext()->RandomIndex(
+                        candidate_rhs_instructions));
+
+                // Add the equation instruction.
+                ApplyTransformation(TransformationEquationInstruction(
+                    GetFuzzerContext()->GetFreshId(), opcode,
+                    {lhs->result_id(), rhs->result_id()},
+                    instruction_descriptor));
+                return;
+              }
+              break;
+            }
+            case SpvOpLogicalNot: {
+              // Choose any available instruction of boolean scalar/vector
+              // result type and equate its negation with a fresh id.
+              auto boolean_instructions =
+                  GetBooleanInstructions(available_instructions);
+              if (!boolean_instructions.empty()) {
+                ApplyTransformation(TransformationEquationInstruction(
+                    GetFuzzerContext()->GetFreshId(), opcode,
+                    {boolean_instructions
+                         .at(GetFuzzerContext()->RandomIndex(
+                             boolean_instructions))
+                         ->result_id()},
+                    instruction_descriptor));
+                return;
+              }
+              break;
+            }
+            case SpvOpSNegate: {
+              // Similar to OpLogicalNot, but for signed integer negation.
+              auto integer_instructions =
+                  GetIntegerInstructions(available_instructions);
+              if (!integer_instructions.empty()) {
+                ApplyTransformation(TransformationEquationInstruction(
+                    GetFuzzerContext()->GetFreshId(), opcode,
+                    {integer_instructions
+                         .at(GetFuzzerContext()->RandomIndex(
+                             integer_instructions))
+                         ->result_id()},
+                    instruction_descriptor));
+                return;
+              }
+              break;
+            }
+            default:
+              assert(false && "Unexpected opcode.");
+              break;
+          }
+        } while (!candidate_opcodes.empty());
+        // Reaching here means that we did not manage to apply any
+        // transformation at this point of the module.
+      });
+}
+
+std::vector<opt::Instruction*>
+FuzzerPassAddEquationInstructions::GetIntegerInstructions(
+    const std::vector<opt::Instruction*>& instructions) const {
+  std::vector<opt::Instruction*> result;
+  for (auto& inst : instructions) {
+    auto type = GetIRContext()->get_type_mgr()->GetType(inst->type_id());
+    if (type->AsInteger() ||
+        (type->AsVector() && type->AsVector()->element_type()->AsInteger())) {
+      result.push_back(inst);
+    }
+  }
+  return result;
+}
+
+std::vector<opt::Instruction*>
+FuzzerPassAddEquationInstructions::GetBooleanInstructions(
+    const std::vector<opt::Instruction*>& instructions) const {
+  std::vector<opt::Instruction*> result;
+  for (auto& inst : instructions) {
+    auto type = GetIRContext()->get_type_mgr()->GetType(inst->type_id());
+    if (type->AsBool() ||
+        (type->AsVector() && type->AsVector()->element_type()->AsBool())) {
+      result.push_back(inst);
+    }
+  }
+  return result;
+}
+
+std::vector<opt::Instruction*>
+FuzzerPassAddEquationInstructions::RestrictToVectorWidth(
+    const std::vector<opt::Instruction*>& instructions,
+    uint32_t vector_width) const {
+  std::vector<opt::Instruction*> result;
+  for (auto& inst : instructions) {
+    auto type = GetIRContext()->get_type_mgr()->GetType(inst->type_id());
+    // Get the vector width of |inst|, which is 1 if |inst| is a scalar and is
+    // otherwise derived from its vector type.
+    uint32_t other_vector_width =
+        type->AsVector() ? type->AsVector()->element_count() : 1;
+    // Keep |inst| if the vector widths match.
+    if (vector_width == other_vector_width) {
+      result.push_back(inst);
+    }
+  }
+  return result;
+}
+
+std::vector<opt::Instruction*>
+FuzzerPassAddEquationInstructions::RestrictToElementBitWidth(
+    const std::vector<opt::Instruction*>& instructions,
+    uint32_t bit_width) const {
+  std::vector<opt::Instruction*> result;
+  for (auto& inst : instructions) {
+    const opt::analysis::Type* type =
+        GetIRContext()->get_type_mgr()->GetType(inst->type_id());
+    if (type->AsVector()) {
+      type = type->AsVector()->element_type();
+    }
+    assert(type->AsInteger() &&
+           "Precondition: all input instructions must "
+           "have integer scalar or vector type.");
+    if (type->AsInteger()->width() == bit_width) {
+      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
new file mode 100644
index 0000000..6e64977
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_equation_instructions.h
@@ -0,0 +1,67 @@
+// 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_EQUATION_INSTRUCTIONS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_ADD_EQUATION_INSTRUCTIONS_H_
+
+#include <vector>
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Fuzzer pass that sprinkles instructions through the module that define
+// equations using various arithmetic and logical operators.
+class FuzzerPassAddEquationInstructions : public FuzzerPass {
+ public:
+  FuzzerPassAddEquationInstructions(
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassAddEquationInstructions();
+
+  void Apply() override;
+
+ private:
+  // Yields those instructions in |instructions| that have integer scalar or
+  // vector result type.
+  std::vector<opt::Instruction*> GetIntegerInstructions(
+      const std::vector<opt::Instruction*>& instructions) const;
+
+  // Yields those instructions in |instructions| that have boolean scalar or
+  // vector result type.
+  std::vector<opt::Instruction*> GetBooleanInstructions(
+      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.
+  std::vector<opt::Instruction*> RestrictToVectorWidth(
+      const std::vector<opt::Instruction*>& instructions,
+      uint32_t vector_width) const;
+
+  // Requires that |instructions| are integer scalars or vectors.  Returns only
+  // those instructions for which the bit-width of the underlying integer type
+  // is |bit_width|.
+  std::vector<opt::Instruction*> RestrictToElementBitWidth(
+      const std::vector<opt::Instruction*>& instructions,
+      uint32_t bit_width) const;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_ADD_EQUATION_INSTRUCTIONS_H_
diff --git a/source/fuzz/fuzzer_pass_add_function_calls.cpp b/source/fuzz/fuzzer_pass_add_function_calls.cpp
new file mode 100644
index 0000000..569df10
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_function_calls.cpp
@@ -0,0 +1,255 @@
+// 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_function_calls.h"
+
+#include "source/fuzz/call_graph.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/transformation_add_global_variable.h"
+#include "source/fuzz/transformation_add_local_variable.h"
+#include "source/fuzz/transformation_function_call.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassAddFunctionCalls::FuzzerPassAddFunctionCalls(
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassAddFunctionCalls::~FuzzerPassAddFunctionCalls() = default;
+
+void FuzzerPassAddFunctionCalls::Apply() {
+  ForEachInstructionWithInstructionDescriptor(
+      [this](opt::Function* function, opt::BasicBlock* block,
+             opt::BasicBlock::iterator inst_it,
+             const protobufs::InstructionDescriptor& instruction_descriptor)
+          -> void {
+        // Check whether it is legitimate to insert a function call before the
+        // instruction.
+        if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpFunctionCall,
+                                                          inst_it)) {
+          return;
+        }
+
+        // Randomly decide whether to try inserting a function call here.
+        if (!GetFuzzerContext()->ChoosePercentage(
+                GetFuzzerContext()->GetChanceOfCallingFunction())) {
+          return;
+        }
+
+        // Compute the module's call graph - we don't cache it since it may
+        // change each time we apply a transformation.  If this proves to be
+        // a bottleneck the call graph data structure could be made updatable.
+        CallGraph call_graph(GetIRContext());
+
+        // Gather all the non-entry point functions different from this
+        // function.  It is important to ignore entry points as a function
+        // cannot be an entry point and the target of an OpFunctionCall
+        // instruction.  We ignore this function to avoid direct recursion.
+        std::vector<opt::Function*> candidate_functions;
+        for (auto& other_function : *GetIRContext()->module()) {
+          if (&other_function != function &&
+              !fuzzerutil::FunctionIsEntryPoint(GetIRContext(),
+                                                other_function.result_id())) {
+            candidate_functions.push_back(&other_function);
+          }
+        }
+
+        // Choose a function to call, at random, by considering candidate
+        // functions until a suitable one is found.
+        opt::Function* chosen_function = nullptr;
+        while (!candidate_functions.empty()) {
+          opt::Function* candidate_function =
+              GetFuzzerContext()->RemoveAtRandomIndex(&candidate_functions);
+          if (!GetTransformationContext()->GetFactManager()->BlockIsDead(
+                  block->id()) &&
+              !GetTransformationContext()->GetFactManager()->FunctionIsLivesafe(
+                  candidate_function->result_id())) {
+            // Unless in a dead block, only livesafe functions can be invoked
+            continue;
+          }
+          if (call_graph.GetIndirectCallees(candidate_function->result_id())
+                  .count(function->result_id())) {
+            // Calling this function could lead to indirect recursion
+            continue;
+          }
+          chosen_function = candidate_function;
+          break;
+        }
+
+        if (!chosen_function) {
+          // No suitable function was found to call.  (This can happen, for
+          // instance, if the current function is the only function in the
+          // module.)
+          return;
+        }
+
+        ApplyTransformation(TransformationFunctionCall(
+            GetFuzzerContext()->GetFreshId(), chosen_function->result_id(),
+            ChooseFunctionCallArguments(*chosen_function, function, block,
+                                        inst_it),
+            instruction_descriptor));
+      });
+}
+
+std::map<uint32_t, std::vector<opt::Instruction*>>
+FuzzerPassAddFunctionCalls::GetAvailableInstructionsSuitableForActualParameters(
+    opt::Function* function, opt::BasicBlock* block,
+    const opt::BasicBlock::iterator& inst_it) {
+  // Find all instructions in scope that could potentially be used as actual
+  // parameters.  Weed out unsuitable pointer arguments immediately.
+  std::vector<opt::Instruction*> potentially_suitable_instructions =
+      FindAvailableInstructions(
+          function, block, inst_it,
+          [this, block](opt::IRContext* context,
+                        opt::Instruction* inst) -> bool {
+            if (!inst->HasResultId() || !inst->type_id()) {
+              // An instruction needs a result id and type in order
+              // to be suitable as an actual parameter.
+              return false;
+            }
+            if (context->get_def_use_mgr()->GetDef(inst->type_id())->opcode() ==
+                SpvOpTypePointer) {
+              switch (inst->opcode()) {
+                case SpvOpFunctionParameter:
+                case SpvOpVariable:
+                  // Function parameters and variables are the only
+                  // kinds of pointer that can be used as actual
+                  // parameters.
+                  break;
+                default:
+                  return false;
+              }
+              if (!GetTransformationContext()->GetFactManager()->BlockIsDead(
+                      block->id()) &&
+                  !GetTransformationContext()
+                       ->GetFactManager()
+                       ->PointeeValueIsIrrelevant(inst->result_id())) {
+                // We can only pass a pointer as an actual parameter
+                // if the pointee value for the pointer is irrelevant,
+                // or if the block from which we would make the
+                // function call is dead.
+                return false;
+              }
+            }
+            return true;
+          });
+
+  // Group all the instructions that are potentially viable as function actual
+  // parameters by their result types.
+  std::map<uint32_t, std::vector<opt::Instruction*>> result;
+  for (auto inst : potentially_suitable_instructions) {
+    if (result.count(inst->type_id()) == 0) {
+      // This is the first instruction of this type we have seen, so populate
+      // the map with an entry.
+      result.insert({inst->type_id(), {}});
+    }
+    // Add the instruction to the sequence of instructions already associated
+    // with this type.
+    result.at(inst->type_id()).push_back(inst);
+  }
+  return result;
+}
+
+std::vector<uint32_t> FuzzerPassAddFunctionCalls::ChooseFunctionCallArguments(
+    const opt::Function& callee, opt::Function* caller_function,
+    opt::BasicBlock* caller_block,
+    const opt::BasicBlock::iterator& caller_inst_it) {
+  auto type_to_available_instructions =
+      GetAvailableInstructionsSuitableForActualParameters(
+          caller_function, caller_block, caller_inst_it);
+
+  opt::Instruction* function_type = GetIRContext()->get_def_use_mgr()->GetDef(
+      callee.DefInst().GetSingleWordInOperand(1));
+  assert(function_type->opcode() == SpvOpTypeFunction &&
+         "The function type does not have the expected opcode.");
+  std::vector<uint32_t> result;
+  for (uint32_t arg_index = 1; arg_index < function_type->NumInOperands();
+       arg_index++) {
+    auto arg_type_id =
+        GetIRContext()
+            ->get_def_use_mgr()
+            ->GetDef(function_type->GetSingleWordInOperand(arg_index))
+            ->result_id();
+    if (type_to_available_instructions.count(arg_type_id)) {
+      std::vector<opt::Instruction*>& candidate_arguments =
+          type_to_available_instructions.at(arg_type_id);
+      // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3177) The value
+      //  selected here is arbitrary.  We should consider adding this
+      //  information as a fact so that the passed parameter could be
+      //  transformed/changed.
+      result.push_back(candidate_arguments[GetFuzzerContext()->RandomIndex(
+                                               candidate_arguments)]
+                           ->result_id());
+    } else {
+      // We don't have a suitable id in scope to pass, so we must make
+      // something up.
+      auto type_instruction =
+          GetIRContext()->get_def_use_mgr()->GetDef(arg_type_id);
+
+      if (type_instruction->opcode() == SpvOpTypePointer) {
+        // In the case of a pointer, we make a new variable, at function
+        // or global scope depending on the storage class of the
+        // pointer.
+
+        // Get a fresh id for the new variable.
+        uint32_t fresh_variable_id = GetFuzzerContext()->GetFreshId();
+
+        // The id of this variable is what we pass as the parameter to
+        // the call.
+        result.push_back(fresh_variable_id);
+
+        // Now bring the variable into existence.
+        auto storage_class = static_cast<SpvStorageClass>(
+            type_instruction->GetSingleWordInOperand(0));
+        if (storage_class == SpvStorageClassFunction) {
+          // Add a new zero-initialized local variable to the current
+          // function, noting that its pointee value is irrelevant.
+          ApplyTransformation(TransformationAddLocalVariable(
+              fresh_variable_id, arg_type_id, caller_function->result_id(),
+              FindOrCreateZeroConstant(
+                  type_instruction->GetSingleWordInOperand(1)),
+              true));
+        } else {
+          assert((storage_class == SpvStorageClassPrivate ||
+                  storage_class == SpvStorageClassWorkgroup) &&
+                 "Only Function, Private and Workgroup storage classes are "
+                 "supported at present.");
+          // Add a new global variable to the module, zero-initializing it if
+          // it has Private storage class, and noting that its pointee value is
+          // irrelevant.
+          ApplyTransformation(TransformationAddGlobalVariable(
+              fresh_variable_id, arg_type_id, storage_class,
+              storage_class == SpvStorageClassPrivate
+                  ? FindOrCreateZeroConstant(
+                        type_instruction->GetSingleWordInOperand(1))
+                  : 0,
+              true));
+        }
+      } else {
+        // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3177): We use
+        //  constant zero for the parameter, but could consider adding a fact
+        //  to allow further passes to obfuscate it.
+        result.push_back(FindOrCreateZeroConstant(arg_type_id));
+      }
+    }
+  }
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_add_function_calls.h b/source/fuzz/fuzzer_pass_add_function_calls.h
new file mode 100644
index 0000000..8f75e8c
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_function_calls.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_FUZZER_PASS_ADD_FUNCTION_CALLS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_ADD_FUNCTION_CALLS_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Fuzzer pass that adds calls at random to (a) livesafe functions, from
+// anywhere, and (b) any functions, from dead blocks.
+class FuzzerPassAddFunctionCalls : public FuzzerPass {
+ public:
+  FuzzerPassAddFunctionCalls(
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassAddFunctionCalls();
+
+  void Apply() override;
+
+ private:
+  // Identify all instructions available at |instr_it|, in block |block| of
+  // |function|, that are potentially suitable as function call actual
+  // parameters.  The results are grouped by type.
+  std::map<uint32_t, std::vector<opt::Instruction*>>
+  GetAvailableInstructionsSuitableForActualParameters(
+      opt::Function* function, opt::BasicBlock* block,
+      const opt::BasicBlock::iterator& inst_it);
+
+  // Randomly chooses suitable arguments to invoke |callee| right before
+  // instruction |caller_inst_it| of block |caller_block| in |caller_function|,
+  // based on both existing available instructions and the addition of new
+  // instructions to the module.
+  std::vector<uint32_t> ChooseFunctionCallArguments(
+      const opt::Function& callee, opt::Function* caller_function,
+      opt::BasicBlock* caller_block,
+      const opt::BasicBlock::iterator& caller_inst_it);
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_ADD_FUNCTION_CALLS_H_
diff --git a/source/fuzz/fuzzer_pass_add_global_variables.cpp b/source/fuzz/fuzzer_pass_add_global_variables.cpp
new file mode 100644
index 0000000..4023b22
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_global_variables.cpp
@@ -0,0 +1,78 @@
+// 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_global_variables.h"
+
+#include "source/fuzz/transformation_add_global_variable.h"
+#include "source/fuzz/transformation_add_type_pointer.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassAddGlobalVariables::FuzzerPassAddGlobalVariables(
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassAddGlobalVariables::~FuzzerPassAddGlobalVariables() = default;
+
+void FuzzerPassAddGlobalVariables::Apply() {
+  auto basic_type_ids_and_pointers =
+      GetAvailableBasicTypesAndPointers(SpvStorageClassPrivate);
+
+  // These are the basic types that are available to this fuzzer pass.
+  auto& basic_types = basic_type_ids_and_pointers.first;
+
+  // These are the pointers to those basic types that are *initially* available
+  // to the fuzzer pass.  The fuzzer pass might add pointer types in cases where
+  // none are available for a given basic type.
+  auto& basic_type_to_pointers = basic_type_ids_and_pointers.second;
+
+  // Probabilistically keep adding global variables.
+  while (GetFuzzerContext()->ChoosePercentage(
+      GetFuzzerContext()->GetChanceOfAddingGlobalVariable())) {
+    // Choose a random basic type; the new variable's type will be a pointer to
+    // this basic type.
+    uint32_t basic_type =
+        basic_types[GetFuzzerContext()->RandomIndex(basic_types)];
+    uint32_t pointer_type_id;
+    std::vector<uint32_t>& available_pointers_to_basic_type =
+        basic_type_to_pointers.at(basic_type);
+    // Determine whether there is at least one pointer to this basic type.
+    if (available_pointers_to_basic_type.empty()) {
+      // There is not.  Make one, to use here, and add it to the available
+      // pointers for the basic type so that future variables can potentially
+      // use it.
+      pointer_type_id = GetFuzzerContext()->GetFreshId();
+      available_pointers_to_basic_type.push_back(pointer_type_id);
+      ApplyTransformation(TransformationAddTypePointer(
+          pointer_type_id, SpvStorageClassPrivate, basic_type));
+    } else {
+      // There is - grab one.
+      pointer_type_id =
+          available_pointers_to_basic_type[GetFuzzerContext()->RandomIndex(
+              available_pointers_to_basic_type)];
+    }
+    // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3274):  We could
+    //  add new variables with Workgroup storage class in compute shaders.
+    ApplyTransformation(TransformationAddGlobalVariable(
+        GetFuzzerContext()->GetFreshId(), pointer_type_id,
+        SpvStorageClassPrivate, FindOrCreateZeroConstant(basic_type), true));
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_add_global_variables.h b/source/fuzz/fuzzer_pass_add_global_variables.h
new file mode 100644
index 0000000..a907d36
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_global_variables.h
@@ -0,0 +1,40 @@
+// 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_GLOBAL_VARIABLES_H_
+#define SOURCE_FUZZ_FUZZER_PASS_ADD_GLOBAL_VARIABLES_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Fuzzer pass that randomly adds global variables, with Private storage class,
+// to the module.
+class FuzzerPassAddGlobalVariables : public FuzzerPass {
+ public:
+  FuzzerPassAddGlobalVariables(
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassAddGlobalVariables();
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_ADD_GLOBAL_VARIABLES_H_
diff --git a/source/fuzz/fuzzer_pass_add_loads.cpp b/source/fuzz/fuzzer_pass_add_loads.cpp
new file mode 100644
index 0000000..16d88e3
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_loads.cpp
@@ -0,0 +1,96 @@
+// 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_loads.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/transformation_load.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassAddLoads::FuzzerPassAddLoads(
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassAddLoads::~FuzzerPassAddLoads() = default;
+
+void FuzzerPassAddLoads::Apply() {
+  ForEachInstructionWithInstructionDescriptor(
+      [this](opt::Function* function, opt::BasicBlock* block,
+             opt::BasicBlock::iterator inst_it,
+             const protobufs::InstructionDescriptor& instruction_descriptor)
+          -> void {
+        assert(inst_it->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");
+
+        // Check whether it is legitimate to insert a load before this
+        // instruction.
+        if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpLoad, inst_it)) {
+          return;
+        }
+
+        // Randomly decide whether to try inserting a load here.
+        if (!GetFuzzerContext()->ChoosePercentage(
+                GetFuzzerContext()->GetChanceOfAddingLoad())) {
+          return;
+        }
+
+        std::vector<opt::Instruction*> relevant_instructions =
+            FindAvailableInstructions(
+                function, block, inst_it,
+                [](opt::IRContext* context,
+                   opt::Instruction* instruction) -> bool {
+                  if (!instruction->result_id() || !instruction->type_id()) {
+                    return false;
+                  }
+                  switch (instruction->result_id()) {
+                    case SpvOpConstantNull:
+                    case SpvOpUndef:
+                      // Do not allow loading from a null or undefined pointer;
+                      // this might be OK if the block is dead, but for now we
+                      // conservatively avoid it.
+                      return false;
+                    default:
+                      break;
+                  }
+                  return context->get_def_use_mgr()
+                             ->GetDef(instruction->type_id())
+                             ->opcode() == SpvOpTypePointer;
+                });
+
+        // At this point, |relevant_instructions| contains all the pointers
+        // we might think of loading from.
+        if (relevant_instructions.empty()) {
+          return;
+        }
+
+        // Choose a pointer at random, and create and apply a loading
+        // transformation based on it.
+        ApplyTransformation(TransformationLoad(
+            GetFuzzerContext()->GetFreshId(),
+            relevant_instructions[GetFuzzerContext()->RandomIndex(
+                                      relevant_instructions)]
+                ->result_id(),
+            instruction_descriptor));
+      });
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_add_loads.h b/source/fuzz/fuzzer_pass_add_loads.h
new file mode 100644
index 0000000..c4d5b27
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_loads.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_ADD_LOADS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_ADD_LOADS_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Fuzzer pass that adds stores, at random, from pointers in the module.
+class FuzzerPassAddLoads : public FuzzerPass {
+ public:
+  FuzzerPassAddLoads(opt::IRContext* ir_context,
+                     TransformationContext* transformation_context,
+                     FuzzerContext* fuzzer_context,
+                     protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassAddLoads();
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_ADD_LOADS_H_
diff --git a/source/fuzz/fuzzer_pass_add_local_variables.cpp b/source/fuzz/fuzzer_pass_add_local_variables.cpp
new file mode 100644
index 0000000..661159e
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_local_variables.cpp
@@ -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.
+
+#include "source/fuzz/fuzzer_pass_add_local_variables.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/transformation_add_local_variable.h"
+#include "source/fuzz/transformation_add_type_pointer.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassAddLocalVariables::FuzzerPassAddLocalVariables(
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassAddLocalVariables::~FuzzerPassAddLocalVariables() = default;
+
+void FuzzerPassAddLocalVariables::Apply() {
+  auto basic_type_ids_and_pointers =
+      GetAvailableBasicTypesAndPointers(SpvStorageClassFunction);
+
+  // These are the basic types that are available to this fuzzer pass.
+  auto& basic_types = basic_type_ids_and_pointers.first;
+
+  // These are the pointers to those basic types that are *initially* available
+  // to the fuzzer pass.  The fuzzer pass might add pointer types in cases where
+  // none are available for a given basic type.
+  auto& basic_type_to_pointers = basic_type_ids_and_pointers.second;
+
+  // Consider every function in the module.
+  for (auto& function : *GetIRContext()->module()) {
+    // Probabilistically keep adding random variables to this function.
+    while (GetFuzzerContext()->ChoosePercentage(
+        GetFuzzerContext()->GetChanceOfAddingLocalVariable())) {
+      // Choose a random basic type; the new variable's type will be a pointer
+      // to this basic type.
+      uint32_t basic_type =
+          basic_types[GetFuzzerContext()->RandomIndex(basic_types)];
+      uint32_t pointer_type;
+      std::vector<uint32_t>& available_pointers_to_basic_type =
+          basic_type_to_pointers.at(basic_type);
+      // Determine whether there is at least one pointer to this basic type.
+      if (available_pointers_to_basic_type.empty()) {
+        // There is not.  Make one, to use here, and add it to the available
+        // pointers for the basic type so that future variables can potentially
+        // use it.
+        pointer_type = GetFuzzerContext()->GetFreshId();
+        ApplyTransformation(TransformationAddTypePointer(
+            pointer_type, SpvStorageClassFunction, basic_type));
+        available_pointers_to_basic_type.push_back(pointer_type);
+      } else {
+        // There is - grab one.
+        pointer_type =
+            available_pointers_to_basic_type[GetFuzzerContext()->RandomIndex(
+                available_pointers_to_basic_type)];
+      }
+      ApplyTransformation(TransformationAddLocalVariable(
+          GetFuzzerContext()->GetFreshId(), pointer_type, function.result_id(),
+          FindOrCreateZeroConstant(basic_type), true));
+    }
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_add_local_variables.h b/source/fuzz/fuzzer_pass_add_local_variables.h
new file mode 100644
index 0000000..08d26d8
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_local_variables.h
@@ -0,0 +1,40 @@
+// 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_LOCAL_VARIABLES_H_
+#define SOURCE_FUZZ_FUZZER_PASS_ADD_LOCAL_VARIABLES_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Fuzzer pass that randomly adds local variables, with Function storage class,
+// to the module.
+class FuzzerPassAddLocalVariables : public FuzzerPass {
+ public:
+  FuzzerPassAddLocalVariables(
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassAddLocalVariables();
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_ADD_LOCAL_VARIABLES_H_
diff --git a/source/fuzz/fuzzer_pass_add_no_contraction_decorations.cpp b/source/fuzz/fuzzer_pass_add_no_contraction_decorations.cpp
index ead8c5c..09627d0 100644
--- a/source/fuzz/fuzzer_pass_add_no_contraction_decorations.cpp
+++ b/source/fuzz/fuzzer_pass_add_no_contraction_decorations.cpp
@@ -20,10 +20,11 @@
 namespace fuzz {
 
 FuzzerPassAddNoContractionDecorations::FuzzerPassAddNoContractionDecorations(
-    opt::IRContext* ir_context, FactManager* fact_manager,
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
     protobufs::TransformationSequence* transformations)
-    : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
 
 FuzzerPassAddNoContractionDecorations::
     ~FuzzerPassAddNoContractionDecorations() = default;
@@ -44,12 +45,7 @@
                       ->GetChanceOfAddingNoContractionDecoration())) {
             TransformationAddNoContractionDecoration transformation(
                 inst.result_id());
-            assert(transformation.IsApplicable(GetIRContext(),
-                                               *GetFactManager()) &&
-                   "Transformation should be applicable by construction.");
-            transformation.Apply(GetIRContext(), GetFactManager());
-            *GetTransformations()->add_transformation() =
-                transformation.ToMessage();
+            ApplyTransformation(transformation);
           }
         }
       }
diff --git a/source/fuzz/fuzzer_pass_add_no_contraction_decorations.h b/source/fuzz/fuzzer_pass_add_no_contraction_decorations.h
index abe5bd7..f32e5bc 100644
--- a/source/fuzz/fuzzer_pass_add_no_contraction_decorations.h
+++ b/source/fuzz/fuzzer_pass_add_no_contraction_decorations.h
@@ -24,7 +24,7 @@
 class FuzzerPassAddNoContractionDecorations : public FuzzerPass {
  public:
   FuzzerPassAddNoContractionDecorations(
-      opt::IRContext* ir_context, FactManager* fact_manager,
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
       protobufs::TransformationSequence* transformations);
 
diff --git a/source/fuzz/fuzzer_pass_add_stores.cpp b/source/fuzz/fuzzer_pass_add_stores.cpp
new file mode 100644
index 0000000..e8871be
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_stores.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_add_stores.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/transformation_store.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassAddStores::FuzzerPassAddStores(
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassAddStores::~FuzzerPassAddStores() = default;
+
+void FuzzerPassAddStores::Apply() {
+  ForEachInstructionWithInstructionDescriptor(
+      [this](opt::Function* function, opt::BasicBlock* block,
+             opt::BasicBlock::iterator inst_it,
+             const protobufs::InstructionDescriptor& instruction_descriptor)
+          -> void {
+        assert(inst_it->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");
+
+        // Check whether it is legitimate to insert a store before this
+        // instruction.
+        if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpStore,
+                                                          inst_it)) {
+          return;
+        }
+
+        // Randomly decide whether to try inserting a store here.
+        if (!GetFuzzerContext()->ChoosePercentage(
+                GetFuzzerContext()->GetChanceOfAddingStore())) {
+          return;
+        }
+
+        // Look for pointers we might consider storing to.
+        std::vector<opt::Instruction*> relevant_pointers =
+            FindAvailableInstructions(
+                function, block, inst_it,
+                [this, block](opt::IRContext* context,
+                              opt::Instruction* instruction) -> bool {
+                  if (!instruction->result_id() || !instruction->type_id()) {
+                    return false;
+                  }
+                  auto type_inst = context->get_def_use_mgr()->GetDef(
+                      instruction->type_id());
+                  if (type_inst->opcode() != SpvOpTypePointer) {
+                    // Not a pointer.
+                    return false;
+                  }
+                  if (type_inst->GetSingleWordInOperand(0) ==
+                      SpvStorageClassInput) {
+                    // Read-only: cannot store to it.
+                    return false;
+                  }
+                  switch (instruction->result_id()) {
+                    case SpvOpConstantNull:
+                    case SpvOpUndef:
+                      // Do not allow storing to a null or undefined pointer;
+                      // this might be OK if the block is dead, but for now we
+                      // conservatively avoid it.
+                      return false;
+                    default:
+                      break;
+                  }
+                  return GetTransformationContext()
+                             ->GetFactManager()
+                             ->BlockIsDead(block->id()) ||
+                         GetTransformationContext()
+                             ->GetFactManager()
+                             ->PointeeValueIsIrrelevant(
+                                 instruction->result_id());
+                });
+
+        // At this point, |relevant_pointers| contains all the pointers we might
+        // think of storing to.
+        if (relevant_pointers.empty()) {
+          return;
+        }
+
+        auto pointer = relevant_pointers[GetFuzzerContext()->RandomIndex(
+            relevant_pointers)];
+
+        std::vector<opt::Instruction*> relevant_values =
+            FindAvailableInstructions(
+                function, block, inst_it,
+                [pointer](opt::IRContext* context,
+                          opt::Instruction* instruction) -> bool {
+                  if (!instruction->result_id() || !instruction->type_id()) {
+                    return false;
+                  }
+                  return instruction->type_id() ==
+                         context->get_def_use_mgr()
+                             ->GetDef(pointer->type_id())
+                             ->GetSingleWordInOperand(1);
+                });
+
+        if (relevant_values.empty()) {
+          return;
+        }
+
+        // Choose a value at random, and create and apply a storing
+        // transformation based on it and the pointer.
+        ApplyTransformation(TransformationStore(
+            pointer->result_id(),
+            relevant_values[GetFuzzerContext()->RandomIndex(relevant_values)]
+                ->result_id(),
+            instruction_descriptor));
+      });
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_add_stores.h b/source/fuzz/fuzzer_pass_add_stores.h
new file mode 100644
index 0000000..55ec67f
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_stores.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_ADD_STORES_H_
+#define SOURCE_FUZZ_FUZZER_PASS_ADD_STORES_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Fuzzer pass that adds stores, at random, through pointers in the module,
+// either (a) from dead blocks, or (b) through pointers whose pointee values
+// are known not to affect the module's overall behaviour.
+class FuzzerPassAddStores : public FuzzerPass {
+ public:
+  FuzzerPassAddStores(opt::IRContext* ir_context,
+                      TransformationContext* transformation_context,
+                      FuzzerContext* fuzzer_context,
+                      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassAddStores();
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_ADD_STORES_H_
diff --git a/source/fuzz/fuzzer_pass_add_useful_constructs.cpp b/source/fuzz/fuzzer_pass_add_useful_constructs.cpp
index 8552dfd..a267612 100644
--- a/source/fuzz/fuzzer_pass_add_useful_constructs.cpp
+++ b/source/fuzz/fuzzer_pass_add_useful_constructs.cpp
@@ -25,10 +25,11 @@
 namespace fuzz {
 
 FuzzerPassAddUsefulConstructs::FuzzerPassAddUsefulConstructs(
-    opt::IRContext* ir_context, FactManager* fact_manager,
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
     protobufs::TransformationSequence* transformations)
-    : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
 
 FuzzerPassAddUsefulConstructs::~FuzzerPassAddUsefulConstructs() = default;
 
@@ -49,9 +50,10 @@
     TransformationAddConstantScalar add_constant_int =
         TransformationAddConstantScalar(GetFuzzerContext()->GetFreshId(),
                                         int_type_id, data);
-    assert(add_constant_int.IsApplicable(GetIRContext(), *GetFactManager()) &&
+    assert(add_constant_int.IsApplicable(GetIRContext(),
+                                         *GetTransformationContext()) &&
            "Should be applicable by construction.");
-    add_constant_int.Apply(GetIRContext(), GetFactManager());
+    add_constant_int.Apply(GetIRContext(), GetTransformationContext());
     *GetTransformations()->add_transformation() = add_constant_int.ToMessage();
   }
 }
@@ -75,9 +77,10 @@
     TransformationAddConstantScalar add_constant_float =
         TransformationAddConstantScalar(GetFuzzerContext()->GetFreshId(),
                                         float_type_id, data);
-    assert(add_constant_float.IsApplicable(GetIRContext(), *GetFactManager()) &&
+    assert(add_constant_float.IsApplicable(GetIRContext(),
+                                           *GetTransformationContext()) &&
            "Should be applicable by construction.");
-    add_constant_float.Apply(GetIRContext(), GetFactManager());
+    add_constant_float.Apply(GetIRContext(), GetTransformationContext());
     *GetTransformations()->add_transformation() =
         add_constant_float.ToMessage();
   }
@@ -90,9 +93,10 @@
     if (!GetIRContext()->get_type_mgr()->GetId(&temp_bool_type)) {
       auto add_type_boolean =
           TransformationAddTypeBoolean(GetFuzzerContext()->GetFreshId());
-      assert(add_type_boolean.IsApplicable(GetIRContext(), *GetFactManager()) &&
+      assert(add_type_boolean.IsApplicable(GetIRContext(),
+                                           *GetTransformationContext()) &&
              "Should be applicable by construction.");
-      add_type_boolean.Apply(GetIRContext(), GetFactManager());
+      add_type_boolean.Apply(GetIRContext(), GetTransformationContext());
       *GetTransformations()->add_transformation() =
           add_type_boolean.ToMessage();
     }
@@ -105,9 +109,10 @@
       if (!GetIRContext()->get_type_mgr()->GetId(&temp_int_type)) {
         TransformationAddTypeInt add_type_int = TransformationAddTypeInt(
             GetFuzzerContext()->GetFreshId(), 32, is_signed);
-        assert(add_type_int.IsApplicable(GetIRContext(), *GetFactManager()) &&
+        assert(add_type_int.IsApplicable(GetIRContext(),
+                                         *GetTransformationContext()) &&
                "Should be applicable by construction.");
-        add_type_int.Apply(GetIRContext(), GetFactManager());
+        add_type_int.Apply(GetIRContext(), GetTransformationContext());
         *GetTransformations()->add_transformation() = add_type_int.ToMessage();
       }
     }
@@ -119,9 +124,10 @@
     if (!GetIRContext()->get_type_mgr()->GetId(&temp_float_type)) {
       TransformationAddTypeFloat add_type_float =
           TransformationAddTypeFloat(GetFuzzerContext()->GetFreshId(), 32);
-      assert(add_type_float.IsApplicable(GetIRContext(), *GetFactManager()) &&
+      assert(add_type_float.IsApplicable(GetIRContext(),
+                                         *GetTransformationContext()) &&
              "Should be applicable by construction.");
-      add_type_float.Apply(GetIRContext(), GetFactManager());
+      add_type_float.Apply(GetIRContext(), GetTransformationContext());
       *GetTransformations()->add_transformation() = add_type_float.ToMessage();
     }
   }
@@ -139,9 +145,9 @@
       TransformationAddConstantBoolean add_constant_boolean(
           GetFuzzerContext()->GetFreshId(), boolean_value);
       assert(add_constant_boolean.IsApplicable(GetIRContext(),
-                                               *GetFactManager()) &&
+                                               *GetTransformationContext()) &&
              "Should be applicable by construction.");
-      add_constant_boolean.Apply(GetIRContext(), GetFactManager());
+      add_constant_boolean.Apply(GetIRContext(), GetTransformationContext());
       *GetTransformations()->add_transformation() =
           add_constant_boolean.ToMessage();
     }
@@ -168,8 +174,9 @@
   //   of the element
   // - a signed integer constant for each index required to access the element
   // - a constant for the constant value itself
-  for (auto& fact_and_type_id :
-       GetFactManager()->GetConstantUniformFactsAndTypes()) {
+  for (auto& fact_and_type_id : GetTransformationContext()
+                                    ->GetFactManager()
+                                    ->GetConstantUniformFactsAndTypes()) {
     uint32_t element_type_id = fact_and_type_id.second;
     assert(element_type_id);
     auto element_type =
@@ -183,9 +190,10 @@
       auto add_pointer =
           TransformationAddTypePointer(GetFuzzerContext()->GetFreshId(),
                                        SpvStorageClassUniform, element_type_id);
-      assert(add_pointer.IsApplicable(GetIRContext(), *GetFactManager()) &&
+      assert(add_pointer.IsApplicable(GetIRContext(),
+                                      *GetTransformationContext()) &&
              "Should be applicable by construction.");
-      add_pointer.Apply(GetIRContext(), GetFactManager());
+      add_pointer.Apply(GetIRContext(), GetTransformationContext());
       *GetTransformations()->add_transformation() = add_pointer.ToMessage();
     }
     std::vector<uint32_t> words;
diff --git a/source/fuzz/fuzzer_pass_add_useful_constructs.h b/source/fuzz/fuzzer_pass_add_useful_constructs.h
index 7dc00f1..17e87a8 100644
--- a/source/fuzz/fuzzer_pass_add_useful_constructs.h
+++ b/source/fuzz/fuzzer_pass_add_useful_constructs.h
@@ -25,7 +25,7 @@
 class FuzzerPassAddUsefulConstructs : public FuzzerPass {
  public:
   FuzzerPassAddUsefulConstructs(
-      opt::IRContext* ir_context, FactManager* fact_manager,
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
       protobufs::TransformationSequence* transformations);
 
diff --git a/source/fuzz/fuzzer_pass_adjust_function_controls.cpp b/source/fuzz/fuzzer_pass_adjust_function_controls.cpp
index 2a11988..aa62d2f 100644
--- a/source/fuzz/fuzzer_pass_adjust_function_controls.cpp
+++ b/source/fuzz/fuzzer_pass_adjust_function_controls.cpp
@@ -20,10 +20,11 @@
 namespace fuzz {
 
 FuzzerPassAdjustFunctionControls::FuzzerPassAdjustFunctionControls(
-    opt::IRContext* ir_context, FactManager* fact_manager,
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
     protobufs::TransformationSequence* transformations)
-    : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
 
 FuzzerPassAdjustFunctionControls::~FuzzerPassAdjustFunctionControls() = default;
 
@@ -61,10 +62,7 @@
       // Create and add a transformation.
       TransformationSetFunctionControl transformation(
           function.DefInst().result_id(), new_function_control_mask);
-      assert(transformation.IsApplicable(GetIRContext(), *GetFactManager()) &&
-             "Transformation should be applicable by construction.");
-      transformation.Apply(GetIRContext(), GetFactManager());
-      *GetTransformations()->add_transformation() = transformation.ToMessage();
+      ApplyTransformation(transformation);
     }
   }
 }
diff --git a/source/fuzz/fuzzer_pass_adjust_function_controls.h b/source/fuzz/fuzzer_pass_adjust_function_controls.h
index 02d3600..e20541b 100644
--- a/source/fuzz/fuzzer_pass_adjust_function_controls.h
+++ b/source/fuzz/fuzzer_pass_adjust_function_controls.h
@@ -24,7 +24,7 @@
 class FuzzerPassAdjustFunctionControls : public FuzzerPass {
  public:
   FuzzerPassAdjustFunctionControls(
-      opt::IRContext* ir_context, FactManager* fact_manager,
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
       protobufs::TransformationSequence* transformations);
 
diff --git a/source/fuzz/fuzzer_pass_adjust_loop_controls.cpp b/source/fuzz/fuzzer_pass_adjust_loop_controls.cpp
index ac2408a..f7addff 100644
--- a/source/fuzz/fuzzer_pass_adjust_loop_controls.cpp
+++ b/source/fuzz/fuzzer_pass_adjust_loop_controls.cpp
@@ -20,10 +20,11 @@
 namespace fuzz {
 
 FuzzerPassAdjustLoopControls::FuzzerPassAdjustLoopControls(
-    opt::IRContext* ir_context, FactManager* fact_manager,
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
     protobufs::TransformationSequence* transformations)
-    : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
 
 FuzzerPassAdjustLoopControls::~FuzzerPassAdjustLoopControls() = default;
 
@@ -107,11 +108,7 @@
         // sequence.
         TransformationSetLoopControl transformation(block.id(), new_mask,
                                                     peel_count, partial_count);
-        assert(transformation.IsApplicable(GetIRContext(), *GetFactManager()) &&
-               "Transformation should be applicable by construction.");
-        transformation.Apply(GetIRContext(), GetFactManager());
-        *GetTransformations()->add_transformation() =
-            transformation.ToMessage();
+        ApplyTransformation(transformation);
       }
     }
   }
diff --git a/source/fuzz/fuzzer_pass_adjust_loop_controls.h b/source/fuzz/fuzzer_pass_adjust_loop_controls.h
index e945606..ee5cd48 100644
--- a/source/fuzz/fuzzer_pass_adjust_loop_controls.h
+++ b/source/fuzz/fuzzer_pass_adjust_loop_controls.h
@@ -24,7 +24,7 @@
 class FuzzerPassAdjustLoopControls : public FuzzerPass {
  public:
   FuzzerPassAdjustLoopControls(
-      opt::IRContext* ir_context, FactManager* fact_manager,
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
       protobufs::TransformationSequence* transformations);
 
diff --git a/source/fuzz/fuzzer_pass_adjust_memory_operands_masks.cpp b/source/fuzz/fuzzer_pass_adjust_memory_operands_masks.cpp
index a9d4b32..32f5ea5 100644
--- a/source/fuzz/fuzzer_pass_adjust_memory_operands_masks.cpp
+++ b/source/fuzz/fuzzer_pass_adjust_memory_operands_masks.cpp
@@ -21,10 +21,11 @@
 namespace fuzz {
 
 FuzzerPassAdjustMemoryOperandsMasks::FuzzerPassAdjustMemoryOperandsMasks(
-    opt::IRContext* ir_context, FactManager* fact_manager,
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
     protobufs::TransformationSequence* transformations)
-    : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
 
 FuzzerPassAdjustMemoryOperandsMasks::~FuzzerPassAdjustMemoryOperandsMasks() =
     default;
@@ -97,12 +98,7 @@
 
           TransformationSetMemoryOperandsMask transformation(
               MakeInstructionDescriptor(block, inst_it), new_mask, mask_index);
-          assert(
-              transformation.IsApplicable(GetIRContext(), *GetFactManager()) &&
-              "Transformation should be applicable by construction.");
-          transformation.Apply(GetIRContext(), GetFactManager());
-          *GetTransformations()->add_transformation() =
-              transformation.ToMessage();
+          ApplyTransformation(transformation);
         }
       }
     }
diff --git a/source/fuzz/fuzzer_pass_adjust_memory_operands_masks.h b/source/fuzz/fuzzer_pass_adjust_memory_operands_masks.h
index c3d7118..699dcb5 100644
--- a/source/fuzz/fuzzer_pass_adjust_memory_operands_masks.h
+++ b/source/fuzz/fuzzer_pass_adjust_memory_operands_masks.h
@@ -25,7 +25,7 @@
 class FuzzerPassAdjustMemoryOperandsMasks : public FuzzerPass {
  public:
   FuzzerPassAdjustMemoryOperandsMasks(
-      opt::IRContext* ir_context, FactManager* fact_manager,
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
       protobufs::TransformationSequence* transformations);
 
diff --git a/source/fuzz/fuzzer_pass_adjust_selection_controls.cpp b/source/fuzz/fuzzer_pass_adjust_selection_controls.cpp
index 22654f2..83b1854 100644
--- a/source/fuzz/fuzzer_pass_adjust_selection_controls.cpp
+++ b/source/fuzz/fuzzer_pass_adjust_selection_controls.cpp
@@ -20,10 +20,11 @@
 namespace fuzz {
 
 FuzzerPassAdjustSelectionControls::FuzzerPassAdjustSelectionControls(
-    opt::IRContext* ir_context, FactManager* fact_manager,
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
     protobufs::TransformationSequence* transformations)
-    : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
 
 FuzzerPassAdjustSelectionControls::~FuzzerPassAdjustSelectionControls() =
     default;
@@ -62,11 +63,7 @@
         // sequence.
         TransformationSetSelectionControl transformation(
             block.id(), choices[GetFuzzerContext()->RandomIndex(choices)]);
-        assert(transformation.IsApplicable(GetIRContext(), *GetFactManager()) &&
-               "Transformation should be applicable by construction.");
-        transformation.Apply(GetIRContext(), GetFactManager());
-        *GetTransformations()->add_transformation() =
-            transformation.ToMessage();
+        ApplyTransformation(transformation);
       }
     }
   }
diff --git a/source/fuzz/fuzzer_pass_adjust_selection_controls.h b/source/fuzz/fuzzer_pass_adjust_selection_controls.h
index b5b255c..820b30d 100644
--- a/source/fuzz/fuzzer_pass_adjust_selection_controls.h
+++ b/source/fuzz/fuzzer_pass_adjust_selection_controls.h
@@ -24,7 +24,7 @@
 class FuzzerPassAdjustSelectionControls : public FuzzerPass {
  public:
   FuzzerPassAdjustSelectionControls(
-      opt::IRContext* ir_context, FactManager* fact_manager,
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
       protobufs::TransformationSequence* transformations);
 
diff --git a/source/fuzz/fuzzer_pass_apply_id_synonyms.cpp b/source/fuzz/fuzzer_pass_apply_id_synonyms.cpp
index 6ff42ca..2025821 100644
--- a/source/fuzz/fuzzer_pass_apply_id_synonyms.cpp
+++ b/source/fuzz/fuzzer_pass_apply_id_synonyms.cpp
@@ -25,26 +25,33 @@
 namespace fuzz {
 
 FuzzerPassApplyIdSynonyms::FuzzerPassApplyIdSynonyms(
-    opt::IRContext* ir_context, FactManager* fact_manager,
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
     protobufs::TransformationSequence* transformations)
-    : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
 
 FuzzerPassApplyIdSynonyms::~FuzzerPassApplyIdSynonyms() = default;
 
 void FuzzerPassApplyIdSynonyms::Apply() {
   for (auto id_with_known_synonyms :
-       GetFactManager()->GetIdsForWhichSynonymsAreKnown(GetIRContext())) {
-    // Gather up all uses of |id_with_known_synonym|, and then subsequently
-    // iterate over these uses.  We use this separation because, when
-    // considering a given use, we might apply a transformation that will
+       GetTransformationContext()
+           ->GetFactManager()
+           ->GetIdsForWhichSynonymsAreKnown(GetIRContext())) {
+    // Gather up all uses of |id_with_known_synonym| as a regular id, and
+    // subsequently iterate over these uses.  We use this separation because,
+    // when considering a given use, we might apply a transformation that will
     // invalidate the def-use manager.
     std::vector<std::pair<opt::Instruction*, uint32_t>> uses;
     GetIRContext()->get_def_use_mgr()->ForEachUse(
         id_with_known_synonyms,
         [&uses](opt::Instruction* use_inst, uint32_t use_index) -> void {
-          uses.emplace_back(
-              std::pair<opt::Instruction*, uint32_t>(use_inst, use_index));
+          // We only gather up regular id uses; e.g. we do not include a use of
+          // the id as the scope for an atomic operation.
+          if (use_inst->GetOperand(use_index).type == SPV_OPERAND_TYPE_ID) {
+            uses.emplace_back(
+                std::pair<opt::Instruction*, uint32_t>(use_inst, use_index));
+          }
         });
 
     for (auto& use : uses) {
@@ -70,7 +77,8 @@
       }
 
       std::vector<const protobufs::DataDescriptor*> synonyms_to_try;
-      for (auto& data_descriptor : GetFactManager()->GetSynonymsForId(
+      for (auto& data_descriptor :
+           GetTransformationContext()->GetFactManager()->GetSynonymsForId(
                id_with_known_synonyms, GetIRContext())) {
         protobufs::DataDescriptor descriptor_for_this_id =
             MakeDataDescriptor(id_with_known_synonyms, {});
@@ -81,62 +89,65 @@
         synonyms_to_try.push_back(data_descriptor);
       }
       while (!synonyms_to_try.empty()) {
-        auto synonym_index = GetFuzzerContext()->RandomIndex(synonyms_to_try);
-        auto synonym_to_try = synonyms_to_try[synonym_index];
-        synonyms_to_try.erase(synonyms_to_try.begin() + synonym_index);
+        auto synonym_to_try =
+            GetFuzzerContext()->RemoveAtRandomIndex(&synonyms_to_try);
 
+        // If the synonym's |index_size| is zero, the synonym represents an id.
+        // Otherwise it represents some element of a composite structure, in
+        // which case we need to be able to add an extract instruction to get
+        // that element out.
         if (synonym_to_try->index_size() > 0 &&
-            use_inst->opcode() == SpvOpPhi) {
-          // We are trying to replace an operand to an OpPhi.  This means
-          // we cannot use a composite synonym, because that requires
-          // extracting a component from a composite and we cannot insert
-          // an extract instruction before an OpPhi.
-          //
-          // TODO(afd): We could consider inserting the extract instruction
-          //  into the relevant parent block of the OpPhi.
+            !fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpCompositeExtract,
+                                                          use_inst) &&
+            use_inst->opcode() != SpvOpPhi) {
+          // We cannot insert an extract before this instruction, so this
+          // synonym is no good.
           continue;
         }
 
-        if (!TransformationReplaceIdWithSynonym::IdsIsAvailableAtUse(
-                GetIRContext(), use_inst, use_in_operand_index,
-                synonym_to_try->object())) {
+        if (!fuzzerutil::IdIsAvailableAtUse(GetIRContext(), use_inst,
+                                            use_in_operand_index,
+                                            synonym_to_try->object())) {
           continue;
         }
 
-        // We either replace the use with an id known to be synonymous, or
-        // an id that will hold the result of extracting a synonym from a
-        // composite.
+        // We either replace the use with an id known to be synonymous (when
+        // the synonym's |index_size| is 0), or an id that will hold the result
+        // of extracting a synonym from a composite (when the synonym's
+        // |index_size| is > 0).
         uint32_t id_with_which_to_replace_use;
         if (synonym_to_try->index_size() == 0) {
           id_with_which_to_replace_use = synonym_to_try->object();
         } else {
           id_with_which_to_replace_use = GetFuzzerContext()->GetFreshId();
-          protobufs::InstructionDescriptor instruction_to_insert_before =
-              MakeInstructionDescriptor(GetIRContext(), use_inst);
-          TransformationCompositeExtract composite_extract_transformation(
-              instruction_to_insert_before, id_with_which_to_replace_use,
-              synonym_to_try->object(),
-              fuzzerutil::RepeatedFieldToVector(synonym_to_try->index()));
-          assert(composite_extract_transformation.IsApplicable(
-                     GetIRContext(), *GetFactManager()) &&
-                 "Transformation should be applicable by construction.");
-          composite_extract_transformation.Apply(GetIRContext(),
-                                                 GetFactManager());
-          *GetTransformations()->add_transformation() =
-              composite_extract_transformation.ToMessage();
+          opt::Instruction* instruction_to_insert_before = nullptr;
+
+          if (use_inst->opcode() != SpvOpPhi) {
+            instruction_to_insert_before = use_inst;
+          } else {
+            auto parent_block_id =
+                use_inst->GetSingleWordInOperand(use_in_operand_index + 1);
+            auto parent_block_instruction =
+                GetIRContext()->get_def_use_mgr()->GetDef(parent_block_id);
+            auto parent_block =
+                GetIRContext()->get_instr_block(parent_block_instruction);
+
+            instruction_to_insert_before = parent_block->GetMergeInst()
+                                               ? parent_block->GetMergeInst()
+                                               : parent_block->terminator();
+          }
+
+          ApplyTransformation(TransformationCompositeExtract(
+              MakeInstructionDescriptor(GetIRContext(),
+                                        instruction_to_insert_before),
+              id_with_which_to_replace_use, synonym_to_try->object(),
+              fuzzerutil::RepeatedFieldToVector(synonym_to_try->index())));
         }
 
-        TransformationReplaceIdWithSynonym replace_id_transformation(
+        ApplyTransformation(TransformationReplaceIdWithSynonym(
             MakeIdUseDescriptorFromUse(GetIRContext(), use_inst,
                                        use_in_operand_index),
-            id_with_which_to_replace_use);
-
-        // The transformation should be applicable by construction.
-        assert(replace_id_transformation.IsApplicable(GetIRContext(),
-                                                      *GetFactManager()));
-        replace_id_transformation.Apply(GetIRContext(), GetFactManager());
-        *GetTransformations()->add_transformation() =
-            replace_id_transformation.ToMessage();
+            id_with_which_to_replace_use));
         break;
       }
     }
diff --git a/source/fuzz/fuzzer_pass_apply_id_synonyms.h b/source/fuzz/fuzzer_pass_apply_id_synonyms.h
index 1a0748e..1a9213d 100644
--- a/source/fuzz/fuzzer_pass_apply_id_synonyms.h
+++ b/source/fuzz/fuzzer_pass_apply_id_synonyms.h
@@ -27,7 +27,7 @@
 class FuzzerPassApplyIdSynonyms : public FuzzerPass {
  public:
   FuzzerPassApplyIdSynonyms(opt::IRContext* ir_context,
-                            FactManager* fact_manager,
+                            TransformationContext* transformation_context,
                             FuzzerContext* fuzzer_context,
                             protobufs::TransformationSequence* transformations);
 
diff --git a/source/fuzz/fuzzer_pass_construct_composites.cpp b/source/fuzz/fuzzer_pass_construct_composites.cpp
index ff0adab..e78f8ec 100644
--- a/source/fuzz/fuzzer_pass_construct_composites.cpp
+++ b/source/fuzz/fuzzer_pass_construct_composites.cpp
@@ -25,10 +25,11 @@
 namespace fuzz {
 
 FuzzerPassConstructComposites::FuzzerPassConstructComposites(
-    opt::IRContext* ir_context, FactManager* fact_manager,
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
     protobufs::TransformationSequence* transformations)
-    : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
 
 FuzzerPassConstructComposites::~FuzzerPassConstructComposites() = default;
 
@@ -42,9 +43,9 @@
     }
   }
 
-  MaybeAddTransformationBeforeEachInstruction(
+  ForEachInstructionWithInstructionDescriptor(
       [this, &composite_type_ids](
-          const opt::Function& function, opt::BasicBlock* block,
+          opt::Function* function, opt::BasicBlock* block,
           opt::BasicBlock::iterator inst_it,
           const protobufs::InstructionDescriptor& instruction_descriptor)
           -> void {
@@ -87,7 +88,7 @@
         // for constructing a composite of that type.  Otherwise these variables
         // will remain 0 and null respectively.
         uint32_t chosen_composite_type = 0;
-        std::unique_ptr<std::vector<uint32_t>> constructor_arguments = nullptr;
+        std::vector<uint32_t> constructor_arguments;
 
         // Initially, all composite type ids are available for us to try.  Keep
         // trying until we run out of options.
@@ -95,35 +96,38 @@
         while (!composites_to_try_constructing.empty()) {
           // Remove a composite type from the composite types left for us to
           // try.
-          auto index =
-              GetFuzzerContext()->RandomIndex(composites_to_try_constructing);
           auto next_composite_to_try_constructing =
-              composites_to_try_constructing[index];
-          composites_to_try_constructing.erase(
-              composites_to_try_constructing.begin() + index);
+              GetFuzzerContext()->RemoveAtRandomIndex(
+                  &composites_to_try_constructing);
 
           // Now try to construct a composite of this type, using an appropriate
           // helper method depending on the kind of composite type.
-          auto composite_type = GetIRContext()->get_type_mgr()->GetType(
+          auto composite_type_inst = GetIRContext()->get_def_use_mgr()->GetDef(
               next_composite_to_try_constructing);
-          if (auto array_type = composite_type->AsArray()) {
-            constructor_arguments = TryConstructingArrayComposite(
-                *array_type, type_id_to_available_instructions);
-          } else if (auto matrix_type = composite_type->AsMatrix()) {
-            constructor_arguments = TryConstructingMatrixComposite(
-                *matrix_type, type_id_to_available_instructions);
-          } else if (auto struct_type = composite_type->AsStruct()) {
-            constructor_arguments = TryConstructingStructComposite(
-                *struct_type, type_id_to_available_instructions);
-          } else {
-            auto vector_type = composite_type->AsVector();
-            assert(vector_type &&
-                   "The space of possible composite types should be covered by "
-                   "the above cases.");
-            constructor_arguments = TryConstructingVectorComposite(
-                *vector_type, type_id_to_available_instructions);
+          switch (composite_type_inst->opcode()) {
+            case SpvOpTypeArray:
+              constructor_arguments = FindComponentsToConstructArray(
+                  *composite_type_inst, type_id_to_available_instructions);
+              break;
+            case SpvOpTypeMatrix:
+              constructor_arguments = FindComponentsToConstructMatrix(
+                  *composite_type_inst, type_id_to_available_instructions);
+              break;
+            case SpvOpTypeStruct:
+              constructor_arguments = FindComponentsToConstructStruct(
+                  *composite_type_inst, type_id_to_available_instructions);
+              break;
+            case SpvOpTypeVector:
+              constructor_arguments = FindComponentsToConstructVector(
+                  *composite_type_inst, type_id_to_available_instructions);
+              break;
+            default:
+              assert(false &&
+                     "The space of possible composite types should be covered "
+                     "by the above cases.");
+              break;
           }
-          if (constructor_arguments != nullptr) {
+          if (!constructor_arguments.empty()) {
             // We succeeded!  Note the composite type we finally settled on, and
             // exit from the loop.
             chosen_composite_type = next_composite_to_try_constructing;
@@ -134,20 +138,15 @@
         if (!chosen_composite_type) {
           // We did not manage to make a composite; return 0 to indicate that no
           // instructions were added.
-          assert(constructor_arguments == nullptr);
+          assert(constructor_arguments.empty());
           return;
         }
-        assert(constructor_arguments != nullptr);
+        assert(!constructor_arguments.empty());
 
         // Make and apply a transformation.
-        TransformationCompositeConstruct transformation(
-            chosen_composite_type, *constructor_arguments,
-            instruction_descriptor, GetFuzzerContext()->GetFreshId());
-        assert(transformation.IsApplicable(GetIRContext(), *GetFactManager()) &&
-               "This transformation should be applicable by construction.");
-        transformation.Apply(GetIRContext(), GetFactManager());
-        *GetTransformations()->add_transformation() =
-            transformation.ToMessage();
+        ApplyTransformation(TransformationCompositeConstruct(
+            chosen_composite_type, constructor_arguments,
+            instruction_descriptor, GetFuzzerContext()->GetFreshId()));
       });
 }
 
@@ -160,20 +159,15 @@
   type_id_to_available_instructions->at(inst->type_id()).push_back(inst);
 }
 
-std::unique_ptr<std::vector<uint32_t>>
-FuzzerPassConstructComposites::TryConstructingArrayComposite(
-    const opt::analysis::Array& array_type,
+std::vector<uint32_t>
+FuzzerPassConstructComposites::FindComponentsToConstructArray(
+    const opt::Instruction& array_type_instruction,
     const TypeIdToInstructions& type_id_to_available_instructions) {
-  // At present we assume arrays have a constant size.
-  assert(array_type.length_info().words.size() == 2);
-  assert(array_type.length_info().words[0] ==
-         opt::analysis::Array::LengthInfo::kConstant);
-
-  auto result = MakeUnique<std::vector<uint32_t>>();
+  assert(array_type_instruction.opcode() == SpvOpTypeArray &&
+         "Precondition: instruction must be an array type.");
 
   // Get the element type for the array.
-  auto element_type_id =
-      GetIRContext()->get_type_mgr()->GetId(array_type.element_type());
+  auto element_type_id = array_type_instruction.GetSingleWordInOperand(0);
 
   // Get all instructions at our disposal that compute something of this element
   // type.
@@ -184,26 +178,34 @@
     // If there are not any instructions available that compute the element type
     // of the array then we are not in a position to construct a composite with
     // this array type.
-    return nullptr;
+    return {};
   }
-  for (uint32_t index = 0; index < array_type.length_info().words[1]; index++) {
-    result->push_back(available_instructions
-                          ->second[GetFuzzerContext()->RandomIndex(
-                              available_instructions->second)]
-                          ->result_id());
+
+  uint32_t array_length =
+      GetIRContext()
+          ->get_def_use_mgr()
+          ->GetDef(array_type_instruction.GetSingleWordInOperand(1))
+          ->GetSingleWordInOperand(0);
+
+  std::vector<uint32_t> result;
+  for (uint32_t index = 0; index < array_length; index++) {
+    result.push_back(available_instructions
+                         ->second[GetFuzzerContext()->RandomIndex(
+                             available_instructions->second)]
+                         ->result_id());
   }
   return result;
 }
 
-std::unique_ptr<std::vector<uint32_t>>
-FuzzerPassConstructComposites::TryConstructingMatrixComposite(
-    const opt::analysis::Matrix& matrix_type,
+std::vector<uint32_t>
+FuzzerPassConstructComposites::FindComponentsToConstructMatrix(
+    const opt::Instruction& matrix_type_instruction,
     const TypeIdToInstructions& type_id_to_available_instructions) {
-  auto result = MakeUnique<std::vector<uint32_t>>();
+  assert(matrix_type_instruction.opcode() == SpvOpTypeMatrix &&
+         "Precondition: instruction must be a matrix type.");
 
   // Get the element type for the matrix.
-  auto element_type_id =
-      GetIRContext()->get_type_mgr()->GetId(matrix_type.element_type());
+  auto element_type_id = matrix_type_instruction.GetSingleWordInOperand(0);
 
   // Get all instructions at our disposal that compute something of this element
   // type.
@@ -214,25 +216,32 @@
     // If there are not any instructions available that compute the element type
     // of the matrix then we are not in a position to construct a composite with
     // this matrix type.
-    return nullptr;
+    return {};
   }
-  for (uint32_t index = 0; index < matrix_type.element_count(); index++) {
-    result->push_back(available_instructions
-                          ->second[GetFuzzerContext()->RandomIndex(
-                              available_instructions->second)]
-                          ->result_id());
+  std::vector<uint32_t> result;
+  for (uint32_t index = 0;
+       index < matrix_type_instruction.GetSingleWordInOperand(1); index++) {
+    result.push_back(available_instructions
+                         ->second[GetFuzzerContext()->RandomIndex(
+                             available_instructions->second)]
+                         ->result_id());
   }
   return result;
 }
 
-std::unique_ptr<std::vector<uint32_t>>
-FuzzerPassConstructComposites::TryConstructingStructComposite(
-    const opt::analysis::Struct& struct_type,
+std::vector<uint32_t>
+FuzzerPassConstructComposites::FindComponentsToConstructStruct(
+    const opt::Instruction& struct_type_instruction,
     const TypeIdToInstructions& type_id_to_available_instructions) {
-  auto result = MakeUnique<std::vector<uint32_t>>();
+  assert(struct_type_instruction.opcode() == SpvOpTypeStruct &&
+         "Precondition: instruction must be a struct type.");
+  std::vector<uint32_t> result;
   // Consider the type of each field of the struct.
-  for (auto element_type : struct_type.element_types()) {
-    auto element_type_id = GetIRContext()->get_type_mgr()->GetId(element_type);
+  for (uint32_t in_operand_index = 0;
+       in_operand_index < struct_type_instruction.NumInOperands();
+       in_operand_index++) {
+    auto element_type_id =
+        struct_type_instruction.GetSingleWordInOperand(in_operand_index);
     // Find the instructions at our disposal that compute something of the field
     // type.
     auto available_instructions =
@@ -240,24 +249,28 @@
     if (available_instructions == type_id_to_available_instructions.cend()) {
       // If there are no such instructions, we cannot construct a composite of
       // this struct type.
-      return nullptr;
+      return {};
     }
-    result->push_back(available_instructions
-                          ->second[GetFuzzerContext()->RandomIndex(
-                              available_instructions->second)]
-                          ->result_id());
+    result.push_back(available_instructions
+                         ->second[GetFuzzerContext()->RandomIndex(
+                             available_instructions->second)]
+                         ->result_id());
   }
   return result;
 }
 
-std::unique_ptr<std::vector<uint32_t>>
-FuzzerPassConstructComposites::TryConstructingVectorComposite(
-    const opt::analysis::Vector& vector_type,
+std::vector<uint32_t>
+FuzzerPassConstructComposites::FindComponentsToConstructVector(
+    const opt::Instruction& vector_type_instruction,
     const TypeIdToInstructions& type_id_to_available_instructions) {
+  assert(vector_type_instruction.opcode() == SpvOpTypeVector &&
+         "Precondition: instruction must be a vector type.");
+
   // Get details of the type underlying the vector, and the width of the vector,
   // for convenience.
-  auto element_type = vector_type.element_type();
-  auto element_count = vector_type.element_count();
+  auto element_type_id = vector_type_instruction.GetSingleWordInOperand(0);
+  auto element_type = GetIRContext()->get_type_mgr()->GetType(element_type_id);
+  auto element_count = vector_type_instruction.GetSingleWordInOperand(1);
 
   // Collect a mapping, from type id to width, for scalar/vector types that are
   // smaller in width than |vector_type|, but that have the same underlying
@@ -268,14 +281,12 @@
   std::map<uint32_t, uint32_t> smaller_vector_type_id_to_width;
   // Add the underlying type.  This id must exist, in order for |vector_type| to
   // exist.
-  auto scalar_type_id = GetIRContext()->get_type_mgr()->GetId(element_type);
-  smaller_vector_type_id_to_width[scalar_type_id] = 1;
+  smaller_vector_type_id_to_width[element_type_id] = 1;
 
   // Now add every vector type with width at least 2, and less than the width of
   // |vector_type|.
   for (uint32_t width = 2; width < element_count; width++) {
-    opt::analysis::Vector smaller_vector_type(vector_type.element_type(),
-                                              width);
+    opt::analysis::Vector smaller_vector_type(element_type, width);
     auto smaller_vector_type_id =
         GetIRContext()->get_type_mgr()->GetId(&smaller_vector_type);
     // We might find that there is no declared type of this smaller width.
@@ -302,12 +313,11 @@
   // order at this stage.
   std::vector<opt::Instruction*> instructions_to_use;
 
-  while (vector_slots_used < vector_type.element_count()) {
+  while (vector_slots_used < element_count) {
     std::vector<opt::Instruction*> instructions_to_choose_from;
     for (auto& entry : smaller_vector_type_id_to_width) {
       if (entry.second >
-          std::min(vector_type.element_count() - 1,
-                   vector_type.element_count() - vector_slots_used)) {
+          std::min(element_count - 1, element_count - vector_slots_used)) {
         continue;
       }
       auto available_instructions =
@@ -326,7 +336,7 @@
       // another manner, so we could opt to retry a few times here, but it is
       // simpler to just give up on the basis that this will not happen
       // frequently.
-      return nullptr;
+      return {};
     }
     auto instruction_to_use =
         instructions_to_choose_from[GetFuzzerContext()->RandomIndex(
@@ -345,16 +355,16 @@
       vector_slots_used += 1;
     }
   }
-  assert(vector_slots_used == vector_type.element_count());
+  assert(vector_slots_used == element_count);
 
-  auto result = MakeUnique<std::vector<uint32_t>>();
+  std::vector<uint32_t> result;
   std::vector<uint32_t> operands;
   while (!instructions_to_use.empty()) {
     auto index = GetFuzzerContext()->RandomIndex(instructions_to_use);
-    result->push_back(instructions_to_use[index]->result_id());
+    result.push_back(instructions_to_use[index]->result_id());
     instructions_to_use.erase(instructions_to_use.begin() + index);
   }
-  assert(result->size() > 1);
+  assert(result.size() > 1);
   return result;
 }
 
diff --git a/source/fuzz/fuzzer_pass_construct_composites.h b/source/fuzz/fuzzer_pass_construct_composites.h
index 99ef31f..9853fad 100644
--- a/source/fuzz/fuzzer_pass_construct_composites.h
+++ b/source/fuzz/fuzzer_pass_construct_composites.h
@@ -27,7 +27,7 @@
 class FuzzerPassConstructComposites : public FuzzerPass {
  public:
   FuzzerPassConstructComposites(
-      opt::IRContext* ir_context, FactManager* fact_manager,
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
       protobufs::TransformationSequence* transformations);
 
@@ -49,27 +49,28 @@
       opt::Instruction* inst,
       TypeIdToInstructions* type_id_to_available_instructions);
 
+  // Requires that |array_type_instruction| has opcode OpTypeArray.
   // Attempts to find suitable instruction result ids from the values of
   // |type_id_to_available_instructions| that would allow a composite of type
-  // |array_type| to be constructed.  Returns said ids if they can be found.
-  // Returns |nullptr| otherwise.
-  std::unique_ptr<std::vector<uint32_t>> TryConstructingArrayComposite(
-      const opt::analysis::Array& array_type,
+  // |array_type_instruction| to be constructed.  Returns said ids if they can
+  // be found and an empty vector otherwise.
+  std::vector<uint32_t> FindComponentsToConstructArray(
+      const opt::Instruction& array_type_instruction,
       const TypeIdToInstructions& type_id_to_available_instructions);
 
-  // Similar to TryConstructingArrayComposite, but for matrices.
-  std::unique_ptr<std::vector<uint32_t>> TryConstructingMatrixComposite(
-      const opt::analysis::Matrix& matrix_type,
+  // Similar to FindComponentsToConstructArray, but for matrices.
+  std::vector<uint32_t> FindComponentsToConstructMatrix(
+      const opt::Instruction& matrix_type_instruction,
       const TypeIdToInstructions& type_id_to_available_instructions);
 
-  // Similar to TryConstructingArrayComposite, but for structs.
-  std::unique_ptr<std::vector<uint32_t>> TryConstructingStructComposite(
-      const opt::analysis::Struct& struct_type,
+  // Similar to FindComponentsToConstructArray, but for structs.
+  std::vector<uint32_t> FindComponentsToConstructStruct(
+      const opt::Instruction& struct_type_instruction,
       const TypeIdToInstructions& type_id_to_available_instructions);
 
-  // Similar to TryConstructingArrayComposite, but for vectors.
-  std::unique_ptr<std::vector<uint32_t>> TryConstructingVectorComposite(
-      const opt::analysis::Vector& vector_type,
+  // Similar to FindComponentsToConstructArray, but for vectors.
+  std::vector<uint32_t> FindComponentsToConstructVector(
+      const opt::Instruction& vector_type_instruction,
       const TypeIdToInstructions& type_id_to_available_instructions);
 };
 
diff --git a/source/fuzz/fuzzer_pass_copy_objects.cpp b/source/fuzz/fuzzer_pass_copy_objects.cpp
index 35b15a3..f055b59 100644
--- a/source/fuzz/fuzzer_pass_copy_objects.cpp
+++ b/source/fuzz/fuzzer_pass_copy_objects.cpp
@@ -21,16 +21,17 @@
 namespace fuzz {
 
 FuzzerPassCopyObjects::FuzzerPassCopyObjects(
-    opt::IRContext* ir_context, FactManager* fact_manager,
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
     protobufs::TransformationSequence* transformations)
-    : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
 
 FuzzerPassCopyObjects::~FuzzerPassCopyObjects() = default;
 
 void FuzzerPassCopyObjects::Apply() {
-  MaybeAddTransformationBeforeEachInstruction(
-      [this](const opt::Function& function, opt::BasicBlock* block,
+  ForEachInstructionWithInstructionDescriptor(
+      [this](opt::Function* function, opt::BasicBlock* block,
              opt::BasicBlock::iterator inst_it,
              const protobufs::InstructionDescriptor& instruction_descriptor)
           -> void {
@@ -64,15 +65,11 @@
 
         // Choose a copyable instruction at random, and create and apply an
         // object copying transformation based on it.
-        uint32_t index = GetFuzzerContext()->RandomIndex(relevant_instructions);
-        TransformationCopyObject transformation(
-            relevant_instructions[index]->result_id(), instruction_descriptor,
-            GetFuzzerContext()->GetFreshId());
-        assert(transformation.IsApplicable(GetIRContext(), *GetFactManager()) &&
-               "This transformation should be applicable by construction.");
-        transformation.Apply(GetIRContext(), GetFactManager());
-        *GetTransformations()->add_transformation() =
-            transformation.ToMessage();
+        ApplyTransformation(TransformationCopyObject(
+            relevant_instructions[GetFuzzerContext()->RandomIndex(
+                                      relevant_instructions)]
+                ->result_id(),
+            instruction_descriptor, GetFuzzerContext()->GetFreshId()));
       });
 }
 
diff --git a/source/fuzz/fuzzer_pass_copy_objects.h b/source/fuzz/fuzzer_pass_copy_objects.h
index 5419459..8de382e 100644
--- a/source/fuzz/fuzzer_pass_copy_objects.h
+++ b/source/fuzz/fuzzer_pass_copy_objects.h
@@ -23,7 +23,8 @@
 // A fuzzer pass for adding adding copies of objects to the module.
 class FuzzerPassCopyObjects : public FuzzerPass {
  public:
-  FuzzerPassCopyObjects(opt::IRContext* ir_context, FactManager* fact_manager,
+  FuzzerPassCopyObjects(opt::IRContext* ir_context,
+                        TransformationContext* transformation_context,
                         FuzzerContext* fuzzer_context,
                         protobufs::TransformationSequence* transformations);
 
diff --git a/source/fuzz/fuzzer_pass_donate_modules.cpp b/source/fuzz/fuzzer_pass_donate_modules.cpp
index 33813d2..bb48303 100644
--- a/source/fuzz/fuzzer_pass_donate_modules.cpp
+++ b/source/fuzz/fuzzer_pass_donate_modules.cpp
@@ -18,9 +18,11 @@
 #include <queue>
 #include <set>
 
+#include "source/fuzz/call_graph.h"
 #include "source/fuzz/instruction_message.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_function.h"
 #include "source/fuzz/transformation_add_global_undef.h"
@@ -39,11 +41,12 @@
 namespace fuzz {
 
 FuzzerPassDonateModules::FuzzerPassDonateModules(
-    opt::IRContext* ir_context, FactManager* fact_manager,
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
     protobufs::TransformationSequence* transformations,
     const std::vector<fuzzerutil::ModuleSupplier>& donor_suppliers)
-    : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations),
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations),
       donor_suppliers_(donor_suppliers) {}
 
 FuzzerPassDonateModules::~FuzzerPassDonateModules() = default;
@@ -61,14 +64,25 @@
     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");
     // Donate the supplied module.
-    DonateSingleModule(donor_ir_context.get());
+    //
+    // Randomly decide whether to make the module livesafe (see
+    // FactFunctionIsLivesafe); doing so allows it to be used for live code
+    // injection but restricts its behaviour to allow this, and means that its
+    // functions cannot be transformed as if they were arbitrary dead code.
+    bool make_livesafe = GetFuzzerContext()->ChoosePercentage(
+        GetFuzzerContext()->ChanceOfMakingDonorLivesafe());
+    DonateSingleModule(donor_ir_context.get(), make_livesafe);
   } while (GetFuzzerContext()->ChoosePercentage(
       GetFuzzerContext()->GetChanceOfDonatingAdditionalModule()));
 }
 
 void FuzzerPassDonateModules::DonateSingleModule(
-    opt::IRContext* donor_ir_context) {
+    opt::IRContext* donor_ir_context, bool make_livesafe) {
   // 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,
@@ -91,7 +105,7 @@
   HandleExternalInstructionImports(donor_ir_context,
                                    &original_id_to_donated_id);
   HandleTypesAndValues(donor_ir_context, &original_id_to_donated_id);
-  HandleFunctions(donor_ir_context, &original_id_to_donated_id);
+  HandleFunctions(donor_ir_context, &original_id_to_donated_id, make_livesafe);
 
   // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3115) Handle some
   //  kinds of decoration.
@@ -102,6 +116,7 @@
   switch (donor_storage_class) {
     case SpvStorageClassFunction:
     case SpvStorageClassPrivate:
+    case SpvStorageClassWorkgroup:
       // We leave these alone
       return donor_storage_class;
     case SpvStorageClassInput:
@@ -152,277 +167,391 @@
     std::map<uint32_t, uint32_t>* original_id_to_donated_id) {
   // Consider every type/global/constant/undef in the module.
   for (auto& type_or_value : donor_ir_context->module()->types_values()) {
-    // Each such instruction generates a result id, and as part of donation we
-    // need to associate the donor's result id with a new result id.  That new
-    // result id will either be the id of some existing instruction, or a fresh
-    // id.  This variable captures it.
-    uint32_t new_result_id;
+    HandleTypeOrValue(type_or_value, original_id_to_donated_id);
+  }
+}
 
-    // Decide how to handle each kind of instruction on a case-by-case basis.
-    //
-    // Because the donor module is required to be valid, when we encounter a
-    // type comprised of component types (e.g. an aggregate or pointer), we know
-    // that its component types will have been considered previously, and that
-    // |original_id_to_donated_id| will already contain an entry for them.
-    switch (type_or_value.opcode()) {
-      case SpvOpTypeVoid: {
-        // Void has to exist already in order for us to have an entry point.
-        // Get the existing id of void.
-        opt::analysis::Void void_type;
-        new_result_id = GetIRContext()->get_type_mgr()->GetId(&void_type);
-        assert(new_result_id &&
-               "The module being transformed will always have 'void' type "
-               "declared.");
-      } break;
-      case SpvOpTypeBool: {
-        // Bool cannot be declared multiple times, so use its existing id if
-        // present, or add a declaration of Bool with a fresh id if not.
-        opt::analysis::Bool bool_type;
-        auto bool_type_id = GetIRContext()->get_type_mgr()->GetId(&bool_type);
-        if (bool_type_id) {
-          new_result_id = bool_type_id;
-        } else {
-          new_result_id = GetFuzzerContext()->GetFreshId();
-          ApplyTransformation(TransformationAddTypeBoolean(new_result_id));
-        }
-      } break;
-      case SpvOpTypeInt: {
-        // Int cannot be declared multiple times with the same width and
-        // signedness, so check whether an existing identical Int type is
-        // present and use its id if so.  Otherwise add a declaration of the
-        // Int type used by the donor, with a fresh id.
-        const uint32_t width = type_or_value.GetSingleWordInOperand(0);
-        const bool is_signed =
-            static_cast<bool>(type_or_value.GetSingleWordInOperand(1));
-        opt::analysis::Integer int_type(width, is_signed);
-        auto int_type_id = GetIRContext()->get_type_mgr()->GetId(&int_type);
-        if (int_type_id) {
-          new_result_id = int_type_id;
-        } else {
-          new_result_id = GetFuzzerContext()->GetFreshId();
-          ApplyTransformation(
-              TransformationAddTypeInt(new_result_id, width, is_signed));
-        }
-      } break;
-      case SpvOpTypeFloat: {
-        // Similar to SpvOpTypeInt.
-        const uint32_t width = type_or_value.GetSingleWordInOperand(0);
-        opt::analysis::Float float_type(width);
-        auto float_type_id = GetIRContext()->get_type_mgr()->GetId(&float_type);
-        if (float_type_id) {
-          new_result_id = float_type_id;
-        } else {
-          new_result_id = GetFuzzerContext()->GetFreshId();
-          ApplyTransformation(TransformationAddTypeFloat(new_result_id, width));
-        }
-      } break;
-      case SpvOpTypeVector: {
-        // It is not legal to have two Vector type declarations with identical
-        // element types and element counts, so check whether an existing
-        // identical Vector type is present and use its id if so.  Otherwise add
-        // a declaration of the Vector type used by the donor, with a fresh id.
+void FuzzerPassDonateModules::HandleTypeOrValue(
+    const opt::Instruction& type_or_value,
+    std::map<uint32_t, uint32_t>* original_id_to_donated_id) {
+  // The type/value instruction generates a result id, and we need to associate
+  // the donor's result id with a new result id.  That new result id will either
+  // be the id of some existing instruction, or a fresh id.  This variable
+  // captures it.
+  uint32_t new_result_id;
 
-        // When considering the vector's component type id, we look up the id
-        // use in the donor to find the id to which this has been remapped.
-        uint32_t component_type_id = original_id_to_donated_id->at(
-            type_or_value.GetSingleWordInOperand(0));
-        auto component_type =
-            GetIRContext()->get_type_mgr()->GetType(component_type_id);
-        assert(component_type && "The base type should be registered.");
-        auto component_count = type_or_value.GetSingleWordInOperand(1);
-        opt::analysis::Vector vector_type(component_type, component_count);
-        auto vector_type_id =
-            GetIRContext()->get_type_mgr()->GetId(&vector_type);
-        if (vector_type_id) {
-          new_result_id = vector_type_id;
-        } else {
-          new_result_id = GetFuzzerContext()->GetFreshId();
-          ApplyTransformation(TransformationAddTypeVector(
-              new_result_id, component_type_id, component_count));
-        }
-      } break;
-      case SpvOpTypeMatrix: {
-        // Similar to SpvOpTypeVector.
-        uint32_t column_type_id = original_id_to_donated_id->at(
-            type_or_value.GetSingleWordInOperand(0));
-        auto column_type =
-            GetIRContext()->get_type_mgr()->GetType(column_type_id);
-        assert(column_type && column_type->AsVector() &&
-               "The column type should be a registered vector type.");
-        auto column_count = type_or_value.GetSingleWordInOperand(1);
-        opt::analysis::Matrix matrix_type(column_type, column_count);
-        auto matrix_type_id =
-            GetIRContext()->get_type_mgr()->GetId(&matrix_type);
-        if (matrix_type_id) {
-          new_result_id = matrix_type_id;
-        } else {
-          new_result_id = GetFuzzerContext()->GetFreshId();
-          ApplyTransformation(TransformationAddTypeMatrix(
-              new_result_id, column_type_id, column_count));
-        }
-
-      } break;
-      case SpvOpTypeArray: {
-        // It is OK to have multiple structurally identical array types, so
-        // we go ahead and add a remapped version of the type declared by the
-        // donor.
+  // Decide how to handle each kind of instruction on a case-by-case basis.
+  //
+  // Because the donor module is required to be valid, when we encounter a
+  // type comprised of component types (e.g. an aggregate or pointer), we know
+  // that its component types will have been considered previously, and that
+  // |original_id_to_donated_id| will already contain an entry for them.
+  switch (type_or_value.opcode()) {
+    case SpvOpTypeImage:
+    case SpvOpTypeSampledImage:
+    case SpvOpTypeSampler:
+      // We do not donate types and variables that relate to images and
+      // samplers, so we skip these types and subsequently skip anything that
+      // depends on them.
+      return;
+    case SpvOpTypeVoid: {
+      // Void has to exist already in order for us to have an entry point.
+      // Get the existing id of void.
+      opt::analysis::Void void_type;
+      new_result_id = GetIRContext()->get_type_mgr()->GetId(&void_type);
+      assert(new_result_id &&
+             "The module being transformed will always have 'void' type "
+             "declared.");
+    } break;
+    case SpvOpTypeBool: {
+      // Bool cannot be declared multiple times, so use its existing id if
+      // present, or add a declaration of Bool with a fresh id if not.
+      opt::analysis::Bool bool_type;
+      auto bool_type_id = GetIRContext()->get_type_mgr()->GetId(&bool_type);
+      if (bool_type_id) {
+        new_result_id = bool_type_id;
+      } else {
         new_result_id = GetFuzzerContext()->GetFreshId();
-        ApplyTransformation(TransformationAddTypeArray(
+        ApplyTransformation(TransformationAddTypeBoolean(new_result_id));
+      }
+    } break;
+    case SpvOpTypeInt: {
+      // Int cannot be declared multiple times with the same width and
+      // signedness, so check whether an existing identical Int type is
+      // present and use its id if so.  Otherwise add a declaration of the
+      // Int type used by the donor, with a fresh id.
+      const uint32_t width = type_or_value.GetSingleWordInOperand(0);
+      const bool is_signed =
+          static_cast<bool>(type_or_value.GetSingleWordInOperand(1));
+      opt::analysis::Integer int_type(width, is_signed);
+      auto int_type_id = GetIRContext()->get_type_mgr()->GetId(&int_type);
+      if (int_type_id) {
+        new_result_id = int_type_id;
+      } else {
+        new_result_id = GetFuzzerContext()->GetFreshId();
+        ApplyTransformation(
+            TransformationAddTypeInt(new_result_id, width, is_signed));
+      }
+    } break;
+    case SpvOpTypeFloat: {
+      // Similar to SpvOpTypeInt.
+      const uint32_t width = type_or_value.GetSingleWordInOperand(0);
+      opt::analysis::Float float_type(width);
+      auto float_type_id = GetIRContext()->get_type_mgr()->GetId(&float_type);
+      if (float_type_id) {
+        new_result_id = float_type_id;
+      } else {
+        new_result_id = GetFuzzerContext()->GetFreshId();
+        ApplyTransformation(TransformationAddTypeFloat(new_result_id, width));
+      }
+    } break;
+    case SpvOpTypeVector: {
+      // It is not legal to have two Vector type declarations with identical
+      // element types and element counts, so check whether an existing
+      // identical Vector type is present and use its id if so.  Otherwise add
+      // a declaration of the Vector type used by the donor, with a fresh id.
+
+      // When considering the vector's component type id, we look up the id
+      // use in the donor to find the id to which this has been remapped.
+      uint32_t component_type_id = original_id_to_donated_id->at(
+          type_or_value.GetSingleWordInOperand(0));
+      auto component_type =
+          GetIRContext()->get_type_mgr()->GetType(component_type_id);
+      assert(component_type && "The base type should be registered.");
+      auto component_count = type_or_value.GetSingleWordInOperand(1);
+      opt::analysis::Vector vector_type(component_type, component_count);
+      auto vector_type_id = GetIRContext()->get_type_mgr()->GetId(&vector_type);
+      if (vector_type_id) {
+        new_result_id = vector_type_id;
+      } else {
+        new_result_id = GetFuzzerContext()->GetFreshId();
+        ApplyTransformation(TransformationAddTypeVector(
+            new_result_id, component_type_id, component_count));
+      }
+    } break;
+    case SpvOpTypeMatrix: {
+      // Similar to SpvOpTypeVector.
+      uint32_t column_type_id = original_id_to_donated_id->at(
+          type_or_value.GetSingleWordInOperand(0));
+      auto column_type =
+          GetIRContext()->get_type_mgr()->GetType(column_type_id);
+      assert(column_type && column_type->AsVector() &&
+             "The column type should be a registered vector type.");
+      auto column_count = type_or_value.GetSingleWordInOperand(1);
+      opt::analysis::Matrix matrix_type(column_type, column_count);
+      auto matrix_type_id = GetIRContext()->get_type_mgr()->GetId(&matrix_type);
+      if (matrix_type_id) {
+        new_result_id = matrix_type_id;
+      } else {
+        new_result_id = GetFuzzerContext()->GetFreshId();
+        ApplyTransformation(TransformationAddTypeMatrix(
+            new_result_id, column_type_id, column_count));
+      }
+
+    } break;
+    case SpvOpTypeArray: {
+      // It is OK to have multiple structurally identical array types, so
+      // we go ahead and add a remapped version of the type declared by the
+      // donor.
+      uint32_t component_type_id = type_or_value.GetSingleWordInOperand(0);
+      if (!original_id_to_donated_id->count(component_type_id)) {
+        // We did not donate the component type of this array type, so we
+        // cannot donate the array type.
+        return;
+      }
+      new_result_id = GetFuzzerContext()->GetFreshId();
+      ApplyTransformation(TransformationAddTypeArray(
+          new_result_id, original_id_to_donated_id->at(component_type_id),
+          original_id_to_donated_id->at(
+              type_or_value.GetSingleWordInOperand(1))));
+    } break;
+    case SpvOpTypeRuntimeArray: {
+      // A runtime array is allowed as the final member of an SSBO.  During
+      // donation we turn runtime arrays into fixed-size arrays.  For dead
+      // code donations this is OK because the array is never indexed into at
+      // runtime, so it does not matter what its size is.  For live-safe code,
+      // all accesses are made in-bounds, so this is also OK.
+      //
+      // The special OpArrayLength instruction, which works on runtime arrays,
+      // is rewritten to yield the fixed length that is used for the array.
+
+      uint32_t component_type_id = type_or_value.GetSingleWordInOperand(0);
+      if (!original_id_to_donated_id->count(component_type_id)) {
+        // We did not donate the component type of this runtime array type, so
+        // we cannot donate it as a fixed-size array.
+        return;
+      }
+      new_result_id = GetFuzzerContext()->GetFreshId();
+      ApplyTransformation(TransformationAddTypeArray(
+          new_result_id, original_id_to_donated_id->at(component_type_id),
+          FindOrCreate32BitIntegerConstant(
+              GetFuzzerContext()->GetRandomSizeForNewArray(), false)));
+    } break;
+    case SpvOpTypeStruct: {
+      // Similar to SpvOpTypeArray.
+      std::vector<uint32_t> member_type_ids;
+      for (uint32_t i = 0; i < type_or_value.NumInOperands(); i++) {
+        auto component_type_id = type_or_value.GetSingleWordInOperand(i);
+        if (!original_id_to_donated_id->count(component_type_id)) {
+          // We did not donate every member type for this struct type, so we
+          // cannot donate the struct type.
+          return;
+        }
+        member_type_ids.push_back(
+            original_id_to_donated_id->at(component_type_id));
+      }
+      new_result_id = GetFuzzerContext()->GetFreshId();
+      ApplyTransformation(
+          TransformationAddTypeStruct(new_result_id, member_type_ids));
+    } break;
+    case SpvOpTypePointer: {
+      // Similar to SpvOpTypeArray.
+      uint32_t pointee_type_id = type_or_value.GetSingleWordInOperand(1);
+      if (!original_id_to_donated_id->count(pointee_type_id)) {
+        // We did not donate the pointee type for this pointer type, so we
+        // cannot donate the pointer type.
+        return;
+      }
+      new_result_id = GetFuzzerContext()->GetFreshId();
+      ApplyTransformation(TransformationAddTypePointer(
+          new_result_id,
+          AdaptStorageClass(static_cast<SpvStorageClass>(
+              type_or_value.GetSingleWordInOperand(0))),
+          original_id_to_donated_id->at(pointee_type_id)));
+    } break;
+    case SpvOpTypeFunction: {
+      // It is not OK to have multiple function types that use identical ids
+      // for their return and parameter types.  We thus go through all
+      // existing function types to look for a match.  We do not use the
+      // type manager here because we want to regard two function types that
+      // are structurally identical but that differ with respect to the
+      // actual ids used for pointer types as different.
+      //
+      // Example:
+      //
+      // %1 = OpTypeVoid
+      // %2 = OpTypeInt 32 0
+      // %3 = OpTypePointer Function %2
+      // %4 = OpTypePointer Function %2
+      // %5 = OpTypeFunction %1 %3
+      // %6 = OpTypeFunction %1 %4
+      //
+      // We regard %5 and %6 as distinct function types here, even though
+      // they both have the form "uint32* -> void"
+
+      std::vector<uint32_t> return_and_parameter_types;
+      for (uint32_t i = 0; i < type_or_value.NumInOperands(); i++) {
+        uint32_t return_or_parameter_type =
+            type_or_value.GetSingleWordInOperand(i);
+        if (!original_id_to_donated_id->count(return_or_parameter_type)) {
+          // We did not donate every return/parameter type for this function
+          // type, so we cannot donate the function type.
+          return;
+        }
+        return_and_parameter_types.push_back(
+            original_id_to_donated_id->at(return_or_parameter_type));
+      }
+      uint32_t existing_function_id = fuzzerutil::FindFunctionType(
+          GetIRContext(), return_and_parameter_types);
+      if (existing_function_id) {
+        new_result_id = existing_function_id;
+      } else {
+        // No match was found, so add a remapped version of the function type
+        // to the module, with a fresh id.
+        new_result_id = GetFuzzerContext()->GetFreshId();
+        std::vector<uint32_t> argument_type_ids;
+        for (uint32_t i = 1; i < type_or_value.NumInOperands(); i++) {
+          argument_type_ids.push_back(original_id_to_donated_id->at(
+              type_or_value.GetSingleWordInOperand(i)));
+        }
+        ApplyTransformation(TransformationAddTypeFunction(
             new_result_id,
             original_id_to_donated_id->at(
                 type_or_value.GetSingleWordInOperand(0)),
-            original_id_to_donated_id->at(
-                type_or_value.GetSingleWordInOperand(1))));
-      } break;
-      case SpvOpTypeStruct: {
-        // Similar to SpvOpTypeArray.
-        new_result_id = GetFuzzerContext()->GetFreshId();
-        std::vector<uint32_t> member_type_ids;
-        type_or_value.ForEachInId(
-            [&member_type_ids,
-             &original_id_to_donated_id](const uint32_t* component_type_id) {
-              member_type_ids.push_back(
-                  original_id_to_donated_id->at(*component_type_id));
-            });
-        ApplyTransformation(
-            TransformationAddTypeStruct(new_result_id, member_type_ids));
-      } break;
-      case SpvOpTypePointer: {
-        // Similar to SpvOpTypeArray.
-        new_result_id = GetFuzzerContext()->GetFreshId();
-        ApplyTransformation(TransformationAddTypePointer(
-            new_result_id,
-            AdaptStorageClass(static_cast<SpvStorageClass>(
-                type_or_value.GetSingleWordInOperand(0))),
-            original_id_to_donated_id->at(
-                type_or_value.GetSingleWordInOperand(1))));
-      } break;
-      case SpvOpTypeFunction: {
-        // It is not OK to have multiple function types that use identical ids
-        // for their return and parameter types.  We thus first look for a
-        // matching function type in the recipient module and use the id of this
-        // type if a match is found.  Otherwise we add a remapped version of the
-        // function type.
+            argument_type_ids));
+      }
+    } break;
+    case SpvOpConstantTrue:
+    case SpvOpConstantFalse: {
+      // It is OK to have duplicate definitions of True and False, so add
+      // these to the module, using a remapped Bool type.
+      new_result_id = GetFuzzerContext()->GetFreshId();
+      ApplyTransformation(TransformationAddConstantBoolean(
+          new_result_id, type_or_value.opcode() == SpvOpConstantTrue));
+    } break;
+    case SpvOpConstant: {
+      // It is OK to have duplicate constant definitions, so add this to the
+      // module using a remapped result type.
+      new_result_id = GetFuzzerContext()->GetFreshId();
+      std::vector<uint32_t> data_words;
+      type_or_value.ForEachInOperand([&data_words](const uint32_t* in_operand) {
+        data_words.push_back(*in_operand);
+      });
+      ApplyTransformation(TransformationAddConstantScalar(
+          new_result_id, original_id_to_donated_id->at(type_or_value.type_id()),
+          data_words));
+    } break;
+    case SpvOpConstantComposite: {
+      assert(original_id_to_donated_id->count(type_or_value.type_id()) &&
+             "Composite types for which it is possible to create a constant "
+             "should have been donated.");
 
-        // Build a sequence of types used as parameters for the function type.
-        std::vector<const opt::analysis::Type*> parameter_types;
-        // We start iterating at 1 because 0 is the function's return type.
-        for (uint32_t index = 1; index < type_or_value.NumInOperands();
-             index++) {
-          parameter_types.push_back(GetIRContext()->get_type_mgr()->GetType(
-              original_id_to_donated_id->at(
-                  type_or_value.GetSingleWordInOperand(index))));
-        }
-        // Make a type object corresponding to the function type.
-        opt::analysis::Function function_type(
-            GetIRContext()->get_type_mgr()->GetType(
-                original_id_to_donated_id->at(
-                    type_or_value.GetSingleWordInOperand(0))),
-            parameter_types);
+      // It is OK to have duplicate constant composite definitions, so add
+      // this to the module using remapped versions of all consituent ids and
+      // the result type.
+      new_result_id = GetFuzzerContext()->GetFreshId();
+      std::vector<uint32_t> constituent_ids;
+      type_or_value.ForEachInId([&constituent_ids, &original_id_to_donated_id](
+                                    const uint32_t* constituent_id) {
+        assert(original_id_to_donated_id->count(*constituent_id) &&
+               "The constants used to construct this composite should "
+               "have been donated.");
+        constituent_ids.push_back(
+            original_id_to_donated_id->at(*constituent_id));
+      });
+      ApplyTransformation(TransformationAddConstantComposite(
+          new_result_id, original_id_to_donated_id->at(type_or_value.type_id()),
+          constituent_ids));
+    } break;
+    case SpvOpConstantNull: {
+      if (!original_id_to_donated_id->count(type_or_value.type_id())) {
+        // We did not donate the type associated with this null constant, so
+        // we cannot donate the null constant.
+        return;
+      }
 
-        // Check whether a function type corresponding to this this type object
-        // is already declared by the module.
-        auto function_type_id =
-            GetIRContext()->get_type_mgr()->GetId(&function_type);
-        if (function_type_id) {
-          // A suitable existing function was found - use its id.
-          new_result_id = function_type_id;
-        } else {
-          // No match was found, so add a remapped version of the function type
-          // to the module, with a fresh id.
-          new_result_id = GetFuzzerContext()->GetFreshId();
-          std::vector<uint32_t> argument_type_ids;
-          for (uint32_t index = 1; index < type_or_value.NumInOperands();
-               index++) {
-            argument_type_ids.push_back(original_id_to_donated_id->at(
-                type_or_value.GetSingleWordInOperand(index)));
-          }
-          ApplyTransformation(TransformationAddTypeFunction(
-              new_result_id,
-              original_id_to_donated_id->at(
-                  type_or_value.GetSingleWordInOperand(0)),
-              argument_type_ids));
-        }
-      } break;
-      case SpvOpConstantTrue:
-      case SpvOpConstantFalse: {
-        // It is OK to have duplicate definitions of True and False, so add
-        // these to the module, using a remapped Bool type.
-        new_result_id = GetFuzzerContext()->GetFreshId();
-        ApplyTransformation(TransformationAddConstantBoolean(
-            new_result_id, type_or_value.opcode() == SpvOpConstantTrue));
-      } break;
-      case SpvOpConstant: {
-        // It is OK to have duplicate constant definitions, so add this to the
-        // module using a remapped result type.
-        new_result_id = GetFuzzerContext()->GetFreshId();
-        std::vector<uint32_t> data_words;
-        type_or_value.ForEachInOperand(
-            [&data_words](const uint32_t* in_operand) {
-              data_words.push_back(*in_operand);
-            });
-        ApplyTransformation(TransformationAddConstantScalar(
-            new_result_id,
-            original_id_to_donated_id->at(type_or_value.type_id()),
-            data_words));
-      } break;
-      case SpvOpConstantComposite: {
-        // It is OK to have duplicate constant composite definitions, so add
-        // this to the module using remapped versions of all consituent ids and
-        // the result type.
-        new_result_id = GetFuzzerContext()->GetFreshId();
-        std::vector<uint32_t> constituent_ids;
-        type_or_value.ForEachInId(
-            [&constituent_ids,
-             &original_id_to_donated_id](const uint32_t* constituent_id) {
-              constituent_ids.push_back(
-                  original_id_to_donated_id->at(*constituent_id));
-            });
-        ApplyTransformation(TransformationAddConstantComposite(
-            new_result_id,
-            original_id_to_donated_id->at(type_or_value.type_id()),
-            constituent_ids));
-      } break;
-      case SpvOpVariable: {
-        // This is a global variable that could have one of various storage
-        // classes.  However, we change all global variable pointer storage
-        // classes (such as Uniform, Input and Output) to private when donating
-        // pointer types.  Thus this variable's pointer type is guaranteed to
-        // have storage class private.  As a result, we simply add a Private
-        // storage class global variable, using remapped versions of the result
-        // type and initializer ids for the global variable in the donor.
-        new_result_id = GetFuzzerContext()->GetFreshId();
-        ApplyTransformation(TransformationAddGlobalVariable(
-            new_result_id,
-            original_id_to_donated_id->at(type_or_value.type_id()),
-            type_or_value.NumInOperands() == 1
-                ? 0
-                : original_id_to_donated_id->at(
-                      type_or_value.GetSingleWordInOperand(1))));
-      } break;
-      case SpvOpUndef: {
-        // It is fine to have multiple Undef instructions of the same type, so
-        // we just add this to the recipient module.
-        new_result_id = GetFuzzerContext()->GetFreshId();
-        ApplyTransformation(TransformationAddGlobalUndef(
-            new_result_id,
-            original_id_to_donated_id->at(type_or_value.type_id())));
-      } break;
-      default: {
-        assert(0 && "Unknown type/value.");
-        new_result_id = 0;
-      } break;
-    }
-    // Update the id mapping to associate the instruction's result id with its
-    // corresponding id in the recipient.
-    original_id_to_donated_id->insert(
-        {type_or_value.result_id(), new_result_id});
+      // It is fine to have multiple OpConstantNull instructions of the same
+      // type, so we just add this to the recipient module.
+      new_result_id = GetFuzzerContext()->GetFreshId();
+      ApplyTransformation(TransformationAddConstantNull(
+          new_result_id,
+          original_id_to_donated_id->at(type_or_value.type_id())));
+    } break;
+    case SpvOpVariable: {
+      if (!original_id_to_donated_id->count(type_or_value.type_id())) {
+        // We did not donate the pointer type associated with this variable,
+        // so we cannot donate the variable.
+        return;
+      }
+
+      // This is a global variable that could have one of various storage
+      // classes.  However, we change all global variable pointer storage
+      // classes (such as Uniform, Input and Output) to private when donating
+      // pointer types, with the exception of the Workgroup storage class.
+      //
+      // Thus this variable's pointer type is guaranteed to have storage class
+      // Private or Workgroup.
+      //
+      // We add a global variable with either Private or Workgroup storage
+      // class, using remapped versions of the result type and initializer ids
+      // for the global variable in the donor.
+      //
+      // We regard the added variable as having an irrelevant value.  This
+      // means that future passes can add stores to the variable in any
+      // way they wish, and pass them as pointer parameters to functions
+      // without worrying about whether their data might get modified.
+      new_result_id = GetFuzzerContext()->GetFreshId();
+      uint32_t remapped_pointer_type =
+          original_id_to_donated_id->at(type_or_value.type_id());
+      uint32_t initializer_id;
+      SpvStorageClass storage_class =
+          static_cast<SpvStorageClass>(type_or_value.GetSingleWordInOperand(
+              0)) == SpvStorageClassWorkgroup
+              ? SpvStorageClassWorkgroup
+              : SpvStorageClassPrivate;
+      if (type_or_value.NumInOperands() == 1) {
+        // The variable did not have an initializer.  Initialize it to zero
+        // if it has Private storage class (to limit problems associated with
+        // uninitialized data), and leave it uninitialized if it has Workgroup
+        // storage class (as Workgroup variables cannot have initializers).
+
+        // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3275): we
+        //  could initialize Workgroup variables at the start of an entry
+        //  point, and should do so if their uninitialized nature proves
+        //  problematic.
+        initializer_id = storage_class == SpvStorageClassWorkgroup
+                             ? 0
+                             : FindOrCreateZeroConstant(
+                                   fuzzerutil::GetPointeeTypeIdFromPointerType(
+                                       GetIRContext(), remapped_pointer_type));
+      } else {
+        // The variable already had an initializer; use its remapped id.
+        initializer_id = original_id_to_donated_id->at(
+            type_or_value.GetSingleWordInOperand(1));
+      }
+      ApplyTransformation(
+          TransformationAddGlobalVariable(new_result_id, remapped_pointer_type,
+                                          storage_class, initializer_id, true));
+    } break;
+    case SpvOpUndef: {
+      if (!original_id_to_donated_id->count(type_or_value.type_id())) {
+        // We did not donate the type associated with this undef, so we cannot
+        // donate the undef.
+        return;
+      }
+
+      // It is fine to have multiple Undef instructions of the same type, so
+      // we just add this to the recipient module.
+      new_result_id = GetFuzzerContext()->GetFreshId();
+      ApplyTransformation(TransformationAddGlobalUndef(
+          new_result_id,
+          original_id_to_donated_id->at(type_or_value.type_id())));
+    } break;
+    default: {
+      assert(0 && "Unknown type/value.");
+      new_result_id = 0;
+    } break;
   }
+
+  // Update the id mapping to associate the instruction's result id with its
+  // corresponding id in the recipient.
+  original_id_to_donated_id->insert({type_or_value.result_id(), new_result_id});
 }
 
 void FuzzerPassDonateModules::HandleFunctions(
     opt::IRContext* donor_ir_context,
-    std::map<uint32_t, uint32_t>* original_id_to_donated_id) {
+    std::map<uint32_t, uint32_t>* original_id_to_donated_id,
+    bool make_livesafe) {
   // Get the ids of functions in the donor module, topologically sorted
   // according to the donor's call graph.
   auto topological_order =
@@ -445,123 +574,197 @@
     }
     assert(function_to_donate && "Function to be donated was not found.");
 
+    if (!original_id_to_donated_id->count(
+            function_to_donate->DefInst().GetSingleWordInOperand(1))) {
+      // We were not able to donate this function's type, so we cannot donate
+      // the function.
+      continue;
+    }
+
     // We will collect up protobuf messages representing the donor function's
     // instructions here, and use them to create an AddFunction transformation.
     std::vector<protobufs::Instruction> donated_instructions;
 
-    // Scan through the function, remapping each result id that it generates to
-    // a fresh id.  This is necessary because functions include forward
-    // references, e.g. to labels.
-    function_to_donate->ForEachInst([this, &original_id_to_donated_id](
-                                        const opt::Instruction* instruction) {
-      if (instruction->result_id()) {
-        original_id_to_donated_id->insert(
-            {instruction->result_id(), GetFuzzerContext()->GetFreshId()});
-      }
-    });
+    // This set tracks the ids of those instructions for which donation was
+    // completely skipped: neither the instruction nor a substitute for it was
+    // donated.
+    std::set<uint32_t> skipped_instructions;
 
     // Consider every instruction of the donor function.
     function_to_donate->ForEachInst(
-        [&donated_instructions,
-         &original_id_to_donated_id](const opt::Instruction* instruction) {
-          // Get the instruction's input operands into donation-ready form,
-          // remapping any id uses in the process.
-          opt::Instruction::OperandList input_operands;
-
-          // Consider each input operand in turn.
-          for (uint32_t in_operand_index = 0;
-               in_operand_index < instruction->NumInOperands();
-               in_operand_index++) {
-            std::vector<uint32_t> operand_data;
-            const opt::Operand& in_operand =
-                instruction->GetInOperand(in_operand_index);
-            switch (in_operand.type) {
-              case SPV_OPERAND_TYPE_ID:
-              case SPV_OPERAND_TYPE_TYPE_ID:
-              case SPV_OPERAND_TYPE_RESULT_ID:
-              case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID:
-              case SPV_OPERAND_TYPE_SCOPE_ID:
-                // This is an id operand - it consists of a single word of data,
-                // which needs to be remapped so that it is replaced with the
-                // donated form of the id.
-                operand_data.push_back(
-                    original_id_to_donated_id->at(in_operand.words[0]));
-                break;
-              default:
-                // For non-id operands, we just add each of the data words.
-                for (auto word : in_operand.words) {
-                  operand_data.push_back(word);
-                }
-                break;
-            }
-            input_operands.push_back({in_operand.type, operand_data});
+        [this, &donated_instructions, donor_ir_context,
+         &original_id_to_donated_id,
+         &skipped_instructions](const opt::Instruction* instruction) {
+          if (instruction->opcode() == SpvOpArrayLength) {
+            // We treat OpArrayLength specially.
+            HandleOpArrayLength(*instruction, original_id_to_donated_id,
+                                &donated_instructions);
+          } else if (!CanDonateInstruction(donor_ir_context, *instruction,
+                                           *original_id_to_donated_id,
+                                           skipped_instructions)) {
+            // This is an instruction that we cannot directly donate.
+            HandleDifficultInstruction(*instruction, original_id_to_donated_id,
+                                       &donated_instructions,
+                                       &skipped_instructions);
+          } else {
+            PrepareInstructionForDonation(*instruction, donor_ir_context,
+                                          original_id_to_donated_id,
+                                          &donated_instructions);
           }
-          // Remap the result type and result id (if present) of the
-          // instruction, and turn it into a protobuf message.
-          donated_instructions.push_back(MakeInstructionMessage(
-              instruction->opcode(),
-              instruction->type_id()
-                  ? original_id_to_donated_id->at(instruction->type_id())
-                  : 0,
-              instruction->result_id()
-                  ? original_id_to_donated_id->at(instruction->result_id())
-                  : 0,
-              input_operands));
         });
-    ApplyTransformation(TransformationAddFunction(donated_instructions));
+
+    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.
+      ApplyTransformation(TransformationAddFunction(donated_instructions));
+    }
+  }
+}
+
+bool FuzzerPassDonateModules::CanDonateInstruction(
+    opt::IRContext* donor_ir_context, const opt::Instruction& instruction,
+    const std::map<uint32_t, uint32_t>& original_id_to_donated_id,
+    const std::set<uint32_t>& skipped_instructions) const {
+  if (instruction.type_id() &&
+      !original_id_to_donated_id.count(instruction.type_id())) {
+    // We could not donate the result type of this instruction, so we cannot
+    // donate the instruction.
+    return false;
+  }
+
+  // Now consider instructions we specifically want to skip because we do not
+  // yet support them.
+  switch (instruction.opcode()) {
+    case SpvOpAtomicLoad:
+    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:
+      // We conservatively ignore all atomic instructions at present.
+      // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3276): Consider
+      //  being less conservative here.
+    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 SpvOpImageWrite:
+    case SpvOpImageSparseSampleImplicitLod:
+    case SpvOpImageSparseSampleExplicitLod:
+    case SpvOpImageSparseSampleDrefImplicitLod:
+    case SpvOpImageSparseSampleDrefExplicitLod:
+    case SpvOpImageSparseSampleProjImplicitLod:
+    case SpvOpImageSparseSampleProjExplicitLod:
+    case SpvOpImageSparseSampleProjDrefImplicitLod:
+    case SpvOpImageSparseSampleProjDrefExplicitLod:
+    case SpvOpImageSparseFetch:
+    case SpvOpImageSparseGather:
+    case SpvOpImageSparseDrefGather:
+    case SpvOpImageSparseRead:
+    case SpvOpImageSampleFootprintNV:
+    case SpvOpImage:
+    case SpvOpImageQueryFormat:
+    case SpvOpImageQueryLevels:
+    case SpvOpImageQueryLod:
+    case SpvOpImageQueryOrder:
+    case SpvOpImageQuerySamples:
+    case SpvOpImageQuerySize:
+    case SpvOpImageQuerySizeLod:
+    case SpvOpSampledImage:
+      // We ignore all instructions related to accessing images, since we do not
+      // donate images.
+      return false;
+    case SpvOpLoad:
+      switch (donor_ir_context->get_def_use_mgr()
+                  ->GetDef(instruction.type_id())
+                  ->opcode()) {
+        case SpvOpTypeImage:
+        case SpvOpTypeSampledImage:
+        case SpvOpTypeSampler:
+          // Again, we ignore instructions that relate to accessing images.
+          return false;
+        default:
+          break;
+      }
+    default:
+      break;
+  }
+
+  // Examine each id input operand to the instruction.  If it turns out that we
+  // have skipped any of these operands then we cannot donate the instruction.
+  bool result = true;
+  instruction.WhileEachInId(
+      [donor_ir_context, &original_id_to_donated_id, &result,
+       &skipped_instructions](const uint32_t* in_id) -> bool {
+        if (!original_id_to_donated_id.count(*in_id)) {
+          // We do not have a mapped result id for this id operand.  That either
+          // means that it is a forward reference (which is OK), that we skipped
+          // the instruction that generated it (which is not OK), or that it is
+          // the id of a function that we did not donate (which is not OK).  We
+          // check for the latter two cases.
+          if (skipped_instructions.count(*in_id) ||
+              donor_ir_context->get_def_use_mgr()->GetDef(*in_id)->opcode() ==
+                  SpvOpFunction) {
+            result = false;
+            return false;
+          }
+        }
+        return true;
+      });
+  return result;
+}
+
+bool FuzzerPassDonateModules::IsBasicType(
+    const opt::Instruction& instruction) const {
+  switch (instruction.opcode()) {
+    case SpvOpTypeArray:
+    case SpvOpTypeFloat:
+    case SpvOpTypeInt:
+    case SpvOpTypeMatrix:
+    case SpvOpTypeStruct:
+    case SpvOpTypeVector:
+      return true;
+    default:
+      return false;
   }
 }
 
 std::vector<uint32_t>
 FuzzerPassDonateModules::GetFunctionsInCallGraphTopologicalOrder(
     opt::IRContext* context) {
+  CallGraph call_graph(context);
+
   // This is an implementation of Kahn’s algorithm for topological sorting.
 
-  // For each function id, stores the number of distinct functions that call
-  // the function.
-  std::map<uint32_t, uint32_t> function_in_degree;
-
-  // We first build a call graph for the module, and compute the in-degree for
-  // each function in the process.
-  // TODO(afd): If there is functionality elsewhere in the SPIR-V tools
-  //  framework to construct call graphs it could be nice to re-use it here.
-  std::map<uint32_t, std::set<uint32_t>> call_graph_edges;
-
-  // Initialize function in-degree and call graph edges to 0 and empty.
-  for (auto& function : *context->module()) {
-    function_in_degree[function.result_id()] = 0;
-    call_graph_edges[function.result_id()] = std::set<uint32_t>();
-  }
-
-  // Consider every function.
-  for (auto& function : *context->module()) {
-    // Avoid considering the same callee of this function multiple times by
-    // recording known callees.
-    std::set<uint32_t> known_callees;
-    // Consider every function call instruction in every block.
-    for (auto& block : function) {
-      for (auto& instruction : block) {
-        if (instruction.opcode() != SpvOpFunctionCall) {
-          continue;
-        }
-        // Get the id of the function being called.
-        uint32_t callee = instruction.GetSingleWordInOperand(0);
-        if (known_callees.count(callee)) {
-          // We have already considered a call to this function - ignore it.
-          continue;
-        }
-        // Increase the callee's in-degree and add an edge to the call graph.
-        function_in_degree[callee]++;
-        call_graph_edges[function.result_id()].insert(callee);
-        // Mark the callee as 'known'.
-        known_callees.insert(callee);
-      }
-    }
-  }
-
   // 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) {
@@ -577,7 +780,7 @@
     auto next = queue.front();
     queue.pop();
     result.push_back(next);
-    for (auto successor : call_graph_edges.at(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;
@@ -593,5 +796,333 @@
   return result;
 }
 
+void FuzzerPassDonateModules::HandleOpArrayLength(
+    const opt::Instruction& instruction,
+    std::map<uint32_t, uint32_t>* original_id_to_donated_id,
+    std::vector<protobufs::Instruction>* donated_instructions) const {
+  assert(instruction.opcode() == SpvOpArrayLength &&
+         "Precondition: instruction must be OpArrayLength.");
+  uint32_t donated_variable_id =
+      original_id_to_donated_id->at(instruction.GetSingleWordInOperand(0));
+  auto donated_variable_instruction =
+      GetIRContext()->get_def_use_mgr()->GetDef(donated_variable_id);
+  auto pointer_to_struct_instruction =
+      GetIRContext()->get_def_use_mgr()->GetDef(
+          donated_variable_instruction->type_id());
+  assert(pointer_to_struct_instruction->opcode() == SpvOpTypePointer &&
+         "Type of variable must be pointer.");
+  auto donated_struct_type_instruction =
+      GetIRContext()->get_def_use_mgr()->GetDef(
+          pointer_to_struct_instruction->GetSingleWordInOperand(1));
+  assert(donated_struct_type_instruction->opcode() == SpvOpTypeStruct &&
+         "Pointee type of pointer used by OpArrayLength must be struct.");
+  assert(donated_struct_type_instruction->NumInOperands() ==
+             instruction.GetSingleWordInOperand(1) + 1 &&
+         "OpArrayLength must refer to the final member of the given "
+         "struct.");
+  uint32_t fixed_size_array_type_id =
+      donated_struct_type_instruction->GetSingleWordInOperand(
+          donated_struct_type_instruction->NumInOperands() - 1);
+  auto fixed_size_array_type_instruction =
+      GetIRContext()->get_def_use_mgr()->GetDef(fixed_size_array_type_id);
+  assert(fixed_size_array_type_instruction->opcode() == SpvOpTypeArray &&
+         "The donated array type must be fixed-size.");
+  auto array_size_id =
+      fixed_size_array_type_instruction->GetSingleWordInOperand(1);
+
+  if (instruction.result_id() &&
+      !original_id_to_donated_id->count(instruction.result_id())) {
+    original_id_to_donated_id->insert(
+        {instruction.result_id(), GetFuzzerContext()->GetFreshId()});
+  }
+
+  donated_instructions->push_back(MakeInstructionMessage(
+      SpvOpCopyObject, original_id_to_donated_id->at(instruction.type_id()),
+      original_id_to_donated_id->at(instruction.result_id()),
+      opt::Instruction::OperandList({{SPV_OPERAND_TYPE_ID, {array_size_id}}})));
+}
+
+void FuzzerPassDonateModules::HandleDifficultInstruction(
+    const opt::Instruction& instruction,
+    std::map<uint32_t, uint32_t>* original_id_to_donated_id,
+    std::vector<protobufs::Instruction>* donated_instructions,
+    std::set<uint32_t>* skipped_instructions) {
+  if (!instruction.result_id()) {
+    // It does not generate a result id, so it can be ignored.
+    return;
+  }
+  if (!original_id_to_donated_id->count(instruction.type_id())) {
+    // We cannot handle this instruction's result type, so we need to skip it
+    // all together.
+    skipped_instructions->insert(instruction.result_id());
+    return;
+  }
+
+  // We now attempt to replace the instruction with an OpCopyObject.
+  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3278): We could do
+  //  something more refined here - we could check which operands to the
+  //  instruction could not be donated and replace those operands with
+  //  references to other ids (such as constants), so that we still get an
+  //  instruction with the opcode and easy-to-handle operands of the donor
+  //  instruction.
+  auto remapped_type_id = original_id_to_donated_id->at(instruction.type_id());
+  if (!IsBasicType(
+          *GetIRContext()->get_def_use_mgr()->GetDef(remapped_type_id))) {
+    // The instruction has a non-basic result type, so we cannot replace it with
+    // an object copy of a constant.  We thus skip it completely.
+    // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3279): We could
+    //  instead look for an available id of the right type and generate an
+    //  OpCopyObject of that id.
+    skipped_instructions->insert(instruction.result_id());
+    return;
+  }
+
+  // We are going to add an OpCopyObject instruction.  Add a mapping for the
+  // result id of the original instruction if does not already exist (it may
+  // exist in the case that it has been forward-referenced).
+  if (!original_id_to_donated_id->count(instruction.result_id())) {
+    original_id_to_donated_id->insert(
+        {instruction.result_id(), GetFuzzerContext()->GetFreshId()});
+  }
+
+  // We find or add a zero constant to the receiving module for the type in
+  // question, and add an OpCopyObject instruction that copies this zero.
+  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3177):
+  //  Using this particular constant is arbitrary, so if we have a
+  //  mechanism for noting that an id use is arbitrary and could be
+  //  fuzzed we should use it here.
+  auto zero_constant = FindOrCreateZeroConstant(remapped_type_id);
+  donated_instructions->push_back(MakeInstructionMessage(
+      SpvOpCopyObject, remapped_type_id,
+      original_id_to_donated_id->at(instruction.result_id()),
+      opt::Instruction::OperandList({{SPV_OPERAND_TYPE_ID, {zero_constant}}})));
+}
+
+void FuzzerPassDonateModules::PrepareInstructionForDonation(
+    const opt::Instruction& instruction, opt::IRContext* donor_ir_context,
+    std::map<uint32_t, uint32_t>* original_id_to_donated_id,
+    std::vector<protobufs::Instruction>* donated_instructions) {
+  // Get the instruction's input operands into donation-ready form,
+  // remapping any id uses in the process.
+  opt::Instruction::OperandList input_operands;
+
+  // Consider each input operand in turn.
+  for (uint32_t in_operand_index = 0;
+       in_operand_index < instruction.NumInOperands(); in_operand_index++) {
+    std::vector<uint32_t> operand_data;
+    const opt::Operand& in_operand = instruction.GetInOperand(in_operand_index);
+    // Check whether this operand is an id.
+    if (spvIsIdType(in_operand.type)) {
+      // This is an id operand - it consists of a single word of data,
+      // which needs to be remapped so that it is replaced with the
+      // donated form of the id.
+      auto operand_id = in_operand.words[0];
+      if (!original_id_to_donated_id->count(operand_id)) {
+        // This is a forward reference.  We will choose a corresponding
+        // donor id for the referenced id and update the mapping to
+        // reflect it.
+
+        // Keep release compilers happy because |donor_ir_context| is only used
+        // in this assertion.
+        (void)(donor_ir_context);
+        assert((donor_ir_context->get_def_use_mgr()
+                        ->GetDef(operand_id)
+                        ->opcode() == SpvOpLabel ||
+                instruction.opcode() == SpvOpPhi) &&
+               "Unsupported forward reference.");
+        original_id_to_donated_id->insert(
+            {operand_id, GetFuzzerContext()->GetFreshId()});
+      }
+      operand_data.push_back(original_id_to_donated_id->at(operand_id));
+    } else {
+      // For non-id operands, we just add each of the data words.
+      for (auto word : in_operand.words) {
+        operand_data.push_back(word);
+      }
+    }
+    input_operands.push_back({in_operand.type, operand_data});
+  }
+
+  if (instruction.opcode() == SpvOpVariable &&
+      instruction.NumInOperands() == 1) {
+    // This is an uninitialized local variable.  Initialize it to zero.
+    input_operands.push_back(
+        {SPV_OPERAND_TYPE_ID,
+         {FindOrCreateZeroConstant(fuzzerutil::GetPointeeTypeIdFromPointerType(
+             GetIRContext(),
+             original_id_to_donated_id->at(instruction.type_id())))}});
+  }
+
+  if (instruction.result_id() &&
+      !original_id_to_donated_id->count(instruction.result_id())) {
+    original_id_to_donated_id->insert(
+        {instruction.result_id(), GetFuzzerContext()->GetFreshId()});
+  }
+
+  // Remap the result type and result id (if present) of the
+  // instruction, and turn it into a protobuf message.
+  donated_instructions->push_back(MakeInstructionMessage(
+      instruction.opcode(),
+      instruction.type_id()
+          ? original_id_to_donated_id->at(instruction.type_id())
+          : 0,
+      instruction.result_id()
+          ? original_id_to_donated_id->at(instruction.result_id())
+          : 0,
+      input_operands));
+}
+
+void FuzzerPassDonateModules::AddLivesafeFunction(
+    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) {
+  // Various types and constants must be in place for a function to be made
+  // live-safe.  Add them if not already present.
+  FindOrCreateBoolType();  // Needed for comparisons
+  FindOrCreatePointerTo32BitIntegerType(
+      false, SpvStorageClassFunction);  // Needed for adding loop limiters
+  FindOrCreate32BitIntegerConstant(
+      0, false);  // Needed for initializing loop limiters
+  FindOrCreate32BitIntegerConstant(
+      1, false);  // Needed for incrementing loop limiters
+
+  // Get a fresh id for the variable that will be used as a loop limiter.
+  const uint32_t loop_limiter_variable_id = GetFuzzerContext()->GetFreshId();
+  // Choose a random loop limit, and add the required constant to the
+  // module if not already there.
+  const uint32_t loop_limit = FindOrCreate32BitIntegerConstant(
+      GetFuzzerContext()->GetRandomLoopLimit(), false);
+
+  // Consider every loop header in the function to donate, and create a
+  // structure capturing the ids to be used for manipulating the loop
+  // limiter each time the loop is iterated.
+  std::vector<protobufs::LoopLimiterInfo> loop_limiters;
+  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);
+    }
+  }
+
+  // Consider every access chain in the function to donate, and create a
+  // structure containing the ids necessary to clamp the access chain
+  // indices to be in-bounds.
+  std::vector<protobufs::AccessChainClampingInfo> access_chain_clamping_info;
+  for (auto& block : function_to_donate) {
+    for (auto& inst : block) {
+      switch (inst.opcode()) {
+        case SpvOpAccessChain:
+        case SpvOpInBoundsAccessChain: {
+          protobufs::AccessChainClampingInfo clamping_info;
+          clamping_info.set_access_chain_id(
+              original_id_to_donated_id.at(inst.result_id()));
+
+          auto base_object = donor_ir_context->get_def_use_mgr()->GetDef(
+              inst.GetSingleWordInOperand(0));
+          assert(base_object && "The base object must exist.");
+          auto pointer_type = donor_ir_context->get_def_use_mgr()->GetDef(
+              base_object->type_id());
+          assert(pointer_type && pointer_type->opcode() == SpvOpTypePointer &&
+                 "The base object must have pointer type.");
+
+          auto should_be_composite_type =
+              donor_ir_context->get_def_use_mgr()->GetDef(
+                  pointer_type->GetSingleWordInOperand(1));
+
+          // Walk the access chain, creating fresh ids to facilitate
+          // clamping each index.  For simplicity we do this for every
+          // index, even though constant indices will not end up being
+          // clamped.
+          for (uint32_t index = 1; index < inst.NumInOperands(); index++) {
+            auto compare_and_select_ids =
+                clamping_info.add_compare_and_select_ids();
+            compare_and_select_ids->set_first(GetFuzzerContext()->GetFreshId());
+            compare_and_select_ids->set_second(
+                GetFuzzerContext()->GetFreshId());
+
+            // Get the bound for the component being indexed into.
+            uint32_t bound;
+            if (should_be_composite_type->opcode() == SpvOpTypeRuntimeArray) {
+              // The donor is indexing into a runtime array.  We do not
+              // donate runtime arrays.  Instead, we donate a corresponding
+              // fixed-size array for every runtime array.  We should thus
+              // find that donor composite type's result id maps to a fixed-
+              // size array.
+              auto fixed_size_array_type =
+                  GetIRContext()->get_def_use_mgr()->GetDef(
+                      original_id_to_donated_id.at(
+                          should_be_composite_type->result_id()));
+              assert(fixed_size_array_type->opcode() == SpvOpTypeArray &&
+                     "A runtime array type in the donor should have been "
+                     "replaced by a fixed-sized array in the recipient.");
+              // The size of this fixed-size array is a suitable bound.
+              bound = TransformationAddFunction::GetBoundForCompositeIndex(
+                  GetIRContext(), *fixed_size_array_type);
+            } else {
+              bound = TransformationAddFunction::GetBoundForCompositeIndex(
+                  donor_ir_context, *should_be_composite_type);
+            }
+            const uint32_t index_id = inst.GetSingleWordInOperand(index);
+            auto index_inst =
+                donor_ir_context->get_def_use_mgr()->GetDef(index_id);
+            auto index_type_inst = donor_ir_context->get_def_use_mgr()->GetDef(
+                index_inst->type_id());
+            assert(index_type_inst->opcode() == SpvOpTypeInt);
+            assert(index_type_inst->GetSingleWordInOperand(0) == 32);
+            opt::analysis::Integer* index_int_type =
+                donor_ir_context->get_type_mgr()
+                    ->GetType(index_type_inst->result_id())
+                    ->AsInteger();
+            if (index_inst->opcode() != SpvOpConstant) {
+              // We will have to clamp this index, so we need a constant
+              // whose value is one less than the bound, to compare
+              // against and to use as the clamped value.
+              FindOrCreate32BitIntegerConstant(bound - 1,
+                                               index_int_type->IsSigned());
+            }
+            should_be_composite_type =
+                TransformationAddFunction::FollowCompositeIndex(
+                    donor_ir_context, *should_be_composite_type, index_id);
+          }
+          access_chain_clamping_info.push_back(clamping_info);
+          break;
+        }
+        default:
+          break;
+      }
+    }
+  }
+
+  // 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;
+  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.");
+    kill_unreachable_return_value_id = FindOrCreateZeroConstant(
+        original_id_to_donated_id.at(function_return_type_inst->result_id()));
+  }
+  // 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));
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_donate_modules.h b/source/fuzz/fuzzer_pass_donate_modules.h
index d719e87..e61572c 100644
--- a/source/fuzz/fuzzer_pass_donate_modules.h
+++ b/source/fuzz/fuzzer_pass_donate_modules.h
@@ -28,7 +28,7 @@
 class FuzzerPassDonateModules : public FuzzerPass {
  public:
   FuzzerPassDonateModules(
-      opt::IRContext* ir_context, FactManager* fact_manager,
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
       protobufs::TransformationSequence* transformations,
       const std::vector<fuzzerutil::ModuleSupplier>& donor_suppliers);
@@ -38,8 +38,10 @@
   void Apply() override;
 
   // Donates the global declarations and functions of |donor_ir_context| into
-  // the fuzzer pass's IR context.
-  void DonateSingleModule(opt::IRContext* donor_ir_context);
+  // the fuzzer pass's IR context.  |make_livesafe| dictates whether the
+  // functions of the donated module will be made livesafe (see
+  // FactFunctionIsLivesafe).
+  void DonateSingleModule(opt::IRContext* donor_ir_context, bool make_livesafe);
 
  private:
   // Adapts a storage class coming from a donor module so that it will work
@@ -64,13 +66,83 @@
       opt::IRContext* donor_ir_context,
       std::map<uint32_t, uint32_t>* original_id_to_donated_id);
 
+  // TODO comment
+  void HandleTypeOrValue(
+      const opt::Instruction& type_or_value,
+      std::map<uint32_t, uint32_t>* original_id_to_donated_id);
+
   // Assumes that |donor_ir_context| does not exhibit recursion.  Considers the
   // functions in |donor_ir_context|'s call graph in a reverse-topologically-
   // sorted order (leaves-to-root), adding each function to the recipient
   // module, rewritten to use fresh ids and using |original_id_to_donated_id| to
-  // remap ids.
+  // remap ids.  The |make_livesafe| argument captures whether the functions in
+  // the module are required to be made livesafe before being added to the
+  // recipient.
   void HandleFunctions(opt::IRContext* donor_ir_context,
-                       std::map<uint32_t, uint32_t>* original_id_to_donated_id);
+                       std::map<uint32_t, uint32_t>* original_id_to_donated_id,
+                       bool make_livesafe);
+
+  // During donation we will have to ignore some instructions, e.g. because they
+  // use opcodes that we cannot support or because they reference the ids of
+  // instructions that have not been donated.  This function encapsulates the
+  // logic for deciding which whether instruction |instruction| from
+  // |donor_ir_context| can be donated.
+  bool CanDonateInstruction(
+      opt::IRContext* donor_ir_context, const opt::Instruction& instruction,
+      const std::map<uint32_t, uint32_t>& original_id_to_donated_id,
+      const std::set<uint32_t>& skipped_instructions) const;
+
+  // We treat the OpArrayLength instruction specially.  In the donor shader this
+  // instruction yields the length of a runtime array that is the final member
+  // of a struct.  During donation, we will have converted the runtime array
+  // type, and the associated struct field, into a fixed-size array.
+  //
+  // Instead of donating this instruction, we turn it into an OpCopyObject
+  // instruction that copies the size of the fixed-size array.
+  void HandleOpArrayLength(
+      const opt::Instruction& instruction,
+      std::map<uint32_t, uint32_t>* original_id_to_donated_id,
+      std::vector<protobufs::Instruction>* donated_instructions) const;
+
+  // The instruction |instruction| is required to be an instruction that cannot
+  // be easily donated, either because it uses an unsupported opcode, has an
+  // unsupported result type, or uses id operands that could not be donated.
+  //
+  // If |instruction| generates a result id, the function attempts to add a
+  // substitute for |instruction| to |donated_instructions| that has the correct
+  // result type.  If this cannot be done, the instruction's result id is added
+  // to |skipped_instructions|.  The mapping from donor ids to recipient ids is
+  // managed by |original_id_to_donated_id|.
+  void HandleDifficultInstruction(
+      const opt::Instruction& instruction,
+      std::map<uint32_t, uint32_t>* original_id_to_donated_id,
+      std::vector<protobufs::Instruction>* donated_instructions,
+      std::set<uint32_t>* skipped_instructions);
+
+  // Adds an instruction based in |instruction| to |donated_instructions| in a
+  // form ready for donation.  The original instruction comes from
+  // |donor_ir_context|, and |original_id_to_donated_id| maps ids from
+  // |donor_ir_context| to corresponding ids in the recipient module.
+  void PrepareInstructionForDonation(
+      const opt::Instruction& instruction, opt::IRContext* donor_ir_context,
+      std::map<uint32_t, uint32_t>* original_id_to_donated_id,
+      std::vector<protobufs::Instruction>* donated_instructions);
+
+  // 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(
+      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);
+
+  // Returns true if and only if |instruction| is a scalar, vector, matrix,
+  // 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-
diff --git a/source/fuzz/fuzzer_pass_merge_blocks.cpp b/source/fuzz/fuzzer_pass_merge_blocks.cpp
index ca1bfb3..49778ae 100644
--- a/source/fuzz/fuzzer_pass_merge_blocks.cpp
+++ b/source/fuzz/fuzzer_pass_merge_blocks.cpp
@@ -22,10 +22,11 @@
 namespace fuzz {
 
 FuzzerPassMergeBlocks::FuzzerPassMergeBlocks(
-    opt::IRContext* ir_context, FactManager* fact_manager,
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
     protobufs::TransformationSequence* transformations)
-    : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
 
 FuzzerPassMergeBlocks::~FuzzerPassMergeBlocks() = default;
 
@@ -44,7 +45,8 @@
       // For other blocks, we add a transformation to merge the block into its
       // predecessor if that transformation would be applicable.
       TransformationMergeBlocks transformation(block.id());
-      if (transformation.IsApplicable(GetIRContext(), *GetFactManager())) {
+      if (transformation.IsApplicable(GetIRContext(),
+                                      *GetTransformationContext())) {
         potential_transformations.push_back(transformation);
       }
     }
@@ -54,8 +56,9 @@
     uint32_t index = GetFuzzerContext()->RandomIndex(potential_transformations);
     auto transformation = potential_transformations.at(index);
     potential_transformations.erase(potential_transformations.begin() + index);
-    if (transformation.IsApplicable(GetIRContext(), *GetFactManager())) {
-      transformation.Apply(GetIRContext(), GetFactManager());
+    if (transformation.IsApplicable(GetIRContext(),
+                                    *GetTransformationContext())) {
+      transformation.Apply(GetIRContext(), GetTransformationContext());
       *GetTransformations()->add_transformation() = transformation.ToMessage();
     }
   }
diff --git a/source/fuzz/fuzzer_pass_merge_blocks.h b/source/fuzz/fuzzer_pass_merge_blocks.h
index 457e591..1a6c2c2 100644
--- a/source/fuzz/fuzzer_pass_merge_blocks.h
+++ b/source/fuzz/fuzzer_pass_merge_blocks.h
@@ -23,7 +23,8 @@
 // A fuzzer pass for merging blocks in the module.
 class FuzzerPassMergeBlocks : public FuzzerPass {
  public:
-  FuzzerPassMergeBlocks(opt::IRContext* ir_context, FactManager* fact_manager,
+  FuzzerPassMergeBlocks(opt::IRContext* ir_context,
+                        TransformationContext* transformation_context,
                         FuzzerContext* fuzzer_context,
                         protobufs::TransformationSequence* transformations);
 
diff --git a/source/fuzz/fuzzer_pass_obfuscate_constants.cpp b/source/fuzz/fuzzer_pass_obfuscate_constants.cpp
index 3df11ae..4ced507 100644
--- a/source/fuzz/fuzzer_pass_obfuscate_constants.cpp
+++ b/source/fuzz/fuzzer_pass_obfuscate_constants.cpp
@@ -25,10 +25,11 @@
 namespace fuzz {
 
 FuzzerPassObfuscateConstants::FuzzerPassObfuscateConstants(
-    opt::IRContext* ir_context, FactManager* fact_manager,
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
     protobufs::TransformationSequence* transformations)
-    : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
 
 FuzzerPassObfuscateConstants::~FuzzerPassObfuscateConstants() = default;
 
@@ -83,12 +84,13 @@
       bool_constant_use, lhs_id, rhs_id, comparison_opcode,
       GetFuzzerContext()->GetFreshId());
   // The transformation should be applicable by construction.
-  assert(transformation.IsApplicable(GetIRContext(), *GetFactManager()));
+  assert(
+      transformation.IsApplicable(GetIRContext(), *GetTransformationContext()));
 
   // Applying this transformation yields a pointer to the new instruction that
   // computes the result of the binary expression.
-  auto binary_operator_instruction =
-      transformation.ApplyWithResult(GetIRContext(), GetFactManager());
+  auto binary_operator_instruction = transformation.ApplyWithResult(
+      GetIRContext(), GetTransformationContext());
 
   // Add this transformation to the sequence of transformations that have been
   // applied.
@@ -245,7 +247,9 @@
   // with uniforms of the same value.
 
   auto available_types_with_uniforms =
-      GetFactManager()->GetTypesForWhichUniformValuesAreKnown();
+      GetTransformationContext()
+          ->GetFactManager()
+          ->GetTypesForWhichUniformValuesAreKnown();
   if (available_types_with_uniforms.empty()) {
     // Do not try to obfuscate if we do not have access to any uniform
     // elements with known values.
@@ -254,9 +258,10 @@
   auto chosen_type_id =
       available_types_with_uniforms[GetFuzzerContext()->RandomIndex(
           available_types_with_uniforms)];
-  auto available_constants =
-      GetFactManager()->GetConstantsAvailableFromUniformsForType(
-          GetIRContext(), chosen_type_id);
+  auto available_constants = GetTransformationContext()
+                                 ->GetFactManager()
+                                 ->GetConstantsAvailableFromUniformsForType(
+                                     GetIRContext(), chosen_type_id);
   if (available_constants.size() == 1) {
     // TODO(afd): for now we only obfuscate a boolean if there are at least
     //  two constants available from uniforms, so that we can do a
@@ -308,8 +313,11 @@
 
   // Check whether we know that any uniforms are guaranteed to be equal to the
   // scalar constant associated with |constant_use|.
-  auto uniform_descriptors = GetFactManager()->GetUniformDescriptorsForConstant(
-      GetIRContext(), constant_use.id_of_interest());
+  auto uniform_descriptors =
+      GetTransformationContext()
+          ->GetFactManager()
+          ->GetUniformDescriptorsForConstant(GetIRContext(),
+                                             constant_use.id_of_interest());
   if (uniform_descriptors.empty()) {
     // No relevant uniforms, so do not obfuscate.
     return;
@@ -324,8 +332,9 @@
       constant_use, uniform_descriptor, GetFuzzerContext()->GetFreshId(),
       GetFuzzerContext()->GetFreshId());
   // Transformation should be applicable by construction.
-  assert(transformation.IsApplicable(GetIRContext(), *GetFactManager()));
-  transformation.Apply(GetIRContext(), GetFactManager());
+  assert(
+      transformation.IsApplicable(GetIRContext(), *GetTransformationContext()));
+  transformation.Apply(GetIRContext(), GetTransformationContext());
   *GetTransformations()->add_transformation() = transformation.ToMessage();
 }
 
@@ -416,13 +425,28 @@
           skipped_opcode_count.clear();
         }
 
-        // Consider each operand of the instruction, and add a constant id use
-        // for the operand if relevant.
-        for (uint32_t in_operand_index = 0;
-             in_operand_index < inst.NumInOperands(); in_operand_index++) {
-          MaybeAddConstantIdUse(inst, in_operand_index,
-                                base_instruction_result_id,
-                                skipped_opcode_count, &constant_uses);
+        switch (inst.opcode()) {
+          case SpvOpPhi:
+            // The instruction must not be an OpPhi, as we cannot insert
+            // instructions before an OpPhi.
+            // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2902):
+            //  there is scope for being less conservative.
+            break;
+          case SpvOpVariable:
+            // The instruction must not be an OpVariable, the only id that an
+            // OpVariable uses is an initializer id, which has to remain
+            // constant.
+            break;
+          default:
+            // Consider each operand of the instruction, and add a constant id
+            // use for the operand if relevant.
+            for (uint32_t in_operand_index = 0;
+                 in_operand_index < inst.NumInOperands(); in_operand_index++) {
+              MaybeAddConstantIdUse(inst, in_operand_index,
+                                    base_instruction_result_id,
+                                    skipped_opcode_count, &constant_uses);
+            }
+            break;
         }
 
         if (!inst.HasResultId()) {
diff --git a/source/fuzz/fuzzer_pass_obfuscate_constants.h b/source/fuzz/fuzzer_pass_obfuscate_constants.h
index f34717b..7755d21 100644
--- a/source/fuzz/fuzzer_pass_obfuscate_constants.h
+++ b/source/fuzz/fuzzer_pass_obfuscate_constants.h
@@ -28,7 +28,7 @@
 class FuzzerPassObfuscateConstants : public FuzzerPass {
  public:
   FuzzerPassObfuscateConstants(
-      opt::IRContext* ir_context, FactManager* fact_manager,
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
       protobufs::TransformationSequence* transformations);
 
diff --git a/source/fuzz/fuzzer_pass_outline_functions.cpp b/source/fuzz/fuzzer_pass_outline_functions.cpp
index d59c195..1665d05 100644
--- a/source/fuzz/fuzzer_pass_outline_functions.cpp
+++ b/source/fuzz/fuzzer_pass_outline_functions.cpp
@@ -23,10 +23,11 @@
 namespace fuzz {
 
 FuzzerPassOutlineFunctions::FuzzerPassOutlineFunctions(
-    opt::IRContext* ir_context, FactManager* fact_manager,
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
     protobufs::TransformationSequence* transformations)
-    : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
 
 FuzzerPassOutlineFunctions::~FuzzerPassOutlineFunctions() = default;
 
@@ -88,8 +89,9 @@
         /*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));
-    if (transformation.IsApplicable(GetIRContext(), *GetFactManager())) {
-      transformation.Apply(GetIRContext(), GetFactManager());
+    if (transformation.IsApplicable(GetIRContext(),
+                                    *GetTransformationContext())) {
+      transformation.Apply(GetIRContext(), GetTransformationContext());
       *GetTransformations()->add_transformation() = transformation.ToMessage();
     }
   }
diff --git a/source/fuzz/fuzzer_pass_outline_functions.h b/source/fuzz/fuzzer_pass_outline_functions.h
index 5448e7d..6532ed9 100644
--- a/source/fuzz/fuzzer_pass_outline_functions.h
+++ b/source/fuzz/fuzzer_pass_outline_functions.h
@@ -25,7 +25,7 @@
 class FuzzerPassOutlineFunctions : public FuzzerPass {
  public:
   FuzzerPassOutlineFunctions(
-      opt::IRContext* ir_context, FactManager* fact_manager,
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
       protobufs::TransformationSequence* transformations);
 
diff --git a/source/fuzz/fuzzer_pass_permute_blocks.cpp b/source/fuzz/fuzzer_pass_permute_blocks.cpp
index af6d2a5..27a2d67 100644
--- a/source/fuzz/fuzzer_pass_permute_blocks.cpp
+++ b/source/fuzz/fuzzer_pass_permute_blocks.cpp
@@ -20,10 +20,11 @@
 namespace fuzz {
 
 FuzzerPassPermuteBlocks::FuzzerPassPermuteBlocks(
-    opt::IRContext* ir_context, FactManager* fact_manager,
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
     protobufs::TransformationSequence* transformations)
-    : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
 
 FuzzerPassPermuteBlocks::~FuzzerPassPermuteBlocks() = default;
 
@@ -66,8 +67,9 @@
       // down indefinitely.
       while (true) {
         TransformationMoveBlockDown transformation(*id);
-        if (transformation.IsApplicable(GetIRContext(), *GetFactManager())) {
-          transformation.Apply(GetIRContext(), GetFactManager());
+        if (transformation.IsApplicable(GetIRContext(),
+                                        *GetTransformationContext())) {
+          transformation.Apply(GetIRContext(), GetTransformationContext());
           *GetTransformations()->add_transformation() =
               transformation.ToMessage();
         } else {
diff --git a/source/fuzz/fuzzer_pass_permute_blocks.h b/source/fuzz/fuzzer_pass_permute_blocks.h
index 6735e95..f2d3b39 100644
--- a/source/fuzz/fuzzer_pass_permute_blocks.h
+++ b/source/fuzz/fuzzer_pass_permute_blocks.h
@@ -24,7 +24,8 @@
 // manner.
 class FuzzerPassPermuteBlocks : public FuzzerPass {
  public:
-  FuzzerPassPermuteBlocks(opt::IRContext* ir_context, FactManager* fact_manager,
+  FuzzerPassPermuteBlocks(opt::IRContext* ir_context,
+                          TransformationContext* transformation_context,
                           FuzzerContext* fuzzer_context,
                           protobufs::TransformationSequence* transformations);
 
diff --git a/source/fuzz/fuzzer_pass_permute_function_parameters.cpp b/source/fuzz/fuzzer_pass_permute_function_parameters.cpp
new file mode 100644
index 0000000..57d9cab
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_permute_function_parameters.cpp
@@ -0,0 +1,82 @@
+// 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 <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"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassPermuteFunctionParameters::FuzzerPassPermuteFunctionParameters(
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassPermuteFunctionParameters::~FuzzerPassPermuteFunctionParameters() =
+    default;
+
+void FuzzerPassPermuteFunctionParameters::Apply() {
+  for (const auto& function : *GetIRContext()->module()) {
+    uint32_t function_id = function.result_id();
+
+    // Skip the function if it is an entry point
+    if (fuzzerutil::FunctionIsEntryPoint(GetIRContext(), function_id)) {
+      continue;
+    }
+
+    if (!GetFuzzerContext()->ChoosePercentage(
+            GetFuzzerContext()->GetChanceOfPermutingParameters())) {
+      continue;
+    }
+
+    // Compute permutation for parameters
+    auto* function_type =
+        fuzzerutil::GetFunctionType(GetIRContext(), &function);
+    assert(function_type && "Function type is null");
+
+    // Don't take return type into account
+    uint32_t arg_size = function_type->NumInOperands() - 1;
+
+    // Create a vector, fill it with [0, n-1] values and shuffle it
+    std::vector<uint32_t> permutation(arg_size);
+    std::iota(permutation.begin(), permutation.end(), 0);
+    GetFuzzerContext()->Shuffle(&permutation);
+
+    // Create a new OpFunctionType instruction with permuted arguments
+    // if needed
+    auto result_type_id = function_type->GetSingleWordInOperand(0);
+    std::vector<uint32_t> argument_ids;
+
+    for (auto index : permutation) {
+      // +1 to take function's return type into account
+      argument_ids.push_back(function_type->GetSingleWordInOperand(index + 1));
+    }
+
+    // Apply our transformation
+    ApplyTransformation(TransformationPermuteFunctionParameters(
+        function_id, FindOrCreateFunctionType(result_type_id, argument_ids),
+        permutation));
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_permute_function_parameters.h b/source/fuzz/fuzzer_pass_permute_function_parameters.h
new file mode 100644
index 0000000..3f32864
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_permute_function_parameters.h
@@ -0,0 +1,45 @@
+// 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_FUNCTION_PARAMETERS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_PERMUTE_FUNCTION_PARAMETERS_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Fuzzer pass that, given a non-entry-point function taking n parameters
+// and a permutation of the set [0, n - 1]:
+//   1. Introduces a new function type that is the same as the original
+//      function's type but with the order of arguments permuted
+//      (only add this if it doesn't already exist)
+//   2. Changes the type of the function to this type
+//   3. Adjusts all calls to the function so that their arguments are permuted
+class FuzzerPassPermuteFunctionParameters : public FuzzerPass {
+ public:
+  FuzzerPassPermuteFunctionParameters(
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassPermuteFunctionParameters();
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_PERMUTE_FUNCTION_PARAMETERS_H_
diff --git a/source/fuzz/fuzzer_pass_split_blocks.cpp b/source/fuzz/fuzzer_pass_split_blocks.cpp
index 6a2ea4d..15c6790 100644
--- a/source/fuzz/fuzzer_pass_split_blocks.cpp
+++ b/source/fuzz/fuzzer_pass_split_blocks.cpp
@@ -23,10 +23,11 @@
 namespace fuzz {
 
 FuzzerPassSplitBlocks::FuzzerPassSplitBlocks(
-    opt::IRContext* ir_context, FactManager* fact_manager,
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
     protobufs::TransformationSequence* transformations)
-    : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
 
 FuzzerPassSplitBlocks::~FuzzerPassSplitBlocks() = default;
 
@@ -95,8 +96,9 @@
     // If the position we have chosen turns out to be a valid place to split
     // the block, we apply the split. Otherwise the block just doesn't get
     // split.
-    if (transformation.IsApplicable(GetIRContext(), *GetFactManager())) {
-      transformation.Apply(GetIRContext(), GetFactManager());
+    if (transformation.IsApplicable(GetIRContext(),
+                                    *GetTransformationContext())) {
+      transformation.Apply(GetIRContext(), GetTransformationContext());
       *GetTransformations()->add_transformation() = transformation.ToMessage();
     }
   }
diff --git a/source/fuzz/fuzzer_pass_split_blocks.h b/source/fuzz/fuzzer_pass_split_blocks.h
index 6e56dde..278ec6d 100644
--- a/source/fuzz/fuzzer_pass_split_blocks.h
+++ b/source/fuzz/fuzzer_pass_split_blocks.h
@@ -24,7 +24,8 @@
 // can be very useful for giving other passes a chance to apply.
 class FuzzerPassSplitBlocks : public FuzzerPass {
  public:
-  FuzzerPassSplitBlocks(opt::IRContext* ir_context, FactManager* fact_manager,
+  FuzzerPassSplitBlocks(opt::IRContext* ir_context,
+                        TransformationContext* transformation_context,
                         FuzzerContext* fuzzer_context,
                         protobufs::TransformationSequence* transformations);
 
diff --git a/source/fuzz/fuzzer_pass_swap_commutable_operands.cpp b/source/fuzz/fuzzer_pass_swap_commutable_operands.cpp
new file mode 100644
index 0000000..321e8ef
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_swap_commutable_operands.cpp
@@ -0,0 +1,51 @@
+// 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_swap_commutable_operands.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "source/fuzz/transformation_swap_commutable_operands.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassSwapCommutableOperands::FuzzerPassSwapCommutableOperands(
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassSwapCommutableOperands::~FuzzerPassSwapCommutableOperands() = default;
+
+void FuzzerPassSwapCommutableOperands::Apply() {
+  auto context = GetIRContext();
+  // Iterates over the module's instructions and checks whether it is
+  // commutative. In this case, the transformation is probabilistically applied.
+  context->module()->ForEachInst(
+      [this, context](opt::Instruction* instruction) {
+        if (spvOpcodeIsCommutativeBinaryOperator(instruction->opcode()) &&
+            GetFuzzerContext()->ChooseEven()) {
+          auto instructionDescriptor =
+              MakeInstructionDescriptor(context, instruction);
+          auto transformation =
+              TransformationSwapCommutableOperands(instructionDescriptor);
+          ApplyTransformation(transformation);
+        }
+      });
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_swap_commutable_operands.h b/source/fuzz/fuzzer_pass_swap_commutable_operands.h
new file mode 100644
index 0000000..74d937d
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_swap_commutable_operands.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_SWAP_COMMUTABLE_OPERANDS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_SWAP_COMMUTABLE_OPERANDS_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// This fuzzer pass searches for all commutative instructions in the module,
+// probabilistically choosing which of these instructions will have its input
+// operands swapped.
+class FuzzerPassSwapCommutableOperands : public FuzzerPass {
+ public:
+  FuzzerPassSwapCommutableOperands(
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassSwapCommutableOperands();
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_SWAP_COMMUTABLE_OPERANDS_H_
diff --git a/source/fuzz/fuzzer_pass_toggle_access_chain_instruction.cpp b/source/fuzz/fuzzer_pass_toggle_access_chain_instruction.cpp
new file mode 100644
index 0000000..4f26cba
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_toggle_access_chain_instruction.cpp
@@ -0,0 +1,55 @@
+// 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_toggle_access_chain_instruction.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "source/fuzz/transformation_toggle_access_chain_instruction.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassToggleAccessChainInstruction::FuzzerPassToggleAccessChainInstruction(
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassToggleAccessChainInstruction::
+    ~FuzzerPassToggleAccessChainInstruction() = default;
+
+void FuzzerPassToggleAccessChainInstruction::Apply() {
+  auto context = GetIRContext();
+  // Iterates over the module's instructions and checks whether it is
+  // OpAccessChain or OpInBoundsAccessChain. In this case, the transformation is
+  // probabilistically applied.
+  context->module()->ForEachInst([this,
+                                  context](opt::Instruction* instruction) {
+    SpvOp opcode = instruction->opcode();
+    if ((opcode == SpvOpAccessChain || opcode == SpvOpInBoundsAccessChain) &&
+        GetFuzzerContext()->ChoosePercentage(
+            GetFuzzerContext()->GetChanceOfTogglingAccessChainInstruction())) {
+      auto instructionDescriptor =
+          MakeInstructionDescriptor(context, instruction);
+      auto transformation =
+          TransformationToggleAccessChainInstruction(instructionDescriptor);
+      ApplyTransformation(transformation);
+    }
+  });
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_toggle_access_chain_instruction.h b/source/fuzz/fuzzer_pass_toggle_access_chain_instruction.h
new file mode 100644
index 0000000..d77c7cb
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_toggle_access_chain_instruction.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_TOGGLE_ACCESS_CHAIN_INSTRUCTION_H_
+#define SOURCE_FUZZ_FUZZER_PASS_TOGGLE_ACCESS_CHAIN_INSTRUCTION_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// This fuzzer pass searches for all access chain instructions in the module,
+// probabilistically choosing which of these instructions will be toggled.
+class FuzzerPassToggleAccessChainInstruction : public FuzzerPass {
+ public:
+  FuzzerPassToggleAccessChainInstruction(
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassToggleAccessChainInstruction();
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_TOGGLE_ACCESS_CHAIN_INSTRUCTION_H_
diff --git a/source/fuzz/fuzzer_util.cpp b/source/fuzz/fuzzer_util.cpp
index 82d761c..4d85984 100644
--- a/source/fuzz/fuzzer_util.cpp
+++ b/source/fuzz/fuzzer_util.cpp
@@ -103,10 +103,10 @@
     }
     phi_index++;
   }
-  // Return false if not all of the ids for extending OpPhi instructions are
-  // needed. This might turn out to be stricter than necessary; perhaps it would
-  // be OK just to not use the ids in this case.
-  return phi_index == static_cast<uint32_t>(phi_ids.size());
+  // We allow some of the ids provided for extending OpPhi instructions to be
+  // unused.  Their presence does no harm, and requiring a perfect match may
+  // make transformations less likely to cleanly apply.
+  return true;
 }
 
 uint32_t MaybeGetBoolConstantId(opt::IRContext* context, bool value) {
@@ -158,13 +158,11 @@
         break;
       }
       assert(phi_index < static_cast<uint32_t>(phi_ids.size()) &&
-             "There should be exactly one phi id per OpPhi instruction.");
+             "There should be at least one phi id per OpPhi instruction.");
       inst.AddOperand({SPV_OPERAND_TYPE_ID, {phi_ids[phi_index]}});
       inst.AddOperand({SPV_OPERAND_TYPE_ID, {bb_from->id()}});
       phi_index++;
     }
-    assert(phi_index == static_cast<uint32_t>(phi_ids.size()) &&
-           "There should be exactly one phi id per OpPhi instruction.");
   }
 }
 
@@ -228,6 +226,20 @@
     // We can only make a synonym of an instruction that has a type.
     return false;
   }
+  auto type_inst = ir_context->get_def_use_mgr()->GetDef(inst->type_id());
+  if (type_inst->opcode() == SpvOpTypePointer) {
+    switch (inst->opcode()) {
+      case SpvOpConstantNull:
+      case SpvOpUndef:
+        // We disallow making synonyms of null or undefined pointers.  This is
+        // to provide the property that if the original shader exhibited no bad
+        // pointer accesses, the transformed shader will not either.
+        return false;
+      default:
+        break;
+    }
+  }
+
   // We do not make synonyms of objects that have decorations: if the synonym is
   // not decorated analogously, using the original object vs. its synonymous
   // form may not be equivalent.
@@ -250,40 +262,47 @@
   return result;
 }
 
+uint32_t WalkOneCompositeTypeIndex(opt::IRContext* context,
+                                   uint32_t base_object_type_id,
+                                   uint32_t index) {
+  auto should_be_composite_type =
+      context->get_def_use_mgr()->GetDef(base_object_type_id);
+  assert(should_be_composite_type && "The type should exist.");
+  switch (should_be_composite_type->opcode()) {
+    case SpvOpTypeArray: {
+      auto array_length = GetArraySize(*should_be_composite_type, context);
+      if (array_length == 0 || index >= array_length) {
+        return 0;
+      }
+      return should_be_composite_type->GetSingleWordInOperand(0);
+    }
+    case SpvOpTypeMatrix:
+    case SpvOpTypeVector: {
+      auto count = should_be_composite_type->GetSingleWordInOperand(1);
+      if (index >= count) {
+        return 0;
+      }
+      return should_be_composite_type->GetSingleWordInOperand(0);
+    }
+    case SpvOpTypeStruct: {
+      if (index >= GetNumberOfStructMembers(*should_be_composite_type)) {
+        return 0;
+      }
+      return should_be_composite_type->GetSingleWordInOperand(index);
+    }
+    default:
+      return 0;
+  }
+}
+
 uint32_t WalkCompositeTypeIndices(
     opt::IRContext* context, uint32_t base_object_type_id,
     const google::protobuf::RepeatedField<google::protobuf::uint32>& indices) {
   uint32_t sub_object_type_id = base_object_type_id;
   for (auto index : indices) {
-    auto should_be_composite_type =
-        context->get_def_use_mgr()->GetDef(sub_object_type_id);
-    assert(should_be_composite_type && "The type should exist.");
-    if (SpvOpTypeArray == should_be_composite_type->opcode()) {
-      auto array_length = GetArraySize(*should_be_composite_type, context);
-      if (array_length == 0 || index >= array_length) {
-        return 0;
-      }
-      sub_object_type_id = should_be_composite_type->GetSingleWordInOperand(0);
-    } else if (SpvOpTypeMatrix == should_be_composite_type->opcode()) {
-      auto matrix_column_count =
-          should_be_composite_type->GetSingleWordInOperand(1);
-      if (index >= matrix_column_count) {
-        return 0;
-      }
-      sub_object_type_id = should_be_composite_type->GetSingleWordInOperand(0);
-    } else if (SpvOpTypeStruct == should_be_composite_type->opcode()) {
-      if (index >= GetNumberOfStructMembers(*should_be_composite_type)) {
-        return 0;
-      }
-      sub_object_type_id =
-          should_be_composite_type->GetSingleWordInOperand(index);
-    } else if (SpvOpTypeVector == should_be_composite_type->opcode()) {
-      auto vector_length = should_be_composite_type->GetSingleWordInOperand(1);
-      if (index >= vector_length) {
-        return 0;
-      }
-      sub_object_type_id = should_be_composite_type->GetSingleWordInOperand(0);
-    } else {
+    sub_object_type_id =
+        WalkOneCompositeTypeIndex(context, sub_object_type_id, index);
+    if (!sub_object_type_id) {
       return 0;
     }
   }
@@ -310,11 +329,11 @@
   return array_length_constant->GetU32();
 }
 
-bool IsValid(opt::IRContext* context) {
+bool IsValid(opt::IRContext* context, spv_validator_options validator_options) {
   std::vector<uint32_t> binary;
   context->module()->ToBinary(&binary, false);
   SpirvTools tools(context->grammar().target_env());
-  return tools.Validate(binary);
+  return tools.Validate(binary.data(), binary.size(), validator_options);
 }
 
 std::unique_ptr<opt::IRContext> CloneIRContext(opt::IRContext* context) {
@@ -347,6 +366,184 @@
   return result;
 }
 
+uint32_t FindFunctionType(opt::IRContext* ir_context,
+                          const std::vector<uint32_t>& type_ids) {
+  // Look through the existing types for a match.
+  for (auto& type_or_value : ir_context->types_values()) {
+    if (type_or_value.opcode() != SpvOpTypeFunction) {
+      // We are only interested in function types.
+      continue;
+    }
+    if (type_or_value.NumInOperands() != type_ids.size()) {
+      // Not a match: different numbers of arguments.
+      continue;
+    }
+    // Check whether the return type and argument types match.
+    bool input_operands_match = true;
+    for (uint32_t i = 0; i < type_or_value.NumInOperands(); i++) {
+      if (type_ids[i] != type_or_value.GetSingleWordInOperand(i)) {
+        input_operands_match = false;
+        break;
+      }
+    }
+    if (input_operands_match) {
+      // Everything matches.
+      return type_or_value.result_id();
+    }
+  }
+  // No match was found.
+  return 0;
+}
+
+opt::Instruction* GetFunctionType(opt::IRContext* context,
+                                  const opt::Function* function) {
+  uint32_t type_id = function->DefInst().GetSingleWordInOperand(1);
+  return context->get_def_use_mgr()->GetDef(type_id);
+}
+
+opt::Function* FindFunction(opt::IRContext* ir_context, uint32_t function_id) {
+  for (auto& function : *ir_context->module()) {
+    if (function.result_id() == function_id) {
+      return &function;
+    }
+  }
+  return nullptr;
+}
+
+bool FunctionIsEntryPoint(opt::IRContext* context, uint32_t function_id) {
+  for (auto& entry_point : context->module()->entry_points()) {
+    if (entry_point.GetSingleWordInOperand(1) == function_id) {
+      return true;
+    }
+  }
+  return false;
+}
+
+bool IdIsAvailableAtUse(opt::IRContext* context,
+                        opt::Instruction* use_instruction,
+                        uint32_t use_input_operand_index, uint32_t id) {
+  auto defining_instruction = context->get_def_use_mgr()->GetDef(id);
+  auto enclosing_function =
+      context->get_instr_block(use_instruction)->GetParent();
+  // If the id a function parameter, it needs to be associated with the
+  // function containing the use.
+  if (defining_instruction->opcode() == SpvOpFunctionParameter) {
+    return InstructionIsFunctionParameter(defining_instruction,
+                                          enclosing_function);
+  }
+  if (!context->get_instr_block(id)) {
+    // The id must be at global scope.
+    return true;
+  }
+  if (defining_instruction == use_instruction) {
+    // It is not OK for a definition to use itself.
+    return false;
+  }
+  auto dominator_analysis = context->GetDominatorAnalysis(enclosing_function);
+  if (use_instruction->opcode() == SpvOpPhi) {
+    // In the case where the use is an operand to OpPhi, it is actually the
+    // *parent* block associated with the operand that must be dominated by
+    // the synonym.
+    auto parent_block =
+        use_instruction->GetSingleWordInOperand(use_input_operand_index + 1);
+    return dominator_analysis->Dominates(
+        context->get_instr_block(defining_instruction)->id(), parent_block);
+  }
+  return dominator_analysis->Dominates(defining_instruction, use_instruction);
+}
+
+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();
+  // 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 (!context->get_instr_block(id)) {
+    // The id is at global scope.
+    return true;
+  }
+  if (defining_instruction == instruction) {
+    // The instruction is not available right before its own definition.
+    return false;
+  }
+  return context->GetDominatorAnalysis(enclosing_function)
+      ->Dominates(defining_instruction, instruction);
+}
+
+bool InstructionIsFunctionParameter(opt::Instruction* instruction,
+                                    opt::Function* function) {
+  if (instruction->opcode() != SpvOpFunctionParameter) {
+    return false;
+  }
+  bool found_parameter = false;
+  function->ForEachParam(
+      [instruction, &found_parameter](opt::Instruction* param) {
+        if (param == instruction) {
+          found_parameter = true;
+        }
+      });
+  return found_parameter;
+}
+
+uint32_t GetTypeId(opt::IRContext* context, uint32_t result_id) {
+  return context->get_def_use_mgr()->GetDef(result_id)->type_id();
+}
+
+uint32_t GetPointeeTypeIdFromPointerType(opt::Instruction* pointer_type_inst) {
+  assert(pointer_type_inst && pointer_type_inst->opcode() == SpvOpTypePointer &&
+         "Precondition: |pointer_type_inst| must be OpTypePointer.");
+  return pointer_type_inst->GetSingleWordInOperand(1);
+}
+
+uint32_t GetPointeeTypeIdFromPointerType(opt::IRContext* context,
+                                         uint32_t pointer_type_id) {
+  return GetPointeeTypeIdFromPointerType(
+      context->get_def_use_mgr()->GetDef(pointer_type_id));
+}
+
+SpvStorageClass GetStorageClassFromPointerType(
+    opt::Instruction* pointer_type_inst) {
+  assert(pointer_type_inst && pointer_type_inst->opcode() == SpvOpTypePointer &&
+         "Precondition: |pointer_type_inst| must be OpTypePointer.");
+  return static_cast<SpvStorageClass>(
+      pointer_type_inst->GetSingleWordInOperand(0));
+}
+
+SpvStorageClass GetStorageClassFromPointerType(opt::IRContext* context,
+                                               uint32_t pointer_type_id) {
+  return GetStorageClassFromPointerType(
+      context->get_def_use_mgr()->GetDef(pointer_type_id));
+}
+
+uint32_t MaybeGetPointerType(opt::IRContext* context, uint32_t pointee_type_id,
+                             SpvStorageClass storage_class) {
+  for (auto& inst : context->types_values()) {
+    switch (inst.opcode()) {
+      case SpvOpTypePointer:
+        if (inst.GetSingleWordInOperand(0) == storage_class &&
+            inst.GetSingleWordInOperand(1) == pointee_type_id) {
+          return inst.result_id();
+        }
+        break;
+      default:
+        break;
+    }
+  }
+  return 0;
+}
+
+bool IsNullConstantSupported(const opt::analysis::Type& type) {
+  return type.AsBool() || type.AsInteger() || type.AsFloat() ||
+         type.AsMatrix() || type.AsVector() || type.AsArray() ||
+         type.AsStruct() || type.AsPointer() || type.AsEvent() ||
+         type.AsDeviceEvent() || type.AsReserveId() || type.AsQueue();
+}
+
 }  // namespace fuzzerutil
 
 }  // namespace fuzz
diff --git a/source/fuzz/fuzzer_util.h b/source/fuzz/fuzzer_util.h
index cbd81cd..886029a 100644
--- a/source/fuzz/fuzzer_util.h
+++ b/source/fuzz/fuzzer_util.h
@@ -98,6 +98,21 @@
 std::vector<uint32_t> RepeatedFieldToVector(
     const google::protobuf::RepeatedField<uint32_t>& repeated_field);
 
+// Given a type id, |base_object_type_id|, returns 0 if the type is not a
+// composite type or if |index| is too large to be used as an index into the
+// composite.  Otherwise returns the type id of the type associated with the
+// composite's index.
+//
+// Example: if |base_object_type_id| is 10, and we have:
+//
+// %10 = OpTypeStruct %3 %4 %5
+//
+// then 3 will be returned if |index| is 0, 5 if |index| is 2, and 0 if index
+// is 3 or larger.
+uint32_t WalkOneCompositeTypeIndex(opt::IRContext* context,
+                                   uint32_t base_object_type_id,
+                                   uint32_t index);
+
 // Given a type id, |base_object_type_id|, checks that the given sequence of
 // |indices| is suitable for indexing into this type.  Returns the id of the
 // type of the final sub-object reached via the indices if they are valid, and
@@ -117,8 +132,9 @@
 uint32_t GetArraySize(const opt::Instruction& array_type_instruction,
                       opt::IRContext* context);
 
-// Returns true if and only if |context| is valid, according to the validator.
-bool IsValid(opt::IRContext* 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);
 
 // Returns a clone of |context|, by writing |context| to a binary and then
 // parsing it again.
@@ -131,6 +147,73 @@
 // 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 result id of an instruction of the form:
+//  %id = OpTypeFunction |type_ids|
+// or 0 if no such instruction exists.
+uint32_t FindFunctionType(opt::IRContext* ir_context,
+                          const std::vector<uint32_t>& type_ids);
+
+// Returns a type instruction (OpTypeFunction) for |function|.
+// Returns |nullptr| if type is not found.
+opt::Instruction* GetFunctionType(opt::IRContext* context,
+                                  const opt::Function* function);
+
+// Returns the function with result id |function_id|, or |nullptr| if no such
+// function exists.
+opt::Function* FindFunction(opt::IRContext* ir_context, uint32_t function_id);
+
+// 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|.
+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|.
+bool IdIsAvailableBeforeInstruction(opt::IRContext* context,
+                                    opt::Instruction* instruction, uint32_t id);
+
+// Returns true if and only if |instruction| is an OpFunctionParameter
+// associated with |function|.
+bool InstructionIsFunctionParameter(opt::Instruction* instruction,
+                                    opt::Function* function);
+
+// Returns the type id of the instruction defined by |result_id|, or 0 if there
+// is no such result id.
+uint32_t GetTypeId(opt::IRContext* context, uint32_t result_id);
+
+// Given |pointer_type_inst|, which must be an OpTypePointer instruction,
+// returns the id of the associated pointee type.
+uint32_t GetPointeeTypeIdFromPointerType(opt::Instruction* pointer_type_inst);
+
+// Given |pointer_type_id|, which must be the id of a pointer type, returns the
+// id of the associated pointee type.
+uint32_t GetPointeeTypeIdFromPointerType(opt::IRContext* context,
+                                         uint32_t pointer_type_id);
+
+// Given |pointer_type_inst|, which must be an OpTypePointer instruction,
+// returns the associated storage class.
+SpvStorageClass GetStorageClassFromPointerType(
+    opt::Instruction* pointer_type_inst);
+
+// Given |pointer_type_id|, which must be the id of a pointer type, returns the
+// associated storage class.
+SpvStorageClass GetStorageClassFromPointerType(opt::IRContext* context,
+                                               uint32_t pointer_type_id);
+
+// Returns the id of a pointer with pointee type |pointee_type_id| and storage
+// class |storage_class|, if it exists, and 0 otherwise.
+uint32_t MaybeGetPointerType(opt::IRContext* context, uint32_t pointee_type_id,
+                             SpvStorageClass storage_class);
+
+// Returns true if and only if |type| is one of the types for which it is legal
+// to have an OpConstantNull value.
+bool IsNullConstantSupported(const opt::analysis::Type& type);
+
 }  // namespace fuzzerutil
 
 }  // namespace fuzz
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto
index 08f3986..2af3a92 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -167,11 +167,23 @@
     FactConstantUniform constant_uniform_fact = 1;
     FactDataSynonym data_synonym_fact = 2;
     FactBlockIsDead block_is_dead_fact = 3;
+    FactFunctionIsLivesafe function_is_livesafe_fact = 4;
+    FactPointeeValueIsIrrelevant pointee_value_is_irrelevant_fact = 5;
+    FactIdEquation id_equation_fact = 6;
   }
 }
 
 // Keep fact message types in alphabetical order:
 
+message FactBlockIsDead {
+
+  // Records the fact that a block is guaranteed to be dynamically unreachable.
+  // This is useful because it informs the fuzzer that rather arbitrary changes
+  // can be made to this block.
+
+  uint32 block_id = 1;
+}
+
 message FactConstantUniform {
 
   // Records the fact that a uniform buffer element is guaranteed to be equal
@@ -201,13 +213,111 @@
 
 }
 
-message FactBlockIsDead {
+message FactFunctionIsLivesafe {
 
-  // Records the fact that a block is guaranteed to be dynamically unreachable.
-  // This is useful because it informs the fuzzer that rather arbitrary changes
-  // can be made to this block.
+  // Records the fact that a function is guaranteed to be "livesafe", meaning
+  // that it will not make out-of-bounds accesses, does not contain reachable
+  // OpKill or OpUnreachable instructions, does not contain loops that will
+  // execute for large numbers of iterations, and only invokes other livesafe
+  // functions.
 
-  uint32 block_id = 1;
+  uint32 function_id = 1;
+}
+
+message FactIdEquation {
+
+  // Records the fact that the equation:
+  //
+  // lhs_id = opcode rhs_id[0] rhs_id[1] ... rhs_id[N-1]
+  //
+  // holds; e.g. that the equation:
+  //
+  // %12 = OpIAdd %13 %14
+  //
+  // holds in the case where lhs_id is 12, rhs_id is [13, 14], and the opcode is
+  // OpIAdd.
+
+  // The left-hand-side of the equation.
+  uint32 lhs_id = 1;
+
+  // A SPIR-V opcode, from a restricted set of instructions for which equation
+  // facts make sense.
+  uint32 opcode = 2;
+
+  // The operands to the right-hand-side of the equation.
+  repeated uint32 rhs_id = 3;
+
+}
+
+message FactPointeeValueIsIrrelevant {
+
+  // Records the fact that value of the data pointed to by a pointer id does
+  // not influence the observable behaviour of the module.  This means that
+  // arbitrary stores can be made through the pointer, and that nothing can be
+  // guaranteed about the values that are loaded via the pointer.
+
+  // A result id of pointer type
+  uint32 pointer_id = 1;
+}
+
+message AccessChainClampingInfo {
+
+  // When making a function livesafe it is necessary to clamp the indices that
+  // occur as operands to access chain instructions so that they are guaranteed
+  // to be in bounds.  This message type allows an access chain instruction to
+  // have an associated sequence of ids that are reserved for comparing an
+  // access chain index with a bound (e.g. an array size), and selecting
+  // between the access chain index (if it is within bounds) and the bound (if
+  // it is not).
+  //
+  // This allows turning an instruction of the form:
+  //
+  // %result = OpAccessChain %type %object ... %index ...
+  //
+  // into:
+  //
+  // %t1 = OpULessThanEqual %bool %index %bound_minus_one
+  // %t2 = OpSelect %int_type %t1 %index %bound_minus_one
+  // %result = OpAccessChain %type %object ... %t2 ...
+
+  // The result id of an OpAccessChain or OpInBoundsAccessChain instruction.
+  uint32 access_chain_id = 1;
+
+  // A series of pairs of fresh ids, one per access chain index, for the results
+  // of a compare instruction and a select instruction, serving the roles of %t1
+  // and %t2 in the above example.
+  repeated UInt32Pair compare_and_select_ids = 2;
+
+}
+
+message LoopLimiterInfo {
+
+  // Structure capturing the information required to manipulate a loop limiter
+  // at a loop header.
+
+  // The header for the loop.
+  uint32 loop_header_id = 1;
+
+  // A fresh id into which the loop limiter's current value can be loaded.
+  uint32 load_id = 2;
+
+  // A fresh id that can be used to increment the loaded value by 1.
+  uint32 increment_id = 3;
+
+  // A fresh id that can be used to compare the loaded value with the loop
+  // limit.
+  uint32 compare_id = 4;
+
+  // A fresh id that can be used to compute the conjunction or disjunction of
+  // an original loop exit condition with |compare_id|, if the loop's back edge
+  // block can conditionally exit the loop.
+  uint32 logical_op_id = 5;
+
+  // A sequence of ids suitable for extending OpPhi instructions of the loop
+  // merge block if it did not previously have an incoming edge from the loop
+  // back edge block.
+  repeated uint32 phi_id = 6;
+
 }
 
 message TransformationSequence {
@@ -253,12 +363,41 @@
     TransformationAddGlobalUndef add_global_undef = 32;
     TransformationAddFunction add_function = 33;
     TransformationAddDeadBlock add_dead_block = 34;
+    TransformationAddLocalVariable add_local_variable = 35;
+    TransformationLoad load = 36;
+    TransformationStore store = 37;
+    TransformationFunctionCall function_call = 38;
+    TransformationAccessChain access_chain = 39;
+    TransformationEquationInstruction equation_instruction = 40;
+    TransformationSwapCommutableOperands swap_commutable_operands = 41;
+    TransformationPermuteFunctionParameters permute_function_parameters = 42;
+    TransformationToggleAccessChainInstruction toggle_access_chain_instruction = 43;
+    TransformationAddConstantNull add_constant_null = 44;
     // Add additional option using the next available number.
   }
 }
 
 // Keep transformation message types in alphabetical order:
 
+message TransformationAccessChain {
+
+  // Adds an access chain instruction based on a given pointer and indices.
+
+  // Result id for the access chain
+  uint32 fresh_id = 1;
+
+  // The pointer from which the access chain starts
+  uint32 pointer_id = 2;
+
+  // Zero or more access chain indices
+  repeated uint32 index_id = 3;
+
+  // A descriptor for an instruction in a block before which the new
+  // OpAccessChain instruction should be inserted
+  InstructionDescriptor instruction_to_insert_before = 4;
+
+}
+
 message TransformationAddConstantBoolean {
 
   // Supports adding the constants true and false to a module, which may be
@@ -284,6 +423,18 @@
 
 }
 
+message TransformationAddConstantNull {
+
+  // Adds a null constant.
+
+  // Id for the constant
+  uint32 fresh_id = 1;
+
+  // Type of the constant
+  uint32 type_id = 2;
+
+}
+
 message TransformationAddConstantScalar {
 
   // Adds a constant of the given scalar type.
@@ -366,6 +517,33 @@
   // The series of instructions that comprise the function.
   repeated Instruction instruction = 1;
 
+  // True if and only if the given function should be made livesafe (see
+  // FactFunctionIsLivesafe for definition).
+  bool is_livesafe = 2;
+
+  // Fresh id for a new variable that will serve as a "loop limiter" for the
+  // function; only relevant if |is_livesafe| holds.
+  uint32 loop_limiter_variable_id = 3;
+
+  // Id of an existing unsigned integer constant providing the maximum value
+  // that the loop limiter can reach before the loop is broken from; only
+  // relevant if |is_livesafe| holds.
+  uint32 loop_limit_constant_id = 4;
+
+  // Fresh ids for each loop in the function that allow the loop limiter to be
+  // manipulated; only relevant if |is_livesafe| holds.
+  repeated LoopLimiterInfo loop_limiter_info = 5;
+
+  // 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.
+  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.
+  repeated AccessChainClampingInfo access_chain_clamping_info = 7;
+
 }
 
 message TransformationAddGlobalUndef {
@@ -382,8 +560,9 @@
 
 message TransformationAddGlobalVariable {
 
-  // Adds a global variable of the given type to the module, with Private
-  // storage class and optionally with an initializer.
+  // Adds a global variable of the given type to the module, with Private or
+  // Workgroup storage class, and optionally (for the Private case) with an
+  // initializer.
 
   // Fresh id for the global variable
   uint32 fresh_id = 1;
@@ -391,8 +570,40 @@
   // The type of the global variable
   uint32 type_id = 2;
 
-  // Optional initializer; 0 if there is no initializer
-  uint32 initializer_id = 3;
+  uint32 storage_class = 3;
+
+  // Initial value of the variable
+  uint32 initializer_id = 4;
+
+  // True if and only if the behaviour of the module should not depend on the
+  // value of the variable, in which case stores to the variable can be
+  // performed in an arbitrary fashion.
+  bool value_is_irrelevant = 5;
+
+}
+
+message TransformationAddLocalVariable {
+
+  // Adds a local variable of the given type (which must be a pointer with
+  // Function storage class) to the given function, initialized to the given
+  // id.
+
+  // Fresh id for the local variable
+  uint32 fresh_id = 1;
+
+  // The type of the local variable
+  uint32 type_id = 2;
+
+  // The id of the function to which the local variable should be added
+  uint32 function_id = 3;
+
+  // Initial value of the variable
+  uint32 initializer_id = 4;
+
+  // True if and only if the behaviour of the module should not depend on the
+  // value of the variable, in which case stores to the variable can be
+  // performed in an arbitrary fashion.
+  bool value_is_irrelevant = 5;
 
 }
 
@@ -587,6 +798,67 @@
 
 }
 
+message TransformationEquationInstruction {
+
+  // A transformation that adds an instruction to the module that defines an
+  // equation between its result id and input operand ids, such that the
+  // equation is guaranteed to hold at any program point where all ids involved
+  // are available (i.e. at any program point dominated by the instruction).
+
+  // The result id of the new instruction
+  uint32 fresh_id = 1;
+
+  // The instruction's opcode
+  uint32 opcode = 2;
+
+  // The input operands to the instruction
+  repeated uint32 in_operand_id = 3;
+
+  // A descriptor for an instruction in a block before which the new
+  // instruction should be inserted
+  InstructionDescriptor instruction_to_insert_before = 4;
+
+}
+
+message TransformationFunctionCall {
+
+  // A transformation that introduces an OpFunctionCall instruction.  The call
+  // must not make the module's call graph cyclic.  Beyond that, if the call
+  // is in a dead block it can be to any function with arbitrary suitably-typed
+  // arguments; otherwise it must be to a livesafe function, with injected
+  // variables as pointer arguments and arbitrary non-pointer arguments.
+
+  // A fresh id for the result of the call
+  uint32 fresh_id = 1;
+
+  // Id of the function to be called
+  uint32 callee_id = 2;
+
+  // Ids for arguments to the function
+  repeated uint32 argument_id = 3;
+
+  // A descriptor for an instruction in a block before which the new
+  // OpFunctionCall instruction should be inserted
+  InstructionDescriptor instruction_to_insert_before = 4;
+
+}
+
+message TransformationLoad {
+
+  // Transformation that adds an OpLoad instruction from a pointer into an id.
+
+  // The result of the load instruction
+  uint32 fresh_id = 1;
+
+  // The pointer to be loaded from
+  uint32 pointer_id = 2;
+
+  // A descriptor for an instruction in a block before which the new OpLoad
+  // instruction should be inserted
+  InstructionDescriptor instruction_to_insert_before = 3;
+
+}
+
 message TransformationMergeBlocks {
 
   // A transformation that merges a block with its predecessor.
@@ -653,6 +925,36 @@
 
 }
 
+message TransformationPermuteFunctionParameters {
+
+  // A transformation that, given a non-entry-point function taking n
+  // parameters and a permutation of the set [0, n-1]:
+  //   - Introduces a new function type that is the same as the original
+  //     function's type but with the order of arguments permuted
+  //     (only if it doesn't already exist)
+  //   - Changes the type of the function to this type
+  //   - Adjusts all calls to the function so that their arguments are permuted
+
+  // Function, whose parameters will be permuted
+  uint32 function_id = 1;
+
+  // |new_type_id| is a result id of a valid OpTypeFunction instruction.
+  // New type is valid if:
+  //   - it has the same number of operands as the old one
+  //   - function's result type is the same as the old one
+  //   - function's arguments are permuted according to |permutation| vector
+  uint32 new_type_id = 2;
+
+  // An array of size |n|, where |n| is a number of arguments to a function
+  // with |function_id|. For each i: 0 <= permutation[i] < n.
+  //
+  // i-th element of this array contains a position for an i-th
+  // function's argument (i.e. i-th argument will be permutation[i]-th
+  // after running this transformation)
+  repeated uint32 permutation = 3;
+
+}
+
 message TransformationReplaceBooleanConstantWithConstantBinary {
 
   // A transformation to capture replacing a use of a boolean constant with
@@ -799,6 +1101,40 @@
 
 }
 
+message TransformationStore {
+
+  // Transformation that adds an OpStore instruction of an id to a pointer.
+
+  // The pointer to be stored to
+  uint32 pointer_id = 1;
+
+  // The value to be stored
+  uint32 value_id = 2;
+
+  // A descriptor for an instruction in a block before which the new OpStore
+  // instruction should be inserted
+  InstructionDescriptor instruction_to_insert_before = 3;
+
+}
+
+message TransformationSwapCommutableOperands {
+
+  // A transformation that swaps the operands of a commutative instruction.
+
+  // A descriptor for a commutative instruction
+  InstructionDescriptor instruction_descriptor = 1;
+
+}
+
+message TransformationToggleAccessChainInstruction {
+
+  // A transformation that toggles an access chain instruction.
+
+  // A descriptor for an access chain instruction
+  InstructionDescriptor instruction_descriptor = 1;
+
+}
+
 message TransformationVectorShuffle {
 
   // A transformation that adds a vector shuffle instruction.
diff --git a/source/fuzz/replayer.cpp b/source/fuzz/replayer.cpp
index 398ce59..6312cba 100644
--- a/source/fuzz/replayer.cpp
+++ b/source/fuzz/replayer.cpp
@@ -26,6 +26,7 @@
 #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"
@@ -37,18 +38,22 @@
 namespace fuzz {
 
 struct Replayer::Impl {
-  explicit Impl(spv_target_env env, bool validate)
-      : target_env(env), validate_during_replay(validate) {}
+  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 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)
-    : impl_(MakeUnique<Impl>(env, validate_during_replay)) {}
+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;
 
@@ -74,7 +79,8 @@
   }
 
   // Initial binary should be valid.
-  if (!tools.Validate(&binary_in[0], binary_in.size())) {
+  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;
@@ -94,16 +100,19 @@
 
   FactManager fact_manager;
   fact_manager.AddFacts(impl_->consumer, initial_facts, ir_context.get());
+  TransformationContext transformation_context(&fact_manager,
+                                               impl_->validator_options);
 
   // Consider the transformation proto messages in turn.
   for (auto& message : transformation_sequence_in.transformation()) {
     auto transformation = Transformation::FromMessage(message);
 
     // Check whether the transformation can be applied.
-    if (transformation->IsApplicable(ir_context.get(), fact_manager)) {
+    if (transformation->IsApplicable(ir_context.get(),
+                                     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(), &fact_manager);
+      transformation->Apply(ir_context.get(), &transformation_context);
       *transformation_sequence_out->add_transformation() = message;
 
       if (impl_->validate_during_replay) {
@@ -111,8 +120,8 @@
         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())) {
+        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.");
diff --git a/source/fuzz/replayer.h b/source/fuzz/replayer.h
index 1d58bae..e77d840 100644
--- a/source/fuzz/replayer.h
+++ b/source/fuzz/replayer.h
@@ -37,7 +37,8 @@
   };
 
   // Constructs a replayer from the given target environment.
-  explicit Replayer(spv_target_env env, bool validate_during_replay);
+  Replayer(spv_target_env env, bool validate_during_replay,
+           spv_validator_options validator_options);
 
   // Disables copy/move constructor/assignment operations.
   Replayer(const Replayer&) = delete;
diff --git a/source/fuzz/shrinker.cpp b/source/fuzz/shrinker.cpp
index 1bb92f1..b8e4145 100644
--- a/source/fuzz/shrinker.cpp
+++ b/source/fuzz/shrinker.cpp
@@ -60,20 +60,27 @@
 }  // namespace
 
 struct Shrinker::Impl {
-  explicit Impl(spv_target_env env, uint32_t limit, bool validate)
-      : target_env(env), step_limit(limit), validate_during_replay(validate) {}
+  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.
+  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)
-    : impl_(MakeUnique<Impl>(env, step_limit, validate_during_replay)) {}
+                   bool validate_during_replay,
+                   spv_validator_options validator_options)
+    : impl_(MakeUnique<Impl>(env, step_limit, validate_during_replay,
+                             validator_options)) {}
 
 Shrinker::~Shrinker() = default;
 
@@ -113,7 +120,8 @@
   // succeeds, (b) get the binary that results from running these
   // transformations, and (c) get the subsequence of the initial transformations
   // that actually apply (in principle this could be a strict subsequence).
-  if (Replayer(impl_->target_env, impl_->validate_during_replay)
+  if (Replayer(impl_->target_env, impl_->validate_during_replay,
+               impl_->validator_options)
           .Run(binary_in, initial_facts, transformation_sequence_in,
                &current_best_binary, &current_best_transformations) !=
       Replayer::ReplayerResultStatus::kComplete) {
@@ -184,7 +192,8 @@
       // transformations inapplicable.
       std::vector<uint32_t> next_binary;
       protobufs::TransformationSequence next_transformation_sequence;
-      if (Replayer(impl_->target_env, false)
+      if (Replayer(impl_->target_env, impl_->validate_during_replay,
+                   impl_->validator_options)
               .Run(binary_in, initial_facts, transformations_with_chunk_removed,
                    &next_binary, &next_transformation_sequence) !=
           Replayer::ReplayerResultStatus::kComplete) {
diff --git a/source/fuzz/shrinker.h b/source/fuzz/shrinker.h
index 0163a53..17b15bf 100644
--- a/source/fuzz/shrinker.h
+++ b/source/fuzz/shrinker.h
@@ -50,8 +50,8 @@
       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 env, uint32_t step_limit, bool validate_during_replay,
+           spv_validator_options validator_options);
 
   // Disables copy/move constructor/assignment operations.
   Shrinker(const Shrinker&) = delete;
diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp
index c7aae58..40d2010 100644
--- a/source/fuzz/transformation.cpp
+++ b/source/fuzz/transformation.cpp
@@ -16,8 +16,11 @@
 
 #include <cassert>
 
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/transformation_access_chain.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_dead_block.h"
 #include "source/fuzz/transformation_add_dead_break.h"
@@ -25,6 +28,7 @@
 #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_local_variable.h"
 #include "source/fuzz/transformation_add_no_contraction_decoration.h"
 #include "source/fuzz/transformation_add_type_array.h"
 #include "source/fuzz/transformation_add_type_boolean.h"
@@ -38,9 +42,13 @@
 #include "source/fuzz/transformation_composite_construct.h"
 #include "source/fuzz/transformation_composite_extract.h"
 #include "source/fuzz/transformation_copy_object.h"
+#include "source/fuzz/transformation_equation_instruction.h"
+#include "source/fuzz/transformation_function_call.h"
+#include "source/fuzz/transformation_load.h"
 #include "source/fuzz/transformation_merge_blocks.h"
 #include "source/fuzz/transformation_move_block_down.h"
 #include "source/fuzz/transformation_outline_function.h"
+#include "source/fuzz/transformation_permute_function_parameters.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_replace_id_with_synonym.h"
@@ -49,6 +57,9 @@
 #include "source/fuzz/transformation_set_memory_operands_mask.h"
 #include "source/fuzz/transformation_set_selection_control.h"
 #include "source/fuzz/transformation_split_block.h"
+#include "source/fuzz/transformation_store.h"
+#include "source/fuzz/transformation_swap_commutable_operands.h"
+#include "source/fuzz/transformation_toggle_access_chain_instruction.h"
 #include "source/fuzz/transformation_vector_shuffle.h"
 #include "source/util/make_unique.h"
 
@@ -60,12 +71,17 @@
 std::unique_ptr<Transformation> Transformation::FromMessage(
     const protobufs::Transformation& message) {
   switch (message.transformation_case()) {
+    case protobufs::Transformation::TransformationCase::kAccessChain:
+      return MakeUnique<TransformationAccessChain>(message.access_chain());
     case protobufs::Transformation::TransformationCase::kAddConstantBoolean:
       return MakeUnique<TransformationAddConstantBoolean>(
           message.add_constant_boolean());
     case protobufs::Transformation::TransformationCase::kAddConstantComposite:
       return MakeUnique<TransformationAddConstantComposite>(
           message.add_constant_composite());
+    case protobufs::Transformation::TransformationCase::kAddConstantNull:
+      return MakeUnique<TransformationAddConstantNull>(
+          message.add_constant_null());
     case protobufs::Transformation::TransformationCase::kAddConstantScalar:
       return MakeUnique<TransformationAddConstantScalar>(
           message.add_constant_scalar());
@@ -84,6 +100,9 @@
     case protobufs::Transformation::TransformationCase::kAddGlobalVariable:
       return MakeUnique<TransformationAddGlobalVariable>(
           message.add_global_variable());
+    case protobufs::Transformation::TransformationCase::kAddLocalVariable:
+      return MakeUnique<TransformationAddLocalVariable>(
+          message.add_local_variable());
     case protobufs::Transformation::TransformationCase::
         kAddNoContractionDecoration:
       return MakeUnique<TransformationAddNoContractionDecoration>(
@@ -117,6 +136,13 @@
           message.composite_extract());
     case protobufs::Transformation::TransformationCase::kCopyObject:
       return MakeUnique<TransformationCopyObject>(message.copy_object());
+    case protobufs::Transformation::TransformationCase::kEquationInstruction:
+      return MakeUnique<TransformationEquationInstruction>(
+          message.equation_instruction());
+    case protobufs::Transformation::TransformationCase::kFunctionCall:
+      return MakeUnique<TransformationFunctionCall>(message.function_call());
+    case protobufs::Transformation::TransformationCase::kLoad:
+      return MakeUnique<TransformationLoad>(message.load());
     case protobufs::Transformation::TransformationCase::kMergeBlocks:
       return MakeUnique<TransformationMergeBlocks>(message.merge_blocks());
     case protobufs::Transformation::TransformationCase::kMoveBlockDown:
@@ -125,6 +151,10 @@
       return MakeUnique<TransformationOutlineFunction>(
           message.outline_function());
     case protobufs::Transformation::TransformationCase::
+        kPermuteFunctionParameters:
+      return MakeUnique<TransformationPermuteFunctionParameters>(
+          message.permute_function_parameters());
+    case protobufs::Transformation::TransformationCase::
         kReplaceBooleanConstantWithConstantBinary:
       return MakeUnique<TransformationReplaceBooleanConstantWithConstantBinary>(
           message.replace_boolean_constant_with_constant_binary());
@@ -149,6 +179,15 @@
           message.set_selection_control());
     case protobufs::Transformation::TransformationCase::kSplitBlock:
       return MakeUnique<TransformationSplitBlock>(message.split_block());
+    case protobufs::Transformation::TransformationCase::kStore:
+      return MakeUnique<TransformationStore>(message.store());
+    case protobufs::Transformation::TransformationCase::kSwapCommutableOperands:
+      return MakeUnique<TransformationSwapCommutableOperands>(
+          message.swap_commutable_operands());
+    case protobufs::Transformation::TransformationCase::
+        kToggleAccessChainInstruction:
+      return MakeUnique<TransformationToggleAccessChainInstruction>(
+          message.toggle_access_chain_instruction());
     case protobufs::Transformation::TransformationCase::kVectorShuffle:
       return MakeUnique<TransformationVectorShuffle>(message.vector_shuffle());
     case protobufs::Transformation::TRANSFORMATION_NOT_SET:
@@ -159,5 +198,18 @@
   return nullptr;
 }
 
+bool Transformation::CheckIdIsFreshAndNotUsedByThisTransformation(
+    uint32_t id, opt::IRContext* ir_context,
+    std::set<uint32_t>* ids_used_by_this_transformation) {
+  if (!fuzzerutil::IsFreshId(ir_context, id)) {
+    return false;
+  }
+  if (ids_used_by_this_transformation->count(id) != 0) {
+    return false;
+  }
+  ids_used_by_this_transformation->insert(id);
+  return true;
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation.h b/source/fuzz/transformation.h
index c6b852f..dbd0fe2 100644
--- a/source/fuzz/transformation.h
+++ b/source/fuzz/transformation.h
@@ -17,8 +17,8 @@
 
 #include <memory>
 
-#include "source/fuzz/fact_manager.h"
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/transformation_context.h"
 #include "source/opt/ir_context.h"
 
 namespace spvtools {
@@ -60,19 +60,22 @@
  public:
   // A precondition that determines whether the transformation can be cleanly
   // applied in a semantics-preserving manner to the SPIR-V module given by
-  // |context|, in the presence of facts captured by |fact_manager|.
+  // |ir_context|, in the presence of facts and other contextual information
+  // captured by |transformation_context|.
+  //
   // Preconditions for individual transformations must be documented in the
-  // associated header file using precise English. The fact manager is used to
-  // provide access to facts about the module that are known to be true, on
+  // associated header file using precise English. The transformation context
+  // provides access to facts about the module that are known to be true, on
   // which the precondition may depend.
-  virtual bool IsApplicable(opt::IRContext* context,
-                            const FactManager& fact_manager) const = 0;
+  virtual bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const = 0;
 
-  // Requires that IsApplicable(context, fact_manager) holds.  Applies the
-  // transformation, mutating |context| and possibly updating |fact_manager|
-  // with new facts established by the transformation.
-  virtual void Apply(opt::IRContext* context,
-                     FactManager* fact_manager) const = 0;
+  // Requires that IsApplicable(ir_context, *transformation_context) holds.
+  // Applies the transformation, mutating |ir_context| and possibly updating
+  // |transformation_context| with new facts established by the transformation.
+  virtual void Apply(opt::IRContext* ir_context,
+                     TransformationContext* transformation_context) const = 0;
 
   // Turns the transformation into a protobuf message for serialization.
   virtual protobufs::Transformation ToMessage() const = 0;
@@ -83,6 +86,15 @@
   // 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
+  // checking id freshness for a transformation that uses many ids, all of which
+  // must be distinct.
+  static bool CheckIdIsFreshAndNotUsedByThisTransformation(
+      uint32_t id, opt::IRContext* ir_context,
+      std::set<uint32_t>* ids_used_by_this_transformation);
 };
 
 }  // namespace fuzz
diff --git a/source/fuzz/transformation_access_chain.cpp b/source/fuzz/transformation_access_chain.cpp
new file mode 100644
index 0000000..ff17c36
--- /dev/null
+++ b/source/fuzz/transformation_access_chain.cpp
@@ -0,0 +1,217 @@
+// 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_access_chain.h"
+
+#include <vector>
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationAccessChain::TransformationAccessChain(
+    const spvtools::fuzz::protobufs::TransformationAccessChain& message)
+    : message_(message) {}
+
+TransformationAccessChain::TransformationAccessChain(
+    uint32_t fresh_id, uint32_t pointer_id,
+    const std::vector<uint32_t>& index_id,
+    const protobufs::InstructionDescriptor& instruction_to_insert_before) {
+  message_.set_fresh_id(fresh_id);
+  message_.set_pointer_id(pointer_id);
+  for (auto id : index_id) {
+    message_.add_index_id(id);
+  }
+  *message_.mutable_instruction_to_insert_before() =
+      instruction_to_insert_before;
+}
+
+bool TransformationAccessChain::IsApplicable(
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
+  // The result id must be fresh
+  if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
+    return false;
+  }
+  // The pointer id must exist and have a type.
+  auto pointer = ir_context->get_def_use_mgr()->GetDef(message_.pointer_id());
+  if (!pointer || !pointer->type_id()) {
+    return false;
+  }
+  // The type must indeed be a pointer
+  auto pointer_type = ir_context->get_def_use_mgr()->GetDef(pointer->type_id());
+  if (pointer_type->opcode() != SpvOpTypePointer) {
+    return false;
+  }
+
+  // The described instruction to insert before must exist and be a suitable
+  // point where an OpAccessChain instruction could be inserted.
+  auto instruction_to_insert_before =
+      FindInstruction(message_.instruction_to_insert_before(), ir_context);
+  if (!instruction_to_insert_before) {
+    return false;
+  }
+  if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(
+          SpvOpAccessChain, instruction_to_insert_before)) {
+    return false;
+  }
+
+  // Do not allow making an access chain from a null or undefined pointer, as
+  // we do not want to allow accessing such pointers.  This might be acceptable
+  // in dead blocks, but we conservatively avoid it.
+  switch (pointer->opcode()) {
+    case SpvOpConstantNull:
+    case SpvOpUndef:
+      // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3185): When
+      //  fuzzing for real we would like an 'assert(false)' here.  But we also
+      //  want to be able to write negative unit tests.
+      return false;
+    default:
+      break;
+  }
+
+  // The pointer on which the access chain is to be based needs to be available
+  // (according to dominance rules) at the insertion point.
+  if (!fuzzerutil::IdIsAvailableBeforeInstruction(
+          ir_context, instruction_to_insert_before, message_.pointer_id())) {
+    return false;
+  }
+
+  // We now need to use the given indices to walk the type structure of the
+  // base type of the pointer, making sure that (a) the indices correspond to
+  // integers, and (b) these integer values are in-bounds.
+
+  // Start from the base type of the pointer.
+  uint32_t subobject_type_id = pointer_type->GetSingleWordInOperand(1);
+
+  // Consider the given index ids in turn.
+  for (auto index_id : message_.index_id()) {
+    // Try to get the integer value associated with this index is.  The first
+    // component of the result will be false if the id did not correspond to an
+    // integer.  Otherwise, the integer with which the id is associated is the
+    // second component.
+    std::pair<bool, uint32_t> maybe_index_value =
+        GetIndexValue(ir_context, index_id);
+    if (!maybe_index_value.first) {
+      // There was no integer: this index is no good.
+      return false;
+    }
+    // Try to walk down the type using this index.  This will yield 0 if the
+    // type is not a composite or the index is out of bounds, and the id of
+    // the next type otherwise.
+    subobject_type_id = fuzzerutil::WalkOneCompositeTypeIndex(
+        ir_context, subobject_type_id, maybe_index_value.second);
+    if (!subobject_type_id) {
+      // Either the type was not a composite (so that too many indices were
+      // provided), or the index was out of bounds.
+      return false;
+    }
+  }
+  // At this point, |subobject_type_id| is the type of the value targeted by
+  // the new access chain.  The result type of the access chain should be a
+  // pointer to this type, with the same storage class as for the original
+  // pointer.  Such a pointer type needs to exist in the module.
+  //
+  // We do not use the type manager to look up this type, due to problems
+  // associated with pointers to isomorphic structs being regarded as the same.
+  return fuzzerutil::MaybeGetPointerType(
+             ir_context, subobject_type_id,
+             static_cast<SpvStorageClass>(
+                 pointer_type->GetSingleWordInOperand(0))) != 0;
+}
+
+void TransformationAccessChain::Apply(
+    opt::IRContext* ir_context,
+    TransformationContext* transformation_context) const {
+  // The operands to the access chain are the pointer followed by the indices.
+  // The result type of the access chain is determined by where the indices
+  // lead.  We thus push the pointer to a sequence of operands, and then follow
+  // the indices, pushing each to the operand list and tracking the type
+  // obtained by following it.  Ultimately this yields the type of the
+  // component reached by following all the indices, and the result type is
+  // a pointer to this component type.
+  opt::Instruction::OperandList operands;
+
+  // Add the pointer id itself.
+  operands.push_back({SPV_OPERAND_TYPE_ID, {message_.pointer_id()}});
+
+  // Start walking the indices, starting with the pointer's base type.
+  auto pointer_type = ir_context->get_def_use_mgr()->GetDef(
+      ir_context->get_def_use_mgr()->GetDef(message_.pointer_id())->type_id());
+  uint32_t subobject_type_id = pointer_type->GetSingleWordInOperand(1);
+
+  // Go through the index ids in turn.
+  for (auto index_id : message_.index_id()) {
+    // Add the index id to the operands.
+    operands.push_back({SPV_OPERAND_TYPE_ID, {index_id}});
+    // Get the integer value associated with the index id.
+    uint32_t index_value = GetIndexValue(ir_context, index_id).second;
+    // Walk to the next type in the composite object using this index.
+    subobject_type_id = fuzzerutil::WalkOneCompositeTypeIndex(
+        ir_context, subobject_type_id, index_value);
+  }
+  // The access chain's result type is a pointer to the composite component that
+  // was reached after following all indices.  The storage class is that of the
+  // original pointer.
+  uint32_t result_type = fuzzerutil::MaybeGetPointerType(
+      ir_context, subobject_type_id,
+      static_cast<SpvStorageClass>(pointer_type->GetSingleWordInOperand(0)));
+
+  // Add the access chain instruction to the module, and update the module's id
+  // bound.
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
+  FindInstruction(message_.instruction_to_insert_before(), ir_context)
+      ->InsertBefore(MakeUnique<opt::Instruction>(
+          ir_context, SpvOpAccessChain, result_type, message_.fresh_id(),
+          operands));
+
+  // Conservatively invalidate all analyses.
+  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+
+  // If the base pointer's pointee value was irrelevant, the same is true of the
+  // pointee value of the result of this access chain.
+  if (transformation_context->GetFactManager()->PointeeValueIsIrrelevant(
+          message_.pointer_id())) {
+    transformation_context->GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
+        message_.fresh_id());
+  }
+}
+
+protobufs::Transformation TransformationAccessChain::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_access_chain() = message_;
+  return result;
+}
+
+std::pair<bool, uint32_t> TransformationAccessChain::GetIndexValue(
+    opt::IRContext* ir_context, uint32_t index_id) const {
+  auto index_instruction = ir_context->get_def_use_mgr()->GetDef(index_id);
+  if (!index_instruction || !spvOpcodeIsConstant(index_instruction->opcode())) {
+    // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3179) We could
+    //  allow non-constant indices when looking up non-structs, using clamping
+    //  to ensure they are in-bounds.
+    return {false, 0};
+  }
+  auto index_type =
+      ir_context->get_def_use_mgr()->GetDef(index_instruction->type_id());
+  if (index_type->opcode() != SpvOpTypeInt ||
+      index_type->GetSingleWordInOperand(0) != 32) {
+    return {false, 0};
+  }
+  return {true, index_instruction->GetSingleWordInOperand(0)};
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_access_chain.h b/source/fuzz/transformation_access_chain.h
new file mode 100644
index 0000000..9306a59
--- /dev/null
+++ b/source/fuzz/transformation_access_chain.h
@@ -0,0 +1,83 @@
+// 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_ACCESS_CHAIN_H_
+#define SOURCE_FUZZ_TRANSFORMATION_ACCESS_CHAIN_H_
+
+#include <utility>
+
+#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 TransformationAccessChain : public Transformation {
+ public:
+  explicit TransformationAccessChain(
+      const protobufs::TransformationAccessChain& message);
+
+  TransformationAccessChain(
+      uint32_t fresh_id, uint32_t pointer_id,
+      const std::vector<uint32_t>& index_id,
+      const protobufs::InstructionDescriptor& instruction_to_insert_before);
+
+  // - |message_.fresh_id| must be fresh
+  // - |message_.instruction_to_insert_before| must identify an instruction
+  //   before which it is legitimate to insert an OpAccessChain instruction
+  // - |message_.pointer_id| must be a result id with pointer type that is
+  //   available (according to dominance rules) at the insertion point.
+  // - The pointer must not be OpConstantNull or OpUndef
+  // - |message_.index_id| must be a sequence of ids of 32-bit integer constants
+  //   such that it is possible to walk the pointee type of
+  //   |message_.pointer_id| using these indices, remaining in-bounds.
+  // - If type t is the final type reached by walking these indices, the module
+  //   must include an instruction "OpTypePointer SC %t" where SC is the storage
+  //   class associated with |message_.pointer_id|
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // Adds an instruction of the form:
+  //   |message_.fresh_id| = OpAccessChain %ptr |message_.index_id|
+  // where %ptr is the result if of an instruction declaring a pointer to the
+  // type reached by walking the pointee type of |message_.pointer_id| using
+  // the indices in |message_.index_id|, and with the same storage class as
+  // |message_.pointer_id|.
+  //
+  // If the fact manager in |transformation_context| reports that
+  // |message_.pointer_id| has an irrelevant pointee value, then the fact that
+  // |message_.fresh_id| (the result of the access chain) also has an irrelevant
+  // pointee value is also recorded.
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+ private:
+  // Returns {false, 0} if |index_id| does not correspond to a 32-bit integer
+  // constant.  Otherwise, returns {true, value}, where value is the value of
+  // the 32-bit integer constant to which |index_id| corresponds.
+  std::pair<bool, uint32_t> GetIndexValue(opt::IRContext* ir_context,
+                                          uint32_t index_id) const;
+
+  protobufs::TransformationAccessChain message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_ACCESS_CHAIN_H_
diff --git a/source/fuzz/transformation_add_constant_boolean.cpp b/source/fuzz/transformation_add_constant_boolean.cpp
index 21c8ed3..1930f7e 100644
--- a/source/fuzz/transformation_add_constant_boolean.cpp
+++ b/source/fuzz/transformation_add_constant_boolean.cpp
@@ -31,27 +31,28 @@
 }
 
 bool TransformationAddConstantBoolean::IsApplicable(
-    opt::IRContext* context, const FactManager& /*unused*/) const {
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
   opt::analysis::Bool bool_type;
-  if (!context->get_type_mgr()->GetId(&bool_type)) {
+  if (!ir_context->get_type_mgr()->GetId(&bool_type)) {
     // No OpTypeBool is present.
     return false;
   }
-  return fuzzerutil::IsFreshId(context, message_.fresh_id());
+  return fuzzerutil::IsFreshId(ir_context, message_.fresh_id());
 }
 
-void TransformationAddConstantBoolean::Apply(opt::IRContext* context,
-                                             FactManager* /*unused*/) const {
+void TransformationAddConstantBoolean::Apply(
+    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
   opt::analysis::Bool bool_type;
   // Add the boolean constant to the module, ensuring the module's id bound is
   // high enough.
-  fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
-  context->module()->AddGlobalValue(
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
+  ir_context->module()->AddGlobalValue(
       message_.is_true() ? SpvOpConstantTrue : SpvOpConstantFalse,
-      message_.fresh_id(), context->get_type_mgr()->GetId(&bool_type));
+      message_.fresh_id(), ir_context->get_type_mgr()->GetId(&bool_type));
   // We have added an instruction to the module, so need to be careful about the
   // validity of existing analyses.
-  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+  ir_context->InvalidateAnalysesExceptFor(
+      opt::IRContext::Analysis::kAnalysisNone);
 }
 
 protobufs::Transformation TransformationAddConstantBoolean::ToMessage() const {
diff --git a/source/fuzz/transformation_add_constant_boolean.h b/source/fuzz/transformation_add_constant_boolean.h
index 79df1cd..5d876cf 100644
--- a/source/fuzz/transformation_add_constant_boolean.h
+++ b/source/fuzz/transformation_add_constant_boolean.h
@@ -15,9 +15,9 @@
 #ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_BOOLEAN_CONSTANT_H_
 #define SOURCE_FUZZ_TRANSFORMATION_ADD_BOOLEAN_CONSTANT_H_
 
-#include "source/fuzz/fact_manager.h"
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
 #include "source/fuzz/transformation.h"
+#include "source/fuzz/transformation_context.h"
 #include "source/opt/ir_context.h"
 
 namespace spvtools {
@@ -32,12 +32,14 @@
 
   // - |message_.fresh_id| must not be used by the module.
   // - The module must already contain OpTypeBool.
-  bool IsApplicable(opt::IRContext* context,
-                    const FactManager& fact_manager) const override;
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
 
   // - Adds OpConstantTrue (OpConstantFalse) to the module with id
   //   |message_.fresh_id| if |message_.is_true| holds (does not hold).
-  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
 
   protobufs::Transformation ToMessage() const override;
 
diff --git a/source/fuzz/transformation_add_constant_composite.cpp b/source/fuzz/transformation_add_constant_composite.cpp
index 7ba1ea4..ae34b26 100644
--- a/source/fuzz/transformation_add_constant_composite.cpp
+++ b/source/fuzz/transformation_add_constant_composite.cpp
@@ -37,15 +37,14 @@
 }
 
 bool TransformationAddConstantComposite::IsApplicable(
-    opt::IRContext* context,
-    const spvtools::fuzz::FactManager& /*unused*/) const {
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
   // Check that the given id is fresh.
-  if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) {
+  if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
     return false;
   }
   // Check that the composite type id is an instruction id.
   auto composite_type_instruction =
-      context->get_def_use_mgr()->GetDef(message_.type_id());
+      ir_context->get_def_use_mgr()->GetDef(message_.type_id());
   if (!composite_type_instruction) {
     return false;
   }
@@ -56,7 +55,7 @@
     case SpvOpTypeArray:
       for (uint32_t index = 0;
            index <
-           fuzzerutil::GetArraySize(*composite_type_instruction, context);
+           fuzzerutil::GetArraySize(*composite_type_instruction, ir_context);
            index++) {
         constituent_type_ids.push_back(
             composite_type_instruction->GetSingleWordInOperand(0));
@@ -93,7 +92,7 @@
   // corresponding constituent type.
   for (uint32_t index = 0; index < constituent_type_ids.size(); index++) {
     auto constituent_instruction =
-        context->get_def_use_mgr()->GetDef(message_.constituent_id(index));
+        ir_context->get_def_use_mgr()->GetDef(message_.constituent_id(index));
     if (!constituent_instruction) {
       return false;
     }
@@ -105,18 +104,19 @@
 }
 
 void TransformationAddConstantComposite::Apply(
-    opt::IRContext* context, spvtools::fuzz::FactManager* /*unused*/) const {
+    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
   opt::Instruction::OperandList in_operands;
   for (auto constituent_id : message_.constituent_id()) {
     in_operands.push_back({SPV_OPERAND_TYPE_ID, {constituent_id}});
   }
-  context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
-      context, SpvOpConstantComposite, message_.type_id(), message_.fresh_id(),
-      in_operands));
-  fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
+  ir_context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpConstantComposite, message_.type_id(),
+      message_.fresh_id(), in_operands));
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
   // We have added an instruction to the module, so need to be careful about the
   // validity of existing analyses.
-  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+  ir_context->InvalidateAnalysesExceptFor(
+      opt::IRContext::Analysis::kAnalysisNone);
 }
 
 protobufs::Transformation TransformationAddConstantComposite::ToMessage()
diff --git a/source/fuzz/transformation_add_constant_composite.h b/source/fuzz/transformation_add_constant_composite.h
index 9a824a0..4fec561 100644
--- a/source/fuzz/transformation_add_constant_composite.h
+++ b/source/fuzz/transformation_add_constant_composite.h
@@ -17,9 +17,9 @@
 
 #include <vector>
 
-#include "source/fuzz/fact_manager.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 {
@@ -38,13 +38,15 @@
   // - |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
-  bool IsApplicable(opt::IRContext* context,
-                    const FactManager& fact_manager) const override;
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
 
   // Adds an OpConstantComposite instruction defining a constant of type
   // |message_.type_id|, using |message_.constituent_id| as constituents, with
   // result id |message_.fresh_id|.
-  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
 
   protobufs::Transformation ToMessage() const override;
 
diff --git a/source/fuzz/transformation_add_constant_null.cpp b/source/fuzz/transformation_add_constant_null.cpp
new file mode 100644
index 0000000..dedbc21
--- /dev/null
+++ b/source/fuzz/transformation_add_constant_null.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/transformation_add_constant_null.h"
+
+#include "source/fuzz/fuzzer_util.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationAddConstantNull::TransformationAddConstantNull(
+    const spvtools::fuzz::protobufs::TransformationAddConstantNull& message)
+    : message_(message) {}
+
+TransformationAddConstantNull::TransformationAddConstantNull(uint32_t fresh_id,
+                                                             uint32_t type_id) {
+  message_.set_fresh_id(fresh_id);
+  message_.set_type_id(type_id);
+}
+
+bool TransformationAddConstantNull::IsApplicable(
+    opt::IRContext* context, const TransformationContext& /*unused*/) const {
+  // A fresh id is required.
+  if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) {
+    return false;
+  }
+  auto type = context->get_type_mgr()->GetType(message_.type_id());
+  // The type must exist.
+  if (!type) {
+    return false;
+  }
+  // The type must be one of the types for which null constants are allowed,
+  // according to the SPIR-V spec.
+  return fuzzerutil::IsNullConstantSupported(*type);
+}
+
+void TransformationAddConstantNull::Apply(
+    opt::IRContext* context, TransformationContext* /*unused*/) const {
+  context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+      context, SpvOpConstantNull, message_.type_id(), message_.fresh_id(),
+      opt::Instruction::OperandList()));
+  fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
+  // We have added an instruction to the module, so need to be careful about the
+  // validity of existing analyses.
+  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+}
+
+protobufs::Transformation TransformationAddConstantNull::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_add_constant_null() = message_;
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_add_constant_null.h b/source/fuzz/transformation_add_constant_null.h
new file mode 100644
index 0000000..590fc0d
--- /dev/null
+++ b/source/fuzz/transformation_add_constant_null.h
@@ -0,0 +1,54 @@
+// 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_CONSTANT_NULL_H_
+#define SOURCE_FUZZ_TRANSFORMATION_ADD_CONSTANT_NULL_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 TransformationAddConstantNull : public Transformation {
+ public:
+  explicit TransformationAddConstantNull(
+      const protobufs::TransformationAddConstantNull& message);
+
+  TransformationAddConstantNull(uint32_t fresh_id, uint32_t type_id);
+
+  // - |message_.fresh_id| must be fresh
+  // - |message_.type_id| must be the id of a type for which it is acceptable
+  //   to create a null constant
+  bool IsApplicable(
+      opt::IRContext* context,
+      const TransformationContext& transformation_context) const override;
+
+  // Adds an OpConstantNull instruction to the module, with |message_.type_id|
+  // as its type.  The instruction has result id |message_.fresh_id|.
+  void Apply(opt::IRContext* context,
+             TransformationContext* transformation_context) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+ private:
+  protobufs::TransformationAddConstantNull message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_ADD_CONSTANT_NULL_H_
diff --git a/source/fuzz/transformation_add_constant_scalar.cpp b/source/fuzz/transformation_add_constant_scalar.cpp
index 36af5e0..e13d08f 100644
--- a/source/fuzz/transformation_add_constant_scalar.cpp
+++ b/source/fuzz/transformation_add_constant_scalar.cpp
@@ -33,14 +33,13 @@
 }
 
 bool TransformationAddConstantScalar::IsApplicable(
-    opt::IRContext* context,
-    const spvtools::fuzz::FactManager& /*unused*/) const {
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
   // The id needs to be fresh.
-  if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) {
+  if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
     return false;
   }
   // The type id for the scalar must exist and be a type.
-  auto type = context->get_type_mgr()->GetType(message_.type_id());
+  auto type = ir_context->get_type_mgr()->GetType(message_.type_id());
   if (!type) {
     return false;
   }
@@ -61,20 +60,21 @@
 }
 
 void TransformationAddConstantScalar::Apply(
-    opt::IRContext* context, spvtools::fuzz::FactManager* /*unused*/) const {
+    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
   opt::Instruction::OperandList operand_list;
   for (auto word : message_.word()) {
     operand_list.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {word}});
   }
-  context->module()->AddGlobalValue(
-      MakeUnique<opt::Instruction>(context, SpvOpConstant, message_.type_id(),
-                                   message_.fresh_id(), operand_list));
+  ir_context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpConstant, message_.type_id(), message_.fresh_id(),
+      operand_list));
 
-  fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
 
   // We have added an instruction to the module, so need to be careful about the
   // validity of existing analyses.
-  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+  ir_context->InvalidateAnalysesExceptFor(
+      opt::IRContext::Analysis::kAnalysisNone);
 }
 
 protobufs::Transformation TransformationAddConstantScalar::ToMessage() const {
diff --git a/source/fuzz/transformation_add_constant_scalar.h b/source/fuzz/transformation_add_constant_scalar.h
index 914cfe6..e0ed39f 100644
--- a/source/fuzz/transformation_add_constant_scalar.h
+++ b/source/fuzz/transformation_add_constant_scalar.h
@@ -17,9 +17,9 @@
 
 #include <vector>
 
-#include "source/fuzz/fact_manager.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 {
@@ -37,11 +37,13 @@
   // - |message_.type_id| must be the id of a floating-point or integer type
   // - The size of |message_.word| must be compatible with the width of this
   //   type
-  bool IsApplicable(opt::IRContext* context,
-                    const FactManager& fact_manager) const override;
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
 
   // Adds a new OpConstant instruction with the given type and words.
-  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
 
   protobufs::Transformation ToMessage() const override;
 
diff --git a/source/fuzz/transformation_add_dead_block.cpp b/source/fuzz/transformation_add_dead_block.cpp
index b58f75e..b246c3f 100644
--- a/source/fuzz/transformation_add_dead_block.cpp
+++ b/source/fuzz/transformation_add_dead_block.cpp
@@ -32,16 +32,15 @@
 }
 
 bool TransformationAddDeadBlock::IsApplicable(
-    opt::IRContext* context,
-    const spvtools::fuzz::FactManager& /*unused*/) const {
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
   // The new block's id must be fresh.
-  if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) {
+  if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
     return false;
   }
 
   // First, we check that a constant with the same value as
   // |message_.condition_value| is present.
-  if (!fuzzerutil::MaybeGetBoolConstantId(context,
+  if (!fuzzerutil::MaybeGetBoolConstantId(ir_context,
                                           message_.condition_value())) {
     // The required constant is not present, so the transformation cannot be
     // applied.
@@ -50,7 +49,7 @@
 
   // The existing block must indeed exist.
   auto existing_block =
-      fuzzerutil::MaybeFindBlock(context, message_.existing_block());
+      fuzzerutil::MaybeFindBlock(ir_context, message_.existing_block());
   if (!existing_block) {
     return false;
   }
@@ -68,13 +67,13 @@
   // Its successor must not be a merge block nor continue target.
   auto successor_block_id =
       existing_block->terminator()->GetSingleWordInOperand(0);
-  if (fuzzerutil::IsMergeOrContinue(context, successor_block_id)) {
+  if (fuzzerutil::IsMergeOrContinue(ir_context, successor_block_id)) {
     return false;
   }
 
   // The successor must not be a loop header (i.e., |message_.existing_block|
   // must not be a back-edge block.
-  if (context->cfg()->block(successor_block_id)->IsLoopHeader()) {
+  if (ir_context->cfg()->block(successor_block_id)->IsLoopHeader()) {
     return false;
   }
 
@@ -82,34 +81,36 @@
 }
 
 void TransformationAddDeadBlock::Apply(
-    opt::IRContext* context, spvtools::fuzz::FactManager* fact_manager) const {
+    opt::IRContext* ir_context,
+    TransformationContext* transformation_context) const {
   // Update the module id bound so that it is at least the id of the new block.
-  fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
 
   // Get the existing block and its successor.
-  auto existing_block = context->cfg()->block(message_.existing_block());
+  auto existing_block = ir_context->cfg()->block(message_.existing_block());
   auto successor_block_id =
       existing_block->terminator()->GetSingleWordInOperand(0);
 
   // Get the id of the boolean value that will be used as the branch condition.
-  auto bool_id =
-      fuzzerutil::MaybeGetBoolConstantId(context, message_.condition_value());
+  auto bool_id = fuzzerutil::MaybeGetBoolConstantId(ir_context,
+                                                    message_.condition_value());
 
   // Make a new block that unconditionally branches to the original successor
   // block.
   auto enclosing_function = existing_block->GetParent();
-  std::unique_ptr<opt::BasicBlock> new_block = MakeUnique<opt::BasicBlock>(
-      MakeUnique<opt::Instruction>(context, SpvOpLabel, 0, message_.fresh_id(),
-                                   opt::Instruction::OperandList()));
+  std::unique_ptr<opt::BasicBlock> new_block =
+      MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>(
+          ir_context, SpvOpLabel, 0, message_.fresh_id(),
+          opt::Instruction::OperandList()));
   new_block->AddInstruction(MakeUnique<opt::Instruction>(
-      context, SpvOpBranch, 0, 0,
+      ir_context, SpvOpBranch, 0, 0,
       opt::Instruction::OperandList(
           {{SPV_OPERAND_TYPE_ID, {successor_block_id}}})));
 
   // Turn the original block into a selection merge, with its original successor
   // as the merge block.
   existing_block->terminator()->InsertBefore(MakeUnique<opt::Instruction>(
-      context, SpvOpSelectionMerge, 0, 0,
+      ir_context, SpvOpSelectionMerge, 0, 0,
       opt::Instruction::OperandList(
           {{SPV_OPERAND_TYPE_ID, {successor_block_id}},
            {SPV_OPERAND_TYPE_SELECTION_CONTROL,
@@ -135,7 +136,8 @@
                                             existing_block);
 
   // Record the fact that the new block is dead.
-  fact_manager->AddFactBlockIsDead(message_.fresh_id());
+  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
@@ -143,7 +145,7 @@
   // to be valid since |message_.existing_block| dominates the new block by
   // construction.  Other transformations can change these phi operands to more
   // interesting values.
-  context->cfg()
+  ir_context->cfg()
       ->block(successor_block_id)
       ->ForEachPhiInst([this](opt::Instruction* phi_inst) {
         // Copy the operand that provides the phi value for the first of any
@@ -156,7 +158,7 @@
 
   // Do not rely on any existing analysis results since the control flow graph
   // of the module has changed.
-  context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
 }
 
 protobufs::Transformation TransformationAddDeadBlock::ToMessage() const {
diff --git a/source/fuzz/transformation_add_dead_block.h b/source/fuzz/transformation_add_dead_block.h
index 059daca..7d07616 100644
--- a/source/fuzz/transformation_add_dead_block.h
+++ b/source/fuzz/transformation_add_dead_block.h
@@ -15,9 +15,9 @@
 #ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_DEAD_BLOCK_H_
 #define SOURCE_FUZZ_TRANSFORMATION_ADD_DEAD_BLOCK_H_
 
-#include "source/fuzz/fact_manager.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 {
@@ -41,15 +41,17 @@
   // - |message_.existing_block| must not be a back-edge block, since in this
   //   case the newly-added block would lead to another back-edge to the
   //   associated loop header
-  bool IsApplicable(opt::IRContext* context,
-                    const FactManager& fact_manager) const override;
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
 
   // Changes the OpBranch from |message_.existing_block| to its successor 's'
   // to an OpBranchConditional to either 's' or a new block,
   // |message_.fresh_id|, which itself unconditionally branches to 's'.  The
   // conditional branch uses |message.condition_value| as its condition, and is
   // arranged so that control will pass to 's' at runtime.
-  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
 
   protobufs::Transformation ToMessage() const override;
 
diff --git a/source/fuzz/transformation_add_dead_break.cpp b/source/fuzz/transformation_add_dead_break.cpp
index 43847fa..db9de7d 100644
--- a/source/fuzz/transformation_add_dead_break.cpp
+++ b/source/fuzz/transformation_add_dead_break.cpp
@@ -14,8 +14,8 @@
 
 #include "source/fuzz/transformation_add_dead_break.h"
 
-#include "source/fuzz/fact_manager.h"
 #include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/transformation_context.h"
 #include "source/opt/basic_block.h"
 #include "source/opt/ir_context.h"
 #include "source/opt/struct_cfg_analysis.h"
@@ -39,7 +39,7 @@
 }
 
 bool TransformationAddDeadBreak::AddingBreakRespectsStructuredControlFlow(
-    opt::IRContext* context, opt::BasicBlock* bb_from) const {
+    opt::IRContext* ir_context, opt::BasicBlock* bb_from) const {
   // Look at the structured control flow associated with |from_block| and
   // check whether it is contained in an appropriate construct with merge id
   // |to_block| such that a break from |from_block| to |to_block| is legal.
@@ -70,7 +70,7 @@
   // structured control flow construct.
 
   auto containing_construct =
-      context->GetStructuredCFGAnalysis()->ContainingConstruct(
+      ir_context->GetStructuredCFGAnalysis()->ContainingConstruct(
           message_.from_block());
   if (!containing_construct) {
     // |from_block| is not in a construct from which we can break.
@@ -79,7 +79,7 @@
 
   // Consider case (2)
   if (message_.to_block() ==
-      context->cfg()->block(containing_construct)->MergeBlockId()) {
+      ir_context->cfg()->block(containing_construct)->MergeBlockId()) {
     // This looks like an instance of case (2).
     // However, the structured CFG analysis regards the continue construct of a
     // loop as part of the loop, but it is not legal to jump from a loop's
@@ -90,28 +90,29 @@
     //  currently allow a dead break from a back edge block, but we could and
     //  ultimately should.
     return !fuzzerutil::BlockIsInLoopContinueConstruct(
-        context, message_.from_block(), containing_construct);
+        ir_context, message_.from_block(), containing_construct);
   }
 
   // Case (3) holds if and only if |to_block| is the merge block for this
   // innermost loop that contains |from_block|
   auto containing_loop_header =
-      context->GetStructuredCFGAnalysis()->ContainingLoop(
+      ir_context->GetStructuredCFGAnalysis()->ContainingLoop(
           message_.from_block());
   if (containing_loop_header &&
       message_.to_block() ==
-          context->cfg()->block(containing_loop_header)->MergeBlockId()) {
+          ir_context->cfg()->block(containing_loop_header)->MergeBlockId()) {
     return !fuzzerutil::BlockIsInLoopContinueConstruct(
-        context, message_.from_block(), containing_loop_header);
+        ir_context, message_.from_block(), containing_loop_header);
   }
   return false;
 }
 
 bool TransformationAddDeadBreak::IsApplicable(
-    opt::IRContext* context, const FactManager& /*unused*/) const {
+    opt::IRContext* ir_context,
+    const TransformationContext& transformation_context) const {
   // First, we check that a constant with the same value as
   // |message_.break_condition_value| is present.
-  if (!fuzzerutil::MaybeGetBoolConstantId(context,
+  if (!fuzzerutil::MaybeGetBoolConstantId(ir_context,
                                           message_.break_condition_value())) {
     // The required constant is not present, so the transformation cannot be
     // applied.
@@ -121,17 +122,17 @@
   // Check that |message_.from_block| and |message_.to_block| really are block
   // ids
   opt::BasicBlock* bb_from =
-      fuzzerutil::MaybeFindBlock(context, message_.from_block());
+      fuzzerutil::MaybeFindBlock(ir_context, message_.from_block());
   if (bb_from == nullptr) {
     return false;
   }
   opt::BasicBlock* bb_to =
-      fuzzerutil::MaybeFindBlock(context, message_.to_block());
+      fuzzerutil::MaybeFindBlock(ir_context, message_.to_block());
   if (bb_to == nullptr) {
     return false;
   }
 
-  if (!fuzzerutil::BlockIsReachableInItsFunction(context, bb_to)) {
+  if (!fuzzerutil::BlockIsReachableInItsFunction(ir_context, bb_to)) {
     // If the target of the break is unreachable, we conservatively do not
     // allow adding a dead break, to avoid the compilations that arise due to
     // the lack of sensible dominance information for unreachable blocks.
@@ -157,14 +158,14 @@
       "The id of the block we found should match the target id for the break.");
 
   // Check whether the data passed to extend OpPhi instructions is appropriate.
-  if (!fuzzerutil::PhiIdsOkForNewEdge(context, bb_from, bb_to,
+  if (!fuzzerutil::PhiIdsOkForNewEdge(ir_context, bb_from, bb_to,
                                       message_.phi_id())) {
     return false;
   }
 
   // Check that adding the break would respect the rules of structured
   // control flow.
-  if (!AddingBreakRespectsStructuredControlFlow(context, bb_from)) {
+  if (!AddingBreakRespectsStructuredControlFlow(ir_context, bb_from)) {
     return false;
   }
 
@@ -177,16 +178,18 @@
   // being places on the validator.  This should be revisited if we are sure
   // the validator is complete with respect to checking structured control flow
   // rules.
-  auto cloned_context = fuzzerutil::CloneIRContext(context);
+  auto cloned_context = fuzzerutil::CloneIRContext(ir_context);
   ApplyImpl(cloned_context.get());
-  return fuzzerutil::IsValid(cloned_context.get());
+  return fuzzerutil::IsValid(cloned_context.get(),
+                             transformation_context.GetValidatorOptions());
 }
 
-void TransformationAddDeadBreak::Apply(opt::IRContext* context,
-                                       FactManager* /*unused*/) const {
-  ApplyImpl(context);
+void TransformationAddDeadBreak::Apply(
+    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
+  ApplyImpl(ir_context);
   // Invalidate all analyses
-  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+  ir_context->InvalidateAnalysesExceptFor(
+      opt::IRContext::Analysis::kAnalysisNone);
 }
 
 protobufs::Transformation TransformationAddDeadBreak::ToMessage() const {
@@ -196,10 +199,10 @@
 }
 
 void TransformationAddDeadBreak::ApplyImpl(
-    spvtools::opt::IRContext* context) const {
+    spvtools::opt::IRContext* ir_context) const {
   fuzzerutil::AddUnreachableEdgeAndUpdateOpPhis(
-      context, context->cfg()->block(message_.from_block()),
-      context->cfg()->block(message_.to_block()),
+      ir_context, ir_context->cfg()->block(message_.from_block()),
+      ir_context->cfg()->block(message_.to_block()),
       message_.break_condition_value(), message_.phi_id());
 }
 
diff --git a/source/fuzz/transformation_add_dead_break.h b/source/fuzz/transformation_add_dead_break.h
index 81a2c99..0ea9210 100644
--- a/source/fuzz/transformation_add_dead_break.h
+++ b/source/fuzz/transformation_add_dead_break.h
@@ -17,9 +17,9 @@
 
 #include <vector>
 
-#include "source/fuzz/fact_manager.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 {
@@ -50,21 +50,23 @@
   //   maintain validity of the module.
   //   In particular, the new branch must not lead to violations of the rule
   //   that a use must be dominated by its definition.
-  bool IsApplicable(opt::IRContext* context,
-                    const FactManager& fact_manager) const override;
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
 
   // Replaces the terminator of a with a conditional branch to b or c.
   // The boolean constant associated with |message_.break_condition_value| is
   // used as the condition, and the order of b and c is arranged such that
   // control is guaranteed to jump to c.
-  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
 
   protobufs::Transformation ToMessage() const override;
 
  private:
   // Returns true if and only if adding an edge from |bb_from| to
   // |message_.to_block| respects structured control flow.
-  bool AddingBreakRespectsStructuredControlFlow(opt::IRContext* context,
+  bool AddingBreakRespectsStructuredControlFlow(opt::IRContext* ir_context,
                                                 opt::BasicBlock* bb_from) const;
 
   // Used by 'Apply' to actually apply the transformation to the module of
@@ -73,7 +75,7 @@
   // module.  This is only invoked by 'IsApplicable' after certain basic
   // applicability checks have been made, ensuring that the invocation of this
   // method is legal.
-  void ApplyImpl(opt::IRContext* context) const;
+  void ApplyImpl(opt::IRContext* ir_context) const;
 
   protobufs::TransformationAddDeadBreak message_;
 };
diff --git a/source/fuzz/transformation_add_dead_continue.cpp b/source/fuzz/transformation_add_dead_continue.cpp
index ffa182e..1fc6d67 100644
--- a/source/fuzz/transformation_add_dead_continue.cpp
+++ b/source/fuzz/transformation_add_dead_continue.cpp
@@ -34,11 +34,12 @@
 }
 
 bool TransformationAddDeadContinue::IsApplicable(
-    opt::IRContext* context, const FactManager& /*unused*/) const {
+    opt::IRContext* ir_context,
+    const TransformationContext& transformation_context) const {
   // First, we check that a constant with the same value as
   // |message_.continue_condition_value| is present.
   if (!fuzzerutil::MaybeGetBoolConstantId(
-          context, message_.continue_condition_value())) {
+          ir_context, message_.continue_condition_value())) {
     // The required constant is not present, so the transformation cannot be
     // applied.
     return false;
@@ -46,7 +47,7 @@
 
   // Check that |message_.from_block| really is a block id.
   opt::BasicBlock* bb_from =
-      fuzzerutil::MaybeFindBlock(context, message_.from_block());
+      fuzzerutil::MaybeFindBlock(ir_context, message_.from_block());
   if (bb_from == nullptr) {
     return false;
   }
@@ -68,31 +69,33 @@
   // Because the structured CFG analysis does not regard a loop header as part
   // of the loop it heads, we check first whether bb_from is a loop header
   // before using the structured CFG analysis.
-  auto loop_header = bb_from->IsLoopHeader()
-                         ? message_.from_block()
-                         : context->GetStructuredCFGAnalysis()->ContainingLoop(
-                               message_.from_block());
+  auto loop_header =
+      bb_from->IsLoopHeader()
+          ? message_.from_block()
+          : ir_context->GetStructuredCFGAnalysis()->ContainingLoop(
+                message_.from_block());
   if (!loop_header) {
     return false;
   }
 
-  auto continue_block = context->cfg()->block(loop_header)->ContinueBlockId();
+  auto continue_block =
+      ir_context->cfg()->block(loop_header)->ContinueBlockId();
 
   if (!fuzzerutil::BlockIsReachableInItsFunction(
-          context, context->cfg()->block(continue_block))) {
+          ir_context, ir_context->cfg()->block(continue_block))) {
     // If the loop's continue block is unreachable, we conservatively do not
     // allow adding a dead continue, to avoid the compilations that arise due to
     // the lack of sensible dominance information for unreachable blocks.
     return false;
   }
 
-  if (fuzzerutil::BlockIsInLoopContinueConstruct(context, message_.from_block(),
-                                                 loop_header)) {
+  if (fuzzerutil::BlockIsInLoopContinueConstruct(
+          ir_context, message_.from_block(), loop_header)) {
     // We cannot jump to the continue target from the continue construct.
     return false;
   }
 
-  if (context->GetStructuredCFGAnalysis()->IsMergeBlock(continue_block)) {
+  if (ir_context->GetStructuredCFGAnalysis()->IsMergeBlock(continue_block)) {
     // A branch straight to the continue target that is also a merge block might
     // break the property that a construct header must dominate its merge block
     // (if the merge block is reachable).
@@ -100,8 +103,8 @@
   }
 
   // Check whether the data passed to extend OpPhi instructions is appropriate.
-  if (!fuzzerutil::PhiIdsOkForNewEdge(context, bb_from,
-                                      context->cfg()->block(continue_block),
+  if (!fuzzerutil::PhiIdsOkForNewEdge(ir_context, bb_from,
+                                      ir_context->cfg()->block(continue_block),
                                       message_.phi_id())) {
     return false;
   }
@@ -112,19 +115,21 @@
   // clone, and check whether the transformed clone is valid.
   //
   // In principle some of the above checks could be removed, with more reliance
-  // being places on the validator.  This should be revisited if we are sure
+  // being placed on the validator.  This should be revisited if we are sure
   // the validator is complete with respect to checking structured control flow
   // rules.
-  auto cloned_context = fuzzerutil::CloneIRContext(context);
+  auto cloned_context = fuzzerutil::CloneIRContext(ir_context);
   ApplyImpl(cloned_context.get());
-  return fuzzerutil::IsValid(cloned_context.get());
+  return fuzzerutil::IsValid(cloned_context.get(),
+                             transformation_context.GetValidatorOptions());
 }
 
-void TransformationAddDeadContinue::Apply(opt::IRContext* context,
-                                          FactManager* /*unused*/) const {
-  ApplyImpl(context);
+void TransformationAddDeadContinue::Apply(
+    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
+  ApplyImpl(ir_context);
   // Invalidate all analyses
-  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+  ir_context->InvalidateAnalysesExceptFor(
+      opt::IRContext::Analysis::kAnalysisNone);
 }
 
 protobufs::Transformation TransformationAddDeadContinue::ToMessage() const {
@@ -134,16 +139,16 @@
 }
 
 void TransformationAddDeadContinue::ApplyImpl(
-    spvtools::opt::IRContext* context) const {
-  auto bb_from = context->cfg()->block(message_.from_block());
+    spvtools::opt::IRContext* ir_context) const {
+  auto bb_from = ir_context->cfg()->block(message_.from_block());
   auto continue_block =
       bb_from->IsLoopHeader()
           ? bb_from->ContinueBlockId()
-          : context->GetStructuredCFGAnalysis()->LoopContinueBlock(
+          : ir_context->GetStructuredCFGAnalysis()->LoopContinueBlock(
                 message_.from_block());
   assert(continue_block && "message_.from_block must be in a loop.");
   fuzzerutil::AddUnreachableEdgeAndUpdateOpPhis(
-      context, bb_from, context->cfg()->block(continue_block),
+      ir_context, bb_from, ir_context->cfg()->block(continue_block),
       message_.continue_condition_value(), message_.phi_id());
 }
 
diff --git a/source/fuzz/transformation_add_dead_continue.h b/source/fuzz/transformation_add_dead_continue.h
index 86b4c93..1053c16 100644
--- a/source/fuzz/transformation_add_dead_continue.h
+++ b/source/fuzz/transformation_add_dead_continue.h
@@ -17,9 +17,9 @@
 
 #include <vector>
 
-#include "source/fuzz/fact_manager.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 {
@@ -52,14 +52,16 @@
   //   In particular, adding an edge from somewhere in the loop to the continue
   //   target must not prevent uses of ids in the continue target from being
   //   dominated by the definitions of those ids.
-  bool IsApplicable(opt::IRContext* context,
-                    const FactManager& fact_manager) const override;
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
 
   // Replaces the terminator of a with a conditional branch to b or c.
   // The boolean constant associated with |message_.continue_condition_value| is
   // used as the condition, and the order of b and c is arranged such that
   // control is guaranteed to jump to c.
-  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
 
   protobufs::Transformation ToMessage() const override;
 
@@ -70,7 +72,7 @@
   // module.  This is only invoked by 'IsApplicable' after certain basic
   // applicability checks have been made, ensuring that the invocation of this
   // method is legal.
-  void ApplyImpl(opt::IRContext* context) const;
+  void ApplyImpl(opt::IRContext* ir_context) const;
 
   protobufs::TransformationAddDeadContinue message_;
 };
diff --git a/source/fuzz/transformation_add_function.cpp b/source/fuzz/transformation_add_function.cpp
index 5e53961..c990f23 100644
--- a/source/fuzz/transformation_add_function.cpp
+++ b/source/fuzz/transformation_add_function.cpp
@@ -29,37 +29,191 @@
   for (auto& instruction : instructions) {
     *message_.add_instruction() = instruction;
   }
+  message_.set_is_livesafe(false);
+}
+
+TransformationAddFunction::TransformationAddFunction(
+    const std::vector<protobufs::Instruction>& instructions,
+    uint32_t loop_limiter_variable_id, uint32_t loop_limit_constant_id,
+    const std::vector<protobufs::LoopLimiterInfo>& loop_limiters,
+    uint32_t kill_unreachable_return_value_id,
+    const std::vector<protobufs::AccessChainClampingInfo>&
+        access_chain_clampers) {
+  for (auto& instruction : instructions) {
+    *message_.add_instruction() = instruction;
+  }
+  message_.set_is_livesafe(true);
+  message_.set_loop_limiter_variable_id(loop_limiter_variable_id);
+  message_.set_loop_limit_constant_id(loop_limit_constant_id);
+  for (auto& loop_limiter : loop_limiters) {
+    *message_.add_loop_limiter_info() = loop_limiter;
+  }
+  message_.set_kill_unreachable_return_value_id(
+      kill_unreachable_return_value_id);
+  for (auto& access_clamper : access_chain_clampers) {
+    *message_.add_access_chain_clamping_info() = access_clamper;
+  }
 }
 
 bool TransformationAddFunction::IsApplicable(
-    opt::IRContext* context,
-    const spvtools::fuzz::FactManager& /*unused*/) const {
+    opt::IRContext* ir_context,
+    const TransformationContext& transformation_context) const {
+  // This transformation may use a lot of ids, all of which need to be fresh
+  // and distinct.  This set tracks them.
+  std::set<uint32_t> ids_used_by_this_transformation;
+
+  // Ensure that all result ids in the new function are fresh and distinct.
+  for (auto& instruction : message_.instruction()) {
+    if (instruction.result_id()) {
+      if (!CheckIdIsFreshAndNotUsedByThisTransformation(
+              instruction.result_id(), ir_context,
+              &ids_used_by_this_transformation)) {
+        return false;
+      }
+    }
+  }
+
+  if (message_.is_livesafe()) {
+    // Ensure that all ids provided for making the function livesafe are fresh
+    // and distinct.
+    if (!CheckIdIsFreshAndNotUsedByThisTransformation(
+            message_.loop_limiter_variable_id(), ir_context,
+            &ids_used_by_this_transformation)) {
+      return false;
+    }
+    for (auto& loop_limiter_info : message_.loop_limiter_info()) {
+      if (!CheckIdIsFreshAndNotUsedByThisTransformation(
+              loop_limiter_info.load_id(), ir_context,
+              &ids_used_by_this_transformation)) {
+        return false;
+      }
+      if (!CheckIdIsFreshAndNotUsedByThisTransformation(
+              loop_limiter_info.increment_id(), ir_context,
+              &ids_used_by_this_transformation)) {
+        return false;
+      }
+      if (!CheckIdIsFreshAndNotUsedByThisTransformation(
+              loop_limiter_info.compare_id(), ir_context,
+              &ids_used_by_this_transformation)) {
+        return false;
+      }
+      if (!CheckIdIsFreshAndNotUsedByThisTransformation(
+              loop_limiter_info.logical_op_id(), ir_context,
+              &ids_used_by_this_transformation)) {
+        return false;
+      }
+    }
+    for (auto& access_chain_clamping_info :
+         message_.access_chain_clamping_info()) {
+      for (auto& pair : access_chain_clamping_info.compare_and_select_ids()) {
+        if (!CheckIdIsFreshAndNotUsedByThisTransformation(
+                pair.first(), ir_context, &ids_used_by_this_transformation)) {
+          return false;
+        }
+        if (!CheckIdIsFreshAndNotUsedByThisTransformation(
+                pair.second(), ir_context, &ids_used_by_this_transformation)) {
+          return false;
+        }
+      }
+    }
+  }
+
   // Because checking all the conditions for a function to be valid is a big
   // job that the SPIR-V validator can already do, a "try it and see" approach
   // is taken here.
 
   // We first clone the current module, so that we can try adding the new
-  // function without risking wrecking |context|.
-  auto cloned_module = fuzzerutil::CloneIRContext(context);
+  // function without risking wrecking |ir_context|.
+  auto cloned_module = fuzzerutil::CloneIRContext(ir_context);
 
   // We try to add a function to the cloned module, which may fail if
   // |message_.instruction| is not sufficiently well-formed.
   if (!TryToAddFunction(cloned_module.get())) {
     return false;
   }
-  // Having managed to add the new function to the cloned module, we ascertain
-  // whether the cloned module is still valid.  If it is, the transformation is
-  // applicable.
-  return fuzzerutil::IsValid(cloned_module.get());
+
+  // 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())) {
+    return false;
+  }
+
+  if (message_.is_livesafe()) {
+    if (!TryToMakeFunctionLivesafe(cloned_module.get(),
+                                   transformation_context)) {
+      return false;
+    }
+    // After making the function livesafe, we check validity of the module
+    // again.  This is because the turning of OpKill, OpUnreachable and OpReturn
+    // instructions into branches changes control flow graph reachability, which
+    // has the potential to make the module invalid when it was otherwise valid.
+    // 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())) {
+      return false;
+    }
+  }
+  return true;
 }
 
 void TransformationAddFunction::Apply(
-    opt::IRContext* context, spvtools::fuzz::FactManager* /*unused*/) const {
-  auto success = TryToAddFunction(context);
+    opt::IRContext* ir_context,
+    TransformationContext* transformation_context) const {
+  // Add the function to the module.  As the transformation is applicable, this
+  // should succeed.
+  bool success = TryToAddFunction(ir_context);
   assert(success && "The function should be successfully added.");
   (void)(success);  // Keep release builds happy (otherwise they may complain
                     // that |success| is not used).
-  context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+
+  // 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
+  // parameters to other functions knowing that it is OK if they get
+  // over-written.
+  for (auto& instruction : message_.instruction()) {
+    switch (instruction.opcode()) {
+      case SpvOpFunctionParameter:
+        if (ir_context->get_def_use_mgr()
+                ->GetDef(instruction.result_type_id())
+                ->opcode() == SpvOpTypePointer) {
+          transformation_context->GetFactManager()
+              ->AddFactValueOfPointeeIsIrrelevant(instruction.result_id());
+        }
+        break;
+      case SpvOpVariable:
+        transformation_context->GetFactManager()
+            ->AddFactValueOfPointeeIsIrrelevant(instruction.result_id());
+        break;
+      default:
+        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 {
@@ -69,9 +223,9 @@
 }
 
 bool TransformationAddFunction::TryToAddFunction(
-    opt::IRContext* context) const {
+    opt::IRContext* ir_context) const {
   // This function returns false if |message_.instruction| was not well-formed
-  // enough to actually create a function and add it to |context|.
+  // enough to actually create a function and add it to |ir_context|.
 
   // A function must have at least some instructions.
   if (message_.instruction().empty()) {
@@ -86,7 +240,7 @@
 
   // Make a function, headed by the OpFunction instruction.
   std::unique_ptr<opt::Function> new_function = MakeUnique<opt::Function>(
-      InstructionFromMessage(context, function_begin));
+      InstructionFromMessage(ir_context, function_begin));
 
   // Keeps track of which instruction protobuf message we are currently
   // considering.
@@ -100,7 +254,7 @@
          message_.instruction(instruction_index).opcode() ==
              SpvOpFunctionParameter) {
     new_function->AddParameter(InstructionFromMessage(
-        context, message_.instruction(instruction_index)));
+        ir_context, message_.instruction(instruction_index)));
     instruction_index++;
   }
 
@@ -121,7 +275,7 @@
     // as its parent.
     std::unique_ptr<opt::BasicBlock> block =
         MakeUnique<opt::BasicBlock>(InstructionFromMessage(
-            context, message_.instruction(instruction_index)));
+            ir_context, message_.instruction(instruction_index)));
     block->SetParent(new_function.get());
 
     // Consider successive instructions until we hit another label or the end
@@ -132,7 +286,7 @@
                SpvOpFunctionEnd &&
            message_.instruction(instruction_index).opcode() != SpvOpLabel) {
       block->AddInstruction(InstructionFromMessage(
-          context, message_.instruction(instruction_index)));
+          ir_context, message_.instruction(instruction_index)));
       instruction_index++;
     }
     // Add the block to the new function.
@@ -146,11 +300,649 @@
   }
   // Set the function's final instruction, add the function to the module and
   // report success.
-  new_function->SetFunctionEnd(
-      InstructionFromMessage(context, message_.instruction(instruction_index)));
-  context->AddFunction(std::move(new_function));
+  new_function->SetFunctionEnd(InstructionFromMessage(
+      ir_context, message_.instruction(instruction_index)));
+  ir_context->AddFunction(std::move(new_function));
+
+  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+
   return true;
 }
 
+bool TransformationAddFunction::TryToMakeFunctionLivesafe(
+    opt::IRContext* ir_context,
+    const TransformationContext& transformation_context) const {
+  assert(message_.is_livesafe() && "Precondition: is_livesafe must hold.");
+
+  // Get a pointer to the added function.
+  opt::Function* added_function = nullptr;
+  for (auto& function : *ir_context->module()) {
+    if (function.result_id() == message_.instruction(0).result_id()) {
+      added_function = &function;
+      break;
+    }
+  }
+  assert(added_function && "The added function should have been found.");
+
+  if (!TryToAddLoopLimiters(ir_context, added_function)) {
+    // Adding loop limiters did not work; bail out.
+    return false;
+  }
+
+  // Consider all the instructions in the function, and:
+  // - attempt to replace OpKill and OpUnreachable with return instructions
+  // - attempt to clamp access chains to be within bounds
+  // - check that OpFunctionCall instructions are only to livesafe functions
+  for (auto& block : *added_function) {
+    for (auto& inst : block) {
+      switch (inst.opcode()) {
+        case SpvOpKill:
+        case SpvOpUnreachable:
+          if (!TryToTurnKillOrUnreachableIntoReturn(ir_context, added_function,
+                                                    &inst)) {
+            return false;
+          }
+          break;
+        case SpvOpAccessChain:
+        case SpvOpInBoundsAccessChain:
+          if (!TryToClampAccessChainIndices(ir_context, &inst)) {
+            return false;
+          }
+          break;
+        case SpvOpFunctionCall:
+          // A livesafe function my only call other livesafe functions.
+          if (!transformation_context.GetFactManager()->FunctionIsLivesafe(
+                  inst.GetSingleWordInOperand(0))) {
+            return false;
+          }
+        default:
+          break;
+      }
+    }
+  }
+  return true;
+}
+
+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
+  // limiting logic.
+  std::vector<opt::BasicBlock*> loop_headers;
+  for (auto& block : *added_function) {
+    if (block.IsLoopHeader()) {
+      loop_headers.push_back(&block);
+    }
+  }
+
+  if (loop_headers.empty()) {
+    // There are no loops, so no need to add any loop limiters.
+    return true;
+  }
+
+  // Check that the module contains appropriate ingredients for declaring and
+  // manipulating a loop limiter.
+
+  auto loop_limit_constant_id_instr =
+      ir_context->get_def_use_mgr()->GetDef(message_.loop_limit_constant_id());
+  if (!loop_limit_constant_id_instr ||
+      loop_limit_constant_id_instr->opcode() != SpvOpConstant) {
+    // The loop limit constant id instruction must exist and have an
+    // appropriate opcode.
+    return false;
+  }
+
+  auto loop_limit_type = ir_context->get_def_use_mgr()->GetDef(
+      loop_limit_constant_id_instr->type_id());
+  if (loop_limit_type->opcode() != SpvOpTypeInt ||
+      loop_limit_type->GetSingleWordInOperand(0) != 32) {
+    // The type of the loop limit constant must be 32-bit integer.  It
+    // doesn't actually matter whether the integer is signed or not.
+    return false;
+  }
+
+  // Find the id of the "unsigned int" type.
+  opt::analysis::Integer unsigned_int_type(32, false);
+  uint32_t unsigned_int_type_id =
+      ir_context->get_type_mgr()->GetId(&unsigned_int_type);
+  if (!unsigned_int_type_id) {
+    // Unsigned int is not available; we need this type in order to add loop
+    // limiters.
+    return false;
+  }
+  auto registered_unsigned_int_type =
+      ir_context->get_type_mgr()->GetRegisteredType(&unsigned_int_type);
+
+  // Look for 0 of type unsigned int.
+  opt::analysis::IntConstant zero(registered_unsigned_int_type->AsInteger(),
+                                  {0});
+  auto registered_zero = ir_context->get_constant_mgr()->FindConstant(&zero);
+  if (!registered_zero) {
+    // We need 0 in order to be able to initialize loop limiters.
+    return false;
+  }
+  uint32_t zero_id = ir_context->get_constant_mgr()
+                         ->GetDefiningInstruction(registered_zero)
+                         ->result_id();
+
+  // Look for 1 of type unsigned int.
+  opt::analysis::IntConstant one(registered_unsigned_int_type->AsInteger(),
+                                 {1});
+  auto registered_one = ir_context->get_constant_mgr()->FindConstant(&one);
+  if (!registered_one) {
+    // We need 1 in order to be able to increment loop limiters.
+    return false;
+  }
+  uint32_t one_id = ir_context->get_constant_mgr()
+                        ->GetDefiningInstruction(registered_one)
+                        ->result_id();
+
+  // Look for pointer-to-unsigned int type.
+  opt::analysis::Pointer pointer_to_unsigned_int_type(
+      registered_unsigned_int_type, SpvStorageClassFunction);
+  uint32_t pointer_to_unsigned_int_type_id =
+      ir_context->get_type_mgr()->GetId(&pointer_to_unsigned_int_type);
+  if (!pointer_to_unsigned_int_type_id) {
+    // We need pointer-to-unsigned int in order to declare the loop limiter
+    // variable.
+    return false;
+  }
+
+  // Look for bool type.
+  opt::analysis::Bool bool_type;
+  uint32_t bool_type_id = ir_context->get_type_mgr()->GetId(&bool_type);
+  if (!bool_type_id) {
+    // We need bool in order to compare the loop limiter's value with the loop
+    // limit constant.
+    return false;
+  }
+
+  // Declare the loop limiter variable at the start of the function's entry
+  // block, via an instruction of the form:
+  //   %loop_limiter_var = SpvOpVariable %ptr_to_uint Function %zero
+  added_function->begin()->begin()->InsertBefore(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpVariable, pointer_to_unsigned_int_type_id,
+      message_.loop_limiter_variable_id(),
+      opt::Instruction::OperandList(
+          {{SPV_OPERAND_TYPE_STORAGE_CLASS, {SpvStorageClassFunction}},
+           {SPV_OPERAND_TYPE_ID, {zero_id}}})));
+  // Update the module's id bound since we have added the loop limiter
+  // variable id.
+  fuzzerutil::UpdateModuleIdBound(ir_context,
+                                  message_.loop_limiter_variable_id());
+
+  // Consider each loop in turn.
+  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;
+      }
+    }
+    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);
+
+    // Go through the sequence of loop limiter infos and find the one
+    // corresponding to this loop.
+    bool found = false;
+    protobufs::LoopLimiterInfo loop_limiter_info;
+    for (auto& info : message_.loop_limiter_info()) {
+      if (info.loop_header_id() == loop_header->id()) {
+        loop_limiter_info = info;
+        found = true;
+        break;
+      }
+    }
+    if (!found) {
+      // We don't have loop limiter info for this loop header.
+      return false;
+    }
+
+    // The back-edge block either has the form:
+    //
+    // (1)
+    //
+    // %l = OpLabel
+    //      ... instructions ...
+    //      OpBranch %loop_header
+    //
+    // (2)
+    //
+    // %l = OpLabel
+    //      ... instructions ...
+    //      OpBranchConditional %c %loop_header %loop_merge
+    //
+    // (3)
+    //
+    // %l = OpLabel
+    //      ... instructions ...
+    //      OpBranchConditional %c %loop_merge %loop_header
+    //
+    // We turn these into the following:
+    //
+    // (1)
+    //
+    //  %l = OpLabel
+    //       ... instructions ...
+    // %t1 = OpLoad %uint32 %loop_limiter
+    // %t2 = OpIAdd %uint32 %t1 %one
+    //       OpStore %loop_limiter %t2
+    // %t3 = OpUGreaterThanEqual %bool %t1 %loop_limit
+    //       OpBranchConditional %t3 %loop_merge %loop_header
+    //
+    // (2)
+    //
+    //  %l = OpLabel
+    //       ... instructions ...
+    // %t1 = OpLoad %uint32 %loop_limiter
+    // %t2 = OpIAdd %uint32 %t1 %one
+    //       OpStore %loop_limiter %t2
+    // %t3 = OpULessThan %bool %t1 %loop_limit
+    // %t4 = OpLogicalAnd %bool %c %t3
+    //       OpBranchConditional %t4 %loop_header %loop_merge
+    //
+    // (3)
+    //
+    //  %l = OpLabel
+    //       ... instructions ...
+    // %t1 = OpLoad %uint32 %loop_limiter
+    // %t2 = OpIAdd %uint32 %t1 %one
+    //       OpStore %loop_limiter %t2
+    // %t3 = OpUGreaterThanEqual %bool %t1 %loop_limit
+    // %t4 = OpLogicalOr %bool %c %t3
+    //       OpBranchConditional %t4 %loop_merge %loop_header
+
+    auto back_edge_block_terminator = back_edge_block->terminator();
+    bool compare_using_greater_than_equal;
+    if (back_edge_block_terminator->opcode() == SpvOpBranch) {
+      compare_using_greater_than_equal = true;
+    } else {
+      assert(back_edge_block_terminator->opcode() == SpvOpBranchConditional);
+      assert(((back_edge_block_terminator->GetSingleWordInOperand(1) ==
+                   loop_header->id() &&
+               back_edge_block_terminator->GetSingleWordInOperand(2) ==
+                   loop_header->MergeBlockId()) ||
+              (back_edge_block_terminator->GetSingleWordInOperand(2) ==
+                   loop_header->id() &&
+               back_edge_block_terminator->GetSingleWordInOperand(1) ==
+                   loop_header->MergeBlockId())) &&
+             "A back edge edge block must branch to"
+             " either the loop header or merge");
+      compare_using_greater_than_equal =
+          back_edge_block_terminator->GetSingleWordInOperand(1) ==
+          loop_header->MergeBlockId();
+    }
+
+    std::vector<std::unique_ptr<opt::Instruction>> new_instructions;
+
+    // Add a load from the loop limiter variable, of the form:
+    //   %t1 = OpLoad %uint32 %loop_limiter
+    new_instructions.push_back(MakeUnique<opt::Instruction>(
+        ir_context, SpvOpLoad, unsigned_int_type_id,
+        loop_limiter_info.load_id(),
+        opt::Instruction::OperandList(
+            {{SPV_OPERAND_TYPE_ID, {message_.loop_limiter_variable_id()}}})));
+
+    // Increment the loaded value:
+    //   %t2 = OpIAdd %uint32 %t1 %one
+    new_instructions.push_back(MakeUnique<opt::Instruction>(
+        ir_context, SpvOpIAdd, unsigned_int_type_id,
+        loop_limiter_info.increment_id(),
+        opt::Instruction::OperandList(
+            {{SPV_OPERAND_TYPE_ID, {loop_limiter_info.load_id()}},
+             {SPV_OPERAND_TYPE_ID, {one_id}}})));
+
+    // Store the incremented value back to the loop limiter variable:
+    //   OpStore %loop_limiter %t2
+    new_instructions.push_back(MakeUnique<opt::Instruction>(
+        ir_context, SpvOpStore, 0, 0,
+        opt::Instruction::OperandList(
+            {{SPV_OPERAND_TYPE_ID, {message_.loop_limiter_variable_id()}},
+             {SPV_OPERAND_TYPE_ID, {loop_limiter_info.increment_id()}}})));
+
+    // Compare the loaded value with the loop limit; either:
+    //   %t3 = OpUGreaterThanEqual %bool %t1 %loop_limit
+    // or
+    //   %t3 = OpULessThan %bool %t1 %loop_limit
+    new_instructions.push_back(MakeUnique<opt::Instruction>(
+        ir_context,
+        compare_using_greater_than_equal ? SpvOpUGreaterThanEqual
+                                         : SpvOpULessThan,
+        bool_type_id, loop_limiter_info.compare_id(),
+        opt::Instruction::OperandList(
+            {{SPV_OPERAND_TYPE_ID, {loop_limiter_info.load_id()}},
+             {SPV_OPERAND_TYPE_ID, {message_.loop_limit_constant_id()}}})));
+
+    if (back_edge_block_terminator->opcode() == SpvOpBranchConditional) {
+      new_instructions.push_back(MakeUnique<opt::Instruction>(
+          ir_context,
+          compare_using_greater_than_equal ? SpvOpLogicalOr : SpvOpLogicalAnd,
+          bool_type_id, loop_limiter_info.logical_op_id(),
+          opt::Instruction::OperandList(
+              {{SPV_OPERAND_TYPE_ID,
+                {back_edge_block_terminator->GetSingleWordInOperand(0)}},
+               {SPV_OPERAND_TYPE_ID, {loop_limiter_info.compare_id()}}})));
+    }
+
+    // Add the new instructions at the end of the back edge block, before the
+    // terminator and any loop merge instruction (as the back edge block can
+    // be the loop header).
+    if (back_edge_block->GetLoopMergeInst()) {
+      back_edge_block->GetLoopMergeInst()->InsertBefore(
+          std::move(new_instructions));
+    } else {
+      back_edge_block_terminator->InsertBefore(std::move(new_instructions));
+    }
+
+    if (back_edge_block_terminator->opcode() == SpvOpBranchConditional) {
+      back_edge_block_terminator->SetInOperand(
+          0, {loop_limiter_info.logical_op_id()});
+    } else {
+      assert(back_edge_block_terminator->opcode() == SpvOpBranch &&
+             "Back-edge terminator must be OpBranch or OpBranchConditional");
+
+      // Check that, if the merge block starts with OpPhi instructions, suitable
+      // ids have been provided to give these instructions a value corresponding
+      // to the new incoming edge from the back edge block.
+      auto merge_block = ir_context->cfg()->block(loop_header->MergeBlockId());
+      if (!fuzzerutil::PhiIdsOkForNewEdge(ir_context, back_edge_block,
+                                          merge_block,
+                                          loop_limiter_info.phi_id())) {
+        return false;
+      }
+
+      // Augment OpPhi instructions at the loop merge with the given ids.
+      uint32_t phi_index = 0;
+      for (auto& inst : *merge_block) {
+        if (inst.opcode() != SpvOpPhi) {
+          break;
+        }
+        assert(phi_index <
+                   static_cast<uint32_t>(loop_limiter_info.phi_id().size()) &&
+               "There should be at least one phi id per OpPhi instruction.");
+        inst.AddOperand(
+            {SPV_OPERAND_TYPE_ID, {loop_limiter_info.phi_id(phi_index)}});
+        inst.AddOperand({SPV_OPERAND_TYPE_ID, {back_edge_block_id}});
+        phi_index++;
+      }
+
+      // 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->id()}}}));
+    }
+
+    // Update the module's id bound with respect to the various ids that
+    // have been used for loop limiter manipulation.
+    fuzzerutil::UpdateModuleIdBound(ir_context, loop_limiter_info.load_id());
+    fuzzerutil::UpdateModuleIdBound(ir_context,
+                                    loop_limiter_info.increment_id());
+    fuzzerutil::UpdateModuleIdBound(ir_context, loop_limiter_info.compare_id());
+    fuzzerutil::UpdateModuleIdBound(ir_context,
+                                    loop_limiter_info.logical_op_id());
+  }
+  return true;
+}
+
+bool TransformationAddFunction::TryToTurnKillOrUnreachableIntoReturn(
+    opt::IRContext* ir_context, opt::Function* added_function,
+    opt::Instruction* kill_or_unreachable_inst) const {
+  assert((kill_or_unreachable_inst->opcode() == SpvOpKill ||
+          kill_or_unreachable_inst->opcode() == SpvOpUnreachable) &&
+         "Precondition: instruction must be OpKill or OpUnreachable.");
+
+  // Get the function's return type.
+  auto function_return_type_inst =
+      ir_context->get_def_use_mgr()->GetDef(added_function->type_id());
+
+  if (function_return_type_inst->opcode() == SpvOpTypeVoid) {
+    // The function has void return type, so change this instruction to
+    // OpReturn.
+    kill_or_unreachable_inst->SetOpcode(SpvOpReturn);
+  } else {
+    // The function has non-void return type, so change this instruction
+    // to OpReturnValue, using the value id provided with the
+    // transformation.
+
+    // We first check that the id, %id, provided with the transformation
+    // specifically to turn OpKill and OpUnreachable instructions into
+    // OpReturnValue %id has the same type as the function's return type.
+    if (ir_context->get_def_use_mgr()
+            ->GetDef(message_.kill_unreachable_return_value_id())
+            ->type_id() != function_return_type_inst->result_id()) {
+      return false;
+    }
+    kill_or_unreachable_inst->SetOpcode(SpvOpReturnValue);
+    kill_or_unreachable_inst->SetInOperands(
+        {{SPV_OPERAND_TYPE_ID, {message_.kill_unreachable_return_value_id()}}});
+  }
+  return true;
+}
+
+bool TransformationAddFunction::TryToClampAccessChainIndices(
+    opt::IRContext* ir_context, opt::Instruction* access_chain_inst) const {
+  assert((access_chain_inst->opcode() == SpvOpAccessChain ||
+          access_chain_inst->opcode() == SpvOpInBoundsAccessChain) &&
+         "Precondition: instruction must be OpAccessChain or "
+         "OpInBoundsAccessChain.");
+
+  // Find the AccessChainClampingInfo associated with this access chain.
+  const protobufs::AccessChainClampingInfo* access_chain_clamping_info =
+      nullptr;
+  for (auto& clamping_info : message_.access_chain_clamping_info()) {
+    if (clamping_info.access_chain_id() == access_chain_inst->result_id()) {
+      access_chain_clamping_info = &clamping_info;
+      break;
+    }
+  }
+  if (!access_chain_clamping_info) {
+    // No access chain clamping information was found; the function cannot be
+    // made livesafe.
+    return false;
+  }
+
+  // Check that there is a (compare_id, select_id) pair for every
+  // index associated with the instruction.
+  if (static_cast<uint32_t>(
+          access_chain_clamping_info->compare_and_select_ids().size()) !=
+      access_chain_inst->NumInOperands() - 1) {
+    return false;
+  }
+
+  // Walk the access chain, clamping each index to be within bounds if it is
+  // not a constant.
+  auto base_object = ir_context->get_def_use_mgr()->GetDef(
+      access_chain_inst->GetSingleWordInOperand(0));
+  assert(base_object && "The base object must exist.");
+  auto pointer_type =
+      ir_context->get_def_use_mgr()->GetDef(base_object->type_id());
+  assert(pointer_type && pointer_type->opcode() == SpvOpTypePointer &&
+         "The base object must have pointer type.");
+  auto should_be_composite_type = ir_context->get_def_use_mgr()->GetDef(
+      pointer_type->GetSingleWordInOperand(1));
+
+  // Consider each index input operand in turn (operand 0 is the base object).
+  for (uint32_t index = 1; index < access_chain_inst->NumInOperands();
+       index++) {
+    // We are going to turn:
+    //
+    // %result = OpAccessChain %type %object ... %index ...
+    //
+    // into:
+    //
+    // %t1 = OpULessThanEqual %bool %index %bound_minus_one
+    // %t2 = OpSelect %int_type %t1 %index %bound_minus_one
+    // %result = OpAccessChain %type %object ... %t2 ...
+    //
+    // ... unless %index is already a constant.
+
+    // Get the bound for the composite being indexed into; e.g. the number of
+    // columns of matrix or the size of an array.
+    uint32_t bound =
+        GetBoundForCompositeIndex(ir_context, *should_be_composite_type);
+
+    // Get the instruction associated with the index and figure out its integer
+    // type.
+    const uint32_t index_id = access_chain_inst->GetSingleWordInOperand(index);
+    auto index_inst = ir_context->get_def_use_mgr()->GetDef(index_id);
+    auto index_type_inst =
+        ir_context->get_def_use_mgr()->GetDef(index_inst->type_id());
+    assert(index_type_inst->opcode() == SpvOpTypeInt);
+    assert(index_type_inst->GetSingleWordInOperand(0) == 32);
+    opt::analysis::Integer* index_int_type =
+        ir_context->get_type_mgr()
+            ->GetType(index_type_inst->result_id())
+            ->AsInteger();
+
+    if (index_inst->opcode() != SpvOpConstant) {
+      // The index is non-constant so we need to clamp it.
+      assert(should_be_composite_type->opcode() != SpvOpTypeStruct &&
+             "Access chain indices into structures are required to be "
+             "constants.");
+      opt::analysis::IntConstant bound_minus_one(index_int_type, {bound - 1});
+      if (!ir_context->get_constant_mgr()->FindConstant(&bound_minus_one)) {
+        // We do not have an integer constant whose value is |bound| -1.
+        return false;
+      }
+
+      opt::analysis::Bool bool_type;
+      uint32_t bool_type_id = ir_context->get_type_mgr()->GetId(&bool_type);
+      if (!bool_type_id) {
+        // Bool type is not declared; we cannot do a comparison.
+        return false;
+      }
+
+      uint32_t bound_minus_one_id =
+          ir_context->get_constant_mgr()
+              ->GetDefiningInstruction(&bound_minus_one)
+              ->result_id();
+
+      uint32_t compare_id =
+          access_chain_clamping_info->compare_and_select_ids(index - 1).first();
+      uint32_t select_id =
+          access_chain_clamping_info->compare_and_select_ids(index - 1)
+              .second();
+      std::vector<std::unique_ptr<opt::Instruction>> new_instructions;
+
+      // Compare the index with the bound via an instruction of the form:
+      //   %t1 = OpULessThanEqual %bool %index %bound_minus_one
+      new_instructions.push_back(MakeUnique<opt::Instruction>(
+          ir_context, SpvOpULessThanEqual, bool_type_id, compare_id,
+          opt::Instruction::OperandList(
+              {{SPV_OPERAND_TYPE_ID, {index_inst->result_id()}},
+               {SPV_OPERAND_TYPE_ID, {bound_minus_one_id}}})));
+
+      // Select the index if in-bounds, otherwise one less than the bound:
+      //   %t2 = OpSelect %int_type %t1 %index %bound_minus_one
+      new_instructions.push_back(MakeUnique<opt::Instruction>(
+          ir_context, SpvOpSelect, index_type_inst->result_id(), select_id,
+          opt::Instruction::OperandList(
+              {{SPV_OPERAND_TYPE_ID, {compare_id}},
+               {SPV_OPERAND_TYPE_ID, {index_inst->result_id()}},
+               {SPV_OPERAND_TYPE_ID, {bound_minus_one_id}}})));
+
+      // Add the new instructions before the access chain
+      access_chain_inst->InsertBefore(std::move(new_instructions));
+
+      // Replace %index with %t2.
+      access_chain_inst->SetInOperand(index, {select_id});
+      fuzzerutil::UpdateModuleIdBound(ir_context, compare_id);
+      fuzzerutil::UpdateModuleIdBound(ir_context, select_id);
+    } else {
+      // TODO(afd): At present the SPIR-V spec is not clear on whether
+      //  statically out-of-bounds indices mean that a module is invalid (so
+      //  that it should be rejected by the validator), or that such accesses
+      //  yield undefined results.  Via the following assertion, we assume that
+      //  functions added to the module do not feature statically out-of-bounds
+      //  accesses.
+      // Assert that the index is smaller (unsigned) than this value.
+      // Return false if it is not (to keep compilers happy).
+      if (index_inst->GetSingleWordInOperand(0) >= bound) {
+        assert(false &&
+               "The function has a statically out-of-bounds access; "
+               "this should not occur.");
+        return false;
+      }
+    }
+    should_be_composite_type =
+        FollowCompositeIndex(ir_context, *should_be_composite_type, index_id);
+  }
+  return true;
+}
+
+uint32_t TransformationAddFunction::GetBoundForCompositeIndex(
+    opt::IRContext* ir_context, const opt::Instruction& composite_type_inst) {
+  switch (composite_type_inst.opcode()) {
+    case SpvOpTypeArray:
+      return fuzzerutil::GetArraySize(composite_type_inst, ir_context);
+    case SpvOpTypeMatrix:
+    case SpvOpTypeVector:
+      return composite_type_inst.GetSingleWordInOperand(1);
+    case SpvOpTypeStruct: {
+      return fuzzerutil::GetNumberOfStructMembers(composite_type_inst);
+    }
+    case SpvOpTypeRuntimeArray:
+      assert(false &&
+             "GetBoundForCompositeIndex should not be invoked with an "
+             "OpTypeRuntimeArray, which does not have a static bound.");
+      return 0;
+    default:
+      assert(false && "Unknown composite type.");
+      return 0;
+  }
+}
+
+opt::Instruction* TransformationAddFunction::FollowCompositeIndex(
+    opt::IRContext* ir_context, const opt::Instruction& composite_type_inst,
+    uint32_t index_id) {
+  uint32_t sub_object_type_id;
+  switch (composite_type_inst.opcode()) {
+    case SpvOpTypeArray:
+    case SpvOpTypeRuntimeArray:
+      sub_object_type_id = composite_type_inst.GetSingleWordInOperand(0);
+      break;
+    case SpvOpTypeMatrix:
+    case SpvOpTypeVector:
+      sub_object_type_id = composite_type_inst.GetSingleWordInOperand(0);
+      break;
+    case SpvOpTypeStruct: {
+      auto index_inst = ir_context->get_def_use_mgr()->GetDef(index_id);
+      assert(index_inst->opcode() == SpvOpConstant);
+      assert(ir_context->get_def_use_mgr()
+                 ->GetDef(index_inst->type_id())
+                 ->opcode() == SpvOpTypeInt);
+      assert(ir_context->get_def_use_mgr()
+                 ->GetDef(index_inst->type_id())
+                 ->GetSingleWordInOperand(0) == 32);
+      uint32_t index_value = index_inst->GetSingleWordInOperand(0);
+      sub_object_type_id =
+          composite_type_inst.GetSingleWordInOperand(index_value);
+      break;
+    }
+    default:
+      assert(false && "Unknown composite type.");
+      sub_object_type_id = 0;
+      break;
+  }
+  assert(sub_object_type_id && "No sub-object found.");
+  return ir_context->get_def_use_mgr()->GetDef(sub_object_type_id);
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_add_function.h b/source/fuzz/transformation_add_function.h
index fee2732..5af197b 100644
--- a/source/fuzz/transformation_add_function.h
+++ b/source/fuzz/transformation_add_function.h
@@ -15,9 +15,9 @@
 #ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_FUNCTION_H_
 #define SOURCE_FUZZ_TRANSFORMATION_ADD_FUNCTION_H_
 
-#include "source/fuzz/fact_manager.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 {
@@ -28,29 +28,61 @@
   explicit TransformationAddFunction(
       const protobufs::TransformationAddFunction& message);
 
+  // Creates a transformation to add a non live-safe function.
   explicit TransformationAddFunction(
       const std::vector<protobufs::Instruction>& instructions);
 
+  // Creates a transformation to add a live-safe function.
+  TransformationAddFunction(
+      const std::vector<protobufs::Instruction>& instructions,
+      uint32_t loop_limiter_variable_id, uint32_t loop_limit_constant_id,
+      const std::vector<protobufs::LoopLimiterInfo>& loop_limiters,
+      uint32_t kill_unreachable_return_value_id,
+      const std::vector<protobufs::AccessChainClampingInfo>&
+          access_chain_clampers);
+
   // - |message_.instruction| must correspond to a sufficiently well-formed
   //   sequence of instructions that a function can be created from them
+  // - If |message_.is_livesafe| holds then |message_| must contain suitable
+  //   ingredients to make the function livesafe, and the function must only
+  //   invoke other livesafe functions
   // - Adding the created function to the module must lead to a valid module.
-  bool IsApplicable(opt::IRContext* context,
-                    const FactManager& fact_manager) const override;
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
 
-  // Adds the function defined by |message_.instruction| to the module
-  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+  // Adds the function defined by |message_.instruction| to the module, making
+  // it livesafe if |message_.is_livesafe| holds.
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
 
   protobufs::Transformation ToMessage() const override;
 
+  // Helper method that returns the bound for indexing into a composite of type
+  // |composite_type_inst|, i.e. the number of fields of a struct, the size of
+  // an array, the number of components of a vector, or the number of columns of
+  // a matrix.
+  static uint32_t GetBoundForCompositeIndex(
+      opt::IRContext* ir_context, const opt::Instruction& composite_type_inst);
+
+  // Helper method that, given composite type |composite_type_inst|, returns the
+  // type of the sub-object at index |index_id|, which is required to be in-
+  // bounds.
+  static opt::Instruction* FollowCompositeIndex(
+      opt::IRContext* ir_context, const opt::Instruction& composite_type_inst,
+      uint32_t index_id);
+
  private:
   // Attempts to create a function from the series of instructions in
-  // |message_.instruction| and add it to |context|.  Returns false if this is
-  // not possible due to the messages not respecting the basic structure of a
-  // function, e.g. if there is no OpFunction instruction or no blocks; in this
-  // case |context| is left in an indeterminate state.
+  // |message_.instruction| and add it to |ir_context|.
   //
-  // Otherwise returns true.  Whether |context| is valid after addition of the
-  // function depends on the contents of |message_.instruction|.
+  // Returns false if adding the function is not possible due to the messages
+  // not respecting the basic structure of a function, e.g. if there is no
+  // OpFunction instruction or no blocks; in this case |ir_context| is left in
+  // an indeterminate state.
+  //
+  // Otherwise returns true.  Whether |ir_context| is valid after addition of
+  // the function depends on the contents of |message_.instruction|.
   //
   // Intended usage:
   // - Perform a dry run of this method on a clone of a module, and use
@@ -59,7 +91,32 @@
   //   added, or leads to an invalid module.
   // - If the dry run succeeds, run the method on the real module of interest,
   //   to add the function.
-  bool TryToAddFunction(opt::IRContext* context) const;
+  bool TryToAddFunction(opt::IRContext* ir_context) const;
+
+  // 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|
+  // not containing sufficient ingredients (such as types and fresh ids) to add
+  // the instrumentation necessary to make the function livesafe.
+  bool TryToMakeFunctionLivesafe(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const;
+
+  // A helper for TryToMakeFunctionLivesafe that tries to add loop-limiting
+  // logic.
+  bool TryToAddLoopLimiters(opt::IRContext* ir_context,
+                            opt::Function* added_function) const;
+
+  // A helper for TryToMakeFunctionLivesafe that tries to replace OpKill and
+  // OpUnreachable instructions into return instructions.
+  bool TryToTurnKillOrUnreachableIntoReturn(
+      opt::IRContext* ir_context, opt::Function* added_function,
+      opt::Instruction* kill_or_unreachable_inst) const;
+
+  // A helper for TryToMakeFunctionLivesafe that tries to clamp access chain
+  // indices so that they are guaranteed to be in-bounds.
+  bool TryToClampAccessChainIndices(opt::IRContext* ir_context,
+                                    opt::Instruction* access_chain_inst) const;
 
   protobufs::TransformationAddFunction message_;
 };
diff --git a/source/fuzz/transformation_add_global_undef.cpp b/source/fuzz/transformation_add_global_undef.cpp
index f9585b3..ba45f22 100644
--- a/source/fuzz/transformation_add_global_undef.cpp
+++ b/source/fuzz/transformation_add_global_undef.cpp
@@ -30,26 +30,26 @@
 }
 
 bool TransformationAddGlobalUndef::IsApplicable(
-    opt::IRContext* context,
-    const spvtools::fuzz::FactManager& /*unused*/) const {
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
   // A fresh id is required.
-  if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) {
+  if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
     return false;
   }
-  auto type = context->get_type_mgr()->GetType(message_.type_id());
+  auto type = ir_context->get_type_mgr()->GetType(message_.type_id());
   // The type must exist, and must not be a function type.
   return type && !type->AsFunction();
 }
 
 void TransformationAddGlobalUndef::Apply(
-    opt::IRContext* context, spvtools::fuzz::FactManager* /*unused*/) const {
-  context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
-      context, SpvOpUndef, message_.type_id(), message_.fresh_id(),
+    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
+  ir_context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpUndef, message_.type_id(), message_.fresh_id(),
       opt::Instruction::OperandList()));
-  fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
   // We have added an instruction to the module, so need to be careful about the
   // validity of existing analyses.
-  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+  ir_context->InvalidateAnalysesExceptFor(
+      opt::IRContext::Analysis::kAnalysisNone);
 }
 
 protobufs::Transformation TransformationAddGlobalUndef::ToMessage() const {
diff --git a/source/fuzz/transformation_add_global_undef.h b/source/fuzz/transformation_add_global_undef.h
index 550d9f6..c89fe9d 100644
--- a/source/fuzz/transformation_add_global_undef.h
+++ b/source/fuzz/transformation_add_global_undef.h
@@ -15,9 +15,9 @@
 #ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_GLOBAL_UNDEF_H_
 #define SOURCE_FUZZ_TRANSFORMATION_ADD_GLOBAL_UNDEF_H_
 
-#include "source/fuzz/fact_manager.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 {
@@ -32,12 +32,14 @@
 
   // - |message_.fresh_id| must be fresh
   // - |message_.type_id| must be the id of a non-function type
-  bool IsApplicable(opt::IRContext* context,
-                    const FactManager& fact_manager) const override;
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
 
   // Adds an OpUndef instruction to the module, with |message_.type_id| as its
   // type.  The instruction has result id |message_.fresh_id|.
-  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
 
   protobufs::Transformation ToMessage() const override;
 
diff --git a/source/fuzz/transformation_add_global_variable.cpp b/source/fuzz/transformation_add_global_variable.cpp
index cea268c..6464bfb 100644
--- a/source/fuzz/transformation_add_global_variable.cpp
+++ b/source/fuzz/transformation_add_global_variable.cpp
@@ -24,21 +24,34 @@
     : message_(message) {}
 
 TransformationAddGlobalVariable::TransformationAddGlobalVariable(
-    uint32_t fresh_id, uint32_t type_id, uint32_t initializer_id) {
+    uint32_t fresh_id, uint32_t type_id, SpvStorageClass storage_class,
+    uint32_t initializer_id, bool value_is_irrelevant) {
   message_.set_fresh_id(fresh_id);
   message_.set_type_id(type_id);
+  message_.set_storage_class(storage_class);
   message_.set_initializer_id(initializer_id);
+  message_.set_value_is_irrelevant(value_is_irrelevant);
 }
 
 bool TransformationAddGlobalVariable::IsApplicable(
-    opt::IRContext* context,
-    const spvtools::fuzz::FactManager& /*unused*/) const {
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
   // The result id must be fresh.
-  if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) {
+  if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
     return false;
   }
+
+  // The storage class must be Private or Workgroup.
+  auto storage_class = static_cast<SpvStorageClass>(message_.storage_class());
+  switch (storage_class) {
+    case SpvStorageClassPrivate:
+    case SpvStorageClassWorkgroup:
+      break;
+    default:
+      assert(false && "Unsupported storage class.");
+      return false;
+  }
   // The type id must correspond to a type.
-  auto type = context->get_type_mgr()->GetType(message_.type_id());
+  auto type = ir_context->get_type_mgr()->GetType(message_.type_id());
   if (!type) {
     return false;
   }
@@ -47,14 +60,21 @@
   if (!pointer_type) {
     return false;
   }
-  // ... with Private storage class.
-  if (pointer_type->storage_class() != SpvStorageClassPrivate) {
+  // ... with the right storage class.
+  if (pointer_type->storage_class() != storage_class) {
     return false;
   }
   if (message_.initializer_id()) {
+    // An initializer is not allowed if the storage class is Workgroup.
+    if (storage_class == SpvStorageClassWorkgroup) {
+      assert(false &&
+             "By construction this transformation should not have an "
+             "initializer when Workgroup storage class is used.");
+      return false;
+    }
     // The initializer id must be the id of a constant.  Check this with the
     // constant manager.
-    auto constant_id = context->get_constant_mgr()->GetConstantsFromIds(
+    auto constant_id = ir_context->get_constant_mgr()->GetConstantsFromIds(
         {message_.initializer_id()});
     if (constant_id.empty()) {
       return false;
@@ -71,20 +91,21 @@
 }
 
 void TransformationAddGlobalVariable::Apply(
-    opt::IRContext* context, spvtools::fuzz::FactManager* /*unused*/) const {
+    opt::IRContext* ir_context,
+    TransformationContext* transformation_context) const {
   opt::Instruction::OperandList input_operands;
   input_operands.push_back(
-      {SPV_OPERAND_TYPE_STORAGE_CLASS, {SpvStorageClassPrivate}});
+      {SPV_OPERAND_TYPE_STORAGE_CLASS, {message_.storage_class()}});
   if (message_.initializer_id()) {
     input_operands.push_back(
         {SPV_OPERAND_TYPE_ID, {message_.initializer_id()}});
   }
-  context->module()->AddGlobalValue(
-      MakeUnique<opt::Instruction>(context, SpvOpVariable, message_.type_id(),
-                                   message_.fresh_id(), input_operands));
-  fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
+  ir_context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpVariable, message_.type_id(), message_.fresh_id(),
+      input_operands));
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
 
-  if (PrivateGlobalsMustBeDeclaredInEntryPointInterfaces(context)) {
+  if (GlobalVariablesMustBeDeclaredInEntryPointInterfaces(ir_context)) {
     // Conservatively add this global to the interface of every entry point in
     // the module.  This means that the global is available for other
     // transformations to use.
@@ -94,14 +115,20 @@
     //
     // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3111) revisit
     //  this if a more thorough approach to entry point interfaces is taken.
-    for (auto& entry_point : context->module()->entry_points()) {
+    for (auto& entry_point : ir_context->module()->entry_points()) {
       entry_point.AddOperand({SPV_OPERAND_TYPE_ID, {message_.fresh_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.
-  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+  ir_context->InvalidateAnalysesExceptFor(
+      opt::IRContext::Analysis::kAnalysisNone);
 }
 
 protobufs::Transformation TransformationAddGlobalVariable::ToMessage() const {
@@ -111,12 +138,12 @@
 }
 
 bool TransformationAddGlobalVariable::
-    PrivateGlobalsMustBeDeclaredInEntryPointInterfaces(
-        opt::IRContext* context) {
+    GlobalVariablesMustBeDeclaredInEntryPointInterfaces(
+        opt::IRContext* ir_context) {
   // TODO(afd): We capture the universal environments for which this requirement
   //  holds.  The check should be refined on demand for other target
   //  environments.
-  switch (context->grammar().target_env()) {
+  switch (ir_context->grammar().target_env()) {
     case SPV_ENV_UNIVERSAL_1_0:
     case SPV_ENV_UNIVERSAL_1_1:
     case SPV_ENV_UNIVERSAL_1_2:
diff --git a/source/fuzz/transformation_add_global_variable.h b/source/fuzz/transformation_add_global_variable.h
index ca63e68..289af9e 100644
--- a/source/fuzz/transformation_add_global_variable.h
+++ b/source/fuzz/transformation_add_global_variable.h
@@ -15,9 +15,9 @@
 #ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_GLOBAL_VARIABLE_H_
 #define SOURCE_FUZZ_TRANSFORMATION_ADD_GLOBAL_VARIABLE_H_
 
-#include "source/fuzz/fact_manager.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 {
@@ -29,27 +29,40 @@
       const protobufs::TransformationAddGlobalVariable& message);
 
   TransformationAddGlobalVariable(uint32_t fresh_id, uint32_t type_id,
-                                  uint32_t initializer_id);
+                                  SpvStorageClass storage_class,
+                                  uint32_t initializer_id,
+                                  bool value_is_irrelevant);
 
   // - |message_.fresh_id| must be fresh
-  // - |message_.type_id| must be the id of a pointer type with Private storage
-  //   class
-  // - |message_.initializer_id| must either be 0 or the id of a constant whose
+  // - |message_.type_id| must be the id of a pointer type with the same storage
+  //   class as |message_.storage_class|
+  // - |message_.storage_class| must be Private or Workgroup
+  // - |message_.initializer_id| must be 0 if |message_.storage_class| is
+  //   Workgroup, and otherwise may either be 0 or the id of a constant whose
   //   type is the pointee type of |message_.type_id|
-  bool IsApplicable(opt::IRContext* context,
-                    const FactManager& fact_manager) const override;
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
 
-  // Adds a global variable with Private storage class to the module, with type
-  // |message_.type_id| and either no initializer or |message_.initializer_id|
-  // as an initializer, depending on whether |message_.initializer_id| is 0.
-  // The global variable has result id |message_.fresh_id|.
-  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+  // Adds a global variable with storage class |message_.storage_class| to the
+  // module, with type |message_.type_id| and either no initializer or
+  // |message_.initializer_id| as an initializer, depending on whether
+  // |message_.initializer_id| is 0.  The global variable has result id
+  // |message_.fresh_id|.
+  //
+  // If |message_.value_is_irrelevant| holds, adds a corresponding fact to the
+  // fact manager in |transformation_context|.
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
 
   protobufs::Transformation ToMessage() const override;
 
  private:
-  static bool PrivateGlobalsMustBeDeclaredInEntryPointInterfaces(
-      opt::IRContext* context);
+  // Returns true if and only if the SPIR-V version being used requires that
+  // global variables accessed in the static call graph of an entry point need
+  // to be listed in that entry point's interface.
+  static bool GlobalVariablesMustBeDeclaredInEntryPointInterfaces(
+      opt::IRContext* ir_context);
 
   protobufs::TransformationAddGlobalVariable message_;
 };
diff --git a/source/fuzz/transformation_add_local_variable.cpp b/source/fuzz/transformation_add_local_variable.cpp
new file mode 100644
index 0000000..5136249
--- /dev/null
+++ b/source/fuzz/transformation_add_local_variable.cpp
@@ -0,0 +1,99 @@
+// 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_local_variable.h"
+
+#include "source/fuzz/fuzzer_util.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationAddLocalVariable::TransformationAddLocalVariable(
+    const spvtools::fuzz::protobufs::TransformationAddLocalVariable& message)
+    : message_(message) {}
+
+TransformationAddLocalVariable::TransformationAddLocalVariable(
+    uint32_t fresh_id, uint32_t type_id, uint32_t function_id,
+    uint32_t initializer_id, bool value_is_irrelevant) {
+  message_.set_fresh_id(fresh_id);
+  message_.set_type_id(type_id);
+  message_.set_function_id(function_id);
+  message_.set_initializer_id(initializer_id);
+  message_.set_value_is_irrelevant(value_is_irrelevant);
+}
+
+bool TransformationAddLocalVariable::IsApplicable(
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
+  // The provided id must be fresh.
+  if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
+    return false;
+  }
+  // The pointer type id must indeed correspond to a pointer, and it must have
+  // function storage class.
+  auto type_instruction =
+      ir_context->get_def_use_mgr()->GetDef(message_.type_id());
+  if (!type_instruction || type_instruction->opcode() != SpvOpTypePointer ||
+      type_instruction->GetSingleWordInOperand(0) != SpvStorageClassFunction) {
+    return false;
+  }
+  // The initializer must...
+  auto initializer_instruction =
+      ir_context->get_def_use_mgr()->GetDef(message_.initializer_id());
+  // ... exist, ...
+  if (!initializer_instruction) {
+    return false;
+  }
+  // ... be a constant, ...
+  if (!spvOpcodeIsConstant(initializer_instruction->opcode())) {
+    return false;
+  }
+  // ... and have the same type as the pointee type.
+  if (initializer_instruction->type_id() !=
+      type_instruction->GetSingleWordInOperand(1)) {
+    return false;
+  }
+  // The function to which the local variable is to be added must exist.
+  return fuzzerutil::FindFunction(ir_context, message_.function_id());
+}
+
+void TransformationAddLocalVariable::Apply(
+    opt::IRContext* ir_context,
+    TransformationContext* transformation_context) const {
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
+  fuzzerutil::FindFunction(ir_context, message_.function_id())
+      ->begin()
+      ->begin()
+      ->InsertBefore(MakeUnique<opt::Instruction>(
+          ir_context, SpvOpVariable, message_.type_id(), message_.fresh_id(),
+          opt::Instruction::OperandList(
+              {{SPV_OPERAND_TYPE_STORAGE_CLASS,
+                {
+
+                    SpvStorageClassFunction}},
+               {SPV_OPERAND_TYPE_ID, {message_.initializer_id()}}})));
+  if (message_.value_is_irrelevant()) {
+    transformation_context->GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
+        message_.fresh_id());
+  }
+  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+}
+
+protobufs::Transformation TransformationAddLocalVariable::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_add_local_variable() = message_;
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_add_local_variable.h b/source/fuzz/transformation_add_local_variable.h
new file mode 100644
index 0000000..6460904
--- /dev/null
+++ b/source/fuzz/transformation_add_local_variable.h
@@ -0,0 +1,62 @@
+// 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_LOCAL_VARIABLE_H_
+#define SOURCE_FUZZ_TRANSFORMATION_ADD_LOCAL_VARIABLE_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 TransformationAddLocalVariable : public Transformation {
+ public:
+  explicit TransformationAddLocalVariable(
+      const protobufs::TransformationAddLocalVariable& message);
+
+  TransformationAddLocalVariable(uint32_t fresh_id, uint32_t type_id,
+                                 uint32_t function_id, uint32_t initializer_id,
+                                 bool value_is_irrelevant);
+
+  // - |message_.fresh_id| must not be used by the module
+  // - |message_.type_id| must be the id of a pointer type with Function
+  //   storage class
+  // - |message_.initializer_id| must be the id of a constant with the same
+  //   type as the pointer's pointee type
+  // - |message_.function_id| must be the id of a function
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // Adds an instruction to the start of |message_.function_id|, of the form:
+  //   |message_.fresh_id| = OpVariable |message_.type_id| Function
+  //                         |message_.initializer_id|
+  // If |message_.value_is_irrelevant| holds, adds a corresponding fact to the
+  // fact manager in |transformation_context|.
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+ private:
+  protobufs::TransformationAddLocalVariable message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_ADD_LOCAL_VARIABLE_H_
diff --git a/source/fuzz/transformation_add_no_contraction_decoration.cpp b/source/fuzz/transformation_add_no_contraction_decoration.cpp
index 7f22cc2..4668534 100644
--- a/source/fuzz/transformation_add_no_contraction_decoration.cpp
+++ b/source/fuzz/transformation_add_no_contraction_decoration.cpp
@@ -31,10 +31,9 @@
 }
 
 bool TransformationAddNoContractionDecoration::IsApplicable(
-    opt::IRContext* context,
-    const spvtools::fuzz::FactManager& /*unused*/) const {
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
   // |message_.result_id| must be the id of an instruction.
-  auto instr = context->get_def_use_mgr()->GetDef(message_.result_id());
+  auto instr = ir_context->get_def_use_mgr()->GetDef(message_.result_id());
   if (!instr) {
     return false;
   }
@@ -43,10 +42,10 @@
 }
 
 void TransformationAddNoContractionDecoration::Apply(
-    opt::IRContext* context, spvtools::fuzz::FactManager* /*unused*/) const {
+    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
   // Add a NoContraction decoration targeting |message_.result_id|.
-  context->get_decoration_mgr()->AddDecoration(message_.result_id(),
-                                               SpvDecorationNoContraction);
+  ir_context->get_decoration_mgr()->AddDecoration(message_.result_id(),
+                                                  SpvDecorationNoContraction);
 }
 
 protobufs::Transformation TransformationAddNoContractionDecoration::ToMessage()
diff --git a/source/fuzz/transformation_add_no_contraction_decoration.h b/source/fuzz/transformation_add_no_contraction_decoration.h
index cec1b2c..27c3a80 100644
--- a/source/fuzz/transformation_add_no_contraction_decoration.h
+++ b/source/fuzz/transformation_add_no_contraction_decoration.h
@@ -15,9 +15,9 @@
 #ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_NO_CONTRACTION_DECORATION_H_
 #define SOURCE_FUZZ_TRANSFORMATION_ADD_NO_CONTRACTION_DECORATION_H_
 
-#include "source/fuzz/fact_manager.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 {
@@ -34,13 +34,15 @@
   //   as defined by the SPIR-V specification.
   // - It does not matter whether this instruction is already annotated with the
   //   NoContraction decoration.
-  bool IsApplicable(opt::IRContext* context,
-                    const FactManager& fact_manager) const override;
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
 
   // Adds a decoration of the form:
   //   'OpDecoration |message_.result_id| NoContraction'
   // to the module.
-  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
 
   protobufs::Transformation ToMessage() const override;
 
diff --git a/source/fuzz/transformation_add_type_array.cpp b/source/fuzz/transformation_add_type_array.cpp
index 2074e98..8f5af07 100644
--- a/source/fuzz/transformation_add_type_array.cpp
+++ b/source/fuzz/transformation_add_type_array.cpp
@@ -32,21 +32,20 @@
 }
 
 bool TransformationAddTypeArray::IsApplicable(
-    opt::IRContext* context,
-    const spvtools::fuzz::FactManager& /*unused*/) const {
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
   // A fresh id is required.
-  if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) {
+  if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
     return false;
   }
   auto element_type =
-      context->get_type_mgr()->GetType(message_.element_type_id());
+      ir_context->get_type_mgr()->GetType(message_.element_type_id());
   if (!element_type || element_type->AsFunction()) {
     // The element type id either does not refer to a type, or refers to a
     // function type; both are illegal.
     return false;
   }
   auto constant =
-      context->get_constant_mgr()->GetConstantsFromIds({message_.size_id()});
+      ir_context->get_constant_mgr()->GetConstantsFromIds({message_.size_id()});
   if (constant.empty()) {
     // The size id does not refer to a constant.
     return false;
@@ -66,16 +65,17 @@
 }
 
 void TransformationAddTypeArray::Apply(
-    opt::IRContext* context, spvtools::fuzz::FactManager* /*unused*/) const {
+    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
   opt::Instruction::OperandList in_operands;
   in_operands.push_back({SPV_OPERAND_TYPE_ID, {message_.element_type_id()}});
   in_operands.push_back({SPV_OPERAND_TYPE_ID, {message_.size_id()}});
-  context->module()->AddType(MakeUnique<opt::Instruction>(
-      context, SpvOpTypeArray, 0, message_.fresh_id(), in_operands));
-  fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
+  ir_context->module()->AddType(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpTypeArray, 0, message_.fresh_id(), in_operands));
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
   // We have added an instruction to the module, so need to be careful about the
   // validity of existing analyses.
-  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+  ir_context->InvalidateAnalysesExceptFor(
+      opt::IRContext::Analysis::kAnalysisNone);
 }
 
 protobufs::Transformation TransformationAddTypeArray::ToMessage() const {
diff --git a/source/fuzz/transformation_add_type_array.h b/source/fuzz/transformation_add_type_array.h
index b6e0718..5e9b8aa 100644
--- a/source/fuzz/transformation_add_type_array.h
+++ b/source/fuzz/transformation_add_type_array.h
@@ -15,9 +15,9 @@
 #ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_ARRAY_H_
 #define SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_ARRAY_H_
 
-#include "source/fuzz/fact_manager.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 {
@@ -35,13 +35,15 @@
   // - |message_.element_type_id| must be the id of a non-function type
   // - |message_.size_id| must be the id of a 32-bit integer constant that is
   //   positive when interpreted as signed.
-  bool IsApplicable(opt::IRContext* context,
-                    const FactManager& fact_manager) const override;
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
 
   // Adds an OpTypeArray instruction to the module, with element type given by
   // |message_.element_type_id| and size given by |message_.size_id|.  The
   // result id of the instruction is |message_.fresh_id|.
-  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
 
   protobufs::Transformation ToMessage() const override;
 
diff --git a/source/fuzz/transformation_add_type_boolean.cpp b/source/fuzz/transformation_add_type_boolean.cpp
index b55028a..77409a8 100644
--- a/source/fuzz/transformation_add_type_boolean.cpp
+++ b/source/fuzz/transformation_add_type_boolean.cpp
@@ -28,27 +28,27 @@
 }
 
 bool TransformationAddTypeBoolean::IsApplicable(
-    opt::IRContext* context,
-    const spvtools::fuzz::FactManager& /*unused*/) const {
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
   // The id must be fresh.
-  if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) {
+  if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
     return false;
   }
 
   // Applicable if there is no bool type already declared in the module.
   opt::analysis::Bool bool_type;
-  return context->get_type_mgr()->GetId(&bool_type) == 0;
+  return ir_context->get_type_mgr()->GetId(&bool_type) == 0;
 }
 
 void TransformationAddTypeBoolean::Apply(
-    opt::IRContext* context, spvtools::fuzz::FactManager* /*unused*/) const {
+    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
   opt::Instruction::OperandList empty_operands;
-  context->module()->AddType(MakeUnique<opt::Instruction>(
-      context, SpvOpTypeBool, 0, message_.fresh_id(), empty_operands));
-  fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
+  ir_context->module()->AddType(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpTypeBool, 0, message_.fresh_id(), empty_operands));
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
   // We have added an instruction to the module, so need to be careful about the
   // validity of existing analyses.
-  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+  ir_context->InvalidateAnalysesExceptFor(
+      opt::IRContext::Analysis::kAnalysisNone);
 }
 
 protobufs::Transformation TransformationAddTypeBoolean::ToMessage() const {
diff --git a/source/fuzz/transformation_add_type_boolean.h b/source/fuzz/transformation_add_type_boolean.h
index 98c1e63..5ce5b9a 100644
--- a/source/fuzz/transformation_add_type_boolean.h
+++ b/source/fuzz/transformation_add_type_boolean.h
@@ -15,7 +15,6 @@
 #ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_BOOLEAN_H_
 #define SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_BOOLEAN_H_
 
-#include "source/fuzz/fact_manager.h"
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
 #include "source/fuzz/transformation.h"
 #include "source/opt/ir_context.h"
@@ -32,11 +31,13 @@
 
   // - |message_.fresh_id| must not be used by the module.
   // - The module must not yet declare OpTypeBoolean
-  bool IsApplicable(opt::IRContext* context,
-                    const FactManager& fact_manager) const override;
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
 
   // Adds OpTypeBoolean with |message_.fresh_id| as result id.
-  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
 
   protobufs::Transformation ToMessage() const override;
 
diff --git a/source/fuzz/transformation_add_type_float.cpp b/source/fuzz/transformation_add_type_float.cpp
index d2af5f8..80716e1 100644
--- a/source/fuzz/transformation_add_type_float.cpp
+++ b/source/fuzz/transformation_add_type_float.cpp
@@ -30,29 +30,29 @@
     : message_(message) {}
 
 bool TransformationAddTypeFloat::IsApplicable(
-    opt::IRContext* context,
-    const spvtools::fuzz::FactManager& /*unused*/) const {
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
   // The id must be fresh.
-  if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) {
+  if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
     return false;
   }
 
   // Applicable if there is no float type with this width already declared in
   // the module.
   opt::analysis::Float float_type(message_.width());
-  return context->get_type_mgr()->GetId(&float_type) == 0;
+  return ir_context->get_type_mgr()->GetId(&float_type) == 0;
 }
 
 void TransformationAddTypeFloat::Apply(
-    opt::IRContext* context, spvtools::fuzz::FactManager* /*unused*/) const {
+    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
   opt::Instruction::OperandList width = {
       {SPV_OPERAND_TYPE_LITERAL_INTEGER, {message_.width()}}};
-  context->module()->AddType(MakeUnique<opt::Instruction>(
-      context, SpvOpTypeFloat, 0, message_.fresh_id(), width));
-  fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
+  ir_context->module()->AddType(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpTypeFloat, 0, message_.fresh_id(), width));
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
   // We have added an instruction to the module, so need to be careful about the
   // validity of existing analyses.
-  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+  ir_context->InvalidateAnalysesExceptFor(
+      opt::IRContext::Analysis::kAnalysisNone);
 }
 
 protobufs::Transformation TransformationAddTypeFloat::ToMessage() const {
diff --git a/source/fuzz/transformation_add_type_float.h b/source/fuzz/transformation_add_type_float.h
index 0fdc831..a8fa0e1 100644
--- a/source/fuzz/transformation_add_type_float.h
+++ b/source/fuzz/transformation_add_type_float.h
@@ -15,9 +15,9 @@
 #ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_FLOAT_H_
 #define SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_FLOAT_H_
 
-#include "source/fuzz/fact_manager.h"
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
 #include "source/fuzz/transformation.h"
+#include "source/fuzz/transformation_context.h"
 #include "source/opt/ir_context.h"
 
 namespace spvtools {
@@ -33,11 +33,13 @@
   // - |message_.fresh_id| must not be used by the module
   // - The module must not contain an OpTypeFloat instruction with width
   //   |message_.width|
-  bool IsApplicable(opt::IRContext* context,
-                    const FactManager& fact_manager) const override;
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
 
   // Adds an OpTypeFloat instruction to the module with the given width
-  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
 
   protobufs::Transformation ToMessage() const override;
 
diff --git a/source/fuzz/transformation_add_type_function.cpp b/source/fuzz/transformation_add_type_function.cpp
index 4b6717b..991a28b 100644
--- a/source/fuzz/transformation_add_type_function.cpp
+++ b/source/fuzz/transformation_add_type_function.cpp
@@ -36,19 +36,18 @@
 }
 
 bool TransformationAddTypeFunction::IsApplicable(
-    opt::IRContext* context,
-    const spvtools::fuzz::FactManager& /*unused*/) const {
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
   // The result id must be fresh.
-  if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) {
+  if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
     return false;
   }
   // The return and argument types must be type ids but not not be function
   // type ids.
-  if (!fuzzerutil::IsNonFunctionTypeId(context, message_.return_type_id())) {
+  if (!fuzzerutil::IsNonFunctionTypeId(ir_context, message_.return_type_id())) {
     return false;
   }
   for (auto argument_type_id : message_.argument_type_id()) {
-    if (!fuzzerutil::IsNonFunctionTypeId(context, argument_type_id)) {
+    if (!fuzzerutil::IsNonFunctionTypeId(ir_context, argument_type_id)) {
       return false;
     }
   }
@@ -56,7 +55,7 @@
   // exactly the same return and argument type ids.  (Note that the type manager
   // does not allow us to check this, as it does not distinguish between
   // function types with different but isomorphic pointer argument types.)
-  for (auto& inst : context->module()->types_values()) {
+  for (auto& inst : ir_context->module()->types_values()) {
     if (inst.opcode() != SpvOpTypeFunction) {
       // Consider only OpTypeFunction instructions.
       continue;
@@ -89,18 +88,19 @@
 }
 
 void TransformationAddTypeFunction::Apply(
-    opt::IRContext* context, spvtools::fuzz::FactManager* /*unused*/) const {
+    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
   opt::Instruction::OperandList in_operands;
   in_operands.push_back({SPV_OPERAND_TYPE_ID, {message_.return_type_id()}});
   for (auto argument_type_id : message_.argument_type_id()) {
     in_operands.push_back({SPV_OPERAND_TYPE_ID, {argument_type_id}});
   }
-  context->module()->AddType(MakeUnique<opt::Instruction>(
-      context, SpvOpTypeFunction, 0, message_.fresh_id(), in_operands));
-  fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
+  ir_context->module()->AddType(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpTypeFunction, 0, message_.fresh_id(), in_operands));
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
   // We have added an instruction to the module, so need to be careful about the
   // validity of existing analyses.
-  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+  ir_context->InvalidateAnalysesExceptFor(
+      opt::IRContext::Analysis::kAnalysisNone);
 }
 
 protobufs::Transformation TransformationAddTypeFunction::ToMessage() const {
diff --git a/source/fuzz/transformation_add_type_function.h b/source/fuzz/transformation_add_type_function.h
index 2b59661..f26b250 100644
--- a/source/fuzz/transformation_add_type_function.h
+++ b/source/fuzz/transformation_add_type_function.h
@@ -17,9 +17,9 @@
 
 #include <vector>
 
-#include "source/fuzz/fact_manager.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 {
@@ -37,15 +37,17 @@
   // - |message_.return_type_id| and each element of |message_.argument_type_id|
   //   must be the ids of non-function types
   // - The module must not contain an OpTypeFunction instruction defining a
-  //   function type with the signature provided by teh given return and
+  //   function type with the signature provided by the given return and
   //   argument types
-  bool IsApplicable(opt::IRContext* context,
-                    const FactManager& fact_manager) const override;
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
 
   // Adds an OpTypeFunction instruction to the module, with signature given by
   // |message_.return_type_id| and |message_.argument_type_id|.  The result id
   // for the instruction is |message_.fresh_id|.
-  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
 
   protobufs::Transformation ToMessage() const override;
 
diff --git a/source/fuzz/transformation_add_type_int.cpp b/source/fuzz/transformation_add_type_int.cpp
index 6f59270..a932a5f 100644
--- a/source/fuzz/transformation_add_type_int.cpp
+++ b/source/fuzz/transformation_add_type_int.cpp
@@ -32,30 +32,30 @@
 }
 
 bool TransformationAddTypeInt::IsApplicable(
-    opt::IRContext* context,
-    const spvtools::fuzz::FactManager& /*unused*/) const {
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
   // The id must be fresh.
-  if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) {
+  if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
     return false;
   }
 
   // Applicable if there is no int type with this width and signedness already
   // declared in the module.
   opt::analysis::Integer int_type(message_.width(), message_.is_signed());
-  return context->get_type_mgr()->GetId(&int_type) == 0;
+  return ir_context->get_type_mgr()->GetId(&int_type) == 0;
 }
 
-void TransformationAddTypeInt::Apply(
-    opt::IRContext* context, spvtools::fuzz::FactManager* /*unused*/) const {
+void TransformationAddTypeInt::Apply(opt::IRContext* ir_context,
+                                     TransformationContext* /*unused*/) const {
   opt::Instruction::OperandList in_operands = {
       {SPV_OPERAND_TYPE_LITERAL_INTEGER, {message_.width()}},
       {SPV_OPERAND_TYPE_LITERAL_INTEGER, {message_.is_signed() ? 1u : 0u}}};
-  context->module()->AddType(MakeUnique<opt::Instruction>(
-      context, SpvOpTypeInt, 0, message_.fresh_id(), in_operands));
-  fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
+  ir_context->module()->AddType(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpTypeInt, 0, message_.fresh_id(), in_operands));
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
   // We have added an instruction to the module, so need to be careful about the
   // validity of existing analyses.
-  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+  ir_context->InvalidateAnalysesExceptFor(
+      opt::IRContext::Analysis::kAnalysisNone);
 }
 
 protobufs::Transformation TransformationAddTypeInt::ToMessage() const {
diff --git a/source/fuzz/transformation_add_type_int.h b/source/fuzz/transformation_add_type_int.h
index 86342d0..5c3c959 100644
--- a/source/fuzz/transformation_add_type_int.h
+++ b/source/fuzz/transformation_add_type_int.h
@@ -15,9 +15,9 @@
 #ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_INT_H_
 #define SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_INT_H_
 
-#include "source/fuzz/fact_manager.h"
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
 #include "source/fuzz/transformation.h"
+#include "source/fuzz/transformation_context.h"
 #include "source/opt/ir_context.h"
 
 namespace spvtools {
@@ -33,12 +33,14 @@
   // - |message_.fresh_id| must not be used by the module
   // - The module must not contain an OpTypeInt instruction with width
   //   |message_.width| and signedness |message.is_signed|
-  bool IsApplicable(opt::IRContext* context,
-                    const FactManager& fact_manager) const override;
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
 
   // Adds an OpTypeInt instruction to the module with the given width and
   // signedness.
-  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
 
   protobufs::Transformation ToMessage() const override;
 
diff --git a/source/fuzz/transformation_add_type_matrix.cpp b/source/fuzz/transformation_add_type_matrix.cpp
index 07ab705..2c24eaa 100644
--- a/source/fuzz/transformation_add_type_matrix.cpp
+++ b/source/fuzz/transformation_add_type_matrix.cpp
@@ -31,15 +31,14 @@
 }
 
 bool TransformationAddTypeMatrix::IsApplicable(
-    opt::IRContext* context,
-    const spvtools::fuzz::FactManager& /*unused*/) const {
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
   // The result id must be fresh.
-  if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) {
+  if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
     return false;
   }
   // The column type must be a floating-point vector.
   auto column_type =
-      context->get_type_mgr()->GetType(message_.column_type_id());
+      ir_context->get_type_mgr()->GetType(message_.column_type_id());
   if (!column_type) {
     return false;
   }
@@ -48,17 +47,18 @@
 }
 
 void TransformationAddTypeMatrix::Apply(
-    opt::IRContext* context, spvtools::fuzz::FactManager* /*unused*/) const {
+    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
   opt::Instruction::OperandList in_operands;
   in_operands.push_back({SPV_OPERAND_TYPE_ID, {message_.column_type_id()}});
   in_operands.push_back(
       {SPV_OPERAND_TYPE_LITERAL_INTEGER, {message_.column_count()}});
-  context->module()->AddType(MakeUnique<opt::Instruction>(
-      context, SpvOpTypeMatrix, 0, message_.fresh_id(), in_operands));
-  fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
+  ir_context->module()->AddType(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpTypeMatrix, 0, message_.fresh_id(), in_operands));
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
   // We have added an instruction to the module, so need to be careful about the
   // validity of existing analyses.
-  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+  ir_context->InvalidateAnalysesExceptFor(
+      opt::IRContext::Analysis::kAnalysisNone);
 }
 
 protobufs::Transformation TransformationAddTypeMatrix::ToMessage() const {
diff --git a/source/fuzz/transformation_add_type_matrix.h b/source/fuzz/transformation_add_type_matrix.h
index ee3caf7..6d0724e 100644
--- a/source/fuzz/transformation_add_type_matrix.h
+++ b/source/fuzz/transformation_add_type_matrix.h
@@ -15,9 +15,9 @@
 #ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_MATRIX_H_
 #define SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_MATRIX_H_
 
-#include "source/fuzz/fact_manager.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 {
@@ -28,18 +28,20 @@
   explicit TransformationAddTypeMatrix(
       const protobufs::TransformationAddTypeMatrix& message);
 
-  TransformationAddTypeMatrix(uint32_t fresh_id, uint32_t base_type_id,
-                              uint32_t size);
+  TransformationAddTypeMatrix(uint32_t fresh_id, uint32_t column_type_id,
+                              uint32_t column_count);
 
   // - |message_.fresh_id| must be a fresh id
   // - |message_.column_type_id| must be the id of a floating-point vector type
-  bool IsApplicable(opt::IRContext* context,
-                    const FactManager& fact_manager) const override;
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
 
   // Adds an OpTypeMatrix instruction to the module, with column type
   // |message_.column_type_id| and |message_.column_count| columns, with result
   // id |message_.fresh_id|.
-  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
 
   protobufs::Transformation ToMessage() const override;
 
diff --git a/source/fuzz/transformation_add_type_pointer.cpp b/source/fuzz/transformation_add_type_pointer.cpp
index 426985a..6cc8171 100644
--- a/source/fuzz/transformation_add_type_pointer.cpp
+++ b/source/fuzz/transformation_add_type_pointer.cpp
@@ -31,28 +31,29 @@
 }
 
 bool TransformationAddTypePointer::IsApplicable(
-    opt::IRContext* context,
-    const spvtools::fuzz::FactManager& /*unused*/) const {
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
   // The id must be fresh.
-  if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) {
+  if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
     return false;
   }
   // The base type must be known.
-  return context->get_type_mgr()->GetType(message_.base_type_id()) != nullptr;
+  return ir_context->get_type_mgr()->GetType(message_.base_type_id()) !=
+         nullptr;
 }
 
 void TransformationAddTypePointer::Apply(
-    opt::IRContext* context, spvtools::fuzz::FactManager* /*unused*/) const {
+    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
   // Add the pointer type.
   opt::Instruction::OperandList in_operands = {
       {SPV_OPERAND_TYPE_STORAGE_CLASS, {message_.storage_class()}},
       {SPV_OPERAND_TYPE_ID, {message_.base_type_id()}}};
-  context->module()->AddType(MakeUnique<opt::Instruction>(
-      context, SpvOpTypePointer, 0, message_.fresh_id(), in_operands));
-  fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
+  ir_context->module()->AddType(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpTypePointer, 0, message_.fresh_id(), in_operands));
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
   // We have added an instruction to the module, so need to be careful about the
   // validity of existing analyses.
-  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+  ir_context->InvalidateAnalysesExceptFor(
+      opt::IRContext::Analysis::kAnalysisNone);
 }
 
 protobufs::Transformation TransformationAddTypePointer::ToMessage() const {
diff --git a/source/fuzz/transformation_add_type_pointer.h b/source/fuzz/transformation_add_type_pointer.h
index 2b9ff77..3b50a29 100644
--- a/source/fuzz/transformation_add_type_pointer.h
+++ b/source/fuzz/transformation_add_type_pointer.h
@@ -15,9 +15,9 @@
 #ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_POINTER_H_
 #define SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_POINTER_H_
 
-#include "source/fuzz/fact_manager.h"
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
 #include "source/fuzz/transformation.h"
+#include "source/fuzz/transformation_context.h"
 #include "source/opt/ir_context.h"
 
 namespace spvtools {
@@ -34,12 +34,14 @@
   // - |message_.fresh_id| must not be used by the module
   // - |message_.base_type_id| must be the result id of an OpType[...]
   // instruction
-  bool IsApplicable(opt::IRContext* context,
-                    const FactManager& fact_manager) const override;
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
 
   // Adds an OpTypePointer instruction with the given storage class and base
   // type to the module.
-  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
 
   protobufs::Transformation ToMessage() const override;
 
diff --git a/source/fuzz/transformation_add_type_struct.cpp b/source/fuzz/transformation_add_type_struct.cpp
index 1ae8372..6ce5ea1 100644
--- a/source/fuzz/transformation_add_type_struct.cpp
+++ b/source/fuzz/transformation_add_type_struct.cpp
@@ -32,14 +32,13 @@
 }
 
 bool TransformationAddTypeStruct::IsApplicable(
-    opt::IRContext* context,
-    const spvtools::fuzz::FactManager& /*unused*/) const {
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
   // A fresh id is required.
-  if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) {
+  if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
     return false;
   }
   for (auto member_type : message_.member_type_id()) {
-    auto type = context->get_type_mgr()->GetType(member_type);
+    auto type = ir_context->get_type_mgr()->GetType(member_type);
     if (!type || type->AsFunction()) {
       // The member type id either does not refer to a type, or refers to a
       // function type; both are illegal.
@@ -50,17 +49,18 @@
 }
 
 void TransformationAddTypeStruct::Apply(
-    opt::IRContext* context, spvtools::fuzz::FactManager* /*unused*/) const {
+    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
   opt::Instruction::OperandList in_operands;
   for (auto member_type : message_.member_type_id()) {
     in_operands.push_back({SPV_OPERAND_TYPE_ID, {member_type}});
   }
-  context->module()->AddType(MakeUnique<opt::Instruction>(
-      context, SpvOpTypeStruct, 0, message_.fresh_id(), in_operands));
-  fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
+  ir_context->module()->AddType(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpTypeStruct, 0, message_.fresh_id(), in_operands));
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
   // We have added an instruction to the module, so need to be careful about the
   // validity of existing analyses.
-  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+  ir_context->InvalidateAnalysesExceptFor(
+      opt::IRContext::Analysis::kAnalysisNone);
 }
 
 protobufs::Transformation TransformationAddTypeStruct::ToMessage() const {
diff --git a/source/fuzz/transformation_add_type_struct.h b/source/fuzz/transformation_add_type_struct.h
index edf3ec6..86a532d 100644
--- a/source/fuzz/transformation_add_type_struct.h
+++ b/source/fuzz/transformation_add_type_struct.h
@@ -17,9 +17,9 @@
 
 #include <vector>
 
-#include "source/fuzz/fact_manager.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 {
@@ -35,12 +35,14 @@
 
   // - |message_.fresh_id| must be a fresh id
   // - |message_.member_type_id| must be a sequence of non-function type ids
-  bool IsApplicable(opt::IRContext* context,
-                    const FactManager& fact_manager) const override;
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
 
   // Adds an OpTypeStruct instruction whose field types are given by
   // |message_.member_type_id|, with result id |message_.fresh_id|.
-  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
 
   protobufs::Transformation ToMessage() const override;
 
diff --git a/source/fuzz/transformation_add_type_vector.cpp b/source/fuzz/transformation_add_type_vector.cpp
index 3fdf50b..f7b2fb5 100644
--- a/source/fuzz/transformation_add_type_vector.cpp
+++ b/source/fuzz/transformation_add_type_vector.cpp
@@ -31,13 +31,12 @@
 }
 
 bool TransformationAddTypeVector::IsApplicable(
-    opt::IRContext* context,
-    const spvtools::fuzz::FactManager& /*unused*/) const {
-  if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) {
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
+  if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
     return false;
   }
   auto component_type =
-      context->get_type_mgr()->GetType(message_.component_type_id());
+      ir_context->get_type_mgr()->GetType(message_.component_type_id());
   if (!component_type) {
     return false;
   }
@@ -46,17 +45,18 @@
 }
 
 void TransformationAddTypeVector::Apply(
-    opt::IRContext* context, spvtools::fuzz::FactManager* /*unused*/) const {
+    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
   opt::Instruction::OperandList in_operands;
   in_operands.push_back({SPV_OPERAND_TYPE_ID, {message_.component_type_id()}});
   in_operands.push_back(
       {SPV_OPERAND_TYPE_LITERAL_INTEGER, {message_.component_count()}});
-  context->module()->AddType(MakeUnique<opt::Instruction>(
-      context, SpvOpTypeVector, 0, message_.fresh_id(), in_operands));
-  fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
+  ir_context->module()->AddType(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpTypeVector, 0, message_.fresh_id(), in_operands));
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
   // We have added an instruction to the module, so need to be careful about the
   // validity of existing analyses.
-  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+  ir_context->InvalidateAnalysesExceptFor(
+      opt::IRContext::Analysis::kAnalysisNone);
 }
 
 protobufs::Transformation TransformationAddTypeVector::ToMessage() const {
diff --git a/source/fuzz/transformation_add_type_vector.h b/source/fuzz/transformation_add_type_vector.h
index 7b50f6a..240f7cc 100644
--- a/source/fuzz/transformation_add_type_vector.h
+++ b/source/fuzz/transformation_add_type_vector.h
@@ -15,9 +15,9 @@
 #ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_VECTOR_H_
 #define SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_VECTOR_H_
 
-#include "source/fuzz/fact_manager.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 {
@@ -28,18 +28,20 @@
   explicit TransformationAddTypeVector(
       const protobufs::TransformationAddTypeVector& message);
 
-  TransformationAddTypeVector(uint32_t fresh_id, uint32_t base_type_id,
-                              uint32_t size);
+  TransformationAddTypeVector(uint32_t fresh_id, uint32_t component_type_id,
+                              uint32_t component_count);
 
   // - |message_.fresh_id| must be a fresh id
   // - |message_.component_type_id| must be the id of a scalar type
-  bool IsApplicable(opt::IRContext* context,
-                    const FactManager& fact_manager) const override;
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
 
   // Adds an OpTypeVector instruction to the module, with component type
   // |message_.component_type_id| and |message_.component_count| components,
   // with result id |message_.fresh_id|.
-  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
 
   protobufs::Transformation ToMessage() const override;
 
diff --git a/source/fuzz/transformation_composite_construct.cpp b/source/fuzz/transformation_composite_construct.cpp
index 7a3aff1..cd4f22f 100644
--- a/source/fuzz/transformation_composite_construct.cpp
+++ b/source/fuzz/transformation_composite_construct.cpp
@@ -40,14 +40,14 @@
 }
 
 bool TransformationCompositeConstruct::IsApplicable(
-    opt::IRContext* context, const FactManager& /*fact_manager*/) const {
-  if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) {
+    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;
   }
 
   auto insert_before =
-      FindInstruction(message_.instruction_to_insert_before(), context);
+      FindInstruction(message_.instruction_to_insert_before(), ir_context);
   if (!insert_before) {
     // The instruction before which the composite should be inserted was not
     // found.
@@ -55,7 +55,7 @@
   }
 
   auto composite_type =
-      context->get_type_mgr()->GetType(message_.composite_type_id());
+      ir_context->get_type_mgr()->GetType(message_.composite_type_id());
 
   if (!fuzzerutil::IsCompositeType(composite_type)) {
     // The type must actually be a composite.
@@ -64,47 +64,32 @@
 
   // If the type is an array, matrix, struct or vector, the components need to
   // be suitable for constructing something of that type.
-  if (composite_type->AsArray() && !ComponentsForArrayConstructionAreOK(
-                                       context, *composite_type->AsArray())) {
+  if (composite_type->AsArray() &&
+      !ComponentsForArrayConstructionAreOK(ir_context,
+                                           *composite_type->AsArray())) {
     return false;
   }
-  if (composite_type->AsMatrix() && !ComponentsForMatrixConstructionAreOK(
-                                        context, *composite_type->AsMatrix())) {
+  if (composite_type->AsMatrix() &&
+      !ComponentsForMatrixConstructionAreOK(ir_context,
+                                            *composite_type->AsMatrix())) {
     return false;
   }
-  if (composite_type->AsStruct() && !ComponentsForStructConstructionAreOK(
-                                        context, *composite_type->AsStruct())) {
+  if (composite_type->AsStruct() &&
+      !ComponentsForStructConstructionAreOK(ir_context,
+                                            *composite_type->AsStruct())) {
     return false;
   }
-  if (composite_type->AsVector() && !ComponentsForVectorConstructionAreOK(
-                                        context, *composite_type->AsVector())) {
+  if (composite_type->AsVector() &&
+      !ComponentsForVectorConstructionAreOK(ir_context,
+                                            *composite_type->AsVector())) {
     return false;
   }
 
   // Now check whether every component being used to initialize the composite is
   // available at the desired program point.
   for (auto& component : message_.component()) {
-    auto component_inst = context->get_def_use_mgr()->GetDef(component);
-    if (!context->get_instr_block(component)) {
-      // The component does not have a block; that means it is in global scope,
-      // which is OK. (Whether the component actually corresponds to an
-      // instruction is checked above when determining whether types are
-      // suitable.)
-      continue;
-    }
-    // Check whether the component is available.
-    if (insert_before->HasResultId() &&
-        insert_before->result_id() == component) {
-      // This constitutes trying to use an id right before it is defined.  The
-      // special case is needed due to an instruction always dominating itself.
-      return false;
-    }
-    if (!context
-             ->GetDominatorAnalysis(
-                 context->get_instr_block(&*insert_before)->GetParent())
-             ->Dominates(component_inst, &*insert_before)) {
-      // The instruction defining the component must dominate the instruction we
-      // wish to insert the composite before.
+    if (!fuzzerutil::IdIsAvailableBeforeInstruction(ir_context, insert_before,
+                                                    component)) {
       return false;
     }
   }
@@ -112,13 +97,14 @@
   return true;
 }
 
-void TransformationCompositeConstruct::Apply(opt::IRContext* context,
-                                             FactManager* fact_manager) const {
+void TransformationCompositeConstruct::Apply(
+    opt::IRContext* ir_context,
+    TransformationContext* transformation_context) const {
   // Use the base and offset information from the transformation to determine
   // where in the module a new instruction should be inserted.
   auto insert_before_inst =
-      FindInstruction(message_.instruction_to_insert_before(), context);
-  auto destination_block = context->get_instr_block(insert_before_inst);
+      FindInstruction(message_.instruction_to_insert_before(), ir_context);
+  auto destination_block = ir_context->get_instr_block(insert_before_inst);
   auto insert_before = fuzzerutil::GetIteratorForInstruction(
       destination_block, insert_before_inst);
 
@@ -130,22 +116,22 @@
 
   // Insert an OpCompositeConstruct instruction.
   insert_before.InsertBefore(MakeUnique<opt::Instruction>(
-      context, SpvOpCompositeConstruct, message_.composite_type_id(),
+      ir_context, SpvOpCompositeConstruct, message_.composite_type_id(),
       message_.fresh_id(), in_operands));
 
-  fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
-  context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+  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 =
-      context->get_type_mgr()->GetType(message_.composite_type_id());
+      ir_context->get_type_mgr()->GetType(message_.composite_type_id());
   uint32_t index = 0;
   for (auto component : message_.component()) {
-    auto component_type = context->get_type_mgr()->GetType(
-        context->get_def_use_mgr()->GetDef(component)->type_id());
+    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
@@ -158,24 +144,24 @@
       for (uint32_t subvector_index = 0;
            subvector_index < component_type->AsVector()->element_count();
            subvector_index++) {
-        fact_manager->AddFactDataSynonym(
+        transformation_context->GetFactManager()->AddFactDataSynonym(
             MakeDataDescriptor(component, {subvector_index}),
-            MakeDataDescriptor(message_.fresh_id(), {index}), context);
+            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.
-      fact_manager->AddFactDataSynonym(
+      transformation_context->GetFactManager()->AddFactDataSynonym(
           MakeDataDescriptor(component, {}),
-          MakeDataDescriptor(message_.fresh_id(), {index}), context);
+          MakeDataDescriptor(message_.fresh_id(), {index}), ir_context);
       index++;
     }
   }
 }
 
 bool TransformationCompositeConstruct::ComponentsForArrayConstructionAreOK(
-    opt::IRContext* context, const opt::analysis::Array& array_type) const {
+    opt::IRContext* ir_context, const opt::analysis::Array& array_type) const {
   if (array_type.length_info().words[0] !=
       opt::analysis::Array::LengthInfo::kConstant) {
     // We only handle constant-sized arrays.
@@ -195,13 +181,13 @@
   // Check that each component is the result id of an instruction whose type is
   // the array's element type.
   for (auto component_id : message_.component()) {
-    auto inst = context->get_def_use_mgr()->GetDef(component_id);
+    auto inst = ir_context->get_def_use_mgr()->GetDef(component_id);
     if (inst == nullptr || !inst->type_id()) {
       // The component does not correspond to an instruction with a result
       // type.
       return false;
     }
-    auto component_type = context->get_type_mgr()->GetType(inst->type_id());
+    auto component_type = ir_context->get_type_mgr()->GetType(inst->type_id());
     assert(component_type);
     if (component_type != array_type.element_type()) {
       // The component's type does not match the array's element type.
@@ -212,7 +198,8 @@
 }
 
 bool TransformationCompositeConstruct::ComponentsForMatrixConstructionAreOK(
-    opt::IRContext* context, const opt::analysis::Matrix& matrix_type) const {
+    opt::IRContext* ir_context,
+    const opt::analysis::Matrix& matrix_type) const {
   if (static_cast<uint32_t>(message_.component().size()) !=
       matrix_type.element_count()) {
     // The number of components must match the number of columns of the matrix.
@@ -221,13 +208,13 @@
   // Check that each component is the result id of an instruction whose type is
   // the matrix's column type.
   for (auto component_id : message_.component()) {
-    auto inst = context->get_def_use_mgr()->GetDef(component_id);
+    auto inst = ir_context->get_def_use_mgr()->GetDef(component_id);
     if (inst == nullptr || !inst->type_id()) {
       // The component does not correspond to an instruction with a result
       // type.
       return false;
     }
-    auto component_type = context->get_type_mgr()->GetType(inst->type_id());
+    auto component_type = ir_context->get_type_mgr()->GetType(inst->type_id());
     assert(component_type);
     if (component_type != matrix_type.element_type()) {
       // The component's type does not match the matrix's column type.
@@ -238,7 +225,8 @@
 }
 
 bool TransformationCompositeConstruct::ComponentsForStructConstructionAreOK(
-    opt::IRContext* context, const opt::analysis::Struct& struct_type) const {
+    opt::IRContext* ir_context,
+    const opt::analysis::Struct& struct_type) const {
   if (static_cast<uint32_t>(message_.component().size()) !=
       struct_type.element_types().size()) {
     // The number of components must match the number of fields of the struct.
@@ -248,14 +236,14 @@
   // matches the associated field type.
   for (uint32_t field_index = 0;
        field_index < struct_type.element_types().size(); field_index++) {
-    auto inst =
-        context->get_def_use_mgr()->GetDef(message_.component()[field_index]);
+    auto inst = ir_context->get_def_use_mgr()->GetDef(
+        message_.component()[field_index]);
     if (inst == nullptr || !inst->type_id()) {
       // The component does not correspond to an instruction with a result
       // type.
       return false;
     }
-    auto component_type = context->get_type_mgr()->GetType(inst->type_id());
+    auto component_type = ir_context->get_type_mgr()->GetType(inst->type_id());
     assert(component_type);
     if (component_type != struct_type.element_types()[field_index]) {
       // The component's type does not match the corresponding field type.
@@ -266,17 +254,18 @@
 }
 
 bool TransformationCompositeConstruct::ComponentsForVectorConstructionAreOK(
-    opt::IRContext* context, const opt::analysis::Vector& vector_type) const {
+    opt::IRContext* ir_context,
+    const opt::analysis::Vector& vector_type) const {
   uint32_t base_element_count = 0;
   auto element_type = vector_type.element_type();
   for (auto& component_id : message_.component()) {
-    auto inst = context->get_def_use_mgr()->GetDef(component_id);
+    auto inst = ir_context->get_def_use_mgr()->GetDef(component_id);
     if (inst == nullptr || !inst->type_id()) {
       // The component does not correspond to an instruction with a result
       // type.
       return false;
     }
-    auto component_type = context->get_type_mgr()->GetType(inst->type_id());
+    auto component_type = ir_context->get_type_mgr()->GetType(inst->type_id());
     assert(component_type);
     if (component_type == element_type) {
       base_element_count++;
diff --git a/source/fuzz/transformation_composite_construct.h b/source/fuzz/transformation_composite_construct.h
index 5369c4c..2e55e70 100644
--- a/source/fuzz/transformation_composite_construct.h
+++ b/source/fuzz/transformation_composite_construct.h
@@ -15,9 +15,9 @@
 #ifndef SOURCE_FUZZ_TRANSFORMATION_COMPOSITE_CONSTRUCT_H_
 #define SOURCE_FUZZ_TRANSFORMATION_COMPOSITE_CONSTRUCT_H_
 
-#include "source/fuzz/fact_manager.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 {
@@ -49,15 +49,17 @@
   //   before 'inst'.
   // - Each element of |message_.component| must be available directly before
   //   'inst'.
-  bool IsApplicable(opt::IRContext* context,
-                    const FactManager& fact_manager) const override;
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
 
   // Inserts a new OpCompositeConstruct instruction, with id
   // |message_.fresh_id|, directly before the instruction identified by
   // |message_.base_instruction_id| and |message_.offset|.  The instruction
   // creates a composite of type |message_.composite_type_id| using the ids of
   // |message_.component|.
-  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
 
   protobufs::Transformation ToMessage() const override;
 
@@ -65,19 +67,22 @@
   // Helper to decide whether the components of the transformation are suitable
   // for constructing an array of the given type.
   bool ComponentsForArrayConstructionAreOK(
-      opt::IRContext* context, const opt::analysis::Array& array_type) const;
+      opt::IRContext* ir_context, const opt::analysis::Array& array_type) const;
 
   // Similar, but for matrices.
   bool ComponentsForMatrixConstructionAreOK(
-      opt::IRContext* context, const opt::analysis::Matrix& matrix_type) const;
+      opt::IRContext* ir_context,
+      const opt::analysis::Matrix& matrix_type) const;
 
   // Similar, but for structs.
   bool ComponentsForStructConstructionAreOK(
-      opt::IRContext* context, const opt::analysis::Struct& struct_type) const;
+      opt::IRContext* ir_context,
+      const opt::analysis::Struct& struct_type) const;
 
   // Similar, but for vectors.
   bool ComponentsForVectorConstructionAreOK(
-      opt::IRContext* context, const opt::analysis::Vector& vector_type) const;
+      opt::IRContext* ir_context,
+      const opt::analysis::Vector& vector_type) const;
 
   protobufs::TransformationCompositeConstruct message_;
 };
diff --git a/source/fuzz/transformation_composite_extract.cpp b/source/fuzz/transformation_composite_extract.cpp
index 5d3a386..3dc3953 100644
--- a/source/fuzz/transformation_composite_extract.cpp
+++ b/source/fuzz/transformation_composite_extract.cpp
@@ -40,24 +40,23 @@
 }
 
 bool TransformationCompositeExtract::IsApplicable(
-    opt::IRContext* context,
-    const spvtools::fuzz::FactManager& /*unused*/) const {
-  if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) {
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
+  if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
     return false;
   }
   auto instruction_to_insert_before =
-      FindInstruction(message_.instruction_to_insert_before(), context);
+      FindInstruction(message_.instruction_to_insert_before(), ir_context);
   if (!instruction_to_insert_before) {
     return false;
   }
   auto composite_instruction =
-      context->get_def_use_mgr()->GetDef(message_.composite_id());
+      ir_context->get_def_use_mgr()->GetDef(message_.composite_id());
   if (!composite_instruction) {
     return false;
   }
-  if (auto block = context->get_instr_block(composite_instruction)) {
+  if (auto block = ir_context->get_instr_block(composite_instruction)) {
     if (composite_instruction == instruction_to_insert_before ||
-        !context->GetDominatorAnalysis(block->GetParent())
+        !ir_context->GetDominatorAnalysis(block->GetParent())
              ->Dominates(composite_instruction, instruction_to_insert_before)) {
       return false;
     }
@@ -66,7 +65,7 @@
          "An instruction in a block cannot have a result id but no type id.");
 
   auto composite_type =
-      context->get_type_mgr()->GetType(composite_instruction->type_id());
+      ir_context->get_type_mgr()->GetType(composite_instruction->type_id());
   if (!composite_type) {
     return false;
   }
@@ -76,30 +75,33 @@
     return false;
   }
 
-  return fuzzerutil::WalkCompositeTypeIndices(
-             context, composite_instruction->type_id(), message_.index()) != 0;
+  return fuzzerutil::WalkCompositeTypeIndices(ir_context,
+                                              composite_instruction->type_id(),
+                                              message_.index()) != 0;
 }
 
 void TransformationCompositeExtract::Apply(
-    opt::IRContext* context, spvtools::fuzz::FactManager* fact_manager) const {
+    opt::IRContext* ir_context,
+    TransformationContext* transformation_context) const {
   opt::Instruction::OperandList extract_operands;
   extract_operands.push_back({SPV_OPERAND_TYPE_ID, {message_.composite_id()}});
   for (auto an_index : message_.index()) {
     extract_operands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {an_index}});
   }
   auto composite_instruction =
-      context->get_def_use_mgr()->GetDef(message_.composite_id());
+      ir_context->get_def_use_mgr()->GetDef(message_.composite_id());
   auto extracted_type = fuzzerutil::WalkCompositeTypeIndices(
-      context, composite_instruction->type_id(), message_.index());
+      ir_context, composite_instruction->type_id(), message_.index());
 
-  FindInstruction(message_.instruction_to_insert_before(), context)
+  FindInstruction(message_.instruction_to_insert_before(), ir_context)
       ->InsertBefore(MakeUnique<opt::Instruction>(
-          context, SpvOpCompositeExtract, extracted_type, message_.fresh_id(),
-          extract_operands));
+          ir_context, SpvOpCompositeExtract, extracted_type,
+          message_.fresh_id(), extract_operands));
 
-  fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
 
-  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+  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.
@@ -111,8 +113,9 @@
       MakeDataDescriptor(message_.composite_id(), std::move(indices));
   protobufs::DataDescriptor data_descriptor_for_result_id =
       MakeDataDescriptor(message_.fresh_id(), {});
-  fact_manager->AddFactDataSynonym(data_descriptor_for_extracted_element,
-                                   data_descriptor_for_result_id, context);
+  transformation_context->GetFactManager()->AddFactDataSynonym(
+      data_descriptor_for_extracted_element, data_descriptor_for_result_id,
+      ir_context);
 }
 
 protobufs::Transformation TransformationCompositeExtract::ToMessage() const {
diff --git a/source/fuzz/transformation_composite_extract.h b/source/fuzz/transformation_composite_extract.h
index c4c9278..8f52d22 100644
--- a/source/fuzz/transformation_composite_extract.h
+++ b/source/fuzz/transformation_composite_extract.h
@@ -15,9 +15,9 @@
 #ifndef SOURCE_FUZZ_TRANSFORMATION_COMPOSITE_EXTRACT_H_
 #define SOURCE_FUZZ_TRANSFORMATION_COMPOSITE_EXTRACT_H_
 
-#include "source/fuzz/fact_manager.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 {
@@ -41,15 +41,17 @@
   // - |message_.index| must be a suitable set of indices for
   //   |message_.composite_id|, i.e. it must be possible to follow this chain
   //   of indices to reach a sub-object of |message_.composite_id|
-  bool IsApplicable(opt::IRContext* context,
-                    const FactManager& fact_manager) const override;
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
 
   // 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|.  Generates a data synonym fact relating
   // |message_.fresh_id| to the extracted element.
-  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
 
   protobufs::Transformation ToMessage() const override;
 
diff --git a/source/fuzz/transformation_context.cpp b/source/fuzz/transformation_context.cpp
new file mode 100644
index 0000000..9c8a90f
--- /dev/null
+++ b/source/fuzz/transformation_context.cpp
@@ -0,0 +1,29 @@
+// 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_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationContext::TransformationContext(
+    FactManager* transformation_context,
+    spv_validator_options validator_options)
+    : fact_manager_(transformation_context),
+      validator_options_(validator_options) {}
+
+TransformationContext::~TransformationContext() = default;
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_context.h b/source/fuzz/transformation_context.h
new file mode 100644
index 0000000..37e15a2
--- /dev/null
+++ b/source/fuzz/transformation_context.h
@@ -0,0 +1,56 @@
+// 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_CONTEXT_H_
+#define SOURCE_FUZZ_TRANSFORMATION_CONTEXT_H_
+
+#include "source/fuzz/fact_manager.h"
+#include "spirv-tools/libspirv.hpp"
+
+namespace spvtools {
+namespace fuzz {
+
+// Encapsulates all information that is required to inform how to apply a
+// transformation to a module.
+class TransformationContext {
+ public:
+  // Constructs a transformation context with a given fact manager and validator
+  // options.
+  TransformationContext(FactManager* fact_manager,
+                        spv_validator_options validator_options);
+
+  ~TransformationContext();
+
+  FactManager* GetFactManager() { return fact_manager_; }
+
+  const FactManager* GetFactManager() const { return fact_manager_; }
+
+  spv_validator_options GetValidatorOptions() const {
+    return validator_options_;
+  }
+
+ private:
+  // Manages facts that inform whether transformations can be applied, and that
+  // are produced by applying transformations.
+  FactManager* fact_manager_;
+
+  // Options to control validation when deciding whether transformations can be
+  // applied.
+  spv_validator_options validator_options_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_CONTEXT_H_
diff --git a/source/fuzz/transformation_copy_object.cpp b/source/fuzz/transformation_copy_object.cpp
index af1e81c..7b5b5c9 100644
--- a/source/fuzz/transformation_copy_object.cpp
+++ b/source/fuzz/transformation_copy_object.cpp
@@ -38,22 +38,22 @@
 }
 
 bool TransformationCopyObject::IsApplicable(
-    opt::IRContext* context, const FactManager& /*fact_manager*/) const {
-  if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) {
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
+  if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
     // We require the id for the object copy to be unused.
     return false;
   }
   // The id of the object to be copied must exist
-  auto object_inst = context->get_def_use_mgr()->GetDef(message_.object());
+  auto object_inst = ir_context->get_def_use_mgr()->GetDef(message_.object());
   if (!object_inst) {
     return false;
   }
-  if (!fuzzerutil::CanMakeSynonymOf(context, object_inst)) {
+  if (!fuzzerutil::CanMakeSynonymOf(ir_context, object_inst)) {
     return false;
   }
 
   auto insert_before =
-      FindInstruction(message_.instruction_to_insert_before(), context);
+      FindInstruction(message_.instruction_to_insert_before(), ir_context);
   if (!insert_before) {
     // The instruction before which the copy should be inserted was not found.
     return false;
@@ -64,29 +64,20 @@
     return false;
   }
 
-  // |message_object| must be available at the point where we want to add the
-  // copy. It is available if it is at global scope (in which case it has no
-  // block), or if it dominates the point of insertion but is different from the
-  // point of insertion.
-  //
-  // The reason why the object needs to be different from the insertion point is
-  // that the copy will be added *before* this point, and we do not want to
-  // insert it before the object's defining instruction.
-  return !context->get_instr_block(object_inst) ||
-         (object_inst != &*insert_before &&
-          context
-              ->GetDominatorAnalysis(
-                  context->get_instr_block(insert_before)->GetParent())
-              ->Dominates(object_inst, &*insert_before));
+  // |message_object| must be available directly before the point where we want
+  // to add the copy.
+  return fuzzerutil::IdIsAvailableBeforeInstruction(ir_context, insert_before,
+                                                    message_.object());
 }
 
-void TransformationCopyObject::Apply(opt::IRContext* context,
-                                     FactManager* fact_manager) const {
-  auto object_inst = context->get_def_use_mgr()->GetDef(message_.object());
+void TransformationCopyObject::Apply(
+    opt::IRContext* ir_context,
+    TransformationContext* transformation_context) const {
+  auto object_inst = ir_context->get_def_use_mgr()->GetDef(message_.object());
   assert(object_inst && "The object to be copied must exist.");
   auto insert_before_inst =
-      FindInstruction(message_.instruction_to_insert_before(), context);
-  auto destination_block = context->get_instr_block(insert_before_inst);
+      FindInstruction(message_.instruction_to_insert_before(), ir_context);
+  auto destination_block = ir_context->get_instr_block(insert_before_inst);
   assert(destination_block && "The base instruction must be in a block.");
   auto insert_before = fuzzerutil::GetIteratorForInstruction(
       destination_block, insert_before_inst);
@@ -96,15 +87,22 @@
   opt::Instruction::OperandList operands = {
       {SPV_OPERAND_TYPE_ID, {message_.object()}}};
   insert_before->InsertBefore(MakeUnique<opt::Instruction>(
-      context, SpvOp::SpvOpCopyObject, object_inst->type_id(),
+      ir_context, SpvOp::SpvOpCopyObject, object_inst->type_id(),
       message_.fresh_id(), operands));
 
-  fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
-  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
+  ir_context->InvalidateAnalysesExceptFor(
+      opt::IRContext::Analysis::kAnalysisNone);
 
-  fact_manager->AddFactDataSynonym(MakeDataDescriptor(message_.object(), {}),
-                                   MakeDataDescriptor(message_.fresh_id(), {}),
-                                   context);
+  transformation_context->GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(message_.object(), {}),
+      MakeDataDescriptor(message_.fresh_id(), {}), ir_context);
+
+  if (transformation_context->GetFactManager()->PointeeValueIsIrrelevant(
+          message_.object())) {
+    transformation_context->GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
+        message_.fresh_id());
+  }
 }
 
 protobufs::Transformation TransformationCopyObject::ToMessage() const {
diff --git a/source/fuzz/transformation_copy_object.h b/source/fuzz/transformation_copy_object.h
index 3a75ac9..80d57ae 100644
--- a/source/fuzz/transformation_copy_object.h
+++ b/source/fuzz/transformation_copy_object.h
@@ -15,9 +15,9 @@
 #ifndef SOURCE_FUZZ_TRANSFORMATION_COPY_OBJECT_H_
 #define SOURCE_FUZZ_TRANSFORMATION_COPY_OBJECT_H_
 
-#include "source/fuzz/fact_manager.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 {
@@ -47,16 +47,23 @@
   // - It must be legal to insert an OpCopyObject instruction directly
   //   before 'inst'.
   // - |message_.object| must be available directly before 'inst'.
-  bool IsApplicable(opt::IRContext* context,
-                    const FactManager& fact_manager) const override;
+  // - |message_.object| must not be a null pointer or undefined pointer (so as
+  //   to make it legal to load from copied pointers).
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
 
   // - A new instruction,
   //     %|message_.fresh_id| = OpCopyObject %ty %|message_.object|
   //   is added directly before the instruction at |message_.insert_after_id| +
   //   |message_|.offset, where %ty is the type of |message_.object|.
   // - The fact that |message_.fresh_id| and |message_.object| are synonyms
-  //   is added to the fact manager.
-  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+  //   is added to the fact manager in |transformation_context|.
+  // - If |message_.object| is a pointer whose pointee value is known to be
+  //   irrelevant, the analogous fact is added to the fact manager in
+  //   |transformation_context| about |message_.fresh_id|.
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
 
   protobufs::Transformation ToMessage() const override;
 
diff --git a/source/fuzz/transformation_equation_instruction.cpp b/source/fuzz/transformation_equation_instruction.cpp
new file mode 100644
index 0000000..5c31417
--- /dev/null
+++ b/source/fuzz/transformation_equation_instruction.cpp
@@ -0,0 +1,186 @@
+// 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_equation_instruction.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationEquationInstruction::TransformationEquationInstruction(
+    const spvtools::fuzz::protobufs::TransformationEquationInstruction& message)
+    : message_(message) {}
+
+TransformationEquationInstruction::TransformationEquationInstruction(
+    uint32_t fresh_id, SpvOp opcode, const std::vector<uint32_t>& in_operand_id,
+    const protobufs::InstructionDescriptor& instruction_to_insert_before) {
+  message_.set_fresh_id(fresh_id);
+  message_.set_opcode(opcode);
+  for (auto id : in_operand_id) {
+    message_.add_in_operand_id(id);
+  }
+  *message_.mutable_instruction_to_insert_before() =
+      instruction_to_insert_before;
+}
+
+bool TransformationEquationInstruction::IsApplicable(
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
+  // The result id must be fresh.
+  if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
+    return false;
+  }
+  // The instruction to insert before must exist.
+  auto insert_before =
+      FindInstruction(message_.instruction_to_insert_before(), ir_context);
+  if (!insert_before) {
+    return false;
+  }
+  // The input ids must all exist, not be OpUndef, 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) {
+      return false;
+    }
+    if (inst->opcode() == SpvOpUndef) {
+      return false;
+    }
+    if (!fuzzerutil::IdIsAvailableBeforeInstruction(ir_context, insert_before,
+                                                    id)) {
+      return false;
+    }
+  }
+
+  return MaybeGetResultType(ir_context) != 0;
+}
+
+void TransformationEquationInstruction::Apply(
+    opt::IRContext* ir_context,
+    TransformationContext* transformation_context) const {
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
+
+  opt::Instruction::OperandList in_operands;
+  std::vector<uint32_t> rhs_id;
+  for (auto id : message_.in_operand_id()) {
+    in_operands.push_back({SPV_OPERAND_TYPE_ID, {id}});
+    rhs_id.push_back(id);
+  }
+
+  FindInstruction(message_.instruction_to_insert_before(), ir_context)
+      ->InsertBefore(MakeUnique<opt::Instruction>(
+          ir_context, static_cast<SpvOp>(message_.opcode()),
+          MaybeGetResultType(ir_context), message_.fresh_id(), in_operands));
+
+  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+
+  transformation_context->GetFactManager()->AddFactIdEquation(
+      message_.fresh_id(), static_cast<SpvOp>(message_.opcode()), rhs_id,
+      ir_context);
+}
+
+protobufs::Transformation TransformationEquationInstruction::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_equation_instruction() = message_;
+  return result;
+}
+
+uint32_t TransformationEquationInstruction::MaybeGetResultType(
+    opt::IRContext* ir_context) const {
+  switch (static_cast<SpvOp>(message_.opcode())) {
+    case SpvOpIAdd:
+    case SpvOpISub: {
+      if (message_.in_operand_id().size() != 2) {
+        return 0;
+      }
+      uint32_t first_operand_width = 0;
+      uint32_t first_operand_type_id = 0;
+      for (uint32_t index = 0; index < 2; index++) {
+        auto operand_inst = ir_context->get_def_use_mgr()->GetDef(
+            message_.in_operand_id(index));
+        if (!operand_inst || !operand_inst->type_id()) {
+          return 0;
+        }
+        auto operand_type =
+            ir_context->get_type_mgr()->GetType(operand_inst->type_id());
+        if (!(operand_type->AsInteger() ||
+              (operand_type->AsVector() &&
+               operand_type->AsVector()->element_type()->AsInteger()))) {
+          return 0;
+        }
+        uint32_t operand_width =
+            operand_type->AsInteger()
+                ? 1
+                : operand_type->AsVector()->element_count();
+        if (index == 0) {
+          first_operand_width = operand_width;
+          first_operand_type_id = operand_inst->type_id();
+        } else {
+          assert(first_operand_width != 0 &&
+                 "The first operand should have been processed.");
+          if (operand_width != first_operand_width) {
+            return 0;
+          }
+        }
+      }
+      assert(first_operand_type_id != 0 &&
+             "A type must have been found for the first operand.");
+      return first_operand_type_id;
+    }
+    case SpvOpLogicalNot: {
+      if (message_.in_operand_id().size() != 1) {
+        return 0;
+      }
+      auto operand_inst =
+          ir_context->get_def_use_mgr()->GetDef(message_.in_operand_id(0));
+      if (!operand_inst || !operand_inst->type_id()) {
+        return 0;
+      }
+      auto operand_type =
+          ir_context->get_type_mgr()->GetType(operand_inst->type_id());
+      if (!(operand_type->AsBool() ||
+            (operand_type->AsVector() &&
+             operand_type->AsVector()->element_type()->AsBool()))) {
+        return 0;
+      }
+      return operand_inst->type_id();
+    }
+    case SpvOpSNegate: {
+      if (message_.in_operand_id().size() != 1) {
+        return 0;
+      }
+      auto operand_inst =
+          ir_context->get_def_use_mgr()->GetDef(message_.in_operand_id(0));
+      if (!operand_inst || !operand_inst->type_id()) {
+        return 0;
+      }
+      auto operand_type =
+          ir_context->get_type_mgr()->GetType(operand_inst->type_id());
+      if (!(operand_type->AsInteger() ||
+            (operand_type->AsVector() &&
+             operand_type->AsVector()->element_type()->AsInteger()))) {
+        return 0;
+      }
+      return operand_inst->type_id();
+    }
+
+    default:
+      assert(false && "Inappropriate opcode for equation instruction.");
+      return 0;
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_equation_instruction.h b/source/fuzz/transformation_equation_instruction.h
new file mode 100644
index 0000000..7eec9c6
--- /dev/null
+++ b/source/fuzz/transformation_equation_instruction.h
@@ -0,0 +1,78 @@
+// 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_EQUATION_INSTRUCTION_H_
+#define SOURCE_FUZZ_TRANSFORMATION_EQUATION_INSTRUCTION_H_
+
+#include <vector>
+
+#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 TransformationEquationInstruction : public Transformation {
+ public:
+  explicit TransformationEquationInstruction(
+      const protobufs::TransformationEquationInstruction& message);
+
+  TransformationEquationInstruction(
+      uint32_t fresh_id, SpvOp opcode,
+      const std::vector<uint32_t>& in_operand_id,
+      const protobufs::InstructionDescriptor& instruction_to_insert_before);
+
+  // - |message_.fresh_id| must be fresh.
+  // - |message_.instruction_to_insert_before| must identify an instruction
+  //   before which an equation instruction can legitimately be inserted.
+  // - Each id in |message_.in_operand_id| must exist, not be an OpUndef, and
+  //   be available before |message_.instruction_to_insert_before|.
+  // - |message_.opcode| must be an opcode for which we know how to handle
+  //   equations, the types of the ids in |message_.in_operand_id| must be
+  //   suitable for use with this opcode, and the module must contain an
+  //   appropriate result type id.
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // Adds an instruction to the module, right before
+  // |message_.instruction_to_insert_before|, of the form:
+  //
+  // |message_.fresh_id| = |message_.opcode| %type |message_.in_operand_ids|
+  //
+  // where %type is a type id that already exists in the module and that is
+  // compatible with the opcode and input operands.
+  //
+  // The fact manager is also updated to inform it of this equation fact.
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+ private:
+  // A helper that, in one fell swoop, checks that |message_.opcode| and the ids
+  // in |message_.in_operand_id| are compatible, and that the module contains
+  // an appropriate result type id.  If all is well, the result type id is
+  // returned.  Otherwise, 0 is returned.
+  uint32_t MaybeGetResultType(opt::IRContext* ir_context) const;
+
+  protobufs::TransformationEquationInstruction message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_EQUATION_INSTRUCTION_H_
diff --git a/source/fuzz/transformation_function_call.cpp b/source/fuzz/transformation_function_call.cpp
new file mode 100644
index 0000000..432634d
--- /dev/null
+++ b/source/fuzz/transformation_function_call.cpp
@@ -0,0 +1,189 @@
+// 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_function_call.h"
+
+#include "source/fuzz/call_graph.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationFunctionCall::TransformationFunctionCall(
+    const spvtools::fuzz::protobufs::TransformationFunctionCall& message)
+    : message_(message) {}
+
+TransformationFunctionCall::TransformationFunctionCall(
+    uint32_t fresh_id, uint32_t callee_id,
+    const std::vector<uint32_t>& argument_id,
+    const protobufs::InstructionDescriptor& instruction_to_insert_before) {
+  message_.set_fresh_id(fresh_id);
+  message_.set_callee_id(callee_id);
+  for (auto argument : argument_id) {
+    message_.add_argument_id(argument);
+  }
+  *message_.mutable_instruction_to_insert_before() =
+      instruction_to_insert_before;
+}
+
+bool TransformationFunctionCall::IsApplicable(
+    opt::IRContext* ir_context,
+    const TransformationContext& transformation_context) const {
+  // The result id must be fresh
+  if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
+    return false;
+  }
+
+  // The function must exist
+  auto callee_inst =
+      ir_context->get_def_use_mgr()->GetDef(message_.callee_id());
+  if (!callee_inst || callee_inst->opcode() != SpvOpFunction) {
+    return false;
+  }
+
+  // The function must not be an entry point
+  if (fuzzerutil::FunctionIsEntryPoint(ir_context, message_.callee_id())) {
+    return false;
+  }
+
+  auto callee_type_inst = ir_context->get_def_use_mgr()->GetDef(
+      callee_inst->GetSingleWordInOperand(1));
+  assert(callee_type_inst->opcode() == SpvOpTypeFunction &&
+         "Bad function type.");
+
+  // The number of expected function arguments must match the number of given
+  // arguments.  The number of expected arguments is one less than the function
+  // type's number of input operands, as one operand is for the return type.
+  if (callee_type_inst->NumInOperands() - 1 !=
+      static_cast<uint32_t>(message_.argument_id().size())) {
+    return false;
+  }
+
+  // The instruction descriptor must refer to a position where it is valid to
+  // insert the call
+  auto insert_before =
+      FindInstruction(message_.instruction_to_insert_before(), ir_context);
+  if (!insert_before) {
+    return false;
+  }
+  if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpFunctionCall,
+                                                    insert_before)) {
+    return false;
+  }
+
+  auto block = ir_context->get_instr_block(insert_before);
+  auto enclosing_function = block->GetParent();
+
+  // If the block is not dead, the function must be livesafe
+  bool block_is_dead =
+      transformation_context.GetFactManager()->BlockIsDead(block->id());
+  if (!block_is_dead &&
+      !transformation_context.GetFactManager()->FunctionIsLivesafe(
+          message_.callee_id())) {
+    return false;
+  }
+
+  // The ids must all match and have the right types and satisfy rules on
+  // pointers.  If the block is not dead, pointers must be arbitrary.
+  for (uint32_t arg_index = 0;
+       arg_index < static_cast<uint32_t>(message_.argument_id().size());
+       arg_index++) {
+    opt::Instruction* arg_inst =
+        ir_context->get_def_use_mgr()->GetDef(message_.argument_id(arg_index));
+    if (!arg_inst) {
+      // The given argument does not correspond to an instruction.
+      return false;
+    }
+    if (!arg_inst->type_id()) {
+      // The given argument does not have a type; it is thus not suitable.
+    }
+    if (arg_inst->type_id() !=
+        callee_type_inst->GetSingleWordInOperand(arg_index + 1)) {
+      // Argument type mismatch.
+      return false;
+    }
+    opt::Instruction* arg_type_inst =
+        ir_context->get_def_use_mgr()->GetDef(arg_inst->type_id());
+    if (arg_type_inst->opcode() == SpvOpTypePointer) {
+      switch (arg_inst->opcode()) {
+        case SpvOpFunctionParameter:
+        case SpvOpVariable:
+          // These are OK
+          break;
+        default:
+          // Other pointer ids cannot be passed as parameters
+          return false;
+      }
+      if (!block_is_dead &&
+          !transformation_context.GetFactManager()->PointeeValueIsIrrelevant(
+              arg_inst->result_id())) {
+        // This is not a dead block, so pointer parameters passed to the called
+        // function might really have their contents modified. We thus require
+        // such pointers to be to arbitrary-valued variables, which this is not.
+        return false;
+      }
+    }
+
+    // The argument id needs to be available (according to dominance rules) at
+    // the point where the call will occur.
+    if (!fuzzerutil::IdIsAvailableBeforeInstruction(ir_context, insert_before,
+                                                    arg_inst->result_id())) {
+      return false;
+    }
+  }
+
+  // Introducing the call must not lead to recursion.
+  if (message_.callee_id() == enclosing_function->result_id()) {
+    // This would be direct recursion.
+    return false;
+  }
+  // Ensure the call would not lead to indirect recursion.
+  return !CallGraph(ir_context)
+              .GetIndirectCallees(message_.callee_id())
+              .count(block->GetParent()->result_id());
+}
+
+void TransformationFunctionCall::Apply(
+    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
+  // Update the module's bound to reflect the fresh id for the result of the
+  // function call.
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
+  // Get the return type of the function being called.
+  uint32_t return_type =
+      ir_context->get_def_use_mgr()->GetDef(message_.callee_id())->type_id();
+  // Populate the operands to the call instruction, with the function id and the
+  // arguments.
+  opt::Instruction::OperandList operands;
+  operands.push_back({SPV_OPERAND_TYPE_ID, {message_.callee_id()}});
+  for (auto arg : message_.argument_id()) {
+    operands.push_back({SPV_OPERAND_TYPE_ID, {arg}});
+  }
+  // Insert the function call before the instruction specified in the message.
+  FindInstruction(message_.instruction_to_insert_before(), ir_context)
+      ->InsertBefore(MakeUnique<opt::Instruction>(
+          ir_context, SpvOpFunctionCall, return_type, message_.fresh_id(),
+          operands));
+  // Invalidate all analyses since we have changed the module.
+  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+}
+
+protobufs::Transformation TransformationFunctionCall::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_function_call() = message_;
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_function_call.h b/source/fuzz/transformation_function_call.h
new file mode 100644
index 0000000..4ad7db1
--- /dev/null
+++ b/source/fuzz/transformation_function_call.h
@@ -0,0 +1,67 @@
+// 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_FUNCTION_CALL_H_
+#define SOURCE_FUZZ_TRANSFORMATION_FUNCTION_CALL_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 TransformationFunctionCall : public Transformation {
+ public:
+  explicit TransformationFunctionCall(
+      const protobufs::TransformationFunctionCall& message);
+
+  TransformationFunctionCall(
+      uint32_t fresh_id, uint32_t callee_id,
+      const std::vector<uint32_t>& argument_id,
+      const protobufs::InstructionDescriptor& instruction_to_insert_before);
+
+  // - |message_.fresh_id| must be fresh
+  // - |message_.instruction_to_insert_before| must identify an instruction
+  //   before which an OpFunctionCall can be legitimately inserted
+  // - |message_.function_id| must be the id of a function, and calling the
+  //   function before the identified instruction must not introduce recursion
+  // - |message_.arg_id| must provide suitable arguments for the function call
+  //   (they must have the right types and be available according to dominance
+  //   rules)
+  // - If the insertion point is not in a dead block then |message_function_id|
+  //   must refer to a livesafe function, and every pointer argument in
+  //   |message_.arg_id| must refer to an arbitrary-valued variable
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // Adds an instruction of the form:
+  //   |fresh_id| = OpFunctionCall %type |callee_id| |arg_id...|
+  // before |instruction_to_insert_before|, where %type is the return type of
+  // |callee_id|.
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+ private:
+  protobufs::TransformationFunctionCall message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_FUNCTION_CALL_H_
diff --git a/source/fuzz/transformation_load.cpp b/source/fuzz/transformation_load.cpp
new file mode 100644
index 0000000..a260c33
--- /dev/null
+++ b/source/fuzz/transformation_load.cpp
@@ -0,0 +1,102 @@
+// 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_load.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationLoad::TransformationLoad(
+    const spvtools::fuzz::protobufs::TransformationLoad& message)
+    : message_(message) {}
+
+TransformationLoad::TransformationLoad(
+    uint32_t fresh_id, uint32_t pointer_id,
+    const protobufs::InstructionDescriptor& instruction_to_insert_before) {
+  message_.set_fresh_id(fresh_id);
+  message_.set_pointer_id(pointer_id);
+  *message_.mutable_instruction_to_insert_before() =
+      instruction_to_insert_before;
+}
+
+bool TransformationLoad::IsApplicable(
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
+  // The result id must be fresh.
+  if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
+    return false;
+  }
+
+  // The pointer must exist and have a type.
+  auto pointer = ir_context->get_def_use_mgr()->GetDef(message_.pointer_id());
+  if (!pointer || !pointer->type_id()) {
+    return false;
+  }
+  // The type must indeed be a pointer type.
+  auto pointer_type = ir_context->get_def_use_mgr()->GetDef(pointer->type_id());
+  assert(pointer_type && "Type id must be defined.");
+  if (pointer_type->opcode() != SpvOpTypePointer) {
+    return false;
+  }
+  // We do not want to allow loading from null or undefined pointers, as it is
+  // not clear how punishing the consequences of doing so are from a semantics
+  // point of view.
+  switch (pointer->opcode()) {
+    case SpvOpConstantNull:
+    case SpvOpUndef:
+      return false;
+    default:
+      break;
+  }
+
+  // Determine which instruction we should be inserting before.
+  auto insert_before =
+      FindInstruction(message_.instruction_to_insert_before(), ir_context);
+  // It must exist, ...
+  if (!insert_before) {
+    return false;
+  }
+  // ... and it must be legitimate to insert a store before it.
+  if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpLoad, insert_before)) {
+    return false;
+  }
+
+  // The pointer needs to be available at the insertion point.
+  return fuzzerutil::IdIsAvailableBeforeInstruction(ir_context, insert_before,
+                                                    message_.pointer_id());
+}
+
+void TransformationLoad::Apply(opt::IRContext* ir_context,
+                               TransformationContext* /*unused*/) const {
+  uint32_t result_type = fuzzerutil::GetPointeeTypeIdFromPointerType(
+      ir_context, fuzzerutil::GetTypeId(ir_context, message_.pointer_id()));
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
+  FindInstruction(message_.instruction_to_insert_before(), ir_context)
+      ->InsertBefore(MakeUnique<opt::Instruction>(
+          ir_context, SpvOpLoad, result_type, message_.fresh_id(),
+          opt::Instruction::OperandList(
+              {{SPV_OPERAND_TYPE_ID, {message_.pointer_id()}}})));
+  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+}
+
+protobufs::Transformation TransformationLoad::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_load() = message_;
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_load.h b/source/fuzz/transformation_load.h
new file mode 100644
index 0000000..4c7c00b
--- /dev/null
+++ b/source/fuzz/transformation_load.h
@@ -0,0 +1,61 @@
+// 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_LOAD_H_
+#define SOURCE_FUZZ_TRANSFORMATION_LOAD_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 TransformationLoad : public Transformation {
+ public:
+  explicit TransformationLoad(const protobufs::TransformationLoad& message);
+
+  TransformationLoad(
+      uint32_t fresh_id, uint32_t pointer_id,
+      const protobufs::InstructionDescriptor& instruction_to_insert_before);
+
+  // - |message_.fresh_id| must be fresh
+  // - |message_.pointer_id| must be the id of a pointer
+  // - The pointer must not be OpConstantNull or OpUndef
+  // - |message_.instruction_to_insert_before| must identify an instruction
+  //   before which it is valid to insert an OpLoad, and where
+  //   |message_.pointer_id| is available (according to dominance rules)
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // Adds an instruction of the form:
+  //   |message_.fresh_id| = OpLoad %type |message_.pointer_id|
+  // before the instruction identified by
+  // |message_.instruction_to_insert_before|, where %type is the pointer's
+  // pointee type.
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+ private:
+  protobufs::TransformationLoad message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_LOAD_H_
diff --git a/source/fuzz/transformation_merge_blocks.cpp b/source/fuzz/transformation_merge_blocks.cpp
index 316e80d..68ac092 100644
--- a/source/fuzz/transformation_merge_blocks.cpp
+++ b/source/fuzz/transformation_merge_blocks.cpp
@@ -29,40 +29,41 @@
 }
 
 bool TransformationMergeBlocks::IsApplicable(
-    opt::IRContext* context,
-    const spvtools::fuzz::FactManager& /*unused*/) const {
-  auto second_block = fuzzerutil::MaybeFindBlock(context, message_.block_id());
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
+  auto second_block =
+      fuzzerutil::MaybeFindBlock(ir_context, message_.block_id());
   // The given block must exist.
   if (!second_block) {
     return false;
   }
   // The block must have just one predecessor.
-  auto predecessors = context->cfg()->preds(second_block->id());
+  auto predecessors = ir_context->cfg()->preds(second_block->id());
   if (predecessors.size() != 1) {
     return false;
   }
-  auto first_block = context->cfg()->block(predecessors.at(0));
+  auto first_block = ir_context->cfg()->block(predecessors.at(0));
 
-  return opt::blockmergeutil::CanMergeWithSuccessor(context, first_block);
+  return opt::blockmergeutil::CanMergeWithSuccessor(ir_context, first_block);
 }
 
-void TransformationMergeBlocks::Apply(
-    opt::IRContext* context, spvtools::fuzz::FactManager* /*unused*/) const {
-  auto second_block = fuzzerutil::MaybeFindBlock(context, message_.block_id());
-  auto first_block =
-      context->cfg()->block(context->cfg()->preds(second_block->id()).at(0));
+void TransformationMergeBlocks::Apply(opt::IRContext* ir_context,
+                                      TransformationContext* /*unused*/) const {
+  auto second_block =
+      fuzzerutil::MaybeFindBlock(ir_context, message_.block_id());
+  auto first_block = ir_context->cfg()->block(
+      ir_context->cfg()->preds(second_block->id()).at(0));
 
   auto function = first_block->GetParent();
   // We need an iterator pointing to the predecessor, hence the loop.
   for (auto bi = function->begin(); bi != function->end(); ++bi) {
     if (bi->id() == first_block->id()) {
-      assert(opt::blockmergeutil::CanMergeWithSuccessor(context, &*bi) &&
+      assert(opt::blockmergeutil::CanMergeWithSuccessor(ir_context, &*bi) &&
              "Because 'Apply' should only be invoked if 'IsApplicable' holds, "
              "it must be possible to merge |bi| with its successor.");
-      opt::blockmergeutil::MergeWithSuccessor(context, function, bi);
+      opt::blockmergeutil::MergeWithSuccessor(ir_context, function, bi);
       // Invalidate all analyses, since we have changed the module
       // significantly.
-      context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+      ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
       return;
     }
   }
diff --git a/source/fuzz/transformation_merge_blocks.h b/source/fuzz/transformation_merge_blocks.h
index 86216db..1dc16d2 100644
--- a/source/fuzz/transformation_merge_blocks.h
+++ b/source/fuzz/transformation_merge_blocks.h
@@ -15,9 +15,9 @@
 #ifndef SOURCE_FUZZ_TRANSFORMATION_MERGE_BLOCKS_H_
 #define SOURCE_FUZZ_TRANSFORMATION_MERGE_BLOCKS_H_
 
-#include "source/fuzz/fact_manager.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 {
@@ -35,12 +35,14 @@
   // - b must be the sole successor of a
   // - Replacing a with the merge of a and b (and removing b) must lead to a
   //   valid module
-  bool IsApplicable(opt::IRContext* context,
-                    const FactManager& fact_manager) const override;
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
 
   // The contents of b are merged into a, and a's terminator is replaced with
   // the terminator of b.  Block b is removed from the module.
-  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
 
   protobufs::Transformation ToMessage() const override;
 
diff --git a/source/fuzz/transformation_move_block_down.cpp b/source/fuzz/transformation_move_block_down.cpp
index f181855..6c71ab7 100644
--- a/source/fuzz/transformation_move_block_down.cpp
+++ b/source/fuzz/transformation_move_block_down.cpp
@@ -28,10 +28,10 @@
 }
 
 bool TransformationMoveBlockDown::IsApplicable(
-    opt::IRContext* context, const FactManager& /*unused*/) const {
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
   // Go through every block in every function, looking for a block whose id
   // matches that of the block we want to consider moving down.
-  for (auto& function : *context->module()) {
+  for (auto& function : *ir_context->module()) {
     for (auto block_it = function.begin(); block_it != function.end();
          ++block_it) {
       if (block_it->id() == message_.block_id()) {
@@ -43,7 +43,7 @@
         }
         // Record the block we would like to consider moving down.
         opt::BasicBlock* block_matching_id = &*block_it;
-        if (!context->GetDominatorAnalysis(&function)->IsReachable(
+        if (!ir_context->GetDominatorAnalysis(&function)->IsReachable(
                 block_matching_id)) {
           // The block is not reachable.  We are not allowed to move it down.
           return false;
@@ -60,7 +60,7 @@
         opt::BasicBlock* next_block_in_program_order = &*block_it;
         // We can move the block of interest down if and only if it does not
         // dominate the block that comes next.
-        return !context->GetDominatorAnalysis(&function)->Dominates(
+        return !ir_context->GetDominatorAnalysis(&function)->Dominates(
             block_matching_id, next_block_in_program_order);
       }
     }
@@ -71,11 +71,11 @@
   return false;
 }
 
-void TransformationMoveBlockDown::Apply(opt::IRContext* context,
-                                        FactManager* /*unused*/) const {
+void TransformationMoveBlockDown::Apply(
+    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
   // Go through every block in every function, looking for a block whose id
   // matches that of the block we want to move down.
-  for (auto& function : *context->module()) {
+  for (auto& function : *ir_context->module()) {
     for (auto block_it = function.begin(); block_it != function.end();
          ++block_it) {
       if (block_it->id() == message_.block_id()) {
@@ -87,7 +87,7 @@
         // For performance, it is vital to keep the dominator analysis valid
         // (which due to https://github.com/KhronosGroup/SPIRV-Tools/issues/2889
         // requires keeping the CFG analysis valid).
-        context->InvalidateAnalysesExceptFor(
+        ir_context->InvalidateAnalysesExceptFor(
             opt::IRContext::Analysis::kAnalysisDefUse |
             opt::IRContext::Analysis::kAnalysisCFG |
             opt::IRContext::Analysis::kAnalysisDominatorAnalysis);
diff --git a/source/fuzz/transformation_move_block_down.h b/source/fuzz/transformation_move_block_down.h
index fd1584a..7551c38 100644
--- a/source/fuzz/transformation_move_block_down.h
+++ b/source/fuzz/transformation_move_block_down.h
@@ -15,9 +15,9 @@
 #ifndef SOURCE_FUZZ_TRANSFORMATION_MOVE_BLOCK_DOWN_H_
 #define SOURCE_FUZZ_TRANSFORMATION_MOVE_BLOCK_DOWN_H_
 
-#include "source/fuzz/fact_manager.h"
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
 #include "source/fuzz/transformation.h"
+#include "source/fuzz/transformation_context.h"
 #include "source/opt/ir_context.h"
 
 namespace spvtools {
@@ -35,12 +35,14 @@
   //   in a function.
   // - b must not dominate the block that follows it in program order.
   // - b must be reachable.
-  bool IsApplicable(opt::IRContext* context,
-                    const FactManager& fact_manager) const override;
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
 
   // The block with id |message_.block_id| is moved down; i.e. the program order
   // between it and the block that follows it is swapped.
-  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
 
   protobufs::Transformation ToMessage() const override;
 
diff --git a/source/fuzz/transformation_outline_function.cpp b/source/fuzz/transformation_outline_function.cpp
index b50b9c5..117cdc6 100644
--- a/source/fuzz/transformation_outline_function.cpp
+++ b/source/fuzz/transformation_outline_function.cpp
@@ -70,72 +70,71 @@
 }
 
 bool TransformationOutlineFunction::IsApplicable(
-    opt::IRContext* context,
-    const spvtools::fuzz::FactManager& /*unused*/) const {
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
   std::set<uint32_t> ids_used_by_this_transformation;
 
   // The various new ids used by the transformation must be fresh and distinct.
 
   if (!CheckIdIsFreshAndNotUsedByThisTransformation(
-          message_.new_function_struct_return_type_id(), context,
+          message_.new_function_struct_return_type_id(), ir_context,
           &ids_used_by_this_transformation)) {
     return false;
   }
 
   if (!CheckIdIsFreshAndNotUsedByThisTransformation(
-          message_.new_function_type_id(), context,
+          message_.new_function_type_id(), ir_context,
           &ids_used_by_this_transformation)) {
     return false;
   }
 
   if (!CheckIdIsFreshAndNotUsedByThisTransformation(
-          message_.new_function_id(), context,
+          message_.new_function_id(), ir_context,
           &ids_used_by_this_transformation)) {
     return false;
   }
 
   if (!CheckIdIsFreshAndNotUsedByThisTransformation(
-          message_.new_function_region_entry_block(), context,
+          message_.new_function_region_entry_block(), ir_context,
           &ids_used_by_this_transformation)) {
     return false;
   }
 
   if (!CheckIdIsFreshAndNotUsedByThisTransformation(
-          message_.new_caller_result_id(), context,
+          message_.new_caller_result_id(), ir_context,
           &ids_used_by_this_transformation)) {
     return false;
   }
 
   if (!CheckIdIsFreshAndNotUsedByThisTransformation(
-          message_.new_callee_result_id(), context,
+          message_.new_callee_result_id(), ir_context,
           &ids_used_by_this_transformation)) {
     return false;
   }
 
   for (auto& pair : message_.input_id_to_fresh_id()) {
     if (!CheckIdIsFreshAndNotUsedByThisTransformation(
-            pair.second(), context, &ids_used_by_this_transformation)) {
+            pair.second(), ir_context, &ids_used_by_this_transformation)) {
       return false;
     }
   }
 
   for (auto& pair : message_.output_id_to_fresh_id()) {
     if (!CheckIdIsFreshAndNotUsedByThisTransformation(
-            pair.second(), context, &ids_used_by_this_transformation)) {
+            pair.second(), ir_context, &ids_used_by_this_transformation)) {
       return false;
     }
   }
 
   // The entry and exit block ids must indeed refer to blocks.
   for (auto block_id : {message_.entry_block(), message_.exit_block()}) {
-    auto block_label = context->get_def_use_mgr()->GetDef(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 = context->cfg()->block(message_.entry_block());
-  auto exit_block = context->cfg()->block(message_.exit_block());
+  auto entry_block = ir_context->cfg()->block(message_.entry_block());
+  auto exit_block = ir_context->cfg()->block(message_.exit_block());
 
   // The entry block cannot start with OpVariable - this would mean that
   // outlining would remove a variable from the function containing the region
@@ -151,7 +150,7 @@
 
   // For simplicity, we do not allow the exit block to be a merge block or
   // continue target.
-  if (fuzzerutil::IsMergeOrContinue(context, exit_block->id())) {
+  if (fuzzerutil::IsMergeOrContinue(ir_context, exit_block->id())) {
     return false;
   }
 
@@ -169,14 +168,14 @@
 
   // The entry block must dominate the exit block.
   auto dominator_analysis =
-      context->GetDominatorAnalysis(entry_block->GetParent());
+      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 =
-      context->GetPostDominatorAnalysis(entry_block->GetParent());
+      ir_context->GetPostDominatorAnalysis(entry_block->GetParent());
   if (!postdominator_analysis->Dominates(exit_block, entry_block)) {
     return false;
   }
@@ -184,8 +183,9 @@
   // Find all the blocks dominated by |message_.entry_block| and post-dominated
   // by |message_.exit_block|.
   auto region_set = GetRegionBlocks(
-      context, entry_block = context->cfg()->block(message_.entry_block()),
-      exit_block = context->cfg()->block(message_.exit_block()));
+      ir_context,
+      entry_block = ir_context->cfg()->block(message_.entry_block()),
+      exit_block = ir_context->cfg()->block(message_.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
@@ -210,9 +210,9 @@
       // 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, context,
+      block.WhileEachSuccessorLabel([&all_successors_in_region, ir_context,
                                      &region_set](uint32_t successor) -> bool {
-        if (region_set.count(context->cfg()->block(successor)) == 0) {
+        if (region_set.count(ir_context->cfg()->block(successor)) == 0) {
           all_successors_in_region = false;
           return false;
         }
@@ -227,7 +227,8 @@
       // The block is a loop or selection header -- the header and its
       // associated merge block had better both be in the region or both be
       // outside the region.
-      auto merge_block = context->cfg()->block(merge->GetSingleWordOperand(0));
+      auto merge_block =
+          ir_context->cfg()->block(merge->GetSingleWordOperand(0));
       if (region_set.count(&block) != region_set.count(merge_block)) {
         return false;
       }
@@ -236,7 +237,7 @@
     if (auto loop_merge = block.GetLoopMergeInst()) {
       // Similar to the above, but for the continue target of a loop.
       auto continue_target =
-          context->cfg()->block(loop_merge->GetSingleWordOperand(1));
+          ir_context->cfg()->block(loop_merge->GetSingleWordOperand(1));
       if (continue_target != exit_block &&
           region_set.count(&block) != region_set.count(continue_target)) {
         return false;
@@ -248,17 +249,27 @@
   // used inside the region, ...
   std::map<uint32_t, uint32_t> input_id_to_fresh_id_map =
       PairSequenceToMap(message_.input_id_to_fresh_id());
-  for (auto id : GetRegionInputIds(context, region_set, exit_block)) {
+  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) {
       return false;
     }
-    // Furthermore, no region input id is allowed to be the result of an access
-    // chain.  This is because region input ids will become function parameters,
-    // and it is not legal to pass an access chain as a function parameter.
-    if (context->get_def_use_mgr()->GetDef(id)->opcode() == SpvOpAccessChain) {
-      return false;
+    // Furthermore, if the input id has pointer type it must be an OpVariable
+    // or OpFunctionParameter.
+    auto input_id_inst = ir_context->get_def_use_mgr()->GetDef(id);
+    if (ir_context->get_def_use_mgr()
+            ->GetDef(input_id_inst->type_id())
+            ->opcode() == SpvOpTypePointer) {
+      switch (input_id_inst->opcode()) {
+        case SpvOpFunctionParameter:
+        case SpvOpVariable:
+          // These are OK.
+          break;
+        default:
+          // Anything else is not OK.
+          return false;
+      }
     }
   }
 
@@ -267,7 +278,7 @@
   // can hold the value for this id computed in the outlined function.
   std::map<uint32_t, uint32_t> output_id_to_fresh_id_map =
       PairSequenceToMap(message_.output_id_to_fresh_id());
-  for (auto id : GetRegionOutputIds(context, region_set, exit_block)) {
+  for (auto id : GetRegionOutputIds(ir_context, region_set, exit_block)) {
     if (output_id_to_fresh_id_map.count(id) == 0) {
       return false;
     }
@@ -277,25 +288,26 @@
 }
 
 void TransformationOutlineFunction::Apply(
-    opt::IRContext* context, spvtools::fuzz::FactManager* fact_manager) const {
+    opt::IRContext* ir_context,
+    TransformationContext* transformation_context) const {
   // The entry block for the region before outlining.
   auto original_region_entry_block =
-      context->cfg()->block(message_.entry_block());
+      ir_context->cfg()->block(message_.entry_block());
 
   // The exit block for the region before outlining.
   auto original_region_exit_block =
-      context->cfg()->block(message_.exit_block());
+      ir_context->cfg()->block(message_.exit_block());
 
   // The single-entry single-exit region defined by |message_.entry_block| and
   // |message_.exit_block|.
   std::set<opt::BasicBlock*> region_blocks = GetRegionBlocks(
-      context, original_region_entry_block, original_region_exit_block);
+      ir_context, original_region_entry_block, original_region_exit_block);
 
   // Input and output ids for the region being outlined.
   std::vector<uint32_t> region_input_ids =
-      GetRegionInputIds(context, region_blocks, original_region_exit_block);
+      GetRegionInputIds(ir_context, region_blocks, original_region_exit_block);
   std::vector<uint32_t> region_output_ids =
-      GetRegionOutputIds(context, region_blocks, original_region_exit_block);
+      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 =
@@ -303,14 +315,14 @@
   std::map<uint32_t, uint32_t> output_id_to_fresh_id_map =
       PairSequenceToMap(message_.output_id_to_fresh_id());
 
-  UpdateModuleIdBoundForFreshIds(context, input_id_to_fresh_id_map,
+  UpdateModuleIdBoundForFreshIds(ir_context, input_id_to_fresh_id_map,
                                  output_id_to_fresh_id_map);
 
   // Construct a map that associates each output id with its type id.
   std::map<uint32_t, uint32_t> output_id_to_type_id;
   for (uint32_t output_id : region_output_ids) {
     output_id_to_type_id[output_id] =
-        context->get_def_use_mgr()->GetDef(output_id)->type_id();
+        ir_context->get_def_use_mgr()->GetDef(output_id)->type_id();
   }
 
   // The region will be collapsed to a single block that calls a function
@@ -321,45 +333,55 @@
   // collapsed block later.
   std::unique_ptr<opt::Instruction> cloned_exit_block_terminator =
       std::unique_ptr<opt::Instruction>(
-          original_region_exit_block->terminator()->Clone(context));
+          original_region_exit_block->terminator()->Clone(ir_context));
   std::unique_ptr<opt::Instruction> cloned_exit_block_merge =
       original_region_exit_block->GetMergeInst()
           ? std::unique_ptr<opt::Instruction>(
-                original_region_exit_block->GetMergeInst()->Clone(context))
+                original_region_exit_block->GetMergeInst()->Clone(ir_context))
           : nullptr;
 
   // Make a function prototype for the outlined function, which involves
   // figuring out its required type.
   std::unique_ptr<opt::Function> outlined_function = PrepareFunctionPrototype(
-      context, region_input_ids, region_output_ids, input_id_to_fresh_id_map);
+      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.
   RemapInputAndOutputIdsInRegion(
-      context, *original_region_exit_block, region_blocks, region_input_ids,
+      ir_context, *original_region_exit_block, region_blocks, region_input_ids,
       region_output_ids, input_id_to_fresh_id_map, output_id_to_fresh_id_map);
 
   // Fill out the body of the outlined function according to the region that is
   // being outlined.
-  PopulateOutlinedFunction(*original_region_entry_block,
-                           *original_region_exit_block, region_blocks,
-                           region_output_ids, output_id_to_fresh_id_map,
-                           context, outlined_function.get(), fact_manager);
+  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);
 
   // Collapse the region that has been outlined into a function down to a single
   // block that calls said function.
   ShrinkOriginalRegion(
-      context, region_blocks, region_input_ids, region_output_ids,
+      ir_context, region_blocks, region_input_ids, region_output_ids,
       output_id_to_type_id, outlined_function->type_id(),
       std::move(cloned_exit_block_merge),
       std::move(cloned_exit_block_terminator), original_region_entry_block);
 
   // Add the outlined function to the module.
-  context->module()->AddFunction(std::move(outlined_function));
+  ir_context->module()->AddFunction(std::move(outlined_function));
 
   // Major surgery has been conducted on the module, so invalidate all analyses.
-  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+  ir_context->InvalidateAnalysesExceptFor(
+      opt::IRContext::Analysis::kAnalysisNone);
 }
 
 protobufs::Transformation TransformationOutlineFunction::ToMessage() const {
@@ -368,45 +390,32 @@
   return result;
 }
 
-bool TransformationOutlineFunction::
-    CheckIdIsFreshAndNotUsedByThisTransformation(
-        uint32_t id, opt::IRContext* context,
-        std::set<uint32_t>* ids_used_by_this_transformation) const {
-  if (!fuzzerutil::IsFreshId(context, id)) {
-    return false;
-  }
-  if (ids_used_by_this_transformation->count(id) != 0) {
-    return false;
-  }
-  ids_used_by_this_transformation->insert(id);
-  return true;
-}
-
 std::vector<uint32_t> TransformationOutlineFunction::GetRegionInputIds(
-    opt::IRContext* context, const std::set<opt::BasicBlock*>& region_set,
+    opt::IRContext* ir_context, const std::set<opt::BasicBlock*>& region_set,
     opt::BasicBlock* region_exit_block) {
   std::vector<uint32_t> result;
 
   auto enclosing_function = region_exit_block->GetParent();
 
   // Consider each parameter of the function containing the region.
-  enclosing_function->ForEachParam([context, &region_set, &result](
-                                       opt::Instruction* function_parameter) {
-    // Consider every use of the parameter.
-    context->get_def_use_mgr()->WhileEachUse(
-        function_parameter, [context, function_parameter, &region_set, &result](
-                                opt::Instruction* use, uint32_t /*unused*/) {
-          // Get the block, if any, in which the parameter is used.
-          auto use_block = context->get_instr_block(use);
-          // If the use is in a block that lies within the region, the
-          // parameter is an input id for the region.
-          if (use_block && region_set.count(use_block) != 0) {
-            result.push_back(function_parameter->result_id());
-            return false;
-          }
-          return true;
-        });
-  });
+  enclosing_function->ForEachParam(
+      [ir_context, &region_set, &result](opt::Instruction* function_parameter) {
+        // Consider every use of the parameter.
+        ir_context->get_def_use_mgr()->WhileEachUse(
+            function_parameter,
+            [ir_context, function_parameter, &region_set, &result](
+                opt::Instruction* use, uint32_t /*unused*/) {
+              // Get the block, if any, in which the parameter is used.
+              auto use_block = ir_context->get_instr_block(use);
+              // If the use is in a block that lies within the region, the
+              // parameter is an input id for the region.
+              if (use_block && region_set.count(use_block) != 0) {
+                result.push_back(function_parameter->result_id());
+                return false;
+              }
+              return true;
+            });
+      });
 
   // Consider all definitions in the function that might turn out to be input
   // ids.
@@ -426,15 +435,15 @@
     // Consider each candidate input id to check whether it is used in the
     // region.
     for (auto& inst : candidate_input_ids_for_block) {
-      context->get_def_use_mgr()->WhileEachUse(
+      ir_context->get_def_use_mgr()->WhileEachUse(
           inst,
-          [context, &inst, region_exit_block, &region_set, &result](
+          [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.
-            auto use_block = context->get_instr_block(use);
+            auto use_block = ir_context->get_instr_block(use);
 
             if (!use_block) {
               // There might be no containing block, e.g. if the use is in a
@@ -463,7 +472,7 @@
 }
 
 std::vector<uint32_t> TransformationOutlineFunction::GetRegionOutputIds(
-    opt::IRContext* context, const std::set<opt::BasicBlock*>& region_set,
+    opt::IRContext* ir_context, const std::set<opt::BasicBlock*>& region_set,
     opt::BasicBlock* region_exit_block) {
   std::vector<uint32_t> result;
 
@@ -475,15 +484,15 @@
     }
     // Consider each use of each instruction defined in the block.
     for (auto& inst : block) {
-      context->get_def_use_mgr()->WhileEachUse(
+      ir_context->get_def_use_mgr()->WhileEachUse(
           &inst,
-          [&region_set, context, &inst, region_exit_block, &result](
+          [&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.
-            auto use_block = context->get_instr_block(use);
+            auto use_block = ir_context->get_instr_block(use);
 
             if (!use_block) {
               // There might be no containing block, e.g. if the use is in a
@@ -509,12 +518,13 @@
 }
 
 std::set<opt::BasicBlock*> TransformationOutlineFunction::GetRegionBlocks(
-    opt::IRContext* context, opt::BasicBlock* entry_block,
+    opt::IRContext* ir_context, opt::BasicBlock* entry_block,
     opt::BasicBlock* exit_block) {
   auto enclosing_function = entry_block->GetParent();
-  auto dominator_analysis = context->GetDominatorAnalysis(enclosing_function);
+  auto dominator_analysis =
+      ir_context->GetDominatorAnalysis(enclosing_function);
   auto postdominator_analysis =
-      context->GetPostDominatorAnalysis(enclosing_function);
+      ir_context->GetPostDominatorAnalysis(enclosing_function);
 
   std::set<opt::BasicBlock*> result;
   for (auto& block : *enclosing_function) {
@@ -528,9 +538,11 @@
 
 std::unique_ptr<opt::Function>
 TransformationOutlineFunction::PrepareFunctionPrototype(
-    opt::IRContext* context, const std::vector<uint32_t>& region_input_ids,
+    const std::vector<uint32_t>& region_input_ids,
     const std::vector<uint32_t>& region_output_ids,
-    const std::map<uint32_t, uint32_t>& input_id_to_fresh_id_map) const {
+    const std::map<uint32_t, uint32_t>& input_id_to_fresh_id_map,
+    opt::IRContext* ir_context,
+    TransformationContext* transformation_context) const {
   uint32_t return_type_id = 0;
   uint32_t function_type_id = 0;
 
@@ -540,15 +552,16 @@
   // not exist there cannot already be a function type with this struct as its
   // return type.
   if (region_output_ids.empty()) {
+    std::vector<uint32_t> return_and_parameter_types;
     opt::analysis::Void void_type;
-    return_type_id = context->get_type_mgr()->GetId(&void_type);
-    std::vector<const opt::analysis::Type*> argument_types;
+    return_type_id = ir_context->get_type_mgr()->GetId(&void_type);
+    return_and_parameter_types.push_back(return_type_id);
     for (auto id : region_input_ids) {
-      argument_types.push_back(context->get_type_mgr()->GetType(
-          context->get_def_use_mgr()->GetDef(id)->type_id()));
+      return_and_parameter_types.push_back(
+          ir_context->get_def_use_mgr()->GetDef(id)->type_id());
     }
-    opt::analysis::Function function_type(&void_type, argument_types);
-    function_type_id = context->get_type_mgr()->GetId(&function_type);
+    function_type_id =
+        fuzzerutil::FindFunctionType(ir_context, return_and_parameter_types);
   }
 
   // If no existing function type was found, we need to create one.
@@ -562,12 +575,12 @@
       opt::Instruction::OperandList struct_member_types;
       for (uint32_t output_id : region_output_ids) {
         auto output_id_type =
-            context->get_def_use_mgr()->GetDef(output_id)->type_id();
+            ir_context->get_def_use_mgr()->GetDef(output_id)->type_id();
         struct_member_types.push_back({SPV_OPERAND_TYPE_ID, {output_id_type}});
       }
       // Add a new struct type to the module.
-      context->module()->AddType(MakeUnique<opt::Instruction>(
-          context, SpvOpTypeStruct, 0,
+      ir_context->module()->AddType(MakeUnique<opt::Instruction>(
+          ir_context, SpvOpTypeStruct, 0,
           message_.new_function_struct_return_type_id(),
           std::move(struct_member_types)));
       // The return type for the function is the newly-created struct.
@@ -583,12 +596,12 @@
     for (auto id : region_input_ids) {
       function_type_operands.push_back(
           {SPV_OPERAND_TYPE_ID,
-           {context->get_def_use_mgr()->GetDef(id)->type_id()}});
+           {ir_context->get_def_use_mgr()->GetDef(id)->type_id()}});
     }
     // Add a new function type to the module, and record that this is the type
     // id for the new function.
-    context->module()->AddType(MakeUnique<opt::Instruction>(
-        context, SpvOpTypeFunction, 0, message_.new_function_type_id(),
+    ir_context->module()->AddType(MakeUnique<opt::Instruction>(
+        ir_context, SpvOpTypeFunction, 0, message_.new_function_type_id(),
         function_type_operands));
     function_type_id = message_.new_function_type_id();
   }
@@ -597,7 +610,7 @@
   // and the return type and function type prepared above.
   std::unique_ptr<opt::Function> outlined_function =
       MakeUnique<opt::Function>(MakeUnique<opt::Instruction>(
-          context, SpvOpFunction, return_type_id, message_.new_function_id(),
+          ir_context, SpvOpFunction, return_type_id, message_.new_function_id(),
           opt::Instruction::OperandList(
               {{spv_operand_type_t ::SPV_OPERAND_TYPE_LITERAL_INTEGER,
                 {SpvFunctionControlMaskNone}},
@@ -608,40 +621,48 @@
   // provided in |input_id_to_fresh_id_map|.
   for (auto id : region_input_ids) {
     outlined_function->AddParameter(MakeUnique<opt::Instruction>(
-        context, SpvOpFunctionParameter,
-        context->get_def_use_mgr()->GetDef(id)->type_id(),
+        ir_context, SpvOpFunctionParameter,
+        ir_context->get_def_use_mgr()->GetDef(id)->type_id(),
         input_id_to_fresh_id_map.at(id), opt::Instruction::OperandList()));
+    // If the input id is an irrelevant-valued variable, the same should be true
+    // of the corresponding parameter.
+    if (transformation_context->GetFactManager()->PointeeValueIsIrrelevant(
+            id)) {
+      transformation_context->GetFactManager()
+          ->AddFactValueOfPointeeIsIrrelevant(input_id_to_fresh_id_map.at(id));
+    }
   }
 
   return outlined_function;
 }
 
 void TransformationOutlineFunction::UpdateModuleIdBoundForFreshIds(
-    opt::IRContext* context,
+    opt::IRContext* ir_context,
     const std::map<uint32_t, uint32_t>& input_id_to_fresh_id_map,
     const std::map<uint32_t, uint32_t>& output_id_to_fresh_id_map) const {
   // Enlarge the module's id bound as needed to accommodate the various fresh
   // ids associated with the transformation.
   fuzzerutil::UpdateModuleIdBound(
-      context, message_.new_function_struct_return_type_id());
-  fuzzerutil::UpdateModuleIdBound(context, message_.new_function_type_id());
-  fuzzerutil::UpdateModuleIdBound(context, message_.new_function_id());
-  fuzzerutil::UpdateModuleIdBound(context,
+      ir_context, message_.new_function_struct_return_type_id());
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.new_function_type_id());
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.new_function_id());
+  fuzzerutil::UpdateModuleIdBound(ir_context,
                                   message_.new_function_region_entry_block());
-  fuzzerutil::UpdateModuleIdBound(context, message_.new_caller_result_id());
-  fuzzerutil::UpdateModuleIdBound(context, message_.new_callee_result_id());
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.new_caller_result_id());
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.new_callee_result_id());
 
   for (auto& entry : input_id_to_fresh_id_map) {
-    fuzzerutil::UpdateModuleIdBound(context, entry.second);
+    fuzzerutil::UpdateModuleIdBound(ir_context, entry.second);
   }
 
   for (auto& entry : output_id_to_fresh_id_map) {
-    fuzzerutil::UpdateModuleIdBound(context, entry.second);
+    fuzzerutil::UpdateModuleIdBound(ir_context, entry.second);
   }
 }
 
 void TransformationOutlineFunction::RemapInputAndOutputIdsInRegion(
-    opt::IRContext* context, const opt::BasicBlock& original_region_exit_block,
+    opt::IRContext* ir_context,
+    const opt::BasicBlock& original_region_exit_block,
     const std::set<opt::BasicBlock*>& region_blocks,
     const std::vector<uint32_t>& region_input_ids,
     const std::vector<uint32_t>& region_output_ids,
@@ -652,11 +673,11 @@
   // This is done by considering each region input id in turn.
   for (uint32_t id : region_input_ids) {
     // We then consider each use of the input id.
-    context->get_def_use_mgr()->ForEachUse(
-        id, [context, id, &input_id_to_fresh_id_map, region_blocks](
+    ir_context->get_def_use_mgr()->ForEachUse(
+        id, [ir_context, id, &input_id_to_fresh_id_map, region_blocks](
                 opt::Instruction* use, uint32_t operand_index) {
           // Find the block in which this use of the input id occurs.
-          opt::BasicBlock* use_block = context->get_instr_block(use);
+          opt::BasicBlock* use_block = ir_context->get_instr_block(use);
           // We want to rewrite the use id if its block occurs in the outlined
           // region.
           if (region_blocks.count(use_block) != 0) {
@@ -672,12 +693,12 @@
   // This is done by considering each region output id in turn.
   for (uint32_t id : region_output_ids) {
     // First consider each use of the output id and update the relevant uses.
-    context->get_def_use_mgr()->ForEachUse(
-        id,
-        [context, &original_region_exit_block, id, &output_id_to_fresh_id_map,
-         region_blocks](opt::Instruction* use, uint32_t operand_index) {
+    ir_context->get_def_use_mgr()->ForEachUse(
+        id, [ir_context, &original_region_exit_block, id,
+             &output_id_to_fresh_id_map,
+             region_blocks](opt::Instruction* use, uint32_t operand_index) {
           // Find the block in which this use of the output id occurs.
-          auto use_block = context->get_instr_block(use);
+          auto use_block = ir_context->get_instr_block(use);
           // We want to rewrite the use id if its block occurs in the outlined
           // region, with one exception: the terminator of the exit block of
           // the region is going to remain in the original function, so if the
@@ -698,7 +719,7 @@
     // defines the corresponding fresh id.  We do this after changing all the
     // uses so that the definition of the original id is still registered when
     // we analyse its uses.
-    context->get_def_use_mgr()->GetDef(id)->SetResultId(
+    ir_context->get_def_use_mgr()->GetDef(id)->SetResultId(
         output_id_to_fresh_id_map.at(id));
   }
 }
@@ -709,8 +730,8 @@
     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_fresh_id_map,
-    opt::IRContext* context, opt::Function* outlined_function,
-    FactManager* fact_manager) const {
+    opt::IRContext* ir_context, opt::Function* outlined_function,
+    TransformationContext* transformation_context) 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;
@@ -720,14 +741,16 @@
   // |message_.new_function_region_entry_block| as its id.
   std::unique_ptr<opt::BasicBlock> outlined_region_entry_block =
       MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>(
-          context, SpvOpLabel, 0, message_.new_function_region_entry_block(),
+          ir_context, SpvOpLabel, 0, message_.new_function_region_entry_block(),
           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 (fact_manager->BlockIsDead(original_region_entry_block.id())) {
-    fact_manager->AddFactBlockIsDead(outlined_region_entry_block->id());
+  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) {
@@ -736,7 +759,7 @@
 
   for (auto& inst : original_region_entry_block) {
     outlined_region_entry_block->AddInstruction(
-        std::unique_ptr<opt::Instruction>(inst.Clone(context)));
+        std::unique_ptr<opt::Instruction>(inst.Clone(ir_context)));
   }
   outlined_function->AddBasicBlock(std::move(outlined_region_entry_block));
 
@@ -755,7 +778,7 @@
     }
     // Clone the block so that it can be added to the new function.
     auto cloned_block =
-        std::unique_ptr<opt::BasicBlock>(block_it->Clone(context));
+        std::unique_ptr<opt::BasicBlock>(block_it->Clone(ir_context));
 
     // If this is the region's exit block, then the cloned block is the outlined
     // region's exit block.
@@ -811,7 +834,7 @@
     // The case where there are no region output ids is simple: we just add
     // OpReturn.
     outlined_region_exit_block->AddInstruction(MakeUnique<opt::Instruction>(
-        context, SpvOpReturn, 0, 0, opt::Instruction::OperandList()));
+        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
@@ -822,21 +845,21 @@
           {SPV_OPERAND_TYPE_ID, {output_id_to_fresh_id_map.at(id)}});
     }
     outlined_region_exit_block->AddInstruction(MakeUnique<opt::Instruction>(
-        context, SpvOpCompositeConstruct,
+        ir_context, SpvOpCompositeConstruct,
         message_.new_function_struct_return_type_id(),
         message_.new_callee_result_id(), struct_member_operands));
     outlined_region_exit_block->AddInstruction(MakeUnique<opt::Instruction>(
-        context, SpvOpReturnValue, 0, 0,
+        ir_context, SpvOpReturnValue, 0, 0,
         opt::Instruction::OperandList(
             {{SPV_OPERAND_TYPE_ID, {message_.new_callee_result_id()}}})));
   }
 
   outlined_function->SetFunctionEnd(MakeUnique<opt::Instruction>(
-      context, SpvOpFunctionEnd, 0, 0, opt::Instruction::OperandList()));
+      ir_context, SpvOpFunctionEnd, 0, 0, opt::Instruction::OperandList()));
 }
 
 void TransformationOutlineFunction::ShrinkOriginalRegion(
-    opt::IRContext* context, std::set<opt::BasicBlock*>& region_blocks,
+    opt::IRContext* ir_context, 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,
@@ -900,7 +923,7 @@
   }
 
   original_region_entry_block->AddInstruction(MakeUnique<opt::Instruction>(
-      context, SpvOpFunctionCall, return_type_id,
+      ir_context, SpvOpFunctionCall, return_type_id,
       message_.new_caller_result_id(), function_call_operands));
 
   // If there are output ids, the function call will return a struct.  For each
@@ -909,7 +932,7 @@
   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>(
-        context, SpvOpCompositeExtract, output_id_to_type_id.at(output_id),
+        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()}},
diff --git a/source/fuzz/transformation_outline_function.h b/source/fuzz/transformation_outline_function.h
index b59e660..ba439c8 100644
--- a/source/fuzz/transformation_outline_function.h
+++ b/source/fuzz/transformation_outline_function.h
@@ -19,9 +19,9 @@
 #include <set>
 #include <vector>
 
-#include "source/fuzz/fact_manager.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 {
@@ -58,8 +58,9 @@
   //   defined outside the region but used in the region
   // - |message_.output_id_to_fresh_id| must contain an entry for every id
   //   defined in the region but used outside the region
-  bool IsApplicable(opt::IRContext* context,
-                    const FactManager& fact_manager) const override;
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
 
   // - A new function with id |message_.new_function_id| is added to the module.
   // - If the region generates output ids, the return type of this function is
@@ -95,14 +96,15 @@
   //   |message_.new_function_struct_return_type| comprised of all the fresh
   //   output ids (unless the return type is void, in which case no value is
   //   returned.
-  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
 
   protobufs::Transformation ToMessage() 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* context, opt::BasicBlock* entry_block,
+      opt::IRContext* ir_context, opt::BasicBlock* entry_block,
       opt::BasicBlock* exit_block);
 
   // Yields ids that are used in |region_set| and that are either parameters
@@ -114,7 +116,7 @@
   // - id uses in OpPhi instructions in |region_entry_block| are ignored
   // - id uses in the terminator instruction of |region_exit_block| are ignored
   static std::vector<uint32_t> GetRegionInputIds(
-      opt::IRContext* context, const std::set<opt::BasicBlock*>& region_set,
+      opt::IRContext* ir_context, const std::set<opt::BasicBlock*>& region_set,
       opt::BasicBlock* region_exit_block);
 
   // Yields all ids that are defined in |region_set| and used outside
@@ -124,23 +126,14 @@
   // - ids defined in the region and used in the terminator of
   //   |region_exit_block| count as output ids
   static std::vector<uint32_t> GetRegionOutputIds(
-      opt::IRContext* context, const std::set<opt::BasicBlock*>& region_set,
+      opt::IRContext* ir_context, const std::set<opt::BasicBlock*>& region_set,
       opt::BasicBlock* region_exit_block);
 
  private:
-  // A helper method for the applicability check.  Returns true if and only if
-  // |id| is (a) a fresh id for the module, and (b) an id that has not
-  // previously been subject to this check.  We use this to check whether the
-  // ids given for the transformation are not only fresh but also different from
-  // one another.
-  bool CheckIdIsFreshAndNotUsedByThisTransformation(
-      uint32_t id, opt::IRContext* context,
-      std::set<uint32_t>* ids_used_by_this_transformation) const;
-
   // Ensures that the module's id bound is at least the maximum of any fresh id
   // associated with the transformation.
   void UpdateModuleIdBoundForFreshIds(
-      opt::IRContext* context,
+      opt::IRContext* ir_context,
       const std::map<uint32_t, uint32_t>& input_id_to_fresh_id_map,
       const std::map<uint32_t, uint32_t>& output_id_to_fresh_id_map) const;
 
@@ -151,7 +144,7 @@
   // modified, and |original_region_exit_block| allows for some special cases
   // where ids should not be remapped.
   void RemapInputAndOutputIdsInRegion(
-      opt::IRContext* context,
+      opt::IRContext* ir_context,
       const opt::BasicBlock& original_region_exit_block,
       const std::set<opt::BasicBlock*>& region_blocks,
       const std::vector<uint32_t>& region_input_ids,
@@ -167,10 +160,16 @@
   // A new struct type to represent the function return type, and a new function
   // type for the function, will be added to the module (unless suitable types
   // are already present).
+  //
+  // Facts about the function containing the outlined region that are relevant
+  // to the new function are propagated via the vact manager in
+  // |transformation_context|.
   std::unique_ptr<opt::Function> PrepareFunctionPrototype(
-      opt::IRContext* context, const std::vector<uint32_t>& region_input_ids,
+      const std::vector<uint32_t>& region_input_ids,
       const std::vector<uint32_t>& region_output_ids,
-      const std::map<uint32_t, uint32_t>& input_id_to_fresh_id_map) const;
+      const std::map<uint32_t, uint32_t>& input_id_to_fresh_id_map,
+      opt::IRContext* ir_context,
+      TransformationContext* transformation_context) const;
 
   // Creates the body of the outlined function by cloning blocks from the
   // original region, given by |region_blocks|, adapting the cloned version
@@ -179,17 +178,17 @@
   // clone.  Parameters |region_output_ids| and |output_id_to_fresh_id_map| are
   // used to determine what the function should return.
   //
-  // The |fact_manager| argument allow facts about blocks being outlined, e.g.
-  // whether they are dead blocks, to be asserted about blocks that get created
-  // during outlining.
+  // The |transformation_context| argument allow facts about blocks being
+  // outlined, e.g. whether they are dead blocks, to be asserted about blocks
+  // that get created during outlining.
   void PopulateOutlinedFunction(
       const opt::BasicBlock& original_region_entry_block,
       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_fresh_id_map,
-      opt::IRContext* context, opt::Function* outlined_function,
-      FactManager* fact_manager) const;
+      opt::IRContext* ir_context, opt::Function* outlined_function,
+      TransformationContext* transformation_context) 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
@@ -208,7 +207,7 @@
   // function is called, this information cannot be gotten from the def-use
   // manager.
   void ShrinkOriginalRegion(
-      opt::IRContext* context, std::set<opt::BasicBlock*>& region_blocks,
+      opt::IRContext* ir_context, 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
new file mode 100644
index 0000000..0f1220e
--- /dev/null
+++ b/source/fuzz/transformation_permute_function_parameters.cpp
@@ -0,0 +1,185 @@
+// 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 <unordered_set>
+#include <vector>
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/transformation_permute_function_parameters.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationPermuteFunctionParameters::
+    TransformationPermuteFunctionParameters(
+        const spvtools::fuzz::protobufs::
+            TransformationPermuteFunctionParameters& message)
+    : message_(message) {}
+
+TransformationPermuteFunctionParameters::
+    TransformationPermuteFunctionParameters(
+        uint32_t function_id, uint32_t new_type_id,
+        const std::vector<uint32_t>& permutation) {
+  message_.set_function_id(function_id);
+  message_.set_new_type_id(new_type_id);
+
+  for (auto index : permutation) {
+    message_.add_permutation(index);
+  }
+}
+
+bool TransformationPermuteFunctionParameters::IsApplicable(
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
+  // Check that function exists
+  const auto* function =
+      fuzzerutil::FindFunction(ir_context, message_.function_id());
+  if (!function || function->DefInst().opcode() != SpvOpFunction ||
+      fuzzerutil::FunctionIsEntryPoint(ir_context, function->result_id())) {
+    return false;
+  }
+
+  // Check that permutation has valid indices
+  const auto* function_type = fuzzerutil::GetFunctionType(ir_context, function);
+  assert(function_type && "Function type is null");
+
+  const auto& permutation = message_.permutation();
+
+  // Don't take return type into account
+  auto arg_size = function_type->NumInOperands() - 1;
+
+  // |permutation| vector should be equal to the number of arguments
+  if (static_cast<uint32_t>(permutation.size()) != arg_size) {
+    return false;
+  }
+
+  // Check that all indices are valid
+  // and unique integers from the [0, n-1] set
+  std::unordered_set<uint32_t> unique_indices;
+  for (auto index : permutation) {
+    // We don't compare |index| with 0 since it's an unsigned integer
+    if (index >= arg_size) {
+      return false;
+    }
+
+    unique_indices.insert(index);
+  }
+
+  // Check that permutation doesn't have duplicated values
+  assert(unique_indices.size() == arg_size && "Permutation has duplicates");
+
+  // Check that new function's type is valid:
+  //   - Has the same number of operands
+  //   - Has the same result type as the old one
+  //   - Order of arguments is permuted
+  auto new_type_id = message_.new_type_id();
+  const auto* new_type = ir_context->get_def_use_mgr()->GetDef(new_type_id);
+
+  if (!new_type || new_type->opcode() != SpvOpTypeFunction ||
+      new_type->NumInOperands() != function_type->NumInOperands()) {
+    return false;
+  }
+
+  // Check that both instructions have the same result type
+  if (new_type->GetSingleWordInOperand(0) !=
+      function_type->GetSingleWordInOperand(0)) {
+    return false;
+  }
+
+  // Check that new function type has its arguments permuted
+  for (int i = 0, n = static_cast<int>(permutation.size()); i < n; ++i) {
+    // +1 to take return type into account
+    if (new_type->GetSingleWordInOperand(i + 1) !=
+        function_type->GetSingleWordInOperand(permutation[i] + 1)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+void TransformationPermuteFunctionParameters::Apply(
+    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
+  // Retrieve all data from the message
+  uint32_t function_id = message_.function_id();
+  uint32_t new_type_id = message_.new_type_id();
+  const auto& permutation = message_.permutation();
+
+  // Find the function that will be transformed
+  auto* function = fuzzerutil::FindFunction(ir_context, function_id);
+  assert(function && "Can't find the function");
+
+  // Change function's type
+  function->DefInst().SetInOperand(1, {new_type_id});
+
+  // Adjust OpFunctionParameter instructions
+
+  // Collect ids and types from OpFunctionParameter instructions
+  std::vector<uint32_t> param_id, param_type;
+  function->ForEachParam(
+      [&param_id, &param_type](const opt::Instruction* param) {
+        param_id.push_back(param->result_id());
+        param_type.push_back(param->type_id());
+      });
+
+  // Permute parameters' ids and types
+  std::vector<uint32_t> permuted_param_id, permuted_param_type;
+  for (auto index : permutation) {
+    permuted_param_id.push_back(param_id[index]);
+    permuted_param_type.push_back(param_type[index]);
+  }
+
+  // Set OpFunctionParameter instructions to point to new parameters
+  size_t i = 0;
+  function->ForEachParam(
+      [&i, &permuted_param_id, &permuted_param_type](opt::Instruction* param) {
+        param->SetResultType(permuted_param_type[i]);
+        param->SetResultId(permuted_param_id[i]);
+        ++i;
+      });
+
+  // Fix all OpFunctionCall instructions
+  ir_context->get_def_use_mgr()->ForEachUser(
+      &function->DefInst(),
+      [function_id, &permutation](opt::Instruction* call) {
+        if (call->opcode() != SpvOpFunctionCall ||
+            call->GetSingleWordInOperand(0) != function_id) {
+          return;
+        }
+
+        opt::Instruction::OperandList call_operands = {
+            call->GetInOperand(0)  // Function id
+        };
+
+        for (auto index : permutation) {
+          // Take function id into account
+          call_operands.push_back(call->GetInOperand(index + 1));
+        }
+
+        call->SetInOperands(std::move(call_operands));
+      });
+
+  // Make sure our changes are analyzed
+  ir_context->InvalidateAnalysesExceptFor(
+      opt::IRContext::Analysis::kAnalysisNone);
+}
+
+protobufs::Transformation TransformationPermuteFunctionParameters::ToMessage()
+    const {
+  protobufs::Transformation result;
+  *result.mutable_permute_function_parameters() = message_;
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_permute_function_parameters.h b/source/fuzz/transformation_permute_function_parameters.h
new file mode 100644
index 0000000..994e4c2
--- /dev/null
+++ b/source/fuzz/transformation_permute_function_parameters.h
@@ -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.
+
+#ifndef SOURCE_FUZZ_TRANSFORMATION_PERMUTE_FUNCTION_PARAMETERS_H_
+#define SOURCE_FUZZ_TRANSFORMATION_PERMUTE_FUNCTION_PARAMETERS_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 TransformationPermuteFunctionParameters : public Transformation {
+ public:
+  explicit TransformationPermuteFunctionParameters(
+      const protobufs::TransformationPermuteFunctionParameters& message);
+
+  TransformationPermuteFunctionParameters(
+      uint32_t function_id, uint32_t new_type_id,
+      const std::vector<uint32_t>& permutation);
+
+  // - |function_id| is a valid non-entry-point OpFunction instruction
+  // - |new_type_id| is a result id of a valid OpTypeFunction instruction.
+  //   New type is valid if:
+  //     - it has the same number of operands as the old one
+  //     - function's result type is the same as the old one
+  //     - function's arguments are permuted according to |permutation| vector
+  // - |permutation| is a set of [0..(n - 1)], where n is a number of arguments
+  //   to the function
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // - OpFunction instruction with |result_id == function_id| is changed.
+  //   Its arguments are permuted according to the |permutation| vector
+  // - Changed function gets a new type specified by |type_id|
+  // - Calls to the function are adjusted accordingly
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+ private:
+  protobufs::TransformationPermuteFunctionParameters message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_PERMUTE_FUNCTION_PARAMETERS_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 b097767..d6f17fc 100644
--- a/source/fuzz/transformation_replace_boolean_constant_with_constant_binary.cpp
+++ b/source/fuzz/transformation_replace_boolean_constant_with_constant_binary.cpp
@@ -128,15 +128,15 @@
 }
 
 bool TransformationReplaceBooleanConstantWithConstantBinary::IsApplicable(
-    opt::IRContext* context, const FactManager& /*unused*/) const {
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
   // The id for the binary result must be fresh
-  if (!fuzzerutil::IsFreshId(context,
+  if (!fuzzerutil::IsFreshId(ir_context,
                              message_.fresh_id_for_binary_operation())) {
     return false;
   }
 
   // The used id must be for a boolean constant
-  auto boolean_constant = context->get_def_use_mgr()->GetDef(
+  auto boolean_constant = ir_context->get_def_use_mgr()->GetDef(
       message_.id_use_descriptor().id_of_interest());
   if (!boolean_constant) {
     return false;
@@ -148,7 +148,7 @@
 
   // The left-hand-side id must correspond to a constant instruction.
   auto lhs_constant_inst =
-      context->get_def_use_mgr()->GetDef(message_.lhs_id());
+      ir_context->get_def_use_mgr()->GetDef(message_.lhs_id());
   if (!lhs_constant_inst) {
     return false;
   }
@@ -158,7 +158,7 @@
 
   // The right-hand-side id must correspond to a constant instruction.
   auto rhs_constant_inst =
-      context->get_def_use_mgr()->GetDef(message_.rhs_id());
+      ir_context->get_def_use_mgr()->GetDef(message_.rhs_id());
   if (!rhs_constant_inst) {
     return false;
   }
@@ -173,9 +173,9 @@
 
   // The expression 'LHS opcode RHS' must evaluate to the boolean constant.
   auto lhs_constant =
-      context->get_constant_mgr()->FindDeclaredConstant(message_.lhs_id());
+      ir_context->get_constant_mgr()->FindDeclaredConstant(message_.lhs_id());
   auto rhs_constant =
-      context->get_constant_mgr()->FindDeclaredConstant(message_.rhs_id());
+      ir_context->get_constant_mgr()->FindDeclaredConstant(message_.rhs_id());
   bool expected_result = (boolean_constant->opcode() == SpvOpConstantTrue);
 
   const auto binary_opcode = static_cast<SpvOp>(message_.opcode());
@@ -238,37 +238,49 @@
 
   // The id use descriptor must identify some instruction
   auto instruction =
-      FindInstructionContainingUse(message_.id_use_descriptor(), context);
+      FindInstructionContainingUse(message_.id_use_descriptor(), ir_context);
   if (instruction == nullptr) {
     return false;
   }
 
-  // The instruction must not be an OpPhi, as we cannot insert a binary
-  // operator instruction before an OpPhi.
-  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2902): there is
-  //  scope for being less conservative.
-  return instruction->opcode() != SpvOpPhi;
+  switch (instruction->opcode()) {
+    case SpvOpPhi:
+      // The instruction must not be an OpPhi, as we cannot insert a binary
+      // operator instruction before an OpPhi.
+      // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2902): there is
+      //  scope for being less conservative.
+      return false;
+    case SpvOpVariable:
+      // The instruction must not be an OpVariable, because (a) we cannot insert
+      // a binary operator before an OpVariable, but in any case (b) the
+      // constant we would be replacing is the initializer constant of the
+      // OpVariable, and this cannot be the result of a binary operation.
+      return false;
+    default:
+      return true;
+  }
 }
 
 void TransformationReplaceBooleanConstantWithConstantBinary::Apply(
-    opt::IRContext* context, FactManager* fact_manager) const {
-  ApplyWithResult(context, fact_manager);
+    opt::IRContext* ir_context,
+    TransformationContext* transformation_context) const {
+  ApplyWithResult(ir_context, transformation_context);
 }
 
 opt::Instruction*
 TransformationReplaceBooleanConstantWithConstantBinary::ApplyWithResult(
-    opt::IRContext* context, FactManager* /*unused*/) const {
+    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
   opt::analysis::Bool bool_type;
   opt::Instruction::OperandList operands = {
       {SPV_OPERAND_TYPE_ID, {message_.lhs_id()}},
       {SPV_OPERAND_TYPE_ID, {message_.rhs_id()}}};
   auto binary_instruction = MakeUnique<opt::Instruction>(
-      context, static_cast<SpvOp>(message_.opcode()),
-      context->get_type_mgr()->GetId(&bool_type),
+      ir_context, static_cast<SpvOp>(message_.opcode()),
+      ir_context->get_type_mgr()->GetId(&bool_type),
       message_.fresh_id_for_binary_operation(), operands);
   opt::Instruction* result = binary_instruction.get();
   auto instruction_containing_constant_use =
-      FindInstructionContainingUse(message_.id_use_descriptor(), context);
+      FindInstructionContainingUse(message_.id_use_descriptor(), ir_context);
 
   // We want to insert the new instruction before the instruction that contains
   // the use of the boolean, but we need to go backwards one more instruction if
@@ -287,9 +299,10 @@
   instruction_containing_constant_use->SetInOperand(
       message_.id_use_descriptor().in_operand_index(),
       {message_.fresh_id_for_binary_operation()});
-  fuzzerutil::UpdateModuleIdBound(context,
+  fuzzerutil::UpdateModuleIdBound(ir_context,
                                   message_.fresh_id_for_binary_operation());
-  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+  ir_context->InvalidateAnalysesExceptFor(
+      opt::IRContext::Analysis::kAnalysisNone);
   return result;
 }
 
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 f74cd8d..3abb485 100644
--- a/source/fuzz/transformation_replace_boolean_constant_with_constant_binary.h
+++ b/source/fuzz/transformation_replace_boolean_constant_with_constant_binary.h
@@ -15,9 +15,9 @@
 #ifndef SOURCE_FUZZ_TRANSFORMATION_REPLACE_BOOLEAN_CONSTANT_WITH_CONSTANT_BINARY_H_
 #define SOURCE_FUZZ_TRANSFORMATION_REPLACE_BOOLEAN_CONSTANT_WITH_CONSTANT_BINARY_H_
 
-#include "source/fuzz/fact_manager.h"
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
 #include "source/fuzz/transformation.h"
+#include "source/fuzz/transformation_context.h"
 #include "source/opt/ir_context.h"
 
 namespace spvtools {
@@ -49,20 +49,23 @@
   //   TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2902): consider
   //    replacing a boolean in an OpPhi by adding a binary operator instruction
   //    to the parent block for the OpPhi.
-  bool IsApplicable(opt::IRContext* context,
-                    const FactManager& fact_manager) const override;
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
 
   // A new instruction is added before the boolean constant usage that computes
   // the result of applying |message_.opcode| to |message_.lhs_id| and
   // |message_.rhs_id| is added, with result id
   // |message_.fresh_id_for_binary_operation|.  The boolean constant usage is
   // replaced with this result id.
-  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
 
   // The same as Apply, except that the newly-added binary instruction is
   // returned.
-  opt::Instruction* ApplyWithResult(opt::IRContext* context,
-                                    FactManager* fact_manager) const;
+  opt::Instruction* ApplyWithResult(
+      opt::IRContext* ir_context,
+      TransformationContext* transformation_context) const;
 
   protobufs::Transformation ToMessage() const override;
 
diff --git a/source/fuzz/transformation_replace_constant_with_uniform.cpp b/source/fuzz/transformation_replace_constant_with_uniform.cpp
index 405776e..a8f9495 100644
--- a/source/fuzz/transformation_replace_constant_with_uniform.cpp
+++ b/source/fuzz/transformation_replace_constant_with_uniform.cpp
@@ -39,12 +39,12 @@
 
 std::unique_ptr<opt::Instruction>
 TransformationReplaceConstantWithUniform::MakeAccessChainInstruction(
-    spvtools::opt::IRContext* context, uint32_t constant_type_id) const {
+    spvtools::opt::IRContext* ir_context, uint32_t constant_type_id) const {
   // The input operands for the access chain.
   opt::Instruction::OperandList operands_for_access_chain;
 
   opt::Instruction* uniform_variable =
-      FindUniformVariable(message_.uniform_descriptor(), context, false);
+      FindUniformVariable(message_.uniform_descriptor(), ir_context, false);
 
   // The first input operand is the id of the uniform variable.
   operands_for_access_chain.push_back(
@@ -56,42 +56,43 @@
   // instruction ids as operands.
   opt::analysis::Integer int_type(32, true);
   auto registered_int_type =
-      context->get_type_mgr()->GetRegisteredType(&int_type)->AsInteger();
-  auto int_type_id = context->get_type_mgr()->GetId(&int_type);
+      ir_context->get_type_mgr()->GetRegisteredType(&int_type)->AsInteger();
+  auto int_type_id = ir_context->get_type_mgr()->GetId(&int_type);
   for (auto index : message_.uniform_descriptor().index()) {
     opt::analysis::IntConstant int_constant(registered_int_type, {index});
-    auto constant_id = context->get_constant_mgr()->FindDeclaredConstant(
+    auto constant_id = ir_context->get_constant_mgr()->FindDeclaredConstant(
         &int_constant, int_type_id);
     operands_for_access_chain.push_back({SPV_OPERAND_TYPE_ID, {constant_id}});
   }
 
   // The type id for the access chain is a uniform pointer with base type
   // matching the given constant id type.
-  auto type_and_pointer_type = context->get_type_mgr()->GetTypeAndPointerType(
-      constant_type_id, SpvStorageClassUniform);
+  auto type_and_pointer_type =
+      ir_context->get_type_mgr()->GetTypeAndPointerType(constant_type_id,
+                                                        SpvStorageClassUniform);
   assert(type_and_pointer_type.first != nullptr);
   assert(type_and_pointer_type.second != nullptr);
   auto pointer_to_uniform_constant_type_id =
-      context->get_type_mgr()->GetId(type_and_pointer_type.second.get());
+      ir_context->get_type_mgr()->GetId(type_and_pointer_type.second.get());
 
   return MakeUnique<opt::Instruction>(
-      context, SpvOpAccessChain, pointer_to_uniform_constant_type_id,
+      ir_context, SpvOpAccessChain, pointer_to_uniform_constant_type_id,
       message_.fresh_id_for_access_chain(), operands_for_access_chain);
 }
 
 std::unique_ptr<opt::Instruction>
 TransformationReplaceConstantWithUniform::MakeLoadInstruction(
-    spvtools::opt::IRContext* context, uint32_t constant_type_id) const {
+    spvtools::opt::IRContext* ir_context, uint32_t constant_type_id) const {
   opt::Instruction::OperandList operands_for_load = {
       {SPV_OPERAND_TYPE_ID, {message_.fresh_id_for_access_chain()}}};
-  return MakeUnique<opt::Instruction>(context, SpvOpLoad, constant_type_id,
+  return MakeUnique<opt::Instruction>(ir_context, SpvOpLoad, constant_type_id,
                                       message_.fresh_id_for_load(),
                                       operands_for_load);
 }
 
 bool TransformationReplaceConstantWithUniform::IsApplicable(
-    spvtools::opt::IRContext* context,
-    const spvtools::fuzz::FactManager& fact_manager) const {
+    opt::IRContext* ir_context,
+    const TransformationContext& transformation_context) const {
   // The following is really an invariant of the transformation rather than
   // merely a requirement of the precondition.  We check it here since we cannot
   // check it in the message_ constructor.
@@ -99,16 +100,17 @@
          "Fresh ids for access chain and load result cannot be the same.");
 
   // The ids for the access chain and load instructions must both be fresh.
-  if (!fuzzerutil::IsFreshId(context, message_.fresh_id_for_access_chain())) {
+  if (!fuzzerutil::IsFreshId(ir_context,
+                             message_.fresh_id_for_access_chain())) {
     return false;
   }
-  if (!fuzzerutil::IsFreshId(context, message_.fresh_id_for_load())) {
+  if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id_for_load())) {
     return false;
   }
 
   // The id specified in the id use descriptor must be that of a declared scalar
   // constant.
-  auto declared_constant = context->get_constant_mgr()->FindDeclaredConstant(
+  auto declared_constant = ir_context->get_constant_mgr()->FindDeclaredConstant(
       message_.id_use_descriptor().id_of_interest());
   if (!declared_constant) {
     return false;
@@ -120,13 +122,13 @@
   // The fact manager needs to believe that the uniform data element described
   // by the uniform buffer element descriptor will hold a scalar value.
   auto constant_id_associated_with_uniform =
-      fact_manager.GetConstantFromUniformDescriptor(
-          context, message_.uniform_descriptor());
+      transformation_context.GetFactManager()->GetConstantFromUniformDescriptor(
+          ir_context, message_.uniform_descriptor());
   if (!constant_id_associated_with_uniform) {
     return false;
   }
   auto constant_associated_with_uniform =
-      context->get_constant_mgr()->FindDeclaredConstant(
+      ir_context->get_constant_mgr()->FindDeclaredConstant(
           constant_id_associated_with_uniform);
   assert(constant_associated_with_uniform &&
          "The constant should be present in the module.");
@@ -149,33 +151,39 @@
   // The id use descriptor must identify some instruction with respect to the
   // module.
   auto instruction_using_constant =
-      FindInstructionContainingUse(message_.id_use_descriptor(), context);
+      FindInstructionContainingUse(message_.id_use_descriptor(), ir_context);
   if (!instruction_using_constant) {
     return false;
   }
 
+  // The use must not be a variable initializer; these are required to be
+  // constants, so it would be illegal to replace one with a uniform access.
+  if (instruction_using_constant->opcode() == SpvOpVariable) {
+    return false;
+  }
+
   // The module needs to have a uniform pointer type suitable for indexing into
   // the uniform variable, i.e. matching the type of the constant we wish to
   // replace with a uniform.
   opt::analysis::Pointer pointer_to_type_of_constant(declared_constant->type(),
                                                      SpvStorageClassUniform);
-  if (!context->get_type_mgr()->GetId(&pointer_to_type_of_constant)) {
+  if (!ir_context->get_type_mgr()->GetId(&pointer_to_type_of_constant)) {
     return false;
   }
 
   // In order to index into the uniform, the module has got to contain the int32
   // type, plus an OpConstant for each of the indices of interest.
   opt::analysis::Integer int_type(32, true);
-  if (!context->get_type_mgr()->GetId(&int_type)) {
+  if (!ir_context->get_type_mgr()->GetId(&int_type)) {
     return false;
   }
   auto registered_int_type =
-      context->get_type_mgr()->GetRegisteredType(&int_type)->AsInteger();
-  auto int_type_id = context->get_type_mgr()->GetId(&int_type);
+      ir_context->get_type_mgr()->GetRegisteredType(&int_type)->AsInteger();
+  auto int_type_id = ir_context->get_type_mgr()->GetId(&int_type);
   for (auto index : message_.uniform_descriptor().index()) {
     opt::analysis::IntConstant int_constant(registered_int_type, {index});
-    if (!context->get_constant_mgr()->FindDeclaredConstant(&int_constant,
-                                                           int_type_id)) {
+    if (!ir_context->get_constant_mgr()->FindDeclaredConstant(&int_constant,
+                                                              int_type_id)) {
       return false;
     }
   }
@@ -184,11 +192,11 @@
 }
 
 void TransformationReplaceConstantWithUniform::Apply(
-    spvtools::opt::IRContext* context,
-    spvtools::fuzz::FactManager* /*unused*/) const {
+    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 =
-      FindInstructionContainingUse(message_.id_use_descriptor(), context);
+      FindInstructionContainingUse(message_.id_use_descriptor(), ir_context);
   assert(instruction_containing_constant_use &&
          "Precondition requires that the id use can be found.");
   assert(instruction_containing_constant_use->GetSingleWordInOperand(
@@ -198,17 +206,17 @@
 
   // The id of the type for the constant whose use we wish to replace.
   auto constant_type_id =
-      context->get_def_use_mgr()
+      ir_context->get_def_use_mgr()
           ->GetDef(message_.id_use_descriptor().id_of_interest())
           ->type_id();
 
   // Add an access chain instruction to target the uniform element.
   instruction_containing_constant_use->InsertBefore(
-      MakeAccessChainInstruction(context, constant_type_id));
+      MakeAccessChainInstruction(ir_context, constant_type_id));
 
   // Add a load from this access chain.
   instruction_containing_constant_use->InsertBefore(
-      MakeLoadInstruction(context, constant_type_id));
+      MakeLoadInstruction(ir_context, constant_type_id));
 
   // Adjust the instruction containing the usage of the constant so that this
   // usage refers instead to the result of the load.
@@ -217,11 +225,12 @@
       {message_.fresh_id_for_load()});
 
   // Update the module id bound to reflect the new instructions.
-  fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id_for_load());
-  fuzzerutil::UpdateModuleIdBound(context,
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id_for_load());
+  fuzzerutil::UpdateModuleIdBound(ir_context,
                                   message_.fresh_id_for_access_chain());
 
-  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+  ir_context->InvalidateAnalysesExceptFor(
+      opt::IRContext::Analysis::kAnalysisNone);
 }
 
 protobufs::Transformation TransformationReplaceConstantWithUniform::ToMessage()
diff --git a/source/fuzz/transformation_replace_constant_with_uniform.h b/source/fuzz/transformation_replace_constant_with_uniform.h
index ed354b1..b72407c 100644
--- a/source/fuzz/transformation_replace_constant_with_uniform.h
+++ b/source/fuzz/transformation_replace_constant_with_uniform.h
@@ -58,8 +58,9 @@
   //     - According to the fact manager, the uniform data element specified by
   //       |message_.uniform_descriptor| holds a value with the same type and
   //       value as %C
-  bool IsApplicable(opt::IRContext* context,
-                    const FactManager& fact_manager) const override;
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
 
   // - Introduces two new instructions:
   //   - An access chain targeting the uniform data element specified by
@@ -68,7 +69,8 @@
   //   - A load from this access chain, with id |message_.fresh_id_for_load|
   // - Replaces the id use specified by |message_.id_use_descriptor| with
   //   |message_.fresh_id_for_load|
-  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
 
   protobufs::Transformation ToMessage() const override;
 
@@ -76,11 +78,11 @@
   // Helper method to create an access chain for the uniform element associated
   // with the transformation.
   std::unique_ptr<opt::Instruction> MakeAccessChainInstruction(
-      spvtools::opt::IRContext* context, uint32_t constant_type_id) const;
+      spvtools::opt::IRContext* ir_context, uint32_t constant_type_id) const;
 
   // Helper to create a load instruction.
   std::unique_ptr<opt::Instruction> MakeLoadInstruction(
-      spvtools::opt::IRContext* context, uint32_t constant_type_id) const;
+      spvtools::opt::IRContext* ir_context, uint32_t constant_type_id) const;
 
   protobufs::TransformationReplaceConstantWithUniform message_;
 };
diff --git a/source/fuzz/transformation_replace_id_with_synonym.cpp b/source/fuzz/transformation_replace_id_with_synonym.cpp
index 79ba012..87194c8 100644
--- a/source/fuzz/transformation_replace_id_with_synonym.cpp
+++ b/source/fuzz/transformation_replace_id_with_synonym.cpp
@@ -37,48 +37,51 @@
 }
 
 bool TransformationReplaceIdWithSynonym::IsApplicable(
-    spvtools::opt::IRContext* context,
-    const spvtools::fuzz::FactManager& fact_manager) const {
+    opt::IRContext* ir_context,
+    const TransformationContext& transformation_context) const {
   auto id_of_interest = message_.id_use_descriptor().id_of_interest();
 
   // Does the fact manager know about the synonym?
   auto data_descriptor_for_synonymous_id =
       MakeDataDescriptor(message_.synonymous_id(), {});
-  if (!fact_manager.IsSynonymous(MakeDataDescriptor(id_of_interest, {}),
-                                 data_descriptor_for_synonymous_id, context)) {
+  if (!transformation_context.GetFactManager()->IsSynonymous(
+          MakeDataDescriptor(id_of_interest, {}),
+          data_descriptor_for_synonymous_id, ir_context)) {
     return false;
   }
 
   // Does the id use descriptor in the transformation identify an instruction?
   auto use_instruction =
-      FindInstructionContainingUse(message_.id_use_descriptor(), context);
+      FindInstructionContainingUse(message_.id_use_descriptor(), ir_context);
   if (!use_instruction) {
     return false;
   }
 
   // Is the use suitable for being replaced in principle?
   if (!UseCanBeReplacedWithSynonym(
-          context, use_instruction,
+          ir_context, use_instruction,
           message_.id_use_descriptor().in_operand_index())) {
     return false;
   }
 
   // The transformation is applicable if the synonymous id is available at the
   // use point.
-  return IdsIsAvailableAtUse(context, use_instruction,
-                             message_.id_use_descriptor().in_operand_index(),
-                             message_.synonymous_id());
+  return fuzzerutil::IdIsAvailableAtUse(
+      ir_context, use_instruction,
+      message_.id_use_descriptor().in_operand_index(),
+      message_.synonymous_id());
 }
 
 void TransformationReplaceIdWithSynonym::Apply(
-    spvtools::opt::IRContext* context,
-    spvtools::fuzz::FactManager* /*unused*/) const {
+    spvtools::opt::IRContext* ir_context,
+    TransformationContext* /*unused*/) const {
   auto instruction_to_change =
-      FindInstructionContainingUse(message_.id_use_descriptor(), context);
+      FindInstructionContainingUse(message_.id_use_descriptor(), ir_context);
   instruction_to_change->SetInOperand(
       message_.id_use_descriptor().in_operand_index(),
       {message_.synonymous_id()});
-  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+  ir_context->InvalidateAnalysesExceptFor(
+      opt::IRContext::Analysis::kAnalysisNone);
 }
 
 protobufs::Transformation TransformationReplaceIdWithSynonym::ToMessage()
@@ -88,32 +91,8 @@
   return result;
 }
 
-bool TransformationReplaceIdWithSynonym::IdsIsAvailableAtUse(
-    opt::IRContext* context, opt::Instruction* use_instruction,
-    uint32_t use_input_operand_index, uint32_t id) {
-  if (!context->get_instr_block(id)) {
-    return true;
-  }
-  auto defining_instruction = context->get_def_use_mgr()->GetDef(id);
-  if (defining_instruction == use_instruction) {
-    return false;
-  }
-  auto dominator_analysis = context->GetDominatorAnalysis(
-      context->get_instr_block(use_instruction)->GetParent());
-  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
-    // the synonym.
-    auto parent_block =
-        use_instruction->GetSingleWordInOperand(use_input_operand_index + 1);
-    return dominator_analysis->Dominates(
-        context->get_instr_block(defining_instruction)->id(), parent_block);
-  }
-  return dominator_analysis->Dominates(defining_instruction, use_instruction);
-}
-
 bool TransformationReplaceIdWithSynonym::UseCanBeReplacedWithSynonym(
-    opt::IRContext* context, opt::Instruction* use_instruction,
+    opt::IRContext* ir_context, opt::Instruction* use_instruction,
     uint32_t use_in_operand_index) {
   if (use_instruction->opcode() == SpvOpAccessChain &&
       use_in_operand_index > 0) {
@@ -122,10 +101,10 @@
     // synonym, as the use needs to be an OpConstant.
 
     // Get the top-level composite type that is being accessed.
-    auto object_being_accessed = context->get_def_use_mgr()->GetDef(
+    auto object_being_accessed = ir_context->get_def_use_mgr()->GetDef(
         use_instruction->GetSingleWordInOperand(0));
     auto pointer_type =
-        context->get_type_mgr()->GetType(object_being_accessed->type_id());
+        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();
@@ -148,7 +127,7 @@
             composite_type_being_accessed->AsArray()->element_type();
       } else {
         assert(composite_type_being_accessed->AsStruct());
-        auto constant_index_instruction = context->get_def_use_mgr()->GetDef(
+        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 =
@@ -173,16 +152,16 @@
     // type.
 
     // Get the definition of the function being called.
-    auto function = context->get_def_use_mgr()->GetDef(
+    auto function = ir_context->get_def_use_mgr()->GetDef(
         use_instruction->GetSingleWordInOperand(0));
     // From the function definition, get the function type.
-    auto function_type =
-        context->get_def_use_mgr()->GetDef(function->GetSingleWordInOperand(1));
+    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 = context->get_type_mgr()->GetType(
+    auto parameter_type = ir_context->get_type_mgr()->GetType(
         function_type->GetSingleWordInOperand(use_in_operand_index));
     if (parameter_type->AsPointer()) {
       return false;
diff --git a/source/fuzz/transformation_replace_id_with_synonym.h b/source/fuzz/transformation_replace_id_with_synonym.h
index c21673d..a5a9dfd 100644
--- a/source/fuzz/transformation_replace_id_with_synonym.h
+++ b/source/fuzz/transformation_replace_id_with_synonym.h
@@ -15,9 +15,9 @@
 #ifndef SOURCE_FUZZ_TRANSFORMATION_REPLACE_ID_WITH_SYNONYM_H_
 #define SOURCE_FUZZ_TRANSFORMATION_REPLACE_ID_WITH_SYNONYM_H_
 
-#include "source/fuzz/fact_manager.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 {
@@ -42,23 +42,17 @@
   // - The id must not be a pointer argument to a function call (because the
   //   synonym might not be a memory object declaration).
   // - |fresh_id_for_temporary| must be 0.
-  bool IsApplicable(opt::IRContext* context,
-                    const FactManager& fact_manager) const override;
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
 
   // Replaces the use identified by |message_.id_use_descriptor| with the
   // synonymous id identified by |message_.synonymous_id|.
-  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
 
   protobufs::Transformation ToMessage() const override;
 
-  // Checks whether the |id| is available (according to dominance rules) at the
-  // use point defined by input operand |use_input_operand_index| of
-  // |use_instruction|.
-  static bool IdsIsAvailableAtUse(opt::IRContext* context,
-                                  opt::Instruction* use_instruction,
-                                  uint32_t use_input_operand_index,
-                                  uint32_t id);
-
   // 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:
@@ -66,7 +60,7 @@
   //   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* context,
+  static bool UseCanBeReplacedWithSynonym(opt::IRContext* ir_context,
                                           opt::Instruction* use_instruction,
                                           uint32_t use_in_operand_index);
 
diff --git a/source/fuzz/transformation_set_function_control.cpp b/source/fuzz/transformation_set_function_control.cpp
index d2b61f1..d01e743 100644
--- a/source/fuzz/transformation_set_function_control.cpp
+++ b/source/fuzz/transformation_set_function_control.cpp
@@ -28,9 +28,9 @@
 }
 
 bool TransformationSetFunctionControl::IsApplicable(
-    opt::IRContext* context, const FactManager& /*unused*/) const {
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
   opt::Instruction* function_def_instruction =
-      FindFunctionDefInstruction(context);
+      FindFunctionDefInstruction(ir_context);
   if (!function_def_instruction) {
     // The given function id does not correspond to any function.
     return false;
@@ -69,10 +69,10 @@
   return true;
 }
 
-void TransformationSetFunctionControl::Apply(opt::IRContext* context,
-                                             FactManager* /*unused*/) const {
+void TransformationSetFunctionControl::Apply(
+    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
   opt::Instruction* function_def_instruction =
-      FindFunctionDefInstruction(context);
+      FindFunctionDefInstruction(ir_context);
   function_def_instruction->SetInOperand(0, {message_.function_control()});
 }
 
@@ -83,11 +83,11 @@
 }
 
 opt::Instruction* TransformationSetFunctionControl ::FindFunctionDefInstruction(
-    opt::IRContext* context) const {
+    opt::IRContext* ir_context) const {
   // Look through all functions for a function whose defining instruction's
   // result id matches |message_.function_id|, returning the defining
   // instruction if found.
-  for (auto& function : *context->module()) {
+  for (auto& function : *ir_context->module()) {
     if (function.DefInst().result_id() == message_.function_id()) {
       return &function.DefInst();
     }
diff --git a/source/fuzz/transformation_set_function_control.h b/source/fuzz/transformation_set_function_control.h
index 0526bb9..5109f74 100644
--- a/source/fuzz/transformation_set_function_control.h
+++ b/source/fuzz/transformation_set_function_control.h
@@ -15,9 +15,9 @@
 #ifndef SOURCE_FUZZ_TRANSFORMATION_SET_FUNCTION_CONTROL_H_
 #define SOURCE_FUZZ_TRANSFORMATION_SET_FUNCTION_CONTROL_H_
 
-#include "source/fuzz/fact_manager.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 {
@@ -37,17 +37,20 @@
   //   at most one of 'Inline' or 'DontInline', and that may not contain 'Pure'
   //   (respectively 'Const') unless the existing function control mask contains
   //   'Pure' (respectively 'Const').
-  bool IsApplicable(opt::IRContext* context,
-                    const FactManager& fact_manager) const override;
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
 
   // The function control operand of instruction |message_.function_id| is
   // over-written with |message_.function_control|.
-  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
 
   protobufs::Transformation ToMessage() const override;
 
  private:
-  opt::Instruction* FindFunctionDefInstruction(opt::IRContext* context) const;
+  opt::Instruction* FindFunctionDefInstruction(
+      opt::IRContext* ir_context) const;
 
   protobufs::TransformationSetFunctionControl message_;
 };
diff --git a/source/fuzz/transformation_set_loop_control.cpp b/source/fuzz/transformation_set_loop_control.cpp
index 9062f17..845ac69 100644
--- a/source/fuzz/transformation_set_loop_control.cpp
+++ b/source/fuzz/transformation_set_loop_control.cpp
@@ -31,9 +31,9 @@
 }
 
 bool TransformationSetLoopControl::IsApplicable(
-    opt::IRContext* context, const FactManager& /*unused*/) const {
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
   // |message_.block_id| must identify a block that ends with OpLoopMerge.
-  auto block = context->get_instr_block(message_.block_id());
+  auto block = ir_context->get_instr_block(message_.block_id());
   if (!block) {
     return false;
   }
@@ -79,7 +79,8 @@
 
   if ((message_.loop_control() &
        (SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask)) &&
-      !(PeelCountIsSupported(context) && PartialCountIsSupported(context))) {
+      !(PeelCountIsSupported(ir_context) &&
+        PartialCountIsSupported(ir_context))) {
     // At least one of PeelCount or PartialCount is used, but the SPIR-V version
     // in question does not support these loop controls.
     return false;
@@ -104,11 +105,11 @@
             (SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask)));
 }
 
-void TransformationSetLoopControl::Apply(opt::IRContext* context,
-                                         FactManager* /*unused*/) const {
+void TransformationSetLoopControl::Apply(
+    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
   // Grab the loop merge instruction and its associated loop control mask.
   auto merge_inst =
-      context->get_instr_block(message_.block_id())->GetMergeInst();
+      ir_context->get_instr_block(message_.block_id())->GetMergeInst();
   auto existing_loop_control_mask =
       merge_inst->GetSingleWordInOperand(kLoopControlMaskInOperandIndex);
 
@@ -181,11 +182,11 @@
 }
 
 bool TransformationSetLoopControl::PartialCountIsSupported(
-    opt::IRContext* context) {
+    opt::IRContext* ir_context) {
   // TODO(afd): We capture the universal environments for which this loop
   //  control is definitely not supported.  The check should be refined on
   //  demand for other target environments.
-  switch (context->grammar().target_env()) {
+  switch (ir_context->grammar().target_env()) {
     case SPV_ENV_UNIVERSAL_1_0:
     case SPV_ENV_UNIVERSAL_1_1:
     case SPV_ENV_UNIVERSAL_1_2:
@@ -197,11 +198,11 @@
 }
 
 bool TransformationSetLoopControl::PeelCountIsSupported(
-    opt::IRContext* context) {
+    opt::IRContext* ir_context) {
   // TODO(afd): We capture the universal environments for which this loop
   //  control is definitely not supported.  The check should be refined on
   //  demand for other target environments.
-  switch (context->grammar().target_env()) {
+  switch (ir_context->grammar().target_env()) {
     case SPV_ENV_UNIVERSAL_1_0:
     case SPV_ENV_UNIVERSAL_1_1:
     case SPV_ENV_UNIVERSAL_1_2:
diff --git a/source/fuzz/transformation_set_loop_control.h b/source/fuzz/transformation_set_loop_control.h
index 28b148c..f0c364f 100644
--- a/source/fuzz/transformation_set_loop_control.h
+++ b/source/fuzz/transformation_set_loop_control.h
@@ -15,9 +15,9 @@
 #ifndef SOURCE_FUZZ_TRANSFORMATION_SET_LOOP_CONTROL_H_
 #define SOURCE_FUZZ_TRANSFORMATION_SET_LOOP_CONTROL_H_
 
-#include "source/fuzz/fact_manager.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 {
@@ -38,13 +38,14 @@
   //   instruction.
   // - |message_.loop_control| must be a legal loop control mask that
   //   only uses controls available in the SPIR-V version associated with
-  //   |context|, and must not add loop controls that are only valid in the
+  //   |ir_context|, and must not add loop controls that are only valid in the
   //   presence of guarantees about what the loop does (e.g. MinIterations).
   // - |message_.peel_count| (respectively |message_.partial_count|) must be
   //   zero PeelCount (respectively PartialCount) is set in
   //   |message_.loop_control|.
-  bool IsApplicable(opt::IRContext* context,
-                    const FactManager& fact_manager) const override;
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
 
   // - The loop control operand of the OpLoopMergeInstruction in
   //   |message_.block_id| is overwritten with |message_.loop_control|.
@@ -52,16 +53,17 @@
   //   controls with associated literals that have been removed (e.g.
   //   MinIterations), and any that have been added (PeelCount and/or
   //   PartialCount).
-  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
 
   protobufs::Transformation ToMessage() const override;
 
   // Does the version of SPIR-V being used support the PartialCount loop
   // control?
-  static bool PartialCountIsSupported(opt::IRContext* context);
+  static bool PartialCountIsSupported(opt::IRContext* ir_context);
 
   // Does the version of SPIR-V being used support the PeelCount loop control?
-  static bool PeelCountIsSupported(opt::IRContext* context);
+  static bool PeelCountIsSupported(opt::IRContext* ir_context);
 
  private:
   // Returns true if and only if |loop_single_bit_mask| is *not* set in
diff --git a/source/fuzz/transformation_set_memory_operands_mask.cpp b/source/fuzz/transformation_set_memory_operands_mask.cpp
index a14e1a6..131a499 100644
--- a/source/fuzz/transformation_set_memory_operands_mask.cpp
+++ b/source/fuzz/transformation_set_memory_operands_mask.cpp
@@ -42,8 +42,7 @@
 }
 
 bool TransformationSetMemoryOperandsMask::IsApplicable(
-    opt::IRContext* context,
-    const spvtools::fuzz::FactManager& /*unused*/) const {
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
   if (message_.memory_operands_mask_index() != 0) {
     // The following conditions should never be violated, even if
     // transformations end up being replayed in a different way to the manner in
@@ -54,11 +53,11 @@
                SpvOpCopyMemory ||
            message_.memory_access_instruction().target_instruction_opcode() ==
                SpvOpCopyMemorySized);
-    assert(MultipleMemoryOperandMasksAreSupported(context));
+    assert(MultipleMemoryOperandMasksAreSupported(ir_context));
   }
 
   auto instruction =
-      FindInstruction(message_.memory_access_instruction(), context);
+      FindInstruction(message_.memory_access_instruction(), ir_context);
   if (!instruction) {
     return false;
   }
@@ -94,9 +93,9 @@
 }
 
 void TransformationSetMemoryOperandsMask::Apply(
-    opt::IRContext* context, spvtools::fuzz::FactManager* /*unused*/) const {
+    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
   auto instruction =
-      FindInstruction(message_.memory_access_instruction(), context);
+      FindInstruction(message_.memory_access_instruction(), ir_context);
   auto original_mask_in_operand_index = GetInOperandIndexForMask(
       *instruction, message_.memory_operands_mask_index());
   // Either add a new operand, if no mask operand was already present, or
@@ -182,11 +181,11 @@
 }
 
 bool TransformationSetMemoryOperandsMask::
-    MultipleMemoryOperandMasksAreSupported(opt::IRContext* context) {
+    MultipleMemoryOperandMasksAreSupported(opt::IRContext* ir_context) {
   // TODO(afd): We capture the universal environments for which this loop
   //  control is definitely not supported.  The check should be refined on
   //  demand for other target environments.
-  switch (context->grammar().target_env()) {
+  switch (ir_context->grammar().target_env()) {
     case SPV_ENV_UNIVERSAL_1_0:
     case SPV_ENV_UNIVERSAL_1_1:
     case SPV_ENV_UNIVERSAL_1_2:
diff --git a/source/fuzz/transformation_set_memory_operands_mask.h b/source/fuzz/transformation_set_memory_operands_mask.h
index 20ae145..9f5081b 100644
--- a/source/fuzz/transformation_set_memory_operands_mask.h
+++ b/source/fuzz/transformation_set_memory_operands_mask.h
@@ -15,9 +15,9 @@
 #ifndef SOURCE_FUZZ_TRANSFORMATION_SET_MEMORY_OPERANDS_MASK_H_
 #define SOURCE_FUZZ_TRANSFORMATION_SET_MEMORY_OPERANDS_MASK_H_
 
-#include "source/fuzz/fact_manager.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 {
@@ -40,14 +40,16 @@
   // - |message_.memory_operands_mask| must be identical to the original memory
   //   operands mask, except that Volatile may be added, and Nontemporal may be
   //   toggled.
-  bool IsApplicable(opt::IRContext* context,
-                    const FactManager& fact_manager) const override;
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
 
   // Replaces the operands mask identified by
   // |message_.memory_operands_mask_index| in the instruction described by
   // |message_.memory_access_instruction| with |message_.memory_operands_mask|,
   // creating an input operand for the mask if no such operand was present.
-  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
 
   protobufs::Transformation ToMessage() const override;
 
@@ -57,7 +59,8 @@
 
   // Does the version of SPIR-V being used support multiple memory operand
   // masks on relevant memory access instructions?
-  static bool MultipleMemoryOperandMasksAreSupported(opt::IRContext* context);
+  static bool MultipleMemoryOperandMasksAreSupported(
+      opt::IRContext* ir_context);
 
   // Helper function to get the input operand index associated with mask number
   // |mask_index|. This is a bit tricky if there are multiple masks, because the
diff --git a/source/fuzz/transformation_set_selection_control.cpp b/source/fuzz/transformation_set_selection_control.cpp
index ebabdef..bee1e35 100644
--- a/source/fuzz/transformation_set_selection_control.cpp
+++ b/source/fuzz/transformation_set_selection_control.cpp
@@ -28,13 +28,13 @@
 }
 
 bool TransformationSetSelectionControl::IsApplicable(
-    opt::IRContext* context, const FactManager& /*unused*/) const {
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
   assert((message_.selection_control() == SpvSelectionControlMaskNone ||
           message_.selection_control() == SpvSelectionControlFlattenMask ||
           message_.selection_control() == SpvSelectionControlDontFlattenMask) &&
          "Selection control should never be set to something other than "
          "'None', 'Flatten' or 'DontFlatten'");
-  if (auto block = context->get_instr_block(message_.block_id())) {
+  if (auto block = ir_context->get_instr_block(message_.block_id())) {
     if (auto merge_inst = block->GetMergeInst()) {
       return merge_inst->opcode() == SpvOpSelectionMerge;
     }
@@ -43,9 +43,9 @@
   return false;
 }
 
-void TransformationSetSelectionControl::Apply(opt::IRContext* context,
-                                              FactManager* /*unused*/) const {
-  context->get_instr_block(message_.block_id())
+void TransformationSetSelectionControl::Apply(
+    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
+  ir_context->get_instr_block(message_.block_id())
       ->GetMergeInst()
       ->SetInOperand(1, {message_.selection_control()});
 }
diff --git a/source/fuzz/transformation_set_selection_control.h b/source/fuzz/transformation_set_selection_control.h
index 19e0c3c..21fbdda 100644
--- a/source/fuzz/transformation_set_selection_control.h
+++ b/source/fuzz/transformation_set_selection_control.h
@@ -15,9 +15,9 @@
 #ifndef SOURCE_FUZZ_TRANSFORMATION_SET_SELECTION_CONTROL_H_
 #define SOURCE_FUZZ_TRANSFORMATION_SET_SELECTION_CONTROL_H_
 
-#include "source/fuzz/fact_manager.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 {
@@ -35,12 +35,14 @@
   //   instruction.
   // - |message_.selection_control| must be one of None, Flatten or
   //   DontFlatten.
-  bool IsApplicable(opt::IRContext* context,
-                    const FactManager& fact_manager) const override;
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
 
   // - The selection control operand of the OpSelectionMergeInstruction in
   //   |message_.block_id| is overwritten with |message_.selection_control|.
-  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
 
   protobufs::Transformation ToMessage() const override;
 
diff --git a/source/fuzz/transformation_split_block.cpp b/source/fuzz/transformation_split_block.cpp
index fc5229e..3de081e 100644
--- a/source/fuzz/transformation_split_block.cpp
+++ b/source/fuzz/transformation_split_block.cpp
@@ -35,18 +35,19 @@
 }
 
 bool TransformationSplitBlock::IsApplicable(
-    opt::IRContext* context, const FactManager& /*unused*/) const {
-  if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) {
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
+  if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
     // We require the id for the new block to be unused.
     return false;
   }
   auto instruction_to_split_before =
-      FindInstruction(message_.instruction_to_split_before(), context);
+      FindInstruction(message_.instruction_to_split_before(), ir_context);
   if (!instruction_to_split_before) {
     // The instruction describing the block we should split does not exist.
     return false;
   }
-  auto block_to_split = context->get_instr_block(instruction_to_split_before);
+  auto block_to_split =
+      ir_context->get_instr_block(instruction_to_split_before);
   assert(block_to_split &&
          "We should not have managed to find the "
          "instruction if it was not contained in a block.");
@@ -79,12 +80,13 @@
            split_before->NumInOperands() != 2);
 }
 
-void TransformationSplitBlock::Apply(opt::IRContext* context,
-                                     FactManager* fact_manager) const {
+void TransformationSplitBlock::Apply(
+    opt::IRContext* ir_context,
+    TransformationContext* transformation_context) const {
   opt::Instruction* instruction_to_split_before =
-      FindInstruction(message_.instruction_to_split_before(), context);
+      FindInstruction(message_.instruction_to_split_before(), ir_context);
   opt::BasicBlock* block_to_split =
-      context->get_instr_block(instruction_to_split_before);
+      ir_context->get_instr_block(instruction_to_split_before);
   auto split_before = fuzzerutil::GetIteratorForInstruction(
       block_to_split, instruction_to_split_before);
   assert(split_before != block_to_split->end() &&
@@ -93,14 +95,14 @@
 
   // We need to make sure the module's id bound is large enough to add the
   // fresh id.
-  fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
   // Split the block.
-  auto new_bb = block_to_split->SplitBasicBlock(context, message_.fresh_id(),
+  auto new_bb = block_to_split->SplitBasicBlock(ir_context, message_.fresh_id(),
                                                 split_before);
   // The split does not automatically add a branch between the two parts of
   // the original block, so we add one.
   block_to_split->AddInstruction(MakeUnique<opt::Instruction>(
-      context, SpvOpBranch, 0, 0,
+      ir_context, SpvOpBranch, 0, 0,
       std::initializer_list<opt::Operand>{opt::Operand(
           spv_operand_type_t::SPV_OPERAND_TYPE_ID, {message_.fresh_id()})}));
   // If we split before OpPhi instructions, we need to update their
@@ -117,12 +119,15 @@
 
   // If the block being split was dead, the new block arising from the split is
   // also dead.
-  if (fact_manager->BlockIsDead(block_to_split->id())) {
-    fact_manager->AddFactBlockIsDead(message_.fresh_id());
+  if (transformation_context->GetFactManager()->BlockIsDead(
+          block_to_split->id())) {
+    transformation_context->GetFactManager()->AddFactBlockIsDead(
+        message_.fresh_id());
   }
 
   // Invalidate all analyses
-  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+  ir_context->InvalidateAnalysesExceptFor(
+      opt::IRContext::Analysis::kAnalysisNone);
 }
 
 protobufs::Transformation TransformationSplitBlock::ToMessage() const {
diff --git a/source/fuzz/transformation_split_block.h b/source/fuzz/transformation_split_block.h
index a193fc7..3bf6dfd 100644
--- a/source/fuzz/transformation_split_block.h
+++ b/source/fuzz/transformation_split_block.h
@@ -15,9 +15,9 @@
 #ifndef SOURCE_FUZZ_TRANSFORMATION_SPLIT_BLOCK_H_
 #define SOURCE_FUZZ_TRANSFORMATION_SPLIT_BLOCK_H_
 
-#include "source/fuzz/fact_manager.h"
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
 #include "source/fuzz/transformation.h"
+#include "source/fuzz/transformation_context.h"
 #include "source/opt/ir_context.h"
 
 namespace spvtools {
@@ -40,8 +40,9 @@
   // - Splitting 'blk' at 'inst', so that all instructions from 'inst' onwards
   //   appear in a new block that 'blk' directly jumps to must be valid.
   // - |message_.fresh_id| must not be used by the module.
-  bool IsApplicable(opt::IRContext* context,
-                    const FactManager& fact_manager) const override;
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
 
   // - A new block with label |message_.fresh_id| is inserted right after 'blk'
   //   in program order.
@@ -49,7 +50,8 @@
   //   block.
   // - 'blk' is made to jump unconditionally to the new block.
   // - If 'blk' was dead, the new block is also dead.
-  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
 
   protobufs::Transformation ToMessage() const override;
 
diff --git a/source/fuzz/transformation_store.cpp b/source/fuzz/transformation_store.cpp
new file mode 100644
index 0000000..3df1b7d
--- /dev/null
+++ b/source/fuzz/transformation_store.cpp
@@ -0,0 +1,129 @@
+// 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_store.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationStore::TransformationStore(
+    const spvtools::fuzz::protobufs::TransformationStore& message)
+    : message_(message) {}
+
+TransformationStore::TransformationStore(
+    uint32_t pointer_id, uint32_t value_id,
+    const protobufs::InstructionDescriptor& instruction_to_insert_before) {
+  message_.set_pointer_id(pointer_id);
+  message_.set_value_id(value_id);
+  *message_.mutable_instruction_to_insert_before() =
+      instruction_to_insert_before;
+}
+
+bool TransformationStore::IsApplicable(
+    opt::IRContext* ir_context,
+    const TransformationContext& transformation_context) const {
+  // The pointer must exist and have a type.
+  auto pointer = ir_context->get_def_use_mgr()->GetDef(message_.pointer_id());
+  if (!pointer || !pointer->type_id()) {
+    return false;
+  }
+
+  // The pointer type must indeed be a pointer.
+  auto pointer_type = ir_context->get_def_use_mgr()->GetDef(pointer->type_id());
+  assert(pointer_type && "Type id must be defined.");
+  if (pointer_type->opcode() != SpvOpTypePointer) {
+    return false;
+  }
+
+  // The pointer must not be read only.
+  if (pointer_type->GetSingleWordInOperand(0) == SpvStorageClassInput) {
+    return false;
+  }
+
+  // We do not want to allow storing to null or undefined pointers.
+  switch (pointer->opcode()) {
+    case SpvOpConstantNull:
+    case SpvOpUndef:
+      return false;
+    default:
+      break;
+  }
+
+  // Determine which instruction we should be inserting before.
+  auto insert_before =
+      FindInstruction(message_.instruction_to_insert_before(), ir_context);
+  // It must exist, ...
+  if (!insert_before) {
+    return false;
+  }
+  // ... and it must be legitimate to insert a store before it.
+  if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpStore,
+                                                    insert_before)) {
+    return false;
+  }
+
+  // The block we are inserting into needs to be dead, or else the pointee type
+  // of the pointer we are storing to needs to be irrelevant (otherwise the
+  // store could impact on the observable behaviour of the module).
+  if (!transformation_context.GetFactManager()->BlockIsDead(
+          ir_context->get_instr_block(insert_before)->id()) &&
+      !transformation_context.GetFactManager()->PointeeValueIsIrrelevant(
+          message_.pointer_id())) {
+    return false;
+  }
+
+  // The value being stored needs to exist and have a type.
+  auto value = ir_context->get_def_use_mgr()->GetDef(message_.value_id());
+  if (!value || !value->type_id()) {
+    return false;
+  }
+
+  // The type of the value must match the pointee type.
+  if (pointer_type->GetSingleWordInOperand(1) != value->type_id()) {
+    return false;
+  }
+
+  // The pointer needs to be available at the insertion point.
+  if (!fuzzerutil::IdIsAvailableBeforeInstruction(ir_context, insert_before,
+                                                  message_.pointer_id())) {
+    return false;
+  }
+
+  // The value needs to be available at the insertion point.
+  return fuzzerutil::IdIsAvailableBeforeInstruction(ir_context, insert_before,
+                                                    message_.value_id());
+}
+
+void TransformationStore::Apply(opt::IRContext* ir_context,
+                                TransformationContext* /*unused*/) const {
+  FindInstruction(message_.instruction_to_insert_before(), ir_context)
+      ->InsertBefore(MakeUnique<opt::Instruction>(
+          ir_context, SpvOpStore, 0, 0,
+          opt::Instruction::OperandList(
+              {{SPV_OPERAND_TYPE_ID, {message_.pointer_id()}},
+               {SPV_OPERAND_TYPE_ID, {message_.value_id()}}})));
+  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+}
+
+protobufs::Transformation TransformationStore::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_store() = message_;
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_store.h b/source/fuzz/transformation_store.h
new file mode 100644
index 0000000..6746aab
--- /dev/null
+++ b/source/fuzz/transformation_store.h
@@ -0,0 +1,65 @@
+// 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_STORE_H_
+#define SOURCE_FUZZ_TRANSFORMATION_STORE_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 TransformationStore : public Transformation {
+ public:
+  explicit TransformationStore(const protobufs::TransformationStore& message);
+
+  TransformationStore(
+      uint32_t pointer_id, uint32_t value_id,
+      const protobufs::InstructionDescriptor& instruction_to_insert_before);
+
+  // - |message_.pointer_id| must be the id of a pointer
+  // - The pointer type must not have read-only storage class
+  // - The pointer must not be OpConstantNull or OpUndef
+  // - |message_.value_id| must be an instruction result id that has the same
+  //   type as the pointee type of |message_.pointer_id|
+  // - |message_.instruction_to_insert_before| must identify an instruction
+  //   before which it is valid to insert an OpStore, and where both
+  //   |message_.pointer_id| and |message_.value_id| are available (according
+  //   to dominance rules)
+  // - Either the insertion point must be in a dead block, or it must be known
+  //   that the pointee value of |message_.pointer_id| is irrelevant
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // Adds an instruction of the form:
+  //   OpStore |pointer_id| |value_id|
+  // before the instruction identified by
+  // |message_.instruction_to_insert_before|.
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+ private:
+  protobufs::TransformationStore message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_STORE_H_
diff --git a/source/fuzz/transformation_swap_commutable_operands.cpp b/source/fuzz/transformation_swap_commutable_operands.cpp
new file mode 100644
index 0000000..b7622a2
--- /dev/null
+++ b/source/fuzz/transformation_swap_commutable_operands.cpp
@@ -0,0 +1,66 @@
+// 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_swap_commutable_operands.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationSwapCommutableOperands::TransformationSwapCommutableOperands(
+    const spvtools::fuzz::protobufs::TransformationSwapCommutableOperands&
+        message)
+    : message_(message) {}
+
+TransformationSwapCommutableOperands::TransformationSwapCommutableOperands(
+    const protobufs::InstructionDescriptor& instruction_descriptor) {
+  *message_.mutable_instruction_descriptor() = instruction_descriptor;
+}
+
+bool TransformationSwapCommutableOperands::IsApplicable(
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/
+    ) const {
+  auto instruction =
+      FindInstruction(message_.instruction_descriptor(), ir_context);
+  if (instruction == nullptr) return false;
+
+  SpvOp opcode = static_cast<SpvOp>(
+      message_.instruction_descriptor().target_instruction_opcode());
+  assert(instruction->opcode() == opcode &&
+         "The located instruction must have the same opcode as in the "
+         "descriptor.");
+  return spvOpcodeIsCommutativeBinaryOperator(opcode);
+}
+
+void TransformationSwapCommutableOperands::Apply(
+    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
+  // input parameters.
+  std::swap(instruction->GetInOperand(0), instruction->GetInOperand(1));
+}
+
+protobufs::Transformation TransformationSwapCommutableOperands::ToMessage()
+    const {
+  protobufs::Transformation result;
+  *result.mutable_swap_commutable_operands() = message_;
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_swap_commutable_operands.h b/source/fuzz/transformation_swap_commutable_operands.h
new file mode 100644
index 0000000..7fe5b70
--- /dev/null
+++ b/source/fuzz/transformation_swap_commutable_operands.h
@@ -0,0 +1,53 @@
+// 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_SWAP_COMMUTABLE_OPERANDS_H_
+#define SOURCE_FUZZ_TRANSFORMATION_SWAP_COMMUTABLE_OPERANDS_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 TransformationSwapCommutableOperands : public Transformation {
+ public:
+  explicit TransformationSwapCommutableOperands(
+      const protobufs::TransformationSwapCommutableOperands& message);
+
+  TransformationSwapCommutableOperands(
+      const protobufs::InstructionDescriptor& instruction_descriptor);
+
+  // - |message_.instruction_descriptor| must identify an existing
+  // commutative instruction
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // Swaps the commutable operands.
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+ private:
+  protobufs::TransformationSwapCommutableOperands message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_SWAP_COMMUTABLE_OPERANDS_H_
diff --git a/source/fuzz/transformation_toggle_access_chain_instruction.cpp b/source/fuzz/transformation_toggle_access_chain_instruction.cpp
new file mode 100644
index 0000000..ca24a18
--- /dev/null
+++ b/source/fuzz/transformation_toggle_access_chain_instruction.cpp
@@ -0,0 +1,83 @@
+// 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_toggle_access_chain_instruction.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationToggleAccessChainInstruction::
+    TransformationToggleAccessChainInstruction(
+        const spvtools::fuzz::protobufs::
+            TransformationToggleAccessChainInstruction& message)
+    : message_(message) {}
+
+TransformationToggleAccessChainInstruction::
+    TransformationToggleAccessChainInstruction(
+        const protobufs::InstructionDescriptor& instruction_descriptor) {
+  *message_.mutable_instruction_descriptor() = instruction_descriptor;
+}
+
+bool TransformationToggleAccessChainInstruction::IsApplicable(
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/
+    ) const {
+  auto instruction =
+      FindInstruction(message_.instruction_descriptor(), ir_context);
+  if (instruction == nullptr) {
+    return false;
+  }
+
+  SpvOp opcode = static_cast<SpvOp>(
+      message_.instruction_descriptor().target_instruction_opcode());
+
+  assert(instruction->opcode() == opcode &&
+         "The located instruction must have the same opcode as in the "
+         "descriptor.");
+
+  if (opcode == SpvOpAccessChain || opcode == SpvOpInBoundsAccessChain) {
+    return true;
+  }
+
+  return false;
+}
+
+void TransformationToggleAccessChainInstruction::Apply(
+    opt::IRContext* ir_context, TransformationContext* /*unused*/
+    ) const {
+  auto instruction =
+      FindInstruction(message_.instruction_descriptor(), ir_context);
+  SpvOp opcode = instruction->opcode();
+
+  if (opcode == SpvOpAccessChain) {
+    instruction->SetOpcode(SpvOpInBoundsAccessChain);
+  } else {
+    assert(opcode == SpvOpInBoundsAccessChain &&
+           "The located instruction must be an OpInBoundsAccessChain "
+           "instruction.");
+    instruction->SetOpcode(SpvOpAccessChain);
+  }
+}
+
+protobufs::Transformation
+TransformationToggleAccessChainInstruction::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_toggle_access_chain_instruction() = message_;
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_toggle_access_chain_instruction.h b/source/fuzz/transformation_toggle_access_chain_instruction.h
new file mode 100644
index 0000000..9cd8fd6
--- /dev/null
+++ b/source/fuzz/transformation_toggle_access_chain_instruction.h
@@ -0,0 +1,53 @@
+// 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_TOGGLE_ACCESS_CHAIN_INSTRUCTION_H_
+#define SOURCE_FUZZ_TRANSFORMATION_TOGGLE_ACCESS_CHAIN_INSTRUCTION_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 TransformationToggleAccessChainInstruction : public Transformation {
+ public:
+  explicit TransformationToggleAccessChainInstruction(
+      const protobufs::TransformationToggleAccessChainInstruction& message);
+
+  TransformationToggleAccessChainInstruction(
+      const protobufs::InstructionDescriptor& instruction_descriptor);
+
+  // - |message_.instruction_descriptor| must identify an existing
+  //   access chain instruction
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // Toggles the access chain instruction.
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+ private:
+  protobufs::TransformationToggleAccessChainInstruction message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_TOGGLE_ACCESS_CHAIN_INSTRUCTION_H_
diff --git a/source/fuzz/transformation_vector_shuffle.cpp b/source/fuzz/transformation_vector_shuffle.cpp
index e2d889d..ee64292 100644
--- a/source/fuzz/transformation_vector_shuffle.cpp
+++ b/source/fuzz/transformation_vector_shuffle.cpp
@@ -39,38 +39,37 @@
 }
 
 bool TransformationVectorShuffle::IsApplicable(
-    opt::IRContext* context,
-    const spvtools::fuzz::FactManager& /*unused*/) const {
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
   // The fresh id must not already be in use.
-  if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) {
+  if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
     return false;
   }
   // The instruction before which the shuffle will be inserted must exist.
   auto instruction_to_insert_before =
-      FindInstruction(message_.instruction_to_insert_before(), context);
+      FindInstruction(message_.instruction_to_insert_before(), ir_context);
   if (!instruction_to_insert_before) {
     return false;
   }
   // The first vector must be an instruction with a type id
   auto vector1_instruction =
-      context->get_def_use_mgr()->GetDef(message_.vector1());
+      ir_context->get_def_use_mgr()->GetDef(message_.vector1());
   if (!vector1_instruction || !vector1_instruction->type_id()) {
     return false;
   }
   // The second vector must be an instruction with a type id
   auto vector2_instruction =
-      context->get_def_use_mgr()->GetDef(message_.vector2());
+      ir_context->get_def_use_mgr()->GetDef(message_.vector2());
   if (!vector2_instruction || !vector2_instruction->type_id()) {
     return false;
   }
   auto vector1_type =
-      context->get_type_mgr()->GetType(vector1_instruction->type_id());
+      ir_context->get_type_mgr()->GetType(vector1_instruction->type_id());
   // The first vector instruction's type must actually be a vector type.
   if (!vector1_type->AsVector()) {
     return false;
   }
   auto vector2_type =
-      context->get_type_mgr()->GetType(vector2_instruction->type_id());
+      ir_context->get_type_mgr()->GetType(vector2_instruction->type_id());
   // The second vector instruction's type must actually be a vector type.
   if (!vector2_type->AsVector()) {
     return false;
@@ -92,14 +91,14 @@
   }
   // The module must already declare an appropriate type in which to store the
   // result of the shuffle.
-  if (!GetResultTypeId(context, *vector1_type->AsVector()->element_type())) {
+  if (!GetResultTypeId(ir_context, *vector1_type->AsVector()->element_type())) {
     return false;
   }
   // Each of the vectors used in the shuffle must be available at the insertion
   // point.
   for (auto used_instruction : {vector1_instruction, vector2_instruction}) {
-    if (auto block = context->get_instr_block(used_instruction)) {
-      if (!context->GetDominatorAnalysis(block->GetParent())
+    if (auto block = ir_context->get_instr_block(used_instruction)) {
+      if (!ir_context->GetDominatorAnalysis(block->GetParent())
                ->Dominates(used_instruction, instruction_to_insert_before)) {
         return false;
       }
@@ -113,7 +112,8 @@
 }
 
 void TransformationVectorShuffle::Apply(
-    opt::IRContext* context, spvtools::fuzz::FactManager* fact_manager) const {
+    opt::IRContext* ir_context,
+    TransformationContext* transformation_context) const {
   // Make input operands for a shuffle instruction - these comprise the two
   // vectors being shuffled, followed by the integer literal components.
   opt::Instruction::OperandList shuffle_operands = {
@@ -125,16 +125,18 @@
   }
 
   uint32_t result_type_id = GetResultTypeId(
-      context, *GetVectorType(context, message_.vector1())->element_type());
+      ir_context,
+      *GetVectorType(ir_context, message_.vector1())->element_type());
 
   // Add a shuffle instruction right before the instruction identified by
   // |message_.instruction_to_insert_before|.
-  FindInstruction(message_.instruction_to_insert_before(), context)
+  FindInstruction(message_.instruction_to_insert_before(), ir_context)
       ->InsertBefore(MakeUnique<opt::Instruction>(
-          context, SpvOpVectorShuffle, result_type_id, message_.fresh_id(),
+          ir_context, SpvOpVectorShuffle, result_type_id, message_.fresh_id(),
           shuffle_operands));
-  fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
-  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
+  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.
@@ -158,24 +160,26 @@
     // Get a data descriptor for the component of the input vector to which
     // |component| refers.
     if (component <
-        GetVectorType(context, message_.vector1())->element_count()) {
+        GetVectorType(ir_context, message_.vector1())->element_count()) {
       descriptor_for_source_component =
           MakeDataDescriptor(message_.vector1(), {component});
     } else {
       auto index_into_vector_2 =
           component -
-          GetVectorType(context, message_.vector1())->element_count();
-      assert(index_into_vector_2 <
-                 GetVectorType(context, message_.vector2())->element_count() &&
-             "Vector shuffle index is out of bounds.");
+          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.
-    fact_manager->AddFactDataSynonym(descriptor_for_result_component,
-                                     descriptor_for_source_component, context);
+    transformation_context->GetFactManager()->AddFactDataSynonym(
+        descriptor_for_result_component, descriptor_for_source_component,
+        ir_context);
   }
 }
 
@@ -186,16 +190,16 @@
 }
 
 uint32_t TransformationVectorShuffle::GetResultTypeId(
-    opt::IRContext* context, const opt::analysis::Type& element_type) const {
+    opt::IRContext* ir_context, const opt::analysis::Type& element_type) const {
   opt::analysis::Vector result_type(
       &element_type, static_cast<uint32_t>(message_.component_size()));
-  return context->get_type_mgr()->GetId(&result_type);
+  return ir_context->get_type_mgr()->GetId(&result_type);
 }
 
 opt::analysis::Vector* TransformationVectorShuffle::GetVectorType(
-    opt::IRContext* context, uint32_t id_of_vector) {
-  return context->get_type_mgr()
-      ->GetType(context->get_def_use_mgr()->GetDef(id_of_vector)->type_id())
+    opt::IRContext* ir_context, uint32_t id_of_vector) {
+  return ir_context->get_type_mgr()
+      ->GetType(ir_context->get_def_use_mgr()->GetDef(id_of_vector)->type_id())
       ->AsVector();
 }
 
diff --git a/source/fuzz/transformation_vector_shuffle.h b/source/fuzz/transformation_vector_shuffle.h
index 81ed227..f73fc31 100644
--- a/source/fuzz/transformation_vector_shuffle.h
+++ b/source/fuzz/transformation_vector_shuffle.h
@@ -15,10 +15,11 @@
 #ifndef SOURCE_FUZZ_TRANSFORMATION_VECTOR_SHUFFLE_H_
 #define SOURCE_FUZZ_TRANSFORMATION_VECTOR_SHUFFLE_H_
 
-#include "source/fuzz/fact_manager.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"
+
 #include "source/opt/types.h"
 
 namespace spvtools {
@@ -45,8 +46,9 @@
   // - The module must already contain a vector type with the same element type
   //   as |message_.vector1| and |message_.vector2|, and with the size of
   //   |message_component| as its element count
-  bool IsApplicable(opt::IRContext* context,
-                    const FactManager& fact_manager) const override;
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
 
   // Inserts an OpVectorShuffle instruction before
   // |message_.instruction_to_insert_before|, shuffles vectors
@@ -58,19 +60,20 @@
   // 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.
-  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
 
   protobufs::Transformation ToMessage() const override;
 
  private:
-  // Returns a type id that already exists in |context| suitable for
+  // Returns a type id that already exists in |ir_context| suitable for
   // representing the result of the shuffle, where |element_type| is known to
   // be the common element type of the vectors to which the shuffle is being
   // applied.  Returns 0 if no such id exists.
-  uint32_t GetResultTypeId(opt::IRContext* context,
+  uint32_t GetResultTypeId(opt::IRContext* ir_context,
                            const opt::analysis::Type& element_type) const;
 
-  static opt::analysis::Vector* GetVectorType(opt::IRContext* context,
+  static opt::analysis::Vector* GetVectorType(opt::IRContext* ir_context,
                                               uint32_t id_of_vector);
 
   protobufs::TransformationVectorShuffle message_;
diff --git a/source/link/linker.cpp b/source/link/linker.cpp
index 14e61a8..da6f0a7 100644
--- a/source/link/linker.cpp
+++ b/source/link/linker.cpp
@@ -324,6 +324,11 @@
       linked_module->AddDebug3Inst(
           std::unique_ptr<Instruction>(inst.Clone(linked_context)));
 
+  for (const auto& module : input_modules)
+    for (const auto& inst : module->ext_inst_debuginfo())
+      linked_module->AddExtInstDebugInfo(
+          std::unique_ptr<Instruction>(inst.Clone(linked_context)));
+
   // If the generated module uses SPIR-V 1.1 or higher, add an
   // OpModuleProcessed instruction about the linking step.
   if (linked_module->version() >= 0x10100) {
diff --git a/source/opcode.cpp b/source/opcode.cpp
index 7f91a0f..80fe3b3 100644
--- a/source/opcode.cpp
+++ b/source/opcode.cpp
@@ -1,4 +1,6 @@
-// Copyright (c) 2015-2016 The Khronos Group Inc.
+// Copyright (c) 2015-2020 The Khronos Group 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.
@@ -181,11 +183,15 @@
   }
 }
 
-const char* spvOpcodeString(const SpvOp opcode) {
+const char* spvOpcodeString(const uint32_t opcode) {
   const auto beg = kOpcodeTableEntries;
   const auto end = kOpcodeTableEntries + ARRAY_SIZE(kOpcodeTableEntries);
-  spv_opcode_desc_t needle = {"",    opcode, 0, nullptr, 0,   {},
-                              false, false,  0, nullptr, ~0u, ~0u};
+  spv_opcode_desc_t needle = {"",    static_cast<SpvOp>(opcode),
+                              0,     nullptr,
+                              0,     {},
+                              false, false,
+                              0,     nullptr,
+                              ~0u,   ~0u};
   auto comp = [](const spv_opcode_desc_t& lhs, const spv_opcode_desc_t& rhs) {
     return lhs.opcode < rhs.opcode;
   };
@@ -329,6 +335,9 @@
     case SpvOpTypeNamedBarrier:
     case SpvOpTypeAccelerationStructureNV:
     case SpvOpTypeCooperativeMatrixNV:
+    // case SpvOpTypeAccelerationStructureKHR: covered by
+    // SpvOpTypeAccelerationStructureNV
+    case SpvOpTypeRayQueryProvisionalKHR:
       return true;
     default:
       // In particular, OpTypeForwardPointer does not generate a type,
@@ -608,6 +617,39 @@
   }
 }
 
+bool spvOpcodeIsCommutativeBinaryOperator(SpvOp opcode) {
+  switch (opcode) {
+    case SpvOpPtrEqual:
+    case SpvOpPtrNotEqual:
+    case SpvOpIAdd:
+    case SpvOpFAdd:
+    case SpvOpIMul:
+    case SpvOpFMul:
+    case SpvOpDot:
+    case SpvOpIAddCarry:
+    case SpvOpUMulExtended:
+    case SpvOpSMulExtended:
+    case SpvOpBitwiseOr:
+    case SpvOpBitwiseXor:
+    case SpvOpBitwiseAnd:
+    case SpvOpOrdered:
+    case SpvOpUnordered:
+    case SpvOpLogicalEqual:
+    case SpvOpLogicalNotEqual:
+    case SpvOpLogicalOr:
+    case SpvOpLogicalAnd:
+    case SpvOpIEqual:
+    case SpvOpINotEqual:
+    case SpvOpFOrdEqual:
+    case SpvOpFUnordEqual:
+    case SpvOpFOrdNotEqual:
+    case SpvOpFUnordNotEqual:
+      return true;
+    default:
+      return false;
+  }
+}
+
 std::vector<uint32_t> spvOpcodeMemorySemanticsOperandIndices(SpvOp opcode) {
   switch (opcode) {
     case SpvOpMemoryBarrier:
diff --git a/source/opcode.h b/source/opcode.h
index ed64f1b..b4f0271 100644
--- a/source/opcode.h
+++ b/source/opcode.h
@@ -56,9 +56,6 @@
                         const uint16_t word_count,
                         const spv_endianness_t endian, spv_instruction_t* inst);
 
-// Gets the name of an instruction, without the "Op" prefix.
-const char* spvOpcodeString(const SpvOp opcode);
-
 // Determine if the given opcode is a scalar type. Returns zero if false,
 // non-zero otherwise.
 int32_t spvOpcodeIsScalarType(const SpvOp opcode);
@@ -133,6 +130,10 @@
 // Returns true if the given opcode is a debug instruction.
 bool spvOpcodeIsDebug(SpvOp opcode);
 
+// Returns true for opcodes that are binary operators,
+// where the order of the operands is irrelevant.
+bool spvOpcodeIsCommutativeBinaryOperator(SpvOp opcode);
+
 // Returns a vector containing the indices of the memory semantics <id>
 // operands for |opcode|.
 std::vector<uint32_t> spvOpcodeMemorySemanticsOperandIndices(SpvOp opcode);
diff --git a/source/operand.cpp b/source/operand.cpp
index 39d17a6..755ad6a 100644
--- a/source/operand.cpp
+++ b/source/operand.cpp
@@ -1,4 +1,6 @@
-// Copyright (c) 2015-2016 The Khronos Group Inc.
+// Copyright (c) 2015-2020 The Khronos Group 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.
@@ -19,6 +21,8 @@
 
 #include <algorithm>
 
+#include "DebugInfo.h"
+#include "OpenCLDebugInfo100.h"
 #include "source/macro.h"
 #include "source/spirv_constant.h"
 #include "source/spirv_target_env.h"
@@ -214,6 +218,14 @@
       return "kernel profiling info";
     case SPV_OPERAND_TYPE_CAPABILITY:
       return "capability";
+    case SPV_OPERAND_TYPE_RAY_FLAGS:
+      return "ray flags";
+    case SPV_OPERAND_TYPE_RAY_QUERY_INTERSECTION:
+      return "ray query intersection";
+    case SPV_OPERAND_TYPE_RAY_QUERY_COMMITTED_INTERSECTION_TYPE:
+      return "ray query committed intersection type";
+    case SPV_OPERAND_TYPE_RAY_QUERY_CANDIDATE_INTERSECTION_TYPE:
+      return "ray query candidate intersection type";
     case SPV_OPERAND_TYPE_IMAGE:
     case SPV_OPERAND_TYPE_OPTIONAL_IMAGE:
       return "image";
@@ -321,6 +333,10 @@
     case SPV_OPERAND_TYPE_KERNEL_ENQ_FLAGS:
     case SPV_OPERAND_TYPE_KERNEL_PROFILING_INFO:
     case SPV_OPERAND_TYPE_CAPABILITY:
+    case SPV_OPERAND_TYPE_RAY_FLAGS:
+    case SPV_OPERAND_TYPE_RAY_QUERY_INTERSECTION:
+    case SPV_OPERAND_TYPE_RAY_QUERY_COMMITTED_INTERSECTION_TYPE:
+    case SPV_OPERAND_TYPE_RAY_QUERY_CANDIDATE_INTERSECTION_TYPE:
     case SPV_OPERAND_TYPE_DEBUG_BASE_TYPE_ATTRIBUTE_ENCODING:
     case SPV_OPERAND_TYPE_DEBUG_COMPOSITE_TYPE:
     case SPV_OPERAND_TYPE_DEBUG_TYPE_QUALIFIER:
@@ -512,3 +528,37 @@
   }
   return out;
 }
+
+std::function<bool(unsigned)> spvDbgInfoExtOperandCanBeForwardDeclaredFunction(
+    spv_ext_inst_type_t ext_type, uint32_t key) {
+  // TODO(https://gitlab.khronos.org/spirv/SPIR-V/issues/532): Forward
+  // references for debug info instructions are still in discussion. We must
+  // update the following lines of code when we conclude the spec.
+  std::function<bool(unsigned index)> out;
+  if (ext_type == SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100) {
+    switch (OpenCLDebugInfo100Instructions(key)) {
+      case OpenCLDebugInfo100DebugFunction:
+        out = [](unsigned index) { return index == 13; };
+        break;
+      case OpenCLDebugInfo100DebugTypeComposite:
+        out = [](unsigned index) { return index >= 13; };
+        break;
+      default:
+        out = [](unsigned) { return false; };
+        break;
+    }
+  } else {
+    switch (DebugInfoInstructions(key)) {
+      case DebugInfoDebugFunction:
+        out = [](unsigned index) { return index == 13; };
+        break;
+      case DebugInfoDebugTypeComposite:
+        out = [](unsigned index) { return index >= 12; };
+        break;
+      default:
+        out = [](unsigned) { return false; };
+        break;
+    }
+  }
+  return out;
+}
diff --git a/source/operand.h b/source/operand.h
index 15a1825..7c73c6f 100644
--- a/source/operand.h
+++ b/source/operand.h
@@ -141,4 +141,11 @@
 std::function<bool(unsigned)> spvOperandCanBeForwardDeclaredFunction(
     SpvOp opcode);
 
+// Takes the instruction key of a debug info extension instruction
+// and returns a function object that will return true if the index
+// of the operand can be forward declared. This function will
+// used in the SSA validation stage of the pipeline
+std::function<bool(unsigned)> spvDbgInfoExtOperandCanBeForwardDeclaredFunction(
+    spv_ext_inst_type_t ext_type, uint32_t key);
+
 #endif  // SOURCE_OPERAND_H_
diff --git a/source/opt/CMakeLists.txt b/source/opt/CMakeLists.txt
index 0f719cb..1428c74 100644
--- a/source/opt/CMakeLists.txt
+++ b/source/opt/CMakeLists.txt
@@ -58,6 +58,7 @@
   inline_pass.h
   inst_bindless_check_pass.h
   inst_buff_addr_check_pass.h
+  inst_debug_printf_pass.h
   instruction.h
   instruction_list.h
   instrument_pass.h
@@ -164,6 +165,7 @@
   inline_pass.cpp
   inst_bindless_check_pass.cpp
   inst_buff_addr_check_pass.cpp
+  inst_debug_printf_pass.cpp
   instruction.cpp
   instruction_list.cpp
   instrument_pass.cpp
diff --git a/source/opt/aggressive_dead_code_elim_pass.cpp b/source/opt/aggressive_dead_code_elim_pass.cpp
index 761ff7c..db2b67b 100644
--- a/source/opt/aggressive_dead_code_elim_pass.cpp
+++ b/source/opt/aggressive_dead_code_elim_pass.cpp
@@ -923,6 +923,7 @@
       "SPV_GOOGLE_hlsl_functionality1",
       "SPV_GOOGLE_user_type",
       "SPV_NV_shader_subgroup_partitioned",
+      "SPV_EXT_demote_to_helper_invocation",
       "SPV_EXT_descriptor_indexing",
       "SPV_NV_fragment_shader_barycentric",
       "SPV_NV_compute_shader_derivatives",
@@ -930,6 +931,7 @@
       "SPV_NV_shading_rate",
       "SPV_NV_mesh_shader",
       "SPV_NV_ray_tracing",
+      "SPV_KHR_ray_tracing",
       "SPV_EXT_fragment_invocation_density",
       "SPV_EXT_physical_storage_buffer",
   });
diff --git a/source/opt/amd_ext_to_khr.cpp b/source/opt/amd_ext_to_khr.cpp
index e9b7f86..ccedc0b 100644
--- a/source/opt/amd_ext_to_khr.cpp
+++ b/source/opt/amd_ext_to_khr.cpp
@@ -53,12 +53,6 @@
   return ctx->get_type_mgr()->GetRegisteredType(&int_type);
 }
 
-bool NotImplementedYet(IRContext*, Instruction*,
-                       const std::vector<const analysis::Constant*>&) {
-  assert(false && "Not implemented.");
-  return false;
-}
-
 // Returns a folding rule that replaces |op(a,b,c)| by |op(op(a,b),c)|, where
 // |op| is either min or max. |opcode| is the binary opcode in the GLSLstd450
 // extended instruction set that corresponds to the trinary instruction being
@@ -686,13 +680,13 @@
   return true;
 }
 
-// A folding rule that will replace the CubeFaceCoordAMD extended
+// A folding rule that will replace the CubeFaceIndexAMD extended
 // instruction in the SPV_AMD_gcn_shader_ballot.  Returns true if the folding
 // is successful.
 //
 // The instruction
 //
-//  %result = OpExtInst %v2float %1 CubeFaceCoordAMD %input
+//  %result = OpExtInst %float %1 CubeFaceIndexAMD %input
 //
 // with
 //
@@ -705,7 +699,7 @@
 //      %is_z_neg = OpFOrdLessThan %bool %z %float_0
 //      %is_y_neg = OpFOrdLessThan %bool %y %float_0
 //      %is_x_neg = OpFOrdLessThan %bool %x %float_0
-//      %amax_x_y = OpExtInst %float %n_1 FMax %ay %ax
+//      %amax_x_y = OpExtInst %float %n_1 FMax %ax %ay
 //      %is_z_max = OpFOrdGreaterThanEqual %bool %az %amax_x_y
 //        %y_gt_x = OpFOrdGreaterThanEqual %bool %ay %ax
 //        %case_z = OpSelect %float %is_z_neg %float_5 %float4
@@ -800,6 +794,37 @@
   return true;
 }
 
+// A folding rule that will replace the TimeAMD extended instruction in the
+// SPV_AMD_gcn_shader_ballot.  It returns true if the folding is successful.
+// It returns False, otherwise.
+//
+// The instruction
+//
+//  %result = OpExtInst %uint64 %1 TimeAMD
+//
+// with
+//
+//  %result = OpReadClockKHR %uint64 %uint_3
+//
+// NOTE: TimeAMD uses subgroup scope (it is not a real time clock).
+bool ReplaceTimeAMD(IRContext* ctx, Instruction* inst,
+                    const std::vector<const analysis::Constant*>&) {
+  InstructionBuilder ir_builder(
+      ctx, inst,
+      IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
+  ctx->AddExtension("SPV_KHR_shader_clock");
+  ctx->AddCapability(SpvCapabilityShaderClockKHR);
+
+  inst->SetOpcode(SpvOpReadClockKHR);
+  Instruction::OperandList args;
+  uint32_t subgroup_scope_id = ir_builder.GetUintConstantId(SpvScopeSubgroup);
+  args.push_back({SPV_OPERAND_TYPE_ID, {subgroup_scope_id}});
+  inst->SetInOperands(std::move(args));
+  ctx->UpdateDefUse(inst);
+
+  return true;
+}
+
 class AmdExtFoldingRules : public FoldingRules {
  public:
   explicit AmdExtFoldingRules(IRContext* ctx) : FoldingRules(ctx) {}
@@ -869,7 +894,7 @@
           ReplaceCubeFaceCoord);
       ext_rules_[{extension_id, CubeFaceIndexAMD}].push_back(
           ReplaceCubeFaceIndex);
-      ext_rules_[{extension_id, TimeAMD}].push_back(NotImplementedYet);
+      ext_rules_[{extension_id, TimeAMD}].push_back(ReplaceTimeAMD);
     }
   }
 };
diff --git a/source/opt/basic_block.h b/source/opt/basic_block.h
index 0bab337..6741a50 100644
--- a/source/opt/basic_block.h
+++ b/source/opt/basic_block.h
@@ -214,7 +214,7 @@
   void KillAllInsts(bool killLabel);
 
   // Splits this basic block into two. Returns a new basic block with label
-  // |labelId| containing the instructions from |iter| onwards. Instructions
+  // |label_id| containing the instructions from |iter| onwards. Instructions
   // prior to |iter| remain in this basic block.  The new block will be added
   // to the function immediately after the original block.
   BasicBlock* SplitBasicBlock(IRContext* context, uint32_t label_id,
diff --git a/source/opt/const_folding_rules.cpp b/source/opt/const_folding_rules.cpp
index 2a2493f..d262a7e 100644
--- a/source/opt/const_folding_rules.cpp
+++ b/source/opt/const_folding_rules.cpp
@@ -265,7 +265,10 @@
       return nullptr;
     }
 
-    if (constants[0] == nullptr) {
+    const analysis::Constant* arg =
+        (inst->opcode() == SpvOpExtInst) ? constants[1] : constants[0];
+
+    if (arg == nullptr) {
       return nullptr;
     }
 
@@ -273,7 +276,7 @@
       std::vector<const analysis::Constant*> a_components;
       std::vector<const analysis::Constant*> results_components;
 
-      a_components = constants[0]->GetVectorComponents(const_mgr);
+      a_components = arg->GetVectorComponents(const_mgr);
 
       // Fold each component of the vector.
       for (uint32_t i = 0; i < a_components.size(); ++i) {
@@ -291,7 +294,7 @@
       }
       return const_mgr->GetConstant(vector_type, ids);
     } else {
-      return scalar_rule(result_type, constants[0], const_mgr);
+      return scalar_rule(result_type, arg, const_mgr);
     }
   };
 }
@@ -1070,6 +1073,60 @@
   return nullptr;
 }
 
+UnaryScalarFoldingRule FoldFTranscendentalUnary(double (*fp)(double)) {
+  return
+      [fp](const analysis::Type* result_type, const analysis::Constant* a,
+           analysis::ConstantManager* const_mgr) -> const analysis::Constant* {
+        assert(result_type != nullptr && a != nullptr);
+        const analysis::Float* float_type = a->type()->AsFloat();
+        assert(float_type != nullptr);
+        assert(float_type == result_type->AsFloat());
+        if (float_type->width() == 32) {
+          float fa = a->GetFloat();
+          float res = static_cast<float>(fp(fa));
+          utils::FloatProxy<float> result(res);
+          std::vector<uint32_t> words = result.GetWords();
+          return const_mgr->GetConstant(result_type, words);
+        } else if (float_type->width() == 64) {
+          double fa = a->GetDouble();
+          double res = fp(fa);
+          utils::FloatProxy<double> result(res);
+          std::vector<uint32_t> words = result.GetWords();
+          return const_mgr->GetConstant(result_type, words);
+        }
+        return nullptr;
+      };
+}
+
+BinaryScalarFoldingRule FoldFTranscendentalBinary(double (*fp)(double,
+                                                               double)) {
+  return
+      [fp](const analysis::Type* result_type, const analysis::Constant* a,
+           const analysis::Constant* b,
+           analysis::ConstantManager* const_mgr) -> const analysis::Constant* {
+        assert(result_type != nullptr && a != nullptr);
+        const analysis::Float* float_type = a->type()->AsFloat();
+        assert(float_type != nullptr);
+        assert(float_type == result_type->AsFloat());
+        assert(float_type == b->type()->AsFloat());
+        if (float_type->width() == 32) {
+          float fa = a->GetFloat();
+          float fb = b->GetFloat();
+          float res = static_cast<float>(fp(fa, fb));
+          utils::FloatProxy<float> result(res);
+          std::vector<uint32_t> words = result.GetWords();
+          return const_mgr->GetConstant(result_type, words);
+        } else if (float_type->width() == 64) {
+          double fa = a->GetDouble();
+          double fb = b->GetDouble();
+          double res = fp(fa, fb);
+          utils::FloatProxy<double> result(res);
+          std::vector<uint32_t> words = result.GetWords();
+          return const_mgr->GetConstant(result_type, words);
+        }
+        return nullptr;
+      };
+}
 }  // namespace
 
 void ConstantFoldingRules::AddFoldingRules() {
@@ -1175,6 +1232,45 @@
         FoldClamp2);
     ext_rules_[{ext_inst_glslstd450_id, GLSLstd450FClamp}].push_back(
         FoldClamp3);
+    ext_rules_[{ext_inst_glslstd450_id, GLSLstd450Sin}].push_back(
+        FoldFPUnaryOp(FoldFTranscendentalUnary(std::sin)));
+    ext_rules_[{ext_inst_glslstd450_id, GLSLstd450Cos}].push_back(
+        FoldFPUnaryOp(FoldFTranscendentalUnary(std::cos)));
+    ext_rules_[{ext_inst_glslstd450_id, GLSLstd450Tan}].push_back(
+        FoldFPUnaryOp(FoldFTranscendentalUnary(std::tan)));
+    ext_rules_[{ext_inst_glslstd450_id, GLSLstd450Asin}].push_back(
+        FoldFPUnaryOp(FoldFTranscendentalUnary(std::asin)));
+    ext_rules_[{ext_inst_glslstd450_id, GLSLstd450Acos}].push_back(
+        FoldFPUnaryOp(FoldFTranscendentalUnary(std::acos)));
+    ext_rules_[{ext_inst_glslstd450_id, GLSLstd450Atan}].push_back(
+        FoldFPUnaryOp(FoldFTranscendentalUnary(std::atan)));
+    ext_rules_[{ext_inst_glslstd450_id, GLSLstd450Exp}].push_back(
+        FoldFPUnaryOp(FoldFTranscendentalUnary(std::exp)));
+    ext_rules_[{ext_inst_glslstd450_id, GLSLstd450Log}].push_back(
+        FoldFPUnaryOp(FoldFTranscendentalUnary(std::log)));
+
+#ifdef __ANDROID__
+    // Android NDK r15c tageting ABI 15 doesn't have full support for C++11
+    // (no std::exp2/log2). ::exp2 is available from C99 but ::log2 isn't
+    // available up until ABI 18 so we use a shim
+    auto log2_shim = [](double v) -> double { return log(v) / log(2.0); };
+    ext_rules_[{ext_inst_glslstd450_id, GLSLstd450Exp2}].push_back(
+        FoldFPUnaryOp(FoldFTranscendentalUnary(::exp2)));
+    ext_rules_[{ext_inst_glslstd450_id, GLSLstd450Log2}].push_back(
+        FoldFPUnaryOp(FoldFTranscendentalUnary(log2_shim)));
+#else
+    ext_rules_[{ext_inst_glslstd450_id, GLSLstd450Exp2}].push_back(
+        FoldFPUnaryOp(FoldFTranscendentalUnary(std::exp2)));
+    ext_rules_[{ext_inst_glslstd450_id, GLSLstd450Log2}].push_back(
+        FoldFPUnaryOp(FoldFTranscendentalUnary(std::log2)));
+#endif
+
+    ext_rules_[{ext_inst_glslstd450_id, GLSLstd450Sqrt}].push_back(
+        FoldFPUnaryOp(FoldFTranscendentalUnary(std::sqrt)));
+    ext_rules_[{ext_inst_glslstd450_id, GLSLstd450Atan2}].push_back(
+        FoldFPBinaryOp(FoldFTranscendentalBinary(std::atan2)));
+    ext_rules_[{ext_inst_glslstd450_id, GLSLstd450Pow}].push_back(
+        FoldFPBinaryOp(FoldFTranscendentalBinary(std::pow)));
   }
 }
 }  // namespace opt
diff --git a/source/opt/dead_branch_elim_pass.cpp b/source/opt/dead_branch_elim_pass.cpp
index 8425a39..16d9fd5 100644
--- a/source/opt/dead_branch_elim_pass.cpp
+++ b/source/opt/dead_branch_elim_pass.cpp
@@ -167,7 +167,6 @@
     }
 
     if (simplify) {
-      modified = true;
       conditions_to_simplify.push_back({block, live_lab_id});
       stack.push_back(GetParentBlock(live_lab_id));
     } else {
@@ -179,24 +178,29 @@
     }
   }
 
-  // Traverse |conditions_to_simplify in reverse order.  This is done so that we
-  // simplify nested constructs before simplifying the constructs that contain
-  // them.
+  // Traverse |conditions_to_simplify| in reverse order.  This is done so that
+  // we simplify nested constructs before simplifying the constructs that
+  // contain them.
   for (auto b = conditions_to_simplify.rbegin();
        b != conditions_to_simplify.rend(); ++b) {
-    SimplifyBranch(b->first, b->second);
+    modified |= SimplifyBranch(b->first, b->second);
   }
 
   return modified;
 }
 
-void DeadBranchElimPass::SimplifyBranch(BasicBlock* block,
+bool DeadBranchElimPass::SimplifyBranch(BasicBlock* block,
                                         uint32_t live_lab_id) {
   Instruction* merge_inst = block->GetMergeInst();
   Instruction* terminator = block->terminator();
   if (merge_inst && merge_inst->opcode() == SpvOpSelectionMerge) {
     if (merge_inst->NextNode()->opcode() == SpvOpSwitch &&
         SwitchHasNestedBreak(block->id())) {
+      if (terminator->NumInOperands() == 2) {
+        // We cannot remove the branch, and it already has a single case, so no
+        // work to do.
+        return false;
+      }
       // We have to keep the switch because it has a nest break, so we
       // remove all cases except for the live one.
       Instruction::OperandList new_operands;
@@ -231,6 +235,7 @@
     AddBranch(live_lab_id, block);
     context()->KillInst(terminator);
   }
+  return true;
 }
 
 void DeadBranchElimPass::MarkUnreachableStructuredTargets(
@@ -643,7 +648,8 @@
         if (bb->id() == switch_header_id) {
           return true;
         }
-        return (cfg_analysis->ContainingConstruct(inst) == switch_header_id);
+        return (cfg_analysis->ContainingConstruct(inst) == switch_header_id &&
+                bb->GetMergeInst() == nullptr);
       });
 }
 
diff --git a/source/opt/dead_branch_elim_pass.h b/source/opt/dead_branch_elim_pass.h
index a50933f..7841bc4 100644
--- a/source/opt/dead_branch_elim_pass.h
+++ b/source/opt/dead_branch_elim_pass.h
@@ -159,14 +159,15 @@
       std::unordered_set<BasicBlock*>* blocks_with_back_edges);
 
   // Returns true if there is a brach to the merge node of the selection
-  // construct |switch_header_id| that is inside a nested selection construct.
+  // construct |switch_header_id| that is inside a nested selection construct or
+  // in the header of the nested selection construct.
   bool SwitchHasNestedBreak(uint32_t switch_header_id);
 
-  // Replaces the terminator of |block| with a branch to |live_lab_id|.  The
-  // merge instruction is deleted or moved as needed to maintain structured
-  // control flow.  Assumes that the StructuredCFGAnalysis is valid for the
-  // constructs containing |block|.
-  void SimplifyBranch(BasicBlock* block, uint32_t live_lab_id);
+  // Return true of the terminator of |block| is successfully replaced with a
+  // branch to |live_lab_id|.  The merge instruction is deleted or moved as
+  // needed to maintain structured control flow.  Assumes that the
+  // StructuredCFGAnalysis is valid for the constructs containing |block|.
+  bool SimplifyBranch(BasicBlock* block, uint32_t live_lab_id);
 };
 
 }  // namespace opt
diff --git a/source/opt/dominator_tree.cpp b/source/opt/dominator_tree.cpp
index c9346e1..da5073a 100644
--- a/source/opt/dominator_tree.cpp
+++ b/source/opt/dominator_tree.cpp
@@ -241,6 +241,7 @@
 
 bool DominatorTree::Dominates(const DominatorTreeNode* a,
                               const DominatorTreeNode* b) const {
+  if (!a || !b) return false;
   // Node A dominates node B if they are the same.
   if (a == b) return true;
 
diff --git a/source/opt/eliminate_dead_members_pass.cpp b/source/opt/eliminate_dead_members_pass.cpp
index 0b73b2d..5b8f4ec 100644
--- a/source/opt/eliminate_dead_members_pass.cpp
+++ b/source/opt/eliminate_dead_members_pass.cpp
@@ -19,6 +19,7 @@
 
 namespace {
 const uint32_t kRemovedMember = 0xFFFFFFFF;
+const uint32_t kSpecConstOpOpcodeIdx = 0;
 }
 
 namespace spvtools {
@@ -40,7 +41,22 @@
   // we have to mark them as fully used just to be safe.
   for (auto& inst : get_module()->types_values()) {
     if (inst.opcode() == SpvOpSpecConstantOp) {
-      MarkTypeAsFullyUsed(inst.type_id());
+      switch (inst.GetSingleWordInOperand(kSpecConstOpOpcodeIdx)) {
+        case SpvOpCompositeExtract:
+          MarkMembersAsLiveForExtract(&inst);
+          break;
+        case SpvOpCompositeInsert:
+          // Nothing specific to do.
+          break;
+        case SpvOpAccessChain:
+        case SpvOpInBoundsAccessChain:
+        case SpvOpPtrAccessChain:
+        case SpvOpInBoundsPtrAccessChain:
+          assert(false && "Not implemented yet.");
+          break;
+        default:
+          break;
+      }
     } else if (inst.opcode() == SpvOpVariable) {
       switch (inst.GetSingleWordInOperand(0)) {
         case SpvStorageClassInput:
@@ -153,13 +169,17 @@
 
 void EliminateDeadMembersPass::MarkMembersAsLiveForExtract(
     const Instruction* inst) {
-  assert(inst->opcode() == SpvOpCompositeExtract);
+  assert(inst->opcode() == SpvOpCompositeExtract ||
+         (inst->opcode() == SpvOpSpecConstantOp &&
+          inst->GetSingleWordInOperand(kSpecConstOpOpcodeIdx) ==
+              SpvOpCompositeExtract));
 
-  uint32_t composite_id = inst->GetSingleWordInOperand(0);
+  uint32_t first_operand = (inst->opcode() == SpvOpSpecConstantOp ? 1 : 0);
+  uint32_t composite_id = inst->GetSingleWordInOperand(first_operand);
   Instruction* composite_inst = get_def_use_mgr()->GetDef(composite_id);
   uint32_t type_id = composite_inst->type_id();
 
-  for (uint32_t i = 1; i < inst->NumInOperands(); ++i) {
+  for (uint32_t i = first_operand + 1; i < inst->NumInOperands(); ++i) {
     Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
     uint32_t member_idx = inst->GetSingleWordInOperand(i);
     switch (type_inst->opcode()) {
@@ -295,10 +315,22 @@
         modified |= UpdateOpArrayLength(inst);
         break;
       case SpvOpSpecConstantOp:
-        assert(false && "Not yet implemented.");
-        // with OpCompositeExtract, OpCompositeInsert
-        // For kernels: OpAccessChain, OpInBoundsAccessChain, OpPtrAccessChain,
-        // OpInBoundsPtrAccessChain
+        switch (inst->GetSingleWordInOperand(kSpecConstOpOpcodeIdx)) {
+          case SpvOpCompositeExtract:
+            modified |= UpdateCompsiteExtract(inst);
+            break;
+          case SpvOpCompositeInsert:
+            modified |= UpdateCompositeInsert(inst);
+            break;
+          case SpvOpAccessChain:
+          case SpvOpInBoundsAccessChain:
+          case SpvOpPtrAccessChain:
+          case SpvOpInBoundsPtrAccessChain:
+            assert(false && "Not implemented yet.");
+            break;
+          default:
+            break;
+        }
         break;
       default:
         break;
@@ -393,7 +425,8 @@
 }
 
 bool EliminateDeadMembersPass::UpdateConstantComposite(Instruction* inst) {
-  assert(inst->opcode() == SpvOpConstantComposite ||
+  assert(inst->opcode() == SpvOpSpecConstantComposite ||
+         inst->opcode() == SpvOpConstantComposite ||
          inst->opcode() == SpvOpCompositeConstruct);
   uint32_t type_id = inst->type_id();
 
@@ -506,14 +539,25 @@
 }
 
 bool EliminateDeadMembersPass::UpdateCompsiteExtract(Instruction* inst) {
-  uint32_t object_id = inst->GetSingleWordInOperand(0);
+  assert(inst->opcode() == SpvOpCompositeExtract ||
+         (inst->opcode() == SpvOpSpecConstantOp &&
+          inst->GetSingleWordInOperand(kSpecConstOpOpcodeIdx) ==
+              SpvOpCompositeExtract));
+
+  uint32_t first_operand = 0;
+  if (inst->opcode() == SpvOpSpecConstantOp) {
+    first_operand = 1;
+  }
+  uint32_t object_id = inst->GetSingleWordInOperand(first_operand);
   Instruction* object_inst = get_def_use_mgr()->GetDef(object_id);
   uint32_t type_id = object_inst->type_id();
 
   Instruction::OperandList new_operands;
   bool modified = false;
-  new_operands.emplace_back(inst->GetInOperand(0));
-  for (uint32_t i = 1; i < inst->NumInOperands(); ++i) {
+  for (uint32_t i = 0; i < first_operand + 1; i++) {
+    new_operands.emplace_back(inst->GetInOperand(i));
+  }
+  for (uint32_t i = first_operand + 1; i < inst->NumInOperands(); ++i) {
     uint32_t member_idx = inst->GetSingleWordInOperand(i);
     uint32_t new_member_idx = GetNewMemberIndex(type_id, member_idx);
     assert(new_member_idx != kRemovedMember);
@@ -526,8 +570,6 @@
     Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
     switch (type_inst->opcode()) {
       case SpvOpTypeStruct:
-        assert(i != 1 || (inst->opcode() != SpvOpPtrAccessChain &&
-                          inst->opcode() != SpvOpInBoundsPtrAccessChain));
         // The type will have already been rewriten, so use the new member
         // index.
         type_id = type_inst->GetSingleWordInOperand(new_member_idx);
@@ -552,15 +594,27 @@
 }
 
 bool EliminateDeadMembersPass::UpdateCompositeInsert(Instruction* inst) {
-  uint32_t composite_id = inst->GetSingleWordInOperand(1);
+  assert(inst->opcode() == SpvOpCompositeInsert ||
+         (inst->opcode() == SpvOpSpecConstantOp &&
+          inst->GetSingleWordInOperand(kSpecConstOpOpcodeIdx) ==
+              SpvOpCompositeInsert));
+
+  uint32_t first_operand = 0;
+  if (inst->opcode() == SpvOpSpecConstantOp) {
+    first_operand = 1;
+  }
+
+  uint32_t composite_id = inst->GetSingleWordInOperand(first_operand + 1);
   Instruction* composite_inst = get_def_use_mgr()->GetDef(composite_id);
   uint32_t type_id = composite_inst->type_id();
 
   Instruction::OperandList new_operands;
   bool modified = false;
-  new_operands.emplace_back(inst->GetInOperand(0));
-  new_operands.emplace_back(inst->GetInOperand(1));
-  for (uint32_t i = 2; i < inst->NumInOperands(); ++i) {
+
+  for (uint32_t i = 0; i < first_operand + 2; ++i) {
+    new_operands.emplace_back(inst->GetInOperand(i));
+  }
+  for (uint32_t i = first_operand + 2; i < inst->NumInOperands(); ++i) {
     uint32_t member_idx = inst->GetSingleWordInOperand(i);
     uint32_t new_member_idx = GetNewMemberIndex(type_id, member_idx);
     if (new_member_idx == kRemovedMember) {
diff --git a/source/opt/feature_manager.cpp b/source/opt/feature_manager.cpp
index 63d50b6..b4d6f1b 100644
--- a/source/opt/feature_manager.cpp
+++ b/source/opt/feature_manager.cpp
@@ -47,6 +47,11 @@
   }
 }
 
+void FeatureManager::RemoveExtension(Extension ext) {
+  if (!extensions_.Contains(ext)) return;
+  extensions_.Remove(ext);
+}
+
 void FeatureManager::AddCapability(SpvCapability cap) {
   if (capabilities_.Contains(cap)) return;
 
@@ -60,6 +65,11 @@
   }
 }
 
+void FeatureManager::RemoveCapability(SpvCapability cap) {
+  if (!capabilities_.Contains(cap)) return;
+  capabilities_.Remove(cap);
+}
+
 void FeatureManager::AddCapabilities(Module* module) {
   for (Instruction& inst : module->capabilities()) {
     AddCapability(static_cast<SpvCapability>(inst.GetSingleWordInOperand(0)));
diff --git a/source/opt/feature_manager.h b/source/opt/feature_manager.h
index 2fe3291..881d5e6 100644
--- a/source/opt/feature_manager.h
+++ b/source/opt/feature_manager.h
@@ -30,11 +30,17 @@
   // Returns true if |ext| is an enabled extension in the module.
   bool HasExtension(Extension ext) const { return extensions_.Contains(ext); }
 
+  // Removes the given |extension| from the current FeatureManager.
+  void RemoveExtension(Extension extension);
+
   // Returns true if |cap| is an enabled capability in the module.
   bool HasCapability(SpvCapability cap) const {
     return capabilities_.Contains(cap);
   }
 
+  // Removes the given |capability| from the current FeatureManager.
+  void RemoveCapability(SpvCapability capability);
+
   // Analyzes |module| and records enabled extensions and capabilities.
   void Analyze(Module* module);
 
diff --git a/source/opt/function.cpp b/source/opt/function.cpp
index efda68b..5d50f37 100644
--- a/source/opt/function.cpp
+++ b/source/opt/function.cpp
@@ -34,6 +34,11 @@
       },
       true);
 
+  for (const auto& i : debug_insts_in_header_) {
+    clone->AddDebugInstructionInHeader(
+        std::unique_ptr<Instruction>(i.Clone(ctx)));
+  }
+
   clone->blocks_.reserve(blocks_.size());
   for (const auto& b : blocks_) {
     std::unique_ptr<BasicBlock> bb(b->Clone(ctx));
@@ -79,6 +84,12 @@
     }
   }
 
+  for (auto& di : debug_insts_in_header_) {
+    if (!di.WhileEachInst(f, run_on_debug_line_insts)) {
+      return false;
+    }
+  }
+
   for (auto& bb : blocks_) {
     if (!bb->WhileEachInst(f, run_on_debug_line_insts)) {
       return false;
@@ -106,6 +117,12 @@
     }
   }
 
+  for (const auto& di : debug_insts_in_header_) {
+    if (!di.WhileEachInst(f, run_on_debug_line_insts)) {
+      return false;
+    }
+  }
+
   for (const auto& bb : blocks_) {
     if (!static_cast<const BasicBlock*>(bb.get())->WhileEachInst(
             f, run_on_debug_line_insts)) {
diff --git a/source/opt/function.h b/source/opt/function.h
index 3908568..f208d8e 100644
--- a/source/opt/function.h
+++ b/source/opt/function.h
@@ -56,6 +56,8 @@
 
   // Appends a parameter to this function.
   inline void AddParameter(std::unique_ptr<Instruction> p);
+  // Appends a debug instruction in function header to this function.
+  inline void AddDebugInstructionInHeader(std::unique_ptr<Instruction> p);
   // Appends a basic block to this function.
   inline void AddBasicBlock(std::unique_ptr<BasicBlock> b);
   // Appends a basic block to this function at the position |ip|.
@@ -151,6 +153,8 @@
   std::unique_ptr<Instruction> def_inst_;
   // All parameters to this function.
   std::vector<std::unique_ptr<Instruction>> params_;
+  // All debug instructions in this function's header.
+  InstructionList debug_insts_in_header_;
   // All basic blocks inside this function in specification order
   std::vector<std::unique_ptr<BasicBlock>> blocks_;
   // The OpFunctionEnd instruction.
@@ -167,6 +171,11 @@
   params_.emplace_back(std::move(p));
 }
 
+inline void Function::AddDebugInstructionInHeader(
+    std::unique_ptr<Instruction> p) {
+  debug_insts_in_header_.push_back(std::move(p));
+}
+
 inline void Function::AddBasicBlock(std::unique_ptr<BasicBlock> b) {
   AddBasicBlock(std::move(b), end());
 }
diff --git a/source/opt/inst_debug_printf_pass.cpp b/source/opt/inst_debug_printf_pass.cpp
new file mode 100644
index 0000000..c0e6bc3
--- /dev/null
+++ b/source/opt/inst_debug_printf_pass.cpp
@@ -0,0 +1,266 @@
+// Copyright (c) 2020 The Khronos Group Inc.
+// Copyright (c) 2020 Valve Corporation
+// Copyright (c) 2020 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 "inst_debug_printf_pass.h"
+
+#include "spirv/unified1/NonSemanticDebugPrintf.h"
+
+namespace spvtools {
+namespace opt {
+
+void InstDebugPrintfPass::GenOutputValues(Instruction* val_inst,
+                                          std::vector<uint32_t>* val_ids,
+                                          InstructionBuilder* builder) {
+  uint32_t val_ty_id = val_inst->type_id();
+  analysis::TypeManager* type_mgr = context()->get_type_mgr();
+  analysis::Type* val_ty = type_mgr->GetType(val_ty_id);
+  switch (val_ty->kind()) {
+    case analysis::Type::kVector: {
+      analysis::Vector* v_ty = val_ty->AsVector();
+      const analysis::Type* c_ty = v_ty->element_type();
+      uint32_t c_ty_id = type_mgr->GetId(c_ty);
+      for (uint32_t c = 0; c < v_ty->element_count(); ++c) {
+        Instruction* c_inst = builder->AddIdLiteralOp(
+            c_ty_id, SpvOpCompositeExtract, val_inst->result_id(), c);
+        GenOutputValues(c_inst, val_ids, builder);
+      }
+      return;
+    }
+    case analysis::Type::kBool: {
+      // Select between uint32 zero or one
+      uint32_t zero_id = builder->GetUintConstantId(0);
+      uint32_t one_id = builder->GetUintConstantId(1);
+      Instruction* sel_inst = builder->AddTernaryOp(
+          GetUintId(), SpvOpSelect, val_inst->result_id(), one_id, zero_id);
+      val_ids->push_back(sel_inst->result_id());
+      return;
+    }
+    case analysis::Type::kFloat: {
+      analysis::Float* f_ty = val_ty->AsFloat();
+      switch (f_ty->width()) {
+        case 16: {
+          // Convert float16 to float32 and recurse
+          Instruction* f32_inst = builder->AddUnaryOp(
+              GetFloatId(), SpvOpFConvert, val_inst->result_id());
+          GenOutputValues(f32_inst, val_ids, builder);
+          return;
+        }
+        case 64: {
+          // Bitcast float64 to uint64 and recurse
+          Instruction* ui64_inst = builder->AddUnaryOp(
+              GetUint64Id(), SpvOpBitcast, val_inst->result_id());
+          GenOutputValues(ui64_inst, val_ids, builder);
+          return;
+        }
+        case 32: {
+          // Bitcase float32 to uint32
+          Instruction* bc_inst = builder->AddUnaryOp(GetUintId(), SpvOpBitcast,
+                                                     val_inst->result_id());
+          val_ids->push_back(bc_inst->result_id());
+          return;
+        }
+        default:
+          assert(false && "unsupported float width");
+          return;
+      }
+    }
+    case analysis::Type::kInteger: {
+      analysis::Integer* i_ty = val_ty->AsInteger();
+      switch (i_ty->width()) {
+        case 64: {
+          Instruction* ui64_inst = val_inst;
+          if (i_ty->IsSigned()) {
+            // Bitcast sint64 to uint64
+            ui64_inst = builder->AddUnaryOp(GetUint64Id(), SpvOpBitcast,
+                                            val_inst->result_id());
+          }
+          // Break uint64 into 2x uint32
+          Instruction* lo_ui64_inst = builder->AddUnaryOp(
+              GetUintId(), SpvOpUConvert, ui64_inst->result_id());
+          Instruction* rshift_ui64_inst = builder->AddBinaryOp(
+              GetUint64Id(), SpvOpShiftRightLogical, ui64_inst->result_id(),
+              builder->GetUintConstantId(32));
+          Instruction* hi_ui64_inst = builder->AddUnaryOp(
+              GetUintId(), SpvOpUConvert, rshift_ui64_inst->result_id());
+          val_ids->push_back(lo_ui64_inst->result_id());
+          val_ids->push_back(hi_ui64_inst->result_id());
+          return;
+        }
+        case 8: {
+          Instruction* ui8_inst = val_inst;
+          if (i_ty->IsSigned()) {
+            // Bitcast sint8 to uint8
+            ui8_inst = builder->AddUnaryOp(GetUint8Id(), SpvOpBitcast,
+                                           val_inst->result_id());
+          }
+          // Convert uint8 to uint32
+          Instruction* ui32_inst = builder->AddUnaryOp(
+              GetUintId(), SpvOpUConvert, ui8_inst->result_id());
+          val_ids->push_back(ui32_inst->result_id());
+          return;
+        }
+        case 32: {
+          Instruction* ui32_inst = val_inst;
+          if (i_ty->IsSigned()) {
+            // Bitcast sint32 to uint32
+            ui32_inst = builder->AddUnaryOp(GetUintId(), SpvOpBitcast,
+                                            val_inst->result_id());
+          }
+          // uint32 needs no further processing
+          val_ids->push_back(ui32_inst->result_id());
+          return;
+        }
+        default:
+          // TODO(greg-lunarg): Support non-32-bit int
+          assert(false && "unsupported int width");
+          return;
+      }
+    }
+    default:
+      assert(false && "unsupported type");
+      return;
+  }
+}
+
+void InstDebugPrintfPass::GenOutputCode(
+    Instruction* printf_inst, uint32_t stage_idx,
+    std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
+  BasicBlock* back_blk_ptr = &*new_blocks->back();
+  InstructionBuilder builder(
+      context(), back_blk_ptr,
+      IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
+  // Gen debug printf record validation-specific values. The format string
+  // will have its id written. Vectors will need to be broken down into
+  // component values. float16 will need to be converted to float32. Pointer
+  // and uint64 will need to be converted to two uint32 values. float32 will
+  // need to be bitcast to uint32. int32 will need to be bitcast to uint32.
+  std::vector<uint32_t> val_ids;
+  bool is_first_operand = false;
+  printf_inst->ForEachInId(
+      [&is_first_operand, &val_ids, &builder, this](const uint32_t* iid) {
+        // skip set operand
+        if (!is_first_operand) {
+          is_first_operand = true;
+          return;
+        }
+        Instruction* opnd_inst = get_def_use_mgr()->GetDef(*iid);
+        if (opnd_inst->opcode() == SpvOpString) {
+          uint32_t string_id_id = builder.GetUintConstantId(*iid);
+          val_ids.push_back(string_id_id);
+        } else {
+          GenOutputValues(opnd_inst, &val_ids, &builder);
+        }
+      });
+  GenDebugStreamWrite(uid2offset_[printf_inst->unique_id()], stage_idx, val_ids,
+                      &builder);
+  context()->KillInst(printf_inst);
+}
+
+void InstDebugPrintfPass::GenDebugPrintfCode(
+    BasicBlock::iterator ref_inst_itr,
+    UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
+    std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
+  // If not DebugPrintf OpExtInst, return.
+  Instruction* printf_inst = &*ref_inst_itr;
+  if (printf_inst->opcode() != SpvOpExtInst) return;
+  if (printf_inst->GetSingleWordInOperand(0) != ext_inst_printf_id_) return;
+  if (printf_inst->GetSingleWordInOperand(1) !=
+      NonSemanticDebugPrintfDebugPrintf)
+    return;
+  // Initialize DefUse manager before dismantling module
+  (void)get_def_use_mgr();
+  // 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);
+  new_blocks->push_back(std::move(new_blk_ptr));
+  // Generate instructions to output printf args to printf buffer
+  GenOutputCode(printf_inst, stage_idx, new_blocks);
+  // Caller expects at least two blocks with last block containing remaining
+  // code, so end block after instrumentation, create remainder block, and
+  // branch to it
+  uint32_t rem_blk_id = TakeNextId();
+  std::unique_ptr<Instruction> rem_label(NewLabel(rem_blk_id));
+  BasicBlock* back_blk_ptr = &*new_blocks->back();
+  InstructionBuilder builder(
+      context(), back_blk_ptr,
+      IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
+  (void)builder.AddBranch(rem_blk_id);
+  // Gen remainder block
+  new_blk_ptr.reset(new BasicBlock(std::move(rem_label)));
+  builder.SetInsertPoint(&*new_blk_ptr);
+  // Move original block's remaining code into remainder block and add
+  // to new blocks
+  MovePostludeCode(ref_block_itr, &*new_blk_ptr);
+  new_blocks->push_back(std::move(new_blk_ptr));
+}
+
+void InstDebugPrintfPass::InitializeInstDebugPrintf() {
+  // Initialize base class
+  InitializeInstrument();
+}
+
+Pass::Status InstDebugPrintfPass::ProcessImpl() {
+  // Perform printf instrumentation on each entry point function in module
+  InstProcessFunction 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 GenDebugPrintfCode(ref_inst_itr, ref_block_itr, stage_idx,
+                                  new_blocks);
+      };
+  (void)InstProcessEntryPointCallTree(pfn);
+  // Remove DebugPrintf OpExtInstImport instruction
+  Instruction* ext_inst_import_inst =
+      get_def_use_mgr()->GetDef(ext_inst_printf_id_);
+  context()->KillInst(ext_inst_import_inst);
+  // If no remaining non-semantic instruction sets, remove non-semantic debug
+  // info extension from module and feature manager
+  bool non_sem_set_seen = false;
+  for (auto c_itr = context()->module()->ext_inst_import_begin();
+       c_itr != context()->module()->ext_inst_import_end(); ++c_itr) {
+    const char* set_name =
+        reinterpret_cast<const char*>(&c_itr->GetInOperand(0).words[0]);
+    const char* non_sem_str = "NonSemantic.";
+    if (!strncmp(set_name, non_sem_str, strlen(non_sem_str))) {
+      non_sem_set_seen = true;
+      break;
+    }
+  }
+  if (!non_sem_set_seen) {
+    for (auto c_itr = context()->module()->extension_begin();
+         c_itr != context()->module()->extension_end(); ++c_itr) {
+      const char* ext_name =
+          reinterpret_cast<const char*>(&c_itr->GetInOperand(0).words[0]);
+      if (!strcmp(ext_name, "SPV_KHR_non_semantic_info")) {
+        context()->KillInst(&*c_itr);
+        break;
+      }
+    }
+    context()->get_feature_mgr()->RemoveExtension(kSPV_KHR_non_semantic_info);
+  }
+  return Status::SuccessWithChange;
+}
+
+Pass::Status InstDebugPrintfPass::Process() {
+  ext_inst_printf_id_ =
+      get_module()->GetExtInstImportId("NonSemantic.DebugPrintf");
+  if (ext_inst_printf_id_ == 0) return Status::SuccessWithoutChange;
+  InitializeInstDebugPrintf();
+  return ProcessImpl();
+}
+
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/inst_debug_printf_pass.h b/source/opt/inst_debug_printf_pass.h
new file mode 100644
index 0000000..2968a20
--- /dev/null
+++ b/source/opt/inst_debug_printf_pass.h
@@ -0,0 +1,96 @@
+// Copyright (c) 2020 The Khronos Group Inc.
+// Copyright (c) 2020 Valve Corporation
+// Copyright (c) 2020 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 LIBSPIRV_OPT_INST_DEBUG_PRINTF_PASS_H_
+#define LIBSPIRV_OPT_INST_DEBUG_PRINTF_PASS_H_
+
+#include "instrument_pass.h"
+
+namespace spvtools {
+namespace opt {
+
+// This class/pass is designed to support the debug printf GPU-assisted layer
+// of https://github.com/KhronosGroup/Vulkan-ValidationLayers. Its internal and
+// external design may change as the layer evolves.
+class InstDebugPrintfPass : public InstrumentPass {
+ public:
+  // For test harness only
+  InstDebugPrintfPass()
+      : InstrumentPass(7, 23, kInstValidationIdDebugPrintf, 2) {}
+  // For all other interfaces
+  InstDebugPrintfPass(uint32_t desc_set, uint32_t shader_id)
+      : InstrumentPass(desc_set, shader_id, kInstValidationIdDebugPrintf, 2) {}
+
+  ~InstDebugPrintfPass() override = default;
+
+  // See optimizer.hpp for pass user documentation.
+  Status Process() override;
+
+  const char* name() const override { return "inst-printf-pass"; }
+
+ private:
+  // Generate instructions for OpDebugPrintf.
+  //
+  // If |ref_inst_itr| is an OpDebugPrintf, return in |new_blocks| the result
+  // of replacing it with buffer write instructions within its block at
+  // |ref_block_itr|.  The instructions write a record to the printf
+  // output buffer stream including |function_idx, instruction_idx, stage_idx|
+  // and removes the OpDebugPrintf. The block at |ref_block_itr| can just be
+  // replaced with the block in |new_blocks|. Besides the buffer writes, this
+  // block will comprise all instructions preceding and following
+  // |ref_inst_itr|.
+  //
+  // This function is designed to be passed to
+  // InstrumentPass::InstProcessEntryPointCallTree(), which applies the
+  // function to each instruction in a module and replaces the instruction
+  // if warranted.
+  //
+  // This instrumentation function utilizes GenDebugStreamWrite() to write its
+  // error records. The validation-specific part of the error record will
+  // consist of a uint32 which is the id of the format string plus a sequence
+  // of uint32s representing the values of the remaining operands of the
+  // DebugPrintf.
+  void GenDebugPrintfCode(BasicBlock::iterator ref_inst_itr,
+                          UptrVectorIterator<BasicBlock> ref_block_itr,
+                          uint32_t stage_idx,
+                          std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
+
+  // Generate a sequence of uint32 instructions in |builder| (if necessary)
+  // representing the value of |val_inst|, which must be a buffer pointer, a
+  // uint64, or a scalar or vector of type uint32, float32 or float16. Append
+  // the ids of all values to the end of |val_ids|.
+  void GenOutputValues(Instruction* val_inst, std::vector<uint32_t>* val_ids,
+                       InstructionBuilder* builder);
+
+  // Generate instructions to write a record containing the operands of
+  // |printf_inst| arguments to printf buffer, adding new code to the end of
+  // the last block in |new_blocks|. Kill OpDebugPrintf instruction.
+  void GenOutputCode(Instruction* printf_inst, uint32_t stage_idx,
+                     std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
+
+  // Initialize state for instrumenting bindless checking
+  void InitializeInstDebugPrintf();
+
+  // Apply GenDebugPrintfCode to every instruction in module.
+  Pass::Status ProcessImpl();
+
+  uint32_t ext_inst_printf_id_;
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // LIBSPIRV_OPT_INST_DEBUG_PRINTF_PASS_H_
diff --git a/source/opt/instruction.cpp b/source/opt/instruction.cpp
index 49f9142..3ce38a9 100644
--- a/source/opt/instruction.cpp
+++ b/source/opt/instruction.cpp
@@ -16,6 +16,7 @@
 
 #include <initializer_list>
 
+#include "OpenCLDebugInfo100.h"
 #include "source/disassemble.h"
 #include "source/opt/fold.h"
 #include "source/opt/ir_context.h"
@@ -30,6 +31,11 @@
 const uint32_t kLoadBaseIndex = 0;
 const uint32_t kVariableStorageClassIndex = 0;
 const uint32_t kTypeImageSampledIndex = 5;
+
+// Constants for OpenCL.DebugInfo.100 extension instructions.
+const uint32_t kDebugScopeNumWords = 7;
+const uint32_t kDebugScopeNumWordsWithoutInlinedAt = 6;
+const uint32_t kDebugNoScopeNumWords = 5;
 }  // namespace
 
 Instruction::Instruction(IRContext* c)
@@ -38,7 +44,8 @@
       opcode_(SpvOpNop),
       has_type_id_(false),
       has_result_id_(false),
-      unique_id_(c->TakeNextUniqueId()) {}
+      unique_id_(c->TakeNextUniqueId()),
+      dbg_scope_(kNoDebugScope, kNoInlinedAt) {}
 
 Instruction::Instruction(IRContext* c, SpvOp op)
     : utils::IntrusiveNodeBase<Instruction>(),
@@ -46,7 +53,8 @@
       opcode_(op),
       has_type_id_(false),
       has_result_id_(false),
-      unique_id_(c->TakeNextUniqueId()) {}
+      unique_id_(c->TakeNextUniqueId()),
+      dbg_scope_(kNoDebugScope, kNoInlinedAt) {}
 
 Instruction::Instruction(IRContext* c, const spv_parsed_instruction_t& inst,
                          std::vector<Instruction>&& dbg_line)
@@ -55,7 +63,8 @@
       has_type_id_(inst.type_id != 0),
       has_result_id_(inst.result_id != 0),
       unique_id_(c->TakeNextUniqueId()),
-      dbg_line_insts_(std::move(dbg_line)) {
+      dbg_line_insts_(std::move(dbg_line)),
+      dbg_scope_(kNoDebugScope, kNoInlinedAt) {
   assert((!IsDebugLineInst(opcode_) || dbg_line.empty()) &&
          "Op(No)Line attaching to Op(No)Line found");
   for (uint32_t i = 0; i < inst.num_operands; ++i) {
@@ -67,6 +76,23 @@
   }
 }
 
+Instruction::Instruction(IRContext* c, const spv_parsed_instruction_t& inst,
+                         const DebugScope& dbg_scope)
+    : context_(c),
+      opcode_(static_cast<SpvOp>(inst.opcode)),
+      has_type_id_(inst.type_id != 0),
+      has_result_id_(inst.result_id != 0),
+      unique_id_(c->TakeNextUniqueId()),
+      dbg_scope_(dbg_scope) {
+  for (uint32_t i = 0; i < inst.num_operands; ++i) {
+    const auto& current_payload = inst.operands[i];
+    std::vector<uint32_t> words(
+        inst.words + current_payload.offset,
+        inst.words + current_payload.offset + current_payload.num_words);
+    operands_.emplace_back(current_payload.type, std::move(words));
+  }
+}
+
 Instruction::Instruction(IRContext* c, SpvOp op, uint32_t ty_id,
                          uint32_t res_id, const OperandList& in_operands)
     : utils::IntrusiveNodeBase<Instruction>(),
@@ -75,7 +101,8 @@
       has_type_id_(ty_id != 0),
       has_result_id_(res_id != 0),
       unique_id_(c->TakeNextUniqueId()),
-      operands_() {
+      operands_(),
+      dbg_scope_(kNoDebugScope, kNoInlinedAt) {
   if (has_type_id_) {
     operands_.emplace_back(spv_operand_type_t::SPV_OPERAND_TYPE_TYPE_ID,
                            std::initializer_list<uint32_t>{ty_id});
@@ -94,7 +121,12 @@
       has_result_id_(that.has_result_id_),
       unique_id_(that.unique_id_),
       operands_(std::move(that.operands_)),
-      dbg_line_insts_(std::move(that.dbg_line_insts_)) {}
+      dbg_line_insts_(std::move(that.dbg_line_insts_)),
+      dbg_scope_(that.dbg_scope_) {
+  for (auto& i : dbg_line_insts_) {
+    i.dbg_scope_ = that.dbg_scope_;
+  }
+}
 
 Instruction& Instruction::operator=(Instruction&& that) {
   opcode_ = that.opcode_;
@@ -103,6 +135,7 @@
   unique_id_ = that.unique_id_;
   operands_ = std::move(that.operands_);
   dbg_line_insts_ = std::move(that.dbg_line_insts_);
+  dbg_scope_ = that.dbg_scope_;
   return *this;
 }
 
@@ -114,6 +147,7 @@
   clone->unique_id_ = c->TakeNextUniqueId();
   clone->operands_ = operands_;
   clone->dbg_line_insts_ = dbg_line_insts_;
+  clone->dbg_scope_ = dbg_scope_;
   return clone;
 }
 
@@ -198,6 +232,14 @@
 
   Instruction* base_type =
       context()->get_def_use_mgr()->GetDef(GetSingleWordInOperand(1));
+
+  // Unpack the optional layer of arraying.
+  if (base_type->opcode() == SpvOpTypeArray ||
+      base_type->opcode() == SpvOpTypeRuntimeArray) {
+    base_type = context()->get_def_use_mgr()->GetDef(
+        base_type->GetSingleWordInOperand(0));
+  }
+
   if (base_type->opcode() != SpvOpTypeImage) {
     return false;
   }
@@ -224,6 +266,14 @@
 
   Instruction* base_type =
       context()->get_def_use_mgr()->GetDef(GetSingleWordInOperand(1));
+
+  // Unpack the optional layer of arraying.
+  if (base_type->opcode() == SpvOpTypeArray ||
+      base_type->opcode() == SpvOpTypeRuntimeArray) {
+    base_type = context()->get_def_use_mgr()->GetDef(
+        base_type->GetSingleWordInOperand(0));
+  }
+
   if (base_type->opcode() != SpvOpTypeImage) {
     return false;
   }
@@ -250,6 +300,14 @@
 
   Instruction* base_type =
       context()->get_def_use_mgr()->GetDef(GetSingleWordInOperand(1));
+
+  // Unpack the optional layer of arraying.
+  if (base_type->opcode() == SpvOpTypeArray ||
+      base_type->opcode() == SpvOpTypeRuntimeArray) {
+    base_type = context()->get_def_use_mgr()->GetDef(
+        base_type->GetSingleWordInOperand(0));
+  }
+
   if (base_type->opcode() != SpvOpTypeImage) {
     return false;
   }
@@ -273,6 +331,13 @@
   Instruction* base_type =
       context()->get_def_use_mgr()->GetDef(GetSingleWordInOperand(1));
 
+  // Unpack the optional layer of arraying.
+  if (base_type->opcode() == SpvOpTypeArray ||
+      base_type->opcode() == SpvOpTypeRuntimeArray) {
+    base_type = context()->get_def_use_mgr()->GetDef(
+        base_type->GetSingleWordInOperand(0));
+  }
+
   if (base_type->opcode() != SpvOpTypeStruct) {
     return false;
   }
@@ -306,6 +371,14 @@
 
   Instruction* base_type =
       context()->get_def_use_mgr()->GetDef(GetSingleWordInOperand(1));
+
+  // Unpack the optional layer of arraying.
+  if (base_type->opcode() == SpvOpTypeArray ||
+      base_type->opcode() == SpvOpTypeRuntimeArray) {
+    base_type = context()->get_def_use_mgr()->GetDef(
+        base_type->GetSingleWordInOperand(0));
+  }
+
   if (base_type->opcode() != SpvOpTypeStruct) {
     return false;
   }
@@ -735,5 +808,28 @@
   }
 }
 
+void DebugScope::ToBinary(uint32_t type_id, uint32_t result_id,
+                          uint32_t ext_set,
+                          std::vector<uint32_t>* binary) const {
+  uint32_t num_words = kDebugScopeNumWords;
+  OpenCLDebugInfo100Instructions dbg_opcode = OpenCLDebugInfo100DebugScope;
+  if (GetLexicalScope() == kNoDebugScope) {
+    num_words = kDebugNoScopeNumWords;
+    dbg_opcode = OpenCLDebugInfo100DebugNoScope;
+  } else if (GetInlinedAt() == kNoInlinedAt) {
+    num_words = kDebugScopeNumWordsWithoutInlinedAt;
+  }
+  std::vector<uint32_t> operands = {
+      (num_words << 16) | static_cast<uint16_t>(SpvOpExtInst),
+      type_id,
+      result_id,
+      ext_set,
+      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());
+}
+
 }  // namespace opt
 }  // namespace spvtools
diff --git a/source/opt/instruction.h b/source/opt/instruction.h
index 322e0aa..a3342c6 100644
--- a/source/opt/instruction.h
+++ b/source/opt/instruction.h
@@ -32,6 +32,9 @@
 #include "source/opt/reflect.h"
 #include "spirv-tools/libspirv.h"
 
+const uint32_t kNoDebugScope = 0;
+const uint32_t kNoInlinedAt = 0;
+
 namespace spvtools {
 namespace opt {
 
@@ -80,6 +83,15 @@
   spv_operand_type_t type;  // Type of this logical operand.
   OperandData words;        // Binary segments of this logical operand.
 
+  // Returns a string operand as a C-style string.
+  const char* AsCString() const {
+    assert(type == SPV_OPERAND_TYPE_LITERAL_STRING);
+    return reinterpret_cast<const char*>(words.data());
+  }
+
+  // Returns a string operand as a std::string.
+  std::string AsString() const { return AsCString(); }
+
   friend bool operator==(const Operand& o1, const Operand& o2) {
     return o1.type == o2.type && o1.words == o2.words;
   }
@@ -91,6 +103,44 @@
   return !(o1 == o2);
 }
 
+// This structure is used to represent a DebugScope instruction from
+// the OpenCL.100.DebugInfo extened instruction set. Note that we can
+// ignore the result id of DebugScope instruction because it is not
+// used for anything. We do not keep it to reduce the size of
+// structure.
+// TODO: Let validator check that the result id is not used anywhere.
+class DebugScope {
+ public:
+  DebugScope(uint32_t lexical_scope, uint32_t inlined_at)
+      : lexical_scope_(lexical_scope), inlined_at_(inlined_at) {}
+
+  inline bool operator!=(const DebugScope& d) const {
+    return lexical_scope_ != d.lexical_scope_ || inlined_at_ != d.inlined_at_;
+  }
+
+  // Accessor functions for |lexical_scope_|.
+  uint32_t GetLexicalScope() const { return lexical_scope_; }
+  void SetLexicalScope(uint32_t scope) { lexical_scope_ = scope; }
+
+  // Accessor functions for |inlined_at_|.
+  uint32_t GetInlinedAt() const { return inlined_at_; }
+  void SetInlinedAt(uint32_t at) { inlined_at_ = at; }
+
+  // Pushes the binary segments for this DebugScope instruction into
+  // the back of *|binary|.
+  void ToBinary(uint32_t type_id, uint32_t result_id, uint32_t ext_set,
+                std::vector<uint32_t>* binary) const;
+
+ private:
+  // The result id of the lexical scope in which this debug scope is
+  // contained. The value is kNoDebugScope if there is no scope.
+  uint32_t lexical_scope_;
+
+  // The result id of DebugInlinedAt if instruction in this debug scope
+  // is inlined. The value is kNoInlinedAt if it is not inlined.
+  uint32_t inlined_at_;
+};
+
 // A SPIR-V instruction. It contains the opcode and any additional logical
 // operand, including the result id (if any) and result type id (if any). It
 // may also contain line-related debug instruction (OpLine, OpNoLine) directly
@@ -111,7 +161,8 @@
         opcode_(SpvOpNop),
         has_type_id_(false),
         has_result_id_(false),
-        unique_id_(0) {}
+        unique_id_(0),
+        dbg_scope_(kNoDebugScope, kNoInlinedAt) {}
 
   // Creates a default OpNop instruction.
   Instruction(IRContext*);
@@ -125,6 +176,9 @@
   Instruction(IRContext* c, const spv_parsed_instruction_t& inst,
               std::vector<Instruction>&& dbg_line = {});
 
+  Instruction(IRContext* c, const spv_parsed_instruction_t& inst,
+              const DebugScope& dbg_scope);
+
   // Creates an instruction with the given opcode |op|, type id: |ty_id|,
   // result id: |res_id| and input operands: |in_operands|.
   Instruction(IRContext* c, SpvOp op, uint32_t ty_id, uint32_t res_id,
@@ -221,6 +275,9 @@
   // Sets the result id
   inline void SetResultId(uint32_t res_id);
   inline bool HasResultId() const { return has_result_id_; }
+  // Sets DebugScope.
+  inline void SetDebugScope(const DebugScope& scope);
+  inline const DebugScope& GetDebugScope() const { return dbg_scope_; }
   // Remove the |index|-th operand
   void RemoveOperand(uint32_t index) {
     operands_.erase(operands_.begin() + index);
@@ -473,6 +530,9 @@
   // empty.
   std::vector<Instruction> dbg_line_insts_;
 
+  // DebugScope that wraps this instruction.
+  DebugScope dbg_scope_;
+
   friend InstructionList;
 };
 
@@ -544,6 +604,13 @@
   operands_[ridx].words = {res_id};
 }
 
+inline void Instruction::SetDebugScope(const DebugScope& scope) {
+  dbg_scope_ = scope;
+  for (auto& i : dbg_line_insts_) {
+    i.dbg_scope_ = scope;
+  }
+}
+
 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,
diff --git a/source/opt/instrument_pass.cpp b/source/opt/instrument_pass.cpp
index dfcd164..c8c6c21 100644
--- a/source/opt/instrument_pass.cpp
+++ b/source/opt/instrument_pass.cpp
@@ -380,6 +380,8 @@
       return kDebugOutputBindingStream;
     case kInstValidationIdBuffAddr:
       return kDebugOutputBindingStream;
+    case kInstValidationIdDebugPrintf:
+      return kDebugOutputPrintfStream;
     default:
       assert(false && "unexpected validation id");
   }
@@ -529,6 +531,16 @@
   return input_buffer_id_;
 }
 
+uint32_t InstrumentPass::GetFloatId() {
+  if (float_id_ == 0) {
+    analysis::TypeManager* type_mgr = context()->get_type_mgr();
+    analysis::Float float_ty(32);
+    analysis::Type* reg_float_ty = type_mgr->GetRegisteredType(&float_ty);
+    float_id_ = type_mgr->GetTypeInstruction(reg_float_ty);
+  }
+  return float_id_;
+}
+
 uint32_t InstrumentPass::GetVec4FloatId() {
   if (v4float_id_ == 0) {
     analysis::TypeManager* type_mgr = context()->get_type_mgr();
@@ -561,6 +573,16 @@
   return uint64_id_;
 }
 
+uint32_t InstrumentPass::GetUint8Id() {
+  if (uint8_id_ == 0) {
+    analysis::TypeManager* type_mgr = context()->get_type_mgr();
+    analysis::Integer uint8_ty(8, false);
+    analysis::Type* reg_uint8_ty = type_mgr->GetRegisteredType(&uint8_ty);
+    uint8_id_ = type_mgr->GetTypeInstruction(reg_uint8_ty);
+  }
+  return uint8_id_;
+}
+
 uint32_t InstrumentPass::GetVecUintId(uint32_t len) {
   analysis::TypeManager* type_mgr = context()->get_type_mgr();
   analysis::Integer uint_ty(32, false);
@@ -606,21 +628,22 @@
   // Total param count is common params plus validation-specific
   // params
   uint32_t param_cnt = kInstCommonParamCnt + val_spec_param_cnt;
-  if (output_func_id_ == 0) {
+  if (param2output_func_id_[param_cnt] == 0) {
     // Create function
-    output_func_id_ = TakeNextId();
+    param2output_func_id_[param_cnt] = TakeNextId();
     analysis::TypeManager* type_mgr = context()->get_type_mgr();
     std::vector<const analysis::Type*> param_types;
     for (uint32_t c = 0; c < param_cnt; ++c)
       param_types.push_back(type_mgr->GetType(GetUintId()));
     analysis::Function func_ty(type_mgr->GetType(GetVoidId()), param_types);
     analysis::Type* reg_func_ty = type_mgr->GetRegisteredType(&func_ty);
-    std::unique_ptr<Instruction> func_inst(new Instruction(
-        get_module()->context(), SpvOpFunction, GetVoidId(), output_func_id_,
-        {{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
-          {SpvFunctionControlMaskNone}},
-         {spv_operand_type_t::SPV_OPERAND_TYPE_ID,
-          {type_mgr->GetTypeInstruction(reg_func_ty)}}}));
+    std::unique_ptr<Instruction> func_inst(
+        new Instruction(get_module()->context(), SpvOpFunction, GetVoidId(),
+                        param2output_func_id_[param_cnt],
+                        {{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
+                          {SpvFunctionControlMaskNone}},
+                         {spv_operand_type_t::SPV_OPERAND_TYPE_ID,
+                          {type_mgr->GetTypeInstruction(reg_func_ty)}}}));
     get_def_use_mgr()->AnalyzeInstDefUse(&*func_inst);
     std::unique_ptr<Function> output_func =
         MakeUnique<Function>(std::move(func_inst));
@@ -709,10 +732,8 @@
     get_def_use_mgr()->AnalyzeInstDefUse(&*func_end_inst);
     output_func->SetFunctionEnd(std::move(func_end_inst));
     context()->AddFunction(std::move(output_func));
-    output_func_param_cnt_ = param_cnt;
   }
-  assert(param_cnt == output_func_param_cnt_ && "bad arg count");
-  return output_func_id_;
+  return param2output_func_id_[param_cnt];
 }
 
 uint32_t InstrumentPass::GetDirectReadFunctionId(uint32_t param_cnt) {
@@ -848,7 +869,7 @@
   std::unordered_set<uint32_t> done;
   // Don't process input and output functions
   for (auto& ifn : param2input_func_id_) done.insert(ifn.second);
-  if (output_func_id_ != 0) done.insert(output_func_id_);
+  for (auto& ofn : param2output_func_id_) done.insert(ofn.second);
   // Process all functions from roots
   while (!roots->empty()) {
     const uint32_t fi = roots->front();
@@ -926,12 +947,12 @@
   output_buffer_id_ = 0;
   output_buffer_ptr_id_ = 0;
   input_buffer_ptr_id_ = 0;
-  output_func_id_ = 0;
-  output_func_param_cnt_ = 0;
   input_buffer_id_ = 0;
+  float_id_ = 0;
   v4float_id_ = 0;
   uint_id_ = 0;
   uint64_id_ = 0;
+  uint8_id_ = 0;
   v4uint_id_ = 0;
   v3uint_id_ = 0;
   bool_id_ = 0;
@@ -944,6 +965,10 @@
   id2function_.clear();
   id2block_.clear();
 
+  // clear maps
+  param2input_func_id_.clear();
+  param2output_func_id_.clear();
+
   // Initialize function and block maps.
   for (auto& fn : *get_module()) {
     id2function_[fn.result_id()] = &fn;
@@ -988,6 +1013,10 @@
     (void)i;
     ++module_offset;
   }
+  for (auto& i : module->ext_inst_debuginfo()) {
+    (void)i;
+    ++module_offset;
+  }
   for (auto& i : module->annotations()) {
     (void)i;
     ++module_offset;
diff --git a/source/opt/instrument_pass.h b/source/opt/instrument_pass.h
index 02568fb..11afdce 100644
--- a/source/opt/instrument_pass.h
+++ b/source/opt/instrument_pass.h
@@ -61,6 +61,7 @@
 // its output buffers.
 static const uint32_t kInstValidationIdBindless = 0;
 static const uint32_t kInstValidationIdBuffAddr = 1;
+static const uint32_t kInstValidationIdDebugPrintf = 2;
 
 class InstrumentPass : public Pass {
   using cbb_ptr = const BasicBlock*;
@@ -227,9 +228,12 @@
   // Return id for 32-bit unsigned type
   uint32_t GetUintId();
 
-  // Return id for 32-bit unsigned type
+  // Return id for 64-bit unsigned type
   uint32_t GetUint64Id();
 
+  // Return id for 8-bit unsigned type
+  uint32_t GetUint8Id();
+
   // Return id for 32-bit unsigned type
   uint32_t GetBoolId();
 
@@ -267,6 +271,9 @@
   // Return id for debug input buffer
   uint32_t GetInputBufferId();
 
+  // Return id for 32-bit float type
+  uint32_t GetFloatId();
+
   // Return id for v4float type
   uint32_t GetVec4FloatId();
 
@@ -383,17 +390,17 @@
   uint32_t input_buffer_ptr_id_;
 
   // id for debug output function
-  uint32_t output_func_id_;
+  std::unordered_map<uint32_t, uint32_t> param2output_func_id_;
 
   // ids for debug input functions
   std::unordered_map<uint32_t, uint32_t> param2input_func_id_;
 
-  // param count for output function
-  uint32_t output_func_param_cnt_;
-
   // id for input buffer variable
   uint32_t input_buffer_id_;
 
+  // id for 32-bit float type
+  uint32_t float_id_;
+
   // id for v4float type
   uint32_t v4float_id_;
 
@@ -406,9 +413,12 @@
   // id for 32-bit unsigned type
   uint32_t uint_id_;
 
-  // id for 32-bit unsigned type
+  // id for 64-bit unsigned type
   uint32_t uint64_id_;
 
+  // id for 8-bit unsigned type
+  uint32_t uint8_id_;
+
   // id for bool type
   uint32_t bool_id_;
 
diff --git a/source/opt/ir_context.cpp b/source/opt/ir_context.cpp
index 7bca29b..72993fd 100644
--- a/source/opt/ir_context.cpp
+++ b/source/opt/ir_context.cpp
@@ -385,6 +385,8 @@
                                SpvOpTypeSampler,
                                SpvOpTypeSampledImage,
                                SpvOpTypeAccelerationStructureNV,
+                               SpvOpTypeAccelerationStructureKHR,
+                               SpvOpTypeRayQueryProvisionalKHR,
                                SpvOpTypeArray,
                                SpvOpTypeRuntimeArray,
                                SpvOpTypeStruct,
diff --git a/source/opt/ir_context.h b/source/opt/ir_context.h
index 45bf129..723a2bb 100644
--- a/source/opt/ir_context.h
+++ b/source/opt/ir_context.h
@@ -187,6 +187,14 @@
   inline IteratorRange<Module::inst_iterator> debugs3();
   inline IteratorRange<Module::const_inst_iterator> debugs3() const;
 
+  // Iterators for debug info instructions (excluding OpLine & OpNoLine)
+  // contained in this module.  These are OpExtInst for OpenCL.DebugInfo.100
+  // or DebugInfo extension placed between section 9 and 10.
+  inline Module::inst_iterator ext_inst_debuginfo_begin();
+  inline Module::inst_iterator ext_inst_debuginfo_end();
+  inline IteratorRange<Module::inst_iterator> ext_inst_debuginfo();
+  inline IteratorRange<Module::const_inst_iterator> ext_inst_debuginfo() const;
+
   // Add |capability| to the module, if it is not already enabled.
   inline void AddCapability(SpvCapability capability);
 
@@ -215,6 +223,8 @@
   // Appends a debug 3 instruction (OpModuleProcessed) to this module.
   // This is due to decision by the SPIR Working Group, pending publication.
   inline void AddDebug3Inst(std::unique_ptr<Instruction>&& d);
+  // Appends a OpExtInst for DebugInfo to this module.
+  inline void AddExtInstDebugInfo(std::unique_ptr<Instruction>&& d);
   // Appends an annotation instruction to this module.
   inline void AddAnnotationInst(std::unique_ptr<Instruction>&& a);
   // Appends a type-declaration instruction to this module.
@@ -925,6 +935,23 @@
   return ((const Module*)module_.get())->debugs3();
 }
 
+Module::inst_iterator IRContext::ext_inst_debuginfo_begin() {
+  return module()->ext_inst_debuginfo_begin();
+}
+
+Module::inst_iterator IRContext::ext_inst_debuginfo_end() {
+  return module()->ext_inst_debuginfo_end();
+}
+
+IteratorRange<Module::inst_iterator> IRContext::ext_inst_debuginfo() {
+  return module()->ext_inst_debuginfo();
+}
+
+IteratorRange<Module::const_inst_iterator> IRContext::ext_inst_debuginfo()
+    const {
+  return ((const Module*)module_.get())->ext_inst_debuginfo();
+}
+
 void IRContext::AddCapability(SpvCapability capability) {
   if (!get_feature_mgr()->HasCapability(capability)) {
     std::unique_ptr<Instruction> capability_inst(new Instruction(
@@ -1018,6 +1045,10 @@
   module()->AddDebug3Inst(std::move(d));
 }
 
+void IRContext::AddExtInstDebugInfo(std::unique_ptr<Instruction>&& d) {
+  module()->AddExtInstDebugInfo(std::move(d));
+}
+
 void IRContext::AddAnnotationInst(std::unique_ptr<Instruction>&& a) {
   if (AreAnalysesValid(kAnalysisDecorations)) {
     get_decoration_mgr()->AddDecoration(a.get());
diff --git a/source/opt/ir_loader.cpp b/source/opt/ir_loader.cpp
index e21e680..fcde079 100644
--- a/source/opt/ir_loader.cpp
+++ b/source/opt/ir_loader.cpp
@@ -16,11 +16,17 @@
 
 #include <utility>
 
+#include "DebugInfo.h"
+#include "OpenCLDebugInfo100.h"
 #include "source/ext_inst.h"
 #include "source/opt/log.h"
 #include "source/opt/reflect.h"
 #include "source/util/make_unique.h"
 
+static const uint32_t kExtInstSetIndex = 4;
+static const uint32_t kLexicalScopeIndex = 5;
+static const uint32_t kInlinedAtIndex = 6;
+
 namespace spvtools {
 namespace opt {
 
@@ -28,16 +34,60 @@
     : consumer_(consumer),
       module_(m),
       source_("<instruction>"),
-      inst_index_(0) {}
+      inst_index_(0),
+      last_dbg_scope_(kNoDebugScope, kNoInlinedAt) {}
 
 bool IrLoader::AddInstruction(const spv_parsed_instruction_t* inst) {
   ++inst_index_;
   const auto opcode = static_cast<SpvOp>(inst->opcode);
   if (IsDebugLineInst(opcode)) {
-    dbg_line_info_.push_back(Instruction(module()->context(), *inst));
+    dbg_line_info_.push_back(
+        Instruction(module()->context(), *inst, last_dbg_scope_));
     return true;
   }
 
+  // If it is a DebugScope or DebugNoScope of debug extension, we do not
+  // create a new instruction, but simply keep the information in
+  // struct DebugScope.
+  if (opcode == SpvOpExtInst && spvExtInstIsDebugInfo(inst->ext_inst_type)) {
+    const uint32_t ext_inst_index = inst->words[kExtInstSetIndex];
+    if (inst->ext_inst_type == SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100) {
+      const OpenCLDebugInfo100Instructions ext_inst_key =
+          OpenCLDebugInfo100Instructions(ext_inst_index);
+      if (ext_inst_key == OpenCLDebugInfo100DebugScope) {
+        uint32_t inlined_at = 0;
+        if (inst->num_words > kInlinedAtIndex)
+          inlined_at = inst->words[kInlinedAtIndex];
+        last_dbg_scope_ =
+            DebugScope(inst->words[kLexicalScopeIndex], inlined_at);
+        module()->SetContainsDebugScope();
+        return true;
+      }
+      if (ext_inst_key == OpenCLDebugInfo100DebugNoScope) {
+        last_dbg_scope_ = DebugScope(kNoDebugScope, kNoInlinedAt);
+        module()->SetContainsDebugScope();
+        return true;
+      }
+    } else {
+      const DebugInfoInstructions ext_inst_key =
+          DebugInfoInstructions(ext_inst_index);
+      if (ext_inst_key == DebugInfoDebugScope) {
+        uint32_t inlined_at = 0;
+        if (inst->num_words > kInlinedAtIndex)
+          inlined_at = inst->words[kInlinedAtIndex];
+        last_dbg_scope_ =
+            DebugScope(inst->words[kLexicalScopeIndex], inlined_at);
+        module()->SetContainsDebugScope();
+        return true;
+      }
+      if (ext_inst_key == DebugInfoDebugNoScope) {
+        last_dbg_scope_ = DebugScope(kNoDebugScope, kNoInlinedAt);
+        module()->SetContainsDebugScope();
+        return true;
+      }
+    }
+  }
+
   std::unique_ptr<Instruction> spv_inst(
       new Instruction(module()->context(), *inst, std::move(dbg_line_info_)));
   dbg_line_info_.clear();
@@ -88,6 +138,7 @@
     block_->AddInstruction(std::move(spv_inst));
     function_->AddBasicBlock(std::move(block_));
     block_ = nullptr;
+    last_dbg_scope_ = DebugScope(kNoDebugScope, kNoInlinedAt);
   } else {
     if (function_ == nullptr) {  // Outside function definition
       SPIRV_ASSERT(consumer_, block_ == nullptr);
@@ -118,6 +169,9 @@
                  (opcode == SpvOpExtInst &&
                   spvExtInstIsNonSemantic(inst->ext_inst_type))) {
         module_->AddGlobalValue(std::move(spv_inst));
+      } else if (opcode == SpvOpExtInst &&
+                 spvExtInstIsDebugInfo(inst->ext_inst_type)) {
+        module_->AddExtInstDebugInfo(std::move(spv_inst));
       } else {
         Errorf(consumer_, src, loc,
                "Unhandled inst type (opcode: %d) found outside function "
@@ -126,17 +180,81 @@
         return false;
       }
     } else {
-      if (block_ == nullptr) {  // Inside function but outside blocks
-        if (opcode != SpvOpFunctionParameter) {
-          Errorf(consumer_, src, loc,
-                 "Non-OpFunctionParameter (opcode: %d) found inside "
-                 "function but outside basic block",
-                 opcode);
-          return false;
+      if (opcode == SpvOpLoopMerge || opcode == SpvOpSelectionMerge)
+        last_dbg_scope_ = DebugScope(kNoDebugScope, kNoInlinedAt);
+      if (last_dbg_scope_.GetLexicalScope() != kNoDebugScope)
+        spv_inst->SetDebugScope(last_dbg_scope_);
+      if (opcode == SpvOpExtInst &&
+          spvExtInstIsDebugInfo(inst->ext_inst_type)) {
+        const uint32_t ext_inst_index = inst->words[kExtInstSetIndex];
+        if (inst->ext_inst_type == SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100) {
+          const OpenCLDebugInfo100Instructions ext_inst_key =
+              OpenCLDebugInfo100Instructions(ext_inst_index);
+          switch (ext_inst_key) {
+            case OpenCLDebugInfo100DebugDeclare: {
+              if (block_ == nullptr)  // Inside function but outside blocks
+                function_->AddDebugInstructionInHeader(std::move(spv_inst));
+              else
+                block_->AddInstruction(std::move(spv_inst));
+              break;
+            }
+            case OpenCLDebugInfo100DebugValue: {
+              if (block_ == nullptr)  // Inside function but outside blocks
+                function_->AddDebugInstructionInHeader(std::move(spv_inst));
+              else
+                block_->AddInstruction(std::move(spv_inst));
+              break;
+            }
+            default: {
+              Errorf(consumer_, src, loc,
+                     "Debug info extension instruction other than DebugScope, "
+                     "DebugNoScope, DebugDeclare, and DebugValue found inside "
+                     "function",
+                     opcode);
+              return false;
+            }
+          }
+        } else {
+          const DebugInfoInstructions ext_inst_key =
+              DebugInfoInstructions(ext_inst_index);
+          switch (ext_inst_key) {
+            case DebugInfoDebugDeclare: {
+              if (block_ == nullptr)  // Inside function but outside blocks
+                function_->AddDebugInstructionInHeader(std::move(spv_inst));
+              else
+                block_->AddInstruction(std::move(spv_inst));
+              break;
+            }
+            case DebugInfoDebugValue: {
+              if (block_ == nullptr)  // Inside function but outside blocks
+                function_->AddDebugInstructionInHeader(std::move(spv_inst));
+              else
+                block_->AddInstruction(std::move(spv_inst));
+              break;
+            }
+            default: {
+              Errorf(consumer_, src, loc,
+                     "Debug info extension instruction other than DebugScope, "
+                     "DebugNoScope, DebugDeclare, and DebugValue found inside "
+                     "function",
+                     opcode);
+              return false;
+            }
+          }
         }
-        function_->AddParameter(std::move(spv_inst));
       } else {
-        block_->AddInstruction(std::move(spv_inst));
+        if (block_ == nullptr) {  // Inside function but outside blocks
+          if (opcode != SpvOpFunctionParameter) {
+            Errorf(consumer_, src, loc,
+                   "Non-OpFunctionParameter (opcode: %d) found inside "
+                   "function but outside basic block",
+                   opcode);
+            return false;
+          }
+          function_->AddParameter(std::move(spv_inst));
+        } else {
+          block_->AddInstruction(std::move(spv_inst));
+        }
       }
     }
   }
diff --git a/source/opt/ir_loader.h b/source/opt/ir_loader.h
index 940d7b0..5079921 100644
--- a/source/opt/ir_loader.h
+++ b/source/opt/ir_loader.h
@@ -78,6 +78,9 @@
   std::unique_ptr<BasicBlock> block_;
   // Line related debug instructions accumulated thus far.
   std::vector<Instruction> dbg_line_info_;
+
+  // The last DebugScope information that IrLoader::AddInstruction() handled.
+  DebugScope last_dbg_scope_;
 };
 
 }  // namespace opt
diff --git a/source/opt/local_access_chain_convert_pass.cpp b/source/opt/local_access_chain_convert_pass.cpp
index 1921596..0afe798 100644
--- a/source/opt/local_access_chain_convert_pass.cpp
+++ b/source/opt/local_access_chain_convert_pass.cpp
@@ -371,6 +371,7 @@
       "SPV_GOOGLE_hlsl_functionality1",
       "SPV_GOOGLE_user_type",
       "SPV_NV_shader_subgroup_partitioned",
+      "SPV_EXT_demote_to_helper_invocation",
       "SPV_EXT_descriptor_indexing",
       "SPV_NV_fragment_shader_barycentric",
       "SPV_NV_compute_shader_derivatives",
@@ -378,6 +379,8 @@
       "SPV_NV_shading_rate",
       "SPV_NV_mesh_shader",
       "SPV_NV_ray_tracing",
+      "SPV_KHR_ray_tracing",
+      "SPV_KHR_ray_query",
       "SPV_EXT_fragment_invocation_density",
   });
 }
diff --git a/source/opt/local_single_block_elim_pass.cpp b/source/opt/local_single_block_elim_pass.cpp
index aebbd00..b5435bb 100644
--- a/source/opt/local_single_block_elim_pass.cpp
+++ b/source/opt/local_single_block_elim_pass.cpp
@@ -248,6 +248,7 @@
       "SPV_GOOGLE_hlsl_functionality1",
       "SPV_GOOGLE_user_type",
       "SPV_NV_shader_subgroup_partitioned",
+      "SPV_EXT_demote_to_helper_invocation",
       "SPV_EXT_descriptor_indexing",
       "SPV_NV_fragment_shader_barycentric",
       "SPV_NV_compute_shader_derivatives",
@@ -255,6 +256,8 @@
       "SPV_NV_shading_rate",
       "SPV_NV_mesh_shader",
       "SPV_NV_ray_tracing",
+      "SPV_KHR_ray_tracing",
+      "SPV_KHR_ray_query",
       "SPV_EXT_fragment_invocation_density",
       "SPV_EXT_physical_storage_buffer",
   });
diff --git a/source/opt/local_single_store_elim_pass.cpp b/source/opt/local_single_store_elim_pass.cpp
index d6beeab..4c71ce1 100644
--- a/source/opt/local_single_store_elim_pass.cpp
+++ b/source/opt/local_single_store_elim_pass.cpp
@@ -118,6 +118,7 @@
       "SPV_NV_shading_rate",
       "SPV_NV_mesh_shader",
       "SPV_NV_ray_tracing",
+      "SPV_KHR_ray_query",
       "SPV_EXT_fragment_invocation_density",
       "SPV_EXT_physical_storage_buffer",
   });
diff --git a/source/opt/merge_return_pass.cpp b/source/opt/merge_return_pass.cpp
index 18c49f5..bbac4bb 100644
--- a/source/opt/merge_return_pass.cpp
+++ b/source/opt/merge_return_pass.cpp
@@ -69,6 +69,32 @@
   return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
 }
 
+void MergeReturnPass::GenerateState(BasicBlock* block) {
+  if (Instruction* mergeInst = block->GetMergeInst()) {
+    if (mergeInst->opcode() == SpvOpLoopMerge) {
+      // If new loop, break to this loop merge block
+      state_.emplace_back(mergeInst, mergeInst);
+    } else {
+      auto branchInst = mergeInst->NextNode();
+      if (branchInst->opcode() == SpvOpSwitch) {
+        // If switch inside of loop, break to innermost loop merge block.
+        // Otherwise need to break to this switch merge block.
+        auto lastMergeInst = state_.back().BreakMergeInst();
+        if (lastMergeInst && lastMergeInst->opcode() == SpvOpLoopMerge)
+          state_.emplace_back(lastMergeInst, mergeInst);
+        else
+          state_.emplace_back(mergeInst, mergeInst);
+      } else {
+        // If branch conditional inside loop, always break to innermost
+        // loop merge block. If branch conditional inside switch, break to
+        // innermost switch merge block.
+        auto lastMergeInst = state_.back().BreakMergeInst();
+        state_.emplace_back(lastMergeInst, mergeInst);
+      }
+    }
+  }
+}
+
 bool MergeReturnPass::ProcessStructured(
     Function* function, const std::vector<BasicBlock*>& return_blocks) {
   if (HasNontrivialUnreachableBlocks(function)) {
@@ -82,7 +108,7 @@
   }
 
   RecordImmediateDominators(function);
-  AddDummyLoopAroundFunction();
+  AddDummySwitchAroundFunction();
 
   std::list<BasicBlock*> order;
   cfg()->ComputeStructuredOrder(function, &*function->begin(), &order);
@@ -103,12 +129,8 @@
 
     ProcessStructuredBlock(block);
 
-    // Generate state for next block
-    if (Instruction* mergeInst = block->GetMergeInst()) {
-      Instruction* loopMergeInst = block->GetLoopMergeInst();
-      if (!loopMergeInst) loopMergeInst = state_.back().LoopMergeInst();
-      state_.emplace_back(loopMergeInst, mergeInst);
-    }
+    // Generate state for next block if warranted
+    GenerateState(block);
   }
 
   state_.clear();
@@ -133,12 +155,8 @@
       }
     }
 
-    // Generate state for next block
-    if (Instruction* mergeInst = block->GetMergeInst()) {
-      Instruction* loopMergeInst = block->GetLoopMergeInst();
-      if (!loopMergeInst) loopMergeInst = state_.back().LoopMergeInst();
-      state_.emplace_back(loopMergeInst, mergeInst);
-    }
+    // Generate state for next block if warranted
+    GenerateState(block);
   }
 
   // We have not kept the dominator tree up-to-date.
@@ -202,8 +220,8 @@
 
   if (tail_opcode == SpvOpReturn || tail_opcode == SpvOpReturnValue ||
       tail_opcode == SpvOpUnreachable) {
-    assert(CurrentState().InLoop() && "Should be in the dummy loop.");
-    BranchToBlock(block, CurrentState().LoopMergeId());
+    assert(CurrentState().InBreakable() && "Should be in the dummy construct.");
+    BranchToBlock(block, CurrentState().BreakMergeId());
     return_blocks_.insert(block->id());
   }
 }
@@ -337,8 +355,8 @@
   std::unordered_set<BasicBlock*> seen;
   if (block->id() == state->CurrentMergeId()) {
     state++;
-  } else if (block->id() == state->LoopMergeId()) {
-    while (state->LoopMergeId() == block->id()) {
+  } else if (block->id() == state->BreakMergeId()) {
+    while (state->BreakMergeId() == block->id()) {
       state++;
     }
   }
@@ -346,15 +364,14 @@
   while (block != nullptr && block != final_return_block_) {
     if (!predicated->insert(block).second) break;
     // Skip structured subgraphs.
-    assert(state->InLoop() && "Should be in the dummy loop at the very least.");
-    Instruction* current_loop_merge_inst = state->LoopMergeInst();
-    uint32_t merge_block_id =
-        current_loop_merge_inst->GetSingleWordInOperand(0);
-    while (state->LoopMergeId() == merge_block_id) {
+    assert(state->InBreakable() &&
+           "Should be in the dummy 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) {
       state++;
     }
-    if (!BreakFromConstruct(block, predicated, order,
-                            current_loop_merge_inst)) {
+    if (!BreakFromConstruct(block, predicated, order, break_merge_inst)) {
       return false;
     }
     block = context()->get_instr_block(merge_block_id);
@@ -364,9 +381,7 @@
 
 bool MergeReturnPass::BreakFromConstruct(
     BasicBlock* block, std::unordered_set<BasicBlock*>* predicated,
-    std::list<BasicBlock*>* order, Instruction* loop_merge_inst) {
-  assert(loop_merge_inst->opcode() == SpvOpLoopMerge &&
-         "loop_merge_inst must be a loop merge instruction.");
+    std::list<BasicBlock*>* order, Instruction* break_merge_inst) {
   // Make sure the CFG is build here.  If we don't then it becomes very hard
   // to know which new blocks need to be updated.
   context()->BuildInvalidAnalyses(IRContext::kAnalysisCFG);
@@ -388,7 +403,7 @@
     }
   }
 
-  uint32_t merge_block_id = loop_merge_inst->GetSingleWordInOperand(0);
+  uint32_t merge_block_id = break_merge_inst->GetSingleWordInOperand(0);
   BasicBlock* merge_block = context()->get_instr_block(merge_block_id);
   if (merge_block->GetLoopMergeInst()) {
     cfg()->SplitLoopHeader(merge_block);
@@ -416,9 +431,10 @@
 
   // If |block| was a continue target for a loop |old_body| is now the correct
   // continue target.
-  if (loop_merge_inst->GetSingleWordInOperand(1) == block->id()) {
-    loop_merge_inst->SetInOperand(1, {old_body->id()});
-    context()->UpdateDefUse(loop_merge_inst);
+  if (break_merge_inst->opcode() == SpvOpLoopMerge &&
+      break_merge_inst->GetSingleWordInOperand(1) == block->id()) {
+    break_merge_inst->SetInOperand(1, {old_body->id()});
+    context()->UpdateDefUse(break_merge_inst);
   }
 
   // Update |order| so old_block will be traversed.
@@ -430,8 +446,8 @@
   // 3. Update OpPhi instructions in |merge_block|.
   // 4. Update the CFG.
   //
-  // Sine we are branching to the merge block of the current construct, there is
-  // no need for an OpSelectionMerge.
+  // Since we are branching to the merge block of the current construct, there
+  // is no need for an OpSelectionMerge.
 
   InstructionBuilder builder(
       context(), block,
@@ -710,7 +726,7 @@
   list->insert(pos, new_element);
 }
 
-void MergeReturnPass::AddDummyLoopAroundFunction() {
+void MergeReturnPass::AddDummySwitchAroundFunction() {
   CreateReturnBlock();
   CreateReturn(final_return_block_);
 
@@ -718,7 +734,7 @@
     cfg()->RegisterBlock(final_return_block_);
   }
 
-  CreateDummyLoop(final_return_block_);
+  CreateDummySwitch(final_return_block_);
 }
 
 BasicBlock* MergeReturnPass::CreateContinueTarget(uint32_t header_label_id) {
@@ -753,14 +769,8 @@
   return new_block;
 }
 
-void MergeReturnPass::CreateDummyLoop(BasicBlock* merge_target) {
-  std::unique_ptr<Instruction> label(
-      new Instruction(context(), SpvOpLabel, 0u, TakeNextId(), {}));
-
-  // Create the new basic block
-  std::unique_ptr<BasicBlock> block(new BasicBlock(std::move(label)));
-
-  // Insert the new block before any code is run.  We have to split the entry
+void MergeReturnPass::CreateDummySwitch(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();
   auto split_pos = start_block->begin();
@@ -771,38 +781,16 @@
   BasicBlock* old_block =
       start_block->SplitBasicBlock(context(), TakeNextId(), split_pos);
 
-  // The new block must be inserted after the entry block.  We cannot make the
-  // entry block the header for the dummy loop because it is not valid to have a
-  // branch to the entry block, and the continue target must branch back to the
-  // loop header.
-  auto pos = function_->begin();
-  pos++;
-  BasicBlock* header_block = &*pos.InsertBefore(std::move(block));
-  context()->AnalyzeDefUse(header_block->GetLabelInst());
-  header_block->SetParent(function_);
-
-  // We have to create the continue block before OpLoopMerge instruction.
-  // Otherwise the def-use manager will compalain that there is a use without a
-  // definition.
-  uint32_t continue_target = CreateContinueTarget(header_block->id())->id();
-
-  // Add the code the the header block.
+  // Add the switch to the end of the entry block.
   InstructionBuilder builder(
-      context(), header_block,
-      IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
-
-  builder.AddLoopMerge(merge_target->id(), continue_target);
-  builder.AddBranch(old_block->id());
-
-  // Fix up the entry block by adding a branch to the loop header.
-  InstructionBuilder builder2(
       context(), start_block,
       IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
-  builder2.AddBranch(header_block->id());
+
+  builder.AddSwitch(builder.GetUintConstantId(0u), old_block->id(), {},
+                    merge_target->id());
 
   if (context()->AreAnalysesValid(IRContext::kAnalysisCFG)) {
     cfg()->RegisterBlock(old_block);
-    cfg()->RegisterBlock(header_block);
     cfg()->AddEdges(start_block);
   }
 }
diff --git a/source/opt/merge_return_pass.h b/source/opt/merge_return_pass.h
index f8edd27..fe85557 100644
--- a/source/opt/merge_return_pass.h
+++ b/source/opt/merge_return_pass.h
@@ -33,7 +33,8 @@
  * Structured control flow guarantees that the CFG will converge at a given
  * point (the merge block). Within structured control flow, all blocks must be
  * post-dominated by the merge block, except return blocks and break blocks.
- * A break block is a block that branches to the innermost loop's merge block.
+ * A break block is a block that branches to a containing construct's merge
+ * block.
  *
  * Beyond this, we further assume that all unreachable blocks have been
  * cleaned up.  This means that the only unreachable blocks are those necessary
@@ -46,13 +47,14 @@
  * with a branch. If current block is not within structured control flow, this
  * 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 loop's merge.  This loop will
- * always exist because a dummy loop 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 it is contained in.
- *Once structured control flow has been exited, it will be at the merge of the
- *dummy loop, with will simply return.
+ * the branch destination should be the innermost construct's merge.  This
+ * merge will always exist because a dummy 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.
  *
  * In the final return block, the return value should be loaded and returned.
  * Memory promotion passes should be able to promote the newly introduced
@@ -71,7 +73,7 @@
  *         ||
  *         \/
  *
- *          0 (dummy loop header)
+ *          0 (dummy switch header)
  *          |
  *          1 (loop header)
  *         / \
@@ -81,11 +83,11 @@
  *        / \
  *        |  3 (original code in 3)
  *        \ /
- *   (ret) 4 (dummy loop merge)
+ *   (ret) 4 (dummy switch merge)
  *
  * In the above (simple) example, the return originally in |2| is passed through
- * the merge. That merge is predicated such that the old body of the block is
- * the else branch. The branch condition is based on the value of the "has
+ * the loop merge. That merge is predicated such that the old body of the block
+ * is the else branch. The branch condition is based on the value of the "has
  * returned" variable.
  *
  ******************************************************************************/
@@ -108,17 +110,17 @@
   }
 
  private:
-  // This class is used to store the a loop merge instruction and a selection
-  // merge instruction.  The intended use is that is represent the inner most
-  // contain selection construct and the inner most loop construct.
+  // This class is used to store the a break merge instruction and a current
+  // merge instruction.  The intended use is to keep track of the block to
+  // break to and the current innermost control flow construct merge block.
   class StructuredControlState {
    public:
-    StructuredControlState(Instruction* loop, Instruction* merge)
-        : loop_merge_(loop), current_merge_(merge) {}
+    StructuredControlState(Instruction* break_merge, Instruction* merge)
+        : break_merge_(break_merge), current_merge_(merge) {}
 
     StructuredControlState(const StructuredControlState&) = default;
 
-    bool InLoop() const { return loop_merge_; }
+    bool InBreakable() const { return break_merge_; }
     bool InStructuredFlow() const { return CurrentMergeId() != 0; }
 
     uint32_t CurrentMergeId() const {
@@ -132,20 +134,14 @@
                             : 0;
     }
 
-    uint32_t LoopMergeId() const {
-      return loop_merge_ ? loop_merge_->GetSingleWordInOperand(0u) : 0u;
+    uint32_t BreakMergeId() const {
+      return break_merge_ ? break_merge_->GetSingleWordInOperand(0u) : 0u;
     }
 
-    uint32_t CurrentLoopHeader() const {
-      return loop_merge_
-                 ? loop_merge_->context()->get_instr_block(loop_merge_)->id()
-                 : 0;
-    }
-
-    Instruction* LoopMergeInst() const { return loop_merge_; }
+    Instruction* BreakMergeInst() const { return break_merge_; }
 
    private:
-    Instruction* loop_merge_;
+    Instruction* break_merge_;
     Instruction* current_merge_;
   };
 
@@ -159,6 +155,9 @@
   void MergeReturnBlocks(Function* function,
                          const std::vector<BasicBlock*>& returnBlocks);
 
+  // Generate and push new control flow state if |block| contains a merge.
+  void GenerateState(BasicBlock* block);
+
   // Merges the return instruction in |function| so that it has a single return
   // statement.  It is assumed that |function| has structured control flow, and
   // that |return_blocks| is a list of all of the basic blocks in |function|
@@ -219,9 +218,9 @@
                        std::list<BasicBlock*>* order);
 
   // Add a conditional branch at the start of |block| that either jumps to
-  // the merge block of |loop_merge_inst| or the original code in |block|
+  // the merge block of |break_merge_inst| or the original code in |block|
   // depending on the value in |return_flag_|.  The continue target in
-  // |loop_merge_inst| will be updated if needed.
+  // |break_merge_inst| will be updated if needed.
   //
   // If new blocks that are created will be added to |order|.  This way a call
   // can traverse these new block in structured order.
@@ -230,7 +229,7 @@
   bool BreakFromConstruct(BasicBlock* block,
                           std::unordered_set<BasicBlock*>* predicated,
                           std::list<BasicBlock*>* order,
-                          Instruction* loop_merge_inst);
+                          Instruction* break_merge_inst);
 
   // Add an |OpReturn| or |OpReturnValue| to the end of |block|.  If an
   // |OpReturnValue| is needed, the return value is loaded from |return_value_|.
@@ -274,27 +273,28 @@
   void InsertAfterElement(BasicBlock* element, BasicBlock* new_element,
                           std::list<BasicBlock*>* list);
 
-  // Creates a single iteration loop around all of the exectuable code of the
-  // current function and returns after the loop is done. Sets
+  // Creates a single case switch around all of the exectuable code of the
+  // current function where the switch and case value are both zero and the
+  // default is the merge block. Returns after the switch is executed. Sets
   // |final_return_block_|.
-  void AddDummyLoopAroundFunction();
+  void AddDummySwitchAroundFunction();
 
   // 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
   // function.
   BasicBlock* CreateContinueTarget(uint32_t header_label_id);
 
-  // Creates a loop around the executable code of the function with
+  // Creates a one case switch around the executable code of the function with
   // |merge_target| as the merge node.
-  void CreateDummyLoop(BasicBlock* merge_target);
+  void CreateDummySwitch(BasicBlock* merge_target);
 
   // Returns true if |function| has an unreachable block that is not a continue
   // target that simply branches back to the header, or a merge block containing
   // 1 instruction which is OpUnreachable.
   bool HasNontrivialUnreachableBlocks(Function* function);
 
-  // A stack used to keep track of the innermost contain loop and selection
-  // constructs.
+  // A stack used to keep track of the break and current control flow construct
+  // merge blocks.
   std::vector<StructuredControlState> state_;
 
   // The current function being transformed.
diff --git a/source/opt/module.cpp b/source/opt/module.cpp
index c7fc247..2959d3d 100644
--- a/source/opt/module.cpp
+++ b/source/opt/module.cpp
@@ -95,6 +95,7 @@
   DELEGATE(debugs1_);
   DELEGATE(debugs2_);
   DELEGATE(debugs3_);
+  DELEGATE(ext_inst_debuginfo_);
   DELEGATE(annotations_);
   DELEGATE(types_values_);
   for (auto& i : functions_) i->ForEachInst(f, run_on_debug_line_insts);
@@ -117,6 +118,7 @@
   for (auto& i : debugs3_) DELEGATE(i);
   for (auto& i : annotations_) DELEGATE(i);
   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);
@@ -135,10 +137,27 @@
   binary->push_back(header_.bound);
   binary->push_back(header_.reserved);
 
-  auto write_inst = [binary, skip_nop](const Instruction* i) {
-    if (!(skip_nop && i->IsNop())) i->ToBinaryWithoutAttachedDebugInsts(binary);
+  size_t bound_idx = binary->size() - 2;
+  DebugScope last_scope(kNoDebugScope, kNoInlinedAt);
+  auto write_inst = [binary, skip_nop, &last_scope,
+                     this](const Instruction* i) {
+    if (!(skip_nop && i->IsNop())) {
+      const auto& scope = i->GetDebugScope();
+      if (scope != last_scope) {
+        // Emit DebugScope |scope| to |binary|.
+        auto dbg_inst = ext_inst_debuginfo_.begin();
+        scope.ToBinary(dbg_inst->type_id(), context()->TakeNextId(),
+                       dbg_inst->GetSingleWordOperand(2), binary);
+        last_scope = scope;
+      }
+
+      i->ToBinaryWithoutAttachedDebugInsts(binary);
+    }
   };
   ForEachInst(write_inst, true);
+
+  // We create new instructions for DebugScope. The bound must be updated.
+  binary->data()[bound_idx] = header_.bound;
 }
 
 uint32_t Module::ComputeIdBound() const {
diff --git a/source/opt/module.h b/source/opt/module.h
index aefa2a5..2c96f02 100644
--- a/source/opt/module.h
+++ b/source/opt/module.h
@@ -17,6 +17,7 @@
 
 #include <functional>
 #include <memory>
+#include <unordered_map>
 #include <utility>
 #include <vector>
 
@@ -48,7 +49,7 @@
   using const_inst_iterator = InstructionList::const_iterator;
 
   // Creates an empty module with zero'd header.
-  Module() : header_({}) {}
+  Module() : header_({}), contains_debug_scope_(false) {}
 
   // Sets the header to the given |header|.
   void SetHeader(const ModuleHeader& header) { header_ = header; }
@@ -102,6 +103,10 @@
   // This is due to decision by the SPIR Working Group, pending publication.
   inline void AddDebug3Inst(std::unique_ptr<Instruction> d);
 
+  // Appends a debug info extension (OpenCL.DebugInfo.100 or DebugInfo)
+  // instruction to this module.
+  inline void AddExtInstDebugInfo(std::unique_ptr<Instruction> d);
+
   // Appends an annotation instruction to this module.
   inline void AddAnnotationInst(std::unique_ptr<Instruction> a);
 
@@ -114,6 +119,10 @@
   // Appends a function to this module.
   inline void AddFunction(std::unique_ptr<Function> f);
 
+  // Sets |contains_debug_scope_| as true.
+  inline void SetContainsDebugScope();
+  inline bool ContainsDebugScope() { return contains_debug_scope_; }
+
   // Returns a vector of pointers to type-declaration instructions in this
   // module.
   std::vector<Instruction*> GetTypes();
@@ -182,6 +191,14 @@
   inline IteratorRange<inst_iterator> debugs3();
   inline IteratorRange<const_inst_iterator> debugs3() const;
 
+  // Iterators for debug info instructions (excluding OpLine & OpNoLine)
+  // contained in this module.  These are OpExtInst for OpenCL.DebugInfo.100
+  // or DebugInfo extension placed between section 9 and 10.
+  inline inst_iterator ext_inst_debuginfo_begin();
+  inline inst_iterator ext_inst_debuginfo_end();
+  inline IteratorRange<inst_iterator> ext_inst_debuginfo();
+  inline IteratorRange<const_inst_iterator> ext_inst_debuginfo() const;
+
   // Iterators for entry point instructions contained in this module
   inline IteratorRange<inst_iterator> entry_points();
   inline IteratorRange<const_inst_iterator> entry_points() const;
@@ -274,6 +291,7 @@
   InstructionList debugs1_;
   InstructionList debugs2_;
   InstructionList debugs3_;
+  InstructionList ext_inst_debuginfo_;
   InstructionList annotations_;
   // Type declarations, constants, and global variable declarations.
   InstructionList types_values_;
@@ -282,6 +300,9 @@
   // If the module ends with Op*Line instruction, they will not be attached to
   // any instruction.  We record them here, so they will not be lost.
   std::vector<Instruction> trailing_dbg_line_info_;
+
+  // This module contains DebugScope or DebugNoScope.
+  bool contains_debug_scope_;
 };
 
 // Pretty-prints |module| to |str|. Returns |str|.
@@ -323,6 +344,10 @@
   debugs3_.push_back(std::move(d));
 }
 
+inline void Module::AddExtInstDebugInfo(std::unique_ptr<Instruction> d) {
+  ext_inst_debuginfo_.push_back(std::move(d));
+}
+
 inline void Module::AddAnnotationInst(std::unique_ptr<Instruction> a) {
   annotations_.push_back(std::move(a));
 }
@@ -339,6 +364,8 @@
   functions_.emplace_back(std::move(f));
 }
 
+inline void Module::SetContainsDebugScope() { contains_debug_scope_ = true; }
+
 inline Module::inst_iterator Module::capability_begin() {
   return capabilities_.begin();
 }
@@ -403,6 +430,22 @@
   return make_range(debugs3_.begin(), debugs3_.end());
 }
 
+inline Module::inst_iterator Module::ext_inst_debuginfo_begin() {
+  return ext_inst_debuginfo_.begin();
+}
+inline Module::inst_iterator Module::ext_inst_debuginfo_end() {
+  return ext_inst_debuginfo_.end();
+}
+
+inline IteratorRange<Module::inst_iterator> Module::ext_inst_debuginfo() {
+  return make_range(ext_inst_debuginfo_.begin(), ext_inst_debuginfo_.end());
+}
+
+inline IteratorRange<Module::const_inst_iterator> Module::ext_inst_debuginfo()
+    const {
+  return make_range(ext_inst_debuginfo_.begin(), ext_inst_debuginfo_.end());
+}
+
 inline IteratorRange<Module::inst_iterator> Module::entry_points() {
   return make_range(entry_points_.begin(), entry_points_.end());
 }
diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp
index 241aa75..0a937e8 100644
--- a/source/opt/optimizer.cpp
+++ b/source/opt/optimizer.cpp
@@ -425,6 +425,8 @@
     RegisterPass(CreateConvertRelaxedToHalfPass());
   } else if (pass_name == "relax-float-ops") {
     RegisterPass(CreateRelaxFloatOpsPass());
+  } else if (pass_name == "inst-debug-printf") {
+    RegisterPass(CreateInstDebugPrintfPass(7, 23));
   } else if (pass_name == "simplify-instructions") {
     RegisterPass(CreateSimplificationPass());
   } else if (pass_name == "ssa-rewrite") {
@@ -567,7 +569,12 @@
   }
 
 #ifndef NDEBUG
-  if (status == opt::Pass::Status::SuccessWithoutChange) {
+  // 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
+  // contains DebugScope instructions.
+  if (status == opt::Pass::Status::SuccessWithoutChange &&
+      !context->module()->ContainsDebugScope()) {
     std::vector<uint32_t> optimized_binary_with_nop;
     context->module()->ToBinary(&optimized_binary_with_nop,
                                 /* skip_nop = */ false);
@@ -886,6 +893,12 @@
                                              input_init_enable, version));
 }
 
+Optimizer::PassToken CreateInstDebugPrintfPass(uint32_t desc_set,
+                                               uint32_t shader_id) {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::InstDebugPrintfPass>(desc_set, shader_id));
+}
+
 Optimizer::PassToken CreateInstBuffAddrCheckPass(uint32_t desc_set,
                                                  uint32_t shader_id,
                                                  uint32_t version) {
diff --git a/source/opt/passes.h b/source/opt/passes.h
index 1a3675c..5b4ab89 100644
--- a/source/opt/passes.h
+++ b/source/opt/passes.h
@@ -46,6 +46,7 @@
 #include "source/opt/inline_opaque_pass.h"
 #include "source/opt/inst_bindless_check_pass.h"
 #include "source/opt/inst_buff_addr_check_pass.h"
+#include "source/opt/inst_debug_printf_pass.h"
 #include "source/opt/legalize_vector_shuffle_pass.h"
 #include "source/opt/licm_pass.h"
 #include "source/opt/local_access_chain_convert_pass.h"
diff --git a/source/opt/reflect.h b/source/opt/reflect.h
index 8106442..51d23a7 100644
--- a/source/opt/reflect.h
+++ b/source/opt/reflect.h
@@ -46,6 +46,8 @@
   return (opcode >= SpvOpTypeVoid && opcode <= SpvOpTypeForwardPointer) ||
          opcode == SpvOpTypePipeStorage || opcode == SpvOpTypeNamedBarrier ||
          opcode == SpvOpTypeAccelerationStructureNV ||
+         opcode == SpvOpTypeAccelerationStructureKHR ||
+         opcode == SpvOpTypeRayQueryProvisionalKHR ||
          opcode == SpvOpTypeCooperativeMatrixNV;
 }
 inline bool IsConstantInst(SpvOp opcode) {
diff --git a/source/opt/strip_debug_info_pass.cpp b/source/opt/strip_debug_info_pass.cpp
index 936c966..c86ce57 100644
--- a/source/opt/strip_debug_info_pass.cpp
+++ b/source/opt/strip_debug_info_pass.cpp
@@ -75,6 +75,7 @@
 
   for (auto& dbg : context()->debugs2()) to_kill.push_back(&dbg);
   for (auto& dbg : context()->debugs3()) to_kill.push_back(&dbg);
+  for (auto& dbg : context()->ext_inst_debuginfo()) to_kill.push_back(&dbg);
 
   // OpName must come first, since they may refer to other debug instructions.
   // If they are after the instructions that refer to, then they will be killed
diff --git a/source/opt/strip_reflect_info_pass.cpp b/source/opt/strip_reflect_info_pass.cpp
index c231ead..8b0f2db 100644
--- a/source/opt/strip_reflect_info_pass.cpp
+++ b/source/opt/strip_reflect_info_pass.cpp
@@ -77,6 +77,7 @@
   for (auto& dbg : context()->debugs1()) to_remove.push_back(&dbg);
   for (auto& dbg : context()->debugs2()) to_remove.push_back(&dbg);
   for (auto& dbg : context()->debugs3()) to_remove.push_back(&dbg);
+  for (auto& dbg : context()->ext_inst_debuginfo()) to_remove.push_back(&dbg);
 
   // remove any extended inst imports that are non semantic
   std::unordered_set<uint32_t> non_semantic_sets;
diff --git a/source/opt/type_manager.cpp b/source/opt/type_manager.cpp
index 166b828..27c7199 100644
--- a/source/opt/type_manager.cpp
+++ b/source/opt/type_manager.cpp
@@ -862,6 +862,9 @@
                                      inst.GetSingleWordInOperand(2),
                                      inst.GetSingleWordInOperand(3));
       break;
+    case SpvOpTypeRayQueryProvisionalKHR:
+      type = new RayQueryProvisionalKHR();
+      break;
     default:
       SPIRV_UNIMPLEMENTED(consumer_, "unhandled type");
       break;
diff --git a/source/opt/types.cpp b/source/opt/types.cpp
index 17f8fe9..426d3ea 100644
--- a/source/opt/types.cpp
+++ b/source/opt/types.cpp
@@ -128,6 +128,7 @@
     DeclareKindCase(NamedBarrier);
     DeclareKindCase(AccelerationStructureNV);
     DeclareKindCase(CooperativeMatrixNV);
+    DeclareKindCase(RayQueryProvisionalKHR);
 #undef DeclareKindCase
     default:
       assert(false && "Unhandled type");
@@ -173,6 +174,7 @@
     DeclareKindCase(NamedBarrier);
     DeclareKindCase(AccelerationStructureNV);
     DeclareKindCase(CooperativeMatrixNV);
+    DeclareKindCase(RayQueryProvisionalKHR);
 #undef DeclareKindCase
     default:
       assert(false && "Unhandled type");
@@ -223,6 +225,7 @@
     DeclareKindCase(NamedBarrier);
     DeclareKindCase(AccelerationStructureNV);
     DeclareKindCase(CooperativeMatrixNV);
+    DeclareKindCase(RayQueryProvisionalKHR);
 #undef DeclareKindCase
     default:
       assert(false && "Unhandled type");
diff --git a/source/opt/types.h b/source/opt/types.h
index 69071ea..ebeb476 100644
--- a/source/opt/types.h
+++ b/source/opt/types.h
@@ -59,6 +59,7 @@
 class NamedBarrier;
 class AccelerationStructureNV;
 class CooperativeMatrixNV;
+class RayQueryProvisionalKHR;
 
 // Abstract class for a SPIR-V type. It has a bunch of As<sublcass>() methods,
 // which is used as a way to probe the actual <subclass>.
@@ -94,7 +95,8 @@
     kPipeStorage,
     kNamedBarrier,
     kAccelerationStructureNV,
-    kCooperativeMatrixNV
+    kCooperativeMatrixNV,
+    kRayQueryProvisionalKHR
   };
 
   Type(Kind k) : kind_(k) {}
@@ -199,6 +201,7 @@
   DeclareCastMethod(NamedBarrier)
   DeclareCastMethod(AccelerationStructureNV)
   DeclareCastMethod(CooperativeMatrixNV)
+  DeclareCastMethod(RayQueryProvisionalKHR)
 #undef DeclareCastMethod
 
  protected:
@@ -659,6 +662,7 @@
 DefineParameterlessType(PipeStorage, pipe_storage);
 DefineParameterlessType(NamedBarrier, named_barrier);
 DefineParameterlessType(AccelerationStructureNV, accelerationStructureNV);
+DefineParameterlessType(RayQueryProvisionalKHR, rayQueryProvisionalKHR);
 #undef DefineParameterlessType
 
 }  // namespace analysis
diff --git a/source/opt/wrap_opkill.cpp b/source/opt/wrap_opkill.cpp
index ffd7a10..05e1db0 100644
--- a/source/opt/wrap_opkill.cpp
+++ b/source/opt/wrap_opkill.cpp
@@ -147,6 +147,7 @@
   bb->AddInstruction(std::move(kill_inst));
 
   // Add the bb to the function
+  bb->SetParent(opkill_function_.get());
   opkill_function_->AddBasicBlock(std::move(bb));
 
   // Add the function to the module.
diff --git a/source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.cpp b/source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.cpp
index da61c8d..ce66691 100644
--- a/source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.cpp
+++ b/source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.cpp
@@ -52,6 +52,13 @@
     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 (context->get_def_use_mgr()->NumUsers(&inst) > 0) {
       continue;
diff --git a/source/spirv_target_env.cpp b/source/spirv_target_env.cpp
index c4730e8..e2ff99c 100644
--- a/source/spirv_target_env.cpp
+++ b/source/spirv_target_env.cpp
@@ -153,6 +153,34 @@
   return false;
 }
 
+#define VULKAN_VER(MAJOR, MINOR) ((MAJOR << 22) | (MINOR << 12))
+#define SPIRV_VER(MAJOR, MINOR) ((MAJOR << 16) | (MINOR << 8))
+
+struct VulkanEnv {
+  spv_target_env vulkan_env;
+  uint32_t vulkan_ver;
+  uint32_t spirv_ver;
+};
+// Maps each Vulkan target environment enum to the Vulkan version, and the
+// maximum supported SPIR-V version for that Vulkan environment.
+// Keep this ordered from least capable to most capable.
+static const VulkanEnv ordered_vulkan_envs[] = {
+    {SPV_ENV_VULKAN_1_0, VULKAN_VER(1, 0), SPIRV_VER(1, 0)},
+    {SPV_ENV_VULKAN_1_1, VULKAN_VER(1, 1), SPIRV_VER(1, 3)},
+    {SPV_ENV_VULKAN_1_1_SPIRV_1_4, VULKAN_VER(1, 1), SPIRV_VER(1, 4)},
+    {SPV_ENV_VULKAN_1_2, VULKAN_VER(1, 2), SPIRV_VER(1, 5)}};
+
+bool spvParseVulkanEnv(uint32_t vulkan_ver, uint32_t spirv_ver,
+                       spv_target_env* env) {
+  for (auto triple : ordered_vulkan_envs) {
+    if (triple.vulkan_ver >= vulkan_ver && triple.spirv_ver >= spirv_ver) {
+      *env = triple.vulkan_env;
+      return true;
+    }
+  }
+  return false;
+}
+
 bool spvIsVulkanEnv(spv_target_env env) {
   switch (env) {
     case SPV_ENV_UNIVERSAL_1_0:
diff --git a/source/val/construct.cpp b/source/val/construct.cpp
index 1564449..733856c 100644
--- a/source/val/construct.cpp
+++ b/source/val/construct.cpp
@@ -169,9 +169,22 @@
       return true;
     }
 
+    // The next block in the traversal is either:
+    //  i.  The header block that declares |block| as its merge block.
+    //  ii. The immediate dominator of |block|.
+    auto NextBlock = [](const BasicBlock* block) -> const BasicBlock* {
+      for (auto& use : block->label()->uses()) {
+        if ((use.first->opcode() == SpvOpLoopMerge ||
+             use.first->opcode() == SpvOpSelectionMerge) &&
+            use.second == 1)
+          return use.first->block();
+      }
+      return block->immediate_dominator();
+    };
+
     bool seen_switch = false;
     auto header = entry_block();
-    auto block = header->immediate_dominator();
+    auto block = NextBlock(header);
     while (block) {
       auto terminator = block->terminator();
       auto index = terminator - &_.ordered_instructions()[0];
@@ -183,7 +196,7 @@
         auto merge_target = merge_inst->GetOperandAs<uint32_t>(0u);
         auto merge_block = merge_inst->function()->GetBlock(merge_target).first;
         if (merge_block->dominates(*header)) {
-          block = block->immediate_dominator();
+          block = NextBlock(block);
           continue;
         }
 
@@ -197,13 +210,15 @@
           }
         }
 
-        if (terminator->opcode() == SpvOpSwitch) seen_switch = true;
+        if (terminator->opcode() == SpvOpSwitch) {
+          seen_switch = true;
+        }
 
         // Hit an enclosing loop and didn't break or continue.
         if (merge_inst->opcode() == SpvOpLoopMerge) return false;
       }
 
-      block = block->immediate_dominator();
+      block = NextBlock(block);
     }
   }
 
diff --git a/source/val/instruction.h b/source/val/instruction.h
index e30bfbc..617cb06 100644
--- a/source/val/instruction.h
+++ b/source/val/instruction.h
@@ -91,6 +91,12 @@
            spvExtInstIsNonSemantic(inst_.ext_inst_type);
   }
 
+  /// True if this is an OpExtInst for debug info extension.
+  bool IsDebugInfo() const {
+    return opcode() == SpvOp::SpvOpExtInst &&
+           spvExtInstIsDebugInfo(inst_.ext_inst_type);
+  }
+
   // Casts the words belonging to the operand under |index| to |T| and returns.
   template <typename T>
   T GetOperandAs(size_t index) const {
diff --git a/source/val/validate.cpp b/source/val/validate.cpp
index 7f4b0dc..168968d 100644
--- a/source/val/validate.cpp
+++ b/source/val/validate.cpp
@@ -142,7 +142,7 @@
   for (const auto other_id : _.entry_points()) {
     if (other_id == id) continue;
     const auto other_id_names = CalculateNamesForEntryPoint(_, other_id);
-    for (const auto other_id_name : other_id_names) {
+    for (const auto& other_id_name : other_id_names) {
       if (names.find(other_id_name) != names.end()) {
         return _.diag(SPV_ERROR_INVALID_BINARY, _.FindDef(id))
                << "Entry point name \"" << other_id_name
@@ -431,7 +431,7 @@
   if (auto error = ValidateBuiltIns(*vstate)) return error;
   // These checks must be performed after individual opcode checks because
   // those checks register the limitation checked here.
-  for (const auto inst : vstate->ordered_instructions()) {
+  for (const auto& inst : vstate->ordered_instructions()) {
     if (auto error = ValidateExecutionLimitations(*vstate, &inst)) return error;
     if (auto error = ValidateSmallTypeUses(*vstate, &inst)) return error;
   }
diff --git a/source/val/validate_adjacency.cpp b/source/val/validate_adjacency.cpp
index 108e361..64655b0 100644
--- a/source/val/validate_adjacency.cpp
+++ b/source/val/validate_adjacency.cpp
@@ -54,6 +54,16 @@
         adjacency_status =
             adjacency_status == IN_NEW_FUNCTION ? IN_ENTRY_BLOCK : PHI_VALID;
         break;
+      case SpvOpExtInst:
+        // If it is a debug info instruction, we do not change the status to
+        // allow debug info instructions before OpVariable in a function.
+        // TODO(https://gitlab.khronos.org/spirv/SPIR-V/issues/533): We need
+        // to discuss the location of DebugScope, DebugNoScope, DebugDeclare,
+        // and DebugValue.
+        if (!spvExtInstIsDebugInfo(inst.ext_inst_type())) {
+          adjacency_status = PHI_AND_VAR_INVALID;
+        }
+        break;
       case SpvOpPhi:
         if (adjacency_status != PHI_VALID) {
           return _.diag(SPV_ERROR_INVALID_DATA, &inst)
diff --git a/source/val/validate_builtins.cpp b/source/val/validate_builtins.cpp
index 7623d49..d86c91e 100644
--- a/source/val/validate_builtins.cpp
+++ b/source/val/validate_builtins.cpp
@@ -2263,8 +2263,11 @@
 spv_result_t BuiltInsValidator::ValidateVertexIdOrInstanceIdAtDefinition(
     const Decoration& decoration, const Instruction& inst) {
   const SpvBuiltIn label = SpvBuiltIn(decoration.params()[0]);
-  bool allow_instance_id = _.HasCapability(SpvCapabilityRayTracingNV) &&
-                           label == SpvBuiltInInstanceId;
+  bool allow_instance_id =
+      (_.HasCapability(SpvCapabilityRayTracingNV) ||
+       _.HasCapability(SpvCapabilityRayTracingProvisionalKHR)) &&
+      label == SpvBuiltInInstanceId;
+
   if (spvIsVulkanEnv(_.context()->target_env) && !allow_instance_id) {
     return _.diag(SPV_ERROR_INVALID_DATA, &inst)
            << "Vulkan spec doesn't allow BuiltIn VertexId/InstanceId "
@@ -3085,7 +3088,8 @@
     case SpvBuiltInWorldToObjectNV:
     case SpvBuiltInHitTNV:
     case SpvBuiltInHitKindNV:
-    case SpvBuiltInIncomingRayFlagsNV: {
+    case SpvBuiltInIncomingRayFlagsNV:
+    case SpvBuiltInRayGeometryIndexKHR: {
       // No validation rules (for the moment).
       break;
     }
diff --git a/source/val/validate_cfg.cpp b/source/val/validate_cfg.cpp
index 43a6af7..1c279f6 100644
--- a/source/val/validate_cfg.cpp
+++ b/source/val/validate_cfg.cpp
@@ -62,6 +62,15 @@
     }
   }
 
+  if (!_.options()->before_hlsl_legalization) {
+    if (type_opcode == SpvOpTypeSampledImage ||
+        (_.HasCapability(SpvCapabilityShader) &&
+         (type_opcode == SpvOpTypeImage || type_opcode == SpvOpTypeSampler))) {
+      return _.diag(SPV_ERROR_INVALID_ID, inst)
+             << "Result type cannot be Op" << spvOpcodeString(type_opcode);
+    }
+  }
+
   // Create a uniqued vector of predecessor ids for comparison against
   // incoming values. OpBranchConditional %cond %label %label produces two
   // predecessors in the CFG.
@@ -728,10 +737,10 @@
     }
 
     Construct::ConstructBlockSet construct_blocks = construct.blocks(function);
+    std::string construct_name, header_name, exit_name;
+    std::tie(construct_name, header_name, exit_name) =
+        ConstructNames(construct.type());
     for (auto block : construct_blocks) {
-      std::string construct_name, header_name, exit_name;
-      std::tie(construct_name, header_name, exit_name) =
-          ConstructNames(construct.type());
       // Check that all exits from the construct are via structured exits.
       for (auto succ : *block->successors()) {
         if (block->reachable() && !construct_blocks.count(succ) &&
diff --git a/source/val/validate_decorations.cpp b/source/val/validate_decorations.cpp
index 3b44833..ce09e18 100644
--- a/source/val/validate_decorations.cpp
+++ b/source/val/validate_decorations.cpp
@@ -1524,6 +1524,22 @@
   return SPV_SUCCESS;
 }
 
+// Returns SPV_SUCCESS if validation rules are satisfied for the Block
+// decoration.  Otherwise emits a diagnostic and returns something other than
+// SPV_SUCCESS.
+spv_result_t CheckBlockDecoration(ValidationState_t& vstate,
+                                  const Instruction& inst,
+                                  const Decoration& decoration) {
+  assert(inst.id() && "Parser ensures the target of the decoration has an ID");
+  if (inst.opcode() != SpvOpTypeStruct) {
+    const char* const dec_name =
+        decoration.dec_type() == SpvDecorationBlock ? "Block" : "BufferBlock";
+    return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
+           << dec_name << " decoration on a non-struct type.";
+  }
+  return SPV_SUCCESS;
+}
+
 #define PASS_OR_BAIL_AT_LINE(X, LINE)           \
   {                                             \
     spv_result_t e##LINE = (X);                 \
@@ -1570,6 +1586,10 @@
         case SpvDecorationNoUnsignedWrap:
           PASS_OR_BAIL(CheckIntegerWrapDecoration(vstate, *inst, decoration));
           break;
+        case SpvDecorationBlock:
+        case SpvDecorationBufferBlock:
+          PASS_OR_BAIL(CheckBlockDecoration(vstate, *inst, decoration));
+          break;
         default:
           break;
       }
diff --git a/source/val/validate_extensions.cpp b/source/val/validate_extensions.cpp
index 070cc4c..1e311c1 100644
--- a/source/val/validate_extensions.cpp
+++ b/source/val/validate_extensions.cpp
@@ -14,12 +14,11 @@
 
 // Validates correctness of extension SPIR-V instructions.
 
-#include "source/val/validate.h"
-
 #include <sstream>
 #include <string>
 #include <vector>
 
+#include "OpenCLDebugInfo100.h"
 #include "source/diagnostic.h"
 #include "source/enum_string_mapping.h"
 #include "source/extensions.h"
@@ -28,6 +27,7 @@
 #include "source/opcode.h"
 #include "source/spirv_target_env.h"
 #include "source/val/instruction.h"
+#include "source/val/validate.h"
 #include "source/val/validation_state.h"
 
 namespace spvtools {
@@ -42,6 +42,144 @@
   return 0;
 }
 
+// Check that the operand of a debug info instruction |inst| at |word_index|
+// is a result id of an instruction with |expected_opcode|.
+spv_result_t ValidateOperandForDebugInfo(
+    ValidationState_t& _, const std::string& operand_name,
+    SpvOp expected_opcode, const Instruction* inst, uint32_t word_index,
+    const std::function<std::string()>& ext_inst_name) {
+  auto* operand = _.FindDef(inst->word(word_index));
+  if (operand->opcode() != expected_opcode) {
+    spv_opcode_desc desc = nullptr;
+    if (_.grammar().lookupOpcode(expected_opcode, &desc) != SPV_SUCCESS ||
+        !desc) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << ext_inst_name() << ": "
+             << "expected operand " << operand_name << " is invalid";
+    }
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << ext_inst_name() << ": "
+           << "expected operand " << operand_name << " must be a result id of "
+           << "Op" << desc->name;
+  }
+  return SPV_SUCCESS;
+}
+
+#define CHECK_OPERAND(NAME, opcode, index)                                  \
+  do {                                                                      \
+    auto result = ValidateOperandForDebugInfo(_, NAME, opcode, inst, index, \
+                                              ext_inst_name);               \
+    if (result != SPV_SUCCESS) return result;                               \
+  } while (0)
+
+// True if the operand of a debug info instruction |inst| at |word_index|
+// satisifies |expectation| that is given as a function. Otherwise,
+// returns false.
+bool DoesDebugInfoOperandMatchExpectation(
+    const ValidationState_t& _,
+    const std::function<bool(OpenCLDebugInfo100Instructions)>& expectation,
+    const Instruction* inst, uint32_t word_index) {
+  auto* debug_inst = _.FindDef(inst->word(word_index));
+  if (debug_inst->opcode() != SpvOpExtInst ||
+      debug_inst->ext_inst_type() != SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100 ||
+      !expectation(OpenCLDebugInfo100Instructions(debug_inst->word(4)))) {
+    return false;
+  }
+  return true;
+}
+
+// Check that the operand of a debug info instruction |inst| at |word_index|
+// is a result id of an debug info instruction whose debug instruction type
+// is |expected_debug_inst|.
+spv_result_t ValidateDebugInfoOperand(
+    ValidationState_t& _, const std::string& debug_inst_name,
+    OpenCLDebugInfo100Instructions expected_debug_inst, const Instruction* inst,
+    uint32_t word_index, const std::function<std::string()>& ext_inst_name) {
+  std::function<bool(OpenCLDebugInfo100Instructions)> expectation =
+      [expected_debug_inst](OpenCLDebugInfo100Instructions dbg_inst) {
+        return dbg_inst == expected_debug_inst;
+      };
+  if (DoesDebugInfoOperandMatchExpectation(_, expectation, inst, word_index))
+    return SPV_SUCCESS;
+
+  spv_ext_inst_desc desc = nullptr;
+  _.grammar().lookupExtInst(SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100,
+                            expected_debug_inst, &desc);
+  if (_.grammar().lookupExtInst(SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100,
+                                expected_debug_inst, &desc) != SPV_SUCCESS ||
+      !desc) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << ext_inst_name() << ": "
+           << "expected operand " << debug_inst_name << " is invalid";
+  }
+  return _.diag(SPV_ERROR_INVALID_DATA, inst)
+         << ext_inst_name() << ": "
+         << "expected operand " << debug_inst_name << " must be a result id of "
+         << desc->name;
+}
+
+#define CHECK_DEBUG_OPERAND(NAME, debug_opcode, index)                         \
+  do {                                                                         \
+    auto result = ValidateDebugInfoOperand(_, NAME, debug_opcode, inst, index, \
+                                           ext_inst_name);                     \
+    if (result != SPV_SUCCESS) return result;                                  \
+  } while (0)
+
+// Check that the operand of a debug info instruction |inst| at |word_index|
+// is a result id of an debug info instruction with DebugTypeBasic.
+spv_result_t ValidateOperandBaseType(
+    ValidationState_t& _, const Instruction* inst, uint32_t word_index,
+    const std::function<std::string()>& ext_inst_name) {
+  return ValidateDebugInfoOperand(_, "Base Type",
+                                  OpenCLDebugInfo100DebugTypeBasic, inst,
+                                  word_index, ext_inst_name);
+}
+
+// Check that the operand of a debug info instruction |inst| at |word_index|
+// is a result id of a debug lexical scope instruction which is one of
+// DebugCompilationUnit, DebugFunction, DebugLexicalBlock, or
+// DebugTypeComposite.
+spv_result_t ValidateOperandLexicalScope(
+    ValidationState_t& _, const std::string& debug_inst_name,
+    const Instruction* inst, uint32_t word_index,
+    const std::function<std::string()>& ext_inst_name) {
+  std::function<bool(OpenCLDebugInfo100Instructions)> expectation =
+      [](OpenCLDebugInfo100Instructions dbg_inst) {
+        return dbg_inst == OpenCLDebugInfo100DebugCompilationUnit ||
+               dbg_inst == OpenCLDebugInfo100DebugFunction ||
+               dbg_inst == OpenCLDebugInfo100DebugLexicalBlock ||
+               dbg_inst == OpenCLDebugInfo100DebugTypeComposite;
+      };
+  if (DoesDebugInfoOperandMatchExpectation(_, expectation, inst, word_index))
+    return SPV_SUCCESS;
+
+  return _.diag(SPV_ERROR_INVALID_DATA, inst)
+         << ext_inst_name() << ": "
+         << "expected operand " << debug_inst_name
+         << " must be a result id of a lexical scope";
+}
+
+// Check that the operand of a debug info instruction |inst| at |word_index|
+// is a result id of a debug type instruction (See DebugTypeXXX in
+// "4.3. Type instructions" section of OpenCL.DebugInfo.100 spec.
+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) {
+  std::function<bool(OpenCLDebugInfo100Instructions)> expectation =
+      [](OpenCLDebugInfo100Instructions dbg_inst) {
+        return OpenCLDebugInfo100DebugTypeBasic <= dbg_inst &&
+               dbg_inst <= OpenCLDebugInfo100DebugTypePtrToMember;
+      };
+  if (DoesDebugInfoOperandMatchExpectation(_, expectation, inst, word_index))
+    return SPV_SUCCESS;
+
+  return _.diag(SPV_ERROR_INVALID_DATA, inst)
+         << ext_inst_name() << ": "
+         << "expected operand " << debug_inst_name
+         << " is not a valid debug type";
+}
+
 }  // anonymous namespace
 
 spv_result_t ValidateExtension(ValidationState_t& _, const Instruction* inst) {
@@ -2028,6 +2166,317 @@
         break;
       }
     }
+  } else if (ext_inst_type == SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100) {
+    if (!_.IsVoidType(result_type)) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << ext_inst_name() << ": "
+             << "expected result type must be a result id of "
+             << "OpTypeVoid";
+    }
+
+    auto num_words = inst->words().size();
+
+    const OpenCLDebugInfo100Instructions ext_inst_key =
+        OpenCLDebugInfo100Instructions(ext_inst_index);
+    switch (ext_inst_key) {
+      case OpenCLDebugInfo100DebugInfoNone:
+      case OpenCLDebugInfo100DebugNoScope:
+      case OpenCLDebugInfo100DebugOperation:
+        // The binary parser validates the opcode for DebugInfoNone,
+        // DebugNoScope, DebugOperation, and the literal values don't need
+        // further checks.
+        break;
+      case OpenCLDebugInfo100DebugCompilationUnit: {
+        CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 7);
+        break;
+      }
+      case OpenCLDebugInfo100DebugSource: {
+        CHECK_OPERAND("File", SpvOpString, 5);
+        if (num_words == 7) CHECK_OPERAND("Text", SpvOpString, 6);
+        break;
+      }
+      case OpenCLDebugInfo100DebugTypeBasic: {
+        CHECK_OPERAND("Name", SpvOpString, 5);
+        CHECK_OPERAND("Size", SpvOpConstant, 6);
+        // "Encoding" param is already validated by the binary parsing stage.
+        break;
+      }
+      case OpenCLDebugInfo100DebugTypePointer:
+      case OpenCLDebugInfo100DebugTypeQualifier: {
+        auto validate_base_type =
+            ValidateOperandBaseType(_, inst, 5, ext_inst_name);
+        if (validate_base_type != SPV_SUCCESS) return validate_base_type;
+        break;
+      }
+      case OpenCLDebugInfo100DebugTypeVector: {
+        auto validate_base_type =
+            ValidateOperandBaseType(_, inst, 5, ext_inst_name);
+        if (validate_base_type != SPV_SUCCESS) return validate_base_type;
+
+        uint32_t component_count = inst->word(6);
+        if (!component_count || component_count > 4) {
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << ext_inst_name() << ": Component Count must be positive "
+                 << "integer less than or equal to 4";
+        }
+        break;
+      }
+      case OpenCLDebugInfo100DebugTypeArray: {
+        auto validate_base_type =
+            ValidateOperandDebugType(_, "Base Type", inst, 5, ext_inst_name);
+        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);
+          auto* component_count = _.FindDef(inst->word(i));
+          if (!_.IsIntScalarType(component_count->type_id()) ||
+              !component_count->word(3)) {
+            return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                   << ext_inst_name() << ": Component Count must be positive "
+                   << "integer";
+          }
+        }
+        break;
+      }
+      case OpenCLDebugInfo100DebugTypedef: {
+        CHECK_OPERAND("Name", SpvOpString, 5);
+        auto validate_base_type =
+            ValidateOperandBaseType(_, inst, 6, ext_inst_name);
+        if (validate_base_type != SPV_SUCCESS) return validate_base_type;
+        CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 7);
+        auto validate_parent =
+            ValidateOperandLexicalScope(_, "Parent", inst, 10, ext_inst_name);
+        if (validate_parent != SPV_SUCCESS) return validate_parent;
+        break;
+      }
+      case OpenCLDebugInfo100DebugTypeFunction: {
+        auto* return_type = _.FindDef(inst->word(6));
+        if (return_type->opcode() != SpvOpTypeVoid) {
+          auto validate_return = ValidateOperandDebugType(
+              _, "Return Type", inst, 6, ext_inst_name);
+          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);
+          if (validate_param != SPV_SUCCESS) return validate_param;
+        }
+        break;
+      }
+      case OpenCLDebugInfo100DebugTypeEnum: {
+        CHECK_OPERAND("Name", SpvOpString, 5);
+        if (!DoesDebugInfoOperandMatchExpectation(
+                _,
+                [](OpenCLDebugInfo100Instructions dbg_inst) {
+                  return dbg_inst == OpenCLDebugInfo100DebugInfoNone;
+                },
+                inst, 6)) {
+          auto validate_underlying_type = ValidateOperandDebugType(
+              _, "Underlying Types", inst, 6, ext_inst_name);
+          if (validate_underlying_type != SPV_SUCCESS)
+            return validate_underlying_type;
+        }
+        CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 7);
+        auto validate_parent =
+            ValidateOperandLexicalScope(_, "Parent", inst, 10, ext_inst_name);
+        if (validate_parent != SPV_SUCCESS) return validate_parent;
+        CHECK_OPERAND("Size", SpvOpConstant, 11);
+        auto* size = _.FindDef(inst->word(11));
+        if (!_.IsIntScalarType(size->type_id()) || !size->word(3)) {
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << ext_inst_name() << ": expected operand Size is a "
+                 << "positive integer";
+        }
+        for (uint32_t word_index = 13; word_index + 1 < num_words;
+             word_index += 2) {
+          CHECK_OPERAND("Value", SpvOpConstant, word_index);
+          CHECK_OPERAND("Name", SpvOpString, word_index + 1);
+        }
+        break;
+      }
+      case OpenCLDebugInfo100DebugTypeComposite: {
+        CHECK_OPERAND("Name", SpvOpString, 5);
+        CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 7);
+        auto validate_parent =
+            ValidateOperandLexicalScope(_, "Parent", inst, 10, ext_inst_name);
+        if (validate_parent != SPV_SUCCESS) return validate_parent;
+        CHECK_OPERAND("Linkage Name", SpvOpString, 11);
+        CHECK_OPERAND("Size", SpvOpConstant, 12);
+        for (uint32_t word_index = 14; word_index < num_words; ++word_index) {
+          if (!DoesDebugInfoOperandMatchExpectation(
+                  _,
+                  [](OpenCLDebugInfo100Instructions dbg_inst) {
+                    return dbg_inst == OpenCLDebugInfo100DebugTypeMember ||
+                           dbg_inst == OpenCLDebugInfo100DebugFunction ||
+                           dbg_inst == OpenCLDebugInfo100DebugTypeInheritance;
+                  },
+                  inst, word_index)) {
+            return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                   << ext_inst_name() << ": "
+                   << "expected operand Members "
+                   << "must be DebugTypeMember, DebugFunction, or "
+                      "DebugTypeInheritance";
+          }
+        }
+        break;
+      }
+      case OpenCLDebugInfo100DebugTypeMember: {
+        CHECK_OPERAND("Name", SpvOpString, 5);
+        auto validate_type =
+            ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name);
+        if (validate_type != SPV_SUCCESS) return validate_type;
+        CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 7);
+        CHECK_DEBUG_OPERAND("Parent", OpenCLDebugInfo100DebugTypeComposite, 10);
+        CHECK_OPERAND("Offset", SpvOpConstant, 11);
+        CHECK_OPERAND("Size", SpvOpConstant, 12);
+        if (num_words == 15) CHECK_OPERAND("Value", SpvOpConstant, 14);
+        break;
+      }
+      case OpenCLDebugInfo100DebugTypeInheritance: {
+        CHECK_DEBUG_OPERAND("Child", OpenCLDebugInfo100DebugTypeComposite, 5);
+        auto* debug_inst = _.FindDef(inst->word(5));
+        auto composite_type =
+            OpenCLDebugInfo100DebugCompositeType(debug_inst->word(6));
+        if (composite_type != OpenCLDebugInfo100Class &&
+            composite_type != OpenCLDebugInfo100Structure) {
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << ext_inst_name() << ": "
+                 << "expected operand Child must be class or struct debug type";
+        }
+        CHECK_DEBUG_OPERAND("Parent", OpenCLDebugInfo100DebugTypeComposite, 6);
+        debug_inst = _.FindDef(inst->word(6));
+        composite_type =
+            OpenCLDebugInfo100DebugCompositeType(debug_inst->word(6));
+        if (composite_type != OpenCLDebugInfo100Class &&
+            composite_type != OpenCLDebugInfo100Structure) {
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << ext_inst_name() << ": "
+                 << "expected operand Parent must be class or struct debug "
+                    "type";
+        }
+        CHECK_OPERAND("Offset", SpvOpConstant, 7);
+        CHECK_OPERAND("Size", SpvOpConstant, 8);
+        break;
+      }
+      case OpenCLDebugInfo100DebugFunction: {
+        CHECK_OPERAND("Name", SpvOpString, 5);
+        auto validate_type =
+            ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name);
+        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) {
+                  return dbg_inst == OpenCLDebugInfo100DebugInfoNone;
+                },
+                inst, 14)) {
+          CHECK_OPERAND("Function", SpvOpFunction, 14);
+        }
+        if (num_words == 16) {
+          CHECK_DEBUG_OPERAND("Declaration",
+                              OpenCLDebugInfo100DebugFunctionDeclaration, 15);
+        }
+        break;
+      }
+      case OpenCLDebugInfo100DebugFunctionDeclaration: {
+        CHECK_OPERAND("Name", SpvOpString, 5);
+        auto validate_type =
+            ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name);
+        if (validate_type != SPV_SUCCESS) return validate_type;
+        CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 7);
+        auto validate_parent =
+            ValidateOperandLexicalScope(_, "Parent", inst, 10, ext_inst_name);
+        if (validate_parent != SPV_SUCCESS) return validate_parent;
+        CHECK_OPERAND("Linkage Name", SpvOpString, 11);
+        break;
+      }
+      case OpenCLDebugInfo100DebugLexicalBlock: {
+        CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 5);
+        auto validate_parent =
+            ValidateOperandLexicalScope(_, "Parent", inst, 8, ext_inst_name);
+        if (validate_parent != SPV_SUCCESS) return validate_parent;
+        if (num_words == 10) CHECK_OPERAND("Name", SpvOpString, 9);
+        break;
+      }
+      case OpenCLDebugInfo100DebugScope: {
+        // 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;
+        if (num_words == 7) {
+          CHECK_DEBUG_OPERAND("Inlined At", OpenCLDebugInfo100DebugInlinedAt,
+                              6);
+        }
+        break;
+      }
+      case OpenCLDebugInfo100DebugLocalVariable: {
+        CHECK_OPERAND("Name", SpvOpString, 5);
+        auto validate_type =
+            ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name);
+        if (validate_type != SPV_SUCCESS) return validate_type;
+        CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 7);
+        auto validate_parent =
+            ValidateOperandLexicalScope(_, "Parent", inst, 10, ext_inst_name);
+        if (validate_parent != SPV_SUCCESS) return validate_parent;
+        break;
+      }
+      case OpenCLDebugInfo100DebugDeclare: {
+        CHECK_DEBUG_OPERAND("Local Variable",
+                            OpenCLDebugInfo100DebugLocalVariable, 5);
+
+        // 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) {
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << ext_inst_name() << ": "
+                 << "expected operand Variable must be a result id of "
+                    "OpVariable or OpFunctionParameter";
+        }
+
+        CHECK_DEBUG_OPERAND("Expression", OpenCLDebugInfo100DebugExpression, 7);
+        break;
+      }
+      case OpenCLDebugInfo100DebugExpression: {
+        for (uint32_t word_index = 5; word_index < num_words; ++word_index) {
+          CHECK_DEBUG_OPERAND("Operation", OpenCLDebugInfo100DebugOperation,
+                              word_index);
+        }
+        break;
+      }
+
+      // 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:
+        break;
+      case OpenCLDebugInfo100InstructionsMax:
+        assert(0);
+        break;
+    }
   }
 
   return SPV_SUCCESS;
diff --git a/source/val/validate_function.cpp b/source/val/validate_function.cpp
index f995ab3..f130eac 100644
--- a/source/val/validate_function.cpp
+++ b/source/val/validate_function.cpp
@@ -88,7 +88,7 @@
     const auto* use = pair.first;
     if (std::find(acceptable.begin(), acceptable.end(), use->opcode()) ==
             acceptable.end() &&
-        !use->IsNonSemantic()) {
+        !use->IsNonSemantic() && !use->IsDebugInfo()) {
       return _.diag(SPV_ERROR_INVALID_ID, use)
              << "Invalid use of function result id " << _.getIdName(inst->id())
              << ".";
diff --git a/source/val/validate_id.cpp b/source/val/validate_id.cpp
index 7406330..e1a775a 100644
--- a/source/val/validate_id.cpp
+++ b/source/val/validate_id.cpp
@@ -131,7 +131,11 @@
 // instruction operand's ID can be forward referenced.
 spv_result_t IdPass(ValidationState_t& _, Instruction* inst) {
   auto can_have_forward_declared_ids =
-      spvOperandCanBeForwardDeclaredFunction(inst->opcode());
+      inst->opcode() == SpvOpExtInst &&
+              spvExtInstIsDebugInfo(inst->ext_inst_type())
+          ? spvDbgInfoExtOperandCanBeForwardDeclaredFunction(
+                inst->ext_inst_type(), inst->word(4))
+          : spvOperandCanBeForwardDeclaredFunction(inst->opcode());
 
   // Keep track of a result id defined by this instruction.  0 means it
   // does not define an id.
@@ -167,8 +171,8 @@
           const auto opcode = inst->opcode();
           if (spvOpcodeGeneratesType(def->opcode()) &&
               !spvOpcodeGeneratesType(opcode) && !spvOpcodeIsDebug(opcode) &&
-              !inst->IsNonSemantic() && !spvOpcodeIsDecoration(opcode) &&
-              opcode != SpvOpFunction &&
+              !inst->IsDebugInfo() && !inst->IsNonSemantic() &&
+              !spvOpcodeIsDecoration(opcode) && opcode != SpvOpFunction &&
               opcode != SpvOpCooperativeMatrixLengthNV &&
               !(opcode == SpvOpSpecConstantOp &&
                 inst->word(3) == SpvOpCooperativeMatrixLengthNV)) {
@@ -176,8 +180,8 @@
                    << "Operand " << _.getIdName(operand_word)
                    << " cannot be a type";
           } else if (def->type_id() == 0 && !spvOpcodeGeneratesType(opcode) &&
-                     !spvOpcodeIsDebug(opcode) && !inst->IsNonSemantic() &&
-                     !spvOpcodeIsDecoration(opcode) &&
+                     !spvOpcodeIsDebug(opcode) && !inst->IsDebugInfo() &&
+                     !inst->IsNonSemantic() && !spvOpcodeIsDecoration(opcode) &&
                      !spvOpcodeIsBranch(opcode) && opcode != SpvOpPhi &&
                      opcode != SpvOpExtInst && opcode != SpvOpExtInstImport &&
                      opcode != SpvOpSelectionMerge &&
diff --git a/source/val/validate_image.cpp b/source/val/validate_image.cpp
index bc2753c..5b77058 100644
--- a/source/val/validate_image.cpp
+++ b/source/val/validate_image.cpp
@@ -149,6 +149,17 @@
   return false;
 }
 
+bool IsValidLodOperand(const ValidationState_t& _, SpvOp opcode) {
+  switch (opcode) {
+    case SpvOpImageRead:
+    case SpvOpImageWrite:
+    case SpvOpImageSparseRead:
+      return _.HasCapability(SpvCapabilityImageReadWriteLodAMD);
+    default:
+      return IsExplicitLod(opcode);
+  }
+}
+
 // Returns true if the opcode is a Image instruction which applies
 // homogenous projection to the coordinates.
 bool IsProj(SpvOp opcode) {
@@ -248,6 +259,7 @@
 
   const bool is_implicit_lod = IsImplicitLod(opcode);
   const bool is_explicit_lod = IsExplicitLod(opcode);
+  const bool is_valid_lod_operand = IsValidLodOperand(_, opcode);
 
   // The checks should be done in the order of definition of OperandImage.
 
@@ -277,7 +289,7 @@
   }
 
   if (mask & SpvImageOperandsLodMask) {
-    if (!is_explicit_lod && opcode != SpvOpImageFetch &&
+    if (!is_valid_lod_operand && opcode != SpvOpImageFetch &&
         opcode != SpvOpImageSparseFetch) {
       return _.diag(SPV_ERROR_INVALID_DATA, inst)
              << "Image Operand Lod can only be used with ExplicitLod opcodes "
@@ -835,6 +847,7 @@
     case SpvOpImageSparseSampleDrefExplicitLod:
     case SpvOpImageSparseGather:
     case SpvOpImageSparseDrefGather:
+    case SpvOpCopyObject:
       return true;
     default:
       return false;
diff --git a/source/val/validate_layout.cpp b/source/val/validate_layout.cpp
index 259befe..707ad52 100644
--- a/source/val/validate_layout.cpp
+++ b/source/val/validate_layout.cpp
@@ -14,15 +14,16 @@
 
 // Source code for logical layout validation as described in section 2.4
 
-#include "source/val/validate.h"
-
 #include <cassert>
 
+#include "DebugInfo.h"
+#include "OpenCLDebugInfo100.h"
 #include "source/diagnostic.h"
 #include "source/opcode.h"
 #include "source/operand.h"
 #include "source/val/function.h"
 #include "source/val/instruction.h"
+#include "source/val/validate.h"
 #include "source/val/validation_state.h"
 
 namespace spvtools {
@@ -46,6 +47,53 @@
                  << "Non-semantic OpExtInst must not appear before types "
                  << "section";
         }
+      } else if (spvExtInstIsDebugInfo(inst->ext_inst_type())) {
+        const uint32_t ext_inst_index = inst->word(4);
+        bool local_debug_info = false;
+        if (inst->ext_inst_type() == SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100) {
+          const OpenCLDebugInfo100Instructions ext_inst_key =
+              OpenCLDebugInfo100Instructions(ext_inst_index);
+          if (ext_inst_key == OpenCLDebugInfo100DebugScope ||
+              ext_inst_key == OpenCLDebugInfo100DebugNoScope ||
+              ext_inst_key == OpenCLDebugInfo100DebugDeclare ||
+              ext_inst_key == OpenCLDebugInfo100DebugValue) {
+            local_debug_info = true;
+          }
+        } else {
+          const DebugInfoInstructions ext_inst_key =
+              DebugInfoInstructions(ext_inst_index);
+          if (ext_inst_key == DebugInfoDebugScope ||
+              ext_inst_key == DebugInfoDebugNoScope ||
+              ext_inst_key == DebugInfoDebugDeclare ||
+              ext_inst_key == DebugInfoDebugValue) {
+            local_debug_info = true;
+          }
+        }
+
+        if (local_debug_info) {
+          if (_.in_function_body() == false) {
+            // DebugScope, DebugNoScope, DebugDeclare, DebugValue must
+            // appear in a function body.
+            return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
+                   << "DebugScope, DebugNoScope, DebugDeclare, DebugValue "
+                   << "of debug info extension must appear in a function "
+                   << "body";
+          }
+        } else {
+          // Debug info extinst opcodes other than DebugScope, DebugNoScope,
+          // DebugDeclare, DebugValue must be placed between section 9 (types,
+          // constants, global variables) and section 10 (function
+          // declarations).
+          if (_.current_layout_section() < kLayoutTypes ||
+              _.current_layout_section() >= kLayoutFunctionDeclarations) {
+            return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
+                   << "Debug info extension instructions other than "
+                   << "DebugScope, DebugNoScope, DebugDeclare, DebugValue "
+                   << "must appear between section 9 (types, constants, "
+                   << "global variables) and section 10 (function "
+                   << "declarations)";
+          }
+        }
       } else {
         // otherwise they must be used in a block
         if (_.current_layout_section() < kLayoutFunctionDefinitions) {
@@ -182,6 +230,53 @@
                    << "Non-semantic OpExtInst within function definition must "
                       "appear in a block";
           }
+        } else if (spvExtInstIsDebugInfo(inst->ext_inst_type())) {
+          const uint32_t ext_inst_index = inst->word(4);
+          bool local_debug_info = false;
+          if (inst->ext_inst_type() == SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100) {
+            const OpenCLDebugInfo100Instructions ext_inst_key =
+                OpenCLDebugInfo100Instructions(ext_inst_index);
+            if (ext_inst_key == OpenCLDebugInfo100DebugScope ||
+                ext_inst_key == OpenCLDebugInfo100DebugNoScope ||
+                ext_inst_key == OpenCLDebugInfo100DebugDeclare ||
+                ext_inst_key == OpenCLDebugInfo100DebugValue) {
+              local_debug_info = true;
+            }
+          } else {
+            const DebugInfoInstructions ext_inst_key =
+                DebugInfoInstructions(ext_inst_index);
+            if (ext_inst_key == DebugInfoDebugScope ||
+                ext_inst_key == DebugInfoDebugNoScope ||
+                ext_inst_key == DebugInfoDebugDeclare ||
+                ext_inst_key == DebugInfoDebugValue) {
+              local_debug_info = true;
+            }
+          }
+
+          if (local_debug_info) {
+            if (_.in_function_body() == false) {
+              // DebugScope, DebugNoScope, DebugDeclare, DebugValue must
+              // appear in a function body.
+              return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
+                     << "DebugScope, DebugNoScope, DebugDeclare, DebugValue "
+                     << "of debug info extension must appear in a function "
+                     << "body";
+            }
+          } else {
+            // Debug info extinst opcodes other than DebugScope, DebugNoScope,
+            // DebugDeclare, DebugValue must be placed between section 9 (types,
+            // constants, global variables) and section 10 (function
+            // declarations).
+            if (_.current_layout_section() < kLayoutTypes ||
+                _.current_layout_section() >= kLayoutFunctionDeclarations) {
+              return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
+                     << "Debug info extension instructions other than "
+                     << "DebugScope, DebugNoScope, DebugDeclare, DebugValue "
+                     << "must appear between section 9 (types, constants, "
+                     << "global variables) and section 10 (function "
+                     << "declarations)";
+            }
+          }
         } else {
           // otherwise they must be used in a block
           if (_.in_block() == false) {
diff --git a/source/val/validate_memory.cpp b/source/val/validate_memory.cpp
index bff8b20..1e1a38d 100644
--- a/source/val/validate_memory.cpp
+++ b/source/val/validate_memory.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.
@@ -533,7 +535,9 @@
       if (!IsAllowedTypeOrArrayOfSame(
               _, pointee,
               {SpvOpTypeImage, SpvOpTypeSampler, SpvOpTypeSampledImage,
-               SpvOpTypeAccelerationStructureNV})) {
+               SpvOpTypeAccelerationStructureNV,
+               SpvOpTypeAccelerationStructureKHR,
+               SpvOpTypeRayQueryProvisionalKHR})) {
         return _.diag(SPV_ERROR_INVALID_ID, inst)
                << "UniformConstant OpVariable <id> '" << _.getIdName(inst->id())
                << "' has illegal type.\n"
@@ -542,6 +546,8 @@
                << "are used only as handles to refer to opaque resources. Such "
                << "variables must be typed as OpTypeImage, OpTypeSampler, "
                << "OpTypeSampledImage, OpTypeAccelerationStructureNV, "
+                  "OpTypeAccelerationStructureKHR, "
+                  "OpTypeRayQueryProvisionalKHR, "
                << "or an array of one of these types.";
       }
     }
diff --git a/source/val/validate_scopes.cpp b/source/val/validate_scopes.cpp
index 320d828..ea3ebcb 100644
--- a/source/val/validate_scopes.cpp
+++ b/source/val/validate_scopes.cpp
@@ -32,6 +32,7 @@
     case SpvScopeSubgroup:
     case SpvScopeInvocation:
     case SpvScopeQueueFamilyKHR:
+    case SpvScopeShaderCallKHR:
       return true;
     case SpvScopeMax:
       break;
@@ -143,6 +144,20 @@
              << spvOpcodeString(opcode)
              << ": in WebGPU environment Execution Scope is limited to "
              << "Workgroup";
+    } else {
+      _.function(inst->function()->id())
+          ->RegisterExecutionModelLimitation(
+              [](SpvExecutionModel model, std::string* message) {
+                if (model != SpvExecutionModelGLCompute) {
+                  if (message) {
+                    *message =
+                        ": in WebGPU environment, Workgroup Execution Scope is "
+                        "limited to GLCompute execution model";
+                  }
+                  return false;
+                }
+                return true;
+              });
     }
   }
 
@@ -261,6 +276,22 @@
         }
         break;
     }
+
+    if (value == SpvScopeWorkgroup) {
+      _.function(inst->function()->id())
+          ->RegisterExecutionModelLimitation(
+              [](SpvExecutionModel model, std::string* message) {
+                if (model != SpvExecutionModelGLCompute) {
+                  if (message) {
+                    *message =
+                        ": in WebGPU environment, Workgroup Memory Scope is "
+                        "limited to GLCompute execution model";
+                  }
+                  return false;
+                }
+                return true;
+              });
+    }
   }
 
   // TODO(atgoo@github.com) Add checks for OpenCL and OpenGL environments.
diff --git a/source/val/validation_state.cpp b/source/val/validation_state.cpp
index 51aebbe..0739148 100644
--- a/source/val/validation_state.cpp
+++ b/source/val/validation_state.cpp
@@ -1052,7 +1052,7 @@
 }
 
 void ValidationState_t::ComputeRecursiveEntryPoints() {
-  for (const Function func : functions()) {
+  for (const Function& func : functions()) {
     std::stack<uint32_t> call_stack;
     std::set<uint32_t> visited;
 
diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt
index 732d9fe..679a61b 100644
--- a/test/fuzz/CMakeLists.txt
+++ b/test/fuzz/CMakeLists.txt
@@ -22,10 +22,13 @@
           fact_manager_test.cpp
           fuzz_test_util.cpp
           fuzzer_pass_add_useful_constructs_test.cpp
+          fuzzer_pass_construct_composites_test.cpp
           fuzzer_pass_donate_modules_test.cpp
           instruction_descriptor_test.cpp
+          transformation_access_chain_test.cpp
           transformation_add_constant_boolean_test.cpp
           transformation_add_constant_composite_test.cpp
+          transformation_add_constant_null_test.cpp
           transformation_add_constant_scalar_test.cpp
           transformation_add_dead_block_test.cpp
           transformation_add_dead_break_test.cpp
@@ -33,6 +36,7 @@
           transformation_add_function_test.cpp
           transformation_add_global_undef_test.cpp
           transformation_add_global_variable_test.cpp
+          transformation_add_local_variable_test.cpp
           transformation_add_no_contraction_decoration_test.cpp
           transformation_add_type_array_test.cpp
           transformation_add_type_boolean_test.cpp
@@ -46,9 +50,13 @@
           transformation_composite_construct_test.cpp
           transformation_composite_extract_test.cpp
           transformation_copy_object_test.cpp
+          transformation_equation_instruction_test.cpp
+          transformation_function_call_test.cpp
+          transformation_load_test.cpp
           transformation_merge_blocks_test.cpp
           transformation_move_block_down_test.cpp
           transformation_outline_function_test.cpp
+          transformation_permute_function_parameters_test.cpp
           transformation_replace_boolean_constant_with_constant_binary_test.cpp
           transformation_replace_constant_with_uniform_test.cpp
           transformation_replace_id_with_synonym_test.cpp
@@ -57,6 +65,9 @@
           transformation_set_memory_operands_mask_test.cpp
           transformation_set_selection_control_test.cpp
           transformation_split_block_test.cpp
+          transformation_store_test.cpp
+          transformation_swap_commutable_operands_test.cpp
+          transformation_toggle_access_chain_instruction_test.cpp
           transformation_vector_shuffle_test.cpp
           uniform_buffer_element_descriptor_test.cpp)
 
diff --git a/test/fuzz/data_synonym_transformation_test.cpp b/test/fuzz/data_synonym_transformation_test.cpp
index 21ea068..2d26d17 100644
--- a/test/fuzz/data_synonym_transformation_test.cpp
+++ b/test/fuzz/data_synonym_transformation_test.cpp
@@ -123,13 +123,24 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
-  fact_manager.AddFact(MakeSynonymFact(12, {}, 100, {0}), context.get());
-  fact_manager.AddFact(MakeSynonymFact(13, {}, 100, {1}), context.get());
-  fact_manager.AddFact(MakeSynonymFact(22, {}, 100, {2}), context.get());
-  fact_manager.AddFact(MakeSynonymFact(28, {}, 101, {0}), context.get());
-  fact_manager.AddFact(MakeSynonymFact(23, {}, 101, {1}), context.get());
-  fact_manager.AddFact(MakeSynonymFact(32, {}, 101, {2}), context.get());
-  fact_manager.AddFact(MakeSynonymFact(23, {}, 101, {3}), context.get());
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               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());
 
   // Replace %12 with %100[0] in '%25 = OpAccessChain %24 %20 %12'
   auto instruction_descriptor_1 =
@@ -139,13 +150,16 @@
   // Bad: id already in use
   auto bad_extract_1 = TransformationCompositeExtract(
       MakeInstructionDescriptor(25, SpvOpAccessChain, 0), 25, 100, {0});
-  ASSERT_TRUE(good_extract_1.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(bad_extract_1.IsApplicable(context.get(), fact_manager));
-  good_extract_1.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      good_extract_1.IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      bad_extract_1.IsApplicable(context.get(), transformation_context));
+  good_extract_1.Apply(context.get(), &transformation_context);
   auto replacement_1 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(12, instruction_descriptor_1, 1), 102);
-  ASSERT_TRUE(replacement_1.IsApplicable(context.get(), fact_manager));
-  replacement_1.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      replacement_1.IsApplicable(context.get(), transformation_context));
+  replacement_1.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // Replace %13 with %100[1] in 'OpStore %15 %13'
@@ -153,12 +167,14 @@
   auto good_extract_2 =
       TransformationCompositeExtract(instruction_descriptor_2, 103, 100, {1});
   // No bad example provided here.
-  ASSERT_TRUE(good_extract_2.IsApplicable(context.get(), fact_manager));
-  good_extract_2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      good_extract_2.IsApplicable(context.get(), transformation_context));
+  good_extract_2.Apply(context.get(), &transformation_context);
   auto replacement_2 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(13, instruction_descriptor_2, 1), 103);
-  ASSERT_TRUE(replacement_2.IsApplicable(context.get(), fact_manager));
-  replacement_2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      replacement_2.IsApplicable(context.get(), transformation_context));
+  replacement_2.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // Replace %22 with %100[2] in '%23 = OpConvertSToF %16 %22'
@@ -166,16 +182,19 @@
       MakeInstructionDescriptor(23, SpvOpConvertSToF, 0);
   auto good_extract_3 =
       TransformationCompositeExtract(instruction_descriptor_3, 104, 100, {2});
-  ASSERT_TRUE(good_extract_3.IsApplicable(context.get(), fact_manager));
-  good_extract_3.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      good_extract_3.IsApplicable(context.get(), transformation_context));
+  good_extract_3.Apply(context.get(), &transformation_context);
   auto replacement_3 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(22, instruction_descriptor_3, 0), 104);
   // Bad: wrong input operand index
   auto bad_replacement_3 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(22, instruction_descriptor_3, 1), 104);
-  ASSERT_TRUE(replacement_3.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(bad_replacement_3.IsApplicable(context.get(), fact_manager));
-  replacement_3.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      replacement_3.IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      bad_replacement_3.IsApplicable(context.get(), transformation_context));
+  replacement_3.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // Replace %28 with %101[0] in 'OpStore %33 %28'
@@ -185,13 +204,16 @@
   // Bad: instruction descriptor does not identify an appropriate instruction
   auto bad_extract_4 = TransformationCompositeExtract(
       MakeInstructionDescriptor(33, SpvOpCopyObject, 0), 105, 101, {0});
-  ASSERT_TRUE(good_extract_4.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(bad_extract_4.IsApplicable(context.get(), fact_manager));
-  good_extract_4.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      good_extract_4.IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      bad_extract_4.IsApplicable(context.get(), transformation_context));
+  good_extract_4.Apply(context.get(), &transformation_context);
   auto replacement_4 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(28, instruction_descriptor_4, 1), 105);
-  ASSERT_TRUE(replacement_4.IsApplicable(context.get(), fact_manager));
-  replacement_4.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      replacement_4.IsApplicable(context.get(), transformation_context));
+  replacement_4.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // Replace %23 with %101[1] in '%50 = OpCopyObject %16 %23'
@@ -199,16 +221,19 @@
       MakeInstructionDescriptor(50, SpvOpCopyObject, 0);
   auto good_extract_5 =
       TransformationCompositeExtract(instruction_descriptor_5, 106, 101, {1});
-  ASSERT_TRUE(good_extract_5.IsApplicable(context.get(), fact_manager));
-  good_extract_5.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      good_extract_5.IsApplicable(context.get(), transformation_context));
+  good_extract_5.Apply(context.get(), &transformation_context);
   auto replacement_5 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(23, instruction_descriptor_5, 0), 106);
   // Bad: wrong synonym fact being used
   auto bad_replacement_5 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(23, instruction_descriptor_5, 0), 105);
-  ASSERT_TRUE(replacement_5.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(bad_replacement_5.IsApplicable(context.get(), fact_manager));
-  replacement_5.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      replacement_5.IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      bad_replacement_5.IsApplicable(context.get(), transformation_context));
+  replacement_5.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // Replace %32 with %101[2] in 'OpStore %33 %32'
@@ -218,13 +243,16 @@
   // Bad: id 1001 does not exist
   auto bad_extract_6 =
       TransformationCompositeExtract(instruction_descriptor_6, 107, 1001, {2});
-  ASSERT_TRUE(good_extract_6.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(bad_extract_6.IsApplicable(context.get(), fact_manager));
-  good_extract_6.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      good_extract_6.IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      bad_extract_6.IsApplicable(context.get(), transformation_context));
+  good_extract_6.Apply(context.get(), &transformation_context);
   auto replacement_6 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(32, instruction_descriptor_6, 1), 107);
-  ASSERT_TRUE(replacement_6.IsApplicable(context.get(), fact_manager));
-  replacement_6.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      replacement_6.IsApplicable(context.get(), transformation_context));
+  replacement_6.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // Replace %23 with %101[3] in '%51 = OpCopyObject %16 %23'
@@ -232,16 +260,19 @@
       MakeInstructionDescriptor(51, SpvOpCopyObject, 0);
   auto good_extract_7 =
       TransformationCompositeExtract(instruction_descriptor_7, 108, 101, {3});
-  ASSERT_TRUE(good_extract_7.IsApplicable(context.get(), fact_manager));
-  good_extract_7.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      good_extract_7.IsApplicable(context.get(), transformation_context));
+  good_extract_7.Apply(context.get(), &transformation_context);
   auto replacement_7 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(23, instruction_descriptor_7, 0), 108);
   // Bad: use id 0 is invalid
   auto bad_replacement_7 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(0, instruction_descriptor_7, 0), 108);
-  ASSERT_TRUE(replacement_7.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(bad_replacement_7.IsApplicable(context.get(), fact_manager));
-  replacement_7.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      replacement_7.IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      bad_replacement_7.IsApplicable(context.get(), transformation_context));
+  replacement_7.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   const std::string after_transformation = R"(
@@ -380,32 +411,41 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
-  fact_manager.AddFact(MakeSynonymFact(23, {}, 100, {0}), context.get());
-  fact_manager.AddFact(MakeSynonymFact(25, {}, 100, {1}), context.get());
-  fact_manager.AddFact(MakeSynonymFact(50, {}, 100, {2}), context.get());
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               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());
 
   // Replace %23 with %100[0] in '%26 = OpFAdd %7 %23 %25'
   auto instruction_descriptor_1 = MakeInstructionDescriptor(26, SpvOpFAdd, 0);
   auto extract_1 =
       TransformationCompositeExtract(instruction_descriptor_1, 101, 100, {0});
-  ASSERT_TRUE(extract_1.IsApplicable(context.get(), fact_manager));
-  extract_1.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(extract_1.IsApplicable(context.get(), transformation_context));
+  extract_1.Apply(context.get(), &transformation_context);
   auto replacement_1 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(23, instruction_descriptor_1, 0), 101);
-  ASSERT_TRUE(replacement_1.IsApplicable(context.get(), fact_manager));
-  replacement_1.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      replacement_1.IsApplicable(context.get(), transformation_context));
+  replacement_1.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // Replace %25 with %100[1] in '%26 = OpFAdd %7 %23 %25'
   auto instruction_descriptor_2 = MakeInstructionDescriptor(26, SpvOpFAdd, 0);
   auto extract_2 =
       TransformationCompositeExtract(instruction_descriptor_2, 102, 100, {1});
-  ASSERT_TRUE(extract_2.IsApplicable(context.get(), fact_manager));
-  extract_2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(extract_2.IsApplicable(context.get(), transformation_context));
+  extract_2.Apply(context.get(), &transformation_context);
   auto replacement_2 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(25, instruction_descriptor_2, 1), 102);
-  ASSERT_TRUE(replacement_2.IsApplicable(context.get(), fact_manager));
-  replacement_2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      replacement_2.IsApplicable(context.get(), transformation_context));
+  replacement_2.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   const std::string after_transformation = R"(
@@ -541,26 +581,37 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
-  fact_manager.AddFact(MakeSynonymFact(16, {}, 100, {0}), context.get());
-  fact_manager.AddFact(MakeSynonymFact(45, {}, 100, {1}), context.get());
-  fact_manager.AddFact(MakeSynonymFact(27, {}, 101, {0}), context.get());
-  fact_manager.AddFact(MakeSynonymFact(36, {}, 101, {1}), context.get());
-  fact_manager.AddFact(MakeSynonymFact(27, {}, 101, {2}), context.get());
-  fact_manager.AddFact(MakeSynonymFact(22, {}, 102, {0}), context.get());
-  fact_manager.AddFact(MakeSynonymFact(15, {}, 102, {1}), context.get());
+  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());
 
   // Replace %45 with %100[1] in '%46 = OpCompositeConstruct %32 %35 %45'
   auto instruction_descriptor_1 =
       MakeInstructionDescriptor(46, SpvOpCompositeConstruct, 0);
   auto extract_1 =
       TransformationCompositeExtract(instruction_descriptor_1, 201, 100, {1});
-  ASSERT_TRUE(extract_1.IsApplicable(context.get(), fact_manager));
-  extract_1.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(extract_1.IsApplicable(context.get(), transformation_context));
+  extract_1.Apply(context.get(), &transformation_context);
   auto replacement_1 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(45, instruction_descriptor_1, 1), 201);
-  ASSERT_TRUE(replacement_1.IsApplicable(context.get(), fact_manager));
-  replacement_1.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      replacement_1.IsApplicable(context.get(), transformation_context));
+  replacement_1.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // Replace second occurrence of %27 with %101[0] in '%28 =
@@ -569,12 +620,13 @@
       MakeInstructionDescriptor(28, SpvOpCompositeConstruct, 0);
   auto extract_2 =
       TransformationCompositeExtract(instruction_descriptor_2, 202, 101, {0});
-  ASSERT_TRUE(extract_2.IsApplicable(context.get(), fact_manager));
-  extract_2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(extract_2.IsApplicable(context.get(), transformation_context));
+  extract_2.Apply(context.get(), &transformation_context);
   auto replacement_2 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(27, instruction_descriptor_2, 1), 202);
-  ASSERT_TRUE(replacement_2.IsApplicable(context.get(), fact_manager));
-  replacement_2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      replacement_2.IsApplicable(context.get(), transformation_context));
+  replacement_2.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // Replace %36 with %101[1] in '%45 = OpCompositeConstruct %31 %36 %41 %44'
@@ -582,12 +634,13 @@
       MakeInstructionDescriptor(45, SpvOpCompositeConstruct, 0);
   auto extract_3 =
       TransformationCompositeExtract(instruction_descriptor_3, 203, 101, {1});
-  ASSERT_TRUE(extract_3.IsApplicable(context.get(), fact_manager));
-  extract_3.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(extract_3.IsApplicable(context.get(), transformation_context));
+  extract_3.Apply(context.get(), &transformation_context);
   auto replacement_3 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(36, instruction_descriptor_3, 0), 203);
-  ASSERT_TRUE(replacement_3.IsApplicable(context.get(), fact_manager));
-  replacement_3.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      replacement_3.IsApplicable(context.get(), transformation_context));
+  replacement_3.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // Replace first occurrence of %27 with %101[2] in '%28 = OpCompositeConstruct
@@ -596,24 +649,26 @@
       MakeInstructionDescriptor(28, SpvOpCompositeConstruct, 0);
   auto extract_4 =
       TransformationCompositeExtract(instruction_descriptor_4, 204, 101, {2});
-  ASSERT_TRUE(extract_4.IsApplicable(context.get(), fact_manager));
-  extract_4.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(extract_4.IsApplicable(context.get(), transformation_context));
+  extract_4.Apply(context.get(), &transformation_context);
   auto replacement_4 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(27, instruction_descriptor_4, 0), 204);
-  ASSERT_TRUE(replacement_4.IsApplicable(context.get(), fact_manager));
-  replacement_4.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      replacement_4.IsApplicable(context.get(), transformation_context));
+  replacement_4.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // Replace %22 with %102[0] in 'OpStore %23 %22'
   auto instruction_descriptor_5 = MakeInstructionDescriptor(23, SpvOpStore, 0);
   auto extract_5 =
       TransformationCompositeExtract(instruction_descriptor_5, 205, 102, {0});
-  ASSERT_TRUE(extract_5.IsApplicable(context.get(), fact_manager));
-  extract_5.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(extract_5.IsApplicable(context.get(), transformation_context));
+  extract_5.Apply(context.get(), &transformation_context);
   auto replacement_5 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(22, instruction_descriptor_5, 1), 205);
-  ASSERT_TRUE(replacement_5.IsApplicable(context.get(), fact_manager));
-  replacement_5.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      replacement_5.IsApplicable(context.get(), transformation_context));
+  replacement_5.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   const std::string after_transformation = R"(
@@ -816,38 +871,63 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
-  fact_manager.AddFact(MakeSynonymFact(20, {0}, 100, {0}), context.get());
-  fact_manager.AddFact(MakeSynonymFact(20, {1}, 100, {1}), context.get());
-  fact_manager.AddFact(MakeSynonymFact(20, {2}, 100, {2}), context.get());
-  fact_manager.AddFact(MakeSynonymFact(54, {}, 100, {3}), context.get());
-  fact_manager.AddFact(MakeSynonymFact(15, {0}, 101, {0}), context.get());
-  fact_manager.AddFact(MakeSynonymFact(15, {1}, 101, {1}), context.get());
-  fact_manager.AddFact(MakeSynonymFact(19, {0}, 101, {2}), context.get());
-  fact_manager.AddFact(MakeSynonymFact(19, {1}, 101, {3}), context.get());
-  fact_manager.AddFact(MakeSynonymFact(27, {}, 102, {0}), context.get());
-  fact_manager.AddFact(MakeSynonymFact(15, {0}, 102, {1}), context.get());
-  fact_manager.AddFact(MakeSynonymFact(15, {1}, 102, {2}), context.get());
-  fact_manager.AddFact(MakeSynonymFact(33, {}, 103, {0}), context.get());
-  fact_manager.AddFact(MakeSynonymFact(47, {0}, 103, {1}), context.get());
-  fact_manager.AddFact(MakeSynonymFact(47, {1}, 103, {2}), context.get());
-  fact_manager.AddFact(MakeSynonymFact(47, {2}, 103, {3}), context.get());
-  fact_manager.AddFact(MakeSynonymFact(42, {}, 104, {0}), context.get());
-  fact_manager.AddFact(MakeSynonymFact(45, {}, 104, {1}), context.get());
-  fact_manager.AddFact(MakeSynonymFact(38, {0}, 105, {0}), context.get());
-  fact_manager.AddFact(MakeSynonymFact(38, {1}, 105, {1}), context.get());
-  fact_manager.AddFact(MakeSynonymFact(46, {}, 105, {2}), context.get());
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               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());
 
   // Replace %20 with %100[0:2] in '%80 = OpCopyObject %16 %20'
   auto instruction_descriptor_1 =
       MakeInstructionDescriptor(80, SpvOpCopyObject, 0);
   auto shuffle_1 = TransformationVectorShuffle(instruction_descriptor_1, 200,
                                                100, 100, {0, 1, 2});
-  ASSERT_TRUE(shuffle_1.IsApplicable(context.get(), fact_manager));
-  shuffle_1.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(shuffle_1.IsApplicable(context.get(), transformation_context));
+  shuffle_1.Apply(context.get(), &transformation_context);
   auto replacement_1 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(20, instruction_descriptor_1, 0), 200);
-  ASSERT_TRUE(replacement_1.IsApplicable(context.get(), fact_manager));
-  replacement_1.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      replacement_1.IsApplicable(context.get(), transformation_context));
+  replacement_1.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // Replace %54 with %100[3] in '%56 = OpFOrdNotEqual %30 %54 %55'
@@ -856,24 +936,26 @@
   auto extract_2 =
       TransformationCompositeExtract(instruction_descriptor_2, 201, 100, {3});
 
-  ASSERT_TRUE(extract_2.IsApplicable(context.get(), fact_manager));
-  extract_2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(extract_2.IsApplicable(context.get(), transformation_context));
+  extract_2.Apply(context.get(), &transformation_context);
   auto replacement_2 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(54, instruction_descriptor_2, 0), 201);
-  ASSERT_TRUE(replacement_2.IsApplicable(context.get(), fact_manager));
-  replacement_2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      replacement_2.IsApplicable(context.get(), transformation_context));
+  replacement_2.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // Replace %15 with %101[0:1] in 'OpStore %12 %15'
   auto instruction_descriptor_3 = MakeInstructionDescriptor(64, SpvOpStore, 0);
   auto shuffle_3 = TransformationVectorShuffle(instruction_descriptor_3, 202,
                                                101, 101, {0, 1});
-  ASSERT_TRUE(shuffle_3.IsApplicable(context.get(), fact_manager));
-  shuffle_3.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(shuffle_3.IsApplicable(context.get(), transformation_context));
+  shuffle_3.Apply(context.get(), &transformation_context);
   auto replacement_3 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(15, instruction_descriptor_3, 1), 202);
-  ASSERT_TRUE(replacement_3.IsApplicable(context.get(), fact_manager));
-  replacement_3.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      replacement_3.IsApplicable(context.get(), transformation_context));
+  replacement_3.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // Replace %19 with %101[2:3] in '%81 = OpVectorShuffle %16 %19 %19 0 0 1'
@@ -881,12 +963,13 @@
       MakeInstructionDescriptor(81, SpvOpVectorShuffle, 0);
   auto shuffle_4 = TransformationVectorShuffle(instruction_descriptor_4, 203,
                                                101, 101, {2, 3});
-  ASSERT_TRUE(shuffle_4.IsApplicable(context.get(), fact_manager));
-  shuffle_4.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(shuffle_4.IsApplicable(context.get(), transformation_context));
+  shuffle_4.Apply(context.get(), &transformation_context);
   auto replacement_4 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(19, instruction_descriptor_4, 0), 203);
-  ASSERT_TRUE(replacement_4.IsApplicable(context.get(), fact_manager));
-  replacement_4.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      replacement_4.IsApplicable(context.get(), transformation_context));
+  replacement_4.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // Replace %27 with %102[0] in '%82 = OpCompositeConstruct %21 %26 %27 %28
@@ -896,12 +979,13 @@
   auto extract_5 =
       TransformationCompositeExtract(instruction_descriptor_5, 204, 102, {0});
 
-  ASSERT_TRUE(extract_5.IsApplicable(context.get(), fact_manager));
-  extract_5.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(extract_5.IsApplicable(context.get(), transformation_context));
+  extract_5.Apply(context.get(), &transformation_context);
   auto replacement_5 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(27, instruction_descriptor_5, 1), 204);
-  ASSERT_TRUE(replacement_5.IsApplicable(context.get(), fact_manager));
-  replacement_5.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      replacement_5.IsApplicable(context.get(), transformation_context));
+  replacement_5.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // Replace %15 with %102[1:2] in '%83 = OpCopyObject %10 %15'
@@ -909,12 +993,13 @@
       MakeInstructionDescriptor(83, SpvOpCopyObject, 0);
   auto shuffle_6 = TransformationVectorShuffle(instruction_descriptor_6, 205,
                                                102, 102, {1, 2});
-  ASSERT_TRUE(shuffle_6.IsApplicable(context.get(), fact_manager));
-  shuffle_6.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(shuffle_6.IsApplicable(context.get(), transformation_context));
+  shuffle_6.Apply(context.get(), &transformation_context);
   auto replacement_6 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(15, instruction_descriptor_6, 0), 205);
-  ASSERT_TRUE(replacement_6.IsApplicable(context.get(), fact_manager));
-  replacement_6.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      replacement_6.IsApplicable(context.get(), transformation_context));
+  replacement_6.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // Replace %33 with %103[0] in '%86 = OpCopyObject %30 %33'
@@ -922,12 +1007,13 @@
       MakeInstructionDescriptor(86, SpvOpCopyObject, 0);
   auto extract_7 =
       TransformationCompositeExtract(instruction_descriptor_7, 206, 103, {0});
-  ASSERT_TRUE(extract_7.IsApplicable(context.get(), fact_manager));
-  extract_7.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(extract_7.IsApplicable(context.get(), transformation_context));
+  extract_7.Apply(context.get(), &transformation_context);
   auto replacement_7 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(33, instruction_descriptor_7, 0), 206);
-  ASSERT_TRUE(replacement_7.IsApplicable(context.get(), fact_manager));
-  replacement_7.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      replacement_7.IsApplicable(context.get(), transformation_context));
+  replacement_7.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // Replace %47 with %103[1:3] in '%84 = OpCopyObject %39 %47'
@@ -935,12 +1021,13 @@
       MakeInstructionDescriptor(84, SpvOpCopyObject, 0);
   auto shuffle_8 = TransformationVectorShuffle(instruction_descriptor_8, 207,
                                                103, 103, {1, 2, 3});
-  ASSERT_TRUE(shuffle_8.IsApplicable(context.get(), fact_manager));
-  shuffle_8.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(shuffle_8.IsApplicable(context.get(), transformation_context));
+  shuffle_8.Apply(context.get(), &transformation_context);
   auto replacement_8 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(47, instruction_descriptor_8, 0), 207);
-  ASSERT_TRUE(replacement_8.IsApplicable(context.get(), fact_manager));
-  replacement_8.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      replacement_8.IsApplicable(context.get(), transformation_context));
+  replacement_8.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // Replace %42 with %104[0] in '%85 = OpCopyObject %30 %42'
@@ -948,12 +1035,13 @@
       MakeInstructionDescriptor(85, SpvOpCopyObject, 0);
   auto extract_9 =
       TransformationCompositeExtract(instruction_descriptor_9, 208, 104, {0});
-  ASSERT_TRUE(extract_9.IsApplicable(context.get(), fact_manager));
-  extract_9.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(extract_9.IsApplicable(context.get(), transformation_context));
+  extract_9.Apply(context.get(), &transformation_context);
   auto replacement_9 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(42, instruction_descriptor_9, 0), 208);
-  ASSERT_TRUE(replacement_9.IsApplicable(context.get(), fact_manager));
-  replacement_9.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      replacement_9.IsApplicable(context.get(), transformation_context));
+  replacement_9.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // Replace %45 with %104[1] in '%63 = OpLogicalOr %30 %45 %46'
@@ -961,24 +1049,26 @@
       MakeInstructionDescriptor(63, SpvOpLogicalOr, 0);
   auto extract_10 =
       TransformationCompositeExtract(instruction_descriptor_10, 209, 104, {1});
-  ASSERT_TRUE(extract_10.IsApplicable(context.get(), fact_manager));
-  extract_10.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(extract_10.IsApplicable(context.get(), transformation_context));
+  extract_10.Apply(context.get(), &transformation_context);
   auto replacement_10 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(45, instruction_descriptor_10, 0), 209);
-  ASSERT_TRUE(replacement_10.IsApplicable(context.get(), fact_manager));
-  replacement_10.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      replacement_10.IsApplicable(context.get(), transformation_context));
+  replacement_10.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // Replace %38 with %105[0:1] in 'OpStore %36 %38'
   auto instruction_descriptor_11 = MakeInstructionDescriptor(85, SpvOpStore, 0);
   auto shuffle_11 = TransformationVectorShuffle(instruction_descriptor_11, 210,
                                                 105, 105, {0, 1});
-  ASSERT_TRUE(shuffle_11.IsApplicable(context.get(), fact_manager));
-  shuffle_11.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(shuffle_11.IsApplicable(context.get(), transformation_context));
+  shuffle_11.Apply(context.get(), &transformation_context);
   auto replacement_11 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(38, instruction_descriptor_11, 1), 210);
-  ASSERT_TRUE(replacement_11.IsApplicable(context.get(), fact_manager));
-  replacement_11.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      replacement_11.IsApplicable(context.get(), transformation_context));
+  replacement_11.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // Replace %46 with %105[2] in '%62 = OpLogicalAnd %30 %45 %46'
@@ -986,12 +1076,13 @@
       MakeInstructionDescriptor(62, SpvOpLogicalAnd, 0);
   auto extract_12 =
       TransformationCompositeExtract(instruction_descriptor_12, 211, 105, {2});
-  ASSERT_TRUE(extract_12.IsApplicable(context.get(), fact_manager));
-  extract_12.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(extract_12.IsApplicable(context.get(), transformation_context));
+  extract_12.Apply(context.get(), &transformation_context);
   auto replacement_12 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(46, instruction_descriptor_12, 1), 211);
-  ASSERT_TRUE(replacement_12.IsApplicable(context.get(), fact_manager));
-  replacement_12.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      replacement_12.IsApplicable(context.get(), transformation_context));
+  replacement_12.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   const std::string after_transformation = R"(
diff --git a/test/fuzz/equivalence_relation_test.cpp b/test/fuzz/equivalence_relation_test.cpp
index 3f2ea58..280aa3a 100644
--- a/test/fuzz/equivalence_relation_test.cpp
+++ b/test/fuzz/equivalence_relation_test.cpp
@@ -47,6 +47,10 @@
   EquivalenceRelation<uint32_t, UInt32Hash, UInt32Equals> relation;
   ASSERT_TRUE(relation.GetAllKnownValues().empty());
 
+  for (uint32_t element = 0; element < 100; element++) {
+    relation.Register(element);
+  }
+
   for (uint32_t element = 2; element < 80; element += 2) {
     relation.MakeEquivalent(0, element);
     relation.MakeEquivalent(element - 1, element + 1);
@@ -123,6 +127,11 @@
   EquivalenceRelation<uint32_t, UInt32Hash, UInt32Equals> relation2;
 
   for (uint32_t i = 0; i < 1000; ++i) {
+    relation1.Register(i);
+    relation2.Register(i);
+  }
+
+  for (uint32_t i = 0; i < 1000; ++i) {
     if (i >= 10) {
       relation1.MakeEquivalent(i, i - 10);
       relation2.MakeEquivalent(i, i - 10);
diff --git a/test/fuzz/fact_manager_test.cpp b/test/fuzz/fact_manager_test.cpp
index b3f32cd..625eb72 100644
--- a/test/fuzz/fact_manager_test.cpp
+++ b/test/fuzz/fact_manager_test.cpp
@@ -1173,6 +1173,278 @@
                                         context.get()));
 }
 
+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, {}), context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(17, {}), MakeDataDescriptor(7, {}), context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(15, {}), MakeDataDescriptor(17, {}), context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(16, {}), MakeDataDescriptor(14, {}), context.get()));
+}
+
+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, {}), context.get()));
+}
+
+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, {}), context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(20, {}), MakeDataDescriptor(16, {}), context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(24, {}), MakeDataDescriptor(15, {}), context.get()));
+}
+
+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, {}), context.get()));
+
+  fact_manager.AddFactIdEquation(18, SpvOpIAdd, {16, 14}, context.get());
+
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(18, {}), MakeDataDescriptor(15, {}), context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(17, {}), MakeDataDescriptor(18, {}), context.get()));
+
+  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, {}), context.get()));
+
+  fact_manager.AddFactIdEquation(21, SpvOpISub, {14, 19}, context.get());
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(21, {}), MakeDataDescriptor(15, {}), context.get()));
+
+  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, {}), context.get()));
+}
+
+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, {}), context.get()));
+
+  fact_manager.AddFactIdEquation(18, SpvOpIAdd, {16, 114}, context.get());
+
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(18, {}), MakeDataDescriptor(15, {}), context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(17, {}), MakeDataDescriptor(18, {}), context.get()));
+
+  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, {}), context.get()));
+
+  fact_manager.AddFactIdEquation(21, SpvOpISub, {14, 19}, context.get());
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(21, {}), MakeDataDescriptor(15, {}), context.get()));
+
+  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, {}), context.get()));
+}
+
 }  // namespace
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/test/fuzz/fuzzer_pass_add_useful_constructs_test.cpp b/test/fuzz/fuzzer_pass_add_useful_constructs_test.cpp
index 89f006e..90d1c84 100644
--- a/test/fuzz/fuzzer_pass_add_useful_constructs_test.cpp
+++ b/test/fuzz/fuzzer_pass_add_useful_constructs_test.cpp
@@ -64,10 +64,14 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
   FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0).get(), 100);
   protobufs::TransformationSequence transformation_sequence;
 
-  FuzzerPassAddUsefulConstructs pass(context.get(), &fact_manager,
+  FuzzerPassAddUsefulConstructs pass(context.get(), &transformation_context,
                                      &fuzzer_context, &transformation_sequence);
   pass.Apply();
   ASSERT_TRUE(IsValid(env, context.get()));
@@ -173,6 +177,10 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
   FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0).get(), 100);
   protobufs::TransformationSequence transformation_sequence;
 
@@ -292,7 +300,7 @@
               context->get_constant_mgr()->FindConstant(&int_constant_8));
   }
 
-  FuzzerPassAddUsefulConstructs pass(context.get(), &fact_manager,
+  FuzzerPassAddUsefulConstructs pass(context.get(), &transformation_context,
                                      &fuzzer_context, &transformation_sequence);
   pass.Apply();
   ASSERT_TRUE(IsValid(env, context.get()));
diff --git a/test/fuzz/fuzzer_pass_construct_composites_test.cpp b/test/fuzz/fuzzer_pass_construct_composites_test.cpp
new file mode 100644
index 0000000..cc21f74
--- /dev/null
+++ b/test/fuzz/fuzzer_pass_construct_composites_test.cpp
@@ -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.
+
+#include "source/fuzz/fuzzer_pass_construct_composites.h"
+#include "source/fuzz/pseudo_random_generator.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(FuzzerPassConstructCompositesTest, IsomorphicStructs) {
+  // This test declares various isomorphic structs, and a struct that is made up
+  // of these isomorphic structs.  The pass to construct composites is then
+  // applied several times to check that no issues arise related to using a
+  // value of one struct type when a value of an isomorphic struct type is
+  // required.
+
+  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 = OpConstant %6 0
+          %8 = OpTypeStruct %6 %6 %6
+          %9 = OpTypeStruct %6 %6 %6
+         %10 = OpTypeStruct %6 %6 %6
+         %11 = OpTypeStruct %6 %6 %6
+         %12 = OpTypeStruct %6 %6 %6
+         %13 = OpTypeStruct %8 %9 %10 %11 %12
+         %14 = OpConstantComposite %8 %7 %7 %7
+         %15 = OpConstantComposite %9 %7 %7 %7
+         %16 = OpConstantComposite %10 %7 %7 %7
+         %17 = OpConstantComposite %11 %7 %7 %7
+         %18 = OpConstantComposite %12 %7 %7 %7
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpNop
+               OpNop
+               OpNop
+               OpNop
+               OpNop
+               OpNop
+               OpNop
+               OpNop
+               OpNop
+               OpNop
+               OpNop
+               OpNop
+               OpNop
+               OpNop
+               OpNop
+               OpNop
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+
+  auto prng = MakeUnique<PseudoRandomGenerator>(0);
+
+  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);
+
+    FuzzerContext fuzzer_context(prng.get(), 100);
+    protobufs::TransformationSequence transformation_sequence;
+
+    FuzzerPassConstructComposites fuzzer_pass(
+        context.get(), &transformation_context, &fuzzer_context,
+        &transformation_sequence);
+
+    fuzzer_pass.Apply();
+
+    // We just check that the result is valid.
+    ASSERT_TRUE(IsValid(env, context.get()));
+  }
+}
+
+TEST(FuzzerPassConstructCompositesTest, IsomorphicArrays) {
+  // This test declares various isomorphic arrays, and a struct that is made up
+  // of these isomorphic arrays.  The pass to construct composites is then
+  // applied several times to check that no issues arise related to using a
+  // value of one array type when a value of an isomorphic array type is
+  // required.
+
+  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
+         %50 = OpTypeInt 32 0
+         %51 = OpConstant %50 3
+          %7 = OpConstant %6 0
+          %8 = OpTypeArray %6 %51
+          %9 = OpTypeArray %6 %51
+         %10 = OpTypeArray %6 %51
+         %11 = OpTypeArray %6 %51
+         %12 = OpTypeArray %6 %51
+         %13 = OpTypeStruct %8 %9 %10 %11 %12
+         %14 = OpConstantComposite %8 %7 %7 %7
+         %15 = OpConstantComposite %9 %7 %7 %7
+         %16 = OpConstantComposite %10 %7 %7 %7
+         %17 = OpConstantComposite %11 %7 %7 %7
+         %18 = OpConstantComposite %12 %7 %7 %7
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpNop
+               OpNop
+               OpNop
+               OpNop
+               OpNop
+               OpNop
+               OpNop
+               OpNop
+               OpNop
+               OpNop
+               OpNop
+               OpNop
+               OpNop
+               OpNop
+               OpNop
+               OpNop
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+
+  auto prng = MakeUnique<PseudoRandomGenerator>(0);
+
+  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);
+
+    FuzzerContext fuzzer_context(prng.get(), 100);
+    protobufs::TransformationSequence transformation_sequence;
+
+    FuzzerPassConstructComposites fuzzer_pass(
+        context.get(), &transformation_context, &fuzzer_context,
+        &transformation_sequence);
+
+    fuzzer_pass.Apply();
+
+    // We just check that the result is valid.
+    ASSERT_TRUE(IsValid(env, context.get()));
+  }
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/fuzzer_pass_donate_modules_test.cpp b/test/fuzz/fuzzer_pass_donate_modules_test.cpp
index 988b675..432ca4f 100644
--- a/test/fuzz/fuzzer_pass_donate_modules_test.cpp
+++ b/test/fuzz/fuzzer_pass_donate_modules_test.cpp
@@ -194,15 +194,19 @@
   ASSERT_TRUE(IsValid(env, donor_context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
-  FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0).get(), 100);
+  PseudoRandomGenerator prng(0);
+  FuzzerContext fuzzer_context(&prng, 100);
   protobufs::TransformationSequence transformation_sequence;
 
-  FuzzerPassDonateModules fuzzer_pass(recipient_context.get(), &fact_manager,
-                                      &fuzzer_context, &transformation_sequence,
-                                      {});
+  FuzzerPassDonateModules fuzzer_pass(recipient_context.get(),
+                                      &transformation_context, &fuzzer_context,
+                                      &transformation_sequence, {});
 
-  fuzzer_pass.DonateSingleModule(donor_context.get());
+  fuzzer_pass.DonateSingleModule(donor_context.get(), false);
 
   // We just check that the result is valid.  Checking to what it should be
   // exactly equal to would be very fragile.
@@ -268,15 +272,19 @@
   ASSERT_TRUE(IsValid(env, donor_context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
-  FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0).get(), 100);
+  PseudoRandomGenerator prng(0);
+  FuzzerContext fuzzer_context(&prng, 100);
   protobufs::TransformationSequence transformation_sequence;
 
-  FuzzerPassDonateModules fuzzer_pass(recipient_context.get(), &fact_manager,
-                                      &fuzzer_context, &transformation_sequence,
-                                      {});
+  FuzzerPassDonateModules fuzzer_pass(recipient_context.get(),
+                                      &transformation_context, &fuzzer_context,
+                                      &transformation_sequence, {});
 
-  fuzzer_pass.DonateSingleModule(donor_context.get());
+  fuzzer_pass.DonateSingleModule(donor_context.get(), false);
 
   ASSERT_TRUE(IsValid(env, recipient_context.get()));
 
@@ -313,14 +321,17 @@
         %100 = OpTypePointer Function %6
         %101 = OpTypeStruct %6
         %102 = OpTypePointer Private %101
-        %103 = OpVariable %102 Private
-        %104 = OpConstant %12 0
-        %105 = OpTypePointer Private %6
-        %106 = OpTypePointer Function %12
-        %107 = OpTypeStruct %12
-        %108 = OpTypePointer Private %107
-        %109 = OpVariable %108 Private
-        %110 = OpTypePointer Private %12
+        %104 = OpConstant %6 0
+        %105 = OpConstantComposite %101 %104
+        %103 = OpVariable %102 Private %105
+        %106 = OpConstant %12 0
+        %107 = OpTypePointer Private %6
+        %108 = OpTypePointer Function %12
+        %109 = OpTypeStruct %12
+        %110 = OpTypePointer Private %109
+        %112 = OpConstantComposite %109 %13
+        %111 = OpVariable %110 Private %112
+        %113 = OpTypePointer Private %12
           %4 = OpFunction %2 None %3
           %5 = OpLabel
           %8 = OpVariable %7 Function
@@ -333,16 +344,16 @@
                OpStore %18 %24
                OpReturn
                OpFunctionEnd
-        %111 = OpFunction %2 None %3
-        %112 = OpLabel
-        %113 = OpVariable %100 Function
-        %114 = OpVariable %106 Function
-        %115 = OpAccessChain %105 %103 %104
-        %116 = OpLoad %6 %115
-               OpStore %113 %116
-        %117 = OpAccessChain %110 %109 %104
-        %118 = OpLoad %12 %117
-               OpStore %114 %118
+        %114 = OpFunction %2 None %3
+        %115 = OpLabel
+        %116 = OpVariable %100 Function %104
+        %117 = OpVariable %108 Function %13
+        %118 = OpAccessChain %107 %103 %106
+        %119 = OpLoad %6 %118
+               OpStore %116 %119
+        %120 = OpAccessChain %113 %111 %106
+        %121 = OpLoad %12 %120
+               OpStore %117 %121
                OpReturn
                OpFunctionEnd
   )";
@@ -389,15 +400,19 @@
   ASSERT_TRUE(IsValid(env, donor_context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
-  FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0).get(), 100);
+  PseudoRandomGenerator prng(0);
+  FuzzerContext fuzzer_context(&prng, 100);
   protobufs::TransformationSequence transformation_sequence;
 
-  FuzzerPassDonateModules fuzzer_pass(recipient_context.get(), &fact_manager,
-                                      &fuzzer_context, &transformation_sequence,
-                                      {});
+  FuzzerPassDonateModules fuzzer_pass(recipient_context.get(),
+                                      &transformation_context, &fuzzer_context,
+                                      &transformation_sequence, {});
 
-  fuzzer_pass.DonateSingleModule(donor_context.get());
+  fuzzer_pass.DonateSingleModule(donor_context.get(), false);
 
   ASSERT_TRUE(IsValid(env, recipient_context.get()));
 
@@ -419,25 +434,991 @@
          %10 = OpTypePointer Input %7
          %11 = OpVariable %10 Input
         %100 = OpTypePointer Private %7
-        %101 = OpVariable %100 Private
-        %102 = OpTypePointer Private %7
-        %103 = OpVariable %102 Private
+        %102 = OpConstant %6 0
+        %103 = OpConstantComposite %7 %102 %102 %102 %102
+        %101 = OpVariable %100 Private %103
+        %104 = OpTypePointer Private %7
+        %105 = OpVariable %104 Private %103
           %4 = OpFunction %2 None %3
           %5 = OpLabel
          %12 = OpLoad %7 %11
                OpStore %9 %12
                OpReturn
                OpFunctionEnd
-        %104 = OpFunction %2 None %3
-        %105 = OpLabel
-        %106 = OpLoad %7 %103
-               OpStore %101 %106
+        %106 = OpFunction %2 None %3
+        %107 = OpLabel
+        %108 = OpLoad %7 %105
+               OpStore %101 %108
                OpReturn
                OpFunctionEnd
   )";
   ASSERT_TRUE(IsEqual(env, after_transformation, recipient_context.get()));
 }
 
+TEST(FuzzerPassDonateModulesTest, DonateFunctionTypeWithDifferentPointers) {
+  std::string recipient_and_donor_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 0
+          %7 = OpTypePointer Function %6
+          %8 = OpTypeFunction %2 %7
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %9 = OpVariable %7 Function
+         %10 = OpFunctionCall %2 %11 %9
+               OpReturn
+               OpFunctionEnd
+         %11 = OpFunction %2 None %8
+         %12 = OpFunctionParameter %7
+         %13 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_5;
+  const auto consumer = nullptr;
+  const auto recipient_context = BuildModule(
+      env, consumer, recipient_and_donor_shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+
+  const auto donor_context = BuildModule(
+      env, consumer, recipient_and_donor_shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, donor_context.get()));
+
+  FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               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(), false);
+
+  // 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()));
+}
+
+TEST(FuzzerPassDonateModulesTest, DonateOpConstantNull) {
+  std::string recipient_shader = R"(
+               OpCapability Shader
+               OpCapability ImageQuery
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+               OpSourceExtension "GL_EXT_samplerless_texture_functions"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  std::string donor_shader = R"(
+               OpCapability Shader
+               OpCapability ImageQuery
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypePointer Private %6
+          %8 = OpConstantNull %7
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto recipient_context =
+      BuildModule(env, consumer, recipient_shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+
+  const auto donor_context =
+      BuildModule(env, consumer, donor_shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, donor_context.get()));
+
+  FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               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(), false);
+
+  // 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()));
+}
+
+TEST(FuzzerPassDonateModulesTest, DonateCodeThatUsesImages) {
+  std::string recipient_shader = R"(
+               OpCapability Shader
+               OpCapability ImageQuery
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+               OpSourceExtension "GL_EXT_samplerless_texture_functions"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  std::string donor_shader = R"(
+               OpCapability Shader
+               OpCapability ImageQuery
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+               OpSourceExtension "GL_EXT_samplerless_texture_functions"
+               OpName %4 "main"
+               OpName %10 "mySampler"
+               OpName %21 "myTexture"
+               OpName %33 "v"
+               OpDecorate %10 RelaxedPrecision
+               OpDecorate %10 DescriptorSet 0
+               OpDecorate %10 Binding 0
+               OpDecorate %11 RelaxedPrecision
+               OpDecorate %21 RelaxedPrecision
+               OpDecorate %21 DescriptorSet 0
+               OpDecorate %21 Binding 1
+               OpDecorate %22 RelaxedPrecision
+               OpDecorate %34 RelaxedPrecision
+               OpDecorate %40 RelaxedPrecision
+               OpDecorate %42 RelaxedPrecision
+               OpDecorate %43 RelaxedPrecision
+          %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 = OpTypeInt 32 1
+         %13 = OpConstant %12 2
+         %15 = OpTypeVector %12 2
+         %17 = OpTypeInt 32 0
+         %18 = OpConstant %17 0
+         %20 = OpTypePointer UniformConstant %7
+         %21 = OpVariable %20 UniformConstant
+         %23 = OpConstant %12 1
+         %25 = OpConstant %17 1
+         %27 = OpTypeBool
+         %31 = OpTypeVector %6 4
+         %32 = OpTypePointer Function %31
+         %35 = OpConstantComposite %15 %23 %23
+         %36 = OpConstant %12 3
+         %37 = OpConstant %12 4
+         %38 = OpConstantComposite %15 %36 %37
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %33 = OpVariable %32 Function
+         %11 = OpLoad %8 %10
+         %14 = OpImage %7 %11
+         %16 = OpImageQuerySizeLod %15 %14 %13
+         %19 = OpCompositeExtract %12 %16 0
+         %22 = OpLoad %7 %21
+         %24 = OpImageQuerySizeLod %15 %22 %23
+         %26 = OpCompositeExtract %12 %24 1
+         %28 = OpSGreaterThan %27 %19 %26
+               OpSelectionMerge %30 None
+               OpBranchConditional %28 %29 %41
+         %29 = OpLabel
+         %34 = OpLoad %8 %10
+         %39 = OpImage %7 %34
+         %40 = OpImageFetch %31 %39 %35 Lod|ConstOffset %13 %38
+               OpStore %33 %40
+               OpBranch %30
+         %41 = OpLabel
+         %42 = OpLoad %7 %21
+         %43 = OpImageFetch %31 %42 %35 Lod|ConstOffset %13 %38
+               OpStore %33 %43
+               OpBranch %30
+         %30 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto recipient_context =
+      BuildModule(env, consumer, recipient_shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+
+  const auto donor_context =
+      BuildModule(env, consumer, donor_shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, donor_context.get()));
+
+  FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               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(), false);
+
+  // 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()));
+}
+
+TEST(FuzzerPassDonateModulesTest, DonateCodeThatUsesSampler) {
+  std::string recipient_shader = R"(
+               OpCapability Shader
+               OpCapability ImageQuery
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+               OpSourceExtension "GL_EXT_samplerless_texture_functions"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  std::string donor_shader = R"(
+               OpCapability Shader
+               OpCapability ImageQuery
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+               OpDecorate %16 DescriptorSet 0
+               OpDecorate %16 Binding 0
+               OpDecorate %12 DescriptorSet 0
+               OpDecorate %12 Binding 64
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %23 = OpTypeFloat 32
+          %6 = OpTypeImage %23 2D 2 0 0 1 Unknown
+         %47 = OpTypePointer UniformConstant %6
+         %12 = OpVariable %47 UniformConstant
+         %15 = OpTypeSampler
+         %55 = OpTypePointer UniformConstant %15
+         %17 = OpTypeSampledImage %6
+         %16 = OpVariable %55 UniformConstant
+         %37 = OpTypeVector %23 4
+        %109 = OpConstant %23 0
+         %66 = OpConstantComposite %37 %109 %109 %109 %109
+         %56 = OpTypeBool
+         %54 = OpConstantTrue %56
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpBranch %50
+         %50 = OpLabel
+         %51 = OpPhi %37 %66 %5 %111 %53
+               OpLoopMerge %52 %53 None
+               OpBranchConditional %54 %53 %52
+         %53 = OpLabel
+        %106 = OpLoad %6 %12
+        %107 = OpLoad %15 %16
+        %110 = OpSampledImage %17 %106 %107
+        %111 = OpImageSampleImplicitLod %37 %110 %66 Bias %109
+               OpBranch %50
+         %52 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto recipient_context =
+      BuildModule(env, consumer, recipient_shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+
+  const auto donor_context =
+      BuildModule(env, consumer, donor_shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, donor_context.get()));
+
+  FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               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(), false);
+
+  // 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()));
+}
+
+TEST(FuzzerPassDonateModulesTest, DonateCodeThatUsesImageStructField) {
+  std::string recipient_shader = R"(
+               OpCapability Shader
+               OpCapability ImageQuery
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+               OpSourceExtension "GL_EXT_samplerless_texture_functions"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  std::string donor_shader = R"(
+               OpCapability Shader
+               OpCapability ImageQuery
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+               OpSourceExtension "GL_EXT_samplerless_texture_functions"
+               OpName %4 "main"
+               OpName %10 "mySampler"
+               OpName %21 "myTexture"
+               OpName %33 "v"
+               OpDecorate %10 RelaxedPrecision
+               OpDecorate %10 DescriptorSet 0
+               OpDecorate %10 Binding 0
+               OpDecorate %11 RelaxedPrecision
+               OpDecorate %21 RelaxedPrecision
+               OpDecorate %21 DescriptorSet 0
+               OpDecorate %21 Binding 1
+               OpDecorate %22 RelaxedPrecision
+               OpDecorate %34 RelaxedPrecision
+               OpDecorate %40 RelaxedPrecision
+               OpDecorate %42 RelaxedPrecision
+               OpDecorate %43 RelaxedPrecision
+          %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 = OpTypeInt 32 1
+         %13 = OpConstant %12 2
+         %15 = OpTypeVector %12 2
+         %17 = OpTypeInt 32 0
+         %18 = OpConstant %17 0
+         %20 = OpTypePointer UniformConstant %7
+         %21 = OpVariable %20 UniformConstant
+         %23 = OpConstant %12 1
+         %25 = OpConstant %17 1
+         %27 = OpTypeBool
+         %31 = OpTypeVector %6 4
+         %32 = OpTypePointer Function %31
+         %35 = OpConstantComposite %15 %23 %23
+         %36 = OpConstant %12 3
+         %37 = OpConstant %12 4
+         %38 = OpConstantComposite %15 %36 %37
+        %201 = OpTypeStruct %7 %7
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %33 = OpVariable %32 Function
+         %11 = OpLoad %8 %10
+         %14 = OpImage %7 %11
+         %22 = OpLoad %7 %21
+        %200 = OpCompositeConstruct %201 %14 %22
+        %202 = OpCompositeExtract %7 %200 0
+        %203 = OpCompositeExtract %7 %200 1
+         %24 = OpImageQuerySizeLod %15 %203 %23
+         %16 = OpImageQuerySizeLod %15 %202 %13
+         %26 = OpCompositeExtract %12 %24 1
+         %19 = OpCompositeExtract %12 %16 0
+         %28 = OpSGreaterThan %27 %19 %26
+               OpSelectionMerge %30 None
+               OpBranchConditional %28 %29 %41
+         %29 = OpLabel
+         %34 = OpLoad %8 %10
+         %39 = OpImage %7 %34
+         %40 = OpImageFetch %31 %39 %35 Lod|ConstOffset %13 %38
+               OpStore %33 %40
+               OpBranch %30
+         %41 = OpLabel
+         %42 = OpLoad %7 %21
+         %43 = OpImageFetch %31 %42 %35 Lod|ConstOffset %13 %38
+               OpStore %33 %43
+               OpBranch %30
+         %30 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto recipient_context =
+      BuildModule(env, consumer, recipient_shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+
+  const auto donor_context =
+      BuildModule(env, consumer, donor_shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, donor_context.get()));
+
+  FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               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(), false);
+
+  // 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()));
+}
+
+TEST(FuzzerPassDonateModulesTest, DonateCodeThatUsesImageFunctionParameter) {
+  std::string recipient_shader = R"(
+               OpCapability Shader
+               OpCapability ImageQuery
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+               OpSourceExtension "GL_EXT_samplerless_texture_functions"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  std::string donor_shader = R"(
+               OpCapability Shader
+               OpCapability ImageQuery
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+               OpSourceExtension "GL_EXT_samplerless_texture_functions"
+               OpName %4 "main"
+               OpName %10 "mySampler"
+               OpName %21 "myTexture"
+               OpName %33 "v"
+               OpDecorate %10 RelaxedPrecision
+               OpDecorate %10 DescriptorSet 0
+               OpDecorate %10 Binding 0
+               OpDecorate %11 RelaxedPrecision
+               OpDecorate %21 RelaxedPrecision
+               OpDecorate %21 DescriptorSet 0
+               OpDecorate %21 Binding 1
+               OpDecorate %22 RelaxedPrecision
+               OpDecorate %34 RelaxedPrecision
+               OpDecorate %40 RelaxedPrecision
+               OpDecorate %42 RelaxedPrecision
+               OpDecorate %43 RelaxedPrecision
+          %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 = OpTypeInt 32 1
+         %13 = OpConstant %12 2
+         %15 = OpTypeVector %12 2
+         %17 = OpTypeInt 32 0
+         %18 = OpConstant %17 0
+         %20 = OpTypePointer UniformConstant %7
+         %21 = OpVariable %20 UniformConstant
+         %23 = OpConstant %12 1
+         %25 = OpConstant %17 1
+         %27 = OpTypeBool
+         %31 = OpTypeVector %6 4
+         %32 = OpTypePointer Function %31
+         %35 = OpConstantComposite %15 %23 %23
+         %36 = OpConstant %12 3
+         %37 = OpConstant %12 4
+         %38 = OpConstantComposite %15 %36 %37
+        %201 = OpTypeFunction %15 %7 %12
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %33 = OpVariable %32 Function
+         %11 = OpLoad %8 %10
+         %14 = OpImage %7 %11
+         %16 = OpFunctionCall %15 %200 %14 %13
+         %19 = OpCompositeExtract %12 %16 0
+         %22 = OpLoad %7 %21
+         %24 = OpImageQuerySizeLod %15 %22 %23
+         %26 = OpCompositeExtract %12 %24 1
+         %28 = OpSGreaterThan %27 %19 %26
+               OpSelectionMerge %30 None
+               OpBranchConditional %28 %29 %41
+         %29 = OpLabel
+         %34 = OpLoad %8 %10
+         %39 = OpImage %7 %34
+         %40 = OpImageFetch %31 %39 %35 Lod|ConstOffset %13 %38
+               OpStore %33 %40
+               OpBranch %30
+         %41 = OpLabel
+         %42 = OpLoad %7 %21
+         %43 = OpImageFetch %31 %42 %35 Lod|ConstOffset %13 %38
+               OpStore %33 %43
+               OpBranch %30
+         %30 = OpLabel
+               OpReturn
+               OpFunctionEnd
+        %200 = OpFunction %15 None %201
+        %202 = OpFunctionParameter %7
+        %203 = OpFunctionParameter %12
+        %204 = OpLabel
+        %205 = OpImageQuerySizeLod %15 %202 %203
+               OpReturnValue %205
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto recipient_context =
+      BuildModule(env, consumer, recipient_shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+
+  const auto donor_context =
+      BuildModule(env, consumer, donor_shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, donor_context.get()));
+
+  FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               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(), false);
+
+  // 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()));
+}
+
+TEST(FuzzerPassDonateModulesTest, DonateComputeShaderWithRuntimeArray) {
+  std::string recipient_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main"
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  std::string donor_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main"
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 310
+               OpDecorate %9 ArrayStride 4
+               OpMemberDecorate %10 0 Offset 0
+               OpDecorate %10 BufferBlock
+               OpDecorate %12 DescriptorSet 0
+               OpDecorate %12 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpTypeRuntimeArray %6
+         %10 = OpTypeStruct %9
+         %11 = OpTypePointer Uniform %10
+         %12 = OpVariable %11 Uniform
+         %13 = OpTypeInt 32 0
+         %16 = OpConstant %6 0
+         %18 = OpConstant %6 1
+         %20 = OpTypePointer Uniform %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %14 = OpArrayLength %13 %12 0
+         %15 = OpBitcast %6 %14
+               OpStore %8 %15
+         %17 = OpLoad %6 %8
+         %19 = OpISub %6 %17 %18
+         %21 = OpAccessChain %20 %12 %16 %19
+               OpStore %21 %16
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto recipient_context =
+      BuildModule(env, consumer, recipient_shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+
+  const auto donor_context =
+      BuildModule(env, consumer, donor_shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, donor_context.get()));
+
+  FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               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(), false);
+
+  // 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()));
+}
+
+TEST(FuzzerPassDonateModulesTest, DonateComputeShaderWithRuntimeArrayLivesafe) {
+  std::string recipient_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main"
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  std::string donor_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main"
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 310
+               OpDecorate %16 ArrayStride 4
+               OpMemberDecorate %17 0 Offset 0
+               OpDecorate %17 BufferBlock
+               OpDecorate %19 DescriptorSet 0
+               OpDecorate %19 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 0
+         %16 = OpTypeRuntimeArray %6
+         %17 = OpTypeStruct %16
+         %18 = OpTypePointer Uniform %17
+         %19 = OpVariable %18 Uniform
+         %20 = OpTypeInt 32 0
+         %23 = OpTypeBool
+         %26 = OpConstant %6 32
+         %27 = OpTypePointer Uniform %6
+         %30 = 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
+         %21 = OpArrayLength %20 %19 0
+         %22 = OpBitcast %6 %21
+         %24 = OpSLessThan %23 %15 %22
+               OpBranchConditional %24 %11 %12
+         %11 = OpLabel
+         %25 = OpLoad %6 %8
+         %28 = OpAccessChain %27 %19 %9 %25
+               OpStore %28 %26
+               OpBranch %13
+         %13 = OpLabel
+         %29 = OpLoad %6 %8
+         %31 = OpIAdd %6 %29 %30
+               OpStore %8 %31
+               OpBranch %10
+         %12 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto recipient_context =
+      BuildModule(env, consumer, recipient_shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+
+  const auto donor_context =
+      BuildModule(env, consumer, donor_shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, donor_context.get()));
+
+  FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               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(IsValid(env, recipient_context.get()));
+}
+
+TEST(FuzzerPassDonateModulesTest, DonateComputeShaderWithWorkgroupVariables) {
+  std::string recipient_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main"
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  std::string donor_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main"
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Workgroup %6
+          %8 = OpVariable %7 Workgroup
+          %9 = OpConstant %6 2
+         %10 = OpVariable %7 Workgroup
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpStore %8 %9
+         %11 = OpLoad %6 %8
+               OpStore %10 %11
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto recipient_context =
+      BuildModule(env, consumer, recipient_shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+
+  const auto donor_context =
+      BuildModule(env, consumer, donor_shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, donor_context.get()));
+
+  FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               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(IsValid(env, recipient_context.get()));
+}
+
+TEST(FuzzerPassDonateModulesTest, DonateComputeShaderWithAtomics) {
+  std::string recipient_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main"
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  std::string donor_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main"
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 310
+               OpMemberDecorate %9 0 Offset 0
+               OpDecorate %9 BufferBlock
+               OpDecorate %11 DescriptorSet 0
+               OpDecorate %11 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 0
+          %7 = OpTypePointer Function %6
+          %9 = OpTypeStruct %6
+         %10 = OpTypePointer Uniform %9
+         %11 = OpVariable %10 Uniform
+         %12 = OpTypeInt 32 1
+         %13 = OpConstant %12 0
+         %14 = OpTypePointer Uniform %6
+         %16 = OpConstant %6 1
+         %17 = OpConstant %6 0
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %15 = OpAccessChain %14 %11 %13
+         %18 = OpAtomicIAdd %6 %15 %16 %17 %16
+               OpStore %8 %18
+         %19 = OpAccessChain %14 %11 %13
+         %20 = OpLoad %6 %8
+         %21 = OpAtomicUMin %6 %19 %16 %17 %20
+               OpStore %8 %21
+         %22 = OpAccessChain %14 %11 %13
+         %23 = OpLoad %6 %8
+         %24 = OpAtomicUMax %6 %22 %16 %17 %23
+               OpStore %8 %24
+         %25 = OpAccessChain %14 %11 %13
+         %26 = OpLoad %6 %8
+         %27 = OpAtomicAnd %6 %25 %16 %17 %26
+               OpStore %8 %27
+         %28 = OpAccessChain %14 %11 %13
+         %29 = OpLoad %6 %8
+         %30 = OpAtomicOr %6 %28 %16 %17 %29
+               OpStore %8 %30
+         %31 = OpAccessChain %14 %11 %13
+         %32 = OpLoad %6 %8
+         %33 = OpAtomicXor %6 %31 %16 %17 %32
+               OpStore %8 %33
+         %34 = OpAccessChain %14 %11 %13
+         %35 = OpLoad %6 %8
+         %36 = OpAtomicExchange %6 %34 %16 %17 %35
+               OpStore %8 %36
+         %37 = OpAccessChain %14 %11 %13
+         %38 = OpLoad %6 %8
+         %39 = OpAtomicCompareExchange %6 %37 %16 %17 %17 %16 %38
+               OpStore %8 %39
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto recipient_context =
+      BuildModule(env, consumer, recipient_shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+
+  const auto donor_context =
+      BuildModule(env, consumer, donor_shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, donor_context.get()));
+
+  FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               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(IsValid(env, recipient_context.get()));
+}
+
 TEST(FuzzerPassDonateModulesTest, Miscellaneous1) {
   std::string recipient_shader = R"(
                OpCapability Shader
@@ -600,15 +1581,18 @@
   ASSERT_TRUE(IsValid(env, donor_context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0).get(), 100);
   protobufs::TransformationSequence transformation_sequence;
 
-  FuzzerPassDonateModules fuzzer_pass(recipient_context.get(), &fact_manager,
-                                      &fuzzer_context, &transformation_sequence,
-                                      {});
+  FuzzerPassDonateModules fuzzer_pass(recipient_context.get(),
+                                      &transformation_context, &fuzzer_context,
+                                      &transformation_sequence, {});
 
-  fuzzer_pass.DonateSingleModule(donor_context.get());
+  fuzzer_pass.DonateSingleModule(donor_context.get(), false);
 
   // We just check that the result is valid.  Checking to what it should be
   // exactly equal to would be very fragile.
diff --git a/test/fuzz/fuzzer_replayer_test.cpp b/test/fuzz/fuzzer_replayer_test.cpp
index f444695..b9024b9 100644
--- a/test/fuzz/fuzzer_replayer_test.cpp
+++ b/test/fuzz/fuzzer_replayer_test.cpp
@@ -1515,6 +1515,44 @@
                OpFunctionEnd
   )";
 
+// The SPIR-V comes from the following GLSL:
+//
+// #version 310 es
+// precision highp float;
+//
+// layout(location = 0) out vec4 color;
+//
+// void main()
+// {
+//   color = vec4(1.0, 0.0, 0.0, 1.0);
+// }
+
+const std::string kTestShader5 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %9
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %9 "color"
+               OpDecorate %9 Location 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeVector %6 4
+          %8 = OpTypePointer Output %7
+          %9 = OpVariable %8 Output
+         %10 = OpConstant %6 1
+         %11 = OpConstant %6 0
+         %12 = OpConstantComposite %7 %10 %11 %11 %10
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpStore %9 %12
+               OpReturn
+               OpFunctionEnd
+  )";
+
 void AddConstantUniformFact(protobufs::FactSequence* facts,
                             uint32_t descriptor_set, uint32_t binding,
                             std::vector<uint32_t>&& indices, uint32_t value) {
@@ -1552,8 +1590,8 @@
   ASSERT_TRUE(t.Validate(binary_in));
 
   std::vector<fuzzerutil::ModuleSupplier> donor_suppliers;
-  for (auto donor :
-       {&kTestShader1, &kTestShader2, &kTestShader3, &kTestShader4}) {
+  for (auto donor : {&kTestShader1, &kTestShader2, &kTestShader3, &kTestShader4,
+                     &kTestShader5}) {
     donor_suppliers.emplace_back([donor]() {
       return BuildModule(env, kConsoleMessageConsumer, *donor,
                          kFuzzAssembleOption);
@@ -1564,8 +1602,9 @@
     std::vector<uint32_t> fuzzer_binary_out;
     protobufs::TransformationSequence fuzzer_transformation_sequence_out;
 
-    Fuzzer fuzzer(env, seed, true);
-    fuzzer.SetMessageConsumer(kSilentConsumer);
+    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);
@@ -1575,8 +1614,8 @@
     std::vector<uint32_t> replayer_binary_out;
     protobufs::TransformationSequence replayer_transformation_sequence_out;
 
-    Replayer replayer(env, false);
-    replayer.SetMessageConsumer(kSilentConsumer);
+    Replayer replayer(env, false, validator_options);
+    replayer.SetMessageConsumer(kConsoleMessageConsumer);
     auto replayer_result_status = replayer.Run(
         binary_in, initial_facts, fuzzer_transformation_sequence_out,
         &replayer_binary_out, &replayer_transformation_sequence_out);
@@ -1636,6 +1675,13 @@
   RunFuzzerAndReplayer(kTestShader4, facts, 14, kNumFuzzerRuns);
 }
 
+TEST(FuzzerReplayerTest, Miscellaneous5) {
+  // Do some fuzzer runs, starting from an initial seed of 29 (seed value chosen
+  // arbitrarily).
+  RunFuzzerAndReplayer(kTestShader5, protobufs::FactSequence(), 29,
+                       kNumFuzzerRuns);
+}
+
 }  // namespace
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/test/fuzz/fuzzer_shrinker_test.cpp b/test/fuzz/fuzzer_shrinker_test.cpp
index c906a1e..24b4460 100644
--- a/test/fuzz/fuzzer_shrinker_test.cpp
+++ b/test/fuzz/fuzzer_shrinker_test.cpp
@@ -979,15 +979,19 @@
 // The |step_limit| parameter restricts the number of steps that the shrinker
 // will try; it can be set to something small for a faster (but less thorough)
 // test.
+//
+// The |validator_options| parameter provides validator options that should be
+// used during shrinking.
 void RunAndCheckShrinker(
     const spv_target_env& target_env, const std::vector<uint32_t>& binary_in,
     const protobufs::FactSequence& initial_facts,
     const protobufs::TransformationSequence& transformation_sequence_in,
     const Shrinker::InterestingnessFunction& interestingness_function,
     const std::vector<uint32_t>& expected_binary_out,
-    uint32_t expected_transformations_out_size, uint32_t step_limit) {
+    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);
+  Shrinker shrinker(target_env, step_limit, false, validator_options);
   shrinker.SetMessageConsumer(kSilentConsumer);
 
   std::vector<uint32_t> binary_out;
@@ -1035,7 +1039,8 @@
   // 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;
-  Fuzzer fuzzer(env, seed, true);
+  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,
@@ -1048,9 +1053,10 @@
 
   // With the AlwaysInteresting test, we should quickly shrink to the original
   // binary with no transformations remaining.
-  RunAndCheckShrinker(
-      env, binary_in, initial_facts, fuzzer_transformation_sequence_out,
-      AlwaysInteresting().AsFunction(), binary_in, 0, kReasonableStepLimit);
+  RunAndCheckShrinker(env, binary_in, initial_facts,
+                      fuzzer_transformation_sequence_out,
+                      AlwaysInteresting().AsFunction(), binary_in, 0,
+                      kReasonableStepLimit, validator_options);
 
   // With the OnlyInterestingFirstTime test, no shrinking should be achieved.
   RunAndCheckShrinker(
@@ -1058,14 +1064,14 @@
       OnlyInterestingFirstTime().AsFunction(), fuzzer_binary_out,
       static_cast<uint32_t>(
           fuzzer_transformation_sequence_out.transformation_size()),
-      kReasonableStepLimit);
+      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,
-                      PingPong().AsFunction(), {}, 0, kSmallStepLimit);
+  RunAndCheckShrinker(
+      env, binary_in, initial_facts, fuzzer_transformation_sequence_out,
+      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
@@ -1073,7 +1079,7 @@
   RunAndCheckShrinker(
       env, binary_in, initial_facts, fuzzer_transformation_sequence_out,
       InterestingThenRandom(PseudoRandomGenerator(seed)).AsFunction(), {}, 0,
-      kSmallStepLimit);
+      kSmallStepLimit, validator_options);
 }
 
 TEST(FuzzerShrinkerTest, Miscellaneous1) {
diff --git a/test/fuzz/transformation_access_chain_test.cpp b/test/fuzz/transformation_access_chain_test.cpp
new file mode 100644
index 0000000..443c31c
--- /dev/null
+++ b/test/fuzz/transformation_access_chain_test.cpp
@@ -0,0 +1,478 @@
+// 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_access_chain.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationAccessChainTest, BasicTest) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %48 %54
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeVector %6 2
+         %50 = OpTypeMatrix %7 2
+         %70 = OpTypePointer Function %7
+         %71 = OpTypePointer Function %50
+          %8 = OpTypeStruct %7 %6
+          %9 = OpTypePointer Function %8
+         %10 = OpTypeInt 32 1
+         %11 = OpTypePointer Function %10
+         %12 = OpTypeFunction %10 %9 %11
+         %17 = OpConstant %10 0
+         %18 = OpTypeInt 32 0
+         %19 = OpConstant %18 0
+         %20 = OpTypePointer Function %6
+         %99 = OpTypePointer Private %6
+         %29 = OpConstant %6 0
+         %30 = OpConstant %6 1
+         %31 = OpConstantComposite %7 %29 %30
+         %32 = OpConstant %6 2
+         %33 = OpConstantComposite %8 %31 %32
+         %35 = OpConstant %10 10
+         %51 = OpConstant %18 10
+         %80 = OpConstant %18 0
+         %81 = OpConstant %10 1
+         %82 = OpConstant %18 2
+         %83 = OpConstant %10 3
+         %84 = OpConstant %18 4
+         %85 = OpConstant %10 5
+         %52 = OpTypeArray %50 %51
+         %53 = OpTypePointer Private %52
+         %45 = OpUndef %9
+         %46 = OpConstantNull %9
+         %47 = OpTypePointer Private %8
+         %48 = OpVariable %47 Private
+         %54 = OpVariable %53 Private
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %28 = OpVariable %9 Function
+         %34 = OpVariable %11 Function
+         %36 = OpVariable %9 Function
+         %38 = OpVariable %11 Function
+         %44 = OpCopyObject %9 %36
+               OpStore %28 %33
+               OpStore %34 %35
+         %37 = OpLoad %8 %28
+               OpStore %36 %37
+         %39 = OpLoad %10 %34
+               OpStore %38 %39
+         %40 = OpFunctionCall %10 %15 %36 %38
+         %41 = OpLoad %10 %34
+         %42 = OpIAdd %10 %41 %40
+               OpStore %34 %42
+               OpReturn
+               OpFunctionEnd
+         %15 = OpFunction %10 None %12
+         %13 = OpFunctionParameter %9
+         %14 = OpFunctionParameter %11
+         %16 = OpLabel
+         %21 = OpAccessChain %20 %13 %17 %19
+         %43 = OpCopyObject %9 %13
+         %22 = OpLoad %6 %21
+         %23 = OpConvertFToS %10 %22
+         %24 = OpLoad %10 %14
+         %25 = OpIAdd %10 %23 %24
+               OpReturnValue %25
+               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()));
+
+  // Types:
+  // Ptr | Pointee | Storage class | GLSL for pointee    | Ids of this type
+  // ----+---------+---------------+---------------------+------------------
+  //  9  |    8    | Function      | struct(vec2, float) | 28, 36, 44, 13, 43
+  // 11  |   10    | Function      | int                 | 34, 38, 14
+  // 20  |    6    | Function      | float               | -
+  // 99  |    6    | Private       | float               | -
+  // 53  |   52    | Private       | mat2x2[10]          | 54
+  // 47  |    8    | Private       | struct(vec2, float) | 48
+  // 70  |    7    | Function      | vec2                | -
+  // 71  |   59    | Function      | mat2x2              | -
+
+  // Indices 0-5 are in ids 80-85
+
+  FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
+  transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
+      54);
+
+  // Bad: id is not fresh
+  ASSERT_FALSE(TransformationAccessChain(
+                   43, 43, {80}, MakeInstructionDescriptor(24, SpvOpLoad, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: pointer id does not exist
+  ASSERT_FALSE(TransformationAccessChain(
+                   100, 1000, {80}, MakeInstructionDescriptor(24, SpvOpLoad, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: pointer id is not a type
+  ASSERT_FALSE(TransformationAccessChain(
+                   100, 5, {80}, MakeInstructionDescriptor(24, SpvOpLoad, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: pointer id is not a pointer
+  ASSERT_FALSE(TransformationAccessChain(
+                   100, 23, {80}, MakeInstructionDescriptor(24, SpvOpLoad, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: index id does not exist
+  ASSERT_FALSE(TransformationAccessChain(
+                   100, 43, {1000}, MakeInstructionDescriptor(24, SpvOpLoad, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: index id is not a constant
+  ASSERT_FALSE(TransformationAccessChain(
+                   100, 43, {24}, MakeInstructionDescriptor(25, SpvOpIAdd, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: too many indices
+  ASSERT_FALSE(
+      TransformationAccessChain(100, 43, {80, 80, 80},
+                                MakeInstructionDescriptor(24, SpvOpLoad, 0))
+          .IsApplicable(context.get(), transformation_context));
+
+  // Bad: index id is out of bounds
+  ASSERT_FALSE(
+      TransformationAccessChain(100, 43, {80, 83},
+                                MakeInstructionDescriptor(24, SpvOpLoad, 0))
+          .IsApplicable(context.get(), transformation_context));
+
+  // Bad: attempt to insert before variable
+  ASSERT_FALSE(TransformationAccessChain(
+                   100, 34, {}, MakeInstructionDescriptor(36, SpvOpVariable, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: pointer not available
+  ASSERT_FALSE(
+      TransformationAccessChain(
+          100, 43, {80}, MakeInstructionDescriptor(21, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
+
+  // Bad: instruction descriptor does not identify anything
+  ASSERT_FALSE(TransformationAccessChain(
+                   100, 43, {80}, MakeInstructionDescriptor(24, SpvOpLoad, 100))
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: pointer is null
+  ASSERT_FALSE(TransformationAccessChain(
+                   100, 45, {80}, MakeInstructionDescriptor(24, SpvOpLoad, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: pointer is undef
+  ASSERT_FALSE(TransformationAccessChain(
+                   100, 46, {80}, MakeInstructionDescriptor(24, SpvOpLoad, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: pointer to result type does not exist
+  ASSERT_FALSE(TransformationAccessChain(
+                   100, 52, {0}, MakeInstructionDescriptor(24, SpvOpLoad, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  {
+    TransformationAccessChain transformation(
+        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()));
+    ASSERT_FALSE(
+        transformation_context.GetFactManager()->PointeeValueIsIrrelevant(100));
+  }
+
+  {
+    TransformationAccessChain transformation(
+        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()));
+    ASSERT_FALSE(
+        transformation_context.GetFactManager()->PointeeValueIsIrrelevant(101));
+  }
+
+  {
+    TransformationAccessChain transformation(
+        102, 36, {80, 81}, MakeInstructionDescriptor(37, SpvOpStore, 0));
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    transformation.Apply(context.get(), &transformation_context);
+    ASSERT_TRUE(IsValid(env, context.get()));
+    ASSERT_FALSE(
+        transformation_context.GetFactManager()->PointeeValueIsIrrelevant(102));
+  }
+
+  {
+    TransformationAccessChain transformation(
+        103, 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()));
+    ASSERT_FALSE(
+        transformation_context.GetFactManager()->PointeeValueIsIrrelevant(103));
+  }
+
+  {
+    TransformationAccessChain transformation(
+        104, 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()));
+    ASSERT_FALSE(
+        transformation_context.GetFactManager()->PointeeValueIsIrrelevant(104));
+  }
+
+  {
+    TransformationAccessChain transformation(
+        105, 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()));
+    ASSERT_FALSE(
+        transformation_context.GetFactManager()->PointeeValueIsIrrelevant(105));
+  }
+
+  {
+    TransformationAccessChain transformation(
+        106, 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()));
+    ASSERT_FALSE(
+        transformation_context.GetFactManager()->PointeeValueIsIrrelevant(106));
+  }
+
+  {
+    TransformationAccessChain transformation(
+        107, 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()));
+    ASSERT_FALSE(
+        transformation_context.GetFactManager()->PointeeValueIsIrrelevant(107));
+  }
+
+  {
+    TransformationAccessChain transformation(
+        108, 54, {85, 81, 81}, MakeInstructionDescriptor(24, SpvOpLoad, 0));
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    transformation.Apply(context.get(), &transformation_context);
+    ASSERT_TRUE(IsValid(env, context.get()));
+    ASSERT_TRUE(
+        transformation_context.GetFactManager()->PointeeValueIsIrrelevant(108));
+  }
+
+  {
+    TransformationAccessChain transformation(
+        109, 48, {80, 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()));
+    ASSERT_FALSE(
+        transformation_context.GetFactManager()->PointeeValueIsIrrelevant(109));
+  }
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %48 %54
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeVector %6 2
+         %50 = OpTypeMatrix %7 2
+         %70 = OpTypePointer Function %7
+         %71 = OpTypePointer Function %50
+          %8 = OpTypeStruct %7 %6
+          %9 = OpTypePointer Function %8
+         %10 = OpTypeInt 32 1
+         %11 = OpTypePointer Function %10
+         %12 = OpTypeFunction %10 %9 %11
+         %17 = OpConstant %10 0
+         %18 = OpTypeInt 32 0
+         %19 = OpConstant %18 0
+         %20 = OpTypePointer Function %6
+         %99 = OpTypePointer Private %6
+         %29 = OpConstant %6 0
+         %30 = OpConstant %6 1
+         %31 = OpConstantComposite %7 %29 %30
+         %32 = OpConstant %6 2
+         %33 = OpConstantComposite %8 %31 %32
+         %35 = OpConstant %10 10
+         %51 = OpConstant %18 10
+         %80 = OpConstant %18 0
+         %81 = OpConstant %10 1
+         %82 = OpConstant %18 2
+         %83 = OpConstant %10 3
+         %84 = OpConstant %18 4
+         %85 = OpConstant %10 5
+         %52 = OpTypeArray %50 %51
+         %53 = OpTypePointer Private %52
+         %45 = OpUndef %9
+         %46 = OpConstantNull %9
+         %47 = OpTypePointer Private %8
+         %48 = OpVariable %47 Private
+         %54 = OpVariable %53 Private
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %28 = OpVariable %9 Function
+         %34 = OpVariable %11 Function
+         %36 = OpVariable %9 Function
+         %38 = OpVariable %11 Function
+         %44 = OpCopyObject %9 %36
+        %103 = OpAccessChain %9 %44
+               OpStore %28 %33
+        %105 = OpAccessChain %11 %34
+               OpStore %34 %35
+         %37 = OpLoad %8 %28
+        %102 = OpAccessChain %20 %36 %80 %81
+               OpStore %36 %37
+         %39 = OpLoad %10 %34
+               OpStore %38 %39
+        %106 = OpAccessChain %11 %38
+         %40 = OpFunctionCall %10 %15 %36 %38
+         %41 = OpLoad %10 %34
+         %42 = OpIAdd %10 %41 %40
+               OpStore %34 %42
+        %101 = OpAccessChain %20 %28 %81
+               OpReturn
+               OpFunctionEnd
+         %15 = OpFunction %10 None %12
+         %13 = OpFunctionParameter %9
+         %14 = OpFunctionParameter %11
+         %16 = OpLabel
+        %104 = OpAccessChain %70 %13 %80
+         %21 = OpAccessChain %20 %13 %17 %19
+         %43 = OpCopyObject %9 %13
+         %22 = OpLoad %6 %21
+         %23 = OpConvertFToS %10 %22
+        %100 = OpAccessChain %70 %43 %80
+        %107 = OpAccessChain %11 %14
+        %108 = OpAccessChain %99 %54 %85 %81 %81
+        %109 = OpAccessChain %99 %48 %80 %80
+         %24 = OpLoad %10 %14
+         %25 = OpIAdd %10 %23 %24
+               OpReturnValue %25
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationAccessChainTest, IsomorphicStructs) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %11 %12
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeStruct %6
+          %8 = OpTypePointer Private %7
+          %9 = OpTypeStruct %6
+         %10 = OpTypePointer Private %9
+         %11 = OpVariable %8 Private
+         %12 = OpVariable %10 Private
+          %4 = OpFunction %2 None %3
+          %5 = 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;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               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()));
+  }
+  {
+    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()));
+  }
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %11 %12
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeStruct %6
+          %8 = OpTypePointer Private %7
+          %9 = OpTypeStruct %6
+         %10 = OpTypePointer Private %9
+         %11 = OpVariable %8 Private
+         %12 = OpVariable %10 Private
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+        %100 = OpAccessChain %8 %11
+        %101 = OpAccessChain %10 %12
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+}  // 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 f51c46b..c603333 100644
--- a/test/fuzz/transformation_add_constant_boolean_test.cpp
+++ b/test/fuzz/transformation_add_constant_boolean_test.cpp
@@ -43,42 +43,47 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   // True and false can both be added as neither is present.
   ASSERT_TRUE(TransformationAddConstantBoolean(7, true).IsApplicable(
-      context.get(), fact_manager));
+      context.get(), transformation_context));
   ASSERT_TRUE(TransformationAddConstantBoolean(7, false).IsApplicable(
-      context.get(), fact_manager));
+      context.get(), transformation_context));
 
   // Id 5 is already taken.
   ASSERT_FALSE(TransformationAddConstantBoolean(5, true).IsApplicable(
-      context.get(), fact_manager));
+      context.get(), transformation_context));
 
   auto add_true = TransformationAddConstantBoolean(7, true);
   auto add_false = TransformationAddConstantBoolean(8, false);
 
-  ASSERT_TRUE(add_true.IsApplicable(context.get(), fact_manager));
-  add_true.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(add_true.IsApplicable(context.get(), transformation_context));
+  add_true.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // Having added true, we cannot add it again with the same id.
-  ASSERT_FALSE(add_true.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(add_true.IsApplicable(context.get(), transformation_context));
   // But we can add it with a different id.
   auto add_true_again = TransformationAddConstantBoolean(100, true);
-  ASSERT_TRUE(add_true_again.IsApplicable(context.get(), fact_manager));
-  add_true_again.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      add_true_again.IsApplicable(context.get(), transformation_context));
+  add_true_again.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(add_false.IsApplicable(context.get(), fact_manager));
-  add_false.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(add_false.IsApplicable(context.get(), transformation_context));
+  add_false.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // Having added false, we cannot add it again with the same id.
-  ASSERT_FALSE(add_false.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(add_false.IsApplicable(context.get(), transformation_context));
   // But we can add it with a different id.
   auto add_false_again = TransformationAddConstantBoolean(101, false);
-  ASSERT_TRUE(add_false_again.IsApplicable(context.get(), fact_manager));
-  add_false_again.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      add_false_again.IsApplicable(context.get(), transformation_context));
+  add_false_again.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   std::string after_transformation = R"(
@@ -128,12 +133,15 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   // Neither true nor false can be added as OpTypeBool is not present.
   ASSERT_FALSE(TransformationAddConstantBoolean(6, true).IsApplicable(
-      context.get(), fact_manager));
+      context.get(), transformation_context));
   ASSERT_FALSE(TransformationAddConstantBoolean(6, false).IsApplicable(
-      context.get(), fact_manager));
+      context.get(), transformation_context));
 }
 
 }  // namespace
diff --git a/test/fuzz/transformation_add_constant_composite_test.cpp b/test/fuzz/transformation_add_constant_composite_test.cpp
index 5ce171b..021bf58 100644
--- a/test/fuzz/transformation_add_constant_composite_test.cpp
+++ b/test/fuzz/transformation_add_constant_composite_test.cpp
@@ -64,19 +64,22 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   // Too few ids
   ASSERT_FALSE(TransformationAddConstantComposite(103, 8, {100, 101})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   // Too many ids
   ASSERT_FALSE(TransformationAddConstantComposite(101, 7, {14, 15, 14})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   // Id already in use
   ASSERT_FALSE(TransformationAddConstantComposite(40, 7, {11, 12})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   // %39 is not a type
   ASSERT_FALSE(TransformationAddConstantComposite(100, 39, {11, 12})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   TransformationAddConstantComposite transformations[] = {
       // %100 = OpConstantComposite %7 %11 %12
@@ -101,8 +104,9 @@
       TransformationAddConstantComposite(106, 35, {38, 39, 40})};
 
   for (auto& transformation : transformations) {
-    ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
-    transformation.Apply(context.get(), &fact_manager);
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    transformation.Apply(context.get(), &transformation_context);
   }
   ASSERT_TRUE(IsValid(env, context.get()));
 
diff --git a/test/fuzz/transformation_add_constant_null_test.cpp b/test/fuzz/transformation_add_constant_null_test.cpp
new file mode 100644
index 0000000..0bfee34
--- /dev/null
+++ b/test/fuzz/transformation_add_constant_null_test.cpp
@@ -0,0 +1,140 @@
+// 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_constant_null.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationAddConstantNullTest, 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 = OpTypeInt 32 1
+          %8 = OpTypeVector %6 2
+          %9 = OpTypeVector %6 3
+         %10 = OpTypeVector %6 4
+         %11 = OpTypeVector %7 2
+         %20 = OpTypeSampler
+         %21 = OpTypeImage %6 2D 0 0 0 0 Rgba32f
+         %22 = OpTypeSampledImage %21
+          %4 = OpFunction %2 None %3
+          %5 = 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;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
+  // Id already in use
+  ASSERT_FALSE(TransformationAddConstantNull(4, 11).IsApplicable(
+      context.get(), transformation_context));
+  // %1 is not a type
+  ASSERT_FALSE(TransformationAddConstantNull(100, 1).IsApplicable(
+      context.get(), transformation_context));
+
+  // %3 is a function type
+  ASSERT_FALSE(TransformationAddConstantNull(100, 3).IsApplicable(
+      context.get(), transformation_context));
+
+  // %20 is a sampler type
+  ASSERT_FALSE(TransformationAddConstantNull(100, 20).IsApplicable(
+      context.get(), transformation_context));
+
+  // %21 is an image type
+  ASSERT_FALSE(TransformationAddConstantNull(100, 21).IsApplicable(
+      context.get(), transformation_context));
+
+  // %22 is a sampled image type
+  ASSERT_FALSE(TransformationAddConstantNull(100, 22).IsApplicable(
+      context.get(), transformation_context));
+
+  TransformationAddConstantNull transformations[] = {
+      // %100 = OpConstantNull %6
+      TransformationAddConstantNull(100, 6),
+
+      // %101 = OpConstantNull %7
+      TransformationAddConstantNull(101, 7),
+
+      // %102 = OpConstantNull %8
+      TransformationAddConstantNull(102, 8),
+
+      // %103 = OpConstantNull %9
+      TransformationAddConstantNull(103, 9),
+
+      // %104 = OpConstantNull %10
+      TransformationAddConstantNull(104, 10),
+
+      // %105 = OpConstantNull %11
+      TransformationAddConstantNull(105, 11)};
+
+  for (auto& transformation : transformations) {
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    transformation.Apply(context.get(), &transformation_context);
+  }
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeInt 32 1
+          %8 = OpTypeVector %6 2
+          %9 = OpTypeVector %6 3
+         %10 = OpTypeVector %6 4
+         %11 = OpTypeVector %7 2
+         %20 = OpTypeSampler
+         %21 = OpTypeImage %6 2D 0 0 0 0 Rgba32f
+         %22 = OpTypeSampledImage %21
+        %100 = OpConstantNull %6
+        %101 = OpConstantNull %7
+        %102 = OpConstantNull %8
+        %103 = OpConstantNull %9
+        %104 = OpConstantNull %10
+        %105 = OpConstantNull %11
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/transformation_add_constant_scalar_test.cpp b/test/fuzz/transformation_add_constant_scalar_test.cpp
index b156111..5124b7d 100644
--- a/test/fuzz/transformation_add_constant_scalar_test.cpp
+++ b/test/fuzz/transformation_add_constant_scalar_test.cpp
@@ -62,6 +62,9 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  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];
@@ -87,55 +90,62 @@
   auto bad_type_id_is_pointer = TransformationAddConstantScalar(111, 11, {0});
 
   // Id is already in use.
-  ASSERT_FALSE(bad_id_already_used.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      bad_id_already_used.IsApplicable(context.get(), transformation_context));
 
   // At least one word of data must be provided.
-  ASSERT_FALSE(bad_no_data.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(bad_no_data.IsApplicable(context.get(), transformation_context));
 
   // Cannot give two data words for a 32-bit type.
-  ASSERT_FALSE(bad_too_much_data.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      bad_too_much_data.IsApplicable(context.get(), transformation_context));
 
   // Type id does not exist
-  ASSERT_FALSE(
-      bad_type_id_does_not_exist.IsApplicable(context.get(), fact_manager));
+  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(), fact_manager));
+  ASSERT_FALSE(bad_type_id_is_not_a_type.IsApplicable(context.get(),
+                                                      transformation_context));
 
   // Type id is void
-  ASSERT_FALSE(bad_type_id_is_void.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      bad_type_id_is_void.IsApplicable(context.get(), transformation_context));
 
   // Type id is pointer
-  ASSERT_FALSE(
-      bad_type_id_is_pointer.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(bad_type_id_is_pointer.IsApplicable(context.get(),
+                                                   transformation_context));
 
-  ASSERT_TRUE(add_signed_int_1.IsApplicable(context.get(), fact_manager));
-  add_signed_int_1.Apply(context.get(), &fact_manager);
+  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()));
 
-  ASSERT_TRUE(add_signed_int_10.IsApplicable(context.get(), fact_manager));
-  add_signed_int_10.Apply(context.get(), &fact_manager);
+  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()));
 
-  ASSERT_TRUE(add_unsigned_int_2.IsApplicable(context.get(), fact_manager));
-  add_unsigned_int_2.Apply(context.get(), &fact_manager);
+  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()));
 
-  ASSERT_TRUE(add_unsigned_int_20.IsApplicable(context.get(), fact_manager));
-  add_unsigned_int_20.Apply(context.get(), &fact_manager);
+  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()));
 
-  ASSERT_TRUE(add_float_3.IsApplicable(context.get(), fact_manager));
-  add_float_3.Apply(context.get(), &fact_manager);
+  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(), fact_manager));
-  add_float_30.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(add_float_30.IsApplicable(context.get(), transformation_context));
+  add_float_30.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_FALSE(bad_add_float_30_id_already_used.IsApplicable(context.get(),
-                                                             fact_manager));
+  ASSERT_FALSE(bad_add_float_30_id_already_used.IsApplicable(
+      context.get(), transformation_context));
 
   std::string after_transformation = R"(
                OpCapability Shader
diff --git a/test/fuzz/transformation_add_dead_block_test.cpp b/test/fuzz/transformation_add_dead_block_test.cpp
index f89140f..c9be520 100644
--- a/test/fuzz/transformation_add_dead_block_test.cpp
+++ b/test/fuzz/transformation_add_dead_block_test.cpp
@@ -46,21 +46,25 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   // Id 4 is already in use
   ASSERT_FALSE(TransformationAddDeadBlock(4, 5, true)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // Id 7 is not a block
   ASSERT_FALSE(TransformationAddDeadBlock(100, 7, true)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   TransformationAddDeadBlock transformation(100, 5, true);
-  ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
-  transformation.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  transformation.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(fact_manager.BlockIsDead(100));
+  ASSERT_TRUE(transformation_context.GetFactManager()->BlockIsDead(100));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -119,9 +123,12 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   ASSERT_FALSE(TransformationAddDeadBlock(100, 9, true)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationAddDeadBlockTest, TargetBlockMustNotBeLoopMergeOrContinue) {
@@ -160,13 +167,16 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   // Bad because 9's successor is the loop continue target.
   ASSERT_FALSE(TransformationAddDeadBlock(100, 9, true)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   // Bad because 10's successor is the loop merge.
   ASSERT_FALSE(TransformationAddDeadBlock(100, 10, true)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationAddDeadBlockTest, SourceBlockMustNotBeLoopHead) {
@@ -203,10 +213,13 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   // Bad because 8 is a loop head.
   ASSERT_FALSE(TransformationAddDeadBlock(100, 8, true)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationAddDeadBlockTest, OpPhiInTarget) {
@@ -240,13 +253,17 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   TransformationAddDeadBlock transformation(100, 5, true);
-  ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
-  transformation.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  transformation.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(fact_manager.BlockIsDead(100));
+  ASSERT_TRUE(transformation_context.GetFactManager()->BlockIsDead(100));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -309,11 +326,14 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               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)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 }
 
 }  // namespace
diff --git a/test/fuzz/transformation_add_dead_break_test.cpp b/test/fuzz/transformation_add_dead_break_test.cpp
index 1dd0c9d..8400b0c 100644
--- a/test/fuzz/transformation_add_dead_break_test.cpp
+++ b/test/fuzz/transformation_add_dead_break_test.cpp
@@ -100,44 +100,47 @@
   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);
 
   const uint32_t merge_block = 16;
 
   // These are all possibilities.
   ASSERT_TRUE(TransformationAddDeadBreak(15, merge_block, true, {})
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationAddDeadBreak(15, merge_block, false, {})
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationAddDeadBreak(21, merge_block, true, {})
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationAddDeadBreak(21, merge_block, false, {})
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationAddDeadBreak(22, merge_block, true, {})
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationAddDeadBreak(22, merge_block, false, {})
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationAddDeadBreak(19, merge_block, true, {})
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationAddDeadBreak(19, merge_block, false, {})
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationAddDeadBreak(23, merge_block, true, {})
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationAddDeadBreak(23, merge_block, false, {})
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationAddDeadBreak(24, merge_block, true, {})
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationAddDeadBreak(24, merge_block, false, {})
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
 
   // Inapplicable: 100 is not a block id.
   ASSERT_FALSE(TransformationAddDeadBreak(100, merge_block, true, {})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationAddDeadBreak(15, 100, true, {})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // Inapplicable: 24 is not a merge block.
   ASSERT_FALSE(TransformationAddDeadBreak(15, 24, true, {})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // These are the transformations we will apply.
   auto transformation1 = TransformationAddDeadBreak(15, merge_block, true, {});
@@ -147,28 +150,34 @@
   auto transformation5 = TransformationAddDeadBreak(23, merge_block, true, {});
   auto transformation6 = TransformationAddDeadBreak(24, merge_block, false, {});
 
-  ASSERT_TRUE(transformation1.IsApplicable(context.get(), fact_manager));
-  transformation1.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation1.IsApplicable(context.get(), transformation_context));
+  transformation1.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(transformation2.IsApplicable(context.get(), fact_manager));
-  transformation2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation2.IsApplicable(context.get(), transformation_context));
+  transformation2.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(transformation3.IsApplicable(context.get(), fact_manager));
-  transformation3.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation3.IsApplicable(context.get(), transformation_context));
+  transformation3.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(transformation4.IsApplicable(context.get(), fact_manager));
-  transformation4.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation4.IsApplicable(context.get(), transformation_context));
+  transformation4.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(transformation5.IsApplicable(context.get(), fact_manager));
-  transformation5.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation5.IsApplicable(context.get(), transformation_context));
+  transformation5.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(transformation6.IsApplicable(context.get(), fact_manager));
-  transformation6.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation6.IsApplicable(context.get(), transformation_context));
+  transformation6.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   std::string after_transformation = R"(
@@ -333,6 +342,9 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   // The header and merge blocks
   const uint32_t header_inner = 34;
@@ -354,53 +366,53 @@
 
   // Fine to break from a construct to its merge
   ASSERT_TRUE(TransformationAddDeadBreak(inner_block_1, merge_inner, true, {})
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationAddDeadBreak(inner_block_2, merge_inner, false, {})
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationAddDeadBreak(outer_block_1, merge_outer, true, {})
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationAddDeadBreak(outer_block_2, merge_outer, false, {})
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationAddDeadBreak(outer_block_3, merge_outer, true, {})
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationAddDeadBreak(outer_block_4, merge_outer, false, {})
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationAddDeadBreak(after_block_1, merge_after, true, {})
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationAddDeadBreak(after_block_2, merge_after, false, {})
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
 
   // Not OK to break to the wrong merge (whether enclosing or not)
   ASSERT_FALSE(TransformationAddDeadBreak(inner_block_1, merge_outer, true, {})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationAddDeadBreak(inner_block_2, merge_after, false, {})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationAddDeadBreak(outer_block_1, merge_inner, true, {})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationAddDeadBreak(outer_block_2, merge_after, false, {})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationAddDeadBreak(after_block_1, merge_inner, true, {})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationAddDeadBreak(after_block_2, merge_outer, false, {})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // Not OK to break from header (as it does not branch unconditionally)
   ASSERT_FALSE(TransformationAddDeadBreak(header_inner, merge_inner, true, {})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationAddDeadBreak(header_outer, merge_outer, false, {})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationAddDeadBreak(header_after, merge_after, true, {})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // Not OK to break to non-merge
   ASSERT_FALSE(
       TransformationAddDeadBreak(inner_block_1, inner_block_2, true, {})
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       TransformationAddDeadBreak(outer_block_2, after_block_1, false, {})
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationAddDeadBreak(outer_block_1, header_after, true, {})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   auto transformation1 =
       TransformationAddDeadBreak(inner_block_1, merge_inner, true, {});
@@ -419,36 +431,44 @@
   auto transformation8 =
       TransformationAddDeadBreak(after_block_2, merge_after, false, {});
 
-  ASSERT_TRUE(transformation1.IsApplicable(context.get(), fact_manager));
-  transformation1.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation1.IsApplicable(context.get(), transformation_context));
+  transformation1.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(transformation2.IsApplicable(context.get(), fact_manager));
-  transformation2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation2.IsApplicable(context.get(), transformation_context));
+  transformation2.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(transformation3.IsApplicable(context.get(), fact_manager));
-  transformation3.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation3.IsApplicable(context.get(), transformation_context));
+  transformation3.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(transformation4.IsApplicable(context.get(), fact_manager));
-  transformation4.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation4.IsApplicable(context.get(), transformation_context));
+  transformation4.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(transformation5.IsApplicable(context.get(), fact_manager));
-  transformation5.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation5.IsApplicable(context.get(), transformation_context));
+  transformation5.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(transformation6.IsApplicable(context.get(), fact_manager));
-  transformation6.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation6.IsApplicable(context.get(), transformation_context));
+  transformation6.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(transformation7.IsApplicable(context.get(), fact_manager));
-  transformation7.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation7.IsApplicable(context.get(), transformation_context));
+  transformation7.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(transformation8.IsApplicable(context.get(), fact_manager));
-  transformation8.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation8.IsApplicable(context.get(), transformation_context));
+  transformation8.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   std::string after_transformation = R"(
@@ -685,6 +705,9 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   // The header and merge blocks
   const uint32_t header_outer_if = 5;
@@ -715,63 +738,63 @@
   // Fine to branch straight to direct merge block for a construct
   ASSERT_TRUE(TransformationAddDeadBreak(then_outer_switch_block_1,
                                          merge_then_outer_switch, true, {})
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationAddDeadBreak(then_inner_switch_block_1,
                                          merge_then_inner_switch, false, {})
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationAddDeadBreak(then_inner_switch_block_2,
                                          merge_then_inner_switch, true, {})
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationAddDeadBreak(then_inner_switch_block_3,
                                          merge_then_inner_switch, true, {})
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationAddDeadBreak(else_switch_block_1, merge_else_switch,
                                          false, {})
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationAddDeadBreak(else_switch_block_2, merge_else_switch,
                                          true, {})
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationAddDeadBreak(else_switch_block_3, merge_else_switch,
                                          false, {})
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(
       TransformationAddDeadBreak(inner_if_1_block_1, merge_inner_if_1, true, {})
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationAddDeadBreak(inner_if_1_block_2, merge_inner_if_1,
                                          false, {})
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(
       TransformationAddDeadBreak(inner_if_2_block_1, merge_inner_if_2, true, {})
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
 
   // Not OK to break out of a switch from a selection construct inside the
   // switch.
   ASSERT_FALSE(TransformationAddDeadBreak(inner_if_1_block_1,
                                           merge_then_outer_switch, true, {})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationAddDeadBreak(inner_if_1_block_2,
                                           merge_then_outer_switch, false, {})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationAddDeadBreak(inner_if_2_block_1,
                                           merge_then_outer_switch, true, {})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // Some miscellaneous inapplicable cases.
   ASSERT_FALSE(
       TransformationAddDeadBreak(header_outer_if, merge_outer_if, true, {})
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationAddDeadBreak(header_inner_if_1, inner_if_1_block_2,
                                           false, {})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationAddDeadBreak(header_then_inner_switch,
                                           header_then_outer_switch, false, {})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationAddDeadBreak(header_else_switch,
                                           then_inner_switch_block_3, false, {})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationAddDeadBreak(header_inner_if_2, header_inner_if_2,
                                           false, {})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   auto transformation1 = TransformationAddDeadBreak(
       then_outer_switch_block_1, merge_then_outer_switch, true, {});
@@ -794,44 +817,54 @@
   auto transformation10 = TransformationAddDeadBreak(
       inner_if_2_block_1, merge_inner_if_2, true, {});
 
-  ASSERT_TRUE(transformation1.IsApplicable(context.get(), fact_manager));
-  transformation1.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation1.IsApplicable(context.get(), transformation_context));
+  transformation1.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(transformation2.IsApplicable(context.get(), fact_manager));
-  transformation2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation2.IsApplicable(context.get(), transformation_context));
+  transformation2.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(transformation3.IsApplicable(context.get(), fact_manager));
-  transformation3.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation3.IsApplicable(context.get(), transformation_context));
+  transformation3.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(transformation4.IsApplicable(context.get(), fact_manager));
-  transformation4.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation4.IsApplicable(context.get(), transformation_context));
+  transformation4.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(transformation5.IsApplicable(context.get(), fact_manager));
-  transformation5.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation5.IsApplicable(context.get(), transformation_context));
+  transformation5.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(transformation6.IsApplicable(context.get(), fact_manager));
-  transformation6.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation6.IsApplicable(context.get(), transformation_context));
+  transformation6.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(transformation7.IsApplicable(context.get(), fact_manager));
-  transformation7.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation7.IsApplicable(context.get(), transformation_context));
+  transformation7.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(transformation8.IsApplicable(context.get(), fact_manager));
-  transformation8.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation8.IsApplicable(context.get(), transformation_context));
+  transformation8.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(transformation9.IsApplicable(context.get(), fact_manager));
-  transformation9.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation9.IsApplicable(context.get(), transformation_context));
+  transformation9.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(transformation10.IsApplicable(context.get(), fact_manager));
-  transformation10.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation10.IsApplicable(context.get(), transformation_context));
+  transformation10.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   std::string after_transformation = R"(
@@ -1094,6 +1127,9 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   // The header and merge blocks
   const uint32_t header_do_while = 6;
@@ -1123,75 +1159,75 @@
   // Fine to break from any loop header to its merge
   ASSERT_TRUE(
       TransformationAddDeadBreak(header_do_while, merge_do_while, true, {})
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationAddDeadBreak(header_for_i, merge_for_i, false, {})
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationAddDeadBreak(header_for_j, merge_for_j, true, {})
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
 
   // Fine to break from any of the blocks in constructs in the "for j" loop to
   // that loop's merge
   ASSERT_TRUE(
       TransformationAddDeadBreak(block_in_inner_if, merge_for_j, false, {})
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(
       TransformationAddDeadBreak(block_switch_case, merge_for_j, true, {})
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(
       TransformationAddDeadBreak(block_switch_default, merge_for_j, false, {})
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
 
   // Fine to break from the body of the "for i" loop to that loop's merge
   ASSERT_TRUE(
       TransformationAddDeadBreak(block_in_for_i_loop, merge_for_i, true, {})
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
 
   // Not OK to break from multiple loops
   ASSERT_FALSE(
       TransformationAddDeadBreak(block_in_inner_if, merge_do_while, false, {})
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       TransformationAddDeadBreak(block_switch_case, merge_do_while, true, {})
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationAddDeadBreak(block_switch_default, merge_do_while,
                                           false, {})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       TransformationAddDeadBreak(header_for_j, merge_do_while, true, {})
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
 
   // Not OK to break loop from its continue construct
   ASSERT_FALSE(
       TransformationAddDeadBreak(continue_do_while, merge_do_while, true, {})
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       TransformationAddDeadBreak(continue_for_j, merge_for_j, false, {})
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationAddDeadBreak(continue_for_i, merge_for_i, true, {})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // Not OK to break out of multiple non-loop constructs if not breaking to a
   // loop merge
   ASSERT_FALSE(
       TransformationAddDeadBreak(block_in_inner_if, merge_if_x_eq_y, false, {})
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       TransformationAddDeadBreak(block_switch_case, merge_if_x_eq_y, true, {})
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationAddDeadBreak(block_switch_default, merge_if_x_eq_y,
                                           false, {})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // Some miscellaneous inapplicable transformations
   ASSERT_FALSE(
       TransformationAddDeadBreak(header_if_x_eq_2, header_if_x_eq_y, false, {})
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       TransformationAddDeadBreak(merge_if_x_eq_2, merge_switch, false, {})
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       TransformationAddDeadBreak(header_switch, header_switch, false, {})
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
 
   auto transformation1 =
       TransformationAddDeadBreak(header_do_while, merge_do_while, true, {});
@@ -1208,32 +1244,39 @@
   auto transformation7 =
       TransformationAddDeadBreak(block_in_for_i_loop, merge_for_i, true, {});
 
-  ASSERT_TRUE(transformation1.IsApplicable(context.get(), fact_manager));
-  transformation1.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation1.IsApplicable(context.get(), transformation_context));
+  transformation1.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(transformation2.IsApplicable(context.get(), fact_manager));
-  transformation2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation2.IsApplicable(context.get(), transformation_context));
+  transformation2.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(transformation3.IsApplicable(context.get(), fact_manager));
-  transformation3.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation3.IsApplicable(context.get(), transformation_context));
+  transformation3.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(transformation4.IsApplicable(context.get(), fact_manager));
-  transformation4.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation4.IsApplicable(context.get(), transformation_context));
+  transformation4.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(transformation5.IsApplicable(context.get(), fact_manager));
-  transformation5.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation5.IsApplicable(context.get(), transformation_context));
+  transformation5.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(transformation6.IsApplicable(context.get(), fact_manager));
-  transformation6.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation6.IsApplicable(context.get(), transformation_context));
+  transformation6.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(transformation7.IsApplicable(context.get(), fact_manager));
-  transformation7.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation7.IsApplicable(context.get(), transformation_context));
+  transformation7.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   std::string after_transformation = R"(
@@ -1421,12 +1464,15 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   // Not OK to break loop from its continue construct
   ASSERT_FALSE(TransformationAddDeadBreak(13, 12, true, {})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationAddDeadBreak(23, 12, true, {})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationAddDeadBreakTest, SelectionInContinueConstruct) {
@@ -1509,6 +1555,9 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   const uint32_t loop_merge = 12;
   const uint32_t selection_merge = 24;
@@ -1520,13 +1569,13 @@
   // Not OK to jump from the selection to the loop merge, as this would break
   // from the loop's continue construct.
   ASSERT_FALSE(TransformationAddDeadBreak(in_selection_1, loop_merge, true, {})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationAddDeadBreak(in_selection_2, loop_merge, true, {})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationAddDeadBreak(in_selection_3, loop_merge, true, {})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationAddDeadBreak(in_selection_4, loop_merge, true, {})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // But fine to jump from the selection to its merge.
 
@@ -1539,20 +1588,24 @@
   auto transformation4 =
       TransformationAddDeadBreak(in_selection_4, selection_merge, true, {});
 
-  ASSERT_TRUE(transformation1.IsApplicable(context.get(), fact_manager));
-  transformation1.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation1.IsApplicable(context.get(), transformation_context));
+  transformation1.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(transformation2.IsApplicable(context.get(), fact_manager));
-  transformation2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation2.IsApplicable(context.get(), transformation_context));
+  transformation2.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(transformation3.IsApplicable(context.get(), fact_manager));
-  transformation3.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation3.IsApplicable(context.get(), transformation_context));
+  transformation3.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(transformation4.IsApplicable(context.get(), fact_manager));
-  transformation4.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation4.IsApplicable(context.get(), transformation_context));
+  transformation4.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   std::string after_transformation = R"(
@@ -1720,6 +1773,9 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   const uint32_t outer_loop_merge = 34;
   const uint32_t outer_loop_block = 33;
@@ -1729,22 +1785,24 @@
   // Some inapplicable cases
   ASSERT_FALSE(
       TransformationAddDeadBreak(inner_loop_block, outer_loop_merge, true, {})
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       TransformationAddDeadBreak(outer_loop_block, inner_loop_merge, true, {})
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
 
   auto transformation1 =
       TransformationAddDeadBreak(inner_loop_block, inner_loop_merge, true, {});
   auto transformation2 =
       TransformationAddDeadBreak(outer_loop_block, outer_loop_merge, true, {});
 
-  ASSERT_TRUE(transformation1.IsApplicable(context.get(), fact_manager));
-  transformation1.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation1.IsApplicable(context.get(), transformation_context));
+  transformation1.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(transformation2.IsApplicable(context.get(), fact_manager));
-  transformation2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation2.IsApplicable(context.get(), transformation_context));
+  transformation2.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   std::string after_transformation = R"(
@@ -1936,39 +1994,39 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               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
   ASSERT_FALSE(TransformationAddDeadBreak(19, 20, true, {13, 21})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   // Not applicable because two OpPhis (not zero) need to be updated at 20
   ASSERT_FALSE(TransformationAddDeadBreak(23, 20, true, {})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   // Not applicable because two OpPhis (not just one) need to be updated at 20
   ASSERT_FALSE(TransformationAddDeadBreak(23, 20, true, {13})
-                   .IsApplicable(context.get(), fact_manager));
-  // Not applicable because only two OpPhis (not three) need to be updated at 20
-  ASSERT_FALSE(TransformationAddDeadBreak(23, 20, true, {13, 21, 22})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   // Not applicable because the given ids do not have types that match the
   // OpPhis at 20, in order
   ASSERT_FALSE(TransformationAddDeadBreak(23, 20, true, {21, 13})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   // Not applicable because id 23 is a label
   ASSERT_FALSE(TransformationAddDeadBreak(23, 20, true, {21, 23})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   // Not applicable because 101 is not an id
   ASSERT_FALSE(TransformationAddDeadBreak(23, 20, true, {21, 101})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   // Not applicable because ids 51 and 47 are not available at the end of block
   // 23
   ASSERT_FALSE(TransformationAddDeadBreak(23, 20, true, {51, 47})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // Not applicable because OpConstantFalse is not present in the module
   ASSERT_FALSE(TransformationAddDeadBreak(19, 20, false, {})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   auto transformation1 = TransformationAddDeadBreak(19, 20, true, {});
   auto transformation2 = TransformationAddDeadBreak(23, 20, true, {13, 21});
@@ -1976,24 +2034,29 @@
   auto transformation4 = TransformationAddDeadBreak(30, 31, true, {21, 13});
   auto transformation5 = TransformationAddDeadBreak(75, 31, true, {47, 51});
 
-  ASSERT_TRUE(transformation1.IsApplicable(context.get(), fact_manager));
-  transformation1.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation1.IsApplicable(context.get(), transformation_context));
+  transformation1.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(transformation2.IsApplicable(context.get(), fact_manager));
-  transformation2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation2.IsApplicable(context.get(), transformation_context));
+  transformation2.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(transformation3.IsApplicable(context.get(), fact_manager));
-  transformation3.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation3.IsApplicable(context.get(), transformation_context));
+  transformation3.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(transformation4.IsApplicable(context.get(), fact_manager));
-  transformation4.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation4.IsApplicable(context.get(), transformation_context));
+  transformation4.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(transformation5.IsApplicable(context.get(), fact_manager));
-  transformation5.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation5.IsApplicable(context.get(), transformation_context));
+  transformation5.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   std::string after_transformation = R"(
@@ -2122,9 +2185,13 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   auto bad_transformation = TransformationAddDeadBreak(100, 101, false, {});
-  ASSERT_FALSE(bad_transformation.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      bad_transformation.IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationAddDeadBreakTest, RespectDominanceRules2) {
@@ -2175,9 +2242,13 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   auto bad_transformation = TransformationAddDeadBreak(102, 101, false, {});
-  ASSERT_FALSE(bad_transformation.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      bad_transformation.IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationAddDeadBreakTest, RespectDominanceRules3) {
@@ -2222,11 +2293,15 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   auto good_transformation = TransformationAddDeadBreak(100, 101, false, {11});
-  ASSERT_TRUE(good_transformation.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(
+      good_transformation.IsApplicable(context.get(), transformation_context));
 
-  good_transformation.Apply(context.get(), &fact_manager);
+  good_transformation.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   std::string after_transformation = R"(
@@ -2310,11 +2385,15 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   auto good_transformation = TransformationAddDeadBreak(102, 101, false, {11});
-  ASSERT_TRUE(good_transformation.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(
+      good_transformation.IsApplicable(context.get(), transformation_context));
 
-  good_transformation.Apply(context.get(), &fact_manager);
+  good_transformation.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   std::string after_transformation = R"(
@@ -2392,9 +2471,13 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   auto bad_transformation = TransformationAddDeadBreak(100, 101, false, {});
-  ASSERT_FALSE(bad_transformation.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      bad_transformation.IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationAddDeadBreakTest, RespectDominanceRules6) {
@@ -2449,9 +2532,13 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   auto bad_transformation = TransformationAddDeadBreak(102, 101, false, {});
-  ASSERT_FALSE(bad_transformation.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      bad_transformation.IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationAddDeadBreakTest, RespectDominanceRules7) {
@@ -2508,9 +2595,13 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   auto bad_transformation = TransformationAddDeadBreak(102, 101, false, {});
-  ASSERT_FALSE(bad_transformation.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      bad_transformation.IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationAddDeadBreakTest, RespectDominanceRules8) {
@@ -2554,9 +2645,13 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   auto bad_transformation = TransformationAddDeadBreak(102, 101, false, {});
-  ASSERT_FALSE(bad_transformation.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      bad_transformation.IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationAddDeadBreakTest,
@@ -2600,12 +2695,16 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               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.
   auto bad_transformation = TransformationAddDeadBreak(12, 14, true, {});
-  ASSERT_FALSE(bad_transformation.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      bad_transformation.IsApplicable(context.get(), transformation_context));
 }
 
 }  // namespace
diff --git a/test/fuzz/transformation_add_dead_continue_test.cpp b/test/fuzz/transformation_add_dead_continue_test.cpp
index ff93da8..07ee3b1 100644
--- a/test/fuzz/transformation_add_dead_continue_test.cpp
+++ b/test/fuzz/transformation_add_dead_continue_test.cpp
@@ -97,57 +97,63 @@
   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);
 
   // These are all possibilities.
   ASSERT_TRUE(TransformationAddDeadContinue(11, true, {})
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationAddDeadContinue(11, false, {})
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationAddDeadContinue(12, true, {})
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationAddDeadContinue(12, false, {})
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationAddDeadContinue(40, true, {})
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationAddDeadContinue(40, false, {})
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
 
   // Inapplicable: 100 is not a block id.
   ASSERT_FALSE(TransformationAddDeadContinue(100, true, {})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // Inapplicable: 10 is not in a loop.
   ASSERT_FALSE(TransformationAddDeadContinue(10, true, {})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // Inapplicable: 15 does not branch unconditionally to a single successor.
   ASSERT_FALSE(TransformationAddDeadContinue(15, true, {})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // Inapplicable: 13 is not in a loop and has no successor.
   ASSERT_FALSE(TransformationAddDeadContinue(13, true, {})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // Inapplicable: 14 is the loop continue target, so it's not OK to jump to
   // the loop continue from there.
   ASSERT_FALSE(TransformationAddDeadContinue(14, false, {})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // These are the transformations we will apply.
   auto transformation1 = TransformationAddDeadContinue(11, true, {});
   auto transformation2 = TransformationAddDeadContinue(12, false, {});
   auto transformation3 = TransformationAddDeadContinue(40, true, {});
 
-  ASSERT_TRUE(transformation1.IsApplicable(context.get(), fact_manager));
-  transformation1.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation1.IsApplicable(context.get(), transformation_context));
+  transformation1.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(transformation2.IsApplicable(context.get(), fact_manager));
-  transformation2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation2.IsApplicable(context.get(), transformation_context));
+  transformation2.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(transformation3.IsApplicable(context.get(), fact_manager));
-  transformation3.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation3.IsApplicable(context.get(), transformation_context));
+  transformation3.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   std::string after_transformation = R"(
@@ -365,19 +371,24 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               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};
 
   for (uint32_t from_block : bad) {
     ASSERT_FALSE(TransformationAddDeadContinue(from_block, true, {})
-                     .IsApplicable(context.get(), fact_manager));
+                     .IsApplicable(context.get(), transformation_context));
   }
   for (uint32_t from_block : good) {
     const TransformationAddDeadContinue transformation(from_block, true, {});
-    ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
-    transformation.Apply(context.get(), &fact_manager);
-    ASSERT_FALSE(transformation.IsApplicable(context.get(), fact_manager));
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    transformation.Apply(context.get(), &transformation_context);
+    ASSERT_FALSE(
+        transformation.IsApplicable(context.get(), transformation_context));
   }
 
   std::string after_transformation = R"(
@@ -600,19 +611,24 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   std::vector<uint32_t> good = {32, 33, 46, 52, 101};
   std::vector<uint32_t> bad = {5, 34, 36, 35, 47, 49, 48};
 
   for (uint32_t from_block : bad) {
     ASSERT_FALSE(TransformationAddDeadContinue(from_block, false, {})
-                     .IsApplicable(context.get(), fact_manager));
+                     .IsApplicable(context.get(), transformation_context));
   }
   for (uint32_t from_block : good) {
     const TransformationAddDeadContinue transformation(from_block, false, {});
-    ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
-    transformation.Apply(context.get(), &fact_manager);
-    ASSERT_FALSE(transformation.IsApplicable(context.get(), fact_manager));
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    transformation.Apply(context.get(), &transformation_context);
+    ASSERT_FALSE(
+        transformation.IsApplicable(context.get(), transformation_context));
   }
 
   std::string after_transformation = R"(
@@ -806,6 +822,9 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   std::vector<uint32_t> bad = {5, 19, 20, 23, 31, 32, 33, 70};
 
@@ -813,24 +832,28 @@
 
   for (uint32_t from_block : bad) {
     ASSERT_FALSE(TransformationAddDeadContinue(from_block, true, {})
-                     .IsApplicable(context.get(), fact_manager));
+                     .IsApplicable(context.get(), transformation_context));
   }
   auto transformation1 = TransformationAddDeadContinue(29, true, {13, 21});
-  ASSERT_TRUE(transformation1.IsApplicable(context.get(), fact_manager));
-  transformation1.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation1.IsApplicable(context.get(), transformation_context));
+  transformation1.Apply(context.get(), &transformation_context);
 
   auto transformation2 = TransformationAddDeadContinue(30, true, {22, 46});
-  ASSERT_TRUE(transformation2.IsApplicable(context.get(), fact_manager));
-  transformation2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation2.IsApplicable(context.get(), transformation_context));
+  transformation2.Apply(context.get(), &transformation_context);
 
   // 75 already has the continue block as a successor, so we should not provide
   // phi ids.
   auto transformationBad = TransformationAddDeadContinue(75, true, {27, 46});
-  ASSERT_FALSE(transformationBad.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformationBad.IsApplicable(context.get(), transformation_context));
 
   auto transformation3 = TransformationAddDeadContinue(75, true, {});
-  ASSERT_TRUE(transformation3.IsApplicable(context.get(), fact_manager));
-  transformation3.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation3.IsApplicable(context.get(), transformation_context));
+  transformation3.Apply(context.get(), &transformation_context);
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -974,26 +997,33 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               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.
   auto bad_transformation = TransformationAddDeadContinue(13, false, {});
-  ASSERT_FALSE(bad_transformation.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      bad_transformation.IsApplicable(context.get(), transformation_context));
 
   auto good_transformation_1 = TransformationAddDeadContinue(7, false, {});
-  ASSERT_TRUE(good_transformation_1.IsApplicable(context.get(), fact_manager));
-  good_transformation_1.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(good_transformation_1.IsApplicable(context.get(),
+                                                 transformation_context));
+  good_transformation_1.Apply(context.get(), &transformation_context);
 
   auto good_transformation_2 = TransformationAddDeadContinue(22, false, {});
-  ASSERT_TRUE(good_transformation_2.IsApplicable(context.get(), fact_manager));
-  good_transformation_2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(good_transformation_2.IsApplicable(context.get(),
+                                                 transformation_context));
+  good_transformation_2.Apply(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(), fact_manager));
-  good_transformation_3.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(good_transformation_3.IsApplicable(context.get(),
+                                                 transformation_context));
+  good_transformation_3.Apply(context.get(), &transformation_context);
 
   std::string after_transformations = R"(
                OpCapability Shader
@@ -1083,11 +1113,15 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               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, {});
-  ASSERT_FALSE(bad_transformation.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      bad_transformation.IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationAddDeadContinueTest, RespectDominanceRules3) {
@@ -1131,11 +1165,15 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               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, {});
-  ASSERT_FALSE(bad_transformation.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      bad_transformation.IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationAddDeadContinueTest, Miscellaneous1) {
@@ -1270,11 +1308,15 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               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, {});
-  ASSERT_FALSE(bad_transformation.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      bad_transformation.IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationAddDeadContinueTest, Miscellaneous2) {
@@ -1336,11 +1378,15 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   // This transformation would introduce a branch from a continue target to
   // itself.
   auto bad_transformation = TransformationAddDeadContinue(1554, true, {});
-  ASSERT_FALSE(bad_transformation.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      bad_transformation.IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationAddDeadContinueTest, Miscellaneous3) {
@@ -1394,13 +1440,17 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   auto bad_transformation = TransformationAddDeadContinue(299, false, {});
 
   // The continue edge would connect %299 to the previously-unreachable %236,
   // making %299 dominate %236, and breaking the rule that block ordering must
   // respect dominance.
-  ASSERT_FALSE(bad_transformation.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      bad_transformation.IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationAddDeadContinueTest, Miscellaneous4) {
@@ -1454,13 +1504,17 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   auto bad_transformation = TransformationAddDeadContinue(10, false, {});
 
   // The continue edge would connect %10 to the previously-unreachable %13,
   // making %10 dominate %13, and breaking the rule that block ordering must
   // respect dominance.
-  ASSERT_FALSE(bad_transformation.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      bad_transformation.IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationAddDeadContinueTest, Miscellaneous5) {
@@ -1506,12 +1560,16 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   auto bad_transformation = TransformationAddDeadContinue(110, true, {});
 
   // The continue edge would lead to the use of %200 in block %101 no longer
   // being dominated by its definition in block %111.
-  ASSERT_FALSE(bad_transformation.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      bad_transformation.IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationAddDeadContinueTest, Miscellaneous6) {
@@ -1551,10 +1609,14 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   auto bad_transformation = TransformationAddDeadContinue(10, true, {});
 
-  ASSERT_FALSE(bad_transformation.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      bad_transformation.IsApplicable(context.get(), transformation_context));
 }
 
 }  // namespace
diff --git a/test/fuzz/transformation_add_function_test.cpp b/test/fuzz/transformation_add_function_test.cpp
index 66130be..0d1f2c4 100644
--- a/test/fuzz/transformation_add_function_test.cpp
+++ b/test/fuzz/transformation_add_function_test.cpp
@@ -20,6 +20,97 @@
 namespace fuzz {
 namespace {
 
+protobufs::AccessChainClampingInfo MakeAccessClampingInfo(
+    uint32_t access_chain_id,
+    const std::vector<std::pair<uint32_t, uint32_t>>& compare_and_select_ids) {
+  protobufs::AccessChainClampingInfo result;
+  result.set_access_chain_id(access_chain_id);
+  for (auto& compare_and_select_id : compare_and_select_ids) {
+    auto pair = result.add_compare_and_select_ids();
+    pair->set_first(compare_and_select_id.first);
+    pair->set_second(compare_and_select_id.second);
+  }
+  return result;
+}
+
+std::vector<protobufs::Instruction> GetInstructionsForFunction(
+    spv_target_env env, const MessageConsumer& consumer,
+    const std::string& donor, uint32_t function_id) {
+  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.");
+  for (auto& function : *donor_context->module()) {
+    if (function.result_id() == function_id) {
+      function.ForEachInst([&result](opt::Instruction* inst) {
+        opt::Instruction::OperandList input_operands;
+        for (uint32_t i = 0; i < inst->NumInOperands(); i++) {
+          input_operands.push_back(inst->GetInOperand(i));
+        }
+        result.push_back(MakeInstructionMessage(inst->opcode(), inst->type_id(),
+                                                inst->result_id(),
+                                                input_operands));
+      });
+      break;
+    }
+  }
+  assert(!result.empty() && "The required function should have been found.");
+  return result;
+}
+
+// Returns true if and only if every pointer parameter and variable associated
+// with |function_id| in |context| is known by |transformation_context| to be
+// irrelevant, with the exception of |loop_limiter_id|, which must not be
+// irrelevant.  (It can be 0 if no loop limiter is expected, and 0 should not be
+// deemed irrelevant).
+bool AllVariablesAndParametersExceptLoopLimiterAreIrrelevant(
+    opt::IRContext* context,
+    const TransformationContext& transformation_context, uint32_t function_id,
+    uint32_t loop_limiter_id) {
+  // Look at all the functions until the function of interest is found.
+  for (auto& function : *context->module()) {
+    if (function.result_id() != function_id) {
+      continue;
+    }
+    // Check that the parameters are all irrelevant.
+    bool found_non_irrelevant_parameter = false;
+    function.ForEachParam([context, &transformation_context,
+                           &found_non_irrelevant_parameter](
+                              opt::Instruction* inst) {
+      if (context->get_def_use_mgr()->GetDef(inst->type_id())->opcode() ==
+              SpvOpTypePointer &&
+          !transformation_context.GetFactManager()->PointeeValueIsIrrelevant(
+              inst->result_id())) {
+        found_non_irrelevant_parameter = true;
+      }
+    });
+    if (found_non_irrelevant_parameter) {
+      // A non-irrelevant parameter was found.
+      return false;
+    }
+    // Look through the instructions in the function's first block.
+    for (auto& inst : *function.begin()) {
+      if (inst.opcode() != SpvOpVariable) {
+        // We have found a non-variable instruction; this means we have gotten
+        // past all variables, so we are done.
+        return true;
+      }
+      // The variable should be irrelevant if and only if it is not the loop
+      // limiter.
+      if ((inst.result_id() == loop_limiter_id) ==
+          transformation_context.GetFactManager()->PointeeValueIsIrrelevant(
+              inst.result_id())) {
+        return false;
+      }
+    }
+    assert(false &&
+           "We should have processed all variables and returned by "
+           "this point.");
+  }
+  assert(false && "We should have found the function of interest.");
+  return true;
+}
+
 TEST(TransformationAddFunctionTest, BasicTest) {
   std::string shader = R"(
                OpCapability Shader
@@ -54,6 +145,9 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   TransformationAddFunction transformation1(std::vector<protobufs::Instruction>(
       {MakeInstructionMessage(
@@ -124,8 +218,9 @@
                               {{SPV_OPERAND_TYPE_ID, {39}}}),
        MakeInstructionMessage(SpvOpFunctionEnd, 0, 0, {})}));
 
-  ASSERT_TRUE(transformation1.IsApplicable(context.get(), fact_manager));
-  transformation1.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation1.IsApplicable(context.get(), transformation_context));
+  transformation1.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   std::string after_transformation1 = R"(
@@ -190,6 +285,12 @@
                OpFunctionEnd
   )";
   ASSERT_TRUE(IsEqual(env, after_transformation1, context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->BlockIsDead(14));
+  ASSERT_TRUE(transformation_context.GetFactManager()->BlockIsDead(21));
+  ASSERT_TRUE(transformation_context.GetFactManager()->BlockIsDead(22));
+  ASSERT_TRUE(transformation_context.GetFactManager()->BlockIsDead(23));
+  ASSERT_TRUE(transformation_context.GetFactManager()->BlockIsDead(24));
+  ASSERT_TRUE(transformation_context.GetFactManager()->BlockIsDead(25));
 
   TransformationAddFunction transformation2(std::vector<protobufs::Instruction>(
       {MakeInstructionMessage(
@@ -238,8 +339,9 @@
        MakeInstructionMessage(SpvOpReturn, 0, 0, {}),
        MakeInstructionMessage(SpvOpFunctionEnd, 0, 0, {})}));
 
-  ASSERT_TRUE(transformation2.IsApplicable(context.get(), fact_manager));
-  transformation2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation2.IsApplicable(context.get(), transformation_context));
+  transformation2.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   std::string after_transformation2 = R"(
@@ -320,6 +422,7 @@
                OpFunctionEnd
   )";
   ASSERT_TRUE(IsEqual(env, after_transformation2, context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->BlockIsDead(16));
 }
 
 TEST(TransformationAddFunctionTest, InapplicableTransformations) {
@@ -391,11 +494,14 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   // No instructions
   ASSERT_FALSE(
       TransformationAddFunction(std::vector<protobufs::Instruction>({}))
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
 
   // No function begin
   ASSERT_FALSE(
@@ -404,7 +510,7 @@
               {MakeInstructionMessage(SpvOpFunctionParameter, 7, 11, {}),
                MakeInstructionMessage(SpvOpFunctionParameter, 9, 12, {}),
                MakeInstructionMessage(SpvOpLabel, 0, 14, {})}))
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
 
   // No OpLabel
   ASSERT_FALSE(
@@ -417,7 +523,7 @@
                MakeInstructionMessage(SpvOpReturnValue, 0, 0,
                                       {{SPV_OPERAND_TYPE_ID, {39}}}),
                MakeInstructionMessage(SpvOpFunctionEnd, 0, 0, {})}))
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
 
   // Abrupt end of instructions
   ASSERT_FALSE(TransformationAddFunction(
@@ -426,7 +532,7 @@
                        {{SPV_OPERAND_TYPE_FUNCTION_CONTROL,
                          {SpvFunctionControlMaskNone}},
                         {SPV_OPERAND_TYPE_ID, {10}}})}))
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // No function end
   ASSERT_FALSE(
@@ -439,7 +545,2384 @@
                MakeInstructionMessage(SpvOpLabel, 0, 14, {}),
                MakeInstructionMessage(SpvOpReturnValue, 0, 0,
                                       {{SPV_OPERAND_TYPE_ID, {39}}})}))
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationAddFunctionTest, LoopLimiters) {
+  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 0
+          %7 = OpTypePointer Function %6
+          %8 = OpConstant %6 0
+          %9 = OpConstant %6 1
+         %10 = OpConstant %6 5
+         %11 = OpTypeBool
+         %12 = OpConstantTrue %11
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+
+  std::vector<protobufs::Instruction> instructions;
+  instructions.push_back(MakeInstructionMessage(
+      SpvOpFunction, 2, 30,
+      {{SPV_OPERAND_TYPE_FUNCTION_CONTROL, {SpvFunctionControlMaskNone}},
+       {SPV_OPERAND_TYPE_TYPE_ID, {3}}}));
+  instructions.push_back(MakeInstructionMessage(SpvOpLabel, 0, 31, {}));
+  instructions.push_back(
+      MakeInstructionMessage(SpvOpBranch, 0, 0, {{SPV_OPERAND_TYPE_ID, {20}}}));
+  instructions.push_back(MakeInstructionMessage(SpvOpLabel, 0, 20, {}));
+  instructions.push_back(MakeInstructionMessage(
+      SpvOpLoopMerge, 0, 0,
+      {{SPV_OPERAND_TYPE_ID, {21}},
+       {SPV_OPERAND_TYPE_ID, {22}},
+       {SPV_OPERAND_TYPE_LOOP_CONTROL, {SpvLoopControlMaskNone}}}));
+  instructions.push_back(MakeInstructionMessage(SpvOpBranchConditional, 0, 0,
+                                                {{SPV_OPERAND_TYPE_ID, {12}},
+                                                 {SPV_OPERAND_TYPE_ID, {23}},
+                                                 {SPV_OPERAND_TYPE_ID, {21}}}));
+  instructions.push_back(MakeInstructionMessage(SpvOpLabel, 0, 23, {}));
+  instructions.push_back(MakeInstructionMessage(
+      SpvOpLoopMerge, 0, 0,
+      {{SPV_OPERAND_TYPE_ID, {25}},
+       {SPV_OPERAND_TYPE_ID, {26}},
+       {SPV_OPERAND_TYPE_LOOP_CONTROL, {SpvLoopControlMaskNone}}}));
+  instructions.push_back(
+      MakeInstructionMessage(SpvOpBranch, 0, 0, {{SPV_OPERAND_TYPE_ID, {28}}}));
+  instructions.push_back(MakeInstructionMessage(SpvOpLabel, 0, 28, {}));
+  instructions.push_back(MakeInstructionMessage(SpvOpBranchConditional, 0, 0,
+                                                {{SPV_OPERAND_TYPE_ID, {12}},
+                                                 {SPV_OPERAND_TYPE_ID, {26}},
+                                                 {SPV_OPERAND_TYPE_ID, {25}}}));
+  instructions.push_back(MakeInstructionMessage(SpvOpLabel, 0, 26, {}));
+  instructions.push_back(
+      MakeInstructionMessage(SpvOpBranch, 0, 0, {{SPV_OPERAND_TYPE_ID, {23}}}));
+  instructions.push_back(MakeInstructionMessage(SpvOpLabel, 0, 25, {}));
+  instructions.push_back(MakeInstructionMessage(
+      SpvOpLoopMerge, 0, 0,
+      {{SPV_OPERAND_TYPE_ID, {24}},
+       {SPV_OPERAND_TYPE_ID, {27}},
+       {SPV_OPERAND_TYPE_LOOP_CONTROL, {SpvLoopControlMaskNone}}}));
+  instructions.push_back(MakeInstructionMessage(SpvOpBranchConditional, 0, 0,
+                                                {{SPV_OPERAND_TYPE_ID, {12}},
+                                                 {SPV_OPERAND_TYPE_ID, {24}},
+                                                 {SPV_OPERAND_TYPE_ID, {27}}}));
+  instructions.push_back(MakeInstructionMessage(SpvOpLabel, 0, 27, {}));
+  instructions.push_back(
+      MakeInstructionMessage(SpvOpBranch, 0, 0, {{SPV_OPERAND_TYPE_ID, {25}}}));
+  instructions.push_back(MakeInstructionMessage(SpvOpLabel, 0, 24, {}));
+  instructions.push_back(
+      MakeInstructionMessage(SpvOpBranch, 0, 0, {{SPV_OPERAND_TYPE_ID, {22}}}));
+  instructions.push_back(MakeInstructionMessage(SpvOpLabel, 0, 22, {}));
+  instructions.push_back(
+      MakeInstructionMessage(SpvOpBranch, 0, 0, {{SPV_OPERAND_TYPE_ID, {20}}}));
+  instructions.push_back(MakeInstructionMessage(SpvOpLabel, 0, 21, {}));
+  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()));
+
+  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()));
+  // The added function should not be deemed livesafe.
+  ASSERT_FALSE(
+      transformation_context1.GetFactManager()->FunctionIsLivesafe(30));
+  // All variables/parameters in the function should be deemed irrelevant.
+  ASSERT_TRUE(AllVariablesAndParametersExceptLoopLimiterAreIrrelevant(
+      context1.get(), transformation_context1, 30, 0));
+
+  std::string added_as_dead_code = 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 0
+          %7 = OpTypePointer Function %6
+          %8 = OpConstant %6 0
+          %9 = OpConstant %6 1
+         %10 = OpConstant %6 5
+         %11 = OpTypeBool
+         %12 = OpConstantTrue %11
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %30 = OpFunction %2 None %3
+         %31 = OpLabel
+               OpBranch %20
+         %20 = OpLabel
+               OpLoopMerge %21 %22 None
+               OpBranchConditional %12 %23 %21
+         %23 = OpLabel
+               OpLoopMerge %25 %26 None
+               OpBranch %28
+         %28 = OpLabel
+               OpBranchConditional %12 %26 %25
+         %26 = OpLabel
+               OpBranch %23
+         %25 = OpLabel
+               OpLoopMerge %24 %27 None
+               OpBranchConditional %12 %24 %27
+         %27 = OpLabel
+               OpBranch %25
+         %24 = OpLabel
+               OpBranch %22
+         %22 = OpLabel
+               OpBranch %20
+         %21 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, added_as_dead_code, context1.get()));
+
+  protobufs::LoopLimiterInfo loop_limiter1;
+  loop_limiter1.set_loop_header_id(20);
+  loop_limiter1.set_load_id(101);
+  loop_limiter1.set_increment_id(102);
+  loop_limiter1.set_compare_id(103);
+  loop_limiter1.set_logical_op_id(104);
+
+  protobufs::LoopLimiterInfo loop_limiter2;
+  loop_limiter2.set_loop_header_id(23);
+  loop_limiter2.set_load_id(105);
+  loop_limiter2.set_increment_id(106);
+  loop_limiter2.set_compare_id(107);
+  loop_limiter2.set_logical_op_id(108);
+
+  protobufs::LoopLimiterInfo loop_limiter3;
+  loop_limiter3.set_loop_header_id(25);
+  loop_limiter3.set_load_id(109);
+  loop_limiter3.set_increment_id(110);
+  loop_limiter3.set_compare_id(111);
+  loop_limiter3.set_logical_op_id(112);
+
+  std::vector<protobufs::LoopLimiterInfo> loop_limiters = {
+      loop_limiter1, loop_limiter2, loop_limiter3};
+
+  TransformationAddFunction add_livesafe_function(instructions, 100, 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()));
+  // 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,
+  // except the loop limiter.
+  ASSERT_TRUE(AllVariablesAndParametersExceptLoopLimiterAreIrrelevant(
+      context2.get(), transformation_context2, 30, 100));
+  std::string added_as_livesafe_code = 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 0
+          %7 = OpTypePointer Function %6
+          %8 = OpConstant %6 0
+          %9 = OpConstant %6 1
+         %10 = OpConstant %6 5
+         %11 = OpTypeBool
+         %12 = OpConstantTrue %11
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %30 = OpFunction %2 None %3
+         %31 = OpLabel
+        %100 = OpVariable %7 Function %8
+               OpBranch %20
+         %20 = OpLabel
+               OpLoopMerge %21 %22 None
+               OpBranchConditional %12 %23 %21
+         %23 = OpLabel
+               OpLoopMerge %25 %26 None
+               OpBranch %28
+         %28 = OpLabel
+               OpBranchConditional %12 %26 %25
+         %26 = OpLabel
+        %105 = OpLoad %6 %100
+        %106 = OpIAdd %6 %105 %9
+               OpStore %100 %106
+        %107 = OpUGreaterThanEqual %11 %105 %10
+               OpBranchConditional %107 %25 %23
+         %25 = OpLabel
+               OpLoopMerge %24 %27 None
+               OpBranchConditional %12 %24 %27
+         %27 = OpLabel
+        %109 = OpLoad %6 %100
+        %110 = OpIAdd %6 %109 %9
+               OpStore %100 %110
+        %111 = OpUGreaterThanEqual %11 %109 %10
+               OpBranchConditional %111 %24 %25
+         %24 = OpLabel
+               OpBranch %22
+         %22 = OpLabel
+        %101 = OpLoad %6 %100
+        %102 = OpIAdd %6 %101 %9
+               OpStore %100 %102
+        %103 = OpUGreaterThanEqual %11 %101 %10
+               OpBranchConditional %103 %21 %20
+         %21 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, added_as_livesafe_code, context2.get()));
+}
+
+TEST(TransformationAddFunctionTest, KillAndUnreachableInVoidFunction) {
+  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 = OpTypePointer Function %6
+          %8 = OpTypeFunction %2 %7
+         %13 = OpConstant %6 2
+         %14 = OpTypeBool
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+
+  std::vector<protobufs::Instruction> instructions;
+
+  instructions.push_back(MakeInstructionMessage(
+      SpvOpFunction, 2, 10,
+      {{SPV_OPERAND_TYPE_FUNCTION_CONTROL, {SpvFunctionControlMaskNone}},
+       {SPV_OPERAND_TYPE_TYPE_ID, {8}}}));
+  instructions.push_back(
+      MakeInstructionMessage(SpvOpFunctionParameter, 7, 9, {}));
+  instructions.push_back(MakeInstructionMessage(SpvOpLabel, 0, 11, {}));
+  instructions.push_back(
+      MakeInstructionMessage(SpvOpLoad, 6, 12, {{SPV_OPERAND_TYPE_ID, {9}}}));
+  instructions.push_back(MakeInstructionMessage(
+      SpvOpIEqual, 14, 15,
+      {{SPV_OPERAND_TYPE_ID, {12}}, {SPV_OPERAND_TYPE_ID, {13}}}));
+  instructions.push_back(MakeInstructionMessage(
+      SpvOpSelectionMerge, 0, 0,
+      {{SPV_OPERAND_TYPE_ID, {17}},
+       {SPV_OPERAND_TYPE_SELECTION_CONTROL, {SpvSelectionControlMaskNone}}}));
+  instructions.push_back(MakeInstructionMessage(SpvOpBranchConditional, 0, 0,
+                                                {{SPV_OPERAND_TYPE_ID, {15}},
+                                                 {SPV_OPERAND_TYPE_ID, {16}},
+                                                 {SPV_OPERAND_TYPE_ID, {17}}}));
+  instructions.push_back(MakeInstructionMessage(SpvOpLabel, 0, 16, {}));
+  instructions.push_back(MakeInstructionMessage(SpvOpUnreachable, 0, 0, {}));
+  instructions.push_back(MakeInstructionMessage(SpvOpLabel, 0, 17, {}));
+  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()));
+
+  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()));
+  // The added function should not be deemed livesafe.
+  ASSERT_FALSE(
+      transformation_context1.GetFactManager()->FunctionIsLivesafe(10));
+  // All variables/parameters in the function should be deemed irrelevant.
+  ASSERT_TRUE(AllVariablesAndParametersExceptLoopLimiterAreIrrelevant(
+      context1.get(), transformation_context1, 10, 0));
+
+  std::string added_as_dead_code = 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 = OpTypePointer Function %6
+          %8 = OpTypeFunction %2 %7
+         %13 = OpConstant %6 2
+         %14 = OpTypeBool
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %2 None %8
+          %9 = OpFunctionParameter %7
+         %11 = OpLabel
+         %12 = OpLoad %6 %9
+         %15 = OpIEqual %14 %12 %13
+               OpSelectionMerge %17 None
+               OpBranchConditional %15 %16 %17
+         %16 = OpLabel
+               OpUnreachable
+         %17 = OpLabel
+               OpKill
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, added_as_dead_code, context1.get()));
+
+  TransformationAddFunction add_livesafe_function(instructions, 0, 0, {}, 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()));
+  // 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.
+  ASSERT_TRUE(AllVariablesAndParametersExceptLoopLimiterAreIrrelevant(
+      context2.get(), transformation_context2, 10, 0));
+  std::string added_as_livesafe_code = 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 = OpTypePointer Function %6
+          %8 = OpTypeFunction %2 %7
+         %13 = OpConstant %6 2
+         %14 = OpTypeBool
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %2 None %8
+          %9 = OpFunctionParameter %7
+         %11 = OpLabel
+         %12 = OpLoad %6 %9
+         %15 = OpIEqual %14 %12 %13
+               OpSelectionMerge %17 None
+               OpBranchConditional %15 %16 %17
+         %16 = OpLabel
+               OpReturn
+         %17 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, added_as_livesafe_code, context2.get()));
+}
+
+TEST(TransformationAddFunctionTest, KillAndUnreachableInNonVoidFunction) {
+  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 = OpTypePointer Function %6
+          %8 = OpTypeFunction %2 %7
+         %50 = OpTypeFunction %6 %7
+         %13 = OpConstant %6 2
+         %14 = OpTypeBool
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+
+  std::vector<protobufs::Instruction> instructions;
+
+  instructions.push_back(MakeInstructionMessage(
+      SpvOpFunction, 6, 10,
+      {{SPV_OPERAND_TYPE_FUNCTION_CONTROL, {SpvFunctionControlMaskNone}},
+       {SPV_OPERAND_TYPE_TYPE_ID, {50}}}));
+  instructions.push_back(
+      MakeInstructionMessage(SpvOpFunctionParameter, 7, 9, {}));
+  instructions.push_back(MakeInstructionMessage(SpvOpLabel, 0, 11, {}));
+  instructions.push_back(
+      MakeInstructionMessage(SpvOpLoad, 6, 12, {{SPV_OPERAND_TYPE_ID, {9}}}));
+  instructions.push_back(MakeInstructionMessage(
+      SpvOpIEqual, 14, 15,
+      {{SPV_OPERAND_TYPE_ID, {12}}, {SPV_OPERAND_TYPE_ID, {13}}}));
+  instructions.push_back(MakeInstructionMessage(
+      SpvOpSelectionMerge, 0, 0,
+      {{SPV_OPERAND_TYPE_ID, {17}},
+       {SPV_OPERAND_TYPE_SELECTION_CONTROL, {SpvSelectionControlMaskNone}}}));
+  instructions.push_back(MakeInstructionMessage(SpvOpBranchConditional, 0, 0,
+                                                {{SPV_OPERAND_TYPE_ID, {15}},
+                                                 {SPV_OPERAND_TYPE_ID, {16}},
+                                                 {SPV_OPERAND_TYPE_ID, {17}}}));
+  instructions.push_back(MakeInstructionMessage(SpvOpLabel, 0, 16, {}));
+  instructions.push_back(MakeInstructionMessage(SpvOpUnreachable, 0, 0, {}));
+  instructions.push_back(MakeInstructionMessage(SpvOpLabel, 0, 17, {}));
+  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()));
+
+  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()));
+  // The added function should not be deemed livesafe.
+  ASSERT_FALSE(
+      transformation_context1.GetFactManager()->FunctionIsLivesafe(10));
+  // All variables/parameters in the function should be deemed irrelevant.
+  ASSERT_TRUE(AllVariablesAndParametersExceptLoopLimiterAreIrrelevant(
+      context1.get(), transformation_context1, 10, 0));
+
+  std::string added_as_dead_code = 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 = OpTypePointer Function %6
+          %8 = OpTypeFunction %2 %7
+         %50 = OpTypeFunction %6 %7
+         %13 = OpConstant %6 2
+         %14 = OpTypeBool
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %6 None %50
+          %9 = OpFunctionParameter %7
+         %11 = OpLabel
+         %12 = OpLoad %6 %9
+         %15 = OpIEqual %14 %12 %13
+               OpSelectionMerge %17 None
+               OpBranchConditional %15 %16 %17
+         %16 = OpLabel
+               OpUnreachable
+         %17 = OpLabel
+               OpKill
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, added_as_dead_code, context1.get()));
+
+  TransformationAddFunction add_livesafe_function(instructions, 0, 0, {}, 13,
+                                                  {});
+  ASSERT_TRUE(add_livesafe_function.IsApplicable(context2.get(),
+                                                 transformation_context2));
+  add_livesafe_function.Apply(context2.get(), &transformation_context2);
+  ASSERT_TRUE(IsValid(env, context2.get()));
+  // 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.
+  ASSERT_TRUE(AllVariablesAndParametersExceptLoopLimiterAreIrrelevant(
+      context2.get(), transformation_context2, 10, 0));
+  std::string added_as_livesafe_code = 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 = OpTypePointer Function %6
+          %8 = OpTypeFunction %2 %7
+         %50 = OpTypeFunction %6 %7
+         %13 = OpConstant %6 2
+         %14 = OpTypeBool
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %6 None %50
+          %9 = OpFunctionParameter %7
+         %11 = OpLabel
+         %12 = OpLoad %6 %9
+         %15 = OpIEqual %14 %12 %13
+               OpSelectionMerge %17 None
+               OpBranchConditional %15 %16 %17
+         %16 = OpLabel
+               OpReturnValue %13
+         %17 = OpLabel
+               OpReturnValue %13
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, added_as_livesafe_code, context2.get()));
+}
+
+TEST(TransformationAddFunctionTest, ClampedAccessChains) {
+  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
+        %100 = OpTypeBool
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+         %15 = OpTypeInt 32 0
+        %102 = OpTypePointer Function %15
+          %8 = OpTypeFunction %2 %7 %102 %7
+         %16 = OpConstant %15 5
+         %17 = OpTypeArray %6 %16
+         %18 = OpTypeArray %17 %16
+         %19 = OpTypePointer Private %18
+         %20 = OpVariable %19 Private
+         %21 = OpConstant %6 0
+         %23 = OpTypePointer Private %6
+         %26 = OpTypePointer Function %17
+         %29 = OpTypePointer Private %17
+         %33 = OpConstant %6 4
+        %200 = OpConstant %15 4
+         %35 = OpConstant %15 10
+         %36 = OpTypeArray %6 %35
+         %37 = OpTypePointer Private %36
+         %38 = OpVariable %37 Private
+         %54 = OpTypeFloat 32
+         %55 = OpTypeVector %54 4
+         %56 = OpTypePointer Private %55
+         %57 = OpVariable %56 Private
+         %59 = OpTypeVector %54 3
+         %60 = OpTypeMatrix %59 2
+         %61 = OpTypePointer Private %60
+         %62 = OpVariable %61 Private
+         %64 = OpTypePointer Private %54
+         %69 = OpConstant %54 2
+         %71 = OpConstant %6 1
+         %72 = OpConstant %6 2
+        %201 = OpConstant %15 2
+         %73 = OpConstant %6 3
+        %202 = OpConstant %15 3
+        %203 = OpConstant %6 1
+        %204 = OpConstant %6 9
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+
+  std::vector<protobufs::Instruction> instructions;
+
+  instructions.push_back(MakeInstructionMessage(
+      SpvOpFunction, 2, 12,
+      {{SPV_OPERAND_TYPE_FUNCTION_CONTROL, {SpvFunctionControlMaskNone}},
+       {SPV_OPERAND_TYPE_TYPE_ID, {8}}}));
+  instructions.push_back(
+      MakeInstructionMessage(SpvOpFunctionParameter, 7, 9, {}));
+  instructions.push_back(
+      MakeInstructionMessage(SpvOpFunctionParameter, 102, 10, {}));
+  instructions.push_back(
+      MakeInstructionMessage(SpvOpFunctionParameter, 7, 11, {}));
+  instructions.push_back(MakeInstructionMessage(SpvOpLabel, 0, 13, {}));
+
+  instructions.push_back(MakeInstructionMessage(
+      SpvOpVariable, 7, 14,
+      {{SPV_OPERAND_TYPE_STORAGE_CLASS, {SpvStorageClassFunction}}}));
+  instructions.push_back(MakeInstructionMessage(
+      SpvOpVariable, 26, 27,
+      {{SPV_OPERAND_TYPE_STORAGE_CLASS, {SpvStorageClassFunction}}}));
+  instructions.push_back(
+      MakeInstructionMessage(SpvOpLoad, 6, 22, {{SPV_OPERAND_TYPE_ID, {11}}}));
+  instructions.push_back(MakeInstructionMessage(SpvOpAccessChain, 23, 24,
+                                                {{SPV_OPERAND_TYPE_ID, {20}},
+                                                 {SPV_OPERAND_TYPE_ID, {21}},
+                                                 {SPV_OPERAND_TYPE_ID, {22}}}));
+  instructions.push_back(
+      MakeInstructionMessage(SpvOpLoad, 6, 25, {{SPV_OPERAND_TYPE_ID, {24}}}));
+  instructions.push_back(MakeInstructionMessage(
+      SpvOpStore, 0, 0,
+      {{SPV_OPERAND_TYPE_ID, {14}}, {SPV_OPERAND_TYPE_ID, {25}}}));
+  instructions.push_back(
+      MakeInstructionMessage(SpvOpLoad, 15, 28, {{SPV_OPERAND_TYPE_ID, {10}}}));
+  instructions.push_back(MakeInstructionMessage(
+      SpvOpAccessChain, 29, 30,
+      {{SPV_OPERAND_TYPE_ID, {20}}, {SPV_OPERAND_TYPE_ID, {28}}}));
+  instructions.push_back(
+      MakeInstructionMessage(SpvOpLoad, 17, 31, {{SPV_OPERAND_TYPE_ID, {30}}}));
+  instructions.push_back(MakeInstructionMessage(
+      SpvOpStore, 0, 0,
+      {{SPV_OPERAND_TYPE_ID, {27}}, {SPV_OPERAND_TYPE_ID, {31}}}));
+  instructions.push_back(
+      MakeInstructionMessage(SpvOpLoad, 6, 32, {{SPV_OPERAND_TYPE_ID, {9}}}));
+  instructions.push_back(MakeInstructionMessage(
+      SpvOpInBoundsAccessChain, 7, 34,
+      {{SPV_OPERAND_TYPE_ID, {27}}, {SPV_OPERAND_TYPE_ID, {32}}}));
+  instructions.push_back(MakeInstructionMessage(
+      SpvOpStore, 0, 0,
+      {{SPV_OPERAND_TYPE_ID, {34}}, {SPV_OPERAND_TYPE_ID, {33}}}));
+  instructions.push_back(
+      MakeInstructionMessage(SpvOpLoad, 6, 39, {{SPV_OPERAND_TYPE_ID, {9}}}));
+  instructions.push_back(MakeInstructionMessage(
+      SpvOpAccessChain, 23, 40,
+      {{SPV_OPERAND_TYPE_ID, {38}}, {SPV_OPERAND_TYPE_ID, {33}}}));
+  instructions.push_back(
+      MakeInstructionMessage(SpvOpLoad, 6, 41, {{SPV_OPERAND_TYPE_ID, {40}}}));
+  instructions.push_back(MakeInstructionMessage(
+      SpvOpInBoundsAccessChain, 23, 42,
+      {{SPV_OPERAND_TYPE_ID, {38}}, {SPV_OPERAND_TYPE_ID, {39}}}));
+  instructions.push_back(MakeInstructionMessage(
+      SpvOpStore, 0, 0,
+      {{SPV_OPERAND_TYPE_ID, {42}}, {SPV_OPERAND_TYPE_ID, {41}}}));
+  instructions.push_back(
+      MakeInstructionMessage(SpvOpLoad, 15, 43, {{SPV_OPERAND_TYPE_ID, {10}}}));
+  instructions.push_back(
+      MakeInstructionMessage(SpvOpLoad, 6, 44, {{SPV_OPERAND_TYPE_ID, {11}}}));
+  instructions.push_back(
+      MakeInstructionMessage(SpvOpLoad, 6, 45, {{SPV_OPERAND_TYPE_ID, {9}}}));
+  instructions.push_back(
+      MakeInstructionMessage(SpvOpLoad, 15, 46, {{SPV_OPERAND_TYPE_ID, {10}}}));
+  instructions.push_back(MakeInstructionMessage(
+      SpvOpIAdd, 6, 47,
+      {{SPV_OPERAND_TYPE_ID, {45}}, {SPV_OPERAND_TYPE_ID, {46}}}));
+  instructions.push_back(MakeInstructionMessage(
+      SpvOpAccessChain, 23, 48,
+      {{SPV_OPERAND_TYPE_ID, {38}}, {SPV_OPERAND_TYPE_ID, {47}}}));
+  instructions.push_back(
+      MakeInstructionMessage(SpvOpLoad, 6, 49, {{SPV_OPERAND_TYPE_ID, {48}}}));
+  instructions.push_back(MakeInstructionMessage(SpvOpInBoundsAccessChain, 23,
+                                                50,
+                                                {{SPV_OPERAND_TYPE_ID, {20}},
+                                                 {SPV_OPERAND_TYPE_ID, {43}},
+                                                 {SPV_OPERAND_TYPE_ID, {44}}}));
+  instructions.push_back(
+      MakeInstructionMessage(SpvOpLoad, 6, 51, {{SPV_OPERAND_TYPE_ID, {50}}}));
+  instructions.push_back(MakeInstructionMessage(
+      SpvOpIAdd, 6, 52,
+      {{SPV_OPERAND_TYPE_ID, {51}}, {SPV_OPERAND_TYPE_ID, {49}}}));
+  instructions.push_back(MakeInstructionMessage(SpvOpAccessChain, 23, 53,
+                                                {{SPV_OPERAND_TYPE_ID, {20}},
+                                                 {SPV_OPERAND_TYPE_ID, {43}},
+                                                 {SPV_OPERAND_TYPE_ID, {44}}}));
+  instructions.push_back(MakeInstructionMessage(
+      SpvOpStore, 0, 0,
+      {{SPV_OPERAND_TYPE_ID, {53}}, {SPV_OPERAND_TYPE_ID, {52}}}));
+  instructions.push_back(
+      MakeInstructionMessage(SpvOpLoad, 15, 58, {{SPV_OPERAND_TYPE_ID, {10}}}));
+  instructions.push_back(
+      MakeInstructionMessage(SpvOpLoad, 6, 63, {{SPV_OPERAND_TYPE_ID, {11}}}));
+  instructions.push_back(MakeInstructionMessage(SpvOpAccessChain, 64, 65,
+                                                {{SPV_OPERAND_TYPE_ID, {62}},
+                                                 {SPV_OPERAND_TYPE_ID, {21}},
+                                                 {SPV_OPERAND_TYPE_ID, {63}}}));
+  instructions.push_back(MakeInstructionMessage(SpvOpAccessChain, 64, 101,
+                                                {{SPV_OPERAND_TYPE_ID, {62}},
+                                                 {SPV_OPERAND_TYPE_ID, {45}},
+                                                 {SPV_OPERAND_TYPE_ID, {46}}}));
+  instructions.push_back(
+      MakeInstructionMessage(SpvOpLoad, 54, 66, {{SPV_OPERAND_TYPE_ID, {65}}}));
+  instructions.push_back(MakeInstructionMessage(
+      SpvOpAccessChain, 64, 67,
+      {{SPV_OPERAND_TYPE_ID, {57}}, {SPV_OPERAND_TYPE_ID, {58}}}));
+  instructions.push_back(MakeInstructionMessage(
+      SpvOpStore, 0, 0,
+      {{SPV_OPERAND_TYPE_ID, {67}}, {SPV_OPERAND_TYPE_ID, {66}}}));
+  instructions.push_back(
+      MakeInstructionMessage(SpvOpLoad, 6, 68, {{SPV_OPERAND_TYPE_ID, {9}}}));
+  instructions.push_back(MakeInstructionMessage(
+      SpvOpInBoundsAccessChain, 64, 70,
+      {{SPV_OPERAND_TYPE_ID, {57}}, {SPV_OPERAND_TYPE_ID, {68}}}));
+  instructions.push_back(MakeInstructionMessage(
+      SpvOpStore, 0, 0,
+      {{SPV_OPERAND_TYPE_ID, {70}}, {SPV_OPERAND_TYPE_ID, {69}}}));
+  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()));
+
+  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()));
+  // The function should not be deemed livesafe
+  ASSERT_FALSE(
+      transformation_context1.GetFactManager()->FunctionIsLivesafe(12));
+  // All variables/parameters in the function should be deemed irrelevant.
+  ASSERT_TRUE(AllVariablesAndParametersExceptLoopLimiterAreIrrelevant(
+      context1.get(), transformation_context1, 12, 0));
+
+  std::string added_as_dead_code = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+        %100 = OpTypeBool
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+         %15 = OpTypeInt 32 0
+        %102 = OpTypePointer Function %15
+          %8 = OpTypeFunction %2 %7 %102 %7
+         %16 = OpConstant %15 5
+         %17 = OpTypeArray %6 %16
+         %18 = OpTypeArray %17 %16
+         %19 = OpTypePointer Private %18
+         %20 = OpVariable %19 Private
+         %21 = OpConstant %6 0
+         %23 = OpTypePointer Private %6
+         %26 = OpTypePointer Function %17
+         %29 = OpTypePointer Private %17
+         %33 = OpConstant %6 4
+        %200 = OpConstant %15 4
+         %35 = OpConstant %15 10
+         %36 = OpTypeArray %6 %35
+         %37 = OpTypePointer Private %36
+         %38 = OpVariable %37 Private
+         %54 = OpTypeFloat 32
+         %55 = OpTypeVector %54 4
+         %56 = OpTypePointer Private %55
+         %57 = OpVariable %56 Private
+         %59 = OpTypeVector %54 3
+         %60 = OpTypeMatrix %59 2
+         %61 = OpTypePointer Private %60
+         %62 = OpVariable %61 Private
+         %64 = OpTypePointer Private %54
+         %69 = OpConstant %54 2
+         %71 = OpConstant %6 1
+         %72 = OpConstant %6 2
+        %201 = OpConstant %15 2
+         %73 = OpConstant %6 3
+        %202 = OpConstant %15 3
+        %203 = OpConstant %6 1
+        %204 = OpConstant %6 9
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %2 None %8
+          %9 = OpFunctionParameter %7
+         %10 = OpFunctionParameter %102
+         %11 = OpFunctionParameter %7
+         %13 = OpLabel
+         %14 = OpVariable %7 Function
+         %27 = OpVariable %26 Function
+         %22 = OpLoad %6 %11
+         %24 = OpAccessChain %23 %20 %21 %22
+         %25 = OpLoad %6 %24
+               OpStore %14 %25
+         %28 = OpLoad %15 %10
+         %30 = OpAccessChain %29 %20 %28
+         %31 = OpLoad %17 %30
+               OpStore %27 %31
+         %32 = OpLoad %6 %9
+         %34 = OpInBoundsAccessChain %7 %27 %32
+               OpStore %34 %33
+         %39 = OpLoad %6 %9
+         %40 = OpAccessChain %23 %38 %33
+         %41 = OpLoad %6 %40
+         %42 = OpInBoundsAccessChain %23 %38 %39
+               OpStore %42 %41
+         %43 = OpLoad %15 %10
+         %44 = OpLoad %6 %11
+         %45 = OpLoad %6 %9
+         %46 = OpLoad %15 %10
+         %47 = OpIAdd %6 %45 %46
+         %48 = OpAccessChain %23 %38 %47
+         %49 = OpLoad %6 %48
+         %50 = OpInBoundsAccessChain %23 %20 %43 %44
+         %51 = OpLoad %6 %50
+         %52 = OpIAdd %6 %51 %49
+         %53 = OpAccessChain %23 %20 %43 %44
+               OpStore %53 %52
+         %58 = OpLoad %15 %10
+         %63 = OpLoad %6 %11
+         %65 = OpAccessChain %64 %62 %21 %63
+        %101 = OpAccessChain %64 %62 %45 %46
+         %66 = OpLoad %54 %65
+         %67 = OpAccessChain %64 %57 %58
+               OpStore %67 %66
+         %68 = OpLoad %6 %9
+         %70 = OpInBoundsAccessChain %64 %57 %68
+               OpStore %70 %69
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, added_as_dead_code, context1.get()));
+
+  std::vector<protobufs::AccessChainClampingInfo> access_chain_clamping_info;
+  access_chain_clamping_info.push_back(
+      MakeAccessClampingInfo(24, {{1001, 2001}, {1002, 2002}}));
+  access_chain_clamping_info.push_back(
+      MakeAccessClampingInfo(30, {{1003, 2003}}));
+  access_chain_clamping_info.push_back(
+      MakeAccessClampingInfo(34, {{1004, 2004}}));
+  access_chain_clamping_info.push_back(
+      MakeAccessClampingInfo(40, {{1005, 2005}}));
+  access_chain_clamping_info.push_back(
+      MakeAccessClampingInfo(42, {{1006, 2006}}));
+  access_chain_clamping_info.push_back(
+      MakeAccessClampingInfo(48, {{1007, 2007}}));
+  access_chain_clamping_info.push_back(
+      MakeAccessClampingInfo(50, {{1008, 2008}, {1009, 2009}}));
+  access_chain_clamping_info.push_back(
+      MakeAccessClampingInfo(53, {{1010, 2010}, {1011, 2011}}));
+  access_chain_clamping_info.push_back(
+      MakeAccessClampingInfo(65, {{1012, 2012}, {1013, 2013}}));
+  access_chain_clamping_info.push_back(
+      MakeAccessClampingInfo(101, {{1014, 2014}, {1015, 2015}}));
+  access_chain_clamping_info.push_back(
+      MakeAccessClampingInfo(67, {{1016, 2016}}));
+  access_chain_clamping_info.push_back(
+      MakeAccessClampingInfo(70, {{1017, 2017}}));
+
+  TransformationAddFunction add_livesafe_function(instructions, 0, 0, {}, 13,
+                                                  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()));
+  // The function should be deemed livesafe
+  ASSERT_TRUE(transformation_context2.GetFactManager()->FunctionIsLivesafe(12));
+  // All variables/parameters in the function should be deemed irrelevant.
+  ASSERT_TRUE(AllVariablesAndParametersExceptLoopLimiterAreIrrelevant(
+      context2.get(), transformation_context2, 12, 0));
+  std::string added_as_livesafe_code = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+        %100 = OpTypeBool
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+         %15 = OpTypeInt 32 0
+        %102 = OpTypePointer Function %15
+          %8 = OpTypeFunction %2 %7 %102 %7
+         %16 = OpConstant %15 5
+         %17 = OpTypeArray %6 %16
+         %18 = OpTypeArray %17 %16
+         %19 = OpTypePointer Private %18
+         %20 = OpVariable %19 Private
+         %21 = OpConstant %6 0
+         %23 = OpTypePointer Private %6
+         %26 = OpTypePointer Function %17
+         %29 = OpTypePointer Private %17
+         %33 = OpConstant %6 4
+        %200 = OpConstant %15 4
+         %35 = OpConstant %15 10
+         %36 = OpTypeArray %6 %35
+         %37 = OpTypePointer Private %36
+         %38 = OpVariable %37 Private
+         %54 = OpTypeFloat 32
+         %55 = OpTypeVector %54 4
+         %56 = OpTypePointer Private %55
+         %57 = OpVariable %56 Private
+         %59 = OpTypeVector %54 3
+         %60 = OpTypeMatrix %59 2
+         %61 = OpTypePointer Private %60
+         %62 = OpVariable %61 Private
+         %64 = OpTypePointer Private %54
+         %69 = OpConstant %54 2
+         %71 = OpConstant %6 1
+         %72 = OpConstant %6 2
+        %201 = OpConstant %15 2
+         %73 = OpConstant %6 3
+        %202 = OpConstant %15 3
+        %203 = OpConstant %6 1
+        %204 = OpConstant %6 9
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %2 None %8
+          %9 = OpFunctionParameter %7
+         %10 = OpFunctionParameter %102
+         %11 = OpFunctionParameter %7
+         %13 = OpLabel
+         %14 = OpVariable %7 Function
+         %27 = OpVariable %26 Function
+         %22 = OpLoad %6 %11
+       %1002 = OpULessThanEqual %100 %22 %33
+       %2002 = OpSelect %6 %1002 %22 %33
+         %24 = OpAccessChain %23 %20 %21 %2002
+         %25 = OpLoad %6 %24
+               OpStore %14 %25
+         %28 = OpLoad %15 %10
+       %1003 = OpULessThanEqual %100 %28 %200
+       %2003 = OpSelect %15 %1003 %28 %200
+         %30 = OpAccessChain %29 %20 %2003
+         %31 = OpLoad %17 %30
+               OpStore %27 %31
+         %32 = OpLoad %6 %9
+       %1004 = OpULessThanEqual %100 %32 %33
+       %2004 = OpSelect %6 %1004 %32 %33
+         %34 = OpInBoundsAccessChain %7 %27 %2004
+               OpStore %34 %33
+         %39 = OpLoad %6 %9
+         %40 = OpAccessChain %23 %38 %33
+         %41 = OpLoad %6 %40
+       %1006 = OpULessThanEqual %100 %39 %204
+       %2006 = OpSelect %6 %1006 %39 %204
+         %42 = OpInBoundsAccessChain %23 %38 %2006
+               OpStore %42 %41
+         %43 = OpLoad %15 %10
+         %44 = OpLoad %6 %11
+         %45 = OpLoad %6 %9
+         %46 = OpLoad %15 %10
+         %47 = OpIAdd %6 %45 %46
+       %1007 = OpULessThanEqual %100 %47 %204
+       %2007 = OpSelect %6 %1007 %47 %204
+         %48 = OpAccessChain %23 %38 %2007
+         %49 = OpLoad %6 %48
+       %1008 = OpULessThanEqual %100 %43 %200
+       %2008 = OpSelect %15 %1008 %43 %200
+       %1009 = OpULessThanEqual %100 %44 %33
+       %2009 = OpSelect %6 %1009 %44 %33
+         %50 = OpInBoundsAccessChain %23 %20 %2008 %2009
+         %51 = OpLoad %6 %50
+         %52 = OpIAdd %6 %51 %49
+       %1010 = OpULessThanEqual %100 %43 %200
+       %2010 = OpSelect %15 %1010 %43 %200
+       %1011 = OpULessThanEqual %100 %44 %33
+       %2011 = OpSelect %6 %1011 %44 %33
+         %53 = OpAccessChain %23 %20 %2010 %2011
+               OpStore %53 %52
+         %58 = OpLoad %15 %10
+         %63 = OpLoad %6 %11
+       %1013 = OpULessThanEqual %100 %63 %72
+       %2013 = OpSelect %6 %1013 %63 %72
+         %65 = OpAccessChain %64 %62 %21 %2013
+       %1014 = OpULessThanEqual %100 %45 %71
+       %2014 = OpSelect %6 %1014 %45 %71
+       %1015 = OpULessThanEqual %100 %46 %201
+       %2015 = OpSelect %15 %1015 %46 %201
+        %101 = OpAccessChain %64 %62 %2014 %2015
+         %66 = OpLoad %54 %65
+       %1016 = OpULessThanEqual %100 %58 %202
+       %2016 = OpSelect %15 %1016 %58 %202
+         %67 = OpAccessChain %64 %57 %2016
+               OpStore %67 %66
+         %68 = OpLoad %6 %9
+       %1017 = OpULessThanEqual %100 %68 %73
+       %2017 = OpSelect %6 %1017 %68 %73
+         %70 = OpInBoundsAccessChain %64 %57 %2017
+               OpStore %70 %69
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, added_as_livesafe_code, context2.get()));
+}
+
+TEST(TransformationAddFunctionTest, LivesafeCanCallLivesafe) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+
+  std::vector<protobufs::Instruction> instructions;
+
+  instructions.push_back(MakeInstructionMessage(
+      SpvOpFunction, 2, 8,
+      {{SPV_OPERAND_TYPE_FUNCTION_CONTROL, {SpvFunctionControlMaskNone}},
+       {SPV_OPERAND_TYPE_TYPE_ID, {3}}}));
+  instructions.push_back(MakeInstructionMessage(SpvOpLabel, 0, 9, {}));
+  instructions.push_back(MakeInstructionMessage(SpvOpFunctionCall, 2, 11,
+                                                {{SPV_OPERAND_TYPE_ID, {6}}}));
+  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);
+
+  // 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()));
+  // The function should not be deemed livesafe
+  ASSERT_FALSE(transformation_context1.GetFactManager()->FunctionIsLivesafe(8));
+  // All variables/parameters in the function should be deemed irrelevant.
+  ASSERT_TRUE(AllVariablesAndParametersExceptLoopLimiterAreIrrelevant(
+      context1.get(), transformation_context1, 8, 0));
+
+  std::string added_as_live_or_dead_code = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+               OpReturn
+               OpFunctionEnd
+          %8 = OpFunction %2 None %3
+          %9 = OpLabel
+         %11 = OpFunctionCall %2 %6
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, added_as_live_or_dead_code, context1.get()));
+
+  TransformationAddFunction add_livesafe_function(instructions, 0, 0, {}, 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()));
+  // The function should be deemed livesafe
+  ASSERT_TRUE(transformation_context2.GetFactManager()->FunctionIsLivesafe(8));
+  // All variables/parameters in the function should be deemed irrelevant.
+  ASSERT_TRUE(AllVariablesAndParametersExceptLoopLimiterAreIrrelevant(
+      context2.get(), transformation_context2, 8, 0));
+  ASSERT_TRUE(IsEqual(env, added_as_live_or_dead_code, context2.get()));
+}
+
+TEST(TransformationAddFunctionTest, LivesafeOnlyCallsLivesafe) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+               OpKill
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+
+  std::vector<protobufs::Instruction> instructions;
+
+  instructions.push_back(MakeInstructionMessage(
+      SpvOpFunction, 2, 8,
+      {{SPV_OPERAND_TYPE_FUNCTION_CONTROL, {SpvFunctionControlMaskNone}},
+       {SPV_OPERAND_TYPE_TYPE_ID, {3}}}));
+  instructions.push_back(MakeInstructionMessage(SpvOpLabel, 0, 9, {}));
+  instructions.push_back(MakeInstructionMessage(SpvOpFunctionCall, 2, 11,
+                                                {{SPV_OPERAND_TYPE_ID, {6}}}));
+  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()));
+
+  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()));
+  // The function should not be deemed livesafe
+  ASSERT_FALSE(transformation_context1.GetFactManager()->FunctionIsLivesafe(8));
+  // All variables/parameters in the function should be deemed irrelevant.
+  ASSERT_TRUE(AllVariablesAndParametersExceptLoopLimiterAreIrrelevant(
+      context1.get(), transformation_context1, 8, 0));
+
+  std::string added_as_dead_code = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+               OpKill
+               OpFunctionEnd
+          %8 = OpFunction %2 None %3
+          %9 = OpLabel
+         %11 = OpFunctionCall %2 %6
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, added_as_dead_code, context1.get()));
+
+  TransformationAddFunction add_livesafe_function(instructions, 0, 0, {}, 0,
+                                                  {});
+  ASSERT_FALSE(add_livesafe_function.IsApplicable(context2.get(),
+                                                  transformation_context2));
+}
+
+TEST(TransformationAddFunctionTest,
+     LoopLimitersBackEdgeBlockEndsWithConditional1) {
+  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
+          %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
+  )";
+
+  std::string donor = 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
+         %10 = OpVariable %9 Function
+               OpStore %10 %11
+               OpBranch %12
+         %12 = OpLabel
+               OpLoopMerge %14 %15 None
+               OpBranch %15
+         %15 = OpLabel
+         %17 = OpLoad %8 %10
+         %20 = OpSLessThan %19 %17 %18
+         %21 = OpLoad %8 %10
+         %23 = OpIAdd %8 %21 %22
+               OpStore %10 %23
+               OpBranchConditional %20 %12 %14
+         %14 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  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()));
+
+  // Make a sequence of instruction messages corresponding to function %8 in
+  // |donor|.
+  std::vector<protobufs::Instruction> instructions =
+      GetInstructionsForFunction(env, consumer, donor, 6);
+
+  protobufs::LoopLimiterInfo loop_limiter_info;
+  loop_limiter_info.set_loop_header_id(12);
+  loop_limiter_info.set_load_id(102);
+  loop_limiter_info.set_increment_id(103);
+  loop_limiter_info.set_compare_id(104);
+  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
+               OpLoopMerge %14 %15 None
+               OpBranch %15
+         %15 = OpLabel
+         %17 = OpLoad %8 %10
+         %20 = OpSLessThan %19 %17 %18
+         %21 = OpLoad %8 %10
+         %23 = OpIAdd %8 %21 %22
+               OpStore %10 %23
+        %102 = OpLoad %28 %100
+        %103 = OpIAdd %28 %102 %31
+               OpStore %100 %103
+        %104 = OpULessThan %19 %102 %32
+        %105 = OpLogicalAnd %19 %20 %104
+               OpBranchConditional %105 %12 %14
+         %14 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, expected, context.get()));
+}
+
+TEST(TransformationAddFunctionTest,
+     LoopLimitersBackEdgeBlockEndsWithConditional2) {
+  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
+          %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
+  )";
+
+  std::string donor = 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
+         %10 = OpVariable %9 Function
+               OpStore %10 %11
+               OpBranch %12
+         %12 = OpLabel
+               OpLoopMerge %14 %15 None
+               OpBranch %15
+         %15 = OpLabel
+         %17 = OpLoad %8 %10
+         %20 = OpSLessThan %19 %17 %18
+         %21 = OpLoad %8 %10
+         %23 = OpIAdd %8 %21 %22
+               OpStore %10 %23
+         %50 = OpLogicalNot %19 %20
+               OpBranchConditional %50 %14 %12
+         %14 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  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()));
+
+  // Make a sequence of instruction messages corresponding to function %8 in
+  // |donor|.
+  std::vector<protobufs::Instruction> instructions =
+      GetInstructionsForFunction(env, consumer, donor, 6);
+
+  protobufs::LoopLimiterInfo loop_limiter_info;
+  loop_limiter_info.set_loop_header_id(12);
+  loop_limiter_info.set_load_id(102);
+  loop_limiter_info.set_increment_id(103);
+  loop_limiter_info.set_compare_id(104);
+  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
+               OpLoopMerge %14 %15 None
+               OpBranch %15
+         %15 = OpLabel
+         %17 = OpLoad %8 %10
+         %20 = OpSLessThan %19 %17 %18
+         %21 = OpLoad %8 %10
+         %23 = OpIAdd %8 %21 %22
+               OpStore %10 %23
+         %50 = OpLogicalNot %19 %20
+        %102 = OpLoad %28 %100
+        %103 = OpIAdd %28 %102 %31
+               OpStore %100 %103
+        %104 = OpUGreaterThanEqual %19 %102 %32
+        %105 = OpLogicalOr %19 %50 %104
+               OpBranchConditional %105 %14 %12
+         %14 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, expected, context.get()));
+}
+
+TEST(TransformationAddFunctionTest, LoopLimitersHeaderIsBackEdgeBlock) {
+  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
+          %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
+  )";
+
+  std::string donor = 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
+         %10 = OpVariable %9 Function
+               OpStore %10 %11
+               OpBranch %12
+         %12 = OpLabel
+         %17 = OpLoad %8 %10
+         %20 = OpSLessThan %19 %17 %18
+         %21 = OpLoad %8 %10
+         %23 = OpIAdd %8 %21 %22
+               OpStore %10 %23
+         %50 = OpLogicalNot %19 %20
+               OpLoopMerge %14 %12 None
+               OpBranchConditional %50 %14 %12
+         %14 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  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()));
+
+  // Make a sequence of instruction messages corresponding to function %8 in
+  // |donor|.
+  std::vector<protobufs::Instruction> instructions =
+      GetInstructionsForFunction(env, consumer, donor, 6);
+
+  protobufs::LoopLimiterInfo loop_limiter_info;
+  loop_limiter_info.set_loop_header_id(12);
+  loop_limiter_info.set_load_id(102);
+  loop_limiter_info.set_increment_id(103);
+  loop_limiter_info.set_compare_id(104);
+  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
+         %17 = OpLoad %8 %10
+         %20 = OpSLessThan %19 %17 %18
+         %21 = OpLoad %8 %10
+         %23 = OpIAdd %8 %21 %22
+               OpStore %10 %23
+         %50 = OpLogicalNot %19 %20
+        %102 = OpLoad %28 %100
+        %103 = OpIAdd %28 %102 %31
+               OpStore %100 %103
+        %104 = OpUGreaterThanEqual %19 %102 %32
+        %105 = OpLogicalOr %19 %50 %104
+               OpLoopMerge %14 %12 None
+               OpBranchConditional %105 %14 %12
+         %14 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, expected, context.get()));
+}
+
+TEST(TransformationAddFunctionTest, InfiniteLoop1) {
+  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
+          %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
+  )";
+
+  std::string donor = 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
+         %10 = OpVariable %9 Function
+               OpStore %10 %11
+               OpBranch %12
+         %12 = OpLabel
+               OpLoopMerge %14 %12 None
+               OpBranch %12
+         %14 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  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()));
+
+  // Make a sequence of instruction messages corresponding to function %8 in
+  // |donor|.
+  std::vector<protobufs::Instruction> instructions =
+      GetInstructionsForFunction(env, consumer, donor, 6);
+
+  protobufs::LoopLimiterInfo loop_limiter_info;
+  loop_limiter_info.set_loop_header_id(12);
+  loop_limiter_info.set_load_id(102);
+  loop_limiter_info.set_increment_id(103);
+  loop_limiter_info.set_compare_id(104);
+  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()));
+}
+
+TEST(TransformationAddFunctionTest, UnreachableContinueConstruct) {
+  // This captures the case where the loop's continue construct is statically
+  // unreachable.  In this case the loop cannot iterate and so we do not add
+  // a loop limiter.  (The reason we do not just add one anyway is that
+  // detecting which block would be the back-edge block is difficult in the
+  // absence of reliable dominance information.)
+  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
+          %8 = OpTypeInt 32 1
+          %9 = OpTypePointer Function %8
+         %11 = OpConstant %8 0
+         %18 = OpConstant %8 10
+         %19 = OpTypeBool
+         %23 = OpConstant %8 1
+         %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
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  std::string donor = 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
+         %23 = OpConstant %8 1
+         %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
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+         %10 = OpVariable %9 Function
+               OpStore %10 %11
+               OpBranch %12
+         %12 = OpLabel
+               OpLoopMerge %14 %15 None
+               OpBranch %16
+         %16 = OpLabel
+         %17 = OpLoad %8 %10
+         %20 = OpSLessThan %19 %17 %18
+               OpBranchConditional %20 %13 %14
+         %13 = OpLabel
+               OpBranch %14
+         %15 = OpLabel
+         %22 = OpLoad %8 %10
+         %24 = OpIAdd %8 %22 %23
+               OpStore %10 %24
+               OpBranch %12
+         %14 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  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()));
+
+  // Make a sequence of instruction messages corresponding to function %8 in
+  // |donor|.
+  std::vector<protobufs::Instruction> instructions =
+      GetInstructionsForFunction(env, consumer, donor, 6);
+
+  protobufs::LoopLimiterInfo loop_limiter_info;
+  loop_limiter_info.set_loop_header_id(12);
+  loop_limiter_info.set_load_id(102);
+  loop_limiter_info.set_increment_id(103);
+  loop_limiter_info.set_compare_id(104);
+  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
+         %23 = OpConstant %8 1
+         %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
+          %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
+               OpLoopMerge %14 %15 None
+               OpBranch %16
+         %16 = OpLabel
+         %17 = OpLoad %8 %10
+         %20 = OpSLessThan %19 %17 %18
+               OpBranchConditional %20 %13 %14
+         %13 = OpLabel
+               OpBranch %14
+         %15 = OpLabel
+         %22 = OpLoad %8 %10
+         %24 = OpIAdd %8 %22 %23
+               OpStore %10 %24
+               OpBranch %12
+         %14 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, expected, context.get()));
+}
+
+TEST(TransformationAddFunctionTest, LoopLimitersAndOpPhi1) {
+  // This captures the scenario where breaking a loop due to a loop limiter
+  // requires patching up OpPhi instructions occurring at the loop merge block.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+         %50 = OpTypeInt 32 0
+         %51 = OpConstant %50 0
+         %52 = OpConstant %50 1
+         %53 = OpTypePointer Function %50
+          %7 = OpTypeFunction %6
+         %10 = OpTypePointer Function %6
+         %12 = OpConstant %6 0
+         %19 = OpConstant %6 100
+         %20 = OpTypeBool
+         %23 = OpConstant %6 20
+         %28 = OpConstant %6 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  std::string donor = 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 = OpTypeFunction %6
+         %10 = OpTypePointer Function %6
+         %12 = OpConstant %6 0
+         %19 = OpConstant %6 100
+         %20 = OpTypeBool
+         %23 = OpConstant %6 20
+         %28 = OpConstant %6 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %36 = OpFunctionCall %6 %8
+               OpReturn
+               OpFunctionEnd
+          %8 = OpFunction %6 None %7
+          %9 = OpLabel
+         %11 = OpVariable %10 Function
+               OpStore %11 %12
+               OpBranch %13
+         %13 = OpLabel
+         %37 = OpPhi %6 %12 %9 %32 %16
+               OpLoopMerge %15 %16 None
+               OpBranch %17
+         %17 = OpLabel
+         %21 = OpSLessThan %20 %37 %19
+               OpBranchConditional %21 %14 %15
+         %14 = OpLabel
+         %24 = OpSGreaterThan %20 %37 %23
+               OpSelectionMerge %26 None
+               OpBranchConditional %24 %25 %26
+         %25 = OpLabel
+         %29 = OpIAdd %6 %37 %28
+               OpStore %11 %29
+               OpBranch %15
+         %26 = OpLabel
+               OpBranch %16
+         %16 = OpLabel
+         %32 = OpIAdd %6 %37 %28
+               OpStore %11 %32
+               OpBranch %13
+         %15 = OpLabel
+         %38 = OpPhi %6 %37 %17 %29 %25
+               OpReturnValue %38
+               OpFunctionEnd
+  )";
+
+  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()));
+
+  // Make a sequence of instruction messages corresponding to function %8 in
+  // |donor|.
+  std::vector<protobufs::Instruction> instructions =
+      GetInstructionsForFunction(env, consumer, donor, 8);
+
+  protobufs::LoopLimiterInfo loop_limiter_info;
+  loop_limiter_info.set_loop_header_id(13);
+  loop_limiter_info.set_load_id(102);
+  loop_limiter_info.set_increment_id(103);
+  loop_limiter_info.set_compare_id(104);
+  loop_limiter_info.set_logical_op_id(105);
+
+  TransformationAddFunction no_op_phi_data(instructions, 100, 28,
+                                           {loop_limiter_info}, 0, {});
+  // The loop limiter info is not good enough; it does not include ids to patch
+  // up the OpPhi at the loop merge.
+  ASSERT_FALSE(
+      no_op_phi_data.IsApplicable(context.get(), transformation_context));
+
+  // Add a phi id for the new edge from the loop back edge block to the loop
+  // merge.
+  loop_limiter_info.add_phi_id(28);
+  TransformationAddFunction with_op_phi_data(instructions, 100, 28,
+                                             {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()));
+  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
+          %6 = OpTypeInt 32 1
+         %50 = OpTypeInt 32 0
+         %51 = OpConstant %50 0
+         %52 = OpConstant %50 1
+         %53 = OpTypePointer Function %50
+          %7 = OpTypeFunction %6
+         %10 = OpTypePointer Function %6
+         %12 = OpConstant %6 0
+         %19 = OpConstant %6 100
+         %20 = OpTypeBool
+         %23 = OpConstant %6 20
+         %28 = OpConstant %6 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+          %8 = OpFunction %6 None %7
+          %9 = OpLabel
+        %100 = OpVariable %53 Function %51
+         %11 = OpVariable %10 Function
+               OpStore %11 %12
+               OpBranch %13
+         %13 = OpLabel
+         %37 = OpPhi %6 %12 %9 %32 %16
+               OpLoopMerge %15 %16 None
+               OpBranch %17
+         %17 = OpLabel
+         %21 = OpSLessThan %20 %37 %19
+               OpBranchConditional %21 %14 %15
+         %14 = OpLabel
+         %24 = OpSGreaterThan %20 %37 %23
+               OpSelectionMerge %26 None
+               OpBranchConditional %24 %25 %26
+         %25 = OpLabel
+         %29 = OpIAdd %6 %37 %28
+               OpStore %11 %29
+               OpBranch %15
+         %26 = OpLabel
+               OpBranch %16
+         %16 = OpLabel
+         %32 = OpIAdd %6 %37 %28
+               OpStore %11 %32
+        %102 = OpLoad %50 %100
+        %103 = OpIAdd %50 %102 %52
+               OpStore %100 %103
+        %104 = OpUGreaterThanEqual %20 %102 %28
+               OpBranchConditional %104 %15 %13
+         %15 = OpLabel
+         %38 = OpPhi %6 %37 %17 %29 %25 %28 %16
+               OpReturnValue %38
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, expected, context.get()));
+}
+
+TEST(TransformationAddFunctionTest, LoopLimitersAndOpPhi2) {
+  // This captures the scenario where the loop merge block already has an OpPhi
+  // with the loop back edge block as a predecessor.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+         %50 = OpTypeInt 32 0
+         %51 = OpConstant %50 0
+         %52 = OpConstant %50 1
+         %53 = OpTypePointer Function %50
+          %7 = OpTypeFunction %6
+         %10 = OpTypePointer Function %6
+         %12 = OpConstant %6 0
+         %19 = OpConstant %6 100
+         %20 = OpTypeBool
+         %60 = OpConstantTrue %20
+         %23 = OpConstant %6 20
+         %28 = OpConstant %6 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  std::string donor = 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
+         %50 = OpTypeInt 32 0
+         %51 = OpConstant %50 0
+         %52 = OpConstant %50 1
+         %53 = OpTypePointer Function %50
+          %7 = OpTypeFunction %6
+         %10 = OpTypePointer Function %6
+         %12 = OpConstant %6 0
+         %19 = OpConstant %6 100
+         %20 = OpTypeBool
+         %60 = OpConstantTrue %20
+         %23 = OpConstant %6 20
+         %28 = OpConstant %6 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+          %8 = OpFunction %6 None %7
+          %9 = OpLabel
+         %11 = OpVariable %10 Function
+               OpStore %11 %12
+               OpBranch %13
+         %13 = OpLabel
+         %37 = OpPhi %6 %12 %9 %32 %16
+               OpLoopMerge %15 %16 None
+               OpBranch %17
+         %17 = OpLabel
+         %21 = OpSLessThan %20 %37 %19
+               OpBranchConditional %21 %14 %15
+         %14 = OpLabel
+         %24 = OpSGreaterThan %20 %37 %23
+               OpSelectionMerge %26 None
+               OpBranchConditional %24 %25 %26
+         %25 = OpLabel
+         %29 = OpIAdd %6 %37 %28
+               OpStore %11 %29
+               OpBranch %15
+         %26 = OpLabel
+               OpBranch %16
+         %16 = OpLabel
+         %32 = OpIAdd %6 %37 %28
+               OpStore %11 %32
+               OpBranchConditional %60 %15 %13
+         %15 = OpLabel
+         %38 = OpPhi %6 %37 %17 %29 %25 %23 %16
+               OpReturnValue %38
+               OpFunctionEnd
+  )";
+
+  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()));
+
+  // Make a sequence of instruction messages corresponding to function %8 in
+  // |donor|.
+  std::vector<protobufs::Instruction> instructions =
+      GetInstructionsForFunction(env, consumer, donor, 8);
+
+  protobufs::LoopLimiterInfo loop_limiter_info;
+  loop_limiter_info.set_loop_header_id(13);
+  loop_limiter_info.set_load_id(102);
+  loop_limiter_info.set_increment_id(103);
+  loop_limiter_info.set_compare_id(104);
+  loop_limiter_info.set_logical_op_id(105);
+
+  TransformationAddFunction transformation(instructions, 100, 28,
+                                           {loop_limiter_info}, 0, {});
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  transformation.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
+          %6 = OpTypeInt 32 1
+         %50 = OpTypeInt 32 0
+         %51 = OpConstant %50 0
+         %52 = OpConstant %50 1
+         %53 = OpTypePointer Function %50
+          %7 = OpTypeFunction %6
+         %10 = OpTypePointer Function %6
+         %12 = OpConstant %6 0
+         %19 = OpConstant %6 100
+         %20 = OpTypeBool
+         %60 = OpConstantTrue %20
+         %23 = OpConstant %6 20
+         %28 = OpConstant %6 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+          %8 = OpFunction %6 None %7
+          %9 = OpLabel
+        %100 = OpVariable %53 Function %51
+         %11 = OpVariable %10 Function
+               OpStore %11 %12
+               OpBranch %13
+         %13 = OpLabel
+         %37 = OpPhi %6 %12 %9 %32 %16
+               OpLoopMerge %15 %16 None
+               OpBranch %17
+         %17 = OpLabel
+         %21 = OpSLessThan %20 %37 %19
+               OpBranchConditional %21 %14 %15
+         %14 = OpLabel
+         %24 = OpSGreaterThan %20 %37 %23
+               OpSelectionMerge %26 None
+               OpBranchConditional %24 %25 %26
+         %25 = OpLabel
+         %29 = OpIAdd %6 %37 %28
+               OpStore %11 %29
+               OpBranch %15
+         %26 = OpLabel
+               OpBranch %16
+         %16 = OpLabel
+         %32 = OpIAdd %6 %37 %28
+               OpStore %11 %32
+        %102 = OpLoad %50 %100
+        %103 = OpIAdd %50 %102 %52
+               OpStore %100 %103
+        %104 = OpUGreaterThanEqual %20 %102 %28
+        %105 = OpLogicalOr %20 %60 %104
+               OpBranchConditional %105 %15 %13
+         %15 = OpLabel
+         %38 = OpPhi %6 %37 %17 %29 %25 %23 %16
+               OpReturnValue %38
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, expected, context.get()));
 }
 
 }  // namespace
diff --git a/test/fuzz/transformation_add_global_undef_test.cpp b/test/fuzz/transformation_add_global_undef_test.cpp
index c14f7e9..8c06db0 100644
--- a/test/fuzz/transformation_add_global_undef_test.cpp
+++ b/test/fuzz/transformation_add_global_undef_test.cpp
@@ -47,17 +47,20 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   // Id already in use
-  ASSERT_FALSE(TransformationAddGlobalUndef(4, 11).IsApplicable(context.get(),
-                                                                fact_manager));
+  ASSERT_FALSE(TransformationAddGlobalUndef(4, 11).IsApplicable(
+      context.get(), transformation_context));
   // %1 is not a type
-  ASSERT_FALSE(TransformationAddGlobalUndef(100, 1).IsApplicable(context.get(),
-                                                                 fact_manager));
+  ASSERT_FALSE(TransformationAddGlobalUndef(100, 1).IsApplicable(
+      context.get(), transformation_context));
 
   // %3 is a function type
-  ASSERT_FALSE(TransformationAddGlobalUndef(100, 3).IsApplicable(context.get(),
-                                                                 fact_manager));
+  ASSERT_FALSE(TransformationAddGlobalUndef(100, 3).IsApplicable(
+      context.get(), transformation_context));
 
   TransformationAddGlobalUndef transformations[] = {
       // %100 = OpUndef %6
@@ -79,8 +82,9 @@
       TransformationAddGlobalUndef(105, 11)};
 
   for (auto& transformation : transformations) {
-    ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
-    transformation.Apply(context.get(), &fact_manager);
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    transformation.Apply(context.get(), &transformation_context);
   }
   ASSERT_TRUE(IsValid(env, context.get()));
 
diff --git a/test/fuzz/transformation_add_global_variable_test.cpp b/test/fuzz/transformation_add_global_variable_test.cpp
index eda6828..5c74ca0 100644
--- a/test/fuzz/transformation_add_global_variable_test.cpp
+++ b/test/fuzz/transformation_add_global_variable_test.cpp
@@ -30,8 +30,10 @@
           %2 = OpTypeVoid
           %3 = OpTypeFunction %2
           %6 = OpTypeFloat 32
+         %40 = OpConstant %6 0
           %7 = OpTypeInt 32 1
           %8 = OpTypeVector %6 2
+         %41 = OpConstantComposite %8 %40 %40
           %9 = OpTypePointer Function %6
          %10 = OpTypePointer Private %6
          %20 = OpTypePointer Uniform %6
@@ -58,73 +60,106 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   // Id already in use
-  ASSERT_FALSE(TransformationAddGlobalVariable(4, 10, 0).IsApplicable(
-      context.get(), fact_manager));
+  ASSERT_FALSE(
+      TransformationAddGlobalVariable(4, 10, SpvStorageClassPrivate, 0, true)
+          .IsApplicable(context.get(), transformation_context));
   // %1 is not a type
-  ASSERT_FALSE(TransformationAddGlobalVariable(100, 1, 0).IsApplicable(
-      context.get(), fact_manager));
+  ASSERT_FALSE(
+      TransformationAddGlobalVariable(100, 1, SpvStorageClassPrivate, 0, false)
+          .IsApplicable(context.get(), transformation_context));
 
   // %7 is not a pointer type
-  ASSERT_FALSE(TransformationAddGlobalVariable(100, 7, 0).IsApplicable(
-      context.get(), fact_manager));
+  ASSERT_FALSE(
+      TransformationAddGlobalVariable(100, 7, SpvStorageClassPrivate, 0, true)
+          .IsApplicable(context.get(), transformation_context));
 
   // %9 does not have Private storage class
-  ASSERT_FALSE(TransformationAddGlobalVariable(100, 9, 0).IsApplicable(
-      context.get(), fact_manager));
+  ASSERT_FALSE(
+      TransformationAddGlobalVariable(100, 9, SpvStorageClassPrivate, 0, false)
+          .IsApplicable(context.get(), transformation_context));
 
   // %15 does not have Private storage class
-  ASSERT_FALSE(TransformationAddGlobalVariable(100, 15, 0)
-                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      TransformationAddGlobalVariable(100, 15, SpvStorageClassPrivate, 0, true)
+          .IsApplicable(context.get(), transformation_context));
 
   // %10 is a pointer to float, while %16 is an int constant
-  ASSERT_FALSE(TransformationAddGlobalVariable(100, 10, 16)
-                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationAddGlobalVariable(100, 10, SpvStorageClassPrivate,
+                                               16, false)
+                   .IsApplicable(context.get(), transformation_context));
 
   // %10 is a Private pointer to float, while %15 is a variable with type
   // Uniform float pointer
-  ASSERT_FALSE(TransformationAddGlobalVariable(100, 10, 15)
-                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      TransformationAddGlobalVariable(100, 10, SpvStorageClassPrivate, 15, true)
+          .IsApplicable(context.get(), transformation_context));
 
   // %12 is a Private pointer to int, while %10 is a variable with type
   // Private float pointer
-  ASSERT_FALSE(TransformationAddGlobalVariable(100, 12, 10)
-                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationAddGlobalVariable(100, 12, SpvStorageClassPrivate,
+                                               10, false)
+                   .IsApplicable(context.get(), transformation_context));
 
   // %10 is pointer-to-float, and %14 has type pointer-to-float; that's not OK
   // since the initializer's type should be the *pointee* type.
-  ASSERT_FALSE(TransformationAddGlobalVariable(104, 10, 14)
-                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      TransformationAddGlobalVariable(104, 10, SpvStorageClassPrivate, 14, true)
+          .IsApplicable(context.get(), transformation_context));
 
   // This would work in principle, but logical addressing does not allow
   // a pointer to a pointer.
-  ASSERT_FALSE(TransformationAddGlobalVariable(104, 17, 14)
-                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationAddGlobalVariable(104, 17, SpvStorageClassPrivate,
+                                               14, false)
+                   .IsApplicable(context.get(), transformation_context));
 
   TransformationAddGlobalVariable transformations[] = {
       // %100 = OpVariable %12 Private
-      TransformationAddGlobalVariable(100, 12, 0),
+      TransformationAddGlobalVariable(100, 12, SpvStorageClassPrivate, 16,
+                                      true),
 
       // %101 = OpVariable %10 Private
-      TransformationAddGlobalVariable(101, 10, 0),
+      TransformationAddGlobalVariable(101, 10, SpvStorageClassPrivate, 40,
+                                      false),
 
       // %102 = OpVariable %13 Private
-      TransformationAddGlobalVariable(102, 13, 0),
+      TransformationAddGlobalVariable(102, 13, SpvStorageClassPrivate, 41,
+                                      true),
 
       // %103 = OpVariable %12 Private %16
-      TransformationAddGlobalVariable(103, 12, 16),
+      TransformationAddGlobalVariable(103, 12, SpvStorageClassPrivate, 16,
+                                      false),
 
       // %104 = OpVariable %19 Private %21
-      TransformationAddGlobalVariable(104, 19, 21),
+      TransformationAddGlobalVariable(104, 19, SpvStorageClassPrivate, 21,
+                                      true),
 
       // %105 = OpVariable %19 Private %22
-      TransformationAddGlobalVariable(105, 19, 22)};
+      TransformationAddGlobalVariable(105, 19, SpvStorageClassPrivate, 22,
+                                      false)};
 
   for (auto& transformation : transformations) {
-    ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
-    transformation.Apply(context.get(), &fact_manager);
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    transformation.Apply(context.get(), &transformation_context);
   }
+  ASSERT_TRUE(
+      transformation_context.GetFactManager()->PointeeValueIsIrrelevant(100));
+  ASSERT_TRUE(
+      transformation_context.GetFactManager()->PointeeValueIsIrrelevant(102));
+  ASSERT_TRUE(
+      transformation_context.GetFactManager()->PointeeValueIsIrrelevant(104));
+  ASSERT_FALSE(
+      transformation_context.GetFactManager()->PointeeValueIsIrrelevant(101));
+  ASSERT_FALSE(
+      transformation_context.GetFactManager()->PointeeValueIsIrrelevant(103));
+  ASSERT_FALSE(
+      transformation_context.GetFactManager()->PointeeValueIsIrrelevant(105));
+
   ASSERT_TRUE(IsValid(env, context.get()));
 
   std::string after_transformation = R"(
@@ -137,8 +172,10 @@
           %2 = OpTypeVoid
           %3 = OpTypeFunction %2
           %6 = OpTypeFloat 32
+         %40 = OpConstant %6 0
           %7 = OpTypeInt 32 1
           %8 = OpTypeVector %6 2
+         %41 = OpConstantComposite %8 %40 %40
           %9 = OpTypePointer Function %6
          %10 = OpTypePointer Private %6
          %20 = OpTypePointer Uniform %6
@@ -153,9 +190,9 @@
          %19 = OpTypePointer Private %18
          %21 = OpConstantTrue %18
          %22 = OpConstantFalse %18
-        %100 = OpVariable %12 Private
-        %101 = OpVariable %10 Private
-        %102 = OpVariable %13 Private
+        %100 = OpVariable %12 Private %16
+        %101 = OpVariable %10 Private %40
+        %102 = OpVariable %13 Private %41
         %103 = OpVariable %12 Private %16
         %104 = OpVariable %19 Private %21
         %105 = OpVariable %19 Private %22
@@ -212,21 +249,34 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   TransformationAddGlobalVariable transformations[] = {
       // %100 = OpVariable %12 Private
-      TransformationAddGlobalVariable(100, 12, 0),
+      TransformationAddGlobalVariable(100, 12, SpvStorageClassPrivate, 16,
+                                      true),
 
       // %101 = OpVariable %12 Private %16
-      TransformationAddGlobalVariable(101, 12, 16),
+      TransformationAddGlobalVariable(101, 12, SpvStorageClassPrivate, 16,
+                                      false),
 
       // %102 = OpVariable %19 Private %21
-      TransformationAddGlobalVariable(102, 19, 21)};
+      TransformationAddGlobalVariable(102, 19, SpvStorageClassPrivate, 21,
+                                      true)};
 
   for (auto& transformation : transformations) {
-    ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
-    transformation.Apply(context.get(), &fact_manager);
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    transformation.Apply(context.get(), &transformation_context);
   }
+  ASSERT_TRUE(
+      transformation_context.GetFactManager()->PointeeValueIsIrrelevant(100));
+  ASSERT_TRUE(
+      transformation_context.GetFactManager()->PointeeValueIsIrrelevant(102));
+  ASSERT_FALSE(
+      transformation_context.GetFactManager()->PointeeValueIsIrrelevant(101));
   ASSERT_TRUE(IsValid(env, context.get()));
 
   std::string after_transformation = R"(
@@ -255,7 +305,7 @@
          %18 = OpTypeBool
          %19 = OpTypePointer Private %18
          %21 = OpConstantTrue %18
-        %100 = OpVariable %12 Private
+        %100 = OpVariable %12 Private %16
         %101 = OpVariable %12 Private %16
         %102 = OpVariable %19 Private %21
           %4 = OpFunction %2 None %3
@@ -270,6 +320,85 @@
   ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
 }
 
+TEST(TransformationAddGlobalVariableTest, TestAddingWorkgroupGlobals) {
+  // This checks that workgroup globals can be added to a compute shader.
+  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 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Workgroup %6
+         %50 = OpConstant %6 2
+          %4 = OpFunction %2 None %3
+          %5 = 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;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
+#ifndef NDEBUG
+  ASSERT_DEATH(
+      TransformationAddGlobalVariable(8, 7, SpvStorageClassWorkgroup, 50, true)
+          .IsApplicable(context.get(), transformation_context),
+      "By construction this transformation should not have an.*initializer "
+      "when Workgroup storage class is used");
+#endif
+
+  TransformationAddGlobalVariable transformations[] = {
+      // %8 = OpVariable %7 Workgroup
+      TransformationAddGlobalVariable(8, 7, SpvStorageClassWorkgroup, 0, true),
+
+      // %10 = OpVariable %7 Workgroup
+      TransformationAddGlobalVariable(10, 7, SpvStorageClassWorkgroup, 0,
+                                      false)};
+
+  for (auto& transformation : transformations) {
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    transformation.Apply(context.get(), &transformation_context);
+  }
+  ASSERT_TRUE(
+      transformation_context.GetFactManager()->PointeeValueIsIrrelevant(8));
+  ASSERT_FALSE(
+      transformation_context.GetFactManager()->PointeeValueIsIrrelevant(10));
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main" %8 %10
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Workgroup %6
+         %50 = OpConstant %6 2
+          %8 = OpVariable %7 Workgroup
+         %10 = OpVariable %7 Workgroup
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
 }  // namespace
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/test/fuzz/transformation_add_local_variable_test.cpp b/test/fuzz/transformation_add_local_variable_test.cpp
new file mode 100644
index 0000000..e989b33
--- /dev/null
+++ b/test/fuzz/transformation_add_local_variable_test.cpp
@@ -0,0 +1,221 @@
+// 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_local_variable.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationAddLocalVariableTest, 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 = OpTypeStruct %6 %6
+          %8 = OpTypePointer Function %7
+         %10 = OpConstant %6 1
+         %11 = OpConstant %6 2
+         %12 = OpConstantComposite %7 %10 %11
+         %13 = OpTypeFloat 32
+         %14 = OpTypeInt 32 0
+         %15 = OpConstant %14 3
+         %16 = OpTypeArray %13 %15
+         %17 = OpTypeBool
+         %18 = OpTypeStruct %16 %7 %17
+         %19 = OpTypePointer Function %18
+         %21 = OpConstant %13 1
+         %22 = OpConstant %13 2
+         %23 = OpConstant %13 4
+         %24 = OpConstantComposite %16 %21 %22 %23
+         %25 = OpConstant %6 5
+         %26 = OpConstant %6 6
+         %27 = OpConstantComposite %7 %25 %26
+         %28 = OpConstantFalse %17
+         %29 = OpConstantComposite %18 %24 %27 %28
+         %30 = OpTypeVector %13 2
+         %31 = OpTypePointer Function %30
+         %33 = OpConstantComposite %30 %21 %21
+         %34 = OpTypeVector %17 3
+         %35 = OpTypePointer Function %34
+         %37 = OpConstantTrue %17
+         %38 = OpConstantComposite %34 %37 %28 %28
+         %39 = OpTypeVector %13 4
+         %40 = OpTypeMatrix %39 3
+         %41 = OpTypePointer Function %40
+         %43 = OpConstantComposite %39 %21 %22 %23 %21
+         %44 = OpConstantComposite %39 %22 %23 %21 %22
+         %45 = OpConstantComposite %39 %23 %21 %22 %23
+         %46 = OpConstantComposite %40 %43 %44 %45
+         %50 = OpTypePointer Function %14
+         %51 = OpConstantNull %14
+          %4 = OpFunction %2 None %3
+          %5 = 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;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
+  // A few cases of inapplicable transformations:
+  // Id 4 is already in use
+  ASSERT_FALSE(TransformationAddLocalVariable(4, 50, 4, 51, true)
+                   .IsApplicable(context.get(), transformation_context));
+  // Type mismatch between initializer and pointer
+  ASSERT_FALSE(TransformationAddLocalVariable(105, 46, 4, 51, true)
+                   .IsApplicable(context.get(), transformation_context));
+  // Id 5 is not a function
+  ASSERT_FALSE(TransformationAddLocalVariable(105, 50, 5, 51, true)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %105 = OpVariable %50 Function %51
+  {
+    TransformationAddLocalVariable transformation(105, 50, 4, 51, true);
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    transformation.Apply(context.get(), &transformation_context);
+  }
+
+  // %104 = OpVariable %41 Function %46
+  {
+    TransformationAddLocalVariable transformation(104, 41, 4, 46, false);
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    transformation.Apply(context.get(), &transformation_context);
+  }
+
+  // %103 = OpVariable %35 Function %38
+  {
+    TransformationAddLocalVariable transformation(103, 35, 4, 38, true);
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    transformation.Apply(context.get(), &transformation_context);
+  }
+
+  // %102 = OpVariable %31 Function %33
+  {
+    TransformationAddLocalVariable transformation(102, 31, 4, 33, false);
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    transformation.Apply(context.get(), &transformation_context);
+  }
+
+  // %101 = OpVariable %19 Function %29
+  {
+    TransformationAddLocalVariable transformation(101, 19, 4, 29, true);
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    transformation.Apply(context.get(), &transformation_context);
+  }
+
+  // %100 = OpVariable %8 Function %12
+  {
+    TransformationAddLocalVariable transformation(100, 8, 4, 12, false);
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    transformation.Apply(context.get(), &transformation_context);
+  }
+
+  ASSERT_FALSE(
+      transformation_context.GetFactManager()->PointeeValueIsIrrelevant(100));
+  ASSERT_TRUE(
+      transformation_context.GetFactManager()->PointeeValueIsIrrelevant(101));
+  ASSERT_FALSE(
+      transformation_context.GetFactManager()->PointeeValueIsIrrelevant(102));
+  ASSERT_TRUE(
+      transformation_context.GetFactManager()->PointeeValueIsIrrelevant(103));
+  ASSERT_FALSE(
+      transformation_context.GetFactManager()->PointeeValueIsIrrelevant(104));
+  ASSERT_TRUE(
+      transformation_context.GetFactManager()->PointeeValueIsIrrelevant(105));
+
+  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 = OpTypeStruct %6 %6
+          %8 = OpTypePointer Function %7
+         %10 = OpConstant %6 1
+         %11 = OpConstant %6 2
+         %12 = OpConstantComposite %7 %10 %11
+         %13 = OpTypeFloat 32
+         %14 = OpTypeInt 32 0
+         %15 = OpConstant %14 3
+         %16 = OpTypeArray %13 %15
+         %17 = OpTypeBool
+         %18 = OpTypeStruct %16 %7 %17
+         %19 = OpTypePointer Function %18
+         %21 = OpConstant %13 1
+         %22 = OpConstant %13 2
+         %23 = OpConstant %13 4
+         %24 = OpConstantComposite %16 %21 %22 %23
+         %25 = OpConstant %6 5
+         %26 = OpConstant %6 6
+         %27 = OpConstantComposite %7 %25 %26
+         %28 = OpConstantFalse %17
+         %29 = OpConstantComposite %18 %24 %27 %28
+         %30 = OpTypeVector %13 2
+         %31 = OpTypePointer Function %30
+         %33 = OpConstantComposite %30 %21 %21
+         %34 = OpTypeVector %17 3
+         %35 = OpTypePointer Function %34
+         %37 = OpConstantTrue %17
+         %38 = OpConstantComposite %34 %37 %28 %28
+         %39 = OpTypeVector %13 4
+         %40 = OpTypeMatrix %39 3
+         %41 = OpTypePointer Function %40
+         %43 = OpConstantComposite %39 %21 %22 %23 %21
+         %44 = OpConstantComposite %39 %22 %23 %21 %22
+         %45 = OpConstantComposite %39 %23 %21 %22 %23
+         %46 = OpConstantComposite %40 %43 %44 %45
+         %50 = OpTypePointer Function %14
+         %51 = OpConstantNull %14
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+        %100 = OpVariable %8 Function %12
+        %101 = OpVariable %19 Function %29
+        %102 = OpVariable %31 Function %33
+        %103 = OpVariable %35 Function %38
+        %104 = OpVariable %41 Function %46
+        %105 = OpVariable %50 Function %51
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_transformation, 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 b1a87ea..46841a5 100644
--- a/test/fuzz/transformation_add_no_contraction_decoration_test.cpp
+++ b/test/fuzz/transformation_add_no_contraction_decoration_test.cpp
@@ -94,23 +94,27 @@
   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);
 
   // Invalid: 200 is not an id
   ASSERT_FALSE(TransformationAddNoContractionDecoration(200).IsApplicable(
-      context.get(), fact_manager));
+      context.get(), transformation_context));
   // Invalid: 17 is a block id
   ASSERT_FALSE(TransformationAddNoContractionDecoration(17).IsApplicable(
-      context.get(), fact_manager));
+      context.get(), transformation_context));
   // Invalid: 24 is not arithmetic
   ASSERT_FALSE(TransformationAddNoContractionDecoration(24).IsApplicable(
-      context.get(), fact_manager));
+      context.get(), transformation_context));
 
   // It is valid to add NoContraction to each of these ids (and it's fine to
   // have duplicates of the decoration, in the case of 32).
   for (uint32_t result_id : {32u, 32u, 27u, 29u, 39u}) {
     TransformationAddNoContractionDecoration transformation(result_id);
-    ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
-    transformation.Apply(context.get(), &fact_manager);
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    transformation.Apply(context.get(), &transformation_context);
     ASSERT_TRUE(IsValid(env, context.get()));
   }
 
diff --git a/test/fuzz/transformation_add_type_array_test.cpp b/test/fuzz/transformation_add_type_array_test.cpp
index 2bcbe73..4392f99 100644
--- a/test/fuzz/transformation_add_type_array_test.cpp
+++ b/test/fuzz/transformation_add_type_array_test.cpp
@@ -54,37 +54,40 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   // Id already in use
   ASSERT_FALSE(TransformationAddTypeArray(4, 10, 16).IsApplicable(
-      context.get(), fact_manager));
+      context.get(), transformation_context));
   // %1 is not a type
   ASSERT_FALSE(TransformationAddTypeArray(100, 1, 16)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // %3 is a function type
   ASSERT_FALSE(TransformationAddTypeArray(100, 3, 16)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // %2 is not a constant
   ASSERT_FALSE(TransformationAddTypeArray(100, 11, 2)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // %18 is not an integer
   ASSERT_FALSE(TransformationAddTypeArray(100, 11, 18)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // %13 is signed 0
   ASSERT_FALSE(TransformationAddTypeArray(100, 11, 13)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // %14 is negative
   ASSERT_FALSE(TransformationAddTypeArray(100, 11, 14)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // %17 is unsigned 0
   ASSERT_FALSE(TransformationAddTypeArray(100, 11, 17)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   TransformationAddTypeArray transformations[] = {
       // %100 = OpTypeArray %10 %16
@@ -94,8 +97,9 @@
       TransformationAddTypeArray(101, 7, 12)};
 
   for (auto& transformation : transformations) {
-    ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
-    transformation.Apply(context.get(), &fact_manager);
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    transformation.Apply(context.get(), &transformation_context);
   }
   ASSERT_TRUE(IsValid(env, context.get()));
 
diff --git a/test/fuzz/transformation_add_type_boolean_test.cpp b/test/fuzz/transformation_add_type_boolean_test.cpp
index 9975953..60eabd9 100644
--- a/test/fuzz/transformation_add_type_boolean_test.cpp
+++ b/test/fuzz/transformation_add_type_boolean_test.cpp
@@ -42,19 +42,23 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   // Not applicable because id 1 is already in use.
-  ASSERT_FALSE(TransformationAddTypeBoolean(1).IsApplicable(context.get(),
-                                                            fact_manager));
+  ASSERT_FALSE(TransformationAddTypeBoolean(1).IsApplicable(
+      context.get(), transformation_context));
 
   auto add_type_bool = TransformationAddTypeBoolean(100);
-  ASSERT_TRUE(add_type_bool.IsApplicable(context.get(), fact_manager));
-  add_type_bool.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      add_type_bool.IsApplicable(context.get(), transformation_context));
+  add_type_bool.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // Not applicable as we already have this type now.
-  ASSERT_FALSE(TransformationAddTypeBoolean(101).IsApplicable(context.get(),
-                                                              fact_manager));
+  ASSERT_FALSE(TransformationAddTypeBoolean(101).IsApplicable(
+      context.get(), transformation_context));
 
   std::string after_transformation = R"(
                OpCapability Shader
diff --git a/test/fuzz/transformation_add_type_float_test.cpp b/test/fuzz/transformation_add_type_float_test.cpp
index 67408da..7d17266 100644
--- a/test/fuzz/transformation_add_type_float_test.cpp
+++ b/test/fuzz/transformation_add_type_float_test.cpp
@@ -42,19 +42,23 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   // Not applicable because id 1 is already in use.
-  ASSERT_FALSE(TransformationAddTypeFloat(1, 32).IsApplicable(context.get(),
-                                                              fact_manager));
+  ASSERT_FALSE(TransformationAddTypeFloat(1, 32).IsApplicable(
+      context.get(), transformation_context));
 
   auto add_type_float_32 = TransformationAddTypeFloat(100, 32);
-  ASSERT_TRUE(add_type_float_32.IsApplicable(context.get(), fact_manager));
-  add_type_float_32.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      add_type_float_32.IsApplicable(context.get(), transformation_context));
+  add_type_float_32.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // Not applicable as we already have this type now.
-  ASSERT_FALSE(TransformationAddTypeFloat(101, 32).IsApplicable(context.get(),
-                                                                fact_manager));
+  ASSERT_FALSE(TransformationAddTypeFloat(101, 32).IsApplicable(
+      context.get(), transformation_context));
 
   std::string after_transformation = R"(
                OpCapability Shader
diff --git a/test/fuzz/transformation_add_type_function_test.cpp b/test/fuzz/transformation_add_type_function_test.cpp
index 46bd436..1557bb8 100644
--- a/test/fuzz/transformation_add_type_function_test.cpp
+++ b/test/fuzz/transformation_add_type_function_test.cpp
@@ -59,21 +59,24 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   // Id already in use
   ASSERT_FALSE(TransformationAddTypeFunction(4, 12, {12, 16, 14})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   // %1 is not a type
   ASSERT_FALSE(TransformationAddTypeFunction(100, 1, {12, 16, 14})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // %18 is a function type
   ASSERT_FALSE(TransformationAddTypeFunction(100, 12, {18})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // A function of this signature already exists
   ASSERT_FALSE(TransformationAddTypeFunction(100, 17, {14, 16})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   TransformationAddTypeFunction transformations[] = {
       // %100 = OpTypeFunction %12 %12 %16 %14
@@ -86,8 +89,9 @@
       TransformationAddTypeFunction(102, 17, {200, 16})};
 
   for (auto& transformation : transformations) {
-    ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
-    transformation.Apply(context.get(), &fact_manager);
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    transformation.Apply(context.get(), &transformation_context);
   }
   ASSERT_TRUE(IsValid(env, context.get()));
 
diff --git a/test/fuzz/transformation_add_type_int_test.cpp b/test/fuzz/transformation_add_type_int_test.cpp
index c6f884c..63b17c2 100644
--- a/test/fuzz/transformation_add_type_int_test.cpp
+++ b/test/fuzz/transformation_add_type_int_test.cpp
@@ -42,10 +42,13 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   // Not applicable because id 1 is already in use.
   ASSERT_FALSE(TransformationAddTypeInt(1, 32, false)
-                   .IsApplicable(context.get(), fact_manager));
+                   .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);
@@ -53,20 +56,21 @@
   auto add_type_unsigned_int_32_again =
       TransformationAddTypeInt(103, 32, false);
 
-  ASSERT_TRUE(add_type_signed_int_32.IsApplicable(context.get(), fact_manager));
-  add_type_signed_int_32.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(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()));
 
-  ASSERT_TRUE(
-      add_type_unsigned_int_32.IsApplicable(context.get(), fact_manager));
-  add_type_unsigned_int_32.Apply(context.get(), &fact_manager);
+  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()));
 
   // Not applicable as we already have these types now.
-  ASSERT_FALSE(
-      add_type_signed_int_32_again.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(
-      add_type_unsigned_int_32_again.IsApplicable(context.get(), fact_manager));
+  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));
 
   std::string after_transformation = R"(
                OpCapability Shader
diff --git a/test/fuzz/transformation_add_type_matrix_test.cpp b/test/fuzz/transformation_add_type_matrix_test.cpp
index 84f27e9..e925012 100644
--- a/test/fuzz/transformation_add_type_matrix_test.cpp
+++ b/test/fuzz/transformation_add_type_matrix_test.cpp
@@ -47,17 +47,20 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   // Id already in use
-  ASSERT_FALSE(TransformationAddTypeMatrix(4, 9, 2).IsApplicable(context.get(),
-                                                                 fact_manager));
+  ASSERT_FALSE(TransformationAddTypeMatrix(4, 9, 2).IsApplicable(
+      context.get(), transformation_context));
   // %1 is not a type
   ASSERT_FALSE(TransformationAddTypeMatrix(100, 1, 2).IsApplicable(
-      context.get(), fact_manager));
+      context.get(), transformation_context));
 
   // %11 is not a floating-point vector
   ASSERT_FALSE(TransformationAddTypeMatrix(100, 11, 2)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   TransformationAddTypeMatrix transformations[] = {
       // %100 = OpTypeMatrix %8 2
@@ -88,8 +91,9 @@
       TransformationAddTypeMatrix(108, 10, 4)};
 
   for (auto& transformation : transformations) {
-    ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
-    transformation.Apply(context.get(), &fact_manager);
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    transformation.Apply(context.get(), &transformation_context);
   }
   ASSERT_TRUE(IsValid(env, context.get()));
 
diff --git a/test/fuzz/transformation_add_type_pointer_test.cpp b/test/fuzz/transformation_add_type_pointer_test.cpp
index e36707f..35303e4 100644
--- a/test/fuzz/transformation_add_type_pointer_test.cpp
+++ b/test/fuzz/transformation_add_type_pointer_test.cpp
@@ -97,6 +97,9 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   auto bad_type_id_does_not_exist =
       TransformationAddTypePointer(100, SpvStorageClassFunction, 101);
@@ -122,12 +125,12 @@
   auto good_new_private_pointer_to_uniform_pointer_to_vec2 =
       TransformationAddTypePointer(108, SpvStorageClassPrivate, 107);
 
-  ASSERT_FALSE(
-      bad_type_id_does_not_exist.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(
-      bad_type_id_is_not_type.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(
-      bad_result_id_is_not_fresh.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(bad_type_id_does_not_exist.IsApplicable(context.get(),
+                                                       transformation_context));
+  ASSERT_FALSE(bad_type_id_is_not_type.IsApplicable(context.get(),
+                                                    transformation_context));
+  ASSERT_FALSE(bad_result_id_is_not_fresh.IsApplicable(context.get(),
+                                                       transformation_context));
 
   for (auto& transformation :
        {good_new_private_pointer_to_t, good_new_uniform_pointer_to_t,
@@ -136,8 +139,9 @@
         good_new_private_pointer_to_private_pointer_to_float,
         good_new_uniform_pointer_to_vec2,
         good_new_private_pointer_to_uniform_pointer_to_vec2}) {
-    ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
-    transformation.Apply(context.get(), &fact_manager);
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    transformation.Apply(context.get(), &transformation_context);
     ASSERT_TRUE(IsValid(env, context.get()));
   }
 
diff --git a/test/fuzz/transformation_add_type_struct_test.cpp b/test/fuzz/transformation_add_type_struct_test.cpp
index ae68c9a..06f78cd 100644
--- a/test/fuzz/transformation_add_type_struct_test.cpp
+++ b/test/fuzz/transformation_add_type_struct_test.cpp
@@ -47,17 +47,20 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   // Id already in use
-  ASSERT_FALSE(TransformationAddTypeStruct(4, {}).IsApplicable(context.get(),
-                                                               fact_manager));
+  ASSERT_FALSE(TransformationAddTypeStruct(4, {}).IsApplicable(
+      context.get(), transformation_context));
   // %1 is not a type
   ASSERT_FALSE(TransformationAddTypeStruct(100, {1}).IsApplicable(
-      context.get(), fact_manager));
+      context.get(), transformation_context));
 
   // %3 is a function type
   ASSERT_FALSE(TransformationAddTypeStruct(100, {3}).IsApplicable(
-      context.get(), fact_manager));
+      context.get(), transformation_context));
 
   TransformationAddTypeStruct transformations[] = {
       // %100 = OpTypeStruct %6 %7 %8 %9 %10 %11
@@ -73,8 +76,9 @@
       TransformationAddTypeStruct(103, {6, 6})};
 
   for (auto& transformation : transformations) {
-    ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
-    transformation.Apply(context.get(), &fact_manager);
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    transformation.Apply(context.get(), &transformation_context);
   }
   ASSERT_TRUE(IsValid(env, context.get()));
 
diff --git a/test/fuzz/transformation_add_type_vector_test.cpp b/test/fuzz/transformation_add_type_vector_test.cpp
index 6ac4498..f1252a3 100644
--- a/test/fuzz/transformation_add_type_vector_test.cpp
+++ b/test/fuzz/transformation_add_type_vector_test.cpp
@@ -45,13 +45,16 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   // Id already in use
-  ASSERT_FALSE(TransformationAddTypeVector(4, 6, 2).IsApplicable(context.get(),
-                                                                 fact_manager));
+  ASSERT_FALSE(TransformationAddTypeVector(4, 6, 2).IsApplicable(
+      context.get(), transformation_context));
   // %1 is not a type
   ASSERT_FALSE(TransformationAddTypeVector(100, 1, 2).IsApplicable(
-      context.get(), fact_manager));
+      context.get(), transformation_context));
 
   TransformationAddTypeVector transformations[] = {
       // %100 = OpTypeVector %6 2
@@ -67,8 +70,9 @@
       TransformationAddTypeVector(103, 9, 2)};
 
   for (auto& transformation : transformations) {
-    ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
-    transformation.Apply(context.get(), &fact_manager);
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    transformation.Apply(context.get(), &transformation_context);
   }
   ASSERT_TRUE(IsValid(env, context.get()));
 
diff --git a/test/fuzz/transformation_composite_construct_test.cpp b/test/fuzz/transformation_composite_construct_test.cpp
index d303368..e9d7610 100644
--- a/test/fuzz/transformation_composite_construct_test.cpp
+++ b/test/fuzz/transformation_composite_construct_test.cpp
@@ -129,6 +129,9 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   // Make a vec2[3]
   TransformationCompositeConstruct make_vec2_array_length_3(
@@ -138,17 +141,17 @@
   TransformationCompositeConstruct make_vec2_array_length_3_bad(
       37, {41, 45, 27, 27}, MakeInstructionDescriptor(46, SpvOpAccessChain, 0),
       200);
-  ASSERT_TRUE(
-      make_vec2_array_length_3.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(
-      make_vec2_array_length_3_bad.IsApplicable(context.get(), fact_manager));
-  make_vec2_array_length_3.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(make_vec2_array_length_3.IsApplicable(context.get(),
+                                                    transformation_context));
+  ASSERT_FALSE(make_vec2_array_length_3_bad.IsApplicable(
+      context.get(), transformation_context));
+  make_vec2_array_length_3.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(41, {}), MakeDataDescriptor(200, {0}), context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(45, {}), MakeDataDescriptor(200, {1}), context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(27, {}), MakeDataDescriptor(200, {2}), context.get()));
 
   // Make a float[2]
@@ -157,15 +160,15 @@
   // Bad: %41 does not have type float
   TransformationCompositeConstruct make_float_array_length_2_bad(
       9, {41, 40}, MakeInstructionDescriptor(71, SpvOpStore, 0), 201);
-  ASSERT_TRUE(
-      make_float_array_length_2.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(
-      make_float_array_length_2_bad.IsApplicable(context.get(), fact_manager));
-  make_float_array_length_2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(make_float_array_length_2.IsApplicable(context.get(),
+                                                     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()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(24, {}), MakeDataDescriptor(201, {0}), context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(40, {}), MakeDataDescriptor(201, {1}), context.get()));
 
   // Make a bool[3]
@@ -176,17 +179,17 @@
   TransformationCompositeConstruct make_bool_array_length_3_bad(
       47, {33, 54, 50}, MakeInstructionDescriptor(33, SpvOpSelectionMerge, 0),
       202);
-  ASSERT_TRUE(
-      make_bool_array_length_3.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(
-      make_bool_array_length_3_bad.IsApplicable(context.get(), fact_manager));
-  make_bool_array_length_3.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(make_bool_array_length_3.IsApplicable(context.get(),
+                                                    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()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(33, {}), MakeDataDescriptor(202, {0}), context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(50, {}), MakeDataDescriptor(202, {1}), context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(50, {}), MakeDataDescriptor(202, {2}), context.get()));
 
   // make a uvec3[2][2]
@@ -195,17 +198,17 @@
   // Bad: Skip count 100 is too large.
   TransformationCompositeConstruct make_uvec3_array_length_2_2_bad(
       58, {33, 54}, MakeInstructionDescriptor(64, SpvOpStore, 100), 203);
-  ASSERT_TRUE(
-      make_uvec3_array_length_2_2.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(make_uvec3_array_length_2_2_bad.IsApplicable(context.get(),
-                                                            fact_manager));
-  make_uvec3_array_length_2_2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(make_uvec3_array_length_2_2.IsApplicable(context.get(),
+                                                       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()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(69, {}), MakeDataDescriptor(203, {0}), context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(100, {}),
-                                        MakeDataDescriptor(203, {1}),
-                                        context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(100, {}), MakeDataDescriptor(203, {1}),
+      context.get()));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -393,6 +396,9 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   // make a mat3x4
   TransformationCompositeConstruct make_mat34(
@@ -400,15 +406,16 @@
   // Bad: %35 is mat4x3, not mat3x4.
   TransformationCompositeConstruct make_mat34_bad(
       35, {25, 28, 31}, MakeInstructionDescriptor(31, SpvOpReturn, 0), 200);
-  ASSERT_TRUE(make_mat34.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(make_mat34_bad.IsApplicable(context.get(), fact_manager));
-  make_mat34.Apply(context.get(), &fact_manager);
+  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()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(25, {}), MakeDataDescriptor(200, {0}), context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(28, {}), MakeDataDescriptor(200, {1}), context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(31, {}), MakeDataDescriptor(200, {2}), context.get()));
 
   // make a mat4x3
@@ -417,19 +424,20 @@
   // Bad: %25 does not match the matrix's column type.
   TransformationCompositeConstruct make_mat43_bad(
       35, {25, 13, 16, 100}, MakeInstructionDescriptor(31, SpvOpStore, 0), 201);
-  ASSERT_TRUE(make_mat43.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(make_mat43_bad.IsApplicable(context.get(), fact_manager));
-  make_mat43.Apply(context.get(), &fact_manager);
+  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()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(11, {}), MakeDataDescriptor(201, {0}), context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(13, {}), MakeDataDescriptor(201, {1}), context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(16, {}), MakeDataDescriptor(201, {2}), context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(100, {}),
-                                        MakeDataDescriptor(201, {3}),
-                                        context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(100, {}), MakeDataDescriptor(201, {3}),
+      context.get()));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -602,6 +610,9 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   // make an Inner
   TransformationCompositeConstruct make_inner(
@@ -609,13 +620,14 @@
   // Bad: Too few fields to make the struct.
   TransformationCompositeConstruct make_inner_bad(
       9, {25}, MakeInstructionDescriptor(57, SpvOpAccessChain, 0), 200);
-  ASSERT_TRUE(make_inner.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(make_inner_bad.IsApplicable(context.get(), fact_manager));
-  make_inner.Apply(context.get(), &fact_manager);
+  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()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(25, {}), MakeDataDescriptor(200, {0}), context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(19, {}), MakeDataDescriptor(200, {1}), context.get()));
 
   // make an Outer
@@ -626,16 +638,17 @@
   TransformationCompositeConstruct make_outer_bad(
       33, {46, 200, 56},
       MakeInstructionDescriptor(200, SpvOpCompositeConstruct, 0), 201);
-  ASSERT_TRUE(make_outer.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(make_outer_bad.IsApplicable(context.get(), fact_manager));
-  make_outer.Apply(context.get(), &fact_manager);
+  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()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(46, {}), MakeDataDescriptor(201, {0}), context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(200, {}),
-                                        MakeDataDescriptor(201, {1}),
-                                        context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(200, {}), MakeDataDescriptor(201, {1}),
+      context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(56, {}), MakeDataDescriptor(201, {2}), context.get()));
 
   std::string after_transformation = R"(
@@ -922,19 +935,23 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   TransformationCompositeConstruct make_vec2(
       7, {17, 11}, MakeInstructionDescriptor(100, SpvOpStore, 0), 200);
   // Bad: not enough data for a vec2
   TransformationCompositeConstruct make_vec2_bad(
       7, {11}, MakeInstructionDescriptor(100, SpvOpStore, 0), 200);
-  ASSERT_TRUE(make_vec2.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(make_vec2_bad.IsApplicable(context.get(), fact_manager));
-  make_vec2.Apply(context.get(), &fact_manager);
+  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()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(17, {}), MakeDataDescriptor(200, {0}), context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(11, {}), MakeDataDescriptor(200, {1}), context.get()));
 
   TransformationCompositeConstruct make_vec3(
@@ -944,17 +961,18 @@
   TransformationCompositeConstruct make_vec3_bad(
       25, {12, 32, 32},
       MakeInstructionDescriptor(35, SpvOpCompositeConstruct, 0), 201);
-  ASSERT_TRUE(make_vec3.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(make_vec3_bad.IsApplicable(context.get(), fact_manager));
-  make_vec3.Apply(context.get(), &fact_manager);
+  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()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(12, {0}),
-                                        MakeDataDescriptor(201, {0}),
-                                        context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(12, {1}),
-                                        MakeDataDescriptor(201, {1}),
-                                        context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(12, {0}), MakeDataDescriptor(201, {0}),
+      context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(12, {1}), MakeDataDescriptor(201, {1}),
+      context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(32, {}), MakeDataDescriptor(201, {2}), context.get()));
 
   TransformationCompositeConstruct make_vec4(
@@ -964,17 +982,18 @@
   TransformationCompositeConstruct make_vec4_bad(
       44, {48, 32, 10, 11}, MakeInstructionDescriptor(75, SpvOpAccessChain, 0),
       202);
-  ASSERT_TRUE(make_vec4.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(make_vec4_bad.IsApplicable(context.get(), fact_manager));
-  make_vec4.Apply(context.get(), &fact_manager);
+  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()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(32, {}), MakeDataDescriptor(202, {0}), context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(32, {}), MakeDataDescriptor(202, {1}), context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(10, {}), MakeDataDescriptor(202, {2}), context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(11, {}), MakeDataDescriptor(202, {3}), context.get()));
 
   TransformationCompositeConstruct make_ivec2(
@@ -982,16 +1001,17 @@
   // Bad: if 128 is not available at the instruction that defines 128
   TransformationCompositeConstruct make_ivec2_bad(
       51, {128, 120}, MakeInstructionDescriptor(128, SpvOpLoad, 0), 203);
-  ASSERT_TRUE(make_ivec2.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(make_ivec2_bad.IsApplicable(context.get(), fact_manager));
-  make_ivec2.Apply(context.get(), &fact_manager);
+  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()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(126, {}),
-                                        MakeDataDescriptor(203, {0}),
-                                        context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(120, {}),
-                                        MakeDataDescriptor(203, {1}),
-                                        context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(126, {}), MakeDataDescriptor(203, {0}),
+      context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(120, {}), MakeDataDescriptor(203, {1}),
+      context.get()));
 
   TransformationCompositeConstruct make_ivec3(
       114, {56, 117, 56}, MakeInstructionDescriptor(66, SpvOpAccessChain, 0),
@@ -1000,16 +1020,17 @@
   TransformationCompositeConstruct make_ivec3_bad(
       114, {56, 117, 1300}, MakeInstructionDescriptor(66, SpvOpAccessChain, 0),
       204);
-  ASSERT_TRUE(make_ivec3.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(make_ivec3_bad.IsApplicable(context.get(), fact_manager));
-  make_ivec3.Apply(context.get(), &fact_manager);
+  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()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(56, {}), MakeDataDescriptor(204, {0}), context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(117, {}),
-                                        MakeDataDescriptor(204, {1}),
-                                        context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(117, {}), MakeDataDescriptor(204, {1}),
+      context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(56, {}), MakeDataDescriptor(204, {2}), context.get()));
 
   TransformationCompositeConstruct make_ivec4(
@@ -1019,33 +1040,35 @@
   TransformationCompositeConstruct make_ivec4_bad(
       86, {56, 117, 117, 117}, MakeInstructionDescriptor(66, SpvOpIAdd, 0),
       205);
-  ASSERT_TRUE(make_ivec4.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(make_ivec4_bad.IsApplicable(context.get(), fact_manager));
-  make_ivec4.Apply(context.get(), &fact_manager);
+  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()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(56, {}), MakeDataDescriptor(205, {0}), context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(117, {}),
-                                        MakeDataDescriptor(205, {1}),
-                                        context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(117, {}),
-                                        MakeDataDescriptor(205, {2}),
-                                        context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(117, {}),
-                                        MakeDataDescriptor(205, {3}),
-                                        context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(117, {}), MakeDataDescriptor(205, {1}),
+      context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(117, {}), MakeDataDescriptor(205, {2}),
+      context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(117, {}), MakeDataDescriptor(205, {3}),
+      context.get()));
 
   TransformationCompositeConstruct make_uvec2(
       86, {18, 38}, MakeInstructionDescriptor(133, SpvOpAccessChain, 0), 206);
   TransformationCompositeConstruct make_uvec2_bad(
       86, {18, 38}, MakeInstructionDescriptor(133, SpvOpAccessChain, 200), 206);
-  ASSERT_TRUE(make_uvec2.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(make_uvec2_bad.IsApplicable(context.get(), fact_manager));
-  make_uvec2.Apply(context.get(), &fact_manager);
+  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()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(18, {}), MakeDataDescriptor(206, {0}), context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(38, {}), MakeDataDescriptor(206, {1}), context.get()));
 
   TransformationCompositeConstruct make_uvec3(
@@ -1053,17 +1076,18 @@
   // Bad because 1300 is not an id
   TransformationCompositeConstruct make_uvec3_bad(
       59, {14, 18, 1300}, MakeInstructionDescriptor(137, SpvOpReturn, 0), 207);
-  ASSERT_TRUE(make_uvec3.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(make_uvec3_bad.IsApplicable(context.get(), fact_manager));
-  make_uvec3.Apply(context.get(), &fact_manager);
+  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()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(14, {}), MakeDataDescriptor(207, {0}), context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(18, {}), MakeDataDescriptor(207, {1}), context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(136, {}),
-                                        MakeDataDescriptor(207, {2}),
-                                        context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(136, {}), MakeDataDescriptor(207, {2}),
+      context.get()));
 
   TransformationCompositeConstruct make_uvec4(
       131, {14, 18, 136, 136},
@@ -1072,20 +1096,21 @@
   TransformationCompositeConstruct make_uvec4_bad(
       86, {14, 18, 136, 136},
       MakeInstructionDescriptor(137, SpvOpAccessChain, 0), 208);
-  ASSERT_TRUE(make_uvec4.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(make_uvec4_bad.IsApplicable(context.get(), fact_manager));
-  make_uvec4.Apply(context.get(), &fact_manager);
+  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()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(14, {}), MakeDataDescriptor(208, {0}), context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(18, {}), MakeDataDescriptor(208, {1}), context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(136, {}),
-                                        MakeDataDescriptor(208, {2}),
-                                        context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(136, {}),
-                                        MakeDataDescriptor(208, {3}),
-                                        context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(136, {}), MakeDataDescriptor(208, {2}),
+      context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(136, {}), MakeDataDescriptor(208, {3}),
+      context.get()));
 
   TransformationCompositeConstruct make_bvec2(
       102,
@@ -1102,14 +1127,15 @@
           41,
       },
       MakeInstructionDescriptor(0, SpvOpExtInstImport, 0), 209);
-  ASSERT_TRUE(make_bvec2.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(make_bvec2_bad.IsApplicable(context.get(), fact_manager));
-  make_bvec2.Apply(context.get(), &fact_manager);
+  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()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(111, {}),
-                                        MakeDataDescriptor(209, {0}),
-                                        context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(111, {}), MakeDataDescriptor(209, {0}),
+      context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(41, {}), MakeDataDescriptor(209, {1}), context.get()));
 
   TransformationCompositeConstruct make_bvec3(
@@ -1117,17 +1143,18 @@
   // Bad because there are too many components for a bvec3
   TransformationCompositeConstruct make_bvec3_bad(
       93, {108, 108}, MakeInstructionDescriptor(108, SpvOpStore, 0), 210);
-  ASSERT_TRUE(make_bvec3.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(make_bvec3_bad.IsApplicable(context.get(), fact_manager));
-  make_bvec3.Apply(context.get(), &fact_manager);
+  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()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(108, {0}),
-                                        MakeDataDescriptor(210, {0}),
-                                        context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(108, {1}),
-                                        MakeDataDescriptor(210, {1}),
-                                        context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(108, {0}), MakeDataDescriptor(210, {0}),
+      context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(108, {1}), MakeDataDescriptor(210, {1}),
+      context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(73, {}), MakeDataDescriptor(210, {2}), context.get()));
 
   TransformationCompositeConstruct make_bvec4(
@@ -1135,22 +1162,23 @@
   // Bad because 21 is a type, not a result id
   TransformationCompositeConstruct make_bvec4_bad(
       70, {21, 108}, MakeInstructionDescriptor(108, SpvOpBranch, 0), 211);
-  ASSERT_TRUE(make_bvec4.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(make_bvec4_bad.IsApplicable(context.get(), fact_manager));
-  make_bvec4.Apply(context.get(), &fact_manager);
+  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()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(108, {0}),
-                                        MakeDataDescriptor(211, {0}),
-                                        context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(108, {1}),
-                                        MakeDataDescriptor(211, {1}),
-                                        context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(108, {0}),
-                                        MakeDataDescriptor(211, {2}),
-                                        context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(108, {1}),
-                                        MakeDataDescriptor(211, {3}),
-                                        context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(108, {0}), MakeDataDescriptor(211, {0}),
+      context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(108, {1}), MakeDataDescriptor(211, {1}),
+      context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(108, {0}), MakeDataDescriptor(211, {2}),
+      context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(108, {1}), MakeDataDescriptor(211, {3}),
+      context.get()));
 
   std::string after_transformation = R"(
                OpCapability Shader
diff --git a/test/fuzz/transformation_composite_extract_test.cpp b/test/fuzz/transformation_composite_extract_test.cpp
index 5cc2115..d57b62c 100644
--- a/test/fuzz/transformation_composite_extract_test.cpp
+++ b/test/fuzz/transformation_composite_extract_test.cpp
@@ -96,100 +96,109 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   // Instruction does not exist.
   ASSERT_FALSE(TransformationCompositeExtract(
                    MakeInstructionDescriptor(36, SpvOpIAdd, 0), 200, 101, {0})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // Id for composite is not a composite.
   ASSERT_FALSE(TransformationCompositeExtract(
                    MakeInstructionDescriptor(36, SpvOpIAdd, 0), 200, 27, {})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // Composite does not dominate instruction being inserted before.
   ASSERT_FALSE(
       TransformationCompositeExtract(
           MakeInstructionDescriptor(37, SpvOpAccessChain, 0), 200, 101, {0})
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
 
   // Too many indices for extraction from struct composite.
   ASSERT_FALSE(
       TransformationCompositeExtract(
           MakeInstructionDescriptor(24, SpvOpAccessChain, 0), 200, 101, {0, 0})
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
 
   // Too many indices for extraction from struct composite.
   ASSERT_FALSE(
       TransformationCompositeExtract(
           MakeInstructionDescriptor(13, SpvOpIEqual, 0), 200, 104, {0, 0, 0})
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
 
   // Out of bounds index for extraction from struct composite.
   ASSERT_FALSE(
       TransformationCompositeExtract(
           MakeInstructionDescriptor(13, SpvOpIEqual, 0), 200, 104, {0, 3})
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
 
   // Result id already used.
   ASSERT_FALSE(TransformationCompositeExtract(
                    MakeInstructionDescriptor(35, SpvOpFAdd, 0), 80, 103, {0})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   TransformationCompositeExtract transformation_1(
       MakeInstructionDescriptor(36, SpvOpConvertFToS, 0), 201, 100, {2});
-  ASSERT_TRUE(transformation_1.IsApplicable(context.get(), fact_manager));
-  transformation_1.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation_1.IsApplicable(context.get(), transformation_context));
+  transformation_1.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   TransformationCompositeExtract transformation_2(
       MakeInstructionDescriptor(37, SpvOpAccessChain, 0), 202, 104, {0, 2});
-  ASSERT_TRUE(transformation_2.IsApplicable(context.get(), fact_manager));
-  transformation_2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation_2.IsApplicable(context.get(), transformation_context));
+  transformation_2.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   TransformationCompositeExtract transformation_3(
       MakeInstructionDescriptor(29, SpvOpAccessChain, 0), 203, 104, {0});
-  ASSERT_TRUE(transformation_3.IsApplicable(context.get(), fact_manager));
-  transformation_3.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation_3.IsApplicable(context.get(), transformation_context));
+  transformation_3.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   TransformationCompositeExtract transformation_4(
       MakeInstructionDescriptor(24, SpvOpStore, 0), 204, 101, {0});
-  ASSERT_TRUE(transformation_4.IsApplicable(context.get(), fact_manager));
-  transformation_4.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation_4.IsApplicable(context.get(), transformation_context));
+  transformation_4.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   TransformationCompositeExtract transformation_5(
       MakeInstructionDescriptor(29, SpvOpBranch, 0), 205, 102, {2});
-  ASSERT_TRUE(transformation_5.IsApplicable(context.get(), fact_manager));
-  transformation_5.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation_5.IsApplicable(context.get(), transformation_context));
+  transformation_5.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   TransformationCompositeExtract transformation_6(
       MakeInstructionDescriptor(37, SpvOpReturn, 0), 206, 103, {1});
-  ASSERT_TRUE(transformation_6.IsApplicable(context.get(), fact_manager));
-  transformation_6.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation_6.IsApplicable(context.get(), transformation_context));
+  transformation_6.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(201, {}),
-                                        MakeDataDescriptor(100, {2}),
-                                        context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(202, {}),
-                                        MakeDataDescriptor(104, {0, 2}),
-                                        context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(203, {}),
-                                        MakeDataDescriptor(104, {0}),
-                                        context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(204, {}),
-                                        MakeDataDescriptor(101, {0}),
-                                        context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(205, {}),
-                                        MakeDataDescriptor(102, {2}),
-                                        context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(206, {}),
-                                        MakeDataDescriptor(103, {1}),
-                                        context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(201, {}), MakeDataDescriptor(100, {2}),
+      context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(202, {}), MakeDataDescriptor(104, {0, 2}),
+      context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(203, {}), MakeDataDescriptor(104, {0}),
+      context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(204, {}), MakeDataDescriptor(101, {0}),
+      context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(205, {}), MakeDataDescriptor(102, {2}),
+      context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(206, {}), MakeDataDescriptor(103, {1}),
+      context.get()));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -348,49 +357,52 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   // Cannot insert before the OpVariables of a function.
   ASSERT_FALSE(
       TransformationCompositeExtract(
           MakeInstructionDescriptor(101, SpvOpVariable, 0), 200, 14, {0})
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       TransformationCompositeExtract(
           MakeInstructionDescriptor(101, SpvOpVariable, 1), 200, 14, {1})
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       TransformationCompositeExtract(
           MakeInstructionDescriptor(102, SpvOpVariable, 0), 200, 14, {1})
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   // OK to insert right after the OpVariables.
   ASSERT_FALSE(TransformationCompositeExtract(
                    MakeInstructionDescriptor(102, SpvOpBranch, 1), 200, 14, {1})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // Cannot insert before the OpPhis of a block.
   ASSERT_FALSE(TransformationCompositeExtract(
                    MakeInstructionDescriptor(60, SpvOpPhi, 0), 200, 14, {2})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationCompositeExtract(
                    MakeInstructionDescriptor(59, SpvOpPhi, 0), 200, 14, {3})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   // OK to insert after the OpPhis.
   ASSERT_TRUE(
       TransformationCompositeExtract(
           MakeInstructionDescriptor(59, SpvOpAccessChain, 0), 200, 14, {3})
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
 
   // Cannot insert before OpLoopMerge
   ASSERT_FALSE(TransformationCompositeExtract(
                    MakeInstructionDescriptor(33, SpvOpBranchConditional, 0),
                    200, 14, {3})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // Cannot insert before OpSelectionMerge
   ASSERT_FALSE(TransformationCompositeExtract(
                    MakeInstructionDescriptor(21, SpvOpBranchConditional, 0),
                    200, 14, {2})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 }
 
 }  // namespace
diff --git a/test/fuzz/transformation_copy_object_test.cpp b/test/fuzz/transformation_copy_object_test.cpp
index b489f71..fa5a5b1 100644
--- a/test/fuzz/transformation_copy_object_test.cpp
+++ b/test/fuzz/transformation_copy_object_test.cpp
@@ -51,77 +51,95 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
-  ASSERT_EQ(0,
-            fact_manager.GetIdsForWhichSynonymsAreKnown(context.get()).size());
+  ASSERT_EQ(0, transformation_context.GetFactManager()
+                   ->GetIdsForWhichSynonymsAreKnown(context.get())
+                   .size());
 
   {
     TransformationCopyObject copy_true(
         7, MakeInstructionDescriptor(5, SpvOpReturn, 0), 100);
-    ASSERT_TRUE(copy_true.IsApplicable(context.get(), fact_manager));
-    copy_true.Apply(context.get(), &fact_manager);
+    ASSERT_TRUE(copy_true.IsApplicable(context.get(), transformation_context));
+    copy_true.Apply(context.get(), &transformation_context);
 
     std::vector<uint32_t> ids_for_which_synonyms_are_known =
-        fact_manager.GetIdsForWhichSynonymsAreKnown(context.get());
+        transformation_context.GetFactManager()->GetIdsForWhichSynonymsAreKnown(
+            context.get());
     ASSERT_EQ(2, ids_for_which_synonyms_are_known.size());
     ASSERT_TRUE(std::find(ids_for_which_synonyms_are_known.begin(),
                           ids_for_which_synonyms_are_known.end(),
                           7) != ids_for_which_synonyms_are_known.end());
-    ASSERT_EQ(2, fact_manager.GetSynonymsForId(7, context.get()).size());
+    ASSERT_EQ(2, transformation_context.GetFactManager()
+                     ->GetSynonymsForId(7, context.get())
+                     .size());
     protobufs::DataDescriptor descriptor_100 = MakeDataDescriptor(100, {});
-    ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(7, {}),
-                                          descriptor_100, context.get()));
+    ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+        MakeDataDescriptor(7, {}), descriptor_100, context.get()));
   }
 
   {
     TransformationCopyObject copy_false(
         8, MakeInstructionDescriptor(100, SpvOpReturn, 0), 101);
-    ASSERT_TRUE(copy_false.IsApplicable(context.get(), fact_manager));
-    copy_false.Apply(context.get(), &fact_manager);
+    ASSERT_TRUE(copy_false.IsApplicable(context.get(), transformation_context));
+    copy_false.Apply(context.get(), &transformation_context);
     std::vector<uint32_t> ids_for_which_synonyms_are_known =
-        fact_manager.GetIdsForWhichSynonymsAreKnown(context.get());
+        transformation_context.GetFactManager()->GetIdsForWhichSynonymsAreKnown(
+            context.get());
     ASSERT_EQ(4, ids_for_which_synonyms_are_known.size());
     ASSERT_TRUE(std::find(ids_for_which_synonyms_are_known.begin(),
                           ids_for_which_synonyms_are_known.end(),
                           8) != ids_for_which_synonyms_are_known.end());
-    ASSERT_EQ(2, fact_manager.GetSynonymsForId(8, context.get()).size());
+    ASSERT_EQ(2, transformation_context.GetFactManager()
+                     ->GetSynonymsForId(8, context.get())
+                     .size());
     protobufs::DataDescriptor descriptor_101 = MakeDataDescriptor(101, {});
-    ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(8, {}),
-                                          descriptor_101, context.get()));
+    ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+        MakeDataDescriptor(8, {}), descriptor_101, context.get()));
   }
 
   {
     TransformationCopyObject copy_false_again(
         101, MakeInstructionDescriptor(5, SpvOpReturn, 0), 102);
-    ASSERT_TRUE(copy_false_again.IsApplicable(context.get(), fact_manager));
-    copy_false_again.Apply(context.get(), &fact_manager);
+    ASSERT_TRUE(
+        copy_false_again.IsApplicable(context.get(), transformation_context));
+    copy_false_again.Apply(context.get(), &transformation_context);
     std::vector<uint32_t> ids_for_which_synonyms_are_known =
-        fact_manager.GetIdsForWhichSynonymsAreKnown(context.get());
+        transformation_context.GetFactManager()->GetIdsForWhichSynonymsAreKnown(
+            context.get());
     ASSERT_EQ(5, ids_for_which_synonyms_are_known.size());
     ASSERT_TRUE(std::find(ids_for_which_synonyms_are_known.begin(),
                           ids_for_which_synonyms_are_known.end(),
                           101) != ids_for_which_synonyms_are_known.end());
-    ASSERT_EQ(3, fact_manager.GetSynonymsForId(101, context.get()).size());
+    ASSERT_EQ(3, transformation_context.GetFactManager()
+                     ->GetSynonymsForId(101, context.get())
+                     .size());
     protobufs::DataDescriptor descriptor_102 = MakeDataDescriptor(102, {});
-    ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(101, {}),
-                                          descriptor_102, context.get()));
+    ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+        MakeDataDescriptor(101, {}), descriptor_102, context.get()));
   }
 
   {
     TransformationCopyObject copy_true_again(
         7, MakeInstructionDescriptor(102, SpvOpReturn, 0), 103);
-    ASSERT_TRUE(copy_true_again.IsApplicable(context.get(), fact_manager));
-    copy_true_again.Apply(context.get(), &fact_manager);
+    ASSERT_TRUE(
+        copy_true_again.IsApplicable(context.get(), transformation_context));
+    copy_true_again.Apply(context.get(), &transformation_context);
     std::vector<uint32_t> ids_for_which_synonyms_are_known =
-        fact_manager.GetIdsForWhichSynonymsAreKnown(context.get());
+        transformation_context.GetFactManager()->GetIdsForWhichSynonymsAreKnown(
+            context.get());
     ASSERT_EQ(6, ids_for_which_synonyms_are_known.size());
     ASSERT_TRUE(std::find(ids_for_which_synonyms_are_known.begin(),
                           ids_for_which_synonyms_are_known.end(),
                           7) != ids_for_which_synonyms_are_known.end());
-    ASSERT_EQ(3, fact_manager.GetSynonymsForId(7, context.get()).size());
+    ASSERT_EQ(3, transformation_context.GetFactManager()
+                     ->GetSynonymsForId(7, context.get())
+                     .size());
     protobufs::DataDescriptor descriptor_103 = MakeDataDescriptor(103, {});
-    ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(7, {}),
-                                          descriptor_103, context.get()));
+    ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+        MakeDataDescriptor(7, {}), descriptor_103, context.get()));
   }
 
   std::string after_transformation = R"(
@@ -340,116 +358,119 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   // Inapplicable because %18 is decorated.
   ASSERT_FALSE(TransformationCopyObject(
                    18, MakeInstructionDescriptor(21, SpvOpAccessChain, 0), 200)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // Inapplicable because %77 is decorated.
   ASSERT_FALSE(TransformationCopyObject(
                    77, MakeInstructionDescriptor(77, SpvOpBranch, 0), 200)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // Inapplicable because %80 is decorated.
   ASSERT_FALSE(TransformationCopyObject(
                    80, MakeInstructionDescriptor(77, SpvOpIAdd, 0), 200)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // Inapplicable because %84 is not available at the requested point
   ASSERT_FALSE(
       TransformationCopyObject(
           84, MakeInstructionDescriptor(32, SpvOpCompositeExtract, 0), 200)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
 
   // Fine because %84 is available at the requested point
   ASSERT_TRUE(
       TransformationCopyObject(
           84, MakeInstructionDescriptor(32, SpvOpCompositeConstruct, 0), 200)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
 
   // Inapplicable because id %9 is already in use
   ASSERT_FALSE(
       TransformationCopyObject(
           84, MakeInstructionDescriptor(32, SpvOpCompositeConstruct, 0), 9)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
 
   // Inapplicable because the requested point does not exist
   ASSERT_FALSE(TransformationCopyObject(
                    84, MakeInstructionDescriptor(86, SpvOpReturn, 2), 200)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // Inapplicable because %9 is not in a function
   ASSERT_FALSE(TransformationCopyObject(
                    9, MakeInstructionDescriptor(9, SpvOpTypeInt, 0), 200)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // Inapplicable because the insert point is right before, or inside, a chunk
   // of OpPhis
   ASSERT_FALSE(TransformationCopyObject(
                    9, MakeInstructionDescriptor(30, SpvOpPhi, 0), 200)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationCopyObject(
                    9, MakeInstructionDescriptor(99, SpvOpPhi, 1), 200)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // OK, because the insert point is just after a chunk of OpPhis.
   ASSERT_TRUE(TransformationCopyObject(
                   9, MakeInstructionDescriptor(96, SpvOpAccessChain, 0), 200)
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
 
   // Inapplicable because the insert point is right after an OpSelectionMerge
   ASSERT_FALSE(
       TransformationCopyObject(
           9, MakeInstructionDescriptor(58, SpvOpBranchConditional, 0), 200)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
 
   // OK, because the insert point is right before the OpSelectionMerge
   ASSERT_TRUE(TransformationCopyObject(
                   9, MakeInstructionDescriptor(58, SpvOpSelectionMerge, 0), 200)
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
 
   // Inapplicable because the insert point is right after an OpSelectionMerge
   ASSERT_FALSE(TransformationCopyObject(
                    9, MakeInstructionDescriptor(43, SpvOpSwitch, 0), 200)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // OK, because the insert point is right before the OpSelectionMerge
   ASSERT_TRUE(TransformationCopyObject(
                   9, MakeInstructionDescriptor(43, SpvOpSelectionMerge, 0), 200)
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
 
   // Inapplicable because the insert point is right after an OpLoopMerge
   ASSERT_FALSE(
       TransformationCopyObject(
           9, MakeInstructionDescriptor(40, SpvOpBranchConditional, 0), 200)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
 
   // OK, because the insert point is right before the OpLoopMerge
   ASSERT_TRUE(TransformationCopyObject(
                   9, MakeInstructionDescriptor(40, SpvOpLoopMerge, 0), 200)
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
 
   // Inapplicable because id %300 does not exist
   ASSERT_FALSE(TransformationCopyObject(
                    300, MakeInstructionDescriptor(40, SpvOpLoopMerge, 0), 200)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // Inapplicable because the following instruction is OpVariable
   ASSERT_FALSE(TransformationCopyObject(
                    9, MakeInstructionDescriptor(180, SpvOpVariable, 0), 200)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationCopyObject(
                    9, MakeInstructionDescriptor(181, SpvOpVariable, 0), 200)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationCopyObject(
                    9, MakeInstructionDescriptor(182, SpvOpVariable, 0), 200)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // OK, because this is just past the group of OpVariable instructions.
   ASSERT_TRUE(TransformationCopyObject(
                   9, MakeInstructionDescriptor(182, SpvOpAccessChain, 0), 200)
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationCopyObjectTest, MiscellaneousCopies) {
@@ -515,6 +536,9 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   std::vector<TransformationCopyObject> transformations = {
       TransformationCopyObject(19, MakeInstructionDescriptor(22, SpvOpStore, 0),
@@ -533,8 +557,9 @@
           17, MakeInstructionDescriptor(22, SpvOpCopyObject, 0), 106)};
 
   for (auto& transformation : transformations) {
-    ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
-    transformation.Apply(context.get(), &fact_manager);
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    transformation.Apply(context.get(), &transformation_context);
   }
 
   ASSERT_TRUE(IsValid(env, context.get()));
@@ -588,6 +613,111 @@
   ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
 }
 
+TEST(TransformationCopyObjectTest, DoNotCopyNullOrUndefPointers) {
+  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 = OpTypePointer Function %6
+          %8 = OpConstantNull %7
+          %9 = OpUndef %7
+          %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;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
+  // Illegal to copy null.
+  ASSERT_FALSE(TransformationCopyObject(
+                   8, MakeInstructionDescriptor(5, SpvOpReturn, 0), 100)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Illegal to copy an OpUndef of pointer type.
+  ASSERT_FALSE(TransformationCopyObject(
+                   9, MakeInstructionDescriptor(5, SpvOpReturn, 0), 100)
+                   .IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationCopyObjectTest, PropagateIrrelevantPointeeFact) {
+  // Checks that if a pointer is known to have an irrelevant value, the same
+  // holds after the pointer is copied.
+
+  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 = OpTypePointer Function %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+          %9 = OpVariable %7 Function
+               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);
+
+  transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant(8);
+
+  TransformationCopyObject transformation1(
+      8, MakeInstructionDescriptor(9, SpvOpReturn, 0), 100);
+  TransformationCopyObject transformation2(
+      9, MakeInstructionDescriptor(9, SpvOpReturn, 0), 101);
+  TransformationCopyObject transformation3(
+      100, MakeInstructionDescriptor(9, SpvOpReturn, 0), 102);
+
+  ASSERT_TRUE(
+      transformation1.IsApplicable(context.get(), transformation_context));
+  transformation1.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(
+      transformation2.IsApplicable(context.get(), transformation_context));
+  transformation2.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(
+      transformation3.IsApplicable(context.get(), transformation_context));
+  transformation3.Apply(context.get(), &transformation_context);
+
+  ASSERT_TRUE(
+      transformation_context.GetFactManager()->PointeeValueIsIrrelevant(8));
+  ASSERT_TRUE(
+      transformation_context.GetFactManager()->PointeeValueIsIrrelevant(100));
+  ASSERT_TRUE(
+      transformation_context.GetFactManager()->PointeeValueIsIrrelevant(102));
+  ASSERT_FALSE(
+      transformation_context.GetFactManager()->PointeeValueIsIrrelevant(9));
+  ASSERT_FALSE(
+      transformation_context.GetFactManager()->PointeeValueIsIrrelevant(101));
+}
+
 }  // namespace
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/test/fuzz/transformation_equation_instruction_test.cpp b/test/fuzz/transformation_equation_instruction_test.cpp
new file mode 100644
index 0000000..3155b18
--- /dev/null
+++ b/test/fuzz/transformation_equation_instruction_test.cpp
@@ -0,0 +1,631 @@
+// 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_equation_instruction.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationEquationInstructionTest, SignedNegate) {
+  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
+         %40 = OpTypeBool
+         %41 = OpConstantTrue %40
+         %20 = OpUndef %6
+         %12 = OpFunction %2 None %3
+         %13 = OpLabel
+         %30 = OpCopyObject %6 %7
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
+  protobufs::InstructionDescriptor return_instruction =
+      MakeInstructionDescriptor(13, SpvOpReturn, 0);
+
+  // Bad: id already in use.
+  ASSERT_FALSE(TransformationEquationInstruction(7, SpvOpSNegate, {7},
+                                                 return_instruction)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: identified instruction does not exist.
+  ASSERT_FALSE(
+      TransformationEquationInstruction(
+          14, SpvOpSNegate, {7}, MakeInstructionDescriptor(13, SpvOpLoad, 0))
+          .IsApplicable(context.get(), transformation_context));
+
+  // Bad: id 100 does not exist
+  ASSERT_FALSE(TransformationEquationInstruction(14, SpvOpSNegate, {100},
+                                                 return_instruction)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: id 20 is an OpUndef
+  ASSERT_FALSE(TransformationEquationInstruction(14, SpvOpSNegate, {20},
+                                                 return_instruction)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: id 30 is not available right before its definition
+  ASSERT_FALSE(TransformationEquationInstruction(
+                   14, SpvOpSNegate, {30},
+                   MakeInstructionDescriptor(30, SpvOpCopyObject, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: too many arguments to OpSNegate.
+  ASSERT_FALSE(TransformationEquationInstruction(14, SpvOpSNegate, {7, 7},
+                                                 return_instruction)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: 40 is a type id.
+  ASSERT_FALSE(TransformationEquationInstruction(14, SpvOpSNegate, {40},
+                                                 return_instruction)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: wrong type of argument to OpSNegate.
+  ASSERT_FALSE(TransformationEquationInstruction(14, SpvOpSNegate, {41},
+                                                 return_instruction)
+                   .IsApplicable(context.get(), transformation_context));
+
+  auto transformation1 = TransformationEquationInstruction(
+      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()));
+
+  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()));
+
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(15, {}), MakeDataDescriptor(7, {}), context.get()));
+
+  std::string after_transformation = 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
+         %40 = OpTypeBool
+         %41 = OpConstantTrue %40
+         %20 = OpUndef %6
+         %12 = OpFunction %2 None %3
+         %13 = OpLabel
+         %30 = OpCopyObject %6 %7
+         %14 = OpSNegate %6 %7
+         %15 = OpSNegate %6 %14
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationEquationInstructionTest, LogicalNot) {
+  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
+         %20 = OpTypeInt 32 0
+         %21 = OpConstant %20 5
+         %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;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
+  protobufs::InstructionDescriptor return_instruction =
+      MakeInstructionDescriptor(13, SpvOpReturn, 0);
+
+  // Bad: too few arguments to OpLogicalNot.
+  ASSERT_FALSE(TransformationEquationInstruction(14, SpvOpLogicalNot, {},
+                                                 return_instruction)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: 6 is a type id.
+  ASSERT_FALSE(TransformationEquationInstruction(14, SpvOpLogicalNot, {6},
+                                                 return_instruction)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: wrong type of argument to OpLogicalNot.
+  ASSERT_FALSE(TransformationEquationInstruction(14, SpvOpLogicalNot, {21},
+                                                 return_instruction)
+                   .IsApplicable(context.get(), transformation_context));
+
+  auto transformation1 = TransformationEquationInstruction(
+      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()));
+
+  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()));
+
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(15, {}), MakeDataDescriptor(7, {}), context.get()));
+
+  std::string after_transformation = 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
+         %20 = OpTypeInt 32 0
+         %21 = OpConstant %20 5
+         %12 = OpFunction %2 None %3
+         %13 = OpLabel
+         %14 = OpLogicalNot %6 %7
+         %15 = OpLogicalNot %6 %14
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationEquationInstructionTest, AddSubNegate1) {
+  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
+               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);
+
+  protobufs::InstructionDescriptor return_instruction =
+      MakeInstructionDescriptor(13, SpvOpReturn, 0);
+
+  // Bad: too many arguments to OpIAdd.
+  ASSERT_FALSE(TransformationEquationInstruction(14, SpvOpIAdd, {15, 16, 16},
+                                                 return_instruction)
+                   .IsApplicable(context.get(), transformation_context));
+  // Bad: boolean argument to OpIAdd.
+  ASSERT_FALSE(TransformationEquationInstruction(14, SpvOpIAdd, {15, 32},
+                                                 return_instruction)
+                   .IsApplicable(context.get(), transformation_context));
+  // Bad: type as argument to OpIAdd.
+  ASSERT_FALSE(TransformationEquationInstruction(14, SpvOpIAdd, {33, 16},
+                                                 return_instruction)
+                   .IsApplicable(context.get(), transformation_context));
+  // Bad: arguments of mismatched widths
+  ASSERT_FALSE(TransformationEquationInstruction(14, SpvOpIAdd, {15, 31},
+                                                 return_instruction)
+                   .IsApplicable(context.get(), transformation_context));
+  // Bad: arguments of mismatched widths
+  ASSERT_FALSE(TransformationEquationInstruction(14, SpvOpIAdd, {31, 15},
+                                                 return_instruction)
+                   .IsApplicable(context.get(), transformation_context));
+
+  auto transformation1 = TransformationEquationInstruction(
+      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()));
+
+  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()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(15, {}), MakeDataDescriptor(19, {}), context.get()));
+
+  auto transformation3 = TransformationEquationInstruction(
+      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()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(20, {}), MakeDataDescriptor(16, {}), context.get()));
+
+  auto transformation4 = TransformationEquationInstruction(
+      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()));
+
+  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()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(24, {}), MakeDataDescriptor(15, {}), context.get()));
+
+  std::string after_transformation = 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
+         %14 = OpIAdd %6 %15 %16
+         %19 = OpISub %6 %14 %16 ; ==> synonymous(%19, %15)
+         %20 = OpISub %6 %14 %15 ; ==> synonymous(%20, %16)
+         %22 = OpISub %6 %16 %14
+         %24 = OpSNegate %6 %22 ; ==> synonymous(%24, %15)
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationEquationInstructionTest, AddSubNegate2) {
+  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
+               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);
+
+  protobufs::InstructionDescriptor return_instruction =
+      MakeInstructionDescriptor(13, SpvOpReturn, 0);
+
+  auto transformation1 = TransformationEquationInstruction(
+      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()));
+
+  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()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(17, {}), MakeDataDescriptor(15, {}), context.get()));
+
+  auto transformation3 = TransformationEquationInstruction(
+      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()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(17, {}), MakeDataDescriptor(18, {}), context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(18, {}), MakeDataDescriptor(15, {}), context.get()));
+
+  auto transformation4 = TransformationEquationInstruction(
+      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()));
+
+  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()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(20, {}), MakeDataDescriptor(16, {}), context.get()));
+
+  auto transformation6 = TransformationEquationInstruction(
+      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()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(21, {}), MakeDataDescriptor(15, {}), context.get()));
+
+  auto transformation7 = TransformationEquationInstruction(
+      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()));
+
+  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()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(23, {}), MakeDataDescriptor(16, {}), context.get()));
+
+  std::string after_transformation = 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
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationEquationInstructionTest, Miscellaneous1) {
+  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
+        %113 = OpConstant %6 24
+         %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;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
+  protobufs::InstructionDescriptor return_instruction =
+      MakeInstructionDescriptor(13, SpvOpReturn, 0);
+
+  auto transformation1 = TransformationEquationInstruction(
+      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()));
+
+  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()));
+
+  std::string after_transformation = 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
+        %113 = OpConstant %6 24
+         %12 = OpFunction %2 None %3
+         %13 = OpLabel
+        %522 = OpISub %6 %113 %113
+        %570 = OpIAdd %6 %522 %113
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(570, {}), MakeDataDescriptor(113, {}), context.get()));
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationEquationInstructionTest, Miscellaneous2) {
+  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
+        %113 = OpConstant %6 24
+         %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;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
+  protobufs::InstructionDescriptor return_instruction =
+      MakeInstructionDescriptor(13, SpvOpReturn, 0);
+
+  auto transformation1 = TransformationEquationInstruction(
+      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()));
+
+  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()));
+
+  std::string after_transformation = 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
+        %113 = OpConstant %6 24
+         %12 = OpFunction %2 None %3
+         %13 = OpLabel
+        %522 = OpISub %6 %113 %113
+        %570 = OpIAdd %6 %522 %113
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(570, {}), MakeDataDescriptor(113, {}), context.get()));
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, 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
new file mode 100644
index 0000000..d7305f8
--- /dev/null
+++ b/test/fuzz/transformation_function_call_test.cpp
@@ -0,0 +1,464 @@
+// 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_function_call.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationFunctionCallTest, 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 = OpTypePointer Function %6
+          %8 = OpTypeFunction %6 %7
+         %12 = OpTypeFloat 32
+         %13 = OpTypePointer Function %12
+         %14 = OpTypeFunction %6 %7 %13
+         %27 = OpConstant %6 1
+         %50 = OpConstant %12 1
+         %57 = OpTypeBool
+         %58 = OpConstantFalse %57
+        %204 = OpUndef %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %61 = OpVariable %7 Function
+         %62 = OpVariable %7 Function
+         %65 = OpVariable %13 Function
+         %66 = OpVariable %7 Function
+         %68 = OpVariable %13 Function
+         %71 = OpVariable %7 Function
+         %72 = OpVariable %13 Function
+         %73 = OpVariable %7 Function
+         %75 = OpVariable %13 Function
+         %78 = OpVariable %7 Function
+         %98 = OpAccessChain %7 %71
+         %99 = OpCopyObject %7 %71
+               OpSelectionMerge %60 None
+               OpBranchConditional %58 %59 %60
+         %59 = OpLabel
+               OpBranch %60
+         %60 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %6 None %8
+          %9 = OpFunctionParameter %7
+         %11 = OpLabel
+         %26 = OpLoad %6 %9
+         %28 = OpIAdd %6 %26 %27
+               OpSelectionMerge %97 None
+               OpBranchConditional %58 %96 %97
+         %96 = OpLabel
+               OpBranch %97
+         %97 = OpLabel
+               OpReturnValue %28
+               OpFunctionEnd
+         %17 = OpFunction %6 None %14
+         %15 = OpFunctionParameter %7
+         %16 = OpFunctionParameter %13
+         %18 = OpLabel
+         %31 = OpVariable %7 Function
+         %32 = OpLoad %6 %15
+               OpStore %31 %32
+         %33 = OpFunctionCall %6 %10 %31
+               OpReturnValue %33
+               OpFunctionEnd
+         %21 = OpFunction %6 None %14
+         %19 = OpFunctionParameter %7
+         %20 = OpFunctionParameter %13
+         %22 = OpLabel
+         %36 = OpLoad %6 %19
+         %37 = OpLoad %12 %20
+         %38 = OpConvertFToS %6 %37
+         %39 = OpIAdd %6 %36 %38
+               OpReturnValue %39
+               OpFunctionEnd
+         %24 = OpFunction %6 None %8
+         %23 = OpFunctionParameter %7
+         %25 = OpLabel
+         %44 = OpVariable %7 Function
+         %46 = OpVariable %13 Function
+         %51 = OpVariable %7 Function
+         %52 = OpVariable %13 Function
+         %42 = OpLoad %6 %23
+         %43 = OpConvertSToF %12 %42
+         %45 = OpLoad %6 %23
+               OpStore %44 %45
+               OpStore %46 %43
+         %47 = OpFunctionCall %6 %17 %44 %46
+         %48 = OpLoad %6 %23
+         %49 = OpIAdd %6 %48 %27
+               OpStore %51 %49
+               OpStore %52 %50
+         %53 = OpFunctionCall %6 %17 %51 %52
+         %54 = OpIAdd %6 %47 %53
+               OpReturnValue %54
+               OpFunctionEnd
+        %200 = OpFunction %6 None %14
+        %201 = OpFunctionParameter %7
+        %202 = OpFunctionParameter %13
+        %203 = OpLabel
+               OpSelectionMerge %206 None
+               OpBranchConditional %58 %205 %206
+        %205 = OpLabel
+               OpBranch %206
+        %206 = OpLabel
+               OpReturnValue %204
+               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;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
+  transformation_context.GetFactManager()->AddFactBlockIsDead(59);
+  transformation_context.GetFactManager()->AddFactBlockIsDead(11);
+  transformation_context.GetFactManager()->AddFactBlockIsDead(18);
+  transformation_context.GetFactManager()->AddFactBlockIsDead(25);
+  transformation_context.GetFactManager()->AddFactBlockIsDead(96);
+  transformation_context.GetFactManager()->AddFactBlockIsDead(205);
+  transformation_context.GetFactManager()->AddFactFunctionIsLivesafe(21);
+  transformation_context.GetFactManager()->AddFactFunctionIsLivesafe(200);
+  transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
+      71);
+  transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
+      72);
+  transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
+      19);
+  transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
+      20);
+  transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
+      23);
+  transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
+      44);
+  transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
+      46);
+  transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
+      51);
+  transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
+      52);
+
+  // Livesafe functions with argument types: 21(7, 13), 200(7, 13)
+  // Non-livesafe functions with argument types: 4(), 10(7), 17(7, 13), 24(7)
+  // Call graph edges:
+  //    17 -> 10
+  //    24 -> 17
+
+  // Bad transformations
+  // Too many arguments
+  ASSERT_FALSE(
+      TransformationFunctionCall(100, 21, {71, 72, 71},
+                                 MakeInstructionDescriptor(59, SpvOpBranch, 0))
+          .IsApplicable(context.get(), transformation_context));
+  // Too few arguments
+  ASSERT_FALSE(TransformationFunctionCall(
+                   100, 21, {71}, MakeInstructionDescriptor(59, SpvOpBranch, 0))
+                   .IsApplicable(context.get(), transformation_context));
+  // Arguments are the wrong way around (types do not match)
+  ASSERT_FALSE(
+      TransformationFunctionCall(100, 21, {72, 71},
+                                 MakeInstructionDescriptor(59, SpvOpBranch, 0))
+          .IsApplicable(context.get(), transformation_context));
+  // 21 is not an appropriate argument
+  ASSERT_FALSE(
+      TransformationFunctionCall(100, 21, {21, 72},
+                                 MakeInstructionDescriptor(59, SpvOpBranch, 0))
+          .IsApplicable(context.get(), transformation_context));
+  // 300 does not exist
+  ASSERT_FALSE(
+      TransformationFunctionCall(100, 21, {300, 72},
+                                 MakeInstructionDescriptor(59, SpvOpBranch, 0))
+          .IsApplicable(context.get(), transformation_context));
+  // 71 is not a function
+  ASSERT_FALSE(
+      TransformationFunctionCall(100, 71, {71, 72},
+                                 MakeInstructionDescriptor(59, SpvOpBranch, 0))
+          .IsApplicable(context.get(), transformation_context));
+  // 500 does not exist
+  ASSERT_FALSE(
+      TransformationFunctionCall(100, 500, {71, 72},
+                                 MakeInstructionDescriptor(59, SpvOpBranch, 0))
+          .IsApplicable(context.get(), transformation_context));
+  // Id is not fresh
+  ASSERT_FALSE(
+      TransformationFunctionCall(21, 21, {71, 72},
+                                 MakeInstructionDescriptor(59, SpvOpBranch, 0))
+          .IsApplicable(context.get(), transformation_context));
+  // Access chain as pointer parameter
+  ASSERT_FALSE(
+      TransformationFunctionCall(100, 21, {98, 72},
+                                 MakeInstructionDescriptor(59, SpvOpBranch, 0))
+          .IsApplicable(context.get(), transformation_context));
+  // Copied object as pointer parameter
+  ASSERT_FALSE(
+      TransformationFunctionCall(100, 21, {99, 72},
+                                 MakeInstructionDescriptor(59, SpvOpBranch, 0))
+          .IsApplicable(context.get(), transformation_context));
+  // Non-livesafe called from original live block
+  ASSERT_FALSE(
+      TransformationFunctionCall(
+          100, 10, {71}, MakeInstructionDescriptor(99, SpvOpSelectionMerge, 0))
+          .IsApplicable(context.get(), transformation_context));
+  // Non-livesafe called from livesafe function
+  ASSERT_FALSE(
+      TransformationFunctionCall(
+          100, 10, {19}, MakeInstructionDescriptor(38, SpvOpConvertFToS, 0))
+          .IsApplicable(context.get(), transformation_context));
+  // Livesafe function called with pointer to non-arbitrary local variable
+  ASSERT_FALSE(
+      TransformationFunctionCall(
+          100, 21, {61, 72}, MakeInstructionDescriptor(38, SpvOpConvertFToS, 0))
+          .IsApplicable(context.get(), transformation_context));
+  // Direct recursion
+  ASSERT_FALSE(TransformationFunctionCall(
+                   100, 4, {}, MakeInstructionDescriptor(59, SpvOpBranch, 0))
+                   .IsApplicable(context.get(), transformation_context));
+  // Indirect recursion
+  ASSERT_FALSE(TransformationFunctionCall(
+                   100, 24, {9}, MakeInstructionDescriptor(96, SpvOpBranch, 0))
+                   .IsApplicable(context.get(), transformation_context));
+  // Parameter 23 is not available at the call site
+  ASSERT_FALSE(
+      TransformationFunctionCall(104, 10, {23},
+                                 MakeInstructionDescriptor(205, SpvOpBranch, 0))
+          .IsApplicable(context.get(), transformation_context));
+
+  // Good transformations
+  {
+    // Livesafe called from dead block: fine
+    TransformationFunctionCall transformation(
+        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()));
+  }
+  {
+    // Livesafe called from original live block: fine
+    TransformationFunctionCall transformation(
+        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()));
+  }
+  {
+    // Livesafe called from livesafe function: fine
+    TransformationFunctionCall transformation(
+        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()));
+  }
+  {
+    // Dead called from dead block in injected function: fine
+    TransformationFunctionCall transformation(
+        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()));
+  }
+  {
+    // Non-livesafe called from dead block in livesafe function: OK
+    TransformationFunctionCall transformation(
+        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()));
+  }
+  {
+    // Livesafe called from dead block with non-arbitrary parameter
+    TransformationFunctionCall transformation(
+        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()));
+  }
+
+  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 = OpTypePointer Function %6
+          %8 = OpTypeFunction %6 %7
+         %12 = OpTypeFloat 32
+         %13 = OpTypePointer Function %12
+         %14 = OpTypeFunction %6 %7 %13
+         %27 = OpConstant %6 1
+         %50 = OpConstant %12 1
+         %57 = OpTypeBool
+         %58 = OpConstantFalse %57
+        %204 = OpUndef %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %61 = OpVariable %7 Function
+         %62 = OpVariable %7 Function
+         %65 = OpVariable %13 Function
+         %66 = OpVariable %7 Function
+         %68 = OpVariable %13 Function
+         %71 = OpVariable %7 Function
+         %72 = OpVariable %13 Function
+         %73 = OpVariable %7 Function
+         %75 = OpVariable %13 Function
+         %78 = OpVariable %7 Function
+        %101 = OpFunctionCall %6 %21 %71 %72
+         %98 = OpAccessChain %7 %71
+         %99 = OpCopyObject %7 %71
+               OpSelectionMerge %60 None
+               OpBranchConditional %58 %59 %60
+         %59 = OpLabel
+        %100 = OpFunctionCall %6 %21 %71 %72
+        %105 = OpFunctionCall %6 %21 %62 %65
+               OpBranch %60
+         %60 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %6 None %8
+          %9 = OpFunctionParameter %7
+         %11 = OpLabel
+         %26 = OpLoad %6 %9
+         %28 = OpIAdd %6 %26 %27
+               OpSelectionMerge %97 None
+               OpBranchConditional %58 %96 %97
+         %96 = OpLabel
+               OpBranch %97
+         %97 = OpLabel
+               OpReturnValue %28
+               OpFunctionEnd
+         %17 = OpFunction %6 None %14
+         %15 = OpFunctionParameter %7
+         %16 = OpFunctionParameter %13
+         %18 = OpLabel
+         %31 = OpVariable %7 Function
+         %32 = OpLoad %6 %15
+               OpStore %31 %32
+         %33 = OpFunctionCall %6 %10 %31
+               OpReturnValue %33
+               OpFunctionEnd
+         %21 = OpFunction %6 None %14
+         %19 = OpFunctionParameter %7
+         %20 = OpFunctionParameter %13
+         %22 = OpLabel
+        %102 = OpFunctionCall %6 %200 %19 %20
+         %36 = OpLoad %6 %19
+         %37 = OpLoad %12 %20
+         %38 = OpConvertFToS %6 %37
+         %39 = OpIAdd %6 %36 %38
+               OpReturnValue %39
+               OpFunctionEnd
+         %24 = OpFunction %6 None %8
+         %23 = OpFunctionParameter %7
+         %25 = OpLabel
+         %44 = OpVariable %7 Function
+         %46 = OpVariable %13 Function
+         %51 = OpVariable %7 Function
+         %52 = OpVariable %13 Function
+         %42 = OpLoad %6 %23
+         %43 = OpConvertSToF %12 %42
+        %103 = OpFunctionCall %6 %10 %23
+         %45 = OpLoad %6 %23
+               OpStore %44 %45
+               OpStore %46 %43
+         %47 = OpFunctionCall %6 %17 %44 %46
+         %48 = OpLoad %6 %23
+         %49 = OpIAdd %6 %48 %27
+               OpStore %51 %49
+               OpStore %52 %50
+         %53 = OpFunctionCall %6 %17 %51 %52
+         %54 = OpIAdd %6 %47 %53
+               OpReturnValue %54
+               OpFunctionEnd
+        %200 = OpFunction %6 None %14
+        %201 = OpFunctionParameter %7
+        %202 = OpFunctionParameter %13
+        %203 = OpLabel
+               OpSelectionMerge %206 None
+               OpBranchConditional %58 %205 %206
+        %205 = OpLabel
+        %104 = OpFunctionCall %6 %10 %201
+               OpBranch %206
+        %206 = OpLabel
+               OpReturnValue %204
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationFunctionCallTest, DoNotInvokeEntryPoint) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %2 None %3
+         %11 = 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;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               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.
+  ASSERT_FALSE(TransformationFunctionCall(
+                   100, 4, {}, MakeInstructionDescriptor(11, SpvOpReturn, 0))
+                   .IsApplicable(context.get(), transformation_context));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/transformation_load_test.cpp b/test/fuzz/transformation_load_test.cpp
new file mode 100644
index 0000000..18ca195
--- /dev/null
+++ b/test/fuzz/transformation_load_test.cpp
@@ -0,0 +1,289 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_load.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationLoadTest, 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
+          %8 = OpTypeStruct %6 %7
+          %9 = OpTypePointer Function %8
+         %10 = OpTypeFunction %6 %9
+         %14 = OpConstant %6 0
+         %15 = OpTypePointer Function %6
+         %51 = OpTypePointer Private %6
+         %21 = OpConstant %6 2
+         %23 = OpConstant %6 1
+         %24 = OpConstant %7 1
+         %25 = OpTypePointer Function %7
+         %50 = OpTypePointer Private %7
+         %34 = OpTypeBool
+         %35 = OpConstantFalse %34
+         %60 = OpConstantNull %50
+         %61 = OpUndef %51
+         %52 = OpVariable %50 Private
+         %53 = OpVariable %51 Private
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %20 = OpVariable %9 Function
+         %27 = OpVariable %9 Function ; irrelevant
+         %22 = OpAccessChain %15 %20 %14
+         %44 = OpCopyObject %9 %20
+         %26 = OpAccessChain %25 %20 %23
+         %29 = OpFunctionCall %6 %12 %27
+         %30 = OpAccessChain %15 %20 %14
+         %45 = OpCopyObject %15 %30
+         %33 = OpAccessChain %15 %20 %14
+               OpSelectionMerge %37 None
+               OpBranchConditional %35 %36 %37
+         %36 = OpLabel
+         %38 = OpAccessChain %15 %20 %14
+         %40 = OpAccessChain %15 %20 %14
+         %43 = OpAccessChain %15 %20 %14
+               OpBranch %37
+         %37 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %6 None %10
+         %11 = OpFunctionParameter %9 ; irrelevant
+         %13 = OpLabel
+         %46 = OpCopyObject %9 %11 ; irrelevant
+         %16 = OpAccessChain %15 %11 %14 ; irrelevant
+               OpReturnValue %21
+               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;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
+  transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
+      27);
+  transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
+      11);
+  transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
+      46);
+  transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
+      16);
+  transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
+      52);
+
+  transformation_context.GetFactManager()->AddFactBlockIsDead(36);
+
+  // Variables with pointee types:
+  //  52 - ptr_to(7)
+  //  53 - ptr_to(6)
+  //  20 - ptr_to(8)
+  //  27 - ptr_to(8) - irrelevant
+
+  // Access chains with pointee type:
+  //  22 - ptr_to(6)
+  //  26 - ptr_to(6)
+  //  30 - ptr_to(6)
+  //  33 - ptr_to(6)
+  //  38 - ptr_to(6)
+  //  40 - ptr_to(6)
+  //  43 - ptr_to(6)
+  //  16 - ptr_to(6) - irrelevant
+
+  // Copied object with pointee type:
+  //  44 - ptr_to(8)
+  //  45 - ptr_to(6)
+  //  46 - ptr_to(8) - irrelevant
+
+  // Function parameters with pointee type:
+  //  11 - ptr_to(8) - irrelevant
+
+  // Pointers that cannot be used:
+  //  60 - null
+  //  61 - undefined
+
+  // Bad: id is not fresh
+  ASSERT_FALSE(TransformationLoad(
+                   33, 33, MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
+                   .IsApplicable(context.get(), transformation_context));
+  // Bad: attempt to load from 11 from outside its function
+  ASSERT_FALSE(TransformationLoad(
+                   100, 11, MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: pointer is not available
+  ASSERT_FALSE(TransformationLoad(
+                   100, 33, MakeInstructionDescriptor(45, SpvOpCopyObject, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: attempt to insert before OpVariable
+  ASSERT_FALSE(TransformationLoad(
+                   100, 27, MakeInstructionDescriptor(27, SpvOpVariable, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: pointer id does not exist
+  ASSERT_FALSE(
+      TransformationLoad(100, 1000,
+                         MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
+
+  // Bad: pointer id exists but does not have a type
+  ASSERT_FALSE(TransformationLoad(
+                   100, 5, MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: pointer id exists and has a type, but is not a pointer
+  ASSERT_FALSE(TransformationLoad(
+                   100, 24, MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: attempt to load from null pointer
+  ASSERT_FALSE(TransformationLoad(
+                   100, 60, MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: attempt to load from undefined pointer
+  ASSERT_FALSE(TransformationLoad(
+                   100, 61, MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
+                   .IsApplicable(context.get(), transformation_context));
+  // Bad: %40 is not available at the program point
+  ASSERT_FALSE(
+      TransformationLoad(100, 40, MakeInstructionDescriptor(37, SpvOpReturn, 0))
+          .IsApplicable(context.get(), transformation_context));
+
+  // Bad: The described instruction does not exist
+  ASSERT_FALSE(TransformationLoad(
+                   100, 33, MakeInstructionDescriptor(1000, SpvOpReturn, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  {
+    TransformationLoad transformation(
+        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()));
+  }
+
+  {
+    TransformationLoad transformation(
+        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()));
+  }
+
+  {
+    TransformationLoad transformation(
+        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()));
+  }
+
+  {
+    TransformationLoad transformation(
+        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()));
+  }
+
+  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
+          %8 = OpTypeStruct %6 %7
+          %9 = OpTypePointer Function %8
+         %10 = OpTypeFunction %6 %9
+         %14 = OpConstant %6 0
+         %15 = OpTypePointer Function %6
+         %51 = OpTypePointer Private %6
+         %21 = OpConstant %6 2
+         %23 = OpConstant %6 1
+         %24 = OpConstant %7 1
+         %25 = OpTypePointer Function %7
+         %50 = OpTypePointer Private %7
+         %34 = OpTypeBool
+         %35 = OpConstantFalse %34
+         %60 = OpConstantNull %50
+         %61 = OpUndef %51
+         %52 = OpVariable %50 Private
+         %53 = OpVariable %51 Private
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %20 = OpVariable %9 Function
+         %27 = OpVariable %9 Function ; irrelevant
+         %22 = OpAccessChain %15 %20 %14
+         %44 = OpCopyObject %9 %20
+         %26 = OpAccessChain %25 %20 %23
+         %29 = OpFunctionCall %6 %12 %27
+         %30 = OpAccessChain %15 %20 %14
+         %45 = OpCopyObject %15 %30
+         %33 = OpAccessChain %15 %20 %14
+               OpSelectionMerge %37 None
+               OpBranchConditional %35 %36 %37
+         %36 = OpLabel
+        %100 = OpLoad %6 %33
+         %38 = OpAccessChain %15 %20 %14
+         %40 = OpAccessChain %15 %20 %14
+        %103 = OpLoad %6 %40
+         %43 = OpAccessChain %15 %20 %14
+               OpBranch %37
+         %37 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %6 None %10
+         %11 = OpFunctionParameter %9 ; irrelevant
+         %13 = OpLabel
+         %46 = OpCopyObject %9 %11 ; irrelevant
+         %16 = OpAccessChain %15 %11 %14 ; irrelevant
+        %101 = OpLoad %8 %46
+        %102 = OpLoad %6 %16
+               OpReturnValue %21
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_transformation, 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 e2b4aa6..4500445 100644
--- a/test/fuzz/transformation_merge_blocks_test.cpp
+++ b/test/fuzz/transformation_merge_blocks_test.cpp
@@ -45,11 +45,14 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
-  ASSERT_FALSE(
-      TransformationMergeBlocks(3).IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(
-      TransformationMergeBlocks(7).IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationMergeBlocks(3).IsApplicable(
+      context.get(), transformation_context));
+  ASSERT_FALSE(TransformationMergeBlocks(7).IsApplicable(
+      context.get(), transformation_context));
 }
 
 TEST(TransformationMergeBlocksTest, DoNotMergeFirstBlockHasMultipleSuccessors) {
@@ -84,9 +87,12 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
-  ASSERT_FALSE(
-      TransformationMergeBlocks(6).IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationMergeBlocks(6).IsApplicable(
+      context.get(), transformation_context));
 }
 
 TEST(TransformationMergeBlocksTest,
@@ -122,9 +128,12 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
-  ASSERT_FALSE(
-      TransformationMergeBlocks(10).IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationMergeBlocks(10).IsApplicable(
+      context.get(), transformation_context));
 }
 
 TEST(TransformationMergeBlocksTest, MergeWhenSecondBlockIsSelectionMerge) {
@@ -161,10 +170,14 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   TransformationMergeBlocks transformation(10);
-  ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
-  transformation.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  transformation.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   std::string after_transformation = R"(
@@ -231,10 +244,14 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   TransformationMergeBlocks transformation(10);
-  ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
-  transformation.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  transformation.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   std::string after_transformation = R"(
@@ -306,10 +323,14 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   TransformationMergeBlocks transformation(11);
-  ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
-  transformation.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  transformation.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   std::string after_transformation = R"(
@@ -377,10 +398,14 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   TransformationMergeBlocks transformation(6);
-  ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
-  transformation.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  transformation.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   std::string after_transformation = R"(
@@ -454,12 +479,16 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   for (auto& transformation :
        {TransformationMergeBlocks(100), TransformationMergeBlocks(101),
         TransformationMergeBlocks(102), TransformationMergeBlocks(103)}) {
-    ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
-    transformation.Apply(context.get(), &fact_manager);
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    transformation.Apply(context.get(), &transformation_context);
     ASSERT_TRUE(IsValid(env, context.get()));
   }
 
@@ -542,11 +571,15 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   for (auto& transformation :
        {TransformationMergeBlocks(101), TransformationMergeBlocks(100)}) {
-    ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
-    transformation.Apply(context.get(), &fact_manager);
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    transformation.Apply(context.get(), &transformation_context);
     ASSERT_TRUE(IsValid(env, context.get()));
   }
 
@@ -629,10 +662,14 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   TransformationMergeBlocks transformation(101);
-  ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
-  transformation.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  transformation.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   std::string after_transformation = R"(
diff --git a/test/fuzz/transformation_move_block_down_test.cpp b/test/fuzz/transformation_move_block_down_test.cpp
index 02761a2..662e88c 100644
--- a/test/fuzz/transformation_move_block_down_test.cpp
+++ b/test/fuzz/transformation_move_block_down_test.cpp
@@ -53,9 +53,13 @@
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   auto transformation = TransformationMoveBlockDown(11);
-  ASSERT_FALSE(transformation.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationMoveBlockDownTest, NoMovePossible2) {
@@ -90,9 +94,13 @@
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   auto transformation = TransformationMoveBlockDown(5);
-  ASSERT_FALSE(transformation.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationMoveBlockDownTest, NoMovePossible3) {
@@ -129,9 +137,13 @@
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   auto transformation = TransformationMoveBlockDown(100);
-  ASSERT_FALSE(transformation.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationMoveBlockDownTest, NoMovePossible4) {
@@ -172,9 +184,13 @@
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   auto transformation = TransformationMoveBlockDown(12);
-  ASSERT_FALSE(transformation.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationMoveBlockDownTest, ManyMovesPossible) {
@@ -277,6 +293,9 @@
       BuildModule(env, consumer, before_transformation, kFuzzAssembleOption);
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               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
@@ -306,110 +325,130 @@
   // 15 dominates nothing
 
   // Current ordering: 5 14 20 23 21 25 29 32 30 15
-  ASSERT_FALSE(move_down_5.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(move_down_14.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_20.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_23.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_21.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(move_down_25.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_29.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_32.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_30.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(move_down_15.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(move_down_5.IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      move_down_14.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_20.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_23.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_21.IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      move_down_25.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_29.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_32.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_30.IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      move_down_15.IsApplicable(context.get(), transformation_context));
 
   // Let's bubble 20 all the way down.
 
-  move_down_20.Apply(context.get(), &fact_manager);
+  move_down_20.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // Current ordering: 5 14 23 20 21 25 29 32 30 15
-  ASSERT_FALSE(move_down_5.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(move_down_14.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_23.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_20.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_21.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(move_down_25.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_29.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_32.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_30.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(move_down_15.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(move_down_5.IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      move_down_14.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_23.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_20.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_21.IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      move_down_25.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_29.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_32.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_30.IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      move_down_15.IsApplicable(context.get(), transformation_context));
 
-  move_down_20.Apply(context.get(), &fact_manager);
+  move_down_20.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // Current ordering: 5 14 23 21 20 25 29 32 30 15
-  ASSERT_FALSE(move_down_5.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(move_down_14.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_23.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_21.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_20.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(move_down_25.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_29.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_32.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_30.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(move_down_15.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(move_down_5.IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      move_down_14.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_23.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_21.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_20.IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      move_down_25.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_29.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_32.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_30.IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      move_down_15.IsApplicable(context.get(), transformation_context));
 
-  move_down_20.Apply(context.get(), &fact_manager);
+  move_down_20.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // Current ordering: 5 14 23 21 25 20 29 32 30 15
-  ASSERT_FALSE(move_down_5.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(move_down_14.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_23.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_21.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_25.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_20.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_29.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_32.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_30.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(move_down_15.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(move_down_5.IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      move_down_14.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_23.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_21.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_25.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_20.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_29.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_32.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_30.IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      move_down_15.IsApplicable(context.get(), transformation_context));
 
-  move_down_20.Apply(context.get(), &fact_manager);
+  move_down_20.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // Current ordering: 5 14 23 21 25 29 20 32 30 15
-  ASSERT_FALSE(move_down_5.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(move_down_14.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_23.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_21.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(move_down_25.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_29.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_20.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_32.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_30.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(move_down_15.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(move_down_5.IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      move_down_14.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_23.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_21.IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      move_down_25.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_29.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_20.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_32.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_30.IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      move_down_15.IsApplicable(context.get(), transformation_context));
 
-  move_down_20.Apply(context.get(), &fact_manager);
+  move_down_20.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // Current ordering: 5 14 23 21 25 29 32 20 30 15
-  ASSERT_FALSE(move_down_5.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(move_down_14.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_23.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_21.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(move_down_25.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_29.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_32.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_20.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_30.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(move_down_15.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(move_down_5.IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      move_down_14.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_23.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_21.IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      move_down_25.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_29.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_32.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_20.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_30.IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      move_down_15.IsApplicable(context.get(), transformation_context));
 
-  move_down_20.Apply(context.get(), &fact_manager);
+  move_down_20.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // Current ordering: 5 14 23 21 25 29 32 30 20 15
-  ASSERT_FALSE(move_down_5.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(move_down_14.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_23.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_21.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(move_down_25.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_29.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_32.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_30.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_20.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(move_down_15.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(move_down_5.IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      move_down_14.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_23.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_21.IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      move_down_25.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_29.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_32.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_30.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_20.IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      move_down_15.IsApplicable(context.get(), transformation_context));
 
-  move_down_20.Apply(context.get(), &fact_manager);
+  move_down_20.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   std::string after_bubbling_20_down = R"(
@@ -485,63 +524,72 @@
   ASSERT_TRUE(IsEqual(env, after_bubbling_20_down, context.get()));
 
   // Current ordering: 5 14 23 21 25 29 32 30 15 20
-  ASSERT_FALSE(move_down_5.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(move_down_14.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_23.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_21.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(move_down_25.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_29.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_32.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_30.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_15.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(move_down_20.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(move_down_5.IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      move_down_14.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_23.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_21.IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      move_down_25.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_29.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_32.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_30.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_15.IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      move_down_20.IsApplicable(context.get(), transformation_context));
 
-  move_down_23.Apply(context.get(), &fact_manager);
+  move_down_23.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // Current ordering: 5 14 21 23 25 29 32 30 15 20
-  ASSERT_FALSE(move_down_5.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(move_down_14.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_21.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_23.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(move_down_25.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_29.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_32.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_30.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_15.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(move_down_20.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(move_down_5.IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      move_down_14.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_21.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_23.IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      move_down_25.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_29.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_32.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_30.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_15.IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      move_down_20.IsApplicable(context.get(), transformation_context));
 
-  move_down_23.Apply(context.get(), &fact_manager);
+  move_down_23.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // Current ordering: 5 14 21 25 23 29 32 30 15 20
-  ASSERT_FALSE(move_down_5.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(move_down_14.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_21.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_25.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_23.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_29.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_32.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_30.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_15.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(move_down_20.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(move_down_5.IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      move_down_14.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_21.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_25.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_23.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_29.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_32.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_30.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_15.IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      move_down_20.IsApplicable(context.get(), transformation_context));
 
-  move_down_21.Apply(context.get(), &fact_manager);
+  move_down_21.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // Current ordering: 5 14 25 21 23 29 32 30 15 20
-  ASSERT_FALSE(move_down_5.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_14.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_21.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_25.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_23.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_29.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_32.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_30.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_15.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(move_down_20.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(move_down_5.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_14.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_21.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_25.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_23.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_29.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_32.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_30.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_15.IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      move_down_20.IsApplicable(context.get(), transformation_context));
 
-  move_down_14.Apply(context.get(), &fact_manager);
+  move_down_14.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   std::string after_more_shuffling = R"(
@@ -617,16 +665,18 @@
   ASSERT_TRUE(IsEqual(env, after_more_shuffling, context.get()));
 
   // Final ordering: 5 25 14 21 23 29 32 30 15 20
-  ASSERT_FALSE(move_down_5.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_25.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(move_down_14.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_21.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_23.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_29.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_32.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_30.IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(move_down_15.IsApplicable(context.get(), fact_manager));
-  ASSERT_FALSE(move_down_20.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(move_down_5.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_25.IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      move_down_14.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_21.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_23.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_29.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_32.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_30.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(move_down_15.IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      move_down_20.IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationMoveBlockDownTest, DoNotMoveUnreachable) {
@@ -660,9 +710,13 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   auto transformation = TransformationMoveBlockDown(6);
-  ASSERT_FALSE(transformation.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
 }
 
 }  // namespace
diff --git a/test/fuzz/transformation_outline_function_test.cpp b/test/fuzz/transformation_outline_function_test.cpp
index 4f828b6..7beed85 100644
--- a/test/fuzz/transformation_outline_function_test.cpp
+++ b/test/fuzz/transformation_outline_function_test.cpp
@@ -44,12 +44,16 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   TransformationOutlineFunction transformation(5, 5, /* not relevant */ 200,
                                                100, 101, 102, 103,
                                                /* not relevant */ 201, {}, {});
-  ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
-  transformation.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  transformation.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   std::string after_transformation = R"(
@@ -105,11 +109,15 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   TransformationOutlineFunction transformation(5, 5, /* not relevant */ 200,
                                                100, 101, 102, 103,
                                                /* not relevant */ 201, {}, {});
-  ASSERT_FALSE(transformation.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationOutlineFunctionTest, OutlineInterestingControlFlowNoState) {
@@ -158,12 +166,16 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   TransformationOutlineFunction transformation(6, 13, /* not relevant */
                                                200, 100, 101, 102, 103,
                                                /* not relevant */ 201, {}, {});
-  ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
-  transformation.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  transformation.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   std::string after_transformation = R"(
@@ -243,12 +255,16 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   TransformationOutlineFunction transformation(6, 6, /* not relevant */ 200,
                                                100, 101, 102, 103,
                                                /* not relevant */ 201, {}, {});
-  ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
-  transformation.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  transformation.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   std::string after_transformation = R"(
@@ -317,11 +333,15 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   TransformationOutlineFunction transformation(6, 6, 99, 100, 101, 102, 103,
                                                105, {}, {{9, 104}});
-  ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
-  transformation.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  transformation.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   std::string after_transformation = R"(
@@ -412,12 +432,16 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               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(), fact_manager));
-  transformation.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  transformation.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   std::string after_transformation = R"(
@@ -508,11 +532,15 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   TransformationOutlineFunction transformation(6, 6, 100, 101, 102, 103, 104,
                                                105, {{7, 106}}, {});
-  ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
-  transformation.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  transformation.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   std::string after_transformation = R"(
@@ -582,11 +610,15 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   TransformationOutlineFunction transformation(6, 6, 100, 101, 102, 103, 104,
                                                105, {{13, 106}}, {});
-  ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
-  transformation.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  transformation.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   std::string after_transformation = R"(
@@ -666,11 +698,15 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   TransformationOutlineFunction transformation(11, 11, 100, 101, 102, 103, 104,
                                                105, {{9, 106}}, {{14, 107}});
-  ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
-  transformation.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  transformation.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   std::string after_transformation = R"(
@@ -752,10 +788,14 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   TransformationOutlineFunction transformation(6, 8, 100, 101, 102, 103, 104,
                                                105, {}, {});
-  ASSERT_FALSE(transformation.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationOutlineFunctionTest, DoNotOutlineIfRegionInvolvesReturn) {
@@ -798,11 +838,15 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   TransformationOutlineFunction transformation(6, 11, /* not relevant */ 200,
                                                100, 101, 102, 103,
                                                /* not relevant */ 201, {}, {});
-  ASSERT_FALSE(transformation.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationOutlineFunctionTest, DoNotOutlineIfRegionInvolvesKill) {
@@ -845,11 +889,15 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   TransformationOutlineFunction transformation(6, 11, /* not relevant */ 200,
                                                100, 101, 102, 103,
                                                /* not relevant */ 201, {}, {});
-  ASSERT_FALSE(transformation.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationOutlineFunctionTest,
@@ -893,11 +941,15 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   TransformationOutlineFunction transformation(6, 11, /* not relevant */ 200,
                                                100, 101, 102, 103,
                                                /* not relevant */ 201, {}, {});
-  ASSERT_FALSE(transformation.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationOutlineFunctionTest,
@@ -933,10 +985,14 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   TransformationOutlineFunction transformation(6, 8, 100, 101, 102, 103, 104,
                                                105, {}, {});
-  ASSERT_FALSE(transformation.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationOutlineFunctionTest, DoNotOutlineIfLoopHeadIsOutsideRegion) {
@@ -973,10 +1029,14 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   TransformationOutlineFunction transformation(7, 8, 100, 101, 102, 103, 104,
                                                105, {}, {});
-  ASSERT_FALSE(transformation.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationOutlineFunctionTest,
@@ -1012,10 +1072,14 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   TransformationOutlineFunction transformation(6, 7, 100, 101, 102, 103, 104,
                                                105, {}, {});
-  ASSERT_FALSE(transformation.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationOutlineFunctionTest,
@@ -1053,10 +1117,14 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   TransformationOutlineFunction transformation(6, 7, 100, 101, 102, 103, 104,
                                                105, {}, {});
-  ASSERT_FALSE(transformation.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationOutlineFunctionTest,
@@ -1094,10 +1162,14 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   TransformationOutlineFunction transformation(8, 11, 100, 101, 102, 103, 104,
                                                105, {}, {});
-  ASSERT_FALSE(transformation.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationOutlineFunctionTest, OutlineRegionEndingWithReturnVoid) {
@@ -1132,6 +1204,9 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   TransformationOutlineFunction transformation(
       /*entry_block*/ 54,
@@ -1145,8 +1220,9 @@
       /*input_id_to_fresh_id*/ {{22, 206}},
       /*output_id_to_fresh_id*/ {});
 
-  ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
-  transformation.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  transformation.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   std::string after_transformation = R"(
@@ -1219,6 +1295,9 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   TransformationOutlineFunction transformation(
       /*entry_block*/ 9,
@@ -1232,8 +1311,9 @@
       /*input_id_to_fresh_id*/ {{31, 206}},
       /*output_id_to_fresh_id*/ {{32, 207}});
 
-  ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
-  transformation.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  transformation.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   std::string after_transformation = R"(
@@ -1310,6 +1390,9 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   TransformationOutlineFunction transformation(
       /*entry_block*/ 54,
@@ -1323,8 +1406,9 @@
       /*input_id_to_fresh_id*/ {{}},
       /*output_id_to_fresh_id*/ {{6, 206}});
 
-  ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
-  transformation.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  transformation.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   std::string after_transformation = R"(
@@ -1396,6 +1480,9 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   TransformationOutlineFunction transformation(
       /*entry_block*/ 54,
@@ -1409,8 +1496,9 @@
       /*input_id_to_fresh_id*/ {},
       /*output_id_to_fresh_id*/ {});
 
-  ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
-  transformation.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  transformation.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   std::string after_transformation = R"(
@@ -1478,6 +1566,9 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   TransformationOutlineFunction transformation(
       /*entry_block*/ 21,
@@ -1491,7 +1582,8 @@
       /*input_id_to_fresh_id*/ {{22, 207}},
       /*output_id_to_fresh_id*/ {{23, 208}});
 
-  ASSERT_FALSE(transformation.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationOutlineFunctionTest,
@@ -1531,6 +1623,9 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   TransformationOutlineFunction transformation(
       /*entry_block*/ 21,
@@ -1544,7 +1639,8 @@
       /*input_id_to_fresh_id*/ {},
       /*output_id_to_fresh_id*/ {});
 
-  ASSERT_FALSE(transformation.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationOutlineFunctionTest,
@@ -1584,6 +1680,9 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   TransformationOutlineFunction transformation(
       /*entry_block*/ 5,
@@ -1597,7 +1696,8 @@
       /*input_id_to_fresh_id*/ {},
       /*output_id_to_fresh_id*/ {});
 
-  ASSERT_FALSE(transformation.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationOutlineFunctionTest, DoNotOutlineRegionThatUsesAccessChain) {
@@ -1640,6 +1740,9 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   TransformationOutlineFunction transformation(
       /*entry_block*/ 13,
@@ -1653,7 +1756,708 @@
       /*input_id_to_fresh_id*/ {{12, 207}},
       /*output_id_to_fresh_id*/ {});
 
-  ASSERT_FALSE(transformation.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationOutlineFunctionTest,
+     DoNotOutlineRegionThatUsesCopiedObject) {
+  // Copying a variable leads to a pointer, but one that cannot be passed as a
+  // function parameter, as it is not a memory object.
+  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 = OpTypeFloat 32
+          %7 = OpTypeVector %6 4
+          %8 = OpTypePointer Function %7
+          %9 = OpTypePointer Function %6
+         %18 = OpTypeInt 32 0
+         %19 = OpConstant %18 0
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %10 = OpVariable %8 Function
+               OpBranch %11
+         %11 = OpLabel
+         %20 = OpCopyObject %8 %10
+               OpBranch %13
+         %13 = OpLabel
+         %12 = OpAccessChain %9 %20 %19
+         %14 = OpLoad %6 %12
+               OpBranch %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);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
+  TransformationOutlineFunction transformation(
+      /*entry_block*/ 13,
+      /*exit_block*/ 15,
+      /*new_function_struct_return_type_id*/ 200,
+      /*new_function_type_id*/ 201,
+      /*new_function_id*/ 202,
+      /*new_function_region_entry_block*/ 204,
+      /*new_caller_result_id*/ 205,
+      /*new_callee_result_id*/ 206,
+      /*input_id_to_fresh_id*/ {{20, 207}},
+      /*output_id_to_fresh_id*/ {});
+
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationOutlineFunctionTest,
+     DoOutlineRegionThatUsesPointerParameter) {
+  // The region being outlined reads from a function parameter of pointer type.
+  // This is OK: the function parameter can itself be passed on as a function
+  // parameter.
+  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 = OpTypePointer Function %6
+          %8 = OpTypeFunction %2 %7
+         %13 = OpConstant %6 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %15 = OpVariable %7 Function
+         %16 = OpVariable %7 Function
+         %17 = OpLoad %6 %15
+               OpStore %16 %17
+         %18 = OpFunctionCall %2 %10 %16
+         %19 = OpLoad %6 %16
+               OpStore %15 %19
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %2 None %8
+          %9 = OpFunctionParameter %7
+         %11 = OpLabel
+         %12 = OpLoad %6 %9
+         %14 = OpIAdd %6 %12 %13
+               OpBranch %20
+         %20 = OpLabel
+               OpStore %9 %14
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  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);
+
+  TransformationOutlineFunction transformation(
+      /*entry_block*/ 11,
+      /*exit_block*/ 11,
+      /*new_function_struct_return_type_id*/ 200,
+      /*new_function_type_id*/ 201,
+      /*new_function_id*/ 202,
+      /*new_function_region_entry_block*/ 204,
+      /*new_caller_result_id*/ 205,
+      /*new_callee_result_id*/ 206,
+      /*input_id_to_fresh_id*/ {{9, 207}},
+      /*output_id_to_fresh_id*/ {{14, 208}});
+
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  transformation.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %8 = OpTypeFunction %2 %7
+         %13 = OpConstant %6 2
+        %200 = OpTypeStruct %6
+        %201 = OpTypeFunction %200 %7
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %15 = OpVariable %7 Function
+         %16 = OpVariable %7 Function
+         %17 = OpLoad %6 %15
+               OpStore %16 %17
+         %18 = OpFunctionCall %2 %10 %16
+         %19 = OpLoad %6 %16
+               OpStore %15 %19
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %2 None %8
+          %9 = OpFunctionParameter %7
+         %11 = OpLabel
+        %205 = OpFunctionCall %200 %202 %9
+         %14 = OpCompositeExtract %6 %205 0
+               OpBranch %20
+         %20 = OpLabel
+               OpStore %9 %14
+               OpReturn
+               OpFunctionEnd
+        %202 = OpFunction %200 None %201
+        %207 = OpFunctionParameter %7
+        %204 = OpLabel
+         %12 = OpLoad %6 %207
+        %208 = OpIAdd %6 %12 %13
+        %206 = OpCompositeConstruct %200 %208
+               OpReturnValue %206
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationOutlineFunctionTest, OutlineLivesafe) {
+  // In the following, %30 is a livesafe function, with irrelevant parameter
+  // %200 and irrelevant local variable %201.  Variable %100 is a loop limiter,
+  // which is not irrelevant.  The test checks that the outlined function is
+  // livesafe, and that the parameters corresponding to %200 and %201 have the
+  // irrelevant fact associated with them.
+  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 0
+          %7 = OpTypePointer Function %6
+        %199 = OpTypeFunction %2 %7
+          %8 = OpConstant %6 0
+          %9 = OpConstant %6 1
+         %10 = OpConstant %6 5
+         %11 = OpTypeBool
+         %12 = OpConstantTrue %11
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %30 = OpFunction %2 None %199
+        %200 = OpFunctionParameter %7
+         %31 = OpLabel
+        %100 = OpVariable %7 Function %8
+        %201 = OpVariable %7 Function %8
+               OpBranch %198
+        %198 = OpLabel
+               OpBranch %20
+         %20 = OpLabel
+        %101 = OpLoad %6 %100
+        %102 = OpIAdd %6 %101 %9
+        %202 = OpLoad %6 %200
+               OpStore %201 %202
+               OpStore %100 %102
+        %103 = OpUGreaterThanEqual %11 %101 %10
+               OpLoopMerge %21 %22 None
+               OpBranchConditional %103 %21 %104
+        %104 = OpLabel
+               OpBranchConditional %12 %23 %21
+         %23 = OpLabel
+        %105 = OpLoad %6 %100
+        %106 = OpIAdd %6 %105 %9
+               OpStore %100 %106
+        %107 = OpUGreaterThanEqual %11 %105 %10
+               OpLoopMerge %25 %26 None
+               OpBranchConditional %107 %25 %108
+        %108 = OpLabel
+               OpBranch %28
+         %28 = OpLabel
+               OpBranchConditional %12 %26 %25
+         %26 = OpLabel
+               OpBranch %23
+         %25 = OpLabel
+        %109 = OpLoad %6 %100
+        %110 = OpIAdd %6 %109 %9
+               OpStore %100 %110
+        %111 = OpUGreaterThanEqual %11 %109 %10
+               OpLoopMerge %24 %27 None
+               OpBranchConditional %111 %24 %112
+        %112 = OpLabel
+               OpBranchConditional %12 %24 %27
+         %27 = OpLabel
+               OpBranch %25
+         %24 = OpLabel
+               OpBranch %22
+         %22 = OpLabel
+               OpBranch %20
+         %21 = OpLabel
+               OpBranch %197
+        %197 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  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);
+
+  transformation_context.GetFactManager()->AddFactFunctionIsLivesafe(30);
+  transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
+      200);
+  transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
+      201);
+
+  TransformationOutlineFunction transformation(
+      /*entry_block*/ 198,
+      /*exit_block*/ 197,
+      /*new_function_struct_return_type_id*/ 400,
+      /*new_function_type_id*/ 401,
+      /*new_function_id*/ 402,
+      /*new_function_region_entry_block*/ 404,
+      /*new_caller_result_id*/ 405,
+      /*new_callee_result_id*/ 406,
+      /*input_id_to_fresh_id*/ {{100, 407}, {200, 408}, {201, 409}},
+      /*output_id_to_fresh_id*/ {});
+
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  transformation.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // The original function should still be livesafe.
+  ASSERT_TRUE(transformation_context.GetFactManager()->FunctionIsLivesafe(30));
+  // The outlined function should be livesafe.
+  ASSERT_TRUE(transformation_context.GetFactManager()->FunctionIsLivesafe(402));
+  // The variable and parameter that were originally irrelevant should still be.
+  ASSERT_TRUE(
+      transformation_context.GetFactManager()->PointeeValueIsIrrelevant(200));
+  ASSERT_TRUE(
+      transformation_context.GetFactManager()->PointeeValueIsIrrelevant(201));
+  // The loop limiter should still be non-irrelevant.
+  ASSERT_FALSE(
+      transformation_context.GetFactManager()->PointeeValueIsIrrelevant(100));
+  // The parameters for the original irrelevant variables should be irrelevant.
+  ASSERT_TRUE(
+      transformation_context.GetFactManager()->PointeeValueIsIrrelevant(408));
+  ASSERT_TRUE(
+      transformation_context.GetFactManager()->PointeeValueIsIrrelevant(409));
+  // The parameter for the loop limiter should not be irrelevant.
+  ASSERT_FALSE(
+      transformation_context.GetFactManager()->PointeeValueIsIrrelevant(407));
+
+  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 0
+          %7 = OpTypePointer Function %6
+        %199 = OpTypeFunction %2 %7
+          %8 = OpConstant %6 0
+          %9 = OpConstant %6 1
+         %10 = OpConstant %6 5
+         %11 = OpTypeBool
+         %12 = OpConstantTrue %11
+        %401 = OpTypeFunction %2 %7 %7 %7
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %30 = OpFunction %2 None %199
+        %200 = OpFunctionParameter %7
+         %31 = OpLabel
+        %100 = OpVariable %7 Function %8
+        %201 = OpVariable %7 Function %8
+               OpBranch %198
+        %198 = OpLabel
+        %405 = OpFunctionCall %2 %402 %200 %100 %201
+               OpReturn
+               OpFunctionEnd
+        %402 = OpFunction %2 None %401
+        %408 = OpFunctionParameter %7
+        %407 = OpFunctionParameter %7
+        %409 = OpFunctionParameter %7
+        %404 = OpLabel
+               OpBranch %20
+         %20 = OpLabel
+        %101 = OpLoad %6 %407
+        %102 = OpIAdd %6 %101 %9
+        %202 = OpLoad %6 %408
+               OpStore %409 %202
+               OpStore %407 %102
+        %103 = OpUGreaterThanEqual %11 %101 %10
+               OpLoopMerge %21 %22 None
+               OpBranchConditional %103 %21 %104
+        %104 = OpLabel
+               OpBranchConditional %12 %23 %21
+         %23 = OpLabel
+        %105 = OpLoad %6 %407
+        %106 = OpIAdd %6 %105 %9
+               OpStore %407 %106
+        %107 = OpUGreaterThanEqual %11 %105 %10
+               OpLoopMerge %25 %26 None
+               OpBranchConditional %107 %25 %108
+        %108 = OpLabel
+               OpBranch %28
+         %28 = OpLabel
+               OpBranchConditional %12 %26 %25
+         %26 = OpLabel
+               OpBranch %23
+         %25 = OpLabel
+        %109 = OpLoad %6 %407
+        %110 = OpIAdd %6 %109 %9
+               OpStore %407 %110
+        %111 = OpUGreaterThanEqual %11 %109 %10
+               OpLoopMerge %24 %27 None
+               OpBranchConditional %111 %24 %112
+        %112 = OpLabel
+               OpBranchConditional %12 %24 %27
+         %27 = OpLabel
+               OpBranch %25
+         %24 = OpLabel
+               OpBranch %22
+         %22 = OpLabel
+               OpBranch %20
+         %21 = OpLabel
+               OpBranch %197
+        %197 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationOutlineFunctionTest, OutlineWithDeadBlocks1) {
+  // This checks that if all blocks in the region being outlined were dead, all
+  // blocks in the outlined function will be dead.
+  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 "foo(i1;"
+               OpName %9 "x"
+               OpName %12 "y"
+               OpName %21 "i"
+               OpName %46 "param"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %8 = OpTypeFunction %2 %7
+         %13 = OpConstant %6 2
+         %14 = OpTypeBool
+         %15 = OpConstantFalse %14
+         %22 = OpConstant %6 0
+         %29 = OpConstant %6 10
+         %41 = OpConstant %6 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %46 = OpVariable %7 Function
+               OpStore %46 %13
+         %47 = OpFunctionCall %2 %10 %46
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %2 None %8
+          %9 = OpFunctionParameter %7
+         %11 = OpLabel
+         %12 = OpVariable %7 Function
+         %21 = OpVariable %7 Function
+               OpStore %12 %13
+               OpSelectionMerge %17 None
+               OpBranchConditional %15 %16 %17
+         %16 = OpLabel
+         %18 = OpLoad %6 %9
+               OpStore %12 %18
+         %19 = OpLoad %6 %9
+         %20 = OpIAdd %6 %19 %13
+               OpStore %9 %20
+               OpStore %21 %22
+               OpBranch %23
+         %23 = OpLabel
+               OpLoopMerge %25 %26 None
+               OpBranch %27
+         %27 = OpLabel
+         %28 = OpLoad %6 %21
+         %30 = OpSLessThan %14 %28 %29
+               OpBranchConditional %30 %24 %25
+         %24 = OpLabel
+         %31 = OpLoad %6 %9
+         %32 = OpLoad %6 %21
+         %33 = OpSGreaterThan %14 %31 %32
+               OpSelectionMerge %35 None
+               OpBranchConditional %33 %34 %35
+         %34 = OpLabel
+               OpBranch %26
+         %35 = OpLabel
+         %37 = OpLoad %6 %9
+         %38 = OpLoad %6 %12
+         %39 = OpIAdd %6 %38 %37
+               OpStore %12 %39
+               OpBranch %26
+         %26 = OpLabel
+         %40 = OpLoad %6 %21
+         %42 = OpIAdd %6 %40 %41
+               OpStore %21 %42
+               OpBranch %23
+         %25 = OpLabel
+               OpBranch %50
+         %50 = OpLabel
+               OpBranch %17
+         %17 = OpLabel
+         %43 = OpLoad %6 %9
+         %44 = OpLoad %6 %12
+         %45 = OpIAdd %6 %44 %43
+               OpStore %12 %45
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  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);
+
+  for (uint32_t block_id : {16u, 23u, 24u, 26u, 27u, 34u, 35u, 50u}) {
+    transformation_context.GetFactManager()->AddFactBlockIsDead(block_id);
+  }
+
+  TransformationOutlineFunction transformation(
+      /*entry_block*/ 16,
+      /*exit_block*/ 50,
+      /*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*/ {{9, 206}, {12, 207}, {21, 208}},
+      /*output_id_to_fresh_id*/ {});
+
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  transformation.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(IsValid(env, context.get()));
+  // 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));
+  }
+}
+
+TEST(TransformationOutlineFunctionTest, OutlineWithDeadBlocks2) {
+  // This checks that if some, but not all, blocks in the outlined region are
+  // dead, those (but not others) will be dead in the outlined function.
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %8
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeBool
+          %7 = OpTypePointer Private %6
+          %8 = OpVariable %7 Private
+          %9 = OpConstantFalse %6
+         %10 = OpTypePointer Function %6
+         %12 = OpConstantTrue %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %11 = OpVariable %10 Function
+               OpBranch %30
+         %30 = OpLabel
+               OpStore %8 %9
+               OpBranch %31
+         %31 = OpLabel
+               OpStore %11 %12
+               OpSelectionMerge %36 None
+               OpBranchConditional %9 %32 %33
+         %32 = OpLabel
+               OpBranch %34
+         %33 = OpLabel
+               OpBranch %36
+         %34 = OpLabel
+               OpBranch %35
+         %35 = OpLabel
+               OpBranch %36
+         %36 = OpLabel
+               OpBranch %37
+         %37 = OpLabel
+         %13 = OpLoad %6 %8
+               OpStore %11 %13
+         %14 = OpLoad %6 %11
+               OpStore %8 %14
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  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);
+
+  for (uint32_t block_id : {32u, 34u, 35u}) {
+    transformation_context.GetFactManager()->AddFactBlockIsDead(block_id);
+  }
+
+  TransformationOutlineFunction transformation(
+      /*entry_block*/ 30,
+      /*exit_block*/ 37,
+      /*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*/ {{11, 206}},
+      /*output_id_to_fresh_id*/ {});
+
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  transformation.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(IsValid(env, context.get()));
+  // 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));
+  }
+  for (uint32_t block_id : {5u, 30u, 31u, 33u, 36u, 37u, 203u}) {
+    ASSERT_FALSE(
+        transformation_context.GetFactManager()->BlockIsDead(block_id));
+  }
+}
+
+TEST(TransformationOutlineFunctionTest,
+     OutlineWithIrrelevantVariablesAndParameters) {
+  // This checks that if the outlined region uses a mixture of irrelevant and
+  // non-irrelevant variables and parameters, these properties are preserved
+  // during outlining.
+  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 = OpTypePointer Function %6
+          %8 = OpTypeFunction %2 %7 %7
+         %13 = OpConstant %6 2
+         %15 = OpConstant %6 3
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %11 = OpFunction %2 None %8
+          %9 = OpFunctionParameter %7
+         %10 = OpFunctionParameter %7
+         %12 = OpLabel
+         %14 = OpVariable %7 Function
+         %20 = OpVariable %7 Function
+               OpBranch %50
+         %50 = OpLabel
+               OpStore %9 %13
+               OpStore %14 %15
+         %16 = OpLoad %6 %14
+               OpStore %10 %16
+         %17 = OpLoad %6 %9
+         %18 = OpLoad %6 %10
+         %19 = OpIAdd %6 %17 %18
+               OpStore %14 %19
+         %21 = OpLoad %6 %9
+               OpStore %20 %21
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  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);
+
+  transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant(9);
+  transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
+      14);
+
+  TransformationOutlineFunction transformation(
+      /*entry_block*/ 50,
+      /*exit_block*/ 50,
+      /*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*/ {{9, 206}, {10, 207}, {14, 208}, {20, 209}},
+      /*output_id_to_fresh_id*/ {});
+
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  transformation.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(IsValid(env, context.get()));
+  // 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}) {
+    ASSERT_TRUE(
+        transformation_context.GetFactManager()->PointeeValueIsIrrelevant(
+            variable_id));
+  }
+  for (uint32_t variable_id : {10u, 20u, 207u, 209u}) {
+    ASSERT_FALSE(
+        transformation_context.GetFactManager()->BlockIsDead(variable_id));
+  }
 }
 
 TEST(TransformationOutlineFunctionTest, Miscellaneous1) {
@@ -1764,6 +2568,9 @@
   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,
@@ -1777,8 +2584,9 @@
       /*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(), fact_manager));
-  transformation.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  transformation.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   std::string after_transformation = R"(
@@ -1929,6 +2737,9 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   TransformationOutlineFunction transformation(
       /*entry_block*/ 38,
@@ -1942,7 +2753,8 @@
       /*input_id_to_fresh_id*/ {},
       /*output_id_to_fresh_id*/ {});
 
-  ASSERT_FALSE(transformation.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationOutlineFunctionTest, Miscellaneous3) {
@@ -1984,6 +2796,9 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   TransformationOutlineFunction transformation(
       /*entry_block*/ 80,
@@ -1997,8 +2812,9 @@
       /*input_id_to_fresh_id*/ {},
       /*output_id_to_fresh_id*/ {});
 
-  ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
-  transformation.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  transformation.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   std::string after_transformation = R"(
@@ -2040,6 +2856,95 @@
   ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
 }
 
+TEST(TransformationOutlineFunctionTest, Miscellaneous4) {
+  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
+        %100 = OpTypeInt 32 0
+        %101 = OpTypePointer Function %100
+        %102 = OpTypePointer Function %100
+        %103 = OpTypeFunction %2 %101
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+        %104 = OpVariable %102 Function
+               OpBranch %80
+         %80 = OpLabel
+        %105 = OpLoad %100 %104
+               OpBranch %106
+        %106 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  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);
+
+  TransformationOutlineFunction transformation(
+      /*entry_block*/ 80,
+      /*exit_block*/ 106,
+      /*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*/ {{104, 307}},
+      /*output_id_to_fresh_id*/ {});
+
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  transformation.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  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
+        %100 = OpTypeInt 32 0
+        %101 = OpTypePointer Function %100
+        %102 = OpTypePointer Function %100
+        %103 = OpTypeFunction %2 %101
+        %301 = OpTypeFunction %2 %102
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+        %104 = OpVariable %102 Function
+               OpBranch %80
+         %80 = OpLabel
+        %305 = OpFunctionCall %2 %302 %104
+               OpReturn
+               OpFunctionEnd
+        %302 = OpFunction %2 None %301
+        %307 = OpFunctionParameter %102
+        %304 = OpLabel
+        %105 = OpLoad %100 %307
+               OpBranch %106
+        %106 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
 }  // namespace
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/test/fuzz/transformation_permute_function_parameters_test.cpp b/test/fuzz/transformation_permute_function_parameters_test.cpp
new file mode 100644
index 0000000..a4a7c00
--- /dev/null
+++ b/test/fuzz/transformation_permute_function_parameters_test.cpp
@@ -0,0 +1,435 @@
+// 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_permute_function_parameters.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationPermuteFunctionParametersTest, BasicTest) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %72 %74
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %12 "g(f1;f1;"
+               OpName %10 "x"
+               OpName %11 "y"
+               OpName %22 "f(f1;i1;vf2;"
+               OpName %19 "x"
+               OpName %20 "y"
+               OpName %21 "z"
+               OpName %28 "cond(i1;f1;"
+               OpName %26 "a"
+               OpName %27 "b"
+               OpName %53 "param"
+               OpName %54 "param"
+               OpName %66 "param"
+               OpName %67 "param"
+               OpName %72 "color"
+               OpName %74 "gl_FragCoord"
+               OpName %75 "param"
+               OpName %79 "param"
+               OpName %85 "param"
+               OpName %86 "param"
+               OpName %91 "param"
+               OpName %92 "param"
+               OpName %93 "param"
+               OpName %99 "param"
+               OpName %100 "param"
+               OpName %101 "param"
+               OpDecorate %20 RelaxedPrecision
+               OpDecorate %26 RelaxedPrecision
+               OpDecorate %47 RelaxedPrecision
+               OpDecorate %58 RelaxedPrecision
+               OpDecorate %72 Location 0
+               OpDecorate %74 BuiltIn FragCoord
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypePointer Function %6
+          %8 = OpTypeVector %6 4
+          %9 = OpTypeFunction %8 %7 %7
+         %14 = OpTypeInt 32 1
+         %15 = OpTypePointer Function %14
+         %16 = OpTypeVector %6 2
+         %17 = OpTypePointer Function %16
+         %18 = OpTypeFunction %8 %7 %15 %17
+         %24 = OpTypeBool
+         %25 = OpTypeFunction %24 %15 %7
+         %105 = OpTypeFunction %24 %7 %15    ; predefined type for %28
+         %31 = OpConstant %6 255
+         %33 = OpConstant %6 0
+         %34 = OpConstant %6 1
+         %42 = OpTypeInt 32 0
+         %43 = OpConstant %42 0
+         %49 = OpConstant %42 1
+         %64 = OpConstant %14 4
+         %65 = OpConstant %6 5
+         %71 = OpTypePointer Output %8
+         %72 = OpVariable %71 Output
+         %73 = OpTypePointer Input %8
+         %74 = OpVariable %73 Input
+         %76 = OpTypePointer Input %6
+         %84 = OpConstant %14 5
+         %90 = OpConstant %6 3
+         %98 = OpConstant %6 4
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %66 = OpVariable %15 Function
+         %67 = OpVariable %7 Function
+         %75 = OpVariable %7 Function
+         %79 = OpVariable %7 Function
+         %85 = OpVariable %15 Function
+         %86 = OpVariable %7 Function
+         %91 = OpVariable %7 Function
+         %92 = OpVariable %15 Function
+         %93 = OpVariable %17 Function
+         %99 = OpVariable %7 Function
+        %100 = OpVariable %15 Function
+        %101 = OpVariable %17 Function
+               OpStore %66 %64
+               OpStore %67 %65
+         %68 = OpFunctionCall %24 %28 %66 %67
+               OpSelectionMerge %70 None
+               OpBranchConditional %68 %69 %83
+         %69 = OpLabel
+         %77 = OpAccessChain %76 %74 %43
+         %78 = OpLoad %6 %77
+               OpStore %75 %78
+         %80 = OpAccessChain %76 %74 %49
+         %81 = OpLoad %6 %80
+               OpStore %79 %81
+         %82 = OpFunctionCall %8 %12 %75 %79
+               OpStore %72 %82
+               OpBranch %70
+         %83 = OpLabel
+               OpStore %85 %84
+               OpStore %86 %65
+         %87 = OpFunctionCall %24 %28 %85 %86
+               OpSelectionMerge %89 None
+               OpBranchConditional %87 %88 %97
+         %88 = OpLabel
+               OpStore %91 %90
+               OpStore %92 %64
+         %94 = OpLoad %8 %74
+         %95 = OpVectorShuffle %16 %94 %94 0 1
+               OpStore %93 %95
+         %96 = OpFunctionCall %8 %22 %91 %92 %93
+               OpStore %72 %96
+               OpBranch %89
+         %97 = OpLabel
+               OpStore %99 %98
+               OpStore %100 %84
+        %102 = OpLoad %8 %74
+        %103 = OpVectorShuffle %16 %102 %102 0 1
+               OpStore %101 %103
+        %104 = OpFunctionCall %8 %22 %99 %100 %101
+               OpStore %72 %104
+               OpBranch %89
+         %89 = OpLabel
+               OpBranch %70
+         %70 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %8 None %9
+         %10 = OpFunctionParameter %7
+         %11 = OpFunctionParameter %7
+         %13 = OpLabel
+         %30 = OpLoad %6 %10
+         %32 = OpFDiv %6 %30 %31
+         %35 = OpLoad %6 %11
+         %36 = OpFDiv %6 %35 %31
+         %37 = OpFSub %6 %34 %36
+         %38 = OpCompositeConstruct %8 %32 %33 %37 %34
+               OpReturnValue %38
+               OpFunctionEnd
+         %22 = OpFunction %8 None %18
+         %19 = OpFunctionParameter %7
+         %20 = OpFunctionParameter %15
+         %21 = OpFunctionParameter %17
+         %23 = OpLabel
+         %53 = OpVariable %7 Function
+         %54 = OpVariable %7 Function
+         %41 = OpLoad %6 %19
+         %44 = OpAccessChain %7 %21 %43
+         %45 = OpLoad %6 %44
+         %46 = OpFAdd %6 %41 %45
+         %47 = OpLoad %14 %20
+         %48 = OpConvertSToF %6 %47
+         %50 = OpAccessChain %7 %21 %49
+         %51 = OpLoad %6 %50
+         %52 = OpFAdd %6 %48 %51
+               OpStore %53 %46
+               OpStore %54 %52
+         %55 = OpFunctionCall %8 %12 %53 %54
+               OpReturnValue %55
+               OpFunctionEnd
+         %28 = OpFunction %24 None %25
+         %26 = OpFunctionParameter %15
+         %27 = OpFunctionParameter %7
+         %29 = OpLabel
+         %58 = OpLoad %14 %26
+         %59 = OpConvertSToF %6 %58
+         %60 = OpLoad %6 %27
+         %61 = OpFOrdLessThan %24 %59 %60
+               OpReturnValue %61
+               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);
+
+  // Can't permute main function
+  ASSERT_FALSE(TransformationPermuteFunctionParameters(4, 0, {}).IsApplicable(
+      context.get(), transformation_context));
+
+  // Can't permute invalid instruction
+  ASSERT_FALSE(TransformationPermuteFunctionParameters(101, 0, {})
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Permutation has too many values
+  ASSERT_FALSE(TransformationPermuteFunctionParameters(22, 0, {2, 1, 0, 3})
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Permutation has too few values
+  ASSERT_FALSE(TransformationPermuteFunctionParameters(22, 0, {0, 1})
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Permutation has invalid values
+  ASSERT_FALSE(TransformationPermuteFunctionParameters(22, 0, {3, 1, 0})
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Type id is not an OpTypeFunction instruction
+  ASSERT_FALSE(TransformationPermuteFunctionParameters(22, 42, {2, 1, 0})
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Type id has incorrect number of operands
+  ASSERT_FALSE(TransformationPermuteFunctionParameters(22, 9, {2, 1, 0})
+                   .IsApplicable(context.get(), transformation_context));
+
+  // OpTypeFunction has operands out of order
+  ASSERT_FALSE(TransformationPermuteFunctionParameters(22, 18, {2, 1, 0})
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Successful transformations
+  {
+    // Function has two operands of the same type:
+    // initial OpTypeFunction should be enough
+    TransformationPermuteFunctionParameters transformation(12, 9, {1, 0});
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    transformation.Apply(context.get(), &transformation_context);
+    ASSERT_TRUE(IsValid(env, context.get()));
+  }
+  {
+    TransformationPermuteFunctionParameters transformation(28, 105, {1, 0});
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    transformation.Apply(context.get(), &transformation_context);
+    ASSERT_TRUE(IsValid(env, context.get()));
+  }
+
+  std::string after_transformation = R"(
+    OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %72 %74
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %12 "g(f1;f1;"
+               OpName %10 "x"
+               OpName %11 "y"
+               OpName %22 "f(f1;i1;vf2;"
+               OpName %19 "x"
+               OpName %20 "y"
+               OpName %21 "z"
+               OpName %28 "cond(i1;f1;"
+               OpName %26 "a"
+               OpName %27 "b"
+               OpName %53 "param"
+               OpName %54 "param"
+               OpName %66 "param"
+               OpName %67 "param"
+               OpName %72 "color"
+               OpName %74 "gl_FragCoord"
+               OpName %75 "param"
+               OpName %79 "param"
+               OpName %85 "param"
+               OpName %86 "param"
+               OpName %91 "param"
+               OpName %92 "param"
+               OpName %93 "param"
+               OpName %99 "param"
+               OpName %100 "param"
+               OpName %101 "param"
+               OpDecorate %20 RelaxedPrecision
+               OpDecorate %26 RelaxedPrecision
+               OpDecorate %47 RelaxedPrecision
+               OpDecorate %58 RelaxedPrecision
+               OpDecorate %72 Location 0
+               OpDecorate %74 BuiltIn FragCoord
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypePointer Function %6
+          %8 = OpTypeVector %6 4
+          %9 = OpTypeFunction %8 %7 %7
+         %14 = OpTypeInt 32 1
+         %15 = OpTypePointer Function %14
+         %16 = OpTypeVector %6 2
+         %17 = OpTypePointer Function %16
+         %18 = OpTypeFunction %8 %7 %15 %17
+         %24 = OpTypeBool
+         %25 = OpTypeFunction %24 %15 %7
+         %105 = OpTypeFunction %24 %7 %15    ; predefined type for %28
+         %31 = OpConstant %6 255
+         %33 = OpConstant %6 0
+         %34 = OpConstant %6 1
+         %42 = OpTypeInt 32 0
+         %43 = OpConstant %42 0
+         %49 = OpConstant %42 1
+         %64 = OpConstant %14 4
+         %65 = OpConstant %6 5
+         %71 = OpTypePointer Output %8
+         %72 = OpVariable %71 Output
+         %73 = OpTypePointer Input %8
+         %74 = OpVariable %73 Input
+         %76 = OpTypePointer Input %6
+         %84 = OpConstant %14 5
+         %90 = OpConstant %6 3
+         %98 = OpConstant %6 4
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %66 = OpVariable %15 Function
+         %67 = OpVariable %7 Function
+         %75 = OpVariable %7 Function
+         %79 = OpVariable %7 Function
+         %85 = OpVariable %15 Function
+         %86 = OpVariable %7 Function
+         %91 = OpVariable %7 Function
+         %92 = OpVariable %15 Function
+         %93 = OpVariable %17 Function
+         %99 = OpVariable %7 Function
+        %100 = OpVariable %15 Function
+        %101 = OpVariable %17 Function
+               OpStore %66 %64
+               OpStore %67 %65
+         %68 = OpFunctionCall %24 %28 %67 %66
+               OpSelectionMerge %70 None
+               OpBranchConditional %68 %69 %83
+         %69 = OpLabel
+         %77 = OpAccessChain %76 %74 %43
+         %78 = OpLoad %6 %77
+               OpStore %75 %78
+         %80 = OpAccessChain %76 %74 %49
+         %81 = OpLoad %6 %80
+               OpStore %79 %81
+         %82 = OpFunctionCall %8 %12 %79 %75
+               OpStore %72 %82
+               OpBranch %70
+         %83 = OpLabel
+               OpStore %85 %84
+               OpStore %86 %65
+         %87 = OpFunctionCall %24 %28 %86 %85
+               OpSelectionMerge %89 None
+               OpBranchConditional %87 %88 %97
+         %88 = OpLabel
+               OpStore %91 %90
+               OpStore %92 %64
+         %94 = OpLoad %8 %74
+         %95 = OpVectorShuffle %16 %94 %94 0 1
+               OpStore %93 %95
+         %96 = OpFunctionCall %8 %22 %91 %92 %93
+               OpStore %72 %96
+               OpBranch %89
+         %97 = OpLabel
+               OpStore %99 %98
+               OpStore %100 %84
+        %102 = OpLoad %8 %74
+        %103 = OpVectorShuffle %16 %102 %102 0 1
+               OpStore %101 %103
+        %104 = OpFunctionCall %8 %22 %99 %100 %101
+               OpStore %72 %104
+               OpBranch %89
+         %89 = OpLabel
+               OpBranch %70
+         %70 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %8 None %9
+         %11 = OpFunctionParameter %7
+         %10 = OpFunctionParameter %7
+         %13 = OpLabel
+         %30 = OpLoad %6 %10
+         %32 = OpFDiv %6 %30 %31
+         %35 = OpLoad %6 %11
+         %36 = OpFDiv %6 %35 %31
+         %37 = OpFSub %6 %34 %36
+         %38 = OpCompositeConstruct %8 %32 %33 %37 %34
+               OpReturnValue %38
+               OpFunctionEnd
+         %22 = OpFunction %8 None %18
+         %19 = OpFunctionParameter %7
+         %20 = OpFunctionParameter %15
+         %21 = OpFunctionParameter %17
+         %23 = OpLabel
+         %53 = OpVariable %7 Function
+         %54 = OpVariable %7 Function
+         %41 = OpLoad %6 %19
+         %44 = OpAccessChain %7 %21 %43
+         %45 = OpLoad %6 %44
+         %46 = OpFAdd %6 %41 %45
+         %47 = OpLoad %14 %20
+         %48 = OpConvertSToF %6 %47
+         %50 = OpAccessChain %7 %21 %49
+         %51 = OpLoad %6 %50
+         %52 = OpFAdd %6 %48 %51
+               OpStore %53 %46
+               OpStore %54 %52
+         %55 = OpFunctionCall %8 %12 %54 %53
+               OpReturnValue %55
+               OpFunctionEnd
+         %28 = OpFunction %24 None %105
+         %27 = OpFunctionParameter %7
+         %26 = OpFunctionParameter %15
+         %29 = OpLabel
+         %58 = OpLoad %14 %26
+         %59 = OpConvertSToF %6 %58
+         %60 = OpLoad %6 %27
+         %61 = OpFOrdLessThan %24 %59 %60
+               OpReturnValue %61
+               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 bfc7fa7..b320308 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
@@ -163,6 +163,9 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   std::vector<protobufs::IdUseDescriptor> uses_of_true = {
       MakeIdUseDescriptor(41, MakeInstructionDescriptor(44, SpvOpStore, 12), 1),
@@ -197,10 +200,10 @@
 #define CHECK_OPERATOR(USE_DESCRIPTOR, LHS_ID, RHS_ID, OPCODE, FRESH_ID) \
   ASSERT_TRUE(TransformationReplaceBooleanConstantWithConstantBinary(    \
                   USE_DESCRIPTOR, LHS_ID, RHS_ID, OPCODE, FRESH_ID)      \
-                  .IsApplicable(context.get(), fact_manager));           \
+                  .IsApplicable(context.get(), transformation_context)); \
   ASSERT_FALSE(TransformationReplaceBooleanConstantWithConstantBinary(   \
                    USE_DESCRIPTOR, RHS_ID, LHS_ID, OPCODE, FRESH_ID)     \
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
 #define CHECK_TRANSFORMATION_APPLICABILITY(GT_OPCODES, LT_OPCODES, SMALL_ID, \
                                            LARGE_ID)                         \
@@ -252,27 +255,27 @@
   // Target id is not fresh
   ASSERT_FALSE(TransformationReplaceBooleanConstantWithConstantBinary(
                    uses_of_true[0], 15, 17, SpvOpFOrdLessThan, 15)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // LHS id does not exist
   ASSERT_FALSE(TransformationReplaceBooleanConstantWithConstantBinary(
                    uses_of_true[0], 300, 17, SpvOpFOrdLessThan, 200)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // RHS id does not exist
   ASSERT_FALSE(TransformationReplaceBooleanConstantWithConstantBinary(
                    uses_of_true[0], 15, 300, SpvOpFOrdLessThan, 200)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // LHS and RHS ids do not match type
   ASSERT_FALSE(TransformationReplaceBooleanConstantWithConstantBinary(
                    uses_of_true[0], 11, 17, SpvOpFOrdLessThan, 200)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // Opcode not appropriate
   ASSERT_FALSE(TransformationReplaceBooleanConstantWithConstantBinary(
                    uses_of_true[0], 15, 17, SpvOpFDiv, 200)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   auto replace_true_with_double_comparison =
       TransformationReplaceBooleanConstantWithConstantBinary(
@@ -287,21 +290,25 @@
       TransformationReplaceBooleanConstantWithConstantBinary(
           uses_of_false[1], 33, 31, SpvOpSLessThan, 103);
 
-  ASSERT_TRUE(replace_true_with_double_comparison.IsApplicable(context.get(),
-                                                               fact_manager));
-  replace_true_with_double_comparison.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(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()));
-  ASSERT_TRUE(replace_true_with_uint32_comparison.IsApplicable(context.get(),
-                                                               fact_manager));
-  replace_true_with_uint32_comparison.Apply(context.get(), &fact_manager);
+  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()));
-  ASSERT_TRUE(replace_false_with_float_comparison.IsApplicable(context.get(),
-                                                               fact_manager));
-  replace_false_with_float_comparison.Apply(context.get(), &fact_manager);
+  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()));
-  ASSERT_TRUE(replace_false_with_sint64_comparison.IsApplicable(context.get(),
-                                                                fact_manager));
-  replace_false_with_sint64_comparison.Apply(context.get(), &fact_manager);
+  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()));
 
   std::string after = R"(
@@ -419,7 +426,7 @@
     // The transformation is not applicable because %200 is NaN.
     ASSERT_FALSE(TransformationReplaceBooleanConstantWithConstantBinary(
                      uses_of_true[0], 11, 200, SpvOpFOrdLessThan, 300)
-                     .IsApplicable(context.get(), fact_manager));
+                     .IsApplicable(context.get(), transformation_context));
   }
   if (std::numeric_limits<double>::has_infinity) {
     double positive_infinity_double = std::numeric_limits<double>::infinity();
@@ -436,7 +443,7 @@
     // transformation is restricted to only apply to finite values.
     ASSERT_FALSE(TransformationReplaceBooleanConstantWithConstantBinary(
                      uses_of_true[0], 11, 201, SpvOpFOrdLessThan, 300)
-                     .IsApplicable(context.get(), fact_manager));
+                     .IsApplicable(context.get(), transformation_context));
   }
   if (std::numeric_limits<float>::has_infinity) {
     float positive_infinity_float = std::numeric_limits<float>::infinity();
@@ -461,7 +468,7 @@
     // values.
     ASSERT_FALSE(TransformationReplaceBooleanConstantWithConstantBinary(
                      uses_of_true[0], 203, 202, SpvOpFOrdLessThan, 300)
-                     .IsApplicable(context.get(), fact_manager));
+                     .IsApplicable(context.get(), transformation_context));
   }
 }
 
@@ -531,6 +538,9 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   auto use_of_true_in_if = MakeIdUseDescriptor(
       13, MakeInstructionDescriptor(10, SpvOpBranchConditional, 0), 0);
@@ -542,12 +552,14 @@
   auto replacement_2 = TransformationReplaceBooleanConstantWithConstantBinary(
       use_of_false_in_while, 9, 11, SpvOpSGreaterThanEqual, 101);
 
-  ASSERT_TRUE(replacement_1.IsApplicable(context.get(), fact_manager));
-  replacement_1.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      replacement_1.IsApplicable(context.get(), transformation_context));
+  replacement_1.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(replacement_2.IsApplicable(context.get(), fact_manager));
-  replacement_2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      replacement_2.IsApplicable(context.get(), transformation_context));
+  replacement_2.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   std::string after = R"(
@@ -642,12 +654,57 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   auto replacement = TransformationReplaceBooleanConstantWithConstantBinary(
       MakeIdUseDescriptor(9, MakeInstructionDescriptor(23, SpvOpPhi, 0), 0), 13,
       15, SpvOpSLessThan, 100);
 
-  ASSERT_FALSE(replacement.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(replacement.IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationReplaceBooleanConstantWithConstantBinaryTest,
+     DoNotReplaceVariableInitializer) {
+  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 = OpTypePointer Function %6
+          %9 = OpConstantTrue %6
+         %10 = OpTypeInt 32 1
+         %13 = OpConstant %10 0
+         %15 = OpConstant %10 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %50 = OpVariable %7 Function %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;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
+  ASSERT_FALSE(TransformationReplaceBooleanConstantWithConstantBinary(
+                   MakeIdUseDescriptor(
+                       9, MakeInstructionDescriptor(50, SpvOpVariable, 0), 1),
+                   13, 15, SpvOpSLessThan, 100)
+                   .IsApplicable(context.get(), transformation_context));
 }
 
 }  // namespace
diff --git a/test/fuzz/transformation_replace_constant_with_uniform_test.cpp b/test/fuzz/transformation_replace_constant_with_uniform_test.cpp
index ac2e3f9..8cbba46 100644
--- a/test/fuzz/transformation_replace_constant_with_uniform_test.cpp
+++ b/test/fuzz/transformation_replace_constant_with_uniform_test.cpp
@@ -22,7 +22,8 @@
 namespace {
 
 bool AddFactHelper(
-    FactManager* fact_manager, opt::IRContext* context, uint32_t word,
+    TransformationContext* transformation_context, opt::IRContext* context,
+    uint32_t word,
     const protobufs::UniformBufferElementDescriptor& descriptor) {
   protobufs::FactConstantUniform constant_uniform_fact;
   constant_uniform_fact.add_constant_word(word);
@@ -30,7 +31,7 @@
       descriptor;
   protobufs::Fact fact;
   *fact.mutable_constant_uniform_fact() = constant_uniform_fact;
-  return fact_manager->AddFact(fact, context);
+  return transformation_context->GetFactManager()->AddFact(fact, context);
 }
 
 TEST(TransformationReplaceConstantWithUniformTest, BasicReplacements) {
@@ -104,6 +105,10 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
   protobufs::UniformBufferElementDescriptor blockname_a =
       MakeUniformBufferElementDescriptor(0, 0, {0});
   protobufs::UniformBufferElementDescriptor blockname_b =
@@ -111,9 +116,12 @@
   protobufs::UniformBufferElementDescriptor blockname_c =
       MakeUniformBufferElementDescriptor(0, 0, {2});
 
-  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 1, blockname_a));
-  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 2, blockname_b));
-  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 3, blockname_c));
+  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));
 
   // The constant ids are 9, 11 and 14, for 1, 2 and 3 respectively.
   protobufs::IdUseDescriptor use_of_9_in_store =
@@ -127,30 +135,30 @@
   auto transformation_use_of_9_in_store =
       TransformationReplaceConstantWithUniform(use_of_9_in_store, blockname_a,
                                                100, 101);
-  ASSERT_TRUE(transformation_use_of_9_in_store.IsApplicable(context.get(),
-                                                            fact_manager));
+  ASSERT_TRUE(transformation_use_of_9_in_store.IsApplicable(
+      context.get(), transformation_context));
   auto transformation_use_of_11_in_add =
       TransformationReplaceConstantWithUniform(use_of_11_in_add, blockname_b,
                                                102, 103);
-  ASSERT_TRUE(transformation_use_of_11_in_add.IsApplicable(context.get(),
-                                                           fact_manager));
+  ASSERT_TRUE(transformation_use_of_11_in_add.IsApplicable(
+      context.get(), transformation_context));
   auto transformation_use_of_14_in_add =
       TransformationReplaceConstantWithUniform(use_of_14_in_add, blockname_c,
                                                104, 105);
-  ASSERT_TRUE(transformation_use_of_14_in_add.IsApplicable(context.get(),
-                                                           fact_manager));
+  ASSERT_TRUE(transformation_use_of_14_in_add.IsApplicable(
+      context.get(), transformation_context));
 
   // The transformations are not applicable if we change which uniforms are
   // applied to which constants.
   ASSERT_FALSE(TransformationReplaceConstantWithUniform(use_of_9_in_store,
                                                         blockname_b, 101, 102)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationReplaceConstantWithUniform(use_of_11_in_add,
                                                         blockname_c, 101, 102)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationReplaceConstantWithUniform(use_of_14_in_add,
                                                         blockname_a, 101, 102)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // The following transformations do not apply because the uniform descriptors
   // are not sensible.
@@ -160,10 +168,10 @@
       MakeUniformBufferElementDescriptor(0, 0, {5});
   ASSERT_FALSE(TransformationReplaceConstantWithUniform(
                    use_of_9_in_store, nonsense_uniform_descriptor1, 101, 102)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationReplaceConstantWithUniform(
                    use_of_9_in_store, nonsense_uniform_descriptor2, 101, 102)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // The following transformation does not apply because the id descriptor is
   // not sensible.
@@ -171,18 +179,19 @@
       MakeIdUseDescriptor(9, MakeInstructionDescriptor(15, SpvOpIAdd, 0), 0);
   ASSERT_FALSE(TransformationReplaceConstantWithUniform(
                    nonsense_id_use_descriptor, blockname_a, 101, 102)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // The following transformations do not apply because the ids are not fresh.
   ASSERT_FALSE(TransformationReplaceConstantWithUniform(use_of_11_in_add,
                                                         blockname_b, 15, 103)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationReplaceConstantWithUniform(use_of_11_in_add,
                                                         blockname_b, 102, 15)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // Apply the use of 9 in a store.
-  transformation_use_of_9_in_store.Apply(context.get(), &fact_manager);
+  transformation_use_of_9_in_store.Apply(context.get(),
+                                         &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
   std::string after_replacing_use_of_9_in_store = R"(
                OpCapability Shader
@@ -233,10 +242,10 @@
   )";
   ASSERT_TRUE(IsEqual(env, after_replacing_use_of_9_in_store, context.get()));
 
-  ASSERT_TRUE(transformation_use_of_11_in_add.IsApplicable(context.get(),
-                                                           fact_manager));
+  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(), &fact_manager);
+  transformation_use_of_11_in_add.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
   std::string after_replacing_use_of_11_in_add = R"(
                OpCapability Shader
@@ -289,10 +298,10 @@
   )";
   ASSERT_TRUE(IsEqual(env, after_replacing_use_of_11_in_add, context.get()));
 
-  ASSERT_TRUE(transformation_use_of_14_in_add.IsApplicable(context.get(),
-                                                           fact_manager));
+  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(), &fact_manager);
+  transformation_use_of_14_in_add.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
   std::string after_replacing_use_of_14_in_add = R"(
                OpCapability Shader
@@ -462,6 +471,10 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
   protobufs::UniformBufferElementDescriptor blockname_1 =
       MakeUniformBufferElementDescriptor(0, 0, {0});
   protobufs::UniformBufferElementDescriptor blockname_2 =
@@ -471,10 +484,14 @@
   protobufs::UniformBufferElementDescriptor blockname_4 =
       MakeUniformBufferElementDescriptor(0, 0, {1, 0, 1, 0});
 
-  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 1, blockname_1));
-  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 2, blockname_2));
-  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 3, blockname_3));
-  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 4, blockname_4));
+  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));
 
   // The constant ids are 13, 15, 17 and 20, for 1, 2, 3 and 4 respectively.
   protobufs::IdUseDescriptor use_of_13_in_store =
@@ -490,76 +507,78 @@
   auto transformation_use_of_13_in_store =
       TransformationReplaceConstantWithUniform(use_of_13_in_store, blockname_1,
                                                100, 101);
-  ASSERT_TRUE(transformation_use_of_13_in_store.IsApplicable(context.get(),
-                                                             fact_manager));
+  ASSERT_TRUE(transformation_use_of_13_in_store.IsApplicable(
+      context.get(), transformation_context));
   auto transformation_use_of_15_in_add =
       TransformationReplaceConstantWithUniform(use_of_15_in_add, blockname_2,
                                                102, 103);
-  ASSERT_TRUE(transformation_use_of_15_in_add.IsApplicable(context.get(),
-                                                           fact_manager));
+  ASSERT_TRUE(transformation_use_of_15_in_add.IsApplicable(
+      context.get(), transformation_context));
   auto transformation_use_of_17_in_add =
       TransformationReplaceConstantWithUniform(use_of_17_in_add, blockname_3,
                                                104, 105);
-  ASSERT_TRUE(transformation_use_of_17_in_add.IsApplicable(context.get(),
-                                                           fact_manager));
+  ASSERT_TRUE(transformation_use_of_17_in_add.IsApplicable(
+      context.get(), transformation_context));
   auto transformation_use_of_20_in_store =
       TransformationReplaceConstantWithUniform(use_of_20_in_store, blockname_4,
                                                106, 107);
-  ASSERT_TRUE(transformation_use_of_20_in_store.IsApplicable(context.get(),
-                                                             fact_manager));
+  ASSERT_TRUE(transformation_use_of_20_in_store.IsApplicable(
+      context.get(), transformation_context));
 
-  ASSERT_TRUE(transformation_use_of_13_in_store.IsApplicable(context.get(),
-                                                             fact_manager));
-  ASSERT_TRUE(transformation_use_of_15_in_add.IsApplicable(context.get(),
-                                                           fact_manager));
-  ASSERT_TRUE(transformation_use_of_17_in_add.IsApplicable(context.get(),
-                                                           fact_manager));
-  ASSERT_TRUE(transformation_use_of_20_in_store.IsApplicable(context.get(),
-                                                             fact_manager));
+  ASSERT_TRUE(transformation_use_of_13_in_store.IsApplicable(
+      context.get(), transformation_context));
+  ASSERT_TRUE(transformation_use_of_15_in_add.IsApplicable(
+      context.get(), transformation_context));
+  ASSERT_TRUE(transformation_use_of_17_in_add.IsApplicable(
+      context.get(), transformation_context));
+  ASSERT_TRUE(transformation_use_of_20_in_store.IsApplicable(
+      context.get(), transformation_context));
 
-  transformation_use_of_13_in_store.Apply(context.get(), &fact_manager);
+  transformation_use_of_13_in_store.Apply(context.get(),
+                                          &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
-  ASSERT_FALSE(transformation_use_of_13_in_store.IsApplicable(context.get(),
-                                                              fact_manager));
-  ASSERT_TRUE(transformation_use_of_15_in_add.IsApplicable(context.get(),
-                                                           fact_manager));
-  ASSERT_TRUE(transformation_use_of_17_in_add.IsApplicable(context.get(),
-                                                           fact_manager));
-  ASSERT_TRUE(transformation_use_of_20_in_store.IsApplicable(context.get(),
-                                                             fact_manager));
+  ASSERT_FALSE(transformation_use_of_13_in_store.IsApplicable(
+      context.get(), transformation_context));
+  ASSERT_TRUE(transformation_use_of_15_in_add.IsApplicable(
+      context.get(), transformation_context));
+  ASSERT_TRUE(transformation_use_of_17_in_add.IsApplicable(
+      context.get(), transformation_context));
+  ASSERT_TRUE(transformation_use_of_20_in_store.IsApplicable(
+      context.get(), transformation_context));
 
-  transformation_use_of_15_in_add.Apply(context.get(), &fact_manager);
+  transformation_use_of_15_in_add.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
-  ASSERT_FALSE(transformation_use_of_13_in_store.IsApplicable(context.get(),
-                                                              fact_manager));
-  ASSERT_FALSE(transformation_use_of_15_in_add.IsApplicable(context.get(),
-                                                            fact_manager));
-  ASSERT_TRUE(transformation_use_of_17_in_add.IsApplicable(context.get(),
-                                                           fact_manager));
-  ASSERT_TRUE(transformation_use_of_20_in_store.IsApplicable(context.get(),
-                                                             fact_manager));
+  ASSERT_FALSE(transformation_use_of_13_in_store.IsApplicable(
+      context.get(), transformation_context));
+  ASSERT_FALSE(transformation_use_of_15_in_add.IsApplicable(
+      context.get(), transformation_context));
+  ASSERT_TRUE(transformation_use_of_17_in_add.IsApplicable(
+      context.get(), transformation_context));
+  ASSERT_TRUE(transformation_use_of_20_in_store.IsApplicable(
+      context.get(), transformation_context));
 
-  transformation_use_of_17_in_add.Apply(context.get(), &fact_manager);
+  transformation_use_of_17_in_add.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
-  ASSERT_FALSE(transformation_use_of_13_in_store.IsApplicable(context.get(),
-                                                              fact_manager));
-  ASSERT_FALSE(transformation_use_of_15_in_add.IsApplicable(context.get(),
-                                                            fact_manager));
-  ASSERT_FALSE(transformation_use_of_17_in_add.IsApplicable(context.get(),
-                                                            fact_manager));
-  ASSERT_TRUE(transformation_use_of_20_in_store.IsApplicable(context.get(),
-                                                             fact_manager));
+  ASSERT_FALSE(transformation_use_of_13_in_store.IsApplicable(
+      context.get(), transformation_context));
+  ASSERT_FALSE(transformation_use_of_15_in_add.IsApplicable(
+      context.get(), transformation_context));
+  ASSERT_FALSE(transformation_use_of_17_in_add.IsApplicable(
+      context.get(), transformation_context));
+  ASSERT_TRUE(transformation_use_of_20_in_store.IsApplicable(
+      context.get(), transformation_context));
 
-  transformation_use_of_20_in_store.Apply(context.get(), &fact_manager);
+  transformation_use_of_20_in_store.Apply(context.get(),
+                                          &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
-  ASSERT_FALSE(transformation_use_of_13_in_store.IsApplicable(context.get(),
-                                                              fact_manager));
-  ASSERT_FALSE(transformation_use_of_15_in_add.IsApplicable(context.get(),
-                                                            fact_manager));
-  ASSERT_FALSE(transformation_use_of_17_in_add.IsApplicable(context.get(),
-                                                            fact_manager));
-  ASSERT_FALSE(transformation_use_of_20_in_store.IsApplicable(context.get(),
-                                                              fact_manager));
+  ASSERT_FALSE(transformation_use_of_13_in_store.IsApplicable(
+      context.get(), transformation_context));
+  ASSERT_FALSE(transformation_use_of_15_in_add.IsApplicable(
+      context.get(), transformation_context));
+  ASSERT_FALSE(transformation_use_of_17_in_add.IsApplicable(
+      context.get(), transformation_context));
+  ASSERT_FALSE(transformation_use_of_20_in_store.IsApplicable(
+      context.get(), transformation_context));
 
   std::string after = R"(
                OpCapability Shader
@@ -697,10 +716,15 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
   protobufs::UniformBufferElementDescriptor blockname_0 =
       MakeUniformBufferElementDescriptor(0, 0, {0});
 
-  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 0, blockname_0));
+  ASSERT_TRUE(
+      AddFactHelper(&transformation_context, context.get(), 0, blockname_0));
 
   // The constant id is 9 for 0.
   protobufs::IdUseDescriptor use_of_9_in_store =
@@ -710,7 +734,7 @@
   // type is present:
   ASSERT_FALSE(TransformationReplaceConstantWithUniform(use_of_9_in_store,
                                                         blockname_0, 100, 101)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationReplaceConstantWithUniformTest, NoConstantPresentForIndex) {
@@ -770,12 +794,17 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
   protobufs::UniformBufferElementDescriptor blockname_0 =
       MakeUniformBufferElementDescriptor(0, 0, {0});
   protobufs::UniformBufferElementDescriptor blockname_9 =
       MakeUniformBufferElementDescriptor(0, 0, {1});
 
-  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 9, blockname_9));
+  ASSERT_TRUE(
+      AddFactHelper(&transformation_context, context.get(), 9, blockname_9));
 
   // The constant id is 9 for 9.
   protobufs::IdUseDescriptor use_of_9_in_store =
@@ -785,7 +814,7 @@
   // index 1 required to index into the uniform buffer:
   ASSERT_FALSE(TransformationReplaceConstantWithUniform(use_of_9_in_store,
                                                         blockname_9, 100, 101)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationReplaceConstantWithUniformTest,
@@ -842,14 +871,18 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               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(&fact_manager, context.get(), float_data[0], blockname_3));
+  ASSERT_TRUE(AddFactHelper(&transformation_context, context.get(),
+                            float_data[0], blockname_3));
 
   // The constant id is 9 for 3.0.
   protobufs::IdUseDescriptor use_of_9_in_store =
@@ -859,7 +892,7 @@
   // allow a constant index to be expressed:
   ASSERT_FALSE(TransformationReplaceConstantWithUniform(use_of_9_in_store,
                                                         blockname_3, 100, 101)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationReplaceConstantWithUniformTest,
@@ -928,13 +961,19 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
   protobufs::UniformBufferElementDescriptor blockname_9 =
       MakeUniformBufferElementDescriptor(0, 0, {0});
   protobufs::UniformBufferElementDescriptor blockname_10 =
       MakeUniformBufferElementDescriptor(0, 0, {1});
 
-  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 9, blockname_9));
-  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 10, blockname_10));
+  ASSERT_TRUE(
+      AddFactHelper(&transformation_context, context.get(), 9, blockname_9));
+  ASSERT_TRUE(
+      AddFactHelper(&transformation_context, context.get(), 10, blockname_10));
 
   // The constant ids for 9 and 10 are 9 and 11 respectively
   protobufs::IdUseDescriptor use_of_9_in_store =
@@ -945,19 +984,19 @@
   // These are right:
   ASSERT_TRUE(TransformationReplaceConstantWithUniform(use_of_9_in_store,
                                                        blockname_9, 100, 101)
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationReplaceConstantWithUniform(use_of_11_in_store,
                                                        blockname_10, 102, 103)
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
 
   // These are wrong because the constants do not match the facts about
   // uniforms.
   ASSERT_FALSE(TransformationReplaceConstantWithUniform(use_of_11_in_store,
                                                         blockname_9, 100, 101)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationReplaceConstantWithUniform(use_of_9_in_store,
                                                         blockname_10, 102, 103)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationReplaceConstantWithUniformTest, ComplexReplacements) {
@@ -1141,6 +1180,9 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   const float float_array_values[5] = {1.0, 1.5, 1.75, 1.875, 1.9375};
   uint32_t float_array_data[5];
@@ -1188,35 +1230,43 @@
   protobufs::UniformBufferElementDescriptor uniform_h_y =
       MakeUniformBufferElementDescriptor(0, 0, {2, 1});
 
-  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), float_array_data[0],
-                            uniform_f_a_0));
-  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), float_array_data[1],
-                            uniform_f_a_1));
-  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), float_array_data[2],
-                            uniform_f_a_2));
-  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), float_array_data[3],
-                            uniform_f_a_3));
-  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), float_array_data[4],
-                            uniform_f_a_4));
+  ASSERT_TRUE(AddFactHelper(&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(&fact_manager, context.get(), 1, uniform_f_b_x));
-  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 2, uniform_f_b_y));
-  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 3, uniform_f_b_z));
-  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 4, uniform_f_b_w));
+  ASSERT_TRUE(
+      AddFactHelper(&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(&fact_manager, context.get(), float_vector_data[0],
-                            uniform_f_c_x));
-  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), float_vector_data[1],
-                            uniform_f_c_y));
-  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), float_vector_data[2],
-                            uniform_f_c_z));
+  ASSERT_TRUE(AddFactHelper(&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(&fact_manager, context.get(), 42, uniform_f_d));
+  ASSERT_TRUE(
+      AddFactHelper(&transformation_context, context.get(), 42, uniform_f_d));
 
-  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 22, uniform_g));
+  ASSERT_TRUE(
+      AddFactHelper(&transformation_context, context.get(), 22, uniform_g));
 
-  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 100, uniform_h_x));
-  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 200, uniform_h_y));
+  ASSERT_TRUE(
+      AddFactHelper(&transformation_context, context.get(), 100, uniform_h_x));
+  ASSERT_TRUE(
+      AddFactHelper(&transformation_context, context.get(), 200, uniform_h_y));
 
   std::vector<TransformationReplaceConstantWithUniform> transformations;
 
@@ -1275,8 +1325,9 @@
       uniform_g, 218, 219));
 
   for (auto& transformation : transformations) {
-    ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
-    transformation.Apply(context.get(), &fact_manager);
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    transformation.Apply(context.get(), &transformation_context);
     ASSERT_TRUE(IsValid(env, context.get()));
   }
 
@@ -1442,6 +1493,61 @@
   ASSERT_TRUE(IsEqual(env, after, context.get()));
 }
 
+TEST(TransformationReplaceConstantWithUniformTest,
+     DoNotReplaceVariableInitializer) {
+  // If a local variable has a constant initializer, this cannot be replaced
+  // by a uniform.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource GLSL 450
+               OpMemberDecorate %16 0 Offset 0
+               OpDecorate %16 Block
+               OpDecorate %18 DescriptorSet 0
+               OpDecorate %18 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+         %50 = OpConstant %6 0
+         %16 = OpTypeStruct %6
+         %17 = OpTypePointer Uniform %16
+         %51 = OpTypePointer Uniform %6
+         %18 = OpVariable %17 Uniform
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function %50
+               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);
+
+  protobufs::UniformBufferElementDescriptor blockname_a =
+      MakeUniformBufferElementDescriptor(0, 0, {0});
+
+  ASSERT_TRUE(
+      AddFactHelper(&transformation_context, context.get(), 0, blockname_a));
+
+  ASSERT_FALSE(TransformationReplaceConstantWithUniform(
+                   MakeIdUseDescriptor(
+                       50, MakeInstructionDescriptor(8, SpvOpVariable, 0), 1),
+                   blockname_a, 100, 101)
+                   .IsApplicable(context.get(), transformation_context));
+}
+
 }  // 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 41b6116..d563afa 100644
--- a/test/fuzz/transformation_replace_id_with_synonym_test.cpp
+++ b/test/fuzz/transformation_replace_id_with_synonym_test.cpp
@@ -220,15 +220,19 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
-  SetUpIdSynonyms(&fact_manager, context.get());
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
+  SetUpIdSynonyms(transformation_context.GetFactManager(), context.get());
 
   // %202 cannot replace %15 as in-operand 0 of %300, since %202 does not
   // dominate %300.
   auto synonym_does_not_dominate_use = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(15, MakeInstructionDescriptor(300, SpvOpIAdd, 0), 0),
       202);
-  ASSERT_FALSE(
-      synonym_does_not_dominate_use.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(synonym_does_not_dominate_use.IsApplicable(
+      context.get(), transformation_context));
 
   // %202 cannot replace %15 as in-operand 2 of %301, since this is the OpPhi's
   // incoming value for block %72, and %202 does not dominate %72.
@@ -237,22 +241,23 @@
           MakeIdUseDescriptor(15, MakeInstructionDescriptor(301, SpvOpPhi, 0),
                               2),
           202);
-  ASSERT_FALSE(synonym_does_not_dominate_use_op_phi.IsApplicable(context.get(),
-                                                                 fact_manager));
+  ASSERT_FALSE(synonym_does_not_dominate_use_op_phi.IsApplicable(
+      context.get(), transformation_context));
 
   // %200 is not a synonym for %84
   auto id_in_use_is_not_synonymous = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(
           84, MakeInstructionDescriptor(67, SpvOpSGreaterThan, 0), 0),
       200);
-  ASSERT_FALSE(
-      id_in_use_is_not_synonymous.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(id_in_use_is_not_synonymous.IsApplicable(
+      context.get(), transformation_context));
 
   // %86 is not a synonym for anything (and in particular not for %74)
   auto id_has_no_synonyms = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(86, MakeInstructionDescriptor(84, SpvOpPhi, 0), 2),
       74);
-  ASSERT_FALSE(id_has_no_synonyms.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      id_has_no_synonyms.IsApplicable(context.get(), transformation_context));
 
   // This would lead to %207 = 'OpCopyObject %type %207' if it were allowed
   auto synonym_use_is_in_synonym_definition =
@@ -260,8 +265,8 @@
           MakeIdUseDescriptor(
               84, MakeInstructionDescriptor(207, SpvOpCopyObject, 0), 0),
           207);
-  ASSERT_FALSE(synonym_use_is_in_synonym_definition.IsApplicable(context.get(),
-                                                                 fact_manager));
+  ASSERT_FALSE(synonym_use_is_in_synonym_definition.IsApplicable(
+      context.get(), transformation_context));
 
   // The id use descriptor does not lead to a use (%84 is not used in the
   // definition of %207)
@@ -269,7 +274,8 @@
       MakeIdUseDescriptor(
           84, MakeInstructionDescriptor(200, SpvOpCopyObject, 0), 0),
       207);
-  ASSERT_FALSE(bad_id_use_descriptor.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(bad_id_use_descriptor.IsApplicable(context.get(),
+                                                  transformation_context));
 
   // This replacement would lead to an access chain into a struct using a
   // non-constant index.
@@ -277,7 +283,8 @@
       MakeIdUseDescriptor(
           12, MakeInstructionDescriptor(14, SpvOpAccessChain, 0), 1),
       209);
-  ASSERT_FALSE(bad_access_chain.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      bad_access_chain.IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationReplaceIdWithSynonymTest, LegalTransformations) {
@@ -288,23 +295,28 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
-  SetUpIdSynonyms(&fact_manager, context.get());
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
+  SetUpIdSynonyms(transformation_context.GetFactManager(), context.get());
 
   auto global_constant_synonym = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(19, MakeInstructionDescriptor(47, SpvOpStore, 0), 1),
       210);
-  ASSERT_TRUE(
-      global_constant_synonym.IsApplicable(context.get(), fact_manager));
-  global_constant_synonym.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(global_constant_synonym.IsApplicable(context.get(),
+                                                   transformation_context));
+  global_constant_synonym.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   auto replace_vector_access_chain_index = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(
           54, MakeInstructionDescriptor(55, SpvOpAccessChain, 0), 1),
       204);
-  ASSERT_TRUE(replace_vector_access_chain_index.IsApplicable(context.get(),
-                                                             fact_manager));
-  replace_vector_access_chain_index.Apply(context.get(), &fact_manager);
+  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()));
 
   // This is an interesting case because it replaces something that is being
@@ -313,22 +325,24 @@
       MakeIdUseDescriptor(
           15, MakeInstructionDescriptor(202, SpvOpCopyObject, 0), 0),
       201);
-  ASSERT_TRUE(regular_replacement.IsApplicable(context.get(), fact_manager));
-  regular_replacement.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      regular_replacement.IsApplicable(context.get(), transformation_context));
+  regular_replacement.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   auto regular_replacement2 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(55, MakeInstructionDescriptor(203, SpvOpStore, 0), 0),
       203);
-  ASSERT_TRUE(regular_replacement2.IsApplicable(context.get(), fact_manager));
-  regular_replacement2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      regular_replacement2.IsApplicable(context.get(), transformation_context));
+  regular_replacement2.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   auto good_op_phi = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(74, MakeInstructionDescriptor(86, SpvOpPhi, 0), 2),
       205);
-  ASSERT_TRUE(good_op_phi.IsApplicable(context.get(), fact_manager));
-  good_op_phi.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(good_op_phi.IsApplicable(context.get(), transformation_context));
+  good_op_phi.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   const std::string after_transformation = R"(
@@ -504,17 +518,22 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
-  fact_manager.AddFact(MakeSynonymFact(10, 100), context.get());
-  fact_manager.AddFact(MakeSynonymFact(8, 101), context.get());
+  transformation_context.GetFactManager()->AddFact(MakeSynonymFact(10, 100),
+                                                   context.get());
+  transformation_context.GetFactManager()->AddFact(MakeSynonymFact(8, 101),
+                                                   context.get());
 
   // Replace %10 with %100 in:
   // %11 = OpLoad %6 %10
   auto replacement1 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(10, MakeInstructionDescriptor(11, SpvOpLoad, 0), 0),
       100);
-  ASSERT_TRUE(replacement1.IsApplicable(context.get(), fact_manager));
-  replacement1.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(replacement1.IsApplicable(context.get(), transformation_context));
+  replacement1.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // Replace %8 with %101 in:
@@ -522,8 +541,8 @@
   auto replacement2 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(8, MakeInstructionDescriptor(11, SpvOpStore, 0), 0),
       101);
-  ASSERT_TRUE(replacement2.IsApplicable(context.get(), fact_manager));
-  replacement2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(replacement2.IsApplicable(context.get(), transformation_context));
+  replacement2.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // Replace %8 with %101 in:
@@ -531,8 +550,8 @@
   auto replacement3 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(8, MakeInstructionDescriptor(12, SpvOpLoad, 0), 0),
       101);
-  ASSERT_TRUE(replacement3.IsApplicable(context.get(), fact_manager));
-  replacement3.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(replacement3.IsApplicable(context.get(), transformation_context));
+  replacement3.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // Replace %10 with %100 in:
@@ -540,8 +559,8 @@
   auto replacement4 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(10, MakeInstructionDescriptor(12, SpvOpStore, 0), 0),
       100);
-  ASSERT_TRUE(replacement4.IsApplicable(context.get(), fact_manager));
-  replacement4.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(replacement4.IsApplicable(context.get(), transformation_context));
+  replacement4.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   const std::string after_transformation = R"(
@@ -633,8 +652,12 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
-  fact_manager.AddFact(MakeSynonymFact(14, 100), context.get());
+  transformation_context.GetFactManager()->AddFact(MakeSynonymFact(14, 100),
+                                                   context.get());
 
   // Replace %14 with %100 in:
   // %16 = OpFunctionCall %2 %10 %14
@@ -642,7 +665,7 @@
       MakeIdUseDescriptor(
           14, MakeInstructionDescriptor(16, SpvOpFunctionCall, 0), 1),
       100);
-  ASSERT_FALSE(replacement.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(replacement.IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationReplaceIdWithSynonymTest, SynonymsOfAccessChainIndices) {
@@ -795,22 +818,38 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   // Add synonym facts corresponding to the OpCopyObject operations that have
   // been applied to all constants in the module.
-  fact_manager.AddFact(MakeSynonymFact(16, 100), context.get());
-  fact_manager.AddFact(MakeSynonymFact(21, 101), context.get());
-  fact_manager.AddFact(MakeSynonymFact(17, 102), context.get());
-  fact_manager.AddFact(MakeSynonymFact(57, 103), context.get());
-  fact_manager.AddFact(MakeSynonymFact(18, 104), context.get());
-  fact_manager.AddFact(MakeSynonymFact(40, 105), context.get());
-  fact_manager.AddFact(MakeSynonymFact(32, 106), context.get());
-  fact_manager.AddFact(MakeSynonymFact(43, 107), context.get());
-  fact_manager.AddFact(MakeSynonymFact(55, 108), context.get());
-  fact_manager.AddFact(MakeSynonymFact(8, 109), context.get());
-  fact_manager.AddFact(MakeSynonymFact(47, 110), context.get());
-  fact_manager.AddFact(MakeSynonymFact(28, 111), context.get());
-  fact_manager.AddFact(MakeSynonymFact(45, 112), context.get());
+  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());
 
   // Replacements of the form %16 -> %100
 
@@ -821,7 +860,8 @@
       MakeIdUseDescriptor(
           16, MakeInstructionDescriptor(20, SpvOpAccessChain, 0), 1),
       100);
-  ASSERT_FALSE(replacement1.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      replacement1.IsApplicable(context.get(), transformation_context));
 
   // %39 = OpAccessChain %23 %37 *%16*
   // Corresponds to h.*f*
@@ -830,7 +870,8 @@
       MakeIdUseDescriptor(
           16, MakeInstructionDescriptor(39, SpvOpAccessChain, 0), 1),
       100);
-  ASSERT_FALSE(replacement2.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      replacement2.IsApplicable(context.get(), transformation_context));
 
   // %41 = OpAccessChain %19 %37 %21 *%16* %21
   // Corresponds to h.g.*a*[1]
@@ -839,7 +880,8 @@
       MakeIdUseDescriptor(
           16, MakeInstructionDescriptor(41, SpvOpAccessChain, 0), 2),
       100);
-  ASSERT_FALSE(replacement3.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      replacement3.IsApplicable(context.get(), transformation_context));
 
   // %52 = OpAccessChain %23 %50 *%16* %16
   // Corresponds to i[*0*].f
@@ -848,8 +890,8 @@
       MakeIdUseDescriptor(
           16, MakeInstructionDescriptor(52, SpvOpAccessChain, 0), 1),
       100);
-  ASSERT_TRUE(replacement4.IsApplicable(context.get(), fact_manager));
-  replacement4.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(replacement4.IsApplicable(context.get(), transformation_context));
+  replacement4.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // %52 = OpAccessChain %23 %50 %16 *%16*
@@ -859,7 +901,8 @@
       MakeIdUseDescriptor(
           16, MakeInstructionDescriptor(52, SpvOpAccessChain, 0), 2),
       100);
-  ASSERT_FALSE(replacement5.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      replacement5.IsApplicable(context.get(), transformation_context));
 
   // %53 = OpAccessChain %19 %50 %21 %21 *%16* %16
   // Corresponds to i[1].g.*a*[0]
@@ -868,7 +911,8 @@
       MakeIdUseDescriptor(
           16, MakeInstructionDescriptor(53, SpvOpAccessChain, 0), 3),
       100);
-  ASSERT_FALSE(replacement6.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      replacement6.IsApplicable(context.get(), transformation_context));
 
   // %53 = OpAccessChain %19 %50 %21 %21 %16 *%16*
   // Corresponds to i[1].g.a[*0*]
@@ -877,8 +921,8 @@
       MakeIdUseDescriptor(
           16, MakeInstructionDescriptor(53, SpvOpAccessChain, 0), 4),
       100);
-  ASSERT_TRUE(replacement7.IsApplicable(context.get(), fact_manager));
-  replacement7.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(replacement7.IsApplicable(context.get(), transformation_context));
+  replacement7.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // Replacements of the form %21 -> %101
@@ -890,7 +934,8 @@
       MakeIdUseDescriptor(
           21, MakeInstructionDescriptor(24, SpvOpAccessChain, 0), 1),
       101);
-  ASSERT_FALSE(replacement8.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      replacement8.IsApplicable(context.get(), transformation_context));
 
   // %41 = OpAccessChain %19 %37 *%21* %16 %21
   // Corresponds to h.*g*.a[1]
@@ -899,7 +944,8 @@
       MakeIdUseDescriptor(
           21, MakeInstructionDescriptor(41, SpvOpAccessChain, 0), 1),
       101);
-  ASSERT_FALSE(replacement9.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      replacement9.IsApplicable(context.get(), transformation_context));
 
   // %41 = OpAccessChain %19 %37 %21 %16 *%21*
   // Corresponds to h.g.a[*1*]
@@ -908,8 +954,9 @@
       MakeIdUseDescriptor(
           21, MakeInstructionDescriptor(41, SpvOpAccessChain, 0), 3),
       101);
-  ASSERT_TRUE(replacement10.IsApplicable(context.get(), fact_manager));
-  replacement10.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      replacement10.IsApplicable(context.get(), transformation_context));
+  replacement10.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // %44 = OpAccessChain %23 %37 *%21* %21 %43
@@ -919,7 +966,8 @@
       MakeIdUseDescriptor(
           21, MakeInstructionDescriptor(44, SpvOpAccessChain, 0), 1),
       101);
-  ASSERT_FALSE(replacement11.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      replacement11.IsApplicable(context.get(), transformation_context));
 
   // %44 = OpAccessChain %23 %37 %21 *%21* %43
   // Corresponds to h.g.*b*[0]
@@ -928,7 +976,8 @@
       MakeIdUseDescriptor(
           21, MakeInstructionDescriptor(44, SpvOpAccessChain, 0), 2),
       101);
-  ASSERT_FALSE(replacement12.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      replacement12.IsApplicable(context.get(), transformation_context));
 
   // %46 = OpAccessChain %26 %37 *%21* %17
   // Corresponds to h.*g*.c
@@ -937,7 +986,8 @@
       MakeIdUseDescriptor(
           21, MakeInstructionDescriptor(46, SpvOpAccessChain, 0), 1),
       101);
-  ASSERT_FALSE(replacement13.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      replacement13.IsApplicable(context.get(), transformation_context));
 
   // %53 = OpAccessChain %19 %50 *%21* %21 %16 %16
   // Corresponds to i[*1*].g.a[0]
@@ -946,8 +996,9 @@
       MakeIdUseDescriptor(
           21, MakeInstructionDescriptor(53, SpvOpAccessChain, 0), 1),
       101);
-  ASSERT_TRUE(replacement14.IsApplicable(context.get(), fact_manager));
-  replacement14.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      replacement14.IsApplicable(context.get(), transformation_context));
+  replacement14.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // %53 = OpAccessChain %19 %50 %21 *%21* %16 %16
@@ -957,7 +1008,8 @@
       MakeIdUseDescriptor(
           21, MakeInstructionDescriptor(53, SpvOpAccessChain, 0), 2),
       101);
-  ASSERT_FALSE(replacement15.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      replacement15.IsApplicable(context.get(), transformation_context));
 
   // %56 = OpAccessChain %23 %50 %17 *%21* %21 %55
   // Corresponds to i[2].*g*.b[1]
@@ -966,7 +1018,8 @@
       MakeIdUseDescriptor(
           21, MakeInstructionDescriptor(56, SpvOpAccessChain, 0), 2),
       101);
-  ASSERT_FALSE(replacement16.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      replacement16.IsApplicable(context.get(), transformation_context));
 
   // %56 = OpAccessChain %23 %50 %17 %21 *%21* %55
   // Corresponds to i[2].g.*b*[1]
@@ -975,7 +1028,8 @@
       MakeIdUseDescriptor(
           21, MakeInstructionDescriptor(56, SpvOpAccessChain, 0), 3),
       101);
-  ASSERT_FALSE(replacement17.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      replacement17.IsApplicable(context.get(), transformation_context));
 
   // %58 = OpAccessChain %26 %50 %57 *%21* %17
   // Corresponds to i[3].*g*.c
@@ -984,7 +1038,8 @@
       MakeIdUseDescriptor(
           21, MakeInstructionDescriptor(58, SpvOpAccessChain, 0), 2),
       101);
-  ASSERT_FALSE(replacement18.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      replacement18.IsApplicable(context.get(), transformation_context));
 
   // Replacements of the form %17 -> %102
 
@@ -995,8 +1050,9 @@
       MakeIdUseDescriptor(
           17, MakeInstructionDescriptor(20, SpvOpAccessChain, 0), 2),
       102);
-  ASSERT_TRUE(replacement19.IsApplicable(context.get(), fact_manager));
-  replacement19.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      replacement19.IsApplicable(context.get(), transformation_context));
+  replacement19.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // %27 = OpAccessChain %26 %15 %17
@@ -1006,7 +1062,8 @@
       MakeIdUseDescriptor(
           17, MakeInstructionDescriptor(27, SpvOpAccessChain, 0), 1),
       102);
-  ASSERT_FALSE(replacement20.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      replacement20.IsApplicable(context.get(), transformation_context));
 
   // %46 = OpAccessChain %26 %37 %21 %17
   // Corresponds to h.g.*c*
@@ -1015,7 +1072,8 @@
       MakeIdUseDescriptor(
           17, MakeInstructionDescriptor(46, SpvOpAccessChain, 0), 2),
       102);
-  ASSERT_FALSE(replacement21.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      replacement21.IsApplicable(context.get(), transformation_context));
 
   // %56 = OpAccessChain %23 %50 %17 %21 %21 %55
   // Corresponds to i[*2*].g.b[1]
@@ -1024,8 +1082,9 @@
       MakeIdUseDescriptor(
           17, MakeInstructionDescriptor(56, SpvOpAccessChain, 0), 1),
       102);
-  ASSERT_TRUE(replacement22.IsApplicable(context.get(), fact_manager));
-  replacement22.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      replacement22.IsApplicable(context.get(), transformation_context));
+  replacement22.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // %58 = OpAccessChain %26 %50 %57 %21 %17
@@ -1035,7 +1094,8 @@
       MakeIdUseDescriptor(
           17, MakeInstructionDescriptor(58, SpvOpAccessChain, 0), 3),
       102);
-  ASSERT_FALSE(replacement23.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      replacement23.IsApplicable(context.get(), transformation_context));
 
   // Replacements of the form %57 -> %103
 
@@ -1046,8 +1106,9 @@
       MakeIdUseDescriptor(
           57, MakeInstructionDescriptor(58, SpvOpAccessChain, 0), 1),
       103);
-  ASSERT_TRUE(replacement24.IsApplicable(context.get(), fact_manager));
-  replacement24.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      replacement24.IsApplicable(context.get(), transformation_context));
+  replacement24.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // Replacements of the form %32 -> %106
@@ -1059,8 +1120,9 @@
       MakeIdUseDescriptor(
           32, MakeInstructionDescriptor(34, SpvOpAccessChain, 0), 1),
       106);
-  ASSERT_TRUE(replacement25.IsApplicable(context.get(), fact_manager));
-  replacement25.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      replacement25.IsApplicable(context.get(), transformation_context));
+  replacement25.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // Replacements of the form %43 -> %107
@@ -1072,8 +1134,9 @@
       MakeIdUseDescriptor(
           43, MakeInstructionDescriptor(44, SpvOpAccessChain, 0), 3),
       107);
-  ASSERT_TRUE(replacement26.IsApplicable(context.get(), fact_manager));
-  replacement26.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      replacement26.IsApplicable(context.get(), transformation_context));
+  replacement26.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // Replacements of the form %55 -> %108
@@ -1085,8 +1148,9 @@
       MakeIdUseDescriptor(
           55, MakeInstructionDescriptor(56, SpvOpAccessChain, 0), 4),
       108);
-  ASSERT_TRUE(replacement27.IsApplicable(context.get(), fact_manager));
-  replacement27.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      replacement27.IsApplicable(context.get(), transformation_context));
+  replacement27.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   // Replacements of the form %8 -> %109
@@ -1098,8 +1162,9 @@
       MakeIdUseDescriptor(8, MakeInstructionDescriptor(24, SpvOpAccessChain, 0),
                           2),
       109);
-  ASSERT_TRUE(replacement28.IsApplicable(context.get(), fact_manager));
-  replacement28.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      replacement28.IsApplicable(context.get(), transformation_context));
+  replacement28.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   const std::string after_transformation = R"(
diff --git a/test/fuzz/transformation_set_function_control_test.cpp b/test/fuzz/transformation_set_function_control_test.cpp
index 536e965..be7f2be 100644
--- a/test/fuzz/transformation_set_function_control_test.cpp
+++ b/test/fuzz/transformation_set_function_control_test.cpp
@@ -118,41 +118,48 @@
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   // %36 is not a function
   ASSERT_FALSE(TransformationSetFunctionControl(36, SpvFunctionControlMaskNone)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   // Cannot add the Pure function control to %4 as it did not already have it
   ASSERT_FALSE(TransformationSetFunctionControl(4, SpvFunctionControlPureMask)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   // Cannot add the Const function control to %21 as it did not already
   // have it
   ASSERT_FALSE(TransformationSetFunctionControl(21, SpvFunctionControlConstMask)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // Set to None, removing Const
   TransformationSetFunctionControl transformation1(11,
                                                    SpvFunctionControlMaskNone);
-  ASSERT_TRUE(transformation1.IsApplicable(context.get(), fact_manager));
-  transformation1.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation1.IsApplicable(context.get(), transformation_context));
+  transformation1.Apply(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(), fact_manager));
-  transformation2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation2.IsApplicable(context.get(), transformation_context));
+  transformation2.Apply(context.get(), &transformation_context);
 
   // Set to Pure, removing DontInline
   TransformationSetFunctionControl transformation3(17,
                                                    SpvFunctionControlPureMask);
-  ASSERT_TRUE(transformation3.IsApplicable(context.get(), fact_manager));
-  transformation3.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation3.IsApplicable(context.get(), transformation_context));
+  transformation3.Apply(context.get(), &transformation_context);
 
   // Change from Inline to DontInline
   TransformationSetFunctionControl transformation4(
       13, SpvFunctionControlDontInlineMask);
-  ASSERT_TRUE(transformation4.IsApplicable(context.get(), fact_manager));
-  transformation4.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation4.IsApplicable(context.get(), transformation_context));
+  transformation4.Apply(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 83953ec..531aa7a 100644
--- a/test/fuzz/transformation_set_loop_control_test.cpp
+++ b/test/fuzz/transformation_set_loop_control_test.cpp
@@ -256,6 +256,9 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   // These are the loop headers together with the selection controls of their
   // merge instructions:
@@ -275,310 +278,310 @@
   // 2 5 90 4 7 14
 
   ASSERT_TRUE(TransformationSetLoopControl(10, SpvLoopControlMaskNone, 0, 0)
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationSetLoopControl(10, SpvLoopControlUnrollMask, 0, 0)
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(
       TransformationSetLoopControl(10, SpvLoopControlDontUnrollMask, 0, 0)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationSetLoopControl(
                    10, SpvLoopControlDependencyInfiniteMask, 0, 0)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       TransformationSetLoopControl(10, SpvLoopControlDependencyLengthMask, 0, 0)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       TransformationSetLoopControl(10, SpvLoopControlMinIterationsMask, 0, 0)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       TransformationSetLoopControl(10, SpvLoopControlMaxIterationsMask, 0, 0)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationSetLoopControl(
                    10, SpvLoopControlIterationMultipleMask, 0, 0)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(
       TransformationSetLoopControl(10, SpvLoopControlPeelCountMask, 3, 0)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       TransformationSetLoopControl(10, SpvLoopControlPeelCountMask, 3, 3)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(
       TransformationSetLoopControl(10, SpvLoopControlPartialCountMask, 0, 3)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       TransformationSetLoopControl(10, SpvLoopControlPartialCountMask, 3, 3)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationSetLoopControl(
                   10,
                   SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask,
                   3, 3)
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationSetLoopControl(10,
                                            SpvLoopControlUnrollMask |
                                                SpvLoopControlPeelCountMask |
                                                SpvLoopControlPartialCountMask,
                                            3, 3)
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationSetLoopControl(10,
                                             SpvLoopControlDontUnrollMask |
                                                 SpvLoopControlPeelCountMask |
                                                 SpvLoopControlPartialCountMask,
                                             3, 3)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   ASSERT_TRUE(TransformationSetLoopControl(23, SpvLoopControlMaskNone, 0, 0)
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationSetLoopControl(23, SpvLoopControlUnrollMask, 0, 0)
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(
       TransformationSetLoopControl(23, SpvLoopControlDontUnrollMask, 0, 0)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationSetLoopControl(
                   23,
                   SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask,
                   3, 3)
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       TransformationSetLoopControl(23, SpvLoopControlMaxIterationsMask, 2, 3)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
 
   ASSERT_TRUE(TransformationSetLoopControl(33, SpvLoopControlMaskNone, 0, 0)
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationSetLoopControl(33, SpvLoopControlUnrollMask, 0, 0)
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(
       TransformationSetLoopControl(33, SpvLoopControlDontUnrollMask, 0, 0)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       TransformationSetLoopControl(33, SpvLoopControlMinIterationsMask, 0, 0)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(
       TransformationSetLoopControl(
           33, SpvLoopControlUnrollMask | SpvLoopControlPeelCountMask, 5, 0)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationSetLoopControl(33,
                                             SpvLoopControlDontUnrollMask |
                                                 SpvLoopControlPartialCountMask,
                                             0, 10)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   ASSERT_TRUE(TransformationSetLoopControl(43, SpvLoopControlMaskNone, 0, 0)
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationSetLoopControl(43, SpvLoopControlUnrollMask, 0, 0)
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(
       TransformationSetLoopControl(43, SpvLoopControlDontUnrollMask, 0, 0)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationSetLoopControl(
                   43,
                   SpvLoopControlMaskNone | SpvLoopControlDependencyInfiniteMask,
                   0, 0)
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(
       TransformationSetLoopControl(
           43, SpvLoopControlUnrollMask | SpvLoopControlDependencyInfiniteMask,
           0, 0)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(
       TransformationSetLoopControl(
           43,
           SpvLoopControlDontUnrollMask | SpvLoopControlDependencyInfiniteMask,
           0, 0)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       TransformationSetLoopControl(43,
                                    SpvLoopControlDependencyInfiniteMask |
                                        SpvLoopControlDependencyLengthMask,
                                    0, 0)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(
       TransformationSetLoopControl(
           43, SpvLoopControlUnrollMask | SpvLoopControlPeelCountMask, 5, 0)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
 
   ASSERT_TRUE(TransformationSetLoopControl(53, SpvLoopControlMaskNone, 0, 0)
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationSetLoopControl(53, SpvLoopControlUnrollMask, 0, 0)
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(
       TransformationSetLoopControl(53, SpvLoopControlDontUnrollMask, 0, 0)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       TransformationSetLoopControl(53, SpvLoopControlMaxIterationsMask, 0, 0)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(
       TransformationSetLoopControl(
           53, SpvLoopControlMaskNone | SpvLoopControlDependencyLengthMask, 0, 0)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       TransformationSetLoopControl(
           53, SpvLoopControlUnrollMask | SpvLoopControlDependencyInfiniteMask,
           0, 0)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(
       TransformationSetLoopControl(
           53, SpvLoopControlDontUnrollMask | SpvLoopControlDependencyLengthMask,
           0, 0)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       TransformationSetLoopControl(53,
                                    SpvLoopControlDependencyInfiniteMask |
                                        SpvLoopControlDependencyLengthMask,
                                    0, 0)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(
       TransformationSetLoopControl(
           53,
           SpvLoopControlUnrollMask | SpvLoopControlDependencyLengthMask |
               SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask,
           5, 3)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
 
   ASSERT_TRUE(TransformationSetLoopControl(63, SpvLoopControlMaskNone, 0, 0)
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationSetLoopControl(63, SpvLoopControlUnrollMask, 0, 0)
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(
       TransformationSetLoopControl(63, SpvLoopControlDontUnrollMask, 0, 0)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationSetLoopControl(63,
                                            SpvLoopControlUnrollMask |
                                                SpvLoopControlMinIterationsMask |
                                                SpvLoopControlPeelCountMask |
                                                SpvLoopControlPartialCountMask,
                                            5, 3)
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationSetLoopControl(63,
                                            SpvLoopControlUnrollMask |
                                                SpvLoopControlMinIterationsMask |
                                                SpvLoopControlPeelCountMask,
                                            23, 0)
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationSetLoopControl(
                    63,
                    SpvLoopControlUnrollMask | SpvLoopControlMinIterationsMask |
                        SpvLoopControlPeelCountMask,
                    2, 23)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   ASSERT_TRUE(TransformationSetLoopControl(73, SpvLoopControlMaskNone, 0, 0)
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationSetLoopControl(73, SpvLoopControlUnrollMask, 0, 0)
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(
       TransformationSetLoopControl(73, SpvLoopControlDontUnrollMask, 0, 0)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationSetLoopControl(
                    73,
                    SpvLoopControlUnrollMask | SpvLoopControlMinIterationsMask |
                        SpvLoopControlPeelCountMask |
                        SpvLoopControlPartialCountMask,
                    5, 3)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationSetLoopControl(73,
                                            SpvLoopControlUnrollMask |
                                                SpvLoopControlMaxIterationsMask |
                                                SpvLoopControlPeelCountMask,
                                            23, 0)
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationSetLoopControl(
                    73,
                    SpvLoopControlUnrollMask | SpvLoopControlMaxIterationsMask |
                        SpvLoopControlPeelCountMask,
                    2, 23)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   ASSERT_TRUE(TransformationSetLoopControl(83, SpvLoopControlMaskNone, 0, 0)
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationSetLoopControl(83, SpvLoopControlUnrollMask, 0, 0)
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(
       TransformationSetLoopControl(83, SpvLoopControlDontUnrollMask, 0, 0)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationSetLoopControl(
                    83,
                    SpvLoopControlUnrollMask | SpvLoopControlMinIterationsMask |
                        SpvLoopControlPeelCountMask |
                        SpvLoopControlPartialCountMask,
                    5, 3)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(
       TransformationSetLoopControl(83,
                                    SpvLoopControlUnrollMask |
                                        SpvLoopControlIterationMultipleMask |
                                        SpvLoopControlPeelCountMask,
                                    23, 0)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       TransformationSetLoopControl(83,
                                    SpvLoopControlUnrollMask |
                                        SpvLoopControlIterationMultipleMask |
                                        SpvLoopControlPeelCountMask,
                                    2, 23)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
 
   ASSERT_TRUE(TransformationSetLoopControl(93, SpvLoopControlMaskNone, 0, 0)
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationSetLoopControl(93, SpvLoopControlUnrollMask, 0, 0)
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(
       TransformationSetLoopControl(93, SpvLoopControlDontUnrollMask, 0, 0)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(
       TransformationSetLoopControl(93, SpvLoopControlPeelCountMask, 8, 0)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       TransformationSetLoopControl(93, SpvLoopControlPeelCountMask, 8, 8)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(
       TransformationSetLoopControl(93, SpvLoopControlPartialCountMask, 0, 8)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationSetLoopControl(
                   93,
                   SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask,
                   16, 8)
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
 
   ASSERT_TRUE(TransformationSetLoopControl(103, SpvLoopControlMaskNone, 0, 0)
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationSetLoopControl(103, SpvLoopControlUnrollMask, 0, 0)
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(
       TransformationSetLoopControl(103, SpvLoopControlDontUnrollMask, 0, 0)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(
       TransformationSetLoopControl(103, SpvLoopControlPartialCountMask, 0, 60)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationSetLoopControl(103,
                                             SpvLoopControlDontUnrollMask |
                                                 SpvLoopControlPartialCountMask,
                                             0, 60)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   ASSERT_TRUE(TransformationSetLoopControl(113, SpvLoopControlMaskNone, 0, 0)
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationSetLoopControl(113, SpvLoopControlUnrollMask, 0, 0)
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(
       TransformationSetLoopControl(113, SpvLoopControlDontUnrollMask, 0, 0)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(
       TransformationSetLoopControl(113, SpvLoopControlPeelCountMask, 12, 0)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       TransformationSetLoopControl(
           113,
           SpvLoopControlIterationMultipleMask | SpvLoopControlPeelCountMask, 12,
           0)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
 
   ASSERT_TRUE(TransformationSetLoopControl(123, SpvLoopControlMaskNone, 0, 0)
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationSetLoopControl(123, SpvLoopControlUnrollMask, 0, 0)
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(
       TransformationSetLoopControl(123, SpvLoopControlDontUnrollMask, 0, 0)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(
       TransformationSetLoopControl(
           123,
@@ -586,72 +589,72 @@
               SpvLoopControlIterationMultipleMask |
               SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask,
           7, 8)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_TRUE(TransformationSetLoopControl(123,
                                            SpvLoopControlUnrollMask |
                                                SpvLoopControlMinIterationsMask |
                                                SpvLoopControlMaxIterationsMask |
                                                SpvLoopControlPartialCountMask,
                                            0, 9)
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationSetLoopControl(
                    123,
                    SpvLoopControlUnrollMask | SpvLoopControlMinIterationsMask |
                        SpvLoopControlMaxIterationsMask |
                        SpvLoopControlPartialCountMask,
                    7, 9)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       TransformationSetLoopControl(
           123,
           SpvLoopControlDontUnrollMask | SpvLoopControlMinIterationsMask |
               SpvLoopControlMaxIterationsMask | SpvLoopControlPartialCountMask,
           7, 9)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
 
   TransformationSetLoopControl(10,
                                SpvLoopControlUnrollMask |
                                    SpvLoopControlPeelCountMask |
                                    SpvLoopControlPartialCountMask,
                                3, 3)
-      .Apply(context.get(), &fact_manager);
+      .Apply(context.get(), &transformation_context);
   TransformationSetLoopControl(23, SpvLoopControlDontUnrollMask, 0, 0)
-      .Apply(context.get(), &fact_manager);
+      .Apply(context.get(), &transformation_context);
   TransformationSetLoopControl(33, SpvLoopControlUnrollMask, 0, 0)
-      .Apply(context.get(), &fact_manager);
+      .Apply(context.get(), &transformation_context);
   TransformationSetLoopControl(
       43, SpvLoopControlDontUnrollMask | SpvLoopControlDependencyInfiniteMask,
       0, 0)
-      .Apply(context.get(), &fact_manager);
+      .Apply(context.get(), &transformation_context);
   TransformationSetLoopControl(53, SpvLoopControlMaskNone, 0, 0)
-      .Apply(context.get(), &fact_manager);
+      .Apply(context.get(), &transformation_context);
   TransformationSetLoopControl(63,
                                SpvLoopControlUnrollMask |
                                    SpvLoopControlMinIterationsMask |
                                    SpvLoopControlPeelCountMask,
                                23, 0)
-      .Apply(context.get(), &fact_manager);
+      .Apply(context.get(), &transformation_context);
   TransformationSetLoopControl(73,
                                SpvLoopControlUnrollMask |
                                    SpvLoopControlMaxIterationsMask |
                                    SpvLoopControlPeelCountMask,
                                23, 0)
-      .Apply(context.get(), &fact_manager);
+      .Apply(context.get(), &transformation_context);
   TransformationSetLoopControl(83, SpvLoopControlDontUnrollMask, 0, 0)
-      .Apply(context.get(), &fact_manager);
+      .Apply(context.get(), &transformation_context);
   TransformationSetLoopControl(
       93, SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask, 16, 8)
-      .Apply(context.get(), &fact_manager);
+      .Apply(context.get(), &transformation_context);
   TransformationSetLoopControl(103, SpvLoopControlPartialCountMask, 0, 60)
-      .Apply(context.get(), &fact_manager);
+      .Apply(context.get(), &transformation_context);
   TransformationSetLoopControl(113, SpvLoopControlPeelCountMask, 12, 0)
-      .Apply(context.get(), &fact_manager);
+      .Apply(context.get(), &transformation_context);
   TransformationSetLoopControl(
       123,
       SpvLoopControlUnrollMask | SpvLoopControlMinIterationsMask |
           SpvLoopControlMaxIterationsMask | SpvLoopControlPartialCountMask,
       0, 9)
-      .Apply(context.get(), &fact_manager);
+      .Apply(context.get(), &transformation_context);
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -942,25 +945,28 @@
       BuildModule(SPV_ENV_UNIVERSAL_1_5, consumer, shader, kFuzzAssembleOption);
 
   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(), fact_manager));
-  ASSERT_FALSE(
-      set_peel_and_partial.IsApplicable(context_1_1.get(), fact_manager));
-  ASSERT_FALSE(
-      set_peel_and_partial.IsApplicable(context_1_2.get(), fact_manager));
-  ASSERT_FALSE(
-      set_peel_and_partial.IsApplicable(context_1_3.get(), fact_manager));
+  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(), fact_manager));
-  ASSERT_TRUE(
-      set_peel_and_partial.IsApplicable(context_1_5.get(), fact_manager));
+  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));
 }
 
 }  // namespace
diff --git a/test/fuzz/transformation_set_memory_operands_mask_test.cpp b/test/fuzz/transformation_set_memory_operands_mask_test.cpp
index ad4dc25..c02d8d4 100644
--- a/test/fuzz/transformation_set_memory_operands_mask_test.cpp
+++ b/test/fuzz/transformation_set_memory_operands_mask_test.cpp
@@ -92,37 +92,41 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   // Not OK: the instruction is not a memory access.
   ASSERT_FALSE(TransformationSetMemoryOperandsMask(
                    MakeInstructionDescriptor(21, SpvOpAccessChain, 0),
                    SpvMemoryAccessMaskNone, 0)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // Not OK to remove Aligned
   ASSERT_FALSE(TransformationSetMemoryOperandsMask(
                    MakeInstructionDescriptor(147, SpvOpLoad, 0),
                    SpvMemoryAccessVolatileMask | SpvMemoryAccessNontemporalMask,
                    0)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   TransformationSetMemoryOperandsMask transformation1(
       MakeInstructionDescriptor(147, SpvOpLoad, 0),
       SpvMemoryAccessAlignedMask | SpvMemoryAccessVolatileMask, 0);
-  ASSERT_TRUE(transformation1.IsApplicable(context.get(), fact_manager));
-  transformation1.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation1.IsApplicable(context.get(), transformation_context));
+  transformation1.Apply(context.get(), &transformation_context);
 
   // Not OK to remove Aligned
   ASSERT_FALSE(TransformationSetMemoryOperandsMask(
                    MakeInstructionDescriptor(21, SpvOpCopyMemory, 0),
                    SpvMemoryAccessMaskNone, 0)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // OK: leaves the mask as is
   ASSERT_TRUE(TransformationSetMemoryOperandsMask(
                   MakeInstructionDescriptor(21, SpvOpCopyMemory, 0),
                   SpvMemoryAccessAlignedMask, 0)
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
 
   // OK: adds Nontemporal and Volatile
   TransformationSetMemoryOperandsMask transformation2(
@@ -130,41 +134,45 @@
       SpvMemoryAccessAlignedMask | SpvMemoryAccessNontemporalMask |
           SpvMemoryAccessVolatileMask,
       0);
-  ASSERT_TRUE(transformation2.IsApplicable(context.get(), fact_manager));
-  transformation2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation2.IsApplicable(context.get(), transformation_context));
+  transformation2.Apply(context.get(), &transformation_context);
 
   // Not OK to remove Volatile
   ASSERT_FALSE(TransformationSetMemoryOperandsMask(
                    MakeInstructionDescriptor(21, SpvOpCopyMemory, 1),
                    SpvMemoryAccessNontemporalMask, 0)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // Not OK to add Aligned
   ASSERT_FALSE(TransformationSetMemoryOperandsMask(
                    MakeInstructionDescriptor(21, SpvOpCopyMemory, 1),
                    SpvMemoryAccessAlignedMask | SpvMemoryAccessVolatileMask, 0)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // OK: adds Nontemporal
   TransformationSetMemoryOperandsMask transformation3(
       MakeInstructionDescriptor(21, SpvOpCopyMemory, 1),
       SpvMemoryAccessNontemporalMask | SpvMemoryAccessVolatileMask, 0);
-  ASSERT_TRUE(transformation3.IsApplicable(context.get(), fact_manager));
-  transformation3.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation3.IsApplicable(context.get(), transformation_context));
+  transformation3.Apply(context.get(), &transformation_context);
 
   // OK: adds Nontemporal and Volatile
   TransformationSetMemoryOperandsMask transformation4(
       MakeInstructionDescriptor(138, SpvOpCopyMemory, 0),
       SpvMemoryAccessNontemporalMask | SpvMemoryAccessVolatileMask, 0);
-  ASSERT_TRUE(transformation4.IsApplicable(context.get(), fact_manager));
-  transformation4.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation4.IsApplicable(context.get(), transformation_context));
+  transformation4.Apply(context.get(), &transformation_context);
 
   // OK: removes Nontemporal, adds Volatile
   TransformationSetMemoryOperandsMask transformation5(
       MakeInstructionDescriptor(148, SpvOpStore, 0),
       SpvMemoryAccessVolatileMask, 0);
-  ASSERT_TRUE(transformation5.IsApplicable(context.get(), fact_manager));
-  transformation5.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation5.IsApplicable(context.get(), transformation_context));
+  transformation5.Apply(context.get(), &transformation_context);
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -306,6 +314,9 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   TransformationSetMemoryOperandsMask transformation1(
       MakeInstructionDescriptor(21, SpvOpCopyMemory, 0),
@@ -314,9 +325,10 @@
   ASSERT_FALSE(TransformationSetMemoryOperandsMask(
                    MakeInstructionDescriptor(21, SpvOpCopyMemory, 0),
                    SpvMemoryAccessVolatileMask, 1)
-                   .IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(transformation1.IsApplicable(context.get(), fact_manager));
-  transformation1.Apply(context.get(), &fact_manager);
+                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(
+      transformation1.IsApplicable(context.get(), transformation_context));
+  transformation1.Apply(context.get(), &transformation_context);
 
   TransformationSetMemoryOperandsMask transformation2(
       MakeInstructionDescriptor(21, SpvOpCopyMemory, 1),
@@ -325,9 +337,10 @@
   ASSERT_FALSE(TransformationSetMemoryOperandsMask(
                    MakeInstructionDescriptor(21, SpvOpCopyMemory, 1),
                    SpvMemoryAccessNontemporalMask, 0)
-                   .IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(transformation2.IsApplicable(context.get(), fact_manager));
-  transformation2.Apply(context.get(), &fact_manager);
+                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(
+      transformation2.IsApplicable(context.get(), transformation_context));
+  transformation2.Apply(context.get(), &transformation_context);
 
   TransformationSetMemoryOperandsMask transformation3(
       MakeInstructionDescriptor(138, SpvOpCopyMemory, 0),
@@ -337,27 +350,31 @@
                    MakeInstructionDescriptor(138, SpvOpCopyMemory, 0),
                    SpvMemoryAccessAlignedMask | SpvMemoryAccessNontemporalMask,
                    0)
-                   .IsApplicable(context.get(), fact_manager));
-  ASSERT_TRUE(transformation3.IsApplicable(context.get(), fact_manager));
-  transformation3.Apply(context.get(), &fact_manager);
+                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(
+      transformation3.IsApplicable(context.get(), transformation_context));
+  transformation3.Apply(context.get(), &transformation_context);
 
   TransformationSetMemoryOperandsMask transformation4(
       MakeInstructionDescriptor(138, SpvOpCopyMemory, 1),
       SpvMemoryAccessVolatileMask, 1);
-  ASSERT_TRUE(transformation4.IsApplicable(context.get(), fact_manager));
-  transformation4.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation4.IsApplicable(context.get(), transformation_context));
+  transformation4.Apply(context.get(), &transformation_context);
 
   TransformationSetMemoryOperandsMask transformation5(
       MakeInstructionDescriptor(147, SpvOpLoad, 0),
       SpvMemoryAccessVolatileMask | SpvMemoryAccessAlignedMask, 0);
-  ASSERT_TRUE(transformation5.IsApplicable(context.get(), fact_manager));
-  transformation5.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation5.IsApplicable(context.get(), transformation_context));
+  transformation5.Apply(context.get(), &transformation_context);
 
   TransformationSetMemoryOperandsMask transformation6(
       MakeInstructionDescriptor(148, SpvOpStore, 0), SpvMemoryAccessMaskNone,
       0);
-  ASSERT_TRUE(transformation6.IsApplicable(context.get(), fact_manager));
-  transformation6.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation6.IsApplicable(context.get(), transformation_context));
+  transformation6.Apply(context.get(), &transformation_context);
 
   std::string after_transformation = R"(
                OpCapability Shader
diff --git a/test/fuzz/transformation_set_selection_control_test.cpp b/test/fuzz/transformation_set_selection_control_test.cpp
index 9696417..9afb89d 100644
--- a/test/fuzz/transformation_set_selection_control_test.cpp
+++ b/test/fuzz/transformation_set_selection_control_test.cpp
@@ -103,39 +103,46 @@
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   // %44 is not a block
   ASSERT_FALSE(
       TransformationSetSelectionControl(44, SpvSelectionControlFlattenMask)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   // %13 does not end with OpSelectionMerge
   ASSERT_FALSE(
       TransformationSetSelectionControl(13, SpvSelectionControlMaskNone)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   // %10 ends in OpLoopMerge, not OpSelectionMerge
   ASSERT_FALSE(
       TransformationSetSelectionControl(10, SpvSelectionControlMaskNone)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
 
   TransformationSetSelectionControl transformation1(
       11, SpvSelectionControlDontFlattenMask);
-  ASSERT_TRUE(transformation1.IsApplicable(context.get(), fact_manager));
-  transformation1.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation1.IsApplicable(context.get(), transformation_context));
+  transformation1.Apply(context.get(), &transformation_context);
 
   TransformationSetSelectionControl transformation2(
       23, SpvSelectionControlFlattenMask);
-  ASSERT_TRUE(transformation2.IsApplicable(context.get(), fact_manager));
-  transformation2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation2.IsApplicable(context.get(), transformation_context));
+  transformation2.Apply(context.get(), &transformation_context);
 
   TransformationSetSelectionControl transformation3(
       31, SpvSelectionControlMaskNone);
-  ASSERT_TRUE(transformation3.IsApplicable(context.get(), fact_manager));
-  transformation3.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation3.IsApplicable(context.get(), transformation_context));
+  transformation3.Apply(context.get(), &transformation_context);
 
   TransformationSetSelectionControl transformation4(
       31, SpvSelectionControlFlattenMask);
-  ASSERT_TRUE(transformation4.IsApplicable(context.get(), fact_manager));
-  transformation4.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation4.IsApplicable(context.get(), transformation_context));
+  transformation4.Apply(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 09007a5..8e9e8a4 100644
--- a/test/fuzz/transformation_split_block_test.cpp
+++ b/test/fuzz/transformation_split_block_test.cpp
@@ -89,57 +89,60 @@
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   // No split before OpVariable
   ASSERT_FALSE(TransformationSplitBlock(
                    MakeInstructionDescriptor(8, SpvOpVariable, 0), 100)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationSplitBlock(
                    MakeInstructionDescriptor(8, SpvOpVariable, 1), 100)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // No split before OpLabel
   ASSERT_FALSE(TransformationSplitBlock(
                    MakeInstructionDescriptor(14, SpvOpLabel, 0), 100)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // No split if base instruction is outside a function
   ASSERT_FALSE(
       TransformationSplitBlock(MakeInstructionDescriptor(1, SpvOpLabel, 0), 100)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationSplitBlock(
                    MakeInstructionDescriptor(1, SpvOpExecutionMode, 0), 100)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // No split if block is loop header
   ASSERT_FALSE(
       TransformationSplitBlock(MakeInstructionDescriptor(27, SpvOpPhi, 0), 100)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       TransformationSplitBlock(MakeInstructionDescriptor(27, SpvOpPhi, 1), 100)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
 
   // No split if base instruction does not exist
   ASSERT_FALSE(
       TransformationSplitBlock(MakeInstructionDescriptor(88, SpvOpIAdd, 0), 100)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationSplitBlock(
                    MakeInstructionDescriptor(88, SpvOpIMul, 22), 100)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // No split if too many instructions with the desired opcode are skipped
   ASSERT_FALSE(
       TransformationSplitBlock(
           MakeInstructionDescriptor(18, SpvOpBranchConditional, 1), 100)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
 
   // No split if id in use
   ASSERT_FALSE(TransformationSplitBlock(
                    MakeInstructionDescriptor(18, SpvOpSLessThan, 0), 27)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationSplitBlock(
                    MakeInstructionDescriptor(18, SpvOpSLessThan, 0), 14)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationSplitBlockTest, SplitBlockSeveralTimes) {
@@ -199,11 +202,14 @@
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   auto split_1 = TransformationSplitBlock(
       MakeInstructionDescriptor(5, SpvOpStore, 0), 100);
-  ASSERT_TRUE(split_1.IsApplicable(context.get(), fact_manager));
-  split_1.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(split_1.IsApplicable(context.get(), transformation_context));
+  split_1.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   std::string after_split_1 = R"(
@@ -250,8 +256,8 @@
 
   auto split_2 = TransformationSplitBlock(
       MakeInstructionDescriptor(11, SpvOpStore, 0), 101);
-  ASSERT_TRUE(split_2.IsApplicable(context.get(), fact_manager));
-  split_2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(split_2.IsApplicable(context.get(), transformation_context));
+  split_2.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   std::string after_split_2 = R"(
@@ -300,8 +306,8 @@
 
   auto split_3 = TransformationSplitBlock(
       MakeInstructionDescriptor(14, SpvOpLoad, 0), 102);
-  ASSERT_TRUE(split_3.IsApplicable(context.get(), fact_manager));
-  split_3.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(split_3.IsApplicable(context.get(), transformation_context));
+  split_3.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   std::string after_split_3 = R"(
@@ -412,21 +418,24 @@
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   // Illegal to split between the merge and the conditional branch.
   ASSERT_FALSE(
       TransformationSplitBlock(
           MakeInstructionDescriptor(14, SpvOpBranchConditional, 0), 100)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       TransformationSplitBlock(
           MakeInstructionDescriptor(12, SpvOpBranchConditional, 0), 100)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
 
   auto split = TransformationSplitBlock(
       MakeInstructionDescriptor(14, SpvOpSelectionMerge, 0), 100);
-  ASSERT_TRUE(split.IsApplicable(context.get(), fact_manager));
-  split.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(split.IsApplicable(context.get(), transformation_context));
+  split.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   std::string after_split = R"(
@@ -541,19 +550,22 @@
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   // Illegal to split between the merge and the conditional branch.
   ASSERT_FALSE(TransformationSplitBlock(
                    MakeInstructionDescriptor(9, SpvOpSwitch, 0), 100)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(TransformationSplitBlock(
                    MakeInstructionDescriptor(15, SpvOpSwitch, 0), 100)
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   auto split = TransformationSplitBlock(
       MakeInstructionDescriptor(9, SpvOpSelectionMerge, 0), 100);
-  ASSERT_TRUE(split.IsApplicable(context.get(), fact_manager));
-  split.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(split.IsApplicable(context.get(), transformation_context));
+  split.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   std::string after_split = R"(
@@ -674,18 +686,21 @@
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   // We cannot split before OpPhi instructions, since the number of incoming
   // blocks may not appropriately match after splitting.
   ASSERT_FALSE(
       TransformationSplitBlock(MakeInstructionDescriptor(26, SpvOpPhi, 0), 100)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       TransformationSplitBlock(MakeInstructionDescriptor(27, SpvOpPhi, 0), 100)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       TransformationSplitBlock(MakeInstructionDescriptor(27, SpvOpPhi, 1), 100)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationSplitBlockTest, SplitOpPhiWithSinglePredecessor) {
@@ -726,16 +741,19 @@
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   ASSERT_TRUE(
       TransformationSplitBlock(MakeInstructionDescriptor(21, SpvOpPhi, 0), 100)
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   // An equivalent transformation to the above, just described with respect to a
   // different base instruction.
   auto split =
       TransformationSplitBlock(MakeInstructionDescriptor(20, SpvOpPhi, 0), 100);
-  ASSERT_TRUE(split.IsApplicable(context.get(), fact_manager));
-  split.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(split.IsApplicable(context.get(), transformation_context));
+  split.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
   std::string after_split = R"(
@@ -805,18 +823,21 @@
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   // Record the fact that block 8 is dead.
-  fact_manager.AddFactBlockIsDead(8);
+  transformation_context.GetFactManager()->AddFactBlockIsDead(8);
 
   auto split = TransformationSplitBlock(
       MakeInstructionDescriptor(8, SpvOpBranch, 0), 100);
-  ASSERT_TRUE(split.IsApplicable(context.get(), fact_manager));
-  split.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(split.IsApplicable(context.get(), transformation_context));
+  split.Apply(context.get(), &transformation_context);
   ASSERT_TRUE(IsValid(env, context.get()));
 
-  ASSERT_TRUE(fact_manager.BlockIsDead(8));
-  ASSERT_TRUE(fact_manager.BlockIsDead(100));
+  ASSERT_TRUE(transformation_context.GetFactManager()->BlockIsDead(8));
+  ASSERT_TRUE(transformation_context.GetFactManager()->BlockIsDead(100));
 
   std::string after_split = R"(
                OpCapability Shader
diff --git a/test/fuzz/transformation_store_test.cpp b/test/fuzz/transformation_store_test.cpp
new file mode 100644
index 0000000..067a5a1
--- /dev/null
+++ b/test/fuzz/transformation_store_test.cpp
@@ -0,0 +1,356 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_store.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationStoreTest, BasicTest) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %92 %52 %53
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpDecorate %92 BuiltIn FragCoord
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeFloat 32
+          %8 = OpTypeStruct %6 %7
+          %9 = OpTypePointer Function %8
+         %10 = OpTypeFunction %6 %9
+         %14 = OpConstant %6 0
+         %15 = OpTypePointer Function %6
+         %51 = OpTypePointer Private %6
+         %21 = OpConstant %6 2
+         %23 = OpConstant %6 1
+         %24 = OpConstant %7 1
+         %25 = OpTypePointer Function %7
+         %50 = OpTypePointer Private %7
+         %34 = OpTypeBool
+         %35 = OpConstantFalse %34
+         %60 = OpConstantNull %50
+         %61 = OpUndef %51
+         %52 = OpVariable %50 Private
+         %53 = OpVariable %51 Private
+         %80 = OpConstantComposite %8 %21 %24
+         %90 = OpTypeVector %7 4
+         %91 = OpTypePointer Input %90
+         %92 = OpVariable %91 Input
+         %93 = OpConstantComposite %90 %24 %24 %24 %24
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %20 = OpVariable %9 Function
+         %27 = OpVariable %9 Function ; irrelevant
+         %22 = OpAccessChain %15 %20 %14
+         %44 = OpCopyObject %9 %20
+         %26 = OpAccessChain %25 %20 %23
+         %29 = OpFunctionCall %6 %12 %27
+         %30 = OpAccessChain %15 %20 %14
+         %45 = OpCopyObject %15 %30
+         %81 = OpCopyObject %9 %27 ; irrelevant
+         %33 = OpAccessChain %15 %20 %14
+               OpSelectionMerge %37 None
+               OpBranchConditional %35 %36 %37
+         %36 = OpLabel
+         %38 = OpAccessChain %15 %20 %14
+         %40 = OpAccessChain %15 %20 %14
+         %43 = OpAccessChain %15 %20 %14
+         %82 = OpCopyObject %9 %27 ; irrelevant
+               OpBranch %37
+         %37 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %6 None %10
+         %11 = OpFunctionParameter %9 ; irrelevant
+         %13 = OpLabel
+         %46 = OpCopyObject %9 %11 ; irrelevant
+         %16 = OpAccessChain %15 %11 %14 ; irrelevant
+         %95 = OpCopyObject %8 %80
+               OpReturnValue %21
+               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;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
+  transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
+      27);
+  transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
+      11);
+  transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
+      46);
+  transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
+      16);
+  transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
+      52);
+  transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
+      81);
+  transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
+      82);
+
+  transformation_context.GetFactManager()->AddFactBlockIsDead(36);
+
+  // Variables with pointee types:
+  //  52 - ptr_to(7)
+  //  53 - ptr_to(6)
+  //  20 - ptr_to(8)
+  //  27 - ptr_to(8) - irrelevant
+  //  92 - ptr_to(90) - read only
+
+  // Access chains with pointee type:
+  //  22 - ptr_to(6)
+  //  26 - ptr_to(6)
+  //  30 - ptr_to(6)
+  //  33 - ptr_to(6)
+  //  38 - ptr_to(6)
+  //  40 - ptr_to(6)
+  //  43 - ptr_to(6)
+  //  16 - ptr_to(6) - irrelevant
+
+  // Copied object with pointee type:
+  //  44 - ptr_to(8)
+  //  45 - ptr_to(6)
+  //  46 - ptr_to(8) - irrelevant
+  //  81 - ptr_to(8) - irrelevant
+  //  82 - ptr_to(8) - irrelevant
+
+  // Function parameters with pointee type:
+  //  11 - ptr_to(8) - irrelevant
+
+  // Pointers that cannot be used:
+  //  60 - null
+  //  61 - undefined
+
+  // Bad: attempt to store to 11 from outside its function
+  ASSERT_FALSE(TransformationStore(
+                   11, 80, MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: pointer is not available
+  ASSERT_FALSE(TransformationStore(
+                   81, 80, MakeInstructionDescriptor(45, SpvOpCopyObject, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: attempt to insert before OpVariable
+  ASSERT_FALSE(TransformationStore(
+                   52, 24, MakeInstructionDescriptor(27, SpvOpVariable, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: pointer id does not exist
+  ASSERT_FALSE(TransformationStore(
+                   1000, 24, MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: pointer id exists but does not have a type
+  ASSERT_FALSE(TransformationStore(
+                   5, 24, MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: pointer id exists and has a type, but is not a pointer
+  ASSERT_FALSE(TransformationStore(
+                   24, 24, MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: attempt to store to a null pointer
+  ASSERT_FALSE(TransformationStore(
+                   60, 24, MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: attempt to store to an undefined pointer
+  ASSERT_FALSE(TransformationStore(
+                   61, 21, MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: %82 is not available at the program point
+  ASSERT_FALSE(
+      TransformationStore(82, 80, MakeInstructionDescriptor(37, SpvOpReturn, 0))
+          .IsApplicable(context.get(), transformation_context));
+
+  // Bad: value id does not exist
+  ASSERT_FALSE(TransformationStore(
+                   27, 1000, MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: value id exists but does not have a type
+  ASSERT_FALSE(TransformationStore(
+                   27, 15, MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: value id exists but has the wrong type
+  ASSERT_FALSE(TransformationStore(
+                   27, 14, MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: attempt to store to read-only variable
+  ASSERT_FALSE(TransformationStore(
+                   92, 93, MakeInstructionDescriptor(40, SpvOpAccessChain, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: value is not available
+  ASSERT_FALSE(TransformationStore(
+                   27, 95, MakeInstructionDescriptor(40, SpvOpAccessChain, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: variable being stored to does not have an irrelevant pointee value,
+  // and the store is not in a dead block.
+  ASSERT_FALSE(TransformationStore(
+                   20, 95, MakeInstructionDescriptor(45, SpvOpCopyObject, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  // The described instruction does not exist.
+  ASSERT_FALSE(TransformationStore(
+                   27, 80, MakeInstructionDescriptor(1000, SpvOpAccessChain, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  {
+    // Store to irrelevant variable from dead block.
+    TransformationStore transformation(
+        27, 80, MakeInstructionDescriptor(38, SpvOpAccessChain, 0));
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    transformation.Apply(context.get(), &transformation_context);
+    ASSERT_TRUE(IsValid(env, context.get()));
+  }
+
+  {
+    // Store to irrelevant variable from live block.
+    TransformationStore transformation(
+        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()));
+  }
+
+  {
+    // Store to irrelevant variable from live block.
+    TransformationStore transformation(
+        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()));
+  }
+
+  {
+    // Store to irrelevant variable from live block.
+    TransformationStore transformation(
+        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()));
+  }
+
+  {
+    // Store to non-irrelevant variable from dead block.
+    TransformationStore transformation(
+        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()));
+  }
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %92 %52 %53
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpDecorate %92 BuiltIn FragCoord
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeFloat 32
+          %8 = OpTypeStruct %6 %7
+          %9 = OpTypePointer Function %8
+         %10 = OpTypeFunction %6 %9
+         %14 = OpConstant %6 0
+         %15 = OpTypePointer Function %6
+         %51 = OpTypePointer Private %6
+         %21 = OpConstant %6 2
+         %23 = OpConstant %6 1
+         %24 = OpConstant %7 1
+         %25 = OpTypePointer Function %7
+         %50 = OpTypePointer Private %7
+         %34 = OpTypeBool
+         %35 = OpConstantFalse %34
+         %60 = OpConstantNull %50
+         %61 = OpUndef %51
+         %52 = OpVariable %50 Private
+         %53 = OpVariable %51 Private
+         %80 = OpConstantComposite %8 %21 %24
+         %90 = OpTypeVector %7 4
+         %91 = OpTypePointer Input %90
+         %92 = OpVariable %91 Input
+         %93 = OpConstantComposite %90 %24 %24 %24 %24
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %20 = OpVariable %9 Function
+         %27 = OpVariable %9 Function ; irrelevant
+         %22 = OpAccessChain %15 %20 %14
+         %44 = OpCopyObject %9 %20
+         %26 = OpAccessChain %25 %20 %23
+         %29 = OpFunctionCall %6 %12 %27
+         %30 = OpAccessChain %15 %20 %14
+         %45 = OpCopyObject %15 %30
+         %81 = OpCopyObject %9 %27 ; irrelevant
+         %33 = OpAccessChain %15 %20 %14
+               OpSelectionMerge %37 None
+               OpBranchConditional %35 %36 %37
+         %36 = OpLabel
+               OpStore %27 %80
+               OpStore %53 %21
+         %38 = OpAccessChain %15 %20 %14
+         %40 = OpAccessChain %15 %20 %14
+         %43 = OpAccessChain %15 %20 %14
+         %82 = OpCopyObject %9 %27 ; irrelevant
+               OpBranch %37
+         %37 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %6 None %10
+         %11 = OpFunctionParameter %9 ; irrelevant
+         %13 = OpLabel
+         %46 = OpCopyObject %9 %11 ; irrelevant
+         %16 = OpAccessChain %15 %11 %14 ; irrelevant
+         %95 = OpCopyObject %8 %80
+               OpStore %11 %95
+               OpStore %46 %80
+               OpStore %16 %21
+               OpReturnValue %21
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/transformation_swap_commutable_operands_test.cpp b/test/fuzz/transformation_swap_commutable_operands_test.cpp
new file mode 100644
index 0000000..c213dfe
--- /dev/null
+++ b/test/fuzz/transformation_swap_commutable_operands_test.cpp
@@ -0,0 +1,457 @@
+// 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_swap_commutable_operands.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationSwapCommutableOperandsTest, IsApplicableTest) {
+  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 = OpTypeInt 32 1
+          %7 = OpTypeInt 32 0
+          %8 = OpConstant %7 2
+          %9 = OpTypeArray %6 %8
+         %10 = OpTypePointer Function %9
+         %12 = OpConstant %6 1
+         %13 = OpConstant %6 2
+         %14 = OpConstantComposite %9 %12 %13
+         %15 = OpTypePointer Function %6
+         %17 = OpConstant %6 0
+         %29 = OpTypeFloat 32
+         %30 = OpTypeArray %29 %8
+         %31 = OpTypePointer Function %30
+         %33 = OpConstant %29 1
+         %34 = OpConstant %29 2
+         %35 = OpConstantComposite %30 %33 %34
+         %36 = OpTypePointer Function %29
+         %49 = OpTypeVector %29 3
+         %50 = OpTypeArray %49 %8
+         %51 = OpTypePointer Function %50
+         %53 = OpConstant %29 3
+         %54 = OpConstantComposite %49 %33 %34 %53
+         %55 = OpConstant %29 4
+         %56 = OpConstant %29 5
+         %57 = OpConstant %29 6
+         %58 = OpConstantComposite %49 %55 %56 %57
+         %59 = OpConstantComposite %50 %54 %58
+         %61 = OpTypePointer Function %49
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %11 = OpVariable %10 Function
+         %16 = OpVariable %15 Function
+         %23 = OpVariable %15 Function
+         %32 = OpVariable %31 Function
+         %37 = OpVariable %36 Function
+         %43 = OpVariable %36 Function
+         %52 = OpVariable %51 Function
+         %60 = OpVariable %36 Function
+               OpStore %11 %14
+         %18 = OpAccessChain %15 %11 %17
+         %19 = OpLoad %6 %18
+         %20 = OpAccessChain %15 %11 %12
+         %21 = OpLoad %6 %20
+         %22 = OpIAdd %6 %19 %21
+               OpStore %16 %22
+         %24 = OpAccessChain %15 %11 %17
+         %25 = OpLoad %6 %24
+         %26 = OpAccessChain %15 %11 %12
+         %27 = OpLoad %6 %26
+         %28 = OpIMul %6 %25 %27
+               OpStore %23 %28
+               OpStore %32 %35
+         %38 = OpAccessChain %36 %32 %17
+         %39 = OpLoad %29 %38
+         %40 = OpAccessChain %36 %32 %12
+         %41 = OpLoad %29 %40
+         %42 = OpFAdd %29 %39 %41
+               OpStore %37 %42
+         %44 = OpAccessChain %36 %32 %17
+         %45 = OpLoad %29 %44
+         %46 = OpAccessChain %36 %32 %12
+         %47 = OpLoad %29 %46
+         %48 = OpFMul %29 %45 %47
+               OpStore %43 %48
+               OpStore %52 %59
+         %62 = OpAccessChain %61 %52 %17
+         %63 = OpLoad %49 %62
+         %64 = OpAccessChain %61 %52 %12
+         %65 = OpLoad %49 %64
+         %66 = OpDot %29 %63 %65
+               OpStore %60 %66
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  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);
+
+  // Tests existing commutative instructions
+  auto instructionDescriptor = MakeInstructionDescriptor(22, SpvOpIAdd, 0);
+  auto transformation =
+      TransformationSwapCommutableOperands(instructionDescriptor);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  instructionDescriptor = MakeInstructionDescriptor(28, SpvOpIMul, 0);
+  transformation = TransformationSwapCommutableOperands(instructionDescriptor);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  instructionDescriptor = MakeInstructionDescriptor(42, SpvOpFAdd, 0);
+  transformation = TransformationSwapCommutableOperands(instructionDescriptor);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  instructionDescriptor = MakeInstructionDescriptor(48, SpvOpFMul, 0);
+  transformation = TransformationSwapCommutableOperands(instructionDescriptor);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  instructionDescriptor = MakeInstructionDescriptor(66, SpvOpDot, 0);
+  transformation = TransformationSwapCommutableOperands(instructionDescriptor);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests existing non-commutative instructions
+  instructionDescriptor = MakeInstructionDescriptor(1, SpvOpExtInstImport, 0);
+  transformation = TransformationSwapCommutableOperands(instructionDescriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  instructionDescriptor = MakeInstructionDescriptor(5, SpvOpLabel, 0);
+  transformation = TransformationSwapCommutableOperands(instructionDescriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  instructionDescriptor = MakeInstructionDescriptor(8, SpvOpConstant, 0);
+  transformation = TransformationSwapCommutableOperands(instructionDescriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  instructionDescriptor = MakeInstructionDescriptor(11, SpvOpVariable, 0);
+  transformation = TransformationSwapCommutableOperands(instructionDescriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  instructionDescriptor =
+      MakeInstructionDescriptor(14, SpvOpConstantComposite, 0);
+  transformation = TransformationSwapCommutableOperands(instructionDescriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests the base instruction id not existing
+  instructionDescriptor = MakeInstructionDescriptor(67, SpvOpIAddCarry, 0);
+  transformation = TransformationSwapCommutableOperands(instructionDescriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  instructionDescriptor = MakeInstructionDescriptor(68, SpvOpIEqual, 0);
+  transformation = TransformationSwapCommutableOperands(instructionDescriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  instructionDescriptor = MakeInstructionDescriptor(69, SpvOpINotEqual, 0);
+  transformation = TransformationSwapCommutableOperands(instructionDescriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  instructionDescriptor = MakeInstructionDescriptor(70, SpvOpFOrdEqual, 0);
+  transformation = TransformationSwapCommutableOperands(instructionDescriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  instructionDescriptor = MakeInstructionDescriptor(71, SpvOpPtrEqual, 0);
+  transformation = TransformationSwapCommutableOperands(instructionDescriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests there being no instruction with the desired opcode after the base
+  // instruction id
+  instructionDescriptor = MakeInstructionDescriptor(24, SpvOpIAdd, 0);
+  transformation = TransformationSwapCommutableOperands(instructionDescriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  instructionDescriptor = MakeInstructionDescriptor(38, SpvOpIMul, 0);
+  transformation = TransformationSwapCommutableOperands(instructionDescriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  instructionDescriptor = MakeInstructionDescriptor(45, SpvOpFAdd, 0);
+  transformation = TransformationSwapCommutableOperands(instructionDescriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  instructionDescriptor = MakeInstructionDescriptor(66, SpvOpFMul, 0);
+  transformation = TransformationSwapCommutableOperands(instructionDescriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests there being an instruction with the desired opcode after the base
+  // instruction id, but the skip count associated with the instruction
+  // descriptor being so high.
+  instructionDescriptor = MakeInstructionDescriptor(11, SpvOpIAdd, 100);
+  transformation = TransformationSwapCommutableOperands(instructionDescriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  instructionDescriptor = MakeInstructionDescriptor(16, SpvOpIMul, 100);
+  transformation = TransformationSwapCommutableOperands(instructionDescriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  instructionDescriptor = MakeInstructionDescriptor(23, SpvOpFAdd, 100);
+  transformation = TransformationSwapCommutableOperands(instructionDescriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  instructionDescriptor = MakeInstructionDescriptor(32, SpvOpFMul, 100);
+  transformation = TransformationSwapCommutableOperands(instructionDescriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  instructionDescriptor = MakeInstructionDescriptor(37, SpvOpDot, 100);
+  transformation = TransformationSwapCommutableOperands(instructionDescriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationSwapCommutableOperandsTest, ApplyTest) {
+  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 = OpTypeInt 32 1
+          %7 = OpTypeInt 32 0
+          %8 = OpConstant %7 2
+          %9 = OpTypeArray %6 %8
+         %10 = OpTypePointer Function %9
+         %12 = OpConstant %6 1
+         %13 = OpConstant %6 2
+         %14 = OpConstantComposite %9 %12 %13
+         %15 = OpTypePointer Function %6
+         %17 = OpConstant %6 0
+         %29 = OpTypeFloat 32
+         %30 = OpTypeArray %29 %8
+         %31 = OpTypePointer Function %30
+         %33 = OpConstant %29 1
+         %34 = OpConstant %29 2
+         %35 = OpConstantComposite %30 %33 %34
+         %36 = OpTypePointer Function %29
+         %49 = OpTypeVector %29 3
+         %50 = OpTypeArray %49 %8
+         %51 = OpTypePointer Function %50
+         %53 = OpConstant %29 3
+         %54 = OpConstantComposite %49 %33 %34 %53
+         %55 = OpConstant %29 4
+         %56 = OpConstant %29 5
+         %57 = OpConstant %29 6
+         %58 = OpConstantComposite %49 %55 %56 %57
+         %59 = OpConstantComposite %50 %54 %58
+         %61 = OpTypePointer Function %49
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %11 = OpVariable %10 Function
+         %16 = OpVariable %15 Function
+         %23 = OpVariable %15 Function
+         %32 = OpVariable %31 Function
+         %37 = OpVariable %36 Function
+         %43 = OpVariable %36 Function
+         %52 = OpVariable %51 Function
+         %60 = OpVariable %36 Function
+               OpStore %11 %14
+         %18 = OpAccessChain %15 %11 %17
+         %19 = OpLoad %6 %18
+         %20 = OpAccessChain %15 %11 %12
+         %21 = OpLoad %6 %20
+         %22 = OpIAdd %6 %19 %21
+               OpStore %16 %22
+         %24 = OpAccessChain %15 %11 %17
+         %25 = OpLoad %6 %24
+         %26 = OpAccessChain %15 %11 %12
+         %27 = OpLoad %6 %26
+         %28 = OpIMul %6 %25 %27
+               OpStore %23 %28
+               OpStore %32 %35
+         %38 = OpAccessChain %36 %32 %17
+         %39 = OpLoad %29 %38
+         %40 = OpAccessChain %36 %32 %12
+         %41 = OpLoad %29 %40
+         %42 = OpFAdd %29 %39 %41
+               OpStore %37 %42
+         %44 = OpAccessChain %36 %32 %17
+         %45 = OpLoad %29 %44
+         %46 = OpAccessChain %36 %32 %12
+         %47 = OpLoad %29 %46
+         %48 = OpFMul %29 %45 %47
+               OpStore %43 %48
+               OpStore %52 %59
+         %62 = OpAccessChain %61 %52 %17
+         %63 = OpLoad %49 %62
+         %64 = OpAccessChain %61 %52 %12
+         %65 = OpLoad %49 %64
+         %66 = OpDot %29 %63 %65
+               OpStore %60 %66
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  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);
+
+  auto instructionDescriptor = MakeInstructionDescriptor(22, SpvOpIAdd, 0);
+  auto transformation =
+      TransformationSwapCommutableOperands(instructionDescriptor);
+  transformation.Apply(context.get(), &transformation_context);
+
+  instructionDescriptor = MakeInstructionDescriptor(28, SpvOpIMul, 0);
+  transformation = TransformationSwapCommutableOperands(instructionDescriptor);
+  transformation.Apply(context.get(), &transformation_context);
+
+  instructionDescriptor = MakeInstructionDescriptor(42, SpvOpFAdd, 0);
+  transformation = TransformationSwapCommutableOperands(instructionDescriptor);
+  transformation.Apply(context.get(), &transformation_context);
+
+  instructionDescriptor = MakeInstructionDescriptor(48, SpvOpFMul, 0);
+  transformation = TransformationSwapCommutableOperands(instructionDescriptor);
+  transformation.Apply(context.get(), &transformation_context);
+
+  instructionDescriptor = MakeInstructionDescriptor(66, SpvOpDot, 0);
+  transformation = TransformationSwapCommutableOperands(instructionDescriptor);
+  transformation.Apply(context.get(), &transformation_context);
+
+  std::string variantShader = 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 = OpTypeInt 32 1
+          %7 = OpTypeInt 32 0
+          %8 = OpConstant %7 2
+          %9 = OpTypeArray %6 %8
+         %10 = OpTypePointer Function %9
+         %12 = OpConstant %6 1
+         %13 = OpConstant %6 2
+         %14 = OpConstantComposite %9 %12 %13
+         %15 = OpTypePointer Function %6
+         %17 = OpConstant %6 0
+         %29 = OpTypeFloat 32
+         %30 = OpTypeArray %29 %8
+         %31 = OpTypePointer Function %30
+         %33 = OpConstant %29 1
+         %34 = OpConstant %29 2
+         %35 = OpConstantComposite %30 %33 %34
+         %36 = OpTypePointer Function %29
+         %49 = OpTypeVector %29 3
+         %50 = OpTypeArray %49 %8
+         %51 = OpTypePointer Function %50
+         %53 = OpConstant %29 3
+         %54 = OpConstantComposite %49 %33 %34 %53
+         %55 = OpConstant %29 4
+         %56 = OpConstant %29 5
+         %57 = OpConstant %29 6
+         %58 = OpConstantComposite %49 %55 %56 %57
+         %59 = OpConstantComposite %50 %54 %58
+         %61 = OpTypePointer Function %49
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %11 = OpVariable %10 Function
+         %16 = OpVariable %15 Function
+         %23 = OpVariable %15 Function
+         %32 = OpVariable %31 Function
+         %37 = OpVariable %36 Function
+         %43 = OpVariable %36 Function
+         %52 = OpVariable %51 Function
+         %60 = OpVariable %36 Function
+               OpStore %11 %14
+         %18 = OpAccessChain %15 %11 %17
+         %19 = OpLoad %6 %18
+         %20 = OpAccessChain %15 %11 %12
+         %21 = OpLoad %6 %20
+         %22 = OpIAdd %6 %21 %19
+               OpStore %16 %22
+         %24 = OpAccessChain %15 %11 %17
+         %25 = OpLoad %6 %24
+         %26 = OpAccessChain %15 %11 %12
+         %27 = OpLoad %6 %26
+         %28 = OpIMul %6 %27 %25
+               OpStore %23 %28
+               OpStore %32 %35
+         %38 = OpAccessChain %36 %32 %17
+         %39 = OpLoad %29 %38
+         %40 = OpAccessChain %36 %32 %12
+         %41 = OpLoad %29 %40
+         %42 = OpFAdd %29 %41 %39
+               OpStore %37 %42
+         %44 = OpAccessChain %36 %32 %17
+         %45 = OpLoad %29 %44
+         %46 = OpAccessChain %36 %32 %12
+         %47 = OpLoad %29 %46
+         %48 = OpFMul %29 %47 %45
+               OpStore %43 %48
+               OpStore %52 %59
+         %62 = OpAccessChain %61 %52 %17
+         %63 = OpLoad %49 %62
+         %64 = OpAccessChain %61 %52 %12
+         %65 = OpLoad %49 %64
+         %66 = OpDot %29 %65 %63
+               OpStore %60 %66
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, variantShader, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/transformation_toggle_access_chain_instruction_test.cpp b/test/fuzz/transformation_toggle_access_chain_instruction_test.cpp
new file mode 100644
index 0000000..b20f59e
--- /dev/null
+++ b/test/fuzz/transformation_toggle_access_chain_instruction_test.cpp
@@ -0,0 +1,433 @@
+// 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_toggle_access_chain_instruction.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationToggleAccessChainInstructionTest, IsApplicableTest) {
+  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 = OpTypeInt 32 1
+          %7 = OpTypeInt 32 0
+          %8 = OpConstant %7 2
+          %9 = OpTypeArray %6 %8
+         %10 = OpTypePointer Function %9
+         %12 = OpConstant %6 1
+         %13 = OpConstant %6 2
+         %14 = OpConstantComposite %9 %12 %13
+         %15 = OpTypePointer Function %6
+         %17 = OpConstant %6 0
+         %29 = OpTypeFloat 32
+         %30 = OpTypeArray %29 %8
+         %31 = OpTypePointer Function %30
+         %33 = OpConstant %29 1
+         %34 = OpConstant %29 2
+         %35 = OpConstantComposite %30 %33 %34
+         %36 = OpTypePointer Function %29
+         %49 = OpTypeVector %29 3
+         %50 = OpTypeArray %49 %8
+         %51 = OpTypePointer Function %50
+         %53 = OpConstant %29 3
+         %54 = OpConstantComposite %49 %33 %34 %53
+         %55 = OpConstant %29 4
+         %56 = OpConstant %29 5
+         %57 = OpConstant %29 6
+         %58 = OpConstantComposite %49 %55 %56 %57
+         %59 = OpConstantComposite %50 %54 %58
+         %61 = OpTypePointer Function %49
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %11 = OpVariable %10 Function
+         %16 = OpVariable %15 Function
+         %23 = OpVariable %15 Function
+         %32 = OpVariable %31 Function
+         %37 = OpVariable %36 Function
+         %43 = OpVariable %36 Function
+         %52 = OpVariable %51 Function
+         %60 = OpVariable %36 Function
+               OpStore %11 %14
+         %18 = OpAccessChain %15 %11 %17
+         %19 = OpLoad %6 %18
+         %20 = OpInBoundsAccessChain %15 %11 %12
+         %21 = OpLoad %6 %20
+         %22 = OpIAdd %6 %19 %21
+               OpStore %16 %22
+         %24 = OpAccessChain %15 %11 %17
+         %25 = OpLoad %6 %24
+         %26 = OpInBoundsAccessChain %15 %11 %12
+         %27 = OpLoad %6 %26
+         %28 = OpIMul %6 %25 %27
+               OpStore %23 %28
+               OpStore %32 %35
+         %38 = OpAccessChain %36 %32 %17
+         %39 = OpLoad %29 %38
+         %40 = OpAccessChain %36 %32 %12
+         %41 = OpLoad %29 %40
+         %42 = OpFAdd %29 %39 %41
+               OpStore %37 %42
+         %44 = OpAccessChain %36 %32 %17
+         %45 = OpLoad %29 %44
+         %46 = OpAccessChain %36 %32 %12
+         %47 = OpLoad %29 %46
+         %48 = OpFMul %29 %45 %47
+               OpStore %43 %48
+               OpStore %52 %59
+         %62 = OpAccessChain %61 %52 %17
+         %63 = OpLoad %49 %62
+         %64 = OpAccessChain %61 %52 %12
+         %65 = OpLoad %49 %64
+         %66 = OpDot %29 %63 %65
+               OpStore %60 %66
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  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);
+
+  // Tests existing access chain instructions
+  auto instructionDescriptor =
+      MakeInstructionDescriptor(18, SpvOpAccessChain, 0);
+  auto transformation =
+      TransformationToggleAccessChainInstruction(instructionDescriptor);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  instructionDescriptor =
+      MakeInstructionDescriptor(20, SpvOpInBoundsAccessChain, 0);
+  transformation =
+      TransformationToggleAccessChainInstruction(instructionDescriptor);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  instructionDescriptor = MakeInstructionDescriptor(24, SpvOpAccessChain, 0);
+  transformation =
+      TransformationToggleAccessChainInstruction(instructionDescriptor);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  instructionDescriptor =
+      MakeInstructionDescriptor(26, SpvOpInBoundsAccessChain, 0);
+  transformation =
+      TransformationToggleAccessChainInstruction(instructionDescriptor);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests existing non-access chain instructions
+  instructionDescriptor = MakeInstructionDescriptor(1, SpvOpExtInstImport, 0);
+  transformation =
+      TransformationToggleAccessChainInstruction(instructionDescriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  instructionDescriptor = MakeInstructionDescriptor(5, SpvOpLabel, 0);
+  transformation =
+      TransformationToggleAccessChainInstruction(instructionDescriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  instructionDescriptor =
+      MakeInstructionDescriptor(14, SpvOpConstantComposite, 0);
+  transformation =
+      TransformationToggleAccessChainInstruction(instructionDescriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests the base instruction id not existing
+  instructionDescriptor = MakeInstructionDescriptor(67, SpvOpAccessChain, 0);
+  transformation =
+      TransformationToggleAccessChainInstruction(instructionDescriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  instructionDescriptor = MakeInstructionDescriptor(68, SpvOpAccessChain, 0);
+  transformation =
+      TransformationToggleAccessChainInstruction(instructionDescriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  instructionDescriptor =
+      MakeInstructionDescriptor(69, SpvOpInBoundsAccessChain, 0);
+  transformation =
+      TransformationToggleAccessChainInstruction(instructionDescriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests there being no instruction with the desired opcode after the base
+  // instruction id
+  instructionDescriptor = MakeInstructionDescriptor(65, SpvOpAccessChain, 0);
+  transformation =
+      TransformationToggleAccessChainInstruction(instructionDescriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  instructionDescriptor =
+      MakeInstructionDescriptor(66, SpvOpInBoundsAccessChain, 0);
+  transformation =
+      TransformationToggleAccessChainInstruction(instructionDescriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests there being an instruction with the desired opcode after the base
+  // instruction id, but the skip count associated with the instruction
+  // descriptor being so high.
+  instructionDescriptor = MakeInstructionDescriptor(11, SpvOpAccessChain, 100);
+  transformation =
+      TransformationToggleAccessChainInstruction(instructionDescriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  instructionDescriptor =
+      MakeInstructionDescriptor(16, SpvOpInBoundsAccessChain, 100);
+  transformation =
+      TransformationToggleAccessChainInstruction(instructionDescriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationToggleAccessChainInstructionTest, ApplyTest) {
+  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 = OpTypeInt 32 1
+          %7 = OpTypeInt 32 0
+          %8 = OpConstant %7 2
+          %9 = OpTypeArray %6 %8
+         %10 = OpTypePointer Function %9
+         %12 = OpConstant %6 1
+         %13 = OpConstant %6 2
+         %14 = OpConstantComposite %9 %12 %13
+         %15 = OpTypePointer Function %6
+         %17 = OpConstant %6 0
+         %29 = OpTypeFloat 32
+         %30 = OpTypeArray %29 %8
+         %31 = OpTypePointer Function %30
+         %33 = OpConstant %29 1
+         %34 = OpConstant %29 2
+         %35 = OpConstantComposite %30 %33 %34
+         %36 = OpTypePointer Function %29
+         %49 = OpTypeVector %29 3
+         %50 = OpTypeArray %49 %8
+         %51 = OpTypePointer Function %50
+         %53 = OpConstant %29 3
+         %54 = OpConstantComposite %49 %33 %34 %53
+         %55 = OpConstant %29 4
+         %56 = OpConstant %29 5
+         %57 = OpConstant %29 6
+         %58 = OpConstantComposite %49 %55 %56 %57
+         %59 = OpConstantComposite %50 %54 %58
+         %61 = OpTypePointer Function %49
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %11 = OpVariable %10 Function
+         %16 = OpVariable %15 Function
+         %23 = OpVariable %15 Function
+         %32 = OpVariable %31 Function
+         %37 = OpVariable %36 Function
+         %43 = OpVariable %36 Function
+         %52 = OpVariable %51 Function
+         %60 = OpVariable %36 Function
+               OpStore %11 %14
+         %18 = OpAccessChain %15 %11 %17
+         %19 = OpLoad %6 %18
+         %20 = OpInBoundsAccessChain %15 %11 %12
+         %21 = OpLoad %6 %20
+         %22 = OpIAdd %6 %19 %21
+               OpStore %16 %22
+         %24 = OpAccessChain %15 %11 %17
+         %25 = OpLoad %6 %24
+         %26 = OpInBoundsAccessChain %15 %11 %12
+         %27 = OpLoad %6 %26
+         %28 = OpIMul %6 %25 %27
+               OpStore %23 %28
+               OpStore %32 %35
+         %38 = OpAccessChain %36 %32 %17
+         %39 = OpLoad %29 %38
+         %40 = OpAccessChain %36 %32 %12
+         %41 = OpLoad %29 %40
+         %42 = OpFAdd %29 %39 %41
+               OpStore %37 %42
+         %44 = OpAccessChain %36 %32 %17
+         %45 = OpLoad %29 %44
+         %46 = OpAccessChain %36 %32 %12
+         %47 = OpLoad %29 %46
+         %48 = OpFMul %29 %45 %47
+               OpStore %43 %48
+               OpStore %52 %59
+         %62 = OpAccessChain %61 %52 %17
+         %63 = OpLoad %49 %62
+         %64 = OpAccessChain %61 %52 %12
+         %65 = OpLoad %49 %64
+         %66 = OpDot %29 %63 %65
+               OpStore %60 %66
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  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);
+
+  auto instructionDescriptor =
+      MakeInstructionDescriptor(18, SpvOpAccessChain, 0);
+  auto transformation =
+      TransformationToggleAccessChainInstruction(instructionDescriptor);
+  transformation.Apply(context.get(), &transformation_context);
+
+  instructionDescriptor =
+      MakeInstructionDescriptor(20, SpvOpInBoundsAccessChain, 0);
+  transformation =
+      TransformationToggleAccessChainInstruction(instructionDescriptor);
+  transformation.Apply(context.get(), &transformation_context);
+
+  instructionDescriptor = MakeInstructionDescriptor(24, SpvOpAccessChain, 0);
+  transformation =
+      TransformationToggleAccessChainInstruction(instructionDescriptor);
+  transformation.Apply(context.get(), &transformation_context);
+
+  instructionDescriptor =
+      MakeInstructionDescriptor(26, SpvOpInBoundsAccessChain, 0);
+  transformation =
+      TransformationToggleAccessChainInstruction(instructionDescriptor);
+  transformation.Apply(context.get(), &transformation_context);
+
+  instructionDescriptor = MakeInstructionDescriptor(38, SpvOpAccessChain, 0);
+  transformation =
+      TransformationToggleAccessChainInstruction(instructionDescriptor);
+  transformation.Apply(context.get(), &transformation_context);
+
+  std::string variantShader = 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 = OpTypeInt 32 1
+          %7 = OpTypeInt 32 0
+          %8 = OpConstant %7 2
+          %9 = OpTypeArray %6 %8
+         %10 = OpTypePointer Function %9
+         %12 = OpConstant %6 1
+         %13 = OpConstant %6 2
+         %14 = OpConstantComposite %9 %12 %13
+         %15 = OpTypePointer Function %6
+         %17 = OpConstant %6 0
+         %29 = OpTypeFloat 32
+         %30 = OpTypeArray %29 %8
+         %31 = OpTypePointer Function %30
+         %33 = OpConstant %29 1
+         %34 = OpConstant %29 2
+         %35 = OpConstantComposite %30 %33 %34
+         %36 = OpTypePointer Function %29
+         %49 = OpTypeVector %29 3
+         %50 = OpTypeArray %49 %8
+         %51 = OpTypePointer Function %50
+         %53 = OpConstant %29 3
+         %54 = OpConstantComposite %49 %33 %34 %53
+         %55 = OpConstant %29 4
+         %56 = OpConstant %29 5
+         %57 = OpConstant %29 6
+         %58 = OpConstantComposite %49 %55 %56 %57
+         %59 = OpConstantComposite %50 %54 %58
+         %61 = OpTypePointer Function %49
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %11 = OpVariable %10 Function
+         %16 = OpVariable %15 Function
+         %23 = OpVariable %15 Function
+         %32 = OpVariable %31 Function
+         %37 = OpVariable %36 Function
+         %43 = OpVariable %36 Function
+         %52 = OpVariable %51 Function
+         %60 = OpVariable %36 Function
+               OpStore %11 %14
+         %18 = OpInBoundsAccessChain %15 %11 %17
+         %19 = OpLoad %6 %18
+         %20 = OpAccessChain %15 %11 %12
+         %21 = OpLoad %6 %20
+         %22 = OpIAdd %6 %19 %21
+               OpStore %16 %22
+         %24 = OpInBoundsAccessChain %15 %11 %17
+         %25 = OpLoad %6 %24
+         %26 = OpAccessChain %15 %11 %12
+         %27 = OpLoad %6 %26
+         %28 = OpIMul %6 %25 %27
+               OpStore %23 %28
+               OpStore %32 %35
+         %38 = OpInBoundsAccessChain %36 %32 %17
+         %39 = OpLoad %29 %38
+         %40 = OpAccessChain %36 %32 %12
+         %41 = OpLoad %29 %40
+         %42 = OpFAdd %29 %39 %41
+               OpStore %37 %42
+         %44 = OpAccessChain %36 %32 %17
+         %45 = OpLoad %29 %44
+         %46 = OpAccessChain %36 %32 %12
+         %47 = OpLoad %29 %46
+         %48 = OpFMul %29 %45 %47
+               OpStore %43 %48
+               OpStore %52 %59
+         %62 = OpAccessChain %61 %52 %17
+         %63 = OpLoad %49 %62
+         %64 = OpAccessChain %61 %52 %12
+         %65 = OpLoad %49 %64
+         %66 = OpDot %29 %63 %65
+               OpStore %60 %66
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, variantShader, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/transformation_vector_shuffle_test.cpp b/test/fuzz/transformation_vector_shuffle_test.cpp
index 385c38b..e3c7fd4 100644
--- a/test/fuzz/transformation_vector_shuffle_test.cpp
+++ b/test/fuzz/transformation_vector_shuffle_test.cpp
@@ -86,249 +86,259 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(10, {}),
-                                  MakeDataDescriptor(12, {0}), context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(11, {}),
-                                  MakeDataDescriptor(12, {1}), context.get());
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(10, {}), MakeDataDescriptor(12, {0}), context.get());
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(11, {}), MakeDataDescriptor(12, {1}), context.get());
 
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(10, {}),
-                                  MakeDataDescriptor(16, {0}), context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(11, {}),
-                                  MakeDataDescriptor(16, {1}), context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(10, {}),
-                                  MakeDataDescriptor(16, {2}), context.get());
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(10, {}), MakeDataDescriptor(16, {0}), context.get());
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(11, {}), MakeDataDescriptor(16, {1}), context.get());
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(10, {}), MakeDataDescriptor(16, {2}), context.get());
 
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(10, {}),
-                                  MakeDataDescriptor(20, {0}), context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(11, {}),
-                                  MakeDataDescriptor(20, {1}), context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(10, {}),
-                                  MakeDataDescriptor(20, {2}), context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(11, {}),
-                                  MakeDataDescriptor(20, {3}), context.get());
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(10, {}), MakeDataDescriptor(20, {0}), context.get());
+  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());
 
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(25, {}),
-                                  MakeDataDescriptor(27, {0}), context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(26, {}),
-                                  MakeDataDescriptor(27, {1}), context.get());
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(25, {}), MakeDataDescriptor(27, {0}), context.get());
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(26, {}), MakeDataDescriptor(27, {1}), context.get());
 
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(25, {}),
-                                  MakeDataDescriptor(31, {0}), context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(26, {}),
-                                  MakeDataDescriptor(31, {1}), context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(25, {}),
-                                  MakeDataDescriptor(31, {2}), context.get());
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(25, {}), MakeDataDescriptor(31, {0}), context.get());
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(26, {}), MakeDataDescriptor(31, {1}), context.get());
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(25, {}), MakeDataDescriptor(31, {2}), context.get());
 
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(25, {}),
-                                  MakeDataDescriptor(35, {0}), context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(26, {}),
-                                  MakeDataDescriptor(35, {1}), context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(25, {}),
-                                  MakeDataDescriptor(35, {2}), context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(26, {}),
-                                  MakeDataDescriptor(35, {3}), context.get());
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(25, {}), MakeDataDescriptor(35, {0}), context.get());
+  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());
 
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(40, {}),
-                                  MakeDataDescriptor(42, {0}), context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(41, {}),
-                                  MakeDataDescriptor(42, {1}), context.get());
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(40, {}), MakeDataDescriptor(42, {0}), context.get());
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(41, {}), MakeDataDescriptor(42, {1}), context.get());
 
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(40, {}),
-                                  MakeDataDescriptor(46, {0}), context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(41, {}),
-                                  MakeDataDescriptor(46, {1}), context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(40, {}),
-                                  MakeDataDescriptor(46, {2}), context.get());
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(40, {}), MakeDataDescriptor(46, {0}), context.get());
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(41, {}), MakeDataDescriptor(46, {1}), context.get());
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(40, {}), MakeDataDescriptor(46, {2}), context.get());
 
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(40, {}),
-                                  MakeDataDescriptor(50, {0}), context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(41, {}),
-                                  MakeDataDescriptor(50, {1}), context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(40, {}),
-                                  MakeDataDescriptor(50, {2}), context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(41, {}),
-                                  MakeDataDescriptor(50, {3}), context.get());
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(40, {}), MakeDataDescriptor(50, {0}), context.get());
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(41, {}), MakeDataDescriptor(50, {1}), context.get());
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(40, {}), MakeDataDescriptor(50, {2}), context.get());
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(41, {}), MakeDataDescriptor(50, {3}), context.get());
 
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(55, {}),
-                                  MakeDataDescriptor(61, {0}), context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(56, {}),
-                                  MakeDataDescriptor(61, {1}), context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(55, {}),
-                                  MakeDataDescriptor(61, {2}), context.get());
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(55, {}), MakeDataDescriptor(61, {0}), context.get());
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(56, {}), MakeDataDescriptor(61, {1}), context.get());
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(55, {}), MakeDataDescriptor(61, {2}), context.get());
 
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(55, {}),
-                                  MakeDataDescriptor(65, {0}), context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(56, {}),
-                                  MakeDataDescriptor(65, {1}), context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(55, {}),
-                                  MakeDataDescriptor(65, {2}), context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(56, {}),
-                                  MakeDataDescriptor(65, {3}), context.get());
+  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());
 
   // %103 does not dominate the return instruction.
   ASSERT_FALSE(TransformationVectorShuffle(
                    MakeInstructionDescriptor(100, SpvOpReturn, 0), 200, 103, 65,
                    {3, 5, 7})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // Illegal to shuffle a bvec2 and a vec3
   ASSERT_FALSE(TransformationVectorShuffle(
                    MakeInstructionDescriptor(100, SpvOpReturn, 0), 200, 112, 61,
                    {0, 2, 4})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // Illegal to shuffle an ivec2 and a uvec4
   ASSERT_FALSE(TransformationVectorShuffle(
                    MakeInstructionDescriptor(100, SpvOpReturn, 0), 200, 27, 50,
                    {1, 3, 5})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // Vector 1 does not exist
   ASSERT_FALSE(TransformationVectorShuffle(
                    MakeInstructionDescriptor(100, SpvOpReturn, 0), 200, 300, 50,
                    {1, 3, 5})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // Vector 2 does not exist
   ASSERT_FALSE(TransformationVectorShuffle(
                    MakeInstructionDescriptor(100, SpvOpReturn, 0), 200, 27, 300,
                    {1, 3, 5})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // Index out of range
   ASSERT_FALSE(
       TransformationVectorShuffle(
           MakeInstructionDescriptor(100, SpvOpReturn, 0), 200, 12, 112, {0, 20})
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
 
   // Too many indices
   ASSERT_FALSE(TransformationVectorShuffle(
                    MakeInstructionDescriptor(100, SpvOpReturn, 0), 200, 12, 112,
                    {0, 1, 0, 1, 0, 1, 0, 1})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // Too few indices
   ASSERT_FALSE(
       TransformationVectorShuffle(
           MakeInstructionDescriptor(100, SpvOpReturn, 0), 200, 12, 112, {})
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
 
   // Too few indices again
   ASSERT_FALSE(
       TransformationVectorShuffle(
           MakeInstructionDescriptor(100, SpvOpReturn, 0), 200, 12, 112, {0})
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
 
   // Indices define unknown type: we do not have vec2
   ASSERT_FALSE(
       TransformationVectorShuffle(
           MakeInstructionDescriptor(100, SpvOpReturn, 0), 200, 65, 65, {0, 1})
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
 
   // The instruction to insert before does not exist
   ASSERT_FALSE(TransformationVectorShuffle(
                    MakeInstructionDescriptor(100, SpvOpCompositeConstruct, 1),
                    201, 20, 12, {0xFFFFFFFF, 3, 5})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // The 'fresh' id is already in use
   ASSERT_FALSE(
       TransformationVectorShuffle(
           MakeInstructionDescriptor(100, SpvOpReturn, 0), 12, 12, 112, {})
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
 
   protobufs::DataDescriptor temp_dd;
 
   TransformationVectorShuffle transformation1(
       MakeInstructionDescriptor(100, SpvOpReturn, 0), 200, 12, 112, {1, 0});
-  ASSERT_TRUE(transformation1.IsApplicable(context.get(), fact_manager));
-  transformation1.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation1.IsApplicable(context.get(), transformation_context));
+  transformation1.Apply(context.get(), &transformation_context);
   temp_dd = MakeDataDescriptor(200, {0});
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(11, {}), temp_dd,
-                                        context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(11, {}), temp_dd, context.get()));
   temp_dd = MakeDataDescriptor(200, {1});
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(10, {}), temp_dd,
-                                        context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(10, {}), temp_dd, context.get()));
 
   TransformationVectorShuffle transformation2(
       MakeInstructionDescriptor(100, SpvOpReturn, 0), 201, 20, 12,
       {0xFFFFFFFF, 3, 5});
-  ASSERT_TRUE(transformation2.IsApplicable(context.get(), fact_manager));
-  transformation2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation2.IsApplicable(context.get(), transformation_context));
+  transformation2.Apply(context.get(), &transformation_context);
   temp_dd = MakeDataDescriptor(201, {1});
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(11, {}), temp_dd,
-                                        context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(11, {}), temp_dd, context.get()));
   temp_dd = MakeDataDescriptor(201, {2});
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(11, {}), temp_dd,
-                                        context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(11, {}), temp_dd, context.get()));
 
   TransformationVectorShuffle transformation3(
       MakeInstructionDescriptor(100, SpvOpReturn, 0), 202, 27, 35, {5, 4, 1});
-  ASSERT_TRUE(transformation3.IsApplicable(context.get(), fact_manager));
-  transformation3.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation3.IsApplicable(context.get(), transformation_context));
+  transformation3.Apply(context.get(), &transformation_context);
   temp_dd = MakeDataDescriptor(202, {0});
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(26, {}), temp_dd,
-                                        context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(26, {}), temp_dd, context.get()));
   temp_dd = MakeDataDescriptor(202, {1});
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(25, {}), temp_dd,
-                                        context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(25, {}), temp_dd, context.get()));
   temp_dd = MakeDataDescriptor(202, {2});
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(26, {}), temp_dd,
-                                        context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(26, {}), temp_dd, context.get()));
 
   TransformationVectorShuffle transformation4(
       MakeInstructionDescriptor(100, SpvOpReturn, 0), 203, 42, 46, {0, 1});
-  ASSERT_TRUE(transformation4.IsApplicable(context.get(), fact_manager));
-  transformation4.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation4.IsApplicable(context.get(), transformation_context));
+  transformation4.Apply(context.get(), &transformation_context);
   temp_dd = MakeDataDescriptor(203, {0});
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {}), temp_dd,
-                                        context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(40, {}), temp_dd, context.get()));
   temp_dd = MakeDataDescriptor(203, {1});
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(41, {}), temp_dd,
-                                        context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(41, {}), temp_dd, context.get()));
 
   TransformationVectorShuffle transformation5(
       MakeInstructionDescriptor(100, SpvOpReturn, 0), 204, 42, 46, {2, 3, 4});
-  ASSERT_TRUE(transformation5.IsApplicable(context.get(), fact_manager));
-  transformation5.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation5.IsApplicable(context.get(), transformation_context));
+  transformation5.Apply(context.get(), &transformation_context);
   temp_dd = MakeDataDescriptor(204, {0});
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {}), temp_dd,
-                                        context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(40, {}), temp_dd, context.get()));
   temp_dd = MakeDataDescriptor(204, {1});
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(41, {}), temp_dd,
-                                        context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(41, {}), temp_dd, context.get()));
   temp_dd = MakeDataDescriptor(204, {2});
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {}), temp_dd,
-                                        context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(40, {}), temp_dd, context.get()));
 
   TransformationVectorShuffle transformation6(
       MakeInstructionDescriptor(100, SpvOpReturn, 0), 205, 42, 42,
       {0, 1, 2, 3});
-  ASSERT_TRUE(transformation6.IsApplicable(context.get(), fact_manager));
-  transformation6.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation6.IsApplicable(context.get(), transformation_context));
+  transformation6.Apply(context.get(), &transformation_context);
   temp_dd = MakeDataDescriptor(205, {0});
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {}), temp_dd,
-                                        context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(40, {}), temp_dd, context.get()));
   temp_dd = MakeDataDescriptor(205, {1});
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(41, {}), temp_dd,
-                                        context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(41, {}), temp_dd, context.get()));
   temp_dd = MakeDataDescriptor(205, {2});
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {}), temp_dd,
-                                        context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(40, {}), temp_dd, context.get()));
   temp_dd = MakeDataDescriptor(205, {3});
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(41, {}), temp_dd,
-                                        context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(41, {}), temp_dd, context.get()));
 
   // swizzle vec4 from vec4 and vec4 using some undefs
   TransformationVectorShuffle transformation7(
       MakeInstructionDescriptor(100, SpvOpReturn, 0), 206, 65, 65,
       {0xFFFFFFFF, 3, 6, 0xFFFFFFFF});
-  ASSERT_TRUE(transformation7.IsApplicable(context.get(), fact_manager));
-  transformation7.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(
+      transformation7.IsApplicable(context.get(), transformation_context));
+  transformation7.Apply(context.get(), &transformation_context);
   temp_dd = MakeDataDescriptor(206, {1});
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(56, {}), temp_dd,
-                                        context.get()));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(56, {}), temp_dd, context.get()));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -479,52 +489,55 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
 
   // Cannot insert before the OpVariables of a function.
   ASSERT_FALSE(
       TransformationVectorShuffle(
           MakeInstructionDescriptor(101, SpvOpVariable, 0), 200, 14, 14, {0, 1})
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       TransformationVectorShuffle(
           MakeInstructionDescriptor(101, SpvOpVariable, 1), 200, 14, 14, {1, 2})
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       TransformationVectorShuffle(
           MakeInstructionDescriptor(102, SpvOpVariable, 0), 200, 14, 14, {1, 2})
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   // OK to insert right after the OpVariables.
   ASSERT_FALSE(
       TransformationVectorShuffle(
           MakeInstructionDescriptor(102, SpvOpBranch, 1), 200, 14, 14, {1, 1})
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
 
   // Cannot insert before the OpPhis of a block.
   ASSERT_FALSE(
       TransformationVectorShuffle(MakeInstructionDescriptor(60, SpvOpPhi, 0),
                                   200, 14, 14, {2, 0})
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       TransformationVectorShuffle(MakeInstructionDescriptor(59, SpvOpPhi, 0),
                                   200, 14, 14, {3, 0})
-          .IsApplicable(context.get(), fact_manager));
+          .IsApplicable(context.get(), transformation_context));
   // OK to insert after the OpPhis.
   ASSERT_TRUE(TransformationVectorShuffle(
                   MakeInstructionDescriptor(59, SpvOpAccessChain, 0), 200, 14,
                   14, {3, 4})
-                  .IsApplicable(context.get(), fact_manager));
+                  .IsApplicable(context.get(), transformation_context));
 
   // Cannot insert before OpLoopMerge
   ASSERT_FALSE(TransformationVectorShuffle(
                    MakeInstructionDescriptor(33, SpvOpBranchConditional, 0),
                    200, 14, 14, {3})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 
   // Cannot insert before OpSelectionMerge
   ASSERT_FALSE(TransformationVectorShuffle(
                    MakeInstructionDescriptor(21, SpvOpBranchConditional, 0),
                    200, 14, 14, {2})
-                   .IsApplicable(context.get(), fact_manager));
+                   .IsApplicable(context.get(), transformation_context));
 }
 
 }  // namespace
diff --git a/test/operand_capabilities_test.cpp b/test/operand_capabilities_test.cpp
index 1195597..addb08a 100644
--- a/test/operand_capabilities_test.cpp
+++ b/test/operand_capabilities_test.cpp
@@ -82,6 +82,13 @@
       SpvCapability##CAP1, SpvCapability##CAP2, SpvCapability##CAP3 \
     }                                                               \
   }
+#define CASE4(TYPE, VALUE, CAP1, CAP2, CAP3, CAP4)                   \
+  {                                                                  \
+    SPV_OPERAND_TYPE_##TYPE, uint32_t(Spv##VALUE), CapabilitySet {   \
+      SpvCapability##CAP1, SpvCapability##CAP2, SpvCapability##CAP3, \
+          SpvCapability##CAP4                                        \
+    }                                                                \
+  }
 #define CASE5(TYPE, VALUE, CAP1, CAP2, CAP3, CAP4, CAP5)             \
   {                                                                  \
     SPV_OPERAND_TYPE_##TYPE, uint32_t(Spv##VALUE), CapabilitySet {   \
@@ -491,8 +498,8 @@
             CASE1(BUILT_IN, BuiltInCullDistance, CullDistance),  // Bug 1407, 15234
             CASE1(BUILT_IN, BuiltInVertexId, Shader),
             CASE1(BUILT_IN, BuiltInInstanceId, Shader),
-            CASE3(BUILT_IN, BuiltInPrimitiveId, Geometry, Tessellation,
-                  RayTracingNV),
+            CASE4(BUILT_IN, BuiltInPrimitiveId, Geometry, Tessellation,
+                  RayTracingNV, RayTracingProvisionalKHR),
             CASE2(BUILT_IN, BuiltInInvocationId, Geometry, Tessellation),
             CASE2(BUILT_IN, BuiltInLayer, Geometry, ShaderViewportIndexLayerEXT),
             CASE2(BUILT_IN, BuiltInViewportIndex, MultiViewport, ShaderViewportIndexLayerEXT),  // Bug 15234
diff --git a/test/opt/CMakeLists.txt b/test/opt/CMakeLists.txt
index 327f265..3954338 100644
--- a/test/opt/CMakeLists.txt
+++ b/test/opt/CMakeLists.txt
@@ -55,6 +55,7 @@
        insert_extract_elim_test.cpp
        inst_bindless_check_test.cpp
        inst_buff_addr_check_test.cpp
+       inst_debug_printf_test.cpp
        instruction_list_test.cpp
        instruction_test.cpp
        ir_builder.cpp
diff --git a/test/opt/amd_ext_to_khr.cpp b/test/opt/amd_ext_to_khr.cpp
index d943d34..3340e89 100644
--- a/test/opt/amd_ext_to_khr.cpp
+++ b/test/opt/amd_ext_to_khr.cpp
@@ -15,7 +15,6 @@
 #include <vector>
 
 #include "gmock/gmock.h"
-
 #include "test/opt/pass_fixture.h"
 #include "test/opt/pass_utils.h"
 
@@ -913,6 +912,42 @@
   EXPECT_THAT(output, HasSubstr("Version: 1.4"));
 }
 
+TEST_F(AmdExtToKhrTest, TimeAMD) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpCapability Int64
+               OpExtension "SPV_AMD_gcn_shader"
+; CHECK-NOT: OpExtension "SPV_AMD_gcn_shader"
+; CHECK: OpExtension "SPV_KHR_shader_clock"
+          %1 = OpExtInstImport "GLSL.std.450"
+          %2 = OpExtInstImport "SPV_AMD_gcn_shader"
+; CHECK-NOT: OpExtInstImport "SPV_AMD_gcn_shader"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main"
+               OpExecutionMode %main OriginUpperLeft
+               OpSource GLSL 450
+               OpSourceExtension "GL_AMD_gcn_shader"
+               OpSourceExtension "GL_ARB_gpu_shader_int64"
+               OpName %main "main"
+               OpName %time "time"
+       %void = OpTypeVoid
+          %6 = OpTypeFunction %void
+      %ulong = OpTypeInt 64 0
+%_ptr_Function_ulong = OpTypePointer Function %ulong
+       %main = OpFunction %void None %6
+          %9 = OpLabel
+       %time = OpVariable %_ptr_Function_ulong Function
+; CHECK: [[uint:%\w+]] = OpTypeInt 32 0
+; CHECK: [[uint_3:%\w+]] = OpConstant [[uint]] 3
+         %10 = OpExtInst %ulong %2 TimeAMD
+; CHECK: %10 = OpReadClockKHR %ulong [[uint_3]]
+               OpStore %time %10
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<AmdExtensionToKhrPass>(text, true);
+}
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/dead_branch_elim_test.cpp b/test/opt/dead_branch_elim_test.cpp
index e612867..3dcc0f7 100644
--- a/test/opt/dead_branch_elim_test.cpp
+++ b/test/opt/dead_branch_elim_test.cpp
@@ -1378,6 +1378,7 @@
 
   SinglePassRunAndMatch<DeadBranchElimPass>(text, true);
 }
+
 TEST_F(DeadBranchElimTest, LeaveContinueBackedgeExtraBlock) {
   const std::string text = R"(
 ; CHECK: OpBranch [[header:%\w+]]
@@ -3161,6 +3162,73 @@
   SinglePassRunAndCheck<DeadBranchElimPass>(before, after, true, true);
 }
 
+TEST_F(DeadBranchElimTest, BreakInNestedHeaderWithSingleCase) {
+  const std::string text = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%bool = OpTypeBool
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%8 = OpUndef %bool
+%main = OpFunction %void None %4
+%9 = OpLabel
+OpSelectionMerge %10 None
+OpSwitch %uint_0 %11
+%11 = OpLabel
+OpSelectionMerge %12 None
+OpBranchConditional %8 %10 %12
+%12 = OpLabel
+OpBranch %10
+%10 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<DeadBranchElimPass>(text, text, true, true);
+}
+
+TEST_F(DeadBranchElimTest, BreakInNestedHeaderWithTwoCases) {
+  const std::string text = R"(
+; CHECK: OpSelectionMerge [[merge:%\w+]] None
+; CHECK-NEXT: OpSwitch %uint_0 [[bb:%\w+\n]]
+OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%bool = OpTypeBool
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%8 = OpUndef %bool
+%main = OpFunction %void None %4
+%9 = OpLabel
+OpSelectionMerge %10 None
+OpSwitch %uint_0 %11 1 %12
+%11 = OpLabel
+OpSelectionMerge %13 None
+OpBranchConditional %8 %10 %13
+%13 = OpLabel
+OpBranch %10
+%12 = OpLabel
+OpBranch %10
+%10 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<DeadBranchElimPass>(text, true);
+}
+
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 //
 //    More complex control flow
diff --git a/test/opt/eliminate_dead_member_test.cpp b/test/opt/eliminate_dead_member_test.cpp
index b6925d7..a9b0f28 100644
--- a/test/opt/eliminate_dead_member_test.cpp
+++ b/test/opt/eliminate_dead_member_test.cpp
@@ -1085,4 +1085,103 @@
   EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result));
 }
 
+TEST_F(EliminateDeadMemberTest, UpdateSpecConstOpExtract) {
+  // Test that an extract in an OpSpecConstantOp is correctly updated.
+  const std::string text = R"(
+; CHECK: OpName
+; CHECK-NEXT: OpMemberName %type__Globals 0 "y"
+; CHECK-NOT: OpMemberName
+; CHECK: OpDecorate [[spec_const:%\w+]] SpecId 1
+; CHECK: OpMemberDecorate %type__Globals 0 Offset 4
+; CHECK: %type__Globals = OpTypeStruct %uint
+; CHECK: [[struct:%\w+]] = OpSpecConstantComposite %type__Globals [[spec_const]]
+; CHECK: OpSpecConstantOp %uint CompositeExtract [[struct]] 0
+               OpCapability Shader
+               OpCapability Addresses
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %main "main"
+               OpDecorate %c_0 SpecId 0
+               OpDecorate %c_1 SpecId 1
+               OpDecorate %c_2 SpecId 2
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 4
+               OpMemberDecorate %type__Globals 2 Offset 16
+       %uint = OpTypeInt 32 0
+        %c_0 = OpSpecConstant %uint 0
+        %c_1 = OpSpecConstant %uint 1
+        %c_2 = OpSpecConstant %uint 2
+     %uint_0 = OpConstant %uint 0
+     %uint_1 = OpConstant %uint 1
+     %uint_2 = OpConstant %uint 2
+     %uint_3 = OpConstant %uint 3
+%type__Globals = OpTypeStruct %uint %uint %uint
+%spec_const_global = OpSpecConstantComposite %type__Globals %c_0 %c_1 %c_2
+%extract = OpSpecConstantOp %uint CompositeExtract %spec_const_global 1
+       %void = OpTypeVoid
+         %14 = OpTypeFunction %void
+       %main = OpFunction %void None %14
+         %16 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::EliminateDeadMembersPass>(text, true);
+}
+
+TEST_F(EliminateDeadMemberTest, UpdateSpecConstOpInsert) {
+  // Test that an insert in an OpSpecConstantOp is correctly updated.
+  const std::string text = R"(
+; CHECK: OpName
+; CHECK-NEXT: OpMemberName %type__Globals 0 "y"
+; CHECK-NOT: OpMemberName
+; CHECK: OpDecorate [[spec_const:%\w+]] SpecId 1
+; CHECK: OpMemberDecorate %type__Globals 0 Offset 4
+; CHECK: %type__Globals = OpTypeStruct %uint
+; CHECK: [[struct:%\w+]] = OpSpecConstantComposite %type__Globals [[spec_const]]
+; CHECK: OpSpecConstantOp %type__Globals CompositeInsert %uint_3 [[struct]] 0
+               OpCapability Shader
+               OpCapability Addresses
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %main "main"
+               OpDecorate %c_0 SpecId 0
+               OpDecorate %c_1 SpecId 1
+               OpDecorate %c_2 SpecId 2
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 4
+               OpMemberDecorate %type__Globals 2 Offset 16
+       %uint = OpTypeInt 32 0
+        %c_0 = OpSpecConstant %uint 0
+        %c_1 = OpSpecConstant %uint 1
+        %c_2 = OpSpecConstant %uint 2
+     %uint_0 = OpConstant %uint 0
+     %uint_1 = OpConstant %uint 1
+     %uint_2 = OpConstant %uint 2
+     %uint_3 = OpConstant %uint 3
+%type__Globals = OpTypeStruct %uint %uint %uint
+%spec_const_global = OpSpecConstantComposite %type__Globals %c_0 %c_1 %c_2
+%insert = OpSpecConstantOp %type__Globals CompositeInsert %uint_3 %spec_const_global 1
+%extract = OpSpecConstantOp %uint CompositeExtract %insert 1
+       %void = OpTypeVoid
+         %14 = OpTypeFunction %void
+       %main = OpFunction %void None %14
+         %16 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::EliminateDeadMembersPass>(text, true);
+}
+
 }  // namespace
diff --git a/test/opt/fold_test.cpp b/test/opt/fold_test.cpp
index 26d1220..db01924 100644
--- a/test/opt/fold_test.cpp
+++ b/test/opt/fold_test.cpp
@@ -1693,7 +1693,7 @@
             "OpReturn\n" +
             "OpFunctionEnd",
         2, 0.2f),
-    // Test case 21: FMax 1.0 4.0
+    // Test case 23: FMax 1.0 4.0
     InstructionFoldingCase<float>(
         Header() + "%main = OpFunction %void None %void_func\n" +
             "%main_lab = OpLabel\n" +
@@ -1701,7 +1701,7 @@
             "OpReturn\n" +
             "OpFunctionEnd",
         2, 4.0f),
-    // Test case 22: FMax 1.0 0.2
+    // Test case 24: FMax 1.0 0.2
     InstructionFoldingCase<float>(
         Header() + "%main = OpFunction %void None %void_func\n" +
             "%main_lab = OpLabel\n" +
@@ -1709,7 +1709,7 @@
             "OpReturn\n" +
             "OpFunctionEnd",
         2, 1.0f),
-    // Test case 23: FClamp 1.0 0.2 4.0
+    // Test case 25: FClamp 1.0 0.2 4.0
     InstructionFoldingCase<float>(
         Header() + "%main = OpFunction %void None %void_func\n" +
             "%main_lab = OpLabel\n" +
@@ -1717,7 +1717,7 @@
             "OpReturn\n" +
             "OpFunctionEnd",
         2, 1.0f),
-    // Test case 24: FClamp 0.2 2.0 4.0
+    // Test case 26: FClamp 0.2 2.0 4.0
     InstructionFoldingCase<float>(
         Header() + "%main = OpFunction %void None %void_func\n" +
             "%main_lab = OpLabel\n" +
@@ -1725,7 +1725,7 @@
             "OpReturn\n" +
             "OpFunctionEnd",
         2, 2.0f),
-    // Test case 25: FClamp 2049.0 2.0 4.0
+    // Test case 27: FClamp 2049.0 2.0 4.0
     InstructionFoldingCase<float>(
         Header() + "%main = OpFunction %void None %void_func\n" +
             "%main_lab = OpLabel\n" +
@@ -1733,7 +1733,7 @@
             "OpReturn\n" +
             "OpFunctionEnd",
         2, 4.0f),
-    // Test case 26: FClamp 1.0 2.0 x
+    // Test case 28: FClamp 1.0 2.0 x
     InstructionFoldingCase<float>(
         Header() + "%main = OpFunction %void None %void_func\n" +
             "%main_lab = OpLabel\n" +
@@ -1742,7 +1742,7 @@
             "OpReturn\n" +
             "OpFunctionEnd",
         2, 2.0),
-    // Test case 27: FClamp 1.0 x 0.5
+    // Test case 29: FClamp 1.0 x 0.5
     InstructionFoldingCase<float>(
         Header() + "%main = OpFunction %void None %void_func\n" +
             "%main_lab = OpLabel\n" +
@@ -1750,7 +1750,111 @@
             "%2 = OpExtInst %float %1 FClamp %float_1 %undef %float_0p5\n" +
             "OpReturn\n" +
             "OpFunctionEnd",
-        2, 0.5)
+        2, 0.5),
+    // Test case 30: Sin 0.0
+    InstructionFoldingCase<float>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpExtInst %float %1 Sin %float_0\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        2, 0.0),
+    // Test case 31: Cos 0.0
+    InstructionFoldingCase<float>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpExtInst %float %1 Cos %float_0\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        2, 1.0),
+    // Test case 32: Tan 0.0
+    InstructionFoldingCase<float>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpExtInst %float %1 Tan %float_0\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        2, 0.0),
+    // Test case 33: Asin 0.0
+    InstructionFoldingCase<float>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpExtInst %float %1 Asin %float_0\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        2, 0.0),
+    // Test case 34: Acos 1.0
+    InstructionFoldingCase<float>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpExtInst %float %1 Acos %float_1\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        2, 0.0),
+    // Test case 35: Atan 0.0
+    InstructionFoldingCase<float>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpExtInst %float %1 Atan %float_0\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        2, 0.0),
+    // Test case 36: Exp 0.0
+    InstructionFoldingCase<float>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpExtInst %float %1 Exp %float_0\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        2, 1.0),
+    // Test case 37: Log 1.0
+    InstructionFoldingCase<float>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpExtInst %float %1 Log %float_1\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        2, 0.0),
+    // Test case 38: Exp2 2.0
+    InstructionFoldingCase<float>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpExtInst %float %1 Exp2 %float_2\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        2, 4.0),
+    // Test case 39: Log2 4.0
+    InstructionFoldingCase<float>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpExtInst %float %1 Log2 %float_4\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        2, 2.0),
+    // Test case 40: Sqrt 4.0
+    InstructionFoldingCase<float>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpExtInst %float %1 Sqrt %float_4\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        2, 2.0),
+    // Test case 41: Atan2 0.0 1.0
+    InstructionFoldingCase<float>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpExtInst %float %1 Atan2 %float_0 %float_1\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        2, 0.0),
+    // Test case 42: Pow 2.0 3.0
+    InstructionFoldingCase<float>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpExtInst %float %1 Pow %float_2 %float_3\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        2, 8.0)
 ));
 // clang-format on
 
@@ -1967,7 +2071,25 @@
                 "%2 = OpExtInst %double %1 FClamp %double_1 %undef %double_0p5\n" +
                 "OpReturn\n" +
                 "OpFunctionEnd",
-            2, 0.5)
+            2, 0.5),
+        // Test case 21: Sqrt 4.0
+        InstructionFoldingCase<double>(
+            Header() + "%main = OpFunction %void None %void_func\n" +
+                "%main_lab = OpLabel\n" +
+                "%undef = OpUndef %double\n" +
+                "%2 = OpExtInst %double %1 Sqrt %double_4\n" +
+                "OpReturn\n" +
+                "OpFunctionEnd",
+            2, 2.0),
+        // Test case 22: Pow 2.0 3.0
+        InstructionFoldingCase<double>(
+            Header() + "%main = OpFunction %void None %void_func\n" +
+                "%main_lab = OpLabel\n" +
+                "%undef = OpUndef %double\n" +
+                "%2 = OpExtInst %double %1 Pow %double_2 %double_3\n" +
+                "OpReturn\n" +
+                "OpFunctionEnd",
+            2, 8.0)
 ));
 // clang-format on
 
diff --git a/test/opt/inst_debug_printf_test.cpp b/test/opt/inst_debug_printf_test.cpp
new file mode 100644
index 0000000..8123ffb
--- /dev/null
+++ b/test/opt/inst_debug_printf_test.cpp
@@ -0,0 +1,215 @@
+// Copyright (c) 2020 Valve Corporation
+// Copyright (c) 2020 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.
+
+// Debug Printf Instrumentation Tests.
+
+#include <string>
+#include <vector>
+
+#include "test/opt/assembly_builder.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using InstDebugPrintfTest = PassTest<::testing::Test>;
+
+TEST_F(InstDebugPrintfTest, V4Float32) {
+  // SamplerState g_sDefault;
+  // Texture2D g_tColor;
+  //
+  // struct PS_INPUT
+  // {
+  //   float2 vBaseTexCoord : TEXCOORD0;
+  // };
+  //
+  // struct PS_OUTPUT
+  // {
+  //   float4 vDiffuse : SV_Target0;
+  // };
+  //
+  // PS_OUTPUT MainPs(PS_INPUT i)
+  // {
+  //   PS_OUTPUT o;
+  //
+  //   o.vDiffuse.rgba = g_tColor.Sample(g_sDefault, (i.vBaseTexCoord.xy).xy);
+  //   debugPrintfEXT("diffuse: %v4f", o.vDiffuse.rgba);
+  //   return o;
+  // }
+
+  const std::string defs =
+      R"(OpCapability Shader
+OpExtension "SPV_KHR_non_semantic_info"
+%1 = OpExtInstImport "NonSemantic.DebugPrintf"
+; CHECK-NOT: OpExtension "SPV_KHR_non_semantic_info"
+; CHECK-NOT: %1 = OpExtInstImport "NonSemantic.DebugPrintf"
+; CHECK: OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "MainPs" %3 %4
+; CHECK: OpEntryPoint Fragment %2 "MainPs" %3 %4 %gl_FragCoord
+OpExecutionMode %2 OriginUpperLeft
+%5 = OpString "Color is %vn"
+)";
+
+  const std::string decorates =
+      R"(OpDecorate %6 DescriptorSet 0
+OpDecorate %6 Binding 1
+OpDecorate %7 DescriptorSet 0
+OpDecorate %7 Binding 0
+OpDecorate %3 Location 0
+OpDecorate %4 Location 0
+; CHECK: OpDecorate %_runtimearr_uint ArrayStride 4
+; CHECK: OpDecorate %_struct_47 Block
+; CHECK: OpMemberDecorate %_struct_47 0 Offset 0
+; CHECK: OpMemberDecorate %_struct_47 1 Offset 4
+; CHECK: OpDecorate %49 DescriptorSet 7
+; CHECK: OpDecorate %49 Binding 3
+; CHECK: OpDecorate %gl_FragCoord BuiltIn FragCoord
+)";
+
+  const std::string globals =
+      R"(%void = OpTypeVoid
+%9 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%13 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%_ptr_UniformConstant_13 = OpTypePointer UniformConstant %13
+%6 = OpVariable %_ptr_UniformConstant_13 UniformConstant
+%15 = OpTypeSampler
+%_ptr_UniformConstant_15 = OpTypePointer UniformConstant %15
+%7 = OpVariable %_ptr_UniformConstant_15 UniformConstant
+%17 = OpTypeSampledImage %13
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%3 = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%4 = OpVariable %_ptr_Output_v4float Output
+; CHECK: %uint = OpTypeInt 32 0
+; CHECK: %38 = OpTypeFunction %void %uint %uint %uint %uint %uint %uint
+; CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
+; CHECK: %_struct_47 = OpTypeStruct %uint %_runtimearr_uint
+; CHECK: %_ptr_StorageBuffer__struct_47 = OpTypePointer StorageBuffer %_struct_47
+; CHECK: %49 = OpVariable %_ptr_StorageBuffer__struct_47 StorageBuffer
+; CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+; CHECK: %bool = OpTypeBool
+; CHECK: %_ptr_Input_v4float = OpTypePointer Input %v4float
+; CHECK: %gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+; CHECK: %v4uint = OpTypeVector %uint 4
+)";
+
+  const std::string main =
+      R"(%2 = OpFunction %void None %9
+%20 = OpLabel
+%21 = OpLoad %v2float %3
+%22 = OpLoad %13 %6
+%23 = OpLoad %15 %7
+%24 = OpSampledImage %17 %22 %23
+%25 = OpImageSampleImplicitLod %v4float %24 %21
+%26 = OpExtInst %void %1 1 %5 %25
+; CHECK-NOT: %26 = OpExtInst %void %1 1 %5 %25
+; CHECK: %29 = OpCompositeExtract %float %25 0
+; CHECK: %30 = OpBitcast %uint %29
+; CHECK: %31 = OpCompositeExtract %float %25 1
+; CHECK: %32 = OpBitcast %uint %31
+; CHECK: %33 = OpCompositeExtract %float %25 2
+; CHECK: %34 = OpBitcast %uint %33
+; CHECK: %35 = OpCompositeExtract %float %25 3
+; CHECK: %36 = OpBitcast %uint %35
+; CHECK: %101 = OpFunctionCall %void %37 %uint_36 %uint_5 %30 %32 %34 %36
+; CHECK: OpBranch %102
+; CHECK: %102 = OpLabel
+OpStore %4 %25
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string output_func =
+      R"(; CHECK: %37 = OpFunction %void None %38
+; CHECK: %39 = OpFunctionParameter %uint
+; CHECK: %40 = OpFunctionParameter %uint
+; CHECK: %41 = OpFunctionParameter %uint
+; CHECK: %42 = OpFunctionParameter %uint
+; CHECK: %43 = OpFunctionParameter %uint
+; CHECK: %44 = OpFunctionParameter %uint
+; CHECK: %45 = OpLabel
+; CHECK: %52 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_0
+; CHECK: %55 = OpAtomicIAdd %uint %52 %uint_4 %uint_0 %uint_12
+; CHECK: %56 = OpIAdd %uint %55 %uint_12
+; CHECK: %57 = OpArrayLength %uint %49 1
+; CHECK: %59 = OpULessThanEqual %bool %56 %57
+; CHECK: OpSelectionMerge %60 None
+; CHECK: OpBranchConditional %59 %61 %60
+; CHECK: %61 = OpLabel
+; CHECK: %62 = OpIAdd %uint %55 %uint_0
+; CHECK: %64 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %62
+; CHECK: OpStore %64 %uint_12
+; CHECK: %66 = OpIAdd %uint %55 %uint_1
+; CHECK: %67 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %66
+; CHECK: OpStore %67 %uint_23
+; CHECK: %69 = OpIAdd %uint %55 %uint_2
+; CHECK: %70 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %69
+; CHECK: OpStore %70 %39
+; CHECK: %72 = OpIAdd %uint %55 %uint_3
+; CHECK: %73 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %72
+; CHECK: OpStore %73 %uint_4
+; CHECK: %76 = OpLoad %v4float %gl_FragCoord
+; CHECK: %78 = OpBitcast %v4uint %76
+; CHECK: %79 = OpCompositeExtract %uint %78 0
+; CHECK: %80 = OpIAdd %uint %55 %uint_4
+; CHECK: %81 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %80
+; CHECK: OpStore %81 %79
+; CHECK: %82 = OpCompositeExtract %uint %78 1
+; CHECK: %83 = OpIAdd %uint %55 %uint_5
+; CHECK: %84 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %83
+; CHECK: OpStore %84 %82
+; CHECK: %86 = OpIAdd %uint %55 %uint_7
+; CHECK: %87 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %86
+; CHECK: OpStore %87 %40
+; CHECK: %89 = OpIAdd %uint %55 %uint_8
+; CHECK: %90 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %89
+; CHECK: OpStore %90 %41
+; CHECK: %92 = OpIAdd %uint %55 %uint_9
+; CHECK: %93 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %92
+; CHECK: OpStore %93 %42
+; CHECK: %95 = OpIAdd %uint %55 %uint_10
+; CHECK: %96 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %95
+; CHECK: OpStore %96 %43
+; CHECK: %98 = OpIAdd %uint %55 %uint_11
+; CHECK: %99 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %98
+; CHECK: OpStore %99 %44
+; CHECK: OpBranch %60
+; CHECK: %60 = OpLabel
+; CHECK: OpReturn
+; CHECK: OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<InstDebugPrintfPass>(
+      defs + decorates + globals + main + output_func, true);
+}
+
+// TODO(greg-lunarg): Add tests to verify handling of these cases:
+//
+//   Compute shader
+//   Geometry shader
+//   Tesselation control shader
+//   Tesselation eval shader
+//   Vertex shader
+
+}  // namespace
+}  // namespace opt
+}  // namespace spvtools
diff --git a/test/opt/instruction_test.cpp b/test/opt/instruction_test.cpp
index a697201..1995c5b 100644
--- a/test/opt/instruction_test.cpp
+++ b/test/opt/instruction_test.cpp
@@ -14,6 +14,7 @@
 
 #include <memory>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include "gmock/gmock.h"
@@ -34,6 +35,7 @@
 using OpaqueTypeTest = PassTest<::testing::Test>;
 using GetBaseTest = PassTest<::testing::Test>;
 using ValidBasePointerTest = PassTest<::testing::Test>;
+using VulkanBufferTest = PassTest<::testing::Test>;
 
 TEST(InstructionTest, CreateTrivial) {
   Instruction empty;
@@ -60,6 +62,18 @@
   EXPECT_EQ(inst.end(), inst.begin());
 }
 
+TEST(InstructionTest, OperandAsCString) {
+  Operand::OperandData abcde{0x64636261, 0x65};
+  Operand operand(SPV_OPERAND_TYPE_LITERAL_STRING, std::move(abcde));
+  EXPECT_STREQ("abcde", operand.AsCString());
+}
+
+TEST(InstructionTest, OperandAsString) {
+  Operand::OperandData abcde{0x64636261, 0x65};
+  Operand operand(SPV_OPERAND_TYPE_LITERAL_STRING, std::move(abcde));
+  EXPECT_EQ("abcde", operand.AsString());
+}
+
 // The words for an OpTypeInt for 32-bit signed integer resulting in Id 44.
 uint32_t kSampleInstructionWords[] = {(4 << 16) | uint32_t(SpvOpTypeInt), 44,
                                       32, 1};
@@ -1130,6 +1144,260 @@
   EXPECT_TRUE(null_inst->IsValidBasePointer());
 }
 
+TEST_F(VulkanBufferTest, VulkanStorageBuffer) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability RuntimeDescriptorArray
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %1 "main"
+OpExecutionMode %1 LocalSize 1 1 1
+OpDecorate %2 Block
+OpMemberDecorate %2 0 Offset 0
+OpDecorate %3 BufferBlock
+OpMemberDecorate %3 0 Offset 0
+%4 = OpTypeVoid
+%5 = OpTypeInt 32 0
+%2 = OpTypeStruct %5
+%3 = OpTypeStruct %5
+
+%6 = OpTypePointer StorageBuffer %2
+%7 = OpTypePointer Uniform %2
+%8 = OpTypePointer Uniform %3
+
+%9 = OpConstant %5 1
+%10 = OpTypeArray %2 %9
+%11 = OpTypeArray %3 %9
+%12 = OpTypePointer StorageBuffer %10
+%13 = OpTypePointer Uniform %10
+%14 = OpTypePointer Uniform %11
+
+%15 = OpTypeRuntimeArray %2
+%16 = OpTypeRuntimeArray %3
+%17 = OpTypePointer StorageBuffer %15
+%18 = OpTypePointer Uniform %15
+%19 = OpTypePointer Uniform %16
+
+%50 = OpTypeFunction %4
+%1 = OpFunction %4 None %50
+%51 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_3, nullptr, text);
+  EXPECT_NE(context, nullptr);
+
+  // Standard SSBO and UBO
+  Instruction* inst = context->get_def_use_mgr()->GetDef(6);
+  EXPECT_EQ(true, inst->IsVulkanStorageBuffer());
+  inst = context->get_def_use_mgr()->GetDef(7);
+  EXPECT_EQ(false, inst->IsVulkanStorageBuffer());
+  inst = context->get_def_use_mgr()->GetDef(8);
+  EXPECT_EQ(true, inst->IsVulkanStorageBuffer());
+
+  // Arrayed SSBO and UBO
+  inst = context->get_def_use_mgr()->GetDef(12);
+  EXPECT_EQ(true, inst->IsVulkanStorageBuffer());
+  inst = context->get_def_use_mgr()->GetDef(13);
+  EXPECT_EQ(false, inst->IsVulkanStorageBuffer());
+  inst = context->get_def_use_mgr()->GetDef(14);
+  EXPECT_EQ(true, inst->IsVulkanStorageBuffer());
+
+  // Runtime arrayed SSBO and UBO
+  inst = context->get_def_use_mgr()->GetDef(17);
+  EXPECT_EQ(true, inst->IsVulkanStorageBuffer());
+  inst = context->get_def_use_mgr()->GetDef(18);
+  EXPECT_EQ(false, inst->IsVulkanStorageBuffer());
+  inst = context->get_def_use_mgr()->GetDef(19);
+  EXPECT_EQ(true, inst->IsVulkanStorageBuffer());
+}
+
+TEST_F(VulkanBufferTest, VulkanUniformBuffer) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability RuntimeDescriptorArray
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %1 "main"
+OpExecutionMode %1 LocalSize 1 1 1
+OpDecorate %2 Block
+OpMemberDecorate %2 0 Offset 0
+OpDecorate %3 BufferBlock
+OpMemberDecorate %3 0 Offset 0
+%4 = OpTypeVoid
+%5 = OpTypeInt 32 0
+%2 = OpTypeStruct %5
+%3 = OpTypeStruct %5
+
+%6 = OpTypePointer StorageBuffer %2
+%7 = OpTypePointer Uniform %2
+%8 = OpTypePointer Uniform %3
+
+%9 = OpConstant %5 1
+%10 = OpTypeArray %2 %9
+%11 = OpTypeArray %3 %9
+%12 = OpTypePointer StorageBuffer %10
+%13 = OpTypePointer Uniform %10
+%14 = OpTypePointer Uniform %11
+
+%15 = OpTypeRuntimeArray %2
+%16 = OpTypeRuntimeArray %3
+%17 = OpTypePointer StorageBuffer %15
+%18 = OpTypePointer Uniform %15
+%19 = OpTypePointer Uniform %16
+
+%50 = OpTypeFunction %4
+%1 = OpFunction %4 None %50
+%51 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_3, nullptr, text);
+  EXPECT_NE(context, nullptr);
+
+  // Standard SSBO and UBO
+  Instruction* inst = context->get_def_use_mgr()->GetDef(6);
+  EXPECT_EQ(false, inst->IsVulkanUniformBuffer());
+  inst = context->get_def_use_mgr()->GetDef(7);
+  EXPECT_EQ(true, inst->IsVulkanUniformBuffer());
+  inst = context->get_def_use_mgr()->GetDef(8);
+  EXPECT_EQ(false, inst->IsVulkanUniformBuffer());
+
+  // Arrayed SSBO and UBO
+  inst = context->get_def_use_mgr()->GetDef(12);
+  EXPECT_EQ(false, inst->IsVulkanUniformBuffer());
+  inst = context->get_def_use_mgr()->GetDef(13);
+  EXPECT_EQ(true, inst->IsVulkanUniformBuffer());
+  inst = context->get_def_use_mgr()->GetDef(14);
+  EXPECT_EQ(false, inst->IsVulkanUniformBuffer());
+
+  // Runtime arrayed SSBO and UBO
+  inst = context->get_def_use_mgr()->GetDef(17);
+  EXPECT_EQ(false, inst->IsVulkanUniformBuffer());
+  inst = context->get_def_use_mgr()->GetDef(18);
+  EXPECT_EQ(true, inst->IsVulkanUniformBuffer());
+  inst = context->get_def_use_mgr()->GetDef(19);
+  EXPECT_EQ(false, inst->IsVulkanUniformBuffer());
+}
+
+TEST_F(VulkanBufferTest, ImageQueries) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability ImageBuffer
+OpCapability RuntimeDescriptorArray
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %1 "main"
+OpExecutionMode %1 LocalSize 1 1 1
+%2 = OpTypeVoid
+%3 = OpTypeFloat 32
+
+%4 = OpTypeImage %3 Buffer 0 0 0 1 Rgba32f
+%5 = OpTypeImage %3 Buffer 0 0 0 2 Rgba32f
+%6 = OpTypeImage %3 2D 0 0 0 1 Rgba32f
+%7 = OpTypeImage %3 2D 0 0 0 2 Rgba32f
+
+%8 = OpTypePointer UniformConstant %4
+%9 = OpTypePointer UniformConstant %5
+%10 = OpTypePointer UniformConstant %6
+%11 = OpTypePointer UniformConstant %7
+
+%12 = OpTypeInt 32 0
+%13 = OpConstant %12 1
+%14 = OpTypeArray %4 %13
+%15 = OpTypeArray %5 %13
+%16 = OpTypeArray %6 %13
+%17 = OpTypeArray %7 %13
+%18 = OpTypePointer UniformConstant %14
+%19 = OpTypePointer UniformConstant %15
+%20 = OpTypePointer UniformConstant %16
+%21 = OpTypePointer UniformConstant %17
+
+%22 = OpTypeRuntimeArray %4
+%23 = OpTypeRuntimeArray %5
+%24 = OpTypeRuntimeArray %6
+%25 = OpTypeRuntimeArray %7
+%26 = OpTypePointer UniformConstant %22
+%27 = OpTypePointer UniformConstant %23
+%28 = OpTypePointer UniformConstant %24
+%29 = OpTypePointer UniformConstant %25
+
+%50 = OpTypeFunction %4
+%1 = OpFunction %4 None %50
+%51 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_3, nullptr, text);
+  EXPECT_NE(context, nullptr);
+
+  // Bare pointers
+  Instruction* inst = context->get_def_use_mgr()->GetDef(8);
+  EXPECT_EQ(false, inst->IsVulkanStorageImage());
+  EXPECT_EQ(false, inst->IsVulkanSampledImage());
+  EXPECT_EQ(false, inst->IsVulkanStorageTexelBuffer());
+
+  inst = context->get_def_use_mgr()->GetDef(9);
+  EXPECT_EQ(false, inst->IsVulkanStorageImage());
+  EXPECT_EQ(false, inst->IsVulkanSampledImage());
+  EXPECT_EQ(true, inst->IsVulkanStorageTexelBuffer());
+
+  inst = context->get_def_use_mgr()->GetDef(10);
+  EXPECT_EQ(false, inst->IsVulkanStorageImage());
+  EXPECT_EQ(true, inst->IsVulkanSampledImage());
+  EXPECT_EQ(false, inst->IsVulkanStorageTexelBuffer());
+
+  inst = context->get_def_use_mgr()->GetDef(11);
+  EXPECT_EQ(true, inst->IsVulkanStorageImage());
+  EXPECT_EQ(false, inst->IsVulkanSampledImage());
+  EXPECT_EQ(false, inst->IsVulkanStorageTexelBuffer());
+
+  // Array pointers
+  inst = context->get_def_use_mgr()->GetDef(18);
+  EXPECT_EQ(false, inst->IsVulkanStorageImage());
+  EXPECT_EQ(false, inst->IsVulkanSampledImage());
+  EXPECT_EQ(false, inst->IsVulkanStorageTexelBuffer());
+
+  inst = context->get_def_use_mgr()->GetDef(19);
+  EXPECT_EQ(false, inst->IsVulkanStorageImage());
+  EXPECT_EQ(false, inst->IsVulkanSampledImage());
+  EXPECT_EQ(true, inst->IsVulkanStorageTexelBuffer());
+
+  inst = context->get_def_use_mgr()->GetDef(20);
+  EXPECT_EQ(false, inst->IsVulkanStorageImage());
+  EXPECT_EQ(true, inst->IsVulkanSampledImage());
+  EXPECT_EQ(false, inst->IsVulkanStorageTexelBuffer());
+
+  inst = context->get_def_use_mgr()->GetDef(21);
+  EXPECT_EQ(true, inst->IsVulkanStorageImage());
+  EXPECT_EQ(false, inst->IsVulkanSampledImage());
+  EXPECT_EQ(false, inst->IsVulkanStorageTexelBuffer());
+
+  // Runtime array pointers
+  inst = context->get_def_use_mgr()->GetDef(26);
+  EXPECT_EQ(false, inst->IsVulkanStorageImage());
+  EXPECT_EQ(false, inst->IsVulkanSampledImage());
+  EXPECT_EQ(false, inst->IsVulkanStorageTexelBuffer());
+
+  inst = context->get_def_use_mgr()->GetDef(27);
+  EXPECT_EQ(false, inst->IsVulkanStorageImage());
+  EXPECT_EQ(false, inst->IsVulkanSampledImage());
+  EXPECT_EQ(true, inst->IsVulkanStorageTexelBuffer());
+
+  inst = context->get_def_use_mgr()->GetDef(28);
+  EXPECT_EQ(false, inst->IsVulkanStorageImage());
+  EXPECT_EQ(true, inst->IsVulkanSampledImage());
+  EXPECT_EQ(false, inst->IsVulkanStorageTexelBuffer());
+
+  inst = context->get_def_use_mgr()->GetDef(29);
+  EXPECT_EQ(true, inst->IsVulkanStorageImage());
+  EXPECT_EQ(false, inst->IsVulkanSampledImage());
+  EXPECT_EQ(false, inst->IsVulkanStorageTexelBuffer());
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/ir_builder.cpp b/test/opt/ir_builder.cpp
index f800ca4..cb234e0 100644
--- a/test/opt/ir_builder.cpp
+++ b/test/opt/ir_builder.cpp
@@ -408,7 +408,7 @@
 
 TEST_F(IRBuilderTest, AccelerationStructureNV) {
   const std::string text = R"(
-; CHECK: OpTypeAccelerationStructureNV
+; CHECK: OpTypeAccelerationStructureKHR
 OpCapability Shader
 OpCapability RayTracingNV
 OpExtension "SPV_NV_ray_tracing"
diff --git a/test/opt/ir_loader_test.cpp b/test/opt/ir_loader_test.cpp
index ac5c520..50e3a08 100644
--- a/test/opt/ir_loader_test.cpp
+++ b/test/opt/ir_loader_test.cpp
@@ -129,6 +129,825 @@
   // clang-format on
 }
 
+TEST(IrBuilder, ConsumeDebugInfoInst) {
+  // /* HLSL */
+  //
+  // struct VS_OUTPUT {
+  //   float4 pos : SV_POSITION;
+  //   float4 color : COLOR;
+  // };
+  //
+  // VS_OUTPUT main(float4 pos : POSITION,
+  //                float4 color : COLOR) {
+  //   VS_OUTPUT vout;
+  //   vout.pos = pos;
+  //   vout.color = color;
+  //   return vout;
+  // }
+  DoRoundTripCheck(R"(OpCapability Shader
+%1 = OpExtInstImport "OpenCL.DebugInfo.100"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main" %pos %color %gl_Position %out_var_COLOR
+%7 = OpString "simple_vs.hlsl"
+%8 = OpString "#line 1 \"simple_vs.hlsl\"
+struct VS_OUTPUT {
+  float4 pos : SV_POSITION;
+  float4 color : COLOR;
+};
+
+VS_OUTPUT main(float4 pos : POSITION,
+               float4 color : COLOR) {
+  VS_OUTPUT vout;
+  vout.pos = pos;
+  vout.color = color;
+  return vout;
+}
+"
+OpSource HLSL 600 %7 "#line 1 \"simple_vs.hlsl\"
+struct VS_OUTPUT {
+  float4 pos : SV_POSITION;
+  float4 color : COLOR;
+};
+
+VS_OUTPUT main(float4 pos : POSITION,
+               float4 color : COLOR) {
+  VS_OUTPUT vout;
+  vout.pos = pos;
+  vout.color = color;
+  return vout;
+}
+"
+%9 = OpString "struct VS_OUTPUT"
+%10 = OpString "float"
+%11 = OpString "pos : SV_POSITION"
+%12 = OpString "color : COLOR"
+%13 = OpString "VS_OUTPUT"
+%14 = OpString "main"
+%15 = OpString "VS_OUTPUT_main_v4f_v4f"
+%16 = OpString "pos : POSITION"
+%17 = OpString "color : COLOR"
+%18 = OpString "vout"
+OpName %out_var_COLOR "out.var.COLOR"
+OpName %main "main"
+OpName %VS_OUTPUT "VS_OUTPUT"
+OpMemberName %VS_OUTPUT 0 "pos"
+OpMemberName %VS_OUTPUT 1 "color"
+OpName %pos "pos"
+OpName %color "color"
+OpName %vout "vout"
+OpDecorate %gl_Position BuiltIn Position
+OpDecorate %pos Location 0
+OpDecorate %color Location 1
+OpDecorate %out_var_COLOR Location 0
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%int_32 = OpConstant %int 32
+%int_128 = OpConstant %int 128
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%void = OpTypeVoid
+%31 = OpTypeFunction %void
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%VS_OUTPUT = OpTypeStruct %v4float %v4float
+%_ptr_Function_VS_OUTPUT = OpTypePointer Function %VS_OUTPUT
+OpLine %7 6 23
+%pos = OpVariable %_ptr_Input_v4float Input
+OpLine %7 7 23
+%color = OpVariable %_ptr_Input_v4float Input
+OpLine %7 2 16
+%gl_Position = OpVariable %_ptr_Output_v4float Output
+OpLine %7 3 18
+%out_var_COLOR = OpVariable %_ptr_Output_v4float Output
+%34 = OpExtInst %void %1 DebugSource %7 %8
+%35 = OpExtInst %void %1 DebugCompilationUnit 2 4 %34 HLSL
+%36 = OpExtInst %void %1 DebugTypeComposite %9 Structure %34 1 1 %35 %13 %int_128 FlagIsProtected|FlagIsPrivate %37 %38
+%39 = OpExtInst %void %1 DebugTypeBasic %10 %int_32 Float
+%40 = OpExtInst %void %1 DebugTypeVector %39 4
+%37 = OpExtInst %void %1 DebugTypeMember %11 %40 %34 2 3 %36 %int_0 %int_128 FlagIsProtected|FlagIsPrivate
+%38 = OpExtInst %void %1 DebugTypeMember %12 %40 %34 3 3 %36 %int_128 %int_128 FlagIsProtected|FlagIsPrivate
+%41 = OpExtInst %void %1 DebugTypeFunction FlagIsProtected|FlagIsPrivate %36 %40 %40
+%42 = OpExtInst %void %1 DebugExpression
+%43 = OpExtInst %void %1 DebugFunction %14 %41 %34 6 1 %35 %15 FlagIsProtected|FlagIsPrivate 7 %main
+%44 = OpExtInst %void %1 DebugLocalVariable %16 %40 %34 6 16 %43 FlagIsLocal 0
+%45 = OpExtInst %void %1 DebugLocalVariable %17 %40 %34 7 16 %43 FlagIsLocal 1
+%46 = OpExtInst %void %1 DebugLocalVariable %18 %36 %34 8 3 %43 FlagIsLocal
+OpLine %7 6 1
+%main = OpFunction %void None %31
+%47 = OpLabel
+%60 = OpExtInst %void %1 DebugScope %43
+OpLine %7 8 13
+%vout = OpVariable %_ptr_Function_VS_OUTPUT Function
+%49 = OpExtInst %void %1 DebugDeclare %46 %vout %42
+OpLine %7 9 14
+%50 = OpLoad %v4float %pos
+OpLine %7 9 3
+%51 = OpAccessChain %_ptr_Function_v4float %vout %int_0
+%52 = OpExtInst %void %1 DebugValue %46 %51 %42 %int_0
+OpStore %51 %50
+OpLine %7 10 16
+%53 = OpLoad %v4float %color
+OpLine %7 10 3
+%54 = OpAccessChain %_ptr_Function_v4float %vout %int_1
+%55 = OpExtInst %void %1 DebugValue %46 %54 %42 %int_1
+OpStore %54 %53
+OpLine %7 11 10
+%56 = OpLoad %VS_OUTPUT %vout
+OpLine %7 11 3
+%57 = OpCompositeExtract %v4float %56 0
+OpStore %gl_Position %57
+%58 = OpCompositeExtract %v4float %56 1
+OpStore %out_var_COLOR %58
+%61 = OpExtInst %void %1 DebugNoScope
+OpReturn
+OpFunctionEnd
+)");
+}
+
+TEST(IrBuilder, ConsumeDebugInfoLexicalScopeInst) {
+  // /* HLSL */
+  //
+  // float4 func2(float arg2) {   // func2_block
+  //   return float4(arg2, 0, 0, 0);
+  // }
+  //
+  // float4 func1(float arg1) {   // func1_block
+  //   if (arg1 > 1) {       // if_true_block
+  //     return float4(0, 0, 0, 0);
+  //   }
+  //   return func2(arg1);   // if_merge_block
+  // }
+  //
+  // float4 main(float pos : POSITION) : SV_POSITION {  // main
+  //   return func1(pos);
+  // }
+  DoRoundTripCheck(R"(OpCapability Shader
+%1 = OpExtInstImport "OpenCL.DebugInfo.100"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main" %pos %gl_Position
+%5 = OpString "block/block.hlsl"
+%6 = OpString "#line 1 \"block/block.hlsl\"
+float4 func2(float arg2) {
+  return float4(arg2, 0, 0, 0);
+}
+
+float4 func1(float arg1) {
+  if (arg1 > 1) {
+    return float4(0, 0, 0, 0);
+  }
+  return func2(arg1);
+}
+
+float4 main(float pos : POSITION) : SV_POSITION {
+  return func1(pos);
+}
+"
+OpSource HLSL 600 %5 "#line 1 \"block/block.hlsl\"
+float4 func2(float arg2) {
+  return float4(arg2, 0, 0, 0);
+}
+
+float4 func1(float arg1) {
+  if (arg1 > 1) {
+    return float4(0, 0, 0, 0);
+  }
+  return func2(arg1);
+}
+
+float4 main(float pos : POSITION) : SV_POSITION {
+  return func1(pos);
+}
+"
+%7 = OpString "float"
+%8 = OpString "main"
+%9 = OpString "v4f_main_f"
+%10 = OpString "v4f_func1_f"
+%11 = OpString "v4f_func2_f"
+%12 = OpString "pos : POSITION"
+%13 = OpString "func1"
+%14 = OpString "func2"
+OpName %main "main"
+OpName %pos "pos"
+OpName %bb_entry "bb.entry"
+OpName %param_var_arg1 "param.var.arg1"
+OpName %func1 "func1"
+OpName %arg1 "arg1"
+OpName %bb_entry_0 "bb.entry"
+OpName %param_var_arg2 "param.var.arg2"
+OpName %if_true "if.true"
+OpName %if_merge "if.merge"
+OpName %func2 "func2"
+OpName %arg2 "arg2"
+OpName %bb_entry_1 "bb.entry"
+OpDecorate %gl_Position BuiltIn Position
+OpDecorate %pos Location 0
+%float = OpTypeFloat 32
+%int = OpTypeInt 32 1
+%float_1 = OpConstant %float 1
+%float_0 = OpConstant %float 0
+%int_32 = OpConstant %int 32
+%v4float = OpTypeVector %float 4
+%32 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
+%_ptr_Input_float = OpTypePointer Input %float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%void = OpTypeVoid
+%36 = OpTypeFunction %void
+%_ptr_Function_float = OpTypePointer Function %float
+%38 = OpTypeFunction %v4float %_ptr_Function_float
+%bool = OpTypeBool
+OpLine %5 12 25
+%pos = OpVariable %_ptr_Input_float Input
+OpLine %5 12 37
+%gl_Position = OpVariable %_ptr_Output_v4float Output
+%40 = OpExtInst %void %1 DebugSource %5 %6
+%41 = OpExtInst %void %1 DebugCompilationUnit 2 4 %40 HLSL
+%42 = OpExtInst %void %1 DebugTypeBasic %7 %int_32 Float
+%43 = OpExtInst %void %1 DebugTypeVector %42 4
+%44 = OpExtInst %void %1 DebugTypeFunction FlagIsProtected|FlagIsPrivate %43 %42
+%45 = OpExtInst %void %1 DebugTypeFunction FlagIsProtected|FlagIsPrivate %43 %42
+%46 = OpExtInst %void %1 DebugTypeFunction FlagIsProtected|FlagIsPrivate %43 %42
+%47 = OpExtInst %void %1 DebugFunction %8 %44 %40 12 1 %41 %9 FlagIsProtected|FlagIsPrivate 13 %main
+%48 = OpExtInst %void %1 DebugFunction %13 %45 %40 5 1 %41 %10 FlagIsProtected|FlagIsPrivate 13 %func1
+%49 = OpExtInst %void %1 DebugFunction %14 %46 %40 1 1 %41 %11 FlagIsProtected|FlagIsPrivate 13 %func2
+%50 = OpExtInst %void %1 DebugLexicalBlock %40 6 17 %48
+%51 = OpExtInst %void %1 DebugLexicalBlock %40 9 3 %48
+OpLine %5 12 1
+%main = OpFunction %void None %36
+%bb_entry = OpLabel
+%70 = OpExtInst %void %1 DebugScope %47
+OpLine %5 13 16
+%param_var_arg1 = OpVariable %_ptr_Function_float Function
+%53 = OpLoad %float %pos
+OpStore %param_var_arg1 %53
+OpLine %5 13 10
+%54 = OpFunctionCall %v4float %func1 %param_var_arg1
+OpLine %5 13 3
+OpStore %gl_Position %54
+%71 = OpExtInst %void %1 DebugNoScope
+OpReturn
+OpFunctionEnd
+OpLine %5 5 1
+%func1 = OpFunction %v4float None %38
+OpLine %5 5 20
+%arg1 = OpFunctionParameter %_ptr_Function_float
+%bb_entry_0 = OpLabel
+%72 = OpExtInst %void %1 DebugScope %48
+OpLine %5 9 16
+%param_var_arg2 = OpVariable %_ptr_Function_float Function
+OpLine %5 6 7
+%57 = OpLoad %float %arg1
+OpLine %5 6 12
+%58 = OpFOrdGreaterThan %bool %57 %float_1
+OpLine %5 6 17
+%73 = OpExtInst %void %1 DebugNoScope
+OpSelectionMerge %if_merge None
+OpBranchConditional %58 %if_true %if_merge
+%if_true = OpLabel
+%74 = OpExtInst %void %1 DebugScope %50
+OpLine %5 7 5
+%75 = OpExtInst %void %1 DebugNoScope
+OpReturnValue %32
+%if_merge = OpLabel
+%76 = OpExtInst %void %1 DebugScope %51
+OpLine %5 9 16
+%63 = OpLoad %float %arg1
+OpStore %param_var_arg2 %63
+OpLine %5 9 10
+%64 = OpFunctionCall %v4float %func2 %param_var_arg2
+OpLine %5 9 3
+%77 = OpExtInst %void %1 DebugNoScope
+OpReturnValue %64
+OpFunctionEnd
+OpLine %5 1 1
+%func2 = OpFunction %v4float None %38
+OpLine %5 1 20
+%arg2 = OpFunctionParameter %_ptr_Function_float
+%bb_entry_1 = OpLabel
+%78 = OpExtInst %void %1 DebugScope %49
+OpLine %5 2 17
+%67 = OpLoad %float %arg2
+%68 = OpCompositeConstruct %v4float %67 %float_0 %float_0 %float_0
+OpLine %5 2 3
+%79 = OpExtInst %void %1 DebugNoScope
+OpReturnValue %68
+OpFunctionEnd
+)");
+}
+
+TEST(IrBuilder, ConsumeDebugInlinedAt) {
+  // /* HLSL */
+  //
+  // float4 func2(float arg2) {   // func2_block
+  //   return float4(arg2, 0, 0, 0);
+  // }
+  //
+  // float4 func1(float arg1) {   // func1_block
+  //   if (arg1 > 1) {       // if_true_block
+  //     return float4(0, 0, 0, 0);
+  //   }
+  //   return func2(arg1);   // if_merge_block
+  // }
+  //
+  // float4 main(float pos : POSITION) : SV_POSITION {  // main
+  //   return func1(pos);
+  // }
+  //
+  // TODO(https://gitlab.khronos.org/spirv/SPIR-V/issues/533): In the following
+  // SPIRV code, we use DebugInfoNone to reference opted-out function from
+  // DebugFunction similar to opted-out global variable for DebugGlobalVariable,
+  // but this is not a part of the spec yet. We are still in discussion and we
+  // must correct it if our decision is different.
+  DoRoundTripCheck(R"(OpCapability Shader
+%1 = OpExtInstImport "OpenCL.DebugInfo.100"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main" %pos %gl_Position
+%5 = OpString "block/block.hlsl"
+%6 = OpString "#line 1 \"block/block.hlsl\"
+float4 func2(float arg2) {
+  return float4(arg2, 0, 0, 0);
+}
+
+float4 func1(float arg1) {
+  if (arg1 > 1) {
+    return float4(0, 0, 0, 0);
+  }
+  return func2(arg1);
+}
+
+float4 main(float pos : POSITION) : SV_POSITION {
+  return func1(pos);
+}
+"
+OpSource HLSL 600 %5 "#line 1 \"block/block.hlsl\"
+float4 func2(float arg2) {
+  return float4(arg2, 0, 0, 0);
+}
+
+float4 func1(float arg1) {
+  if (arg1 > 1) {
+    return float4(0, 0, 0, 0);
+  }
+  return func2(arg1);
+}
+
+float4 main(float pos : POSITION) : SV_POSITION {
+  return func1(pos);
+}
+"
+%7 = OpString "float"
+%8 = OpString "main"
+%9 = OpString "v4f_main_f"
+%10 = OpString "v4f_func1_f"
+%11 = OpString "v4f_func2_f"
+%12 = OpString "pos : POSITION"
+%13 = OpString "func1"
+%14 = OpString "func2"
+OpName %main "main"
+OpName %pos "pos"
+OpName %bb_entry "bb.entry"
+OpName %if_true "if.true"
+OpName %if_merge "if.merge"
+OpDecorate %gl_Position BuiltIn Position
+OpDecorate %pos Location 0
+%float = OpTypeFloat 32
+%int = OpTypeInt 32 1
+%float_1 = OpConstant %float 1
+%float_0 = OpConstant %float 0
+%int_32 = OpConstant %int 32
+%v4float = OpTypeVector %float 4
+%24 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
+%_ptr_Input_float = OpTypePointer Input %float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%void = OpTypeVoid
+%28 = OpTypeFunction %void
+%_ptr_Function_float = OpTypePointer Function %float
+%30 = OpTypeFunction %v4float %_ptr_Function_float
+%bool = OpTypeBool
+OpLine %5 12 25
+%pos = OpVariable %_ptr_Input_float Input
+OpLine %5 12 37
+%gl_Position = OpVariable %_ptr_Output_v4float Output
+%32 = OpExtInst %void %1 DebugInfoNone
+%33 = OpExtInst %void %1 DebugSource %5 %6
+%34 = OpExtInst %void %1 DebugCompilationUnit 2 4 %33 HLSL
+%35 = OpExtInst %void %1 DebugTypeBasic %7 %int_32 Float
+%36 = OpExtInst %void %1 DebugTypeVector %35 4
+%37 = OpExtInst %void %1 DebugTypeFunction FlagIsProtected|FlagIsPrivate %36 %35
+%38 = OpExtInst %void %1 DebugTypeFunction FlagIsProtected|FlagIsPrivate %36 %35
+%39 = OpExtInst %void %1 DebugTypeFunction FlagIsProtected|FlagIsPrivate %36 %35
+%40 = OpExtInst %void %1 DebugFunction %8 %37 %33 12 1 %34 %9 FlagIsProtected|FlagIsPrivate 13 %main
+%41 = OpExtInst %void %1 DebugFunction %13 %38 %33 5 1 %34 %10 FlagIsProtected|FlagIsPrivate 13 %32
+%42 = OpExtInst %void %1 DebugFunction %14 %39 %33 1 1 %34 %11 FlagIsProtected|FlagIsPrivate 13 %32
+%43 = OpExtInst %void %1 DebugLexicalBlock %33 12 49 %40
+%44 = OpExtInst %void %1 DebugLexicalBlock %33 5 26 %41
+%45 = OpExtInst %void %1 DebugLexicalBlock %33 1 26 %42
+%46 = OpExtInst %void %1 DebugLexicalBlock %33 6 17 %44
+%47 = OpExtInst %void %1 DebugLexicalBlock %33 9 3 %44
+%48 = OpExtInst %void %1 DebugInlinedAt 9 %47
+%49 = OpExtInst %void %1 DebugInlinedAt 13 %43
+%50 = OpExtInst %void %1 DebugInlinedAt 13 %43 %48
+OpLine %5 12 1
+%main = OpFunction %void None %28
+%bb_entry = OpLabel
+%62 = OpExtInst %void %1 DebugScope %44 %49
+OpLine %5 6 7
+%52 = OpLoad %float %pos
+OpLine %5 6 12
+%53 = OpFOrdGreaterThan %bool %52 %float_1
+OpLine %5 6 17
+%63 = OpExtInst %void %1 DebugNoScope
+OpSelectionMerge %if_merge None
+OpBranchConditional %53 %if_true %if_merge
+%if_true = OpLabel
+%64 = OpExtInst %void %1 DebugScope %46 %49
+OpLine %5 7 5
+OpStore %gl_Position %24
+%65 = OpExtInst %void %1 DebugNoScope
+OpReturn
+%if_merge = OpLabel
+%66 = OpExtInst %void %1 DebugScope %45 %50
+OpLine %5 2 17
+%58 = OpLoad %float %pos
+OpLine %5 2 10
+%59 = OpCompositeConstruct %v4float %58 %float_0 %float_0 %float_0
+%67 = OpExtInst %void %1 DebugScope %43
+OpLine %5 13 3
+OpStore %gl_Position %59
+%68 = OpExtInst %void %1 DebugNoScope
+OpReturn
+OpFunctionEnd
+)");
+}
+
+TEST(IrBuilder, DebugInfoInstInFunctionOutOfBlock) {
+  // /* HLSL */
+  //
+  // float4 func2(float arg2) {   // func2_block
+  //   return float4(arg2, 0, 0, 0);
+  // }
+  //
+  // float4 func1(float arg1) {   // func1_block
+  //   if (arg1 > 1) {       // if_true_block
+  //     return float4(0, 0, 0, 0);
+  //   }
+  //   return func2(arg1);   // if_merge_block
+  // }
+  //
+  // float4 main(float pos : POSITION) : SV_POSITION {  // main
+  //   return func1(pos);
+  // }
+  const std::string text = R"(OpCapability Shader
+%1 = OpExtInstImport "OpenCL.DebugInfo.100"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main" %pos %gl_Position
+%5 = OpString "block/block.hlsl"
+%6 = OpString "#line 1 \"block/block.hlsl\"
+float4 func2(float arg2) {
+  return float4(arg2, 0, 0, 0);
+}
+
+float4 func1(float arg1) {
+  if (arg1 > 1) {
+    return float4(0, 0, 0, 0);
+  }
+  return func2(arg1);
+}
+
+float4 main(float pos : POSITION) : SV_POSITION {
+  return func1(pos);
+}
+"
+OpSource HLSL 600 %5 "#line 1 \"block/block.hlsl\"
+float4 func2(float arg2) {
+  return float4(arg2, 0, 0, 0);
+}
+
+float4 func1(float arg1) {
+  if (arg1 > 1) {
+    return float4(0, 0, 0, 0);
+  }
+  return func2(arg1);
+}
+
+float4 main(float pos : POSITION) : SV_POSITION {
+  return func1(pos);
+}
+"
+%7 = OpString "float"
+%8 = OpString "main"
+%9 = OpString "v4f_main_f"
+%10 = OpString "v4f_func1_f"
+%11 = OpString "v4f_func2_f"
+%12 = OpString "pos : POSITION"
+%13 = OpString "func1"
+%14 = OpString "func2"
+OpName %main "main"
+OpName %pos "pos"
+OpName %bb_entry "bb.entry"
+OpName %param_var_arg1 "param.var.arg1"
+OpName %func1 "func1"
+OpName %arg1 "arg1"
+OpName %bb_entry_0 "bb.entry"
+OpName %param_var_arg2 "param.var.arg2"
+OpName %if_true "if.true"
+OpName %if_merge "if.merge"
+OpName %func2 "func2"
+OpName %arg2 "arg2"
+OpName %bb_entry_1 "bb.entry"
+OpDecorate %gl_Position BuiltIn Position
+OpDecorate %pos Location 0
+%float = OpTypeFloat 32
+%int = OpTypeInt 32 1
+%float_1 = OpConstant %float 1
+%float_0 = OpConstant %float 0
+%int_32 = OpConstant %int 32
+%v4float = OpTypeVector %float 4
+%32 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
+%_ptr_Input_float = OpTypePointer Input %float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%void = OpTypeVoid
+%36 = OpTypeFunction %void
+%_ptr_Function_float = OpTypePointer Function %float
+%38 = OpTypeFunction %v4float %_ptr_Function_float
+%bool = OpTypeBool
+OpLine %5 12 25
+%pos = OpVariable %_ptr_Input_float Input
+OpLine %5 12 37
+%gl_Position = OpVariable %_ptr_Output_v4float Output
+%40 = OpExtInst %void %1 DebugSource %5 %6
+%41 = OpExtInst %void %1 DebugCompilationUnit 2 4 %40 HLSL
+%42 = OpExtInst %void %1 DebugTypeBasic %7 %int_32 Float
+%43 = OpExtInst %void %1 DebugTypeVector %42 4
+%44 = OpExtInst %void %1 DebugTypeFunction FlagIsProtected|FlagIsPrivate %43 %42
+%45 = OpExtInst %void %1 DebugTypeFunction FlagIsProtected|FlagIsPrivate %43 %42
+%46 = OpExtInst %void %1 DebugTypeFunction FlagIsProtected|FlagIsPrivate %43 %42
+%47 = OpExtInst %void %1 DebugFunction %8 %44 %40 12 1 %41 %9 FlagIsProtected|FlagIsPrivate 13 %main
+%48 = OpExtInst %void %1 DebugFunction %13 %45 %40 5 1 %41 %10 FlagIsProtected|FlagIsPrivate 13 %func1
+%49 = OpExtInst %void %1 DebugFunction %14 %46 %40 1 1 %41 %11 FlagIsProtected|FlagIsPrivate 13 %func2
+%50 = OpExtInst %void %1 DebugLexicalBlock %40 6 17 %48
+%51 = OpExtInst %void %1 DebugLexicalBlock %40 9 3 %48
+OpLine %5 12 1
+%main = OpFunction %void None %36
+%bb_entry = OpLabel
+%70 = OpExtInst %void %1 DebugScope %47
+OpLine %5 13 16
+%param_var_arg1 = OpVariable %_ptr_Function_float Function
+%53 = OpLoad %float %pos
+OpStore %param_var_arg1 %53
+OpLine %5 13 10
+%54 = OpFunctionCall %v4float %func1 %param_var_arg1
+OpLine %5 13 3
+OpStore %gl_Position %54
+%71 = OpExtInst %void %1 DebugNoScope
+OpReturn
+OpFunctionEnd
+OpLine %5 5 1
+%func1 = OpFunction %v4float None %38
+OpLine %5 5 20
+%arg1 = OpFunctionParameter %_ptr_Function_float
+%bb_entry_0 = OpLabel
+%72 = OpExtInst %void %1 DebugScope %48
+OpLine %5 9 16
+%param_var_arg2 = OpVariable %_ptr_Function_float Function
+OpLine %5 6 7
+%57 = OpLoad %float %arg1
+OpLine %5 6 12
+%58 = OpFOrdGreaterThan %bool %57 %float_1
+OpLine %5 6 17
+%73 = OpExtInst %void %1 DebugNoScope
+OpSelectionMerge %if_merge None
+OpBranchConditional %58 %if_true %if_merge
+%if_true = OpLabel
+%74 = OpExtInst %void %1 DebugScope %50
+OpLine %5 7 5
+%75 = OpExtInst %void %1 DebugNoScope
+OpReturnValue %32
+%if_merge = OpLabel
+%76 = OpExtInst %void %1 DebugScope %51
+OpLine %5 9 16
+%63 = OpLoad %float %arg1
+OpStore %param_var_arg2 %63
+OpLine %5 9 10
+%64 = OpFunctionCall %v4float %func2 %param_var_arg2
+OpLine %5 9 3
+%77 = OpExtInst %void %1 DebugNoScope
+OpReturnValue %64
+OpFunctionEnd
+OpLine %5 1 1
+%func2 = OpFunction %v4float None %38
+OpLine %5 1 20
+%arg2 = OpFunctionParameter %_ptr_Function_float
+%bb_entry_1 = OpLabel
+%78 = OpExtInst %void %1 DebugScope %49
+OpLine %5 2 17
+%67 = OpLoad %float %arg2
+%68 = OpCompositeConstruct %v4float %67 %float_0 %float_0 %float_0
+OpLine %5 2 3
+%79 = OpExtInst %void %1 DebugNoScope
+OpReturnValue %68
+OpFunctionEnd
+)";
+
+  SpirvTools t(SPV_ENV_UNIVERSAL_1_1);
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text);
+  ASSERT_NE(nullptr, context);
+
+  std::vector<uint32_t> binary;
+  context->module()->ToBinary(&binary, /* skip_nop = */ false);
+
+  std::string disassembled_text;
+  EXPECT_TRUE(t.Disassemble(binary, &disassembled_text));
+  EXPECT_EQ(text, disassembled_text);
+}
+
+TEST(IrBuilder, DebugInfoInstInFunctionOutOfBlock2) {
+  // /* HLSL */
+  //
+  // struct VS_OUTPUT {
+  //   float4 pos : SV_POSITION;
+  //   float4 color : COLOR;
+  // };
+  //
+  // VS_OUTPUT main(float4 pos : POSITION,
+  //                float4 color : COLOR) {
+  //   VS_OUTPUT vout;
+  //   vout.pos = 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 "vs.hlsl"
+OpSource HLSL 600 %7 "#line 1 \"vs.hlsl\"
+struct VS_OUTPUT {
+  float4 pos : SV_POSITION;
+  float4 color : COLOR;
+};
+
+VS_OUTPUT main(float4 pos : POSITION,
+               float4 color : COLOR) {
+  VS_OUTPUT vout;
+  vout.pos = pos;
+  vout.color = color;
+  return vout;
+}
+"
+%8 = OpString "#line 1 \"vs.hlsl\"
+struct VS_OUTPUT {
+  float4 pos : SV_POSITION;
+  float4 color : COLOR;
+};
+
+VS_OUTPUT main(float4 pos : POSITION,
+               float4 color : COLOR) {
+  VS_OUTPUT vout;
+  vout.pos = pos;
+  vout.color = color;
+  return vout;
+}
+"
+%9 = OpString "VS_OUTPUT"
+%10 = OpString "float"
+%11 = OpString "src.main"
+%12 = OpString "pos"
+%13 = OpString "color"
+%14 = 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"
+OpName %src_main "src.main"
+OpName %pos "pos"
+OpName %color "color"
+OpName %bb_entry "bb.entry"
+OpName %vout "vout"
+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_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_0 = OpConstant %uint 0
+%uint_128 = OpConstant %uint 128
+%36 = OpTypeFunction %void
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%VS_OUTPUT = OpTypeStruct %v4float %v4float
+%38 = OpTypeFunction %VS_OUTPUT %_ptr_Function_v4float %_ptr_Function_v4float
+%_ptr_Function_VS_OUTPUT = OpTypePointer Function %VS_OUTPUT
+OpLine %7 6 29
+%in_var_POSITION = OpVariable %_ptr_Input_v4float Input
+OpLine %7 7 31
+%in_var_COLOR = OpVariable %_ptr_Input_v4float Input
+OpLine %7 2 16
+%gl_Position = OpVariable %_ptr_Output_v4float Output
+OpLine %7 3 18
+%out_var_COLOR = OpVariable %_ptr_Output_v4float Output
+%40 = OpExtInst %void %1 DebugExpression
+%41 = OpExtInst %void %1 DebugSource %7 %8
+%42 = OpExtInst %void %1 DebugCompilationUnit 1 4 %41 HLSL
+%43 = OpExtInst %void %1 DebugTypeComposite %9 Structure %41 1 1 %42 %9 %uint_256 FlagIsProtected|FlagIsPrivate %44 %45
+%46 = OpExtInst %void %1 DebugTypeBasic %10 %uint_32 Float
+%47 = OpExtInst %void %1 DebugTypeVector %46 4
+%48 = OpExtInst %void %1 DebugTypeFunction FlagIsProtected|FlagIsPrivate %43 %47 %47
+%49 = OpExtInst %void %1 DebugFunction %11 %48 %41 6 1 %42 %11 FlagIsProtected|FlagIsPrivate 7 %src_main
+%50 = OpExtInst %void %1 DebugLocalVariable %12 %47 %41 6 23 %49 FlagIsLocal 0
+%51 = OpExtInst %void %1 DebugLocalVariable %13 %47 %41 7 23 %49 FlagIsLocal 1
+%52 = OpExtInst %void %1 DebugLexicalBlock %41 7 38 %49
+%53 = OpExtInst %void %1 DebugLocalVariable %14 %43 %41 8 13 %52 FlagIsLocal
+%44 = OpExtInst %void %1 DebugTypeMember %12 %47 %41 2 3 %43 %uint_0 %uint_128 FlagIsProtected|FlagIsPrivate
+%45 = OpExtInst %void %1 DebugTypeMember %13 %47 %41 3 3 %43 %uint_128 %uint_128 FlagIsProtected|FlagIsPrivate
+OpLine %7 6 1
+%main = OpFunction %void None %36
+%54 = OpLabel
+%74 = OpExtInst %void %1 DebugScope %42
+OpLine %7 6 23
+%param_var_pos = OpVariable %_ptr_Function_v4float Function
+OpLine %7 7 23
+%param_var_color = OpVariable %_ptr_Function_v4float Function
+OpLine %7 6 23
+%56 = OpLoad %v4float %in_var_POSITION
+OpStore %param_var_pos %56
+OpLine %7 7 23
+%57 = OpLoad %v4float %in_var_COLOR
+OpStore %param_var_color %57
+OpLine %7 6 1
+%58 = OpFunctionCall %VS_OUTPUT %src_main %param_var_pos %param_var_color
+OpLine %7 6 11
+%59 = OpCompositeExtract %v4float %58 0
+OpLine %7 2 16
+OpStore %gl_Position %59
+OpLine %7 6 11
+%60 = OpCompositeExtract %v4float %58 1
+OpLine %7 3 18
+OpStore %out_var_COLOR %60
+%75 = OpExtInst %void %1 DebugNoScope
+OpReturn
+OpFunctionEnd
+OpLine %7 6 1
+%src_main = OpFunction %VS_OUTPUT None %38
+%76 = OpExtInst %void %1 DebugScope %49
+OpLine %7 6 23
+%pos = OpFunctionParameter %_ptr_Function_v4float
+OpLine %7 7 23
+%color = OpFunctionParameter %_ptr_Function_v4float
+%63 = OpExtInst %void %1 DebugDeclare %50 %pos %40
+%64 = OpExtInst %void %1 DebugDeclare %51 %color %40
+%77 = OpExtInst %void %1 DebugNoScope
+%bb_entry = OpLabel
+%78 = OpExtInst %void %1 DebugScope %52
+OpLine %7 8 13
+%vout = OpVariable %_ptr_Function_VS_OUTPUT Function
+%67 = OpExtInst %void %1 DebugDeclare %53 %vout %40
+OpLine %7 9 14
+%68 = OpLoad %v4float %pos
+OpLine %7 9 3
+%69 = OpAccessChain %_ptr_Function_v4float %vout %int_0
+OpStore %69 %68
+OpLine %7 10 16
+%70 = OpLoad %v4float %color
+OpLine %7 10 3
+%71 = OpAccessChain %_ptr_Function_v4float %vout %int_1
+OpStore %71 %70
+OpLine %7 11 10
+%72 = OpLoad %VS_OUTPUT %vout
+OpLine %7 11 3
+%79 = OpExtInst %void %1 DebugNoScope
+OpReturnValue %72
+OpFunctionEnd
+)";
+
+  SpirvTools t(SPV_ENV_UNIVERSAL_1_1);
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text);
+  ASSERT_NE(nullptr, context);
+
+  std::vector<uint32_t> binary;
+  context->module()->ToBinary(&binary, /* skip_nop = */ false);
+
+  std::string disassembled_text;
+  EXPECT_TRUE(t.Disassemble(binary, &disassembled_text));
+  EXPECT_EQ(text, disassembled_text);
+}
+
 TEST(IrBuilder, LocalGlobalVariables) {
   // #version 310 es
   //
diff --git a/test/opt/local_redundancy_elimination_test.cpp b/test/opt/local_redundancy_elimination_test.cpp
index bc4635e..291e1bc 100644
--- a/test/opt/local_redundancy_elimination_test.cpp
+++ b/test/opt/local_redundancy_elimination_test.cpp
@@ -154,6 +154,50 @@
   SinglePassRunAndMatch<LocalRedundancyEliminationPass>(text, false);
 }
 
+TEST_F(LocalRedundancyEliminationTest, StorageBufferIdentification) {
+  const std::string text = R"(
+; CHECK: [[gep:%\w+]] = OpAccessChain
+; CHECK: [[ld:%\w+]] = OpLoad {{%\w+}} [[gep]]
+; CHECK: [[add:%\w+]] = OpIAdd {{%\w+}} [[ld]]
+; CHECK: OpStore [[gep]] [[add]]
+; CHECK: [[ld:%\w+]] = OpLoad {{%\w+}} [[gep]]
+; CHECK: [[add:%\w+]] = OpIAdd {{%\w+}} [[ld]]
+; CHECK: OpStore [[gep]] [[add]]
+
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %block BufferBlock
+OpMemberDecorate %block 0 Offset 0
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%block = OpTypeStruct %int
+%array = OpTypeArray %block %int_1
+%ptr_ssbo_array = OpTypePointer Uniform %array
+%ptr_ssbo_int = OpTypePointer Uniform %int
+%var = OpVariable %ptr_ssbo_array Uniform
+%void_fn = OpTypeFunction %void
+%fn = OpFunction %void None %void_fn
+%entry = OpLabel
+%gep1 = OpAccessChain %ptr_ssbo_int %var %int_0 %int_0
+%ld1 = OpLoad %int %gep1
+%add1 = OpIAdd %int %ld1 %int_1
+%gep2 = OpAccessChain %ptr_ssbo_int %var %int_0 %int_0
+OpStore %gep2 %add1
+%gep3 = OpAccessChain %ptr_ssbo_int %var %int_0 %int_0
+%ld3 = OpLoad %int %gep3
+%add3 = OpIAdd %int %ld3 %int_1
+%gep4 = OpAccessChain %ptr_ssbo_int %var %int_0 %int_0
+OpStore %gep4 %add3
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<LocalRedundancyEliminationPass>(text, true);
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/loop_optimizations/loop_descriptions.cpp b/test/opt/loop_optimizations/loop_descriptions.cpp
index 91dbdc6..4d2f989 100644
--- a/test/opt/loop_optimizations/loop_descriptions.cpp
+++ b/test/opt/loop_optimizations/loop_descriptions.cpp
@@ -379,6 +379,43 @@
   EXPECT_EQ(loop.GetLatchBlock()->id(), 30u);
 }
 
+TEST_F(PassClassTest, UnreachableMerge) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %1 "main"
+               OpExecutionMode %1 OriginUpperLeft
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+          %1 = OpFunction %void None %3
+          %4 = OpLabel
+               OpBranch %5
+          %5 = OpLabel
+               OpLoopMerge %6 %7 None
+               OpBranch %8
+          %8 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+               OpBranch %7
+          %7 = OpLabel
+               OpBranch %5
+          %6 = OpLabel
+               OpUnreachable
+               OpFunctionEnd
+)";
+
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_3, nullptr, text,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  Module* module = context->module();
+  EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+                             << text << std::endl;
+  const Function* f = spvtest::GetFunction(module, 1);
+  LoopDescriptor ld{context.get(), f};
+
+  EXPECT_EQ(ld.NumLoops(), 1u);
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/pass_merge_return_test.cpp b/test/opt/pass_merge_return_test.cpp
index 4118169..d16b65c 100644
--- a/test/opt/pass_merge_return_test.cpp
+++ b/test/opt/pass_merge_return_test.cpp
@@ -268,7 +268,7 @@
 ; CHECK: [[true:%\w+]] = OpConstantTrue
 ; CHECK: OpFunction
 ; CHECK: [[var:%\w+]] = OpVariable [[:%\w+]] Function [[false]]
-; CHECK: OpLoopMerge [[return_block:%\w+]]
+; CHECK: OpSelectionMerge [[return_block:%\w+]]
 ; CHECK: OpSelectionMerge [[merge_lab:%\w+]]
 ; CHECK: OpBranchConditional [[cond:%\w+]] [[if_lab:%\w+]] [[then_lab:%\w+]]
 ; CHECK: [[if_lab]] = OpLabel
@@ -314,7 +314,7 @@
 ; CHECK: [[true:%\w+]] = OpConstantTrue
 ; CHECK: OpFunction
 ; CHECK: [[var:%\w+]] = OpVariable [[:%\w+]] Function [[false]]
-; CHECK: OpLoopMerge [[dummy_loop_merge:%\w+]]
+; CHECK: OpSelectionMerge [[dummy_loop_merge:%\w+]]
 ; CHECK: OpSelectionMerge [[merge_lab:%\w+]]
 ; CHECK: OpBranchConditional [[cond:%\w+]] [[if_lab:%\w+]] [[then_lab:%\w+]]
 ; CHECK: [[if_lab]] = OpLabel
@@ -364,7 +364,7 @@
 ; CHECK: [[true:%\w+]] = OpConstantTrue
 ; CHECK: OpFunction
 ; CHECK: [[var:%\w+]] = OpVariable [[:%\w+]] Function [[false]]
-; CHECK: OpLoopMerge [[return_block:%\w+]]
+; CHECK: OpSelectionMerge [[return_block:%\w+]]
 ; CHECK: OpSelectionMerge [[merge_lab:%\w+]]
 ; CHECK: OpBranchConditional [[cond:%\w+]] [[if_lab:%\w+]] [[then_lab:%\w+]]
 ; CHECK: [[if_lab]] = OpLabel
@@ -411,7 +411,7 @@
   const std::string before =
       R"(
 ; CHECK: OpFunction
-; CHECK: OpLoopMerge [[dummy_loop_merge:%\w+]]
+; CHECK: OpSelectionMerge [[dummy_loop_merge:%\w+]]
 ; CHECK: OpLoopMerge [[loop_merge:%\w+]]
 ; CHECK: [[loop_merge]] = OpLabel
 ; CHECK: OpBranchConditional {{%\w+}} [[dummy_loop_merge]] [[old_code_path:%\w+]]
@@ -525,7 +525,7 @@
       R"(
 ; CHECK: OpFunction
 ; CHECK: [[ret_flag:%\w+]] = OpVariable %_ptr_Function_bool Function %false
-; CHECK: OpLoopMerge [[dummy_loop_merge:%\w+]]
+; CHECK: OpSelectionMerge [[dummy_loop_merge:%\w+]]
 ; CHECK: OpLoopMerge [[loop1_merge:%\w+]] {{%\w+}}
 ; CHECK-NEXT: OpBranchConditional {{%\w+}} [[if_lab:%\w+]] {{%\w+}}
 ; CHECK: [[if_lab]] = OpLabel
@@ -914,7 +914,7 @@
   const std::string test =
       R"(
 ; CHECK: OpFunction
-; CHECK: OpLoopMerge [[dummy_loop_merge:%\w+]]
+; CHECK: OpSelectionMerge [[dummy_loop_merge:%\w+]]
 ; CHECK: OpLoopMerge [[outer_loop_merge:%\w+]]
 ; CHECK: OpLoopMerge [[inner_loop_merge:%\w+]]
 ; CHECK: OpSelectionMerge
@@ -1150,7 +1150,6 @@
       R"(
 ; CHECK: OpFunction %void
 ; CHECK: OpLabel
-; CHECK: OpLabel
 ; CHECK: [[pre_header:%\w+]] = OpLabel
 ; CHECK: [[header:%\w+]] = OpLabel
 ; CHECK-NEXT: OpPhi %bool {{%\w+}} [[pre_header]] [[iv:%\w+]] [[continue:%\w+]]
@@ -1196,7 +1195,6 @@
       R"(
 ; CHECK: OpFunction %void
 ; CHECK: OpLabel
-; CHECK: OpLabel
 ; CHECK: [[pre_header:%\w+]] = OpLabel
 ; CHECK: [[header:%\w+]] = OpLabel
 ; CHECK-NEXT: OpPhi
@@ -1258,7 +1256,9 @@
 TEST_F(MergeReturnPassTest, GeneratePhiInOuterLoop) {
   const std::string before =
       R"(
-      ; CHECK: OpLoopMerge
+      ; CHECK: OpSelectionMerge
+      ; CHECK-NEXT: OpSwitch {{%\w+}} [[def_bb1:%\w+]]
+      ; CHECK-NEXT: [[def_bb1]] = OpLabel
       ; CHECK: OpLoopMerge [[merge:%\w+]] [[continue:%\w+]]
       ; CHECK: [[continue]] = OpLabel
       ; CHECK-NEXT: [[undef:%\w+]] = OpUndef
@@ -1322,7 +1322,9 @@
   // #2455: This test case triggers phi insertions that use previously inserted
   // phis. Without the fix, it fails to validate.
   const std::string spirv = R"(
-; CHECK: OpLoopMerge
+; CHECK: OpSelectionMerge
+; CHECK-NEXT: OpSwitch {{%\w+}} [[def_bb1:%\w+]]
+; CHECK-NEXT: [[def_bb1]] = OpLabel
 ; CHECK: OpLoopMerge
 ; CHECK: OpLoopMerge
 ; CHECK: OpLoopMerge [[merge:%\w+]]
@@ -1430,9 +1432,9 @@
 TEST_F(MergeReturnPassTest, InnerLoopMergeIsOuterLoopContinue) {
   const std::string before =
       R"(
-      ; CHECK: OpLoopMerge
-      ; CHECK-NEXT: OpBranch [[bb1:%\w+]]
-      ; CHECK: [[bb1]] = OpLabel
+      ; CHECK: OpSelectionMerge
+      ; CHECK-NEXT: OpSwitch {{%\w+}} [[def_bb1:%\w+]]
+      ; CHECK-NEXT: [[def_bb1]] = OpLabel
       ; CHECK-NEXT: OpBranch [[outer_loop_header:%\w+]]
       ; CHECK: [[outer_loop_header]] = OpLabel
       ; CHECK-NEXT: OpLoopMerge [[outer_loop_merge:%\w+]] [[outer_loop_continue:%\w+]] None
@@ -1481,7 +1483,9 @@
 TEST_F(MergeReturnPassTest, BreakFromLoopUseNoLongerDominated) {
   const std::string spirv = R"(
 ; CHECK: [[undef:%\w+]] = OpUndef
-; CHECK: OpLoopMerge
+; CHECK: OpSelectionMerge
+; CHECK-NEXT: OpSwitch {{%\w+}} [[def_bb1:%\w+]]
+; CHECK-NEXT: [[def_bb1]] = OpLabel
 ; CHECK: OpLoopMerge [[merge:%\w+]] [[cont:%\w+]]
 ; CHECK-NEXT: OpBranch [[body:%\w+]]
 ; CHECK: [[body]] = OpLabel
@@ -1541,7 +1545,9 @@
 TEST_F(MergeReturnPassTest, TwoBreaksFromLoopUsesNoLongerDominated) {
   const std::string spirv = R"(
 ; CHECK: [[undef:%\w+]] = OpUndef
-; CHECK: OpLoopMerge
+; CHECK: OpSelectionMerge
+; CHECK-NEXT: OpSwitch {{%\w+}} [[def_bb1:%\w+]]
+; CHECK-NEXT: [[def_bb1]] = OpLabel
 ; CHECK: OpLoopMerge [[merge:%\w+]] [[cont:%\w+]]
 ; CHECK-NEXT: OpBranch [[body:%\w+]]
 ; CHECK: [[body]] = OpLabel
@@ -1725,7 +1731,9 @@
   const std::string text =
       R"(
 ; CHECK: [[new_undef:%\w+]] = OpUndef %uint
-; CHECK: OpLoopMerge
+; CHECK: OpSelectionMerge
+; CHECK-NEXT: OpSwitch {{%\w+}} [[def_bb1:%\w+]]
+; CHECK-NEXT: [[def_bb1]] = OpLabel
 ; CHECK: OpLoopMerge [[merge1:%\w+]]
 ; CHECK: OpLoopMerge [[merge2:%\w+]]
 ; CHECK: [[merge1]] = OpLabel
@@ -1781,7 +1789,9 @@
   //  Add and use a phi in the second merge block from the return.
   const std::string text =
       R"(
-; CHECK: OpLoopMerge
+; CHECK: OpSelectionMerge
+; CHECK-NEXT: OpSwitch {{%\w+}} [[def_bb1:%\w+]]
+; CHECK-NEXT: [[def_bb1]] = OpLabel
 ; CHECK: OpLoopMerge [[merge_bb:%\w+]] [[continue_bb:%\w+]]
 ; CHECK: [[continue_bb]] = OpLabel
 ; CHECK-NEXT: [[val:%\w+]] = OpUndef %float
@@ -1831,6 +1841,91 @@
   SinglePassRunAndMatch<MergeReturnPass>(text, true);
 }
 
+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.
+  const std::string text =
+      R"(
+; CHECK: OpSelectionMerge [[dummy_merge_bb:%\w+]]
+; CHECK-NEXT: OpSwitch {{%\w+}} [[def_bb1:%\w+]]
+; CHECK-NEXT: [[def_bb1]] = OpLabel
+; CHECK: OpSelectionMerge
+; CHECK-NEXT: OpSwitch {{%\w+}} [[inner_merge_bb:%\w+]] 0 {{%\w+}} 1 {{%\w+}}
+; CHECK: OpBranch [[inner_merge_bb]]
+; CHECK: OpBranch [[inner_merge_bb]]
+; CHECK-NEXT: [[inner_merge_bb]] = OpLabel
+; CHECK: OpBranchConditional {{%\w+}} [[dummy_merge_bb]] {{%\w+}}
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %PSMain "PSMain" %_entryPointOutput_color
+               OpExecutionMode %PSMain OriginUpperLeft
+               OpSource HLSL 500
+               OpMemberDecorate %cb 0 Offset 0
+               OpMemberDecorate %cb 1 Offset 16
+               OpMemberDecorate %cb 2 Offset 32
+               OpMemberDecorate %cb 3 Offset 48
+               OpDecorate %cb Block
+               OpDecorate %_ DescriptorSet 0
+               OpDecorate %_ Binding 0
+               OpDecorate %_entryPointOutput_color Location 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+          %8 = OpTypeFunction %v4float
+        %int = OpTypeInt 32 1
+         %cb = OpTypeStruct %v4float %v4float %v4float %int
+%_ptr_Uniform_cb = OpTypePointer Uniform %cb
+          %_ = OpVariable %_ptr_Uniform_cb Uniform
+      %int_3 = OpConstant %int 3
+%_ptr_Uniform_int = OpTypePointer Uniform %int
+      %int_0 = OpConstant %int 0
+%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float
+      %int_1 = OpConstant %int 1
+      %int_2 = OpConstant %int 2
+    %float_0 = OpConstant %float 0
+    %float_1 = OpConstant %float 1
+         %45 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_1
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_color = OpVariable %_ptr_Output_v4float Output
+     %PSMain = OpFunction %void None %3
+          %5 = OpLabel
+         %50 = OpFunctionCall %v4float %BlendValue_
+               OpStore %_entryPointOutput_color %50
+               OpReturn
+               OpFunctionEnd
+%BlendValue_ = OpFunction %v4float None %8
+         %10 = OpLabel
+         %21 = OpAccessChain %_ptr_Uniform_int %_ %int_3
+         %22 = OpLoad %int %21
+               OpSelectionMerge %25 None
+               OpSwitch %22 %25 0 %23 1 %24
+         %23 = OpLabel
+         %28 = OpAccessChain %_ptr_Uniform_v4float %_ %int_0
+         %29 = OpLoad %v4float %28
+               OpReturnValue %29
+         %24 = OpLabel
+         %31 = OpAccessChain %_ptr_Uniform_v4float %_ %int_0
+         %32 = OpLoad %v4float %31
+         %34 = OpAccessChain %_ptr_Uniform_v4float %_ %int_1
+         %35 = OpLoad %v4float %34
+         %37 = OpAccessChain %_ptr_Uniform_v4float %_ %int_2
+         %38 = OpLoad %v4float %37
+         %39 = OpFMul %v4float %35 %38
+         %40 = OpFAdd %v4float %32 %39
+               OpReturnValue %40
+         %25 = OpLabel
+               OpReturnValue %45
+               OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<MergeReturnPass>(text, true);
+}
+
 TEST_F(MergeReturnPassTest, UnreachableMergeAndContinue) {
   // Make sure that the pass can handle a single block that is both a merge and
   // a continue.
diff --git a/test/opt/type_manager_test.cpp b/test/opt/type_manager_test.cpp
index 743d0b6..fdae2ef 100644
--- a/test/opt/type_manager_test.cpp
+++ b/test/opt/type_manager_test.cpp
@@ -1063,7 +1063,7 @@
 ; CHECK: OpTypeForwardPointer [[uniform_ptr]] Uniform
 ; CHECK: OpTypePipeStorage
 ; CHECK: OpTypeNamedBarrier
-; CHECK: OpTypeAccelerationStructureNV
+; CHECK: OpTypeAccelerationStructureKHR
 ; CHECK: OpTypeCooperativeMatrixNV [[f32]] [[uint24]] [[uint24]] [[uint24]]
 OpCapability Shader
 OpCapability Int64
diff --git a/test/opt/wrap_opkill_test.cpp b/test/opt/wrap_opkill_test.cpp
index d50af28..b6b6a23 100644
--- a/test/opt/wrap_opkill_test.cpp
+++ b/test/opt/wrap_opkill_test.cpp
@@ -513,6 +513,40 @@
   EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
 }
 
+TEST_F(WrapOpKillTest, SetParentBlock) {
+  const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+%void = OpTypeVoid
+%bool = OpTypeBool
+%undef = OpUndef %bool
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpBranch %loop
+%loop = OpLabel
+OpLoopMerge %merge %continue None
+OpBranchConditional %undef %merge %continue
+%continue = OpLabel
+%call = OpFunctionCall %void %kill_func
+OpBranch %loop
+%merge = OpLabel
+OpReturn
+OpFunctionEnd
+%kill_func = OpFunction %void None %void_fn
+%kill_entry = OpLabel
+OpKill
+OpFunctionEnd
+)";
+
+  auto result = SinglePassRunToBinary<WrapOpKill>(text, true);
+  EXPECT_EQ(Pass::Status::SuccessWithChange, std::get<1>(result));
+  result = SinglePassRunToBinary<WrapOpKill>(text, true);
+  EXPECT_EQ(Pass::Status::SuccessWithChange, std::get<1>(result));
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/target_env_test.cpp b/test/target_env_test.cpp
index 5dc7415..9c86e2d 100644
--- a/test/target_env_test.cpp
+++ b/test/target_env_test.cpp
@@ -63,11 +63,13 @@
 
 using TargetParseTest = ::testing::TestWithParam<ParseCase>;
 
-TEST_P(TargetParseTest, InvalidTargetEnvProducesNull) {
+TEST_P(TargetParseTest, Samples) {
   spv_target_env env;
   bool parsed = spvParseTargetEnv(GetParam().input, &env);
   EXPECT_THAT(parsed, Eq(GetParam().success));
-  EXPECT_THAT(env, Eq(GetParam().env));
+  if (parsed) {
+    EXPECT_THAT(env, Eq(GetParam().env));
+  }
 }
 
 INSTANTIATE_TEST_SUITE_P(
@@ -103,5 +105,61 @@
         {"abc", false, SPV_ENV_UNIVERSAL_1_0},
     }));
 
+// A test case for parsing an environment string.
+struct ParseVulkanCase {
+  uint32_t vulkan;
+  uint32_t spirv;
+  bool success;        // Expect to successfully parse?
+  spv_target_env env;  // The parsed environment, if successful.
+};
+
+using TargetParseVulkanTest = ::testing::TestWithParam<ParseVulkanCase>;
+
+TEST_P(TargetParseVulkanTest, Samples) {
+  spv_target_env env;
+  bool parsed = spvParseVulkanEnv(GetParam().vulkan, GetParam().spirv, &env);
+  EXPECT_THAT(parsed, Eq(GetParam().success));
+  if (parsed) {
+    EXPECT_THAT(env, Eq(GetParam().env));
+  }
+}
+
+#define VK(MAJ, MIN) ((MAJ << 22) | (MIN << 12))
+#define SPV(MAJ, MIN) ((MAJ << 16) | (MIN << 8))
+INSTANTIATE_TEST_SUITE_P(
+    TargetVulkanParsing, TargetParseVulkanTest,
+    ValuesIn(std::vector<ParseVulkanCase>{
+        // Vulkan 1.0 cases
+        {VK(1, 0), SPV(1, 0), true, SPV_ENV_VULKAN_1_0},
+        {VK(1, 0), SPV(1, 1), true, SPV_ENV_VULKAN_1_1},
+        {VK(1, 0), SPV(1, 2), true, SPV_ENV_VULKAN_1_1},
+        {VK(1, 0), SPV(1, 3), true, SPV_ENV_VULKAN_1_1},
+        {VK(1, 0), SPV(1, 4), true, SPV_ENV_VULKAN_1_1_SPIRV_1_4},
+        {VK(1, 0), SPV(1, 5), true, SPV_ENV_VULKAN_1_2},
+        {VK(1, 0), SPV(1, 6), false, SPV_ENV_UNIVERSAL_1_0},
+        // Vulkan 1.1 cases
+        {VK(1, 1), SPV(1, 0), true, SPV_ENV_VULKAN_1_1},
+        {VK(1, 1), SPV(1, 1), true, SPV_ENV_VULKAN_1_1},
+        {VK(1, 1), SPV(1, 2), true, SPV_ENV_VULKAN_1_1},
+        {VK(1, 1), SPV(1, 3), true, SPV_ENV_VULKAN_1_1},
+        {VK(1, 1), SPV(1, 4), true, SPV_ENV_VULKAN_1_1_SPIRV_1_4},
+        {VK(1, 1), SPV(1, 5), true, SPV_ENV_VULKAN_1_2},
+        {VK(1, 1), SPV(1, 6), false, SPV_ENV_UNIVERSAL_1_0},
+        // Vulkan 1.2 cases
+        {VK(1, 2), SPV(1, 0), true, SPV_ENV_VULKAN_1_2},
+        {VK(1, 2), SPV(1, 1), true, SPV_ENV_VULKAN_1_2},
+        {VK(1, 2), SPV(1, 2), true, SPV_ENV_VULKAN_1_2},
+        {VK(1, 2), SPV(1, 3), true, SPV_ENV_VULKAN_1_2},
+        {VK(1, 2), SPV(1, 4), true, SPV_ENV_VULKAN_1_2},
+        {VK(1, 2), SPV(1, 5), true, SPV_ENV_VULKAN_1_2},
+        {VK(1, 2), SPV(1, 6), false, SPV_ENV_UNIVERSAL_1_0},
+        // Vulkan 1.3 cases
+        {VK(1, 3), SPV(1, 0), false, SPV_ENV_UNIVERSAL_1_0},
+        // Vulkan 2.0 cases
+        {VK(2, 0), SPV(1, 0), false, SPV_ENV_UNIVERSAL_1_0},
+        // Vulkan 99.0 cases
+        {VK(99, 0), SPV(1, 0), false, SPV_ENV_UNIVERSAL_1_0},
+    }));
+
 }  // namespace
 }  // namespace spvtools
diff --git a/test/val/val_barriers_test.cpp b/test/val/val_barriers_test.cpp
index ae166d9..fa2b153 100644
--- a/test/val/val_barriers_test.cpp
+++ b/test/val/val_barriers_test.cpp
@@ -120,7 +120,7 @@
       execution_model, memory_model);
 }
 
-std::string GenerateWebGPUShaderCode(
+std::string GenerateWebGPUComputeShaderCode(
     const std::string& body,
     const std::string& capabilities_and_extensions = "",
     const std::string& execution_model = "GLCompute") {
@@ -138,6 +138,24 @@
                                 "", execution_model, memory_model);
 }
 
+std::string GenerateWebGPUVertexShaderCode(
+    const std::string& body,
+    const std::string& capabilities_and_extensions = "",
+    const std::string& execution_model = "Vertex") {
+  const std::string vulkan_memory_capability = R"(
+OpCapability VulkanMemoryModelKHR
+)";
+  const std::string vulkan_memory_extension = R"(
+OpExtension "SPV_KHR_vulkan_memory_model"
+)";
+  const std::string memory_model = "OpMemoryModel Logical VulkanKHR";
+  return GenerateShaderCodeImpl(body,
+                                vulkan_memory_capability +
+                                    capabilities_and_extensions +
+                                    vulkan_memory_extension,
+                                "", execution_model, memory_model);
+}
+
 std::string GenerateKernelCode(
     const std::string& body,
     const std::string& capabilities_and_extensions = "") {
@@ -257,7 +275,7 @@
 OpControlBarrier %workgroup %workgroup %acquire_release_workgroup
 )";
 
-  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  CompileSuccessfully(GenerateWebGPUComputeShaderCode(body), SPV_ENV_WEBGPU_0);
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
 }
 
@@ -266,7 +284,7 @@
 OpControlBarrier %workgroup %workgroup %workgroup
 )";
 
-  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  CompileSuccessfully(GenerateWebGPUComputeShaderCode(body), SPV_ENV_WEBGPU_0);
   EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("For WebGPU, AcquireRelease must be set for Memory "
@@ -278,7 +296,7 @@
 OpControlBarrier %workgroup %workgroup %acquire_release
 )";
 
-  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  CompileSuccessfully(GenerateWebGPUComputeShaderCode(body), SPV_ENV_WEBGPU_0);
   EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("For WebGPU, WorkgroupMemory must be set for Memory "
@@ -290,7 +308,7 @@
 OpControlBarrier %workgroup %workgroup %acquire_release_uniform_workgroup
 )";
 
-  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  CompileSuccessfully(GenerateWebGPUComputeShaderCode(body), SPV_ENV_WEBGPU_0);
   EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
   EXPECT_THAT(
       getDiagnosticString(),
@@ -303,7 +321,7 @@
 OpControlBarrier %workgroup %workgroup %release_uniform_workgroup
 )";
 
-  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  CompileSuccessfully(GenerateWebGPUComputeShaderCode(body), SPV_ENV_WEBGPU_0);
   EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("For WebGPU, AcquireRelease must be set for Memory "
@@ -421,7 +439,7 @@
 OpControlBarrier %device %workgroup %none
 )";
 
-  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  CompileSuccessfully(GenerateWebGPUComputeShaderCode(body), SPV_ENV_WEBGPU_0);
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("ControlBarrier: in WebGPU environment Execution Scope "
@@ -433,13 +451,27 @@
 OpControlBarrier %subgroup %workgroup %none
 )";
 
-  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  CompileSuccessfully(GenerateWebGPUComputeShaderCode(body), SPV_ENV_WEBGPU_0);
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("ControlBarrier: in WebGPU environment Execution Scope "
                         "is limited to Workgroup"));
 }
 
+TEST_F(ValidateBarriers,
+       OpControlBarrierWebGPUExecutionScopeWorkgroupNonComputeBad) {
+  const std::string body = R"(
+OpControlBarrier %workgroup %workgroup %acquire_release_workgroup
+)";
+
+  CompileSuccessfully(GenerateWebGPUVertexShaderCode(body), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Workgroup Execution Scope is limited to GLCompute execution model"));
+}
+
 TEST_F(ValidateBarriers, OpControlBarrierVulkanMemoryScopeSubgroup) {
   const std::string body = R"(
 OpControlBarrier %subgroup %subgroup %none
@@ -479,7 +511,7 @@
 OpControlBarrier %workgroup %subgroup %acquire_release_workgroup
 )";
 
-  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  CompileSuccessfully(GenerateWebGPUComputeShaderCode(body), SPV_ENV_WEBGPU_0);
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("ControlBarrier: in WebGPU environment Memory Scope is "
@@ -710,7 +742,7 @@
 OpMemoryBarrier %workgroup %image_memory
 )";
 
-  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  CompileSuccessfully(GenerateWebGPUComputeShaderCode(body), SPV_ENV_WEBGPU_0);
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
 }
 
@@ -719,7 +751,7 @@
 OpMemoryBarrier %subgroup %image_memory
 )";
 
-  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  CompileSuccessfully(GenerateWebGPUComputeShaderCode(body), SPV_ENV_WEBGPU_0);
   EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("in WebGPU environment Memory Scope is limited to "
@@ -731,7 +763,7 @@
 OpMemoryBarrier %workgroup %acquire_release_uniform_workgroup
 )";
 
-  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  CompileSuccessfully(GenerateWebGPUComputeShaderCode(body), SPV_ENV_WEBGPU_0);
   EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("ImageMemory must be set for Memory Semantics of "
@@ -743,7 +775,7 @@
 OpMemoryBarrier %workgroup %uniform_workgroup
 )";
 
-  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  CompileSuccessfully(GenerateWebGPUComputeShaderCode(body), SPV_ENV_WEBGPU_0);
   EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("ImageMemory must be set for Memory Semantics of "
@@ -755,7 +787,7 @@
 OpMemoryBarrier %workgroup %acquire_uniform_workgroup
 )";
 
-  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  CompileSuccessfully(GenerateWebGPUComputeShaderCode(body), SPV_ENV_WEBGPU_0);
   EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("ImageMemory must be set for Memory Semantics of "
@@ -767,7 +799,7 @@
 OpMemoryBarrier %workgroup %release_uniform_workgroup
 )";
 
-  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  CompileSuccessfully(GenerateWebGPUComputeShaderCode(body), SPV_ENV_WEBGPU_0);
   EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("ImageMemory must be set for Memory Semantics of "
@@ -779,13 +811,26 @@
 OpMemoryBarrier %workgroup %uniform_image_memory
 )";
 
-  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  CompileSuccessfully(GenerateWebGPUComputeShaderCode(body), SPV_ENV_WEBGPU_0);
   EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("only ImageMemory may be set for Memory Semantics of "
                         "OpMemoryBarrier"));
 }
 
+TEST_F(ValidateBarriers, OpMemoryBarrierWebGPUWorkgroupNonComputeFailure) {
+  const std::string body = R"(
+OpMemoryBarrier %workgroup %image_memory
+)";
+
+  CompileSuccessfully(GenerateWebGPUVertexShaderCode(body), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Workgroup Memory Scope is limited to GLCompute execution model"));
+}
+
 TEST_F(ValidateBarriers, OpMemoryBarrierFloatMemoryScope) {
   const std::string body = R"(
 OpMemoryBarrier %f32_1 %acquire_release_uniform_workgroup
diff --git a/test/val/val_capability_test.cpp b/test/val/val_capability_test.cpp
index 098fa2f..8580818 100644
--- a/test/val/val_capability_test.cpp
+++ b/test/val/val_capability_test.cpp
@@ -1229,14 +1229,18 @@
           "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
           ShaderDependencies()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
+          // Block applies to struct type.
           "OpEntryPoint Kernel %func \"compute\" \n"
-          "OpDecorate %intt Block\n"
-          "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
+          "OpDecorate %block Block\n"
+          "%intt = OpTypeInt 32 0\n"
+          "%block = OpTypeStruct %intt\n" + std::string(kVoidFVoid),
           ShaderDependencies()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
+          // BufferBlock applies to struct type.
           "OpEntryPoint Kernel %func \"compute\" \n"
-          "OpDecorate %intt BufferBlock\n"
-          "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
+          "OpDecorate %block BufferBlock\n"
+          "%intt = OpTypeInt 32 0\n"
+          "%block = OpTypeStruct %intt\n" + std::string(kVoidFVoid),
           ShaderDependencies()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n"
diff --git a/test/val/val_cfg_test.cpp b/test/val/val_cfg_test.cpp
index 8d8de37..0d09642 100644
--- a/test/val/val_cfg_test.cpp
+++ b/test/val/val_cfg_test.cpp
@@ -4187,6 +4187,322 @@
           "1[%loop], but its merge block 2[%continue] is not"));
 }
 
+TEST_F(ValidateCFG, ExitFromConstructWhoseHeaderIsAMerge) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%2 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%4 = OpUndef %int
+%bool = OpTypeBool
+%6 = OpUndef %bool
+%7 = OpFunction %void None %2
+%8 = OpLabel
+OpSelectionMerge %9 None
+OpSwitch %4 %10 0 %11
+%10 = OpLabel
+OpBranch %9
+%11 = OpLabel
+OpBranch %12
+%12 = OpLabel
+OpLoopMerge %13 %14 None
+OpBranch %15
+%15 = OpLabel
+OpSelectionMerge %16 None
+OpSwitch %4 %17 1 %18 2 %19
+%17 = OpLabel
+OpBranch %16
+%18 = OpLabel
+OpBranch %14
+%19 = OpLabel
+OpBranch %16
+%16 = OpLabel
+OpBranch %14
+%14 = OpLabel
+OpBranchConditional %6 %12 %13
+%13 = OpLabel
+OpSelectionMerge %20 None
+OpBranchConditional %6 %21 %20
+%21 = OpLabel
+OpBranch %9
+%20 = OpLabel
+OpBranch %10
+%9 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, ExitFromConstructWhoseHeaderIsAMerge2) {
+  const std::string text = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+       %void = OpTypeVoid
+          %4 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+          %6 = OpUndef %int
+       %bool = OpTypeBool
+          %8 = OpUndef %bool
+          %2 = OpFunction %void None %4
+          %9 = OpLabel
+               OpSelectionMerge %10 None
+               OpSwitch %6 %11 0 %12
+         %11 = OpLabel
+               OpBranch %10
+         %12 = OpLabel
+               OpBranch %13
+         %13 = OpLabel
+               OpLoopMerge %14 %15 None
+               OpBranch %16
+         %16 = OpLabel
+               OpSelectionMerge %17 None
+               OpSwitch %6 %18 1 %19 2 %20
+         %18 = OpLabel
+               OpBranch %17
+         %19 = OpLabel
+               OpBranch %15
+         %20 = OpLabel
+               OpBranch %17
+         %17 = OpLabel
+               OpBranch %15
+         %15 = OpLabel
+               OpBranchConditional %8 %13 %14
+         %14 = OpLabel
+               OpSelectionMerge %21 None
+               OpBranchConditional %8 %22 %21
+         %22 = OpLabel
+               OpSelectionMerge %23 None
+               OpBranchConditional %8 %24 %23
+         %24 = OpLabel
+               OpBranch %10
+         %23 = OpLabel
+               OpBranch %21
+         %21 = OpLabel
+               OpBranch %11
+         %10 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, PhiResultInvalidSampler) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%bool = OpTypeBool
+%f32 = OpTypeFloat 32
+%sampler = OpTypeSampler
+%ptr_uc_sampler = OpTypePointer UniformConstant %sampler
+%sampler_var = OpVariable %ptr_uc_sampler UniformConstant
+%undef_bool = OpUndef %bool
+%undef_sampler = OpUndef %sampler
+%void_fn = OpTypeFunction %void
+%fn = OpFunction %void None %void_fn
+%entry = OpLabel
+%ld_sampler = OpLoad %sampler %sampler_var
+OpBranch %loop
+%loop = OpLabel
+%phi = OpPhi %sampler %undef_sampler %entry %ld_sampler %loop
+OpLoopMerge %exit %loop None
+OpBranchConditional %undef_bool %exit %loop
+%exit = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Result type cannot be OpTypeSampler"));
+}
+
+TEST_F(ValidateCFG, PhiResultInvalidImage) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%bool = OpTypeBool
+%f32 = OpTypeFloat 32
+%image = OpTypeImage %f32 2D 0 0 0 1 Rgba32f
+%ptr_uc_image = OpTypePointer UniformConstant %image
+%image_var = OpVariable %ptr_uc_image UniformConstant
+%undef_bool = OpUndef %bool
+%undef_image = OpUndef %image
+%void_fn = OpTypeFunction %void
+%fn = OpFunction %void None %void_fn
+%entry = OpLabel
+%ld_image = OpLoad %image %image_var
+OpBranch %loop
+%loop = OpLabel
+%phi = OpPhi %image %undef_image %entry %ld_image %loop
+OpLoopMerge %exit %loop None
+OpBranchConditional %undef_bool %exit %loop
+%exit = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Result type cannot be OpTypeImage"));
+}
+
+TEST_F(ValidateCFG, PhiResultInvalidSampledImage) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%bool = OpTypeBool
+%f32 = OpTypeFloat 32
+%sampler = OpTypeSampler
+%ptr_uc_sampler = OpTypePointer UniformConstant %sampler
+%sampler_var = OpVariable %ptr_uc_sampler UniformConstant
+%image = OpTypeImage %f32 2D 0 0 0 1 Rgba32f
+%ptr_uc_image = OpTypePointer UniformConstant %image
+%image_var = OpVariable %ptr_uc_image UniformConstant
+%sampled_image = OpTypeSampledImage %image
+%undef_bool = OpUndef %bool
+%undef_sampled_image = OpUndef %sampled_image
+%void_fn = OpTypeFunction %void
+%fn = OpFunction %void None %void_fn
+%entry = OpLabel
+%ld_image = OpLoad %image %image_var
+%ld_sampler = OpLoad %sampler %sampler_var
+OpBranch %loop
+%loop = OpLabel
+%phi = OpPhi %sampled_image %undef_sampled_image %entry %sample %loop
+%sample = OpSampledImage %sampled_image %ld_image %ld_sampler
+OpLoopMerge %exit %loop None
+OpBranchConditional %undef_bool %exit %loop
+%exit = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Result type cannot be OpTypeSampledImage"));
+}
+
+TEST_F(ValidateCFG, PhiResultValidPreLegalizationSampler) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%bool = OpTypeBool
+%f32 = OpTypeFloat 32
+%sampler = OpTypeSampler
+%ptr_uc_sampler = OpTypePointer UniformConstant %sampler
+%sampler_var = OpVariable %ptr_uc_sampler UniformConstant
+%undef_bool = OpUndef %bool
+%undef_sampler = OpUndef %sampler
+%void_fn = OpTypeFunction %void
+%fn = OpFunction %void None %void_fn
+%entry = OpLabel
+%ld_sampler = OpLoad %sampler %sampler_var
+OpBranch %loop
+%loop = OpLabel
+%phi = OpPhi %sampler %undef_sampler %entry %ld_sampler %loop
+OpLoopMerge %exit %loop None
+OpBranchConditional %undef_bool %exit %loop
+%exit = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  options_->before_hlsl_legalization = true;
+  CompileSuccessfully(text);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, PhiResultValidPreLegalizationImage) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%bool = OpTypeBool
+%f32 = OpTypeFloat 32
+%image = OpTypeImage %f32 2D 0 0 0 1 Rgba32f
+%ptr_uc_image = OpTypePointer UniformConstant %image
+%image_var = OpVariable %ptr_uc_image UniformConstant
+%undef_bool = OpUndef %bool
+%undef_image = OpUndef %image
+%void_fn = OpTypeFunction %void
+%fn = OpFunction %void None %void_fn
+%entry = OpLabel
+%ld_image = OpLoad %image %image_var
+OpBranch %loop
+%loop = OpLabel
+%phi = OpPhi %image %undef_image %entry %ld_image %loop
+OpLoopMerge %exit %loop None
+OpBranchConditional %undef_bool %exit %loop
+%exit = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  options_->before_hlsl_legalization = true;
+  CompileSuccessfully(text);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, PhiResultValidPreLegalizationSampledImage) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%bool = OpTypeBool
+%f32 = OpTypeFloat 32
+%sampler = OpTypeSampler
+%ptr_uc_sampler = OpTypePointer UniformConstant %sampler
+%sampler_var = OpVariable %ptr_uc_sampler UniformConstant
+%image = OpTypeImage %f32 2D 0 0 0 1 Rgba32f
+%ptr_uc_image = OpTypePointer UniformConstant %image
+%image_var = OpVariable %ptr_uc_image UniformConstant
+%sampled_image = OpTypeSampledImage %image
+%undef_bool = OpUndef %bool
+%undef_sampled_image = OpUndef %sampled_image
+%void_fn = OpTypeFunction %void
+%fn = OpFunction %void None %void_fn
+%entry = OpLabel
+%ld_image = OpLoad %image %image_var
+%ld_sampler = OpLoad %sampler %sampler_var
+OpBranch %loop
+%loop = OpLabel
+%phi = OpPhi %sampled_image %undef_sampled_image %entry %sample %loop
+%sample = OpSampledImage %sampled_image %ld_image %ld_sampler
+OpLoopMerge %exit %loop None
+OpBranchConditional %undef_bool %exit %loop
+%exit = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  options_->before_hlsl_legalization = true;
+  CompileSuccessfully(text);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_decoration_test.cpp b/test/val/val_decoration_test.cpp
index 256e115..ec25d58 100644
--- a/test/val/val_decoration_test.cpp
+++ b/test/val/val_decoration_test.cpp
@@ -700,6 +700,61 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
 }
 
+TEST_F(ValidateDecorations, BlockDecoratingArrayBad) {
+  std::string spirv = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpSource GLSL 430
+               OpDecorate %Output Block
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+        %int = OpTypeInt 32 1
+      %int_3 = OpConstant %int 3
+     %Output = OpTypeArray %float %int_3
+%_ptr_Uniform_Output = OpTypePointer Uniform %Output
+ %dataOutput = OpVariable %_ptr_Uniform_Output Uniform
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Block decoration on a non-struct type"));
+}
+
+TEST_F(ValidateDecorations, BlockDecoratingIntBad) {
+  std::string spirv = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpSource GLSL 430
+               OpDecorate %Output Block
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+     %Output = OpTypeInt 32 1
+%_ptr_Uniform_Output = OpTypePointer Uniform %Output
+ %dataOutput = OpVariable %_ptr_Uniform_Output Uniform
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Block decoration on a non-struct type"));
+}
+
 TEST_F(ValidateDecorations, BlockMissingOffsetBad) {
   std::string spirv = R"(
                OpCapability Shader
diff --git a/test/val/val_ext_inst_test.cpp b/test/val/val_ext_inst_test.cpp
index 67df43d..aa73989 100644
--- a/test/val/val_ext_inst_test.cpp
+++ b/test/val/val_ext_inst_test.cpp
@@ -33,6 +33,29 @@
 using ::testing::Not;
 
 using ValidateExtInst = spvtest::ValidateBase<bool>;
+using ValidateOldDebugInfo = spvtest::ValidateBase<std::string>;
+using ValidateOpenCL100DebugInfo = spvtest::ValidateBase<std::string>;
+using ValidateLocalDebugInfoOutOfFunction = spvtest::ValidateBase<std::string>;
+using ValidateOpenCL100DebugInfoDebugTypedef =
+    spvtest::ValidateBase<std::pair<std::string, std::string>>;
+using ValidateOpenCL100DebugInfoDebugTypeEnum =
+    spvtest::ValidateBase<std::pair<std::string, std::string>>;
+using ValidateOpenCL100DebugInfoDebugTypeComposite =
+    spvtest::ValidateBase<std::pair<std::string, std::string>>;
+using ValidateOpenCL100DebugInfoDebugTypeMember =
+    spvtest::ValidateBase<std::pair<std::string, std::string>>;
+using ValidateOpenCL100DebugInfoDebugTypeInheritance =
+    spvtest::ValidateBase<std::pair<std::string, std::string>>;
+using ValidateOpenCL100DebugInfoDebugFunction =
+    spvtest::ValidateBase<std::pair<std::string, std::string>>;
+using ValidateOpenCL100DebugInfoDebugFunctionDeclaration =
+    spvtest::ValidateBase<std::pair<std::string, std::string>>;
+using ValidateOpenCL100DebugInfoDebugLexicalBlock =
+    spvtest::ValidateBase<std::pair<std::string, std::string>>;
+using ValidateOpenCL100DebugInfoDebugLocalVariable =
+    spvtest::ValidateBase<std::pair<std::string, std::string>>;
+using ValidateOpenCL100DebugInfoDebugDeclare =
+    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>;
@@ -460,6 +483,1584 @@
   return ss.str();
 }
 
+std::string GenerateShaderCodeForDebugInfo(
+    const std::string& op_string_instructions,
+    const std::string& op_const_instructions,
+    const std::string& debug_instructions_before_main, const std::string& body,
+    const std::string& capabilities_and_extensions = "",
+    const std::string& execution_model = "Fragment") {
+  std::ostringstream ss;
+  ss << R"(
+OpCapability Shader
+OpCapability Float16
+OpCapability Float64
+OpCapability Int16
+OpCapability Int64
+)";
+
+  ss << capabilities_and_extensions;
+  ss << "%extinst = OpExtInstImport \"GLSL.std.450\"\n";
+  ss << "OpMemoryModel Logical GLSL450\n";
+  ss << "OpEntryPoint " << execution_model << " %main \"main\""
+     << " %f32_output"
+     << " %f32vec2_output"
+     << " %u32_output"
+     << " %u32vec2_output"
+     << " %u64_output"
+     << " %f32_input"
+     << " %f32vec2_input"
+     << " %u32_input"
+     << " %u32vec2_input"
+     << " %u64_input"
+     << "\n";
+  if (execution_model == "Fragment") {
+    ss << "OpExecutionMode %main OriginUpperLeft\n";
+  }
+
+  ss << op_string_instructions;
+
+  ss << R"(
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%bool = OpTypeBool
+%f16 = OpTypeFloat 16
+%f32 = OpTypeFloat 32
+%f64 = OpTypeFloat 64
+%u32 = OpTypeInt 32 0
+%s32 = OpTypeInt 32 1
+%u64 = OpTypeInt 64 0
+%s64 = OpTypeInt 64 1
+%u16 = OpTypeInt 16 0
+%s16 = OpTypeInt 16 1
+%f32vec2 = OpTypeVector %f32 2
+%f32vec3 = OpTypeVector %f32 3
+%f32vec4 = OpTypeVector %f32 4
+%f64vec2 = OpTypeVector %f64 2
+%f64vec3 = OpTypeVector %f64 3
+%f64vec4 = OpTypeVector %f64 4
+%u32vec2 = OpTypeVector %u32 2
+%u32vec3 = OpTypeVector %u32 3
+%s32vec2 = OpTypeVector %s32 2
+%u32vec4 = OpTypeVector %u32 4
+%s32vec4 = OpTypeVector %s32 4
+%u64vec2 = OpTypeVector %u64 2
+%s64vec2 = OpTypeVector %s64 2
+%f64mat22 = OpTypeMatrix %f64vec2 2
+%f32mat22 = OpTypeMatrix %f32vec2 2
+%f32mat23 = OpTypeMatrix %f32vec2 3
+%f32mat32 = OpTypeMatrix %f32vec3 2
+%f32mat33 = OpTypeMatrix %f32vec3 3
+
+%f32_0 = OpConstant %f32 0
+%f32_1 = OpConstant %f32 1
+%f32_2 = OpConstant %f32 2
+%f32_3 = OpConstant %f32 3
+%f32_4 = OpConstant %f32 4
+%f32_h = OpConstant %f32 0.5
+%f32vec2_01 = OpConstantComposite %f32vec2 %f32_0 %f32_1
+%f32vec2_12 = OpConstantComposite %f32vec2 %f32_1 %f32_2
+%f32vec3_012 = OpConstantComposite %f32vec3 %f32_0 %f32_1 %f32_2
+%f32vec3_123 = OpConstantComposite %f32vec3 %f32_1 %f32_2 %f32_3
+%f32vec4_0123 = OpConstantComposite %f32vec4 %f32_0 %f32_1 %f32_2 %f32_3
+%f32vec4_1234 = OpConstantComposite %f32vec4 %f32_1 %f32_2 %f32_3 %f32_4
+
+%f64_0 = OpConstant %f64 0
+%f64_1 = OpConstant %f64 1
+%f64_2 = OpConstant %f64 2
+%f64_3 = OpConstant %f64 3
+%f64vec2_01 = OpConstantComposite %f64vec2 %f64_0 %f64_1
+%f64vec3_012 = OpConstantComposite %f64vec3 %f64_0 %f64_1 %f64_2
+%f64vec4_0123 = OpConstantComposite %f64vec4 %f64_0 %f64_1 %f64_2 %f64_3
+
+%f16_0 = OpConstant %f16 0
+%f16_1 = OpConstant %f16 1
+%f16_h = OpConstant %f16 0.5
+
+%u32_0 = OpConstant %u32 0
+%u32_1 = OpConstant %u32 1
+%u32_2 = OpConstant %u32 2
+%u32_3 = OpConstant %u32 3
+
+%s32_0 = OpConstant %s32 0
+%s32_1 = OpConstant %s32 1
+%s32_2 = OpConstant %s32 2
+%s32_3 = OpConstant %s32 3
+
+%u64_0 = OpConstant %u64 0
+%u64_1 = OpConstant %u64 1
+%u64_2 = OpConstant %u64 2
+%u64_3 = OpConstant %u64 3
+
+%s64_0 = OpConstant %s64 0
+%s64_1 = OpConstant %s64 1
+%s64_2 = OpConstant %s64 2
+%s64_3 = OpConstant %s64 3
+)";
+
+  ss << op_const_instructions;
+
+  ss << R"(
+%s32vec2_01 = OpConstantComposite %s32vec2 %s32_0 %s32_1
+%u32vec2_01 = OpConstantComposite %u32vec2 %u32_0 %u32_1
+
+%s32vec2_12 = OpConstantComposite %s32vec2 %s32_1 %s32_2
+%u32vec2_12 = OpConstantComposite %u32vec2 %u32_1 %u32_2
+
+%s32vec4_0123 = OpConstantComposite %s32vec4 %s32_0 %s32_1 %s32_2 %s32_3
+%u32vec4_0123 = OpConstantComposite %u32vec4 %u32_0 %u32_1 %u32_2 %u32_3
+
+%s64vec2_01 = OpConstantComposite %s64vec2 %s64_0 %s64_1
+%u64vec2_01 = OpConstantComposite %u64vec2 %u64_0 %u64_1
+
+%f32mat22_1212 = OpConstantComposite %f32mat22 %f32vec2_12 %f32vec2_12
+%f32mat23_121212 = OpConstantComposite %f32mat23 %f32vec2_12 %f32vec2_12 %f32vec2_12
+
+%f32_ptr_output = OpTypePointer Output %f32
+%f32vec2_ptr_output = OpTypePointer Output %f32vec2
+
+%u32_ptr_output = OpTypePointer Output %u32
+%u32vec2_ptr_output = OpTypePointer Output %u32vec2
+
+%u64_ptr_output = OpTypePointer Output %u64
+
+%f32_output = OpVariable %f32_ptr_output Output
+%f32vec2_output = OpVariable %f32vec2_ptr_output Output
+
+%u32_output = OpVariable %u32_ptr_output Output
+%u32vec2_output = OpVariable %u32vec2_ptr_output Output
+
+%u64_output = OpVariable %u64_ptr_output Output
+
+%f32_ptr_input = OpTypePointer Input %f32
+%f32vec2_ptr_input = OpTypePointer Input %f32vec2
+
+%u32_ptr_input = OpTypePointer Input %u32
+%u32vec2_ptr_input = OpTypePointer Input %u32vec2
+
+%u64_ptr_input = OpTypePointer Input %u64
+
+%f32_ptr_function = OpTypePointer Function %f32
+
+%f32_input = OpVariable %f32_ptr_input Input
+%f32vec2_input = OpVariable %f32vec2_ptr_input Input
+
+%u32_input = OpVariable %u32_ptr_input Input
+%u32vec2_input = OpVariable %u32vec2_ptr_input Input
+
+%u64_input = OpVariable %u64_ptr_input Input
+
+%u32_ptr_function = OpTypePointer Function %u32
+
+%struct_f16_u16 = OpTypeStruct %f16 %u16
+%struct_f32_f32 = OpTypeStruct %f32 %f32
+%struct_f32_f32_f32 = OpTypeStruct %f32 %f32 %f32
+%struct_f32_u32 = OpTypeStruct %f32 %u32
+%struct_f32_u32_f32 = OpTypeStruct %f32 %u32 %f32
+%struct_u32_f32 = OpTypeStruct %u32 %f32
+%struct_u32_u32 = OpTypeStruct %u32 %u32
+%struct_f32_f64 = OpTypeStruct %f32 %f64
+%struct_f32vec2_f32vec2 = OpTypeStruct %f32vec2 %f32vec2
+%struct_f32vec2_u32vec2 = OpTypeStruct %f32vec2 %u32vec2
+)";
+
+  ss << debug_instructions_before_main;
+
+  ss << R"(
+%main = OpFunction %void None %func
+%main_entry = OpLabel
+)";
+
+  ss << body;
+
+  ss << R"(
+OpReturn
+OpFunctionEnd)";
+
+  return ss.str();
+}
+
+TEST_F(ValidateOldDebugInfo, UseDebugInstructionOutOfFunction) {
+  const std::string src = R"(
+%code = OpString "main() {}"
+)";
+
+  const std::string dbg_inst = R"(
+%cu = OpExtInst %void %DbgExt DebugCompilationUnit %code 1 1
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "DebugInfo"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", dbg_inst, "",
+                                                     extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, UseDebugInstructionOutOfFunction) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+)";
+
+  const std::string dbg_inst = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", dbg_inst, "",
+                                                     extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugSourceInFunction) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+)";
+
+  const std::string dbg_inst = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", "", dbg_inst,
+                                                     extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Debug info extension instructions other than DebugScope, "
+                "DebugNoScope, DebugDeclare, DebugValue must appear between "
+                "section 9 (types, constants, global variables) and section 10 "
+                "(function declarations)"));
+}
+
+TEST_P(ValidateLocalDebugInfoOutOfFunction, OpenCLDebugInfo100DebugScope) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "void main() {}"
+%void_name = OpString "void"
+%main_name = OpString "main"
+%main_linkage_name = OpString "v_main"
+%int_name = OpString "int"
+%foo_name = OpString "foo"
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%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_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 1 1 %comp_unit %main_linkage_name FlagIsPublic 1 %main
+%foo_info = OpExtInst %void %DbgExt DebugLocalVariable %foo_name %int_info %dbg_src 1 1 %main_info FlagIsLocal
+%expr = OpExtInst %void %DbgExt DebugExpression
+)";
+
+  const std::string body = R"(
+%foo = OpVariable %u32_ptr_function Function
+%foo_val = OpLoad %u32 %foo
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, "", dbg_inst_header + GetParam(), body, extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("DebugScope, DebugNoScope, DebugDeclare, DebugValue "
+                        "of debug info extension must appear in a function "
+                        "body"));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    AllLocalDebugInfo, ValidateLocalDebugInfoOutOfFunction,
+    ::testing::ValuesIn(std::vector<std::string>{
+        "%main_scope = OpExtInst %void %DbgExt DebugScope %main_info",
+        "%no_scope = OpExtInst %void %DbgExt DebugNoScope",
+    }));
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugFunctionForwardReference) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "void main() {}"
+%void_name = OpString "void"
+%main_name = OpString "main"
+%main_linkage_name = OpString "v_main"
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%void_info = OpExtInst %void %DbgExt DebugTypeBasic %void_name %u32_0 Unspecified
+%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void_info %void_info
+%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 1 1 %comp_unit %main_linkage_name FlagIsPublic 1 %main
+)";
+
+  const std::string body = R"(
+%main_scope = OpExtInst %void %DbgExt DebugScope %main_info
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, "", dbg_inst_header, body, extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugFunctionMissingOpFunction) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "void main() {}"
+%void_name = OpString "void"
+%main_name = OpString "main"
+%main_linkage_name = OpString "v_main"
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbgNone = OpExtInst %void %DbgExt DebugInfoNone
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%void_info = OpExtInst %void %DbgExt DebugTypeBasic %void_name %u32_0 Unspecified
+%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void_info %void_info
+%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 1 1 %comp_unit %main_linkage_name FlagIsPublic 1 %dbgNone
+)";
+
+  const std::string body = R"(
+%main_scope = OpExtInst %void %DbgExt DebugScope %main_info
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, "", dbg_inst_header, body, extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugScopeBeforeOpVariableInFunction) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "float4 main(float arg) {
+  float foo;
+  return float4(0, 0, 0, 0);
+}
+"
+%float_name = OpString "float"
+%main_name = OpString "main"
+%main_linkage_name = OpString "v4f_main_f"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%v4float_info = OpExtInst %void %DbgExt DebugTypeVector %float_info 4
+%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %v4float_info %float_info
+%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 12 1 %comp_unit %main_linkage_name FlagIsPublic 13 %main
+)";
+
+  const std::string body = R"(
+%main_scope = OpExtInst %void %DbgExt DebugScope %main_info
+%foo = OpVariable %f32_ptr_function Function
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, body, extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeCompositeForwardReference) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "struct VS_OUTPUT {
+  float4 pos : SV_POSITION;
+  float4 color : COLOR;
+};
+main() {}
+"
+%VS_OUTPUT_name = OpString "struct VS_OUTPUT"
+%float_name = OpString "float"
+%VS_OUTPUT_pos_name = OpString "pos : SV_POSITION"
+%VS_OUTPUT_color_name = OpString "color : COLOR"
+%VS_OUTPUT_linkage_name = OpString "VS_OUTPUT"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+%int_128 = OpConstant %u32 128
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%VS_OUTPUT_info = OpExtInst %void %DbgExt DebugTypeComposite %VS_OUTPUT_name Structure %dbg_src 1 1 %comp_unit %VS_OUTPUT_linkage_name %int_128 FlagIsPublic %VS_OUTPUT_pos_info %VS_OUTPUT_color_info
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%v4float_info = OpExtInst %void %DbgExt DebugTypeVector %float_info 4
+%VS_OUTPUT_pos_info = OpExtInst %void %DbgExt DebugTypeMember %VS_OUTPUT_pos_name %v4float_info %dbg_src 2 3 %VS_OUTPUT_info %u32_0 %int_128 FlagIsPublic
+%VS_OUTPUT_color_info = OpExtInst %void %DbgExt DebugTypeMember %VS_OUTPUT_color_name %v4float_info %dbg_src 3 3 %VS_OUTPUT_info %int_128 %int_128 FlagIsPublic
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeCompositeMissingReference) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "struct VS_OUTPUT {
+  float4 pos : SV_POSITION;
+  float4 color : COLOR;
+};
+main() {}
+"
+%VS_OUTPUT_name = OpString "struct VS_OUTPUT"
+%float_name = OpString "float"
+%VS_OUTPUT_pos_name = OpString "pos : SV_POSITION"
+%VS_OUTPUT_color_name = OpString "color : COLOR"
+%VS_OUTPUT_linkage_name = OpString "VS_OUTPUT"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+%int_128 = OpConstant %u32 128
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%VS_OUTPUT_info = OpExtInst %void %DbgExt DebugTypeComposite %VS_OUTPUT_name Structure %dbg_src 1 1 %comp_unit %VS_OUTPUT_linkage_name %int_128 FlagIsPublic %VS_OUTPUT_pos_info %VS_OUTPUT_color_info
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%v4float_info = OpExtInst %void %DbgExt DebugTypeVector %float_info 4
+%VS_OUTPUT_pos_info = OpExtInst %void %DbgExt DebugTypeMember %VS_OUTPUT_pos_name %v4float_info %dbg_src 2 3 %VS_OUTPUT_info %u32_0 %int_128 FlagIsPublic
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("forward referenced IDs have not been defined"));
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugInstructionWrongResultType) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+)";
+
+  const std::string dbg_inst = R"(
+%dbg_src = OpExtInst %bool %DbgExt DebugSource %src %code
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", dbg_inst, "",
+                                                     extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected result type must be a result id of "
+                        "OpTypeVoid"));
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugCompilationUnit) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+)";
+
+  const std::string dbg_inst = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", dbg_inst, "",
+                                                     extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugCompilationUnitFail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+)";
+
+  const std::string dbg_inst = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %src HLSL
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", dbg_inst, "",
+                                                     extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand Source must be a result id of "
+                        "DebugSource"));
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugSourceFailFile) {
+  const std::string src = R"(
+%code = OpString "main() {}"
+)";
+
+  const std::string dbg_inst = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %DbgExt %code
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", dbg_inst, "",
+                                                     extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand File must be a result id of "
+                        "OpString"));
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugSourceFailSource) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+)";
+
+  const std::string dbg_inst = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %DbgExt
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", dbg_inst, "",
+                                                     extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand Text must be a result id of "
+                        "OpString"));
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugSourceNoText) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+)";
+
+  const std::string dbg_inst = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", dbg_inst, "",
+                                                     extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeBasicFailName) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "float4 main(float arg) {
+  float foo;
+  return float4(0, 0, 0, 0);
+}
+"
+%float_name = OpString "float"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %int_32 %int_32 Float
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand Name must be a result id of "
+                        "OpString"));
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeBasicFailSize) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "float4 main(float arg) {
+  float foo;
+  return float4(0, 0, 0, 0);
+}
+"
+%float_name = OpString "float"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %float_name Float
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand Size must be a result id of "
+                        "OpConstant"));
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypePointer) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "float4 main(float arg) {
+  float foo;
+  return float4(0, 0, 0, 0);
+}
+"
+%float_name = OpString "float"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%pfloat_info = OpExtInst %void %DbgExt DebugTypePointer %float_info Function FlagIsLocal
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypePointerFail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "float4 main(float arg) {
+  float foo;
+  return float4(0, 0, 0, 0);
+}
+"
+%float_name = OpString "float"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%pfloat_info = OpExtInst %void %DbgExt DebugTypePointer %dbg_src Function FlagIsLocal
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand Base Type must be a result id of "
+                        "DebugTypeBasic"));
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeQualifier) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "float4 main(float arg) {
+  float foo;
+  return float4(0, 0, 0, 0);
+}
+"
+%float_name = OpString "float"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%cfloat_info = OpExtInst %void %DbgExt DebugTypeQualifier %float_info ConstType
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeQualifierFail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "float4 main(float arg) {
+  float foo;
+  return float4(0, 0, 0, 0);
+}
+"
+%float_name = OpString "float"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%cfloat_info = OpExtInst %void %DbgExt DebugTypeQualifier %comp_unit ConstType
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand Base Type must be a result id of "
+                        "DebugTypeBasic"));
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeArray) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%float_arr_info = OpExtInst %void %DbgExt DebugTypeArray %float_info %int_32
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeArrayFailBaseType) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%float_arr_info = OpExtInst %void %DbgExt DebugTypeArray %comp_unit %int_32
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand Base Type is not a valid debug "
+                        "type"));
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeArrayFailComponentCount) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%float_arr_info = OpExtInst %void %DbgExt DebugTypeArray %float_info %float_info
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand Component Count must be a result id "
+                        "of OpConstant"));
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeArrayFailComponentCountFloat) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%float_arr_info = OpExtInst %void %DbgExt DebugTypeArray %float_info %f32_4
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Component Count must be positive integer"));
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeArrayFailComponentCountZero) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%float_arr_info = OpExtInst %void %DbgExt DebugTypeArray %float_info %u32_0
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Component Count must be positive integer"));
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeVector) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%vfloat_info = OpExtInst %void %DbgExt DebugTypeVector %float_info 4
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeVectorFail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%vfloat_info = OpExtInst %void %DbgExt DebugTypeVector %dbg_src 4
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand Base Type must be a result id of "
+                        "DebugTypeBasic"));
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeVectorFailComponentZero) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%vfloat_info = OpExtInst %void %DbgExt DebugTypeVector %dbg_src 0
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand Base Type must be a result id of "
+                        "DebugTypeBasic"));
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeVectorFailComponentFive) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%vfloat_info = OpExtInst %void %DbgExt DebugTypeVector %dbg_src 5
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand Base Type must be a result id of "
+                        "DebugTypeBasic"));
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypedef) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%foo_info = OpExtInst %void %DbgExt DebugTypedef %foo_name %float_info %dbg_src 1 1 %comp_unit
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(ValidateOpenCL100DebugInfoDebugTypedef, Fail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const auto& param = GetParam();
+
+  std::ostringstream ss;
+  ss << R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%foo_info = OpExtInst %void %DbgExt DebugTypedef )";
+  ss << param.first;
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, size_const, ss.str(),
+                                                     "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand " + param.second +
+                        " must be a result id of "));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    AllOpenCL100DebugInfoFail, ValidateOpenCL100DebugInfoDebugTypedef,
+    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
+        std::make_pair(R"(%dbg_src %float_info %dbg_src 1 1 %comp_unit)",
+                       "Name"),
+        std::make_pair(R"(%foo_name %dbg_src %dbg_src 1 1 %comp_unit)",
+                       "Base Type"),
+        std::make_pair(R"(%foo_name %float_info %comp_unit 1 1 %comp_unit)",
+                       "Source"),
+        std::make_pair(R"(%foo_name %float_info %dbg_src 1 1 %dbg_src)",
+                       "Parent"),
+    }));
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeFunction) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%main_name = OpString "main"
+%main_linkage_name = OpString "v_main"
+%float_name = OpString "float"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%main_type_info1 = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void
+%main_type_info2 = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %float_info
+%main_type_info3 = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %float_info %float_info
+%main_type_info4 = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void %float_info %float_info
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeFunctionFailReturn) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%main_name = OpString "main"
+%main_linkage_name = OpString "v_main"
+%float_name = OpString "float"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %dbg_src %float_info
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("expected operand Return Type is not a valid debug type"));
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeFunctionFailParam) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%main_name = OpString "main"
+%main_linkage_name = OpString "v_main"
+%float_name = OpString "float"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %float_info %void
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("expected operand Parameter Types is not a valid debug type"));
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeEnum) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%none = OpExtInst %void %DbgExt DebugInfoNone
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%foo_info1 = OpExtInst %void %DbgExt DebugTypeEnum %foo_name %float_info %dbg_src 1 1 %comp_unit %int_32 FlagIsPublic %u32_0 %foo_name %u32_1 %foo_name
+%foo_info2 = OpExtInst %void %DbgExt DebugTypeEnum %foo_name %none %dbg_src 1 1 %comp_unit %int_32 FlagIsPublic %u32_0 %foo_name %u32_1 %foo_name
+%foo_info3 = OpExtInst %void %DbgExt DebugTypeEnum %foo_name %none %dbg_src 1 1 %comp_unit %int_32 FlagIsPublic
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(ValidateOpenCL100DebugInfoDebugTypeEnum, Fail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const auto& param = GetParam();
+
+  std::ostringstream ss;
+  ss << R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%foo_info = OpExtInst %void %DbgExt DebugTypeEnum )";
+  ss << param.first;
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, size_const, ss.str(),
+                                                     "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand " + param.second));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    AllOpenCL100DebugInfoFail, ValidateOpenCL100DebugInfoDebugTypeEnum,
+    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
+        std::make_pair(
+            R"(%dbg_src %float_info %dbg_src 1 1 %comp_unit %int_32 FlagIsPublic %u32_0 %foo_name)",
+            "Name"),
+        std::make_pair(
+            R"(%foo_name %dbg_src %dbg_src 1 1 %comp_unit %int_32 FlagIsPublic %u32_0 %foo_name)",
+            "Underlying Types"),
+        std::make_pair(
+            R"(%foo_name %float_info %comp_unit 1 1 %comp_unit %int_32 FlagIsPublic %u32_0 %foo_name)",
+            "Source"),
+        std::make_pair(
+            R"(%foo_name %float_info %dbg_src 1 1 %dbg_src %int_32 FlagIsPublic %u32_0 %foo_name)",
+            "Parent"),
+        std::make_pair(
+            R"(%foo_name %float_info %dbg_src 1 1 %comp_unit %void FlagIsPublic %u32_0 %foo_name)",
+            "Size"),
+        std::make_pair(
+            R"(%foo_name %float_info %dbg_src 1 1 %comp_unit %u32_0 FlagIsPublic %u32_0 %foo_name)",
+            "Size"),
+        std::make_pair(
+            R"(%foo_name %float_info %dbg_src 1 1 %comp_unit %int_32 FlagIsPublic %foo_name %foo_name)",
+            "Value"),
+        std::make_pair(
+            R"(%foo_name %float_info %dbg_src 1 1 %comp_unit %int_32 FlagIsPublic %u32_0 %u32_1)",
+            "Name"),
+    }));
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeCompositeFunctionAndInheritance) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "struct VS_OUTPUT {
+  float4 pos : SV_POSITION;
+};
+struct foo : VS_OUTPUT {
+};
+main() {}
+"
+%VS_OUTPUT_name = OpString "struct VS_OUTPUT"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+%VS_OUTPUT_pos_name = OpString "pos : SV_POSITION"
+%VS_OUTPUT_linkage_name = OpString "VS_OUTPUT"
+%main_name = OpString "main"
+%main_linkage_name = OpString "v4f_main_f"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+%int_128 = OpConstant %u32 128
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%VS_OUTPUT_info = OpExtInst %void %DbgExt DebugTypeComposite %VS_OUTPUT_name Structure %dbg_src 1 1 %comp_unit %VS_OUTPUT_linkage_name %int_128 FlagIsPublic %VS_OUTPUT_pos_info %main_info %child
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%v4float_info = OpExtInst %void %DbgExt DebugTypeVector %float_info 4
+%VS_OUTPUT_pos_info = OpExtInst %void %DbgExt DebugTypeMember %VS_OUTPUT_pos_name %v4float_info %dbg_src 2 3 %VS_OUTPUT_info %u32_0 %int_128 FlagIsPublic
+%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %v4float_info %float_info
+%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 12 1 %comp_unit %main_linkage_name FlagIsPublic 13 %main
+%foo_info = OpExtInst %void %DbgExt DebugTypeComposite %foo_name Structure %dbg_src 1 1 %comp_unit %foo_name %u32_0 FlagIsPublic
+%child = OpExtInst %void %DbgExt DebugTypeInheritance %foo_info %VS_OUTPUT_info %int_128 %int_128 FlagIsPublic
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(ValidateOpenCL100DebugInfoDebugTypeComposite, Fail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "struct VS_OUTPUT {
+  float4 pos : SV_POSITION;
+};
+struct foo : VS_OUTPUT {
+};
+main() {}
+"
+%VS_OUTPUT_name = OpString "struct VS_OUTPUT"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+%VS_OUTPUT_pos_name = OpString "pos : SV_POSITION"
+%VS_OUTPUT_linkage_name = OpString "VS_OUTPUT"
+%main_name = OpString "main"
+%main_linkage_name = OpString "v4f_main_f"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+%int_128 = OpConstant %u32 128
+)";
+
+  const auto& param = GetParam();
+
+  std::ostringstream ss;
+  ss << R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%VS_OUTPUT_info = OpExtInst %void %DbgExt DebugTypeComposite )";
+  ss << param.first;
+  ss << R"(
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%v4float_info = OpExtInst %void %DbgExt DebugTypeVector %float_info 4
+%VS_OUTPUT_pos_info = OpExtInst %void %DbgExt DebugTypeMember %VS_OUTPUT_pos_name %v4float_info %dbg_src 2 3 %VS_OUTPUT_info %u32_0 %int_128 FlagIsPublic
+%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %v4float_info %float_info
+%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 12 1 %comp_unit %main_linkage_name FlagIsPublic 13 %main
+%foo_info = OpExtInst %void %DbgExt DebugTypeComposite %foo_name Structure %dbg_src 1 1 %comp_unit %foo_name %u32_0 FlagIsPublic
+%child = OpExtInst %void %DbgExt DebugTypeInheritance %foo_info %VS_OUTPUT_info %int_128 %int_128 FlagIsPublic
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, size_const, ss.str(),
+                                                     "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand " + param.second + " must be "));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    AllOpenCL100DebugInfoFail, ValidateOpenCL100DebugInfoDebugTypeComposite,
+    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
+        std::make_pair(
+            R"(%dbg_src Structure %dbg_src 1 1 %comp_unit %VS_OUTPUT_linkage_name %int_128 FlagIsPublic %VS_OUTPUT_pos_info %main_info %child)",
+            "Name"),
+        std::make_pair(
+            R"(%VS_OUTPUT_name Structure %comp_unit 1 1 %comp_unit %VS_OUTPUT_linkage_name %int_128 FlagIsPublic %VS_OUTPUT_pos_info %main_info %child)",
+            "Source"),
+        std::make_pair(
+            R"(%VS_OUTPUT_name Structure %dbg_src 1 1 %dbg_src %VS_OUTPUT_linkage_name %int_128 FlagIsPublic %VS_OUTPUT_pos_info %main_info %child)",
+            "Parent"),
+        std::make_pair(
+            R"(%VS_OUTPUT_name Structure %dbg_src 1 1 %comp_unit %int_128 %int_128 FlagIsPublic %VS_OUTPUT_pos_info %main_info %child)",
+            "Linkage Name"),
+        std::make_pair(
+            R"(%VS_OUTPUT_name Structure %dbg_src 1 1 %comp_unit %VS_OUTPUT_linkage_name %dbg_src FlagIsPublic %VS_OUTPUT_pos_info %main_info %child)",
+            "Size"),
+        std::make_pair(
+            R"(%VS_OUTPUT_name Structure %dbg_src 1 1 %comp_unit %VS_OUTPUT_linkage_name %int_128 FlagIsPublic %dbg_src %main_info %child)",
+            "Members"),
+        std::make_pair(
+            R"(%VS_OUTPUT_name Structure %dbg_src 1 1 %comp_unit %VS_OUTPUT_linkage_name %int_128 FlagIsPublic %VS_OUTPUT_pos_info %dbg_src %child)",
+            "Members"),
+        std::make_pair(
+            R"(%VS_OUTPUT_name Structure %dbg_src 1 1 %comp_unit %VS_OUTPUT_linkage_name %int_128 FlagIsPublic %VS_OUTPUT_pos_info %main_info %dbg_src)",
+            "Members"),
+    }));
+
+TEST_P(ValidateOpenCL100DebugInfoDebugTypeMember, Fail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "struct VS_OUTPUT {
+  float pos : SV_POSITION;
+};
+main() {}
+"
+%VS_OUTPUT_name = OpString "struct VS_OUTPUT"
+%float_name = OpString "float"
+%VS_OUTPUT_pos_name = OpString "pos : SV_POSITION"
+%VS_OUTPUT_linkage_name = OpString "VS_OUTPUT"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const auto& param = GetParam();
+
+  std::ostringstream ss;
+  ss << R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%VS_OUTPUT_info = OpExtInst %void %DbgExt DebugTypeComposite %VS_OUTPUT_name Structure %dbg_src 1 1 %comp_unit %VS_OUTPUT_linkage_name %int_32 FlagIsPublic %VS_OUTPUT_pos_info
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%VS_OUTPUT_pos_info = OpExtInst %void %DbgExt DebugTypeMember )";
+  ss << param.first;
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, size_const, ss.str(),
+                                                     "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  if (!param.second.empty()) {
+    EXPECT_THAT(getDiagnosticString(),
+                HasSubstr("expected operand " + param.second +
+                          " must be a result id of "));
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    AllOpenCL100DebugInfoFail, ValidateOpenCL100DebugInfoDebugTypeMember,
+    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
+        std::make_pair(
+            R"(%dbg_src %float_info %dbg_src 2 3 %VS_OUTPUT_info %u32_0 %int_32 FlagIsPublic)",
+            "Name"),
+        std::make_pair(
+            R"(%VS_OUTPUT_pos_name %dbg_src %dbg_src 2 3 %VS_OUTPUT_info %u32_0 %int_32 FlagIsPublic)",
+            ""),
+        std::make_pair(
+            R"(%VS_OUTPUT_pos_name %float_info %float_info 2 3 %VS_OUTPUT_info %u32_0 %int_32 FlagIsPublic)",
+            "Source"),
+        std::make_pair(
+            R"(%VS_OUTPUT_pos_name %float_info %dbg_src 2 3 %float_info %u32_0 %int_32 FlagIsPublic)",
+            "Parent"),
+        std::make_pair(
+            R"(%VS_OUTPUT_pos_name %float_info %dbg_src 2 3 %VS_OUTPUT_info %void %int_32 FlagIsPublic)",
+            "Offset"),
+        std::make_pair(
+            R"(%VS_OUTPUT_pos_name %float_info %dbg_src 2 3 %VS_OUTPUT_info %u32_0 %void FlagIsPublic)",
+            "Size"),
+    }));
+
+TEST_P(ValidateOpenCL100DebugInfoDebugTypeInheritance, Fail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "struct VS_OUTPUT {};
+struct foo : VS_OUTPUT {};
+"
+%VS_OUTPUT_name = OpString "struct VS_OUTPUT"
+%foo_name = OpString "foo"
+)";
+
+  const auto& param = GetParam();
+
+  std::ostringstream ss;
+  ss << R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%VS_OUTPUT_info = OpExtInst %void %DbgExt DebugTypeComposite %VS_OUTPUT_name Structure %dbg_src 1 1 %comp_unit %VS_OUTPUT_name %u32_0 FlagIsPublic %child
+%foo_info = OpExtInst %void %DbgExt DebugTypeComposite %foo_name Structure %dbg_src 1 1 %comp_unit %foo_name %u32_0 FlagIsPublic
+%bar_info = OpExtInst %void %DbgExt DebugTypeComposite %foo_name Union %dbg_src 1 1 %comp_unit %foo_name %u32_0 FlagIsPublic
+%child = OpExtInst %void %DbgExt DebugTypeInheritance )"
+     << param.first;
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", ss.str(), "",
+                                                     extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand " + param.second));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    AllOpenCL100DebugInfoFail, ValidateOpenCL100DebugInfoDebugTypeInheritance,
+    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
+        std::make_pair(R"(%dbg_src %VS_OUTPUT_info %u32_0 %u32_0 FlagIsPublic)",
+                       "Child must be a result id of"),
+        std::make_pair(R"(%foo_info %dbg_src %u32_0 %u32_0 FlagIsPublic)",
+                       "Parent must be a result id of"),
+        std::make_pair(
+            R"(%bar_info %VS_OUTPUT_info %u32_0 %u32_0 FlagIsPublic)",
+            "Child must be class or struct debug type"),
+        std::make_pair(R"(%foo_info %bar_info %u32_0 %u32_0 FlagIsPublic)",
+                       "Parent must be class or struct debug type"),
+        std::make_pair(R"(%foo_info %VS_OUTPUT_info %void %u32_0 FlagIsPublic)",
+                       "Offset"),
+        std::make_pair(R"(%foo_info %VS_OUTPUT_info %u32_0 %void FlagIsPublic)",
+                       "Size"),
+    }));
 TEST_P(ValidateGlslStd450SqrtLike, Success) {
   const std::string ext_inst_name = GetParam();
   std::ostringstream ss;
@@ -471,6 +2072,511 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
+TEST_F(ValidateOpenCL100DebugInfo, DebugFunctionDeclaration) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "struct VS_OUTPUT {
+  float4 pos : SV_POSITION;
+};
+main() {}
+"
+%main_name = OpString "main"
+%main_linkage_name = OpString "v4f_main_f"
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void
+%main_decl = OpExtInst %void %DbgExt DebugFunctionDeclaration %main_name %main_type_info %dbg_src 12 1 %comp_unit %main_linkage_name FlagIsPublic
+%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 12 1 %comp_unit %main_linkage_name FlagIsPublic 13 %main)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", dbg_inst_header,
+                                                     "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(ValidateOpenCL100DebugInfoDebugFunction, Fail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "struct VS_OUTPUT {
+  float4 pos : SV_POSITION;
+};
+main() {}
+"
+%main_name = OpString "main"
+%main_linkage_name = OpString "v4f_main_f"
+)";
+
+  const auto& param = GetParam();
+
+  std::ostringstream ss;
+  ss << R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void
+%main_decl = OpExtInst %void %DbgExt DebugFunctionDeclaration %main_name %main_type_info %dbg_src 12 1 %comp_unit %main_linkage_name FlagIsPublic
+%main_info = OpExtInst %void %DbgExt DebugFunction )"
+     << param.first;
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", ss.str(), "",
+                                                     extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand " + param.second));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    AllOpenCL100DebugInfoFail, ValidateOpenCL100DebugInfoDebugFunction,
+    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
+        std::make_pair(
+            R"(%u32_0 %main_type_info %dbg_src 12 1 %comp_unit %main_linkage_name FlagIsPublic 13 %main)",
+            "Name"),
+        std::make_pair(
+            R"(%main_name %dbg_src %dbg_src 12 1 %comp_unit %main_linkage_name FlagIsPublic 13 %main)",
+            "Type"),
+        std::make_pair(
+            R"(%main_name %main_type_info %comp_unit 12 1 %comp_unit %main_linkage_name FlagIsPublic 13 %main)",
+            "Source"),
+        std::make_pair(
+            R"(%main_name %main_type_info %dbg_src 12 1 %dbg_src %main_linkage_name FlagIsPublic 13 %main)",
+            "Parent"),
+        std::make_pair(
+            R"(%main_name %main_type_info %dbg_src 12 1 %comp_unit %void FlagIsPublic 13 %main)",
+            "Linkage Name"),
+        std::make_pair(
+            R"(%main_name %main_type_info %dbg_src 12 1 %comp_unit %main_linkage_name FlagIsPublic 13 %void)",
+            "Function"),
+        std::make_pair(
+            R"(%main_name %main_type_info %dbg_src 12 1 %comp_unit %main_linkage_name FlagIsPublic 13 %main %dbg_src)",
+            "Declaration"),
+    }));
+
+TEST_P(ValidateOpenCL100DebugInfoDebugFunctionDeclaration, Fail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "struct VS_OUTPUT {
+  float4 pos : SV_POSITION;
+};
+main() {}
+"
+%main_name = OpString "main"
+%main_linkage_name = OpString "v4f_main_f"
+)";
+
+  const auto& param = GetParam();
+
+  std::ostringstream ss;
+  ss << R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void
+%main_decl = OpExtInst %void %DbgExt DebugFunctionDeclaration )"
+     << param.first;
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", ss.str(), "",
+                                                     extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand " + param.second));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    AllOpenCL100DebugInfoFail,
+    ValidateOpenCL100DebugInfoDebugFunctionDeclaration,
+    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
+        std::make_pair(
+            R"(%u32_0 %main_type_info %dbg_src 12 1 %comp_unit %main_linkage_name FlagIsPublic)",
+            "Name"),
+        std::make_pair(
+            R"(%main_name %dbg_src %dbg_src 12 1 %comp_unit %main_linkage_name FlagIsPublic)",
+            "Type"),
+        std::make_pair(
+            R"(%main_name %main_type_info %comp_unit 12 1 %comp_unit %main_linkage_name FlagIsPublic)",
+            "Source"),
+        std::make_pair(
+            R"(%main_name %main_type_info %dbg_src 12 1 %dbg_src %main_linkage_name FlagIsPublic)",
+            "Parent"),
+        std::make_pair(
+            R"(%main_name %main_type_info %dbg_src 12 1 %comp_unit %void FlagIsPublic)",
+            "Linkage Name"),
+    }));
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugLexicalBlock) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%main_name = OpString "main"
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%main_block = OpExtInst %void %DbgExt DebugLexicalBlock %dbg_src 1 1 %comp_unit %main_name)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", dbg_inst_header,
+                                                     "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(ValidateOpenCL100DebugInfoDebugLexicalBlock, Fail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%main_name = OpString "main"
+)";
+
+  const auto& param = GetParam();
+
+  std::ostringstream ss;
+  ss << R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%main_block = OpExtInst %void %DbgExt DebugLexicalBlock )"
+     << param.first;
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", ss.str(), "",
+                                                     extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand " + param.second));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    AllOpenCL100DebugInfoFail, ValidateOpenCL100DebugInfoDebugLexicalBlock,
+    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
+        std::make_pair(R"(%comp_unit 1 1 %comp_unit %main_name)", "Source"),
+        std::make_pair(R"(%dbg_src 1 1 %dbg_src %main_name)", "Parent"),
+        std::make_pair(R"(%dbg_src 1 1 %comp_unit %void)", "Name"),
+    }));
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugScopeFailScope) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "void main() {}"
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+)";
+
+  const std::string body = R"(
+%main_scope = OpExtInst %void %DbgExt DebugScope %dbg_src
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, "", dbg_inst_header, body, extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("expected operand Scope"));
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugScopeFailInlinedAt) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "void main() {}"
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+)";
+
+  const std::string body = R"(
+%main_scope = OpExtInst %void %DbgExt DebugScope %comp_unit %dbg_src
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, "", dbg_inst_header, body, extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("expected operand Inlined At"));
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugLocalVariable) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "void main() { float foo; }"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%foo = OpExtInst %void %DbgExt DebugLocalVariable %foo_name %float_info %dbg_src 1 10 %comp_unit FlagIsLocal 0
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(ValidateOpenCL100DebugInfoDebugLocalVariable, Fail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "void main() { float foo; }"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const auto& param = GetParam();
+
+  std::ostringstream ss;
+  ss << R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%foo = OpExtInst %void %DbgExt DebugLocalVariable )"
+     << param.first;
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, size_const, ss.str(),
+                                                     "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand " + param.second));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    AllOpenCL100DebugInfoFail, ValidateOpenCL100DebugInfoDebugLocalVariable,
+    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
+        std::make_pair(
+            R"(%void %float_info %dbg_src 1 10 %comp_unit FlagIsLocal 0)",
+            "Name"),
+        std::make_pair(
+            R"(%foo_name %dbg_src %dbg_src 1 10 %comp_unit FlagIsLocal 0)",
+            "Type"),
+        std::make_pair(
+            R"(%foo_name %float_info %comp_unit 1 10 %comp_unit FlagIsLocal 0)",
+            "Source"),
+        std::make_pair(
+            R"(%foo_name %float_info %dbg_src 1 10 %dbg_src FlagIsLocal 0)",
+            "Parent"),
+    }));
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugDeclare) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "void main() { float foo; }"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%null_expr = OpExtInst %void %DbgExt DebugExpression
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%foo_info = OpExtInst %void %DbgExt DebugLocalVariable %foo_name %float_info %dbg_src 1 10 %comp_unit FlagIsLocal 0
+)";
+
+  const std::string body = R"(
+%foo = OpVariable %f32_ptr_function Function
+%decl = OpExtInst %void %DbgExt DebugDeclare %foo_info %foo %null_expr
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, body, extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugDeclareParam) {
+  CompileSuccessfully(R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "OpenCL.DebugInfo.100"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %in_var_COLOR
+          %4 = OpString "test.hlsl"
+               OpSource HLSL 620 %4 "#line 1 \"test.hlsl\"
+void main(float foo:COLOR) {}
+"
+         %11 = OpString "#line 1 \"test.hlsl\"
+void main(float foo:COLOR) {}
+"
+         %14 = OpString "float"
+         %17 = OpString "src.main"
+         %20 = OpString "foo"
+               OpName %in_var_COLOR "in.var.COLOR"
+               OpName %main "main"
+               OpName %param_var_foo "param.var.foo"
+               OpName %src_main "src.main"
+               OpName %foo "foo"
+               OpName %bb_entry "bb.entry"
+               OpDecorate %in_var_COLOR Location 0
+       %uint = OpTypeInt 32 0
+    %uint_32 = OpConstant %uint 32
+      %float = OpTypeFloat 32
+%_ptr_Input_float = OpTypePointer Input %float
+       %void = OpTypeVoid
+         %23 = OpTypeFunction %void
+%_ptr_Function_float = OpTypePointer Function %float
+         %29 = OpTypeFunction %void %_ptr_Function_float
+               OpLine %4 1 21
+%in_var_COLOR = OpVariable %_ptr_Input_float Input
+         %10 = OpExtInst %void %1 DebugExpression
+         %12 = OpExtInst %void %1 DebugSource %4 %11
+         %13 = OpExtInst %void %1 DebugCompilationUnit 1 4 %12 HLSL
+         %15 = OpExtInst %void %1 DebugTypeBasic %14 %uint_32 Float
+         %16 = OpExtInst %void %1 DebugTypeFunction FlagIsProtected|FlagIsPrivate %void %15
+         %18 = OpExtInst %void %1 DebugFunction %17 %16 %12 1 1 %13 %17 FlagIsProtected|FlagIsPrivate 1 %src_main
+         %21 = OpExtInst %void %1 DebugLocalVariable %20 %15 %12 1 17 %18 FlagIsLocal 0
+         %22 = OpExtInst %void %1 DebugLexicalBlock %12 1 28 %18
+               OpLine %4 1 1
+       %main = OpFunction %void None %23
+         %24 = OpLabel
+               OpLine %4 1 17
+%param_var_foo = OpVariable %_ptr_Function_float Function
+         %27 = OpLoad %float %in_var_COLOR
+               OpLine %4 1 1
+         %28 = OpFunctionCall %void %src_main %param_var_foo
+               OpReturn
+               OpFunctionEnd
+   %src_main = OpFunction %void None %29
+               OpLine %4 1 17
+        %foo = OpFunctionParameter %_ptr_Function_float
+         %31 = OpExtInst %void %1 DebugDeclare %21 %foo %10
+   %bb_entry = OpLabel
+               OpLine %4 1 29
+               OpReturn
+               OpFunctionEnd
+)");
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(ValidateOpenCL100DebugInfoDebugDeclare, Fail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "void main() { float foo; }"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%null_expr = OpExtInst %void %DbgExt DebugExpression
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%foo_info = OpExtInst %void %DbgExt DebugLocalVariable %foo_name %float_info %dbg_src 1 10 %comp_unit FlagIsLocal 0
+)";
+
+  const auto& param = GetParam();
+
+  std::ostringstream ss;
+  ss << R"(
+%foo = OpVariable %f32_ptr_function Function
+%decl = OpExtInst %void %DbgExt DebugDeclare )"
+     << param.first;
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, ss.str(), extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand " + param.second));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    AllOpenCL100DebugInfoFail, ValidateOpenCL100DebugInfoDebugDeclare,
+    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
+        std::make_pair(R"(%dbg_src %foo %null_expr)", "Local Variable"),
+        std::make_pair(R"(%foo_info %void %null_expr)", "Variable"),
+        std::make_pair(R"(%foo_info %foo %dbg_src)", "Expression"),
+    }));
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugExpression) {
+  const std::string dbg_inst_header = R"(
+%op0 = OpExtInst %void %DbgExt DebugOperation Deref
+%op1 = OpExtInst %void %DbgExt DebugOperation Plus
+%null_expr = OpExtInst %void %DbgExt DebugExpression %op0 %op1
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo("", "", dbg_inst_header,
+                                                     "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugExpressionFail) {
+  const std::string dbg_inst_header = R"(
+%op = OpExtInst %void %DbgExt DebugOperation Deref
+%null_expr = OpExtInst %void %DbgExt DebugExpression %op %void
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo("", "", dbg_inst_header,
+                                                     "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "expected operand Operation must be a result id of DebugOperation"));
+}
+
 TEST_P(ValidateGlslStd450SqrtLike, IntResultType) {
   const std::string ext_inst_name = GetParam();
   const std::string body =
diff --git a/test/val/val_id_test.cpp b/test/val/val_id_test.cpp
index 019d91a..adea563 100644
--- a/test/val/val_id_test.cpp
+++ b/test/val/val_id_test.cpp
@@ -4355,6 +4355,17 @@
                         "'23' as an operand of <id> '24'."));
 }
 
+TEST_F(ValidateIdWithMessage, OpCopyObjectSampledImageGood) {
+  std::string spirv = kGLSL450MemoryModel + sampledImageSetup + R"(
+%smpld_img  = OpSampledImage %sampled_image_type %image_inst %sampler_inst
+%smpld_img2 = OpCopyObject %sampled_image_type %smpld_img
+%image_inst2 = OpCopyObject %image_type %image_inst
+OpReturn
+OpFunctionEnd)";
+  CompileSuccessfully(spirv.c_str());
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
 // Valid: Get a float in a matrix using CompositeExtract.
 // Valid: Insert float into a matrix using CompositeInsert.
 TEST_F(ValidateIdWithMessage, CompositeExtractInsertGood) {
diff --git a/test/val/val_image_test.cpp b/test/val/val_image_test.cpp
index 433c9fa..570dd16 100644
--- a/test/val/val_image_test.cpp
+++ b/test/val/val_image_test.cpp
@@ -4841,6 +4841,169 @@
   EXPECT_THAT(getDiagnosticString(), Eq(""));
 }
 
+TEST_F(ValidateImage, ReadLodAMDSuccess1) {
+  const std::string body = R"(
+%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000
+%res1 = OpImageRead %u32vec4 %img %u32vec2_01 Lod %u32_0
+)";
+
+  const std::string extra =
+      "\nOpCapability StorageImageReadWithoutFormat\n"
+      "OpCapability ImageReadWriteLodAMD\n"
+      "OpExtension \"SPV_AMD_shader_image_load_store_lod\"\n";
+  CompileSuccessfully(
+      GenerateShaderCode(body, extra, "Fragment", "", SPV_ENV_UNIVERSAL_1_1),
+      SPV_ENV_UNIVERSAL_1_1);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_1));
+}
+
+TEST_F(ValidateImage, ReadLodAMDSuccess2) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_1d_0002_rgba32f %uniform_image_f32_1d_0002_rgba32f
+%res1 = OpImageRead %f32vec4 %img %u32vec2_01 Lod %u32_0
+)";
+
+  const std::string extra =
+      "\nOpCapability Image1D\n"
+      "OpCapability ImageReadWriteLodAMD\n"
+      "OpExtension \"SPV_AMD_shader_image_load_store_lod\"\n";
+  CompileSuccessfully(
+      GenerateShaderCode(body, extra, "Fragment", "", SPV_ENV_UNIVERSAL_1_1),
+      SPV_ENV_UNIVERSAL_1_1);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_1));
+}
+
+TEST_F(ValidateImage, ReadLodAMDSuccess3) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_cube_0102_rgba32f %uniform_image_f32_cube_0102_rgba32f
+%res1 = OpImageRead %f32vec4 %img %u32vec3_012 Lod %u32_0
+)";
+
+  const std::string extra =
+      "\nOpCapability ImageCubeArray\n"
+      "OpCapability ImageReadWriteLodAMD\n"
+      "OpExtension \"SPV_AMD_shader_image_load_store_lod\"\n";
+  CompileSuccessfully(
+      GenerateShaderCode(body, extra, "Fragment", "", SPV_ENV_UNIVERSAL_1_1),
+      SPV_ENV_UNIVERSAL_1_1);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_1));
+}
+
+TEST_F(ValidateImage, ReadLodAMDNeedCapability) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_cube_0102_rgba32f %uniform_image_f32_cube_0102_rgba32f
+%res1 = OpImageRead %f32vec4 %img %u32vec3_012 Lod %u32_0
+)";
+
+  const std::string extra = "\nOpCapability ImageCubeArray\n";
+  CompileSuccessfully(
+      GenerateShaderCode(body, extra, "Fragment", "", SPV_ENV_UNIVERSAL_1_1),
+      SPV_ENV_UNIVERSAL_1_1);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Image Operand Lod can only be used with ExplicitLod "
+                        "opcodes and OpImageFetch"));
+}
+
+TEST_F(ValidateImage, WriteLodAMDSuccess1) {
+  const std::string body = R"(
+%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000
+OpImageWrite %img %u32vec2_01 %u32vec4_0123 Lod %u32_0
+)";
+
+  const std::string extra =
+      "\nOpCapability StorageImageWriteWithoutFormat\n"
+      "OpCapability ImageReadWriteLodAMD\n"
+      "OpExtension \"SPV_AMD_shader_image_load_store_lod\"\n";
+  CompileSuccessfully(
+      GenerateShaderCode(body, extra, "Fragment", "", SPV_ENV_UNIVERSAL_1_1),
+      SPV_ENV_UNIVERSAL_1_1);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_1));
+}
+
+TEST_F(ValidateImage, WriteLodAMDSuccess2) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_1d_0002_rgba32f %uniform_image_f32_1d_0002_rgba32f
+OpImageWrite %img %u32_1 %f32vec4_0000 Lod %u32_0
+)";
+
+  const std::string extra =
+      "\nOpCapability Image1D\n"
+      "OpCapability ImageReadWriteLodAMD\n"
+      "OpExtension \"SPV_AMD_shader_image_load_store_lod\"\n";
+  CompileSuccessfully(
+      GenerateShaderCode(body, extra, "Fragment", "", SPV_ENV_UNIVERSAL_1_1),
+      SPV_ENV_UNIVERSAL_1_1);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_1));
+}
+
+TEST_F(ValidateImage, WriteLodAMDSuccess3) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_cube_0102_rgba32f %uniform_image_f32_cube_0102_rgba32f
+OpImageWrite %img %u32vec3_012 %f32vec4_0000 Lod %u32_0
+)";
+
+  const std::string extra =
+      "\nOpCapability ImageCubeArray\n"
+      "OpCapability ImageReadWriteLodAMD\n"
+      "OpExtension \"SPV_AMD_shader_image_load_store_lod\"\n";
+  CompileSuccessfully(
+      GenerateShaderCode(body, extra, "Fragment", "", SPV_ENV_UNIVERSAL_1_1),
+      SPV_ENV_UNIVERSAL_1_1);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_1));
+}
+
+TEST_F(ValidateImage, WriteLodAMDNeedCapability) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_cube_0102_rgba32f %uniform_image_f32_cube_0102_rgba32f
+OpImageWrite %img %u32vec3_012 %f32vec4_0000 Lod %u32_0
+)";
+
+  const std::string extra = "\nOpCapability ImageCubeArray\n";
+  CompileSuccessfully(
+      GenerateShaderCode(body, extra, "Fragment", "", SPV_ENV_UNIVERSAL_1_1),
+      SPV_ENV_UNIVERSAL_1_1);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Image Operand Lod can only be used with ExplicitLod "
+                        "opcodes and OpImageFetch"));
+}
+
+TEST_F(ValidateImage, SparseReadLodAMDSuccess) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0002 %uniform_image_f32_2d_0002
+%res1 = OpImageSparseRead %struct_u32_f32vec4 %img %u32vec2_01 Lod %u32_0
+)";
+
+  const std::string extra =
+      "\nOpCapability StorageImageReadWithoutFormat\n"
+      "OpCapability ImageReadWriteLodAMD\n"
+      "OpExtension \"SPV_AMD_shader_image_load_store_lod\"\n";
+  CompileSuccessfully(
+      GenerateShaderCode(body, extra, "Fragment", "", SPV_ENV_UNIVERSAL_1_1),
+      SPV_ENV_UNIVERSAL_1_1);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_1));
+}
+
+TEST_F(ValidateImage, SparseReadLodAMDNeedCapability) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0002 %uniform_image_f32_2d_0002
+%res1 = OpImageSparseRead %struct_u32_f32vec4 %img %u32vec2_01 Lod %u32_0
+)";
+
+  const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n";
+  CompileSuccessfully(
+      GenerateShaderCode(body, extra, "Fragment", "", SPV_ENV_UNIVERSAL_1_1),
+      SPV_ENV_UNIVERSAL_1_1);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Image Operand Lod can only be used with ExplicitLod "
+                        "opcodes and OpImageFetch"));
+}
+
 // No negative tests for ZeroExtend since we don't truly know the
 // texel format.
 
diff --git a/test/val/val_memory_test.cpp b/test/val/val_memory_test.cpp
index 22761cc..b32867b 100644
--- a/test/val/val_memory_test.cpp
+++ b/test/val/val_memory_test.cpp
@@ -57,8 +57,9 @@
                 "Variables identified with the UniformConstant storage class "
                 "are used only as handles to refer to opaque resources. Such "
                 "variables must be typed as OpTypeImage, OpTypeSampler, "
-                "OpTypeSampledImage, OpTypeAccelerationStructureNV, or an "
-                "array of one of these types."));
+                "OpTypeSampledImage, OpTypeAccelerationStructureNV, "
+                "OpTypeAccelerationStructureKHR, OpTypeRayQueryProvisionalKHR, "
+                "or an array of one of these types."));
 }
 
 TEST_F(ValidateMemory, VulkanUniformConstantOnOpaqueResourceGood) {
@@ -110,8 +111,9 @@
                 "Variables identified with the UniformConstant storage class "
                 "are used only as handles to refer to opaque resources. Such "
                 "variables must be typed as OpTypeImage, OpTypeSampler, "
-                "OpTypeSampledImage, OpTypeAccelerationStructureNV, or an "
-                "array of one of these types."));
+                "OpTypeSampledImage, OpTypeAccelerationStructureNV, "
+                "OpTypeAccelerationStructureKHR, OpTypeRayQueryProvisionalKHR, "
+                "or an array of one of these types."));
 }
 
 TEST_F(ValidateMemory, VulkanUniformConstantOnOpaqueResourceArrayGood) {
diff --git a/tools/fuzz/fuzz.cpp b/tools/fuzz/fuzz.cpp
index 718d038..2c9807d 100644
--- a/tools/fuzz/fuzz.cpp
+++ b/tools/fuzz/fuzz.cpp
@@ -145,6 +145,13 @@
   --version
                Display fuzzer version information.
 
+Supported validator options are as follows. See `spirv-val --help` for details.
+  --before-hlsl-legalization
+  --relax-block-layout
+  --relax-logical-pointer
+  --relax-struct-store
+  --scalar-block-layout
+  --skip-block-layout
 )",
       program, program, program, program);
 }
@@ -166,7 +173,8 @@
                       std::vector<std::string>* interestingness_test,
                       std::string* shrink_transformations_file,
                       std::string* shrink_temp_file_prefix,
-                      spvtools::FuzzerOptions* fuzzer_options) {
+                      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;
@@ -227,6 +235,18 @@
                               sizeof("--shrinker-temp-file-prefix=") - 1)) {
         const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
         *shrink_temp_file_prefix = std::string(split_flag.second);
+      } else if (0 == strcmp(cur_arg, "--before-hlsl-legalization")) {
+        validator_options->SetBeforeHlslLegalization(true);
+      } else if (0 == strcmp(cur_arg, "--relax-logical-pointer")) {
+        validator_options->SetRelaxLogicalPointer(true);
+      } else if (0 == strcmp(cur_arg, "--relax-block-layout")) {
+        validator_options->SetRelaxBlockLayout(true);
+      } else if (0 == strcmp(cur_arg, "--scalar-block-layout")) {
+        validator_options->SetScalarBlockLayout(true);
+      } else if (0 == strcmp(cur_arg, "--skip-block-layout")) {
+        validator_options->SetSkipBlockLayout(true);
+      } else if (0 == strcmp(cur_arg, "--relax-struct-store")) {
+        validator_options->SetRelaxStructStore(true);
       } else if (0 == strcmp(cur_arg, "--")) {
         only_positional_arguments_remain = true;
       } else {
@@ -357,6 +377,7 @@
 
 bool Replay(const spv_target_env& target_env,
             spv_const_fuzzer_options fuzzer_options,
+            spv_validator_options validator_options,
             const std::vector<uint32_t>& binary_in,
             const spvtools::fuzz::protobufs::FactSequence& initial_facts,
             const std::string& replay_transformations_file,
@@ -368,8 +389,8 @@
                             &transformation_sequence)) {
     return false;
   }
-  spvtools::fuzz::Replayer replayer(target_env,
-                                    fuzzer_options->replay_validation_enabled);
+  spvtools::fuzz::Replayer replayer(
+      target_env, fuzzer_options->replay_validation_enabled, validator_options);
   replayer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
   auto replay_result_status =
       replayer.Run(binary_in, initial_facts, transformation_sequence,
@@ -380,6 +401,7 @@
 
 bool Shrink(const spv_target_env& target_env,
             spv_const_fuzzer_options fuzzer_options,
+            spv_validator_options validator_options,
             const std::vector<uint32_t>& binary_in,
             const spvtools::fuzz::protobufs::FactSequence& initial_facts,
             const std::string& shrink_transformations_file,
@@ -393,9 +415,9 @@
                             &transformation_sequence)) {
     return false;
   }
-  spvtools::fuzz::Shrinker shrinker(target_env,
-                                    fuzzer_options->shrinker_step_limit,
-                                    fuzzer_options->replay_validation_enabled);
+  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() &&
@@ -434,6 +456,7 @@
 
 bool Fuzz(const spv_target_env& target_env,
           spv_const_fuzzer_options fuzzer_options,
+          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,
@@ -469,7 +492,7 @@
       fuzzer_options->has_random_seed
           ? fuzzer_options->random_seed
           : static_cast<uint32_t>(std::random_device()()),
-      fuzzer_options->fuzzer_pass_validation_enabled);
+      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,
@@ -513,11 +536,13 @@
   std::string shrink_temp_file_prefix = "temp_";
 
   spvtools::FuzzerOptions fuzzer_options;
+  spvtools::ValidatorOptions validator_options;
 
-  FuzzStatus status = 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);
+  FuzzStatus status =
+      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);
 
   if (status.action == FuzzActions::STOP) {
     return status.code;
@@ -555,20 +580,22 @@
 
   switch (status.action) {
     case FuzzActions::FORCE_RENDER_RED:
-      if (!spvtools::fuzz::ForceRenderRed(target_env, binary_in, initial_facts,
+      if (!spvtools::fuzz::ForceRenderRed(target_env, validator_options,
+                                          binary_in, initial_facts,
                                           &binary_out)) {
         return 1;
       }
       break;
     case FuzzActions::FUZZ:
-      if (!Fuzz(target_env, fuzzer_options, binary_in, initial_facts,
-                donors_file, &binary_out, &transformations_applied)) {
+      if (!Fuzz(target_env, fuzzer_options, validator_options, binary_in,
+                initial_facts, donors_file, &binary_out,
+                &transformations_applied)) {
         return 1;
       }
       break;
     case FuzzActions::REPLAY:
-      if (!Replay(target_env, fuzzer_options, binary_in, initial_facts,
-                  replay_transformations_file, &binary_out,
+      if (!Replay(target_env, fuzzer_options, validator_options, binary_in,
+                  initial_facts, replay_transformations_file, &binary_out,
                   &transformations_applied)) {
         return 1;
       }
@@ -579,9 +606,9 @@
                   << std::endl;
         return 1;
       }
-      if (!Shrink(target_env, fuzzer_options, binary_in, initial_facts,
-                  shrink_transformations_file, shrink_temp_file_prefix,
-                  interestingness_test, &binary_out,
+      if (!Shrink(target_env, fuzzer_options, validator_options, binary_in,
+                  initial_facts, shrink_transformations_file,
+                  shrink_temp_file_prefix, interestingness_test, &binary_out,
                   &transformations_applied)) {
         return 1;
       }
diff --git a/tools/opt/opt.cpp b/tools/opt/opt.cpp
index 0ff937a..658bd5b 100644
--- a/tools/opt/opt.cpp
+++ b/tools/opt/opt.cpp
@@ -111,7 +111,7 @@
   printf(R"(
   --amd-ext-to-khr
                Replaces the extensions VK_AMD_shader_ballot, VK_AMD_gcn_shader,
-               and VK_AMD_shader_trinary_minmax with equivalant code using core
+               and VK_AMD_shader_trinary_minmax with equivalent code using core
                instructions and capabilities.)");
   printf(R"(
   --ccp
@@ -173,7 +173,7 @@
   printf(R"(
   --eliminate-dead-branches
                Convert conditional branches with constant condition to the
-               indicated unconditional brranch. Delete all resulting dead
+               indicated unconditional branch. Delete all resulting dead
                code. Performed only on entry point call tree functions.)");
   printf(R"(
   --eliminate-dead-code-aggressive
@@ -302,7 +302,7 @@
                the optimization is above the threshold.)");
   printf(R"(
   --max-id-bound=<n>
-               Sets the maximum value for the id bound for the moudle.  The
+               Sets the maximum value for the id bound for the module.  The
                default is the minimum value for this limit, 0x3FFFFF.  See
                section 2.17 of the Spir-V specification.)");
   printf(R"(
@@ -428,7 +428,7 @@
   --scalar-replacement[=<n>]
                Replace aggregate function scope variables that are only accessed
                via their elements with new function variables representing each
-               element.  <n> is a limit on the size of the aggragates that will
+               element.  <n> is a limit on the size of the aggregates that will
                be replaced.  0 means there is no limit.  The default value is
                100.)");
   printf(R"(
@@ -447,7 +447,7 @@
   --split-invalid-unreachable
                Attempts to legalize for WebGPU cases where an unreachable
                merge-block is also a continue-target by splitting it into two
-               seperate blocks. There exist legal, for Vulkan, instances of this
+               separate blocks. There exist legal, for Vulkan, instances of this
                pattern that cannot be converted into legal WebGPU, so this
                conversion may not succeed.)");
   printf(R"(
@@ -472,7 +472,7 @@
   printf(R"(
   --target-env=<env>
                Set the target environment. Without this flag the target
-               enviroment defaults to spv1.3. <env> must be one of
+               environment defaults to spv1.5. <env> must be one of
                {%s})",
          target_env_list.c_str());
   printf(R"(
diff --git a/utils/check_copyright.py b/utils/check_copyright.py
index 969371d..4467a32 100755
--- a/utils/check_copyright.py
+++ b/utils/check_copyright.py
@@ -1,4 +1,5 @@
 #!/usr/bin/env python
+# coding=utf-8
 # Copyright (c) 2016 Google Inc.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -31,10 +32,13 @@
            'Google Inc.',
            'Google LLC',
            'Pierre Moreau',
-           'Samsung Inc']
-CURRENT_YEAR='2019'
+           'Samsung Inc',
+           'André Perez Maselco',
+           'Vasyl Teliman',
+           'Advanced Micro Devices, Inc.']
+CURRENT_YEAR='2020'
 
-YEARS = '(2014-2016|2015-2016|2016|2016-2017|2017|2017-2019|2018|2019)'
+YEARS = '(2014-2016|2015-2016|2015-2020|2016|2016-2017|2017|2017-2019|2018|2019|2020)'
 COPYRIGHT_RE = re.compile(
         'Copyright \(c\) {} ({})'.format(YEARS, '|'.join(AUTHORS)))
 
@@ -167,7 +171,7 @@
         has_apache2 = False
         line_num = 0
         apache_expected_end = 0
-        with open(file) as contents:
+        with open(file, encoding='utf-8') as contents:
             for line in contents:
                 line_num += 1
                 if COPYRIGHT_RE.search(line):
diff --git a/utils/generate_language_headers.py b/utils/generate_language_headers.py
index 0296163..83fa99e 100755
--- a/utils/generate_language_headers.py
+++ b/utils/generate_language_headers.py
@@ -159,27 +159,25 @@
     import argparse
     parser = argparse.ArgumentParser(description='Generate language headers from a JSON grammar')
 
-    parser.add_argument('--extinst-name',
-                        type=str, required=True,
-                        help='The name to use in tokens')
     parser.add_argument('--extinst-grammar', metavar='<path>',
                         type=str, required=True,
                         help='input JSON grammar file for extended instruction set')
-    parser.add_argument('--extinst-output-base', metavar='<path>',
+    parser.add_argument('--extinst-output-path', metavar='<path>',
                         type=str, required=True,
-                        help='Basename of the language-specific output file.')
+                        help='Path of the language-specific output file.')
     args = parser.parse_args()
 
     with open(args.extinst_grammar) as json_file:
         grammar_json = json.loads(json_file.read())
-        grammar = ExtInstGrammar(name = args.extinst_name,
+        grammar_name = os.path.splitext(os.path.basename(args.extinst_output_path))[0]
+        grammar = ExtInstGrammar(name = grammar_name,
                                  copyright = grammar_json['copyright'],
                                  instructions = grammar_json['instructions'],
                                  operand_kinds = grammar_json['operand_kinds'],
                                  version = grammar_json['version'],
                                  revision = grammar_json['revision'])
-        make_path_to_file(args.extinst_output_base)
-        with open(args.extinst_output_base + '.h', 'w') as f:
+        make_path_to_file(args.extinst_output_path)
+        with open(args.extinst_output_path, 'w') as f:
             f.write(CGenerator().generate(grammar))
 
 
diff --git a/utils/generate_registry_tables.py b/utils/generate_registry_tables.py
index e662ba9..28152ef 100755
--- a/utils/generate_registry_tables.py
+++ b/utils/generate_registry_tables.py
@@ -14,11 +14,30 @@
 # limitations under the License.
 """Generates the vendor tool table from the SPIR-V XML registry."""
 
-import distutils.dir_util
+import errno
 import os.path
 import xml.etree.ElementTree
 
 
+def mkdir_p(directory):
+    """Make the directory, and all its ancestors as required.  Any of the
+    directories are allowed to already exist.
+    This is compatible with Python down to 3.0.
+    """
+
+    if directory == "":
+        # We're being asked to make the current directory.
+        return
+
+    try:
+        os.makedirs(directory)
+    except OSError as e:
+        if e.errno == errno.EEXIST and os.path.isdir(directory):
+            pass
+        else:
+            raise
+
+
 def generate_vendor_table(registry):
     """Returns a list of C style initializers for the registered vendors
     and their tools.
@@ -62,7 +81,7 @@
     with open(args.xml) as xml_in:
        registry = xml.etree.ElementTree.fromstring(xml_in.read())
 
-    distutils.dir_util.mkpath(os.path.dirname(args.generator_output))
+    mkdir_p(os.path.dirname(args.generator_output))
     with open(args.generator_output, 'w') as f:
       f.write(generate_vendor_table(registry))
 
diff --git a/utils/vscode/extension.js b/utils/vscode/extension.js
index f220172..e7fec28 100644
--- a/utils/vscode/extension.js
+++ b/utils/vscode/extension.js
@@ -63,4 +63,4 @@
 // this method is called when your extension is deactivated
 function deactivate() {
 }
-exports.deactivate = deactivate;
\ No newline at end of file
+exports.deactivate = deactivate;
diff --git a/utils/vscode/spirv.json b/utils/vscode/spirv.json
index 30573d4..7999b52 100644
--- a/utils/vscode/spirv.json
+++ b/utils/vscode/spirv.json
@@ -33,7 +33,14 @@
 		{ "include": "#ValueEnum_GroupOperation" },
 		{ "include": "#ValueEnum_KernelEnqueueFlags" },
 		{ "include": "#ValueEnum_Capability" },
+		{ "include": "#BitEnum_DebugInfoFlags" },
+		{ "include": "#ValueEnum_DebugBaseTypeAttributeEncoding" },
+		{ "include": "#ValueEnum_DebugCompositeType" },
+		{ "include": "#ValueEnum_DebugTypeQualifier" },
+		{ "include": "#ValueEnum_DebugOperation" },
+		{ "include": "#ValueEnum_DebugImportedEntity" },
 		{ "include": "#opcode" },
+		{ "include": "#extopcode" },
 		{ "include": "#identifier" },
 		{ "include": "#number" },
 		{ "include": "#string" },
@@ -161,10 +168,38 @@
 			"match": "\\b(Matrix|Shader|Geometry|Tessellation|Addresses|Linkage|Kernel|Vector16|Float16Buffer|Float16|Float64|Int64|Int64Atomics|ImageBasic|ImageReadWrite|ImageMipmap|Pipes|Groups|DeviceEnqueue|LiteralSampler|AtomicStorage|Int16|TessellationPointSize|GeometryPointSize|ImageGatherExtended|StorageImageMultisample|UniformBufferArrayDynamicIndexing|SampledImageArrayDynamicIndexing|StorageBufferArrayDynamicIndexing|StorageImageArrayDynamicIndexing|ClipDistance|CullDistance|ImageCubeArray|SampleRateShading|ImageRect|SampledRect|GenericPointer|Int8|InputAttachment|SparseResidency|MinLod|Sampled1D|Image1D|SampledCubeArray|SampledBuffer|ImageBuffer|ImageMSArray|StorageImageExtendedFormats|ImageQuery|DerivativeControl|InterpolationFunction|TransformFeedback|GeometryStreams|StorageImageReadWithoutFormat|StorageImageWriteWithoutFormat|MultiViewport|SubgroupDispatch|NamedBarrier|PipeStorage|GroupNonUniform|GroupNonUniformVote|GroupNonUniformArithmetic|GroupNonUniformBallot|GroupNonUniformShuffle|GroupNonUniformShuffleRelative|GroupNonUniformClustered|GroupNonUniformQuad|ShaderLayer|ShaderViewportIndex|SubgroupBallotKHR|DrawParameters|SubgroupVoteKHR|StorageBuffer16BitAccess|StorageUniformBufferBlock16|UniformAndStorageBuffer16BitAccess|StorageUniform16|StoragePushConstant16|StorageInputOutput16|DeviceGroup|MultiView|VariablePointersStorageBuffer|VariablePointers|AtomicStorageOps|SampleMaskPostDepthCoverage|StorageBuffer8BitAccess|UniformAndStorageBuffer8BitAccess|StoragePushConstant8|DenormPreserve|DenormFlushToZero|SignedZeroInfNanPreserve|RoundingModeRTE|RoundingModeRTZ|Float16ImageAMD|ImageGatherBiasLodAMD|FragmentMaskAMD|StencilExportEXT|ImageReadWriteLodAMD|ShaderClockKHR|SampleMaskOverrideCoverageNV|GeometryShaderPassthroughNV|ShaderViewportIndexLayerEXT|ShaderViewportIndexLayerNV|ShaderViewportMaskNV|ShaderStereoViewNV|PerViewAttributesNV|FragmentFullyCoveredEXT|MeshShadingNV|ImageFootprintNV|FragmentBarycentricNV|ComputeDerivativeGroupQuadsNV|FragmentDensityEXT|ShadingRateNV|GroupNonUniformPartitionedNV|ShaderNonUniform|ShaderNonUniformEXT|RuntimeDescriptorArray|RuntimeDescriptorArrayEXT|InputAttachmentArrayDynamicIndexing|InputAttachmentArrayDynamicIndexingEXT|UniformTexelBufferArrayDynamicIndexing|UniformTexelBufferArrayDynamicIndexingEXT|StorageTexelBufferArrayDynamicIndexing|StorageTexelBufferArrayDynamicIndexingEXT|UniformBufferArrayNonUniformIndexing|UniformBufferArrayNonUniformIndexingEXT|SampledImageArrayNonUniformIndexing|SampledImageArrayNonUniformIndexingEXT|StorageBufferArrayNonUniformIndexing|StorageBufferArrayNonUniformIndexingEXT|StorageImageArrayNonUniformIndexing|StorageImageArrayNonUniformIndexingEXT|InputAttachmentArrayNonUniformIndexing|InputAttachmentArrayNonUniformIndexingEXT|UniformTexelBufferArrayNonUniformIndexing|UniformTexelBufferArrayNonUniformIndexingEXT|StorageTexelBufferArrayNonUniformIndexing|StorageTexelBufferArrayNonUniformIndexingEXT|RayTracingNV|VulkanMemoryModel|VulkanMemoryModelKHR|VulkanMemoryModelDeviceScope|VulkanMemoryModelDeviceScopeKHR|PhysicalStorageBufferAddresses|PhysicalStorageBufferAddressesEXT|ComputeDerivativeGroupLinearNV|CooperativeMatrixNV|FragmentShaderSampleInterlockEXT|FragmentShaderShadingRateInterlockEXT|ShaderSMBuiltinsNV|FragmentShaderPixelInterlockEXT|DemoteToHelperInvocationEXT|SubgroupShuffleINTEL|SubgroupBufferBlockIOINTEL|SubgroupImageBlockIOINTEL|SubgroupImageMediaBlockIOINTEL|IntegerFunctions2INTEL|SubgroupAvcMotionEstimationINTEL|SubgroupAvcMotionEstimationIntraINTEL|SubgroupAvcMotionEstimationChromaINTEL)\\b",
 			"name": "keyword.spirv"
 		},
+		"BitEnum_DebugInfoFlags": {
+			"match": "\\b(FlagIsProtected|FlagIsPrivate|FlagIsPublic|FlagIsLocal|FlagIsDefinition|FlagFwdDecl|FlagArtificial|FlagExplicit|FlagPrototyped|FlagObjectPointer|FlagStaticMember|FlagIndirectVariable|FlagLValueReference|FlagRValueReference|FlagIsOptimized|FlagIsEnumClass|FlagTypePassByValue|FlagTypePassByReference)\\b",
+			"name": "keyword.spirv"
+		},
+		"ValueEnum_DebugBaseTypeAttributeEncoding": {
+			"match": "\\b(Unspecified|Address|Boolean|Float|Signed|SignedChar|Unsigned|UnsignedChar)\\b",
+			"name": "keyword.spirv"
+		},
+		"ValueEnum_DebugCompositeType": {
+			"match": "\\b(Class|Structure|Union)\\b",
+			"name": "keyword.spirv"
+		},
+		"ValueEnum_DebugTypeQualifier": {
+			"match": "\\b(ConstType|VolatileType|RestrictType|AtomicType)\\b",
+			"name": "keyword.spirv"
+		},
+		"ValueEnum_DebugOperation": {
+			"match": "\\b(Deref|Plus|Minus|PlusUconst|BitPiece|Swap|Xderef|StackValue|Constu|Fragment)\\b",
+			"name": "keyword.spirv"
+		},
+		"ValueEnum_DebugImportedEntity": {
+			"match": "\\b(ImportedModule|ImportedDeclaration)\\b",
+			"name": "keyword.spirv"
+		},
 		"opcode": {
 			"match": "(Op[a-zA-Z]+)",
 			"name": "entity.name.function.spirv"
 		},
+		"extopcode": {
+			"match": "(Round|RoundEven|Trunc|FAbs|SAbs|FSign|SSign|Floor|Ceil|Fract|Radians|Degrees|Sin|Cos|Tan|Asin|Acos|Atan|Sinh|Cosh|Tanh|Asinh|Acosh|Atanh|Atan2|Pow|Exp|Log|Exp2|Log2|Sqrt|InverseSqrt|Determinant|MatrixInverse|Modf|ModfStruct|FMin|UMin|SMin|FMax|UMax|SMax|FClamp|UClamp|SClamp|FMix|IMix|Step|SmoothStep|Fma|Frexp|FrexpStruct|Ldexp|PackSnorm4x8|PackUnorm4x8|PackSnorm2x16|PackUnorm2x16|PackHalf2x16|PackDouble2x32|UnpackSnorm2x16|UnpackUnorm2x16|UnpackHalf2x16|UnpackSnorm4x8|UnpackUnorm4x8|UnpackDouble2x32|Length|Distance|Cross|Normalize|FaceForward|Reflect|Refract|FindILsb|FindSMsb|FindUMsb|InterpolateAtCentroid|InterpolateAtSample|InterpolateAtOffset|NMin|NMax|NClamp|acos|acosh|acospi|asin|asinh|asinpi|atan|atan2|atanh|atanpi|atan2pi|cbrt|ceil|copysign|cos|cosh|cospi|erfc|erf|exp|exp2|exp10|expm1|fabs|fdim|floor|fma|fmax|fmin|fmod|fract|frexp|hypot|ilogb|ldexp|lgamma|lgamma_r|log|log2|log10|log1p|logb|mad|maxmag|minmag|modf|nan|nextafter|pow|pown|powr|remainder|remquo|rint|rootn|round|rsqrt|sin|sincos|sinh|sinpi|sqrt|tan|tanh|tanpi|tgamma|trunc|half_cos|half_divide|half_exp|half_exp2|half_exp10|half_log|half_log2|half_log10|half_powr|half_recip|half_rsqrt|half_sin|half_sqrt|half_tan|native_cos|native_divide|native_exp|native_exp2|native_exp10|native_log|native_log2|native_log10|native_powr|native_recip|native_rsqrt|native_sin|native_sqrt|native_tan|s_abs|s_abs_diff|s_add_sat|u_add_sat|s_hadd|u_hadd|s_rhadd|u_rhadd|s_clamp|u_clamp|clz|ctz|s_mad_hi|u_mad_sat|s_mad_sat|s_max|u_max|s_min|u_min|s_mul_hi|rotate|s_sub_sat|u_sub_sat|u_upsample|s_upsample|popcount|s_mad24|u_mad24|s_mul24|u_mul24|u_abs|u_abs_diff|u_mul_hi|u_mad_hi|fclamp|degrees|fmax_common|fmin_common|mix|radians|step|smoothstep|sign|cross|distance|length|normalize|fast_distance|fast_length|fast_normalize|bitselect|select|vloadn|vstoren|vload_half|vload_halfn|vstore_half|vstore_half_r|vstore_halfn|vstore_halfn_r|vloada_halfn|vstorea_halfn|vstorea_halfn_r|shuffle|shuffle2|printf|prefetch|DebugInfoNone|DebugCompilationUnit|DebugTypeBasic|DebugTypePointer|DebugTypeQualifier|DebugTypeArray|DebugTypeVector|DebugTypedef|DebugTypeFunction|DebugTypeEnum|DebugTypeComposite|DebugTypeMember|DebugTypeInheritance|DebugTypePtrToMember|DebugTypeTemplate|DebugTypeTemplateParameter|DebugTypeTemplateTemplateParameter|DebugTypeTemplateParameterPack|DebugGlobalVariable|DebugFunctionDeclaration|DebugFunction|DebugLexicalBlock|DebugLexicalBlockDiscriminator|DebugScope|DebugNoScope|DebugInlinedAt|DebugLocalVariable|DebugInlinedVariable|DebugDeclare|DebugValue|DebugOperation|DebugExpression|DebugMacroDef|DebugMacroUndef|DebugImportedEntity|DebugSource)",
+			"name": "entity.name.function.ext"
+		},
 		"identifier": {
 			"match": "%[a-zA-Z0-9_]+",
 			"name": "variable.spirv"
diff --git a/utils/vscode/spirv.json.tmpl b/utils/vscode/spirv.json.tmpl
index 8582d03..f655e52 100644
--- a/utils/vscode/spirv.json.tmpl
+++ b/utils/vscode/spirv.json.tmpl
@@ -3,15 +3,16 @@
 	"name": "SPIR-V",
 	"comment": "Generated by {{GenerateArguments}}. Do not modify this file directly.",
 	"patterns": [
-{{range $o := .OperandKinds}}{{if len $o.Enumerants}}		{ "include": "#{{$o.Category}}_{{$o.Kind}}" },
+{{range $o := .All.OperandKinds}}{{if len $o.Enumerants}}		{ "include": "#{{$o.Category}}_{{$o.Kind}}" },
 {{end}}{{end}}		{ "include": "#opcode" },
+		{ "include": "#extopcode" },
 		{ "include": "#identifier" },
 		{ "include": "#number" },
 		{ "include": "#string" },
 		{ "include": "#comment" },
 		{ "include": "#operator" }
 	],
-	"repository": { {{range $o := .OperandKinds}}{{if len $o.Enumerants}}
+	"repository": { {{range $o := .All.OperandKinds}}{{if len $o.Enumerants}}
 		"{{$o.Category}}_{{$o.Kind}}": {
 			"match": "\\b({{OperandKindsMatch $o}})\\b",
 			"name": "keyword.spirv"
@@ -20,6 +21,10 @@
 			"match": "(Op[a-zA-Z]+)",
 			"name": "entity.name.function.spirv"
 		},
+		"extopcode": {
+			"match": "({{AllExtOpcodes}})",
+			"name": "entity.name.function.ext"
+		},
 		"identifier": {
 			"match": "%[a-zA-Z0-9_]+",
 			"name": "variable.spirv"
diff --git a/utils/vscode/src/parser/parser.go b/utils/vscode/src/parser/parser.go
index 64ba462..9f5691b 100644
--- a/utils/vscode/src/parser/parser.go
+++ b/utils/vscode/src/parser/parser.go
@@ -396,12 +396,13 @@
 func isAlphaNumeric(r rune) bool { return isAlpha(r) || isNumeric(r) }
 
 type parser struct {
-	lines    []string               // all source lines
-	toks     []*Token               // all tokens
-	diags    []Diagnostic           // parser emitted diagnostics
-	idents   map[string]*Identifier // identifiers by name
-	mappings map[*Token]interface{} // tokens to semantic map
-	insts    []*Instruction         // all instructions
+	lines          []string                    // all source lines
+	toks           []*Token                    // all tokens
+	diags          []Diagnostic                // parser emitted diagnostics
+	idents         map[string]*Identifier      // identifiers by name
+	mappings       map[*Token]interface{}      // tokens to semantic map
+	extInstImports map[string]schema.OpcodeMap // extension imports by identifier
+	insts          []*Instruction              // all instructions
 }
 
 func (p *parser) parse() error {
@@ -459,9 +460,9 @@
 		p.addIdentDef(inst.Result.Text(p.lines), inst, p.tok(i))
 	}
 
-	for _, o := range operands {
+	processOperand := func(o schema.Operand) bool {
 		if p.newline(i + n) {
-			break
+			return false
 		}
 
 		switch o.Quantifier {
@@ -481,10 +482,37 @@
 					inst.Tokens = append(inst.Tokens, op.Tokens...)
 					n += c
 				} else {
-					break
+					return false
 				}
 			}
 		}
+		return true
+	}
+
+	for _, o := range operands {
+		if !processOperand(o) {
+			break
+		}
+
+		if inst.Opcode == schema.OpExtInst && n == 4 {
+			extImportTok, extNameTok := p.tok(i+n), p.tok(i+n+1)
+			extImport := extImportTok.Text(p.lines)
+			if extOpcodes, ok := p.extInstImports[extImport]; ok {
+				extName := extNameTok.Text(p.lines)
+				if extOpcode, ok := extOpcodes[extName]; ok {
+					n += 2 // skip ext import, ext name
+					for _, o := range extOpcode.Operands {
+						if !processOperand(o) {
+							break
+						}
+					}
+				} else {
+					p.err(extNameTok, "Unknown extension opcode '%s'", extName)
+				}
+			} else {
+				p.err(extImportTok, "Expected identifier to OpExtInstImport")
+			}
+		}
 	}
 
 	for _, t := range inst.Tokens {
@@ -492,6 +520,19 @@
 	}
 
 	p.insts = append(p.insts, inst)
+
+	if inst.Opcode == schema.OpExtInstImport && len(inst.Tokens) >= 4 {
+		// Instruction is a OpExtInstImport. Keep track of this.
+		extTok := inst.Tokens[3]
+		extName := strings.Trim(extTok.Text(p.lines), `"`)
+		extOpcodes, ok := schema.ExtOpcodes[extName]
+		if !ok {
+			p.err(extTok, "Unknown extension '%s'", extName)
+		}
+		extImport := inst.Result.Text(p.lines)
+		p.extInstImports[extImport] = extOpcodes
+	}
+
 	return
 }
 
@@ -545,7 +586,7 @@
 
 	case schema.OperandCategoryLiteral:
 		switch tok.Type {
-		case String, Integer, Float:
+		case String, Integer, Float, Ident:
 			return op, 1
 		}
 		if !optional {
@@ -683,10 +724,11 @@
 	}
 	lines := strings.SplitAfter(source, "\n")
 	p := parser{
-		lines:    lines,
-		toks:     toks,
-		idents:   map[string]*Identifier{},
-		mappings: map[*Token]interface{}{},
+		lines:          lines,
+		toks:           toks,
+		idents:         map[string]*Identifier{},
+		mappings:       map[*Token]interface{}{},
+		extInstImports: map[string]schema.OpcodeMap{},
 	}
 	if err := p.parse(); err != nil {
 		return Results{}, err
diff --git a/utils/vscode/src/schema/schema.go b/utils/vscode/src/schema/schema.go
index c7e1ca4..66bd7dc 100755
--- a/utils/vscode/src/schema/schema.go
+++ b/utils/vscode/src/schema/schema.go
@@ -97,9 +97,11 @@
 	OperandCategoryComposite = "Composite"
 )
 
+type OpcodeMap map[string]*Opcode
+
 var (
 	// Opcodes is a map of opcode name to Opcode description.
-	Opcodes = map[string]*Opcode {
+	Opcodes = OpcodeMap {
 		"OpNop": OpNop,
 		"OpUndef": OpUndef,
 		"OpSourceContinued": OpSourceContinued,
@@ -627,13 +629,306 @@
 		"OpSubgroupAvcSicGetInterRawSadsINTEL": OpSubgroupAvcSicGetInterRawSadsINTEL,
 	}
 
+	// ExtOpcodes is a map of extension name to Opcode description list.
+	ExtOpcodes = map[string]OpcodeMap {
+		"GLSL.std.450": {
+			"Round": GLSLStd450_Round,
+			"RoundEven": GLSLStd450_RoundEven,
+			"Trunc": GLSLStd450_Trunc,
+			"FAbs": GLSLStd450_FAbs,
+			"SAbs": GLSLStd450_SAbs,
+			"FSign": GLSLStd450_FSign,
+			"SSign": GLSLStd450_SSign,
+			"Floor": GLSLStd450_Floor,
+			"Ceil": GLSLStd450_Ceil,
+			"Fract": GLSLStd450_Fract,
+			"Radians": GLSLStd450_Radians,
+			"Degrees": GLSLStd450_Degrees,
+			"Sin": GLSLStd450_Sin,
+			"Cos": GLSLStd450_Cos,
+			"Tan": GLSLStd450_Tan,
+			"Asin": GLSLStd450_Asin,
+			"Acos": GLSLStd450_Acos,
+			"Atan": GLSLStd450_Atan,
+			"Sinh": GLSLStd450_Sinh,
+			"Cosh": GLSLStd450_Cosh,
+			"Tanh": GLSLStd450_Tanh,
+			"Asinh": GLSLStd450_Asinh,
+			"Acosh": GLSLStd450_Acosh,
+			"Atanh": GLSLStd450_Atanh,
+			"Atan2": GLSLStd450_Atan2,
+			"Pow": GLSLStd450_Pow,
+			"Exp": GLSLStd450_Exp,
+			"Log": GLSLStd450_Log,
+			"Exp2": GLSLStd450_Exp2,
+			"Log2": GLSLStd450_Log2,
+			"Sqrt": GLSLStd450_Sqrt,
+			"InverseSqrt": GLSLStd450_InverseSqrt,
+			"Determinant": GLSLStd450_Determinant,
+			"MatrixInverse": GLSLStd450_MatrixInverse,
+			"Modf": GLSLStd450_Modf,
+			"ModfStruct": GLSLStd450_ModfStruct,
+			"FMin": GLSLStd450_FMin,
+			"UMin": GLSLStd450_UMin,
+			"SMin": GLSLStd450_SMin,
+			"FMax": GLSLStd450_FMax,
+			"UMax": GLSLStd450_UMax,
+			"SMax": GLSLStd450_SMax,
+			"FClamp": GLSLStd450_FClamp,
+			"UClamp": GLSLStd450_UClamp,
+			"SClamp": GLSLStd450_SClamp,
+			"FMix": GLSLStd450_FMix,
+			"IMix": GLSLStd450_IMix,
+			"Step": GLSLStd450_Step,
+			"SmoothStep": GLSLStd450_SmoothStep,
+			"Fma": GLSLStd450_Fma,
+			"Frexp": GLSLStd450_Frexp,
+			"FrexpStruct": GLSLStd450_FrexpStruct,
+			"Ldexp": GLSLStd450_Ldexp,
+			"PackSnorm4x8": GLSLStd450_PackSnorm4x8,
+			"PackUnorm4x8": GLSLStd450_PackUnorm4x8,
+			"PackSnorm2x16": GLSLStd450_PackSnorm2x16,
+			"PackUnorm2x16": GLSLStd450_PackUnorm2x16,
+			"PackHalf2x16": GLSLStd450_PackHalf2x16,
+			"PackDouble2x32": GLSLStd450_PackDouble2x32,
+			"UnpackSnorm2x16": GLSLStd450_UnpackSnorm2x16,
+			"UnpackUnorm2x16": GLSLStd450_UnpackUnorm2x16,
+			"UnpackHalf2x16": GLSLStd450_UnpackHalf2x16,
+			"UnpackSnorm4x8": GLSLStd450_UnpackSnorm4x8,
+			"UnpackUnorm4x8": GLSLStd450_UnpackUnorm4x8,
+			"UnpackDouble2x32": GLSLStd450_UnpackDouble2x32,
+			"Length": GLSLStd450_Length,
+			"Distance": GLSLStd450_Distance,
+			"Cross": GLSLStd450_Cross,
+			"Normalize": GLSLStd450_Normalize,
+			"FaceForward": GLSLStd450_FaceForward,
+			"Reflect": GLSLStd450_Reflect,
+			"Refract": GLSLStd450_Refract,
+			"FindILsb": GLSLStd450_FindILsb,
+			"FindSMsb": GLSLStd450_FindSMsb,
+			"FindUMsb": GLSLStd450_FindUMsb,
+			"InterpolateAtCentroid": GLSLStd450_InterpolateAtCentroid,
+			"InterpolateAtSample": GLSLStd450_InterpolateAtSample,
+			"InterpolateAtOffset": GLSLStd450_InterpolateAtOffset,
+			"NMin": GLSLStd450_NMin,
+			"NMax": GLSLStd450_NMax,
+			"NClamp": GLSLStd450_NClamp,
+		},
+		"OpenCL.std": {
+			"acos": OpenCLStd_acos,
+			"acosh": OpenCLStd_acosh,
+			"acospi": OpenCLStd_acospi,
+			"asin": OpenCLStd_asin,
+			"asinh": OpenCLStd_asinh,
+			"asinpi": OpenCLStd_asinpi,
+			"atan": OpenCLStd_atan,
+			"atan2": OpenCLStd_atan2,
+			"atanh": OpenCLStd_atanh,
+			"atanpi": OpenCLStd_atanpi,
+			"atan2pi": OpenCLStd_atan2pi,
+			"cbrt": OpenCLStd_cbrt,
+			"ceil": OpenCLStd_ceil,
+			"copysign": OpenCLStd_copysign,
+			"cos": OpenCLStd_cos,
+			"cosh": OpenCLStd_cosh,
+			"cospi": OpenCLStd_cospi,
+			"erfc": OpenCLStd_erfc,
+			"erf": OpenCLStd_erf,
+			"exp": OpenCLStd_exp,
+			"exp2": OpenCLStd_exp2,
+			"exp10": OpenCLStd_exp10,
+			"expm1": OpenCLStd_expm1,
+			"fabs": OpenCLStd_fabs,
+			"fdim": OpenCLStd_fdim,
+			"floor": OpenCLStd_floor,
+			"fma": OpenCLStd_fma,
+			"fmax": OpenCLStd_fmax,
+			"fmin": OpenCLStd_fmin,
+			"fmod": OpenCLStd_fmod,
+			"fract": OpenCLStd_fract,
+			"frexp": OpenCLStd_frexp,
+			"hypot": OpenCLStd_hypot,
+			"ilogb": OpenCLStd_ilogb,
+			"ldexp": OpenCLStd_ldexp,
+			"lgamma": OpenCLStd_lgamma,
+			"lgamma_r": OpenCLStd_lgamma_r,
+			"log": OpenCLStd_log,
+			"log2": OpenCLStd_log2,
+			"log10": OpenCLStd_log10,
+			"log1p": OpenCLStd_log1p,
+			"logb": OpenCLStd_logb,
+			"mad": OpenCLStd_mad,
+			"maxmag": OpenCLStd_maxmag,
+			"minmag": OpenCLStd_minmag,
+			"modf": OpenCLStd_modf,
+			"nan": OpenCLStd_nan,
+			"nextafter": OpenCLStd_nextafter,
+			"pow": OpenCLStd_pow,
+			"pown": OpenCLStd_pown,
+			"powr": OpenCLStd_powr,
+			"remainder": OpenCLStd_remainder,
+			"remquo": OpenCLStd_remquo,
+			"rint": OpenCLStd_rint,
+			"rootn": OpenCLStd_rootn,
+			"round": OpenCLStd_round,
+			"rsqrt": OpenCLStd_rsqrt,
+			"sin": OpenCLStd_sin,
+			"sincos": OpenCLStd_sincos,
+			"sinh": OpenCLStd_sinh,
+			"sinpi": OpenCLStd_sinpi,
+			"sqrt": OpenCLStd_sqrt,
+			"tan": OpenCLStd_tan,
+			"tanh": OpenCLStd_tanh,
+			"tanpi": OpenCLStd_tanpi,
+			"tgamma": OpenCLStd_tgamma,
+			"trunc": OpenCLStd_trunc,
+			"half_cos": OpenCLStd_half_cos,
+			"half_divide": OpenCLStd_half_divide,
+			"half_exp": OpenCLStd_half_exp,
+			"half_exp2": OpenCLStd_half_exp2,
+			"half_exp10": OpenCLStd_half_exp10,
+			"half_log": OpenCLStd_half_log,
+			"half_log2": OpenCLStd_half_log2,
+			"half_log10": OpenCLStd_half_log10,
+			"half_powr": OpenCLStd_half_powr,
+			"half_recip": OpenCLStd_half_recip,
+			"half_rsqrt": OpenCLStd_half_rsqrt,
+			"half_sin": OpenCLStd_half_sin,
+			"half_sqrt": OpenCLStd_half_sqrt,
+			"half_tan": OpenCLStd_half_tan,
+			"native_cos": OpenCLStd_native_cos,
+			"native_divide": OpenCLStd_native_divide,
+			"native_exp": OpenCLStd_native_exp,
+			"native_exp2": OpenCLStd_native_exp2,
+			"native_exp10": OpenCLStd_native_exp10,
+			"native_log": OpenCLStd_native_log,
+			"native_log2": OpenCLStd_native_log2,
+			"native_log10": OpenCLStd_native_log10,
+			"native_powr": OpenCLStd_native_powr,
+			"native_recip": OpenCLStd_native_recip,
+			"native_rsqrt": OpenCLStd_native_rsqrt,
+			"native_sin": OpenCLStd_native_sin,
+			"native_sqrt": OpenCLStd_native_sqrt,
+			"native_tan": OpenCLStd_native_tan,
+			"s_abs": OpenCLStd_s_abs,
+			"s_abs_diff": OpenCLStd_s_abs_diff,
+			"s_add_sat": OpenCLStd_s_add_sat,
+			"u_add_sat": OpenCLStd_u_add_sat,
+			"s_hadd": OpenCLStd_s_hadd,
+			"u_hadd": OpenCLStd_u_hadd,
+			"s_rhadd": OpenCLStd_s_rhadd,
+			"u_rhadd": OpenCLStd_u_rhadd,
+			"s_clamp": OpenCLStd_s_clamp,
+			"u_clamp": OpenCLStd_u_clamp,
+			"clz": OpenCLStd_clz,
+			"ctz": OpenCLStd_ctz,
+			"s_mad_hi": OpenCLStd_s_mad_hi,
+			"u_mad_sat": OpenCLStd_u_mad_sat,
+			"s_mad_sat": OpenCLStd_s_mad_sat,
+			"s_max": OpenCLStd_s_max,
+			"u_max": OpenCLStd_u_max,
+			"s_min": OpenCLStd_s_min,
+			"u_min": OpenCLStd_u_min,
+			"s_mul_hi": OpenCLStd_s_mul_hi,
+			"rotate": OpenCLStd_rotate,
+			"s_sub_sat": OpenCLStd_s_sub_sat,
+			"u_sub_sat": OpenCLStd_u_sub_sat,
+			"u_upsample": OpenCLStd_u_upsample,
+			"s_upsample": OpenCLStd_s_upsample,
+			"popcount": OpenCLStd_popcount,
+			"s_mad24": OpenCLStd_s_mad24,
+			"u_mad24": OpenCLStd_u_mad24,
+			"s_mul24": OpenCLStd_s_mul24,
+			"u_mul24": OpenCLStd_u_mul24,
+			"u_abs": OpenCLStd_u_abs,
+			"u_abs_diff": OpenCLStd_u_abs_diff,
+			"u_mul_hi": OpenCLStd_u_mul_hi,
+			"u_mad_hi": OpenCLStd_u_mad_hi,
+			"fclamp": OpenCLStd_fclamp,
+			"degrees": OpenCLStd_degrees,
+			"fmax_common": OpenCLStd_fmax_common,
+			"fmin_common": OpenCLStd_fmin_common,
+			"mix": OpenCLStd_mix,
+			"radians": OpenCLStd_radians,
+			"step": OpenCLStd_step,
+			"smoothstep": OpenCLStd_smoothstep,
+			"sign": OpenCLStd_sign,
+			"cross": OpenCLStd_cross,
+			"distance": OpenCLStd_distance,
+			"length": OpenCLStd_length,
+			"normalize": OpenCLStd_normalize,
+			"fast_distance": OpenCLStd_fast_distance,
+			"fast_length": OpenCLStd_fast_length,
+			"fast_normalize": OpenCLStd_fast_normalize,
+			"bitselect": OpenCLStd_bitselect,
+			"select": OpenCLStd_select,
+			"vloadn": OpenCLStd_vloadn,
+			"vstoren": OpenCLStd_vstoren,
+			"vload_half": OpenCLStd_vload_half,
+			"vload_halfn": OpenCLStd_vload_halfn,
+			"vstore_half": OpenCLStd_vstore_half,
+			"vstore_half_r": OpenCLStd_vstore_half_r,
+			"vstore_halfn": OpenCLStd_vstore_halfn,
+			"vstore_halfn_r": OpenCLStd_vstore_halfn_r,
+			"vloada_halfn": OpenCLStd_vloada_halfn,
+			"vstorea_halfn": OpenCLStd_vstorea_halfn,
+			"vstorea_halfn_r": OpenCLStd_vstorea_halfn_r,
+			"shuffle": OpenCLStd_shuffle,
+			"shuffle2": OpenCLStd_shuffle2,
+			"printf": OpenCLStd_printf,
+			"prefetch": OpenCLStd_prefetch,
+		},
+		"OpenCL.DebugInfo.100": {
+			"DebugInfoNone": OpenCLDebugInfo100_DebugInfoNone,
+			"DebugCompilationUnit": OpenCLDebugInfo100_DebugCompilationUnit,
+			"DebugTypeBasic": OpenCLDebugInfo100_DebugTypeBasic,
+			"DebugTypePointer": OpenCLDebugInfo100_DebugTypePointer,
+			"DebugTypeQualifier": OpenCLDebugInfo100_DebugTypeQualifier,
+			"DebugTypeArray": OpenCLDebugInfo100_DebugTypeArray,
+			"DebugTypeVector": OpenCLDebugInfo100_DebugTypeVector,
+			"DebugTypedef": OpenCLDebugInfo100_DebugTypedef,
+			"DebugTypeFunction": OpenCLDebugInfo100_DebugTypeFunction,
+			"DebugTypeEnum": OpenCLDebugInfo100_DebugTypeEnum,
+			"DebugTypeComposite": OpenCLDebugInfo100_DebugTypeComposite,
+			"DebugTypeMember": OpenCLDebugInfo100_DebugTypeMember,
+			"DebugTypeInheritance": OpenCLDebugInfo100_DebugTypeInheritance,
+			"DebugTypePtrToMember": OpenCLDebugInfo100_DebugTypePtrToMember,
+			"DebugTypeTemplate": OpenCLDebugInfo100_DebugTypeTemplate,
+			"DebugTypeTemplateParameter": OpenCLDebugInfo100_DebugTypeTemplateParameter,
+			"DebugTypeTemplateTemplateParameter": OpenCLDebugInfo100_DebugTypeTemplateTemplateParameter,
+			"DebugTypeTemplateParameterPack": OpenCLDebugInfo100_DebugTypeTemplateParameterPack,
+			"DebugGlobalVariable": OpenCLDebugInfo100_DebugGlobalVariable,
+			"DebugFunctionDeclaration": OpenCLDebugInfo100_DebugFunctionDeclaration,
+			"DebugFunction": OpenCLDebugInfo100_DebugFunction,
+			"DebugLexicalBlock": OpenCLDebugInfo100_DebugLexicalBlock,
+			"DebugLexicalBlockDiscriminator": OpenCLDebugInfo100_DebugLexicalBlockDiscriminator,
+			"DebugScope": OpenCLDebugInfo100_DebugScope,
+			"DebugNoScope": OpenCLDebugInfo100_DebugNoScope,
+			"DebugInlinedAt": OpenCLDebugInfo100_DebugInlinedAt,
+			"DebugLocalVariable": OpenCLDebugInfo100_DebugLocalVariable,
+			"DebugInlinedVariable": OpenCLDebugInfo100_DebugInlinedVariable,
+			"DebugDeclare": OpenCLDebugInfo100_DebugDeclare,
+			"DebugValue": OpenCLDebugInfo100_DebugValue,
+			"DebugOperation": OpenCLDebugInfo100_DebugOperation,
+			"DebugExpression": OpenCLDebugInfo100_DebugExpression,
+			"DebugMacroDef": OpenCLDebugInfo100_DebugMacroDef,
+			"DebugMacroUndef": OpenCLDebugInfo100_DebugMacroUndef,
+			"DebugImportedEntity": OpenCLDebugInfo100_DebugImportedEntity,
+			"DebugSource": OpenCLDebugInfo100_DebugSource,
+		},
+	}
+
 	OpNop = &Opcode {
 		Opname:   "OpNop",
+		Class:    "Miscellaneous",
+		Opcode:   0,
 		Operands: []Operand {
 		},
 	}
 	OpUndef = &Opcode {
 		Opname:   "OpUndef",
+		Class:    "Miscellaneous",
+		Opcode:   1,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -649,6 +944,8 @@
 	}
 	OpSourceContinued = &Opcode {
 		Opname:   "OpSourceContinued",
+		Class:    "Debug",
+		Opcode:   2,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindLiteralString,
@@ -659,6 +956,8 @@
 	}
 	OpSource = &Opcode {
 		Opname:   "OpSource",
+		Class:    "Debug",
+		Opcode:   3,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindSourceLanguage,
@@ -684,6 +983,8 @@
 	}
 	OpSourceExtension = &Opcode {
 		Opname:   "OpSourceExtension",
+		Class:    "Debug",
+		Opcode:   4,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindLiteralString,
@@ -694,6 +995,8 @@
 	}
 	OpName = &Opcode {
 		Opname:   "OpName",
+		Class:    "Debug",
+		Opcode:   5,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -709,6 +1012,8 @@
 	}
 	OpMemberName = &Opcode {
 		Opname:   "OpMemberName",
+		Class:    "Debug",
+		Opcode:   6,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -729,6 +1034,8 @@
 	}
 	OpString = &Opcode {
 		Opname:   "OpString",
+		Class:    "Debug",
+		Opcode:   7,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResult,
@@ -744,6 +1051,8 @@
 	}
 	OpLine = &Opcode {
 		Opname:   "OpLine",
+		Class:    "Debug",
+		Opcode:   8,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -764,6 +1073,8 @@
 	}
 	OpExtension = &Opcode {
 		Opname:   "OpExtension",
+		Class:    "Extension",
+		Opcode:   10,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindLiteralString,
@@ -774,6 +1085,8 @@
 	}
 	OpExtInstImport = &Opcode {
 		Opname:   "OpExtInstImport",
+		Class:    "Extension",
+		Opcode:   11,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResult,
@@ -789,6 +1102,8 @@
 	}
 	OpExtInst = &Opcode {
 		Opname:   "OpExtInst",
+		Class:    "Extension",
+		Opcode:   12,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -819,6 +1134,8 @@
 	}
 	OpMemoryModel = &Opcode {
 		Opname:   "OpMemoryModel",
+		Class:    "Mode-Setting",
+		Opcode:   14,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindAddressingModel,
@@ -834,6 +1151,8 @@
 	}
 	OpEntryPoint = &Opcode {
 		Opname:   "OpEntryPoint",
+		Class:    "Mode-Setting",
+		Opcode:   15,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindExecutionModel,
@@ -859,6 +1178,8 @@
 	}
 	OpExecutionMode = &Opcode {
 		Opname:   "OpExecutionMode",
+		Class:    "Mode-Setting",
+		Opcode:   16,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -874,6 +1195,8 @@
 	}
 	OpCapability = &Opcode {
 		Opname:   "OpCapability",
+		Class:    "Mode-Setting",
+		Opcode:   17,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindCapability,
@@ -884,6 +1207,8 @@
 	}
 	OpTypeVoid = &Opcode {
 		Opname:   "OpTypeVoid",
+		Class:    "Type-Declaration",
+		Opcode:   19,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResult,
@@ -894,6 +1219,8 @@
 	}
 	OpTypeBool = &Opcode {
 		Opname:   "OpTypeBool",
+		Class:    "Type-Declaration",
+		Opcode:   20,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResult,
@@ -904,6 +1231,8 @@
 	}
 	OpTypeInt = &Opcode {
 		Opname:   "OpTypeInt",
+		Class:    "Type-Declaration",
+		Opcode:   21,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResult,
@@ -924,6 +1253,8 @@
 	}
 	OpTypeFloat = &Opcode {
 		Opname:   "OpTypeFloat",
+		Class:    "Type-Declaration",
+		Opcode:   22,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResult,
@@ -939,6 +1270,8 @@
 	}
 	OpTypeVector = &Opcode {
 		Opname:   "OpTypeVector",
+		Class:    "Type-Declaration",
+		Opcode:   23,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResult,
@@ -959,6 +1292,8 @@
 	}
 	OpTypeMatrix = &Opcode {
 		Opname:   "OpTypeMatrix",
+		Class:    "Type-Declaration",
+		Opcode:   24,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResult,
@@ -979,6 +1314,8 @@
 	}
 	OpTypeImage = &Opcode {
 		Opname:   "OpTypeImage",
+		Class:    "Type-Declaration",
+		Opcode:   25,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResult,
@@ -1029,6 +1366,8 @@
 	}
 	OpTypeSampler = &Opcode {
 		Opname:   "OpTypeSampler",
+		Class:    "Type-Declaration",
+		Opcode:   26,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResult,
@@ -1039,6 +1378,8 @@
 	}
 	OpTypeSampledImage = &Opcode {
 		Opname:   "OpTypeSampledImage",
+		Class:    "Type-Declaration",
+		Opcode:   27,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResult,
@@ -1054,6 +1395,8 @@
 	}
 	OpTypeArray = &Opcode {
 		Opname:   "OpTypeArray",
+		Class:    "Type-Declaration",
+		Opcode:   28,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResult,
@@ -1074,6 +1417,8 @@
 	}
 	OpTypeRuntimeArray = &Opcode {
 		Opname:   "OpTypeRuntimeArray",
+		Class:    "Type-Declaration",
+		Opcode:   29,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResult,
@@ -1089,6 +1434,8 @@
 	}
 	OpTypeStruct = &Opcode {
 		Opname:   "OpTypeStruct",
+		Class:    "Type-Declaration",
+		Opcode:   30,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResult,
@@ -1104,6 +1451,8 @@
 	}
 	OpTypeOpaque = &Opcode {
 		Opname:   "OpTypeOpaque",
+		Class:    "Type-Declaration",
+		Opcode:   31,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResult,
@@ -1119,6 +1468,8 @@
 	}
 	OpTypePointer = &Opcode {
 		Opname:   "OpTypePointer",
+		Class:    "Type-Declaration",
+		Opcode:   32,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResult,
@@ -1139,6 +1490,8 @@
 	}
 	OpTypeFunction = &Opcode {
 		Opname:   "OpTypeFunction",
+		Class:    "Type-Declaration",
+		Opcode:   33,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResult,
@@ -1159,6 +1512,8 @@
 	}
 	OpTypeEvent = &Opcode {
 		Opname:   "OpTypeEvent",
+		Class:    "Type-Declaration",
+		Opcode:   34,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResult,
@@ -1169,6 +1524,8 @@
 	}
 	OpTypeDeviceEvent = &Opcode {
 		Opname:   "OpTypeDeviceEvent",
+		Class:    "Type-Declaration",
+		Opcode:   35,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResult,
@@ -1179,6 +1536,8 @@
 	}
 	OpTypeReserveId = &Opcode {
 		Opname:   "OpTypeReserveId",
+		Class:    "Type-Declaration",
+		Opcode:   36,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResult,
@@ -1189,6 +1548,8 @@
 	}
 	OpTypeQueue = &Opcode {
 		Opname:   "OpTypeQueue",
+		Class:    "Type-Declaration",
+		Opcode:   37,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResult,
@@ -1199,6 +1560,8 @@
 	}
 	OpTypePipe = &Opcode {
 		Opname:   "OpTypePipe",
+		Class:    "Type-Declaration",
+		Opcode:   38,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResult,
@@ -1214,6 +1577,8 @@
 	}
 	OpTypeForwardPointer = &Opcode {
 		Opname:   "OpTypeForwardPointer",
+		Class:    "Type-Declaration",
+		Opcode:   39,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -1229,6 +1594,8 @@
 	}
 	OpConstantTrue = &Opcode {
 		Opname:   "OpConstantTrue",
+		Class:    "Constant-Creation",
+		Opcode:   41,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -1244,6 +1611,8 @@
 	}
 	OpConstantFalse = &Opcode {
 		Opname:   "OpConstantFalse",
+		Class:    "Constant-Creation",
+		Opcode:   42,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -1259,6 +1628,8 @@
 	}
 	OpConstant = &Opcode {
 		Opname:   "OpConstant",
+		Class:    "Constant-Creation",
+		Opcode:   43,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -1279,6 +1650,8 @@
 	}
 	OpConstantComposite = &Opcode {
 		Opname:   "OpConstantComposite",
+		Class:    "Constant-Creation",
+		Opcode:   44,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -1299,6 +1672,8 @@
 	}
 	OpConstantSampler = &Opcode {
 		Opname:   "OpConstantSampler",
+		Class:    "Constant-Creation",
+		Opcode:   45,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -1329,6 +1704,8 @@
 	}
 	OpConstantNull = &Opcode {
 		Opname:   "OpConstantNull",
+		Class:    "Constant-Creation",
+		Opcode:   46,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -1344,6 +1721,8 @@
 	}
 	OpSpecConstantTrue = &Opcode {
 		Opname:   "OpSpecConstantTrue",
+		Class:    "Constant-Creation",
+		Opcode:   48,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -1359,6 +1738,8 @@
 	}
 	OpSpecConstantFalse = &Opcode {
 		Opname:   "OpSpecConstantFalse",
+		Class:    "Constant-Creation",
+		Opcode:   49,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -1374,6 +1755,8 @@
 	}
 	OpSpecConstant = &Opcode {
 		Opname:   "OpSpecConstant",
+		Class:    "Constant-Creation",
+		Opcode:   50,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -1394,6 +1777,8 @@
 	}
 	OpSpecConstantComposite = &Opcode {
 		Opname:   "OpSpecConstantComposite",
+		Class:    "Constant-Creation",
+		Opcode:   51,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -1414,6 +1799,8 @@
 	}
 	OpSpecConstantOp = &Opcode {
 		Opname:   "OpSpecConstantOp",
+		Class:    "Constant-Creation",
+		Opcode:   52,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -1434,6 +1821,8 @@
 	}
 	OpFunction = &Opcode {
 		Opname:   "OpFunction",
+		Class:    "Function",
+		Opcode:   54,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -1459,6 +1848,8 @@
 	}
 	OpFunctionParameter = &Opcode {
 		Opname:   "OpFunctionParameter",
+		Class:    "Function",
+		Opcode:   55,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -1474,11 +1865,15 @@
 	}
 	OpFunctionEnd = &Opcode {
 		Opname:   "OpFunctionEnd",
+		Class:    "Function",
+		Opcode:   56,
 		Operands: []Operand {
 		},
 	}
 	OpFunctionCall = &Opcode {
 		Opname:   "OpFunctionCall",
+		Class:    "Function",
+		Opcode:   57,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -1504,6 +1899,8 @@
 	}
 	OpVariable = &Opcode {
 		Opname:   "OpVariable",
+		Class:    "Memory",
+		Opcode:   59,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -1529,6 +1926,8 @@
 	}
 	OpImageTexelPointer = &Opcode {
 		Opname:   "OpImageTexelPointer",
+		Class:    "Memory",
+		Opcode:   60,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -1559,6 +1958,8 @@
 	}
 	OpLoad = &Opcode {
 		Opname:   "OpLoad",
+		Class:    "Memory",
+		Opcode:   61,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -1584,6 +1985,8 @@
 	}
 	OpStore = &Opcode {
 		Opname:   "OpStore",
+		Class:    "Memory",
+		Opcode:   62,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -1604,6 +2007,8 @@
 	}
 	OpCopyMemory = &Opcode {
 		Opname:   "OpCopyMemory",
+		Class:    "Memory",
+		Opcode:   63,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -1629,6 +2034,8 @@
 	}
 	OpCopyMemorySized = &Opcode {
 		Opname:   "OpCopyMemorySized",
+		Class:    "Memory",
+		Opcode:   64,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -1659,6 +2066,8 @@
 	}
 	OpAccessChain = &Opcode {
 		Opname:   "OpAccessChain",
+		Class:    "Memory",
+		Opcode:   65,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -1684,6 +2093,8 @@
 	}
 	OpInBoundsAccessChain = &Opcode {
 		Opname:   "OpInBoundsAccessChain",
+		Class:    "Memory",
+		Opcode:   66,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -1709,6 +2120,8 @@
 	}
 	OpPtrAccessChain = &Opcode {
 		Opname:   "OpPtrAccessChain",
+		Class:    "Memory",
+		Opcode:   67,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -1739,6 +2152,8 @@
 	}
 	OpArrayLength = &Opcode {
 		Opname:   "OpArrayLength",
+		Class:    "Memory",
+		Opcode:   68,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -1764,6 +2179,8 @@
 	}
 	OpGenericPtrMemSemantics = &Opcode {
 		Opname:   "OpGenericPtrMemSemantics",
+		Class:    "Memory",
+		Opcode:   69,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -1784,6 +2201,8 @@
 	}
 	OpInBoundsPtrAccessChain = &Opcode {
 		Opname:   "OpInBoundsPtrAccessChain",
+		Class:    "Memory",
+		Opcode:   70,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -1814,6 +2233,8 @@
 	}
 	OpDecorate = &Opcode {
 		Opname:   "OpDecorate",
+		Class:    "Annotation",
+		Opcode:   71,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -1829,6 +2250,8 @@
 	}
 	OpMemberDecorate = &Opcode {
 		Opname:   "OpMemberDecorate",
+		Class:    "Annotation",
+		Opcode:   72,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -1849,6 +2272,8 @@
 	}
 	OpDecorationGroup = &Opcode {
 		Opname:   "OpDecorationGroup",
+		Class:    "Annotation",
+		Opcode:   73,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResult,
@@ -1859,6 +2284,8 @@
 	}
 	OpGroupDecorate = &Opcode {
 		Opname:   "OpGroupDecorate",
+		Class:    "Annotation",
+		Opcode:   74,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -1874,6 +2301,8 @@
 	}
 	OpGroupMemberDecorate = &Opcode {
 		Opname:   "OpGroupMemberDecorate",
+		Class:    "Annotation",
+		Opcode:   75,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -1889,6 +2318,8 @@
 	}
 	OpVectorExtractDynamic = &Opcode {
 		Opname:   "OpVectorExtractDynamic",
+		Class:    "Composite",
+		Opcode:   77,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -1914,6 +2345,8 @@
 	}
 	OpVectorInsertDynamic = &Opcode {
 		Opname:   "OpVectorInsertDynamic",
+		Class:    "Composite",
+		Opcode:   78,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -1944,6 +2377,8 @@
 	}
 	OpVectorShuffle = &Opcode {
 		Opname:   "OpVectorShuffle",
+		Class:    "Composite",
+		Opcode:   79,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -1974,6 +2409,8 @@
 	}
 	OpCompositeConstruct = &Opcode {
 		Opname:   "OpCompositeConstruct",
+		Class:    "Composite",
+		Opcode:   80,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -1994,6 +2431,8 @@
 	}
 	OpCompositeExtract = &Opcode {
 		Opname:   "OpCompositeExtract",
+		Class:    "Composite",
+		Opcode:   81,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -2019,6 +2458,8 @@
 	}
 	OpCompositeInsert = &Opcode {
 		Opname:   "OpCompositeInsert",
+		Class:    "Composite",
+		Opcode:   82,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -2049,6 +2490,8 @@
 	}
 	OpCopyObject = &Opcode {
 		Opname:   "OpCopyObject",
+		Class:    "Composite",
+		Opcode:   83,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -2069,6 +2512,8 @@
 	}
 	OpTranspose = &Opcode {
 		Opname:   "OpTranspose",
+		Class:    "Composite",
+		Opcode:   84,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -2089,6 +2534,8 @@
 	}
 	OpSampledImage = &Opcode {
 		Opname:   "OpSampledImage",
+		Class:    "Image",
+		Opcode:   86,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -2114,6 +2561,8 @@
 	}
 	OpImageSampleImplicitLod = &Opcode {
 		Opname:   "OpImageSampleImplicitLod",
+		Class:    "Image",
+		Opcode:   87,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -2144,6 +2593,8 @@
 	}
 	OpImageSampleExplicitLod = &Opcode {
 		Opname:   "OpImageSampleExplicitLod",
+		Class:    "Image",
+		Opcode:   88,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -2174,6 +2625,8 @@
 	}
 	OpImageSampleDrefImplicitLod = &Opcode {
 		Opname:   "OpImageSampleDrefImplicitLod",
+		Class:    "Image",
+		Opcode:   89,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -2209,6 +2662,8 @@
 	}
 	OpImageSampleDrefExplicitLod = &Opcode {
 		Opname:   "OpImageSampleDrefExplicitLod",
+		Class:    "Image",
+		Opcode:   90,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -2244,6 +2699,8 @@
 	}
 	OpImageSampleProjImplicitLod = &Opcode {
 		Opname:   "OpImageSampleProjImplicitLod",
+		Class:    "Image",
+		Opcode:   91,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -2274,6 +2731,8 @@
 	}
 	OpImageSampleProjExplicitLod = &Opcode {
 		Opname:   "OpImageSampleProjExplicitLod",
+		Class:    "Image",
+		Opcode:   92,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -2304,6 +2763,8 @@
 	}
 	OpImageSampleProjDrefImplicitLod = &Opcode {
 		Opname:   "OpImageSampleProjDrefImplicitLod",
+		Class:    "Image",
+		Opcode:   93,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -2339,6 +2800,8 @@
 	}
 	OpImageSampleProjDrefExplicitLod = &Opcode {
 		Opname:   "OpImageSampleProjDrefExplicitLod",
+		Class:    "Image",
+		Opcode:   94,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -2374,6 +2837,8 @@
 	}
 	OpImageFetch = &Opcode {
 		Opname:   "OpImageFetch",
+		Class:    "Image",
+		Opcode:   95,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -2404,6 +2869,8 @@
 	}
 	OpImageGather = &Opcode {
 		Opname:   "OpImageGather",
+		Class:    "Image",
+		Opcode:   96,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -2439,6 +2906,8 @@
 	}
 	OpImageDrefGather = &Opcode {
 		Opname:   "OpImageDrefGather",
+		Class:    "Image",
+		Opcode:   97,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -2474,6 +2943,8 @@
 	}
 	OpImageRead = &Opcode {
 		Opname:   "OpImageRead",
+		Class:    "Image",
+		Opcode:   98,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -2504,6 +2975,8 @@
 	}
 	OpImageWrite = &Opcode {
 		Opname:   "OpImageWrite",
+		Class:    "Image",
+		Opcode:   99,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -2529,6 +3002,8 @@
 	}
 	OpImage = &Opcode {
 		Opname:   "OpImage",
+		Class:    "Image",
+		Opcode:   100,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -2549,6 +3024,8 @@
 	}
 	OpImageQueryFormat = &Opcode {
 		Opname:   "OpImageQueryFormat",
+		Class:    "Image",
+		Opcode:   101,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -2569,6 +3046,8 @@
 	}
 	OpImageQueryOrder = &Opcode {
 		Opname:   "OpImageQueryOrder",
+		Class:    "Image",
+		Opcode:   102,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -2589,6 +3068,8 @@
 	}
 	OpImageQuerySizeLod = &Opcode {
 		Opname:   "OpImageQuerySizeLod",
+		Class:    "Image",
+		Opcode:   103,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -2614,6 +3095,8 @@
 	}
 	OpImageQuerySize = &Opcode {
 		Opname:   "OpImageQuerySize",
+		Class:    "Image",
+		Opcode:   104,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -2634,6 +3117,8 @@
 	}
 	OpImageQueryLod = &Opcode {
 		Opname:   "OpImageQueryLod",
+		Class:    "Image",
+		Opcode:   105,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -2659,6 +3144,8 @@
 	}
 	OpImageQueryLevels = &Opcode {
 		Opname:   "OpImageQueryLevels",
+		Class:    "Image",
+		Opcode:   106,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -2679,6 +3166,8 @@
 	}
 	OpImageQuerySamples = &Opcode {
 		Opname:   "OpImageQuerySamples",
+		Class:    "Image",
+		Opcode:   107,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -2699,6 +3188,8 @@
 	}
 	OpConvertFToU = &Opcode {
 		Opname:   "OpConvertFToU",
+		Class:    "Conversion",
+		Opcode:   109,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -2719,6 +3210,8 @@
 	}
 	OpConvertFToS = &Opcode {
 		Opname:   "OpConvertFToS",
+		Class:    "Conversion",
+		Opcode:   110,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -2739,6 +3232,8 @@
 	}
 	OpConvertSToF = &Opcode {
 		Opname:   "OpConvertSToF",
+		Class:    "Conversion",
+		Opcode:   111,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -2759,6 +3254,8 @@
 	}
 	OpConvertUToF = &Opcode {
 		Opname:   "OpConvertUToF",
+		Class:    "Conversion",
+		Opcode:   112,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -2779,6 +3276,8 @@
 	}
 	OpUConvert = &Opcode {
 		Opname:   "OpUConvert",
+		Class:    "Conversion",
+		Opcode:   113,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -2799,6 +3298,8 @@
 	}
 	OpSConvert = &Opcode {
 		Opname:   "OpSConvert",
+		Class:    "Conversion",
+		Opcode:   114,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -2819,6 +3320,8 @@
 	}
 	OpFConvert = &Opcode {
 		Opname:   "OpFConvert",
+		Class:    "Conversion",
+		Opcode:   115,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -2839,6 +3342,8 @@
 	}
 	OpQuantizeToF16 = &Opcode {
 		Opname:   "OpQuantizeToF16",
+		Class:    "Conversion",
+		Opcode:   116,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -2859,6 +3364,8 @@
 	}
 	OpConvertPtrToU = &Opcode {
 		Opname:   "OpConvertPtrToU",
+		Class:    "Conversion",
+		Opcode:   117,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -2879,6 +3386,8 @@
 	}
 	OpSatConvertSToU = &Opcode {
 		Opname:   "OpSatConvertSToU",
+		Class:    "Conversion",
+		Opcode:   118,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -2899,6 +3408,8 @@
 	}
 	OpSatConvertUToS = &Opcode {
 		Opname:   "OpSatConvertUToS",
+		Class:    "Conversion",
+		Opcode:   119,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -2919,6 +3430,8 @@
 	}
 	OpConvertUToPtr = &Opcode {
 		Opname:   "OpConvertUToPtr",
+		Class:    "Conversion",
+		Opcode:   120,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -2939,6 +3452,8 @@
 	}
 	OpPtrCastToGeneric = &Opcode {
 		Opname:   "OpPtrCastToGeneric",
+		Class:    "Conversion",
+		Opcode:   121,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -2959,6 +3474,8 @@
 	}
 	OpGenericCastToPtr = &Opcode {
 		Opname:   "OpGenericCastToPtr",
+		Class:    "Conversion",
+		Opcode:   122,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -2979,6 +3496,8 @@
 	}
 	OpGenericCastToPtrExplicit = &Opcode {
 		Opname:   "OpGenericCastToPtrExplicit",
+		Class:    "Conversion",
+		Opcode:   123,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -3004,6 +3523,8 @@
 	}
 	OpBitcast = &Opcode {
 		Opname:   "OpBitcast",
+		Class:    "Conversion",
+		Opcode:   124,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -3024,6 +3545,8 @@
 	}
 	OpSNegate = &Opcode {
 		Opname:   "OpSNegate",
+		Class:    "Arithmetic",
+		Opcode:   126,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -3044,6 +3567,8 @@
 	}
 	OpFNegate = &Opcode {
 		Opname:   "OpFNegate",
+		Class:    "Arithmetic",
+		Opcode:   127,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -3064,6 +3589,8 @@
 	}
 	OpIAdd = &Opcode {
 		Opname:   "OpIAdd",
+		Class:    "Arithmetic",
+		Opcode:   128,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -3089,6 +3616,8 @@
 	}
 	OpFAdd = &Opcode {
 		Opname:   "OpFAdd",
+		Class:    "Arithmetic",
+		Opcode:   129,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -3114,6 +3643,8 @@
 	}
 	OpISub = &Opcode {
 		Opname:   "OpISub",
+		Class:    "Arithmetic",
+		Opcode:   130,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -3139,6 +3670,8 @@
 	}
 	OpFSub = &Opcode {
 		Opname:   "OpFSub",
+		Class:    "Arithmetic",
+		Opcode:   131,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -3164,6 +3697,8 @@
 	}
 	OpIMul = &Opcode {
 		Opname:   "OpIMul",
+		Class:    "Arithmetic",
+		Opcode:   132,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -3189,6 +3724,8 @@
 	}
 	OpFMul = &Opcode {
 		Opname:   "OpFMul",
+		Class:    "Arithmetic",
+		Opcode:   133,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -3214,6 +3751,8 @@
 	}
 	OpUDiv = &Opcode {
 		Opname:   "OpUDiv",
+		Class:    "Arithmetic",
+		Opcode:   134,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -3239,6 +3778,8 @@
 	}
 	OpSDiv = &Opcode {
 		Opname:   "OpSDiv",
+		Class:    "Arithmetic",
+		Opcode:   135,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -3264,6 +3805,8 @@
 	}
 	OpFDiv = &Opcode {
 		Opname:   "OpFDiv",
+		Class:    "Arithmetic",
+		Opcode:   136,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -3289,6 +3832,8 @@
 	}
 	OpUMod = &Opcode {
 		Opname:   "OpUMod",
+		Class:    "Arithmetic",
+		Opcode:   137,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -3314,6 +3859,8 @@
 	}
 	OpSRem = &Opcode {
 		Opname:   "OpSRem",
+		Class:    "Arithmetic",
+		Opcode:   138,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -3339,6 +3886,8 @@
 	}
 	OpSMod = &Opcode {
 		Opname:   "OpSMod",
+		Class:    "Arithmetic",
+		Opcode:   139,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -3364,6 +3913,8 @@
 	}
 	OpFRem = &Opcode {
 		Opname:   "OpFRem",
+		Class:    "Arithmetic",
+		Opcode:   140,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -3389,6 +3940,8 @@
 	}
 	OpFMod = &Opcode {
 		Opname:   "OpFMod",
+		Class:    "Arithmetic",
+		Opcode:   141,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -3414,6 +3967,8 @@
 	}
 	OpVectorTimesScalar = &Opcode {
 		Opname:   "OpVectorTimesScalar",
+		Class:    "Arithmetic",
+		Opcode:   142,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -3439,6 +3994,8 @@
 	}
 	OpMatrixTimesScalar = &Opcode {
 		Opname:   "OpMatrixTimesScalar",
+		Class:    "Arithmetic",
+		Opcode:   143,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -3464,6 +4021,8 @@
 	}
 	OpVectorTimesMatrix = &Opcode {
 		Opname:   "OpVectorTimesMatrix",
+		Class:    "Arithmetic",
+		Opcode:   144,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -3489,6 +4048,8 @@
 	}
 	OpMatrixTimesVector = &Opcode {
 		Opname:   "OpMatrixTimesVector",
+		Class:    "Arithmetic",
+		Opcode:   145,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -3514,6 +4075,8 @@
 	}
 	OpMatrixTimesMatrix = &Opcode {
 		Opname:   "OpMatrixTimesMatrix",
+		Class:    "Arithmetic",
+		Opcode:   146,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -3539,6 +4102,8 @@
 	}
 	OpOuterProduct = &Opcode {
 		Opname:   "OpOuterProduct",
+		Class:    "Arithmetic",
+		Opcode:   147,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -3564,6 +4129,8 @@
 	}
 	OpDot = &Opcode {
 		Opname:   "OpDot",
+		Class:    "Arithmetic",
+		Opcode:   148,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -3589,6 +4156,8 @@
 	}
 	OpIAddCarry = &Opcode {
 		Opname:   "OpIAddCarry",
+		Class:    "Arithmetic",
+		Opcode:   149,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -3614,6 +4183,8 @@
 	}
 	OpISubBorrow = &Opcode {
 		Opname:   "OpISubBorrow",
+		Class:    "Arithmetic",
+		Opcode:   150,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -3639,6 +4210,8 @@
 	}
 	OpUMulExtended = &Opcode {
 		Opname:   "OpUMulExtended",
+		Class:    "Arithmetic",
+		Opcode:   151,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -3664,6 +4237,8 @@
 	}
 	OpSMulExtended = &Opcode {
 		Opname:   "OpSMulExtended",
+		Class:    "Arithmetic",
+		Opcode:   152,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -3689,6 +4264,8 @@
 	}
 	OpAny = &Opcode {
 		Opname:   "OpAny",
+		Class:    "Relational_and_Logical",
+		Opcode:   154,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -3709,6 +4286,8 @@
 	}
 	OpAll = &Opcode {
 		Opname:   "OpAll",
+		Class:    "Relational_and_Logical",
+		Opcode:   155,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -3729,6 +4308,8 @@
 	}
 	OpIsNan = &Opcode {
 		Opname:   "OpIsNan",
+		Class:    "Relational_and_Logical",
+		Opcode:   156,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -3749,6 +4330,8 @@
 	}
 	OpIsInf = &Opcode {
 		Opname:   "OpIsInf",
+		Class:    "Relational_and_Logical",
+		Opcode:   157,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -3769,6 +4352,8 @@
 	}
 	OpIsFinite = &Opcode {
 		Opname:   "OpIsFinite",
+		Class:    "Relational_and_Logical",
+		Opcode:   158,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -3789,6 +4374,8 @@
 	}
 	OpIsNormal = &Opcode {
 		Opname:   "OpIsNormal",
+		Class:    "Relational_and_Logical",
+		Opcode:   159,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -3809,6 +4396,8 @@
 	}
 	OpSignBitSet = &Opcode {
 		Opname:   "OpSignBitSet",
+		Class:    "Relational_and_Logical",
+		Opcode:   160,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -3829,6 +4418,8 @@
 	}
 	OpLessOrGreater = &Opcode {
 		Opname:   "OpLessOrGreater",
+		Class:    "Relational_and_Logical",
+		Opcode:   161,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -3854,6 +4445,8 @@
 	}
 	OpOrdered = &Opcode {
 		Opname:   "OpOrdered",
+		Class:    "Relational_and_Logical",
+		Opcode:   162,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -3879,6 +4472,8 @@
 	}
 	OpUnordered = &Opcode {
 		Opname:   "OpUnordered",
+		Class:    "Relational_and_Logical",
+		Opcode:   163,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -3904,6 +4499,8 @@
 	}
 	OpLogicalEqual = &Opcode {
 		Opname:   "OpLogicalEqual",
+		Class:    "Relational_and_Logical",
+		Opcode:   164,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -3929,6 +4526,8 @@
 	}
 	OpLogicalNotEqual = &Opcode {
 		Opname:   "OpLogicalNotEqual",
+		Class:    "Relational_and_Logical",
+		Opcode:   165,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -3954,6 +4553,8 @@
 	}
 	OpLogicalOr = &Opcode {
 		Opname:   "OpLogicalOr",
+		Class:    "Relational_and_Logical",
+		Opcode:   166,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -3979,6 +4580,8 @@
 	}
 	OpLogicalAnd = &Opcode {
 		Opname:   "OpLogicalAnd",
+		Class:    "Relational_and_Logical",
+		Opcode:   167,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -4004,6 +4607,8 @@
 	}
 	OpLogicalNot = &Opcode {
 		Opname:   "OpLogicalNot",
+		Class:    "Relational_and_Logical",
+		Opcode:   168,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -4024,6 +4629,8 @@
 	}
 	OpSelect = &Opcode {
 		Opname:   "OpSelect",
+		Class:    "Relational_and_Logical",
+		Opcode:   169,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -4054,6 +4661,8 @@
 	}
 	OpIEqual = &Opcode {
 		Opname:   "OpIEqual",
+		Class:    "Relational_and_Logical",
+		Opcode:   170,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -4079,6 +4688,8 @@
 	}
 	OpINotEqual = &Opcode {
 		Opname:   "OpINotEqual",
+		Class:    "Relational_and_Logical",
+		Opcode:   171,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -4104,6 +4715,8 @@
 	}
 	OpUGreaterThan = &Opcode {
 		Opname:   "OpUGreaterThan",
+		Class:    "Relational_and_Logical",
+		Opcode:   172,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -4129,6 +4742,8 @@
 	}
 	OpSGreaterThan = &Opcode {
 		Opname:   "OpSGreaterThan",
+		Class:    "Relational_and_Logical",
+		Opcode:   173,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -4154,6 +4769,8 @@
 	}
 	OpUGreaterThanEqual = &Opcode {
 		Opname:   "OpUGreaterThanEqual",
+		Class:    "Relational_and_Logical",
+		Opcode:   174,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -4179,6 +4796,8 @@
 	}
 	OpSGreaterThanEqual = &Opcode {
 		Opname:   "OpSGreaterThanEqual",
+		Class:    "Relational_and_Logical",
+		Opcode:   175,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -4204,6 +4823,8 @@
 	}
 	OpULessThan = &Opcode {
 		Opname:   "OpULessThan",
+		Class:    "Relational_and_Logical",
+		Opcode:   176,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -4229,6 +4850,8 @@
 	}
 	OpSLessThan = &Opcode {
 		Opname:   "OpSLessThan",
+		Class:    "Relational_and_Logical",
+		Opcode:   177,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -4254,6 +4877,8 @@
 	}
 	OpULessThanEqual = &Opcode {
 		Opname:   "OpULessThanEqual",
+		Class:    "Relational_and_Logical",
+		Opcode:   178,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -4279,6 +4904,8 @@
 	}
 	OpSLessThanEqual = &Opcode {
 		Opname:   "OpSLessThanEqual",
+		Class:    "Relational_and_Logical",
+		Opcode:   179,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -4304,6 +4931,8 @@
 	}
 	OpFOrdEqual = &Opcode {
 		Opname:   "OpFOrdEqual",
+		Class:    "Relational_and_Logical",
+		Opcode:   180,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -4329,6 +4958,8 @@
 	}
 	OpFUnordEqual = &Opcode {
 		Opname:   "OpFUnordEqual",
+		Class:    "Relational_and_Logical",
+		Opcode:   181,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -4354,6 +4985,8 @@
 	}
 	OpFOrdNotEqual = &Opcode {
 		Opname:   "OpFOrdNotEqual",
+		Class:    "Relational_and_Logical",
+		Opcode:   182,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -4379,6 +5012,8 @@
 	}
 	OpFUnordNotEqual = &Opcode {
 		Opname:   "OpFUnordNotEqual",
+		Class:    "Relational_and_Logical",
+		Opcode:   183,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -4404,6 +5039,8 @@
 	}
 	OpFOrdLessThan = &Opcode {
 		Opname:   "OpFOrdLessThan",
+		Class:    "Relational_and_Logical",
+		Opcode:   184,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -4429,6 +5066,8 @@
 	}
 	OpFUnordLessThan = &Opcode {
 		Opname:   "OpFUnordLessThan",
+		Class:    "Relational_and_Logical",
+		Opcode:   185,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -4454,6 +5093,8 @@
 	}
 	OpFOrdGreaterThan = &Opcode {
 		Opname:   "OpFOrdGreaterThan",
+		Class:    "Relational_and_Logical",
+		Opcode:   186,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -4479,6 +5120,8 @@
 	}
 	OpFUnordGreaterThan = &Opcode {
 		Opname:   "OpFUnordGreaterThan",
+		Class:    "Relational_and_Logical",
+		Opcode:   187,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -4504,6 +5147,8 @@
 	}
 	OpFOrdLessThanEqual = &Opcode {
 		Opname:   "OpFOrdLessThanEqual",
+		Class:    "Relational_and_Logical",
+		Opcode:   188,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -4529,6 +5174,8 @@
 	}
 	OpFUnordLessThanEqual = &Opcode {
 		Opname:   "OpFUnordLessThanEqual",
+		Class:    "Relational_and_Logical",
+		Opcode:   189,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -4554,6 +5201,8 @@
 	}
 	OpFOrdGreaterThanEqual = &Opcode {
 		Opname:   "OpFOrdGreaterThanEqual",
+		Class:    "Relational_and_Logical",
+		Opcode:   190,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -4579,6 +5228,8 @@
 	}
 	OpFUnordGreaterThanEqual = &Opcode {
 		Opname:   "OpFUnordGreaterThanEqual",
+		Class:    "Relational_and_Logical",
+		Opcode:   191,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -4604,6 +5255,8 @@
 	}
 	OpShiftRightLogical = &Opcode {
 		Opname:   "OpShiftRightLogical",
+		Class:    "Bit",
+		Opcode:   194,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -4629,6 +5282,8 @@
 	}
 	OpShiftRightArithmetic = &Opcode {
 		Opname:   "OpShiftRightArithmetic",
+		Class:    "Bit",
+		Opcode:   195,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -4654,6 +5309,8 @@
 	}
 	OpShiftLeftLogical = &Opcode {
 		Opname:   "OpShiftLeftLogical",
+		Class:    "Bit",
+		Opcode:   196,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -4679,6 +5336,8 @@
 	}
 	OpBitwiseOr = &Opcode {
 		Opname:   "OpBitwiseOr",
+		Class:    "Bit",
+		Opcode:   197,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -4704,6 +5363,8 @@
 	}
 	OpBitwiseXor = &Opcode {
 		Opname:   "OpBitwiseXor",
+		Class:    "Bit",
+		Opcode:   198,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -4729,6 +5390,8 @@
 	}
 	OpBitwiseAnd = &Opcode {
 		Opname:   "OpBitwiseAnd",
+		Class:    "Bit",
+		Opcode:   199,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -4754,6 +5417,8 @@
 	}
 	OpNot = &Opcode {
 		Opname:   "OpNot",
+		Class:    "Bit",
+		Opcode:   200,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -4774,6 +5439,8 @@
 	}
 	OpBitFieldInsert = &Opcode {
 		Opname:   "OpBitFieldInsert",
+		Class:    "Bit",
+		Opcode:   201,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -4809,6 +5476,8 @@
 	}
 	OpBitFieldSExtract = &Opcode {
 		Opname:   "OpBitFieldSExtract",
+		Class:    "Bit",
+		Opcode:   202,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -4839,6 +5508,8 @@
 	}
 	OpBitFieldUExtract = &Opcode {
 		Opname:   "OpBitFieldUExtract",
+		Class:    "Bit",
+		Opcode:   203,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -4869,6 +5540,8 @@
 	}
 	OpBitReverse = &Opcode {
 		Opname:   "OpBitReverse",
+		Class:    "Bit",
+		Opcode:   204,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -4889,6 +5562,8 @@
 	}
 	OpBitCount = &Opcode {
 		Opname:   "OpBitCount",
+		Class:    "Bit",
+		Opcode:   205,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -4909,6 +5584,8 @@
 	}
 	OpDPdx = &Opcode {
 		Opname:   "OpDPdx",
+		Class:    "Derivative",
+		Opcode:   207,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -4929,6 +5606,8 @@
 	}
 	OpDPdy = &Opcode {
 		Opname:   "OpDPdy",
+		Class:    "Derivative",
+		Opcode:   208,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -4949,6 +5628,8 @@
 	}
 	OpFwidth = &Opcode {
 		Opname:   "OpFwidth",
+		Class:    "Derivative",
+		Opcode:   209,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -4969,6 +5650,8 @@
 	}
 	OpDPdxFine = &Opcode {
 		Opname:   "OpDPdxFine",
+		Class:    "Derivative",
+		Opcode:   210,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -4989,6 +5672,8 @@
 	}
 	OpDPdyFine = &Opcode {
 		Opname:   "OpDPdyFine",
+		Class:    "Derivative",
+		Opcode:   211,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -5009,6 +5694,8 @@
 	}
 	OpFwidthFine = &Opcode {
 		Opname:   "OpFwidthFine",
+		Class:    "Derivative",
+		Opcode:   212,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -5029,6 +5716,8 @@
 	}
 	OpDPdxCoarse = &Opcode {
 		Opname:   "OpDPdxCoarse",
+		Class:    "Derivative",
+		Opcode:   213,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -5049,6 +5738,8 @@
 	}
 	OpDPdyCoarse = &Opcode {
 		Opname:   "OpDPdyCoarse",
+		Class:    "Derivative",
+		Opcode:   214,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -5069,6 +5760,8 @@
 	}
 	OpFwidthCoarse = &Opcode {
 		Opname:   "OpFwidthCoarse",
+		Class:    "Derivative",
+		Opcode:   215,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -5089,16 +5782,22 @@
 	}
 	OpEmitVertex = &Opcode {
 		Opname:   "OpEmitVertex",
+		Class:    "Primitive",
+		Opcode:   218,
 		Operands: []Operand {
 		},
 	}
 	OpEndPrimitive = &Opcode {
 		Opname:   "OpEndPrimitive",
+		Class:    "Primitive",
+		Opcode:   219,
 		Operands: []Operand {
 		},
 	}
 	OpEmitStreamVertex = &Opcode {
 		Opname:   "OpEmitStreamVertex",
+		Class:    "Primitive",
+		Opcode:   220,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -5109,6 +5808,8 @@
 	}
 	OpEndStreamPrimitive = &Opcode {
 		Opname:   "OpEndStreamPrimitive",
+		Class:    "Primitive",
+		Opcode:   221,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -5119,6 +5820,8 @@
 	}
 	OpControlBarrier = &Opcode {
 		Opname:   "OpControlBarrier",
+		Class:    "Barrier",
+		Opcode:   224,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdScope,
@@ -5139,6 +5842,8 @@
 	}
 	OpMemoryBarrier = &Opcode {
 		Opname:   "OpMemoryBarrier",
+		Class:    "Barrier",
+		Opcode:   225,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdScope,
@@ -5154,6 +5859,8 @@
 	}
 	OpAtomicLoad = &Opcode {
 		Opname:   "OpAtomicLoad",
+		Class:    "Atomic",
+		Opcode:   227,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -5184,6 +5891,8 @@
 	}
 	OpAtomicStore = &Opcode {
 		Opname:   "OpAtomicStore",
+		Class:    "Atomic",
+		Opcode:   228,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -5209,6 +5918,8 @@
 	}
 	OpAtomicExchange = &Opcode {
 		Opname:   "OpAtomicExchange",
+		Class:    "Atomic",
+		Opcode:   229,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -5244,6 +5955,8 @@
 	}
 	OpAtomicCompareExchange = &Opcode {
 		Opname:   "OpAtomicCompareExchange",
+		Class:    "Atomic",
+		Opcode:   230,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -5289,6 +6002,8 @@
 	}
 	OpAtomicCompareExchangeWeak = &Opcode {
 		Opname:   "OpAtomicCompareExchangeWeak",
+		Class:    "Atomic",
+		Opcode:   231,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -5334,6 +6049,8 @@
 	}
 	OpAtomicIIncrement = &Opcode {
 		Opname:   "OpAtomicIIncrement",
+		Class:    "Atomic",
+		Opcode:   232,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -5364,6 +6081,8 @@
 	}
 	OpAtomicIDecrement = &Opcode {
 		Opname:   "OpAtomicIDecrement",
+		Class:    "Atomic",
+		Opcode:   233,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -5394,6 +6113,8 @@
 	}
 	OpAtomicIAdd = &Opcode {
 		Opname:   "OpAtomicIAdd",
+		Class:    "Atomic",
+		Opcode:   234,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -5429,6 +6150,8 @@
 	}
 	OpAtomicISub = &Opcode {
 		Opname:   "OpAtomicISub",
+		Class:    "Atomic",
+		Opcode:   235,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -5464,6 +6187,8 @@
 	}
 	OpAtomicSMin = &Opcode {
 		Opname:   "OpAtomicSMin",
+		Class:    "Atomic",
+		Opcode:   236,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -5499,6 +6224,8 @@
 	}
 	OpAtomicUMin = &Opcode {
 		Opname:   "OpAtomicUMin",
+		Class:    "Atomic",
+		Opcode:   237,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -5534,6 +6261,8 @@
 	}
 	OpAtomicSMax = &Opcode {
 		Opname:   "OpAtomicSMax",
+		Class:    "Atomic",
+		Opcode:   238,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -5569,6 +6298,8 @@
 	}
 	OpAtomicUMax = &Opcode {
 		Opname:   "OpAtomicUMax",
+		Class:    "Atomic",
+		Opcode:   239,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -5604,6 +6335,8 @@
 	}
 	OpAtomicAnd = &Opcode {
 		Opname:   "OpAtomicAnd",
+		Class:    "Atomic",
+		Opcode:   240,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -5639,6 +6372,8 @@
 	}
 	OpAtomicOr = &Opcode {
 		Opname:   "OpAtomicOr",
+		Class:    "Atomic",
+		Opcode:   241,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -5674,6 +6409,8 @@
 	}
 	OpAtomicXor = &Opcode {
 		Opname:   "OpAtomicXor",
+		Class:    "Atomic",
+		Opcode:   242,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -5709,6 +6446,8 @@
 	}
 	OpPhi = &Opcode {
 		Opname:   "OpPhi",
+		Class:    "Control-Flow",
+		Opcode:   245,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -5729,6 +6468,8 @@
 	}
 	OpLoopMerge = &Opcode {
 		Opname:   "OpLoopMerge",
+		Class:    "Control-Flow",
+		Opcode:   246,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -5749,6 +6490,8 @@
 	}
 	OpSelectionMerge = &Opcode {
 		Opname:   "OpSelectionMerge",
+		Class:    "Control-Flow",
+		Opcode:   247,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -5764,6 +6507,8 @@
 	}
 	OpLabel = &Opcode {
 		Opname:   "OpLabel",
+		Class:    "Control-Flow",
+		Opcode:   248,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResult,
@@ -5774,6 +6519,8 @@
 	}
 	OpBranch = &Opcode {
 		Opname:   "OpBranch",
+		Class:    "Control-Flow",
+		Opcode:   249,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -5784,6 +6531,8 @@
 	}
 	OpBranchConditional = &Opcode {
 		Opname:   "OpBranchConditional",
+		Class:    "Control-Flow",
+		Opcode:   250,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -5809,6 +6558,8 @@
 	}
 	OpSwitch = &Opcode {
 		Opname:   "OpSwitch",
+		Class:    "Control-Flow",
+		Opcode:   251,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -5829,16 +6580,22 @@
 	}
 	OpKill = &Opcode {
 		Opname:   "OpKill",
+		Class:    "Control-Flow",
+		Opcode:   252,
 		Operands: []Operand {
 		},
 	}
 	OpReturn = &Opcode {
 		Opname:   "OpReturn",
+		Class:    "Control-Flow",
+		Opcode:   253,
 		Operands: []Operand {
 		},
 	}
 	OpReturnValue = &Opcode {
 		Opname:   "OpReturnValue",
+		Class:    "Control-Flow",
+		Opcode:   254,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -5849,11 +6606,15 @@
 	}
 	OpUnreachable = &Opcode {
 		Opname:   "OpUnreachable",
+		Class:    "Control-Flow",
+		Opcode:   255,
 		Operands: []Operand {
 		},
 	}
 	OpLifetimeStart = &Opcode {
 		Opname:   "OpLifetimeStart",
+		Class:    "Control-Flow",
+		Opcode:   256,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -5869,6 +6630,8 @@
 	}
 	OpLifetimeStop = &Opcode {
 		Opname:   "OpLifetimeStop",
+		Class:    "Control-Flow",
+		Opcode:   257,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -5884,6 +6647,8 @@
 	}
 	OpGroupAsyncCopy = &Opcode {
 		Opname:   "OpGroupAsyncCopy",
+		Class:    "Group",
+		Opcode:   259,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -5929,6 +6694,8 @@
 	}
 	OpGroupWaitEvents = &Opcode {
 		Opname:   "OpGroupWaitEvents",
+		Class:    "Group",
+		Opcode:   260,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdScope,
@@ -5949,6 +6716,8 @@
 	}
 	OpGroupAll = &Opcode {
 		Opname:   "OpGroupAll",
+		Class:    "Group",
+		Opcode:   261,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -5974,6 +6743,8 @@
 	}
 	OpGroupAny = &Opcode {
 		Opname:   "OpGroupAny",
+		Class:    "Group",
+		Opcode:   262,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -5999,6 +6770,8 @@
 	}
 	OpGroupBroadcast = &Opcode {
 		Opname:   "OpGroupBroadcast",
+		Class:    "Group",
+		Opcode:   263,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -6029,6 +6802,8 @@
 	}
 	OpGroupIAdd = &Opcode {
 		Opname:   "OpGroupIAdd",
+		Class:    "Group",
+		Opcode:   264,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -6059,6 +6834,8 @@
 	}
 	OpGroupFAdd = &Opcode {
 		Opname:   "OpGroupFAdd",
+		Class:    "Group",
+		Opcode:   265,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -6089,6 +6866,8 @@
 	}
 	OpGroupFMin = &Opcode {
 		Opname:   "OpGroupFMin",
+		Class:    "Group",
+		Opcode:   266,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -6119,6 +6898,8 @@
 	}
 	OpGroupUMin = &Opcode {
 		Opname:   "OpGroupUMin",
+		Class:    "Group",
+		Opcode:   267,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -6149,6 +6930,8 @@
 	}
 	OpGroupSMin = &Opcode {
 		Opname:   "OpGroupSMin",
+		Class:    "Group",
+		Opcode:   268,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -6179,6 +6962,8 @@
 	}
 	OpGroupFMax = &Opcode {
 		Opname:   "OpGroupFMax",
+		Class:    "Group",
+		Opcode:   269,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -6209,6 +6994,8 @@
 	}
 	OpGroupUMax = &Opcode {
 		Opname:   "OpGroupUMax",
+		Class:    "Group",
+		Opcode:   270,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -6239,6 +7026,8 @@
 	}
 	OpGroupSMax = &Opcode {
 		Opname:   "OpGroupSMax",
+		Class:    "Group",
+		Opcode:   271,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -6269,6 +7058,8 @@
 	}
 	OpReadPipe = &Opcode {
 		Opname:   "OpReadPipe",
+		Class:    "Pipe",
+		Opcode:   274,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -6304,6 +7095,8 @@
 	}
 	OpWritePipe = &Opcode {
 		Opname:   "OpWritePipe",
+		Class:    "Pipe",
+		Opcode:   275,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -6339,6 +7132,8 @@
 	}
 	OpReservedReadPipe = &Opcode {
 		Opname:   "OpReservedReadPipe",
+		Class:    "Pipe",
+		Opcode:   276,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -6384,6 +7179,8 @@
 	}
 	OpReservedWritePipe = &Opcode {
 		Opname:   "OpReservedWritePipe",
+		Class:    "Pipe",
+		Opcode:   277,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -6429,6 +7226,8 @@
 	}
 	OpReserveReadPipePackets = &Opcode {
 		Opname:   "OpReserveReadPipePackets",
+		Class:    "Pipe",
+		Opcode:   278,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -6464,6 +7263,8 @@
 	}
 	OpReserveWritePipePackets = &Opcode {
 		Opname:   "OpReserveWritePipePackets",
+		Class:    "Pipe",
+		Opcode:   279,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -6499,6 +7300,8 @@
 	}
 	OpCommitReadPipe = &Opcode {
 		Opname:   "OpCommitReadPipe",
+		Class:    "Pipe",
+		Opcode:   280,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -6524,6 +7327,8 @@
 	}
 	OpCommitWritePipe = &Opcode {
 		Opname:   "OpCommitWritePipe",
+		Class:    "Pipe",
+		Opcode:   281,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -6549,6 +7354,8 @@
 	}
 	OpIsValidReserveId = &Opcode {
 		Opname:   "OpIsValidReserveId",
+		Class:    "Pipe",
+		Opcode:   282,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -6569,6 +7376,8 @@
 	}
 	OpGetNumPipePackets = &Opcode {
 		Opname:   "OpGetNumPipePackets",
+		Class:    "Pipe",
+		Opcode:   283,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -6599,6 +7408,8 @@
 	}
 	OpGetMaxPipePackets = &Opcode {
 		Opname:   "OpGetMaxPipePackets",
+		Class:    "Pipe",
+		Opcode:   284,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -6629,6 +7440,8 @@
 	}
 	OpGroupReserveReadPipePackets = &Opcode {
 		Opname:   "OpGroupReserveReadPipePackets",
+		Class:    "Pipe",
+		Opcode:   285,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -6669,6 +7482,8 @@
 	}
 	OpGroupReserveWritePipePackets = &Opcode {
 		Opname:   "OpGroupReserveWritePipePackets",
+		Class:    "Pipe",
+		Opcode:   286,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -6709,6 +7524,8 @@
 	}
 	OpGroupCommitReadPipe = &Opcode {
 		Opname:   "OpGroupCommitReadPipe",
+		Class:    "Pipe",
+		Opcode:   287,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdScope,
@@ -6739,6 +7556,8 @@
 	}
 	OpGroupCommitWritePipe = &Opcode {
 		Opname:   "OpGroupCommitWritePipe",
+		Class:    "Pipe",
+		Opcode:   288,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdScope,
@@ -6769,6 +7588,8 @@
 	}
 	OpEnqueueMarker = &Opcode {
 		Opname:   "OpEnqueueMarker",
+		Class:    "Device-Side_Enqueue",
+		Opcode:   291,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -6804,6 +7625,8 @@
 	}
 	OpEnqueueKernel = &Opcode {
 		Opname:   "OpEnqueueKernel",
+		Class:    "Device-Side_Enqueue",
+		Opcode:   292,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -6874,6 +7697,8 @@
 	}
 	OpGetKernelNDrangeSubGroupCount = &Opcode {
 		Opname:   "OpGetKernelNDrangeSubGroupCount",
+		Class:    "Device-Side_Enqueue",
+		Opcode:   293,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -6914,6 +7739,8 @@
 	}
 	OpGetKernelNDrangeMaxSubGroupSize = &Opcode {
 		Opname:   "OpGetKernelNDrangeMaxSubGroupSize",
+		Class:    "Device-Side_Enqueue",
+		Opcode:   294,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -6954,6 +7781,8 @@
 	}
 	OpGetKernelWorkGroupSize = &Opcode {
 		Opname:   "OpGetKernelWorkGroupSize",
+		Class:    "Device-Side_Enqueue",
+		Opcode:   295,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -6989,6 +7818,8 @@
 	}
 	OpGetKernelPreferredWorkGroupSizeMultiple = &Opcode {
 		Opname:   "OpGetKernelPreferredWorkGroupSizeMultiple",
+		Class:    "Device-Side_Enqueue",
+		Opcode:   296,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -7024,6 +7855,8 @@
 	}
 	OpRetainEvent = &Opcode {
 		Opname:   "OpRetainEvent",
+		Class:    "Device-Side_Enqueue",
+		Opcode:   297,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -7034,6 +7867,8 @@
 	}
 	OpReleaseEvent = &Opcode {
 		Opname:   "OpReleaseEvent",
+		Class:    "Device-Side_Enqueue",
+		Opcode:   298,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -7044,6 +7879,8 @@
 	}
 	OpCreateUserEvent = &Opcode {
 		Opname:   "OpCreateUserEvent",
+		Class:    "Device-Side_Enqueue",
+		Opcode:   299,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -7059,6 +7896,8 @@
 	}
 	OpIsValidEvent = &Opcode {
 		Opname:   "OpIsValidEvent",
+		Class:    "Device-Side_Enqueue",
+		Opcode:   300,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -7079,6 +7918,8 @@
 	}
 	OpSetUserEventStatus = &Opcode {
 		Opname:   "OpSetUserEventStatus",
+		Class:    "Device-Side_Enqueue",
+		Opcode:   301,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -7094,6 +7935,8 @@
 	}
 	OpCaptureEventProfilingInfo = &Opcode {
 		Opname:   "OpCaptureEventProfilingInfo",
+		Class:    "Device-Side_Enqueue",
+		Opcode:   302,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -7114,6 +7957,8 @@
 	}
 	OpGetDefaultQueue = &Opcode {
 		Opname:   "OpGetDefaultQueue",
+		Class:    "Device-Side_Enqueue",
+		Opcode:   303,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -7129,6 +7974,8 @@
 	}
 	OpBuildNDRange = &Opcode {
 		Opname:   "OpBuildNDRange",
+		Class:    "Device-Side_Enqueue",
+		Opcode:   304,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -7159,6 +8006,8 @@
 	}
 	OpImageSparseSampleImplicitLod = &Opcode {
 		Opname:   "OpImageSparseSampleImplicitLod",
+		Class:    "Image",
+		Opcode:   305,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -7189,6 +8038,8 @@
 	}
 	OpImageSparseSampleExplicitLod = &Opcode {
 		Opname:   "OpImageSparseSampleExplicitLod",
+		Class:    "Image",
+		Opcode:   306,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -7219,6 +8070,8 @@
 	}
 	OpImageSparseSampleDrefImplicitLod = &Opcode {
 		Opname:   "OpImageSparseSampleDrefImplicitLod",
+		Class:    "Image",
+		Opcode:   307,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -7254,6 +8107,8 @@
 	}
 	OpImageSparseSampleDrefExplicitLod = &Opcode {
 		Opname:   "OpImageSparseSampleDrefExplicitLod",
+		Class:    "Image",
+		Opcode:   308,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -7289,6 +8144,8 @@
 	}
 	OpImageSparseSampleProjImplicitLod = &Opcode {
 		Opname:   "OpImageSparseSampleProjImplicitLod",
+		Class:    "Image",
+		Opcode:   309,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -7319,6 +8176,8 @@
 	}
 	OpImageSparseSampleProjExplicitLod = &Opcode {
 		Opname:   "OpImageSparseSampleProjExplicitLod",
+		Class:    "Image",
+		Opcode:   310,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -7349,6 +8208,8 @@
 	}
 	OpImageSparseSampleProjDrefImplicitLod = &Opcode {
 		Opname:   "OpImageSparseSampleProjDrefImplicitLod",
+		Class:    "Image",
+		Opcode:   311,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -7384,6 +8245,8 @@
 	}
 	OpImageSparseSampleProjDrefExplicitLod = &Opcode {
 		Opname:   "OpImageSparseSampleProjDrefExplicitLod",
+		Class:    "Image",
+		Opcode:   312,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -7419,6 +8282,8 @@
 	}
 	OpImageSparseFetch = &Opcode {
 		Opname:   "OpImageSparseFetch",
+		Class:    "Image",
+		Opcode:   313,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -7449,6 +8314,8 @@
 	}
 	OpImageSparseGather = &Opcode {
 		Opname:   "OpImageSparseGather",
+		Class:    "Image",
+		Opcode:   314,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -7484,6 +8351,8 @@
 	}
 	OpImageSparseDrefGather = &Opcode {
 		Opname:   "OpImageSparseDrefGather",
+		Class:    "Image",
+		Opcode:   315,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -7519,6 +8388,8 @@
 	}
 	OpImageSparseTexelsResident = &Opcode {
 		Opname:   "OpImageSparseTexelsResident",
+		Class:    "Image",
+		Opcode:   316,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -7539,11 +8410,15 @@
 	}
 	OpNoLine = &Opcode {
 		Opname:   "OpNoLine",
+		Class:    "Debug",
+		Opcode:   317,
 		Operands: []Operand {
 		},
 	}
 	OpAtomicFlagTestAndSet = &Opcode {
 		Opname:   "OpAtomicFlagTestAndSet",
+		Class:    "Atomic",
+		Opcode:   318,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -7574,6 +8449,8 @@
 	}
 	OpAtomicFlagClear = &Opcode {
 		Opname:   "OpAtomicFlagClear",
+		Class:    "Atomic",
+		Opcode:   319,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -7594,6 +8471,8 @@
 	}
 	OpImageSparseRead = &Opcode {
 		Opname:   "OpImageSparseRead",
+		Class:    "Image",
+		Opcode:   320,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -7624,6 +8503,8 @@
 	}
 	OpSizeOf = &Opcode {
 		Opname:   "OpSizeOf",
+		Class:    "Miscellaneous",
+		Opcode:   321,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -7644,6 +8525,8 @@
 	}
 	OpTypePipeStorage = &Opcode {
 		Opname:   "OpTypePipeStorage",
+		Class:    "Type-Declaration",
+		Opcode:   322,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResult,
@@ -7654,6 +8537,8 @@
 	}
 	OpConstantPipeStorage = &Opcode {
 		Opname:   "OpConstantPipeStorage",
+		Class:    "Pipe",
+		Opcode:   323,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -7684,6 +8569,8 @@
 	}
 	OpCreatePipeFromPipeStorage = &Opcode {
 		Opname:   "OpCreatePipeFromPipeStorage",
+		Class:    "Pipe",
+		Opcode:   324,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -7704,6 +8591,8 @@
 	}
 	OpGetKernelLocalSizeForSubgroupCount = &Opcode {
 		Opname:   "OpGetKernelLocalSizeForSubgroupCount",
+		Class:    "Device-Side_Enqueue",
+		Opcode:   325,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -7744,6 +8633,8 @@
 	}
 	OpGetKernelMaxNumSubgroups = &Opcode {
 		Opname:   "OpGetKernelMaxNumSubgroups",
+		Class:    "Device-Side_Enqueue",
+		Opcode:   326,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -7779,6 +8670,8 @@
 	}
 	OpTypeNamedBarrier = &Opcode {
 		Opname:   "OpTypeNamedBarrier",
+		Class:    "Type-Declaration",
+		Opcode:   327,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResult,
@@ -7789,6 +8682,8 @@
 	}
 	OpNamedBarrierInitialize = &Opcode {
 		Opname:   "OpNamedBarrierInitialize",
+		Class:    "Barrier",
+		Opcode:   328,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -7809,6 +8704,8 @@
 	}
 	OpMemoryNamedBarrier = &Opcode {
 		Opname:   "OpMemoryNamedBarrier",
+		Class:    "Barrier",
+		Opcode:   329,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -7829,6 +8726,8 @@
 	}
 	OpModuleProcessed = &Opcode {
 		Opname:   "OpModuleProcessed",
+		Class:    "Debug",
+		Opcode:   330,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindLiteralString,
@@ -7839,6 +8738,8 @@
 	}
 	OpExecutionModeId = &Opcode {
 		Opname:   "OpExecutionModeId",
+		Class:    "Mode-Setting",
+		Opcode:   331,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -7854,6 +8755,8 @@
 	}
 	OpDecorateId = &Opcode {
 		Opname:   "OpDecorateId",
+		Class:    "Annotation",
+		Opcode:   332,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -7869,6 +8772,8 @@
 	}
 	OpGroupNonUniformElect = &Opcode {
 		Opname:   "OpGroupNonUniformElect",
+		Class:    "Non-Uniform",
+		Opcode:   333,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -7889,6 +8794,8 @@
 	}
 	OpGroupNonUniformAll = &Opcode {
 		Opname:   "OpGroupNonUniformAll",
+		Class:    "Non-Uniform",
+		Opcode:   334,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -7914,6 +8821,8 @@
 	}
 	OpGroupNonUniformAny = &Opcode {
 		Opname:   "OpGroupNonUniformAny",
+		Class:    "Non-Uniform",
+		Opcode:   335,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -7939,6 +8848,8 @@
 	}
 	OpGroupNonUniformAllEqual = &Opcode {
 		Opname:   "OpGroupNonUniformAllEqual",
+		Class:    "Non-Uniform",
+		Opcode:   336,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -7964,6 +8875,8 @@
 	}
 	OpGroupNonUniformBroadcast = &Opcode {
 		Opname:   "OpGroupNonUniformBroadcast",
+		Class:    "Non-Uniform",
+		Opcode:   337,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -7994,6 +8907,8 @@
 	}
 	OpGroupNonUniformBroadcastFirst = &Opcode {
 		Opname:   "OpGroupNonUniformBroadcastFirst",
+		Class:    "Non-Uniform",
+		Opcode:   338,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -8019,6 +8934,8 @@
 	}
 	OpGroupNonUniformBallot = &Opcode {
 		Opname:   "OpGroupNonUniformBallot",
+		Class:    "Non-Uniform",
+		Opcode:   339,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -8044,6 +8961,8 @@
 	}
 	OpGroupNonUniformInverseBallot = &Opcode {
 		Opname:   "OpGroupNonUniformInverseBallot",
+		Class:    "Non-Uniform",
+		Opcode:   340,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -8069,6 +8988,8 @@
 	}
 	OpGroupNonUniformBallotBitExtract = &Opcode {
 		Opname:   "OpGroupNonUniformBallotBitExtract",
+		Class:    "Non-Uniform",
+		Opcode:   341,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -8099,6 +9020,8 @@
 	}
 	OpGroupNonUniformBallotBitCount = &Opcode {
 		Opname:   "OpGroupNonUniformBallotBitCount",
+		Class:    "Non-Uniform",
+		Opcode:   342,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -8129,6 +9052,8 @@
 	}
 	OpGroupNonUniformBallotFindLSB = &Opcode {
 		Opname:   "OpGroupNonUniformBallotFindLSB",
+		Class:    "Non-Uniform",
+		Opcode:   343,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -8154,6 +9079,8 @@
 	}
 	OpGroupNonUniformBallotFindMSB = &Opcode {
 		Opname:   "OpGroupNonUniformBallotFindMSB",
+		Class:    "Non-Uniform",
+		Opcode:   344,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -8179,6 +9106,8 @@
 	}
 	OpGroupNonUniformShuffle = &Opcode {
 		Opname:   "OpGroupNonUniformShuffle",
+		Class:    "Non-Uniform",
+		Opcode:   345,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -8209,6 +9138,8 @@
 	}
 	OpGroupNonUniformShuffleXor = &Opcode {
 		Opname:   "OpGroupNonUniformShuffleXor",
+		Class:    "Non-Uniform",
+		Opcode:   346,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -8239,6 +9170,8 @@
 	}
 	OpGroupNonUniformShuffleUp = &Opcode {
 		Opname:   "OpGroupNonUniformShuffleUp",
+		Class:    "Non-Uniform",
+		Opcode:   347,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -8269,6 +9202,8 @@
 	}
 	OpGroupNonUniformShuffleDown = &Opcode {
 		Opname:   "OpGroupNonUniformShuffleDown",
+		Class:    "Non-Uniform",
+		Opcode:   348,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -8299,6 +9234,8 @@
 	}
 	OpGroupNonUniformIAdd = &Opcode {
 		Opname:   "OpGroupNonUniformIAdd",
+		Class:    "Non-Uniform",
+		Opcode:   349,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -8334,6 +9271,8 @@
 	}
 	OpGroupNonUniformFAdd = &Opcode {
 		Opname:   "OpGroupNonUniformFAdd",
+		Class:    "Non-Uniform",
+		Opcode:   350,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -8369,6 +9308,8 @@
 	}
 	OpGroupNonUniformIMul = &Opcode {
 		Opname:   "OpGroupNonUniformIMul",
+		Class:    "Non-Uniform",
+		Opcode:   351,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -8404,6 +9345,8 @@
 	}
 	OpGroupNonUniformFMul = &Opcode {
 		Opname:   "OpGroupNonUniformFMul",
+		Class:    "Non-Uniform",
+		Opcode:   352,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -8439,6 +9382,8 @@
 	}
 	OpGroupNonUniformSMin = &Opcode {
 		Opname:   "OpGroupNonUniformSMin",
+		Class:    "Non-Uniform",
+		Opcode:   353,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -8474,6 +9419,8 @@
 	}
 	OpGroupNonUniformUMin = &Opcode {
 		Opname:   "OpGroupNonUniformUMin",
+		Class:    "Non-Uniform",
+		Opcode:   354,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -8509,6 +9456,8 @@
 	}
 	OpGroupNonUniformFMin = &Opcode {
 		Opname:   "OpGroupNonUniformFMin",
+		Class:    "Non-Uniform",
+		Opcode:   355,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -8544,6 +9493,8 @@
 	}
 	OpGroupNonUniformSMax = &Opcode {
 		Opname:   "OpGroupNonUniformSMax",
+		Class:    "Non-Uniform",
+		Opcode:   356,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -8579,6 +9530,8 @@
 	}
 	OpGroupNonUniformUMax = &Opcode {
 		Opname:   "OpGroupNonUniformUMax",
+		Class:    "Non-Uniform",
+		Opcode:   357,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -8614,6 +9567,8 @@
 	}
 	OpGroupNonUniformFMax = &Opcode {
 		Opname:   "OpGroupNonUniformFMax",
+		Class:    "Non-Uniform",
+		Opcode:   358,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -8649,6 +9604,8 @@
 	}
 	OpGroupNonUniformBitwiseAnd = &Opcode {
 		Opname:   "OpGroupNonUniformBitwiseAnd",
+		Class:    "Non-Uniform",
+		Opcode:   359,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -8684,6 +9641,8 @@
 	}
 	OpGroupNonUniformBitwiseOr = &Opcode {
 		Opname:   "OpGroupNonUniformBitwiseOr",
+		Class:    "Non-Uniform",
+		Opcode:   360,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -8719,6 +9678,8 @@
 	}
 	OpGroupNonUniformBitwiseXor = &Opcode {
 		Opname:   "OpGroupNonUniformBitwiseXor",
+		Class:    "Non-Uniform",
+		Opcode:   361,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -8754,6 +9715,8 @@
 	}
 	OpGroupNonUniformLogicalAnd = &Opcode {
 		Opname:   "OpGroupNonUniformLogicalAnd",
+		Class:    "Non-Uniform",
+		Opcode:   362,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -8789,6 +9752,8 @@
 	}
 	OpGroupNonUniformLogicalOr = &Opcode {
 		Opname:   "OpGroupNonUniformLogicalOr",
+		Class:    "Non-Uniform",
+		Opcode:   363,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -8824,6 +9789,8 @@
 	}
 	OpGroupNonUniformLogicalXor = &Opcode {
 		Opname:   "OpGroupNonUniformLogicalXor",
+		Class:    "Non-Uniform",
+		Opcode:   364,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -8859,6 +9826,8 @@
 	}
 	OpGroupNonUniformQuadBroadcast = &Opcode {
 		Opname:   "OpGroupNonUniformQuadBroadcast",
+		Class:    "Non-Uniform",
+		Opcode:   365,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -8889,6 +9858,8 @@
 	}
 	OpGroupNonUniformQuadSwap = &Opcode {
 		Opname:   "OpGroupNonUniformQuadSwap",
+		Class:    "Non-Uniform",
+		Opcode:   366,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -8919,6 +9890,8 @@
 	}
 	OpCopyLogical = &Opcode {
 		Opname:   "OpCopyLogical",
+		Class:    "Composite",
+		Opcode:   400,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -8939,6 +9912,8 @@
 	}
 	OpPtrEqual = &Opcode {
 		Opname:   "OpPtrEqual",
+		Class:    "Memory",
+		Opcode:   401,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -8964,6 +9939,8 @@
 	}
 	OpPtrNotEqual = &Opcode {
 		Opname:   "OpPtrNotEqual",
+		Class:    "Memory",
+		Opcode:   402,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -8989,6 +9966,8 @@
 	}
 	OpPtrDiff = &Opcode {
 		Opname:   "OpPtrDiff",
+		Class:    "Memory",
+		Opcode:   403,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -9014,6 +9993,8 @@
 	}
 	OpSubgroupBallotKHR = &Opcode {
 		Opname:   "OpSubgroupBallotKHR",
+		Class:    "Group",
+		Opcode:   4421,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -9034,6 +10015,8 @@
 	}
 	OpSubgroupFirstInvocationKHR = &Opcode {
 		Opname:   "OpSubgroupFirstInvocationKHR",
+		Class:    "Group",
+		Opcode:   4422,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -9054,6 +10037,8 @@
 	}
 	OpSubgroupAllKHR = &Opcode {
 		Opname:   "OpSubgroupAllKHR",
+		Class:    "Group",
+		Opcode:   4428,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -9074,6 +10059,8 @@
 	}
 	OpSubgroupAnyKHR = &Opcode {
 		Opname:   "OpSubgroupAnyKHR",
+		Class:    "Group",
+		Opcode:   4429,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -9094,6 +10081,8 @@
 	}
 	OpSubgroupAllEqualKHR = &Opcode {
 		Opname:   "OpSubgroupAllEqualKHR",
+		Class:    "Group",
+		Opcode:   4430,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -9114,6 +10103,8 @@
 	}
 	OpSubgroupReadInvocationKHR = &Opcode {
 		Opname:   "OpSubgroupReadInvocationKHR",
+		Class:    "Group",
+		Opcode:   4432,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -9139,6 +10130,8 @@
 	}
 	OpGroupIAddNonUniformAMD = &Opcode {
 		Opname:   "OpGroupIAddNonUniformAMD",
+		Class:    "Group",
+		Opcode:   5000,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -9169,6 +10162,8 @@
 	}
 	OpGroupFAddNonUniformAMD = &Opcode {
 		Opname:   "OpGroupFAddNonUniformAMD",
+		Class:    "Group",
+		Opcode:   5001,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -9199,6 +10194,8 @@
 	}
 	OpGroupFMinNonUniformAMD = &Opcode {
 		Opname:   "OpGroupFMinNonUniformAMD",
+		Class:    "Group",
+		Opcode:   5002,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -9229,6 +10226,8 @@
 	}
 	OpGroupUMinNonUniformAMD = &Opcode {
 		Opname:   "OpGroupUMinNonUniformAMD",
+		Class:    "Group",
+		Opcode:   5003,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -9259,6 +10258,8 @@
 	}
 	OpGroupSMinNonUniformAMD = &Opcode {
 		Opname:   "OpGroupSMinNonUniformAMD",
+		Class:    "Group",
+		Opcode:   5004,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -9289,6 +10290,8 @@
 	}
 	OpGroupFMaxNonUniformAMD = &Opcode {
 		Opname:   "OpGroupFMaxNonUniformAMD",
+		Class:    "Group",
+		Opcode:   5005,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -9319,6 +10322,8 @@
 	}
 	OpGroupUMaxNonUniformAMD = &Opcode {
 		Opname:   "OpGroupUMaxNonUniformAMD",
+		Class:    "Group",
+		Opcode:   5006,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -9349,6 +10354,8 @@
 	}
 	OpGroupSMaxNonUniformAMD = &Opcode {
 		Opname:   "OpGroupSMaxNonUniformAMD",
+		Class:    "Group",
+		Opcode:   5007,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -9379,6 +10386,8 @@
 	}
 	OpFragmentMaskFetchAMD = &Opcode {
 		Opname:   "OpFragmentMaskFetchAMD",
+		Class:    "Reserved",
+		Opcode:   5011,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -9404,6 +10413,8 @@
 	}
 	OpFragmentFetchAMD = &Opcode {
 		Opname:   "OpFragmentFetchAMD",
+		Class:    "Reserved",
+		Opcode:   5012,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -9434,6 +10445,8 @@
 	}
 	OpReadClockKHR = &Opcode {
 		Opname:   "OpReadClockKHR",
+		Class:    "Reserved",
+		Opcode:   5056,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -9454,6 +10467,8 @@
 	}
 	OpImageSampleFootprintNV = &Opcode {
 		Opname:   "OpImageSampleFootprintNV",
+		Class:    "Image",
+		Opcode:   5283,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -9494,6 +10509,8 @@
 	}
 	OpGroupNonUniformPartitionNV = &Opcode {
 		Opname:   "OpGroupNonUniformPartitionNV",
+		Class:    "Non-Uniform",
+		Opcode:   5296,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -9514,6 +10531,8 @@
 	}
 	OpWritePackedPrimitiveIndices4x8NV = &Opcode {
 		Opname:   "OpWritePackedPrimitiveIndices4x8NV",
+		Class:    "Reserved",
+		Opcode:   5299,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -9529,6 +10548,8 @@
 	}
 	OpReportIntersectionNV = &Opcode {
 		Opname:   "OpReportIntersectionNV",
+		Class:    "Reserved",
+		Opcode:   5334,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -9554,16 +10575,22 @@
 	}
 	OpIgnoreIntersectionNV = &Opcode {
 		Opname:   "OpIgnoreIntersectionNV",
+		Class:    "Reserved",
+		Opcode:   5335,
 		Operands: []Operand {
 		},
 	}
 	OpTerminateRayNV = &Opcode {
 		Opname:   "OpTerminateRayNV",
+		Class:    "Reserved",
+		Opcode:   5336,
 		Operands: []Operand {
 		},
 	}
 	OpTraceNV = &Opcode {
 		Opname:   "OpTraceNV",
+		Class:    "Reserved",
+		Opcode:   5337,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -9624,6 +10651,8 @@
 	}
 	OpTypeAccelerationStructureNV = &Opcode {
 		Opname:   "OpTypeAccelerationStructureNV",
+		Class:    "Reserved",
+		Opcode:   5341,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResult,
@@ -9634,6 +10663,8 @@
 	}
 	OpExecuteCallableNV = &Opcode {
 		Opname:   "OpExecuteCallableNV",
+		Class:    "Reserved",
+		Opcode:   5344,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -9649,6 +10680,8 @@
 	}
 	OpTypeCooperativeMatrixNV = &Opcode {
 		Opname:   "OpTypeCooperativeMatrixNV",
+		Class:    "Reserved",
+		Opcode:   5358,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResult,
@@ -9679,6 +10712,8 @@
 	}
 	OpCooperativeMatrixLoadNV = &Opcode {
 		Opname:   "OpCooperativeMatrixLoadNV",
+		Class:    "Reserved",
+		Opcode:   5359,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -9714,6 +10749,8 @@
 	}
 	OpCooperativeMatrixStoreNV = &Opcode {
 		Opname:   "OpCooperativeMatrixStoreNV",
+		Class:    "Reserved",
+		Opcode:   5360,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -9744,6 +10781,8 @@
 	}
 	OpCooperativeMatrixMulAddNV = &Opcode {
 		Opname:   "OpCooperativeMatrixMulAddNV",
+		Class:    "Reserved",
+		Opcode:   5361,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -9774,6 +10813,8 @@
 	}
 	OpCooperativeMatrixLengthNV = &Opcode {
 		Opname:   "OpCooperativeMatrixLengthNV",
+		Class:    "Reserved",
+		Opcode:   5362,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -9794,21 +10835,29 @@
 	}
 	OpBeginInvocationInterlockEXT = &Opcode {
 		Opname:   "OpBeginInvocationInterlockEXT",
+		Class:    "Reserved",
+		Opcode:   5364,
 		Operands: []Operand {
 		},
 	}
 	OpEndInvocationInterlockEXT = &Opcode {
 		Opname:   "OpEndInvocationInterlockEXT",
+		Class:    "Reserved",
+		Opcode:   5365,
 		Operands: []Operand {
 		},
 	}
 	OpDemoteToHelperInvocationEXT = &Opcode {
 		Opname:   "OpDemoteToHelperInvocationEXT",
+		Class:    "Reserved",
+		Opcode:   5380,
 		Operands: []Operand {
 		},
 	}
 	OpIsHelperInvocationEXT = &Opcode {
 		Opname:   "OpIsHelperInvocationEXT",
+		Class:    "Reserved",
+		Opcode:   5381,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -9824,6 +10873,8 @@
 	}
 	OpSubgroupShuffleINTEL = &Opcode {
 		Opname:   "OpSubgroupShuffleINTEL",
+		Class:    "Group",
+		Opcode:   5571,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -9849,6 +10900,8 @@
 	}
 	OpSubgroupShuffleDownINTEL = &Opcode {
 		Opname:   "OpSubgroupShuffleDownINTEL",
+		Class:    "Group",
+		Opcode:   5572,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -9879,6 +10932,8 @@
 	}
 	OpSubgroupShuffleUpINTEL = &Opcode {
 		Opname:   "OpSubgroupShuffleUpINTEL",
+		Class:    "Group",
+		Opcode:   5573,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -9909,6 +10964,8 @@
 	}
 	OpSubgroupShuffleXorINTEL = &Opcode {
 		Opname:   "OpSubgroupShuffleXorINTEL",
+		Class:    "Group",
+		Opcode:   5574,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -9934,6 +10991,8 @@
 	}
 	OpSubgroupBlockReadINTEL = &Opcode {
 		Opname:   "OpSubgroupBlockReadINTEL",
+		Class:    "Group",
+		Opcode:   5575,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -9954,6 +11013,8 @@
 	}
 	OpSubgroupBlockWriteINTEL = &Opcode {
 		Opname:   "OpSubgroupBlockWriteINTEL",
+		Class:    "Group",
+		Opcode:   5576,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -9969,6 +11030,8 @@
 	}
 	OpSubgroupImageBlockReadINTEL = &Opcode {
 		Opname:   "OpSubgroupImageBlockReadINTEL",
+		Class:    "Group",
+		Opcode:   5577,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -9994,6 +11057,8 @@
 	}
 	OpSubgroupImageBlockWriteINTEL = &Opcode {
 		Opname:   "OpSubgroupImageBlockWriteINTEL",
+		Class:    "Group",
+		Opcode:   5578,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -10014,6 +11079,8 @@
 	}
 	OpSubgroupImageMediaBlockReadINTEL = &Opcode {
 		Opname:   "OpSubgroupImageMediaBlockReadINTEL",
+		Class:    "Group",
+		Opcode:   5580,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -10049,6 +11116,8 @@
 	}
 	OpSubgroupImageMediaBlockWriteINTEL = &Opcode {
 		Opname:   "OpSubgroupImageMediaBlockWriteINTEL",
+		Class:    "Group",
+		Opcode:   5581,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -10079,6 +11148,8 @@
 	}
 	OpUCountLeadingZerosINTEL = &Opcode {
 		Opname:   "OpUCountLeadingZerosINTEL",
+		Class:    "Reserved",
+		Opcode:   5585,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -10099,6 +11170,8 @@
 	}
 	OpUCountTrailingZerosINTEL = &Opcode {
 		Opname:   "OpUCountTrailingZerosINTEL",
+		Class:    "Reserved",
+		Opcode:   5586,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -10119,6 +11192,8 @@
 	}
 	OpAbsISubINTEL = &Opcode {
 		Opname:   "OpAbsISubINTEL",
+		Class:    "Reserved",
+		Opcode:   5587,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -10144,6 +11219,8 @@
 	}
 	OpAbsUSubINTEL = &Opcode {
 		Opname:   "OpAbsUSubINTEL",
+		Class:    "Reserved",
+		Opcode:   5588,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -10169,6 +11246,8 @@
 	}
 	OpIAddSatINTEL = &Opcode {
 		Opname:   "OpIAddSatINTEL",
+		Class:    "Reserved",
+		Opcode:   5589,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -10194,6 +11273,8 @@
 	}
 	OpUAddSatINTEL = &Opcode {
 		Opname:   "OpUAddSatINTEL",
+		Class:    "Reserved",
+		Opcode:   5590,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -10219,6 +11300,8 @@
 	}
 	OpIAverageINTEL = &Opcode {
 		Opname:   "OpIAverageINTEL",
+		Class:    "Reserved",
+		Opcode:   5591,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -10244,6 +11327,8 @@
 	}
 	OpUAverageINTEL = &Opcode {
 		Opname:   "OpUAverageINTEL",
+		Class:    "Reserved",
+		Opcode:   5592,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -10269,6 +11354,8 @@
 	}
 	OpIAverageRoundedINTEL = &Opcode {
 		Opname:   "OpIAverageRoundedINTEL",
+		Class:    "Reserved",
+		Opcode:   5593,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -10294,6 +11381,8 @@
 	}
 	OpUAverageRoundedINTEL = &Opcode {
 		Opname:   "OpUAverageRoundedINTEL",
+		Class:    "Reserved",
+		Opcode:   5594,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -10319,6 +11408,8 @@
 	}
 	OpISubSatINTEL = &Opcode {
 		Opname:   "OpISubSatINTEL",
+		Class:    "Reserved",
+		Opcode:   5595,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -10344,6 +11435,8 @@
 	}
 	OpUSubSatINTEL = &Opcode {
 		Opname:   "OpUSubSatINTEL",
+		Class:    "Reserved",
+		Opcode:   5596,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -10369,6 +11462,8 @@
 	}
 	OpIMul32x16INTEL = &Opcode {
 		Opname:   "OpIMul32x16INTEL",
+		Class:    "Reserved",
+		Opcode:   5597,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -10394,6 +11489,8 @@
 	}
 	OpUMul32x16INTEL = &Opcode {
 		Opname:   "OpUMul32x16INTEL",
+		Class:    "Reserved",
+		Opcode:   5598,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -10419,6 +11516,8 @@
 	}
 	OpDecorateString = &Opcode {
 		Opname:   "OpDecorateString",
+		Class:    "Annotation",
+		Opcode:   5632,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -10434,6 +11533,8 @@
 	}
 	OpDecorateStringGOOGLE = &Opcode {
 		Opname:   "OpDecorateStringGOOGLE",
+		Class:    "Annotation",
+		Opcode:   5632,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -10449,6 +11550,8 @@
 	}
 	OpMemberDecorateString = &Opcode {
 		Opname:   "OpMemberDecorateString",
+		Class:    "Annotation",
+		Opcode:   5633,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -10469,6 +11572,8 @@
 	}
 	OpMemberDecorateStringGOOGLE = &Opcode {
 		Opname:   "OpMemberDecorateStringGOOGLE",
+		Class:    "Annotation",
+		Opcode:   5633,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdRef,
@@ -10489,6 +11594,8 @@
 	}
 	OpVmeImageINTEL = &Opcode {
 		Opname:   "OpVmeImageINTEL",
+		Class:    "@exclude",
+		Opcode:   5699,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -10514,6 +11621,8 @@
 	}
 	OpTypeVmeImageINTEL = &Opcode {
 		Opname:   "OpTypeVmeImageINTEL",
+		Class:    "@exclude",
+		Opcode:   5700,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResult,
@@ -10529,6 +11638,8 @@
 	}
 	OpTypeAvcImePayloadINTEL = &Opcode {
 		Opname:   "OpTypeAvcImePayloadINTEL",
+		Class:    "@exclude",
+		Opcode:   5701,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResult,
@@ -10539,6 +11650,8 @@
 	}
 	OpTypeAvcRefPayloadINTEL = &Opcode {
 		Opname:   "OpTypeAvcRefPayloadINTEL",
+		Class:    "@exclude",
+		Opcode:   5702,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResult,
@@ -10549,6 +11662,8 @@
 	}
 	OpTypeAvcSicPayloadINTEL = &Opcode {
 		Opname:   "OpTypeAvcSicPayloadINTEL",
+		Class:    "@exclude",
+		Opcode:   5703,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResult,
@@ -10559,6 +11674,8 @@
 	}
 	OpTypeAvcMcePayloadINTEL = &Opcode {
 		Opname:   "OpTypeAvcMcePayloadINTEL",
+		Class:    "@exclude",
+		Opcode:   5704,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResult,
@@ -10569,6 +11686,8 @@
 	}
 	OpTypeAvcMceResultINTEL = &Opcode {
 		Opname:   "OpTypeAvcMceResultINTEL",
+		Class:    "@exclude",
+		Opcode:   5705,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResult,
@@ -10579,6 +11698,8 @@
 	}
 	OpTypeAvcImeResultINTEL = &Opcode {
 		Opname:   "OpTypeAvcImeResultINTEL",
+		Class:    "@exclude",
+		Opcode:   5706,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResult,
@@ -10589,6 +11710,8 @@
 	}
 	OpTypeAvcImeResultSingleReferenceStreamoutINTEL = &Opcode {
 		Opname:   "OpTypeAvcImeResultSingleReferenceStreamoutINTEL",
+		Class:    "@exclude",
+		Opcode:   5707,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResult,
@@ -10599,6 +11722,8 @@
 	}
 	OpTypeAvcImeResultDualReferenceStreamoutINTEL = &Opcode {
 		Opname:   "OpTypeAvcImeResultDualReferenceStreamoutINTEL",
+		Class:    "@exclude",
+		Opcode:   5708,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResult,
@@ -10609,6 +11734,8 @@
 	}
 	OpTypeAvcImeSingleReferenceStreaminINTEL = &Opcode {
 		Opname:   "OpTypeAvcImeSingleReferenceStreaminINTEL",
+		Class:    "@exclude",
+		Opcode:   5709,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResult,
@@ -10619,6 +11746,8 @@
 	}
 	OpTypeAvcImeDualReferenceStreaminINTEL = &Opcode {
 		Opname:   "OpTypeAvcImeDualReferenceStreaminINTEL",
+		Class:    "@exclude",
+		Opcode:   5710,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResult,
@@ -10629,6 +11758,8 @@
 	}
 	OpTypeAvcRefResultINTEL = &Opcode {
 		Opname:   "OpTypeAvcRefResultINTEL",
+		Class:    "@exclude",
+		Opcode:   5711,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResult,
@@ -10639,6 +11770,8 @@
 	}
 	OpTypeAvcSicResultINTEL = &Opcode {
 		Opname:   "OpTypeAvcSicResultINTEL",
+		Class:    "@exclude",
+		Opcode:   5712,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResult,
@@ -10649,6 +11782,8 @@
 	}
 	OpSubgroupAvcMceGetDefaultInterBaseMultiReferencePenaltyINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcMceGetDefaultInterBaseMultiReferencePenaltyINTEL",
+		Class:    "@exclude",
+		Opcode:   5713,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -10674,6 +11809,8 @@
 	}
 	OpSubgroupAvcMceSetInterBaseMultiReferencePenaltyINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcMceSetInterBaseMultiReferencePenaltyINTEL",
+		Class:    "@exclude",
+		Opcode:   5714,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -10699,6 +11836,8 @@
 	}
 	OpSubgroupAvcMceGetDefaultInterShapePenaltyINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcMceGetDefaultInterShapePenaltyINTEL",
+		Class:    "@exclude",
+		Opcode:   5715,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -10724,6 +11863,8 @@
 	}
 	OpSubgroupAvcMceSetInterShapePenaltyINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcMceSetInterShapePenaltyINTEL",
+		Class:    "@exclude",
+		Opcode:   5716,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -10749,6 +11890,8 @@
 	}
 	OpSubgroupAvcMceGetDefaultInterDirectionPenaltyINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcMceGetDefaultInterDirectionPenaltyINTEL",
+		Class:    "@exclude",
+		Opcode:   5717,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -10774,6 +11917,8 @@
 	}
 	OpSubgroupAvcMceSetInterDirectionPenaltyINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcMceSetInterDirectionPenaltyINTEL",
+		Class:    "@exclude",
+		Opcode:   5718,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -10799,6 +11944,8 @@
 	}
 	OpSubgroupAvcMceGetDefaultIntraLumaShapePenaltyINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcMceGetDefaultIntraLumaShapePenaltyINTEL",
+		Class:    "@exclude",
+		Opcode:   5719,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -10824,6 +11971,8 @@
 	}
 	OpSubgroupAvcMceGetDefaultInterMotionVectorCostTableINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcMceGetDefaultInterMotionVectorCostTableINTEL",
+		Class:    "@exclude",
+		Opcode:   5720,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -10849,6 +11998,8 @@
 	}
 	OpSubgroupAvcMceGetDefaultHighPenaltyCostTableINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcMceGetDefaultHighPenaltyCostTableINTEL",
+		Class:    "@exclude",
+		Opcode:   5721,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -10864,6 +12015,8 @@
 	}
 	OpSubgroupAvcMceGetDefaultMediumPenaltyCostTableINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcMceGetDefaultMediumPenaltyCostTableINTEL",
+		Class:    "@exclude",
+		Opcode:   5722,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -10879,6 +12032,8 @@
 	}
 	OpSubgroupAvcMceGetDefaultLowPenaltyCostTableINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcMceGetDefaultLowPenaltyCostTableINTEL",
+		Class:    "@exclude",
+		Opcode:   5723,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -10894,6 +12049,8 @@
 	}
 	OpSubgroupAvcMceSetMotionVectorCostFunctionINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcMceSetMotionVectorCostFunctionINTEL",
+		Class:    "@exclude",
+		Opcode:   5724,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -10929,6 +12086,8 @@
 	}
 	OpSubgroupAvcMceGetDefaultIntraLumaModePenaltyINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcMceGetDefaultIntraLumaModePenaltyINTEL",
+		Class:    "@exclude",
+		Opcode:   5725,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -10954,6 +12113,8 @@
 	}
 	OpSubgroupAvcMceGetDefaultNonDcLumaIntraPenaltyINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcMceGetDefaultNonDcLumaIntraPenaltyINTEL",
+		Class:    "@exclude",
+		Opcode:   5726,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -10969,6 +12130,8 @@
 	}
 	OpSubgroupAvcMceGetDefaultIntraChromaModeBasePenaltyINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcMceGetDefaultIntraChromaModeBasePenaltyINTEL",
+		Class:    "@exclude",
+		Opcode:   5727,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -10984,6 +12147,8 @@
 	}
 	OpSubgroupAvcMceSetAcOnlyHaarINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcMceSetAcOnlyHaarINTEL",
+		Class:    "@exclude",
+		Opcode:   5728,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -11004,6 +12169,8 @@
 	}
 	OpSubgroupAvcMceSetSourceInterlacedFieldPolarityINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcMceSetSourceInterlacedFieldPolarityINTEL",
+		Class:    "@exclude",
+		Opcode:   5729,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -11029,6 +12196,8 @@
 	}
 	OpSubgroupAvcMceSetSingleReferenceInterlacedFieldPolarityINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcMceSetSingleReferenceInterlacedFieldPolarityINTEL",
+		Class:    "@exclude",
+		Opcode:   5730,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -11054,6 +12223,8 @@
 	}
 	OpSubgroupAvcMceSetDualReferenceInterlacedFieldPolaritiesINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcMceSetDualReferenceInterlacedFieldPolaritiesINTEL",
+		Class:    "@exclude",
+		Opcode:   5731,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -11084,6 +12255,8 @@
 	}
 	OpSubgroupAvcMceConvertToImePayloadINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcMceConvertToImePayloadINTEL",
+		Class:    "@exclude",
+		Opcode:   5732,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -11104,6 +12277,8 @@
 	}
 	OpSubgroupAvcMceConvertToImeResultINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcMceConvertToImeResultINTEL",
+		Class:    "@exclude",
+		Opcode:   5733,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -11124,6 +12299,8 @@
 	}
 	OpSubgroupAvcMceConvertToRefPayloadINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcMceConvertToRefPayloadINTEL",
+		Class:    "@exclude",
+		Opcode:   5734,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -11144,6 +12321,8 @@
 	}
 	OpSubgroupAvcMceConvertToRefResultINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcMceConvertToRefResultINTEL",
+		Class:    "@exclude",
+		Opcode:   5735,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -11164,6 +12343,8 @@
 	}
 	OpSubgroupAvcMceConvertToSicPayloadINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcMceConvertToSicPayloadINTEL",
+		Class:    "@exclude",
+		Opcode:   5736,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -11184,6 +12365,8 @@
 	}
 	OpSubgroupAvcMceConvertToSicResultINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcMceConvertToSicResultINTEL",
+		Class:    "@exclude",
+		Opcode:   5737,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -11204,6 +12387,8 @@
 	}
 	OpSubgroupAvcMceGetMotionVectorsINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcMceGetMotionVectorsINTEL",
+		Class:    "@exclude",
+		Opcode:   5738,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -11224,6 +12409,8 @@
 	}
 	OpSubgroupAvcMceGetInterDistortionsINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcMceGetInterDistortionsINTEL",
+		Class:    "@exclude",
+		Opcode:   5739,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -11244,6 +12431,8 @@
 	}
 	OpSubgroupAvcMceGetBestInterDistortionsINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcMceGetBestInterDistortionsINTEL",
+		Class:    "@exclude",
+		Opcode:   5740,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -11264,6 +12453,8 @@
 	}
 	OpSubgroupAvcMceGetInterMajorShapeINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcMceGetInterMajorShapeINTEL",
+		Class:    "@exclude",
+		Opcode:   5741,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -11284,6 +12475,8 @@
 	}
 	OpSubgroupAvcMceGetInterMinorShapeINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcMceGetInterMinorShapeINTEL",
+		Class:    "@exclude",
+		Opcode:   5742,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -11304,6 +12497,8 @@
 	}
 	OpSubgroupAvcMceGetInterDirectionsINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcMceGetInterDirectionsINTEL",
+		Class:    "@exclude",
+		Opcode:   5743,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -11324,6 +12519,8 @@
 	}
 	OpSubgroupAvcMceGetInterMotionVectorCountINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcMceGetInterMotionVectorCountINTEL",
+		Class:    "@exclude",
+		Opcode:   5744,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -11344,6 +12541,8 @@
 	}
 	OpSubgroupAvcMceGetInterReferenceIdsINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcMceGetInterReferenceIdsINTEL",
+		Class:    "@exclude",
+		Opcode:   5745,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -11364,6 +12563,8 @@
 	}
 	OpSubgroupAvcMceGetInterReferenceInterlacedFieldPolaritiesINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcMceGetInterReferenceInterlacedFieldPolaritiesINTEL",
+		Class:    "@exclude",
+		Opcode:   5746,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -11394,6 +12595,8 @@
 	}
 	OpSubgroupAvcImeInitializeINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcImeInitializeINTEL",
+		Class:    "@exclude",
+		Opcode:   5747,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -11424,6 +12627,8 @@
 	}
 	OpSubgroupAvcImeSetSingleReferenceINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcImeSetSingleReferenceINTEL",
+		Class:    "@exclude",
+		Opcode:   5748,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -11454,6 +12659,8 @@
 	}
 	OpSubgroupAvcImeSetDualReferenceINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcImeSetDualReferenceINTEL",
+		Class:    "@exclude",
+		Opcode:   5749,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -11489,6 +12696,8 @@
 	}
 	OpSubgroupAvcImeRefWindowSizeINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcImeRefWindowSizeINTEL",
+		Class:    "@exclude",
+		Opcode:   5750,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -11514,6 +12723,8 @@
 	}
 	OpSubgroupAvcImeAdjustRefOffsetINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcImeAdjustRefOffsetINTEL",
+		Class:    "@exclude",
+		Opcode:   5751,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -11549,6 +12760,8 @@
 	}
 	OpSubgroupAvcImeConvertToMcePayloadINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcImeConvertToMcePayloadINTEL",
+		Class:    "@exclude",
+		Opcode:   5752,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -11569,6 +12782,8 @@
 	}
 	OpSubgroupAvcImeSetMaxMotionVectorCountINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcImeSetMaxMotionVectorCountINTEL",
+		Class:    "@exclude",
+		Opcode:   5753,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -11594,6 +12809,8 @@
 	}
 	OpSubgroupAvcImeSetUnidirectionalMixDisableINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcImeSetUnidirectionalMixDisableINTEL",
+		Class:    "@exclude",
+		Opcode:   5754,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -11614,6 +12831,8 @@
 	}
 	OpSubgroupAvcImeSetEarlySearchTerminationThresholdINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcImeSetEarlySearchTerminationThresholdINTEL",
+		Class:    "@exclude",
+		Opcode:   5755,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -11639,6 +12858,8 @@
 	}
 	OpSubgroupAvcImeSetWeightedSadINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcImeSetWeightedSadINTEL",
+		Class:    "@exclude",
+		Opcode:   5756,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -11664,6 +12885,8 @@
 	}
 	OpSubgroupAvcImeEvaluateWithSingleReferenceINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcImeEvaluateWithSingleReferenceINTEL",
+		Class:    "@exclude",
+		Opcode:   5757,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -11694,6 +12917,8 @@
 	}
 	OpSubgroupAvcImeEvaluateWithDualReferenceINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcImeEvaluateWithDualReferenceINTEL",
+		Class:    "@exclude",
+		Opcode:   5758,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -11729,6 +12954,8 @@
 	}
 	OpSubgroupAvcImeEvaluateWithSingleReferenceStreaminINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcImeEvaluateWithSingleReferenceStreaminINTEL",
+		Class:    "@exclude",
+		Opcode:   5759,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -11764,6 +12991,8 @@
 	}
 	OpSubgroupAvcImeEvaluateWithDualReferenceStreaminINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcImeEvaluateWithDualReferenceStreaminINTEL",
+		Class:    "@exclude",
+		Opcode:   5760,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -11804,6 +13033,8 @@
 	}
 	OpSubgroupAvcImeEvaluateWithSingleReferenceStreamoutINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcImeEvaluateWithSingleReferenceStreamoutINTEL",
+		Class:    "@exclude",
+		Opcode:   5761,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -11834,6 +13065,8 @@
 	}
 	OpSubgroupAvcImeEvaluateWithDualReferenceStreamoutINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcImeEvaluateWithDualReferenceStreamoutINTEL",
+		Class:    "@exclude",
+		Opcode:   5762,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -11869,6 +13102,8 @@
 	}
 	OpSubgroupAvcImeEvaluateWithSingleReferenceStreaminoutINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcImeEvaluateWithSingleReferenceStreaminoutINTEL",
+		Class:    "@exclude",
+		Opcode:   5763,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -11904,6 +13139,8 @@
 	}
 	OpSubgroupAvcImeEvaluateWithDualReferenceStreaminoutINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcImeEvaluateWithDualReferenceStreaminoutINTEL",
+		Class:    "@exclude",
+		Opcode:   5764,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -11944,6 +13181,8 @@
 	}
 	OpSubgroupAvcImeConvertToMceResultINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcImeConvertToMceResultINTEL",
+		Class:    "@exclude",
+		Opcode:   5765,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -11964,6 +13203,8 @@
 	}
 	OpSubgroupAvcImeGetSingleReferenceStreaminINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcImeGetSingleReferenceStreaminINTEL",
+		Class:    "@exclude",
+		Opcode:   5766,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -11984,6 +13225,8 @@
 	}
 	OpSubgroupAvcImeGetDualReferenceStreaminINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcImeGetDualReferenceStreaminINTEL",
+		Class:    "@exclude",
+		Opcode:   5767,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -12004,6 +13247,8 @@
 	}
 	OpSubgroupAvcImeStripSingleReferenceStreamoutINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcImeStripSingleReferenceStreamoutINTEL",
+		Class:    "@exclude",
+		Opcode:   5768,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -12024,6 +13269,8 @@
 	}
 	OpSubgroupAvcImeStripDualReferenceStreamoutINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcImeStripDualReferenceStreamoutINTEL",
+		Class:    "@exclude",
+		Opcode:   5769,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -12044,6 +13291,8 @@
 	}
 	OpSubgroupAvcImeGetStreamoutSingleReferenceMajorShapeMotionVectorsINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcImeGetStreamoutSingleReferenceMajorShapeMotionVectorsINTEL",
+		Class:    "@exclude",
+		Opcode:   5770,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -12069,6 +13318,8 @@
 	}
 	OpSubgroupAvcImeGetStreamoutSingleReferenceMajorShapeDistortionsINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcImeGetStreamoutSingleReferenceMajorShapeDistortionsINTEL",
+		Class:    "@exclude",
+		Opcode:   5771,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -12094,6 +13345,8 @@
 	}
 	OpSubgroupAvcImeGetStreamoutSingleReferenceMajorShapeReferenceIdsINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcImeGetStreamoutSingleReferenceMajorShapeReferenceIdsINTEL",
+		Class:    "@exclude",
+		Opcode:   5772,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -12119,6 +13372,8 @@
 	}
 	OpSubgroupAvcImeGetStreamoutDualReferenceMajorShapeMotionVectorsINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcImeGetStreamoutDualReferenceMajorShapeMotionVectorsINTEL",
+		Class:    "@exclude",
+		Opcode:   5773,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -12149,6 +13404,8 @@
 	}
 	OpSubgroupAvcImeGetStreamoutDualReferenceMajorShapeDistortionsINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcImeGetStreamoutDualReferenceMajorShapeDistortionsINTEL",
+		Class:    "@exclude",
+		Opcode:   5774,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -12179,6 +13436,8 @@
 	}
 	OpSubgroupAvcImeGetStreamoutDualReferenceMajorShapeReferenceIdsINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcImeGetStreamoutDualReferenceMajorShapeReferenceIdsINTEL",
+		Class:    "@exclude",
+		Opcode:   5775,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -12209,6 +13468,8 @@
 	}
 	OpSubgroupAvcImeGetBorderReachedINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcImeGetBorderReachedINTEL",
+		Class:    "@exclude",
+		Opcode:   5776,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -12234,6 +13495,8 @@
 	}
 	OpSubgroupAvcImeGetTruncatedSearchIndicationINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcImeGetTruncatedSearchIndicationINTEL",
+		Class:    "@exclude",
+		Opcode:   5777,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -12254,6 +13517,8 @@
 	}
 	OpSubgroupAvcImeGetUnidirectionalEarlySearchTerminationINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcImeGetUnidirectionalEarlySearchTerminationINTEL",
+		Class:    "@exclude",
+		Opcode:   5778,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -12274,6 +13539,8 @@
 	}
 	OpSubgroupAvcImeGetWeightingPatternMinimumMotionVectorINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcImeGetWeightingPatternMinimumMotionVectorINTEL",
+		Class:    "@exclude",
+		Opcode:   5779,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -12294,6 +13561,8 @@
 	}
 	OpSubgroupAvcImeGetWeightingPatternMinimumDistortionINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcImeGetWeightingPatternMinimumDistortionINTEL",
+		Class:    "@exclude",
+		Opcode:   5780,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -12314,6 +13583,8 @@
 	}
 	OpSubgroupAvcFmeInitializeINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcFmeInitializeINTEL",
+		Class:    "@exclude",
+		Opcode:   5781,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -12364,6 +13635,8 @@
 	}
 	OpSubgroupAvcBmeInitializeINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcBmeInitializeINTEL",
+		Class:    "@exclude",
+		Opcode:   5782,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -12419,6 +13692,8 @@
 	}
 	OpSubgroupAvcRefConvertToMcePayloadINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcRefConvertToMcePayloadINTEL",
+		Class:    "@exclude",
+		Opcode:   5783,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -12439,6 +13714,8 @@
 	}
 	OpSubgroupAvcRefSetBidirectionalMixDisableINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcRefSetBidirectionalMixDisableINTEL",
+		Class:    "@exclude",
+		Opcode:   5784,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -12459,6 +13736,8 @@
 	}
 	OpSubgroupAvcRefSetBilinearFilterEnableINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcRefSetBilinearFilterEnableINTEL",
+		Class:    "@exclude",
+		Opcode:   5785,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -12479,6 +13758,8 @@
 	}
 	OpSubgroupAvcRefEvaluateWithSingleReferenceINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcRefEvaluateWithSingleReferenceINTEL",
+		Class:    "@exclude",
+		Opcode:   5786,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -12509,6 +13790,8 @@
 	}
 	OpSubgroupAvcRefEvaluateWithDualReferenceINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcRefEvaluateWithDualReferenceINTEL",
+		Class:    "@exclude",
+		Opcode:   5787,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -12544,6 +13827,8 @@
 	}
 	OpSubgroupAvcRefEvaluateWithMultiReferenceINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcRefEvaluateWithMultiReferenceINTEL",
+		Class:    "@exclude",
+		Opcode:   5788,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -12574,6 +13859,8 @@
 	}
 	OpSubgroupAvcRefEvaluateWithMultiReferenceInterlacedINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcRefEvaluateWithMultiReferenceInterlacedINTEL",
+		Class:    "@exclude",
+		Opcode:   5789,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -12609,6 +13896,8 @@
 	}
 	OpSubgroupAvcRefConvertToMceResultINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcRefConvertToMceResultINTEL",
+		Class:    "@exclude",
+		Opcode:   5790,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -12629,6 +13918,8 @@
 	}
 	OpSubgroupAvcSicInitializeINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcSicInitializeINTEL",
+		Class:    "@exclude",
+		Opcode:   5791,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -12649,6 +13940,8 @@
 	}
 	OpSubgroupAvcSicConfigureSkcINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcSicConfigureSkcINTEL",
+		Class:    "@exclude",
+		Opcode:   5792,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -12694,6 +13987,8 @@
 	}
 	OpSubgroupAvcSicConfigureIpeLumaINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcSicConfigureIpeLumaINTEL",
+		Class:    "@exclude",
+		Opcode:   5793,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -12749,6 +14044,8 @@
 	}
 	OpSubgroupAvcSicConfigureIpeLumaChromaINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcSicConfigureIpeLumaChromaINTEL",
+		Class:    "@exclude",
+		Opcode:   5794,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -12819,6 +14116,8 @@
 	}
 	OpSubgroupAvcSicGetMotionVectorMaskINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcSicGetMotionVectorMaskINTEL",
+		Class:    "@exclude",
+		Opcode:   5795,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -12844,6 +14143,8 @@
 	}
 	OpSubgroupAvcSicConvertToMcePayloadINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcSicConvertToMcePayloadINTEL",
+		Class:    "@exclude",
+		Opcode:   5796,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -12864,6 +14165,8 @@
 	}
 	OpSubgroupAvcSicSetIntraLumaShapePenaltyINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcSicSetIntraLumaShapePenaltyINTEL",
+		Class:    "@exclude",
+		Opcode:   5797,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -12889,6 +14192,8 @@
 	}
 	OpSubgroupAvcSicSetIntraLumaModeCostFunctionINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcSicSetIntraLumaModeCostFunctionINTEL",
+		Class:    "@exclude",
+		Opcode:   5798,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -12924,6 +14229,8 @@
 	}
 	OpSubgroupAvcSicSetIntraChromaModeCostFunctionINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcSicSetIntraChromaModeCostFunctionINTEL",
+		Class:    "@exclude",
+		Opcode:   5799,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -12949,6 +14256,8 @@
 	}
 	OpSubgroupAvcSicSetBilinearFilterEnableINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcSicSetBilinearFilterEnableINTEL",
+		Class:    "@exclude",
+		Opcode:   5800,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -12969,6 +14278,8 @@
 	}
 	OpSubgroupAvcSicSetSkcForwardTransformEnableINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcSicSetSkcForwardTransformEnableINTEL",
+		Class:    "@exclude",
+		Opcode:   5801,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -12994,6 +14305,8 @@
 	}
 	OpSubgroupAvcSicSetBlockBasedRawSkipSadINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcSicSetBlockBasedRawSkipSadINTEL",
+		Class:    "@exclude",
+		Opcode:   5802,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -13019,6 +14332,8 @@
 	}
 	OpSubgroupAvcSicEvaluateIpeINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcSicEvaluateIpeINTEL",
+		Class:    "@exclude",
+		Opcode:   5803,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -13044,6 +14359,8 @@
 	}
 	OpSubgroupAvcSicEvaluateWithSingleReferenceINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcSicEvaluateWithSingleReferenceINTEL",
+		Class:    "@exclude",
+		Opcode:   5804,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -13074,6 +14391,8 @@
 	}
 	OpSubgroupAvcSicEvaluateWithDualReferenceINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcSicEvaluateWithDualReferenceINTEL",
+		Class:    "@exclude",
+		Opcode:   5805,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -13109,6 +14428,8 @@
 	}
 	OpSubgroupAvcSicEvaluateWithMultiReferenceINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcSicEvaluateWithMultiReferenceINTEL",
+		Class:    "@exclude",
+		Opcode:   5806,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -13139,6 +14460,8 @@
 	}
 	OpSubgroupAvcSicEvaluateWithMultiReferenceInterlacedINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcSicEvaluateWithMultiReferenceInterlacedINTEL",
+		Class:    "@exclude",
+		Opcode:   5807,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -13174,6 +14497,8 @@
 	}
 	OpSubgroupAvcSicConvertToMceResultINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcSicConvertToMceResultINTEL",
+		Class:    "@exclude",
+		Opcode:   5808,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -13194,6 +14519,8 @@
 	}
 	OpSubgroupAvcSicGetIpeLumaShapeINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcSicGetIpeLumaShapeINTEL",
+		Class:    "@exclude",
+		Opcode:   5809,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -13214,6 +14541,8 @@
 	}
 	OpSubgroupAvcSicGetBestIpeLumaDistortionINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcSicGetBestIpeLumaDistortionINTEL",
+		Class:    "@exclude",
+		Opcode:   5810,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -13234,6 +14563,8 @@
 	}
 	OpSubgroupAvcSicGetBestIpeChromaDistortionINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcSicGetBestIpeChromaDistortionINTEL",
+		Class:    "@exclude",
+		Opcode:   5811,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -13254,6 +14585,8 @@
 	}
 	OpSubgroupAvcSicGetPackedIpeLumaModesINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcSicGetPackedIpeLumaModesINTEL",
+		Class:    "@exclude",
+		Opcode:   5812,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -13274,6 +14607,8 @@
 	}
 	OpSubgroupAvcSicGetIpeChromaModeINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcSicGetIpeChromaModeINTEL",
+		Class:    "@exclude",
+		Opcode:   5813,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -13294,6 +14629,8 @@
 	}
 	OpSubgroupAvcSicGetPackedSkcLumaCountThresholdINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcSicGetPackedSkcLumaCountThresholdINTEL",
+		Class:    "@exclude",
+		Opcode:   5814,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -13314,6 +14651,8 @@
 	}
 	OpSubgroupAvcSicGetPackedSkcLumaSumThresholdINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcSicGetPackedSkcLumaSumThresholdINTEL",
+		Class:    "@exclude",
+		Opcode:   5815,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -13334,6 +14673,8 @@
 	}
 	OpSubgroupAvcSicGetInterRawSadsINTEL = &Opcode {
 		Opname:   "OpSubgroupAvcSicGetInterRawSadsINTEL",
+		Class:    "@exclude",
+		Opcode:   5816,
 		Operands: []Operand {
 			Operand {
 				Kind:       OperandKindIdResultType,
@@ -13353,6 +14694,4167 @@
 		},
 	}
 
+	GLSLStd450_Round = &Opcode {
+		Opname:   "Round",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_RoundEven = &Opcode {
+		Opname:   "RoundEven",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_Trunc = &Opcode {
+		Opname:   "Trunc",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_FAbs = &Opcode {
+		Opname:   "FAbs",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_SAbs = &Opcode {
+		Opname:   "SAbs",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_FSign = &Opcode {
+		Opname:   "FSign",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_SSign = &Opcode {
+		Opname:   "SSign",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_Floor = &Opcode {
+		Opname:   "Floor",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_Ceil = &Opcode {
+		Opname:   "Ceil",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_Fract = &Opcode {
+		Opname:   "Fract",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_Radians = &Opcode {
+		Opname:   "Radians",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'degrees'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_Degrees = &Opcode {
+		Opname:   "Degrees",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'radians'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_Sin = &Opcode {
+		Opname:   "Sin",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_Cos = &Opcode {
+		Opname:   "Cos",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_Tan = &Opcode {
+		Opname:   "Tan",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_Asin = &Opcode {
+		Opname:   "Asin",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_Acos = &Opcode {
+		Opname:   "Acos",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_Atan = &Opcode {
+		Opname:   "Atan",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y_over_x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_Sinh = &Opcode {
+		Opname:   "Sinh",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_Cosh = &Opcode {
+		Opname:   "Cosh",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_Tanh = &Opcode {
+		Opname:   "Tanh",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_Asinh = &Opcode {
+		Opname:   "Asinh",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_Acosh = &Opcode {
+		Opname:   "Acosh",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_Atanh = &Opcode {
+		Opname:   "Atanh",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_Atan2 = &Opcode {
+		Opname:   "Atan2",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_Pow = &Opcode {
+		Opname:   "Pow",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_Exp = &Opcode {
+		Opname:   "Exp",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_Log = &Opcode {
+		Opname:   "Log",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_Exp2 = &Opcode {
+		Opname:   "Exp2",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_Log2 = &Opcode {
+		Opname:   "Log2",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_Sqrt = &Opcode {
+		Opname:   "Sqrt",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_InverseSqrt = &Opcode {
+		Opname:   "InverseSqrt",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_Determinant = &Opcode {
+		Opname:   "Determinant",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_MatrixInverse = &Opcode {
+		Opname:   "MatrixInverse",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_Modf = &Opcode {
+		Opname:   "Modf",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'i'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_ModfStruct = &Opcode {
+		Opname:   "ModfStruct",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_FMin = &Opcode {
+		Opname:   "FMin",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_UMin = &Opcode {
+		Opname:   "UMin",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_SMin = &Opcode {
+		Opname:   "SMin",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_FMax = &Opcode {
+		Opname:   "FMax",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_UMax = &Opcode {
+		Opname:   "UMax",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_SMax = &Opcode {
+		Opname:   "SMax",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_FClamp = &Opcode {
+		Opname:   "FClamp",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'minVal'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'maxVal'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_UClamp = &Opcode {
+		Opname:   "UClamp",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'minVal'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'maxVal'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_SClamp = &Opcode {
+		Opname:   "SClamp",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'minVal'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'maxVal'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_FMix = &Opcode {
+		Opname:   "FMix",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'a'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_IMix = &Opcode {
+		Opname:   "IMix",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'a'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_Step = &Opcode {
+		Opname:   "Step",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'edge'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_SmoothStep = &Opcode {
+		Opname:   "SmoothStep",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'edge0'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'edge1'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_Fma = &Opcode {
+		Opname:   "Fma",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'a'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'b'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'c'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_Frexp = &Opcode {
+		Opname:   "Frexp",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'exp'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_FrexpStruct = &Opcode {
+		Opname:   "FrexpStruct",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_Ldexp = &Opcode {
+		Opname:   "Ldexp",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'exp'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_PackSnorm4x8 = &Opcode {
+		Opname:   "PackSnorm4x8",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'v'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_PackUnorm4x8 = &Opcode {
+		Opname:   "PackUnorm4x8",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'v'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_PackSnorm2x16 = &Opcode {
+		Opname:   "PackSnorm2x16",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'v'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_PackUnorm2x16 = &Opcode {
+		Opname:   "PackUnorm2x16",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'v'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_PackHalf2x16 = &Opcode {
+		Opname:   "PackHalf2x16",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'v'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_PackDouble2x32 = &Opcode {
+		Opname:   "PackDouble2x32",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'v'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_UnpackSnorm2x16 = &Opcode {
+		Opname:   "UnpackSnorm2x16",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'p'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_UnpackUnorm2x16 = &Opcode {
+		Opname:   "UnpackUnorm2x16",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'p'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_UnpackHalf2x16 = &Opcode {
+		Opname:   "UnpackHalf2x16",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'v'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_UnpackSnorm4x8 = &Opcode {
+		Opname:   "UnpackSnorm4x8",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'p'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_UnpackUnorm4x8 = &Opcode {
+		Opname:   "UnpackUnorm4x8",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'p'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_UnpackDouble2x32 = &Opcode {
+		Opname:   "UnpackDouble2x32",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'v'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_Length = &Opcode {
+		Opname:   "Length",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_Distance = &Opcode {
+		Opname:   "Distance",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'p0'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'p1'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_Cross = &Opcode {
+		Opname:   "Cross",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_Normalize = &Opcode {
+		Opname:   "Normalize",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_FaceForward = &Opcode {
+		Opname:   "FaceForward",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'N'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'I'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Nref'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_Reflect = &Opcode {
+		Opname:   "Reflect",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'I'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'N'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_Refract = &Opcode {
+		Opname:   "Refract",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'I'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'N'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'eta'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_FindILsb = &Opcode {
+		Opname:   "FindILsb",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Value'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_FindSMsb = &Opcode {
+		Opname:   "FindSMsb",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Value'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_FindUMsb = &Opcode {
+		Opname:   "FindUMsb",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Value'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_InterpolateAtCentroid = &Opcode {
+		Opname:   "InterpolateAtCentroid",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'interpolant'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_InterpolateAtSample = &Opcode {
+		Opname:   "InterpolateAtSample",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'interpolant'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'sample'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_InterpolateAtOffset = &Opcode {
+		Opname:   "InterpolateAtOffset",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'interpolant'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'offset'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_NMin = &Opcode {
+		Opname:   "NMin",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_NMax = &Opcode {
+		Opname:   "NMax",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	GLSLStd450_NClamp = &Opcode {
+		Opname:   "NClamp",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'minVal'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'maxVal'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_acos = &Opcode {
+		Opname:   "acos",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_acosh = &Opcode {
+		Opname:   "acosh",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_acospi = &Opcode {
+		Opname:   "acospi",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_asin = &Opcode {
+		Opname:   "asin",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_asinh = &Opcode {
+		Opname:   "asinh",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_asinpi = &Opcode {
+		Opname:   "asinpi",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_atan = &Opcode {
+		Opname:   "atan",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_atan2 = &Opcode {
+		Opname:   "atan2",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_atanh = &Opcode {
+		Opname:   "atanh",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_atanpi = &Opcode {
+		Opname:   "atanpi",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_atan2pi = &Opcode {
+		Opname:   "atan2pi",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_cbrt = &Opcode {
+		Opname:   "cbrt",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_ceil = &Opcode {
+		Opname:   "ceil",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_copysign = &Opcode {
+		Opname:   "copysign",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_cos = &Opcode {
+		Opname:   "cos",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_cosh = &Opcode {
+		Opname:   "cosh",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_cospi = &Opcode {
+		Opname:   "cospi",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_erfc = &Opcode {
+		Opname:   "erfc",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_erf = &Opcode {
+		Opname:   "erf",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_exp = &Opcode {
+		Opname:   "exp",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_exp2 = &Opcode {
+		Opname:   "exp2",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_exp10 = &Opcode {
+		Opname:   "exp10",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_expm1 = &Opcode {
+		Opname:   "expm1",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_fabs = &Opcode {
+		Opname:   "fabs",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_fdim = &Opcode {
+		Opname:   "fdim",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_floor = &Opcode {
+		Opname:   "floor",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_fma = &Opcode {
+		Opname:   "fma",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'a'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'b'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'c'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_fmax = &Opcode {
+		Opname:   "fmax",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_fmin = &Opcode {
+		Opname:   "fmin",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_fmod = &Opcode {
+		Opname:   "fmod",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_fract = &Opcode {
+		Opname:   "fract",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'ptr'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_frexp = &Opcode {
+		Opname:   "frexp",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'exp'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_hypot = &Opcode {
+		Opname:   "hypot",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_ilogb = &Opcode {
+		Opname:   "ilogb",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_ldexp = &Opcode {
+		Opname:   "ldexp",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'k'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_lgamma = &Opcode {
+		Opname:   "lgamma",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_lgamma_r = &Opcode {
+		Opname:   "lgamma_r",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'signp'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_log = &Opcode {
+		Opname:   "log",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_log2 = &Opcode {
+		Opname:   "log2",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_log10 = &Opcode {
+		Opname:   "log10",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_log1p = &Opcode {
+		Opname:   "log1p",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_logb = &Opcode {
+		Opname:   "logb",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_mad = &Opcode {
+		Opname:   "mad",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'a'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'b'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'c'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_maxmag = &Opcode {
+		Opname:   "maxmag",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_minmag = &Opcode {
+		Opname:   "minmag",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_modf = &Opcode {
+		Opname:   "modf",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'iptr'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_nan = &Opcode {
+		Opname:   "nan",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'nancode'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_nextafter = &Opcode {
+		Opname:   "nextafter",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_pow = &Opcode {
+		Opname:   "pow",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_pown = &Opcode {
+		Opname:   "pown",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_powr = &Opcode {
+		Opname:   "powr",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_remainder = &Opcode {
+		Opname:   "remainder",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_remquo = &Opcode {
+		Opname:   "remquo",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'quo'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_rint = &Opcode {
+		Opname:   "rint",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_rootn = &Opcode {
+		Opname:   "rootn",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_round = &Opcode {
+		Opname:   "round",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_rsqrt = &Opcode {
+		Opname:   "rsqrt",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_sin = &Opcode {
+		Opname:   "sin",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_sincos = &Opcode {
+		Opname:   "sincos",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'cosval'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_sinh = &Opcode {
+		Opname:   "sinh",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_sinpi = &Opcode {
+		Opname:   "sinpi",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_sqrt = &Opcode {
+		Opname:   "sqrt",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_tan = &Opcode {
+		Opname:   "tan",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_tanh = &Opcode {
+		Opname:   "tanh",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_tanpi = &Opcode {
+		Opname:   "tanpi",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_tgamma = &Opcode {
+		Opname:   "tgamma",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_trunc = &Opcode {
+		Opname:   "trunc",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_half_cos = &Opcode {
+		Opname:   "half_cos",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_half_divide = &Opcode {
+		Opname:   "half_divide",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_half_exp = &Opcode {
+		Opname:   "half_exp",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_half_exp2 = &Opcode {
+		Opname:   "half_exp2",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_half_exp10 = &Opcode {
+		Opname:   "half_exp10",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_half_log = &Opcode {
+		Opname:   "half_log",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_half_log2 = &Opcode {
+		Opname:   "half_log2",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_half_log10 = &Opcode {
+		Opname:   "half_log10",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_half_powr = &Opcode {
+		Opname:   "half_powr",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_half_recip = &Opcode {
+		Opname:   "half_recip",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_half_rsqrt = &Opcode {
+		Opname:   "half_rsqrt",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_half_sin = &Opcode {
+		Opname:   "half_sin",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_half_sqrt = &Opcode {
+		Opname:   "half_sqrt",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_half_tan = &Opcode {
+		Opname:   "half_tan",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_native_cos = &Opcode {
+		Opname:   "native_cos",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_native_divide = &Opcode {
+		Opname:   "native_divide",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_native_exp = &Opcode {
+		Opname:   "native_exp",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_native_exp2 = &Opcode {
+		Opname:   "native_exp2",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_native_exp10 = &Opcode {
+		Opname:   "native_exp10",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_native_log = &Opcode {
+		Opname:   "native_log",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_native_log2 = &Opcode {
+		Opname:   "native_log2",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_native_log10 = &Opcode {
+		Opname:   "native_log10",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_native_powr = &Opcode {
+		Opname:   "native_powr",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_native_recip = &Opcode {
+		Opname:   "native_recip",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_native_rsqrt = &Opcode {
+		Opname:   "native_rsqrt",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_native_sin = &Opcode {
+		Opname:   "native_sin",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_native_sqrt = &Opcode {
+		Opname:   "native_sqrt",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_native_tan = &Opcode {
+		Opname:   "native_tan",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_s_abs = &Opcode {
+		Opname:   "s_abs",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_s_abs_diff = &Opcode {
+		Opname:   "s_abs_diff",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_s_add_sat = &Opcode {
+		Opname:   "s_add_sat",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_u_add_sat = &Opcode {
+		Opname:   "u_add_sat",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_s_hadd = &Opcode {
+		Opname:   "s_hadd",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_u_hadd = &Opcode {
+		Opname:   "u_hadd",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_s_rhadd = &Opcode {
+		Opname:   "s_rhadd",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_u_rhadd = &Opcode {
+		Opname:   "u_rhadd",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_s_clamp = &Opcode {
+		Opname:   "s_clamp",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'minval'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'maxval'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_u_clamp = &Opcode {
+		Opname:   "u_clamp",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'minval'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'maxval'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_clz = &Opcode {
+		Opname:   "clz",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_ctz = &Opcode {
+		Opname:   "ctz",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_s_mad_hi = &Opcode {
+		Opname:   "s_mad_hi",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'a'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'b'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'c'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_u_mad_sat = &Opcode {
+		Opname:   "u_mad_sat",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'z'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_s_mad_sat = &Opcode {
+		Opname:   "s_mad_sat",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'z'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_s_max = &Opcode {
+		Opname:   "s_max",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_u_max = &Opcode {
+		Opname:   "u_max",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_s_min = &Opcode {
+		Opname:   "s_min",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_u_min = &Opcode {
+		Opname:   "u_min",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_s_mul_hi = &Opcode {
+		Opname:   "s_mul_hi",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_rotate = &Opcode {
+		Opname:   "rotate",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'v'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'i'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_s_sub_sat = &Opcode {
+		Opname:   "s_sub_sat",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_u_sub_sat = &Opcode {
+		Opname:   "u_sub_sat",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_u_upsample = &Opcode {
+		Opname:   "u_upsample",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'hi'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'lo'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_s_upsample = &Opcode {
+		Opname:   "s_upsample",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'hi'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'lo'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_popcount = &Opcode {
+		Opname:   "popcount",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_s_mad24 = &Opcode {
+		Opname:   "s_mad24",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'z'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_u_mad24 = &Opcode {
+		Opname:   "u_mad24",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'z'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_s_mul24 = &Opcode {
+		Opname:   "s_mul24",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_u_mul24 = &Opcode {
+		Opname:   "u_mul24",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_u_abs = &Opcode {
+		Opname:   "u_abs",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_u_abs_diff = &Opcode {
+		Opname:   "u_abs_diff",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_u_mul_hi = &Opcode {
+		Opname:   "u_mul_hi",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_u_mad_hi = &Opcode {
+		Opname:   "u_mad_hi",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'a'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'b'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'c'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_fclamp = &Opcode {
+		Opname:   "fclamp",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'minval'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'maxval'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_degrees = &Opcode {
+		Opname:   "degrees",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'radians'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_fmax_common = &Opcode {
+		Opname:   "fmax_common",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_fmin_common = &Opcode {
+		Opname:   "fmin_common",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_mix = &Opcode {
+		Opname:   "mix",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'a'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_radians = &Opcode {
+		Opname:   "radians",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'degrees'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_step = &Opcode {
+		Opname:   "step",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'edge'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_smoothstep = &Opcode {
+		Opname:   "smoothstep",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'edge0'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'edge1'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_sign = &Opcode {
+		Opname:   "sign",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_cross = &Opcode {
+		Opname:   "cross",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'p0'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'p1'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_distance = &Opcode {
+		Opname:   "distance",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'p0'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'p1'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_length = &Opcode {
+		Opname:   "length",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'p'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_normalize = &Opcode {
+		Opname:   "normalize",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'p'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_fast_distance = &Opcode {
+		Opname:   "fast_distance",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'p0'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'p1'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_fast_length = &Opcode {
+		Opname:   "fast_length",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'p'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_fast_normalize = &Opcode {
+		Opname:   "fast_normalize",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'p'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_bitselect = &Opcode {
+		Opname:   "bitselect",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'a'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'b'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'c'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_select = &Opcode {
+		Opname:   "select",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'a'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'b'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'c'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_vloadn = &Opcode {
+		Opname:   "vloadn",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'offset'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'p'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindLiteralInteger,
+				Name:       "'n'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_vstoren = &Opcode {
+		Opname:   "vstoren",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'data'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'offset'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'p'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_vload_half = &Opcode {
+		Opname:   "vload_half",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'offset'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'p'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_vload_halfn = &Opcode {
+		Opname:   "vload_halfn",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'offset'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'p'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindLiteralInteger,
+				Name:       "'n'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_vstore_half = &Opcode {
+		Opname:   "vstore_half",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'data'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'offset'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'p'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_vstore_half_r = &Opcode {
+		Opname:   "vstore_half_r",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'data'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'offset'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'p'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindFPRoundingMode,
+				Name:       "'mode'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_vstore_halfn = &Opcode {
+		Opname:   "vstore_halfn",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'data'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'offset'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'p'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_vstore_halfn_r = &Opcode {
+		Opname:   "vstore_halfn_r",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'data'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'offset'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'p'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindFPRoundingMode,
+				Name:       "'mode'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_vloada_halfn = &Opcode {
+		Opname:   "vloada_halfn",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'offset'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'p'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindLiteralInteger,
+				Name:       "'n'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_vstorea_halfn = &Opcode {
+		Opname:   "vstorea_halfn",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'data'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'offset'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'p'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_vstorea_halfn_r = &Opcode {
+		Opname:   "vstorea_halfn_r",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'data'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'offset'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'p'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindFPRoundingMode,
+				Name:       "'mode'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_shuffle = &Opcode {
+		Opname:   "shuffle",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'shuffle mask'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_shuffle2 = &Opcode {
+		Opname:   "shuffle2",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'x'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'y'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'shuffle mask'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLStd_printf = &Opcode {
+		Opname:   "printf",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'format'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'additional arguments'",
+				Quantifier: "*",
+			}, 
+		},
+	}
+	OpenCLStd_prefetch = &Opcode {
+		Opname:   "prefetch",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'ptr'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'num elements'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLDebugInfo100_DebugInfoNone = &Opcode {
+		Opname:   "DebugInfoNone",
+		Operands: []Operand {
+		},
+	}
+	OpenCLDebugInfo100_DebugCompilationUnit = &Opcode {
+		Opname:   "DebugCompilationUnit",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindLiteralInteger,
+				Name:       "'Version'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindLiteralInteger,
+				Name:       "'DWARF Version'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Source'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindSourceLanguage,
+				Name:       "'Language'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLDebugInfo100_DebugTypeBasic = &Opcode {
+		Opname:   "DebugTypeBasic",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Name'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Size'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindDebugBaseTypeAttributeEncoding,
+				Name:       "'Encoding'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLDebugInfo100_DebugTypePointer = &Opcode {
+		Opname:   "DebugTypePointer",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Base Type'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindStorageClass,
+				Name:       "'Storage Class'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindDebugInfoFlags,
+				Name:       "'Flags'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLDebugInfo100_DebugTypeQualifier = &Opcode {
+		Opname:   "DebugTypeQualifier",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Base Type'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindDebugTypeQualifier,
+				Name:       "'Type Qualifier'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLDebugInfo100_DebugTypeArray = &Opcode {
+		Opname:   "DebugTypeArray",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Base Type'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Component Counts'",
+				Quantifier: "*",
+			}, 
+		},
+	}
+	OpenCLDebugInfo100_DebugTypeVector = &Opcode {
+		Opname:   "DebugTypeVector",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Base Type'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindLiteralInteger,
+				Name:       "'Component Count'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLDebugInfo100_DebugTypedef = &Opcode {
+		Opname:   "DebugTypedef",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Name'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Base Type'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Source'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindLiteralInteger,
+				Name:       "'Line'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindLiteralInteger,
+				Name:       "'Column'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Parent'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLDebugInfo100_DebugTypeFunction = &Opcode {
+		Opname:   "DebugTypeFunction",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindDebugInfoFlags,
+				Name:       "'Flags'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Return Type'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Parameter Types'",
+				Quantifier: "*",
+			}, 
+		},
+	}
+	OpenCLDebugInfo100_DebugTypeEnum = &Opcode {
+		Opname:   "DebugTypeEnum",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Name'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Underlying Type'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Source'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindLiteralInteger,
+				Name:       "'Line'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindLiteralInteger,
+				Name:       "'Column'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Parent'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Size'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindDebugInfoFlags,
+				Name:       "'Flags'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindPairIdRefIdRef,
+				Name:       "'Value, Name, Value, Name, ...'",
+				Quantifier: "*",
+			}, 
+		},
+	}
+	OpenCLDebugInfo100_DebugTypeComposite = &Opcode {
+		Opname:   "DebugTypeComposite",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Name'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindDebugCompositeType,
+				Name:       "'Tag'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Source'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindLiteralInteger,
+				Name:       "'Line'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindLiteralInteger,
+				Name:       "'Column'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Parent'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Linkage Name'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Size'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindDebugInfoFlags,
+				Name:       "'Flags'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Members'",
+				Quantifier: "*",
+			}, 
+		},
+	}
+	OpenCLDebugInfo100_DebugTypeMember = &Opcode {
+		Opname:   "DebugTypeMember",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Name'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Type'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Source'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindLiteralInteger,
+				Name:       "'Line'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindLiteralInteger,
+				Name:       "'Column'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Parent'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Offset'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Size'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindDebugInfoFlags,
+				Name:       "'Flags'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Value'",
+				Quantifier: "?",
+			}, 
+		},
+	}
+	OpenCLDebugInfo100_DebugTypeInheritance = &Opcode {
+		Opname:   "DebugTypeInheritance",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Child'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Parent'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Offset'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Size'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindDebugInfoFlags,
+				Name:       "'Flags'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLDebugInfo100_DebugTypePtrToMember = &Opcode {
+		Opname:   "DebugTypePtrToMember",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Member Type'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Parent'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLDebugInfo100_DebugTypeTemplate = &Opcode {
+		Opname:   "DebugTypeTemplate",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Target'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Parameters'",
+				Quantifier: "*",
+			}, 
+		},
+	}
+	OpenCLDebugInfo100_DebugTypeTemplateParameter = &Opcode {
+		Opname:   "DebugTypeTemplateParameter",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Name'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Actual Type'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Value'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Source'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindLiteralInteger,
+				Name:       "'Line'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindLiteralInteger,
+				Name:       "'Column'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLDebugInfo100_DebugTypeTemplateTemplateParameter = &Opcode {
+		Opname:   "DebugTypeTemplateTemplateParameter",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Name'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Template Name'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Source'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindLiteralInteger,
+				Name:       "'Line'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindLiteralInteger,
+				Name:       "'Column'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLDebugInfo100_DebugTypeTemplateParameterPack = &Opcode {
+		Opname:   "DebugTypeTemplateParameterPack",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Name'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Source'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindLiteralInteger,
+				Name:       "'Line'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindLiteralInteger,
+				Name:       "'Column'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Template Parameters'",
+				Quantifier: "*",
+			}, 
+		},
+	}
+	OpenCLDebugInfo100_DebugGlobalVariable = &Opcode {
+		Opname:   "DebugGlobalVariable",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Name'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Type'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Source'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindLiteralInteger,
+				Name:       "'Line'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindLiteralInteger,
+				Name:       "'Column'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Parent'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Linkage Name'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Variable'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindDebugInfoFlags,
+				Name:       "'Flags'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Static Member Declaration'",
+				Quantifier: "?",
+			}, 
+		},
+	}
+	OpenCLDebugInfo100_DebugFunctionDeclaration = &Opcode {
+		Opname:   "DebugFunctionDeclaration",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Name'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Type'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Source'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindLiteralInteger,
+				Name:       "'Line'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindLiteralInteger,
+				Name:       "'Column'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Parent'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Linkage Name'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindDebugInfoFlags,
+				Name:       "'Flags'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLDebugInfo100_DebugFunction = &Opcode {
+		Opname:   "DebugFunction",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Name'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Type'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Source'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindLiteralInteger,
+				Name:       "'Line'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindLiteralInteger,
+				Name:       "'Column'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Parent'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Linkage Name'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindDebugInfoFlags,
+				Name:       "'Flags'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindLiteralInteger,
+				Name:       "'Scope Line'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Function'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Declaration'",
+				Quantifier: "?",
+			}, 
+		},
+	}
+	OpenCLDebugInfo100_DebugLexicalBlock = &Opcode {
+		Opname:   "DebugLexicalBlock",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Source'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindLiteralInteger,
+				Name:       "'Line'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindLiteralInteger,
+				Name:       "'Column'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Parent'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Name'",
+				Quantifier: "?",
+			}, 
+		},
+	}
+	OpenCLDebugInfo100_DebugLexicalBlockDiscriminator = &Opcode {
+		Opname:   "DebugLexicalBlockDiscriminator",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Source'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindLiteralInteger,
+				Name:       "'Discriminator'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Parent'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLDebugInfo100_DebugScope = &Opcode {
+		Opname:   "DebugScope",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Scope'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Inlined At'",
+				Quantifier: "?",
+			}, 
+		},
+	}
+	OpenCLDebugInfo100_DebugNoScope = &Opcode {
+		Opname:   "DebugNoScope",
+		Operands: []Operand {
+		},
+	}
+	OpenCLDebugInfo100_DebugInlinedAt = &Opcode {
+		Opname:   "DebugInlinedAt",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindLiteralInteger,
+				Name:       "'Line'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Scope'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Inlined'",
+				Quantifier: "?",
+			}, 
+		},
+	}
+	OpenCLDebugInfo100_DebugLocalVariable = &Opcode {
+		Opname:   "DebugLocalVariable",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Name'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Type'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Source'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindLiteralInteger,
+				Name:       "'Line'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindLiteralInteger,
+				Name:       "'Column'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Parent'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindDebugInfoFlags,
+				Name:       "'Flags'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindLiteralInteger,
+				Name:       "'Arg Number'",
+				Quantifier: "?",
+			}, 
+		},
+	}
+	OpenCLDebugInfo100_DebugInlinedVariable = &Opcode {
+		Opname:   "DebugInlinedVariable",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Variable'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Inlined'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLDebugInfo100_DebugDeclare = &Opcode {
+		Opname:   "DebugDeclare",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Local Variable'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Variable'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Expression'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLDebugInfo100_DebugValue = &Opcode {
+		Opname:   "DebugValue",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Local Variable'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Value'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Expression'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Indexes'",
+				Quantifier: "*",
+			}, 
+		},
+	}
+	OpenCLDebugInfo100_DebugOperation = &Opcode {
+		Opname:   "DebugOperation",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindDebugOperation,
+				Name:       "'OpCode'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindLiteralInteger,
+				Name:       "'Operands ...'",
+				Quantifier: "*",
+			}, 
+		},
+	}
+	OpenCLDebugInfo100_DebugExpression = &Opcode {
+		Opname:   "DebugExpression",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Operands ...'",
+				Quantifier: "*",
+			}, 
+		},
+	}
+	OpenCLDebugInfo100_DebugMacroDef = &Opcode {
+		Opname:   "DebugMacroDef",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Source'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindLiteralInteger,
+				Name:       "'Line'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Name'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Value'",
+				Quantifier: "?",
+			}, 
+		},
+	}
+	OpenCLDebugInfo100_DebugMacroUndef = &Opcode {
+		Opname:   "DebugMacroUndef",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Source'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindLiteralInteger,
+				Name:       "'Line'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Macro'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLDebugInfo100_DebugImportedEntity = &Opcode {
+		Opname:   "DebugImportedEntity",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Name'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindDebugImportedEntity,
+				Name:       "'Tag'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Source'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Entity'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindLiteralInteger,
+				Name:       "'Line'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindLiteralInteger,
+				Name:       "'Column'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Parent'",
+				Quantifier: "",
+			}, 
+		},
+	}
+	OpenCLDebugInfo100_DebugSource = &Opcode {
+		Opname:   "DebugSource",
+		Operands: []Operand {
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'File'",
+				Quantifier: "",
+			}, 
+			Operand {
+				Kind:       OperandKindIdRef,
+				Name:       "'Text'",
+				Quantifier: "?",
+			}, 
+		},
+	}
+
 
 	OperandKindImageOperands = &OperandKind {
 		Kind:       "ImageOperands",
@@ -18114,5 +23616,362 @@
 		},
 		Bases:      []*OperandKind {OperandKindIdRef,OperandKindIdRef,},
 	}
+	OperandKindDebugInfoFlags = &OperandKind {
+		Kind:       "DebugInfoFlags",
+		Category:   "BitEnum",
+		Enumerants: []Enumerant {
+			Enumerant{
+				Enumerant:    "FlagIsProtected",
+				Value:        0x01,
+				Capabilities: []string{},
+				Parameters:   []Parameter{},
+				Version:      "",
+			},
+			Enumerant{
+				Enumerant:    "FlagIsPrivate",
+				Value:        0x02,
+				Capabilities: []string{},
+				Parameters:   []Parameter{},
+				Version:      "",
+			},
+			Enumerant{
+				Enumerant:    "FlagIsPublic",
+				Value:        0x03,
+				Capabilities: []string{},
+				Parameters:   []Parameter{},
+				Version:      "",
+			},
+			Enumerant{
+				Enumerant:    "FlagIsLocal",
+				Value:        0x04,
+				Capabilities: []string{},
+				Parameters:   []Parameter{},
+				Version:      "",
+			},
+			Enumerant{
+				Enumerant:    "FlagIsDefinition",
+				Value:        0x08,
+				Capabilities: []string{},
+				Parameters:   []Parameter{},
+				Version:      "",
+			},
+			Enumerant{
+				Enumerant:    "FlagFwdDecl",
+				Value:        0x10,
+				Capabilities: []string{},
+				Parameters:   []Parameter{},
+				Version:      "",
+			},
+			Enumerant{
+				Enumerant:    "FlagArtificial",
+				Value:        0x20,
+				Capabilities: []string{},
+				Parameters:   []Parameter{},
+				Version:      "",
+			},
+			Enumerant{
+				Enumerant:    "FlagExplicit",
+				Value:        0x40,
+				Capabilities: []string{},
+				Parameters:   []Parameter{},
+				Version:      "",
+			},
+			Enumerant{
+				Enumerant:    "FlagPrototyped",
+				Value:        0x80,
+				Capabilities: []string{},
+				Parameters:   []Parameter{},
+				Version:      "",
+			},
+			Enumerant{
+				Enumerant:    "FlagObjectPointer",
+				Value:        0x100,
+				Capabilities: []string{},
+				Parameters:   []Parameter{},
+				Version:      "",
+			},
+			Enumerant{
+				Enumerant:    "FlagStaticMember",
+				Value:        0x200,
+				Capabilities: []string{},
+				Parameters:   []Parameter{},
+				Version:      "",
+			},
+			Enumerant{
+				Enumerant:    "FlagIndirectVariable",
+				Value:        0x400,
+				Capabilities: []string{},
+				Parameters:   []Parameter{},
+				Version:      "",
+			},
+			Enumerant{
+				Enumerant:    "FlagLValueReference",
+				Value:        0x800,
+				Capabilities: []string{},
+				Parameters:   []Parameter{},
+				Version:      "",
+			},
+			Enumerant{
+				Enumerant:    "FlagRValueReference",
+				Value:        0x1000,
+				Capabilities: []string{},
+				Parameters:   []Parameter{},
+				Version:      "",
+			},
+			Enumerant{
+				Enumerant:    "FlagIsOptimized",
+				Value:        0x2000,
+				Capabilities: []string{},
+				Parameters:   []Parameter{},
+				Version:      "",
+			},
+			Enumerant{
+				Enumerant:    "FlagIsEnumClass",
+				Value:        0x4000,
+				Capabilities: []string{},
+				Parameters:   []Parameter{},
+				Version:      "",
+			},
+			Enumerant{
+				Enumerant:    "FlagTypePassByValue",
+				Value:        0x8000,
+				Capabilities: []string{},
+				Parameters:   []Parameter{},
+				Version:      "",
+			},
+			Enumerant{
+				Enumerant:    "FlagTypePassByReference",
+				Value:        0x10000,
+				Capabilities: []string{},
+				Parameters:   []Parameter{},
+				Version:      "",
+			},
+		},
+		Bases:      []*OperandKind {},
+	}
+	OperandKindDebugBaseTypeAttributeEncoding = &OperandKind {
+		Kind:       "DebugBaseTypeAttributeEncoding",
+		Category:   "ValueEnum",
+		Enumerants: []Enumerant {
+			Enumerant{
+				Enumerant:    "Unspecified",
+				Value:        0,
+				Capabilities: []string{},
+				Parameters:   []Parameter{},
+				Version:      "",
+			},
+			Enumerant{
+				Enumerant:    "Address",
+				Value:        1,
+				Capabilities: []string{},
+				Parameters:   []Parameter{},
+				Version:      "",
+			},
+			Enumerant{
+				Enumerant:    "Boolean",
+				Value:        2,
+				Capabilities: []string{},
+				Parameters:   []Parameter{},
+				Version:      "",
+			},
+			Enumerant{
+				Enumerant:    "Float",
+				Value:        3,
+				Capabilities: []string{},
+				Parameters:   []Parameter{},
+				Version:      "",
+			},
+			Enumerant{
+				Enumerant:    "Signed",
+				Value:        4,
+				Capabilities: []string{},
+				Parameters:   []Parameter{},
+				Version:      "",
+			},
+			Enumerant{
+				Enumerant:    "SignedChar",
+				Value:        5,
+				Capabilities: []string{},
+				Parameters:   []Parameter{},
+				Version:      "",
+			},
+			Enumerant{
+				Enumerant:    "Unsigned",
+				Value:        6,
+				Capabilities: []string{},
+				Parameters:   []Parameter{},
+				Version:      "",
+			},
+			Enumerant{
+				Enumerant:    "UnsignedChar",
+				Value:        7,
+				Capabilities: []string{},
+				Parameters:   []Parameter{},
+				Version:      "",
+			},
+		},
+		Bases:      []*OperandKind {},
+	}
+	OperandKindDebugCompositeType = &OperandKind {
+		Kind:       "DebugCompositeType",
+		Category:   "ValueEnum",
+		Enumerants: []Enumerant {
+			Enumerant{
+				Enumerant:    "Class",
+				Value:        0,
+				Capabilities: []string{},
+				Parameters:   []Parameter{},
+				Version:      "",
+			},
+			Enumerant{
+				Enumerant:    "Structure",
+				Value:        1,
+				Capabilities: []string{},
+				Parameters:   []Parameter{},
+				Version:      "",
+			},
+			Enumerant{
+				Enumerant:    "Union",
+				Value:        2,
+				Capabilities: []string{},
+				Parameters:   []Parameter{},
+				Version:      "",
+			},
+		},
+		Bases:      []*OperandKind {},
+	}
+	OperandKindDebugTypeQualifier = &OperandKind {
+		Kind:       "DebugTypeQualifier",
+		Category:   "ValueEnum",
+		Enumerants: []Enumerant {
+			Enumerant{
+				Enumerant:    "ConstType",
+				Value:        0,
+				Capabilities: []string{},
+				Parameters:   []Parameter{},
+				Version:      "",
+			},
+			Enumerant{
+				Enumerant:    "VolatileType",
+				Value:        1,
+				Capabilities: []string{},
+				Parameters:   []Parameter{},
+				Version:      "",
+			},
+			Enumerant{
+				Enumerant:    "RestrictType",
+				Value:        2,
+				Capabilities: []string{},
+				Parameters:   []Parameter{},
+				Version:      "",
+			},
+			Enumerant{
+				Enumerant:    "AtomicType",
+				Value:        3,
+				Capabilities: []string{},
+				Parameters:   []Parameter{},
+				Version:      "",
+			},
+		},
+		Bases:      []*OperandKind {},
+	}
+	OperandKindDebugOperation = &OperandKind {
+		Kind:       "DebugOperation",
+		Category:   "ValueEnum",
+		Enumerants: []Enumerant {
+			Enumerant{
+				Enumerant:    "Deref",
+				Value:        0,
+				Capabilities: []string{},
+				Parameters:   []Parameter{},
+				Version:      "",
+			},
+			Enumerant{
+				Enumerant:    "Plus",
+				Value:        1,
+				Capabilities: []string{},
+				Parameters:   []Parameter{},
+				Version:      "",
+			},
+			Enumerant{
+				Enumerant:    "Minus",
+				Value:        2,
+				Capabilities: []string{},
+				Parameters:   []Parameter{},
+				Version:      "",
+			},
+			Enumerant{
+				Enumerant:    "PlusUconst",
+				Value:        3,
+				Capabilities: []string{},
+				Parameters:   []Parameter{{OperandKindLiteralInteger, ""},},
+				Version:      "",
+			},
+			Enumerant{
+				Enumerant:    "BitPiece",
+				Value:        4,
+				Capabilities: []string{},
+				Parameters:   []Parameter{{OperandKindLiteralInteger, ""},{OperandKindLiteralInteger, ""},},
+				Version:      "",
+			},
+			Enumerant{
+				Enumerant:    "Swap",
+				Value:        5,
+				Capabilities: []string{},
+				Parameters:   []Parameter{},
+				Version:      "",
+			},
+			Enumerant{
+				Enumerant:    "Xderef",
+				Value:        6,
+				Capabilities: []string{},
+				Parameters:   []Parameter{},
+				Version:      "",
+			},
+			Enumerant{
+				Enumerant:    "StackValue",
+				Value:        7,
+				Capabilities: []string{},
+				Parameters:   []Parameter{},
+				Version:      "",
+			},
+			Enumerant{
+				Enumerant:    "Constu",
+				Value:        8,
+				Capabilities: []string{},
+				Parameters:   []Parameter{{OperandKindLiteralInteger, ""},},
+				Version:      "",
+			},
+			Enumerant{
+				Enumerant:    "Fragment",
+				Value:        9,
+				Capabilities: []string{},
+				Parameters:   []Parameter{{OperandKindLiteralInteger, ""},{OperandKindLiteralInteger, ""},},
+				Version:      "",
+			},
+		},
+		Bases:      []*OperandKind {},
+	}
+	OperandKindDebugImportedEntity = &OperandKind {
+		Kind:       "DebugImportedEntity",
+		Category:   "ValueEnum",
+		Enumerants: []Enumerant {
+			Enumerant{
+				Enumerant:    "ImportedModule",
+				Value:        0,
+				Capabilities: []string{},
+				Parameters:   []Parameter{},
+				Version:      "",
+			},
+			Enumerant{
+				Enumerant:    "ImportedDeclaration",
+				Value:        1,
+				Capabilities: []string{},
+				Parameters:   []Parameter{},
+				Version:      "",
+			},
+		},
+		Bases:      []*OperandKind {},
+	}
 
 )
diff --git a/utils/vscode/src/schema/schema.go.tmpl b/utils/vscode/src/schema/schema.go.tmpl
index b584058..987fcd0 100644
--- a/utils/vscode/src/schema/schema.go.tmpl
+++ b/utils/vscode/src/schema/schema.go.tmpl
@@ -97,14 +97,26 @@
 	OperandCategoryComposite = "Composite"
 )
 
+// OpcodeMap is a map of opcode name to Opcode type.
+type OpcodeMap map[string]*Opcode
+
 var (
 	// Opcodes is a map of opcode name to Opcode description.
-	Opcodes = map[string]*Opcode {•{{range $i := .Instructions}}
-		"{{$i.Opname}}": {{$i.Opname}},{{end}}
+	Opcodes = OpcodeMap {•{{range $i := .SPIRV.Instructions}}
+		"{{$i.Opname}}": {{Title $i.Opname}},{{end}}
 	}
 
-{{range $i := .Instructions}}	{{$i.Opname}} = &Opcode {
+	// ExtOpcodes is a map of extension name to Opcode description list.
+	ExtOpcodes = map[string]OpcodeMap {•{{range $ext := .Extensions}}
+		"{{$ext.Name}}": {•{{range $i := $ext.Instructions}}
+			"{{$i.Opname}}": {{Global $ext.Name}}_{{$i.Opname}},{{end}}
+		},{{end}}
+	}
+
+{{range $i := .SPIRV.Instructions}}	{{Title $i.Opname}} = &Opcode {
 		Opname:   "{{$i.Opname}}",
+		Class:    "{{$i.Class}}",
+		Opcode:   {{$i.Opcode}},
 		Operands: []Operand {•{{range $i := $i.Operands}}
 			Operand {
 				Kind:       OperandKind{{$i.Kind}},
@@ -114,8 +126,19 @@
 		},
 	}
 {{end}}
+{{range $ext := .Extensions}}{{range $i := $ext.Instructions}}	{{Global $ext.Name}}_{{$i.Opname}} = &Opcode {
+		Opname:   "{{$i.Opname}}",
+		Operands: []Operand {•{{range $i := $i.Operands}}
+			Operand {
+				Kind:       OperandKind{{$i.Kind}},
+				Name:       "{{Replace $i.Name "\n" " "}}",
+				Quantifier: "{{$i.Quantifier}}",
+			}, {{end}}
+		},
+	}
+{{end}}{{end}}
 
-{{range $o := .OperandKinds}}	OperandKind{{$o.Kind}} = &OperandKind {
+{{range $o := .All.OperandKinds}}	OperandKind{{$o.Kind}} = &OperandKind {
 		Kind:       "{{$o.Kind}}",
 		Category:   "{{$o.Category}}",
 		Enumerants: []Enumerant {•{{range $e := $o.Enumerants}}
diff --git a/utils/vscode/src/tools/gen-grammar.go b/utils/vscode/src/tools/gen-grammar.go
index 42cbbe9..200f695 100644
--- a/utils/vscode/src/tools/gen-grammar.go
+++ b/utils/vscode/src/tools/gen-grammar.go
@@ -34,12 +34,30 @@
 	"../grammar"
 )
 
-const (
-	spirvGrammarURL  = "https://raw.githubusercontent.com/KhronosGroup/SPIRV-Headers/master/include/spirv/unified1/spirv.core.grammar.json"
-	spirvGrammarName = "spirv.core.grammar.json"
-)
+type grammarDefinition struct {
+	name string
+	url  string
+}
 
 var (
+	spirvGrammar = grammarDefinition{
+		name: "SPIR-V",
+		url:  "https://raw.githubusercontent.com/KhronosGroup/SPIRV-Headers/master/include/spirv/unified1/spirv.core.grammar.json",
+	}
+
+	extensionGrammars = []grammarDefinition{
+		{
+			name: "GLSL.std.450",
+			url:  "https://raw.githubusercontent.com/KhronosGroup/SPIRV-Headers/master/include/spirv/unified1/extinst.glsl.std.450.grammar.json",
+		}, {
+			name: "OpenCL.std",
+			url:  "https://raw.githubusercontent.com/KhronosGroup/SPIRV-Headers/master/include/spirv/unified1/extinst.opencl.std.100.grammar.json",
+		}, {
+			name: "OpenCL.DebugInfo.100",
+			url:  "https://raw.githubusercontent.com/KhronosGroup/SPIRV-Tools/master/source/extinst.opencl.debuginfo.100.grammar.json",
+		},
+	}
+
 	templatePath = flag.String("template", "", "Path to input template file (required)")
 	outputPath   = flag.String("out", "", "Path to output generated file (required)")
 	cachePath    = flag.String("cache", "", "Cache directory for downloaded files (optional)")
@@ -67,6 +85,34 @@
 	if err != nil {
 		return errors.Wrap(err, "Could not open template file")
 	}
+
+	type extension struct {
+		grammar.Root
+		Name string
+	}
+
+	args := struct {
+		SPIRV      grammar.Root
+		Extensions []extension
+		All        grammar.Root // Combination of SPIRV + Extensions
+	}{}
+
+	if args.SPIRV, err = parseGrammar(spirvGrammar); err != nil {
+		return errors.Wrap(err, "Failed to parse SPIR-V grammar file")
+	}
+	args.All.Instructions = append(args.All.Instructions, args.SPIRV.Instructions...)
+	args.All.OperandKinds = append(args.All.OperandKinds, args.SPIRV.OperandKinds...)
+
+	for _, ext := range extensionGrammars {
+		root, err := parseGrammar(ext)
+		if err != nil {
+			return errors.Wrap(err, "Failed to parse extension grammar file")
+		}
+		args.Extensions = append(args.Extensions, extension{Root: root, Name: ext.name})
+		args.All.Instructions = append(args.All.Instructions, root.Instructions...)
+		args.All.OperandKinds = append(args.All.OperandKinds, root.OperandKinds...)
+	}
+
 	t, err := template.New("tmpl").
 		Funcs(template.FuncMap{
 			"GenerateArguments": func() string {
@@ -96,24 +142,30 @@
 				}
 				return sb.String()
 			},
+			"AllExtOpcodes": func() string {
+				sb := strings.Builder{}
+				for _, ext := range args.Extensions {
+					for _, inst := range ext.Root.Instructions {
+						if sb.Len() > 0 {
+							sb.WriteString("|")
+						}
+						sb.WriteString(inst.Opname)
+					}
+				}
+				return sb.String()
+			},
+			"Title":   strings.Title,
 			"Replace": strings.ReplaceAll,
+			"Global": func(s string) string {
+				return strings.ReplaceAll(strings.Title(s), ".", "")
+			},
 		}).Parse(string(tf))
 	if err != nil {
 		return errors.Wrap(err, "Failed to parse template")
 	}
 
-	file, err := getOrDownload(spirvGrammarName, spirvGrammarURL)
-	if err != nil {
-		return errors.Wrap(err, "Failed to load grammar file")
-	}
-
-	g := grammar.Root{}
-	if err := json.NewDecoder(bytes.NewReader(file)).Decode(&g); err != nil {
-		return errors.Wrap(err, "Failed to parse grammar file")
-	}
-
 	buf := bytes.Buffer{}
-	if err := t.Execute(&buf, g); err != nil {
+	if err := t.Execute(&buf, args); err != nil {
 		return errors.Wrap(err, "Failed to execute template")
 	}
 
@@ -127,6 +179,22 @@
 	return nil
 }
 
+// parseGrammar downloads (or loads from the cache) the grammar file and returns
+// the parsed grammar.Root.
+func parseGrammar(def grammarDefinition) (grammar.Root, error) {
+	file, err := getOrDownload(def.name, def.url)
+	if err != nil {
+		return grammar.Root{}, errors.Wrap(err, "Failed to load grammar file")
+	}
+
+	g := grammar.Root{}
+	if err := json.NewDecoder(bytes.NewReader(file)).Decode(&g); err != nil {
+		return grammar.Root{}, errors.Wrap(err, "Failed to parse grammar file")
+	}
+
+	return g, nil
+}
+
 // getOrDownload loads the specific file from the cache, or downloads the file
 // from the given url.
 func getOrDownload(name, url string) ([]byte, error) {