Merge commit 'f2803c4a7f58237aa0dd9d39ccc6dea362527b96' into vulkan-cts

Change-Id: Icc934dee248beb972ba7e8084a6ad0c3fa2897ce
diff --git a/.appveyor.yml b/.appveyor.yml
index a50c7c2..669de03 100644
--- a/.appveyor.yml
+++ b/.appveyor.yml
@@ -42,7 +42,7 @@
   - set NINJA_URL="https://github.com/ninja-build/ninja/releases/download/v1.8.2/ninja-win.zip"
   - appveyor DownloadFile %NINJA_URL% -FileName ninja.zip
   - 7z x ninja.zip -oC:\ninja > nul
-  - set PATH=C:\ninja;%PATH%
+  - set PATH=C:\ninja;C:\Python36;%PATH%
 
 before_build:
   - git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Headers.git external/spirv-headers
diff --git a/.gitignore b/.gitignore
index 059b18e..e097bab 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,5 +21,5 @@
 *~
 
 # C-Lion
-.idea
-cmake-build-debug
\ No newline at end of file
+/.idea/
+/cmake-build-*/
diff --git a/Android.mk b/Android.mk
index 8597c50..7c6a076 100644
--- a/Android.mk
+++ b/Android.mk
@@ -76,10 +76,12 @@
 		source/opt/aggressive_dead_code_elim_pass.cpp \
 		source/opt/basic_block.cpp \
 		source/opt/block_merge_pass.cpp \
+		source/opt/block_merge_util.cpp \
 		source/opt/build_module.cpp \
 		source/opt/cfg.cpp \
 		source/opt/cfg_cleanup_pass.cpp \
 		source/opt/ccp_pass.cpp \
+		source/opt/code_sink.cpp \
 		source/opt/combine_access_chains.cpp \
 		source/opt/common_uniform_elim_pass.cpp \
 		source/opt/compact_ids_pass.cpp \
@@ -90,19 +92,24 @@
 		source/opt/dead_branch_elim_pass.cpp \
 		source/opt/dead_insert_elim_pass.cpp \
 		source/opt/dead_variable_elimination.cpp \
+		source/opt/decompose_initialized_variables_pass.cpp \
 		source/opt/decoration_manager.cpp \
 		source/opt/def_use_manager.cpp \
 		source/opt/dominator_analysis.cpp \
 		source/opt/dominator_tree.cpp \
 		source/opt/eliminate_dead_constant_pass.cpp \
 		source/opt/eliminate_dead_functions_pass.cpp \
+		source/opt/eliminate_dead_functions_util.cpp \
+		source/opt/eliminate_dead_members_pass.cpp \
 		source/opt/feature_manager.cpp \
+		source/opt/fix_storage_class.cpp \
 		source/opt/flatten_decoration_pass.cpp \
 		source/opt/fold.cpp \
 		source/opt/folding_rules.cpp \
 		source/opt/fold_spec_constant_op_and_composite_pass.cpp \
 		source/opt/freeze_spec_constant_value_pass.cpp \
 		source/opt/function.cpp \
+		source/opt/generate_webgpu_initializers_pass.cpp \
 		source/opt/if_conversion.cpp \
 		source/opt/inline_pass.cpp \
 		source/opt/inline_exhaustive_pass.cpp \
@@ -113,6 +120,7 @@
 		source/opt/instrument_pass.cpp \
 		source/opt/ir_context.cpp \
 		source/opt/ir_loader.cpp \
+                source/opt/legalize_vector_shuffle_pass.cpp \
 		source/opt/licm_pass.cpp \
 		source/opt/local_access_chain_convert_pass.cpp \
 		source/opt/local_redundancy_elimination.cpp \
@@ -150,6 +158,7 @@
 		source/opt/simplification_pass.cpp \
 		source/opt/ssa_rewrite_pass.cpp \
 		source/opt/strength_reduction_pass.cpp \
+		source/opt/strip_atomic_counter_memory_pass.cpp \
 		source/opt/strip_debug_info_pass.cpp \
 		source/opt/strip_reflect_info_pass.cpp \
 		source/opt/struct_cfg_analysis.cpp \
diff --git a/BUILD.gn b/BUILD.gn
index 5f0eba9..6c28118 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -305,7 +305,6 @@
 
 static_library("spvtools") {
   deps = [
-    ":spvtools_core_enums_unified1",
     ":spvtools_core_tables_unified1",
     ":spvtools_generators_inc",
     ":spvtools_glsl_tables_glsl1-0",
@@ -376,6 +375,7 @@
   ]
 
   public_deps = [
+    ":spvtools_core_enums_unified1",
     ":spvtools_headers",
   ]
 
@@ -452,6 +452,8 @@
     "source/opt/basic_block.h",
     "source/opt/block_merge_pass.cpp",
     "source/opt/block_merge_pass.h",
+    "source/opt/block_merge_util.cpp",
+    "source/opt/block_merge_util.h",
     "source/opt/build_module.cpp",
     "source/opt/build_module.h",
     "source/opt/ccp_pass.cpp",
@@ -460,6 +462,8 @@
     "source/opt/cfg.h",
     "source/opt/cfg_cleanup_pass.cpp",
     "source/opt/cfg_cleanup_pass.h",
+    "source/opt/code_sink.cpp",
+    "source/opt/code_sink.h",
     "source/opt/combine_access_chains.cpp",
     "source/opt/combine_access_chains.h",
     "source/opt/common_uniform_elim_pass.cpp",
@@ -480,6 +484,8 @@
     "source/opt/dead_insert_elim_pass.h",
     "source/opt/dead_variable_elimination.cpp",
     "source/opt/dead_variable_elimination.h",
+    "source/opt/decompose_initialized_variables_pass.cpp",
+    "source/opt/decompose_initialized_variables_pass.h",
     "source/opt/decoration_manager.cpp",
     "source/opt/decoration_manager.h",
     "source/opt/def_use_manager.cpp",
@@ -492,8 +498,14 @@
     "source/opt/eliminate_dead_constant_pass.h",
     "source/opt/eliminate_dead_functions_pass.cpp",
     "source/opt/eliminate_dead_functions_pass.h",
+    "source/opt/eliminate_dead_functions_util.cpp",
+    "source/opt/eliminate_dead_functions_util.h",
+    "source/opt/eliminate_dead_members_pass.cpp",
+    "source/opt/eliminate_dead_members_pass.h",
     "source/opt/feature_manager.cpp",
     "source/opt/feature_manager.h",
+    "source/opt/fix_storage_class.cpp",
+    "source/opt/fix_storage_class.h",
     "source/opt/flatten_decoration_pass.cpp",
     "source/opt/flatten_decoration_pass.h",
     "source/opt/fold.cpp",
@@ -506,6 +518,8 @@
     "source/opt/freeze_spec_constant_value_pass.h",
     "source/opt/function.cpp",
     "source/opt/function.h",
+    "source/opt/generate_webgpu_initializers_pass.cpp",
+    "source/opt/generate_webgpu_initializers_pass.h",
     "source/opt/if_conversion.cpp",
     "source/opt/if_conversion.h",
     "source/opt/inline_exhaustive_pass.cpp",
@@ -528,6 +542,8 @@
     "source/opt/ir_loader.cpp",
     "source/opt/ir_loader.h",
     "source/opt/iterator.h",
+    "source/opt/legalize_vector_shuffle_pass.cpp",
+    "source/opt/legalize_vector_shuffle_pass.h",
     "source/opt/licm_pass.cpp",
     "source/opt/licm_pass.h",
     "source/opt/local_access_chain_convert_pass.cpp",
@@ -604,6 +620,8 @@
     "source/opt/ssa_rewrite_pass.h",
     "source/opt/strength_reduction_pass.cpp",
     "source/opt/strength_reduction_pass.h",
+    "source/opt/strip_atomic_counter_memory_pass.cpp",
+    "source/opt/strip_atomic_counter_memory_pass.h",
     "source/opt/strip_debug_info_pass.cpp",
     "source/opt/strip_debug_info_pass.h",
     "source/opt/strip_reflect_info_pass.cpp",
@@ -649,132 +667,85 @@
   ]
 }
 
-if (!build_with_chromium) {
-  googletest_dir = spirv_tools_googletest_dir
-
-  config("gtest_config") {
-    include_dirs = [
-      "${googletest_dir}/googletest",
-      "${googletest_dir}/googletest/include",
-    ]
-  }
-
-  static_library("gtest") {
-    testonly = true
+# The tests are scoped to Chromium to avoid needing to write gtest integration.
+# See Chromium's third_party/googletest/BUILD.gn for a complete integration.
+if (build_with_chromium) {
+  test("spvtools_test") {
     sources = [
-      "${googletest_dir}/googletest/src/gtest-all.cc",
+      "test/assembly_context_test.cpp",
+      "test/assembly_format_test.cpp",
+      "test/binary_destroy_test.cpp",
+      "test/binary_endianness_test.cpp",
+      "test/binary_header_get_test.cpp",
+      "test/binary_parse_test.cpp",
+      "test/binary_strnlen_s_test.cpp",
+      "test/binary_to_text.literal_test.cpp",
+      "test/binary_to_text_test.cpp",
+      "test/comment_test.cpp",
+      "test/enum_set_test.cpp",
+      "test/enum_string_mapping_test.cpp",
+      "test/ext_inst.debuginfo_test.cpp",
+      "test/ext_inst.glsl_test.cpp",
+      "test/ext_inst.opencl_test.cpp",
+      "test/fix_word_test.cpp",
+      "test/generator_magic_number_test.cpp",
+      "test/hex_float_test.cpp",
+      "test/immediate_int_test.cpp",
+      "test/libspirv_macros_test.cpp",
+      "test/name_mapper_test.cpp",
+      "test/named_id_test.cpp",
+      "test/opcode_make_test.cpp",
+      "test/opcode_require_capabilities_test.cpp",
+      "test/opcode_split_test.cpp",
+      "test/opcode_table_get_test.cpp",
+      "test/operand_capabilities_test.cpp",
+      "test/operand_pattern_test.cpp",
+      "test/operand_test.cpp",
+      "test/target_env_test.cpp",
+      "test/test_fixture.h",
+      "test/text_advance_test.cpp",
+      "test/text_destroy_test.cpp",
+      "test/text_literal_test.cpp",
+      "test/text_start_new_inst_test.cpp",
+      "test/text_to_binary.annotation_test.cpp",
+      "test/text_to_binary.barrier_test.cpp",
+      "test/text_to_binary.constant_test.cpp",
+      "test/text_to_binary.control_flow_test.cpp",
+      "test/text_to_binary.debug_test.cpp",
+      "test/text_to_binary.device_side_enqueue_test.cpp",
+      "test/text_to_binary.extension_test.cpp",
+      "test/text_to_binary.function_test.cpp",
+      "test/text_to_binary.group_test.cpp",
+      "test/text_to_binary.image_test.cpp",
+      "test/text_to_binary.literal_test.cpp",
+      "test/text_to_binary.memory_test.cpp",
+      "test/text_to_binary.misc_test.cpp",
+      "test/text_to_binary.mode_setting_test.cpp",
+      "test/text_to_binary.pipe_storage_test.cpp",
+      "test/text_to_binary.reserved_sampling_test.cpp",
+      "test/text_to_binary.subgroup_dispatch_test.cpp",
+      "test/text_to_binary.type_declaration_test.cpp",
+      "test/text_to_binary_test.cpp",
+      "test/text_word_get_test.cpp",
+      "test/unit_spirv.cpp",
+      "test/unit_spirv.h",
     ]
-    public_configs = [ ":gtest_config" ]
-  }
 
-  config("gmock_config") {
-    include_dirs = [
-      "${googletest_dir}/googlemock",
-      "${googletest_dir}/googlemock/include",
-      "${googletest_dir}/googletest/include",
-    ]
-    if (is_clang) {
-      # TODO: Can remove this if/when the issue is fixed.
-      # https://github.com/google/googletest/issues/533
-      cflags = [ "-Wno-inconsistent-missing-override" ]
-    }
-  }
-
-  static_library("gmock") {
-    testonly = true
-    sources = [
-      "${googletest_dir}/googlemock/src/gmock-all.cc",
-    ]
-    public_configs = [ ":gmock_config" ]
-  }
-}
-
-test("spvtools_test") {
-  sources = [
-    "test/assembly_context_test.cpp",
-    "test/assembly_format_test.cpp",
-    "test/binary_destroy_test.cpp",
-    "test/binary_endianness_test.cpp",
-    "test/binary_header_get_test.cpp",
-    "test/binary_parse_test.cpp",
-    "test/binary_strnlen_s_test.cpp",
-    "test/binary_to_text.literal_test.cpp",
-    "test/binary_to_text_test.cpp",
-    "test/comment_test.cpp",
-    "test/enum_set_test.cpp",
-    "test/enum_string_mapping_test.cpp",
-    "test/ext_inst.debuginfo_test.cpp",
-    "test/ext_inst.glsl_test.cpp",
-    "test/ext_inst.opencl_test.cpp",
-    "test/fix_word_test.cpp",
-    "test/generator_magic_number_test.cpp",
-    "test/hex_float_test.cpp",
-    "test/immediate_int_test.cpp",
-    "test/libspirv_macros_test.cpp",
-    "test/name_mapper_test.cpp",
-    "test/named_id_test.cpp",
-    "test/opcode_make_test.cpp",
-    "test/opcode_require_capabilities_test.cpp",
-    "test/opcode_split_test.cpp",
-    "test/opcode_table_get_test.cpp",
-    "test/operand_capabilities_test.cpp",
-    "test/operand_pattern_test.cpp",
-    "test/operand_test.cpp",
-    "test/target_env_test.cpp",
-    "test/test_fixture.h",
-    "test/text_advance_test.cpp",
-    "test/text_destroy_test.cpp",
-    "test/text_literal_test.cpp",
-    "test/text_start_new_inst_test.cpp",
-    "test/text_to_binary.annotation_test.cpp",
-    "test/text_to_binary.barrier_test.cpp",
-    "test/text_to_binary.constant_test.cpp",
-    "test/text_to_binary.control_flow_test.cpp",
-    "test/text_to_binary.debug_test.cpp",
-    "test/text_to_binary.device_side_enqueue_test.cpp",
-    "test/text_to_binary.extension_test.cpp",
-    "test/text_to_binary.function_test.cpp",
-    "test/text_to_binary.group_test.cpp",
-    "test/text_to_binary.image_test.cpp",
-    "test/text_to_binary.literal_test.cpp",
-    "test/text_to_binary.memory_test.cpp",
-    "test/text_to_binary.misc_test.cpp",
-    "test/text_to_binary.mode_setting_test.cpp",
-    "test/text_to_binary.pipe_storage_test.cpp",
-    "test/text_to_binary.reserved_sampling_test.cpp",
-    "test/text_to_binary.subgroup_dispatch_test.cpp",
-    "test/text_to_binary.type_declaration_test.cpp",
-    "test/text_to_binary_test.cpp",
-    "test/text_word_get_test.cpp",
-    "test/unit_spirv.cpp",
-    "test/unit_spirv.h",
-  ]
-
-  deps = [
-    ":spvtools",
-    ":spvtools_language_header_unified1",
-    ":spvtools_val",
-  ]
-
-  if (build_with_chromium) {
-    deps += [
+    deps = [
       "//testing/gmock",
       "//testing/gtest",
       "//testing/gtest:gtest_main",
+      ":spvtools",
+      ":spvtools_language_header_unified1",
+      ":spvtools_val",
     ]
-  } else {
-    deps += [
-      ":gmock",
-      ":gtest",
-    ]
-    sources += [ "${googletest_dir}/googletest/src/gtest_main.cc" ]
-  }
 
-  if (is_clang) {
-    cflags_cc = [ "-Wno-self-assign" ]
-  }
+    if (is_clang) {
+      cflags_cc = [ "-Wno-self-assign" ]
+    }
 
-  configs += [ ":spvtools_internal_config" ]
+    configs += [ ":spvtools_internal_config" ]
+  }
 }
 
 if (spirv_tools_standalone) {
diff --git a/CHANGES b/CHANGES
index 7f9008f..bfc70e5 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,20 +1,116 @@
 Revision history for SPIRV-Tools
 
-v2018.7-dev 2018-12-10
+v2019.3-dev 2019-04-03
+ - General:
+   - Updated Python scripts to work for both Python 2 and Python 3.
+   - Add a continuous test that does memory checks using the address sanitizer.
+ - Optimizer
+   - Remove duplicates from list of interface IDs in OpEntryPoint instruction (#2449)
+   - Bindless Validation: Descriptor Initialization Check (#2419)
+   - Add option to validate after each pass (#2462)
+   Fixes:
+   - #2412: Dead memeber elimination should not change input and output variables.
+   - #2405: Fix OpDot folding of half float vectors.
+   - #2391: Dead branch elim should not fold away back edges.
+   - #2441: Removing decorations when doing constant propagation.
+   - #2455: Maintain inst to block mapping in merge return.
+   - #2453: Fix merge return in the face of breaks.
+   - #2456: Handle dead infinite loops in DCE.
+   - #2458: Handle variable pointer in some optimizations.
+   - #2452: Fix dead branch elimination to handle unreachable blocks better.
+ - Validator
+   - Add validation of storage classes for WebGPU (#2446)
+   - Add validation for ExecutionMode in WebGPU (#2443)
+   - Implement WebGPU specific CFG validation (#2386)
+   - Allow NonWritable to target struct members. (#2420)
+   - Allow storage type mismatch for parameter in relaxed addressing mode.
+   - Allow non memory objects as parameter in relaxed addressing mode.
+   - Disallow nested Blocks and buffer blocks (#2410).
+   - Add validation for SPV_NV_cooperative_matrix (#2404)
+   - Add --strip-atomic-counter-memory (#2413)
+   - Check OpSampledImage is only passed into valid instructions (#2467)
+   - Handle function decls in Structured CFG analysis (#2474)
+   - Validate that OpUnreacahble is not statically reachable (#2473)
+   - Add pass to generate needed initializers for WebGPU (#2481)
+   Fixes:
+   - #2439: Add missing DepthGreater case to Fragment only check.
+   - #2168: Disallow BufferBlock on StorageBuffer variables for Vulkan.
+   - #2408: Restrict and Aliased decorations cannot be applied to the same id.
+   - #2447: Improve function call parameter check.
+ - Reduce
+   - Add Pass to remove unreferenced blocks. (#2398)
+   - Allows passing options to the validator. (#2401)
+   - Improve reducer algorithm and other changes (#2472)
+   - Add Pass to remove selections (#2485)
+   Fixes:
+   - #2478: fix loop to selection pass for loops with combined header/continue block
+
+v2019.2 2019-02-20
+ - General:
+   - Support SPV_EXT_physical_storage_buffer
+   - A number of memory leak have been fixed.
+   - Removed use of deprecated Google test macro:
+   - Changed the BUILD.gn to only build tests in Chromium.
+ - Optimizer
+   - Upgrade memory model improvments for modf and frexp.
+   - Add a new pass to move loads closer to their uses: code sinking.
+   - Invalidating the type manager now invalidates the constnat manager.
+   - Expand instrumentation pass for bindless bounds checking to runtime-sized descriptor arrays.
+   - Add a new pass that removes members from structs that are not used: dead member elimination.
+   Fixes:
+   - #2292: Remove undefined behaviour when folding bit shifts.
+   - #2294: Fixes for instrumentation code.
+   - #2293: Fix overflow when folding -INT_MIN.
+   - #2374: Don't merge unreachable blocks when merging blocks.
+ - Validator
+   - Support SPV_KHR_no_integer_wrap and related decorations.
+   - Validate Vulkan rules for OpTypeRuntimeArray.
+   - Validate NonWritable decoration.
+   - Many WebGPU specific validation rules were added.
+   - Validate variable pointer related function call rules.
+   - Better error messages.
+   Fixes:
+   - #2307: Check forwards references in OpTypeArray.
+   - #2315, #2303: Fixed the layout check for relaxed layout.
+   - #1628: Emit an error when an OpSwitch target is not an OpLabel.
+ - Reduce
+   - Added more documentation for spirv-reduce.
+   - Add ability to remove OpPhi instructions.
+   - Add ability to merge two basic blocks.
+   - Add ability to remove unused functions and unused basic blocks.
+   Fixes:
+
+v2019.1 2019-01-07
  - General:
    - Created a new tool called spirv-reduce.
    - Add cmake option to turn off SPIRV_TIMER_ENABLED (#2103)
    - New optimization pass to update the memory model from GLSL450 to VulkanKHR.
+   - Recognize OpTypeAccelerationStructureNV as a type instruction and ray tracing storage classes.
+   - Fix GCC8 build.
+   - Add --target-env flag to spirv-opt.
+   - Add --webgpu-mode flag to run optimizations for webgpu.
+   - The output disassembled line number stead of byte offset in validation errors. (#2091)
  - Optimizer
    - Added the instrumentation passes for bindless validation.
    - Added passes to help preserve OpLine information (#2027)
    - Add basic support for EXT_fragment_invocation_density (#2100)
    - Fix invalid OpPhi generated by merge-return. (#2172)
+   - Constant and type manager have been turned into analysies. (#2251)
    Fixes:
    - #2018: Don't inline functions with a return in a structured CFG contstruct.
    - #2047: Fix bug in folding when volatile stores are present.
    - #2053: Fix check for when folding floating pointer values is allowed.
    - #2130: Don't inline recursive functions.
+   - #2202: Handle multiple edges between two basic blocks in SSA-rewriter.
+   - #2205: Don't unswitch a latch condition during loop unswitch.
+   - #2245: Don't fold branch in loop unswitch.  Run dead branch elimination to fold them.
+   - #2204: Fix eliminate common uniform to place OpPhi instructions correctly.
+   - #2247: Fix type mismatches caused by scalar replacement.
+   - #2248: Fix missing OpPhi after merge return.
+   - #2211: After merge return, fix invalid continue target.
+   - #2210: Fix loop invariant code motion to not place code between merge instruction and branch.
+   - #2258: Handle CompositeInsert with no indices in VDCE.
+   - #2261: Have replace load size handle extact with no index.
  - Validator
    - Changed the naming convention of outputing ids with names in diagnostic messages.
    - Added validation rules for UniformConstant variables in Vulkan.
@@ -32,12 +128,14 @@
    - Allow Float16/Int8 for Vulkan 1.0 (#2153)
    - Check binding annotations in resource variables (#2151, #2167)
    - Validate OpForwardPointer (#2156)
+   - Validate operation for OpSpecConstantOp (#2260)
    Fixes:
    - #2049: Allow InstanceId for NV ray tracing
  - Reduce
    - Initial commit wit a few passes to reduce test cases.
+   - Validation is run after each reduction step.
    Fixes:
- 
+
 
 v2018.6 2018-11-07
  - General:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index df0dd8d..f23f154 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -142,6 +142,8 @@
       if(NOT "${SPIRV_USE_SANITIZER}" STREQUAL "")
         target_compile_options(${TARGET} PRIVATE
           -fsanitize=${SPIRV_USE_SANITIZER})
+        set_target_properties(${TARGET} PROPERTIES
+          LINK_FLAGS -fsanitize=${SPIRV_USE_SANITIZER})
       endif()
       target_compile_options(${TARGET} PRIVATE
          -ftemplate-depth=1024)
@@ -178,7 +180,8 @@
   endmacro()
 endif()
 
-find_host_package(PythonInterp)
+# Tests require Python3
+find_host_package(PythonInterp 3 REQUIRED)
 
 # Check for symbol exports on Linux.
 # At the moment, this check will fail on the OSX build machines for the Android NDK.
diff --git a/DEPS b/DEPS
index f7bac6d..3c5361f 100644
--- a/DEPS
+++ b/DEPS
@@ -11,7 +11,7 @@
   'googletest_revision': '98a0d007d7092b72eea0e501bb9ad17908a1a036',
   'testing_revision': '340252637e2e7c72c0901dcbeeacfff419e19b59',
   're2_revision': '6cf8ccd82dbaab2668e9b13596c68183c9ecd13f',
-  'spirv_headers_revision': 'd5b2e1255f706ce1f88812217e9a554f299848af',
+  'spirv_headers_revision': 'e74c389f81915d0a48d6df1af83c3862c5ad85ab',
 }
 
 deps = {
diff --git a/README.md b/README.md
index 534664f..edc5eca 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,5 @@
 # SPIR-V Tools
 
-[![Build status](https://ci.appveyor.com/api/projects/status/gpue87cesrx3pi0d/branch/master?svg=true)](https://ci.appveyor.com/project/Khronoswebmaster/spirv-tools/branch/master)
-<img alt="Linux" src="kokoro/img/linux.png" width="20px" height="20px" hspace="2px"/>![Linux Build Status](https://storage.googleapis.com/spirv-tools/badges/build_status_linux_release.svg)
-<img alt="MacOS" src="kokoro/img/macos.png" width="20px" height="20px" hspace="2px"/>![MacOS Build Status](https://storage.googleapis.com/spirv-tools/badges/build_status_macos_release.svg)
-<img alt="Windows" src="kokoro/img/windows.png" width="20px" height="20px" hspace="2px"/>![Windows Build Status](https://storage.googleapis.com/spirv-tools/badges/build_status_windows_release.svg)
-
 ## Overview
 
 The SPIR-V Tools project provides an API and commands for processing SPIR-V
@@ -24,6 +19,15 @@
 See the [SPIR-V Registry][spirv-registry] for the SPIR-V specification,
 headers, and XML registry.
 
+## Downloads
+
+[![Build status](https://ci.appveyor.com/api/projects/status/gpue87cesrx3pi0d/branch/master?svg=true)](https://ci.appveyor.com/project/Khronoswebmaster/spirv-tools/branch/master)
+<img alt="Linux" src="kokoro/img/linux.png" width="20px" height="20px" hspace="2px"/>[![Linux Build Status](https://storage.googleapis.com/spirv-tools/badges/build_status_linux_clang_release.svg)](https://storage.googleapis.com/spirv-tools/badges/build_link_linux_clang_release.html)
+<img alt="MacOS" src="kokoro/img/macos.png" width="20px" height="20px" hspace="2px"/>[![MacOS Build Status](https://storage.googleapis.com/spirv-tools/badges/build_status_macos_clang_release.svg)](https://storage.googleapis.com/spirv-tools/badges/build_link_macos_clang_release.html)
+<img alt="Windows" src="kokoro/img/windows.png" width="20px" height="20px" hspace="2px"/>[![Windows Build Status](https://storage.googleapis.com/spirv-tools/badges/build_status_windows_release.svg)](https://storage.googleapis.com/spirv-tools/badges/build_link_windows_vs2017_release.html)
+
+[More downloads](downloads.md)
+
 ## Versioning SPIRV-Tools
 
 See [`CHANGES`](CHANGES) for a high level summary of recent changes, by version.
@@ -47,7 +51,7 @@
 
 * Support for SPIR-V 1.0, 1.1, 1.2, and 1.3
   * Based on SPIR-V syntax described by JSON grammar files in the
-    [SPIRV-Headers](spirv-headers) repository.
+    [SPIRV-Headers](https://github.com/KhronosGroup/SPIRV-Headers) repository.
 * Support for extended instruction sets:
   * GLSL std450 version 1.0 Rev 3
   * OpenCL version 1.0 Rev 2
@@ -128,6 +132,23 @@
 sub-project](https://github.com/KhronosGroup/SPIRV-Tools/projects/2) for
 planned and in-progress work.
 
+
+### Reducer
+
+*Note:* The reducer is still under development.
+
+The reducer simplifies and shrinks a SPIR-V module with respect to a
+user-supplied *interestingness function*.  For example, given a large
+SPIR-V module that cause some SPIR-V compiler to fail with a given
+fatal error message, the reducer could be used to look for a smaller
+version of the module that causes the compiler to fail with the same
+fatal error message.
+
+To suggest an additional capability for the reducer, [file an
+issue](https://github.com/KhronosGroup/SPIRV-Tools/issues]) with
+"Reducer:" as the start of its title.
+
+
 ### Extras
 
 * [Utility filters](#utility-filters)
@@ -252,6 +273,31 @@
 Once the build files have been generated, build using your preferred
 development environment.
 
+### Tools you'll need
+
+For building and testing SPIRV-Tools, the following tools should be
+installed regardless of your OS:
+
+- [CMake](http://www.cmake.org/): for generating compilation targets.  Version
+  2.8.12 or later.
+- [Python 3](http://www.python.org/): for utility scripts and running the test
+suite.
+
+SPIRV-Tools is regularly tested with the the following compilers:
+
+On Linux
+- GCC version 4.8.5
+- Clang version 3.8
+
+On MacOS
+- AppleClang 10.0
+
+On Windows
+- Visual Studio 2015
+- Visual Studio 2017
+
+Other compilers or later versions may work, but they are not tested.
+
 ### CMake options
 
 The following CMake options are supported:
@@ -415,6 +461,18 @@
 * `spirv-val` - the standalone validator
   * `<spirv-dir>/tools/val`
 
+### Reducer tool
+
+The reducer shrinks a SPIR-V binary module, guided by a user-supplied
+*interestingness test*.
+
+This is a work in progress, with initially only shrinks a module in a few ways.
+
+* `spirv-reduce` - the standalone reducer
+  * `<spirv-dir>/tools/reduce`
+
+Run `spirv-reduce --help` to see how to specify interestingness.
+
 ### Control flow dumper tool
 
 The control flow dumper prints the control flow graph for a SPIR-V module as a
diff --git a/downloads.md b/downloads.md
new file mode 100644
index 0000000..9c7d856
--- /dev/null
+++ b/downloads.md
@@ -0,0 +1,14 @@
+# Downloads
+Download the latest builds.
+
+## Release
+| Windows | Linux | MacOS |
+| --- | --- | --- |
+| [MSVC 2017](https://storage.googleapis.com/spirv-tools/badges/build_link_windows_vs2017_release.html) | [clang](https://storage.googleapis.com/spirv-tools/badges/build_link_linux_clang_release.html) | [clang](https://storage.googleapis.com/spirv-tools/badges/build_link_macos_clang_release.html) |
+| | [gcc](https://storage.googleapis.com/spirv-tools/badges/build_link_linux_gcc_release.html) | |
+
+## Debug
+| Windows | Linux | MacOS |
+| --- | --- | --- |
+| [MSVC 2017](https://storage.googleapis.com/spirv-tools/badges/build_link_windows_vs2017_debug.html) | [clang](https://storage.googleapis.com/spirv-tools/badges/build_link_linux_clang_debug.html) | [clang](https://storage.googleapis.com/spirv-tools/badges/build_link_macos_clang_debug.html) |
+| | [gcc](https://storage.googleapis.com/spirv-tools/badges/build_link_linux_gcc_debug.html) | |
diff --git a/examples/cpp-interface/main.cpp b/examples/cpp-interface/main.cpp
index c5354b8..a1e22c7 100644
--- a/examples/cpp-interface/main.cpp
+++ b/examples/cpp-interface/main.cpp
@@ -28,6 +28,7 @@
 
 int main() {
   const std::string source =
+      "         OpCapability Linkage "
       "         OpCapability Shader "
       "         OpMemoryModel Logical GLSL450 "
       "         OpSource GLSL 450 "
@@ -36,8 +37,8 @@
       " %spec = OpSpecConstant %int 0 "
       "%const = OpConstant %int 42";
 
-  spvtools::SpirvTools core(SPV_ENV_VULKAN_1_0);
-  spvtools::Optimizer opt(SPV_ENV_VULKAN_1_0);
+  spvtools::SpirvTools core(SPV_ENV_UNIVERSAL_1_3);
+  spvtools::Optimizer opt(SPV_ENV_UNIVERSAL_1_3);
 
   auto print_msg_to_stderr = [](spv_message_level_t, const char*,
                                 const spv_position_t&, const char* m) {
diff --git a/include/spirv-tools/instrument.hpp b/include/spirv-tools/instrument.hpp
index 69d1ad2..9711b92 100644
--- a/include/spirv-tools/instrument.hpp
+++ b/include/spirv-tools/instrument.hpp
@@ -30,17 +30,17 @@
 
 // Stream Output Buffer Offsets
 //
-// The following values provide 32-bit word offsets into the output buffer
+// The following values provide offsets into the output buffer struct
 // generated by InstrumentPass::GenDebugStreamWrite. This method is utilized
 // by InstBindlessCheckPass.
 //
-// The first word of the debug output buffer contains the next available word
+// The first member of the debug output buffer contains the next available word
 // in the data stream to be written. Shaders will atomically read and update
 // this value so as not to overwrite each others records. This value must be
 // initialized to zero
 static const int kDebugOutputSizeOffset = 0;
 
-// The second word of the output buffer is the start of the stream of records
+// The second member of the output buffer is the start of the stream of records
 // written by the instrumented shaders. Each record represents a validation
 // error. The format of the records is documented below.
 static const int kDebugOutputDataOffset = 1;
@@ -75,8 +75,8 @@
 // error.
 //
 // Vertex Shader Output Record Offsets
-static const int kInstVertOutVertexId = kInstCommonOutCnt;
-static const int kInstVertOutInstanceId = kInstCommonOutCnt + 1;
+static const int kInstVertOutVertexIndex = kInstCommonOutCnt;
+static const int kInstVertOutInstanceIndex = kInstCommonOutCnt + 1;
 
 // Frag Shader Output Record Offsets
 static const int kInstFragOutFragCoordX = kInstCommonOutCnt;
@@ -110,6 +110,16 @@
 // about the validation error.
 //
 // A bindless bounds error will output the index and the bound.
+static const int kInstBindlessBoundsOutDescIndex = kInstStageOutCnt + 1;
+static const int kInstBindlessBoundsOutDescBound = kInstStageOutCnt + 2;
+static const int kInstBindlessBoundsOutCnt = kInstStageOutCnt + 3;
+
+// A bindless uninitialized error will output the index.
+static const int kInstBindlessUninitOutDescIndex = kInstStageOutCnt + 1;
+static const int kInstBindlessUninitOutUnused = kInstStageOutCnt + 2;
+static const int kInstBindlessUninitOutCnt = kInstStageOutCnt + 3;
+
+// DEPRECATED
 static const int kInstBindlessOutDescIndex = kInstStageOutCnt + 1;
 static const int kInstBindlessOutDescBound = kInstStageOutCnt + 2;
 static const int kInstBindlessOutCnt = kInstStageOutCnt + 3;
@@ -121,15 +131,52 @@
 //
 // These are the possible validation error codes.
 static const int kInstErrorBindlessBounds = 0;
+static const int kInstErrorBindlessUninit = 1;
+
+// Direct Input Buffer Offsets
+//
+// The following values provide member offsets into the input buffers
+// consumed by InstrumentPass::GenDebugDirectRead(). This method is utilized
+// by InstBindlessCheckPass.
+//
+// The only object in an input buffer is a runtime array of unsigned
+// integers. Each validation will have its own formatting of this array.
+static const int kDebugInputDataOffset = 0;
 
 // Debug Buffer Bindings
 //
 // These are the bindings for the different buffers which are
 // read or written by the instrumentation passes.
 //
-// This is the output buffer written by InstBindlessCheckPass.
+// This is the output buffer written by InstBindlessCheckPass
+// and possibly other future validations.
 static const int kDebugOutputBindingStream = 0;
 
+// The binding for the input buffer read by InstBindlessCheckPass and
+// possibly other future validations.
+static const int kDebugInputBindingBindless = 1;
+
+// Bindless Validation Input Buffer Format
+//
+// An input buffer for bindless validation consists of a single array of
+// unsigned integers we will call Data[]. This array is formatted as follows.
+//
+// At offset kDebugInputBindlessInitOffset in Data[] is a single uint which
+// gives an offset to the start of the bindless initialization data. More
+// specifically, if the following value is zero, we know that the descriptor at
+// (set = s, binding = b, index = i) is not initialized:
+// Data[ i + Data[ b + Data[ s + Data[ kDebugInputBindlessInitOffset ] ] ] ]
+static const int kDebugInputBindlessInitOffset = 0;
+
+// DEPRECATED
+static const int kDebugInputBindlessOffsetReserved = 0;
+
+// At offset kDebugInputBindlessOffsetLengths is some number of uints which
+// provide the bindless length data. More specifically, the number of
+// descriptors at (set=s, binding=b) is:
+// Data[ Data[ s + kDebugInputBindlessOffsetLengths ] + b ]
+static const int kDebugInputBindlessOffsetLengths = 1;
+
 }  // namespace spvtools
 
 #endif  // INCLUDE_SPIRV_TOOLS_INSTRUMENT_HPP_
diff --git a/include/spirv-tools/libspirv.h b/include/spirv-tools/libspirv.h
index ff7eb6b..f794faf 100644
--- a/include/spirv-tools/libspirv.h
+++ b/include/spirv-tools/libspirv.h
@@ -427,6 +427,8 @@
   SPV_ENV_UNIVERSAL_1_3,  // SPIR-V 1.3 latest revision, no other restrictions.
   SPV_ENV_VULKAN_1_1,     // Vulkan 1.1 latest revision.
   SPV_ENV_WEBGPU_0,       // Work in progress WebGPU 1.0.
+  SPV_ENV_UNIVERSAL_1_4,  // SPIR-V 1.4 latest revision, no other restrictions.
+  SPV_ENV_VULKAN_1_1_SPIRV_1_4,  // Vulkan 1.1 with SPIR-V 1.4 binary.
 } spv_target_env;
 
 // SPIR-V Validator can be parameterized with the following Universal Limits.
@@ -445,6 +447,10 @@
 // Returns a string describing the given SPIR-V target environment.
 SPIRV_TOOLS_EXPORT const char* spvTargetEnvDescription(spv_target_env env);
 
+// Parses s into *env and returns true if successful.  If unparsable, returns
+// false and sets *env to SPV_ENV_UNIVERSAL_1_0.
+SPIRV_TOOLS_EXPORT bool spvParseTargetEnv(const char* s, spv_target_env* env);
+
 // Creates a context object.  Returns null if env is invalid.
 SPIRV_TOOLS_EXPORT spv_context spvContextCreate(spv_target_env env);
 
@@ -498,6 +504,11 @@
 SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetRelaxBlockLayout(
     spv_validator_options options, bool val);
 
+// Records whether the validator should use standard block layout rules for
+// uniform blocks.
+SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetUniformBufferStandardLayout(
+    spv_validator_options options, bool val);
+
 // Records whether the validator should use "scalar" block layout rules.
 // Scalar layout rules are more permissive than relaxed block layout.
 //
@@ -553,14 +564,17 @@
 // Destroys the given reducer options object.
 SPIRV_TOOLS_EXPORT void spvReducerOptionsDestroy(spv_reducer_options options);
 
-// Records the maximum number of reduction steps that should run before the
-// reducer gives up.
+// Sets the maximum number of reduction steps that should run before the reducer
+// gives up.
 SPIRV_TOOLS_EXPORT void spvReducerOptionsSetStepLimit(
     spv_reducer_options options, uint32_t step_limit);
 
-// Sets seed for random number generation.
-SPIRV_TOOLS_EXPORT void spvReducerOptionsSetSeed(spv_reducer_options options,
-                                                 uint32_t seed);
+// Sets the fail-on-validation-error option; if true, the reducer will return
+// kStateInvalid if a reduction step yields a state that fails SPIR-V
+// validation. Otherwise, an invalid state is treated as uninteresting and the
+// reduction backtracks and continues.
+SPIRV_TOOLS_EXPORT void spvReducerOptionsSetFailOnValidationError(
+    spv_reducer_options options, bool fail_on_validation_error);
 
 // Encodes the given SPIR-V assembly text to its binary representation. The
 // length parameter specifies the number of bytes for text. Encoded binary will
diff --git a/include/spirv-tools/libspirv.hpp b/include/spirv-tools/libspirv.hpp
index 9cb5afe..27b87e7 100644
--- a/include/spirv-tools/libspirv.hpp
+++ b/include/spirv-tools/libspirv.hpp
@@ -88,6 +88,12 @@
     spvValidatorOptionsSetRelaxBlockLayout(options_, val);
   }
 
+  // Enables VK_KHR_uniform_buffer_standard_layout when validating standard
+  // uniform layout.  If true, disables scalar block layout rules.
+  void SetUniformBufferStandardLayout(bool val) {
+    spvValidatorOptionsSetUniformBufferStandardLayout(options_, val);
+  }
+
   // Enables VK_EXT_scalar_block_layout when validating standard
   // uniform/storage buffer/push-constant layout.  If true, disables
   // relaxed block layout rules.
@@ -151,16 +157,20 @@
   ~ReducerOptions() { spvReducerOptionsDestroy(options_); }
 
   // Allow implicit conversion to the underlying object.
-  operator spv_reducer_options() const { return options_; }
+  operator spv_reducer_options() const {  // NOLINT(google-explicit-constructor)
+    return options_;
+  }
 
-  // Records the maximum number of reduction steps that should
-  // run before the reducer gives up.
+  // See spvReducerOptionsSetStepLimit.
   void set_step_limit(uint32_t step_limit) {
     spvReducerOptionsSetStepLimit(options_, step_limit);
   }
 
-  // Sets a seed to be used for random number generation.
-  void set_seed(uint32_t seed) { spvReducerOptionsSetSeed(options_, seed); }
+  // See spvReducerOptionsSetFailOnValidationError.
+  void set_fail_on_validation_error(bool fail_on_validation_error) {
+    spvReducerOptionsSetFailOnValidationError(options_,
+                                              fail_on_validation_error);
+  }
 
  private:
   spv_reducer_options options_;
@@ -235,6 +245,9 @@
   bool Validate(const uint32_t* binary, size_t binary_size,
                 spv_validator_options options) const;
 
+  // Was this object successfully constructed.
+  bool IsValid() const;
+
  private:
   struct Impl;  // Opaque struct for holding the data fields used by this class.
   std::unique_ptr<Impl> impl_;  // Unique pointer to implementation data.
diff --git a/include/spirv-tools/optimizer.hpp b/include/spirv-tools/optimizer.hpp
index f8769c9..af9f3e5 100644
--- a/include/spirv-tools/optimizer.hpp
+++ b/include/spirv-tools/optimizer.hpp
@@ -101,6 +101,16 @@
   // from time to time.
   Optimizer& RegisterSizePasses();
 
+  // Registers passes that have been prescribed for converting from Vulkan to
+  // WebGPU. This sequence of passes is subject to constant review and will
+  // change from time to time.
+  Optimizer& RegisterVulkanToWebGPUPasses();
+
+  // Registers passes that have been prescribed for converting from WebGPU to
+  // Vulkan. This sequence of passes is subject to constant review and will
+  // change from time to time.
+  Optimizer& RegisterWebGPUToVulkanPasses();
+
   // Registers passes that attempt to legalize the generated code.
   //
   // Note: this recipe is specially designed for legalizing SPIR-V. It should be
@@ -194,6 +204,9 @@
   // |out| output stream.
   Optimizer& SetTimeReport(std::ostream* out);
 
+  // Sets the option to validate the module after each pass.
+  Optimizer& SetValidateAfterAll(bool validate);
+
  private:
   struct Impl;                  // Opaque struct for holding internal data.
   std::unique_ptr<Impl> impl_;  // Unique pointer to internal data.
@@ -203,6 +216,13 @@
 // A null pass does nothing to the SPIR-V module to be optimized.
 Optimizer::PassToken CreateNullPass();
 
+// Creates a strip-atomic-counter-memory pass.
+// A strip-atomic-counter-memory pass removes all usages of the
+// AtomicCounterMemory bit in Memory Semantics bitmasks. This bit is a no-op in
+// Vulkan, so isn't needed in that env. And the related capability is not
+// allowed in WebGPU, so it is not allowed in that env.
+Optimizer::PassToken CreateStripAtomicCounterMemoryPass();
+
 // Creates a strip-debug-info pass.
 // A strip-debug-info pass removes all debug instructions (as documented in
 // Section 3.32.2 of the SPIR-V spec) of the SPIR-V module to be optimized.
@@ -221,6 +241,11 @@
 // functions are not needed because they will never be called.
 Optimizer::PassToken CreateEliminateDeadFunctionsPass();
 
+// Creates an eliminate-dead-members pass.
+// An eliminate-dead-members pass will remove all unused members of structures.
+// This will not affect the data layout of the remaining members.
+Optimizer::PassToken CreateEliminateDeadMembersPass();
+
 // Creates a set-spec-constant-default-value pass from a mapping from spec-ids
 // to the default values in the form of string.
 // A set-spec-constant-default-value pass sets the default values for the
@@ -687,10 +712,14 @@
 
 // Create a pass to instrument bindless descriptor checking
 // This pass instruments all bindless references to check that descriptor
-// array indices are inbounds. If the reference is invalid, a record is
-// written to the debug output buffer (if space allows) and a null value is
-// returned. This pass is designed to support bindless validation in the Vulkan
-// validation layers.
+// array indices are inbounds, and if the descriptor indexing extension is
+// enabled, that the descriptor has been initialized. If the reference is
+// invalid, a record is written to the debug output buffer (if space allows)
+// and a null value is returned. This pass is designed to support bindless
+// validation in the Vulkan validation layers.
+//
+// TODO(greg-lunarg): Add support for buffer references. Currently only does
+// checking for image references.
 //
 // Dead code elimination should be run after this pass as the original,
 // potentially invalid code is not removed and could cause undefined behavior,
@@ -706,10 +735,12 @@
 // The instrumentation will read and write buffers in debug
 // descriptor set |desc_set|. It will write |shader_id| in each output record
 // to identify the shader module which generated the record.
-//
-// TODO(greg-lunarg): Add support for vk_ext_descriptor_indexing.
-Optimizer::PassToken CreateInstBindlessCheckPass(uint32_t desc_set,
-                                                 uint32_t shader_id);
+// |input_length_enable| controls instrumentation of runtime descriptor array
+// references, and |input_init_enable| controls instrumentation of descriptor
+// initialization checking, both of which require input buffer support.
+Optimizer::PassToken CreateInstBindlessCheckPass(
+    uint32_t desc_set, uint32_t shader_id, bool input_length_enable = false,
+    bool input_init_enable = false);
 
 // Create a pass to upgrade to the VulkanKHR memory model.
 // This pass upgrades the Logical GLSL450 memory model to Logical VulkanKHR.
@@ -717,6 +748,30 @@
 // conform to that model's requirements.
 Optimizer::PassToken CreateUpgradeMemoryModelPass();
 
+// Create a pass to do code sinking.  Code sinking is a transformation
+// where an instruction is moved into a more deeply nested construct.
+Optimizer::PassToken CreateCodeSinkingPass();
+
+// Create a pass to adds initializers for OpVariable calls that require them
+// in WebGPU. Currently this pass naively initializes variables that are
+// missing an initializer with a null value. In the future it may initialize
+// variables to the first value stored in them, if that is a constant.
+Optimizer::PassToken CreateGenerateWebGPUInitializersPass();
+
+// Create a pass to fix incorrect storage classes.  In order to make code
+// generation simpler, DXC may generate code where the storage classes do not
+// match up correctly.  This pass will fix the errors that it can.
+Optimizer::PassToken CreateFixStorageClassPass();
+
+// Create a pass to legalize OpVectorShuffle operands going into WebGPU. WebGPU
+// forbids using 0xFFFFFFFF, which indicates an undefined result, so this pass
+// converts those literals to 0.
+Optimizer::PassToken CreateLegalizeVectorShufflePass();
+
+// Create a pass to decompose initialized variables into a seperate variable
+// declaration and an initial store.
+Optimizer::PassToken CreateDecomposeInitializedVariablesPass();
+
 }  // namespace spvtools
 
 #endif  // INCLUDE_SPIRV_TOOLS_OPTIMIZER_HPP_
diff --git a/kokoro/linux-clang-asan/build.sh b/kokoro/linux-clang-asan/build.sh
new file mode 100644
index 0000000..8f86e6e
--- /dev/null
+++ b/kokoro/linux-clang-asan/build.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+# Copyright (c) 2019 Google LLC.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Linux Build Script.
+
+# Fail on any error.
+set -e
+# Display commands being run.
+set -x
+
+SCRIPT_DIR=`dirname "$BASH_SOURCE"`
+source $SCRIPT_DIR/../scripts/linux/build.sh ASAN clang
diff --git a/kokoro/linux-clang-asan/continuous.cfg b/kokoro/linux-clang-asan/continuous.cfg
new file mode 100644
index 0000000..3a98fc7
--- /dev/null
+++ b/kokoro/linux-clang-asan/continuous.cfg
@@ -0,0 +1,16 @@
+# Copyright (c) 2019 Google LLC.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Continuous build configuration.
+build_file: "SPIRV-Tools/kokoro/linux-clang-asan/build.sh"
diff --git a/kokoro/linux-clang-asan/presubmit.cfg b/kokoro/linux-clang-asan/presubmit.cfg
new file mode 100644
index 0000000..ceac44b
--- /dev/null
+++ b/kokoro/linux-clang-asan/presubmit.cfg
@@ -0,0 +1,16 @@
+# Copyright (c) 2019 Google LLC.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Presubmit build configuration.
+build_file: "SPIRV-Tools/kokoro/linux-clang-asan/build.sh"
diff --git a/kokoro/linux-clang-debug/continuous.cfg b/kokoro/linux-clang-debug/continuous.cfg
index e92f059..3350f3b 100644
--- a/kokoro/linux-clang-debug/continuous.cfg
+++ b/kokoro/linux-clang-debug/continuous.cfg
@@ -14,3 +14,9 @@
 
 # Continuous build configuration.
 build_file: "SPIRV-Tools/kokoro/linux-clang-debug/build.sh"
+
+action {
+  define_artifacts {
+    regex: "install.tgz"
+  }
+}
diff --git a/kokoro/linux-clang-release/continuous.cfg b/kokoro/linux-clang-release/continuous.cfg
index 687434a..8b075c6 100644
--- a/kokoro/linux-clang-release/continuous.cfg
+++ b/kokoro/linux-clang-release/continuous.cfg
@@ -14,3 +14,9 @@
 
 # Continuous build configuration.
 build_file: "SPIRV-Tools/kokoro/linux-clang-release/build.sh"
+
+action {
+  define_artifacts {
+    regex: "install.tgz"
+  }
+}
diff --git a/kokoro/linux-gcc-debug/continuous.cfg b/kokoro/linux-gcc-debug/continuous.cfg
index 4f8418d..d9579d5 100644
--- a/kokoro/linux-gcc-debug/continuous.cfg
+++ b/kokoro/linux-gcc-debug/continuous.cfg
@@ -14,3 +14,9 @@
 
 # Continuous build configuration.
 build_file: "SPIRV-Tools/kokoro/linux-gcc-debug/build.sh"
+
+action {
+  define_artifacts {
+    regex: "install.tgz"
+  }
+}
diff --git a/kokoro/linux-gcc-release/continuous.cfg b/kokoro/linux-gcc-release/continuous.cfg
index 41a0024..ead07bf 100644
--- a/kokoro/linux-gcc-release/continuous.cfg
+++ b/kokoro/linux-gcc-release/continuous.cfg
@@ -14,3 +14,9 @@
 
 # Continuous build configuration.
 build_file: "SPIRV-Tools/kokoro/linux-gcc-release/build.sh"
+
+action {
+  define_artifacts {
+    regex: "install.tgz"
+  }
+}
diff --git a/kokoro/macos-clang-debug/continuous.cfg b/kokoro/macos-clang-debug/continuous.cfg
index 84aaa5c..f5f274a 100644
--- a/kokoro/macos-clang-debug/continuous.cfg
+++ b/kokoro/macos-clang-debug/continuous.cfg
@@ -14,3 +14,9 @@
 
 # Continuous build configuration.
 build_file: "SPIRV-Tools/kokoro/macos-clang-debug/build.sh"
+
+action {
+  define_artifacts {
+    regex: "install.tgz"
+  }
+}
diff --git a/kokoro/macos-clang-release/continuous.cfg b/kokoro/macos-clang-release/continuous.cfg
index a8e23a7..710185e 100644
--- a/kokoro/macos-clang-release/continuous.cfg
+++ b/kokoro/macos-clang-release/continuous.cfg
@@ -14,3 +14,9 @@
 
 # Continuous build configuration.
 build_file: "SPIRV-Tools/kokoro/macos-clang-release/build.sh"
+
+action {
+  define_artifacts {
+    regex: "install.tgz"
+  }
+}
diff --git a/kokoro/scripts/linux/build.sh b/kokoro/scripts/linux/build.sh
index d457539..e89d2ea 100644
--- a/kokoro/scripts/linux/build.sh
+++ b/kokoro/scripts/linux/build.sh
@@ -31,8 +31,7 @@
 CMAKE_C_CXX_COMPILER=""
 if [ $COMPILER = "clang" ]
 then
-  sudo ln -s /usr/bin/clang-3.8 /usr/bin/clang
-  sudo ln -s /usr/bin/clang++-3.8 /usr/bin/clang++
+  PATH=/usr/lib/llvm-3.8/bin:$PATH
   CMAKE_C_CXX_COMPILER="-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++"
 fi
 
@@ -47,8 +46,8 @@
 ADDITIONAL_CMAKE_FLAGS=""
 if [ $CONFIG = "ASAN" ]
 then
-  ADDITIONAL_CMAKE_FLAGS="-DCMAKE_CXX_FLAGS=-fsanitize=address -DCMAKE_C_FLAGS=-fsanitize=address"
-  export ASAN_SYMBOLIZER_PATH=/usr/bin/llvm-symbolizer-3.4
+  ADDITIONAL_CMAKE_FLAGS="SPIRV_USE_SANITIZER=address"
+  [ $COMPILER = "clang" ] || { echo "$CONFIG requires clang"; exit 1; }
 elif [ $CONFIG = "COVERAGE" ]
 then
   ADDITIONAL_CMAKE_FLAGS="-DENABLE_CODE_COVERAGE=ON"
@@ -78,7 +77,7 @@
 # Invoke the build.
 BUILD_SHA=${KOKORO_GITHUB_COMMIT:-$KOKORO_GITHUB_PULL_REQUEST_COMMIT}
 echo $(date): Starting build...
-cmake -GNinja -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_INSTALL_PREFIX=install -DRE2_BUILD_TESTING=OFF $ADDITIONAL_CMAKE_FLAGS $CMAKE_C_CXX_COMPILER ..
+cmake -DPYTHON_EXECUTABLE:FILEPATH=/usr/bin/python3 -GNinja -DCMAKE_INSTALL_PREFIX=$KOKORO_ARTIFACTS_DIR/install -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DRE2_BUILD_TESTING=OFF $ADDITIONAL_CMAKE_FLAGS $CMAKE_C_CXX_COMPILER ..
 
 echo $(date): Build everything...
 ninja
@@ -98,3 +97,7 @@
 fi
 echo $(date): ctest completed.
 
+# Package the build.
+ninja install
+cd $KOKORO_ARTIFACTS_DIR
+tar czf install.tgz install
diff --git a/kokoro/scripts/macos/build.sh b/kokoro/scripts/macos/build.sh
index a7f0453..98e6366 100644
--- a/kokoro/scripts/macos/build.sh
+++ b/kokoro/scripts/macos/build.sh
@@ -41,7 +41,15 @@
 # Invoke the build.
 BUILD_SHA=${KOKORO_GITHUB_COMMIT:-$KOKORO_GITHUB_PULL_REQUEST_COMMIT}
 echo $(date): Starting build...
-cmake -GNinja -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_BUILD_TYPE=$BUILD_TYPE ..
+# We need Python 3.  At the moment python3.7 is the newest Python on Kokoro.
+cmake \
+  -GNinja \
+  -DCMAKE_INSTALL_PREFIX=$KOKORO_ARTIFACTS_DIR/install \
+  -DPYTHON_EXECUTABLE:FILEPATH=/usr/local/bin/python3.7 \
+  -DCMAKE_C_COMPILER=clang \
+  -DCMAKE_CXX_COMPILER=clang++ \
+  -DCMAKE_BUILD_TYPE=$BUILD_TYPE \
+  ..
 
 echo $(date): Build everything...
 ninja
@@ -51,3 +59,8 @@
 ctest -j4 --output-on-failure --timeout 300
 echo $(date): ctest completed.
 
+# Package the build.
+ninja install
+cd $KOKORO_ARTIFACTS_DIR
+tar czf install.tgz install
+
diff --git a/kokoro/scripts/windows/build.bat b/kokoro/scripts/windows/build.bat
index a2472fb..0532118 100644
--- a/kokoro/scripts/windows/build.bat
+++ b/kokoro/scripts/windows/build.bat
@@ -21,8 +21,8 @@
 set BUILD_TYPE=%1
 set VS_VERSION=%2
 
-:: Force usage of python 2.7 rather than 3.6
-set PATH=C:\python27;%PATH%
+:: Force usage of python 3.6
+set PATH=C:\python36;%PATH%
 
 cd %SRC%
 git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Headers external/spirv-headers
@@ -58,33 +58,45 @@
   set BUILD_SHA=%KOKORO_GITHUB_COMMIT%
 )
 
+set CMAKE_FLAGS=-DCMAKE_INSTALL_PREFIX=%KOKORO_ARTIFACTS_DIR%\install -GNinja -DSPIRV_BUILD_COMPRESSION=ON -DCMAKE_BUILD_TYPE=%BUILD_TYPE% -DRE2_BUILD_TESTING=OFF -DCMAKE_C_COMPILER=cl.exe -DCMAKE_CXX_COMPILER=cl.exe
+
 :: Skip building tests for VS2013
 if %VS_VERSION% == 2013 (
-  cmake -GNinja -DSPIRV_SKIP_TESTS=ON -DSPIRV_BUILD_COMPRESSION=ON -DCMAKE_BUILD_TYPE=%BUILD_TYPE% -DCMAKE_INSTALL_PREFIX=install -DRE2_BUILD_TESTING=OFF -DCMAKE_C_COMPILER=cl.exe -DCMAKE_CXX_COMPILER=cl.exe ..
-) else (
-  cmake -GNinja -DSPIRV_BUILD_COMPRESSION=ON -DCMAKE_BUILD_TYPE=%BUILD_TYPE% -DCMAKE_INSTALL_PREFIX=install -DRE2_BUILD_TESTING=OFF -DCMAKE_C_COMPILER=cl.exe -DCMAKE_CXX_COMPILER=cl.exe ..
+  set CMAKE_FLAGS=%CMAKE_FLAGS% -DSPIRV_SKIP_TESTS=ON
 )
 
-if %ERRORLEVEL% GEQ 1 exit /b %ERRORLEVEL%
+cmake %CMAKE_FLAGS% ..
+
+if %ERRORLEVEL% NEQ 0 exit /b %ERRORLEVEL%
 
 echo "Build everything... %DATE% %TIME%"
 ninja
-if %ERRORLEVEL% GEQ 1 exit /b %ERRORLEVEL%
+if %ERRORLEVEL% NEQ 0 exit /b %ERRORLEVEL%
 echo "Build Completed %DATE% %TIME%"
 
+:: This lets us use !ERRORLEVEL! inside an IF ... () and get the actual error at that point.
+setlocal ENABLEDELAYEDEXPANSION
+
 :: ################################################
 :: Run the tests (We no longer run tests on VS2013)
 :: ################################################
-if NOT %VS_VERSION% == 2013 (
-  echo "Running Tests... %DATE% %TIME%"
+echo "Running Tests... %DATE% %TIME%"
+if %VS_VERSION% NEQ 2013 (
   ctest -C %BUILD_TYPE% --output-on-failure --timeout 300
-  if %ERRORLEVEL% GEQ 1 exit /b %ERRORLEVEL%
-  echo "Tests Completed %DATE% %TIME%"
+  if !ERRORLEVEL! NEQ 0 exit /b !ERRORLEVEL!
 )
+echo "Tests Completed %DATE% %TIME%"
+
+:: ################################################
+:: Install and package.
+:: ################################################
+ninja install
+cd %KOKORO_ARTIFACTS_DIR%
+zip -r install.zip install
 
 :: Clean up some directories.
 rm -rf %SRC%\build
 rm -rf %SRC%\external
 
-exit /b %ERRORLEVEL%
+exit /b 0
 
diff --git a/kokoro/shaderc-smoketest/build.sh b/kokoro/shaderc-smoketest/build.sh
index 638ca8c..0856c9b 100644
--- a/kokoro/shaderc-smoketest/build.sh
+++ b/kokoro/shaderc-smoketest/build.sh
@@ -37,7 +37,7 @@
 
 # Get shaderc dependencies. Link the appropriate SPIRV-Tools.
 git clone https://github.com/google/googletest.git
-git clone https://github.com/google/glslang.git
+git clone https://github.com/KhronosGroup/glslang.git
 ln -s $GITHUB_DIR/SPIRV-Tools spirv-tools
 git clone https://github.com/KhronosGroup/SPIRV-Headers.git spirv-headers
 git clone https://github.com/google/re2
diff --git a/kokoro/windows-msvc-2017-debug/continuous.cfg b/kokoro/windows-msvc-2017-debug/continuous.cfg
index b842c30..25c5e11 100644
--- a/kokoro/windows-msvc-2017-debug/continuous.cfg
+++ b/kokoro/windows-msvc-2017-debug/continuous.cfg
@@ -14,3 +14,9 @@
 
 # Continuous build configuration.
 build_file: "SPIRV-Tools/kokoro/windows-msvc-2017-debug/build.bat"
+
+action {
+  define_artifacts {
+    regex: "install.zip"
+  }
+}
diff --git a/kokoro/windows-msvc-2017-release/continuous.cfg b/kokoro/windows-msvc-2017-release/continuous.cfg
index 7b8c2ff..a9ac6ec 100644
--- a/kokoro/windows-msvc-2017-release/continuous.cfg
+++ b/kokoro/windows-msvc-2017-release/continuous.cfg
@@ -14,3 +14,9 @@
 
 # Continuous build configuration.
 build_file: "SPIRV-Tools/kokoro/windows-msvc-2017-release/build.bat"
+
+action {
+  define_artifacts {
+    regex: "install.zip"
+  }
+}
diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt
index efd6330..1f96018 100644
--- a/source/CMakeLists.txt
+++ b/source/CMakeLists.txt
@@ -101,7 +101,7 @@
   list(APPEND EXTINST_CPP_DEPENDS ${GRAMMAR_INC_FILE})
 endmacro(spvtools_opencl_tables)
 
-macro(spvtools_vendor_tables VENDOR_TABLE)
+macro(spvtools_vendor_tables VENDOR_TABLE SHORT_NAME)
   set(INSTS_FILE "${spirv-tools_BINARY_DIR}/${VENDOR_TABLE}.insts.inc")
   set(GRAMMAR_FILE "${spirv-tools_SOURCE_DIR}/source/extinst.${VENDOR_TABLE}.grammar.json")
   add_custom_command(OUTPUT ${INSTS_FILE}
@@ -110,9 +110,9 @@
       --vendor-insts-output=${INSTS_FILE}
     DEPENDS ${GRAMMAR_PROCESSING_SCRIPT} ${GRAMMAR_FILE}
     COMMENT "Generate extended instruction tables for ${VENDOR_TABLE}.")
-  list(APPEND EXTINST_CPP_DEPENDS ${INSTS_FILE})
-  add_custom_target(spirv-tools-${VENDOR_TABLE} DEPENDS ${INSTS_FILE})
-  set_property(TARGET spirv-tools-${VENDOR_TABLE} PROPERTY FOLDER "SPIRV-Tools build")
+  add_custom_target(spv-tools-${SHORT_NAME} DEPENDS ${INSTS_FILE})
+  set_property(TARGET spv-tools-${SHORT_NAME} PROPERTY FOLDER "SPIRV-Tools build")
+  list(APPEND EXTINST_CPP_DEPENDS spv-tools-${SHORT_NAME})
 endmacro(spvtools_vendor_tables)
 
 macro(spvtools_extinst_lang_headers NAME GRAMMAR_FILE)
@@ -125,20 +125,20 @@
       --extinst-output-base=${OUTBASE}
     DEPENDS ${LANG_HEADER_PROCESSING_SCRIPT} ${GRAMMAR_FILE}
     COMMENT "Generate language specific header for ${NAME}.")
-  list(APPEND EXTINST_CPP_DEPENDS ${OUT_H})
   add_custom_target(spirv-tools-header-${NAME} DEPENDS ${OUT_H})
   set_property(TARGET spirv-tools-header-${NAME} PROPERTY FOLDER "SPIRV-Tools build")
+  list(APPEND EXTINST_CPP_DEPENDS spirv-tools-header-${NAME})
 endmacro(spvtools_extinst_lang_headers)
 
 spvtools_core_tables("unified1")
 spvtools_enum_string_mapping("unified1")
 spvtools_opencl_tables("unified1")
 spvtools_glsl_tables("unified1")
-spvtools_vendor_tables("spv-amd-shader-explicit-vertex-parameter")
-spvtools_vendor_tables("spv-amd-shader-trinary-minmax")
-spvtools_vendor_tables("spv-amd-gcn-shader")
-spvtools_vendor_tables("spv-amd-shader-ballot")
-spvtools_vendor_tables("debuginfo")
+spvtools_vendor_tables("spv-amd-shader-explicit-vertex-parameter" "spv-amd-sevp")
+spvtools_vendor_tables("spv-amd-shader-trinary-minmax" "spv-amd-stm")
+spvtools_vendor_tables("spv-amd-gcn-shader" "spv-amd-gs")
+spvtools_vendor_tables("spv-amd-shader-ballot" "spv-amd-sb")
+spvtools_vendor_tables("debuginfo" "debuginfo")
 spvtools_extinst_lang_headers("DebugInfo" ${DEBUGINFO_GRAMMAR_JSON_FILE})
 
 spvtools_vimsyntax("unified1" "1.0")
@@ -159,36 +159,18 @@
 # The following .cpp files include the above generated .inc files.
 # Add those .inc files as their dependencies.
 #
-# Why using such an awkward way?
-# * If we use add_custom_target() to define a target to generate all .inc files
-#   and let ${SPIRV_TOOLS} depend on it, then we need to run ninja twice every
-#   time the grammar is updated: the first time is for generating those .inc
-#   files, and the second time is for rebuilding .cpp files, when ninja finds
-#   out that .inc files are updated.
-# * If we use add_custom_command() with PRE_BUILD, then the grammar processing
-#   script will always run no matter whether the grammar is updated.
-# * add_dependencies() is used to add *target* dependencies to a target.
-# * The following solution only generates .inc files when the script or the
-#   grammar files is updated, and in a single ninja run.
-set_source_files_properties(
-  ${CMAKE_CURRENT_SOURCE_DIR}/opcode.cpp
-  PROPERTIES OBJECT_DEPENDS "${OPCODE_CPP_DEPENDS}")
-set_source_files_properties(
-  ${CMAKE_CURRENT_SOURCE_DIR}/operand.cpp
-  PROPERTIES OBJECT_DEPENDS "${OPERAND_CPP_DEPENDS}")
-set_source_files_properties(
-  ${CMAKE_CURRENT_SOURCE_DIR}/ext_inst.cpp
-  PROPERTIES OBJECT_DEPENDS "${EXTINST_CPP_DEPENDS}")
-set_source_files_properties(
-  ${CMAKE_CURRENT_SOURCE_DIR}/enum_string_mapping.cpp
-  PROPERTIES OBJECT_DEPENDS "${ENUM_STRING_MAPPING_CPP_DEPENDS}")
+# We need to wrap the .inc files with a custom target to avoid problems when
+# multiple targets depend on the same custom command.
+add_custom_target(core_tables
+  DEPENDS ${OPCODE_CPP_DEPENDS} ${OPERAND_CPP_DEPENDS})
+add_custom_target(enum_string_mapping
+  DEPENDS ${EXTENSION_H_DEPENDS} ${ENUM_STRING_MAPPING_CPP_DEPENDS})
+add_custom_target(extinst_tables
+  DEPENDS ${EXTINST_CPP_DEPENDS})
 
 set_source_files_properties(
   ${CMAKE_CURRENT_SOURCE_DIR}/extensions.h
   PROPERTIES HEADER_FILE_ONLY TRUE)
-set_source_files_properties(
-  ${CMAKE_CURRENT_SOURCE_DIR}/extensions.h
-  PROPERTIES OBJECT_DEPENDS "${EXTENSION_H_DEPENDS}")
 
 set(SPIRV_TOOLS_BUILD_VERSION_INC
   ${spirv-tools_BINARY_DIR}/build-version.inc)
@@ -361,6 +343,7 @@
   )
 set_property(TARGET ${SPIRV_TOOLS} PROPERTY FOLDER "SPIRV-Tools libraries")
 spvtools_check_symbol_exports(${SPIRV_TOOLS})
+add_dependencies( ${SPIRV_TOOLS} core_tables enum_string_mapping extinst_tables )
 
 add_library(${SPIRV_TOOLS}-shared SHARED ${SPIRV_SOURCES})
 spvtools_default_compile_options(${SPIRV_TOOLS}-shared)
@@ -376,6 +359,15 @@
   PRIVATE SPIRV_TOOLS_IMPLEMENTATION
   PUBLIC SPIRV_TOOLS_SHAREDLIB
 )
+add_dependencies( ${SPIRV_TOOLS}-shared core_tables enum_string_mapping extinst_tables )
+
+if("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux")
+  find_library(LIBRT rt)
+  if(LIBRT)
+    target_link_libraries(${SPIRV_TOOLS} ${LIBRT})
+    target_link_libraries(${SPIRV_TOOLS}-shared ${LIBRT})
+  endif()
+endif()
 
 if(ENABLE_SPIRV_TOOLS_INSTALL)
   install(TARGETS ${SPIRV_TOOLS} ${SPIRV_TOOLS}-shared
diff --git a/source/assembly_grammar.cpp b/source/assembly_grammar.cpp
index 4d98e3d..79f18ee 100644
--- a/source/assembly_grammar.cpp
+++ b/source/assembly_grammar.cpp
@@ -154,10 +154,11 @@
     CASE(InBoundsAccessChain),
     CASE(PtrAccessChain),
     CASE(InBoundsPtrAccessChain),
+    CASE(CooperativeMatrixLengthNV)
 };
 
-// The 59 is determined by counting the opcodes listed in the spec.
-static_assert(59 == sizeof(kOpSpecConstantOpcodes)/sizeof(kOpSpecConstantOpcodes[0]),
+// The 60 is determined by counting the opcodes listed in the spec.
+static_assert(60 == sizeof(kOpSpecConstantOpcodes)/sizeof(kOpSpecConstantOpcodes[0]),
               "OpSpecConstantOp opcode table is incomplete");
 #undef CASE
 // clang-format on
diff --git a/source/binary.cpp b/source/binary.cpp
index 6604d80..636dac8 100644
--- a/source/binary.cpp
+++ b/source/binary.cpp
@@ -123,8 +123,8 @@
   // returned object will be propagated to the current parse's diagnostic
   // object.
   spvtools::DiagnosticStream diagnostic(spv_result_t error) {
-    return spvtools::DiagnosticStream({0, 0, _.word_index}, consumer_, "",
-                                      error);
+    return spvtools::DiagnosticStream({0, 0, _.instruction_count}, consumer_,
+                                      "", error);
   }
 
   // Returns a diagnostic stream object with the default parse error code.
@@ -179,6 +179,7 @@
           num_words(num_words_arg),
           diagnostic(diagnostic_arg),
           word_index(0),
+          instruction_count(0),
           endian(),
           requires_endian_conversion(false) {
       // Temporary storage for parser state within a single instruction.
@@ -192,6 +193,7 @@
     size_t num_words;            // Number of words in the module.
     spv_diagnostic* diagnostic;  // Where diagnostics go.
     size_t word_index;           // The current position in words.
+    size_t instruction_count;    // The count of processed instructions
     spv_endianness_t endian;     // The endianness of the binary.
     // Is the SPIR-V binary in a different endiannes from the host native
     // endianness?
@@ -269,6 +271,8 @@
 }
 
 spv_result_t Parser::parseInstruction() {
+  _.instruction_count++;
+
   // The zero values for all members except for opcode are the
   // correct initial values.
   spv_parsed_instruction_t inst = {};
diff --git a/source/ext_inst.cpp b/source/ext_inst.cpp
index 08c775e..1198f76 100644
--- a/source/ext_inst.cpp
+++ b/source/ext_inst.cpp
@@ -30,6 +30,7 @@
 #include "glsl.std.450.insts.inc"
 #include "opencl.std.insts.inc"
 
+#include "spirv-tools/libspirv.h"
 #include "spv-amd-gcn-shader.insts.inc"
 #include "spv-amd-shader-ballot.insts.inc"
 #include "spv-amd-shader-explicit-vertex-parameter.insts.inc"
@@ -80,7 +81,9 @@
     case SPV_ENV_OPENGL_4_5:
     case SPV_ENV_UNIVERSAL_1_3:
     case SPV_ENV_VULKAN_1_1:
+    case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
     case SPV_ENV_WEBGPU_0:
+    case SPV_ENV_UNIVERSAL_1_4:
       *pExtInstTable = &kTable_1_0;
       return SPV_SUCCESS;
     default:
diff --git a/source/libspirv.cpp b/source/libspirv.cpp
index b5fe897..a1ed11d 100644
--- a/source/libspirv.cpp
+++ b/source/libspirv.cpp
@@ -128,4 +128,6 @@
   return valid;
 }
 
+bool SpirvTools::IsValid() const { return impl_->context != nullptr; }
+
 }  // namespace spvtools
diff --git a/source/opcode.cpp b/source/opcode.cpp
index 78c2386..ddf2deb 100644
--- a/source/opcode.cpp
+++ b/source/opcode.cpp
@@ -260,6 +260,7 @@
     case SpvOpTypeMatrix:
     case SpvOpTypeArray:
     case SpvOpTypeStruct:
+    case SpvOpTypeCooperativeMatrixNV:
       return true;
     default:
       return false;
@@ -325,6 +326,7 @@
     case SpvOpTypePipeStorage:
     case SpvOpTypeNamedBarrier:
     case SpvOpTypeAccelerationStructureNV:
+    case SpvOpTypeCooperativeMatrixNV:
       return true;
     default:
       // In particular, OpTypeForwardPointer does not generate a type,
@@ -603,3 +605,35 @@
       return false;
   }
 }
+
+std::vector<uint32_t> spvOpcodeMemorySemanticsOperandIndices(SpvOp opcode) {
+  switch (opcode) {
+    case SpvOpMemoryBarrier:
+      return {1};
+    case SpvOpAtomicStore:
+    case SpvOpControlBarrier:
+    case SpvOpAtomicFlagClear:
+    case SpvOpMemoryNamedBarrier:
+      return {2};
+    case SpvOpAtomicLoad:
+    case SpvOpAtomicExchange:
+    case SpvOpAtomicIIncrement:
+    case SpvOpAtomicIDecrement:
+    case SpvOpAtomicIAdd:
+    case SpvOpAtomicISub:
+    case SpvOpAtomicSMin:
+    case SpvOpAtomicUMin:
+    case SpvOpAtomicSMax:
+    case SpvOpAtomicUMax:
+    case SpvOpAtomicAnd:
+    case SpvOpAtomicOr:
+    case SpvOpAtomicXor:
+    case SpvOpAtomicFlagTestAndSet:
+      return {4};
+    case SpvOpAtomicCompareExchange:
+    case SpvOpAtomicCompareExchangeWeak:
+      return {4, 5};
+    default:
+      return {};
+  }
+}
diff --git a/source/opcode.h b/source/opcode.h
index 76f9a0e..ed64f1b 100644
--- a/source/opcode.h
+++ b/source/opcode.h
@@ -133,4 +133,8 @@
 // Returns true if the given opcode is a debug instruction.
 bool spvOpcodeIsDebug(SpvOp opcode);
 
+// Returns a vector containing the indices of the memory semantics <id>
+// operands for |opcode|.
+std::vector<uint32_t> spvOpcodeMemorySemanticsOperandIndices(SpvOp opcode);
+
 #endif  // SOURCE_OPCODE_H_
diff --git a/source/operand.cpp b/source/operand.cpp
index 923074e..00dc53d 100644
--- a/source/operand.cpp
+++ b/source/operand.cpp
@@ -481,6 +481,9 @@
     case SpvOpTypeForwardPointer:
       out = [](unsigned index) { return index == 0; };
       break;
+    case SpvOpTypeArray:
+      out = [](unsigned index) { return index == 1; };
+      break;
     default:
       out = [](unsigned) { return false; };
       break;
diff --git a/source/opt/CMakeLists.txt b/source/opt/CMakeLists.txt
index 96ee8b3..b02485a 100644
--- a/source/opt/CMakeLists.txt
+++ b/source/opt/CMakeLists.txt
@@ -15,10 +15,12 @@
   aggressive_dead_code_elim_pass.h
   basic_block.h
   block_merge_pass.h
+  block_merge_util.h
   build_module.h
   ccp_pass.h
   cfg_cleanup_pass.h
   cfg.h
+  code_sink.h
   combine_access_chains.h
   common_uniform_elim_pass.h
   compact_ids_pass.h
@@ -29,19 +31,24 @@
   dead_branch_elim_pass.h
   dead_insert_elim_pass.h
   dead_variable_elimination.h
+  decompose_initialized_variables_pass.h
   decoration_manager.h
   def_use_manager.h
   dominator_analysis.h
   dominator_tree.h
   eliminate_dead_constant_pass.h
   eliminate_dead_functions_pass.h
+  eliminate_dead_functions_util.h
+  eliminate_dead_members_pass.h
   feature_manager.h
+  fix_storage_class.h
   flatten_decoration_pass.h
   fold.h
   folding_rules.h
   fold_spec_constant_op_and_composite_pass.h
   freeze_spec_constant_value_pass.h
   function.h
+  generate_webgpu_initializers_pass.h
   if_conversion.h
   inline_exhaustive_pass.h
   inline_opaque_pass.h
@@ -92,6 +99,7 @@
   simplification_pass.h
   ssa_rewrite_pass.h
   strength_reduction_pass.h
+  strip_atomic_counter_memory_pass.h
   strip_debug_info_pass.h
   strip_reflect_info_pass.h
   struct_cfg_analysis.h
@@ -107,10 +115,12 @@
   aggressive_dead_code_elim_pass.cpp
   basic_block.cpp
   block_merge_pass.cpp
+  block_merge_util.cpp
   build_module.cpp
   ccp_pass.cpp
   cfg_cleanup_pass.cpp
   cfg.cpp
+  code_sink.cpp
   combine_access_chains.cpp
   common_uniform_elim_pass.cpp
   compact_ids_pass.cpp
@@ -121,19 +131,24 @@
   dead_branch_elim_pass.cpp
   dead_insert_elim_pass.cpp
   dead_variable_elimination.cpp
+  decompose_initialized_variables_pass.cpp
   decoration_manager.cpp
   def_use_manager.cpp
   dominator_analysis.cpp
   dominator_tree.cpp
   eliminate_dead_constant_pass.cpp
   eliminate_dead_functions_pass.cpp
+  eliminate_dead_functions_util.cpp
+  eliminate_dead_members_pass.cpp
   feature_manager.cpp
+  fix_storage_class.cpp
   flatten_decoration_pass.cpp
   fold.cpp
   folding_rules.cpp
   fold_spec_constant_op_and_composite_pass.cpp
   freeze_spec_constant_value_pass.cpp
   function.cpp
+  generate_webgpu_initializers_pass.cpp
   if_conversion.cpp
   inline_exhaustive_pass.cpp
   inline_opaque_pass.cpp
@@ -144,6 +159,7 @@
   instrument_pass.cpp
   ir_context.cpp
   ir_loader.cpp
+  legalize_vector_shuffle_pass.cpp
   licm_pass.cpp
   local_access_chain_convert_pass.cpp
   local_redundancy_elimination.cpp
@@ -181,6 +197,7 @@
   simplification_pass.cpp
   ssa_rewrite_pass.cpp
   strength_reduction_pass.cpp
+  strip_atomic_counter_memory_pass.cpp
   strip_debug_info_pass.cpp
   strip_reflect_info_pass.cpp
   struct_cfg_analysis.cpp
diff --git a/source/opt/aggressive_dead_code_elim_pass.cpp b/source/opt/aggressive_dead_code_elim_pass.cpp
index 82d7499..4063eb5 100644
--- a/source/opt/aggressive_dead_code_elim_pass.cpp
+++ b/source/opt/aggressive_dead_code_elim_pass.cpp
@@ -24,6 +24,7 @@
 #include "source/latest_version_glsl_std_450_header.h"
 #include "source/opt/iterator.h"
 #include "source/opt/reflect.h"
+#include "source/spirv_constant.h"
 
 namespace spvtools {
 namespace opt {
@@ -513,6 +514,26 @@
       AddBranch(mergeBlockId, *bi);
       for (++bi; (*bi)->id() != mergeBlockId; ++bi) {
       }
+
+      auto merge_terminator = (*bi)->terminator();
+      if (merge_terminator->opcode() == SpvOpUnreachable) {
+        // The merge was unreachable. This is undefined behaviour so just
+        // return (or return an undef). Then mark the new return as live.
+        auto func_ret_type_inst = get_def_use_mgr()->GetDef(func->type_id());
+        if (func_ret_type_inst->opcode() == SpvOpTypeVoid) {
+          merge_terminator->SetOpcode(SpvOpReturn);
+        } else {
+          // Find an undef for the return value and make sure it gets kept by
+          // the pass.
+          auto undef_id = Type2Undef(func->type_id());
+          auto undef = get_def_use_mgr()->GetDef(undef_id);
+          live_insts_.Set(undef->unique_id());
+          merge_terminator->SetOpcode(SpvOpReturnValue);
+          merge_terminator->SetInOperands({{SPV_OPERAND_TYPE_ID, {undef_id}}});
+          get_def_use_mgr()->AnalyzeInstUse(merge_terminator);
+        }
+        live_insts_.Set(merge_terminator->unique_id());
+      }
     } else {
       ++bi;
     }
@@ -528,7 +549,26 @@
   }
   // Keep all entry points.
   for (auto& entry : get_module()->entry_points()) {
-    AddToWorklist(&entry);
+    if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4)) {
+      // In SPIR-V 1.4 and later, entry points must list all global variables
+      // used. DCE can still remove non-input/output variables and update the
+      // interface list. Mark the entry point as live and inputs and outputs as
+      // live, but defer decisions all other interfaces.
+      live_insts_.Set(entry.unique_id());
+      // The actual function is live always.
+      AddToWorklist(
+          get_def_use_mgr()->GetDef(entry.GetSingleWordInOperand(1u)));
+      for (uint32_t i = 3; i < entry.NumInOperands(); ++i) {
+        auto* var = get_def_use_mgr()->GetDef(entry.GetSingleWordInOperand(i));
+        auto storage_class = var->GetSingleWordInOperand(0u);
+        if (storage_class == SpvStorageClassInput ||
+            storage_class == SpvStorageClassOutput) {
+          AddToWorklist(var);
+        }
+      }
+    } else {
+      AddToWorklist(&entry);
+    }
   }
   // Keep workgroup size.
   for (auto& anno : get_module()->annotations()) {
@@ -546,11 +586,19 @@
   // TODO(greg-lunarg): Handle additional capabilities
   if (!context()->get_feature_mgr()->HasCapability(SpvCapabilityShader))
     return Status::SuccessWithoutChange;
+
   // Current functionality assumes relaxed logical addressing (see
   // instruction.h)
   // TODO(greg-lunarg): Handle non-logical addressing
   if (context()->get_feature_mgr()->HasCapability(SpvCapabilityAddresses))
     return Status::SuccessWithoutChange;
+
+  // The variable pointer extension is no longer needed to use the capability,
+  // so we have to look for the capability.
+  if (context()->get_feature_mgr()->HasCapability(
+          SpvCapabilityVariablePointersStorageBuffer))
+    return Status::SuccessWithoutChange;
+
   // If any extensions in the module are not explicitly supported,
   // return unmodified.
   if (!AllExtensionsSupported()) return Status::SuccessWithoutChange;
@@ -752,6 +800,29 @@
     }
   }
 
+  if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4)) {
+    // Remove the dead interface variables from the entry point interface list.
+    for (auto& entry : get_module()->entry_points()) {
+      std::vector<Operand> new_operands;
+      for (uint32_t i = 0; i < entry.NumInOperands(); ++i) {
+        if (i < 3) {
+          // Execution model, function id and name are always valid.
+          new_operands.push_back(entry.GetInOperand(i));
+        } else {
+          auto* var =
+              get_def_use_mgr()->GetDef(entry.GetSingleWordInOperand(i));
+          if (!IsDead(var)) {
+            new_operands.push_back(entry.GetInOperand(i));
+          }
+        }
+      }
+      if (new_operands.size() != entry.NumInOperands()) {
+        entry.SetInOperands(std::move(new_operands));
+        get_def_use_mgr()->UpdateDefUse(&entry);
+      }
+    }
+  }
+
   return modified;
 }
 
diff --git a/source/opt/aggressive_dead_code_elim_pass.h b/source/opt/aggressive_dead_code_elim_pass.h
index b4b34b0..c043a96 100644
--- a/source/opt/aggressive_dead_code_elim_pass.h
+++ b/source/opt/aggressive_dead_code_elim_pass.h
@@ -49,7 +49,9 @@
   Status Process() override;
 
   IRContext::Analysis GetPreservedAnalyses() override {
-    return IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping;
+    return IRContext::kAnalysisDefUse |
+           IRContext::kAnalysisInstrToBlockMapping |
+           IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
   }
 
  private:
diff --git a/source/opt/basic_block.cpp b/source/opt/basic_block.cpp
index aafee51..3608448 100644
--- a/source/opt/basic_block.cpp
+++ b/source/opt/basic_block.cpp
@@ -108,21 +108,29 @@
 
 void BasicBlock::ForEachSuccessorLabel(
     const std::function<void(const uint32_t)>& f) const {
+  WhileEachSuccessorLabel([f](const uint32_t l) {
+    f(l);
+    return true;
+  });
+}
+
+bool BasicBlock::WhileEachSuccessorLabel(
+    const std::function<bool(const uint32_t)>& f) const {
   const auto br = &insts_.back();
   switch (br->opcode()) {
-    case SpvOpBranch: {
-      f(br->GetOperand(0).words[0]);
-    } break;
+    case SpvOpBranch:
+      return f(br->GetOperand(0).words[0]);
     case SpvOpBranchConditional:
     case SpvOpSwitch: {
       bool is_first = true;
-      br->ForEachInId([&is_first, &f](const uint32_t* idp) {
-        if (!is_first) f(*idp);
+      return br->WhileEachInId([&is_first, &f](const uint32_t* idp) {
+        if (!is_first) return f(*idp);
         is_first = false;
+        return true;
       });
-    } break;
+    }
     default:
-      break;
+      return true;
   }
 }
 
@@ -184,6 +192,12 @@
   return mbid;
 }
 
+uint32_t BasicBlock::MergeBlockId() const {
+  uint32_t mbid = MergeBlockIdIfAny();
+  assert(mbid && "Expected block to have a corresponding merge block");
+  return mbid;
+}
+
 uint32_t BasicBlock::ContinueBlockIdIfAny() const {
   auto merge_ii = cend();
   --merge_ii;
@@ -197,6 +211,12 @@
   return cbid;
 }
 
+uint32_t BasicBlock::ContinueBlockId() const {
+  uint32_t cbid = ContinueBlockIdIfAny();
+  assert(cbid && "Expected block to have a corresponding continue target");
+  return cbid;
+}
+
 std::ostream& operator<<(std::ostream& str, const BasicBlock& block) {
   str << block.PrettyPrint();
   return str;
diff --git a/source/opt/basic_block.h b/source/opt/basic_block.h
index ff3a412..0bab337 100644
--- a/source/opt/basic_block.h
+++ b/source/opt/basic_block.h
@@ -160,6 +160,11 @@
   void ForEachSuccessorLabel(
       const std::function<void(const uint32_t)>& f) const;
 
+  // Runs the given function |f| on each label id of each successor block.  If
+  // |f| returns false, iteration is terminated and this function returns false.
+  bool WhileEachSuccessorLabel(
+      const std::function<bool(const uint32_t)>& f) const;
+
   // Runs the given function |f| on each label id of each successor block.
   // Modifying the pointed value will change the branch taken by the basic
   // block. It is the caller responsibility to update or invalidate the CFG.
@@ -183,10 +188,16 @@
   // block, if any.  If none, returns zero.
   uint32_t MergeBlockIdIfAny() const;
 
+  // Returns MergeBlockIdIfAny() and asserts that it is non-zero.
+  uint32_t MergeBlockId() const;
+
   // Returns the ID of the continue block declared by a merge instruction in
   // this block, if any.  If none, returns zero.
   uint32_t ContinueBlockIdIfAny() const;
 
+  // Returns ContinueBlockIdIfAny() and asserts that it is non-zero.
+  uint32_t ContinueBlockId() const;
+
   // Returns the terminator instruction.  Assumes the terminator exists.
   Instruction* terminator() { return &*tail(); }
   const Instruction* terminator() const { return &*ctail(); }
diff --git a/source/opt/block_merge_pass.cpp b/source/opt/block_merge_pass.cpp
index 09deb21..c7315ba 100644
--- a/source/opt/block_merge_pass.cpp
+++ b/source/opt/block_merge_pass.cpp
@@ -18,6 +18,7 @@
 
 #include <vector>
 
+#include "source/opt/block_merge_util.h"
 #include "source/opt/ir_context.h"
 #include "source/opt/iterator.h"
 
@@ -27,112 +28,17 @@
 bool BlockMergePass::MergeBlocks(Function* func) {
   bool modified = false;
   for (auto bi = func->begin(); bi != func->end();) {
-    // Find block with single successor which has no other predecessors.
-    auto ii = bi->end();
-    --ii;
-    Instruction* br = &*ii;
-    if (br->opcode() != SpvOpBranch) {
+    if (blockmergeutil::CanMergeWithSuccessor(context(), &*bi)) {
+      blockmergeutil::MergeWithSuccessor(context(), func, bi);
+      // Reprocess block.
+      modified = true;
+    } else {
       ++bi;
-      continue;
     }
-
-    const uint32_t lab_id = br->GetSingleWordInOperand(0);
-    if (cfg()->preds(lab_id).size() != 1) {
-      ++bi;
-      continue;
-    }
-
-    bool pred_is_merge = IsMerge(&*bi);
-    bool succ_is_merge = IsMerge(lab_id);
-    if (pred_is_merge && succ_is_merge) {
-      // Cannot merge two merges together.
-      ++bi;
-      continue;
-    }
-
-    Instruction* merge_inst = bi->GetMergeInst();
-    bool pred_is_header = IsHeader(&*bi);
-    if (pred_is_header && lab_id != merge_inst->GetSingleWordInOperand(0u)) {
-      bool succ_is_header = IsHeader(lab_id);
-      if (pred_is_header && succ_is_header) {
-        // Cannot merge two headers together when the successor is not the merge
-        // block of the predecessor.
-        ++bi;
-        continue;
-      }
-
-      // If this is a header block and the successor is not its merge, we must
-      // be careful about which blocks we are willing to merge together.
-      // OpLoopMerge must be followed by a conditional or unconditional branch.
-      // The merge must be a loop merge because a selection merge cannot be
-      // followed by an unconditional branch.
-      BasicBlock* succ_block = context()->get_instr_block(lab_id);
-      SpvOp succ_term_op = succ_block->terminator()->opcode();
-      assert(merge_inst->opcode() == SpvOpLoopMerge);
-      if (succ_term_op != SpvOpBranch &&
-          succ_term_op != SpvOpBranchConditional) {
-        ++bi;
-        continue;
-      }
-    }
-
-    // Merge blocks.
-    context()->KillInst(br);
-    auto sbi = bi;
-    for (; sbi != func->end(); ++sbi)
-      if (sbi->id() == lab_id) break;
-    // If bi is sbi's only predecessor, it dominates sbi and thus
-    // sbi must follow bi in func's ordering.
-    assert(sbi != func->end());
-
-    // Update the inst-to-block mapping for the instructions in sbi.
-    for (auto& inst : *sbi) {
-      context()->set_instr_block(&inst, &*bi);
-    }
-
-    // Now actually move the instructions.
-    bi->AddInstructions(&*sbi);
-
-    if (merge_inst) {
-      if (pred_is_header && lab_id == merge_inst->GetSingleWordInOperand(0u)) {
-        // Merging the header and merge blocks, so remove the structured control
-        // flow declaration.
-        context()->KillInst(merge_inst);
-      } else {
-        // Move the merge instruction to just before the terminator.
-        merge_inst->InsertBefore(bi->terminator());
-      }
-    }
-    context()->ReplaceAllUsesWith(lab_id, bi->id());
-    context()->KillInst(sbi->GetLabelInst());
-    (void)sbi.Erase();
-    // Reprocess block.
-    modified = true;
   }
   return modified;
 }
 
-bool BlockMergePass::IsHeader(BasicBlock* block) {
-  return block->GetMergeInst() != nullptr;
-}
-
-bool BlockMergePass::IsHeader(uint32_t id) {
-  return IsHeader(context()->get_instr_block(get_def_use_mgr()->GetDef(id)));
-}
-
-bool BlockMergePass::IsMerge(uint32_t id) {
-  return !get_def_use_mgr()->WhileEachUse(id, [](Instruction* user,
-                                                 uint32_t index) {
-    SpvOp op = user->opcode();
-    if ((op == SpvOpLoopMerge || op == SpvOpSelectionMerge) && index == 0u) {
-      return false;
-    }
-    return true;
-  });
-}
-
-bool BlockMergePass::IsMerge(BasicBlock* block) { return IsMerge(block->id()); }
-
 Pass::Status BlockMergePass::Process() {
   // Process all entry point functions.
   ProcessFunction pfn = [this](Function* fp) { return MergeBlocks(fp); };
diff --git a/source/opt/block_merge_pass.h b/source/opt/block_merge_pass.h
index be14b02..aabf789 100644
--- a/source/opt/block_merge_pass.h
+++ b/source/opt/block_merge_pass.h
@@ -44,7 +44,8 @@
     return IRContext::kAnalysisDefUse |
            IRContext::kAnalysisInstrToBlockMapping |
            IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
-           IRContext::kAnalysisNameMap;
+           IRContext::kAnalysisNameMap | IRContext::kAnalysisConstants |
+           IRContext::kAnalysisTypes;
   }
 
  private:
@@ -53,14 +54,6 @@
   // with no other predecessors. Merge these blocks into a single block.
   bool MergeBlocks(Function* func);
 
-  // Returns true if |block| (or |id|) contains a merge instruction.
-  bool IsHeader(BasicBlock* block);
-  bool IsHeader(uint32_t id);
-
-  // Returns true if |block| (or |id|) is the merge target of a merge
-  // instruction.
-  bool IsMerge(BasicBlock* block);
-  bool IsMerge(uint32_t id);
 };
 
 }  // namespace opt
diff --git a/source/opt/block_merge_util.cpp b/source/opt/block_merge_util.cpp
new file mode 100644
index 0000000..107723d
--- /dev/null
+++ b/source/opt/block_merge_util.cpp
@@ -0,0 +1,168 @@
+// Copyright (c) 2017 The Khronos Group Inc.
+// Copyright (c) 2017 Valve Corporation
+// Copyright (c) 2017 LunarG Inc.
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "block_merge_util.h"
+
+namespace spvtools {
+namespace opt {
+namespace blockmergeutil {
+
+namespace {
+
+// Returns true if |block| contains a merge instruction.
+bool IsHeader(BasicBlock* block) { return block->GetMergeInst() != nullptr; }
+
+// Returns true if |id| contains a merge instruction.
+bool IsHeader(IRContext* context, uint32_t id) {
+  return IsHeader(
+      context->get_instr_block(context->get_def_use_mgr()->GetDef(id)));
+}
+
+// Returns true if |id| is the merge target of a merge instruction.
+bool IsMerge(IRContext* context, uint32_t id) {
+  return !context->get_def_use_mgr()->WhileEachUse(id, [](Instruction* user,
+                                                          uint32_t index) {
+    SpvOp op = user->opcode();
+    if ((op == SpvOpLoopMerge || op == SpvOpSelectionMerge) && index == 0u) {
+      return false;
+    }
+    return true;
+  });
+}
+
+// Returns true if |block| is the merge target of a merge instruction.
+bool IsMerge(IRContext* context, BasicBlock* block) {
+  return IsMerge(context, block->id());
+}
+
+// Removes any OpPhi instructions in |block|, which should have exactly one
+// predecessor, replacing uses of OpPhi ids with the ids associated with the
+// predecessor.
+void EliminateOpPhiInstructions(IRContext* context, BasicBlock* block) {
+  block->ForEachPhiInst([context](Instruction* phi) {
+    assert(2 == phi->NumInOperands() &&
+           "A block can only have one predecessor for block merging to make "
+           "sense.");
+    context->ReplaceAllUsesWith(phi->result_id(),
+                                phi->GetSingleWordInOperand(0));
+    context->KillInst(phi);
+  });
+}
+
+}  // Anonymous namespace
+
+bool CanMergeWithSuccessor(IRContext* context, BasicBlock* block) {
+  // Find block with single successor which has no other predecessors.
+  auto ii = block->end();
+  --ii;
+  Instruction* br = &*ii;
+  if (br->opcode() != SpvOpBranch) {
+    return false;
+  }
+
+  const uint32_t lab_id = br->GetSingleWordInOperand(0);
+  if (context->cfg()->preds(lab_id).size() != 1) {
+    return false;
+  }
+
+  bool pred_is_merge = IsMerge(context, block);
+  bool succ_is_merge = IsMerge(context, lab_id);
+  if (pred_is_merge && succ_is_merge) {
+    // Cannot merge two merges together.
+    return false;
+  }
+
+  // Don't bother trying to merge unreachable blocks.
+  if (auto dominators = context->GetDominatorAnalysis(block->GetParent())) {
+    if (!dominators->IsReachable(block)) return false;
+  }
+
+  Instruction* merge_inst = block->GetMergeInst();
+  const bool pred_is_header = IsHeader(block);
+  if (pred_is_header && lab_id != merge_inst->GetSingleWordInOperand(0u)) {
+    bool succ_is_header = IsHeader(context, lab_id);
+    if (pred_is_header && succ_is_header) {
+      // Cannot merge two headers together when the successor is not the merge
+      // block of the predecessor.
+      return false;
+    }
+
+    // If this is a header block and the successor is not its merge, we must
+    // be careful about which blocks we are willing to merge together.
+    // OpLoopMerge must be followed by a conditional or unconditional branch.
+    // The merge must be a loop merge because a selection merge cannot be
+    // followed by an unconditional branch.
+    BasicBlock* succ_block = context->get_instr_block(lab_id);
+    SpvOp succ_term_op = succ_block->terminator()->opcode();
+    assert(merge_inst->opcode() == SpvOpLoopMerge);
+    if (succ_term_op != SpvOpBranch && succ_term_op != SpvOpBranchConditional) {
+      return false;
+    }
+  }
+  return true;
+}
+
+void MergeWithSuccessor(IRContext* context, Function* func,
+                        Function::iterator bi) {
+  assert(CanMergeWithSuccessor(context, &*bi) &&
+         "Precondition failure for MergeWithSuccessor: it must be legal to "
+         "merge the block and its successor.");
+
+  auto ii = bi->end();
+  --ii;
+  Instruction* br = &*ii;
+  const uint32_t lab_id = br->GetSingleWordInOperand(0);
+  Instruction* merge_inst = bi->GetMergeInst();
+  bool pred_is_header = IsHeader(&*bi);
+
+  // Merge blocks.
+  context->KillInst(br);
+  auto sbi = bi;
+  for (; sbi != func->end(); ++sbi)
+    if (sbi->id() == lab_id) break;
+  // If bi is sbi's only predecessor, it dominates sbi and thus
+  // sbi must follow bi in func's ordering.
+  assert(sbi != func->end());
+
+  // Update the inst-to-block mapping for the instructions in sbi.
+  for (auto& inst : *sbi) {
+    context->set_instr_block(&inst, &*bi);
+  }
+
+  EliminateOpPhiInstructions(context, &*sbi);
+
+  // Now actually move the instructions.
+  bi->AddInstructions(&*sbi);
+
+  if (merge_inst) {
+    if (pred_is_header && lab_id == merge_inst->GetSingleWordInOperand(0u)) {
+      // Merging the header and merge blocks, so remove the structured control
+      // flow declaration.
+      context->KillInst(merge_inst);
+    } else {
+      // Move the merge instruction to just before the terminator.
+      merge_inst->InsertBefore(bi->terminator());
+    }
+  }
+  context->ReplaceAllUsesWith(lab_id, bi->id());
+  context->KillInst(sbi->GetLabelInst());
+  (void)sbi.Erase();
+}
+
+}  // namespace blockmergeutil
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/block_merge_util.h b/source/opt/block_merge_util.h
new file mode 100644
index 0000000..e71e3d6
--- /dev/null
+++ b/source/opt/block_merge_util.h
@@ -0,0 +1,44 @@
+// Copyright (c) 2017 The Khronos Group Inc.
+// Copyright (c) 2017 Valve Corporation
+// Copyright (c) 2017 LunarG Inc.
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_BLOCK_MERGE_UTIL_H_
+#define SOURCE_OPT_BLOCK_MERGE_UTIL_H_
+
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace opt {
+
+// Provides functions for determining when it is safe to merge blocks, and for
+// actually merging blocks, for use by various analyses and passes.
+namespace blockmergeutil {
+
+// Returns true if and only if |block| has exactly one successor and merging
+// this successor into |block| has no impact on the semantics or validity of the
+// SPIR-V module.
+bool CanMergeWithSuccessor(IRContext* context, BasicBlock* block);
+
+// Requires that |bi| has a successor that can be safely merged into |bi|, and
+// performs the merge.
+void MergeWithSuccessor(IRContext* context, Function* func,
+                        Function::iterator bi);
+
+}  // namespace blockmergeutil
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  //  SOURCE_OPT_BLOCK_MERGE_UTIL_H_
diff --git a/source/opt/ccp_pass.cpp b/source/opt/ccp_pass.cpp
index 8356195..2de9250 100644
--- a/source/opt/ccp_pass.cpp
+++ b/source/opt/ccp_pass.cpp
@@ -271,6 +271,7 @@
     uint32_t id = it.first;
     uint32_t cst_id = it.second;
     if (!IsVaryingValue(cst_id) && id != cst_id) {
+      context()->KillNamesAndDecorates(id);
       retval |= context()->ReplaceAllUsesWith(id, cst_id);
     }
   }
diff --git a/source/opt/ccp_pass.h b/source/opt/ccp_pass.h
index 178fd12..527f459 100644
--- a/source/opt/ccp_pass.h
+++ b/source/opt/ccp_pass.h
@@ -40,7 +40,8 @@
            IRContext::kAnalysisInstrToBlockMapping |
            IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
            IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
-           IRContext::kAnalysisNameMap;
+           IRContext::kAnalysisNameMap | IRContext::kAnalysisConstants |
+           IRContext::kAnalysisTypes;
   }
 
  private:
diff --git a/source/opt/cfg.cpp b/source/opt/cfg.cpp
index 7e1097e..72693cb 100644
--- a/source/opt/cfg.cpp
+++ b/source/opt/cfg.cpp
@@ -150,15 +150,25 @@
 void CFG::ComputePostOrderTraversal(BasicBlock* bb,
                                     std::vector<BasicBlock*>* order,
                                     std::unordered_set<BasicBlock*>* seen) {
-  seen->insert(bb);
-  static_cast<const BasicBlock*>(bb)->ForEachSuccessorLabel(
-      [&order, &seen, this](const uint32_t sbid) {
-        BasicBlock* succ_bb = id2block_[sbid];
-        if (!seen->count(succ_bb)) {
-          ComputePostOrderTraversal(succ_bb, order, seen);
-        }
-      });
-  order->push_back(bb);
+  std::vector<BasicBlock*> stack;
+  stack.push_back(bb);
+  while (!stack.empty()) {
+    bb = stack.back();
+    seen->insert(bb);
+    static_cast<const BasicBlock*>(bb)->WhileEachSuccessorLabel(
+        [&seen, &stack, this](const uint32_t sbid) {
+          BasicBlock* succ_bb = id2block_[sbid];
+          if (!seen->count(succ_bb)) {
+            stack.push_back(succ_bb);
+            return false;
+          }
+          return true;
+        });
+    if (stack.back() == bb) {
+      order->push_back(bb);
+      stack.pop_back();
+    }
+  }
 }
 
 BasicBlock* CFG::SplitLoopHeader(BasicBlock* bb) {
diff --git a/source/opt/cfg_cleanup_pass.h b/source/opt/cfg_cleanup_pass.h
index afbc67c..5095428 100644
--- a/source/opt/cfg_cleanup_pass.h
+++ b/source/opt/cfg_cleanup_pass.h
@@ -30,7 +30,8 @@
   Status Process() override;
 
   IRContext::Analysis GetPreservedAnalyses() override {
-    return IRContext::kAnalysisDefUse;
+    return IRContext::kAnalysisDefUse | IRContext::kAnalysisConstants |
+           IRContext::kAnalysisTypes;
   }
 };
 
diff --git a/source/opt/code_sink.cpp b/source/opt/code_sink.cpp
new file mode 100644
index 0000000..9d54ee5
--- /dev/null
+++ b/source/opt/code_sink.cpp
@@ -0,0 +1,319 @@
+// 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 "code_sink.h"
+
+#include <set>
+#include <vector>
+
+#include "source/opt/instruction.h"
+#include "source/opt/ir_builder.h"
+#include "source/opt/ir_context.h"
+#include "source/util/bit_vector.h"
+
+namespace spvtools {
+namespace opt {
+
+Pass::Status CodeSinkingPass::Process() {
+  bool modified = false;
+  for (Function& function : *get_module()) {
+    cfg()->ForEachBlockInPostOrder(function.entry().get(),
+                                   [&modified, this](BasicBlock* bb) {
+                                     if (SinkInstructionsInBB(bb)) {
+                                       modified = true;
+                                     }
+                                   });
+  }
+  return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
+}
+
+bool CodeSinkingPass::SinkInstructionsInBB(BasicBlock* bb) {
+  bool modified = false;
+  for (auto inst = bb->rbegin(); inst != bb->rend(); ++inst) {
+    if (SinkInstruction(&*inst)) {
+      inst = bb->rbegin();
+      modified = true;
+    }
+  }
+  return modified;
+}
+
+bool CodeSinkingPass::SinkInstruction(Instruction* inst) {
+  if (inst->opcode() != SpvOpLoad && inst->opcode() != SpvOpAccessChain) {
+    return false;
+  }
+
+  if (ReferencesMutableMemory(inst)) {
+    return false;
+  }
+
+  if (BasicBlock* target_bb = FindNewBasicBlockFor(inst)) {
+    Instruction* pos = &*target_bb->begin();
+    while (pos->opcode() == SpvOpPhi) {
+      pos = pos->NextNode();
+    }
+
+    inst->InsertBefore(pos);
+    context()->set_instr_block(inst, target_bb);
+    return true;
+  }
+  return false;
+}
+
+BasicBlock* CodeSinkingPass::FindNewBasicBlockFor(Instruction* inst) {
+  assert(inst->result_id() != 0 && "Instruction should have a result.");
+  BasicBlock* original_bb = context()->get_instr_block(inst);
+  BasicBlock* bb = original_bb;
+
+  std::unordered_set<uint32_t> bbs_with_uses;
+  get_def_use_mgr()->ForEachUse(
+      inst, [&bbs_with_uses, this](Instruction* use, uint32_t idx) {
+        if (use->opcode() != SpvOpPhi) {
+          BasicBlock* use_bb = context()->get_instr_block(use);
+          if (use_bb) {
+            bbs_with_uses.insert(use_bb->id());
+          }
+        } else {
+          bbs_with_uses.insert(use->GetSingleWordOperand(idx + 1));
+        }
+      });
+
+  while (true) {
+    // If |inst| is used in |bb|, then |inst| cannot be moved any further.
+    if (bbs_with_uses.count(bb->id())) {
+      break;
+    }
+
+    // If |bb| has one successor (succ_bb), and |bb| is the only predecessor
+    // of succ_bb, then |inst| can be moved to succ_bb.  If succ_bb, has move
+    // then one predecessor, then moving |inst| into succ_bb could cause it to
+    // be executed more often, so the search has to stop.
+    if (bb->terminator()->opcode() == SpvOpBranch) {
+      uint32_t succ_bb_id = bb->terminator()->GetSingleWordInOperand(0);
+      if (cfg()->preds(succ_bb_id).size() == 1) {
+        bb = context()->get_instr_block(succ_bb_id);
+        continue;
+      } else {
+        break;
+      }
+    }
+
+    // The remaining checks need to know the merge node.  If there is no merge
+    // instruction or an OpLoopMerge, then it is a break or continue.  We could
+    // figure it out, but not worth doing it now.
+    Instruction* merge_inst = bb->GetMergeInst();
+    if (merge_inst == nullptr || merge_inst->opcode() != SpvOpSelectionMerge) {
+      break;
+    }
+
+    // Check all of the successors of |bb| it see which lead to a use of |inst|
+    // before reaching the merge node.
+    bool used_in_multiple_blocks = false;
+    uint32_t bb_used_in = 0;
+    bb->ForEachSuccessorLabel([this, bb, &bb_used_in, &used_in_multiple_blocks,
+                               &bbs_with_uses](uint32_t* succ_bb_id) {
+      if (IntersectsPath(*succ_bb_id, bb->MergeBlockIdIfAny(), bbs_with_uses)) {
+        if (bb_used_in == 0) {
+          bb_used_in = *succ_bb_id;
+        } else {
+          used_in_multiple_blocks = true;
+        }
+      }
+    });
+
+    // If more than one successor, which is not the merge block, uses |inst|
+    // then we have to leave |inst| in bb because there is none of the
+    // successors dominate all uses of |inst|.
+    if (used_in_multiple_blocks) {
+      break;
+    }
+
+    if (bb_used_in == 0) {
+      // If |inst| is not used before reaching the merge node, then we can move
+      // |inst| to the merge node.
+      bb = context()->get_instr_block(bb->MergeBlockIdIfAny());
+    } else {
+      // If the only successor that leads to a used of |inst| has more than 1
+      // predecessor, then moving |inst| could cause it to be executed more
+      // often, so we cannot move it.
+      if (cfg()->preds(bb_used_in).size() != 1) {
+        break;
+      }
+
+      // If |inst| is used after the merge block, then |bb_used_in| does not
+      // dominate all of the uses.  So we cannot move |inst| any further.
+      if (IntersectsPath(bb->MergeBlockIdIfAny(), original_bb->id(),
+                         bbs_with_uses)) {
+        break;
+      }
+
+      // Otherwise, |bb_used_in| dominates all uses, so move |inst| into that
+      // block.
+      bb = context()->get_instr_block(bb_used_in);
+    }
+    continue;
+  }
+  return (bb != original_bb ? bb : nullptr);
+}
+
+bool CodeSinkingPass::ReferencesMutableMemory(Instruction* inst) {
+  if (!inst->IsLoad()) {
+    return false;
+  }
+
+  Instruction* base_ptr = inst->GetBaseAddress();
+  if (base_ptr->opcode() != SpvOpVariable) {
+    return true;
+  }
+
+  if (base_ptr->IsReadOnlyVariable()) {
+    return false;
+  }
+
+  if (HasUniformMemorySync()) {
+    return true;
+  }
+
+  if (base_ptr->GetSingleWordInOperand(0) != SpvStorageClassUniform) {
+    return true;
+  }
+
+  return HasPossibleStore(base_ptr);
+}
+
+bool CodeSinkingPass::HasUniformMemorySync() {
+  if (checked_for_uniform_sync_) {
+    return has_uniform_sync_;
+  }
+
+  bool has_sync = false;
+  get_module()->ForEachInst([this, &has_sync](Instruction* inst) {
+    switch (inst->opcode()) {
+      case SpvOpMemoryBarrier: {
+        uint32_t mem_semantics_id = inst->GetSingleWordInOperand(1);
+        if (IsSyncOnUniform(mem_semantics_id)) {
+          has_sync = true;
+        }
+        break;
+      }
+      case SpvOpControlBarrier:
+      case SpvOpAtomicLoad:
+      case SpvOpAtomicStore:
+      case SpvOpAtomicExchange:
+      case SpvOpAtomicIIncrement:
+      case SpvOpAtomicIDecrement:
+      case SpvOpAtomicIAdd:
+      case SpvOpAtomicISub:
+      case SpvOpAtomicSMin:
+      case SpvOpAtomicUMin:
+      case SpvOpAtomicSMax:
+      case SpvOpAtomicUMax:
+      case SpvOpAtomicAnd:
+      case SpvOpAtomicOr:
+      case SpvOpAtomicXor:
+      case SpvOpAtomicFlagTestAndSet:
+      case SpvOpAtomicFlagClear: {
+        uint32_t mem_semantics_id = inst->GetSingleWordInOperand(2);
+        if (IsSyncOnUniform(mem_semantics_id)) {
+          has_sync = true;
+        }
+        break;
+      }
+      case SpvOpAtomicCompareExchange:
+      case SpvOpAtomicCompareExchangeWeak:
+        if (IsSyncOnUniform(inst->GetSingleWordInOperand(2)) ||
+            IsSyncOnUniform(inst->GetSingleWordInOperand(3))) {
+          has_sync = true;
+        }
+        break;
+      default:
+        break;
+    }
+  });
+  has_uniform_sync_ = has_sync;
+  return has_sync;
+}
+
+bool CodeSinkingPass::IsSyncOnUniform(uint32_t mem_semantics_id) const {
+  const analysis::Constant* mem_semantics_const =
+      context()->get_constant_mgr()->FindDeclaredConstant(mem_semantics_id);
+  assert(mem_semantics_const != nullptr &&
+         "Expecting memory semantics id to be a constant.");
+  assert(mem_semantics_const->AsIntConstant() &&
+         "Memory semantics should be an integer.");
+  uint32_t mem_semantics_int = mem_semantics_const->GetU32();
+
+  // If it does not affect uniform memory, then it is does not apply to uniform
+  // memory.
+  if ((mem_semantics_int & SpvMemorySemanticsUniformMemoryMask) == 0) {
+    return false;
+  }
+
+  // Check if there is an acquire or release.  If so not, this it does not add
+  // any memory constraints.
+  return (mem_semantics_int & (SpvMemorySemanticsAcquireMask |
+                               SpvMemorySemanticsAcquireReleaseMask |
+                               SpvMemorySemanticsReleaseMask)) != 0;
+}
+
+bool CodeSinkingPass::HasPossibleStore(Instruction* var_inst) {
+  assert(var_inst->opcode() == SpvOpVariable ||
+         var_inst->opcode() == SpvOpAccessChain ||
+         var_inst->opcode() == SpvOpPtrAccessChain);
+
+  return get_def_use_mgr()->WhileEachUser(var_inst, [this](Instruction* use) {
+    switch (use->opcode()) {
+      case SpvOpStore:
+        return true;
+      case SpvOpAccessChain:
+      case SpvOpPtrAccessChain:
+        return HasPossibleStore(use);
+      default:
+        return false;
+    }
+  });
+}
+
+bool CodeSinkingPass::IntersectsPath(uint32_t start, uint32_t end,
+                                     const std::unordered_set<uint32_t>& set) {
+  std::vector<uint32_t> worklist;
+  worklist.push_back(start);
+  std::unordered_set<uint32_t> already_done;
+  already_done.insert(start);
+
+  while (!worklist.empty()) {
+    BasicBlock* bb = context()->get_instr_block(worklist.back());
+    worklist.pop_back();
+
+    if (bb->id() == end) {
+      continue;
+    }
+
+    if (set.count(bb->id())) {
+      return true;
+    }
+
+    bb->ForEachSuccessorLabel([&already_done, &worklist](uint32_t* succ_bb_id) {
+      if (already_done.insert(*succ_bb_id).second) {
+        worklist.push_back(*succ_bb_id);
+      }
+    });
+  }
+  return false;
+}
+
+// namespace opt
+
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/code_sink.h b/source/opt/code_sink.h
new file mode 100644
index 0000000..d24df03
--- /dev/null
+++ b/source/opt/code_sink.h
@@ -0,0 +1,107 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_CODE_SINK_H_
+#define SOURCE_OPT_CODE_SINK_H_
+
+#include <unordered_map>
+
+#include "source/opt/ir_context.h"
+#include "source/opt/module.h"
+#include "source/opt/pass.h"
+
+namespace spvtools {
+namespace opt {
+
+// This pass does code sinking for OpAccessChain and OpLoad on variables in
+// uniform storage or in read only memory.  Code sinking is a transformation
+// where an instruction is moved into a more deeply nested construct.
+//
+// The goal is to move these instructions as close as possible to their uses
+// without having to execute them more often or to replicate the instruction.
+// Moving the instruction in this way can lead to shorter live ranges, which can
+// lead to less register pressure.  It can also cause instructions to be
+// executed less often because they could be moved into one path of a selection
+// construct.
+//
+// This optimization can cause register pressure to rise if the operands of the
+// instructions go dead after the instructions being moved. That is why we only
+// move certain OpLoad and OpAccessChain instructions.  They generally have
+// constants, loop induction variables, and global pointers as operands.  The
+// operands are live for a longer time in most cases.
+class CodeSinkingPass : public Pass {
+ public:
+  const char* name() const override { return "code-sink"; }
+  Status Process() override;
+
+  // Return the mask of preserved Analyses.
+  IRContext::Analysis GetPreservedAnalyses() override {
+    return IRContext::kAnalysisDefUse |
+           IRContext::kAnalysisInstrToBlockMapping |
+           IRContext::kAnalysisCombinators | IRContext::kAnalysisCFG |
+           IRContext::kAnalysisDominatorAnalysis |
+           IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap |
+           IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
+  }
+
+ private:
+  // Sinks the instructions in |bb| as much as possible.  Returns true if
+  // something changes.
+  bool SinkInstructionsInBB(BasicBlock* bb);
+
+  // Tries the sink |inst| as much as possible.  Returns true if the instruction
+  // is moved.
+  bool SinkInstruction(Instruction* inst);
+
+  // Returns the basic block in which to move |inst| to move is as close as
+  // possible to the uses of |inst| without increasing the number of times
+  // |inst| will be executed.  Return |nullptr| if there is no need to move
+  // |inst|.
+  BasicBlock* FindNewBasicBlockFor(Instruction* inst);
+
+  // Return true if |inst| reference memory and it is possible that the data in
+  // the memory changes at some point.
+  bool ReferencesMutableMemory(Instruction* inst);
+
+  // Returns true if the module contains an instruction that has a memory
+  // semantics id as an operand, and the memory semantics enforces a
+  // synchronization of uniform memory.  See section 3.25 of the SPIR-V
+  // specification.
+  bool HasUniformMemorySync();
+
+  // Returns true if there may be a store to the variable |var_inst|.
+  bool HasPossibleStore(Instruction* var_inst);
+
+  // Returns true if one of the basic blocks in |set| exists on a path from the
+  // basic block |start| to |end|.
+  bool IntersectsPath(uint32_t start, uint32_t end,
+                      const std::unordered_set<uint32_t>& set);
+
+  // Returns true if |mem_semantics_id| is the id of a constant that, when
+  // interpreted as a memory semantics mask enforces synchronization of uniform
+  // memory.  See section 3.25 of the SPIR-V specification.
+  bool IsSyncOnUniform(uint32_t mem_semantics_id) const;
+
+  // True if a check has for uniform storage has taken place.
+  bool checked_for_uniform_sync_;
+
+  // Cache of whether or not the module has a memory sync on uniform storage.
+  // only valid if |check_for_uniform_sync_| is true.
+  bool has_uniform_sync_;
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // SOURCE_OPT_CODE_SINK_H_
diff --git a/source/opt/combine_access_chains.h b/source/opt/combine_access_chains.h
index 75885da..531209e 100644
--- a/source/opt/combine_access_chains.h
+++ b/source/opt/combine_access_chains.h
@@ -33,7 +33,8 @@
            IRContext::kAnalysisInstrToBlockMapping |
            IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
            IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
-           IRContext::kAnalysisNameMap;
+           IRContext::kAnalysisNameMap | IRContext::kAnalysisConstants |
+           IRContext::kAnalysisTypes;
   }
 
  private:
diff --git a/source/opt/common_uniform_elim_pass.cpp b/source/opt/common_uniform_elim_pass.cpp
index efa40aa..7762fba 100644
--- a/source/opt/common_uniform_elim_pass.cpp
+++ b/source/opt/common_uniform_elim_pass.cpp
@@ -282,6 +282,7 @@
       uint32_t replId;
       GenACLoadRepl(ptrInst, &newInsts, &replId);
       inst = ReplaceAndDeleteLoad(inst, replId, ptrInst);
+      assert(inst->opcode() != SpvOpPhi);
       inst = inst->InsertBefore(std::move(newInsts));
       modified = true;
     }
@@ -355,6 +356,10 @@
     if (mergeBlockId == bp->id()) {
       mergeBlockId = 0;
       insertItr = bp->begin();
+      while (insertItr->opcode() == SpvOpPhi) {
+        ++insertItr;
+      }
+
       // Update insertItr until it will not be removed. Without this code,
       // ReplaceAndDeleteLoad() can set |insertItr| as a dangling pointer.
       while (IsUniformLoadToBeRemoved(&*insertItr)) ++insertItr;
@@ -509,6 +514,9 @@
   // TODO(greg-lunarg): Add support for physical addressing
   if (context()->get_feature_mgr()->HasCapability(SpvCapabilityAddresses))
     return Status::SuccessWithoutChange;
+  if (context()->get_feature_mgr()->HasCapability(
+          SpvCapabilityVariablePointersStorageBuffer))
+    return Status::SuccessWithoutChange;
   // Do not process if any disallowed extensions are enabled
   if (!AllExtensionsSupported()) return Status::SuccessWithoutChange;
   // Do not process if module contains OpGroupDecorate. Additional
diff --git a/source/opt/const_folding_rules.cpp b/source/opt/const_folding_rules.cpp
index f6013a3..3df5a83 100644
--- a/source/opt/const_folding_rules.cpp
+++ b/source/opt/const_folding_rules.cpp
@@ -582,13 +582,17 @@
     std::vector<uint32_t> words = result.GetWords();
     const analysis::Constant* result_const =
         const_mgr->GetConstant(float_type, words);
-    for (uint32_t i = 0; i < a_components.size(); ++i) {
+    for (uint32_t i = 0; i < a_components.size() && result_const != nullptr;
+         ++i) {
       if (a_components[i] == nullptr || b_components[i] == nullptr) {
         return nullptr;
       }
 
       const analysis::Constant* component = FOLD_FPARITH_OP(*)(
           new_type, a_components[i], b_components[i], const_mgr);
+      if (component == nullptr) {
+        return nullptr;
+      }
       result_const =
           FOLD_FPARITH_OP(+)(new_type, result_const, component, const_mgr);
     }
diff --git a/source/opt/copy_prop_arrays.cpp b/source/opt/copy_prop_arrays.cpp
index e508c05..751786c 100644
--- a/source/opt/copy_prop_arrays.cpp
+++ b/source/opt/copy_prop_arrays.cpp
@@ -563,11 +563,6 @@
 }
 void CopyPropagateArrays::UpdateUses(Instruction* original_ptr_inst,
                                      Instruction* new_ptr_inst) {
-  // TODO (s-perron): Keep the def-use manager up to date.  Not done now because
-  // it can cause problems for the |ForEachUse| traversals.  Can be use by
-  // keeping a list of instructions that need updating, and then updating them
-  // in |PropagateObject|.
-
   analysis::TypeManager* type_mgr = context()->get_type_mgr();
   analysis::ConstantManager* const_mgr = context()->get_constant_mgr();
   analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
@@ -703,74 +698,6 @@
   }
 }
 
-uint32_t CopyPropagateArrays::GenerateCopy(Instruction* object_inst,
-                                           uint32_t new_type_id,
-                                           Instruction* insertion_position) {
-  analysis::TypeManager* type_mgr = context()->get_type_mgr();
-  analysis::ConstantManager* const_mgr = context()->get_constant_mgr();
-
-  uint32_t original_type_id = object_inst->type_id();
-  if (original_type_id == new_type_id) {
-    return object_inst->result_id();
-  }
-
-  InstructionBuilder ir_builder(
-      context(), insertion_position,
-      IRContext::kAnalysisInstrToBlockMapping | IRContext::kAnalysisDefUse);
-
-  analysis::Type* original_type = type_mgr->GetType(original_type_id);
-  analysis::Type* new_type = type_mgr->GetType(new_type_id);
-
-  if (const analysis::Array* original_array_type = original_type->AsArray()) {
-    uint32_t original_element_type_id =
-        type_mgr->GetId(original_array_type->element_type());
-
-    analysis::Array* new_array_type = new_type->AsArray();
-    assert(new_array_type != nullptr && "Can't copy an array to a non-array.");
-    uint32_t new_element_type_id =
-        type_mgr->GetId(new_array_type->element_type());
-
-    std::vector<uint32_t> element_ids;
-    const analysis::Constant* length_const =
-        const_mgr->FindDeclaredConstant(original_array_type->LengthId());
-    assert(length_const->AsIntConstant());
-    uint32_t array_length = length_const->AsIntConstant()->GetU32();
-    for (uint32_t i = 0; i < array_length; i++) {
-      Instruction* extract = ir_builder.AddCompositeExtract(
-          original_element_type_id, object_inst->result_id(), {i});
-      element_ids.push_back(
-          GenerateCopy(extract, new_element_type_id, insertion_position));
-    }
-
-    return ir_builder.AddCompositeConstruct(new_type_id, element_ids)
-        ->result_id();
-  } else if (const analysis::Struct* original_struct_type =
-                 original_type->AsStruct()) {
-    analysis::Struct* new_struct_type = new_type->AsStruct();
-
-    const std::vector<const analysis::Type*>& original_types =
-        original_struct_type->element_types();
-    const std::vector<const analysis::Type*>& new_types =
-        new_struct_type->element_types();
-    std::vector<uint32_t> element_ids;
-    for (uint32_t i = 0; i < original_types.size(); i++) {
-      Instruction* extract = ir_builder.AddCompositeExtract(
-          type_mgr->GetId(original_types[i]), object_inst->result_id(), {i});
-      element_ids.push_back(GenerateCopy(extract, type_mgr->GetId(new_types[i]),
-                                         insertion_position));
-    }
-    return ir_builder.AddCompositeConstruct(new_type_id, element_ids)
-        ->result_id();
-  } else {
-    // If we do not have an aggregate type, then we have a problem.  Either we
-    // found multiple instances of the same type, or we are copying to an
-    // incompatible type.  Either way the code is illegal.
-    assert(false &&
-           "Don't know how to copy this type.  Code is likely illegal.");
-  }
-  return 0;
-}
-
 uint32_t CopyPropagateArrays::GetMemberTypeId(
     uint32_t id, const std::vector<uint32_t>& access_chain) const {
   for (uint32_t element_index : access_chain) {
diff --git a/source/opt/copy_prop_arrays.h b/source/opt/copy_prop_arrays.h
index ed9da0e..f4314a7 100644
--- a/source/opt/copy_prop_arrays.h
+++ b/source/opt/copy_prop_arrays.h
@@ -47,7 +47,8 @@
     return IRContext::kAnalysisDefUse | IRContext::kAnalysisCFG |
            IRContext::kAnalysisInstrToBlockMapping |
            IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisDecorations |
-           IRContext::kAnalysisDominatorAnalysis | IRContext::kAnalysisNameMap;
+           IRContext::kAnalysisDominatorAnalysis | IRContext::kAnalysisNameMap |
+           IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
   }
 
  private:
@@ -216,12 +217,6 @@
   // |original_ptr_inst| to |type_id| and still have valid code.
   bool CanUpdateUses(Instruction* original_ptr_inst, uint32_t type_id);
 
-  // Returns the id whose value is the same as |object_to_copy| except its type
-  // is |new_type_id|.  Any instructions need to generate this value will be
-  // inserted before |insertion_position|.
-  uint32_t GenerateCopy(Instruction* object_to_copy, uint32_t new_type_id,
-                        Instruction* insertion_position);
-
   // Returns a store to |var_inst| that writes to the entire variable, and is
   // the only store that does so.  Note it does not look through OpAccessChain
   // instruction, so partial stores are not considered.
diff --git a/source/opt/dead_branch_elim_pass.cpp b/source/opt/dead_branch_elim_pass.cpp
index 9893536..087c2af 100644
--- a/source/opt/dead_branch_elim_pass.cpp
+++ b/source/opt/dead_branch_elim_pass.cpp
@@ -95,7 +95,7 @@
     Function* func, std::unordered_set<BasicBlock*>* live_blocks) {
   StructuredCFGAnalysis* cfgAnalysis = context()->GetStructuredCFGAnalysis();
 
-  std::unordered_set<BasicBlock*> continues;
+  std::unordered_set<BasicBlock*> blocks_with_backedge;
   std::vector<BasicBlock*> stack;
   stack.push_back(&*func->begin());
   bool modified = false;
@@ -107,7 +107,10 @@
     if (!live_blocks->insert(block).second) continue;
 
     uint32_t cont_id = block->ContinueBlockIdIfAny();
-    if (cont_id != 0) continues.insert(GetParentBlock(cont_id));
+    if (cont_id != 0) {
+      AddBlocksWithBackEdge(cont_id, block->id(), block->MergeBlockIdIfAny(),
+                            &blocks_with_backedge);
+    }
 
     Instruction* terminator = block->terminator();
     uint32_t live_lab_id = 0;
@@ -146,13 +149,23 @@
       }
     }
 
-    // Don't simplify branches of continue blocks. A path from the continue to
-    // the header is required.
-    // TODO(alan-baker): They can be simplified iff there remains a path to the
-    // backedge. Structured control flow should guarantee one path hits the
-    // backedge, but I've removed the requirement for structured control flow
-    // from this pass.
-    bool simplify = live_lab_id != 0 && !continues.count(block);
+    // Don't simplify back edges unless it becomes a branch to the header. Every
+    // loop must have exactly one back edge to the loop header, so we cannot
+    // remove it.
+    bool simplify = false;
+    if (live_lab_id != 0) {
+      if (!blocks_with_backedge.count(block)) {
+        // This is not a back edge.
+        simplify = true;
+      } else {
+        const auto& struct_cfg_analysis = context()->GetStructuredCFGAnalysis();
+        uint32_t header_id = struct_cfg_analysis->ContainingLoop(block->id());
+        if (live_lab_id == header_id) {
+          // The new branch will be a branch to the header.
+          simplify = true;
+        }
+      }
+    }
 
     if (simplify) {
       modified = true;
@@ -323,21 +336,7 @@
     const std::unordered_map<BasicBlock*, BasicBlock*>& unreachable_continues) {
   bool modified = false;
   for (auto ebi = func->begin(); ebi != func->end();) {
-    if (unreachable_merges.count(&*ebi)) {
-      if (ebi->begin() != ebi->tail() ||
-          ebi->terminator()->opcode() != SpvOpUnreachable) {
-        // Make unreachable, but leave the label.
-        KillAllInsts(&*ebi, false);
-        // Add unreachable terminator.
-        ebi->AddInstruction(
-            MakeUnique<Instruction>(context(), SpvOpUnreachable, 0, 0,
-                                    std::initializer_list<Operand>{}));
-        context()->AnalyzeUses(ebi->terminator());
-        context()->set_instr_block(ebi->terminator(), &*ebi);
-        modified = true;
-      }
-      ++ebi;
-    } else if (unreachable_continues.count(&*ebi)) {
+    if (unreachable_continues.count(&*ebi)) {
       uint32_t cont_id = unreachable_continues.find(&*ebi)->second->id();
       if (ebi->begin() != ebi->tail() ||
           ebi->terminator()->opcode() != SpvOpBranch ||
@@ -354,6 +353,20 @@
         modified = true;
       }
       ++ebi;
+    } else if (unreachable_merges.count(&*ebi)) {
+      if (ebi->begin() != ebi->tail() ||
+          ebi->terminator()->opcode() != SpvOpUnreachable) {
+        // Make unreachable, but leave the label.
+        KillAllInsts(&*ebi, false);
+        // Add unreachable terminator.
+        ebi->AddInstruction(
+            MakeUnique<Instruction>(context(), SpvOpUnreachable, 0, 0,
+                                    std::initializer_list<Operand>{}));
+        context()->AnalyzeUses(ebi->terminator());
+        context()->set_instr_block(ebi->terminator(), &*ebi);
+        modified = true;
+      }
+      ++ebi;
     } else if (!live_blocks.count(&*ebi)) {
       // Kill this block.
       KillAllInsts(&*ebi);
@@ -538,5 +551,39 @@
   return nullptr;
 }
 
+void DeadBranchElimPass::AddBlocksWithBackEdge(
+    uint32_t cont_id, uint32_t header_id, uint32_t merge_id,
+    std::unordered_set<BasicBlock*>* blocks_with_back_edges) {
+  std::unordered_set<uint32_t> visited;
+  visited.insert(cont_id);
+  visited.insert(header_id);
+  visited.insert(merge_id);
+
+  std::vector<uint32_t> work_list;
+  work_list.push_back(cont_id);
+
+  while (!work_list.empty()) {
+    uint32_t bb_id = work_list.back();
+    work_list.pop_back();
+
+    BasicBlock* bb = context()->get_instr_block(bb_id);
+
+    bool has_back_edge = false;
+    bb->ForEachSuccessorLabel([header_id, &visited, &work_list,
+                               &has_back_edge](uint32_t* succ_label_id) {
+      if (visited.insert(*succ_label_id).second) {
+        work_list.push_back(*succ_label_id);
+      }
+      if (*succ_label_id == header_id) {
+        has_back_edge = true;
+      }
+    });
+
+    if (has_back_edge) {
+      blocks_with_back_edges->insert(bb);
+    }
+  }
+}
+
 }  // namespace opt
 }  // namespace spvtools
diff --git a/source/opt/dead_branch_elim_pass.h b/source/opt/dead_branch_elim_pass.h
index a3a4f5a..2ced589 100644
--- a/source/opt/dead_branch_elim_pass.h
+++ b/source/opt/dead_branch_elim_pass.h
@@ -44,7 +44,9 @@
   Status Process() override;
 
   IRContext::Analysis GetPreservedAnalyses() override {
-    return IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping;
+    return IRContext::kAnalysisDefUse |
+           IRContext::kAnalysisInstrToBlockMapping |
+           IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
   }
 
  private:
@@ -146,6 +148,14 @@
                                                uint32_t merge_block_id,
                                                uint32_t loop_merge_id,
                                                uint32_t loop_continue_id);
+
+  // Adds to |blocks_with_back_edges| all of the blocks on the path from the
+  // basic block |cont_id| to |header_id| and |merge_id|.  The intention is that
+  // |cond_id| is a the continue target of a loop, |header_id| is the header of
+  // the loop, and |merge_id| is the merge block of the loop.
+  void AddBlocksWithBackEdge(
+      uint32_t cont_id, uint32_t header_id, uint32_t merge_id,
+      std::unordered_set<BasicBlock*>* blocks_with_back_edges);
 };
 
 }  // namespace opt
diff --git a/source/opt/dead_insert_elim_pass.h b/source/opt/dead_insert_elim_pass.h
index 0b111d0..01f12bb 100644
--- a/source/opt/dead_insert_elim_pass.h
+++ b/source/opt/dead_insert_elim_pass.h
@@ -45,7 +45,8 @@
            IRContext::kAnalysisInstrToBlockMapping |
            IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
            IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
-           IRContext::kAnalysisNameMap;
+           IRContext::kAnalysisNameMap | IRContext::kAnalysisConstants |
+           IRContext::kAnalysisTypes;
   }
 
  private:
diff --git a/source/opt/dead_variable_elimination.h b/source/opt/dead_variable_elimination.h
index 40a7bc0..5dde71b 100644
--- a/source/opt/dead_variable_elimination.h
+++ b/source/opt/dead_variable_elimination.h
@@ -30,7 +30,8 @@
   Status Process() override;
 
   IRContext::Analysis GetPreservedAnalyses() override {
-    return IRContext::kAnalysisDefUse;
+    return IRContext::kAnalysisDefUse | IRContext::kAnalysisConstants |
+           IRContext::kAnalysisTypes;
   }
 
  private:
diff --git a/source/opt/decompose_initialized_variables_pass.cpp b/source/opt/decompose_initialized_variables_pass.cpp
new file mode 100644
index 0000000..875bf7e
--- /dev/null
+++ b/source/opt/decompose_initialized_variables_pass.cpp
@@ -0,0 +1,112 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/opt/decompose_initialized_variables_pass.h"
+
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace opt {
+
+using inst_iterator = InstructionList::iterator;
+
+namespace {
+
+bool HasInitializer(Instruction* inst) {
+  if (inst->opcode() != SpvOpVariable) return false;
+  if (inst->NumOperands() < 4) return false;
+
+  return true;
+}
+
+}  // namespace
+
+Pass::Status DecomposeInitializedVariablesPass::Process() {
+  auto* module = context()->module();
+  std::unordered_set<Instruction*> changed;
+
+  std::vector<std::tuple<uint32_t, uint32_t>> global_stores;
+  for (auto iter = module->types_values_begin();
+       iter != module->types_values_end(); ++iter) {
+    Instruction* inst = &(*iter);
+    if (!HasInitializer(inst)) continue;
+
+    auto var_id = inst->result_id();
+    auto val_id = inst->GetOperand(3).words[0];
+    global_stores.push_back(std::make_tuple(var_id, val_id));
+    iter->RemoveOperand(3);
+    changed.insert(&*iter);
+  }
+
+  std::unordered_set<uint32_t> entry_ids;
+  for (auto entry = module->entry_points().begin();
+       entry != module->entry_points().end(); ++entry) {
+    entry_ids.insert(entry->GetSingleWordInOperand(1));
+  }
+
+  for (auto func = module->begin(); func != module->end(); ++func) {
+    std::vector<Instruction*> function_stores;
+    auto first_block = func->entry().get();
+    inst_iterator insert_point = first_block->begin();
+    for (auto iter = first_block->begin();
+         iter != first_block->end() && iter->opcode() == SpvOpVariable;
+         ++iter) {
+      // For valid SPIRV-V, there is guaranteed to be at least one instruction
+      // after the OpVariable instructions.
+      insert_point = (*iter).NextNode();
+      Instruction* inst = &(*iter);
+      if (!HasInitializer(inst)) continue;
+
+      auto var_id = inst->result_id();
+      auto val_id = inst->GetOperand(3).words[0];
+      Instruction* store_inst = new Instruction(
+          context(), SpvOpStore, 0, 0,
+          {{SPV_OPERAND_TYPE_ID, {var_id}}, {SPV_OPERAND_TYPE_ID, {val_id}}});
+      function_stores.push_back(store_inst);
+      iter->RemoveOperand(3);
+      changed.insert(&*iter);
+    }
+
+    if (entry_ids.find(func->result_id()) != entry_ids.end()) {
+      for (auto store_ids : global_stores) {
+        uint32_t var_id;
+        uint32_t val_id;
+        std::tie(var_id, val_id) = store_ids;
+        auto* store_inst = new Instruction(
+            context(), SpvOpStore, 0, 0,
+            {{SPV_OPERAND_TYPE_ID, {var_id}}, {SPV_OPERAND_TYPE_ID, {val_id}}});
+        context()->set_instr_block(store_inst, &*first_block);
+        first_block->AddInstruction(std::unique_ptr<Instruction>(store_inst));
+        store_inst->InsertBefore(&*insert_point);
+        changed.insert(store_inst);
+      }
+    }
+
+    for (auto store = function_stores.begin(); store != function_stores.end();
+         ++store) {
+      context()->set_instr_block(*store, first_block);
+      (*store)->InsertBefore(&*insert_point);
+      changed.insert(*store);
+    }
+  }
+
+  auto* def_use_mgr = get_def_use_mgr();
+  for (auto* inst : changed) def_use_mgr->UpdateDefUse(inst);
+
+  return !changed.empty() ? Pass::Status::SuccessWithChange
+                          : Pass::Status::SuccessWithoutChange;
+}
+
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/decompose_initialized_variables_pass.h b/source/opt/decompose_initialized_variables_pass.h
new file mode 100644
index 0000000..c0bd35e
--- /dev/null
+++ b/source/opt/decompose_initialized_variables_pass.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_DECOMPOSE_INITALIZED_VAIRABLES_PASS_H_
+#define SOURCE_OPT_DECOMPOSE_INITALIZED_VAIRABLES_PASS_H_
+
+#include "source/opt/ir_context.h"
+#include "source/opt/module.h"
+#include "source/opt/pass.h"
+
+namespace spvtools {
+namespace opt {
+
+// Converts variable declartions with initializers into seperate declaration and
+// assignment statements. This is done due to known issues with some Vulkan
+// implementations' handling of initialized variables.
+//
+// Only decomposes variables with storage classes that are valid in Vulkan
+// execution environments; Output, Private, and Function.
+// Currently only Function is implemented.
+class DecomposeInitializedVariablesPass : public Pass {
+ public:
+  const char* name() const override {
+    return "decompose-initialized-variables";
+  }
+  Status Process() override;
+
+  IRContext::Analysis GetPreservedAnalyses() override {
+    return IRContext::kAnalysisInstrToBlockMapping |
+           IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
+           IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
+           IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap |
+           IRContext::kAnalysisScalarEvolution |
+           IRContext::kAnalysisRegisterPressure |
+           IRContext::kAnalysisValueNumberTable |
+           IRContext::kAnalysisStructuredCFG |
+           IRContext::kAnalysisBuiltinVarId |
+           IRContext::kAnalysisIdToFuncMapping | IRContext::kAnalysisTypes |
+           IRContext::kAnalysisDefUse | IRContext::kAnalysisConstants;
+  }
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // SOURCE_OPT_DECOMPOSE_INITALIZED_VAIRABLES_PASS_H_
diff --git a/source/opt/eliminate_dead_functions_pass.cpp b/source/opt/eliminate_dead_functions_pass.cpp
index f067be5..a465521 100644
--- a/source/opt/eliminate_dead_functions_pass.cpp
+++ b/source/opt/eliminate_dead_functions_pass.cpp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #include "source/opt/eliminate_dead_functions_pass.h"
+#include "source/opt/eliminate_dead_functions_util.h"
 
 #include <unordered_set>
 
@@ -36,8 +37,8 @@
        funcIter != get_module()->end();) {
     if (live_function_set.count(&*funcIter) == 0) {
       modified = true;
-      EliminateFunction(&*funcIter);
-      funcIter = funcIter.Erase();
+      funcIter =
+          eliminatedeadfunctionsutil::EliminateFunction(context(), &funcIter);
     } else {
       ++funcIter;
     }
@@ -47,10 +48,5 @@
                   : Pass::Status::SuccessWithoutChange;
 }
 
-void EliminateDeadFunctionsPass::EliminateFunction(Function* func) {
-  // Remove all of the instruction in the function body
-  func->ForEachInst([this](Instruction* inst) { context()->KillInst(inst); },
-                    true);
-}
 }  // namespace opt
 }  // namespace spvtools
diff --git a/source/opt/eliminate_dead_functions_pass.h b/source/opt/eliminate_dead_functions_pass.h
index 165e9a6..6ed5c42 100644
--- a/source/opt/eliminate_dead_functions_pass.h
+++ b/source/opt/eliminate_dead_functions_pass.h
@@ -30,7 +30,8 @@
   Status Process() override;
 
   IRContext::Analysis GetPreservedAnalyses() override {
-    return IRContext::kAnalysisDefUse;
+    return IRContext::kAnalysisDefUse | IRContext::kAnalysisConstants |
+           IRContext::kAnalysisTypes;
   }
 
  private:
diff --git a/source/opt/eliminate_dead_functions_util.cpp b/source/opt/eliminate_dead_functions_util.cpp
new file mode 100644
index 0000000..8a38959
--- /dev/null
+++ b/source/opt/eliminate_dead_functions_util.cpp
@@ -0,0 +1,32 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "eliminate_dead_functions_util.h"
+
+namespace spvtools {
+namespace opt {
+
+namespace eliminatedeadfunctionsutil {
+
+Module::iterator EliminateFunction(IRContext* context,
+                                   Module::iterator* func_iter) {
+  (*func_iter)
+      ->ForEachInst([context](Instruction* inst) { context->KillInst(inst); },
+                    true);
+  return func_iter->Erase();
+}
+
+}  // namespace eliminatedeadfunctionsutil
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/eliminate_dead_functions_util.h b/source/opt/eliminate_dead_functions_util.h
new file mode 100644
index 0000000..9fcce95
--- /dev/null
+++ b/source/opt/eliminate_dead_functions_util.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_ELIMINATE_DEAD_FUNCTIONS_UTIL_H_
+#define SOURCE_OPT_ELIMINATE_DEAD_FUNCTIONS_UTIL_H_
+
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace opt {
+
+// Provides functionality for eliminating functions that are not needed, for use
+// by various analyses and passes.
+namespace eliminatedeadfunctionsutil {
+
+// Removes all of the function's instructions, removes the function from the
+// module, and returns the next iterator.
+Module::iterator EliminateFunction(IRContext* context,
+                                   Module::iterator* func_iter);
+
+}  // namespace eliminatedeadfunctionsutil
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  //  SOURCE_OPT_ELIMINATE_DEAD_FUNCTIONS_UTIL_H_
diff --git a/source/opt/eliminate_dead_members_pass.cpp b/source/opt/eliminate_dead_members_pass.cpp
new file mode 100644
index 0000000..0b73b2d
--- /dev/null
+++ b/source/opt/eliminate_dead_members_pass.cpp
@@ -0,0 +1,637 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/opt/eliminate_dead_members_pass.h"
+
+#include "ir_builder.h"
+#include "source/opt/ir_context.h"
+
+namespace {
+const uint32_t kRemovedMember = 0xFFFFFFFF;
+}
+
+namespace spvtools {
+namespace opt {
+
+Pass::Status EliminateDeadMembersPass::Process() {
+  if (!context()->get_feature_mgr()->HasCapability(SpvCapabilityShader))
+    return Status::SuccessWithoutChange;
+
+  FindLiveMembers();
+  if (RemoveDeadMembers()) {
+    return Status::SuccessWithChange;
+  }
+  return Status::SuccessWithoutChange;
+}
+
+void EliminateDeadMembersPass::FindLiveMembers() {
+  // Until we have implemented the rewritting of OpSpecConsantOp instructions,
+  // we have to mark them as fully used just to be safe.
+  for (auto& inst : get_module()->types_values()) {
+    if (inst.opcode() == SpvOpSpecConstantOp) {
+      MarkTypeAsFullyUsed(inst.type_id());
+    } else if (inst.opcode() == SpvOpVariable) {
+      switch (inst.GetSingleWordInOperand(0)) {
+        case SpvStorageClassInput:
+        case SpvStorageClassOutput:
+          MarkPointeeTypeAsFullUsed(inst.type_id());
+          break;
+        default:
+          break;
+      }
+    }
+  }
+
+  for (const Function& func : *get_module()) {
+    FindLiveMembers(func);
+  }
+}
+
+void EliminateDeadMembersPass::FindLiveMembers(const Function& function) {
+  function.ForEachInst(
+      [this](const Instruction* inst) { FindLiveMembers(inst); });
+}
+
+void EliminateDeadMembersPass::FindLiveMembers(const Instruction* inst) {
+  switch (inst->opcode()) {
+    case SpvOpStore:
+      MarkMembersAsLiveForStore(inst);
+      break;
+    case SpvOpCopyMemory:
+    case SpvOpCopyMemorySized:
+      MarkMembersAsLiveForCopyMemory(inst);
+      break;
+    case SpvOpCompositeExtract:
+      MarkMembersAsLiveForExtract(inst);
+      break;
+    case SpvOpAccessChain:
+    case SpvOpInBoundsAccessChain:
+    case SpvOpPtrAccessChain:
+    case SpvOpInBoundsPtrAccessChain:
+      MarkMembersAsLiveForAccessChain(inst);
+      break;
+    case SpvOpReturnValue:
+      // This should be an issue only if we are returning from the entry point.
+      // However, for now I will keep it more conservative because functions are
+      // often inlined leaving only the entry points.
+      MarkOperandTypeAsFullyUsed(inst, 0);
+      break;
+    case SpvOpArrayLength:
+      MarkMembersAsLiveForArrayLength(inst);
+      break;
+    case SpvOpLoad:
+    case SpvOpCompositeInsert:
+    case SpvOpCompositeConstruct:
+      break;
+    default:
+      // This path is here for safety.  All instructions that can reference
+      // structs in a function body should be handled above.  However, this will
+      // keep the pass valid, but not optimal, as new instructions get added
+      // or if something was missed.
+      MarkStructOperandsAsFullyUsed(inst);
+      break;
+  }
+}
+
+void EliminateDeadMembersPass::MarkMembersAsLiveForStore(
+    const Instruction* inst) {
+  // We should only have to mark the members as live if the store is to
+  // memory that is read outside of the shader.  Other passes can remove all
+  // store to memory that is not visible outside of the shader, so we do not
+  // complicate the code for now.
+  assert(inst->opcode() == SpvOpStore);
+  uint32_t object_id = inst->GetSingleWordInOperand(1);
+  Instruction* object_inst = context()->get_def_use_mgr()->GetDef(object_id);
+  uint32_t object_type_id = object_inst->type_id();
+  MarkTypeAsFullyUsed(object_type_id);
+}
+
+void EliminateDeadMembersPass::MarkTypeAsFullyUsed(uint32_t type_id) {
+  Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
+  assert(type_inst != nullptr);
+  if (type_inst->opcode() != SpvOpTypeStruct) {
+    return;
+  }
+
+  // Mark every member of the current struct as used.
+  for (uint32_t i = 0; i < type_inst->NumInOperands(); ++i) {
+    used_members_[type_id].insert(i);
+  }
+
+  // Mark any sub struct as fully used.
+  for (uint32_t i = 0; i < type_inst->NumInOperands(); ++i) {
+    MarkTypeAsFullyUsed(type_inst->GetSingleWordInOperand(i));
+  }
+}
+
+void EliminateDeadMembersPass::MarkPointeeTypeAsFullUsed(uint32_t ptr_type_id) {
+  Instruction* ptr_type_inst = get_def_use_mgr()->GetDef(ptr_type_id);
+  assert(ptr_type_inst->opcode() == SpvOpTypePointer);
+  MarkTypeAsFullyUsed(ptr_type_inst->GetSingleWordInOperand(1));
+}
+
+void EliminateDeadMembersPass::MarkMembersAsLiveForCopyMemory(
+    const Instruction* inst) {
+  uint32_t target_id = inst->GetSingleWordInOperand(0);
+  Instruction* target_inst = get_def_use_mgr()->GetDef(target_id);
+  uint32_t pointer_type_id = target_inst->type_id();
+  Instruction* pointer_type_inst = get_def_use_mgr()->GetDef(pointer_type_id);
+  uint32_t type_id = pointer_type_inst->GetSingleWordInOperand(1);
+  MarkTypeAsFullyUsed(type_id);
+}
+
+void EliminateDeadMembersPass::MarkMembersAsLiveForExtract(
+    const Instruction* inst) {
+  assert(inst->opcode() == SpvOpCompositeExtract);
+
+  uint32_t composite_id = inst->GetSingleWordInOperand(0);
+  Instruction* composite_inst = get_def_use_mgr()->GetDef(composite_id);
+  uint32_t type_id = composite_inst->type_id();
+
+  for (uint32_t i = 1; i < inst->NumInOperands(); ++i) {
+    Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
+    uint32_t member_idx = inst->GetSingleWordInOperand(i);
+    switch (type_inst->opcode()) {
+      case SpvOpTypeStruct:
+        used_members_[type_id].insert(member_idx);
+        type_id = type_inst->GetSingleWordInOperand(member_idx);
+        break;
+      case SpvOpTypeArray:
+      case SpvOpTypeRuntimeArray:
+      case SpvOpTypeVector:
+      case SpvOpTypeMatrix:
+        type_id = type_inst->GetSingleWordInOperand(0);
+        break;
+      default:
+        assert(false);
+    }
+  }
+}
+
+void EliminateDeadMembersPass::MarkMembersAsLiveForAccessChain(
+    const Instruction* inst) {
+  assert(inst->opcode() == SpvOpAccessChain ||
+         inst->opcode() == SpvOpInBoundsAccessChain ||
+         inst->opcode() == SpvOpPtrAccessChain ||
+         inst->opcode() == SpvOpInBoundsPtrAccessChain);
+
+  uint32_t pointer_id = inst->GetSingleWordInOperand(0);
+  Instruction* pointer_inst = get_def_use_mgr()->GetDef(pointer_id);
+  uint32_t pointer_type_id = pointer_inst->type_id();
+  Instruction* pointer_type_inst = get_def_use_mgr()->GetDef(pointer_type_id);
+  uint32_t type_id = pointer_type_inst->GetSingleWordInOperand(1);
+
+  analysis::ConstantManager* const_mgr = context()->get_constant_mgr();
+
+  // For a pointer access chain, we need to skip the |element| index.  It is not
+  // a reference to the member of a struct, and it does not change the type.
+  uint32_t i = (inst->opcode() == SpvOpAccessChain ||
+                        inst->opcode() == SpvOpInBoundsAccessChain
+                    ? 1
+                    : 2);
+  for (; i < inst->NumInOperands(); ++i) {
+    Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
+    switch (type_inst->opcode()) {
+      case SpvOpTypeStruct: {
+        const analysis::IntConstant* member_idx =
+            const_mgr->FindDeclaredConstant(inst->GetSingleWordInOperand(i))
+                ->AsIntConstant();
+        assert(member_idx);
+        if (member_idx->type()->AsInteger()->width() == 32) {
+          used_members_[type_id].insert(member_idx->GetU32());
+          type_id = type_inst->GetSingleWordInOperand(member_idx->GetU32());
+        } else {
+          used_members_[type_id].insert(
+              static_cast<uint32_t>(member_idx->GetU64()));
+          type_id = type_inst->GetSingleWordInOperand(
+              static_cast<uint32_t>(member_idx->GetU64()));
+        }
+      } break;
+      case SpvOpTypeArray:
+      case SpvOpTypeRuntimeArray:
+      case SpvOpTypeVector:
+      case SpvOpTypeMatrix:
+        type_id = type_inst->GetSingleWordInOperand(0);
+        break;
+      default:
+        assert(false);
+    }
+  }
+}
+
+void EliminateDeadMembersPass::MarkOperandTypeAsFullyUsed(
+    const Instruction* inst, uint32_t in_idx) {
+  uint32_t op_id = inst->GetSingleWordInOperand(in_idx);
+  Instruction* op_inst = get_def_use_mgr()->GetDef(op_id);
+  MarkTypeAsFullyUsed(op_inst->type_id());
+}
+
+void EliminateDeadMembersPass::MarkMembersAsLiveForArrayLength(
+    const Instruction* inst) {
+  assert(inst->opcode() == SpvOpArrayLength);
+  uint32_t object_id = inst->GetSingleWordInOperand(0);
+  Instruction* object_inst = get_def_use_mgr()->GetDef(object_id);
+  uint32_t pointer_type_id = object_inst->type_id();
+  Instruction* pointer_type_inst = get_def_use_mgr()->GetDef(pointer_type_id);
+  uint32_t type_id = pointer_type_inst->GetSingleWordInOperand(1);
+  used_members_[type_id].insert(inst->GetSingleWordInOperand(1));
+}
+
+bool EliminateDeadMembersPass::RemoveDeadMembers() {
+  bool modified = false;
+
+  // First update all of the OpTypeStruct instructions.
+  get_module()->ForEachInst([&modified, this](Instruction* inst) {
+    switch (inst->opcode()) {
+      case SpvOpTypeStruct:
+        modified |= UpdateOpTypeStruct(inst);
+        break;
+      default:
+        break;
+    }
+  });
+
+  // Now update all of the instructions that reference the OpTypeStructs.
+  get_module()->ForEachInst([&modified, this](Instruction* inst) {
+    switch (inst->opcode()) {
+      case SpvOpMemberName:
+        modified |= UpdateOpMemberNameOrDecorate(inst);
+        break;
+      case SpvOpMemberDecorate:
+        modified |= UpdateOpMemberNameOrDecorate(inst);
+        break;
+      case SpvOpGroupMemberDecorate:
+        modified |= UpdateOpGroupMemberDecorate(inst);
+        break;
+      case SpvOpSpecConstantComposite:
+      case SpvOpConstantComposite:
+      case SpvOpCompositeConstruct:
+        modified |= UpdateConstantComposite(inst);
+        break;
+      case SpvOpAccessChain:
+      case SpvOpInBoundsAccessChain:
+      case SpvOpPtrAccessChain:
+      case SpvOpInBoundsPtrAccessChain:
+        modified |= UpdateAccessChain(inst);
+        break;
+      case SpvOpCompositeExtract:
+        modified |= UpdateCompsiteExtract(inst);
+        break;
+      case SpvOpCompositeInsert:
+        modified |= UpdateCompositeInsert(inst);
+        break;
+      case SpvOpArrayLength:
+        modified |= UpdateOpArrayLength(inst);
+        break;
+      case SpvOpSpecConstantOp:
+        assert(false && "Not yet implemented.");
+        // with OpCompositeExtract, OpCompositeInsert
+        // For kernels: OpAccessChain, OpInBoundsAccessChain, OpPtrAccessChain,
+        // OpInBoundsPtrAccessChain
+        break;
+      default:
+        break;
+    }
+  });
+  return modified;
+}
+
+bool EliminateDeadMembersPass::UpdateOpTypeStruct(Instruction* inst) {
+  assert(inst->opcode() == SpvOpTypeStruct);
+
+  const auto& live_members = used_members_[inst->result_id()];
+  if (live_members.size() == inst->NumInOperands()) {
+    return false;
+  }
+
+  Instruction::OperandList new_operands;
+  for (uint32_t idx : live_members) {
+    new_operands.emplace_back(inst->GetInOperand(idx));
+  }
+
+  inst->SetInOperands(std::move(new_operands));
+  context()->UpdateDefUse(inst);
+  return true;
+}
+
+bool EliminateDeadMembersPass::UpdateOpMemberNameOrDecorate(Instruction* inst) {
+  assert(inst->opcode() == SpvOpMemberName ||
+         inst->opcode() == SpvOpMemberDecorate);
+
+  uint32_t type_id = inst->GetSingleWordInOperand(0);
+  auto live_members = used_members_.find(type_id);
+  if (live_members == used_members_.end()) {
+    return false;
+  }
+
+  uint32_t orig_member_idx = inst->GetSingleWordInOperand(1);
+  uint32_t new_member_idx = GetNewMemberIndex(type_id, orig_member_idx);
+
+  if (new_member_idx == kRemovedMember) {
+    context()->KillInst(inst);
+    return true;
+  }
+
+  if (new_member_idx == orig_member_idx) {
+    return false;
+  }
+
+  inst->SetInOperand(1, {new_member_idx});
+  return true;
+}
+
+bool EliminateDeadMembersPass::UpdateOpGroupMemberDecorate(Instruction* inst) {
+  assert(inst->opcode() == SpvOpGroupMemberDecorate);
+
+  bool modified = false;
+
+  Instruction::OperandList new_operands;
+  new_operands.emplace_back(inst->GetInOperand(0));
+  for (uint32_t i = 1; i < inst->NumInOperands(); i += 2) {
+    uint32_t type_id = inst->GetSingleWordInOperand(i);
+    uint32_t member_idx = inst->GetSingleWordInOperand(i + 1);
+    uint32_t new_member_idx = GetNewMemberIndex(type_id, member_idx);
+
+    if (new_member_idx == kRemovedMember) {
+      modified = true;
+      continue;
+    }
+
+    new_operands.emplace_back(inst->GetOperand(i));
+    if (new_member_idx != member_idx) {
+      new_operands.emplace_back(
+          Operand({SPV_OPERAND_TYPE_LITERAL_INTEGER, {new_member_idx}}));
+      modified = true;
+    } else {
+      new_operands.emplace_back(inst->GetOperand(i + 1));
+    }
+  }
+
+  if (!modified) {
+    return false;
+  }
+
+  if (new_operands.size() == 1) {
+    context()->KillInst(inst);
+    return true;
+  }
+
+  inst->SetInOperands(std::move(new_operands));
+  context()->UpdateDefUse(inst);
+  return true;
+}
+
+bool EliminateDeadMembersPass::UpdateConstantComposite(Instruction* inst) {
+  assert(inst->opcode() == SpvOpConstantComposite ||
+         inst->opcode() == SpvOpCompositeConstruct);
+  uint32_t type_id = inst->type_id();
+
+  bool modified = false;
+  Instruction::OperandList new_operands;
+  for (uint32_t i = 0; i < inst->NumInOperands(); ++i) {
+    uint32_t new_idx = GetNewMemberIndex(type_id, i);
+    if (new_idx == kRemovedMember) {
+      modified = true;
+    } else {
+      new_operands.emplace_back(inst->GetInOperand(i));
+    }
+  }
+  inst->SetInOperands(std::move(new_operands));
+  context()->UpdateDefUse(inst);
+  return modified;
+}
+
+bool EliminateDeadMembersPass::UpdateAccessChain(Instruction* inst) {
+  assert(inst->opcode() == SpvOpAccessChain ||
+         inst->opcode() == SpvOpInBoundsAccessChain ||
+         inst->opcode() == SpvOpPtrAccessChain ||
+         inst->opcode() == SpvOpInBoundsPtrAccessChain);
+
+  uint32_t pointer_id = inst->GetSingleWordInOperand(0);
+  Instruction* pointer_inst = get_def_use_mgr()->GetDef(pointer_id);
+  uint32_t pointer_type_id = pointer_inst->type_id();
+  Instruction* pointer_type_inst = get_def_use_mgr()->GetDef(pointer_type_id);
+  uint32_t type_id = pointer_type_inst->GetSingleWordInOperand(1);
+
+  analysis::ConstantManager* const_mgr = context()->get_constant_mgr();
+  Instruction::OperandList new_operands;
+  bool modified = false;
+  new_operands.emplace_back(inst->GetInOperand(0));
+
+  // For pointer access chains we want to copy the element operand.
+  if (inst->opcode() == SpvOpPtrAccessChain ||
+      inst->opcode() == SpvOpInBoundsPtrAccessChain) {
+    new_operands.emplace_back(inst->GetInOperand(1));
+  }
+
+  for (uint32_t i = static_cast<uint32_t>(new_operands.size());
+       i < inst->NumInOperands(); ++i) {
+    Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
+    switch (type_inst->opcode()) {
+      case SpvOpTypeStruct: {
+        const analysis::IntConstant* member_idx =
+            const_mgr->FindDeclaredConstant(inst->GetSingleWordInOperand(i))
+                ->AsIntConstant();
+        assert(member_idx);
+        uint32_t orig_member_idx;
+        if (member_idx->type()->AsInteger()->width() == 32) {
+          orig_member_idx = member_idx->GetU32();
+        } else {
+          orig_member_idx = static_cast<uint32_t>(member_idx->GetU64());
+        }
+        uint32_t new_member_idx = GetNewMemberIndex(type_id, orig_member_idx);
+        assert(new_member_idx != kRemovedMember);
+        if (orig_member_idx != new_member_idx) {
+          InstructionBuilder ir_builder(
+              context(), inst,
+              IRContext::kAnalysisDefUse |
+                  IRContext::kAnalysisInstrToBlockMapping);
+          uint32_t const_id =
+              ir_builder.GetUintConstant(new_member_idx)->result_id();
+          new_operands.emplace_back(Operand({SPV_OPERAND_TYPE_ID, {const_id}}));
+          modified = true;
+        } else {
+          new_operands.emplace_back(inst->GetInOperand(i));
+        }
+        // The type will have already been rewritten, so use the new member
+        // index.
+        type_id = type_inst->GetSingleWordInOperand(new_member_idx);
+      } break;
+      case SpvOpTypeArray:
+      case SpvOpTypeRuntimeArray:
+      case SpvOpTypeVector:
+      case SpvOpTypeMatrix:
+        new_operands.emplace_back(inst->GetInOperand(i));
+        type_id = type_inst->GetSingleWordInOperand(0);
+        break;
+      default:
+        assert(false);
+        break;
+    }
+  }
+
+  if (!modified) {
+    return false;
+  }
+  inst->SetInOperands(std::move(new_operands));
+  context()->UpdateDefUse(inst);
+  return true;
+}
+
+uint32_t EliminateDeadMembersPass::GetNewMemberIndex(uint32_t type_id,
+                                                     uint32_t member_idx) {
+  auto live_members = used_members_.find(type_id);
+  if (live_members == used_members_.end()) {
+    return member_idx;
+  }
+
+  auto current_member = live_members->second.find(member_idx);
+  if (current_member == live_members->second.end()) {
+    return kRemovedMember;
+  }
+
+  return static_cast<uint32_t>(
+      std::distance(live_members->second.begin(), current_member));
+}
+
+bool EliminateDeadMembersPass::UpdateCompsiteExtract(Instruction* inst) {
+  uint32_t object_id = inst->GetSingleWordInOperand(0);
+  Instruction* object_inst = get_def_use_mgr()->GetDef(object_id);
+  uint32_t type_id = object_inst->type_id();
+
+  Instruction::OperandList new_operands;
+  bool modified = false;
+  new_operands.emplace_back(inst->GetInOperand(0));
+  for (uint32_t i = 1; i < inst->NumInOperands(); ++i) {
+    uint32_t member_idx = inst->GetSingleWordInOperand(i);
+    uint32_t new_member_idx = GetNewMemberIndex(type_id, member_idx);
+    assert(new_member_idx != kRemovedMember);
+    if (member_idx != new_member_idx) {
+      modified = true;
+    }
+    new_operands.emplace_back(
+        Operand({SPV_OPERAND_TYPE_LITERAL_INTEGER, {new_member_idx}}));
+
+    Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
+    switch (type_inst->opcode()) {
+      case SpvOpTypeStruct:
+        assert(i != 1 || (inst->opcode() != SpvOpPtrAccessChain &&
+                          inst->opcode() != SpvOpInBoundsPtrAccessChain));
+        // The type will have already been rewriten, so use the new member
+        // index.
+        type_id = type_inst->GetSingleWordInOperand(new_member_idx);
+        break;
+      case SpvOpTypeArray:
+      case SpvOpTypeRuntimeArray:
+      case SpvOpTypeVector:
+      case SpvOpTypeMatrix:
+        type_id = type_inst->GetSingleWordInOperand(0);
+        break;
+      default:
+        assert(false);
+    }
+  }
+
+  if (!modified) {
+    return false;
+  }
+  inst->SetInOperands(std::move(new_operands));
+  context()->UpdateDefUse(inst);
+  return true;
+}
+
+bool EliminateDeadMembersPass::UpdateCompositeInsert(Instruction* inst) {
+  uint32_t composite_id = inst->GetSingleWordInOperand(1);
+  Instruction* composite_inst = get_def_use_mgr()->GetDef(composite_id);
+  uint32_t type_id = composite_inst->type_id();
+
+  Instruction::OperandList new_operands;
+  bool modified = false;
+  new_operands.emplace_back(inst->GetInOperand(0));
+  new_operands.emplace_back(inst->GetInOperand(1));
+  for (uint32_t i = 2; i < inst->NumInOperands(); ++i) {
+    uint32_t member_idx = inst->GetSingleWordInOperand(i);
+    uint32_t new_member_idx = GetNewMemberIndex(type_id, member_idx);
+    if (new_member_idx == kRemovedMember) {
+      context()->KillInst(inst);
+      return true;
+    }
+
+    if (member_idx != new_member_idx) {
+      modified = true;
+    }
+    new_operands.emplace_back(
+        Operand({SPV_OPERAND_TYPE_LITERAL_INTEGER, {new_member_idx}}));
+
+    Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
+    switch (type_inst->opcode()) {
+      case SpvOpTypeStruct:
+        // The type will have already been rewritten, so use the new member
+        // index.
+        type_id = type_inst->GetSingleWordInOperand(new_member_idx);
+        break;
+      case SpvOpTypeArray:
+      case SpvOpTypeRuntimeArray:
+      case SpvOpTypeVector:
+      case SpvOpTypeMatrix:
+        type_id = type_inst->GetSingleWordInOperand(0);
+        break;
+      default:
+        assert(false);
+    }
+  }
+
+  if (!modified) {
+    return false;
+  }
+  inst->SetInOperands(std::move(new_operands));
+  context()->UpdateDefUse(inst);
+  return true;
+}
+
+bool EliminateDeadMembersPass::UpdateOpArrayLength(Instruction* inst) {
+  uint32_t struct_id = inst->GetSingleWordInOperand(0);
+  Instruction* struct_inst = get_def_use_mgr()->GetDef(struct_id);
+  uint32_t pointer_type_id = struct_inst->type_id();
+  Instruction* pointer_type_inst = get_def_use_mgr()->GetDef(pointer_type_id);
+  uint32_t type_id = pointer_type_inst->GetSingleWordInOperand(1);
+
+  uint32_t member_idx = inst->GetSingleWordInOperand(1);
+  uint32_t new_member_idx = GetNewMemberIndex(type_id, member_idx);
+  assert(new_member_idx != kRemovedMember);
+
+  if (member_idx == new_member_idx) {
+    return false;
+  }
+
+  inst->SetInOperand(1, {new_member_idx});
+  context()->UpdateDefUse(inst);
+  return true;
+}
+
+void EliminateDeadMembersPass::MarkStructOperandsAsFullyUsed(
+    const Instruction* inst) {
+  if (inst->type_id() != 0) {
+    MarkTypeAsFullyUsed(inst->type_id());
+  }
+
+  inst->ForEachInId([this](const uint32_t* id) {
+    Instruction* instruction = get_def_use_mgr()->GetDef(*id);
+    if (instruction->type_id() != 0) {
+      MarkTypeAsFullyUsed(instruction->type_id());
+    }
+  });
+}
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/eliminate_dead_members_pass.h b/source/opt/eliminate_dead_members_pass.h
new file mode 100644
index 0000000..4feaa55
--- /dev/null
+++ b/source/opt/eliminate_dead_members_pass.h
@@ -0,0 +1,146 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_ELIMINATE_DEAD_MEMBERS_PASS_H_
+#define SOURCE_OPT_ELIMINATE_DEAD_MEMBERS_PASS_H_
+
+#include "source/opt/def_use_manager.h"
+#include "source/opt/function.h"
+#include "source/opt/mem_pass.h"
+#include "source/opt/module.h"
+
+namespace spvtools {
+namespace opt {
+
+// Remove unused members from structures.  The remaining members will remain at
+// the same offset.
+class EliminateDeadMembersPass : public MemPass {
+ public:
+  const char* name() const override { return "eliminate-dead-members"; }
+  Status Process() override;
+
+  IRContext::Analysis GetPreservedAnalyses() override {
+    return IRContext::kAnalysisDefUse |
+           IRContext::kAnalysisInstrToBlockMapping |
+           IRContext::kAnalysisCombinators | IRContext::kAnalysisCFG |
+           IRContext::kAnalysisDominatorAnalysis |
+           IRContext::kAnalysisLoopAnalysis |
+           IRContext::kAnalysisScalarEvolution |
+           IRContext::kAnalysisRegisterPressure |
+           IRContext::kAnalysisValueNumberTable |
+           IRContext::kAnalysisStructuredCFG |
+           IRContext::kAnalysisBuiltinVarId |
+           IRContext::kAnalysisIdToFuncMapping;
+  }
+
+ private:
+  // Populate |used_members_| with the member of structures that are live in the
+  // current context.
+  void FindLiveMembers();
+
+  // Add to |used_members_| the member of structures that are live in
+  // |function|.
+  void FindLiveMembers(const Function& function);
+  // Add to |used_members_| the member of structures that are live in |inst|.
+  void FindLiveMembers(const Instruction* inst);
+
+  // Add to |used_members_| the members that are live in the |OpStore|
+  // instruction |inst|.
+  void MarkMembersAsLiveForStore(const Instruction* inst);
+
+  // Add to |used_members_| the members that are live in the |OpCopyMemory*|
+  // instruction |inst|.
+  void MarkMembersAsLiveForCopyMemory(const Instruction* inst);
+
+  // Add to |used_members_| the members that are live in the
+  // |OpCompositeExtract| instruction |inst|.
+  void MarkMembersAsLiveForExtract(const Instruction* inst);
+
+  // Add to |used_members_| the members that are live in the |Op*AccessChain|
+  // instruction |inst|.
+  void MarkMembersAsLiveForAccessChain(const Instruction* inst);
+
+  // Add the member referenced by the OpArrayLength instruction |inst| to
+  // |uses_members_|.
+  void MarkMembersAsLiveForArrayLength(const Instruction* inst);
+
+  // Remove dead members from structs and updates any instructions that need to
+  // be updated as a consequence.  Return true if something changed.
+  bool RemoveDeadMembers();
+
+  // Update |inst|, which must be an |OpMemberName| or |OpMemberDecorate|
+  // instruction, so it references the correct member after the struct is
+  // updated.  Return true if something changed.
+  bool UpdateOpMemberNameOrDecorate(Instruction* inst);
+
+  // Update |inst|, which must be an |OpGroupMemberDecorate| instruction, so it
+  // references the correct member after the struct is updated.  Return true if
+  // something changed.
+  bool UpdateOpGroupMemberDecorate(Instruction* inst);
+
+  // Update the |OpTypeStruct| instruction |inst| my removing the members that
+  // are not live.  Return true if something changed.
+  bool UpdateOpTypeStruct(Instruction* inst);
+
+  // Update the |OpConstantComposite| instruction |inst| to match the change
+  // made to the type that was being generated.  Return true if something
+  // changed.
+  bool UpdateConstantComposite(Instruction* inst);
+
+  // Update the |Op*AccessChain| instruction |inst| to reference the correct
+  // members. All members referenced in the access chain must be live.  This
+  // function must be called after the |OpTypeStruct| instruction for the type
+  // has been updated.  Return true if something changed.
+  bool UpdateAccessChain(Instruction* inst);
+
+  // Update the |OpCompositeExtract| instruction |inst| to reference the correct
+  // members. All members referenced in the instruction must be live.  This
+  // function must be called after the |OpTypeStruct| instruction for the type
+  // has been updated.  Return true if something changed.
+  bool UpdateCompsiteExtract(Instruction* inst);
+
+  // Update the |OpCompositeInsert| instruction |inst| to reference the correct
+  // members. If the member being inserted is not live, then |inst| is killed.
+  // This function must be called after the |OpTypeStruct| instruction for the
+  // type has been updated.  Return true if something changed.
+  bool UpdateCompositeInsert(Instruction* inst);
+
+  // Update the |OpArrayLength| instruction |inst| to reference the correct
+  // member. The member referenced in the instruction must be live.  Return true
+  // if something changed.
+  bool UpdateOpArrayLength(Instruction* inst);
+
+  // Add all of the members of type |type_id| and members of any subtypes to
+  // |used_members_|.
+  void MarkTypeAsFullyUsed(uint32_t type_id);
+
+  // Add all of the members of the type of the operand |in_idx| in |inst| and
+  // members of any subtypes to |uses_members_|.
+  void MarkOperandTypeAsFullyUsed(const Instruction* inst, uint32_t in_idx);
+
+  // Return the index of the member that use to be the |member_idx|th member of
+  // |type_id|.  If the member has been removed, |kRemovedMember| is returned.
+  uint32_t GetNewMemberIndex(uint32_t type_id, uint32_t member_idx);
+
+  // A map from a type id to a set of indices representing the members of the
+  // type that are used, and must be kept.
+  std::unordered_map<uint32_t, std::set<uint32_t>> used_members_;
+  void MarkStructOperandsAsFullyUsed(const Instruction* inst);
+  void MarkPointeeTypeAsFullUsed(uint32_t ptr_type_id);
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // SOURCE_OPT_ELIMINATE_DEAD_MEMBERS_PASS_H_
diff --git a/source/opt/fix_storage_class.cpp b/source/opt/fix_storage_class.cpp
new file mode 100644
index 0000000..03da0d0
--- /dev/null
+++ b/source/opt/fix_storage_class.cpp
@@ -0,0 +1,330 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "fix_storage_class.h"
+
+#include <set>
+
+#include "source/opt/instruction.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace opt {
+
+Pass::Status FixStorageClass::Process() {
+  bool modified = false;
+
+  get_module()->ForEachInst([this, &modified](Instruction* inst) {
+    if (inst->opcode() == SpvOpVariable) {
+      std::set<uint32_t> seen;
+      std::vector<std::pair<Instruction*, uint32_t>> uses;
+      get_def_use_mgr()->ForEachUse(inst,
+                                    [&uses](Instruction* use, uint32_t op_idx) {
+                                      uses.push_back({use, op_idx});
+                                    });
+
+      for (auto& use : uses) {
+        modified |= PropagateStorageClass(
+            use.first,
+            static_cast<SpvStorageClass>(inst->GetSingleWordInOperand(0)),
+            &seen);
+        assert(seen.empty() && "Seen was not properly reset.");
+        modified |=
+            PropagateType(use.first, inst->type_id(), use.second, &seen);
+        assert(seen.empty() && "Seen was not properly reset.");
+      }
+    }
+  });
+  return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
+}
+
+bool FixStorageClass::PropagateStorageClass(Instruction* inst,
+                                            SpvStorageClass storage_class,
+                                            std::set<uint32_t>* seen) {
+  if (!IsPointerResultType(inst)) {
+    return false;
+  }
+
+  if (IsPointerToStorageClass(inst, storage_class)) {
+    if (inst->opcode() == SpvOpPhi) {
+      if (!seen->insert(inst->result_id()).second) {
+        return false;
+      }
+    }
+
+    bool modified = false;
+    std::vector<Instruction*> uses;
+    get_def_use_mgr()->ForEachUser(
+        inst, [&uses](Instruction* use) { uses.push_back(use); });
+    for (Instruction* use : uses) {
+      modified |= PropagateStorageClass(use, storage_class, seen);
+    }
+
+    if (inst->opcode() == SpvOpPhi) {
+      seen->erase(inst->result_id());
+    }
+    return modified;
+  }
+
+  switch (inst->opcode()) {
+    case SpvOpAccessChain:
+    case SpvOpPtrAccessChain:
+    case SpvOpInBoundsAccessChain:
+    case SpvOpCopyObject:
+    case SpvOpPhi:
+    case SpvOpSelect:
+      FixInstructionStorageClass(inst, storage_class, seen);
+      return true;
+    case SpvOpFunctionCall:
+      // We cannot be sure of the actual connection between the storage class
+      // of the parameter and the storage class of the result, so we should not
+      // do anything.  If the result type needs to be fixed, the function call
+      // should be inlined.
+      return false;
+    case SpvOpImageTexelPointer:
+    case SpvOpLoad:
+    case SpvOpStore:
+    case SpvOpCopyMemory:
+    case SpvOpCopyMemorySized:
+    case SpvOpVariable:
+    case SpvOpBitcast:
+      // Nothing to change for these opcode.  The result type is the same
+      // regardless of the storage class of the operand.
+      return false;
+    default:
+      assert(false &&
+             "Not expecting instruction to have a pointer result type.");
+      return false;
+  }
+}
+
+void FixStorageClass::FixInstructionStorageClass(Instruction* inst,
+                                                 SpvStorageClass storage_class,
+                                                 std::set<uint32_t>* seen) {
+  assert(IsPointerResultType(inst) &&
+         "The result type of the instruction must be a pointer.");
+
+  ChangeResultStorageClass(inst, storage_class);
+
+  std::vector<Instruction*> uses;
+  get_def_use_mgr()->ForEachUser(
+      inst, [&uses](Instruction* use) { uses.push_back(use); });
+  for (Instruction* use : uses) {
+    PropagateStorageClass(use, storage_class, seen);
+  }
+}
+
+void FixStorageClass::ChangeResultStorageClass(
+    Instruction* inst, SpvStorageClass storage_class) const {
+  analysis::TypeManager* type_mgr = context()->get_type_mgr();
+  Instruction* result_type_inst = get_def_use_mgr()->GetDef(inst->type_id());
+  assert(result_type_inst->opcode() == SpvOpTypePointer);
+  uint32_t pointee_type_id = result_type_inst->GetSingleWordInOperand(1);
+  uint32_t new_result_type_id =
+      type_mgr->FindPointerToType(pointee_type_id, storage_class);
+  inst->SetResultType(new_result_type_id);
+  context()->UpdateDefUse(inst);
+}
+
+bool FixStorageClass::IsPointerResultType(Instruction* inst) {
+  if (inst->type_id() == 0) {
+    return false;
+  }
+  const analysis::Type* ret_type =
+      context()->get_type_mgr()->GetType(inst->type_id());
+  return ret_type->AsPointer() != nullptr;
+}
+
+bool FixStorageClass::IsPointerToStorageClass(Instruction* inst,
+                                              SpvStorageClass storage_class) {
+  analysis::TypeManager* type_mgr = context()->get_type_mgr();
+  analysis::Type* pType = type_mgr->GetType(inst->type_id());
+  const analysis::Pointer* result_type = pType->AsPointer();
+
+  if (result_type == nullptr) {
+    return false;
+  }
+
+  return (result_type->storage_class() == storage_class);
+}
+
+bool FixStorageClass::ChangeResultType(Instruction* inst,
+                                       uint32_t new_type_id) {
+  if (inst->type_id() == new_type_id) {
+    return false;
+  }
+
+  context()->ForgetUses(inst);
+  inst->SetResultType(new_type_id);
+  context()->AnalyzeUses(inst);
+  return true;
+}
+
+bool FixStorageClass::PropagateType(Instruction* inst, uint32_t type_id,
+                                    uint32_t op_idx, std::set<uint32_t>* seen) {
+  assert(type_id != 0 && "Not given a valid type in PropagateType");
+  bool modified = false;
+
+  // If the type of operand |op_idx| forces the result type of |inst| to a
+  // particular type, then we want find that type.
+  uint32_t new_type_id = 0;
+  switch (inst->opcode()) {
+    case SpvOpAccessChain:
+    case SpvOpPtrAccessChain:
+    case SpvOpInBoundsAccessChain:
+    case SpvOpInBoundsPtrAccessChain:
+      if (op_idx == 2) {
+        new_type_id = WalkAccessChainType(inst, type_id);
+      }
+      break;
+    case SpvOpCopyObject:
+      new_type_id = type_id;
+      break;
+    case SpvOpPhi:
+      if (seen->insert(inst->result_id()).second) {
+        new_type_id = type_id;
+      }
+      break;
+    case SpvOpSelect:
+      if (op_idx > 2) {
+        new_type_id = type_id;
+      }
+      break;
+    case SpvOpFunctionCall:
+      // We cannot be sure of the actual connection between the type
+      // of the parameter and the type of the result, so we should not
+      // do anything.  If the result type needs to be fixed, the function call
+      // should be inlined.
+      return false;
+    case SpvOpLoad: {
+      Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
+      new_type_id = type_inst->GetSingleWordInOperand(1);
+      break;
+    }
+    case SpvOpStore: {
+      uint32_t obj_id = inst->GetSingleWordInOperand(1);
+      Instruction* obj_inst = get_def_use_mgr()->GetDef(obj_id);
+      uint32_t obj_type_id = obj_inst->type_id();
+
+      uint32_t ptr_id = inst->GetSingleWordInOperand(0);
+      Instruction* ptr_inst = get_def_use_mgr()->GetDef(ptr_id);
+      uint32_t pointee_type_id = GetPointeeTypeId(ptr_inst);
+
+      if (obj_type_id != pointee_type_id) {
+        uint32_t copy_id = GenerateCopy(obj_inst, pointee_type_id, inst);
+        inst->SetInOperand(1, {copy_id});
+        context()->UpdateDefUse(inst);
+      }
+    } break;
+    case SpvOpCopyMemory:
+    case SpvOpCopyMemorySized:
+      // TODO: May need to expand the copy as we do with the stores.
+      break;
+    case SpvOpCompositeConstruct:
+    case SpvOpCompositeExtract:
+    case SpvOpCompositeInsert:
+      // TODO: DXC does not seem to generate code that will require changes to
+      // these opcode.  The can be implemented when they come up.
+      break;
+    case SpvOpImageTexelPointer:
+    case SpvOpBitcast:
+      // Nothing to change for these opcode.  The result type is the same
+      // regardless of the type of the operand.
+      return false;
+    default:
+      // I expect the remaining instructions to act on types that are guaranteed
+      // to be unique, so no change will be necessary.
+      break;
+  }
+
+  // If the operand forces the result type, then make sure the result type
+  // matches, and update the uses of |inst|.  We do not have to check the uses
+  // of |inst| in the result type is not forced because we are only looking for
+  // issue that come from mismatches between function formal and actual
+  // parameters after the function has been inlined.  These parameters are
+  // pointers. Once the type no longer depends on the type of the parameter,
+  // then the types should have be correct.
+  if (new_type_id != 0) {
+    modified = ChangeResultType(inst, new_type_id);
+
+    std::vector<std::pair<Instruction*, uint32_t>> uses;
+    get_def_use_mgr()->ForEachUse(inst,
+                                  [&uses](Instruction* use, uint32_t idx) {
+                                    uses.push_back({use, idx});
+                                  });
+
+    for (auto& use : uses) {
+      PropagateType(use.first, new_type_id, use.second, seen);
+    }
+
+    if (inst->opcode() == SpvOpPhi) {
+      seen->erase(inst->result_id());
+    }
+  }
+  return modified;
+}
+
+uint32_t FixStorageClass::WalkAccessChainType(Instruction* inst, uint32_t id) {
+  uint32_t start_idx = 0;
+  switch (inst->opcode()) {
+    case SpvOpAccessChain:
+    case SpvOpInBoundsAccessChain:
+      start_idx = 1;
+      break;
+    case SpvOpPtrAccessChain:
+    case SpvOpInBoundsPtrAccessChain:
+      start_idx = 2;
+      break;
+    default:
+      assert(false);
+      break;
+  }
+
+  Instruction* orig_type_inst = get_def_use_mgr()->GetDef(id);
+  assert(orig_type_inst->opcode() == SpvOpTypePointer);
+  id = orig_type_inst->GetSingleWordInOperand(1);
+
+  for (uint32_t i = start_idx; i < inst->NumInOperands(); ++i) {
+    Instruction* type_inst = get_def_use_mgr()->GetDef(id);
+    switch (type_inst->opcode()) {
+      case SpvOpTypeArray:
+      case SpvOpTypeRuntimeArray:
+      case SpvOpTypeMatrix:
+      case SpvOpTypeVector:
+        id = type_inst->GetSingleWordInOperand(0);
+        break;
+      case SpvOpTypeStruct: {
+        const analysis::Constant* index_const =
+            context()->get_constant_mgr()->FindDeclaredConstant(
+                inst->GetSingleWordInOperand(i));
+        uint32_t index = index_const->GetU32();
+        id = type_inst->GetSingleWordInOperand(index);
+        break;
+      }
+      default:
+        break;
+    }
+    assert(id != 0 &&
+           "Tried to extract from an object where it cannot be done.");
+  }
+
+  return context()->get_type_mgr()->FindPointerToType(
+      id,
+      static_cast<SpvStorageClass>(orig_type_inst->GetSingleWordInOperand(0)));
+}
+
+// namespace opt
+
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/fix_storage_class.h b/source/opt/fix_storage_class.h
new file mode 100644
index 0000000..e72e864
--- /dev/null
+++ b/source/opt/fix_storage_class.h
@@ -0,0 +1,93 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_FIX_STORAGE_CLASS_H_
+#define SOURCE_OPT_FIX_STORAGE_CLASS_H_
+
+#include <unordered_map>
+
+#include "source/opt/ir_context.h"
+#include "source/opt/module.h"
+#include "source/opt/pass.h"
+
+namespace spvtools {
+namespace opt {
+
+// This pass tries to fix validation error due to a mismatch of storage classes
+// in instructions.  There is no guarantee that all such error will be fixed,
+// and it is possible that in fixing these errors, it could lead to other
+// errors.
+class FixStorageClass : public Pass {
+ public:
+  const char* name() const override { return "fix-storage-class"; }
+  Status Process() override;
+
+  // Return the mask of preserved Analyses.
+  IRContext::Analysis GetPreservedAnalyses() override {
+    return IRContext::kAnalysisDefUse |
+           IRContext::kAnalysisInstrToBlockMapping |
+           IRContext::kAnalysisCombinators | IRContext::kAnalysisCFG |
+           IRContext::kAnalysisDominatorAnalysis |
+           IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap |
+           IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
+  }
+
+ private:
+  // Changes the storage class of the result of |inst| to |storage_class| in
+  // appropriate, and propagates the change to the users of |inst| as well.
+  // Returns true of any changes were made.
+  // |seen| is used to track OpPhi instructions that should not be processed.
+  bool PropagateStorageClass(Instruction* inst, SpvStorageClass storage_class,
+                             std::set<uint32_t>* seen);
+
+  // Changes the storage class of the result of |inst| to |storage_class|.
+  // Is it assumed that the result type of |inst| is a pointer type.
+  // Propagates the change to the users of |inst| as well.
+  // Returns true of any changes were made.
+  // |seen| is used to track OpPhi instructions that should not be processed by
+  // |PropagateStorageClass|
+  void FixInstructionStorageClass(Instruction* inst,
+                                  SpvStorageClass storage_class,
+                                  std::set<uint32_t>* seen);
+
+  // Changes the storage class of the result of |inst| to |storage_class|.  The
+  // result type of |inst| must be a pointer.
+  void ChangeResultStorageClass(Instruction* inst,
+                                SpvStorageClass storage_class) const;
+
+  // Returns true if the result type of |inst| is a pointer.
+  bool IsPointerResultType(Instruction* inst);
+
+  // Returns true if the result of |inst| is a pointer to storage class
+  // |storage_class|.
+  bool IsPointerToStorageClass(Instruction* inst,
+                               SpvStorageClass storage_class);
+
+  // Change |inst| to match that operand |op_idx| now has type |type_id|, and
+  // adjust any uses of |inst| accordingly. Returns true if the code changed.
+  bool PropagateType(Instruction* inst, uint32_t type_id, uint32_t op_idx,
+                     std::set<uint32_t>* seen);
+
+  // Changes the result type of |inst| to |new_type_id|.
+  bool ChangeResultType(Instruction* inst, uint32_t new_type_id);
+
+  // Returns the type id of the member of the type |id| that would be returned
+  // by following the indices of the access chain instruction |inst|.
+  uint32_t WalkAccessChainType(Instruction* inst, uint32_t id);
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // SOURCE_OPT_FIX_STORAGE_CLASS_H_
diff --git a/source/opt/fold.cpp b/source/opt/fold.cpp
index d6b583f..944f438 100644
--- a/source/opt/fold.cpp
+++ b/source/opt/fold.cpp
@@ -45,8 +45,13 @@
 uint32_t InstructionFolder::UnaryOperate(SpvOp opcode, uint32_t operand) const {
   switch (opcode) {
     // Arthimetics
-    case SpvOp::SpvOpSNegate:
-      return -static_cast<int32_t>(operand);
+    case SpvOp::SpvOpSNegate: {
+      int32_t s_operand = static_cast<int32_t>(operand);
+      if (s_operand == std::numeric_limits<int32_t>::min()) {
+        return s_operand;
+      }
+      return -s_operand;
+    }
     case SpvOp::SpvOpNot:
       return ~operand;
     case SpvOp::SpvOpLogicalNot:
@@ -115,8 +120,10 @@
 
     // Shifting
     case SpvOp::SpvOpShiftRightLogical:
-      if (b > 32) {
-        // This is undefined behaviour.  Choose 0 for consistency.
+      if (b >= 32) {
+        // This is undefined behaviour when |b| > 32.  Choose 0 for consistency.
+        // When |b| == 32, doing the shift in C++ in undefined, but the result
+        // will be 0, so just return that value.
         return 0;
       }
       return a >> b;
@@ -125,10 +132,21 @@
         // This is undefined behaviour.  Choose 0 for consistency.
         return 0;
       }
+      if (b == 32) {
+        // Doing the shift in C++ is undefined, but the result is defined in the
+        // spir-v spec.  Find that value another way.
+        if (static_cast<int32_t>(a) >= 0) {
+          return 0;
+        } else {
+          return static_cast<uint32_t>(-1);
+        }
+      }
       return (static_cast<int32_t>(a)) >> b;
     case SpvOp::SpvOpShiftLeftLogical:
-      if (b > 32) {
-        // This is undefined behaviour.  Choose 0 for consistency.
+      if (b >= 32) {
+        // This is undefined behaviour when |b| > 32.  Choose 0 for consistency.
+        // When |b| == 32, doing the shift in C++ in undefined, but the result
+        // will be 0, so just return that value.
         return 0;
       }
       return a << b;
@@ -307,7 +325,8 @@
       if (constants[1] != nullptr) {
         // When shifting by a value larger than the size of the result, the
         // result is undefined.  We are setting the undefined behaviour to a
-        // result of 0.
+        // result of 0.  If the shift amount is the same as the size of the
+        // result, then the result is defined, and it 0.
         uint32_t shift_amount = constants[1]->GetU32BitValue();
         if (shift_amount >= 32) {
           *result = 0;
diff --git a/source/opt/folding_rules.cpp b/source/opt/folding_rules.cpp
index 3274319..18d5149 100644
--- a/source/opt/folding_rules.cpp
+++ b/source/opt/folding_rules.cpp
@@ -2167,6 +2167,37 @@
   };
 }
 
+// Removes duplicate ids from the interface list of an OpEntryPoint
+// instruction.
+FoldingRule RemoveRedundantOperands() {
+  return [](IRContext*, Instruction* inst,
+            const std::vector<const analysis::Constant*>&) {
+    assert(inst->opcode() == SpvOpEntryPoint &&
+           "Wrong opcode.  Should be OpEntryPoint.");
+    bool has_redundant_operand = false;
+    std::unordered_set<uint32_t> seen_operands;
+    std::vector<Operand> new_operands;
+
+    new_operands.emplace_back(inst->GetOperand(0));
+    new_operands.emplace_back(inst->GetOperand(1));
+    new_operands.emplace_back(inst->GetOperand(2));
+    for (uint32_t i = 3; i < inst->NumOperands(); ++i) {
+      if (seen_operands.insert(inst->GetSingleWordOperand(i)).second) {
+        new_operands.emplace_back(inst->GetOperand(i));
+      } else {
+        has_redundant_operand = true;
+      }
+    }
+
+    if (!has_redundant_operand) {
+      return false;
+    }
+
+    inst->SetInOperands(std::move(new_operands));
+    return true;
+  };
+}
+
 }  // namespace
 
 FoldingRules::FoldingRules() {
@@ -2183,6 +2214,8 @@
 
   rules_[SpvOpDot].push_back(DotProductDoingExtract());
 
+  rules_[SpvOpEntryPoint].push_back(RemoveRedundantOperands());
+
   rules_[SpvOpExtInst].push_back(RedundantFMix());
 
   rules_[SpvOpFAdd].push_back(RedundantFAdd());
diff --git a/source/opt/function.cpp b/source/opt/function.cpp
index d4457ad..9bd46e2 100644
--- a/source/opt/function.cpp
+++ b/source/opt/function.cpp
@@ -46,29 +46,77 @@
 
 void Function::ForEachInst(const std::function<void(Instruction*)>& f,
                            bool run_on_debug_line_insts) {
-  if (def_inst_) def_inst_->ForEachInst(f, run_on_debug_line_insts);
-  for (auto& param : params_) param->ForEachInst(f, run_on_debug_line_insts);
-  for (auto& bb : blocks_) bb->ForEachInst(f, run_on_debug_line_insts);
-  if (end_inst_) end_inst_->ForEachInst(f, run_on_debug_line_insts);
+  WhileEachInst(
+      [&f](Instruction* inst) {
+        f(inst);
+        return true;
+      },
+      run_on_debug_line_insts);
 }
 
 void Function::ForEachInst(const std::function<void(const Instruction*)>& f,
                            bool run_on_debug_line_insts) const {
-  if (def_inst_)
-    static_cast<const Instruction*>(def_inst_.get())
-        ->ForEachInst(f, run_on_debug_line_insts);
+  WhileEachInst(
+      [&f](const Instruction* inst) {
+        f(inst);
+        return true;
+      },
+      run_on_debug_line_insts);
+}
 
-  for (const auto& param : params_)
-    static_cast<const Instruction*>(param.get())
-        ->ForEachInst(f, run_on_debug_line_insts);
+bool Function::WhileEachInst(const std::function<bool(Instruction*)>& f,
+                             bool run_on_debug_line_insts) {
+  if (def_inst_) {
+    if (!def_inst_->WhileEachInst(f, run_on_debug_line_insts)) {
+      return false;
+    }
+  }
 
-  for (const auto& bb : blocks_)
-    static_cast<const BasicBlock*>(bb.get())->ForEachInst(
-        f, run_on_debug_line_insts);
+  for (auto& param : params_) {
+    if (!param->WhileEachInst(f, run_on_debug_line_insts)) {
+      return false;
+    }
+  }
+
+  for (auto& bb : blocks_) {
+    if (!bb->WhileEachInst(f, run_on_debug_line_insts)) {
+      return false;
+    }
+  }
+
+  if (end_inst_) return end_inst_->WhileEachInst(f, run_on_debug_line_insts);
+
+  return true;
+}
+
+bool Function::WhileEachInst(const std::function<bool(const Instruction*)>& f,
+                             bool run_on_debug_line_insts) const {
+  if (def_inst_) {
+    if (!static_cast<const Instruction*>(def_inst_.get())
+             ->WhileEachInst(f, run_on_debug_line_insts)) {
+      return false;
+    }
+  }
+
+  for (const auto& param : params_) {
+    if (!static_cast<const Instruction*>(param.get())
+             ->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)) {
+      return false;
+    }
+  }
 
   if (end_inst_)
-    static_cast<const Instruction*>(end_inst_.get())
-        ->ForEachInst(f, run_on_debug_line_insts);
+    return static_cast<const Instruction*>(end_inst_.get())
+        ->WhileEachInst(f, run_on_debug_line_insts);
+
+  return true;
 }
 
 void Function::ForEachParam(const std::function<void(Instruction*)>& f,
diff --git a/source/opt/function.h b/source/opt/function.h
index 7c0166f..c80b078 100644
--- a/source/opt/function.h
+++ b/source/opt/function.h
@@ -110,6 +110,10 @@
                    bool run_on_debug_line_insts = false);
   void ForEachInst(const std::function<void(const Instruction*)>& f,
                    bool run_on_debug_line_insts = false) const;
+  bool WhileEachInst(const std::function<bool(Instruction*)>& f,
+                     bool run_on_debug_line_insts = false);
+  bool WhileEachInst(const std::function<bool(const Instruction*)>& f,
+                     bool run_on_debug_line_insts = false) const;
 
   // Runs the given function |f| on each parameter instruction in this function,
   // and optionally on debug line instructions that might precede them.
diff --git a/source/opt/generate_webgpu_initializers_pass.cpp b/source/opt/generate_webgpu_initializers_pass.cpp
new file mode 100644
index 0000000..9334b43
--- /dev/null
+++ b/source/opt/generate_webgpu_initializers_pass.cpp
@@ -0,0 +1,112 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/opt/generate_webgpu_initializers_pass.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace opt {
+
+using inst_iterator = InstructionList::iterator;
+
+namespace {
+
+bool NeedsWebGPUInitializer(Instruction* inst) {
+  if (inst->opcode() != SpvOpVariable) return false;
+
+  auto storage_class = inst->GetSingleWordOperand(2);
+  if (storage_class != SpvStorageClassOutput &&
+      storage_class != SpvStorageClassPrivate &&
+      storage_class != SpvStorageClassFunction) {
+    return false;
+  }
+
+  if (inst->NumOperands() > 3) return false;
+
+  return true;
+}
+
+}  // namespace
+
+Pass::Status GenerateWebGPUInitializersPass::Process() {
+  auto* module = context()->module();
+  bool changed = false;
+
+  // Handle global/module scoped variables
+  for (auto iter = module->types_values_begin();
+       iter != module->types_values_end(); ++iter) {
+    Instruction* inst = &(*iter);
+
+    if (inst->opcode() == SpvOpConstantNull) {
+      null_constant_type_map_[inst->type_id()] = inst;
+      seen_null_constants_.insert(inst);
+      continue;
+    }
+
+    if (!NeedsWebGPUInitializer(inst)) continue;
+
+    changed = true;
+
+    auto* constant_inst = GetNullConstantForVariable(inst);
+    if (seen_null_constants_.find(constant_inst) ==
+        seen_null_constants_.end()) {
+      constant_inst->InsertBefore(inst);
+      null_constant_type_map_[inst->type_id()] = inst;
+      seen_null_constants_.insert(inst);
+    }
+    AddNullInitializerToVariable(constant_inst, inst);
+  }
+
+  // Handle local/function scoped variables
+  for (auto func = module->begin(); func != module->end(); ++func) {
+    auto block = func->entry().get();
+    for (auto iter = block->begin();
+         iter != block->end() && iter->opcode() == SpvOpVariable; ++iter) {
+      Instruction* inst = &(*iter);
+      if (!NeedsWebGPUInitializer(inst)) continue;
+
+      changed = true;
+      auto* constant_inst = GetNullConstantForVariable(inst);
+      AddNullInitializerToVariable(constant_inst, inst);
+    }
+  }
+
+  return changed ? Status::SuccessWithChange : Status::SuccessWithoutChange;
+}
+
+Instruction* GenerateWebGPUInitializersPass::GetNullConstantForVariable(
+    Instruction* variable_inst) {
+  auto constant_mgr = context()->get_constant_mgr();
+  auto* def_use_mgr = get_def_use_mgr();
+
+  auto* ptr_inst = def_use_mgr->GetDef(variable_inst->type_id());
+  auto type_id = ptr_inst->GetInOperand(1).words[0];
+  if (null_constant_type_map_.find(type_id) == null_constant_type_map_.end()) {
+    auto* constant_type = context()->get_type_mgr()->GetType(type_id);
+    auto* constant = constant_mgr->GetConstant(constant_type, {});
+    return constant_mgr->GetDefiningInstruction(constant, type_id);
+  } else {
+    return null_constant_type_map_[type_id];
+  }
+}
+
+void GenerateWebGPUInitializersPass::AddNullInitializerToVariable(
+    Instruction* constant_inst, Instruction* variable_inst) {
+  auto constant_id = constant_inst->result_id();
+  variable_inst->AddOperand(Operand(SPV_OPERAND_TYPE_ID, {constant_id}));
+  get_def_use_mgr()->AnalyzeInstUse(variable_inst);
+}
+
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/generate_webgpu_initializers_pass.h b/source/opt/generate_webgpu_initializers_pass.h
new file mode 100644
index 0000000..f95e84c
--- /dev/null
+++ b/source/opt/generate_webgpu_initializers_pass.h
@@ -0,0 +1,62 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_GENERATE_WEBGPU_INITIALIZERS_PASS_H_
+#define SOURCE_OPT_GENERATE_WEBGPU_INITIALIZERS_PASS_H_
+
+#include "source/opt/ir_context.h"
+#include "source/opt/module.h"
+#include "source/opt/pass.h"
+
+namespace spvtools {
+namespace opt {
+
+// Adds initializers to variables with storage classes Output, Private, and
+// Function if they are missing. In the WebGPU environment these storage classes
+// require that the variables are initialized. Currently they are initialized to
+// NULL, though in the future some of them may be initialized to the first value
+// that is stored in them, if that was a constant.
+class GenerateWebGPUInitializersPass : public Pass {
+ public:
+  const char* name() const override { return "generate-webgpu-initializers"; }
+  Status Process() override;
+
+  IRContext::Analysis GetPreservedAnalyses() override {
+    return IRContext::kAnalysisInstrToBlockMapping |
+           IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
+           IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
+           IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap |
+           IRContext::kAnalysisScalarEvolution |
+           IRContext::kAnalysisRegisterPressure |
+           IRContext::kAnalysisValueNumberTable |
+           IRContext::kAnalysisStructuredCFG |
+           IRContext::kAnalysisBuiltinVarId |
+           IRContext::kAnalysisIdToFuncMapping | IRContext::kAnalysisTypes |
+           IRContext::kAnalysisDefUse | IRContext::kAnalysisConstants;
+  }
+
+ private:
+  using NullConstantTypeMap = std::unordered_map<uint32_t, Instruction*>;
+  NullConstantTypeMap null_constant_type_map_;
+  std::unordered_set<Instruction*> seen_null_constants_;
+
+  Instruction* GetNullConstantForVariable(Instruction* variable_inst);
+  void AddNullInitializerToVariable(Instruction* constant_inst,
+                                    Instruction* variable_inst);
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // SOURCE_OPT_GENERATE_WEBGPU_INITIALIZERS_PASS_H_
diff --git a/source/opt/if_conversion.h b/source/opt/if_conversion.h
index 609bdf3..db84e70 100644
--- a/source/opt/if_conversion.h
+++ b/source/opt/if_conversion.h
@@ -32,7 +32,8 @@
   IRContext::Analysis GetPreservedAnalyses() override {
     return IRContext::kAnalysisDefUse | IRContext::kAnalysisDominatorAnalysis |
            IRContext::kAnalysisInstrToBlockMapping | IRContext::kAnalysisCFG |
-           IRContext::kAnalysisNameMap;
+           IRContext::kAnalysisNameMap | IRContext::kAnalysisConstants |
+           IRContext::kAnalysisTypes;
   }
 
  private:
diff --git a/source/opt/inline_exhaustive_pass.cpp b/source/opt/inline_exhaustive_pass.cpp
index 10b5e98..24f4e73 100644
--- a/source/opt/inline_exhaustive_pass.cpp
+++ b/source/opt/inline_exhaustive_pass.cpp
@@ -21,7 +21,7 @@
 namespace spvtools {
 namespace opt {
 
-bool InlineExhaustivePass::InlineExhaustive(Function* func) {
+Pass::Status InlineExhaustivePass::InlineExhaustive(Function* func) {
   bool modified = false;
   // Using block iterators here because of block erasures and insertions.
   for (auto bi = func->begin(); bi != func->end(); ++bi) {
@@ -30,7 +30,9 @@
         // Inline call.
         std::vector<std::unique_ptr<BasicBlock>> newBlocks;
         std::vector<std::unique_ptr<Instruction>> newVars;
-        GenInlineCode(&newBlocks, &newVars, ii, bi);
+        if (!GenInlineCode(&newBlocks, &newVars, ii, bi)) {
+          return Status::Failure;
+        }
         // If call block is replaced with more than one block, point
         // succeeding phis at new last block.
         if (newBlocks.size() > 1) UpdateSucceedingPhis(newBlocks);
@@ -58,14 +60,18 @@
       }
     }
   }
-  return modified;
+  return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange);
 }
 
 Pass::Status InlineExhaustivePass::ProcessImpl() {
+  Status status = Status::SuccessWithoutChange;
   // Attempt exhaustive inlining on each entry point function in module
-  ProcessFunction pfn = [this](Function* fp) { return InlineExhaustive(fp); };
-  bool modified = context()->ProcessEntryPointCallTree(pfn);
-  return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
+  ProcessFunction pfn = [&status, this](Function* fp) {
+    status = CombineStatus(status, InlineExhaustive(fp));
+    return false;
+  };
+  context()->ProcessEntryPointCallTree(pfn);
+  return status;
 }
 
 InlineExhaustivePass::InlineExhaustivePass() = default;
diff --git a/source/opt/inline_exhaustive_pass.h b/source/opt/inline_exhaustive_pass.h
index 103e091..c2e8547 100644
--- a/source/opt/inline_exhaustive_pass.h
+++ b/source/opt/inline_exhaustive_pass.h
@@ -40,8 +40,8 @@
 
  private:
   // Exhaustively inline all function calls in func as well as in
-  // all code that is inlined into func. Return true if func is modified.
-  bool InlineExhaustive(Function* func);
+  // all code that is inlined into func. Returns the status.
+  Status InlineExhaustive(Function* func);
 
   void Initialize();
   Pass::Status ProcessImpl();
diff --git a/source/opt/inline_opaque_pass.cpp b/source/opt/inline_opaque_pass.cpp
index e94f26d..6ccaf90 100644
--- a/source/opt/inline_opaque_pass.cpp
+++ b/source/opt/inline_opaque_pass.cpp
@@ -63,7 +63,7 @@
   });
 }
 
-bool InlineOpaquePass::InlineOpaque(Function* func) {
+Pass::Status InlineOpaquePass::InlineOpaque(Function* func) {
   bool modified = false;
   // Using block iterators here because of block erasures and insertions.
   for (auto bi = func->begin(); bi != func->end(); ++bi) {
@@ -72,7 +72,10 @@
         // Inline call.
         std::vector<std::unique_ptr<BasicBlock>> newBlocks;
         std::vector<std::unique_ptr<Instruction>> newVars;
-        GenInlineCode(&newBlocks, &newVars, ii, bi);
+        if (!GenInlineCode(&newBlocks, &newVars, ii, bi)) {
+          return Status::Failure;
+        }
+
         // If call block is replaced with more than one block, point
         // succeeding phis at new last block.
         if (newBlocks.size() > 1) UpdateSucceedingPhis(newBlocks);
@@ -90,16 +93,20 @@
       }
     }
   }
-  return modified;
+  return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange);
 }
 
 void InlineOpaquePass::Initialize() { InitializeInline(); }
 
 Pass::Status InlineOpaquePass::ProcessImpl() {
+  Status status = Status::SuccessWithoutChange;
   // Do opaque inlining on each function in entry point call tree
-  ProcessFunction pfn = [this](Function* fp) { return InlineOpaque(fp); };
-  bool modified = context()->ProcessEntryPointCallTree(pfn);
-  return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
+  ProcessFunction pfn = [&status, this](Function* fp) {
+    status = CombineStatus(status, InlineOpaque(fp));
+    return false;
+  };
+  context()->ProcessEntryPointCallTree(pfn);
+  return status;
 }
 
 InlineOpaquePass::InlineOpaquePass() = default;
diff --git a/source/opt/inline_opaque_pass.h b/source/opt/inline_opaque_pass.h
index aad43fd..1e3081d 100644
--- a/source/opt/inline_opaque_pass.h
+++ b/source/opt/inline_opaque_pass.h
@@ -48,7 +48,7 @@
   // Inline all function calls in |func| that have opaque params or return
   // type. Inline similarly all code that is inlined into func. Return true
   // if func is modified.
-  bool InlineOpaque(Function* func);
+  Status InlineOpaque(Function* func);
 
   void Initialize();
   Pass::Status ProcessImpl();
diff --git a/source/opt/inline_pass.cpp b/source/opt/inline_pass.cpp
index cdd5659..f348bbe 100644
--- a/source/opt/inline_pass.cpp
+++ b/source/opt/inline_pass.cpp
@@ -34,7 +34,11 @@
 
 uint32_t InlinePass::AddPointerToType(uint32_t type_id,
                                       SpvStorageClass storage_class) {
-  uint32_t resultId = TakeNextId();
+  uint32_t resultId = context()->TakeNextId();
+  if (resultId == 0) {
+    return resultId;
+  }
+
   std::unique_ptr<Instruction> type_inst(
       new Instruction(context(), SpvOpTypePointer, 0, resultId,
                       {{spv_operand_type_t::SPV_OPERAND_TYPE_STORAGE_CLASS,
@@ -108,10 +112,16 @@
   if (false_id_ != 0) return false_id_;
   uint32_t boolId = get_module()->GetGlobalValue(SpvOpTypeBool);
   if (boolId == 0) {
-    boolId = TakeNextId();
+    boolId = context()->TakeNextId();
+    if (boolId == 0) {
+      return 0;
+    }
     get_module()->AddGlobalValue(SpvOpTypeBool, boolId, 0);
   }
-  false_id_ = TakeNextId();
+  false_id_ = context()->TakeNextId();
+  if (false_id_ == 0) {
+    return 0;
+  }
   get_module()->AddGlobalValue(SpvOpConstantFalse, false_id_, boolId);
   return false_id_;
 }
@@ -120,50 +130,64 @@
     Function* calleeFn, BasicBlock::iterator call_inst_itr,
     std::unordered_map<uint32_t, uint32_t>* callee2caller) {
   int param_idx = 0;
-  calleeFn->ForEachParam([&call_inst_itr, &param_idx,
-                          &callee2caller](const Instruction* cpi) {
-    const uint32_t pid = cpi->result_id();
-    (*callee2caller)[pid] = call_inst_itr->GetSingleWordOperand(
-        kSpvFunctionCallArgumentId + param_idx);
-    ++param_idx;
-  });
+  calleeFn->ForEachParam(
+      [&call_inst_itr, &param_idx, &callee2caller](const Instruction* cpi) {
+        const uint32_t pid = cpi->result_id();
+        (*callee2caller)[pid] = call_inst_itr->GetSingleWordOperand(
+            kSpvFunctionCallArgumentId + param_idx);
+        ++param_idx;
+      });
 }
 
-void InlinePass::CloneAndMapLocals(
+bool InlinePass::CloneAndMapLocals(
     Function* calleeFn, std::vector<std::unique_ptr<Instruction>>* new_vars,
     std::unordered_map<uint32_t, uint32_t>* callee2caller) {
   auto callee_block_itr = calleeFn->begin();
   auto callee_var_itr = callee_block_itr->begin();
   while (callee_var_itr->opcode() == SpvOp::SpvOpVariable) {
     std::unique_ptr<Instruction> var_inst(callee_var_itr->Clone(context()));
-    uint32_t newId = TakeNextId();
+    uint32_t newId = context()->TakeNextId();
+    if (newId == 0) {
+      return false;
+    }
     get_decoration_mgr()->CloneDecorations(callee_var_itr->result_id(), newId);
     var_inst->SetResultId(newId);
     (*callee2caller)[callee_var_itr->result_id()] = newId;
     new_vars->push_back(std::move(var_inst));
     ++callee_var_itr;
   }
+  return true;
 }
 
 uint32_t InlinePass::CreateReturnVar(
     Function* calleeFn, std::vector<std::unique_ptr<Instruction>>* new_vars) {
   uint32_t returnVarId = 0;
   const uint32_t calleeTypeId = calleeFn->type_id();
-  analysis::Type* calleeType = context()->get_type_mgr()->GetType(calleeTypeId);
-  if (calleeType->AsVoid() == nullptr) {
-    // Find or create ptr to callee return type.
-    uint32_t returnVarTypeId = context()->get_type_mgr()->FindPointerToType(
-        calleeTypeId, SpvStorageClassFunction);
-    if (returnVarTypeId == 0)
-      returnVarTypeId = AddPointerToType(calleeTypeId, SpvStorageClassFunction);
-    // Add return var to new function scope variables.
-    returnVarId = TakeNextId();
-    std::unique_ptr<Instruction> var_inst(
-        new Instruction(context(), SpvOpVariable, returnVarTypeId, returnVarId,
-                        {{spv_operand_type_t::SPV_OPERAND_TYPE_STORAGE_CLASS,
-                          {SpvStorageClassFunction}}}));
-    new_vars->push_back(std::move(var_inst));
+  analysis::TypeManager* type_mgr = context()->get_type_mgr();
+  assert(type_mgr->GetType(calleeTypeId)->AsVoid() == nullptr &&
+         "Cannot create a return variable of type void.");
+  // Find or create ptr to callee return type.
+  uint32_t returnVarTypeId =
+      type_mgr->FindPointerToType(calleeTypeId, SpvStorageClassFunction);
+
+  if (returnVarTypeId == 0) {
+    returnVarTypeId = AddPointerToType(calleeTypeId, SpvStorageClassFunction);
+    if (returnVarTypeId == 0) {
+      return 0;
+    }
   }
+
+  // Add return var to new function scope variables.
+  returnVarId = context()->TakeNextId();
+  if (returnVarId == 0) {
+    return 0;
+  }
+
+  std::unique_ptr<Instruction> var_inst(
+      new Instruction(context(), SpvOpVariable, returnVarTypeId, returnVarId,
+                      {{spv_operand_type_t::SPV_OPERAND_TYPE_STORAGE_CLASS,
+                        {SpvStorageClassFunction}}}));
+  new_vars->push_back(std::move(var_inst));
   get_decoration_mgr()->CloneDecorations(calleeFn->result_id(), returnVarId);
   return returnVarId;
 }
@@ -172,37 +196,44 @@
   return inst->opcode() == SpvOpSampledImage || inst->opcode() == SpvOpImage;
 }
 
-void InlinePass::CloneSameBlockOps(
+bool InlinePass::CloneSameBlockOps(
     std::unique_ptr<Instruction>* inst,
     std::unordered_map<uint32_t, uint32_t>* postCallSB,
     std::unordered_map<uint32_t, Instruction*>* preCallSB,
     std::unique_ptr<BasicBlock>* block_ptr) {
-  (*inst)->ForEachInId(
-      [&postCallSB, &preCallSB, &block_ptr, this](uint32_t* iid) {
-        const auto mapItr = (*postCallSB).find(*iid);
-        if (mapItr == (*postCallSB).end()) {
-          const auto mapItr2 = (*preCallSB).find(*iid);
-          if (mapItr2 != (*preCallSB).end()) {
-            // Clone pre-call same-block ops, map result id.
-            const Instruction* inInst = mapItr2->second;
-            std::unique_ptr<Instruction> sb_inst(inInst->Clone(context()));
-            CloneSameBlockOps(&sb_inst, postCallSB, preCallSB, block_ptr);
-            const uint32_t rid = sb_inst->result_id();
-            const uint32_t nid = this->TakeNextId();
-            get_decoration_mgr()->CloneDecorations(rid, nid);
-            sb_inst->SetResultId(nid);
-            (*postCallSB)[rid] = nid;
-            *iid = nid;
-            (*block_ptr)->AddInstruction(std::move(sb_inst));
-          }
-        } else {
-          // Reset same-block op operand.
-          *iid = mapItr->second;
+  return (*inst)->WhileEachInId([&postCallSB, &preCallSB, &block_ptr,
+                                 this](uint32_t* iid) {
+    const auto mapItr = (*postCallSB).find(*iid);
+    if (mapItr == (*postCallSB).end()) {
+      const auto mapItr2 = (*preCallSB).find(*iid);
+      if (mapItr2 != (*preCallSB).end()) {
+        // Clone pre-call same-block ops, map result id.
+        const Instruction* inInst = mapItr2->second;
+        std::unique_ptr<Instruction> sb_inst(inInst->Clone(context()));
+        if (!CloneSameBlockOps(&sb_inst, postCallSB, preCallSB, block_ptr)) {
+          return false;
         }
-      });
+
+        const uint32_t rid = sb_inst->result_id();
+        const uint32_t nid = context()->TakeNextId();
+        if (nid == 0) {
+          return false;
+        }
+        get_decoration_mgr()->CloneDecorations(rid, nid);
+        sb_inst->SetResultId(nid);
+        (*postCallSB)[rid] = nid;
+        *iid = nid;
+        (*block_ptr)->AddInstruction(std::move(sb_inst));
+      }
+    } else {
+      // Reset same-block op operand.
+      *iid = mapItr->second;
+    }
+    return true;
+  });
 }
 
-void InlinePass::GenInlineCode(
+bool InlinePass::GenInlineCode(
     std::vector<std::unique_ptr<BasicBlock>>* new_blocks,
     std::vector<std::unique_ptr<Instruction>>* new_vars,
     BasicBlock::iterator call_inst_itr,
@@ -232,10 +263,20 @@
 
   // Define caller local variables for all callee variables and create map to
   // them.
-  CloneAndMapLocals(calleeFn, new_vars, &callee2caller);
+  if (!CloneAndMapLocals(calleeFn, new_vars, &callee2caller)) {
+    return false;
+  }
 
   // Create return var if needed.
-  uint32_t returnVarId = CreateReturnVar(calleeFn, new_vars);
+  const uint32_t calleeTypeId = calleeFn->type_id();
+  uint32_t returnVarId = 0;
+  analysis::Type* calleeType = context()->get_type_mgr()->GetType(calleeTypeId);
+  if (calleeType->AsVoid() == nullptr) {
+    returnVarId = CreateReturnVar(calleeFn, new_vars);
+    if (returnVarId == 0) {
+      return false;
+    }
+  }
 
   // Create set of callee result ids. Used to detect forward references
   std::unordered_set<uint32_t> callee_result_ids;
@@ -269,241 +310,294 @@
   uint32_t singleTripLoopContinueId = 0;
   uint32_t returnLabelId = 0;
   bool multiBlocks = false;
-  const uint32_t calleeTypeId = calleeFn->type_id();
   // new_blk_ptr is a new basic block in the caller.  New instructions are
   // written to it.  It is created when we encounter the OpLabel
   // of the first callee block.  It is appended to new_blocks only when
   // it is complete.
   std::unique_ptr<BasicBlock> new_blk_ptr;
-  calleeFn->ForEachInst([&new_blocks, &callee2caller, &call_block_itr,
-                         &call_inst_itr, &new_blk_ptr, &prevInstWasReturn,
-                         &returnLabelId, &returnVarId, caller_is_loop_header,
-                         callee_begins_with_structured_header, &calleeTypeId,
-                         &multiBlocks, &postCallSB, &preCallSB, earlyReturn,
-                         &singleTripLoopHeaderId, &singleTripLoopContinueId,
-                         &callee_result_ids, this](const Instruction* cpi) {
-    switch (cpi->opcode()) {
-      case SpvOpFunction:
-      case SpvOpFunctionParameter:
-        // Already processed
-        break;
-      case SpvOpVariable:
-        if (cpi->NumInOperands() == 2) {
-          assert(callee2caller.count(cpi->result_id()) &&
-                 "Expected the variable to have already been mapped.");
-          uint32_t new_var_id = callee2caller.at(cpi->result_id());
+  bool successful = calleeFn->WhileEachInst(
+      [&new_blocks, &callee2caller, &call_block_itr, &call_inst_itr,
+       &new_blk_ptr, &prevInstWasReturn, &returnLabelId, &returnVarId,
+       caller_is_loop_header, callee_begins_with_structured_header,
+       &calleeTypeId, &multiBlocks, &postCallSB, &preCallSB, earlyReturn,
+       &singleTripLoopHeaderId, &singleTripLoopContinueId, &callee_result_ids,
+       this](const Instruction* cpi) {
+        switch (cpi->opcode()) {
+          case SpvOpFunction:
+          case SpvOpFunctionParameter:
+            // Already processed
+            break;
+          case SpvOpVariable:
+            if (cpi->NumInOperands() == 2) {
+              assert(callee2caller.count(cpi->result_id()) &&
+                     "Expected the variable to have already been mapped.");
+              uint32_t new_var_id = callee2caller.at(cpi->result_id());
 
-          // The initializer must be a constant or global value.  No mapped
-          // should be used.
-          uint32_t val_id = cpi->GetSingleWordInOperand(1);
-          AddStore(new_var_id, val_id, &new_blk_ptr);
-        }
-        break;
-      case SpvOpUnreachable:
-      case SpvOpKill: {
-        // Generate a return label so that we split the block with the function
-        // call. Copy the terminator into the new block.
-        if (returnLabelId == 0) returnLabelId = this->TakeNextId();
-        std::unique_ptr<Instruction> terminator(
-            new Instruction(context(), cpi->opcode(), 0, 0, {}));
-        new_blk_ptr->AddInstruction(std::move(terminator));
-        break;
-      }
-      case SpvOpLabel: {
-        // If previous instruction was early return, insert branch
-        // instruction to return block.
-        if (prevInstWasReturn) {
-          if (returnLabelId == 0) returnLabelId = this->TakeNextId();
-          AddBranch(returnLabelId, &new_blk_ptr);
-          prevInstWasReturn = false;
-        }
-        // Finish current block (if it exists) and get label for next block.
-        uint32_t labelId;
-        bool firstBlock = false;
-        if (new_blk_ptr != nullptr) {
-          new_blocks->push_back(std::move(new_blk_ptr));
-          // If result id is already mapped, use it, otherwise get a new
-          // one.
-          const uint32_t rid = cpi->result_id();
-          const auto mapItr = callee2caller.find(rid);
-          labelId = (mapItr != callee2caller.end()) ? mapItr->second
-                                                    : this->TakeNextId();
-        } else {
-          // First block needs to use label of original block
-          // but map callee label in case of phi reference.
-          labelId = call_block_itr->id();
-          callee2caller[cpi->result_id()] = labelId;
-          firstBlock = true;
-        }
-        // Create first/next block.
-        new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(labelId));
-        if (firstBlock) {
-          // Copy contents of original caller block up to call instruction.
-          for (auto cii = call_block_itr->begin(); cii != call_inst_itr;
-               cii = call_block_itr->begin()) {
-            Instruction* inst = &*cii;
-            inst->RemoveFromList();
-            std::unique_ptr<Instruction> cp_inst(inst);
-            // Remember same-block ops for possible regeneration.
-            if (IsSameBlockOp(&*cp_inst)) {
-              auto* sb_inst_ptr = cp_inst.get();
-              preCallSB[cp_inst->result_id()] = sb_inst_ptr;
+              // The initializer must be a constant or global value.  No mapped
+              // should be used.
+              uint32_t val_id = cpi->GetSingleWordInOperand(1);
+              AddStore(new_var_id, val_id, &new_blk_ptr);
+            }
+            break;
+          case SpvOpUnreachable:
+          case SpvOpKill: {
+            // Generate a return label so that we split the block with the
+            // function call. Copy the terminator into the new block.
+            if (returnLabelId == 0) {
+              returnLabelId = context()->TakeNextId();
+              if (returnLabelId == 0) {
+                return false;
+              }
+            }
+            std::unique_ptr<Instruction> terminator(
+                new Instruction(context(), cpi->opcode(), 0, 0, {}));
+            new_blk_ptr->AddInstruction(std::move(terminator));
+            break;
+          }
+          case SpvOpLabel: {
+            // If previous instruction was early return, insert branch
+            // instruction to return block.
+            if (prevInstWasReturn) {
+              if (returnLabelId == 0) {
+                returnLabelId = context()->TakeNextId();
+                if (returnLabelId == 0) {
+                  return false;
+                }
+              }
+              AddBranch(returnLabelId, &new_blk_ptr);
+              prevInstWasReturn = false;
+            }
+            // Finish current block (if it exists) and get label for next block.
+            uint32_t labelId;
+            bool firstBlock = false;
+            if (new_blk_ptr != nullptr) {
+              new_blocks->push_back(std::move(new_blk_ptr));
+              // If result id is already mapped, use it, otherwise get a new
+              // one.
+              const uint32_t rid = cpi->result_id();
+              const auto mapItr = callee2caller.find(rid);
+              labelId = (mapItr != callee2caller.end())
+                            ? mapItr->second
+                            : context()->TakeNextId();
+              if (labelId == 0) {
+                return false;
+              }
+            } else {
+              // First block needs to use label of original block
+              // but map callee label in case of phi reference.
+              labelId = call_block_itr->id();
+              callee2caller[cpi->result_id()] = labelId;
+              firstBlock = true;
+            }
+            // Create first/next block.
+            new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(labelId));
+            if (firstBlock) {
+              // Copy contents of original caller block up to call instruction.
+              for (auto cii = call_block_itr->begin(); cii != call_inst_itr;
+                   cii = call_block_itr->begin()) {
+                Instruction* inst = &*cii;
+                inst->RemoveFromList();
+                std::unique_ptr<Instruction> cp_inst(inst);
+                // Remember same-block ops for possible regeneration.
+                if (IsSameBlockOp(&*cp_inst)) {
+                  auto* sb_inst_ptr = cp_inst.get();
+                  preCallSB[cp_inst->result_id()] = sb_inst_ptr;
+                }
+                new_blk_ptr->AddInstruction(std::move(cp_inst));
+              }
+              if (caller_is_loop_header &&
+                  callee_begins_with_structured_header) {
+                // We can't place both the caller's merge instruction and
+                // another merge instruction in the same block.  So split the
+                // calling block. Insert an unconditional branch to a new guard
+                // block.  Later, once we know the ID of the last block,  we
+                // will move the caller's OpLoopMerge from the last generated
+                // block into the first block. We also wait to avoid
+                // invalidating various iterators.
+                const auto guard_block_id = context()->TakeNextId();
+                if (guard_block_id == 0) {
+                  return false;
+                }
+                AddBranch(guard_block_id, &new_blk_ptr);
+                new_blocks->push_back(std::move(new_blk_ptr));
+                // Start the next block.
+                new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(guard_block_id));
+                // Reset the mapping of the callee's entry block to point to
+                // the guard block.  Do this so we can fix up phis later on to
+                // satisfy dominance.
+                callee2caller[cpi->result_id()] = guard_block_id;
+              }
+              // If callee has early return, insert a header block for
+              // single-trip loop that will encompass callee code.  Start
+              // postheader block.
+              //
+              // Note: Consider the following combination:
+              //  - the caller is a single block loop
+              //  - the callee does not begin with a structure header
+              //  - the callee has multiple returns.
+              // We still need to split the caller block and insert a guard
+              // block. But we only need to do it once. We haven't done it yet,
+              // but the single-trip loop header will serve the same purpose.
+              if (earlyReturn) {
+                singleTripLoopHeaderId = context()->TakeNextId();
+                if (singleTripLoopHeaderId == 0) {
+                  return false;
+                }
+                AddBranch(singleTripLoopHeaderId, &new_blk_ptr);
+                new_blocks->push_back(std::move(new_blk_ptr));
+                new_blk_ptr =
+                    MakeUnique<BasicBlock>(NewLabel(singleTripLoopHeaderId));
+                returnLabelId = context()->TakeNextId();
+                singleTripLoopContinueId = context()->TakeNextId();
+                if (returnLabelId == 0 || singleTripLoopContinueId == 0) {
+                  return false;
+                }
+                AddLoopMerge(returnLabelId, singleTripLoopContinueId,
+                             &new_blk_ptr);
+                uint32_t postHeaderId = context()->TakeNextId();
+                if (postHeaderId == 0) {
+                  return false;
+                }
+                AddBranch(postHeaderId, &new_blk_ptr);
+                new_blocks->push_back(std::move(new_blk_ptr));
+                new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(postHeaderId));
+                multiBlocks = true;
+                // Reset the mapping of the callee's entry block to point to
+                // the post-header block.  Do this so we can fix up phis later
+                // on to satisfy dominance.
+                callee2caller[cpi->result_id()] = postHeaderId;
+              }
+            } else {
+              multiBlocks = true;
+            }
+          } break;
+          case SpvOpReturnValue: {
+            // Store return value to return variable.
+            assert(returnVarId != 0);
+            uint32_t valId = cpi->GetInOperand(kSpvReturnValueId).words[0];
+            const auto mapItr = callee2caller.find(valId);
+            if (mapItr != callee2caller.end()) {
+              valId = mapItr->second;
+            }
+            AddStore(returnVarId, valId, &new_blk_ptr);
+
+            // Remember we saw a return; if followed by a label, will need to
+            // insert branch.
+            prevInstWasReturn = true;
+          } break;
+          case SpvOpReturn: {
+            // Remember we saw a return; if followed by a label, will need to
+            // insert branch.
+            prevInstWasReturn = true;
+          } break;
+          case SpvOpFunctionEnd: {
+            // If there was an early return, we generated a return label id
+            // for it.  Now we have to generate the return block with that Id.
+            if (returnLabelId != 0) {
+              // If previous instruction was return, insert branch instruction
+              // to return block.
+              if (prevInstWasReturn) AddBranch(returnLabelId, &new_blk_ptr);
+              if (earlyReturn) {
+                // If we generated a loop header for the single-trip loop
+                // to accommodate early returns, insert the continue
+                // target block now, with a false branch back to the loop
+                // header.
+                new_blocks->push_back(std::move(new_blk_ptr));
+                new_blk_ptr =
+                    MakeUnique<BasicBlock>(NewLabel(singleTripLoopContinueId));
+                uint32_t false_id = GetFalseId();
+                if (false_id == 0) {
+                  return false;
+                }
+                AddBranchCond(false_id, singleTripLoopHeaderId, returnLabelId,
+                              &new_blk_ptr);
+              }
+              // Generate the return block.
+              new_blocks->push_back(std::move(new_blk_ptr));
+              new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(returnLabelId));
+              multiBlocks = true;
+            }
+            // Load return value into result id of call, if it exists.
+            if (returnVarId != 0) {
+              const uint32_t resId = call_inst_itr->result_id();
+              assert(resId != 0);
+              AddLoad(calleeTypeId, resId, returnVarId, &new_blk_ptr);
+            }
+            // Copy remaining instructions from caller block.
+            for (Instruction* inst = call_inst_itr->NextNode(); inst;
+                 inst = call_inst_itr->NextNode()) {
+              inst->RemoveFromList();
+              std::unique_ptr<Instruction> cp_inst(inst);
+              // If multiple blocks generated, regenerate any same-block
+              // instruction that has not been seen in this last block.
+              if (multiBlocks) {
+                if (!CloneSameBlockOps(&cp_inst, &postCallSB, &preCallSB,
+                                       &new_blk_ptr)) {
+                  return false;
+                }
+
+                // Remember same-block ops in this block.
+                if (IsSameBlockOp(&*cp_inst)) {
+                  const uint32_t rid = cp_inst->result_id();
+                  postCallSB[rid] = rid;
+                }
+              }
+              new_blk_ptr->AddInstruction(std::move(cp_inst));
+            }
+            // Finalize inline code.
+            new_blocks->push_back(std::move(new_blk_ptr));
+          } break;
+          default: {
+            // Copy callee instruction and remap all input Ids.
+            std::unique_ptr<Instruction> cp_inst(cpi->Clone(context()));
+            bool succeeded = cp_inst->WhileEachInId(
+                [&callee2caller, &callee_result_ids, this](uint32_t* iid) {
+                  const auto mapItr = callee2caller.find(*iid);
+                  if (mapItr != callee2caller.end()) {
+                    *iid = mapItr->second;
+                  } else if (callee_result_ids.find(*iid) !=
+                             callee_result_ids.end()) {
+                    // Forward reference. Allocate a new id, map it,
+                    // use it and check for it when remapping result ids
+                    const uint32_t nid = context()->TakeNextId();
+                    if (nid == 0) {
+                      return false;
+                    }
+                    callee2caller[*iid] = nid;
+                    *iid = nid;
+                  }
+                  return true;
+                });
+            if (!succeeded) {
+              return false;
+            }
+            // If result id is non-zero, remap it. If already mapped, use mapped
+            // value, else use next id.
+            const uint32_t rid = cp_inst->result_id();
+            if (rid != 0) {
+              const auto mapItr = callee2caller.find(rid);
+              uint32_t nid;
+              if (mapItr != callee2caller.end()) {
+                nid = mapItr->second;
+              } else {
+                nid = context()->TakeNextId();
+                if (nid == 0) {
+                  return false;
+                }
+                callee2caller[rid] = nid;
+              }
+              cp_inst->SetResultId(nid);
+              get_decoration_mgr()->CloneDecorations(rid, nid);
             }
             new_blk_ptr->AddInstruction(std::move(cp_inst));
-          }
-          if (caller_is_loop_header && callee_begins_with_structured_header) {
-            // We can't place both the caller's merge instruction and another
-            // merge instruction in the same block.  So split the calling block.
-            // Insert an unconditional branch to a new guard block.  Later,
-            // once we know the ID of the last block,  we will move the caller's
-            // OpLoopMerge from the last generated block into the first block.
-            // We also wait to avoid invalidating various iterators.
-            const auto guard_block_id = this->TakeNextId();
-            AddBranch(guard_block_id, &new_blk_ptr);
-            new_blocks->push_back(std::move(new_blk_ptr));
-            // Start the next block.
-            new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(guard_block_id));
-            // Reset the mapping of the callee's entry block to point to
-            // the guard block.  Do this so we can fix up phis later on to
-            // satisfy dominance.
-            callee2caller[cpi->result_id()] = guard_block_id;
-          }
-          // If callee has early return, insert a header block for
-          // single-trip loop that will encompass callee code.  Start postheader
-          // block.
-          //
-          // Note: Consider the following combination:
-          //  - the caller is a single block loop
-          //  - the callee does not begin with a structure header
-          //  - the callee has multiple returns.
-          // We still need to split the caller block and insert a guard block.
-          // But we only need to do it once. We haven't done it yet, but the
-          // single-trip loop header will serve the same purpose.
-          if (earlyReturn) {
-            singleTripLoopHeaderId = this->TakeNextId();
-            AddBranch(singleTripLoopHeaderId, &new_blk_ptr);
-            new_blocks->push_back(std::move(new_blk_ptr));
-            new_blk_ptr =
-                MakeUnique<BasicBlock>(NewLabel(singleTripLoopHeaderId));
-            returnLabelId = this->TakeNextId();
-            singleTripLoopContinueId = this->TakeNextId();
-            AddLoopMerge(returnLabelId, singleTripLoopContinueId, &new_blk_ptr);
-            uint32_t postHeaderId = this->TakeNextId();
-            AddBranch(postHeaderId, &new_blk_ptr);
-            new_blocks->push_back(std::move(new_blk_ptr));
-            new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(postHeaderId));
-            multiBlocks = true;
-            // Reset the mapping of the callee's entry block to point to
-            // the post-header block.  Do this so we can fix up phis later
-            // on to satisfy dominance.
-            callee2caller[cpi->result_id()] = postHeaderId;
-          }
-        } else {
-          multiBlocks = true;
+          } break;
         }
-      } break;
-      case SpvOpReturnValue: {
-        // Store return value to return variable.
-        assert(returnVarId != 0);
-        uint32_t valId = cpi->GetInOperand(kSpvReturnValueId).words[0];
-        const auto mapItr = callee2caller.find(valId);
-        if (mapItr != callee2caller.end()) {
-          valId = mapItr->second;
-        }
-        AddStore(returnVarId, valId, &new_blk_ptr);
+        return true;
+      });
 
-        // Remember we saw a return; if followed by a label, will need to
-        // insert branch.
-        prevInstWasReturn = true;
-      } break;
-      case SpvOpReturn: {
-        // Remember we saw a return; if followed by a label, will need to
-        // insert branch.
-        prevInstWasReturn = true;
-      } break;
-      case SpvOpFunctionEnd: {
-        // If there was an early return, we generated a return label id
-        // for it.  Now we have to generate the return block with that Id.
-        if (returnLabelId != 0) {
-          // If previous instruction was return, insert branch instruction
-          // to return block.
-          if (prevInstWasReturn) AddBranch(returnLabelId, &new_blk_ptr);
-          if (earlyReturn) {
-            // If we generated a loop header for the single-trip loop
-            // to accommodate early returns, insert the continue
-            // target block now, with a false branch back to the loop header.
-            new_blocks->push_back(std::move(new_blk_ptr));
-            new_blk_ptr =
-                MakeUnique<BasicBlock>(NewLabel(singleTripLoopContinueId));
-            AddBranchCond(GetFalseId(), singleTripLoopHeaderId, returnLabelId,
-                          &new_blk_ptr);
-          }
-          // Generate the return block.
-          new_blocks->push_back(std::move(new_blk_ptr));
-          new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(returnLabelId));
-          multiBlocks = true;
-        }
-        // Load return value into result id of call, if it exists.
-        if (returnVarId != 0) {
-          const uint32_t resId = call_inst_itr->result_id();
-          assert(resId != 0);
-          AddLoad(calleeTypeId, resId, returnVarId, &new_blk_ptr);
-        }
-        // Copy remaining instructions from caller block.
-        for (Instruction* inst = call_inst_itr->NextNode(); inst;
-             inst = call_inst_itr->NextNode()) {
-          inst->RemoveFromList();
-          std::unique_ptr<Instruction> cp_inst(inst);
-          // If multiple blocks generated, regenerate any same-block
-          // instruction that has not been seen in this last block.
-          if (multiBlocks) {
-            CloneSameBlockOps(&cp_inst, &postCallSB, &preCallSB, &new_blk_ptr);
-            // Remember same-block ops in this block.
-            if (IsSameBlockOp(&*cp_inst)) {
-              const uint32_t rid = cp_inst->result_id();
-              postCallSB[rid] = rid;
-            }
-          }
-          new_blk_ptr->AddInstruction(std::move(cp_inst));
-        }
-        // Finalize inline code.
-        new_blocks->push_back(std::move(new_blk_ptr));
-      } break;
-      default: {
-        // Copy callee instruction and remap all input Ids.
-        std::unique_ptr<Instruction> cp_inst(cpi->Clone(context()));
-        cp_inst->ForEachInId([&callee2caller, &callee_result_ids,
-                              this](uint32_t* iid) {
-          const auto mapItr = callee2caller.find(*iid);
-          if (mapItr != callee2caller.end()) {
-            *iid = mapItr->second;
-          } else if (callee_result_ids.find(*iid) != callee_result_ids.end()) {
-            // Forward reference. Allocate a new id, map it,
-            // use it and check for it when remapping result ids
-            const uint32_t nid = this->TakeNextId();
-            callee2caller[*iid] = nid;
-            *iid = nid;
-          }
-        });
-        // If result id is non-zero, remap it. If already mapped, use mapped
-        // value, else use next id.
-        const uint32_t rid = cp_inst->result_id();
-        if (rid != 0) {
-          const auto mapItr = callee2caller.find(rid);
-          uint32_t nid;
-          if (mapItr != callee2caller.end()) {
-            nid = mapItr->second;
-          } else {
-            nid = this->TakeNextId();
-            callee2caller[rid] = nid;
-          }
-          cp_inst->SetResultId(nid);
-          get_decoration_mgr()->CloneDecorations(rid, nid);
-        }
-        new_blk_ptr->AddInstruction(std::move(cp_inst));
-      } break;
-    }
-  });
+  if (!successful) {
+    return false;
+  }
 
   if (caller_is_loop_header && (new_blocks->size() > 1)) {
     // Move the OpLoopMerge from the last block back to the first, where
@@ -532,6 +626,7 @@
   for (auto& blk : *new_blocks) {
     id2block_[blk->id()] = &*blk;
   }
+  return true;
 }
 
 bool InlinePass::IsInlinableFunctionCall(const Instruction* inst) {
diff --git a/source/opt/inline_pass.h b/source/opt/inline_pass.h
index e23e7f0..ecfe964 100644
--- a/source/opt/inline_pass.h
+++ b/source/opt/inline_pass.h
@@ -41,7 +41,8 @@
  protected:
   InlinePass();
 
-  // Add pointer to type to module and return resultId.
+  // Add pointer to type to module and return resultId.  Returns 0 if the type
+  // could not be created.
   uint32_t AddPointerToType(uint32_t type_id, SpvStorageClass storage_class);
 
   // Add unconditional branch to labelId to end of block block_ptr.
@@ -67,20 +68,22 @@
   std::unique_ptr<Instruction> NewLabel(uint32_t label_id);
 
   // Returns the id for the boolean false value. Looks in the module first
-  // and creates it if not found. Remembers it for future calls.
+  // and creates it if not found. Remembers it for future calls.  Returns 0 if
+  // the value could not be created.
   uint32_t GetFalseId();
 
   // Map callee params to caller args
   void MapParams(Function* calleeFn, BasicBlock::iterator call_inst_itr,
                  std::unordered_map<uint32_t, uint32_t>* callee2caller);
 
-  // Clone and map callee locals
-  void CloneAndMapLocals(Function* calleeFn,
+  // Clone and map callee locals.  Return true if successful.
+  bool CloneAndMapLocals(Function* calleeFn,
                          std::vector<std::unique_ptr<Instruction>>* new_vars,
                          std::unordered_map<uint32_t, uint32_t>* callee2caller);
 
-  // Create return variable for callee clone code if needed. Return id
-  // if created, otherwise 0.
+  // Create return variable for callee clone code.  The return type of
+  // |calleeFn| must not be void.  Returns  the id of the return variable if
+  // created.  Returns 0 if the return variable could not be created.
   uint32_t CreateReturnVar(Function* calleeFn,
                            std::vector<std::unique_ptr<Instruction>>* new_vars);
 
@@ -92,7 +95,7 @@
   // Look in preCallSB for instructions that need cloning. Look in
   // postCallSB for instructions already cloned. Add cloned instruction
   // to postCallSB.
-  void CloneSameBlockOps(std::unique_ptr<Instruction>* inst,
+  bool CloneSameBlockOps(std::unique_ptr<Instruction>* inst,
                          std::unordered_map<uint32_t, uint32_t>* postCallSB,
                          std::unordered_map<uint32_t, Instruction*>* preCallSB,
                          std::unique_ptr<BasicBlock>* block_ptr);
@@ -111,7 +114,9 @@
   // Also return in new_vars additional OpVariable instructions required by
   // and to be inserted into the caller function after the block at
   // call_block_itr is replaced with new_blocks.
-  void GenInlineCode(std::vector<std::unique_ptr<BasicBlock>>* new_blocks,
+  //
+  // Returns true if successful.
+  bool GenInlineCode(std::vector<std::unique_ptr<BasicBlock>>* new_blocks,
                      std::vector<std::unique_ptr<Instruction>>* new_vars,
                      BasicBlock::iterator call_inst_itr,
                      UptrVectorIterator<BasicBlock> call_block_itr);
diff --git a/source/opt/inst_bindless_check_pass.cpp b/source/opt/inst_bindless_check_pass.cpp
index 1901f76..47c8134 100644
--- a/source/opt/inst_bindless_check_pass.cpp
+++ b/source/opt/inst_bindless_check_pass.cpp
@@ -35,14 +35,79 @@
 namespace spvtools {
 namespace opt {
 
-void InstBindlessCheckPass::GenBindlessCheckCode(
-    BasicBlock::iterator ref_inst_itr,
-    UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t instruction_idx,
-    uint32_t stage_idx, std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
-  // Look for reference through bindless descriptor. If not, return.
-  std::unique_ptr<BasicBlock> new_blk_ptr;
-  uint32_t image_id;
-  switch (ref_inst_itr->opcode()) {
+uint32_t InstBindlessCheckPass::GenDebugReadLength(
+    uint32_t var_id, InstructionBuilder* builder) {
+  uint32_t desc_set_idx =
+      var2desc_set_[var_id] + kDebugInputBindlessOffsetLengths;
+  uint32_t desc_set_idx_id = builder->GetUintConstantId(desc_set_idx);
+  uint32_t binding_idx_id = builder->GetUintConstantId(var2binding_[var_id]);
+  return GenDebugDirectRead({desc_set_idx_id, binding_idx_id}, builder);
+}
+
+uint32_t InstBindlessCheckPass::GenDebugReadInit(uint32_t var_id,
+                                                 uint32_t desc_idx_id,
+                                                 InstructionBuilder* builder) {
+  uint32_t desc_set_base_id =
+      builder->GetUintConstantId(kDebugInputBindlessInitOffset);
+  uint32_t desc_set_idx_id = builder->GetUintConstantId(var2desc_set_[var_id]);
+  uint32_t binding_idx_id = builder->GetUintConstantId(var2binding_[var_id]);
+  uint32_t u_desc_idx_id = GenUintCastCode(desc_idx_id, builder);
+  return GenDebugDirectRead(
+      {desc_set_base_id, desc_set_idx_id, binding_idx_id, u_desc_idx_id},
+      builder);
+}
+
+uint32_t InstBindlessCheckPass::CloneOriginalReference(
+    ref_analysis* ref, InstructionBuilder* builder) {
+  // Clone descriptor load
+  Instruction* load_inst = get_def_use_mgr()->GetDef(ref->load_id);
+  Instruction* new_load_inst =
+      builder->AddLoad(load_inst->type_id(),
+                       load_inst->GetSingleWordInOperand(kSpvLoadPtrIdInIdx));
+  uid2offset_[new_load_inst->unique_id()] = uid2offset_[load_inst->unique_id()];
+  uint32_t new_load_id = new_load_inst->result_id();
+  get_decoration_mgr()->CloneDecorations(load_inst->result_id(), new_load_id);
+  uint32_t new_image_id = new_load_id;
+  // Clone Image/SampledImage with new load, if needed
+  if (ref->image_id != 0) {
+    Instruction* image_inst = get_def_use_mgr()->GetDef(ref->image_id);
+    if (image_inst->opcode() == SpvOp::SpvOpSampledImage) {
+      Instruction* new_image_inst = builder->AddBinaryOp(
+          image_inst->type_id(), SpvOpSampledImage, new_load_id,
+          image_inst->GetSingleWordInOperand(kSpvSampledImageSamplerIdInIdx));
+      uid2offset_[new_image_inst->unique_id()] =
+          uid2offset_[image_inst->unique_id()];
+      new_image_id = new_image_inst->result_id();
+    } else {
+      assert(image_inst->opcode() == SpvOp::SpvOpImage && "expecting OpImage");
+      Instruction* new_image_inst =
+          builder->AddUnaryOp(image_inst->type_id(), SpvOpImage, new_load_id);
+      uid2offset_[new_image_inst->unique_id()] =
+          uid2offset_[image_inst->unique_id()];
+      new_image_id = new_image_inst->result_id();
+    }
+    get_decoration_mgr()->CloneDecorations(ref->image_id, new_image_id);
+  }
+  // Clone original reference using new image code
+  std::unique_ptr<Instruction> new_ref_inst(ref->ref_inst->Clone(context()));
+  uint32_t ref_result_id = ref->ref_inst->result_id();
+  uint32_t new_ref_id = 0;
+  if (ref_result_id != 0) {
+    new_ref_id = TakeNextId();
+    new_ref_inst->SetResultId(new_ref_id);
+  }
+  new_ref_inst->SetInOperand(kSpvImageSampleImageIdInIdx, {new_image_id});
+  // Register new reference and add to new block
+  Instruction* added_inst = builder->AddInstruction(std::move(new_ref_inst));
+  uid2offset_[added_inst->unique_id()] =
+      uid2offset_[ref->ref_inst->unique_id()];
+  if (new_ref_id != 0)
+    get_decoration_mgr()->CloneDecorations(ref_result_id, new_ref_id);
+  return new_ref_id;
+}
+
+uint32_t InstBindlessCheckPass::GetDescriptorValueId(Instruction* inst) {
+  switch (inst->opcode()) {
     case SpvOp::SpvOpImageSampleImplicitLod:
     case SpvOp::SpvOpImageSampleExplicitLod:
     case SpvOp::SpvOpImageSampleDrefImplicitLod:
@@ -75,152 +140,205 @@
     case SpvOp::SpvOpImageSparseFetch:
     case SpvOp::SpvOpImageSparseRead:
     case SpvOp::SpvOpImageWrite:
-      image_id =
-          ref_inst_itr->GetSingleWordInOperand(kSpvImageSampleImageIdInIdx);
-      break;
+      return inst->GetSingleWordInOperand(kSpvImageSampleImageIdInIdx);
     default:
-      return;
+      break;
   }
-  Instruction* image_inst = get_def_use_mgr()->GetDef(image_id);
-  uint32_t load_id;
-  Instruction* load_inst;
+  return 0;
+}
+
+bool InstBindlessCheckPass::AnalyzeDescriptorReference(Instruction* ref_inst,
+                                                       ref_analysis* ref) {
+  ref->image_id = GetDescriptorValueId(ref_inst);
+  if (ref->image_id == 0) return false;
+  Instruction* image_inst = get_def_use_mgr()->GetDef(ref->image_id);
   if (image_inst->opcode() == SpvOp::SpvOpSampledImage) {
-    load_id = image_inst->GetSingleWordInOperand(kSpvSampledImageImageIdInIdx);
-    load_inst = get_def_use_mgr()->GetDef(load_id);
+    ref->load_id =
+        image_inst->GetSingleWordInOperand(kSpvSampledImageImageIdInIdx);
   } else if (image_inst->opcode() == SpvOp::SpvOpImage) {
-    load_id = image_inst->GetSingleWordInOperand(kSpvImageSampledImageIdInIdx);
-    load_inst = get_def_use_mgr()->GetDef(load_id);
+    ref->load_id =
+        image_inst->GetSingleWordInOperand(kSpvImageSampledImageIdInIdx);
   } else {
-    load_id = image_id;
-    load_inst = image_inst;
-    image_id = 0;
+    ref->load_id = ref->image_id;
+    ref->image_id = 0;
   }
+  Instruction* load_inst = get_def_use_mgr()->GetDef(ref->load_id);
   if (load_inst->opcode() != SpvOp::SpvOpLoad) {
-    // TODO(greg-lunarg): Handle additional possibilities
-    return;
+    // TODO(greg-lunarg): Handle additional possibilities?
+    return false;
   }
-  uint32_t ptr_id = load_inst->GetSingleWordInOperand(kSpvLoadPtrIdInIdx);
-  Instruction* ptr_inst = get_def_use_mgr()->GetDef(ptr_id);
-  if (ptr_inst->opcode() != SpvOp::SpvOpAccessChain) return;
-  if (ptr_inst->NumInOperands() != 2) {
-    assert(false && "unexpected bindless index number");
-    return;
+  ref->ptr_id = load_inst->GetSingleWordInOperand(kSpvLoadPtrIdInIdx);
+  Instruction* ptr_inst = get_def_use_mgr()->GetDef(ref->ptr_id);
+  if (ptr_inst->opcode() == SpvOp::SpvOpVariable) {
+    ref->index_id = 0;
+    ref->var_id = ref->ptr_id;
+  } else if (ptr_inst->opcode() == SpvOp::SpvOpAccessChain) {
+    if (ptr_inst->NumInOperands() != 2) {
+      assert(false && "unexpected bindless index number");
+      return false;
+    }
+    ref->index_id =
+        ptr_inst->GetSingleWordInOperand(kSpvAccessChainIndex0IdInIdx);
+    ref->var_id = ptr_inst->GetSingleWordInOperand(kSpvAccessChainBaseIdInIdx);
+    Instruction* var_inst = get_def_use_mgr()->GetDef(ref->var_id);
+    if (var_inst->opcode() != SpvOpVariable) {
+      assert(false && "unexpected bindless base");
+      return false;
+    }
+  } else {
+    // TODO(greg-lunarg): Handle additional possibilities?
+    return false;
   }
-  uint32_t index_id =
-      ptr_inst->GetSingleWordInOperand(kSpvAccessChainIndex0IdInIdx);
-  ptr_id = ptr_inst->GetSingleWordInOperand(kSpvAccessChainBaseIdInIdx);
-  ptr_inst = get_def_use_mgr()->GetDef(ptr_id);
-  if (ptr_inst->opcode() != SpvOpVariable) {
-    assert(false && "unexpected bindless base");
-    return;
-  }
-  uint32_t var_type_id = ptr_inst->type_id();
-  Instruction* var_type_inst = get_def_use_mgr()->GetDef(var_type_id);
-  uint32_t ptr_type_id =
-      var_type_inst->GetSingleWordInOperand(kSpvTypePointerTypeIdInIdx);
-  Instruction* ptr_type_inst = get_def_use_mgr()->GetDef(ptr_type_id);
-  // TODO(greg-lunarg): Handle RuntimeArray. Will need to pull length
-  // out of debug input buffer.
-  if (ptr_type_inst->opcode() != SpvOpTypeArray) return;
-  // If index and bound both compile-time constants and index < bound,
-  // return without changing
-  uint32_t length_id =
-      ptr_type_inst->GetSingleWordInOperand(kSpvTypeArrayLengthIdInIdx);
-  Instruction* index_inst = get_def_use_mgr()->GetDef(index_id);
-  Instruction* length_inst = get_def_use_mgr()->GetDef(length_id);
-  if (index_inst->opcode() == SpvOpConstant &&
-      length_inst->opcode() == SpvOpConstant &&
-      index_inst->GetSingleWordInOperand(kSpvConstantValueInIdx) <
-          length_inst->GetSingleWordInOperand(kSpvConstantValueInIdx))
-    return;
-  // Generate full runtime bounds test code with true branch
-  // being full reference and false branch being debug output and zero
-  // for the referenced value.
-  MovePreludeCode(ref_inst_itr, ref_block_itr, &new_blk_ptr);
+  ref->ref_inst = ref_inst;
+  return true;
+}
+
+void InstBindlessCheckPass::GenCheckCode(
+    uint32_t check_id, uint32_t error_id, uint32_t length_id,
+    uint32_t stage_idx, ref_analysis* ref,
+    std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
+  BasicBlock* back_blk_ptr = &*new_blocks->back();
   InstructionBuilder builder(
-      context(), &*new_blk_ptr,
+      context(), back_blk_ptr,
       IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
-  uint32_t error_id = builder.GetUintConstantId(kInstErrorBindlessBounds);
-  Instruction* ult_inst =
-      builder.AddBinaryOp(GetBoolId(), SpvOpULessThan, index_id, length_id);
+  // Gen conditional branch on check_id. Valid branch generates original
+  // reference. Invalid generates debug output and zero result (if needed).
   uint32_t merge_blk_id = TakeNextId();
   uint32_t valid_blk_id = TakeNextId();
   uint32_t invalid_blk_id = TakeNextId();
   std::unique_ptr<Instruction> merge_label(NewLabel(merge_blk_id));
   std::unique_ptr<Instruction> valid_label(NewLabel(valid_blk_id));
   std::unique_ptr<Instruction> invalid_label(NewLabel(invalid_blk_id));
-  (void)builder.AddConditionalBranch(ult_inst->result_id(), valid_blk_id,
-                                     invalid_blk_id, merge_blk_id,
-                                     SpvSelectionControlMaskNone);
-  // Close selection block and gen valid reference block
-  new_blocks->push_back(std::move(new_blk_ptr));
-  new_blk_ptr.reset(new BasicBlock(std::move(valid_label)));
+  (void)builder.AddConditionalBranch(check_id, valid_blk_id, invalid_blk_id,
+                                     merge_blk_id, SpvSelectionControlMaskNone);
+  // Gen valid bounds branch
+  std::unique_ptr<BasicBlock> new_blk_ptr(
+      new BasicBlock(std::move(valid_label)));
   builder.SetInsertPoint(&*new_blk_ptr);
-  // Clone descriptor load
-  Instruction* new_load_inst =
-      builder.AddLoad(load_inst->type_id(),
-                      load_inst->GetSingleWordInOperand(kSpvLoadPtrIdInIdx));
-  uint32_t new_load_id = new_load_inst->result_id();
-  get_decoration_mgr()->CloneDecorations(load_inst->result_id(), new_load_id);
-  uint32_t new_image_id = new_load_id;
-  // Clone Image/SampledImage with new load, if needed
-  if (image_id != 0) {
-    if (image_inst->opcode() == SpvOp::SpvOpSampledImage) {
-      Instruction* new_image_inst = builder.AddBinaryOp(
-          image_inst->type_id(), SpvOpSampledImage, new_load_id,
-          image_inst->GetSingleWordInOperand(kSpvSampledImageSamplerIdInIdx));
-      new_image_id = new_image_inst->result_id();
-    } else {
-      assert(image_inst->opcode() == SpvOp::SpvOpImage && "expecting OpImage");
-      Instruction* new_image_inst =
-          builder.AddUnaryOp(image_inst->type_id(), SpvOpImage, new_load_id);
-      new_image_id = new_image_inst->result_id();
-    }
-    get_decoration_mgr()->CloneDecorations(image_id, new_image_id);
-  }
-  // Clone original reference using new image code
-  std::unique_ptr<Instruction> new_ref_inst(ref_inst_itr->Clone(context()));
-  uint32_t ref_result_id = ref_inst_itr->result_id();
-  uint32_t new_ref_id = 0;
-  if (ref_result_id != 0) {
-    new_ref_id = TakeNextId();
-    new_ref_inst->SetResultId(new_ref_id);
-  }
-  new_ref_inst->SetInOperand(kSpvImageSampleImageIdInIdx, {new_image_id});
-  // Register new reference and add to new block
-  builder.AddInstruction(std::move(new_ref_inst));
-  if (new_ref_id != 0)
-    get_decoration_mgr()->CloneDecorations(ref_result_id, new_ref_id);
-  // Close valid block and gen invalid block
+  uint32_t new_ref_id = CloneOriginalReference(ref, &builder);
   (void)builder.AddBranch(merge_blk_id);
   new_blocks->push_back(std::move(new_blk_ptr));
+  // Gen invalid block
   new_blk_ptr.reset(new BasicBlock(std::move(invalid_label)));
   builder.SetInsertPoint(&*new_blk_ptr);
-  uint32_t u_index_id = GenUintCastCode(index_id, &builder);
-  GenDebugStreamWrite(instruction_idx, stage_idx,
+  uint32_t u_index_id = GenUintCastCode(ref->index_id, &builder);
+  GenDebugStreamWrite(uid2offset_[ref->ref_inst->unique_id()], stage_idx,
                       {error_id, u_index_id, length_id}, &builder);
   // Remember last invalid block id
   uint32_t last_invalid_blk_id = new_blk_ptr->GetLabelInst()->result_id();
   // Gen zero for invalid  reference
-  uint32_t ref_type_id = ref_inst_itr->type_id();
-  // Close invalid block and gen merge block
+  uint32_t ref_type_id = ref->ref_inst->type_id();
   (void)builder.AddBranch(merge_blk_id);
   new_blocks->push_back(std::move(new_blk_ptr));
+  // Gen merge block
   new_blk_ptr.reset(new BasicBlock(std::move(merge_label)));
   builder.SetInsertPoint(&*new_blk_ptr);
   // Gen phi of new reference and zero, if necessary, and replace the
   // result id of the original reference with that of the Phi. Kill original
-  // reference and move in remainder of original block.
+  // reference.
   if (new_ref_id != 0) {
     Instruction* phi_inst = builder.AddPhi(
         ref_type_id, {new_ref_id, valid_blk_id, builder.GetNullId(ref_type_id),
                       last_invalid_blk_id});
-    context()->ReplaceAllUsesWith(ref_result_id, phi_inst->result_id());
+    context()->ReplaceAllUsesWith(ref->ref_inst->result_id(),
+                                  phi_inst->result_id());
   }
-  context()->KillInst(&*ref_inst_itr);
-  MovePostludeCode(ref_block_itr, &new_blk_ptr);
-  // Add remainder/merge block to new blocks
   new_blocks->push_back(std::move(new_blk_ptr));
+  context()->KillInst(ref->ref_inst);
+}
+
+void InstBindlessCheckPass::GenBoundsCheckCode(
+    BasicBlock::iterator ref_inst_itr,
+    UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
+    std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
+  // Look for reference through indexed descriptor. If found, analyze and
+  // save components. If not, return.
+  ref_analysis ref;
+  if (!AnalyzeDescriptorReference(&*ref_inst_itr, &ref)) return;
+  Instruction* ptr_inst = get_def_use_mgr()->GetDef(ref.ptr_id);
+  if (ptr_inst->opcode() != SpvOp::SpvOpAccessChain) return;
+  // If index and bound both compile-time constants and index < bound,
+  // return without changing
+  Instruction* var_inst = get_def_use_mgr()->GetDef(ref.var_id);
+  uint32_t var_type_id = var_inst->type_id();
+  Instruction* var_type_inst = get_def_use_mgr()->GetDef(var_type_id);
+  uint32_t desc_type_id =
+      var_type_inst->GetSingleWordInOperand(kSpvTypePointerTypeIdInIdx);
+  Instruction* desc_type_inst = get_def_use_mgr()->GetDef(desc_type_id);
+  uint32_t length_id = 0;
+  if (desc_type_inst->opcode() == SpvOpTypeArray) {
+    length_id =
+        desc_type_inst->GetSingleWordInOperand(kSpvTypeArrayLengthIdInIdx);
+    Instruction* index_inst = get_def_use_mgr()->GetDef(ref.index_id);
+    Instruction* length_inst = get_def_use_mgr()->GetDef(length_id);
+    if (index_inst->opcode() == SpvOpConstant &&
+        length_inst->opcode() == SpvOpConstant &&
+        index_inst->GetSingleWordInOperand(kSpvConstantValueInIdx) <
+            length_inst->GetSingleWordInOperand(kSpvConstantValueInIdx))
+      return;
+  } else if (!input_length_enabled_ ||
+             desc_type_inst->opcode() != SpvOpTypeRuntimeArray) {
+    return;
+  }
+  // Move original block's preceding instructions into first new block
+  std::unique_ptr<BasicBlock> new_blk_ptr;
+  MovePreludeCode(ref_inst_itr, ref_block_itr, &new_blk_ptr);
+  InstructionBuilder builder(
+      context(), &*new_blk_ptr,
+      IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
+  new_blocks->push_back(std::move(new_blk_ptr));
+  uint32_t error_id = builder.GetUintConstantId(kInstErrorBindlessBounds);
+  // If length id not yet set, descriptor array is runtime size so
+  // generate load of length from stage's debug input buffer.
+  if (length_id == 0) {
+    assert(desc_type_inst->opcode() == SpvOpTypeRuntimeArray &&
+           "unexpected bindless type");
+    length_id = GenDebugReadLength(ref.var_id, &builder);
+  }
+  // Generate full runtime bounds test code with true branch
+  // being full reference and false branch being debug output and zero
+  // for the referenced value.
+  Instruction* ult_inst =
+      builder.AddBinaryOp(GetBoolId(), SpvOpULessThan, ref.index_id, length_id);
+  GenCheckCode(ult_inst->result_id(), error_id, length_id, stage_idx, &ref,
+               new_blocks);
+  // Move original block's remaining code into remainder/merge block and add
+  // to new blocks
+  BasicBlock* back_blk_ptr = &*new_blocks->back();
+  MovePostludeCode(ref_block_itr, back_blk_ptr);
+}
+
+void InstBindlessCheckPass::GenInitCheckCode(
+    BasicBlock::iterator ref_inst_itr,
+    UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
+    std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
+  // Look for reference through descriptor. If not, return.
+  ref_analysis ref;
+  if (!AnalyzeDescriptorReference(&*ref_inst_itr, &ref)) return;
+  // Move original block's preceding instructions into first new block
+  std::unique_ptr<BasicBlock> new_blk_ptr;
+  MovePreludeCode(ref_inst_itr, ref_block_itr, &new_blk_ptr);
+  InstructionBuilder builder(
+      context(), &*new_blk_ptr,
+      IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
+  new_blocks->push_back(std::move(new_blk_ptr));
+  // Read initialization status from debug input buffer. If index id not yet
+  // set, binding is single descriptor, so set index to constant 0.
+  uint32_t zero_id = builder.GetUintConstantId(0u);
+  if (ref.index_id == 0) ref.index_id = zero_id;
+  uint32_t init_id = GenDebugReadInit(ref.var_id, ref.index_id, &builder);
+  // Generate full runtime non-zero init test code with true branch
+  // being full reference and false branch being debug output and zero
+  // for the referenced value.
+  Instruction* uneq_inst =
+      builder.AddBinaryOp(GetBoolId(), SpvOpINotEqual, init_id, zero_id);
+  uint32_t error_id = builder.GetUintConstantId(kInstErrorBindlessUninit);
+  GenCheckCode(uneq_inst->result_id(), error_id, zero_id, stage_idx, &ref,
+               new_blocks);
+  // Move original block's remaining code into remainder/merge block and add
+  // to new blocks
+  BasicBlock* back_blk_ptr = &*new_blocks->back();
+  MovePostludeCode(ref_block_itr, back_blk_ptr);
 }
 
 void InstBindlessCheckPass::InitializeInstBindlessCheck() {
@@ -236,21 +354,43 @@
       break;
     }
   }
+  // If descriptor indexing extension and runtime array length support enabled,
+  // create variable mappings. Length support is always enabled if descriptor
+  // init check is enabled.
+  if (ext_descriptor_indexing_defined_ && input_length_enabled_)
+    for (auto& anno : get_module()->annotations())
+      if (anno.opcode() == SpvOpDecorate) {
+        if (anno.GetSingleWordInOperand(1u) == SpvDecorationDescriptorSet)
+          var2desc_set_[anno.GetSingleWordInOperand(0u)] =
+              anno.GetSingleWordInOperand(2u);
+        else if (anno.GetSingleWordInOperand(1u) == SpvDecorationBinding)
+          var2binding_[anno.GetSingleWordInOperand(0u)] =
+              anno.GetSingleWordInOperand(2u);
+      }
 }
 
 Pass::Status InstBindlessCheckPass::ProcessImpl() {
-  // Perform instrumentation on each entry point function in module
+  // Perform bindless bounds check on each entry point function in module
   InstProcessFunction pfn =
       [this](BasicBlock::iterator ref_inst_itr,
-             UptrVectorIterator<BasicBlock> ref_block_itr,
-             uint32_t instruction_idx, uint32_t stage_idx,
+             UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
              std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
-        return GenBindlessCheckCode(ref_inst_itr, ref_block_itr,
-                                    instruction_idx, stage_idx, new_blocks);
+        return GenBoundsCheckCode(ref_inst_itr, ref_block_itr, stage_idx,
+                                  new_blocks);
       };
   bool modified = InstProcessEntryPointCallTree(pfn);
-  // This pass does not update inst->blk info
-  context()->InvalidateAnalyses(IRContext::kAnalysisInstrToBlockMapping);
+  if (ext_descriptor_indexing_defined_ && input_init_enabled_) {
+    // Perform descriptor initialization check on each entry point function in
+    // module
+    pfn = [this](BasicBlock::iterator ref_inst_itr,
+                 UptrVectorIterator<BasicBlock> ref_block_itr,
+                 uint32_t stage_idx,
+                 std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
+      return GenInitCheckCode(ref_inst_itr, ref_block_itr, stage_idx,
+                              new_blocks);
+    };
+    modified |= InstProcessEntryPointCallTree(pfn);
+  }
   return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
 }
 
diff --git a/source/opt/inst_bindless_check_pass.h b/source/opt/inst_bindless_check_pass.h
index 3ab5ab7..79c34f1 100644
--- a/source/opt/inst_bindless_check_pass.h
+++ b/source/opt/inst_bindless_check_pass.h
@@ -29,10 +29,16 @@
 class InstBindlessCheckPass : public InstrumentPass {
  public:
   // For test harness only
-  InstBindlessCheckPass() : InstrumentPass(7, 23, kInstValidationIdBindless) {}
+  InstBindlessCheckPass()
+      : InstrumentPass(7, 23, kInstValidationIdBindless),
+        input_length_enabled_(true),
+        input_init_enabled_(true) {}
   // For all other interfaces
-  InstBindlessCheckPass(uint32_t desc_set, uint32_t shader_id)
-      : InstrumentPass(desc_set, shader_id, kInstValidationIdBindless) {}
+  InstBindlessCheckPass(uint32_t desc_set, uint32_t shader_id,
+                        bool input_length_enable, bool input_init_enable)
+      : InstrumentPass(desc_set, shader_id, kInstValidationIdBindless),
+        input_length_enabled_(input_length_enable),
+        input_init_enabled_(input_init_enable) {}
 
   ~InstBindlessCheckPass() override = default;
 
@@ -42,28 +48,40 @@
   const char* name() const override { return "inst-bindless-check-pass"; }
 
  private:
-  // Initialize state for instrumenting bindless checking
-  void InitializeInstBindlessCheck();
-
-  // This function does bindless checking instrumentation on a single
-  // instruction. It is designed to be passed to
+  // These functions do bindless checking instrumentation on a single
+  // instruction which references through a descriptor (ie references into an
+  // image or buffer). Refer to Vulkan API for further information on
+  // descriptors. GenBoundsCheckCode checks that an index into a descriptor
+  // array (array of images or buffers) is in-bounds. GenInitCheckCode
+  // checks that the referenced descriptor has been initialized, if the
+  // SPV_EXT_descriptor_indexing extension is enabled.
+  //
+  // TODO(greg-lunarg): Add support for buffers. Currently only does
+  // checking of references of images.
+  //
+  // The functions are designed to be passed to
   // InstrumentPass::InstProcessEntryPointCallTree(), which applies the
   // function to each instruction in a module and replaces the instruction
   // if warranted.
   //
   // If |ref_inst_itr| is a bindless reference, return in |new_blocks| the
   // result of instrumenting it with validation code within its block at
-  // |ref_block_itr|. Specifically, generate code to check that the index
-  // into the descriptor array is in-bounds. If the check passes, execute
-  // the remainder of the reference, otherwise write a record to the debug
+  // |ref_block_itr|.  The validation code first executes a check for the
+  // specific condition called for. If the check passes, it executes
+  // the remainder of the reference, otherwise writes a record to the debug
   // output buffer stream including |function_idx, instruction_idx, stage_idx|
-  // and replace the reference with the null value of the original type. The
+  // and replaces the reference with the null value of the original type. The
   // block at |ref_block_itr| can just be replaced with the blocks in
   // |new_blocks|, which will contain at least two blocks. The last block will
   // comprise all instructions following |ref_inst_itr|,
   // preceded by a phi instruction.
   //
-  // This instrumentation pass utilizes GenDebugStreamWrite() to write its
+  // These instrumentation functions utilize GenDebugDirectRead() to read data
+  // from the debug input buffer, specifically the lengths of variable length
+  // descriptor arrays, and the initialization status of each descriptor.
+  // The format of the debug input buffer is documented in instrument.hpp.
+  //
+  // These instrumentation functions utilize GenDebugStreamWrite() to write its
   // error records. The validation-specific part of the error record will
   // have the format:
   //
@@ -76,15 +94,83 @@
   //
   // The Descriptor Array Size is the size of the descriptor array which was
   // indexed.
-  void GenBindlessCheckCode(
-      BasicBlock::iterator ref_inst_itr,
-      UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t instruction_idx,
-      uint32_t stage_idx, std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
+  void GenBoundsCheckCode(BasicBlock::iterator ref_inst_itr,
+                          UptrVectorIterator<BasicBlock> ref_block_itr,
+                          uint32_t stage_idx,
+                          std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
 
+  void GenInitCheckCode(BasicBlock::iterator ref_inst_itr,
+                        UptrVectorIterator<BasicBlock> ref_block_itr,
+                        uint32_t stage_idx,
+                        std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
+
+  // Generate instructions into |builder| to read length of runtime descriptor
+  // array |var_id| from debug input buffer and return id of value.
+  uint32_t GenDebugReadLength(uint32_t var_id, InstructionBuilder* builder);
+
+  // Generate instructions into |builder| to read initialization status of
+  // descriptor array |image_id| at |index_id| from debug input buffer and
+  // return id of value.
+  uint32_t GenDebugReadInit(uint32_t image_id, uint32_t index_id,
+                            InstructionBuilder* builder);
+
+  // Analysis data for descriptor reference components, generated by
+  // AnalyzeDescriptorReference. It is necessary and sufficient for further
+  // analysis and regeneration of the reference.
+  typedef struct ref_analysis {
+    uint32_t image_id;
+    uint32_t load_id;
+    uint32_t ptr_id;
+    uint32_t var_id;
+    uint32_t index_id;
+    Instruction* ref_inst;
+  } ref_analysis;
+
+  // Clone original original reference encapsulated by |ref| into |builder|.
+  // This may generate more than one instruction if neccessary.
+  uint32_t CloneOriginalReference(ref_analysis* ref,
+                                  InstructionBuilder* builder);
+
+  // If |inst| references through a descriptor, (ie references into an image
+  // or buffer), return the id of the value it references. Else return 0.
+  uint32_t GetDescriptorValueId(Instruction* inst);
+
+  // Analyze descriptor reference |ref_inst| and save components into |ref|.
+  // Return true if |ref_inst| is a descriptor reference, false otherwise.
+  bool AnalyzeDescriptorReference(Instruction* ref_inst, ref_analysis* ref);
+
+  // Generate instrumentation code for generic test result |check_id|, starting
+  // with |builder| of block |new_blk_ptr|, adding new blocks to |new_blocks|.
+  // Generate conditional branch to a valid or invalid branch. Generate valid
+  // block which does original reference |ref|. Generate invalid block which
+  // writes debug error output utilizing |ref|, |error_id|, |length_id| and
+  // |stage_idx|. Generate merge block for valid and invalid branches. Kill
+  // original reference.
+  void GenCheckCode(uint32_t check_id, uint32_t error_id, uint32_t length_id,
+                    uint32_t stage_idx, ref_analysis* ref,
+                    std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
+
+  // Initialize state for instrumenting bindless checking
+  void InitializeInstBindlessCheck();
+
+  // Apply GenBoundsCheckCode to every instruction in module. Then apply
+  // GenInitCheckCode to every instruction in module.
   Pass::Status ProcessImpl();
 
   // True if VK_EXT_descriptor_indexing is defined
   bool ext_descriptor_indexing_defined_;
+
+  // Enable instrumentation of runtime array length checking
+  bool input_length_enabled_;
+
+  // Enable instrumentation of descriptor initialization checking
+  bool input_init_enabled_;
+
+  // Mapping from variable to descriptor set
+  std::unordered_map<uint32_t, uint32_t> var2desc_set_;
+
+  // Mapping from variable to binding
+  std::unordered_map<uint32_t, uint32_t> var2binding_;
 };
 
 }  // namespace opt
diff --git a/source/opt/instruction.cpp b/source/opt/instruction.cpp
index 5f3c5a8..5a82e53 100644
--- a/source/opt/instruction.cpp
+++ b/source/opt/instruction.cpp
@@ -155,12 +155,6 @@
 }
 
 Instruction* Instruction::GetBaseAddress() const {
-  assert((IsLoad() || opcode() == SpvOpStore || opcode() == SpvOpAccessChain ||
-          opcode() == SpvOpPtrAccessChain ||
-          opcode() == SpvOpInBoundsAccessChain || opcode() == SpvOpCopyObject ||
-          opcode() == SpvOpImageTexelPointer) &&
-         "GetBaseAddress should only be called on instructions that take a "
-         "pointer or image.");
   uint32_t base = GetSingleWordInOperand(kLoadBaseIndex);
   Instruction* base_inst = context()->get_def_use_mgr()->GetDef(base);
   bool done = false;
@@ -182,24 +176,6 @@
         break;
     }
   }
-
-  switch (opcode()) {
-    case SpvOpLoad:
-    case SpvOpStore:
-    case SpvOpAccessChain:
-    case SpvOpInBoundsAccessChain:
-    case SpvOpPtrAccessChain:
-    case SpvOpImageTexelPointer:
-    case SpvOpCopyObject:
-      // A load or store through a pointer.
-      assert(base_inst->IsValidBasePointer() &&
-             "We cannot have a base pointer come from this load");
-      break;
-    default:
-      // A load or store of an image.
-      assert(base_inst->IsValidBaseImage() && "We are expecting an image.");
-      break;
-  }
   return base_inst;
 }
 
diff --git a/source/opt/instrument_pass.cpp b/source/opt/instrument_pass.cpp
index 7f56a1e..668c980 100644
--- a/source/opt/instrument_pass.cpp
+++ b/source/opt/instrument_pass.cpp
@@ -57,8 +57,7 @@
 }
 
 void InstrumentPass::MovePostludeCode(
-    UptrVectorIterator<BasicBlock> ref_block_itr,
-    std::unique_ptr<BasicBlock>* new_blk_ptr) {
+    UptrVectorIterator<BasicBlock> ref_block_itr, BasicBlock* new_blk_ptr) {
   // new_blk_ptr->reset(new BasicBlock(NewLabel(ref_block_itr->id())));
   // Move contents of original ref block.
   for (auto cii = ref_block_itr->begin(); cii != ref_block_itr->end();
@@ -77,7 +76,7 @@
         same_block_post_[rid] = rid;
       }
     }
-    (*new_blk_ptr)->AddInstruction(std::move(mv_inst));
+    new_blk_ptr->AddInstruction(std::move(mv_inst));
   }
 }
 
@@ -107,7 +106,7 @@
       builder->AddBinaryOp(GetUintId(), SpvOpIAdd, base_offset_id,
                            builder->GetUintConstantId(field_offset));
   uint32_t buf_id = GetOutputBufferId();
-  uint32_t buf_uint_ptr_id = GetOutputBufferUintPtrId();
+  uint32_t buf_uint_ptr_id = GetBufferUintPtrId();
   Instruction* achain_inst =
       builder->AddTernaryOp(buf_uint_ptr_id, SpvOpAccessChain, buf_id,
                             builder->GetUintConstantId(kDebugOutputDataOffset),
@@ -168,10 +167,10 @@
   switch (stage_idx) {
     case SpvExecutionModelVertex: {
       // Load and store VertexId and InstanceId
-      GenBuiltinOutputCode(context()->GetBuiltinVarId(SpvBuiltInVertexId),
-                           kInstVertOutVertexId, base_offset_id, builder);
-      GenBuiltinOutputCode(context()->GetBuiltinVarId(SpvBuiltInInstanceId),
-                           kInstVertOutInstanceId, base_offset_id, builder);
+      GenBuiltinOutputCode(context()->GetBuiltinVarId(SpvBuiltInVertexIndex),
+                           kInstVertOutVertexIndex, base_offset_id, builder);
+      GenBuiltinOutputCode(context()->GetBuiltinVarId(SpvBuiltInInstanceIndex),
+                           kInstVertOutInstanceIndex, base_offset_id, builder);
     } break;
     case SpvExecutionModelGLCompute: {
       // Load and store GlobalInvocationId. Second word is unused; store zero.
@@ -222,6 +221,16 @@
   (void)builder->AddNaryOp(GetVoidId(), SpvOpFunctionCall, args);
 }
 
+uint32_t InstrumentPass::GenDebugDirectRead(
+    const std::vector<uint32_t>& offset_ids, InstructionBuilder* builder) {
+  // Call debug input function. Pass func_idx and offset ids as args.
+  uint32_t off_id_cnt = static_cast<uint32_t>(offset_ids.size());
+  uint32_t input_func_id = GetDirectReadFunctionId(off_id_cnt);
+  std::vector<uint32_t> args = {input_func_id};
+  (void)args.insert(args.end(), offset_ids.begin(), offset_ids.end());
+  return builder->AddNaryOp(GetUintId(), SpvOpFunctionCall, args)->result_id();
+}
+
 bool InstrumentPass::IsSameBlockOp(const Instruction* inst) const {
   return inst->opcode() == SpvOpSampledImage || inst->opcode() == SpvOpImage;
 }
@@ -230,7 +239,7 @@
     std::unique_ptr<Instruction>* inst,
     std::unordered_map<uint32_t, uint32_t>* same_blk_post,
     std::unordered_map<uint32_t, Instruction*>* same_blk_pre,
-    std::unique_ptr<BasicBlock>* block_ptr) {
+    BasicBlock* block_ptr) {
   (*inst)->ForEachInId(
       [&same_blk_post, &same_blk_pre, &block_ptr, this](uint32_t* iid) {
         const auto map_itr = (*same_blk_post).find(*iid);
@@ -247,7 +256,7 @@
             sb_inst->SetResultId(nid);
             (*same_blk_post)[rid] = nid;
             *iid = nid;
-            (*block_ptr)->AddInstruction(std::move(sb_inst));
+            block_ptr->AddInstruction(std::move(sb_inst));
           }
         } else {
           // Reset same-block op operand.
@@ -280,12 +289,12 @@
 }
 
 // Return id for output buffer uint ptr type
-uint32_t InstrumentPass::GetOutputBufferUintPtrId() {
-  if (output_buffer_uint_ptr_id_ == 0) {
-    output_buffer_uint_ptr_id_ = context()->get_type_mgr()->FindPointerToType(
+uint32_t InstrumentPass::GetBufferUintPtrId() {
+  if (buffer_uint_ptr_id_ == 0) {
+    buffer_uint_ptr_id_ = context()->get_type_mgr()->FindPointerToType(
         GetUintId(), SpvStorageClassStorageBuffer);
   }
-  return output_buffer_uint_ptr_id_;
+  return buffer_uint_ptr_id_;
 }
 
 uint32_t InstrumentPass::GetOutputBufferBinding() {
@@ -298,20 +307,74 @@
   return 0;
 }
 
+uint32_t InstrumentPass::GetInputBufferBinding() {
+  switch (validation_id_) {
+    case kInstValidationIdBindless:
+      return kDebugInputBindingBindless;
+    default:
+      assert(false && "unexpected validation id");
+  }
+  return 0;
+}
+
+analysis::Type* InstrumentPass::GetUintRuntimeArrayType(
+    analysis::DecorationManager* deco_mgr, analysis::TypeManager* type_mgr) {
+  if (uint_rarr_ty_ == nullptr) {
+    analysis::Integer uint_ty(32, false);
+    analysis::Type* reg_uint_ty = type_mgr->GetRegisteredType(&uint_ty);
+    analysis::RuntimeArray uint_rarr_ty_tmp(reg_uint_ty);
+    uint_rarr_ty_ = type_mgr->GetRegisteredType(&uint_rarr_ty_tmp);
+    uint32_t uint_arr_ty_id = type_mgr->GetTypeInstruction(uint_rarr_ty_);
+    // By the Vulkan spec, a pre-existing RuntimeArray of uint must be part of
+    // a block, and will therefore be decorated with an ArrayStride. Therefore
+    // the undecorated type returned here will not be pre-existing and can
+    // safely be decorated. Since this type is now decorated, it is out of
+    // sync with the TypeManager and therefore the TypeManager must be
+    // invalidated after this pass.
+    assert(context()->get_def_use_mgr()->NumUses(uint_arr_ty_id) == 0 &&
+           "used RuntimeArray type returned");
+    deco_mgr->AddDecorationVal(uint_arr_ty_id, SpvDecorationArrayStride, 4u);
+  }
+  return uint_rarr_ty_;
+}
+
+void InstrumentPass::AddStorageBufferExt() {
+  if (storage_buffer_ext_defined_) return;
+  if (!get_feature_mgr()->HasExtension(kSPV_KHR_storage_buffer_storage_class)) {
+    const std::string ext_name("SPV_KHR_storage_buffer_storage_class");
+    const auto num_chars = ext_name.size();
+    // Compute num words, accommodate the terminating null character.
+    const auto num_words = (num_chars + 1 + 3) / 4;
+    std::vector<uint32_t> ext_words(num_words, 0u);
+    std::memcpy(ext_words.data(), ext_name.data(), num_chars);
+    context()->AddExtension(std::unique_ptr<Instruction>(
+        new Instruction(context(), SpvOpExtension, 0u, 0u,
+                        {{SPV_OPERAND_TYPE_LITERAL_STRING, ext_words}})));
+  }
+  storage_buffer_ext_defined_ = true;
+}
+
 // Return id for output buffer
 uint32_t InstrumentPass::GetOutputBufferId() {
   if (output_buffer_id_ == 0) {
     // If not created yet, create one
     analysis::DecorationManager* deco_mgr = get_decoration_mgr();
     analysis::TypeManager* type_mgr = context()->get_type_mgr();
+    analysis::Type* reg_uint_rarr_ty =
+        GetUintRuntimeArrayType(deco_mgr, type_mgr);
     analysis::Integer uint_ty(32, false);
     analysis::Type* reg_uint_ty = type_mgr->GetRegisteredType(&uint_ty);
-    analysis::RuntimeArray uint_rarr_ty(reg_uint_ty);
-    analysis::Type* reg_uint_rarr_ty =
-        type_mgr->GetRegisteredType(&uint_rarr_ty);
-    analysis::Struct obuf_ty({reg_uint_ty, reg_uint_rarr_ty});
-    analysis::Type* reg_obuf_ty = type_mgr->GetRegisteredType(&obuf_ty);
-    uint32_t obufTyId = type_mgr->GetTypeInstruction(reg_obuf_ty);
+    analysis::Struct buf_ty({reg_uint_ty, reg_uint_rarr_ty});
+    analysis::Type* reg_buf_ty = type_mgr->GetRegisteredType(&buf_ty);
+    uint32_t obufTyId = type_mgr->GetTypeInstruction(reg_buf_ty);
+    // By the Vulkan spec, a pre-existing struct containing a RuntimeArray
+    // must be a block, and will therefore be decorated with Block. Therefore
+    // the undecorated type returned here will not be pre-existing and can
+    // safely be decorated. Since this type is now decorated, it is out of
+    // sync with the TypeManager and therefore the TypeManager must be
+    // invalidated after this pass.
+    assert(context()->get_def_use_mgr()->NumUses(obufTyId) == 0 &&
+           "used struct type returned");
     deco_mgr->AddDecoration(obufTyId, SpvDecorationBlock);
     deco_mgr->AddMemberDecoration(obufTyId, kDebugOutputSizeOffset,
                                   SpvDecorationOffset, 0);
@@ -329,23 +392,48 @@
                                desc_set_);
     deco_mgr->AddDecorationVal(output_buffer_id_, SpvDecorationBinding,
                                GetOutputBufferBinding());
-    // Look for storage buffer extension. If none, create one.
-    if (!get_feature_mgr()->HasExtension(
-            kSPV_KHR_storage_buffer_storage_class)) {
-      const std::string ext_name("SPV_KHR_storage_buffer_storage_class");
-      const auto num_chars = ext_name.size();
-      // Compute num words, accommodate the terminating null character.
-      const auto num_words = (num_chars + 1 + 3) / 4;
-      std::vector<uint32_t> ext_words(num_words, 0u);
-      std::memcpy(ext_words.data(), ext_name.data(), num_chars);
-      context()->AddExtension(std::unique_ptr<Instruction>(
-          new Instruction(context(), SpvOpExtension, 0u, 0u,
-                          {{SPV_OPERAND_TYPE_LITERAL_STRING, ext_words}})));
-    }
+    AddStorageBufferExt();
   }
   return output_buffer_id_;
 }
 
+uint32_t InstrumentPass::GetInputBufferId() {
+  if (input_buffer_id_ == 0) {
+    // If not created yet, create one
+    analysis::DecorationManager* deco_mgr = get_decoration_mgr();
+    analysis::TypeManager* type_mgr = context()->get_type_mgr();
+    analysis::Type* reg_uint_rarr_ty =
+        GetUintRuntimeArrayType(deco_mgr, type_mgr);
+    analysis::Struct buf_ty({reg_uint_rarr_ty});
+    analysis::Type* reg_buf_ty = type_mgr->GetRegisteredType(&buf_ty);
+    uint32_t ibufTyId = type_mgr->GetTypeInstruction(reg_buf_ty);
+    // By the Vulkan spec, a pre-existing struct containing a RuntimeArray
+    // must be a block, and will therefore be decorated with Block. Therefore
+    // the undecorated type returned here will not be pre-existing and can
+    // safely be decorated. Since this type is now decorated, it is out of
+    // sync with the TypeManager and therefore the TypeManager must be
+    // invalidated after this pass.
+    assert(context()->get_def_use_mgr()->NumUses(ibufTyId) == 0 &&
+           "used struct type returned");
+    deco_mgr->AddDecoration(ibufTyId, SpvDecorationBlock);
+    deco_mgr->AddMemberDecoration(ibufTyId, 0, SpvDecorationOffset, 0);
+    uint32_t ibufTyPtrId_ =
+        type_mgr->FindPointerToType(ibufTyId, SpvStorageClassStorageBuffer);
+    input_buffer_id_ = TakeNextId();
+    std::unique_ptr<Instruction> newVarOp(new Instruction(
+        context(), SpvOpVariable, ibufTyPtrId_, input_buffer_id_,
+        {{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
+          {SpvStorageClassStorageBuffer}}}));
+    context()->AddGlobalValue(std::move(newVarOp));
+    deco_mgr->AddDecorationVal(input_buffer_id_, SpvDecorationDescriptorSet,
+                               desc_set_);
+    deco_mgr->AddDecorationVal(input_buffer_id_, SpvDecorationBinding,
+                               GetInputBufferBinding());
+    AddStorageBufferExt();
+  }
+  return input_buffer_id_;
+}
+
 uint32_t InstrumentPass::GetVec4FloatId() {
   if (v4float_id_ == 0) {
     analysis::TypeManager* type_mgr = context()->get_type_mgr();
@@ -445,7 +533,7 @@
     // Gen test if debug output buffer size will not be exceeded.
     uint32_t obuf_record_sz = kInstStageOutCnt + val_spec_param_cnt;
     uint32_t buf_id = GetOutputBufferId();
-    uint32_t buf_uint_ptr_id = GetOutputBufferUintPtrId();
+    uint32_t buf_uint_ptr_id = GetBufferUintPtrId();
     Instruction* obuf_curr_sz_ac_inst =
         builder.AddBinaryOp(buf_uint_ptr_id, SpvOpAccessChain, buf_id,
                             builder.GetUintConstantId(kDebugOutputSizeOffset));
@@ -513,6 +601,81 @@
   return output_func_id_;
 }
 
+uint32_t InstrumentPass::GetDirectReadFunctionId(uint32_t param_cnt) {
+  uint32_t func_id = param2input_func_id_[param_cnt];
+  if (func_id != 0) return func_id;
+  // Create input function for param_cnt
+  func_id = TakeNextId();
+  analysis::TypeManager* type_mgr = context()->get_type_mgr();
+  std::vector<const analysis::Type*> param_types;
+  for (uint32_t c = 0; c < param_cnt; ++c)
+    param_types.push_back(type_mgr->GetType(GetUintId()));
+  analysis::Function func_ty(type_mgr->GetType(GetUintId()), param_types);
+  analysis::Type* reg_func_ty = type_mgr->GetRegisteredType(&func_ty);
+  std::unique_ptr<Instruction> func_inst(new Instruction(
+      get_module()->context(), SpvOpFunction, GetUintId(), func_id,
+      {{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
+        {SpvFunctionControlMaskNone}},
+       {spv_operand_type_t::SPV_OPERAND_TYPE_ID,
+        {type_mgr->GetTypeInstruction(reg_func_ty)}}}));
+  get_def_use_mgr()->AnalyzeInstDefUse(&*func_inst);
+  std::unique_ptr<Function> input_func =
+      MakeUnique<Function>(std::move(func_inst));
+  // Add parameters
+  std::vector<uint32_t> param_vec;
+  for (uint32_t c = 0; c < param_cnt; ++c) {
+    uint32_t pid = TakeNextId();
+    param_vec.push_back(pid);
+    std::unique_ptr<Instruction> param_inst(new Instruction(
+        get_module()->context(), SpvOpFunctionParameter, GetUintId(), pid, {}));
+    get_def_use_mgr()->AnalyzeInstDefUse(&*param_inst);
+    input_func->AddParameter(std::move(param_inst));
+  }
+  // Create block
+  uint32_t blk_id = TakeNextId();
+  std::unique_ptr<Instruction> blk_label(NewLabel(blk_id));
+  std::unique_ptr<BasicBlock> new_blk_ptr =
+      MakeUnique<BasicBlock>(std::move(blk_label));
+  InstructionBuilder builder(
+      context(), &*new_blk_ptr,
+      IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
+  // For each offset parameter, generate new offset with parameter, adding last
+  // loaded value if it exists, and load value from input buffer at new offset.
+  // Return last loaded value.
+  uint32_t buf_id = GetInputBufferId();
+  uint32_t buf_uint_ptr_id = GetBufferUintPtrId();
+  uint32_t last_value_id = 0;
+  for (uint32_t p = 0; p < param_cnt; ++p) {
+    uint32_t offset_id;
+    if (p == 0) {
+      offset_id = param_vec[0];
+    } else {
+      Instruction* offset_inst = builder.AddBinaryOp(
+          GetUintId(), SpvOpIAdd, last_value_id, param_vec[p]);
+      offset_id = offset_inst->result_id();
+    }
+    Instruction* ac_inst = builder.AddTernaryOp(
+        buf_uint_ptr_id, SpvOpAccessChain, buf_id,
+        builder.GetUintConstantId(kDebugInputDataOffset), offset_id);
+    Instruction* load_inst =
+        builder.AddUnaryOp(GetUintId(), SpvOpLoad, ac_inst->result_id());
+    last_value_id = load_inst->result_id();
+  }
+  (void)builder.AddInstruction(MakeUnique<Instruction>(
+      context(), SpvOpReturnValue, 0, 0,
+      std::initializer_list<Operand>{{SPV_OPERAND_TYPE_ID, {last_value_id}}}));
+  // Close block and function and add function to module
+  new_blk_ptr->SetParent(&*input_func);
+  input_func->AddBasicBlock(std::move(new_blk_ptr));
+  std::unique_ptr<Instruction> func_end_inst(
+      new Instruction(get_module()->context(), SpvOpFunctionEnd, 0, 0, {}));
+  get_def_use_mgr()->AnalyzeInstDefUse(&*func_end_inst);
+  input_func->SetFunctionEnd(std::move(func_end_inst));
+  context()->AddFunction(std::move(input_func));
+  param2input_func_id_[param_cnt] = func_id;
+  return func_id;
+}
+
 bool InstrumentPass::InstrumentFunction(Function* func, uint32_t stage_idx,
                                         InstProcessFunction& pfn) {
   bool modified = false;
@@ -523,21 +686,17 @@
     ++function_idx;
   }
   std::vector<std::unique_ptr<BasicBlock>> new_blks;
-  // Start count after function instruction
-  uint32_t instruction_idx = funcIdx2offset_[function_idx] + 1;
   // Using block iterators here because of block erasures and insertions.
   for (auto bi = func->begin(); bi != func->end(); ++bi) {
-    // Count block's label
-    ++instruction_idx;
-    for (auto ii = bi->begin(); ii != bi->end(); ++instruction_idx) {
-      // Bump instruction count if debug instructions
-      instruction_idx += static_cast<uint32_t>(ii->dbg_line_insts().size());
+    for (auto ii = bi->begin(); ii != bi->end();) {
       // Generate instrumentation if warranted
-      pfn(ii, bi, instruction_idx, stage_idx, &new_blks);
+      pfn(ii, bi, stage_idx, &new_blks);
       if (new_blks.size() == 0) {
         ++ii;
         continue;
       }
+      // Add new blocks to label id map
+      for (auto& blk : new_blks) id2block_[blk->id()] = &*blk;
       // If there are new blocks we know there will always be two or
       // more, so update succeeding phis with label of new last block.
       size_t newBlocksSize = new_blks.size();
@@ -567,6 +726,9 @@
                                                   uint32_t stage_idx) {
   bool modified = false;
   std::unordered_set<uint32_t> done;
+  // Don't process input and output functions
+  for (auto& ifn : param2input_func_id_) done.insert(ifn.second);
+  if (output_func_id_ != 0) done.insert(output_func_id_);
   // Process all functions from roots
   while (!roots->empty()) {
     const uint32_t fi = roots->front();
@@ -618,14 +780,17 @@
 
 void InstrumentPass::InitializeInstrument() {
   output_buffer_id_ = 0;
-  output_buffer_uint_ptr_id_ = 0;
+  buffer_uint_ptr_id_ = 0;
   output_func_id_ = 0;
   output_func_param_cnt_ = 0;
+  input_buffer_id_ = 0;
   v4float_id_ = 0;
   uint_id_ = 0;
   v4uint_id_ = 0;
   bool_id_ = 0;
   void_id_ = 0;
+  storage_buffer_ext_defined_ = false;
+  uint_rarr_ty_ = nullptr;
 
   // clear collections
   id2function_.clear();
@@ -639,70 +804,68 @@
     }
   }
 
-  // Calculate instruction offset of first function
-  uint32_t pre_func_size = 0;
+  // Remember original instruction offsets
+  uint32_t module_offset = 0;
   Module* module = get_module();
   for (auto& i : context()->capabilities()) {
     (void)i;
-    ++pre_func_size;
+    ++module_offset;
   }
   for (auto& i : module->extensions()) {
     (void)i;
-    ++pre_func_size;
+    ++module_offset;
   }
   for (auto& i : module->ext_inst_imports()) {
     (void)i;
-    ++pre_func_size;
+    ++module_offset;
   }
-  ++pre_func_size;  // memory_model
+  ++module_offset;  // memory_model
   for (auto& i : module->entry_points()) {
     (void)i;
-    ++pre_func_size;
+    ++module_offset;
   }
   for (auto& i : module->execution_modes()) {
     (void)i;
-    ++pre_func_size;
+    ++module_offset;
   }
   for (auto& i : module->debugs1()) {
     (void)i;
-    ++pre_func_size;
+    ++module_offset;
   }
   for (auto& i : module->debugs2()) {
     (void)i;
-    ++pre_func_size;
+    ++module_offset;
   }
   for (auto& i : module->debugs3()) {
     (void)i;
-    ++pre_func_size;
+    ++module_offset;
   }
   for (auto& i : module->annotations()) {
     (void)i;
-    ++pre_func_size;
+    ++module_offset;
   }
   for (auto& i : module->types_values()) {
-    pre_func_size += 1;
-    pre_func_size += static_cast<uint32_t>(i.dbg_line_insts().size());
+    module_offset += 1;
+    module_offset += static_cast<uint32_t>(i.dbg_line_insts().size());
   }
-  funcIdx2offset_[0] = pre_func_size;
 
-  // Set instruction offsets for all other functions.
-  uint32_t func_idx = 1;
-  auto prev_fn = get_module()->begin();
-  auto curr_fn = prev_fn;
-  for (++curr_fn; curr_fn != get_module()->end(); ++curr_fn) {
-    // Count function and end instructions
-    uint32_t func_size = 2;
-    for (auto& blk : *prev_fn) {
+  auto curr_fn = get_module()->begin();
+  for (; curr_fn != get_module()->end(); ++curr_fn) {
+    // Count function instruction
+    module_offset += 1;
+    curr_fn->ForEachParam(
+        [&module_offset](const Instruction*) { module_offset += 1; }, true);
+    for (auto& blk : *curr_fn) {
       // Count label
-      func_size += 1;
+      module_offset += 1;
       for (auto& inst : blk) {
-        func_size += 1;
-        func_size += static_cast<uint32_t>(inst.dbg_line_insts().size());
+        module_offset += static_cast<uint32_t>(inst.dbg_line_insts().size());
+        uid2offset_[inst.unique_id()] = module_offset;
+        module_offset += 1;
       }
     }
-    funcIdx2offset_[func_idx] = func_size;
-    ++prev_fn;
-    ++func_idx;
+    // Count function end instruction
+    module_offset += 1;
   }
 }
 
diff --git a/source/opt/instrument_pass.h b/source/opt/instrument_pass.h
index baf510b..c4b97d6 100644
--- a/source/opt/instrument_pass.h
+++ b/source/opt/instrument_pass.h
@@ -65,17 +65,16 @@
   using cbb_ptr = const BasicBlock*;
 
  public:
-  using InstProcessFunction = std::function<void(
-      BasicBlock::iterator, UptrVectorIterator<BasicBlock>, uint32_t, uint32_t,
-      std::vector<std::unique_ptr<BasicBlock>>*)>;
+  using InstProcessFunction =
+      std::function<void(BasicBlock::iterator, UptrVectorIterator<BasicBlock>,
+                         uint32_t, std::vector<std::unique_ptr<BasicBlock>>*)>;
 
   ~InstrumentPass() override = default;
 
   IRContext::Analysis GetPreservedAnalyses() override {
-    return IRContext::kAnalysisDefUse |
-           IRContext::kAnalysisInstrToBlockMapping |
-           IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
-           IRContext::kAnalysisNameMap | IRContext::kAnalysisBuiltinVarId;
+    return IRContext::kAnalysisDefUse | IRContext::kAnalysisDecorations |
+           IRContext::kAnalysisCombinators | IRContext::kAnalysisNameMap |
+           IRContext::kAnalysisBuiltinVarId | IRContext::kAnalysisConstants;
   }
 
  protected:
@@ -106,7 +105,7 @@
   // Move all code in |ref_block_itr| succeeding the instruction |ref_inst_itr|
   // to be instrumented into block |new_blk_ptr|.
   void MovePostludeCode(UptrVectorIterator<BasicBlock> ref_block_itr,
-                        std::unique_ptr<BasicBlock>* new_blk_ptr);
+                        BasicBlock* new_blk_ptr);
 
   // Generate instructions in |builder| which will atomically fetch and
   // increment the size of the debug output buffer stream of the current
@@ -194,6 +193,17 @@
                            const std::vector<uint32_t>& validation_ids,
                            InstructionBuilder* builder);
 
+  // Generate in |builder| instructions to read the unsigned integer from the
+  // input buffer specified by the offsets in |offset_ids|. Given offsets
+  // o0, o1, ... oN, and input buffer ibuf, return the id for the value:
+  //
+  // ibuf[...ibuf[ibuf[o0]+o1]...+oN]
+  //
+  // The binding and the format of the input buffer is determined by each
+  // specific validation, which is specified at the creation of the pass.
+  uint32_t GenDebugDirectRead(const std::vector<uint32_t>& offset_ids,
+                              InstructionBuilder* builder);
+
   // Generate code to cast |value_id| to unsigned, if needed. Return
   // an id to the unsigned equivalent.
   uint32_t GenUintCastCode(uint32_t value_id, InstructionBuilder* builder);
@@ -210,15 +220,28 @@
   // Return id for void type
   uint32_t GetVoidId();
 
-  // Return id for output buffer uint type
-  uint32_t GetOutputBufferUintPtrId();
+  // Return pointer to type for runtime array of uint
+  analysis::Type* GetUintRuntimeArrayType(analysis::DecorationManager* deco_mgr,
+                                          analysis::TypeManager* type_mgr);
+
+  // Return id for buffer uint type
+  uint32_t GetBufferUintPtrId();
 
   // Return binding for output buffer for current validation.
   uint32_t GetOutputBufferBinding();
 
+  // Return binding for input buffer for current validation.
+  uint32_t GetInputBufferBinding();
+
+  // Add storage buffer extension if needed
+  void AddStorageBufferExt();
+
   // Return id for debug output buffer
   uint32_t GetOutputBufferId();
 
+  // Return id for debug input buffer
+  uint32_t GetInputBufferId();
+
   // Return id for v4float type
   uint32_t GetVec4FloatId();
 
@@ -226,10 +249,14 @@
   uint32_t GetVec4UintId();
 
   // Return id for output function. Define if it doesn't exist with
-  // |val_spec_arg_cnt| validation-specific uint32 arguments.
+  // |val_spec_param_cnt| validation-specific uint32 parameters.
   uint32_t GetStreamWriteFunctionId(uint32_t stage_idx,
                                     uint32_t val_spec_param_cnt);
 
+  // Return id for input function taking |param_cnt| uint32 parameters. Define
+  // if it doesn't exist.
+  uint32_t GetDirectReadFunctionId(uint32_t param_cnt);
+
   // Apply instrumentation function |pfn| to every instruction in |func|.
   // If code is generated for an instruction, replace the instruction's
   // block with the new blocks that are generated. Continue processing at the
@@ -291,7 +318,7 @@
       std::unique_ptr<Instruction>* inst,
       std::unordered_map<uint32_t, uint32_t>* same_blk_post,
       std::unordered_map<uint32_t, Instruction*>* same_blk_pre,
-      std::unique_ptr<BasicBlock>* block_ptr);
+      BasicBlock* block_ptr);
 
   // Update phis in succeeding blocks to point to new last block
   void UpdateSucceedingPhis(
@@ -310,8 +337,8 @@
   // CFG. It has functionality not present in CFG. Consolidate.
   std::unordered_map<uint32_t, BasicBlock*> id2block_;
 
-  // Map from function's position index to the offset of its first instruction
-  std::unordered_map<uint32_t, uint32_t> funcIdx2offset_;
+  // Map from instruction's unique id to offset in original file.
+  std::unordered_map<uint32_t, uint32_t> uid2offset_;
 
   // result id for OpConstantFalse
   uint32_t validation_id_;
@@ -320,14 +347,20 @@
   uint32_t output_buffer_id_;
 
   // type id for output buffer element
-  uint32_t output_buffer_uint_ptr_id_;
+  uint32_t buffer_uint_ptr_id_;
 
   // id for debug output function
   uint32_t output_func_id_;
 
+  // ids for debug input functions
+  std::unordered_map<uint32_t, uint32_t> param2input_func_id_;
+
   // param count for output function
   uint32_t output_func_param_cnt_;
 
+  // id for input buffer variable
+  uint32_t input_buffer_id_;
+
   // id for v4float type
   uint32_t v4float_id_;
 
@@ -343,6 +376,12 @@
   // id for void type
   uint32_t void_id_;
 
+  // boolean to remember storage buffer extension
+  bool storage_buffer_ext_defined_;
+
+  // runtime array of uint type
+  analysis::Type* uint_rarr_ty_;
+
   // Pre-instrumentation same-block insts
   std::unordered_map<uint32_t, Instruction*> same_block_pre_;
 
diff --git a/source/opt/ir_builder.h b/source/opt/ir_builder.h
index 2f741d8..da74055 100644
--- a/source/opt/ir_builder.h
+++ b/source/opt/ir_builder.h
@@ -455,6 +455,16 @@
     return AddInstruction(std::move(new_inst));
   }
 
+  Instruction* AddStore(uint32_t ptr_id, uint32_t obj_id) {
+    std::vector<Operand> operands;
+    operands.push_back({SPV_OPERAND_TYPE_ID, {ptr_id}});
+    operands.push_back({SPV_OPERAND_TYPE_ID, {obj_id}});
+
+    std::unique_ptr<Instruction> new_inst(
+        new Instruction(GetContext(), SpvOpStore, 0, 0, operands));
+    return AddInstruction(std::move(new_inst));
+  }
+
   // Inserts the new instruction before the insertion point.
   Instruction* AddInstruction(std::unique_ptr<Instruction>&& insn) {
     Instruction* insn_ptr = &*insert_before_.InsertBefore(std::move(insn));
diff --git a/source/opt/ir_context.cpp b/source/opt/ir_context.cpp
index af9ac1a..61c5425 100644
--- a/source/opt/ir_context.cpp
+++ b/source/opt/ir_context.cpp
@@ -74,6 +74,12 @@
   if (set & kAnalysisIdToFuncMapping) {
     BuildIdToFuncMapping();
   }
+  if (set & kAnalysisConstants) {
+    BuildConstantManager();
+  }
+  if (set & kAnalysisTypes) {
+    BuildTypeManager();
+  }
 }
 
 void IRContext::InvalidateAnalysesExceptFor(
@@ -83,6 +89,11 @@
 }
 
 void IRContext::InvalidateAnalyses(IRContext::Analysis analyses_to_invalidate) {
+  // The ConstantManager contains Type pointers. If the TypeManager goes
+  // away, the ConstantManager has to go away.
+  if (analyses_to_invalidate & kAnalysisTypes) {
+    analyses_to_invalidate |= kAnalysisConstants;
+  }
   if (analyses_to_invalidate & kAnalysisDefUse) {
     def_use_mgr_.reset(nullptr);
   }
@@ -117,6 +128,12 @@
   if (analyses_to_invalidate & kAnalysisIdToFuncMapping) {
     id_to_func_.clear();
   }
+  if (analyses_to_invalidate & kAnalysisConstants) {
+    constant_mgr_.reset(nullptr);
+  }
+  if (analyses_to_invalidate & kAnalysisTypes) {
+    type_mgr_.reset(nullptr);
+  }
 
   valid_analyses_ = Analysis(valid_analyses_ & ~analyses_to_invalidate);
 }
@@ -328,6 +345,7 @@
                                SpvOpTypeImage,
                                SpvOpTypeSampler,
                                SpvOpTypeSampledImage,
+                               SpvOpTypeAccelerationStructureNV,
                                SpvOpTypeArray,
                                SpvOpTypeRuntimeArray,
                                SpvOpTypeStruct,
@@ -656,8 +674,8 @@
         reg_type = type_mgr->GetRegisteredType(&v4float_ty);
         break;
       }
-      case SpvBuiltInVertexId:
-      case SpvBuiltInInstanceId:
+      case SpvBuiltInVertexIndex:
+      case SpvBuiltInInstanceIndex:
       case SpvBuiltInPrimitiveId:
       case SpvBuiltInInvocationId:
       case SpvBuiltInGlobalInvocationId: {
diff --git a/source/opt/ir_context.h b/source/opt/ir_context.h
index 94bfd01..185b494 100644
--- a/source/opt/ir_context.h
+++ b/source/opt/ir_context.h
@@ -76,7 +76,9 @@
     kAnalysisStructuredCFG = 1 << 11,
     kAnalysisBuiltinVarId = 1 << 12,
     kAnalysisIdToFuncMapping = 1 << 13,
-    kAnalysisEnd = 1 << 14
+    kAnalysisConstants = 1 << 14,
+    kAnalysisTypes = 1 << 15,
+    kAnalysisEnd = 1 << 16
   };
 
   using ProcessFunction = std::function<bool(Function*)>;
@@ -292,8 +294,9 @@
   // created yet, it creates one.  NOTE: Once created, the constant manager
   // remains active and it is never re-built.
   analysis::ConstantManager* get_constant_mgr() {
-    if (!constant_mgr_)
-      constant_mgr_ = MakeUnique<analysis::ConstantManager>(this);
+    if (!AreAnalysesValid(kAnalysisConstants)) {
+      BuildConstantManager();
+    }
     return constant_mgr_.get();
   }
 
@@ -301,8 +304,9 @@
   // yet, it creates one. NOTE: Once created, the type manager remains active it
   // is never re-built.
   analysis::TypeManager* get_type_mgr() {
-    if (!type_mgr_)
-      type_mgr_ = MakeUnique<analysis::TypeManager>(consumer(), this);
+    if (!AreAnalysesValid(kAnalysisTypes)) {
+      BuildTypeManager();
+    }
     return type_mgr_.get();
   }
 
@@ -584,6 +588,20 @@
     valid_analyses_ = valid_analyses_ | kAnalysisStructuredCFG;
   }
 
+  // Builds the constant manager from scratch, even if it was already
+  // valid.
+  void BuildConstantManager() {
+    constant_mgr_ = MakeUnique<analysis::ConstantManager>(this);
+    valid_analyses_ = valid_analyses_ | kAnalysisConstants;
+  }
+
+  // Builds the type manager from scratch, even if it was already
+  // valid.
+  void BuildTypeManager() {
+    type_mgr_ = MakeUnique<analysis::TypeManager>(consumer(), this);
+    valid_analyses_ = valid_analyses_ | kAnalysisTypes;
+  }
+
   // Removes all computed dominator and post-dominator trees. This will force
   // the context to rebuild the trees on demand.
   void ResetDominatorAnalysis() {
diff --git a/source/opt/ir_loader.cpp b/source/opt/ir_loader.cpp
index 46e2bee..edd4832 100644
--- a/source/opt/ir_loader.cpp
+++ b/source/opt/ir_loader.cpp
@@ -116,8 +116,10 @@
                  opcode == SpvOpUndef) {
         module_->AddGlobalValue(std::move(spv_inst));
       } else {
-        SPIRV_UNIMPLEMENTED(consumer_,
-                            "unhandled inst type outside function definition");
+        Errorf(consumer_, src, loc,
+               "Unhandled inst type (opcode: %d) found outside function definition.",
+               opcode);
+        return false;
       }
     } else {
       if (block_ == nullptr) {  // Inside function but outside blocks
diff --git a/source/opt/legalize_vector_shuffle_pass.cpp b/source/opt/legalize_vector_shuffle_pass.cpp
new file mode 100644
index 0000000..b5d5d59
--- /dev/null
+++ b/source/opt/legalize_vector_shuffle_pass.cpp
@@ -0,0 +1,39 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/opt/legalize_vector_shuffle_pass.h"
+
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace opt {
+
+Pass::Status LegalizeVectorShufflePass::Process() {
+  bool changed = false;
+  context()->module()->ForEachInst([&changed](Instruction* inst) {
+    if (inst->opcode() != SpvOpVectorShuffle) return;
+
+    for (uint32_t idx = 2; idx < inst->NumInOperands(); ++idx) {
+      auto literal = inst->GetSingleWordInOperand(idx);
+      if (literal != 0xFFFFFFFF) continue;
+      changed = true;
+      inst->SetInOperand(idx, {0});
+    }
+  });
+
+  return changed ? Status::SuccessWithChange : Status::SuccessWithoutChange;
+}
+
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/legalize_vector_shuffle_pass.h b/source/opt/legalize_vector_shuffle_pass.h
new file mode 100644
index 0000000..ca6e1df
--- /dev/null
+++ b/source/opt/legalize_vector_shuffle_pass.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_LEGALIZE_VECTOR_SHUFFLE_PASS_H_
+#define SOURCE_OPT_LEGALIZE_VECTOR_SHUFFLE_PASS_H_
+
+#include "source/opt/ir_context.h"
+#include "source/opt/module.h"
+#include "source/opt/pass.h"
+
+namespace spvtools {
+namespace opt {
+
+// Converts any usages of 0xFFFFFFFF for the literals in OpVectorShuffle to a
+// literal 0. This is needed because using OxFFFFFFFF is forbidden by the WebGPU
+// spec. 0xFFFFFFFF in the main spec indicates that the result for this
+// component has no source, thus is undefined. Since this is undefined
+// behaviour we are free to use 0.
+class LegalizeVectorShufflePass : public Pass {
+ public:
+  const char* name() const override { return "legalize-vector-shuffle"; }
+  Status Process() override;
+
+  IRContext::Analysis GetPreservedAnalyses() override {
+    return IRContext::kAnalysisInstrToBlockMapping |
+           IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
+           IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
+           IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap |
+           IRContext::kAnalysisScalarEvolution |
+           IRContext::kAnalysisRegisterPressure |
+           IRContext::kAnalysisValueNumberTable |
+           IRContext::kAnalysisStructuredCFG |
+           IRContext::kAnalysisBuiltinVarId |
+           IRContext::kAnalysisIdToFuncMapping | IRContext::kAnalysisTypes |
+           IRContext::kAnalysisDefUse | IRContext::kAnalysisConstants;
+  }
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // SOURCE_OPT_LEGALIZE_VECTOR_SHUFFLE_PASS_H_
diff --git a/source/opt/licm_pass.cpp b/source/opt/licm_pass.cpp
index c553221..82851fd 100644
--- a/source/opt/licm_pass.cpp
+++ b/source/opt/licm_pass.cpp
@@ -124,7 +124,14 @@
   if (!pre_header_bb) {
     return false;
   }
-  inst->InsertBefore(std::move(&(*pre_header_bb->tail())));
+  Instruction* insertion_point = &*pre_header_bb->tail();
+  Instruction* previous_node = insertion_point->PreviousNode();
+  if (previous_node && (previous_node->opcode() == SpvOpLoopMerge ||
+                        previous_node->opcode() == SpvOpSelectionMerge)) {
+    insertion_point = previous_node;
+  }
+
+  inst->InsertBefore(insertion_point);
   context()->set_instr_block(inst, pre_header_bb);
   return true;
 }
diff --git a/source/opt/licm_pass.h b/source/opt/licm_pass.h
index a94ae11..597fe92 100644
--- a/source/opt/licm_pass.h
+++ b/source/opt/licm_pass.h
@@ -61,7 +61,7 @@
   // Returns true if |bb| is immediately contained in |loop|
   bool IsImmediatelyContainedInLoop(Loop* loop, Function* f, BasicBlock* bb);
 
-  // Move the instruction to the given BasicBlock
+  // Move the instruction to the preheader of |loop|.
   // This method will update the instruction to block mapping for the context
   bool HoistInstruction(Loop* loop, Instruction* inst);
 };
diff --git a/source/opt/local_access_chain_convert_pass.cpp b/source/opt/local_access_chain_convert_pass.cpp
index 5b976a1..2258e61 100644
--- a/source/opt/local_access_chain_convert_pass.cpp
+++ b/source/opt/local_access_chain_convert_pass.cpp
@@ -264,6 +264,12 @@
 }
 
 bool LocalAccessChainConvertPass::AllExtensionsSupported() const {
+  // This capability can now exist without the extension, so we have to check
+  // for the capability.  This pass is only looking at function scope symbols,
+  // so we do not care if there are variable pointers on storage buffers.
+  if (context()->get_feature_mgr()->HasCapability(
+          SpvCapabilityVariablePointers))
+    return false;
   // If any extension not in whitelist, return false
   for (auto& ei : get_module()->extensions()) {
     const char* extName =
diff --git a/source/opt/local_access_chain_convert_pass.h b/source/opt/local_access_chain_convert_pass.h
index 9d06890..1c7e3d5 100644
--- a/source/opt/local_access_chain_convert_pass.h
+++ b/source/opt/local_access_chain_convert_pass.h
@@ -44,7 +44,8 @@
   Status Process() override;
 
   IRContext::Analysis GetPreservedAnalyses() override {
-    return IRContext::kAnalysisDefUse;
+    return IRContext::kAnalysisDefUse | IRContext::kAnalysisConstants |
+           IRContext::kAnalysisTypes;
   }
 
   using ProcessFunction = std::function<bool(Function*)>;
diff --git a/source/opt/local_redundancy_elimination.h b/source/opt/local_redundancy_elimination.h
index 9f55c8b..770457a 100644
--- a/source/opt/local_redundancy_elimination.h
+++ b/source/opt/local_redundancy_elimination.h
@@ -41,7 +41,8 @@
            IRContext::kAnalysisInstrToBlockMapping |
            IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
            IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
-           IRContext::kAnalysisNameMap;
+           IRContext::kAnalysisNameMap | IRContext::kAnalysisConstants |
+           IRContext::kAnalysisTypes;
   }
 
  protected:
diff --git a/source/opt/local_single_block_elim_pass.cpp b/source/opt/local_single_block_elim_pass.cpp
index 9330ab7..3e2eb7e 100644
--- a/source/opt/local_single_block_elim_pass.cpp
+++ b/source/opt/local_single_block_elim_pass.cpp
@@ -187,6 +187,7 @@
   // Assumes relaxed logical addressing only (see instruction.h).
   if (context()->get_feature_mgr()->HasCapability(SpvCapabilityAddresses))
     return Status::SuccessWithoutChange;
+
   // Do not process if module contains OpGroupDecorate. Additional
   // support required in KillNamesAndDecorates().
   // TODO(greg-lunarg): Add support for OpGroupDecorate
@@ -233,8 +234,7 @@
       "SPV_NV_geometry_shader_passthrough",
       "SPV_AMD_texture_gather_bias_lod",
       "SPV_KHR_storage_buffer_storage_class",
-      // SPV_KHR_variable_pointers
-      //   Currently do not support extended pointer expressions
+      "SPV_KHR_variable_pointers",
       "SPV_AMD_gpu_shader_int16",
       "SPV_KHR_post_depth_coverage",
       "SPV_KHR_shader_atomic_counter_ops",
diff --git a/source/opt/local_single_block_elim_pass.h b/source/opt/local_single_block_elim_pass.h
index 3dead98..0fe7732 100644
--- a/source/opt/local_single_block_elim_pass.h
+++ b/source/opt/local_single_block_elim_pass.h
@@ -42,7 +42,9 @@
   Status Process() override;
 
   IRContext::Analysis GetPreservedAnalyses() override {
-    return IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping;
+    return IRContext::kAnalysisDefUse |
+           IRContext::kAnalysisInstrToBlockMapping |
+           IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
   }
 
  private:
diff --git a/source/opt/local_single_store_elim_pass.cpp b/source/opt/local_single_store_elim_pass.cpp
index 6c09dec..f47777e 100644
--- a/source/opt/local_single_store_elim_pass.cpp
+++ b/source/opt/local_single_store_elim_pass.cpp
@@ -98,8 +98,7 @@
       "SPV_NV_geometry_shader_passthrough",
       "SPV_AMD_texture_gather_bias_lod",
       "SPV_KHR_storage_buffer_storage_class",
-      // SPV_KHR_variable_pointers
-      //   Currently do not support extended pointer expressions
+      "SPV_KHR_variable_pointers",
       "SPV_AMD_gpu_shader_int16",
       "SPV_KHR_post_depth_coverage",
       "SPV_KHR_shader_atomic_counter_ops",
diff --git a/source/opt/local_single_store_elim_pass.h b/source/opt/local_single_store_elim_pass.h
index d3d64b8..4cf8bbb 100644
--- a/source/opt/local_single_store_elim_pass.h
+++ b/source/opt/local_single_store_elim_pass.h
@@ -45,7 +45,9 @@
   Status Process() override;
 
   IRContext::Analysis GetPreservedAnalyses() override {
-    return IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping;
+    return IRContext::kAnalysisDefUse |
+           IRContext::kAnalysisInstrToBlockMapping |
+           IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
   }
 
  private:
diff --git a/source/opt/local_ssa_elim_pass.cpp b/source/opt/local_ssa_elim_pass.cpp
index 8209aa4..df327a6 100644
--- a/source/opt/local_ssa_elim_pass.cpp
+++ b/source/opt/local_ssa_elim_pass.cpp
@@ -83,8 +83,7 @@
       "SPV_NV_geometry_shader_passthrough",
       "SPV_AMD_texture_gather_bias_lod",
       "SPV_KHR_storage_buffer_storage_class",
-      // SPV_KHR_variable_pointers
-      //   Currently do not support extended pointer expressions
+      "SPV_KHR_variable_pointers",
       "SPV_AMD_gpu_shader_int16",
       "SPV_KHR_post_depth_coverage",
       "SPV_KHR_shader_atomic_counter_ops",
diff --git a/source/opt/local_ssa_elim_pass.h b/source/opt/local_ssa_elim_pass.h
index 63d3c33..de80d5a 100644
--- a/source/opt/local_ssa_elim_pass.h
+++ b/source/opt/local_ssa_elim_pass.h
@@ -48,7 +48,9 @@
   Status Process() override;
 
   IRContext::Analysis GetPreservedAnalyses() override {
-    return IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping;
+    return IRContext::kAnalysisDefUse |
+           IRContext::kAnalysisInstrToBlockMapping |
+           IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
   }
 
  private:
diff --git a/source/opt/loop_descriptor.cpp b/source/opt/loop_descriptor.cpp
index 5aff34c..11f7e9c 100644
--- a/source/opt/loop_descriptor.cpp
+++ b/source/opt/loop_descriptor.cpp
@@ -937,22 +937,23 @@
 
   for (Loop* loop : loops_to_remove_) {
     loops_.erase(std::find(loops_.begin(), loops_.end(), loop));
+    delete loop;
   }
 
   for (auto& pair : loops_to_add_) {
     Loop* parent = pair.first;
-    Loop* loop = pair.second;
+    std::unique_ptr<Loop> loop = std::move(pair.second);
 
     if (parent) {
       loop->SetParent(nullptr);
-      parent->AddNestedLoop(loop);
+      parent->AddNestedLoop(loop.get());
 
       for (uint32_t block_id : loop->GetBlocks()) {
         parent->AddBasicBlock(block_id);
       }
     }
 
-    loops_.emplace_back(loop);
+    loops_.emplace_back(loop.release());
   }
 
   loops_to_add_.clear();
diff --git a/source/opt/loop_descriptor.h b/source/opt/loop_descriptor.h
index 38f017b..6e2b828 100644
--- a/source/opt/loop_descriptor.h
+++ b/source/opt/loop_descriptor.h
@@ -499,8 +499,8 @@
 
   // Mark the loop |loop_to_add| as needing to be added when the user calls
   // PostModificationCleanup. |parent| may be null.
-  inline void AddLoop(Loop* loop_to_add, Loop* parent) {
-    loops_to_add_.emplace_back(std::make_pair(parent, loop_to_add));
+  inline void AddLoop(std::unique_ptr<Loop>&& loop_to_add, Loop* parent) {
+    loops_to_add_.emplace_back(std::make_pair(parent, std::move(loop_to_add)));
   }
 
   // Checks all loops in |this| and will create pre-headers for all loops
@@ -537,7 +537,9 @@
   // TODO(dneto): This should be a vector of unique_ptr.  But VisualStudio 2013
   // is unable to compile it.
   using LoopContainerType = std::vector<Loop*>;
-  using LoopsToAddContainerType = std::vector<std::pair<Loop*, Loop*>>;
+
+  using LoopsToAddContainerType =
+      std::vector<std::pair<Loop*, std::unique_ptr<Loop>>>;
 
   // Creates loop descriptors for the function |f|.
   void PopulateList(IRContext* context, const Function* f);
diff --git a/source/opt/loop_unroller.cpp b/source/opt/loop_unroller.cpp
index 0d49d88..10fac04 100644
--- a/source/opt/loop_unroller.cpp
+++ b/source/opt/loop_unroller.cpp
@@ -394,12 +394,12 @@
   // This is a naked new due to the VS2013 requirement of not having unique
   // pointers in vectors, as it will be inserted into a vector with
   // loop_descriptor.AddLoop.
-  Loop* new_loop = new Loop(*loop);
+  std::unique_ptr<Loop> new_loop = MakeUnique<Loop>(*loop);
 
   // Clear the basic blocks of the new loop.
   new_loop->ClearBlocks();
 
-  DuplicateLoop(loop, new_loop);
+  DuplicateLoop(loop, new_loop.get());
 
   // Add the blocks to the function.
   AddBlocksToFunction(loop->GetMergeBlock());
@@ -414,10 +414,10 @@
   loop_induction_variable_ = state_.new_phi;
   // Unroll the new loop by the factor with the usual -1 to account for the
   // existing block iteration.
-  Unroll(new_loop, factor);
+  Unroll(new_loop.get(), factor);
 
-  LinkLastPhisToStart(new_loop);
-  AddBlocksToLoop(new_loop);
+  LinkLastPhisToStart(new_loop.get());
+  AddBlocksToLoop(new_loop.get());
 
   // Add the new merge block to the back of the list of blocks to be added. It
   // needs to be the last block added to maintain dominator order in the binary.
@@ -507,7 +507,7 @@
 
   LoopDescriptor& loop_descriptor = *context_->GetLoopDescriptor(&function_);
 
-  loop_descriptor.AddLoop(new_loop, loop->GetParent());
+  loop_descriptor.AddLoop(std::move(new_loop), loop->GetParent());
 
   RemoveDeadInstructions();
 }
diff --git a/source/opt/loop_unroller.h b/source/opt/loop_unroller.h
index 41a91a2..71e7cca 100644
--- a/source/opt/loop_unroller.h
+++ b/source/opt/loop_unroller.h
@@ -34,7 +34,8 @@
     return IRContext::kAnalysisDefUse |
            IRContext::kAnalysisInstrToBlockMapping |
            IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
-           IRContext::kAnalysisNameMap;
+           IRContext::kAnalysisNameMap | IRContext::kAnalysisConstants |
+           IRContext::kAnalysisTypes;
   }
 
  private:
diff --git a/source/opt/loop_unswitch_pass.cpp b/source/opt/loop_unswitch_pass.cpp
index 7e374d9..502fc6b 100644
--- a/source/opt/loop_unswitch_pass.cpp
+++ b/source/opt/loop_unswitch_pass.cpp
@@ -39,8 +39,6 @@
 namespace {
 
 static const uint32_t kTypePointerStorageClassInIdx = 0;
-static const uint32_t kBranchCondTrueLabIdInIdx = 1;
-static const uint32_t kBranchCondFalseLabIdInIdx = 2;
 
 }  // anonymous namespace
 
@@ -74,9 +72,13 @@
 
     for (uint32_t bb_id : loop_->GetBlocks()) {
       BasicBlock* bb = cfg.block(bb_id);
+      if (loop_->GetLatchBlock() == bb) {
+        continue;
+      }
+
       if (bb->terminator()->IsBranch() &&
           bb->terminator()->opcode() != SpvOpBranch) {
-        if (IsConditionLoopInvariant(bb->terminator())) {
+        if (IsConditionNonConstantLoopInvariant(bb->terminator())) {
           switch_block_ = bb;
           break;
         }
@@ -110,6 +112,35 @@
     return bb;
   }
 
+  Instruction* GetValueForDefaultPathForSwitch(Instruction* switch_inst) {
+    assert(switch_inst->opcode() == SpvOpSwitch &&
+           "The given instructoin must be an OpSwitch.");
+
+    // Find a value that can be used to select the default path.
+    // If none are possible, then it will just use 0.  The value does not matter
+    // because this path will never be taken becaues the new switch outside of
+    // the loop cannot select this path either.
+    std::vector<uint32_t> existing_values;
+    for (uint32_t i = 2; i < switch_inst->NumInOperands(); i += 2) {
+      existing_values.push_back(switch_inst->GetSingleWordInOperand(i));
+    }
+    std::sort(existing_values.begin(), existing_values.end());
+    uint32_t value_for_default_path = 0;
+    if (existing_values.size() < std::numeric_limits<uint32_t>::max()) {
+      for (value_for_default_path = 0;
+           value_for_default_path < existing_values.size();
+           value_for_default_path++) {
+        if (existing_values[value_for_default_path] != value_for_default_path) {
+          break;
+        }
+      }
+    }
+    InstructionBuilder builder(
+        context_, static_cast<Instruction*>(nullptr),
+        IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
+    return builder.GetUintConstant(value_for_default_path);
+  }
+
   // Unswitches |loop_|.
   void PerformUnswitch() {
     assert(CanUnswitchLoop() &&
@@ -154,6 +185,7 @@
       if_merge_block->ForEachPhiInst(
           [loop_merge_block, &builder, this](Instruction* phi) {
             Instruction* cloned = phi->Clone(context_);
+            cloned->SetResultId(TakeNextId());
             builder.AddInstruction(std::unique_ptr<Instruction>(cloned));
             phi->SetInOperand(0, {cloned->result_id()});
             phi->SetInOperand(1, {loop_merge_block->id()});
@@ -177,7 +209,6 @@
         ploop->AddBasicBlock(loop_merge_block);
         loop_desc_.SetBasicBlockToLoop(loop_merge_block->id(), ploop);
       }
-
       // Update the dominator tree.
       DominatorTreeNode* loop_merge_dtn =
           dom_tree->GetOrInsertNode(loop_merge_block);
@@ -195,7 +226,7 @@
 
     ////////////////////////////////////////////////////////////////////////////
     // Step 2: Build a new preheader for |loop_|, use the old one
-    //         for the constant branch.
+    //         for the invariant branch.
     ////////////////////////////////////////////////////////////////////////////
 
     BasicBlock* if_block = loop_->GetPreHeaderBlock();
@@ -273,7 +304,6 @@
     std::vector<std::pair<Instruction*, BasicBlock*>> constant_branch;
     // Special case for the original loop
     Instruction* original_loop_constant_value;
-    BasicBlock* original_loop_target;
     if (iv_opcode == SpvOpBranchConditional) {
       constant_branch.emplace_back(
           cst_mgr->GetDefiningInstruction(cst_mgr->GetConstant(cond_type, {0})),
@@ -283,7 +313,9 @@
     } else {
       // We are looking to take the default branch, so we can't provide a
       // specific value.
-      original_loop_constant_value = nullptr;
+      original_loop_constant_value =
+          GetValueForDefaultPathForSwitch(iv_condition);
+
       for (uint32_t i = 2; i < iv_condition->NumInOperands(); i += 2) {
         constant_branch.emplace_back(
             cst_mgr->GetDefiningInstruction(cst_mgr->GetConstant(
@@ -323,24 +355,7 @@
       ////////////////////////////////////
 
       {
-        std::unordered_set<uint32_t> dead_blocks;
-        std::unordered_set<uint32_t> unreachable_merges;
-        SimplifyLoop(
-            make_range(
-                UptrVectorIterator<BasicBlock>(&clone_result.cloned_bb_,
-                                               clone_result.cloned_bb_.begin()),
-                UptrVectorIterator<BasicBlock>(&clone_result.cloned_bb_,
-                                               clone_result.cloned_bb_.end())),
-            cloned_loop, condition, specialisation_value, &dead_blocks);
-
-        // We tagged dead blocks, create the loop before we invalidate any basic
-        // block.
-        cloned_loop =
-            CleanLoopNest(cloned_loop, dead_blocks, &unreachable_merges);
-        CleanUpCFG(
-            UptrVectorIterator<BasicBlock>(&clone_result.cloned_bb_,
-                                           clone_result.cloned_bb_.begin()),
-            dead_blocks, unreachable_merges);
+        SpecializeLoop(cloned_loop, condition, specialisation_value);
 
         ///////////////////////////////////////////////////////////
         // Step 5: Connect convergent edges to the landing pads. //
@@ -349,27 +364,25 @@
         for (uint32_t merge_bb_id : if_merging_blocks) {
           BasicBlock* merge = context_->cfg()->block(merge_bb_id);
           // We are in LCSSA so we only care about phi instructions.
-          merge->ForEachPhiInst([is_from_original_loop, &dead_blocks,
-                                 &clone_result](Instruction* phi) {
-            uint32_t num_in_operands = phi->NumInOperands();
-            for (uint32_t i = 0; i < num_in_operands; i += 2) {
-              uint32_t pred = phi->GetSingleWordInOperand(i + 1);
-              if (is_from_original_loop(pred)) {
-                pred = clone_result.value_map_.at(pred);
-                if (!dead_blocks.count(pred)) {
-                  uint32_t incoming_value_id = phi->GetSingleWordInOperand(i);
-                  // Not all the incoming value are coming from the loop.
-                  ValueMapTy::iterator new_value =
-                      clone_result.value_map_.find(incoming_value_id);
-                  if (new_value != clone_result.value_map_.end()) {
-                    incoming_value_id = new_value->second;
+          merge->ForEachPhiInst(
+              [is_from_original_loop, &clone_result](Instruction* phi) {
+                uint32_t num_in_operands = phi->NumInOperands();
+                for (uint32_t i = 0; i < num_in_operands; i += 2) {
+                  uint32_t pred = phi->GetSingleWordInOperand(i + 1);
+                  if (is_from_original_loop(pred)) {
+                    pred = clone_result.value_map_.at(pred);
+                    uint32_t incoming_value_id = phi->GetSingleWordInOperand(i);
+                    // Not all the incoming values are coming from the loop.
+                    ValueMapTy::iterator new_value =
+                        clone_result.value_map_.find(incoming_value_id);
+                    if (new_value != clone_result.value_map_.end()) {
+                      incoming_value_id = new_value->second;
+                    }
+                    phi->AddOperand({SPV_OPERAND_TYPE_ID, {incoming_value_id}});
+                    phi->AddOperand({SPV_OPERAND_TYPE_ID, {pred}});
                   }
-                  phi->AddOperand({SPV_OPERAND_TYPE_ID, {incoming_value_id}});
-                  phi->AddOperand({SPV_OPERAND_TYPE_ID, {pred}});
                 }
-              }
-            }
-          });
+              });
         }
       }
       function_->AddBasicBlocks(clone_result.cloned_bb_.begin(),
@@ -377,38 +390,9 @@
                                 ++FindBasicBlockPosition(if_block));
     }
 
-    // Same as above but specialize the existing loop
-    {
-      std::unordered_set<uint32_t> dead_blocks;
-      std::unordered_set<uint32_t> unreachable_merges;
-      SimplifyLoop(make_range(function_->begin(), function_->end()), loop_,
-                   condition, original_loop_constant_value, &dead_blocks);
-
-      for (uint32_t merge_bb_id : if_merging_blocks) {
-        BasicBlock* merge = context_->cfg()->block(merge_bb_id);
-        // LCSSA, so we only care about phi instructions.
-        // If we the phi is reduced to a single incoming branch, do not
-        // propagate it to preserve LCSSA.
-        PatchPhis(merge, dead_blocks, true);
-      }
-      if (if_merge_block) {
-        bool has_live_pred = false;
-        for (uint32_t pid : cfg.preds(if_merge_block->id())) {
-          if (!dead_blocks.count(pid)) {
-            has_live_pred = true;
-            break;
-          }
-        }
-        if (!has_live_pred) unreachable_merges.insert(if_merge_block->id());
-      }
-      original_loop_target = loop_->GetPreHeaderBlock();
-      // We tagged dead blocks, prune the loop descriptor from any dead loops.
-      // After this call, |loop_| can be nullptr (i.e. the unswitch killed this
-      // loop).
-      loop_ = CleanLoopNest(loop_, dead_blocks, &unreachable_merges);
-
-      CleanUpCFG(function_->begin(), dead_blocks, unreachable_merges);
-    }
+    // Specialize the existing loop.
+    SpecializeLoop(loop_, condition, original_loop_constant_value);
+    BasicBlock* original_loop_target = loop_->GetPreHeaderBlock();
 
     /////////////////////////////////////
     // Finally: connect the new loops. //
@@ -441,9 +425,6 @@
         IRContext::Analysis::kAnalysisLoopAnalysis);
   }
 
-  // Returns true if the unswitch killed the original |loop_|.
-  bool WasLoopKilled() const { return loop_ == nullptr; }
-
  private:
   using ValueMapTy = std::unordered_map<uint32_t, uint32_t>;
   using BlockMapTy = std::unordered_map<uint32_t, BasicBlock*>;
@@ -465,96 +446,6 @@
     return context_->TakeNextId();
   }
 
-  // Patches |bb|'s phi instruction by removing incoming value from unexisting
-  // or tagged as dead branches.
-  void PatchPhis(BasicBlock* bb,
-                 const std::unordered_set<uint32_t>& dead_blocks,
-                 bool preserve_phi) {
-    CFG& cfg = *context_->cfg();
-
-    std::vector<Instruction*> phi_to_kill;
-    const std::vector<uint32_t>& bb_preds = cfg.preds(bb->id());
-    auto is_branch_dead = [&bb_preds, &dead_blocks](uint32_t id) {
-      return dead_blocks.count(id) ||
-             std::find(bb_preds.begin(), bb_preds.end(), id) == bb_preds.end();
-    };
-    bb->ForEachPhiInst(
-        [&phi_to_kill, &is_branch_dead, preserve_phi, this](Instruction* insn) {
-          uint32_t i = 0;
-          while (i < insn->NumInOperands()) {
-            uint32_t incoming_id = insn->GetSingleWordInOperand(i + 1);
-            if (is_branch_dead(incoming_id)) {
-              // Remove the incoming block id operand.
-              insn->RemoveInOperand(i + 1);
-              // Remove the definition id operand.
-              insn->RemoveInOperand(i);
-              continue;
-            }
-            i += 2;
-          }
-          // If there is only 1 remaining edge, propagate the value and
-          // kill the instruction.
-          if (insn->NumInOperands() == 2 && !preserve_phi) {
-            phi_to_kill.push_back(insn);
-            context_->ReplaceAllUsesWith(insn->result_id(),
-                                         insn->GetSingleWordInOperand(0));
-          }
-        });
-    for (Instruction* insn : phi_to_kill) {
-      context_->KillInst(insn);
-    }
-  }
-
-  // Removes any block that is tagged as dead, if the block is in
-  // |unreachable_merges| then all block's instructions are replaced by a
-  // OpUnreachable.
-  void CleanUpCFG(UptrVectorIterator<BasicBlock> bb_it,
-                  const std::unordered_set<uint32_t>& dead_blocks,
-                  const std::unordered_set<uint32_t>& unreachable_merges) {
-    CFG& cfg = *context_->cfg();
-
-    while (bb_it != bb_it.End()) {
-      BasicBlock& bb = *bb_it;
-
-      if (unreachable_merges.count(bb.id())) {
-        if (bb.begin() != bb.tail() ||
-            bb.terminator()->opcode() != SpvOpUnreachable) {
-          // Make unreachable, but leave the label.
-          bb.KillAllInsts(false);
-          InstructionBuilder(context_, &bb).AddUnreachable();
-          cfg.RemoveNonExistingEdges(bb.id());
-        }
-        ++bb_it;
-      } else if (dead_blocks.count(bb.id())) {
-        cfg.ForgetBlock(&bb);
-        // Kill this block.
-        bb.KillAllInsts(true);
-        bb_it = bb_it.Erase();
-      } else {
-        cfg.RemoveNonExistingEdges(bb.id());
-        ++bb_it;
-      }
-    }
-  }
-
-  // Return true if |c_inst| is a Boolean constant and set |cond_val| with the
-  // value that |c_inst|
-  bool GetConstCondition(const Instruction* c_inst, bool* cond_val) {
-    bool cond_is_const;
-    switch (c_inst->opcode()) {
-      case SpvOpConstantFalse: {
-        *cond_val = false;
-        cond_is_const = true;
-      } break;
-      case SpvOpConstantTrue: {
-        *cond_val = true;
-        cond_is_const = true;
-      } break;
-      default: { cond_is_const = false; } break;
-    }
-    return cond_is_const;
-  }
-
   // Simplifies |loop| assuming the instruction |to_version_insn| takes the
   // value |cst_value|. |block_range| is an iterator range returning the loop
   // basic blocks in a structured order (dominator first).
@@ -564,13 +455,9 @@
   //
   // Requirements:
   //   - |loop| must be in the LCSSA form;
-  //   - |cst_value| must be constant or null (to represent the default target
-  //   of an OpSwitch).
-  void SimplifyLoop(IteratorRange<UptrVectorIterator<BasicBlock>> block_range,
-                    Loop* loop, Instruction* to_version_insn,
-                    Instruction* cst_value,
-                    std::unordered_set<uint32_t>* dead_blocks) {
-    CFG& cfg = *context_->cfg();
+  //   - |cst_value| must be constant.
+  void SpecializeLoop(Loop* loop, Instruction* to_version_insn,
+                      Instruction* cst_value) {
     analysis::DefUseManager* def_use_mgr = context_->get_def_use_mgr();
 
     std::function<bool(uint32_t)> ignore_node;
@@ -595,192 +482,15 @@
     for (auto use : use_list) {
       Instruction* inst = use.first;
       uint32_t operand_index = use.second;
-      BasicBlock* bb = context_->get_instr_block(inst);
 
-      // If it is not a branch, simply inject the value.
-      if (!inst->IsBranch()) {
-        // To also handle switch, cst_value can be nullptr: this case
-        // means that we are looking to branch to the default target of
-        // the switch. We don't actually know its value so we don't touch
-        // it if it not a switch.
-        if (cst_value) {
-          inst->SetOperand(operand_index, {cst_value->result_id()});
-          def_use_mgr->AnalyzeInstUse(inst);
-        }
-      }
-
-      // The user is a branch, kill dead branches.
-      uint32_t live_target = 0;
-      std::unordered_set<uint32_t> dead_branches;
-      switch (inst->opcode()) {
-        case SpvOpBranchConditional: {
-          assert(cst_value && "No constant value to specialize !");
-          bool branch_cond = false;
-          if (GetConstCondition(cst_value, &branch_cond)) {
-            uint32_t true_label =
-                inst->GetSingleWordInOperand(kBranchCondTrueLabIdInIdx);
-            uint32_t false_label =
-                inst->GetSingleWordInOperand(kBranchCondFalseLabIdInIdx);
-            live_target = branch_cond ? true_label : false_label;
-            uint32_t dead_target = !branch_cond ? true_label : false_label;
-            cfg.RemoveEdge(bb->id(), dead_target);
-          }
-          break;
-        }
-        case SpvOpSwitch: {
-          live_target = inst->GetSingleWordInOperand(1);
-          if (cst_value) {
-            if (!cst_value->IsConstant()) break;
-            const Operand& cst = cst_value->GetInOperand(0);
-            for (uint32_t i = 2; i < inst->NumInOperands(); i += 2) {
-              const Operand& literal = inst->GetInOperand(i);
-              if (literal == cst) {
-                live_target = inst->GetSingleWordInOperand(i + 1);
-                break;
-              }
-            }
-          }
-          for (uint32_t i = 1; i < inst->NumInOperands(); i += 2) {
-            uint32_t id = inst->GetSingleWordInOperand(i);
-            if (id != live_target) {
-              cfg.RemoveEdge(bb->id(), id);
-            }
-          }
-        }
-        default:
-          break;
-      }
-      if (live_target != 0) {
-        // Check for the presence of the merge block.
-        if (Instruction* merge = bb->GetMergeInst()) context_->KillInst(merge);
-        context_->KillInst(&*bb->tail());
-        InstructionBuilder builder(context_, bb,
-                                   IRContext::kAnalysisDefUse |
-                                       IRContext::kAnalysisInstrToBlockMapping);
-        builder.AddBranch(live_target);
-      }
+      // To also handle switch, cst_value can be nullptr: this case
+      // means that we are looking to branch to the default target of
+      // the switch. We don't actually know its value so we don't touch
+      // it if it not a switch.
+      assert(cst_value && "We do not have a value to use.");
+      inst->SetOperand(operand_index, {cst_value->result_id()});
+      def_use_mgr->AnalyzeInstUse(inst);
     }
-
-    // Go through the loop basic block and tag all blocks that are obviously
-    // dead.
-    std::unordered_set<uint32_t> visited;
-    for (BasicBlock& bb : block_range) {
-      if (ignore_node(bb.id())) continue;
-      visited.insert(bb.id());
-
-      // Check if this block is dead, if so tag it as dead otherwise patch phi
-      // instructions.
-      bool has_live_pred = false;
-      for (uint32_t pid : cfg.preds(bb.id())) {
-        if (!dead_blocks->count(pid)) {
-          has_live_pred = true;
-          break;
-        }
-      }
-      if (!has_live_pred) {
-        dead_blocks->insert(bb.id());
-        const BasicBlock& cbb = bb;
-        // Patch the phis for any back-edge.
-        cbb.ForEachSuccessorLabel(
-            [dead_blocks, &visited, &cfg, this](uint32_t id) {
-              if (!visited.count(id) || dead_blocks->count(id)) return;
-              BasicBlock* succ = cfg.block(id);
-              PatchPhis(succ, *dead_blocks, false);
-            });
-        continue;
-      }
-      // Update the phi instructions, some incoming branch have/will disappear.
-      PatchPhis(&bb, *dead_blocks, /* preserve_phi = */ false);
-    }
-  }
-
-  // Returns true if the header is not reachable or tagged as dead or if we
-  // never loop back.
-  bool IsLoopDead(BasicBlock* header, BasicBlock* latch,
-                  const std::unordered_set<uint32_t>& dead_blocks) {
-    if (!header || dead_blocks.count(header->id())) return true;
-    if (!latch || dead_blocks.count(latch->id())) return true;
-    for (uint32_t pid : context_->cfg()->preds(header->id())) {
-      if (!dead_blocks.count(pid)) {
-        // Seems reachable.
-        return false;
-      }
-    }
-    return true;
-  }
-
-  // Cleans the loop nest under |loop| and reflect changes to the loop
-  // descriptor. This will kill all descriptors that represent dead loops.
-  // If |loop_| is killed, it will be set to nullptr.
-  // Any merge blocks that become unreachable will be added to
-  // |unreachable_merges|.
-  // The function returns the pointer to |loop| or nullptr if the loop was
-  // killed.
-  Loop* CleanLoopNest(Loop* loop,
-                      const std::unordered_set<uint32_t>& dead_blocks,
-                      std::unordered_set<uint32_t>* unreachable_merges) {
-    // This represent the pair of dead loop and nearest alive parent (nullptr if
-    // no parent).
-    std::unordered_map<Loop*, Loop*> dead_loops;
-    auto get_parent = [&dead_loops](Loop* l) -> Loop* {
-      std::unordered_map<Loop*, Loop*>::iterator it = dead_loops.find(l);
-      if (it != dead_loops.end()) return it->second;
-      return nullptr;
-    };
-
-    bool is_main_loop_dead =
-        IsLoopDead(loop->GetHeaderBlock(), loop->GetLatchBlock(), dead_blocks);
-    if (is_main_loop_dead) {
-      if (Instruction* merge = loop->GetHeaderBlock()->GetLoopMergeInst()) {
-        context_->KillInst(merge);
-      }
-      dead_loops[loop] = loop->GetParent();
-    } else {
-      dead_loops[loop] = loop;
-    }
-
-    // For each loop, check if we killed it. If we did, find a suitable parent
-    // for its children.
-    for (Loop& sub_loop :
-         make_range(++TreeDFIterator<Loop>(loop), TreeDFIterator<Loop>())) {
-      if (IsLoopDead(sub_loop.GetHeaderBlock(), sub_loop.GetLatchBlock(),
-                     dead_blocks)) {
-        if (Instruction* merge =
-                sub_loop.GetHeaderBlock()->GetLoopMergeInst()) {
-          context_->KillInst(merge);
-        }
-        dead_loops[&sub_loop] = get_parent(&sub_loop);
-      } else {
-        // The loop is alive, check if its merge block is dead, if it is, tag it
-        // as required.
-        if (sub_loop.GetMergeBlock()) {
-          uint32_t merge_id = sub_loop.GetMergeBlock()->id();
-          if (dead_blocks.count(merge_id)) {
-            unreachable_merges->insert(sub_loop.GetMergeBlock()->id());
-          }
-        }
-      }
-    }
-    if (!is_main_loop_dead) dead_loops.erase(loop);
-
-    // Remove dead blocks from live loops.
-    for (uint32_t bb_id : dead_blocks) {
-      Loop* l = loop_desc_[bb_id];
-      if (l) {
-        l->RemoveBasicBlock(bb_id);
-        loop_desc_.ForgetBasicBlock(bb_id);
-      }
-    }
-
-    std::for_each(
-        dead_loops.begin(), dead_loops.end(),
-        [&loop,
-         this](std::unordered_map<Loop*, Loop*>::iterator::reference it) {
-          if (it.first == loop) loop = nullptr;
-          loop_desc_.RemoveLoop(it.first);
-        });
-
-    return loop;
   }
 
   // Returns true if |var| is dynamically uniform.
@@ -839,17 +549,25 @@
     });
   }
 
-  // Returns true if |insn| is constant and dynamically uniform within the loop.
-  bool IsConditionLoopInvariant(Instruction* insn) {
+  // Returns true if |insn| is not a constant, but is loop invariant and
+  // dynamically uniform.
+  bool IsConditionNonConstantLoopInvariant(Instruction* insn) {
     assert(insn->IsBranch());
     assert(insn->opcode() != SpvOpBranch);
     analysis::DefUseManager* def_use_mgr = context_->get_def_use_mgr();
 
     Instruction* condition = def_use_mgr->GetDef(insn->GetOperand(0).words[0]);
-    return !loop_->IsInsideLoop(condition) &&
-           IsDynamicallyUniform(
-               condition, function_->entry().get(),
-               context_->GetPostDominatorAnalysis(function_)->GetDomTree());
+    if (condition->IsConstant()) {
+      return false;
+    }
+
+    if (loop_->IsInsideLoop(condition)) {
+      return false;
+    }
+
+    return IsDynamicallyUniform(
+        condition, function_->entry().get(),
+        context_->GetPostDominatorAnalysis(function_)->GetDomTree());
   }
 };
 
@@ -883,7 +601,7 @@
       processed_loop.insert(&loop);
 
       LoopUnswitch unswitcher(context(), f, &loop, &loop_descriptor);
-      while (!unswitcher.WasLoopKilled() && unswitcher.CanUnswitchLoop()) {
+      while (unswitcher.CanUnswitchLoop()) {
         if (!loop.IsLCSSA()) {
           LoopUtils(context(), &loop).MakeLoopClosedSSA();
         }
diff --git a/source/opt/merge_return_pass.cpp b/source/opt/merge_return_pass.cpp
index a13540c..a12b2ca 100644
--- a/source/opt/merge_return_pass.cpp
+++ b/source/opt/merge_return_pass.cpp
@@ -197,6 +197,7 @@
       tail_opcode == SpvOpUnreachable) {
     assert(CurrentState().InLoop() && "Should be in the dummy loop.");
     BranchToBlock(block, CurrentState().LoopMergeId());
+    return_blocks_.insert(block->id());
   }
 }
 
@@ -232,11 +233,19 @@
   const auto& target_pred = cfg()->preds(target->id());
   if (target_pred.size() == 1) {
     MarkForNewPhiNodes(target, context()->get_instr_block(target_pred[0]));
+  } else {
+    // If the loop contained a break and a return, OpPhi instructions may be
+    // required starting from the dominator of the loop merge.
+    DominatorAnalysis* dom_tree =
+        context()->GetDominatorAnalysis(target->GetParent());
+    auto idom = dom_tree->ImmediateDominator(target);
+    if (idom) {
+      MarkForNewPhiNodes(target, idom);
+    }
   }
 }
 
 void MergeReturnPass::CreatePhiNodesForInst(BasicBlock* merge_block,
-                                            uint32_t predecessor,
                                             Instruction& inst) {
   DominatorAnalysis* dom_tree =
       context()->GetDominatorAnalysis(merge_block->GetParent());
@@ -275,22 +284,22 @@
 
     // There is at least one values that needs to be replaced.
     // First create the OpPhi instruction.
-    InstructionBuilder builder(context(), &*merge_block->begin(),
-                               IRContext::kAnalysisDefUse);
+    InstructionBuilder builder(
+        context(), &*merge_block->begin(),
+        IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
     uint32_t undef_id = Type2Undef(inst.type_id());
     std::vector<uint32_t> phi_operands;
 
-    // Add the operands for the defining instructions.
-    phi_operands.push_back(inst.result_id());
-    phi_operands.push_back(predecessor);
-
-    // Add undef from all other blocks.
+    // Add the OpPhi operands. If the predecessor is a return block use undef,
+    // otherwise use |inst|'s id.
     std::vector<uint32_t> preds = cfg()->preds(merge_block->id());
     for (uint32_t pred_id : preds) {
-      if (pred_id != predecessor) {
+      if (return_blocks_.count(pred_id)) {
         phi_operands.push_back(undef_id);
-        phi_operands.push_back(pred_id);
+      } else {
+        phi_operands.push_back(inst.result_id());
       }
+      phi_operands.push_back(pred_id);
     }
 
     Instruction* new_phi = builder.AddPhi(inst.type_id(), phi_operands);
@@ -342,25 +351,28 @@
   while (block != nullptr && block != final_return_block_) {
     if (!predicated->insert(block).second) break;
     // Skip structured subgraphs.
-    BasicBlock* next = nullptr;
     assert(state->InLoop() && "Should be in the dummy loop at the very least.");
-    next = context()->get_instr_block(state->LoopMergeId());
-    while (state->LoopMergeId() == next->id()) {
+    Instruction* current_loop_merge_inst = state->LoopMergeInst();
+    uint32_t merge_block_id =
+        current_loop_merge_inst->GetSingleWordInOperand(0);
+    while (state->LoopMergeId() == merge_block_id) {
       state++;
     }
-    if (!BreakFromConstruct(block, next, predicated, order)) {
+    if (!BreakFromConstruct(block, predicated, order,
+                            current_loop_merge_inst)) {
       return false;
     }
-    block = next;
+    block = context()->get_instr_block(merge_block_id);
   }
   return true;
 }
 
 bool MergeReturnPass::BreakFromConstruct(
-    BasicBlock* block, BasicBlock* merge_block,
-    std::unordered_set<BasicBlock*>* predicated,
-    std::list<BasicBlock*>* order) {
-  // Make sure the cfg is build here.  If we don't then it becomes very hard
+    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.");
+  // 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);
 
@@ -380,6 +392,9 @@
       return false;
     }
   }
+
+  uint32_t merge_block_id = loop_merge_inst->GetSingleWordInOperand(0);
+  BasicBlock* merge_block = context()->get_instr_block(merge_block_id);
   if (merge_block->GetLoopMergeInst()) {
     cfg()->SplitLoopHeader(merge_block);
   }
@@ -393,8 +408,21 @@
   // Forget about the edges leaving block.  They will be removed.
   cfg()->RemoveSuccessorEdges(block);
 
-  BasicBlock* old_body = block->SplitBasicBlock(context(), TakeNextId(), iter);
+  auto old_body_id = TakeNextId();
+  BasicBlock* old_body = block->SplitBasicBlock(context(), old_body_id, iter);
   predicated->insert(old_body);
+  // If a return block is being split, mark the new body block also as a return
+  // block.
+  if (return_blocks_.count(block->id())) {
+    return_blocks_.insert(old_body_id);
+  }
+
+  // If |block| was a continue target for a loop |old_body| is now the correct
+  // continue target.
+  if (loop_merge_inst->GetSingleWordInOperand(1) == block->id()) {
+    loop_merge_inst->SetInOperand(1, {old_body->id()});
+    context()->UpdateDefUse(loop_merge_inst);
+  }
 
   // Update |order| so old_block will be traversed.
   InsertAfterElement(block, old_body, order);
@@ -403,6 +431,7 @@
   // 1. Load of the return status flag
   // 2. Branch to |merge_block| (true) or old body (false)
   // 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.
@@ -421,10 +450,6 @@
   builder.AddConditionalBranch(load_id, merge_block->id(), old_body->id(),
                                old_body->id());
 
-  // Update the cfg
-  cfg()->AddEdges(block);
-  cfg()->RegisterBlock(old_body);
-
   // 3. Update OpPhi instructions in |merge_block|.
   BasicBlock* merge_original_pred = MarkedSinglePred(merge_block);
   if (merge_original_pred == nullptr) {
@@ -433,6 +458,12 @@
     MarkForNewPhiNodes(merge_block, old_body);
   }
 
+  // 4. Update the CFG.  We do this after updating the OpPhi instructions
+  // because |UpdatePhiNodes| assumes the edge from |block| has not been added
+  // to the CFG yet.
+  cfg()->AddEdges(block);
+  cfg()->RegisterBlock(old_body);
+
   assert(old_body->begin() != old_body->end());
   assert(block->begin() != block->end());
   return true;
@@ -643,7 +674,7 @@
   BasicBlock* current_bb = pred;
   while (current_bb != nullptr && current_bb->id() != header_id) {
     for (Instruction& inst : *current_bb) {
-      CreatePhiNodesForInst(bb, pred->id(), inst);
+      CreatePhiNodesForInst(bb, inst);
     }
     current_bb = dom_tree->ImmediateDominator(current_bb);
   }
diff --git a/source/opt/merge_return_pass.h b/source/opt/merge_return_pass.h
index 264e8b7..63094b7 100644
--- a/source/opt/merge_return_pass.h
+++ b/source/opt/merge_return_pass.h
@@ -104,7 +104,7 @@
   Status Process() override;
 
   IRContext::Analysis GetPreservedAnalyses() override {
-    return IRContext::kAnalysisNone;
+    return IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
   }
 
  private:
@@ -219,16 +219,18 @@
                        std::list<BasicBlock*>* order);
 
   // Add a conditional branch at the start of |block| that either jumps to
-  // |merge_block| or the original code in |block| depending on the value in
-  // |return_flag_|.
+  // the merge block of |loop_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.
   //
   // If new blocks that are created will be added to |order|.  This way a call
   // can traverse these new block in structured order.
   //
   // Returns true if successful.
-  bool BreakFromConstruct(BasicBlock* block, BasicBlock* merge_block,
+  bool BreakFromConstruct(BasicBlock* block,
                           std::unordered_set<BasicBlock*>* predicated,
-                          std::list<BasicBlock*>* order);
+                          std::list<BasicBlock*>* order,
+                          Instruction* loop_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_|.
@@ -238,12 +240,11 @@
   // return block at the end of the pass.
   void CreateReturnBlock();
 
-  // Creates a Phi node in |merge_block| for the result of |inst| coming from
-  // |predecessor|.  Any uses of the result of |inst| that are no longer
+  // Creates a Phi node in |merge_block| for the result of |inst|.
+  // Any uses of the result of |inst| that are no longer
   // dominated by |inst|, are replaced with the result of the new |OpPhi|
   // instruction.
-  void CreatePhiNodesForInst(BasicBlock* merge_block, uint32_t predecessor,
-                             Instruction& inst);
+  void CreatePhiNodesForInst(BasicBlock* merge_block, Instruction& inst);
 
   // Traverse the nodes in |new_merge_nodes_|, and adds the OpPhi instructions
   // that are needed to make the code correct.  It is assumed that at this point
@@ -275,6 +276,8 @@
   // new edge from |new_source|.  The value for that edge will be an Undef. If
   // |target| only had a single predecessor, then it is marked as needing new
   // phi nodes.  See |MarkForNewPhiNodes|.
+  //
+  // The CFG must not include the edge from |new_source| to |target| yet.
   void UpdatePhiNodes(BasicBlock* new_source, BasicBlock* target);
 
   StructuredControlState& CurrentState() { return state_.back(); }
@@ -327,6 +330,11 @@
   // values that will need a phi on the new edges.
   std::unordered_map<BasicBlock*, BasicBlock*> new_merge_nodes_;
   bool HasNontrivialUnreachableBlocks(Function* function);
+
+  // Contains all return blocks that are merged. This is set is populated while
+  // processing structured blocks and used to properly construct OpPhi
+  // instructions.
+  std::unordered_set<uint32_t> return_blocks_;
 };
 
 }  // namespace opt
diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp
index 856ede7..34ddedc 100644
--- a/source/opt/optimizer.cpp
+++ b/source/opt/optimizer.cpp
@@ -56,8 +56,8 @@
 struct Optimizer::Impl {
   explicit Impl(spv_target_env env) : target_env(env), pass_manager() {}
 
-  spv_target_env target_env;        // Target environment.
-  opt::PassManager pass_manager;    // Internal implementation pass manager.
+  spv_target_env target_env;      // Target environment.
+  opt::PassManager pass_manager;  // Internal implementation pass manager.
 };
 
 Optimizer::Optimizer(spv_target_env env) : impl_(new Impl(env)) {}
@@ -115,6 +115,10 @@
           // Make private variable function scope
           .RegisterPass(CreateEliminateDeadFunctionsPass())
           .RegisterPass(CreatePrivateToLocalPass())
+          // Fix up the storage classes that DXC may have purposely generated
+          // incorrectly.  All functions are inlined, and a lot of dead code has
+          // been removed.
+          .RegisterPass(CreateFixStorageClassPass())
           // Propagate the value stored to the loads in very simple cases.
           .RegisterPass(CreateLocalSingleBlockLoadStoreElimPass())
           .RegisterPass(CreateLocalSingleStoreElimPass())
@@ -216,6 +220,21 @@
       .RegisterPass(CreateAggressiveDCEPass());
 }
 
+Optimizer& Optimizer::RegisterVulkanToWebGPUPasses() {
+  return RegisterPass(CreateStripDebugInfoPass())
+      .RegisterPass(CreateStripAtomicCounterMemoryPass())
+      .RegisterPass(CreateGenerateWebGPUInitializersPass())
+      .RegisterPass(CreateLegalizeVectorShufflePass())
+      .RegisterPass(CreateEliminateDeadConstantPass())
+      .RegisterPass(CreateFlattenDecorationPass())
+      .RegisterPass(CreateAggressiveDCEPass())
+      .RegisterPass(CreateDeadBranchElimPass());
+}
+
+Optimizer& Optimizer::RegisterWebGPUToVulkanPasses() {
+  return RegisterPass(CreateDecomposeInitializedVariablesPass());
+}
+
 bool Optimizer::RegisterPassesFromFlags(const std::vector<std::string>& flags) {
   for (const auto& flag : flags) {
     if (!RegisterPassFromFlag(flag)) {
@@ -258,7 +277,9 @@
   //
   // Both Pass::name() and Pass::desc() should be static class members so they
   // can be invoked without creating a pass instance.
-  if (pass_name == "strip-debug") {
+  if (pass_name == "strip-atomic-counter-memory") {
+    RegisterPass(CreateStripAtomicCounterMemoryPass());
+  } else if (pass_name == "strip-debug") {
     RegisterPass(CreateStripDebugInfoPass());
   } else if (pass_name == "strip-reflect") {
     RegisterPass(CreateStripReflectInfoPass());
@@ -324,6 +345,8 @@
     RegisterPass(CreateDeadInsertElimPass());
   } else if (pass_name == "eliminate-dead-variables") {
     RegisterPass(CreateDeadVariableEliminationPass());
+  } else if (pass_name == "eliminate-dead-members") {
+    RegisterPass(CreateEliminateDeadMembersPass());
   } else if (pass_name == "fold-spec-const-op-composite") {
     RegisterPass(CreateFoldSpecConstantOpAndCompositePass());
   } else if (pass_name == "loop-unswitch") {
@@ -373,7 +396,7 @@
   } else if (pass_name == "replace-invalid-opcode") {
     RegisterPass(CreateReplaceInvalidOpcodePass());
   } else if (pass_name == "inst-bindless-check") {
-    RegisterPass(CreateInstBindlessCheckPass(7, 23));
+    RegisterPass(CreateInstBindlessCheckPass(7, 23, true, true));
     RegisterPass(CreateSimplificationPass());
     RegisterPass(CreateDeadBranchElimPass());
     RegisterPass(CreateBlockMergePass());
@@ -434,12 +457,20 @@
     }
   } else if (pass_name == "ccp") {
     RegisterPass(CreateCCPPass());
+  } else if (pass_name == "code-sink") {
+    RegisterPass(CreateCodeSinkingPass());
+  } else if (pass_name == "fix-storage-class") {
+    RegisterPass(CreateFixStorageClassPass());
   } else if (pass_name == "O") {
     RegisterPerformancePasses();
   } else if (pass_name == "Os") {
     RegisterSizePasses();
   } else if (pass_name == "legalize-hlsl") {
     RegisterLegalizationPasses();
+  } else if (pass_name == "generate-webgpu-initializers") {
+    RegisterPass(CreateGenerateWebGPUInitializersPass());
+  } else if (pass_name == "legalize-vector-shuffle") {
+    RegisterPass(CreateLegalizeVectorShufflePass());
   } else {
     Errorf(consumer(), nullptr, {},
            "Unknown flag '--%s'. Use --help for a list of valid flags",
@@ -491,11 +522,25 @@
 
   context->set_max_id_bound(opt_options->max_id_bound_);
 
+  impl_->pass_manager.SetValidatorOptions(&opt_options->val_options_);
+  impl_->pass_manager.SetTargetEnv(impl_->target_env);
   auto status = impl_->pass_manager.Run(context.get());
-  if (status == opt::Pass::Status::SuccessWithChange ||
-      (status == opt::Pass::Status::SuccessWithoutChange &&
-       (optimized_binary->data() != original_binary ||
-        optimized_binary->size() != original_binary_size))) {
+
+  bool binary_changed = false;
+  if (status == opt::Pass::Status::SuccessWithChange) {
+    binary_changed = true;
+  } else if (status == opt::Pass::Status::SuccessWithoutChange) {
+    if (optimized_binary->size() != original_binary_size ||
+        (memcmp(optimized_binary->data(), original_binary,
+                original_binary_size) != 0)) {
+      binary_changed = true;
+      Log(consumer(), SPV_MSG_WARNING, nullptr, {},
+          "Binary unexpectedly changed despite optimizer saying there was no "
+          "change");
+    }
+  }
+
+  if (binary_changed) {
     optimized_binary->clear();
     context->module()->ToBinary(optimized_binary, /* skip_nop = */ true);
   }
@@ -513,10 +558,20 @@
   return *this;
 }
 
+Optimizer& Optimizer::SetValidateAfterAll(bool validate) {
+  impl_->pass_manager.SetValidateAfterAll(validate);
+  return *this;
+}
+
 Optimizer::PassToken CreateNullPass() {
   return MakeUnique<Optimizer::PassToken::Impl>(MakeUnique<opt::NullPass>());
 }
 
+Optimizer::PassToken CreateStripAtomicCounterMemoryPass() {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::StripAtomicCounterMemoryPass>());
+}
+
 Optimizer::PassToken CreateStripDebugInfoPass() {
   return MakeUnique<Optimizer::PassToken::Impl>(
       MakeUnique<opt::StripDebugInfoPass>());
@@ -532,6 +587,11 @@
       MakeUnique<opt::EliminateDeadFunctionsPass>());
 }
 
+Optimizer::PassToken CreateEliminateDeadMembersPass() {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::EliminateDeadMembersPass>());
+}
+
 Optimizer::PassToken CreateSetSpecConstantDefaultValuePass(
     const std::unordered_map<uint32_t, std::string>& id_value_map) {
   return MakeUnique<Optimizer::PassToken::Impl>(
@@ -780,9 +840,37 @@
 }
 
 Optimizer::PassToken CreateInstBindlessCheckPass(uint32_t desc_set,
-                                                 uint32_t shader_id) {
+                                                 uint32_t shader_id,
+                                                 bool input_length_enable,
+                                                 bool input_init_enable) {
   return MakeUnique<Optimizer::PassToken::Impl>(
-      MakeUnique<opt::InstBindlessCheckPass>(desc_set, shader_id));
+      MakeUnique<opt::InstBindlessCheckPass>(
+          desc_set, shader_id, input_length_enable, input_init_enable));
+}
+
+Optimizer::PassToken CreateCodeSinkingPass() {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::CodeSinkingPass>());
+}
+
+Optimizer::PassToken CreateGenerateWebGPUInitializersPass() {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::GenerateWebGPUInitializersPass>());
+}
+
+Optimizer::PassToken CreateFixStorageClassPass() {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::FixStorageClass>());
+}
+
+Optimizer::PassToken CreateLegalizeVectorShufflePass() {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::LegalizeVectorShufflePass>());
+}
+
+Optimizer::PassToken CreateDecomposeInitializedVariablesPass() {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::DecomposeInitializedVariablesPass>());
 }
 
 }  // namespace spvtools
diff --git a/source/opt/pass.cpp b/source/opt/pass.cpp
index edcd245..80075db 100644
--- a/source/opt/pass.cpp
+++ b/source/opt/pass.cpp
@@ -16,6 +16,7 @@
 
 #include "source/opt/pass.h"
 
+#include "source/opt/ir_builder.h"
 #include "source/opt/iterator.h"
 
 namespace spvtools {
@@ -52,5 +53,72 @@
   return ptrTypeInst->GetSingleWordInOperand(kTypePointerTypeIdInIdx);
 }
 
+uint32_t Pass::GenerateCopy(Instruction* object_inst, uint32_t new_type_id,
+                            Instruction* insertion_position) {
+  analysis::TypeManager* type_mgr = context()->get_type_mgr();
+  analysis::ConstantManager* const_mgr = context()->get_constant_mgr();
+
+  uint32_t original_type_id = object_inst->type_id();
+  if (original_type_id == new_type_id) {
+    return object_inst->result_id();
+  }
+
+  InstructionBuilder ir_builder(
+      context(), insertion_position,
+      IRContext::kAnalysisInstrToBlockMapping | IRContext::kAnalysisDefUse);
+
+  analysis::Type* original_type = type_mgr->GetType(original_type_id);
+  analysis::Type* new_type = type_mgr->GetType(new_type_id);
+
+  if (const analysis::Array* original_array_type = original_type->AsArray()) {
+    uint32_t original_element_type_id =
+        type_mgr->GetId(original_array_type->element_type());
+
+    analysis::Array* new_array_type = new_type->AsArray();
+    assert(new_array_type != nullptr && "Can't copy an array to a non-array.");
+    uint32_t new_element_type_id =
+        type_mgr->GetId(new_array_type->element_type());
+
+    std::vector<uint32_t> element_ids;
+    const analysis::Constant* length_const =
+        const_mgr->FindDeclaredConstant(original_array_type->LengthId());
+    assert(length_const->AsIntConstant());
+    uint32_t array_length = length_const->AsIntConstant()->GetU32();
+    for (uint32_t i = 0; i < array_length; i++) {
+      Instruction* extract = ir_builder.AddCompositeExtract(
+          original_element_type_id, object_inst->result_id(), {i});
+      element_ids.push_back(
+          GenerateCopy(extract, new_element_type_id, insertion_position));
+    }
+
+    return ir_builder.AddCompositeConstruct(new_type_id, element_ids)
+        ->result_id();
+  } else if (const analysis::Struct* original_struct_type =
+                 original_type->AsStruct()) {
+    analysis::Struct* new_struct_type = new_type->AsStruct();
+
+    const std::vector<const analysis::Type*>& original_types =
+        original_struct_type->element_types();
+    const std::vector<const analysis::Type*>& new_types =
+        new_struct_type->element_types();
+    std::vector<uint32_t> element_ids;
+    for (uint32_t i = 0; i < original_types.size(); i++) {
+      Instruction* extract = ir_builder.AddCompositeExtract(
+          type_mgr->GetId(original_types[i]), object_inst->result_id(), {i});
+      element_ids.push_back(GenerateCopy(extract, type_mgr->GetId(new_types[i]),
+                                         insertion_position));
+    }
+    return ir_builder.AddCompositeConstruct(new_type_id, element_ids)
+        ->result_id();
+  } else {
+    // If we do not have an aggregate type, then we have a problem.  Either we
+    // found multiple instances of the same type, or we are copying to an
+    // incompatible type.  Either way the code is illegal.
+    assert(false &&
+           "Don't know how to copy this type.  Code is likely illegal.");
+  }
+  return 0;
+}
+
 }  // namespace opt
 }  // namespace spvtools
diff --git a/source/opt/pass.h b/source/opt/pass.h
index c95f502..0667c3d 100644
--- a/source/opt/pass.h
+++ b/source/opt/pass.h
@@ -125,6 +125,12 @@
   // TODO(1841): Handle id overflow.
   uint32_t TakeNextId() { return context_->TakeNextId(); }
 
+  // Returns the id whose value is the same as |object_to_copy| except its type
+  // is |new_type_id|.  Any instructions needed to generate this value will be
+  // inserted before |insertion_position|.
+  uint32_t GenerateCopy(Instruction* object_to_copy, uint32_t new_type_id,
+                        Instruction* insertion_position);
+
  private:
   MessageConsumer consumer_;  // Message consumer.
 
diff --git a/source/opt/pass_manager.cpp b/source/opt/pass_manager.cpp
index fa1e1d8..be53d34 100644
--- a/source/opt/pass_manager.cpp
+++ b/source/opt/pass_manager.cpp
@@ -51,6 +51,20 @@
     if (one_status == Pass::Status::Failure) return one_status;
     if (one_status == Pass::Status::SuccessWithChange) status = one_status;
 
+    if (validate_after_all_) {
+      spvtools::SpirvTools tools(target_env_);
+      tools.SetMessageConsumer(consumer());
+      std::vector<uint32_t> binary;
+      context->module()->ToBinary(&binary, true);
+      if (!tools.Validate(binary.data(), binary.size(), val_options_)) {
+        std::string msg = "Validation failed after pass ";
+        msg += pass->name();
+        spv_position_t null_pos{0, 0, 0};
+        consumer()(SPV_MSG_INTERNAL_ERROR, "", null_pos, msg.c_str());
+        return Pass::Status::Failure;
+      }
+    }
+
     // Reset the pass to free any memory used by the pass.
     pass.reset(nullptr);
   }
diff --git a/source/opt/pass_manager.h b/source/opt/pass_manager.h
index ed88aa1..9686ddd 100644
--- a/source/opt/pass_manager.h
+++ b/source/opt/pass_manager.h
@@ -43,7 +43,10 @@
   PassManager()
       : consumer_(nullptr),
         print_all_stream_(nullptr),
-        time_report_stream_(nullptr) {}
+        time_report_stream_(nullptr),
+        target_env_(SPV_ENV_UNIVERSAL_1_2),
+        val_options_(nullptr),
+        validate_after_all_(false) {}
 
   // Sets the message consumer to the given |consumer|.
   void SetMessageConsumer(MessageConsumer c) { consumer_ = std::move(c); }
@@ -89,6 +92,24 @@
     return *this;
   }
 
+  // Sets the target environment for validation.
+  PassManager& SetTargetEnv(spv_target_env env) {
+    target_env_ = env;
+    return *this;
+  }
+
+  // Sets the validation options.
+  PassManager& SetValidatorOptions(spv_validator_options options) {
+    val_options_ = options;
+    return *this;
+  }
+
+  // Sets the option to validate after each pass.
+  PassManager& SetValidateAfterAll(bool validate) {
+    validate_after_all_ = validate;
+    return *this;
+  }
+
  private:
   // Consumer for messages.
   MessageConsumer consumer_;
@@ -100,6 +121,12 @@
   // The output stream to write the resource utilization of each pass. If this
   // is null, no output is generated.
   std::ostream* time_report_stream_;
+  // The target environment.
+  spv_target_env target_env_;
+  // The validator options (used when validating each pass).
+  spv_validator_options val_options_;
+  // Controls whether validation occurs after every pass.
+  bool validate_after_all_;
 };
 
 inline void PassManager::AddPass(std::unique_ptr<Pass> pass) {
diff --git a/source/opt/passes.h b/source/opt/passes.h
index b9f30ae..06464b0 100644
--- a/source/opt/passes.h
+++ b/source/opt/passes.h
@@ -21,6 +21,7 @@
 #include "source/opt/block_merge_pass.h"
 #include "source/opt/ccp_pass.h"
 #include "source/opt/cfg_cleanup_pass.h"
+#include "source/opt/code_sink.h"
 #include "source/opt/combine_access_chains.h"
 #include "source/opt/common_uniform_elim_pass.h"
 #include "source/opt/compact_ids_pass.h"
@@ -28,15 +29,20 @@
 #include "source/opt/dead_branch_elim_pass.h"
 #include "source/opt/dead_insert_elim_pass.h"
 #include "source/opt/dead_variable_elimination.h"
+#include "source/opt/decompose_initialized_variables_pass.h"
 #include "source/opt/eliminate_dead_constant_pass.h"
 #include "source/opt/eliminate_dead_functions_pass.h"
+#include "source/opt/eliminate_dead_members_pass.h"
+#include "source/opt/fix_storage_class.h"
 #include "source/opt/flatten_decoration_pass.h"
 #include "source/opt/fold_spec_constant_op_and_composite_pass.h"
 #include "source/opt/freeze_spec_constant_value_pass.h"
+#include "source/opt/generate_webgpu_initializers_pass.h"
 #include "source/opt/if_conversion.h"
 #include "source/opt/inline_exhaustive_pass.h"
 #include "source/opt/inline_opaque_pass.h"
 #include "source/opt/inst_bindless_check_pass.h"
+#include "source/opt/legalize_vector_shuffle_pass.h"
 #include "source/opt/licm_pass.h"
 #include "source/opt/local_access_chain_convert_pass.h"
 #include "source/opt/local_redundancy_elimination.h"
@@ -61,6 +67,7 @@
 #include "source/opt/simplification_pass.h"
 #include "source/opt/ssa_rewrite_pass.h"
 #include "source/opt/strength_reduction_pass.h"
+#include "source/opt/strip_atomic_counter_memory_pass.h"
 #include "source/opt/strip_debug_info_pass.h"
 #include "source/opt/strip_reflect_info_pass.h"
 #include "source/opt/unify_const_pass.h"
diff --git a/source/opt/private_to_local_pass.cpp b/source/opt/private_to_local_pass.cpp
index 02909a7..d41d8f2 100644
--- a/source/opt/private_to_local_pass.cpp
+++ b/source/opt/private_to_local_pass.cpp
@@ -19,6 +19,7 @@
 #include <vector>
 
 #include "source/opt/ir_context.h"
+#include "source/spirv_constant.h"
 
 namespace spvtools {
 namespace opt {
@@ -38,6 +39,7 @@
     return Status::SuccessWithoutChange;
 
   std::vector<std::pair<Instruction*, Function*>> variables_to_move;
+  std::unordered_set<uint32_t> localized_variables;
   for (auto& inst : context()->types_values()) {
     if (inst.opcode() != SpvOpVariable) {
       continue;
@@ -57,6 +59,27 @@
   modified = !variables_to_move.empty();
   for (auto p : variables_to_move) {
     MoveVariable(p.first, p.second);
+    localized_variables.insert(p.first->result_id());
+  }
+
+  if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4)) {
+    // In SPIR-V 1.4 and later entry points must list private storage class
+    // variables that are statically used by the entry point. Go through the
+    // entry points and remove any references to variables that were localized.
+    for (auto& entry : get_module()->entry_points()) {
+      std::vector<Operand> new_operands;
+      for (uint32_t i = 0; i < entry.NumInOperands(); ++i) {
+        // Execution model, function id and name are always kept.
+        if (i < 3 ||
+            !localized_variables.count(entry.GetSingleWordInOperand(i))) {
+          new_operands.push_back(entry.GetInOperand(i));
+        }
+      }
+      if (new_operands.size() != entry.NumInOperands()) {
+        entry.SetInOperands(std::move(new_operands));
+        context()->AnalyzeUses(&entry);
+      }
+    }
   }
 
   return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange);
@@ -165,6 +188,7 @@
       UpdateUses(inst->result_id());
       break;
     case SpvOpName:
+    case SpvOpEntryPoint:  // entry points will be updated separately.
       break;
     default:
       assert(spvOpcodeIsDecoration(inst->opcode()) &&
diff --git a/source/opt/private_to_local_pass.h b/source/opt/private_to_local_pass.h
index f706e6e..4678530 100644
--- a/source/opt/private_to_local_pass.h
+++ b/source/opt/private_to_local_pass.h
@@ -35,7 +35,8 @@
            IRContext::kAnalysisInstrToBlockMapping |
            IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
            IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
-           IRContext::kAnalysisNameMap;
+           IRContext::kAnalysisNameMap | IRContext::kAnalysisConstants |
+           IRContext::kAnalysisTypes;
   }
 
  private:
diff --git a/source/opt/process_lines_pass.h b/source/opt/process_lines_pass.h
index 91b2136..c988bfd 100644
--- a/source/opt/process_lines_pass.h
+++ b/source/opt/process_lines_pass.h
@@ -51,7 +51,8 @@
            IRContext::kAnalysisInstrToBlockMapping |
            IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
            IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
-           IRContext::kAnalysisNameMap;
+           IRContext::kAnalysisNameMap | IRContext::kAnalysisConstants |
+           IRContext::kAnalysisTypes;
   }
 
  private:
diff --git a/source/opt/reduce_load_size.cpp b/source/opt/reduce_load_size.cpp
index b692c6b..7b5a015 100644
--- a/source/opt/reduce_load_size.cpp
+++ b/source/opt/reduce_load_size.cpp
@@ -139,7 +139,8 @@
 
   all_elements_used =
       !def_use_mgr->WhileEachUser(op_inst, [&elements_used](Instruction* use) {
-        if (use->opcode() != SpvOpCompositeExtract) {
+        if (use->opcode() != SpvOpCompositeExtract ||
+            use->NumInOperands() == 1) {
           return false;
         }
         elements_used.insert(use->GetSingleWordInOperand(1));
diff --git a/source/opt/reduce_load_size.h b/source/opt/reduce_load_size.h
index 724a430..ccac49b 100644
--- a/source/opt/reduce_load_size.h
+++ b/source/opt/reduce_load_size.h
@@ -36,7 +36,8 @@
            IRContext::kAnalysisInstrToBlockMapping |
            IRContext::kAnalysisCombinators | IRContext::kAnalysisCFG |
            IRContext::kAnalysisDominatorAnalysis |
-           IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap;
+           IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap |
+           IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
   }
 
  private:
diff --git a/source/opt/reflect.h b/source/opt/reflect.h
index 79d90bd..8106442 100644
--- a/source/opt/reflect.h
+++ b/source/opt/reflect.h
@@ -45,7 +45,8 @@
 inline bool IsTypeInst(SpvOp opcode) {
   return (opcode >= SpvOpTypeVoid && opcode <= SpvOpTypeForwardPointer) ||
          opcode == SpvOpTypePipeStorage || opcode == SpvOpTypeNamedBarrier ||
-         opcode == SpvOpTypeAccelerationStructureNV;
+         opcode == SpvOpTypeAccelerationStructureNV ||
+         opcode == SpvOpTypeCooperativeMatrixNV;
 }
 inline bool IsConstantInst(SpvOp opcode) {
   return opcode >= SpvOpConstantTrue && opcode <= SpvOpSpecConstantOp;
diff --git a/source/opt/scalar_replacement_pass.cpp b/source/opt/scalar_replacement_pass.cpp
index 66a97bd..bfa8598 100644
--- a/source/opt/scalar_replacement_pass.cpp
+++ b/source/opt/scalar_replacement_pass.cpp
@@ -225,12 +225,12 @@
   // indexes) or a direct use of the replacement variable.
   uint32_t indexId = chain->GetSingleWordInOperand(1u);
   const Instruction* index = get_def_use_mgr()->GetDef(indexId);
-  size_t indexValue = GetConstantInteger(index);
+  uint64_t indexValue = GetConstantInteger(index);
   if (indexValue > replacements.size()) {
     // Out of bounds access, this is illegal IR.
     return false;
   } else {
-    const Instruction* var = replacements[indexValue];
+    const Instruction* var = replacements[static_cast<size_t>(indexValue)];
     if (chain->NumInOperands() > 2) {
       // Replace input access chain with another access chain.
       BasicBlock::iterator chainIter(chain);
@@ -363,7 +363,7 @@
       context()->get_type_mgr()->GetTypeAndPointerType(id,
                                                        SpvStorageClassFunction);
   uint32_t ptrId = 0;
-  if (id == context()->get_type_mgr()->GetId(pointeeTy)) {
+  if (pointeeTy->IsUniqueType()) {
     // Non-ambiguous type, just ask the type manager for an id.
     ptrId = context()->get_type_mgr()->GetTypeInstruction(pointerTy.get());
     pointee_to_pointer_[id] = ptrId;
@@ -457,16 +457,16 @@
   }
 }
 
-size_t ScalarReplacementPass::GetIntegerLiteral(const Operand& op) const {
+uint64_t ScalarReplacementPass::GetIntegerLiteral(const Operand& op) const {
   assert(op.words.size() <= 2);
-  size_t len = 0;
+  uint64_t len = 0;
   for (uint32_t i = 0; i != op.words.size(); ++i) {
     len |= (op.words[i] << (32 * i));
   }
   return len;
 }
 
-size_t ScalarReplacementPass::GetConstantInteger(
+uint64_t ScalarReplacementPass::GetConstantInteger(
     const Instruction* constant) const {
   assert(get_def_use_mgr()->GetDef(constant->type_id())->opcode() ==
          SpvOpTypeInt);
@@ -480,7 +480,7 @@
   return GetIntegerLiteral(op);
 }
 
-size_t ScalarReplacementPass::GetArrayLength(
+uint64_t ScalarReplacementPass::GetArrayLength(
     const Instruction* arrayType) const {
   assert(arrayType->opcode() == SpvOpTypeArray);
   const Instruction* length =
@@ -488,14 +488,14 @@
   return GetConstantInteger(length);
 }
 
-size_t ScalarReplacementPass::GetNumElements(const Instruction* type) const {
+uint64_t ScalarReplacementPass::GetNumElements(const Instruction* type) const {
   assert(type->opcode() == SpvOpTypeVector ||
          type->opcode() == SpvOpTypeMatrix);
   const Operand& op = type->GetInOperand(1u);
   assert(op.words.size() <= 2);
-  size_t len = 0;
-  for (uint32_t i = 0; i != op.words.size(); ++i) {
-    len |= (op.words[i] << (32 * i));
+  uint64_t len = 0;
+  for (size_t i = 0; i != op.words.size(); ++i) {
+    len |= (static_cast<uint64_t>(op.words[i]) << (32ull * i));
   }
   return len;
 }
@@ -638,8 +638,8 @@
           switch (user->opcode()) {
             case SpvOpAccessChain:
             case SpvOpInBoundsAccessChain:
-              if (index == 2u) {
-                uint32_t id = user->GetSingleWordOperand(3u);
+              if (index == 2u && user->NumInOperands() > 1) {
+                uint32_t id = user->GetSingleWordInOperand(1u);
                 const Instruction* opInst = get_def_use_mgr()->GetDef(id);
                 if (!IsCompileTimeConstantInst(opInst->opcode())) {
                   ok = false;
@@ -717,7 +717,7 @@
     return false;
   return true;
 }
-bool ScalarReplacementPass::IsLargerThanSizeLimit(size_t length) const {
+bool ScalarReplacementPass::IsLargerThanSizeLimit(uint64_t length) const {
   if (max_num_elements_ == 0) {
     return false;
   }
@@ -751,8 +751,10 @@
           return false;
         }
       }
+      case SpvOpName:
+      case SpvOpMemberName:
       case SpvOpStore:
-        // No components are used.  Things are just stored to.
+        // No components are used.
         return true;
       case SpvOpAccessChain:
       case SpvOpInBoundsAccessChain: {
@@ -781,16 +783,6 @@
           return false;
         }
       }
-      case SpvOpCopyObject: {
-        // Follow the copy to see which components are used.
-        auto t = GetUsedComponents(use);
-        if (!t) {
-          result.reset(nullptr);
-          return false;
-        }
-        result->insert(t->begin(), t->end());
-        return true;
-      }
       default:
         // We do not know what is happening.  Have to assume the worst.
         result.reset(nullptr);
diff --git a/source/opt/scalar_replacement_pass.h b/source/opt/scalar_replacement_pass.h
index a82abf4..0ff9947 100644
--- a/source/opt/scalar_replacement_pass.h
+++ b/source/opt/scalar_replacement_pass.h
@@ -52,7 +52,8 @@
     return IRContext::kAnalysisDefUse |
            IRContext::kAnalysisInstrToBlockMapping |
            IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
-           IRContext::kAnalysisCFG | IRContext::kAnalysisNameMap;
+           IRContext::kAnalysisCFG | IRContext::kAnalysisNameMap |
+           IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
   }
 
  private:
@@ -156,18 +157,18 @@
   // Returns the value of an OpConstant of integer type.
   //
   // |constant| must use two or fewer words to generate the value.
-  size_t GetConstantInteger(const Instruction* constant) const;
+  uint64_t GetConstantInteger(const Instruction* constant) const;
 
   // Returns the integer literal for |op|.
-  size_t GetIntegerLiteral(const Operand& op) const;
+  uint64_t GetIntegerLiteral(const Operand& op) const;
 
   // Returns the array length for |arrayInst|.
-  size_t GetArrayLength(const Instruction* arrayInst) const;
+  uint64_t GetArrayLength(const Instruction* arrayInst) const;
 
   // Returns the number of elements in |type|.
   //
   // |type| must be a vector or matrix type.
-  size_t GetNumElements(const Instruction* type) const;
+  uint64_t GetNumElements(const Instruction* type) const;
 
   // Returns true if |id| is a specialization constant.
   //
@@ -227,7 +228,7 @@
   // Limit on the number of members in an object that will be replaced.
   // 0 means there is no limit.
   uint32_t max_num_elements_;
-  bool IsLargerThanSizeLimit(size_t length) const;
+  bool IsLargerThanSizeLimit(uint64_t length) const;
   char name_[55];
 };
 
diff --git a/source/opt/simplification_pass.h b/source/opt/simplification_pass.h
index 348c96a..bcb88bf 100644
--- a/source/opt/simplification_pass.h
+++ b/source/opt/simplification_pass.h
@@ -33,7 +33,8 @@
            IRContext::kAnalysisInstrToBlockMapping |
            IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
            IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
-           IRContext::kAnalysisNameMap;
+           IRContext::kAnalysisNameMap | IRContext::kAnalysisConstants |
+           IRContext::kAnalysisTypes;
   }
 
  private:
diff --git a/source/opt/ssa_rewrite_pass.cpp b/source/opt/ssa_rewrite_pass.cpp
index 0a5d390..f2cb2da 100644
--- a/source/opt/ssa_rewrite_pass.cpp
+++ b/source/opt/ssa_rewrite_pass.cpp
@@ -435,12 +435,22 @@
         pass_->get_def_use_mgr()->GetDef(phi_candidate->var_id()));
     std::vector<Operand> phi_operands;
     uint32_t arg_ix = 0;
+    std::unordered_map<uint32_t, uint32_t> already_seen;
     for (uint32_t pred_label : pass_->cfg()->preds(phi_candidate->bb()->id())) {
       uint32_t op_val_id = GetPhiArgument(phi_candidate, arg_ix++);
-      phi_operands.push_back(
-          {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {op_val_id}});
-      phi_operands.push_back(
-          {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {pred_label}});
+      if (already_seen.count(pred_label) == 0) {
+        phi_operands.push_back(
+            {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {op_val_id}});
+        phi_operands.push_back(
+            {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {pred_label}});
+        already_seen[pred_label] = op_val_id;
+      } else {
+        // It is possible that there are two edges from the same parent block.
+        // Since the OpPhi can have only one entry for each parent, we have to
+        // make sure the two edges are consistent with each other.
+        assert(already_seen[pred_label] == op_val_id &&
+               "Inconsistent value for duplicate edges.");
+      }
     }
 
     // Generate a new OpPhi instruction and insert it in its basic
@@ -453,7 +463,6 @@
     pass_->context()->set_instr_block(&*phi_inst, phi_candidate->bb());
     auto insert_it = phi_candidate->bb()->begin();
     insert_it.InsertBefore(std::move(phi_inst));
-
     pass_->context()->get_decoration_mgr()->CloneDecorations(
         phi_candidate->var_id(), phi_candidate->result_id(),
         {SpvDecorationRelaxedPrecision});
diff --git a/source/opt/strip_atomic_counter_memory_pass.cpp b/source/opt/strip_atomic_counter_memory_pass.cpp
new file mode 100644
index 0000000..47714b7
--- /dev/null
+++ b/source/opt/strip_atomic_counter_memory_pass.cpp
@@ -0,0 +1,57 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/opt/strip_atomic_counter_memory_pass.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace opt {
+
+Pass::Status StripAtomicCounterMemoryPass::Process() {
+  bool changed = false;
+  context()->module()->ForEachInst([this, &changed](Instruction* inst) {
+    auto indices = spvOpcodeMemorySemanticsOperandIndices(inst->opcode());
+    if (indices.empty()) return;
+
+    for (auto idx : indices) {
+      auto mem_sem_id = inst->GetSingleWordOperand(idx);
+      const auto& mem_sem_inst =
+          context()->get_def_use_mgr()->GetDef(mem_sem_id);
+      // The spec explicitly says that this id must be an OpConstant
+      auto mem_sem_val = mem_sem_inst->GetSingleWordOperand(2);
+      if (!(mem_sem_val & SpvMemorySemanticsAtomicCounterMemoryMask)) {
+        continue;
+      }
+      mem_sem_val &= ~SpvMemorySemanticsAtomicCounterMemoryMask;
+
+      analysis::Integer int_type(32, false);
+      const analysis::Type* uint32_type =
+          context()->get_type_mgr()->GetRegisteredType(&int_type);
+      auto* new_const = context()->get_constant_mgr()->GetConstant(
+          uint32_type, {mem_sem_val});
+      auto* new_const_inst =
+          context()->get_constant_mgr()->GetDefiningInstruction(new_const);
+      auto new_const_id = new_const_inst->result_id();
+
+      inst->SetOperand(idx, {new_const_id});
+      context()->UpdateDefUse(inst);
+      changed = true;
+    }
+  });
+
+  return changed ? Status::SuccessWithChange : Status::SuccessWithoutChange;
+}
+
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/strip_atomic_counter_memory_pass.h b/source/opt/strip_atomic_counter_memory_pass.h
new file mode 100644
index 0000000..62e274a
--- /dev/null
+++ b/source/opt/strip_atomic_counter_memory_pass.h
@@ -0,0 +1,51 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_STRIP_ATOMIC_COUNT_MEMORY_PASS_H_
+#define SOURCE_OPT_STRIP_ATOMIC_COUNT_MEMORY_PASS_H_
+
+#include "source/opt/ir_context.h"
+#include "source/opt/module.h"
+#include "source/opt/pass.h"
+
+namespace spvtools {
+namespace opt {
+
+// Removes the AtomicCounterMemory bit from the value being passed into memory
+// semantics. This bit being set is ignored in Vulkan environments and
+// forbidden WebGPU ones.
+class StripAtomicCounterMemoryPass : public Pass {
+ public:
+  const char* name() const override { return "strip-atomic-counter-memory"; }
+  Status Process() override;
+
+  IRContext::Analysis GetPreservedAnalyses() override {
+    return IRContext::kAnalysisInstrToBlockMapping |
+           IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
+           IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
+           IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap |
+           IRContext::kAnalysisScalarEvolution |
+           IRContext::kAnalysisRegisterPressure |
+           IRContext::kAnalysisValueNumberTable |
+           IRContext::kAnalysisStructuredCFG |
+           IRContext::kAnalysisBuiltinVarId |
+           IRContext::kAnalysisIdToFuncMapping | IRContext::kAnalysisTypes |
+           IRContext::kAnalysisDefUse | IRContext::kAnalysisConstants;
+  }
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // SOURCE_OPT_STRIP_ATOMIC_COUNT_MEMORY_PASS_H_
diff --git a/source/opt/strip_reflect_info_pass.h b/source/opt/strip_reflect_info_pass.h
index 935a605..4e1999e 100644
--- a/source/opt/strip_reflect_info_pass.h
+++ b/source/opt/strip_reflect_info_pass.h
@@ -33,7 +33,8 @@
     return IRContext::kAnalysisInstrToBlockMapping |
            IRContext::kAnalysisCombinators | IRContext::kAnalysisCFG |
            IRContext::kAnalysisDominatorAnalysis |
-           IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap;
+           IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap |
+           IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
   }
 };
 
diff --git a/source/opt/struct_cfg_analysis.cpp b/source/opt/struct_cfg_analysis.cpp
index d78ec56..dcfd4a5 100644
--- a/source/opt/struct_cfg_analysis.cpp
+++ b/source/opt/struct_cfg_analysis.cpp
@@ -19,7 +19,7 @@
 namespace {
 const uint32_t kMergeNodeIndex = 0;
 const uint32_t kContinueNodeIndex = 1;
-}
+}  // namespace
 
 namespace spvtools {
 namespace opt {
@@ -37,6 +37,8 @@
 }
 
 void StructuredCFGAnalysis::AddBlocksInFunction(Function* func) {
+  if (func->begin() == func->end()) return;
+
   std::list<BasicBlock*> order;
   context_->cfg()->ComputeStructuredOrder(func, &*func->begin(), &order);
 
diff --git a/source/opt/type_manager.cpp b/source/opt/type_manager.cpp
index 722b9b9..001883c 100644
--- a/source/opt/type_manager.cpp
+++ b/source/opt/type_manager.cpp
@@ -744,6 +744,9 @@
     case SpvOpTypeNamedBarrier:
       type = new NamedBarrier();
       break;
+    case SpvOpTypeAccelerationStructureNV:
+      type = new AccelerationStructureNV();
+      break;
     default:
       SPIRV_UNIMPLEMENTED(consumer_, "unhandled type");
       break;
diff --git a/source/opt/types.h b/source/opt/types.h
index d77117d..fe0f39a 100644
--- a/source/opt/types.h
+++ b/source/opt/types.h
@@ -598,7 +598,7 @@
 DefineParameterlessType(Queue, queue);
 DefineParameterlessType(PipeStorage, pipe_storage);
 DefineParameterlessType(NamedBarrier, named_barrier);
-DefineParameterlessType(AccelerationStructureNV, acceleration_structure);
+DefineParameterlessType(AccelerationStructureNV, accelerationStructureNV);
 #undef DefineParameterlessType
 
 }  // namespace analysis
diff --git a/source/opt/upgrade_memory_model.cpp b/source/opt/upgrade_memory_model.cpp
index 8def55f..d8836a4 100644
--- a/source/opt/upgrade_memory_model.cpp
+++ b/source/opt/upgrade_memory_model.cpp
@@ -16,6 +16,7 @@
 
 #include <utility>
 
+#include "source/opt/ir_builder.h"
 #include "source/opt/ir_context.h"
 #include "source/util/make_unique.h"
 
@@ -68,6 +69,22 @@
   // instructions. Additionally, Workgroup storage class variables and function
   // parameters are implicitly coherent in GLSL450.
 
+  // Upgrade modf and frexp first since they generate new stores.
+  for (auto& func : *get_module()) {
+    func.ForEachInst([this](Instruction* inst) {
+      if (inst->opcode() == SpvOpExtInst) {
+        auto ext_inst = inst->GetSingleWordInOperand(1u);
+        if (ext_inst == GLSLstd450Modf || ext_inst == GLSLstd450Frexp) {
+          auto import =
+              get_def_use_mgr()->GetDef(inst->GetSingleWordInOperand(0u));
+          if (reinterpret_cast<char*>(import->GetInOperand(0u).words.data()) ==
+              std::string("GLSL.std.450")) {
+            UpgradeExtInst(inst);
+          }
+        }
+      }
+    });
+  }
   for (auto& func : *get_module()) {
     func.ForEachInst([this](Instruction* inst) {
       bool is_coherent = false;
@@ -104,11 +121,11 @@
 
       switch (inst->opcode()) {
         case SpvOpLoad:
-          UpgradeFlags(inst, 1u, is_coherent, is_volatile, kAvailability,
+          UpgradeFlags(inst, 1u, is_coherent, is_volatile, kVisibility,
                        kMemory);
           break;
         case SpvOpStore:
-          UpgradeFlags(inst, 2u, is_coherent, is_volatile, kVisibility,
+          UpgradeFlags(inst, 2u, is_coherent, is_volatile, kAvailability,
                        kMemory);
           break;
         case SpvOpCopyMemory:
@@ -125,11 +142,11 @@
           break;
         case SpvOpImageRead:
         case SpvOpImageSparseRead:
-          UpgradeFlags(inst, 2u, is_coherent, is_volatile, kAvailability,
-                       kImage);
+          UpgradeFlags(inst, 2u, is_coherent, is_volatile, kVisibility, kImage);
           break;
         case SpvOpImageWrite:
-          UpgradeFlags(inst, 3u, is_coherent, is_volatile, kVisibility, kImage);
+          UpgradeFlags(inst, 3u, is_coherent, is_volatile, kAvailability,
+                       kImage);
           break;
         default:
           break;
@@ -143,15 +160,15 @@
       }
       // According to SPV_KHR_vulkan_memory_model, if both available and
       // visible flags are used the first scope operand is for availability
-      // (reads) and the second is for visibiity (writes).
-      if (src_coherent) {
-        inst->AddOperand(
-            {SPV_OPERAND_TYPE_SCOPE_ID, {GetScopeConstant(src_scope)}});
-      }
+      // (writes) and the second is for visibility (reads).
       if (dst_coherent) {
         inst->AddOperand(
             {SPV_OPERAND_TYPE_SCOPE_ID, {GetScopeConstant(dst_scope)}});
       }
+      if (src_coherent) {
+        inst->AddOperand(
+            {SPV_OPERAND_TYPE_SCOPE_ID, {GetScopeConstant(src_scope)}});
+      }
     });
   }
 }
@@ -581,5 +598,43 @@
   return false;
 }
 
+void UpgradeMemoryModel::UpgradeExtInst(Instruction* ext_inst) {
+  const bool is_modf = ext_inst->GetSingleWordInOperand(1u) == GLSLstd450Modf;
+  auto ptr_id = ext_inst->GetSingleWordInOperand(3u);
+  auto ptr_type_id = get_def_use_mgr()->GetDef(ptr_id)->type_id();
+  auto pointee_type_id =
+      get_def_use_mgr()->GetDef(ptr_type_id)->GetSingleWordInOperand(1u);
+  auto element_type_id = ext_inst->type_id();
+  std::vector<const analysis::Type*> element_types(2);
+  element_types[0] = context()->get_type_mgr()->GetType(element_type_id);
+  element_types[1] = context()->get_type_mgr()->GetType(pointee_type_id);
+  analysis::Struct struct_type(element_types);
+  uint32_t struct_id =
+      context()->get_type_mgr()->GetTypeInstruction(&struct_type);
+  // Change the operation
+  GLSLstd450 new_op = is_modf ? GLSLstd450ModfStruct : GLSLstd450FrexpStruct;
+  ext_inst->SetOperand(3u, {static_cast<uint32_t>(new_op)});
+  // Remove the pointer argument
+  ext_inst->RemoveOperand(5u);
+  // Set the type id to the new struct.
+  ext_inst->SetResultType(struct_id);
+
+  // The result is now a struct of the original result. The zero'th element is
+  // old result and should replace the old result. The one'th element needs to
+  // be stored via a new instruction.
+  auto where = ext_inst->NextNode();
+  InstructionBuilder builder(
+      context(), where,
+      IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
+  auto extract_0 =
+      builder.AddCompositeExtract(element_type_id, ext_inst->result_id(), {0});
+  context()->ReplaceAllUsesWith(ext_inst->result_id(), extract_0->result_id());
+  // The extract's input was just changed to itself, so fix that.
+  extract_0->SetInOperand(0u, {ext_inst->result_id()});
+  auto extract_1 =
+      builder.AddCompositeExtract(pointee_type_id, ext_inst->result_id(), {1});
+  builder.AddStore(ptr_id, extract_1->result_id());
+}
+
 }  // namespace opt
 }  // namespace spvtools
diff --git a/source/opt/upgrade_memory_model.h b/source/opt/upgrade_memory_model.h
index 29c801c..9adc33b 100644
--- a/source/opt/upgrade_memory_model.h
+++ b/source/opt/upgrade_memory_model.h
@@ -118,6 +118,11 @@
   // Returns true if |scope_id| is SpvScopeDevice.
   bool IsDeviceScope(uint32_t scope_id);
 
+  // Upgrades GLSL.std.450 modf and frexp. Both instructions are replaced with
+  // their struct versions. New extracts and a store are added in order to
+  // facilitate adding memory model flags.
+  void UpgradeExtInst(Instruction* modf);
+
   // Caches the result of TraceInstruction. For a given result id and set of
   // indices, stores whether that combination is coherent and/or volatile.
   std::unordered_map<std::pair<uint32_t, std::vector<uint32_t>>,
diff --git a/source/opt/vector_dce.cpp b/source/opt/vector_dce.cpp
index 911242e..92532e3 100644
--- a/source/opt/vector_dce.cpp
+++ b/source/opt/vector_dce.cpp
@@ -66,7 +66,8 @@
 
     switch (current_inst->opcode()) {
       case SpvOpCompositeExtract:
-        MarkExtractUseAsLive(current_inst, live_components, &work_list);
+        MarkExtractUseAsLive(current_inst, current_item.components,
+                             live_components, &work_list);
         break;
       case SpvOpCompositeInsert:
         MarkInsertUsesAsLive(current_item, live_components, &work_list);
@@ -92,6 +93,7 @@
 }
 
 void VectorDCE::MarkExtractUseAsLive(const Instruction* current_inst,
+                                     const utils::BitVector& live_elements,
                                      LiveComponentMap* live_components,
                                      std::vector<WorkListItem>* work_list) {
   analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
@@ -102,7 +104,11 @@
   if (HasVectorOrScalarResult(operand_inst)) {
     WorkListItem new_item;
     new_item.instruction = operand_inst;
-    new_item.components.Set(current_inst->GetSingleWordInOperand(1));
+    if (current_inst->NumInOperands() < 2) {
+      new_item.components = live_elements;
+    } else {
+      new_item.components.Set(current_inst->GetSingleWordInOperand(1));
+    }
     AddItemToWorkListIfNeeded(new_item, live_components, work_list);
   }
 }
@@ -113,30 +119,44 @@
     std::vector<VectorDCE::WorkListItem>* work_list) {
   analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
 
-  uint32_t insert_position =
-      current_item.instruction->GetSingleWordInOperand(2);
+  if (current_item.instruction->NumInOperands() > 2) {
+    uint32_t insert_position =
+        current_item.instruction->GetSingleWordInOperand(2);
 
-  // Add the elements of the composite object that are used.
-  uint32_t operand_id =
-      current_item.instruction->GetSingleWordInOperand(kInsertCompositeIdInIdx);
-  Instruction* operand_inst = def_use_mgr->GetDef(operand_id);
+    // Add the elements of the composite object that are used.
+    uint32_t operand_id = current_item.instruction->GetSingleWordInOperand(
+        kInsertCompositeIdInIdx);
+    Instruction* operand_inst = def_use_mgr->GetDef(operand_id);
 
-  WorkListItem new_item;
-  new_item.instruction = operand_inst;
-  new_item.components = current_item.components;
-  new_item.components.Clear(insert_position);
+    WorkListItem new_item;
+    new_item.instruction = operand_inst;
+    new_item.components = current_item.components;
+    new_item.components.Clear(insert_position);
 
-  AddItemToWorkListIfNeeded(new_item, live_components, work_list);
+    AddItemToWorkListIfNeeded(new_item, live_components, work_list);
 
-  // Add the element being inserted if it is used.
-  if (current_item.components.Get(insert_position)) {
-    uint32_t obj_operand_id =
+    // Add the element being inserted if it is used.
+    if (current_item.components.Get(insert_position)) {
+      uint32_t obj_operand_id =
+          current_item.instruction->GetSingleWordInOperand(
+              kInsertObjectIdInIdx);
+      Instruction* obj_operand_inst = def_use_mgr->GetDef(obj_operand_id);
+      WorkListItem new_item_for_obj;
+      new_item_for_obj.instruction = obj_operand_inst;
+      new_item_for_obj.components.Set(0);
+      AddItemToWorkListIfNeeded(new_item_for_obj, live_components, work_list);
+    }
+  } else {
+    // If there are no indices, then this is a copy of the object being
+    // inserted.
+    uint32_t object_id =
         current_item.instruction->GetSingleWordInOperand(kInsertObjectIdInIdx);
-    Instruction* obj_operand_inst = def_use_mgr->GetDef(obj_operand_id);
-    WorkListItem new_item_for_obj;
-    new_item_for_obj.instruction = obj_operand_inst;
-    new_item_for_obj.components.Set(0);
-    AddItemToWorkListIfNeeded(new_item_for_obj, live_components, work_list);
+    Instruction* object_inst = def_use_mgr->GetDef(object_id);
+
+    WorkListItem new_item;
+    new_item.instruction = object_inst;
+    new_item.components = current_item.components;
+    AddItemToWorkListIfNeeded(new_item, live_components, work_list);
   }
 }
 
@@ -323,14 +343,23 @@
 bool VectorDCE::RewriteInsertInstruction(
     Instruction* current_inst, const utils::BitVector& live_components) {
   // If the value being inserted is not live, then we can skip the insert.
-  bool modified = false;
+
+  if (current_inst->NumInOperands() == 2) {
+    // If there are no indices, then this is the same as a copy.
+    context()->KillNamesAndDecorates(current_inst->result_id());
+    uint32_t object_id =
+        current_inst->GetSingleWordInOperand(kInsertObjectIdInIdx);
+    context()->ReplaceAllUsesWith(current_inst->result_id(), object_id);
+    return true;
+  }
+
   uint32_t insert_index = current_inst->GetSingleWordInOperand(2);
   if (!live_components.Get(insert_index)) {
-    modified = true;
     context()->KillNamesAndDecorates(current_inst->result_id());
     uint32_t composite_id =
         current_inst->GetSingleWordInOperand(kInsertCompositeIdInIdx);
     context()->ReplaceAllUsesWith(current_inst->result_id(), composite_id);
+    return true;
   }
 
   // If the values already in the composite are not used, then replace it with
@@ -342,9 +371,10 @@
     uint32_t undef_id = Type2Undef(current_inst->type_id());
     current_inst->SetInOperand(kInsertCompositeIdInIdx, {undef_id});
     context()->AnalyzeUses(current_inst);
+    return true;
   }
 
-  return modified;
+  return false;
 }
 
 void VectorDCE::AddItemToWorkListIfNeeded(
diff --git a/source/opt/vector_dce.h b/source/opt/vector_dce.h
index 4888699..4f039c5 100644
--- a/source/opt/vector_dce.h
+++ b/source/opt/vector_dce.h
@@ -53,7 +53,8 @@
     return IRContext::kAnalysisDefUse | IRContext::kAnalysisCFG |
            IRContext::kAnalysisInstrToBlockMapping |
            IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisDecorations |
-           IRContext::kAnalysisDominatorAnalysis | IRContext::kAnalysisNameMap;
+           IRContext::kAnalysisDominatorAnalysis | IRContext::kAnalysisNameMap |
+           IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
   }
 
  private:
@@ -128,6 +129,7 @@
   // live. If anything becomes live they are added to |work_list| and
   // |live_components| is updated accordingly.
   void MarkExtractUseAsLive(const Instruction* current_inst,
+                            const utils::BitVector& live_elements,
                             LiveComponentMap* live_components,
                             std::vector<WorkListItem>* work_list);
 
diff --git a/source/reduce/CMakeLists.txt b/source/reduce/CMakeLists.txt
index 57475ac..def4d21 100644
--- a/source/reduce/CMakeLists.txt
+++ b/source/reduce/CMakeLists.txt
@@ -13,29 +13,60 @@
 # limitations under the License.
 set(SPIRV_TOOLS_REDUCE_SOURCES
         change_operand_reduction_opportunity.h
-        operand_to_const_reduction_pass.h
-        operand_to_dominating_id_reduction_pass.h
+        change_operand_to_undef_reduction_opportunity.h
+        merge_blocks_reduction_opportunity.h
+        merge_blocks_reduction_opportunity_finder.h
+        operand_to_const_reduction_opportunity_finder.h
+        operand_to_undef_reduction_opportunity_finder.h
+        operand_to_dominating_id_reduction_opportunity_finder.h
         reducer.h
         reduction_opportunity.h
+        reduction_opportunity_finder.h
         reduction_pass.h
+        reduction_util.h
+        remove_block_reduction_opportunity.h
+        remove_block_reduction_opportunity_finder.h
         remove_instruction_reduction_opportunity.h
-        remove_opname_instruction_reduction_pass.h
-        remove_unreferenced_instruction_reduction_pass.h
+        remove_function_reduction_opportunity.h
+        remove_function_reduction_opportunity_finder.h
+        remove_opname_instruction_reduction_opportunity_finder.h
+        remove_selection_reduction_opportunity.h
+        remove_selection_reduction_opportunity_finder.h
+        remove_unreferenced_instruction_reduction_opportunity_finder.h
         structured_loop_to_selection_reduction_opportunity.h
-        structured_loop_to_selection_reduction_pass.h
+        structured_loop_to_selection_reduction_opportunity_finder.h
+        conditional_branch_to_simple_conditional_branch_opportunity_finder.h
+        conditional_branch_to_simple_conditional_branch_reduction_opportunity.h
+        simple_conditional_branch_to_branch_opportunity_finder.h
+        simple_conditional_branch_to_branch_reduction_opportunity.h
 
         change_operand_reduction_opportunity.cpp
-        operand_to_const_reduction_pass.cpp
-        operand_to_dominating_id_reduction_pass.cpp
+        change_operand_to_undef_reduction_opportunity.cpp
+        merge_blocks_reduction_opportunity.cpp
+        merge_blocks_reduction_opportunity_finder.cpp
+        operand_to_const_reduction_opportunity_finder.cpp
+        operand_to_undef_reduction_opportunity_finder.cpp
+        operand_to_dominating_id_reduction_opportunity_finder.cpp
         reducer.cpp
         reduction_opportunity.cpp
         reduction_pass.cpp
+        reduction_util.cpp
+        remove_block_reduction_opportunity.cpp
+        remove_block_reduction_opportunity_finder.cpp
+        remove_function_reduction_opportunity.cpp
+        remove_function_reduction_opportunity_finder.cpp
         remove_instruction_reduction_opportunity.cpp
-        remove_unreferenced_instruction_reduction_pass.cpp
-        remove_opname_instruction_reduction_pass.cpp
+        remove_selection_reduction_opportunity.cpp
+        remove_selection_reduction_opportunity_finder.cpp
+        remove_unreferenced_instruction_reduction_opportunity_finder.cpp
+        remove_opname_instruction_reduction_opportunity_finder.cpp
         structured_loop_to_selection_reduction_opportunity.cpp
-        structured_loop_to_selection_reduction_pass.cpp
-        )
+        structured_loop_to_selection_reduction_opportunity_finder.cpp
+        conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp
+        conditional_branch_to_simple_conditional_branch_reduction_opportunity.cpp
+        simple_conditional_branch_to_branch_opportunity_finder.cpp
+        simple_conditional_branch_to_branch_reduction_opportunity.cpp
+)
 
 if(MSVC)
   # Enable parallel builds across four cores for this lib
diff --git a/source/reduce/change_operand_reduction_opportunity.cpp b/source/reduce/change_operand_reduction_opportunity.cpp
index 5430d3e..c3f6fd7 100644
--- a/source/reduce/change_operand_reduction_opportunity.cpp
+++ b/source/reduce/change_operand_reduction_opportunity.cpp
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "change_operand_reduction_opportunity.h"
+#include "source/reduce/change_operand_reduction_opportunity.h"
 
 namespace spvtools {
 namespace reduce {
diff --git a/source/reduce/change_operand_reduction_opportunity.h b/source/reduce/change_operand_reduction_opportunity.h
index 9e43ec9..18e6ca1 100644
--- a/source/reduce/change_operand_reduction_opportunity.h
+++ b/source/reduce/change_operand_reduction_opportunity.h
@@ -15,40 +15,33 @@
 #ifndef SOURCE_REDUCE_CHANGE_OPERAND_REDUCTION_OPPORTUNITY_H_
 #define SOURCE_REDUCE_CHANGE_OPERAND_REDUCTION_OPPORTUNITY_H_
 
-#include "reduction_opportunity.h"
 #include "source/opt/instruction.h"
+#include "source/reduce/reduction_opportunity.h"
 #include "spirv-tools/libspirv.h"
 
 namespace spvtools {
 namespace reduce {
 
-using namespace opt;
-
-// Captures the opportunity to change an id operand of an instruction to some
-// other id.
+// An opportunity to replace an id operand of an instruction with some other id.
 class ChangeOperandReductionOpportunity : public ReductionOpportunity {
  public:
   // Constructs the opportunity to replace operand |operand_index| of |inst|
   // with |new_id|.
-  ChangeOperandReductionOpportunity(Instruction* inst, uint32_t operand_index,
-                                    uint32_t new_id)
+  ChangeOperandReductionOpportunity(opt::Instruction* inst,
+                                    uint32_t operand_index, uint32_t new_id)
       : inst_(inst),
         operand_index_(operand_index),
         original_id_(inst->GetOperand(operand_index).words[0]),
         original_type_(inst->GetOperand(operand_index).type),
         new_id_(new_id) {}
 
-  // Determines whether the opportunity can be applied; it may have been viable
-  // when discovered but later disabled by the application of some other
-  // reduction opportunity.
   bool PreconditionHolds() override;
 
  protected:
-  // Apply the change of operand.
   void Apply() override;
 
  private:
-  Instruction* const inst_;
+  opt::Instruction* const inst_;
   const uint32_t operand_index_;
   const uint32_t original_id_;
   const spv_operand_type_t original_type_;
diff --git a/source/reduce/change_operand_to_undef_reduction_opportunity.cpp b/source/reduce/change_operand_to_undef_reduction_opportunity.cpp
new file mode 100644
index 0000000..8e33da6
--- /dev/null
+++ b/source/reduce/change_operand_to_undef_reduction_opportunity.cpp
@@ -0,0 +1,41 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/change_operand_to_undef_reduction_opportunity.h"
+
+#include "source/opt/ir_context.h"
+#include "source/reduce/reduction_util.h"
+
+namespace spvtools {
+namespace reduce {
+
+bool ChangeOperandToUndefReductionOpportunity::PreconditionHolds() {
+  // Check that the instruction still has the original operand.
+  return inst_->NumOperands() > operand_index_ &&
+         inst_->GetOperand(operand_index_).words[0] == original_id_;
+}
+
+void ChangeOperandToUndefReductionOpportunity::Apply() {
+  auto operand = inst_->GetOperand(operand_index_);
+  auto operand_id = operand.words[0];
+  auto operand_id_def = context_->get_def_use_mgr()->GetDef(operand_id);
+  auto operand_type_id = operand_id_def->type_id();
+  // The opportunity should not exist unless this holds.
+  assert(operand_type_id);
+  auto undef_id = FindOrCreateGlobalUndef(context_, operand_type_id);
+  inst_->SetOperand(operand_index_, {undef_id});
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/change_operand_to_undef_reduction_opportunity.h b/source/reduce/change_operand_to_undef_reduction_opportunity.h
new file mode 100644
index 0000000..ffd3155
--- /dev/null
+++ b/source/reduce/change_operand_to_undef_reduction_opportunity.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_CHANGE_OPERAND_TO_UNDEF_REDUCTION_OPPORTUNITY_H_
+#define SOURCE_REDUCE_CHANGE_OPERAND_TO_UNDEF_REDUCTION_OPPORTUNITY_H_
+
+#include "source/opt/instruction.h"
+#include "source/reduce/reduction_opportunity.h"
+#include "spirv-tools/libspirv.h"
+
+namespace spvtools {
+namespace reduce {
+
+// An opportunity to replace an id operand of an instruction with undef.
+class ChangeOperandToUndefReductionOpportunity : public ReductionOpportunity {
+ public:
+  // Constructs the opportunity to replace operand |operand_index| of |inst|
+  // with undef.
+  ChangeOperandToUndefReductionOpportunity(opt::IRContext* context,
+                                           opt::Instruction* inst,
+                                           uint32_t operand_index)
+      : context_(context),
+        inst_(inst),
+        operand_index_(operand_index),
+        original_id_(inst->GetOperand(operand_index).words[0]) {}
+
+  bool PreconditionHolds() override;
+
+ protected:
+  void Apply() override;
+
+ private:
+  opt::IRContext* context_;
+  opt::Instruction* const inst_;
+  const uint32_t operand_index_;
+  const uint32_t original_id_;
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_CHANGE_OPERAND_TO_UNDEF_REDUCTION_OPPORTUNITY_H_
diff --git a/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp b/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp
new file mode 100644
index 0000000..2feca4a
--- /dev/null
+++ b/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp
@@ -0,0 +1,89 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h"
+
+#include "source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.h"
+#include "source/reduce/reduction_util.h"
+
+namespace spvtools {
+namespace reduce {
+
+using opt::IRContext;
+using opt::Instruction;
+
+std::vector<std::unique_ptr<ReductionOpportunity>>
+ConditionalBranchToSimpleConditionalBranchOpportunityFinder::
+    GetAvailableOpportunities(IRContext* context) const {
+  std::vector<std::unique_ptr<ReductionOpportunity>> result;
+
+  // Find the opportunities for redirecting all false targets before the
+  // opportunities for redirecting all true targets because the former
+  // opportunities disable the latter, and vice versa, and the efficiency of the
+  // reducer is improved by avoiding contiguous opportunities that disable one
+  // another.
+  for (bool redirect_to_true : {true, false}) {
+    // Consider every function.
+    for (auto& function : *context->module()) {
+      // Consider every block in the function.
+      for (auto& block : function) {
+        // The terminator must be SpvOpBranchConditional.
+        Instruction* terminator = block.terminator();
+        if (terminator->opcode() != SpvOpBranchConditional) {
+          continue;
+        }
+
+        uint32_t true_block_id =
+            terminator->GetSingleWordInOperand(kTrueBranchOperandIndex);
+        uint32_t false_block_id =
+            terminator->GetSingleWordInOperand(kFalseBranchOperandIndex);
+
+        // The conditional branch must not already be simplified.
+        if (true_block_id == false_block_id) {
+          continue;
+        }
+
+        // The redirected target must not be a back-edge to a structured loop
+        // header.
+        uint32_t redirected_block_id =
+            redirect_to_true ? false_block_id : true_block_id;
+        uint32_t containing_loop_header =
+            context->GetStructuredCFGAnalysis()->ContainingLoop(block.id());
+        // The structured CFG analysis does not include a loop header as part
+        // of the loop construct, but we want to include it, so handle this
+        // special case:
+        if (block.GetLoopMergeInst() != nullptr) {
+          containing_loop_header = block.id();
+        }
+        if (redirected_block_id == containing_loop_header) {
+          continue;
+        }
+
+        result.push_back(
+            MakeUnique<
+                ConditionalBranchToSimpleConditionalBranchReductionOpportunity>(
+                block.terminator(), redirect_to_true));
+      }
+    }
+  }
+  return result;
+}
+
+std::string
+ConditionalBranchToSimpleConditionalBranchOpportunityFinder::GetName() const {
+  return "ConditionalBranchToSimpleConditionalBranchOpportunityFinder";
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h b/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h
new file mode 100644
index 0000000..c582a88
--- /dev/null
+++ b/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_SIMPLIFY_SELECTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_SIMPLIFY_SELECTION_OPPORTUNITY_FINDER_H_
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder for opportunities to simplify conditional branches into simple
+// conditional branches (conditional branches with one target).
+class ConditionalBranchToSimpleConditionalBranchOpportunityFinder
+    : public ReductionOpportunityFinder {
+ public:
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      opt::IRContext* context) const override;
+
+  std::string GetName() const override;
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_SIMPLIFY_SELECTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.cpp b/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.cpp
new file mode 100644
index 0000000..92c621e
--- /dev/null
+++ b/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.cpp
@@ -0,0 +1,54 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.h"
+
+#include "source/reduce/reduction_util.h"
+
+namespace spvtools {
+namespace reduce {
+
+using opt::Instruction;
+
+ConditionalBranchToSimpleConditionalBranchReductionOpportunity::
+    ConditionalBranchToSimpleConditionalBranchReductionOpportunity(
+        Instruction* conditional_branch_instruction, bool redirect_to_true)
+    : conditional_branch_instruction_(conditional_branch_instruction),
+      redirect_to_true_(redirect_to_true) {}
+
+bool ConditionalBranchToSimpleConditionalBranchReductionOpportunity::
+    PreconditionHolds() {
+  // Another opportunity may have already simplified this conditional branch,
+  // which should disable this opportunity.
+  return conditional_branch_instruction_->GetSingleWordInOperand(
+             kTrueBranchOperandIndex) !=
+         conditional_branch_instruction_->GetSingleWordInOperand(
+             kFalseBranchOperandIndex);
+}
+
+void ConditionalBranchToSimpleConditionalBranchReductionOpportunity::Apply() {
+  uint32_t operand_to_modify =
+      redirect_to_true_ ? kFalseBranchOperandIndex : kTrueBranchOperandIndex;
+  uint32_t operand_to_copy =
+      redirect_to_true_ ? kTrueBranchOperandIndex : kFalseBranchOperandIndex;
+
+  // Do the branch redirection.
+  conditional_branch_instruction_->SetInOperand(
+      operand_to_modify,
+      {conditional_branch_instruction_->GetSingleWordInOperand(
+          operand_to_copy)});
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.h b/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.h
new file mode 100644
index 0000000..421906b
--- /dev/null
+++ b/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.h
@@ -0,0 +1,52 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_SIMPLIFY_CONDITIONAL_BRANCH_REDUCTION_OPPORTUNITY_H_
+#define SOURCE_REDUCE_SIMPLIFY_CONDITIONAL_BRANCH_REDUCTION_OPPORTUNITY_H_
+
+#include "source/opt/basic_block.h"
+#include "source/reduce/reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+// An opportunity to simplify a conditional branch to a simple conditional
+// branch (a conditional branch with one target).
+class ConditionalBranchToSimpleConditionalBranchReductionOpportunity
+    : public ReductionOpportunity {
+ public:
+  // Constructs an opportunity to simplify |conditional_branch_instruction|. If
+  // |redirect_to_true| is true, the false target will be changed to also point
+  // to the true target; otherwise, the true target will be changed to also
+  // point to the false target.
+  explicit ConditionalBranchToSimpleConditionalBranchReductionOpportunity(
+      opt::Instruction* conditional_branch_instruction, bool redirect_to_true);
+
+  bool PreconditionHolds() override;
+
+ protected:
+  void Apply() override;
+
+ private:
+  opt::Instruction* conditional_branch_instruction_;
+
+  // If true, the false target will be changed to point to the true target;
+  // otherwise, the true target will be changed to point to the false target.
+  bool redirect_to_true_;
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_SIMPLIFY_CONDITIONAL_BRANCH_REDUCTION_OPPORTUNITY_H_
diff --git a/source/reduce/merge_blocks_reduction_opportunity.cpp b/source/reduce/merge_blocks_reduction_opportunity.cpp
new file mode 100644
index 0000000..42c7843
--- /dev/null
+++ b/source/reduce/merge_blocks_reduction_opportunity.cpp
@@ -0,0 +1,83 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/merge_blocks_reduction_opportunity.h"
+
+#include "source/opt/block_merge_util.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace reduce {
+
+using opt::BasicBlock;
+using opt::Function;
+using opt::IRContext;
+
+MergeBlocksReductionOpportunity::MergeBlocksReductionOpportunity(
+    IRContext* context, Function* function, BasicBlock* block) {
+  // Precondition: the terminator has to be OpBranch.
+  assert(block->terminator()->opcode() == SpvOpBranch);
+  context_ = context;
+  function_ = function;
+  // Get the successor block associated with the OpBranch.
+  successor_block_ =
+      context->cfg()->block(block->terminator()->GetSingleWordInOperand(0));
+}
+
+bool MergeBlocksReductionOpportunity::PreconditionHolds() {
+  // Merge block opportunities can disable each other.
+  // Example: Given blocks: A->B->C.
+  // A is a loop header; B and C are blocks in the loop; C ends with OpReturn.
+  // There are two opportunities: B and C can be merged with their predecessors.
+  // Merge C. B now ends with OpReturn. We now just have: A->B.
+  // Merge B is now disabled, as this would lead to A, a loop header, ending
+  // with an OpReturn, which is invalid.
+
+  const auto predecessors = context_->cfg()->preds(successor_block_->id());
+  assert(1 == predecessors.size() &&
+         "For a successor to be merged into its predecessor, exactly one "
+         "predecessor must be present.");
+  const uint32_t predecessor_id = predecessors[0];
+  BasicBlock* predecessor_block = context_->get_instr_block(predecessor_id);
+  return opt::blockmergeutil::CanMergeWithSuccessor(context_,
+                                                    predecessor_block);
+}
+
+void MergeBlocksReductionOpportunity::Apply() {
+  // While the original block that targeted the successor may not exist anymore
+  // (it might have been merged with another block), some block must exist that
+  // targets the successor.  Find it.
+
+  const auto predecessors = context_->cfg()->preds(successor_block_->id());
+  assert(1 == predecessors.size() &&
+         "For a successor to be merged into its predecessor, exactly one "
+         "predecessor must be present.");
+  const uint32_t predecessor_id = predecessors[0];
+
+  // We need an iterator pointing to the predecessor, hence the loop.
+  for (auto bi = function_->begin(); bi != function_->end(); ++bi) {
+    if (bi->id() == predecessor_id) {
+      opt::blockmergeutil::MergeWithSuccessor(context_, function_, bi);
+      // Block merging changes the control flow graph, so invalidate it.
+      context_->InvalidateAnalysesExceptFor(IRContext::Analysis::kAnalysisNone);
+      return;
+    }
+  }
+
+  assert(false &&
+         "Unreachable: we should have found a block with the desired id.");
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/merge_blocks_reduction_opportunity.h b/source/reduce/merge_blocks_reduction_opportunity.h
new file mode 100644
index 0000000..5c9180b
--- /dev/null
+++ b/source/reduce/merge_blocks_reduction_opportunity.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_MERGE_BLOCKS_REDUCTION_OPPORTUNITY_H_
+#define SOURCE_REDUCE_MERGE_BLOCKS_REDUCTION_OPPORTUNITY_H_
+
+#include "source/opt/basic_block.h"
+#include "source/opt/function.h"
+#include "source/reduce/reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+// An opportunity to merge two blocks into one.
+class MergeBlocksReductionOpportunity : public ReductionOpportunity {
+ public:
+  // Creates the opportunity to merge |block| with its successor, where |block|
+  // is inside |function|, and |context| is the enclosing IR context.
+  MergeBlocksReductionOpportunity(opt::IRContext* context,
+                                  opt::Function* function,
+                                  opt::BasicBlock* block);
+
+  bool PreconditionHolds() override;
+
+ protected:
+  void Apply() override;
+
+ private:
+  opt::IRContext* context_;
+  opt::Function* function_;
+
+  // Rather than holding on to the block that can be merged with its successor,
+  // we hold on to its successor. This is because the predecessor block might
+  // get merged with *its* predecessor, and so will no longer exist, while the
+  // successor will continue to exist until this opportunity gets applied.
+  opt::BasicBlock* successor_block_;
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_MERGE_BLOCKS_REDUCTION_OPPORTUNITY_H_
diff --git a/source/reduce/merge_blocks_reduction_opportunity_finder.cpp b/source/reduce/merge_blocks_reduction_opportunity_finder.cpp
new file mode 100644
index 0000000..89d6263
--- /dev/null
+++ b/source/reduce/merge_blocks_reduction_opportunity_finder.cpp
@@ -0,0 +1,48 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/merge_blocks_reduction_opportunity_finder.h"
+#include "source/opt/block_merge_util.h"
+#include "source/reduce/merge_blocks_reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+using opt::IRContext;
+
+std::string MergeBlocksReductionOpportunityFinder::GetName() const {
+  return "MergeBlocksReductionOpportunityFinder";
+}
+
+std::vector<std::unique_ptr<ReductionOpportunity>>
+MergeBlocksReductionOpportunityFinder::GetAvailableOpportunities(
+    IRContext* context) const {
+  std::vector<std::unique_ptr<ReductionOpportunity>> result;
+
+  // Consider every block in every function.
+  for (auto& function : *context->module()) {
+    for (auto& block : function) {
+      // See whether it is possible to merge this block with its successor.
+      if (opt::blockmergeutil::CanMergeWithSuccessor(context, &block)) {
+        // It is, so record an opportunity to do this.
+        result.push_back(spvtools::MakeUnique<MergeBlocksReductionOpportunity>(
+            context, &function, &block));
+      }
+    }
+  }
+  return result;
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/merge_blocks_reduction_opportunity_finder.h b/source/reduce/merge_blocks_reduction_opportunity_finder.h
new file mode 100644
index 0000000..dbf82fe
--- /dev/null
+++ b/source/reduce/merge_blocks_reduction_opportunity_finder.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_MERGE_BLOCKS_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_MERGE_BLOCKS_REDUCTION_OPPORTUNITY_FINDER_H_
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder of opportunities to merge blocks together.
+class MergeBlocksReductionOpportunityFinder
+    : public ReductionOpportunityFinder {
+ public:
+  MergeBlocksReductionOpportunityFinder() = default;
+
+  ~MergeBlocksReductionOpportunityFinder() override = default;
+
+  std::string GetName() const final;
+
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      opt::IRContext* context) const final;
+
+ private:
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_MERGE_BLOCKS_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/operand_to_const_reduction_pass.cpp b/source/reduce/operand_to_const_reduction_opportunity_finder.cpp
similarity index 88%
rename from source/reduce/operand_to_const_reduction_pass.cpp
rename to source/reduce/operand_to_const_reduction_opportunity_finder.cpp
index 35c0f4b..3e0a224 100644
--- a/source/reduce/operand_to_const_reduction_pass.cpp
+++ b/source/reduce/operand_to_const_reduction_opportunity_finder.cpp
@@ -12,18 +12,19 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "operand_to_const_reduction_pass.h"
-#include "change_operand_reduction_opportunity.h"
+#include "source/reduce/operand_to_const_reduction_opportunity_finder.h"
+
 #include "source/opt/instruction.h"
+#include "source/reduce/change_operand_reduction_opportunity.h"
 
 namespace spvtools {
 namespace reduce {
 
-using namespace opt;
+using opt::IRContext;
 
 std::vector<std::unique_ptr<ReductionOpportunity>>
-OperandToConstReductionPass::GetAvailableOpportunities(
-    opt::IRContext* context) const {
+OperandToConstReductionOpportunityFinder::GetAvailableOpportunities(
+    IRContext* context) const {
   std::vector<std::unique_ptr<ReductionOpportunity>> result;
   assert(result.empty());
 
@@ -74,8 +75,8 @@
   return result;
 }
 
-std::string OperandToConstReductionPass::GetName() const {
-  return "OperandToConstReductionPass";
+std::string OperandToConstReductionOpportunityFinder::GetName() const {
+  return "OperandToConstReductionOpportunityFinder";
 }
 
 }  // namespace reduce
diff --git a/source/reduce/operand_to_const_reduction_opportunity_finder.h b/source/reduce/operand_to_const_reduction_opportunity_finder.h
new file mode 100644
index 0000000..93c0dcd
--- /dev/null
+++ b/source/reduce/operand_to_const_reduction_opportunity_finder.h
@@ -0,0 +1,44 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_OPERAND_TO_CONST_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_OPERAND_TO_CONST_REDUCTION_OPPORTUNITY_FINDER_H_
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder for opportunities to replace id operands of instructions with ids of
+// constants.  This reduces the extent to which ids of non-constants are used,
+// paving the way for instructions that generate them to be eliminated.
+class OperandToConstReductionOpportunityFinder
+    : public ReductionOpportunityFinder {
+ public:
+  OperandToConstReductionOpportunityFinder() = default;
+
+  ~OperandToConstReductionOpportunityFinder() override = default;
+
+  std::string GetName() const final;
+
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      opt::IRContext* context) const final;
+
+ private:
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_OPERAND_TO_CONST_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/operand_to_const_reduction_pass.h b/source/reduce/operand_to_const_reduction_pass.h
deleted file mode 100644
index fd7cfa0..0000000
--- a/source/reduce/operand_to_const_reduction_pass.h
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright (c) 2018 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SOURCE_REDUCE_OPERAND_TO_CONST_REDUCTION_PASS_H_
-#define SOURCE_REDUCE_OPERAND_TO_CONST_REDUCTION_PASS_H_
-
-#include "reduction_pass.h"
-
-namespace spvtools {
-namespace reduce {
-
-// A reduction pass for turning id operands of instructions into ids of
-// constants.  This reduces the extent to which ids of non-constants are used,
-// paving the way for instructions that generate them to be eliminated by other
-// passes.
-class OperandToConstReductionPass : public ReductionPass {
- public:
-  // Creates the reduction pass in the context of the given target environment
-  // |target_env|
-  explicit OperandToConstReductionPass(const spv_target_env target_env)
-      : ReductionPass(target_env) {}
-
-  ~OperandToConstReductionPass() override = default;
-
-  // The name of this pass.
-  std::string GetName() const final;
-
- protected:
-  // Finds all opportunities for replacing an operand with a constant in the
-  // given module.
-  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
-      opt::IRContext* context) const final;
-
- private:
-};
-
-}  // namespace reduce
-}  // namespace spvtools
-
-#endif  // SOURCE_REDUCE_OPERAND_TO_CONST_REDUCTION_PASS_H_
diff --git a/source/reduce/operand_to_dominating_id_reduction_pass.cpp b/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.cpp
similarity index 84%
rename from source/reduce/operand_to_dominating_id_reduction_pass.cpp
rename to source/reduce/operand_to_dominating_id_reduction_opportunity_finder.cpp
index 9280a41..13beb89 100644
--- a/source/reduce/operand_to_dominating_id_reduction_pass.cpp
+++ b/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.cpp
@@ -12,18 +12,21 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "operand_to_dominating_id_reduction_pass.h"
-#include "change_operand_reduction_opportunity.h"
+#include "source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h"
+
 #include "source/opt/instruction.h"
+#include "source/reduce/change_operand_reduction_opportunity.h"
 
 namespace spvtools {
 namespace reduce {
 
-using namespace opt;
+using opt::Function;
+using opt::IRContext;
+using opt::Instruction;
 
 std::vector<std::unique_ptr<ReductionOpportunity>>
-OperandToDominatingIdReductionPass::GetAvailableOpportunities(
-    opt::IRContext* context) const {
+OperandToDominatingIdReductionOpportunityFinder::GetAvailableOpportunities(
+    IRContext* context) const {
   std::vector<std::unique_ptr<ReductionOpportunity>> result;
 
   // Go through every instruction in every block, considering it as a potential
@@ -55,11 +58,12 @@
   return result;
 }
 
-void OperandToDominatingIdReductionPass::GetOpportunitiesForDominatingInst(
-    std::vector<std::unique_ptr<ReductionOpportunity>>* opportunities,
-    opt::Instruction* candidate_dominator,
-    opt::Function::iterator candidate_dominator_block, opt::Function* function,
-    opt::IRContext* context) const {
+void OperandToDominatingIdReductionOpportunityFinder::
+    GetOpportunitiesForDominatingInst(
+        std::vector<std::unique_ptr<ReductionOpportunity>>* opportunities,
+        Instruction* candidate_dominator,
+        Function::iterator candidate_dominator_block, Function* function,
+        IRContext* context) const {
   assert(candidate_dominator->HasResultId());
   assert(candidate_dominator->type_id());
   auto dominator_analysis = context->GetDominatorAnalysis(function);
@@ -106,8 +110,8 @@
   }
 }
 
-std::string OperandToDominatingIdReductionPass::GetName() const {
-  return "OperandToDominatingIdReductionPass";
+std::string OperandToDominatingIdReductionOpportunityFinder::GetName() const {
+  return "OperandToDominatingIdReductionOpportunityFinder";
 }
 
 }  // namespace reduce
diff --git a/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h b/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h
new file mode 100644
index 0000000..7745ff7
--- /dev/null
+++ b/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h
@@ -0,0 +1,56 @@
+// Copyright (c) 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_OPERAND_TO_DOMINATING_ID_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_OPERAND_TO_DOMINATING_ID_REDUCTION_OPPORTUNITY_FINDER_H_
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder that aims to bring to SPIR-V (and generalize) the idea from
+// human-readable languages of e.g. finding opportunities to replace an
+// expression with one of its arguments, (x + y) -> x, or with a reference to an
+// identifier that was assigned to higher up in the program.  The generalization
+// of this is to replace an id with a different id of the same type defined in
+// some dominating instruction.
+//
+// If id x is defined and then used several times, changing each use of x to
+// some dominating definition may eventually allow the statement defining x
+// to be eliminated by another pass.
+class OperandToDominatingIdReductionOpportunityFinder
+    : public ReductionOpportunityFinder {
+ public:
+  OperandToDominatingIdReductionOpportunityFinder() = default;
+
+  ~OperandToDominatingIdReductionOpportunityFinder() override = default;
+
+  std::string GetName() const final;
+
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      opt::IRContext* context) const final;
+
+ private:
+  void GetOpportunitiesForDominatingInst(
+      std::vector<std::unique_ptr<ReductionOpportunity>>* opportunities,
+      opt::Instruction* dominating_instruction,
+      opt::Function::iterator candidate_dominator_block,
+      opt::Function* function, opt::IRContext* context) const;
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_OPERAND_TO_DOMINATING_ID_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/operand_to_dominating_id_reduction_pass.h b/source/reduce/operand_to_dominating_id_reduction_pass.h
deleted file mode 100644
index b62b6ae..0000000
--- a/source/reduce/operand_to_dominating_id_reduction_pass.h
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright (c) 2018 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SOURCE_REDUCE_OPERAND_TO_DOMINATING_ID_REDUCTION_PASS_H_
-#define SOURCE_REDUCE_OPERAND_TO_DOMINATING_ID_REDUCTION_PASS_H_
-
-#include "reduction_pass.h"
-
-namespace spvtools {
-namespace reduce {
-
-// A reduction pass that aims to bring to SPIR-V (and generalize) the idea from
-// human-readable languages of e.g. replacing an expression with one of its
-// arguments, (x + y) -> x, or with a reference to an identifier that was
-// assigned to higher up in the program.  The generalization of this is to
-// replace an id with a different id of the same type defined in some
-// dominating instruction.
-//
-// If id x is defined and then used several times, changing each use of x to
-// some dominating definition may eventually allow the statement defining x
-// to be eliminated by another pass.
-class OperandToDominatingIdReductionPass : public ReductionPass {
- public:
-  // Creates the reduction pass in the context of the given target environment
-  // |target_env|
-  explicit OperandToDominatingIdReductionPass(const spv_target_env target_env)
-      : ReductionPass(target_env) {}
-
-  ~OperandToDominatingIdReductionPass() override = default;
-
-  // The name of this pass.
-  std::string GetName() const final;
-
- protected:
-  // Finds all opportunities for replacing an operand with a dominating
-  // instruction in a given module.
-  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
-      opt::IRContext* context) const final;
-
- private:
-  void GetOpportunitiesForDominatingInst(
-      std::vector<std::unique_ptr<ReductionOpportunity>>* opportunities,
-      opt::Instruction* dominating_instruction,
-      opt::Function::iterator candidate_dominator_block,
-      opt::Function* function, opt::IRContext* context) const;
-};
-
-}  // namespace reduce
-}  // namespace spvtools
-
-#endif  // SOURCE_REDUCE_OPERAND_TO_DOMINATING_ID_REDUCTION_PASS_H_
diff --git a/source/reduce/operand_to_undef_reduction_opportunity_finder.cpp b/source/reduce/operand_to_undef_reduction_opportunity_finder.cpp
new file mode 100644
index 0000000..579b7df
--- /dev/null
+++ b/source/reduce/operand_to_undef_reduction_opportunity_finder.cpp
@@ -0,0 +1,94 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/operand_to_undef_reduction_opportunity_finder.h"
+
+#include "source/opt/instruction.h"
+#include "source/reduce/change_operand_to_undef_reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+using opt::IRContext;
+
+std::vector<std::unique_ptr<ReductionOpportunity>>
+OperandToUndefReductionOpportunityFinder::GetAvailableOpportunities(
+    IRContext* context) const {
+  std::vector<std::unique_ptr<ReductionOpportunity>> result;
+
+  for (auto& function : *context->module()) {
+    for (auto& block : function) {
+      for (auto& inst : block) {
+        // Skip instructions that result in a pointer type.
+        auto type_id = inst.type_id();
+        if (type_id) {
+          auto type_id_def = context->get_def_use_mgr()->GetDef(type_id);
+          if (type_id_def->opcode() == SpvOpTypePointer) {
+            continue;
+          }
+        }
+
+        // We iterate through the operands using an explicit index (rather
+        // than using a lambda) so that we use said index in the construction
+        // of a ChangeOperandToUndefReductionOpportunity
+        for (uint32_t index = 0; index < inst.NumOperands(); index++) {
+          const auto& operand = inst.GetOperand(index);
+
+          if (spvIsInIdType(operand.type)) {
+            const auto operand_id = operand.words[0];
+            auto operand_id_def =
+                context->get_def_use_mgr()->GetDef(operand_id);
+
+            // Skip constant and undef operands.
+            // We always want the reducer to make the module "smaller", which
+            // ensures termination.
+            // Therefore, we assume: id > undef id > constant id.
+            if (spvOpcodeIsConstantOrUndef(operand_id_def->opcode())) {
+              continue;
+            }
+
+            // Don't replace function operands with undef.
+            if (operand_id_def->opcode() == SpvOpFunction) {
+              continue;
+            }
+
+            // Only consider operands that have a type.
+            auto operand_type_id = operand_id_def->type_id();
+            if (operand_type_id) {
+              auto operand_type_id_def =
+                  context->get_def_use_mgr()->GetDef(operand_type_id);
+
+              // Skip pointer operands.
+              if (operand_type_id_def->opcode() == SpvOpTypePointer) {
+                continue;
+              }
+
+              result.push_back(
+                  MakeUnique<ChangeOperandToUndefReductionOpportunity>(
+                      context, &inst, index));
+            }
+          }
+        }
+      }
+    }
+  }
+  return result;
+}
+
+std::string OperandToUndefReductionOpportunityFinder::GetName() const {
+  return "OperandToUndefReductionOpportunityFinder";
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/operand_to_undef_reduction_opportunity_finder.h b/source/reduce/operand_to_undef_reduction_opportunity_finder.h
new file mode 100644
index 0000000..9cdd8cd
--- /dev/null
+++ b/source/reduce/operand_to_undef_reduction_opportunity_finder.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_OPERAND_TO_UNDEF_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_OPERAND_TO_UNDEF_REDUCTION_OPPORTUNITY_FINDER_H_
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder of opportunities to replace id operands of instructions with ids of
+// undef.
+class OperandToUndefReductionOpportunityFinder
+    : public ReductionOpportunityFinder {
+ public:
+  OperandToUndefReductionOpportunityFinder() = default;
+
+  ~OperandToUndefReductionOpportunityFinder() override = default;
+
+  std::string GetName() const final;
+
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      opt::IRContext* context) const final;
+
+ private:
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_OPERAND_TO_UNDEF_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/pch_source_reduce.h b/source/reduce/pch_source_reduce.h
index 823b55a..6c0da0c 100644
--- a/source/reduce/pch_source_reduce.h
+++ b/source/reduce/pch_source_reduce.h
@@ -16,8 +16,8 @@
 #include <functional>
 #include <string>
 #include "source/reduce/change_operand_reduction_opportunity.h"
-#include "source/reduce/operand_to_const_reduction_pass.h"
+#include "source/reduce/operand_to_const_reduction_opportunity_finder.h"
 #include "source/reduce/reduction_opportunity.h"
 #include "source/reduce/reduction_pass.h"
 #include "source/reduce/remove_instruction_reduction_opportunity.h"
-#include "source/reduce/remove_unreferenced_instruction_reduction_pass.h"
+#include "source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h"
diff --git a/source/reduce/reducer.cpp b/source/reduce/reducer.cpp
index 4f4429a..a677be3 100644
--- a/source/reduce/reducer.cpp
+++ b/source/reduce/reducer.cpp
@@ -12,14 +12,25 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "source/reduce/reducer.h"
+
 #include <cassert>
 #include <sstream>
 
+#include "source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h"
+#include "source/reduce/merge_blocks_reduction_opportunity_finder.h"
+#include "source/reduce/operand_to_const_reduction_opportunity_finder.h"
+#include "source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h"
+#include "source/reduce/operand_to_undef_reduction_opportunity_finder.h"
+#include "source/reduce/remove_block_reduction_opportunity_finder.h"
+#include "source/reduce/remove_function_reduction_opportunity_finder.h"
+#include "source/reduce/remove_opname_instruction_reduction_opportunity_finder.h"
+#include "source/reduce/remove_selection_reduction_opportunity_finder.h"
+#include "source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h"
+#include "source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h"
+#include "source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h"
 #include "source/spirv_reducer_options.h"
 
-#include "reducer.h"
-#include "reduction_pass.h"
-
 namespace spvtools {
 namespace reduce {
 
@@ -53,13 +64,25 @@
 
 Reducer::ReductionResultStatus Reducer::Run(
     std::vector<uint32_t>&& binary_in, std::vector<uint32_t>* binary_out,
-    spv_const_reducer_options options) const {
-  std::vector<uint32_t> current_binary = binary_in;
+    spv_const_reducer_options options,
+    spv_validator_options validator_options) const {
+  std::vector<uint32_t> current_binary(std::move(binary_in));
+
+  spvtools::SpirvTools tools(impl_->target_env);
+  assert(tools.IsValid() && "Failed to create SPIRV-Tools interface");
 
   // Keeps track of how many reduction attempts have been tried.  Reduction
   // bails out if this reaches a given limit.
   uint32_t reductions_applied = 0;
 
+  // Initial state should be valid.
+  if (!tools.Validate(&current_binary[0], current_binary.size(),
+                      validator_options)) {
+    impl_->consumer(SPV_MSG_INFO, nullptr, {},
+                    "Initial binary is invalid; stopping.");
+    return Reducer::ReductionResultStatus::kInitialStateInvalid;
+  }
+
   // Initial state should be interesting.
   if (!impl_->interestingness_function(current_binary, reductions_applied)) {
     impl_->consumer(SPV_MSG_INFO, nullptr, {},
@@ -93,25 +116,31 @@
       do {
         auto maybe_result = pass->TryApplyReduction(current_binary);
         if (maybe_result.empty()) {
-          // This pass did not have any impact, so move on to the next pass.
+          // For this round, the pass has no more opportunities (chunks) to
+          // apply, so move on to the next pass.
           impl_->consumer(
               SPV_MSG_INFO, nullptr, {},
               ("Pass " + pass->GetName() + " did not make a reduction step.")
                   .c_str());
           break;
         }
+        bool interesting = false;
         std::stringstream stringstream;
         reductions_applied++;
         stringstream << "Pass " << pass->GetName() << " made reduction step "
                      << reductions_applied << ".";
         impl_->consumer(SPV_MSG_INFO, nullptr, {},
                         (stringstream.str().c_str()));
-        if (!spvtools::SpirvTools(impl_->target_env).Validate(maybe_result)) {
+        if (!tools.Validate(&maybe_result[0], maybe_result.size(),
+                            validator_options)) {
           // The reduction step went wrong and an invalid binary was produced.
           // By design, this shouldn't happen; this is a safeguard to stop an
           // invalid binary from being regarded as interesting.
           impl_->consumer(SPV_MSG_INFO, nullptr, {},
                           "Reduction step produced an invalid binary.");
+          if (options->fail_on_validation_error) {
+            return Reducer::ReductionResultStatus::kStateInvalid;
+          }
         } else if (impl_->interestingness_function(maybe_result,
                                                    reductions_applied)) {
           // Success!  The binary produced by this reduction step is
@@ -120,8 +149,11 @@
           impl_->consumer(SPV_MSG_INFO, nullptr, {},
                           "Reduction step succeeded.");
           current_binary = std::move(maybe_result);
+          interesting = true;
           another_round_worthwhile = true;
         }
+        // We must call this before the next call to TryApplyReduction.
+        pass->NotifyInteresting(interesting);
         // Bail out if the reduction step limit has been reached.
       } while (!impl_->ReachedStepLimit(reductions_applied, options));
     }
@@ -140,9 +172,38 @@
   return Reducer::ReductionResultStatus::kComplete;
 }
 
+void Reducer::AddDefaultReductionPasses() {
+  AddReductionPass(spvtools::MakeUnique<
+                   RemoveOpNameInstructionReductionOpportunityFinder>());
+  AddReductionPass(
+      spvtools::MakeUnique<OperandToUndefReductionOpportunityFinder>());
+  AddReductionPass(
+      spvtools::MakeUnique<OperandToConstReductionOpportunityFinder>());
+  AddReductionPass(
+      spvtools::MakeUnique<OperandToDominatingIdReductionOpportunityFinder>());
+  AddReductionPass(spvtools::MakeUnique<
+                   RemoveUnreferencedInstructionReductionOpportunityFinder>());
+  AddReductionPass(spvtools::MakeUnique<
+                   StructuredLoopToSelectionReductionOpportunityFinder>());
+  AddReductionPass(
+      spvtools::MakeUnique<MergeBlocksReductionOpportunityFinder>());
+  AddReductionPass(
+      spvtools::MakeUnique<RemoveFunctionReductionOpportunityFinder>());
+  AddReductionPass(
+      spvtools::MakeUnique<RemoveBlockReductionOpportunityFinder>());
+  AddReductionPass(
+      spvtools::MakeUnique<RemoveSelectionReductionOpportunityFinder>());
+  AddReductionPass(
+      spvtools::MakeUnique<
+          ConditionalBranchToSimpleConditionalBranchOpportunityFinder>());
+  AddReductionPass(
+      spvtools::MakeUnique<SimpleConditionalBranchToBranchOpportunityFinder>());
+}
+
 void Reducer::AddReductionPass(
-    std::unique_ptr<ReductionPass>&& reduction_pass) {
-  impl_->passes.push_back(std::move(reduction_pass));
+    std::unique_ptr<ReductionOpportunityFinder>&& finder) {
+  impl_->passes.push_back(spvtools::MakeUnique<ReductionPass>(
+      impl_->target_env, std::move(finder)));
 }
 
 bool Reducer::Impl::ReachedStepLimit(uint32_t current_step,
diff --git a/source/reduce/reducer.h b/source/reduce/reducer.h
index 3a4c26c..a9b28c3 100644
--- a/source/reduce/reducer.h
+++ b/source/reduce/reducer.h
@@ -18,10 +18,9 @@
 #include <functional>
 #include <string>
 
+#include "source/reduce/reduction_pass.h"
 #include "spirv-tools/libspirv.hpp"
 
-#include "reduction_pass.h"
-
 namespace spvtools {
 namespace reduce {
 
@@ -33,7 +32,12 @@
   enum ReductionResultStatus {
     kInitialStateNotInteresting,
     kReachedStepLimit,
-    kComplete
+    kComplete,
+    kInitialStateInvalid,
+
+    // Returned when the fail-on-validation-error option is set and a
+    // reduction step yields a state that fails validation.
+    kStateInvalid,
   };
 
   // The type for a function that will take a binary and return true if and
@@ -75,16 +79,20 @@
   void SetInterestingnessFunction(
       InterestingnessFunction interestingness_function);
 
-  // Adds a reduction pass to the sequence of passes that will be iterated
-  // over.
-  void AddReductionPass(std::unique_ptr<ReductionPass>&& reduction_pass);
+  // Adds all default reduction passes.
+  void AddDefaultReductionPasses();
+
+  // Adds a reduction pass based on the given finder to the sequence of passes
+  // that will be iterated over.
+  void AddReductionPass(std::unique_ptr<ReductionOpportunityFinder>&& finder);
 
   // Reduces the given SPIR-V module |binary_out|.
   // The reduced binary ends up in |binary_out|.
   // A status is returned.
   ReductionResultStatus Run(std::vector<uint32_t>&& binary_in,
                             std::vector<uint32_t>* binary_out,
-                            spv_const_reducer_options options) const;
+                            spv_const_reducer_options options,
+                            spv_validator_options validator_options) const;
 
  private:
   struct Impl;                  // Opaque struct for holding internal data.
diff --git a/source/reduce/reduction_opportunity.cpp b/source/reduce/reduction_opportunity.cpp
index f562678..77be784 100644
--- a/source/reduce/reduction_opportunity.cpp
+++ b/source/reduce/reduction_opportunity.cpp
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "reduction_opportunity.h"
+#include "source/reduce/reduction_opportunity.h"
 
 namespace spvtools {
 namespace reduce {
diff --git a/source/reduce/reduction_opportunity.h b/source/reduce/reduction_opportunity.h
index da382a2..703a50a 100644
--- a/source/reduce/reduction_opportunity.h
+++ b/source/reduce/reduction_opportunity.h
@@ -20,23 +20,24 @@
 namespace spvtools {
 namespace reduce {
 
-// Abstract class capturing an opportunity to apply a reducing transformation.
+// Abstract class: an opportunity to apply a reducing transformation.
 class ReductionOpportunity {
  public:
   ReductionOpportunity() = default;
   virtual ~ReductionOpportunity() = default;
 
-  // Determines whether the opportunity can be applied; it may have been viable
-  // when discovered but later disabled by the application of some other
-  // reduction opportunity.
+  // Returns true if this opportunity has not been disabled by the application
+  // of another conflicting opportunity.
   virtual bool PreconditionHolds() = 0;
 
-  // A no-op if PreconditoinHolds() returns false; otherwise applies the
-  // opportunity.
+  // Applies the opportunity, mutating the module from which the opportunity was
+  // created. It is a no-op if PreconditionHolds() returns false.
   void TryToApply();
 
  protected:
-  // Apply the reduction opportunity.
+  // Applies the opportunity, mutating the module from which the opportunity was
+  // created.
+  // Precondition: PreconditionHolds() must return true.
   virtual void Apply() = 0;
 };
 
diff --git a/source/reduce/reduction_opportunity_finder.h b/source/reduce/reduction_opportunity_finder.h
new file mode 100644
index 0000000..1837484
--- /dev/null
+++ b/source/reduce/reduction_opportunity_finder.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_REDUCTION_OPPORTUNITY_FINDER_H_
+
+#include "source/opt/ir_context.h"
+#include "source/reduce/reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+// Abstract class for finding opportunities for reducing a SPIR-V module.
+class ReductionOpportunityFinder {
+ public:
+  ReductionOpportunityFinder() = default;
+
+  virtual ~ReductionOpportunityFinder() = default;
+
+  // Finds and returns the reduction opportunities relevant to this pass that
+  // could be applied to the given SPIR-V module.
+  virtual std::vector<std::unique_ptr<ReductionOpportunity>>
+  GetAvailableOpportunities(opt::IRContext* context) const = 0;
+
+  // Provides a name for the finder.
+  virtual std::string GetName() const = 0;
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/reduction_pass.cpp b/source/reduce/reduction_pass.cpp
index befba8b..2cb986d 100644
--- a/source/reduce/reduction_pass.cpp
+++ b/source/reduce/reduction_pass.cpp
@@ -12,9 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include <algorithm>
+#include "source/reduce/reduction_pass.h"
 
-#include "reduction_pass.h"
+#include <algorithm>
 
 #include "source/opt/build_module.h"
 
@@ -34,22 +34,21 @@
   assert(context);
 
   std::vector<std::unique_ptr<ReductionOpportunity>> opportunities =
-      GetAvailableOpportunities(context.get());
+      finder_->GetAvailableOpportunities(context.get());
 
-  if (!is_initialized_) {
-    is_initialized_ = true;
-    index_ = 0;
-    granularity_ = (uint32_t)opportunities.size();
-  }
-
-  if (opportunities.empty()) {
-    granularity_ = 1;
-    return std::vector<uint32_t>();
+  // There is no point in having a granularity larger than the number of
+  // opportunities, so reduce the granularity in this case.
+  if (granularity_ > opportunities.size()) {
+    granularity_ = std::max((uint32_t)1, (uint32_t)opportunities.size());
   }
 
   assert(granularity_ > 0);
 
   if (index_ >= opportunities.size()) {
+    // We have reached the end of the available opportunities and, therefore,
+    // the end of the round for this pass, so reset the index and decrease the
+    // granularity for the next round. Return an empty vector to signal the end
+    // of the round.
     index_ = 0;
     granularity_ = std::max((uint32_t)1, granularity_ / 2);
     return std::vector<uint32_t>();
@@ -61,8 +60,6 @@
     opportunities[i]->TryToApply();
   }
 
-  index_ += granularity_;
-
   std::vector<uint32_t> result;
   context->module()->ToBinary(&result, false);
   return result;
@@ -73,14 +70,17 @@
 }
 
 bool ReductionPass::ReachedMinimumGranularity() const {
-  if (!is_initialized_) {
-    // Conceptually we can think that if the pass has not yet been initialized,
-    // it is operating at unbounded granularity.
-    return false;
-  }
   assert(granularity_ != 0);
   return granularity_ == 1;
 }
 
+std::string ReductionPass::GetName() const { return finder_->GetName(); }
+
+void ReductionPass::NotifyInteresting(bool interesting) {
+  if (!interesting) {
+    index_ += granularity_;
+  }
+}
+
 }  // namespace reduce
 }  // namespace spvtools
diff --git a/source/reduce/reduction_pass.h b/source/reduce/reduction_pass.h
index afb95cc..f2d937b 100644
--- a/source/reduce/reduction_pass.h
+++ b/source/reduce/reduction_pass.h
@@ -15,10 +15,11 @@
 #ifndef SOURCE_REDUCE_REDUCTION_PASS_H_
 #define SOURCE_REDUCE_REDUCTION_PASS_H_
 
-#include "spirv-tools/libspirv.hpp"
+#include <limits>
 
-#include "reduction_opportunity.h"
 #include "source/opt/ir_context.h"
+#include "source/reduce/reduction_opportunity_finder.h"
+#include "spirv-tools/libspirv.hpp"
 
 namespace spvtools {
 namespace reduce {
@@ -32,37 +33,44 @@
 // again, until the minimum granularity is reached.
 class ReductionPass {
  public:
-  // Constructs a reduction pass with a given target environment, |target_env|.
-  // Initially the pass is uninitialized.
-  explicit ReductionPass(const spv_target_env target_env)
-      : target_env_(target_env), is_initialized_(false) {}
+  // Constructs a reduction pass with a given target environment, |target_env|,
+  // and a given finder of reduction opportunities, |finder|.
+  explicit ReductionPass(const spv_target_env target_env,
+                         std::unique_ptr<ReductionOpportunityFinder> finder)
+      : target_env_(target_env),
+        finder_(std::move(finder)),
+        index_(0),
+        granularity_(std::numeric_limits<uint32_t>::max()) {}
 
-  virtual ~ReductionPass() = default;
-
-  // Apply the reduction pass to the given binary.
+  // Applies the reduction pass to the given binary by applying a "chunk" of
+  // reduction opportunities. Returns the new binary if a chunk was applied; in
+  // this case, before the next call the caller must invoke
+  // NotifyInteresting(...) to indicate whether the new binary is interesting.
+  // Returns an empty vector if there are no more chunks left to apply; in this
+  // case, the index will be reset and the granularity lowered for the next
+  // round.
   std::vector<uint32_t> TryApplyReduction(const std::vector<uint32_t>& binary);
 
-  // Set a consumer to which relevant messages will be directed.
+  // Notifies the reduction pass whether the binary returned from
+  // TryApplyReduction is interesting, so that the next call to
+  // TryApplyReduction will avoid applying the same chunk of opportunities.
+  void NotifyInteresting(bool interesting);
+
+  // Sets a consumer to which relevant messages will be directed.
   void SetMessageConsumer(MessageConsumer consumer);
 
-  // Determines whether the granularity with which reduction opportunities are
+  // Returns true if the granularity with which reduction opportunities are
   // applied has reached a minimum.
   bool ReachedMinimumGranularity() const;
 
-  // Returns the name of the reduction pass (useful for monitoring reduction
-  // progress).
-  virtual std::string GetName() const = 0;
-
- protected:
-  // Finds the reduction opportunities relevant to this pass that could be
-  // applied to a given SPIR-V module.
-  virtual std::vector<std::unique_ptr<ReductionOpportunity>>
-  GetAvailableOpportunities(opt::IRContext* context) const = 0;
+  // Returns the name associated with this reduction pass (based on its
+  // associated finder).
+  std::string GetName() const;
 
  private:
   const spv_target_env target_env_;
+  const std::unique_ptr<ReductionOpportunityFinder> finder_;
   MessageConsumer consumer_;
-  bool is_initialized_;
   uint32_t index_;
   uint32_t granularity_;
 };
diff --git a/source/reduce/reduction_util.cpp b/source/reduce/reduction_util.cpp
new file mode 100644
index 0000000..2b2b7e6
--- /dev/null
+++ b/source/reduce/reduction_util.cpp
@@ -0,0 +1,48 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/reduction_util.h"
+
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace reduce {
+
+using opt::IRContext;
+using opt::Instruction;
+
+const uint32_t kTrueBranchOperandIndex = 1;
+const uint32_t kFalseBranchOperandIndex = 2;
+
+uint32_t FindOrCreateGlobalUndef(IRContext* context, uint32_t type_id) {
+  for (auto& inst : context->module()->types_values()) {
+    if (inst.opcode() != SpvOpUndef) {
+      continue;
+    }
+    if (inst.type_id() == type_id) {
+      return inst.result_id();
+    }
+  }
+  // TODO(2182): this is adapted from MemPass::Type2Undef.  In due course it
+  // would be good to factor out this duplication.
+  const uint32_t undef_id = context->TakeNextId();
+  std::unique_ptr<Instruction> undef_inst(
+      new Instruction(context, SpvOpUndef, type_id, undef_id, {}));
+  assert(undef_id == undef_inst->result_id());
+  context->module()->AddGlobalValue(std::move(undef_inst));
+  return undef_id;
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/reduction_util.h b/source/reduce/reduction_util.h
new file mode 100644
index 0000000..b8ffb6e
--- /dev/null
+++ b/source/reduce/reduction_util.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_REDUCTION_UTIL_H_
+#define SOURCE_REDUCE_REDUCTION_UTIL_H_
+
+#include "spirv-tools/libspirv.hpp"
+
+#include "source/opt/ir_context.h"
+#include "source/reduce/reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+extern const uint32_t kTrueBranchOperandIndex;
+extern const uint32_t kFalseBranchOperandIndex;
+
+// Returns an OpUndef id from the global value list that is of the given type,
+// adding one if it does not exist.
+uint32_t FindOrCreateGlobalUndef(opt::IRContext* context, uint32_t type_id);
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_REDUCTION_UTIL_H_
diff --git a/source/reduce/remove_block_reduction_opportunity.cpp b/source/reduce/remove_block_reduction_opportunity.cpp
new file mode 100644
index 0000000..3ad7f72
--- /dev/null
+++ b/source/reduce/remove_block_reduction_opportunity.cpp
@@ -0,0 +1,57 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/remove_block_reduction_opportunity.h"
+
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace reduce {
+
+using opt::BasicBlock;
+using opt::Function;
+
+RemoveBlockReductionOpportunity::RemoveBlockReductionOpportunity(
+    Function* function, BasicBlock* block)
+    : function_(function), block_(block) {
+  // precondition:
+  assert(block_->begin() != block_->end() &&
+         block_->begin()->context()->get_def_use_mgr()->NumUsers(
+             block_->id()) == 0 &&
+         "RemoveBlockReductionOpportunity block must have 0 references");
+}
+
+bool RemoveBlockReductionOpportunity::PreconditionHolds() {
+  // Removing other blocks cannot disable this opportunity.
+  return true;
+}
+
+void RemoveBlockReductionOpportunity::Apply() {
+  // We need an iterator pointing to the block, hence the loop.
+  for (auto bi = function_->begin(); bi != function_->end(); ++bi) {
+    if (bi->id() == block_->id()) {
+      bi->KillAllInsts(true);
+      bi.Erase();
+      // Block removal changes the function, but we don't use analyses, so no
+      // need to invalidate them.
+      return;
+    }
+  }
+
+  assert(false &&
+         "Unreachable: we should have found a block with the desired id.");
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/remove_block_reduction_opportunity.h b/source/reduce/remove_block_reduction_opportunity.h
new file mode 100644
index 0000000..4b358ab
--- /dev/null
+++ b/source/reduce/remove_block_reduction_opportunity.h
@@ -0,0 +1,46 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_H_
+#define SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_H_
+
+#include "source/opt/basic_block.h"
+#include "source/opt/function.h"
+#include "source/reduce/reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+// An opportunity to remove an unreferenced block.
+// See RemoveBlockReductionOpportunityFinder.
+class RemoveBlockReductionOpportunity : public ReductionOpportunity {
+ public:
+  // Creates the opportunity to remove |block| in |function| in |context|.
+  RemoveBlockReductionOpportunity(opt::Function* function,
+                                  opt::BasicBlock* block);
+
+  bool PreconditionHolds() override;
+
+ protected:
+  void Apply() override;
+
+ private:
+  opt::Function* function_;
+  opt::BasicBlock* block_;
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_H_
diff --git a/source/reduce/remove_block_reduction_opportunity_finder.cpp b/source/reduce/remove_block_reduction_opportunity_finder.cpp
new file mode 100644
index 0000000..a3f873f
--- /dev/null
+++ b/source/reduce/remove_block_reduction_opportunity_finder.cpp
@@ -0,0 +1,98 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/remove_block_reduction_opportunity_finder.h"
+
+#include "source/reduce/remove_block_reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+using opt::Function;
+using opt::IRContext;
+using opt::Instruction;
+
+std::string RemoveBlockReductionOpportunityFinder::GetName() const {
+  return "RemoveBlockReductionOpportunityFinder";
+}
+
+std::vector<std::unique_ptr<ReductionOpportunity>>
+RemoveBlockReductionOpportunityFinder::GetAvailableOpportunities(
+    IRContext* context) const {
+  std::vector<std::unique_ptr<ReductionOpportunity>> result;
+
+  // Consider every block in every function.
+  for (auto& function : *context->module()) {
+    for (auto bi = function.begin(); bi != function.end(); ++bi) {
+      if (IsBlockValidOpportunity(context, function, bi)) {
+        result.push_back(spvtools::MakeUnique<RemoveBlockReductionOpportunity>(
+            &function, &*bi));
+      }
+    }
+  }
+  return result;
+}
+
+bool RemoveBlockReductionOpportunityFinder::IsBlockValidOpportunity(
+    IRContext* context, Function& function, Function::iterator& bi) {
+  assert(bi != function.end() && "Block iterator was out of bounds");
+
+  // Don't remove first block; we don't want to end up with no blocks.
+  if (bi == function.begin()) {
+    return false;
+  }
+
+  // Don't remove blocks with references.
+  if (context->get_def_use_mgr()->NumUsers(bi->id()) > 0) {
+    return false;
+  }
+
+  // Don't remove blocks whose instructions have outside references.
+  if (!BlockInstructionsHaveNoOutsideReferences(context, bi)) {
+    return false;
+  }
+
+  return true;
+}
+
+bool RemoveBlockReductionOpportunityFinder::
+    BlockInstructionsHaveNoOutsideReferences(IRContext* context,
+                                             const Function::iterator& bi) {
+  // Get all instructions in block.
+  std::unordered_set<uint32_t> instructions_in_block;
+  for (const Instruction& instruction : *bi) {
+    instructions_in_block.insert(instruction.unique_id());
+  }
+
+  // For each instruction...
+  for (const Instruction& instruction : *bi) {
+    // For each use of the instruction...
+    bool no_uses_outside_block = context->get_def_use_mgr()->WhileEachUser(
+        &instruction, [&instructions_in_block](Instruction* user) -> bool {
+          // If the use is in this block, continue (return true). Otherwise, we
+          // found an outside use; return false (and stop).
+          return instructions_in_block.find(user->unique_id()) !=
+                 instructions_in_block.end();
+        });
+
+    if (!no_uses_outside_block) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/remove_block_reduction_opportunity_finder.h b/source/reduce/remove_block_reduction_opportunity_finder.h
new file mode 100644
index 0000000..83cd04b
--- /dev/null
+++ b/source/reduce/remove_block_reduction_opportunity_finder.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_FINDER_H_
+
+#include "source/opt/function.h"
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder of opportunities to remove a block. The optimizer can remove dead
+// code. However, the reducer needs to be able to remove at a fine-grained
+// level.
+class RemoveBlockReductionOpportunityFinder
+    : public ReductionOpportunityFinder {
+ public:
+  RemoveBlockReductionOpportunityFinder() = default;
+
+  ~RemoveBlockReductionOpportunityFinder() override = default;
+
+  std::string GetName() const final;
+
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      opt::IRContext* context) const final;
+
+ private:
+  // Returns true if the block |bi| in function |function| is a valid
+  // opportunity according to various restrictions.
+  static bool IsBlockValidOpportunity(opt::IRContext* context,
+                                      opt::Function& function,
+                                      opt::Function::iterator& bi);
+
+  // Returns true if the instructions (definitions) in block |bi| have no
+  // references, except for references from inside the block itself.
+  static bool BlockInstructionsHaveNoOutsideReferences(
+      opt::IRContext* context, const opt::Function::iterator& bi);
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/remove_function_reduction_opportunity.cpp b/source/reduce/remove_function_reduction_opportunity.cpp
new file mode 100644
index 0000000..ecad670
--- /dev/null
+++ b/source/reduce/remove_function_reduction_opportunity.cpp
@@ -0,0 +1,41 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/remove_function_reduction_opportunity.h"
+
+#include "source/opt/eliminate_dead_functions_util.h"
+
+namespace spvtools {
+namespace reduce {
+
+bool RemoveFunctionReductionOpportunity::PreconditionHolds() {
+  // Removing one function cannot influence whether another function can be
+  // removed.
+  return true;
+}
+
+void RemoveFunctionReductionOpportunity::Apply() {
+  for (opt::Module::iterator function_it = context_->module()->begin();
+       function_it != context_->module()->end(); ++function_it) {
+    if (&*function_it == function_) {
+      opt::eliminatedeadfunctionsutil::EliminateFunction(context_,
+                                                         &function_it);
+      return;
+    }
+  }
+  assert(0 && "Function to be removed was not found.");
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/remove_function_reduction_opportunity.h b/source/reduce/remove_function_reduction_opportunity.h
new file mode 100644
index 0000000..d8c57db
--- /dev/null
+++ b/source/reduce/remove_function_reduction_opportunity.h
@@ -0,0 +1,49 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_REMOVE_FUNCTION_REDUCTION_OPPORTUNITY_H_
+#define SOURCE_REDUCE_REMOVE_FUNCTION_REDUCTION_OPPORTUNITY_H_
+
+#include "source/opt/function.h"
+#include "source/reduce/reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+// An opportunity to remove an unreferenced function.
+class RemoveFunctionReductionOpportunity : public ReductionOpportunity {
+ public:
+  // Creates an opportunity to remove |function| from the module represented by
+  // |context|.
+  RemoveFunctionReductionOpportunity(opt::IRContext* context,
+                                     opt::Function* function)
+      : context_(context), function_(function) {}
+
+  bool PreconditionHolds() override;
+
+ protected:
+  void Apply() override;
+
+ private:
+  // The IR context for the module under analysis.
+  opt::IRContext* context_;
+
+  // The function that can be removed.
+  opt::Function* function_;
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  //   SOURCE_REDUCE_REMOVE_FUNCTION_REDUCTION_OPPORTUNITY_H_
diff --git a/source/reduce/remove_function_reduction_opportunity_finder.cpp b/source/reduce/remove_function_reduction_opportunity_finder.cpp
new file mode 100644
index 0000000..1edb973
--- /dev/null
+++ b/source/reduce/remove_function_reduction_opportunity_finder.cpp
@@ -0,0 +1,43 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/remove_function_reduction_opportunity_finder.h"
+
+#include "source/reduce/remove_function_reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+std::vector<std::unique_ptr<ReductionOpportunity>>
+RemoveFunctionReductionOpportunityFinder::GetAvailableOpportunities(
+    opt::IRContext* context) const {
+  std::vector<std::unique_ptr<ReductionOpportunity>> result;
+  // Consider each function.
+  for (auto& function : *context->module()) {
+    if (context->get_def_use_mgr()->NumUses(function.result_id()) > 0) {
+      // If the function is referenced, ignore it.
+      continue;
+    }
+    result.push_back(
+        MakeUnique<RemoveFunctionReductionOpportunity>(context, &function));
+  }
+  return result;
+}
+
+std::string RemoveFunctionReductionOpportunityFinder::GetName() const {
+  return "RemoveFunctionReductionOpportunityFinder";
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/remove_function_reduction_opportunity_finder.h b/source/reduce/remove_function_reduction_opportunity_finder.h
new file mode 100644
index 0000000..7952a22
--- /dev/null
+++ b/source/reduce/remove_function_reduction_opportunity_finder.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_REMOVE_FUNCTION_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_REMOVE_FUNCTION_REDUCTION_OPPORTUNITY_FINDER_H_
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder of opportunities to remove unreferenced functions.
+class RemoveFunctionReductionOpportunityFinder
+    : public ReductionOpportunityFinder {
+ public:
+  RemoveFunctionReductionOpportunityFinder() = default;
+
+  ~RemoveFunctionReductionOpportunityFinder() override = default;
+
+  std::string GetName() const final;
+
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      opt::IRContext* context) const final;
+
+ private:
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  //   SOURCE_REDUCE_REMOVE_FUNCTION_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/remove_instruction_reduction_opportunity.cpp b/source/reduce/remove_instruction_reduction_opportunity.cpp
index 7b7a74e..9ca093b 100644
--- a/source/reduce/remove_instruction_reduction_opportunity.cpp
+++ b/source/reduce/remove_instruction_reduction_opportunity.cpp
@@ -12,9 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "source/opt/ir_context.h"
+#include "source/reduce/remove_instruction_reduction_opportunity.h"
 
-#include "remove_instruction_reduction_opportunity.h"
+#include "source/opt/ir_context.h"
 
 namespace spvtools {
 namespace reduce {
diff --git a/source/reduce/remove_instruction_reduction_opportunity.h b/source/reduce/remove_instruction_reduction_opportunity.h
index 471ff15..07bef50 100644
--- a/source/reduce/remove_instruction_reduction_opportunity.h
+++ b/source/reduce/remove_instruction_reduction_opportunity.h
@@ -15,30 +15,27 @@
 #ifndef SOURCE_REDUCE_REMOVE_INSTRUCTION_REDUCTION_OPPORTUNITY_H_
 #define SOURCE_REDUCE_REMOVE_INSTRUCTION_REDUCTION_OPPORTUNITY_H_
 
-#include "reduction_opportunity.h"
 #include "source/opt/instruction.h"
+#include "source/reduce/reduction_opportunity.h"
 
 namespace spvtools {
 namespace reduce {
 
-using namespace opt;
-
-// Captures the opportunity to remove an instruction from the SPIR-V module.
+// An opportunity to remove an instruction from the SPIR-V module.
 class RemoveInstructionReductionOpportunity : public ReductionOpportunity {
  public:
   // Constructs the opportunity to remove |inst|.
-  explicit RemoveInstructionReductionOpportunity(Instruction* inst)
+  explicit RemoveInstructionReductionOpportunity(opt::Instruction* inst)
       : inst_(inst) {}
 
-  // This kind of opportunity can be unconditionally applied.
+  // Always returns true, as this opportunity can always be applied.
   bool PreconditionHolds() override;
 
  protected:
-  // Remove the instruction.
   void Apply() override;
 
  private:
-  Instruction* inst_;
+  opt::Instruction* inst_;
 };
 
 }  // namespace reduce
diff --git a/source/reduce/remove_opname_instruction_reduction_pass.cpp b/source/reduce/remove_opname_instruction_reduction_opportunity_finder.cpp
similarity index 72%
rename from source/reduce/remove_opname_instruction_reduction_pass.cpp
rename to source/reduce/remove_opname_instruction_reduction_opportunity_finder.cpp
index bf99bc5..f687d71 100644
--- a/source/reduce/remove_opname_instruction_reduction_pass.cpp
+++ b/source/reduce/remove_opname_instruction_reduction_opportunity_finder.cpp
@@ -12,19 +12,20 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "remove_opname_instruction_reduction_pass.h"
-#include "remove_instruction_reduction_opportunity.h"
+#include "source/reduce/remove_opname_instruction_reduction_opportunity_finder.h"
+
 #include "source/opcode.h"
 #include "source/opt/instruction.h"
+#include "source/reduce/remove_instruction_reduction_opportunity.h"
 
 namespace spvtools {
 namespace reduce {
 
-using namespace opt;
+using opt::IRContext;
 
 std::vector<std::unique_ptr<ReductionOpportunity>>
-RemoveOpNameInstructionReductionPass::GetAvailableOpportunities(
-    opt::IRContext* context) const {
+RemoveOpNameInstructionReductionOpportunityFinder::GetAvailableOpportunities(
+    IRContext* context) const {
   std::vector<std::unique_ptr<ReductionOpportunity>> result;
 
   for (auto& inst : context->module()->debugs2()) {
@@ -36,8 +37,8 @@
   return result;
 }
 
-std::string RemoveOpNameInstructionReductionPass::GetName() const {
-  return "RemoveOpNameInstructionReductionPass";
+std::string RemoveOpNameInstructionReductionOpportunityFinder::GetName() const {
+  return "RemoveOpNameInstructionReductionOpportunityFinder";
 }
 
 }  // namespace reduce
diff --git a/source/reduce/remove_opname_instruction_reduction_opportunity_finder.h b/source/reduce/remove_opname_instruction_reduction_opportunity_finder.h
new file mode 100644
index 0000000..8b9fd6f
--- /dev/null
+++ b/source/reduce/remove_opname_instruction_reduction_opportunity_finder.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_REMOVE_OPNAME_INSTRUCTION_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_REMOVE_OPNAME_INSTRUCTION_REDUCTION_OPPORTUNITY_FINDER_H_
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder for opportunities to remove OpName instructions.  As well as making
+// the module smaller, removing an OpName instruction may create opportunities
+// for subsequently removing the instructions that create the ids to which the
+// OpName applies.
+class RemoveOpNameInstructionReductionOpportunityFinder
+    : public ReductionOpportunityFinder {
+ public:
+  RemoveOpNameInstructionReductionOpportunityFinder() = default;
+
+  ~RemoveOpNameInstructionReductionOpportunityFinder() override = default;
+
+  std::string GetName() const final;
+
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      opt::IRContext* context) const final;
+
+ private:
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_REMOVE_OPNAME_INSTRUCTION_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/remove_opname_instruction_reduction_pass.h b/source/reduce/remove_opname_instruction_reduction_pass.h
deleted file mode 100644
index d20b6e1..0000000
--- a/source/reduce/remove_opname_instruction_reduction_pass.h
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright (c) 2018 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SOURCE_REDUCE_REMOVE_OPNAME_INSTRUCTION_REDUCTION_PASS_H_
-#define SOURCE_REDUCE_REMOVE_OPNAME_INSTRUCTION_REDUCTION_PASS_H_
-
-#include "reduction_pass.h"
-
-namespace spvtools {
-namespace reduce {
-
-// A reduction pass for removing OpName instructions.  As well as making the
-// module smaller, removing an OpName instruction may create opportunities to
-// remove the instruction that create the id to which the OpName applies.
-class RemoveOpNameInstructionReductionPass : public ReductionPass {
- public:
-  // Creates the reduction pass in the context of the given target environment
-  // |target_env|
-  explicit RemoveOpNameInstructionReductionPass(const spv_target_env target_env)
-      : ReductionPass(target_env) {}
-
-  ~RemoveOpNameInstructionReductionPass() override = default;
-
-  // The name of this pass.
-  std::string GetName() const final;
-
- protected:
-  // Finds all opportunities for removing opName instructions in the
-  // given module.
-  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
-      opt::IRContext* context) const final;
-
- private:
-};
-
-}  // namespace reduce
-}  // namespace spvtools
-
-#endif  // SOURCE_REDUCE_REMOVE_OpName_INSTRUCTION_REDUCTION_PASS_H_
diff --git a/source/reduce/remove_selection_reduction_opportunity.cpp b/source/reduce/remove_selection_reduction_opportunity.cpp
new file mode 100644
index 0000000..96f0147
--- /dev/null
+++ b/source/reduce/remove_selection_reduction_opportunity.cpp
@@ -0,0 +1,31 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/remove_selection_reduction_opportunity.h"
+
+#include "source/opt/basic_block.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace reduce {
+
+bool RemoveSelectionReductionOpportunity::PreconditionHolds() { return true; }
+
+void RemoveSelectionReductionOpportunity::Apply() {
+  auto merge_instruction = header_block_->GetMergeInst();
+  merge_instruction->context()->KillInst(merge_instruction);
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/remove_selection_reduction_opportunity.h b/source/reduce/remove_selection_reduction_opportunity.h
new file mode 100644
index 0000000..892618e
--- /dev/null
+++ b/source/reduce/remove_selection_reduction_opportunity.h
@@ -0,0 +1,47 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_REMOVE_SELECTION_REDUCTION_OPPORTUNITY_H_
+#define SOURCE_REDUCE_REMOVE_SELECTION_REDUCTION_OPPORTUNITY_H_
+
+#include "source/opt/basic_block.h"
+#include "source/reduce/reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+// An opportunity for removing a selection construct by simply removing the
+// OpSelectionMerge instruction; thus, the selection must have already been
+// simplified to a point where the instruction can be trivially removed.
+class RemoveSelectionReductionOpportunity : public ReductionOpportunity {
+ public:
+  // Constructs a reduction opportunity from the selection header |block| in
+  // |function|.
+  RemoveSelectionReductionOpportunity(opt::BasicBlock* header_block)
+      : header_block_(header_block) {}
+
+  bool PreconditionHolds() override;
+
+ protected:
+  void Apply() override;
+
+ private:
+  // The header block of the selection.
+  opt::BasicBlock* header_block_;
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  //   SOURCE_REDUCE_REMOVE_SELECTION_REDUCTION_OPPORTUNITY_H_
diff --git a/source/reduce/remove_selection_reduction_opportunity_finder.cpp b/source/reduce/remove_selection_reduction_opportunity_finder.cpp
new file mode 100644
index 0000000..45821e2
--- /dev/null
+++ b/source/reduce/remove_selection_reduction_opportunity_finder.cpp
@@ -0,0 +1,150 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/remove_selection_reduction_opportunity_finder.h"
+
+#include "source/reduce/remove_selection_reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+using opt::BasicBlock;
+using opt::IRContext;
+using opt::Instruction;
+
+namespace {
+const uint32_t kMergeNodeIndex = 0;
+const uint32_t kContinueNodeIndex = 1;
+}  // namespace
+
+std::string RemoveSelectionReductionOpportunityFinder::GetName() const {
+  return "RemoveSelectionReductionOpportunityFinder";
+}
+
+std::vector<std::unique_ptr<ReductionOpportunity>>
+RemoveSelectionReductionOpportunityFinder::GetAvailableOpportunities(
+    IRContext* context) const {
+  // Get all loop merge and continue blocks so we can check for these later.
+  std::unordered_set<uint32_t> merge_and_continue_blocks_from_loops;
+  for (auto& function : *context->module()) {
+    for (auto& block : function) {
+      if (auto merge_instruction = block.GetMergeInst()) {
+        if (merge_instruction->opcode() == SpvOpLoopMerge) {
+          uint32_t merge_block_id =
+              merge_instruction->GetSingleWordOperand(kMergeNodeIndex);
+          uint32_t continue_block_id =
+              merge_instruction->GetSingleWordOperand(kContinueNodeIndex);
+          merge_and_continue_blocks_from_loops.insert(merge_block_id);
+          merge_and_continue_blocks_from_loops.insert(continue_block_id);
+        }
+      }
+    }
+  }
+
+  // Return all selection headers where the OpSelectionMergeInstruction can be
+  // removed.
+  std::vector<std::unique_ptr<ReductionOpportunity>> result;
+  for (auto& function : *context->module()) {
+    for (auto& block : function) {
+      if (auto merge_instruction = block.GetMergeInst()) {
+        if (merge_instruction->opcode() == SpvOpSelectionMerge) {
+          if (CanOpSelectionMergeBeRemoved(
+                  context, block, merge_instruction,
+                  merge_and_continue_blocks_from_loops)) {
+            result.push_back(
+                MakeUnique<RemoveSelectionReductionOpportunity>(&block));
+          }
+        }
+      }
+    }
+  }
+  return result;
+}
+
+bool RemoveSelectionReductionOpportunityFinder::CanOpSelectionMergeBeRemoved(
+    IRContext* context, const BasicBlock& header_block,
+    Instruction* merge_instruction,
+    std::unordered_set<uint32_t> merge_and_continue_blocks_from_loops) {
+  assert(header_block.GetMergeInst() == merge_instruction &&
+         "CanOpSelectionMergeBeRemoved(...): header block and merge "
+         "instruction mismatch");
+
+  // The OpSelectionMerge instruction is needed if either of the following are
+  // true.
+  //
+  // 1. The header block has at least two (unique) successors that are not
+  // merge or continue blocks of a loop.
+  //
+  // 2. The predecessors of the merge block are "using" the merge block to avoid
+  // divergence. In other words, there exists a predecessor of the merge block
+  // that has a successor that is not the merge block of this construct and not
+  // a merge or continue block of a loop.
+
+  // 1.
+  {
+    uint32_t divergent_successor_count = 0;
+
+    std::unordered_set<uint32_t> seen_successors;
+
+    header_block.ForEachSuccessorLabel(
+        [&seen_successors, &merge_and_continue_blocks_from_loops,
+         &divergent_successor_count](uint32_t successor) {
+          // Not already seen.
+          if (seen_successors.find(successor) == seen_successors.end()) {
+            seen_successors.insert(successor);
+            // Not a loop continue or merge.
+            if (merge_and_continue_blocks_from_loops.find(successor) ==
+                merge_and_continue_blocks_from_loops.end()) {
+              ++divergent_successor_count;
+            }
+          }
+        });
+
+    if (divergent_successor_count > 1) {
+      return false;
+    }
+  }
+
+  // 2.
+  {
+    uint32_t merge_block_id =
+        merge_instruction->GetSingleWordOperand(kMergeNodeIndex);
+    for (uint32_t predecessor_block_id :
+         context->cfg()->preds(merge_block_id)) {
+      const BasicBlock* predecessor_block =
+          context->cfg()->block(predecessor_block_id);
+      assert(predecessor_block);
+      bool found_divergent_successor = false;
+      predecessor_block->ForEachSuccessorLabel(
+          [&found_divergent_successor, merge_block_id,
+           &merge_and_continue_blocks_from_loops](uint32_t successor_id) {
+            // The successor is not the merge block, nor a loop merge or
+            // continue.
+            if (successor_id != merge_block_id &&
+                merge_and_continue_blocks_from_loops.find(successor_id) ==
+                    merge_and_continue_blocks_from_loops.end()) {
+              found_divergent_successor = true;
+            }
+          });
+      if (found_divergent_successor) {
+        return false;
+      }
+    }
+  }
+
+  return true;
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/remove_selection_reduction_opportunity_finder.h b/source/reduce/remove_selection_reduction_opportunity_finder.h
new file mode 100644
index 0000000..848122b
--- /dev/null
+++ b/source/reduce/remove_selection_reduction_opportunity_finder.h
@@ -0,0 +1,49 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_REMOVE_SELECTION_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_REMOVE_SELECTION_REDUCTION_OPPORTUNITY_FINDER_H_
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder for opportunities for removing a selection construct by simply
+// removing the OpSelectionMerge instruction; thus, the selections must have
+// already been simplified to a point where they can be trivially removed.
+class RemoveSelectionReductionOpportunityFinder
+    : public ReductionOpportunityFinder {
+ public:
+  RemoveSelectionReductionOpportunityFinder() = default;
+
+  ~RemoveSelectionReductionOpportunityFinder() override = default;
+
+  std::string GetName() const final;
+
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      opt::IRContext* context) const final;
+
+  // Returns true if the OpSelectionMerge instruction |merge_instruction| in
+  // block |header_block| can be removed.
+  static bool CanOpSelectionMergeBeRemoved(
+      opt::IRContext* context, const opt::BasicBlock& header_block,
+      opt::Instruction* merge_instruction,
+      std::unordered_set<uint32_t> merge_and_continue_blocks_from_loops);
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_REMOVE_SELECTION_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/remove_unreferenced_instruction_reduction_pass.cpp b/source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.cpp
similarity index 79%
rename from source/reduce/remove_unreferenced_instruction_reduction_pass.cpp
rename to source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.cpp
index bc4998d..8f32435 100644
--- a/source/reduce/remove_unreferenced_instruction_reduction_pass.cpp
+++ b/source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.cpp
@@ -12,19 +12,20 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "remove_unreferenced_instruction_reduction_pass.h"
-#include "remove_instruction_reduction_opportunity.h"
+#include "source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h"
+
 #include "source/opcode.h"
 #include "source/opt/instruction.h"
+#include "source/reduce/remove_instruction_reduction_opportunity.h"
 
 namespace spvtools {
 namespace reduce {
 
-using namespace opt;
+using opt::IRContext;
 
 std::vector<std::unique_ptr<ReductionOpportunity>>
-RemoveUnreferencedInstructionReductionPass::GetAvailableOpportunities(
-    opt::IRContext* context) const {
+RemoveUnreferencedInstructionReductionOpportunityFinder::
+    GetAvailableOpportunities(IRContext* context) const {
   std::vector<std::unique_ptr<ReductionOpportunity>> result;
 
   for (auto& function : *context->module()) {
@@ -52,8 +53,9 @@
   return result;
 }
 
-std::string RemoveUnreferencedInstructionReductionPass::GetName() const {
-  return "RemoveUnreferencedInstructionReductionPass";
+std::string RemoveUnreferencedInstructionReductionOpportunityFinder::GetName()
+    const {
+  return "RemoveUnreferencedInstructionReductionOpportunityFinder";
 }
 
 }  // namespace reduce
diff --git a/source/reduce/remove_unreferenced_instruction_reduction_pass.h b/source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h
similarity index 60%
rename from source/reduce/remove_unreferenced_instruction_reduction_pass.h
rename to source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h
index 27c3fc2..30f460b 100644
--- a/source/reduce/remove_unreferenced_instruction_reduction_pass.h
+++ b/source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h
@@ -12,35 +12,28 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef SOURCE_REDUCE_REMOVE_UNREFERENCED_INSTRUCTION_REDUCTION_PASS_H_
-#define SOURCE_REDUCE_REMOVE_UNREFERENCED_INSTRUCTION_REDUCTION_PASS_H_
+#ifndef SOURCE_REDUCE_REMOVE_UNREFERENCED_INSTRUCTION_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_REMOVE_UNREFERENCED_INSTRUCTION_REDUCTION_OPPORTUNITY_FINDER_H_
 
-#include "reduction_pass.h"
+#include "source/reduce/reduction_opportunity_finder.h"
 
 namespace spvtools {
 namespace reduce {
 
-// A reduction pass for removing non-control-flow instructions in blocks in
-// cases where the instruction's id is not referenced.  As well as making the
-// module smaller, removing an instruction that referenced particular ids may
+// A finder for opportunities to remove non-control-flow instructions in blocks
+// in cases where the instruction's id is not referenced.  As well as making the
+// module smaller, removing an instruction that references particular ids may
 // create opportunities for subsequently removing the instructions that
 // generated those ids.
-class RemoveUnreferencedInstructionReductionPass : public ReductionPass {
+class RemoveUnreferencedInstructionReductionOpportunityFinder
+    : public ReductionOpportunityFinder {
  public:
-  // Creates the reduction pass in the context of the given target environment
-  // |target_env|
-  explicit RemoveUnreferencedInstructionReductionPass(
-      const spv_target_env target_env)
-      : ReductionPass(target_env) {}
+  RemoveUnreferencedInstructionReductionOpportunityFinder() = default;
 
-  ~RemoveUnreferencedInstructionReductionPass() override = default;
+  ~RemoveUnreferencedInstructionReductionOpportunityFinder() override = default;
 
-  // The name of this pass.
   std::string GetName() const final;
 
- protected:
-  // Finds all opportunities for removing unreferenced instructions in the
-  // given module.
   std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
       opt::IRContext* context) const final;
 
@@ -50,4 +43,4 @@
 }  // namespace reduce
 }  // namespace spvtools
 
-#endif  // SOURCE_REDUCE_REMOVE_UNREFERENCED_INSTRUCTION_REDUCTION_PASS_H_
+#endif  // SOURCE_REDUCE_REMOVE_UNREFERENCED_INSTRUCTION_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.cpp b/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.cpp
new file mode 100644
index 0000000..17a5c7e
--- /dev/null
+++ b/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.cpp
@@ -0,0 +1,65 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h"
+
+#include "source/reduce/reduction_util.h"
+#include "source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+using opt::IRContext;
+using opt::Instruction;
+
+std::vector<std::unique_ptr<ReductionOpportunity>>
+SimpleConditionalBranchToBranchOpportunityFinder::GetAvailableOpportunities(
+    IRContext* context) const {
+  std::vector<std::unique_ptr<ReductionOpportunity>> result;
+
+  // Consider every function.
+  for (auto& function : *context->module()) {
+    // Consider every block in the function.
+    for (auto& block : function) {
+      // The terminator must be SpvOpBranchConditional.
+      Instruction* terminator = block.terminator();
+      if (terminator->opcode() != SpvOpBranchConditional) {
+        continue;
+      }
+      // It must not be a selection header, as these cannot be followed by
+      // OpBranch.
+      if (block.GetMergeInst() &&
+          block.GetMergeInst()->opcode() == SpvOpSelectionMerge) {
+        continue;
+      }
+      // The conditional branch must be simplified.
+      if (terminator->GetSingleWordInOperand(kTrueBranchOperandIndex) !=
+          terminator->GetSingleWordInOperand(kFalseBranchOperandIndex)) {
+        continue;
+      }
+
+      result.push_back(
+          MakeUnique<SimpleConditionalBranchToBranchReductionOpportunity>(
+              block.terminator()));
+    }
+  }
+  return result;
+}
+
+std::string SimpleConditionalBranchToBranchOpportunityFinder::GetName() const {
+  return "SimpleConditionalBranchToBranchOpportunityFinder";
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h b/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h
new file mode 100644
index 0000000..10b9dce
--- /dev/null
+++ b/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_SIMPLE_CONDITIONAL_BRANCH_TO_BRANCH_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_SIMPLE_CONDITIONAL_BRANCH_TO_BRANCH_OPPORTUNITY_FINDER_H_
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder for opportunities to change simple conditional branches (conditional
+// branches with one target) to an OpBranch.
+class SimpleConditionalBranchToBranchOpportunityFinder
+    : public ReductionOpportunityFinder {
+ public:
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      opt::IRContext* context) const override;
+
+  std::string GetName() const override;
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_SIMPLE_CONDITIONAL_BRANCH_TO_BRANCH_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.cpp b/source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.cpp
new file mode 100644
index 0000000..8968b96
--- /dev/null
+++ b/source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.cpp
@@ -0,0 +1,59 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.h"
+
+#include "source/reduce/reduction_util.h"
+
+namespace spvtools {
+namespace reduce {
+
+using namespace opt;
+
+SimpleConditionalBranchToBranchReductionOpportunity::
+    SimpleConditionalBranchToBranchReductionOpportunity(
+        Instruction* conditional_branch_instruction)
+    : conditional_branch_instruction_(conditional_branch_instruction) {}
+
+bool SimpleConditionalBranchToBranchReductionOpportunity::PreconditionHolds() {
+  // We find at most one opportunity per conditional branch and simplifying
+  // another branch cannot disable this opportunity.
+  return true;
+}
+
+void SimpleConditionalBranchToBranchReductionOpportunity::Apply() {
+  assert(conditional_branch_instruction_->opcode() == SpvOpBranchConditional &&
+         "SimpleConditionalBranchToBranchReductionOpportunity: branch was not "
+         "a conditional branch");
+
+  assert(conditional_branch_instruction_->GetSingleWordInOperand(
+             kTrueBranchOperandIndex) ==
+             conditional_branch_instruction_->GetSingleWordInOperand(
+                 kFalseBranchOperandIndex) &&
+         "SimpleConditionalBranchToBranchReductionOpportunity: branch was not "
+         "simple");
+
+  // OpBranchConditional %condition %block_id %block_id ...
+  // ->
+  // OpBranch %block_id
+
+  conditional_branch_instruction_->SetOpcode(SpvOpBranch);
+  conditional_branch_instruction_->ReplaceOperands(
+      {{SPV_OPERAND_TYPE_ID,
+        {conditional_branch_instruction_->GetSingleWordInOperand(
+            kTrueBranchOperandIndex)}}});
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.h b/source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.h
new file mode 100644
index 0000000..eddb464
--- /dev/null
+++ b/source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_SIMPLE_CONDITIONAL_BRANCH_TO_BRANCH_REDUCTION_OPPORTUNITY_H_
+#define SOURCE_REDUCE_SIMPLE_CONDITIONAL_BRANCH_TO_BRANCH_REDUCTION_OPPORTUNITY_H_
+
+#include "source/opt/instruction.h"
+#include "source/reduce/reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+// An opportunity to change simple conditional branches (conditional branches
+// with one target) to an OpBranch.
+class SimpleConditionalBranchToBranchReductionOpportunity
+    : public ReductionOpportunity {
+ public:
+  // Constructs an opportunity to simplify |conditional_branch_instruction|.
+  explicit SimpleConditionalBranchToBranchReductionOpportunity(
+      opt::Instruction* conditional_branch_instruction);
+
+  bool PreconditionHolds() override;
+
+ protected:
+  void Apply() override;
+
+ private:
+  opt::Instruction* conditional_branch_instruction_;
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_SIMPLE_CONDITIONAL_BRANCH_TO_BRANCH_REDUCTION_OPPORTUNITY_H_
diff --git a/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp b/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp
index 679cfc1..afc1298 100644
--- a/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp
+++ b/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp
@@ -12,16 +12,22 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "structured_loop_to_selection_reduction_opportunity.h"
+#include "source/reduce/structured_loop_to_selection_reduction_opportunity.h"
+
 #include "source/opt/aggressive_dead_code_elim_pass.h"
 #include "source/opt/ir_context.h"
+#include "source/reduce/reduction_util.h"
 
 namespace spvtools {
 namespace reduce {
 
+using opt::BasicBlock;
+using opt::IRContext;
+using opt::Instruction;
+using opt::Operand;
+
 namespace {
 const uint32_t kMergeNodeIndex = 0;
-const uint32_t kContinueNodeIndex = 1;
 }  // namespace
 
 bool StructuredLoopToSelectionReductionOpportunity::PreconditionHolds() {
@@ -41,15 +47,11 @@
 
   // (1) Redirect edges that point to the loop's continue target to their
   // closest merge block.
-  RedirectToClosestMergeBlock(
-      loop_construct_header_->GetLoopMergeInst()->GetSingleWordOperand(
-          kContinueNodeIndex));
+  RedirectToClosestMergeBlock(loop_construct_header_->ContinueBlockId());
 
   // (2) Redirect edges that point to the loop's merge block to their closest
   // merge block (which might be that of an enclosing selection, for instance).
-  RedirectToClosestMergeBlock(
-      loop_construct_header_->GetLoopMergeInst()->GetSingleWordOperand(
-          kMergeNodeIndex));
+  RedirectToClosestMergeBlock(loop_construct_header_->MergeBlockId());
 
   // (3) Turn the loop construct header into a selection.
   ChangeLoopToSelection();
@@ -125,12 +127,8 @@
 
   // original_target_id must either be the merge target or continue construct
   // for the loop being operated on.
-  assert(original_target_id ==
-             loop_construct_header_->GetMergeInst()->GetSingleWordOperand(
-                 kMergeNodeIndex) ||
-         original_target_id ==
-             loop_construct_header_->GetMergeInst()->GetSingleWordOperand(
-                 kContinueNodeIndex));
+  assert(original_target_id == loop_construct_header_->MergeBlockId() ||
+         original_target_id == loop_construct_header_->ContinueBlockId());
 
   auto terminator = context_->cfg()->block(source_id)->terminator();
 
@@ -191,7 +189,7 @@
   to_block->ForEachPhiInst([this, &from_id](Instruction* phi_inst) {
     // Add to the phi operand an (undef, from_id) pair to reflect the added
     // edge.
-    auto undef_id = FindOrCreateGlobalUndef(phi_inst->type_id());
+    auto undef_id = FindOrCreateGlobalUndef(context_, phi_inst->type_id());
     phi_inst->AddOperand(Operand(SPV_OPERAND_TYPE_ID, {undef_id}));
     phi_inst->AddOperand(Operand(SPV_OPERAND_TYPE_ID, {from_id}));
   });
@@ -215,11 +213,11 @@
   // the "else" branch be the merge block.
   auto terminator = loop_construct_header_->terminator();
   if (terminator->opcode() == SpvOpBranch) {
-    analysis::Bool temp;
-    const analysis::Bool* bool_type =
+    opt::analysis::Bool temp;
+    const opt::analysis::Bool* bool_type =
         context_->get_type_mgr()->GetRegisteredType(&temp)->AsBool();
     auto const_mgr = context_->get_constant_mgr();
-    auto true_const = const_mgr->GetConstant(bool_type, {true});
+    auto true_const = const_mgr->GetConstant(bool_type, {1});
     auto true_const_result_id =
         const_mgr->GetDefiningInstruction(true_const)->result_id();
     auto original_branch_id = terminator->GetSingleWordOperand(0);
@@ -248,6 +246,10 @@
       context_->get_def_use_mgr()->ForEachUse(&def, [this, &block, &def](
                                                         Instruction* use,
                                                         uint32_t index) {
+        // Ignore uses outside of blocks, such as in OpDecorate.
+        if (context_->get_instr_block(use) == nullptr) {
+          return;
+        }
         // If a use is not appropriately dominated by its definition,
         // replace the use with an OpUndef, unless the definition is an
         // access chain, in which case replace it with some (possibly fresh)
@@ -273,7 +275,8 @@
                 break;
             }
           } else {
-            use->SetOperand(index, {FindOrCreateGlobalUndef(def.type_id())});
+            use->SetOperand(index,
+                            {FindOrCreateGlobalUndef(context_, def.type_id())});
           }
         }
       });
@@ -296,26 +299,6 @@
       ->Dominates(def, use);
 }
 
-uint32_t StructuredLoopToSelectionReductionOpportunity::FindOrCreateGlobalUndef(
-    uint32_t type_id) {
-  for (auto& inst : context_->module()->types_values()) {
-    if (inst.opcode() != SpvOpUndef) {
-      continue;
-    }
-    if (inst.type_id() == type_id) {
-      return inst.result_id();
-    }
-  }
-  // TODO(2182): this is adapted from MemPass::Type2Undef.  In due course it
-  // would be good to factor out this duplication.
-  const uint32_t undef_id = context_->TakeNextId();
-  std::unique_ptr<Instruction> undef_inst(
-      new Instruction(context_, SpvOpUndef, type_id, undef_id, {}));
-  assert(undef_id == undef_inst->result_id());
-  context_->module()->AddGlobalValue(std::move(undef_inst));
-  return undef_id;
-}
-
 uint32_t
 StructuredLoopToSelectionReductionOpportunity::FindOrCreateGlobalVariable(
     uint32_t pointer_type_id) {
diff --git a/source/reduce/structured_loop_to_selection_reduction_opportunity.h b/source/reduce/structured_loop_to_selection_reduction_opportunity.h
index b139016..f6c065b 100644
--- a/source/reduce/structured_loop_to_selection_reduction_opportunity.h
+++ b/source/reduce/structured_loop_to_selection_reduction_opportunity.h
@@ -15,36 +15,33 @@
 #ifndef SOURCE_REDUCE_CUT_LOOP_REDUCTION_OPPORTUNITY_H_
 #define SOURCE_REDUCE_CUT_LOOP_REDUCTION_OPPORTUNITY_H_
 
-#include <source/opt/def_use_manager.h>
-#include "reduction_opportunity.h"
+#include "source/opt/def_use_manager.h"
 #include "source/opt/dominator_analysis.h"
 #include "source/opt/function.h"
+#include "source/reduce/reduction_opportunity.h"
 
 namespace spvtools {
 namespace reduce {
 
-using namespace opt;
-
-// Captures an opportunity to replace a structured loop with a selection.
+// An opportunity to replace a structured loop with a selection.
 class StructuredLoopToSelectionReductionOpportunity
     : public ReductionOpportunity {
  public:
   // Constructs an opportunity from a loop header block and the function that
   // encloses it.
   explicit StructuredLoopToSelectionReductionOpportunity(
-      IRContext* context, BasicBlock* loop_construct_header,
-      Function* enclosing_function)
+      opt::IRContext* context, opt::BasicBlock* loop_construct_header,
+      opt::Function* enclosing_function)
       : context_(context),
         loop_construct_header_(loop_construct_header),
         enclosing_function_(enclosing_function) {}
 
-  // We require the loop header to be reachable.  A structured loop might
+  // Returns true if the loop header is reachable.  A structured loop might
   // become unreachable as a result of turning another structured loop into
   // a selection.
   bool PreconditionHolds() override;
 
  protected:
-  // Perform the structured loop to selection transformation.
   void Apply() override;
 
  private:
@@ -68,11 +65,12 @@
   // Removes any components of |to_block|'s phi instructions relating to
   // |from_id|.
   void AdaptPhiInstructionsForRemovedEdge(uint32_t from_id,
-                                          BasicBlock* to_block);
+                                          opt::BasicBlock* to_block);
 
   // Adds components to |to_block|'s phi instructions to account for a new
   // incoming edge from |from_id|.
-  void AdaptPhiInstructionsForAddedEdge(uint32_t from_id, BasicBlock* to_block);
+  void AdaptPhiInstructionsForAddedEdge(uint32_t from_id,
+                                        opt::BasicBlock* to_block);
 
   // Turns the OpLoopMerge for the loop into OpSelectionMerge, and adapts the
   // following branch instruction accordingly.
@@ -88,17 +86,10 @@
   // 2) |def| is an OpVariable
   // 3) |use| is part of an OpPhi, with associated incoming block b, and |def|
   // dominates b.
-  bool DefinitionSufficientlyDominatesUse(Instruction* def, Instruction* use,
+  bool DefinitionSufficientlyDominatesUse(opt::Instruction* def,
+                                          opt::Instruction* use,
                                           uint32_t use_index,
-                                          BasicBlock& def_block);
-
-  // Checks whether the global value list has an OpUndef of the given type,
-  // adding one if not, and returns the id of such an OpUndef.
-  //
-  // TODO(2184): This will likely be used by other reduction passes, so should
-  // be factored out in due course.  Parts of the spirv-opt framework provide
-  // similar functionality, so there may be a case for further refactoring.
-  uint32_t FindOrCreateGlobalUndef(uint32_t type_id);
+                                          opt::BasicBlock& def_block);
 
   // Checks whether the global value list has an OpVariable of the given pointer
   // type, adding one if not, and returns the id of such an OpVariable.
@@ -114,9 +105,9 @@
   // be factored out in due course.
   uint32_t FindOrCreateFunctionVariable(uint32_t pointer_type_id);
 
-  IRContext* context_;
-  BasicBlock* loop_construct_header_;
-  Function* enclosing_function_;
+  opt::IRContext* context_;
+  opt::BasicBlock* loop_construct_header_;
+  opt::Function* enclosing_function_;
 };
 
 }  // namespace reduce
diff --git a/source/reduce/structured_loop_to_selection_reduction_pass.cpp b/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.cpp
similarity index 73%
rename from source/reduce/structured_loop_to_selection_reduction_pass.cpp
rename to source/reduce/structured_loop_to_selection_reduction_opportunity_finder.cpp
index 768a2e8..085b267 100644
--- a/source/reduce/structured_loop_to_selection_reduction_pass.cpp
+++ b/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.cpp
@@ -12,13 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "structured_loop_to_selection_reduction_pass.h"
-#include "structured_loop_to_selection_reduction_opportunity.h"
+#include "source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h"
+
+#include "source/reduce/structured_loop_to_selection_reduction_opportunity.h"
 
 namespace spvtools {
 namespace reduce {
 
-using namespace opt;
+using opt::IRContext;
 
 namespace {
 const uint32_t kMergeNodeIndex = 0;
@@ -26,17 +27,16 @@
 }  // namespace
 
 std::vector<std::unique_ptr<ReductionOpportunity>>
-StructuredLoopToSelectionReductionPass::GetAvailableOpportunities(
-    opt::IRContext* context) const {
+StructuredLoopToSelectionReductionOpportunityFinder::GetAvailableOpportunities(
+    IRContext* context) const {
   std::vector<std::unique_ptr<ReductionOpportunity>> result;
 
   std::set<uint32_t> merge_block_ids;
   for (auto& function : *context->module()) {
     for (auto& block : function) {
-      auto merge_inst = block.GetMergeInst();
-      if (merge_inst) {
-        merge_block_ids.insert(
-            merge_inst->GetSingleWordOperand(kMergeNodeIndex));
+      auto merge_block_id = block.MergeBlockIdIfAny();
+      if (merge_block_id) {
+        merge_block_ids.insert(merge_block_id);
       }
     }
   }
@@ -50,11 +50,19 @@
         continue;
       }
 
+      uint32_t continue_block_id =
+          loop_merge_inst->GetSingleWordOperand(kContinueNodeIndex);
+
       // Check whether the loop construct's continue target is the merge block
       // of some structured control flow construct.  If it is, we cautiously do
       // not consider applying a transformation.
-      if (merge_block_ids.find(loop_merge_inst->GetSingleWordOperand(
-              kContinueNodeIndex)) != merge_block_ids.end()) {
+      if (merge_block_ids.find(continue_block_id) != merge_block_ids.end()) {
+        continue;
+      }
+
+      // Check whether the loop header block is also the continue target. If it
+      // is, we cautiously do not consider applying a transformation.
+      if (block.id() == continue_block_id) {
         continue;
       }
 
@@ -87,8 +95,9 @@
   return result;
 }
 
-std::string StructuredLoopToSelectionReductionPass::GetName() const {
-  return "StructuredLoopToSelectionReductionPass";
+std::string StructuredLoopToSelectionReductionOpportunityFinder::GetName()
+    const {
+  return "StructuredLoopToSelectionReductionOpportunityFinder";
 }
 
 }  // namespace reduce
diff --git a/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h b/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h
new file mode 100644
index 0000000..d63d434
--- /dev/null
+++ b/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_STRUCTURED_LOOP_TO_SELECTION_REDUCTION_OPPORTUNITY_FINDER_H
+#define SOURCE_REDUCE_STRUCTURED_LOOP_TO_SELECTION_REDUCTION_OPPORTUNITY_FINDER_H
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder for opportunities to turn structured loops into selections,
+// generalizing from a human-writable language the idea of turning a loop:
+//
+// while (c) {
+//   body;
+// }
+//
+// into:
+//
+// if (c) {
+//   body;
+// }
+//
+// Applying such opportunities results in continue constructs of transformed
+// loops becoming unreachable, so that it may be possible to remove them
+// subsequently.
+class StructuredLoopToSelectionReductionOpportunityFinder
+    : public ReductionOpportunityFinder {
+ public:
+  StructuredLoopToSelectionReductionOpportunityFinder() = default;
+
+  ~StructuredLoopToSelectionReductionOpportunityFinder() override = default;
+
+  std::string GetName() const final;
+
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      opt::IRContext* context) const final;
+
+ private: