Merge remote-tracking branch 'aosp/upstream-master' into update-shaderc

Includes:
90862fe Validate SpvOpVectorShuffle
13e6598 restrict opcodes targeting OpDecorationGroup
97990dc Fixed --eliminate-common-uniform so that it does not eliminate loads of volatile variables.
98072b7 Optimizer: Line and NoLine are not debug1 or debug2
cfd95f3 Refactored compression debugger
8d6e4db Run dead variable elimination when using -O and -Os
e3a7209 DeadBranchElim: Fix dead block elimination
5834719 Add pass to remove dead variables at the module level.
2436794 Optimizer: OpModuleProcessed is in its own layout section
89b6af9 Add Android.mk build to Travis CI
cfb711a Added dummy android test application
d819f51 Fix cfg_cleanup.cpp.  My bad.
e6f3416 Remove coding redundancy in cfg_cleanup_pass.cpp
a743c47 Add instruction_list to Android.mk
39e25fd Add validation pass for conversion instructions
bb7802b Change BasicBlock to use InstructionList to hold instructions.
ea9d1d0 Removed todos from validate_id.cpp
863578a DeadBranchElim: Slightly more defensive coding
8ec62de The reviewed cfg_cleanup optimize pass
c75704e CFG cleanup pass - Remove unreachable blocks.
f17326c Android.mk: add source/opt/fold.cpp
4101cf4 Merge pull request #885 from dnovillo/const-prop
332a1f1 Re-factor generic constant folding code out of FoldSpecConstantOpAndCompositePass
1a9061a ADCE: Treat privates like locals in entry point with no calls
1e7994c Opt: Move *NextId functionality into MemPass
941a234 Validator: Test OpReturnValue type check
8de8dd8 Reenable validate type unique pass
2401fc0 Refactored MARK-V API
b54997e Validator checks OpReturn called from void func
720beb1 Generic intrusive linked list class.
63064bd DeadBranchElim: Add dead case elimination
99f2043 Add Android.mk to SPIRV-Tools
c90d730 Add -O, -Os and -Oconfig flags.
c26778f Set cmake-policy CMP0048 to NEW
86627f7 Implement Linker (module combiner)
4b1577a Remove duplicate dead branch elim pass declaration
d7f199b Hack around bug in gcc-4.8.1 templates.
da04f56 AggressiveDCE: Fix to not treat parameter memory refs as local
c87e967 Compact-ids pass should update the header ID bound
169266e DiagnosticStream move ctor moves output duties to new object
17a843c Cache end iterators for speed
6526c42 No use to check OpBitCount result width
d7ea99b Skip checking copyright if SPIRV_SKIP_TESTS is enabled
6eaaf7b Update CHANGES to reflect fix for #827
77feb8d Compact-ids pass should update instruction's result_id member
99cd25c Remove duplicated declaration of CreateAggressiveDCEPass().
64d5e52 Add bitwise operations validator pass
dcf4243 Add remaining opcodes to arithmetics validation
d84df94 Update CHANGES with recent news
e43c910 Create the dead function elimination pass
976e421 Detach MARK-V from the validator
16981f8 Avoid using global static variables
c25b5be Add SPIRV_SPIRV_COMPRESSION option to cmake
cf6c20e Merge pull request #829 from atgoo/fix_val_logicals
3f5e1a9 Validator: fix logicals pass for OpSelect pointers
33b879c elim-multi-store: only patch loop header phis that we created
cf85ad1 Add validate logicals pass to the validator
4e3cc2f Refactored validate_aritmetics.cpp
9b14dd0 Updated markv_autogen
8be28f7 ElimLocalMultiStore: Reset structured successors for each function
e4c7d8e Add strength reduction; for now replace multiply by power of 2
7be791a ExtractInsert: Handle rudimentary CompositeConstruct and ConstantComposite
a91cecf Recognize SPV_AMD_shader_fragment_mask
c6dfc11 Add new checks to validate arithmetics pass
4442102 Update CHANGES for OpModuleProcessed validation rule
c843ef8 validator: OpModuleProcessed allowed in layout section 7c
b36acbe Update MARK-V to version 1.01
40e9c60 spirv-as: Fail for unrecognized long option
25ddfec Inliner: Fix LoopMerge when inline into loop header of multi block loop
82df4bb Add validation pass for arithmetic operations
0d3b832 Make enums for all currently published extensions

Test: checkbuild.py on Linux; unit tests on Windows
Change-Id: Ia04ac3f73c2c5d8c41ba69992b1ee0a9b941c289
diff --git a/.appveyor.yml b/.appveyor.yml
index d091322..f8f603d 100644
--- a/.appveyor.yml
+++ b/.appveyor.yml
@@ -36,7 +36,7 @@
 
 build_script:
   - mkdir build && cd build
-  - cmake ..
+  - cmake -DSPIRV_BUILD_COMPRESSION=ON ..
   - cmake --build . --config %CONFIGURATION%
 
 test_script:
diff --git a/.travis.yml b/.travis.yml
index bf677c0..e8529d9 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -25,8 +25,10 @@
 matrix:
   fast_finish: true
   include:
-    # Additional build using Android NDK
-    - env: BUILD_NDK=ON
+    # Additional build using Android NDK with android-cmake
+    - env: BUILD_ANDROID_CMAKE=ON
+    # Additional build using Android NDK with Android.mk
+    - env: BUILD_ANDROID_MK=ON
   exclude:
     # Skip GCC builds on macOS.
     - os: osx
@@ -43,7 +45,7 @@
     - master
 
 before_install:
-  - if [[ "$BUILD_NDK" == "ON" ]]; then
+  - if [[ "$BUILD_ANDROID_CMAKE" == "ON" ]] || [[ "$BUILD_ANDROID_MK" == "ON" ]]; then
       git clone --depth=1 https://github.com/urho3d/android-ndk.git $HOME/android-ndk;
       export ANDROID_NDK=$HOME/android-ndk;
       git clone --depth=1 https://github.com/taka-no-me/android-cmake.git $HOME/android-cmake;
@@ -55,30 +57,37 @@
   - git clone --depth=1 https://github.com/google/googletest          external/googletest
 
 script:
-  - mkdir build && cd build
-  - if [[ "$BUILD_NDK" == "ON" ]]; then
+  # Due to the limitation of Travis platform, we cannot start too many concurrent jobs.
+  # Otherwise GCC will panic with internal error, possibility because of memory issues.
+  # ctest with the current tests doesn't profit from using more than 4 threads.
+  - export NPROC=4;
+  - mkdir build && cd build;
+  - if [[ "$BUILD_ANDROID_MK" == "ON" ]]; then
+      export BUILD_DIR=$(pwd);
+      mkdir ${BUILD_DIR}/libs;
+      mkdir ${BUILD_DIR}/app;
+      $ANDROID_NDK/ndk-build -C ../android_test NDK_PROJECT_PATH=.
+                             NDK_LIBS_OUT=${BUILD_DIR}/libs
+                             NDK_APP_OUT=${BUILD_DIR}/app -j${NPROC};
+    elif [[ "$BUILD_ANDROID_CMAKE" == "ON" ]]; then
       cmake -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_PATH}
             -DANDROID_NATIVE_API_LEVEL=android-9
             -DCMAKE_BUILD_TYPE=Release
             -DANDROID_ABI="armeabi-v7a with NEON"
+            -DSPIRV_BUILD_COMPRESSION=ON
             -DSPIRV_SKIP_TESTS=ON ..;
+      make -j${NPROC};
     else
-      cmake -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ..;
+      cmake -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DSPIRV_BUILD_COMPRESSION=ON ..;
+      make -j${NPROC};
+      ctest -j${NPROC} --output-on-failure --timeout 300;
     fi
-  # Due to the limitation of Travis platform, we cannot start too many concurrent jobs.
-  # Otherwise GCC will panic with internal error, possibility because of memory issues.
-  - make -j4
-  - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
-      export NPROC=`nproc`;
-    else
-      export NPROC=`sysctl -n hw.ncpu`;
-    fi
-  - if [[ "$BUILD_NDK" != "ON" ]]; then ctest -j${NPROC} --output-on-failure --timeout 300; fi
 
 
 notifications:
   email:
     recipients:
+      - andreyt@google.com
       - antiagainst@google.com
       - awoloszyn@google.com
       - dneto@google.com
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..ed68c40
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,218 @@
+LOCAL_PATH := $(call my-dir)
+SPVTOOLS_OUT_PATH=$(if $(call host-path-is-absolute,$(TARGET_OUT)),$(TARGET_OUT),$(abspath $(TARGET_OUT)))
+SPVHEADERS_LOCAL_PATH := $(LOCAL_PATH)/external/spirv-headers
+
+SPVTOOLS_SRC_FILES := \
+		source/assembly_grammar.cpp \
+		source/binary.cpp \
+		source/diagnostic.cpp \
+		source/disassemble.cpp \
+		source/ext_inst.cpp \
+		source/enum_string_mapping.cpp \
+		source/extensions.cpp \
+		source/id_descriptor.cpp \
+		source/libspirv.cpp \
+		source/name_mapper.cpp \
+		source/opcode.cpp \
+		source/operand.cpp \
+		source/parsed_operand.cpp \
+		source/print.cpp \
+		source/software_version.cpp \
+		source/spirv_endian.cpp \
+		source/spirv_target_env.cpp \
+		source/spirv_validator_options.cpp \
+		source/table.cpp \
+		source/text.cpp \
+		source/text_handler.cpp \
+		source/util/bit_stream.cpp \
+		source/util/parse_number.cpp \
+		source/util/string_utils.cpp \
+		source/val/basic_block.cpp \
+		source/val/construct.cpp \
+		source/val/function.cpp \
+		source/val/instruction.cpp \
+		source/val/validation_state.cpp \
+		source/validate.cpp \
+		source/validate_arithmetics.cpp \
+		source/validate_bitwise.cpp \
+		source/validate_capability.cpp \
+		source/validate_cfg.cpp \
+		source/validate_conversion.cpp \
+		source/validate_datarules.cpp \
+		source/validate_decorations.cpp \
+		source/validate_id.cpp \
+		source/validate_instruction.cpp \
+		source/validate_layout.cpp \
+		source/validate_logicals.cpp \
+		source/validate_type_unique.cpp
+
+SPVTOOLS_OPT_SRC_FILES := \
+		source/opt/aggressive_dead_code_elim_pass.cpp \
+		source/opt/basic_block.cpp \
+		source/opt/block_merge_pass.cpp \
+		source/opt/build_module.cpp \
+		source/opt/cfg_cleanup_pass.cpp \
+		source/opt/compact_ids_pass.cpp \
+		source/opt/common_uniform_elim_pass.cpp \
+		source/opt/dead_branch_elim_pass.cpp \
+		source/opt/dead_variable_elimination.cpp \
+		source/opt/decoration_manager.cpp \
+		source/opt/def_use_manager.cpp \
+		source/opt/eliminate_dead_constant_pass.cpp \
+		source/opt/eliminate_dead_functions_pass.cpp \
+		source/opt/flatten_decoration_pass.cpp \
+		source/opt/fold.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/inline_pass.cpp \
+		source/opt/inline_exhaustive_pass.cpp \
+		source/opt/inline_opaque_pass.cpp \
+		source/opt/insert_extract_elim.cpp \
+		source/opt/instruction.cpp \
+		source/opt/instruction_list.cpp \
+		source/opt/ir_loader.cpp \
+		source/opt/local_access_chain_convert_pass.cpp \
+		source/opt/local_single_block_elim_pass.cpp \
+		source/opt/local_single_store_elim_pass.cpp \
+		source/opt/local_ssa_elim_pass.cpp \
+		source/opt/mem_pass.cpp \
+		source/opt/module.cpp \
+		source/opt/optimizer.cpp \
+		source/opt/pass.cpp \
+		source/opt/pass_manager.cpp \
+		source/opt/remove_duplicates_pass.cpp \
+		source/opt/set_spec_constant_default_value_pass.cpp \
+		source/opt/strength_reduction_pass.cpp \
+		source/opt/strip_debug_info_pass.cpp \
+		source/opt/type_manager.cpp \
+		source/opt/types.cpp \
+		source/opt/unify_const_pass.cpp
+
+# Locations of grammar files.
+SPV_CORE10_GRAMMAR=$(SPVHEADERS_LOCAL_PATH)/include/spirv/1.0/spirv.core.grammar.json
+SPV_CORE11_GRAMMAR=$(SPVHEADERS_LOCAL_PATH)/include/spirv/1.1/spirv.core.grammar.json
+SPV_CORE12_GRAMMAR=$(SPVHEADERS_LOCAL_PATH)/include/spirv/1.2/spirv.core.grammar.json
+SPV_GLSL_GRAMMAR=$(SPVHEADERS_LOCAL_PATH)/include/spirv/1.0/extinst.glsl.std.450.grammar.json
+SPV_OPENCL_GRAMMAR=$(SPVHEADERS_LOCAL_PATH)/include/spirv/1.0/extinst.opencl.std.100.grammar.json
+
+define gen_spvtools_grammar_tables
+$(call generate-file-dir,$(1)/core.insts-1.0.inc)
+$(1)/core.insts-1.0.inc $(1)/operand.kinds-1.0.inc $(1)/glsl.std.450.insts-1.0.inc $(1)/opencl.std.insts-1.0.inc: \
+        $(LOCAL_PATH)/utils/generate_grammar_tables.py \
+        $(SPV_CORE10_GRAMMAR) \
+        $(SPV_GLSL_GRAMMAR) \
+        $(SPV_OPENCL_GRAMMAR)
+		@$(HOST_PYTHON) $(LOCAL_PATH)/utils/generate_grammar_tables.py \
+		                --spirv-core-grammar=$(SPV_CORE10_GRAMMAR) \
+		                --extinst-glsl-grammar=$(SPV_GLSL_GRAMMAR) \
+		                --extinst-opencl-grammar=$(SPV_OPENCL_GRAMMAR) \
+		                --core-insts-output=$(1)/core.insts-1.0.inc \
+		                --glsl-insts-output=$(1)/glsl.std.450.insts-1.0.inc \
+		                --opencl-insts-output=$(1)/opencl.std.insts-1.0.inc \
+		                --operand-kinds-output=$(1)/operand.kinds-1.0.inc
+		@echo "[$(TARGET_ARCH_ABI)] Grammar v1.0   : instructions & operands <= grammar JSON files"
+$(1)/core.insts-1.1.inc $(1)/operand.kinds-1.1.inc: \
+        $(LOCAL_PATH)/utils/generate_grammar_tables.py \
+        $(SPV_CORE11_GRAMMAR)
+		@$(HOST_PYTHON) $(LOCAL_PATH)/utils/generate_grammar_tables.py \
+		                --spirv-core-grammar=$(SPV_CORE11_GRAMMAR) \
+		                --core-insts-output=$(1)/core.insts-1.1.inc \
+		                --operand-kinds-output=$(1)/operand.kinds-1.1.inc
+		@echo "[$(TARGET_ARCH_ABI)] Grammar v1.1   : instructions & operands <= grammar JSON files"
+$(1)/core.insts-1.2.inc $(1)/operand.kinds-1.2.inc: \
+        $(LOCAL_PATH)/utils/generate_grammar_tables.py \
+        $(SPV_CORE12_GRAMMAR)
+		@$(HOST_PYTHON) $(LOCAL_PATH)/utils/generate_grammar_tables.py \
+		                --spirv-core-grammar=$(SPV_CORE12_GRAMMAR) \
+		                --core-insts-output=$(1)/core.insts-1.2.inc \
+		                --operand-kinds-output=$(1)/operand.kinds-1.2.inc
+		@echo "[$(TARGET_ARCH_ABI)] Grammar v1.2   : instructions & operands <= grammar JSON files"
+$(LOCAL_PATH)/source/opcode.cpp: $(1)/core.insts-1.0.inc $(1)/core.insts-1.1.inc $(1)/core.insts-1.2.inc
+$(LOCAL_PATH)/source/operand.cpp: $(1)/operand.kinds-1.0.inc $(1)/operand.kinds-1.1.inc $(1)/operand.kinds-1.2.inc
+$(LOCAL_PATH)/source/ext_inst.cpp: $(1)/glsl.std.450.insts-1.0.inc $(1)/opencl.std.insts-1.0.inc
+endef
+$(eval $(call gen_spvtools_grammar_tables,$(SPVTOOLS_OUT_PATH)))
+
+define gen_spvtools_vendor_tables
+$(call generate-file-dir,$(1)/$(2).insts.inc)
+$(1)/$(2).insts.inc : \
+        $(LOCAL_PATH)/utils/generate_grammar_tables.py \
+        $(LOCAL_PATH)/source/extinst.$(2).grammar.json
+		@$(HOST_PYTHON) $(LOCAL_PATH)/utils/generate_grammar_tables.py \
+		    --extinst-vendor-grammar=$(LOCAL_PATH)/source/extinst.$(2).grammar.json \
+		    --vendor-insts-output=$(1)/$(2).insts.inc
+		@echo "[$(TARGET_ARCH_ABI)] Vendor extended instruction set: $(2) tables <= grammar"
+$(LOCAL_PATH)/source/ext_inst.cpp: $(1)/$(2).insts.inc
+endef
+# Vendor extended instruction sets, with grammars from SPIRV-Tools source tree.
+SPV_NONSTANDARD_EXTINST_GRAMMARS=$(foreach F,$(wildcard $(LOCAL_PATH)/source/extinst.*.grammar.json),$(patsubst extinst.%.grammar.json,%,$(notdir $F)))
+$(foreach E,$(SPV_NONSTANDARD_EXTINST_GRAMMARS),$(eval $(call gen_spvtools_vendor_tables,$(SPVTOOLS_OUT_PATH),$E)))
+
+define gen_spvtools_enum_string_mapping
+$(call generate-file-dir,$(1)/extension_enum.inc.inc)
+$(1)/extension_enum.inc $(1)/enum_string_mapping.inc: \
+        $(LOCAL_PATH)/utils/generate_grammar_tables.py \
+        $(SPV_CORE11_GRAMMAR)
+		@$(HOST_PYTHON) $(LOCAL_PATH)/utils/generate_grammar_tables.py \
+		                --spirv-core-grammar=$(SPV_CORE11_GRAMMAR) \
+		                --extension-enum-output=$(1)/extension_enum.inc \
+		                --enum-string-mapping-output=$(1)/enum_string_mapping.inc
+		@echo "[$(TARGET_ARCH_ABI)] Generate enum<->string mapping <= grammar JSON files"
+# Generated header extension_enum.inc is transitively included by table.h, which is
+# used pervasively.  Capture the pervasive dependency.
+$(foreach F,$(SPVTOOLS_SRC_FILES) $(SPVTOOLS_OPT_SRC_FILES),$(LOCAL_PATH)/$F ) \
+  : $(1)/extension_enum.inc
+$(LOCAL_PATH)/source/enum_string_mapping.cpp: $(1)/enum_string_mapping.inc
+endef
+$(eval $(call gen_spvtools_enum_string_mapping,$(SPVTOOLS_OUT_PATH)))
+
+define gen_spvtools_build_version_inc
+$(call generate-file-dir,$(1)/dummy_filename)
+$(1)/build-version.inc: \
+        $(LOCAL_PATH)/utils/update_build_version.py \
+        $(LOCAL_PATH)/CHANGES
+		@$(HOST_PYTHON) $(LOCAL_PATH)/utils/update_build_version.py \
+		                $(LOCAL_PATH) $(1)/build-version.inc
+		@echo "[$(TARGET_ARCH_ABI)] Generate       : build-version.inc <= CHANGES"
+$(LOCAL_PATH)/source/software_version.cpp: $(1)/build-version.inc
+endef
+$(eval $(call gen_spvtools_build_version_inc,$(SPVTOOLS_OUT_PATH)))
+
+define gen_spvtools_generators_inc
+$(call generate-file-dir,$(1)/dummy_filename)
+$(1)/generators.inc: \
+        $(LOCAL_PATH)/utils/generate_registry_tables.py \
+        $(SPVHEADERS_LOCAL_PATH)/include/spirv/spir-v.xml
+		@$(HOST_PYTHON) $(LOCAL_PATH)/utils/generate_registry_tables.py \
+		                --xml=$(SPVHEADERS_LOCAL_PATH)/include/spirv/spir-v.xml \
+				--generator-output=$(1)/generators.inc
+		@echo "[$(TARGET_ARCH_ABI)] Generate       : generators.inc <= spir-v.xml"
+$(LOCAL_PATH)/source/opcode.cpp: $(1)/generators.inc
+endef
+$(eval $(call gen_spvtools_generators_inc,$(SPVTOOLS_OUT_PATH)))
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := SPIRV-Tools
+LOCAL_C_INCLUDES := \
+		$(LOCAL_PATH)/include \
+		$(LOCAL_PATH)/source \
+		$(LOCAL_PATH)/external/spirv-headers/include \
+		$(SPVTOOLS_OUT_PATH)
+LOCAL_EXPORT_C_INCLUDES := \
+		$(LOCAL_PATH)/include
+LOCAL_CXXFLAGS:=-std=c++11 -fno-exceptions -fno-rtti -Werror
+LOCAL_SRC_FILES:= $(SPVTOOLS_SRC_FILES)
+include $(BUILD_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := SPIRV-Tools-opt
+LOCAL_C_INCLUDES := \
+		$(LOCAL_PATH)/include \
+		$(LOCAL_PATH)/source \
+		$(LOCAL_PATH)/external/spirv-headers/include \
+		$(SPVTOOLS_OUT_PATH)
+LOCAL_CXXFLAGS:=-std=c++11 -fno-exceptions -fno-rtti -Werror
+LOCAL_STATIC_LIBRARIES:=SPIRV-Tools
+LOCAL_SRC_FILES:= $(SPVTOOLS_OPT_SRC_FILES)
+include $(BUILD_STATIC_LIBRARY)
diff --git a/CHANGES b/CHANGES
index 3541b24..a2daabd 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,6 +1,29 @@
 Revision history for SPIRV-Tools
 
 v2017.1-dev 2017-09-01
+ - General:
+   - Avoid static-duration variables of class type (with constructors).
+ - Validator:
+   - Type check basic arithmetic operations
+   - Type check Relational and Logical instructions
+   - Type check Bit instructions
+   - OpModuleProcessed is only allowed after debug names section and before annotations
+     section.
+   - Recognize extensions listed on SPIR-V registry,
+     through #25 SPV_AMD_shader_fragment_mask
+ - Optimizer:
+   - Add eliminater-dead-function transform
+   - Add strength reduction transform: For now, convert multiply by power of 2
+     to a bit shift.
+   - Extract-insert elimination: Recognize the case where the first instruction
+     in the sequence is an OpCompositeConstruct or OpConstantComposite
+ - Fixes:
+   #798: spirv-as should fail when given unrecognized long option
+   #800: Inliner: Fix inlining function into header of multi-block loop
+   #824: Eliminate-local-multi-store: Fix a crash
+   #826: Elimiante-local-multi-store: Fix a crash
+   #827: Fix crash when compact-ids transform runs before another transform.
+   #834: Add Cmake option to build the compressing codec. Off by default.
 
 v2017.0 2017-09-01
  - Update README to describe that assembler, disassembler, and binary parser support
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 070c6da..d53f01b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -13,6 +13,9 @@
 # limitations under the License.
 
 cmake_minimum_required(VERSION 2.8.12)
+if (POLICY CMP0048)
+  cmake_policy(SET CMP0048 NEW)
+endif()
 if (POLICY CMP0054)
   # Avoid dereferencing variables or interpret keywords that have been
   # quoted or bracketed.
@@ -54,7 +57,9 @@
 if(NOT ${SKIP_SPIRV_TOOLS_INSTALL})
   set(ENABLE_SPIRV_TOOLS_INSTALL ON)
 endif()
- 
+
+option(SPIRV_BUILD_COMPRESSION "Build SPIR-V compressing codec" OFF)
+
 option(SPIRV_WERROR "Enable error on warning" ON)
 if(("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang"))
   set(COMPILER_IS_LIKE_GNU TRUE)
@@ -182,10 +187,13 @@
       ${CMAKE_CURRENT_SOURCE_DIR}/include/spirv-tools/libspirv.h
       ${CMAKE_CURRENT_SOURCE_DIR}/include/spirv-tools/libspirv.hpp
       ${CMAKE_CURRENT_SOURCE_DIR}/include/spirv-tools/optimizer.hpp
+      ${CMAKE_CURRENT_SOURCE_DIR}/include/spirv-tools/linker.hpp
     DESTINATION
       ${CMAKE_INSTALL_INCLUDEDIR}/spirv-tools/)
 endif(ENABLE_SPIRV_TOOLS_INSTALL)
 
-add_test(NAME spirv-tools-copyrights
-         COMMAND ${PYTHON_EXECUTABLE} utils/check_copyright.py
-         WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
+if (NOT "${SPIRV_SKIP_TESTS}")
+  add_test(NAME spirv-tools-copyrights
+           COMMAND ${PYTHON_EXECUTABLE} utils/check_copyright.py
+           WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
+ endif()
diff --git a/README.md b/README.md
index 902cdac..cd739dd 100644
--- a/README.md
+++ b/README.md
@@ -109,12 +109,28 @@
   * Eliminate dead branches
   * Merge single successor / single predecessor block pairs
   * Eliminate common uniform loads
+  * Remove duplicates: Capabilities, extended instruction imports, types, and
+    decorations.
 
 For the latest list with detailed documentation, please refer to
 [`include/spirv-tools/optimizer.hpp`](include/spirv-tools/optimizer.hpp).
 
 For suggestions on using the code reduction options, please refer to this [white paper](https://www.lunarg.com/shader-compiler-technologies/white-paper-spirv-opt/).
 
+
+### Linker
+
+*Note:* The linker is still under development.
+
+Current features:
+* Combine multiple SPIR-V binary modules together.
+* Combine into a library (exports are retained) or an executable (no symbols
+  are exported).
+
+See the [CHANGES](CHANGES) file for reports on completed work, and the [General
+sub-project](https://github.com/KhronosGroup/SPIRV-Tools/projects/2) for
+planned and in-progress work.
+
 ### Extras
 
 * [Utility filters](#utility-filters)
@@ -197,6 +213,8 @@
   the command line tools.  This will prevent the tests from being built.
 * `SPIRV_SKIP_EXECUTABLES={ON|OFF}`, default `OFF`- Build only the library, not
   the command line tools and tests.
+* `SPIRV_BUILD_COMPRESSION={ON|OFF}`, default `OFF`- Build SPIR-V compressing
+  codec.
 * `SPIRV_USE_SANITIZER=<sanitizer>`, default is no sanitizing - On UNIX
   platforms with an appropriate version of `clang` this option enables the use
   of the sanitizers documented [here][clang-sanitizers].
@@ -253,10 +271,11 @@
 * `spvValidate` implements the validator functionality. *Incomplete*
 * `spvValidateBinary` implements the validator functionality. *Incomplete*
 
-The C++ interface is comprised of two classes, `SpirvTools` and `Optimizer`,
-both in the `spvtools` namespace.
+The C++ interface is comprised of three classes, `SpirvTools`, `Optimizer` and
+`Linker`, all in the `spvtools` namespace.
 * `SpirvTools` provides `Assemble`, `Disassemble`, and `Validate` methods.
 * `Optimizer` provides methods for registering and running optimization passes.
+* `Linker` provides methods for combining together multiple binaries.
 
 ## Command line tools
 
@@ -293,6 +312,18 @@
 The output includes syntax colouring when printing to the standard output stream,
 on Linux, Windows, and OS X.
 
+### Linker tool
+
+The linker combines multiple SPIR-V binary modules together, resulting in a single
+binary module as output.
+
+This is a work in progress.
+The linker does not support OpenCL program linking options related to math
+flags. (See section 5.6.5.2 in OpenCL 1.2)
+
+* `spirv-link` - the standalone linker
+  * `<spirv-dir>/tools/link`
+
 ### Optimizer tool
 
 The optimizer processes a SPIR-V binary module, applying transformations
@@ -393,6 +424,14 @@
 
 This is a work in progress.
 
+### Linker
+
+* The linker could accept math transformations such as allowing MADs, or other
+  math flags passed at linking-time in OpenCL.
+* Linkage attributes can not be applied through a group.
+* Check decorations of linked functions attributes.
+* Remove dead instructions, such as OpName targeting imported symbols.
+
 ## Licence
 <a name="license"></a>
 Full license terms are in [LICENSE](LICENSE)
diff --git a/android_test/Android.mk b/android_test/Android.mk
new file mode 100644
index 0000000..dbaf93b
--- /dev/null
+++ b/android_test/Android.mk
@@ -0,0 +1,12 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_CPP_EXTENSION := .cc .cpp .cxx
+LOCAL_SRC_FILES:=test.cpp
+LOCAL_MODULE:=spirvtools_test
+LOCAL_LDLIBS:=-landroid
+LOCAL_CXXFLAGS:=-std=c++11 -fno-exceptions -fno-rtti -Werror
+LOCAL_STATIC_LIBRARIES=SPIRV-Tools SPIRV-Tools-opt
+include $(BUILD_SHARED_LIBRARY)
+
+include $(LOCAL_PATH)/../Android.mk
diff --git a/android_test/jni/Application.mk b/android_test/jni/Application.mk
new file mode 100644
index 0000000..d7ccd34
--- /dev/null
+++ b/android_test/jni/Application.mk
@@ -0,0 +1,5 @@
+APP_ABI := all
+APP_BUILD_SCRIPT := Android.mk
+APP_STL := gnustl_static
+APP_PLATFORM := android-9
+NDK_TOOLCHAIN_VERSION := 4.9
diff --git a/android_test/test.cpp b/android_test/test.cpp
new file mode 100644
index 0000000..e6a57c1
--- /dev/null
+++ b/android_test/test.cpp
@@ -0,0 +1,22 @@
+// Copyright (c) 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <memory>
+#include "spirv-tools/libspirv.hpp"
+#include "spirv-tools/optimizer.hpp"
+
+void android_main(struct android_app* /*state*/) {
+  spvtools::SpirvTools tools(SPV_ENV_UNIVERSAL_1_2);
+  spvtools::Optimizer optimizer(SPV_ENV_UNIVERSAL_1_2);
+}
diff --git a/include/spirv-tools/linker.hpp b/include/spirv-tools/linker.hpp
new file mode 100644
index 0000000..43c725d
--- /dev/null
+++ b/include/spirv-tools/linker.hpp
@@ -0,0 +1,98 @@
+// Copyright (c) 2017 Pierre Moreau
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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 SPIRV_TOOLS_LINKER_HPP_
+#define SPIRV_TOOLS_LINKER_HPP_
+
+#include <cstdint>
+
+#include <memory>
+#include <vector>
+
+#include "libspirv.hpp"
+
+namespace spvtools {
+
+class LinkerOptions {
+ public:
+  LinkerOptions() : createLibrary_(false) {}
+
+  // Returns whether a library or an executable should be produced by the
+  // linking phase.
+  //
+  // All exported symbols are kept when creating a library, whereas they will
+  // be removed when creating an executable.
+  // The returned value will be true if creating a library, and false if
+  // creating an executable.
+  bool GetCreateLibrary() const { return createLibrary_; }
+  // Sets whether a library or an executable should be produced.
+  void SetCreateLibrary(bool create_library) {
+    createLibrary_ = create_library;
+  }
+
+ private:
+  bool createLibrary_;
+};
+
+class Linker {
+ public:
+  // Constructs an instance targeting the given environment |env|.
+  //
+  // The constructed instance will have an empty message consumer, which just
+  // ignores all messages from the library. Use SetMessageConsumer() to supply
+  // one if messages are of concern.
+  explicit Linker(spv_target_env env);
+
+  // Disables copy/move constructor/assignment operations.
+  Linker(const Linker&) = delete;
+  Linker(Linker&&) = delete;
+  Linker& operator=(const Linker&) = delete;
+  Linker& operator=(Linker&&) = delete;
+
+  // Destructs this instance.
+  ~Linker();
+
+  // Sets the message consumer to the given |consumer|. The |consumer| will be
+  // invoked once for each message communicated from the library.
+  void SetMessageConsumer(MessageConsumer consumer);
+
+  // Links one or more SPIR-V modules into a new SPIR-V module. That is,
+  // combine several SPIR-V modules into one, resolving link dependencies
+  // between them.
+  //
+  // At least one binary has to be provided in |binaries|. Those binaries do
+  // not have to be valid, but they should be at least parseable.
+  // The functions can fail due to the following:
+  // * No input modules were given;
+  // * One or more of those modules were not parseable;
+  // * The input modules used different addressing or memory models;
+  // * The ID or global variable number limit were exceeded;
+  // * Some entry points were defined multiple times;
+  // * Some imported symbols did not have an exported counterpart;
+  // * Possibly other reasons.
+  spv_result_t Link(const std::vector<std::vector<uint32_t>>& binaries,
+                    std::vector<uint32_t>& linked_binary,
+                    const LinkerOptions& options = LinkerOptions()) const;
+  spv_result_t Link(const uint32_t* const* binaries, const size_t* binary_sizes,
+                    size_t num_binaries, std::vector<uint32_t>& linked_binary,
+                    const LinkerOptions& options = LinkerOptions()) 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.
+};
+
+}  // namespace spvtools
+
+#endif  // SPIRV_TOOLS_LINKER_HPP_
diff --git a/include/spirv-tools/markv.h b/include/spirv-tools/markv.h
deleted file mode 100644
index 9941d43..0000000
--- a/include/spirv-tools/markv.h
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright (c) 2017 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// MARK-V is a compression format for SPIR-V binaries. It strips away
-// non-essential information (such as result ids which can be regenerated) and
-// uses various bit reduction techiniques to reduce the size of the binary.
-//
-// WIP: MARK-V codec is in early stages of development. At the moment it only
-// can encode and decode some SPIR-V files and only if exacly the same build of
-// software is used (is doesn't write or handle version numbers yet).
-
-#ifndef SPIRV_TOOLS_MARKV_H_
-#define SPIRV_TOOLS_MARKV_H_
-
-#include "libspirv.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-typedef struct spv_markv_binary_t {
-  uint8_t* data;
-  size_t length;
-} spv_markv_binary_t;
-
-typedef spv_markv_binary_t* spv_markv_binary;
-typedef const spv_markv_binary_t* const_spv_markv_binary;
-
-typedef struct spv_markv_encoder_options_t spv_markv_encoder_options_t;
-typedef spv_markv_encoder_options_t* spv_markv_encoder_options;
-typedef const spv_markv_encoder_options_t* spv_const_markv_encoder_options;
-
-typedef struct spv_markv_decoder_options_t spv_markv_decoder_options_t;
-typedef spv_markv_decoder_options_t* spv_markv_decoder_options;
-typedef const spv_markv_decoder_options_t* spv_const_markv_decoder_options;
-
-// Creates spv_markv_encoder_options with default options. Returns a valid
-// options object. The object remains valid until it is passed into
-// spvMarkvEncoderOptionsDestroy.
-spv_markv_encoder_options spvMarkvEncoderOptionsCreate();
-
-// Destroys the given spv_markv_encoder_options object.
-void spvMarkvEncoderOptionsDestroy(spv_markv_encoder_options options);
-
-// Creates spv_markv_decoder_options with default options. Returns a valid
-// options object. The object remains valid until it is passed into
-// spvMarkvDecoderOptionsDestroy.
-spv_markv_decoder_options spvMarkvDecoderOptionsCreate();
-
-// Destroys the given spv_markv_decoder_options object.
-void spvMarkvDecoderOptionsDestroy(spv_markv_decoder_options options);
-
-// Encodes the given SPIR-V binary to MARK-V binary.
-// If |comments| is not nullptr, it would contain a textual description of
-// how encoding was done (with snippets of disassembly and bit sequences).
-spv_result_t spvSpirvToMarkv(spv_const_context context,
-                             const uint32_t* spirv_words,
-                             size_t spirv_num_words,
-                             spv_const_markv_encoder_options options,
-                             spv_markv_binary* markv_binary,
-                             spv_text* comments, spv_diagnostic* diagnostic);
-
-// Decodes a SPIR-V binary from the given MARK-V binary.
-// If |comments| is not nullptr, it would contain a textual description of
-// how decoding was done (with snippets of disassembly and bit sequences).
-spv_result_t spvMarkvToSpirv(spv_const_context context,
-                             const uint8_t* markv_data,
-                             size_t markv_size_bytes,
-                             spv_const_markv_decoder_options options,
-                             spv_binary* spirv_binary,
-                             spv_text* comments, spv_diagnostic* diagnostic);
-
-// Destroys MARK-V binary created by spvSpirvToMarkv().
-void spvMarkvBinaryDestroy(spv_markv_binary binary);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif  // SPIRV_TOOLS_MARKV_H_
diff --git a/include/spirv-tools/optimizer.hpp b/include/spirv-tools/optimizer.hpp
index 5cfcdb9..8d9f94d 100644
--- a/include/spirv-tools/optimizer.hpp
+++ b/include/spirv-tools/optimizer.hpp
@@ -77,17 +77,32 @@
   // method.
   Optimizer& RegisterPass(PassToken&& pass);
 
+  // Registers passes that attempt to improve performance of generated code.
+  // This sequence of passes is subject to constant review and will change
+  // from time to time.
+  Optimizer& RegisterPerformancePasses();
+
+  // Registers passes that attempt to improve the size of generated code.
+  // This sequence of passes is subject to constant review and will change
+  // from time to time.
+  Optimizer& RegisterSizePasses();
+
   // Optimizes the given SPIR-V module |original_binary| and writes the
   // optimized binary into |optimized_binary|.
   // Returns true on successful optimization, whether or not the module is
   // modified. Returns false if errors occur when processing |original_binary|
   // using any of the registered passes. In that case, no further passes are
-  // excuted and the contents in |optimized_binary| may be invalid.
+  // executed and the contents in |optimized_binary| may be invalid.
   //
   // It's allowed to alias |original_binary| to the start of |optimized_binary|.
   bool Run(const uint32_t* original_binary, size_t original_binary_size,
            std::vector<uint32_t>* optimized_binary) const;
 
+  // Returns a vector of strings with all the pass names added to this
+  // optimizer's pass manager. These strings are valid until the associated
+  // pass manager is destroyed.
+  std::vector<const char*> GetPassNames() const;
+
  private:
   struct Impl;                  // Opaque struct for holding internal data.
   std::unique_ptr<Impl> impl_;  // Unique pointer to internal data.
@@ -102,6 +117,12 @@
 // Section 3.32.2 of the SPIR-V spec) of the SPIR-V module to be optimized.
 Optimizer::PassToken CreateStripDebugInfoPass();
 
+// Creates an eliminate-dead-functions pass.
+// An eliminate-dead-functions pass will remove all functions that are not in
+// the call trees rooted at entry points and exported functions.  These
+// functions are not needed because they will never be called.
+Optimizer::PassToken CreateEliminateDeadFunctionsPass();
+
 // 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
@@ -185,6 +206,12 @@
 // OpSpecConstantOp.
 Optimizer::PassToken CreateEliminateDeadConstantPass();
 
+// Creates a strength-reduction pass.
+// A strength-reduction pass will look for opportunities to replace an
+// instruction with an equivalent and less expensive one.  For example,
+// multiplying by a power of 2 can be replaced by a bit shift.
+Optimizer::PassToken CreateStrengthReductionPass();
+
 // Creates a block merge pass.
 // This pass searches for blocks with a single Branch to a block with no
 // other predecessors and merges the blocks into a single block. Continue
@@ -198,7 +225,7 @@
 // this time it does not guarantee all such sequences are eliminated.
 //
 // Presence of phi instructions can inhibit this optimization. Handling
-// these is left for future improvements. 
+// these is left for future improvements.
 Optimizer::PassToken CreateBlockMergePass();
 
 // Creates an exhaustive inline pass.
@@ -209,7 +236,7 @@
 // there is no attempt to optimize for size or runtime performance. Functions
 // that are not in the call tree of an entry point are not changed.
 Optimizer::PassToken CreateInlineExhaustivePass();
-  
+
 // Creates an opaque inline pass.
 // An opaque inline pass inlines all function calls in all functions in all
 // entry point call trees where the called function contains an opaque type
@@ -220,9 +247,9 @@
 // not legal in Vulkan. Functions that are not in the call tree of an entry
 // point are not changed.
 Optimizer::PassToken CreateInlineOpaquePass();
-  
+
 // Creates a single-block local variable load/store elimination pass.
-// For every entry point function, do single block memory optimization of 
+// For every entry point function, do single block memory optimization of
 // function variables referenced only with non-access-chain loads and stores.
 // For each targeted variable load, if previous store to that variable in the
 // block, replace the load's result id with the value id of the store.
@@ -234,9 +261,9 @@
 // The presence of access chain references and function calls can inhibit
 // the above optimization.
 //
-// Only modules with logical addressing are currently processed. 
+// Only modules with logical addressing are currently processed.
 //
-// This pass is most effective if preceeded by Inlining and 
+// This pass is most effective if preceeded by Inlining and
 // LocalAccessChainConvert. This pass will reduce the work needed to be done
 // by LocalSingleStoreElim and LocalMultiStoreElim.
 //
@@ -248,6 +275,9 @@
 // BranchConditionals with constant condition and convert to a Branch to
 // the indicated label. It will delete resulting dead blocks.
 //
+// For all phi functions in merge block, replace all uses with the id
+// corresponding to the living predecessor.
+//
 // This pass only works on shaders (guaranteed to have structured control
 // flow). Note that some such branches and blocks may be left to avoid
 // creating invalid control flow. Improving this is left to future work.
@@ -260,7 +290,7 @@
 // Creates an SSA local variable load/store elimination pass.
 // For every entry point function, eliminate all loads and stores of function
 // scope variables only referenced with non-access-chain loads and stores.
-// Eliminate the variables as well. 
+// Eliminate the variables as well.
 //
 // The presence of access chain references and function calls can inhibit
 // the above optimization.
@@ -269,7 +299,7 @@
 // Currently modules with any extensions enabled are not processed. This
 // is left for future work.
 //
-// This pass is most effective if preceeded by Inlining and 
+// This pass is most effective if preceeded by Inlining and
 // LocalAccessChainConvert. LocalSingleStoreElim and LocalSingleBlockElim
 // will reduce the work that this pass has to do.
 Optimizer::PassToken CreateLocalMultiStoreElimPass();
@@ -291,30 +321,8 @@
 // possible.
 Optimizer::PassToken CreateLocalAccessChainConvertPass();
 
-// Create aggressive dead code elimination pass
-// This pass eliminates unused code from functions. In addition,
-// it detects and eliminates code which may have spurious uses but which do
-// not contribute to the output of the function. The most common cause of
-// such code sequences is summations in loops whose result is no longer used
-// due to dead code elimination. This optimization has additional compile
-// time cost over standard dead code elimination.
-//
-// This pass only processes entry point functions. It also only processes
-// shaders with logical addressing. It currently will not process functions
-// with function calls. It currently only supports the GLSL.std.450 extended
-// instruction set. It currently does not support any extensions.
-//
-// This pass will be made more effective by first running passes that remove
-// dead control flow and inlines function calls.
-//
-// This pass can be especially useful after running Local Access Chain
-// Conversion, which tends to cause cycles of dead code to be left after
-// Store/Load elimination passes are completed. These cycles cannot be
-// eliminated with standard dead code elimination.
-Optimizer::PassToken CreateAggressiveDCEPass();
-
 // Creates a local single store elimination pass.
-// For each entry point function, this pass eliminates loads and stores for 
+// For each entry point function, this pass eliminates loads and stores for
 // function scope variable that are stored to only once, where possible. Only
 // whole variable loads and stores are eliminated; access-chain references are
 // not optimized. Replace all loads of such variables with the value that is
@@ -343,20 +351,9 @@
 // converted to inserts and extracts and local loads and stores are eliminated.
 Optimizer::PassToken CreateInsertExtractElimPass();
 
-// Create dead branch elimination pass.
-// For each entry point function, this pass will look for BranchConditionals
-// with constant condition and convert to a branch. The BranchConditional must
-// be preceeded by OpSelectionMerge. For all phi functions in merge block,
-// replace all uses with the id corresponding to the living predecessor.
-//
-// This pass is most effective when preceeded by passes which eliminate
-// local loads and stores, effectively propagating constant values where
-// possible.
-Optimizer::PassToken CreateDeadBranchElimPass();
-
 // Creates a pass to consolidate uniform references.
 // For each entry point function in the module, first change all constant index
-// access chain loads into equivalent composite extracts. Then consolidate 
+// access chain loads into equivalent composite extracts. Then consolidate
 // identical uniform loads into one uniform load. Finally, consolidate
 // identical uniform extracts into one uniform extract. This may require
 // moving a load or extract to a point which dominates all uses.
@@ -393,6 +390,22 @@
 // The pass remaps result ids to a compact and gapless range starting from %1.
 Optimizer::PassToken CreateCompactIdsPass();
 
+// Creates a remove duplicate capabilities pass.
+Optimizer::PassToken CreateRemoveDuplicatesPass();
+
+// Creates a CFG cleanup pass.
+// This pass removes cruft from the control flow graph of functions that are
+// reachable from entry points and exported functions. It currently includes the
+// following functionality:
+//
+// - Removal of unreachable basic blocks.
+Optimizer::PassToken CreateCFGCleanupPass();
+
+// Create dead variable elimination pass.
+// This pass will delete module scope variables, along with their decorations,
+// that are not referenced.
+Optimizer::PassToken CreateDeadVariableEliminationPass();
+
 }  // namespace spvtools
 
 #endif  // SPIRV_TOOLS_OPTIMIZER_HPP_
diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt
index 44bb973..4868c78 100644
--- a/source/CMakeLists.txt
+++ b/source/CMakeLists.txt
@@ -190,6 +190,7 @@
 
 add_subdirectory(comp)
 add_subdirectory(opt)
+add_subdirectory(link)
 
 set(SPIRV_SOURCES
   ${spirv-tools_SOURCE_DIR}/include/spirv-tools/libspirv.h
@@ -252,13 +253,17 @@
   ${CMAKE_CURRENT_SOURCE_DIR}/text.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/text_handler.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/validate.cpp
-  ${CMAKE_CURRENT_SOURCE_DIR}/validate_cfg.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/validate_arithmetics.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/validate_bitwise.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/validate_capability.cpp
-  ${CMAKE_CURRENT_SOURCE_DIR}/validate_id.cpp
-  ${CMAKE_CURRENT_SOURCE_DIR}/validate_instruction.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/validate_cfg.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/validate_conversion.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/validate_datarules.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/validate_decorations.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/validate_id.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/validate_instruction.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/validate_layout.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/validate_logicals.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/validate_type_unique.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/val/decoration.h
   ${CMAKE_CURRENT_SOURCE_DIR}/val/basic_block.cpp
diff --git a/source/comp/CMakeLists.txt b/source/comp/CMakeLists.txt
index 11def56..b1d3e5c 100644
--- a/source/comp/CMakeLists.txt
+++ b/source/comp/CMakeLists.txt
@@ -12,23 +12,26 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-add_library(SPIRV-Tools-comp markv_codec.cpp)
+if(SPIRV_BUILD_COMPRESSION)
+  add_library(SPIRV-Tools-comp markv_codec.cpp)
 
-spvtools_default_compile_options(SPIRV-Tools-comp)
-target_include_directories(SPIRV-Tools-comp
-  PUBLIC ${spirv-tools_SOURCE_DIR}/include
-  PUBLIC ${SPIRV_HEADER_INCLUDE_DIR}
-  PRIVATE ${spirv-tools_BINARY_DIR}
-)
+  spvtools_default_compile_options(SPIRV-Tools-comp)
+  target_include_directories(SPIRV-Tools-comp
+    PUBLIC ${spirv-tools_SOURCE_DIR}/include
+    PUBLIC ${SPIRV_HEADER_INCLUDE_DIR}
+    PRIVATE ${spirv-tools_BINARY_DIR}
+  )
 
-target_link_libraries(SPIRV-Tools-comp
-  PUBLIC ${SPIRV_TOOLS})
+  target_link_libraries(SPIRV-Tools-comp
+    PUBLIC ${SPIRV_TOOLS})
 
-set_property(TARGET SPIRV-Tools-comp PROPERTY FOLDER "SPIRV-Tools libraries")
+  set_property(TARGET SPIRV-Tools-comp PROPERTY FOLDER "SPIRV-Tools libraries")
 
-if(ENABLE_SPIRV_TOOLS_INSTALL)
-  install(TARGETS SPIRV-Tools-comp
-    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
-    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
-    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
-endif(ENABLE_SPIRV_TOOLS_INSTALL)
+  if(ENABLE_SPIRV_TOOLS_INSTALL)
+    install(TARGETS SPIRV-Tools-comp
+      RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+      LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+      ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
+  endif(ENABLE_SPIRV_TOOLS_INSTALL)
+
+endif(SPIRV_BUILD_COMPRESSION)
diff --git a/source/comp/markv.h b/source/comp/markv.h
new file mode 100644
index 0000000..b3e27bb
--- /dev/null
+++ b/source/comp/markv.h
@@ -0,0 +1,80 @@
+// Copyright (c) 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// MARK-V is a compression format for SPIR-V binaries. It strips away
+// non-essential information (such as result ids which can be regenerated) and
+// uses various bit reduction techiniques to reduce the size of the binary and
+// make it more similar to other compressed SPIR-V files to further improve
+// compression of the dataset.
+
+#ifndef SPIRV_TOOLS_MARKV_HPP_
+#define SPIRV_TOOLS_MARKV_HPP_
+
+#include <string>
+#include <vector>
+
+#include "markv_model.h"
+#include "spirv-tools/libspirv.hpp"
+
+namespace spvtools {
+
+struct MarkvCodecOptions {
+  bool validate_spirv_binary = false;
+};
+
+// Debug callback. Called once per instruction.
+// |words| is instruction SPIR-V words.
+// |bits| is a textual representation of the MARK-V bit sequence used to encode
+// the instruction (char '0' for 0, char '1' for 1).
+// |comment| contains all logs generated while processing the instruction.
+using MarkvDebugConsumer = std::function<bool(
+    const std::vector<uint32_t>& words, const std::string& bits,
+    const std::string& comment)>;
+
+// Logging callback. Called often (if decoder reads a single bit, the log
+// consumer will receive 1 character string with that bit).
+// This callback is more suitable for continous output than MarkvDebugConsumer,
+// for example if the codec crashes it would allow to pinpoint on which operand
+// or bit the crash happened.
+// |snippet| could be any atomic fragment of text logged by the codec. It can
+// contain a paragraph of text with newlines, or can be just one character.
+using MarkvLogConsumer = std::function<void(const std::string& snippet)>;
+
+// Encodes the given SPIR-V binary to MARK-V binary.
+// |log_consumer| is optional (pass MarkvLogConsumer() to disable).
+// |debug_consumer| is optional (pass MarkvDebugConsumer() to disable).
+spv_result_t SpirvToMarkv(spv_const_context context,
+                          const std::vector<uint32_t>& spirv,
+                          const MarkvCodecOptions& options,
+                          const MarkvModel& markv_model,
+                          MessageConsumer message_consumer,
+                          MarkvLogConsumer log_consumer,
+                          MarkvDebugConsumer debug_consumer,
+                          std::vector<uint8_t>* markv);
+
+// Decodes a SPIR-V binary from the given MARK-V binary.
+// |log_consumer| is optional (pass MarkvLogConsumer() to disable).
+// |debug_consumer| is optional (pass MarkvDebugConsumer() to disable).
+spv_result_t MarkvToSpirv(spv_const_context context,
+                          const std::vector<uint8_t>& markv,
+                          const MarkvCodecOptions& options,
+                          const MarkvModel& markv_model,
+                          MessageConsumer message_consumer,
+                          MarkvLogConsumer log_consumer,
+                          MarkvDebugConsumer debug_consumer,
+                          std::vector<uint32_t>* spirv);
+
+}  // namespace spvtools
+
+#endif  // SPIRV_TOOLS_MARKV_HPP_
diff --git a/source/comp/markv_codec.cpp b/source/comp/markv_codec.cpp
index 2288dc3..46fb09b 100644
--- a/source/comp/markv_codec.cpp
+++ b/source/comp/markv_codec.cpp
@@ -19,155 +19,183 @@
 // MARK-V is a compression format for SPIR-V binaries. It strips away
 // non-essential information (such as result ids which can be regenerated) and
 // uses various bit reduction techiniques to reduce the size of the binary.
-//
-// MarkvModel is a flatbuffers object containing a set of rules defining how
-// compression/decompression is done (coding schemes, dictionaries).
 
 #include <algorithm>
 #include <cassert>
 #include <cstring>
 #include <functional>
 #include <iostream>
+#include <iterator>
 #include <list>
 #include <memory>
 #include <numeric>
 #include <string>
 #include <vector>
+#include <unordered_map>
+#include <unordered_set>
+
+#include "spirv/1.2/GLSL.std.450.h"
+#include "spirv/1.2/OpenCL.std.h"
+#include "spirv/1.2/spirv.h"
 
 #include "binary.h"
 #include "diagnostic.h"
 #include "enum_string_mapping.h"
 #include "extensions.h"
 #include "ext_inst.h"
+#include "id_descriptor.h"
 #include "instruction.h"
+#include "markv.h"
+#include "markv_model.h"
 #include "opcode.h"
 #include "operand.h"
 #include "spirv-tools/libspirv.h"
-#include "spirv-tools/markv.h"
 #include "spirv_endian.h"
 #include "spirv_validator_options.h"
 #include "util/bit_stream.h"
+#include "util/huffman_codec.h"
+#include "util/move_to_front.h"
 #include "util/parse_number.h"
 #include "validate.h"
 #include "val/instruction.h"
 #include "val/validation_state.h"
 
+using libspirv::IdDescriptorCollection;
 using libspirv::Instruction;
 using libspirv::ValidationState_t;
-using spvtools::ValidateInstructionAndUpdateValidationState;
+using libspirv::DiagnosticStream;
 using spvutils::BitReaderWord64;
 using spvutils::BitWriterWord64;
+using spvutils::HuffmanCodec;
+using MoveToFront = spvutils::MoveToFront<uint32_t>;
+using MultiMoveToFront = spvutils::MultiMoveToFront<uint32_t>;
 
-struct spv_markv_encoder_options_t {
-};
-
-struct spv_markv_decoder_options_t {
-};
+namespace spvtools {
 
 namespace {
 
 const uint32_t kSpirvMagicNumber = SpvMagicNumber;
 const uint32_t kMarkvMagicNumber = 0x07230303;
 
-enum {
-  kMarkvFirstOpcode = 65536,
-  kMarkvOpNextInstructionEncodesResultId = 65536,
+// Handles for move-to-front sequences. Enums which end with "Begin" define
+// handle spaces which start at that value and span 16 or 32 bit wide.
+enum : uint64_t {
+  kMtfNone = 0,
+  // All ids.
+  kMtfAll,
+  // All forward declared ids.
+  kMtfForwardDeclared,
+  // All type ids except for generated by OpTypeFunction.
+  kMtfTypeNonFunction,
+  // All labels.
+  kMtfLabel,
+  // All ids created by instructions which had type_id.
+  kMtfObject,
+  // All types generated by OpTypeFloat, OpTypeInt, OpTypeBool.
+  kMtfTypeScalar,
+  // All composite types.
+  kMtfTypeComposite,
+  // Boolean type or any vector type of it.
+  kMtfTypeBoolScalarOrVector,
+  // All float types or any vector floats type.
+  kMtfTypeFloatScalarOrVector,
+  // All int types or any vector int type.
+  kMtfTypeIntScalarOrVector,
+  // All types declared as return types in OpTypeFunction.
+  kMtfTypeReturnedByFunction,
+  // All object ids which are integer constants.
+  kMtfConstInteger,
+  // All composite objects.
+  kMtfComposite,
+  // All bool objects or vectors of bools.
+  kMtfBoolScalarOrVector,
+  // All float objects or vectors of float.
+  kMtfFloatScalarOrVector,
+  // All int objects or vectors of int.
+  kMtfIntScalarOrVector,
+  // All pointer types which point to composited.
+  kMtfTypePointerToComposite,
+  // Used by EncodeMtfRankHuffman.
+  kMtfGenericNonZeroRank,
+  // Handle space for ids of specific type.
+  kMtfIdOfTypeBegin = 0x10000,
+  // Handle space for ids generated by specific opcode.
+  kMtfIdGeneratedByOpcode = 0x20000,
+  // Handle space for ids of objects with type generated by specific opcode.
+  kMtfIdWithTypeGeneratedByOpcodeBegin = 0x30000,
+  // All vectors of specific component type.
+  kMtfVectorOfComponentTypeBegin = 0x40000,
+  // All vector types of specific size.
+  kMtfTypeVectorOfSizeBegin = 0x50000,
+  // All pointer types to specific type.
+  kMtfPointerToTypeBegin = 0x60000,
+  // All function types which return specific type.
+  kMtfFunctionTypeWithReturnTypeBegin = 0x70000,
+  // All function objects which return specific type.
+  kMtfFunctionWithReturnTypeBegin = 0x80000,
+  // All float vectors of specific size.
+  kMtfFloatVectorOfSizeBegin = 0x90000,
+  // Id descriptor space (32-bit).
+  kMtfIdDescriptorSpaceBegin = 0x100000000,
 };
 
+// Used by "presumed index" technique which does special treatment of integer
+// constants no greater than this value.
+const uint32_t kMarkvMaxPresumedAccessIndex = 31;
+
+// Signals that the value is not in the coding scheme and a fallback method
+// needs to be used.
+const uint64_t kMarkvNoneOfTheAbove = MarkvModel::GetMarkvNoneOfTheAbove();
+
+// Mtf ranks smaller than this are encoded with Huffman coding.
+const uint32_t kMtfSmallestRankEncodedByValue = 10;
+
+// Signals that the mtf rank is too large to be encoded with Huffman.
+const uint32_t kMtfRankEncodedByValueSignal =
+    std::numeric_limits<uint32_t>::max();
+
 const size_t kCommentNumWhitespaces = 2;
 
-// TODO(atgoo@github.com): This is a placeholder for an autogenerated flatbuffer
-// containing MARK-V model for a specific dataset.
-class MarkvModel {
- public:
-  size_t opcode_chunk_length() const { return 7; }
-  size_t num_operands_chunk_length() const { return 3; }
-  size_t id_index_chunk_length() const { return 3; }
+const size_t kByteBreakAfterInstIfLessThanUntilNextByte = 8;
 
-  size_t u16_chunk_length() const { return 4; }
-  size_t s16_chunk_length() const { return 4; }
-  size_t s16_block_exponent() const { return 6; }
+// Returns a set of mtf rank codecs based on a plausible hand-coded
+// distribution.
+std::map<uint64_t, std::unique_ptr<HuffmanCodec<uint32_t>>>
+GetMtfHuffmanCodecs() {
+  std::map<uint64_t, std::unique_ptr<HuffmanCodec<uint32_t>>> codecs;
 
-  size_t u32_chunk_length() const { return 8; }
-  size_t s32_chunk_length() const { return 8; }
-  size_t s32_block_exponent() const { return 10; }
+  std::unique_ptr<HuffmanCodec<uint32_t>> codec;
 
-  size_t u64_chunk_length() const { return 8; }
-  size_t s64_chunk_length() const { return 8; }
-  size_t s64_block_exponent() const { return 10; }
-};
+  codec.reset(new HuffmanCodec<uint32_t>(std::map<uint32_t, uint32_t>({
+    { 0, 5 },
+    { 1, 40 },
+    { 2, 10 },
+    { 3, 5 },
+    { 4, 5 },
+    { 5, 5 },
+    { 6, 3 },
+    { 7, 3 },
+    { 8, 3 },
+    { 9, 3 },
+    { kMtfRankEncodedByValueSignal, 10 },
+  })));
+  codecs.emplace(kMtfAll, std::move(codec));
 
-const MarkvModel* GetDefaultModel() {
-  static MarkvModel model;
-  return &model;
-}
+  codec.reset(new HuffmanCodec<uint32_t>(std::map<uint32_t, uint32_t>({
+    { 1, 50 },
+    { 2, 20 },
+    { 3, 5 },
+    { 4, 5 },
+    { 5, 2 },
+    { 6, 1 },
+    { 7, 1 },
+    { 8, 1 },
+    { 9, 1 },
+    { kMtfRankEncodedByValueSignal, 10 },
+  })));
+  codecs.emplace(kMtfGenericNonZeroRank, std::move(codec));
 
-// Returns chunk length used for variable length encoding of spirv operand
-// words. Returns zero if operand type corresponds to potentially multiple
-// words or a word which is not expected to profit from variable width encoding.
-// Chunk length is selected based on the size of expected value.
-// Most of these values will later be encoded with probability-based coding,
-// but variable width integer coding is a good quick solution.
-// TODO(atgoo@github.com): Put this in MarkvModel flatbuffer.
-size_t GetOperandVariableWidthChunkLength(spv_operand_type_t type) {
-  switch (type) {
-    case SPV_OPERAND_TYPE_TYPE_ID:
-      return 4;
-    case SPV_OPERAND_TYPE_RESULT_ID:
-    case SPV_OPERAND_TYPE_ID:
-    case SPV_OPERAND_TYPE_SCOPE_ID:
-    case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID:
-      return 8;
-    case SPV_OPERAND_TYPE_LITERAL_INTEGER:
-    case SPV_OPERAND_TYPE_OPTIONAL_LITERAL_INTEGER:
-      return 6;
-    case SPV_OPERAND_TYPE_CAPABILITY:
-      return 6;
-    case SPV_OPERAND_TYPE_SOURCE_LANGUAGE:
-    case SPV_OPERAND_TYPE_EXECUTION_MODEL:
-      return 3;
-    case SPV_OPERAND_TYPE_ADDRESSING_MODEL:
-    case SPV_OPERAND_TYPE_MEMORY_MODEL:
-      return 2;
-    case SPV_OPERAND_TYPE_EXECUTION_MODE:
-      return 6;
-    case SPV_OPERAND_TYPE_STORAGE_CLASS:
-      return 4;
-    case SPV_OPERAND_TYPE_DIMENSIONALITY:
-    case SPV_OPERAND_TYPE_SAMPLER_ADDRESSING_MODE:
-      return 3;
-    case SPV_OPERAND_TYPE_SAMPLER_FILTER_MODE:
-      return 2;
-    case SPV_OPERAND_TYPE_SAMPLER_IMAGE_FORMAT:
-      return 6;
-    case SPV_OPERAND_TYPE_FP_ROUNDING_MODE:
-    case SPV_OPERAND_TYPE_LINKAGE_TYPE:
-    case SPV_OPERAND_TYPE_ACCESS_QUALIFIER:
-    case SPV_OPERAND_TYPE_OPTIONAL_ACCESS_QUALIFIER:
-      return 2;
-    case SPV_OPERAND_TYPE_FUNCTION_PARAMETER_ATTRIBUTE:
-      return 3;
-    case SPV_OPERAND_TYPE_DECORATION:
-    case SPV_OPERAND_TYPE_BUILT_IN:
-      return 6;
-    case SPV_OPERAND_TYPE_GROUP_OPERATION:
-    case SPV_OPERAND_TYPE_KERNEL_ENQ_FLAGS:
-    case SPV_OPERAND_TYPE_KERNEL_PROFILING_INFO:
-      return 2;
-    case SPV_OPERAND_TYPE_FP_FAST_MATH_MODE:
-    case SPV_OPERAND_TYPE_FUNCTION_CONTROL:
-    case SPV_OPERAND_TYPE_LOOP_CONTROL:
-    case SPV_OPERAND_TYPE_IMAGE:
-    case SPV_OPERAND_TYPE_OPTIONAL_IMAGE:
-    case SPV_OPERAND_TYPE_OPTIONAL_MEMORY_ACCESS:
-    case SPV_OPERAND_TYPE_SELECTION_CONTROL:
-      return 4;
-    case SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER:
-      return 6;
-    default:
-      return 0;
-  }
-  return 0;
+  return codecs;
 }
 
 // Returns true if the opcode has a fixed number of operands. May return a
@@ -240,20 +268,18 @@
   return (8 - (bit_pos % 8)) % 8;
 }
 
-bool ShouldByteBreak(size_t bit_pos) {
-  const size_t num_bits_to_next_byte = GetNumBitsToNextByte(bit_pos);
-  return num_bits_to_next_byte > 0; // && num_bits_to_next_byte <= 2;
-}
-
 // Defines and returns current MARK-V version.
 uint32_t GetMarkvVersion() {
   const uint32_t kVersionMajor = 1;
-  const uint32_t kVersionMinor = 0;
+  const uint32_t kVersionMinor = 2;
   return kVersionMinor | (kVersionMajor << 16);
 }
 
-class CommentLogger {
+class MarkvLogger {
  public:
+  MarkvLogger(MarkvLogConsumer log_consumer, MarkvDebugConsumer debug_consumer)
+      : log_consumer_(log_consumer), debug_consumer_(debug_consumer) {}
+
   void AppendText(const std::string& str) {
     Append(str);
     use_delimiter_ = false;
@@ -266,6 +292,8 @@
   }
 
   void AppendBitSequence(const std::string& str) {
+    if (debug_consumer_)
+      instruction_bits_ << str;
     if (use_delimiter_)
       Append("-");
     Append(str);
@@ -282,38 +310,42 @@
     use_delimiter_ = false;
   }
 
-  std::string GetText() const {
-    return ss_.str();
+  bool DebugInstruction(const spv_parsed_instruction_t& inst) {
+    bool result = true;
+    if (debug_consumer_) {
+      result = debug_consumer_(
+          std::vector<uint32_t>(inst.words, inst.words + inst.num_words),
+          instruction_bits_.str(), instruction_comment_.str());
+      instruction_bits_.str(std::string());
+      instruction_comment_.str(std::string());
+    }
+    return result;
   }
 
  private:
+  MarkvLogger(const MarkvLogger&) = delete;
+  MarkvLogger(MarkvLogger&&) = delete;
+  MarkvLogger& operator=(const MarkvLogger&) = delete;
+  MarkvLogger& operator=(MarkvLogger&&) = delete;
+
   void Append(const std::string& str) {
-    ss_ << str;
-    // std::cerr << str;
+    if (log_consumer_)
+      log_consumer_(str);
+    if (debug_consumer_)
+      instruction_comment_ << str;
   }
 
-  std::stringstream ss_;
+  MarkvLogConsumer log_consumer_;
+  MarkvDebugConsumer debug_consumer_;
+
+  std::stringstream instruction_bits_;
+  std::stringstream instruction_comment_;
 
   // If true a delimiter will be appended before the next bit sequence.
   // Used to generate outputs like: 1100-0 1110-1-1100-1-1111-0 110-0.
   bool use_delimiter_ = false;
 };
 
-// Creates spv_text object containing text from |str|.
-// The returned value is owned by the caller and needs to be destroyed with
-// spvTextDestroy.
-spv_text CreateSpvText(const std::string& str) {
-  spv_text out = new spv_text_t();
-  assert(out);
-  char* cstr = new char[str.length() + 1];
-  assert(cstr);
-  std::strncpy(cstr, str.c_str(), str.length());
-  cstr[str.length()] = '\0';
-  out->str = cstr;
-  out->length = str.length();
-  return out;
-}
-
 // Base class for MARK-V encoder and decoder. Contains common functionality
 // such as:
 // - Validator connection and validation state.
@@ -326,10 +358,6 @@
 
   MarkvCodecBase() = delete;
 
-  void SetModel(const MarkvModel* model) {
-    model_ = model;
-  }
-
  protected:
   struct MarkvHeader {
     MarkvHeader() {
@@ -350,31 +378,210 @@
     uint32_t spirv_generator;
   };
 
+  // |model| is owned by the caller, must be not null and valid during the
+  // lifetime of the codec.
   explicit MarkvCodecBase(spv_const_context context,
-                          spv_validator_options validator_options)
-      : validator_options_(validator_options),
-        vstate_(context, validator_options_), grammar_(context),
-        model_(GetDefaultModel()) {}
+                          spv_validator_options validator_options,
+                          const MarkvModel* model)
+      : validator_options_(validator_options), grammar_(context),
+        model_(model), mtf_huffman_codecs_(GetMtfHuffmanCodecs()),
+        context_(context),
+        vstate_(validator_options ?
+                new ValidationState_t(context, validator_options_) : nullptr) {}
 
   // Validates a single instruction and updates validation state of the module.
+  // Does nothing and returns SPV_SUCCESS if validator was not created.
   spv_result_t UpdateValidationState(const spv_parsed_instruction_t& inst) {
-    return ValidateInstructionAndUpdateValidationState(&vstate_, &inst);
+    if (!vstate_)
+      return SPV_SUCCESS;
+
+    return ValidateInstructionAndUpdateValidationState(vstate_.get(), &inst);
   }
 
-  // Returns the current instruction (the one last processed by the validator).
-  const Instruction& GetCurrentInstruction() const {
-    return vstate_.ordered_instructions().back();
+  // Returns instruction which created |id| or nullptr if such instruction was
+  // not registered.
+  const Instruction* FindDef(uint32_t id) const {
+    const auto it = id_to_def_instruction_.find(id);
+    if (it == id_to_def_instruction_.end())
+      return nullptr;
+    return it->second;
   }
 
-  spv_validator_options validator_options_;
-  ValidationState_t vstate_;
+  // Returns type id of vector type component.
+  uint32_t GetVectorComponentType(uint32_t vector_type_id) const {
+    const Instruction* type_inst = FindDef(vector_type_id);
+    assert(type_inst);
+    assert(type_inst->opcode() == SpvOpTypeVector);
+
+    const uint32_t component_type =
+        type_inst->word(type_inst->operands()[1].offset);
+    return component_type;
+  }
+
+  // Returns mtf handle for ids of given type.
+  uint64_t GetMtfIdOfType(uint32_t type_id) const {
+    return kMtfIdOfTypeBegin + type_id;
+  }
+
+  // Returns mtf handle for ids generated by given opcode.
+  uint64_t GetMtfIdGeneratedByOpcode(SpvOp opcode) const {
+    return kMtfIdGeneratedByOpcode + opcode;
+  }
+
+  // Returns mtf handle for ids of type generated by given opcode.
+  uint64_t GetMtfIdWithTypeGeneratedByOpcode(SpvOp opcode) const {
+    return kMtfIdWithTypeGeneratedByOpcodeBegin + opcode;
+  }
+
+  // Returns mtf handle for vectors of specific component type.
+  uint64_t GetMtfVectorOfComponentType(uint32_t type_id) const {
+    return kMtfVectorOfComponentTypeBegin + type_id;
+  }
+
+  // Returns mtf handle for float vectors of specific size.
+  uint64_t GetMtfFloatVectorOfSize(uint32_t size) const {
+    return kMtfFloatVectorOfSizeBegin + size;
+  }
+
+  // Returns mtf handle for vector type of specific size.
+  uint64_t GetMtfTypeVectorOfSize(uint32_t size) const {
+    return kMtfTypeVectorOfSizeBegin + size;
+  }
+
+  // Returns mtf handle for pointers to specific size.
+  uint64_t GetMtfPointerToType(uint32_t type_id) const {
+    return kMtfPointerToTypeBegin + type_id;
+  }
+
+  // Returns mtf handle for function types with given return type.
+  uint64_t GetMtfFunctionTypeWithReturnType(uint32_t type_id) const {
+    return kMtfFunctionTypeWithReturnTypeBegin + type_id;
+  }
+
+  // Returns mtf handle for functions with given return type.
+  uint64_t GetMtfFunctionWithReturnType(uint32_t type_id) const {
+    return kMtfFunctionWithReturnTypeBegin + type_id;
+  }
+
+  // Returns mtf handle for the given id descriptor.
+  uint64_t GetMtfIdDescriptor(uint32_t descriptor) const {
+    return kMtfIdDescriptorSpaceBegin + descriptor;
+  }
+
+  // Process data from the current instruction. This would update MTFs and
+  // other data containers.
+  void ProcessCurInstruction();
+
+  // Returns move-to-front handle to be used for the current operand slot.
+  // Mtf handle is chosen based on a set of rules defined by SPIR-V grammar.
+  uint64_t GetRuleBasedMtf();
+
+  // Returns words of the current instruction. Decoder has a different
+  // implementation and the array is valid only until the previously decoded
+  // word.
+  virtual const uint32_t* GetInstWords() const {
+    return inst_.words;
+  }
+
+  // Returns the opcode of the previous instruction.
+  SpvOp GetPrevOpcode() const {
+    if (instructions_.empty())
+      return SpvOpNop;
+
+    return instructions_.back()->opcode();
+  }
+
+  // Returns diagnostic stream, position index is set to instruction number.
+  DiagnosticStream Diag(spv_result_t error_code) const {
+    return DiagnosticStream({0, 0, instructions_.size()},
+                            context_->consumer, error_code);
+  }
+
+  // Returns current id bound.
+  uint32_t GetIdBound() const {
+    return id_bound_;
+  }
+
+  // Sets current id bound, expected to be no lower than the previous one.
+  void SetIdBound(uint32_t id_bound) {
+    assert(id_bound >= id_bound_);
+    id_bound_ = id_bound;
+    if (vstate_)
+      vstate_->setIdBound(id_bound);
+  }
+
+  // Returns Huffman codec for ranks of the mtf with given |handle|.
+  // Different mtfs can use different rank distributions.
+  // May return nullptr if the codec doesn't exist.
+  const spvutils::HuffmanCodec<uint32_t>* GetMtfHuffmanCodec(uint64_t handle) const {
+    const auto it = mtf_huffman_codecs_.find(handle);
+    if (it == mtf_huffman_codecs_.end())
+      return nullptr;
+    return it->second.get();
+  }
+
+  spv_validator_options validator_options_ = nullptr;
   const libspirv::AssemblyGrammar grammar_;
   MarkvHeader header_;
-  const MarkvModel* model_;
 
-  // Move-to-front list of all ids.
-  // TODO(atgoo@github.com) Consider a better move-to-front implementation.
-  std::list<uint32_t> move_to_front_ids_;
+  // MARK-V model, not owned.
+  const MarkvModel* model_ = nullptr;
+
+  // Current instruction, current operand and current operand index.
+  spv_parsed_instruction_t inst_;
+  spv_parsed_operand_t operand_;
+  uint32_t operand_index_;
+
+  // Maps a result ID to its type ID.  By convention:
+  //  - a result ID that is a type definition maps to itself.
+  //  - a result ID without a type maps to 0.  (E.g. for OpLabel)
+  std::unordered_map<uint32_t, uint32_t> id_to_type_id_;
+
+  // Container for all move-to-front sequences.
+  MultiMoveToFront multi_mtf_;
+
+  // Id of the current function or zero if outside of function.
+  uint32_t cur_function_id_ = 0;
+
+  // Return type of the current function.
+  uint32_t cur_function_return_type_ = 0;
+
+  // Remaining function parameter types. This container is filled on OpFunction,
+  // and drained on OpFunctionParameter.
+  std::list<uint32_t> remaining_function_parameter_types_;
+
+  // List of ids local to the current function.
+  std::vector<uint32_t> ids_local_to_cur_function_;
+
+  // List of instructions in the order they are given in the module.
+  std::vector<std::unique_ptr<const Instruction>> instructions_;
+
+  // Maps used for the 'presumed id' techniques. Maps small constant integer
+  // value to its id and back.
+  std::map<uint32_t, uint32_t> presumed_index_to_id_;
+  std::map<uint32_t, uint32_t> id_to_presumed_index_;
+
+  // Container/computer for id descriptors.
+  IdDescriptorCollection id_descriptors_;
+
+  // Huffman codecs for move-to-front ranks. The map key is mtf handle. Doesn't
+  // need to contain a different codec for every handle as most use one and the
+  // same.
+  std::map<uint64_t, std::unique_ptr<HuffmanCodec<uint32_t>>>
+      mtf_huffman_codecs_;
+
+  // If not nullptr, codec will log comments on the compression process.
+  std::unique_ptr<MarkvLogger> logger_;
+
+ private:
+  spv_const_context context_ = nullptr;
+
+  std::unique_ptr<ValidationState_t> vstate_;
+
+  // Maps result id to the instruction which defined it.
+  std::unordered_map<uint32_t, const Instruction*> id_to_def_instruction_;
+
+  uint32_t id_bound_ = 1;
 };
 
 // SPIR-V to MARK-V encoder. Exposes functions EncodeHeader and
@@ -388,9 +595,12 @@
 // on how encoding was done, which can later be accessed with GetComments().
 class MarkvEncoder : public MarkvCodecBase {
  public:
+  // |model| is owned by the caller, must be not null and valid during the
+  // lifetime of MarkvEncoder.
   MarkvEncoder(spv_const_context context,
-               spv_const_markv_encoder_options options)
-      : MarkvCodecBase(context, GetValidatorOptions(options)),
+               const MarkvCodecOptions& options,
+               const MarkvModel* model)
+      : MarkvCodecBase(context, GetValidatorOptions(options), model),
         options_(options) {
     (void) options_;
   }
@@ -400,12 +610,21 @@
       spv_endianness_t /* endian */, uint32_t /* magic */,
       uint32_t version, uint32_t generator, uint32_t id_bound,
       uint32_t /* schema */) {
-    vstate_.setIdBound(id_bound);
+    SetIdBound(id_bound);
     header_.spirv_version = version;
     header_.spirv_generator = generator;
     return SPV_SUCCESS;
   }
 
+  // Creates an internal logger which writes comments on the encoding process.
+  void CreateLogger(MarkvLogConsumer log_consumer,
+                    MarkvDebugConsumer debug_consumer) {
+    logger_.reset(new MarkvLogger(log_consumer, debug_consumer));
+    writer_.SetCallback([this](const std::string& str){
+      logger_->AppendBitSequence(str);
+    });
+  }
+
   // Encodes SPIR-V instruction to MARK-V and writes to bit stream.
   // Operation can fail if the instruction fails to pass the validator or if
   // the encoder stubmles on something unexpected.
@@ -415,28 +634,20 @@
   // into a single buffer and returns it as spv_markv_binary. The returned
   // value is owned by the caller and needs to be destroyed with
   // spvMarkvBinaryDestroy().
-  spv_markv_binary GetMarkvBinary() {
+  std::vector<uint8_t> GetMarkvBinary() {
     header_.markv_length_in_bits =
         static_cast<uint32_t>(sizeof(header_) * 8 + writer_.GetNumBits());
+    header_.markv_model =
+        (model_->model_type() << 16) | model_->model_version();
+
     const size_t num_bytes = sizeof(header_) + writer_.GetDataSizeBytes();
+    std::vector<uint8_t> markv(num_bytes);
 
-    spv_markv_binary markv_binary = new spv_markv_binary_t();
-    markv_binary->data = new uint8_t[num_bytes];
-    markv_binary->length = num_bytes;
     assert(writer_.GetData());
-    std::memcpy(markv_binary->data, &header_, sizeof(header_));
-    std::memcpy(markv_binary->data + sizeof(header_),
-           writer_.GetData(), writer_.GetDataSizeBytes());
-    return markv_binary;
-  }
-
-  // Creates an internal logger which writes comments on the encoding process.
-  // Output can later be accessed with GetComments().
-  void CreateCommentsLogger() {
-    logger_.reset(new CommentLogger());
-    writer_.SetCallback([this](const std::string& str){
-      logger_->AppendBitSequence(str);
-    });
+    std::memcpy(markv.data(), &header_, sizeof(header_));
+    std::memcpy(markv.data() + sizeof(header_),
+                writer_.GetData(), writer_.GetDataSizeBytes());
+    return markv;
   }
 
   // Optionally adds disassembly to the comments.
@@ -455,102 +666,82 @@
     }
   }
 
-  // Extracts the text from the comment logger.
-  std::string GetComments() const {
-    if (!logger_)
-      return "";
-    return logger_->GetText();
-  }
-
  private:
-  // Creates and returns validator options. Return value owned by the caller.
+  // Creates and returns validator options. Returned value owned by the caller.
   static spv_validator_options GetValidatorOptions(
-      spv_const_markv_encoder_options) {
-    return spvValidatorOptionsCreate();
+      const MarkvCodecOptions& options) {
+    return options.validate_spirv_binary ?
+        spvValidatorOptionsCreate() : nullptr;
   }
 
-  // Writes a single word to bit stream. |type| determines if the word is
+  // Writes a single word to bit stream. operand_.type determines if the word is
   // encoded and how.
-  void EncodeOperandWord(spv_operand_type_t type, uint32_t word) {
-    const size_t chunk_length =
-        GetOperandVariableWidthChunkLength(type);
-    if (chunk_length) {
-      writer_.WriteVariableWidthU32(word, chunk_length);
-    } else {
-      writer_.WriteUnencoded(word);
-    }
-  }
+  spv_result_t EncodeNonIdWord(uint32_t word);
 
-  // Returns id index and updates move-to-front.
-  // Index is uint16 as SPIR-V module is guaranteed to have no more than 65535
-  // instructions.
-  uint16_t GetIdIndex(uint32_t id) {
-    if (all_known_ids_.count(id)) {
-      uint16_t index = 0;
-      for (auto it = move_to_front_ids_.begin();
-           it != move_to_front_ids_.end(); ++it) {
-        if (*it == id) {
-          if (index != 0) {
-            move_to_front_ids_.erase(it);
-            move_to_front_ids_.push_front(id);
-          }
-          return index;
-        }
-        ++index;
-      }
-      assert(0 && "Id not found in move_to_front_ids_");
-      return 0;
-    } else {
-      all_known_ids_.insert(id);
-      move_to_front_ids_.push_front(id);
-      return static_cast<uint16_t>(move_to_front_ids_.size() - 1);
-    }
-  }
+  // Writes both opcode and num_operands as a single code.
+  // Returns SPV_UNSUPPORTED iff no suitable codec was found.
+  spv_result_t EncodeOpcodeAndNumOperands(uint32_t opcode, uint32_t num_operands);
 
-  void AddByteBreakIfAgreed() {
-    if (!ShouldByteBreak(writer_.GetNumBits()))
-      return;
+  // Writes mtf rank to bit stream. |mtf| is used to determine the codec
+  // scheme. |fallback_method| is used if no codec defined for |mtf|.
+  spv_result_t EncodeMtfRankHuffman(uint32_t rank, uint64_t mtf,
+                                    uint64_t fallback_method);
 
-    if (logger_) {
-      logger_->AppendWhitespaces(kCommentNumWhitespaces);
-      logger_->AppendText("ByteBreak:");
-    }
+  // Writes id using coding based on mtf associated with the id descriptor.
+  // Returns SPV_UNSUPPORTED iff fallback method needs to be used.
+  spv_result_t EncodeIdWithDescriptor(uint32_t id);
 
-    writer_.WriteBits(0, GetNumBitsToNextByte(writer_.GetNumBits()));
-  }
+  // Writes id using coding based on the given |mtf|, which is expected to
+  // contain the given |id|.
+  spv_result_t EncodeExistingId(uint64_t mtf, uint32_t id);
+
+  // Writes type id of the current instruction if can't be inferred.
+  spv_result_t EncodeTypeId();
+
+  // Writes result id of the current instruction if can't be inferred.
+  spv_result_t EncodeResultId();
+
+  // Writes ids which are neither type nor result ids.
+  spv_result_t EncodeRefId(uint32_t id);
+
+  // Writes bits to the stream until the beginning of the next byte if the
+  // number of bits until the next byte is less than |byte_break_if_less_than|.
+  void AddByteBreak(size_t byte_break_if_less_than);
 
   // Encodes a literal number operand and writes it to the bit stream.
-  void EncodeLiteralNumber(const Instruction& instruction,
-                           const spv_parsed_operand_t& operand);
+  spv_result_t EncodeLiteralNumber(const spv_parsed_operand_t& operand);
 
-  spv_const_markv_encoder_options options_;
+  MarkvCodecOptions options_;
 
   // Bit stream where encoded instructions are written.
   BitWriterWord64 writer_;
 
-  // If not nullptr, encoder will write comments.
-  std::unique_ptr<CommentLogger> logger_;
-
   // If not nullptr, disassembled instruction lines will be written to comments.
   // Format: \n separated instruction lines, no header.
   std::unique_ptr<std::stringstream> disassembly_;
-
-  // All ids which were previosly encountered in the module.
-  std::unordered_set<uint32_t> all_known_ids_;
 };
 
 // Decodes MARK-V buffers written by MarkvEncoder.
 class MarkvDecoder : public MarkvCodecBase {
  public:
+  // |model| is owned by the caller, must be not null and valid during the
+  // lifetime of MarkvEncoder.
   MarkvDecoder(spv_const_context context,
-               const uint8_t* markv_data,
-               size_t markv_size_bytes,
-               spv_const_markv_decoder_options options)
-      : MarkvCodecBase(context, GetValidatorOptions(options)),
-        options_(options), reader_(markv_data, markv_size_bytes) {
+               const std::vector<uint8_t>& markv,
+               const MarkvCodecOptions& options,
+               const MarkvModel* model)
+      : MarkvCodecBase(context, GetValidatorOptions(options), model),
+        options_(options), reader_(markv) {
     (void) options_;
-    vstate_.setIdBound(1);
+    SetIdBound(1);
     parsed_operands_.reserve(25);
+    inst_words_.reserve(25);
+  }
+
+  // Creates an internal logger which writes comments on the decoding process.
+  void CreateLogger(MarkvLogConsumer log_consumer,
+                    MarkvDebugConsumer debug_consumer) {
+    logger_.reset(new MarkvLogger(log_consumer, debug_consumer));
   }
 
   // Decodes SPIR-V from MARK-V and stores the words in |spirv_binary|.
@@ -565,69 +756,66 @@
     uint32_t bit_width;
   };
 
-  // Creates and returns validator options. Return value owned by the caller.
+  // Creates and returns validator options. Returned value owned by the caller.
   static spv_validator_options GetValidatorOptions(
-      spv_const_markv_decoder_options) {
-    return spvValidatorOptionsCreate();
+      const MarkvCodecOptions& options) {
+    return options.validate_spirv_binary ?
+        spvValidatorOptionsCreate() : nullptr;
   }
 
-  // Reads a single word from bit stream. |type| determines if the word needs
-  // to be decoded and how. Returns false if read fails.
-  bool DecodeOperandWord(spv_operand_type_t type, uint32_t* word) {
-    const size_t chunk_length = GetOperandVariableWidthChunkLength(type);
-    if (chunk_length) {
-      return reader_.ReadVariableWidthU32(word, chunk_length);
-    } else {
-      return reader_.ReadUnencoded(word);
-    }
-  }
-
-  // Fetches the id from the move-to-front list and moves it to front.
-  uint32_t GetIdAndMoveToFront(uint16_t index) {
-    if (index >= move_to_front_ids_.size()) {
-      // Issue new id.
-      const uint32_t id = vstate_.getIdBound();
-      move_to_front_ids_.push_front(id);
-      vstate_.setIdBound(id + 1);
-      return id;
-    } else {
-      if (index == 0)
-        return move_to_front_ids_.front();
-
-      // Iterate to index.
-      auto it = move_to_front_ids_.begin();
-      for (size_t i = 0; i < index; ++i)
-        ++it;
-      const uint32_t id = *it;
-      move_to_front_ids_.erase(it);
-      move_to_front_ids_.push_front(id);
-      return id;
-    }
-  }
-
-  // Decodes id index and fetches the id from move-to-front list.
-  bool DecodeId(uint32_t* id) {
-    uint16_t index = 0;
-    if (!reader_.ReadVariableWidthU16(&index, model_->id_index_chunk_length()))
-       return false;
-
-    *id = GetIdAndMoveToFront(index);
-    return true;
-  }
-
-  bool ReadToByteBreakIfAgreed() {
-    if (!ShouldByteBreak(reader_.GetNumReadBits()))
-      return true;
-
+  // Reads a single bit from reader_. The read bit is stored in |bit|.
+  // Returns false iff reader_ fails.
+  bool ReadBit(bool* bit) {
     uint64_t bits = 0;
-    if (!reader_.ReadBits(&bits,
-                          GetNumBitsToNextByte(reader_.GetNumReadBits())))
-      return false;
+    const bool result = reader_.ReadBits(&bits, 1);
+    if (result)
+      *bit = bits ? true : false;
+    return result;
+  };
 
-    if (bits != 0)
-      return false;
+  // Returns ReadBit bound to the class object.
+  std::function<bool(bool*)> GetReadBitCallback() {
+    return std::bind(&MarkvDecoder::ReadBit, this, std::placeholders::_1);
+  }
 
-    return true;
+  // Reads a single non-id word from bit stream. operand_.type determines if
+  // the word needs to be decoded and how.
+  spv_result_t DecodeNonIdWord(uint32_t* word);
+
+  // Reads and decodes both opcode and num_operands as a single code.
+  // Returns SPV_UNSUPPORTED iff no suitable codec was found.
+  spv_result_t DecodeOpcodeAndNumberOfOperands(uint32_t* opcode,
+                                               uint32_t* num_operands);
+
+  // Reads mtf rank from bit stream. |mtf| is used to determine the codec
+  // scheme. |fallback_method| is used if no codec defined for |mtf|.
+  spv_result_t DecodeMtfRankHuffman(uint64_t mtf, uint32_t fallback_method,
+                                    uint32_t* rank);
+
+  // Reads id using coding based on mtf associated with the id descriptor.
+  // Returns SPV_UNSUPPORTED iff fallback method needs to be used.
+  spv_result_t DecodeIdWithDescriptor(uint32_t* id);
+
+  // Reads id using coding based on the given |mtf|, which is expected to
+  // contain the needed |id|.
+  spv_result_t DecodeExistingId(uint64_t mtf, uint32_t* id);
+
+  // Reads type id of the current instruction if can't be inferred.
+  spv_result_t DecodeTypeId();
+
+  // Reads result id of the current instruction if can't be inferred.
+  spv_result_t DecodeResultId();
+
+  // Reads id which is neither type nor result id.
+  spv_result_t DecodeRefId(uint32_t* id);
+
+  // Reads and discards bits until the beginning of the next byte if the
+  // number of bits until the next byte is less than |byte_break_if_less_than|.
+  bool ReadToByteBreak(size_t byte_break_if_less_than);
+
+  // Returns instruction words decoded up to this point.
+  const uint32_t* GetInstWords() const override {
+    return inst_words_.data();
   }
 
   // Reads a literal number as it is described in |operand| from the bit stream,
@@ -636,14 +824,12 @@
 
   // Reads instruction from bit stream, decodes and validates it.
   // Decoded instruction is valid until the next call of DecodeInstruction().
-  spv_result_t DecodeInstruction(spv_parsed_instruction_t* inst);
+  spv_result_t DecodeInstruction();
 
   // Read operand from the stream decodes and validates it.
-  spv_result_t DecodeOperand(size_t instruction_offset, size_t operand_offset,
-                             spv_parsed_instruction_t* inst,
+  spv_result_t DecodeOperand(size_t operand_offset,
                              const spv_operand_type_t type,
-                             spv_operand_pattern_t* expected_operands,
-                             bool read_result_id);
+                             spv_operand_pattern_t* expected_operands);
 
   // Records the numeric type for an operand according to the type information
   // associated with the given non-zero type Id.  This can fail if the type Id
@@ -653,12 +839,12 @@
   spv_result_t SetNumericTypeInfoForType(spv_parsed_operand_t* parsed_operand,
                                          uint32_t type_id);
 
-  // Records the number type for the given instruction, if that
-  // instruction generates a type.  For types that aren't scalar numbers,
-  // record something with number kind SPV_NUMBER_NONE.
-  void RecordNumberType(const spv_parsed_instruction_t& inst);
+  // Records the number type for the current instruction, if it generates a
+  // type. For types that aren't scalar numbers, record something with number
+  // kind SPV_NUMBER_NONE.
+  void RecordNumberType();
 
-  spv_const_markv_decoder_options options_;
+  MarkvCodecOptions options_;
 
   // Temporary sink where decoded SPIR-V words are written. Once it contains the
   // entire module, the container is moved and returned.
@@ -671,53 +857,1214 @@
   // Valid until next DecodeInstruction call.
   std::vector<spv_parsed_operand_t> parsed_operands_;
 
-  // Maps a result ID to its type ID.  By convention:
-  //  - a result ID that is a type definition maps to itself.
-  //  - a result ID without a type maps to 0.  (E.g. for OpLabel)
-  std::unordered_map<uint32_t, uint32_t> id_to_type_id_;
+  // Temporary storage for current instruction words.
+  // Valid until next DecodeInstruction call.
+  std::vector<uint32_t> inst_words_;
+
   // Maps a type ID to its number type description.
   std::unordered_map<uint32_t, NumberType> type_id_to_number_type_info_;
+
   // Maps an ExtInstImport id to the extended instruction type.
   std::unordered_map<uint32_t, spv_ext_inst_type_t> import_id_to_ext_inst_type_;
 };
 
-void MarkvEncoder::EncodeLiteralNumber(const Instruction& instruction,
-                                       const spv_parsed_operand_t& operand) {
-  if (operand.number_bit_width == 32) {
-    const uint32_t word = instruction.word(operand.offset);
-    if (operand.number_kind == SPV_NUMBER_UNSIGNED_INT) {
-      writer_.WriteVariableWidthU32(word, model_->u32_chunk_length());
-    } else if (operand.number_kind == SPV_NUMBER_SIGNED_INT) {
-      int32_t val = 0;
-      std::memcpy(&val, &word, 4);
-      writer_.WriteVariableWidthS32(val, model_->s32_chunk_length(),
-                                    model_->s32_block_exponent());
-    } else if (operand.number_kind == SPV_NUMBER_FLOATING) {
-      writer_.WriteUnencoded(word);
-    } else {
-      assert(0);
+void MarkvCodecBase::ProcessCurInstruction() {
+  instructions_.emplace_back(new Instruction(&inst_));
+
+  const SpvOp opcode = SpvOp(inst_.opcode);
+
+  if (inst_.result_id) {
+    id_to_def_instruction_.emplace(inst_.result_id, instructions_.back().get());
+
+    // Collect ids local to the current function.
+    if (cur_function_id_){
+      ids_local_to_cur_function_.push_back(inst_.result_id);
     }
-  } else if (operand.number_bit_width == 16) {
-    const uint16_t word =
-        static_cast<uint16_t>(instruction.word(operand.offset));
-    if (operand.number_kind == SPV_NUMBER_UNSIGNED_INT) {
-      writer_.WriteVariableWidthU16(word, model_->u16_chunk_length());
-    } else if (operand.number_kind == SPV_NUMBER_SIGNED_INT) {
-      int16_t val = 0;
-      std::memcpy(&val, &word, 2);
-      writer_.WriteVariableWidthS16(val, model_->s16_chunk_length(),
-                                    model_->s16_block_exponent());
-    } else if (operand.number_kind == SPV_NUMBER_FLOATING) {
-      // TODO(atgoo@github.com) Write only 16 bits.
-      writer_.WriteUnencoded(word);
-    } else {
-      assert(0);
+
+    // Starting new function.
+    if (opcode == SpvOpFunction) {
+      cur_function_id_ = inst_.result_id;
+      cur_function_return_type_ = inst_.type_id;
+      multi_mtf_.Insert(GetMtfFunctionWithReturnType(inst_.type_id),
+                        inst_.result_id);
+
+      // Store function parameter types in a queue, so that we know which types
+      // to expect in the following OpFunctionParameter instructions.
+      const Instruction* def_inst = FindDef(inst_.words[4]);
+      assert(def_inst);
+      assert(def_inst->opcode() == SpvOpTypeFunction);
+      for (uint32_t i = 3; i < def_inst->words().size(); ++i) {
+        remaining_function_parameter_types_.push_back(def_inst->word(i));
+      }
     }
+  }
+
+  // Remove local ids from MTFs if function end.
+  if (opcode == SpvOpFunctionEnd) {
+    cur_function_id_ = 0;
+    for (uint32_t id : ids_local_to_cur_function_)
+      multi_mtf_.RemoveFromAll(id);
+    ids_local_to_cur_function_.clear();
+    assert(remaining_function_parameter_types_.empty());
+  }
+
+  if (!inst_.result_id)
+    return;
+
+  {
+    // Save the result ID to type ID mapping.
+    // In the grammar, type ID always appears before result ID.
+    // A regular value maps to its type. Some instructions (e.g. OpLabel)
+    // have no type Id, and will map to 0. The result Id for a
+    // type-generating instruction (e.g. OpTypeInt) maps to itself.
+    auto insertion_result = id_to_type_id_.emplace(
+        inst_.result_id,
+        spvOpcodeGeneratesType(SpvOp(inst_.opcode)) ?
+        inst_.result_id : inst_.type_id);
+    (void)insertion_result;
+    assert(insertion_result.second);
+  }
+
+  // Add result_id to MTFs.
+
+  switch (opcode) {
+    case SpvOpTypeFloat:
+    case SpvOpTypeInt:
+    case SpvOpTypeBool:
+    case SpvOpTypeVector:
+    case SpvOpTypePointer:
+    case SpvOpExtInstImport:
+    case SpvOpTypeSampledImage:
+    case SpvOpTypeImage:
+    case SpvOpTypeSampler:
+      multi_mtf_.Insert(GetMtfIdGeneratedByOpcode(opcode), inst_.result_id);
+      break;
+    default:
+      break;
+  }
+
+  if (spvOpcodeIsComposite(opcode)) {
+    multi_mtf_.Insert(kMtfTypeComposite, inst_.result_id);
+  }
+
+  if (opcode == SpvOpLabel) {
+    multi_mtf_.InsertOrPromote(kMtfLabel, inst_.result_id);
+  }
+
+  if (opcode == SpvOpTypeInt) {
+    multi_mtf_.Insert(kMtfTypeScalar, inst_.result_id);
+    multi_mtf_.Insert(kMtfTypeIntScalarOrVector, inst_.result_id);
+  }
+
+  if (opcode == SpvOpTypeFloat) {
+    multi_mtf_.Insert(kMtfTypeScalar, inst_.result_id);
+    multi_mtf_.Insert(kMtfTypeFloatScalarOrVector, inst_.result_id);
+  }
+
+  if (opcode == SpvOpTypeBool) {
+    multi_mtf_.Insert(kMtfTypeScalar, inst_.result_id);
+    multi_mtf_.Insert(kMtfTypeBoolScalarOrVector, inst_.result_id);
+  }
+
+  if (opcode == SpvOpTypeVector) {
+    const uint32_t component_type_id = inst_.words[2];
+    const uint32_t size = inst_.words[3];
+    if (multi_mtf_.HasValue(GetMtfIdGeneratedByOpcode(SpvOpTypeFloat),
+                            component_type_id)) {
+      multi_mtf_.Insert(kMtfTypeFloatScalarOrVector, inst_.result_id);
+    } else if (multi_mtf_.HasValue(GetMtfIdGeneratedByOpcode(SpvOpTypeInt),
+                            component_type_id)) {
+      multi_mtf_.Insert(kMtfTypeIntScalarOrVector, inst_.result_id);
+    } else if (multi_mtf_.HasValue(GetMtfIdGeneratedByOpcode(SpvOpTypeBool),
+                            component_type_id)) {
+      multi_mtf_.Insert(kMtfTypeBoolScalarOrVector, inst_.result_id);
+    }
+    multi_mtf_.Insert(GetMtfTypeVectorOfSize(size), inst_.result_id);
+  }
+
+  if (inst_.opcode == SpvOpTypeFunction) {
+    const uint32_t return_type = inst_.words[2];
+    multi_mtf_.Insert(kMtfTypeReturnedByFunction, return_type);
+    multi_mtf_.Insert(GetMtfFunctionTypeWithReturnType(return_type),
+                      inst_.result_id);
+  }
+
+  if (inst_.type_id) {
+    const Instruction* type_inst = FindDef(inst_.type_id);
+    assert(type_inst);
+
+    multi_mtf_.Insert(kMtfObject, inst_.result_id);
+
+    multi_mtf_.Insert(GetMtfIdOfType(inst_.type_id), inst_.result_id);
+
+    if (multi_mtf_.HasValue(kMtfTypeFloatScalarOrVector, inst_.type_id)) {
+      multi_mtf_.Insert(kMtfFloatScalarOrVector, inst_.result_id);
+    }
+
+    if (multi_mtf_.HasValue(kMtfTypeIntScalarOrVector, inst_.type_id))
+      multi_mtf_.Insert(kMtfIntScalarOrVector, inst_.result_id);
+
+    if (multi_mtf_.HasValue(kMtfTypeBoolScalarOrVector, inst_.type_id))
+      multi_mtf_.Insert(kMtfBoolScalarOrVector, inst_.result_id);
+
+    if (multi_mtf_.HasValue(kMtfTypeComposite, inst_.type_id))
+      multi_mtf_.Insert(kMtfComposite, inst_.result_id);
+
+    if (inst_.opcode == SpvOpConstant) {
+      if (multi_mtf_.HasValue(
+          GetMtfIdGeneratedByOpcode(SpvOpTypeInt), inst_.type_id)) {
+        multi_mtf_.Insert(kMtfConstInteger, inst_.result_id);
+        const uint32_t presumed_index = inst_.words[3];
+        if (presumed_index <= kMarkvMaxPresumedAccessIndex) {
+          const auto result =
+              presumed_index_to_id_.emplace(presumed_index, inst_.result_id);
+          if (result.second) {
+            id_to_presumed_index_.emplace(inst_.result_id, presumed_index);
+          }
+        }
+      }
+    }
+
+    switch (type_inst->opcode()) {
+      case SpvOpTypeInt:
+      case SpvOpTypeBool:
+      case SpvOpTypePointer:
+      case SpvOpTypeVector:
+      case SpvOpTypeImage:
+      case SpvOpTypeSampledImage:
+      case SpvOpTypeSampler:
+        multi_mtf_.Insert(GetMtfIdWithTypeGeneratedByOpcode(
+            type_inst->opcode()), inst_.result_id);
+        break;
+      default:
+        break;
+    }
+
+    if (type_inst->opcode() == SpvOpTypeVector) {
+      const uint32_t component_type = type_inst->word(2);
+      multi_mtf_.Insert(GetMtfVectorOfComponentType(component_type),
+                        inst_.result_id);
+    }
+
+    if (type_inst->opcode() == SpvOpTypePointer) {
+      assert(type_inst->operands().size() > 2);
+      assert(type_inst->words().size() > type_inst->operands()[2].offset);
+      const uint32_t data_type =
+          type_inst->word(type_inst->operands()[2].offset);
+      multi_mtf_.Insert(GetMtfPointerToType(data_type), inst_.result_id);
+
+      if (multi_mtf_.HasValue(kMtfTypeComposite, data_type))
+        multi_mtf_.Insert(kMtfTypePointerToComposite, inst_.result_id);
+    }
+  }
+
+  if (spvOpcodeGeneratesType(opcode)) {
+    if (opcode != SpvOpTypeFunction) {
+      multi_mtf_.Insert(kMtfTypeNonFunction, inst_.result_id);
+    }
+  }
+
+  const uint32_t descriptor = id_descriptors_.ProcessInstruction(inst_);
+  if (model_->DescriptorHasCodingScheme(descriptor))
+    multi_mtf_.Insert(GetMtfIdDescriptor(descriptor), inst_.result_id);
+}
+
+uint64_t MarkvCodecBase::GetRuleBasedMtf() {
+  // This function is only called for id operands (but not result ids).
+  assert(spvIsIdType(operand_.type) ||
+         operand_.type == SPV_OPERAND_TYPE_OPTIONAL_ID);
+  assert(operand_.type != SPV_OPERAND_TYPE_RESULT_ID);
+
+  const SpvOp opcode = static_cast<SpvOp>(inst_.opcode);
+
+  // All operand slots which expect label id.
+  if ((inst_.opcode == SpvOpLoopMerge && operand_index_ <= 1) ||
+      (inst_.opcode == SpvOpSelectionMerge && operand_index_ == 0) ||
+      (inst_.opcode == SpvOpBranch && operand_index_ == 0) ||
+      (inst_.opcode == SpvOpBranchConditional &&
+       (operand_index_ == 1 || operand_index_ == 2 )) ||
+      (inst_.opcode == SpvOpPhi && operand_index_ >= 3 &&
+       operand_index_ % 2 == 1) ||
+      (inst_.opcode == SpvOpSwitch && operand_index_ > 0)) {
+    return kMtfLabel;
+  }
+
+  switch (opcode) {
+    case SpvOpFAdd:
+    case SpvOpFSub:
+    case SpvOpFMul:
+    case SpvOpFDiv:
+    case SpvOpFRem:
+    case SpvOpFMod:
+    case SpvOpFNegate: {
+      if (operand_index_ == 0)
+        return kMtfTypeFloatScalarOrVector;
+
+      return GetMtfIdOfType(inst_.type_id);
+    }
+
+    case SpvOpISub:
+    case SpvOpIAdd:
+    case SpvOpIMul:
+    case SpvOpSDiv:
+    case SpvOpUDiv:
+    case SpvOpSMod:
+    case SpvOpUMod:
+    case SpvOpSRem:
+    case SpvOpSNegate: {
+      if (operand_index_ == 0)
+        return kMtfTypeIntScalarOrVector;
+
+      return kMtfIntScalarOrVector;
+    }
+
+    // TODO(atgoo@github.com) Add OpConvertFToU and other opcodes.
+
+    case SpvOpFOrdEqual:
+    case SpvOpFUnordEqual:
+    case SpvOpFOrdNotEqual:
+    case SpvOpFUnordNotEqual:
+    case SpvOpFOrdLessThan:
+    case SpvOpFUnordLessThan:
+    case SpvOpFOrdGreaterThan:
+    case SpvOpFUnordGreaterThan:
+    case SpvOpFOrdLessThanEqual:
+    case SpvOpFUnordLessThanEqual:
+    case SpvOpFOrdGreaterThanEqual:
+    case SpvOpFUnordGreaterThanEqual: {
+      if (operand_index_ == 0)
+        return kMtfTypeBoolScalarOrVector;
+      if (operand_index_ == 2)
+        return kMtfFloatScalarOrVector;
+      if (operand_index_ == 3) {
+        const uint32_t first_operand_id = GetInstWords()[3];
+        const uint32_t first_operand_type =
+            id_to_type_id_.at(first_operand_id);
+        return GetMtfIdOfType(first_operand_type);
+      }
+      break;
+    }
+
+    case SpvOpVectorShuffle: {
+      if (operand_index_ == 0) {
+        assert(inst_.num_operands > 4);
+        return GetMtfTypeVectorOfSize(inst_.num_operands - 4);
+      }
+
+      assert(inst_.type_id);
+      if (operand_index_ == 2 || operand_index_ == 3)
+        return GetMtfVectorOfComponentType(
+            GetVectorComponentType(inst_.type_id));
+      break;
+    }
+
+    case SpvOpVectorTimesScalar: {
+      if (operand_index_ == 0) {
+        // TODO(atgoo@github.com) Could be narrowed to vector of floats.
+        return GetMtfIdGeneratedByOpcode(SpvOpTypeVector);
+      }
+
+      assert(inst_.type_id);
+      if (operand_index_ == 2)
+        return GetMtfIdOfType(inst_.type_id);
+      if (operand_index_ == 3)
+        return GetMtfIdOfType(GetVectorComponentType(inst_.type_id));
+      break;
+    }
+
+    case SpvOpDot: {
+      if (operand_index_ == 0)
+        return GetMtfIdGeneratedByOpcode(SpvOpTypeFloat);
+
+      assert(inst_.type_id);
+      if (operand_index_ == 2)
+        return GetMtfVectorOfComponentType(inst_.type_id);
+      if (operand_index_ == 3) {
+        const uint32_t vector_id = GetInstWords()[3];
+        const uint32_t vector_type = id_to_type_id_.at(vector_id);
+        return GetMtfIdOfType(vector_type);
+      }
+      break;
+    }
+
+    case SpvOpTypeVector: {
+      if (operand_index_ == 1) {
+        return kMtfTypeScalar;
+      }
+      break;
+    }
+
+    case SpvOpTypeMatrix: {
+      if (operand_index_ == 1) {
+        return GetMtfIdGeneratedByOpcode(SpvOpTypeVector);
+      }
+      break;
+    }
+
+    case SpvOpTypePointer: {
+      if (operand_index_ == 2) {
+        return kMtfTypeNonFunction;
+      }
+      break;
+    }
+
+    case SpvOpTypeStruct: {
+      if (operand_index_ >= 1) {
+        return kMtfTypeNonFunction;
+      }
+      break;
+    }
+
+    case SpvOpTypeFunction: {
+      if (operand_index_ == 1) {
+        return kMtfTypeNonFunction;
+      }
+
+      if (operand_index_ >= 2) {
+        return kMtfTypeNonFunction;
+      }
+      break;
+    }
+
+    case SpvOpLoad: {
+      if (operand_index_ == 0)
+        return kMtfTypeNonFunction;
+
+      if (operand_index_ == 2) {
+        assert(inst_.type_id);
+        return GetMtfPointerToType(inst_.type_id);
+      }
+      break;
+    }
+
+    case SpvOpStore: {
+      if (operand_index_ == 0)
+        return GetMtfIdWithTypeGeneratedByOpcode(SpvOpTypePointer);
+      if (operand_index_ == 1) {
+        const uint32_t pointer_id = GetInstWords()[1];
+        const uint32_t pointer_type = id_to_type_id_.at(pointer_id);
+        const Instruction* pointer_inst = FindDef(pointer_type);
+        assert(pointer_inst);
+        assert(pointer_inst->opcode() == SpvOpTypePointer);
+        const uint32_t data_type =
+            pointer_inst->word(pointer_inst->operands()[2].offset);
+        return GetMtfIdOfType(data_type);
+      }
+      break;
+    }
+
+    case SpvOpVariable: {
+      if (operand_index_ == 0)
+        return GetMtfIdGeneratedByOpcode(SpvOpTypePointer);
+      break;
+    }
+
+    case SpvOpAccessChain: {
+      if (operand_index_ == 0)
+        return GetMtfIdGeneratedByOpcode(SpvOpTypePointer);
+      if (operand_index_ == 2)
+        return kMtfTypePointerToComposite;
+      if (operand_index_ >= 3)
+        return GetMtfIdWithTypeGeneratedByOpcode(SpvOpTypeInt);
+      break;
+    }
+
+    case SpvOpCompositeConstruct: {
+      if (operand_index_ == 0)
+        return kMtfTypeComposite;
+      if (operand_index_ >= 2) {
+        const uint32_t composite_type = GetInstWords()[1];
+        if (multi_mtf_.HasValue(kMtfTypeFloatScalarOrVector, composite_type))
+          return kMtfFloatScalarOrVector;
+        if (multi_mtf_.HasValue(kMtfTypeIntScalarOrVector, composite_type))
+          return kMtfIntScalarOrVector;
+        if (multi_mtf_.HasValue(kMtfTypeBoolScalarOrVector, composite_type))
+          return kMtfBoolScalarOrVector;
+      }
+      break;
+    }
+
+    case SpvOpCompositeExtract: {
+      if (operand_index_ == 2)
+        return kMtfComposite;
+      break;
+    }
+
+    case SpvOpConstantComposite: {
+      if (operand_index_ == 0)
+        return kMtfTypeComposite;
+      if (operand_index_ >= 2) {
+        const Instruction* composite_type_inst = FindDef(inst_.type_id);
+        assert(composite_type_inst);
+        if (composite_type_inst->opcode() == SpvOpTypeVector) {
+          return GetMtfIdOfType(composite_type_inst->word(2));
+        }
+      }
+      break;
+    }
+
+    case SpvOpExtInst: {
+      if (operand_index_ == 2)
+        return GetMtfIdGeneratedByOpcode(SpvOpExtInstImport);
+      if (operand_index_ >= 4) {
+        const uint32_t return_type = GetInstWords()[1];
+        const uint32_t ext_inst_type = inst_.ext_inst_type;
+        const uint32_t ext_inst_index = GetInstWords()[4];
+        // TODO(atgoo@github.com) The list of extended instructions is
+        // incomplete. Only common instructions and low-hanging fruits listed.
+        if (ext_inst_type == SPV_EXT_INST_TYPE_GLSL_STD_450) {
+          switch (ext_inst_index) {
+            case GLSLstd450FAbs:
+            case GLSLstd450FClamp:
+            case GLSLstd450FMax:
+            case GLSLstd450FMin:
+            case GLSLstd450FMix:
+            case GLSLstd450Step:
+            case GLSLstd450SmoothStep:
+            case GLSLstd450Fma:
+            case GLSLstd450Pow:
+            case GLSLstd450Exp:
+            case GLSLstd450Exp2:
+            case GLSLstd450Log:
+            case GLSLstd450Log2:
+            case GLSLstd450Sqrt:
+            case GLSLstd450InverseSqrt:
+            case GLSLstd450Fract:
+            case GLSLstd450Floor:
+            case GLSLstd450Ceil:
+            case GLSLstd450Radians:
+            case GLSLstd450Degrees:
+            case GLSLstd450Sin:
+            case GLSLstd450Cos:
+            case GLSLstd450Tan:
+            case GLSLstd450Sinh:
+            case GLSLstd450Cosh:
+            case GLSLstd450Tanh:
+            case GLSLstd450Asin:
+            case GLSLstd450Acos:
+            case GLSLstd450Atan:
+            case GLSLstd450Atan2:
+            case GLSLstd450Asinh:
+            case GLSLstd450Acosh:
+            case GLSLstd450Atanh:
+            case GLSLstd450MatrixInverse:
+            case GLSLstd450Cross:
+            case GLSLstd450Normalize:
+            case GLSLstd450Reflect:
+            case GLSLstd450FaceForward:
+              return GetMtfIdOfType(return_type);
+            case GLSLstd450Length:
+            case GLSLstd450Distance:
+            case GLSLstd450Refract:
+              return kMtfFloatScalarOrVector;
+            default:
+              break;
+          }
+        } else if (ext_inst_type == SPV_EXT_INST_TYPE_OPENCL_STD) {
+          switch (ext_inst_index) {
+            case OpenCLLIB::Fabs:
+            case OpenCLLIB::FClamp:
+            case OpenCLLIB::Fmax:
+            case OpenCLLIB::Fmin:
+            case OpenCLLIB::Step:
+            case OpenCLLIB::Smoothstep:
+            case OpenCLLIB::Fma:
+            case OpenCLLIB::Pow:
+            case OpenCLLIB::Exp:
+            case OpenCLLIB::Exp2:
+            case OpenCLLIB::Log:
+            case OpenCLLIB::Log2:
+            case OpenCLLIB::Sqrt:
+            case OpenCLLIB::Rsqrt:
+            case OpenCLLIB::Fract:
+            case OpenCLLIB::Floor:
+            case OpenCLLIB::Ceil:
+            case OpenCLLIB::Radians:
+            case OpenCLLIB::Degrees:
+            case OpenCLLIB::Sin:
+            case OpenCLLIB::Cos:
+            case OpenCLLIB::Tan:
+            case OpenCLLIB::Sinh:
+            case OpenCLLIB::Cosh:
+            case OpenCLLIB::Tanh:
+            case OpenCLLIB::Asin:
+            case OpenCLLIB::Acos:
+            case OpenCLLIB::Atan:
+            case OpenCLLIB::Atan2:
+            case OpenCLLIB::Asinh:
+            case OpenCLLIB::Acosh:
+            case OpenCLLIB::Atanh:
+            case OpenCLLIB::Cross:
+            case OpenCLLIB::Normalize:
+              return GetMtfIdOfType(return_type);
+            case OpenCLLIB::Length:
+            case OpenCLLIB::Distance:
+              return kMtfFloatScalarOrVector;
+            default:
+              break;
+          }
+        }
+      }
+      break;
+    }
+
+    case SpvOpFunction: {
+      if (operand_index_ == 0)
+        return kMtfTypeReturnedByFunction;
+
+      if (operand_index_ == 3) {
+        const uint32_t return_type = GetInstWords()[1];
+        return GetMtfFunctionTypeWithReturnType(return_type);
+      }
+      break;
+    }
+
+    case SpvOpFunctionCall: {
+      if (operand_index_ == 0)
+        return kMtfTypeReturnedByFunction;
+
+      if (operand_index_ == 2) {
+        const uint32_t return_type = GetInstWords()[1];
+        return GetMtfFunctionWithReturnType(return_type);
+      }
+
+      if (operand_index_ >= 3) {
+        const uint32_t function_id = GetInstWords()[3];
+        const Instruction* function_inst = FindDef(function_id);
+        if (!function_inst)
+          return kMtfObject;
+
+        assert(function_inst->opcode() == SpvOpFunction);
+
+        const uint32_t function_type_id = function_inst->word(4);
+        const Instruction* function_type_inst = FindDef(function_type_id);
+        assert(function_type_inst);
+        assert(function_type_inst->opcode() == SpvOpTypeFunction);
+
+        const uint32_t argument_type =
+            function_type_inst->word(operand_index_);
+        return GetMtfIdOfType(argument_type);
+      }
+      break;
+    }
+
+    case SpvOpReturnValue: {
+      if (operand_index_ == 0)
+        return GetMtfIdOfType(cur_function_return_type_);
+      break;
+    }
+
+    case SpvOpBranchConditional: {
+      if (operand_index_ == 0)
+        return GetMtfIdWithTypeGeneratedByOpcode(SpvOpTypeBool);
+      break;
+    }
+
+    case SpvOpSampledImage: {
+      if (operand_index_ == 0)
+        return GetMtfIdGeneratedByOpcode(SpvOpTypeSampledImage);
+      if (operand_index_ == 2)
+        return GetMtfIdWithTypeGeneratedByOpcode(SpvOpTypeImage);
+      if (operand_index_ == 3)
+        return GetMtfIdWithTypeGeneratedByOpcode(SpvOpTypeSampler);
+      break;
+    }
+
+    case SpvOpImageSampleImplicitLod: {
+      if (operand_index_ == 0)
+        return GetMtfIdGeneratedByOpcode(SpvOpTypeVector);
+      if (operand_index_ == 2)
+        return GetMtfIdWithTypeGeneratedByOpcode(SpvOpTypeSampledImage);
+      if (operand_index_ == 3)
+        return GetMtfIdWithTypeGeneratedByOpcode(SpvOpTypeVector);
+      break;
+    }
+
+    default:
+      break;
+  }
+
+  return kMtfNone;
+}
+
+spv_result_t MarkvEncoder::EncodeNonIdWord(uint32_t word) {
+  auto* codec = model_->GetNonIdWordHuffmanCodec(inst_.opcode, operand_index_);
+
+  if (codec) {
+    uint64_t bits = 0;
+    size_t num_bits = 0;
+    if (codec->Encode(word, &bits, &num_bits)) {
+      // Encoding successful.
+      writer_.WriteBits(bits, num_bits);
+      return SPV_SUCCESS;
+    } else {
+      // Encoding failed, write kMarkvNoneOfTheAbove flag.
+      if (!codec->Encode(kMarkvNoneOfTheAbove, &bits, &num_bits))
+        return Diag(SPV_ERROR_INTERNAL)
+            << "Non-id word Huffman table for "
+            << spvOpcodeString(SpvOp(inst_.opcode))
+            << " operand index " << operand_index_
+            << " is missing kMarkvNoneOfTheAbove";
+      writer_.WriteBits(bits, num_bits);
+    }
+  }
+
+  // Fallback encoding.
+  const size_t chunk_length =
+      model_->GetOperandVariableWidthChunkLength(operand_.type);
+  if (chunk_length) {
+    writer_.WriteVariableWidthU32(word, chunk_length);
   } else {
-    assert(operand.number_bit_width == 64);
+    writer_.WriteUnencoded(word);
+  }
+  return SPV_SUCCESS;
+}
+
+spv_result_t MarkvDecoder::DecodeNonIdWord(uint32_t* word) {
+  auto* codec = model_->GetNonIdWordHuffmanCodec(inst_.opcode, operand_index_);
+
+  if (codec) {
+    uint64_t decoded_value = 0;
+    if (!codec->DecodeFromStream(GetReadBitCallback(), &decoded_value))
+      return Diag(SPV_ERROR_INVALID_BINARY)
+          << "Failed to decode non-id word with Huffman";
+
+    if (decoded_value != kMarkvNoneOfTheAbove) {
+      // The word decoded successfully.
+      *word = uint32_t(decoded_value);
+      assert(*word == decoded_value);
+      return SPV_SUCCESS;
+    }
+
+    // Received kMarkvNoneOfTheAbove signal, use fallback decoding.
+  }
+
+  const size_t chunk_length =
+      model_->GetOperandVariableWidthChunkLength(operand_.type);
+  if (chunk_length) {
+    if (!reader_.ReadVariableWidthU32(word, chunk_length))
+      return Diag(SPV_ERROR_INVALID_BINARY)
+          << "Failed to decode non-id word with varint";
+  } else {
+    if (!reader_.ReadUnencoded(word))
+      return Diag(SPV_ERROR_INVALID_BINARY)
+          << "Failed to read unencoded non-id word";
+  }
+  return SPV_SUCCESS;
+}
+
+spv_result_t MarkvEncoder::EncodeOpcodeAndNumOperands(
+    uint32_t opcode, uint32_t num_operands) {
+  uint64_t bits = 0;
+  size_t num_bits = 0;
+
+  const uint32_t word = opcode | (num_operands << 16);
+
+  // First try to use the Markov chain codec.
+  auto* codec = model_->GetOpcodeAndNumOperandsMarkovHuffmanCodec(GetPrevOpcode());
+  if (codec) {
+    if (codec->Encode(word, &bits, &num_bits)) {
+      // The word was successfully encoded into bits/num_bits.
+      writer_.WriteBits(bits, num_bits);
+      return SPV_SUCCESS;
+    } else {
+      // The word is not in the Huffman table. Write kMarkvNoneOfTheAbove
+      // and use fallback encoding.
+      if (!codec->Encode(kMarkvNoneOfTheAbove, &bits, &num_bits))
+        return Diag(SPV_ERROR_INTERNAL)
+            << "opcode_and_num_operands Huffman table for "
+            << spvOpcodeString(GetPrevOpcode())
+            << "is missing kMarkvNoneOfTheAbove";
+      writer_.WriteBits(bits, num_bits);
+    }
+  }
+
+  // Fallback to base-rate codec.
+  codec = model_->GetOpcodeAndNumOperandsMarkovHuffmanCodec(SpvOpNop);
+  assert(codec);
+  if (codec->Encode(word, &bits, &num_bits)) {
+    // The word was successfully encoded into bits/num_bits.
+    writer_.WriteBits(bits, num_bits);
+    return SPV_SUCCESS;
+  } else {
+    // The word is not in the Huffman table. Write kMarkvNoneOfTheAbove
+    // and return false.
+    if (!codec->Encode(kMarkvNoneOfTheAbove, &bits, &num_bits))
+      return Diag(SPV_ERROR_INTERNAL)
+          << "Global opcode_and_num_operands Huffman table is missing "
+          << "kMarkvNoneOfTheAbove";
+    writer_.WriteBits(bits, num_bits);
+    return SPV_UNSUPPORTED;
+  }
+}
+
+spv_result_t MarkvDecoder::DecodeOpcodeAndNumberOfOperands(
+    uint32_t* opcode, uint32_t* num_operands) {
+  // First try to use the Markov chain codec.
+  auto* codec = model_->GetOpcodeAndNumOperandsMarkovHuffmanCodec(GetPrevOpcode());
+  if (codec) {
+    uint64_t decoded_value = 0;
+    if (!codec->DecodeFromStream(GetReadBitCallback(), &decoded_value))
+      return Diag(SPV_ERROR_INTERNAL)
+          << "Failed to decode opcode_and_num_operands, previous opcode is "
+          << spvOpcodeString(GetPrevOpcode());
+
+    if (decoded_value != kMarkvNoneOfTheAbove) {
+      // The word was successfully decoded.
+      *opcode = uint32_t(decoded_value & 0xFFFF);
+      *num_operands = uint32_t(decoded_value >> 16);
+      return SPV_SUCCESS;
+    }
+
+    // Received kMarkvNoneOfTheAbove signal, use fallback decoding.
+  }
+
+  // Fallback to base-rate codec.
+  codec = model_->GetOpcodeAndNumOperandsMarkovHuffmanCodec(SpvOpNop);
+  assert(codec);
+  uint64_t decoded_value = 0;
+  if (!codec->DecodeFromStream(GetReadBitCallback(), &decoded_value))
+    return Diag(SPV_ERROR_INTERNAL)
+        << "Failed to decode opcode_and_num_operands with global codec";
+
+  if (decoded_value == kMarkvNoneOfTheAbove) {
+    // Received kMarkvNoneOfTheAbove signal, fallback further.
+    return SPV_UNSUPPORTED;
+  }
+
+  *opcode = uint32_t(decoded_value & 0xFFFF);
+  *num_operands = uint32_t(decoded_value >> 16);
+  return SPV_SUCCESS;
+}
+
+spv_result_t MarkvEncoder::EncodeMtfRankHuffman(uint32_t rank, uint64_t mtf,
+                                                uint64_t fallback_method) {
+  const auto* codec = GetMtfHuffmanCodec(mtf);
+  if (!codec) {
+    assert(fallback_method != kMtfNone);
+    codec = GetMtfHuffmanCodec(fallback_method);
+  }
+
+  if (!codec)
+    return Diag(SPV_ERROR_INTERNAL) << "No codec to encode MTF rank";
+
+  uint64_t bits = 0;
+  size_t num_bits = 0;
+  if (rank < kMtfSmallestRankEncodedByValue) {
+    // Encode using Huffman coding.
+    if (!codec->Encode(rank, &bits, &num_bits))
+      return Diag(SPV_ERROR_INTERNAL)
+          << "Failed to encode MTF rank with Huffman";
+
+    writer_.WriteBits(bits, num_bits);
+  } else {
+    // Encode by value.
+    if (!codec->Encode(kMtfRankEncodedByValueSignal, &bits, &num_bits))
+      return Diag(SPV_ERROR_INTERNAL)
+          << "Failed to encode kMtfRankEncodedByValueSignal";
+
+    writer_.WriteBits(bits, num_bits);
+    writer_.WriteVariableWidthU32(rank - kMtfSmallestRankEncodedByValue,
+                                  model_->mtf_rank_chunk_length());
+  }
+  return SPV_SUCCESS;
+}
+
+spv_result_t MarkvDecoder::DecodeMtfRankHuffman(
+    uint64_t mtf, uint32_t fallback_method, uint32_t* rank) {
+  const auto* codec = GetMtfHuffmanCodec(mtf);
+  if (!codec) {
+    assert(fallback_method != kMtfNone);
+    codec = GetMtfHuffmanCodec(fallback_method);
+  }
+
+  if (!codec)
+    return Diag(SPV_ERROR_INTERNAL) << "No codec to decode MTF rank";
+
+  uint32_t decoded_value = 0;
+  if (!codec->DecodeFromStream(GetReadBitCallback(), &decoded_value))
+    return Diag(SPV_ERROR_INTERNAL)
+        << "Failed to decode MTF rank with Huffman";
+
+  if (decoded_value == kMtfRankEncodedByValueSignal) {
+    // Decode by value.
+    if (!reader_.ReadVariableWidthU32(rank, model_->mtf_rank_chunk_length()))
+      return Diag(SPV_ERROR_INTERNAL)
+          << "Failed to decode MTF rank with varint";
+    *rank += kMtfSmallestRankEncodedByValue;
+  } else {
+    // Decode using Huffman coding.
+    assert(decoded_value < kMtfSmallestRankEncodedByValue);
+    *rank = decoded_value;
+  }
+  return SPV_SUCCESS;
+}
+
+spv_result_t MarkvEncoder::EncodeIdWithDescriptor(uint32_t id) {
+  auto* codec = model_->GetIdDescriptorHuffmanCodec(inst_.opcode,
+                                                    operand_index_);
+  if (!codec)
+    return SPV_UNSUPPORTED;
+
+  uint64_t bits = 0;
+  size_t num_bits = 0;
+
+  // Get the descriptor for id.
+  const uint32_t descriptor = id_descriptors_.GetDescriptor(id);
+
+  if (descriptor && codec->Encode(descriptor, &bits, &num_bits)) {
+    // If the descriptor exists and is in the table, write the descriptor and
+    // proceed to encoding the rank.
+    writer_.WriteBits(bits, num_bits);
+  } else {
+    // The descriptor doesn't exist or we have no coding for it. Write
+    // kMarkvNoneOfTheAbove and go to fallback method.
+    if (!codec->Encode(kMarkvNoneOfTheAbove, &bits, &num_bits))
+      return Diag(SPV_ERROR_INTERNAL)
+          << "Descriptor Huffman table for "
+          << spvOpcodeString(SpvOp(inst_.opcode))
+          << " operand index " << operand_index_
+          << " is missing kMarkvNoneOfTheAbove";
+
+    writer_.WriteBits(bits, num_bits);
+    return SPV_UNSUPPORTED;
+  }
+
+  // Descriptor has been encoded. Now encode the rank of the id in the
+  // associated mtf sequence.
+  const uint64_t mtf = GetMtfIdDescriptor(descriptor);
+  return EncodeExistingId(mtf, id);
+}
+
+spv_result_t MarkvDecoder::DecodeIdWithDescriptor(uint32_t* id) {
+  auto* codec = model_->GetIdDescriptorHuffmanCodec(inst_.opcode,
+                                                    operand_index_);
+  if (!codec)
+    return SPV_UNSUPPORTED;
+
+  uint64_t decoded_value = 0;
+  if (!codec->DecodeFromStream(GetReadBitCallback(), &decoded_value))
+    return Diag(SPV_ERROR_INTERNAL)
+        << "Failed to decode descriptor with Huffman";
+
+  if (decoded_value == kMarkvNoneOfTheAbove)
+    return SPV_UNSUPPORTED;
+
+  // If descriptor exists then the id was encoded through descriptor mtf.
+  const uint32_t descriptor = uint32_t(decoded_value);
+  assert(descriptor == decoded_value);
+  assert(descriptor);
+
+  const uint64_t mtf = GetMtfIdDescriptor(descriptor);
+  return DecodeExistingId(mtf, id);
+}
+
+spv_result_t MarkvEncoder::EncodeExistingId(uint64_t mtf, uint32_t id) {
+  assert(multi_mtf_.GetSize(mtf) > 0);
+  if (multi_mtf_.GetSize(mtf) == 1) {
+    // If the sequence has only one element no need to write rank, the decoder
+    // would make the same decision.
+    return SPV_SUCCESS;
+  }
+
+  uint32_t rank = 0;
+  if (!multi_mtf_.RankFromValue(mtf, id, &rank))
+    return Diag(SPV_ERROR_INTERNAL)
+        << "Id is not in the MTF sequence";
+
+  return EncodeMtfRankHuffman(rank, mtf, kMtfGenericNonZeroRank);
+}
+
+spv_result_t MarkvDecoder::DecodeExistingId(uint64_t mtf, uint32_t* id) {
+  assert(multi_mtf_.GetSize(mtf) > 0);
+  *id = 0;
+
+  uint32_t rank = 0;
+
+  if (multi_mtf_.GetSize(mtf) == 1) {
+    rank = 1;
+  } else {
+    const spv_result_t result =
+        DecodeMtfRankHuffman(mtf, kMtfGenericNonZeroRank, &rank);
+    if (result != SPV_SUCCESS)
+      return result;
+  }
+
+  assert(rank);
+  if (!multi_mtf_.ValueFromRank(mtf, rank, id))
+    return Diag(SPV_ERROR_INTERNAL)
+        << "MTF rank is out of bounds";
+
+  return SPV_SUCCESS;
+}
+
+spv_result_t MarkvEncoder::EncodeRefId(uint32_t id) {
+  // TODO(atgoo@github.com) This might not be needed as EncodeIdWithDescriptor
+  // can handle SpvOpAccessChain indices if enough statistics is collected.
+  if (inst_.opcode == SpvOpAccessChain && operand_index_ >= 3) {
+    const auto it = id_to_presumed_index_.find(id);
+    if (it != id_to_presumed_index_.end()) {
+      writer_.WriteBits(1, 1);
+      writer_.WriteFixedWidth(it->second, kMarkvMaxPresumedAccessIndex);
+      return SPV_SUCCESS;
+    }
+
+    writer_.WriteBits(0, 1);
+  }
+
+  {
+    // Try to encode using id descriptor mtfs.
+    const spv_result_t result = EncodeIdWithDescriptor(id);
+    if (result != SPV_UNSUPPORTED)
+      return result;
+    // If can't be done continue with other methods.
+  }
+
+  // Encode using rule-based mtf.
+  uint64_t mtf = GetRuleBasedMtf();
+  const bool can_forward_declare =
+      spvOperandCanBeForwardDeclaredFunction(
+          SpvOp(inst_.opcode))(operand_index_);
+
+  if (mtf != kMtfNone && !can_forward_declare) {
+    assert(multi_mtf_.HasValue(kMtfAll, id));
+    return EncodeExistingId(mtf, id);
+  }
+
+  if (mtf == kMtfNone)
+    mtf = kMtfAll;
+
+  uint32_t rank = 0;
+
+  if (!multi_mtf_.RankFromValue(mtf, id, &rank)) {
+    // This is the first occurrence of a forward declared id.
+    multi_mtf_.Insert(kMtfAll, id);
+    multi_mtf_.Insert(kMtfForwardDeclared, id);
+    if (mtf != kMtfAll)
+      multi_mtf_.Insert(mtf, id);
+    rank = 0;
+  }
+
+  return EncodeMtfRankHuffman(rank, mtf, kMtfAll);
+}
+
+spv_result_t MarkvDecoder::DecodeRefId(uint32_t* id) {
+  if (inst_.opcode == SpvOpAccessChain && operand_index_ >= 3) {
+    uint64_t use_presumed_index_technique = 0;
+    if (!reader_.ReadBits(&use_presumed_index_technique, 1))
+      return Diag(SPV_ERROR_INVALID_BINARY)
+          << "Failed to read use_presumed_index_technique flag";
+
+    if (use_presumed_index_technique) {
+      uint64_t value = 0;
+      if (!reader_.ReadFixedWidth(&value, kMarkvMaxPresumedAccessIndex))
+        return Diag(SPV_ERROR_INVALID_BINARY)
+            << "Failed to read presumed_index";
+
+      const uint32_t presumed_index = static_cast<uint32_t>(value);
+
+      const auto it = presumed_index_to_id_.find(presumed_index);
+      if (it == presumed_index_to_id_.end()) {
+        assert(0);
+        return Diag(SPV_ERROR_INTERNAL)
+            << "Presumed index id not found";
+      }
+
+      *id = it->second;
+      return SPV_SUCCESS;
+    }
+  }
+
+  {
+    const spv_result_t result = DecodeIdWithDescriptor(id);
+    if (result != SPV_UNSUPPORTED)
+      return result;
+  }
+
+  uint64_t mtf = GetRuleBasedMtf();
+  const bool can_forward_declare =
+      spvOperandCanBeForwardDeclaredFunction(
+          SpvOp(inst_.opcode))(operand_index_);
+
+  if (mtf != kMtfNone && !can_forward_declare) {
+    return DecodeExistingId(mtf, id);
+  }
+
+  if (mtf == kMtfNone)
+    mtf = kMtfAll;
+
+  *id = 0;
+
+  uint32_t rank = 0;
+
+  {
+    const spv_result_t result = DecodeMtfRankHuffman(mtf, kMtfAll, &rank);
+    if (result != SPV_SUCCESS)
+      return result;
+  }
+
+  if (rank == 0) {
+    // This is the first occurrence of a forward declared id.
+    *id = GetIdBound();
+    SetIdBound(*id + 1);
+    multi_mtf_.Insert(kMtfAll, *id);
+    multi_mtf_.Insert(kMtfForwardDeclared, *id);
+    if (mtf != kMtfAll)
+      multi_mtf_.Insert(mtf, *id);
+  } else {
+    if (!multi_mtf_.ValueFromRank(mtf, rank, id))
+      return Diag(SPV_ERROR_INTERNAL) << "MTF rank out of bounds";
+  }
+
+  assert(*id);
+  return SPV_SUCCESS;
+}
+
+spv_result_t MarkvEncoder::EncodeTypeId() {
+  if (inst_.opcode == SpvOpFunctionParameter) {
+    assert(!remaining_function_parameter_types_.empty());
+    assert(inst_.type_id == remaining_function_parameter_types_.front());
+    remaining_function_parameter_types_.pop_front();
+    return SPV_SUCCESS;
+  }
+
+  {
+    // Try to encode using id descriptor mtfs.
+    const spv_result_t result = EncodeIdWithDescriptor(inst_.type_id);
+    if (result != SPV_UNSUPPORTED)
+      return result;
+    // If can't be done continue with other methods.
+  }
+
+  uint64_t mtf = GetRuleBasedMtf();
+  assert(!spvOperandCanBeForwardDeclaredFunction(
+      SpvOp(inst_.opcode))(operand_index_));
+
+  if (mtf == kMtfNone) {
+    mtf = kMtfTypeNonFunction;
+    // Function types should have been handled by GetRuleBasedMtf.
+    assert(inst_.opcode != SpvOpFunction);
+  }
+
+  return EncodeExistingId(mtf, inst_.type_id);
+}
+
+spv_result_t MarkvDecoder::DecodeTypeId() {
+  if (inst_.opcode == SpvOpFunctionParameter) {
+    assert(!remaining_function_parameter_types_.empty());
+    inst_.type_id = remaining_function_parameter_types_.front();
+    remaining_function_parameter_types_.pop_front();
+    return SPV_SUCCESS;
+  }
+
+  {
+    const spv_result_t result = DecodeIdWithDescriptor(&inst_.type_id);
+    if (result != SPV_UNSUPPORTED)
+      return result;
+  }
+
+  uint64_t mtf = GetRuleBasedMtf();
+  assert(!spvOperandCanBeForwardDeclaredFunction(
+      SpvOp(inst_.opcode))(operand_index_));
+
+  if (mtf == kMtfNone) {
+    mtf = kMtfTypeNonFunction;
+    // Function types should have been handled by GetRuleBasedMtf.
+    assert(inst_.opcode != SpvOpFunction);
+  }
+
+  return DecodeExistingId(mtf, &inst_.type_id);
+}
+
+spv_result_t MarkvEncoder::EncodeResultId() {
+  uint32_t rank = 0;
+
+  const uint64_t num_still_forward_declared =
+      multi_mtf_.GetSize(kMtfForwardDeclared);
+
+  if (num_still_forward_declared) {
+    // We write the rank only if kMtfForwardDeclared is not empty. If it is
+    // empty the decoder knows that there are no forward declared ids to expect.
+    if (multi_mtf_.RankFromValue(kMtfForwardDeclared,
+                                 inst_.result_id, &rank)) {
+      // This is a definition of a forward declared id. We can remove the id
+      // from kMtfForwardDeclared.
+      if (!multi_mtf_.Remove(kMtfForwardDeclared, inst_.result_id))
+        return Diag(SPV_ERROR_INTERNAL)
+            << "Failed to remove id from kMtfForwardDeclared";
+      writer_.WriteBits(1, 1);
+      writer_.WriteVariableWidthU32(
+          rank, model_->mtf_rank_chunk_length());
+    } else {
+      rank = 0;
+      writer_.WriteBits(0, 1);
+    }
+  }
+
+  if (!rank) {
+    multi_mtf_.Insert(kMtfAll, inst_.result_id);
+  }
+
+  return SPV_SUCCESS;
+}
+
+spv_result_t MarkvDecoder::DecodeResultId() {
+  uint32_t rank = 0;
+
+  const uint64_t num_still_forward_declared =
+      multi_mtf_.GetSize(kMtfForwardDeclared);
+
+  if (num_still_forward_declared) {
+    // Some ids were forward declared. Check if this id is one of them.
+    uint64_t id_was_forward_declared;
+    if (!reader_.ReadBits(&id_was_forward_declared, 1))
+      return Diag(SPV_ERROR_INVALID_BINARY)
+          << "Failed to read id_was_forward_declared flag";
+
+    if (id_was_forward_declared) {
+      if (!reader_.ReadVariableWidthU32(
+          &rank, model_->mtf_rank_chunk_length()))
+        return Diag(SPV_ERROR_INVALID_BINARY)
+            << "Failed to read MTF rank of forward declared id";
+
+      if (rank) {
+        // The id was forward declared, recover it from kMtfForwardDeclared.
+        if (!multi_mtf_.ValueFromRank(kMtfForwardDeclared,
+                                     rank, &inst_.result_id))
+          return Diag(SPV_ERROR_INTERNAL)
+              << "Forward declared MTF rank is out of bounds";
+
+        // We can now remove the id from kMtfForwardDeclared.
+        if (!multi_mtf_.Remove(kMtfForwardDeclared, inst_.result_id))
+          return Diag(SPV_ERROR_INTERNAL)
+              << "Failed to remove id from kMtfForwardDeclared";
+      }
+    }
+  }
+
+  if (inst_.result_id == 0) {
+    // The id was not forward declared, issue a new id.
+    inst_.result_id = GetIdBound();
+    SetIdBound(inst_.result_id + 1);
+  }
+
+  if (!rank) {
+    multi_mtf_.Insert(kMtfAll, inst_.result_id);
+  }
+
+  return SPV_SUCCESS;
+}
+
+spv_result_t MarkvEncoder::EncodeLiteralNumber(
+    const spv_parsed_operand_t& operand) {
+  if (operand.number_bit_width <= 32) {
+    const uint32_t word = inst_.words[operand.offset];
+    return EncodeNonIdWord(word);
+  } else {
+    assert(operand.number_bit_width <= 64);
     const uint64_t word =
-        uint64_t(instruction.word(operand.offset)) |
-        (uint64_t(instruction.word(operand.offset + 1)) << 32);
+        uint64_t(inst_.words[operand.offset]) |
+        (uint64_t(inst_.words[operand.offset + 1]) << 32);
     if (operand.number_kind == SPV_NUMBER_UNSIGNED_INT) {
       writer_.WriteVariableWidthU64(word, model_->u64_chunk_length());
     } else if (operand.number_kind == SPV_NUMBER_SIGNED_INT) {
@@ -728,166 +2075,222 @@
     } else if (operand.number_kind == SPV_NUMBER_FLOATING) {
       writer_.WriteUnencoded(word);
     } else {
-      assert(0);
+      return Diag(SPV_ERROR_INTERNAL) << "Unsupported bit length";
     }
   }
-}
-
-spv_result_t MarkvEncoder::EncodeInstruction(
-    const spv_parsed_instruction_t& inst) {
-  const spv_result_t validation_result = UpdateValidationState(inst);
-  if (validation_result != SPV_SUCCESS)
-    return validation_result;
-
-  bool result_id_was_forward_declared = false;
-  if (all_known_ids_.count(inst.result_id)) {
-    // Result id of the instruction was forward declared.
-    // Write a service opcode to signal this to the decoder.
-    writer_.WriteVariableWidthU32(kMarkvOpNextInstructionEncodesResultId,
-                                  model_->opcode_chunk_length());
-    result_id_was_forward_declared = true;
-  }
-
-  const Instruction& instruction = GetCurrentInstruction();
-  const auto& operands = instruction.operands();
-
-  LogDisassemblyInstruction();
-
-  // Write opcode.
-  writer_.WriteVariableWidthU32(inst.opcode, model_->opcode_chunk_length());
-
-  if (!OpcodeHasFixedNumberOfOperands(SpvOp(inst.opcode))) {
-    // If the opcode has a variable number of operands, encode the number of
-    // operands with the instruction.
-
-    if (logger_)
-      logger_->AppendWhitespaces(kCommentNumWhitespaces);
-
-    writer_.WriteVariableWidthU16(inst.num_operands,
-                                  model_->num_operands_chunk_length());
-  }
-
-  // Write operands.
-  for (const auto& operand : operands) {
-    if (operand.type == SPV_OPERAND_TYPE_RESULT_ID &&
-        !result_id_was_forward_declared) {
-      // Register the id, but don't encode it.
-      GetIdIndex(instruction.word(operand.offset));
-      continue;
-    }
-
-    if (logger_)
-      logger_->AppendWhitespaces(kCommentNumWhitespaces);
-
-    if (operand.type == SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER) {
-      EncodeLiteralNumber(instruction, operand);
-    } else if (operand.type == SPV_OPERAND_TYPE_LITERAL_STRING) {
-      const char* src =
-          reinterpret_cast<const char*>(&instruction.words()[operand.offset]);
-      const size_t length = spv_strnlen_s(src, operand.num_words * 4);
-      if (length == operand.num_words * 4)
-        return vstate_.diag(SPV_ERROR_INVALID_BINARY)
-            << "Failed to find terminal character of literal string";
-      for (size_t i = 0; i < length + 1; ++i)
-        writer_.WriteUnencoded(src[i]);
-    } else if (spvIsIdType(operand.type)) {
-      const uint16_t id_index = GetIdIndex(instruction.word(operand.offset));
-      writer_.WriteVariableWidthU16(id_index, model_->id_index_chunk_length());
-    } else {
-      for (int i = 0; i < operand.num_words; ++i) {
-        const uint32_t word = instruction.word(operand.offset + i);
-        EncodeOperandWord(operand.type, word);
-      }
-    }
-  }
-
-  AddByteBreakIfAgreed();
-
-  if (logger_) {
-    logger_->NewLine();
-    logger_->NewLine();
-  }
-
   return SPV_SUCCESS;
 }
 
 spv_result_t MarkvDecoder::DecodeLiteralNumber(
     const spv_parsed_operand_t& operand) {
-  if (operand.number_bit_width == 32) {
+  if (operand.number_bit_width <= 32) {
     uint32_t word = 0;
-    if (operand.number_kind == SPV_NUMBER_UNSIGNED_INT) {
-      if (!reader_.ReadVariableWidthU32(&word, model_->u32_chunk_length()))
-        return vstate_.diag(SPV_ERROR_INVALID_BINARY)
-            << "Failed to read literal U32";
-    } else if (operand.number_kind == SPV_NUMBER_SIGNED_INT) {
-      int32_t val = 0;
-      if (!reader_.ReadVariableWidthS32(&val, model_->s32_chunk_length(),
-                                        model_->s32_block_exponent()))
-        return vstate_.diag(SPV_ERROR_INVALID_BINARY)
-            << "Failed to read literal S32";
-      std::memcpy(&word, &val, 4);
-    } else if (operand.number_kind == SPV_NUMBER_FLOATING) {
-      if (!reader_.ReadUnencoded(&word))
-        return vstate_.diag(SPV_ERROR_INVALID_BINARY)
-            << "Failed to read literal F32";
-    } else {
-      assert(0);
-    }
-    spirv_.push_back(word);
-  } else if (operand.number_bit_width == 16) {
-    uint32_t word = 0;
-    if (operand.number_kind == SPV_NUMBER_UNSIGNED_INT) {
-      uint16_t val = 0;
-      if (!reader_.ReadVariableWidthU16(&val, model_->u16_chunk_length()))
-        return vstate_.diag(SPV_ERROR_INVALID_BINARY)
-            << "Failed to read literal U16";
-      word = val;
-    } else if (operand.number_kind == SPV_NUMBER_SIGNED_INT) {
-      int16_t val = 0;
-      if (!reader_.ReadVariableWidthS16(&val, model_->s16_chunk_length(),
-                                        model_->s16_block_exponent()))
-        return vstate_.diag(SPV_ERROR_INVALID_BINARY)
-            << "Failed to read literal S16";
-      // Int16 is stored as int32 in SPIR-V, not as bits.
-      int32_t val32 = val;
-      std::memcpy(&word, &val32, 4);
-    } else if (operand.number_kind == SPV_NUMBER_FLOATING) {
-      uint16_t word16 = 0;
-      if (!reader_.ReadUnencoded(&word16))
-        return vstate_.diag(SPV_ERROR_INVALID_BINARY)
-            << "Failed to read literal F16";
-      word = word16;
-    } else {
-      assert(0);
-    }
-    spirv_.push_back(word);
+    const spv_result_t result = DecodeNonIdWord(&word);
+    if (result != SPV_SUCCESS)
+      return result;
+    inst_words_.push_back(word);
   } else {
-    assert(operand.number_bit_width == 64);
+    assert(operand.number_bit_width <= 64);
     uint64_t word = 0;
     if (operand.number_kind == SPV_NUMBER_UNSIGNED_INT) {
       if (!reader_.ReadVariableWidthU64(&word, model_->u64_chunk_length()))
-        return vstate_.diag(SPV_ERROR_INVALID_BINARY)
+        return Diag(SPV_ERROR_INVALID_BINARY)
             << "Failed to read literal U64";
     } else if (operand.number_kind == SPV_NUMBER_SIGNED_INT) {
       int64_t val = 0;
       if (!reader_.ReadVariableWidthS64(&val, model_->s64_chunk_length(),
                                         model_->s64_block_exponent()))
-        return vstate_.diag(SPV_ERROR_INVALID_BINARY)
+        return Diag(SPV_ERROR_INVALID_BINARY)
             << "Failed to read literal S64";
       std::memcpy(&word, &val, 8);
     } else if (operand.number_kind == SPV_NUMBER_FLOATING) {
       if (!reader_.ReadUnencoded(&word))
-        return vstate_.diag(SPV_ERROR_INVALID_BINARY)
+        return Diag(SPV_ERROR_INVALID_BINARY)
             << "Failed to read literal F64";
     } else {
-      assert(0);
+      return Diag(SPV_ERROR_INTERNAL) << "Unsupported bit length";
     }
-    spirv_.push_back(static_cast<uint32_t>(word));
-    spirv_.push_back(static_cast<uint32_t>(word >> 32));
+    inst_words_.push_back(static_cast<uint32_t>(word));
+    inst_words_.push_back(static_cast<uint32_t>(word >> 32));
   }
   return SPV_SUCCESS;
 }
 
+void MarkvEncoder::AddByteBreak(size_t byte_break_if_less_than) {
+  const size_t num_bits_to_next_byte =
+      GetNumBitsToNextByte(writer_.GetNumBits());
+  if (num_bits_to_next_byte == 0 ||
+      num_bits_to_next_byte > byte_break_if_less_than)
+    return;
+
+  if (logger_) {
+    logger_->AppendWhitespaces(kCommentNumWhitespaces);
+    logger_->AppendText("<byte break>");
+  }
+
+  writer_.WriteBits(0, num_bits_to_next_byte);
+}
+
+bool MarkvDecoder::ReadToByteBreak(size_t byte_break_if_less_than) {
+  const size_t num_bits_to_next_byte =
+      GetNumBitsToNextByte(reader_.GetNumReadBits());
+  if (num_bits_to_next_byte == 0 ||
+      num_bits_to_next_byte > byte_break_if_less_than)
+    return true;
+
+
+  uint64_t bits = 0;
+  if (!reader_.ReadBits(&bits, num_bits_to_next_byte))
+    return false;
+
+  assert(bits == 0);
+  if (bits != 0)
+    return false;
+
+  return true;
+}
+
+spv_result_t MarkvEncoder::EncodeInstruction(
+    const spv_parsed_instruction_t& inst) {
+  SpvOp opcode = SpvOp(inst.opcode);
+  inst_ = inst;
+
+  const spv_result_t validation_result = UpdateValidationState(inst);
+  if (validation_result != SPV_SUCCESS)
+    return validation_result;
+
+  LogDisassemblyInstruction();
+
+  const spv_result_t opcode_encodig_result =
+      EncodeOpcodeAndNumOperands(opcode, inst.num_operands);
+  if (opcode_encodig_result < 0)
+    return opcode_encodig_result;
+
+  if (opcode_encodig_result != SPV_SUCCESS) {
+    // Fallback encoding for opcode and num_operands.
+    writer_.WriteVariableWidthU32(opcode, model_->opcode_chunk_length());
+
+    if (!OpcodeHasFixedNumberOfOperands(opcode)) {
+      // If the opcode has a variable number of operands, encode the number of
+      // operands with the instruction.
+
+      if (logger_)
+        logger_->AppendWhitespaces(kCommentNumWhitespaces);
+
+      writer_.WriteVariableWidthU16(inst.num_operands,
+                                    model_->num_operands_chunk_length());
+    }
+  }
+
+  // Write operands.
+  const uint32_t num_operands = inst_.num_operands;
+  for (operand_index_ = 0; operand_index_ < num_operands; ++operand_index_) {
+    operand_ = inst_.operands[operand_index_];
+
+    if (logger_) {
+      logger_->AppendWhitespaces(kCommentNumWhitespaces);
+      logger_->AppendText("<");
+      logger_->AppendText(spvOperandTypeStr(operand_.type));
+      logger_->AppendText(">");
+    }
+
+    switch (operand_.type) {
+      case SPV_OPERAND_TYPE_RESULT_ID:
+      case SPV_OPERAND_TYPE_TYPE_ID:
+      case SPV_OPERAND_TYPE_ID:
+      case SPV_OPERAND_TYPE_OPTIONAL_ID:
+      case SPV_OPERAND_TYPE_SCOPE_ID:
+      case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID: {
+        const uint32_t id = inst_.words[operand_.offset];
+        if (operand_.type == SPV_OPERAND_TYPE_TYPE_ID) {
+          const spv_result_t result = EncodeTypeId();
+          if (result != SPV_SUCCESS)
+            return result;
+        } else if (operand_.type == SPV_OPERAND_TYPE_RESULT_ID) {
+          const spv_result_t result = EncodeResultId();
+          if (result != SPV_SUCCESS)
+            return result;
+        } else {
+          const spv_result_t result = EncodeRefId(id);
+          if (result != SPV_SUCCESS)
+            return result;
+        }
+
+        multi_mtf_.Promote(id);
+        break;
+      }
+
+      case SPV_OPERAND_TYPE_LITERAL_INTEGER: {
+        const spv_result_t result =
+            EncodeNonIdWord(inst_.words[operand_.offset]);
+        if (result != SPV_SUCCESS)
+          return result;
+        break;
+      }
+
+      case SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER: {
+        const spv_result_t result = EncodeLiteralNumber(operand_);
+        if (result != SPV_SUCCESS)
+          return result;
+        break;
+      }
+
+      case SPV_OPERAND_TYPE_LITERAL_STRING: {
+        const char* src = reinterpret_cast<const char*>(
+            &inst_.words[operand_.offset]);
+
+        auto* codec = model_->GetLiteralStringHuffmanCodec(opcode);
+        if (codec) {
+          uint64_t bits = 0;
+          size_t num_bits = 0;
+          const std::string str = src;
+          if (codec->Encode(str, &bits, &num_bits)) {
+            writer_.WriteBits(bits, num_bits);
+            break;
+          } else {
+            bool result = codec->Encode("kMarkvNoneOfTheAbove",
+                                        &bits, &num_bits);
+            (void)result;
+            assert(result);
+            writer_.WriteBits(bits, num_bits);
+          }
+        }
+
+        const size_t length = spv_strnlen_s(src, operand_.num_words * 4);
+        if (length == operand_.num_words * 4)
+          return Diag(SPV_ERROR_INVALID_BINARY)
+              << "Failed to find terminal character of literal string";
+        for (size_t i = 0; i < length + 1; ++i)
+          writer_.WriteUnencoded(src[i]);
+        break;
+      }
+
+      default: {
+        for (int i = 0; i < operand_.num_words; ++i) {
+          const uint32_t word = inst_.words[operand_.offset + i];
+          const spv_result_t result = EncodeNonIdWord(word);
+          if (result != SPV_SUCCESS)
+            return result;
+        }
+        break;
+      }
+    }
+  }
+
+  AddByteBreak(kByteBreakAfterInstIfLessThanUntilNextByte);
+
+  if (logger_) {
+    logger_->NewLine();
+    logger_->NewLine();
+    if (!logger_->DebugInstruction(inst_))
+      return SPV_REQUESTED_TERMINATION;
+  }
+
+  ProcessCurInstruction();
+
+  return SPV_SUCCESS;
+}
+
 spv_result_t MarkvDecoder::DecodeModule(std::vector<uint32_t>* spirv_binary) {
   const bool header_read_success =
       reader_.ReadUnencoded(&header_.magic_number) &&
@@ -898,34 +2301,52 @@
       reader_.ReadUnencoded(&header_.spirv_generator);
 
   if (!header_read_success)
-    return vstate_.diag(SPV_ERROR_INVALID_BINARY)
+    return Diag(SPV_ERROR_INVALID_BINARY)
         << "Unable to read MARK-V header";
 
-  assert(header_.magic_number == kMarkvMagicNumber);
-  assert(header_.markv_length_in_bits > 0);
+  if (header_.markv_length_in_bits == 0)
+    return Diag(SPV_ERROR_INVALID_BINARY)
+        << "Header markv_length_in_bits field is zero";
 
   if (header_.magic_number != kMarkvMagicNumber)
-    return vstate_.diag(SPV_ERROR_INVALID_BINARY)
+    return Diag(SPV_ERROR_INVALID_BINARY)
         << "MARK-V binary has incorrect magic number";
 
   // TODO(atgoo@github.com): Print version strings.
   if (header_.markv_version != GetMarkvVersion())
-    return vstate_.diag(SPV_ERROR_INVALID_BINARY)
+    return Diag(SPV_ERROR_INVALID_BINARY)
         << "MARK-V binary and the codec have different versions";
 
+  const uint32_t model_type = header_.markv_model >> 16;
+  const uint32_t model_version = header_.markv_model & 0xFFFF;
+  if (model_type != model_->model_type())
+    return Diag(SPV_ERROR_INVALID_BINARY)
+        << "MARK-V binary and the codec use different MARK-V models";
+
+  if (model_version != model_->model_version())
+    return Diag(SPV_ERROR_INVALID_BINARY)
+        << "MARK-V binary and the codec use different versions if the same "
+        << "MARK-V model";
+
   spirv_.reserve(header_.markv_length_in_bits / 2); // Heuristic.
   spirv_.resize(5, 0);
   spirv_[0] = kSpirvMagicNumber;
   spirv_[1] = header_.spirv_version;
   spirv_[2] = header_.spirv_generator;
 
+  if (logger_) {
+    reader_.SetCallback([this](const std::string& str){
+      logger_->AppendBitSequence(str);
+    });
+  }
+
   while (reader_.GetNumReadBits() < header_.markv_length_in_bits) {
-    spv_parsed_instruction_t inst = {};
-    const spv_result_t decode_result = DecodeInstruction(&inst);
+    inst_ = {};
+    const spv_result_t decode_result = DecodeInstruction();
     if (decode_result != SPV_SUCCESS)
       return decode_result;
 
-    const spv_result_t validation_result = UpdateValidationState(inst);
+    const spv_result_t validation_result = UpdateValidationState(inst_);
     if (validation_result != SPV_SUCCESS)
       return validation_result;
   }
@@ -933,14 +2354,14 @@
 
   if (reader_.GetNumReadBits() != header_.markv_length_in_bits ||
       !reader_.OnlyZeroesLeft()) {
-    return vstate_.diag(SPV_ERROR_INVALID_BINARY)
+    return Diag(SPV_ERROR_INVALID_BINARY)
         << "MARK-V binary has wrong stated bit length "
         << reader_.GetNumReadBits() << " " << header_.markv_length_in_bits;
   }
 
   // Decoding of the module is finished, validation state should have correct
   // id bound.
-  spirv_[3] = vstate_.getIdBound();
+  spirv_[3] = GetIdBound();
 
   *spirv_binary = std::move(spirv_);
   return SPV_SUCCESS;
@@ -952,65 +2373,43 @@
 // For now it's better to keep the code independent for experimentation
 // purposes.
 spv_result_t MarkvDecoder::DecodeOperand(
-    size_t instruction_offset, size_t operand_offset,
-    spv_parsed_instruction_t* inst, const spv_operand_type_t type,
-    spv_operand_pattern_t* expected_operands,
-    bool read_result_id) {
-  const SpvOp opcode = static_cast<SpvOp>(inst->opcode);
+    size_t operand_offset,
+    const spv_operand_type_t type,
+    spv_operand_pattern_t* expected_operands) {
+  const SpvOp opcode = static_cast<SpvOp>(inst_.opcode);
 
-  spv_parsed_operand_t parsed_operand;
-  memset(&parsed_operand, 0, sizeof(parsed_operand));
+  memset(&operand_, 0, sizeof(operand_));
 
   assert((operand_offset >> 16) == 0);
-  parsed_operand.offset = static_cast<uint16_t>(operand_offset);
-  parsed_operand.type = type;
+  operand_.offset = static_cast<uint16_t>(operand_offset);
+  operand_.type = type;
 
   // Set default values, may be updated later.
-  parsed_operand.number_kind = SPV_NUMBER_NONE;
-  parsed_operand.number_bit_width = 0;
+  operand_.number_kind = SPV_NUMBER_NONE;
+  operand_.number_bit_width = 0;
 
-  const size_t first_word_index = spirv_.size();
+  const size_t first_word_index = inst_words_.size();
 
   switch (type) {
-    case SPV_OPERAND_TYPE_TYPE_ID: {
-      if (!DecodeId(&inst->type_id)) {
-        return vstate_.diag(SPV_ERROR_INVALID_BINARY)
-            << "Failed to read type_id";
-      }
+    case SPV_OPERAND_TYPE_RESULT_ID: {
+      const spv_result_t result = DecodeResultId();
+      if (result != SPV_SUCCESS)
+        return result;
 
-      if (inst->type_id == 0)
-        return vstate_.diag(SPV_ERROR_INVALID_BINARY) << "Decoded type_id is 0";
-
-      spirv_.push_back(inst->type_id);
-      vstate_.setIdBound(std::max(vstate_.getIdBound(), inst->type_id + 1));
+      inst_words_.push_back(inst_.result_id);
+      SetIdBound(std::max(GetIdBound(), inst_.result_id + 1));
+      multi_mtf_.Promote(inst_.result_id);
       break;
     }
 
-    case SPV_OPERAND_TYPE_RESULT_ID: {
-      if (read_result_id) {
-        if (!DecodeId(&inst->result_id))
-          return vstate_.diag(SPV_ERROR_INVALID_BINARY)
-              << "Failed to read result_id";
-      } else {
-        inst->result_id = vstate_.getIdBound();
-        vstate_.setIdBound(inst->result_id + 1);
-        move_to_front_ids_.push_front(inst->result_id);
-      }
+    case SPV_OPERAND_TYPE_TYPE_ID: {
+      const spv_result_t result = DecodeTypeId();
+      if (result != SPV_SUCCESS)
+        return result;
 
-      spirv_.push_back(inst->result_id);
-
-      // Save the result ID to type ID mapping.
-      // In the grammar, type ID always appears before result ID.
-      // A regular value maps to its type. Some instructions (e.g. OpLabel)
-      // have no type Id, and will map to 0. The result Id for a
-      // type-generating instruction (e.g. OpTypeInt) maps to itself.
-      auto insertion_result = id_to_type_id_.emplace(
-          inst->result_id,
-          spvOpcodeGeneratesType(opcode) ? inst->result_id : inst->type_id);
-      if(!insertion_result.second) {
-        return vstate_.diag(SPV_ERROR_INVALID_ID)
-            << "Unexpected behavior: id->type_id pair was already registered";
-      }
+      inst_words_.push_back(inst_.type_id);
+      SetIdBound(std::max(GetIdBound(), inst_.type_id + 1));
+      multi_mtf_.Promote(inst_.type_id);
       break;
     }
 
@@ -1019,47 +2418,51 @@
     case SPV_OPERAND_TYPE_SCOPE_ID:
     case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID: {
       uint32_t id = 0;
-      if (!DecodeId(&id))
-        return vstate_.diag(SPV_ERROR_INVALID_BINARY) << "Failed to read id";
+      const spv_result_t result = DecodeRefId(&id);
+      if (result != SPV_SUCCESS)
+        return result;
 
       if (id == 0)
-        return vstate_.diag(SPV_ERROR_INVALID_BINARY) << "Decoded id is 0";
+        return Diag(SPV_ERROR_INVALID_BINARY) << "Decoded id is 0";
 
-      spirv_.push_back(id);
-      vstate_.setIdBound(std::max(vstate_.getIdBound(), id + 1));
+      if (type == SPV_OPERAND_TYPE_ID ||
+          type == SPV_OPERAND_TYPE_OPTIONAL_ID) {
 
-      if (type == SPV_OPERAND_TYPE_ID || type == SPV_OPERAND_TYPE_OPTIONAL_ID) {
+        operand_.type = SPV_OPERAND_TYPE_ID;
 
-        parsed_operand.type = SPV_OPERAND_TYPE_ID;
-
-        if (opcode == SpvOpExtInst && parsed_operand.offset == 3) {
+        if (opcode == SpvOpExtInst && operand_.offset == 3) {
           // The current word is the extended instruction set id.
-          // Set the extended instruction set type for the current instruction.
+          // Set the extended instruction set type for the current
+          // instruction.
           auto ext_inst_type_iter = import_id_to_ext_inst_type_.find(id);
           if (ext_inst_type_iter == import_id_to_ext_inst_type_.end()) {
-            return vstate_.diag(SPV_ERROR_INVALID_ID)
+            return Diag(SPV_ERROR_INVALID_ID)
                 << "OpExtInst set id " << id
                 << " does not reference an OpExtInstImport result Id";
           }
-          inst->ext_inst_type = ext_inst_type_iter->second;
+          inst_.ext_inst_type = ext_inst_type_iter->second;
         }
       }
+
+      inst_words_.push_back(id);
+      SetIdBound(std::max(GetIdBound(), id + 1));
+      multi_mtf_.Promote(id);
       break;
     }
 
     case SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER: {
       uint32_t word = 0;
-      if (!DecodeOperandWord(type, &word))
-        return vstate_.diag(SPV_ERROR_INVALID_BINARY)
-            << "Failed to read enum";
+      const spv_result_t result = DecodeNonIdWord(&word);
+      if (result != SPV_SUCCESS)
+        return result;
 
-      spirv_.push_back(word);
+      inst_words_.push_back(word);
 
       assert(SpvOpExtInst == opcode);
-      assert(inst->ext_inst_type != SPV_EXT_INST_TYPE_NONE);
+      assert(inst_.ext_inst_type != SPV_EXT_INST_TYPE_NONE);
       spv_ext_inst_desc ext_inst;
-      if (grammar_.lookupExtInst(inst->ext_inst_type, word, &ext_inst))
-        return vstate_.diag(SPV_ERROR_INVALID_BINARY)
+      if (grammar_.lookupExtInst(inst_.ext_inst_type, word, &ext_inst))
+        return Diag(SPV_ERROR_INVALID_BINARY)
             << "Invalid extended instruction number: " << word;
       spvPushOperandTypes(ext_inst->operandTypes, expected_operands);
       break;
@@ -1069,31 +2472,31 @@
     case SPV_OPERAND_TYPE_OPTIONAL_LITERAL_INTEGER: {
       // These are regular single-word literal integer operands.
       // Post-parsing validation should check the range of the parsed value.
-      parsed_operand.type = SPV_OPERAND_TYPE_LITERAL_INTEGER;
+      operand_.type = SPV_OPERAND_TYPE_LITERAL_INTEGER;
       // It turns out they are always unsigned integers!
-      parsed_operand.number_kind = SPV_NUMBER_UNSIGNED_INT;
-      parsed_operand.number_bit_width = 32;
+      operand_.number_kind = SPV_NUMBER_UNSIGNED_INT;
+      operand_.number_bit_width = 32;
 
       uint32_t word = 0;
-      if (!DecodeOperandWord(type, &word))
-        return vstate_.diag(SPV_ERROR_INVALID_BINARY)
-            << "Failed to read literal integer";
+      const spv_result_t result = DecodeNonIdWord(&word);
+      if (result != SPV_SUCCESS)
+        return result;
 
-      spirv_.push_back(word);
+      inst_words_.push_back(word);
       break;
     }
 
     case SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER:
-    case SPV_OPERAND_TYPE_OPTIONAL_TYPED_LITERAL_INTEGER:
-      parsed_operand.type = SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER;
+    case SPV_OPERAND_TYPE_OPTIONAL_TYPED_LITERAL_INTEGER: {
+      operand_.type = SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER;
       if (opcode == SpvOpSwitch) {
         // The literal operands have the same type as the value
         // referenced by the selector Id.
-        const uint32_t selector_id = spirv_.at(instruction_offset + 1);
+        const uint32_t selector_id = inst_words_.at(1);
         const auto type_id_iter = id_to_type_id_.find(selector_id);
         if (type_id_iter == id_to_type_id_.end() ||
             type_id_iter->second == 0) {
-          return vstate_.diag(SPV_ERROR_INVALID_BINARY)
+          return Diag(SPV_ERROR_INVALID_BINARY)
               << "Invalid OpSwitch: selector id " << selector_id
               << " has no type";
         }
@@ -1102,15 +2505,15 @@
         if (selector_id == type_id) {
           // Recall that by convention, a result ID that is a type definition
           // maps to itself.
-          return vstate_.diag(SPV_ERROR_INVALID_BINARY)
+          return Diag(SPV_ERROR_INVALID_BINARY)
               << "Invalid OpSwitch: selector id " << selector_id
               << " is a type, not a value";
         }
-        if (auto error = SetNumericTypeInfoForType(&parsed_operand, type_id))
+        if (auto error = SetNumericTypeInfoForType(&operand_, type_id))
           return error;
-        if (parsed_operand.number_kind != SPV_NUMBER_UNSIGNED_INT &&
-            parsed_operand.number_kind != SPV_NUMBER_SIGNED_INT) {
-          return vstate_.diag(SPV_ERROR_INVALID_BINARY)
+        if (operand_.number_kind != SPV_NUMBER_UNSIGNED_INT &&
+            operand_.number_kind != SPV_NUMBER_SIGNED_INT) {
+          return Diag(SPV_ERROR_INVALID_BINARY)
               << "Invalid OpSwitch: selector id " << selector_id
               << " is not a scalar integer";
         }
@@ -1118,40 +2521,60 @@
         assert(opcode == SpvOpConstant || opcode == SpvOpSpecConstant);
         // The literal number type is determined by the type Id for the
         // constant.
-        assert(inst->type_id);
-        if (auto error =
-            SetNumericTypeInfoForType(&parsed_operand, inst->type_id))
+        assert(inst_.type_id);
+        if (auto error = SetNumericTypeInfoForType(&operand_, inst_.type_id))
           return error;
       }
 
-      if (auto error = DecodeLiteralNumber(parsed_operand))
+      if (auto error = DecodeLiteralNumber(operand_))
         return error;
 
       break;
+    }
 
     case SPV_OPERAND_TYPE_LITERAL_STRING:
     case SPV_OPERAND_TYPE_OPTIONAL_LITERAL_STRING: {
-      parsed_operand.type = SPV_OPERAND_TYPE_LITERAL_STRING;
+      operand_.type = SPV_OPERAND_TYPE_LITERAL_STRING;
       std::vector<char> str;
-      // The loop is expected to terminate once we encounter '\0' or exhaust
-      // the bit stream.
-      while (true) {
-        char ch = 0;
-        if (!reader_.ReadUnencoded(&ch))
-          return vstate_.diag(SPV_ERROR_INVALID_BINARY)
+      auto* codec = model_->GetLiteralStringHuffmanCodec(inst_.opcode);
+
+      if (codec) {
+        std::string decoded_string;
+        const bool huffman_result =
+            codec->DecodeFromStream(GetReadBitCallback(), &decoded_string);
+        assert(huffman_result);
+        if (!huffman_result)
+          return Diag(SPV_ERROR_INVALID_BINARY)
               << "Failed to read literal string";
 
-        str.push_back(ch);
+        if (decoded_string != "kMarkvNoneOfTheAbove") {
+          std::copy(decoded_string.begin(), decoded_string.end(),
+                    std::back_inserter(str));
+          str.push_back('\0');
+        }
+      }
 
-        if (ch == '\0')
-          break;
+      // The loop is expected to terminate once we encounter '\0' or exhaust
+      // the bit stream.
+      if (str.empty()) {
+        while (true) {
+          char ch = 0;
+          if (!reader_.ReadUnencoded(&ch))
+            return Diag(SPV_ERROR_INVALID_BINARY)
+                << "Failed to read literal string";
+
+          str.push_back(ch);
+
+          if (ch == '\0')
+            break;
+        }
       }
 
       while (str.size() % 4 != 0)
         str.push_back('\0');
 
-      spirv_.resize(spirv_.size() + str.size() / 4);
-      std::memcpy(&spirv_[first_word_index], str.data(), str.size());
+      inst_words_.resize(inst_words_.size() + str.size() / 4);
+      std::memcpy(&inst_words_[first_word_index], str.data(), str.size());
 
       if (SpvOpExtInstImport == opcode) {
         // Record the extended instruction type for the ID for this import.
@@ -1160,14 +2583,14 @@
         const spv_ext_inst_type_t ext_inst_type =
             spvExtInstImportTypeGet(str.data());
         if (SPV_EXT_INST_TYPE_NONE == ext_inst_type) {
-          return vstate_.diag(SPV_ERROR_INVALID_BINARY)
+          return Diag(SPV_ERROR_INVALID_BINARY)
               << "Invalid extended instruction import '" << str.data() << "'";
         }
         // We must have parsed a valid result ID.  It's a condition
         // of the grammar, and we only accept non-zero result Ids.
-        assert(inst->result_id);
+        assert(inst_.result_id);
         const bool inserted = import_id_to_ext_inst_type_.emplace(
-            inst->result_id, ext_inst_type).second;
+            inst_.result_id, ext_inst_type).second;
         (void)inserted;
         assert(inserted);
       }
@@ -1197,21 +2620,21 @@
     case SPV_OPERAND_TYPE_KERNEL_PROFILING_INFO: {
       // A single word that is a plain enum value.
       uint32_t word = 0;
-      if (!DecodeOperandWord(type, &word))
-        return vstate_.diag(SPV_ERROR_INVALID_BINARY)
-            << "Failed to read enum";
+      const spv_result_t result = DecodeNonIdWord(&word);
+      if (result != SPV_SUCCESS)
+        return result;
 
-      spirv_.push_back(word);
+      inst_words_.push_back(word);
 
       // Map an optional operand type to its corresponding concrete type.
       if (type == SPV_OPERAND_TYPE_OPTIONAL_ACCESS_QUALIFIER)
-        parsed_operand.type = SPV_OPERAND_TYPE_ACCESS_QUALIFIER;
+        operand_.type = SPV_OPERAND_TYPE_ACCESS_QUALIFIER;
 
       spv_operand_desc entry;
       if (grammar_.lookupOperand(type, word, &entry)) {
-        return vstate_.diag(SPV_ERROR_INVALID_BINARY)
+        return Diag(SPV_ERROR_INVALID_BINARY)
             << "Invalid "
-            << spvOperandTypeStr(parsed_operand.type)
+            << spvOperandTypeStr(operand_.type)
             << " operand: " << word;
       }
 
@@ -1229,18 +2652,17 @@
     case SPV_OPERAND_TYPE_SELECTION_CONTROL: {
       // This operand is a mask.
       uint32_t word = 0;
-      if (!DecodeOperandWord(type, &word))
-        return vstate_.diag(SPV_ERROR_INVALID_BINARY)
-            << "Failed to read " << spvOperandTypeStr(type)
-            << " for " << spvOpcodeString(SpvOp(inst->opcode));
+      const spv_result_t result = DecodeNonIdWord(&word);
+      if (result != SPV_SUCCESS)
+        return result;
 
-      spirv_.push_back(word);
+      inst_words_.push_back(word);
 
       // Map an optional operand type to its corresponding concrete type.
       if (type == SPV_OPERAND_TYPE_OPTIONAL_IMAGE)
-        parsed_operand.type = SPV_OPERAND_TYPE_IMAGE;
+        operand_.type = SPV_OPERAND_TYPE_IMAGE;
       else if (type == SPV_OPERAND_TYPE_OPTIONAL_MEMORY_ACCESS)
-        parsed_operand.type = SPV_OPERAND_TYPE_MEMORY_ACCESS;
+        operand_.type = SPV_OPERAND_TYPE_MEMORY_ACCESS;
 
       // Check validity of set mask bits. Also prepare for operands for those
       // masks if they have any.  To get operand order correct, scan from
@@ -1253,8 +2675,8 @@
         if (remaining_word & mask) {
           spv_operand_desc entry;
           if (grammar_.lookupOperand(type, mask, &entry)) {
-            return vstate_.diag(SPV_ERROR_INVALID_BINARY)
-                   << "Invalid " << spvOperandTypeStr(parsed_operand.type)
+            return Diag(SPV_ERROR_INVALID_BINARY)
+                   << "Invalid " << spvOperandTypeStr(operand_.type)
                    << " operand: " << word << " has invalid mask component "
                    << mask;
           }
@@ -1273,111 +2695,131 @@
       break;
     }
     default:
-      return vstate_.diag(SPV_ERROR_INVALID_BINARY)
+      return Diag(SPV_ERROR_INVALID_BINARY)
           << "Internal error: Unhandled operand type: " << type;
   }
 
-  parsed_operand.num_words = uint16_t(spirv_.size() - first_word_index);
+  operand_.num_words = uint16_t(inst_words_.size() - first_word_index);
 
-  assert(int(SPV_OPERAND_TYPE_FIRST_CONCRETE_TYPE) <= int(parsed_operand.type));
-  assert(int(SPV_OPERAND_TYPE_LAST_CONCRETE_TYPE) >= int(parsed_operand.type));
+  assert(int(SPV_OPERAND_TYPE_FIRST_CONCRETE_TYPE) <= int(operand_.type));
+  assert(int(SPV_OPERAND_TYPE_LAST_CONCRETE_TYPE) >= int(operand_.type));
 
-  parsed_operands_.push_back(parsed_operand);
+  parsed_operands_.push_back(operand_);
 
   return SPV_SUCCESS;
 }
 
-spv_result_t MarkvDecoder::DecodeInstruction(spv_parsed_instruction_t* inst) {
+spv_result_t MarkvDecoder::DecodeInstruction() {
   parsed_operands_.clear();
-  const size_t instruction_offset = spirv_.size();
-
-  bool read_result_id = false;
-
-  while (true) {
-    uint32_t word = 0;
-    if (!reader_.ReadVariableWidthU32(&word,
-                                      model_->opcode_chunk_length())) {
-      return vstate_.diag(SPV_ERROR_INVALID_BINARY)
-          << "Failed to read opcode of instruction";
-    }
-
-    if (word >= kMarkvFirstOpcode) {
-      if (word == kMarkvOpNextInstructionEncodesResultId) {
-        read_result_id = true;
-      } else {
-        return vstate_.diag(SPV_ERROR_INVALID_BINARY)
-            << "Encountered unknown MARK-V opcode";
-      }
-    } else {
-      inst->opcode = static_cast<uint16_t>(word);
-      break;
-    }
-  }
-
-  const SpvOp opcode = static_cast<SpvOp>(inst->opcode);
+  inst_words_.clear();
 
   // Opcode/num_words placeholder, the word will be filled in later.
-  spirv_.push_back(0);
+  inst_words_.push_back(0);
+
+  bool num_operands_still_unknown = true;
+  {
+    uint32_t opcode = 0;
+    uint32_t num_operands = 0;
+
+    const spv_result_t opcode_decoding_result =
+        DecodeOpcodeAndNumberOfOperands(&opcode, &num_operands);
+    if (opcode_decoding_result < 0)
+      return opcode_decoding_result;
+
+    if (opcode_decoding_result == SPV_SUCCESS) {
+      inst_.num_operands = static_cast<uint16_t>(num_operands);
+      num_operands_still_unknown = false;
+    } else {
+      if (!reader_.ReadVariableWidthU32(
+          &opcode, model_->opcode_chunk_length())) {
+        return Diag(SPV_ERROR_INVALID_BINARY)
+            << "Failed to read opcode of instruction";
+      }
+    }
+
+    inst_.opcode = static_cast<uint16_t>(opcode);
+  }
+
+  const SpvOp opcode = static_cast<SpvOp>(inst_.opcode);
 
   spv_opcode_desc opcode_desc;
-  if (grammar_.lookupOpcode(opcode, &opcode_desc)
-      != SPV_SUCCESS) {
-    return vstate_.diag(SPV_ERROR_INVALID_BINARY) << "Invalid opcode";
+  if (grammar_.lookupOpcode(opcode, &opcode_desc) != SPV_SUCCESS) {
+    return Diag(SPV_ERROR_INVALID_BINARY) << "Invalid opcode";
   }
 
   spv_operand_pattern_t expected_operands;
   expected_operands.reserve(opcode_desc->numTypes);
-  for (auto i = 0; i < opcode_desc->numTypes; i++)
-    expected_operands.push_back(opcode_desc->operandTypes[opcode_desc->numTypes - i - 1]);
-
-  if (!OpcodeHasFixedNumberOfOperands(opcode)) {
-    if (!reader_.ReadVariableWidthU16(&inst->num_operands,
-                                      model_->num_operands_chunk_length()))
-      return vstate_.diag(SPV_ERROR_INVALID_BINARY)
-          << "Failed to read num_operands of instruction";
-  } else {
-    inst->num_operands = static_cast<uint16_t>(expected_operands.size());
+  for (auto i = 0; i < opcode_desc->numTypes; i++) {
+    expected_operands.push_back(
+        opcode_desc->operandTypes[opcode_desc->numTypes - i - 1]);
   }
 
-  for (size_t operand_index = 0;
-       operand_index < static_cast<size_t>(inst->num_operands);
-       ++operand_index) {
+  if (num_operands_still_unknown) {
+    if (!OpcodeHasFixedNumberOfOperands(opcode)) {
+      if (!reader_.ReadVariableWidthU16(&inst_.num_operands,
+                                        model_->num_operands_chunk_length()))
+        return Diag(SPV_ERROR_INVALID_BINARY)
+            << "Failed to read num_operands of instruction";
+    } else {
+      inst_.num_operands = static_cast<uint16_t>(expected_operands.size());
+    }
+  }
+
+  for (operand_index_ = 0;
+       operand_index_ < static_cast<size_t>(inst_.num_operands);
+       ++operand_index_) {
     assert(!expected_operands.empty());
     const spv_operand_type_t type =
         spvTakeFirstMatchableOperand(&expected_operands);
 
-    const size_t operand_offset = spirv_.size() - instruction_offset;
+    const size_t operand_offset = inst_words_.size();
 
-    const spv_result_t decode_result =
-        DecodeOperand(instruction_offset, operand_offset, inst, type,
-                      &expected_operands, read_result_id);
+    const spv_result_t decode_result = DecodeOperand(
+        operand_offset, type, &expected_operands);
 
     if (decode_result != SPV_SUCCESS)
       return decode_result;
   }
 
-  assert(inst->num_operands == parsed_operands_.size());
 
-  // Only valid while spirv_ and parsed_operands_ remain unchanged.
-  inst->words = &spirv_[instruction_offset];
-  inst->operands = parsed_operands_.empty() ? nullptr : parsed_operands_.data();
-  inst->num_words = static_cast<uint16_t>(spirv_.size() - instruction_offset);
-  spirv_[instruction_offset] =
-      spvOpcodeMake(inst->num_words, SpvOp(inst->opcode));
+  assert(inst_.num_operands == parsed_operands_.size());
 
-  assert(inst->num_words == std::accumulate(
+  // Only valid while inst_words_ and parsed_operands_ remain unchanged (until
+  // next DecodeInstruction call).
+  inst_.words = inst_words_.data();
+  inst_.operands = parsed_operands_.empty() ? nullptr : parsed_operands_.data();
+  inst_.num_words = static_cast<uint16_t>(inst_words_.size());
+  inst_words_[0] = spvOpcodeMake(inst_.num_words, SpvOp(inst_.opcode));
+
+  std::copy(inst_words_.begin(), inst_words_.end(), std::back_inserter(spirv_));
+
+  assert(inst_.num_words == std::accumulate(
       parsed_operands_.begin(), parsed_operands_.end(), 1,
       [](int num_words, const spv_parsed_operand_t& operand) {
         return num_words += operand.num_words;
   }) && "num_words in instruction doesn't correspond to the sum of num_words"
         "in the operands");
 
-  RecordNumberType(*inst);
+  RecordNumberType();
+  ProcessCurInstruction();
 
-  if (!ReadToByteBreakIfAgreed())
-    return vstate_.diag(SPV_ERROR_INVALID_BINARY)
+  if (!ReadToByteBreak(kByteBreakAfterInstIfLessThanUntilNextByte))
+    return Diag(SPV_ERROR_INVALID_BINARY)
         << "Failed to read to byte break";
 
+  if (logger_) {
+    logger_->NewLine();
+    std::stringstream ss;
+    ss << spvOpcodeString(opcode) << " ";
+    for (size_t index = 1; index < inst_words_.size(); ++index)
+      ss << inst_words_[index] << " ";
+    logger_->AppendText(ss.str());
+    logger_->NewLine();
+    logger_->NewLine();
+    if (!logger_->DebugInstruction(inst_))
+      return SPV_REQUESTED_TERMINATION;
+  }
+
   return SPV_SUCCESS;
 }
 
@@ -1386,14 +2828,14 @@
   assert(type_id != 0);
   auto type_info_iter = type_id_to_number_type_info_.find(type_id);
   if (type_info_iter == type_id_to_number_type_info_.end()) {
-    return vstate_.diag(SPV_ERROR_INVALID_BINARY)
+    return Diag(SPV_ERROR_INVALID_BINARY)
         << "Type Id " << type_id << " is not a type";
   }
 
   const NumberType& info = type_info_iter->second;
   if (info.type == SPV_NUMBER_NONE) {
     // This is a valid type, but for something other than a scalar number.
-    return vstate_.diag(SPV_ERROR_INVALID_BINARY)
+    return Diag(SPV_ERROR_INVALID_BINARY)
         << "Type Id " << type_id << " is not a scalar numeric type";
   }
 
@@ -1404,20 +2846,20 @@
   return SPV_SUCCESS;
 }
 
-void MarkvDecoder::RecordNumberType(const spv_parsed_instruction_t& inst) {
-  const SpvOp opcode = static_cast<SpvOp>(inst.opcode);
+void MarkvDecoder::RecordNumberType() {
+  const SpvOp opcode = static_cast<SpvOp>(inst_.opcode);
   if (spvOpcodeGeneratesType(opcode)) {
     NumberType info = {SPV_NUMBER_NONE, 0};
     if (SpvOpTypeInt == opcode) {
-      info.bit_width = inst.words[inst.operands[1].offset];
-      info.type = inst.words[inst.operands[2].offset] ?
+      info.bit_width = inst_.words[inst_.operands[1].offset];
+      info.type = inst_.words[inst_.operands[2].offset] ?
           SPV_NUMBER_SIGNED_INT : SPV_NUMBER_UNSIGNED_INT;
     } else if (SpvOpTypeFloat == opcode) {
-      info.bit_width = inst.words[inst.operands[1].offset];
+      info.bit_width = inst_.words[inst_.operands[1].offset];
       info.type = SPV_NUMBER_FLOATING;
     }
     // The *result* Id of a type generating instruction is the type Id.
-    type_id_to_number_type_info_[inst.result_id] = info;
+    type_id_to_number_type_info_[inst_.result_id] = info;
   }
 }
 
@@ -1438,46 +2880,45 @@
 
 }  // namespace
 
-spv_result_t spvSpirvToMarkv(spv_const_context context,
-                             const uint32_t* spirv_words,
-                             const size_t spirv_num_words,
-                             spv_const_markv_encoder_options options,
-                             spv_markv_binary* markv_binary,
-                             spv_text* comments, spv_diagnostic* diagnostic) {
+spv_result_t SpirvToMarkv(spv_const_context context,
+                          const std::vector<uint32_t>& spirv,
+                          const MarkvCodecOptions& options,
+                          const MarkvModel& markv_model,
+                          MessageConsumer message_consumer,
+                          MarkvLogConsumer log_consumer,
+                          MarkvDebugConsumer debug_consumer,
+                          std::vector<uint8_t>* markv) {
   spv_context_t hijack_context = *context;
-  if (diagnostic) {
-    *diagnostic = nullptr;
-    libspirv::UseDiagnosticAsMessageConsumer(&hijack_context, diagnostic);
-  }
+  SetContextMessageConsumer(&hijack_context, message_consumer);
 
-  spv_const_binary_t spirv_binary = {spirv_words, spirv_num_words};
+  spv_const_binary_t spirv_binary = {spirv.data(), spirv.size()};
 
   spv_endianness_t endian;
   spv_position_t position = {};
   if (spvBinaryEndianness(&spirv_binary, &endian)) {
-    return libspirv::DiagnosticStream(position, hijack_context.consumer,
+    return DiagnosticStream(position, hijack_context.consumer,
                                       SPV_ERROR_INVALID_BINARY)
         << "Invalid SPIR-V magic number.";
   }
 
   spv_header_t header;
   if (spvBinaryHeaderGet(&spirv_binary, endian, &header)) {
-    return libspirv::DiagnosticStream(position, hijack_context.consumer,
-                                      SPV_ERROR_INVALID_BINARY)
+    return DiagnosticStream(position, hijack_context.consumer,
+                            SPV_ERROR_INVALID_BINARY)
         << "Invalid SPIR-V header.";
   }
 
-  MarkvEncoder encoder(&hijack_context, options);
+  MarkvEncoder encoder(&hijack_context, options, &markv_model);
 
-  if (comments) {
-    encoder.CreateCommentsLogger();
+  if (log_consumer || debug_consumer) {
+    encoder.CreateLogger(log_consumer, debug_consumer);
 
     spv_text text = nullptr;
-    if (spvBinaryToText(&hijack_context, spirv_words, spirv_num_words,
+    if (spvBinaryToText(&hijack_context, spirv.data(), spirv.size(),
                         SPV_BINARY_TO_TEXT_OPTION_NO_HEADER, &text, nullptr)
         != SPV_SUCCESS) {
-      return libspirv::DiagnosticStream(position, hijack_context.consumer,
-                                        SPV_ERROR_INVALID_BINARY)
+      return DiagnosticStream(position, hijack_context.consumer,
+                              SPV_ERROR_INVALID_BINARY)
           << "Failed to disassemble SPIR-V binary.";
     }
     assert(text);
@@ -1486,71 +2927,42 @@
   }
 
   if (spvBinaryParse(
-      &hijack_context, &encoder, spirv_words, spirv_num_words, EncodeHeader,
-      EncodeInstruction, diagnostic) != SPV_SUCCESS) {
-    return libspirv::DiagnosticStream(position, hijack_context.consumer,
-                                      SPV_ERROR_INVALID_BINARY)
+      &hijack_context, &encoder, spirv.data(), spirv.size(), EncodeHeader,
+      EncodeInstruction, nullptr) != SPV_SUCCESS) {
+    return DiagnosticStream(position, hijack_context.consumer,
+                            SPV_ERROR_INVALID_BINARY)
         << "Unable to encode to MARK-V.";
   }
 
-  if (comments)
-    *comments = CreateSpvText(encoder.GetComments());
-
-  *markv_binary = encoder.GetMarkvBinary();
+  *markv = encoder.GetMarkvBinary();
   return SPV_SUCCESS;
 }
 
-spv_result_t spvMarkvToSpirv(spv_const_context context,
-                             const uint8_t* markv_data,
-                             size_t markv_size_bytes,
-                             spv_const_markv_decoder_options options,
-                             spv_binary* spirv_binary,
-                             spv_text* /* comments */, spv_diagnostic* diagnostic) {
+spv_result_t MarkvToSpirv(spv_const_context context,
+                          const std::vector<uint8_t>& markv,
+                          const MarkvCodecOptions& options,
+                          const MarkvModel& markv_model,
+                          MessageConsumer message_consumer,
+                          MarkvLogConsumer log_consumer,
+                          MarkvDebugConsumer debug_consumer,
+                          std::vector<uint32_t>* spirv) {
   spv_position_t position = {};
   spv_context_t hijack_context = *context;
-  if (diagnostic) {
-    *diagnostic = nullptr;
-    libspirv::UseDiagnosticAsMessageConsumer(&hijack_context, diagnostic);
-  }
+  SetContextMessageConsumer(&hijack_context, message_consumer);
 
-  MarkvDecoder decoder(&hijack_context, markv_data, markv_size_bytes, options);
+  MarkvDecoder decoder(&hijack_context, markv, options, &markv_model);
 
-  std::vector<uint32_t> words;
+  if (log_consumer || debug_consumer)
+    decoder.CreateLogger(log_consumer, debug_consumer);
 
-  if (decoder.DecodeModule(&words) != SPV_SUCCESS) {
-    return libspirv::DiagnosticStream(position, hijack_context.consumer,
-                                      SPV_ERROR_INVALID_BINARY)
+  if (decoder.DecodeModule(spirv) != SPV_SUCCESS) {
+    return DiagnosticStream(position, hijack_context.consumer,
+                            SPV_ERROR_INVALID_BINARY)
         << "Unable to decode MARK-V.";
   }
 
-  assert(!words.empty());
-
-  *spirv_binary = new spv_binary_t();
-  (*spirv_binary)->code = new uint32_t[words.size()];
-  (*spirv_binary)->wordCount = words.size();
-  std::memcpy((*spirv_binary)->code, words.data(), 4 * words.size());
-
+  assert(!spirv->empty());
   return SPV_SUCCESS;
 }
 
-void spvMarkvBinaryDestroy(spv_markv_binary binary) {
-  if (!binary) return;
-  delete[] binary->data;
-  delete binary;
-}
-
-spv_markv_encoder_options spvMarkvEncoderOptionsCreate() {
-  return new spv_markv_encoder_options_t;
-}
-
-void spvMarkvEncoderOptionsDestroy(spv_markv_encoder_options options) {
-  delete options;
-}
-
-spv_markv_decoder_options spvMarkvDecoderOptionsCreate() {
-  return new spv_markv_decoder_options_t;
-}
-
-void spvMarkvDecoderOptionsDestroy(spv_markv_decoder_options options) {
-  delete options;
-}
+}  // namespave spvtools
diff --git a/source/comp/markv_model.h b/source/comp/markv_model.h
new file mode 100644
index 0000000..f656df4
--- /dev/null
+++ b/source/comp/markv_model.h
@@ -0,0 +1,176 @@
+// Copyright (c) 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef LIBSPIRV_COMP_MARKV_MODEL_H_
+#define LIBSPIRV_COMP_MARKV_MODEL_H_
+
+#include <map>
+#include <unordered_set>
+#include <vector>
+
+#include "spirv/1.2/spirv.h"
+#include "spirv-tools/libspirv.h"
+#include "util/huffman_codec.h"
+
+namespace spvtools {
+
+// Base class for MARK-V models.
+// The class contains encoding/decoding model with various constants and
+// codecs used by the compression algorithm.
+class MarkvModel {
+ public:
+  MarkvModel() : operand_chunk_lengths_(
+      static_cast<size_t>(SPV_OPERAND_TYPE_NUM_OPERAND_TYPES), 0) {}
+
+  uint32_t model_type() const { return model_type_; }
+  uint32_t model_version() const { return model_version_; }
+
+  uint32_t opcode_chunk_length() const { return opcode_chunk_length_; }
+  uint32_t num_operands_chunk_length() const { return num_operands_chunk_length_; }
+  uint32_t mtf_rank_chunk_length() const { return mtf_rank_chunk_length_; }
+
+  uint32_t u64_chunk_length() const { return u64_chunk_length_; }
+  uint32_t s64_chunk_length() const { return s64_chunk_length_; }
+  uint32_t s64_block_exponent() const { return s64_block_exponent_; }
+
+  // Returns a codec for common opcode_and_num_operands words for the given
+  // previous opcode. May return nullptr if the codec doesn't exist.
+  const spvutils::HuffmanCodec<uint64_t>* GetOpcodeAndNumOperandsMarkovHuffmanCodec(
+      uint32_t prev_opcode) const {
+    if (prev_opcode == SpvOpNop)
+      return opcode_and_num_operands_huffman_codec_.get();
+
+    const auto it =
+        opcode_and_num_operands_markov_huffman_codecs_.find(prev_opcode);
+    if (it == opcode_and_num_operands_markov_huffman_codecs_.end())
+      return nullptr;
+    return it->second.get();
+  }
+
+  // Returns a codec for common non-id words used for given operand slot.
+  // Operand slot is defined by the opcode and the operand index.
+  // May return nullptr if the codec doesn't exist.
+  const spvutils::HuffmanCodec<uint64_t>* GetNonIdWordHuffmanCodec(
+      uint32_t opcode, uint32_t operand_index) const {
+    const auto it = non_id_word_huffman_codecs_.find(
+        std::pair<uint32_t, uint32_t>(opcode, operand_index));
+    if (it == non_id_word_huffman_codecs_.end())
+      return nullptr;
+    return it->second.get();
+  }
+
+  // Returns a codec for common id descriptos used for given operand slot.
+  // Operand slot is defined by the opcode and the operand index.
+  // May return nullptr if the codec doesn't exist.
+  const spvutils::HuffmanCodec<uint64_t>* GetIdDescriptorHuffmanCodec(
+      uint32_t opcode, uint32_t operand_index) const {
+    const auto it = id_descriptor_huffman_codecs_.find(
+        std::pair<uint32_t, uint32_t>(opcode, operand_index));
+    if (it == id_descriptor_huffman_codecs_.end())
+      return nullptr;
+    return it->second.get();
+  }
+
+  // Returns a codec for common strings used by the given opcode.
+  // Operand slot is defined by the opcode and the operand index.
+  // May return nullptr if the codec doesn't exist.
+  const spvutils::HuffmanCodec<std::string>* GetLiteralStringHuffmanCodec(
+      uint32_t opcode) const {
+    const auto it = literal_string_huffman_codecs_.find(opcode);
+    if (it == literal_string_huffman_codecs_.end())
+      return nullptr;
+    return it->second.get();
+  }
+
+  // Checks if |descriptor| has a coding scheme in any of
+  // id_descriptor_huffman_codecs_.
+  bool DescriptorHasCodingScheme(uint32_t descriptor) const {
+    return descriptors_with_coding_scheme_.count(descriptor);
+  }
+
+  // Returns chunk length used for variable length encoding of spirv operand
+  // words.
+  uint32_t GetOperandVariableWidthChunkLength(spv_operand_type_t type) const {
+    return operand_chunk_lengths_.at(static_cast<size_t>(type));
+  }
+
+  // Sets model type.
+  void SetModelType(uint32_t in_model_type) {
+    model_type_ = in_model_type;
+  }
+
+  // Sets model version.
+  void SetModelVersion(uint32_t in_model_version) {
+    model_version_ = in_model_version;
+  }
+
+  // Returns value used by Huffman codecs as a signal that a value is not in the
+  // coding table.
+  static uint64_t GetMarkvNoneOfTheAbove() {
+    // Magic number.
+    return 1111111111111111111;
+  }
+
+  MarkvModel(const MarkvModel&) = delete;
+  const MarkvModel& operator=(const MarkvModel&) = delete;
+
+ protected:
+  // Huffman codec for base-rate of opcode_and_num_operands.
+  std::unique_ptr<spvutils::HuffmanCodec<uint64_t>>
+      opcode_and_num_operands_huffman_codec_;
+
+  // Huffman codecs for opcode_and_num_operands. The map key is previous opcode.
+  std::map<uint32_t, std::unique_ptr<spvutils::HuffmanCodec<uint64_t>>>
+      opcode_and_num_operands_markov_huffman_codecs_;
+
+  // Huffman codecs for non-id single-word operand values.
+  // The map key is pair <opcode, operand_index>.
+  std::map<std::pair<uint32_t, uint32_t>,
+      std::unique_ptr<spvutils::HuffmanCodec<uint64_t>>> non_id_word_huffman_codecs_;
+
+  // Huffman codecs for id descriptors. The map key is pair
+  // <opcode, operand_index>.
+  std::map<std::pair<uint32_t, uint32_t>,
+      std::unique_ptr<spvutils::HuffmanCodec<uint64_t>>> id_descriptor_huffman_codecs_;
+
+  // Set of all descriptors which have a coding scheme in any of
+  // id_descriptor_huffman_codecs_.
+  std::unordered_set<uint32_t> descriptors_with_coding_scheme_;
+
+  // Huffman codecs for literal strings. The map key is the opcode of the
+  // current instruction. This assumes, that there is no more than one literal
+  // string operand per instruction, but would still work even if this is not
+  // the case. Names and debug information strings are not collected.
+  std::map<uint32_t, std::unique_ptr<spvutils::HuffmanCodec<std::string>>>
+      literal_string_huffman_codecs_;
+
+  // Chunk lengths used for variable width encoding of operands (index is
+  // spv_operand_type of the operand).
+  std::vector<uint32_t> operand_chunk_lengths_;
+
+  uint32_t opcode_chunk_length_ = 7;
+  uint32_t num_operands_chunk_length_ =  3;
+  uint32_t mtf_rank_chunk_length_ = 5;
+
+  uint32_t u64_chunk_length_ = 8;
+  uint32_t s64_chunk_length_ = 8;
+  uint32_t s64_block_exponent_ = 10;
+
+  uint32_t model_type_ = 0;
+  uint32_t model_version_ = 0;
+};
+
+}  // namespace spvtools
+
+#endif  // LIBSPIRV_COMP_MARKV_MODEL_H_
diff --git a/source/diagnostic.cpp b/source/diagnostic.cpp
index b887c7a..44339f6 100644
--- a/source/diagnostic.cpp
+++ b/source/diagnostic.cpp
@@ -17,6 +17,7 @@
 #include <cassert>
 #include <cstring>
 #include <iostream>
+#include <sstream>
 
 #include "table.h"
 
@@ -66,6 +67,19 @@
 
 namespace libspirv {
 
+DiagnosticStream::DiagnosticStream(DiagnosticStream&& other)
+    : stream_(),
+      position_(other.position_),
+      consumer_(other.consumer_),
+      error_(other.error_) {
+  // Prevent the other object from emitting output during destruction.
+  other.error_ = SPV_FAILED_MATCH;
+  // Some platforms are missing support for std::ostringstream functionality,
+  // including:  move constructor, swap method.  Either would have been a
+  // better choice than copying the string.
+  stream_ << other.stream_.str();
+}
+
 DiagnosticStream::~DiagnosticStream() {
   if (error_ != SPV_FAILED_MATCH && consumer_ != nullptr) {
     auto level = SPV_MSG_ERROR;
diff --git a/source/diagnostic.h b/source/diagnostic.h
index 78f9a5b..3d06f8d 100644
--- a/source/diagnostic.h
+++ b/source/diagnostic.h
@@ -33,12 +33,14 @@
                    spv_result_t error)
       : position_(position), consumer_(consumer), error_(error) {}
 
-  DiagnosticStream(DiagnosticStream&& other)
-      : stream_(other.stream_.str()),
-        position_(other.position_),
-        consumer_(other.consumer_),
-        error_(other.error_) {}
+  // Creates a DiagnosticStream from an expiring DiagnosticStream.
+  // The new object takes the contents of the other, and prevents the
+  // other from emitting anything during destruction.
+  DiagnosticStream(DiagnosticStream&& other);
 
+  // Destroys a DiagnosticStream.
+  // If its status code is something other than SPV_FAILED_MATCH
+  // then emit the accumulated message to the consumer.
   ~DiagnosticStream();
 
   // Adds the given value to the diagnostic message to be written.
@@ -52,9 +54,9 @@
   operator spv_result_t() { return error_; }
 
  private:
-  std::stringstream stream_;
+  std::ostringstream stream_;
   spv_position_t position_;
-  const spvtools::MessageConsumer& consumer_;  // Message consumer callback.
+  spvtools::MessageConsumer consumer_;  // Message consumer callback.
   spv_result_t error_;
 };
 
diff --git a/source/ext_inst.cpp b/source/ext_inst.cpp
index 12930f1..71f72fc 100644
--- a/source/ext_inst.cpp
+++ b/source/ext_inst.cpp
@@ -23,47 +23,51 @@
 
 #include "macro.h"
 
-static const spv_ext_inst_desc_t glslStd450Entries_1_0[] = {
-#include "glsl.std.450.insts-1.0.inc"
-};
-
-static const spv_ext_inst_desc_t openclEntries_1_0[] = {
-#include "opencl.std.insts-1.0.inc"
-};
-
-static const spv_ext_inst_desc_t spv_amd_shader_explicit_vertex_parameter_entries[] = {
-#include "spv-amd-shader-explicit-vertex-parameter.insts.inc"
-};
-
-static const spv_ext_inst_desc_t spv_amd_shader_trinary_minmax_entries[] = {
-#include "spv-amd-shader-trinary-minmax.insts.inc"
-};
-
-static const spv_ext_inst_desc_t spv_amd_gcn_shader_entries[] = {
-#include "spv-amd-gcn-shader.insts.inc"
-};
-
-static const spv_ext_inst_desc_t spv_amd_shader_ballot_entries[] = {
-#include "spv-amd-shader-ballot.insts.inc"
-};
-
 spv_result_t spvExtInstTableGet(spv_ext_inst_table* pExtInstTable,
                                 spv_target_env env) {
   if (!pExtInstTable) return SPV_ERROR_INVALID_POINTER;
 
+  static const spv_ext_inst_desc_t glslStd450Entries_1_0[] = {
+#include "glsl.std.450.insts-1.0.inc"
+  };
+
+  static const spv_ext_inst_desc_t openclEntries_1_0[] = {
+#include "opencl.std.insts-1.0.inc"
+  };
+
+  static const spv_ext_inst_desc_t
+      spv_amd_shader_explicit_vertex_parameter_entries[] = {
+#include "spv-amd-shader-explicit-vertex-parameter.insts.inc"
+      };
+
+  static const spv_ext_inst_desc_t spv_amd_shader_trinary_minmax_entries[] = {
+#include "spv-amd-shader-trinary-minmax.insts.inc"
+  };
+
+  static const spv_ext_inst_desc_t spv_amd_gcn_shader_entries[] = {
+#include "spv-amd-gcn-shader.insts.inc"
+  };
+
+  static const spv_ext_inst_desc_t spv_amd_shader_ballot_entries[] = {
+#include "spv-amd-shader-ballot.insts.inc"
+  };
+
   static const spv_ext_inst_group_t groups_1_0[] = {
       {SPV_EXT_INST_TYPE_GLSL_STD_450, ARRAY_SIZE(glslStd450Entries_1_0),
        glslStd450Entries_1_0},
       {SPV_EXT_INST_TYPE_OPENCL_STD, ARRAY_SIZE(openclEntries_1_0),
        openclEntries_1_0},
       {SPV_EXT_INST_TYPE_SPV_AMD_SHADER_EXPLICIT_VERTEX_PARAMETER,
-       ARRAY_SIZE(spv_amd_shader_explicit_vertex_parameter_entries), spv_amd_shader_explicit_vertex_parameter_entries},
+       ARRAY_SIZE(spv_amd_shader_explicit_vertex_parameter_entries),
+       spv_amd_shader_explicit_vertex_parameter_entries},
       {SPV_EXT_INST_TYPE_SPV_AMD_SHADER_TRINARY_MINMAX,
-       ARRAY_SIZE(spv_amd_shader_trinary_minmax_entries), spv_amd_shader_trinary_minmax_entries},
+       ARRAY_SIZE(spv_amd_shader_trinary_minmax_entries),
+       spv_amd_shader_trinary_minmax_entries},
       {SPV_EXT_INST_TYPE_SPV_AMD_GCN_SHADER,
        ARRAY_SIZE(spv_amd_gcn_shader_entries), spv_amd_gcn_shader_entries},
       {SPV_EXT_INST_TYPE_SPV_AMD_SHADER_BALLOT,
-       ARRAY_SIZE(spv_amd_shader_ballot_entries), spv_amd_shader_ballot_entries},
+       ARRAY_SIZE(spv_amd_shader_ballot_entries),
+       spv_amd_shader_ballot_entries},
   };
 
   static const spv_ext_inst_table_t table_1_0 = {ARRAY_SIZE(groups_1_0),
diff --git a/source/link/CMakeLists.txt b/source/link/CMakeLists.txt
new file mode 100644
index 0000000..9db3cab
--- /dev/null
+++ b/source/link/CMakeLists.txt
@@ -0,0 +1,35 @@
+# Copyright (c) 2017 Pierre Moreau
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+add_library(SPIRV-Tools-link
+  linker.cpp
+)
+
+spvtools_default_compile_options(SPIRV-Tools-link)
+target_include_directories(SPIRV-Tools-link
+  PUBLIC ${spirv-tools_SOURCE_DIR}/include
+  PUBLIC ${SPIRV_HEADER_INCLUDE_DIR}
+  PRIVATE ${spirv-tools_BINARY_DIR}
+)
+# We need the IR functionnalities from the optimizer
+target_link_libraries(SPIRV-Tools-link
+  PUBLIC SPIRV-Tools-opt)
+
+set_property(TARGET SPIRV-Tools-link PROPERTY FOLDER "SPIRV-Tools libraries")
+
+if(ENABLE_SPIRV_TOOLS_INSTALL)
+  install(TARGETS SPIRV-Tools-link
+    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
+endif(ENABLE_SPIRV_TOOLS_INSTALL)
diff --git a/source/link/linker.cpp b/source/link/linker.cpp
new file mode 100644
index 0000000..51e720a
--- /dev/null
+++ b/source/link/linker.cpp
@@ -0,0 +1,716 @@
+// Copyright (c) 2017 Pierre Moreau
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "spirv-tools/linker.hpp"
+
+#include <cstdio>
+#include <cstring>
+
+#include <algorithm>
+#include <iostream>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include "assembly_grammar.h"
+#include "diagnostic.h"
+#include "opt/build_module.h"
+#include "opt/compact_ids_pass.h"
+#include "opt/decoration_manager.h"
+#include "opt/ir_loader.h"
+#include "opt/make_unique.h"
+#include "opt/pass_manager.h"
+#include "opt/remove_duplicates_pass.h"
+#include "spirv-tools/libspirv.hpp"
+#include "spirv_target_env.h"
+
+namespace spvtools {
+
+using ir::Instruction;
+using ir::Module;
+using ir::Operand;
+using opt::PassManager;
+using opt::RemoveDuplicatesPass;
+using opt::analysis::DecorationManager;
+using opt::analysis::DefUseManager;
+
+// Stores various information about an imported or exported symbol.
+struct LinkageSymbolInfo {
+  SpvId id;          // ID of the symbol
+  SpvId type_id;     // ID of the type of the symbol
+  std::string name;  // unique name defining the symbol and used for matching
+                     // imports and exports together
+  std::vector<SpvId> parameter_ids;  // ID of the parameters of the symbol, if
+                                     // it is a function
+};
+struct LinkageEntry {
+  LinkageSymbolInfo imported_symbol;
+  LinkageSymbolInfo exported_symbol;
+
+  LinkageEntry(const LinkageSymbolInfo& import_info,
+               const LinkageSymbolInfo& export_info)
+      : imported_symbol(import_info), exported_symbol(export_info) {}
+};
+using LinkageTable = std::vector<LinkageEntry>;
+
+// Shifts the IDs used in each binary of |modules| so that they occupy a
+// disjoint range from the other binaries, and compute the new ID bound which
+// is returned in |max_id_bound|.
+//
+// Both |modules| and |max_id_bound| should not be null, and |modules| should
+// not be empty either.
+static spv_result_t ShiftIdsInModules(
+    const MessageConsumer& consumer,
+    std::vector<std::unique_ptr<ir::Module>>* modules, uint32_t* max_id_bound);
+
+// Generates the header for the linked module and returns it in |header|.
+//
+// |header| should not be null, |modules| should not be empty and
+// |max_id_bound| should be strictly greater than 0.
+//
+// TODO(pierremoreau): What to do when binaries use different versions of
+//                     SPIR-V? For now, use the max of all versions found in
+//                     the input modules.
+static spv_result_t GenerateHeader(
+    const MessageConsumer& consumer,
+    const std::vector<std::unique_ptr<ir::Module>>& modules,
+    uint32_t max_id_bound, ir::ModuleHeader* header);
+
+// Merge all the modules from |inModules| into |linked_module|.
+//
+// |linked_module| should not be null.
+static spv_result_t MergeModules(
+    const MessageConsumer& consumer,
+    const std::vector<std::unique_ptr<Module>>& inModules,
+    const libspirv::AssemblyGrammar& grammar, Module* linked_module);
+
+// Compute all pairs of import and export and return it in |linkings_to_do|.
+//
+// |linkings_to_do should not be null. Built-in symbols will be ignored.
+//
+// TODO(pierremoreau): Linkage attributes applied by a group decoration are
+//                     currently not handled. (You could have a group being
+//                     applied to a single ID.)
+// TODO(pierremoreau): What should be the proper behaviour with built-in
+//                     symbols?
+static spv_result_t GetImportExportPairs(const MessageConsumer& consumer,
+                                         const Module& linked_module,
+                                         const DefUseManager& def_use_manager,
+                                         const DecorationManager& decoration_manager,
+                                         LinkageTable* linkings_to_do);
+
+// Checks that for each pair of import and export, the import and export have
+// the same type as well as the same decorations.
+//
+// TODO(pierremoreau): Decorations on functions parameters are currently not
+// checked.
+static spv_result_t CheckImportExportCompatibility(
+    const MessageConsumer& consumer, const LinkageTable& linkings_to_do,
+    const DefUseManager& def_use_manager,
+    const DecorationManager& decoration_manager);
+
+// Remove linkage specific instructions, such as prototypes of imported
+// functions, declarations of imported variables, import (and export if
+// necessary) linkage attribtes.
+//
+// |linked_module| and |decoration_manager| should not be null, and the
+// 'RemoveDuplicatePass' should be run first.
+//
+// TODO(pierremoreau): Linkage attributes applied by a group decoration are
+//                     currently not handled. (You could have a group being
+//                     applied to a single ID.)
+// TODO(pierremoreau): Run a pass for removing dead instructions, for example
+//                     OpName for prototypes of imported funcions.
+static spv_result_t RemoveLinkageSpecificInstructions(
+    const MessageConsumer& consumer, bool create_executable,
+    const LinkageTable& linkings_to_do, DecorationManager* decoration_manager,
+    Module* linked_module);
+
+// Structs for holding the data members for SpvLinker.
+struct Linker::Impl {
+  explicit Impl(spv_target_env env) : context(spvContextCreate(env)) {
+    // The default consumer in spv_context_t is a null consumer, which provides
+    // equivalent functionality (from the user's perspective) as a real consumer
+    // does nothing.
+  }
+  ~Impl() { spvContextDestroy(context); }
+
+  spv_context context;  // C interface context object.
+};
+
+Linker::Linker(spv_target_env env) : impl_(new Impl(env)) {}
+
+Linker::~Linker() {}
+
+void Linker::SetMessageConsumer(MessageConsumer consumer) {
+  SetContextMessageConsumer(impl_->context, std::move(consumer));
+}
+
+spv_result_t Linker::Link(const std::vector<std::vector<uint32_t>>& binaries,
+                          std::vector<uint32_t>& linked_binary,
+                          const LinkerOptions& options) const {
+  std::vector<const uint32_t*> binary_ptrs;
+  binary_ptrs.reserve(binaries.size());
+  std::vector<size_t> binary_sizes;
+  binary_sizes.reserve(binaries.size());
+
+  for (const auto& binary : binaries) {
+    binary_ptrs.push_back(binary.data());
+    binary_sizes.push_back(binary.size());
+  }
+
+  return Link(binary_ptrs.data(), binary_sizes.data(), binaries.size(),
+              linked_binary, options);
+}
+
+spv_result_t Linker::Link(const uint32_t* const* binaries,
+                          const size_t* binary_sizes, size_t num_binaries,
+                          std::vector<uint32_t>& linked_binary,
+                          const LinkerOptions& options) const {
+  spv_position_t position = {};
+  const MessageConsumer& consumer = impl_->context->consumer;
+
+  linked_binary.clear();
+  if (num_binaries == 0u)
+    return libspirv::DiagnosticStream(position, consumer,
+                                      SPV_ERROR_INVALID_BINARY)
+           << "No modules were given.";
+
+  std::vector<std::unique_ptr<Module>> modules;
+  modules.reserve(num_binaries);
+  for (size_t i = 0u; i < num_binaries; ++i) {
+    const uint32_t schema = binaries[i][4u];
+    if (schema != 0u) {
+      position.index = 4u;
+      return libspirv::DiagnosticStream(position, consumer,
+                                        SPV_ERROR_INVALID_BINARY)
+             << "Schema is non-zero for module " << i << ".";
+    }
+
+    std::unique_ptr<Module> module = BuildModule(
+        impl_->context->target_env, consumer, binaries[i], binary_sizes[i]);
+    if (module == nullptr)
+      return libspirv::DiagnosticStream(position, consumer,
+                                        SPV_ERROR_INVALID_BINARY)
+             << "Failed to build a module out of " << modules.size() << ".";
+    modules.push_back(std::move(module));
+  }
+
+  // Phase 1: Shift the IDs used in each binary so that they occupy a disjoint
+  //          range from the other binaries, and compute the new ID bound.
+  uint32_t max_id_bound = 0u;
+  spv_result_t res = ShiftIdsInModules(consumer, &modules, &max_id_bound);
+  if (res != SPV_SUCCESS) return res;
+
+  // Phase 2: Generate the header
+  ir::ModuleHeader header;
+  res = GenerateHeader(consumer, modules, max_id_bound, &header);
+  if (res != SPV_SUCCESS) return res;
+  auto linked_module = MakeUnique<Module>();
+  linked_module->SetHeader(header);
+
+  // Phase 3: Merge all the binaries into a single one.
+  libspirv::AssemblyGrammar grammar(impl_->context);
+  res = MergeModules(consumer, modules, grammar, linked_module.get());
+  if (res != SPV_SUCCESS) return res;
+
+  DefUseManager def_use_manager(consumer, linked_module.get());
+
+  // Phase 4: Find the import/export pairs
+  LinkageTable linkings_to_do;
+  DecorationManager decoration_manager(linked_module.get());
+  res = GetImportExportPairs(consumer, *linked_module, def_use_manager,
+                             decoration_manager, &linkings_to_do);
+  if (res != SPV_SUCCESS) return res;
+
+  // Phase 5: Ensure the import and export have the same types and decorations.
+  res = CheckImportExportCompatibility(consumer, linkings_to_do,
+                                       def_use_manager, decoration_manager);
+  if (res != SPV_SUCCESS) return res;
+
+  // Phase 6: Remove duplicates
+  PassManager manager;
+  manager.SetMessageConsumer(consumer);
+  manager.AddPass<RemoveDuplicatesPass>();
+  opt::Pass::Status pass_res = manager.Run(linked_module.get());
+  if (pass_res == opt::Pass::Status::Failure) return SPV_ERROR_INVALID_DATA;
+
+  // Phase 7: Remove linkage specific instructions, such as import/export
+  // attributes, linkage capability, etc. if applicable
+  res = RemoveLinkageSpecificInstructions(consumer, !options.GetCreateLibrary(),
+                                          linkings_to_do, &decoration_manager,
+                                          linked_module.get());
+  if (res != SPV_SUCCESS) return res;
+
+  // Phase 8: Rematch import variables/functions to export variables/functions
+  // TODO(pierremoreau): Keep the previous DefUseManager up-to-date
+  DefUseManager def_use_manager2(consumer, linked_module.get());
+  for (const auto& linking_entry : linkings_to_do)
+    def_use_manager2.ReplaceAllUsesWith(linking_entry.imported_symbol.id,
+                                        linking_entry.exported_symbol.id);
+
+  // Phase 9: Compact the IDs used in the module
+  manager.AddPass<opt::CompactIdsPass>();
+  pass_res = manager.Run(linked_module.get());
+  if (pass_res == opt::Pass::Status::Failure) return SPV_ERROR_INVALID_DATA;
+
+  // Phase 10: Output the module
+  linked_module->ToBinary(&linked_binary, true);
+
+  return SPV_SUCCESS;
+}
+
+static spv_result_t ShiftIdsInModules(
+    const MessageConsumer& consumer,
+    std::vector<std::unique_ptr<ir::Module>>* modules, uint32_t* max_id_bound) {
+  spv_position_t position = {};
+
+  if (modules == nullptr)
+    return libspirv::DiagnosticStream(position, consumer,
+                                      SPV_ERROR_INVALID_DATA)
+           << "|modules| of ShiftIdsInModules should not be null.";
+  if (modules->empty())
+    return libspirv::DiagnosticStream(position, consumer,
+                                      SPV_ERROR_INVALID_DATA)
+           << "|modules| of ShiftIdsInModules should not be empty.";
+  if (max_id_bound == nullptr)
+    return libspirv::DiagnosticStream(position, consumer,
+                                      SPV_ERROR_INVALID_DATA)
+           << "|max_id_bound| of ShiftIdsInModules should not be null.";
+
+  uint32_t id_bound = modules->front()->IdBound() - 1u;
+  for (auto module_iter = modules->begin() + 1; module_iter != modules->end();
+       ++module_iter) {
+    Module* module = module_iter->get();
+    module->ForEachInst([&id_bound](Instruction* insn) {
+      insn->ForEachId([&id_bound](uint32_t* id) { *id += id_bound; });
+    });
+    id_bound += module->IdBound() - 1u;
+    if (id_bound > 0x3FFFFF)
+      return libspirv::DiagnosticStream(position, consumer,
+                                        SPV_ERROR_INVALID_ID)
+             << "The limit of IDs, 4194303, was exceeded:"
+             << " " << id_bound << " is the current ID bound.";
+  }
+  ++id_bound;
+  if (id_bound > 0x3FFFFF)
+    return libspirv::DiagnosticStream(position, consumer, SPV_ERROR_INVALID_ID)
+           << "The limit of IDs, 4194303, was exceeded:"
+           << " " << id_bound << " is the current ID bound.";
+
+  *max_id_bound = id_bound;
+
+  return SPV_SUCCESS;
+}
+
+static spv_result_t GenerateHeader(
+    const MessageConsumer& consumer,
+    const std::vector<std::unique_ptr<ir::Module>>& modules,
+    uint32_t max_id_bound, ir::ModuleHeader* header) {
+  spv_position_t position = {};
+
+  if (modules.empty())
+    return libspirv::DiagnosticStream(position, consumer,
+                                      SPV_ERROR_INVALID_DATA)
+           << "|modules| of GenerateHeader should not be empty.";
+  if (max_id_bound == 0u)
+    return libspirv::DiagnosticStream(position, consumer,
+                                      SPV_ERROR_INVALID_DATA)
+           << "|max_id_bound| of GenerateHeader should not be null.";
+
+  uint32_t version = 0u;
+  for (const auto& module : modules)
+    version = std::max(version, module->version());
+
+  header->magic_number = SpvMagicNumber;
+  header->version = version;
+  header->generator = 17u;
+  header->bound = max_id_bound;
+  header->reserved = 0u;
+
+  return SPV_SUCCESS;
+}
+
+static spv_result_t MergeModules(
+    const MessageConsumer& consumer,
+    const std::vector<std::unique_ptr<Module>>& input_modules,
+    const libspirv::AssemblyGrammar& grammar, Module* linked_module) {
+  spv_position_t position = {};
+
+  if (linked_module == nullptr)
+    return libspirv::DiagnosticStream(position, consumer,
+                                      SPV_ERROR_INVALID_DATA)
+           << "|linked_module| of MergeModules should not be null.";
+
+  if (input_modules.empty()) return SPV_SUCCESS;
+
+  for (const auto& module : input_modules)
+    for (const auto& inst : module->capabilities())
+      linked_module->AddCapability(MakeUnique<Instruction>(inst));
+
+  for (const auto& module : input_modules)
+    for (const auto& inst : module->extensions())
+      linked_module->AddExtension(MakeUnique<Instruction>(inst));
+
+  for (const auto& module : input_modules)
+    for (const auto& inst : module->ext_inst_imports())
+      linked_module->AddExtInstImport(MakeUnique<Instruction>(inst));
+
+  do {
+    const Instruction* memory_model_inst = input_modules[0]->GetMemoryModel();
+    if (memory_model_inst == nullptr) break;
+
+    uint32_t addressing_model = memory_model_inst->GetSingleWordOperand(0u);
+    uint32_t memory_model = memory_model_inst->GetSingleWordOperand(1u);
+    for (const auto& module : input_modules) {
+      memory_model_inst = module->GetMemoryModel();
+      if (memory_model_inst == nullptr) continue;
+
+      if (addressing_model != memory_model_inst->GetSingleWordOperand(0u)) {
+        spv_operand_desc initial_desc = nullptr, current_desc = nullptr;
+        grammar.lookupOperand(SPV_OPERAND_TYPE_ADDRESSING_MODEL,
+                              addressing_model, &initial_desc);
+        grammar.lookupOperand(SPV_OPERAND_TYPE_ADDRESSING_MODEL,
+                              memory_model_inst->GetSingleWordOperand(0u),
+                              &current_desc);
+        return libspirv::DiagnosticStream(position, consumer,
+                                          SPV_ERROR_INTERNAL)
+               << "Conflicting addressing models: " << initial_desc->name
+               << " vs " << current_desc->name << ".";
+      }
+      if (memory_model != memory_model_inst->GetSingleWordOperand(1u)) {
+        spv_operand_desc initial_desc = nullptr, current_desc = nullptr;
+        grammar.lookupOperand(SPV_OPERAND_TYPE_MEMORY_MODEL, memory_model,
+                              &initial_desc);
+        grammar.lookupOperand(SPV_OPERAND_TYPE_MEMORY_MODEL,
+                              memory_model_inst->GetSingleWordOperand(1u),
+                              &current_desc);
+        return libspirv::DiagnosticStream(position, consumer,
+                                          SPV_ERROR_INTERNAL)
+               << "Conflicting memory models: " << initial_desc->name << " vs "
+               << current_desc->name << ".";
+      }
+    }
+
+    if (memory_model_inst != nullptr)
+      linked_module->SetMemoryModel(
+          MakeUnique<Instruction>(*memory_model_inst));
+  } while (false);
+
+  std::vector<std::pair<uint32_t, const char*>> entry_points;
+  for (const auto& module : input_modules)
+    for (const auto& inst : module->entry_points()) {
+      const uint32_t model = inst.GetSingleWordInOperand(0);
+      const char* const name =
+          reinterpret_cast<const char*>(inst.GetInOperand(2).words.data());
+      const auto i = std::find_if(
+          entry_points.begin(), entry_points.end(),
+          [model, name](const std::pair<uint32_t, const char*>& v) {
+            return v.first == model && strcmp(name, v.second) == 0;
+          });
+      if (i != entry_points.end()) {
+        spv_operand_desc desc = nullptr;
+        grammar.lookupOperand(SPV_OPERAND_TYPE_EXECUTION_MODEL, model, &desc);
+        return libspirv::DiagnosticStream(position, consumer,
+                                          SPV_ERROR_INTERNAL)
+               << "The entry point \"" << name << "\", with execution model "
+               << desc->name << ", was already defined.";
+      }
+      linked_module->AddEntryPoint(MakeUnique<Instruction>(inst));
+      entry_points.emplace_back(model, name);
+    }
+
+  for (const auto& module : input_modules)
+    for (const auto& inst : module->execution_modes())
+      linked_module->AddExecutionMode(MakeUnique<Instruction>(inst));
+
+  for (const auto& module : input_modules)
+    for (const auto& inst : module->debugs1())
+      linked_module->AddDebug1Inst(MakeUnique<Instruction>(inst));
+
+  for (const auto& module : input_modules)
+    for (const auto& inst : module->debugs2())
+      linked_module->AddDebug2Inst(MakeUnique<Instruction>(inst));
+
+  for (const auto& module : input_modules)
+    for (const auto& inst : module->annotations())
+      linked_module->AddAnnotationInst(MakeUnique<Instruction>(inst));
+
+  // TODO(pierremoreau): Since the modules have not been validate, should we
+  //                     expect SpvStorageClassFunction variables outside
+  //                     functions?
+  uint32_t num_global_values = 0u;
+  for (const auto& module : input_modules) {
+    for (const auto& inst : module->types_values()) {
+      linked_module->AddType(MakeUnique<Instruction>(inst));
+      num_global_values += inst.opcode() == SpvOpVariable;
+    }
+  }
+  if (num_global_values > 0xFFFF)
+    return libspirv::DiagnosticStream(position, consumer, SPV_ERROR_INTERNAL)
+           << "The limit of global values, 65535, was exceeded;"
+           << " " << num_global_values << " global values were found.";
+
+  // Process functions and their basic blocks
+  for (const auto& module : input_modules) {
+    for (const auto& func : *module) {
+      std::unique_ptr<ir::Function> cloned_func =
+          MakeUnique<ir::Function>(func);
+      cloned_func->SetParent(linked_module);
+      linked_module->AddFunction(std::move(cloned_func));
+    }
+  }
+
+  return SPV_SUCCESS;
+}
+
+static spv_result_t GetImportExportPairs(const MessageConsumer& consumer,
+                                         const Module& linked_module,
+                                         const DefUseManager& def_use_manager,
+                                         const DecorationManager& decoration_manager,
+                                         LinkageTable* linkings_to_do) {
+  spv_position_t position = {};
+
+  if (linkings_to_do == nullptr)
+    return libspirv::DiagnosticStream(position, consumer,
+                                      SPV_ERROR_INVALID_DATA)
+           << "|linkings_to_do| of GetImportExportPairs should not be empty.";
+
+  std::vector<LinkageSymbolInfo> imports;
+  std::unordered_map<std::string, std::vector<LinkageSymbolInfo>> exports;
+
+  // Figure out the imports and exports
+  for (const auto& decoration : linked_module.annotations()) {
+    if (decoration.opcode() != SpvOpDecorate ||
+        decoration.GetSingleWordInOperand(1u) != SpvDecorationLinkageAttributes)
+      continue;
+
+    const SpvId id = decoration.GetSingleWordInOperand(0u);
+    // Ignore if the targeted symbol is a built-in
+    bool is_built_in = false;
+    for (const auto& id_decoration : decoration_manager.GetDecorationsFor(id, false)) {
+      if (id_decoration->GetSingleWordInOperand(1u) == SpvDecorationBuiltIn) {
+        is_built_in = true;
+        break;
+      }
+    }
+    if (is_built_in)
+      continue;
+
+    const uint32_t type = decoration.GetSingleWordInOperand(3u);
+
+    LinkageSymbolInfo symbol_info;
+    symbol_info.name =
+        reinterpret_cast<const char*>(decoration.GetInOperand(2u).words.data());
+    symbol_info.id = id;
+    symbol_info.type_id = 0u;
+
+    // Retrieve the type of the current symbol. This information will be used
+    // when checking that the imported and exported symbols have the same
+    // types.
+    const Instruction* def_inst = def_use_manager.GetDef(id);
+    if (def_inst == nullptr)
+      return libspirv::DiagnosticStream(position, consumer,
+                                        SPV_ERROR_INVALID_BINARY)
+             << "ID " << id << " is never defined:\n";
+
+    if (def_inst->opcode() == SpvOpVariable) {
+      symbol_info.type_id = def_inst->type_id();
+    } else if (def_inst->opcode() == SpvOpFunction) {
+      symbol_info.type_id = def_inst->GetSingleWordInOperand(1u);
+
+      // range-based for loop calls begin()/end(), but never cbegin()/cend(),
+      // which will not work here.
+      for (auto func_iter = linked_module.cbegin();
+           func_iter != linked_module.cend(); ++func_iter) {
+        if (func_iter->result_id() != id) continue;
+        func_iter->ForEachParam([&symbol_info](const Instruction* inst) {
+          symbol_info.parameter_ids.push_back(inst->result_id());
+        });
+      }
+    } else {
+      return libspirv::DiagnosticStream(position, consumer,
+                                        SPV_ERROR_INVALID_BINARY)
+             << "Only global variables and functions can be decorated using"
+             << " LinkageAttributes; " << id << " is neither of them.\n";
+    }
+
+    if (type == SpvLinkageTypeImport)
+      imports.push_back(symbol_info);
+    else if (type == SpvLinkageTypeExport)
+      exports[symbol_info.name].push_back(symbol_info);
+  }
+
+  // Find the import/export pairs
+  for (const auto& import : imports) {
+    std::vector<LinkageSymbolInfo> possible_exports;
+    const auto& exp = exports.find(import.name);
+    if (exp != exports.end()) possible_exports = exp->second;
+    if (possible_exports.empty())
+      return libspirv::DiagnosticStream(position, consumer,
+                                        SPV_ERROR_INVALID_BINARY)
+             << "No export linkage was found for \"" << import.name << "\".";
+    else if (possible_exports.size() > 1u)
+      return libspirv::DiagnosticStream(position, consumer,
+                                        SPV_ERROR_INVALID_BINARY)
+             << "Too many export linkages, " << possible_exports.size()
+             << ", were found for \"" << import.name << "\".";
+
+    linkings_to_do->emplace_back(import, possible_exports.front());
+  }
+
+  return SPV_SUCCESS;
+}
+
+static spv_result_t CheckImportExportCompatibility(
+    const MessageConsumer& consumer, const LinkageTable& linkings_to_do,
+    const DefUseManager& def_use_manager,
+    const DecorationManager& decoration_manager) {
+  spv_position_t position = {};
+
+  // Ensure th import and export types are the same.
+  for (const auto& linking_entry : linkings_to_do) {
+    if (!RemoveDuplicatesPass::AreTypesEqual(
+            *def_use_manager.GetDef(linking_entry.imported_symbol.type_id),
+            *def_use_manager.GetDef(linking_entry.exported_symbol.type_id),
+            def_use_manager, decoration_manager))
+      return libspirv::DiagnosticStream(position, consumer,
+                                        SPV_ERROR_INVALID_BINARY)
+             << "Type mismatch between imported variable/function %"
+             << linking_entry.imported_symbol.id
+             << " and exported variable/function %"
+             << linking_entry.exported_symbol.id << ".";
+  }
+
+  // Ensure the import and export decorations are similar
+  for (const auto& linking_entry : linkings_to_do) {
+    if (!decoration_manager.HaveTheSameDecorations(
+            linking_entry.imported_symbol.id, linking_entry.exported_symbol.id))
+      return libspirv::DiagnosticStream(position, consumer,
+                                        SPV_ERROR_INVALID_BINARY)
+             << "Decorations mismatch between imported variable/function %"
+             << linking_entry.imported_symbol.id
+             << " and exported variable/function %"
+             << linking_entry.exported_symbol.id << ".";
+    // TODO(pierremoreau): Decorations on function parameters should probably
+    //                     match, except for FuncParamAttr if I understand the
+    //                     spec correctly, which makes the code more
+    //                     complicated.
+    //    for (uint32_t i = 0u; i <
+    //    linking_entry.imported_symbol.parameter_ids.size(); ++i)
+    //      if
+    //      (!decoration_manager.HaveTheSameDecorations(linking_entry.imported_symbol.parameter_ids[i],
+    //      linking_entry.exported_symbol.parameter_ids[i]))
+    //          return libspirv::DiagnosticStream(position,
+    //          impl_->context->consumer,
+    //                                            SPV_ERROR_INVALID_BINARY)
+    //                 << "Decorations mismatch between imported function %" <<
+    //                 linking_entry.imported_symbol.id << "'s"
+    //                 << " and exported function %" <<
+    //                 linking_entry.exported_symbol.id << "'s " << (i + 1u) <<
+    //                 "th parameter.";
+  }
+
+  return SPV_SUCCESS;
+}
+
+static spv_result_t RemoveLinkageSpecificInstructions(
+    const MessageConsumer& consumer, bool create_executable,
+    const LinkageTable& linkings_to_do, DecorationManager* decoration_manager,
+    Module* linked_module) {
+  spv_position_t position = {};
+
+  if (decoration_manager == nullptr)
+    return libspirv::DiagnosticStream(position, consumer,
+                                      SPV_ERROR_INVALID_DATA)
+           << "|decoration_manager| of RemoveLinkageSpecificInstructions "
+              "should "
+              "not "
+              "be empty.";
+  if (linked_module == nullptr)
+    return libspirv::DiagnosticStream(position, consumer,
+                                      SPV_ERROR_INVALID_DATA)
+           << "|linked_module| of RemoveLinkageSpecificInstructions should not "
+              "be empty.";
+
+  // Remove FuncParamAttr decorations of imported functions' parameters.
+  // From the SPIR-V specification, Sec. 2.13:
+  //   When resolving imported functions, the Function Control and all Function
+  //   Parameter Attributes are taken from the function definition, and not
+  //   from the function declaration.
+  for (const auto& linking_entry : linkings_to_do) {
+    for (const auto parameter_id :
+         linking_entry.imported_symbol.parameter_ids) {
+      for (ir::Instruction* decoration :
+           decoration_manager->GetDecorationsFor(parameter_id, false)) {
+        switch (decoration->opcode()) {
+          case SpvOpDecorate:
+          case SpvOpMemberDecorate:
+            if (decoration->GetSingleWordInOperand(1u) ==
+                SpvDecorationFuncParamAttr)
+              decoration->ToNop();
+            break;
+          default:
+            break;
+        }
+      }
+    }
+  }
+
+  // Remove prototypes of imported functions
+  for (const auto& linking_entry : linkings_to_do) {
+    for (auto func_iter = linked_module->begin();
+         func_iter != linked_module->end();) {
+      if (func_iter->result_id() == linking_entry.imported_symbol.id)
+        func_iter = func_iter.Erase();
+      else
+        ++func_iter;
+    }
+  }
+
+  // Remove declarations of imported variables
+  for (const auto& linking_entry : linkings_to_do) {
+    for (auto& inst : linked_module->types_values())
+      if (inst.result_id() == linking_entry.imported_symbol.id) inst.ToNop();
+  }
+
+  // Remove import linkage attributes
+  for (auto& inst : linked_module->annotations())
+    if (inst.opcode() == SpvOpDecorate &&
+        inst.GetSingleWordOperand(1u) == SpvDecorationLinkageAttributes &&
+        inst.GetSingleWordOperand(3u) == SpvLinkageTypeImport)
+      inst.ToNop();
+
+  // Remove export linkage attributes and Linkage capability if making an
+  // executable
+  if (create_executable) {
+    for (auto& inst : linked_module->annotations())
+      if (inst.opcode() == SpvOpDecorate &&
+          inst.GetSingleWordOperand(1u) == SpvDecorationLinkageAttributes &&
+          inst.GetSingleWordOperand(3u) == SpvLinkageTypeExport)
+        inst.ToNop();
+
+    for (auto& inst : linked_module->capabilities())
+      if (inst.GetSingleWordInOperand(0u) == SpvCapabilityLinkage) {
+        inst.ToNop();
+        // The RemoveDuplicatesPass did remove duplicated capabilities, so we
+        // now there aren’t more SpvCapabilityLinkage further down.
+        break;
+      }
+  }
+
+  return SPV_SUCCESS;
+}
+
+}  // namespace spvtools
diff --git a/source/opcode.cpp b/source/opcode.cpp
index c13e4ef..9bb8cb7 100644
--- a/source/opcode.cpp
+++ b/source/opcode.cpp
@@ -27,25 +27,25 @@
 #include "spirv_endian.h"
 
 namespace {
+struct OpcodeDescPtrLen {
+  const spv_opcode_desc_t* ptr;
+  uint32_t len;
+};
 
-// Descriptions of each opcode.  Each entry describes the format of the
-// instruction that follows a particular opcode.
-const spv_opcode_desc_t opcodeTableEntries_1_0[] = {
-#include "core.insts-1.0.inc"
-};
-const spv_opcode_desc_t opcodeTableEntries_1_1[] = {
-#include "core.insts-1.1.inc"
-};
-const spv_opcode_desc_t opcodeTableEntries_1_2[] = {
+OpcodeDescPtrLen getOpcodeTableEntries_1_2() {
+  static const spv_opcode_desc_t opcodeTableEntries_1_2[] = {
 #include "core.insts-1.2.inc"
-};
+  };
+
+  return {opcodeTableEntries_1_2, ARRAY_SIZE(opcodeTableEntries_1_2)};
+}
 
 // Represents a vendor tool entry in the SPIR-V XML Regsitry.
 struct VendorTool {
   uint32_t value;
   const char* vendor;
-  const char* tool; // Might be empty string.
-  const char* vendor_tool; // Combiantion of vendor and tool.
+  const char* tool;         // Might be empty string.
+  const char* vendor_tool;  // Combiantion of vendor and tool.
 };
 
 const VendorTool vendor_tools[] = {
@@ -82,12 +82,22 @@
                                spv_target_env env) {
   if (!pInstTable) return SPV_ERROR_INVALID_POINTER;
 
+  // Descriptions of each opcode.  Each entry describes the format of the
+  // instruction that follows a particular opcode.
+  static const spv_opcode_desc_t opcodeTableEntries_1_0[] = {
+#include "core.insts-1.0.inc"
+  };
+  static const spv_opcode_desc_t opcodeTableEntries_1_1[] = {
+#include "core.insts-1.1.inc"
+  };
+
+  const auto ptr_len = getOpcodeTableEntries_1_2();
+
   static const spv_opcode_table_t table_1_0 = {
       ARRAY_SIZE(opcodeTableEntries_1_0), opcodeTableEntries_1_0};
   static const spv_opcode_table_t table_1_1 = {
       ARRAY_SIZE(opcodeTableEntries_1_1), opcodeTableEntries_1_1};
-  static const spv_opcode_table_t table_1_2 = {
-      ARRAY_SIZE(opcodeTableEntries_1_2), opcodeTableEntries_1_2};
+  static const spv_opcode_table_t table_1_2 = {ptr_len.len, ptr_len.ptr};
 
   switch (env) {
     case SPV_ENV_UNIVERSAL_1_0:
@@ -173,10 +183,12 @@
 const char* spvOpcodeString(const SpvOp opcode) {
   // Use the latest SPIR-V version, which should be backward-compatible with all
   // previous ones.
-  for (uint32_t i = 0; i < ARRAY_SIZE(opcodeTableEntries_1_2); ++i) {
-    if (opcodeTableEntries_1_2[i].opcode == opcode)
-      return opcodeTableEntries_1_2[i].name;
+  const auto entries = getOpcodeTableEntries_1_2();
+
+  for (uint32_t i = 0; i < entries.len; ++i) {
+    if (entries.ptr[i].opcode == opcode) return entries.ptr[i].name;
   }
+
   assert(0 && "Unreachable!");
   return "unknown";
 }
diff --git a/source/operand.cpp b/source/operand.cpp
index 29da94d..3176eb7 100644
--- a/source/operand.cpp
+++ b/source/operand.cpp
@@ -20,30 +20,23 @@
 
 #include "macro.h"
 
-// Pull in operand info tables automatically generated from JSON grammar.
-namespace v1_0 {
-#include "operand.kinds-1.0.inc"
-}  // namespace v1_0
-namespace v1_1 {
-#include "operand.kinds-1.1.inc"
-}  // namespace v1_1
-namespace v1_2 {
-#include "operand.kinds-1.2.inc"
-}  // namespace v1_2
-
 spv_result_t spvOperandTableGet(spv_operand_table* pOperandTable,
                                 spv_target_env env) {
   if (!pOperandTable) return SPV_ERROR_INVALID_POINTER;
 
+#include "operand.kinds-1.0.inc"
+#include "operand.kinds-1.1.inc"
+#include "operand.kinds-1.2.inc"
+
   static const spv_operand_table_t table_1_0 = {
-      ARRAY_SIZE(v1_0::pygen_variable_OperandInfoTable),
-      v1_0::pygen_variable_OperandInfoTable};
+      ARRAY_SIZE(pygen_variable_OperandInfoTable_1_0),
+      pygen_variable_OperandInfoTable_1_0};
   static const spv_operand_table_t table_1_1 = {
-      ARRAY_SIZE(v1_1::pygen_variable_OperandInfoTable),
-      v1_1::pygen_variable_OperandInfoTable};
+      ARRAY_SIZE(pygen_variable_OperandInfoTable_1_1),
+      pygen_variable_OperandInfoTable_1_1};
   static const spv_operand_table_t table_1_2 = {
-      ARRAY_SIZE(v1_2::pygen_variable_OperandInfoTable),
-      v1_2::pygen_variable_OperandInfoTable};
+      ARRAY_SIZE(pygen_variable_OperandInfoTable_1_2),
+      pygen_variable_OperandInfoTable_1_2};
 
   switch (env) {
     case SPV_ENV_UNIVERSAL_1_0:
@@ -225,7 +218,7 @@
   for (endTypes = types; *endTypes != SPV_OPERAND_TYPE_NONE; ++endTypes)
     ;
   while (endTypes-- != types) {
-      pattern->push_back(*endTypes);
+    pattern->push_back(*endTypes);
   }
 }
 
@@ -235,7 +228,8 @@
                                 spv_operand_pattern_t* pattern) {
   // Scan from highest bits to lowest bits because we will append in LIFO
   // fashion, and we need the operands for lower order bits to be consumed first
-  for (uint32_t candidate_bit = (1u << 31u); candidate_bit; candidate_bit >>= 1) {
+  for (uint32_t candidate_bit = (1u << 31u); candidate_bit;
+       candidate_bit >>= 1) {
     if (candidate_bit & mask) {
       spv_operand_desc entry = nullptr;
       if (SPV_SUCCESS == spvOperandTableValueLookup(operandTable, type,
@@ -304,16 +298,17 @@
 
 spv_operand_pattern_t spvAlternatePatternFollowingImmediate(
     const spv_operand_pattern_t& pattern) {
-
-  auto it = std::find(pattern.crbegin(), pattern.crend(), SPV_OPERAND_TYPE_RESULT_ID);
+  auto it =
+      std::find(pattern.crbegin(), pattern.crend(), SPV_OPERAND_TYPE_RESULT_ID);
   if (it != pattern.crend()) {
-    spv_operand_pattern_t alternatePattern(it - pattern.crbegin() + 2, SPV_OPERAND_TYPE_OPTIONAL_CIV);
+    spv_operand_pattern_t alternatePattern(it - pattern.crbegin() + 2,
+                                           SPV_OPERAND_TYPE_OPTIONAL_CIV);
     alternatePattern[1] = SPV_OPERAND_TYPE_RESULT_ID;
     return alternatePattern;
   }
 
   // No result-id found, so just expect CIVs.
-  return{ SPV_OPERAND_TYPE_OPTIONAL_CIV };
+  return {SPV_OPERAND_TYPE_OPTIONAL_CIV};
 }
 
 bool spvIsIdType(spv_operand_type_t type) {
@@ -328,3 +323,61 @@
       return false;
   }
 }
+
+std::function<bool(unsigned)> spvOperandCanBeForwardDeclaredFunction(
+    SpvOp opcode) {
+  std::function<bool(unsigned index)> out;
+  switch (opcode) {
+    case SpvOpExecutionMode:
+    case SpvOpEntryPoint:
+    case SpvOpName:
+    case SpvOpMemberName:
+    case SpvOpSelectionMerge:
+    case SpvOpDecorate:
+    case SpvOpMemberDecorate:
+    case SpvOpTypeStruct:
+    case SpvOpBranch:
+    case SpvOpLoopMerge:
+      out = [](unsigned) { return true; };
+      break;
+    case SpvOpGroupDecorate:
+    case SpvOpGroupMemberDecorate:
+    case SpvOpBranchConditional:
+    case SpvOpSwitch:
+      out = [](unsigned index) { return index != 0; };
+      break;
+
+    case SpvOpFunctionCall:
+      // The Function parameter.
+      out = [](unsigned index) { return index == 2; };
+      break;
+
+    case SpvOpPhi:
+      out = [](unsigned index) { return index > 1; };
+      break;
+
+    case SpvOpEnqueueKernel:
+      // The Invoke parameter.
+      out = [](unsigned index) { return index == 8; };
+      break;
+
+    case SpvOpGetKernelNDrangeSubGroupCount:
+    case SpvOpGetKernelNDrangeMaxSubGroupSize:
+      // The Invoke parameter.
+      out = [](unsigned index) { return index == 3; };
+      break;
+
+    case SpvOpGetKernelWorkGroupSize:
+    case SpvOpGetKernelPreferredWorkGroupSizeMultiple:
+      // The Invoke parameter.
+      out = [](unsigned index) { return index == 2; };
+      break;
+    case SpvOpTypeForwardPointer:
+      out = [](unsigned index) { return index == 0; };
+      break;
+    default:
+      out = [](unsigned) { return false; };
+      break;
+  }
+  return out;
+}
diff --git a/source/operand.h b/source/operand.h
index fa7c6f2..4209993 100644
--- a/source/operand.h
+++ b/source/operand.h
@@ -16,6 +16,7 @@
 #define LIBSPIRV_OPERAND_H_
 
 #include <deque>
+#include <functional>
 
 #include "spirv-tools/libspirv.h"
 #include "table.h"
@@ -124,4 +125,11 @@
 // Is the operand an ID?
 bool spvIsIdType(spv_operand_type_t type);
 
+// Takes the opcode of an instruction and returns
+// a function object that will return true if the index
+// of the operand can be forward declared. This function will
+// used in the SSA validation stage of the pipeline
+std::function<bool(unsigned)> spvOperandCanBeForwardDeclaredFunction(
+    SpvOp opcode);
+
 #endif  // LIBSPIRV_OPERAND_H_
diff --git a/source/opt/CMakeLists.txt b/source/opt/CMakeLists.txt
index 09b0469..99fe747 100644
--- a/source/opt/CMakeLists.txt
+++ b/source/opt/CMakeLists.txt
@@ -16,14 +16,18 @@
   basic_block.h
   block_merge_pass.h
   build_module.h
+  cfg_cleanup_pass.h
   common_uniform_elim_pass.h
   compact_ids_pass.h
   constants.h
   dead_branch_elim_pass.h
+  dead_variable_elimination.h
+  decoration_manager.h
   def_use_manager.h
   eliminate_dead_constant_pass.h
   flatten_decoration_pass.h
   function.h
+  fold.h
   fold_spec_constant_op_and_composite_pass.h
   freeze_spec_constant_value_pass.h
   inline_pass.h
@@ -44,7 +48,10 @@
   pass.h
   passes.h
   pass_manager.h
+  eliminate_dead_functions_pass.h
+  remove_duplicates_pass.h
   set_spec_constant_default_value_pass.h
+  strength_reduction_pass.h
   strip_debug_info_pass.h
   types.h
   type_manager.h
@@ -54,15 +61,19 @@
   basic_block.cpp
   block_merge_pass.cpp
   build_module.cpp
+  cfg_cleanup_pass.cpp
   common_uniform_elim_pass.cpp
   compact_ids_pass.cpp
+  decoration_manager.cpp
   def_use_manager.cpp
   dead_branch_elim_pass.cpp
+  dead_variable_elimination.cpp
   eliminate_dead_constant_pass.cpp
   flatten_decoration_pass.cpp
-  function.cpp
+  fold.cpp
   fold_spec_constant_op_and_composite_pass.cpp
   freeze_spec_constant_value_pass.cpp
+  function.cpp
   inline_pass.cpp
   inline_exhaustive_pass.cpp
   inline_opaque_pass.cpp
@@ -74,16 +85,19 @@
   local_single_store_elim_pass.cpp
   local_ssa_elim_pass.cpp
   module.cpp
+  eliminate_dead_functions_pass.cpp
+  remove_duplicates_pass.cpp
   set_spec_constant_default_value_pass.cpp
   optimizer.cpp
   mem_pass.cpp
   pass.cpp
   pass_manager.cpp
+  strength_reduction_pass.cpp
   strip_debug_info_pass.cpp
   types.cpp
   type_manager.cpp
   unify_const_pass.cpp
-)
+  instruction_list.cpp)
 
 spvtools_default_compile_options(SPIRV-Tools-opt)
 target_include_directories(SPIRV-Tools-opt
diff --git a/source/opt/aggressive_dead_code_elim_pass.cpp b/source/opt/aggressive_dead_code_elim_pass.cpp
index dcb5a26..355865f 100644
--- a/source/opt/aggressive_dead_code_elim_pass.cpp
+++ b/source/opt/aggressive_dead_code_elim_pass.cpp
@@ -27,20 +27,27 @@
 const uint32_t kTypePointerStorageClassInIdx = 0;
 const uint32_t kExtInstSetIdInIndx = 0;
 const uint32_t kExtInstInstructionInIndx = 1;
+const uint32_t kEntryPointFunctionIdInIdx = 1;
 
 }  // namespace anonymous
 
-bool AggressiveDCEPass::IsLocalVar(uint32_t varId) {
+bool AggressiveDCEPass::IsVarOfStorage(uint32_t varId, 
+      uint32_t storageClass) {
   const ir::Instruction* varInst = def_use_mgr_->GetDef(varId);
   const SpvOp op = varInst->opcode();
-  if (op != SpvOpVariable && op != SpvOpFunctionParameter) 
+  if (op != SpvOpVariable) 
     return false;
   const uint32_t varTypeId = varInst->type_id();
   const ir::Instruction* varTypeInst = def_use_mgr_->GetDef(varTypeId);
   if (varTypeInst->opcode() != SpvOpTypePointer)
     return false;
   return varTypeInst->GetSingleWordInOperand(kTypePointerStorageClassInIdx) ==
-      SpvStorageClassFunction;
+      storageClass;
+}
+
+bool AggressiveDCEPass::IsLocalVar(uint32_t varId) {
+ return IsVarOfStorage(varId, SpvStorageClassFunction) ||
+        (IsVarOfStorage(varId, SpvStorageClassPrivate) && private_like_local_);
 }
 
 void AggressiveDCEPass::AddStores(uint32_t ptrId) {
@@ -121,6 +128,9 @@
   // Add all control flow and instructions with external side effects 
   // to worklist
   // TODO(greg-lunarg): Handle Frexp, Modf more optimally
+  call_in_func_ = false;
+  func_is_entry_point_ = false;
+  private_stores_.clear();
   for (auto& blk : *func) {
     for (auto& inst : blk) {
       uint32_t op = inst.opcode();
@@ -128,10 +138,13 @@
         case SpvOpStore: {
           uint32_t varId;
           (void) GetPtr(&inst, &varId);
-          // non-function-scope stores
-          if (!IsLocalVar(varId)) {
+          // Mark stores as live if their variable is not function scope
+          // and is not private scope. Remember private stores for possible
+          // later inclusion
+          if (IsVarOfStorage(varId, SpvStorageClassPrivate))
+            private_stores_.push_back(&inst);
+          else if (!IsVarOfStorage(varId, SpvStorageClassFunction))
             worklist_.push(&inst);
-          }
         } break;
         case SpvOpExtInst: {
           // eg. GLSL frexp, modf
@@ -144,10 +157,28 @@
           // TODO(greg-lunarg): function calls live only if write to non-local
           if (!IsCombinator(op))
             worklist_.push(&inst);
+          // Remember function calls
+          if (op == SpvOpFunctionCall)
+            call_in_func_ = true;
         } break;
       }
     }
   }
+  // See if current function is an entry point
+  for (auto& ei : module_->entry_points()) {
+    if (ei.GetSingleWordInOperand(kEntryPointFunctionIdInIdx) ==
+        func->result_id()) {
+      func_is_entry_point_ = true;
+      break;
+    }
+  }
+  // If the current function is an entry point and has no function calls,
+  // we can optimize private variables as locals
+  private_like_local_ = func_is_entry_point_ && !call_in_func_;
+  // If privates are not like local, add their stores to worklist
+  if (!private_like_local_)
+    for (auto& ps : private_stores_)
+      worklist_.push(ps);
   // Add OpGroupDecorates to worklist because they are a pain to remove
   // ids from.
   // TODO(greg-lunarg): Handle dead ids in OpGroupDecorate
@@ -198,7 +229,7 @@
   // Remove debug and annotation statements referencing dead instructions.
   // This must be done before killing the instructions, otherwise there are
   // dead objects in the def/use database.
-  for (auto& di : module_->debugs()) {
+  for (auto& di : module_->debugs2()) {
     if (di.opcode() != SpvOpName)
       continue;
     if (KillInstIfTargetDead(&di))
diff --git a/source/opt/aggressive_dead_code_elim_pass.h b/source/opt/aggressive_dead_code_elim_pass.h
index 94eb049..5495c36 100644
--- a/source/opt/aggressive_dead_code_elim_pass.h
+++ b/source/opt/aggressive_dead_code_elim_pass.h
@@ -42,18 +42,22 @@
      std::function<std::vector<ir::BasicBlock*>*(const ir::BasicBlock*)>;
 
   AggressiveDCEPass();
-  const char* name() const override { return "aggressive-dce"; }
+  const char* name() const override { return "eliminate-dead-code-aggressive"; }
   Status Process(ir::Module*) override;
 
  private:
+  // Return true if |varId| is variable of |storageClass|.
+  bool IsVarOfStorage(uint32_t varId, uint32_t storageClass);
+
+  // Return true if |varId| is variable of function storage class or is
+  // private variable and privates can be optimized like locals (see
+  // privates_like_local_)
+  bool IsLocalVar(uint32_t varId);
+
   // Add all store instruction which use |ptrId|, directly or indirectly,
   // to the live instruction worklist.
   void AddStores(uint32_t ptrId);
 
-  // Return true if object with |varId| is function scope variable or
-  // function parameter with pointer type.
-  bool IsLocalVar(uint32_t varId);
-
   // Initialize combinator data structures
   void InitCombinatorSets();
 
@@ -94,6 +98,15 @@
   void Initialize(ir::Module* module);
   Pass::Status ProcessImpl();
 
+  // True if current function has a call instruction contained in it
+  bool call_in_func_;
+
+  // True if current function is an entry point
+  bool func_is_entry_point_;
+
+  // True if current function is entry point and has no function calls.
+  bool private_like_local_;
+
   // Live Instruction Worklist.  An instruction is added to this list
   // if it might have a side effect, either directly or indirectly.
   // If we don't know, then add it to this list.  Instructions are
@@ -101,6 +114,9 @@
   // building up the live instructions set |live_insts_|.
   std::queue<ir::Instruction*> worklist_;
 
+  // Store instructions to variables of private storage
+  std::vector<ir::Instruction*> private_stores_;
+
   // Live Instructions
   std::unordered_set<const ir::Instruction*> live_insts_;
 
diff --git a/source/opt/basic_block.cpp b/source/opt/basic_block.cpp
index 7420f3b..5eef51e 100644
--- a/source/opt/basic_block.cpp
+++ b/source/opt/basic_block.cpp
@@ -14,9 +14,19 @@
 
 #include "basic_block.h"
 
+#include "make_unique.h"
+
 namespace spvtools {
 namespace ir {
 
+BasicBlock::BasicBlock(const BasicBlock& bb)
+    : function_(nullptr),
+      label_(MakeUnique<Instruction>(bb.GetLabelInst())),
+      insts_() {
+  for (auto& inst : bb.insts_)
+    AddInstruction(std::unique_ptr<Instruction>(inst.Clone()));
+}
+
 const Instruction* BasicBlock::GetMergeInst() const {
   const Instruction* result = nullptr;
   // If it exists, the merge instruction immediately precedes the
@@ -67,7 +77,7 @@
 
 void BasicBlock::ForEachSuccessorLabel(
     const std::function<void(const uint32_t)>& f) {
-  const auto br = &*insts_.back();
+  const auto br = &insts_.back();
   switch (br->opcode()) {
     case SpvOpBranch: {
       f(br->GetOperand(0).words[0]);
@@ -91,9 +101,9 @@
   --ii;
   if (ii == insts_.begin()) return;
   --ii;
-  if ((*ii)->opcode() == SpvOpSelectionMerge || 
-      (*ii)->opcode() == SpvOpLoopMerge)
-    (*ii)->ForEachInId([&f](const uint32_t* idp) {
+  if (ii->opcode() == SpvOpSelectionMerge || 
+      ii->opcode() == SpvOpLoopMerge)
+    ii->ForEachInId([&f](const uint32_t* idp) {
       f(*idp);
     });
 }
diff --git a/source/opt/basic_block.h b/source/opt/basic_block.h
index 66353e0..969d039 100644
--- a/source/opt/basic_block.h
+++ b/source/opt/basic_block.h
@@ -24,6 +24,7 @@
 #include <vector>
 
 #include "instruction.h"
+#include "instruction_list.h"
 #include "iterator.h"
 
 namespace spvtools {
@@ -34,12 +35,18 @@
 // A SPIR-V basic block.
 class BasicBlock {
  public:
-  using iterator = UptrVectorIterator<Instruction>;
-  using const_iterator = UptrVectorIterator<Instruction, true>;
+  using iterator = InstructionList::iterator;
+  using const_iterator = InstructionList::const_iterator;
 
   // Creates a basic block with the given starting |label|.
   inline explicit BasicBlock(std::unique_ptr<Instruction> label);
 
+  // Creates a basic block from the given basic block |bb|.
+  //
+  // The parent function will default to null and needs to be explicitly set by
+  // the user.
+  explicit BasicBlock(const BasicBlock& bb);
+
   // Sets the enclosing function for this basic block.
   void SetParent(Function* function) { function_ = function; }
 
@@ -51,6 +58,7 @@
 
   // The label starting this basic block.
   Instruction* GetLabelInst() { return label_.get(); }
+  const Instruction& GetLabelInst() const { return *label_; }
 
   // Returns the merge instruction in this basic block, if it exists.
   // Otherwise return null.  May be used whenever tail() can be used.
@@ -64,26 +72,22 @@
   // Returns the id of the label at the top of this block
   inline uint32_t id() const { return label_->result_id(); }
 
-  iterator begin() { return iterator(&insts_, insts_.begin()); }
-  iterator end() { return iterator(&insts_, insts_.end()); }
-  const_iterator cbegin() const {
-    return const_iterator(&insts_, insts_.cbegin());
-  }
-  const_iterator cend() const {
-    return const_iterator(&insts_, insts_.cend());
-  }
+  iterator begin() { return insts_.begin(); }
+  iterator end() { return insts_.end(); }
+  const_iterator cbegin() const { return insts_.cbegin(); }
+  const_iterator cend() const { return insts_.cend(); }
 
   // Returns an iterator pointing to the last instruction.  This may only
   // be used if this block has an instruction other than the OpLabel
   // that defines it.
   iterator tail() {
     assert(!insts_.empty());
-    return iterator(&insts_, std::prev(insts_.end()));
+    return --end();
   }
   // Returns a const iterator, but othewrise similar to tail().
   const_iterator ctail() const {
     assert(!insts_.empty());
-    return const_iterator(&insts_, std::prev(insts_.cend()));
+    return --insts_.cend();
   }
 
   // Runs the given function |f| on each instruction in this basic block, and
@@ -99,12 +103,20 @@
                              bool run_on_debug_line_insts = false);
 
   // Runs the given function |f| on each label id of each successor block
-  void ForEachSuccessorLabel(
-      const std::function<void(const uint32_t)>& f);
+  void ForEachSuccessorLabel(const std::function<void(const uint32_t)>& f);
 
   // Runs the given function |f| on the merge and continue label, if any
-  void ForMergeAndContinueLabel(
-      const std::function<void(const uint32_t)>& f);
+  void ForMergeAndContinueLabel(const std::function<void(const uint32_t)>& f);
+
+  // Returns true if this basic block has any Phi instructions.
+  bool HasPhiInstructions() {
+    int count = 0;
+    ForEachPhiInst([&count](ir::Instruction*) {
+      ++count;
+      return;
+    });
+    return count > 0;
+  }
 
  private:
   // The enclosing function.
@@ -112,25 +124,25 @@
   // The label starting this basic block.
   std::unique_ptr<Instruction> label_;
   // Instructions inside this basic block, but not the OpLabel.
-  std::vector<std::unique_ptr<Instruction>> insts_;
+  InstructionList insts_;
 };
 
 inline BasicBlock::BasicBlock(std::unique_ptr<Instruction> label)
     : function_(nullptr), label_(std::move(label)) {}
 
 inline void BasicBlock::AddInstruction(std::unique_ptr<Instruction> i) {
-  insts_.emplace_back(std::move(i));
+  insts_.push_back(i.release());
 }
 
 inline void BasicBlock::AddInstructions(BasicBlock* bp) {
   auto bEnd = end();
-  (void) bEnd.InsertBefore(&bp->insts_);
+  (void) bEnd.MoveBefore(&bp->insts_);
 }
 
 inline void BasicBlock::ForEachInst(const std::function<void(Instruction*)>& f,
                                     bool run_on_debug_line_insts) {
   if (label_) label_->ForEachInst(f, run_on_debug_line_insts);
-  for (auto& inst : insts_) inst->ForEachInst(f, run_on_debug_line_insts);
+  for (auto& inst : insts_) inst.ForEachInst(f, run_on_debug_line_insts);
 }
 
 inline void BasicBlock::ForEachInst(
@@ -140,15 +152,15 @@
     static_cast<const Instruction*>(label_.get())
         ->ForEachInst(f, run_on_debug_line_insts);
   for (const auto& inst : insts_)
-    static_cast<const Instruction*>(inst.get())
+    static_cast<const Instruction*>(&inst)
         ->ForEachInst(f, run_on_debug_line_insts);
 }
 
 inline void BasicBlock::ForEachPhiInst(
     const std::function<void(Instruction*)>& f, bool run_on_debug_line_insts) {
   for (auto& inst : insts_) {
-    if (inst->opcode() != SpvOpPhi) break;
-    inst->ForEachInst(f, run_on_debug_line_insts);
+    if (inst.opcode() != SpvOpPhi) break;
+    inst.ForEachInst(f, run_on_debug_line_insts);
   }
 }
 
diff --git a/source/opt/block_merge_pass.h b/source/opt/block_merge_pass.h
index ee68617..25cfa59 100644
--- a/source/opt/block_merge_pass.h
+++ b/source/opt/block_merge_pass.h
@@ -36,7 +36,7 @@
 class BlockMergePass : public Pass {
  public:
   BlockMergePass();
-  const char* name() const override { return "sroa"; }
+  const char* name() const override { return "merge-blocks"; }
   Status Process(ir::Module*) override;
 
  private:
diff --git a/source/opt/cfg_cleanup_pass.cpp b/source/opt/cfg_cleanup_pass.cpp
new file mode 100644
index 0000000..d786da8
--- /dev/null
+++ b/source/opt/cfg_cleanup_pass.cpp
@@ -0,0 +1,285 @@
+// Copyright (c) 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// This file implements a pass to cleanup the CFG to remove superfluous
+// constructs (e.g., unreachable basic blocks, empty control flow structures,
+// etc)
+
+#include <queue>
+#include <unordered_set>
+
+#include "cfg_cleanup_pass.h"
+
+#include "function.h"
+#include "module.h"
+
+namespace spvtools {
+namespace opt {
+
+uint32_t CFGCleanupPass::TypeToUndef(uint32_t type_id) {
+  const auto uitr = type2undefs_.find(type_id);
+  if (uitr != type2undefs_.end()) {
+    return uitr->second;
+  }
+
+  const uint32_t undefId = TakeNextId();
+  std::unique_ptr<ir::Instruction> undef_inst(
+      new ir::Instruction(SpvOpUndef, type_id, undefId, {}));
+  def_use_mgr_->AnalyzeInstDefUse(&*undef_inst);
+  module_->AddGlobalValue(std::move(undef_inst));
+  type2undefs_[type_id] = undefId;
+
+  return undefId;
+}
+
+// Remove all |phi| operands coming from unreachable blocks (i.e., blocks not in
+// |reachable_blocks|).  There are two types of removal that this function can
+// perform:
+//
+// 1- Any operand that comes directly from an unreachable block is completely
+//    removed.  Since the block is unreachable, the edge between the unreachable
+//    block and the block holding |phi| has been removed.
+//
+// 2- Any operand that comes via a live block and was defined at an unreachable
+//    block gets its value replaced with an OpUndef value. Since the argument
+//    was generated in an unreachable block, it no longer exists, so it cannot
+//    be referenced.  However, since the value does not reach |phi| directly
+//    from the unreachable block, the operand cannot be removed from |phi|.
+//    Therefore, we replace the argument value with OpUndef.
+//
+// For example, in the switch() below, assume that we want to remove the
+// argument with value %11 coming from block %41.
+//
+//          [ ... ]
+//          %41 = OpLabel                    <--- Unreachable block
+//          %11 = OpLoad %int %y
+//          [ ... ]
+//                OpSelectionMerge %16 None
+//                OpSwitch %12 %16 10 %13 13 %14 18 %15
+//          %13 = OpLabel
+//                OpBranch %16
+//          %14 = OpLabel
+//                OpStore %outparm %int_14
+//                OpBranch %16
+//          %15 = OpLabel
+//                OpStore %outparm %int_15
+//                OpBranch %16
+//          %16 = OpLabel
+//          %30 = OpPhi %int %11 %41 %int_42 %13 %11 %14 %11 %15
+//
+// Since %41 is now an unreachable block, the first operand of |phi| needs to
+// be removed completely.  But the operands (%11 %14) and (%11 %15) cannot be
+// removed because %14 and %15 are reachable blocks.  Since %11 no longer exist,
+// in those arguments, we replace all references to %11 with an OpUndef value.
+// This results in |phi| looking like:
+//
+//           %50 = OpUndef %int
+//           [ ... ]
+//           %30 = OpPhi %int %int_42 %13 %50 %14 %50 %15
+void CFGCleanupPass::RemovePhiOperands(
+    ir::Instruction* phi,
+    std::unordered_set<ir::BasicBlock*> reachable_blocks) {
+  std::vector<ir::Operand> keep_operands;
+  uint32_t type_id = 0;
+  // The id of an undefined value we've generated.
+  uint32_t undef_id = 0;
+
+  // Traverse all the operands in |phi|. Build the new operand vector by adding
+  // all the original operands from |phi| except the unwanted ones.
+  for (uint32_t i = 0; i < phi->NumOperands();) {
+    if (i < 2) {
+      // The first two arguments are always preserved.
+      keep_operands.push_back(phi->GetOperand(i));
+      ++i;
+      continue;
+    }
+
+    // The remaining Phi arguments come in pairs. Index 'i' contains the
+    // variable id, index 'i + 1' is the originating block id.
+    assert(i % 2 == 0 && i < phi->NumOperands() - 1 &&
+           "malformed Phi arguments");
+
+    ir::BasicBlock *in_block = label2block_[phi->GetSingleWordOperand(i + 1)];
+    if (reachable_blocks.find(in_block) == reachable_blocks.end()) {
+      // If the incoming block is unreachable, remove both operands as this
+      // means that the |phi| has lost an incoming edge.
+      i += 2;
+      continue;
+    }
+
+    // In all other cases, the operand must be kept but may need to be changed.
+    uint32_t arg_id = phi->GetSingleWordOperand(i);
+    ir::BasicBlock *def_block = def_block_[arg_id];
+    if (def_block &&
+        reachable_blocks.find(def_block_[arg_id]) == reachable_blocks.end()) {
+      // If the current |phi| argument was defined in an unreachable block, it
+      // means that this |phi| argument is no longer defined. Replace it with
+      // |undef_id|.
+      if (!undef_id) {
+        type_id = def_use_mgr_->GetDef(arg_id)->type_id();
+        undef_id = TypeToUndef(type_id);
+      }
+      keep_operands.push_back(
+          ir::Operand(spv_operand_type_t::SPV_OPERAND_TYPE_ID, {undef_id}));
+    } else {
+      // Otherwise, the argument comes from a reachable block or from no block
+      // at all (meaning that it was defined in the global section of the
+      // program).  In both cases, keep the argument intact.
+      keep_operands.push_back(phi->GetOperand(i));
+    }
+
+    keep_operands.push_back(phi->GetOperand(i + 1));
+
+    i += 2;
+  }
+
+  phi->ReplaceOperands(keep_operands);
+}
+
+void CFGCleanupPass::RemoveBlock(ir::Function::iterator* bi) {
+  auto& rm_block = **bi;
+
+  // Remove instructions from the block.
+  rm_block.ForEachInst([&rm_block, this](ir::Instruction* inst) {
+    // Note that we do not kill the block label instruction here. The label
+    // instruction is needed to identify the block, which is needed by the
+    // removal of phi operands.
+    if (inst != rm_block.GetLabelInst()) {
+      KillNamesAndDecorates(inst);
+      def_use_mgr_->KillInst(inst);
+    }
+  });
+
+  // Remove the label instruction last.
+  auto label = rm_block.GetLabelInst();
+  KillNamesAndDecorates(label);
+  def_use_mgr_->KillInst(label);
+
+  *bi = bi->Erase();
+}
+
+bool CFGCleanupPass::RemoveUnreachableBlocks(ir::Function* func) {
+  bool modified = false;
+
+  // Mark reachable all blocks reachable from the function's entry block.
+  std::unordered_set<ir::BasicBlock*> reachable_blocks;
+  std::unordered_set<ir::BasicBlock*> visited_blocks;
+  std::queue<ir::BasicBlock*> worklist;
+  reachable_blocks.insert(func->entry().get());
+
+  // Initially mark the function entry point as reachable.
+  worklist.push(func->entry().get());
+
+  auto mark_reachable = [&reachable_blocks, &visited_blocks, &worklist,
+                         this](uint32_t label_id) {
+    auto successor = label2block_[label_id];
+    if (visited_blocks.count(successor) == 0) {
+      reachable_blocks.insert(successor);
+      worklist.push(successor);
+      visited_blocks.insert(successor);
+    }
+  };
+
+  // Transitively mark all blocks reachable from the entry as reachable.
+  while (!worklist.empty()) {
+    ir::BasicBlock* block = worklist.front();
+    worklist.pop();
+
+    // All the successors of a live block are also live.
+    block->ForEachSuccessorLabel(mark_reachable);
+
+    // All the Merge and ContinueTarget blocks of a live block are also live.
+    block->ForMergeAndContinueLabel(mark_reachable);
+  }
+
+  // Update operands of Phi nodes that reference unreachable blocks.
+  for (auto& block : *func) {
+    // If the block is about to be removed, don't bother updating its
+    // Phi instructions.
+    if (reachable_blocks.count(&block) == 0) {
+      continue;
+    }
+
+    // If the block is reachable and has Phi instructions, remove all
+    // operands from its Phi instructions that reference unreachable blocks.
+    // If the block has no Phi instructions, this is a no-op.
+    block.ForEachPhiInst(
+        [&block, &reachable_blocks, this](ir::Instruction* phi) {
+          RemovePhiOperands(phi, reachable_blocks);
+        });
+  }
+
+  // Erase unreachable blocks.
+  for (auto ebi = func->begin(); ebi != func->end();) {
+    if (reachable_blocks.count(&*ebi) == 0) {
+      RemoveBlock(&ebi);
+      modified = true;
+    } else {
+      ++ebi;
+    }
+  }
+
+  return modified;
+}
+
+bool CFGCleanupPass::CFGCleanup(ir::Function* func) {
+  bool modified = false;
+  modified |= RemoveUnreachableBlocks(func);
+  return modified;
+}
+
+void CFGCleanupPass::Initialize(ir::Module* module) {
+  // Initialize the DefUse manager. TODO(dnovillo): Re-factor all this into the
+  // module or some other context class for the optimizer.
+  module_ = module;
+  def_use_mgr_.reset(new analysis::DefUseManager(consumer(), module));
+  FindNamedOrDecoratedIds();
+
+  // Initialize next unused Id. TODO(dnovillo): Re-factor into the module or
+  // some other context class for the optimizer.
+  next_id_ = module_->id_bound();
+
+  // Initialize block lookup map.
+  label2block_.clear();
+  for (auto& fn : *module) {
+    for (auto& block : fn) {
+      label2block_[block.id()] = &block;
+
+      // Build a map between SSA names to the block they are defined in.
+      // TODO(dnovillo): This is expensive and unnecessary if ir::Instruction
+      // instances could figure out what basic block they belong to. Remove this
+      // once this is possible.
+      block.ForEachInst([this, &block](ir::Instruction* inst) {
+        uint32_t result_id = inst->result_id();
+        if (result_id > 0) {
+          def_block_[result_id] = &block;
+        }
+      });
+    }
+  }
+}
+
+Pass::Status CFGCleanupPass::Process(ir::Module* module) {
+  Initialize(module);
+
+  // Process all entry point functions.
+  ProcessFunction pfn = [this](ir::Function* fp) { return CFGCleanup(fp); };
+  bool modified = ProcessReachableCallTree(pfn, module);
+  FinalizeNextId(module_);
+  return modified ? Pass::Status::SuccessWithChange
+                  : Pass::Status::SuccessWithoutChange;
+}
+
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/cfg_cleanup_pass.h b/source/opt/cfg_cleanup_pass.h
new file mode 100644
index 0000000..810bc35
--- /dev/null
+++ b/source/opt/cfg_cleanup_pass.h
@@ -0,0 +1,88 @@
+// Copyright (c) 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef LIBSPIRV_OPT_CFG_CLEANUP_PASS_H_
+#define LIBSPIRV_OPT_CFG_CLEANUP_PASS_H_
+
+#include "function.h"
+#include "mem_pass.h"
+#include "module.h"
+
+namespace spvtools {
+namespace opt {
+
+class CFGCleanupPass : public MemPass {
+ public:
+  CFGCleanupPass() = default;
+  const char* name() const override { return "cfg-cleanup"; }
+  Status Process(ir::Module*) override;
+
+ private:
+  // Call all the cleanup helper functions on |func|.
+  bool CFGCleanup(ir::Function* func);
+
+  // Remove all the unreachable basic blocks in |func|.
+  bool RemoveUnreachableBlocks(ir::Function* func);
+
+  // Remove the block pointed by the iterator |*bi|. This also removes
+  // all the instructions in the pointed-to block.
+  void RemoveBlock(ir::Function::iterator* bi);
+
+  // Initialize the pass.
+  void Initialize(ir::Module* module);
+
+  // Remove Phi operands in |phi| that are coming from blocks not in
+  // |reachable_blocks|.
+  void RemovePhiOperands(ir::Instruction* phi,
+                         std::unordered_set<ir::BasicBlock*> reachable_blocks);
+
+  // Return the next available Id and increment it. TODO(dnovillo): Refactor
+  // into a new type pool manager to be used for all passes.
+  inline uint32_t TakeNextId() { return next_id_++; }
+
+  // Save next available id into |module|. TODO(dnovillo): Refactor
+  // into a new type pool manager to be used for all passes.
+  inline void FinalizeNextId(ir::Module* module) {
+    module->SetIdBound(next_id_);
+  }
+
+  // Return undef in function for type. Create and insert an undef after the
+  // first non-variable in the function if it doesn't already exist. Add
+  // undef to function undef map. TODO(dnovillo): Refactor into a new
+  // type pool manager to be used for all passes.
+  uint32_t TypeToUndef(uint32_t type_id);
+
+  // Map from block's label id to block. TODO(dnovillo): Basic blocks ought to
+  // have basic blocks in their pred/succ list.
+  std::unordered_map<uint32_t, ir::BasicBlock*> label2block_;
+
+  // Map from an instruction result ID to the block that holds it.
+  // TODO(dnovillo): This would be unnecessary if ir::Instruction instances
+  // knew what basic block they belong to.
+  std::unordered_map<uint32_t, ir::BasicBlock*> def_block_;
+
+  // Map from type to undef values. TODO(dnovillo): This is replicated from
+  // class LocalMultiStoreElimPass. It should be refactored into a type
+  // pool manager.
+  std::unordered_map<uint32_t, uint32_t> type2undefs_;
+
+  // Next unused ID. TODO(dnovillo): Refactor this to some common utility class.
+  // Seems to be implemented in very many passes.
+  uint32_t next_id_;
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif
diff --git a/source/opt/common_uniform_elim_pass.cpp b/source/opt/common_uniform_elim_pass.cpp
index d8d6519..acc8f77 100644
--- a/source/opt/common_uniform_elim_pass.cpp
+++ b/source/opt/common_uniform_elim_pass.cpp
@@ -15,9 +15,7 @@
 // limitations under the License.
 
 #include "common_uniform_elim_pass.h"
-
 #include "cfa.h"
-#include "iterator.h"
 
 namespace spvtools {
 namespace opt {
@@ -130,6 +128,68 @@
   return ptrInst;
 }
 
+bool CommonUniformElimPass::IsVolatileStruct(uint32_t type_id) {
+  assert(def_use_mgr_->GetDef(type_id)->opcode() == SpvOpTypeStruct);
+  bool has_volatile_deco = false;
+  dec_mgr_->ForEachDecoration(type_id, SpvDecorationVolatile,
+                              [&has_volatile_deco](const ir::Instruction&){ has_volatile_deco = true;});
+  return has_volatile_deco;
+}
+
+bool CommonUniformElimPass::IsAccessChainToVolatileStructType(const ir::Instruction &AccessChainInst) {
+  assert(AccessChainInst.opcode() == SpvOpAccessChain);
+
+  uint32_t ptr_id = AccessChainInst.GetSingleWordInOperand(0);
+  const ir::Instruction* ptr_inst = def_use_mgr_->GetDef(ptr_id);
+  uint32_t pointee_type_id = GetPointeeTypeId(ptr_inst);
+  const uint32_t num_operands = AccessChainInst.NumOperands();
+
+  // walk the type tree:
+  for (uint32_t idx = 3; idx < num_operands; ++idx) {
+    ir::Instruction* pointee_type = def_use_mgr_->GetDef(pointee_type_id);
+
+    switch (pointee_type->opcode()) {
+      case SpvOpTypeMatrix:
+      case SpvOpTypeVector:
+      case SpvOpTypeArray:
+      case SpvOpTypeRuntimeArray:
+        pointee_type_id = pointee_type->GetSingleWordOperand(1);
+        break;
+      case SpvOpTypeStruct:
+        // check for volatile decorations:
+        if (IsVolatileStruct(pointee_type_id)) return true;
+
+        if (idx < num_operands - 1) {
+          const uint32_t index_id = AccessChainInst.GetSingleWordOperand(idx);
+          const ir::Instruction* index_inst = def_use_mgr_->GetDef(index_id);
+          uint32_t index_value = index_inst->GetSingleWordOperand(2); // TODO: replace with GetUintValueFromConstant()
+          pointee_type_id = pointee_type->GetSingleWordInOperand(index_value);
+        }
+        break;
+      default:
+        assert(false && "Unhandled pointee type.");
+    }
+  }
+  return false;
+}
+
+bool CommonUniformElimPass::IsVolatileLoad(const ir::Instruction& loadInst) {
+  assert(loadInst.opcode() == SpvOpLoad);
+  // Check if this Load instruction has Volatile Memory Access flag
+  if (loadInst.NumOperands() == 4) {
+    uint32_t memory_access_mask = loadInst.GetSingleWordOperand(3);
+    if (memory_access_mask & SpvMemoryAccessVolatileMask)
+      return true;
+  }
+  // If we load a struct directly (result type is struct),
+  // check if the struct is decorated volatile
+  uint32_t type_id = loadInst.type_id();
+  if (def_use_mgr_->GetDef(type_id)->opcode() == SpvOpTypeStruct)
+    return IsVolatileStruct(type_id);
+  else
+    return false;
+}
+
 bool CommonUniformElimPass::IsUniformVar(uint32_t varId) {
   const ir::Instruction* varInst =
     def_use_mgr_->id_to_defs().find(varId)->second;
@@ -301,12 +361,16 @@
         continue;
       if (HasUnsupportedDecorates(ptrInst->result_id()))
         continue;
+      if (IsVolatileLoad(*ii))
+        continue;
+      if (IsAccessChainToVolatileStructType(*ptrInst))
+        continue;
       std::vector<std::unique_ptr<ir::Instruction>> newInsts;
       uint32_t replId;
       GenACLoadRepl(ptrInst, &newInsts, &replId);
       ReplaceAndDeleteLoad(&*ii, replId, ptrInst);
       ++ii;
-      ii = ii.InsertBefore(&newInsts);
+      ii = ii.InsertBefore(std::move(newInsts));
       ++ii;
       modified = true;
     }
@@ -386,6 +450,8 @@
         continue;
       if (HasUnsupportedDecorates(ii->result_id()))
         continue;
+      if (IsVolatileLoad(*ii))
+        continue;
       uint32_t replId;
       const auto uItr = uniform2load_id_.find(varId);
       if (uItr != uniform2load_id_.end()) {
@@ -438,6 +504,8 @@
         continue;
       if (HasUnsupportedDecorates(ii->result_id()))
         continue;
+      if (IsVolatileLoad(*ii))
+        continue;
       uint32_t replId;
       const auto uItr = uniform2load_id_.find(varId);
       if (uItr != uniform2load_id_.end()) {
@@ -527,6 +595,7 @@
 
   // TODO(greg-lunarg): Use def/use from previous pass
   def_use_mgr_.reset(new analysis::DefUseManager(consumer(), module_));
+  dec_mgr_.reset(new analysis::DecorationManager(module_));
 
   // Initialize next unused Id.
   next_id_ = module->id_bound();
diff --git a/source/opt/common_uniform_elim_pass.h b/source/opt/common_uniform_elim_pass.h
index 1e332eb..47c7767 100644
--- a/source/opt/common_uniform_elim_pass.h
+++ b/source/opt/common_uniform_elim_pass.h
@@ -25,6 +25,7 @@
 #include <utility>
 
 #include "def_use_manager.h"
+#include "decoration_manager.h"
 #include "module.h"
 #include "basic_block.h"
 #include "pass.h"
@@ -41,7 +42,7 @@
      std::function<std::vector<ir::BasicBlock*>*(const ir::BasicBlock*)>;
 
   CommonUniformElimPass();
-  const char* name() const override { return "common-uniform-elim"; }
+  const char* name() const override { return "eliminate-common-uniform"; }
   Status Process(ir::Module*) override;
 
  private:
@@ -66,6 +67,20 @@
   // Return true if variable is uniform
   bool IsUniformVar(uint32_t varId);
 
+  // Given the type id for a struct type, checks if the struct type
+  // or any struct member is volatile decorated
+  bool IsVolatileStruct(uint32_t type_id);
+
+  // Given an OpAccessChain instruction, return true
+  // if the accessed variable belongs to a volatile
+  // decorated object or member of a struct type
+  bool IsAccessChainToVolatileStructType(const ir::Instruction &AccessChainInst);
+
+  // Given an OpLoad instruction, return true if
+  // OpLoad has a Volatile Memory Access flag or if
+  // the resulting type is a volatile decorated struct
+  bool IsVolatileLoad(const ir::Instruction& loadInst);
+
   // Return true if any uses of |id| are decorate ops.
   bool HasUnsupportedDecorates(uint32_t id) const;
 
@@ -179,6 +194,9 @@
   // Def-Uses for the module we are processing
   std::unique_ptr<analysis::DefUseManager> def_use_mgr_;
 
+  // Decorations for the module we are processing
+  std::unique_ptr<analysis::DecorationManager> dec_mgr_;
+
   // Map from block's label id to block.
   std::unordered_map<uint32_t, ir::BasicBlock*> id2block_;
 
diff --git a/source/opt/compact_ids_pass.cpp b/source/opt/compact_ids_pass.cpp
index 39792e8..cf5eacf 100644
--- a/source/opt/compact_ids_pass.cpp
+++ b/source/opt/compact_ids_pass.cpp
@@ -30,7 +30,8 @@
   module->ForEachInst([&result_id_mapping, &modified] (Instruction* inst) {
     auto operand = inst->begin();
     while (operand != inst->end()) {
-      if (spvIsIdType(operand->type)) {
+      const auto type = operand->type;
+      if (spvIsIdType(type)) {
         assert(operand->words.size() == 1);
         uint32_t& id = operand->words[0];
         auto it = result_id_mapping.find(id);
@@ -44,12 +45,21 @@
         if (id != it->second) {
           modified = true;
           id = it->second;
+          // Update data cached in the instruction object.
+          if (type == SPV_OPERAND_TYPE_RESULT_ID) {
+            inst->SetResultId(id);
+          } else if (type == SPV_OPERAND_TYPE_TYPE_ID) {
+            inst->SetResultType(id);
+          }
         }
       }
       ++operand;
     }
   }, true);
 
+  if (modified)
+    module->SetIdBound(static_cast<uint32_t>(result_id_mapping.size() + 1));
+
   return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
 }
 
diff --git a/source/opt/dead_branch_elim_pass.cpp b/source/opt/dead_branch_elim_pass.cpp
index ef25705..44e4f90 100644
--- a/source/opt/dead_branch_elim_pass.cpp
+++ b/source/opt/dead_branch_elim_pass.cpp
@@ -24,13 +24,10 @@
 
 namespace {
 
-const uint32_t kBranchCondConditionalIdInIdx = 0;
+const uint32_t kBranchTargetLabIdInIdx = 0;
 const uint32_t kBranchCondTrueLabIdInIdx = 1;
 const uint32_t kBranchCondFalseLabIdInIdx = 2;
 const uint32_t kSelectionMergeMergeBlockIdInIdx = 0;
-const uint32_t kPhiVal0IdInIdx = 0;
-const uint32_t kPhiLab0IdInIdx = 1;
-const uint32_t kPhiVal1IdInIdx = 2;
 const uint32_t kLoopMergeMergeBlockIdInIdx = 0;
 const uint32_t kLoopMergeContinueBlockIdInIdx = 1;
 
@@ -92,29 +89,49 @@
       ignore_edge);
 }
 
-void DeadBranchElimPass::GetConstCondition(
-    uint32_t condId, bool* condVal, bool* condIsConst) {
+bool DeadBranchElimPass::GetConstCondition(uint32_t condId, bool* condVal) {
+  bool condIsConst;
   ir::Instruction* cInst = def_use_mgr_->GetDef(condId);
   switch (cInst->opcode()) {
     case SpvOpConstantFalse: {
       *condVal = false;
-      *condIsConst = true;
+      condIsConst = true;
     } break;
     case SpvOpConstantTrue: {
       *condVal = true;
-      *condIsConst = true;
+      condIsConst = true;
     } break;
     case SpvOpLogicalNot: {
       bool negVal;
-      (void)GetConstCondition(cInst->GetSingleWordInOperand(0),
-          &negVal, condIsConst);
-      if (*condIsConst)
+      condIsConst = GetConstCondition(cInst->GetSingleWordInOperand(0),
+          &negVal);
+      if (condIsConst)
         *condVal = !negVal;
     } break;
     default: {
-      *condIsConst = false;
+      condIsConst = false;
     } break;
   }
+  return condIsConst;
+}
+
+bool DeadBranchElimPass::GetConstInteger(uint32_t selId, uint32_t* selVal) {
+  ir::Instruction* sInst = def_use_mgr_->GetDef(selId);
+  uint32_t typeId = sInst->type_id();
+  ir::Instruction* typeInst = def_use_mgr_->GetDef(typeId);
+  if (!typeInst || (typeInst->opcode() != SpvOpTypeInt)) return false;
+  // TODO(greg-lunarg): Support non-32 bit ints
+  if (typeInst->GetSingleWordInOperand(0) != 32)
+    return false;
+  if (sInst->opcode() == SpvOpConstant) {
+    *selVal = sInst->GetSingleWordInOperand(0);
+    return true;
+  }
+  else if (sInst->opcode() == SpvOpConstantNull) {
+    *selVal = 0;
+    return true;
+  }
+  return false;
 }
 
 void DeadBranchElimPass::AddBranch(uint32_t labelId, ir::BasicBlock* bp) {
@@ -153,138 +170,235 @@
   });
 }
 
-bool DeadBranchElimPass::GetConstConditionalSelectionBranch(ir::BasicBlock* bp,
+bool DeadBranchElimPass::GetSelectionBranch(ir::BasicBlock* bp,
     ir::Instruction** branchInst, ir::Instruction** mergeInst,
-    uint32_t *condId, bool *condVal) {
+    uint32_t *condId) {
   auto ii = bp->end();
   --ii;
   *branchInst = &*ii;
-  if ((*branchInst)->opcode() != SpvOpBranchConditional)
-    return false;
   if (ii == bp->begin())
     return false;
   --ii;
   *mergeInst = &*ii;
   if ((*mergeInst)->opcode() != SpvOpSelectionMerge)
     return false;
-  bool condIsConst;
-  *condId = (*branchInst)->GetSingleWordInOperand(
-      kBranchCondConditionalIdInIdx);
-  (void) GetConstCondition(*condId, condVal, &condIsConst);
-  return condIsConst;
+  // SPIR-V says the terminator for an OpSelectionMerge must be
+  // either a conditional branch or a switch.
+  assert((*branchInst)->opcode() == SpvOpBranchConditional ||
+         (*branchInst)->opcode() == SpvOpSwitch);
+  // Both BranchConidtional and Switch have their conditional value at 0.
+  *condId = (*branchInst)->GetSingleWordInOperand(0);
+  return true;
 }
 
-bool DeadBranchElimPass::HasNonPhiRef(uint32_t labelId) {
+bool DeadBranchElimPass::HasNonPhiNonBackedgeRef(uint32_t labelId) {
   analysis::UseList* uses = def_use_mgr_->GetUses(labelId);
   if (uses == nullptr)
     return false;
-  for (auto u : *uses)
-    if (u.inst->opcode() != SpvOpPhi)
+  for (auto u : *uses) {
+    if (u.inst->opcode() != SpvOpPhi &&
+        backedges_.find(u.inst) == backedges_.end())
       return true;
+  }
   return false;
 }
 
+void DeadBranchElimPass::ComputeBackEdges(
+    std::list<ir::BasicBlock*>& structuredOrder) {
+  backedges_.clear();
+  std::unordered_set<uint32_t> visited;
+  // In structured order, edges to visited blocks are back edges
+  for (auto bi = structuredOrder.begin(); bi != structuredOrder.end(); ++bi) {
+    visited.insert((*bi)->id());
+    auto ii = (*bi)->end();
+    --ii;
+    switch(ii->opcode()) {
+      case SpvOpBranch: {
+        const uint32_t labId = ii->GetSingleWordInOperand(
+            kBranchTargetLabIdInIdx);
+        if (visited.find(labId) != visited.end())
+          backedges_.insert(&*ii);
+      } break;
+      case SpvOpBranchConditional: {
+        const uint32_t tLabId = ii->GetSingleWordInOperand(
+            kBranchCondTrueLabIdInIdx);
+        if (visited.find(tLabId) != visited.end()) {
+          backedges_.insert(&*ii);
+          break;
+        }
+        const uint32_t fLabId = ii->GetSingleWordInOperand(
+            kBranchCondFalseLabIdInIdx);
+        if (visited.find(fLabId) != visited.end())
+          backedges_.insert(&*ii);
+      } break;
+      default:
+        break;
+    }
+  }
+}
+
 bool DeadBranchElimPass::EliminateDeadBranches(ir::Function* func) {
   // Traverse blocks in structured order
   std::list<ir::BasicBlock*> structuredOrder;
   ComputeStructuredOrder(func, &structuredOrder);
+  ComputeBackEdges(structuredOrder);
   std::unordered_set<ir::BasicBlock*> elimBlocks;
   bool modified = false;
   for (auto bi = structuredOrder.begin(); bi != structuredOrder.end(); ++bi) {
     // Skip blocks that are already in the elimination set
     if (elimBlocks.find(*bi) != elimBlocks.end())
       continue;
-    // Skip blocks that don't have constant conditional branch preceded
+    // Skip blocks that don't have conditional branch preceded
     // by OpSelectionMerge
     ir::Instruction* br;
     ir::Instruction* mergeInst;
     uint32_t condId;
-    bool condVal;
-    if (!GetConstConditionalSelectionBranch(*bi, &br, &mergeInst, &condId,
-        &condVal))
+    if (!GetSelectionBranch(*bi, &br, &mergeInst, &condId))
       continue;
 
-    // Replace conditional branch with unconditional branch
-    const uint32_t trueLabId =
-        br->GetSingleWordInOperand(kBranchCondTrueLabIdInIdx);
-    const uint32_t falseLabId =
-        br->GetSingleWordInOperand(kBranchCondFalseLabIdInIdx);
+    // If constant condition/selector, replace conditional branch/switch
+    // with unconditional branch and delete merge
+    uint32_t liveLabId;
+    if (br->opcode() == SpvOpBranchConditional) {
+      bool condVal;
+      if (!GetConstCondition(condId, &condVal))
+        continue;
+      liveLabId = (condVal == true) ? 
+          br->GetSingleWordInOperand(kBranchCondTrueLabIdInIdx) :
+          br->GetSingleWordInOperand(kBranchCondFalseLabIdInIdx);
+    }
+    else {
+      assert(br->opcode() == SpvOpSwitch);
+      // Search switch operands for selector value, set liveLabId to
+      // corresponding label, use default if not found
+      uint32_t selVal;
+      if (!GetConstInteger(condId, &selVal))
+        continue;
+      uint32_t icnt = 0;
+      uint32_t caseVal;
+      br->ForEachInOperand(
+            [&icnt,&caseVal,&selVal,&liveLabId](const uint32_t* idp) {
+        if (icnt == 1) {
+          // Start with default label
+          liveLabId = *idp;
+        }
+        else if (icnt > 1) {
+          if (icnt % 2 == 0) {
+            caseVal = *idp;
+          }
+          else {
+            if (caseVal == selVal)
+              liveLabId = *idp;
+          }
+        }
+        ++icnt;
+      });
+    }
+
     const uint32_t mergeLabId =
         mergeInst->GetSingleWordInOperand(kSelectionMergeMergeBlockIdInIdx);
-    const uint32_t liveLabId = condVal == true ? trueLabId : falseLabId;
-    const uint32_t deadLabId = condVal == true ? falseLabId : trueLabId;
     AddBranch(liveLabId, *bi);
     def_use_mgr_->KillInst(br);
     def_use_mgr_->KillInst(mergeInst);
 
-    // Initialize live block set to the live label
-    std::unordered_set<uint32_t> liveLabIds;
-    liveLabIds.insert(liveLabId);
+    modified = true;
 
     // Iterate to merge block adding dead blocks to elimination set
     auto dbi = bi;
     ++dbi;
     uint32_t dLabId = (*dbi)->id();
     while (dLabId != mergeLabId) {
-      if (liveLabIds.find(dLabId) == liveLabIds.end()) {
+      if (!HasNonPhiNonBackedgeRef(dLabId)) {
         // Kill use/def for all instructions and mark block for elimination
         KillAllInsts(*dbi);
         elimBlocks.insert(*dbi);
       }
-      else {
-        // Mark all successors as live
-        (*dbi)->ForEachSuccessorLabel([&liveLabIds](const uint32_t succId){
-          liveLabIds.insert(succId);
-        });
-        // Mark merge and continue blocks as live
-        (*dbi)->ForMergeAndContinueLabel([&liveLabIds](const uint32_t succId){
-          liveLabIds.insert(succId);
-        });
-      }
       ++dbi;
       dLabId = (*dbi)->id();
     }
 
-    // Process phi instructions in merge block.
-    // elimBlocks are now blocks which cannot precede merge block. Also,
-    // if eliminated branch is to merge label, remember the conditional block
-    // also cannot precede merge block.
-    uint32_t deadCondLabId = 0;
-    if (deadLabId == mergeLabId)
-      deadCondLabId = (*bi)->id();
-    (*dbi)->ForEachPhiInst([&elimBlocks, &deadCondLabId, this](
-        ir::Instruction* phiInst) {
-      const uint32_t phiLabId0 =
-          phiInst->GetSingleWordInOperand(kPhiLab0IdInIdx);
-      const bool useFirst =
-          elimBlocks.find(id2block_[phiLabId0]) == elimBlocks.end() &&
-          phiLabId0 != deadCondLabId;
-      const uint32_t phiValIdx =
-          useFirst ? kPhiVal0IdInIdx : kPhiVal1IdInIdx;
-      const uint32_t replId = phiInst->GetSingleWordInOperand(phiValIdx);
-      const uint32_t phiId = phiInst->result_id();
+    // If merge block is unreachable, continue eliminating blocks until
+    // a live block or last block is reached.
+    while (!HasNonPhiNonBackedgeRef(dLabId)) {
+      KillAllInsts(*dbi);
+      elimBlocks.insert(*dbi);
+      ++dbi;
+      if (dbi == structuredOrder.end())
+        break;
+      dLabId = (*dbi)->id();
+    }
+
+    // If last block reached, look for next dead branch
+    if (dbi == structuredOrder.end())
+      continue;
+
+    // Create set of dead predecessors in preparation for phi update.
+    // Add the header block if the live branch is not the merge block.
+    std::unordered_set<ir::BasicBlock*> deadPreds(elimBlocks);
+    if (liveLabId != dLabId)
+      deadPreds.insert(*bi);
+
+    // Update phi instructions in terminating block.
+    for (auto pii = (*dbi)->begin(); ; ++pii) {
+      // Skip NoOps, break at end of phis
+      SpvOp op = pii->opcode();
+      if (op == SpvOpNop)
+        continue;
+      if (op != SpvOpPhi)
+        break;
+      // Count phi's live predecessors with lcnt and remember last one
+      // with lidx.
+      uint32_t lcnt = 0;
+      uint32_t lidx = 0;
+      uint32_t icnt = 0;
+      pii->ForEachInId(
+          [&deadPreds,&icnt,&lcnt,&lidx,this](uint32_t* idp) {
+        if (icnt % 2 == 1) {
+          if (deadPreds.find(id2block_[*idp]) == deadPreds.end()) {
+            ++lcnt;
+            lidx = icnt - 1;
+          }
+        }
+        ++icnt;
+      });
+      // If just one live predecessor, replace resultid with live value id.
+      uint32_t replId;
+      if (lcnt == 1) {
+        replId = pii->GetSingleWordInOperand(lidx);
+      }
+      else {
+        // Otherwise create new phi eliminating dead predecessor entries
+        assert(lcnt > 1);
+        replId = TakeNextId();
+        std::vector<ir::Operand> phi_in_opnds;
+        icnt = 0;
+        uint32_t lastId;
+        pii->ForEachInId(
+            [&deadPreds,&icnt,&phi_in_opnds,&lastId,this](uint32_t* idp) {
+          if (icnt % 2 == 1) {
+            if (deadPreds.find(id2block_[*idp]) == deadPreds.end()) {
+              phi_in_opnds.push_back(
+                  {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {lastId}});
+              phi_in_opnds.push_back(
+                  {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {*idp}});
+            }
+          }
+          else {
+            lastId = *idp;
+          }
+          ++icnt;
+        });
+        std::unique_ptr<ir::Instruction> newPhi(new ir::Instruction(
+            SpvOpPhi, pii->type_id(), replId, phi_in_opnds));
+        def_use_mgr_->AnalyzeInstDefUse(&*newPhi);
+        pii = pii.InsertBefore(std::move(newPhi));
+        ++pii;
+      }
+      const uint32_t phiId = pii->result_id();
       KillNamesAndDecorates(phiId);
       (void)def_use_mgr_->ReplaceAllUsesWith(phiId, replId);
-      def_use_mgr_->KillInst(phiInst);
-    });
-
-    // If merge block has no predecessors, replace the new branch with
-    // a MergeSelection/BranchCondition using the original constant condition
-    // and the mergeblock as the false branch. This is done so the merge block
-    // is not orphaned, which could cause invalid control flow in certain case.
-    // TODO(greg-lunarg): Do this only in cases where invalid code is caused.
-    if (!HasNonPhiRef(mergeLabId)) {
-      auto eii = (*bi)->end();
-      --eii;
-      ir::Instruction* nbr = &*eii;
-      AddSelectionMerge(mergeLabId, *bi);
-      if (condVal == true)
-        AddBranchConditional(condId, liveLabId, mergeLabId, *bi);
-      else
-        AddBranchConditional(condId, mergeLabId, liveLabId, *bi);
-      def_use_mgr_->KillInst(nbr);
+      def_use_mgr_->KillInst(&*pii);
     }
-    modified = true;
   }
 
   // Erase dead blocks
@@ -312,6 +426,9 @@
   // TODO(greg-lunarg): Reuse def/use from previous passes
   def_use_mgr_.reset(new analysis::DefUseManager(consumer(), module_));
 
+  // Initialize next unused Id.
+  InitNextId();
+
   // Initialize extension whitelist
   InitExtensions();
 };
@@ -348,6 +465,7 @@
     return EliminateDeadBranches(fp);
   };
   bool modified = ProcessEntryPointCallTree(pfn, module_);
+  FinalizeNextId();
   return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
 }
 
diff --git a/source/opt/dead_branch_elim_pass.h b/source/opt/dead_branch_elim_pass.h
index 01e84b2..0b0f25d 100644
--- a/source/opt/dead_branch_elim_pass.h
+++ b/source/opt/dead_branch_elim_pass.h
@@ -43,7 +43,7 @@
      std::function<std::vector<ir::BasicBlock*>*(const ir::BasicBlock*)>;
 
   DeadBranchElimPass();
-  const char* name() const override { return "dead-branch-elim"; }
+  const char* name() const override { return "eliminate-dead-branches"; }
   Status Process(ir::Module*) override;
 
  private:
@@ -69,9 +69,13 @@
   void ComputeStructuredOrder(
     ir::Function* func, std::list<ir::BasicBlock*>* order);
 
-  // If |condId| is boolean constant, return value in |condVal| and
-  // |condIsConst| as true, otherwise return |condIsConst| as false.
-  void GetConstCondition(uint32_t condId, bool* condVal, bool* condIsConst);
+  // If |condId| is boolean constant, return conditional value in |condVal| and
+  // return true, otherwise return false.
+  bool GetConstCondition(uint32_t condId, bool* condVal);
+
+  // If |valId| is a 32-bit integer constant, return value via |value| and
+  // return true, otherwise return false.
+  bool GetConstInteger(uint32_t valId, uint32_t* value);
 
   // Add branch to |labelId| to end of block |bp|.
   void AddBranch(uint32_t labelId, ir::BasicBlock* bp);
@@ -87,15 +91,17 @@
   // Kill all instructions in block |bp|.
   void KillAllInsts(ir::BasicBlock* bp);
 
-  // If block |bp| contains constant conditional branch preceeded by an
+  // If block |bp| contains conditional branch or switch preceeded by an
   // OpSelctionMerge, return true and return branch and merge instructions
-  // in |branchInst| and |mergeInst| and the boolean constant in |condVal|. 
-  bool GetConstConditionalSelectionBranch(ir::BasicBlock* bp,
-    ir::Instruction** branchInst, ir::Instruction** mergeInst,
-    uint32_t *condId, bool *condVal);
+  // in |branchInst| and |mergeInst| and the conditional id in |condId|. 
+  bool GetSelectionBranch(ir::BasicBlock* bp, ir::Instruction** branchInst,
+    ir::Instruction** mergeInst, uint32_t *condId);
 
-  // Return true if |labelId| has any non-phi references
-  bool HasNonPhiRef(uint32_t labelId);
+  // Return true if |labelId| has any non-phi, non-backedge references
+  bool HasNonPhiNonBackedgeRef(uint32_t labelId);
+
+  // Compute backedges for blocks in |structuredOrder|.
+  void ComputeBackEdges(std::list<ir::BasicBlock*>& structuredOrder);
 
   // For function |func|, look for BranchConditionals with constant condition
   // and convert to a Branch to the indicated label. Delete resulting dead
@@ -122,6 +128,9 @@
   std::unordered_map<const ir::BasicBlock*, std::vector<ir::BasicBlock*>>
       block2structured_succs_;
   
+  // All backedge branches in current function
+  std::unordered_set<ir::Instruction*> backedges_;
+
   // Extensions supported by this pass.
   std::unordered_set<std::string> extensions_whitelist_;
 };
diff --git a/source/opt/dead_variable_elimination.cpp b/source/opt/dead_variable_elimination.cpp
new file mode 100644
index 0000000..0e16081
--- /dev/null
+++ b/source/opt/dead_variable_elimination.cpp
@@ -0,0 +1,118 @@
+// Copyright (c) 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "dead_variable_elimination.h"
+
+#include "reflect.h"
+
+namespace spvtools {
+namespace opt {
+
+// This optimization removes global variables that are not needed because they
+// are definitely not accessed.
+Pass::Status DeadVariableElimination::Process(spvtools::ir::Module* module) {
+  // The algorithm will compute the reference count for every global variable.
+  // Anything with a reference count of 0 will then be deleted.  For variables
+  // that might have references that are not explicit in this module, we use the
+  // value kMustKeep as the reference count.
+
+  bool modified = false;
+  module_ = module;
+  def_use_mgr_.reset(new analysis::DefUseManager(consumer(), module));
+  FindNamedOrDecoratedIds();
+
+  //  Decoration manager to help organize decorations.
+  analysis::DecorationManager decoration_manager(module);
+
+  std::vector<uint32_t> ids_to_remove;
+
+  // Get the reference count for all of the global OpVariable instructions.
+  for (auto& inst : module->types_values()) {
+    if (inst.opcode() != SpvOp::SpvOpVariable) {
+      continue;
+    }
+
+    size_t count = 0;
+    uint32_t result_id = inst.result_id();
+
+    // Check the linkage.  If it is exported, it could be reference somewhere
+    // else, so we must keep the variable around.
+    decoration_manager.ForEachDecoration(
+        result_id, SpvDecorationLinkageAttributes,
+        [&count](const ir::Instruction& linkage_instruction) {
+          uint32_t last_operand = linkage_instruction.NumOperands() - 1;
+          if (linkage_instruction.GetSingleWordOperand(last_operand) ==
+              SpvLinkageTypeExport) {
+            count = kMustKeep;
+          }
+        });
+
+    if (count != kMustKeep) {
+      // If we don't have to keep the instruction for other reasons, then look
+      // at the uses and count the number of real references.
+      if (analysis::UseList* uses = def_use_mgr_->GetUses(result_id)) {
+        count = std::count_if(
+            uses->begin(), uses->end(), [](const analysis::Use& u) {
+              return (!ir::IsAnnotationInst(u.inst->opcode()) &&
+                  u.inst->opcode() != SpvOpName);
+            });
+      }
+    }
+    reference_count_[result_id] = count;
+    if (count == 0) {
+      ids_to_remove.push_back(result_id);
+    }
+  }
+
+  // Remove all of the variables that have a reference count of 0.
+  if (!ids_to_remove.empty()) {
+    modified = true;
+    for (auto result_id : ids_to_remove) {
+      DeleteVariable(result_id);
+    }
+  }
+  return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange);
+}
+
+void DeadVariableElimination::DeleteVariable(uint32_t result_id) {
+  ir::Instruction* inst = def_use_mgr_->GetDef(result_id);
+  assert(inst->opcode() == SpvOpVariable &&
+      "Should not be trying to delete anything other than an OpVariable.");
+
+  // Look for an initializer that references another variable.  We need to know
+  // if that variable can be deleted after the reference is removed.
+  if (inst->NumOperands() == 4) {
+    ir::Instruction* initializer =
+        def_use_mgr_->GetDef(inst->GetSingleWordOperand(3));
+
+    // TODO: Handle OpSpecConstantOP which might be defined in terms of other
+    // variables.  Will probably require a unified dead code pass that does all
+    // instruction types.  (Issue 906)
+    if (initializer->opcode() == SpvOpVariable) {
+      uint32_t initializer_id = initializer->result_id();
+      size_t& count = reference_count_[initializer_id];
+      if (count != kMustKeep) {
+        --count;
+      }
+
+      if (count == 0) {
+        DeleteVariable(initializer_id);
+      }
+    }
+  }
+  this->KillNamesAndDecorates(result_id);
+  def_use_mgr_->KillDef(result_id);
+}
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/dead_variable_elimination.h b/source/opt/dead_variable_elimination.h
new file mode 100644
index 0000000..a0992cc
--- /dev/null
+++ b/source/opt/dead_variable_elimination.h
@@ -0,0 +1,51 @@
+// Copyright (c) 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SPIRV_TOOLS_DEAD_VARIABLE_ELIMINATION_H
+#define SPIRV_TOOLS_DEAD_VARIABLE_ELIMINATION_H
+
+#include <unordered_map>
+#include <climits>
+
+#include "decoration_manager.h"
+#include "mem_pass.h"
+
+namespace spvtools {
+namespace opt {
+
+class DeadVariableElimination : public MemPass {
+ public:
+  const char* name() const override { return "dead-variable-elimination"; }
+  Status Process(ir::Module*) override;
+
+ private:
+  // Deletes the OpVariable instruction who result id is |result_id|.
+  void DeleteVariable(uint32_t result_id);
+
+  // Keeps track of the number of references of an id.  Once that value is 0, it
+  // is safe to remove the corresponding instruction.
+  //
+  // Note that the special value kMustKeep is used to indicate that the
+  // instruction cannot be deleted for reasons other that is being explicitly
+  // referenced.
+  std::unordered_map<uint32_t, size_t> reference_count_;
+
+  // Special value used to indicate that an id cannot be safely deleted.
+  enum { kMustKeep = INT_MAX };
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // SPIRV_TOOLS_DEAD_VARIABLE_ELIMINATION_H
diff --git a/source/opt/decoration_manager.cpp b/source/opt/decoration_manager.cpp
new file mode 100644
index 0000000..b12670b
--- /dev/null
+++ b/source/opt/decoration_manager.cpp
@@ -0,0 +1,263 @@
+// Copyright (c) 2017 Pierre Moreau
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "decoration_manager.h"
+
+#include <stack>
+
+namespace spvtools {
+namespace opt {
+namespace analysis {
+
+void DecorationManager::RemoveDecorationsFrom(uint32_t id, bool keep_linkage) {
+  auto const ids_iter = id_to_decoration_insts_.find(id);
+  if (ids_iter == id_to_decoration_insts_.end()) return;
+
+  for (ir::Instruction* inst : ids_iter->second) {
+    switch (inst->opcode()) {
+      case SpvOpDecorate:
+      case SpvOpDecorateId:
+      case SpvOpMemberDecorate:
+        if (!(keep_linkage && inst->GetSingleWordInOperand(1u) ==
+                                  SpvDecorationLinkageAttributes))
+          inst->ToNop();
+        break;
+      case SpvOpGroupDecorate:
+        for (uint32_t i = 1u; i < inst->NumInOperands(); ++i) {
+          if (inst->GetSingleWordInOperand(i) == inst->result_id()) {
+            // TODO(pierremoreau): This could be optimised by copying the last
+            //                     operand over this one, or using a compacting
+            //                     filtering algorithm over all other IDs
+            inst->RemoveInOperand(i);
+          }
+        }
+        break;
+      case SpvOpGroupMemberDecorate:
+        for (uint32_t i = 1u; i < inst->NumInOperands(); i += 2u) {
+          if (inst->GetSingleWordInOperand(i) == inst->result_id()) {
+            // TODO(pierremoreau): Same optimisation opportunity as above.
+            inst->RemoveInOperand(i);
+          }
+        }
+        break;
+      default:
+        break;
+    }
+  }
+}
+
+std::vector<ir::Instruction*> DecorationManager::GetDecorationsFor(
+    uint32_t id, bool include_linkage) {
+  return InternalGetDecorationsFor<ir::Instruction*>(id, include_linkage);
+}
+
+std::vector<const ir::Instruction*> DecorationManager::GetDecorationsFor(
+    uint32_t id, bool include_linkage) const {
+  return const_cast<DecorationManager*>(this)
+      ->InternalGetDecorationsFor<const ir::Instruction*>(id, include_linkage);
+}
+
+// TODO(pierremoreau): The code will return true for { deco1, deco1 }, { deco1,
+//                     deco2 } when it should return false.
+bool DecorationManager::HaveTheSameDecorations(uint32_t id1,
+                                               uint32_t id2) const {
+  const auto decorationsFor1 = GetDecorationsFor(id1, false);
+  const auto decorationsFor2 = GetDecorationsFor(id2, false);
+  if (decorationsFor1.size() != decorationsFor2.size()) return false;
+
+  for (const ir::Instruction* inst1 : decorationsFor1) {
+    bool didFindAMatch = false;
+    for (const ir::Instruction* inst2 : decorationsFor2) {
+      if (AreDecorationsTheSame(inst1, inst2)) {
+        didFindAMatch = true;
+        break;
+      }
+    }
+    if (!didFindAMatch) return false;
+  }
+  return true;
+}
+
+// TODO(pierremoreau): Handle SpvOpDecorateId by converting them to a regular
+//                     SpvOpDecorate.
+bool DecorationManager::AreDecorationsTheSame(
+    const ir::Instruction* inst1, const ir::Instruction* inst2) const {
+  //  const auto decorateIdToDecorate = [&constants](const Instruction& inst) {
+  //    std::vector<Operand> operands;
+  //    operands.reserve(inst.NumInOperands());
+  //    for (uint32_t i = 2u; i < inst.NumInOperands(); ++i) {
+  //      const auto& j = constants.find(inst.GetSingleWordInOperand(i));
+  //      if (j == constants.end())
+  //        return Instruction();
+  //      const auto operand = j->second->GetOperand(0u);
+  //      operands.emplace_back(operand.type, operand.words);
+  //    }
+  //    return Instruction(SpvOpDecorate, 0u, 0u, operands);
+  //  };
+  //  Instruction tmpA = (deco1.opcode() == SpvOpDecorateId) ?
+  //  decorateIdToDecorate(deco1) : deco1;
+  //  Instruction tmpB = (deco2.opcode() == SpvOpDecorateId) ?
+  //  decorateIdToDecorate(deco2) : deco2;
+  //
+  if (inst1->opcode() == SpvOpDecorateId || inst2->opcode() == SpvOpDecorateId)
+    return false;
+
+  ir::Instruction tmpA = *inst1, tmpB = *inst2;
+  if (tmpA.opcode() != tmpB.opcode() ||
+      tmpA.NumInOperands() != tmpB.NumInOperands() ||
+      tmpA.opcode() == SpvOpNop || tmpB.opcode() == SpvOpNop)
+    return false;
+
+  for (uint32_t i = (tmpA.opcode() == SpvOpDecorate) ? 1u : 2u;
+       i < tmpA.NumInOperands(); ++i)
+    if (tmpA.GetInOperand(i) != tmpB.GetInOperand(i)) return false;
+
+  return true;
+}
+
+void DecorationManager::AnalyzeDecorations(ir::Module* module) {
+  if (!module) return;
+
+  // Collect all group ids.
+  for (const ir::Instruction& inst : module->annotations()) {
+    switch (inst.opcode()) {
+      case SpvOpDecorationGroup:
+        group_to_decoration_insts_.insert({inst.result_id(), {}});
+        break;
+      default:
+        break;
+    }
+  }
+
+  // For each group and instruction, collect all their decoration instructions.
+  for (ir::Instruction& inst : module->annotations()) {
+    switch (inst.opcode()) {
+      case SpvOpDecorate:
+      case SpvOpDecorateId:
+      case SpvOpMemberDecorate: {
+        auto const target_id = inst.GetSingleWordInOperand(0u);
+        auto const group_iter = group_to_decoration_insts_.find(target_id);
+        if (group_iter != group_to_decoration_insts_.end())
+          group_iter->second.push_back(&inst);
+        else
+          id_to_decoration_insts_[target_id].push_back(&inst);
+        break;
+      }
+      case SpvOpGroupDecorate:
+        for (uint32_t i = 1u; i < inst.NumInOperands(); ++i) {
+          auto const target_id = inst.GetSingleWordInOperand(i);
+          auto const group_iter = group_to_decoration_insts_.find(target_id);
+          if (group_iter != group_to_decoration_insts_.end())
+            group_iter->second.push_back(&inst);
+          else
+            id_to_decoration_insts_[target_id].push_back(&inst);
+        }
+        break;
+      case SpvOpGroupMemberDecorate:
+        for (uint32_t i = 1u; i < inst.NumInOperands(); i += 2u) {
+          auto const target_id = inst.GetSingleWordInOperand(i);
+          auto const group_iter = group_to_decoration_insts_.find(target_id);
+          if (group_iter != group_to_decoration_insts_.end())
+            group_iter->second.push_back(&inst);
+          else
+            id_to_decoration_insts_[target_id].push_back(&inst);
+        }
+        break;
+      default:
+        break;
+    }
+  }
+}
+
+template <typename T>
+std::vector<T> DecorationManager::InternalGetDecorationsFor(
+    uint32_t id, bool include_linkage) {
+  std::vector<T> decorations;
+  std::stack<uint32_t> ids_to_process;
+
+  const auto process = [&ids_to_process, &decorations](T inst) {
+    if (inst->opcode() == SpvOpGroupDecorate ||
+        inst->opcode() == SpvOpGroupMemberDecorate)
+      ids_to_process.push(inst->GetSingleWordInOperand(0u));
+    else
+      decorations.push_back(inst);
+  };
+
+  const auto ids_iter = id_to_decoration_insts_.find(id);
+  // |id| has no decorations
+  if (ids_iter == id_to_decoration_insts_.end()) return decorations;
+
+  // Process |id|'s decorations. Some of them might be groups, in which case
+  // add them to the stack.
+  for (ir::Instruction* inst : ids_iter->second) {
+    const bool is_linkage =
+        inst->opcode() == SpvOpDecorate &&
+        inst->GetSingleWordInOperand(1u) == SpvDecorationLinkageAttributes;
+    if (include_linkage || !is_linkage) process(inst);
+  }
+
+  // If the stack is not empty, then it contains groups ID: retrieve their
+  // decorations and process them. If any of those decorations is applying a
+  // group, push that group ID onto the stack.
+  while (!ids_to_process.empty()) {
+    const uint32_t id_to_process = ids_to_process.top();
+    ids_to_process.pop();
+
+    // Retrieve the decorations of that group
+    const auto group_iter = group_to_decoration_insts_.find(id_to_process);
+    if (group_iter != group_to_decoration_insts_.end()) {
+      // Process all the decorations applied by the group.
+      for (T inst : group_iter->second) process(inst);
+    } else {
+      // Something went wrong.
+      assert(false);
+      return std::vector<T>();
+    }
+  }
+
+  return decorations;
+}
+
+void DecorationManager::ForEachDecoration(uint32_t id,
+                                          uint32_t decoration,
+                                          std::function<void(const ir::Instruction&)> f) const {
+  auto decoration_list = id_to_decoration_insts_.find(id);
+  if (decoration_list != id_to_decoration_insts_.end()) {
+    for (const ir::Instruction* inst : decoration_list->second) {
+      switch (inst->opcode()) {
+        case SpvOpDecorate:
+          if (inst->GetSingleWordInOperand(1) == decoration) {
+            f(*inst);
+          }
+          break;
+        case SpvOpMemberDecorate:
+          if (inst->GetSingleWordInOperand(2) == decoration) {
+            f(*inst);
+          }
+          break;
+        case SpvOpDecorateId:
+          if (inst->GetSingleWordInOperand(1) == decoration) {
+            f(*inst);
+          }
+          break;
+        default:
+          assert(false && "Unexpected decoration instruction");
+      }
+    }
+  }
+}
+
+}  // namespace analysis
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/decoration_manager.h b/source/opt/decoration_manager.h
new file mode 100644
index 0000000..3089412
--- /dev/null
+++ b/source/opt/decoration_manager.h
@@ -0,0 +1,83 @@
+// Copyright (c) 2017 Pierre Moreau
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef LIBSPIRV_OPT_DECORATION_MANAGER_H_
+#define LIBSPIRV_OPT_DECORATION_MANAGER_H_
+
+#include <functional>
+#include <unordered_map>
+#include <vector>
+
+#include "instruction.h"
+#include "module.h"
+
+namespace spvtools {
+namespace opt {
+namespace analysis {
+
+// A class for analyzing and managing decorations in an ir::Module.
+class DecorationManager {
+ public:
+  // Constructs a decoration manager from the given |module|
+  DecorationManager(ir::Module* module) { AnalyzeDecorations(module); }
+  // Removes all decorations from |id|, which should not be a group ID, except
+  // for linkage decorations if |keep_linkage| is set.
+  void RemoveDecorationsFrom(uint32_t id, bool keep_linkage);
+  // Returns a vector of all decorations affecting |id|. If a group is applied
+  // to |id|, the decorations of that group are returned rather than the group
+  // decoration instruction. If |include_linkage| is not set, linkage
+  // decorations won't be returned.
+  std::vector<ir::Instruction*> GetDecorationsFor(uint32_t id,
+                                                  bool include_linkage);
+  std::vector<const ir::Instruction*> GetDecorationsFor(
+      uint32_t id, bool include_linkage) const;
+  // Returns whether two IDs have the same decorations. Two SpvOpGroupDecorate
+  // instructions that apply the same decorations but to different IDs, still
+  // count as being the same.
+  bool HaveTheSameDecorations(uint32_t id1, uint32_t id2) const;
+  // Returns whether two decorations are the same. SpvOpDecorateId is currently
+  // not handled and will return false no matter what.
+  bool AreDecorationsTheSame(const ir::Instruction* inst1,
+                             const ir::Instruction* inst2) const;
+
+  // |f| is run on each decoration instruction for |id| with decoration
+  // |decoration|.
+  void ForEachDecoration(uint32_t id, uint32_t decoration,
+                         std::function<void(const ir::Instruction& f)>) const;
+
+ private:
+  using IdToDecorationInstsMap =
+      std::unordered_map<uint32_t, std::vector<ir::Instruction*>>;
+  // Analyzes the defs and uses in the given |module| and populates data
+  // structures in this class. Does nothing if |module| is nullptr.
+  void AnalyzeDecorations(ir::Module* module);
+
+  template <typename T>
+  std::vector<T> InternalGetDecorationsFor(uint32_t id, bool include_linkage);
+
+  // Mapping from ids to the instructions applying a decoration to them. In
+  // other words, for each id you get all decoration instructions referencing
+  // that id, be it directly (SpvOpDecorate, SpvOpMemberDecorate and
+  // SpvOpDecorateId), or indirectly (SpvOpGroupDecorate,
+  // SpvOpMemberGroupDecorate).
+  IdToDecorationInstsMap id_to_decoration_insts_;
+  // Mapping from group ids to all the decoration instructions they apply.
+  IdToDecorationInstsMap group_to_decoration_insts_;
+};
+
+}  // namespace analysis
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // LIBSPIRV_OPT_DECORATION_MANAGER_H_
diff --git a/source/opt/def_use_manager.cpp b/source/opt/def_use_manager.cpp
index a144acd..da4283a 100644
--- a/source/opt/def_use_manager.cpp
+++ b/source/opt/def_use_manager.cpp
@@ -72,6 +72,12 @@
   return iter->second;
 }
 
+const ir::Instruction* DefUseManager::GetDef(uint32_t id) const {
+  const auto iter = id_to_def_.find(id);
+  if (iter == id_to_def_.end()) return nullptr;
+  return iter->second;
+}
+
 UseList* DefUseManager::GetUses(uint32_t id) {
   auto iter = id_to_uses_.find(id);
   if (iter == id_to_uses_.end()) return nullptr;
@@ -171,13 +177,25 @@
 void DefUseManager::EraseUseRecordsOfOperandIds(const ir::Instruction* inst) {
   // Go through all ids used by this instruction, remove this instruction's
   // uses of them.
+  //
+  // We cache end iterators to avoid the cost of repeatedly constructing
+  // and destructing their value.  This cuts runtime on some examples by
+  // a factor of about 3 (e.g. on Windows debug builds, with many thousands
+  // of instructions).
   auto iter = inst_to_used_ids_.find(inst);
   if (iter != inst_to_used_ids_.end()) {
+    // Cache the end iterator on the map.  The end iterator on
+    // an unordered map does not get invalidated when erasing an
+    // element.
+    const auto& id_to_uses_end = id_to_uses_.end();
     for (const auto use_id : iter->second) {
       auto uses_iter = id_to_uses_.find(use_id);
-      if (uses_iter == id_to_uses_.end()) continue;
+      if (uses_iter == id_to_uses_end) continue;
       auto& uses = uses_iter->second;
-      for (auto it = uses.begin(); it != uses.end();) {
+      // Similarly, cache this end iterator.  It is not invalidated
+      // by erasure of an element from the list.
+      const auto& uses_end = uses.end();
+      for (auto it = uses.begin(); it != uses_end;) {
         if (it->inst == inst) {
           it = uses.erase(it);
         } else {
diff --git a/source/opt/def_use_manager.h b/source/opt/def_use_manager.h
index a639cab..e4d8a3e 100644
--- a/source/opt/def_use_manager.h
+++ b/source/opt/def_use_manager.h
@@ -71,6 +71,7 @@
   // Returns the def instruction for the given |id|. If there is no instruction
   // defining |id|, returns nullptr.
   ir::Instruction* GetDef(uint32_t id);
+  const ir::Instruction* GetDef(uint32_t id) const;
   // Returns the use instructions for the given |id|. If there is no uses of
   // |id|, returns nullptr.
   UseList* GetUses(uint32_t id);
diff --git a/source/opt/eliminate_dead_constant_pass.cpp b/source/opt/eliminate_dead_constant_pass.cpp
index 9d7a148..a36c29a 100644
--- a/source/opt/eliminate_dead_constant_pass.cpp
+++ b/source/opt/eliminate_dead_constant_pass.cpp
@@ -40,7 +40,9 @@
       count =
           std::count_if(uses->begin(), uses->end(), [](const analysis::Use& u) {
             return !(ir::IsAnnotationInst(u.inst->opcode()) ||
-                     ir::IsDebugInst(u.inst->opcode()));
+                     ir::IsDebug1Inst(u.inst->opcode()) ||
+                     ir::IsDebug2Inst(u.inst->opcode()) ||
+                     ir::IsDebug3Inst(u.inst->opcode()));
           });
     }
     use_counts[c] = count;
@@ -95,7 +97,9 @@
     if (analysis::UseList* uses = def_use.GetUses(dc->result_id())) {
       for (const auto& u : *uses) {
         if (ir::IsAnnotationInst(u.inst->opcode()) ||
-            ir::IsDebugInst(u.inst->opcode())) {
+            ir::IsDebug1Inst(u.inst->opcode()) ||
+            ir::IsDebug2Inst(u.inst->opcode()) ||
+            ir::IsDebug3Inst(u.inst->opcode())) {
           dead_others.insert(u.inst);
         }
       }
diff --git a/source/opt/eliminate_dead_functions_pass.cpp b/source/opt/eliminate_dead_functions_pass.cpp
new file mode 100644
index 0000000..037dbd0
--- /dev/null
+++ b/source/opt/eliminate_dead_functions_pass.cpp
@@ -0,0 +1,61 @@
+// Copyright (c) 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "eliminate_dead_functions_pass.h"
+
+#include <unordered_set>
+
+namespace spvtools {
+namespace opt {
+
+Pass::Status EliminateDeadFunctionsPass::Process(ir::Module* module) {
+  bool modified = false;
+  module_ = module;
+
+  // Identify live functions first.  Those that are not live
+  // are dead.
+  std::unordered_set<const ir::Function*> live_function_set;
+  ProcessFunction mark_live = [&live_function_set](ir::Function* fp) {
+    live_function_set.insert(fp);
+    return false;
+  };
+  ProcessReachableCallTree(mark_live, module);
+
+  def_use_mgr_.reset(new analysis::DefUseManager(consumer(), module));
+  FindNamedOrDecoratedIds();
+  for (auto funcIter = module->begin(); funcIter != module->end();) {
+    if (live_function_set.count(&*funcIter) == 0) {
+      modified = true;
+      EliminateFunction(&*funcIter);
+      funcIter = funcIter.Erase();
+    } else {
+      ++funcIter;
+    }
+  }
+
+  return modified ? Pass::Status::SuccessWithChange
+                  : Pass::Status::SuccessWithoutChange;
+}
+
+void EliminateDeadFunctionsPass::EliminateFunction(ir::Function* func) {
+  // Remove all of the instruction in the function body
+  func->ForEachInst(
+      [this](ir::Instruction* inst) {
+        KillNamesAndDecorates(inst);
+        def_use_mgr_->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
new file mode 100644
index 0000000..a7d0742
--- /dev/null
+++ b/source/opt/eliminate_dead_functions_pass.h
@@ -0,0 +1,39 @@
+// Copyright (c) 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef LIBSPIRV_OPT_ELIMINATE_DEAD_FUNCTIONS_PASS_H_
+#define LIBSPIRV_OPT_ELIMINATE_DEAD_FUNCTIONS_PASS_H_
+
+#include "def_use_manager.h"
+#include "function.h"
+#include "mem_pass.h"
+#include "module.h"
+
+namespace spvtools {
+namespace opt {
+
+// See optimizer.hpp for documentation.
+class EliminateDeadFunctionsPass : public MemPass {
+ public:
+  const char* name() const override { return "eliminate-dead-functions"; }
+  Status Process(ir::Module*) override;
+
+ private:
+  void EliminateFunction(ir::Function* func);
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // LIBSPIRV_OPT_ELIMINATE_DEAD_FUNCTIONS_PASS_H_
diff --git a/source/opt/flatten_decoration_pass.cpp b/source/opt/flatten_decoration_pass.cpp
index 98bb69c..20437e3 100644
--- a/source/opt/flatten_decoration_pass.cpp
+++ b/source/opt/flatten_decoration_pass.cpp
@@ -143,8 +143,8 @@
   // An OpDecorationGroup instruction might not have been used by an
   // OpGroupDecorate or OpGroupMemberDecorate instruction.
   if (!group_ids.empty()) {
-    for (auto debug_inst_iter = module->debug_begin();
-         debug_inst_iter != module->debug_end();) {
+    for (auto debug_inst_iter = module->debug2_begin();
+         debug_inst_iter != module->debug2_end();) {
       if (debug_inst_iter->opcode() == SpvOp::SpvOpName) {
         const uint32_t target = debug_inst_iter->GetSingleWordOperand(0);
         if (group_ids.count(target)) {
diff --git a/source/opt/fold.cpp b/source/opt/fold.cpp
new file mode 100644
index 0000000..005cb76
--- /dev/null
+++ b/source/opt/fold.cpp
@@ -0,0 +1,244 @@
+// Copyright (c) 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "fold.h"
+#include "def_use_manager.h"
+
+#include <cassert>
+#include <vector>
+
+namespace spvtools {
+namespace opt {
+
+namespace {
+
+// Returns the single-word result from performing the given unary operation on
+// the operand value which is passed in as a 32-bit word.
+uint32_t UnaryOperate(SpvOp opcode, uint32_t operand) {
+  switch (opcode) {
+    // Arthimetics
+    case SpvOp::SpvOpSNegate:
+      return -static_cast<int32_t>(operand);
+    case SpvOp::SpvOpNot:
+      return ~operand;
+    case SpvOp::SpvOpLogicalNot:
+      return !static_cast<bool>(operand);
+    default:
+      assert(false &&
+             "Unsupported unary operation for OpSpecConstantOp instruction");
+      return 0u;
+  }
+}
+
+// Returns the single-word result from performing the given binary operation on
+// the operand values which are passed in as two 32-bit word.
+uint32_t BinaryOperate(SpvOp opcode, uint32_t a, uint32_t b) {
+  switch (opcode) {
+    // Arthimetics
+    case SpvOp::SpvOpIAdd:
+      return a + b;
+    case SpvOp::SpvOpISub:
+      return a - b;
+    case SpvOp::SpvOpIMul:
+      return a * b;
+    case SpvOp::SpvOpUDiv:
+      assert(b != 0);
+      return a / b;
+    case SpvOp::SpvOpSDiv:
+      assert(b != 0u);
+      return (static_cast<int32_t>(a)) / (static_cast<int32_t>(b));
+    case SpvOp::SpvOpSRem: {
+      // The sign of non-zero result comes from the first operand: a. This is
+      // guaranteed by C++11 rules for integer division operator. The division
+      // result is rounded toward zero, so the result of '%' has the sign of
+      // the first operand.
+      assert(b != 0u);
+      return static_cast<int32_t>(a) % static_cast<int32_t>(b);
+    }
+    case SpvOp::SpvOpSMod: {
+      // The sign of non-zero result comes from the second operand: b
+      assert(b != 0u);
+      int32_t rem = BinaryOperate(SpvOp::SpvOpSRem, a, b);
+      int32_t b_prim = static_cast<int32_t>(b);
+      return (rem + b_prim) % b_prim;
+    }
+    case SpvOp::SpvOpUMod:
+      assert(b != 0u);
+      return (a % b);
+
+    // Shifting
+    case SpvOp::SpvOpShiftRightLogical: {
+      return a >> b;
+    }
+    case SpvOp::SpvOpShiftRightArithmetic:
+      return (static_cast<int32_t>(a)) >> b;
+    case SpvOp::SpvOpShiftLeftLogical:
+      return a << b;
+
+    // Bitwise operations
+    case SpvOp::SpvOpBitwiseOr:
+      return a | b;
+    case SpvOp::SpvOpBitwiseAnd:
+      return a & b;
+    case SpvOp::SpvOpBitwiseXor:
+      return a ^ b;
+
+    // Logical
+    case SpvOp::SpvOpLogicalEqual:
+      return (static_cast<bool>(a)) == (static_cast<bool>(b));
+    case SpvOp::SpvOpLogicalNotEqual:
+      return (static_cast<bool>(a)) != (static_cast<bool>(b));
+    case SpvOp::SpvOpLogicalOr:
+      return (static_cast<bool>(a)) || (static_cast<bool>(b));
+    case SpvOp::SpvOpLogicalAnd:
+      return (static_cast<bool>(a)) && (static_cast<bool>(b));
+
+    // Comparison
+    case SpvOp::SpvOpIEqual:
+      return a == b;
+    case SpvOp::SpvOpINotEqual:
+      return a != b;
+    case SpvOp::SpvOpULessThan:
+      return a < b;
+    case SpvOp::SpvOpSLessThan:
+      return (static_cast<int32_t>(a)) < (static_cast<int32_t>(b));
+    case SpvOp::SpvOpUGreaterThan:
+      return a > b;
+    case SpvOp::SpvOpSGreaterThan:
+      return (static_cast<int32_t>(a)) > (static_cast<int32_t>(b));
+    case SpvOp::SpvOpULessThanEqual:
+      return a <= b;
+    case SpvOp::SpvOpSLessThanEqual:
+      return (static_cast<int32_t>(a)) <= (static_cast<int32_t>(b));
+    case SpvOp::SpvOpUGreaterThanEqual:
+      return a >= b;
+    case SpvOp::SpvOpSGreaterThanEqual:
+      return (static_cast<int32_t>(a)) >= (static_cast<int32_t>(b));
+    default:
+      assert(false &&
+             "Unsupported binary operation for OpSpecConstantOp instruction");
+      return 0u;
+  }
+}
+
+// Returns the single-word result from performing the given ternary operation
+// on the operand values which are passed in as three 32-bit word.
+uint32_t TernaryOperate(SpvOp opcode, uint32_t a, uint32_t b, uint32_t c) {
+  switch (opcode) {
+    case SpvOp::SpvOpSelect:
+      return (static_cast<bool>(a)) ? b : c;
+    default:
+      assert(false &&
+             "Unsupported ternary operation for OpSpecConstantOp instruction");
+      return 0u;
+  }
+}
+
+// Returns the single-word result from performing the given operation on the
+// operand words. This only works with 32-bit operations and uses boolean
+// convention that 0u is false, and anything else is boolean true.
+// TODO(qining): Support operands other than 32-bit wide.
+uint32_t OperateWords(SpvOp opcode,
+                      const std::vector<uint32_t>& operand_words) {
+  switch (operand_words.size()) {
+    case 1:
+      return UnaryOperate(opcode, operand_words.front());
+    case 2:
+      return BinaryOperate(opcode, operand_words.front(), operand_words.back());
+    case 3:
+      return TernaryOperate(opcode, operand_words[0], operand_words[1],
+                            operand_words[2]);
+    default:
+      assert(false && "Invalid number of operands");
+      return 0;
+  }
+}
+
+}  // namespace
+
+// Returns the result of performing an operation on scalar constant operands.
+// This function extracts the operand values as 32 bit words and returns the
+// result in 32 bit word. Scalar constants with longer than 32-bit width are
+// not accepted in this function.
+uint32_t FoldScalars(SpvOp opcode,
+                     const std::vector<analysis::Constant*>& operands) {
+  std::vector<uint32_t> operand_values_in_raw_words;
+  for (analysis::Constant* operand : operands) {
+    if (analysis::ScalarConstant* scalar = operand->AsScalarConstant()) {
+      const auto& scalar_words = scalar->words();
+      assert(scalar_words.size() == 1 &&
+             "Scalar constants with longer than 32-bit width are not allowed "
+             "in FoldScalars()");
+      operand_values_in_raw_words.push_back(scalar_words.front());
+    } else if (operand->AsNullConstant()) {
+      operand_values_in_raw_words.push_back(0u);
+    } else {
+      assert(false &&
+             "FoldScalars() only accepts ScalarConst or NullConst type of "
+             "constant");
+    }
+  }
+  return OperateWords(opcode, operand_values_in_raw_words);
+}
+
+// Returns the result of performing an operation over constant vectors. This
+// function iterates through the given vector type constant operands and
+// calculates the result for each element of the result vector to return.
+// Vectors with longer than 32-bit scalar components are not accepted in this
+// function.
+std::vector<uint32_t> FoldVectors(
+    SpvOp opcode, uint32_t num_dims,
+    const std::vector<analysis::Constant*>& operands) {
+  std::vector<uint32_t> result;
+  for (uint32_t d = 0; d < num_dims; d++) {
+    std::vector<uint32_t> operand_values_for_one_dimension;
+    for (analysis::Constant* operand : operands) {
+      if (analysis::VectorConstant* vector_operand =
+              operand->AsVectorConstant()) {
+        // Extract the raw value of the scalar component constants
+        // in 32-bit words here. The reason of not using FoldScalars() here
+        // is that we do not create temporary null constants as components
+        // when the vector operand is a NullConstant because Constant creation
+        // may need extra checks for the validity and that is not manageed in
+        // here.
+        if (const analysis::ScalarConstant* scalar_component =
+                vector_operand->GetComponents().at(d)->AsScalarConstant()) {
+          const auto& scalar_words = scalar_component->words();
+          assert(
+              scalar_words.size() == 1 &&
+              "Vector components with longer than 32-bit width are not allowed "
+              "in FoldVectors()");
+          operand_values_for_one_dimension.push_back(scalar_words.front());
+        } else if (operand->AsNullConstant()) {
+          operand_values_for_one_dimension.push_back(0u);
+        } else {
+          assert(false &&
+                 "VectorConst should only has ScalarConst or NullConst as "
+                 "components");
+        }
+      } else if (operand->AsNullConstant()) {
+        operand_values_for_one_dimension.push_back(0u);
+      } else {
+        assert(false &&
+               "FoldVectors() only accepts VectorConst or NullConst type of "
+               "constant");
+      }
+    }
+    result.push_back(OperateWords(opcode, operand_values_for_one_dimension));
+  }
+  return result;
+}
+
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/fold.h b/source/opt/fold.h
new file mode 100644
index 0000000..c6e61d6
--- /dev/null
+++ b/source/opt/fold.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef LIBSPIRV_UTIL_FOLD_H_
+#define LIBSPIRV_UTIL_FOLD_H_
+
+#include "def_use_manager.h"
+#include "constants.h"
+
+#include <cstdint>
+#include <vector>
+
+namespace spvtools {
+namespace opt {
+
+uint32_t FoldScalars(SpvOp opcode,
+                     const std::vector<analysis::Constant*>& operands);
+
+std::vector<uint32_t> FoldVectors(
+    SpvOp opcode, uint32_t num_dims,
+    const std::vector<analysis::Constant*>& operands);
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // LIBSPIRV_UTIL_FOLD_H_
diff --git a/source/opt/function.cpp b/source/opt/function.cpp
index 7f7952c..4ad2dce 100644
--- a/source/opt/function.cpp
+++ b/source/opt/function.cpp
@@ -14,9 +14,34 @@
 
 #include "function.h"
 
+#include "make_unique.h"
+
 namespace spvtools {
 namespace ir {
 
+Function::Function(const Function& f)
+    : module_(nullptr),
+      def_inst_(MakeUnique<Instruction>(f.DefInst())),
+      params_(),
+      blocks_(),
+      end_inst_() {
+  params_.reserve(f.params_.size());
+  f.ForEachParam(
+      [this](const Instruction* insn) {
+        AddParameter(MakeUnique<Instruction>(*insn));
+      },
+      true);
+
+  blocks_.reserve(f.blocks_.size());
+  for (const auto& b : f.blocks_) {
+    std::unique_ptr<BasicBlock> bb = MakeUnique<BasicBlock>(*b);
+    bb->SetParent(this);
+    AddBasicBlock(std::move(bb));
+  }
+
+  SetFunctionEnd(MakeUnique<Instruction>(f.function_end()));
+}
+
 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);
@@ -36,8 +61,8 @@
         ->ForEachInst(f, run_on_debug_line_insts);
 
   for (const auto& bb : blocks_)
-    static_cast<const BasicBlock*>(bb.get())
-        ->ForEachInst(f, run_on_debug_line_insts);
+    static_cast<const BasicBlock*>(bb.get())->ForEachInst(
+        f, run_on_debug_line_insts);
 
   if (end_inst_)
     static_cast<const Instruction*>(end_inst_.get())
diff --git a/source/opt/function.h b/source/opt/function.h
index 949f99a..a67c329 100644
--- a/source/opt/function.h
+++ b/source/opt/function.h
@@ -38,8 +38,14 @@
   // Creates a function instance declared by the given OpFunction instruction
   // |def_inst|.
   inline explicit Function(std::unique_ptr<Instruction> def_inst);
+  // Creates a function instance based on the given function |f|.
+  //
+  // The parent module will default to null and needs to be explicitly set by
+  // the user.
+  explicit Function(const Function& f);
   // The OpFunction instruction that begins the definition of this function.
   Instruction& DefInst() { return *def_inst_; }
+  const Instruction& DefInst() const { return *def_inst_; }
 
   // Sets the enclosing module for this function.
   void SetParent(Module* module) { module_ = module; }
@@ -51,12 +57,22 @@
   // Saves the given function end instruction.
   inline void SetFunctionEnd(std::unique_ptr<Instruction> end_inst);
 
+  // Returns the given function end instruction.
+  inline Instruction* function_end() { return end_inst_.get(); }
+  inline const Instruction& function_end() const { return *end_inst_; }
+
   // Returns function's id
   inline uint32_t result_id() const { return def_inst_->result_id(); }
 
-  // Returns function's type id
+//  // Returns function's type id
+//  inline uint32_t type_id() const { return def_inst_->GetSingleWordInOperand(1u); }
+
+  // Returns function's return type id
   inline uint32_t type_id() const { return def_inst_->type_id(); }
 
+  // Returns the entry basic block for this function.
+  const std::unique_ptr<BasicBlock>& entry() const { return blocks_.front(); }
+
   iterator begin() { return iterator(&blocks_, blocks_.begin()); }
   iterator end() { return iterator(&blocks_, blocks_.end()); }
   const_iterator cbegin() const {
diff --git a/source/opt/inline_exhaustive_pass.cpp b/source/opt/inline_exhaustive_pass.cpp
index 3c894bc..7ef5c82 100644
--- a/source/opt/inline_exhaustive_pass.cpp
+++ b/source/opt/inline_exhaustive_pass.cpp
@@ -37,7 +37,7 @@
         bi = bi.Erase();
         bi = bi.InsertBefore(&newBlocks);
         // Insert new function variables.
-        if (newVars.size() > 0) func->begin()->begin().InsertBefore(&newVars);
+        if (newVars.size() > 0) func->begin()->begin().InsertBefore(std::move(newVars));
         // Restart inlining at beginning of calling block.
         ii = bi->begin();
         modified = true;
diff --git a/source/opt/inline_exhaustive_pass.h b/source/opt/inline_exhaustive_pass.h
index 71e0902..e4773d8 100644
--- a/source/opt/inline_exhaustive_pass.h
+++ b/source/opt/inline_exhaustive_pass.h
@@ -37,7 +37,7 @@
   InlineExhaustivePass();
   Status Process(ir::Module*) override;
 
-  const char* name() const override { return "inline-exhaustive"; }
+  const char* name() const override { return "inline-entry-points-exhaustive"; }
 
  private:
   // Exhaustively inline all function calls in func as well as in
diff --git a/source/opt/inline_opaque_pass.cpp b/source/opt/inline_opaque_pass.cpp
index 214e812..8ab5645 100644
--- a/source/opt/inline_opaque_pass.cpp
+++ b/source/opt/inline_opaque_pass.cpp
@@ -85,7 +85,7 @@
         bi = bi.Erase();
         bi = bi.InsertBefore(&newBlocks);
         // Insert new function variables.
-        if (newVars.size() > 0) func->begin()->begin().InsertBefore(&newVars);
+        if (newVars.size() > 0) func->begin()->begin().InsertBefore(std::move(newVars));
         // Restart inlining at beginning of calling block.
         ii = bi->begin();
         modified = true;
diff --git a/source/opt/inline_opaque_pass.h b/source/opt/inline_opaque_pass.h
index d174209..e166617 100644
--- a/source/opt/inline_opaque_pass.h
+++ b/source/opt/inline_opaque_pass.h
@@ -37,7 +37,7 @@
   InlineOpaquePass();
   Status Process(ir::Module*) override;
 
-  const char* name() const override { return "inline-opaque"; }
+  const char* name() const override { return "inline-entry-points-opaque"; }
 
  private:
   // Return true if |typeId| is or contains opaque type
diff --git a/source/opt/inline_pass.cpp b/source/opt/inline_pass.cpp
index 061f298..926c980 100644
--- a/source/opt/inline_pass.cpp
+++ b/source/opt/inline_pass.cpp
@@ -126,7 +126,7 @@
 
 void InlinePass::MapParams(
     ir::Function* calleeFn,
-    ir::UptrVectorIterator<ir::Instruction> call_inst_itr,
+    ir::BasicBlock::iterator call_inst_itr,
     std::unordered_map<uint32_t, uint32_t>* callee2caller) {
   int param_idx = 0;
   calleeFn->ForEachParam(
@@ -145,8 +145,7 @@
   auto callee_block_itr = calleeFn->begin();
   auto callee_var_itr = callee_block_itr->begin();
   while (callee_var_itr->opcode() == SpvOp::SpvOpVariable) {
-    std::unique_ptr<ir::Instruction> var_inst(
-        new ir::Instruction(*callee_var_itr));
+    std::unique_ptr<ir::Instruction> var_inst(callee_var_itr->Clone());
     uint32_t newId = TakeNextId();
     var_inst->SetResultId(newId);
     (*callee2caller)[callee_var_itr->result_id()] = newId;
@@ -196,8 +195,7 @@
           if (mapItr2 != (*preCallSB).end()) {
             // Clone pre-call same-block ops, map result id.
             const ir::Instruction* inInst = mapItr2->second;
-            std::unique_ptr<ir::Instruction> sb_inst(
-                new ir::Instruction(*inInst));
+            std::unique_ptr<ir::Instruction> sb_inst(inInst->Clone());
             CloneSameBlockOps(&sb_inst, postCallSB, preCallSB, block_ptr);
             const uint32_t rid = sb_inst->result_id();
             const uint32_t nid = this->TakeNextId();
@@ -216,7 +214,7 @@
 void InlinePass::GenInlineCode(
     std::vector<std::unique_ptr<ir::BasicBlock>>* new_blocks,
     std::vector<std::unique_ptr<ir::Instruction>>* new_vars,
-    ir::UptrVectorIterator<ir::Instruction> call_inst_itr,
+    ir::BasicBlock::iterator call_inst_itr,
     ir::UptrVectorIterator<ir::BasicBlock> call_block_itr) {
   // Map from all ids in the callee to their equivalent id in the caller
   // as callee instructions are copied into caller.
@@ -259,7 +257,9 @@
   // single block loop.  We'll wait to move the OpLoopMerge until the end
   // of the regular inlining logic, and only if necessary.
   bool caller_is_single_block_loop = false;
+  bool caller_is_loop_header = false;
   if (auto* loop_merge = call_block_itr->GetLoopMergeInst()) {
+    caller_is_loop_header = true;
     caller_is_single_block_loop =
         call_block_itr->id() ==
         loop_merge->GetSingleWordInOperand(kSpvLoopMergeContinueTargetIdInIdx);
@@ -283,8 +283,7 @@
   std::unique_ptr<ir::BasicBlock> new_blk_ptr;
   calleeFn->ForEachInst([&new_blocks, &callee2caller, &call_block_itr,
                          &call_inst_itr, &new_blk_ptr, &prevInstWasReturn,
-                         &returnLabelId, &returnVarId,
-                         caller_is_single_block_loop,
+                         &returnLabelId, &returnVarId, caller_is_loop_header,
                          callee_begins_with_structured_header, &calleeTypeId,
                          &multiBlocks, &postCallSB, &preCallSB, multiReturn,
                          &singleTripLoopHeaderId, &singleTripLoopContinueId,
@@ -327,7 +326,7 @@
           // Copy contents of original caller block up to call instruction.
           for (auto cii = call_block_itr->begin(); cii != call_inst_itr;
                ++cii) {
-            std::unique_ptr<ir::Instruction> cp_inst(new ir::Instruction(*cii));
+            std::unique_ptr<ir::Instruction> cp_inst(cii->Clone());
             // Remember same-block ops for possible regeneration.
             if (IsSameBlockOp(&*cp_inst)) {
               auto* sb_inst_ptr = cp_inst.get();
@@ -335,7 +334,7 @@
             }
             new_blk_ptr->AddInstruction(std::move(cp_inst));
           }
-          if (caller_is_single_block_loop &&
+          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.
@@ -437,7 +436,7 @@
         // Copy remaining instructions from caller block.
         auto cii = call_inst_itr;
         for (++cii; cii != call_block_itr->end(); ++cii) {
-          std::unique_ptr<ir::Instruction> cp_inst(new ir::Instruction(*cii));
+          std::unique_ptr<ir::Instruction> cp_inst(cii->Clone());
           // If multiple blocks generated, regenerate any same-block
           // instruction that has not been seen in this last block.
           if (multiBlocks) {
@@ -455,7 +454,7 @@
       } break;
       default: {
         // Copy callee instruction and remap all input Ids.
-        std::unique_ptr<ir::Instruction> cp_inst(new ir::Instruction(*cpi));
+        std::unique_ptr<ir::Instruction> cp_inst(cpi->Clone());
         cp_inst->ForEachInId([&callee2caller, &callee_result_ids,
                               this](uint32_t* iid) {
           const auto mapItr = callee2caller.find(*iid);
@@ -489,10 +488,9 @@
     }
   });
 
-  if (caller_is_single_block_loop && (new_blocks->size() > 1)) {
+  if (caller_is_loop_header && (new_blocks->size() > 1)) {
     // Move the OpLoopMerge from the last block back to the first, where
-    // it belongs.  Also, update its continue target to point to the last
-    // block.
+    // it belongs.
     auto& first = new_blocks->front();
     auto& last = new_blocks->back();
     assert(first != last);
@@ -501,12 +499,16 @@
     auto loop_merge_itr = last->tail();
     --loop_merge_itr;
     assert(loop_merge_itr->opcode() == SpvOpLoopMerge);
-    std::unique_ptr<ir::Instruction> cp_inst(new ir::Instruction(*loop_merge_itr));
-    cp_inst->SetInOperand(kSpvLoopMergeContinueTargetIdInIdx, {last->id()});
+    std::unique_ptr<ir::Instruction> cp_inst(loop_merge_itr->Clone());
+    if (caller_is_single_block_loop) {
+      // Also, update its continue target to point to the last block.
+      cp_inst->SetInOperand(kSpvLoopMergeContinueTargetIdInIdx, {last->id()});
+    }
     first->tail().InsertBefore(std::move(cp_inst));
 
     // Remove the loop merge from the last block.
-    loop_merge_itr.Erase();
+    loop_merge_itr->RemoveFromList();
+    delete &*loop_merge_itr;
   }
 
   // Update block map given replacement blocks.
diff --git a/source/opt/inline_pass.h b/source/opt/inline_pass.h
index 3f9136f..1e3869d 100644
--- a/source/opt/inline_pass.h
+++ b/source/opt/inline_pass.h
@@ -86,7 +86,7 @@
 
   // Map callee params to caller args
   void MapParams(ir::Function* calleeFn,
-                 ir::UptrVectorIterator<ir::Instruction> call_inst_itr,
+                 ir::BasicBlock::iterator call_inst_itr,
                  std::unordered_map<uint32_t, uint32_t>* callee2caller);
 
   // Clone and map callee locals
@@ -131,7 +131,7 @@
   // call_block_itr is replaced with new_blocks.
   void GenInlineCode(std::vector<std::unique_ptr<ir::BasicBlock>>* new_blocks,
                      std::vector<std::unique_ptr<ir::Instruction>>* new_vars,
-                     ir::UptrVectorIterator<ir::Instruction> call_inst_itr,
+                     ir::BasicBlock::iterator call_inst_itr,
                      ir::UptrVectorIterator<ir::BasicBlock> call_block_itr);
 
   // Return true if |inst| is a function call that can be inlined.
diff --git a/source/opt/insert_extract_elim.cpp b/source/opt/insert_extract_elim.cpp
index c543644..4ccaac7 100644
--- a/source/opt/insert_extract_elim.cpp
+++ b/source/opt/insert_extract_elim.cpp
@@ -55,6 +55,11 @@
   return true;
 }
 
+bool InsertExtractElimPass::IsVectorType(uint32_t typeId) {
+  ir::Instruction* typeInst = def_use_mgr_->GetDef(typeId);
+  return typeInst->opcode() == SpvOpTypeVector;
+}
+
 bool InsertExtractElimPass::EliminateInsertExtract(ir::Function* func) {
   bool modified = false;
   for (auto bi = func->begin(); bi != func->end(); ++bi) {
@@ -74,6 +79,33 @@
             cid = cinst->GetSingleWordInOperand(kInsertCompositeIdInIdx);
             cinst = def_use_mgr_->GetDef(cid);
           }
+          // If search ended with CompositeConstruct or ConstantComposite
+          // and the extract has one index, return the appropriate component.
+          // If a vector CompositeConstruct we make sure all preceding
+          // components are of component type (not vector composition).
+          // TODO(greg-lunarg): Handle multiple-indices, ConstantNull, special
+          // vector composition, and additional CompositeInsert.
+          if ((cinst->opcode() == SpvOpCompositeConstruct ||
+               cinst->opcode() == SpvOpConstantComposite) &&
+              (*ii).NumInOperands() == 2) {
+            uint32_t compIdx = (*ii).GetSingleWordInOperand(1);
+            if (IsVectorType(cinst->type_id())) {
+              if (compIdx < cinst->NumInOperands()) {
+                uint32_t i = 0;
+                for (; i <= compIdx; i++) {
+                  uint32_t compId = cinst->GetSingleWordInOperand(i);
+                  ir::Instruction* compInst = def_use_mgr_->GetDef(compId);
+                  if (compInst->type_id() != (*ii).type_id())
+                    break;
+                }
+                if (i > compIdx)
+                  replId = cinst->GetSingleWordInOperand(compIdx);
+              }
+            }
+            else {
+              replId = cinst->GetSingleWordInOperand(compIdx);
+            }
+          }
           if (replId != 0) {
             const uint32_t extId = ii->result_id();
             (void)def_use_mgr_->ReplaceAllUsesWith(extId, replId);
diff --git a/source/opt/insert_extract_elim.h b/source/opt/insert_extract_elim.h
index 52213db..4f7509c 100644
--- a/source/opt/insert_extract_elim.h
+++ b/source/opt/insert_extract_elim.h
@@ -36,7 +36,7 @@
 class InsertExtractElimPass : public Pass {
  public:
   InsertExtractElimPass();
-  const char* name() const override { return "insert_extract_elim"; }
+  const char* name() const override { return "eliminate-insert-extract"; }
   Status Process(ir::Module*) override;
 
  private:
@@ -52,6 +52,9 @@
   bool ExtInsConflict(
     const ir::Instruction* extInst, const ir::Instruction* insInst) const;
 
+  // Return true if |typeId| is a vector type
+  bool IsVectorType(uint32_t typeId);
+
   // Look for OpExtract on sequence of OpInserts in |func|. If there is an
   // insert with identical indices, replace the extract with the value
   // that is inserted if possible. Specifically, replace if there is no
diff --git a/source/opt/instruction.cpp b/source/opt/instruction.cpp
index bccac12..78906c7 100644
--- a/source/opt/instruction.cpp
+++ b/source/opt/instruction.cpp
@@ -40,7 +40,11 @@
 
 Instruction::Instruction(SpvOp op, uint32_t ty_id, uint32_t res_id,
                          const std::vector<Operand>& in_operands)
-    : opcode_(op), type_id_(ty_id), result_id_(res_id), operands_() {
+    : utils::IntrusiveNodeBase<Instruction>(),
+      opcode_(op),
+      type_id_(ty_id),
+      result_id_(res_id),
+      operands_() {
   if (type_id_ != 0) {
     operands_.emplace_back(spv_operand_type_t::SPV_OPERAND_TYPE_TYPE_ID,
                            std::initializer_list<uint32_t>{type_id_});
@@ -53,7 +57,8 @@
 }
 
 Instruction::Instruction(Instruction&& that)
-    : opcode_(that.opcode_),
+    : utils::IntrusiveNodeBase<Instruction>(),
+      opcode_(that.opcode_),
       type_id_(that.type_id_),
       result_id_(that.result_id_),
       operands_(std::move(that.operands_)),
@@ -68,6 +73,16 @@
   return *this;
 }
 
+Instruction* Instruction::Clone() const {
+  Instruction* clone = new Instruction();
+  clone->opcode_ = opcode_;
+  clone->type_id_ = type_id_;
+  clone->result_id_ = result_id_;
+  clone->operands_ = operands_;
+  clone->dbg_line_insts_ = dbg_line_insts_;
+  return clone;
+}
+
 uint32_t Instruction::GetSingleWordOperand(uint32_t index) const {
   const auto& words = GetOperand(index).words;
   assert(words.size() == 1 && "expected the operand only taking one word");
@@ -89,5 +104,11 @@
     binary->insert(binary->end(), operand.words.begin(), operand.words.end());
 }
 
+void Instruction::ReplaceOperands(const std::vector<Operand>& new_operands) {
+  operands_.clear();
+  operands_.insert(operands_.begin(), new_operands.begin(), new_operands.end());
+  operands_.shrink_to_fit();
+}
+
 }  // namespace ir
 }  // namespace spvtools
diff --git a/source/opt/instruction.h b/source/opt/instruction.h
index 89c9da0..95ac0d5 100644
--- a/source/opt/instruction.h
+++ b/source/opt/instruction.h
@@ -21,6 +21,7 @@
 #include <vector>
 
 #include "operand.h"
+#include "util/ilist_node.h"
 
 #include "spirv-tools/libspirv.h"
 #include "spirv/1.2/spirv.h"
@@ -30,13 +31,14 @@
 
 class Function;
 class Module;
+class InstructionList;
 
 // About operand:
 //
 // In the SPIR-V specification, the term "operand" is used to mean any single
 // SPIR-V word following the leading wordcount-opcode word. Here, the term
 // "operand" is used to mean a *logical* operand. A logical operand may consist
-// of mulitple SPIR-V words, which together make up the same component. For
+// of multiple SPIR-V words, which together make up the same component. For
 // example, a logical operand of a 64-bit integer needs two words to express.
 //
 // Further, we categorize logical operands into *in* and *out* operands.
@@ -58,25 +60,41 @@
   spv_operand_type_t type;      // Type of this logical operand.
   std::vector<uint32_t> words;  // Binary segments of this logical operand.
 
+  friend bool operator==(const Operand& o1, const Operand& o2) {
+    return o1.type == o2.type && o1.words == o2.words;
+  }
+
   // TODO(antiagainst): create fields for literal number kind, width, etc.
 };
 
+inline bool operator!=(const Operand& o1, const Operand& o2) {
+  return !(o1 == o2);
+}
+
 // A SPIR-V instruction. It contains the opcode and any additional logical
 // operand, including the result id (if any) and result type id (if any). It
 // may also contain line-related debug instruction (OpLine, OpNoLine) directly
 // appearing before this instruction. Note that the result id of an instruction
 // should never change after the instruction being built. If the result id
 // needs to change, the user should create a new instruction instead.
-class Instruction {
+class Instruction : public utils::IntrusiveNodeBase<Instruction> {
  public:
   using iterator = std::vector<Operand>::iterator;
   using const_iterator = std::vector<Operand>::const_iterator;
 
   // Creates a default OpNop instruction.
-  Instruction() : opcode_(SpvOpNop), type_id_(0), result_id_(0) {}
+  Instruction()
+      : utils::IntrusiveNodeBase<Instruction>(),
+        opcode_(SpvOpNop),
+        type_id_(0),
+        result_id_(0) {}
   // Creates an instruction with the given opcode |op| and no additional logical
   // operands.
-  Instruction(SpvOp op) : opcode_(op), type_id_(0), result_id_(0) {}
+  Instruction(SpvOp op)
+      : utils::IntrusiveNodeBase<Instruction>(),
+        opcode_(op),
+        type_id_(0),
+        result_id_(0) {}
   // Creates an instruction using the given spv_parsed_instruction_t |inst|. All
   // the data inside |inst| will be copied and owned in this instance. And keep
   // record of line-related debug instructions |dbg_line| ahead of this
@@ -89,12 +107,23 @@
   Instruction(SpvOp op, uint32_t ty_id, uint32_t res_id,
               const std::vector<Operand>& in_operands);
 
+  // TODO: I will want to remove these, but will first have to remove the use of
+  // std::vector<Instruction>.
   Instruction(const Instruction&) = default;
   Instruction& operator=(const Instruction&) = default;
 
   Instruction(Instruction&&);
   Instruction& operator=(Instruction&&);
 
+  virtual ~Instruction() = default;
+
+  // Returns a newly allocated instruction that has the same operands, result,
+  // and type as |this|.  The new instruction is not linked into any list.
+  // It is the responsibility of the caller to make sure that the storage is
+  // removed. It is the caller's responsibility to make sure that there is only
+  // one instruction for each result id.
+  Instruction* Clone() const;
+
   SpvOp opcode() const { return opcode_; }
   // Sets the opcode of this instruction to a specific opcode. Note this may
   // invalidate the instruction.
@@ -110,6 +139,11 @@
     return dbg_line_insts_;
   }
 
+  // Same semantics as in the base class except the list the InstructionList
+  // containing |pos| will now assume ownership of |this|.
+  // inline void MoveBefore(Instruction* pos);
+  // inline void InsertAfter(Instruction* pos);
+
   // Begin and end iterators for operands.
   iterator begin() { return operands_.begin(); }
   iterator end() { return operands_.end(); }
@@ -139,6 +173,10 @@
   inline void SetResultType(uint32_t ty_id);
   // Sets the result id
   inline void SetResultId(uint32_t res_id);
+  // Remove the |index|-th operand
+  void RemoveOperand(uint32_t index) {
+    operands_.erase(operands_.begin() + index);
+  }
 
   // The following methods are similar to the above, but are for in operands.
   uint32_t NumInOperands() const {
@@ -151,6 +189,9 @@
   uint32_t GetSingleWordInOperand(uint32_t index) const {
     return GetSingleWordOperand(index + TypeResultIdCount());
   }
+  void RemoveInOperand(uint32_t index) {
+    operands_.erase(operands_.begin() + index + TypeResultIdCount());
+  }
 
   // Returns true if this instruction is OpNop.
   inline bool IsNop() const;
@@ -166,18 +207,34 @@
   inline void ForEachInst(const std::function<void(const Instruction*)>& f,
                           bool run_on_debug_line_insts = false) const;
 
+  // Runs the given function |f| on all operand ids.
+  //
+  // |f| should not transform an ID into 0, as 0 is an invalid ID.
+  inline void ForEachId(const std::function<void(uint32_t*)>& f);
+  inline void ForEachId(const std::function<void(const uint32_t*)>& f) const;
+
   // Runs the given function |f| on all "in" operand ids
   inline void ForEachInId(const std::function<void(uint32_t*)>& f);
   inline void ForEachInId(const std::function<void(const uint32_t*)>& f) const;
 
+  // Runs the given function |f| on all "in" operands
+  inline void ForEachInOperand(const std::function<void(uint32_t*)>& f);
+  inline void ForEachInOperand(const std::function<void(const uint32_t*)>& f)
+      const;
+
   // Returns true if any operands can be labels
   inline bool HasLabels() const;
 
   // Pushes the binary segments for this instruction into the back of *|binary|.
   void ToBinaryWithoutAttachedDebugInsts(std::vector<uint32_t>* binary) const;
 
+  // Replaces the operands to the instruction with |new_operands|. The caller
+  // is responsible for building a complete and valid list of operands for
+  // this instruction.
+  void ReplaceOperands(const std::vector<Operand>& new_operands);
+
  private:
-  // Returns the toal count of result type id and result id.
+  // Returns the total count of result type id and result id.
   uint32_t TypeResultIdCount() const {
     return (type_id_ != 0) + (result_id_ != 0);
   }
@@ -191,6 +248,8 @@
   // Instructions representing OpLine or OpNonLine itself, this field should be
   // empty.
   std::vector<Instruction> dbg_line_insts_;
+
+  friend InstructionList;
 };
 
 inline const Operand& Instruction::GetOperand(uint32_t index) const {
@@ -246,6 +305,20 @@
   f(this);
 }
 
+inline void Instruction::ForEachId(const std::function<void(uint32_t*)>& f) {
+  for (auto& opnd : operands_)
+    if (spvIsIdType(opnd.type)) f(&opnd.words[0]);
+  if (type_id_ != 0u)
+    type_id_ = GetSingleWordOperand(0u);
+  if (result_id_ != 0u) result_id_ = GetSingleWordOperand(type_id_ == 0u ? 0u : 1u);
+}
+
+inline void Instruction::ForEachId(
+    const std::function<void(const uint32_t*)>& f) const {
+  for (const auto& opnd : operands_)
+    if (spvIsIdType(opnd.type)) f(&opnd.words[0]);
+}
+
 inline void Instruction::ForEachInId(const std::function<void(uint32_t*)>& f) {
   for (auto& opnd : operands_) {
     switch (opnd.type) {
@@ -273,6 +346,34 @@
   }
 }
 
+inline void Instruction::ForEachInOperand(
+      const std::function<void(uint32_t*)>& f) {
+  for (auto& opnd : operands_) {
+    switch (opnd.type) {
+      case SPV_OPERAND_TYPE_RESULT_ID:
+      case SPV_OPERAND_TYPE_TYPE_ID:
+        break;
+      default:
+        f(&opnd.words[0]);
+        break;
+    }
+  }
+}
+
+inline void Instruction::ForEachInOperand(
+    const std::function<void(const uint32_t*)>& f) const {
+  for (const auto& opnd : operands_) {
+    switch (opnd.type) {
+      case SPV_OPERAND_TYPE_RESULT_ID:
+      case SPV_OPERAND_TYPE_TYPE_ID:
+        break;
+      default:
+        f(&opnd.words[0]);
+        break;
+    }
+  }
+}
+
 inline bool Instruction::HasLabels() const {
   switch (opcode_) {
     case SpvOpSelectionMerge:
diff --git a/source/opt/instruction_list.cpp b/source/opt/instruction_list.cpp
new file mode 100644
index 0000000..2885c45
--- /dev/null
+++ b/source/opt/instruction_list.cpp
@@ -0,0 +1,44 @@
+// Copyright (c) 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "instruction_list.h"
+
+namespace spvtools {
+namespace ir {
+
+InstructionList::~InstructionList() {
+  while (!empty()) {
+    Instruction* inst = &front();
+    inst->RemoveFromList();
+    delete inst;
+  }
+}
+
+InstructionList::iterator InstructionList::iterator::InsertBefore(
+    std::vector<std::unique_ptr<Instruction>>&& list) {
+  Instruction* first_node = list.front().get();
+  for (auto& i : list) {
+    i.release()->InsertBefore(node_);
+  }
+  list.clear();
+  return iterator(first_node);
+}
+
+InstructionList::iterator InstructionList::iterator::InsertBefore(
+    std::unique_ptr<Instruction>&& i) {
+  i.get()->InsertBefore(node_);
+  return iterator(i.release());
+}
+}  // namespace ir
+}  // namespace spvtools
\ No newline at end of file
diff --git a/source/opt/instruction_list.h b/source/opt/instruction_list.h
new file mode 100644
index 0000000..c25f0d8
--- /dev/null
+++ b/source/opt/instruction_list.h
@@ -0,0 +1,92 @@
+
+// Copyright (c) 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef LIBSPIRV_OPT_INSTRUCTION_LIST_H_
+#define LIBSPIRV_OPT_INSTRUCTION_LIST_H_
+
+#include <cassert>
+#include <functional>
+#include <utility>
+#include <vector>
+
+#include "instruction.h"
+#include "operand.h"
+#include "util/ilist.h"
+
+#include "spirv-tools/libspirv.h"
+#include "spirv/1.2/spirv.h"
+
+namespace spvtools {
+namespace ir {
+
+// This class is intended to be the container for Instructions.  This container
+// owns the instructions that are in it.  When removing an Instruction from the
+// list, the caller is assuming responsibility for deleting the storage.
+//
+// TODO: Because there are a number of other data structures that will want
+// pointers to instruction, ownership should probably be moved to the module.
+// Because of that I have not made the ownership passing in this class fully
+// explicit.  For example, RemoveFromList takes ownership from the list, but
+// does not return an std::unique_ptr to signal that.  When we fully decide on
+// ownership, this will have to be fixed up one way or the other.
+class InstructionList : public utils::IntrusiveList<Instruction> {
+ public:
+  InstructionList() = default;
+  InstructionList(InstructionList&& that)
+      : utils::IntrusiveList<Instruction>(std::move(that)) {}
+  InstructionList& operator=(InstructionList&& that) {
+    auto p = static_cast<utils::IntrusiveList<Instruction>*>(this);
+    *p = std::move(that);
+    return *this;
+  }
+
+  // Destroy this list and any instructions in the list.
+  virtual ~InstructionList();
+
+  class iterator : public utils::IntrusiveList<Instruction>::iterator {
+   public:
+    iterator(const utils::IntrusiveList<Instruction>::iterator& i)
+        : utils::IntrusiveList<Instruction>::iterator(&*i) {}
+    iterator(Instruction* i) : utils::IntrusiveList<Instruction>::iterator(i) {}
+
+    // DEPRECATED: Please use MoveBefore with an InstructionList instead.
+    //
+    // Moves the nodes in |list| to the list that |this| points to.  The
+    // positions of the nodes will be immediately before the element pointed to
+    // by the iterator.  The return value will be an iterator pointing to the
+    // first of the newly inserted elements.  Ownership of the elements in
+    // |list| is now passed on to |*this|.
+    iterator InsertBefore(std::vector<std::unique_ptr<Instruction>>&& list);
+
+    // The node |i| will be inserted immediately before |this|. The return value
+    // will be an iterator pointing to the newly inserted node.  The owner of
+    // |*i| becomes |*this|
+    iterator InsertBefore(std::unique_ptr<Instruction>&& i);
+  };
+
+  iterator begin() { return utils::IntrusiveList<Instruction>::begin(); }
+  iterator end() { return utils::IntrusiveList<Instruction>::end(); }
+  const_iterator begin() const {
+    return utils::IntrusiveList<Instruction>::begin();
+  }
+  const_iterator end() const {
+    return utils::IntrusiveList<Instruction>::end();
+  }
+};
+
+}  // namespace ir
+}  // namespace spvtools
+
+#endif  // LIBSPIRV_OPT_INSTRUCTION_LIST_H_
diff --git a/source/opt/ir_loader.cpp b/source/opt/ir_loader.cpp
index d358e97..e3d8484 100644
--- a/source/opt/ir_loader.cpp
+++ b/source/opt/ir_loader.cpp
@@ -99,8 +99,12 @@
         module_->AddEntryPoint(std::move(spv_inst));
       } else if (opcode == SpvOpExecutionMode) {
         module_->AddExecutionMode(std::move(spv_inst));
-      } else if (IsDebugInst(opcode)) {
-        module_->AddDebugInst(std::move(spv_inst));
+      } else if (IsDebug1Inst(opcode)) {
+        module_->AddDebug1Inst(std::move(spv_inst));
+      } else if (IsDebug2Inst(opcode)) {
+        module_->AddDebug2Inst(std::move(spv_inst));
+      } else if (IsDebug3Inst(opcode)) {
+        module_->AddDebug3Inst(std::move(spv_inst));
       } else if (IsAnnotationInst(opcode)) {
         module_->AddAnnotationInst(std::move(spv_inst));
       } else if (IsTypeInst(opcode)) {
diff --git a/source/opt/local_access_chain_convert_pass.cpp b/source/opt/local_access_chain_convert_pass.cpp
index 1e5863d..e57daaa 100644
--- a/source/opt/local_access_chain_convert_pass.cpp
+++ b/source/opt/local_access_chain_convert_pass.cpp
@@ -227,7 +227,7 @@
             GenAccessChainLoadReplacement(ptrInst, &newInsts);
         ReplaceAndDeleteLoad(&*ii, replId);
         ++ii;
-        ii = ii.InsertBefore(&newInsts);
+        ii = ii.InsertBefore(std::move(newInsts));
         ++ii;
         modified = true;
       } break;
@@ -244,7 +244,7 @@
         def_use_mgr_->KillInst(&*ii);
         DeleteIfUseless(ptrInst);
         ++ii;
-        ii = ii.InsertBefore(&newInsts);
+        ii = ii.InsertBefore(std::move(newInsts));
         ++ii;
         ++ii;
         modified = true;
@@ -271,7 +271,7 @@
   def_use_mgr_.reset(new analysis::DefUseManager(consumer(), module_));
 
   // Initialize next unused Id.
-  next_id_ = module->id_bound();
+  InitNextId();
 
   // Initialize extension whitelist
   InitExtensions();
@@ -311,11 +311,11 @@
     return ConvertLocalAccessChains(fp);
   };
   bool modified = ProcessEntryPointCallTree(pfn, module_);
-  FinalizeNextId(module_);
+  FinalizeNextId();
   return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
 }
 
-LocalAccessChainConvertPass::LocalAccessChainConvertPass() : next_id_(0) {}
+LocalAccessChainConvertPass::LocalAccessChainConvertPass() {}
 
 Pass::Status LocalAccessChainConvertPass::Process(ir::Module* module) {
   Initialize(module);
diff --git a/source/opt/local_access_chain_convert_pass.h b/source/opt/local_access_chain_convert_pass.h
index d7f51ba..e43503d 100644
--- a/source/opt/local_access_chain_convert_pass.h
+++ b/source/opt/local_access_chain_convert_pass.h
@@ -108,16 +108,6 @@
   // Return true if all extensions in this module are allowed by this pass.
   bool AllExtensionsSupported() const;
 
-  // Save next available id into |module|.
-  inline void FinalizeNextId(ir::Module* module) {
-    module->SetIdBound(next_id_);
-  }
-
-  // Return next available id and calculate next.
-  inline uint32_t TakeNextId() {
-    return next_id_++;
-  }
-
   void Initialize(ir::Module* module);
   Pass::Status ProcessImpl();
 
@@ -127,9 +117,6 @@
 
   // Extensions supported by this pass.
   std::unordered_set<std::string> extensions_whitelist_;
-
-  // Next unused ID
-  uint32_t next_id_;
 };
 
 }  // namespace opt
diff --git a/source/opt/local_single_block_elim_pass.cpp b/source/opt/local_single_block_elim_pass.cpp
index deeead6..630fba3 100644
--- a/source/opt/local_single_block_elim_pass.cpp
+++ b/source/opt/local_single_block_elim_pass.cpp
@@ -152,7 +152,7 @@
   def_use_mgr_.reset(new analysis::DefUseManager(consumer(), module_));
 
   // Start new ids with next availablein module
-  next_id_ = module_->id_bound();
+  InitNextId();
 
   // Initialize extensions whitelist
   InitExtensions();
@@ -190,12 +190,11 @@
     return LocalSingleBlockLoadStoreElim(fp);
   };
   bool modified = ProcessEntryPointCallTree(pfn, module_);
-  FinalizeNextId(module_);
+  FinalizeNextId();
   return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
 }
 
-LocalSingleBlockLoadStoreElimPass::LocalSingleBlockLoadStoreElimPass()
-    : next_id_(0) {}
+LocalSingleBlockLoadStoreElimPass::LocalSingleBlockLoadStoreElimPass() {}
 
 Pass::Status LocalSingleBlockLoadStoreElimPass::Process(ir::Module* module) {
   Initialize(module);
diff --git a/source/opt/local_single_block_elim_pass.h b/source/opt/local_single_block_elim_pass.h
index ef744b1..8116cf6 100644
--- a/source/opt/local_single_block_elim_pass.h
+++ b/source/opt/local_single_block_elim_pass.h
@@ -53,16 +53,6 @@
   // where possible. Assumes logical addressing.
   bool LocalSingleBlockLoadStoreElim(ir::Function* func);
 
-  // Save next available id into |module|.
-  inline void FinalizeNextId(ir::Module* module) {
-    module->SetIdBound(next_id_);
-  }
-
-  // Return next available id and calculate next.
-  inline uint32_t TakeNextId() {
-    return next_id_++;
-  }
-
   // Initialize extensions whitelist
   void InitExtensions();
 
@@ -99,9 +89,6 @@
   // Variables that are only referenced by supported operations for this
   // pass ie. loads and stores.
   std::unordered_set<uint32_t> supported_ref_ptrs_;
-
-  // Next unused ID
-  uint32_t next_id_;
 };
 
 }  // namespace opt
diff --git a/source/opt/local_single_store_elim_pass.cpp b/source/opt/local_single_store_elim_pass.cpp
index c32b027..b9603d9 100644
--- a/source/opt/local_single_store_elim_pass.cpp
+++ b/source/opt/local_single_store_elim_pass.cpp
@@ -258,7 +258,7 @@
   def_use_mgr_.reset(new analysis::DefUseManager(consumer(), module_));
 
   // Initialize next unused Id
-  next_id_ = module_->id_bound();
+  InitNextId();
 
   // Initialize extension whitelist
   InitExtensions();
@@ -295,7 +295,7 @@
     return LocalSingleStoreElim(fp);
   };
   bool modified = ProcessEntryPointCallTree(pfn, module_);
-  FinalizeNextId(module_);
+  FinalizeNextId();
   return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
 }
 
@@ -303,8 +303,7 @@
     : pseudo_entry_block_(std::unique_ptr<ir::Instruction>(
           new ir::Instruction(SpvOpLabel, 0, 0, {}))),
       pseudo_exit_block_(std::unique_ptr<ir::Instruction>(
-          new ir::Instruction(SpvOpLabel, 0, kInvalidId, {}))),
-      next_id_(0) {}
+          new ir::Instruction(SpvOpLabel, 0, kInvalidId, {}))) {}
 
 Pass::Status LocalSingleStoreElimPass::Process(ir::Module* module) {
   Initialize(module);
diff --git a/source/opt/local_single_store_elim_pass.h b/source/opt/local_single_store_elim_pass.h
index 4352378..4d52112 100644
--- a/source/opt/local_single_store_elim_pass.h
+++ b/source/opt/local_single_store_elim_pass.h
@@ -96,16 +96,6 @@
   // Return true if all extensions in this module are allowed by this pass.
   bool AllExtensionsSupported() const;
 
-  // Save next available id into |module|.
-  inline void FinalizeNextId(ir::Module* module) {
-    module->SetIdBound(next_id_);
-  }
-
-  // Return next available id and generate next.
-  inline uint32_t TakeNextId() {
-    return next_id_++;
-  }
-
   void Initialize(ir::Module* module);
   Pass::Status ProcessImpl();
 
@@ -156,9 +146,6 @@
 
   // Extensions supported by this pass.
   std::unordered_set<std::string> extensions_whitelist_;
-
-  // Next unused ID
-  uint32_t next_id_;
 };
 
 }  // namespace opt
diff --git a/source/opt/local_ssa_elim_pass.cpp b/source/opt/local_ssa_elim_pass.cpp
index 5dd4233..bea8bbf 100644
--- a/source/opt/local_ssa_elim_pass.cpp
+++ b/source/opt/local_ssa_elim_pass.cpp
@@ -100,6 +100,7 @@
 }
 
 void LocalMultiStoreElimPass::ComputeStructuredSuccessors(ir::Function* func) {
+  block2structured_succs_.clear();
   for (auto& blk : *func) {
     // If no predecessors in function, make successor to pseudo entry
     if (label2preds_[blk.id()].size() == 0)
@@ -279,6 +280,8 @@
     const uint32_t phiId = TakeNextId();
     std::unique_ptr<ir::Instruction> newPhi(
       new ir::Instruction(SpvOpPhi, typeId, phiId, phi_in_operands));
+    // The only phis requiring patching are the ones we create.
+    phis_to_patch_.insert(phiId);
     // Only analyze the phi define now; analyze the phi uses after the
     // phi backedge predecessor value is patched.
     def_use_mgr_->AnalyzeInstDef(&*newPhi);
@@ -378,19 +381,27 @@
   ir::BasicBlock* header = id2block_[header_id];
   auto phiItr = header->begin();
   for (; phiItr->opcode() == SpvOpPhi; ++phiItr) {
+    // Only patch phis that we created in a loop header.
+    // There might be other phis unrelated to our optimizations.
+    if (0 == phis_to_patch_.count(phiItr->result_id())) continue;
+
+    // Find phi operand index for back edge
     uint32_t cnt = 0;
-    uint32_t idx;
+    uint32_t idx = phiItr->NumInOperands();
     phiItr->ForEachInId([&cnt,&back_id,&idx](uint32_t* iid) {
       if (cnt % 2 == 1 && *iid == back_id) idx = cnt - 1;
       ++cnt;
     });
-    // Use undef if variable not in backedge predecessor map
+    assert(idx != phiItr->NumInOperands());
+    // Replace temporary phi operand with variable's value in backedge block
+    // map. Use undef if variable not in map.
     const uint32_t varId = phiItr->GetSingleWordInOperand(idx);
     const auto valItr = label2ssa_map_[back_id].find(varId);
-    uint32_t valId = (valItr != label2ssa_map_[back_id].end()) ?
-      valItr->second :
-      Type2Undef(GetPointeeTypeId(def_use_mgr_->GetDef(varId)));
-    phiItr->SetInOperand(idx, { valId });
+    uint32_t valId =
+        (valItr != label2ssa_map_[back_id].end())
+            ? valItr->second
+            : Type2Undef(GetPointeeTypeId(def_use_mgr_->GetDef(varId)));
+    phiItr->SetInOperand(idx, {valId});
     // Analyze uses now that they are complete
     def_use_mgr_->AnalyzeInstUse(&*phiItr);
   }
@@ -505,9 +516,10 @@
   block2structured_succs_.clear();
   label2preds_.clear();
   label2ssa_map_.clear();
+  phis_to_patch_.clear();
 
   // Start new ids with next availablein module
-  next_id_ = module_->id_bound();
+  InitNextId();
 
   // Initialize extension whitelist
   InitExtensions();
@@ -549,14 +561,13 @@
     return EliminateMultiStoreLocal(fp);
   };
   bool modified = ProcessEntryPointCallTree(pfn, module_);
-  FinalizeNextId(module_);
+  FinalizeNextId();
   return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
 }
 
 LocalMultiStoreElimPass::LocalMultiStoreElimPass()
     : pseudo_entry_block_(std::unique_ptr<ir::Instruction>(
-          new ir::Instruction(SpvOpLabel, 0, 0, {}))),
-      next_id_(0) {}
+          new ir::Instruction(SpvOpLabel, 0, 0, {}))) {}
 
 Pass::Status LocalMultiStoreElimPass::Process(ir::Module* module) {
   Initialize(module);
diff --git a/source/opt/local_ssa_elim_pass.h b/source/opt/local_ssa_elim_pass.h
index 8b6898c..0a90fea 100644
--- a/source/opt/local_ssa_elim_pass.h
+++ b/source/opt/local_ssa_elim_pass.h
@@ -116,11 +116,12 @@
   // undef to function undef map.
   uint32_t Type2Undef(uint32_t type_id);
 
-  // Patch phis in loop header block now that the map is complete for the
-  // backedge predecessor. Specifically, for each phi, find the value
-  // corresponding to the backedge predecessor. That contains the variable id
-  // that this phi corresponds to. Change this phi operand to the the value
-  // which corresponds to that variable in the predecessor map.
+  // Patch phis in loop header block |header_id| now that the map is complete
+  // for the backedge predecessor |back_id|. Specifically, for each phi, find
+  // the value corresponding to the backedge predecessor. That was temporarily
+  // set with the variable id that this phi corresponds to. Change this phi
+  // operand to the the value which corresponds to that variable in the
+  // predecessor map.
   void PatchPhis(uint32_t header_id, uint32_t back_id);
 
   // Initialize extensions whitelist
@@ -136,16 +137,6 @@
   // the runtime and effectiveness of this function.
   bool EliminateMultiStoreLocal(ir::Function* func);
 
-  // Save next available id into |module|.
-  inline void FinalizeNextId(ir::Module* module) {
-    module->SetIdBound(next_id_);
-  }
-
-  // Return next available id and calculate next.
-  inline uint32_t TakeNextId() {
-    return next_id_++;
-  }
-
   void Initialize(ir::Module* module);
   Pass::Status ProcessImpl();
 
@@ -175,15 +166,16 @@
   std::unordered_map<uint32_t, std::unordered_map<uint32_t, uint32_t>>
       label2ssa_map_;
 
+  // The Ids of OpPhi instructions that are in a loop header and which require
+  // patching of the value for the loop back-edge.
+  std::unordered_set<uint32_t> phis_to_patch_;
+
   // Extra block whose successors are all blocks with no predecessors
   // in function.
   ir::BasicBlock pseudo_entry_block_;
 
   // Extensions supported by this pass.
   std::unordered_set<std::string> extensions_whitelist_;
-
-  // Next unused ID
-  uint32_t next_id_;
 };
 
 }  // namespace opt
diff --git a/source/opt/mem_pass.cpp b/source/opt/mem_pass.cpp
index 6768606..b176a6f 100644
--- a/source/opt/mem_pass.cpp
+++ b/source/opt/mem_pass.cpp
@@ -150,7 +150,7 @@
 
 void MemPass::FindNamedOrDecoratedIds() {
   named_or_decorated_ids_.clear();
-  for (auto& di : module_->debugs())
+  for (auto& di : module_->debugs2())
     if (di.opcode() == SpvOpName)
       named_or_decorated_ids_.insert(di.GetSingleWordInOperand(0));
   for (auto& ai : module_->annotations())
@@ -292,7 +292,7 @@
   DCEInst(loadInst);
 }
 
-MemPass::MemPass() : module_(nullptr), def_use_mgr_(nullptr) {}
+MemPass::MemPass() : module_(nullptr), def_use_mgr_(nullptr), next_id_(0) {}
 
 }  // namespace opt
 }  // namespace spvtools
diff --git a/source/opt/mem_pass.h b/source/opt/mem_pass.h
index eff6900..f86f306 100644
--- a/source/opt/mem_pass.h
+++ b/source/opt/mem_pass.h
@@ -113,6 +113,21 @@
     return (op == SpvOpDecorate || op == SpvOpDecorateId);
   }
 
+  // Initialize next available id from |module|.
+  void InitNextId() {
+    next_id_ = module_->IdBound();
+  }
+
+  // Save next available id into |module|.
+  void FinalizeNextId() {
+    module_->SetIdBound(next_id_);
+  }
+
+  // Return next available id and calculate next.
+  inline uint32_t TakeNextId() {
+    return next_id_++;
+  }
+
   // Module this pass is processing
   ir::Module* module_;
 
@@ -127,6 +142,9 @@
 
   // named or decorated ids
   std::unordered_set<uint32_t> named_or_decorated_ids_;
+
+  // Next unused ID
+  uint32_t next_id_;
 };
 
 }  // namespace opt
diff --git a/source/opt/module.cpp b/source/opt/module.cpp
index 290e88d..1a86921 100644
--- a/source/opt/module.cpp
+++ b/source/opt/module.cpp
@@ -83,7 +83,9 @@
   if (memory_model_) DELEGATE(memory_model_);
   for (auto& i : entry_points_) DELEGATE(i);
   for (auto& i : execution_modes_) DELEGATE(i);
-  for (auto& i : debugs_) DELEGATE(i);
+  for (auto& i : debugs1_) DELEGATE(i);
+  for (auto& i : debugs2_) DELEGATE(i);
+  for (auto& i : debugs3_) DELEGATE(i);
   for (auto& i : annotations_) DELEGATE(i);
   for (auto& i : types_values_) DELEGATE(i);
   for (auto& i : functions_) DELEGATE(i);
@@ -101,7 +103,9 @@
   if (memory_model_) DELEGATE(memory_model_);
   for (auto& i : entry_points_) DELEGATE(i);
   for (auto& i : execution_modes_) DELEGATE(i);
-  for (auto& i : debugs_) DELEGATE(i);
+  for (auto& i : debugs1_) DELEGATE(i);
+  for (auto& i : debugs2_) DELEGATE(i);
+  for (auto& i : debugs3_) DELEGATE(i);
   for (auto& i : annotations_) DELEGATE(i);
   for (auto& i : types_values_) DELEGATE(i);
   for (auto& i : functions_) {
diff --git a/source/opt/module.h b/source/opt/module.h
index e29615f..0ce6398 100644
--- a/source/opt/module.h
+++ b/source/opt/module.h
@@ -66,8 +66,17 @@
   inline void AddEntryPoint(std::unique_ptr<Instruction> e);
   // Appends an execution mode instruction to this module.
   inline void AddExecutionMode(std::unique_ptr<Instruction> e);
-  // Appends a debug instruction (excluding OpLine & OpNoLine) to this module.
-  inline void AddDebugInst(std::unique_ptr<Instruction> d);
+  // Appends a debug 1 instruction (excluding OpLine & OpNoLine) to this module.
+  // "debug 1" instructions are the ones in layout section 7.a), see section
+  // 2.4 Logical Layout of a Module from the SPIR-V specification.
+  inline void AddDebug1Inst(std::unique_ptr<Instruction> d);
+  // Appends a debug 2 instruction (excluding OpLine & OpNoLine) to this module.
+  // "debug 2" instructions are the ones in layout section 7.b), see section
+  // 2.4 Logical Layout of a Module from the SPIR-V specification.
+  inline void AddDebug2Inst(std::unique_ptr<Instruction> d);
+  // Appends a debug 3 instruction (OpModuleProcessed) to this module.
+  // This is due to decision by the SPIR Working Group, pending publication.
+  inline void AddDebug3Inst(std::unique_ptr<Instruction> d);
   // Appends an annotation instruction to this module.
   inline void AddAnnotationInst(std::unique_ptr<Instruction> a);
   // Appends a type-declaration instruction to this module.
@@ -94,25 +103,84 @@
 
   inline uint32_t id_bound() const { return header_.bound; }
 
-  // Iterators for debug instructions (excluding OpLine & OpNoLine) contained in
-  // this module.
-  inline inst_iterator debug_begin();
-  inline inst_iterator debug_end();
-  inline IteratorRange<inst_iterator> debugs();
-  inline IteratorRange<const_inst_iterator> debugs() const;
+  inline uint32_t version() const { return header_.version; }
+
+  // Iterators for capabilities instructions contained in this module.
+  inline inst_iterator capability_begin();
+  inline inst_iterator capability_end();
+  inline IteratorRange<inst_iterator> capabilities();
+  inline IteratorRange<const_inst_iterator> capabilities() const;
+
+  // Iterators for ext_inst_imports instructions contained in this module.
+  inline inst_iterator ext_inst_import_begin();
+  inline inst_iterator ext_inst_import_end();
+  inline IteratorRange<inst_iterator> ext_inst_imports();
+  inline IteratorRange<const_inst_iterator> ext_inst_imports() const;
+
+  // Return the memory model instruction contained inthis module.
+  inline Instruction* GetMemoryModel() { return memory_model_.get(); }
+  inline const Instruction* GetMemoryModel() const { return memory_model_.get(); }
+
+  // There are several kinds of debug instructions, according to where they can
+  // appear in the logical layout of a module:
+  //  - Section 7a:  OpString, OpSourceExtension, OpSource, OpSourceContinued
+  //  - Section 7b:  OpName, OpMemberName
+  //  - Section 7c:  OpModuleProcessed
+  //  - Mostly anywhere: OpLine and OpNoLine
+  //
+
+  // Iterators for debug 1 instructions (excluding OpLine & OpNoLine) contained
+  // in this module.  These are for layout section 7a.
+  inline inst_iterator debug1_begin();
+  inline inst_iterator debug1_end();
+  inline IteratorRange<inst_iterator> debugs1();
+  inline IteratorRange<const_inst_iterator> debugs1() const;
+
+  // Iterators for debug 2 instructions (excluding OpLine & OpNoLine) contained
+  // in this module.  These are for layout section 7b.
+  inline inst_iterator debug2_begin();
+  inline inst_iterator debug2_end();
+  inline IteratorRange<inst_iterator> debugs2();
+  inline IteratorRange<const_inst_iterator> debugs2() const;
+
+  // Iterators for debug 3 instructions (excluding OpLine & OpNoLine) contained
+  // in this module.  These are for layout section 7c.
+  inline inst_iterator debug3_begin();
+  inline inst_iterator debug3_end();
+  inline IteratorRange<inst_iterator> debugs3();
+  inline IteratorRange<const_inst_iterator> debugs3() const;
 
   // Iterators for entry point instructions contained in this module
   inline IteratorRange<inst_iterator> entry_points();
   inline IteratorRange<const_inst_iterator> entry_points() const;
 
+  // Iterators for execution_modes instructions contained in this module.
+  inline inst_iterator execution_mode_begin();
+  inline inst_iterator execution_mode_end();
+  inline IteratorRange<inst_iterator> execution_modes();
+  inline IteratorRange<const_inst_iterator> execution_modes() const;
+
   // Clears all debug instructions (excluding OpLine & OpNoLine).
-  void debug_clear() { debugs_.clear(); }
+  void debug_clear() { debug1_clear(); debug2_clear(); debug3_clear(); }
+
+  // Clears all debug 1 instructions (excluding OpLine & OpNoLine).
+  void debug1_clear() { debugs1_.clear(); }
+
+  // Clears all debug 2 instructions (excluding OpLine & OpNoLine).
+  void debug2_clear() { debugs2_.clear(); }
+
+  // Clears all debug 3 instructions (excluding OpLine & OpNoLine).
+  void debug3_clear() { debugs3_.clear(); }
 
   // Iterators for annotation instructions contained in this module.
+  inline inst_iterator annotation_begin();
+  inline inst_iterator annotation_end();
   IteratorRange<inst_iterator> annotations();
   IteratorRange<const_inst_iterator> annotations() const;
 
   // Iterators for extension instructions contained in this module.
+  inline inst_iterator extension_begin();
+  inline inst_iterator extension_end();
   IteratorRange<inst_iterator> extensions();
   IteratorRange<const_inst_iterator> extensions() const;
 
@@ -161,7 +229,9 @@
   std::unique_ptr<Instruction> memory_model_;
   std::vector<std::unique_ptr<Instruction>> entry_points_;
   std::vector<std::unique_ptr<Instruction>> execution_modes_;
-  std::vector<std::unique_ptr<Instruction>> debugs_;
+  std::vector<std::unique_ptr<Instruction>> debugs1_;
+  std::vector<std::unique_ptr<Instruction>> debugs2_;
+  std::vector<std::unique_ptr<Instruction>> debugs3_;
   std::vector<std::unique_ptr<Instruction>> annotations_;
   // Type declarations, constants, and global variable declarations.
   std::vector<std::unique_ptr<Instruction>> types_values_;
@@ -192,8 +262,16 @@
   execution_modes_.emplace_back(std::move(e));
 }
 
-inline void Module::AddDebugInst(std::unique_ptr<Instruction> d) {
-  debugs_.emplace_back(std::move(d));
+inline void Module::AddDebug1Inst(std::unique_ptr<Instruction> d) {
+  debugs1_.emplace_back(std::move(d));
+}
+
+inline void Module::AddDebug2Inst(std::unique_ptr<Instruction> d) {
+  debugs2_.emplace_back(std::move(d));
+}
+
+inline void Module::AddDebug3Inst(std::unique_ptr<Instruction> d) {
+  debugs3_.emplace_back(std::move(d));
 }
 
 inline void Module::AddAnnotationInst(std::unique_ptr<Instruction> a) {
@@ -212,19 +290,79 @@
   functions_.emplace_back(std::move(f));
 }
 
-inline Module::inst_iterator Module::debug_begin() {
-  return inst_iterator(&debugs_, debugs_.begin());
+inline Module::inst_iterator Module::capability_begin() {
+  return inst_iterator(&capabilities_, capabilities_.begin());
 }
-inline Module::inst_iterator Module::debug_end() {
-  return inst_iterator(&debugs_, debugs_.end());
+inline Module::inst_iterator Module::capability_end() {
+  return inst_iterator(&capabilities_, capabilities_.end());
 }
 
-inline IteratorRange<Module::inst_iterator> Module::debugs() {
-  return make_range(debugs_);
+inline IteratorRange<Module::inst_iterator> Module::capabilities() {
+  return make_range(capabilities_);
 }
 
-inline IteratorRange<Module::const_inst_iterator> Module::debugs() const {
-  return make_const_range(debugs_);
+inline IteratorRange<Module::const_inst_iterator> Module::capabilities() const {
+  return make_const_range(capabilities_);
+}
+
+inline Module::inst_iterator Module::ext_inst_import_begin() {
+  return inst_iterator(&ext_inst_imports_, ext_inst_imports_.begin());
+}
+inline Module::inst_iterator Module::ext_inst_import_end() {
+  return inst_iterator(&ext_inst_imports_, ext_inst_imports_.end());
+}
+
+inline IteratorRange<Module::inst_iterator> Module::ext_inst_imports() {
+  return make_range(ext_inst_imports_);
+}
+
+inline IteratorRange<Module::const_inst_iterator> Module::ext_inst_imports() const {
+  return make_const_range(ext_inst_imports_);
+}
+
+inline Module::inst_iterator Module::debug1_begin() {
+  return inst_iterator(&debugs1_, debugs1_.begin());
+}
+inline Module::inst_iterator Module::debug1_end() {
+  return inst_iterator(&debugs1_, debugs1_.end());
+}
+
+inline IteratorRange<Module::inst_iterator> Module::debugs1() {
+  return make_range(debugs1_);
+}
+
+inline IteratorRange<Module::const_inst_iterator> Module::debugs1() const {
+  return make_const_range(debugs1_);
+}
+
+inline Module::inst_iterator Module::debug2_begin() {
+  return inst_iterator(&debugs2_, debugs2_.begin());
+}
+inline Module::inst_iterator Module::debug2_end() {
+  return inst_iterator(&debugs2_, debugs2_.end());
+}
+
+inline IteratorRange<Module::inst_iterator> Module::debugs2() {
+  return make_range(debugs2_);
+}
+
+inline IteratorRange<Module::const_inst_iterator> Module::debugs2() const {
+  return make_const_range(debugs2_);
+}
+
+inline Module::inst_iterator Module::debug3_begin() {
+  return inst_iterator(&debugs3_, debugs3_.begin());
+}
+inline Module::inst_iterator Module::debug3_end() {
+  return inst_iterator(&debugs3_, debugs3_.end());
+}
+
+inline IteratorRange<Module::inst_iterator> Module::debugs3() {
+  return make_range(debugs3_);
+}
+
+inline IteratorRange<Module::const_inst_iterator> Module::debugs3() const {
+  return make_const_range(debugs3_);
 }
 
 inline IteratorRange<Module::inst_iterator> Module::entry_points() {
@@ -235,6 +373,28 @@
   return make_const_range(entry_points_);
 }
 
+inline Module::inst_iterator Module::execution_mode_begin() {
+  return inst_iterator(&execution_modes_, execution_modes_.begin());
+}
+inline Module::inst_iterator Module::execution_mode_end() {
+  return inst_iterator(&execution_modes_, execution_modes_.end());
+}
+
+inline IteratorRange<Module::inst_iterator> Module::execution_modes() {
+  return make_range(execution_modes_);
+}
+
+inline IteratorRange<Module::const_inst_iterator> Module::execution_modes() const {
+  return make_const_range(execution_modes_);
+}
+
+inline Module::inst_iterator Module::annotation_begin() {
+  return inst_iterator(&annotations_, annotations_.begin());
+}
+inline Module::inst_iterator Module::annotation_end() {
+  return inst_iterator(&annotations_, annotations_.end());
+}
+
 inline IteratorRange<Module::inst_iterator> Module::annotations() {
   return make_range(annotations_);
 }
@@ -243,6 +403,13 @@
   return make_const_range(annotations_);
 }
 
+inline Module::inst_iterator Module::extension_begin() {
+  return inst_iterator(&extensions_, extensions_.begin());
+}
+inline Module::inst_iterator Module::extension_end() {
+  return inst_iterator(&extensions_, extensions_.end());
+}
+
 inline IteratorRange<Module::inst_iterator> Module::extensions() {
   return make_range(extensions_);
 }
diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp
index 2e3133d..2fdfe21 100644
--- a/source/opt/optimizer.cpp
+++ b/source/opt/optimizer.cpp
@@ -66,6 +66,36 @@
   return *this;
 }
 
+Optimizer& Optimizer::RegisterPerformancePasses() {
+  return RegisterPass(CreateInlineExhaustivePass())
+      .RegisterPass(CreateLocalAccessChainConvertPass())
+      .RegisterPass(CreateLocalSingleBlockLoadStoreElimPass())
+      .RegisterPass(CreateLocalSingleStoreElimPass())
+      .RegisterPass(CreateInsertExtractElimPass())
+      .RegisterPass(CreateAggressiveDCEPass())
+      .RegisterPass(CreateDeadBranchElimPass())
+      .RegisterPass(CreateBlockMergePass())
+      .RegisterPass(CreateLocalMultiStoreElimPass())
+      .RegisterPass(CreateInsertExtractElimPass())
+      .RegisterPass(CreateCommonUniformElimPass())
+      .RegisterPass(CreateDeadVariableEliminationPass());
+}
+
+Optimizer& Optimizer::RegisterSizePasses() {
+  return RegisterPass(CreateInlineExhaustivePass())
+      .RegisterPass(CreateLocalAccessChainConvertPass())
+      .RegisterPass(CreateLocalSingleBlockLoadStoreElimPass())
+      .RegisterPass(CreateLocalSingleStoreElimPass())
+      .RegisterPass(CreateInsertExtractElimPass())
+      .RegisterPass(CreateAggressiveDCEPass())
+      .RegisterPass(CreateDeadBranchElimPass())
+      .RegisterPass(CreateBlockMergePass())
+      .RegisterPass(CreateLocalMultiStoreElimPass())
+      .RegisterPass(CreateInsertExtractElimPass())
+      .RegisterPass(CreateCommonUniformElimPass())
+      .RegisterPass(CreateDeadVariableEliminationPass());
+}
+
 bool Optimizer::Run(const uint32_t* original_binary,
                     const size_t original_binary_size,
                     std::vector<uint32_t>* optimized_binary) const {
@@ -95,6 +125,11 @@
       MakeUnique<opt::StripDebugInfoPass>());
 }
 
+Optimizer::PassToken CreateEliminateDeadFunctionsPass() {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::EliminateDeadFunctionsPass>());
+}
+
 Optimizer::PassToken CreateSetSpecConstantDefaultValuePass(
     const std::unordered_map<uint32_t, std::string>& id_value_map) {
   return MakeUnique<Optimizer::PassToken::Impl>(
@@ -132,6 +167,16 @@
       MakeUnique<opt::EliminateDeadConstantPass>());
 }
 
+Optimizer::PassToken CreateDeadVariableEliminationPass() {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::DeadVariableElimination>());
+}
+
+Optimizer::PassToken CreateStrengthReductionPass() {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::StrengthReductionPass>());
+}
+
 Optimizer::PassToken CreateBlockMergePass() {
   return MakeUnique<Optimizer::PassToken::Impl>(
       MakeUnique<opt::BlockMergePass>());
@@ -141,17 +186,17 @@
   return MakeUnique<Optimizer::PassToken::Impl>(
       MakeUnique<opt::InlineExhaustivePass>());
 }
-  
+
 Optimizer::PassToken CreateInlineOpaquePass() {
   return MakeUnique<Optimizer::PassToken::Impl>(
       MakeUnique<opt::InlineOpaquePass>());
 }
-  
+
 Optimizer::PassToken CreateLocalAccessChainConvertPass() {
   return MakeUnique<Optimizer::PassToken::Impl>(
       MakeUnique<opt::LocalAccessChainConvertPass>());
 }
-  
+
 Optimizer::PassToken CreateLocalSingleBlockLoadStoreElimPass() {
   return MakeUnique<Optimizer::PassToken::Impl>(
       MakeUnique<opt::LocalSingleBlockLoadStoreElimPass>());
@@ -192,4 +237,17 @@
       MakeUnique<opt::CompactIdsPass>());
 }
 
+std::vector<const char*> Optimizer::GetPassNames() const {
+  std::vector<const char*> v;
+  for (uint32_t i = 0; i < impl_->pass_manager.NumPasses(); i++) {
+    v.push_back(impl_->pass_manager.GetPass(i)->name());
+  }
+  return v;
+}
+
+Optimizer::PassToken CreateCFGCleanupPass() {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::CFGCleanupPass>());
+}
+
 }  // namespace spvtools
diff --git a/source/opt/pass.cpp b/source/opt/pass.cpp
index 626b323..c9d7c11 100644
--- a/source/opt/pass.cpp
+++ b/source/opt/pass.cpp
@@ -25,41 +25,78 @@
 
 const uint32_t kEntryPointFunctionIdInIdx = 1;
 
-}  // namespace anonymous
+}  // namespace
 
-void Pass::AddCalls(ir::Function* func,
-    std::queue<uint32_t>* todo) {
+void Pass::AddCalls(ir::Function* func, std::queue<uint32_t>* todo) {
   for (auto bi = func->begin(); bi != func->end(); ++bi)
     for (auto ii = bi->begin(); ii != bi->end(); ++ii)
       if (ii->opcode() == SpvOpFunctionCall)
         todo->push(ii->GetSingleWordInOperand(0));
 }
 
-bool Pass::ProcessEntryPointCallTree(
-    ProcessFunction& pfn, ir::Module* module) {
+bool Pass::ProcessEntryPointCallTree(ProcessFunction& pfn, ir::Module* module) {
   // Map from function's result id to function
   std::unordered_map<uint32_t, ir::Function*> id2function;
-  for (auto& fn : *module)
-    id2function[fn.result_id()] = &fn;
+  for (auto& fn : *module) id2function[fn.result_id()] = &fn;
+
+  // Collect all of the entry points as the roots.
+  std::queue<uint32_t> roots;
+  for (auto& e : module->entry_points())
+    roots.push(e.GetSingleWordInOperand(kEntryPointFunctionIdInIdx));
+  return ProcessCallTreeFromRoots(pfn, id2function, &roots);
+}
+
+bool Pass::ProcessReachableCallTree(ProcessFunction& pfn, ir::Module* module) {
+  // Map from function's result id to function
+  std::unordered_map<uint32_t, ir::Function*> id2function;
+  for (auto& fn : *module) id2function[fn.result_id()] = &fn;
+
+  std::queue<uint32_t> roots;
+
+  // Add all entry points since they can be reached from outside the module.
+  for (auto& e : module->entry_points())
+    roots.push(e.GetSingleWordInOperand(kEntryPointFunctionIdInIdx));
+
+  // Add all exported functions since they can be reached from outside the
+  // module.
+  for (auto& a : module->annotations()) {
+    // TODO: Handle group decorations as well.  Currently not generate by any
+    // front-end, but could be coming.
+    if (a.opcode() == SpvOp::SpvOpDecorate) {
+      if (a.GetSingleWordOperand(1) ==
+          SpvDecoration::SpvDecorationLinkageAttributes) {
+        uint32_t lastOperand = a.NumOperands() - 1;
+        if (a.GetSingleWordOperand(lastOperand) ==
+            SpvLinkageType::SpvLinkageTypeExport) {
+          uint32_t id = a.GetSingleWordOperand(0);
+          if (id2function.count(id) != 0) roots.push(id);
+        }
+      }
+    }
+  }
+
+  return ProcessCallTreeFromRoots(pfn, id2function, &roots);
+}
+
+bool Pass::ProcessCallTreeFromRoots(
+    ProcessFunction& pfn,
+    const std::unordered_map<uint32_t, ir::Function*>& id2function,
+    std::queue<uint32_t>* roots) {
   // Process call tree
   bool modified = false;
-  std::queue<uint32_t> todo;
   std::unordered_set<uint32_t> done;
-  for (auto& e : module->entry_points())
-    todo.push(e.GetSingleWordInOperand(kEntryPointFunctionIdInIdx));
-  while (!todo.empty()) {
-    const uint32_t fi = todo.front();
-    if (done.find(fi) == done.end()) {
-      ir::Function* fn = id2function[fi];
+
+  while (!roots->empty()) {
+    const uint32_t fi = roots->front();
+    roots->pop();
+    if (done.insert(fi).second) {
+      ir::Function* fn = id2function.at(fi);
       modified = pfn(fn) || modified;
-      done.insert(fi);
-      AddCalls(fn, &todo);
+      AddCalls(fn, roots);
     }
-    todo.pop();
   }
   return modified;
 }
-
 }  // namespace opt
 }  // namespace spvtools
 
diff --git a/source/opt/pass.h b/source/opt/pass.h
index 897203f..ee04ae5 100644
--- a/source/opt/pass.h
+++ b/source/opt/pass.h
@@ -56,6 +56,11 @@
   virtual ~Pass() = default;
 
   // Returns a descriptive name for this pass.
+  //
+  // NOTE: When deriving a new pass class, make sure you make the name
+  // compatible with the corresponding spirv-opt command-line flag. For example,
+  // if you add the flag --my-pass to spirv-opt, make this function return
+  // "my-pass" (no leading hyphens).
   virtual const char* name() const = 0;
 
   // Sets the message consumer to the given |consumer|. |consumer| which will be
@@ -67,9 +72,25 @@
   // Add to |todo| all ids of functions called in |func|.
   void AddCalls(ir::Function* func, std::queue<uint32_t>* todo);
 
-  // 
+  // Applies |pfn| to every function in the call trees that are rooted at the
+  // entry points.  Returns true if any call |pfn| returns true.  By convention
+  // |pfn| should return true if it modified the module.
   bool ProcessEntryPointCallTree(ProcessFunction& pfn, ir::Module* module);
 
+  // Applies |pfn| to every function in the call trees rooted at the entry
+  // points and exported functions.  Returns true if any call |pfn| returns
+  // true.  By convention |pfn| should return true if it modified the module.
+  bool ProcessReachableCallTree(ProcessFunction& pfn, ir::Module* module);
+
+  // Applies |pfn| to every function in the call trees rooted at the elements of
+  // |roots|.  Returns true if any call to |pfn| returns true.  By convention
+  // |pfn| should return true if it modified the module.  After returning
+  // |roots| will be empty.
+  bool ProcessCallTreeFromRoots(
+      ProcessFunction& pfn,
+      const std::unordered_map<uint32_t, ir::Function*>& id2function,
+      std::queue<uint32_t>* roots);
+
   // Processes the given |module|. Returns Status::Failure if errors occur when
   // processing. Returns the corresponding Status::Success if processing is
   // succesful to indicate whether changes are made to the module.
diff --git a/source/opt/pass_manager.cpp b/source/opt/pass_manager.cpp
index 18267db..2def7bb 100644
--- a/source/opt/pass_manager.cpp
+++ b/source/opt/pass_manager.cpp
@@ -28,6 +28,7 @@
   if (status == Pass::Status::SuccessWithChange) {
     module->SetIdBound(module->ComputeIdBound());
   }
+  passes_.clear();
   return status;
 }
 
diff --git a/source/opt/pass_manager.h b/source/opt/pass_manager.h
index 0bf29f7..2cf9159 100644
--- a/source/opt/pass_manager.h
+++ b/source/opt/pass_manager.h
@@ -63,6 +63,8 @@
   // registered after the error-reporting pass will be skipped. Returns the
   // corresponding Status::Success if processing is succesful to indicate
   // whether changes are made to the module.
+  //
+  // After running all the passes, they are removed from the list.
   Pass::Status Run(ir::Module* module);
 
  private:
diff --git a/source/opt/passes.h b/source/opt/passes.h
index c1fdd6a..657f494 100644
--- a/source/opt/passes.h
+++ b/source/opt/passes.h
@@ -18,9 +18,11 @@
 // A single header to include all passes.
 
 #include "block_merge_pass.h"
+#include "cfg_cleanup_pass.h"
 #include "common_uniform_elim_pass.h"
 #include "compact_ids_pass.h"
 #include "dead_branch_elim_pass.h"
+#include "dead_variable_elimination.h"
 #include "eliminate_dead_constant_pass.h"
 #include "flatten_decoration_pass.h"
 #include "fold_spec_constant_op_and_composite_pass.h"
@@ -35,7 +37,9 @@
 #include "aggressive_dead_code_elim_pass.h"
 #include "null_pass.h"
 #include "set_spec_constant_default_value_pass.h"
+#include "strength_reduction_pass.h"
 #include "strip_debug_info_pass.h"
 #include "unify_const_pass.h"
+#include "eliminate_dead_functions_pass.h"
 
 #endif  // LIBSPIRV_OPT_PASSES_H_
diff --git a/source/opt/reflect.h b/source/opt/reflect.h
index 16ea0bd..46d0049 100644
--- a/source/opt/reflect.h
+++ b/source/opt/reflect.h
@@ -24,9 +24,15 @@
 // following functions tend to be outdated and should be updated when SPIR-V
 // version bumps.
 
-inline bool IsDebugInst(SpvOp opcode) {
-  return (opcode >= SpvOpSourceContinued && opcode <= SpvOpLine) ||
-         opcode == SpvOpNoLine || opcode == SpvOpModuleProcessed;
+inline bool IsDebug1Inst(SpvOp opcode) {
+  return (opcode >= SpvOpSourceContinued && opcode <= SpvOpSourceExtension) ||
+         opcode == SpvOpString;
+}
+inline bool IsDebug2Inst(SpvOp opcode) {
+  return opcode == SpvOpName || opcode == SpvOpMemberName;
+}
+inline bool IsDebug3Inst(SpvOp opcode) {
+  return opcode == SpvOpModuleProcessed;
 }
 inline bool IsDebugLineInst(SpvOp opcode) {
   return opcode == SpvOpLine || opcode == SpvOpNoLine;
diff --git a/source/opt/remove_duplicates_pass.cpp b/source/opt/remove_duplicates_pass.cpp
new file mode 100644
index 0000000..a44e5dc
--- /dev/null
+++ b/source/opt/remove_duplicates_pass.cpp
@@ -0,0 +1,274 @@
+// Copyright (c) 2017 Pierre Moreau
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "remove_duplicates_pass.h"
+
+#include <cstring>
+
+#include <algorithm>
+#include <limits>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include "decoration_manager.h"
+#include "opcode.h"
+
+namespace spvtools {
+namespace opt {
+
+using ir::Instruction;
+using ir::Module;
+using ir::Operand;
+using opt::analysis::DefUseManager;
+using opt::analysis::DecorationManager;
+
+Pass::Status RemoveDuplicatesPass::Process(Module* module) {
+  DefUseManager defUseManager(consumer(), module);
+  DecorationManager decManager(module);
+
+  bool modified = RemoveDuplicateCapabilities(module);
+  modified |= RemoveDuplicatesExtInstImports(module, defUseManager);
+  modified |= RemoveDuplicateTypes(module, defUseManager, decManager);
+  modified |= RemoveDuplicateDecorations(module);
+
+  return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
+}
+
+bool RemoveDuplicatesPass::RemoveDuplicateCapabilities(Module* module) const {
+  bool modified = false;
+
+  std::unordered_set<uint32_t> capabilities;
+  for (auto i = module->capability_begin(); i != module->capability_end();) {
+    auto res = capabilities.insert(i->GetSingleWordOperand(0u));
+
+    if (res.second) {
+      // Never seen before, keep it.
+      ++i;
+    } else {
+      // It's a duplicate, remove it.
+      i = i.Erase();
+      modified = true;
+    }
+  }
+
+  return modified;
+}
+
+bool RemoveDuplicatesPass::RemoveDuplicatesExtInstImports(
+    Module* module, analysis::DefUseManager& defUseManager) const {
+  bool modified = false;
+
+  std::unordered_map<std::string, SpvId> extInstImports;
+  for (auto i = module->ext_inst_import_begin();
+       i != module->ext_inst_import_end();) {
+    auto res = extInstImports.emplace(
+        reinterpret_cast<const char*>(i->GetInOperand(0u).words.data()),
+        i->result_id());
+    if (res.second) {
+      // Never seen before, keep it.
+      ++i;
+    } else {
+      // It's a duplicate, remove it.
+      defUseManager.ReplaceAllUsesWith(i->result_id(), res.first->second);
+      i = i.Erase();
+      modified = true;
+    }
+  }
+
+  return modified;
+}
+
+bool RemoveDuplicatesPass::RemoveDuplicateTypes(
+    Module* module, DefUseManager& defUseManager,
+    DecorationManager& decManager) const {
+  bool modified = false;
+
+  std::vector<Instruction> visitedTypes;
+  visitedTypes.reserve(module->types_values().size());
+
+  for (auto i = module->types_values_begin();
+       i != module->types_values_end();) {
+    // We only care about types.
+    if (!spvOpcodeGeneratesType((i->opcode())) &&
+        i->opcode() != SpvOpTypeForwardPointer) {
+      ++i;
+      continue;
+    }
+
+    // Is the current type equal to one of the types we have aready visited?
+    SpvId idToKeep = 0u;
+    for (auto j : visitedTypes) {
+      if (AreTypesEqual(*i, j, defUseManager, decManager)) {
+        idToKeep = j.result_id();
+        break;
+      }
+    }
+
+    if (idToKeep == 0u) {
+      // This is a never seen before type, keep it around.
+      visitedTypes.emplace_back(*i);
+      ++i;
+    } else {
+      // The same type has already been seen before, remove this one.
+      defUseManager.ReplaceAllUsesWith(i->result_id(), idToKeep);
+      modified = true;
+      i = i.Erase();
+    }
+  }
+
+  return modified;
+}
+
+bool RemoveDuplicatesPass::RemoveDuplicateDecorations(
+    ir::Module* module) const {
+  bool modified = false;
+
+  std::unordered_map<SpvId, const Instruction*> constants;
+  for (const auto& i : module->types_values())
+    if (i.opcode() == SpvOpConstant) constants[i.result_id()] = &i;
+  for (const auto& i : module->types_values())
+    if (i.opcode() == SpvOpConstant) constants[i.result_id()] = &i;
+
+  std::vector<const Instruction*> visitedDecorations;
+  visitedDecorations.reserve(module->annotations().size());
+
+  opt::analysis::DecorationManager decorationManager(module);
+  for (auto i = module->annotation_begin(); i != module->annotation_end();) {
+    // Is the current decoration equal to one of the decorations we have aready
+    // visited?
+    bool alreadyVisited = false;
+    for (const Instruction* j : visitedDecorations) {
+      if (decorationManager.AreDecorationsTheSame(&*i, j)) {
+        alreadyVisited = true;
+        break;
+      }
+    }
+
+    if (!alreadyVisited) {
+      // This is a never seen before decoration, keep it around.
+      visitedDecorations.emplace_back(&*i);
+      ++i;
+    } else {
+      // The same decoration has already been seen before, remove this one.
+      modified = true;
+      i = i.Erase();
+    }
+  }
+
+  return modified;
+}
+
+bool RemoveDuplicatesPass::AreTypesEqual(const Instruction& inst1,
+                                         const Instruction& inst2,
+                                         const DefUseManager& defUseManager,
+                                         const DecorationManager& decManager) {
+  if (inst1.opcode() != inst2.opcode()) return false;
+  if (!decManager.HaveTheSameDecorations(inst1.result_id(), inst2.result_id()))
+    return false;
+
+  switch (inst1.opcode()) {
+    case SpvOpTypeVoid:
+    case SpvOpTypeBool:
+    case SpvOpTypeSampler:
+    case SpvOpTypeEvent:
+    case SpvOpTypeDeviceEvent:
+    case SpvOpTypeReserveId:
+    case SpvOpTypeQueue:
+    case SpvOpTypePipeStorage:
+    case SpvOpTypeNamedBarrier:
+      return true;
+    case SpvOpTypeInt:
+      return inst1.GetSingleWordInOperand(0u) ==
+                 inst2.GetSingleWordInOperand(0u) &&
+             inst1.GetSingleWordInOperand(1u) ==
+                 inst2.GetSingleWordInOperand(1u);
+    case SpvOpTypeFloat:
+    case SpvOpTypePipe:
+    case SpvOpTypeForwardPointer:
+      return inst1.GetSingleWordInOperand(0u) ==
+             inst2.GetSingleWordInOperand(0u);
+    case SpvOpTypeVector:
+    case SpvOpTypeMatrix:
+      return AreTypesEqual(
+                 *defUseManager.GetDef(inst1.GetSingleWordInOperand(0u)),
+                 *defUseManager.GetDef(inst2.GetSingleWordInOperand(0u)),
+                 defUseManager, decManager) &&
+             inst1.GetSingleWordInOperand(1u) ==
+                 inst2.GetSingleWordInOperand(1u);
+    case SpvOpTypeImage:
+      return AreTypesEqual(
+                 *defUseManager.GetDef(inst1.GetSingleWordInOperand(0u)),
+                 *defUseManager.GetDef(inst2.GetSingleWordInOperand(0u)),
+                 defUseManager, decManager) &&
+             inst1.GetSingleWordInOperand(1u) ==
+                 inst2.GetSingleWordInOperand(1u) &&
+             inst1.GetSingleWordInOperand(2u) ==
+                 inst2.GetSingleWordInOperand(2u) &&
+             inst1.GetSingleWordInOperand(3u) ==
+                 inst2.GetSingleWordInOperand(3u) &&
+             inst1.GetSingleWordInOperand(4u) ==
+                 inst2.GetSingleWordInOperand(4u) &&
+             inst1.GetSingleWordInOperand(5u) ==
+                 inst2.GetSingleWordInOperand(5u) &&
+             inst1.GetSingleWordInOperand(6u) ==
+                 inst2.GetSingleWordInOperand(6u) &&
+             inst1.NumOperands() == inst2.NumOperands() &&
+             (inst1.NumInOperands() == 7u ||
+              inst1.GetSingleWordInOperand(7u) ==
+                  inst2.GetSingleWordInOperand(7u));
+    case SpvOpTypeSampledImage:
+    case SpvOpTypeRuntimeArray:
+      return AreTypesEqual(
+          *defUseManager.GetDef(inst1.GetSingleWordInOperand(0u)),
+          *defUseManager.GetDef(inst2.GetSingleWordInOperand(0u)),
+          defUseManager, decManager);
+    case SpvOpTypeArray:
+      return AreTypesEqual(
+                 *defUseManager.GetDef(inst1.GetSingleWordInOperand(0u)),
+                 *defUseManager.GetDef(inst2.GetSingleWordInOperand(0u)),
+                 defUseManager, decManager) &&
+             AreTypesEqual(
+                 *defUseManager.GetDef(inst1.GetSingleWordInOperand(1u)),
+                 *defUseManager.GetDef(inst2.GetSingleWordInOperand(1u)),
+                 defUseManager, decManager);
+    case SpvOpTypeStruct:
+    case SpvOpTypeFunction: {
+      bool res = inst1.NumInOperands() == inst2.NumInOperands();
+      for (uint32_t i = 0u; i < inst1.NumInOperands() && res; ++i)
+        res &= AreTypesEqual(
+            *defUseManager.GetDef(inst1.GetSingleWordInOperand(i)),
+            *defUseManager.GetDef(inst2.GetSingleWordInOperand(i)),
+            defUseManager, decManager);
+      return res;
+    }
+    case SpvOpTypeOpaque:
+      return std::strcmp(reinterpret_cast<const char*>(
+                             inst1.GetInOperand(0u).words.data()),
+                         reinterpret_cast<const char*>(
+                             inst2.GetInOperand(0u).words.data())) == 0;
+    case SpvOpTypePointer:
+      return inst1.GetSingleWordInOperand(0u) ==
+                 inst2.GetSingleWordInOperand(0u) &&
+             AreTypesEqual(
+                 *defUseManager.GetDef(inst1.GetSingleWordInOperand(1u)),
+                 *defUseManager.GetDef(inst2.GetSingleWordInOperand(1u)),
+                 defUseManager, decManager);
+    default:
+      return false;
+  }
+}
+
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/remove_duplicates_pass.h b/source/opt/remove_duplicates_pass.h
new file mode 100644
index 0000000..fcf4a05
--- /dev/null
+++ b/source/opt/remove_duplicates_pass.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2017 Pierre Moreau
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef LIBSPIRV_OPT_REMOVE_DUPLICATES_PASS_H_
+#define LIBSPIRV_OPT_REMOVE_DUPLICATES_PASS_H_
+
+#include <unordered_map>
+
+#include "decoration_manager.h"
+#include "def_use_manager.h"
+#include "module.h"
+#include "pass.h"
+
+namespace spvtools {
+namespace opt {
+
+using IdDecorationsList =
+    std::unordered_map<uint32_t, std::vector<ir::Instruction*>>;
+
+// See optimizer.hpp for documentation.
+class RemoveDuplicatesPass : public Pass {
+ public:
+  const char* name() const override { return "remove-duplicates"; }
+  Status Process(ir::Module*) override;
+  // Returns whether two types are equal, and have the same decorations.
+  static bool AreTypesEqual(const ir::Instruction& inst1,
+                            const ir::Instruction& inst2,
+                            const analysis::DefUseManager& defUseManager,
+                            const analysis::DecorationManager& decoManager);
+
+ private:
+  bool RemoveDuplicateCapabilities(ir::Module* module) const;
+  bool RemoveDuplicatesExtInstImports(
+      ir::Module* module, analysis::DefUseManager& defUseManager) const;
+  bool RemoveDuplicateTypes(ir::Module* module,
+                            analysis::DefUseManager& defUseManager,
+                            analysis::DecorationManager& decManager) const;
+  bool RemoveDuplicateDecorations(ir::Module* module) const;
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // LIBSPIRV_OPT_REMOVE_DUPLICATES_PASS_H_
diff --git a/source/opt/strength_reduction_pass.cpp b/source/opt/strength_reduction_pass.cpp
new file mode 100644
index 0000000..58928c1
--- /dev/null
+++ b/source/opt/strength_reduction_pass.cpp
@@ -0,0 +1,210 @@
+// Copyright (c) 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "strength_reduction_pass.h"
+
+#include <algorithm>
+#include <cstdio>
+#include <cstring>
+#include <unordered_map>
+#include <unordered_set>
+
+#include "def_use_manager.h"
+#include "log.h"
+#include "reflect.h"
+
+namespace {
+// Count the number of trailing zeros in the binary representation of
+// |constVal|.
+uint32_t CountTrailingZeros(uint32_t constVal) {
+  // Faster if we use the hardware count trailing zeros instruction.
+  // If not available, we could create a table.
+  uint32_t shiftAmount = 0;
+  while ((constVal & 1) == 0) {
+    ++shiftAmount;
+    constVal = (constVal >> 1);
+  }
+  return shiftAmount;
+}
+
+// Return true if |val| is a power of 2.
+bool IsPowerOf2(uint32_t val) {
+  // The idea is that the & will clear out the least
+  // significant 1 bit.  If it is a power of 2, then
+  // there is exactly 1 bit set, and the value becomes 0.
+  if (val == 0) return false;
+  return ((val - 1) & val) == 0;
+}
+
+}  // namespace
+
+namespace spvtools {
+namespace opt {
+
+Pass::Status StrengthReductionPass::Process(ir::Module* module) {
+  // Initialize the member variables on a per module basis.
+  bool modified = false;
+  def_use_mgr_.reset(new analysis::DefUseManager(consumer(), module));
+  int32_type_id_ = 0;
+  uint32_type_id_ = 0;
+  std::memset(constant_ids_, 0, sizeof(constant_ids_));
+  next_id_ = module->IdBound();
+  module_ = module;
+
+  FindIntTypesAndConstants();
+  modified = ScanFunctions();
+  // Have to reset the id bound.
+  module->SetIdBound(next_id_);
+  return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange);
+}
+
+bool StrengthReductionPass::ReplaceMultiplyByPowerOf2(
+    ir::BasicBlock::iterator* instPtr) {
+  ir::BasicBlock::iterator& inst = *instPtr;
+  assert(inst->opcode() == SpvOp::SpvOpIMul &&
+         "Only works for multiplication of integers.");
+  bool modified = false;
+
+  // Currently only works on 32-bit integers.
+  if (inst->type_id() != int32_type_id_ && inst->type_id() != uint32_type_id_) {
+    return modified;
+  }
+
+  // Check the operands for a constant that is a power of 2.
+  for (int i = 0; i < 2; i++) {
+    uint32_t opId = inst->GetSingleWordInOperand(i);
+    ir::Instruction* opInst = def_use_mgr_->GetDef(opId);
+    if (opInst->opcode() == SpvOp::SpvOpConstant) {
+      // We found a constant operand.
+      uint32_t constVal = opInst->GetSingleWordOperand(2);
+
+      if (IsPowerOf2(constVal)) {
+        modified = true;
+        uint32_t shiftAmount = CountTrailingZeros(constVal);
+        uint32_t shiftConstResultId = GetConstantId(shiftAmount);
+
+        // Create the new instruction.
+        uint32_t newResultId = next_id_++;
+        std::vector<ir::Operand> newOperands;
+        newOperands.push_back(inst->GetInOperand(1 - i));
+        ir::Operand shiftOperand(spv_operand_type_t::SPV_OPERAND_TYPE_ID,
+                                 {shiftConstResultId});
+        newOperands.push_back(shiftOperand);
+        std::unique_ptr<ir::Instruction> newInstruction(
+            new ir::Instruction(SpvOp::SpvOpShiftLeftLogical, inst->type_id(),
+                                newResultId, newOperands));
+
+        // Insert the new instruction and update the data structures.
+        def_use_mgr_->AnalyzeInstDefUse(&*newInstruction);
+        inst = inst.InsertBefore(std::move(newInstruction));
+        ++inst;
+        def_use_mgr_->ReplaceAllUsesWith(inst->result_id(), newResultId);
+
+        // Remove the old instruction.
+        def_use_mgr_->KillInst(&*inst);
+
+        // We do not want to replace the instruction twice if both operands
+        // are constants that are a power of 2.  So we break here.
+        break;
+      }
+    }
+  }
+
+  return modified;
+}
+
+void StrengthReductionPass::FindIntTypesAndConstants() {
+  for (auto iter = module_->types_values_begin();
+       iter != module_->types_values_end(); ++iter) {
+    switch (iter->opcode()) {
+      case SpvOp::SpvOpTypeInt:
+        if (iter->GetSingleWordOperand(1) == 32) {
+          if (iter->GetSingleWordOperand(2) == 1) {
+            int32_type_id_ = iter->result_id();
+          } else {
+            uint32_type_id_ = iter->result_id();
+          }
+        }
+        break;
+      case SpvOp::SpvOpConstant:
+        if (iter->type_id() == uint32_type_id_) {
+          uint32_t value = iter->GetSingleWordOperand(2);
+          if (value <= 32) constant_ids_[value] = iter->result_id();
+        }
+        break;
+      default:
+        break;
+    }
+  }
+}
+
+uint32_t StrengthReductionPass::GetConstantId(uint32_t val) {
+  assert(val <= 32 &&
+         "This function does not handle constants larger than 32.");
+
+  if (constant_ids_[val] == 0) {
+    if (uint32_type_id_ == 0) {
+      uint32_type_id_ = CreateUint32Type();
+    }
+
+    // Construct the constant.
+    uint32_t resultId = next_id_++;
+    ir::Operand constant(spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
+                         {val});
+    std::unique_ptr<ir::Instruction> newConstant(new ir::Instruction(
+        SpvOp::SpvOpConstant, uint32_type_id_, resultId, {constant}));
+    module_->AddGlobalValue(std::move(newConstant));
+
+    // Store the result id for next time.
+    constant_ids_[val] = resultId;
+  }
+
+  return constant_ids_[val];
+}
+
+bool StrengthReductionPass::ScanFunctions() {
+  // I did not use |ForEachInst| in the module because the function that acts on
+  // the instruction gets a pointer to the instruction.  We cannot use that to
+  // insert a new instruction.  I want an iterator.
+  bool modified = false;
+  for (auto& func : *module_) {
+    for (auto& bb : func) {
+      for (auto inst = bb.begin(); inst != bb.end(); ++inst) {
+        switch (inst->opcode()) {
+          case SpvOp::SpvOpIMul:
+            if (ReplaceMultiplyByPowerOf2(&inst)) modified = true;
+            break;
+          default:
+            break;
+        }
+      }
+    }
+  }
+  return modified;
+}
+
+uint32_t StrengthReductionPass::CreateUint32Type() {
+  uint32_t type_id = next_id_++;
+  ir::Operand widthOperand(spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
+                           {32});
+  ir::Operand signOperand(spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
+                          {0});
+  std::unique_ptr<ir::Instruction> newType(new ir::Instruction(
+      SpvOp::SpvOpTypeInt, type_id, 0, {widthOperand, signOperand}));
+  module_->AddType(std::move(newType));
+  return type_id;
+}
+
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/strength_reduction_pass.h b/source/opt/strength_reduction_pass.h
new file mode 100644
index 0000000..3d39d45
--- /dev/null
+++ b/source/opt/strength_reduction_pass.h
@@ -0,0 +1,75 @@
+// Copyright (c) 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef LIBSPIRV_OPT_STRENGTH_REDUCTION_PASS_H_
+#define LIBSPIRV_OPT_STRENGTH_REDUCTION_PASS_H_
+
+#include "def_use_manager.h"
+#include "module.h"
+#include "pass.h"
+
+namespace spvtools {
+namespace opt {
+
+// See optimizer.hpp for documentation.
+class StrengthReductionPass : public Pass {
+ public:
+  const char* name() const override { return "strength-reduction"; }
+  Status Process(ir::Module*) override;
+
+ private:
+  // Replaces multiple by power of 2 with an equivalent bit shift.
+  // Returns true if something changed.
+  bool ReplaceMultiplyByPowerOf2(ir::BasicBlock::iterator*);
+
+  // Scan the types and constants in the module looking for the the integer types that we are
+  // interested in.  The shift operation needs a small unsigned integer.  We need to find
+  // them or create them.  We do not want duplicates.
+  void FindIntTypesAndConstants();
+
+  // Get the id for the given constant.  If it does not exist, it will be
+  // created.  The parameter must be between 0 and 32 inclusive.
+  uint32_t GetConstantId(uint32_t);
+
+  // Replaces certain instructions in function bodies with presumably cheaper
+  // ones. Returns true if something changed.
+  bool ScanFunctions();
+
+  // Will create the type for an unsigned 32-bit integer and return the id.
+  // This functions assumes one does not already exist.
+  uint32_t CreateUint32Type();
+
+  // Def-Uses for the module we are processing
+  std::unique_ptr<analysis::DefUseManager> def_use_mgr_;
+
+  // Type ids for the types of interest, or 0 if they do not exist.
+  uint32_t int32_type_id_;
+  uint32_t uint32_type_id_;
+
+  // constant_ids[i] is the id for unsigned integer constant i.
+  // We set the limit at 32 because a bit shift of a 32-bit integer does not
+  // need a value larger than 32.
+  uint32_t constant_ids_[33];
+
+  // Next unused ID
+  uint32_t next_id_;
+
+  // The module that the pass is being applied to.
+  ir::Module* module_;
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // LIBSPIRV_OPT_STRENGTH_REDUCTION_PASS_H_
diff --git a/source/opt/strip_debug_info_pass.cpp b/source/opt/strip_debug_info_pass.cpp
index 45dd344..3b4b443 100644
--- a/source/opt/strip_debug_info_pass.cpp
+++ b/source/opt/strip_debug_info_pass.cpp
@@ -18,7 +18,8 @@
 namespace opt {
 
 Pass::Status StripDebugInfoPass::Process(ir::Module* module) {
-  bool modified = !module->debugs().empty();
+  bool modified = !module->debugs1().empty() || !module->debugs2().empty() ||
+                  !module->debugs3().empty();
   module->debug_clear();
 
   module->ForEachInst([&modified](ir::Instruction* inst) {
diff --git a/source/util/bit_stream.cpp b/source/util/bit_stream.cpp
index 56f3700..d66f13d 100644
--- a/source/util/bit_stream.cpp
+++ b/source/util/bit_stream.cpp
@@ -414,6 +414,7 @@
 
   if (pos_ >= buffer_.size() * 64) {
     // Reached end of buffer_.
+    EmitSequence(*bits, num_read_from_first_word);
     return num_read_from_first_word;
   }
 
@@ -426,6 +427,7 @@
 
   // We likely have written more bits than requested. Clear excessive bits.
   *bits = GetLowerBits(*bits, num_bits);
+  EmitSequence(*bits, num_bits);
   return num_bits;
 }
 
diff --git a/source/util/bit_stream.h b/source/util/bit_stream.h
index b626d2c..f98b74b 100644
--- a/source/util/bit_stream.h
+++ b/source/util/bit_stream.h
@@ -436,9 +436,26 @@
   bool OnlyZeroesLeft() const override;
 
   BitReaderWord64() = delete;
+
+  // Sets callback to emit bit sequences after every read.
+  void SetCallback(std::function<void(const std::string&)> callback) {
+    callback_ = callback;
+  }
+
+ protected:
+  // Sends string generated from arguments to callback_ if defined.
+  void EmitSequence(uint64_t bits, size_t num_bits) const {
+    if (callback_)
+      callback_(BitsToStream(bits, num_bits));
+  }
+
  private:
   const std::vector<uint64_t> buffer_;
   size_t pos_;
+
+  // If not null, the reader will use the callback to emit the read bit
+  // sequence as a string of '0' and '1'.
+  std::function<void(const std::string&)> callback_;
 };
 
 }  // namespace spvutils
diff --git a/source/util/hex_float.h b/source/util/hex_float.h
index ac7e002..83f3974 100644
--- a/source/util/hex_float.h
+++ b/source/util/hex_float.h
@@ -25,6 +25,13 @@
 
 #include "bitutils.h"
 
+#ifndef __GNUC__
+#define GCC_VERSION 0
+#else
+#define GCC_VERSION \
+  (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__)
+#endif
+
 namespace spvutils {
 
 class Float16 {
@@ -439,11 +446,38 @@
     return significand;
   }
 
+#if GCC_VERSION == 40801
   // These exist because MSVC throws warnings on negative right-shifts
   // even if they are not going to be executed. Eg:
   // constant_number < 0? 0: constant_number
   // These convert the negative left-shifts into right shifts.
+  template <int_type N>
+  struct negatable_left_shift {
+    static uint_type val(uint_type val) {
+      if (N > 0) {
+        return static_cast<uint_type>(val << N);
+      } else {
+        return static_cast<uint_type>(val >> N);
+      }
+    }
+  };
 
+  template <int_type N>
+  struct negatable_right_shift {
+    static uint_type val(uint_type val) {
+      if (N > 0) {
+        return static_cast<uint_type>(val >> N);
+      } else {
+        return static_cast<uint_type>(val << N);
+      }
+    }
+  };
+
+#else
+  // These exist because MSVC throws warnings on negative right-shifts
+  // even if they are not going to be executed. Eg:
+  // constant_number < 0? 0: constant_number
+  // These convert the negative left-shifts into right shifts.
   template <int_type N, typename enable = void>
   struct negatable_left_shift {
     static uint_type val(uint_type val) {
@@ -471,6 +505,7 @@
       return static_cast<uint_type>(val >> N);
     }
   };
+#endif
 
   // Returns the significand, rounded to fit in a significand in
   // other_T. This is shifted so that the most significant
@@ -1071,6 +1106,6 @@
   os << HexFloat<FloatProxy<Float16>>(value);
   return os;
 }
-}
+}  // namespace spvutils
 
 #endif  // LIBSPIRV_UTIL_HEX_FLOAT_H_
diff --git a/source/util/huffman_codec.h b/source/util/huffman_codec.h
index 3588020..2ccc3c9 100644
--- a/source/util/huffman_codec.h
+++ b/source/util/huffman_codec.h
@@ -210,7 +210,7 @@
 
   // Encodes |val| and stores its Huffman code in the lower |num_bits| of
   // |bits|. Returns false of |val| is not in the Huffman table.
-  bool Encode(const Val& val, uint64_t* bits, size_t* num_bits) {
+  bool Encode(const Val& val, uint64_t* bits, size_t* num_bits) const {
     auto it = encoding_table_.find(val);
     if (it == encoding_table_.end())
       return false;
@@ -225,7 +225,8 @@
   // |read_bit| has type bool func(bool* bit). When called, the next bit is
   // stored in |bit|. |read_bit| returns false if the stream terminates
   // prematurely.
-  bool DecodeFromStream(const std::function<bool(bool*)>& read_bit, Val* val) {
+  bool DecodeFromStream(
+      const std::function<bool(bool*)>& read_bit, Val* val) const {
     uint32_t node = root_;
     while (true) {
       assert(node);
diff --git a/source/util/ilist.h b/source/util/ilist.h
new file mode 100644
index 0000000..68dc641
--- /dev/null
+++ b/source/util/ilist.h
@@ -0,0 +1,317 @@
+// Copyright (c) 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef LIBSPIRV_OPT_ILIST_H_
+#define LIBSPIRV_OPT_ILIST_H_
+
+#include <cassert>
+#include <memory>
+#include <type_traits>
+#include <vector>
+
+#include "ilist_node.h"
+
+namespace spvtools {
+namespace utils {
+
+// An IntrusiveList is a generic implementation of a doubly-linked list.  The
+// intended convention for using this container is:
+//
+//      class Node : public IntrusiveNodeBase<Node> {
+//        // Note that "Node", the class being defined is the template.
+//        // Must have a default constructor accessible to List.
+//        // Add whatever data is needed in the node
+//      };
+//
+//      using List = IntrusiveList<Node>;
+//
+// You can also inherit from IntrusiveList instead of a typedef if you want to
+// add more functionality.
+//
+// The condition on the template for IntrusiveNodeBase is there to add some type
+// checking to the container.  The compiler will still allow inserting elements
+// of type IntrusiveNodeBase<Node>, but that would be an error. This assumption
+// allows NextNode and PreviousNode to return pointers to Node, and casting will
+// not be required by the user.
+
+template <class NodeType>
+class IntrusiveList {
+ public:
+  static_assert(
+      std::is_base_of<IntrusiveNodeBase<NodeType>, NodeType>::value,
+      "The type from the node must be derived from IntrusiveNodeBase, with "
+      "itself in the template.");
+
+  // Creates an empty list.
+  inline IntrusiveList();
+
+  // Moves the contents of the given list to the list being constructed.
+  IntrusiveList(IntrusiveList&&);
+
+  // Destorys the list.  Note that the elements of the list will not be deleted,
+  // but they will be removed from the list.
+  virtual ~IntrusiveList();
+
+  // Moves all of the elements in the list on the RHS to the list on the LHS.
+  IntrusiveList& operator=(IntrusiveList&&);
+
+  // Basetype for iterators so an IntrusiveList can be traversed like STL
+  // containers.
+  template <class T>
+  class iterator_template {
+   public:
+    iterator_template(const iterator_template& i) : node_(i.node_) {}
+
+    iterator_template& operator++() {
+      node_ = node_->next_node_;
+      return *this;
+    }
+
+    iterator_template& operator--() {
+      node_ = node_->previous_node_;
+      return *this;
+    }
+
+    iterator_template& operator=(const iterator_template& i) {
+      node_ = i.node_;
+      return *this;
+    }
+
+    T& operator*() const { return *node_; }
+    T* operator->() const { return node_; }
+
+    friend inline bool operator==(const iterator_template& lhs,
+                                  const iterator_template& rhs) {
+      return lhs.node_ == rhs.node_;
+    }
+    friend inline bool operator!=(const iterator_template& lhs,
+                                  const iterator_template& rhs) {
+      return !(lhs == rhs);
+    }
+
+    // Moves the nodes in |list| to the list that |this| points to.  The
+    // positions of the nodes will be immediately before the element pointed to
+    // by the iterator.  The return value will be an iterator pointing to the
+    // first of the newly inserted elements.
+    iterator_template MoveBefore(IntrusiveList* list) {
+      if (list->empty()) return *this;
+
+      NodeType* first_node = list->sentinel_.next_node_;
+      NodeType* last_node = list->sentinel_.previous_node_;
+
+      this->node_->previous_node_->next_node_ = first_node;
+      first_node->previous_node_ = this->node_->previous_node_;
+
+      last_node->next_node_ = this->node_;
+      this->node_->previous_node_ = last_node;
+
+      list->sentinel_.next_node_ = &list->sentinel_;
+      list->sentinel_.previous_node_ = &list->sentinel_;
+
+      return iterator(first_node);
+    }
+
+   protected:
+    iterator_template() = delete;
+    inline iterator_template(T* node) { node_ = node; }
+    T* node_;
+
+    friend IntrusiveList;
+  };
+
+  using iterator = iterator_template<NodeType>;
+  using const_iterator = iterator_template<const NodeType>;
+
+  // Various types of iterators for the start (begin) and one past the end (end)
+  // of the list.
+  //
+  // Decrementing |end()| iterator will give and iterator pointing to the last
+  // element in the list, if one exists.
+  //
+  // Incrementing |end()| iterator will give |begin()|.
+  //
+  // Decrementing |begin()| will give |end()|.
+  //
+  // TODO: Not marking these functions as noexcept because Visual Studio 2013
+  // does not support it.  When we no longer care about that compiler, we should
+  // mark these as noexcept.
+  iterator begin();
+  iterator end();
+  const_iterator begin() const;
+  const_iterator end() const;
+  const_iterator cbegin() const;
+  const_iterator cend() const;
+
+  // Appends |node| to the end of the list.  If |node| is already in a list, it
+  // will be removed from that list first.
+  void push_back(NodeType* node);
+
+  // Returns true if the list is empty.
+  bool empty() const;
+
+  // Returns references to the first or last element in the list.  It is an
+  // error to call these functions on an empty list.
+  NodeType& front();
+  NodeType& back();
+  const NodeType& front() const;
+  const NodeType& back() const;
+
+ protected:
+  // Doing a deep copy of the list does not make sense if the list does not own
+  // the data.  It is not clear who will own the newly created data.  Making
+  // copies illegal for that reason.
+  IntrusiveList(const IntrusiveList&) = delete;
+  IntrusiveList& operator=(const IntrusiveList&) = delete;
+
+  // This function will assert if it finds the list containing |node| is not in
+  // a valid state.
+  static void Check(NodeType* node);
+
+  // A special node used to represent both the start and end of the list,
+  // without being part of the list.
+  NodeType sentinel_;
+};
+
+// Implementation of IntrusiveList
+
+template <class NodeType>
+inline IntrusiveList<NodeType>::IntrusiveList() : sentinel_() {
+  sentinel_.next_node_ = &sentinel_;
+  sentinel_.previous_node_ = &sentinel_;
+  sentinel_.is_sentinel_ = true;
+}
+
+template <class NodeType>
+IntrusiveList<NodeType>::IntrusiveList(IntrusiveList&& list) : sentinel_() {
+  sentinel_.next_node_ = &sentinel_;
+  sentinel_.previous_node_ = &sentinel_;
+  sentinel_.is_sentinel_ = true;
+  list.sentinel_.ReplaceWith(&sentinel_);
+}
+
+template <class NodeType>
+IntrusiveList<NodeType>::~IntrusiveList() {
+  while (!empty()) {
+    front().RemoveFromList();
+  }
+}
+
+template <class NodeType>
+IntrusiveList<NodeType>& IntrusiveList<NodeType>::operator=(
+    IntrusiveList<NodeType>&& list) {
+  list.sentinel_.ReplaceWith(&sentinel_);
+  return *this;
+}
+
+template <class NodeType>
+inline typename IntrusiveList<NodeType>::iterator
+IntrusiveList<NodeType>::begin() {
+  return iterator(sentinel_.next_node_);
+}
+
+template <class NodeType>
+inline typename IntrusiveList<NodeType>::iterator
+IntrusiveList<NodeType>::end() {
+  return iterator(&sentinel_);
+}
+
+template <class NodeType>
+inline typename IntrusiveList<NodeType>::const_iterator
+IntrusiveList<NodeType>::begin() const {
+  return const_iterator(sentinel_.next_node_);
+}
+
+template <class NodeType>
+inline typename IntrusiveList<NodeType>::const_iterator
+IntrusiveList<NodeType>::end() const {
+  return const_iterator(&sentinel_);
+}
+
+template <class NodeType>
+inline typename IntrusiveList<NodeType>::const_iterator
+IntrusiveList<NodeType>::cbegin() const {
+  return const_iterator(sentinel_.next_node_);
+}
+
+template <class NodeType>
+inline typename IntrusiveList<NodeType>::const_iterator
+IntrusiveList<NodeType>::cend() const {
+  return const_iterator(&sentinel_);
+}
+
+template <class NodeType>
+void IntrusiveList<NodeType>::push_back(NodeType* node) {
+  node->InsertBefore(&sentinel_);
+}
+
+template <class NodeType>
+bool IntrusiveList<NodeType>::empty() const {
+  return sentinel_.NextNode() == nullptr;
+}
+
+template <class NodeType>
+NodeType& IntrusiveList<NodeType>::front() {
+  NodeType* node = sentinel_.NextNode();
+  assert(node != nullptr && "Can't get the front of an empty list.");
+  return *node;
+}
+
+template <class NodeType>
+NodeType& IntrusiveList<NodeType>::back() {
+  NodeType* node = sentinel_.PreviousNode();
+  assert(node != nullptr && "Can't get the back of an empty list.");
+  return *node;
+}
+
+template <class NodeType>
+const NodeType& IntrusiveList<NodeType>::front() const {
+  NodeType* node = sentinel_.NextNode();
+  assert(node != nullptr && "Can't get the front of an empty list.");
+  return *node;
+}
+
+template <class NodeType>
+const NodeType& IntrusiveList<NodeType>::back() const {
+  NodeType* node = sentinel_.PreviousNode();
+  assert(node != nullptr && "Can't get the back of an empty list.");
+  return *node;
+}
+
+template <class NodeType>
+void IntrusiveList<NodeType>::Check(NodeType* start) {
+  int sentinel_count = 0;
+  NodeType* p = start;
+  do {
+    assert(p != nullptr);
+    assert(p->next_node_->previous_node_ == p);
+    assert(p->previous_node_->next_node_ == p);
+    if (p->is_sentinel_) sentinel_count++;
+    p = p->next_node_;
+  } while (p != start);
+  assert(sentinel_count == 1 && "List should have exactly 1 sentinel node.");
+
+  p = start;
+  do {
+    assert(p != nullptr);
+    assert(p->previous_node_->next_node_ == p);
+    assert(p->next_node_->previous_node_ == p);
+    if (p->is_sentinel_) sentinel_count++;
+    p = p->previous_node_;
+  } while (p != start);
+}
+
+}  // namespace utils
+}  // namespace spvtools
+
+#endif  // LIBSPIRV_OPT_ILIST_H_
diff --git a/source/util/ilist_node.h b/source/util/ilist_node.h
new file mode 100644
index 0000000..342fb1d
--- /dev/null
+++ b/source/util/ilist_node.h
@@ -0,0 +1,263 @@
+// Copyright (c) 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef LIBSPIRV_OPT_ILIST_NODE_H_
+#define LIBSPIRV_OPT_ILIST_NODE_H_
+
+#include <cassert>
+
+namespace spvtools {
+namespace utils {
+
+template <class NodeType>
+class IntrusiveList;
+
+// IntrusiveNodeBase is the base class for nodes in an IntrusiveList.
+// See the comments in ilist.h on how to use the class.
+
+template <class NodeType>
+class IntrusiveNodeBase {
+ public:
+  // Creates a new node that is not in a list.
+  inline IntrusiveNodeBase();
+  inline IntrusiveNodeBase(const IntrusiveNodeBase&);
+  inline IntrusiveNodeBase& operator=(const IntrusiveNodeBase&);
+  inline IntrusiveNodeBase(IntrusiveNodeBase&& that);
+
+  // Destroys a node.  It is an error to destroy a node that is part of a
+  // list, unless it is a sentinel.
+  virtual ~IntrusiveNodeBase();
+
+  IntrusiveNodeBase& operator=(IntrusiveNodeBase&& that);
+
+  // Returns true if |this| is in a list.
+  inline bool IsInAList() const;
+
+  // Returns the node that comes after the given node in the list, if one
+  // exists.  If the given node is not in a list or is at the end of the list,
+  // the return value is nullptr.
+  inline NodeType* NextNode() const;
+
+  // Returns the node that comes before the given node in the list, if one
+  // exists.  If the given node is not in a list or is at the start of the
+  // list, the return value is nullptr.
+  inline NodeType* PreviousNode() const;
+
+  // Inserts the given node immediately before |pos| in the list.
+  // If the given node is already in a list, it will first be removed
+  // from that list.
+  //
+  // It is assumed that the given node is of type NodeType.  It is an error if
+  // |pos| is not already in a list.
+  inline void InsertBefore(NodeType* pos);
+
+  // Inserts the given node immediately after |pos| in the list.
+  // If the given node is already in a list, it will first be removed
+  // from that list.
+  //
+  // It is assumed that the given node is of type NodeType.  It is an error if
+  // |pos| is not already in a list.
+  inline void InsertAfter(NodeType* pos);
+
+  // Removes the given node from the list.  It is assumed that the node is
+  // in a list.  Note that this does not free any storage related to the node,
+  // it becomes the caller's responsibility to free the storage.
+  inline void RemoveFromList();
+
+ protected:
+  // Replaces |this| with |target|.  |this| is a sentinel if and only if
+  // |target| is also a sentinel.
+  //
+  // If neither node is a sentinel, |target| takes
+  // the place of |this|.  It is assumed that |target| is not in a list.
+  //
+  // If both are sentinels, then it will cause all of the
+  // nodes in the list containing |this| to be moved to the list containing
+  // |target|.  In this case, it is assumed that |target| is an empty list.
+  //
+  // No storage will be deleted.
+  void ReplaceWith(NodeType* target);
+
+  // Returns true if |this| is the sentinel node of an empty list.
+  bool IsEmptyList();
+
+  // The pointers to the next and previous nodes in the list.
+  // If the current node is not part of a list, then |next_node_| and
+  // |previous_node_| are equal to |nullptr|.
+  NodeType* next_node_;
+  NodeType* previous_node_;
+
+  // Only true for the sentinel node stored in the list itself.
+  bool is_sentinel_;
+
+  friend IntrusiveList<NodeType>;
+};
+
+// Implementation of IntrusiveNodeBase
+
+template <class NodeType>
+inline IntrusiveNodeBase<NodeType>::IntrusiveNodeBase()
+    : next_node_(nullptr), previous_node_(nullptr), is_sentinel_(false) {}
+
+template<class NodeType>
+inline IntrusiveNodeBase<NodeType>::IntrusiveNodeBase(
+    const IntrusiveNodeBase&) {
+  next_node_ = nullptr;
+  previous_node_ = nullptr;
+  is_sentinel_ = false;
+}
+
+template<class NodeType>
+inline IntrusiveNodeBase<NodeType>& IntrusiveNodeBase<NodeType>::operator=(
+    const IntrusiveNodeBase&) {
+  assert(!is_sentinel_);
+  if (IsInAList()) {
+    RemoveFromList();
+  }
+  return *this;
+}
+
+template<class NodeType>
+inline IntrusiveNodeBase<NodeType>::IntrusiveNodeBase(IntrusiveNodeBase&& that)
+    : next_node_(nullptr),
+      previous_node_(nullptr),
+      is_sentinel_(that.is_sentinel_) {
+  if (is_sentinel_) {
+    next_node_ = this;
+    previous_node_ = this;
+  }
+  that.ReplaceWith(this);
+}
+
+template<class NodeType>
+IntrusiveNodeBase<NodeType>::~IntrusiveNodeBase() {
+  assert(is_sentinel_ || !IsInAList());
+}
+
+template<class NodeType>
+IntrusiveNodeBase<NodeType>& IntrusiveNodeBase<NodeType>::operator=(
+    IntrusiveNodeBase&& that) {
+  that.ReplaceWith(this);
+  return *this;
+}
+
+template<class NodeType>
+inline bool IntrusiveNodeBase<NodeType>::IsInAList() const {
+  return next_node_ != nullptr;
+}
+
+template <class NodeType>
+inline NodeType* IntrusiveNodeBase<NodeType>::NextNode() const {
+  if (!next_node_->is_sentinel_) return next_node_;
+  return nullptr;
+}
+
+template <class NodeType>
+inline NodeType* IntrusiveNodeBase<NodeType>::PreviousNode() const {
+  if (!previous_node_->is_sentinel_) return previous_node_;
+  return nullptr;
+}
+
+template <class NodeType>
+inline void IntrusiveNodeBase<NodeType>::InsertBefore(NodeType* pos) {
+  assert(!this->is_sentinel_ && "Sentinel nodes cannot be moved around.");
+  assert(pos->IsInAList() && "Pos should already be in a list.");
+  if (this->IsInAList()) this->RemoveFromList();
+
+  this->next_node_ = pos;
+  this->previous_node_ = pos->previous_node_;
+  pos->previous_node_ = static_cast<NodeType*>(this);
+  this->previous_node_->next_node_ = static_cast<NodeType*>(this);
+}
+
+template <class NodeType>
+inline void IntrusiveNodeBase<NodeType>::InsertAfter(NodeType* pos) {
+  assert(!this->is_sentinel_ && "Sentinel nodes cannot be moved around.");
+  assert(pos->IsInAList() && "Pos should already be in a list.");
+  if (this->IsInAList()) {
+    this->RemoveFromList();
+  }
+
+  this->previous_node_ = pos;
+  this->next_node_ = pos->next_node_;
+  pos->next_node_ = static_cast<NodeType*>(this);
+  this->next_node_->previous_node_ = static_cast<NodeType*>(this);
+}
+
+template <class NodeType>
+inline void IntrusiveNodeBase<NodeType>::RemoveFromList() {
+  assert(!this->is_sentinel_ && "Sentinel nodes cannot be moved around.");
+  assert(this->IsInAList() &&
+      "Cannot remove a node from a list if it is not in a list.");
+
+  this->next_node_->previous_node_ = this->previous_node_;
+  this->previous_node_->next_node_ = this->next_node_;
+  this->next_node_ = nullptr;
+  this->previous_node_ = nullptr;
+}
+
+template<class NodeType>
+void IntrusiveNodeBase<NodeType>::ReplaceWith(NodeType* target) {
+  if (this->is_sentinel_) {
+    assert(target->IsEmptyList() &&
+        "If target is not an empty list, the nodes in that list would not "
+            "be linked to a sentinel.");
+  } else {
+    assert(IsInAList() && "The node being replaced must be in a list.");
+    assert(!target->is_sentinel_ &&
+        "Cannot turn a sentinel node into one that is not.");
+  }
+
+  if (!this->IsEmptyList()) {
+    // Link target into the same position that |this| was in.
+    target->next_node_ = this->next_node_;
+    target->previous_node_ = this->previous_node_;
+    target->next_node_->previous_node_ = target;
+    target->previous_node_->next_node_ = target;
+
+    // Reset |this| to itself default value.
+    if (!this->is_sentinel_) {
+      // Reset |this| so that it is not in a list.
+      this->next_node_ = nullptr;
+      this->previous_node_ = nullptr;
+    } else {
+      // Set |this| so that it is the head of an empty list.
+      // We cannot treat sentinel nodes like others because it is invalid for
+      // a sentinel node to not be in a list.
+      this->next_node_ = static_cast<NodeType*>(this);
+      this->previous_node_ = static_cast<NodeType*>(this);
+    }
+  } else {
+    // If |this| points to itself, it must be a sentinel node with an empty
+    // list.  Reset |this| so that it is the head of an empty list.  We want
+    // |target| to be the same.  The asserts above guarantee that.
+  }
+}
+
+template<class NodeType>
+bool IntrusiveNodeBase<NodeType>::IsEmptyList() {
+  if (next_node_ == this) {
+    assert(is_sentinel_ &&
+               "None sentinel nodes should never point to themselves.");
+    assert(previous_node_ == this &&
+        "Inconsistency with the previous and next nodes.");
+    return true;
+  }
+  return false;
+}
+
+}  // namespace utils
+}  // namespace spvtools
+
+#endif  // LIBSPIRV_OPT_ILIST_NODE_H_
diff --git a/source/util/move_to_front.h b/source/util/move_to_front.h
index f02529e..efa79a7 100644
--- a/source/util/move_to_front.h
+++ b/source/util/move_to_front.h
@@ -325,7 +325,7 @@
  public:
   // Inserts |value| to sequence with handle |mtf|.
   // Returns false if |mtf| already has |value|.
-  bool Insert(uint32_t mtf, const Val& value) {
+  bool Insert(uint64_t mtf, const Val& value) {
     if (GetMtf(mtf).Insert(value)) {
       val_to_mtfs_[value].insert(mtf);
       return true;
@@ -335,7 +335,7 @@
 
   // Removes |value| from sequence with handle |mtf|.
   // Returns false if |mtf| doesn't have |value|.
-  bool Remove(uint32_t mtf, const Val& value) {
+  bool Remove(uint64_t mtf, const Val& value) {
     if (GetMtf(mtf).Remove(value)) {
       val_to_mtfs_[value].erase(mtf);
       return true;
@@ -351,7 +351,7 @@
       return;
 
     auto& mtfs_containing_value = it->second;
-    for (uint32_t mtf : mtfs_containing_value) {
+    for (uint64_t mtf : mtfs_containing_value) {
       GetMtf(mtf).Remove(value);
     }
 
@@ -360,18 +360,18 @@
 
   // Computes rank of |value| in sequence |mtf|.
   // Returns false if |mtf| doesn't have |value|.
-  bool RankFromValue(uint32_t mtf, const Val& value, uint32_t* rank) {
+  bool RankFromValue(uint64_t mtf, const Val& value, uint32_t* rank) {
     return GetMtf(mtf).RankFromValue(value, rank);
   }
 
   // Finds |value| with |rank| in sequence |mtf|.
   // Returns false if |rank| is out of bounds.
-  bool ValueFromRank(uint32_t mtf, uint32_t rank, Val* value) {
+  bool ValueFromRank(uint64_t mtf, uint32_t rank, Val* value) {
     return GetMtf(mtf).ValueFromRank(rank, value);
   }
 
   // Returns size of |mtf| sequence.
-  uint32_t GetSize(uint32_t mtf) {
+  uint32_t GetSize(uint64_t mtf) {
     return GetMtf(mtf).GetSize();
   }
 
@@ -382,20 +382,20 @@
       return;
 
     const auto& mtfs_containing_value = it->second;
-    for (uint32_t mtf : mtfs_containing_value) {
+    for (uint64_t mtf : mtfs_containing_value) {
       GetMtf(mtf).Promote(value);
     }
   }
 
   // Inserts |value| in sequence |mtf| or promotes if it's already there.
-  void InsertOrPromote(uint32_t mtf, const Val& value) {
+  void InsertOrPromote(uint64_t mtf, const Val& value) {
     if (!Insert(mtf, value)) {
       GetMtf(mtf).Promote(value);
     }
   }
 
   // Returns if |mtf| sequence has |value|.
-  bool HasValue(uint32_t mtf, const Val& value) {
+  bool HasValue(uint64_t mtf, const Val& value) {
     return GetMtf(mtf).HasValue(value);
   }
 
@@ -403,7 +403,7 @@
   // Returns actual MoveToFront object corresponding to |handle|.
   // As multiple operations are often performed consecutively for the same
   // sequence, the last returned value is cached.
-  MoveToFront<Val>& GetMtf(uint32_t handle) {
+  MoveToFront<Val>& GetMtf(uint64_t handle) {
     if (!cached_mtf_ || cached_handle_ != handle) {
       cached_handle_ = handle;
       cached_mtf_ = &mtfs_[handle];
@@ -413,13 +413,13 @@
   }
 
   // Container holding MoveToFront objects. Map key is sequence handle.
-  std::map<uint32_t, MoveToFront<Val>> mtfs_;
+  std::map<uint64_t, MoveToFront<Val>> mtfs_;
 
   // Container mapping value to sequences which contain that value.
-  std::unordered_map<Val, std::set<uint32_t>> val_to_mtfs_;
+  std::unordered_map<Val, std::set<uint64_t>> val_to_mtfs_;
 
   // Cache for the last accessed sequence.
-  uint32_t cached_handle_ = 0;
+  uint64_t cached_handle_ = 0;
   MoveToFront<Val>* cached_mtf_ = nullptr;
 };
 
@@ -471,7 +471,15 @@
   }
 
   const uint32_t old_size = GetSize();
-  (void)old_size;
+  if (old_size == 1) {
+    if (ValueOf(root_) == value) {
+      *rank = 1;
+      return true;
+    } else {
+      return false;
+    }
+  }
+
   const auto it = value_to_node_.find(value);
   if (it == value_to_node_.end()) {
     return false;
@@ -524,7 +532,9 @@
   }
 
   const uint32_t old_size = GetSize();
-  (void)old_size;
+  if (old_size == 1)
+    return ValueOf(root_) == value;
+
   const auto it = value_to_node_.find(value);
   if (it == value_to_node_.end()) {
     return false;
@@ -561,6 +571,11 @@
     return false;
   }
 
+  if (old_size == 1) {
+    *value = ValueOf(root_);
+    return true;
+  }
+
   const bool update_timestamp = (rank != 1);
 
   uint32_t node = root_;
diff --git a/source/val/function.h b/source/val/function.h
index 7eb8dcd..8baeb33 100644
--- a/source/val/function.h
+++ b/source/val/function.h
@@ -133,9 +133,12 @@
   /// Returns the number of blocks in the current function being parsed
   size_t block_count() const;
 
-  /// Returns the id of the funciton
+  /// Returns the id of the function
   uint32_t id() const { return id_; }
 
+  /// Returns return type id of the function
+  uint32_t GetResultTypeId() const { return result_type_id_; }
+
   /// Returns the number of blocks in the current function being parsed
   size_t undefined_block_count() const;
   const std::unordered_set<uint32_t>& undefined_blocks() const {
diff --git a/source/val/validation_state.cpp b/source/val/validation_state.cpp
index 850f15e..6374840 100644
--- a/source/val/validation_state.cpp
+++ b/source/val/validation_state.cpp
@@ -62,6 +62,10 @@
         default: break;
       }
       break;
+    case kLayoutDebug3:
+      // Only OpModuleProcessed is allowed here.
+      out = (op == SpvOpModuleProcessed);
+      break;
     case kLayoutAnnotations:
       switch (op) {
         case SpvOpDecorate:
@@ -110,6 +114,7 @@
         case SpvOpString:
         case SpvOpName:
         case SpvOpMemberName:
+        case SpvOpModuleProcessed:
         case SpvOpDecorate:
         case SpvOpMemberDecorate:
         case SpvOpGroupDecorate:
@@ -256,6 +261,11 @@
   return module_functions_.back();
 }
 
+const Function& ValidationState_t::current_function() const {
+  assert(in_function_body());
+  return module_functions_.back();
+}
+
 bool ValidationState_t::in_function_body() const { return in_function_; }
 
 bool ValidationState_t::in_block() const {
@@ -427,4 +437,252 @@
 
   return unique_type_declarations_.insert(std::move(key)).second;
 }
+
+uint32_t ValidationState_t::GetTypeId(uint32_t id) const {
+  const Instruction* inst = FindDef(id);
+  assert(inst);
+  return inst->type_id();
+}
+
+uint32_t ValidationState_t::GetComponentType(uint32_t id) const {
+  const Instruction* inst = FindDef(id);
+  assert(inst);
+
+  switch (inst->opcode()) {
+    case SpvOpTypeFloat:
+    case SpvOpTypeInt:
+    case SpvOpTypeBool:
+      return id;
+
+    case SpvOpTypeVector:
+      return inst->word(2);
+
+    case SpvOpTypeMatrix:
+      return GetComponentType(inst->word(2));
+
+    default:
+      break;
+  }
+
+  if (inst->type_id())
+    return GetComponentType(inst->type_id());
+
+  assert(0);
+  return 0;
+}
+
+uint32_t ValidationState_t::GetDimension(uint32_t id) const {
+  const Instruction* inst = FindDef(id);
+  assert(inst);
+
+  switch (inst->opcode()) {
+    case SpvOpTypeFloat:
+    case SpvOpTypeInt:
+    case SpvOpTypeBool:
+      return 1;
+
+    case SpvOpTypeVector:
+    case SpvOpTypeMatrix:
+      return inst->word(3);
+
+    default:
+      break;
+  }
+
+  if (inst->type_id())
+    return GetDimension(inst->type_id());
+
+  assert(0);
+  return 0;
+}
+
+uint32_t ValidationState_t::GetBitWidth(uint32_t id) const {
+  const uint32_t component_type_id = GetComponentType(id);
+  const Instruction* inst = FindDef(component_type_id);
+  assert(inst);
+
+  if (inst->opcode() == SpvOpTypeFloat || inst->opcode() == SpvOpTypeInt)
+    return inst->word(2);
+
+  if (inst->opcode() == SpvOpTypeBool)
+    return 1;
+
+  assert(0);
+  return 0;
+}
+
+bool ValidationState_t::IsFloatScalarType(uint32_t id) const {
+  const Instruction* inst = FindDef(id);
+  assert(inst);
+  return inst->opcode() == SpvOpTypeFloat;
+}
+
+bool ValidationState_t::IsFloatVectorType(uint32_t id) const {
+  const Instruction* inst = FindDef(id);
+  assert(inst);
+
+  if (inst->opcode() == SpvOpTypeVector) {
+    return IsFloatScalarType(GetComponentType(id));
+  }
+
+  return false;
+}
+
+bool ValidationState_t::IsIntScalarType(uint32_t id) const {
+  const Instruction* inst = FindDef(id);
+  assert(inst);
+  return inst->opcode() == SpvOpTypeInt;
+}
+
+bool ValidationState_t::IsIntVectorType(uint32_t id) const {
+  const Instruction* inst = FindDef(id);
+  assert(inst);
+
+  if (inst->opcode() == SpvOpTypeVector) {
+    return IsIntScalarType(GetComponentType(id));
+  }
+
+  return false;
+}
+
+bool ValidationState_t::IsUnsignedIntScalarType(uint32_t id) const {
+  const Instruction* inst = FindDef(id);
+  assert(inst);
+  return inst->opcode() == SpvOpTypeInt && inst->word(3) == 0;
+}
+
+bool ValidationState_t::IsUnsignedIntVectorType(uint32_t id) const {
+  const Instruction* inst = FindDef(id);
+  assert(inst);
+
+  if (inst->opcode() == SpvOpTypeVector) {
+    return IsUnsignedIntScalarType(GetComponentType(id));
+  }
+
+  return false;
+}
+
+bool ValidationState_t::IsSignedIntScalarType(uint32_t id) const {
+  const Instruction* inst = FindDef(id);
+  assert(inst);
+  return inst->opcode() == SpvOpTypeInt && inst->word(3) == 1;
+}
+
+bool ValidationState_t::IsSignedIntVectorType(uint32_t id) const {
+  const Instruction* inst = FindDef(id);
+  assert(inst);
+
+  if (inst->opcode() == SpvOpTypeVector) {
+    return IsSignedIntScalarType(GetComponentType(id));
+  }
+
+  return false;
+}
+
+bool ValidationState_t::IsBoolScalarType(uint32_t id) const {
+  const Instruction* inst = FindDef(id);
+  assert(inst);
+  return inst->opcode() == SpvOpTypeBool;
+}
+
+bool ValidationState_t::IsBoolVectorType(uint32_t id) const {
+  const Instruction* inst = FindDef(id);
+  assert(inst);
+
+  if (inst->opcode() == SpvOpTypeVector) {
+    return IsBoolScalarType(GetComponentType(id));
+  }
+
+  return false;
+}
+
+bool ValidationState_t::IsFloatMatrixType(uint32_t id) const {
+  const Instruction* inst = FindDef(id);
+  assert(inst);
+
+  if (inst->opcode() == SpvOpTypeMatrix) {
+    return IsFloatScalarType(GetComponentType(id));
+  }
+
+  return false;
+}
+
+bool ValidationState_t::GetMatrixTypeInfo(
+    uint32_t id, uint32_t* num_rows, uint32_t* num_cols,
+    uint32_t* column_type, uint32_t* component_type) const {
+  if (!id)
+    return false;
+
+  const Instruction* mat_inst = FindDef(id);
+  assert(mat_inst);
+  if (mat_inst->opcode() != SpvOpTypeMatrix)
+    return false;
+
+  const uint32_t vec_type = mat_inst->word(2);
+  const Instruction* vec_inst = FindDef(vec_type);
+  assert(vec_inst);
+
+  if (vec_inst->opcode() != SpvOpTypeVector) {
+    assert(0);
+    return false;
+  }
+
+  *num_cols = mat_inst->word(3);
+  *num_rows = vec_inst->word(3);
+  *column_type = mat_inst->word(2);
+  *component_type = vec_inst->word(2);
+
+  return true;
+}
+
+bool ValidationState_t::GetStructMemberTypes(
+    uint32_t struct_type_id, std::vector<uint32_t>* member_types) const {
+  member_types->clear();
+  if (!struct_type_id)
+    return false;
+
+  const Instruction* inst = FindDef(struct_type_id);
+  assert(inst);
+  if (inst->opcode() != SpvOpTypeStruct)
+    return false;
+
+  *member_types = std::vector<uint32_t>(inst->words().cbegin() + 2,
+                                        inst->words().cend());
+
+ if (member_types->empty())
+   return false;
+
+  return true;
+}
+
+bool ValidationState_t::IsPointerType(uint32_t id) const {
+  const Instruction* inst = FindDef(id);
+  assert(inst);
+  return inst->opcode() == SpvOpTypePointer;
+}
+
+bool ValidationState_t::GetPointerTypeInfo(
+    uint32_t id, uint32_t* data_type, uint32_t* storage_class) const {
+  if (!id)
+    return false;
+
+  const Instruction* inst = FindDef(id);
+  assert(inst);
+  if (inst->opcode() != SpvOpTypePointer)
+    return false;
+
+  *storage_class = inst->word(2);
+  *data_type = inst->word(3);
+  return true;
+}
+
+uint32_t ValidationState_t::GetOperandTypeId(
+    const spv_parsed_instruction_t* inst,
+    size_t operand_index) const {
+  assert(operand_index < inst->num_operands);
+  const spv_parsed_operand_t& operand = inst->operands[operand_index];
+  assert(operand.num_words == 1);
+  return GetTypeId(inst->words[operand.offset]);
+}
+
 }  /// namespace libspirv
diff --git a/source/val/validation_state.h b/source/val/validation_state.h
index d94093b..7b11bc7 100644
--- a/source/val/validation_state.h
+++ b/source/val/validation_state.h
@@ -46,6 +46,7 @@
   kLayoutExecutionMode,         /// < Section 2.4 #6
   kLayoutDebug1,                /// < Section 2.4 #7 > 1
   kLayoutDebug2,                /// < Section 2.4 #7 > 2
+  kLayoutDebug3,                /// < Section 2.4 #7 > 3
   kLayoutAnnotations,           /// < Section 2.4 #8
   kLayoutTypes,                 /// < Section 2.4 #9
   kLayoutFunctionDeclarations,  /// < Section 2.4 #10
@@ -137,6 +138,7 @@
 
   /// Returns the function states
   Function& current_function();
+  const Function& current_function() const;
 
   /// Returns true if the called after a function instruction but before the
   /// function end instruction
@@ -330,6 +332,67 @@
   /// Returns false if an identical type declaration already exists.
   bool RegisterUniqueTypeDeclaration(const spv_parsed_instruction_t& inst);
 
+  // Returns type_id of the scalar component of |id|.
+  // |id| can be either
+  // - scalar, vector or matrix type
+  // - object of either scalar, vector or matrix type
+  uint32_t GetComponentType(uint32_t id) const;
+
+  // Returns
+  // - 1 for scalar types or objects
+  // - vector size for vector types or objects
+  // - num columns for matrix types or objects
+  // Should not be called with any other arguments (will return zero and invoke
+  // assertion).
+  uint32_t GetDimension(uint32_t id) const;
+
+  // Returns bit width of scalar or component.
+  // |id| can be
+  // - scalar, vector or matrix type
+  // - object of either scalar, vector or matrix type
+  // Will invoke assertion and return 0 if |id| is none of the above.
+  uint32_t GetBitWidth(uint32_t id) const;
+
+  // Provides detailed information on matrix type.
+  // Returns false iff |id| is not matrix type.
+  bool GetMatrixTypeInfo(
+      uint32_t id, uint32_t* num_rows, uint32_t* num_cols,
+      uint32_t* column_type, uint32_t* component_type) const;
+
+  // Collects struct member types into |member_types|.
+  // Returns false iff not struct type or has no members.
+  // Deletes prior contents of |member_types|.
+  bool GetStructMemberTypes(
+      uint32_t struct_type_id, std::vector<uint32_t>* member_types) const;
+
+  // Returns true iff |id| is a type corresponding to the name of the function.
+  // Only works for types not for objects.
+  bool IsFloatScalarType(uint32_t id) const;
+  bool IsFloatVectorType(uint32_t id) const;
+  bool IsFloatMatrixType(uint32_t id) const;
+  bool IsIntScalarType(uint32_t id) const;
+  bool IsIntVectorType(uint32_t id) const;
+  bool IsUnsignedIntScalarType(uint32_t id) const;
+  bool IsUnsignedIntVectorType(uint32_t id) const;
+  bool IsSignedIntScalarType(uint32_t id) const;
+  bool IsSignedIntVectorType(uint32_t id) const;
+  bool IsBoolScalarType(uint32_t id) const;
+  bool IsBoolVectorType(uint32_t id) const;
+  bool IsPointerType(uint32_t id) const;
+
+  // Returns type_id if id has type or zero otherwise.
+  uint32_t GetTypeId(uint32_t id) const;
+
+  // Returns type_id for given id operand if it has a type or zero otherwise.
+  // |operand_index| is expected to be pointing towards an operand which is an
+  // id.
+  uint32_t GetOperandTypeId(const spv_parsed_instruction_t* inst,
+                            size_t operand_index) const;
+
+  // Provides information on pointer type. Returns false iff not pointer type.
+  bool GetPointerTypeInfo(
+      uint32_t id, uint32_t* data_type, uint32_t* storage_class) const;
+
  private:
   ValidationState_t(const ValidationState_t&);
 
diff --git a/source/validate.cpp b/source/validate.cpp
index ad0fbb9..536f798 100644
--- a/source/validate.cpp
+++ b/source/validate.cpp
@@ -180,6 +180,10 @@
   if (auto error = CfgPass(_, inst)) return error;
   if (auto error = InstructionPass(_, inst)) return error;
   if (auto error = TypeUniquePass(_, inst)) return error;
+  if (auto error = ArithmeticsPass(_, inst)) return error;
+  if (auto error = ConversionPass(_, inst)) return error;
+  if (auto error = LogicalsPass(_, inst)) return error;
+  if (auto error = BitwisePass(_, inst)) return error;
 
   return SPV_SUCCESS;
 }
diff --git a/source/validate.h b/source/validate.h
index 34d1ffe..2e088d9 100644
--- a/source/validate.h
+++ b/source/validate.h
@@ -111,6 +111,22 @@
 spv_result_t TypeUniquePass(ValidationState_t& _,
                             const spv_parsed_instruction_t* inst);
 
+/// Validates correctness of arithmetic instructions.
+spv_result_t ArithmeticsPass(ValidationState_t& _,
+                            const spv_parsed_instruction_t* inst);
+
+/// Validates correctness of conversion instructions.
+spv_result_t ConversionPass(ValidationState_t& _,
+                            const spv_parsed_instruction_t* inst);
+
+/// Validates correctness of logical instructions.
+spv_result_t LogicalsPass(ValidationState_t& _,
+                          const spv_parsed_instruction_t* inst);
+
+/// Validates correctness of bitwise instructions.
+spv_result_t BitwisePass(ValidationState_t& _,
+                         const spv_parsed_instruction_t* inst);
+
 // Validates that capability declarations use operands allowed in the current
 // context.
 spv_result_t CapabilityPass(ValidationState_t& _,
diff --git a/source/validate_arithmetics.cpp b/source/validate_arithmetics.cpp
new file mode 100644
index 0000000..a2d3fbc
--- /dev/null
+++ b/source/validate_arithmetics.cpp
@@ -0,0 +1,467 @@
+// Copyright (c) 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Performs validation of arithmetic instructions.
+
+#include "validate.h"
+
+#include "diagnostic.h"
+#include "opcode.h"
+#include "val/instruction.h"
+#include "val/validation_state.h"
+
+namespace libspirv {
+
+namespace {
+
+// Returns operand word for given instruction and operand index.
+// The operand is expected to only have one word.
+inline uint32_t GetOperandWord(const spv_parsed_instruction_t* inst,
+                               size_t operand_index) {
+  assert(operand_index < inst->num_operands);
+  const spv_parsed_operand_t& operand = inst->operands[operand_index];
+  assert(operand.num_words == 1);
+  return inst->words[operand.offset];
+}
+
+// Returns the type id of instruction operand at |operand_index|.
+// The operand is expected to be an id.
+inline uint32_t GetOperandTypeId(ValidationState_t& _,
+                               const spv_parsed_instruction_t* inst,
+                               size_t operand_index) {
+  return _.GetTypeId(GetOperandWord(inst, operand_index));
+}
+
+}
+
+// Validates correctness of arithmetic instructions.
+spv_result_t ArithmeticsPass(ValidationState_t& _,
+                            const spv_parsed_instruction_t* inst) {
+  const SpvOp opcode = static_cast<SpvOp>(inst->opcode);
+  const uint32_t result_type = inst->type_id;
+
+  switch (opcode) {
+    case SpvOpFAdd:
+    case SpvOpFSub:
+    case SpvOpFMul:
+    case SpvOpFDiv:
+    case SpvOpFRem:
+    case SpvOpFMod:
+    case SpvOpFNegate: {
+      if (!_.IsFloatScalarType(result_type) &&
+          !_.IsFloatVectorType(result_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected floating scalar or vector type as Result Type: "
+            << spvOpcodeString(opcode);
+
+      for (size_t operand_index = 2; operand_index < inst->num_operands;
+           ++operand_index) {
+        if (GetOperandTypeId(_, inst, operand_index) != result_type)
+          return _.diag(SPV_ERROR_INVALID_DATA)
+              << "Expected arithmetic operands to be of Result Type: "
+              << spvOpcodeString(opcode) << " operand index " << operand_index;
+      }
+      break;
+    }
+
+    case SpvOpUDiv:
+    case SpvOpUMod: {
+      if (!_.IsUnsignedIntScalarType(result_type) &&
+          !_.IsUnsignedIntVectorType(result_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected unsigned int scalar or vector type as Result Type: "
+            << spvOpcodeString(opcode);
+
+      for (size_t operand_index = 2; operand_index < inst->num_operands;
+           ++operand_index) {
+        if (GetOperandTypeId(_, inst, operand_index) != result_type)
+          return _.diag(SPV_ERROR_INVALID_DATA)
+              << "Expected arithmetic operands to be of Result Type: "
+              << spvOpcodeString(opcode) << " operand index " << operand_index;
+      }
+      break;
+    }
+
+    case SpvOpISub:
+    case SpvOpIAdd:
+    case SpvOpIMul:
+    case SpvOpSDiv:
+    case SpvOpSMod:
+    case SpvOpSRem:
+    case SpvOpSNegate: {
+      if (!_.IsIntScalarType(result_type) &&
+          !_.IsIntVectorType(result_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected int scalar or vector type as Result Type: "
+            << spvOpcodeString(opcode);
+
+      const uint32_t dimension = _.GetDimension(result_type);
+      const uint32_t bit_width = _.GetBitWidth(result_type);
+
+      for (size_t operand_index = 2; operand_index < inst->num_operands;
+           ++operand_index) {
+
+        const uint32_t type_id = GetOperandTypeId(_, inst, operand_index);
+        if (!type_id ||
+            (!_.IsIntScalarType(type_id) && !_.IsIntVectorType(type_id)))
+          return _.diag(SPV_ERROR_INVALID_DATA)
+              << "Expected int scalar or vector type as operand: "
+              << spvOpcodeString(opcode) << " operand index " << operand_index;
+
+        if (_.GetDimension(type_id) != dimension)
+          return _.diag(SPV_ERROR_INVALID_DATA)
+              << "Expected arithmetic operands to have the same dimension "
+              << "as Result Type: "
+              << spvOpcodeString(opcode) << " operand index " << operand_index;
+
+        if (_.GetBitWidth(type_id) != bit_width)
+          return _.diag(SPV_ERROR_INVALID_DATA)
+              << "Expected arithmetic operands to have the same bit width "
+              << "as Result Type: "
+              << spvOpcodeString(opcode) << " operand index " << operand_index;
+      }
+      break;
+    }
+
+    case SpvOpDot: {
+      if (!_.IsFloatScalarType(result_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected float scalar type as Result Type: "
+            << spvOpcodeString(opcode);
+
+      uint32_t first_vector_num_components = 0;
+
+      for (size_t operand_index = 2; operand_index < inst->num_operands;
+           ++operand_index) {
+        const uint32_t type_id = GetOperandTypeId(_, inst, operand_index);
+
+        if (!type_id || !_.IsFloatVectorType(type_id))
+          return _.diag(SPV_ERROR_INVALID_DATA)
+              << "Expected float vector as operand: "
+              << spvOpcodeString(opcode) << " operand index " << operand_index;
+
+
+        const uint32_t component_type = _.GetComponentType(type_id);
+        if (component_type != result_type)
+          return _.diag(SPV_ERROR_INVALID_DATA)
+              << "Expected component type to be equal to Result Type: "
+              << spvOpcodeString(opcode) << " operand index " << operand_index;
+
+        const uint32_t num_components = _.GetDimension(type_id);
+        if (operand_index == 2) {
+          first_vector_num_components = num_components;
+        } else if (num_components != first_vector_num_components) {
+          return _.diag(SPV_ERROR_INVALID_DATA)
+              << "Expected operands to have the same number of componenets: "
+              << spvOpcodeString(opcode);
+        }
+      }
+      break;
+    }
+
+    case SpvOpVectorTimesScalar: {
+      if (!_.IsFloatVectorType(result_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected float vector type as Result Type: "
+            << spvOpcodeString(opcode);
+
+      const uint32_t vector_type_id = GetOperandTypeId(_, inst, 2);
+      if (result_type != vector_type_id)
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected vector operand type to be equal to Result Type: "
+            << spvOpcodeString(opcode);
+
+      const uint32_t component_type = _.GetComponentType(vector_type_id);
+
+      const uint32_t scalar_type_id = GetOperandTypeId(_, inst, 3);
+      if (component_type != scalar_type_id)
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected scalar operand type to be equal to the component "
+            << "type of the vector operand: "
+            << spvOpcodeString(opcode);
+
+      break;
+    }
+
+    case SpvOpMatrixTimesScalar: {
+      if (!_.IsFloatMatrixType(result_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected float matrix type as Result Type: "
+            << spvOpcodeString(opcode);
+
+      const uint32_t matrix_type_id = GetOperandTypeId(_, inst, 2);
+      if (result_type != matrix_type_id)
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected matrix operand type to be equal to Result Type: "
+            << spvOpcodeString(opcode);
+
+      const uint32_t component_type = _.GetComponentType(matrix_type_id);
+
+      const uint32_t scalar_type_id = GetOperandTypeId(_, inst, 3);
+      if (component_type != scalar_type_id)
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected scalar operand type to be equal to the component "
+            << "type of the matrix operand: "
+            << spvOpcodeString(opcode);
+
+      break;
+    }
+
+    case SpvOpVectorTimesMatrix: {
+      const uint32_t vector_type_id = GetOperandTypeId(_, inst, 2);
+      const uint32_t matrix_type_id = GetOperandTypeId(_, inst, 3);
+
+      if (!_.IsFloatVectorType(result_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected float vector type as Result Type: "
+            << spvOpcodeString(opcode);
+
+      const uint32_t res_component_type = _.GetComponentType(result_type);
+
+      if (!vector_type_id || !_.IsFloatVectorType(vector_type_id))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected float vector type as left operand: "
+            << spvOpcodeString(opcode);
+
+      if (res_component_type != _.GetComponentType(vector_type_id))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected component types of Result Type and vector to be "
+            << "equal: " << spvOpcodeString(opcode);
+
+      uint32_t matrix_num_rows = 0;
+      uint32_t matrix_num_cols = 0;
+      uint32_t matrix_col_type = 0;
+      uint32_t matrix_component_type = 0;
+      if (!_.GetMatrixTypeInfo(matrix_type_id, &matrix_num_rows,
+                               &matrix_num_cols, &matrix_col_type,
+                               &matrix_component_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected float matrix type as right operand: "
+            << spvOpcodeString(opcode);
+
+      if (res_component_type != matrix_component_type)
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected component types of Result Type and matrix to be "
+            << "equal: " << spvOpcodeString(opcode);
+
+      if (matrix_num_cols != _.GetDimension(result_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected number of columns of the matrix to be equal to "
+            << "Result Type vector size: " << spvOpcodeString(opcode);
+
+      if (matrix_num_rows != _.GetDimension(vector_type_id))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected number of rows of the matrix to be equal to the "
+            << "vector operand size: " << spvOpcodeString(opcode);
+
+      break;
+    }
+
+    case SpvOpMatrixTimesVector: {
+      const uint32_t matrix_type_id = GetOperandTypeId(_, inst, 2);
+      const uint32_t vector_type_id = GetOperandTypeId(_, inst, 3);
+
+      if (!_.IsFloatVectorType(result_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected float vector type as Result Type: "
+            << spvOpcodeString(opcode);
+
+      uint32_t matrix_num_rows = 0;
+      uint32_t matrix_num_cols = 0;
+      uint32_t matrix_col_type = 0;
+      uint32_t matrix_component_type = 0;
+      if (!_.GetMatrixTypeInfo(matrix_type_id, &matrix_num_rows,
+                               &matrix_num_cols, &matrix_col_type,
+                               &matrix_component_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected float matrix type as left operand: "
+            << spvOpcodeString(opcode);
+
+      if (result_type != matrix_col_type)
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected column type of the matrix to be equal to Result Type: "
+            << spvOpcodeString(opcode);
+
+      if (!vector_type_id || !_.IsFloatVectorType(vector_type_id))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected float vector type as right operand: "
+            << spvOpcodeString(opcode);
+
+      if (matrix_component_type != _.GetComponentType(vector_type_id))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected component types of the operands to be equal: "
+            << spvOpcodeString(opcode);
+
+      if (matrix_num_cols != _.GetDimension(vector_type_id))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected number of columns of the matrix to be equal to the "
+            << "vector size: " << spvOpcodeString(opcode);
+
+      break;
+    }
+
+    case SpvOpMatrixTimesMatrix: {
+      const uint32_t left_type_id = GetOperandTypeId(_, inst, 2);
+      const uint32_t right_type_id = GetOperandTypeId(_, inst, 3);
+
+      uint32_t res_num_rows = 0;
+      uint32_t res_num_cols = 0;
+      uint32_t res_col_type = 0;
+      uint32_t res_component_type = 0;
+      if (!_.GetMatrixTypeInfo(result_type, &res_num_rows, &res_num_cols,
+                               &res_col_type, &res_component_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected float matrix type as Result Type: "
+            << spvOpcodeString(opcode);
+
+      uint32_t left_num_rows = 0;
+      uint32_t left_num_cols = 0;
+      uint32_t left_col_type = 0;
+      uint32_t left_component_type = 0;
+      if (!_.GetMatrixTypeInfo(left_type_id, &left_num_rows, &left_num_cols,
+                               &left_col_type, &left_component_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected float matrix type as left operand: "
+            << spvOpcodeString(opcode);
+
+      uint32_t right_num_rows = 0;
+      uint32_t right_num_cols = 0;
+      uint32_t right_col_type = 0;
+      uint32_t right_component_type = 0;
+      if (!_.GetMatrixTypeInfo(right_type_id, &right_num_rows, &right_num_cols,
+                               &right_col_type, &right_component_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected float matrix type as right operand: "
+            << spvOpcodeString(opcode);
+
+      if (!_.IsFloatScalarType(res_component_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected float matrix type as Result Type: "
+            << spvOpcodeString(opcode);
+
+      if (res_col_type != left_col_type)
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected column types of Result Type and left matrix to be "
+            << "equal: " << spvOpcodeString(opcode);
+
+      if (res_component_type != right_component_type)
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected component types of Result Type and right matrix to be "
+            << "equal: " << spvOpcodeString(opcode);
+
+      if (res_num_cols != right_num_cols)
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected number of columns of Result Type and right matrix to "
+            << "be equal: " << spvOpcodeString(opcode);
+
+      if (left_num_cols != right_num_rows)
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected number of columns of left matrix and number of rows "
+            << "of right matrix to be equal: " << spvOpcodeString(opcode);
+
+      assert(left_num_rows == res_num_rows);
+      break;
+    }
+
+    case SpvOpOuterProduct: {
+      const uint32_t left_type_id = GetOperandTypeId(_, inst, 2);
+      const uint32_t right_type_id = GetOperandTypeId(_, inst, 3);
+
+      uint32_t res_num_rows = 0;
+      uint32_t res_num_cols = 0;
+      uint32_t res_col_type = 0;
+      uint32_t res_component_type = 0;
+      if (!_.GetMatrixTypeInfo(result_type, &res_num_rows, &res_num_cols,
+                               &res_col_type, &res_component_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected float matrix type as Result Type: "
+            << spvOpcodeString(opcode);
+
+      if (left_type_id != res_col_type)
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected column type of Result Type to be equal to the type "
+            << "of the left operand: "
+            << spvOpcodeString(opcode);
+
+      if (!right_type_id || !_.IsFloatVectorType(right_type_id))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected float vector type as right operand: "
+            << spvOpcodeString(opcode);
+
+      if (res_component_type != _.GetComponentType(right_type_id))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected component types of the operands to be equal: "
+            << spvOpcodeString(opcode);
+
+      if (res_num_cols != _.GetDimension(right_type_id))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected number of columns of the matrix to be equal to the "
+            << "vector size of the right operand: " << spvOpcodeString(opcode);
+
+      break;
+    }
+
+    case SpvOpIAddCarry:
+    case SpvOpISubBorrow:
+    case SpvOpUMulExtended:
+    case SpvOpSMulExtended: {
+      std::vector<uint32_t> result_types;
+      if (!_.GetStructMemberTypes(result_type, &result_types))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected a struct as Result Type: "
+            << spvOpcodeString(opcode);
+
+      if (result_types.size() != 2)
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected Result Type struct to have two members: "
+            << spvOpcodeString(opcode);
+
+      if (opcode == SpvOpSMulExtended) {
+        if (!_.IsIntScalarType(result_types[0]) &&
+            !_.IsIntVectorType(result_types[0]))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected Result Type struct member types to be integer scalar "
+            << "or vector: " << spvOpcodeString(opcode);
+      } else {
+        if (!_.IsUnsignedIntScalarType(result_types[0]) &&
+            !_.IsUnsignedIntVectorType(result_types[0]))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected Result Type struct member types to be unsigned "
+            << "integer scalar or vector: " << spvOpcodeString(opcode);
+      }
+
+      if (result_types[0] != result_types[1])
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected Result Type struct member types to be identical: "
+            << spvOpcodeString(opcode);
+
+      const uint32_t left_type_id = GetOperandTypeId(_, inst, 2);
+      const uint32_t right_type_id = GetOperandTypeId(_, inst, 3);
+
+      if (left_type_id != result_types[0] || right_type_id != result_types[0])
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected both operands to be of Result Type member type: "
+            << spvOpcodeString(opcode);
+
+      break;
+    }
+
+    default:
+      break;
+  }
+
+  return SPV_SUCCESS;
+}
+
+}  // namespace libspirv
diff --git a/source/validate_bitwise.cpp b/source/validate_bitwise.cpp
new file mode 100644
index 0000000..c175ea6
--- /dev/null
+++ b/source/validate_bitwise.cpp
@@ -0,0 +1,248 @@
+// Copyright (c) 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Validates correctness of bitwise instructions.
+
+#include "validate.h"
+
+#include "diagnostic.h"
+#include "opcode.h"
+#include "val/instruction.h"
+#include "val/validation_state.h"
+
+namespace libspirv {
+
+namespace {
+
+// Returns operand word for given instruction and operand index.
+// The operand is expected to only have one word.
+inline uint32_t GetOperandWord(const spv_parsed_instruction_t* inst,
+                               size_t operand_index) {
+  assert(operand_index < inst->num_operands);
+  const spv_parsed_operand_t& operand = inst->operands[operand_index];
+  assert(operand.num_words == 1);
+  return inst->words[operand.offset];
+}
+
+// Returns the type id of instruction operand at |operand_index|.
+// The operand is expected to be an id.
+inline uint32_t GetOperandTypeId(ValidationState_t& _,
+                               const spv_parsed_instruction_t* inst,
+                               size_t operand_index) {
+  return _.GetTypeId(GetOperandWord(inst, operand_index));
+}
+
+}
+
+// Validates correctness of bitwise instructions.
+spv_result_t BitwisePass(ValidationState_t& _,
+                         const spv_parsed_instruction_t* inst) {
+  const SpvOp opcode = static_cast<SpvOp>(inst->opcode);
+  const uint32_t result_type = inst->type_id;
+
+  switch (opcode) {
+    case SpvOpShiftRightLogical:
+    case SpvOpShiftRightArithmetic:
+    case SpvOpShiftLeftLogical: {
+      if (!_.IsIntScalarType(result_type) &&
+          !_.IsIntVectorType(result_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected int scalar or vector type as Result Type: "
+            << spvOpcodeString(opcode);
+
+      const uint32_t result_dimension = _.GetDimension(result_type);
+      const uint32_t base_type = GetOperandTypeId(_, inst, 2);
+      const uint32_t shift_type = GetOperandTypeId(_, inst, 3);
+
+      if (!base_type ||
+          (!_.IsIntScalarType(base_type) && !_.IsIntVectorType(base_type)))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected Base to be int scalar or vector: "
+            << spvOpcodeString(opcode);
+
+      if (_.GetDimension(base_type) != result_dimension)
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected Base to have the same dimension "
+            << "as Result Type: "
+            << spvOpcodeString(opcode);
+
+      if (_.GetBitWidth(base_type) != _.GetBitWidth(result_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected Base to have the same bit width "
+            << "as Result Type: "
+            << spvOpcodeString(opcode);
+
+      if (!shift_type ||
+          (!_.IsIntScalarType(shift_type) && !_.IsIntVectorType(shift_type)))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected Shift to be int scalar or vector: "
+            << spvOpcodeString(opcode);
+
+      if (_.GetDimension(shift_type) != result_dimension)
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected Shift to have the same dimension "
+            << "as Result Type: "
+            << spvOpcodeString(opcode);
+      break;
+    }
+
+    case SpvOpBitwiseOr:
+    case SpvOpBitwiseXor:
+    case SpvOpBitwiseAnd:
+    case SpvOpNot: {
+      if (!_.IsIntScalarType(result_type) &&
+          !_.IsIntVectorType(result_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected int scalar or vector type as Result Type: "
+            << spvOpcodeString(opcode);
+
+      const uint32_t result_dimension = _.GetDimension(result_type);
+      const uint32_t result_bit_width = _.GetBitWidth(result_type);
+
+      for (size_t operand_index = 2; operand_index < inst->num_operands;
+           ++operand_index) {
+
+        const uint32_t type_id = GetOperandTypeId(_, inst, operand_index);
+        if (!type_id ||
+            (!_.IsIntScalarType(type_id) && !_.IsIntVectorType(type_id)))
+          return _.diag(SPV_ERROR_INVALID_DATA)
+              << "Expected int scalar or vector as operand: "
+              << spvOpcodeString(opcode) << " operand index " << operand_index;
+
+        if (_.GetDimension(type_id) != result_dimension)
+          return _.diag(SPV_ERROR_INVALID_DATA)
+              << "Expected operands to have the same dimension "
+              << "as Result Type: "
+              << spvOpcodeString(opcode) << " operand index " << operand_index;
+
+        if (_.GetBitWidth(type_id) != result_bit_width)
+          return _.diag(SPV_ERROR_INVALID_DATA)
+              << "Expected operands to have the same bit width "
+              << "as Result Type: "
+              << spvOpcodeString(opcode) << " operand index " << operand_index;
+      }
+      break;
+    }
+
+    case SpvOpBitFieldInsert: {
+      if (!_.IsIntScalarType(result_type) &&
+          !_.IsIntVectorType(result_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected int scalar or vector type as Result Type: "
+            << spvOpcodeString(opcode);
+
+      const uint32_t base_type = GetOperandTypeId(_, inst, 2);
+      const uint32_t insert_type = GetOperandTypeId(_, inst, 3);
+      const uint32_t offset_type = GetOperandTypeId(_, inst, 4);
+      const uint32_t count_type = GetOperandTypeId(_, inst, 5);
+
+      if (base_type != result_type)
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected Base Type to be equal to Result Type: "
+            << spvOpcodeString(opcode);
+
+      if (insert_type != result_type)
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected Insert Type to be equal to Result Type: "
+            << spvOpcodeString(opcode);
+
+      if (!offset_type || !_.IsIntScalarType(offset_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected Offset Type to be int scalar: "
+            << spvOpcodeString(opcode);
+
+      if (!count_type || !_.IsIntScalarType(count_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected Count Type to be int scalar: "
+            << spvOpcodeString(opcode);
+      break;
+    }
+
+    case SpvOpBitFieldSExtract:
+    case SpvOpBitFieldUExtract: {
+      if (!_.IsIntScalarType(result_type) &&
+          !_.IsIntVectorType(result_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected int scalar or vector type as Result Type: "
+            << spvOpcodeString(opcode);
+
+      const uint32_t base_type = GetOperandTypeId(_, inst, 2);
+      const uint32_t offset_type = GetOperandTypeId(_, inst, 3);
+      const uint32_t count_type = GetOperandTypeId(_, inst, 4);
+
+      if (base_type != result_type)
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected Base Type to be equal to Result Type: "
+            << spvOpcodeString(opcode);
+
+      if (!offset_type || !_.IsIntScalarType(offset_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected Offset Type to be int scalar: "
+            << spvOpcodeString(opcode);
+
+      if (!count_type || !_.IsIntScalarType(count_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected Count Type to be int scalar: "
+            << spvOpcodeString(opcode);
+      break;
+    }
+
+    case SpvOpBitReverse: {
+      if (!_.IsIntScalarType(result_type) &&
+          !_.IsIntVectorType(result_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected int scalar or vector type as Result Type: "
+            << spvOpcodeString(opcode);
+
+      const uint32_t base_type = GetOperandTypeId(_, inst, 2);
+
+      if (base_type != result_type)
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected Base Type to be equal to Result Type: "
+            << spvOpcodeString(opcode);
+      break;
+    }
+
+    case SpvOpBitCount: {
+      if (!_.IsIntScalarType(result_type) &&
+          !_.IsIntVectorType(result_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected int scalar or vector type as Result Type: "
+            << spvOpcodeString(opcode);
+
+      const uint32_t base_type = GetOperandTypeId(_, inst, 2);
+      if (!base_type ||
+          (!_.IsIntScalarType(base_type) && !_.IsIntVectorType(base_type)))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+               << "Expected Base Type to be int scalar or vector: "
+               << spvOpcodeString(opcode);
+
+      const uint32_t base_dimension = _.GetDimension(base_type);
+      const uint32_t result_dimension = _.GetDimension(result_type);
+
+      if (base_dimension != result_dimension)
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected Base dimension to be equal to Result Type dimension: "
+            << spvOpcodeString(opcode);
+      break;
+    }
+
+    default:
+      break;
+  }
+
+  return SPV_SUCCESS;
+}
+
+}  // namespace libspirv
diff --git a/source/validate_cfg.cpp b/source/validate_cfg.cpp
index 9234542..f237a0f 100644
--- a/source/validate_cfg.cpp
+++ b/source/validate_cfg.cpp
@@ -401,8 +401,17 @@
       }
       _.current_function().RegisterBlockEnd({cases}, opcode);
     } break;
+    case SpvOpReturn: {
+      const uint32_t return_type = _.current_function().GetResultTypeId();
+      const Instruction* return_type_inst = _.FindDef(return_type);
+      assert(return_type_inst);
+      if (return_type_inst->opcode() != SpvOpTypeVoid)
+        return _.diag(SPV_ERROR_INVALID_CFG)
+            << "OpReturn can only be called from a function with void "
+            << "return type.";
+    }
+    // Fallthrough.
     case SpvOpKill:
-    case SpvOpReturn:
     case SpvOpReturnValue:
     case SpvOpUnreachable:
       _.current_function().RegisterBlockEnd(vector<uint32_t>(), opcode);
diff --git a/source/validate_conversion.cpp b/source/validate_conversion.cpp
new file mode 100644
index 0000000..222796e
--- /dev/null
+++ b/source/validate_conversion.cpp
@@ -0,0 +1,419 @@
+// Copyright (c) 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Validates correctness of conversion instructions.
+
+#include "validate.h"
+
+#include "diagnostic.h"
+#include "opcode.h"
+#include "val/instruction.h"
+#include "val/validation_state.h"
+
+namespace libspirv {
+
+// Validates correctness of conversion instructions.
+spv_result_t ConversionPass(ValidationState_t& _,
+                            const spv_parsed_instruction_t* inst) {
+  const SpvOp opcode = static_cast<SpvOp>(inst->opcode);
+  const uint32_t result_type = inst->type_id;
+
+  switch (opcode) {
+    case SpvOpConvertFToU: {
+      if (!_.IsUnsignedIntScalarType(result_type) &&
+          !_.IsUnsignedIntVectorType(result_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected unsigned int scalar or vector type as Result Type: "
+            << spvOpcodeString(opcode);
+
+      const uint32_t input_type = _.GetOperandTypeId(inst, 2);
+      if (!input_type || (!_.IsFloatScalarType(input_type) &&
+                          !_.IsFloatVectorType(input_type)))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected input to be float scalar or vector: "
+            << spvOpcodeString(opcode);
+
+      if (_.GetDimension(result_type) != _.GetDimension(input_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected input to have the same dimension as Result Type: "
+            << spvOpcodeString(opcode);
+
+      break;
+    }
+
+    case SpvOpConvertFToS: {
+      if (!_.IsIntScalarType(result_type) && !_.IsIntVectorType(result_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected int scalar or vector type as Result Type: "
+            << spvOpcodeString(opcode);
+
+      const uint32_t input_type = _.GetOperandTypeId(inst, 2);
+      if (!input_type || (!_.IsFloatScalarType(input_type) &&
+                          !_.IsFloatVectorType(input_type)))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected input to be float scalar or vector: "
+            << spvOpcodeString(opcode);
+
+      if (_.GetDimension(result_type) != _.GetDimension(input_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected input to have the same dimension as Result Type: "
+            << spvOpcodeString(opcode);
+
+      break;
+    }
+
+    case SpvOpConvertSToF:
+    case SpvOpConvertUToF: {
+      if (!_.IsFloatScalarType(result_type) &&
+          !_.IsFloatVectorType(result_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected float scalar or vector type as Result Type: "
+            << spvOpcodeString(opcode);
+
+      const uint32_t input_type = _.GetOperandTypeId(inst, 2);
+      if (!input_type || (!_.IsIntScalarType(input_type) &&
+                          !_.IsIntVectorType(input_type)))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected input to be int scalar or vector: "
+            << spvOpcodeString(opcode);
+
+      if (_.GetDimension(result_type) != _.GetDimension(input_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected input to have the same dimension as Result Type: "
+            << spvOpcodeString(opcode);
+
+      break;
+    }
+
+#if 0
+    // TODO(atgoo@github.com) Reenable this once VulkanCTS can pass this test.
+    case SpvOpUConvert: {
+      if (!_.IsUnsignedIntScalarType(result_type) &&
+          !_.IsUnsignedIntVectorType(result_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected unsigned int scalar or vector type as Result Type: "
+            << spvOpcodeString(opcode);
+
+      const uint32_t input_type = _.GetOperandTypeId(inst, 2);
+      if (!input_type || (!_.IsIntScalarType(input_type) &&
+                          !_.IsIntVectorType(input_type)))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected input to be int scalar or vector: "
+            << spvOpcodeString(opcode);
+
+      if (_.GetDimension(result_type) != _.GetDimension(input_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected input to have the same dimension as Result Type: "
+            << spvOpcodeString(opcode);
+
+      if (_.GetBitWidth(result_type) == _.GetBitWidth(input_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected input to have different bit width from Result Type: "
+            << spvOpcodeString(opcode);
+      break;
+    }
+#endif
+
+    case SpvOpSConvert: {
+      if (!_.IsIntScalarType(result_type) && !_.IsIntVectorType(result_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected int scalar or vector type as Result Type: "
+            << spvOpcodeString(opcode);
+
+      const uint32_t input_type = _.GetOperandTypeId(inst, 2);
+      if (!input_type || (!_.IsIntScalarType(input_type) &&
+                          !_.IsIntVectorType(input_type)))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected input to be int scalar or vector: "
+            << spvOpcodeString(opcode);
+
+      if (_.GetDimension(result_type) != _.GetDimension(input_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected input to have the same dimension as Result Type: "
+            << spvOpcodeString(opcode);
+
+      if (_.GetBitWidth(result_type) == _.GetBitWidth(input_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected input to have different bit width from Result Type: "
+            << spvOpcodeString(opcode);
+      break;
+    }
+
+    case SpvOpFConvert: {
+      if (!_.IsFloatScalarType(result_type) &&
+          !_.IsFloatVectorType(result_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected float scalar or vector type as Result Type: "
+            << spvOpcodeString(opcode);
+
+      const uint32_t input_type = _.GetOperandTypeId(inst, 2);
+      if (!input_type || (!_.IsFloatScalarType(input_type) &&
+                          !_.IsFloatVectorType(input_type)))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected input to be float scalar or vector: "
+            << spvOpcodeString(opcode);
+
+      if (_.GetDimension(result_type) != _.GetDimension(input_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected input to have the same dimension as Result Type: "
+            << spvOpcodeString(opcode);
+
+      if (_.GetBitWidth(result_type) == _.GetBitWidth(input_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected input to have different bit width from Result Type: "
+            << spvOpcodeString(opcode);
+      break;
+    }
+
+    case SpvOpQuantizeToF16: {
+      if ((!_.IsFloatScalarType(result_type) &&
+          !_.IsFloatVectorType(result_type)) ||
+          _.GetBitWidth(result_type) != 32)
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected 32-bit float scalar or vector type as Result Type: "
+            << spvOpcodeString(opcode);
+
+      const uint32_t input_type = _.GetOperandTypeId(inst, 2);
+      if (input_type != result_type)
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected input type to be equal to Result Type: "
+            << spvOpcodeString(opcode);
+      break;
+    }
+
+    case SpvOpConvertPtrToU: {
+      if (!_.IsUnsignedIntScalarType(result_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected unsigned int scalar type as Result Type: "
+            << spvOpcodeString(opcode);
+
+      const uint32_t input_type = _.GetOperandTypeId(inst, 2);
+      if (!_.IsPointerType(input_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected input to be a pointer: " << spvOpcodeString(opcode);
+      break;
+    }
+
+    case SpvOpSatConvertSToU:
+    case SpvOpSatConvertUToS: {
+      if (!_.IsIntScalarType(result_type) && !_.IsIntVectorType(result_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected int scalar or vector type as Result Type: "
+            << spvOpcodeString(opcode);
+
+      const uint32_t input_type = _.GetOperandTypeId(inst, 2);
+      if (!input_type || (!_.IsIntScalarType(input_type) &&
+                          !_.IsIntVectorType(input_type)))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected int scalar or vector as input: "
+            << spvOpcodeString(opcode);
+
+      if (_.GetDimension(result_type) != _.GetDimension(input_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected input to have the same dimension as Result Type: "
+            << spvOpcodeString(opcode);
+      break;
+    }
+
+    case SpvOpConvertUToPtr: {
+      if (!_.IsPointerType(result_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected Result Type to be a pointer: "
+            << spvOpcodeString(opcode);
+
+      const uint32_t input_type = _.GetOperandTypeId(inst, 2);
+      if (!_.IsIntScalarType(input_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected int scalar as input: " << spvOpcodeString(opcode);
+      break;
+    }
+
+    case SpvOpPtrCastToGeneric: {
+      uint32_t result_storage_class = 0;
+      uint32_t result_data_type = 0;
+      if (!_.GetPointerTypeInfo(result_type, &result_data_type,
+                                &result_storage_class))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected Result Type to be a pointer: "
+            << spvOpcodeString(opcode);
+
+      if (result_storage_class != SpvStorageClassGeneric)
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected Result Type to have storage class Generic: "
+            << spvOpcodeString(opcode);
+
+      const uint32_t input_type = _.GetOperandTypeId(inst, 2);
+      uint32_t input_storage_class = 0;
+      uint32_t input_data_type = 0;
+      if (!_.GetPointerTypeInfo(input_type, &input_data_type,
+                                &input_storage_class))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected input to be a pointer: "
+            << spvOpcodeString(opcode);
+
+      if (input_storage_class != SpvStorageClassWorkgroup &&
+          input_storage_class != SpvStorageClassCrossWorkgroup &&
+          input_storage_class != SpvStorageClassFunction)
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected input to have storage class Workgroup, "
+            << "CrossWorkgroup or Function: "
+            << spvOpcodeString(opcode);
+
+      if (result_data_type != input_data_type)
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected input and Result Type to point to the same type: "
+            << spvOpcodeString(opcode);
+      break;
+    }
+
+    case SpvOpGenericCastToPtr: {
+      uint32_t result_storage_class = 0;
+      uint32_t result_data_type = 0;
+      if (!_.GetPointerTypeInfo(result_type, &result_data_type,
+                                &result_storage_class))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected Result Type to be a pointer: "
+            << spvOpcodeString(opcode);
+
+      if (result_storage_class != SpvStorageClassWorkgroup &&
+          result_storage_class != SpvStorageClassCrossWorkgroup &&
+          result_storage_class != SpvStorageClassFunction)
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected Result Type to have storage class Workgroup, "
+            << "CrossWorkgroup or Function: "
+            << spvOpcodeString(opcode);
+
+      const uint32_t input_type = _.GetOperandTypeId(inst, 2);
+      uint32_t input_storage_class = 0;
+      uint32_t input_data_type = 0;
+      if (!_.GetPointerTypeInfo(input_type, &input_data_type,
+                                &input_storage_class))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected input to be a pointer: "
+            << spvOpcodeString(opcode);
+
+      if (input_storage_class != SpvStorageClassGeneric)
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected input to have storage class Generic: "
+            << spvOpcodeString(opcode);
+
+      if (result_data_type != input_data_type)
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected input and Result Type to point to the same type: "
+            << spvOpcodeString(opcode);
+      break;
+    }
+
+    case SpvOpGenericCastToPtrExplicit: {
+      uint32_t result_storage_class = 0;
+      uint32_t result_data_type = 0;
+      if (!_.GetPointerTypeInfo(result_type, &result_data_type,
+                                &result_storage_class))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected Result Type to be a pointer: "
+            << spvOpcodeString(opcode);
+
+      const uint32_t target_storage_class = inst->words[4];
+      if (result_storage_class != target_storage_class)
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected Result Type to be of target storage class: "
+            << spvOpcodeString(opcode);
+
+      const uint32_t input_type = _.GetOperandTypeId(inst, 2);
+      uint32_t input_storage_class = 0;
+      uint32_t input_data_type = 0;
+      if (!_.GetPointerTypeInfo(input_type, &input_data_type,
+                                &input_storage_class))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected input to be a pointer: "
+            << spvOpcodeString(opcode);
+
+      if (input_storage_class != SpvStorageClassGeneric)
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected input to have storage class Generic: "
+            << spvOpcodeString(opcode);
+
+      if (result_data_type != input_data_type)
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected input and Result Type to point to the same type: "
+            << spvOpcodeString(opcode);
+
+      if (target_storage_class != SpvStorageClassWorkgroup &&
+          target_storage_class != SpvStorageClassCrossWorkgroup &&
+          target_storage_class != SpvStorageClassFunction)
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected target storage class to be Workgroup, "
+            << "CrossWorkgroup or Function: "
+            << spvOpcodeString(opcode);
+      break;
+    }
+
+    case SpvOpBitcast: {
+      const uint32_t input_type = _.GetOperandTypeId(inst, 2);
+      if (!input_type)
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected input to have a type: " << spvOpcodeString(opcode);
+
+      const bool result_is_pointer = _.IsPointerType(result_type);
+      const bool result_is_int_scalar = _.IsIntScalarType(result_type);
+      const bool input_is_pointer = _.IsPointerType(input_type);
+      const bool input_is_int_scalar = _.IsIntScalarType(input_type);
+
+      if (!result_is_pointer && !result_is_int_scalar &&
+          !_.IsIntVectorType(result_type) &&
+          !_.IsFloatScalarType(result_type) &&
+          !_.IsFloatVectorType(result_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected Result Type to be a pointer or int or float vector "
+            << "or scalar type: " << spvOpcodeString(opcode);
+
+      if (!input_is_pointer && !input_is_int_scalar &&
+          !_.IsIntVectorType(input_type) &&
+          !_.IsFloatScalarType(input_type) &&
+          !_.IsFloatVectorType(input_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected input to be a pointer or int or float vector "
+            << "or scalar: " << spvOpcodeString(opcode);
+
+      if (result_is_pointer && !input_is_pointer && !input_is_int_scalar)
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected input to be a pointer or int scalar if Result Type "
+            << "is pointer: " << spvOpcodeString(opcode);
+
+      if (input_is_pointer && !result_is_pointer && !result_is_int_scalar)
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Pointer can only be converted to another pointer or int "
+            << "scalar: " << spvOpcodeString(opcode);
+
+      if (!result_is_pointer && !input_is_pointer) {
+        const uint32_t result_size =
+            _.GetBitWidth(result_type) * _.GetDimension(result_type);
+        const uint32_t input_size =
+            _.GetBitWidth(input_type) * _.GetDimension(input_type);
+        if (result_size != input_size)
+          return _.diag(SPV_ERROR_INVALID_DATA)
+              << "Expected input to have the same total bit width as "
+              << "Result Type: " << spvOpcodeString(opcode);
+      }
+      break;
+    }
+
+    default:
+      break;
+  }
+
+  return SPV_SUCCESS;
+}
+
+}  // namespace libspirv
diff --git a/source/validate_id.cpp b/source/validate_id.cpp
index ae3b59f..e898b23 100644
--- a/source/validate_id.cpp
+++ b/source/validate_id.cpp
@@ -26,6 +26,7 @@
 #include "instruction.h"
 #include "message.h"
 #include "opcode.h"
+#include "operand.h"
 #include "spirv_validator_options.h"
 #include "spirv-tools/libspirv.h"
 #include "val/function.h"
@@ -180,6 +181,27 @@
 }
 
 template <>
+bool idUsage::isValid<SpvOpDecorationGroup>(const spv_instruction_t* inst,
+                                     const spv_opcode_desc) {
+  auto decorationGroupIndex = 1;
+  auto decorationGroup = module_.FindDef(inst->words[decorationGroupIndex]);
+
+  for (auto pair : decorationGroup->uses()) {
+    auto use = pair.first;
+    if (use->opcode() != SpvOpDecorate &&
+        use->opcode() != SpvOpGroupDecorate &&
+        use->opcode() != SpvOpGroupMemberDecorate &&
+        use->opcode() != SpvOpName ) {
+      DIAG(decorationGroupIndex) << "Result id of OpDecorationGroup can only "
+                                 << "be targeted by OpName, OpGroupDecorate, "
+                                 << "OpDecorate, and OpGroupMemberDecorate";
+      return false;
+    }
+  }                                        
+  return true;
+}
+
+template <>
 bool idUsage::isValid<SpvOpGroupDecorate>(const spv_instruction_t* inst,
                                           const spv_opcode_desc) {
   auto decorationGroupIndex = 1;
@@ -1600,96 +1622,6 @@
 
 #if 0
 template <>
-bool idUsage::isValid<OpConvertUToF>(const spv_instruction_t *inst,
-                                     const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpConvertFToS>(const spv_instruction_t *inst,
-                                     const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpConvertSToF>(const spv_instruction_t *inst,
-                                     const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpConvertUToF>(const spv_instruction_t *inst,
-                                     const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpUConvert>(const spv_instruction_t *inst,
-                                  const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpSConvert>(const spv_instruction_t *inst,
-                                  const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpFConvert>(const spv_instruction_t *inst,
-                                  const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpConvertPtrToU>(const spv_instruction_t *inst,
-                                       const spv_opcode_desc opcodeEntry) {
-}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpConvertUToPtr>(const spv_instruction_t *inst,
-                                       const spv_opcode_desc opcodeEntry) {
-}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpPtrCastToGeneric>(
-    const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpGenericCastToPtr>(
-    const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpBitcast>(const spv_instruction_t *inst,
-                                 const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpGenericCastToPtrExplicit>(
-    const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpSatConvertSToU>(const spv_instruction_t *inst) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpSatConvertUToS>(const spv_instruction_t *inst) {}
-#endif
-
-#if 0
-template <>
 bool idUsage::isValid<OpVectorExtractDynamic>(
     const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {}
 #endif
@@ -1700,12 +1632,87 @@
     const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {}
 #endif
 
-#if 0
 template <>
-bool idUsage::isValid<OpVectorShuffle>(const spv_instruction_t *inst,
-                                       const spv_opcode_desc opcodeEntry) {
+bool idUsage::isValid<SpvOpVectorShuffle>(const spv_instruction_t* inst,
+                                          const spv_opcode_desc) {
+  auto instr_name = [&inst]() {
+    std::string name =
+        "Op" + std::string(spvOpcodeString(static_cast<SpvOp>(inst->opcode)));
+    return name;
+  };
+
+  // Result Type must be an OpTypeVector.
+  auto resultTypeIndex = 1;
+  auto resultType = module_.FindDef(inst->words[resultTypeIndex]);
+  if (!resultType || resultType->opcode() != SpvOpTypeVector) {
+    DIAG(resultTypeIndex) << "The Result Type of " << instr_name()
+                          << " must be OpTypeVector. Found Op"
+                          << spvOpcodeString(
+                                 static_cast<SpvOp>(resultType->opcode()))
+                          << ".";
+    return false;
+  }
+
+  // The number of components in Result Type must be the same as the number of
+  // Component operands.
+  auto componentCount = inst->words.size() - 5;
+  auto vectorComponentCountIndex = 3;
+  auto resultVectorDimension = resultType->words()[vectorComponentCountIndex];
+  if (componentCount != resultVectorDimension) {
+    DIAG(inst->words.size() - 1)
+        << instr_name() << " component literals count does not match "
+                           "Result Type <id> '"
+        << resultType->id() << "'s vector component count.";
+    return false;
+  }
+
+  // Vector 1 and Vector 2 must both have vector types, with the same Component
+  // Type as Result Type.
+  auto vector1Index = 3;
+  auto vector1Object = module_.FindDef(inst->words[vector1Index]);
+  auto vector1Type = module_.FindDef(vector1Object->type_id());
+  auto vector2Index = 4;
+  auto vector2Object = module_.FindDef(inst->words[vector2Index]);
+  auto vector2Type = module_.FindDef(vector2Object->type_id());
+  if (!vector1Type || vector1Type->opcode() != SpvOpTypeVector) {
+    DIAG(vector1Index) << "The type of Vector 1 must be OpTypeVector.";
+    return false;
+  }
+  if (!vector2Type || vector2Type->opcode() != SpvOpTypeVector) {
+    DIAG(vector2Index) << "The type of Vector 2 must be OpTypeVector.";
+    return false;
+  }
+  auto vectorComponentTypeIndex = 2;
+  auto resultComponentType = resultType->words()[vectorComponentTypeIndex];
+  auto vector1ComponentType = vector1Type->words()[vectorComponentTypeIndex];
+  if (vector1ComponentType != resultComponentType) {
+    DIAG(vector1Index) << "The Component Type of Vector 1 must be the same "
+                          "as ResultType.";
+    return false;
+  }
+  auto vector2ComponentType = vector2Type->words()[vectorComponentTypeIndex];
+  if (vector2ComponentType != resultComponentType) {
+    DIAG(vector2Index) << "The Component Type of Vector 2 must be the same "
+                          "as ResultType.";
+    return false;
+  }
+
+  // All Component literals must either be FFFFFFFF or in [0, N - 1].
+  auto vector1ComponentCount = vector1Type->words()[vectorComponentCountIndex];
+  auto vector2ComponentCount = vector2Type->words()[vectorComponentCountIndex];
+  auto N = vector1ComponentCount + vector2ComponentCount;
+  auto firstLiteralIndex = 5;
+  for (size_t i = firstLiteralIndex; i < inst->words.size(); ++i) {
+    auto literal = inst->words[i];
+    if (literal != 0xFFFFFFFF && literal >= N) {
+      DIAG(i) << "Component literal value " << literal << " is greater than "
+              << N - 1 << ".";
+      return false;
+    }
+  }
+
+  return true;
 }
-#endif
 
 #if 0
 template <>
@@ -1919,403 +1926,6 @@
 
 #if 0
 template <>
-bool idUsage::isValid<OpSNegate>(const spv_instruction_t *inst,
-                                 const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpFNegate>(const spv_instruction_t *inst,
-                                 const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpNot>(const spv_instruction_t *inst,
-                             const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpIAdd>(const spv_instruction_t *inst,
-                              const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpFAdd>(const spv_instruction_t *inst,
-                              const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpISub>(const spv_instruction_t *inst,
-                              const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpFSub>(const spv_instruction_t *inst,
-                              const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpIMul>(const spv_instruction_t *inst,
-                              const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpFMul>(const spv_instruction_t *inst,
-                              const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpUDiv>(const spv_instruction_t *inst,
-                              const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpSDiv>(const spv_instruction_t *inst,
-                              const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpFDiv>(const spv_instruction_t *inst,
-                              const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpUMod>(const spv_instruction_t *inst,
-                              const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpSRem>(const spv_instruction_t *inst,
-                              const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpSMod>(const spv_instruction_t *inst,
-                              const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpFRem>(const spv_instruction_t *inst,
-                              const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpFMod>(const spv_instruction_t *inst,
-                              const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpVectorTimesScalar>(
-    const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpMatrixTimesScalar>(
-    const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpVectorTimesMatrix>(
-    const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpMatrixTimesVector>(
-    const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpMatrixTimesMatrix>(
-    const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpOuterProduct>(const spv_instruction_t *inst,
-                                      const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpDot>(const spv_instruction_t *inst,
-                             const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpShiftRightLogical>(
-    const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpShiftRightArithmetic>(
-    const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpShiftLeftLogical>(
-    const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpBitwiseOr>(const spv_instruction_t *inst,
-                                   const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpBitwiseXor>(const spv_instruction_t *inst,
-                                    const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpBitwiseAnd>(const spv_instruction_t *inst,
-                                    const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpAny>(const spv_instruction_t *inst,
-                             const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpAll>(const spv_instruction_t *inst,
-                             const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpIsNan>(const spv_instruction_t *inst,
-                               const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpIsInf>(const spv_instruction_t *inst,
-                               const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpIsFinite>(const spv_instruction_t *inst,
-                                  const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpIsNormal>(const spv_instruction_t *inst,
-                                  const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpSignBitSet>(const spv_instruction_t *inst,
-                                    const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpLessOrGreater>(const spv_instruction_t *inst,
-                                       const spv_opcode_desc opcodeEntry) {
-}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpOrdered>(const spv_instruction_t *inst,
-                                 const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpUnordered>(const spv_instruction_t *inst,
-                                   const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpLogicalOr>(const spv_instruction_t *inst,
-                                   const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpLogicalXor>(const spv_instruction_t *inst,
-                                    const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpLogicalAnd>(const spv_instruction_t *inst,
-                                    const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpSelect>(const spv_instruction_t *inst,
-                                const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpIEqual>(const spv_instruction_t *inst,
-                                const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpFOrdEqual>(const spv_instruction_t *inst,
-                                   const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpFUnordEqual>(const spv_instruction_t *inst,
-                                     const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpINotEqual>(const spv_instruction_t *inst,
-                                   const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpFOrdNotEqual>(const spv_instruction_t *inst,
-                                      const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpFUnordNotEqual>(
-    const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpULessThan>(const spv_instruction_t *inst,
-                                   const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpSLessThan>(const spv_instruction_t *inst,
-                                   const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpFOrdLessThan>(const spv_instruction_t *inst,
-                                      const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpFUnordLessThan>(
-    const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpUGreaterThan>(const spv_instruction_t *inst,
-                                      const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpSGreaterThan>(const spv_instruction_t *inst,
-                                      const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpFOrdGreaterThan>(
-    const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpFUnordGreaterThan>(
-    const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpULessThanEqual>(
-    const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpSLessThanEqual>(
-    const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpFOrdLessThanEqual>(
-    const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpFUnordLessThanEqual>(
-    const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpUGreaterThanEqual>(
-    const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpSGreaterThanEqual>(
-    const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpFOrdGreaterThanEqual>(
-    const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
-bool idUsage::isValid<OpFUnordGreaterThanEqual>(
-    const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {}
-#endif
-
-#if 0
-template <>
 bool idUsage::isValid<OpDPdx>(const spv_instruction_t *inst,
                               const spv_opcode_desc opcodeEntry) {}
 #endif
@@ -2848,6 +2458,7 @@
     CASE(OpLine)
     CASE(OpDecorate)
     CASE(OpMemberDecorate)
+    CASE(OpDecorationGroup)
     CASE(OpGroupDecorate)
     CASE(OpGroupMemberDecorate)
     TODO(OpExtInst)
@@ -2886,93 +2497,18 @@
     CASE(OpFunction)
     CASE(OpFunctionParameter)
     CASE(OpFunctionCall)
-    TODO(OpConvertUToF)
-    TODO(OpConvertFToS)
-    TODO(OpConvertSToF)
-    TODO(OpUConvert)
-    TODO(OpSConvert)
-    TODO(OpFConvert)
-    TODO(OpConvertPtrToU)
-    TODO(OpConvertUToPtr)
-    TODO(OpPtrCastToGeneric)
-    TODO(OpGenericCastToPtr)
-    TODO(OpBitcast)
-    TODO(OpGenericCastToPtrExplicit)
-    TODO(OpSatConvertSToU)
-    TODO(OpSatConvertUToS)
+    // Conversion opcodes are validated in validate_conversion.cpp.
     TODO(OpVectorExtractDynamic)
     TODO(OpVectorInsertDynamic)
-    TODO(OpVectorShuffle)
+    CASE(OpVectorShuffle)
     TODO(OpCompositeConstruct)
     CASE(OpCompositeExtract)
     CASE(OpCompositeInsert)
     TODO(OpCopyObject)
     TODO(OpTranspose)
-    TODO(OpSNegate)
-    TODO(OpFNegate)
-    TODO(OpNot)
-    TODO(OpIAdd)
-    TODO(OpFAdd)
-    TODO(OpISub)
-    TODO(OpFSub)
-    TODO(OpIMul)
-    TODO(OpFMul)
-    TODO(OpUDiv)
-    TODO(OpSDiv)
-    TODO(OpFDiv)
-    TODO(OpUMod)
-    TODO(OpSRem)
-    TODO(OpSMod)
-    TODO(OpFRem)
-    TODO(OpFMod)
-    TODO(OpVectorTimesScalar)
-    TODO(OpMatrixTimesScalar)
-    TODO(OpVectorTimesMatrix)
-    TODO(OpMatrixTimesVector)
-    TODO(OpMatrixTimesMatrix)
-    TODO(OpOuterProduct)
-    TODO(OpDot)
-    TODO(OpShiftRightLogical)
-    TODO(OpShiftRightArithmetic)
-    TODO(OpShiftLeftLogical)
-    TODO(OpBitwiseOr)
-    TODO(OpBitwiseXor)
-    TODO(OpBitwiseAnd)
-    TODO(OpAny)
-    TODO(OpAll)
-    TODO(OpIsNan)
-    TODO(OpIsInf)
-    TODO(OpIsFinite)
-    TODO(OpIsNormal)
-    TODO(OpSignBitSet)
-    TODO(OpLessOrGreater)
-    TODO(OpOrdered)
-    TODO(OpUnordered)
-    TODO(OpLogicalOr)
-    TODO(OpLogicalAnd)
-    TODO(OpSelect)
-    TODO(OpIEqual)
-    TODO(OpFOrdEqual)
-    TODO(OpFUnordEqual)
-    TODO(OpINotEqual)
-    TODO(OpFOrdNotEqual)
-    TODO(OpFUnordNotEqual)
-    TODO(OpULessThan)
-    TODO(OpSLessThan)
-    TODO(OpFOrdLessThan)
-    TODO(OpFUnordLessThan)
-    TODO(OpUGreaterThan)
-    TODO(OpSGreaterThan)
-    TODO(OpFOrdGreaterThan)
-    TODO(OpFUnordGreaterThan)
-    TODO(OpULessThanEqual)
-    TODO(OpSLessThanEqual)
-    TODO(OpFOrdLessThanEqual)
-    TODO(OpFUnordLessThanEqual)
-    TODO(OpUGreaterThanEqual)
-    TODO(OpSGreaterThanEqual)
-    TODO(OpFOrdGreaterThanEqual)
-    TODO(OpFUnordGreaterThanEqual)
+    // Arithmetic opcodes are validated in validate_arithmetics.cpp.
+    // Bitwise opcodes are validated in validate_bitwise.cpp.
+    // Logical opcodes are validated in validate_logicals.cpp.
     TODO(OpDPdx)
     TODO(OpDPdy)
     TODO(OpFwidth)
@@ -3056,66 +2592,6 @@
 #undef TODO
 #undef CASE
 }
-// This function takes the opcode of an instruction and returns
-// a function object that will return true if the index
-// of the operand can be forwarad declared. This function will
-// used in the SSA validation stage of the pipeline
-function<bool(unsigned)> getCanBeForwardDeclaredFunction(SpvOp opcode) {
-  function<bool(unsigned index)> out;
-  switch (opcode) {
-    case SpvOpExecutionMode:
-    case SpvOpEntryPoint:
-    case SpvOpName:
-    case SpvOpMemberName:
-    case SpvOpSelectionMerge:
-    case SpvOpDecorate:
-    case SpvOpMemberDecorate:
-    case SpvOpTypeStruct:
-    case SpvOpBranch:
-    case SpvOpLoopMerge:
-      out = [](unsigned) { return true; };
-      break;
-    case SpvOpGroupDecorate:
-    case SpvOpGroupMemberDecorate:
-    case SpvOpBranchConditional:
-    case SpvOpSwitch:
-      out = [](unsigned index) { return index != 0; };
-      break;
-
-    case SpvOpFunctionCall:
-      // The Function parameter.
-      out = [](unsigned index) { return index == 2; };
-      break;
-
-    case SpvOpPhi:
-      out = [](unsigned index) { return index > 1; };
-      break;
-
-    case SpvOpEnqueueKernel:
-      // The Invoke parameter.
-      out = [](unsigned index) { return index == 8; };
-      break;
-
-    case SpvOpGetKernelNDrangeSubGroupCount:
-    case SpvOpGetKernelNDrangeMaxSubGroupSize:
-      // The Invoke parameter.
-      out = [](unsigned index) { return index == 3; };
-      break;
-
-    case SpvOpGetKernelWorkGroupSize:
-    case SpvOpGetKernelPreferredWorkGroupSizeMultiple:
-      // The Invoke parameter.
-      out = [](unsigned index) { return index == 2; };
-      break;
-    case SpvOpTypeForwardPointer:
-      out = [](unsigned index) { return index == 0; };
-      break;
-    default:
-      out = [](unsigned) { return false; };
-      break;
-  }
-  return out;
-}
 }  // anonymous namespace
 
 namespace libspirv {
@@ -3214,7 +2690,7 @@
 spv_result_t IdPass(ValidationState_t& _,
                     const spv_parsed_instruction_t* inst) {
   auto can_have_forward_declared_ids =
-      getCanBeForwardDeclaredFunction(static_cast<SpvOp>(inst->opcode));
+      spvOperandCanBeForwardDeclaredFunction(static_cast<SpvOp>(inst->opcode));
 
   // Keep track of a result id defined by this instruction.  0 means it
   // does not define an id.
diff --git a/source/validate_layout.cpp b/source/validate_layout.cpp
index b8b5518..4718b91 100644
--- a/source/validate_layout.cpp
+++ b/source/validate_layout.cpp
@@ -50,7 +50,7 @@
         }
         break;
       case kLayoutFunctionDeclarations:
-        // All module sections have been processed. Recursivly call
+        // All module sections have been processed. Recursively call
         // ModuleLayoutPass to process the next section of the module
         return libspirv::ModuleLayoutPass(_, inst);
       default:
@@ -190,6 +190,7 @@
     case kLayoutExecutionMode:
     case kLayoutDebug1:
     case kLayoutDebug2:
+    case kLayoutDebug3:
     case kLayoutAnnotations:
     case kLayoutTypes:
       if (auto error = ModuleScopedInstructions(_, inst, opcode)) return error;
diff --git a/source/validate_logicals.cpp b/source/validate_logicals.cpp
new file mode 100644
index 0000000..1a3fb14
--- /dev/null
+++ b/source/validate_logicals.cpp
@@ -0,0 +1,288 @@
+// Copyright (c) 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Validates correctness of logical SPIR-V instructions.
+
+#include "validate.h"
+
+#include "diagnostic.h"
+#include "opcode.h"
+#include "val/instruction.h"
+#include "val/validation_state.h"
+
+namespace libspirv {
+
+namespace {
+
+// Returns operand word for given instruction and operand index.
+// The operand is expected to only have one word.
+inline uint32_t GetOperandWord(const spv_parsed_instruction_t* inst,
+                               size_t operand_index) {
+  assert(operand_index < inst->num_operands);
+  const spv_parsed_operand_t& operand = inst->operands[operand_index];
+  assert(operand.num_words == 1);
+  return inst->words[operand.offset];
+}
+
+// Returns the type id of instruction operand at |operand_index|.
+// The operand is expected to be an id.
+inline uint32_t GetOperandTypeId(ValidationState_t& _,
+                               const spv_parsed_instruction_t* inst,
+                               size_t operand_index) {
+  return _.GetTypeId(GetOperandWord(inst, operand_index));
+}
+
+}
+
+// Validates correctness of logical instructions.
+spv_result_t LogicalsPass(ValidationState_t& _,
+                          const spv_parsed_instruction_t* inst) {
+  const SpvOp opcode = static_cast<SpvOp>(inst->opcode);
+  const uint32_t result_type = inst->type_id;
+
+  switch (opcode) {
+    case SpvOpAny:
+    case SpvOpAll: {
+      if (!_.IsBoolScalarType(result_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected bool scalar type as Result Type: "
+            << spvOpcodeString(opcode);
+
+      const uint32_t vector_type = GetOperandTypeId(_, inst, 2);
+      if (!vector_type || !_.IsBoolVectorType(vector_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected operand to be vector bool: "
+            << spvOpcodeString(opcode);
+
+      break;
+    }
+
+    case SpvOpIsNan:
+    case SpvOpIsInf:
+    case SpvOpIsFinite:
+    case SpvOpIsNormal:
+    case SpvOpSignBitSet: {
+      if (!_.IsBoolScalarType(result_type) &&
+          !_.IsBoolVectorType(result_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected bool scalar or vector type as Result Type: "
+            << spvOpcodeString(opcode);
+
+      const uint32_t operand_type = GetOperandTypeId(_, inst, 2);
+      if (!operand_type || (!_.IsFloatScalarType(operand_type) &&
+                           !_.IsFloatVectorType(operand_type)))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected operand to be scalar or vector float: "
+            << spvOpcodeString(opcode);
+
+      if (_.GetDimension(result_type) != _.GetDimension(operand_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected vector sizes of Result Type and the operand to be equal: "
+            << spvOpcodeString(opcode);
+
+      break;
+    }
+
+
+    case SpvOpFOrdEqual:
+    case SpvOpFUnordEqual:
+    case SpvOpFOrdNotEqual:
+    case SpvOpFUnordNotEqual:
+    case SpvOpFOrdLessThan:
+    case SpvOpFUnordLessThan:
+    case SpvOpFOrdGreaterThan:
+    case SpvOpFUnordGreaterThan:
+    case SpvOpFOrdLessThanEqual:
+    case SpvOpFUnordLessThanEqual:
+    case SpvOpFOrdGreaterThanEqual:
+    case SpvOpFUnordGreaterThanEqual:
+    case SpvOpLessOrGreater:
+    case SpvOpOrdered:
+    case SpvOpUnordered: {
+      if (!_.IsBoolScalarType(result_type) &&
+          !_.IsBoolVectorType(result_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected bool scalar or vector type as Result Type: "
+            << spvOpcodeString(opcode);
+
+      const uint32_t left_operand_type = GetOperandTypeId(_, inst, 2);
+      if (!left_operand_type || (!_.IsFloatScalarType(left_operand_type) &&
+                                 !_.IsFloatVectorType(left_operand_type)))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected operands to be scalar or vector float: "
+            << spvOpcodeString(opcode);
+
+      if (_.GetDimension(result_type) != _.GetDimension(left_operand_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected vector sizes of Result Type and the operands to be equal: "
+            << spvOpcodeString(opcode);
+
+      if (left_operand_type != GetOperandTypeId(_, inst, 3))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected left and right operands to have the same type: "
+            << spvOpcodeString(opcode);
+
+      break;
+    }
+
+    case SpvOpLogicalEqual:
+    case SpvOpLogicalNotEqual:
+    case SpvOpLogicalOr:
+    case SpvOpLogicalAnd: {
+      if (!_.IsBoolScalarType(result_type) &&
+          !_.IsBoolVectorType(result_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected bool scalar or vector type as Result Type: "
+            << spvOpcodeString(opcode);
+
+      if (result_type != GetOperandTypeId(_, inst, 2) ||
+          result_type != GetOperandTypeId(_, inst, 3))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected both operands to be of Result Type: "
+            << spvOpcodeString(opcode);
+
+      break;
+    }
+
+    case SpvOpLogicalNot: {
+      if (!_.IsBoolScalarType(result_type) &&
+          !_.IsBoolVectorType(result_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected bool scalar or vector type as Result Type: "
+            << spvOpcodeString(opcode);
+
+      if (result_type != GetOperandTypeId(_, inst, 2))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected operand to be of Result Type: "
+            << spvOpcodeString(opcode);
+
+      break;
+    }
+
+    case SpvOpSelect: {
+      uint32_t dimension = 1;
+      {
+        const Instruction* type_inst = _.FindDef(result_type);
+        assert(type_inst);
+
+        const SpvOp type_opcode = type_inst->opcode();
+        switch (type_opcode) {
+          case SpvOpTypePointer: {
+            if (!_.features().variable_pointers &&
+                !_.features().variable_pointers_storage_buffer)
+              return _.diag(SPV_ERROR_INVALID_DATA)
+                  << "Using pointers with OpSelect requires capability "
+                  << "VariablePointers or VariablePointersStorageBuffer";
+            break;
+          }
+
+          case SpvOpTypeVector: {
+            dimension = type_inst->word(3);
+            break;
+          }
+
+          case SpvOpTypeBool:
+          case SpvOpTypeInt:
+          case SpvOpTypeFloat: {
+            break;
+          }
+
+          default: {
+            return _.diag(SPV_ERROR_INVALID_DATA)
+                << "Expected scalar or vector type as Result Type: "
+                << spvOpcodeString(opcode);
+          }
+        }
+      }
+
+      const uint32_t condition_type = GetOperandTypeId(_, inst, 2);
+      const uint32_t left_type = GetOperandTypeId(_, inst, 3);
+      const uint32_t right_type = GetOperandTypeId(_, inst, 4);
+
+      if (!condition_type || (!_.IsBoolScalarType(condition_type) &&
+                              !_.IsBoolVectorType(condition_type)))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected bool scalar or vector type as condition: "
+            << spvOpcodeString(opcode);
+
+      if (_.GetDimension(condition_type) != dimension)
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected vector sizes of Result Type and the condition to be"
+            << " equal: " << spvOpcodeString(opcode);
+
+      if (result_type != left_type || result_type != right_type)
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected both objects to be of Result Type: "
+            << spvOpcodeString(opcode);
+
+      break;
+    }
+
+    case SpvOpIEqual:
+    case SpvOpINotEqual:
+    case SpvOpUGreaterThan:
+    case SpvOpUGreaterThanEqual:
+    case SpvOpULessThan:
+    case SpvOpULessThanEqual:
+    case SpvOpSGreaterThan:
+    case SpvOpSGreaterThanEqual:
+    case SpvOpSLessThan:
+    case SpvOpSLessThanEqual: {
+      if (!_.IsBoolScalarType(result_type) &&
+          !_.IsBoolVectorType(result_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected bool scalar or vector type as Result Type: "
+            << spvOpcodeString(opcode);
+
+      const uint32_t left_type = GetOperandTypeId(_, inst, 2);
+      const uint32_t right_type = GetOperandTypeId(_, inst, 3);
+
+      if (!left_type || (!_.IsIntScalarType(left_type) &&
+                         !_.IsIntVectorType(left_type)))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected operands to be scalar or vector int: "
+            << spvOpcodeString(opcode);
+
+      if (_.GetDimension(result_type) != _.GetDimension(left_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected vector sizes of Result Type and the operands to be"
+            << " equal: " << spvOpcodeString(opcode);
+
+      if (!right_type || (!_.IsIntScalarType(right_type) &&
+                         !_.IsIntVectorType(right_type)))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected operands to be scalar or vector int: "
+            << spvOpcodeString(opcode);
+
+      if (_.GetDimension(result_type) != _.GetDimension(right_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected vector sizes of Result Type and the operands to be"
+            << " equal: " << spvOpcodeString(opcode);
+
+      if (_.GetBitWidth(left_type) != _.GetBitWidth(right_type))
+        return _.diag(SPV_ERROR_INVALID_DATA)
+            << "Expected both operands to have the same component bit width: "
+            << spvOpcodeString(opcode);
+
+      break;
+    }
+
+    default:
+      break;
+  }
+
+  return SPV_SUCCESS;
+}
+
+}  // namespace libspirv
diff --git a/source/validate_type_unique.cpp b/source/validate_type_unique.cpp
index 2088a82..7b54200 100644
--- a/source/validate_type_unique.cpp
+++ b/source/validate_type_unique.cpp
@@ -49,11 +49,7 @@
     }
 
     if (!_.RegisterUniqueTypeDeclaration(*inst)) {
-      // TODO(atgoo@github) Error logging temporarily disabled because it's
-      // failing vulkancts tests. Message in the diagnostics is for unit tests.
-      // See https://github.com/KhronosGroup/SPIRV-Tools/issues/559
-      // return _.diag(SPV_ERROR_INVALID_DATA)
-      return _.diag(SPV_SUCCESS)
+      return _.diag(SPV_ERROR_INVALID_DATA)
           << "Duplicate non-aggregate type declarations are not allowed."
           << " Opcode: " << spvOpcodeString(SpvOp(inst->opcode))
           << " id: " << inst->result_id;
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 8ebcd93..1b8cad2 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -180,6 +180,8 @@
   LIBS ${SPIRV_TOOLS})
 
 add_subdirectory(comp)
+add_subdirectory(link)
 add_subdirectory(opt)
 add_subdirectory(stats)
+add_subdirectory(util)
 add_subdirectory(val)
diff --git a/test/comp/CMakeLists.txt b/test/comp/CMakeLists.txt
index f769b8c..9243fa2 100644
--- a/test/comp/CMakeLists.txt
+++ b/test/comp/CMakeLists.txt
@@ -17,7 +17,13 @@
   ${CMAKE_CURRENT_SOURCE_DIR}/../unit_spirv.h
 )
 
-add_spvtools_unittest(TARGET markv_codec
-  SRCS markv_codec_test.cpp ${VAL_TEST_COMMON_SRCS}
-  LIBS SPIRV-Tools-comp ${SPIRV_TOOLS}
-)
+if(SPIRV_BUILD_COMPRESSION)
+  add_spvtools_unittest(TARGET markv_codec
+    SRCS
+      markv_codec_test.cpp
+      ${CMAKE_CURRENT_SOURCE_DIR}/../../tools/comp/markv_model_factory.cpp
+      ${CMAKE_CURRENT_SOURCE_DIR}/../../tools/comp/markv_model_shader_default.cpp
+      ${VAL_TEST_COMMON_SRCS}
+    LIBS SPIRV-Tools-comp ${SPIRV_TOOLS}
+  )
+endif(SPIRV_BUILD_COMPRESSION)
diff --git a/test/comp/markv_codec_test.cpp b/test/comp/markv_codec_test.cpp
index c43cc77..46dde85 100644
--- a/test/comp/markv_codec_test.cpp
+++ b/test/comp/markv_codec_test.cpp
@@ -19,8 +19,9 @@
 #include <string>
 
 #include "gmock/gmock.h"
-#include "spirv-tools/markv.h"
+#include "source/comp/markv.h"
 #include "test_fixture.h"
+#include "tools/comp/markv_model_factory.h"
 #include "unit_spirv.h"
 
 namespace {
@@ -82,52 +83,14 @@
   spvTextDestroy(text);
 }
 
-// Encodes SPIR-V |words| to |markv_binary|. |comments| context snippets of
-// disassembly and bit sequences for debugging.
-void Encode(const std::vector<uint32_t>& words,
-            spv_markv_binary* markv_binary,
-            std::string* comments,
-            spv_target_env env = SPV_ENV_UNIVERSAL_1_2) {
-  ScopedContext ctx(env);
-  SetContextMessageConsumer(ctx.context, DiagnosticsMessageHandler);
-
-  std::unique_ptr<spv_markv_encoder_options_t,
-      std::function<void(spv_markv_encoder_options_t*)>> options(
-          spvMarkvEncoderOptionsCreate(), &spvMarkvEncoderOptionsDestroy);
-  spv_text spv_text_comments;
-  ASSERT_EQ(SPV_SUCCESS, spvSpirvToMarkv(ctx.context, words.data(),
-                                         words.size(), options.get(),
-                                         markv_binary, &spv_text_comments,
-                                         nullptr));
-
-  *comments = std::string(spv_text_comments->str, spv_text_comments->length);
-  spvTextDestroy(spv_text_comments);
-}
-
-// Decodes |markv_binary| to SPIR-V |words|.
-void Decode(const spv_markv_binary markv_binary,
-            std::vector<uint32_t>* words,
-            spv_target_env env = SPV_ENV_UNIVERSAL_1_2) {
-  ScopedContext ctx(env);
-  SetContextMessageConsumer(ctx.context, DiagnosticsMessageHandler);
-
-  spv_binary spirv_binary = nullptr;
-  std::unique_ptr<spv_markv_decoder_options_t,
-      std::function<void(spv_markv_decoder_options_t*)>> options(
-          spvMarkvDecoderOptionsCreate(), &spvMarkvDecoderOptionsDestroy);
-  ASSERT_EQ(SPV_SUCCESS, spvMarkvToSpirv(ctx.context, markv_binary->data,
-                                         markv_binary->length, options.get(),
-                                         &spirv_binary, nullptr, nullptr));
-
-  *words = std::vector<uint32_t>(
-      spirv_binary->code, spirv_binary->code + spirv_binary->wordCount);
-
-  spvBinaryDestroy(spirv_binary);
-}
-
 // Encodes/decodes |original|, assembles/dissasembles |original|, then compares
 // the results of the two operations.
 void TestEncodeDecode(const std::string& original_text) {
+  ScopedContext ctx(SPV_ENV_UNIVERSAL_1_2);
+  std::unique_ptr<spvtools::MarkvModel> model =
+      spvtools::CreateMarkvModel(spvtools::kMarkvModelShaderDefault);
+  spvtools::MarkvCodecOptions options;
+
   std::vector<uint32_t> expected_binary;
   Compile(original_text, &expected_binary);
   ASSERT_FALSE(expected_binary.empty());
@@ -141,28 +104,119 @@
           SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
   ASSERT_FALSE(binary_to_encode.empty());
 
-  spv_markv_binary markv_binary = nullptr;
-  std::string encoder_comments;
-  Encode(binary_to_encode, &markv_binary, &encoder_comments);
-  ASSERT_NE(nullptr, markv_binary);
+  std::stringstream encoder_comments;
+  const auto output_to_string_stream =
+      [&encoder_comments](const std::string& str) {
+    encoder_comments << str;
+  };
 
-  // std::cerr << encoder_comments << std::endl;
-  // std::cerr << "SPIR-V size: " << expected_binary.size() * 4 << std::endl;
-  // std::cerr << "MARK-V size: " << markv_binary->length << std::endl;
+  std::vector<uint8_t> markv;
+  ASSERT_EQ(SPV_SUCCESS, spvtools::SpirvToMarkv(
+      ctx.context, binary_to_encode, options, *model,
+      DiagnosticsMessageHandler, output_to_string_stream,
+      spvtools::MarkvDebugConsumer(), &markv));
+  ASSERT_FALSE(markv.empty());
 
   std::vector<uint32_t> decoded_binary;
-  Decode(markv_binary, &decoded_binary);
+  ASSERT_EQ(SPV_SUCCESS, spvtools::MarkvToSpirv(
+      ctx.context, markv, options, *model,
+      DiagnosticsMessageHandler, spvtools::MarkvLogConsumer(),
+      spvtools::MarkvDebugConsumer(), &decoded_binary));
   ASSERT_FALSE(decoded_binary.empty());
 
-  EXPECT_EQ(expected_binary, decoded_binary) << encoder_comments;
+  EXPECT_EQ(expected_binary, decoded_binary) << encoder_comments.str();
 
   std::string decoded_text;
   Disassemble(decoded_binary, &decoded_text);
   ASSERT_FALSE(decoded_text.empty());
 
-  EXPECT_EQ(expected_text, decoded_text) << encoder_comments;
+  EXPECT_EQ(expected_text, decoded_text) << encoder_comments.str();
+}
 
-  spvMarkvBinaryDestroy(markv_binary);
+void TestEncodeDecodeShaderMainBody(const std::string& body) {
+  const std::string prefix =
+R"(
+OpCapability Shader
+OpCapability Int64
+OpCapability Float64
+%ext_inst = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%bool = OpTypeBool
+%f32 = OpTypeFloat 32
+%u32 = OpTypeInt 32 0
+%s32 = OpTypeInt 32 1
+%f64 = OpTypeFloat 64
+%u64 = OpTypeInt 64 0
+%s64 = OpTypeInt 64 1
+%boolvec2 = OpTypeVector %bool 2
+%s32vec2 = OpTypeVector %s32 2
+%u32vec2 = OpTypeVector %u32 2
+%f32vec2 = OpTypeVector %f32 2
+%f64vec2 = OpTypeVector %f64 2
+%boolvec3 = OpTypeVector %bool 3
+%u32vec3 = OpTypeVector %u32 3
+%s32vec3 = OpTypeVector %s32 3
+%f32vec3 = OpTypeVector %f32 3
+%f64vec3 = OpTypeVector %f64 3
+%boolvec4 = OpTypeVector %bool 4
+%u32vec4 = OpTypeVector %u32 4
+%s32vec4 = OpTypeVector %s32 4
+%f32vec4 = OpTypeVector %f32 4
+%f64vec4 = OpTypeVector %f64 4
+
+%f32_0 = OpConstant %f32 0
+%f32_1 = OpConstant %f32 1
+%f32_2 = OpConstant %f32 2
+%f32_3 = OpConstant %f32 3
+%f32_4 = OpConstant %f32 4
+%f32_pi = OpConstant %f32 3.14159
+
+%s32_0 = OpConstant %s32 0
+%s32_1 = OpConstant %s32 1
+%s32_2 = OpConstant %s32 2
+%s32_3 = OpConstant %s32 3
+%s32_4 = OpConstant %s32 4
+%s32_m1 = OpConstant %s32 -1
+
+%u32_0 = OpConstant %u32 0
+%u32_1 = OpConstant %u32 1
+%u32_2 = OpConstant %u32 2
+%u32_3 = OpConstant %u32 3
+%u32_4 = OpConstant %u32 4
+
+%u32vec2_01 = OpConstantComposite %u32vec2 %u32_0 %u32_1
+%u32vec2_12 = OpConstantComposite %u32vec2 %u32_1 %u32_2
+%u32vec3_012 = OpConstantComposite %u32vec3 %u32_0 %u32_1 %u32_2
+%u32vec3_123 = OpConstantComposite %u32vec3 %u32_1 %u32_2 %u32_3
+%u32vec4_0123 = OpConstantComposite %u32vec4 %u32_0 %u32_1 %u32_2 %u32_3
+%u32vec4_1234 = OpConstantComposite %u32vec4 %u32_1 %u32_2 %u32_3 %u32_4
+
+%s32vec2_01 = OpConstantComposite %s32vec2 %s32_0 %s32_1
+%s32vec2_12 = OpConstantComposite %s32vec2 %s32_1 %s32_2
+%s32vec3_012 = OpConstantComposite %s32vec3 %s32_0 %s32_1 %s32_2
+%s32vec3_123 = OpConstantComposite %s32vec3 %s32_1 %s32_2 %s32_3
+%s32vec4_0123 = OpConstantComposite %s32vec4 %s32_0 %s32_1 %s32_2 %s32_3
+%s32vec4_1234 = OpConstantComposite %s32vec4 %s32_1 %s32_2 %s32_3 %s32_4
+
+%f32vec2_01 = OpConstantComposite %f32vec2 %f32_0 %f32_1
+%f32vec2_12 = OpConstantComposite %f32vec2 %f32_1 %f32_2
+%f32vec3_012 = OpConstantComposite %f32vec3 %f32_0 %f32_1 %f32_2
+%f32vec3_123 = OpConstantComposite %f32vec3 %f32_1 %f32_2 %f32_3
+%f32vec4_0123 = OpConstantComposite %f32vec4 %f32_0 %f32_1 %f32_2 %f32_3
+%f32vec4_1234 = OpConstantComposite %f32vec4 %f32_1 %f32_2 %f32_3 %f32_4
+
+%main = OpFunction %void None %func
+%main_entry = OpLabel)";
+
+  const std::string suffix =
+R"(
+OpReturn
+OpFunctionEnd)";
+
+  TestEncodeDecode(prefix + body + suffix);
 }
 
 TEST(Markv, U32Literal) {
@@ -322,6 +376,39 @@
 )");
 }
 
+TEST(Markv, WithMultipleFunctions) {
+  TestEncodeDecode(R"(
+OpCapability Addresses
+OpCapability Kernel
+OpCapability GenericPointer
+OpCapability Linkage
+OpMemoryModel Physical32 OpenCL
+%f32 = OpTypeFloat 32
+%one = OpConstant %f32 1
+%void = OpTypeVoid
+%void_func = OpTypeFunction %void
+%f32_func = OpTypeFunction %f32 %f32
+%sqr_plus_one = OpFunction %f32 None %f32_func
+%x = OpFunctionParameter %f32
+%100 = OpLabel
+%x2 = OpFMul %f32 %x %x
+%x2p1 = OpFunctionCall %f32 %plus_one %x2
+OpReturnValue %x2p1
+OpFunctionEnd
+%plus_one = OpFunction %f32 None %f32_func
+%y = OpFunctionParameter %f32
+%200 = OpLabel
+%yp1 = OpFAdd %f32 %y %one
+OpReturnValue %yp1
+OpFunctionEnd
+%main = OpFunction %void None %void_func
+%entry_main = OpLabel
+%1p1 = OpFunctionCall %f32 %sqr_plus_one %one
+OpReturn
+OpFunctionEnd
+)");
+}
+
 TEST(Markv, ForwardDeclaredId) {
   TestEncodeDecode(R"(
 OpCapability Addresses
@@ -430,4 +517,302 @@
 )");
 }
 
+TEST(Markv, F32Mul) {
+  TestEncodeDecodeShaderMainBody(R"(
+%val1 = OpFMul %f32 %f32_0 %f32_1
+%val2 = OpFMul %f32 %f32_2 %f32_0
+%val3 = OpFMul %f32 %f32_pi %f32_2
+%val4 = OpFMul %f32 %f32_1 %f32_1
+)");
+}
+
+TEST(Markv, U32Mul) {
+  TestEncodeDecodeShaderMainBody(R"(
+%val1 = OpIMul %u32 %u32_0 %u32_1
+%val2 = OpIMul %u32 %u32_2 %u32_0
+%val3 = OpIMul %u32 %u32_3 %u32_2
+%val4 = OpIMul %u32 %u32_1 %u32_1
+)");
+}
+
+TEST(Markv, S32Mul) {
+  TestEncodeDecodeShaderMainBody(R"(
+%val1 = OpIMul %s32 %s32_0 %s32_1
+%val2 = OpIMul %s32 %s32_2 %s32_0
+%val3 = OpIMul %s32 %s32_m1 %s32_2
+%val4 = OpIMul %s32 %s32_1 %s32_1
+)");
+}
+
+TEST(Markv, F32Add) {
+  TestEncodeDecodeShaderMainBody(R"(
+%val1 = OpFAdd %f32 %f32_0 %f32_1
+%val2 = OpFAdd %f32 %f32_2 %f32_0
+%val3 = OpFAdd %f32 %f32_pi %f32_2
+%val4 = OpFAdd %f32 %f32_1 %f32_1
+)");
+}
+
+TEST(Markv, U32Add) {
+  TestEncodeDecodeShaderMainBody(R"(
+%val1 = OpIAdd %u32 %u32_0 %u32_1
+%val2 = OpIAdd %u32 %u32_2 %u32_0
+%val3 = OpIAdd %u32 %u32_3 %u32_2
+%val4 = OpIAdd %u32 %u32_1 %u32_1
+)");
+}
+
+TEST(Markv, S32Add) {
+  TestEncodeDecodeShaderMainBody(R"(
+%val1 = OpIAdd %s32 %s32_0 %s32_1
+%val2 = OpIAdd %s32 %s32_2 %s32_0
+%val3 = OpIAdd %s32 %s32_m1 %s32_2
+%val4 = OpIAdd %s32 %s32_1 %s32_1
+)");
+}
+
+TEST(Markv, F32Dot) {
+  TestEncodeDecodeShaderMainBody(R"(
+%dot2_1 = OpDot %f32 %f32vec2_01 %f32vec2_12
+%dot2_2 = OpDot %f32 %f32vec2_01 %f32vec2_01
+%dot2_3 = OpDot %f32 %f32vec2_12 %f32vec2_12
+%dot3_1 = OpDot %f32 %f32vec3_012 %f32vec3_123
+%dot3_2 = OpDot %f32 %f32vec3_012 %f32vec3_012
+%dot3_3 = OpDot %f32 %f32vec3_123 %f32vec3_123
+%dot4_1 = OpDot %f32 %f32vec4_0123 %f32vec4_1234
+%dot4_2 = OpDot %f32 %f32vec4_0123 %f32vec4_0123
+%dot4_3 = OpDot %f32 %f32vec4_1234 %f32vec4_1234
+)");
+}
+
+TEST(Markv, F32VectorCompositeConstruct) {
+  TestEncodeDecodeShaderMainBody(R"(
+%cc1 = OpCompositeConstruct %f32vec4 %f32vec2_01 %f32vec2_12
+%cc2 = OpCompositeConstruct %f32vec3 %f32vec2_01 %f32_2
+%cc3 = OpCompositeConstruct %f32vec2 %f32_1 %f32_2
+%cc4 = OpCompositeConstruct %f32vec4 %f32_1 %f32_2 %cc3
+)");
+}
+
+TEST(Markv, U32VectorCompositeConstruct) {
+  TestEncodeDecodeShaderMainBody(R"(
+%cc1 = OpCompositeConstruct %u32vec4 %u32vec2_01 %u32vec2_12
+%cc2 = OpCompositeConstruct %u32vec3 %u32vec2_01 %u32_2
+%cc3 = OpCompositeConstruct %u32vec2 %u32_1 %u32_2
+%cc4 = OpCompositeConstruct %u32vec4 %u32_1 %u32_2 %cc3
+)");
+}
+
+TEST(Markv, S32VectorCompositeConstruct) {
+  TestEncodeDecodeShaderMainBody(R"(
+%cc1 = OpCompositeConstruct %u32vec4 %u32vec2_01 %u32vec2_12
+%cc2 = OpCompositeConstruct %u32vec3 %u32vec2_01 %u32_2
+%cc3 = OpCompositeConstruct %u32vec2 %u32_1 %u32_2
+%cc4 = OpCompositeConstruct %u32vec4 %u32_1 %u32_2 %cc3
+)");
+}
+
+TEST(Markv, F32VectorCompositeExtract) {
+  TestEncodeDecodeShaderMainBody(R"(
+%f32vec4_3210 = OpCompositeConstruct %f32vec4 %f32_3 %f32_2 %f32_1 %f32_0
+%f32vec3_013 = OpCompositeExtract %f32vec3 %f32vec4_0123 0 1 3
+)");
+}
+
+TEST(Markv, F32VectorComparison) {
+  TestEncodeDecodeShaderMainBody(R"(
+%f32vec4_3210 = OpCompositeConstruct %f32vec4 %f32_3 %f32_2 %f32_1 %f32_0
+%c1 = OpFOrdEqual %boolvec4 %f32vec4_0123 %f32vec4_3210
+%c2 = OpFUnordEqual %boolvec4 %f32vec4_0123 %f32vec4_3210
+%c3 = OpFOrdNotEqual %boolvec4 %f32vec4_0123 %f32vec4_3210
+%c4 = OpFUnordNotEqual %boolvec4 %f32vec4_0123 %f32vec4_3210
+%c5 = OpFOrdLessThan %boolvec4 %f32vec4_0123 %f32vec4_3210
+%c6 = OpFUnordLessThan %boolvec4 %f32vec4_0123 %f32vec4_3210
+%c7 = OpFOrdGreaterThan %boolvec4 %f32vec4_0123 %f32vec4_3210
+%c8 = OpFUnordGreaterThan %boolvec4 %f32vec4_0123 %f32vec4_3210
+%c9 = OpFOrdLessThanEqual %boolvec4 %f32vec4_0123 %f32vec4_3210
+%c10 = OpFUnordLessThanEqual %boolvec4 %f32vec4_0123 %f32vec4_3210
+%c11 = OpFOrdGreaterThanEqual %boolvec4 %f32vec4_0123 %f32vec4_3210
+%c12 = OpFUnordGreaterThanEqual %boolvec4 %f32vec4_0123 %f32vec4_3210
+)");
+}
+
+TEST(Markv, VectorShuffle) {
+  TestEncodeDecodeShaderMainBody(R"(
+%f32vec4_3210 = OpCompositeConstruct %f32vec4 %f32_3 %f32_2 %f32_1 %f32_0
+%sh1 = OpVectorShuffle %f32vec2 %f32vec4_0123 %f32vec4_3210 3 6
+%sh2 = OpVectorShuffle %f32vec3 %f32vec2_01 %f32vec4_3210 0 3 4
+)");
+}
+
+TEST(Markv, VectorTimesScalar) {
+  TestEncodeDecodeShaderMainBody(R"(
+%f32vec4_3210 = OpCompositeConstruct %f32vec4 %f32_3 %f32_2 %f32_1 %f32_0
+%res1 = OpVectorTimesScalar %f32vec4 %f32vec4_0123 %f32_2
+%res2 = OpVectorTimesScalar %f32vec4 %f32vec4_3210 %f32_2
+)");
+}
+
+TEST(Markv, SpirvSpecSample) {
+  TestEncodeDecode(R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %31 %33 %42 %57
+               OpExecutionMode %4 OriginLowerLeft
+
+; Debug information
+               OpSource GLSL 450
+               OpName %4 "main"
+               OpName %9 "scale"
+               OpName %17 "S"
+               OpMemberName %17 0 "b"
+               OpMemberName %17 1 "v"
+               OpMemberName %17 2 "i"
+               OpName %18 "blockName"
+               OpMemberName %18 0 "s"
+               OpMemberName %18 1 "cond"
+               OpName %20 ""
+               OpName %31 "color"
+               OpName %33 "color1"
+               OpName %42 "color2"
+               OpName %48 "i"
+               OpName %57 "multiplier"
+
+; Annotations (non-debug)
+               OpDecorate %15 ArrayStride 16
+               OpMemberDecorate %17 0 Offset 0
+               OpMemberDecorate %17 1 Offset 16
+               OpMemberDecorate %17 2 Offset 96
+               OpMemberDecorate %18 0 Offset 0
+               OpMemberDecorate %18 1 Offset 112
+               OpDecorate %18 Block
+               OpDecorate %20 DescriptorSet 0
+               OpDecorate %42 NoPerspective
+
+; All types, variables, and constants
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2                      ; void ()
+          %6 = OpTypeFloat 32                         ; 32-bit float
+          %7 = OpTypeVector %6 4                      ; vec4
+          %8 = OpTypePointer Function %7              ; function-local vec4*
+         %10 = OpConstant %6 1
+         %11 = OpConstant %6 2
+         %12 = OpConstantComposite %7 %10 %10 %11 %10 ; vec4(1.0, 1.0, 2.0, 1.0)
+         %13 = OpTypeInt 32 0                         ; 32-bit int, sign-less
+         %14 = OpConstant %13 5
+         %15 = OpTypeArray %7 %14
+         %16 = OpTypeInt 32 1
+         %17 = OpTypeStruct %13 %15 %16
+         %18 = OpTypeStruct %17 %13
+         %19 = OpTypePointer Uniform %18
+         %20 = OpVariable %19 Uniform
+         %21 = OpConstant %16 1
+         %22 = OpTypePointer Uniform %13
+         %25 = OpTypeBool
+         %26 = OpConstant %13 0
+         %30 = OpTypePointer Output %7
+         %31 = OpVariable %30 Output
+         %32 = OpTypePointer Input %7
+         %33 = OpVariable %32 Input
+         %35 = OpConstant %16 0
+         %36 = OpConstant %16 2
+         %37 = OpTypePointer Uniform %7
+         %42 = OpVariable %32 Input
+         %47 = OpTypePointer Function %16
+         %55 = OpConstant %16 4
+         %57 = OpVariable %32 Input
+
+; All functions
+          %4 = OpFunction %2 None %3                  ; main()
+          %5 = OpLabel
+          %9 = OpVariable %8 Function
+         %48 = OpVariable %47 Function
+               OpStore %9 %12
+         %23 = OpAccessChain %22 %20 %21              ; location of cond
+         %24 = OpLoad %13 %23                         ; load 32-bit int from cond
+         %27 = OpINotEqual %25 %24 %26                ; convert to bool
+               OpSelectionMerge %29 None              ; structured if
+               OpBranchConditional %27 %28 %41        ; if cond
+         %28 = OpLabel                                ; then
+         %34 = OpLoad %7 %33
+         %38 = OpAccessChain %37 %20 %35 %21 %36      ; s.v[2]
+         %39 = OpLoad %7 %38
+         %40 = OpFAdd %7 %34 %39
+               OpStore %31 %40
+               OpBranch %29
+         %41 = OpLabel                                ; else
+         %43 = OpLoad %7 %42
+         %44 = OpExtInst %7 %1 Sqrt %43               ; extended instruction sqrt
+         %45 = OpLoad %7 %9
+         %46 = OpFMul %7 %44 %45
+               OpStore %31 %46
+               OpBranch %29
+         %29 = OpLabel                                ; endif
+               OpStore %48 %35
+               OpBranch %49
+         %49 = OpLabel
+               OpLoopMerge %51 %52 None               ; structured loop
+               OpBranch %53
+         %53 = OpLabel
+         %54 = OpLoad %16 %48
+         %56 = OpSLessThan %25 %54 %55                ; i < 4 ?
+               OpBranchConditional %56 %50 %51        ; body or break
+         %50 = OpLabel                                ; body
+         %58 = OpLoad %7 %57
+         %59 = OpLoad %7 %31
+         %60 = OpFMul %7 %59 %58
+               OpStore %31 %60
+               OpBranch %52
+         %52 = OpLabel                                ; continue target
+         %61 = OpLoad %16 %48
+         %62 = OpIAdd %16 %61 %21                     ; ++i
+               OpStore %48 %62
+               OpBranch %49                           ; loop back
+         %51 = OpLabel                                ; loop merge point
+               OpReturn
+               OpFunctionEnd
+)");
+}
+
+TEST(Markv, SampleFromDeadBranchEliminationTest) {
+  TestEncodeDecode(R"(
+OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %gl_FragColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %gl_FragColor "gl_FragColor"
+%void = OpTypeVoid
+%5 = OpTypeFunction %void
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%float_0 = OpConstant %float 0
+%12 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
+%float_1 = OpConstant %float 1
+%14 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%main = OpFunction %void None %5
+%17 = OpLabel
+OpSelectionMerge %18 None
+OpBranchConditional %true %19 %20
+%19 = OpLabel
+OpBranch %18
+%20 = OpLabel
+OpBranch %18
+%18 = OpLabel
+%21 = OpPhi %v4float %12 %19 %14 %20
+OpStore %gl_FragColor %21
+OpReturn
+OpFunctionEnd
+)");
+}
+
 }  // namespace
diff --git a/test/diagnostic_test.cpp b/test/diagnostic_test.cpp
index 65af274..f023730 100644
--- a/test/diagnostic_test.cpp
+++ b/test/diagnostic_test.cpp
@@ -12,11 +12,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <algorithm>
+#include <sstream>
+
+#include "gmock/gmock.h"
 #include "unit_spirv.h"
 
 namespace {
 
 using libspirv::DiagnosticStream;
+using ::testing::Eq;
 
 // Returns a newly created diagnostic value.
 spv_diagnostic MakeValidDiagnostic() {
@@ -75,4 +80,25 @@
             spv_result_t(DiagnosticStream({}, nullptr, SPV_FAILED_MATCH)));
 }
 
+TEST(DiagnosticStream, MoveConstructorPreservesPreviousMessagesAndPreventsOutputFromExpiringValue) {
+  std::ostringstream messages;
+  int message_count = 0;
+  auto consumer = [&messages, &message_count](spv_message_level_t, const char*,
+                                              const spv_position_t&,
+                                              const char* msg) {
+    message_count++;
+    messages << msg;
+  };
+
+  // Enclose the DiagnosticStream variables in a scope to force destruction.
+  {
+    DiagnosticStream ds0({}, consumer, SPV_ERROR_INVALID_BINARY);
+    ds0 << "First";
+    DiagnosticStream ds1(std::move(ds0));
+    ds1 << "Second";
+  }
+  EXPECT_THAT(message_count, Eq(1));
+  EXPECT_THAT(messages.str(), Eq("FirstSecond"));
+}
+
 }  // anonymous namespace
diff --git a/test/link/CMakeLists.txt b/test/link/CMakeLists.txt
new file mode 100644
index 0000000..9768ab3
--- /dev/null
+++ b/test/link/CMakeLists.txt
@@ -0,0 +1,43 @@
+# Copyright (c) 2017 Pierre Moreau
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+add_spvtools_unittest(TARGET link_binary_version
+  SRCS binary_version_test.cpp
+  LIBS SPIRV-Tools-opt SPIRV-Tools-link
+)
+
+add_spvtools_unittest(TARGET link_memory_model
+  SRCS memory_model_test.cpp
+  LIBS SPIRV-Tools-opt SPIRV-Tools-link
+)
+
+add_spvtools_unittest(TARGET link_entry_points
+  SRCS entry_points_test.cpp
+  LIBS SPIRV-Tools-opt SPIRV-Tools-link
+)
+
+add_spvtools_unittest(TARGET link_global_values_amount
+  SRCS global_values_amount_test.cpp
+  LIBS SPIRV-Tools-opt SPIRV-Tools-link
+)
+
+add_spvtools_unittest(TARGET link_ids_limit
+  SRCS ids_limit_test.cpp
+  LIBS SPIRV-Tools-opt SPIRV-Tools-link
+)
+
+add_spvtools_unittest(TARGET link_matching_imports_to_exports
+  SRCS matching_imports_to_exports_test.cpp
+  LIBS SPIRV-Tools-opt SPIRV-Tools-link
+)
diff --git a/test/link/binary_version_test.cpp b/test/link/binary_version_test.cpp
new file mode 100644
index 0000000..b78440d
--- /dev/null
+++ b/test/link/binary_version_test.cpp
@@ -0,0 +1,54 @@
+// Copyright (c) 2017 Pierre Moreau
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "gmock/gmock.h"
+#include "linker_fixture.h"
+
+namespace {
+
+using BinaryVersion = spvtest::LinkerTest;
+
+TEST_F(BinaryVersion, LinkerChoosesMaxSpirvVersion) {
+  spvtest::Binaries binaries = {
+      {
+          SpvMagicNumber,
+          0x00000300u,
+          SPV_GENERATOR_CODEPLAY,
+          1u,  // NOTE: Bound
+          0u   // NOTE: Schema; reserved
+      },
+      {
+          SpvMagicNumber,
+          0x00000600u,
+          SPV_GENERATOR_CODEPLAY,
+          1u,  // NOTE: Bound
+          0u   // NOTE: Schema; reserved
+      },
+      {
+          SpvMagicNumber,
+          0x00000100u,
+          SPV_GENERATOR_CODEPLAY,
+          1u,  // NOTE: Bound
+          0u   // NOTE: Schema; reserved
+      }
+  };
+  spvtest::Binary linked_binary;
+
+  ASSERT_EQ(SPV_SUCCESS, Link(binaries, &linked_binary));
+  EXPECT_THAT(GetErrorMessage(), std::string());
+
+  ASSERT_EQ(0x00000600u, linked_binary[1]);
+}
+
+}  // anonymous namespace
diff --git a/test/link/entry_points_test.cpp b/test/link/entry_points_test.cpp
new file mode 100644
index 0000000..54561d5
--- /dev/null
+++ b/test/link/entry_points_test.cpp
@@ -0,0 +1,66 @@
+// Copyright (c) 2017 Pierre Moreau
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "gmock/gmock.h"
+#include "linker_fixture.h"
+
+namespace {
+
+using ::testing::HasSubstr;
+
+class EntryPoints : public spvtest::LinkerTest {};
+
+TEST_F(EntryPoints, SameModelDifferentName) {
+  const std::string body1 = R"(
+OpEntryPoint GLCompute %1 "foo"
+)";
+  const std::string body2 = R"(
+OpEntryPoint GLCompute %1 "bar"
+)";
+
+  spvtest::Binary linked_binary;
+  ASSERT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body2}, &linked_binary));
+  EXPECT_THAT(GetErrorMessage(), std::string());
+}
+
+TEST_F(EntryPoints, DifferentModelSameName) {
+  const std::string body1 = R"(
+OpEntryPoint GLCompute %1 "foo"
+)";
+  const std::string body2 = R"(
+OpEntryPoint Vertex %1 "foo"
+)";
+
+  spvtest::Binary linked_binary;
+  ASSERT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body2}, &linked_binary));
+  EXPECT_THAT(GetErrorMessage(), std::string());
+}
+
+TEST_F(EntryPoints, SameModelAndName) {
+  const std::string body1 = R"(
+OpEntryPoint GLCompute %1 "foo"
+)";
+  const std::string body2 = R"(
+OpEntryPoint GLCompute %1 "foo"
+)";
+
+  spvtest::Binary linked_binary;
+  ASSERT_EQ(SPV_ERROR_INTERNAL,
+            AssembleAndLink({body1, body2}, &linked_binary));
+  EXPECT_THAT(GetErrorMessage(),
+              HasSubstr("The entry point \"foo\", with execution model "
+                        "GLCompute, was already defined."));
+}
+
+}  // anonymous namespace
diff --git a/test/link/global_values_amount_test.cpp b/test/link/global_values_amount_test.cpp
new file mode 100644
index 0000000..068e6fa
--- /dev/null
+++ b/test/link/global_values_amount_test.cpp
@@ -0,0 +1,153 @@
+// Copyright (c) 2017 Pierre Moreau
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "gmock/gmock.h"
+#include "linker_fixture.h"
+
+namespace {
+
+using ::testing::HasSubstr;
+
+class EntryPoints : public spvtest::LinkerTest {
+ public:
+  EntryPoints() { binaries.reserve(0xFFFF); }
+
+  virtual void SetUp() override {
+    binaries.push_back({SpvMagicNumber,
+                        SpvVersion,
+                        SPV_GENERATOR_CODEPLAY,
+                        10u,  // NOTE: Bound
+                        0u,   // NOTE: Schema; reserved
+
+                        3u << SpvWordCountShift | SpvOpTypeFloat,
+                        1u,   // NOTE: Result ID
+                        32u,  // NOTE: Width
+
+                        4u << SpvWordCountShift | SpvOpTypePointer,
+                        2u,  // NOTE: Result ID
+                        SpvStorageClassInput,
+                        1u,  // NOTE: Type ID
+
+                        2u << SpvWordCountShift | SpvOpTypeVoid,
+                        3u,  // NOTE: Result ID
+
+                        3u << SpvWordCountShift | SpvOpTypeFunction,
+                        4u,  // NOTE: Result ID
+                        3u,  // NOTE: Return type
+
+                        5u << SpvWordCountShift | SpvOpFunction,
+                        3u,  // NOTE: Result type
+                        5u,  // NOTE: Result ID
+                        SpvFunctionControlMaskNone,
+                        4u,  // NOTE: Function type
+
+                        2u << SpvWordCountShift | SpvOpLabel,
+                        6u,  // NOTE: Result ID
+
+                        4u << SpvWordCountShift | SpvOpVariable,
+                        2u,  // NOTE: Type ID
+                        7u,  // NOTE: Result ID
+                        SpvStorageClassFunction,
+
+                        4u << SpvWordCountShift | SpvOpVariable,
+                        2u,  // NOTE: Type ID
+                        8u,  // NOTE: Result ID
+                        SpvStorageClassFunction,
+
+                        4u << SpvWordCountShift | SpvOpVariable,
+                        2u,  // NOTE: Type ID
+                        9u,  // NOTE: Result ID
+                        SpvStorageClassFunction,
+
+                        1u << SpvWordCountShift | SpvOpReturn,
+
+                        1u << SpvWordCountShift | SpvOpFunctionEnd});
+    for (size_t i = 0u; i < 2u; ++i) {
+      spvtest::Binary binary = {
+          SpvMagicNumber,
+          SpvVersion,
+          SPV_GENERATOR_CODEPLAY,
+          103u,  // NOTE: Bound
+          0u,    // NOTE: Schema; reserved
+
+          3u << SpvWordCountShift | SpvOpTypeFloat,
+          1u,   // NOTE: Result ID
+          32u,  // NOTE: Width
+
+          4u << SpvWordCountShift | SpvOpTypePointer,
+          2u,  // NOTE: Result ID
+          SpvStorageClassInput,
+          1u  // NOTE: Type ID
+      };
+
+      for (uint32_t j = 0u; j < 0xFFFFu / 2u; ++j) {
+        binary.push_back(4u << SpvWordCountShift | SpvOpVariable);
+        binary.push_back(2u);      // NOTE: Type ID
+        binary.push_back(j + 3u);  // NOTE: Result ID
+        binary.push_back(SpvStorageClassInput);
+      }
+      binaries.push_back(binary);
+    }
+  }
+  virtual void TearDown() override { binaries.clear(); }
+
+  spvtest::Binaries binaries;
+};
+
+// TODO(dneto): Fix performance issue for debug builds on Windows
+#if !(defined(SPIRV_WINDOWS) && defined(_DEBUG))
+
+TEST_F(EntryPoints, UnderLimit) {
+  spvtest::Binary linked_binary;
+
+  ASSERT_EQ(SPV_SUCCESS, Link(binaries, &linked_binary));
+  EXPECT_THAT(GetErrorMessage(), std::string());
+}
+
+TEST_F(EntryPoints, OverLimit) {
+  binaries.push_back({SpvMagicNumber,
+                      SpvVersion,
+                      SPV_GENERATOR_CODEPLAY,
+                      5u,  // NOTE: Bound
+                      0u,  // NOTE: Schema; reserved
+
+                      3u << SpvWordCountShift | SpvOpTypeFloat,
+                      1u,   // NOTE: Result ID
+                      32u,  // NOTE: Width
+
+                      4u << SpvWordCountShift | SpvOpTypePointer,
+                      2u,  // NOTE: Result ID
+                      SpvStorageClassInput,
+                      1u,  // NOTE: Type ID
+
+                      4u << SpvWordCountShift | SpvOpVariable,
+                      2u,  // NOTE: Type ID
+                      3u,  // NOTE: Result ID
+                      SpvStorageClassInput,
+
+                      4u << SpvWordCountShift | SpvOpVariable,
+                      2u,  // NOTE: Type ID
+                      4u,  // NOTE: Result ID
+                      SpvStorageClassInput});
+
+  spvtest::Binary linked_binary;
+
+  ASSERT_EQ(SPV_ERROR_INTERNAL, Link(binaries, &linked_binary));
+  EXPECT_THAT(GetErrorMessage(),
+              HasSubstr("The limit of global values, 65535, was exceeded; "
+                        "65536 global values were found."));
+}
+#endif // !(defined(SPIRV_WINDOWS) && defined(_DEBUG))
+
+}  // anonymous namespace
diff --git a/test/link/ids_limit_test.cpp b/test/link/ids_limit_test.cpp
new file mode 100644
index 0000000..0d1bf21
--- /dev/null
+++ b/test/link/ids_limit_test.cpp
@@ -0,0 +1,81 @@
+// Copyright (c) 2017 Pierre Moreau
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "gmock/gmock.h"
+#include "linker_fixture.h"
+
+namespace {
+
+using ::testing::HasSubstr;
+
+using IdsLimit = spvtest::LinkerTest;
+
+TEST_F(IdsLimit, UnderLimit) {
+  spvtest::Binaries binaries = {
+      {
+          SpvMagicNumber,
+          SpvVersion,
+          SPV_GENERATOR_CODEPLAY,
+          0x2FFFFFu, // NOTE: Bound
+          0u,        // NOTE: Schema; reserved
+      },
+      {
+          SpvMagicNumber,
+          SpvVersion,
+          SPV_GENERATOR_CODEPLAY,
+          0x100000u, // NOTE: Bound
+          0u,        // NOTE: Schema; reserved
+      }
+  };
+  spvtest::Binary linked_binary;
+
+  ASSERT_EQ(SPV_SUCCESS, Link(binaries, &linked_binary));
+  EXPECT_THAT(GetErrorMessage(), std::string());
+  ASSERT_EQ(0x3FFFFEu, linked_binary[3]);
+}
+
+TEST_F(IdsLimit, OverLimit) {
+  spvtest::Binaries binaries = {
+      {
+          SpvMagicNumber,
+          SpvVersion,
+          SPV_GENERATOR_CODEPLAY,
+          0x2FFFFFu, // NOTE: Bound
+          0u,        // NOTE: Schema; reserved
+      },
+      {
+          SpvMagicNumber,
+          SpvVersion,
+          SPV_GENERATOR_CODEPLAY,
+          0x100000u, // NOTE: Bound
+          0u,        // NOTE: Schema; reserved
+      },
+      {
+          SpvMagicNumber,
+          SpvVersion,
+          SPV_GENERATOR_CODEPLAY,
+          3u,  // NOTE: Bound
+          0u,  // NOTE: Schema; reserved
+      }
+  };
+
+  spvtest::Binary linked_binary;
+
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, Link(binaries, &linked_binary));
+  EXPECT_THAT(GetErrorMessage(),
+              HasSubstr("The limit of IDs, 4194303, was exceeded: 4194304 is "
+                        "the current ID bound."));
+}
+
+}  // anonymous namespace
diff --git a/test/link/linker_fixture.h b/test/link/linker_fixture.h
new file mode 100644
index 0000000..33a9660
--- /dev/null
+++ b/test/link/linker_fixture.h
@@ -0,0 +1,124 @@
+// Copyright (c) 2017 Pierre Moreau
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef LIBSPIRV_TEST_LINK_LINK_TEST
+#define LIBSPIRV_TEST_LINK_LINK_TEST
+
+#include <iostream>
+
+#include "source/spirv_constant.h"
+#include "unit_spirv.h"
+
+#include "spirv-tools/linker.hpp"
+
+namespace spvtest {
+
+using Binary = std::vector<uint32_t>;
+using Binaries = std::vector<Binary>;
+
+class LinkerTest : public ::testing::Test {
+ public:
+  LinkerTest()
+      : tools_(SPV_ENV_UNIVERSAL_1_2),
+        linker_(SPV_ENV_UNIVERSAL_1_2),
+        assemble_options_(spvtools::SpirvTools::kDefaultAssembleOption),
+        disassemble_options_(spvtools::SpirvTools::kDefaultDisassembleOption) {
+    const auto consumer = [this](spv_message_level_t level, const char*,
+                                 const spv_position_t& position,
+                                 const char* message) {
+      if (!error_message_.empty()) error_message_ += "\n";
+      switch (level) {
+        case SPV_MSG_FATAL:
+        case SPV_MSG_INTERNAL_ERROR:
+        case SPV_MSG_ERROR:
+          error_message_ += "ERROR";
+          break;
+        case SPV_MSG_WARNING:
+          error_message_ += "WARNING";
+          break;
+        case SPV_MSG_INFO:
+          error_message_ += "INFO";
+          break;
+        case SPV_MSG_DEBUG:
+          error_message_ += "DEBUG";
+          break;
+      }
+      error_message_ += ": " + std::to_string(position.index) + ": " + message;
+    };
+    tools_.SetMessageConsumer(consumer);
+    linker_.SetMessageConsumer(consumer);
+  }
+
+  virtual void TearDown() override { error_message_.clear(); }
+
+  // Assembles each of the given strings into SPIR-V binaries before linking
+  // them together. SPV_ERROR_INVALID_TEXT is returned if the assembling failed
+  // for any of the input strings, and SPV_ERROR_INVALID_POINTER if
+  // |linked_binary| is a null pointer.
+  spv_result_t AssembleAndLink(
+      const std::vector<std::string>& bodies, spvtest::Binary* linked_binary,
+      spvtools::LinkerOptions options = spvtools::LinkerOptions()) {
+    if (!linked_binary) return SPV_ERROR_INVALID_POINTER;
+
+    spvtest::Binaries binaries(bodies.size());
+    for (size_t i = 0u; i < bodies.size(); ++i)
+      if (!tools_.Assemble(bodies[i], binaries.data() + i, assemble_options_))
+        return SPV_ERROR_INVALID_TEXT;
+
+    return linker_.Link(binaries, *linked_binary, options);
+  }
+
+  // Links the given SPIR-V binaries together; SPV_ERROR_INVALID_POINTER is
+  // returned if |linked_binary| is a null pointer.
+  spv_result_t Link(
+      const spvtest::Binaries& binaries, spvtest::Binary* linked_binary,
+      spvtools::LinkerOptions options = spvtools::LinkerOptions()) {
+    if (!linked_binary) return SPV_ERROR_INVALID_POINTER;
+    return linker_.Link(binaries, *linked_binary, options);
+  }
+
+  // Disassembles |binary| and outputs the result in |text|. If |text| is a
+  // null pointer, SPV_ERROR_INVALID_POINTER is returned.
+  spv_result_t Disassemble(const spvtest::Binary& binary, std::string* text) {
+    if (!text) return SPV_ERROR_INVALID_POINTER;
+    return tools_.Disassemble(binary, text, disassemble_options_)
+               ? SPV_SUCCESS
+               : SPV_ERROR_INVALID_BINARY;
+  }
+
+  // Sets the options for the assembler.
+  void SetAssembleOptions(uint32_t assemble_options) {
+    assemble_options_ = assemble_options;
+  }
+
+  // Sets the options used by the disassembler.
+  void SetDisassembleOptions(uint32_t disassemble_options) {
+    disassemble_options_ = disassemble_options;
+  }
+
+  // Returns the accumulated error messages for the test.
+  std::string GetErrorMessage() const { return error_message_; }
+
+ private:
+  spvtools::SpirvTools
+      tools_;  // An instance for calling SPIRV-Tools functionalities.
+  spvtools::Linker linker_;
+  uint32_t assemble_options_;
+  uint32_t disassemble_options_;
+  std::string error_message_;
+};
+
+}  // namespace spvtest
+
+#endif  // LIBSPIRV_TEST_LINK_LINK_TEST
diff --git a/test/link/matching_imports_to_exports_test.cpp b/test/link/matching_imports_to_exports_test.cpp
new file mode 100644
index 0000000..894cfea
--- /dev/null
+++ b/test/link/matching_imports_to_exports_test.cpp
@@ -0,0 +1,326 @@
+// Copyright (c) 2017 Pierre Moreau
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "gmock/gmock.h"
+#include "linker_fixture.h"
+
+namespace {
+
+using ::testing::HasSubstr;
+using MatchingImportsToExports = spvtest::LinkerTest;
+
+TEST_F(MatchingImportsToExports, Default) {
+  const std::string body1 = R"(
+OpCapability Linkage
+OpDecorate %1 LinkageAttributes "foo" Import
+%2 = OpTypeFloat 32
+%1 = OpVariable %2 Uniform
+%3 = OpVariable %2 Input
+)";
+  const std::string body2 = R"(
+OpCapability Linkage
+OpDecorate %1 LinkageAttributes "foo" Export
+%2 = OpTypeFloat 32
+%3 = OpConstant %2 42
+%1 = OpVariable %2 Uniform %3
+)";
+
+  spvtest::Binary linked_binary;
+  ASSERT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body2}, &linked_binary))
+      << GetErrorMessage();
+
+  const std::string expected_res = R"(%1 = OpTypeFloat 32
+%2 = OpVariable %1 Input
+%3 = OpConstant %1 42
+%4 = OpVariable %1 Uniform %3
+)";
+  std::string res_body;
+  SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+  ASSERT_EQ(SPV_SUCCESS, Disassemble(linked_binary, &res_body))
+      << GetErrorMessage();
+  ASSERT_EQ(expected_res, res_body);
+}
+
+TEST_F(MatchingImportsToExports, NotALibraryExtraExports) {
+  const std::string body = R"(
+OpCapability Linkage
+OpDecorate %1 LinkageAttributes "foo" Export
+%2 = OpTypeFloat 32
+%1 = OpVariable %2 Uniform
+)";
+
+  spvtest::Binary linked_binary;
+  ASSERT_EQ(SPV_SUCCESS, AssembleAndLink({body}, &linked_binary))
+      << GetErrorMessage();
+
+  const std::string expected_res = R"(%1 = OpTypeFloat 32
+%2 = OpVariable %1 Uniform
+)";
+  std::string res_body;
+  SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+  ASSERT_EQ(SPV_SUCCESS, Disassemble(linked_binary, &res_body))
+      << GetErrorMessage();
+  ASSERT_EQ(expected_res, res_body);
+}
+
+TEST_F(MatchingImportsToExports, LibraryExtraExports) {
+  const std::string body = R"(
+OpCapability Linkage
+OpDecorate %1 LinkageAttributes "foo" Export
+%2 = OpTypeFloat 32
+%1 = OpVariable %2 Uniform
+)";
+
+  spvtest::Binary linked_binary;
+  spvtools::LinkerOptions options;
+  options.SetCreateLibrary(true);
+  ASSERT_EQ(SPV_SUCCESS, AssembleAndLink({body}, &linked_binary, options))
+      << GetErrorMessage();
+
+  const std::string expected_res = R"(OpCapability Linkage
+OpDecorate %1 LinkageAttributes "foo" Export
+%2 = OpTypeFloat 32
+%1 = OpVariable %2 Uniform
+)";
+  std::string res_body;
+  SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+  ASSERT_EQ(SPV_SUCCESS, Disassemble(linked_binary, &res_body))
+      << GetErrorMessage();
+  ASSERT_EQ(expected_res, res_body);
+}
+
+TEST_F(MatchingImportsToExports, UnresolvedImports) {
+  const std::string body1 = R"(
+OpCapability Linkage
+OpDecorate %1 LinkageAttributes "foo" Import
+%2 = OpTypeFloat 32
+%1 = OpVariable %2 Uniform
+)";
+  const std::string body2 = R"()";
+
+  spvtest::Binary linked_binary;
+  ASSERT_EQ(SPV_ERROR_INVALID_BINARY,
+            AssembleAndLink({body1, body2}, &linked_binary));
+  EXPECT_THAT(GetErrorMessage(),
+              HasSubstr("No export linkage was found for \"foo\"."));
+}
+
+TEST_F(MatchingImportsToExports, TypeMismatch) {
+  const std::string body1 = R"(
+OpCapability Linkage
+OpDecorate %1 LinkageAttributes "foo" Import
+%2 = OpTypeFloat 32
+%1 = OpVariable %2 Uniform
+%3 = OpVariable %2 Input
+)";
+  const std::string body2 = R"(
+OpCapability Linkage
+OpDecorate %1 LinkageAttributes "foo" Export
+%2 = OpTypeInt 32 0
+%3 = OpConstant %2 42
+%1 = OpVariable %2 Uniform %3
+)";
+
+  spvtest::Binary linked_binary;
+  ASSERT_EQ(SPV_ERROR_INVALID_BINARY,
+            AssembleAndLink({body1, body2}, &linked_binary))
+      << GetErrorMessage();
+  EXPECT_THAT(GetErrorMessage(),
+              HasSubstr("Type mismatch between imported variable/function %1 "
+                        "and exported variable/function %4"));
+}
+
+TEST_F(MatchingImportsToExports, MultipleDefinitions) {
+  const std::string body1 = R"(
+OpCapability Linkage
+OpDecorate %1 LinkageAttributes "foo" Import
+%2 = OpTypeFloat 32
+%1 = OpVariable %2 Uniform
+%3 = OpVariable %2 Input
+)";
+  const std::string body2 = R"(
+OpCapability Linkage
+OpDecorate %1 LinkageAttributes "foo" Export
+%2 = OpTypeFloat 32
+%3 = OpConstant %2 42
+%1 = OpVariable %2 Uniform %3
+)";
+  const std::string body3 = R"(
+OpCapability Linkage
+OpDecorate %1 LinkageAttributes "foo" Export
+%2 = OpTypeFloat 32
+%3 = OpConstant %2 -1
+%1 = OpVariable %2 Uniform %3
+)";
+
+  spvtest::Binary linked_binary;
+  ASSERT_EQ(SPV_ERROR_INVALID_BINARY,
+            AssembleAndLink({body1, body2, body3}, &linked_binary))
+      << GetErrorMessage();
+  EXPECT_THAT(
+      GetErrorMessage(),
+      HasSubstr("Too many export linkages, 2, were found for \"foo\"."));
+}
+
+TEST_F(MatchingImportsToExports, SameNameDifferentTypes) {
+  const std::string body1 = R"(
+OpCapability Linkage
+OpDecorate %1 LinkageAttributes "foo" Import
+%2 = OpTypeFloat 32
+%1 = OpVariable %2 Uniform
+%3 = OpVariable %2 Input
+)";
+  const std::string body2 = R"(
+OpCapability Linkage
+OpDecorate %1 LinkageAttributes "foo" Export
+%2 = OpTypeInt 32 0
+%3 = OpConstant %2 42
+%1 = OpVariable %2 Uniform %3
+)";
+  const std::string body3 = R"(
+OpCapability Linkage
+OpDecorate %1 LinkageAttributes "foo" Export
+%2 = OpTypeFloat 32
+%3 = OpConstant %2 12
+%1 = OpVariable %2 Uniform %3
+)";
+
+  spvtest::Binary linked_binary;
+  ASSERT_EQ(SPV_ERROR_INVALID_BINARY,
+            AssembleAndLink({body1, body2, body3}, &linked_binary))
+      << GetErrorMessage();
+  EXPECT_THAT(
+      GetErrorMessage(),
+      HasSubstr("Too many export linkages, 2, were found for \"foo\"."));
+}
+
+TEST_F(MatchingImportsToExports, DecorationMismatch) {
+  const std::string body1 = R"(
+OpCapability Linkage
+OpDecorate %1 LinkageAttributes "foo" Import
+OpDecorate %2 Constant
+%2 = OpTypeFloat 32
+%1 = OpVariable %2 Uniform
+%3 = OpVariable %2 Input
+)";
+  const std::string body2 = R"(
+OpCapability Linkage
+OpDecorate %1 LinkageAttributes "foo" Export
+%2 = OpTypeFloat 32
+%3 = OpConstant %2 42
+%1 = OpVariable %2 Uniform %3
+)";
+
+  spvtest::Binary linked_binary;
+  ASSERT_EQ(SPV_ERROR_INVALID_BINARY,
+            AssembleAndLink({body1, body2}, &linked_binary))
+      << GetErrorMessage();
+  EXPECT_THAT(GetErrorMessage(),
+              HasSubstr("Type mismatch between imported variable/function %1 "
+                        "and exported variable/function %4."));
+}
+
+TEST_F(MatchingImportsToExports, FuncParamAttr) {
+  const std::string body1 = R"(
+OpCapability Kernel
+OpCapability Linkage
+OpDecorate %1 LinkageAttributes "foo" Import
+OpDecorate %2 FuncParamAttr Zext
+%3 = OpTypeVoid
+%4 = OpTypeInt 32 0
+%5 = OpTypeFunction %3 %4
+%1 = OpFunction %3 None %5
+%2 = OpFunctionParameter %4
+OpFunctionEnd
+)";
+  const std::string body2 = R"(
+OpCapability Kernel
+OpCapability Linkage
+OpDecorate %1 LinkageAttributes "foo" Export
+OpDecorate %2 FuncParamAttr Sext
+%3 = OpTypeVoid
+%4 = OpTypeInt 32 0
+%5 = OpTypeFunction %3 %4
+%1 = OpFunction %3 None %5
+%2 = OpFunctionParameter %4
+%6 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  spvtest::Binary linked_binary;
+  ASSERT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body2}, &linked_binary))
+      << GetErrorMessage();
+
+  const std::string expected_res = R"(OpCapability Kernel
+OpDecorate %1 FuncParamAttr Sext
+%2 = OpTypeVoid
+%3 = OpTypeInt 32 0
+%4 = OpTypeFunction %2 %3
+%5 = OpFunction %2 None %4
+%1 = OpFunctionParameter %3
+%6 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+  std::string res_body;
+  SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+  ASSERT_EQ(SPV_SUCCESS, Disassemble(linked_binary, &res_body))
+      << GetErrorMessage();
+  ASSERT_EQ(expected_res, res_body);
+}
+
+TEST_F(MatchingImportsToExports, FunctionCtrl) {
+  const std::string body1 = R"(
+OpCapability Linkage
+OpDecorate %1 LinkageAttributes "foo" Import
+%2 = OpTypeVoid
+%3 = OpTypeFunction %2
+%4 = OpTypeFloat 32
+%5 = OpVariable %4 Uniform
+%1 = OpFunction %2 None %3
+OpFunctionEnd
+)";
+  const std::string body2 = R"(
+OpCapability Linkage
+OpDecorate %1 LinkageAttributes "foo" Export
+%2 = OpTypeVoid
+%3 = OpTypeFunction %2
+%1 = OpFunction %2 Inline %3
+%4 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  spvtest::Binary linked_binary;
+  ASSERT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body2}, &linked_binary))
+      << GetErrorMessage();
+
+  const std::string expected_res = R"(%1 = OpTypeVoid
+%2 = OpTypeFunction %1
+%3 = OpTypeFloat 32
+%4 = OpVariable %3 Uniform
+%5 = OpFunction %1 Inline %2
+%6 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+  std::string res_body;
+  SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+  ASSERT_EQ(SPV_SUCCESS, Disassemble(linked_binary, &res_body))
+      << GetErrorMessage();
+  ASSERT_EQ(expected_res, res_body);
+}
+
+}  // anonymous namespace
diff --git a/test/link/memory_model_test.cpp b/test/link/memory_model_test.cpp
new file mode 100644
index 0000000..76eae9a
--- /dev/null
+++ b/test/link/memory_model_test.cpp
@@ -0,0 +1,71 @@
+// Copyright (c) 2017 Pierre Moreau
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "gmock/gmock.h"
+#include "linker_fixture.h"
+
+namespace {
+
+using ::testing::HasSubstr;
+
+using MemoryModel = spvtest::LinkerTest;
+
+TEST_F(MemoryModel, Default) {
+  const std::string body1 = R"(
+OpMemoryModel Logical Simple
+)";
+  const std::string body2 = R"(
+OpMemoryModel Logical Simple
+)";
+
+  spvtest::Binary linked_binary;
+  ASSERT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body2}, &linked_binary));
+  EXPECT_THAT(GetErrorMessage(), std::string());
+
+  ASSERT_EQ(SpvAddressingModelLogical, linked_binary[6]);
+  ASSERT_EQ(SpvMemoryModelSimple, linked_binary[7]);
+}
+
+TEST_F(MemoryModel, AddressingMismatch) {
+  const std::string body1 = R"(
+OpMemoryModel Logical Simple
+)";
+  const std::string body2 = R"(
+OpMemoryModel Physical32 Simple
+)";
+
+  spvtest::Binary linked_binary;
+  ASSERT_EQ(SPV_ERROR_INTERNAL,
+            AssembleAndLink({body1, body2}, &linked_binary));
+  EXPECT_THAT(
+      GetErrorMessage(),
+      HasSubstr("Conflicting addressing models: Logical vs Physical32."));
+}
+
+TEST_F(MemoryModel, MemoryMismatch) {
+  const std::string body1 = R"(
+OpMemoryModel Logical Simple
+)";
+  const std::string body2 = R"(
+OpMemoryModel Logical GLSL450
+)";
+
+  spvtest::Binary linked_binary;
+  ASSERT_EQ(SPV_ERROR_INTERNAL,
+            AssembleAndLink({body1, body2}, &linked_binary));
+  EXPECT_THAT(GetErrorMessage(),
+              HasSubstr("Conflicting memory models: Simple vs GLSL450."));
+}
+
+}  // anonymous namespace
diff --git a/test/opt/CMakeLists.txt b/test/opt/CMakeLists.txt
index 8bb13c3..f19ff7f 100644
--- a/test/opt/CMakeLists.txt
+++ b/test/opt/CMakeLists.txt
@@ -17,6 +17,11 @@
   LIBS SPIRV-Tools-opt
 )
 
+add_spvtools_unittest(TARGET instruction_list
+        SRCS instruction_list_test.cpp
+        LIBS SPIRV-Tools-opt
+        )
+
 add_spvtools_unittest(TARGET ir_loader
   SRCS ir_loader_test.cpp
   LIBS SPIRV-Tools-opt
@@ -98,6 +103,11 @@
   LIBS SPIRV-Tools-opt
 )
 
+add_spvtools_unittest(TARGET pass_dead_variable_elim
+        SRCS dead_variable_elim_test.cpp pass_utils.cpp
+        LIBS SPIRV-Tools-opt
+        )
+
 add_spvtools_unittest(TARGET pass_aggressive_dce
   SRCS aggressive_dead_code_elim_test.cpp pass_utils.cpp
   LIBS SPIRV-Tools-opt
@@ -113,6 +123,16 @@
   LIBS SPIRV-Tools-opt
 )
 
+add_spvtools_unittest(TARGET pass_eliminate_dead_functions
+  SRCS eliminate_dead_functions_test.cpp pass_utils.cpp
+  LIBS SPIRV-Tools-opt
+)
+
+add_spvtools_unittest(TARGET pass_pass
+  SRCS pass_test.cpp pass_utils.cpp
+  LIBS SPIRV-Tools-opt
+)
+
 add_spvtools_unittest(TARGET pass_utils
   SRCS utils_test.cpp pass_utils.cpp
   LIBS SPIRV-Tools-opt
@@ -168,3 +188,13 @@
   SRCS line_debug_info_test.cpp pass_utils.cpp
   LIBS SPIRV-Tools-opt
 )
+
+add_spvtools_unittest(TARGET pass_strength_reduction
+  SRCS strength_reduction_test.cpp pass_utils.cpp
+  LIBS SPIRV-Tools-opt
+)
+
+add_spvtools_unittest(TARGET cfg_cleanup
+  SRCS cfg_cleanup_test.cpp pass_utils.cpp
+  LIBS SPIRV-Tools-opt
+)
diff --git a/test/opt/aggressive_dead_code_elim_test.cpp b/test/opt/aggressive_dead_code_elim_test.cpp
index ac8cf74..8e8b407 100644
--- a/test/opt/aggressive_dead_code_elim_test.cpp
+++ b/test/opt/aggressive_dead_code_elim_test.cpp
@@ -1051,6 +1051,343 @@
       defs_before + func_before, defs_after + func_after, true, true);
 }
 
+TEST_F(AggressiveDCETest, NoParamStoreElim) {
+  // Should not eliminate stores to params
+  //
+  // #version 450
+  // 
+  // layout(location = 0) in vec4 BaseColor;
+  // layout(location = 0) out vec4 OutColor;
+  // 
+  // void foo(in vec4 v1, out vec4 v2)
+  // {
+  //     v2 = -v1;
+  // }
+  // 
+  // void main()
+  // {
+  //     foo(BaseColor, OutColor);
+  // }
+
+  const std::string assembly =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BaseColor %OutColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+OpName %foo_vf4_vf4_ "foo(vf4;vf4;"
+OpName %v1 "v1"
+OpName %v2 "v2"
+OpName %BaseColor "BaseColor"
+OpName %OutColor "OutColor"
+OpName %param "param"
+OpName %param_0 "param"
+OpDecorate %BaseColor Location 0
+OpDecorate %OutColor Location 0
+%void = OpTypeVoid
+%11 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%15 = OpTypeFunction %void %_ptr_Function_v4float %_ptr_Function_v4float
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+%main = OpFunction %void None %11
+%18 = OpLabel
+%param = OpVariable %_ptr_Function_v4float Function
+%param_0 = OpVariable %_ptr_Function_v4float Function
+%19 = OpLoad %v4float %BaseColor
+OpStore %param %19
+%20 = OpFunctionCall %void %foo_vf4_vf4_ %param %param_0
+%21 = OpLoad %v4float %param_0
+OpStore %OutColor %21
+OpReturn
+OpFunctionEnd
+%foo_vf4_vf4_ = OpFunction %void None %15
+%v1 = OpFunctionParameter %_ptr_Function_v4float
+%v2 = OpFunctionParameter %_ptr_Function_v4float
+%22 = OpLabel
+%23 = OpLoad %v4float %v1
+%24 = OpFNegate %v4float %23
+OpStore %v2 %24
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<opt::AggressiveDCEPass>(
+      assembly, assembly, true, true);
+}
+
+TEST_F(AggressiveDCETest, PrivateStoreElimInEntryNoCalls) {
+  // Eliminate stores to private in entry point with no calls
+  // Note: Not legal GLSL
+  //
+  // layout(location = 0) in vec4 BaseColor;
+  // layout(location = 1) in vec4 Dead;
+  // layout(location = 0) out vec4 OutColor;
+  //
+  // private vec4 dv;
+  // 
+  // void main()
+  // {
+  //     vec4 v = BaseColor;
+  //     dv = Dead;
+  //     OutColor = v;
+  // }
+
+  const std::string predefs =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BaseColor %Dead %OutColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+OpName %v "v"
+OpName %BaseColor "BaseColor"
+OpName %dv "dv"
+OpName %Dead "Dead"
+OpName %OutColor "OutColor"
+OpDecorate %BaseColor Location 0
+OpDecorate %Dead Location 1
+OpDecorate %OutColor Location 0
+%void = OpTypeVoid
+%9 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Private_v4float = OpTypePointer Private %v4float
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%Dead = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%dv = OpVariable %_ptr_Private_v4float Private
+%OutColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+  const std::string main_before =
+      R"(%main = OpFunction %void None %9
+%16 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%17 = OpLoad %v4float %BaseColor
+OpStore %v %17
+%18 = OpLoad %v4float %Dead
+OpStore %dv %18
+%19 = OpLoad %v4float %v
+%20 = OpFNegate %v4float %19
+OpStore %OutColor %20
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string main_after =
+      R"(%main = OpFunction %void None %9
+%16 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%17 = OpLoad %v4float %BaseColor
+OpStore %v %17
+%19 = OpLoad %v4float %v
+%20 = OpFNegate %v4float %19
+OpStore %OutColor %20
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<opt::AggressiveDCEPass>(
+      predefs + main_before, predefs + main_after, true, true);
+}
+
+TEST_F(AggressiveDCETest, NoPrivateStoreElimIfLoad) {
+  // Should not eliminate stores to private when there is a load
+  // Note: Not legal GLSL
+  //
+  // #version 450
+  // 
+  // layout(location = 0) in vec4 BaseColor;
+  // layout(location = 0) out vec4 OutColor;
+  //
+  // private vec4 pv;
+  // 
+  // void main()
+  // {
+  //     pv = BaseColor;
+  //     OutColor = pv;
+  // }
+
+  const std::string assembly =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BaseColor %OutColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+OpName %pv "pv"
+OpName %BaseColor "BaseColor"
+OpName %OutColor "OutColor"
+OpDecorate %BaseColor Location 0
+OpDecorate %OutColor Location 0
+%void = OpTypeVoid
+%7 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Private_v4float = OpTypePointer Private %v4float
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+%pv = OpVariable %_ptr_Private_v4float Private
+%main = OpFunction %void None %7
+%13 = OpLabel
+%14 = OpLoad %v4float %BaseColor
+OpStore %pv %14
+%15 = OpLoad %v4float %pv
+%16 = OpFNegate %v4float %15
+OpStore %OutColor %16
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<opt::AggressiveDCEPass>(
+      assembly, assembly, true, true);
+}
+
+TEST_F(AggressiveDCETest, NoPrivateStoreElimWithCall) {
+  // Should not eliminate stores to private when function contains call
+  // Note: Not legal GLSL
+  //
+  // #version 450
+  // 
+  // layout(location = 0) in vec4 BaseColor;
+  // layout(location = 0) out vec4 OutColor;
+  //
+  // private vec4 v1;
+  // 
+  // void foo()
+  // {
+  //     OutColor = -v1;
+  // }
+  // 
+  // void main()
+  // {
+  //     v1 = BaseColor;
+  //     foo();
+  // }
+
+  const std::string assembly =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %OutColor %BaseColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+OpName %foo_ "foo("
+OpName %OutColor "OutColor"
+OpName %v1 "v1"
+OpName %BaseColor "BaseColor"
+OpDecorate %OutColor Location 0
+OpDecorate %BaseColor Location 0
+%void = OpTypeVoid
+%8 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+%_ptr_Private_v4float = OpTypePointer Private %v4float
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%v1 = OpVariable %_ptr_Private_v4float Private
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%main = OpFunction %void None %8
+%14 = OpLabel
+%15 = OpLoad %v4float %BaseColor
+OpStore %v1 %15
+%16 = OpFunctionCall %void %foo_
+OpReturn
+OpFunctionEnd
+%foo_ = OpFunction %void None %8
+%17 = OpLabel
+%18 = OpLoad %v4float %v1
+%19 = OpFNegate %v4float %18
+OpStore %OutColor %19
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<opt::AggressiveDCEPass>(
+      assembly, assembly, true, true);
+}
+
+TEST_F(AggressiveDCETest, NoPrivateStoreElimInNonEntry) {
+  // Should not eliminate stores to private when function is not entry point
+  // Note: Not legal GLSL
+  //
+  // #version 450
+  // 
+  // layout(location = 0) in vec4 BaseColor;
+  // layout(location = 0) out vec4 OutColor;
+  //
+  // private vec4 v1;
+  // 
+  // void foo()
+  // {
+  //     v1 = BaseColor;
+  // }
+  // 
+  // void main()
+  // {
+  //     foo();
+  //     OutColor = -v1;
+  // }
+
+  const std::string assembly =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BaseColor %OutColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+OpName %foo_ "foo("
+OpName %v1 "v1"
+OpName %BaseColor "BaseColor"
+OpName %OutColor "OutColor"
+OpDecorate %BaseColor Location 0
+OpDecorate %OutColor Location 0
+%void = OpTypeVoid
+%8 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Private_v4float = OpTypePointer Private %v4float
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%v1 = OpVariable %_ptr_Private_v4float Private
+%OutColor = OpVariable %_ptr_Output_v4float Output
+%main = OpFunction %void None %8
+%14 = OpLabel
+%15 = OpFunctionCall %void %foo_
+%16 = OpLoad %v4float %v1
+%17 = OpFNegate %v4float %16
+OpStore %OutColor %17
+OpReturn
+OpFunctionEnd
+%foo_ = OpFunction %void None %8
+%18 = OpLabel
+%19 = OpLoad %v4float %BaseColor
+OpStore %v1 %19
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<opt::AggressiveDCEPass>(
+      assembly, assembly, true, true);
+}
+
 
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 //
diff --git a/test/opt/cfg_cleanup_test.cpp b/test/opt/cfg_cleanup_test.cpp
new file mode 100644
index 0000000..9699ee4
--- /dev/null
+++ b/test/opt/cfg_cleanup_test.cpp
@@ -0,0 +1,448 @@
+// Copyright (c) 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "pass_fixture.h"
+#include "pass_utils.h"
+
+namespace {
+
+using namespace spvtools;
+
+using CFGCleanupTest = PassTest<::testing::Test>;
+
+TEST_F(CFGCleanupTest, RemoveUnreachableBlocks) {
+  const std::string declarations = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %inf %outf4
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+OpName %inf "inf"
+OpName %outf4 "outf4"
+OpDecorate %inf Location 0
+OpDecorate %outf4 Location 0
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Input_float = OpTypePointer Input %float
+%inf = OpVariable %_ptr_Input_float Input
+%float_2 = OpConstant %float 2
+%bool = OpTypeBool
+%v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%outf4 = OpVariable %_ptr_Output_v4float Output
+%float_n0_5 = OpConstant %float -0.5
+)";
+
+  const std::string body_before = R"(%main = OpFunction %void None %6
+%14 = OpLabel
+OpSelectionMerge %17 None
+OpBranch %18
+%19 = OpLabel
+%20 = OpLoad %float %inf
+%21 = OpCompositeConstruct %v4float %20 %20 %20 %20
+OpStore %outf4 %21
+OpBranch %17
+%18 = OpLabel
+%22 = OpLoad %float %inf
+%23 = OpFAdd %float %22 %float_n0_5
+%24 = OpCompositeConstruct %v4float %23 %23 %23 %23
+OpStore %outf4 %24
+OpBranch %17
+%17 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string body_after = R"(%main = OpFunction %void None %6
+%14 = OpLabel
+OpSelectionMerge %15 None
+OpBranch %16
+%16 = OpLabel
+%20 = OpLoad %float %inf
+%21 = OpFAdd %float %20 %float_n0_5
+%22 = OpCompositeConstruct %v4float %21 %21 %21 %21
+OpStore %outf4 %22
+OpBranch %15
+%15 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<opt::CFGCleanupPass>(
+      declarations + body_before, declarations + body_after, true, true);
+}
+
+TEST_F(CFGCleanupTest, RemoveDecorations) {
+  const std::string before = R"(
+                       OpCapability Shader
+                  %1 = OpExtInstImport "GLSL.std.450"
+                       OpMemoryModel Logical GLSL450
+                       OpEntryPoint Fragment %main "main"
+                       OpName %main "main"
+                       OpName %x "x"
+                       OpName %dead "dead"
+                       OpDecorate %x RelaxedPrecision
+                       OpDecorate %dead RelaxedPrecision
+               %void = OpTypeVoid
+                  %6 = OpTypeFunction %void
+              %float = OpTypeFloat 32
+%_ptr_Function_float = OpTypePointer Function %float
+            %float_2 = OpConstant %float 2
+            %float_4 = OpConstant %float 4
+
+               %main = OpFunction %void None %6
+                 %14 = OpLabel
+                  %x = OpVariable %_ptr_Function_float Function
+                       OpSelectionMerge %17 None
+                       OpBranch %18
+                 %19 = OpLabel
+               %dead = OpVariable %_ptr_Function_float Function
+                       OpStore %dead %float_2
+                       OpBranch %17
+                 %18 = OpLabel
+                       OpStore %x %float_4
+                       OpBranch %17
+                 %17 = OpLabel
+                       OpReturn
+                       OpFunctionEnd
+)";
+
+  const std::string after = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpName %main "main"
+OpName %x "x"
+OpDecorate %x RelaxedPrecision
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Function_float = OpTypePointer Function %float
+%float_2 = OpConstant %float 2
+%float_4 = OpConstant %float 4
+%main = OpFunction %void None %6
+%11 = OpLabel
+%x = OpVariable %_ptr_Function_float Function
+OpSelectionMerge %12 None
+OpBranch %13
+%13 = OpLabel
+OpStore %x %float_4
+OpBranch %12
+%12 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<opt::CFGCleanupPass>(before, after, true, true);
+}
+
+
+TEST_F(CFGCleanupTest, UpdatePhis) {
+  const std::string before = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %y %outparm
+               OpName %main "main"
+               OpName %y "y"
+               OpName %outparm "outparm"
+               OpDecorate %y Flat
+               OpDecorate %y Location 0
+               OpDecorate %outparm Location 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%_ptr_Input_int = OpTypePointer Input %int
+          %y = OpVariable %_ptr_Input_int Input
+     %int_10 = OpConstant %int 10
+       %bool = OpTypeBool
+     %int_42 = OpConstant %int 42
+     %int_23 = OpConstant %int 23
+      %int_5 = OpConstant %int 5
+%_ptr_Output_int = OpTypePointer Output %int
+    %outparm = OpVariable %_ptr_Output_int Output
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+         %11 = OpLoad %int %y
+               OpBranch %21
+         %16 = OpLabel
+         %20 = OpIAdd %int %11 %int_42
+               OpBranch %17
+         %21 = OpLabel
+         %24 = OpISub %int %11 %int_23
+               OpBranch %17
+         %17 = OpLabel
+         %31 = OpPhi %int %20 %16 %24 %21
+         %27 = OpIAdd %int %31 %int_5
+               OpStore %outparm %27
+               OpReturn
+               OpFunctionEnd
+)";
+
+  const std::string after = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %y %outparm
+OpName %main "main"
+OpName %y "y"
+OpName %outparm "outparm"
+OpDecorate %y Flat
+OpDecorate %y Location 0
+OpDecorate %outparm Location 0
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%_ptr_Input_int = OpTypePointer Input %int
+%y = OpVariable %_ptr_Input_int Input
+%int_10 = OpConstant %int 10
+%bool = OpTypeBool
+%int_42 = OpConstant %int 42
+%int_23 = OpConstant %int 23
+%int_5 = OpConstant %int 5
+%_ptr_Output_int = OpTypePointer Output %int
+%outparm = OpVariable %_ptr_Output_int Output
+%main = OpFunction %void None %6
+%16 = OpLabel
+%17 = OpLoad %int %y
+OpBranch %18
+%18 = OpLabel
+%22 = OpISub %int %17 %int_23
+OpBranch %21
+%21 = OpLabel
+%23 = OpPhi %int %22 %18
+%24 = OpIAdd %int %23 %int_5
+OpStore %outparm %24
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<opt::CFGCleanupPass>(before, after, true, true);
+}
+
+TEST_F(CFGCleanupTest, RemoveNamedLabels) {
+  const std::string before = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource GLSL 430
+               OpName %main "main"
+               OpName %dead "dead"
+       %void = OpTypeVoid
+          %5 = OpTypeFunction %void
+       %main = OpFunction %void None %5
+          %6 = OpLabel
+               OpReturn
+       %dead = OpLabel
+               OpReturn
+               OpFunctionEnd)";
+
+    const std::string after = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpSource GLSL 430
+OpName %main "main"
+%void = OpTypeVoid
+%5 = OpTypeFunction %void
+%main = OpFunction %void None %5
+%6 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<opt::CFGCleanupPass>(before, after, true, true);
+}
+
+TEST_F(CFGCleanupTest, RemovePhiArgsFromFarBlocks) {
+    const std::string before = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %y %outparm
+               OpName %main "main"
+               OpName %y "y"
+               OpName %outparm "outparm"
+               OpDecorate %y Flat
+               OpDecorate %y Location 0
+               OpDecorate %outparm Location 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%_ptr_Input_int = OpTypePointer Input %int
+          %y = OpVariable %_ptr_Input_int Input
+     %int_42 = OpConstant %int 42
+%_ptr_Output_int = OpTypePointer Output %int
+    %outparm = OpVariable %_ptr_Output_int Output
+     %int_14 = OpConstant %int 14
+     %int_15 = OpConstant %int 15
+      %int_5 = OpConstant %int 5
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               OpBranch %40
+         %41 = OpLabel
+         %11 = OpLoad %int %y
+               OpBranch %40
+         %40 = OpLabel
+         %12 = OpLoad %int %y
+               OpSelectionMerge %16 None
+               OpSwitch %12 %16 10 %13 13 %14 18 %15
+         %13 = OpLabel
+               OpBranch %16
+         %14 = OpLabel
+               OpStore %outparm %int_14
+               OpBranch %16
+         %15 = OpLabel
+               OpStore %outparm %int_15
+               OpBranch %16
+         %16 = OpLabel
+         %30 = OpPhi %int %11 %41 %int_42 %13 %11 %14 %11 %15
+         %28 = OpIAdd %int %30 %int_5
+               OpStore %outparm %28
+               OpReturn
+               OpFunctionEnd)";
+
+    const std::string after = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %y %outparm
+OpName %main "main"
+OpName %y "y"
+OpName %outparm "outparm"
+OpDecorate %y Flat
+OpDecorate %y Location 0
+OpDecorate %outparm Location 0
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%_ptr_Input_int = OpTypePointer Input %int
+%y = OpVariable %_ptr_Input_int Input
+%int_42 = OpConstant %int 42
+%_ptr_Output_int = OpTypePointer Output %int
+%outparm = OpVariable %_ptr_Output_int Output
+%int_14 = OpConstant %int 14
+%int_15 = OpConstant %int 15
+%int_5 = OpConstant %int 5
+%26 = OpUndef %int
+%main = OpFunction %void None %6
+%15 = OpLabel
+OpBranch %16
+%16 = OpLabel
+%19 = OpLoad %int %y
+OpSelectionMerge %20 None
+OpSwitch %19 %20 10 %21 13 %22 18 %23
+%21 = OpLabel
+OpBranch %20
+%22 = OpLabel
+OpStore %outparm %int_14
+OpBranch %20
+%23 = OpLabel
+OpStore %outparm %int_15
+OpBranch %20
+%20 = OpLabel
+%24 = OpPhi %int %int_42 %21 %26 %22 %26 %23
+%25 = OpIAdd %int %24 %int_5
+OpStore %outparm %25
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<opt::CFGCleanupPass>(before, after, true, true);
+}
+
+TEST_F(CFGCleanupTest, RemovePhiConstantArgs) {
+    const std::string before = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %y %outparm
+               OpName %main "main"
+               OpName %y "y"
+               OpName %outparm "outparm"
+               OpDecorate %y Flat
+               OpDecorate %y Location 0
+               OpDecorate %outparm Location 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+          %y = OpVariable %_ptr_Input_int Input
+     %int_10 = OpConstant %int 10
+       %bool = OpTypeBool
+%_ptr_Function_int = OpTypePointer Function %int
+     %int_23 = OpConstant %int 23
+      %int_5 = OpConstant %int 5
+%_ptr_Output_int = OpTypePointer Output %int
+    %outparm = OpVariable %_ptr_Output_int Output
+         %24 = OpUndef %int
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               OpBranch %14
+         %40 = OpLabel
+          %9 = OpLoad %int %y
+         %12 = OpSGreaterThan %bool %9 %int_10
+               OpSelectionMerge %14 None
+               OpBranchConditional %12 %13 %14
+         %13 = OpLabel
+               OpBranch %14
+         %14 = OpLabel
+         %25 = OpPhi %int %24 %5 %int_23 %13
+         %20 = OpIAdd %int %25 %int_5
+               OpStore %outparm %20
+               OpReturn
+               OpFunctionEnd)";
+
+    const std::string after = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %y %outparm
+OpName %main "main"
+OpName %y "y"
+OpName %outparm "outparm"
+OpDecorate %y Flat
+OpDecorate %y Location 0
+OpDecorate %outparm Location 0
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+%y = OpVariable %_ptr_Input_int Input
+%int_10 = OpConstant %int 10
+%bool = OpTypeBool
+%_ptr_Function_int = OpTypePointer Function %int
+%int_23 = OpConstant %int 23
+%int_5 = OpConstant %int 5
+%_ptr_Output_int = OpTypePointer Output %int
+%outparm = OpVariable %_ptr_Output_int Output
+%15 = OpUndef %int
+%main = OpFunction %void None %6
+%16 = OpLabel
+OpBranch %17
+%17 = OpLabel
+%22 = OpPhi %int %15 %16
+%23 = OpIAdd %int %22 %int_5
+OpStore %outparm %23
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<opt::CFGCleanupPass>(before, after, true, true);
+}
+}  // anonymous namespace
diff --git a/test/opt/common_uniform_elim_test.cpp b/test/opt/common_uniform_elim_test.cpp
index 61917e8..bc642f3 100644
--- a/test/opt/common_uniform_elim_test.cpp
+++ b/test/opt/common_uniform_elim_test.cpp
@@ -14,7 +14,6 @@
 // limitations under the License.
 
 #include "pass_fixture.h"
-#include "pass_utils.h"
 
 namespace {
 
@@ -664,6 +663,380 @@
       predefs + before, predefs + after, true, true);
 }
 
+  TEST_F(CommonUniformElimTest, Volatile1) {
+  // Note: This test exemplifies the following:
+  // - Same test as Basic1 with the exception that
+  //   the Load of g_F in else-branch is volatile
+  // - Common uniform (%_) load floated to nearest non-controlled block
+  //
+  // #version 140
+  // in vec4 BaseColor;
+  // in float fi;
+  //
+  // layout(std140) uniform U_t
+  // {
+  //     float g_F;
+  //     float g_F2;
+  // } ;
+  //
+  // void main()
+  // {
+  //     vec4 v = BaseColor;
+  //     if (fi > 0) {
+  //       v = v * g_F;
+  //     }
+  //     else {
+  //       float f2 = g_F2 - g_F;
+  //       v = v * f2;
+  //     }
+  //     gl_FragColor = v;
+  // }
+
+  const std::string predefs =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BaseColor %fi %gl_FragColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %v "v"
+OpName %BaseColor "BaseColor"
+OpName %fi "fi"
+OpName %U_t "U_t"
+OpMemberName %U_t 0 "g_F"
+OpMemberName %U_t 1 "g_F2"
+OpName %_ ""
+OpName %f2 "f2"
+OpName %gl_FragColor "gl_FragColor"
+OpMemberDecorate %U_t 0 Offset 0
+OpMemberDecorate %U_t 1 Offset 4
+OpDecorate %U_t Block
+OpDecorate %_ DescriptorSet 0
+%void = OpTypeVoid
+%11 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%_ptr_Input_float = OpTypePointer Input %float
+%fi = OpVariable %_ptr_Input_float Input
+%float_0 = OpConstant %float 0
+%bool = OpTypeBool
+%U_t = OpTypeStruct %float %float
+%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t
+%_ = OpVariable %_ptr_Uniform_U_t Uniform
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+%_ptr_Function_float = OpTypePointer Function %float
+%int_1 = OpConstant %int 1
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+  const std::string before =
+      R"(%main = OpFunction %void None %11
+%26 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%f2 = OpVariable %_ptr_Function_float Function
+%27 = OpLoad %v4float %BaseColor
+OpStore %v %27
+%28 = OpLoad %float %fi
+%29 = OpFOrdGreaterThan %bool %28 %float_0
+OpSelectionMerge %30 None
+OpBranchConditional %29 %31 %32
+%31 = OpLabel
+%33 = OpLoad %v4float %v
+%34 = OpAccessChain %_ptr_Uniform_float %_ %int_0
+%35 = OpLoad %float %34
+%36 = OpVectorTimesScalar %v4float %33 %35
+OpStore %v %36
+OpBranch %30
+%32 = OpLabel
+%37 = OpAccessChain %_ptr_Uniform_float %_ %int_1
+%38 = OpLoad %float %37
+%39 = OpAccessChain %_ptr_Uniform_float %_ %int_0
+%40 = OpLoad %float %39 Volatile
+%41 = OpFSub %float %38 %40
+OpStore %f2 %41
+%42 = OpLoad %v4float %v
+%43 = OpLoad %float %f2
+%44 = OpVectorTimesScalar %v4float %42 %43
+OpStore %v %44
+OpBranch %30
+%30 = OpLabel
+%45 = OpLoad %v4float %v
+OpStore %gl_FragColor %45
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string after =
+      R"(%main = OpFunction %void None %11
+%26 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%f2 = OpVariable %_ptr_Function_float Function
+%50 = OpLoad %U_t %_
+%27 = OpLoad %v4float %BaseColor
+OpStore %v %27
+%28 = OpLoad %float %fi
+%29 = OpFOrdGreaterThan %bool %28 %float_0
+OpSelectionMerge %30 None
+OpBranchConditional %29 %31 %32
+%31 = OpLabel
+%33 = OpLoad %v4float %v
+%47 = OpCompositeExtract %float %50 0
+%36 = OpVectorTimesScalar %v4float %33 %47
+OpStore %v %36
+OpBranch %30
+%32 = OpLabel
+%49 = OpCompositeExtract %float %50 1
+%39 = OpAccessChain %_ptr_Uniform_float %_ %int_0
+%40 = OpLoad %float %39 Volatile
+%41 = OpFSub %float %49 %40
+OpStore %f2 %41
+%42 = OpLoad %v4float %v
+%43 = OpLoad %float %f2
+%44 = OpVectorTimesScalar %v4float %42 %43
+OpStore %v %44
+OpBranch %30
+%30 = OpLabel
+%45 = OpLoad %v4float %v
+OpStore %gl_FragColor %45
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<opt::CommonUniformElimPass>(
+  predefs + before, predefs + after, true, true);
+}
+
+TEST_F(CommonUniformElimTest, Volatile2) {
+  // Note: This test exemplifies the following:
+  // - Same test as Basic1 with the exception that
+  //   U_t is Volatile.
+  // - No optimizations are applied
+  //
+  // #version 430
+  // in vec4 BaseColor;
+  // in float fi;
+  //
+  // layout(std430) volatile buffer U_t
+  // {
+  //   float g_F;
+  //   float g_F2;
+  // };
+  //
+  //
+  // void main(void)
+  // {
+  //   vec4 v = BaseColor;
+  //   if (fi > 0) {
+  //     v = v * g_F;
+  //   } else {
+  //     float f2 = g_F2 - g_F;
+  //     v = v * f2;
+  //   }
+  // }
+
+  const std::string text =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BaseColor %fi
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 430
+OpName %main "main"
+OpName %v "v"
+OpName %BaseColor "BaseColor"
+OpName %fi "fi"
+OpName %U_t "U_t"
+OpMemberName %U_t 0 "g_F"
+OpMemberName %U_t 1 "g_F2"
+OpName %_ ""
+OpName %f2 "f2"
+OpDecorate %BaseColor Location 0
+OpDecorate %fi Location 0
+OpMemberDecorate %U_t 0 Volatile
+OpMemberDecorate %U_t 0 Offset 0
+OpMemberDecorate %U_t 1 Volatile
+OpMemberDecorate %U_t 1 Offset 4
+OpDecorate %U_t BufferBlock
+OpDecorate %_ DescriptorSet 0
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%_ptr_Input_float = OpTypePointer Input %float
+%fi = OpVariable %_ptr_Input_float Input
+%float_0 = OpConstant %float 0
+%bool = OpTypeBool
+%U_t = OpTypeStruct %float %float
+%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t
+%_ = OpVariable %_ptr_Uniform_U_t Uniform
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+%_ptr_Function_float = OpTypePointer Function %float
+%int_1 = OpConstant %int 1
+%main = OpFunction %void None %3
+%5 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%f2 = OpVariable %_ptr_Function_float Function
+%12 = OpLoad %v4float %BaseColor
+OpStore %v %12
+%15 = OpLoad %float %fi
+%18 = OpFOrdGreaterThan %bool %15 %float_0
+OpSelectionMerge %20 None
+OpBranchConditional %18 %19 %31
+%19 = OpLabel
+%21 = OpLoad %v4float %v
+%28 = OpAccessChain %_ptr_Uniform_float %_ %int_0
+%29 = OpLoad %float %28
+%30 = OpVectorTimesScalar %v4float %21 %29
+OpStore %v %30
+OpBranch %20
+%31 = OpLabel
+%35 = OpAccessChain %_ptr_Uniform_float %_ %int_1
+%36 = OpLoad %float %35
+%37 = OpAccessChain %_ptr_Uniform_float %_ %int_0
+%38 = OpLoad %float %37
+%39 = OpFSub %float %36 %38
+OpStore %f2 %39
+%40 = OpLoad %v4float %v
+%41 = OpLoad %float %f2
+%42 = OpVectorTimesScalar %v4float %40 %41
+OpStore %v %42
+OpBranch %20
+%20 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  opt::Pass::Status res = std::get<1>(SinglePassRunAndDisassemble<opt::CommonUniformElimPass>(text, true));
+  EXPECT_EQ(res, opt::Pass::Status::SuccessWithoutChange);
+}
+
+TEST_F(CommonUniformElimTest, Volatile3) {
+  // Note: This test exemplifies the following:
+  // - Same test as Volatile2 with the exception that
+  //   the nested struct S is volatile
+  // - No optimizations are applied
+  //
+  // #version 430
+  // in vec4 BaseColor;
+  // in float fi;
+  //
+  // struct S {
+  //   volatile float a;
+  // };
+  //
+  // layout(std430) buffer U_t
+  // {
+  //   S g_F;
+  //   S g_F2;
+  // };
+  //
+  //
+  // void main(void)
+  // {
+  //   vec4 v = BaseColor;
+  //   if (fi > 0) {
+  //     v = v * g_F.a;
+  //   } else {
+  //     float f2 = g_F2.a - g_F.a;
+  //     v = v * f2;
+  //   }
+  // }
+
+  const std::string text =
+  R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BaseColor %fi
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 430
+OpName %main "main"
+OpName %v "v"
+OpName %BaseColor "BaseColor"
+OpName %fi "fi"
+OpName %S "S"
+OpMemberName %S 0 "a"
+OpName %U_t "U_t"
+OpMemberName %U_t 0 "g_F"
+OpMemberName %U_t 1 "g_F2"
+OpName %_ ""
+OpName %f2 "f2"
+OpDecorate %BaseColor Location 0
+OpDecorate %fi Location 0
+OpMemberDecorate %S 0 Offset 0
+OpMemberDecorate %S 0 Volatile
+OpMemberDecorate %U_t 0 Offset 0
+OpMemberDecorate %U_t 1 Offset 4
+OpDecorate %U_t BufferBlock
+OpDecorate %_ DescriptorSet 0
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%_ptr_Input_float = OpTypePointer Input %float
+%fi = OpVariable %_ptr_Input_float Input
+%float_0 = OpConstant %float 0
+%bool = OpTypeBool
+%S = OpTypeStruct %float
+%U_t = OpTypeStruct %S %S
+%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t
+%_ = OpVariable %_ptr_Uniform_U_t Uniform
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+%_ptr_Function_float = OpTypePointer Function %float
+%int_1 = OpConstant %int 1
+%main = OpFunction %void None %3
+%5 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%f2 = OpVariable %_ptr_Function_float Function
+%12 = OpLoad %v4float %BaseColor
+OpStore %v %12
+%15 = OpLoad %float %fi
+%18 = OpFOrdGreaterThan %bool %15 %float_0
+OpSelectionMerge %20 None
+OpBranchConditional %18 %19 %32
+%19 = OpLabel
+%21 = OpLoad %v4float %v
+%29 = OpAccessChain %_ptr_Uniform_float %_ %int_0 %int_0
+%30 = OpLoad %float %29
+%31 = OpVectorTimesScalar %v4float %21 %30
+OpStore %v %31
+OpBranch %20
+%32 = OpLabel
+%36 = OpAccessChain %_ptr_Uniform_float %_ %int_1 %int_0
+%37 = OpLoad %float %36
+%38 = OpAccessChain %_ptr_Uniform_float %_ %int_0 %int_0
+%39 = OpLoad %float %38
+%40 = OpFSub %float %37 %39
+OpStore %f2 %40
+%41 = OpLoad %v4float %v
+%42 = OpLoad %float %f2
+%43 = OpVectorTimesScalar %v4float %41 %42
+OpStore %v %43
+OpBranch %20
+%20 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  opt::Pass::Status res = std::get<1>(SinglePassRunAndDisassemble<opt::CommonUniformElimPass>(text, true));
+  EXPECT_EQ(res, opt::Pass::Status::SuccessWithoutChange);
+}
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 //
 //    Disqualifying cases: extensions, decorations, non-logical addressing,
diff --git a/test/opt/compact_ids_test.cpp b/test/opt/compact_ids_test.cpp
index 7094609..ac95d4a 100644
--- a/test/opt/compact_ids_test.cpp
+++ b/test/opt/compact_ids_test.cpp
@@ -14,6 +14,9 @@
 
 #include <gmock/gmock.h>
 
+#include "spirv-tools/libspirv.hpp"
+#include "spirv-tools/optimizer.hpp"
+
 #include "pass_fixture.h"
 #include "pass_utils.h"
 
@@ -87,4 +90,106 @@
   SinglePassRunAndCheck<opt::CompactIdsPass>(before, after, false, false);
 }
 
+TEST(CompactIds, InstructionResultIsUpdated) {
+  // For https://github.com/KhronosGroup/SPIRV-Tools/issues/827
+  // In that bug, the compact Ids pass was directly updating the result Id
+  // word for an OpFunction instruction, but not updating the cached
+  // result_id_ in that Instruction object.
+  //
+  // This test is a bit cheesy.  We don't expose internal interfaces enough
+  // to see the inconsistency.  So reproduce the original scenario, with
+  // compact ids followed by a pass that trips up on the inconsistency.
+
+  const std::string input(R"(OpCapability Shader
+OpMemoryModel Logical Simple
+OpEntryPoint GLCompute %100 "main"
+%200 = OpTypeVoid
+%300 = OpTypeFunction %200
+%100 = OpFunction %300 None %200
+%400 = OpLabel
+OpReturn
+OpFunctionEnd
+)");
+
+  std::vector<uint32_t> binary;
+  const spv_target_env env = SPV_ENV_UNIVERSAL_1_0;
+  spvtools::SpirvTools tools(env);
+  auto assembled = tools.Assemble(
+      input, &binary, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  EXPECT_TRUE(assembled);
+
+  spvtools::Optimizer optimizer(env);
+  optimizer.RegisterPass(CreateCompactIdsPass());
+  // The exhaustive inliner will use the result_id
+  optimizer.RegisterPass(CreateInlineExhaustivePass());
+
+  // This should not crash!
+  optimizer.Run(binary.data(), binary.size(), &binary);
+
+  std::string disassembly;
+  tools.Disassemble(binary, &disassembly, SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+
+  const std::string expected(R"(OpCapability Shader
+OpMemoryModel Logical Simple
+OpEntryPoint GLCompute %1 "main"
+%2 = OpTypeVoid
+%3 = OpTypeFunction %2
+%1 = OpFunction %3 None %2
+%4 = OpLabel
+OpReturn
+OpFunctionEnd
+)");
+
+  EXPECT_THAT(disassembly, ::testing::Eq(expected));
+}
+
+TEST(CompactIds, HeaderIsUpdated) {
+  const std::string input(R"(OpCapability Shader
+OpMemoryModel Logical Simple
+OpEntryPoint GLCompute %100 "main"
+%200 = OpTypeVoid
+%300 = OpTypeFunction %200
+%100 = OpFunction %300 None %200
+%400 = OpLabel
+OpReturn
+OpFunctionEnd
+)");
+
+  std::vector<uint32_t> binary;
+  const spv_target_env env = SPV_ENV_UNIVERSAL_1_0;
+  spvtools::SpirvTools tools(env);
+  auto assembled = tools.Assemble(
+      input, &binary, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  EXPECT_TRUE(assembled);
+
+  spvtools::Optimizer optimizer(env);
+  optimizer.RegisterPass(CreateCompactIdsPass());
+  // The exhaustive inliner will use the result_id
+  optimizer.RegisterPass(CreateInlineExhaustivePass());
+
+  // This should not crash!
+  optimizer.Run(binary.data(), binary.size(), &binary);
+
+  std::string disassembly;
+  tools.Disassemble(binary, &disassembly, SPV_BINARY_TO_TEXT_OPTION_NONE);
+
+  const std::string expected(R"(; SPIR-V
+; Version: 1.0
+; Generator: Khronos SPIR-V Tools Assembler; 0
+; Bound: 5
+; Schema: 0
+OpCapability Shader
+OpMemoryModel Logical Simple
+OpEntryPoint GLCompute %1 "main"
+%2 = OpTypeVoid
+%3 = OpTypeFunction %2
+%1 = OpFunction %3 None %2
+%4 = OpLabel
+OpReturn
+OpFunctionEnd
+)");
+
+  EXPECT_THAT(disassembly, ::testing::Eq(expected));
+}
+
 }  // anonymous namespace
diff --git a/test/opt/dead_branch_elim_test.cpp b/test/opt/dead_branch_elim_test.cpp
index 286232e..661f9c7 100644
--- a/test/opt/dead_branch_elim_test.cpp
+++ b/test/opt/dead_branch_elim_test.cpp
@@ -679,14 +679,9 @@
 %v = OpVariable %_ptr_Function_v4float Function
 %17 = OpLoad %v4float %BaseColor
 OpStore %v %17
-OpSelectionMerge %18 None
-OpBranchConditional %true %19 %18
+OpBranch %19
 %19 = OpLabel
 OpKill
-%18 = OpLabel
-%23 = OpLoad %v4float %v
-OpStore %gl_FragColor %23
-OpReturn
 OpFunctionEnd
 )";
 
@@ -746,13 +741,9 @@
   const std::string after =
       R"(%foo_ = OpFunction %v4float None %9
 %19 = OpLabel
-OpSelectionMerge %20 None
-OpBranchConditional %true %21 %20
+OpBranch %21
 %21 = OpLabel
 OpReturnValue %13
-%20 = OpLabel
-%23 = OpUndef %v4float
-OpReturnValue %23
 OpFunctionEnd
 )";
 
@@ -1068,6 +1059,295 @@
       predefs + before, predefs + after, true, true);
 }
 
+TEST_F(DeadBranchElimTest, SwitchLiveCase) {
+  // #version 450
+  // 
+  // layout (location=0) in vec4 BaseColor;
+  // layout (location=0) out vec4 OutColor;
+  // 
+  // void main()
+  // {
+  //     switch (1) {
+  //       case 0:
+  //         OutColor = vec4(0.0,0.0,0.0,0.0);
+  //         break;
+  //       case 1:
+  //         OutColor = vec4(0.1,0.1,0.1,0.1);
+  //         break;
+  //       case 2:
+  //         OutColor = vec4(0.2,0.2,0.2,0.2);
+  //         break;
+  //       default:
+  //         OutColor = vec4(1.0,1.0,1.0,1.0);
+  //     }
+  // }
+
+  const std::string predefs =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %OutColor %BaseColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+OpName %OutColor "OutColor"
+OpName %BaseColor "BaseColor"
+OpDecorate %OutColor Location 0
+OpDecorate %BaseColor Location 0
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%int_1 = OpConstant %int 1
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+%float_0 = OpConstant %float 0
+%13 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
+%float_0_1 = OpConstant %float 0.1
+%15 = OpConstantComposite %v4float %float_0_1 %float_0_1 %float_0_1 %float_0_1
+%float_0_2 = OpConstant %float 0.2
+%17 = OpConstantComposite %v4float %float_0_2 %float_0_2 %float_0_2 %float_0_2
+%float_1 = OpConstant %float 1
+%19 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+)";
+
+  const std::string before =
+      R"(%main = OpFunction %void None %6
+%21 = OpLabel
+OpSelectionMerge %22 None 
+OpSwitch %int_1 %23 0 %24 1 %25 2 %26
+%23 = OpLabel
+OpStore %OutColor %19
+OpBranch %22 
+%24 = OpLabel
+OpStore %OutColor %13
+OpBranch %22 
+%25 = OpLabel
+OpStore %OutColor %15
+OpBranch %22 
+%26 = OpLabel
+OpStore %OutColor %17
+OpBranch %22 
+%22 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string after =
+      R"(%main = OpFunction %void None %6
+%21 = OpLabel
+OpBranch %25
+%25 = OpLabel
+OpStore %OutColor %15
+OpBranch %22
+%22 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<opt::DeadBranchElimPass>(
+      predefs + before, predefs + after, true, true);
+}
+
+TEST_F(DeadBranchElimTest, SwitchLiveDefault) {
+  // #version 450
+  // 
+  // layout (location=0) in vec4 BaseColor;
+  // layout (location=0) out vec4 OutColor;
+  // 
+  // void main()
+  // {
+  //     switch (7) {
+  //       case 0:
+  //         OutColor = vec4(0.0,0.0,0.0,0.0);
+  //         break;
+  //       case 1:
+  //         OutColor = vec4(0.1,0.1,0.1,0.1);
+  //         break;
+  //       case 2:
+  //         OutColor = vec4(0.2,0.2,0.2,0.2);
+  //         break;
+  //       default:
+  //         OutColor = vec4(1.0,1.0,1.0,1.0);
+  //     }
+  // }
+
+  const std::string predefs =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %OutColor %BaseColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+OpName %OutColor "OutColor"
+OpName %BaseColor "BaseColor"
+OpDecorate %OutColor Location 0
+OpDecorate %BaseColor Location 0
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%int_7 = OpConstant %int 7
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+%float_0 = OpConstant %float 0
+%13 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
+%float_0_1 = OpConstant %float 0.1
+%15 = OpConstantComposite %v4float %float_0_1 %float_0_1 %float_0_1 %float_0_1
+%float_0_2 = OpConstant %float 0.2
+%17 = OpConstantComposite %v4float %float_0_2 %float_0_2 %float_0_2 %float_0_2
+%float_1 = OpConstant %float 1
+%19 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+)";
+
+  const std::string before =
+      R"(%main = OpFunction %void None %6
+%21 = OpLabel
+OpSelectionMerge %22 None 
+OpSwitch %int_7 %23 0 %24 1 %25 2 %26
+%23 = OpLabel
+OpStore %OutColor %19
+OpBranch %22 
+%24 = OpLabel
+OpStore %OutColor %13
+OpBranch %22 
+%25 = OpLabel
+OpStore %OutColor %15
+OpBranch %22 
+%26 = OpLabel
+OpStore %OutColor %17
+OpBranch %22 
+%22 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string after =
+      R"(%main = OpFunction %void None %6
+%21 = OpLabel
+OpBranch %23
+%23 = OpLabel
+OpStore %OutColor %19
+OpBranch %22
+%22 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<opt::DeadBranchElimPass>(
+      predefs + before, predefs + after, true, true);
+}
+
+TEST_F(DeadBranchElimTest, SwitchLiveCaseBreakFromLoop) {
+  // This sample does not directly translate to GLSL/HLSL as
+  // direct breaks from a loop cannot be made from a switch.
+  // This construct is currently formed by inlining a function
+  // containing early returns from the cases of a switch. The
+  // function is wrapped in a one-trip loop and returns are
+  // translated to branches to the loop's merge block. 
+
+  const std::string predefs =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %OutColor %BaseColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+OpName %oc "oc"
+OpName %OutColor "OutColor"
+OpName %BaseColor "BaseColor"
+OpDecorate %OutColor Location 0
+OpDecorate %BaseColor Location 0
+%void = OpTypeVoid
+%7 = OpTypeFunction %void
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%false = OpConstantFalse %bool
+%int = OpTypeInt 32 1
+%int_1 = OpConstant %int 1
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%float_0 = OpConstant %float 0
+%17 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
+%float_0_1 = OpConstant %float 0.1
+%19 = OpConstantComposite %v4float %float_0_1 %float_0_1 %float_0_1 %float_0_1
+%float_0_2 = OpConstant %float 0.2
+%21 = OpConstantComposite %v4float %float_0_2 %float_0_2 %float_0_2 %float_0_2
+%float_1 = OpConstant %float 1
+%23 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+)";
+
+  const std::string before =
+      R"(%main = OpFunction %void None %7
+%26 = OpLabel
+%oc = OpVariable %_ptr_Function_v4float Function
+OpBranch %27
+%27 = OpLabel
+OpLoopMerge %28 %29 None
+OpBranch %30
+%30 = OpLabel
+OpSelectionMerge %31 None
+OpSwitch %int_1 %31 0 %32 1 %33 2 %34
+%32 = OpLabel
+OpStore %oc %17
+OpBranch %28
+%33 = OpLabel
+OpStore %oc %19
+OpBranch %28 
+%34 = OpLabel
+OpStore %oc %21
+OpBranch %28 
+%31 = OpLabel
+OpStore %oc %23
+OpBranch %28 
+%29 = OpLabel
+OpBranchConditional %false %27 %28
+%28 = OpLabel
+%35 = OpLoad %v4float %oc
+OpStore %OutColor %35
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string after =
+      R"(%main = OpFunction %void None %7
+%26 = OpLabel
+%oc = OpVariable %_ptr_Function_v4float Function
+OpBranch %27
+%27 = OpLabel
+OpLoopMerge %28 %29 None
+OpBranch %30
+%30 = OpLabel
+OpBranch %33
+%33 = OpLabel
+OpStore %oc %19
+OpBranch %28
+%29 = OpLabel
+OpBranchConditional %false %27 %28
+%28 = OpLabel
+%35 = OpLoad %v4float %oc
+OpStore %OutColor %35
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<opt::DeadBranchElimPass>(
+      predefs + before, predefs + after, true, true);
+}
+
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 //
 //    More complex control flow
diff --git a/test/opt/dead_variable_elim_test.cpp b/test/opt/dead_variable_elim_test.cpp
new file mode 100644
index 0000000..676719c
--- /dev/null
+++ b/test/opt/dead_variable_elim_test.cpp
@@ -0,0 +1,299 @@
+// Copyright (c) 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "pass_fixture.h"
+#include "pass_utils.h"
+
+namespace {
+
+using namespace spvtools;
+
+using DeadVariableElimTest = PassTest<::testing::Test>;
+
+// %dead is unused.  Make sure we remove it along with its name.
+TEST_F(DeadVariableElimTest, RemoveUnreferenced) {
+  const std::string before =
+      R"(OpCapability Shader
+OpCapability Linkage
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 150
+OpName %main "main"
+OpName %dead "dead"
+%void = OpTypeVoid
+%5 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Private_float = OpTypePointer Private %float
+%dead = OpVariable %_ptr_Private_float Private
+%main = OpFunction %void None %5
+%8 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string after =
+      R"(OpCapability Shader
+OpCapability Linkage
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 150
+OpName %main "main"
+%void = OpTypeVoid
+%5 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Private_float = OpTypePointer Private %float
+%main = OpFunction %void None %5
+%8 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<opt::DeadVariableElimination>(before, after, true,
+                                                      true);
+}
+
+// Since %dead is exported, make sure we keep it.  It could be referenced
+// somewhere else.
+TEST_F(DeadVariableElimTest, KeepExported) {
+  const std::string before =
+      R"(OpCapability Shader
+OpCapability Linkage
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 150
+OpName %main "main"
+OpName %dead "dead"
+OpDecorate %dead LinkageAttributes "dead" Export
+%void = OpTypeVoid
+%5 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Private_float = OpTypePointer Private %float
+%dead = OpVariable %_ptr_Private_float Private
+%main = OpFunction %void None %5
+%8 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<opt::DeadVariableElimination>(before, before, true,
+                                                      true);
+}
+
+// Delete %dead because it is unreferenced.  Then %initializer becomes
+// unreferenced, so remove it as well.
+TEST_F(DeadVariableElimTest, RemoveUnreferencedWithInit1) {
+  const std::string before =
+      R"(OpCapability Shader
+OpCapability Linkage
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 150
+OpName %main "main"
+OpName %dead "dead"
+OpName %initializer "initializer"
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Private_float = OpTypePointer Private %float
+%initializer = OpVariable %_ptr_Private_float Private
+%dead = OpVariable %_ptr_Private_float Private %initializer
+%main = OpFunction %void None %6
+%9 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string after =
+      R"(OpCapability Shader
+OpCapability Linkage
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 150
+OpName %main "main"
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Private_float = OpTypePointer Private %float
+%main = OpFunction %void None %6
+%9 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<opt::DeadVariableElimination>(before, after, true,
+                                                      true);
+}
+
+// Delete %dead because it is unreferenced.  In this case, the initialized has
+// another reference, and should not be removed.
+TEST_F(DeadVariableElimTest, RemoveUnreferencedWithInit2) {
+  const std::string before =
+      R"(OpCapability Shader
+OpCapability Linkage
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 150
+OpName %main "main"
+OpName %dead "dead"
+OpName %initializer "initializer"
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Private_float = OpTypePointer Private %float
+%initializer = OpVariable %_ptr_Private_float Private
+%dead = OpVariable %_ptr_Private_float Private %initializer
+%main = OpFunction %void None %6
+%9 = OpLabel
+%10 = OpLoad %float %initializer
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string after =
+      R"(OpCapability Shader
+OpCapability Linkage
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 150
+OpName %main "main"
+OpName %initializer "initializer"
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Private_float = OpTypePointer Private %float
+%initializer = OpVariable %_ptr_Private_float Private
+%main = OpFunction %void None %6
+%9 = OpLabel
+%10 = OpLoad %float %initializer
+OpReturn
+OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<opt::DeadVariableElimination>(before, after, true,
+                                                      true);
+}
+
+// Keep %live because it is used, and its initializer.
+TEST_F(DeadVariableElimTest, KeepReferenced) {
+  const std::string before =
+      R"(OpCapability Shader
+OpCapability Linkage
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 150
+OpName %main "main"
+OpName %live "live"
+OpName %initializer "initializer"
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Private_float = OpTypePointer Private %float
+%initializer = OpVariable %_ptr_Private_float Private
+%live = OpVariable %_ptr_Private_float Private %initializer
+%main = OpFunction %void None %6
+%9 = OpLabel
+%10 = OpLoad %float %live
+OpReturn
+OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<opt::DeadVariableElimination>(before, before, true,
+                                                      true);
+}
+
+// This test that the decoration associated with a variable are removed when the
+// variable is removed.
+TEST_F(DeadVariableElimTest, RemoveVariableAndDecorations) {
+  const std::string before =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpSource GLSL 450
+OpName %main "main"
+OpName %B "B"
+OpMemberName %B 0 "a"
+OpName %Bdat "Bdat"
+OpMemberDecorate %B 0 Offset 0
+OpDecorate %B BufferBlock
+OpDecorate %Bdat DescriptorSet 0
+OpDecorate %Bdat Binding 0
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%B = OpTypeStruct %uint
+%_ptr_Uniform_B = OpTypePointer Uniform %B
+%Bdat = OpVariable %_ptr_Uniform_B Uniform
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%uint_1 = OpConstant %uint 1
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%main = OpFunction %void None %6
+%13 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string after =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpSource GLSL 450
+OpName %main "main"
+OpName %B "B"
+OpMemberName %B 0 "a"
+OpMemberDecorate %B 0 Offset 0
+OpDecorate %B BufferBlock
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%B = OpTypeStruct %uint
+%_ptr_Uniform_B = OpTypePointer Uniform %B
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%uint_1 = OpConstant %uint 1
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%main = OpFunction %void None %6
+%13 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<opt::DeadVariableElimination>(before, after, true,
+                                                      true);
+}
+}  // namespace
diff --git a/test/opt/eliminate_dead_functions_test.cpp b/test/opt/eliminate_dead_functions_test.cpp
new file mode 100644
index 0000000..6f40a91
--- /dev/null
+++ b/test/opt/eliminate_dead_functions_test.cpp
@@ -0,0 +1,206 @@
+// Copyright (c) 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <vector>
+
+#include <gmock/gmock.h>
+
+#include "assembly_builder.h"
+#include "pass_fixture.h"
+#include "pass_utils.h"
+
+namespace {
+
+using namespace spvtools;
+using ::testing::HasSubstr;
+
+using EliminateDeadFunctionsBasicTest = PassTest<::testing::Test>;
+
+TEST_F(EliminateDeadFunctionsBasicTest, BasicDeleteDeadFunction) {
+  // The function Dead should be removed because it is never called.
+  const std::vector<const char*> common_code = {
+      // clang-format off
+               "OpCapability Shader",
+               "OpMemoryModel Logical GLSL450",
+               "OpEntryPoint Fragment %main \"main\"",
+               "OpName %main \"main\"",
+               "OpName %Live \"Live\"",
+       "%void = OpTypeVoid",
+          "%7 = OpTypeFunction %void",
+       "%main = OpFunction %void None %7",
+         "%15 = OpLabel",
+         "%16 = OpFunctionCall %void %Live",
+         "%17 = OpFunctionCall %void %Live",
+               "OpReturn",
+               "OpFunctionEnd",
+  "%Live = OpFunction %void None %7",
+         "%20 = OpLabel",
+               "OpReturn",
+               "OpFunctionEnd"
+      // clang-format on
+  };
+
+  const std::vector<const char*> dead_function = {
+      // clang-format off
+      "%Dead = OpFunction %void None %7",
+         "%19 = OpLabel",
+               "OpReturn",
+               "OpFunctionEnd",
+      // clang-format on
+  };
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<opt::EliminateDeadFunctionsPass>(
+      JoinAllInsts(Concat(common_code, dead_function)),
+      JoinAllInsts(common_code), /* skip_nop = */ true);
+}
+
+TEST_F(EliminateDeadFunctionsBasicTest, BasicKeepLiveFunction) {
+  // Everything is reachable from an entry point, so no functions should be
+  // deleted.
+  const std::vector<const char*> text = {
+      // clang-format off
+               "OpCapability Shader",
+               "OpMemoryModel Logical GLSL450",
+               "OpEntryPoint Fragment %main \"main\"",
+               "OpName %main \"main\"",
+               "OpName %Live1 \"Live1\"",
+               "OpName %Live2 \"Live2\"",
+       "%void = OpTypeVoid",
+          "%7 = OpTypeFunction %void",
+       "%main = OpFunction %void None %7",
+         "%15 = OpLabel",
+         "%16 = OpFunctionCall %void %Live2",
+         "%17 = OpFunctionCall %void %Live1",
+               "OpReturn",
+               "OpFunctionEnd",
+      "%Live1 = OpFunction %void None %7",
+         "%19 = OpLabel",
+               "OpReturn",
+               "OpFunctionEnd",
+      "%Live2 = OpFunction %void None %7",
+         "%20 = OpLabel",
+               "OpReturn",
+               "OpFunctionEnd"
+      // clang-format on
+  };
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  std::string assembly = JoinAllInsts(text);
+  auto result = SinglePassRunAndDisassemble<opt::EliminateDeadFunctionsPass>(
+      assembly, /* skip_nop = */ true);
+  EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result));
+  EXPECT_EQ(assembly, std::get<0>(result));
+}
+
+TEST_F(EliminateDeadFunctionsBasicTest, BasicKeepExportFunctions) {
+  // All functions are reachable.  In particular, ExportedFunc and Constant are
+  // reachable because ExportedFunc is exported.  Nothing should be removed.
+  const std::vector<const char*> text = {
+      // clang-format off
+               "OpCapability Shader",
+               "OpCapability Linkage",
+               "OpMemoryModel Logical GLSL450",
+               "OpEntryPoint Fragment %main \"main\"",
+               "OpName %main \"main\"",
+               "OpName %ExportedFunc \"ExportedFunc\"",
+               "OpName %Live \"Live\"",
+               "OpDecorate %ExportedFunc LinkageAttributes \"ExportedFunc\" Export",
+       "%void = OpTypeVoid",
+          "%7 = OpTypeFunction %void",
+       "%main = OpFunction %void None %7",
+         "%15 = OpLabel",
+               "OpReturn",
+               "OpFunctionEnd",
+"%ExportedFunc = OpFunction %void None %7",
+         "%19 = OpLabel",
+         "%16 = OpFunctionCall %void %Live",
+               "OpReturn",
+               "OpFunctionEnd",
+  "%Live = OpFunction %void None %7",
+         "%20 = OpLabel",
+               "OpReturn",
+               "OpFunctionEnd"
+      // clang-format on
+  };
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  std::string assembly = JoinAllInsts(text);
+  auto result = SinglePassRunAndDisassemble<opt::EliminateDeadFunctionsPass>(
+      assembly, /* skip_nop = */ true);
+  EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result));
+  EXPECT_EQ(assembly, std::get<0>(result));
+}
+
+TEST_F(EliminateDeadFunctionsBasicTest, BasicRemoveDecorationsAndNames) {
+  // We want to remove the names and decorations associated with results that
+  // are removed.  This test will check for that.
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpName %main "main"
+               OpName %Dead "Dead"
+               OpName %x "x"
+               OpName %y "y"
+               OpName %z "z"
+               OpDecorate %x RelaxedPrecision
+               OpDecorate %y RelaxedPrecision
+               OpDecorate %z RelaxedPrecision
+               OpDecorate %6 RelaxedPrecision
+               OpDecorate %7 RelaxedPrecision
+               OpDecorate %8 RelaxedPrecision
+       %void = OpTypeVoid
+         %10 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+%_ptr_Function_float = OpTypePointer Function %float
+    %float_1 = OpConstant %float 1
+       %main = OpFunction %void None %10
+         %14 = OpLabel
+               OpReturn
+               OpFunctionEnd
+       %Dead = OpFunction %void None %10
+         %15 = OpLabel
+          %x = OpVariable %_ptr_Function_float Function
+          %y = OpVariable %_ptr_Function_float Function
+          %z = OpVariable %_ptr_Function_float Function
+               OpStore %x %float_1
+               OpStore %y %float_1
+          %6 = OpLoad %float %x
+          %7 = OpLoad %float %y
+          %8 = OpFAdd %float %6 %7
+               OpStore %z %8
+               OpReturn
+               OpFunctionEnd)";
+
+  const std::string expected_output = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpName %main "main"
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Function_float = OpTypePointer Function %float
+%float_1 = OpConstant %float 1
+%main = OpFunction %void None %10
+%14 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<opt::EliminateDeadFunctionsPass>(text, expected_output,
+                                                         /* skip_nop = */ true);
+}
+}  // anonymous namespace
diff --git a/test/opt/inline_test.cpp b/test/opt/inline_test.cpp
index d1fb59b..8f35044 100644
--- a/test/opt/inline_test.cpp
+++ b/test/opt/inline_test.cpp
@@ -16,13 +16,6 @@
 #include "pass_fixture.h"
 #include "pass_utils.h"
 
-template <typename T> std::vector<T> concat(const std::vector<T> &a, const std::vector<T> &b) {
-    std::vector<T> ret = std::vector<T>();
-    std::copy(a.begin(), a.end(), back_inserter(ret));
-    std::copy(b.begin(), b.end(), back_inserter(ret));
-    return ret;
-}
-
 namespace {
 
 using namespace spvtools;
@@ -134,8 +127,8 @@
       // clang-format on
   };
   SinglePassRunAndCheck<opt::InlineExhaustivePass>(
-      JoinAllInsts(concat(concat(predefs, before), nonEntryFuncs)),
-      JoinAllInsts(concat(concat(predefs, after), nonEntryFuncs)),
+      JoinAllInsts(Concat(Concat(predefs, before), nonEntryFuncs)),
+      JoinAllInsts(Concat(Concat(predefs, after), nonEntryFuncs)),
       /* skip_nop = */ false, /* do_validate = */ true);
 }
 
@@ -284,8 +277,8 @@
       // clang-format on
   };
   SinglePassRunAndCheck<opt::InlineExhaustivePass>(
-      JoinAllInsts(concat(concat(predefs, before), nonEntryFuncs)),
-      JoinAllInsts(concat(concat(predefs, after), nonEntryFuncs)),
+      JoinAllInsts(Concat(Concat(predefs, before), nonEntryFuncs)),
+      JoinAllInsts(Concat(Concat(predefs, after), nonEntryFuncs)),
       /* skip_nop = */ false, /* do_validate = */ true);
 }
 
@@ -413,8 +406,8 @@
       // clang-format on
   };
   SinglePassRunAndCheck<opt::InlineExhaustivePass>(
-      JoinAllInsts(concat(concat(predefs, before), nonEntryFuncs)),
-      JoinAllInsts(concat(concat(predefs, after), nonEntryFuncs)),
+      JoinAllInsts(Concat(Concat(predefs, before), nonEntryFuncs)),
+      JoinAllInsts(Concat(Concat(predefs, after), nonEntryFuncs)),
       /* skip_nop = */ false, /* do_validate = */ true);
 }
 
@@ -549,8 +542,8 @@
       // clang-format on
   };
   SinglePassRunAndCheck<opt::InlineExhaustivePass>(
-      JoinAllInsts(concat(concat(predefs, before), nonEntryFuncs)),
-      JoinAllInsts(concat(concat(predefs, after), nonEntryFuncs)),
+      JoinAllInsts(Concat(Concat(predefs, before), nonEntryFuncs)),
+      JoinAllInsts(Concat(Concat(predefs, after), nonEntryFuncs)),
       /* skip_nop = */ false, /* do_validate = */ true);
 }
 
@@ -744,8 +737,8 @@
       // clang-format on
   };
   SinglePassRunAndCheck<opt::InlineExhaustivePass>(
-      JoinAllInsts(concat(concat(predefs, before), nonEntryFuncs)),
-      JoinAllInsts(concat(concat(predefs, after), nonEntryFuncs)),
+      JoinAllInsts(Concat(Concat(predefs, before), nonEntryFuncs)),
+      JoinAllInsts(Concat(Concat(predefs, after), nonEntryFuncs)),
       /* skip_nop = */ false, /* do_validate = */ true);
 }
 
@@ -941,8 +934,8 @@
       // clang-format on
   };
   SinglePassRunAndCheck<opt::InlineExhaustivePass>(
-      JoinAllInsts(concat(concat(predefs, before), nonEntryFuncs)),
-      JoinAllInsts(concat(concat(predefs, after), nonEntryFuncs)),
+      JoinAllInsts(Concat(Concat(predefs, before), nonEntryFuncs)),
+      JoinAllInsts(Concat(Concat(predefs, after), nonEntryFuncs)),
       /* skip_nop = */ false, /* do_validate = */ true);
 }
 
@@ -1147,8 +1140,8 @@
       // clang-format on
   };
   SinglePassRunAndCheck<opt::InlineExhaustivePass>(
-      JoinAllInsts(concat(concat(predefs, before), nonEntryFuncs)),
-      JoinAllInsts(concat(concat(predefs, after), nonEntryFuncs)),
+      JoinAllInsts(Concat(Concat(predefs, before), nonEntryFuncs)),
+      JoinAllInsts(Concat(Concat(predefs, after), nonEntryFuncs)),
       /* skip_nop = */ false, /* do_validate = */ true);
 }
 
@@ -1353,8 +1346,8 @@
       // clang-format on
   };
   SinglePassRunAndCheck<opt::InlineExhaustivePass>(
-      JoinAllInsts(concat(concat(predefs, before), nonEntryFuncs)),
-      JoinAllInsts(concat(concat(predefs, after), nonEntryFuncs)),
+      JoinAllInsts(Concat(Concat(predefs, before), nonEntryFuncs)),
+      JoinAllInsts(Concat(Concat(predefs, after), nonEntryFuncs)),
       /* skip_nop = */ false, /* do_validate = */ true);
 }
 
@@ -1838,6 +1831,83 @@
       true);
 }
 
+TEST_F(InlineTest, MultiBlockLoopHeaderCallsMultiBlockCallee) {
+  // Like SingleBlockLoopCallsMultiBlockCallee but the loop has several
+  // blocks, but the function call still occurs in the loop header.
+  // Example from https://github.com/KhronosGroup/SPIRV-Tools/issues/800
+
+  const std::string predefs =
+      R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %1 "main"
+OpSource OpenCL_C 120
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%int = OpTypeInt 32 1
+%int_1 = OpConstant %int 1
+%int_2 = OpConstant %int 2
+%int_3 = OpConstant %int 3
+%int_4 = OpConstant %int 4
+%int_5 = OpConstant %int 5
+%void = OpTypeVoid
+%11 = OpTypeFunction %void
+)";
+
+  const std::string nonEntryFuncs =
+      R"(%12 = OpFunction %void None %11
+%13 = OpLabel
+%14 = OpCopyObject %int %int_1
+OpBranch %15
+%15 = OpLabel
+%16 = OpCopyObject %int %int_2
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string before =
+      R"(%1 = OpFunction %void None %11
+%17 = OpLabel
+OpBranch %18
+%18 = OpLabel
+%19 = OpCopyObject %int %int_3
+%20 = OpFunctionCall %void %12
+%21 = OpCopyObject %int %int_4
+OpLoopMerge %22 %23 None
+OpBranchConditional %true %23 %22
+%23 = OpLabel
+%24 = OpCopyObject %int %int_5
+OpBranchConditional %true %18 %22
+%22 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string after =
+      R"(%1 = OpFunction %void None %11
+%17 = OpLabel
+OpBranch %18
+%18 = OpLabel
+%19 = OpCopyObject %int %int_3
+%25 = OpCopyObject %int %int_1
+OpLoopMerge %22 %23 None
+OpBranch %26
+%26 = OpLabel
+%27 = OpCopyObject %int %int_2
+%21 = OpCopyObject %int %int_4
+OpBranchConditional %true %23 %22
+%23 = OpLabel
+%24 = OpCopyObject %int %int_5
+OpBranchConditional %true %18 %22
+%22 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<opt::InlineExhaustivePass>(
+      predefs + nonEntryFuncs + before, predefs + nonEntryFuncs + after, false,
+      true);
+}
+
 TEST_F(InlineTest, SingleBlockLoopCallsMultiBlockCalleeHavingSelectionMerge) {
   // This is similar to SingleBlockLoopCallsMultiBlockCallee except
   // that calleee block also has a merge instruction in its first block.
@@ -1927,6 +1997,86 @@
       true);
 }
 
+TEST_F(InlineTest, MultiBlockLoopHeaderCallsFromToMultiBlockCalleeHavingSelectionMerge) {
+  // This is similar to SingleBlockLoopCallsMultiBlockCalleeHavingSelectionMerge
+  // but the call is in the header block of a multi block loop.
+
+  const std::string predefs =
+R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %1 "main"
+OpSource OpenCL_C 120
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%int = OpTypeInt 32 1
+%int_1 = OpConstant %int 1
+%int_2 = OpConstant %int 2
+%int_3 = OpConstant %int 3
+%int_4 = OpConstant %int 4
+%int_5 = OpConstant %int 5
+%void = OpTypeVoid
+%11 = OpTypeFunction %void
+)";
+
+  const std::string nonEntryFuncs =
+  R"(%12 = OpFunction %void None %11
+%13 = OpLabel
+%14 = OpCopyObject %int %int_1
+OpSelectionMerge %15 None
+OpBranchConditional %true %15 %15
+%15 = OpLabel
+%16 = OpCopyObject %int %int_2
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string before =
+      R"(%1 = OpFunction %void None %11
+%17 = OpLabel
+OpBranch %18
+%18 = OpLabel
+%19 = OpCopyObject %int %int_3
+%20 = OpFunctionCall %void %12
+%21 = OpCopyObject %int %int_4
+OpLoopMerge %22 %23 None
+OpBranchConditional %true %23 %22
+%23 = OpLabel
+%24 = OpCopyObject %int %int_5
+OpBranchConditional %true %18 %22
+%22 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string after =
+      R"(%1 = OpFunction %void None %11
+%17 = OpLabel
+OpBranch %18
+%18 = OpLabel
+%19 = OpCopyObject %int %int_3
+OpLoopMerge %22 %23 None
+OpBranch %25
+%25 = OpLabel
+%26 = OpCopyObject %int %int_1
+OpSelectionMerge %27 None
+OpBranchConditional %true %27 %27
+%27 = OpLabel
+%28 = OpCopyObject %int %int_2
+%21 = OpCopyObject %int %int_4
+OpBranchConditional %true %23 %22
+%23 = OpLabel
+%24 = OpCopyObject %int %int_5
+OpBranchConditional %true %18 %22
+%22 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<opt::InlineExhaustivePass>(
+      predefs + nonEntryFuncs + before, predefs + nonEntryFuncs + after, false,
+      true);
+}
+
 TEST_F(InlineTest, SingleBlockLoopCallsMultiBlockCalleeHavingSelectionMergeAndMultiReturns) {
   // This is similar to SingleBlockLoopCallsMultiBlockCalleeHavingSelectionMerge
   // except that in addition to starting with a selection header, the
diff --git a/test/opt/instruction_list_test.cpp b/test/opt/instruction_list_test.cpp
new file mode 100644
index 0000000..167348a
--- /dev/null
+++ b/test/opt/instruction_list_test.cpp
@@ -0,0 +1,112 @@
+// Copyright (c) 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <algorithm>
+#include <memory>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "opt/instruction.h"
+#include "opt/instruction_list.h"
+
+namespace {
+
+using Instruction = spvtools::ir::Instruction;
+using InstructionList = spvtools::ir::InstructionList;
+using ::testing::ContainerEq;
+using ::testing::ElementsAre;
+using InstructionListTest = ::testing::Test;
+
+// A class that overrides the destructor, so we can trace it.
+class TestInstruction : public Instruction {
+ public:
+  TestInstruction() : Instruction() { created_instructions_.push_back(this); }
+
+  ~TestInstruction() { deleted_instructions_.push_back(this); }
+
+  static std::vector<TestInstruction*> created_instructions_;
+  static std::vector<TestInstruction*> deleted_instructions_;
+};
+
+std::vector<TestInstruction*> TestInstruction::created_instructions_;
+std::vector<TestInstruction*> TestInstruction::deleted_instructions_;
+
+// Test that the destructor for InstructionList is calling the destructor
+// for every element that is in the list.
+TEST(InstructionListTest, Destructor) {
+  InstructionList* list = new InstructionList();
+  list->push_back(new Instruction());
+  list->push_back(new Instruction());
+  delete list;
+
+  // Sorting because we do not care if the order of create and destruction is
+  // the same.  Using generic sort just incase things are changed above.
+  std::sort(TestInstruction::created_instructions_.begin(),
+            TestInstruction::created_instructions_.end());
+  std::sort(TestInstruction::deleted_instructions_.begin(),
+            TestInstruction::deleted_instructions_.end());
+  EXPECT_THAT(TestInstruction::created_instructions_,
+              ContainerEq(TestInstruction::deleted_instructions_));
+}
+
+// Test the |InsertBefore| with a single instruction in the iterator class.
+// Need to make sure the elements are inserted in the correct order, and the
+// return value points to the correct location.
+//
+// Comparing addresses to make sure they remain stable, so other data structures
+// can have pointers to instructions in InstructionList.
+TEST(InstructionListTest, InsertBefore1) {
+  InstructionList list;
+  std::vector<Instruction*> inserted_instructions;
+  for (int i = 0; i < 4; i++) {
+    std::unique_ptr<Instruction> inst(new Instruction());
+    inserted_instructions.push_back(inst.get());
+    auto new_element = list.end().InsertBefore(std::move(inst));
+    EXPECT_EQ(&*new_element, inserted_instructions.back());
+  }
+
+  std::vector<Instruction*> output;
+  for (auto& i : list) {
+    output.push_back(&i);
+  }
+  EXPECT_THAT(output, ContainerEq(inserted_instructions));
+}
+
+// Test inserting an entire vector of instructions using InsertBefore.  Checking
+// the order of insertion and the return value.
+//
+// Comparing addresses to make sure they remain stable, so other data structures
+// can have pointers to instructions in InstructionList.
+TEST(InstructionListTest, InsertBefore2) {
+  InstructionList list;
+  std::vector<std::unique_ptr<Instruction>> new_instructions;
+  std::vector<Instruction*> created_instructions;
+  for (int i = 0; i < 4; i++) {
+    std::unique_ptr<Instruction> inst(new Instruction());
+    created_instructions.push_back(inst.get());
+    new_instructions.push_back(std::move(inst));
+  }
+  auto new_element = list.begin().InsertBefore(std::move(new_instructions));
+  EXPECT_TRUE(new_instructions.empty());
+  EXPECT_EQ(&*new_element, created_instructions.front());
+
+  std::vector<Instruction*> output;
+  for (auto& i : list) {
+    output.push_back(&i);
+  }
+  EXPECT_THAT(output, ContainerEq(created_instructions));
+}
+}  // namespace
diff --git a/test/opt/ir_loader_test.cpp b/test/opt/ir_loader_test.cpp
index 4e40e70..b61f7cb 100644
--- a/test/opt/ir_loader_test.cpp
+++ b/test/opt/ir_loader_test.cpp
@@ -298,6 +298,19 @@
   // clang-format on
 }
 
+TEST(IrBuilder, KeepModuleProcessedInRightPlace) {
+  DoRoundTripCheck(
+   // clang-format off
+               "OpCapability Shader\n"
+               "OpMemoryModel Logical GLSL450\n"
+          "%1 = OpString \"minimal.vert\"\n"
+               "OpName %void \"void\"\n"
+               "OpModuleProcessed \"Made it faster\"\n"
+               "OpModuleProcessed \".. and smaller\"\n"
+       "%void = OpTypeVoid\n");
+  // clang-format on
+}
+
 // Checks the given |error_message| is reported when trying to build a module
 // from the given |assembly|.
 void DoErrorMessageCheck(const std::string& assembly,
diff --git a/test/opt/local_single_block_elim.cpp b/test/opt/local_single_block_elim.cpp
index cc55d5d..7d7559a 100644
--- a/test/opt/local_single_block_elim.cpp
+++ b/test/opt/local_single_block_elim.cpp
@@ -16,14 +16,6 @@
 #include "pass_fixture.h"
 #include "pass_utils.h"
 
-template <typename T>
-std::vector<T> concat(const std::vector<T>& a, const std::vector<T>& b) {
-  std::vector<T> ret;
-  std::copy(a.begin(), a.end(), back_inserter(ret));
-  std::copy(b.begin(), b.end(), back_inserter(ret));
-  return ret;
-}
-
 namespace {
 
 using namespace spvtools;
diff --git a/test/opt/local_ssa_elim_test.cpp b/test/opt/local_ssa_elim_test.cpp
index bcee7ca..4e49b14 100644
--- a/test/opt/local_ssa_elim_test.cpp
+++ b/test/opt/local_ssa_elim_test.cpp
@@ -1227,6 +1227,37 @@
       true, true);
 }
 
+TEST_F(LocalSSAElimTest, DontPatchPhiInLoopHeaderThatIsNotAVar) {
+  // From https://github.com/KhronosGroup/SPIRV-Tools/issues/826
+  // Don't try patching the (%16 %7) value/predecessor pair in the OpPhi.
+  // That OpPhi is unrelated to this optimization: we did not set that up
+  // in the SSA initialization for the loop header block.
+  // The pass should be a no-op on this module.
+
+  const std::string before = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %1 "main"
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%float_1 = OpConstant %float 1
+%1 = OpFunction %void None %3
+%6 = OpLabel
+OpBranch %7
+%7 = OpLabel
+%8 = OpPhi %float %float_1 %6 %9 %7
+%9 = OpFAdd %float %8 %float_1
+OpLoopMerge %10 %7 None
+OpBranch %7
+%10 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<opt::LocalMultiStoreElimPass>(before, before, true,
+                                                      true);
+}
+
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 //
 //    No optimization in the presence of
diff --git a/test/opt/pass_manager_test.cpp b/test/opt/pass_manager_test.cpp
index 704aa53..d06ad3d 100644
--- a/test/opt/pass_manager_test.cpp
+++ b/test/opt/pass_manager_test.cpp
@@ -70,17 +70,17 @@
   EXPECT_STREQ("null-with-args", manager.GetPass(6)->name());
 }
 
-// A pass that appends an OpNop instruction to the debug section.
+// A pass that appends an OpNop instruction to the debug1 section.
 class AppendOpNopPass : public opt::Pass {
  public:
   const char* name() const override { return "AppendOpNop"; }
   Status Process(ir::Module* module) override {
-    module->AddDebugInst(MakeUnique<ir::Instruction>());
+    module->AddDebug1Inst(MakeUnique<ir::Instruction>());
     return Status::SuccessWithChange;
   }
 };
 
-// A pass that appends specified number of OpNop instructions to the debug
+// A pass that appends specified number of OpNop instructions to the debug1
 // section.
 class AppendMultipleOpNopPass : public opt::Pass {
  public:
@@ -89,7 +89,7 @@
   const char* name() const override { return "AppendOpNop"; }
   Status Process(ir::Module* module) override {
     for (uint32_t i = 0; i < num_nop_; i++) {
-      module->AddDebugInst(MakeUnique<ir::Instruction>());
+      module->AddDebug1Inst(MakeUnique<ir::Instruction>());
     }
     return Status::SuccessWithChange;
   }
@@ -98,13 +98,13 @@
   uint32_t num_nop_;
 };
 
-// A pass that duplicates the last instruction in the debug section.
+// A pass that duplicates the last instruction in the debug1 section.
 class DuplicateInstPass : public opt::Pass {
  public:
   const char* name() const override { return "DuplicateInst"; }
   Status Process(ir::Module* module) override {
-    auto inst = MakeUnique<ir::Instruction>(*(--module->debug_end()));
-    module->AddDebugInst(std::move(inst));
+    auto inst = MakeUnique<ir::Instruction>(*(--module->debug1_end()));
+    module->AddDebug1Inst(std::move(inst));
     return Status::SuccessWithChange;
   }
 };
diff --git a/test/opt/pass_test.cpp b/test/opt/pass_test.cpp
new file mode 100644
index 0000000..a6298ca
--- /dev/null
+++ b/test/opt/pass_test.cpp
@@ -0,0 +1,241 @@
+// Copyright (c) 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <string>
+#include <vector>
+
+#include <gmock/gmock.h>
+
+#include "assembly_builder.h"
+#include "opt/pass.h"
+#include "pass_fixture.h"
+#include "pass_utils.h"
+
+namespace {
+using namespace spvtools;
+class DummyPass : public opt::Pass {
+ public:
+  const char* name() const override { return "dummy-pass"; }
+  Status Process(ir::Module* module) override {
+    return module ? Status::SuccessWithoutChange : Status::Failure;
+  }
+};
+}  // namespace
+
+namespace {
+
+using namespace spvtools;
+using ::testing::UnorderedElementsAre;
+
+using PassClassTest = PassTest<::testing::Test>;
+
+TEST_F(PassClassTest, BasicVisitFromEntryPoint) {
+  // Make sure we visit the entry point, and the function it calls.
+  // Do not visit Dead or Exported.
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %10 "main"
+               OpName %10 "main"
+               OpName %Dead "Dead"
+               OpName %11 "Constant"
+               OpName %ExportedFunc "ExportedFunc"
+               OpDecorate %ExportedFunc LinkageAttributes "ExportedFunc" Export
+       %void = OpTypeVoid
+          %6 = OpTypeFunction %void
+         %10 = OpFunction %void None %6
+         %14 = OpLabel
+         %15 = OpFunctionCall %void %11
+         %16 = OpFunctionCall %void %11
+               OpReturn
+               OpFunctionEnd
+         %11 = OpFunction %void None %6
+         %18 = OpLabel
+               OpReturn
+               OpFunctionEnd
+       %Dead = OpFunction %void None %6
+         %19 = OpLabel
+               OpReturn
+               OpFunctionEnd
+%ExportedFunc = OpFunction %void None %7
+         %20 = OpLabel
+         %21 = OpFunctionCall %void %11
+               OpReturn
+               OpFunctionEnd
+)";
+  // clang-format on
+
+  std::unique_ptr<ir::Module> module =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+                             << text << std::endl;
+  DummyPass testPass;
+  std::vector<uint32_t> processed;
+  opt::Pass::ProcessFunction mark_visited = [&processed](ir::Function* fp) {
+    processed.push_back(fp->result_id());
+    return false;
+  };
+  testPass.ProcessEntryPointCallTree(mark_visited, module.get());
+  EXPECT_THAT(processed, UnorderedElementsAre(10, 11));
+}
+
+TEST_F(PassClassTest, BasicVisitReachable) {
+  // Make sure we visit the entry point, exported function, and the function
+  // they call. Do not visit Dead.
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %10 "main"
+               OpName %10 "main"
+               OpName %Dead "Dead"
+               OpName %11 "Constant"
+               OpName %12 "ExportedFunc"
+               OpName %13 "Constant2"
+               OpDecorate %12 LinkageAttributes "ExportedFunc" Export
+       %void = OpTypeVoid
+          %6 = OpTypeFunction %void
+         %10 = OpFunction %void None %6
+         %14 = OpLabel
+         %15 = OpFunctionCall %void %11
+         %16 = OpFunctionCall %void %11
+               OpReturn
+               OpFunctionEnd
+         %11 = OpFunction %void None %6
+         %18 = OpLabel
+               OpReturn
+               OpFunctionEnd
+       %Dead = OpFunction %void None %6
+         %19 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %void None %9
+         %20 = OpLabel
+         %21 = OpFunctionCall %void %13
+               OpReturn
+               OpFunctionEnd
+         %13 = OpFunction %void None %6
+         %22 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+  // clang-format on
+
+  std::unique_ptr<ir::Module> module =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+                             << text << std::endl;
+  DummyPass testPass;
+  std::vector<uint32_t> processed;
+  opt::Pass::ProcessFunction mark_visited = [&processed](ir::Function* fp) {
+    processed.push_back(fp->result_id());
+    return false;
+  };
+  testPass.ProcessReachableCallTree(mark_visited, module.get());
+  EXPECT_THAT(processed, UnorderedElementsAre(10, 11, 12, 13));
+}
+
+TEST_F(PassClassTest, BasicVisitOnlyOnce) {
+  // Make sure we visit %11 only once, even if it is called from two different
+  // functions.
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %10 "main" %gl_FragColor
+               OpName %10 "main"
+               OpName %Dead "Dead"
+               OpName %11 "Constant"
+               OpName %12 "ExportedFunc"
+               OpDecorate %12 LinkageAttributes "ExportedFunc" Export
+       %void = OpTypeVoid
+          %6 = OpTypeFunction %void
+         %10 = OpFunction %void None %6
+         %14 = OpLabel
+         %15 = OpFunctionCall %void %11
+         %16 = OpFunctionCall %void %12
+               OpReturn
+               OpFunctionEnd
+         %11 = OpFunction %void None %6
+         %18 = OpLabel
+         %19 = OpFunctionCall %void %12
+               OpReturn
+               OpFunctionEnd
+       %Dead = OpFunction %void None %6
+         %20 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %void None %9
+         %21 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+  // clang-format on
+
+  std::unique_ptr<ir::Module> module =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+                             << text << std::endl;
+  DummyPass testPass;
+  std::vector<uint32_t> processed;
+  opt::Pass::ProcessFunction mark_visited = [&processed](ir::Function* fp) {
+    processed.push_back(fp->result_id());
+    return false;
+  };
+  testPass.ProcessReachableCallTree(mark_visited, module.get());
+  EXPECT_THAT(processed, UnorderedElementsAre(10, 11, 12));
+}
+
+TEST_F(PassClassTest, BasicDontVisitExportedVariable) {
+  // Make sure we only visit functions and not exported variables.
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %10 "main" %gl_FragColor
+               OpExecutionMode %10 OriginUpperLeft
+               OpSource GLSL 150
+               OpName %10 "main"
+               OpName %Dead "Dead"
+               OpName %11 "Constant"
+               OpName %12 "export_var"
+               OpDecorate %12 LinkageAttributes "export_var" Export
+       %void = OpTypeVoid
+          %6 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+  %float_1 = OpConstant %float 1
+         %12 = OpVariable %float Output
+         %10 = OpFunction %void None %6
+         %14 = OpLabel
+               OpStore %12 %float_1
+               OpReturn
+               OpFunctionEnd
+)";
+  // clang-format on
+
+  std::unique_ptr<ir::Module> module =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+                             << text << std::endl;
+  DummyPass testPass;
+  std::vector<uint32_t> processed;
+  opt::Pass::ProcessFunction mark_visited = [&processed](ir::Function* fp) {
+    processed.push_back(fp->result_id());
+    return false;
+  };
+  testPass.ProcessReachableCallTree(mark_visited, module.get());
+  EXPECT_THAT(processed, UnorderedElementsAre(10));
+}
+}  // namespace
diff --git a/test/opt/pass_utils.h b/test/opt/pass_utils.h
index a2a3b3c..dbf87c9 100644
--- a/test/opt/pass_utils.h
+++ b/test/opt/pass_utils.h
@@ -49,6 +49,16 @@
 // will be ignored.
 std::string JoinNonDebugInsts(const std::vector<const char*>& insts);
 
+// Returns a vector that contains the contents of |a| followed by the contents
+// of |b|.
+template <typename T>
+std::vector<T> Concat(const std::vector<T>& a, const std::vector<T>& b) {
+  std::vector<T> ret;
+  std::copy(a.begin(), a.end(), back_inserter(ret));
+  std::copy(b.begin(), b.end(), back_inserter(ret));
+  return ret;
+}
+
 }  // namespace spvtools
 
 #endif  // LIBSPIRV_TEST_OPT_PASS_UTILS_H_
diff --git a/test/opt/strength_reduction_test.cpp b/test/opt/strength_reduction_test.cpp
new file mode 100644
index 0000000..541be60
--- /dev/null
+++ b/test/opt/strength_reduction_test.cpp
@@ -0,0 +1,427 @@
+// Copyright (c) 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "assembly_builder.h"
+#include "gmock/gmock.h"
+#include "pass_fixture.h"
+#include "pass_utils.h"
+
+#include <algorithm>
+#include <cstdarg>
+#include <iostream>
+#include <sstream>
+#include <unordered_set>
+
+namespace {
+
+using namespace spvtools;
+
+using ::testing::HasSubstr;
+using ::testing::MatchesRegex;
+
+using StrengthReductionBasicTest = PassTest<::testing::Test>;
+
+// Test to make sure we replace 5*8.
+TEST_F(StrengthReductionBasicTest, BasicReplaceMulBy8) {
+  const std::vector<const char*> text = {
+      // clang-format off
+               "OpCapability Shader",
+          "%1 = OpExtInstImport \"GLSL.std.450\"",
+               "OpMemoryModel Logical GLSL450",
+               "OpEntryPoint Vertex %main \"main\"",
+               "OpName %main \"main\"",
+       "%void = OpTypeVoid",
+          "%4 = OpTypeFunction %void",
+       "%uint = OpTypeInt 32 0",
+     "%uint_5 = OpConstant %uint 5",
+     "%uint_8 = OpConstant %uint 8",
+       "%main = OpFunction %void None %4",
+          "%8 = OpLabel",
+          "%9 = OpIMul %uint %uint_5 %uint_8",
+               "OpReturn",
+               "OpFunctionEnd"
+      // clang-format on
+  };
+
+  auto result = SinglePassRunAndDisassemble<opt::StrengthReductionPass>(
+      JoinAllInsts(text), /* skip_nop = */ true);
+
+  EXPECT_EQ(opt::Pass::Status::SuccessWithChange, std::get<1>(result));
+  const std::string& output = std::get<0>(result);
+  EXPECT_THAT(output, Not(HasSubstr("OpIMul")));
+  EXPECT_THAT(output, HasSubstr("OpShiftLeftLogical %uint %uint_5 %uint_3"));
+}
+
+// Test to make sure we replace 16*5.
+TEST_F(StrengthReductionBasicTest, BasicReplaceMulBy16) {
+  const std::vector<const char*> text = {
+      // clang-format off
+               "OpCapability Shader",
+          "%1 = OpExtInstImport \"GLSL.std.450\"",
+               "OpMemoryModel Logical GLSL450",
+               "OpEntryPoint Vertex %main \"main\"",
+               "OpName %main \"main\"",
+       "%void = OpTypeVoid",
+          "%4 = OpTypeFunction %void",
+        "%int = OpTypeInt 32 1",
+      "%int_16 = OpConstant %int 16",
+      "%int_5 = OpConstant %int 5",
+       "%main = OpFunction %void None %4",
+          "%8 = OpLabel",
+          "%9 = OpIMul %int %int_16 %int_5",
+               "OpReturn",
+               "OpFunctionEnd"
+      // clang-format on
+  };
+
+  auto result = SinglePassRunAndDisassemble<opt::StrengthReductionPass>(
+      JoinAllInsts(text), /* skip_nop = */ true);
+
+  EXPECT_EQ(opt::Pass::Status::SuccessWithChange, std::get<1>(result));
+  const std::string& output = std::get<0>(result);
+  EXPECT_THAT(output, Not(HasSubstr("OpIMul")));
+  EXPECT_THAT(output, HasSubstr("OpShiftLeftLogical %int %int_5 %uint_4"));
+}
+
+// Test to make sure we replace a multiple of 32 and 4.
+TEST_F(StrengthReductionBasicTest, BasicTwoPowersOf2) {
+  // In this case, we have two powers of 2.  Need to make sure we replace only
+  // one of them for the bit shift.
+  // clang-format off
+  const std::string text = R"(
+          OpCapability Shader
+     %1 = OpExtInstImport "GLSL.std.450"
+          OpMemoryModel Logical GLSL450
+          OpEntryPoint Vertex %main "main"
+          OpName %main "main"
+  %void = OpTypeVoid
+     %4 = OpTypeFunction %void
+   %int = OpTypeInt 32 1
+%int_32 = OpConstant %int 32
+ %int_4 = OpConstant %int 4
+  %main = OpFunction %void None %4
+     %8 = OpLabel
+     %9 = OpIMul %int %int_32 %int_4
+          OpReturn
+          OpFunctionEnd
+)";
+  // clang-format on
+  auto result = SinglePassRunAndDisassemble<opt::StrengthReductionPass>(
+      text, /* skip_nop = */ true);
+
+  EXPECT_EQ(opt::Pass::Status::SuccessWithChange, std::get<1>(result));
+  const std::string& output = std::get<0>(result);
+  EXPECT_THAT(output, Not(HasSubstr("OpIMul")));
+  EXPECT_THAT(output, HasSubstr("OpShiftLeftLogical %int %int_4 %uint_5"));
+}
+
+// Test to make sure we don't replace 0*5.
+TEST_F(StrengthReductionBasicTest, BasicDontReplace0) {
+  const std::vector<const char*> text = {
+      // clang-format off
+               "OpCapability Shader",
+          "%1 = OpExtInstImport \"GLSL.std.450\"",
+               "OpMemoryModel Logical GLSL450",
+               "OpEntryPoint Vertex %main \"main\"",
+               "OpName %main \"main\"",
+       "%void = OpTypeVoid",
+          "%4 = OpTypeFunction %void",
+        "%int = OpTypeInt 32 1",
+      "%int_0 = OpConstant %int 0",
+      "%int_5 = OpConstant %int 5",
+       "%main = OpFunction %void None %4",
+          "%8 = OpLabel",
+          "%9 = OpIMul %int %int_0 %int_5",
+               "OpReturn",
+               "OpFunctionEnd"
+      // clang-format on
+  };
+
+  auto result = SinglePassRunAndDisassemble<opt::StrengthReductionPass>(
+      JoinAllInsts(text), /* skip_nop = */ true);
+
+  EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+// Test to make sure we do not replace a multiple of 5 and 7.
+TEST_F(StrengthReductionBasicTest, BasicNoChange) {
+  const std::vector<const char*> text = {
+      // clang-format off
+             "OpCapability Shader",
+        "%1 = OpExtInstImport \"GLSL.std.450\"",
+             "OpMemoryModel Logical GLSL450",
+             "OpEntryPoint Vertex %2 \"main\"",
+             "OpName %2 \"main\"",
+        "%3 = OpTypeVoid",
+        "%4 = OpTypeFunction %3",
+        "%5 = OpTypeInt 32 1",
+        "%6 = OpTypeInt 32 0",
+        "%7 = OpConstant %5 5",
+        "%8 = OpConstant %5 7",
+        "%2 = OpFunction %3 None %4",
+        "%9 = OpLabel",
+        "%10 = OpIMul %5 %7 %8",
+             "OpReturn",
+             "OpFunctionEnd",
+      // clang-format on
+  };
+
+  auto result = SinglePassRunAndDisassemble<opt::StrengthReductionPass>(
+      JoinAllInsts(text), /* skip_nop = */ true);
+
+  EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+// Test to make sure constants and types are reused and not duplicated.
+TEST_F(StrengthReductionBasicTest, NoDuplicateConstantsAndTypes) {
+  const std::vector<const char*> text = {
+      // clang-format off
+               "OpCapability Shader",
+          "%1 = OpExtInstImport \"GLSL.std.450\"",
+               "OpMemoryModel Logical GLSL450",
+               "OpEntryPoint Vertex %main \"main\"",
+               "OpName %main \"main\"",
+       "%void = OpTypeVoid",
+          "%4 = OpTypeFunction %void",
+       "%uint = OpTypeInt 32 0",
+     "%uint_8 = OpConstant %uint 8",
+     "%uint_3 = OpConstant %uint 3",
+       "%main = OpFunction %void None %4",
+          "%8 = OpLabel",
+          "%9 = OpIMul %uint %uint_8 %uint_3",
+               "OpReturn",
+               "OpFunctionEnd",
+      // clang-format on
+  };
+
+  auto result = SinglePassRunAndDisassemble<opt::StrengthReductionPass>(
+      JoinAllInsts(text), /* skip_nop = */ true);
+
+  EXPECT_EQ(opt::Pass::Status::SuccessWithChange, std::get<1>(result));
+  const std::string& output = std::get<0>(result);
+  EXPECT_THAT(output,
+              Not(MatchesRegex(".*OpConstant %uint 3.*OpConstant %uint 3.*")));
+  EXPECT_THAT(output, Not(MatchesRegex(".*OpTypeInt 32 0.*OpTypeInt 32 0.*")));
+}
+
+// Test to make sure we generate the constants only once
+TEST_F(StrengthReductionBasicTest, BasicCreateOneConst) {
+  const std::vector<const char*> text = {
+      // clang-format off
+               "OpCapability Shader",
+          "%1 = OpExtInstImport \"GLSL.std.450\"",
+               "OpMemoryModel Logical GLSL450",
+               "OpEntryPoint Vertex %main \"main\"",
+               "OpName %main \"main\"",
+       "%void = OpTypeVoid",
+          "%4 = OpTypeFunction %void",
+       "%uint = OpTypeInt 32 0",
+     "%uint_5 = OpConstant %uint 5",
+     "%uint_9 = OpConstant %uint 9",
+   "%uint_128 = OpConstant %uint 128",
+       "%main = OpFunction %void None %4",
+          "%8 = OpLabel",
+          "%9 = OpIMul %uint %uint_5 %uint_128",
+         "%10 = OpIMul %uint %uint_9 %uint_128",
+               "OpReturn",
+               "OpFunctionEnd"
+      // clang-format on
+  };
+
+  auto result = SinglePassRunAndDisassemble<opt::StrengthReductionPass>(
+      JoinAllInsts(text), /* skip_nop = */ true);
+
+  EXPECT_EQ(opt::Pass::Status::SuccessWithChange, std::get<1>(result));
+  const std::string& output = std::get<0>(result);
+  EXPECT_THAT(output, Not(HasSubstr("OpIMul")));
+  EXPECT_THAT(output, HasSubstr("OpShiftLeftLogical %uint %uint_5 %uint_7"));
+  EXPECT_THAT(output, HasSubstr("OpShiftLeftLogical %uint %uint_9 %uint_7"));
+}
+
+// Test to make sure we generate the instructions in the correct position and
+// that the uses get replaced as well.  Here we check that the use in the return
+// is replaced, we also check that we can replace two OpIMuls when one feeds the
+// other.
+TEST_F(StrengthReductionBasicTest, BasicCheckPositionAndReplacement) {
+  // This is just the preamble to set up the test.
+  const std::vector<const char*> common_text = {
+      // clang-format off
+               "OpCapability Shader",
+          "%1 = OpExtInstImport \"GLSL.std.450\"",
+               "OpMemoryModel Logical GLSL450",
+               "OpEntryPoint Fragment %main \"main\" %gl_FragColor",
+               "OpExecutionMode %main OriginUpperLeft",
+               "OpName %main \"main\"",
+               "OpName %foo_i1_ \"foo(i1;\"",
+               "OpName %n \"n\"",
+               "OpName %gl_FragColor \"gl_FragColor\"",
+               "OpName %param \"param\"",
+               "OpDecorate %gl_FragColor Location 0",
+       "%void = OpTypeVoid",
+          "%3 = OpTypeFunction %void",
+        "%int = OpTypeInt 32 1",
+"%_ptr_Function_int = OpTypePointer Function %int",
+          "%8 = OpTypeFunction %int %_ptr_Function_int",
+    "%int_256 = OpConstant %int 256",
+      "%int_2 = OpConstant %int 2",
+      "%float = OpTypeFloat 32",
+    "%v4float = OpTypeVector %float 4",
+"%_ptr_Output_v4float = OpTypePointer Output %v4float",
+"%gl_FragColor = OpVariable %_ptr_Output_v4float Output",
+    "%float_1 = OpConstant %float 1",
+     "%int_10 = OpConstant %int 10",
+  "%float_0_4 = OpConstant %float 0.4",
+  "%float_0_8 = OpConstant %float 0.8",
+       "%uint = OpTypeInt 32 0",
+     "%uint_8 = OpConstant %uint 8",
+     "%uint_1 = OpConstant %uint 1",
+       "%main = OpFunction %void None %3",
+          "%5 = OpLabel",
+      "%param = OpVariable %_ptr_Function_int Function",
+               "OpStore %param %int_10",
+         "%26 = OpFunctionCall %int %foo_i1_ %param",
+         "%27 = OpConvertSToF %float %26",
+         "%28 = OpFDiv %float %float_1 %27",
+         "%31 = OpCompositeConstruct %v4float %28 %float_0_4 %float_0_8 %float_1",
+               "OpStore %gl_FragColor %31",
+               "OpReturn",
+               "OpFunctionEnd"
+      // clang-format on
+  };
+
+  // This is the real test.  The two OpIMul should be replaced.  The expected
+  // output is in |foo_after|.
+  const std::vector<const char*> foo_before = {
+      // clang-format off
+    "%foo_i1_ = OpFunction %int None %8",
+          "%n = OpFunctionParameter %_ptr_Function_int",
+         "%11 = OpLabel",
+         "%12 = OpLoad %int %n",
+         "%14 = OpIMul %int %12 %int_256",
+         "%16 = OpIMul %int %14 %int_2",
+               "OpReturnValue %16",
+               "OpFunctionEnd",
+
+      // clang-format on
+  };
+
+  const std::vector<const char*> foo_after = {
+      // clang-format off
+    "%foo_i1_ = OpFunction %int None %8",
+          "%n = OpFunctionParameter %_ptr_Function_int",
+         "%11 = OpLabel",
+         "%12 = OpLoad %int %n",
+         "%33 = OpShiftLeftLogical %int %12 %uint_8",
+         "%34 = OpShiftLeftLogical %int %33 %uint_1",
+               "OpReturnValue %34",
+               "OpFunctionEnd",
+      // clang-format on
+  };
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<opt::StrengthReductionPass>(
+      JoinAllInsts(Concat(common_text, foo_before)),
+      JoinAllInsts(Concat(common_text, foo_after)),
+      /* skip_nop = */ true, /* do_validate = */ true);
+}
+
+// Test that, when the result of an OpIMul instruction has more than 1 use, and
+// the instruction is replaced, all of the uses of the results are replace with
+// the new result.
+TEST_F(StrengthReductionBasicTest, BasicTestMultipleReplacements) {
+  // This is just the preamble to set up the test.
+  const std::vector<const char*> common_text = {
+      // clang-format off
+               "OpCapability Shader",
+          "%1 = OpExtInstImport \"GLSL.std.450\"",
+               "OpMemoryModel Logical GLSL450",
+               "OpEntryPoint Fragment %main \"main\" %gl_FragColor",
+               "OpExecutionMode %main OriginUpperLeft",
+               "OpName %main \"main\"",
+               "OpName %foo_i1_ \"foo(i1;\"",
+               "OpName %n \"n\"",
+               "OpName %gl_FragColor \"gl_FragColor\"",
+               "OpName %param \"param\"",
+               "OpDecorate %gl_FragColor Location 0",
+       "%void = OpTypeVoid",
+          "%3 = OpTypeFunction %void",
+        "%int = OpTypeInt 32 1",
+"%_ptr_Function_int = OpTypePointer Function %int",
+          "%8 = OpTypeFunction %int %_ptr_Function_int",
+    "%int_256 = OpConstant %int 256",
+      "%int_2 = OpConstant %int 2",
+      "%float = OpTypeFloat 32",
+    "%v4float = OpTypeVector %float 4",
+"%_ptr_Output_v4float = OpTypePointer Output %v4float",
+"%gl_FragColor = OpVariable %_ptr_Output_v4float Output",
+    "%float_1 = OpConstant %float 1",
+     "%int_10 = OpConstant %int 10",
+  "%float_0_4 = OpConstant %float 0.4",
+  "%float_0_8 = OpConstant %float 0.8",
+       "%uint = OpTypeInt 32 0",
+     "%uint_8 = OpConstant %uint 8",
+     "%uint_1 = OpConstant %uint 1",
+       "%main = OpFunction %void None %3",
+          "%5 = OpLabel",
+      "%param = OpVariable %_ptr_Function_int Function",
+               "OpStore %param %int_10",
+         "%26 = OpFunctionCall %int %foo_i1_ %param",
+         "%27 = OpConvertSToF %float %26",
+         "%28 = OpFDiv %float %float_1 %27",
+         "%31 = OpCompositeConstruct %v4float %28 %float_0_4 %float_0_8 %float_1",
+               "OpStore %gl_FragColor %31",
+               "OpReturn",
+               "OpFunctionEnd"
+      // clang-format on
+  };
+
+  // This is the real test.  The two OpIMul instructions should be replaced.  In
+  // particular, we want to be sure that both uses of %16 are changed to use the
+  // new result.
+  const std::vector<const char*> foo_before = {
+      // clang-format off
+    "%foo_i1_ = OpFunction %int None %8",
+          "%n = OpFunctionParameter %_ptr_Function_int",
+         "%11 = OpLabel",
+         "%12 = OpLoad %int %n",
+         "%14 = OpIMul %int %12 %int_256",
+         "%16 = OpIMul %int %14 %int_2",
+         "%17 = OpIAdd %int %14 %16",
+               "OpReturnValue %17",
+               "OpFunctionEnd",
+
+      // clang-format on
+  };
+
+  const std::vector<const char*> foo_after = {
+      // clang-format off
+    "%foo_i1_ = OpFunction %int None %8",
+          "%n = OpFunctionParameter %_ptr_Function_int",
+         "%11 = OpLabel",
+         "%12 = OpLoad %int %n",
+         "%34 = OpShiftLeftLogical %int %12 %uint_8",
+         "%35 = OpShiftLeftLogical %int %34 %uint_1",
+         "%17 = OpIAdd %int %34 %35",
+               "OpReturnValue %17",
+               "OpFunctionEnd",
+      // clang-format on
+  };
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<opt::StrengthReductionPass>(
+      JoinAllInsts(Concat(common_text, foo_before)),
+      JoinAllInsts(Concat(common_text, foo_after)),
+      /* skip_nop = */ true, /* do_validate = */ true);
+}
+}  // anonymous namespace
diff --git a/test/opt/strip_debug_info_test.cpp b/test/opt/strip_debug_info_test.cpp
index 9a56d86..8e6ee01 100644
--- a/test/opt/strip_debug_info_test.cpp
+++ b/test/opt/strip_debug_info_test.cpp
@@ -29,6 +29,9 @@
                "OpMemoryModel Logical GLSL450",
                "OpEntryPoint Vertex %2 \"main\"",
           "%3 = OpString \"minimal.vert\"",
+               "OpModuleProcessed \"42\"",
+               "OpModuleProcessed \"43\"",
+               "OpModuleProcessed \"44\"",
                "OpNoLine",
                "OpLine %3 10 10",
        "%void = OpTypeVoid",
@@ -62,9 +65,6 @@
       "OpSourceContinued \"wahahaha\"",
       "OpSourceExtension \"save-the-world-extension\"",
       "OpName %2 \"main\"",
-      "OpModuleProcessed \"42\"",
-      "OpModuleProcessed \"43\"",
-      "OpModuleProcessed \"44\"",
   };
   text.insert(text.begin() + 4, more_text.cbegin(), more_text.cend());
   SinglePassRunAndCheck<opt::StripDebugInfoPass>(JoinAllInsts(text),
diff --git a/test/util/CMakeLists.txt b/test/util/CMakeLists.txt
new file mode 100644
index 0000000..9b6ca2c
--- /dev/null
+++ b/test/util/CMakeLists.txt
@@ -0,0 +1,18 @@
+# Copyright (c) 2017 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+add_spvtools_unittest(TARGET util_intrusive_list
+  SRCS ilist_test.cpp
+  LIBS SPIRV-Tools-opt
+)
diff --git a/test/util/ilist_test.cpp b/test/util/ilist_test.cpp
new file mode 100644
index 0000000..0c87709
--- /dev/null
+++ b/test/util/ilist_test.cpp
@@ -0,0 +1,318 @@
+// Copyright (c) 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <vector>
+
+#include "gmock/gmock.h"
+
+#include "util/ilist.h"
+
+namespace {
+
+using ::testing::ElementsAre;
+using spvtools::utils::IntrusiveList;
+using spvtools::utils::IntrusiveNodeBase;
+using IListTest = ::testing::Test;
+
+class TestNode : public IntrusiveNodeBase<TestNode> {
+ public:
+  TestNode() : IntrusiveNodeBase<TestNode>() {}
+  int data_;
+};
+
+class TestList : public IntrusiveList<TestNode> {
+ public:
+  TestList() = default;
+  TestList(TestList&& that) : IntrusiveList<TestNode>(std::move(that)) {}
+  TestList& operator=(TestList&& that) {
+    static_cast<IntrusiveList<TestNode>&>(*this) =
+        static_cast<IntrusiveList<TestNode>&&>(that);
+    return *this;
+  }
+};
+
+// This test checks the push_back method, as well as using an iterator to
+// traverse the list from begin() to end().  This implicitly test the
+// PreviousNode and NextNode functions.
+TEST(IListTest, PushBack) {
+  TestNode nodes[10];
+  TestList list;
+  for (int i = 0; i < 10; i++) {
+    nodes[i].data_ = i;
+    list.push_back(&nodes[i]);
+  }
+
+  std::vector<int> output;
+  for (auto& i : list) output.push_back(i.data_);
+
+  EXPECT_THAT(output, ElementsAre(0, 1, 2, 3, 4, 5, 6, 7, 8, 9));
+}
+
+// Returns a list containing the values 0 to n-1 using the first n elements of
+// nodes to build the list.
+TestList BuildList(TestNode nodes[], int n) {
+  TestList list;
+  for (int i = 0; i < n; i++) {
+    nodes[i].data_ = i;
+    list.push_back(&nodes[i]);
+  }
+  return list;
+}
+
+// Test decrementing begin()
+TEST(IListTest, DecrementingBegin) {
+  TestNode nodes[10];
+  TestList list = BuildList(nodes, 10);
+  EXPECT_EQ(--list.begin(), list.end());
+}
+
+// Test incrementing end()
+TEST(IListTest, IncrementingEnd1) {
+  TestNode nodes[10];
+  TestList list = BuildList(nodes, 10);
+  EXPECT_EQ((++list.end())->data_, 0);
+}
+
+// Test incrementing end() should equal begin()
+TEST(IListTest, IncrementingEnd2) {
+  TestNode nodes[10];
+  TestList list = BuildList(nodes, 10);
+  EXPECT_EQ(++list.end(), list.begin());
+}
+
+// Test decrementing end()
+TEST(IListTest, DecrementingEnd) {
+  TestNode nodes[10];
+  TestList list = BuildList(nodes, 10);
+  EXPECT_EQ((--list.end())->data_, 9);
+}
+
+// Test the move constructor for the list class.
+TEST(IListTest, MoveConstructor) {
+  TestNode nodes[10];
+  TestList list = BuildList(nodes, 10);
+  std::vector<int> output;
+  for (auto& i : list) output.push_back(i.data_);
+
+  EXPECT_THAT(output, ElementsAre(0, 1, 2, 3, 4, 5, 6, 7, 8, 9));
+}
+
+// Using a const list so we can test the const_iterator.
+TEST(IListTest, ConstIterator) {
+  TestNode nodes[10];
+  const TestList list = BuildList(nodes, 10);
+  std::vector<int> output;
+  for (auto& i : list) output.push_back(i.data_);
+
+  EXPECT_THAT(output, ElementsAre(0, 1, 2, 3, 4, 5, 6, 7, 8, 9));
+}
+
+// Uses the move assignement instead of the move constructor.
+TEST(IListTest, MoveAssignment) {
+  TestNode nodes[10];
+  TestList list;
+  list = BuildList(nodes, 10);
+  std::vector<int> output;
+  for (auto& i : list) output.push_back(i.data_);
+
+  EXPECT_THAT(output, ElementsAre(0, 1, 2, 3, 4, 5, 6, 7, 8, 9));
+}
+
+// Test inserting a new element at the end of a list using the IntrusiveNodeBase
+// "InsertAfter" function.
+TEST(IListTest, InsertAfter1) {
+  TestNode nodes[10];
+  TestList list = BuildList(nodes, 5);
+
+  nodes[5].data_ = 5;
+  nodes[5].InsertAfter(&nodes[4]);
+
+  std::vector<int> output;
+  for (auto& i : list) output.push_back(i.data_);
+
+  EXPECT_THAT(output, ElementsAre(0, 1, 2, 3, 4, 5));
+}
+
+// Test inserting a new element in the middle of a list using the
+// IntrusiveNodeBase "InsertAfter" function.
+TEST(IListTest, InsertAfter2) {
+  TestNode nodes[10];
+  TestList list = BuildList(nodes, 5);
+
+  nodes[5].data_ = 5;
+  nodes[5].InsertAfter(&nodes[2]);
+
+  std::vector<int> output;
+  for (auto& i : list) output.push_back(i.data_);
+
+  EXPECT_THAT(output, ElementsAre(0, 1, 2, 5, 3, 4));
+}
+
+// Test moving an element already in the list in the middle of a list using the
+// IntrusiveNodeBase "InsertAfter" function.
+TEST(IListTest, MoveUsingInsertAfter1) {
+  TestNode nodes[10];
+  TestList list = BuildList(nodes, 6);
+
+  nodes[5].InsertAfter(&nodes[2]);
+
+  std::vector<int> output;
+  for (auto& i : list) output.push_back(i.data_);
+
+  EXPECT_THAT(output, ElementsAre(0, 1, 2, 5, 3, 4));
+}
+
+// Move the element at the start of the list into the middle.
+TEST(IListTest, MoveUsingInsertAfter2) {
+  TestNode nodes[10];
+  TestList list = BuildList(nodes, 6);
+
+  nodes[0].InsertAfter(&nodes[2]);
+
+  std::vector<int> output;
+  for (auto& i : list) output.push_back(i.data_);
+
+  EXPECT_THAT(output, ElementsAre(1, 2, 0, 3, 4, 5));
+}
+
+// Move an element in the middle of the list to the end.
+TEST(IListTest, MoveUsingInsertAfter3) {
+  TestNode nodes[10];
+  TestList list = BuildList(nodes, 6);
+
+  nodes[2].InsertAfter(&nodes[5]);
+
+  std::vector<int> output;
+  for (auto& i : list) output.push_back(i.data_);
+
+  EXPECT_THAT(output, ElementsAre(0, 1, 3, 4, 5, 2));
+}
+
+// Removing an element from the middle of a list.
+TEST(IListTest, Remove1) {
+  TestNode nodes[10];
+  TestList list = BuildList(nodes, 6);
+
+  nodes[2].RemoveFromList();
+
+  std::vector<int> output;
+  for (auto& i : list) output.push_back(i.data_);
+
+  EXPECT_THAT(output, ElementsAre(0, 1, 3, 4, 5));
+}
+
+// Removing an element from the beginning of the list.
+TEST(IListTest, Remove2) {
+  TestNode nodes[10];
+  TestList list = BuildList(nodes, 6);
+
+  nodes[0].RemoveFromList();
+
+  std::vector<int> output;
+  for (auto& i : list) output.push_back(i.data_);
+
+  EXPECT_THAT(output, ElementsAre(1, 2, 3, 4, 5));
+}
+
+// Removing the last element of a list.
+TEST(IListTest, Remove3) {
+  TestNode nodes[10];
+  TestList list = BuildList(nodes, 6);
+
+  nodes[5].RemoveFromList();
+
+  std::vector<int> output;
+  for (auto& i : list) output.push_back(i.data_);
+
+  EXPECT_THAT(output, ElementsAre(0, 1, 2, 3, 4));
+}
+
+// Test that operator== and operator!= work properly for the iterator class.
+TEST(IListTest, IteratorEqual) {
+  TestNode nodes[10];
+  TestList list = BuildList(nodes, 6);
+
+  std::vector<int> output;
+  for (auto i = list.begin(); i != list.end(); ++i)
+    for (auto j = list.begin(); j != list.end(); ++j)
+      if (i == j) output.push_back(i->data_);
+
+  EXPECT_THAT(output, ElementsAre(0, 1, 2, 3, 4, 5));
+}
+
+// Test MoveBefore.  Moving into middle of a list.
+TEST(IListTest, MoveBefore1) {
+  TestNode nodes[10];
+  TestList list1 = BuildList(nodes, 6);
+  TestList list2 = BuildList(nodes + 6, 3);
+
+  TestList::iterator insertion_point = list1.begin();
+  ++insertion_point;
+  insertion_point.MoveBefore(&list2);
+
+  std::vector<int> output;
+  for (auto i = list1.begin(); i != list1.end(); ++i)
+    output.push_back(i->data_);
+
+  EXPECT_THAT(output, ElementsAre(0, 0, 1, 2, 1, 2, 3, 4, 5));
+}
+
+// Test MoveBefore.  Moving to the start of a list.
+TEST(IListTest, MoveBefore2) {
+  TestNode nodes[10];
+  TestList list1 = BuildList(nodes, 6);
+  TestList list2 = BuildList(nodes + 6, 3);
+
+  TestList::iterator insertion_point = list1.begin();
+  insertion_point.MoveBefore(&list2);
+
+  std::vector<int> output;
+  for (auto i = list1.begin(); i != list1.end(); ++i)
+    output.push_back(i->data_);
+
+  EXPECT_THAT(output, ElementsAre(0, 1, 2, 0, 1, 2, 3, 4, 5));
+}
+
+// Test MoveBefore.  Moving to the end of a list.
+TEST(IListTest, MoveBefore3) {
+  TestNode nodes[10];
+  TestList list1 = BuildList(nodes, 6);
+  TestList list2 = BuildList(nodes + 6, 3);
+
+  TestList::iterator insertion_point = list1.end();
+  insertion_point.MoveBefore(&list2);
+
+  std::vector<int> output;
+  for (auto i = list1.begin(); i != list1.end(); ++i)
+    output.push_back(i->data_);
+
+  EXPECT_THAT(output, ElementsAre(0, 1, 2, 3, 4, 5, 0, 1, 2));
+}
+
+// Test MoveBefore.  Moving an empty list.
+TEST(IListTest, MoveBefore4) {
+  TestNode nodes[10];
+  TestList list1 = BuildList(nodes, 6);
+  TestList list2;
+
+  TestList::iterator insertion_point = list1.end();
+  insertion_point.MoveBefore(&list2);
+
+  std::vector<int> output;
+  for (auto i = list1.begin(); i != list1.end(); ++i)
+    output.push_back(i->data_);
+
+  EXPECT_THAT(output, ElementsAre(0, 1, 2, 3, 4, 5));
+}
+}  // namespace
diff --git a/test/val/CMakeLists.txt b/test/val/CMakeLists.txt
index 9f59eb1..50b93be 100644
--- a/test/val/CMakeLists.txt
+++ b/test/val/CMakeLists.txt
@@ -15,7 +15,6 @@
 set(VAL_TEST_COMMON_SRCS
   ${CMAKE_CURRENT_SOURCE_DIR}/../test_fixture.h
   ${CMAKE_CURRENT_SOURCE_DIR}/../unit_spirv.h
-  ${CMAKE_CURRENT_SOURCE_DIR}/../unit_spirv.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/val_fixtures.h
   ${CMAKE_CURRENT_SOURCE_DIR}/val_fixtures.cpp
 )
@@ -75,6 +74,30 @@
   LIBS ${SPIRV_TOOLS}
 )
 
+add_spvtools_unittest(TARGET val_arithmetics
+	SRCS val_arithmetics_test.cpp
+       ${VAL_TEST_COMMON_SRCS}
+  LIBS ${SPIRV_TOOLS}
+)
+
+add_spvtools_unittest(TARGET val_conversion
+	SRCS val_conversion_test.cpp
+       ${VAL_TEST_COMMON_SRCS}
+  LIBS ${SPIRV_TOOLS}
+)
+
+add_spvtools_unittest(TARGET val_logicals
+	SRCS val_logicals_test.cpp
+       ${VAL_TEST_COMMON_SRCS}
+  LIBS ${SPIRV_TOOLS}
+)
+
+add_spvtools_unittest(TARGET val_bitwise
+	SRCS val_bitwise_test.cpp
+       ${VAL_TEST_COMMON_SRCS}
+  LIBS ${SPIRV_TOOLS}
+)
+
 add_spvtools_unittest(TARGET val_limits
 	SRCS val_limits_test.cpp
        ${VAL_TEST_COMMON_SRCS}
diff --git a/test/val/val_arithmetics_test.cpp b/test/val/val_arithmetics_test.cpp
new file mode 100644
index 0000000..096fbfe
--- /dev/null
+++ b/test/val/val_arithmetics_test.cpp
@@ -0,0 +1,1215 @@
+// Copyright (c) 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Tests for unique type declaration rules validator.
+
+#include <string>
+
+#include "gmock/gmock.h"
+#include "unit_spirv.h"
+#include "val_fixtures.h"
+
+namespace {
+
+using ::testing::HasSubstr;
+using ::testing::Not;
+
+using std::string;
+
+using ValidateArithmetics = spvtest::ValidateBase<bool>;
+
+std::string GenerateCode(const std::string& main_body) {
+  const std::string prefix =
+R"(
+OpCapability Shader
+OpCapability Int64
+OpCapability Float64
+OpCapability Matrix
+%ext_inst = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%bool = OpTypeBool
+%f32 = OpTypeFloat 32
+%u32 = OpTypeInt 32 0
+%s32 = OpTypeInt 32 1
+%f64 = OpTypeFloat 64
+%u64 = OpTypeInt 64 0
+%s64 = OpTypeInt 64 1
+%boolvec2 = OpTypeVector %bool 2
+%s32vec2 = OpTypeVector %s32 2
+%u32vec2 = OpTypeVector %u32 2
+%u64vec2 = OpTypeVector %u64 2
+%f32vec2 = OpTypeVector %f32 2
+%f64vec2 = OpTypeVector %f64 2
+%boolvec3 = OpTypeVector %bool 3
+%u32vec3 = OpTypeVector %u32 3
+%u64vec3 = OpTypeVector %u64 3
+%s32vec3 = OpTypeVector %s32 3
+%f32vec3 = OpTypeVector %f32 3
+%f64vec3 = OpTypeVector %f64 3
+%boolvec4 = OpTypeVector %bool 4
+%u32vec4 = OpTypeVector %u32 4
+%u64vec4 = OpTypeVector %u64 4
+%s32vec4 = OpTypeVector %s32 4
+%f32vec4 = OpTypeVector %f32 4
+%f64vec4 = OpTypeVector %f64 4
+
+%f32mat22 = OpTypeMatrix %f32vec2 2
+%f32mat23 = OpTypeMatrix %f32vec2 3
+%f32mat32 = OpTypeMatrix %f32vec3 2
+%f32mat33 = OpTypeMatrix %f32vec3 3
+%f64mat22 = OpTypeMatrix %f64vec2 2
+
+%struct_f32_f32 = OpTypeStruct %f32 %f32
+%struct_u32_u32 = OpTypeStruct %u32 %u32
+%struct_u32_u32_u32 = OpTypeStruct %u32 %u32 %u32
+%struct_s32_s32 = OpTypeStruct %s32 %s32
+%struct_s32_u32 = OpTypeStruct %s32 %u32
+%struct_u32vec2_u32vec2 = OpTypeStruct %u32vec2 %u32vec2
+%struct_s32vec2_s32vec2 = OpTypeStruct %s32vec2 %s32vec2
+
+%f32_0 = OpConstant %f32 0
+%f32_1 = OpConstant %f32 1
+%f32_2 = OpConstant %f32 2
+%f32_3 = OpConstant %f32 3
+%f32_4 = OpConstant %f32 4
+%f32_pi = OpConstant %f32 3.14159
+
+%s32_0 = OpConstant %s32 0
+%s32_1 = OpConstant %s32 1
+%s32_2 = OpConstant %s32 2
+%s32_3 = OpConstant %s32 3
+%s32_4 = OpConstant %s32 4
+%s32_m1 = OpConstant %s32 -1
+
+%u32_0 = OpConstant %u32 0
+%u32_1 = OpConstant %u32 1
+%u32_2 = OpConstant %u32 2
+%u32_3 = OpConstant %u32 3
+%u32_4 = OpConstant %u32 4
+
+%f64_0 = OpConstant %f64 0
+%f64_1 = OpConstant %f64 1
+%f64_2 = OpConstant %f64 2
+%f64_3 = OpConstant %f64 3
+%f64_4 = OpConstant %f64 4
+
+%s64_0 = OpConstant %s64 0
+%s64_1 = OpConstant %s64 1
+%s64_2 = OpConstant %s64 2
+%s64_3 = OpConstant %s64 3
+%s64_4 = OpConstant %s64 4
+%s64_m1 = OpConstant %s64 -1
+
+%u64_0 = OpConstant %u64 0
+%u64_1 = OpConstant %u64 1
+%u64_2 = OpConstant %u64 2
+%u64_3 = OpConstant %u64 3
+%u64_4 = OpConstant %u64 4
+
+%u32vec2_01 = OpConstantComposite %u32vec2 %u32_0 %u32_1
+%u32vec2_12 = OpConstantComposite %u32vec2 %u32_1 %u32_2
+%u32vec3_012 = OpConstantComposite %u32vec3 %u32_0 %u32_1 %u32_2
+%u32vec3_123 = OpConstantComposite %u32vec3 %u32_1 %u32_2 %u32_3
+%u32vec4_0123 = OpConstantComposite %u32vec4 %u32_0 %u32_1 %u32_2 %u32_3
+%u32vec4_1234 = OpConstantComposite %u32vec4 %u32_1 %u32_2 %u32_3 %u32_4
+
+%s32vec2_01 = OpConstantComposite %s32vec2 %s32_0 %s32_1
+%s32vec2_12 = OpConstantComposite %s32vec2 %s32_1 %s32_2
+%s32vec3_012 = OpConstantComposite %s32vec3 %s32_0 %s32_1 %s32_2
+%s32vec3_123 = OpConstantComposite %s32vec3 %s32_1 %s32_2 %s32_3
+%s32vec4_0123 = OpConstantComposite %s32vec4 %s32_0 %s32_1 %s32_2 %s32_3
+%s32vec4_1234 = OpConstantComposite %s32vec4 %s32_1 %s32_2 %s32_3 %s32_4
+
+%f32vec2_01 = OpConstantComposite %f32vec2 %f32_0 %f32_1
+%f32vec2_12 = OpConstantComposite %f32vec2 %f32_1 %f32_2
+%f32vec3_012 = OpConstantComposite %f32vec3 %f32_0 %f32_1 %f32_2
+%f32vec3_123 = OpConstantComposite %f32vec3 %f32_1 %f32_2 %f32_3
+%f32vec4_0123 = OpConstantComposite %f32vec4 %f32_0 %f32_1 %f32_2 %f32_3
+%f32vec4_1234 = OpConstantComposite %f32vec4 %f32_1 %f32_2 %f32_3 %f32_4
+
+%f64vec2_01 = OpConstantComposite %f64vec2 %f64_0 %f64_1
+%f64vec2_12 = OpConstantComposite %f64vec2 %f64_1 %f64_2
+%f64vec3_012 = OpConstantComposite %f64vec3 %f64_0 %f64_1 %f64_2
+%f64vec3_123 = OpConstantComposite %f64vec3 %f64_1 %f64_2 %f64_3
+%f64vec4_0123 = OpConstantComposite %f64vec4 %f64_0 %f64_1 %f64_2 %f64_3
+%f64vec4_1234 = OpConstantComposite %f64vec4 %f64_1 %f64_2 %f64_3 %f64_4
+
+%f32mat22_1212 = OpConstantComposite %f32mat22 %f32vec2_12 %f32vec2_12
+%f32mat23_121212 = OpConstantComposite %f32mat23 %f32vec2_12 %f32vec2_12 %f32vec2_12
+%f32mat32_123123 = OpConstantComposite %f32mat32 %f32vec3_123 %f32vec3_123
+%f32mat33_123123123 = OpConstantComposite %f32mat33 %f32vec3_123 %f32vec3_123 %f32vec3_123
+
+%f64mat22_1212 = OpConstantComposite %f64mat22 %f64vec2_12 %f64vec2_12
+
+%main = OpFunction %void None %func
+%main_entry = OpLabel)";
+
+  const std::string suffix =
+R"(
+OpReturn
+OpFunctionEnd)";
+
+  return prefix + main_body + suffix;
+}
+
+TEST_F(ValidateArithmetics, F32Success) {
+  const std::string body = R"(
+%val1 = OpFMul %f32 %f32_0 %f32_1
+%val2 = OpFSub %f32 %f32_2 %f32_0
+%val3 = OpFAdd %f32 %val1 %val2
+%val4 = OpFNegate %f32 %val3
+%val5 = OpFDiv %f32 %val4 %val1
+%val6 = OpFRem %f32 %val4 %f32_2
+%val7 = OpFMod %f32 %val4 %f32_2
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateArithmetics, F64Success) {
+  const std::string body = R"(
+%val1 = OpFMul %f64 %f64_0 %f64_1
+%val2 = OpFSub %f64 %f64_2 %f64_0
+%val3 = OpFAdd %f64 %val1 %val2
+%val4 = OpFNegate %f64 %val3
+%val5 = OpFDiv %f64 %val4 %val1
+%val6 = OpFRem %f64 %val4 %f64_2
+%val7 = OpFMod %f64 %val4 %f64_2
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateArithmetics, Int32Success) {
+  const std::string body = R"(
+%val1 = OpIMul %u32 %s32_0 %u32_1
+%val2 = OpIMul %s32 %s32_2 %u32_1
+%val3 = OpIAdd %u32 %val1 %val2
+%val4 = OpIAdd %s32 %val1 %val2
+%val5 = OpISub %u32 %val3 %val4
+%val6 = OpISub %s32 %val4 %val3
+%val7 = OpSDiv %s32 %val4 %val3
+%val8 = OpSNegate %s32 %val7
+%val9 = OpSRem %s32 %val4 %val3
+%val10 = OpSMod %s32 %val4 %val3
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateArithmetics, Int64Success) {
+  const std::string body = R"(
+%val1 = OpIMul %u64 %s64_0 %u64_1
+%val2 = OpIMul %s64 %s64_2 %u64_1
+%val3 = OpIAdd %u64 %val1 %val2
+%val4 = OpIAdd %s64 %val1 %val2
+%val5 = OpISub %u64 %val3 %val4
+%val6 = OpISub %s64 %val4 %val3
+%val7 = OpSDiv %s64 %val4 %val3
+%val8 = OpSNegate %s64 %val7
+%val9 = OpSRem %s64 %val4 %val3
+%val10 = OpSMod %s64 %val4 %val3
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateArithmetics, F32Vec2Success) {
+  const std::string body = R"(
+%val1 = OpFMul %f32vec2 %f32vec2_01 %f32vec2_12
+%val2 = OpFSub %f32vec2 %f32vec2_12 %f32vec2_01
+%val3 = OpFAdd %f32vec2 %val1 %val2
+%val4 = OpFNegate %f32vec2 %val3
+%val5 = OpFDiv %f32vec2 %val4 %val1
+%val6 = OpFRem %f32vec2 %val4 %f32vec2_12
+%val7 = OpFMod %f32vec2 %val4 %f32vec2_12
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateArithmetics, F64Vec2Success) {
+  const std::string body = R"(
+%val1 = OpFMul %f64vec2 %f64vec2_01 %f64vec2_12
+%val2 = OpFSub %f64vec2 %f64vec2_12 %f64vec2_01
+%val3 = OpFAdd %f64vec2 %val1 %val2
+%val4 = OpFNegate %f64vec2 %val3
+%val5 = OpFDiv %f64vec2 %val4 %val1
+%val6 = OpFRem %f64vec2 %val4 %f64vec2_12
+%val7 = OpFMod %f64vec2 %val4 %f64vec2_12
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateArithmetics, U32Vec2Success) {
+  const std::string body = R"(
+%val1 = OpIMul %u32vec2 %u32vec2_01 %u32vec2_12
+%val2 = OpISub %u32vec2 %u32vec2_12 %u32vec2_01
+%val3 = OpIAdd %u32vec2 %val1 %val2
+%val4 = OpSNegate %u32vec2 %val3
+%val5 = OpSDiv %u32vec2 %val4 %val1
+%val6 = OpSRem %u32vec2 %val4 %u32vec2_12
+%val7 = OpSMod %u32vec2 %val4 %u32vec2_12
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateArithmetics, FNegateTypeIdU32) {
+  const std::string body = R"(
+%val = OpFNegate %u32 %u32_0
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected floating scalar or vector type as Result Type: FNegate"));
+}
+
+TEST_F(ValidateArithmetics, FNegateTypeIdVec2U32) {
+  const std::string body = R"(
+%val = OpFNegate %u32vec2 %u32vec2_01
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected floating scalar or vector type as Result Type: FNegate"));
+}
+
+TEST_F(ValidateArithmetics, FNegateWrongOperand) {
+  const std::string body = R"(
+%val = OpFNegate %f32 %u32_0
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected arithmetic operands to be of Result Type: "
+      "FNegate operand index 2"));
+}
+
+TEST_F(ValidateArithmetics, FMulTypeIdU32) {
+  const std::string body = R"(
+%val = OpFMul %u32 %u32_0 %u32_1
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected floating scalar or vector type as Result Type: FMul"));
+}
+
+TEST_F(ValidateArithmetics, FMulTypeIdVec2U32) {
+  const std::string body = R"(
+%val = OpFMul %u32vec2 %u32vec2_01 %u32vec2_12
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected floating scalar or vector type as Result Type: FMul"));
+}
+
+TEST_F(ValidateArithmetics, FMulWrongOperand1) {
+  const std::string body = R"(
+%val = OpFMul %f32 %u32_0 %f32_1
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected arithmetic operands to be of Result Type: "
+      "FMul operand index 2"));
+}
+
+TEST_F(ValidateArithmetics, FMulWrongOperand2) {
+  const std::string body = R"(
+%val = OpFMul %f32 %f32_0 %u32_1
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected arithmetic operands to be of Result Type: "
+      "FMul operand index 3"));
+}
+
+TEST_F(ValidateArithmetics, FMulWrongVectorOperand1) {
+  const std::string body = R"(
+%val = OpFMul %f64vec3 %f32vec3_123 %f64vec3_012
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected arithmetic operands to be of Result Type: "
+      "FMul operand index 2"));
+}
+
+TEST_F(ValidateArithmetics, FMulWrongVectorOperand2) {
+  const std::string body = R"(
+%val = OpFMul %f32vec3 %f32vec3_123 %f64vec3_012
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected arithmetic operands to be of Result Type: "
+      "FMul operand index 3"));
+}
+
+TEST_F(ValidateArithmetics, IMulFloatTypeId) {
+  const std::string body = R"(
+%val = OpIMul %f32 %u32_0 %s32_1
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected int scalar or vector type as Result Type: IMul"));
+}
+
+TEST_F(ValidateArithmetics, IMulFloatOperand1) {
+  const std::string body = R"(
+%val = OpIMul %u32 %f32_0 %s32_1
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected int scalar or vector type as operand: "
+      "IMul operand index 2"));
+}
+
+TEST_F(ValidateArithmetics, IMulFloatOperand2) {
+  const std::string body = R"(
+%val = OpIMul %u32 %s32_0 %f32_1
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected int scalar or vector type as operand: "
+      "IMul operand index 3"));
+}
+
+TEST_F(ValidateArithmetics, IMulWrongBitWidthOperand1) {
+  const std::string body = R"(
+%val = OpIMul %u64 %u32_0 %s64_1
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected arithmetic operands to have the same bit width "
+      "as Result Type: IMul operand index 2"));
+}
+
+TEST_F(ValidateArithmetics, IMulWrongBitWidthOperand2) {
+  const std::string body = R"(
+%val = OpIMul %u32 %u32_0 %s64_1
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected arithmetic operands to have the same bit width "
+      "as Result Type: IMul operand index 3"));
+}
+
+TEST_F(ValidateArithmetics, IMulWrongBitWidthVector) {
+  const std::string body = R"(
+%val = OpIMul %u64vec3 %u32vec3_012 %u32vec3_123
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected arithmetic operands to have the same bit width "
+      "as Result Type: IMul operand index 2"));
+}
+
+TEST_F(ValidateArithmetics, IMulVectorScalarOperand1) {
+  const std::string body = R"(
+%val = OpIMul %u32vec2 %u32_0 %u32vec2_01
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected arithmetic operands to have the same dimension "
+      "as Result Type: IMul operand index 2"));
+}
+
+TEST_F(ValidateArithmetics, IMulVectorScalarOperand2) {
+  const std::string body = R"(
+%val = OpIMul %u32vec2 %u32vec2_01 %u32_0
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected arithmetic operands to have the same dimension "
+      "as Result Type: IMul operand index 3"));
+}
+
+TEST_F(ValidateArithmetics, IMulScalarVectorOperand1) {
+  const std::string body = R"(
+%val = OpIMul %s32 %u32vec2_01 %u32_0
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected arithmetic operands to have the same dimension "
+      "as Result Type: IMul operand index 2"));
+}
+
+TEST_F(ValidateArithmetics, IMulScalarVectorOperand2) {
+  const std::string body = R"(
+%val = OpIMul %u32 %u32_0 %s32vec2_01
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected arithmetic operands to have the same dimension "
+      "as Result Type: IMul operand index 3"));
+}
+
+TEST_F(ValidateArithmetics, SNegateFloat) {
+  const std::string body = R"(
+%val = OpSNegate %s32 %f32_1
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected int scalar or vector type as operand: "
+      "SNegate operand index 2"));
+}
+
+TEST_F(ValidateArithmetics, UDivFloatType) {
+  const std::string body = R"(
+%val = OpUDiv %f32 %u32_2 %u32_1
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected unsigned int scalar or vector type as Result Type: UDiv"));
+}
+
+TEST_F(ValidateArithmetics, UDivSignedIntType) {
+  const std::string body = R"(
+%val = OpUDiv %s32 %u32_2 %u32_1
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected unsigned int scalar or vector type as Result Type: UDiv"));
+}
+
+TEST_F(ValidateArithmetics, UDivWrongOperand1) {
+  const std::string body = R"(
+%val = OpUDiv %u64 %f64_2 %u64_1
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected arithmetic operands to be of Result Type: "
+      "UDiv operand index 2"));
+}
+
+TEST_F(ValidateArithmetics, UDivWrongOperand2) {
+  const std::string body = R"(
+%val = OpUDiv %u64 %u64_2 %u32_1
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected arithmetic operands to be of Result Type: "
+      "UDiv operand index 3"));
+}
+
+TEST_F(ValidateArithmetics, DotSuccess) {
+  const std::string body = R"(
+%val = OpDot %f32 %f32vec2_01 %f32vec2_12
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateArithmetics, DotWrongTypeId) {
+  const std::string body = R"(
+%val = OpDot %u32 %u32vec2_01 %u32vec2_12
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected float scalar type as Result Type: Dot"));
+}
+
+TEST_F(ValidateArithmetics, DotNotVectorTypeOperand1) {
+  const std::string body = R"(
+%val = OpDot %f32 %f32 %f32vec2_12
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected float vector as operand: Dot operand index 2"));
+}
+
+TEST_F(ValidateArithmetics, DotNotVectorTypeOperand2) {
+  const std::string body = R"(
+%val = OpDot %f32 %f32vec3_012 %f32_1
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected float vector as operand: Dot operand index 3"));
+}
+
+TEST_F(ValidateArithmetics, DotWrongComponentOperand1) {
+  const std::string body = R"(
+%val = OpDot %f64 %f32vec2_01 %f64vec2_12
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected component type to be equal to Result Type: "
+      "Dot operand index 2"));
+}
+
+TEST_F(ValidateArithmetics, DotWrongComponentOperand2) {
+  const std::string body = R"(
+%val = OpDot %f32 %f32vec2_01 %f64vec2_12
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected component type to be equal to Result Type: "
+      "Dot operand index 3"));
+}
+
+TEST_F(ValidateArithmetics, DotDifferentVectorSize) {
+  const std::string body = R"(
+%val = OpDot %f32 %f32vec2_01 %f32vec3_123
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected operands to have the same number of componenets: Dot"));
+}
+
+TEST_F(ValidateArithmetics, VectorTimesScalarSuccess) {
+  const std::string body = R"(
+%val = OpVectorTimesScalar %f32vec2 %f32vec2_01 %f32_2
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateArithmetics, VectorTimesScalarWrongTypeId) {
+  const std::string body = R"(
+%val = OpVectorTimesScalar %u32vec2 %f32vec2_01 %f32_2
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected float vector type as Result Type: "
+      "VectorTimesScalar"));
+}
+
+TEST_F(ValidateArithmetics, VectorTimesScalarWrongVector) {
+  const std::string body = R"(
+%val = OpVectorTimesScalar %f32vec2 %f32vec3_012 %f32_2
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected vector operand type to be equal to Result Type: "
+      "VectorTimesScalar"));
+}
+
+TEST_F(ValidateArithmetics, VectorTimesScalarWrongScalar) {
+  const std::string body = R"(
+%val = OpVectorTimesScalar %f32vec2 %f32vec2_01 %f64_2
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected scalar operand type to be equal to the component "
+      "type of the vector operand: VectorTimesScalar"));
+}
+
+TEST_F(ValidateArithmetics, MatrixTimesScalarSuccess) {
+  const std::string body = R"(
+%val = OpMatrixTimesScalar %f32mat22 %f32mat22_1212 %f32_2
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateArithmetics, MatrixTimesScalarWrongTypeId) {
+  const std::string body = R"(
+%val = OpMatrixTimesScalar %f32vec2 %f32mat22_1212 %f32_2
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected float matrix type as Result Type: "
+      "MatrixTimesScalar"));
+}
+
+TEST_F(ValidateArithmetics, MatrixTimesScalarWrongMatrix) {
+  const std::string body = R"(
+%val = OpMatrixTimesScalar %f32mat22 %f32vec2_01 %f32_2
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected matrix operand type to be equal to Result Type: "
+      "MatrixTimesScalar"));
+}
+
+TEST_F(ValidateArithmetics, MatrixTimesScalarWrongScalar) {
+  const std::string body = R"(
+%val = OpMatrixTimesScalar %f32mat22 %f32mat22_1212 %f64_2
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected scalar operand type to be equal to the component "
+      "type of the matrix operand: MatrixTimesScalar"));
+}
+
+TEST_F(ValidateArithmetics, VectorTimesMatrix2x22Success) {
+  const std::string body = R"(
+%val = OpVectorTimesMatrix %f32vec2 %f32vec2_12 %f32mat22_1212
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateArithmetics, VectorTimesMatrix3x32Success) {
+  const std::string body = R"(
+%val = OpVectorTimesMatrix %f32vec2 %f32vec3_123 %f32mat32_123123
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateArithmetics, VectorTimesMatrixWrongTypeId) {
+  const std::string body = R"(
+%val = OpVectorTimesMatrix %f32mat22 %f32vec2_12 %f32mat22_1212
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected float vector type as Result Type: "
+      "VectorTimesMatrix"));
+}
+
+TEST_F(ValidateArithmetics, VectorTimesMatrixNotFloatVector) {
+  const std::string body = R"(
+%val = OpVectorTimesMatrix %f32vec2 %u32vec2_12 %f32mat22_1212
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected float vector type as left operand: "
+      "VectorTimesMatrix"));
+}
+
+TEST_F(ValidateArithmetics, VectorTimesMatrixWrongVectorComponent) {
+  const std::string body = R"(
+%val = OpVectorTimesMatrix %f32vec2 %f64vec2_12 %f32mat22_1212
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected component types of Result Type and vector to be equal: "
+      "VectorTimesMatrix"));
+}
+
+TEST_F(ValidateArithmetics, VectorTimesMatrixWrongMatrix) {
+  const std::string body = R"(
+%val = OpVectorTimesMatrix %f32vec2 %f32vec2_12 %f32vec2_12
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected float matrix type as right operand: "
+      "VectorTimesMatrix"));
+}
+
+TEST_F(ValidateArithmetics, VectorTimesMatrixWrongMatrixComponent) {
+  const std::string body = R"(
+%val = OpVectorTimesMatrix %f32vec2 %f32vec2_12 %f64mat22_1212
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected component types of Result Type and matrix to be equal: "
+      "VectorTimesMatrix"));
+}
+
+TEST_F(ValidateArithmetics, VectorTimesMatrix2eq2x23Fail) {
+  const std::string body = R"(
+%val = OpVectorTimesMatrix %f32vec2 %f32vec2_12 %f32mat23_121212
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected number of columns of the matrix to be equal to Result Type "
+      "vector size: VectorTimesMatrix"));
+}
+
+TEST_F(ValidateArithmetics, VectorTimesMatrix2x32Fail) {
+  const std::string body = R"(
+%val = OpVectorTimesMatrix %f32vec2 %f32vec2_12 %f32mat32_123123
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected number of rows of the matrix to be equal to the vector "
+      "operand size: VectorTimesMatrix"));
+}
+
+TEST_F(ValidateArithmetics, MatrixTimesVector22x2Success) {
+  const std::string body = R"(
+%val = OpMatrixTimesVector %f32vec2 %f32mat22_1212 %f32vec2_12
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateArithmetics, MatrixTimesVector23x3Success) {
+  const std::string body = R"(
+%val = OpMatrixTimesVector %f32vec2 %f32mat23_121212 %f32vec3_123
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateArithmetics, MatrixTimesVectorWrongTypeId) {
+  const std::string body = R"(
+%val = OpMatrixTimesVector %f32mat22 %f32mat22_1212 %f32vec2_12
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected float vector type as Result Type: "
+      "MatrixTimesVector"));
+}
+
+TEST_F(ValidateArithmetics, MatrixTimesVectorWrongMatrix) {
+  const std::string body = R"(
+%val = OpMatrixTimesVector %f32vec3 %f32vec3_123 %f32vec3_123
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected float matrix type as left operand: "
+      "MatrixTimesVector"));
+}
+
+TEST_F(ValidateArithmetics, MatrixTimesVectorWrongMatrixCol) {
+  const std::string body = R"(
+%val = OpMatrixTimesVector %f32vec3 %f32mat23_121212 %f32vec3_123
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected column type of the matrix to be equal to Result Type: "
+      "MatrixTimesVector"));
+}
+
+TEST_F(ValidateArithmetics, MatrixTimesVectorWrongVector) {
+  const std::string body = R"(
+%val = OpMatrixTimesVector %f32vec2 %f32mat22_1212 %u32vec2_12
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected float vector type as right operand: "
+      "MatrixTimesVector"));
+}
+
+TEST_F(ValidateArithmetics, MatrixTimesVectorDifferentComponents) {
+  const std::string body = R"(
+%val = OpMatrixTimesVector %f32vec2 %f32mat22_1212 %f64vec2_12
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected component types of the operands to be equal: "
+      "MatrixTimesVector"));
+}
+
+TEST_F(ValidateArithmetics, MatrixTimesVector22x3Fail) {
+  const std::string body = R"(
+%val = OpMatrixTimesVector %f32vec2 %f32mat22_1212 %f32vec3_123
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected number of columns of the matrix to be equal to the vector "
+      "size: MatrixTimesVector"));
+}
+
+TEST_F(ValidateArithmetics, MatrixTimesMatrix22x22Success) {
+  const std::string body = R"(
+%val = OpMatrixTimesMatrix %f32mat22 %f32mat22_1212 %f32mat22_1212
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateArithmetics, MatrixTimesMatrix23x32Success) {
+  const std::string body = R"(
+%val = OpMatrixTimesMatrix %f32mat22 %f32mat23_121212 %f32mat32_123123
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateArithmetics, MatrixTimesMatrix33x33Success) {
+  const std::string body = R"(
+%val = OpMatrixTimesMatrix %f32mat33 %f32mat33_123123123 %f32mat33_123123123
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateArithmetics, MatrixTimesMatrixWrongTypeId) {
+  const std::string body = R"(
+%val = OpMatrixTimesMatrix %f32vec2 %f32mat22_1212 %f32mat22_1212
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected float matrix type as Result Type: MatrixTimesMatrix"));
+}
+
+TEST_F(ValidateArithmetics, MatrixTimesMatrixWrongLeftOperand) {
+  const std::string body = R"(
+%val = OpMatrixTimesMatrix %f32mat22 %f32vec2_12 %f32mat22_1212
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected float matrix type as left operand: MatrixTimesMatrix"));
+}
+
+TEST_F(ValidateArithmetics, MatrixTimesMatrixWrongRightOperand) {
+  const std::string body = R"(
+%val = OpMatrixTimesMatrix %f32mat22 %f32mat22_1212 %f32vec2_12
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected float matrix type as right operand: MatrixTimesMatrix"));
+}
+
+TEST_F(ValidateArithmetics, MatrixTimesMatrix32x23Fail) {
+  const std::string body = R"(
+%val = OpMatrixTimesMatrix %f32mat22 %f32mat32_123123 %f32mat23_121212
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected column types of Result Type and left matrix to be equal: "
+      "MatrixTimesMatrix"));
+}
+
+TEST_F(ValidateArithmetics, MatrixTimesMatrixDifferentComponents) {
+  const std::string body = R"(
+%val = OpMatrixTimesMatrix %f32mat22 %f32mat22_1212 %f64mat22_1212
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected component types of Result Type and right matrix to be equal: "
+      "MatrixTimesMatrix"));
+}
+
+TEST_F(ValidateArithmetics, MatrixTimesMatrix23x23Fail) {
+  const std::string body = R"(
+%val = OpMatrixTimesMatrix %f32mat22 %f32mat23_121212 %f32mat23_121212
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected number of columns of Result Type and right matrix to be equal: "
+      "MatrixTimesMatrix"));
+}
+
+TEST_F(ValidateArithmetics, MatrixTimesMatrix23x22Fail) {
+  const std::string body = R"(
+%val = OpMatrixTimesMatrix %f32mat22 %f32mat23_121212 %f32mat22_1212
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected number of columns of left matrix and number of rows of right "
+      "matrix to be equal: MatrixTimesMatrix"));
+}
+
+TEST_F(ValidateArithmetics, OuterProduct2x2Success) {
+  const std::string body = R"(
+%val = OpOuterProduct %f32mat22 %f32vec2_12 %f32vec2_01
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateArithmetics, OuterProduct3x2Success) {
+  const std::string body = R"(
+%val = OpOuterProduct %f32mat32 %f32vec3_123 %f32vec2_01
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateArithmetics, OuterProduct2x3Success) {
+  const std::string body = R"(
+%val = OpOuterProduct %f32mat23 %f32vec2_01 %f32vec3_123
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateArithmetics, OuterProductWrongTypeId) {
+  const std::string body = R"(
+%val = OpOuterProduct %f32vec2 %f32vec2_01 %f32vec3_123
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected float matrix type as Result Type: "
+      "OuterProduct"));
+}
+
+TEST_F(ValidateArithmetics, OuterProductWrongLeftOperand) {
+  const std::string body = R"(
+%val = OpOuterProduct %f32mat22 %f32vec3_123 %f32vec2_01
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected column type of Result Type to be equal to the type "
+      "of the left operand: OuterProduct"));
+}
+
+TEST_F(ValidateArithmetics, OuterProductRightOperandNotFloatVector) {
+  const std::string body = R"(
+%val = OpOuterProduct %f32mat22 %f32vec2_12 %u32vec2_01
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected float vector type as right operand: OuterProduct"));
+}
+
+TEST_F(ValidateArithmetics, OuterProductRightOperandWrongComponent) {
+  const std::string body = R"(
+%val = OpOuterProduct %f32mat22 %f32vec2_12 %f64vec2_01
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected component types of the operands to be equal: OuterProduct"));
+}
+
+TEST_F(ValidateArithmetics, OuterProductRightOperandWrongDimension) {
+  const std::string body = R"(
+%val = OpOuterProduct %f32mat22 %f32vec2_12 %f32vec3_123
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected number of columns of the matrix to be equal to the "
+      "vector size of the right operand: OuterProduct"));
+}
+
+TEST_F(ValidateArithmetics, IAddCarrySuccess) {
+  const std::string body = R"(
+%val1 = OpIAddCarry %struct_u32_u32 %u32_0 %u32_1
+%val2 = OpIAddCarry %struct_u32vec2_u32vec2 %u32vec2_01 %u32vec2_12
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateArithmetics, IAddCarryResultTypeNotStruct) {
+  const std::string body = R"(
+%val = OpIAddCarry %u32 %u32_0 %u32_1
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected a struct as Result Type: IAddCarry"));
+}
+
+TEST_F(ValidateArithmetics, IAddCarryResultTypeNotTwoMembers) {
+  const std::string body = R"(
+%val = OpIAddCarry %struct_u32_u32_u32 %u32_0 %u32_1
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected Result Type struct to have two members: IAddCarry"));
+}
+
+TEST_F(ValidateArithmetics, IAddCarryResultTypeMemberNotUnsignedInt) {
+  const std::string body = R"(
+%val = OpIAddCarry %struct_s32_s32 %s32_0 %s32_1
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected Result Type struct member types to be unsigned integer scalar "
+      "or vector: IAddCarry"));
+}
+
+TEST_F(ValidateArithmetics, IAddCarryWrongLeftOperand) {
+  const std::string body = R"(
+%val = OpIAddCarry %struct_u32_u32 %s32_0 %u32_1
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected both operands to be of Result Type member type: IAddCarry"));
+}
+
+TEST_F(ValidateArithmetics, IAddCarryWrongRightOperand) {
+  const std::string body = R"(
+%val = OpIAddCarry %struct_u32_u32 %u32_0 %s32_1
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected both operands to be of Result Type member type: IAddCarry"));
+}
+
+TEST_F(ValidateArithmetics, OpSMulExtendedSuccess) {
+  const std::string body = R"(
+%val1 = OpSMulExtended %struct_u32_u32 %u32_0 %u32_1
+%val2 = OpSMulExtended %struct_s32_s32 %s32_0 %s32_1
+%val3 = OpSMulExtended %struct_u32vec2_u32vec2 %u32vec2_01 %u32vec2_12
+%val4 = OpSMulExtended %struct_s32vec2_s32vec2 %s32vec2_01 %s32vec2_12
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateArithmetics, SMulExtendedResultTypeMemberNotInt) {
+  const std::string body = R"(
+%val = OpSMulExtended %struct_f32_f32 %f32_0 %f32_1
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected Result Type struct member types to be integer scalar "
+      "or vector: SMulExtended"));
+}
+
+TEST_F(ValidateArithmetics, SMulExtendedResultTypeMembersNotIdentical) {
+  const std::string body = R"(
+%val = OpSMulExtended %struct_s32_u32 %s32_0 %s32_1
+)";
+
+  CompileSuccessfully(GenerateCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected Result Type struct member types to be identical: "
+      "SMulExtended"));
+}
+
+}  // anonymous namespace
diff --git a/test/val/val_bitwise_test.cpp b/test/val/val_bitwise_test.cpp
new file mode 100644
index 0000000..c302001
--- /dev/null
+++ b/test/val/val_bitwise_test.cpp
@@ -0,0 +1,512 @@
+// Copyright (c) 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Tests for unique type declaration rules validator.
+
+#include <string>
+
+#include "gmock/gmock.h"
+#include "unit_spirv.h"
+#include "val_fixtures.h"
+
+namespace {
+
+using ::testing::HasSubstr;
+using ::testing::Not;
+
+using ValidateBitwise = spvtest::ValidateBase<bool>;
+
+std::string GenerateShaderCode(
+    const std::string& body,
+    const std::string& capabilities_and_extensions = "") {
+  const std::string capabilities =
+R"(
+OpCapability Shader
+OpCapability Int64
+OpCapability Float64)";
+
+  const std::string after_extension_before_body =
+R"(
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%bool = OpTypeBool
+%f32 = OpTypeFloat 32
+%u32 = OpTypeInt 32 0
+%s32 = OpTypeInt 32 1
+%f64 = OpTypeFloat 64
+%u64 = OpTypeInt 64 0
+%s64 = OpTypeInt 64 1
+%boolvec2 = OpTypeVector %bool 2
+%s32vec2 = OpTypeVector %s32 2
+%u32vec2 = OpTypeVector %u32 2
+%u64vec2 = OpTypeVector %u64 2
+%f32vec2 = OpTypeVector %f32 2
+%f64vec2 = OpTypeVector %f64 2
+%boolvec3 = OpTypeVector %bool 3
+%u32vec3 = OpTypeVector %u32 3
+%u64vec3 = OpTypeVector %u64 3
+%s32vec3 = OpTypeVector %s32 3
+%f32vec3 = OpTypeVector %f32 3
+%f64vec3 = OpTypeVector %f64 3
+%boolvec4 = OpTypeVector %bool 4
+%u32vec4 = OpTypeVector %u32 4
+%u64vec4 = OpTypeVector %u64 4
+%s32vec4 = OpTypeVector %s32 4
+%f32vec4 = OpTypeVector %f32 4
+%f64vec4 = OpTypeVector %f64 4
+
+%f32_0 = OpConstant %f32 0
+%f32_1 = OpConstant %f32 1
+%f32_2 = OpConstant %f32 2
+%f32_3 = OpConstant %f32 3
+%f32_4 = OpConstant %f32 4
+
+%s32_0 = OpConstant %s32 0
+%s32_1 = OpConstant %s32 1
+%s32_2 = OpConstant %s32 2
+%s32_3 = OpConstant %s32 3
+%s32_4 = OpConstant %s32 4
+%s32_m1 = OpConstant %s32 -1
+
+%u32_0 = OpConstant %u32 0
+%u32_1 = OpConstant %u32 1
+%u32_2 = OpConstant %u32 2
+%u32_3 = OpConstant %u32 3
+%u32_4 = OpConstant %u32 4
+
+%f64_0 = OpConstant %f64 0
+%f64_1 = OpConstant %f64 1
+%f64_2 = OpConstant %f64 2
+%f64_3 = OpConstant %f64 3
+%f64_4 = OpConstant %f64 4
+
+%s64_0 = OpConstant %s64 0
+%s64_1 = OpConstant %s64 1
+%s64_2 = OpConstant %s64 2
+%s64_3 = OpConstant %s64 3
+%s64_4 = OpConstant %s64 4
+%s64_m1 = OpConstant %s64 -1
+
+%u64_0 = OpConstant %u64 0
+%u64_1 = OpConstant %u64 1
+%u64_2 = OpConstant %u64 2
+%u64_3 = OpConstant %u64 3
+%u64_4 = OpConstant %u64 4
+
+%u32vec2_01 = OpConstantComposite %u32vec2 %u32_0 %u32_1
+%u32vec2_12 = OpConstantComposite %u32vec2 %u32_1 %u32_2
+%u32vec3_012 = OpConstantComposite %u32vec3 %u32_0 %u32_1 %u32_2
+%u32vec3_123 = OpConstantComposite %u32vec3 %u32_1 %u32_2 %u32_3
+%u32vec4_0123 = OpConstantComposite %u32vec4 %u32_0 %u32_1 %u32_2 %u32_3
+%u32vec4_1234 = OpConstantComposite %u32vec4 %u32_1 %u32_2 %u32_3 %u32_4
+
+%s32vec2_01 = OpConstantComposite %s32vec2 %s32_0 %s32_1
+%s32vec2_12 = OpConstantComposite %s32vec2 %s32_1 %s32_2
+%s32vec3_012 = OpConstantComposite %s32vec3 %s32_0 %s32_1 %s32_2
+%s32vec3_123 = OpConstantComposite %s32vec3 %s32_1 %s32_2 %s32_3
+%s32vec4_0123 = OpConstantComposite %s32vec4 %s32_0 %s32_1 %s32_2 %s32_3
+%s32vec4_1234 = OpConstantComposite %s32vec4 %s32_1 %s32_2 %s32_3 %s32_4
+
+%f32vec2_01 = OpConstantComposite %f32vec2 %f32_0 %f32_1
+%f32vec2_12 = OpConstantComposite %f32vec2 %f32_1 %f32_2
+%f32vec3_012 = OpConstantComposite %f32vec3 %f32_0 %f32_1 %f32_2
+%f32vec3_123 = OpConstantComposite %f32vec3 %f32_1 %f32_2 %f32_3
+%f32vec4_0123 = OpConstantComposite %f32vec4 %f32_0 %f32_1 %f32_2 %f32_3
+%f32vec4_1234 = OpConstantComposite %f32vec4 %f32_1 %f32_2 %f32_3 %f32_4
+
+%main = OpFunction %void None %func
+%main_entry = OpLabel)";
+
+  const std::string after_body =
+R"(
+OpReturn
+OpFunctionEnd)";
+
+  return capabilities + capabilities_and_extensions +
+      after_extension_before_body + body + after_body;
+}
+
+TEST_F(ValidateBitwise, ShiftAllSuccess) {
+  const std::string body = R"(
+%val1 = OpShiftRightLogical %u64 %u64_1 %s32_2
+%val2 = OpShiftRightArithmetic %s32vec2 %s32vec2_12 %s32vec2_12
+%val3 = OpShiftLeftLogical %u32vec2 %s32vec2_12 %u32vec2_12
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateBitwise, OpShiftRightLogicalWrongResultType) {
+  const std::string body = R"(
+%val1 = OpShiftRightLogical %bool %u64_1 %s32_2
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected int scalar or vector type as Result Type: ShiftRightLogical"));
+}
+
+TEST_F(ValidateBitwise, OpShiftRightLogicalBaseNotInt) {
+  const std::string body = R"(
+%val1 = OpShiftRightLogical %u32 %f32_1 %s32_2
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected Base to be int scalar or vector: ShiftRightLogical"));
+}
+
+TEST_F(ValidateBitwise, OpShiftRightLogicalBaseWrongDimension) {
+  const std::string body = R"(
+%val1 = OpShiftRightLogical %u32 %u32vec2_12 %s32_2
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected Base to have the same dimension as Result Type: "
+      "ShiftRightLogical"));
+}
+
+TEST_F(ValidateBitwise, OpShiftRightLogicalBaseWrongBitWidth) {
+  const std::string body = R"(
+%val1 = OpShiftRightLogical %u64 %u32_1 %s32_2
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected Base to have the same bit width as Result Type: "
+      "ShiftRightLogical"));
+}
+
+TEST_F(ValidateBitwise, OpShiftRightLogicalShiftNotInt) {
+  const std::string body = R"(
+%val1 = OpShiftRightLogical %u32 %u32_1 %f32_2
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected Shift to be int scalar or vector: ShiftRightLogical"));
+}
+
+TEST_F(ValidateBitwise, OpShiftRightLogicalShiftWrongDimension) {
+  const std::string body = R"(
+%val1 = OpShiftRightLogical %u32 %u32_1 %s32vec2_12
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected Shift to have the same dimension as Result Type: "
+      "ShiftRightLogical"));
+}
+
+TEST_F(ValidateBitwise, LogicAllSuccess) {
+  const std::string body = R"(
+%val1 = OpBitwiseOr %u64 %u64_1 %s64_0
+%val2 = OpBitwiseAnd %s64 %s64_1 %u64_0
+%val3 = OpBitwiseXor %s32vec2 %s32vec2_12 %u32vec2_01
+%val4 = OpNot %s32vec2 %u32vec2_01
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateBitwise, OpBitwiseAndWrongResultType) {
+  const std::string body = R"(
+%val1 = OpBitwiseAnd %bool %u64_1 %s32_2
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected int scalar or vector type as Result Type: BitwiseAnd"));
+}
+
+TEST_F(ValidateBitwise, OpBitwiseAndLeftNotInt) {
+  const std::string body = R"(
+%val1 = OpBitwiseAnd %u32 %f32_1 %s32_2
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected int scalar or vector as operand: BitwiseAnd "
+      "operand index 2"));
+}
+
+TEST_F(ValidateBitwise, OpBitwiseAndRightNotInt) {
+  const std::string body = R"(
+%val1 = OpBitwiseAnd %u32 %u32_1 %f32_2
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected int scalar or vector as operand: BitwiseAnd "
+      "operand index 3"));
+}
+
+TEST_F(ValidateBitwise, OpBitwiseAndLeftWrongDimension) {
+  const std::string body = R"(
+%val1 = OpBitwiseAnd %u32 %u32vec2_12 %s32_2
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected operands to have the same dimension as Result Type: "
+      "BitwiseAnd operand index 2"));
+}
+
+TEST_F(ValidateBitwise, OpBitwiseAndRightWrongDimension) {
+  const std::string body = R"(
+%val1 = OpBitwiseAnd %u32 %s32_2 %u32vec2_12
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected operands to have the same dimension as Result Type: "
+      "BitwiseAnd operand index 3"));
+}
+
+TEST_F(ValidateBitwise, OpBitwiseAndLeftWrongBitWidth) {
+  const std::string body = R"(
+%val1 = OpBitwiseAnd %u64 %u32_1 %s64_2
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected operands to have the same bit width as Result Type: "
+      "BitwiseAnd operand index 2"));
+}
+
+TEST_F(ValidateBitwise, OpBitwiseAndRightWrongBitWidth) {
+  const std::string body = R"(
+%val1 = OpBitwiseAnd %u64 %u64_1 %s32_2
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected operands to have the same bit width as Result Type: "
+      "BitwiseAnd operand index 3"));
+}
+
+TEST_F(ValidateBitwise, OpBitFieldInsertSuccess) {
+  const std::string body = R"(
+%val1 = OpBitFieldInsert %u64 %u64_1 %u64_2 %s32_1 %s32_2
+%val2 = OpBitFieldInsert %s32vec2 %s32vec2_12 %s32vec2_12 %s32_1 %u32_2
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateBitwise, OpBitFieldInsertWrongResultType) {
+  const std::string body = R"(
+%val1 = OpBitFieldInsert %bool %u64_1 %u64_2 %s32_1 %s32_2
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected int scalar or vector type as Result Type: BitFieldInsert"));
+}
+
+TEST_F(ValidateBitwise, OpBitFieldInsertWrongBaseType) {
+  const std::string body = R"(
+%val1 = OpBitFieldInsert %u64 %s64_1 %u64_2 %s32_1 %s32_2
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected Base Type to be equal to Result Type: BitFieldInsert"));
+}
+
+TEST_F(ValidateBitwise, OpBitFieldInsertWrongInsertType) {
+  const std::string body = R"(
+%val1 = OpBitFieldInsert %u64 %u64_1 %s64_2 %s32_1 %s32_2
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected Insert Type to be equal to Result Type: BitFieldInsert"));
+}
+
+TEST_F(ValidateBitwise, OpBitFieldInsertOffsetNotInt) {
+  const std::string body = R"(
+%val1 = OpBitFieldInsert %u64 %u64_1 %u64_2 %f32_1 %s32_2
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected Offset Type to be int scalar: BitFieldInsert"));
+}
+
+TEST_F(ValidateBitwise, OpBitFieldInsertCountNotInt) {
+  const std::string body = R"(
+%val1 = OpBitFieldInsert %u64 %u64_1 %u64_2 %u32_1 %f32_2
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected Count Type to be int scalar: BitFieldInsert"));
+}
+
+TEST_F(ValidateBitwise, OpBitFieldSExtractSuccess) {
+  const std::string body = R"(
+%val1 = OpBitFieldSExtract %u64 %u64_1 %s32_1 %s32_2
+%val2 = OpBitFieldSExtract %s32vec2 %s32vec2_12 %s32_1 %u32_2
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateBitwise, OpBitFieldSExtractWrongResultType) {
+  const std::string body = R"(
+%val1 = OpBitFieldSExtract %bool %u64_1 %s32_1 %s32_2
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected int scalar or vector type as Result Type: BitFieldSExtract"));
+}
+
+TEST_F(ValidateBitwise, OpBitFieldSExtractWrongBaseType) {
+  const std::string body = R"(
+%val1 = OpBitFieldSExtract %u64 %s64_1 %s32_1 %s32_2
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected Base Type to be equal to Result Type: BitFieldSExtract"));
+}
+
+TEST_F(ValidateBitwise, OpBitFieldSExtractOffsetNotInt) {
+  const std::string body = R"(
+%val1 = OpBitFieldSExtract %u64 %u64_1 %f32_1 %s32_2
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected Offset Type to be int scalar: BitFieldSExtract"));
+}
+
+TEST_F(ValidateBitwise, OpBitFieldSExtractCountNotInt) {
+  const std::string body = R"(
+%val1 = OpBitFieldSExtract %u64 %u64_1 %u32_1 %f32_2
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected Count Type to be int scalar: BitFieldSExtract"));
+}
+
+TEST_F(ValidateBitwise, OpBitReverseSuccess) {
+  const std::string body = R"(
+%val1 = OpBitReverse %u64 %u64_1
+%val2 = OpBitReverse %s32vec2 %s32vec2_12
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateBitwise, OpBitReverseWrongResultType) {
+  const std::string body = R"(
+%val1 = OpBitReverse %bool %u64_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected int scalar or vector type as Result Type: BitReverse"));
+}
+
+TEST_F(ValidateBitwise, OpBitReverseWrongBaseType) {
+  const std::string body = R"(
+%val1 = OpBitReverse %u64 %s64_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected Base Type to be equal to Result Type: BitReverse"));
+}
+
+TEST_F(ValidateBitwise, OpBitCountSuccess) {
+  const std::string body = R"(
+%val1 = OpBitCount %s32 %u64_1
+%val2 = OpBitCount %u32vec2 %s32vec2_12
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateBitwise, OpBitCountWrongResultType) {
+  const std::string body = R"(
+%val1 = OpBitCount %bool %u64_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected int scalar or vector type as Result Type: BitCount"));
+}
+
+TEST_F(ValidateBitwise, OpBitCountBaseNotInt) {
+  const std::string body = R"(
+%val1 = OpBitCount %u32 %f64_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected Base Type to be int scalar or vector: BitCount"));
+}
+
+TEST_F(ValidateBitwise, OpBitCountBaseWrongDimension) {
+  const std::string body = R"(
+%val1 = OpBitCount %u32 %u32vec2_12
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected Base dimension to be equal to Result Type dimension: "
+      "BitCount"));
+}
+
+}  // anonymous namespace
diff --git a/test/val/val_cfg_test.cpp b/test/val/val_cfg_test.cpp
index 01d92fa..c1b5ce2 100644
--- a/test/val/val_cfg_test.cpp
+++ b/test/val/val_cfg_test.cpp
@@ -273,7 +273,7 @@
   Block cont("cont");
   Block merge("merge", SpvOpReturn);
 
-  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   if (is_shader) {
     loop.SetBody("OpLoopMerge %merge %cont None\n");
   }
@@ -340,7 +340,7 @@
   Block loop("loop", SpvOpBranchConditional);
   Block merge("merge", SpvOpReturn);
 
-  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   if (is_shader) loop.SetBody("OpLoopMerge %merge %loop None\n");
 
   string str = header(GetParam()) +
@@ -364,7 +364,7 @@
   Block branch("branch", SpvOpBranchConditional);
   Block merge("merge", SpvOpReturn);
 
-  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   if (is_shader) branch.SetBody("OpSelectionMerge %merge None\n");
 
   string str = header(GetParam()) +
@@ -391,7 +391,7 @@
   Block selection("selection", SpvOpBranchConditional);
   Block merge("merge", SpvOpReturn);
 
-  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   if (is_shader) loop.SetBody(" OpLoopMerge %merge %loop None\n");
 
   // cannot share the same merge
@@ -425,7 +425,7 @@
   Block selection("selection", SpvOpBranchConditional);
   Block merge("merge", SpvOpReturn);
 
-  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   if (is_shader) selection.SetBody(" OpSelectionMerge %merge None\n");
 
   // cannot share the same merge
@@ -477,7 +477,7 @@
   Block bad("bad", SpvOpBranchConditional);
   Block exit("exit", SpvOpReturn);
 
-  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   bad.SetBody(" OpLoopMerge %entry %exit None\n");
 
   string str = header(GetParam()) +
@@ -503,7 +503,7 @@
   Block merge("merge");
   Block end("end", SpvOpReturn);
 
-  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   bad.SetBody("OpLoopMerge %merge %cont None\n");
 
   string str = header(GetParam()) +
@@ -533,7 +533,7 @@
   Block merge("merge");
   Block end("end", SpvOpReturn);
 
-  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   bad.SetBody("OpSelectionMerge %merge None\n");
 
   string str = header(GetParam()) +
@@ -562,7 +562,7 @@
   Block middle("middle", SpvOpBranchConditional);
   Block end("end", SpvOpReturn);
 
-  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   middle.SetBody("OpSelectionMerge %end None\n");
 
   Block entry2("entry2");
@@ -599,7 +599,7 @@
   Block f("f");
   Block merge("merge", SpvOpReturn);
 
-  head.SetBody("%cond = OpSLessThan %intt %one %two\n");
+  head.SetBody("%cond = OpSLessThan %boolt %one %two\n");
 
   if (is_shader) head.AppendBody("OpSelectionMerge %merge None\n");
 
@@ -633,7 +633,7 @@
   Block head("head", SpvOpBranchConditional);
   Block exit("exit", SpvOpReturn);
 
-  head.SetBody("%cond = OpSLessThan %intt %one %two\n");
+  head.SetBody("%cond = OpSLessThan %boolt %one %two\n");
 
   if (is_shader) head.AppendBody("OpSelectionMerge %head None\n");
 
@@ -666,7 +666,7 @@
   Block f("f", SpvOpReturn);
   Block merge("merge", SpvOpReturn);
 
-  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   if (is_shader) branch.AppendBody("OpSelectionMerge %merge None\n");
 
   string str = header(GetParam()) +
@@ -692,7 +692,7 @@
   Block f("f", SpvOpReturn);
   Block merge("merge", SpvOpUnreachable);
 
-  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   if (is_shader) branch.AppendBody("OpSelectionMerge %merge None\n");
 
   string str = header(GetParam()) +
@@ -737,7 +737,7 @@
   Block merge("merge");
   Block exit("exit", SpvOpReturn);
 
-  unreachable.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  unreachable.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   if (is_shader) unreachable.AppendBody("OpSelectionMerge %merge None\n");
   string str = header(GetParam()) +
                nameOps("unreachable", "exit", make_pair("func", "Main")) +
@@ -772,7 +772,7 @@
   Block loop("loop", SpvOpBranchConditional);
   Block exit("exit", SpvOpReturn);
 
-  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   if (is_shader) loop.AppendBody("OpLoopMerge %exit %loop None\n");
 
   string str = header(GetParam()) + string(types_consts()) +
@@ -798,7 +798,7 @@
   Block loop1_merge("loop1_merge");
   Block exit("exit", SpvOpReturn);
 
-  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   if (is_shader) {
     loop1.SetBody("OpLoopMerge %loop1_merge %loop2 None\n");
     loop2.SetBody("OpLoopMerge %loop2_merge %loop2 None\n");
@@ -828,7 +828,7 @@
   vector<Block> merge_blocks;
   Block inner("inner");
 
-  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
 
   if_blocks.emplace_back("if0", SpvOpBranchConditional);
 
@@ -871,7 +871,7 @@
   Block be_block("be_block");
   Block exit("exit", SpvOpReturn);
 
-  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   if (is_shader) {
     loop1.SetBody("OpLoopMerge %exit %loop2_merge None\n");
     loop2.SetBody("OpLoopMerge %loop2_merge %loop2 None\n");
@@ -909,7 +909,7 @@
   Block f("f");
   Block exit("exit", SpvOpReturn);
 
-  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   if (is_shader) split.SetBody("OpSelectionMerge %exit None\n");
 
   string str = header(GetParam()) + nameOps("split", "f") + types_consts() +
@@ -940,7 +940,7 @@
   Block split("split", SpvOpBranchConditional);
   Block exit("exit", SpvOpReturn);
 
-  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   if (is_shader) split.SetBody("OpSelectionMerge %exit None\n");
 
   string str = header(GetParam()) + nameOps("split") + types_consts() +
@@ -971,7 +971,7 @@
   Block back1("back1");
   Block merge("merge", SpvOpReturn);
 
-  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   if (is_shader) loop.SetBody("OpLoopMerge %merge %back0 None\n");
 
   string str = header(GetParam()) + nameOps("loop", "back0", "back1") +
@@ -1006,7 +1006,7 @@
   Block merge("merge", SpvOpReturn);
   Block exit("exit", SpvOpReturn);
 
-  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   if (is_shader) loop.SetBody("OpLoopMerge %merge %cheader None\n");
 
   string str = header(GetParam()) + nameOps("cheader", "be_block") +
@@ -1039,7 +1039,7 @@
   Block cont("cont", SpvOpBranchConditional);
   Block merge("merge", SpvOpReturn);
 
-  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   if (is_shader) loop.SetBody("OpLoopMerge %merge %loop None\n");
 
   string str = header(GetParam()) + nameOps("cont", "loop") + types_consts() +
@@ -1072,7 +1072,7 @@
   Block merge("merge");
   Block exit("exit", SpvOpReturn);
 
-  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   if (is_shader) loop.SetBody("OpLoopMerge %merge %loop None\n");
 
   string str = header(GetParam()) + nameOps("cont", "loop") + types_consts() +
@@ -1216,7 +1216,7 @@
   Block inner_merge("inner_merge");
   Block exit("exit", SpvOpReturn);
 
-  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   if (is_shader) {
     entry.AppendBody("OpSelectionMerge %exit None\n");
     inner_head.SetBody("OpSelectionMerge %inner_merge None\n");
@@ -1252,7 +1252,7 @@
   Block if_merge("if_merge", SpvOpBranchConditional);
   Block merge("merge", SpvOpReturn);
 
-  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   if (is_shader) {
     loop.SetBody("OpLoopMerge %merge %if_merge None\n");
     if_head.SetBody("OpSelectionMerge %if_merge None\n");
@@ -1283,7 +1283,7 @@
   Block latch("latch", SpvOpBranchConditional);
   Block merge("merge", SpvOpReturn);
 
-  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   if (is_shader) {
     loop.SetBody("OpLoopMerge %merge %latch None\n");
   }
@@ -1315,7 +1315,7 @@
   Block latch("latch");
   Block merge("merge", SpvOpReturn);
 
-  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   if (is_shader) {
     loop.SetBody("OpLoopMerge %merge %loop None\n");
   }
@@ -1377,6 +1377,26 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
+TEST_F(ValidateCFG, OpReturnInNonVoidFunc) {
+  std::string spirv = R"(
+               OpCapability Shader
+               OpCapability Linkage
+               OpMemoryModel Logical GLSL450
+        %int = OpTypeInt 32 1
+   %int_func = OpTypeFunction %int
+    %testfun = OpFunction %int None %int_func
+    %label_1 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  CompileSuccessfully(spirv);
+  ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "OpReturn can only be called from a function with void return type"));
+}
+
 /// TODO(umar): Switch instructions
 /// TODO(umar): Nested CFG constructs
 }  /// namespace
diff --git a/test/val/val_conversion_test.cpp b/test/val/val_conversion_test.cpp
new file mode 100644
index 0000000..391651d
--- /dev/null
+++ b/test/val/val_conversion_test.cpp
@@ -0,0 +1,1065 @@
+// Copyright (c) 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Tests for unique type declaration rules validator.
+
+#include <string>
+
+#include "gmock/gmock.h"
+#include "unit_spirv.h"
+#include "val_fixtures.h"
+
+namespace {
+
+using ::testing::HasSubstr;
+using ::testing::Not;
+
+using ValidateConversion = spvtest::ValidateBase<bool>;
+
+std::string GenerateShaderCode(
+    const std::string& body,
+    const std::string& capabilities_and_extensions = "") {
+  const std::string capabilities =
+R"(
+OpCapability Shader
+OpCapability Int64
+OpCapability Float64)";
+
+  const std::string after_extension_before_body =
+R"(
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%bool = OpTypeBool
+%f32 = OpTypeFloat 32
+%u32 = OpTypeInt 32 0
+%s32 = OpTypeInt 32 1
+%f64 = OpTypeFloat 64
+%u64 = OpTypeInt 64 0
+%s64 = OpTypeInt 64 1
+%boolvec2 = OpTypeVector %bool 2
+%s32vec2 = OpTypeVector %s32 2
+%u32vec2 = OpTypeVector %u32 2
+%u64vec2 = OpTypeVector %u64 2
+%f32vec2 = OpTypeVector %f32 2
+%f64vec2 = OpTypeVector %f64 2
+%boolvec3 = OpTypeVector %bool 3
+%u32vec3 = OpTypeVector %u32 3
+%u64vec3 = OpTypeVector %u64 3
+%s32vec3 = OpTypeVector %s32 3
+%f32vec3 = OpTypeVector %f32 3
+%f64vec3 = OpTypeVector %f64 3
+%boolvec4 = OpTypeVector %bool 4
+%u32vec4 = OpTypeVector %u32 4
+%u64vec4 = OpTypeVector %u64 4
+%s32vec4 = OpTypeVector %s32 4
+%f32vec4 = OpTypeVector %f32 4
+%f64vec4 = OpTypeVector %f64 4
+
+%f32_0 = OpConstant %f32 0
+%f32_1 = OpConstant %f32 1
+%f32_2 = OpConstant %f32 2
+%f32_3 = OpConstant %f32 3
+%f32_4 = OpConstant %f32 4
+
+%s32_0 = OpConstant %s32 0
+%s32_1 = OpConstant %s32 1
+%s32_2 = OpConstant %s32 2
+%s32_3 = OpConstant %s32 3
+%s32_4 = OpConstant %s32 4
+%s32_m1 = OpConstant %s32 -1
+
+%u32_0 = OpConstant %u32 0
+%u32_1 = OpConstant %u32 1
+%u32_2 = OpConstant %u32 2
+%u32_3 = OpConstant %u32 3
+%u32_4 = OpConstant %u32 4
+
+%f64_0 = OpConstant %f64 0
+%f64_1 = OpConstant %f64 1
+%f64_2 = OpConstant %f64 2
+%f64_3 = OpConstant %f64 3
+%f64_4 = OpConstant %f64 4
+
+%s64_0 = OpConstant %s64 0
+%s64_1 = OpConstant %s64 1
+%s64_2 = OpConstant %s64 2
+%s64_3 = OpConstant %s64 3
+%s64_4 = OpConstant %s64 4
+%s64_m1 = OpConstant %s64 -1
+
+%u64_0 = OpConstant %u64 0
+%u64_1 = OpConstant %u64 1
+%u64_2 = OpConstant %u64 2
+%u64_3 = OpConstant %u64 3
+%u64_4 = OpConstant %u64 4
+
+%u32vec2_01 = OpConstantComposite %u32vec2 %u32_0 %u32_1
+%u32vec2_12 = OpConstantComposite %u32vec2 %u32_1 %u32_2
+%u32vec3_012 = OpConstantComposite %u32vec3 %u32_0 %u32_1 %u32_2
+%u32vec3_123 = OpConstantComposite %u32vec3 %u32_1 %u32_2 %u32_3
+%u32vec4_0123 = OpConstantComposite %u32vec4 %u32_0 %u32_1 %u32_2 %u32_3
+%u32vec4_1234 = OpConstantComposite %u32vec4 %u32_1 %u32_2 %u32_3 %u32_4
+
+%s32vec2_01 = OpConstantComposite %s32vec2 %s32_0 %s32_1
+%s32vec2_12 = OpConstantComposite %s32vec2 %s32_1 %s32_2
+%s32vec3_012 = OpConstantComposite %s32vec3 %s32_0 %s32_1 %s32_2
+%s32vec3_123 = OpConstantComposite %s32vec3 %s32_1 %s32_2 %s32_3
+%s32vec4_0123 = OpConstantComposite %s32vec4 %s32_0 %s32_1 %s32_2 %s32_3
+%s32vec4_1234 = OpConstantComposite %s32vec4 %s32_1 %s32_2 %s32_3 %s32_4
+
+%f32vec2_01 = OpConstantComposite %f32vec2 %f32_0 %f32_1
+%f32vec2_12 = OpConstantComposite %f32vec2 %f32_1 %f32_2
+%f32vec3_012 = OpConstantComposite %f32vec3 %f32_0 %f32_1 %f32_2
+%f32vec3_123 = OpConstantComposite %f32vec3 %f32_1 %f32_2 %f32_3
+%f32vec4_0123 = OpConstantComposite %f32vec4 %f32_0 %f32_1 %f32_2 %f32_3
+%f32vec4_1234 = OpConstantComposite %f32vec4 %f32_1 %f32_2 %f32_3 %f32_4
+
+%f64vec2_01 = OpConstantComposite %f64vec2 %f64_0 %f64_1
+%f64vec2_12 = OpConstantComposite %f64vec2 %f64_1 %f64_2
+%f64vec3_012 = OpConstantComposite %f64vec3 %f64_0 %f64_1 %f64_2
+%f64vec3_123 = OpConstantComposite %f64vec3 %f64_1 %f64_2 %f64_3
+%f64vec4_0123 = OpConstantComposite %f64vec4 %f64_0 %f64_1 %f64_2 %f64_3
+%f64vec4_1234 = OpConstantComposite %f64vec4 %f64_1 %f64_2 %f64_3 %f64_4
+
+%true = OpConstantTrue %bool
+%false = OpConstantFalse %bool
+
+%f32ptr_func = OpTypePointer Function %f32
+
+%main = OpFunction %void None %func
+%main_entry = OpLabel)";
+
+  const std::string after_body =
+R"(
+OpReturn
+OpFunctionEnd)";
+
+  return capabilities + capabilities_and_extensions +
+      after_extension_before_body + body + after_body;
+}
+
+std::string GenerateKernelCode(
+    const std::string& body,
+    const std::string& capabilities_and_extensions = "") {
+  const std::string capabilities =
+R"(
+OpCapability Addresses
+OpCapability Kernel
+OpCapability Linkage
+OpCapability GenericPointer
+OpCapability Int64
+OpCapability Float64)";
+
+  const std::string after_extension_before_body =
+R"(
+OpMemoryModel Physical32 OpenCL
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%bool = OpTypeBool
+%f32 = OpTypeFloat 32
+%u32 = OpTypeInt 32 0
+%f64 = OpTypeFloat 64
+%u64 = OpTypeInt 64 0
+%boolvec2 = OpTypeVector %bool 2
+%u32vec2 = OpTypeVector %u32 2
+%u64vec2 = OpTypeVector %u64 2
+%f32vec2 = OpTypeVector %f32 2
+%f64vec2 = OpTypeVector %f64 2
+%boolvec3 = OpTypeVector %bool 3
+%u32vec3 = OpTypeVector %u32 3
+%u64vec3 = OpTypeVector %u64 3
+%f32vec3 = OpTypeVector %f32 3
+%f64vec3 = OpTypeVector %f64 3
+%boolvec4 = OpTypeVector %bool 4
+%u32vec4 = OpTypeVector %u32 4
+%u64vec4 = OpTypeVector %u64 4
+%f32vec4 = OpTypeVector %f32 4
+%f64vec4 = OpTypeVector %f64 4
+
+%f32_0 = OpConstant %f32 0
+%f32_1 = OpConstant %f32 1
+%f32_2 = OpConstant %f32 2
+%f32_3 = OpConstant %f32 3
+%f32_4 = OpConstant %f32 4
+
+%u32_0 = OpConstant %u32 0
+%u32_1 = OpConstant %u32 1
+%u32_2 = OpConstant %u32 2
+%u32_3 = OpConstant %u32 3
+%u32_4 = OpConstant %u32 4
+
+%f64_0 = OpConstant %f64 0
+%f64_1 = OpConstant %f64 1
+%f64_2 = OpConstant %f64 2
+%f64_3 = OpConstant %f64 3
+%f64_4 = OpConstant %f64 4
+
+%u64_0 = OpConstant %u64 0
+%u64_1 = OpConstant %u64 1
+%u64_2 = OpConstant %u64 2
+%u64_3 = OpConstant %u64 3
+%u64_4 = OpConstant %u64 4
+
+%u32vec2_01 = OpConstantComposite %u32vec2 %u32_0 %u32_1
+%u32vec2_12 = OpConstantComposite %u32vec2 %u32_1 %u32_2
+%u32vec3_012 = OpConstantComposite %u32vec3 %u32_0 %u32_1 %u32_2
+%u32vec3_123 = OpConstantComposite %u32vec3 %u32_1 %u32_2 %u32_3
+%u32vec4_0123 = OpConstantComposite %u32vec4 %u32_0 %u32_1 %u32_2 %u32_3
+%u32vec4_1234 = OpConstantComposite %u32vec4 %u32_1 %u32_2 %u32_3 %u32_4
+
+%f32vec2_01 = OpConstantComposite %f32vec2 %f32_0 %f32_1
+%f32vec2_12 = OpConstantComposite %f32vec2 %f32_1 %f32_2
+%f32vec3_012 = OpConstantComposite %f32vec3 %f32_0 %f32_1 %f32_2
+%f32vec3_123 = OpConstantComposite %f32vec3 %f32_1 %f32_2 %f32_3
+%f32vec4_0123 = OpConstantComposite %f32vec4 %f32_0 %f32_1 %f32_2 %f32_3
+%f32vec4_1234 = OpConstantComposite %f32vec4 %f32_1 %f32_2 %f32_3 %f32_4
+
+%f64vec2_01 = OpConstantComposite %f64vec2 %f64_0 %f64_1
+%f64vec2_12 = OpConstantComposite %f64vec2 %f64_1 %f64_2
+%f64vec3_012 = OpConstantComposite %f64vec3 %f64_0 %f64_1 %f64_2
+%f64vec3_123 = OpConstantComposite %f64vec3 %f64_1 %f64_2 %f64_3
+%f64vec4_0123 = OpConstantComposite %f64vec4 %f64_0 %f64_1 %f64_2 %f64_3
+%f64vec4_1234 = OpConstantComposite %f64vec4 %f64_1 %f64_2 %f64_3 %f64_4
+
+%true = OpConstantTrue %bool
+%false = OpConstantFalse %bool
+
+%f32ptr_func = OpTypePointer Function %f32
+%u32ptr_func = OpTypePointer Function %u32
+%f32ptr_gen = OpTypePointer Generic %f32
+%f32ptr_inp = OpTypePointer Input %f32
+%f32ptr_wg = OpTypePointer Workgroup %f32
+%f32ptr_cwg = OpTypePointer CrossWorkgroup %f32
+
+%f32inp = OpVariable %f32ptr_inp Input
+
+%main = OpFunction %void None %func
+%main_entry = OpLabel)";
+
+  const std::string after_body =
+R"(
+OpReturn
+OpFunctionEnd)";
+
+  return capabilities + capabilities_and_extensions +
+      after_extension_before_body + body + after_body;
+}
+
+TEST_F(ValidateConversion, ConvertFToUSuccess) {
+  const std::string body = R"(
+%val1 = OpConvertFToU %u32 %f32_1
+%val2 = OpConvertFToU %u32 %f64_0
+%val3 = OpConvertFToU %u32vec2 %f32vec2_01
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateConversion, ConvertFToUWrongResultType) {
+  const std::string body = R"(
+%val = OpConvertFToU %s32 %f32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected unsigned int scalar or vector type as Result Type: ConvertFToU"));
+}
+
+TEST_F(ValidateConversion, ConvertFToUWrongInputType) {
+  const std::string body = R"(
+%val = OpConvertFToU %u32 %u32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected input to be float scalar or vector: ConvertFToU"));
+}
+
+TEST_F(ValidateConversion, ConvertFToUDifferentDimension) {
+  const std::string body = R"(
+%val = OpConvertFToU %u32 %f32vec2_01
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected input to have the same dimension as Result Type: ConvertFToU"));
+}
+
+TEST_F(ValidateConversion, ConvertFToSSuccess) {
+  const std::string body = R"(
+%val1 = OpConvertFToS %s32 %f32_1
+%val2 = OpConvertFToS %u32 %f64_0
+%val3 = OpConvertFToS %s32vec2 %f32vec2_01
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateConversion, ConvertFToSWrongResultType) {
+  const std::string body = R"(
+%val = OpConvertFToS %bool %f32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected int scalar or vector type as Result Type: ConvertFToS"));
+}
+
+TEST_F(ValidateConversion, ConvertFToSWrongInputType) {
+  const std::string body = R"(
+%val = OpConvertFToS %s32 %u32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected input to be float scalar or vector: ConvertFToS"));
+}
+
+TEST_F(ValidateConversion, ConvertFToSDifferentDimension) {
+  const std::string body = R"(
+%val = OpConvertFToS %u32 %f32vec2_01
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected input to have the same dimension as Result Type: ConvertFToS"));
+}
+
+TEST_F(ValidateConversion, ConvertSToFSuccess) {
+  const std::string body = R"(
+%val1 = OpConvertSToF %f32 %u32_1
+%val2 = OpConvertSToF %f32 %s64_0
+%val3 = OpConvertSToF %f32vec2 %s32vec2_01
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateConversion, ConvertSToFWrongResultType) {
+  const std::string body = R"(
+%val = OpConvertSToF %u32 %s32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected float scalar or vector type as Result Type: ConvertSToF"));
+}
+
+TEST_F(ValidateConversion, ConvertSToFWrongInputType) {
+  const std::string body = R"(
+%val = OpConvertSToF %f32 %f32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected input to be int scalar or vector: ConvertSToF"));
+}
+
+TEST_F(ValidateConversion, ConvertSToFDifferentDimension) {
+  const std::string body = R"(
+%val = OpConvertSToF %f32 %u32vec2_01
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected input to have the same dimension as Result Type: ConvertSToF"));
+}
+
+TEST_F(ValidateConversion, UConvertSuccess) {
+  const std::string body = R"(
+%val1 = OpUConvert %u32 %u64_1
+%val2 = OpUConvert %u64 %s32_0
+%val3 = OpUConvert %u64vec2 %s32vec2_01
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+#if 0
+// TODO(atgoo@github.com) Reenable the test once the check is reenabled.
+TEST_F(ValidateConversion, UConvertWrongResultType) {
+  const std::string body = R"(
+%val = OpUConvert %s32 %s32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected unsigned int scalar or vector type as Result Type: UConvert"));
+}
+
+TEST_F(ValidateConversion, UConvertWrongInputType) {
+  const std::string body = R"(
+%val = OpUConvert %u32 %f32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected input to be int scalar or vector: UConvert"));
+}
+
+TEST_F(ValidateConversion, UConvertDifferentDimension) {
+  const std::string body = R"(
+%val = OpUConvert %u32 %u32vec2_01
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected input to have the same dimension as Result Type: UConvert"));
+}
+
+TEST_F(ValidateConversion, UConvertSameBitWidth) {
+  const std::string body = R"(
+%val = OpUConvert %u32 %s32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected input to have different bit width from Result Type: UConvert"));
+}
+#endif
+
+TEST_F(ValidateConversion, SConvertSuccess) {
+  const std::string body = R"(
+%val1 = OpSConvert %s32 %u64_1
+%val2 = OpSConvert %s64 %s32_0
+%val3 = OpSConvert %u64vec2 %s32vec2_01
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateConversion, SConvertWrongResultType) {
+  const std::string body = R"(
+%val = OpSConvert %f32 %s32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected int scalar or vector type as Result Type: SConvert"));
+}
+
+TEST_F(ValidateConversion, SConvertWrongInputType) {
+  const std::string body = R"(
+%val = OpSConvert %u32 %f32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected input to be int scalar or vector: SConvert"));
+}
+
+TEST_F(ValidateConversion, SConvertDifferentDimension) {
+  const std::string body = R"(
+%val = OpSConvert %s32 %u32vec2_01
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected input to have the same dimension as Result Type: SConvert"));
+}
+
+TEST_F(ValidateConversion, SConvertSameBitWidth) {
+  const std::string body = R"(
+%val = OpSConvert %u32 %s32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected input to have different bit width from Result Type: SConvert"));
+}
+
+TEST_F(ValidateConversion, FConvertSuccess) {
+  const std::string body = R"(
+%val1 = OpFConvert %f32 %f64_1
+%val2 = OpFConvert %f64 %f32_0
+%val3 = OpFConvert %f64vec2 %f32vec2_01
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateConversion, FConvertWrongResultType) {
+  const std::string body = R"(
+%val = OpFConvert %u32 %f32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected float scalar or vector type as Result Type: FConvert"));
+}
+
+TEST_F(ValidateConversion, FConvertWrongInputType) {
+  const std::string body = R"(
+%val = OpFConvert %f32 %u64_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected input to be float scalar or vector: FConvert"));
+}
+
+TEST_F(ValidateConversion, FConvertDifferentDimension) {
+  const std::string body = R"(
+%val = OpFConvert %f64 %f32vec2_01
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected input to have the same dimension as Result Type: FConvert"));
+}
+
+TEST_F(ValidateConversion, FConvertSameBitWidth) {
+  const std::string body = R"(
+%val = OpFConvert %f32 %f32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected input to have different bit width from Result Type: FConvert"));
+}
+
+TEST_F(ValidateConversion, QuantizeToF16Success) {
+  const std::string body = R"(
+%val1 = OpQuantizeToF16 %f32 %f32_1
+%val2 = OpQuantizeToF16 %f32 %f32_0
+%val3 = OpQuantizeToF16 %f32vec2 %f32vec2_01
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateConversion, QuantizeToF16WrongResultType) {
+  const std::string body = R"(
+%val = OpQuantizeToF16 %u32 %f32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected 32-bit float scalar or vector type as Result Type: "
+      "QuantizeToF16"));
+}
+
+TEST_F(ValidateConversion, QuantizeToF16WrongResultTypeBitWidth) {
+  const std::string body = R"(
+%val = OpQuantizeToF16 %u64 %f64_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected 32-bit float scalar or vector type as Result Type: "
+      "QuantizeToF16"));
+}
+
+TEST_F(ValidateConversion, QuantizeToF16WrongInputType) {
+  const std::string body = R"(
+%val = OpQuantizeToF16 %f32 %f64_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected input type to be equal to Result Type: QuantizeToF16"));
+}
+
+TEST_F(ValidateConversion, ConvertPtrToUSuccess) {
+  const std::string body = R"(
+%ptr = OpVariable %f32ptr_func Function
+%val1 = OpConvertPtrToU %u32 %ptr
+%val2 = OpConvertPtrToU %u64 %ptr
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateConversion, ConvertPtrToUWrongResultType) {
+  const std::string body = R"(
+%ptr = OpVariable %f32ptr_func Function
+%val = OpConvertPtrToU %f32 %ptr
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected unsigned int scalar type as Result Type: "
+      "ConvertPtrToU"));
+}
+
+TEST_F(ValidateConversion, ConvertPtrToUNotPointer) {
+  const std::string body = R"(
+%val = OpConvertPtrToU %u32 %f32_1
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected input to be a pointer: ConvertPtrToU"));
+}
+
+TEST_F(ValidateConversion, SatConvertSToUSuccess) {
+  const std::string body = R"(
+%val1 = OpSatConvertSToU %u32 %u64_2
+%val2 = OpSatConvertSToU %u64 %u32_1
+%val3 = OpSatConvertSToU %u64vec2 %u32vec2_12
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateConversion, SatConvertSToUWrongResultType) {
+  const std::string body = R"(
+%val = OpSatConvertSToU %f32 %u32_1
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected int scalar or vector type as Result Type: "
+      "SatConvertSToU"));
+}
+
+TEST_F(ValidateConversion, SatConvertSToUWrongInputType) {
+  const std::string body = R"(
+%val = OpSatConvertSToU %u32 %f32_1
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected int scalar or vector as input: SatConvertSToU"));
+}
+
+TEST_F(ValidateConversion, SatConvertSToUDifferentDimension) {
+  const std::string body = R"(
+%val = OpSatConvertSToU %u32 %u32vec2_12
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected input to have the same dimension as Result Type: "
+      "SatConvertSToU"));
+}
+
+TEST_F(ValidateConversion, ConvertUToPtrSuccess) {
+  const std::string body = R"(
+%val1 = OpConvertUToPtr %f32ptr_func %u32_1
+%val2 = OpConvertUToPtr %f32ptr_func %u64_1
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateConversion, ConvertUToPtrWrongResultType) {
+  const std::string body = R"(
+%val = OpConvertUToPtr %f32 %u32_1
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected Result Type to be a pointer: ConvertUToPtr"));
+}
+
+TEST_F(ValidateConversion, ConvertUToPtrNotInt) {
+  const std::string body = R"(
+%val = OpConvertUToPtr %f32ptr_func %f32_1
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected int scalar as input: ConvertUToPtr"));
+}
+
+TEST_F(ValidateConversion, ConvertUToPtrNotIntScalar) {
+  const std::string body = R"(
+%val = OpConvertUToPtr %f32ptr_func %u32vec2_12
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected int scalar as input: ConvertUToPtr"));
+}
+
+TEST_F(ValidateConversion, PtrCastToGenericSuccess) {
+  const std::string body = R"(
+%ptr_func = OpVariable %f32ptr_func Function
+%val = OpPtrCastToGeneric %f32ptr_gen %ptr_func
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateConversion, PtrCastToGenericWrongResultType) {
+  const std::string body = R"(
+%ptr_func = OpVariable %f32ptr_func Function
+%val = OpPtrCastToGeneric %f32 %ptr_func
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected Result Type to be a pointer: PtrCastToGeneric"));
+}
+
+TEST_F(ValidateConversion, PtrCastToGenericWrongResultStorageClass) {
+  const std::string body = R"(
+%ptr_func = OpVariable %f32ptr_func Function
+%val = OpPtrCastToGeneric %f32ptr_func %ptr_func
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected Result Type to have storage class Generic: PtrCastToGeneric"));
+}
+
+TEST_F(ValidateConversion, PtrCastToGenericWrongInputType) {
+  const std::string body = R"(
+%ptr_func = OpVariable %f32ptr_func Function
+%val = OpPtrCastToGeneric %f32ptr_gen %f32
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected input to be a pointer: PtrCastToGeneric"));
+}
+
+TEST_F(ValidateConversion, PtrCastToGenericWrongInputStorageClass) {
+  const std::string body = R"(
+%val = OpPtrCastToGeneric %f32ptr_gen %f32inp
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected input to have storage class Workgroup, "
+      "CrossWorkgroup or Function: PtrCastToGeneric"));
+}
+
+TEST_F(ValidateConversion, PtrCastToGenericPointToDifferentType) {
+  const std::string body = R"(
+%ptr_func = OpVariable %u32ptr_func Function
+%val = OpPtrCastToGeneric %f32ptr_gen %ptr_func
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected input and Result Type to point to the same type: "
+      "PtrCastToGeneric"));
+}
+
+TEST_F(ValidateConversion, GenericCastToPtrSuccess) {
+  const std::string body = R"(
+%ptr_func = OpVariable %f32ptr_func Function
+%ptr_gen = OpPtrCastToGeneric %f32ptr_gen %ptr_func
+%ptr_func2 = OpGenericCastToPtr %f32ptr_func %ptr_gen
+%ptr_wg = OpGenericCastToPtr %f32ptr_wg %ptr_gen
+%ptr_cwg = OpGenericCastToPtr %f32ptr_cwg %ptr_gen
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateConversion, GenericCastToPtrWrongResultType) {
+  const std::string body = R"(
+%ptr_func = OpVariable %f32ptr_func Function
+%ptr_gen = OpPtrCastToGeneric %f32ptr_gen %ptr_func
+%ptr_func2 = OpGenericCastToPtr %f32 %ptr_gen
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected Result Type to be a pointer: GenericCastToPtr"));
+}
+
+TEST_F(ValidateConversion, GenericCastToPtrWrongResultStorageClass) {
+  const std::string body = R"(
+%ptr_func = OpVariable %f32ptr_func Function
+%ptr_gen = OpPtrCastToGeneric %f32ptr_gen %ptr_func
+%ptr_func2 = OpGenericCastToPtr %f32ptr_gen %ptr_gen
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected Result Type to have storage class Workgroup, "
+      "CrossWorkgroup or Function: GenericCastToPtr"));
+}
+
+TEST_F(ValidateConversion, GenericCastToPtrWrongInputType) {
+  const std::string body = R"(
+%ptr_func = OpVariable %f32ptr_func Function
+%ptr_gen = OpPtrCastToGeneric %f32ptr_gen %ptr_func
+%ptr_func2 = OpGenericCastToPtr %f32ptr_func %f32_1
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected input to be a pointer: GenericCastToPtr"));
+}
+
+TEST_F(ValidateConversion, GenericCastToPtrWrongInputStorageClass) {
+  const std::string body = R"(
+%ptr_func = OpVariable %f32ptr_func Function
+%ptr_func2 = OpGenericCastToPtr %f32ptr_func %ptr_func
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected input to have storage class Generic: "
+      "GenericCastToPtr"));
+}
+
+TEST_F(ValidateConversion, GenericCastToPtrPointToDifferentType) {
+  const std::string body = R"(
+%ptr_func = OpVariable %f32ptr_func Function
+%ptr_gen = OpPtrCastToGeneric %f32ptr_gen %ptr_func
+%ptr_func2 = OpGenericCastToPtr %u32ptr_func %ptr_gen
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected input and Result Type to point to the same type: "
+      "GenericCastToPtr"));
+}
+
+TEST_F(ValidateConversion, GenericCastToPtrExplicitSuccess) {
+  const std::string body = R"(
+%ptr_func = OpVariable %f32ptr_func Function
+%ptr_gen = OpPtrCastToGeneric %f32ptr_gen %ptr_func
+%ptr_func2 = OpGenericCastToPtrExplicit %f32ptr_func %ptr_gen Function
+%ptr_wg = OpGenericCastToPtrExplicit %f32ptr_wg %ptr_gen Workgroup
+%ptr_cwg = OpGenericCastToPtrExplicit %f32ptr_cwg %ptr_gen CrossWorkgroup
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateConversion, GenericCastToPtrExplicitWrongResultType) {
+  const std::string body = R"(
+%ptr_func = OpVariable %f32ptr_func Function
+%ptr_gen = OpPtrCastToGeneric %f32ptr_gen %ptr_func
+%ptr_func2 = OpGenericCastToPtrExplicit %f32 %ptr_gen Function
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected Result Type to be a pointer: GenericCastToPtrExplicit"));
+}
+
+TEST_F(ValidateConversion, GenericCastToPtrExplicitResultStorageClassDiffers) {
+  const std::string body = R"(
+%ptr_func = OpVariable %f32ptr_func Function
+%ptr_gen = OpPtrCastToGeneric %f32ptr_gen %ptr_func
+%ptr_func2 = OpGenericCastToPtrExplicit %f32ptr_func %ptr_gen Workgroup
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected Result Type to be of target storage class: "
+      "GenericCastToPtrExplicit"));
+}
+
+TEST_F(ValidateConversion, GenericCastToPtrExplicitWrongResultStorageClass) {
+  const std::string body = R"(
+%ptr_func = OpVariable %f32ptr_func Function
+%ptr_gen = OpPtrCastToGeneric %f32ptr_gen %ptr_func
+%ptr_func2 = OpGenericCastToPtrExplicit %f32ptr_gen %ptr_gen Generic
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected target storage class to be Workgroup, "
+      "CrossWorkgroup or Function: GenericCastToPtrExplicit"));
+}
+
+TEST_F(ValidateConversion, GenericCastToPtrExplicitWrongInputType) {
+  const std::string body = R"(
+%ptr_func = OpVariable %f32ptr_func Function
+%ptr_gen = OpPtrCastToGeneric %f32ptr_gen %ptr_func
+%ptr_func2 = OpGenericCastToPtrExplicit %f32ptr_func %f32_1 Function
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected input to be a pointer: GenericCastToPtrExplicit"));
+}
+
+TEST_F(ValidateConversion, GenericCastToPtrExplicitWrongInputStorageClass) {
+  const std::string body = R"(
+%ptr_func = OpVariable %f32ptr_func Function
+%ptr_func2 = OpGenericCastToPtrExplicit %f32ptr_func %ptr_func Function
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected input to have storage class Generic: "
+      "GenericCastToPtrExplicit"));
+}
+
+TEST_F(ValidateConversion, GenericCastToPtrExplicitPointToDifferentType) {
+  const std::string body = R"(
+%ptr_func = OpVariable %f32ptr_func Function
+%ptr_gen = OpPtrCastToGeneric %f32ptr_gen %ptr_func
+%ptr_func2 = OpGenericCastToPtrExplicit %u32ptr_func %ptr_gen Function
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected input and Result Type to point to the same type: "
+      "GenericCastToPtrExplicit"));
+}
+
+TEST_F(ValidateConversion, BitcastSuccess) {
+  const std::string body = R"(
+%ptr = OpVariable %f32ptr_func Function
+%val1 = OpBitcast %u32 %ptr
+%val2 = OpBitcast %u64 %ptr
+%val3 = OpBitcast %f32ptr_func %u32_1
+%val4 = OpBitcast %f32ptr_wg %u64_1
+%val5 = OpBitcast %f32 %u32_1
+%val6 = OpBitcast %f32vec2 %u32vec2_12
+%val7 = OpBitcast %f32vec2 %u64_1
+%val8 = OpBitcast %f64 %u32vec2_12
+%val9 = OpBitcast %f32vec4 %f64vec2_12
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateConversion, BitcastInputHasNoType) {
+  const std::string body = R"(
+%val = OpBitcast %u32 %f32
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected input to have a type: Bitcast"));
+}
+
+TEST_F(ValidateConversion, BitcastWrongResultType) {
+  const std::string body = R"(
+%val = OpBitcast %bool %f32_1
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected Result Type to be a pointer or int or float vector "
+      "or scalar type: Bitcast"));
+}
+
+TEST_F(ValidateConversion, BitcastWrongInputType) {
+  const std::string body = R"(
+%val = OpBitcast %u32 %true
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected input to be a pointer or int or float vector "
+      "or scalar: Bitcast"));
+}
+
+TEST_F(ValidateConversion, BitcastPtrWrongInputType) {
+  const std::string body = R"(
+%val = OpBitcast %u32ptr_func %f32_1
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected input to be a pointer or int scalar if Result Type "
+      "is pointer: Bitcast"));
+}
+
+TEST_F(ValidateConversion, BitcastPtrWrongResultType) {
+  const std::string body = R"(
+%val = OpBitcast %f32 %f32inp
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Pointer can only be converted to another pointer or int scalar: "
+      "Bitcast"));
+}
+
+TEST_F(ValidateConversion, BitcastDifferentTotalBitWidth) {
+  const std::string body = R"(
+%val = OpBitcast %f32 %u64_1
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected input to have the same total bit width as Result Type: "
+      "Bitcast"));
+}
+
+}  // anonymous namespace
diff --git a/test/val/val_extensions_test.cpp b/test/val/val_extensions_test.cpp
index dd66876..dad4772 100644
--- a/test/val/val_extensions_test.cpp
+++ b/test/val/val_extensions_test.cpp
@@ -43,24 +43,33 @@
 }
 
 INSTANTIATE_TEST_CASE_P(ExpectSuccess, ValidateKnownExtensions, Values(
+    // Match the order as published on the SPIR-V Registry.
     "SPV_AMD_shader_explicit_vertex_parameter",
     "SPV_AMD_shader_trinary_minmax",
     "SPV_AMD_gcn_shader",
+    "SPV_KHR_shader_ballot",
     "SPV_AMD_shader_ballot",
     "SPV_AMD_gpu_shader_half_float",
-    "SPV_AMD_texture_gather_bias_lod",
-    "SPV_AMD_gpu_shader_int16",
-    "SPV_KHR_shader_ballot",
     "SPV_KHR_shader_draw_parameters",
     "SPV_KHR_subgroup_vote",
     "SPV_KHR_16bit_storage",
     "SPV_KHR_device_group",
     "SPV_KHR_multiview",
-    "SPV_NV_sample_mask_override_coverage",
-    "SPV_NV_geometry_shader_passthrough",
+    "SPV_NVX_multiview_per_view_attributes",
     "SPV_NV_viewport_array2",
     "SPV_NV_stereo_view_rendering",
-    "SPV_NVX_multiview_per_view_attributes"
+    "SPV_NV_sample_mask_override_coverage",
+    "SPV_NV_geometry_shader_passthrough",
+    "SPV_AMD_texture_gather_bias_lod",
+    "SPV_KHR_storage_buffer_storage_class",
+    "SPV_KHR_variable_pointers",
+    "SPV_AMD_gpu_shader_int16",
+    "SPV_KHR_post_depth_coverage",
+    "SPV_KHR_shader_atomic_counter_ops",
+    "SPV_EXT_shader_stencil_export",
+    "SPV_EXT_shader_viewport_index_layer",
+    "SPV_AMD_shader_image_load_store_lod",
+    "SPV_AMD_shader_fragment_mask"
     ));
 
 INSTANTIATE_TEST_CASE_P(FailSilently, ValidateUnknownExtensions, Values(
diff --git a/test/val/val_id_test.cpp b/test/val/val_id_test.cpp
index e1bd577..9cd32f9 100644
--- a/test/val/val_id_test.cpp
+++ b/test/val/val_id_test.cpp
@@ -218,6 +218,20 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
+TEST_F(ValidateIdWithMessage, OpDecorationGroupBad) {
+  string spirv = kGLSL450MemoryModel + R"(
+%1 = OpDecorationGroup
+     OpDecorate %1 Uniform
+     OpDecorate %1 GLSLShared
+     OpMemberDecorate %1 0 Constant
+    )";
+  CompileSuccessfully(spirv.c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Result id of OpDecorationGroup can only "
+                        "be targeted by OpName, OpGroupDecorate, "
+                        "OpDecorate, and OpGroupMemberDecorate"));
+}
 TEST_F(ValidateIdWithMessage, OpGroupDecorateDecorationGroupBad) {
   string spirv = R"(
     OpCapability Shader
@@ -330,10 +344,11 @@
   string spirv = kGLSL450MemoryModel + R"(
      OpEntryPoint GLCompute %3 ""
 %1 = OpTypeInt 32 0
+%ret = OpConstant %1 0
 %2 = OpTypeFunction %1
 %3 = OpFunction %1 None %2
 %4 = OpLabel
-     OpReturn
+     OpReturnValue %ret
      OpFunctionEnd)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
@@ -1878,7 +1893,9 @@
 
 // Without the VariablePointers Capability, OpLoad will not allow loading
 // through a variable pointer.
-TEST_F(ValidateIdWithMessage, OpLoadVarPtrOpSelectBad) {
+// Disabled since using OpSelect with pointers without VariablePointers will
+// fail LogicalsPass.
+TEST_F(ValidateIdWithMessage, DISABLED_OpLoadVarPtrOpSelectBad) {
   std::string result_strategy = R"(
     %isneg     = OpSLessThan %bool %i %zero
     %varptr    = OpSelect %f32ptr %isneg %ptr1 %ptr2
@@ -2002,7 +2019,8 @@
               HasSubstr("ID 8 has not been defined"));
 }
 
-TEST_F(ValidateIdWithMessage, OpLoadLogicalPointerBad) {
+// Disabled as bitcasting type to object is now not valid.
+TEST_F(ValidateIdWithMessage, DISABLED_OpLoadLogicalPointerBad) {
   string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 32 0
@@ -2062,7 +2080,8 @@
               HasSubstr("OpStore Pointer <id> '3' is not a logical pointer."));
 }
 
-TEST_F(ValidateIdWithMessage, OpStoreLogicalPointerBad) {
+// Disabled as bitcasting type to object is now not valid.
+TEST_F(ValidateIdWithMessage, DISABLED_OpStoreLogicalPointerBad) {
   string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 32 0
@@ -2086,7 +2105,9 @@
 
 // Without the VariablePointer Capability, OpStore should may not store
 // through a variable pointer.
-TEST_F(ValidateIdWithMessage, OpStoreVarPtrBad) {
+// Disabled since using OpSelect with pointers without VariablePointers will
+// fail LogicalsPass.
+TEST_F(ValidateIdWithMessage, DISABLED_OpStoreVarPtrBad) {
   std::string result_strategy = R"(
     %isneg     = OpSLessThan %bool %i %zero
     %varptr    = OpSelect %f32ptr %isneg %ptr1 %ptr2
@@ -2932,7 +2953,7 @@
 %4 = OpTypeFunction %1 %2 %2
 %5 = OpFunction %2 None %4
 %6 = OpLabel
-     OpReturn
+     OpReturnValue %3
      OpFunctionEnd)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
@@ -2940,6 +2961,22 @@
               HasSubstr("OpFunction Result Type <id> '2' does not match the "
                         "Function Type <id> '2's return type."));
 }
+TEST_F(ValidateIdWithMessage, OpReturnValueTypeBad) {
+  string spirv = kGLSL450MemoryModel + R"(
+%1 = OpTypeInt 32 0
+%2 = OpTypeFloat 32
+%3 = OpConstant %2 0
+%4 = OpTypeFunction %1
+%5 = OpFunction %1 None %4
+%6 = OpLabel
+     OpReturnValue %3
+     OpFunctionEnd)";
+  CompileSuccessfully(spirv.c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("OpReturnValue Value <id> '3's type does not match "
+                        "OpFunction's return type."));
+}
 TEST_F(ValidateIdWithMessage, OpFunctionFunctionTypeBad) {
   string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
@@ -3134,7 +3171,9 @@
 // However, the OpSelect validation does not catch this today. Therefore, it is
 // caught by the OpSampledImage validation. If the OpSelect validation code is
 // updated, the error message for this test may change.
-TEST_F(ValidateIdWithMessage, OpSampledImageUsedInOpSelectBad) {
+//
+// Disabled since OpSelect catches this now.
+TEST_F(ValidateIdWithMessage, DISABLED_OpSampledImageUsedInOpSelectBad) {
   string spirv = kGLSL450MemoryModel + sampledImageSetup + R"(
 %smpld_img  = OpSampledImage %sampled_image_type %image_inst %sampler_inst
 %select_img = OpSelect %sampled_image_type %spec_true %smpld_img %smpld_img
@@ -3586,7 +3625,243 @@
 // TODO: OpSatConvertUToS
 // TODO: OpVectorExtractDynamic
 // TODO: OpVectorInsertDynamic
-// TODO: OpVectorShuffle
+
+TEST_F(ValidateIdWithMessage, OpVectorShuffleIntGood) {
+  string spirv = kGLSL450MemoryModel + R"(
+%int = OpTypeInt 32 0
+%ivec3 = OpTypeVector %int 3
+%ivec4 = OpTypeVector %int 4
+%ptr_ivec3 = OpTypePointer Function %ivec3
+%undef = OpUndef %ivec4
+%int_42 = OpConstant %int 42
+%int_0 = OpConstant %int 0
+%int_2 = OpConstant %int 2
+%1 = OpConstantComposite %ivec3 %int_42 %int_0 %int_2
+%2 = OpTypeFunction %ivec3
+%3 = OpFunction %ivec3 None %2
+%4 = OpLabel
+%var = OpVariable %ptr_ivec3 Function %1
+%5 = OpLoad %ivec3 %var
+%6 = OpVectorShuffle %ivec3 %5 %undef 2 1 0
+     OpReturnValue %6
+     OpFunctionEnd)";
+  CompileSuccessfully(spirv.c_str());
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateIdWithMessage, OpVectorShuffleFloatGood) {
+  string spirv = kGLSL450MemoryModel + R"(
+%float = OpTypeFloat 32
+%vec2 = OpTypeVector %float 2
+%vec3 = OpTypeVector %float 3
+%vec4 = OpTypeVector %float 4
+%ptr_vec2 = OpTypePointer Function %vec2
+%ptr_vec3 = OpTypePointer Function %vec3
+%float_1 = OpConstant %float 1
+%float_2 = OpConstant %float 2
+%1 = OpConstantComposite %vec2 %float_2 %float_1
+%2 = OpConstantComposite %vec3 %float_1 %float_2 %float_2
+%3 = OpTypeFunction %vec4
+%4 = OpFunction %vec4 None %3
+%5 = OpLabel
+%var = OpVariable %ptr_vec2 Function %1
+%var2 = OpVariable %ptr_vec3 Function %2
+%6 = OpLoad %vec2 %var
+%7 = OpLoad %vec3 %var2
+%8 = OpVectorShuffle %vec4 %6 %7 4 3 1 0xffffffff
+     OpReturnValue %8
+     OpFunctionEnd)";
+  CompileSuccessfully(spirv.c_str());
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateIdWithMessage, OpVectorShuffleScalarResultType) {
+  string spirv = kGLSL450MemoryModel + R"(
+%float = OpTypeFloat 32
+%vec2 = OpTypeVector %float 2
+%ptr_vec2 = OpTypePointer Function %vec2
+%float_1 = OpConstant %float 1
+%float_2 = OpConstant %float 2
+%1 = OpConstantComposite %vec2 %float_2 %float_1
+%2 = OpTypeFunction %float
+%3 = OpFunction %float None %2
+%4 = OpLabel
+%var = OpVariable %ptr_vec2 Function %1
+%5 = OpLoad %vec2 %var
+%6 = OpVectorShuffle %float %5 %5 0
+     OpReturnValue %6
+     OpFunctionEnd)";
+  CompileSuccessfully(spirv.c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Result Type of OpVectorShuffle must be OpTypeVector."));
+}
+
+TEST_F(ValidateIdWithMessage, OpVectorShuffleComponentCount) {
+  string spirv = kGLSL450MemoryModel + R"(
+%int = OpTypeInt 32 0
+%ivec3 = OpTypeVector %int 3
+%ptr_ivec3 = OpTypePointer Function %ivec3
+%int_42 = OpConstant %int 42
+%int_0 = OpConstant %int 0
+%int_2 = OpConstant %int 2
+%1 = OpConstantComposite %ivec3 %int_42 %int_0 %int_2
+%2 = OpTypeFunction %ivec3
+%3 = OpFunction %ivec3 None %2
+%4 = OpLabel
+%var = OpVariable %ptr_ivec3 Function %1
+%5 = OpLoad %ivec3 %var
+%6 = OpVectorShuffle %ivec3 %5 %5 0 1
+     OpReturnValue %6
+     OpFunctionEnd)";
+  CompileSuccessfully(spirv.c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("OpVectorShuffle component literals count does not match "
+                "Result Type <id> '2's vector component count."));
+}
+
+TEST_F(ValidateIdWithMessage, OpVectorShuffleVector1Type) {
+  string spirv = kGLSL450MemoryModel + R"(
+%int = OpTypeInt 32 0
+%ivec2 = OpTypeVector %int 2
+%ptr_int = OpTypePointer Function %int
+%undef = OpUndef %ivec2
+%int_42 = OpConstant %int 42
+%2 = OpTypeFunction %ivec2
+%3 = OpFunction %ivec2 None %2
+%4 = OpLabel
+%var = OpVariable %ptr_int Function %int_42
+%5 = OpLoad %int %var
+%6 = OpVectorShuffle %ivec2 %5 %undef 0 0
+     OpReturnValue %6
+     OpFunctionEnd)";
+  CompileSuccessfully(spirv.c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("The type of Vector 1 must be OpTypeVector."));
+}
+
+TEST_F(ValidateIdWithMessage, OpVectorShuffleVector2Type) {
+  string spirv = kGLSL450MemoryModel + R"(
+%int = OpTypeInt 32 0
+%ivec2 = OpTypeVector %int 2
+%ptr_ivec2 = OpTypePointer Function %ivec2
+%undef = OpUndef %int
+%int_42 = OpConstant %int 42
+%1 = OpConstantComposite %ivec2 %int_42 %int_42
+%2 = OpTypeFunction %ivec2
+%3 = OpFunction %ivec2 None %2
+%4 = OpLabel
+%var = OpVariable %ptr_ivec2 Function %1
+%5 = OpLoad %ivec2 %var
+%6 = OpVectorShuffle %ivec2 %5 %undef 0 1
+     OpReturnValue %6
+     OpFunctionEnd)";
+  CompileSuccessfully(spirv.c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("The type of Vector 2 must be OpTypeVector."));
+}
+
+TEST_F(ValidateIdWithMessage, OpVectorShuffleVector1ComponentType) {
+  string spirv = kGLSL450MemoryModel + R"(
+%int = OpTypeInt 32 0
+%ivec3 = OpTypeVector %int 3
+%ptr_ivec3 = OpTypePointer Function %ivec3
+%int_42 = OpConstant %int 42
+%int_0 = OpConstant %int 0
+%int_2 = OpConstant %int 2
+%float = OpTypeFloat 32
+%vec3 = OpTypeVector %float 3
+%vec4 = OpTypeVector %float 4
+%ptr_vec3 = OpTypePointer Function %vec3
+%float_1 = OpConstant %float 1
+%float_2 = OpConstant %float 2
+%1 = OpConstantComposite %ivec3 %int_42 %int_0 %int_2
+%2 = OpConstantComposite %vec3 %float_1 %float_2 %float_2
+%3 = OpTypeFunction %vec4
+%4 = OpFunction %vec4 None %3
+%5 = OpLabel
+%var = OpVariable %ptr_ivec3 Function %1
+%var2 = OpVariable %ptr_vec3 Function %2
+%6 = OpLoad %ivec3 %var
+%7 = OpLoad %vec3 %var2
+%8 = OpVectorShuffle %vec4 %6 %7 4 3 1 0
+     OpReturnValue %8
+     OpFunctionEnd)";
+  CompileSuccessfully(spirv.c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("The Component Type of Vector 1 must be the same as "
+                "ResultType."));
+}
+
+TEST_F(ValidateIdWithMessage, OpVectorShuffleVector2ComponentType) {
+  string spirv = kGLSL450MemoryModel + R"(
+%int = OpTypeInt 32 0
+%ivec3 = OpTypeVector %int 3
+%ptr_ivec3 = OpTypePointer Function %ivec3
+%int_42 = OpConstant %int 42
+%int_0 = OpConstant %int 0
+%int_2 = OpConstant %int 2
+%float = OpTypeFloat 32
+%vec3 = OpTypeVector %float 3
+%vec4 = OpTypeVector %float 4
+%ptr_vec3 = OpTypePointer Function %vec3
+%float_1 = OpConstant %float 1
+%float_2 = OpConstant %float 2
+%1 = OpConstantComposite %ivec3 %int_42 %int_0 %int_2
+%2 = OpConstantComposite %vec3 %float_1 %float_2 %float_2
+%3 = OpTypeFunction %vec4
+%4 = OpFunction %vec4 None %3
+%5 = OpLabel
+%var = OpVariable %ptr_ivec3 Function %1
+%var2 = OpVariable %ptr_vec3 Function %2
+%6 = OpLoad %vec3 %var2
+%7 = OpLoad %ivec3 %var
+%8 = OpVectorShuffle %vec4 %6 %7 4 3 1 0
+     OpReturnValue %8
+     OpFunctionEnd)";
+  CompileSuccessfully(spirv.c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("The Component Type of Vector 2 must be the same as "
+                "ResultType."));
+}
+
+TEST_F(ValidateIdWithMessage, OpVectorShuffleLiterals) {
+  string spirv = kGLSL450MemoryModel + R"(
+%float = OpTypeFloat 32
+%vec2 = OpTypeVector %float 2
+%vec3 = OpTypeVector %float 3
+%vec4 = OpTypeVector %float 4
+%ptr_vec2 = OpTypePointer Function %vec2
+%ptr_vec3 = OpTypePointer Function %vec3
+%float_1 = OpConstant %float 1
+%float_2 = OpConstant %float 2
+%1 = OpConstantComposite %vec2 %float_2 %float_1
+%2 = OpConstantComposite %vec3 %float_1 %float_2 %float_2
+%3 = OpTypeFunction %vec4
+%4 = OpFunction %vec4 None %3
+%5 = OpLabel
+%var = OpVariable %ptr_vec2 Function %1
+%var2 = OpVariable %ptr_vec3 Function %2
+%6 = OpLoad %vec2 %var
+%7 = OpLoad %vec3 %var2
+%8 = OpVectorShuffle %vec4 %6 %7 0 5 2 6
+     OpReturnValue %8
+     OpFunctionEnd)";
+  CompileSuccessfully(spirv.c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Component literal value 5 is greater than 4."));
+}
+
 // TODO: OpCompositeConstruct
 // TODO: OpCompositeExtract
 // TODO: OpCompositeInsert
@@ -3820,7 +4095,9 @@
 
 // Without the VariablePointer Capability, the return value of a function is
 // *not* allowed to be a pointer.
-TEST_F(ValidateIdWithMessage, OpReturnValueVarPtrBad) {
+// Disabled since using OpSelect with pointers without VariablePointers will
+// fail LogicalsPass.
+TEST_F(ValidateIdWithMessage, DISABLED_OpReturnValueVarPtrBad) {
   std::ostringstream spirv;
   createVariablePointerSpirvProgram(&spirv,
                                     ""    /* Instructions to add to "main" */,
@@ -4077,7 +4354,7 @@
 %1 = OpConstant %int 3
 %main = OpFunction %1 None %2
 %4 = OpLabel
-OpReturn
+OpReturnValue %1
 OpFunctionEnd
   )";
   CompileSuccessfully(spirv.c_str());
@@ -4099,7 +4376,7 @@
 %1 = OpSpecConstantOp %int IAdd %3 %4
 %main = OpFunction %1 None %2
 %6 = OpLabel
-OpReturn
+OpReturnValue %3
 OpFunctionEnd
   )";
   CompileSuccessfully(spirv.c_str());
@@ -4116,10 +4393,11 @@
 %void = OpTypeVoid
 %2 = OpTypeFunction %void
 %int = OpTypeInt 32 0
+%3 = OpConstant %int 1
 %1 = OpSpecConstantComposite %int
 %main = OpFunction %1 None %2
 %4 = OpLabel
-OpReturn
+OpReturnValue %3
 OpFunctionEnd
   )";
   CompileSuccessfully(spirv.c_str());
diff --git a/test/val/val_layout_test.cpp b/test/val/val_layout_test.cpp
index 3a99008..c6750e1 100644
--- a/test/val/val_layout_test.cpp
+++ b/test/val/val_layout_test.cpp
@@ -34,8 +34,9 @@
 using std::tuple;
 using std::vector;
 
-using ::testing::StrEq;
+using ::testing::Eq;
 using ::testing::HasSubstr;
+using ::testing::StrEq;
 using libspirv::spvResultToString;
 
 using pred_type = function<spv_result_t(int)>;
@@ -491,5 +492,114 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
+TEST_F(ValidateLayout, ModuleProcessedInvalidIn10) {
+  char str[] = R"(
+           OpCapability Shader
+           OpCapability Linkage
+           OpMemoryModel Logical GLSL450
+           OpName %void "void"
+           OpModuleProcessed "this is ok in 1.1 and later"
+           OpDecorate %void Volatile ; bogus, but makes the example short
+%void    = OpTypeVoid
+)";
+
+  CompileSuccessfully(str, SPV_ENV_UNIVERSAL_1_1);
+  ASSERT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions(SPV_ENV_UNIVERSAL_1_0));
+  // In a 1.0 environment the binary parse fails before we even get to
+  // validation.  This occurs no matter where the OpModuleProcessed is placed.
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("Invalid opcode: 330"));
+}
+
+TEST_F(ValidateLayout, ModuleProcessedValidIn11) {
+  char str[] = R"(
+           OpCapability Shader
+           OpCapability Linkage
+           OpMemoryModel Logical GLSL450
+           OpName %void "void"
+           OpModuleProcessed "this is ok in 1.1 and later"
+           OpDecorate %void Volatile ; bogus, but makes the example short
+%void    = OpTypeVoid
+)";
+
+  CompileSuccessfully(str, SPV_ENV_UNIVERSAL_1_1);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_1));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateLayout, ModuleProcessedBeforeLastNameIsTooEarly) {
+  char str[] = R"(
+           OpCapability Shader
+           OpCapability Linkage
+           OpMemoryModel Logical GLSL450
+           OpModuleProcessed "this is too early"
+           OpName %void "void"
+%void    = OpTypeVoid
+)";
+
+  CompileSuccessfully(str, SPV_ENV_UNIVERSAL_1_1);
+  ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions(SPV_ENV_UNIVERSAL_1_1));
+  // By the mechanics of the validator, we assume ModuleProcessed is in the
+  // right spot, but then that OpName is in the wrong spot.
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Name cannot appear in a function declaration"));
+}
+
+TEST_F(ValidateLayout, ModuleProcessedInvalidAfterFirstAnnotation) {
+  char str[] = R"(
+           OpCapability Shader
+           OpCapability Linkage
+           OpMemoryModel Logical GLSL450
+           OpDecorate %void Volatile ; this is bogus, but keeps the example short
+           OpModuleProcessed "this is too late"
+%void    = OpTypeVoid
+)";
+
+  CompileSuccessfully(str, SPV_ENV_UNIVERSAL_1_1);
+  ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions(SPV_ENV_UNIVERSAL_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ModuleProcessed cannot appear in a function declaration"));
+}
+
+TEST_F(ValidateLayout, ModuleProcessedInvalidInFunctionBeforeLabel) {
+  char str[] = R"(
+           OpCapability Shader
+           OpMemoryModel Logical GLSL450
+           OpEntryPoint GLCompute %main "main"
+%void    = OpTypeVoid
+%voidfn  = OpTypeFunction %void
+%main    = OpFunction %void None %voidfn
+           OpModuleProcessed "this is too late, in function before label"
+%entry  =  OpLabel
+           OpReturn
+           OpFunctionEnd
+)";
+
+  CompileSuccessfully(str, SPV_ENV_UNIVERSAL_1_1);
+  ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions(SPV_ENV_UNIVERSAL_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ModuleProcessed cannot appear in a function declaration"));
+}
+
+TEST_F(ValidateLayout, ModuleProcessedInvalidInBasicBlock) {
+  char str[] = R"(
+           OpCapability Shader
+           OpMemoryModel Logical GLSL450
+           OpEntryPoint GLCompute %main "main"
+%void    = OpTypeVoid
+%voidfn  = OpTypeFunction %void
+%main    = OpFunction %void None %voidfn
+%entry   = OpLabel
+           OpModuleProcessed "this is too late, in basic block"
+           OpReturn
+           OpFunctionEnd
+)";
+
+  CompileSuccessfully(str, SPV_ENV_UNIVERSAL_1_1);
+  ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions(SPV_ENV_UNIVERSAL_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ModuleProcessed cannot appear in a function declaration"));
+}
+
+
 // TODO(umar): Test optional instructions
 }
diff --git a/test/val/val_logicals_test.cpp b/test/val/val_logicals_test.cpp
new file mode 100644
index 0000000..348b6f2
--- /dev/null
+++ b/test/val/val_logicals_test.cpp
@@ -0,0 +1,870 @@
+// Copyright (c) 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Tests for unique type declaration rules validator.
+
+#include <string>
+
+#include "gmock/gmock.h"
+#include "unit_spirv.h"
+#include "val_fixtures.h"
+
+namespace {
+
+using ::testing::HasSubstr;
+using ::testing::Not;
+
+using ValidateLogicals = spvtest::ValidateBase<bool>;
+
+std::string GenerateShaderCode(
+    const std::string& body,
+    const std::string& capabilities_and_extensions = "") {
+  const std::string capabilities =
+R"(
+OpCapability Shader
+OpCapability Int64
+OpCapability Float64)";
+
+  const std::string after_extension_before_body =
+R"(
+%ext_inst = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%bool = OpTypeBool
+%f32 = OpTypeFloat 32
+%u32 = OpTypeInt 32 0
+%s32 = OpTypeInt 32 1
+%f64 = OpTypeFloat 64
+%u64 = OpTypeInt 64 0
+%s64 = OpTypeInt 64 1
+%boolvec2 = OpTypeVector %bool 2
+%s32vec2 = OpTypeVector %s32 2
+%u32vec2 = OpTypeVector %u32 2
+%u64vec2 = OpTypeVector %u64 2
+%f32vec2 = OpTypeVector %f32 2
+%f64vec2 = OpTypeVector %f64 2
+%boolvec3 = OpTypeVector %bool 3
+%u32vec3 = OpTypeVector %u32 3
+%u64vec3 = OpTypeVector %u64 3
+%s32vec3 = OpTypeVector %s32 3
+%f32vec3 = OpTypeVector %f32 3
+%f64vec3 = OpTypeVector %f64 3
+%boolvec4 = OpTypeVector %bool 4
+%u32vec4 = OpTypeVector %u32 4
+%u64vec4 = OpTypeVector %u64 4
+%s32vec4 = OpTypeVector %s32 4
+%f32vec4 = OpTypeVector %f32 4
+%f64vec4 = OpTypeVector %f64 4
+
+%f32_0 = OpConstant %f32 0
+%f32_1 = OpConstant %f32 1
+%f32_2 = OpConstant %f32 2
+%f32_3 = OpConstant %f32 3
+%f32_4 = OpConstant %f32 4
+
+%s32_0 = OpConstant %s32 0
+%s32_1 = OpConstant %s32 1
+%s32_2 = OpConstant %s32 2
+%s32_3 = OpConstant %s32 3
+%s32_4 = OpConstant %s32 4
+%s32_m1 = OpConstant %s32 -1
+
+%u32_0 = OpConstant %u32 0
+%u32_1 = OpConstant %u32 1
+%u32_2 = OpConstant %u32 2
+%u32_3 = OpConstant %u32 3
+%u32_4 = OpConstant %u32 4
+
+%f64_0 = OpConstant %f64 0
+%f64_1 = OpConstant %f64 1
+%f64_2 = OpConstant %f64 2
+%f64_3 = OpConstant %f64 3
+%f64_4 = OpConstant %f64 4
+
+%s64_0 = OpConstant %s64 0
+%s64_1 = OpConstant %s64 1
+%s64_2 = OpConstant %s64 2
+%s64_3 = OpConstant %s64 3
+%s64_4 = OpConstant %s64 4
+%s64_m1 = OpConstant %s64 -1
+
+%u64_0 = OpConstant %u64 0
+%u64_1 = OpConstant %u64 1
+%u64_2 = OpConstant %u64 2
+%u64_3 = OpConstant %u64 3
+%u64_4 = OpConstant %u64 4
+
+%u32vec2_01 = OpConstantComposite %u32vec2 %u32_0 %u32_1
+%u32vec2_12 = OpConstantComposite %u32vec2 %u32_1 %u32_2
+%u32vec3_012 = OpConstantComposite %u32vec3 %u32_0 %u32_1 %u32_2
+%u32vec3_123 = OpConstantComposite %u32vec3 %u32_1 %u32_2 %u32_3
+%u32vec4_0123 = OpConstantComposite %u32vec4 %u32_0 %u32_1 %u32_2 %u32_3
+%u32vec4_1234 = OpConstantComposite %u32vec4 %u32_1 %u32_2 %u32_3 %u32_4
+
+%s32vec2_01 = OpConstantComposite %s32vec2 %s32_0 %s32_1
+%s32vec2_12 = OpConstantComposite %s32vec2 %s32_1 %s32_2
+%s32vec3_012 = OpConstantComposite %s32vec3 %s32_0 %s32_1 %s32_2
+%s32vec3_123 = OpConstantComposite %s32vec3 %s32_1 %s32_2 %s32_3
+%s32vec4_0123 = OpConstantComposite %s32vec4 %s32_0 %s32_1 %s32_2 %s32_3
+%s32vec4_1234 = OpConstantComposite %s32vec4 %s32_1 %s32_2 %s32_3 %s32_4
+
+%f32vec2_01 = OpConstantComposite %f32vec2 %f32_0 %f32_1
+%f32vec2_12 = OpConstantComposite %f32vec2 %f32_1 %f32_2
+%f32vec3_012 = OpConstantComposite %f32vec3 %f32_0 %f32_1 %f32_2
+%f32vec3_123 = OpConstantComposite %f32vec3 %f32_1 %f32_2 %f32_3
+%f32vec4_0123 = OpConstantComposite %f32vec4 %f32_0 %f32_1 %f32_2 %f32_3
+%f32vec4_1234 = OpConstantComposite %f32vec4 %f32_1 %f32_2 %f32_3 %f32_4
+
+%f64vec2_01 = OpConstantComposite %f64vec2 %f64_0 %f64_1
+%f64vec2_12 = OpConstantComposite %f64vec2 %f64_1 %f64_2
+%f64vec3_012 = OpConstantComposite %f64vec3 %f64_0 %f64_1 %f64_2
+%f64vec3_123 = OpConstantComposite %f64vec3 %f64_1 %f64_2 %f64_3
+%f64vec4_0123 = OpConstantComposite %f64vec4 %f64_0 %f64_1 %f64_2 %f64_3
+%f64vec4_1234 = OpConstantComposite %f64vec4 %f64_1 %f64_2 %f64_3 %f64_4
+
+%true = OpConstantTrue %bool
+%false = OpConstantFalse %bool
+%boolvec2_tf = OpConstantComposite %boolvec2 %true %false
+%boolvec3_tft = OpConstantComposite %boolvec3 %true %false %true
+%boolvec4_tftf = OpConstantComposite %boolvec4 %true %false %true %false
+
+%f32vec4ptr = OpTypePointer Function %f32vec4
+
+%main = OpFunction %void None %func
+%main_entry = OpLabel)";
+
+  const std::string after_body =
+R"(
+OpReturn
+OpFunctionEnd)";
+
+  return capabilities + capabilities_and_extensions +
+      after_extension_before_body + body + after_body;
+}
+
+std::string GenerateKernelCode(
+    const std::string& body,
+    const std::string& capabilities_and_extensions = "") {
+  const std::string capabilities =
+R"(
+OpCapability Addresses
+OpCapability Kernel
+OpCapability Linkage
+OpCapability Int64
+OpCapability Float64)";
+
+  const std::string after_extension_before_body =
+R"(
+OpMemoryModel Physical32 OpenCL
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%bool = OpTypeBool
+%f32 = OpTypeFloat 32
+%u32 = OpTypeInt 32 0
+%f64 = OpTypeFloat 64
+%u64 = OpTypeInt 64 0
+%boolvec2 = OpTypeVector %bool 2
+%u32vec2 = OpTypeVector %u32 2
+%u64vec2 = OpTypeVector %u64 2
+%f32vec2 = OpTypeVector %f32 2
+%f64vec2 = OpTypeVector %f64 2
+%boolvec3 = OpTypeVector %bool 3
+%u32vec3 = OpTypeVector %u32 3
+%u64vec3 = OpTypeVector %u64 3
+%f32vec3 = OpTypeVector %f32 3
+%f64vec3 = OpTypeVector %f64 3
+%boolvec4 = OpTypeVector %bool 4
+%u32vec4 = OpTypeVector %u32 4
+%u64vec4 = OpTypeVector %u64 4
+%f32vec4 = OpTypeVector %f32 4
+%f64vec4 = OpTypeVector %f64 4
+
+%f32_0 = OpConstant %f32 0
+%f32_1 = OpConstant %f32 1
+%f32_2 = OpConstant %f32 2
+%f32_3 = OpConstant %f32 3
+%f32_4 = OpConstant %f32 4
+
+%u32_0 = OpConstant %u32 0
+%u32_1 = OpConstant %u32 1
+%u32_2 = OpConstant %u32 2
+%u32_3 = OpConstant %u32 3
+%u32_4 = OpConstant %u32 4
+
+%f64_0 = OpConstant %f64 0
+%f64_1 = OpConstant %f64 1
+%f64_2 = OpConstant %f64 2
+%f64_3 = OpConstant %f64 3
+%f64_4 = OpConstant %f64 4
+
+%u64_0 = OpConstant %u64 0
+%u64_1 = OpConstant %u64 1
+%u64_2 = OpConstant %u64 2
+%u64_3 = OpConstant %u64 3
+%u64_4 = OpConstant %u64 4
+
+%u32vec2_01 = OpConstantComposite %u32vec2 %u32_0 %u32_1
+%u32vec2_12 = OpConstantComposite %u32vec2 %u32_1 %u32_2
+%u32vec3_012 = OpConstantComposite %u32vec3 %u32_0 %u32_1 %u32_2
+%u32vec3_123 = OpConstantComposite %u32vec3 %u32_1 %u32_2 %u32_3
+%u32vec4_0123 = OpConstantComposite %u32vec4 %u32_0 %u32_1 %u32_2 %u32_3
+%u32vec4_1234 = OpConstantComposite %u32vec4 %u32_1 %u32_2 %u32_3 %u32_4
+
+%f32vec2_01 = OpConstantComposite %f32vec2 %f32_0 %f32_1
+%f32vec2_12 = OpConstantComposite %f32vec2 %f32_1 %f32_2
+%f32vec3_012 = OpConstantComposite %f32vec3 %f32_0 %f32_1 %f32_2
+%f32vec3_123 = OpConstantComposite %f32vec3 %f32_1 %f32_2 %f32_3
+%f32vec4_0123 = OpConstantComposite %f32vec4 %f32_0 %f32_1 %f32_2 %f32_3
+%f32vec4_1234 = OpConstantComposite %f32vec4 %f32_1 %f32_2 %f32_3 %f32_4
+
+%f64vec2_01 = OpConstantComposite %f64vec2 %f64_0 %f64_1
+%f64vec2_12 = OpConstantComposite %f64vec2 %f64_1 %f64_2
+%f64vec3_012 = OpConstantComposite %f64vec3 %f64_0 %f64_1 %f64_2
+%f64vec3_123 = OpConstantComposite %f64vec3 %f64_1 %f64_2 %f64_3
+%f64vec4_0123 = OpConstantComposite %f64vec4 %f64_0 %f64_1 %f64_2 %f64_3
+%f64vec4_1234 = OpConstantComposite %f64vec4 %f64_1 %f64_2 %f64_3 %f64_4
+
+%true = OpConstantTrue %bool
+%false = OpConstantFalse %bool
+%boolvec2_tf = OpConstantComposite %boolvec2 %true %false
+%boolvec3_tft = OpConstantComposite %boolvec3 %true %false %true
+%boolvec4_tftf = OpConstantComposite %boolvec4 %true %false %true %false
+
+%f32vec4ptr = OpTypePointer Function %f32vec4
+
+%main = OpFunction %void None %func
+%main_entry = OpLabel)";
+
+  const std::string after_body =
+R"(
+OpReturn
+OpFunctionEnd)";
+
+  return capabilities + capabilities_and_extensions +
+      after_extension_before_body + body + after_body;
+}
+
+TEST_F(ValidateLogicals, OpAnySuccess) {
+  const std::string body = R"(
+%val1 = OpAny %bool %boolvec2_tf
+%val2 = OpAny %bool %boolvec3_tft
+%val3 = OpAny %bool %boolvec4_tftf
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateLogicals, OpAnyWrongTypeId) {
+  const std::string body = R"(
+%val = OpAny %u32 %boolvec2_tf
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected bool scalar type as Result Type: Any"));
+}
+
+TEST_F(ValidateLogicals, OpAnyWrongOperand) {
+  const std::string body = R"(
+%val = OpAny %bool %u32vec3_123
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected operand to be vector bool: Any"));
+}
+
+TEST_F(ValidateLogicals, OpIsNanSuccess) {
+  const std::string body = R"(
+%val1 = OpIsNan %bool %f32_1
+%val2 = OpIsNan %bool %f64_0
+%val3 = OpIsNan %boolvec2 %f32vec2_12
+%val4 = OpIsNan %boolvec3 %f32vec3_123
+%val5 = OpIsNan %boolvec4 %f32vec4_1234
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateLogicals, OpIsNanWrongTypeId) {
+  const std::string body = R"(
+%val1 = OpIsNan %u32 %f32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected bool scalar or vector type as Result Type: IsNan"));
+}
+
+TEST_F(ValidateLogicals, OpIsNanOperandNotFloat) {
+  const std::string body = R"(
+%val1 = OpIsNan %bool %u32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected operand to be scalar or vector float: IsNan"));
+}
+
+TEST_F(ValidateLogicals, OpIsNanOperandWrongSize) {
+  const std::string body = R"(
+%val1 = OpIsNan %bool %f32vec2_12
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected vector sizes of Result Type and the operand to be equal: "
+      "IsNan"));
+}
+
+TEST_F(ValidateLogicals, OpLessOrGreaterSuccess) {
+  const std::string body = R"(
+%val1 = OpLessOrGreater %bool %f32_0 %f32_1
+%val2 = OpLessOrGreater %bool %f64_0 %f64_0
+%val3 = OpLessOrGreater %boolvec2 %f32vec2_12 %f32vec2_12
+%val4 = OpLessOrGreater %boolvec3 %f32vec3_123 %f32vec3_123
+%val5 = OpLessOrGreater %boolvec4 %f32vec4_1234 %f32vec4_1234
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateLogicals, OpLessOrGreaterWrongTypeId) {
+  const std::string body = R"(
+%val1 = OpLessOrGreater %u32 %f32_1 %f32_1
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected bool scalar or vector type as Result Type: LessOrGreater"));
+}
+
+TEST_F(ValidateLogicals, OpLessOrGreaterLeftOperandNotFloat) {
+  const std::string body = R"(
+%val1 = OpLessOrGreater %bool %u32_1 %f32_1
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected operands to be scalar or vector float: LessOrGreater"));
+}
+
+TEST_F(ValidateLogicals, OpLessOrGreaterLeftOperandWrongSize) {
+  const std::string body = R"(
+%val1 = OpLessOrGreater %bool %f32vec2_12 %f32_1
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected vector sizes of Result Type and the operands to be equal: "
+      "LessOrGreater"));
+}
+
+TEST_F(ValidateLogicals, OpLessOrGreaterOperandsDifferentType) {
+  const std::string body = R"(
+%val1 = OpLessOrGreater %bool %f32_1 %f64_1
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected left and right operands to have the same type: "
+      "LessOrGreater"));
+}
+
+TEST_F(ValidateLogicals, OpFOrdEqualSuccess) {
+  const std::string body = R"(
+%val1 = OpFOrdEqual %bool %f32_0 %f32_1
+%val2 = OpFOrdEqual %bool %f64_0 %f64_0
+%val3 = OpFOrdEqual %boolvec2 %f32vec2_12 %f32vec2_12
+%val4 = OpFOrdEqual %boolvec3 %f32vec3_123 %f32vec3_123
+%val5 = OpFOrdEqual %boolvec4 %f32vec4_1234 %f32vec4_1234
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateLogicals, OpFOrdEqualWrongTypeId) {
+  const std::string body = R"(
+%val1 = OpFOrdEqual %u32 %f32_1 %f32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected bool scalar or vector type as Result Type: FOrdEqual"));
+}
+
+TEST_F(ValidateLogicals, OpFOrdEqualLeftOperandNotFloat) {
+  const std::string body = R"(
+%val1 = OpFOrdEqual %bool %u32_1 %f32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected operands to be scalar or vector float: FOrdEqual"));
+}
+
+TEST_F(ValidateLogicals, OpFOrdEqualLeftOperandWrongSize) {
+  const std::string body = R"(
+%val1 = OpFOrdEqual %bool %f32vec2_12 %f32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected vector sizes of Result Type and the operands to be equal: "
+      "FOrdEqual"));
+}
+
+TEST_F(ValidateLogicals, OpFOrdEqualOperandsDifferentType) {
+  const std::string body = R"(
+%val1 = OpFOrdEqual %bool %f32_1 %f64_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected left and right operands to have the same type: "
+      "FOrdEqual"));
+}
+
+TEST_F(ValidateLogicals, OpLogicalEqualSuccess) {
+  const std::string body = R"(
+%val1 = OpLogicalEqual %bool %true %false
+%val2 = OpLogicalEqual %boolvec2 %boolvec2_tf   %boolvec2_tf
+%val3 = OpLogicalEqual %boolvec3 %boolvec3_tft  %boolvec3_tft
+%val4 = OpLogicalEqual %boolvec4 %boolvec4_tftf %boolvec4_tftf
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateLogicals, OpLogicalEqualWrongTypeId) {
+  const std::string body = R"(
+%val1 = OpLogicalEqual %u32 %true %false
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected bool scalar or vector type as Result Type: LogicalEqual"));
+}
+
+TEST_F(ValidateLogicals, OpLogicalEqualWrongLeftOperand) {
+  const std::string body = R"(
+%val1 = OpLogicalEqual %bool %boolvec2_tf %false
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected both operands to be of Result Type: LogicalEqual"));
+}
+
+TEST_F(ValidateLogicals, OpLogicalEqualWrongRightOperand) {
+  const std::string body = R"(
+%val1 = OpLogicalEqual %boolvec2 %boolvec2_tf %false
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected both operands to be of Result Type: LogicalEqual"));
+}
+
+TEST_F(ValidateLogicals, OpLogicalNotSuccess) {
+  const std::string body = R"(
+%val1 = OpLogicalNot %bool %true
+%val2 = OpLogicalNot %boolvec2 %boolvec2_tf
+%val3 = OpLogicalNot %boolvec3 %boolvec3_tft
+%val4 = OpLogicalNot %boolvec4 %boolvec4_tftf
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateLogicals, OpLogicalNotWrongTypeId) {
+  const std::string body = R"(
+%val1 = OpLogicalNot %u32 %true
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected bool scalar or vector type as Result Type: LogicalNot"));
+}
+
+TEST_F(ValidateLogicals, OpLogicalNotWrongOperand) {
+  const std::string body = R"(
+%val1 = OpLogicalNot %bool %boolvec2_tf
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected operand to be of Result Type: LogicalNot"));
+}
+
+TEST_F(ValidateLogicals, OpSelectSuccess) {
+  const std::string body = R"(
+%val1 = OpSelect %u32 %true %u32_0 %u32_1
+%val2 = OpSelect %f32 %true %f32_0 %f32_1
+%val3 = OpSelect %f64 %true %f64_0 %f64_1
+%val4 = OpSelect %f32vec2 %boolvec2_tf %f32vec2_01 %f32vec2_12
+%val5 = OpSelect %f32vec4 %boolvec4_tftf %f32vec4_0123 %f32vec4_1234
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateLogicals, OpSelectWrongTypeId) {
+  const std::string body = R"(
+%val1 = OpSelect %void %true %u32_0 %u32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected scalar or vector type as Result Type: Select"));
+}
+
+TEST_F(ValidateLogicals, OpSelectPointerNoCapability) {
+  const std::string body = R"(
+%x = OpVariable %f32vec4ptr Function
+%y = OpVariable %f32vec4ptr Function
+OpStore %x %f32vec4_0123
+OpStore %y %f32vec4_1234
+%val1 = OpSelect %f32vec4ptr %true %x %y
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Using pointers with OpSelect requires capability VariablePointers "
+      "or VariablePointersStorageBuffer"));
+}
+
+TEST_F(ValidateLogicals, OpSelectPointerWithCapability1) {
+  const std::string body = R"(
+%x = OpVariable %f32vec4ptr Function
+%y = OpVariable %f32vec4ptr Function
+OpStore %x %f32vec4_0123
+OpStore %y %f32vec4_1234
+%val1 = OpSelect %f32vec4ptr %true %x %y
+)";
+
+  const std::string extra_cap_ext = R"(
+OpCapability VariablePointers
+OpExtension "SPV_KHR_variable_pointers"
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body, extra_cap_ext).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateLogicals, OpSelectPointerWithCapability2) {
+  const std::string body = R"(
+%x = OpVariable %f32vec4ptr Function
+%y = OpVariable %f32vec4ptr Function
+OpStore %x %f32vec4_0123
+OpStore %y %f32vec4_1234
+%val1 = OpSelect %f32vec4ptr %true %x %y
+)";
+
+  const std::string extra_cap_ext = R"(
+OpCapability VariablePointersStorageBuffer
+OpExtension "SPV_KHR_variable_pointers"
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body, extra_cap_ext).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateLogicals, OpSelectWrongCondition) {
+  const std::string body = R"(
+%val1 = OpSelect %u32 %u32_1 %u32_0 %u32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected bool scalar or vector type as condition: Select"));
+}
+
+TEST_F(ValidateLogicals, OpSelectWrongConditionDimension) {
+  const std::string body = R"(
+%val1 = OpSelect %u32vec2 %true %u32vec2_01 %u32vec2_12
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected vector sizes of Result Type and the condition to be equal: "
+      "Select"));
+}
+
+TEST_F(ValidateLogicals, OpSelectWrongLeftObject) {
+  const std::string body = R"(
+%val1 = OpSelect %bool %true %u32vec2_01 %u32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected both objects to be of Result Type: Select"));
+}
+
+TEST_F(ValidateLogicals, OpSelectWrongRightObject) {
+  const std::string body = R"(
+%val1 = OpSelect %bool %true %u32_1 %u32vec2_01
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected both objects to be of Result Type: Select"));
+}
+
+TEST_F(ValidateLogicals, OpIEqualSuccess) {
+  const std::string body = R"(
+%val1 = OpIEqual %bool %u32_0 %s32_1
+%val2 = OpIEqual %bool %s64_0 %u64_0
+%val3 = OpIEqual %boolvec2 %s32vec2_12 %u32vec2_12
+%val4 = OpIEqual %boolvec3 %s32vec3_123 %u32vec3_123
+%val5 = OpIEqual %boolvec4 %s32vec4_1234 %u32vec4_1234
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateLogicals, OpIEqualWrongTypeId) {
+  const std::string body = R"(
+%val1 = OpIEqual %u32 %s32_1 %s32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected bool scalar or vector type as Result Type: IEqual"));
+}
+
+TEST_F(ValidateLogicals, OpIEqualLeftOperandNotInt) {
+  const std::string body = R"(
+%val1 = OpIEqual %bool %f32_1 %s32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected operands to be scalar or vector int: IEqual"));
+}
+
+TEST_F(ValidateLogicals, OpIEqualLeftOperandWrongSize) {
+  const std::string body = R"(
+%val1 = OpIEqual %bool %s32vec2_12 %s32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected vector sizes of Result Type and the operands to be equal: "
+      "IEqual"));
+}
+
+TEST_F(ValidateLogicals, OpIEqualRightOperandNotInt) {
+  const std::string body = R"(
+%val1 = OpIEqual %bool %u32_1 %f32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected operands to be scalar or vector int: IEqual"));
+}
+
+TEST_F(ValidateLogicals, OpIEqualDifferentBitWidth) {
+  const std::string body = R"(
+%val1 = OpIEqual %bool %u32_1 %u64_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected both operands to have the same component bit width: IEqual"));
+}
+
+TEST_F(ValidateLogicals, OpUGreaterThanSuccess) {
+  const std::string body = R"(
+%val1 = OpUGreaterThan %bool %u32_0 %u32_1
+%val2 = OpUGreaterThan %bool %s32_0 %u32_1
+%val3 = OpUGreaterThan %bool %u64_0 %u64_0
+%val4 = OpUGreaterThan %bool %u64_0 %s64_0
+%val5 = OpUGreaterThan %boolvec2 %u32vec2_12 %u32vec2_12
+%val6 = OpUGreaterThan %boolvec3 %s32vec3_123 %u32vec3_123
+%val7 = OpUGreaterThan %boolvec4 %u32vec4_1234 %u32vec4_1234
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateLogicals, OpUGreaterThanWrongTypeId) {
+  const std::string body = R"(
+%val1 = OpUGreaterThan %u32 %u32_1 %u32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected bool scalar or vector type as Result Type: UGreaterThan"));
+}
+
+TEST_F(ValidateLogicals, OpUGreaterThanLeftOperandNotInt) {
+  const std::string body = R"(
+%val1 = OpUGreaterThan %bool %f32_1 %u32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected operands to be scalar or vector int: UGreaterThan"));
+}
+
+TEST_F(ValidateLogicals, OpUGreaterThanLeftOperandWrongSize) {
+  const std::string body = R"(
+%val1 = OpUGreaterThan %bool %u32vec2_12 %u32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected vector sizes of Result Type and the operands to be equal: "
+      "UGreaterThan"));
+}
+
+TEST_F(ValidateLogicals, OpUGreaterThanRightOperandNotInt) {
+  const std::string body = R"(
+%val1 = OpUGreaterThan %bool %u32_1 %f32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected operands to be scalar or vector int: UGreaterThan"));
+}
+
+TEST_F(ValidateLogicals, OpUGreaterThanDifferentBitWidth) {
+  const std::string body = R"(
+%val1 = OpUGreaterThan %bool %u32_1 %u64_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected both operands to have the same component bit width: "
+      "UGreaterThan"));
+}
+
+TEST_F(ValidateLogicals, OpSGreaterThanSuccess) {
+  const std::string body = R"(
+%val1 = OpSGreaterThan %bool %s32_0 %s32_1
+%val2 = OpSGreaterThan %bool %u32_0 %s32_1
+%val3 = OpSGreaterThan %bool %s64_0 %s64_0
+%val4 = OpSGreaterThan %bool %s64_0 %u64_0
+%val5 = OpSGreaterThan %boolvec2 %s32vec2_12 %s32vec2_12
+%val6 = OpSGreaterThan %boolvec3 %s32vec3_123 %u32vec3_123
+%val7 = OpSGreaterThan %boolvec4 %s32vec4_1234 %s32vec4_1234
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateLogicals, OpSGreaterThanWrongTypeId) {
+  const std::string body = R"(
+%val1 = OpSGreaterThan %s32 %s32_1 %s32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected bool scalar or vector type as Result Type: SGreaterThan"));
+}
+
+TEST_F(ValidateLogicals, OpSGreaterThanLeftOperandNotInt) {
+  const std::string body = R"(
+%val1 = OpSGreaterThan %bool %f32_1 %s32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected operands to be scalar or vector int: SGreaterThan"));
+}
+
+TEST_F(ValidateLogicals, OpSGreaterThanLeftOperandWrongSize) {
+  const std::string body = R"(
+%val1 = OpSGreaterThan %bool %s32vec2_12 %s32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected vector sizes of Result Type and the operands to be equal: "
+      "SGreaterThan"));
+}
+
+TEST_F(ValidateLogicals, OpSGreaterThanRightOperandNotInt) {
+  const std::string body = R"(
+%val1 = OpSGreaterThan %bool %s32_1 %f32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected operands to be scalar or vector int: SGreaterThan"));
+}
+
+TEST_F(ValidateLogicals, OpSGreaterThanDifferentBitWidth) {
+  const std::string body = R"(
+%val1 = OpSGreaterThan %bool %s32_1 %s64_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(
+      "Expected both operands to have the same component bit width: SGreaterThan"));
+}
+
+}  // anonymous namespace
diff --git a/test/val/val_ssa_test.cpp b/test/val/val_ssa_test.cpp
index b2074b5..22e94f2 100644
--- a/test/val/val_ssa_test.cpp
+++ b/test/val/val_ssa_test.cpp
@@ -1080,7 +1080,7 @@
                R"(
 %func      = OpFunction %voidt None %vfunct
 %entry     = OpLabel
-%cond      = OpSLessThan %uintt %one %ten
+%cond      = OpSLessThan %boolt %one %ten
 %eleven    = OpIAdd %uintt %one %ten
              OpSelectionMerge %merge None
              OpBranchConditional %cond %t %f
@@ -1108,7 +1108,7 @@
                R"(
 %func        = OpFunction %voidt None %vfunct
 %entry       = OpLabel
-%cond        = OpSLessThan %uintt %one %ten
+%cond        = OpSLessThan %boolt %one %ten
                OpSelectionMerge %merge None
                OpBranchConditional %cond %true_block %false_block
 %true_block  = OpLabel
@@ -1140,7 +1140,7 @@
                OpBranch %loop
 %loop        = OpLabel
 %i           = OpPhi %uintt %one_val %entry %inew %cont
-%cond        = OpSLessThan %uintt %one %ten
+%cond        = OpSLessThan %boolt %one %ten
                OpLoopMerge %merge %cont None
                OpBranchConditional %cond %body %merge
 %body        = OpLabel
@@ -1169,7 +1169,7 @@
 %loop        = OpLabel
 %i           = OpPhi %uintt %one_val %entry %inew %cont
 %bad         = OpIAdd %uintt %inew %one
-%cond        = OpSLessThan %uintt %one %ten
+%cond        = OpSLessThan %boolt %one %ten
                OpLoopMerge %merge %cont None
                OpBranchConditional %cond %body %merge
 %body        = OpLabel
diff --git a/test/val/val_type_unique_test.cpp b/test/val/val_type_unique_test.cpp
index d9d2f76..8bca1f0 100644
--- a/test/val/val_type_unique_test.cpp
+++ b/test/val/val_type_unique_test.cpp
@@ -29,10 +29,7 @@
 
 using ValidateTypeUnique = spvtest::ValidateBase<bool>;
 
-// TODO(atgoo@github) Error logging temporarily disabled because it's failing
-// vulkancts tests. See https://github.com/KhronosGroup/SPIRV-Tools/issues/559
-// const spv_result_t kDuplicateTypeError = SPV_ERROR_INVALID_DATA;
-const spv_result_t kDuplicateTypeError = SPV_SUCCESS;
+const spv_result_t kDuplicateTypeError = SPV_ERROR_INVALID_DATA;
 
 const string& GetHeader() {
   static const string header = R"(
diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt
index 4dc0f13..f650b3f 100644
--- a/tools/CMakeLists.txt
+++ b/tools/CMakeLists.txt
@@ -42,8 +42,7 @@
   add_spvtools_tool(TARGET spirv-dis SRCS dis/dis.cpp LIBS ${SPIRV_TOOLS})
   add_spvtools_tool(TARGET spirv-val SRCS val/val.cpp LIBS ${SPIRV_TOOLS})
   add_spvtools_tool(TARGET spirv-opt SRCS opt/opt.cpp LIBS SPIRV-Tools-opt ${SPIRV_TOOLS})
-  add_spvtools_tool(TARGET spirv-markv SRCS comp/markv.cpp
-	            LIBS SPIRV-Tools-comp ${SPIRV_TOOLS})
+  add_spvtools_tool(TARGET spirv-link SRCS link/linker.cpp LIBS SPIRV-Tools-link ${SPIRV_TOOLS})
   add_spvtools_tool(TARGET spirv-stats
 	            SRCS stats/stats.cpp
 		         stats/stats_analyzer.cpp
@@ -57,11 +56,21 @@
                                                ${SPIRV_HEADER_INCLUDE_DIR})
   target_include_directories(spirv-stats PRIVATE ${spirv-tools_SOURCE_DIR}
                                                  ${SPIRV_HEADER_INCLUDE_DIR})
-  target_include_directories(spirv-markv PRIVATE ${spirv-tools_SOURCE_DIR}
-                                                 ${SPIRV_HEADER_INCLUDE_DIR})
 
-  set(SPIRV_INSTALL_TARGETS spirv-as spirv-dis spirv-val spirv-opt spirv-stats spirv-cfg
-	                    spirv-markv)
+  set(SPIRV_INSTALL_TARGETS spirv-as spirv-dis spirv-val spirv-opt spirv-stats
+                            spirv-cfg spirv-link)
+
+  if(SPIRV_BUILD_COMPRESSION)
+    add_spvtools_tool(TARGET spirv-markv
+                      SRCS comp/markv.cpp
+                           comp/markv_model_factory.cpp
+                           comp/markv_model_shader_default.cpp
+	              LIBS SPIRV-Tools-comp SPIRV-Tools-opt ${SPIRV_TOOLS})
+    target_include_directories(spirv-markv PRIVATE ${spirv-tools_SOURCE_DIR}
+                                                   ${SPIRV_HEADER_INCLUDE_DIR})
+    set(SPIRV_INSTALL_TARGETS ${SPIRV_INSTALL_TARGETS} spirv-markv)
+  endif(SPIRV_BUILD_COMPRESSION)
+
   if(ENABLE_SPIRV_TOOLS_INSTALL)
     install(TARGETS ${SPIRV_INSTALL_TARGETS}
       RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
diff --git a/tools/as/as.cpp b/tools/as/as.cpp
index eb9f9ff..475d44d 100644
--- a/tools/as/as.cpp
+++ b/tools/as/as.cpp
@@ -85,15 +85,12 @@
             printf("Target: %s\n",
                    spvTargetEnvDescription(kDefaultEnvironment));
             return 0;
-          }
-          if (0 == strcmp(argv[argi], "--help")) {
+          } else if (0 == strcmp(argv[argi], "--help")) {
             print_usage(argv[0]);
             return 0;
-          }
-          if (0 == strcmp(argv[argi], "--preserve-numeric-ids")) {
+          } else if (0 == strcmp(argv[argi], "--preserve-numeric-ids")) {
             options |= SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS;
-          }
-          if (0 == strcmp(argv[argi], "--target-env")) {
+          } else if (0 == strcmp(argv[argi], "--target-env")) {
             if (argi + 1 < argc) {
               const auto env_str = argv[++argi];
               if (!spvParseTargetEnv(env_str, &target_env)) {
@@ -105,9 +102,14 @@
               fprintf(stderr, "error: Missing argument to --target-env\n");
               return 1;
             }
+          } else {
+            fprintf(stderr, "error: Unrecognized option: %s\n\n", argv[argi]);
+            print_usage(argv[0]);
+            return 1;
           }
         } break;
         default:
+          fprintf(stderr, "error: Unrecognized option: %s\n\n", argv[argi]);
           print_usage(argv[0]);
           return 1;
       }
diff --git a/tools/comp/markv.cpp b/tools/comp/markv.cpp
index f9df9ca..23585e5 100644
--- a/tools/comp/markv.cpp
+++ b/tools/comp/markv.cpp
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <algorithm>
 #include <cassert>
 #include <cstdio>
 #include <cstring>
@@ -20,17 +21,22 @@
 #include <memory>
 #include <vector>
 
+#include "markv_model_factory.h"
+#include "source/comp/markv.h"
 #include "source/spirv_target_env.h"
 #include "source/table.h"
-#include "spirv-tools/markv.h"
+#include "spirv-tools/optimizer.hpp"
 #include "tools/io.h"
 
 namespace {
 
+const auto kSpvEnv = SPV_ENV_UNIVERSAL_1_2;
+
 enum Task {
   kNoTask = 0,
   kEncode,
   kDecode,
+  kTest,
 };
 
 struct ScopedContext {
@@ -43,7 +49,7 @@
   printf(
       R"(%s - Encodes or decodes a SPIR-V binary to or from a MARK-V binary.
 
-USAGE: %s [e|d] [options] [<filename>]
+USAGE: %s [e|d|t] [options] [<filename>]
 
 The input binary is read from <filename>. If no file is specified,
 or if the filename is "-", then the binary is read from standard input.
@@ -58,15 +64,19 @@
 Tasks:
   e               Encode SPIR-V to MARK-V.
   d               Decode MARK-V to SPIR-V.
+  t               Test the codec by first encoding the given SPIR-V file to
+                  MARK-V, then decoding it back to SPIR-V and comparing results.
 
 Options:
   -h, --help      Print this help.
-  --comments      Write codec comments to stdout.
+  --comments      Write codec comments to stderr.
   --version       Display MARK-V codec version.
+  --validate      Validate SPIR-V while encoding or decoding.
 
   -o <filename>   Set the output filename.
                   Output goes to standard output if this option is
                   not specified, or if the filename is "-".
+                  Not needed for 't' task (testing).
 )",
       argv0, argv0);
 }
@@ -82,11 +92,11 @@
                 << std::endl;
       break;
     case SPV_MSG_WARNING:
-      std::cout << "warning: " << position.index << ": " << message
+      std::cerr << "warning: " << position.index << ": " << message
                 << std::endl;
       break;
     case SPV_MSG_INFO:
-      std::cout << "info: " << position.index << ": " << message << std::endl;
+      std::cerr << "info: " << position.index << ": " << message << std::endl;
       break;
     default:
       break;
@@ -111,6 +121,8 @@
     task = kEncode;
   } else if (0 == strcmp("d", task_char)) {
     task = kDecode;
+  } else if (0 == strcmp("t", task_char)) {
+    task = kTest;
   }
 
   if (task == kNoTask) {
@@ -119,6 +131,7 @@
   }
 
   bool want_comments = false;
+  bool validate_spirv_binary = false;
 
   for (int argi = 2; argi < argc; ++argi) {
     if ('-' == argv[argi][0]) {
@@ -127,7 +140,8 @@
           print_usage(argv[0]);
           return 0;
         case 'o': {
-          if (!output_filename && argi + 1 < argc) {
+          if (!output_filename && argi + 1 < argc &&
+              (task == kEncode || task == kDecode)) {
             output_filename = argv[++argi];
           } else {
             print_usage(argv[0]);
@@ -143,6 +157,8 @@
           } else if (0 == strcmp(argv[argi], "--version")) {
             fprintf(stderr, "error: Not implemented\n");
             return 1;
+          } else if (0 == strcmp(argv[argi], "--validate")) {
+            validate_spirv_binary = true;
           } else {
             print_usage(argv[0]);
             return 1;
@@ -171,77 +187,169 @@
     }
   }
 
-  if (task == kDecode && want_comments) {
-    fprintf(stderr, "warning: Decoder comments not yet implemented\n");
-    want_comments = false;
-  }
+  const auto no_comments = spvtools::MarkvLogConsumer();
+  const auto output_to_stderr = [](const std::string& str) {
+    std::cerr << str;
+  };
 
-  const bool write_to_stdout = output_filename == nullptr ||
-      0 == strcmp(output_filename, "-");
+  ScopedContext ctx(kSpvEnv);
 
-  spv_text comments = nullptr;
-  spv_text* comments_ptr = want_comments ? &comments : nullptr;
+  std::unique_ptr<spvtools::MarkvModel> model =
+      spvtools::CreateMarkvModel(spvtools::kMarkvModelShaderDefault);
 
-  ScopedContext ctx(SPV_ENV_UNIVERSAL_1_2);
-  SetContextMessageConsumer(ctx.context, DiagnosticsMessageHandler);
+  std::vector<uint32_t> spirv;
+  std::vector<uint8_t> markv;
+
+  spvtools::MarkvCodecOptions options;
+  options.validate_spirv_binary = validate_spirv_binary;
 
   if (task == kEncode) {
-    std::vector<uint32_t> contents;
-    if (!ReadFile<uint32_t>(input_filename, "rb", &contents)) return 1;
+    if (!ReadFile<uint32_t>(input_filename, "rb", &spirv)) return 1;
+    assert(!spirv.empty());
 
-    std::unique_ptr<spv_markv_encoder_options_t,
-        std::function<void(spv_markv_encoder_options_t*)>> options(
-            spvMarkvEncoderOptionsCreate(), &spvMarkvEncoderOptionsDestroy);
-    spv_markv_binary markv_binary = nullptr;
-
-    if (SPV_SUCCESS !=
-        spvSpirvToMarkv(ctx.context, contents.data(), contents.size(),
-                        options.get(), &markv_binary, comments_ptr, nullptr)) {
+    if (SPV_SUCCESS != spvtools::SpirvToMarkv(
+        ctx.context, spirv, options, *model, DiagnosticsMessageHandler,
+        want_comments ? output_to_stderr : no_comments,
+        spvtools::MarkvDebugConsumer(), &markv)) {
       std::cerr << "error: Failed to encode " << input_filename << " to MARK-V "
                 << std::endl;
       return 1;
     }
 
-    if (want_comments) {
-      if (!WriteFile<char>(nullptr, "w", comments->str,
-                           comments->length)) return 1;
-    }
-
-    if (!want_comments || !write_to_stdout) {
-      if (!WriteFile<uint8_t>(output_filename, "wb", markv_binary->data,
-                              markv_binary->length)) return 1;
-    }
+    if (!WriteFile<uint8_t>(output_filename, "wb", markv.data(),
+                            markv.size())) return 1;
   } else if (task == kDecode) {
-    std::vector<uint8_t> contents;
-    if (!ReadFile<uint8_t>(input_filename, "rb", &contents)) return 1;
+    if (!ReadFile<uint8_t>(input_filename, "rb", &markv)) return 1;
+    assert(!markv.empty());
 
-    std::unique_ptr<spv_markv_decoder_options_t,
-        std::function<void(spv_markv_decoder_options_t*)>> options(
-            spvMarkvDecoderOptionsCreate(), &spvMarkvDecoderOptionsDestroy);
-    spv_binary spirv_binary = nullptr;
+    if (SPV_SUCCESS != spvtools::MarkvToSpirv(
+        ctx.context, markv, options, *model, DiagnosticsMessageHandler,
+        want_comments ? output_to_stderr : no_comments,
+        spvtools::MarkvDebugConsumer(), &spirv)) {
+      std::cerr << "error: Failed to decode " << input_filename << " to SPIR-V "
+                << std::endl;
+      return 1;
+    }
 
-    if (SPV_SUCCESS !=
-        spvMarkvToSpirv(ctx.context, contents.data(), contents.size(),
-                        options.get(), &spirv_binary, comments_ptr, nullptr)) {
+    if (!WriteFile<uint32_t>(output_filename, "wb", spirv.data(),
+                             spirv.size())) return 1;
+  } else if (task == kTest) {
+    if (!ReadFile<uint32_t>(input_filename, "rb", &spirv)) return 1;
+    assert(!spirv.empty());
+
+    std::vector<uint32_t> spirv_before;
+    spvtools::Optimizer optimizer(kSpvEnv);
+    optimizer.RegisterPass(spvtools::CreateCompactIdsPass());
+    if (!optimizer.Run(spirv.data(), spirv.size(), &spirv_before)) {
+      std::cerr << "error: Optimizer failure on: "
+                << input_filename << std::endl;
+    }
+
+    std::vector<std::string> encoder_instruction_bits;
+    std::vector<std::string> encoder_instruction_comments;
+    std::vector<std::vector<uint32_t>> encoder_instruction_words;
+    std::vector<std::string> decoder_instruction_bits;
+    std::vector<std::string> decoder_instruction_comments;
+    std::vector<std::vector<uint32_t>> decoder_instruction_words;
+
+    const auto encoder_debug_consumer = [&](
+        const std::vector<uint32_t>& words, const std::string& bits,
+        const std::string& comment) {
+      encoder_instruction_words.push_back(words);
+      encoder_instruction_bits.push_back(bits);
+      encoder_instruction_comments.push_back(comment);
+      return true;
+    };
+
+    if (SPV_SUCCESS != spvtools::SpirvToMarkv(
+        ctx.context, spirv_before, options, *model, DiagnosticsMessageHandler,
+        want_comments ? output_to_stderr : no_comments,
+        encoder_debug_consumer, &markv)) {
       std::cerr << "error: Failed to encode " << input_filename << " to MARK-V "
                 << std::endl;
       return 1;
     }
 
-    if (want_comments) {
-      if (!WriteFile<char>(nullptr, "w", comments->str,
-                           comments->length)) return 1;
+    const auto write_bug_report = [&]() {
+      for (size_t inst_index = 0; inst_index < decoder_instruction_words.size();
+           ++inst_index) {
+        std::cerr << "\nInstruction #" << inst_index << std::endl;
+        std::cerr << "\nEncoder words: ";
+        for (uint32_t word : encoder_instruction_words[inst_index])
+          std::cerr << word << " ";
+        std::cerr << "\nDecoder words: ";
+        for (uint32_t word : decoder_instruction_words[inst_index])
+          std::cerr << word << " ";
+        std::cerr << std::endl;
+
+        std::cerr << "\nEncoder bits: " << encoder_instruction_bits[inst_index];
+        std::cerr << "\nDecoder bits: " << decoder_instruction_bits[inst_index];
+        std::cerr << std::endl;
+
+        std::cerr << "\nEncoder comments:\n"
+                  << encoder_instruction_comments[inst_index];
+        std::cerr << "Decoder comments:\n"
+                  << decoder_instruction_comments[inst_index];
+        std::cerr << std::endl;
+      }
+    };
+
+    const auto decoder_debug_consumer = [&](
+        const std::vector<uint32_t>& words, const std::string& bits,
+        const std::string& comment) {
+      const size_t inst_index = decoder_instruction_words.size();
+      if (inst_index >= encoder_instruction_words.size()) {
+        write_bug_report();
+        std::cerr << "error: Decoder has more instructions than encoder: "
+                  << input_filename << std::endl;
+        return false;
+      }
+
+      decoder_instruction_words.push_back(words);
+      decoder_instruction_bits.push_back(bits);
+      decoder_instruction_comments.push_back(comment);
+
+      if (encoder_instruction_words[inst_index] !=
+          decoder_instruction_words[inst_index]) {
+        write_bug_report();
+        std::cerr << "error: Words of the last decoded instruction differ from "
+                  "reference: " << input_filename << std::endl;
+        return false;
+      }
+
+      if (encoder_instruction_bits[inst_index] !=
+          decoder_instruction_bits[inst_index]) {
+        write_bug_report();
+        std::cerr << "error: Bits of the last decoded instruction differ from "
+                  "reference: " << input_filename << std::endl;
+        return false;
+      }
+      return true;
+    };
+
+    std::vector<uint32_t> spirv_after;
+    const spv_result_t decoding_result = spvtools::MarkvToSpirv(
+        ctx.context, markv, options, *model, DiagnosticsMessageHandler,
+        want_comments ? output_to_stderr : no_comments,
+        decoder_debug_consumer, &spirv_after);
+
+    if (decoding_result == SPV_REQUESTED_TERMINATION) {
+      std::cerr << "error: Decoding interrupted by the debugger: "
+                << input_filename << std::endl;
+      return 1;
     }
 
-    if (!want_comments || !write_to_stdout) {
-      if (!WriteFile<uint32_t>(output_filename, "wb", spirv_binary->code,
-                              spirv_binary->wordCount)) return 1;
+    if (decoding_result != SPV_SUCCESS) {
+      std::cerr << "error: Failed to decode encoded " << input_filename
+                << " back to SPIR-V " << std::endl;
+      return 1;
     }
-  } else {
-    assert(false && "Unknown task");
+
+    assert(spirv_before.size() == spirv_after.size());
+    assert(std::mismatch(std::next(spirv_before.begin(), 5), spirv_before.end(),
+                         std::next(spirv_after.begin(), 5)) ==
+           std::make_pair(spirv_before.end(), spirv_after.end()));
   }
 
-  spvTextDestroy(comments);
-
   return 0;
 }
diff --git a/tools/comp/markv_model_factory.cpp b/tools/comp/markv_model_factory.cpp
new file mode 100644
index 0000000..c0a833b
--- /dev/null
+++ b/tools/comp/markv_model_factory.cpp
@@ -0,0 +1,34 @@
+// Copyright (c) 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "markv_model_factory.h"
+#include "markv_model_shader_default.h"
+
+namespace spvtools {
+
+std::unique_ptr<MarkvModel> CreateMarkvModel(MarkvModelType type) {
+  std::unique_ptr<MarkvModel> model;
+  switch (type) {
+    case kMarkvModelShaderDefault: {
+      model.reset(new MarkvModelShaderDefault());
+      break;
+    }
+  }
+
+  model->SetModelType(static_cast<uint32_t>(type));
+
+  return model;
+}
+
+}  // namespace spvtools
diff --git a/tools/comp/markv_model_factory.h b/tools/comp/markv_model_factory.h
new file mode 100644
index 0000000..235efe0
--- /dev/null
+++ b/tools/comp/markv_model_factory.h
@@ -0,0 +1,32 @@
+// Copyright (c) 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SPIRV_TOOLS_COMP_MARKV_MODEL_FACTORY_H_
+#define SPIRV_TOOLS_COMP_MARKV_MODEL_FACTORY_H_
+
+#include <memory>
+
+#include "source/comp/markv_model.h"
+
+namespace spvtools {
+
+enum MarkvModelType {
+  kMarkvModelShaderDefault = 1,
+};
+
+std::unique_ptr<MarkvModel> CreateMarkvModel(MarkvModelType type);
+
+}  // namespace spvtools
+
+#endif  // SPIRV_TOOLS_COMP_MARKV_MODEL_FACTORY_H_
diff --git a/tools/comp/markv_model_shader_default.cpp b/tools/comp/markv_model_shader_default.cpp
new file mode 100644
index 0000000..a58c103
--- /dev/null
+++ b/tools/comp/markv_model_shader_default.cpp
@@ -0,0 +1,112 @@
+// Copyright (c) 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "markv_model_shader_default.h"
+
+#include <algorithm>
+#include <map>
+#include <memory>
+#include <vector>
+#include <unordered_map>
+#include <unordered_set>
+
+using spvutils::HuffmanCodec;
+
+namespace spvtools {
+
+namespace {
+
+// Signals that the value is not in the coding scheme and a fallback method
+// needs to be used.
+const uint64_t kMarkvNoneOfTheAbove = MarkvModel::GetMarkvNoneOfTheAbove();
+
+inline uint32_t CombineOpcodeAndNumOperands(uint32_t opcode,
+                                            uint32_t num_operands) {
+  return opcode | (num_operands << 16);
+}
+
+// The following file contains autogenerated statistical coding rules.
+// Generated by running spirv-stats on representative corpus of shaders with
+// flags:
+// --codegen_opcode_and_num_operands_hist
+// --codegen_opcode_and_num_operands_markov_huffman_codecs
+// --codegen_literal_string_huffman_codecs
+// --codegen_non_id_word_huffman_codecs
+// --codegen_id_descriptor_huffman_codecs
+//
+// Example:
+// find <SHADER_CORPUS_DIR> -type f -print0 | xargs -0 -s 2000000
+// ~/SPIRV-Tools/build/tools/spirv-stats -v
+// --codegen_opcode_and_num_operands_hist
+// --codegen_opcode_and_num_operands_markov_huffman_codecs
+// --codegen_literal_string_huffman_codecs --codegen_non_id_word_huffman_codecs
+// --codegen_id_descriptor_huffman_codecs -o
+// ~/SPIRV-Tools/source/comp/markv_autogen.inc
+#include "markv_model_shader_default_autogen.inc"
+
+}  // namespace
+
+MarkvModelShaderDefault::MarkvModelShaderDefault() {
+  const uint16_t kVersionNumber = 0;
+  SetModelVersion(kVersionNumber);
+
+  opcode_and_num_operands_huffman_codec_.reset(
+      new HuffmanCodec<uint64_t>(GetOpcodeAndNumOperandsHist()));
+  opcode_and_num_operands_markov_huffman_codecs_ =
+      GetOpcodeAndNumOperandsMarkovHuffmanCodecs();
+  non_id_word_huffman_codecs_ = GetNonIdWordHuffmanCodecs();
+  id_descriptor_huffman_codecs_ = GetIdDescriptorHuffmanCodecs();
+  descriptors_with_coding_scheme_ = GetDescriptorsWithCodingScheme();
+  literal_string_huffman_codecs_ = GetLiteralStringHuffmanCodecs();
+
+  operand_chunk_lengths_[SPV_OPERAND_TYPE_TYPE_ID] = 4;
+  operand_chunk_lengths_[SPV_OPERAND_TYPE_RESULT_ID] = 8;
+  operand_chunk_lengths_[SPV_OPERAND_TYPE_ID] = 8;
+  operand_chunk_lengths_[SPV_OPERAND_TYPE_SCOPE_ID] = 8;
+  operand_chunk_lengths_[SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID] = 8;
+  operand_chunk_lengths_[SPV_OPERAND_TYPE_LITERAL_INTEGER] = 6;
+  operand_chunk_lengths_[SPV_OPERAND_TYPE_OPTIONAL_LITERAL_INTEGER] = 6;
+  operand_chunk_lengths_[SPV_OPERAND_TYPE_CAPABILITY] = 6;
+  operand_chunk_lengths_[SPV_OPERAND_TYPE_SOURCE_LANGUAGE] = 3;
+  operand_chunk_lengths_[SPV_OPERAND_TYPE_EXECUTION_MODEL] = 3;
+  operand_chunk_lengths_[SPV_OPERAND_TYPE_ADDRESSING_MODEL] = 2;
+  operand_chunk_lengths_[SPV_OPERAND_TYPE_MEMORY_MODEL] = 2;
+  operand_chunk_lengths_[SPV_OPERAND_TYPE_EXECUTION_MODE] = 6;
+  operand_chunk_lengths_[SPV_OPERAND_TYPE_STORAGE_CLASS] = 4;
+  operand_chunk_lengths_[SPV_OPERAND_TYPE_DIMENSIONALITY] = 3;
+  operand_chunk_lengths_[SPV_OPERAND_TYPE_SAMPLER_ADDRESSING_MODE] = 3;
+  operand_chunk_lengths_[SPV_OPERAND_TYPE_SAMPLER_FILTER_MODE] = 2;
+  operand_chunk_lengths_[SPV_OPERAND_TYPE_SAMPLER_IMAGE_FORMAT] = 6;
+  operand_chunk_lengths_[SPV_OPERAND_TYPE_FP_ROUNDING_MODE] = 2;
+  operand_chunk_lengths_[SPV_OPERAND_TYPE_LINKAGE_TYPE] = 2;
+  operand_chunk_lengths_[SPV_OPERAND_TYPE_ACCESS_QUALIFIER] = 2;
+  operand_chunk_lengths_[SPV_OPERAND_TYPE_OPTIONAL_ACCESS_QUALIFIER] = 2;
+  operand_chunk_lengths_[SPV_OPERAND_TYPE_FUNCTION_PARAMETER_ATTRIBUTE] = 3;
+  operand_chunk_lengths_[SPV_OPERAND_TYPE_DECORATION] = 6;
+  operand_chunk_lengths_[SPV_OPERAND_TYPE_BUILT_IN] = 6;
+  operand_chunk_lengths_[SPV_OPERAND_TYPE_GROUP_OPERATION] = 2;
+  operand_chunk_lengths_[SPV_OPERAND_TYPE_KERNEL_ENQ_FLAGS] = 2;
+  operand_chunk_lengths_[SPV_OPERAND_TYPE_KERNEL_PROFILING_INFO] = 2;
+  operand_chunk_lengths_[SPV_OPERAND_TYPE_FP_FAST_MATH_MODE] = 4;
+  operand_chunk_lengths_[SPV_OPERAND_TYPE_FUNCTION_CONTROL] = 4;
+  operand_chunk_lengths_[SPV_OPERAND_TYPE_LOOP_CONTROL] = 4;
+  operand_chunk_lengths_[SPV_OPERAND_TYPE_IMAGE] = 4;
+  operand_chunk_lengths_[SPV_OPERAND_TYPE_OPTIONAL_IMAGE] = 4;
+  operand_chunk_lengths_[SPV_OPERAND_TYPE_OPTIONAL_MEMORY_ACCESS] = 4;
+  operand_chunk_lengths_[SPV_OPERAND_TYPE_SELECTION_CONTROL] = 4;
+  operand_chunk_lengths_[SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER] = 6;
+  operand_chunk_lengths_[SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER] = 6;
+}
+
+}  // namespace spvtools
diff --git a/tools/comp/markv_model_shader_default.h b/tools/comp/markv_model_shader_default.h
new file mode 100644
index 0000000..c338b78
--- /dev/null
+++ b/tools/comp/markv_model_shader_default.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SPIRV_TOOLS_COMP_DEFAULT_SHADER_MARKV_MODEL_H_
+#define SPIRV_TOOLS_COMP_DEFAULT_SHADER_MARKV_MODEL_H_
+
+#include "source/comp/markv_model.h"
+
+namespace spvtools {
+
+// MARK-V model designed to be a default model for shader compression.
+class MarkvModelShaderDefault : public MarkvModel {
+ public:
+  MarkvModelShaderDefault();
+};
+
+}  // namespace spvtools
+
+#endif  // SPIRV_TOOLS_COMP_DEFAULT_SHADER_MARKV_MODEL_H_
diff --git a/tools/comp/markv_model_shader_default_autogen.inc b/tools/comp/markv_model_shader_default_autogen.inc
new file mode 100644
index 0000000..0093cf1
--- /dev/null
+++ b/tools/comp/markv_model_shader_default_autogen.inc
@@ -0,0 +1,14519 @@
+
+std::map<uint64_t, uint32_t> GetOpcodeAndNumOperandsHist() {
+  return std::map<uint64_t, uint32_t>({
+    { CombineOpcodeAndNumOperands(SpvOpExtInst, 7), 158282 },
+    { CombineOpcodeAndNumOperands(SpvOpDot, 4), 151035 },
+    { CombineOpcodeAndNumOperands(SpvOpVectorShuffle, 6), 183292 },
+    { CombineOpcodeAndNumOperands(SpvOpImageSampleImplicitLod, 4), 126492 },
+    { CombineOpcodeAndNumOperands(SpvOpExecutionMode, 2), 13311 },
+    { CombineOpcodeAndNumOperands(SpvOpFNegate, 3), 29952 },
+    { CombineOpcodeAndNumOperands(SpvOpExtInst, 5), 106847 },
+    { CombineOpcodeAndNumOperands(SpvOpImageSampleExplicitLod, 7), 26350 },
+    { CombineOpcodeAndNumOperands(SpvOpImageSampleExplicitLod, 6), 28186 },
+    { CombineOpcodeAndNumOperands(SpvOpFDiv, 4), 41635 },
+    { CombineOpcodeAndNumOperands(SpvOpFMul, 4), 412786 },
+    { CombineOpcodeAndNumOperands(SpvOpFunction, 4), 62905 },
+    { CombineOpcodeAndNumOperands(SpvOpVectorShuffle, 8), 118614 },
+    { CombineOpcodeAndNumOperands(SpvOpDecorate, 2), 100735 },
+    { CombineOpcodeAndNumOperands(SpvOpReturnValue, 1), 40852 },
+    { CombineOpcodeAndNumOperands(SpvOpVectorTimesScalar, 4), 157091 },
+    { CombineOpcodeAndNumOperands(SpvOpExtInst, 6), 122100 },
+    { CombineOpcodeAndNumOperands(SpvOpAccessChain, 5), 82930 },
+    { CombineOpcodeAndNumOperands(SpvOpFSub, 4), 161019 },
+    { CombineOpcodeAndNumOperands(SpvOpConstant, 3), 466014 },
+    { CombineOpcodeAndNumOperands(SpvOpCompositeExtract, 5), 107126 },
+    { CombineOpcodeAndNumOperands(SpvOpTypeImage, 8), 34775 },
+    { CombineOpcodeAndNumOperands(SpvOpImageSampleDrefExplicitLod, 7), 26146 },
+    { CombineOpcodeAndNumOperands(SpvOpMemoryModel, 2), 18879 },
+    { CombineOpcodeAndNumOperands(SpvOpDecorate, 3), 485251 },
+    { CombineOpcodeAndNumOperands(SpvOpCompositeConstruct, 4), 78011 },
+    { CombineOpcodeAndNumOperands(SpvOpTypeFloat, 2), 18879 },
+    { CombineOpcodeAndNumOperands(SpvOpVectorTimesMatrix, 4), 15848 },
+    { CombineOpcodeAndNumOperands(SpvOpTypeVector, 3), 69404 },
+    { CombineOpcodeAndNumOperands(SpvOpTypeFunction, 3), 19998 },
+    { CombineOpcodeAndNumOperands(SpvOpConstantComposite, 6), 40228 },
+    { CombineOpcodeAndNumOperands(SpvOpCapability, 1), 22510 },
+    { CombineOpcodeAndNumOperands(SpvOpTypeArray, 3), 37585 },
+    { CombineOpcodeAndNumOperands(SpvOpTypeInt, 3), 30454 },
+    { CombineOpcodeAndNumOperands(SpvOpFunctionCall, 4), 29021 },
+    { CombineOpcodeAndNumOperands(SpvOpFAdd, 4), 342237 },
+    { CombineOpcodeAndNumOperands(SpvOpTypeMatrix, 3), 24449 },
+    { CombineOpcodeAndNumOperands(SpvOpLabel, 1), 129408 },
+    { CombineOpcodeAndNumOperands(SpvOpTypePointer, 3), 246535 },
+    { CombineOpcodeAndNumOperands(SpvOpAccessChain, 4), 503456 },
+    { CombineOpcodeAndNumOperands(SpvOpTypeFunction, 2), 19779 },
+    { CombineOpcodeAndNumOperands(SpvOpBranchConditional, 3), 24139 },
+    { CombineOpcodeAndNumOperands(SpvOpVariable, 3), 697946 },
+    { CombineOpcodeAndNumOperands(SpvOpConstantComposite, 5), 55769 },
+    { CombineOpcodeAndNumOperands(SpvOpTypeVoid, 1), 18879 },
+    { CombineOpcodeAndNumOperands(SpvOpCompositeConstruct, 6), 145508 },
+    { CombineOpcodeAndNumOperands(SpvOpFunctionParameter, 2), 85583 },
+    { CombineOpcodeAndNumOperands(SpvOpTypeSampledImage, 2), 34775 },
+    { CombineOpcodeAndNumOperands(SpvOpConstantComposite, 4), 66362 },
+    { CombineOpcodeAndNumOperands(SpvOpLoad, 3), 1272902 },
+    { CombineOpcodeAndNumOperands(SpvOpReturn, 0), 22122 },
+    { CombineOpcodeAndNumOperands(SpvOpCompositeExtract, 4), 861008 },
+    { CombineOpcodeAndNumOperands(SpvOpFunctionEnd, 0), 62905 },
+    { CombineOpcodeAndNumOperands(SpvOpExtInstImport, 2), 18879 },
+    { CombineOpcodeAndNumOperands(SpvOpSelectionMerge, 2), 22009 },
+    { CombineOpcodeAndNumOperands(SpvOpBranch, 1), 38275 },
+    { CombineOpcodeAndNumOperands(SpvOpTypeBool, 1), 12208 },
+    { CombineOpcodeAndNumOperands(SpvOpSampledImage, 4), 95518 },
+    { CombineOpcodeAndNumOperands(SpvOpMemberDecorate, 3), 94887 },
+    { CombineOpcodeAndNumOperands(SpvOpMemberDecorate, 4), 1942215 },
+    { CombineOpcodeAndNumOperands(SpvOpCompositeConstruct, 5), 205266 },
+    { CombineOpcodeAndNumOperands(SpvOpUndef, 2), 22157 },
+    { CombineOpcodeAndNumOperands(SpvOpCompositeInsert, 5), 142749 },
+    { CombineOpcodeAndNumOperands(SpvOpCompositeInsert, 6), 24420 },
+    { CombineOpcodeAndNumOperands(SpvOpCompositeExtract, 6), 16896 },
+    { CombineOpcodeAndNumOperands(SpvOpStore, 2), 604982 },
+    { CombineOpcodeAndNumOperands(SpvOpIAdd, 4), 14471 },
+    { CombineOpcodeAndNumOperands(SpvOpVectorShuffle, 7), 269658 },
+    { kMarkvNoneOfTheAbove, 399895 },
+  });
+}
+
+std::map<uint32_t, std::unique_ptr<HuffmanCodec<uint64_t>>>
+GetOpcodeAndNumOperandsMarkovHuffmanCodecs() {
+  std::map<uint32_t, std::unique_ptr<HuffmanCodec<uint64_t>>> codecs;
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(35, {
+      {0, 0, 0},
+      {65790, 0, 0},
+      {131134, 0, 0},
+      {196669, 0, 0},
+      {262209, 0, 0},
+      {262221, 0, 0},
+      {262225, 0, 0},
+      {262230, 0, 0},
+      {262273, 0, 0},
+      {262277, 0, 0},
+      {262286, 0, 0},
+      {327745, 0, 0},
+      {327761, 0, 0},
+      {327762, 0, 0},
+      {393295, 0, 0},
+      {393304, 0, 0},
+      {458831, 0, 0},
+      {458840, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 11, 8},
+      {0, 12, 19},
+      {0, 18, 20},
+      {0, 5, 21},
+      {0, 15, 7},
+      {0, 10, 1},
+      {0, 23, 22},
+      {0, 14, 24},
+      {0, 6, 4},
+      {0, 2, 17},
+      {0, 13, 25},
+      {0, 9, 26},
+      {0, 28, 27},
+      {0, 3, 29},
+      {0, 30, 16},
+      {0, 32, 31},
+      {0, 34, 33},
+    }));
+
+    codecs.emplace(SpvOpImageSampleExplicitLod, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(55, {
+      {0, 0, 0},
+      {65785, 0, 0},
+      {65790, 0, 0},
+      {131134, 0, 0},
+      {196669, 0, 0},
+      {196735, 0, 0},
+      {262201, 0, 0},
+      {262209, 0, 0},
+      {262224, 0, 0},
+      {262225, 0, 0},
+      {262231, 0, 0},
+      {262273, 0, 0},
+      {262275, 0, 0},
+      {262277, 0, 0},
+      {262280, 0, 0},
+      {262286, 0, 0},
+      {327692, 0, 0},
+      {327745, 0, 0},
+      {327760, 0, 0},
+      {327762, 0, 0},
+      {393228, 0, 0},
+      {393295, 0, 0},
+      {393296, 0, 0},
+      {393303, 0, 0},
+      {393304, 0, 0},
+      {458764, 0, 0},
+      {458831, 0, 0},
+      {524367, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 14, 5},
+      {0, 29, 17},
+      {0, 1, 30},
+      {0, 10, 20},
+      {0, 32, 31},
+      {0, 33, 2},
+      {0, 34, 23},
+      {0, 8, 35},
+      {0, 6, 36},
+      {0, 19, 22},
+      {0, 28, 25},
+      {0, 38, 37},
+      {0, 13, 39},
+      {0, 40, 24},
+      {0, 27, 21},
+      {0, 26, 41},
+      {0, 42, 12},
+      {0, 15, 43},
+      {0, 44, 18},
+      {0, 45, 3},
+      {0, 11, 7},
+      {0, 16, 46},
+      {0, 47, 9},
+      {0, 4, 48},
+      {0, 50, 49},
+      {0, 52, 51},
+      {0, 54, 53},
+    }));
+
+    codecs.emplace(SpvOpFDiv, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(19, {
+      {0, 0, 0},
+      {196669, 0, 0},
+      {262209, 0, 0},
+      {262224, 0, 0},
+      {262225, 0, 0},
+      {262231, 0, 0},
+      {262286, 0, 0},
+      {393295, 0, 0},
+      {393304, 0, 0},
+      {458840, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 8, 10},
+      {0, 11, 3},
+      {0, 2, 9},
+      {0, 4, 1},
+      {0, 5, 6},
+      {0, 13, 12},
+      {0, 15, 14},
+      {0, 16, 7},
+      {0, 18, 17},
+    }));
+
+    codecs.emplace(SpvOpSampledImage, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(67, {
+      {0, 0, 0},
+      {65785, 0, 0},
+      {65790, 0, 0},
+      {131134, 0, 0},
+      {131319, 0, 0},
+      {196669, 0, 0},
+      {196735, 0, 0},
+      {262209, 0, 0},
+      {262224, 0, 0},
+      {262225, 0, 0},
+      {262231, 0, 0},
+      {262272, 0, 0},
+      {262273, 0, 0},
+      {262275, 0, 0},
+      {262277, 0, 0},
+      {262280, 0, 0},
+      {262285, 0, 0},
+      {262286, 0, 0},
+      {262292, 0, 0},
+      {327692, 0, 0},
+      {327745, 0, 0},
+      {327760, 0, 0},
+      {327761, 0, 0},
+      {327762, 0, 0},
+      {393228, 0, 0},
+      {393281, 0, 0},
+      {393295, 0, 0},
+      {393296, 0, 0},
+      {393297, 0, 0},
+      {393298, 0, 0},
+      {393304, 0, 0},
+      {458764, 0, 0},
+      {458831, 0, 0},
+      {524367, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 4, 10},
+      {0, 30, 35},
+      {0, 1, 36},
+      {0, 11, 37},
+      {0, 38, 6},
+      {0, 16, 39},
+      {0, 15, 40},
+      {0, 25, 2},
+      {0, 41, 20},
+      {0, 26, 19},
+      {0, 42, 29},
+      {0, 28, 22},
+      {0, 23, 34},
+      {0, 44, 43},
+      {0, 17, 45},
+      {0, 24, 27},
+      {0, 18, 33},
+      {0, 47, 46},
+      {0, 8, 48},
+      {0, 50, 49},
+      {0, 32, 51},
+      {0, 31, 52},
+      {0, 53, 21},
+      {0, 54, 13},
+      {0, 3, 55},
+      {0, 7, 14},
+      {0, 57, 56},
+      {0, 58, 5},
+      {0, 59, 9},
+      {0, 61, 60},
+      {0, 63, 62},
+      {0, 64, 12},
+      {0, 66, 65},
+    }));
+
+    codecs.emplace(SpvOpFMul, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(79, {
+      {0, 0, 0},
+      {65785, 0, 0},
+      {65790, 0, 0},
+      {131134, 0, 0},
+      {196669, 0, 0},
+      {196735, 0, 0},
+      {262201, 0, 0},
+      {262209, 0, 0},
+      {262224, 0, 0},
+      {262225, 0, 0},
+      {262230, 0, 0},
+      {262231, 0, 0},
+      {262272, 0, 0},
+      {262273, 0, 0},
+      {262275, 0, 0},
+      {262277, 0, 0},
+      {262280, 0, 0},
+      {262286, 0, 0},
+      {262288, 0, 0},
+      {262292, 0, 0},
+      {262328, 0, 0},
+      {262334, 0, 0},
+      {327692, 0, 0},
+      {327737, 0, 0},
+      {327745, 0, 0},
+      {327760, 0, 0},
+      {327761, 0, 0},
+      {327762, 0, 0},
+      {393228, 0, 0},
+      {393281, 0, 0},
+      {393295, 0, 0},
+      {393296, 0, 0},
+      {393297, 0, 0},
+      {393303, 0, 0},
+      {393304, 0, 0},
+      {458764, 0, 0},
+      {458831, 0, 0},
+      {458840, 0, 0},
+      {524345, 0, 0},
+      {524367, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 38, 33},
+      {0, 18, 41},
+      {0, 42, 23},
+      {0, 43, 6},
+      {0, 34, 44},
+      {0, 1, 45},
+      {0, 31, 14},
+      {0, 47, 46},
+      {0, 48, 2},
+      {0, 12, 21},
+      {0, 49, 30},
+      {0, 37, 50},
+      {0, 51, 20},
+      {0, 5, 24},
+      {0, 40, 16},
+      {0, 29, 13},
+      {0, 26, 52},
+      {0, 53, 17},
+      {0, 36, 54},
+      {0, 55, 28},
+      {0, 57, 56},
+      {0, 19, 25},
+      {0, 39, 8},
+      {0, 32, 58},
+      {0, 59, 27},
+      {0, 22, 10},
+      {0, 35, 60},
+      {0, 62, 61},
+      {0, 63, 7},
+      {0, 65, 64},
+      {0, 4, 66},
+      {0, 68, 67},
+      {0, 11, 3},
+      {0, 15, 69},
+      {0, 9, 70},
+      {0, 72, 71},
+      {0, 74, 73},
+      {0, 76, 75},
+      {0, 78, 77},
+    }));
+
+    codecs.emplace(SpvOpFAdd, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(55, {
+      {0, 0, 0},
+      {65556, 0, 0},
+      {65562, 0, 0},
+      {131073, 0, 0},
+      {131094, 0, 0},
+      {131105, 0, 0},
+      {196629, 0, 0},
+      {196631, 0, 0},
+      {196632, 0, 0},
+      {196636, 0, 0},
+      {196640, 0, 0},
+      {196641, 0, 0},
+      {196651, 0, 0},
+      {196667, 0, 0},
+      {262177, 0, 0},
+      {262188, 0, 0},
+      {262198, 0, 0},
+      {327713, 0, 0},
+      {327724, 0, 0},
+      {393249, 0, 0},
+      {393260, 0, 0},
+      {458785, 0, 0},
+      {524313, 0, 0},
+      {524321, 0, 0},
+      {589857, 0, 0},
+      {655393, 0, 0},
+      {720929, 0, 0},
+      {852001, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 26, 24},
+      {0, 29, 27},
+      {0, 4, 30},
+      {0, 21, 9},
+      {0, 31, 20},
+      {0, 33, 32},
+      {0, 34, 3},
+      {0, 8, 35},
+      {0, 36, 5},
+      {0, 23, 16},
+      {0, 38, 37},
+      {0, 25, 2},
+      {0, 39, 1},
+      {0, 17, 40},
+      {0, 41, 15},
+      {0, 18, 42},
+      {0, 43, 6},
+      {0, 44, 14},
+      {0, 28, 19},
+      {0, 7, 45},
+      {0, 46, 22},
+      {0, 48, 47},
+      {0, 49, 11},
+      {0, 51, 50},
+      {0, 12, 10},
+      {0, 53, 52},
+      {0, 13, 54},
+    }));
+
+    codecs.emplace(SpvOpTypePointer, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(57, {
+      {0, 0, 0},
+      {65785, 0, 0},
+      {65790, 0, 0},
+      {131134, 0, 0},
+      {196669, 0, 0},
+      {196735, 0, 0},
+      {262209, 0, 0},
+      {262224, 0, 0},
+      {262225, 0, 0},
+      {262272, 0, 0},
+      {262273, 0, 0},
+      {262275, 0, 0},
+      {262277, 0, 0},
+      {262280, 0, 0},
+      {262286, 0, 0},
+      {262292, 0, 0},
+      {262328, 0, 0},
+      {327692, 0, 0},
+      {327745, 0, 0},
+      {327760, 0, 0},
+      {327761, 0, 0},
+      {327762, 0, 0},
+      {393228, 0, 0},
+      {393273, 0, 0},
+      {393295, 0, 0},
+      {393296, 0, 0},
+      {458764, 0, 0},
+      {458831, 0, 0},
+      {524367, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 9, 23},
+      {0, 1, 30},
+      {0, 5, 31},
+      {0, 32, 28},
+      {0, 33, 25},
+      {0, 34, 29},
+      {0, 18, 24},
+      {0, 27, 16},
+      {0, 7, 13},
+      {0, 14, 35},
+      {0, 20, 10},
+      {0, 36, 21},
+      {0, 2, 37},
+      {0, 38, 3},
+      {0, 39, 22},
+      {0, 40, 19},
+      {0, 41, 11},
+      {0, 6, 4},
+      {0, 12, 42},
+      {0, 43, 8},
+      {0, 15, 26},
+      {0, 45, 44},
+      {0, 47, 46},
+      {0, 48, 17},
+      {0, 50, 49},
+      {0, 52, 51},
+      {0, 54, 53},
+      {0, 56, 55},
+    }));
+
+    codecs.emplace(SpvOpFSub, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(13, {
+      {0, 0, 0},
+      {65785, 0, 0},
+      {131134, 0, 0},
+      {196719, 0, 0},
+      {262209, 0, 0},
+      {262276, 0, 0},
+      {327745, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 7, 4},
+      {0, 2, 8},
+      {0, 1, 9},
+      {0, 5, 10},
+      {0, 3, 6},
+      {0, 12, 11},
+    }));
+
+    codecs.emplace(SpvOpIAdd, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(83, {
+      {0, 0, 0},
+      {65785, 0, 0},
+      {65790, 0, 0},
+      {131134, 0, 0},
+      {131319, 0, 0},
+      {196669, 0, 0},
+      {196732, 0, 0},
+      {196735, 0, 0},
+      {262209, 0, 0},
+      {262221, 0, 0},
+      {262224, 0, 0},
+      {262225, 0, 0},
+      {262230, 0, 0},
+      {262231, 0, 0},
+      {262273, 0, 0},
+      {262275, 0, 0},
+      {262277, 0, 0},
+      {262280, 0, 0},
+      {262286, 0, 0},
+      {262288, 0, 0},
+      {262292, 0, 0},
+      {262328, 0, 0},
+      {262334, 0, 0},
+      {262340, 0, 0},
+      {327692, 0, 0},
+      {327737, 0, 0},
+      {327745, 0, 0},
+      {327760, 0, 0},
+      {327761, 0, 0},
+      {327762, 0, 0},
+      {393228, 0, 0},
+      {393273, 0, 0},
+      {393295, 0, 0},
+      {393296, 0, 0},
+      {393297, 0, 0},
+      {393298, 0, 0},
+      {393304, 0, 0},
+      {458764, 0, 0},
+      {458831, 0, 0},
+      {458840, 0, 0},
+      {458842, 0, 0},
+      {524367, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 25, 2},
+      {0, 31, 43},
+      {0, 4, 44},
+      {0, 26, 45},
+      {0, 39, 46},
+      {0, 34, 36},
+      {0, 19, 47},
+      {0, 6, 48},
+      {0, 35, 9},
+      {0, 12, 29},
+      {0, 21, 49},
+      {0, 22, 13},
+      {0, 17, 50},
+      {0, 23, 51},
+      {0, 52, 7},
+      {0, 37, 1},
+      {0, 53, 3},
+      {0, 54, 24},
+      {0, 56, 55},
+      {0, 32, 57},
+      {0, 59, 58},
+      {0, 42, 10},
+      {0, 60, 8},
+      {0, 5, 41},
+      {0, 61, 20},
+      {0, 62, 38},
+      {0, 64, 63},
+      {0, 40, 65},
+      {0, 66, 18},
+      {0, 15, 28},
+      {0, 14, 67},
+      {0, 68, 30},
+      {0, 70, 69},
+      {0, 72, 71},
+      {0, 73, 27},
+      {0, 16, 74},
+      {0, 75, 33},
+      {0, 77, 76},
+      {0, 79, 78},
+      {0, 81, 80},
+      {0, 82, 11},
+    }));
+
+    codecs.emplace(SpvOpCompositeExtract, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(29, {
+      {0, 0, 0},
+      {65790, 0, 0},
+      {131134, 0, 0},
+      {196669, 0, 0},
+      {262209, 0, 0},
+      {262225, 0, 0},
+      {262273, 0, 0},
+      {262288, 0, 0},
+      {262292, 0, 0},
+      {327692, 0, 0},
+      {327761, 0, 0},
+      {327762, 0, 0},
+      {393295, 0, 0},
+      {458831, 0, 0},
+      {524367, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 10, 6},
+      {0, 16, 13},
+      {0, 7, 17},
+      {0, 15, 18},
+      {0, 19, 12},
+      {0, 20, 14},
+      {0, 1, 4},
+      {0, 22, 21},
+      {0, 11, 8},
+      {0, 2, 5},
+      {0, 9, 23},
+      {0, 3, 24},
+      {0, 26, 25},
+      {0, 28, 27},
+    }));
+
+    codecs.emplace(SpvOpVectorTimesMatrix, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
+      {0, 0, 0},
+      {65784, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 2},
+    }));
+
+    codecs.emplace(SpvOpBranch, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
+      {0, 0, 0},
+      {262198, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 2},
+    }));
+
+    codecs.emplace(SpvOpFunctionEnd, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
+      {0, 0, 0},
+      {65784, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 2},
+    }));
+
+    codecs.emplace(SpvOpBranchConditional, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(53, {
+      {0, 0, 0},
+      {65785, 0, 0},
+      {65790, 0, 0},
+      {131134, 0, 0},
+      {131319, 0, 0},
+      {196665, 0, 0},
+      {196669, 0, 0},
+      {196735, 0, 0},
+      {262201, 0, 0},
+      {262209, 0, 0},
+      {262224, 0, 0},
+      {262225, 0, 0},
+      {262231, 0, 0},
+      {262273, 0, 0},
+      {262275, 0, 0},
+      {262277, 0, 0},
+      {262280, 0, 0},
+      {262286, 0, 0},
+      {262288, 0, 0},
+      {262292, 0, 0},
+      {327692, 0, 0},
+      {327745, 0, 0},
+      {327760, 0, 0},
+      {393228, 0, 0},
+      {393295, 0, 0},
+      {458764, 0, 0},
+      {458831, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 25, 16},
+      {0, 21, 28},
+      {0, 18, 23},
+      {0, 4, 29},
+      {0, 10, 5},
+      {0, 1, 30},
+      {0, 32, 31},
+      {0, 22, 33},
+      {0, 34, 8},
+      {0, 35, 15},
+      {0, 13, 36},
+      {0, 26, 17},
+      {0, 38, 37},
+      {0, 39, 11},
+      {0, 40, 14},
+      {0, 12, 27},
+      {0, 19, 41},
+      {0, 24, 42},
+      {0, 44, 43},
+      {0, 45, 7},
+      {0, 20, 46},
+      {0, 9, 47},
+      {0, 48, 2},
+      {0, 50, 49},
+      {0, 6, 3},
+      {0, 52, 51},
+    }));
+
+    codecs.emplace(SpvOpFunctionCall, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(71, {
+      {0, 0, 0},
+      {65556, 0, 0},
+      {65562, 0, 0},
+      {131073, 0, 0},
+      {131094, 0, 0},
+      {131099, 0, 0},
+      {131134, 0, 0},
+      {196629, 0, 0},
+      {196631, 0, 0},
+      {196632, 0, 0},
+      {196636, 0, 0},
+      {196640, 0, 0},
+      {196651, 0, 0},
+      {196665, 0, 0},
+      {196667, 0, 0},
+      {196669, 0, 0},
+      {262188, 0, 0},
+      {262198, 0, 0},
+      {262201, 0, 0},
+      {262209, 0, 0},
+      {262225, 0, 0},
+      {262275, 0, 0},
+      {262280, 0, 0},
+      {262292, 0, 0},
+      {327692, 0, 0},
+      {327724, 0, 0},
+      {327737, 0, 0},
+      {327745, 0, 0},
+      {393228, 0, 0},
+      {393260, 0, 0},
+      {393273, 0, 0},
+      {393295, 0, 0},
+      {393296, 0, 0},
+      {458831, 0, 0},
+      {524313, 0, 0},
+      {524367, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 22, 4},
+      {0, 32, 23},
+      {0, 37, 30},
+      {0, 21, 38},
+      {0, 39, 31},
+      {0, 41, 40},
+      {0, 13, 42},
+      {0, 43, 26},
+      {0, 10, 44},
+      {0, 28, 45},
+      {0, 35, 18},
+      {0, 20, 46},
+      {0, 33, 47},
+      {0, 24, 48},
+      {0, 6, 49},
+      {0, 3, 50},
+      {0, 16, 51},
+      {0, 27, 52},
+      {0, 53, 1},
+      {0, 9, 17},
+      {0, 29, 54},
+      {0, 19, 2},
+      {0, 8, 36},
+      {0, 55, 34},
+      {0, 25, 56},
+      {0, 7, 57},
+      {0, 5, 58},
+      {0, 60, 59},
+      {0, 61, 15},
+      {0, 63, 62},
+      {0, 65, 64},
+      {0, 66, 11},
+      {0, 12, 67},
+      {0, 69, 68},
+      {0, 14, 70},
+    }));
+
+    codecs.emplace(SpvOpVariable, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
+      {0, 0, 0},
+      {131134, 0, 0},
+      {196669, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 3},
+      {0, 2, 4},
+    }));
+
+    codecs.emplace(SpvOpAccessChain, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(73, {
+      {0, 0, 0},
+      {252, 0, 0},
+      {253, 0, 0},
+      {65785, 0, 0},
+      {65790, 0, 0},
+      {131073, 0, 0},
+      {131134, 0, 0},
+      {131319, 0, 0},
+      {196665, 0, 0},
+      {196667, 0, 0},
+      {196669, 0, 0},
+      {196735, 0, 0},
+      {196854, 0, 0},
+      {262201, 0, 0},
+      {262209, 0, 0},
+      {262221, 0, 0},
+      {262225, 0, 0},
+      {262272, 0, 0},
+      {262273, 0, 0},
+      {262275, 0, 0},
+      {262276, 0, 0},
+      {262277, 0, 0},
+      {262280, 0, 0},
+      {262286, 0, 0},
+      {262292, 0, 0},
+      {262321, 0, 0},
+      {327692, 0, 0},
+      {327745, 0, 0},
+      {327761, 0, 0},
+      {327762, 0, 0},
+      {393228, 0, 0},
+      {393295, 0, 0},
+      {393296, 0, 0},
+      {393298, 0, 0},
+      {393461, 0, 0},
+      {458831, 0, 0},
+      {524367, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 28, 5},
+      {0, 30, 8},
+      {0, 13, 38},
+      {0, 40, 39},
+      {0, 41, 26},
+      {0, 42, 19},
+      {0, 43, 29},
+      {0, 23, 44},
+      {0, 36, 32},
+      {0, 45, 22},
+      {0, 2, 46},
+      {0, 21, 20},
+      {0, 48, 47},
+      {0, 33, 49},
+      {0, 4, 50},
+      {0, 51, 24},
+      {0, 18, 11},
+      {0, 52, 12},
+      {0, 25, 15},
+      {0, 53, 17},
+      {0, 37, 54},
+      {0, 55, 35},
+      {0, 7, 27},
+      {0, 57, 56},
+      {0, 58, 31},
+      {0, 6, 59},
+      {0, 1, 60},
+      {0, 62, 61},
+      {0, 63, 14},
+      {0, 3, 16},
+      {0, 34, 64},
+      {0, 66, 65},
+      {0, 68, 67},
+      {0, 70, 69},
+      {0, 10, 9},
+      {0, 72, 71},
+    }));
+
+    codecs.emplace(SpvOpLabel, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
+      {0, 0, 0},
+      {56, 0, 0},
+      {65784, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 3, 2},
+      {0, 1, 4},
+    }));
+
+    codecs.emplace(SpvOpReturn, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
+      {0, 0, 0},
+      {65784, 0, 0},
+      {131127, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 3},
+      {0, 2, 4},
+    }));
+
+    codecs.emplace(SpvOpFunction, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(31, {
+      {0, 0, 0},
+      {65556, 0, 0},
+      {196629, 0, 0},
+      {196631, 0, 0},
+      {196632, 0, 0},
+      {196636, 0, 0},
+      {196640, 0, 0},
+      {196641, 0, 0},
+      {196651, 0, 0},
+      {196667, 0, 0},
+      {262177, 0, 0},
+      {262188, 0, 0},
+      {262198, 0, 0},
+      {327713, 0, 0},
+      {393260, 0, 0},
+      {524313, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 12, 1},
+      {0, 13, 5},
+      {0, 18, 17},
+      {0, 7, 19},
+      {0, 9, 20},
+      {0, 16, 21},
+      {0, 15, 10},
+      {0, 22, 4},
+      {0, 24, 23},
+      {0, 25, 14},
+      {0, 8, 11},
+      {0, 2, 26},
+      {0, 28, 27},
+      {0, 3, 6},
+      {0, 30, 29},
+    }));
+
+    codecs.emplace(SpvOpTypeVector, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
+      {0, 0, 0},
+      {65784, 0, 0},
+      {131127, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 2, 3},
+      {0, 4, 1},
+    }));
+
+    codecs.emplace(SpvOpFunctionParameter, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
+      {0, 0, 0},
+      {56, 0, 0},
+      {65784, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 3, 2},
+      {0, 1, 4},
+    }));
+
+    codecs.emplace(SpvOpReturnValue, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
+      {0, 0, 0},
+      {131105, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 2},
+    }));
+
+    codecs.emplace(SpvOpTypeVoid, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(89, {
+      {0, 0, 0},
+      {253, 0, 0},
+      {65785, 0, 0},
+      {65790, 0, 0},
+      {131134, 0, 0},
+      {131319, 0, 0},
+      {196665, 0, 0},
+      {196669, 0, 0},
+      {196735, 0, 0},
+      {262201, 0, 0},
+      {262209, 0, 0},
+      {262224, 0, 0},
+      {262225, 0, 0},
+      {262272, 0, 0},
+      {262273, 0, 0},
+      {262275, 0, 0},
+      {262277, 0, 0},
+      {262280, 0, 0},
+      {262286, 0, 0},
+      {262288, 0, 0},
+      {262292, 0, 0},
+      {327692, 0, 0},
+      {327737, 0, 0},
+      {327745, 0, 0},
+      {327760, 0, 0},
+      {327761, 0, 0},
+      {327762, 0, 0},
+      {393228, 0, 0},
+      {393273, 0, 0},
+      {393281, 0, 0},
+      {393295, 0, 0},
+      {393296, 0, 0},
+      {458764, 0, 0},
+      {458809, 0, 0},
+      {458831, 0, 0},
+      {524345, 0, 0},
+      {524367, 0, 0},
+      {589881, 0, 0},
+      {655417, 0, 0},
+      {720953, 0, 0},
+      {786489, 0, 0},
+      {852025, 0, 0},
+      {917561, 0, 0},
+      {983097, 0, 0},
+      {1114169, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 40, 32},
+      {0, 46, 29},
+      {0, 38, 27},
+      {0, 20, 47},
+      {0, 49, 48},
+      {0, 50, 44},
+      {0, 51, 43},
+      {0, 14, 5},
+      {0, 42, 52},
+      {0, 13, 19},
+      {0, 3, 26},
+      {0, 54, 53},
+      {0, 56, 55},
+      {0, 57, 6},
+      {0, 39, 37},
+      {0, 15, 58},
+      {0, 18, 31},
+      {0, 59, 21},
+      {0, 60, 17},
+      {0, 61, 41},
+      {0, 62, 24},
+      {0, 34, 63},
+      {0, 35, 64},
+      {0, 65, 8},
+      {0, 66, 36},
+      {0, 67, 30},
+      {0, 16, 11},
+      {0, 69, 68},
+      {0, 70, 28},
+      {0, 22, 71},
+      {0, 33, 72},
+      {0, 45, 73},
+      {0, 75, 74},
+      {0, 77, 76},
+      {0, 78, 12},
+      {0, 1, 2},
+      {0, 9, 79},
+      {0, 25, 80},
+      {0, 23, 81},
+      {0, 4, 82},
+      {0, 84, 83},
+      {0, 86, 85},
+      {0, 7, 10},
+      {0, 88, 87},
+    }));
+
+    codecs.emplace(SpvOpStore, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(13, {
+      {0, 0, 0},
+      {131075, 0, 0},
+      {131088, 0, 0},
+      {131143, 0, 0},
+      {196624, 0, 0},
+      {196679, 0, 0},
+      {262216, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 3, 4},
+      {0, 1, 8},
+      {0, 7, 9},
+      {0, 6, 10},
+      {0, 5, 11},
+      {0, 2, 12},
+    }));
+
+    codecs.emplace(SpvOpEntryPoint, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(97, {
+      {0, 0, 0},
+      {65785, 0, 0},
+      {65790, 0, 0},
+      {131134, 0, 0},
+      {131319, 0, 0},
+      {196665, 0, 0},
+      {196669, 0, 0},
+      {196732, 0, 0},
+      {196735, 0, 0},
+      {262201, 0, 0},
+      {262209, 0, 0},
+      {262224, 0, 0},
+      {262225, 0, 0},
+      {262230, 0, 0},
+      {262231, 0, 0},
+      {262272, 0, 0},
+      {262273, 0, 0},
+      {262275, 0, 0},
+      {262276, 0, 0},
+      {262277, 0, 0},
+      {262280, 0, 0},
+      {262286, 0, 0},
+      {262288, 0, 0},
+      {262292, 0, 0},
+      {262326, 0, 0},
+      {262328, 0, 0},
+      {262330, 0, 0},
+      {327692, 0, 0},
+      {327737, 0, 0},
+      {327745, 0, 0},
+      {327760, 0, 0},
+      {327761, 0, 0},
+      {327762, 0, 0},
+      {393228, 0, 0},
+      {393273, 0, 0},
+      {393281, 0, 0},
+      {393295, 0, 0},
+      {393296, 0, 0},
+      {393297, 0, 0},
+      {393304, 0, 0},
+      {458764, 0, 0},
+      {458809, 0, 0},
+      {458817, 0, 0},
+      {458831, 0, 0},
+      {458840, 0, 0},
+      {524345, 0, 0},
+      {524367, 0, 0},
+      {589881, 0, 0},
+      {720953, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 42, 47},
+      {0, 48, 50},
+      {0, 45, 51},
+      {0, 34, 52},
+      {0, 53, 41},
+      {0, 1, 54},
+      {0, 55, 5},
+      {0, 15, 4},
+      {0, 56, 35},
+      {0, 26, 24},
+      {0, 18, 28},
+      {0, 57, 38},
+      {0, 59, 58},
+      {0, 60, 25},
+      {0, 20, 9},
+      {0, 7, 61},
+      {0, 62, 22},
+      {0, 11, 31},
+      {0, 63, 8},
+      {0, 64, 40},
+      {0, 66, 65},
+      {0, 27, 44},
+      {0, 29, 67},
+      {0, 68, 39},
+      {0, 69, 2},
+      {0, 37, 49},
+      {0, 71, 70},
+      {0, 30, 72},
+      {0, 73, 17},
+      {0, 33, 74},
+      {0, 23, 14},
+      {0, 32, 75},
+      {0, 21, 76},
+      {0, 77, 16},
+      {0, 46, 78},
+      {0, 13, 79},
+      {0, 80, 12},
+      {0, 19, 81},
+      {0, 43, 36},
+      {0, 83, 82},
+      {0, 10, 84},
+      {0, 85, 3},
+      {0, 6, 86},
+      {0, 88, 87},
+      {0, 90, 89},
+      {0, 92, 91},
+      {0, 94, 93},
+      {0, 96, 95},
+    }));
+
+    codecs.emplace(SpvOpLoad, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(47, {
+      {0, 0, 0},
+      {262159, 0, 0},
+      {327695, 0, 0},
+      {393231, 0, 0},
+      {458767, 0, 0},
+      {524303, 0, 0},
+      {589839, 0, 0},
+      {655375, 0, 0},
+      {720911, 0, 0},
+      {786447, 0, 0},
+      {851983, 0, 0},
+      {917519, 0, 0},
+      {983055, 0, 0},
+      {1048591, 0, 0},
+      {1114127, 0, 0},
+      {1179663, 0, 0},
+      {1245199, 0, 0},
+      {1310735, 0, 0},
+      {1376271, 0, 0},
+      {1441807, 0, 0},
+      {1507343, 0, 0},
+      {1572879, 0, 0},
+      {1638415, 0, 0},
+      {1703951, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 23},
+      {0, 22, 25},
+      {0, 21, 26},
+      {0, 6, 20},
+      {0, 19, 27},
+      {0, 29, 28},
+      {0, 24, 18},
+      {0, 30, 13},
+      {0, 31, 14},
+      {0, 32, 7},
+      {0, 17, 15},
+      {0, 33, 2},
+      {0, 34, 8},
+      {0, 16, 12},
+      {0, 35, 3},
+      {0, 36, 5},
+      {0, 9, 37},
+      {0, 39, 38},
+      {0, 11, 40},
+      {0, 4, 10},
+      {0, 42, 41},
+      {0, 44, 43},
+      {0, 46, 45},
+    }));
+
+    codecs.emplace(SpvOpMemoryModel, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
+      {0, 0, 0},
+      {196631, 0, 0},
+      {196640, 0, 0},
+      {196641, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 2, 3},
+      {0, 4, 5},
+      {0, 1, 6},
+    }));
+
+    codecs.emplace(SpvOpTypeFloat, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(69, {
+      {0, 0, 0},
+      {65790, 0, 0},
+      {131134, 0, 0},
+      {196669, 0, 0},
+      {196735, 0, 0},
+      {262201, 0, 0},
+      {262209, 0, 0},
+      {262224, 0, 0},
+      {262225, 0, 0},
+      {262231, 0, 0},
+      {262272, 0, 0},
+      {262273, 0, 0},
+      {262275, 0, 0},
+      {262277, 0, 0},
+      {262280, 0, 0},
+      {262286, 0, 0},
+      {262288, 0, 0},
+      {262289, 0, 0},
+      {262292, 0, 0},
+      {327692, 0, 0},
+      {327745, 0, 0},
+      {327760, 0, 0},
+      {327761, 0, 0},
+      {327762, 0, 0},
+      {327849, 0, 0},
+      {393228, 0, 0},
+      {393281, 0, 0},
+      {393295, 0, 0},
+      {393296, 0, 0},
+      {393304, 0, 0},
+      {458764, 0, 0},
+      {458809, 0, 0},
+      {458831, 0, 0},
+      {524345, 0, 0},
+      {524367, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 33, 10},
+      {0, 31, 36},
+      {0, 26, 37},
+      {0, 5, 38},
+      {0, 20, 39},
+      {0, 22, 40},
+      {0, 24, 25},
+      {0, 15, 41},
+      {0, 9, 17},
+      {0, 1, 42},
+      {0, 4, 43},
+      {0, 35, 44},
+      {0, 34, 45},
+      {0, 19, 46},
+      {0, 7, 29},
+      {0, 16, 47},
+      {0, 48, 32},
+      {0, 49, 27},
+      {0, 11, 14},
+      {0, 18, 28},
+      {0, 23, 50},
+      {0, 51, 12},
+      {0, 52, 21},
+      {0, 6, 53},
+      {0, 55, 54},
+      {0, 57, 56},
+      {0, 3, 58},
+      {0, 13, 59},
+      {0, 60, 8},
+      {0, 30, 61},
+      {0, 62, 2},
+      {0, 64, 63},
+      {0, 66, 65},
+      {0, 68, 67},
+    }));
+
+    codecs.emplace(SpvOpCompositeConstruct, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(39, {
+      {0, 0, 0},
+      {65556, 0, 0},
+      {131094, 0, 0},
+      {131105, 0, 0},
+      {196629, 0, 0},
+      {196631, 0, 0},
+      {196632, 0, 0},
+      {196640, 0, 0},
+      {196641, 0, 0},
+      {262177, 0, 0},
+      {327713, 0, 0},
+      {393249, 0, 0},
+      {458785, 0, 0},
+      {524313, 0, 0},
+      {524321, 0, 0},
+      {589857, 0, 0},
+      {655393, 0, 0},
+      {786465, 0, 0},
+      {917537, 0, 0},
+      {1048609, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 19, 18},
+      {0, 21, 15},
+      {0, 1, 22},
+      {0, 16, 23},
+      {0, 14, 24},
+      {0, 20, 25},
+      {0, 13, 17},
+      {0, 3, 26},
+      {0, 6, 11},
+      {0, 27, 12},
+      {0, 4, 28},
+      {0, 29, 10},
+      {0, 9, 30},
+      {0, 7, 31},
+      {0, 33, 32},
+      {0, 34, 5},
+      {0, 8, 35},
+      {0, 2, 36},
+      {0, 38, 37},
+    }));
+
+    codecs.emplace(SpvOpTypeFunction, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
+      {0, 0, 0},
+      {131086, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 2},
+    }));
+
+    codecs.emplace(SpvOpExtInstImport, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
+      {0, 0, 0},
+      {131099, 0, 0},
+      {196640, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 2, 3},
+      {0, 1, 4},
+    }));
+
+    codecs.emplace(SpvOpTypeImage, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
+      {0, 0, 0},
+      {131143, 0, 0},
+      {196679, 0, 0},
+      {196680, 0, 0},
+      {262216, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 5, 2},
+      {0, 3, 6},
+      {0, 7, 1},
+      {0, 4, 8},
+    }));
+
+    codecs.emplace(SpvOpMemberDecorate, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
+      {0, 0, 0},
+      {65553, 0, 0},
+      {131083, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 3},
+      {0, 2, 4},
+    }));
+
+    codecs.emplace(SpvOpCapability, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(17, {
+      {0, 0, 0},
+      {196629, 0, 0},
+      {196631, 0, 0},
+      {196632, 0, 0},
+      {196640, 0, 0},
+      {196651, 0, 0},
+      {196667, 0, 0},
+      {327713, 0, 0},
+      {458785, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 7, 8},
+      {0, 1, 10},
+      {0, 6, 11},
+      {0, 9, 12},
+      {0, 4, 13},
+      {0, 3, 14},
+      {0, 15, 2},
+      {0, 5, 16},
+    }));
+
+    codecs.emplace(SpvOpTypeInt, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(29, {
+      {0, 0, 0},
+      {65556, 0, 0},
+      {131073, 0, 0},
+      {196629, 0, 0},
+      {196631, 0, 0},
+      {196632, 0, 0},
+      {196636, 0, 0},
+      {196640, 0, 0},
+      {196651, 0, 0},
+      {196667, 0, 0},
+      {262188, 0, 0},
+      {262198, 0, 0},
+      {327724, 0, 0},
+      {393260, 0, 0},
+      {524313, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 2, 6},
+      {0, 16, 3},
+      {0, 11, 17},
+      {0, 5, 18},
+      {0, 15, 19},
+      {0, 13, 20},
+      {0, 1, 4},
+      {0, 12, 21},
+      {0, 7, 22},
+      {0, 14, 23},
+      {0, 24, 10},
+      {0, 25, 9},
+      {0, 27, 26},
+      {0, 8, 28},
+    }));
+
+    codecs.emplace(SpvOpConstantComposite, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(15, {
+      {0, 0, 0},
+      {65556, 0, 0},
+      {196631, 0, 0},
+      {196640, 0, 0},
+      {196651, 0, 0},
+      {196667, 0, 0},
+      {327724, 0, 0},
+      {393260, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 6, 7},
+      {0, 1, 9},
+      {0, 10, 8},
+      {0, 2, 11},
+      {0, 5, 12},
+      {0, 13, 4},
+      {0, 3, 14},
+    }));
+
+    codecs.emplace(SpvOpTypeSampledImage, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(21, {
+      {0, 0, 0},
+      {131073, 0, 0},
+      {196629, 0, 0},
+      {196631, 0, 0},
+      {196632, 0, 0},
+      {196636, 0, 0},
+      {196640, 0, 0},
+      {196641, 0, 0},
+      {196651, 0, 0},
+      {196667, 0, 0},
+      {262198, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 3, 5},
+      {0, 11, 12},
+      {0, 8, 13},
+      {0, 7, 14},
+      {0, 4, 10},
+      {0, 9, 2},
+      {0, 16, 15},
+      {0, 1, 17},
+      {0, 19, 18},
+      {0, 6, 20},
+    }));
+
+    codecs.emplace(SpvOpTypeStruct, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(49, {
+      {0, 0, 0},
+      {65785, 0, 0},
+      {65790, 0, 0},
+      {131134, 0, 0},
+      {196669, 0, 0},
+      {196735, 0, 0},
+      {262201, 0, 0},
+      {262209, 0, 0},
+      {262224, 0, 0},
+      {262225, 0, 0},
+      {262272, 0, 0},
+      {262273, 0, 0},
+      {262275, 0, 0},
+      {262277, 0, 0},
+      {262286, 0, 0},
+      {262292, 0, 0},
+      {327692, 0, 0},
+      {327745, 0, 0},
+      {327760, 0, 0},
+      {327762, 0, 0},
+      {393228, 0, 0},
+      {393295, 0, 0},
+      {393296, 0, 0},
+      {458764, 0, 0},
+      {458831, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 20, 12},
+      {0, 26, 24},
+      {0, 21, 27},
+      {0, 28, 16},
+      {0, 10, 8},
+      {0, 30, 29},
+      {0, 31, 17},
+      {0, 32, 13},
+      {0, 25, 6},
+      {0, 1, 33},
+      {0, 14, 11},
+      {0, 3, 34},
+      {0, 18, 35},
+      {0, 37, 36},
+      {0, 23, 5},
+      {0, 38, 2},
+      {0, 39, 7},
+      {0, 4, 9},
+      {0, 40, 19},
+      {0, 42, 41},
+      {0, 43, 22},
+      {0, 45, 44},
+      {0, 46, 15},
+      {0, 48, 47},
+    }));
+
+    codecs.emplace(SpvOpFNegate, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(11, {
+      {0, 0, 0},
+      {65555, 0, 0},
+      {131143, 0, 0},
+      {196679, 0, 0},
+      {196680, 0, 0},
+      {262216, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 4, 6},
+      {0, 1, 2},
+      {0, 8, 7},
+      {0, 5, 9},
+      {0, 3, 10},
+    }));
+
+    codecs.emplace(SpvOpDecorate, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(25, {
+      {0, 0, 0},
+      {65562, 0, 0},
+      {196629, 0, 0},
+      {196631, 0, 0},
+      {196632, 0, 0},
+      {196636, 0, 0},
+      {196640, 0, 0},
+      {196641, 0, 0},
+      {196651, 0, 0},
+      {196667, 0, 0},
+      {262177, 0, 0},
+      {262198, 0, 0},
+      {327713, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 12, 11},
+      {0, 9, 14},
+      {0, 10, 15},
+      {0, 13, 16},
+      {0, 4, 17},
+      {0, 2, 1},
+      {0, 18, 7},
+      {0, 20, 19},
+      {0, 21, 3},
+      {0, 22, 6},
+      {0, 5, 8},
+      {0, 24, 23},
+    }));
+
+    codecs.emplace(SpvOpTypeMatrix, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(31, {
+      {0, 0, 0},
+      {65556, 0, 0},
+      {131073, 0, 0},
+      {131094, 0, 0},
+      {196629, 0, 0},
+      {196631, 0, 0},
+      {196632, 0, 0},
+      {196636, 0, 0},
+      {196640, 0, 0},
+      {196651, 0, 0},
+      {196667, 0, 0},
+      {262188, 0, 0},
+      {262198, 0, 0},
+      {327724, 0, 0},
+      {393260, 0, 0},
+      {524313, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 12, 2},
+      {0, 17, 3},
+      {0, 5, 18},
+      {0, 1, 19},
+      {0, 16, 4},
+      {0, 21, 20},
+      {0, 6, 15},
+      {0, 7, 22},
+      {0, 24, 23},
+      {0, 13, 14},
+      {0, 25, 8},
+      {0, 26, 11},
+      {0, 27, 10},
+      {0, 29, 28},
+      {0, 30, 9},
+    }));
+
+    codecs.emplace(SpvOpConstant, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(33, {
+      {0, 0, 0},
+      {131113, 0, 0},
+      {196629, 0, 0},
+      {196631, 0, 0},
+      {196632, 0, 0},
+      {196640, 0, 0},
+      {196641, 0, 0},
+      {196651, 0, 0},
+      {196667, 0, 0},
+      {262188, 0, 0},
+      {262198, 0, 0},
+      {327713, 0, 0},
+      {327724, 0, 0},
+      {393249, 0, 0},
+      {393260, 0, 0},
+      {524313, 0, 0},
+      {524321, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 6, 4},
+      {0, 13, 11},
+      {0, 16, 15},
+      {0, 18, 10},
+      {0, 20, 19},
+      {0, 21, 2},
+      {0, 23, 22},
+      {0, 8, 24},
+      {0, 9, 25},
+      {0, 17, 26},
+      {0, 14, 27},
+      {0, 12, 28},
+      {0, 1, 3},
+      {0, 5, 29},
+      {0, 30, 7},
+      {0, 32, 31},
+    }));
+
+    codecs.emplace(SpvOpTypeBool, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(11, {
+      {0, 0, 0},
+      {196636, 0, 0},
+      {196640, 0, 0},
+      {196651, 0, 0},
+      {196667, 0, 0},
+      {524313, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 4, 5},
+      {0, 3, 7},
+      {0, 2, 8},
+      {0, 6, 9},
+      {0, 1, 10},
+    }));
+
+    codecs.emplace(SpvOpTypeArray, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(67, {
+      {0, 0, 0},
+      {65785, 0, 0},
+      {65790, 0, 0},
+      {131134, 0, 0},
+      {131319, 0, 0},
+      {196669, 0, 0},
+      {196735, 0, 0},
+      {262201, 0, 0},
+      {262209, 0, 0},
+      {262224, 0, 0},
+      {262225, 0, 0},
+      {262231, 0, 0},
+      {262272, 0, 0},
+      {262273, 0, 0},
+      {262275, 0, 0},
+      {262277, 0, 0},
+      {262280, 0, 0},
+      {262286, 0, 0},
+      {262292, 0, 0},
+      {262334, 0, 0},
+      {327692, 0, 0},
+      {327737, 0, 0},
+      {327745, 0, 0},
+      {327760, 0, 0},
+      {327761, 0, 0},
+      {327762, 0, 0},
+      {393228, 0, 0},
+      {393273, 0, 0},
+      {393281, 0, 0},
+      {393295, 0, 0},
+      {393296, 0, 0},
+      {458764, 0, 0},
+      {458831, 0, 0},
+      {524367, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 7, 27},
+      {0, 11, 28},
+      {0, 35, 21},
+      {0, 36, 1},
+      {0, 4, 37},
+      {0, 39, 38},
+      {0, 40, 30},
+      {0, 41, 12},
+      {0, 19, 42},
+      {0, 13, 43},
+      {0, 16, 44},
+      {0, 45, 22},
+      {0, 34, 18},
+      {0, 29, 24},
+      {0, 46, 25},
+      {0, 6, 2},
+      {0, 9, 31},
+      {0, 17, 47},
+      {0, 49, 48},
+      {0, 50, 33},
+      {0, 51, 26},
+      {0, 20, 52},
+      {0, 32, 53},
+      {0, 3, 54},
+      {0, 15, 14},
+      {0, 23, 55},
+      {0, 8, 56},
+      {0, 58, 57},
+      {0, 10, 59},
+      {0, 5, 60},
+      {0, 62, 61},
+      {0, 64, 63},
+      {0, 66, 65},
+    }));
+
+    codecs.emplace(SpvOpExtInst, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(57, {
+      {0, 0, 0},
+      {65790, 0, 0},
+      {131134, 0, 0},
+      {196665, 0, 0},
+      {196669, 0, 0},
+      {196718, 0, 0},
+      {262201, 0, 0},
+      {262209, 0, 0},
+      {262224, 0, 0},
+      {262225, 0, 0},
+      {262231, 0, 0},
+      {262273, 0, 0},
+      {262275, 0, 0},
+      {262277, 0, 0},
+      {262280, 0, 0},
+      {262286, 0, 0},
+      {262292, 0, 0},
+      {327692, 0, 0},
+      {327745, 0, 0},
+      {327760, 0, 0},
+      {327762, 0, 0},
+      {393228, 0, 0},
+      {393273, 0, 0},
+      {393295, 0, 0},
+      {393296, 0, 0},
+      {393303, 0, 0},
+      {458764, 0, 0},
+      {458831, 0, 0},
+      {524367, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 18, 6},
+      {0, 30, 22},
+      {0, 31, 25},
+      {0, 10, 32},
+      {0, 21, 33},
+      {0, 3, 34},
+      {0, 35, 5},
+      {0, 23, 36},
+      {0, 14, 17},
+      {0, 37, 26},
+      {0, 1, 38},
+      {0, 29, 39},
+      {0, 13, 40},
+      {0, 41, 19},
+      {0, 28, 20},
+      {0, 16, 42},
+      {0, 27, 43},
+      {0, 8, 24},
+      {0, 7, 44},
+      {0, 9, 45},
+      {0, 15, 46},
+      {0, 12, 47},
+      {0, 48, 2},
+      {0, 4, 49},
+      {0, 51, 50},
+      {0, 11, 52},
+      {0, 54, 53},
+      {0, 56, 55},
+    }));
+
+    codecs.emplace(SpvOpVectorTimesScalar, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(67, {
+      {0, 0, 0},
+      {65785, 0, 0},
+      {65790, 0, 0},
+      {131134, 0, 0},
+      {196669, 0, 0},
+      {196735, 0, 0},
+      {262201, 0, 0},
+      {262209, 0, 0},
+      {262224, 0, 0},
+      {262225, 0, 0},
+      {262230, 0, 0},
+      {262231, 0, 0},
+      {262272, 0, 0},
+      {262273, 0, 0},
+      {262275, 0, 0},
+      {262277, 0, 0},
+      {262280, 0, 0},
+      {262286, 0, 0},
+      {262292, 0, 0},
+      {327692, 0, 0},
+      {327737, 0, 0},
+      {327745, 0, 0},
+      {327760, 0, 0},
+      {327761, 0, 0},
+      {327762, 0, 0},
+      {393228, 0, 0},
+      {393273, 0, 0},
+      {393295, 0, 0},
+      {393296, 0, 0},
+      {393303, 0, 0},
+      {393304, 0, 0},
+      {458764, 0, 0},
+      {458831, 0, 0},
+      {524367, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 26, 29},
+      {0, 20, 35},
+      {0, 12, 36},
+      {0, 6, 37},
+      {0, 38, 28},
+      {0, 30, 5},
+      {0, 8, 39},
+      {0, 2, 40},
+      {0, 41, 21},
+      {0, 1, 10},
+      {0, 43, 42},
+      {0, 23, 16},
+      {0, 44, 33},
+      {0, 34, 31},
+      {0, 14, 45},
+      {0, 19, 46},
+      {0, 25, 47},
+      {0, 49, 48},
+      {0, 27, 22},
+      {0, 7, 50},
+      {0, 17, 32},
+      {0, 18, 51},
+      {0, 24, 52},
+      {0, 54, 53},
+      {0, 55, 9},
+      {0, 56, 11},
+      {0, 57, 4},
+      {0, 15, 58},
+      {0, 59, 13},
+      {0, 60, 3},
+      {0, 62, 61},
+      {0, 64, 63},
+      {0, 66, 65},
+    }));
+
+    codecs.emplace(SpvOpVectorShuffle, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(33, {
+      {0, 0, 0},
+      {65790, 0, 0},
+      {131134, 0, 0},
+      {196669, 0, 0},
+      {262201, 0, 0},
+      {262209, 0, 0},
+      {262225, 0, 0},
+      {262231, 0, 0},
+      {262273, 0, 0},
+      {262277, 0, 0},
+      {262286, 0, 0},
+      {262292, 0, 0},
+      {327745, 0, 0},
+      {393281, 0, 0},
+      {393295, 0, 0},
+      {393296, 0, 0},
+      {458831, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 13, 12},
+      {0, 1, 18},
+      {0, 19, 11},
+      {0, 9, 20},
+      {0, 10, 21},
+      {0, 22, 15},
+      {0, 23, 8},
+      {0, 4, 24},
+      {0, 25, 7},
+      {0, 17, 26},
+      {0, 5, 27},
+      {0, 14, 3},
+      {0, 29, 28},
+      {0, 30, 2},
+      {0, 6, 31},
+      {0, 32, 16},
+    }));
+
+    codecs.emplace(SpvOpImageSampleImplicitLod, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(55, {
+      {0, 0, 0},
+      {65785, 0, 0},
+      {65790, 0, 0},
+      {131134, 0, 0},
+      {196669, 0, 0},
+      {196735, 0, 0},
+      {196817, 0, 0},
+      {262209, 0, 0},
+      {262224, 0, 0},
+      {262225, 0, 0},
+      {262273, 0, 0},
+      {262275, 0, 0},
+      {262277, 0, 0},
+      {262280, 0, 0},
+      {262286, 0, 0},
+      {262292, 0, 0},
+      {327692, 0, 0},
+      {327745, 0, 0},
+      {327760, 0, 0},
+      {327761, 0, 0},
+      {327762, 0, 0},
+      {393228, 0, 0},
+      {393281, 0, 0},
+      {393295, 0, 0},
+      {393296, 0, 0},
+      {393298, 0, 0},
+      {458764, 0, 0},
+      {458831, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 5, 2},
+      {0, 22, 29},
+      {0, 30, 1},
+      {0, 6, 31},
+      {0, 9, 32},
+      {0, 28, 3},
+      {0, 27, 33},
+      {0, 20, 16},
+      {0, 34, 8},
+      {0, 10, 35},
+      {0, 4, 36},
+      {0, 24, 23},
+      {0, 21, 13},
+      {0, 7, 37},
+      {0, 38, 14},
+      {0, 25, 39},
+      {0, 17, 11},
+      {0, 12, 19},
+      {0, 41, 40},
+      {0, 42, 18},
+      {0, 15, 43},
+      {0, 45, 44},
+      {0, 47, 46},
+      {0, 26, 48},
+      {0, 50, 49},
+      {0, 52, 51},
+      {0, 54, 53},
+    }));
+
+    codecs.emplace(SpvOpDot, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(11, {
+      {0, 0, 0},
+      {131075, 0, 0},
+      {131088, 0, 0},
+      {196624, 0, 0},
+      {196679, 0, 0},
+      {262216, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 5, 3},
+      {0, 2, 7},
+      {0, 1, 8},
+      {0, 6, 9},
+      {0, 4, 10},
+    }));
+
+    codecs.emplace(SpvOpExecutionMode, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
+      {0, 0, 0},
+      {196858, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 2},
+    }));
+
+    codecs.emplace(SpvOpSelectionMerge, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(23, {
+      {0, 0, 0},
+      {131134, 0, 0},
+      {196669, 0, 0},
+      {262209, 0, 0},
+      {262224, 0, 0},
+      {262225, 0, 0},
+      {262277, 0, 0},
+      {327745, 0, 0},
+      {327761, 0, 0},
+      {327762, 0, 0},
+      {393295, 0, 0},
+      {393296, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 2, 12},
+      {0, 7, 13},
+      {0, 5, 1},
+      {0, 4, 10},
+      {0, 14, 6},
+      {0, 16, 15},
+      {0, 17, 11},
+      {0, 3, 8},
+      {0, 19, 18},
+      {0, 9, 20},
+      {0, 22, 21},
+    }));
+
+    codecs.emplace(SpvOpImageSampleDrefExplicitLod, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
+      {0, 0, 0},
+      {65790, 0, 0},
+      {131073, 0, 0},
+      {262198, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 4, 1},
+      {0, 3, 5},
+      {0, 2, 6},
+    }));
+
+    codecs.emplace(SpvOpUndef, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(59, {
+      {0, 0, 0},
+      {65785, 0, 0},
+      {131134, 0, 0},
+      {131319, 0, 0},
+      {196669, 0, 0},
+      {196735, 0, 0},
+      {262209, 0, 0},
+      {262221, 0, 0},
+      {262224, 0, 0},
+      {262225, 0, 0},
+      {262230, 0, 0},
+      {262273, 0, 0},
+      {262275, 0, 0},
+      {262277, 0, 0},
+      {262280, 0, 0},
+      {262286, 0, 0},
+      {262288, 0, 0},
+      {262292, 0, 0},
+      {262334, 0, 0},
+      {327692, 0, 0},
+      {327760, 0, 0},
+      {327761, 0, 0},
+      {327762, 0, 0},
+      {393228, 0, 0},
+      {393295, 0, 0},
+      {393296, 0, 0},
+      {393298, 0, 0},
+      {458764, 0, 0},
+      {458831, 0, 0},
+      {524367, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 17, 3},
+      {0, 5, 31},
+      {0, 11, 32},
+      {0, 33, 12},
+      {0, 34, 20},
+      {0, 16, 27},
+      {0, 35, 23},
+      {0, 37, 36},
+      {0, 14, 18},
+      {0, 39, 38},
+      {0, 7, 30},
+      {0, 8, 25},
+      {0, 40, 15},
+      {0, 13, 2},
+      {0, 1, 29},
+      {0, 19, 41},
+      {0, 43, 42},
+      {0, 28, 44},
+      {0, 46, 45},
+      {0, 22, 21},
+      {0, 47, 24},
+      {0, 48, 26},
+      {0, 10, 6},
+      {0, 50, 49},
+      {0, 52, 51},
+      {0, 54, 53},
+      {0, 4, 9},
+      {0, 56, 55},
+      {0, 58, 57},
+    }));
+
+    codecs.emplace(SpvOpCompositeInsert, std::move(codec));
+  }
+
+  return codecs;
+}
+
+std::map<uint32_t, std::unique_ptr<HuffmanCodec<std::string>>>
+GetLiteralStringHuffmanCodecs() {
+  std::map<uint32_t, std::unique_ptr<HuffmanCodec<std::string>>> codecs;
+  {
+    std::unique_ptr<HuffmanCodec<std::string>> codec(new HuffmanCodec<std::string>(7, {
+      {"", 0, 0},
+      {"MainPs", 0, 0},
+      {"MainVs", 0, 0},
+      {"kMarkvNoneOfTheAbove", 0, 0},
+      {"main", 0, 0},
+      {"", 2, 3},
+      {"", 1, 5},
+      {"", 4, 6},
+    }));
+
+    codecs.emplace(SpvOpEntryPoint, std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<std::string>> codec(new HuffmanCodec<std::string>(3, {
+      {"", 0, 0},
+      {"GLSL.std.450", 0, 0},
+      {"kMarkvNoneOfTheAbove", 0, 0},
+      {"", 1, 2},
+    }));
+
+    codecs.emplace(SpvOpExtInstImport, std::move(codec));
+  }
+
+  return codecs;
+}
+
+std::map<std::pair<uint32_t, uint32_t>, std::unique_ptr<HuffmanCodec<uint64_t>>>
+GetNonIdWordHuffmanCodecs() {
+  std::map<std::pair<uint32_t, uint32_t>, std::unique_ptr<HuffmanCodec<uint64_t>>> codecs;
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(33, {
+      {0, 0, 0},
+      {4, 0, 0},
+      {8, 0, 0},
+      {10, 0, 0},
+      {26, 0, 0},
+      {29, 0, 0},
+      {31, 0, 0},
+      {37, 0, 0},
+      {40, 0, 0},
+      {43, 0, 0},
+      {46, 0, 0},
+      {49, 0, 0},
+      {66, 0, 0},
+      {67, 0, 0},
+      {68, 0, 0},
+      {69, 0, 0},
+      {71, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 12, 5},
+      {0, 18, 13},
+      {0, 3, 7},
+      {0, 19, 11},
+      {0, 20, 16},
+      {0, 14, 17},
+      {0, 21, 1},
+      {0, 2, 6},
+      {0, 23, 22},
+      {0, 4, 24},
+      {0, 26, 25},
+      {0, 28, 27},
+      {0, 10, 15},
+      {0, 8, 9},
+      {0, 30, 29},
+      {0, 32, 31},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpExtInst, 3), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
+      {0, 0, 0},
+      {0, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 2},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpMemoryModel, 0), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
+      {0, 0, 0},
+      {1, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 2},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpMemoryModel, 1), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
+      {0, 0, 0},
+      {0, 0, 0},
+      {4, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 3},
+      {0, 2, 4},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpEntryPoint, 0), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
+      {0, 0, 0},
+      {7, 0, 0},
+      {8, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 3, 2},
+      {0, 1, 4},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpExecutionMode, 1), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(11, {
+      {0, 0, 0},
+      {1, 0, 0},
+      {2, 0, 0},
+      {3, 0, 0},
+      {4, 0, 0},
+      {18, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 4, 2},
+      {0, 6, 5},
+      {0, 7, 1},
+      {0, 3, 8},
+      {0, 10, 9},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpExecutionMode, 2), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
+      {0, 0, 0},
+      {1, 0, 0},
+      {32, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 2, 3},
+      {0, 1, 4},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpCapability, 0), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
+      {0, 0, 0},
+      {32, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 2},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeInt, 1), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
+      {0, 0, 0},
+      {0, 0, 0},
+      {1, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 2, 3},
+      {0, 1, 4},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeInt, 2), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
+      {0, 0, 0},
+      {32, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 2},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeFloat, 1), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
+      {0, 0, 0},
+      {2, 0, 0},
+      {3, 0, 0},
+      {4, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 2, 4},
+      {0, 1, 5},
+      {0, 6, 3},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeVector, 2), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
+      {0, 0, 0},
+      {2, 0, 0},
+      {3, 0, 0},
+      {4, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 4},
+      {0, 2, 5},
+      {0, 3, 6},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeMatrix, 2), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
+      {0, 0, 0},
+      {1, 0, 0},
+      {2, 0, 0},
+      {3, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 3, 4},
+      {0, 2, 5},
+      {0, 1, 6},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeImage, 2), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
+      {0, 0, 0},
+      {0, 0, 0},
+      {1, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 2, 3},
+      {0, 1, 4},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeImage, 3), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
+      {0, 0, 0},
+      {0, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 2},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeImage, 4), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
+      {0, 0, 0},
+      {0, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 2},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeImage, 5), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
+      {0, 0, 0},
+      {1, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 2},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeImage, 6), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
+      {0, 0, 0},
+      {0, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 2},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeImage, 7), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(13, {
+      {0, 0, 0},
+      {0, 0, 0},
+      {1, 0, 0},
+      {2, 0, 0},
+      {3, 0, 0},
+      {6, 0, 0},
+      {7, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 5, 7},
+      {0, 6, 8},
+      {0, 1, 4},
+      {0, 2, 9},
+      {0, 10, 3},
+      {0, 12, 11},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypePointer, 1), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(173, {
+      {0, 0, 0},
+      {0, 0, 0},
+      {1, 0, 0},
+      {2, 0, 0},
+      {3, 0, 0},
+      {4, 0, 0},
+      {5, 0, 0},
+      {6, 0, 0},
+      {7, 0, 0},
+      {8, 0, 0},
+      {9, 0, 0},
+      {10, 0, 0},
+      {11, 0, 0},
+      {12, 0, 0},
+      {13, 0, 0},
+      {14, 0, 0},
+      {15, 0, 0},
+      {16, 0, 0},
+      {17, 0, 0},
+      {18, 0, 0},
+      {19, 0, 0},
+      {20, 0, 0},
+      {21, 0, 0},
+      {22, 0, 0},
+      {23, 0, 0},
+      {24, 0, 0},
+      {26, 0, 0},
+      {27, 0, 0},
+      {28, 0, 0},
+      {29, 0, 0},
+      {30, 0, 0},
+      {31, 0, 0},
+      {32, 0, 0},
+      {256, 0, 0},
+      {507307272, 0, 0},
+      {864026611, 0, 0},
+      {981668463, 0, 0},
+      {997553156, 0, 0},
+      {1014330372, 0, 0},
+      {1020708227, 0, 0},
+      {1028443341, 0, 0},
+      {1032953056, 0, 0},
+      {1033463938, 0, 0},
+      {1033463943, 0, 0},
+      {1039998884, 0, 0},
+      {1039998950, 0, 0},
+      {1040187392, 0, 0},
+      {1042401985, 0, 0},
+      {1044220635, 0, 0},
+      {1045622707, 0, 0},
+      {1045622740, 0, 0},
+      {1048576000, 0, 0},
+      {1053609165, 0, 0},
+      {1053790359, 0, 0},
+      {1054448026, 0, 0},
+      {1055437881, 0, 0},
+      {1056300230, 0, 0},
+      {1056964608, 0, 0},
+      {1058056805, 0, 0},
+      {1059286575, 0, 0},
+      {1061158912, 0, 0},
+      {1061997773, 0, 0},
+      {1064514355, 0, 0},
+      {1064854933, 0, 0},
+      {1065353216, 0, 0},
+      {1069547520, 0, 0},
+      {1073741824, 0, 0},
+      {1077936128, 0, 0},
+      {1082130432, 0, 0},
+      {1091567616, 0, 0},
+      {1115422720, 0, 0},
+      {1124073472, 0, 0},
+      {1132396544, 0, 0},
+      {1140850688, 0, 0},
+      {1199562752, 0, 0},
+      {3179067684, 0, 0},
+      {3180973575, 0, 0},
+      {3182651297, 0, 0},
+      {3196448879, 0, 0},
+      {3204448256, 0, 0},
+      {3204993516, 0, 0},
+      {3205248529, 0, 0},
+      {3207137644, 0, 0},
+      {3208642560, 0, 0},
+      {3211081967, 0, 0},
+      {3212836864, 0, 0},
+      {3332128768, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 38, 37},
+      {0, 42, 39},
+      {0, 49, 44},
+      {0, 45, 43},
+      {0, 26, 50},
+      {0, 46, 73},
+      {0, 35, 28},
+      {0, 32, 65},
+      {0, 83, 40},
+      {0, 60, 62},
+      {0, 27, 54},
+      {0, 79, 67},
+      {0, 31, 74},
+      {0, 51, 12},
+      {0, 70, 30},
+      {0, 15, 16},
+      {0, 88, 25},
+      {0, 90, 89},
+      {0, 34, 71},
+      {0, 72, 29},
+      {0, 92, 91},
+      {0, 14, 33},
+      {0, 94, 93},
+      {0, 22, 23},
+      {0, 21, 95},
+      {0, 19, 24},
+      {0, 96, 13},
+      {0, 47, 41},
+      {0, 53, 48},
+      {0, 58, 56},
+      {0, 63, 59},
+      {0, 76, 75},
+      {0, 78, 77},
+      {0, 81, 80},
+      {0, 84, 82},
+      {0, 52, 20},
+      {0, 97, 69},
+      {0, 99, 98},
+      {0, 18, 10},
+      {0, 68, 61},
+      {0, 17, 100},
+      {0, 102, 101},
+      {0, 11, 36},
+      {0, 104, 103},
+      {0, 86, 105},
+      {0, 107, 106},
+      {0, 109, 108},
+      {0, 110, 9},
+      {0, 8, 111},
+      {0, 113, 112},
+      {0, 115, 114},
+      {0, 117, 116},
+      {0, 119, 118},
+      {0, 121, 120},
+      {0, 123, 122},
+      {0, 125, 124},
+      {0, 126, 7},
+      {0, 127, 85},
+      {0, 6, 128},
+      {0, 129, 55},
+      {0, 130, 5},
+      {0, 132, 131},
+      {0, 134, 133},
+      {0, 136, 135},
+      {0, 137, 66},
+      {0, 139, 138},
+      {0, 141, 140},
+      {0, 143, 142},
+      {0, 145, 144},
+      {0, 146, 57},
+      {0, 147, 64},
+      {0, 148, 4},
+      {0, 149, 2},
+      {0, 151, 150},
+      {0, 152, 3},
+      {0, 154, 153},
+      {0, 156, 155},
+      {0, 158, 157},
+      {0, 159, 1},
+      {0, 160, 87},
+      {0, 162, 161},
+      {0, 164, 163},
+      {0, 166, 165},
+      {0, 168, 167},
+      {0, 170, 169},
+      {0, 172, 171},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpConstant, 2), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
+      {0, 0, 0},
+      {0, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 2},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunction, 2), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(13, {
+      {0, 0, 0},
+      {0, 0, 0},
+      {1, 0, 0},
+      {2, 0, 0},
+      {3, 0, 0},
+      {6, 0, 0},
+      {7, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 3, 7},
+      {0, 4, 8},
+      {0, 9, 2},
+      {0, 1, 5},
+      {0, 10, 6},
+      {0, 12, 11},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpVariable, 2), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(15, {
+      {0, 0, 0},
+      {0, 0, 0},
+      {2, 0, 0},
+      {6, 0, 0},
+      {11, 0, 0},
+      {30, 0, 0},
+      {33, 0, 0},
+      {34, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 4, 8},
+      {0, 9, 1},
+      {0, 3, 10},
+      {0, 6, 11},
+      {0, 12, 2},
+      {0, 7, 5},
+      {0, 14, 13},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpDecorate, 1), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(37, {
+      {0, 0, 0},
+      {0, 0, 0},
+      {1, 0, 0},
+      {2, 0, 0},
+      {3, 0, 0},
+      {4, 0, 0},
+      {5, 0, 0},
+      {6, 0, 0},
+      {7, 0, 0},
+      {8, 0, 0},
+      {9, 0, 0},
+      {10, 0, 0},
+      {12, 0, 0},
+      {13, 0, 0},
+      {14, 0, 0},
+      {15, 0, 0},
+      {16, 0, 0},
+      {18, 0, 0},
+      {64, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 17, 11},
+      {0, 10, 13},
+      {0, 12, 14},
+      {0, 21, 20},
+      {0, 9, 22},
+      {0, 19, 15},
+      {0, 8, 23},
+      {0, 18, 24},
+      {0, 25, 7},
+      {0, 5, 6},
+      {0, 26, 16},
+      {0, 27, 4},
+      {0, 28, 3},
+      {0, 30, 29},
+      {0, 31, 2},
+      {0, 33, 32},
+      {0, 35, 34},
+      {0, 1, 36},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpDecorate, 2), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(79, {
+      {0, 0, 0},
+      {0, 0, 0},
+      {1, 0, 0},
+      {2, 0, 0},
+      {3, 0, 0},
+      {4, 0, 0},
+      {5, 0, 0},
+      {6, 0, 0},
+      {7, 0, 0},
+      {8, 0, 0},
+      {9, 0, 0},
+      {10, 0, 0},
+      {11, 0, 0},
+      {12, 0, 0},
+      {13, 0, 0},
+      {14, 0, 0},
+      {15, 0, 0},
+      {16, 0, 0},
+      {17, 0, 0},
+      {18, 0, 0},
+      {19, 0, 0},
+      {20, 0, 0},
+      {21, 0, 0},
+      {22, 0, 0},
+      {23, 0, 0},
+      {24, 0, 0},
+      {25, 0, 0},
+      {26, 0, 0},
+      {27, 0, 0},
+      {28, 0, 0},
+      {29, 0, 0},
+      {30, 0, 0},
+      {31, 0, 0},
+      {32, 0, 0},
+      {33, 0, 0},
+      {34, 0, 0},
+      {35, 0, 0},
+      {36, 0, 0},
+      {37, 0, 0},
+      {38, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 39, 37},
+      {0, 40, 36},
+      {0, 34, 35},
+      {0, 32, 33},
+      {0, 30, 31},
+      {0, 27, 29},
+      {0, 26, 28},
+      {0, 42, 41},
+      {0, 23, 25},
+      {0, 38, 22},
+      {0, 44, 43},
+      {0, 46, 45},
+      {0, 21, 47},
+      {0, 19, 20},
+      {0, 17, 18},
+      {0, 14, 15},
+      {0, 12, 10},
+      {0, 16, 13},
+      {0, 9, 11},
+      {0, 7, 8},
+      {0, 6, 5},
+      {0, 24, 48},
+      {0, 50, 49},
+      {0, 3, 4},
+      {0, 51, 2},
+      {0, 1, 52},
+      {0, 54, 53},
+      {0, 56, 55},
+      {0, 58, 57},
+      {0, 60, 59},
+      {0, 62, 61},
+      {0, 64, 63},
+      {0, 66, 65},
+      {0, 68, 67},
+      {0, 70, 69},
+      {0, 72, 71},
+      {0, 74, 73},
+      {0, 76, 75},
+      {0, 78, 77},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpMemberDecorate, 1), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
+      {0, 0, 0},
+      {4, 0, 0},
+      {7, 0, 0},
+      {35, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 4},
+      {0, 5, 2},
+      {0, 3, 6},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpMemberDecorate, 2), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(149, {
+      {0, 0, 0},
+      {0, 0, 0},
+      {16, 0, 0},
+      {28, 0, 0},
+      {32, 0, 0},
+      {36, 0, 0},
+      {40, 0, 0},
+      {44, 0, 0},
+      {48, 0, 0},
+      {60, 0, 0},
+      {64, 0, 0},
+      {76, 0, 0},
+      {80, 0, 0},
+      {84, 0, 0},
+      {88, 0, 0},
+      {92, 0, 0},
+      {96, 0, 0},
+      {100, 0, 0},
+      {108, 0, 0},
+      {112, 0, 0},
+      {120, 0, 0},
+      {124, 0, 0},
+      {128, 0, 0},
+      {132, 0, 0},
+      {136, 0, 0},
+      {140, 0, 0},
+      {144, 0, 0},
+      {148, 0, 0},
+      {152, 0, 0},
+      {156, 0, 0},
+      {160, 0, 0},
+      {172, 0, 0},
+      {176, 0, 0},
+      {192, 0, 0},
+      {204, 0, 0},
+      {208, 0, 0},
+      {224, 0, 0},
+      {236, 0, 0},
+      {240, 0, 0},
+      {248, 0, 0},
+      {256, 0, 0},
+      {272, 0, 0},
+      {288, 0, 0},
+      {292, 0, 0},
+      {296, 0, 0},
+      {300, 0, 0},
+      {304, 0, 0},
+      {316, 0, 0},
+      {320, 0, 0},
+      {332, 0, 0},
+      {336, 0, 0},
+      {348, 0, 0},
+      {352, 0, 0},
+      {364, 0, 0},
+      {368, 0, 0},
+      {372, 0, 0},
+      {376, 0, 0},
+      {384, 0, 0},
+      {392, 0, 0},
+      {400, 0, 0},
+      {416, 0, 0},
+      {424, 0, 0},
+      {432, 0, 0},
+      {448, 0, 0},
+      {460, 0, 0},
+      {464, 0, 0},
+      {468, 0, 0},
+      {472, 0, 0},
+      {476, 0, 0},
+      {480, 0, 0},
+      {488, 0, 0},
+      {492, 0, 0},
+      {496, 0, 0},
+      {512, 0, 0},
+      {640, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 14, 17},
+      {0, 37, 31},
+      {0, 21, 39},
+      {0, 24, 23},
+      {0, 5, 13},
+      {0, 38, 76},
+      {0, 51, 77},
+      {0, 55, 53},
+      {0, 58, 56},
+      {0, 64, 61},
+      {0, 67, 66},
+      {0, 70, 68},
+      {0, 54, 71},
+      {0, 62, 60},
+      {0, 65, 63},
+      {0, 73, 72},
+      {0, 59, 57},
+      {0, 52, 74},
+      {0, 50, 69},
+      {0, 49, 47},
+      {0, 48, 46},
+      {0, 45, 43},
+      {0, 42, 44},
+      {0, 78, 41},
+      {0, 20, 18},
+      {0, 80, 79},
+      {0, 15, 27},
+      {0, 7, 34},
+      {0, 81, 6},
+      {0, 28, 3},
+      {0, 35, 82},
+      {0, 9, 36},
+      {0, 84, 83},
+      {0, 86, 85},
+      {0, 88, 87},
+      {0, 90, 89},
+      {0, 92, 91},
+      {0, 94, 93},
+      {0, 96, 95},
+      {0, 98, 97},
+      {0, 11, 29},
+      {0, 99, 25},
+      {0, 100, 40},
+      {0, 102, 101},
+      {0, 26, 32},
+      {0, 19, 30},
+      {0, 16, 12},
+      {0, 4, 8},
+      {0, 104, 103},
+      {0, 106, 105},
+      {0, 33, 107},
+      {0, 109, 108},
+      {0, 111, 110},
+      {0, 22, 112},
+      {0, 113, 10},
+      {0, 115, 114},
+      {0, 75, 116},
+      {0, 118, 117},
+      {0, 119, 1},
+      {0, 121, 120},
+      {0, 123, 122},
+      {0, 125, 124},
+      {0, 127, 126},
+      {0, 129, 128},
+      {0, 131, 130},
+      {0, 132, 2},
+      {0, 134, 133},
+      {0, 136, 135},
+      {0, 138, 137},
+      {0, 140, 139},
+      {0, 142, 141},
+      {0, 144, 143},
+      {0, 146, 145},
+      {0, 148, 147},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpMemberDecorate, 3), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(11, {
+      {0, 0, 0},
+      {0, 0, 0},
+      {1, 0, 0},
+      {2, 0, 0},
+      {3, 0, 0},
+      {4, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 2, 6},
+      {0, 4, 7},
+      {0, 8, 3},
+      {0, 9, 5},
+      {0, 1, 10},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpVectorShuffle, 4), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(13, {
+      {0, 0, 0},
+      {0, 0, 0},
+      {1, 0, 0},
+      {2, 0, 0},
+      {3, 0, 0},
+      {4, 0, 0},
+      {5, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 3, 7},
+      {0, 8, 5},
+      {0, 9, 1},
+      {0, 4, 10},
+      {0, 11, 6},
+      {0, 2, 12},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpVectorShuffle, 5), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(15, {
+      {0, 0, 0},
+      {0, 0, 0},
+      {1, 0, 0},
+      {2, 0, 0},
+      {3, 0, 0},
+      {4, 0, 0},
+      {5, 0, 0},
+      {6, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 6, 8},
+      {0, 5, 2},
+      {0, 10, 9},
+      {0, 1, 4},
+      {0, 12, 11},
+      {0, 7, 13},
+      {0, 3, 14},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpVectorShuffle, 6), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(15, {
+      {0, 0, 0},
+      {0, 0, 0},
+      {1, 0, 0},
+      {2, 0, 0},
+      {3, 0, 0},
+      {4, 0, 0},
+      {5, 0, 0},
+      {6, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 8, 5},
+      {0, 9, 7},
+      {0, 10, 3},
+      {0, 11, 2},
+      {0, 6, 1},
+      {0, 13, 12},
+      {0, 4, 14},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpVectorShuffle, 7), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(61, {
+      {0, 0, 0},
+      {0, 0, 0},
+      {1, 0, 0},
+      {2, 0, 0},
+      {3, 0, 0},
+      {4, 0, 0},
+      {5, 0, 0},
+      {6, 0, 0},
+      {7, 0, 0},
+      {8, 0, 0},
+      {9, 0, 0},
+      {10, 0, 0},
+      {11, 0, 0},
+      {12, 0, 0},
+      {13, 0, 0},
+      {14, 0, 0},
+      {15, 0, 0},
+      {16, 0, 0},
+      {17, 0, 0},
+      {18, 0, 0},
+      {19, 0, 0},
+      {20, 0, 0},
+      {21, 0, 0},
+      {22, 0, 0},
+      {23, 0, 0},
+      {24, 0, 0},
+      {27, 0, 0},
+      {28, 0, 0},
+      {29, 0, 0},
+      {30, 0, 0},
+      {31, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 30, 16},
+      {0, 26, 27},
+      {0, 29, 28},
+      {0, 18, 22},
+      {0, 12, 19},
+      {0, 15, 20},
+      {0, 14, 23},
+      {0, 32, 7},
+      {0, 8, 21},
+      {0, 11, 33},
+      {0, 17, 34},
+      {0, 25, 13},
+      {0, 36, 35},
+      {0, 9, 10},
+      {0, 38, 37},
+      {0, 39, 31},
+      {0, 5, 40},
+      {0, 42, 41},
+      {0, 44, 43},
+      {0, 6, 45},
+      {0, 46, 24},
+      {0, 48, 47},
+      {0, 50, 49},
+      {0, 52, 51},
+      {0, 54, 53},
+      {0, 55, 4},
+      {0, 56, 3},
+      {0, 57, 2},
+      {0, 58, 1},
+      {0, 60, 59},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpCompositeExtract, 3), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(63, {
+      {0, 0, 0},
+      {0, 0, 0},
+      {1, 0, 0},
+      {2, 0, 0},
+      {3, 0, 0},
+      {4, 0, 0},
+      {5, 0, 0},
+      {6, 0, 0},
+      {7, 0, 0},
+      {8, 0, 0},
+      {9, 0, 0},
+      {10, 0, 0},
+      {11, 0, 0},
+      {12, 0, 0},
+      {13, 0, 0},
+      {29, 0, 0},
+      {30, 0, 0},
+      {31, 0, 0},
+      {32, 0, 0},
+      {33, 0, 0},
+      {34, 0, 0},
+      {35, 0, 0},
+      {36, 0, 0},
+      {37, 0, 0},
+      {38, 0, 0},
+      {39, 0, 0},
+      {40, 0, 0},
+      {41, 0, 0},
+      {42, 0, 0},
+      {43, 0, 0},
+      {44, 0, 0},
+      {45, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 13, 14},
+      {0, 12, 9},
+      {0, 11, 25},
+      {0, 27, 26},
+      {0, 29, 28},
+      {0, 31, 30},
+      {0, 23, 22},
+      {0, 10, 24},
+      {0, 8, 21},
+      {0, 17, 7},
+      {0, 19, 18},
+      {0, 15, 20},
+      {0, 6, 16},
+      {0, 5, 33},
+      {0, 35, 34},
+      {0, 37, 36},
+      {0, 39, 38},
+      {0, 41, 40},
+      {0, 43, 42},
+      {0, 45, 44},
+      {0, 47, 46},
+      {0, 49, 48},
+      {0, 51, 50},
+      {0, 32, 52},
+      {0, 54, 53},
+      {0, 56, 55},
+      {0, 58, 57},
+      {0, 3, 2},
+      {0, 59, 4},
+      {0, 60, 1},
+      {0, 62, 61},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpCompositeExtract, 4), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
+      {0, 0, 0},
+      {0, 0, 0},
+      {1, 0, 0},
+      {2, 0, 0},
+      {3, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 5},
+      {0, 3, 2},
+      {0, 6, 4},
+      {0, 8, 7},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpCompositeExtract, 5), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(23, {
+      {0, 0, 0},
+      {0, 0, 0},
+      {1, 0, 0},
+      {2, 0, 0},
+      {3, 0, 0},
+      {4, 0, 0},
+      {5, 0, 0},
+      {6, 0, 0},
+      {7, 0, 0},
+      {8, 0, 0},
+      {9, 0, 0},
+      {10, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 12, 11},
+      {0, 10, 13},
+      {0, 9, 14},
+      {0, 7, 5},
+      {0, 8, 6},
+      {0, 4, 15},
+      {0, 17, 16},
+      {0, 18, 3},
+      {0, 19, 2},
+      {0, 20, 1},
+      {0, 22, 21},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpCompositeInsert, 4), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
+      {0, 0, 0},
+      {0, 0, 0},
+      {1, 0, 0},
+      {2, 0, 0},
+      {3, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 3, 5},
+      {0, 2, 6},
+      {0, 7, 1},
+      {0, 4, 8},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpCompositeInsert, 5), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
+      {0, 0, 0},
+      {1, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 2},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpImageSampleImplicitLod, 4), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
+      {0, 0, 0},
+      {2, 0, 0},
+      {10, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 2, 3},
+      {0, 1, 4},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpImageSampleExplicitLod, 4), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
+      {0, 0, 0},
+      {2, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 2},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpImageSampleDrefExplicitLod, 5), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
+      {0, 0, 0},
+      {0, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 2},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpSelectionMerge, 1), std::move(codec));
+  }
+
+  return codecs;
+}
+
+std::map<std::pair<uint32_t, uint32_t>, std::unique_ptr<HuffmanCodec<uint64_t>>>
+GetIdDescriptorHuffmanCodecs() {
+  std::map<std::pair<uint32_t, uint32_t>, std::unique_ptr<HuffmanCodec<uint64_t>>> codecs;
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1951208733, 0, 0},
+      {2320303498, 0, 0},
+      {3334207724, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 3, 5},
+      {0, 4, 6},
+      {0, 1, 7},
+      {0, 2, 8},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpExtInst, 0), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(63, {
+      {0, 0, 0},
+      {34183582, 0, 0},
+      {223800276, 0, 0},
+      {295018543, 0, 0},
+      {439764402, 0, 0},
+      {443558693, 0, 0},
+      {583624926, 0, 0},
+      {599185303, 0, 0},
+      {779021139, 0, 0},
+      {1015552308, 0, 0},
+      {1027242654, 0, 0},
+      {1077859090, 0, 0},
+      {1104362365, 0, 0},
+      {1132589448, 0, 0},
+      {1236389532, 0, 0},
+      {1739837626, 0, 0},
+      {1955104493, 0, 0},
+      {2161102232, 0, 0},
+      {2197874825, 0, 0},
+      {2217833278, 0, 0},
+      {2244470522, 0, 0},
+      {2532518896, 0, 0},
+      {2789375411, 0, 0},
+      {3061690214, 0, 0},
+      {3287039847, 0, 0},
+      {3357301402, 0, 0},
+      {3365041621, 0, 0},
+      {3510257966, 0, 0},
+      {3534235309, 0, 0},
+      {4018237905, 0, 0},
+      {4145966869, 0, 0},
+      {4272200782, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 10, 19},
+      {0, 6, 1},
+      {0, 26, 13},
+      {0, 2, 11},
+      {0, 15, 22},
+      {0, 23, 18},
+      {0, 4, 27},
+      {0, 28, 12},
+      {0, 3, 30},
+      {0, 9, 7},
+      {0, 20, 14},
+      {0, 29, 16},
+      {0, 21, 8},
+      {0, 34, 33},
+      {0, 36, 35},
+      {0, 31, 25},
+      {0, 37, 24},
+      {0, 39, 38},
+      {0, 41, 40},
+      {0, 43, 42},
+      {0, 45, 44},
+      {0, 17, 5},
+      {0, 47, 46},
+      {0, 49, 48},
+      {0, 51, 50},
+      {0, 53, 52},
+      {0, 55, 54},
+      {0, 57, 56},
+      {0, 59, 58},
+      {0, 61, 60},
+      {0, 32, 62},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpExtInst, 1), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
+      {0, 0, 0},
+      {4228502127, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 2},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpExtInst, 2), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(113, {
+      {0, 0, 0},
+      {50998433, 0, 0},
+      {139011596, 0, 0},
+      {181902171, 0, 0},
+      {296981500, 0, 0},
+      {321630747, 0, 0},
+      {416853049, 0, 0},
+      {464259778, 0, 0},
+      {615982737, 0, 0},
+      {669982125, 0, 0},
+      {759277550, 0, 0},
+      {810488476, 0, 0},
+      {870594305, 0, 0},
+      {922996215, 0, 0},
+      {969500141, 0, 0},
+      {1015552308, 0, 0},
+      {1139547465, 0, 0},
+      {1203545131, 0, 0},
+      {1220643281, 0, 0},
+      {1220749418, 0, 0},
+      {1367301635, 0, 0},
+      {1395923345, 0, 0},
+      {1554194368, 0, 0},
+      {1742737136, 0, 0},
+      {1755648697, 0, 0},
+      {1962162282, 0, 0},
+      {1964254745, 0, 0},
+      {2055836767, 0, 0},
+      {2096388952, 0, 0},
+      {2124837447, 0, 0},
+      {2161102232, 0, 0},
+      {2321729979, 0, 0},
+      {2346547796, 0, 0},
+      {2399809085, 0, 0},
+      {2432827426, 0, 0},
+      {2455417440, 0, 0},
+      {2572638469, 0, 0},
+      {2614879967, 0, 0},
+      {2855506940, 0, 0},
+      {2919796598, 0, 0},
+      {2970183398, 0, 0},
+      {2976066508, 0, 0},
+      {3044188332, 0, 0},
+      {3061690214, 0, 0},
+      {3091876332, 0, 0},
+      {3104643263, 0, 0},
+      {3107165180, 0, 0},
+      {3187066832, 0, 0},
+      {3413713311, 0, 0},
+      {3487022798, 0, 0},
+      {3602693817, 0, 0},
+      {3678875745, 0, 0},
+      {3701632935, 0, 0},
+      {3829325073, 0, 0},
+      {4040340620, 0, 0},
+      {4174489262, 0, 0},
+      {4272200782, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 33, 7},
+      {0, 13, 34},
+      {0, 21, 18},
+      {0, 53, 22},
+      {0, 39, 1},
+      {0, 14, 9},
+      {0, 43, 26},
+      {0, 51, 35},
+      {0, 19, 6},
+      {0, 15, 25},
+      {0, 55, 29},
+      {0, 32, 3},
+      {0, 27, 44},
+      {0, 10, 46},
+      {0, 45, 24},
+      {0, 36, 40},
+      {0, 47, 8},
+      {0, 48, 54},
+      {0, 58, 5},
+      {0, 60, 59},
+      {0, 30, 61},
+      {0, 62, 56},
+      {0, 64, 63},
+      {0, 41, 50},
+      {0, 66, 65},
+      {0, 68, 67},
+      {0, 70, 69},
+      {0, 37, 31},
+      {0, 4, 17},
+      {0, 16, 20},
+      {0, 72, 71},
+      {0, 73, 52},
+      {0, 49, 12},
+      {0, 75, 74},
+      {0, 76, 11},
+      {0, 23, 42},
+      {0, 78, 77},
+      {0, 80, 79},
+      {0, 82, 81},
+      {0, 84, 83},
+      {0, 85, 28},
+      {0, 87, 86},
+      {0, 89, 88},
+      {0, 91, 90},
+      {0, 93, 92},
+      {0, 94, 2},
+      {0, 96, 95},
+      {0, 98, 97},
+      {0, 100, 99},
+      {0, 102, 101},
+      {0, 38, 103},
+      {0, 105, 104},
+      {0, 107, 106},
+      {0, 109, 108},
+      {0, 111, 110},
+      {0, 57, 112},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpExtInst, 4), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(127, {
+      {0, 0, 0},
+      {72782198, 0, 0},
+      {139011596, 0, 0},
+      {296981500, 0, 0},
+      {300939750, 0, 0},
+      {401211099, 0, 0},
+      {429277936, 0, 0},
+      {505940164, 0, 0},
+      {538168945, 0, 0},
+      {603915804, 0, 0},
+      {688216667, 0, 0},
+      {706016261, 0, 0},
+      {790502615, 0, 0},
+      {810488476, 0, 0},
+      {993150979, 0, 0},
+      {1203545131, 0, 0},
+      {1206726575, 0, 0},
+      {1265796414, 0, 0},
+      {1314843976, 0, 0},
+      {1367301635, 0, 0},
+      {1378082995, 0, 0},
+      {1410311776, 0, 0},
+      {1443829854, 0, 0},
+      {1448448666, 0, 0},
+      {1468919488, 0, 0},
+      {1496351055, 0, 0},
+      {1619778288, 0, 0},
+      {1684282922, 0, 0},
+      {1848784182, 0, 0},
+      {1901166356, 0, 0},
+      {2095546797, 0, 0},
+      {2096388952, 0, 0},
+      {2162986400, 0, 0},
+      {2197874825, 0, 0},
+      {2246405597, 0, 0},
+      {2250225826, 0, 0},
+      {2282454607, 0, 0},
+      {2328748202, 0, 0},
+      {2348201466, 0, 0},
+      {2597020383, 0, 0},
+      {2633682514, 0, 0},
+      {2817335337, 0, 0},
+      {2855506940, 0, 0},
+      {2936040203, 0, 0},
+      {2955375511, 0, 0},
+      {3122368657, 0, 0},
+      {3154597438, 0, 0},
+      {3184381405, 0, 0},
+      {3187066832, 0, 0},
+      {3233393284, 0, 0},
+      {3251128023, 0, 0},
+      {3260309823, 0, 0},
+      {3441531391, 0, 0},
+      {3496407048, 0, 0},
+      {3582002820, 0, 0},
+      {3647586740, 0, 0},
+      {3653838348, 0, 0},
+      {3730093054, 0, 0},
+      {3759072440, 0, 0},
+      {3928764629, 0, 0},
+      {3969279737, 0, 0},
+      {3994511488, 0, 0},
+      {4026740269, 0, 0},
+      {4274214049, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 43, 23},
+      {0, 5, 24},
+      {0, 9, 8},
+      {0, 36, 21},
+      {0, 13, 46},
+      {0, 7, 12},
+      {0, 35, 20},
+      {0, 61, 59},
+      {0, 22, 29},
+      {0, 38, 62},
+      {0, 56, 45},
+      {0, 6, 48},
+      {0, 33, 30},
+      {0, 14, 58},
+      {0, 34, 28},
+      {0, 51, 40},
+      {0, 63, 55},
+      {0, 25, 16},
+      {0, 17, 11},
+      {0, 53, 52},
+      {0, 65, 27},
+      {0, 39, 41},
+      {0, 67, 66},
+      {0, 69, 68},
+      {0, 10, 4},
+      {0, 37, 18},
+      {0, 60, 47},
+      {0, 1, 32},
+      {0, 71, 70},
+      {0, 73, 72},
+      {0, 57, 26},
+      {0, 74, 31},
+      {0, 76, 75},
+      {0, 77, 44},
+      {0, 78, 15},
+      {0, 79, 54},
+      {0, 81, 80},
+      {0, 82, 49},
+      {0, 84, 83},
+      {0, 86, 85},
+      {0, 88, 87},
+      {0, 89, 19},
+      {0, 91, 90},
+      {0, 93, 92},
+      {0, 95, 94},
+      {0, 2, 96},
+      {0, 98, 97},
+      {0, 100, 99},
+      {0, 102, 101},
+      {0, 104, 103},
+      {0, 106, 105},
+      {0, 3, 107},
+      {0, 109, 108},
+      {0, 111, 110},
+      {0, 113, 112},
+      {0, 114, 50},
+      {0, 116, 115},
+      {0, 118, 117},
+      {0, 120, 119},
+      {0, 122, 121},
+      {0, 124, 123},
+      {0, 64, 42},
+      {0, 126, 125},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpExtInst, 5), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(93, {
+      {0, 0, 0},
+      {99347751, 0, 0},
+      {102542696, 0, 0},
+      {107497541, 0, 0},
+      {112452386, 0, 0},
+      {139011596, 0, 0},
+      {296981500, 0, 0},
+      {429277936, 0, 0},
+      {451957774, 0, 0},
+      {508217552, 0, 0},
+      {573901046, 0, 0},
+      {774727851, 0, 0},
+      {801484894, 0, 0},
+      {920604853, 0, 0},
+      {925559698, 0, 0},
+      {1022915255, 0, 0},
+      {1209418480, 0, 0},
+      {1287937401, 0, 0},
+      {1319785741, 0, 0},
+      {1392080469, 0, 0},
+      {1538342947, 0, 0},
+      {1541020250, 0, 0},
+      {1587209598, 0, 0},
+      {1594733696, 0, 0},
+      {1631434666, 0, 0},
+      {1636389511, 0, 0},
+      {1684282922, 0, 0},
+      {1859128680, 0, 0},
+      {1901166356, 0, 0},
+      {2004567202, 0, 0},
+      {2119793999, 0, 0},
+      {2280400314, 0, 0},
+      {2538917932, 0, 0},
+      {2677264274, 0, 0},
+      {2683080096, 0, 0},
+      {2854085372, 0, 0},
+      {2879917501, 0, 0},
+      {3059119137, 0, 0},
+      {3174324790, 0, 0},
+      {3194725903, 0, 0},
+      {3358097187, 0, 0},
+      {3547456240, 0, 0},
+      {3614752756, 0, 0},
+      {3753486980, 0, 0},
+      {3811268385, 0, 0},
+      {3953733490, 0, 0},
+      {3990925720, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 23, 22},
+      {0, 36, 31},
+      {0, 17, 40},
+      {0, 27, 19},
+      {0, 35, 33},
+      {0, 30, 38},
+      {0, 42, 39},
+      {0, 46, 32},
+      {0, 13, 12},
+      {0, 44, 14},
+      {0, 29, 11},
+      {0, 10, 18},
+      {0, 15, 37},
+      {0, 1, 4},
+      {0, 45, 2},
+      {0, 21, 28},
+      {0, 8, 5},
+      {0, 49, 48},
+      {0, 51, 50},
+      {0, 53, 52},
+      {0, 54, 16},
+      {0, 55, 25},
+      {0, 56, 3},
+      {0, 58, 57},
+      {0, 59, 26},
+      {0, 20, 7},
+      {0, 61, 60},
+      {0, 62, 24},
+      {0, 41, 63},
+      {0, 65, 64},
+      {0, 9, 34},
+      {0, 67, 66},
+      {0, 69, 68},
+      {0, 71, 70},
+      {0, 73, 72},
+      {0, 75, 74},
+      {0, 76, 43},
+      {0, 78, 77},
+      {0, 80, 79},
+      {0, 82, 81},
+      {0, 84, 83},
+      {0, 86, 85},
+      {0, 88, 87},
+      {0, 90, 89},
+      {0, 47, 91},
+      {0, 92, 6},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpExtInst, 6), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(15, {
+      {0, 0, 0},
+      {166253838, 0, 0},
+      {679771963, 0, 0},
+      {1247793383, 0, 0},
+      {2261697609, 0, 0},
+      {2263349224, 0, 0},
+      {2320303498, 0, 0},
+      {3334207724, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 4, 8},
+      {0, 9, 1},
+      {0, 3, 5},
+      {0, 11, 10},
+      {0, 2, 12},
+      {0, 7, 6},
+      {0, 14, 13},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeVector, 0), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
+      {0, 0, 0},
+      {789872778, 0, 0},
+      {1415510495, 0, 0},
+      {1951208733, 0, 0},
+      {2430404313, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 2, 5},
+      {0, 4, 6},
+      {0, 7, 1},
+      {0, 3, 8},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeVector, 1), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(15, {
+      {0, 0, 0},
+      {1389644742, 0, 0},
+      {3232633974, 0, 0},
+      {3278176820, 0, 0},
+      {3648138580, 0, 0},
+      {3687777340, 0, 0},
+      {3694383800, 0, 0},
+      {3697687030, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 5, 4},
+      {0, 9, 6},
+      {0, 10, 8},
+      {0, 2, 11},
+      {0, 12, 3},
+      {0, 1, 13},
+      {0, 14, 7},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeArray, 0), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
+      {0, 0, 0},
+      {1951208733, 0, 0},
+      {2160380860, 0, 0},
+      {3334207724, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 4},
+      {0, 2, 5},
+      {0, 3, 6},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeArray, 1), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(13, {
+      {0, 0, 0},
+      {144116905, 0, 0},
+      {827246872, 0, 0},
+      {1545298048, 0, 0},
+      {2715370488, 0, 0},
+      {2798552666, 0, 0},
+      {3812456892, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 2, 3},
+      {0, 8, 6},
+      {0, 9, 7},
+      {0, 1, 10},
+      {0, 11, 4},
+      {0, 5, 12},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeArray, 2), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(67, {
+      {0, 0, 0},
+      {40653745, 0, 0},
+      {119981689, 0, 0},
+      {153085016, 0, 0},
+      {451382997, 0, 0},
+      {545678922, 0, 0},
+      {899570100, 0, 0},
+      {929101967, 0, 0},
+      {1070791291, 0, 0},
+      {1100599986, 0, 0},
+      {1103903216, 0, 0},
+      {1154919607, 0, 0},
+      {1199157863, 0, 0},
+      {1258105452, 0, 0},
+      {1369578001, 0, 0},
+      {1372881231, 0, 0},
+      {1674803691, 0, 0},
+      {1677700667, 0, 0},
+      {1989520052, 0, 0},
+      {2593884753, 0, 0},
+      {2664825925, 0, 0},
+      {2924146124, 0, 0},
+      {2926633629, 0, 0},
+      {3249265647, 0, 0},
+      {3345288309, 0, 0},
+      {3410158390, 0, 0},
+      {3489360962, 0, 0},
+      {3495967422, 0, 0},
+      {3504981554, 0, 0},
+      {3705139860, 0, 0},
+      {3822983876, 0, 0},
+      {4141567741, 0, 0},
+      {4234287173, 0, 0},
+      {4240893633, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 15, 23},
+      {0, 20, 17},
+      {0, 32, 22},
+      {0, 19, 12},
+      {0, 13, 3},
+      {0, 30, 27},
+      {0, 4, 35},
+      {0, 24, 36},
+      {0, 31, 37},
+      {0, 33, 38},
+      {0, 39, 7},
+      {0, 6, 40},
+      {0, 41, 29},
+      {0, 14, 42},
+      {0, 43, 28},
+      {0, 10, 44},
+      {0, 45, 18},
+      {0, 26, 46},
+      {0, 5, 47},
+      {0, 48, 2},
+      {0, 49, 9},
+      {0, 50, 16},
+      {0, 34, 25},
+      {0, 52, 51},
+      {0, 54, 53},
+      {0, 56, 55},
+      {0, 58, 57},
+      {0, 60, 59},
+      {0, 8, 21},
+      {0, 1, 11},
+      {0, 62, 61},
+      {0, 64, 63},
+      {0, 66, 65},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 0), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(11, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1951208733, 0, 0},
+      {2160380860, 0, 0},
+      {3278176820, 0, 0},
+      {3334207724, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 4, 6},
+      {0, 2, 7},
+      {0, 3, 8},
+      {0, 9, 1},
+      {0, 5, 10},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 1), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(13, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1951208733, 0, 0},
+      {2160380860, 0, 0},
+      {2320303498, 0, 0},
+      {3232633974, 0, 0},
+      {3334207724, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 5, 7},
+      {0, 2, 8},
+      {0, 4, 9},
+      {0, 10, 3},
+      {0, 1, 6},
+      {0, 12, 11},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 2), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(11, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1951208733, 0, 0},
+      {2160380860, 0, 0},
+      {2320303498, 0, 0},
+      {3334207724, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 5, 6},
+      {0, 1, 7},
+      {0, 3, 4},
+      {0, 8, 2},
+      {0, 10, 9},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 3), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(11, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1951208733, 0, 0},
+      {2160380860, 0, 0},
+      {2320303498, 0, 0},
+      {3334207724, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 2, 6},
+      {0, 3, 7},
+      {0, 5, 4},
+      {0, 8, 1},
+      {0, 10, 9},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 4), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1951208733, 0, 0},
+      {2263349224, 0, 0},
+      {3334207724, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 3, 5},
+      {0, 1, 6},
+      {0, 2, 7},
+      {0, 8, 4},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 5), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1951208733, 0, 0},
+      {2320303498, 0, 0},
+      {3334207724, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 3, 5},
+      {0, 1, 6},
+      {0, 2, 7},
+      {0, 8, 4},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 6), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1951208733, 0, 0},
+      {2320303498, 0, 0},
+      {3334207724, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 3, 5},
+      {0, 4, 6},
+      {0, 7, 1},
+      {0, 2, 8},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 7), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1951208733, 0, 0},
+      {2320303498, 0, 0},
+      {3334207724, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 3, 5},
+      {0, 1, 6},
+      {0, 7, 4},
+      {0, 2, 8},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 8), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
+      {0, 0, 0},
+      {1951208733, 0, 0},
+      {2320303498, 0, 0},
+      {3334207724, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 2, 4},
+      {0, 3, 5},
+      {0, 1, 6},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 9), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1951208733, 0, 0},
+      {2320303498, 0, 0},
+      {3334207724, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 5, 3},
+      {0, 1, 6},
+      {0, 4, 7},
+      {0, 8, 2},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 10), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1951208733, 0, 0},
+      {2320303498, 0, 0},
+      {3334207724, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 3, 5},
+      {0, 1, 6},
+      {0, 7, 4},
+      {0, 8, 2},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 11), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1951208733, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 2, 3},
+      {0, 4, 1},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 12), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1951208733, 0, 0},
+      {2320303498, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 3, 4},
+      {0, 1, 5},
+      {0, 2, 6},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 13), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1951208733, 0, 0},
+      {2320303498, 0, 0},
+      {3334207724, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 4, 5},
+      {0, 3, 6},
+      {0, 7, 1},
+      {0, 8, 2},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 14), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1951208733, 0, 0},
+      {2320303498, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 4},
+      {0, 5, 3},
+      {0, 6, 2},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 15), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1951208733, 0, 0},
+      {2320303498, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 3, 4},
+      {0, 2, 5},
+      {0, 1, 6},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 16), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1951208733, 0, 0},
+      {2320303498, 0, 0},
+      {3334207724, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 3, 5},
+      {0, 4, 6},
+      {0, 7, 1},
+      {0, 8, 2},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 17), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1951208733, 0, 0},
+      {3334207724, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 3, 4},
+      {0, 1, 5},
+      {0, 2, 6},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 18), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1951208733, 0, 0},
+      {2320303498, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 4},
+      {0, 3, 5},
+      {0, 2, 6},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 19), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1951208733, 0, 0},
+      {2320303498, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 3, 4},
+      {0, 1, 5},
+      {0, 2, 6},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 20), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1951208733, 0, 0},
+      {2320303498, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 4},
+      {0, 2, 5},
+      {0, 3, 6},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 21), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1951208733, 0, 0},
+      {2320303498, 0, 0},
+      {3334207724, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 5, 1},
+      {0, 2, 6},
+      {0, 3, 7},
+      {0, 8, 4},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 22), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1951208733, 0, 0},
+      {2320303498, 0, 0},
+      {3334207724, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 5},
+      {0, 2, 6},
+      {0, 4, 7},
+      {0, 8, 3},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 23), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(11, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1951208733, 0, 0},
+      {2160380860, 0, 0},
+      {2320303498, 0, 0},
+      {3334207724, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 6, 4},
+      {0, 1, 7},
+      {0, 2, 8},
+      {0, 3, 9},
+      {0, 10, 5},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 24), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1951208733, 0, 0},
+      {2320303498, 0, 0},
+      {3334207724, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 5},
+      {0, 2, 6},
+      {0, 4, 7},
+      {0, 8, 3},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 25), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1951208733, 0, 0},
+      {2320303498, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 4},
+      {0, 2, 5},
+      {0, 3, 6},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 26), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1951208733, 0, 0},
+      {2320303498, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 4},
+      {0, 2, 5},
+      {0, 3, 6},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 27), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1951208733, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 2, 3},
+      {0, 1, 4},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 28), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1951208733, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 3},
+      {0, 2, 4},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 29), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1951208733, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 3},
+      {0, 2, 4},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 30), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1951208733, 0, 0},
+      {2320303498, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 4, 3},
+      {0, 1, 5},
+      {0, 2, 6},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 31), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1951208733, 0, 0},
+      {2320303498, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 3, 4},
+      {0, 1, 5},
+      {0, 2, 6},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 32), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
+      {0, 0, 0},
+      {1951208733, 0, 0},
+      {2320303498, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 2, 3},
+      {0, 1, 4},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 33), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
+      {0, 0, 0},
+      {1951208733, 0, 0},
+      {2320303498, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 3},
+      {0, 2, 4},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 34), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
+      {0, 0, 0},
+      {1951208733, 0, 0},
+      {2320303498, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 2, 3},
+      {0, 1, 4},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 35), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
+      {0, 0, 0},
+      {1951208733, 0, 0},
+      {2320303498, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 2, 3},
+      {0, 1, 4},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 36), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 2},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 37), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
+      {0, 0, 0},
+      {1389644742, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 2},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 38), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
+      {0, 0, 0},
+      {3697687030, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 2},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 39), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
+      {0, 0, 0},
+      {2320303498, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 2},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 40), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
+      {0, 0, 0},
+      {2320303498, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 2},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 41), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 2},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 42), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 2},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 43), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 2},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 44), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 2},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 45), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 2},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 46), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 2},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 47), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 2},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 48), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 2},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 49), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 2},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 50), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 2},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 51), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(101, {
+      {0, 0, 0},
+      {85880059, 0, 0},
+      {135486769, 0, 0},
+      {304448521, 0, 0},
+      {436416061, 0, 0},
+      {440421571, 0, 0},
+      {450406196, 0, 0},
+      {503094540, 0, 0},
+      {543621065, 0, 0},
+      {626892406, 0, 0},
+      {628544021, 0, 0},
+      {827698488, 0, 0},
+      {869050696, 0, 0},
+      {907126242, 0, 0},
+      {908777857, 0, 0},
+      {910429472, 0, 0},
+      {1113409935, 0, 0},
+      {1294403159, 0, 0},
+      {1296054774, 0, 0},
+      {1297706389, 0, 0},
+      {1322549027, 0, 0},
+      {1784441183, 0, 0},
+      {2080953106, 0, 0},
+      {2194691858, 0, 0},
+      {2448331885, 0, 0},
+      {2466255445, 0, 0},
+      {2468230023, 0, 0},
+      {2547657777, 0, 0},
+      {2549309392, 0, 0},
+      {2550961007, 0, 0},
+      {2894051250, 0, 0},
+      {2929019254, 0, 0},
+      {2934934694, 0, 0},
+      {2936586309, 0, 0},
+      {2938237924, 0, 0},
+      {3077271274, 0, 0},
+      {3092528578, 0, 0},
+      {3094180193, 0, 0},
+      {3094857332, 0, 0},
+      {3095831808, 0, 0},
+      {3183924418, 0, 0},
+      {3207966516, 0, 0},
+      {3282979782, 0, 0},
+      {3433956341, 0, 0},
+      {3561562003, 0, 0},
+      {3563213618, 0, 0},
+      {3564865233, 0, 0},
+      {3585511591, 0, 0},
+      {4028622909, 0, 0},
+      {4039938779, 0, 0},
+      {4050155669, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 16, 25},
+      {0, 50, 1},
+      {0, 42, 35},
+      {0, 31, 41},
+      {0, 4, 43},
+      {0, 9, 10},
+      {0, 3, 30},
+      {0, 52, 47},
+      {0, 12, 53},
+      {0, 55, 54},
+      {0, 36, 56},
+      {0, 49, 57},
+      {0, 6, 58},
+      {0, 34, 33},
+      {0, 59, 26},
+      {0, 21, 32},
+      {0, 60, 15},
+      {0, 24, 61},
+      {0, 62, 38},
+      {0, 22, 2},
+      {0, 37, 7},
+      {0, 63, 46},
+      {0, 14, 13},
+      {0, 64, 5},
+      {0, 65, 45},
+      {0, 66, 19},
+      {0, 18, 67},
+      {0, 17, 20},
+      {0, 68, 11},
+      {0, 8, 69},
+      {0, 70, 39},
+      {0, 72, 71},
+      {0, 74, 73},
+      {0, 40, 75},
+      {0, 76, 23},
+      {0, 78, 77},
+      {0, 29, 79},
+      {0, 28, 80},
+      {0, 27, 48},
+      {0, 82, 81},
+      {0, 51, 83},
+      {0, 84, 44},
+      {0, 86, 85},
+      {0, 88, 87},
+      {0, 90, 89},
+      {0, 92, 91},
+      {0, 94, 93},
+      {0, 96, 95},
+      {0, 98, 97},
+      {0, 100, 99},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypePointer, 0), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(65, {
+      {0, 0, 0},
+      {119981689, 0, 0},
+      {162255877, 0, 0},
+      {451382997, 0, 0},
+      {545678922, 0, 0},
+      {679771963, 0, 0},
+      {789872778, 0, 0},
+      {1100599986, 0, 0},
+      {1103903216, 0, 0},
+      {1154919607, 0, 0},
+      {1343794461, 0, 0},
+      {1415510495, 0, 0},
+      {1674803691, 0, 0},
+      {1951208733, 0, 0},
+      {1989520052, 0, 0},
+      {2160380860, 0, 0},
+      {2263349224, 0, 0},
+      {2320303498, 0, 0},
+      {2924146124, 0, 0},
+      {2984325996, 0, 0},
+      {3334207724, 0, 0},
+      {3345288309, 0, 0},
+      {3410158390, 0, 0},
+      {3489360962, 0, 0},
+      {3495967422, 0, 0},
+      {3504981554, 0, 0},
+      {3800912395, 0, 0},
+      {3802564010, 0, 0},
+      {3866587616, 0, 0},
+      {3868239231, 0, 0},
+      {3869890846, 0, 0},
+      {3998230222, 0, 0},
+      {4240893633, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 4, 3},
+      {0, 6, 24},
+      {0, 11, 7},
+      {0, 32, 21},
+      {0, 27, 34},
+      {0, 35, 25},
+      {0, 36, 8},
+      {0, 26, 31},
+      {0, 14, 15},
+      {0, 28, 37},
+      {0, 1, 23},
+      {0, 39, 38},
+      {0, 12, 40},
+      {0, 22, 41},
+      {0, 10, 16},
+      {0, 43, 42},
+      {0, 29, 44},
+      {0, 2, 45},
+      {0, 46, 19},
+      {0, 48, 47},
+      {0, 18, 49},
+      {0, 50, 30},
+      {0, 9, 33},
+      {0, 52, 51},
+      {0, 54, 53},
+      {0, 13, 55},
+      {0, 17, 56},
+      {0, 5, 57},
+      {0, 59, 58},
+      {0, 60, 20},
+      {0, 62, 61},
+      {0, 64, 63},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypePointer, 2), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(99, {
+      {0, 0, 0},
+      {75986790, 0, 0},
+      {95470391, 0, 0},
+      {170378107, 0, 0},
+      {172029722, 0, 0},
+      {204234270, 0, 0},
+      {205885885, 0, 0},
+      {244668133, 0, 0},
+      {265778447, 0, 0},
+      {616435646, 0, 0},
+      {618087261, 0, 0},
+      {753954113, 0, 0},
+      {1000070091, 0, 0},
+      {1308462133, 0, 0},
+      {1671139745, 0, 0},
+      {1774874546, 0, 0},
+      {1776526161, 0, 0},
+      {1887808856, 0, 0},
+      {1889460471, 0, 0},
+      {1917966999, 0, 0},
+      {2044728014, 0, 0},
+      {2192810893, 0, 0},
+      {2293247016, 0, 0},
+      {2503194620, 0, 0},
+      {2605012269, 0, 0},
+      {2608484640, 0, 0},
+      {2615111110, 0, 0},
+      {2668769415, 0, 0},
+      {2759951687, 0, 0},
+      {2761603302, 0, 0},
+      {2856623532, 0, 0},
+      {2945369269, 0, 0},
+      {2956189845, 0, 0},
+      {3085119011, 0, 0},
+      {3367313400, 0, 0},
+      {3447882276, 0, 0},
+      {3633746133, 0, 0},
+      {3635397748, 0, 0},
+      {3710645347, 0, 0},
+      {3712296962, 0, 0},
+      {3715846592, 0, 0},
+      {3727494858, 0, 0},
+      {3747079365, 0, 0},
+      {3748965853, 0, 0},
+      {3750617468, 0, 0},
+      {4018820793, 0, 0},
+      {4022124023, 0, 0},
+      {4024173916, 0, 0},
+      {4215670524, 0, 0},
+      {4217322139, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 10, 9},
+      {0, 31, 24},
+      {0, 40, 13},
+      {0, 45, 33},
+      {0, 34, 46},
+      {0, 43, 38},
+      {0, 44, 15},
+      {0, 11, 30},
+      {0, 21, 6},
+      {0, 47, 3},
+      {0, 51, 16},
+      {0, 14, 52},
+      {0, 8, 53},
+      {0, 35, 5},
+      {0, 55, 54},
+      {0, 56, 26},
+      {0, 20, 57},
+      {0, 39, 19},
+      {0, 59, 58},
+      {0, 61, 60},
+      {0, 4, 62},
+      {0, 2, 63},
+      {0, 25, 7},
+      {0, 64, 27},
+      {0, 12, 22},
+      {0, 65, 48},
+      {0, 41, 42},
+      {0, 17, 23},
+      {0, 49, 66},
+      {0, 68, 67},
+      {0, 70, 69},
+      {0, 72, 71},
+      {0, 74, 73},
+      {0, 18, 75},
+      {0, 37, 32},
+      {0, 76, 36},
+      {0, 78, 77},
+      {0, 79, 28},
+      {0, 81, 80},
+      {0, 82, 29},
+      {0, 84, 83},
+      {0, 86, 85},
+      {0, 88, 87},
+      {0, 90, 89},
+      {0, 91, 50},
+      {0, 93, 92},
+      {0, 95, 94},
+      {0, 1, 96},
+      {0, 98, 97},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeFunction, 0), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(27, {
+      {0, 0, 0},
+      {545678922, 0, 0},
+      {679771963, 0, 0},
+      {899570100, 0, 0},
+      {929101967, 0, 0},
+      {1100599986, 0, 0},
+      {1951208733, 0, 0},
+      {2320303498, 0, 0},
+      {3056042030, 0, 0},
+      {3334207724, 0, 0},
+      {3357250579, 0, 0},
+      {3705139860, 0, 0},
+      {3800912395, 0, 0},
+      {3802564010, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 5, 3},
+      {0, 10, 13},
+      {0, 4, 15},
+      {0, 16, 11},
+      {0, 17, 1},
+      {0, 14, 12},
+      {0, 19, 18},
+      {0, 21, 20},
+      {0, 7, 6},
+      {0, 9, 22},
+      {0, 24, 23},
+      {0, 25, 2},
+      {0, 26, 8},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeFunction, 1), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(57, {
+      {0, 0, 0},
+      {283209196, 0, 0},
+      {436416061, 0, 0},
+      {679771963, 0, 0},
+      {789872778, 0, 0},
+      {815757910, 0, 0},
+      {827698488, 0, 0},
+      {1164221089, 0, 0},
+      {1294403159, 0, 0},
+      {1296054774, 0, 0},
+      {1297706389, 0, 0},
+      {1525861001, 0, 0},
+      {1579585816, 0, 0},
+      {1675764636, 0, 0},
+      {1824016656, 0, 0},
+      {1951208733, 0, 0},
+      {1991787192, 0, 0},
+      {2180701723, 0, 0},
+      {2194691858, 0, 0},
+      {2320303498, 0, 0},
+      {2881886868, 0, 0},
+      {2926633629, 0, 0},
+      {3249265647, 0, 0},
+      {3334207724, 0, 0},
+      {3472123498, 0, 0},
+      {3674863070, 0, 0},
+      {4050155669, 0, 0},
+      {4141567741, 0, 0},
+      {4155122613, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 24, 7},
+      {0, 17, 1},
+      {0, 4, 15},
+      {0, 11, 16},
+      {0, 28, 30},
+      {0, 25, 20},
+      {0, 14, 31},
+      {0, 32, 26},
+      {0, 12, 5},
+      {0, 2, 22},
+      {0, 33, 13},
+      {0, 35, 34},
+      {0, 37, 36},
+      {0, 39, 38},
+      {0, 40, 21},
+      {0, 29, 18},
+      {0, 27, 41},
+      {0, 43, 42},
+      {0, 19, 44},
+      {0, 45, 23},
+      {0, 6, 3},
+      {0, 47, 46},
+      {0, 49, 48},
+      {0, 51, 50},
+      {0, 10, 8},
+      {0, 53, 52},
+      {0, 9, 54},
+      {0, 56, 55},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeFunction, 2), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(17, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {827698488, 0, 0},
+      {1294403159, 0, 0},
+      {1296054774, 0, 0},
+      {1297706389, 0, 0},
+      {1951208733, 0, 0},
+      {2320303498, 0, 0},
+      {3334207724, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 8, 9},
+      {0, 10, 6},
+      {0, 1, 5},
+      {0, 11, 3},
+      {0, 12, 7},
+      {0, 13, 2},
+      {0, 15, 14},
+      {0, 16, 4},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeFunction, 3), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(17, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {827698488, 0, 0},
+      {1294403159, 0, 0},
+      {1296054774, 0, 0},
+      {1951208733, 0, 0},
+      {2194691858, 0, 0},
+      {2320303498, 0, 0},
+      {3334207724, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 8, 5},
+      {0, 10, 9},
+      {0, 11, 6},
+      {0, 7, 12},
+      {0, 1, 3},
+      {0, 2, 13},
+      {0, 15, 14},
+      {0, 4, 16},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeFunction, 4), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(11, {
+      {0, 0, 0},
+      {827698488, 0, 0},
+      {1294403159, 0, 0},
+      {1296054774, 0, 0},
+      {1297706389, 0, 0},
+      {1951208733, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 4, 6},
+      {0, 5, 7},
+      {0, 2, 8},
+      {0, 1, 9},
+      {0, 10, 3},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeFunction, 5), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(11, {
+      {0, 0, 0},
+      {827698488, 0, 0},
+      {1294403159, 0, 0},
+      {1296054774, 0, 0},
+      {1951208733, 0, 0},
+      {3334207724, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 2, 6},
+      {0, 4, 7},
+      {0, 8, 5},
+      {0, 3, 9},
+      {0, 1, 10},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeFunction, 6), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
+      {0, 0, 0},
+      {789872778, 0, 0},
+      {827698488, 0, 0},
+      {1951208733, 0, 0},
+      {2320303498, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 5, 1},
+      {0, 4, 6},
+      {0, 3, 7},
+      {0, 2, 8},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeFunction, 7), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
+      {0, 0, 0},
+      {543621065, 0, 0},
+      {827698488, 0, 0},
+      {1951208733, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 3, 4},
+      {0, 1, 5},
+      {0, 2, 6},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeFunction, 8), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
+      {0, 0, 0},
+      {827698488, 0, 0},
+      {1951208733, 0, 0},
+      {3095831808, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 2, 4},
+      {0, 3, 5},
+      {0, 1, 6},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeFunction, 9), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
+      {0, 0, 0},
+      {1296054774, 0, 0},
+      {1951208733, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 3, 2},
+      {0, 1, 4},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeFunction, 10), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
+      {0, 0, 0},
+      {1296054774, 0, 0},
+      {2320303498, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 3, 2},
+      {0, 1, 4},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeFunction, 11), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
+      {0, 0, 0},
+      {789872778, 0, 0},
+      {1951208733, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 3, 2},
+      {0, 4, 1},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeFunction, 12), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
+      {0, 0, 0},
+      {789872778, 0, 0},
+      {1951208733, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 2, 1},
+      {0, 4, 3},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeFunction, 13), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
+      {0, 0, 0},
+      {1951208733, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 2, 1},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeFunction, 14), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
+      {0, 0, 0},
+      {1951208733, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 2, 1},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeFunction, 15), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
+      {0, 0, 0},
+      {789872778, 0, 0},
+      {1951208733, 0, 0},
+      {2430404313, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 3, 4},
+      {0, 1, 5},
+      {0, 2, 6},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpConstant, 0), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(183, {
+      {0, 0, 0},
+      {51041423, 0, 0},
+      {52882140, 0, 0},
+      {72782198, 0, 0},
+      {142465290, 0, 0},
+      {144116905, 0, 0},
+      {158160339, 0, 0},
+      {169135842, 0, 0},
+      {210116709, 0, 0},
+      {290391815, 0, 0},
+      {296981500, 0, 0},
+      {385229009, 0, 0},
+      {438318340, 0, 0},
+      {529742207, 0, 0},
+      {628331516, 0, 0},
+      {677668732, 0, 0},
+      {778500192, 0, 0},
+      {825595257, 0, 0},
+      {910398460, 0, 0},
+      {917019124, 0, 0},
+      {959681532, 0, 0},
+      {1031290113, 0, 0},
+      {1039111164, 0, 0},
+      {1064945649, 0, 0},
+      {1087394637, 0, 0},
+      {1092948665, 0, 0},
+      {1156369516, 0, 0},
+      {1158021131, 0, 0},
+      {1172110445, 0, 0},
+      {1304296041, 0, 0},
+      {1400019344, 0, 0},
+      {1450415100, 0, 0},
+      {1452222566, 0, 0},
+      {1543646433, 0, 0},
+      {1543672828, 0, 0},
+      {1612361408, 0, 0},
+      {1622381564, 0, 0},
+      {1691572958, 0, 0},
+      {1755648697, 0, 0},
+      {1782996825, 0, 0},
+      {1784648440, 0, 0},
+      {1930923350, 0, 0},
+      {1939359710, 0, 0},
+      {1971252067, 0, 0},
+      {1979847999, 0, 0},
+      {2078849875, 0, 0},
+      {2113115132, 0, 0},
+      {2135340676, 0, 0},
+      {2170273742, 0, 0},
+      {2268204687, 0, 0},
+      {2285081596, 0, 0},
+      {2318200267, 0, 0},
+      {2321729979, 0, 0},
+      {2326636627, 0, 0},
+      {2444465148, 0, 0},
+      {2466126792, 0, 0},
+      {2490492987, 0, 0},
+      {2524697596, 0, 0},
+      {2557550659, 0, 0},
+      {2678954464, 0, 0},
+      {2705477184, 0, 0},
+      {2715370488, 0, 0},
+      {2732195517, 0, 0},
+      {2775815164, 0, 0},
+      {2796901051, 0, 0},
+      {2798552666, 0, 0},
+      {2855506940, 0, 0},
+      {2860348412, 0, 0},
+      {2922615804, 0, 0},
+      {2937761472, 0, 0},
+      {2944827576, 0, 0},
+      {3092754101, 0, 0},
+      {3107165180, 0, 0},
+      {3168953855, 0, 0},
+      {3184177968, 0, 0},
+      {3202349435, 0, 0},
+      {3266548732, 0, 0},
+      {3332104493, 0, 0},
+      {3362723943, 0, 0},
+      {3571454885, 0, 0},
+      {3712763835, 0, 0},
+      {3743748793, 0, 0},
+      {3810805277, 0, 0},
+      {3912967080, 0, 0},
+      {3929248764, 0, 0},
+      {3958731802, 0, 0},
+      {3997952447, 0, 0},
+      {4016096296, 0, 0},
+      {4106658327, 0, 0},
+      {4172568578, 0, 0},
+      {4198082194, 0, 0},
+      {4248015868, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 35, 16},
+      {0, 49, 42},
+      {0, 86, 69},
+      {0, 53, 30},
+      {0, 45, 89},
+      {0, 50, 68},
+      {0, 73, 71},
+      {0, 17, 46},
+      {0, 14, 81},
+      {0, 63, 44},
+      {0, 12, 3},
+      {0, 72, 31},
+      {0, 55, 67},
+      {0, 36, 19},
+      {0, 22, 88},
+      {0, 9, 70},
+      {0, 93, 23},
+      {0, 95, 94},
+      {0, 47, 91},
+      {0, 34, 32},
+      {0, 97, 96},
+      {0, 41, 61},
+      {0, 99, 98},
+      {0, 37, 1},
+      {0, 77, 100},
+      {0, 51, 60},
+      {0, 101, 79},
+      {0, 6, 2},
+      {0, 11, 7},
+      {0, 24, 21},
+      {0, 43, 28},
+      {0, 59, 56},
+      {0, 75, 62},
+      {0, 80, 78},
+      {0, 87, 83},
+      {0, 18, 15},
+      {0, 102, 38},
+      {0, 104, 103},
+      {0, 85, 90},
+      {0, 76, 25},
+      {0, 29, 105},
+      {0, 107, 106},
+      {0, 58, 52},
+      {0, 109, 108},
+      {0, 57, 110},
+      {0, 112, 111},
+      {0, 114, 113},
+      {0, 115, 33},
+      {0, 74, 116},
+      {0, 118, 117},
+      {0, 120, 119},
+      {0, 122, 121},
+      {0, 124, 123},
+      {0, 126, 125},
+      {0, 128, 127},
+      {0, 130, 129},
+      {0, 131, 13},
+      {0, 54, 27},
+      {0, 133, 132},
+      {0, 48, 40},
+      {0, 5, 8},
+      {0, 82, 134},
+      {0, 26, 135},
+      {0, 39, 4},
+      {0, 136, 64},
+      {0, 138, 137},
+      {0, 140, 139},
+      {0, 84, 141},
+      {0, 143, 142},
+      {0, 145, 144},
+      {0, 147, 146},
+      {0, 149, 148},
+      {0, 20, 150},
+      {0, 65, 151},
+      {0, 66, 152},
+      {0, 153, 10},
+      {0, 155, 154},
+      {0, 157, 156},
+      {0, 159, 158},
+      {0, 161, 160},
+      {0, 163, 162},
+      {0, 165, 164},
+      {0, 167, 166},
+      {0, 169, 168},
+      {0, 170, 92},
+      {0, 172, 171},
+      {0, 174, 173},
+      {0, 176, 175},
+      {0, 178, 177},
+      {0, 180, 179},
+      {0, 182, 181},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpConstant, 1), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1247793383, 0, 0},
+      {2320303498, 0, 0},
+      {3334207724, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 2, 5},
+      {0, 4, 6},
+      {0, 1, 3},
+      {0, 8, 7},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpConstantComposite, 0), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(83, {
+      {0, 0, 0},
+      {15502752, 0, 0},
+      {46736908, 0, 0},
+      {139011596, 0, 0},
+      {149720480, 0, 0},
+      {249378857, 0, 0},
+      {251209228, 0, 0},
+      {503145996, 0, 0},
+      {836581417, 0, 0},
+      {882718761, 0, 0},
+      {1289566249, 0, 0},
+      {1325348861, 0, 0},
+      {1558001705, 0, 0},
+      {1646147798, 0, 0},
+      {1679946323, 0, 0},
+      {1766401548, 0, 0},
+      {1992893964, 0, 0},
+      {2123388694, 0, 0},
+      {2162986400, 0, 0},
+      {2580096524, 0, 0},
+      {2598189097, 0, 0},
+      {2683080096, 0, 0},
+      {2698156268, 0, 0},
+      {2763960513, 0, 0},
+      {3015046341, 0, 0},
+      {3133016299, 0, 0},
+      {3251128023, 0, 0},
+      {3504158761, 0, 0},
+      {3535289452, 0, 0},
+      {3536941067, 0, 0},
+      {3538592682, 0, 0},
+      {3540244297, 0, 0},
+      {3541895912, 0, 0},
+      {3570219049, 0, 0},
+      {3653838348, 0, 0},
+      {3764205609, 0, 0},
+      {3882634684, 0, 0},
+      {3913885196, 0, 0},
+      {3982047273, 0, 0},
+      {4024252457, 0, 0},
+      {4243119782, 0, 0},
+      {4255182614, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 8, 4},
+      {0, 39, 2},
+      {0, 38, 10},
+      {0, 29, 41},
+      {0, 23, 28},
+      {0, 9, 24},
+      {0, 44, 43},
+      {0, 45, 6},
+      {0, 20, 12},
+      {0, 18, 33},
+      {0, 19, 16},
+      {0, 7, 46},
+      {0, 48, 47},
+      {0, 5, 49},
+      {0, 13, 11},
+      {0, 17, 14},
+      {0, 25, 22},
+      {0, 40, 36},
+      {0, 1, 50},
+      {0, 31, 30},
+      {0, 51, 32},
+      {0, 42, 52},
+      {0, 54, 53},
+      {0, 55, 15},
+      {0, 37, 56},
+      {0, 57, 34},
+      {0, 59, 58},
+      {0, 61, 60},
+      {0, 35, 21},
+      {0, 62, 26},
+      {0, 64, 63},
+      {0, 65, 27},
+      {0, 3, 66},
+      {0, 68, 67},
+      {0, 70, 69},
+      {0, 72, 71},
+      {0, 74, 73},
+      {0, 76, 75},
+      {0, 78, 77},
+      {0, 80, 79},
+      {0, 82, 81},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpConstantComposite, 1), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(65, {
+      {0, 0, 0},
+      {142465290, 0, 0},
+      {158160339, 0, 0},
+      {169135842, 0, 0},
+      {210116709, 0, 0},
+      {296981500, 0, 0},
+      {615748604, 0, 0},
+      {910398460, 0, 0},
+      {959681532, 0, 0},
+      {1039111164, 0, 0},
+      {1087394637, 0, 0},
+      {1156369516, 0, 0},
+      {1450415100, 0, 0},
+      {1543672828, 0, 0},
+      {2100532220, 0, 0},
+      {2170273742, 0, 0},
+      {2285081596, 0, 0},
+      {2326636627, 0, 0},
+      {2444465148, 0, 0},
+      {2732195517, 0, 0},
+      {2763232252, 0, 0},
+      {2796901051, 0, 0},
+      {2855506940, 0, 0},
+      {2922615804, 0, 0},
+      {2937761472, 0, 0},
+      {3202349435, 0, 0},
+      {3362723943, 0, 0},
+      {3712763835, 0, 0},
+      {3810805277, 0, 0},
+      {3929248764, 0, 0},
+      {4016096296, 0, 0},
+      {4172568578, 0, 0},
+      {4248015868, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 12, 23},
+      {0, 13, 6},
+      {0, 20, 14},
+      {0, 15, 24},
+      {0, 17, 28},
+      {0, 16, 31},
+      {0, 7, 34},
+      {0, 9, 32},
+      {0, 36, 35},
+      {0, 38, 37},
+      {0, 40, 39},
+      {0, 2, 8},
+      {0, 10, 3},
+      {0, 25, 19},
+      {0, 27, 26},
+      {0, 33, 30},
+      {0, 11, 41},
+      {0, 1, 21},
+      {0, 18, 42},
+      {0, 44, 43},
+      {0, 46, 45},
+      {0, 48, 47},
+      {0, 29, 49},
+      {0, 4, 50},
+      {0, 52, 51},
+      {0, 54, 53},
+      {0, 56, 55},
+      {0, 58, 57},
+      {0, 59, 5},
+      {0, 61, 60},
+      {0, 62, 22},
+      {0, 64, 63},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpConstantComposite, 2), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(57, {
+      {0, 0, 0},
+      {52882140, 0, 0},
+      {210116709, 0, 0},
+      {296981500, 0, 0},
+      {385229009, 0, 0},
+      {615748604, 0, 0},
+      {910398460, 0, 0},
+      {959681532, 0, 0},
+      {1031290113, 0, 0},
+      {1039111164, 0, 0},
+      {1172110445, 0, 0},
+      {1450415100, 0, 0},
+      {1543672828, 0, 0},
+      {1622381564, 0, 0},
+      {1782996825, 0, 0},
+      {1971252067, 0, 0},
+      {2100532220, 0, 0},
+      {2268204687, 0, 0},
+      {2326636627, 0, 0},
+      {2444465148, 0, 0},
+      {2490492987, 0, 0},
+      {2678954464, 0, 0},
+      {2763232252, 0, 0},
+      {2855506940, 0, 0},
+      {2922615804, 0, 0},
+      {3912967080, 0, 0},
+      {3929248764, 0, 0},
+      {4172568578, 0, 0},
+      {4248015868, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 11, 24},
+      {0, 12, 5},
+      {0, 22, 16},
+      {0, 18, 17},
+      {0, 30, 27},
+      {0, 6, 13},
+      {0, 9, 28},
+      {0, 32, 31},
+      {0, 34, 33},
+      {0, 7, 35},
+      {0, 4, 1},
+      {0, 10, 8},
+      {0, 20, 15},
+      {0, 25, 21},
+      {0, 36, 29},
+      {0, 19, 37},
+      {0, 39, 38},
+      {0, 41, 40},
+      {0, 43, 42},
+      {0, 26, 44},
+      {0, 45, 2},
+      {0, 47, 46},
+      {0, 49, 48},
+      {0, 50, 14},
+      {0, 51, 3},
+      {0, 53, 52},
+      {0, 54, 23},
+      {0, 56, 55},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpConstantComposite, 3), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(39, {
+      {0, 0, 0},
+      {210116709, 0, 0},
+      {296981500, 0, 0},
+      {615748604, 0, 0},
+      {910398460, 0, 0},
+      {959681532, 0, 0},
+      {1039111164, 0, 0},
+      {1092948665, 0, 0},
+      {1450415100, 0, 0},
+      {1543672828, 0, 0},
+      {1612361408, 0, 0},
+      {2100532220, 0, 0},
+      {2326636627, 0, 0},
+      {2444465148, 0, 0},
+      {2524697596, 0, 0},
+      {2763232252, 0, 0},
+      {2855506940, 0, 0},
+      {3929248764, 0, 0},
+      {4172568578, 0, 0},
+      {4248015868, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 8, 7},
+      {0, 9, 3},
+      {0, 15, 11},
+      {0, 10, 21},
+      {0, 18, 12},
+      {0, 4, 20},
+      {0, 22, 19},
+      {0, 23, 6},
+      {0, 14, 24},
+      {0, 5, 25},
+      {0, 27, 26},
+      {0, 28, 17},
+      {0, 30, 29},
+      {0, 31, 13},
+      {0, 1, 32},
+      {0, 34, 33},
+      {0, 16, 35},
+      {0, 2, 36},
+      {0, 38, 37},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpConstantComposite, 4), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(35, {
+      {0, 0, 0},
+      {296981500, 0, 0},
+      {615748604, 0, 0},
+      {673708384, 0, 0},
+      {959681532, 0, 0},
+      {1039111164, 0, 0},
+      {1450415100, 0, 0},
+      {1543672828, 0, 0},
+      {1939359710, 0, 0},
+      {2100532220, 0, 0},
+      {2113115132, 0, 0},
+      {2326636627, 0, 0},
+      {2444465148, 0, 0},
+      {2763232252, 0, 0},
+      {2855506940, 0, 0},
+      {3929248764, 0, 0},
+      {4172568578, 0, 0},
+      {4248015868, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 18, 3},
+      {0, 6, 19},
+      {0, 12, 4},
+      {0, 17, 2},
+      {0, 9, 7},
+      {0, 20, 13},
+      {0, 11, 8},
+      {0, 10, 16},
+      {0, 21, 15},
+      {0, 5, 22},
+      {0, 24, 23},
+      {0, 26, 25},
+      {0, 28, 27},
+      {0, 29, 1},
+      {0, 31, 30},
+      {0, 33, 32},
+      {0, 34, 14},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpConstantComposite, 5), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(23, {
+      {0, 0, 0},
+      {545678922, 0, 0},
+      {679771963, 0, 0},
+      {929101967, 0, 0},
+      {1951208733, 0, 0},
+      {2320303498, 0, 0},
+      {3056042030, 0, 0},
+      {3334207724, 0, 0},
+      {3357250579, 0, 0},
+      {3705139860, 0, 0},
+      {3800912395, 0, 0},
+      {3802564010, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 8, 11},
+      {0, 9, 3},
+      {0, 1, 13},
+      {0, 14, 10},
+      {0, 12, 15},
+      {0, 17, 16},
+      {0, 18, 4},
+      {0, 7, 5},
+      {0, 20, 19},
+      {0, 2, 21},
+      {0, 22, 6},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunction, 0), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(89, {
+      {0, 0, 0},
+      {35240468, 0, 0},
+      {123060826, 0, 0},
+      {184634770, 0, 0},
+      {359054425, 0, 0},
+      {459968607, 0, 0},
+      {619875033, 0, 0},
+      {904486530, 0, 0},
+      {945128292, 0, 0},
+      {950731750, 0, 0},
+      {1058429216, 0, 0},
+      {1182296898, 0, 0},
+      {1238120570, 0, 0},
+      {1429389803, 0, 0},
+      {1652168174, 0, 0},
+      {1717510093, 0, 0},
+      {1766422419, 0, 0},
+      {1775308984, 0, 0},
+      {1776629361, 0, 0},
+      {1824526196, 0, 0},
+      {1957265068, 0, 0},
+      {1998433745, 0, 0},
+      {2055664760, 0, 0},
+      {2303184249, 0, 0},
+      {2451531615, 0, 0},
+      {2507457870, 0, 0},
+      {2550501832, 0, 0},
+      {2590402790, 0, 0},
+      {2649103430, 0, 0},
+      {2780190687, 0, 0},
+      {2831059514, 0, 0},
+      {3167253437, 0, 0},
+      {3269075805, 0, 0},
+      {3323202731, 0, 0},
+      {3361419439, 0, 0},
+      {3464197236, 0, 0},
+      {3472029049, 0, 0},
+      {3518630848, 0, 0},
+      {3604842236, 0, 0},
+      {3653985133, 0, 0},
+      {4091916710, 0, 0},
+      {4121643374, 0, 0},
+      {4185590212, 0, 0},
+      {4233562270, 0, 0},
+      {4235213885, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 6, 40},
+      {0, 14, 31},
+      {0, 7, 9},
+      {0, 29, 27},
+      {0, 18, 44},
+      {0, 8, 5},
+      {0, 10, 3},
+      {0, 41, 37},
+      {0, 42, 35},
+      {0, 2, 1},
+      {0, 47, 46},
+      {0, 48, 4},
+      {0, 11, 49},
+      {0, 50, 36},
+      {0, 19, 51},
+      {0, 53, 52},
+      {0, 55, 54},
+      {0, 15, 12},
+      {0, 26, 16},
+      {0, 56, 21},
+      {0, 25, 33},
+      {0, 43, 24},
+      {0, 57, 39},
+      {0, 59, 58},
+      {0, 61, 60},
+      {0, 62, 34},
+      {0, 64, 63},
+      {0, 17, 30},
+      {0, 66, 65},
+      {0, 20, 67},
+      {0, 13, 68},
+      {0, 28, 69},
+      {0, 70, 32},
+      {0, 72, 71},
+      {0, 73, 22},
+      {0, 75, 74},
+      {0, 77, 76},
+      {0, 79, 78},
+      {0, 80, 23},
+      {0, 45, 81},
+      {0, 83, 82},
+      {0, 85, 84},
+      {0, 38, 86},
+      {0, 88, 87},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunction, 1), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(87, {
+      {0, 0, 0},
+      {75986790, 0, 0},
+      {95470391, 0, 0},
+      {170378107, 0, 0},
+      {172029722, 0, 0},
+      {204234270, 0, 0},
+      {205885885, 0, 0},
+      {244668133, 0, 0},
+      {265778447, 0, 0},
+      {753954113, 0, 0},
+      {1000070091, 0, 0},
+      {1671139745, 0, 0},
+      {1774874546, 0, 0},
+      {1776526161, 0, 0},
+      {1887808856, 0, 0},
+      {1889460471, 0, 0},
+      {1917966999, 0, 0},
+      {2044728014, 0, 0},
+      {2192810893, 0, 0},
+      {2293247016, 0, 0},
+      {2503194620, 0, 0},
+      {2608484640, 0, 0},
+      {2615111110, 0, 0},
+      {2668769415, 0, 0},
+      {2759951687, 0, 0},
+      {2761603302, 0, 0},
+      {2856623532, 0, 0},
+      {2956189845, 0, 0},
+      {3085119011, 0, 0},
+      {3367313400, 0, 0},
+      {3447882276, 0, 0},
+      {3633746133, 0, 0},
+      {3635397748, 0, 0},
+      {3710645347, 0, 0},
+      {3712296962, 0, 0},
+      {3727494858, 0, 0},
+      {3747079365, 0, 0},
+      {3748965853, 0, 0},
+      {3750617468, 0, 0},
+      {4018820793, 0, 0},
+      {4022124023, 0, 0},
+      {4024173916, 0, 0},
+      {4215670524, 0, 0},
+      {4217322139, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 39, 28},
+      {0, 29, 40},
+      {0, 37, 33},
+      {0, 38, 12},
+      {0, 9, 26},
+      {0, 18, 6},
+      {0, 41, 3},
+      {0, 11, 13},
+      {0, 5, 8},
+      {0, 45, 30},
+      {0, 22, 46},
+      {0, 48, 47},
+      {0, 16, 17},
+      {0, 34, 49},
+      {0, 51, 50},
+      {0, 53, 52},
+      {0, 7, 2},
+      {0, 23, 21},
+      {0, 54, 10},
+      {0, 20, 36},
+      {0, 55, 35},
+      {0, 56, 4},
+      {0, 43, 57},
+      {0, 59, 58},
+      {0, 60, 42},
+      {0, 62, 61},
+      {0, 63, 15},
+      {0, 64, 31},
+      {0, 14, 65},
+      {0, 66, 24},
+      {0, 67, 32},
+      {0, 68, 19},
+      {0, 70, 69},
+      {0, 71, 27},
+      {0, 73, 72},
+      {0, 75, 74},
+      {0, 77, 76},
+      {0, 78, 25},
+      {0, 44, 79},
+      {0, 81, 80},
+      {0, 83, 82},
+      {0, 1, 84},
+      {0, 86, 85},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunction, 3), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(41, {
+      {0, 0, 0},
+      {436416061, 0, 0},
+      {543621065, 0, 0},
+      {679771963, 0, 0},
+      {815757910, 0, 0},
+      {827698488, 0, 0},
+      {1294403159, 0, 0},
+      {1296054774, 0, 0},
+      {1297706389, 0, 0},
+      {1579585816, 0, 0},
+      {1675764636, 0, 0},
+      {1824016656, 0, 0},
+      {1951208733, 0, 0},
+      {2194691858, 0, 0},
+      {2320303498, 0, 0},
+      {2926633629, 0, 0},
+      {3095831808, 0, 0},
+      {3249265647, 0, 0},
+      {3334207724, 0, 0},
+      {4050155669, 0, 0},
+      {4141567741, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 2, 11},
+      {0, 19, 16},
+      {0, 9, 4},
+      {0, 1, 17},
+      {0, 22, 10},
+      {0, 24, 23},
+      {0, 15, 25},
+      {0, 13, 26},
+      {0, 27, 20},
+      {0, 12, 28},
+      {0, 30, 29},
+      {0, 31, 18},
+      {0, 3, 21},
+      {0, 32, 14},
+      {0, 34, 33},
+      {0, 35, 8},
+      {0, 5, 6},
+      {0, 37, 36},
+      {0, 39, 38},
+      {0, 40, 7},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunctionParameter, 0), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(41, {
+      {0, 0, 0},
+      {522971108, 0, 0},
+      {615341051, 0, 0},
+      {718301639, 0, 0},
+      {985750227, 0, 0},
+      {1395113939, 0, 0},
+      {1510333659, 0, 0},
+      {1642805350, 0, 0},
+      {1846856260, 0, 0},
+      {1957218950, 0, 0},
+      {1977038330, 0, 0},
+      {1978689945, 0, 0},
+      {1980341560, 0, 0},
+      {2262220987, 0, 0},
+      {2674422363, 0, 0},
+      {3197739982, 0, 0},
+      {3465954368, 0, 0},
+      {3941049054, 0, 0},
+      {3945795573, 0, 0},
+      {4080527786, 0, 0},
+      {4154758669, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 3, 17},
+      {0, 4, 15},
+      {0, 8, 7},
+      {0, 2, 20},
+      {0, 22, 19},
+      {0, 24, 23},
+      {0, 14, 25},
+      {0, 16, 26},
+      {0, 27, 13},
+      {0, 6, 28},
+      {0, 30, 29},
+      {0, 31, 10},
+      {0, 11, 21},
+      {0, 32, 12},
+      {0, 34, 33},
+      {0, 35, 5},
+      {0, 9, 18},
+      {0, 37, 36},
+      {0, 39, 38},
+      {0, 40, 1},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunctionParameter, 1), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(27, {
+      {0, 0, 0},
+      {545678922, 0, 0},
+      {679771963, 0, 0},
+      {899570100, 0, 0},
+      {929101967, 0, 0},
+      {1100599986, 0, 0},
+      {1951208733, 0, 0},
+      {2320303498, 0, 0},
+      {3056042030, 0, 0},
+      {3334207724, 0, 0},
+      {3357250579, 0, 0},
+      {3705139860, 0, 0},
+      {3800912395, 0, 0},
+      {3802564010, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 5, 3},
+      {0, 10, 13},
+      {0, 4, 15},
+      {0, 16, 11},
+      {0, 17, 1},
+      {0, 14, 12},
+      {0, 19, 18},
+      {0, 21, 20},
+      {0, 22, 8},
+      {0, 7, 6},
+      {0, 23, 9},
+      {0, 25, 24},
+      {0, 26, 2},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunctionCall, 0), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(115, {
+      {0, 0, 0},
+      {57149555, 0, 0},
+      {86116519, 0, 0},
+      {168339452, 0, 0},
+      {181902171, 0, 0},
+      {284226441, 0, 0},
+      {314809953, 0, 0},
+      {330249537, 0, 0},
+      {527665290, 0, 0},
+      {545363837, 0, 0},
+      {707478563, 0, 0},
+      {740921498, 0, 0},
+      {807276090, 0, 0},
+      {824323032, 0, 0},
+      {835458563, 0, 0},
+      {1162127370, 0, 0},
+      {1245448751, 0, 0},
+      {1277245109, 0, 0},
+      {1375043498, 0, 0},
+      {1380991098, 0, 0},
+      {1603937321, 0, 0},
+      {1708264968, 0, 0},
+      {1717555224, 0, 0},
+      {1765126703, 0, 0},
+      {1838993983, 0, 0},
+      {1949856502, 0, 0},
+      {2108571893, 0, 0},
+      {2110223508, 0, 0},
+      {2293637521, 0, 0},
+      {2377112119, 0, 0},
+      {2378763734, 0, 0},
+      {2512398201, 0, 0},
+      {2516325050, 0, 0},
+      {2645135839, 0, 0},
+      {2708915136, 0, 0},
+      {2894979602, 0, 0},
+      {2903897222, 0, 0},
+      {2976581453, 0, 0},
+      {3054834317, 0, 0},
+      {3075866530, 0, 0},
+      {3085157904, 0, 0},
+      {3242843022, 0, 0},
+      {3266028549, 0, 0},
+      {3296691317, 0, 0},
+      {3299488628, 0, 0},
+      {3322500634, 0, 0},
+      {3345707173, 0, 0},
+      {3536390697, 0, 0},
+      {3584683259, 0, 0},
+      {3647606635, 0, 0},
+      {3760372982, 0, 0},
+      {3823959661, 0, 0},
+      {3839389658, 0, 0},
+      {4124281183, 0, 0},
+      {4130950286, 0, 0},
+      {4169878842, 0, 0},
+      {4174489262, 0, 0},
+      {4237497041, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 17, 23},
+      {0, 37, 8},
+      {0, 45, 39},
+      {0, 41, 14},
+      {0, 48, 43},
+      {0, 40, 31},
+      {0, 19, 29},
+      {0, 53, 26},
+      {0, 10, 5},
+      {0, 50, 24},
+      {0, 27, 3},
+      {0, 59, 32},
+      {0, 51, 18},
+      {0, 52, 55},
+      {0, 60, 57},
+      {0, 62, 61},
+      {0, 36, 33},
+      {0, 64, 63},
+      {0, 65, 22},
+      {0, 66, 46},
+      {0, 6, 67},
+      {0, 68, 13},
+      {0, 21, 44},
+      {0, 1, 69},
+      {0, 30, 11},
+      {0, 71, 70},
+      {0, 12, 72},
+      {0, 74, 73},
+      {0, 76, 75},
+      {0, 16, 2},
+      {0, 49, 35},
+      {0, 77, 9},
+      {0, 42, 28},
+      {0, 15, 78},
+      {0, 80, 79},
+      {0, 82, 81},
+      {0, 47, 83},
+      {0, 85, 84},
+      {0, 87, 86},
+      {0, 89, 88},
+      {0, 20, 38},
+      {0, 54, 90},
+      {0, 34, 91},
+      {0, 93, 92},
+      {0, 25, 94},
+      {0, 95, 7},
+      {0, 97, 96},
+      {0, 56, 98},
+      {0, 100, 99},
+      {0, 102, 101},
+      {0, 104, 103},
+      {0, 4, 105},
+      {0, 107, 106},
+      {0, 58, 108},
+      {0, 110, 109},
+      {0, 112, 111},
+      {0, 114, 113},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunctionCall, 1), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(81, {
+      {0, 0, 0},
+      {35240468, 0, 0},
+      {36096192, 0, 0},
+      {123060826, 0, 0},
+      {184634770, 0, 0},
+      {459968607, 0, 0},
+      {619875033, 0, 0},
+      {950731750, 0, 0},
+      {1058429216, 0, 0},
+      {1182296898, 0, 0},
+      {1238120570, 0, 0},
+      {1271484400, 0, 0},
+      {1429389803, 0, 0},
+      {1717510093, 0, 0},
+      {1766422419, 0, 0},
+      {1775308984, 0, 0},
+      {1817271123, 0, 0},
+      {1917336504, 0, 0},
+      {1957265068, 0, 0},
+      {1998433745, 0, 0},
+      {2055664760, 0, 0},
+      {2303184249, 0, 0},
+      {2308565678, 0, 0},
+      {2451531615, 0, 0},
+      {2496297824, 0, 0},
+      {2507457870, 0, 0},
+      {2550501832, 0, 0},
+      {2590402790, 0, 0},
+      {2649103430, 0, 0},
+      {2831059514, 0, 0},
+      {2836440943, 0, 0},
+      {3269075805, 0, 0},
+      {3361419439, 0, 0},
+      {3457269042, 0, 0},
+      {3464197236, 0, 0},
+      {3472029049, 0, 0},
+      {3518630848, 0, 0},
+      {3587381650, 0, 0},
+      {3653985133, 0, 0},
+      {4185590212, 0, 0},
+      {4233562270, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 40, 37},
+      {0, 22, 30},
+      {0, 2, 7},
+      {0, 24, 11},
+      {0, 16, 33},
+      {0, 6, 34},
+      {0, 42, 27},
+      {0, 5, 43},
+      {0, 4, 44},
+      {0, 36, 8},
+      {0, 39, 45},
+      {0, 46, 1},
+      {0, 3, 47},
+      {0, 48, 23},
+      {0, 49, 9},
+      {0, 50, 35},
+      {0, 52, 51},
+      {0, 32, 53},
+      {0, 13, 10},
+      {0, 26, 14},
+      {0, 19, 54},
+      {0, 55, 25},
+      {0, 56, 38},
+      {0, 17, 57},
+      {0, 59, 58},
+      {0, 61, 60},
+      {0, 62, 29},
+      {0, 12, 15},
+      {0, 18, 63},
+      {0, 28, 64},
+      {0, 65, 31},
+      {0, 67, 66},
+      {0, 20, 41},
+      {0, 69, 68},
+      {0, 71, 70},
+      {0, 21, 72},
+      {0, 74, 73},
+      {0, 76, 75},
+      {0, 78, 77},
+      {0, 80, 79},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunctionCall, 2), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(61, {
+      {0, 0, 0},
+      {37459569, 0, 0},
+      {162167595, 0, 0},
+      {535067202, 0, 0},
+      {701281393, 0, 0},
+      {837715723, 0, 0},
+      {1320550031, 0, 0},
+      {1630583316, 0, 0},
+      {1913735398, 0, 0},
+      {1918481917, 0, 0},
+      {1955871800, 0, 0},
+      {1977038330, 0, 0},
+      {2053214130, 0, 0},
+      {2443959748, 0, 0},
+      {2564745684, 0, 0},
+      {2622612602, 0, 0},
+      {2677252364, 0, 0},
+      {2736026107, 0, 0},
+      {2790624748, 0, 0},
+      {2882994691, 0, 0},
+      {2888125966, 0, 0},
+      {2970183398, 0, 0},
+      {3253403867, 0, 0},
+      {3427283542, 0, 0},
+      {3570411982, 0, 0},
+      {3619787319, 0, 0},
+      {3662767579, 0, 0},
+      {3884846406, 0, 0},
+      {3910458990, 0, 0},
+      {3927915220, 0, 0},
+      {4224872590, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 5, 20},
+      {0, 6, 25},
+      {0, 23, 3},
+      {0, 2, 4},
+      {0, 14, 17},
+      {0, 11, 8},
+      {0, 27, 10},
+      {0, 19, 28},
+      {0, 12, 16},
+      {0, 33, 32},
+      {0, 35, 34},
+      {0, 37, 36},
+      {0, 39, 38},
+      {0, 40, 15},
+      {0, 41, 7},
+      {0, 1, 21},
+      {0, 24, 13},
+      {0, 29, 42},
+      {0, 44, 43},
+      {0, 22, 45},
+      {0, 47, 46},
+      {0, 49, 48},
+      {0, 50, 30},
+      {0, 31, 51},
+      {0, 53, 52},
+      {0, 55, 54},
+      {0, 56, 9},
+      {0, 57, 26},
+      {0, 59, 58},
+      {0, 60, 18},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunctionCall, 3), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(39, {
+      {0, 0, 0},
+      {744062262, 0, 0},
+      {810488476, 0, 0},
+      {1040775722, 0, 0},
+      {1280126114, 0, 0},
+      {1367301635, 0, 0},
+      {1684282922, 0, 0},
+      {1918481917, 0, 0},
+      {1978689945, 0, 0},
+      {1980341560, 0, 0},
+      {2443959748, 0, 0},
+      {2629265310, 0, 0},
+      {2790624748, 0, 0},
+      {2970183398, 0, 0},
+      {3044188332, 0, 0},
+      {3496407048, 0, 0},
+      {3662767579, 0, 0},
+      {3887377256, 0, 0},
+      {3971481069, 0, 0},
+      {4224872590, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 3, 2},
+      {0, 18, 15},
+      {0, 21, 6},
+      {0, 13, 11},
+      {0, 4, 22},
+      {0, 14, 1},
+      {0, 24, 23},
+      {0, 25, 8},
+      {0, 27, 26},
+      {0, 20, 17},
+      {0, 5, 28},
+      {0, 29, 9},
+      {0, 16, 10},
+      {0, 31, 30},
+      {0, 32, 7},
+      {0, 19, 33},
+      {0, 35, 34},
+      {0, 37, 36},
+      {0, 38, 12},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunctionCall, 4), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(27, {
+      {0, 0, 0},
+      {37459569, 0, 0},
+      {837715723, 0, 0},
+      {1352628475, 0, 0},
+      {1918481917, 0, 0},
+      {1978689945, 0, 0},
+      {1980341560, 0, 0},
+      {2096388952, 0, 0},
+      {2622612602, 0, 0},
+      {2790624748, 0, 0},
+      {2970183398, 0, 0},
+      {3510682541, 0, 0},
+      {3783543823, 0, 0},
+      {4224872590, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 7, 11},
+      {0, 2, 8},
+      {0, 15, 12},
+      {0, 1, 3},
+      {0, 16, 6},
+      {0, 18, 17},
+      {0, 19, 14},
+      {0, 20, 5},
+      {0, 10, 21},
+      {0, 22, 4},
+      {0, 23, 13},
+      {0, 25, 24},
+      {0, 9, 26},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunctionCall, 5), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(13, {
+      {0, 0, 0},
+      {1510333659, 0, 0},
+      {1684282922, 0, 0},
+      {1918481917, 0, 0},
+      {2790624748, 0, 0},
+      {3662767579, 0, 0},
+      {4224872590, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 5, 1},
+      {0, 8, 2},
+      {0, 9, 7},
+      {0, 3, 10},
+      {0, 6, 11},
+      {0, 4, 12},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunctionCall, 6), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(27, {
+      {0, 0, 0},
+      {161668409, 0, 0},
+      {188347929, 0, 0},
+      {653708953, 0, 0},
+      {976111724, 0, 0},
+      {1510333659, 0, 0},
+      {1918481917, 0, 0},
+      {2790624748, 0, 0},
+      {3033873113, 0, 0},
+      {3499234137, 0, 0},
+      {3525913657, 0, 0},
+      {3552593177, 0, 0},
+      {3570411982, 0, 0},
+      {4224872590, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 8, 3},
+      {0, 2, 9},
+      {0, 10, 11},
+      {0, 15, 1},
+      {0, 17, 16},
+      {0, 19, 18},
+      {0, 5, 4},
+      {0, 20, 6},
+      {0, 12, 21},
+      {0, 14, 22},
+      {0, 24, 23},
+      {0, 7, 25},
+      {0, 13, 26},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunctionCall, 7), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(31, {
+      {0, 0, 0},
+      {226836633, 0, 0},
+      {296981500, 0, 0},
+      {718877177, 0, 0},
+      {745556697, 0, 0},
+      {798915737, 0, 0},
+      {1510333659, 0, 0},
+      {1684282922, 0, 0},
+      {2444465148, 0, 0},
+      {2713718873, 0, 0},
+      {3495546641, 0, 0},
+      {3564402361, 0, 0},
+      {4056442905, 0, 0},
+      {4083122425, 0, 0},
+      {4123141705, 0, 0},
+      {4224872590, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 14, 4},
+      {0, 5, 3},
+      {0, 9, 8},
+      {0, 13, 12},
+      {0, 1, 11},
+      {0, 18, 17},
+      {0, 2, 19},
+      {0, 21, 20},
+      {0, 23, 22},
+      {0, 25, 24},
+      {0, 26, 7},
+      {0, 27, 16},
+      {0, 10, 6},
+      {0, 29, 28},
+      {0, 15, 30},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunctionCall, 8), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(35, {
+      {0, 0, 0},
+      {161668409, 0, 0},
+      {188347929, 0, 0},
+      {215027449, 0, 0},
+      {296981500, 0, 0},
+      {653708953, 0, 0},
+      {680388473, 0, 0},
+      {1119069977, 0, 0},
+      {1510333659, 0, 0},
+      {1584774136, 0, 0},
+      {2049792025, 0, 0},
+      {2444465148, 0, 0},
+      {2568512089, 0, 0},
+      {3033873113, 0, 0},
+      {3499234137, 0, 0},
+      {3525913657, 0, 0},
+      {3552593177, 0, 0},
+      {4224872590, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 7, 6},
+      {0, 10, 12},
+      {0, 4, 3},
+      {0, 16, 11},
+      {0, 19, 14},
+      {0, 5, 2},
+      {0, 20, 13},
+      {0, 21, 15},
+      {0, 1, 22},
+      {0, 24, 23},
+      {0, 26, 25},
+      {0, 28, 27},
+      {0, 18, 29},
+      {0, 8, 30},
+      {0, 32, 31},
+      {0, 9, 33},
+      {0, 17, 34},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunctionCall, 9), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(25, {
+      {0, 0, 0},
+      {825595257, 0, 0},
+      {1064945649, 0, 0},
+      {1290956281, 0, 0},
+      {1510333659, 0, 0},
+      {2096388952, 0, 0},
+      {2248357849, 0, 0},
+      {2713718873, 0, 0},
+      {3187066832, 0, 0},
+      {3205759417, 0, 0},
+      {4064212479, 0, 0},
+      {4163160985, 0, 0},
+      {4224872590, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 8, 3},
+      {0, 2, 9},
+      {0, 7, 6},
+      {0, 5, 14},
+      {0, 16, 15},
+      {0, 17, 11},
+      {0, 19, 18},
+      {0, 20, 1},
+      {0, 4, 13},
+      {0, 22, 21},
+      {0, 10, 23},
+      {0, 12, 24},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunctionCall, 10), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(27, {
+      {0, 0, 0},
+      {123108003, 0, 0},
+      {296981500, 0, 0},
+      {595410904, 0, 0},
+      {1466938734, 0, 0},
+      {1503477720, 0, 0},
+      {1816558243, 0, 0},
+      {1990431740, 0, 0},
+      {2724625059, 0, 0},
+      {2790624748, 0, 0},
+      {2812498065, 0, 0},
+      {3160388974, 0, 0},
+      {3745223676, 0, 0},
+      {3982311384, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 5, 13},
+      {0, 8, 1},
+      {0, 12, 11},
+      {0, 15, 3},
+      {0, 6, 4},
+      {0, 16, 7},
+      {0, 17, 14},
+      {0, 18, 2},
+      {0, 19, 10},
+      {0, 21, 20},
+      {0, 23, 22},
+      {0, 25, 24},
+      {0, 9, 26},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunctionCall, 11), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(25, {
+      {0, 0, 0},
+      {94145952, 0, 0},
+      {1054641568, 0, 0},
+      {1269075360, 0, 0},
+      {1675922848, 0, 0},
+      {2038205856, 0, 0},
+      {2433519008, 0, 0},
+      {2636942752, 0, 0},
+      {2790624748, 0, 0},
+      {2840366496, 0, 0},
+      {2851900832, 0, 0},
+      {2964622752, 0, 0},
+      {3654061472, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 7, 1},
+      {0, 12, 6},
+      {0, 14, 10},
+      {0, 13, 4},
+      {0, 11, 15},
+      {0, 3, 16},
+      {0, 2, 17},
+      {0, 18, 5},
+      {0, 9, 19},
+      {0, 21, 20},
+      {0, 23, 22},
+      {0, 8, 24},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunctionCall, 12), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(45, {
+      {0, 0, 0},
+      {107544081, 0, 0},
+      {125015036, 0, 0},
+      {586244865, 0, 0},
+      {1033081852, 0, 0},
+      {1064945649, 0, 0},
+      {1155765244, 0, 0},
+      {1304296041, 0, 0},
+      {1543646433, 0, 0},
+      {1782996825, 0, 0},
+      {1941148668, 0, 0},
+      {2002490364, 0, 0},
+      {2022347217, 0, 0},
+      {2063832060, 0, 0},
+      {2487708241, 0, 0},
+      {2726532092, 0, 0},
+      {2849215484, 0, 0},
+      {2966409025, 0, 0},
+      {3445109809, 0, 0},
+      {3458449569, 0, 0},
+      {3634598908, 0, 0},
+      {3695940604, 0, 0},
+      {3923810593, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 7, 2},
+      {0, 14, 13},
+      {0, 1, 23},
+      {0, 6, 5},
+      {0, 16, 15},
+      {0, 24, 17},
+      {0, 12, 25},
+      {0, 22, 18},
+      {0, 10, 26},
+      {0, 28, 27},
+      {0, 21, 29},
+      {0, 31, 30},
+      {0, 9, 8},
+      {0, 11, 32},
+      {0, 33, 19},
+      {0, 3, 34},
+      {0, 36, 35},
+      {0, 38, 37},
+      {0, 20, 39},
+      {0, 41, 40},
+      {0, 42, 4},
+      {0, 44, 43},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunctionCall, 13), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(23, {
+      {0, 0, 0},
+      {247698428, 0, 0},
+      {309040124, 0, 0},
+      {333554713, 0, 0},
+      {572905105, 0, 0},
+      {1033081852, 0, 0},
+      {2002490364, 0, 0},
+      {2009007457, 0, 0},
+      {2487708241, 0, 0},
+      {3634598908, 0, 0},
+      {3695940604, 0, 0},
+      {3923810593, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 6, 1},
+      {0, 9, 7},
+      {0, 5, 12},
+      {0, 14, 13},
+      {0, 15, 8},
+      {0, 3, 16},
+      {0, 17, 11},
+      {0, 10, 4},
+      {0, 2, 18},
+      {0, 20, 19},
+      {0, 22, 21},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunctionCall, 14), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(11, {
+      {0, 0, 0},
+      {247698428, 0, 0},
+      {1033081852, 0, 0},
+      {2002490364, 0, 0},
+      {2910557180, 0, 0},
+      {3757282300, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 6, 4},
+      {0, 7, 3},
+      {0, 2, 8},
+      {0, 1, 5},
+      {0, 10, 9},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunctionCall, 15), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
+      {0, 0, 0},
+      {1033081852, 0, 0},
+      {1094423548, 0, 0},
+      {2002490364, 0, 0},
+      {3757282300, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 3, 5},
+      {0, 6, 2},
+      {0, 4, 7},
+      {0, 8, 1},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunctionCall, 16), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(57, {
+      {0, 0, 0},
+      {135486769, 0, 0},
+      {450406196, 0, 0},
+      {503094540, 0, 0},
+      {543621065, 0, 0},
+      {827698488, 0, 0},
+      {1294403159, 0, 0},
+      {1296054774, 0, 0},
+      {1297706389, 0, 0},
+      {1322549027, 0, 0},
+      {1784441183, 0, 0},
+      {2194691858, 0, 0},
+      {2448331885, 0, 0},
+      {2468230023, 0, 0},
+      {2547657777, 0, 0},
+      {2549309392, 0, 0},
+      {2550961007, 0, 0},
+      {2934934694, 0, 0},
+      {2936586309, 0, 0},
+      {2938237924, 0, 0},
+      {3094180193, 0, 0},
+      {3095831808, 0, 0},
+      {3183924418, 0, 0},
+      {3561562003, 0, 0},
+      {3563213618, 0, 0},
+      {3564865233, 0, 0},
+      {4028622909, 0, 0},
+      {4039938779, 0, 0},
+      {4050155669, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 27, 28},
+      {0, 10, 2},
+      {0, 25, 24},
+      {0, 1, 12},
+      {0, 30, 3},
+      {0, 20, 31},
+      {0, 9, 32},
+      {0, 34, 33},
+      {0, 35, 22},
+      {0, 26, 15},
+      {0, 19, 36},
+      {0, 18, 37},
+      {0, 38, 16},
+      {0, 39, 8},
+      {0, 5, 40},
+      {0, 6, 41},
+      {0, 21, 42},
+      {0, 11, 29},
+      {0, 4, 43},
+      {0, 13, 23},
+      {0, 14, 17},
+      {0, 7, 44},
+      {0, 46, 45},
+      {0, 48, 47},
+      {0, 50, 49},
+      {0, 52, 51},
+      {0, 54, 53},
+      {0, 56, 55},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpVariable, 0), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(57, {
+      {0, 0, 0},
+      {37459569, 0, 0},
+      {112745085, 0, 0},
+      {137840602, 0, 0},
+      {565334834, 0, 0},
+      {625975427, 0, 0},
+      {630964591, 0, 0},
+      {680016782, 0, 0},
+      {769422756, 0, 0},
+      {1009983433, 0, 0},
+      {1093210099, 0, 0},
+      {1572088444, 0, 0},
+      {1584774136, 0, 0},
+      {1641565587, 0, 0},
+      {1918481917, 0, 0},
+      {2190437442, 0, 0},
+      {2790624748, 0, 0},
+      {3085467405, 0, 0},
+      {3181646225, 0, 0},
+      {3192069648, 0, 0},
+      {3253403867, 0, 0},
+      {3390051757, 0, 0},
+      {3560665067, 0, 0},
+      {3662767579, 0, 0},
+      {4053789056, 0, 0},
+      {4064212479, 0, 0},
+      {4192247221, 0, 0},
+      {4224872590, 0, 0},
+      {4290024976, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 2, 20},
+      {0, 28, 10},
+      {0, 13, 8},
+      {0, 15, 17},
+      {0, 30, 21},
+      {0, 19, 31},
+      {0, 4, 32},
+      {0, 34, 33},
+      {0, 35, 5},
+      {0, 7, 24},
+      {0, 9, 36},
+      {0, 3, 37},
+      {0, 38, 6},
+      {0, 39, 23},
+      {0, 27, 40},
+      {0, 14, 41},
+      {0, 25, 42},
+      {0, 1, 29},
+      {0, 12, 43},
+      {0, 11, 26},
+      {0, 18, 22},
+      {0, 16, 44},
+      {0, 46, 45},
+      {0, 48, 47},
+      {0, 50, 49},
+      {0, 52, 51},
+      {0, 54, 53},
+      {0, 56, 55},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpVariable, 1), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(27, {
+      {0, 0, 0},
+      {162255877, 0, 0},
+      {679771963, 0, 0},
+      {789872778, 0, 0},
+      {1154919607, 0, 0},
+      {1343794461, 0, 0},
+      {1951208733, 0, 0},
+      {2263349224, 0, 0},
+      {2320303498, 0, 0},
+      {2924146124, 0, 0},
+      {2984325996, 0, 0},
+      {3334207724, 0, 0},
+      {3868239231, 0, 0},
+      {3869890846, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 5, 3},
+      {0, 9, 7},
+      {0, 12, 4},
+      {0, 16, 15},
+      {0, 18, 17},
+      {0, 14, 19},
+      {0, 13, 10},
+      {0, 20, 1},
+      {0, 21, 8},
+      {0, 2, 22},
+      {0, 11, 23},
+      {0, 6, 24},
+      {0, 26, 25},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpLoad, 0), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(83, {
+      {0, 0, 0},
+      {169674806, 0, 0},
+      {269823086, 0, 0},
+      {408465899, 0, 0},
+      {451264926, 0, 0},
+      {543558236, 0, 0},
+      {810488476, 0, 0},
+      {850497536, 0, 0},
+      {870594305, 0, 0},
+      {883854656, 0, 0},
+      {1033363654, 0, 0},
+      {1069781886, 0, 0},
+      {1141965917, 0, 0},
+      {1323407757, 0, 0},
+      {1570165302, 0, 0},
+      {1684282922, 0, 0},
+      {1742737136, 0, 0},
+      {1901166356, 0, 0},
+      {1949759310, 0, 0},
+      {2043873558, 0, 0},
+      {2087004702, 0, 0},
+      {2096388952, 0, 0},
+      {2157103435, 0, 0},
+      {2219733501, 0, 0},
+      {2356768706, 0, 0},
+      {2443959748, 0, 0},
+      {2517964682, 0, 0},
+      {2614879967, 0, 0},
+      {2622612602, 0, 0},
+      {2660843182, 0, 0},
+      {2959147533, 0, 0},
+      {2970183398, 0, 0},
+      {3044188332, 0, 0},
+      {3091876332, 0, 0},
+      {3187066832, 0, 0},
+      {3244209297, 0, 0},
+      {3487022798, 0, 0},
+      {3496407048, 0, 0},
+      {3570411982, 0, 0},
+      {3692647551, 0, 0},
+      {3713290482, 0, 0},
+      {3831290364, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 4, 1},
+      {0, 35, 13},
+      {0, 25, 11},
+      {0, 7, 10},
+      {0, 19, 36},
+      {0, 43, 27},
+      {0, 16, 29},
+      {0, 22, 3},
+      {0, 41, 30},
+      {0, 44, 12},
+      {0, 2, 24},
+      {0, 40, 32},
+      {0, 23, 45},
+      {0, 46, 39},
+      {0, 17, 33},
+      {0, 48, 47},
+      {0, 8, 49},
+      {0, 51, 50},
+      {0, 52, 20},
+      {0, 53, 14},
+      {0, 31, 54},
+      {0, 15, 55},
+      {0, 57, 56},
+      {0, 59, 58},
+      {0, 6, 26},
+      {0, 61, 60},
+      {0, 34, 62},
+      {0, 64, 63},
+      {0, 5, 37},
+      {0, 9, 65},
+      {0, 18, 28},
+      {0, 66, 38},
+      {0, 68, 67},
+      {0, 69, 21},
+      {0, 71, 70},
+      {0, 73, 72},
+      {0, 75, 74},
+      {0, 77, 76},
+      {0, 79, 78},
+      {0, 80, 42},
+      {0, 82, 81},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpLoad, 1), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(83, {
+      {0, 0, 0},
+      {28782128, 0, 0},
+      {30433743, 0, 0},
+      {37459569, 0, 0},
+      {137840602, 0, 0},
+      {522971108, 0, 0},
+      {565334834, 0, 0},
+      {625975427, 0, 0},
+      {630964591, 0, 0},
+      {680016782, 0, 0},
+      {1009983433, 0, 0},
+      {1079999262, 0, 0},
+      {1395113939, 0, 0},
+      {1572088444, 0, 0},
+      {1584774136, 0, 0},
+      {1649426421, 0, 0},
+      {1918481917, 0, 0},
+      {1957218950, 0, 0},
+      {2311941439, 0, 0},
+      {2313593054, 0, 0},
+      {2790624748, 0, 0},
+      {2838165089, 0, 0},
+      {2839816704, 0, 0},
+      {2841468319, 0, 0},
+      {3085467405, 0, 0},
+      {3181646225, 0, 0},
+      {3192069648, 0, 0},
+      {3253403867, 0, 0},
+      {3364388739, 0, 0},
+      {3366040354, 0, 0},
+      {3367691969, 0, 0},
+      {3369343584, 0, 0},
+      {3560665067, 0, 0},
+      {3662767579, 0, 0},
+      {3945795573, 0, 0},
+      {4053789056, 0, 0},
+      {4064212479, 0, 0},
+      {4224872590, 0, 0},
+      {4239834800, 0, 0},
+      {4241486415, 0, 0},
+      {4243138030, 0, 0},
+      {4244789645, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 27},
+      {0, 15, 2},
+      {0, 10, 26},
+      {0, 7, 24},
+      {0, 9, 31},
+      {0, 43, 30},
+      {0, 29, 12},
+      {0, 11, 41},
+      {0, 40, 39},
+      {0, 44, 23},
+      {0, 22, 6},
+      {0, 34, 35},
+      {0, 18, 45},
+      {0, 46, 21},
+      {0, 17, 19},
+      {0, 48, 47},
+      {0, 28, 49},
+      {0, 51, 50},
+      {0, 52, 38},
+      {0, 53, 33},
+      {0, 4, 54},
+      {0, 13, 55},
+      {0, 57, 56},
+      {0, 59, 58},
+      {0, 37, 8},
+      {0, 61, 60},
+      {0, 5, 62},
+      {0, 64, 63},
+      {0, 36, 32},
+      {0, 3, 65},
+      {0, 14, 16},
+      {0, 66, 25},
+      {0, 68, 67},
+      {0, 69, 20},
+      {0, 71, 70},
+      {0, 73, 72},
+      {0, 75, 74},
+      {0, 77, 76},
+      {0, 79, 78},
+      {0, 80, 42},
+      {0, 82, 81},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpLoad, 2), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(49, {
+      {0, 0, 0},
+      {137840602, 0, 0},
+      {522971108, 0, 0},
+      {769422756, 0, 0},
+      {1009983433, 0, 0},
+      {1079999262, 0, 0},
+      {1558345254, 0, 0},
+      {1572088444, 0, 0},
+      {1641565587, 0, 0},
+      {1918481917, 0, 0},
+      {2311941439, 0, 0},
+      {2313593054, 0, 0},
+      {2790624748, 0, 0},
+      {2838165089, 0, 0},
+      {2994529201, 0, 0},
+      {2996180816, 0, 0},
+      {2997832431, 0, 0},
+      {3027538652, 0, 0},
+      {3253403867, 0, 0},
+      {3364388739, 0, 0},
+      {3560665067, 0, 0},
+      {3662767579, 0, 0},
+      {3945795573, 0, 0},
+      {4192247221, 0, 0},
+      {4224872590, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 14, 17},
+      {0, 16, 15},
+      {0, 13, 11},
+      {0, 10, 3},
+      {0, 22, 18},
+      {0, 6, 8},
+      {0, 19, 2},
+      {0, 27, 26},
+      {0, 28, 5},
+      {0, 30, 29},
+      {0, 32, 31},
+      {0, 34, 33},
+      {0, 4, 35},
+      {0, 37, 36},
+      {0, 21, 1},
+      {0, 39, 38},
+      {0, 40, 24},
+      {0, 7, 23},
+      {0, 20, 9},
+      {0, 42, 41},
+      {0, 43, 25},
+      {0, 44, 12},
+      {0, 46, 45},
+      {0, 48, 47},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpStore, 0), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(59, {
+      {0, 0, 0},
+      {139011596, 0, 0},
+      {177111659, 0, 0},
+      {296981500, 0, 0},
+      {408465899, 0, 0},
+      {495107308, 0, 0},
+      {810488476, 0, 0},
+      {870594305, 0, 0},
+      {1367301635, 0, 0},
+      {1901166356, 0, 0},
+      {2055836767, 0, 0},
+      {2087004702, 0, 0},
+      {2096388952, 0, 0},
+      {2204920111, 0, 0},
+      {2517964682, 0, 0},
+      {2622612602, 0, 0},
+      {2660843182, 0, 0},
+      {2842919847, 0, 0},
+      {2855506940, 0, 0},
+      {2959147533, 0, 0},
+      {3044188332, 0, 0},
+      {3187066832, 0, 0},
+      {3504158761, 0, 0},
+      {3570411982, 0, 0},
+      {3619787319, 0, 0},
+      {3653838348, 0, 0},
+      {3692647551, 0, 0},
+      {3764205609, 0, 0},
+      {3831290364, 0, 0},
+      {3913885196, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 20, 29},
+      {0, 25, 8},
+      {0, 5, 1},
+      {0, 24, 26},
+      {0, 14, 9},
+      {0, 27, 16},
+      {0, 31, 7},
+      {0, 33, 32},
+      {0, 17, 34},
+      {0, 35, 13},
+      {0, 22, 6},
+      {0, 3, 2},
+      {0, 23, 36},
+      {0, 28, 37},
+      {0, 19, 4},
+      {0, 38, 10},
+      {0, 39, 15},
+      {0, 40, 18},
+      {0, 42, 41},
+      {0, 43, 12},
+      {0, 44, 21},
+      {0, 45, 11},
+      {0, 47, 46},
+      {0, 49, 48},
+      {0, 51, 50},
+      {0, 53, 52},
+      {0, 55, 54},
+      {0, 57, 56},
+      {0, 30, 58},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpStore, 1), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(35, {
+      {0, 0, 0},
+      {440421571, 0, 0},
+      {827698488, 0, 0},
+      {907126242, 0, 0},
+      {908777857, 0, 0},
+      {910429472, 0, 0},
+      {1294403159, 0, 0},
+      {1296054774, 0, 0},
+      {1297706389, 0, 0},
+      {2080953106, 0, 0},
+      {2468230023, 0, 0},
+      {2547657777, 0, 0},
+      {2549309392, 0, 0},
+      {2550961007, 0, 0},
+      {3094857332, 0, 0},
+      {3561562003, 0, 0},
+      {3563213618, 0, 0},
+      {3564865233, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 16, 12},
+      {0, 17, 13},
+      {0, 14, 19},
+      {0, 18, 20},
+      {0, 5, 21},
+      {0, 11, 7},
+      {0, 15, 22},
+      {0, 9, 8},
+      {0, 24, 23},
+      {0, 25, 4},
+      {0, 27, 26},
+      {0, 28, 3},
+      {0, 29, 10},
+      {0, 6, 1},
+      {0, 31, 30},
+      {0, 32, 2},
+      {0, 34, 33},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpAccessChain, 0), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(99, {
+      {0, 0, 0},
+      {27130513, 0, 0},
+      {28782128, 0, 0},
+      {30433743, 0, 0},
+      {32085358, 0, 0},
+      {155458798, 0, 0},
+      {157110413, 0, 0},
+      {163402553, 0, 0},
+      {165054168, 0, 0},
+      {213642219, 0, 0},
+      {215293834, 0, 0},
+      {216945449, 0, 0},
+      {221900294, 0, 0},
+      {545986953, 0, 0},
+      {979993429, 0, 0},
+      {1079999262, 0, 0},
+      {1302400505, 0, 0},
+      {1313182965, 0, 0},
+      {1314834580, 0, 0},
+      {1315613425, 0, 0},
+      {1317265040, 0, 0},
+      {1558345254, 0, 0},
+      {1649426421, 0, 0},
+      {2311941439, 0, 0},
+      {2313593054, 0, 0},
+      {2602027658, 0, 0},
+      {2838165089, 0, 0},
+      {2839816704, 0, 0},
+      {2841468319, 0, 0},
+      {2863084840, 0, 0},
+      {2994529201, 0, 0},
+      {2996180816, 0, 0},
+      {2997832431, 0, 0},
+      {3027538652, 0, 0},
+      {3187387500, 0, 0},
+      {3189039115, 0, 0},
+      {3364388739, 0, 0},
+      {3366040354, 0, 0},
+      {3367691969, 0, 0},
+      {3369343584, 0, 0},
+      {3716914380, 0, 0},
+      {3928842969, 0, 0},
+      {3930494584, 0, 0},
+      {3932146199, 0, 0},
+      {3945482286, 0, 0},
+      {4105051793, 0, 0},
+      {4239834800, 0, 0},
+      {4241486415, 0, 0},
+      {4243138030, 0, 0},
+      {4244789645, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 29, 10},
+      {0, 17, 18},
+      {0, 13, 14},
+      {0, 44, 25},
+      {0, 8, 7},
+      {0, 20, 11},
+      {0, 33, 19},
+      {0, 6, 45},
+      {0, 42, 43},
+      {0, 40, 5},
+      {0, 9, 16},
+      {0, 1, 4},
+      {0, 35, 34},
+      {0, 12, 21},
+      {0, 52, 51},
+      {0, 31, 30},
+      {0, 41, 32},
+      {0, 54, 53},
+      {0, 55, 2},
+      {0, 3, 56},
+      {0, 58, 57},
+      {0, 60, 59},
+      {0, 61, 22},
+      {0, 63, 62},
+      {0, 65, 64},
+      {0, 67, 66},
+      {0, 39, 68},
+      {0, 38, 69},
+      {0, 47, 70},
+      {0, 49, 71},
+      {0, 28, 48},
+      {0, 37, 15},
+      {0, 73, 72},
+      {0, 74, 27},
+      {0, 23, 75},
+      {0, 76, 26},
+      {0, 24, 77},
+      {0, 79, 78},
+      {0, 81, 80},
+      {0, 82, 46},
+      {0, 36, 83},
+      {0, 85, 84},
+      {0, 87, 86},
+      {0, 89, 88},
+      {0, 91, 90},
+      {0, 93, 92},
+      {0, 95, 94},
+      {0, 97, 96},
+      {0, 50, 98},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpAccessChain, 1), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(101, {
+      {0, 0, 0},
+      {112745085, 0, 0},
+      {116376005, 0, 0},
+      {137840602, 0, 0},
+      {400248103, 0, 0},
+      {406044930, 0, 0},
+      {468372467, 0, 0},
+      {522971108, 0, 0},
+      {615341051, 0, 0},
+      {625975427, 0, 0},
+      {630964591, 0, 0},
+      {680016782, 0, 0},
+      {763027711, 0, 0},
+      {977312655, 0, 0},
+      {1009983433, 0, 0},
+      {1062250709, 0, 0},
+      {1395113939, 0, 0},
+      {1410849099, 0, 0},
+      {1642805350, 0, 0},
+      {1692932387, 0, 0},
+      {1698730948, 0, 0},
+      {1827244161, 0, 0},
+      {1918481917, 0, 0},
+      {2096472894, 0, 0},
+      {2190437442, 0, 0},
+      {2299842241, 0, 0},
+      {2433358586, 0, 0},
+      {2593325766, 0, 0},
+      {2785441472, 0, 0},
+      {2790624748, 0, 0},
+      {2879917723, 0, 0},
+      {2882994691, 0, 0},
+      {2902069960, 0, 0},
+      {3090408469, 0, 0},
+      {3181646225, 0, 0},
+      {3255947500, 0, 0},
+      {3263901372, 0, 0},
+      {3268751013, 0, 0},
+      {3347863687, 0, 0},
+      {3390051757, 0, 0},
+      {3560665067, 0, 0},
+      {3617689692, 0, 0},
+      {3662767579, 0, 0},
+      {3717523241, 0, 0},
+      {3854557817, 0, 0},
+      {3910458990, 0, 0},
+      {3941049054, 0, 0},
+      {3945795573, 0, 0},
+      {4080527786, 0, 0},
+      {4101009465, 0, 0},
+      {4290024976, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 32, 44},
+      {0, 41, 26},
+      {0, 16, 10},
+      {0, 27, 45},
+      {0, 25, 38},
+      {0, 12, 18},
+      {0, 6, 35},
+      {0, 46, 23},
+      {0, 20, 37},
+      {0, 52, 19},
+      {0, 53, 21},
+      {0, 54, 48},
+      {0, 33, 55},
+      {0, 3, 8},
+      {0, 28, 56},
+      {0, 13, 57},
+      {0, 59, 58},
+      {0, 1, 49},
+      {0, 47, 60},
+      {0, 61, 14},
+      {0, 63, 62},
+      {0, 64, 43},
+      {0, 7, 4},
+      {0, 65, 15},
+      {0, 67, 66},
+      {0, 68, 17},
+      {0, 36, 2},
+      {0, 30, 69},
+      {0, 71, 70},
+      {0, 34, 5},
+      {0, 73, 72},
+      {0, 75, 74},
+      {0, 77, 76},
+      {0, 24, 78},
+      {0, 39, 31},
+      {0, 80, 79},
+      {0, 9, 11},
+      {0, 42, 81},
+      {0, 83, 82},
+      {0, 29, 50},
+      {0, 84, 51},
+      {0, 86, 85},
+      {0, 22, 40},
+      {0, 88, 87},
+      {0, 90, 89},
+      {0, 92, 91},
+      {0, 94, 93},
+      {0, 96, 95},
+      {0, 98, 97},
+      {0, 100, 99},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpAccessChain, 2), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(69, {
+      {0, 0, 0},
+      {51041423, 0, 0},
+      {142465290, 0, 0},
+      {144116905, 0, 0},
+      {290391815, 0, 0},
+      {438318340, 0, 0},
+      {529742207, 0, 0},
+      {677668732, 0, 0},
+      {917019124, 0, 0},
+      {1064945649, 0, 0},
+      {1156369516, 0, 0},
+      {1158021131, 0, 0},
+      {1304296041, 0, 0},
+      {1452222566, 0, 0},
+      {1543646433, 0, 0},
+      {1691572958, 0, 0},
+      {1782996825, 0, 0},
+      {1784648440, 0, 0},
+      {1930923350, 0, 0},
+      {2170273742, 0, 0},
+      {2318200267, 0, 0},
+      {2466126792, 0, 0},
+      {2557550659, 0, 0},
+      {2705477184, 0, 0},
+      {2796901051, 0, 0},
+      {2798552666, 0, 0},
+      {2944827576, 0, 0},
+      {3092754101, 0, 0},
+      {3184177968, 0, 0},
+      {3332104493, 0, 0},
+      {3571454885, 0, 0},
+      {3810805277, 0, 0},
+      {3958731802, 0, 0},
+      {4106658327, 0, 0},
+      {4198082194, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 27, 33},
+      {0, 21, 5},
+      {0, 26, 13},
+      {0, 20, 8},
+      {0, 15, 7},
+      {0, 37, 36},
+      {0, 32, 29},
+      {0, 38, 4},
+      {0, 30, 1},
+      {0, 9, 12},
+      {0, 39, 18},
+      {0, 22, 40},
+      {0, 42, 41},
+      {0, 44, 43},
+      {0, 45, 35},
+      {0, 46, 34},
+      {0, 6, 14},
+      {0, 28, 23},
+      {0, 48, 47},
+      {0, 49, 31},
+      {0, 51, 50},
+      {0, 19, 24},
+      {0, 52, 10},
+      {0, 2, 53},
+      {0, 55, 54},
+      {0, 25, 56},
+      {0, 11, 57},
+      {0, 59, 58},
+      {0, 3, 17},
+      {0, 61, 60},
+      {0, 16, 62},
+      {0, 64, 63},
+      {0, 66, 65},
+      {0, 68, 67},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpAccessChain, 3), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(85, {
+      {0, 0, 0},
+      {142465290, 0, 0},
+      {144116905, 0, 0},
+      {198967948, 0, 0},
+      {290391815, 0, 0},
+      {529742207, 0, 0},
+      {586244865, 0, 0},
+      {677668732, 0, 0},
+      {825595257, 0, 0},
+      {917019124, 0, 0},
+      {973521782, 0, 0},
+      {1064945649, 0, 0},
+      {1156369516, 0, 0},
+      {1158021131, 0, 0},
+      {1212872174, 0, 0},
+      {1304296041, 0, 0},
+      {1452222566, 0, 0},
+      {1543646433, 0, 0},
+      {1600149091, 0, 0},
+      {1782996825, 0, 0},
+      {1784648440, 0, 0},
+      {1839499483, 0, 0},
+      {1930923350, 0, 0},
+      {2170273742, 0, 0},
+      {2226776400, 0, 0},
+      {2318200267, 0, 0},
+      {2466126792, 0, 0},
+      {2557550659, 0, 0},
+      {2614053317, 0, 0},
+      {2796901051, 0, 0},
+      {2798552666, 0, 0},
+      {2853403709, 0, 0},
+      {2944827576, 0, 0},
+      {3184177968, 0, 0},
+      {3240680626, 0, 0},
+      {3480031018, 0, 0},
+      {3571454885, 0, 0},
+      {3810805277, 0, 0},
+      {3867307935, 0, 0},
+      {3958731802, 0, 0},
+      {4106658327, 0, 0},
+      {4198082194, 0, 0},
+      {4254584852, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 7, 11},
+      {0, 15, 4},
+      {0, 32, 25},
+      {0, 44, 39},
+      {0, 36, 22},
+      {0, 45, 17},
+      {0, 24, 46},
+      {0, 10, 9},
+      {0, 6, 27},
+      {0, 28, 18},
+      {0, 42, 34},
+      {0, 31, 14},
+      {0, 41, 38},
+      {0, 26, 3},
+      {0, 47, 33},
+      {0, 21, 8},
+      {0, 5, 35},
+      {0, 40, 16},
+      {0, 37, 23},
+      {0, 49, 48},
+      {0, 51, 50},
+      {0, 53, 52},
+      {0, 55, 54},
+      {0, 57, 56},
+      {0, 59, 58},
+      {0, 61, 60},
+      {0, 63, 62},
+      {0, 65, 64},
+      {0, 67, 66},
+      {0, 68, 12},
+      {0, 29, 69},
+      {0, 70, 1},
+      {0, 30, 2},
+      {0, 43, 71},
+      {0, 73, 72},
+      {0, 74, 20},
+      {0, 75, 19},
+      {0, 77, 76},
+      {0, 13, 78},
+      {0, 80, 79},
+      {0, 82, 81},
+      {0, 84, 83},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpAccessChain, 4), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
+      {0, 0, 0},
+      {144116905, 0, 0},
+      {1158021131, 0, 0},
+      {1784648440, 0, 0},
+      {2798552666, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 5},
+      {0, 4, 2},
+      {0, 6, 3},
+      {0, 8, 7},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpAccessChain, 5), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
+      {0, 0, 0},
+      {142465290, 0, 0},
+      {1782996825, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 2, 1},
+      {0, 4, 3},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpAccessChain, 6), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {2320303498, 0, 0},
+      {3334207724, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 3, 4},
+      {0, 2, 5},
+      {0, 6, 1},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpVectorShuffle, 0), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(59, {
+      {0, 0, 0},
+      {177111659, 0, 0},
+      {413918748, 0, 0},
+      {529383565, 0, 0},
+      {646282397, 0, 0},
+      {837715723, 0, 0},
+      {1019457583, 0, 0},
+      {1022544883, 0, 0},
+      {1054461787, 0, 0},
+      {1097775533, 0, 0},
+      {1136775085, 0, 0},
+      {1191015885, 0, 0},
+      {1196280518, 0, 0},
+      {1203545131, 0, 0},
+      {1352628475, 0, 0},
+      {1367301635, 0, 0},
+      {1918742169, 0, 0},
+      {1922045399, 0, 0},
+      {2055836767, 0, 0},
+      {2183547611, 0, 0},
+      {2204920111, 0, 0},
+      {2358141757, 0, 0},
+      {2572638469, 0, 0},
+      {2597020383, 0, 0},
+      {2842919847, 0, 0},
+      {3619787319, 0, 0},
+      {3701632935, 0, 0},
+      {3783543823, 0, 0},
+      {4245257809, 0, 0},
+      {4265894873, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 11, 23},
+      {0, 12, 2},
+      {0, 9, 7},
+      {0, 21, 19},
+      {0, 4, 29},
+      {0, 10, 28},
+      {0, 17, 16},
+      {0, 27, 3},
+      {0, 32, 31},
+      {0, 33, 22},
+      {0, 6, 34},
+      {0, 35, 8},
+      {0, 36, 24},
+      {0, 38, 37},
+      {0, 1, 14},
+      {0, 39, 20},
+      {0, 5, 40},
+      {0, 42, 41},
+      {0, 43, 26},
+      {0, 45, 44},
+      {0, 47, 46},
+      {0, 48, 18},
+      {0, 15, 49},
+      {0, 50, 25},
+      {0, 51, 13},
+      {0, 53, 52},
+      {0, 55, 54},
+      {0, 57, 56},
+      {0, 30, 58},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpVectorShuffle, 1), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(59, {
+      {0, 0, 0},
+      {236660303, 0, 0},
+      {342159236, 0, 0},
+      {371428004, 0, 0},
+      {373079619, 0, 0},
+      {488500848, 0, 0},
+      {495107308, 0, 0},
+      {864295921, 0, 0},
+      {1071164424, 0, 0},
+      {1136911283, 0, 0},
+      {1178317551, 0, 0},
+      {1510422521, 0, 0},
+      {1570165302, 0, 0},
+      {1822823090, 0, 0},
+      {1858116930, 0, 0},
+      {1977038330, 0, 0},
+      {2096388952, 0, 0},
+      {2157103435, 0, 0},
+      {2231688008, 0, 0},
+      {2604576561, 0, 0},
+      {2622612602, 0, 0},
+      {2771938750, 0, 0},
+      {2777172031, 0, 0},
+      {2996594997, 0, 0},
+      {3187066832, 0, 0},
+      {3496407048, 0, 0},
+      {3570411982, 0, 0},
+      {3609540589, 0, 0},
+      {3713290482, 0, 0},
+      {3797761273, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 18, 8},
+      {0, 27, 9},
+      {0, 21, 10},
+      {0, 14, 24},
+      {0, 12, 19},
+      {0, 11, 15},
+      {0, 23, 2},
+      {0, 7, 13},
+      {0, 31, 22},
+      {0, 32, 4},
+      {0, 33, 29},
+      {0, 34, 1},
+      {0, 35, 3},
+      {0, 37, 36},
+      {0, 38, 28},
+      {0, 39, 5},
+      {0, 41, 40},
+      {0, 42, 17},
+      {0, 16, 43},
+      {0, 45, 44},
+      {0, 46, 6},
+      {0, 48, 47},
+      {0, 50, 49},
+      {0, 52, 51},
+      {0, 25, 53},
+      {0, 54, 20},
+      {0, 55, 26},
+      {0, 57, 56},
+      {0, 30, 58},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpVectorShuffle, 2), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(47, {
+      {0, 0, 0},
+      {236660303, 0, 0},
+      {342159236, 0, 0},
+      {488500848, 0, 0},
+      {495107308, 0, 0},
+      {864295921, 0, 0},
+      {1178317551, 0, 0},
+      {1510422521, 0, 0},
+      {1570165302, 0, 0},
+      {1858116930, 0, 0},
+      {1977038330, 0, 0},
+      {2096388952, 0, 0},
+      {2157103435, 0, 0},
+      {2231688008, 0, 0},
+      {2604576561, 0, 0},
+      {2622612602, 0, 0},
+      {2771938750, 0, 0},
+      {2777172031, 0, 0},
+      {2996594997, 0, 0},
+      {3496407048, 0, 0},
+      {3570411982, 0, 0},
+      {3609540589, 0, 0},
+      {3713290482, 0, 0},
+      {3797761273, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 21, 13},
+      {0, 16, 6},
+      {0, 14, 9},
+      {0, 7, 10},
+      {0, 18, 2},
+      {0, 17, 5},
+      {0, 25, 8},
+      {0, 22, 12},
+      {0, 26, 23},
+      {0, 27, 1},
+      {0, 28, 3},
+      {0, 30, 29},
+      {0, 32, 31},
+      {0, 34, 33},
+      {0, 35, 11},
+      {0, 36, 4},
+      {0, 38, 37},
+      {0, 40, 39},
+      {0, 41, 15},
+      {0, 42, 19},
+      {0, 20, 43},
+      {0, 45, 44},
+      {0, 24, 46},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpVectorShuffle, 3), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(15, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1146476634, 0, 0},
+      {2160380860, 0, 0},
+      {2320303498, 0, 0},
+      {3334207724, 0, 0},
+      {3800912395, 0, 0},
+      {3802564010, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 2, 3},
+      {0, 9, 6},
+      {0, 8, 7},
+      {0, 11, 10},
+      {0, 4, 12},
+      {0, 5, 13},
+      {0, 14, 1},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpCompositeConstruct, 0), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(79, {
+      {0, 0, 0},
+      {107497541, 0, 0},
+      {289648234, 0, 0},
+      {348584153, 0, 0},
+      {369686787, 0, 0},
+      {429277936, 0, 0},
+      {449954059, 0, 0},
+      {508217552, 0, 0},
+      {742917749, 0, 0},
+      {1032593647, 0, 0},
+      {1158929937, 0, 0},
+      {1209418480, 0, 0},
+      {1319785741, 0, 0},
+      {1321616112, 0, 0},
+      {1417363940, 0, 0},
+      {1541020250, 0, 0},
+      {1564342316, 0, 0},
+      {1578775276, 0, 0},
+      {1631434666, 0, 0},
+      {1636389511, 0, 0},
+      {2012838864, 0, 0},
+      {2262137600, 0, 0},
+      {2281956980, 0, 0},
+      {2359973133, 0, 0},
+      {2464905186, 0, 0},
+      {2613179511, 0, 0},
+      {2621255555, 0, 0},
+      {2817335337, 0, 0},
+      {2881302403, 0, 0},
+      {3063300848, 0, 0},
+      {3151638847, 0, 0},
+      {3233393284, 0, 0},
+      {3323682385, 0, 0},
+      {3337532056, 0, 0},
+      {3456899824, 0, 0},
+      {3547456240, 0, 0},
+      {3675926744, 0, 0},
+      {3753486980, 0, 0},
+      {3931641900, 0, 0},
+      {3970432934, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 25, 1},
+      {0, 6, 4},
+      {0, 8, 19},
+      {0, 39, 24},
+      {0, 3, 2},
+      {0, 34, 14},
+      {0, 10, 9},
+      {0, 18, 38},
+      {0, 32, 15},
+      {0, 27, 16},
+      {0, 28, 35},
+      {0, 13, 26},
+      {0, 20, 23},
+      {0, 21, 11},
+      {0, 36, 33},
+      {0, 5, 22},
+      {0, 42, 41},
+      {0, 43, 29},
+      {0, 45, 44},
+      {0, 7, 46},
+      {0, 48, 47},
+      {0, 30, 31},
+      {0, 50, 49},
+      {0, 52, 51},
+      {0, 54, 53},
+      {0, 55, 17},
+      {0, 57, 56},
+      {0, 59, 58},
+      {0, 61, 60},
+      {0, 62, 12},
+      {0, 64, 63},
+      {0, 66, 65},
+      {0, 67, 37},
+      {0, 69, 68},
+      {0, 71, 70},
+      {0, 73, 72},
+      {0, 75, 74},
+      {0, 77, 76},
+      {0, 40, 78},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpCompositeConstruct, 1), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(87, {
+      {0, 0, 0},
+      {153013225, 0, 0},
+      {296836635, 0, 0},
+      {296981500, 0, 0},
+      {778500192, 0, 0},
+      {810488476, 0, 0},
+      {848380423, 0, 0},
+      {900522183, 0, 0},
+      {910398460, 0, 0},
+      {959681532, 0, 0},
+      {1141965917, 0, 0},
+      {1287304304, 0, 0},
+      {1323407757, 0, 0},
+      {1417363940, 0, 0},
+      {1471851763, 0, 0},
+      {1526654696, 0, 0},
+      {1654776395, 0, 0},
+      {1684282922, 0, 0},
+      {1739837626, 0, 0},
+      {1791352211, 0, 0},
+      {2195550588, 0, 0},
+      {2319227476, 0, 0},
+      {2491124112, 0, 0},
+      {2789375411, 0, 0},
+      {2807448986, 0, 0},
+      {2817579280, 0, 0},
+      {2835131395, 0, 0},
+      {2847102741, 0, 0},
+      {2855506940, 0, 0},
+      {2860348412, 0, 0},
+      {3079287749, 0, 0},
+      {3091876332, 0, 0},
+      {3168953855, 0, 0},
+      {3374978006, 0, 0},
+      {3399062057, 0, 0},
+      {3510257966, 0, 0},
+      {3554463148, 0, 0},
+      {3579593979, 0, 0},
+      {3757851979, 0, 0},
+      {3759503594, 0, 0},
+      {3761155209, 0, 0},
+      {3762806824, 0, 0},
+      {3902853271, 0, 0},
+      {4140081844, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 38, 42},
+      {0, 14, 23},
+      {0, 26, 18},
+      {0, 39, 35},
+      {0, 6, 40},
+      {0, 16, 13},
+      {0, 33, 34},
+      {0, 12, 4},
+      {0, 27, 41},
+      {0, 25, 21},
+      {0, 24, 1},
+      {0, 37, 19},
+      {0, 32, 22},
+      {0, 2, 8},
+      {0, 20, 17},
+      {0, 43, 36},
+      {0, 29, 15},
+      {0, 46, 45},
+      {0, 48, 47},
+      {0, 50, 49},
+      {0, 52, 51},
+      {0, 54, 53},
+      {0, 7, 55},
+      {0, 56, 30},
+      {0, 57, 5},
+      {0, 59, 58},
+      {0, 60, 11},
+      {0, 9, 61},
+      {0, 63, 62},
+      {0, 65, 64},
+      {0, 66, 31},
+      {0, 68, 67},
+      {0, 10, 69},
+      {0, 71, 70},
+      {0, 28, 72},
+      {0, 74, 73},
+      {0, 76, 75},
+      {0, 78, 77},
+      {0, 79, 3},
+      {0, 81, 80},
+      {0, 83, 82},
+      {0, 85, 84},
+      {0, 44, 86},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpCompositeConstruct, 2), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(81, {
+      {0, 0, 0},
+      {14244860, 0, 0},
+      {150820676, 0, 0},
+      {153013225, 0, 0},
+      {269823086, 0, 0},
+      {289648234, 0, 0},
+      {296981500, 0, 0},
+      {678695941, 0, 0},
+      {810488476, 0, 0},
+      {850592577, 0, 0},
+      {870594305, 0, 0},
+      {910398460, 0, 0},
+      {959681532, 0, 0},
+      {1206571206, 0, 0},
+      {1287304304, 0, 0},
+      {1323407757, 0, 0},
+      {1471851763, 0, 0},
+      {1526654696, 0, 0},
+      {1684282922, 0, 0},
+      {1734446471, 0, 0},
+      {1758530522, 0, 0},
+      {2117320444, 0, 0},
+      {2118972059, 0, 0},
+      {2120623674, 0, 0},
+      {2122275289, 0, 0},
+      {2219733501, 0, 0},
+      {2262321736, 0, 0},
+      {2807448986, 0, 0},
+      {2817579280, 0, 0},
+      {2835131395, 0, 0},
+      {2855506940, 0, 0},
+      {2860348412, 0, 0},
+      {2951272396, 0, 0},
+      {3079287749, 0, 0},
+      {3168953855, 0, 0},
+      {3502816184, 0, 0},
+      {3510257966, 0, 0},
+      {3554463148, 0, 0},
+      {3997952447, 0, 0},
+      {4140081844, 0, 0},
+      {4182141402, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 21, 26},
+      {0, 29, 16},
+      {0, 22, 36},
+      {0, 1, 23},
+      {0, 20, 5},
+      {0, 19, 35},
+      {0, 10, 38},
+      {0, 13, 24},
+      {0, 28, 7},
+      {0, 27, 3},
+      {0, 40, 2},
+      {0, 34, 9},
+      {0, 32, 11},
+      {0, 33, 18},
+      {0, 39, 37},
+      {0, 31, 17},
+      {0, 43, 42},
+      {0, 45, 44},
+      {0, 47, 46},
+      {0, 49, 48},
+      {0, 51, 50},
+      {0, 8, 52},
+      {0, 15, 53},
+      {0, 55, 54},
+      {0, 56, 14},
+      {0, 58, 57},
+      {0, 60, 59},
+      {0, 61, 25},
+      {0, 63, 62},
+      {0, 4, 64},
+      {0, 66, 65},
+      {0, 68, 67},
+      {0, 70, 69},
+      {0, 71, 12},
+      {0, 6, 72},
+      {0, 30, 73},
+      {0, 75, 74},
+      {0, 77, 76},
+      {0, 79, 78},
+      {0, 41, 80},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpCompositeConstruct, 3), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(111, {
+      {0, 0, 0},
+      {34183582, 0, 0},
+      {93914936, 0, 0},
+      {94303122, 0, 0},
+      {117998987, 0, 0},
+      {153013225, 0, 0},
+      {296981500, 0, 0},
+      {451264926, 0, 0},
+      {473485679, 0, 0},
+      {476788909, 0, 0},
+      {478440524, 0, 0},
+      {480092139, 0, 0},
+      {481743754, 0, 0},
+      {810488476, 0, 0},
+      {871966503, 0, 0},
+      {910398460, 0, 0},
+      {918189168, 0, 0},
+      {933769938, 0, 0},
+      {959681532, 0, 0},
+      {1149665466, 0, 0},
+      {1166917451, 0, 0},
+      {1227221002, 0, 0},
+      {1310740861, 0, 0},
+      {1323407757, 0, 0},
+      {1341516288, 0, 0},
+      {1373166395, 0, 0},
+      {1445161581, 0, 0},
+      {1461645203, 0, 0},
+      {1471851763, 0, 0},
+      {1526654696, 0, 0},
+      {1561718045, 0, 0},
+      {1593584949, 0, 0},
+      {1684282922, 0, 0},
+      {1800404122, 0, 0},
+      {1862284649, 0, 0},
+      {2213411495, 0, 0},
+      {2668680621, 0, 0},
+      {2805256437, 0, 0},
+      {2807448986, 0, 0},
+      {2835131395, 0, 0},
+      {2855506940, 0, 0},
+      {2860348412, 0, 0},
+      {3000904950, 0, 0},
+      {3107413701, 0, 0},
+      {3168953855, 0, 0},
+      {3333131702, 0, 0},
+      {3365041621, 0, 0},
+      {3456899824, 0, 0},
+      {3505028338, 0, 0},
+      {3510257966, 0, 0},
+      {3554463148, 0, 0},
+      {3606320646, 0, 0},
+      {3692647551, 0, 0},
+      {3861006967, 0, 0},
+      {4126287524, 0, 0},
+      {4140081844, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 14, 33},
+      {0, 35, 25},
+      {0, 27, 17},
+      {0, 8, 20},
+      {0, 3, 54},
+      {0, 1, 19},
+      {0, 10, 46},
+      {0, 11, 9},
+      {0, 39, 28},
+      {0, 53, 49},
+      {0, 12, 2},
+      {0, 34, 4},
+      {0, 47, 36},
+      {0, 23, 45},
+      {0, 5, 37},
+      {0, 24, 38},
+      {0, 43, 26},
+      {0, 48, 51},
+      {0, 44, 32},
+      {0, 15, 16},
+      {0, 57, 22},
+      {0, 55, 50},
+      {0, 29, 58},
+      {0, 60, 59},
+      {0, 41, 61},
+      {0, 63, 62},
+      {0, 65, 64},
+      {0, 67, 66},
+      {0, 69, 68},
+      {0, 13, 70},
+      {0, 71, 7},
+      {0, 42, 31},
+      {0, 73, 72},
+      {0, 75, 74},
+      {0, 21, 30},
+      {0, 77, 76},
+      {0, 79, 78},
+      {0, 81, 80},
+      {0, 82, 18},
+      {0, 84, 83},
+      {0, 86, 85},
+      {0, 88, 87},
+      {0, 90, 89},
+      {0, 52, 91},
+      {0, 6, 92},
+      {0, 94, 93},
+      {0, 96, 95},
+      {0, 98, 97},
+      {0, 99, 40},
+      {0, 101, 100},
+      {0, 103, 102},
+      {0, 105, 104},
+      {0, 107, 106},
+      {0, 109, 108},
+      {0, 56, 110},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpCompositeConstruct, 4), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(155, {
+      {0, 0, 0},
+      {18776483, 0, 0},
+      {37009196, 0, 0},
+      {277023757, 0, 0},
+      {296981500, 0, 0},
+      {348988933, 0, 0},
+      {451264926, 0, 0},
+      {564884461, 0, 0},
+      {804899022, 0, 0},
+      {810488476, 0, 0},
+      {870594305, 0, 0},
+      {876864198, 0, 0},
+      {900522183, 0, 0},
+      {928261291, 0, 0},
+      {959681532, 0, 0},
+      {1164724902, 0, 0},
+      {1323407757, 0, 0},
+      {1332774287, 0, 0},
+      {1404739463, 0, 0},
+      {1447712361, 0, 0},
+      {1450415100, 0, 0},
+      {1513770932, 0, 0},
+      {1620634991, 0, 0},
+      {1692600167, 0, 0},
+      {1860649552, 0, 0},
+      {1932614728, 0, 0},
+      {2087004702, 0, 0},
+      {2148510256, 0, 0},
+      {2220475432, 0, 0},
+      {2388524817, 0, 0},
+      {2460489993, 0, 0},
+      {2676385521, 0, 0},
+      {2748350697, 0, 0},
+      {2855506940, 0, 0},
+      {2860348412, 0, 0},
+      {2916400082, 0, 0},
+      {2988365258, 0, 0},
+      {3061856840, 0, 0},
+      {3063508455, 0, 0},
+      {3065160070, 0, 0},
+      {3066811685, 0, 0},
+      {3068463300, 0, 0},
+      {3070114915, 0, 0},
+      {3071766530, 0, 0},
+      {3073418145, 0, 0},
+      {3075069760, 0, 0},
+      {3076721375, 0, 0},
+      {3078372990, 0, 0},
+      {3080024605, 0, 0},
+      {3081676220, 0, 0},
+      {3083327835, 0, 0},
+      {3084979450, 0, 0},
+      {3086631065, 0, 0},
+      {3088282680, 0, 0},
+      {3114708520, 0, 0},
+      {3116360135, 0, 0},
+      {3118011750, 0, 0},
+      {3119663365, 0, 0},
+      {3121314980, 0, 0},
+      {3124618210, 0, 0},
+      {3126269825, 0, 0},
+      {3127921440, 0, 0},
+      {3129573055, 0, 0},
+      {3131224670, 0, 0},
+      {3132876285, 0, 0},
+      {3134527900, 0, 0},
+      {3136179515, 0, 0},
+      {3204260786, 0, 0},
+      {3264086791, 0, 0},
+      {3276225962, 0, 0},
+      {3444275347, 0, 0},
+      {3516240523, 0, 0},
+      {3588205699, 0, 0},
+      {3732136051, 0, 0},
+      {3804101227, 0, 0},
+      {3874089391, 0, 0},
+      {4044115788, 0, 0},
+      {4116080964, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 45, 43},
+      {0, 3, 46},
+      {0, 71, 36},
+      {0, 44, 34},
+      {0, 76, 54},
+      {0, 73, 55},
+      {0, 57, 67},
+      {0, 51, 56},
+      {0, 31, 27},
+      {0, 38, 37},
+      {0, 40, 39},
+      {0, 42, 41},
+      {0, 49, 47},
+      {0, 35, 50},
+      {0, 21, 70},
+      {0, 19, 5},
+      {0, 8, 58},
+      {0, 17, 11},
+      {0, 24, 18},
+      {0, 30, 29},
+      {0, 52, 9},
+      {0, 77, 22},
+      {0, 62, 48},
+      {0, 25, 53},
+      {0, 20, 59},
+      {0, 26, 60},
+      {0, 72, 6},
+      {0, 79, 69},
+      {0, 80, 7},
+      {0, 81, 2},
+      {0, 12, 13},
+      {0, 82, 68},
+      {0, 65, 61},
+      {0, 74, 63},
+      {0, 23, 83},
+      {0, 64, 10},
+      {0, 84, 32},
+      {0, 66, 28},
+      {0, 15, 85},
+      {0, 86, 16},
+      {0, 88, 87},
+      {0, 90, 89},
+      {0, 92, 91},
+      {0, 1, 93},
+      {0, 95, 94},
+      {0, 97, 96},
+      {0, 99, 98},
+      {0, 100, 75},
+      {0, 102, 101},
+      {0, 104, 103},
+      {0, 106, 105},
+      {0, 107, 14},
+      {0, 109, 108},
+      {0, 111, 110},
+      {0, 113, 112},
+      {0, 115, 114},
+      {0, 117, 116},
+      {0, 119, 118},
+      {0, 121, 120},
+      {0, 123, 122},
+      {0, 125, 124},
+      {0, 127, 126},
+      {0, 129, 128},
+      {0, 131, 130},
+      {0, 133, 132},
+      {0, 135, 134},
+      {0, 137, 136},
+      {0, 139, 138},
+      {0, 141, 140},
+      {0, 143, 142},
+      {0, 145, 144},
+      {0, 147, 146},
+      {0, 33, 148},
+      {0, 4, 149},
+      {0, 78, 150},
+      {0, 152, 151},
+      {0, 154, 153},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpCompositeConstruct, 5), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(11, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {789872778, 0, 0},
+      {1951208733, 0, 0},
+      {2320303498, 0, 0},
+      {3334207724, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 6, 2},
+      {0, 4, 7},
+      {0, 1, 8},
+      {0, 9, 5},
+      {0, 3, 10},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpCompositeExtract, 0), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(49, {
+      {0, 0, 0},
+      {126463145, 0, 0},
+      {171307615, 0, 0},
+      {342159236, 0, 0},
+      {354479447, 0, 0},
+      {593829839, 0, 0},
+      {743407979, 0, 0},
+      {898191441, 0, 0},
+      {900522183, 0, 0},
+      {1265796414, 0, 0},
+      {1287304304, 0, 0},
+      {1356063462, 0, 0},
+      {1368383673, 0, 0},
+      {1526654696, 0, 0},
+      {1766994680, 0, 0},
+      {1793544760, 0, 0},
+      {1811839150, 0, 0},
+      {2234361374, 0, 0},
+      {2279700640, 0, 0},
+      {2383939514, 0, 0},
+      {2780898906, 0, 0},
+      {2996594997, 0, 0},
+      {3413713311, 0, 0},
+      {3554463148, 0, 0},
+      {3635542517, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 11, 15},
+      {0, 20, 14},
+      {0, 7, 18},
+      {0, 6, 1},
+      {0, 12, 10},
+      {0, 23, 19},
+      {0, 13, 5},
+      {0, 24, 17},
+      {0, 21, 3},
+      {0, 22, 16},
+      {0, 26, 2},
+      {0, 27, 8},
+      {0, 4, 28},
+      {0, 29, 9},
+      {0, 31, 30},
+      {0, 33, 32},
+      {0, 35, 34},
+      {0, 37, 36},
+      {0, 39, 38},
+      {0, 41, 40},
+      {0, 43, 42},
+      {0, 45, 44},
+      {0, 47, 46},
+      {0, 25, 48},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpCompositeExtract, 1), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(153, {
+      {0, 0, 0},
+      {13107491, 0, 0},
+      {257136089, 0, 0},
+      {293528591, 0, 0},
+      {321459212, 0, 0},
+      {425022309, 0, 0},
+      {490769168, 0, 0},
+      {495107308, 0, 0},
+      {517919178, 0, 0},
+      {617312262, 0, 0},
+      {708736129, 0, 0},
+      {753756604, 0, 0},
+      {765238787, 0, 0},
+      {796985462, 0, 0},
+      {819503463, 0, 0},
+      {850497536, 0, 0},
+      {948086521, 0, 0},
+      {1004589179, 0, 0},
+      {1120149824, 0, 0},
+      {1165671422, 0, 0},
+      {1203545131, 0, 0},
+      {1297165140, 0, 0},
+      {1335363438, 0, 0},
+      {1351676723, 0, 0},
+      {1391866096, 0, 0},
+      {1584369690, 0, 0},
+      {1631216488, 0, 0},
+      {1691646294, 0, 0},
+      {1779143013, 0, 0},
+      {1858116930, 0, 0},
+      {1890300748, 0, 0},
+      {1915438939, 0, 0},
+      {1918742169, 0, 0},
+      {1922045399, 0, 0},
+      {1961990747, 0, 0},
+      {2037710159, 0, 0},
+      {2037814253, 0, 0},
+      {2043873558, 0, 0},
+      {2096388952, 0, 0},
+      {2169307971, 0, 0},
+      {2257843797, 0, 0},
+      {2262220987, 0, 0},
+      {2338272340, 0, 0},
+      {2405770322, 0, 0},
+      {2498042266, 0, 0},
+      {2563789125, 0, 0},
+      {2588618056, 0, 0},
+      {2645120714, 0, 0},
+      {2864863800, 0, 0},
+      {2909957084, 0, 0},
+      {2975894973, 0, 0},
+      {3041450802, 0, 0},
+      {3151638847, 0, 0},
+      {3187066832, 0, 0},
+      {3244716568, 0, 0},
+      {3271748023, 0, 0},
+      {3304438238, 0, 0},
+      {3312467582, 0, 0},
+      {3325419312, 0, 0},
+      {3370185097, 0, 0},
+      {3419674548, 0, 0},
+      {3435931956, 0, 0},
+      {3504158761, 0, 0},
+      {3602522282, 0, 0},
+      {3653059026, 0, 0},
+      {3716353056, 0, 0},
+      {3782099915, 0, 0},
+      {3838648480, 0, 0},
+      {3847846774, 0, 0},
+      {3913593633, 0, 0},
+      {3989799199, 0, 0},
+      {3997038726, 0, 0},
+      {4046301857, 0, 0},
+      {4092654294, 0, 0},
+      {4176581069, 0, 0},
+      {4242327928, 0, 0},
+      {4285652249, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 74, 38},
+      {0, 12, 56},
+      {0, 28, 24},
+      {0, 60, 43},
+      {0, 65, 72},
+      {0, 18, 2},
+      {0, 52, 3},
+      {0, 19, 10},
+      {0, 49, 36},
+      {0, 67, 66},
+      {0, 41, 17},
+      {0, 53, 11},
+      {0, 29, 68},
+      {0, 26, 55},
+      {0, 70, 76},
+      {0, 73, 47},
+      {0, 51, 22},
+      {0, 39, 21},
+      {0, 5, 9},
+      {0, 40, 48},
+      {0, 59, 44},
+      {0, 6, 69},
+      {0, 32, 31},
+      {0, 4, 33},
+      {0, 13, 54},
+      {0, 14, 50},
+      {0, 35, 75},
+      {0, 58, 23},
+      {0, 16, 34},
+      {0, 27, 63},
+      {0, 45, 61},
+      {0, 20, 46},
+      {0, 71, 1},
+      {0, 79, 78},
+      {0, 81, 80},
+      {0, 83, 82},
+      {0, 84, 8},
+      {0, 86, 85},
+      {0, 88, 87},
+      {0, 90, 89},
+      {0, 92, 91},
+      {0, 94, 93},
+      {0, 96, 95},
+      {0, 98, 97},
+      {0, 64, 99},
+      {0, 101, 100},
+      {0, 103, 102},
+      {0, 105, 104},
+      {0, 106, 62},
+      {0, 108, 107},
+      {0, 110, 109},
+      {0, 7, 111},
+      {0, 113, 112},
+      {0, 115, 114},
+      {0, 117, 116},
+      {0, 119, 118},
+      {0, 121, 120},
+      {0, 123, 122},
+      {0, 30, 124},
+      {0, 126, 125},
+      {0, 128, 127},
+      {0, 130, 129},
+      {0, 132, 131},
+      {0, 134, 133},
+      {0, 135, 25},
+      {0, 57, 136},
+      {0, 138, 137},
+      {0, 42, 139},
+      {0, 37, 140},
+      {0, 142, 141},
+      {0, 143, 15},
+      {0, 145, 144},
+      {0, 147, 146},
+      {0, 149, 148},
+      {0, 151, 150},
+      {0, 152, 77},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpCompositeExtract, 2), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(47, {
+      {0, 0, 0},
+      {545678922, 0, 0},
+      {630592085, 0, 0},
+      {679771963, 0, 0},
+      {899570100, 0, 0},
+      {906176560, 0, 0},
+      {929101967, 0, 0},
+      {1100599986, 0, 0},
+      {1103903216, 0, 0},
+      {1107206446, 0, 0},
+      {1369578001, 0, 0},
+      {1372881231, 0, 0},
+      {2320303498, 0, 0},
+      {2926633629, 0, 0},
+      {3249265647, 0, 0},
+      {3334207724, 0, 0},
+      {3486057732, 0, 0},
+      {3674863070, 0, 0},
+      {3705139860, 0, 0},
+      {3800912395, 0, 0},
+      {3802564010, 0, 0},
+      {3822983876, 0, 0},
+      {4141567741, 0, 0},
+      {4292991777, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 9, 17},
+      {0, 20, 11},
+      {0, 25, 5},
+      {0, 2, 14},
+      {0, 23, 13},
+      {0, 16, 26},
+      {0, 27, 24},
+      {0, 28, 8},
+      {0, 29, 18},
+      {0, 22, 30},
+      {0, 6, 31},
+      {0, 21, 32},
+      {0, 3, 33},
+      {0, 35, 34},
+      {0, 1, 12},
+      {0, 10, 36},
+      {0, 37, 19},
+      {0, 4, 15},
+      {0, 39, 38},
+      {0, 7, 40},
+      {0, 42, 41},
+      {0, 44, 43},
+      {0, 46, 45},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpCompositeInsert, 0), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(103, {
+      {0, 0, 0},
+      {125792961, 0, 0},
+      {132755933, 0, 0},
+      {156014509, 0, 0},
+      {436066778, 0, 0},
+      {463084678, 0, 0},
+      {531559080, 0, 0},
+      {565233904, 0, 0},
+      {578132535, 0, 0},
+      {600906020, 0, 0},
+      {602222721, 0, 0},
+      {694743357, 0, 0},
+      {760554870, 0, 0},
+      {996663016, 0, 0},
+      {1022309772, 0, 0},
+      {1351676723, 0, 0},
+      {1496901698, 0, 0},
+      {1502470404, 0, 0},
+      {1522901980, 0, 0},
+      {1548254487, 0, 0},
+      {1637661947, 0, 0},
+      {1788504755, 0, 0},
+      {2092468906, 0, 0},
+      {2094647776, 0, 0},
+      {2127660080, 0, 0},
+      {2213946343, 0, 0},
+      {2225172640, 0, 0},
+      {2259467579, 0, 0},
+      {2263866576, 0, 0},
+      {2600961503, 0, 0},
+      {2727022058, 0, 0},
+      {2752967311, 0, 0},
+      {2864705739, 0, 0},
+      {3021406120, 0, 0},
+      {3044723416, 0, 0},
+      {3052439312, 0, 0},
+      {3136865519, 0, 0},
+      {3297860332, 0, 0},
+      {3352361837, 0, 0},
+      {3670298840, 0, 0},
+      {3712946115, 0, 0},
+      {3732709413, 0, 0},
+      {3764662384, 0, 0},
+      {3788324110, 0, 0},
+      {3928555688, 0, 0},
+      {4083347580, 0, 0},
+      {4098876453, 0, 0},
+      {4147239510, 0, 0},
+      {4199470013, 0, 0},
+      {4211577142, 0, 0},
+      {4218799564, 0, 0},
+      {4290374884, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 4, 2},
+      {0, 9, 8},
+      {0, 17, 10},
+      {0, 20, 18},
+      {0, 22, 21},
+      {0, 26, 23},
+      {0, 31, 29},
+      {0, 35, 34},
+      {0, 45, 36},
+      {0, 5, 3},
+      {0, 12, 6},
+      {0, 15, 14},
+      {0, 25, 19},
+      {0, 28, 27},
+      {0, 38, 33},
+      {0, 43, 39},
+      {0, 47, 46},
+      {0, 50, 49},
+      {0, 7, 51},
+      {0, 1, 48},
+      {0, 37, 24},
+      {0, 44, 42},
+      {0, 13, 11},
+      {0, 41, 40},
+      {0, 54, 53},
+      {0, 56, 55},
+      {0, 58, 57},
+      {0, 60, 59},
+      {0, 62, 61},
+      {0, 64, 63},
+      {0, 66, 65},
+      {0, 68, 67},
+      {0, 70, 69},
+      {0, 72, 71},
+      {0, 30, 16},
+      {0, 73, 32},
+      {0, 75, 74},
+      {0, 77, 76},
+      {0, 79, 78},
+      {0, 81, 80},
+      {0, 83, 82},
+      {0, 85, 84},
+      {0, 87, 86},
+      {0, 89, 88},
+      {0, 91, 90},
+      {0, 93, 92},
+      {0, 95, 94},
+      {0, 97, 96},
+      {0, 99, 98},
+      {0, 101, 100},
+      {0, 52, 102},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpCompositeInsert, 1), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(93, {
+      {0, 0, 0},
+      {17185761, 0, 0},
+      {117250846, 0, 0},
+      {296981500, 0, 0},
+      {330388453, 0, 0},
+      {346929928, 0, 0},
+      {533021259, 0, 0},
+      {564302770, 0, 0},
+      {680157484, 0, 0},
+      {721450866, 0, 0},
+      {798549062, 0, 0},
+      {853200279, 0, 0},
+      {864295921, 0, 0},
+      {900522183, 0, 0},
+      {973908139, 0, 0},
+      {983243705, 0, 0},
+      {1033363654, 0, 0},
+      {1037370721, 0, 0},
+      {1464587427, 0, 0},
+      {1670691893, 0, 0},
+      {1686512349, 0, 0},
+      {1849065716, 0, 0},
+      {1917602962, 0, 0},
+      {1965902997, 0, 0},
+      {2121980967, 0, 0},
+      {2311072371, 0, 0},
+      {2339901602, 0, 0},
+      {2517964682, 0, 0},
+      {2542834724, 0, 0},
+      {2558655180, 0, 0},
+      {2736881867, 0, 0},
+      {2855506940, 0, 0},
+      {2888753905, 0, 0},
+      {2950446516, 0, 0},
+      {3044188332, 0, 0},
+      {3079287749, 0, 0},
+      {3153451899, 0, 0},
+      {3214537066, 0, 0},
+      {3234673086, 0, 0},
+      {3349230696, 0, 0},
+      {3504158761, 0, 0},
+      {3570411982, 0, 0},
+      {3652695478, 0, 0},
+      {3764205609, 0, 0},
+      {3940720663, 0, 0},
+      {4180570743, 0, 0},
+      {4221373527, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 24, 18},
+      {0, 4, 2},
+      {0, 15, 14},
+      {0, 21, 20},
+      {0, 29, 26},
+      {0, 42, 36},
+      {0, 7, 45},
+      {0, 37, 9},
+      {0, 8, 5},
+      {0, 32, 11},
+      {0, 39, 38},
+      {0, 12, 10},
+      {0, 28, 19},
+      {0, 1, 46},
+      {0, 17, 6},
+      {0, 30, 23},
+      {0, 44, 33},
+      {0, 35, 13},
+      {0, 16, 48},
+      {0, 50, 49},
+      {0, 52, 51},
+      {0, 54, 53},
+      {0, 55, 40},
+      {0, 57, 56},
+      {0, 59, 58},
+      {0, 61, 60},
+      {0, 25, 22},
+      {0, 63, 62},
+      {0, 3, 64},
+      {0, 66, 65},
+      {0, 68, 67},
+      {0, 70, 69},
+      {0, 34, 71},
+      {0, 73, 72},
+      {0, 75, 74},
+      {0, 77, 76},
+      {0, 27, 43},
+      {0, 79, 78},
+      {0, 81, 80},
+      {0, 83, 82},
+      {0, 84, 31},
+      {0, 86, 85},
+      {0, 41, 87},
+      {0, 89, 88},
+      {0, 91, 90},
+      {0, 47, 92},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpCompositeInsert, 2), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(115, {
+      {0, 0, 0},
+      {132755933, 0, 0},
+      {156014509, 0, 0},
+      {255227811, 0, 0},
+      {371186900, 0, 0},
+      {371428004, 0, 0},
+      {374731234, 0, 0},
+      {531559080, 0, 0},
+      {565233904, 0, 0},
+      {578132535, 0, 0},
+      {591140762, 0, 0},
+      {600906020, 0, 0},
+      {602222721, 0, 0},
+      {656610661, 0, 0},
+      {760554870, 0, 0},
+      {996663016, 0, 0},
+      {1022309772, 0, 0},
+      {1496901698, 0, 0},
+      {1502470404, 0, 0},
+      {1522901980, 0, 0},
+      {1536350567, 0, 0},
+      {1543280290, 0, 0},
+      {1548254487, 0, 0},
+      {1788504755, 0, 0},
+      {2064733527, 0, 0},
+      {2092468906, 0, 0},
+      {2094647776, 0, 0},
+      {2162986400, 0, 0},
+      {2225172640, 0, 0},
+      {2259467579, 0, 0},
+      {2263866576, 0, 0},
+      {2360004627, 0, 0},
+      {2507709226, 0, 0},
+      {2600961503, 0, 0},
+      {2727022058, 0, 0},
+      {2752967311, 0, 0},
+      {2864705739, 0, 0},
+      {3021406120, 0, 0},
+      {3052439312, 0, 0},
+      {3136865519, 0, 0},
+      {3297860332, 0, 0},
+      {3352361837, 0, 0},
+      {3598957382, 0, 0},
+      {3619787319, 0, 0},
+      {3655201337, 0, 0},
+      {3670298840, 0, 0},
+      {3774892253, 0, 0},
+      {3788324110, 0, 0},
+      {3808408202, 0, 0},
+      {3951925872, 0, 0},
+      {3952316364, 0, 0},
+      {4098876453, 0, 0},
+      {4147239510, 0, 0},
+      {4199470013, 0, 0},
+      {4211577142, 0, 0},
+      {4217306348, 0, 0},
+      {4218799564, 0, 0},
+      {4290374884, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 6, 43},
+      {0, 4, 1},
+      {0, 11, 9},
+      {0, 13, 12},
+      {0, 19, 18},
+      {0, 25, 23},
+      {0, 28, 26},
+      {0, 35, 33},
+      {0, 39, 38},
+      {0, 2, 49},
+      {0, 7, 3},
+      {0, 16, 14},
+      {0, 29, 22},
+      {0, 37, 30},
+      {0, 45, 41},
+      {0, 51, 47},
+      {0, 54, 52},
+      {0, 57, 56},
+      {0, 53, 8},
+      {0, 32, 10},
+      {0, 42, 40},
+      {0, 24, 46},
+      {0, 15, 50},
+      {0, 55, 20},
+      {0, 59, 44},
+      {0, 61, 60},
+      {0, 63, 62},
+      {0, 65, 64},
+      {0, 67, 66},
+      {0, 69, 68},
+      {0, 71, 70},
+      {0, 73, 72},
+      {0, 75, 74},
+      {0, 77, 76},
+      {0, 31, 17},
+      {0, 36, 34},
+      {0, 79, 78},
+      {0, 81, 80},
+      {0, 27, 82},
+      {0, 5, 21},
+      {0, 48, 83},
+      {0, 85, 84},
+      {0, 87, 86},
+      {0, 89, 88},
+      {0, 91, 90},
+      {0, 93, 92},
+      {0, 95, 94},
+      {0, 97, 96},
+      {0, 99, 98},
+      {0, 101, 100},
+      {0, 103, 102},
+      {0, 105, 104},
+      {0, 107, 106},
+      {0, 109, 108},
+      {0, 111, 110},
+      {0, 113, 112},
+      {0, 58, 114},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpCompositeInsert, 3), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
+      {0, 0, 0},
+      {3866587616, 0, 0},
+      {3868239231, 0, 0},
+      {3869890846, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 4},
+      {0, 2, 5},
+      {0, 3, 6},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpSampledImage, 0), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
+      {0, 0, 0},
+      {1164218401, 0, 0},
+      {2036361232, 0, 0},
+      {2637132451, 0, 0},
+      {3237903670, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 4, 5},
+      {0, 3, 6},
+      {0, 1, 7},
+      {0, 2, 8},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpSampledImage, 1), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
+      {0, 0, 0},
+      {543558236, 0, 0},
+      {1069781886, 0, 0},
+      {1596005536, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 3, 4},
+      {0, 2, 5},
+      {0, 1, 6},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpSampledImage, 2), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
+      {0, 0, 0},
+      {1949759310, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 2},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpSampledImage, 3), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
+      {0, 0, 0},
+      {3334207724, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 2},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpImageSampleImplicitLod, 0), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(87, {
+      {0, 0, 0},
+      {236660303, 0, 0},
+      {347505241, 0, 0},
+      {426360862, 0, 0},
+      {439998433, 0, 0},
+      {488500848, 0, 0},
+      {495107308, 0, 0},
+      {868652905, 0, 0},
+      {1191735827, 0, 0},
+      {1265998516, 0, 0},
+      {1309728002, 0, 0},
+      {1365842164, 0, 0},
+      {1396344138, 0, 0},
+      {1508074873, 0, 0},
+      {1553476262, 0, 0},
+      {1642818143, 0, 0},
+      {1851510470, 0, 0},
+      {1858116930, 0, 0},
+      {1863199739, 0, 0},
+      {1979978194, 0, 0},
+      {1986584654, 0, 0},
+      {2092100514, 0, 0},
+      {2098706974, 0, 0},
+      {2231688008, 0, 0},
+      {2232491275, 0, 0},
+      {2329992200, 0, 0},
+      {2637935122, 0, 0},
+      {2693892518, 0, 0},
+      {2759250216, 0, 0},
+      {2839765116, 0, 0},
+      {2855895374, 0, 0},
+      {2913136690, 0, 0},
+      {3012980338, 0, 0},
+      {3327770644, 0, 0},
+      {3362344229, 0, 0},
+      {3398925952, 0, 0},
+      {3448018532, 0, 0},
+      {3457985288, 0, 0},
+      {3566035349, 0, 0},
+      {3657635382, 0, 0},
+      {3702405475, 0, 0},
+      {3757479030, 0, 0},
+      {3797204453, 0, 0},
+      {4291477370, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 32, 28},
+      {0, 9, 35},
+      {0, 31, 11},
+      {0, 10, 30},
+      {0, 25, 21},
+      {0, 40, 2},
+      {0, 15, 19},
+      {0, 24, 36},
+      {0, 42, 4},
+      {0, 18, 16},
+      {0, 29, 26},
+      {0, 43, 7},
+      {0, 45, 8},
+      {0, 37, 13},
+      {0, 47, 46},
+      {0, 48, 33},
+      {0, 49, 14},
+      {0, 3, 22},
+      {0, 50, 12},
+      {0, 41, 39},
+      {0, 51, 34},
+      {0, 52, 20},
+      {0, 54, 53},
+      {0, 56, 55},
+      {0, 58, 57},
+      {0, 60, 59},
+      {0, 61, 23},
+      {0, 63, 62},
+      {0, 65, 64},
+      {0, 27, 66},
+      {0, 67, 38},
+      {0, 68, 17},
+      {0, 70, 69},
+      {0, 72, 71},
+      {0, 74, 73},
+      {0, 76, 75},
+      {0, 5, 77},
+      {0, 78, 1},
+      {0, 80, 79},
+      {0, 82, 81},
+      {0, 83, 6},
+      {0, 85, 84},
+      {0, 44, 86},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpImageSampleImplicitLod, 1), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(15, {
+      {0, 0, 0},
+      {883854656, 0, 0},
+      {1962971231, 0, 0},
+      {2036361232, 0, 0},
+      {2356768706, 0, 0},
+      {2637132451, 0, 0},
+      {3237903670, 0, 0},
+      {3829682756, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 8, 2},
+      {0, 6, 9},
+      {0, 10, 7},
+      {0, 4, 5},
+      {0, 12, 11},
+      {0, 3, 13},
+      {0, 14, 1},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpImageSampleImplicitLod, 2), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(87, {
+      {0, 0, 0},
+      {150685616, 0, 0},
+      {255302575, 0, 0},
+      {414620710, 0, 0},
+      {557400685, 0, 0},
+      {575205902, 0, 0},
+      {618761615, 0, 0},
+      {646282397, 0, 0},
+      {686024761, 0, 0},
+      {740921498, 0, 0},
+      {921246433, 0, 0},
+      {1057578789, 0, 0},
+      {1162127370, 0, 0},
+      {1329499601, 0, 0},
+      {1352628475, 0, 0},
+      {1502028603, 0, 0},
+      {1519723107, 0, 0},
+      {1543798545, 0, 0},
+      {1545450160, 0, 0},
+      {1570165302, 0, 0},
+      {1600392975, 0, 0},
+      {1641415225, 0, 0},
+      {2204920111, 0, 0},
+      {2257971049, 0, 0},
+      {2276405827, 0, 0},
+      {2339018837, 0, 0},
+      {2340670452, 0, 0},
+      {2517964682, 0, 0},
+      {2532518896, 0, 0},
+      {2674090849, 0, 0},
+      {2754074729, 0, 0},
+      {2804281092, 0, 0},
+      {2816338013, 0, 0},
+      {2841008029, 0, 0},
+      {3234673086, 0, 0},
+      {3249261197, 0, 0},
+      {3619787319, 0, 0},
+      {3627739127, 0, 0},
+      {3669223677, 0, 0},
+      {3787567939, 0, 0},
+      {3898287302, 0, 0},
+      {4142016703, 0, 0},
+      {4237092412, 0, 0},
+      {4285779501, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 16, 15},
+      {0, 2, 33},
+      {0, 41, 35},
+      {0, 32, 30},
+      {0, 39, 38},
+      {0, 5, 1},
+      {0, 9, 43},
+      {0, 40, 22},
+      {0, 29, 12},
+      {0, 4, 3},
+      {0, 25, 37},
+      {0, 34, 26},
+      {0, 45, 19},
+      {0, 31, 24},
+      {0, 47, 46},
+      {0, 48, 20},
+      {0, 49, 6},
+      {0, 8, 21},
+      {0, 50, 11},
+      {0, 13, 10},
+      {0, 51, 42},
+      {0, 52, 23},
+      {0, 54, 53},
+      {0, 56, 55},
+      {0, 58, 57},
+      {0, 60, 59},
+      {0, 61, 28},
+      {0, 63, 62},
+      {0, 65, 64},
+      {0, 17, 66},
+      {0, 67, 18},
+      {0, 68, 7},
+      {0, 70, 69},
+      {0, 72, 71},
+      {0, 74, 73},
+      {0, 76, 75},
+      {0, 14, 77},
+      {0, 78, 27},
+      {0, 80, 79},
+      {0, 82, 81},
+      {0, 83, 36},
+      {0, 85, 84},
+      {0, 44, 86},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpImageSampleImplicitLod, 3), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
+      {0, 0, 0},
+      {2855506940, 0, 0},
+      {3266548732, 0, 0},
+      {3732640764, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 2, 1},
+      {0, 5, 4},
+      {0, 3, 6},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpImageSampleImplicitLod, 5), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
+      {0, 0, 0},
+      {3334207724, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 2},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpImageSampleExplicitLod, 0), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(139, {
+      {0, 0, 0},
+      {27177503, 0, 0},
+      {30663912, 0, 0},
+      {151672195, 0, 0},
+      {162608772, 0, 0},
+      {180913835, 0, 0},
+      {371621315, 0, 0},
+      {414444763, 0, 0},
+      {421602934, 0, 0},
+      {443347828, 0, 0},
+      {458937500, 0, 0},
+      {587888644, 0, 0},
+      {601656217, 0, 0},
+      {665789406, 0, 0},
+      {712168842, 0, 0},
+      {730943059, 0, 0},
+      {750870327, 0, 0},
+      {875212982, 0, 0},
+      {899320334, 0, 0},
+      {973908139, 0, 0},
+      {989813600, 0, 0},
+      {1057606514, 0, 0},
+      {1171541710, 0, 0},
+      {1243764146, 0, 0},
+      {1310404265, 0, 0},
+      {1366337101, 0, 0},
+      {1443547269, 0, 0},
+      {1472185378, 0, 0},
+      {1473799048, 0, 0},
+      {1543935193, 0, 0},
+      {1572834111, 0, 0},
+      {1623013158, 0, 0},
+      {1686512349, 0, 0},
+      {1705716306, 0, 0},
+      {1747355813, 0, 0},
+      {1755165354, 0, 0},
+      {1781864804, 0, 0},
+      {1916983087, 0, 0},
+      {1941403425, 0, 0},
+      {2023008475, 0, 0},
+      {2043684541, 0, 0},
+      {2274226560, 0, 0},
+      {2285438321, 0, 0},
+      {2315690100, 0, 0},
+      {2344328209, 0, 0},
+      {2414725163, 0, 0},
+      {2493146691, 0, 0},
+      {2495155989, 0, 0},
+      {2558655180, 0, 0},
+      {2577859137, 0, 0},
+      {2857814560, 0, 0},
+      {2895151306, 0, 0},
+      {2986830770, 0, 0},
+      {3006548167, 0, 0},
+      {3127329373, 0, 0},
+      {3157581152, 0, 0},
+      {3216471040, 0, 0},
+      {3296722158, 0, 0},
+      {3367298820, 0, 0},
+      {3376009661, 0, 0},
+      {3450001968, 0, 0},
+      {3526837441, 0, 0},
+      {3609540589, 0, 0},
+      {3743398113, 0, 0},
+      {3858973601, 0, 0},
+      {3953984401, 0, 0},
+      {3999472204, 0, 0},
+      {4088613871, 0, 0},
+      {4184019303, 0, 0},
+      {4258229445, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 31, 16},
+      {0, 58, 47},
+      {0, 21, 61},
+      {0, 6, 14},
+      {0, 65, 23},
+      {0, 35, 5},
+      {0, 2, 7},
+      {0, 10, 25},
+      {0, 40, 22},
+      {0, 9, 50},
+      {0, 20, 11},
+      {0, 38, 36},
+      {0, 13, 12},
+      {0, 67, 28},
+      {0, 71, 68},
+      {0, 73, 72},
+      {0, 3, 29},
+      {0, 27, 8},
+      {0, 44, 37},
+      {0, 74, 63},
+      {0, 76, 75},
+      {0, 18, 1},
+      {0, 78, 77},
+      {0, 80, 79},
+      {0, 82, 81},
+      {0, 26, 15},
+      {0, 83, 43},
+      {0, 85, 84},
+      {0, 19, 86},
+      {0, 48, 32},
+      {0, 33, 46},
+      {0, 87, 49},
+      {0, 89, 88},
+      {0, 91, 90},
+      {0, 41, 30},
+      {0, 52, 42},
+      {0, 64, 55},
+      {0, 92, 53},
+      {0, 94, 93},
+      {0, 51, 39},
+      {0, 45, 95},
+      {0, 66, 54},
+      {0, 97, 96},
+      {0, 57, 98},
+      {0, 99, 69},
+      {0, 101, 100},
+      {0, 56, 102},
+      {0, 4, 59},
+      {0, 34, 17},
+      {0, 103, 24},
+      {0, 105, 104},
+      {0, 107, 106},
+      {0, 109, 108},
+      {0, 60, 110},
+      {0, 111, 62},
+      {0, 113, 112},
+      {0, 115, 114},
+      {0, 117, 116},
+      {0, 119, 118},
+      {0, 121, 120},
+      {0, 123, 122},
+      {0, 125, 124},
+      {0, 127, 126},
+      {0, 129, 128},
+      {0, 70, 130},
+      {0, 132, 131},
+      {0, 134, 133},
+      {0, 136, 135},
+      {0, 138, 137},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpImageSampleExplicitLod, 1), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(11, {
+      {0, 0, 0},
+      {883854656, 0, 0},
+      {1962971231, 0, 0},
+      {2036361232, 0, 0},
+      {2366506734, 0, 0},
+      {3829682756, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 4, 2},
+      {0, 6, 7},
+      {0, 8, 5},
+      {0, 3, 9},
+      {0, 1, 10},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpImageSampleExplicitLod, 2), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(73, {
+      {0, 0, 0},
+      {178571546, 0, 0},
+      {223310468, 0, 0},
+      {388034151, 0, 0},
+      {449954059, 0, 0},
+      {694743357, 0, 0},
+      {797415788, 0, 0},
+      {835638766, 0, 0},
+      {1002144380, 0, 0},
+      {1221183390, 0, 0},
+      {1570165302, 0, 0},
+      {1663234329, 0, 0},
+      {1750829822, 0, 0},
+      {1894133125, 0, 0},
+      {1967643923, 0, 0},
+      {1980341560, 0, 0},
+      {2278706468, 0, 0},
+      {2326990117, 0, 0},
+      {2464905186, 0, 0},
+      {2511346984, 0, 0},
+      {2517964682, 0, 0},
+      {2616085763, 0, 0},
+      {2710583246, 0, 0},
+      {2745872368, 0, 0},
+      {2924263085, 0, 0},
+      {3027500544, 0, 0},
+      {3044723416, 0, 0},
+      {3202324433, 0, 0},
+      {3289213933, 0, 0},
+      {3323682385, 0, 0},
+      {3366848728, 0, 0},
+      {3417583519, 0, 0},
+      {3732916270, 0, 0},
+      {3787909072, 0, 0},
+      {3877813395, 0, 0},
+      {4028028350, 0, 0},
+      {4178218543, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 36, 31},
+      {0, 15, 3},
+      {0, 17, 1},
+      {0, 24, 12},
+      {0, 35, 34},
+      {0, 28, 27},
+      {0, 21, 38},
+      {0, 6, 13},
+      {0, 14, 7},
+      {0, 39, 25},
+      {0, 40, 30},
+      {0, 42, 41},
+      {0, 32, 43},
+      {0, 23, 9},
+      {0, 11, 44},
+      {0, 45, 22},
+      {0, 47, 46},
+      {0, 2, 16},
+      {0, 49, 48},
+      {0, 4, 50},
+      {0, 51, 18},
+      {0, 53, 52},
+      {0, 33, 54},
+      {0, 26, 55},
+      {0, 57, 56},
+      {0, 5, 58},
+      {0, 59, 8},
+      {0, 19, 60},
+      {0, 10, 61},
+      {0, 29, 62},
+      {0, 37, 63},
+      {0, 65, 64},
+      {0, 67, 66},
+      {0, 20, 68},
+      {0, 70, 69},
+      {0, 72, 71},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpImageSampleExplicitLod, 3), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
+      {0, 0, 0},
+      {2855506940, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 2},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpImageSampleExplicitLod, 5), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(13, {
+      {0, 0, 0},
+      {3533637837, 0, 0},
+      {3535289452, 0, 0},
+      {3536941067, 0, 0},
+      {3538592682, 0, 0},
+      {3540244297, 0, 0},
+      {3541895912, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 7},
+      {0, 2, 8},
+      {0, 9, 3},
+      {0, 4, 10},
+      {0, 5, 11},
+      {0, 12, 6},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpImageSampleExplicitLod, 6), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1951208733, 0, 0},
+      {2320303498, 0, 0},
+      {3334207724, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 4, 5},
+      {0, 2, 6},
+      {0, 1, 3},
+      {0, 8, 7},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFAdd, 0), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(45, {
+      {0, 0, 0},
+      {328661377, 0, 0},
+      {464259778, 0, 0},
+      {920941800, 0, 0},
+      {969500141, 0, 0},
+      {1449907751, 0, 0},
+      {1451831482, 0, 0},
+      {1543798545, 0, 0},
+      {1545450160, 0, 0},
+      {1626224034, 0, 0},
+      {1669930486, 0, 0},
+      {1770165905, 0, 0},
+      {2278571792, 0, 0},
+      {2432827426, 0, 0},
+      {2656211099, 0, 0},
+      {2736844435, 0, 0},
+      {2870852215, 0, 0},
+      {2919626325, 0, 0},
+      {2923708820, 0, 0},
+      {3325419312, 0, 0},
+      {3678875745, 0, 0},
+      {4182141402, 0, 0},
+      {4241374559, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 2, 6},
+      {0, 9, 13},
+      {0, 5, 15},
+      {0, 4, 11},
+      {0, 20, 22},
+      {0, 10, 1},
+      {0, 18, 14},
+      {0, 16, 3},
+      {0, 12, 21},
+      {0, 8, 7},
+      {0, 24, 17},
+      {0, 19, 25},
+      {0, 27, 26},
+      {0, 29, 28},
+      {0, 31, 30},
+      {0, 33, 32},
+      {0, 35, 34},
+      {0, 37, 36},
+      {0, 39, 38},
+      {0, 41, 40},
+      {0, 43, 42},
+      {0, 23, 44},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFAdd, 1), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(89, {
+      {0, 0, 0},
+      {135920445, 0, 0},
+      {176166202, 0, 0},
+      {294390719, 0, 0},
+      {296981500, 0, 0},
+      {743407979, 0, 0},
+      {810488476, 0, 0},
+      {837715723, 0, 0},
+      {885020215, 0, 0},
+      {922996215, 0, 0},
+      {959681532, 0, 0},
+      {963902061, 0, 0},
+      {1136775085, 0, 0},
+      {1189681639, 0, 0},
+      {1203545131, 0, 0},
+      {1297294717, 0, 0},
+      {1317058015, 0, 0},
+      {1352397672, 0, 0},
+      {1367301635, 0, 0},
+      {1412908157, 0, 0},
+      {1570165302, 0, 0},
+      {1763758554, 0, 0},
+      {1791427568, 0, 0},
+      {1992893964, 0, 0},
+      {2013867381, 0, 0},
+      {2096388952, 0, 0},
+      {2219733501, 0, 0},
+      {2383939514, 0, 0},
+      {2517964682, 0, 0},
+      {2555315060, 0, 0},
+      {2572638469, 0, 0},
+      {2762094724, 0, 0},
+      {2770161927, 0, 0},
+      {2855506940, 0, 0},
+      {3044188332, 0, 0},
+      {3187066832, 0, 0},
+      {3319278167, 0, 0},
+      {3653838348, 0, 0},
+      {3675926744, 0, 0},
+      {3701632935, 0, 0},
+      {3712946115, 0, 0},
+      {3732709413, 0, 0},
+      {3743748793, 0, 0},
+      {3783543823, 0, 0},
+      {3930727258, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 15, 12},
+      {0, 38, 16},
+      {0, 41, 40},
+      {0, 1, 33},
+      {0, 21, 34},
+      {0, 9, 2},
+      {0, 24, 7},
+      {0, 39, 44},
+      {0, 29, 22},
+      {0, 17, 19},
+      {0, 36, 32},
+      {0, 26, 18},
+      {0, 30, 3},
+      {0, 11, 8},
+      {0, 42, 35},
+      {0, 46, 31},
+      {0, 27, 5},
+      {0, 48, 47},
+      {0, 28, 49},
+      {0, 51, 50},
+      {0, 52, 23},
+      {0, 54, 53},
+      {0, 13, 14},
+      {0, 6, 55},
+      {0, 57, 56},
+      {0, 59, 58},
+      {0, 60, 43},
+      {0, 62, 61},
+      {0, 37, 63},
+      {0, 65, 64},
+      {0, 67, 66},
+      {0, 69, 68},
+      {0, 70, 4},
+      {0, 10, 71},
+      {0, 72, 20},
+      {0, 74, 73},
+      {0, 76, 75},
+      {0, 78, 77},
+      {0, 80, 79},
+      {0, 81, 25},
+      {0, 83, 82},
+      {0, 85, 84},
+      {0, 87, 86},
+      {0, 45, 88},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFAdd, 2), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(103, {
+      {0, 0, 0},
+      {126463145, 0, 0},
+      {220008971, 0, 0},
+      {246375791, 0, 0},
+      {503145996, 0, 0},
+      {628331516, 0, 0},
+      {643418617, 0, 0},
+      {743407979, 0, 0},
+      {837715723, 0, 0},
+      {858902117, 0, 0},
+      {870594305, 0, 0},
+      {939671928, 0, 0},
+      {959681532, 0, 0},
+      {1051471757, 0, 0},
+      {1092948665, 0, 0},
+      {1097775533, 0, 0},
+      {1136775085, 0, 0},
+      {1140367371, 0, 0},
+      {1332643570, 0, 0},
+      {1367301635, 0, 0},
+      {1558001705, 0, 0},
+      {1684282922, 0, 0},
+      {2096388952, 0, 0},
+      {2183547611, 0, 0},
+      {2219733501, 0, 0},
+      {2358141757, 0, 0},
+      {2359973133, 0, 0},
+      {2383939514, 0, 0},
+      {2444465148, 0, 0},
+      {2517964682, 0, 0},
+      {2567901801, 0, 0},
+      {2598189097, 0, 0},
+      {2655147757, 0, 0},
+      {2683080096, 0, 0},
+      {2705434194, 0, 0},
+      {2738307068, 0, 0},
+      {2780898906, 0, 0},
+      {3030911670, 0, 0},
+      {3032677281, 0, 0},
+      {3063300848, 0, 0},
+      {3277199633, 0, 0},
+      {3289969989, 0, 0},
+      {3401762422, 0, 0},
+      {3436143898, 0, 0},
+      {3560552546, 0, 0},
+      {3656163446, 0, 0},
+      {3675926744, 0, 0},
+      {3701632935, 0, 0},
+      {3743748793, 0, 0},
+      {3752211294, 0, 0},
+      {3794803132, 0, 0},
+      {4241374559, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 21},
+      {0, 17, 11},
+      {0, 36, 35},
+      {0, 46, 45},
+      {0, 50, 49},
+      {0, 9, 3},
+      {0, 20, 47},
+      {0, 37, 31},
+      {0, 2, 34},
+      {0, 40, 13},
+      {0, 51, 32},
+      {0, 41, 10},
+      {0, 38, 19},
+      {0, 18, 44},
+      {0, 43, 16},
+      {0, 48, 24},
+      {0, 26, 5},
+      {0, 53, 8},
+      {0, 15, 7},
+      {0, 25, 23},
+      {0, 54, 27},
+      {0, 56, 55},
+      {0, 58, 57},
+      {0, 60, 59},
+      {0, 39, 42},
+      {0, 62, 61},
+      {0, 30, 63},
+      {0, 4, 64},
+      {0, 65, 28},
+      {0, 66, 22},
+      {0, 68, 67},
+      {0, 69, 14},
+      {0, 70, 33},
+      {0, 71, 6},
+      {0, 73, 72},
+      {0, 75, 74},
+      {0, 29, 76},
+      {0, 78, 77},
+      {0, 80, 79},
+      {0, 82, 81},
+      {0, 84, 83},
+      {0, 86, 85},
+      {0, 88, 87},
+      {0, 90, 89},
+      {0, 91, 12},
+      {0, 93, 92},
+      {0, 95, 94},
+      {0, 97, 96},
+      {0, 99, 98},
+      {0, 101, 100},
+      {0, 52, 102},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFAdd, 3), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1951208733, 0, 0},
+      {2320303498, 0, 0},
+      {3334207724, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 3, 5},
+      {0, 4, 6},
+      {0, 1, 7},
+      {0, 2, 8},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFSub, 0), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(159, {
+      {0, 0, 0},
+      {50385656, 0, 0},
+      {117250846, 0, 0},
+      {171494987, 0, 0},
+      {195244192, 0, 0},
+      {210754155, 0, 0},
+      {265392489, 0, 0},
+      {333855951, 0, 0},
+      {416853049, 0, 0},
+      {529068443, 0, 0},
+      {533021259, 0, 0},
+      {615982737, 0, 0},
+      {660038281, 0, 0},
+      {663341511, 0, 0},
+      {669812542, 0, 0},
+      {716890919, 0, 0},
+      {1081536219, 0, 0},
+      {1119744229, 0, 0},
+      {1123617794, 0, 0},
+      {1139547465, 0, 0},
+      {1162789888, 0, 0},
+      {1178317551, 0, 0},
+      {1190147516, 0, 0},
+      {1193734351, 0, 0},
+      {1215030156, 0, 0},
+      {1220749418, 0, 0},
+      {1318479490, 0, 0},
+      {1461398554, 0, 0},
+      {1486207619, 0, 0},
+      {1551372768, 0, 0},
+      {1763758554, 0, 0},
+      {1797960910, 0, 0},
+      {1850331254, 0, 0},
+      {1894417995, 0, 0},
+      {1964254745, 0, 0},
+      {1965902997, 0, 0},
+      {1989327599, 0, 0},
+      {2095027856, 0, 0},
+      {2123683379, 0, 0},
+      {2124837447, 0, 0},
+      {2137526937, 0, 0},
+      {2269114589, 0, 0},
+      {2269130237, 0, 0},
+      {2330636993, 0, 0},
+      {2481746922, 0, 0},
+      {2503770904, 0, 0},
+      {2589449658, 0, 0},
+      {2603020391, 0, 0},
+      {2604576561, 0, 0},
+      {2795773560, 0, 0},
+      {2835131395, 0, 0},
+      {2852854788, 0, 0},
+      {2890638791, 0, 0},
+      {2895413148, 0, 0},
+      {2950446516, 0, 0},
+      {2963744582, 0, 0},
+      {3079287749, 0, 0},
+      {3088785099, 0, 0},
+      {3280064277, 0, 0},
+      {3335250889, 0, 0},
+      {3510242586, 0, 0},
+      {3517169445, 0, 0},
+      {3518703473, 0, 0},
+      {3536471583, 0, 0},
+      {3579593979, 0, 0},
+      {3591222197, 0, 0},
+      {3673811979, 0, 0},
+      {3727034815, 0, 0},
+      {3730093054, 0, 0},
+      {3898287302, 0, 0},
+      {3944781937, 0, 0},
+      {3950980241, 0, 0},
+      {4033586023, 0, 0},
+      {4041974454, 0, 0},
+      {4052965752, 0, 0},
+      {4083161638, 0, 0},
+      {4167600590, 0, 0},
+      {4185661467, 0, 0},
+      {4237092412, 0, 0},
+      {4244540017, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 44, 18},
+      {0, 69, 57},
+      {0, 24, 16},
+      {0, 79, 5},
+      {0, 59, 4},
+      {0, 76, 40},
+      {0, 53, 45},
+      {0, 14, 2},
+      {0, 62, 61},
+      {0, 33, 75},
+      {0, 38, 37},
+      {0, 42, 58},
+      {0, 66, 47},
+      {0, 63, 67},
+      {0, 1, 7},
+      {0, 10, 3},
+      {0, 13, 12},
+      {0, 23, 22},
+      {0, 32, 28},
+      {0, 36, 35},
+      {0, 72, 49},
+      {0, 74, 73},
+      {0, 77, 55},
+      {0, 27, 41},
+      {0, 31, 15},
+      {0, 6, 54},
+      {0, 78, 17},
+      {0, 81, 56},
+      {0, 83, 82},
+      {0, 85, 84},
+      {0, 48, 30},
+      {0, 71, 60},
+      {0, 65, 51},
+      {0, 87, 86},
+      {0, 50, 34},
+      {0, 89, 88},
+      {0, 90, 9},
+      {0, 25, 8},
+      {0, 92, 91},
+      {0, 93, 26},
+      {0, 95, 94},
+      {0, 52, 39},
+      {0, 29, 20},
+      {0, 97, 96},
+      {0, 99, 98},
+      {0, 101, 100},
+      {0, 64, 102},
+      {0, 104, 103},
+      {0, 106, 105},
+      {0, 21, 107},
+      {0, 108, 68},
+      {0, 109, 46},
+      {0, 110, 11},
+      {0, 112, 111},
+      {0, 114, 113},
+      {0, 116, 115},
+      {0, 117, 70},
+      {0, 43, 118},
+      {0, 120, 119},
+      {0, 122, 121},
+      {0, 124, 123},
+      {0, 126, 125},
+      {0, 128, 127},
+      {0, 129, 19},
+      {0, 131, 130},
+      {0, 133, 132},
+      {0, 135, 134},
+      {0, 137, 136},
+      {0, 139, 138},
+      {0, 141, 140},
+      {0, 143, 142},
+      {0, 145, 144},
+      {0, 147, 146},
+      {0, 149, 148},
+      {0, 151, 150},
+      {0, 153, 152},
+      {0, 155, 154},
+      {0, 157, 156},
+      {0, 158, 80},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFSub, 1), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(103, {
+      {0, 0, 0},
+      {50998433, 0, 0},
+      {171494987, 0, 0},
+      {249378857, 0, 0},
+      {296981500, 0, 0},
+      {508007510, 0, 0},
+      {610429940, 0, 0},
+      {660038281, 0, 0},
+      {663341511, 0, 0},
+      {836581417, 0, 0},
+      {1027242654, 0, 0},
+      {1167160774, 0, 0},
+      {1191015885, 0, 0},
+      {1200870684, 0, 0},
+      {1203545131, 0, 0},
+      {1265796414, 0, 0},
+      {1319785741, 0, 0},
+      {1669959736, 0, 0},
+      {1684282922, 0, 0},
+      {1752686878, 0, 0},
+      {1850331254, 0, 0},
+      {1901166356, 0, 0},
+      {1906988301, 0, 0},
+      {2055836767, 0, 0},
+      {2095027856, 0, 0},
+      {2096388952, 0, 0},
+      {2144962711, 0, 0},
+      {2217833278, 0, 0},
+      {2500819054, 0, 0},
+      {2525173102, 0, 0},
+      {2575525651, 0, 0},
+      {2660843182, 0, 0},
+      {2855506940, 0, 0},
+      {2918750759, 0, 0},
+      {2919787747, 0, 0},
+      {3091876332, 0, 0},
+      {3187066832, 0, 0},
+      {3244209297, 0, 0},
+      {3423702268, 0, 0},
+      {3508792859, 0, 0},
+      {3548535223, 0, 0},
+      {3619787319, 0, 0},
+      {3653838348, 0, 0},
+      {3692647551, 0, 0},
+      {3713290482, 0, 0},
+      {3753486980, 0, 0},
+      {3783756895, 0, 0},
+      {3797961332, 0, 0},
+      {3836822275, 0, 0},
+      {4043078107, 0, 0},
+      {4052965752, 0, 0},
+      {4091394002, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 31, 49},
+      {0, 24, 19},
+      {0, 46, 45},
+      {0, 6, 48},
+      {0, 12, 33},
+      {0, 17, 21},
+      {0, 43, 11},
+      {0, 7, 2},
+      {0, 9, 8},
+      {0, 28, 13},
+      {0, 44, 38},
+      {0, 30, 50},
+      {0, 26, 22},
+      {0, 29, 51},
+      {0, 34, 37},
+      {0, 53, 40},
+      {0, 23, 54},
+      {0, 55, 25},
+      {0, 27, 18},
+      {0, 1, 10},
+      {0, 57, 56},
+      {0, 59, 58},
+      {0, 5, 47},
+      {0, 60, 20},
+      {0, 62, 61},
+      {0, 64, 63},
+      {0, 66, 65},
+      {0, 67, 39},
+      {0, 69, 68},
+      {0, 16, 70},
+      {0, 3, 71},
+      {0, 73, 72},
+      {0, 41, 15},
+      {0, 35, 74},
+      {0, 76, 75},
+      {0, 78, 77},
+      {0, 36, 79},
+      {0, 81, 80},
+      {0, 83, 82},
+      {0, 14, 84},
+      {0, 86, 85},
+      {0, 88, 87},
+      {0, 32, 89},
+      {0, 42, 90},
+      {0, 92, 91},
+      {0, 94, 93},
+      {0, 96, 95},
+      {0, 98, 97},
+      {0, 52, 99},
+      {0, 100, 4},
+      {0, 102, 101},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFSub, 2), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(157, {
+      {0, 0, 0},
+      {49456560, 0, 0},
+      {170690025, 0, 0},
+      {243178923, 0, 0},
+      {295017943, 0, 0},
+      {296981500, 0, 0},
+      {330249537, 0, 0},
+      {435256475, 0, 0},
+      {443558693, 0, 0},
+      {456043370, 0, 0},
+      {470277359, 0, 0},
+      {592180731, 0, 0},
+      {663258455, 0, 0},
+      {706238670, 0, 0},
+      {810488476, 0, 0},
+      {870594305, 0, 0},
+      {877895868, 0, 0},
+      {900522183, 0, 0},
+      {1077859090, 0, 0},
+      {1082941229, 0, 0},
+      {1104362365, 0, 0},
+      {1132589448, 0, 0},
+      {1173092699, 0, 0},
+      {1203545131, 0, 0},
+      {1265796414, 0, 0},
+      {1278818058, 0, 0},
+      {1285705317, 0, 0},
+      {1319785741, 0, 0},
+      {1382106590, 0, 0},
+      {1461897718, 0, 0},
+      {1474506522, 0, 0},
+      {1530183840, 0, 0},
+      {1558001705, 0, 0},
+      {1558990974, 0, 0},
+      {1616846013, 0, 0},
+      {1633850097, 0, 0},
+      {1684282922, 0, 0},
+      {1725011064, 0, 0},
+      {1767704813, 0, 0},
+      {1923453688, 0, 0},
+      {1941148668, 0, 0},
+      {1955104493, 0, 0},
+      {2022961611, 0, 0},
+      {2162274327, 0, 0},
+      {2212501241, 0, 0},
+      {2219733501, 0, 0},
+      {2234361374, 0, 0},
+      {2272221101, 0, 0},
+      {2305269460, 0, 0},
+      {2488410748, 0, 0},
+      {2566666743, 0, 0},
+      {2598189097, 0, 0},
+      {2775815164, 0, 0},
+      {2793529873, 0, 0},
+      {2844616706, 0, 0},
+      {2970183398, 0, 0},
+      {3103302036, 0, 0},
+      {3110479131, 0, 0},
+      {3115038057, 0, 0},
+      {3116932970, 0, 0},
+      {3152745753, 0, 0},
+      {3187066832, 0, 0},
+      {3244209297, 0, 0},
+      {3383007207, 0, 0},
+      {3392887901, 0, 0},
+      {3508792859, 0, 0},
+      {3737376990, 0, 0},
+      {3753486980, 0, 0},
+      {3765247327, 0, 0},
+      {3817149113, 0, 0},
+      {3839047923, 0, 0},
+      {3886529747, 0, 0},
+      {4044928561, 0, 0},
+      {4061558677, 0, 0},
+      {4069720347, 0, 0},
+      {4069810315, 0, 0},
+      {4128942283, 0, 0},
+      {4164704452, 0, 0},
+      {4273793488, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 74, 47},
+      {0, 34, 33},
+      {0, 36, 14},
+      {0, 61, 48},
+      {0, 13, 31},
+      {0, 39, 25},
+      {0, 37, 29},
+      {0, 65, 54},
+      {0, 4, 73},
+      {0, 38, 10},
+      {0, 15, 43},
+      {0, 6, 35},
+      {0, 9, 16},
+      {0, 30, 19},
+      {0, 49, 44},
+      {0, 57, 53},
+      {0, 60, 58},
+      {0, 72, 66},
+      {0, 59, 76},
+      {0, 1, 68},
+      {0, 70, 42},
+      {0, 63, 3},
+      {0, 28, 69},
+      {0, 17, 55},
+      {0, 45, 64},
+      {0, 81, 80},
+      {0, 7, 82},
+      {0, 12, 11},
+      {0, 21, 50},
+      {0, 83, 18},
+      {0, 22, 84},
+      {0, 85, 26},
+      {0, 20, 86},
+      {0, 87, 40},
+      {0, 56, 88},
+      {0, 90, 89},
+      {0, 92, 91},
+      {0, 93, 2},
+      {0, 95, 94},
+      {0, 97, 96},
+      {0, 98, 41},
+      {0, 100, 99},
+      {0, 101, 52},
+      {0, 103, 102},
+      {0, 77, 71},
+      {0, 104, 78},
+      {0, 105, 46},
+      {0, 32, 8},
+      {0, 106, 51},
+      {0, 108, 107},
+      {0, 23, 109},
+      {0, 110, 27},
+      {0, 112, 111},
+      {0, 113, 75},
+      {0, 115, 114},
+      {0, 117, 116},
+      {0, 119, 118},
+      {0, 121, 120},
+      {0, 123, 122},
+      {0, 124, 62},
+      {0, 126, 125},
+      {0, 128, 127},
+      {0, 67, 129},
+      {0, 131, 130},
+      {0, 5, 132},
+      {0, 134, 133},
+      {0, 136, 135},
+      {0, 138, 137},
+      {0, 139, 24},
+      {0, 141, 140},
+      {0, 143, 142},
+      {0, 145, 144},
+      {0, 147, 146},
+      {0, 149, 148},
+      {0, 151, 150},
+      {0, 153, 152},
+      {0, 79, 154},
+      {0, 156, 155},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFSub, 3), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1951208733, 0, 0},
+      {2320303498, 0, 0},
+      {3334207724, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 4, 5},
+      {0, 3, 6},
+      {0, 1, 7},
+      {0, 8, 2},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFMul, 0), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(41, {
+      {0, 0, 0},
+      {342197850, 0, 0},
+      {885020215, 0, 0},
+      {963902061, 0, 0},
+      {1041368449, 0, 0},
+      {1352397672, 0, 0},
+      {1791427568, 0, 0},
+      {2013867381, 0, 0},
+      {2513230733, 0, 0},
+      {2555315060, 0, 0},
+      {2562485583, 0, 0},
+      {2567901801, 0, 0},
+      {2655147757, 0, 0},
+      {2680283743, 0, 0},
+      {2752766693, 0, 0},
+      {2806716850, 0, 0},
+      {3030911670, 0, 0},
+      {3401762422, 0, 0},
+      {3697738938, 0, 0},
+      {4164704452, 0, 0},
+      {4273793488, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 14, 10},
+      {0, 7, 16},
+      {0, 1, 15},
+      {0, 9, 6},
+      {0, 4, 12},
+      {0, 18, 5},
+      {0, 13, 2},
+      {0, 19, 3},
+      {0, 17, 20},
+      {0, 23, 22},
+      {0, 24, 8},
+      {0, 26, 25},
+      {0, 27, 11},
+      {0, 29, 28},
+      {0, 31, 30},
+      {0, 33, 32},
+      {0, 35, 34},
+      {0, 37, 36},
+      {0, 39, 38},
+      {0, 21, 40},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFMul, 1), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(129, {
+      {0, 0, 0},
+      {126463145, 0, 0},
+      {129135650, 0, 0},
+      {200922300, 0, 0},
+      {328661377, 0, 0},
+      {354479447, 0, 0},
+      {360730278, 0, 0},
+      {451264926, 0, 0},
+      {529068443, 0, 0},
+      {593829839, 0, 0},
+      {742917749, 0, 0},
+      {761731755, 0, 0},
+      {810488476, 0, 0},
+      {870594305, 0, 0},
+      {894529125, 0, 0},
+      {959681532, 0, 0},
+      {1054461787, 0, 0},
+      {1077859090, 0, 0},
+      {1086964761, 0, 0},
+      {1158929937, 0, 0},
+      {1168927492, 0, 0},
+      {1196280518, 0, 0},
+      {1203545131, 0, 0},
+      {1367301635, 0, 0},
+      {1508550646, 0, 0},
+      {1618544981, 0, 0},
+      {1661163736, 0, 0},
+      {1684282922, 0, 0},
+      {1766994680, 0, 0},
+      {1830851200, 0, 0},
+      {1901166356, 0, 0},
+      {1955104493, 0, 0},
+      {2055836767, 0, 0},
+      {2096388952, 0, 0},
+      {2100052708, 0, 0},
+      {2161102232, 0, 0},
+      {2197904616, 0, 0},
+      {2262137600, 0, 0},
+      {2278571792, 0, 0},
+      {2281956980, 0, 0},
+      {2438466459, 0, 0},
+      {2443959748, 0, 0},
+      {2517964682, 0, 0},
+      {2557754096, 0, 0},
+      {2622612602, 0, 0},
+      {2660843182, 0, 0},
+      {2736844435, 0, 0},
+      {2780898906, 0, 0},
+      {3044188332, 0, 0},
+      {3059119137, 0, 0},
+      {3194725903, 0, 0},
+      {3270430997, 0, 0},
+      {3337532056, 0, 0},
+      {3407526215, 0, 0},
+      {3496407048, 0, 0},
+      {3504158761, 0, 0},
+      {3534518722, 0, 0},
+      {3570411982, 0, 0},
+      {3701632935, 0, 0},
+      {3929248764, 0, 0},
+      {3944781937, 0, 0},
+      {3970432934, 0, 0},
+      {4008405264, 0, 0},
+      {4245257809, 0, 0},
+      {4253051659, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 6, 26},
+      {0, 46, 24},
+      {0, 64, 50},
+      {0, 7, 17},
+      {0, 40, 57},
+      {0, 56, 49},
+      {0, 34, 10},
+      {0, 32, 61},
+      {0, 36, 44},
+      {0, 8, 43},
+      {0, 4, 18},
+      {0, 25, 23},
+      {0, 9, 54},
+      {0, 45, 41},
+      {0, 13, 21},
+      {0, 47, 31},
+      {0, 39, 53},
+      {0, 11, 3},
+      {0, 29, 20},
+      {0, 38, 58},
+      {0, 37, 14},
+      {0, 66, 52},
+      {0, 67, 35},
+      {0, 48, 68},
+      {0, 1, 69},
+      {0, 70, 28},
+      {0, 27, 63},
+      {0, 72, 71},
+      {0, 74, 73},
+      {0, 75, 60},
+      {0, 77, 76},
+      {0, 5, 51},
+      {0, 15, 78},
+      {0, 30, 79},
+      {0, 55, 80},
+      {0, 42, 81},
+      {0, 83, 82},
+      {0, 85, 84},
+      {0, 86, 2},
+      {0, 19, 16},
+      {0, 87, 59},
+      {0, 62, 88},
+      {0, 90, 89},
+      {0, 22, 91},
+      {0, 93, 92},
+      {0, 95, 94},
+      {0, 97, 96},
+      {0, 99, 98},
+      {0, 101, 100},
+      {0, 12, 102},
+      {0, 104, 103},
+      {0, 33, 105},
+      {0, 107, 106},
+      {0, 109, 108},
+      {0, 111, 110},
+      {0, 113, 112},
+      {0, 115, 114},
+      {0, 117, 116},
+      {0, 119, 118},
+      {0, 121, 120},
+      {0, 123, 122},
+      {0, 125, 124},
+      {0, 127, 126},
+      {0, 65, 128},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFMul, 2), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(127, {
+      {0, 0, 0},
+      {13319433, 0, 0},
+      {15502752, 0, 0},
+      {162608772, 0, 0},
+      {171307615, 0, 0},
+      {296981500, 0, 0},
+      {354479447, 0, 0},
+      {413918748, 0, 0},
+      {443490822, 0, 0},
+      {487719832, 0, 0},
+      {593829839, 0, 0},
+      {615982737, 0, 0},
+      {703543228, 0, 0},
+      {810488476, 0, 0},
+      {870594305, 0, 0},
+      {875212982, 0, 0},
+      {959681532, 0, 0},
+      {1019457583, 0, 0},
+      {1203545131, 0, 0},
+      {1278448636, 0, 0},
+      {1325348861, 0, 0},
+      {1368383673, 0, 0},
+      {1400019344, 0, 0},
+      {1646147798, 0, 0},
+      {1679946323, 0, 0},
+      {1684282922, 0, 0},
+      {1747355813, 0, 0},
+      {1755648697, 0, 0},
+      {1793544760, 0, 0},
+      {1811839150, 0, 0},
+      {1901166356, 0, 0},
+      {1947620272, 0, 0},
+      {1992893964, 0, 0},
+      {2042001863, 0, 0},
+      {2096388952, 0, 0},
+      {2123388694, 0, 0},
+      {2128251367, 0, 0},
+      {2130747644, 0, 0},
+      {2135340676, 0, 0},
+      {2161102232, 0, 0},
+      {2443959748, 0, 0},
+      {2513230733, 0, 0},
+      {2557754096, 0, 0},
+      {2580096524, 0, 0},
+      {2589449658, 0, 0},
+      {2614879967, 0, 0},
+      {2698156268, 0, 0},
+      {2970183398, 0, 0},
+      {3002890475, 0, 0},
+      {3133016299, 0, 0},
+      {3142155593, 0, 0},
+      {3187066832, 0, 0},
+      {3266548732, 0, 0},
+      {3287039847, 0, 0},
+      {3357301402, 0, 0},
+      {3413713311, 0, 0},
+      {3434076295, 0, 0},
+      {3496407048, 0, 0},
+      {3504158761, 0, 0},
+      {3882634684, 0, 0},
+      {3929248764, 0, 0},
+      {3987079331, 0, 0},
+      {4076840151, 0, 0},
+      {4243119782, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 31, 8},
+      {0, 14, 56},
+      {0, 7, 12},
+      {0, 9, 30},
+      {0, 42, 36},
+      {0, 19, 11},
+      {0, 22, 40},
+      {0, 15, 3},
+      {0, 57, 26},
+      {0, 58, 61},
+      {0, 55, 51},
+      {0, 48, 34},
+      {0, 20, 1},
+      {0, 24, 23},
+      {0, 46, 35},
+      {0, 59, 49},
+      {0, 21, 63},
+      {0, 62, 44},
+      {0, 6, 50},
+      {0, 28, 18},
+      {0, 66, 65},
+      {0, 41, 32},
+      {0, 39, 54},
+      {0, 53, 67},
+      {0, 68, 37},
+      {0, 33, 69},
+      {0, 43, 70},
+      {0, 71, 38},
+      {0, 72, 27},
+      {0, 13, 47},
+      {0, 45, 73},
+      {0, 75, 74},
+      {0, 76, 5},
+      {0, 77, 17},
+      {0, 79, 78},
+      {0, 52, 80},
+      {0, 2, 81},
+      {0, 83, 82},
+      {0, 85, 84},
+      {0, 87, 86},
+      {0, 4, 88},
+      {0, 16, 29},
+      {0, 90, 89},
+      {0, 92, 91},
+      {0, 94, 93},
+      {0, 60, 95},
+      {0, 97, 96},
+      {0, 98, 10},
+      {0, 25, 99},
+      {0, 101, 100},
+      {0, 103, 102},
+      {0, 105, 104},
+      {0, 107, 106},
+      {0, 109, 108},
+      {0, 111, 110},
+      {0, 113, 112},
+      {0, 115, 114},
+      {0, 117, 116},
+      {0, 119, 118},
+      {0, 121, 120},
+      {0, 123, 122},
+      {0, 125, 124},
+      {0, 64, 126},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFMul, 3), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {1951208733, 0, 0},
+      {2320303498, 0, 0},
+      {3334207724, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 4, 5},
+      {0, 3, 6},
+      {0, 7, 1},
+      {0, 2, 8},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFDiv, 0), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(153, {
+      {0, 0, 0},
+      {10142671, 0, 0},
+      {27865391, 0, 0},
+      {29517006, 0, 0},
+      {41739659, 0, 0},
+      {97231530, 0, 0},
+      {171334650, 0, 0},
+      {200553094, 0, 0},
+      {257136089, 0, 0},
+      {294390719, 0, 0},
+      {375530199, 0, 0},
+      {380957745, 0, 0},
+      {388034151, 0, 0},
+      {455591063, 0, 0},
+      {462664429, 0, 0},
+      {491456522, 0, 0},
+      {502863753, 0, 0},
+      {626480004, 0, 0},
+      {643418617, 0, 0},
+      {651464351, 0, 0},
+      {701281393, 0, 0},
+      {744817486, 0, 0},
+      {783918780, 0, 0},
+      {862784766, 0, 0},
+      {930804377, 0, 0},
+      {952536201, 0, 0},
+      {955476870, 0, 0},
+      {1043738701, 0, 0},
+      {1047011733, 0, 0},
+      {1080545747, 0, 0},
+      {1137442027, 0, 0},
+      {1235468610, 0, 0},
+      {1412908157, 0, 0},
+      {1431749301, 0, 0},
+      {1434223270, 0, 0},
+      {1440646342, 0, 0},
+      {1508570930, 0, 0},
+      {1510422521, 0, 0},
+      {1548121999, 0, 0},
+      {1582841441, 0, 0},
+      {1612225949, 0, 0},
+      {1665981878, 0, 0},
+      {1680746207, 0, 0},
+      {1696076631, 0, 0},
+      {1702168830, 0, 0},
+      {1761469971, 0, 0},
+      {1799299383, 0, 0},
+      {1910240213, 0, 0},
+      {1917451875, 0, 0},
+      {1945006185, 0, 0},
+      {1998444837, 0, 0},
+      {2045285083, 0, 0},
+      {2217966239, 0, 0},
+      {2279273489, 0, 0},
+      {2289803479, 0, 0},
+      {2348676810, 0, 0},
+      {2353194283, 0, 0},
+      {2403632109, 0, 0},
+      {2409539315, 0, 0},
+      {2414984922, 0, 0},
+      {2477389837, 0, 0},
+      {2524531022, 0, 0},
+      {2573160348, 0, 0},
+      {2639720559, 0, 0},
+      {2773229577, 0, 0},
+      {2796513469, 0, 0},
+      {2881225774, 0, 0},
+      {2890570341, 0, 0},
+      {2952850186, 0, 0},
+      {3023287679, 0, 0},
+      {3118548424, 0, 0},
+      {3877813395, 0, 0},
+      {3931288033, 0, 0},
+      {3972309363, 0, 0},
+      {4117704995, 0, 0},
+      {4140081844, 0, 0},
+      {4258414038, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 74, 53},
+      {0, 58, 52},
+      {0, 65, 60},
+      {0, 41, 5},
+      {0, 1, 67},
+      {0, 24, 28},
+      {0, 27, 26},
+      {0, 55, 31},
+      {0, 36, 61},
+      {0, 13, 49},
+      {0, 56, 48},
+      {0, 16, 64},
+      {0, 76, 42},
+      {0, 45, 29},
+      {0, 23, 6},
+      {0, 72, 12},
+      {0, 35, 19},
+      {0, 20, 7},
+      {0, 21, 46},
+      {0, 71, 78},
+      {0, 80, 79},
+      {0, 47, 17},
+      {0, 81, 70},
+      {0, 34, 25},
+      {0, 83, 82},
+      {0, 85, 84},
+      {0, 37, 86},
+      {0, 87, 73},
+      {0, 10, 4},
+      {0, 40, 30},
+      {0, 88, 57},
+      {0, 54, 89},
+      {0, 50, 90},
+      {0, 11, 91},
+      {0, 39, 15},
+      {0, 59, 44},
+      {0, 92, 66},
+      {0, 69, 93},
+      {0, 95, 94},
+      {0, 14, 96},
+      {0, 98, 97},
+      {0, 62, 51},
+      {0, 100, 99},
+      {0, 102, 101},
+      {0, 104, 103},
+      {0, 32, 43},
+      {0, 105, 38},
+      {0, 107, 106},
+      {0, 109, 108},
+      {0, 22, 9},
+      {0, 33, 110},
+      {0, 2, 111},
+      {0, 112, 3},
+      {0, 114, 113},
+      {0, 116, 115},
+      {0, 68, 63},
+      {0, 118, 117},
+      {0, 120, 119},
+      {0, 121, 8},
+      {0, 123, 122},
+      {0, 125, 124},
+      {0, 127, 126},
+      {0, 129, 128},
+      {0, 131, 130},
+      {0, 133, 132},
+      {0, 75, 18},
+      {0, 135, 134},
+      {0, 137, 136},
+      {0, 139, 138},
+      {0, 141, 140},
+      {0, 143, 142},
+      {0, 145, 144},
+      {0, 147, 146},
+      {0, 149, 148},
+      {0, 150, 77},
+      {0, 152, 151},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFDiv, 1), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(131, {
+      {0, 0, 0},
+      {5908395, 0, 0},
+      {139011596, 0, 0},
+      {296981500, 0, 0},
+      {342615870, 0, 0},
+      {370232173, 0, 0},
+      {492958971, 0, 0},
+      {528662843, 0, 0},
+      {551924251, 0, 0},
+      {604894932, 0, 0},
+      {610429940, 0, 0},
+      {780957373, 0, 0},
+      {810488476, 0, 0},
+      {872544165, 0, 0},
+      {878733439, 0, 0},
+      {918849409, 0, 0},
+      {959681532, 0, 0},
+      {1013756921, 0, 0},
+      {1038982109, 0, 0},
+      {1081611718, 0, 0},
+      {1125913837, 0, 0},
+      {1209418480, 0, 0},
+      {1318081294, 0, 0},
+      {1367301635, 0, 0},
+      {1417425499, 0, 0},
+      {1625742020, 0, 0},
+      {1684282922, 0, 0},
+      {1746004874, 0, 0},
+      {1758287856, 0, 0},
+      {1777640493, 0, 0},
+      {2066323109, 0, 0},
+      {2094550054, 0, 0},
+      {2096388952, 0, 0},
+      {2144962711, 0, 0},
+      {2434845539, 0, 0},
+      {2480811229, 0, 0},
+      {2552825357, 0, 0},
+      {2636946065, 0, 0},
+      {2651956495, 0, 0},
+      {2669086217, 0, 0},
+      {2680819379, 0, 0},
+      {2709694527, 0, 0},
+      {2715304020, 0, 0},
+      {2790648021, 0, 0},
+      {2802261839, 0, 0},
+      {2806296851, 0, 0},
+      {2864543087, 0, 0},
+      {2952260510, 0, 0},
+      {2963184673, 0, 0},
+      {3091876332, 0, 0},
+      {3098991995, 0, 0},
+      {3131890669, 0, 0},
+      {3138977758, 0, 0},
+      {3198541202, 0, 0},
+      {3260579369, 0, 0},
+      {3263841912, 0, 0},
+      {3335250889, 0, 0},
+      {3345856521, 0, 0},
+      {3381478137, 0, 0},
+      {3489269251, 0, 0},
+      {3510242586, 0, 0},
+      {3820814597, 0, 0},
+      {3900859293, 0, 0},
+      {4041974454, 0, 0},
+      {4244540017, 0, 0},
+      {4265894873, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 15, 52},
+      {0, 20, 18},
+      {0, 39, 29},
+      {0, 9, 43},
+      {0, 22, 13},
+      {0, 46, 27},
+      {0, 51, 48},
+      {0, 19, 57},
+      {0, 34, 24},
+      {0, 64, 59},
+      {0, 5, 7},
+      {0, 38, 37},
+      {0, 45, 47},
+      {0, 2, 56},
+      {0, 67, 8},
+      {0, 17, 68},
+      {0, 69, 61},
+      {0, 70, 6},
+      {0, 55, 54},
+      {0, 72, 71},
+      {0, 4, 73},
+      {0, 74, 40},
+      {0, 30, 11},
+      {0, 42, 36},
+      {0, 75, 58},
+      {0, 31, 76},
+      {0, 1, 77},
+      {0, 44, 14},
+      {0, 78, 50},
+      {0, 79, 23},
+      {0, 26, 80},
+      {0, 81, 12},
+      {0, 83, 82},
+      {0, 84, 21},
+      {0, 32, 85},
+      {0, 87, 86},
+      {0, 35, 10},
+      {0, 88, 62},
+      {0, 90, 89},
+      {0, 41, 91},
+      {0, 92, 53},
+      {0, 93, 63},
+      {0, 95, 94},
+      {0, 33, 96},
+      {0, 98, 97},
+      {0, 99, 3},
+      {0, 100, 28},
+      {0, 101, 49},
+      {0, 102, 60},
+      {0, 104, 103},
+      {0, 106, 105},
+      {0, 108, 107},
+      {0, 110, 109},
+      {0, 65, 111},
+      {0, 25, 112},
+      {0, 114, 113},
+      {0, 116, 115},
+      {0, 117, 16},
+      {0, 119, 118},
+      {0, 121, 120},
+      {0, 123, 122},
+      {0, 125, 124},
+      {0, 127, 126},
+      {0, 128, 66},
+      {0, 130, 129},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFDiv, 2), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(95, {
+      {0, 0, 0},
+      {116093251, 0, 0},
+      {149720480, 0, 0},
+      {183103444, 0, 0},
+      {251209228, 0, 0},
+      {296981500, 0, 0},
+      {357505993, 0, 0},
+      {394654115, 0, 0},
+      {410274915, 0, 0},
+      {452208841, 0, 0},
+      {788046331, 0, 0},
+      {797934924, 0, 0},
+      {810488476, 0, 0},
+      {1144188012, 0, 0},
+      {1220127364, 0, 0},
+      {1321616112, 0, 0},
+      {1324351672, 0, 0},
+      {1348149915, 0, 0},
+      {1459457331, 0, 0},
+      {1465623797, 0, 0},
+      {1531216990, 0, 0},
+      {1543672828, 0, 0},
+      {1578775276, 0, 0},
+      {1738815671, 0, 0},
+      {1904128160, 0, 0},
+      {2071351379, 0, 0},
+      {2119793999, 0, 0},
+      {2274779301, 0, 0},
+      {2291766425, 0, 0},
+      {2357410109, 0, 0},
+      {2438466459, 0, 0},
+      {2496463830, 0, 0},
+      {2630220147, 0, 0},
+      {2682510803, 0, 0},
+      {3047649911, 0, 0},
+      {3085703811, 0, 0},
+      {3235459678, 0, 0},
+      {3261703164, 0, 0},
+      {3331487616, 0, 0},
+      {3462674048, 0, 0},
+      {3570219049, 0, 0},
+      {3585315836, 0, 0},
+      {3602108619, 0, 0},
+      {3724004880, 0, 0},
+      {3931641900, 0, 0},
+      {3955205564, 0, 0},
+      {4073492988, 0, 0},
+      {4127308103, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 24, 37},
+      {0, 13, 38},
+      {0, 17, 39},
+      {0, 35, 23},
+      {0, 18, 36},
+      {0, 46, 19},
+      {0, 20, 33},
+      {0, 47, 6},
+      {0, 1, 45},
+      {0, 3, 27},
+      {0, 8, 49},
+      {0, 50, 29},
+      {0, 10, 51},
+      {0, 43, 31},
+      {0, 53, 52},
+      {0, 54, 26},
+      {0, 7, 55},
+      {0, 56, 32},
+      {0, 57, 41},
+      {0, 59, 58},
+      {0, 61, 60},
+      {0, 63, 62},
+      {0, 64, 25},
+      {0, 2, 34},
+      {0, 65, 14},
+      {0, 67, 66},
+      {0, 12, 21},
+      {0, 9, 68},
+      {0, 69, 16},
+      {0, 71, 70},
+      {0, 72, 44},
+      {0, 11, 73},
+      {0, 74, 30},
+      {0, 4, 75},
+      {0, 28, 15},
+      {0, 76, 42},
+      {0, 5, 77},
+      {0, 78, 40},
+      {0, 80, 79},
+      {0, 82, 81},
+      {0, 22, 83},
+      {0, 85, 84},
+      {0, 86, 48},
+      {0, 88, 87},
+      {0, 90, 89},
+      {0, 92, 91},
+      {0, 94, 93},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFDiv, 3), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
+      {0, 0, 0},
+      {679771963, 0, 0},
+      {2320303498, 0, 0},
+      {3334207724, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 3, 4},
+      {0, 2, 5},
+      {0, 1, 6},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpVectorTimesScalar, 0), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(121, {
+      {0, 0, 0},
+      {14113753, 0, 0},
+      {102358168, 0, 0},
+      {179458548, 0, 0},
+      {330388453, 0, 0},
+      {386525753, 0, 0},
+      {470277359, 0, 0},
+      {497658126, 0, 0},
+      {508007510, 0, 0},
+      {815034111, 0, 0},
+      {826214242, 0, 0},
+      {849867303, 0, 0},
+      {885645401, 0, 0},
+      {939415664, 0, 0},
+      {968885186, 0, 0},
+      {1105835505, 0, 0},
+      {1159301677, 0, 0},
+      {1461897718, 0, 0},
+      {1482251215, 0, 0},
+      {1486206763, 0, 0},
+      {1527762373, 0, 0},
+      {1558990974, 0, 0},
+      {1618754372, 0, 0},
+      {1669959736, 0, 0},
+      {1752686878, 0, 0},
+      {2004567202, 0, 0},
+      {2055637638, 0, 0},
+      {2113506324, 0, 0},
+      {2154320787, 0, 0},
+      {2162274327, 0, 0},
+      {2306141594, 0, 0},
+      {2345566651, 0, 0},
+      {2457690657, 0, 0},
+      {2473053808, 0, 0},
+      {2500422644, 0, 0},
+      {2504802016, 0, 0},
+      {2506771164, 0, 0},
+      {2793529873, 0, 0},
+      {2801333547, 0, 0},
+      {2879050471, 0, 0},
+      {3032677281, 0, 0},
+      {3045470312, 0, 0},
+      {3181546731, 0, 0},
+      {3240977890, 0, 0},
+      {3262572726, 0, 0},
+      {3307100165, 0, 0},
+      {3425841570, 0, 0},
+      {3560552546, 0, 0},
+      {3641833815, 0, 0},
+      {3652695478, 0, 0},
+      {3782362128, 0, 0},
+      {3797961332, 0, 0},
+      {3837583704, 0, 0},
+      {3886529747, 0, 0},
+      {3907920335, 0, 0},
+      {4043078107, 0, 0},
+      {4044928561, 0, 0},
+      {4069720347, 0, 0},
+      {4180570743, 0, 0},
+      {4245743275, 0, 0},
+      {4285201458, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 44, 28},
+      {0, 13, 45},
+      {0, 19, 15},
+      {0, 32, 31},
+      {0, 43, 42},
+      {0, 16, 52},
+      {0, 33, 22},
+      {0, 57, 55},
+      {0, 24, 21},
+      {0, 2, 59},
+      {0, 10, 3},
+      {0, 18, 12},
+      {0, 41, 39},
+      {0, 60, 46},
+      {0, 4, 25},
+      {0, 58, 49},
+      {0, 14, 1},
+      {0, 27, 17},
+      {0, 50, 36},
+      {0, 23, 54},
+      {0, 5, 30},
+      {0, 11, 7},
+      {0, 38, 29},
+      {0, 37, 8},
+      {0, 48, 56},
+      {0, 20, 6},
+      {0, 34, 26},
+      {0, 63, 62},
+      {0, 65, 64},
+      {0, 67, 66},
+      {0, 69, 68},
+      {0, 71, 70},
+      {0, 73, 72},
+      {0, 75, 74},
+      {0, 9, 76},
+      {0, 78, 77},
+      {0, 80, 79},
+      {0, 82, 81},
+      {0, 84, 83},
+      {0, 40, 35},
+      {0, 85, 47},
+      {0, 86, 51},
+      {0, 88, 87},
+      {0, 90, 89},
+      {0, 53, 91},
+      {0, 93, 92},
+      {0, 95, 94},
+      {0, 97, 96},
+      {0, 99, 98},
+      {0, 101, 100},
+      {0, 103, 102},
+      {0, 105, 104},
+      {0, 107, 106},
+      {0, 109, 108},
+      {0, 111, 110},
+      {0, 113, 112},
+      {0, 115, 114},
+      {0, 117, 116},
+      {0, 119, 118},
+      {0, 61, 120},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpVectorTimesScalar, 1), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(127, {
+      {0, 0, 0},
+      {100979271, 0, 0},
+      {269576093, 0, 0},
+      {314809953, 0, 0},
+      {354479447, 0, 0},
+      {497658126, 0, 0},
+      {882718761, 0, 0},
+      {968885186, 0, 0},
+      {973908139, 0, 0},
+      {1019457583, 0, 0},
+      {1191015885, 0, 0},
+      {1266262705, 0, 0},
+      {1310404265, 0, 0},
+      {1325348861, 0, 0},
+      {1367301635, 0, 0},
+      {1368383673, 0, 0},
+      {1570165302, 0, 0},
+      {1618544981, 0, 0},
+      {1646147798, 0, 0},
+      {1674464100, 0, 0},
+      {1679946323, 0, 0},
+      {1686512349, 0, 0},
+      {1766401548, 0, 0},
+      {1774052499, 0, 0},
+      {1788301425, 0, 0},
+      {2023008475, 0, 0},
+      {2055836767, 0, 0},
+      {2096388952, 0, 0},
+      {2123388694, 0, 0},
+      {2129301998, 0, 0},
+      {2212501241, 0, 0},
+      {2274226560, 0, 0},
+      {2362972044, 0, 0},
+      {2378763734, 0, 0},
+      {2506771164, 0, 0},
+      {2558655180, 0, 0},
+      {2622612602, 0, 0},
+      {2660843182, 0, 0},
+      {2698156268, 0, 0},
+      {2801333547, 0, 0},
+      {2850246066, 0, 0},
+      {2895151306, 0, 0},
+      {2970183398, 0, 0},
+      {2986830770, 0, 0},
+      {3001444829, 0, 0},
+      {3133016299, 0, 0},
+      {3152745753, 0, 0},
+      {3187066832, 0, 0},
+      {3261122899, 0, 0},
+      {3496407048, 0, 0},
+      {3513669836, 0, 0},
+      {3536390697, 0, 0},
+      {3570411982, 0, 0},
+      {3653838348, 0, 0},
+      {3713290482, 0, 0},
+      {3858973601, 0, 0},
+      {3873587660, 0, 0},
+      {3877583949, 0, 0},
+      {3882634684, 0, 0},
+      {3907920335, 0, 0},
+      {3997432565, 0, 0},
+      {4169226615, 0, 0},
+      {4219766939, 0, 0},
+      {4243119782, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 25, 12},
+      {0, 41, 29},
+      {0, 56, 44},
+      {0, 1, 3},
+      {0, 48, 24},
+      {0, 33, 60},
+      {0, 8, 50},
+      {0, 35, 21},
+      {0, 11, 7},
+      {0, 34, 23},
+      {0, 59, 57},
+      {0, 10, 62},
+      {0, 40, 2},
+      {0, 5, 49},
+      {0, 39, 17},
+      {0, 9, 61},
+      {0, 30, 6},
+      {0, 19, 46},
+      {0, 53, 54},
+      {0, 31, 52},
+      {0, 55, 43},
+      {0, 66, 65},
+      {0, 16, 67},
+      {0, 51, 68},
+      {0, 70, 69},
+      {0, 26, 36},
+      {0, 72, 71},
+      {0, 74, 73},
+      {0, 76, 75},
+      {0, 78, 77},
+      {0, 80, 79},
+      {0, 82, 81},
+      {0, 37, 83},
+      {0, 85, 84},
+      {0, 13, 86},
+      {0, 20, 18},
+      {0, 38, 28},
+      {0, 58, 45},
+      {0, 87, 63},
+      {0, 15, 88},
+      {0, 32, 22},
+      {0, 89, 4},
+      {0, 90, 14},
+      {0, 91, 42},
+      {0, 93, 92},
+      {0, 95, 94},
+      {0, 97, 96},
+      {0, 99, 98},
+      {0, 101, 100},
+      {0, 103, 102},
+      {0, 105, 104},
+      {0, 107, 106},
+      {0, 109, 108},
+      {0, 111, 110},
+      {0, 113, 112},
+      {0, 115, 114},
+      {0, 27, 47},
+      {0, 117, 116},
+      {0, 119, 118},
+      {0, 121, 120},
+      {0, 123, 122},
+      {0, 125, 124},
+      {0, 126, 64},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpVectorTimesScalar, 2), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(137, {
+      {0, 0, 0},
+      {11698369, 0, 0},
+      {146392076, 0, 0},
+      {151810803, 0, 0},
+      {223800276, 0, 0},
+      {227103506, 0, 0},
+      {253329281, 0, 0},
+      {346929928, 0, 0},
+      {461040879, 0, 0},
+      {629859130, 0, 0},
+      {680157484, 0, 0},
+      {783918780, 0, 0},
+      {810488476, 0, 0},
+      {824323032, 0, 0},
+      {870594305, 0, 0},
+      {959681532, 0, 0},
+      {975807626, 0, 0},
+      {1081642571, 0, 0},
+      {1084574846, 0, 0},
+      {1094817798, 0, 0},
+      {1141965917, 0, 0},
+      {1164137269, 0, 0},
+      {1166917451, 0, 0},
+      {1204787336, 0, 0},
+      {1232501371, 0, 0},
+      {1318479490, 0, 0},
+      {1369818198, 0, 0},
+      {1372785527, 0, 0},
+      {1526654696, 0, 0},
+      {1543672828, 0, 0},
+      {1548121999, 0, 0},
+      {1635292159, 0, 0},
+      {1641070431, 0, 0},
+      {1684282922, 0, 0},
+      {1767704813, 0, 0},
+      {1781765116, 0, 0},
+      {1838763297, 0, 0},
+      {1901166356, 0, 0},
+      {1904846533, 0, 0},
+      {2011183308, 0, 0},
+      {2032069771, 0, 0},
+      {2071351379, 0, 0},
+      {2087004702, 0, 0},
+      {2244928358, 0, 0},
+      {2314864456, 0, 0},
+      {2374216296, 0, 0},
+      {2394332122, 0, 0},
+      {2443610186, 0, 0},
+      {2524697596, 0, 0},
+      {2526961521, 0, 0},
+      {2568098594, 0, 0},
+      {2807907995, 0, 0},
+      {3103302036, 0, 0},
+      {3117071189, 0, 0},
+      {3188115516, 0, 0},
+      {3417584874, 0, 0},
+      {3554463148, 0, 0},
+      {3561482820, 0, 0},
+      {3691770462, 0, 0},
+      {3729929345, 0, 0},
+      {3733675151, 0, 0},
+      {3831290364, 0, 0},
+      {3866493821, 0, 0},
+      {3929248764, 0, 0},
+      {4060703604, 0, 0},
+      {4092487128, 0, 0},
+      {4167600590, 0, 0},
+      {4214779116, 0, 0},
+      {4248015868, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 36, 13},
+      {0, 49, 60},
+      {0, 51, 9},
+      {0, 3, 62},
+      {0, 67, 41},
+      {0, 4, 31},
+      {0, 66, 5},
+      {0, 55, 32},
+      {0, 2, 1},
+      {0, 30, 16},
+      {0, 7, 38},
+      {0, 19, 10},
+      {0, 34, 20},
+      {0, 45, 46},
+      {0, 22, 11},
+      {0, 25, 23},
+      {0, 40, 39},
+      {0, 21, 57},
+      {0, 6, 35},
+      {0, 61, 8},
+      {0, 52, 26},
+      {0, 70, 59},
+      {0, 71, 14},
+      {0, 68, 47},
+      {0, 73, 72},
+      {0, 29, 74},
+      {0, 76, 75},
+      {0, 77, 17},
+      {0, 79, 78},
+      {0, 81, 80},
+      {0, 82, 18},
+      {0, 83, 42},
+      {0, 85, 84},
+      {0, 87, 86},
+      {0, 27, 37},
+      {0, 53, 43},
+      {0, 89, 88},
+      {0, 64, 54},
+      {0, 90, 65},
+      {0, 92, 91},
+      {0, 58, 93},
+      {0, 56, 48},
+      {0, 94, 28},
+      {0, 96, 95},
+      {0, 98, 97},
+      {0, 44, 99},
+      {0, 101, 100},
+      {0, 15, 12},
+      {0, 103, 102},
+      {0, 104, 33},
+      {0, 106, 105},
+      {0, 108, 107},
+      {0, 24, 109},
+      {0, 111, 110},
+      {0, 113, 112},
+      {0, 114, 50},
+      {0, 116, 115},
+      {0, 118, 117},
+      {0, 120, 119},
+      {0, 122, 121},
+      {0, 124, 123},
+      {0, 126, 125},
+      {0, 128, 127},
+      {0, 129, 63},
+      {0, 131, 130},
+      {0, 133, 132},
+      {0, 135, 134},
+      {0, 136, 69},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpVectorTimesScalar, 3), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
+      {0, 0, 0},
+      {1951208733, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 2},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpDot, 0), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(97, {
+      {0, 0, 0},
+      {78001013, 0, 0},
+      {170690025, 0, 0},
+      {206688607, 0, 0},
+      {443490822, 0, 0},
+      {461476226, 0, 0},
+      {537830163, 0, 0},
+      {669982125, 0, 0},
+      {790502615, 0, 0},
+      {805072272, 0, 0},
+      {1173092699, 0, 0},
+      {1220643281, 0, 0},
+      {1448448666, 0, 0},
+      {1466804584, 0, 0},
+      {1473411044, 0, 0},
+      {1515695460, 0, 0},
+      {1587730355, 0, 0},
+      {1625742020, 0, 0},
+      {2071351379, 0, 0},
+      {2250055803, 0, 0},
+      {2291766425, 0, 0},
+      {2416108131, 0, 0},
+      {2427834344, 0, 0},
+      {2436009347, 0, 0},
+      {2455417440, 0, 0},
+      {2480811229, 0, 0},
+      {2654325647, 0, 0},
+      {2919796598, 0, 0},
+      {3047649911, 0, 0},
+      {3088511797, 0, 0},
+      {3104643263, 0, 0},
+      {3198541202, 0, 0},
+      {3204986803, 0, 0},
+      {3272233597, 0, 0},
+      {3383007207, 0, 0},
+      {3602108619, 0, 0},
+      {3622349409, 0, 0},
+      {3714664910, 0, 0},
+      {3717942504, 0, 0},
+      {3732000233, 0, 0},
+      {3759072440, 0, 0},
+      {3765247327, 0, 0},
+      {3805423332, 0, 0},
+      {3829325073, 0, 0},
+      {3866493821, 0, 0},
+      {4058280485, 0, 0},
+      {4061558677, 0, 0},
+      {4148979936, 0, 0},
+      {4155586396, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 13, 38},
+      {0, 39, 14},
+      {0, 44, 9},
+      {0, 48, 47},
+      {0, 23, 15},
+      {0, 33, 25},
+      {0, 1, 42},
+      {0, 5, 46},
+      {0, 31, 3},
+      {0, 36, 28},
+      {0, 16, 12},
+      {0, 32, 22},
+      {0, 41, 21},
+      {0, 6, 50},
+      {0, 51, 29},
+      {0, 45, 34},
+      {0, 37, 8},
+      {0, 19, 52},
+      {0, 11, 4},
+      {0, 43, 40},
+      {0, 27, 53},
+      {0, 54, 10},
+      {0, 24, 55},
+      {0, 57, 56},
+      {0, 58, 26},
+      {0, 2, 59},
+      {0, 61, 60},
+      {0, 63, 62},
+      {0, 65, 64},
+      {0, 20, 66},
+      {0, 30, 35},
+      {0, 67, 17},
+      {0, 68, 7},
+      {0, 70, 69},
+      {0, 71, 18},
+      {0, 73, 72},
+      {0, 75, 74},
+      {0, 77, 76},
+      {0, 79, 78},
+      {0, 81, 80},
+      {0, 83, 82},
+      {0, 85, 84},
+      {0, 87, 86},
+      {0, 89, 88},
+      {0, 91, 90},
+      {0, 93, 92},
+      {0, 95, 94},
+      {0, 49, 96},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpDot, 1), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(117, {
+      {0, 0, 0},
+      {50385656, 0, 0},
+      {181902171, 0, 0},
+      {560078433, 0, 0},
+      {615982737, 0, 0},
+      {674428451, 0, 0},
+      {837715723, 0, 0},
+      {886972033, 0, 0},
+      {900101778, 0, 0},
+      {983299427, 0, 0},
+      {1237148906, 0, 0},
+      {1364157225, 0, 0},
+      {1367301635, 0, 0},
+      {1380160211, 0, 0},
+      {1451831482, 0, 0},
+      {1499923635, 0, 0},
+      {1570165302, 0, 0},
+      {1735295265, 0, 0},
+      {1766401548, 0, 0},
+      {1796311149, 0, 0},
+      {1826456251, 0, 0},
+      {1839669171, 0, 0},
+      {2012838864, 0, 0},
+      {2024071551, 0, 0},
+      {2096388952, 0, 0},
+      {2161102232, 0, 0},
+      {2197874825, 0, 0},
+      {2279700640, 0, 0},
+      {2289183712, 0, 0},
+      {2351620600, 0, 0},
+      {2362972044, 0, 0},
+      {2472176885, 0, 0},
+      {2477434291, 0, 0},
+      {2530899578, 0, 0},
+      {2531826164, 0, 0},
+      {2558133383, 0, 0},
+      {2589449658, 0, 0},
+      {2621255555, 0, 0},
+      {2622612602, 0, 0},
+      {2872580757, 0, 0},
+      {2881302403, 0, 0},
+      {2891091137, 0, 0},
+      {2923708820, 0, 0},
+      {2936040203, 0, 0},
+      {2970183398, 0, 0},
+      {3187066832, 0, 0},
+      {3224952074, 0, 0},
+      {3244383472, 0, 0},
+      {3261122899, 0, 0},
+      {3362830643, 0, 0},
+      {3538158875, 0, 0},
+      {3635542517, 0, 0},
+      {3682213068, 0, 0},
+      {3721902098, 0, 0},
+      {3826846522, 0, 0},
+      {3877583949, 0, 0},
+      {3997432565, 0, 0},
+      {4093615095, 0, 0},
+      {4106828015, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 52, 28},
+      {0, 33, 20},
+      {0, 46, 57},
+      {0, 47, 54},
+      {0, 21, 17},
+      {0, 31, 58},
+      {0, 12, 53},
+      {0, 29, 3},
+      {0, 35, 34},
+      {0, 48, 41},
+      {0, 8, 5},
+      {0, 7, 55},
+      {0, 37, 32},
+      {0, 60, 38},
+      {0, 61, 16},
+      {0, 14, 62},
+      {0, 23, 63},
+      {0, 13, 19},
+      {0, 64, 9},
+      {0, 65, 39},
+      {0, 2, 66},
+      {0, 67, 42},
+      {0, 69, 68},
+      {0, 25, 70},
+      {0, 1, 49},
+      {0, 6, 71},
+      {0, 72, 15},
+      {0, 73, 11},
+      {0, 75, 74},
+      {0, 77, 76},
+      {0, 4, 78},
+      {0, 56, 50},
+      {0, 80, 79},
+      {0, 10, 81},
+      {0, 83, 82},
+      {0, 85, 84},
+      {0, 86, 27},
+      {0, 43, 40},
+      {0, 88, 87},
+      {0, 44, 24},
+      {0, 30, 89},
+      {0, 51, 36},
+      {0, 45, 90},
+      {0, 18, 91},
+      {0, 93, 92},
+      {0, 22, 94},
+      {0, 26, 95},
+      {0, 97, 96},
+      {0, 99, 98},
+      {0, 101, 100},
+      {0, 103, 102},
+      {0, 105, 104},
+      {0, 107, 106},
+      {0, 109, 108},
+      {0, 111, 110},
+      {0, 113, 112},
+      {0, 59, 114},
+      {0, 116, 115},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpDot, 2), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(179, {
+      {0, 0, 0},
+      {27177503, 0, 0},
+      {50385656, 0, 0},
+      {129748122, 0, 0},
+      {139011596, 0, 0},
+      {162608772, 0, 0},
+      {181902171, 0, 0},
+      {225200779, 0, 0},
+      {342159236, 0, 0},
+      {386293029, 0, 0},
+      {429023543, 0, 0},
+      {443558693, 0, 0},
+      {504514034, 0, 0},
+      {615982737, 0, 0},
+      {669812542, 0, 0},
+      {674428451, 0, 0},
+      {837715723, 0, 0},
+      {861753115, 0, 0},
+      {875212982, 0, 0},
+      {876867882, 0, 0},
+      {899320334, 0, 0},
+      {900101778, 0, 0},
+      {938517572, 0, 0},
+      {1347339159, 0, 0},
+      {1356063462, 0, 0},
+      {1373856501, 0, 0},
+      {1376656865, 0, 0},
+      {1451831482, 0, 0},
+      {1522979646, 0, 0},
+      {1548491889, 0, 0},
+      {1570165302, 0, 0},
+      {1735295265, 0, 0},
+      {1747355813, 0, 0},
+      {1766401548, 0, 0},
+      {1871105284, 0, 0},
+      {1918742169, 0, 0},
+      {1922045399, 0, 0},
+      {1978689945, 0, 0},
+      {2024071551, 0, 0},
+      {2059975069, 0, 0},
+      {2076833303, 0, 0},
+      {2096388952, 0, 0},
+      {2181030375, 0, 0},
+      {2197874825, 0, 0},
+      {2362972044, 0, 0},
+      {2414725163, 0, 0},
+      {2517964682, 0, 0},
+      {2564745684, 0, 0},
+      {2577387676, 0, 0},
+      {2589449658, 0, 0},
+      {2604242419, 0, 0},
+      {2683080096, 0, 0},
+      {2696349144, 0, 0},
+      {2763960513, 0, 0},
+      {2817823941, 0, 0},
+      {2852854788, 0, 0},
+      {2891091137, 0, 0},
+      {2919626325, 0, 0},
+      {2923708820, 0, 0},
+      {2936040203, 0, 0},
+      {2963744582, 0, 0},
+      {2970183398, 0, 0},
+      {2984459037, 0, 0},
+      {2996594997, 0, 0},
+      {3015046341, 0, 0},
+      {3055195668, 0, 0},
+      {3127329373, 0, 0},
+      {3187066832, 0, 0},
+      {3193597927, 0, 0},
+      {3200890815, 0, 0},
+      {3224258475, 0, 0},
+      {3224480461, 0, 0},
+      {3261122899, 0, 0},
+      {3609540589, 0, 0},
+      {3619404941, 0, 0},
+      {3619626927, 0, 0},
+      {3727034815, 0, 0},
+      {3742724777, 0, 0},
+      {3742946763, 0, 0},
+      {3836179806, 0, 0},
+      {3913885196, 0, 0},
+      {3927338499, 0, 0},
+      {3927466635, 0, 0},
+      {3997432565, 0, 0},
+      {3999472204, 0, 0},
+      {4010499223, 0, 0},
+      {4032662899, 0, 0},
+      {4110915453, 0, 0},
+      {4145966869, 0, 0},
+      {4228303141, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 23, 87},
+      {0, 9, 28},
+      {0, 42, 17},
+      {0, 74, 70},
+      {0, 86, 77},
+      {0, 18, 5},
+      {0, 31, 32},
+      {0, 34, 3},
+      {0, 38, 68},
+      {0, 50, 29},
+      {0, 72, 62},
+      {0, 21, 15},
+      {0, 14, 54},
+      {0, 56, 22},
+      {0, 48, 88},
+      {0, 2, 76},
+      {0, 6, 47},
+      {0, 26, 79},
+      {0, 65, 12},
+      {0, 37, 81},
+      {0, 91, 60},
+      {0, 30, 92},
+      {0, 25, 7},
+      {0, 45, 40},
+      {0, 66, 52},
+      {0, 71, 69},
+      {0, 78, 75},
+      {0, 84, 82},
+      {0, 94, 93},
+      {0, 27, 95},
+      {0, 97, 96},
+      {0, 99, 98},
+      {0, 100, 39},
+      {0, 55, 101},
+      {0, 58, 102},
+      {0, 89, 103},
+      {0, 35, 11},
+      {0, 104, 36},
+      {0, 53, 10},
+      {0, 1, 64},
+      {0, 73, 20},
+      {0, 105, 13},
+      {0, 107, 106},
+      {0, 8, 16},
+      {0, 24, 19},
+      {0, 85, 63},
+      {0, 109, 108},
+      {0, 111, 110},
+      {0, 4, 112},
+      {0, 114, 113},
+      {0, 116, 115},
+      {0, 118, 117},
+      {0, 83, 119},
+      {0, 121, 120},
+      {0, 123, 122},
+      {0, 49, 44},
+      {0, 124, 57},
+      {0, 125, 59},
+      {0, 126, 67},
+      {0, 128, 127},
+      {0, 130, 129},
+      {0, 132, 131},
+      {0, 134, 133},
+      {0, 135, 51},
+      {0, 137, 136},
+      {0, 138, 61},
+      {0, 43, 41},
+      {0, 140, 139},
+      {0, 142, 141},
+      {0, 144, 143},
+      {0, 146, 145},
+      {0, 148, 147},
+      {0, 149, 33},
+      {0, 80, 150},
+      {0, 152, 151},
+      {0, 154, 153},
+      {0, 156, 155},
+      {0, 158, 157},
+      {0, 160, 159},
+      {0, 162, 161},
+      {0, 164, 163},
+      {0, 166, 165},
+      {0, 168, 167},
+      {0, 46, 169},
+      {0, 171, 170},
+      {0, 90, 172},
+      {0, 174, 173},
+      {0, 176, 175},
+      {0, 178, 177},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpDot, 3), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
+      {0, 0, 0},
+      {1036475267, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 2},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpLabel, 0), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
+      {0, 0, 0},
+      {1036475267, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 1, 2},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpBranch, 0), std::move(codec));
+  }
+
+  {
+    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(119, {
+      {0, 0, 0},
+      {57149555, 0, 0},
+      {139011596, 0, 0},
+      {255835594, 0, 0},
+      {330249537, 0, 0},
+      {388686774, 0, 0},
+      {508217552, 0, 0},
+      {550831114, 0, 0},
+      {559246409, 0, 0},
+      {599185303, 0, 0},
+      {649208064, 0, 0},
+      {679061455, 0, 0},
+      {810488476, 0, 0},
+      {951841533, 0, 0},
+      {1008886329, 0, 0},
+      {1022544883, 0, 0},
+      {1215030156, 0, 0},
+      {1305703280, 0, 0},
+      {1367301635, 0, 0},
+      {1453447304, 0, 0},
+      {1487177499, 0, 0},
+      {1603937321, 0, 0},
+      {1617826947, 0, 0},
+      {1643868273, 0, 0},
+      {1672607981, 0, 0},
+      {1681941034, 0, 0},
+      {1755165354, 0, 0},
+      {1781864804, 0, 0},
+      {1795715718, 0, 0},
+      {1977038330, 0, 0},
+      {2096388952, 0, 0},
+      {2204920111, 0, 0},
+      {2244470522, 0, 0},
+      {2330636993, 0, 0},
+      {2400601988, 0, 0},
+      {2424848261, 0, 0},
+      {2603020391, 0, 0},
+      {2622612602, 0, 0},
+      {2645135839, 0, 0},
+      {2660843182, 0, 0},
+      {2708915136, 0, 0},
+      {2724166585, 0, 0},
+      {2728667725, 0, 0},
+      {2890638791, 0, 0},
+      {2901034693, 0, 0},
+      {2941648648, 0, 0},
+      {2970183398, 0, 0},
+      {2998120306, 0, 0},
+      {3123244280, 0, 0},
+      {3187066832, 0, 0},
+      {3209399506, 0, 0},
+      {3230260738, 0, 0},
+      {3344189994, 0, 0},
+      {3345707173, 0, 0},
+      {3367298820, 0, 0},
+      {3397078357, 0, 0},
+      {3569736966, 0, 0},
+      {3816961131, 0, 0},
+      {4091670162, 0, 0},
+      {4237497041, 0, 0},
+      {1111111111111111111, 0, 0},
+      {0, 17, 44},
+      {0, 25, 20},
+      {0, 29, 34},
+      {0, 18, 2},
+      {0, 54, 49},
+      {0, 28, 7},
+      {0, 47, 52},
+      {0, 23, 56},
+      {0, 55, 26},
+      {0, 24, 61},
+      {0, 13, 62},
+      {0, 63, 45},
+      {0, 27, 15},
+      {0, 64, 8},
+      {0, 65, 59},
+      {0, 35, 22},
+      {0, 53, 38},
+      {0, 58, 51},
+      {0, 11, 66},
+      {0, 10, 3},
+      {0, 46, 67},
+      {0, 69, 68},
+      {0, 1, 50},
+      {0, 42, 19},
+      {0, 70, 6},
+      {0, 31, 71},
+      {0, 16, 72},
+      {0, 74, 73},
+      {0, 76, 75},
+      {0, 78, 77},
+      {0, 79, 4},
+      {0, 5, 37},
+      {0, 14, 36},
+      {0, 80, 57},
+      {0, 81, 48},
+      {0, 83, 82},
+      {0, 39, 84},
+      {0, 86, 85},
+      {0, 40, 87},
+      {0, 89, 88},
+      {0, 91, 90},
+      {0, 93, 92},
+      {0, 21, 9},
+      {0, 41, 32},
+      {0, 12, 43},
+      {0, 95, 94},
+      {0, 97, 96},
+      {0, 99, 98},
+      {0, 100, 33},
+      {0, 60, 101},
+      {0, 103, 102},
+      {0, 105, 104},
+      {0, 107, 106},
+      {0, 109, 108},
+      {0, 110, 30},
+      {0, 112, 111},
+      {0, 114, 113},
+      {0, 116, 115},
+      {0, 118, 117},
+    }));
+
+    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpReturnValue, 0), std::move(codec));
+  }
+
+  return codecs;
+}
+
+std::unordered_set<uint32_t> GetDescriptorsWithCodingScheme() {
+  std::unordered_set<uint32_t> descriptors_with_coding_scheme = {
+    3816961131,
+    3569736966,
+    3397078357,
+    3344189994,
+    3230260738,
+    2941648648,
+    2901034693,
+    2728667725,
+    2400601988,
+    1795715718,
+    1681941034,
+    1487177499,
+    1453447304,
+    679061455,
+    649208064,
+    559246409,
+    388686774,
+    4228303141,
+    4110915453,
+    4010499223,
+    3927466635,
+    3927338499,
+    3836179806,
+    3742724777,
+    3619404941,
+    3224480461,
+    3224258475,
+    3200890815,
+    3742946763,
+    3193597927,
+    2604242419,
+    2577387676,
+    2181030375,
+    1376656865,
+    1347339159,
+    938517572,
+    876867882,
+    429023543,
+    129748122,
+    4106828015,
+    4093615095,
+    3826846522,
+    3721902098,
+    3244383472,
+    2891091137,
+    2872580757,
+    2558133383,
+    2477434291,
+    1839669171,
+    2059975069,
+    1735295265,
+    1364157225,
+    1237148906,
+    886972033,
+    674428451,
+    4148979936,
+    3805423332,
+    3732000233,
+    3717942504,
+    3714664910,
+    3622349409,
+    3272233597,
+    3204986803,
+    3088511797,
+    1672607981,
+    2416108131,
+    2250055803,
+    1796311149,
+    1515695460,
+    537830163,
+    461476226,
+    206688607,
+    78001013,
+    3866493821,
+    3417584874,
+    3188115516,
+    2526961521,
+    2443610186,
+    2394332122,
+    2374216296,
+    2032069771,
+    2011183308,
+    1904846533,
+    1641070431,
+    1635292159,
+    1372785527,
+    1369818198,
+    1204787336,
+    1826456251,
+    1164137269,
+    1081642571,
+    629859130,
+    253329281,
+    227103506,
+    11698369,
+    4219766939,
+    4169226615,
+    3997432565,
+    3873587660,
+    3513669836,
+    3261122899,
+    2129301998,
+    1774052499,
+    1266262705,
+    4285201458,
+    4245743275,
+    3907920335,
+    3837583704,
+    3641833815,
+    3307100165,
+    1232501371,
+    3262572726,
+    3045470312,
+    2879050471,
+    2801333547,
+    2506771164,
+    2504802016,
+    2500422644,
+    2473053808,
+    2457690657,
+    2345566651,
+    2306141594,
+    2154320787,
+    2055637638,
+    1527762373,
+    1486206763,
+    1159301677,
+    1105835505,
+    968885186,
+    885645401,
+    849867303,
+    815034111,
+    497658126,
+    386525753,
+    179458548,
+    102358168,
+    4127308103,
+    4073492988,
+    1473411044,
+    805072272,
+    3724004880,
+    3602108619,
+    3585315836,
+    3331487616,
+    3261703164,
+    3235459678,
+    3085703811,
+    3047649911,
+    2357410109,
+    2291766425,
+    2071351379,
+    1904128160,
+    1738815671,
+    1531216990,
+    1465623797,
+    1324351672,
+    1220127364,
+    1144188012,
+    183103444,
+    116093251,
+    3900859293,
+    3345856521,
+    3691770462,
+    3263841912,
+    3198541202,
+    3098991995,
+    3682213068,
+    2963184673,
+    2864543087,
+    2802261839,
+    2790648021,
+    900101778,
+    2715304020,
+    100979271,
+    2709694527,
+    2669086217,
+    2531826164,
+    2651956495,
+    2552825357,
+    2480811229,
+    3138977758,
+    2434845539,
+    2066323109,
+    1777640493,
+    1758287856,
+    1746004874,
+    3945482286,
+    3932146199,
+    3129573055,
+    3126269825,
+    3716914380,
+    985750227,
+    1543672828,
+    3189039115,
+    1839499483,
+    2696349144,
+    1536350567,
+    3971481069,
+    3001444829,
+    4028622909,
+    215293834,
+    213642219,
+    153085016,
+    1189681639,
+    165054168,
+    29517006,
+    2614879967,
+    27865391,
+    1649426421,
+    4239834800,
+    1947620272,
+    28782128,
+    3207966516,
+    3713290482,
+    2042001863,
+    2724166585,
+    2356768706,
+    1793544760,
+    4092654294,
+    2157103435,
+    2087004702,
+    2043873558,
+    27177503,
+    1033363654,
+    4214779116,
+    408465899,
+    451264926,
+    2377112119,
+    1182296898,
+    760554870,
+    3566035349,
+    2630220147,
+    4192247221,
+    1572088444,
+    3538592682,
+    769422756,
+    1674803691,
+    630964591,
+    3458449569,
+    565334834,
+    137840602,
+    3955205564,
+    2009007457,
+    1258105452,
+    333554713,
+    3923810593,
+    126463145,
+    3445109809,
+    2966409025,
+    2849215484,
+    1910240213,
+    3131890669,
+    586244865,
+    2320303498,
+    3116932970,
+    1317265040,
+    2812498065,
+    1466938734,
+    4064212479,
+    2613179511,
+    2095546797,
+    1671139745,
+    2568512089,
+    3695940604,
+    1119069977,
+    215027449,
+    4123141705,
+    3495546641,
+    1978689945,
+    3202324433,
+    3783543823,
+    2674422363,
+    1352628475,
+    1290956281,
+    1894417995,
+    740921498,
+    4211577142,
+    1033081852,
+    3884846406,
+    3253403867,
+    2790624748,
+    2538917932,
+    2144962711,
+    3323202731,
+    4290024976,
+    2564745684,
+    2963744582,
+    2443959748,
+    354479447,
+    750870327,
+    1918481917,
+    4032662899,
+    3587381650,
+    2414725163,
+    1081611718,
+    1625742020,
+    2308565678,
+    1871105284,
+    2807907995,
+    2121980967,
+    1054641568,
+    413918748,
+    1917336504,
+    1816558243,
+    4130950286,
+    1522979646,
+    1669959736,
+    1320550031,
+    3104643263,
+    3823959661,
+    3525913657,
+    3584683259,
+    2918750759,
+    3536390697,
+    94303122,
+    3296691317,
+    801484894,
+    2496463830,
+    3266028549,
+    3085157904,
+    973908139,
+    3787909072,
+    3107413701,
+    2378763734,
+    920604853,
+    2516325050,
+    1838993983,
+    1603937321,
+    3183924418,
+    1945006185,
+    3982311384,
+    2682510803,
+    680388473,
+    979993429,
+    2405770322,
+    461040879,
+    2817579280,
+    14113753,
+    2894979602,
+    168339452,
+    951841533,
+    4154758669,
+    2637132451,
+    3877583949,
+    1949856502,
+    922996215,
+    3941049054,
+    4182141402,
+    2262220987,
+    1957218950,
+    2094550054,
+    1846856260,
+    3499234137,
+    3086631065,
+    3054834317,
+    593829839,
+    522971108,
+    1162127370,
+    4233562270,
+    2780190687,
+    1558345254,
+    3716353056,
+    3518630848,
+    1158929937,
+    2038205856,
+    86116519,
+    4185661467,
+    975807626,
+    3910458990,
+    4124281183,
+    3361419439,
+    171334650,
+    2590402790,
+    2890570341,
+    2303184249,
+    385229009,
+    1998433745,
+    1717510093,
+    4022124023,
+    1429389803,
+    945128292,
+    904486530,
+    3869890846,
+    619875033,
+    459968607,
+    3743748793,
+    359054425,
+    1417363940,
+    3653985133,
+    255835594,
+    1047011733,
+    2763232252,
+    1329499601,
+    328661377,
+    2162274327,
+    2100532220,
+    4255182614,
+    4243119782,
+    3982047273,
+    4053789056,
+    401211099,
+    950731750,
+    1319785741,
+    32085358,
+    3882634684,
+    3117071189,
+    3554463148,
+    3570219049,
+    3535289452,
+    2314864456,
+    3913885196,
+    2763960513,
+    1079999262,
+    27130513,
+    3033873113,
+    2976581453,
+    2598189097,
+    595410904,
+    1572834111,
+    13319433,
+    1084574846,
+    2123388694,
+    560078433,
+    1679946323,
+    3518703473,
+    184634770,
+    296981500,
+    1646147798,
+    455591063,
+    1325348861,
+    3224952074,
+    1027242654,
+    2281956980,
+    4221373527,
+    1289566249,
+    4044928561,
+    882718761,
+    1510333659,
+    836581417,
+    1901166356,
+    2276405827,
+    4052965752,
+    1155765244,
+    503145996,
+    251209228,
+    495107308,
+    3944781937,
+    37459569,
+    4248015868,
+    4198082194,
+    1302400505,
+    4106658327,
+    680016782,
+    2319227476,
+    2738307068,
+    3929248764,
+    2850246066,
+    1824526196,
+    3912967080,
+    3044723416,
+    3133016299,
+    2517964682,
+    3647586740,
+    3653838348,
+    929101967,
+    3571454885,
+    2806296851,
+    977312655,
+    646282397,
+    3448018532,
+    824323032,
+    204234270,
+    1579585816,
+    3712763835,
+    1212872174,
+    3953984401,
+    3168953855,
+    2944827576,
+    1582841441,
+    2796901051,
+    3323682385,
+    1317058015,
+    2557550659,
+    1620634991,
+    2986830770,
+    2490492987,
+    1817271123,
+    40653745,
+    1696076631,
+    2466126792,
+    4169878842,
+    3251128023,
+    2444465148,
+    678695941,
+    2481746922,
+    2836440943,
+    774727851,
+    2246405597,
+    4028028350,
+    2524697596,
+    1977038330,
+    2817823941,
+    2219733501,
+    688216667,
+    3634598908,
+    3232633974,
+    2724625059,
+    3269075805,
+    3732640764,
+    2263349224,
+    1680746207,
+    2414984922,
+    2507457870,
+    50998433,
+    3092528578,
+    3712946115,
+    1543935193,
+    807276090,
+    1221183390,
+    172029722,
+    2122275289,
+    3990925720,
+    2261697609,
+    2736881867,
+    295017943,
+    3278176820,
+    3748965853,
+    3174324790,
+    1103903216,
+    3184177968,
+    1113409935,
+    2299842241,
+    2162986400,
+    1538342947,
+    4056442905,
+    1631434666,
+    205885885,
+    1594733696,
+    1955104493,
+    1022309772,
+    3820814597,
+    993150979,
+    1209418480,
+    1784441183,
+    3958731802,
+    2250225826,
+    3065160070,
+    2024071551,
+    107497541,
+    628544021,
+    2732195517,
+    4241486415,
+    3969279737,
+    870594305,
+    2916400082,
+    1193734351,
+    3202349435,
+    3831290364,
+    3282979782,
+    3928764629,
+    1308462133,
+    3216471040,
+    2433519008,
+    2022961611,
+    3604842236,
+    3374978006,
+    2855895374,
+    3496407048,
+    1482251215,
+    3994511488,
+    2997832431,
+    1132589448,
+    1348149915,
+    2092468906,
+    2451531615,
+    779021139,
+    3730093054,
+    3413713311,
+    1022915255,
+    2204920111,
+    2660843182,
+    1080545747,
+    1642805350,
+    1766422419,
+    4141567741,
+    1558990974,
+    4185590212,
+    2841468319,
+    701281393,
+    3325419312,
+    451957774,
+    357505993,
+    1156369516,
+    3187387500,
+    2259467579,
+    2678954464,
+    3154597438,
+    543558236,
+    2359973133,
+    1990431740,
+    2705477184,
+    1041368449,
+    3122368657,
+    3181646225,
+    1094423548,
+    2955375511,
+    2888125966,
+    153013225,
+    2936040203,
+    1758530522,
+    573901046,
+    3030911670,
+    1675922848,
+    4235213885,
+    4091916710,
+    2633682514,
+    4254584852,
+    2328748202,
+    3357301402,
+    3877813395,
+    2004567202,
+    2496297824,
+    3334207724,
+    1600149091,
+    293528591,
+    1782996825,
+    3757282300,
+    1107206446,
+    1092948665,
+    1797960910,
+    1206726575,
+    1496351055,
+    3021406120,
+    99347751,
+    3797204453,
+    1468919488,
+    797415788,
+    1314843976,
+    2934934694,
+    490769168,
+    1474506522,
+    3811268385,
+    864295921,
+    3081676220,
+    151810803,
+    2588618056,
+    2998120306,
+    416853049,
+    3495967422,
+    3233393284,
+    508007510,
+    759277550,
+    1971252067,
+    869050696,
+    810488476,
+    745556697,
+    789872778,
+    3362723943,
+    1617826947,
+    3260309823,
+    2197904616,
+    1199157863,
+    1643868273,
+    2430404313,
+    321630747,
+    2503194620,
+    3194725903,
+    2881225774,
+    3997952447,
+    1389644742,
+    2713718873,
+    3585511591,
+    1684282922,
+    3366848728,
+    284226441,
+    1541020250,
+    4018237905,
+    1369578001,
+    2424848261,
+    2654325647,
+    1626224034,
+    1081536219,
+    309040124,
+    123060826,
+    3997038726,
+    1670691893,
+    1543280290,
+    443347828,
+    1776629361,
+    3118548424,
+    478440524,
+    679771963,
+    3729929345,
+    4244789645,
+    2366506734,
+    2838165089,
+    1619778288,
+    1313182965,
+    3240680626,
+    1323407757,
+    883854656,
+    2194691858,
+    15502752,
+    3760372982,
+    1366337101,
+    3656163446,
+    295018543,
+    825595257,
+    57149555,
+    2563789125,
+    2353194283,
+    2636942752,
+    4026740269,
+    3570411982,
+    123108003,
+    3782362128,
+    1280126114,
+    1410849099,
+    4228502127,
+    3609540589,
+    3365041621,
+    269823086,
+    348988933,
+    1636389511,
+    2936586309,
+    2761603302,
+    2318200267,
+    449954059,
+    2895413148,
+    1755165354,
+    4274214049,
+    778500192,
+    3345707173,
+    3732136051,
+    721450866,
+    1600392975,
+    2466255445,
+    4050155669,
+    3541895912,
+    1139547465,
+    394654115,
+    1380991098,
+    3516240523,
+    2234361374,
+    1094817798,
+    744817486,
+    3564402361,
+    1452222566,
+    1851510470,
+    3619787319,
+    4265894873,
+    216945449,
+    3061690214,
+    2910557180,
+    255227811,
+    4167600590,
+    1587209598,
+    3157581152,
+    3184381405,
+    2572638469,
+    615748604,
+    2532518896,
+    1774874546,
+    599185303,
+    1561718045,
+    1742737136,
+    1674464100,
+    3136865519,
+    706016261,
+    2793529873,
+    3504981554,
+    4155122613,
+    2080953106,
+    1104362365,
+    2879917501,
+    850497536,
+    1392080469,
+    1287937401,
+    718877177,
+    1917966999,
+    1822823090,
+    3701632935,
+    3591222197,
+    2817335337,
+    1941148668,
+    3110479131,
+    3289213933,
+    583624926,
+    468372467,
+    1633850097,
+    2110223508,
+    898191441,
+    112745085,
+    4018820793,
+    3085119011,
+    2919626325,
+    3094857332,
+    2348201466,
+    2192810893,
+    4163160985,
+    1269075360,
+    3952316364,
+    2881886868,
+    439764402,
+    1584774136,
+    169674806,
+    3759072440,
+    102542696,
+    2996180816,
+    804899022,
+    1015552308,
+    963902061,
+    3504158761,
+    2002490364,
+    2806716850,
+    265778447,
+    4083122425,
+    181902171,
+    1238120570,
+    75986790,
+    1265796414,
+    899570100,
+    2988365258,
+    3655201337,
+    3654061472,
+    3061856840,
+    1077859090,
+    615341051,
+    3678875745,
+    3349230696,
+    3647606635,
+    2549309392,
+    1508570930,
+    1766401548,
+    1448448666,
+    1499923635,
+    2882994691,
+    3674863070,
+    3056042030,
+    4240893633,
+    1395113939,
+    2964622752,
+    1951208733,
+    3536941067,
+    4176581069,
+    1203545131,
+    3092754101,
+    246375791,
+    2736026107,
+    1069781886,
+    3687777340,
+    1564342316,
+    535067202,
+    1395923345,
+    3240977890,
+    1447712361,
+    2602027658,
+    718301639,
+    3123244280,
+    1032593647,
+    2840366496,
+    2680819379,
+    3839389658,
+    277023757,
+    1172110445,
+    1755648697,
+    2472176885,
+    223800276,
+    625975427,
+    976111724,
+    4145966869,
+    2789375411,
+    618087261,
+    249378857,
+    4058280485,
+    827698488,
+    1558001705,
+    3561482820,
+    2562485583,
+    4243138030,
+    615982737,
+    1220643281,
+    150685616,
+    3091876332,
+    1040775722,
+    669982125,
+    4116080964,
+    3582002820,
+    910398460,
+    1036475267,
+    3800912395,
+    146392076,
+    1686512349,
+    2326636627,
+    2839816704,
+    3502816184,
+    226836633,
+    3953733490,
+    257136089,
+    819503463,
+    2863084840,
+    1949759310,
+    210754155,
+    1367301635,
+    3822983876,
+    4273793488,
+    3635397748,
+    3930494584,
+    3127921440,
+    3167253437,
+    3868239231,
+    1859128680,
+    3480031018,
+    3810805277,
+    2677252364,
+    156014509,
+    3627739127,
+    2321729979,
+    1146476634,
+    4039938779,
+    1964254745,
+    2055836767,
+    119981689,
+    2629265310,
+    2448331885,
+    3737376990,
+    144116905,
+    2272221101,
+    2197874825,
+    1277245109,
+    2503770904,
+    360730278,
+    3489360962,
+    1166917451,
+    707478563,
+    4155586396,
+    162255877,
+    347505241,
+    4215670524,
+    3187066832,
+    2399809085,
+    2754074729,
+    4060703604,
+    628331516,
+    1304296041,
+    616435646,
+    4080527786,
+    1443829854,
+    2512398201,
+    708736129,
+    13107491,
+    3794803132,
+    2049792025,
+    2455417440,
+    3367313400,
+    3357250579,
+    3694383800,
+    2339901602,
+    3242843022,
+    2282454607,
+    1243764146,
+    835458563,
+    1297706389,
+    464259778,
+    1766994680,
+    1294403159,
+    2568098594,
+    3107165180,
+    4040340620,
+    3352361837,
+    1031290113,
+    2903897222,
+    1677700667,
+    3160388974,
+    107544081,
+    3044188332,
+    2285081596,
+    2835131395,
+    2984459037,
+    4174489262,
+    1236389532,
+    2938237924,
+    321459212,
+    3407526215,
+    300939750,
+    3441531391,
+    2909957084,
+    3192069648,
+    1849065716,
+    2524531022,
+    505940164,
+    4121643374,
+    3774892253,
+    3197739982,
+    2161102232,
+    2715370488,
+    1992893964,
+    1781864804,
+    587888644,
+    1039111164,
+    4237497041,
+    451382997,
+    969500141,
+    1415510495,
+    3743398113,
+    3027538652,
+    2525173102,
+    1708264968,
+    3366040354,
+    1100599986,
+    188347929,
+    2597020383,
+    2705434194,
+    2593884753,
+    3472123498,
+    2975894973,
+    3152745753,
+    1154919607,
+    1930923350,
+    3287039847,
+    1372881231,
+    2280400314,
+    3369343584,
+    2351620600,
+    2645135839,
+    2752766693,
+    1471851763,
+    1989520052,
+    1141965917,
+    1503477720,
+    653708953,
+    1765126703,
+    2432827426,
+    95470391,
+    2567901801,
+    2589449658,
+    4218799564,
+    3249265647,
+    3673811979,
+    210116709,
+    1593584949,
+    1791352211,
+    3457985288,
+    3345288309,
+    531559080,
+    2491124112,
+    3410158390,
+    4224872590,
+    3705139860,
+    162608772,
+    4258229445,
+    925559698,
+    3928842969,
+    4253051659,
+    3633746133,
+    3867307935,
+    3560665067,
+    798915737,
+    2945369269,
+    2677264274,
+    2278571792,
+    177111659,
+    85880059,
+    1297165140,
+    1630583316,
+    2232491275,
+    1848784182,
+    2487708241,
+    626480004,
+    3427283542,
+    2108571893,
+    304448521,
+    3332104493,
+    2244470522,
+    436416061,
+    221900294,
+    1502470404,
+    3552593177,
+    440421571,
+    450406196,
+    503094540,
+    3836822275,
+    2708915136,
+    3750617468,
+    1119744229,
+    3614752756,
+    921246433,
+    2285438321,
+    626892406,
+    2362972044,
+    72782198,
+    2929019254,
+    2795773560,
+    907126242,
+    155458798,
+    2798552666,
+    1404739463,
+    4285652249,
+    1998444837,
+    908777857,
+    872544165,
+    910429472,
+    135486769,
+    3457269042,
+    426360862,
+    1725011064,
+    296836635,
+    1322549027,
+    2044728014,
+    1530183840,
+    529742207,
+    4272200782,
+    1341516288,
+    2608484640,
+    41739659,
+    3260579369,
+    2745872368,
+    2894051250,
+    862784766,
+    3077271274,
+    3094180193,
+    3619626927,
+    3745223676,
+    2976066508,
+    2854085372,
+    2959147533,
+    3266548732,
+    1776526161,
+    3712296962,
+    1955871800,
+    2580096524,
+    2507709226,
+    3564865233,
+    948086521,
+    1548254487,
+    142465290,
+    1472185378,
+    1459457331,
+    2274226560,
+    3153451899,
+    492958971,
+    3563213618,
+    1285705317,
+    410274915,
+    3710645347,
+    1309728002,
+    2119793999,
+    1343794461,
+    4024173916,
+    2383939514,
+    955476870,
+    2698156268,
+    35240468,
+    2655147757,
+    3764205609,
+    3802564010,
+    170690025,
+    2311941439,
+    3181546731,
+    3866587616,
+    3648138580,
+    93914936,
+    170378107,
+    2120623674,
+    1064945649,
+    1618754372,
+    244668133,
+    247698428,
+    3669223677,
+    470277359,
+    1781765116,
+    1691572958,
+    1373856501,
+    2668769415,
+    1087394637,
+    1009983433,
+    2180701723,
+    4008405264,
+    2831059514,
+    2645120714,
+    2649103430,
+    2664825925,
+    790502615,
+    1739837626,
+    2293247016,
+    1784648440,
+    1887808856,
+    1788504755,
+    112452386,
+    1979978194,
+    3462674048,
+    2170273742,
+    538168945,
+    753954113,
+    374731234,
+    3715846592,
+    1962971231,
+    1860649552,
+    1378082995,
+    665789406,
+    1717555224,
+    139011596,
+    1375043498,
+    1618544981,
+    1889460471,
+    2262321736,
+    1788301425,
+    1652168174,
+    2668680621,
+    2636946065,
+    2856623532,
+    2759951687,
+    959681532,
+    3209399506,
+    3055195668,
+    1227221002,
+    508217552,
+    3289969989,
+    243178923,
+    2956189845,
+    3075866530,
+    2274779301,
+    3940720663,
+    3998230222,
+    1178317551,
+    4016096296,
+    1545450160,
+    2842919847,
+    314809953,
+    2952850186,
+    3747079365,
+    4147239510,
+    169135842,
+    1332643570,
+    2994529201,
+    973521782,
+    1584369690,
+    1043738701,
+    2851900832,
+    290391815,
+    283209196,
+    2468230023,
+    1164221089,
+    1991787192,
+    3358097187,
+    51041423,
+    52882140,
+    2339018837,
+    2053214130,
+    3757479030,
+    158160339,
+    853200279,
+    1986584654,
+    438318340,
+    827246872,
+    3299488628,
+    2924263085,
+    3472029049,
+    2736844435,
+    677668732,
+    604894932,
+    1158021131,
+    1400019344,
+    2268204687,
+    1450415100,
+    3854557817,
+    1543646433,
+    1278448636,
+    342615870,
+    1554194368,
+    3080024605,
+    3423702268,
+    1675764636,
+    1622381564,
+    2078849875,
+    2113115132,
+    1380160211,
+    3132876285,
+    125015036,
+    269576093,
+    94145952,
+    2777172031,
+    2683080096,
+    3812456892,
+    488500848,
+    3270430997,
+    2895151306,
+    116376005,
+    400248103,
+    406044930,
+    1616846013,
+    10142671,
+    763027711,
+    225200779,
+    1062250709,
+    2013867381,
+    2113506324,
+    1692932387,
+    1827244161,
+    3124618210,
+    2096472894,
+    2924146124,
+    2128251367,
+    2433358586,
+    1939359710,
+    2593325766,
+    2879917723,
+    694743357,
+    2902069960,
+    220008971,
+    3090408469,
+    917019124,
+    1705716306,
+    3263901372,
+    3347863687,
+    3447882276,
+    1661163736,
+    3617689692,
+    3928555688,
+    1057578789,
+    435256475,
+    4101009465,
+    1941403425,
+    198967948,
+    3733675151,
+    2043684541,
+    3517169445,
+    2226776400,
+    2853403709,
+    529383565,
+    2807448986,
+    4234287173,
+    1019457583,
+    1022544883,
+    2493146691,
+    1054461787,
+    1008886329,
+    1136775085,
+    1191015885,
+    1196280518,
+    1979847999,
+    50385656,
+    1918742169,
+    3999472204,
+    3697687030,
+    2220475432,
+    2358141757,
+    2360004627,
+    4245257809,
+    236660303,
+    429277936,
+    342159236,
+    2622612602,
+    371428004,
+    373079619,
+    643418617,
+    2095027856,
+    1071164424,
+    1136911283,
+    1548491889,
+    2169307971,
+    375530199,
+    1510422521,
+    3151638847,
+    1698730948,
+    2231688008,
+    2604576561,
+    2771938750,
+    2996594997,
+    289648234,
+    348584153,
+    2748350697,
+    2926633629,
+    2123683379,
+    369686787,
+    742917749,
+    3538158875,
+    2937761472,
+    1545298048,
+    1321616112,
+    2855506940,
+    900522183,
+    1578775276,
+    2217833278,
+    2012838864,
+    3753486980,
+    2839765116,
+    2464905186,
+    2621255555,
+    1305703280,
+    861753115,
+    3319278167,
+    3063300848,
+    149720480,
+    1082941229,
+    3337532056,
+    2248357849,
+    3675926744,
+    1508550646,
+    2289803479,
+    3456899824,
+    3931641900,
+    3970432934,
+    3419674548,
+    1093210099,
+    456043370,
+    848380423,
+    1287304304,
+    1526654696,
+    2055664760,
+    1373166395,
+    4291477370,
+    2195550588,
+    2847102741,
+    3399062057,
+    1641565587,
+    2888753905,
+    3579593979,
+    3653059026,
+    3757851979,
+    2922615804,
+    2919796598,
+    1553476262,
+    2566666743,
+    3759503594,
+    550831114,
+    3761155209,
+    3762806824,
+    3902853271,
+    4140081844,
+    14244860,
+    3847846774,
+    150820676,
+    1278818058,
+    850592577,
+    1206571206,
+    1734446471,
+    2117320444,
+    1382106590,
+    2436009347,
+    2118972059,
+    2951272396,
+    36096192,
+    117998987,
+    473485679,
+    2244928358,
+    476788909,
+    3489269251,
+    610429940,
+    480092139,
+    481743754,
+    871966503,
+    918189168,
+    601656217,
+    933769938,
+    939671928,
+    1799299383,
+    3312467582,
+    1149665466,
+    3006548167,
+    1310740861,
+    3602693817,
+    1461645203,
+    3367691969,
+    1800404122,
+    3486057732,
+    1862284649,
+    2076833303,
+    2213411495,
+    2805256437,
+    3927915220,
+    3000904950,
+    2094647776,
+    3333131702,
+    1315613425,
+    3752211294,
+    603915804,
+    3505028338,
+    663258455,
+    3322500634,
+    1612225949,
+    3606320646,
+    157110413,
+    1352397672,
+    3861006967,
+    452208841,
+    18776483,
+    1058429216,
+    37009196,
+    564884461,
+    876864198,
+    2952260510,
+    2860348412,
+    928261291,
+    1164724902,
+    2775815164,
+    1332774287,
+    780957373,
+    939415664,
+    1513770932,
+    788046331,
+    1692600167,
+    4069810315,
+    673708384,
+    4024252457,
+    1932614728,
+    2148510256,
+    3131224670,
+    2388524817,
+    2460489993,
+    2676385521,
+    826214242,
+    3692647551,
+    3063508455,
+    3071766530,
+    2063832060,
+    1525861001,
+    3073418145,
+    837715723,
+    3075069760,
+    3076721375,
+    3078372990,
+    983243705,
+    3083327835,
+    171307615,
+    1824016656,
+    3084979450,
+    1310404265,
+    1775308984,
+    3114708520,
+    3116360135,
+    3121314980,
+    3134527900,
+    1691646294,
+    2804281092,
+    97231530,
+    3136179515,
+    3204260786,
+    3276225962,
+    1220749418,
+    3588205699,
+    3874089391,
+    4044115788,
+    3268751013,
+    743407979,
+    166253838,
+    1356063462,
+    1368383673,
+    2279700640,
+    2130747644,
+    3945795573,
+    2780898906,
+    3635542517,
+    425022309,
+    517919178,
+    4061558677,
+    2190437442,
+    543621065,
+    753756604,
+    2500819054,
+    1004589179,
+    1165671422,
+    30433743,
+    3444275347,
+    1335363438,
+    1913735398,
+    1265998516,
+    3829325073,
+    3662767579,
+    463084678,
+    1351676723,
+    1391866096,
+    3398925952,
+    1631216488,
+    815757910,
+    1915438939,
+    2427834344,
+    1445161581,
+    1890300748,
+    2864863800,
+    1961990747,
+    575205902,
+    2037710159,
+    2037814253,
+    617312262,
+    3732916270,
+    783918780,
+    2257843797,
+    2096388952,
+    2338272340,
+    1434223270,
+    578132535,
+    1980341560,
+    1002144380,
+    3244716568,
+    4258414038,
+    3271748023,
+    3304438238,
+    3717523241,
+    3370185097,
+    3435931956,
+    1957265068,
+    3602522282,
+    2547657777,
+    439998433,
+    3838648480,
+    3913593633,
+    3989799199,
+    906176560,
+    1894133125,
+    4046301857,
+    4242327928,
+    630592085,
+    2693892518,
+    4292991777,
+    545678922,
+    125792961,
+    3015046341,
+    132755933,
+    2615111110,
+    1570165302,
+    1440646342,
+    436066778,
+    565233904,
+    600906020,
+    602222721,
+    3951925872,
+    1496901698,
+    1522901980,
+    2785441472,
+    3041450802,
+    1637661947,
+    2127660080,
+    3487022798,
+    2269114589,
+    1314834580,
+    2315690100,
+    3817149113,
+    4091670162,
+    1431749301,
+    1858116930,
+    2213946343,
+    2225172640,
+    2263866576,
+    2727022058,
+    2752967311,
+    2864705739,
+    3052439312,
+    3510257966,
+    2614053317,
+    3297860332,
+    3670298840,
+    3732709413,
+    3788324110,
+    4098876453,
+    4290374884,
+    1623013158,
+    3381478137,
+    17185761,
+    3931288033,
+    2890638791,
+    330388453,
+    346929928,
+    2022347217,
+    4083347580,
+    533021259,
+    564302770,
+    1917602962,
+    680157484,
+    3264086791,
+    3727034815,
+    798549062,
+    3068463300,
+    669812542,
+    1965902997,
+    2311072371,
+    3079287749,
+    2542834724,
+    1587730355,
+    2558655180,
+    1838763297,
+    4172568578,
+    2160380860,
+    2950446516,
+    1830851200,
+    3214537066,
+    3234673086,
+    3652695478,
+    3103302036,
+    3465954368,
+    4180570743,
+    3534518722,
+    371186900,
+    4091394002,
+    1013756921,
+    443558693,
+    591140762,
+    656610661,
+    2064733527,
+    3808408202,
+    983299427,
+    4217306348,
+    1164218401,
+    2036361232,
+    3237903670,
+    2970183398,
+    2293637521,
+    135920445,
+    1596005536,
+    868652905,
+    1191735827,
+    3987079331,
+    1365842164,
+    1508074873,
+    1642818143,
+    3436143898,
+    4105051793,
+    1863199739,
+    3425841570,
+    1070791291,
+    2135340676,
+    2639720559,
+    3364388739,
+    3797761273,
+    2092100514,
+    2098706974,
+    2329992200,
+    414444763,
+    2759250216,
+    2913136690,
+    3012980338,
+    3327770644,
+    4128942283,
+    3362344229,
+    161668409,
+    3401762422,
+    2852854788,
+    4237092412,
+    1245448751,
+    3702405475,
+    918849409,
+    3829682756,
+    1612361408,
+    255302575,
+    414620710,
+    386293029,
+    618761615,
+    686024761,
+    744062262,
+    1502028603,
+    1543798545,
+    1641415225,
+    1548121999,
+    2257971049,
+    2124837447,
+    878733439,
+    2340670452,
+    2674090849,
+    3118011750,
+    2816338013,
+    178571546,
+    2841008029,
+    3249261197,
+    370232173,
+    4092487128,
+    3787567939,
+    3898287302,
+    4142016703,
+    4285779501,
+    30663912,
+    151672195,
+    180913835,
+    3534235309,
+    34183582,
+    4083161638,
+    651464351,
+    1410311776,
+    371621315,
+    421602934,
+    458937500,
+    2710583246,
+    712168842,
+    730943059,
+    1519723107,
+    875212982,
+    1247793383,
+    4217322139,
+    989813600,
+    1057606514,
+    3764662384,
+    1443547269,
+    3066811685,
+    3598957382,
+    1791427568,
+    1171541710,
+    3930727258,
+    1473799048,
+    1296054774,
+    1747355813,
+    765238787,
+    2023008475,
+    1190147516,
+    2344328209,
+    2495155989,
+    2577859137,
+    2857814560,
+    3127329373,
+    3296722158,
+    2773229577,
+    3376009661,
+    3450001968,
+    920941800,
+    3526837441,
+    3858973601,
+    1702168830,
+    4088613871,
+    1464587427,
+    223310468,
+    388034151,
+    2346547796,
+    1663234329,
+    1750829822,
+    1967643923,
+    2881302403,
+    2278706468,
+    2326990117,
+    2511346984,
+    3088785099,
+    2616085763,
+    3027500544,
+    3417583519,
+    4178218543,
+    1412908157,
+    797934924,
+    3533637837,
+    1449907751,
+    3362830643,
+    1451831482,
+    2637935122,
+    3070114915,
+    3023287679,
+    551924251,
+    1669930486,
+    46736908,
+    2870852215,
+    1120149824,
+    2923708820,
+    3887377256,
+    3464197236,
+    4241374559,
+    527665290,
+    996663016,
+    885020215,
+    1763758554,
+    3059119137,
+    2555315060,
+    2762094724,
+    2530899578,
+    2770161927,
+    2262137600,
+    3547456240,
+    858902117,
+    1140367371,
+    1215030156,
+    443490822,
+    294390719,
+    3032677281,
+    1917451875,
+    4184019303,
+    3277199633,
+    1271484400,
+    1297294717,
+    3560552546,
+    171494987,
+    195244192,
+    3002890475,
+    1811839150,
+    265392489,
+    1461398554,
+    3205759417,
+    333855951,
+    529068443,
+    660038281,
+    557400685,
+    663341511,
+    930804377,
+    1922045399,
+    716890919,
+    162167595,
+    1654776395,
+    1779143013,
+    1123617794,
+    2984325996,
+    1162789888,
+    1318479490,
+    1235468610,
+    3561562003,
+    1486207619,
+    1551372768,
+    1850331254,
+    3255947500,
+    1037370721,
+    1989327599,
+    2137526937,
+    835638766,
+    2269130237,
+    1962162282,
+    3244209297,
+    2330636993,
+    3095831808,
+    1396344138,
+    2603020391,
+    3434076295,
+    3280064277,
+    2656211099,
+    3335250889,
+    2550961007,
+    3510242586,
+    3536471583,
+    3950980241,
+    4033586023,
+    117250846,
+    3088282680,
+    4041974454,
+    4244540017,
+    1167160774,
+    899320334,
+    1200870684,
+    1752686878,
+    1906988301,
+    3804101227,
+    2575525651,
+    2919787747,
+    3508792859,
+    3548535223,
+    3783756895,
+    3797961332,
+    4043078107,
+    3115038057,
+    2313593054,
+    49456560,
+    592180731,
+    1051471757,
+    1097775533,
+    706238670,
+    877895868,
+    1173092699,
+    1461897718,
+    1767704813,
+    1770165905,
+    1923453688,
+    2212501241,
+    2305269460,
+    2488410748,
+    3782099915,
+    2844616706,
+    3383007207,
+    3392887901,
+    504514034,
+    3765247327,
+    1000070091,
+    3727494858,
+    3657635382,
+    3839047923,
+    3886529747,
+    4069720347,
+    4164704452,
+    342197850,
+    3540244297,
+    2513230733,
+    4117704995,
+    3367298820,
+    2680283743,
+    3119663365,
+    3697738938,
+    545363837,
+    163402553,
+    5908395,
+    129135650,
+    2289183712,
+    200922300,
+    761731755,
+    894529125,
+    1086964761,
+    1168927492,
+    2100052708,
+    2438466459,
+    3390051757,
+    2498042266,
+    2557754096,
+    2600961503,
+    487719832,
+    703543228,
+    2726532092,
+    4199470013,
+    3142155593,
+    2550501832,
+    4076840151,
+    200553094,
+    380957745,
+    572905105,
+    462664429,
+    1466804584,
+    330249537,
+    2605012269,
+    491456522,
+    4126287524,
+    502863753,
+    952536201,
+    3510682541,
+    1137442027,
+    1665981878,
+    1761469971,
+    3085467405,
+    2045285083,
+    796985462,
+    3433956341,
+    2217966239,
+    2183547611,
+    2279273489,
+    1916983087,
+    2348676810,
+    2403632109,
+    2409539315,
+    545986953,
+    176166202,
+    2477389837,
+    2573160348,
+    2796513469,
+    3972309363,
+    528662843,
+    1038982109,
+    1125913837,
+    1318081294,
+    1417425499,
+  };
+  return descriptors_with_coding_scheme;
+}
diff --git a/tools/link/linker.cpp b/tools/link/linker.cpp
new file mode 100644
index 0000000..6549be5
--- /dev/null
+++ b/tools/link/linker.cpp
@@ -0,0 +1,150 @@
+// Copyright (c) 2017 Pierre Moreau
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <cstring>
+#include <iostream>
+#include <vector>
+
+#include "source/spirv_target_env.h"
+#include "spirv-tools/libspirv.hpp"
+#include "spirv-tools/linker.hpp"
+#include "tools/io.h"
+
+void print_usage(char* argv0) {
+  printf(
+      R"(%s - Link SPIR-V binary files together.
+
+USAGE: %s [options] <filename> [<filename> ...]
+
+The SPIR-V binaries are read from the different <filename>.
+
+NOTE: The linker is a work in progress.
+
+Options:
+  -h, --help       Print this help.
+  -o               Name of the resulting linked SPIR-V binary.
+  --create-library Link the binaries into a library, keeping all exported symbols.
+  --version        Display linker version information
+  --target-env     {vulkan1.0|spv1.0|spv1.1|spv1.2|opencl2.1|opencl2.2}
+                   Use Vulkan1.0/SPIR-V1.0/SPIR-V1.1/SPIR-V1.2/OpenCL-2.1/OpenCL2.2 validation rules.
+)",
+      argv0, argv0);
+}
+
+int main(int argc, char** argv) {
+  std::vector<const char*> inFiles;
+  const char* outFile = nullptr;
+  spv_target_env target_env = SPV_ENV_UNIVERSAL_1_0;
+  spvtools::LinkerOptions options;
+  bool continue_processing = true;
+  int return_code = 0;
+
+  for (int argi = 1; continue_processing && argi < argc; ++argi) {
+    const char* cur_arg = argv[argi];
+    if ('-' == cur_arg[0]) {
+      if (0 == strcmp(cur_arg, "-o")) {
+        if (argi + 1 < argc) {
+          if (!outFile) {
+            outFile = argv[++argi];
+          } else {
+            fprintf(stderr, "error: More than one output file specified\n");
+            continue_processing = false;
+            return_code = 1;
+          }
+        } else {
+          fprintf(stderr, "error: Missing argument to %s\n", cur_arg);
+          continue_processing = false;
+          return_code = 1;
+        }
+      } else if (0 == strcmp(cur_arg, "--create-library")) {
+        options.SetCreateLibrary(true);
+      } else if (0 == strcmp(cur_arg, "--version")) {
+        printf("%s\n", spvSoftwareVersionDetailsString());
+        // TODO(dneto): Add OpenCL 2.2 at least.
+        printf("Targets:\n  %s\n  %s\n  %s\n",
+               spvTargetEnvDescription(SPV_ENV_UNIVERSAL_1_1),
+               spvTargetEnvDescription(SPV_ENV_VULKAN_1_0),
+               spvTargetEnvDescription(SPV_ENV_UNIVERSAL_1_2));
+        continue_processing = false;
+        return_code = 0;
+      } else if (0 == strcmp(cur_arg, "--help") || 0 == strcmp(cur_arg, "-h")) {
+        print_usage(argv[0]);
+        continue_processing = false;
+        return_code = 0;
+      } else if (0 == strcmp(cur_arg, "--target-env")) {
+        if (argi + 1 < argc) {
+          const auto env_str = argv[++argi];
+          if (!spvParseTargetEnv(env_str, &target_env)) {
+            fprintf(stderr, "error: Unrecognized target env: %s\n", env_str);
+            continue_processing = false;
+            return_code = 1;
+          }
+        } else {
+          fprintf(stderr, "error: Missing argument to --target-env\n");
+          continue_processing = false;
+          return_code = 1;
+        }
+      }
+    } else {
+      inFiles.push_back(cur_arg);
+    }
+  }
+
+  // Exit if command line parsing was not successful.
+  if (!continue_processing) {
+    return return_code;
+  }
+
+  if (inFiles.empty()) {
+    fprintf(stderr, "error: No input file specified\n");
+    return 1;
+  }
+
+  std::vector<std::vector<uint32_t>> contents(inFiles.size());
+  for (size_t i = 0u; i < inFiles.size(); ++i) {
+    if (!ReadFile<uint32_t>(inFiles[i], "rb", &contents[i])) return 1;
+  }
+
+  spvtools::Linker linker(target_env);
+  linker.SetMessageConsumer([](spv_message_level_t level, const char*,
+                               const spv_position_t& position,
+                               const char* message) {
+    switch (level) {
+      case SPV_MSG_FATAL:
+      case SPV_MSG_INTERNAL_ERROR:
+      case SPV_MSG_ERROR:
+        std::cerr << "error: " << position.index << ": " << message
+                  << std::endl;
+        break;
+      case SPV_MSG_WARNING:
+        std::cout << "warning: " << position.index << ": " << message
+                  << std::endl;
+        break;
+      case SPV_MSG_INFO:
+        std::cout << "info: " << position.index << ": " << message << std::endl;
+        break;
+      default:
+        break;
+    }
+  });
+
+  std::vector<uint32_t> linkingResult;
+  bool succeed = linker.Link(contents, linkingResult, options);
+
+  if (!WriteFile<uint32_t>(outFile, "wb", linkingResult.data(),
+                           linkingResult.size()))
+    return 1;
+
+  return !succeed;
+}
diff --git a/tools/opt/opt.cpp b/tools/opt/opt.cpp
index f8bd520..dd44d24 100644
--- a/tools/opt/opt.cpp
+++ b/tools/opt/opt.cpp
@@ -12,8 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include <cstring>
 #include <algorithm>
+#include <cassert>
+#include <cstring>
+#include <fstream>
 #include <iostream>
 #include <memory>
 #include <sstream>
@@ -27,6 +29,36 @@
 
 using namespace spvtools;
 
+namespace {
+
+// Status and actions to perform after parsing command-line arguments.
+enum OptActions { OPT_CONTINUE, OPT_STOP };
+
+struct OptStatus {
+  OptActions action;
+  int code;
+};
+
+std::string GetListOfPassesAsString(const spvtools::Optimizer& optimizer) {
+  std::stringstream ss;
+  for (const auto& name : optimizer.GetPassNames()) {
+    ss << "\n\t\t" << name;
+  }
+  return ss.str();
+}
+
+std::string GetOptimizationPasses() {
+  spvtools::Optimizer optimizer(SPV_ENV_UNIVERSAL_1_2);
+  optimizer.RegisterPerformancePasses();
+  return GetListOfPassesAsString(optimizer);
+}
+
+std::string GetSizePasses() {
+  spvtools::Optimizer optimizer(SPV_ENV_UNIVERSAL_1_2);
+  optimizer.RegisterSizePasses();
+  return GetListOfPassesAsString(optimizer);
+}
+
 void PrintUsage(const char* program) {
   printf(
       R"(%s - Optimize a SPIR-V binary file.
@@ -67,6 +99,10 @@
   --compact-ids
                Remap result ids to a compact range starting from %%1 and without
                any gaps.
+  --cfg-cleanup
+               Cleanup the control flow graph. This will remove any unnecessary
+               code from the CFG like unreachable code. Performed on entry
+               point call tree functions and exported functions.
   --inline-entry-points-exhaustive
                Exhaustively inline all function calls in entry point call tree
                functions. Currently does not inline calls to functions with
@@ -108,19 +144,272 @@
                Convert conditional branches with constant condition to the
                indicated unconditional brranch. Delete all resulting dead
                code. Performed only on entry point call tree functions.
+  --eliminate-dead-functions
+               Deletes functions that cannot be reached from entry points or
+               exported functions.
   --merge-blocks
                Join two blocks into a single block if the second has the
                first as its only predecessor. Performed only on entry point
                call tree functions.
-  -h, --help   
+  --strength-reduction
+               Replaces instructions with equivalent and less expensive ones.
+  --eliminate-dead-variables
+               Deletes module scope variables that are not referenced.
+  -O
+               Optimize for performance. Apply a sequence of transformations
+               in an attempt to improve the performance of the generated
+               code. For this version of the optimizer, this flag is equivalent
+               to specifying the following optimization code names:
+               %s
+  -Os
+               Optimize for size. Apply a sequence of transformations in an
+               attempt to minimize the size of the generated code. For this
+               version of the optimizer, this flag is equivalent to specifying
+               the following optimization code names:
+               %s
+
+               NOTE: The specific transformations done by -O and -Os change
+                     from release to release.
+  -Oconfig=<file>
+               Apply the sequence of transformations indicated in <file>.
+               This file contains a sequence of strings separated by whitespace
+               (tabs, newlines or blanks). Each string is one of the flags
+               accepted by spirv-opt. Optimizations will be applied in the
+               sequence they appear in the file. This is equivalent to
+               specifying all the flags on the command line. For example,
+               given the file opts.cfg with the content:
+
+                --inline-entry-points-exhaustive
+                --eliminate-dead-code-aggressive
+
+               The following two invocations to spirv-opt are equivalent:
+
+               $ spirv-opt -Oconfig=opts.cfg program.spv
+
+               $ spirv-opt --inline-entry-points-exhaustive \
+                    --eliminate-dead-code-aggressive program.spv
+
+               Lines starting with the character '#' in the configuration
+               file indicate a comment and will be ignored.
+
+               The -O, -Os, and -Oconfig flags act as macros. Using one of them
+               is equivalent to explicitly inserting the underlying flags at
+               that position in the command line. For example, the invocation
+               'spirv-opt --merge-blocks -O ...' applies the transformation
+               --merge-blocks followed by all the transformations implied by
+               -O.
+  -h, --help
                Print this help.
-  --version    
+  --version
                Display optimizer version information.
 )",
-      program, program);
+      program, program, GetOptimizationPasses().c_str(),
+      GetSizePasses().c_str());
 }
 
-int main(int argc, char** argv) {
+// Reads command-line flags  the file specified in |oconfig_flag|. This string
+// is assumed to have the form "-Oconfig=FILENAME". This function parses the
+// string and extracts the file name after the '=' sign.
+//
+// Flags found in |FILENAME| are pushed at the end of the vector |file_flags|.
+//
+// This function returns true on success, false on failure.
+bool ReadFlagsFromFile(const char* oconfig_flag,
+                       std::vector<std::string>* file_flags) {
+  const char* fname = strchr(oconfig_flag, '=');
+  if (fname == nullptr || fname[0] != '=') {
+    fprintf(stderr, "error: Invalid -Oconfig flag %s\n", oconfig_flag);
+    return false;
+  }
+  fname++;
+
+  std::ifstream input_file;
+  input_file.open(fname);
+  if (input_file.fail()) {
+    fprintf(stderr, "error: Could not open file '%s'\n", fname);
+    return false;
+  }
+
+  while (!input_file.eof()) {
+    std::string flag;
+    input_file >> flag;
+    if (flag.length() > 0 && flag[0] != '#') {
+      file_flags->push_back(flag);
+    }
+  }
+
+  return true;
+}
+
+OptStatus ParseFlags(int argc, const char** argv, Optimizer* optimizer,
+                     const char** in_file, const char** out_file);
+
+// Parses and handles the -Oconfig flag. |prog_name| contains the name of
+// the spirv-opt binary (used to build a new argv vector for the recursive
+// invocation to ParseFlags). |opt_flag| contains the -Oconfig=FILENAME flag.
+// |optimizer|, |in_file| and |out_file| are as in ParseFlags.
+//
+// This returns the same OptStatus instance returned by ParseFlags.
+OptStatus ParseOconfigFlag(const char* prog_name, const char* opt_flag,
+                           Optimizer* optimizer, const char** in_file,
+                           const char** out_file) {
+  std::vector<std::string> flags;
+  flags.push_back(prog_name);
+
+  std::vector<std::string> file_flags;
+  if (!ReadFlagsFromFile(opt_flag, &file_flags)) {
+    fprintf(stderr,
+            "error: Could not read optimizer flags from configuration file\n");
+    return {OPT_STOP, 1};
+  }
+  flags.insert(flags.end(), file_flags.begin(), file_flags.end());
+
+  const char** new_argv = new const char*[flags.size()];
+  for (size_t i = 0; i < flags.size(); i++) {
+    if (flags[i].find("-Oconfig=") != std::string::npos) {
+      fprintf(stderr,
+              "error: Flag -Oconfig= may not be used inside the configuration "
+              "file\n");
+      return {OPT_STOP, 1};
+    }
+    new_argv[i] = flags[i].c_str();
+  }
+
+  return ParseFlags(static_cast<int>(flags.size()), new_argv, optimizer,
+                    in_file, out_file);
+}
+
+// Parses command-line flags. |argc| contains the number of command-line flags.
+// |argv| points to an array of strings holding the flags. |optimizer| is the
+// Optimizer instance used to optimize the program.
+//
+// On return, this function stores the name of the input program in |in_file|.
+// The name of the output file in |out_file|. The return value indicates whether
+// optimization should continue and a status code indicating an error or
+// success.
+OptStatus ParseFlags(int argc, const char** argv, Optimizer* optimizer,
+                     const char** in_file, const char** out_file) {
+  for (int argi = 1; argi < argc; ++argi) {
+    const char* cur_arg = argv[argi];
+    if ('-' == cur_arg[0]) {
+      if (0 == strcmp(cur_arg, "--version")) {
+        printf("%s\n", spvSoftwareVersionDetailsString());
+        return {OPT_STOP, 0};
+      } else if (0 == strcmp(cur_arg, "--help") || 0 == strcmp(cur_arg, "-h")) {
+        PrintUsage(argv[0]);
+        return {OPT_STOP, 0};
+      } else if (0 == strcmp(cur_arg, "-o")) {
+        if (!*out_file && argi + 1 < argc) {
+          *out_file = argv[++argi];
+        } else {
+          PrintUsage(argv[0]);
+          return {OPT_STOP, 1};
+        }
+      } else if (0 == strcmp(cur_arg, "--strip-debug")) {
+        optimizer->RegisterPass(CreateStripDebugInfoPass());
+      } else if (0 == strcmp(cur_arg, "--set-spec-const-default-value")) {
+        if (++argi < argc) {
+          auto spec_ids_vals =
+              opt::SetSpecConstantDefaultValuePass::ParseDefaultValuesString(
+                  argv[argi]);
+          if (!spec_ids_vals) {
+            fprintf(stderr,
+                    "error: Invalid argument for "
+                    "--set-spec-const-default-value: %s\n",
+                    argv[argi]);
+            return {OPT_STOP, 1};
+          }
+          optimizer->RegisterPass(
+              CreateSetSpecConstantDefaultValuePass(std::move(*spec_ids_vals)));
+        } else {
+          fprintf(
+              stderr,
+              "error: Expected a string of <spec id>:<default value> pairs.");
+          return {OPT_STOP, 1};
+        }
+      } else if (0 == strcmp(cur_arg, "--freeze-spec-const")) {
+        optimizer->RegisterPass(CreateFreezeSpecConstantValuePass());
+      } else if (0 == strcmp(cur_arg, "--inline-entry-points-exhaustive")) {
+        optimizer->RegisterPass(CreateInlineExhaustivePass());
+      } else if (0 == strcmp(cur_arg, "--inline-entry-points-opaque")) {
+        optimizer->RegisterPass(CreateInlineOpaquePass());
+      } else if (0 == strcmp(cur_arg, "--convert-local-access-chains")) {
+        optimizer->RegisterPass(CreateLocalAccessChainConvertPass());
+      } else if (0 == strcmp(cur_arg, "--eliminate-dead-code-aggressive")) {
+        optimizer->RegisterPass(CreateAggressiveDCEPass());
+      } else if (0 == strcmp(cur_arg, "--eliminate-insert-extract")) {
+        optimizer->RegisterPass(CreateInsertExtractElimPass());
+      } else if (0 == strcmp(cur_arg, "--eliminate-local-single-block")) {
+        optimizer->RegisterPass(CreateLocalSingleBlockLoadStoreElimPass());
+      } else if (0 == strcmp(cur_arg, "--eliminate-local-single-store")) {
+        optimizer->RegisterPass(CreateLocalSingleStoreElimPass());
+      } else if (0 == strcmp(cur_arg, "--merge-blocks")) {
+        optimizer->RegisterPass(CreateBlockMergePass());
+      } else if (0 == strcmp(cur_arg, "--eliminate-dead-branches")) {
+        optimizer->RegisterPass(CreateDeadBranchElimPass());
+      } else if (0 == strcmp(cur_arg, "--eliminate-dead-functions")) {
+        optimizer->RegisterPass(CreateEliminateDeadFunctionsPass());
+      } else if (0 == strcmp(cur_arg, "--eliminate-local-multi-store")) {
+        optimizer->RegisterPass(CreateLocalMultiStoreElimPass());
+      } else if (0 == strcmp(cur_arg, "--eliminate-common-uniform")) {
+        optimizer->RegisterPass(CreateCommonUniformElimPass());
+      } else if (0 == strcmp(cur_arg, "--eliminate-dead-const")) {
+        optimizer->RegisterPass(CreateEliminateDeadConstantPass());
+      } else if (0 == strcmp(cur_arg, "--eliminate-dead-variables")) {
+        optimizer->RegisterPass(CreateDeadVariableEliminationPass());
+      } else if (0 == strcmp(cur_arg, "--fold-spec-const-op-composite")) {
+        optimizer->RegisterPass(CreateFoldSpecConstantOpAndCompositePass());
+      } else if (0 == strcmp(cur_arg, "--strength-reduction")) {
+        optimizer->RegisterPass(CreateStrengthReductionPass());
+      } else if (0 == strcmp(cur_arg, "--unify-const")) {
+        optimizer->RegisterPass(CreateUnifyConstantPass());
+      } else if (0 == strcmp(cur_arg, "--flatten-decorations")) {
+        optimizer->RegisterPass(CreateFlattenDecorationPass());
+      } else if (0 == strcmp(cur_arg, "--compact-ids")) {
+        optimizer->RegisterPass(CreateCompactIdsPass());
+      } else if (0 == strcmp(cur_arg, "--cfg-cleanup")) {
+        optimizer->RegisterPass(CreateCFGCleanupPass());
+      } else if (0 == strcmp(cur_arg, "-O")) {
+        optimizer->RegisterPerformancePasses();
+      } else if (0 == strcmp(cur_arg, "-Os")) {
+        optimizer->RegisterSizePasses();
+      } else if (0 == strncmp(cur_arg, "-Oconfig=", sizeof("-Oconfig=") - 1)) {
+        OptStatus status =
+            ParseOconfigFlag(argv[0], cur_arg, optimizer, in_file, out_file);
+        if (status.action != OPT_CONTINUE) {
+          return status;
+        }
+      } else if ('\0' == cur_arg[1]) {
+        // Setting a filename of "-" to indicate stdin.
+        if (!*in_file) {
+          *in_file = cur_arg;
+        } else {
+          fprintf(stderr, "error: More than one input file specified\n");
+          return {OPT_STOP, 1};
+        }
+      } else {
+        fprintf(
+            stderr,
+            "error: Unknown flag '%s'. Use --help for a list of valid flags\n",
+            cur_arg);
+        return {OPT_STOP, 1};
+      }
+    } else {
+      if (!*in_file) {
+        *in_file = cur_arg;
+      } else {
+        fprintf(stderr, "error: More than one input file specified\n");
+        return {OPT_STOP, 1};
+      }
+    }
+  }
+
+  return {OPT_CONTINUE, 0};
+}
+
+}  // namespace
+
+int main(int argc, const char** argv) {
   const char* in_file = nullptr;
   const char* out_file = nullptr;
 
@@ -134,98 +423,9 @@
               << std::endl;
   });
 
-  for (int argi = 1; argi < argc; ++argi) {
-    const char* cur_arg = argv[argi];
-    if ('-' == cur_arg[0]) {
-      if (0 == strcmp(cur_arg, "--version")) {
-        printf("%s\n", spvSoftwareVersionDetailsString());
-        return 0;
-      } else if (0 == strcmp(cur_arg, "--help") || 0 == strcmp(cur_arg, "-h")) {
-        PrintUsage(argv[0]);
-        return 0;
-      } else if (0 == strcmp(cur_arg, "-o")) {
-        if (!out_file && argi + 1 < argc) {
-          out_file = argv[++argi];
-        } else {
-          PrintUsage(argv[0]);
-          return 1;
-        }
-      } else if (0 == strcmp(cur_arg, "--strip-debug")) {
-        optimizer.RegisterPass(CreateStripDebugInfoPass());
-      } else if (0 == strcmp(cur_arg, "--set-spec-const-default-value")) {
-        if (++argi < argc) {
-          auto spec_ids_vals =
-              opt::SetSpecConstantDefaultValuePass::ParseDefaultValuesString(
-                  argv[argi]);
-          if (!spec_ids_vals) {
-            fprintf(stderr,
-                    "error: Invalid argument for "
-                    "--set-spec-const-default-value: %s\n",
-                    argv[argi]);
-            return 1;
-          }
-          optimizer.RegisterPass(
-              CreateSetSpecConstantDefaultValuePass(std::move(*spec_ids_vals)));
-        } else {
-          fprintf(
-              stderr,
-              "error: Expected a string of <spec id>:<default value> pairs.");
-          return 1;
-        }
-      } else if (0 == strcmp(cur_arg, "--freeze-spec-const")) {
-        optimizer.RegisterPass(CreateFreezeSpecConstantValuePass());
-      } else if (0 == strcmp(cur_arg, "--inline-entry-points-exhaustive")) {
-        optimizer.RegisterPass(CreateInlineExhaustivePass());
-      } else if (0 == strcmp(cur_arg, "--inline-entry-points-opaque")) {
-        optimizer.RegisterPass(CreateInlineOpaquePass());
-      } else if (0 == strcmp(cur_arg, "--convert-local-access-chains")) {
-        optimizer.RegisterPass(CreateLocalAccessChainConvertPass());
-      } else if (0 == strcmp(cur_arg, "--eliminate-dead-code-aggressive")) {
-        optimizer.RegisterPass(CreateAggressiveDCEPass());
-      } else if (0 == strcmp(cur_arg, "--eliminate-insert-extract")) {
-        optimizer.RegisterPass(CreateInsertExtractElimPass());
-      } else if (0 == strcmp(cur_arg, "--eliminate-local-single-block")) {
-        optimizer.RegisterPass(CreateLocalSingleBlockLoadStoreElimPass());
-      } else if (0 == strcmp(cur_arg, "--eliminate-local-single-store")) {
-        optimizer.RegisterPass(CreateLocalSingleStoreElimPass());
-      } else if (0 == strcmp(cur_arg, "--merge-blocks")) {
-        optimizer.RegisterPass(CreateBlockMergePass());
-      } else if (0 == strcmp(cur_arg, "--eliminate-dead-branches")) {
-        optimizer.RegisterPass(CreateDeadBranchElimPass());
-      } else if (0 == strcmp(cur_arg, "--eliminate-local-multi-store")) {
-        optimizer.RegisterPass(CreateLocalMultiStoreElimPass());
-      } else if (0 == strcmp(cur_arg, "--eliminate-common-uniform")) {
-        optimizer.RegisterPass(CreateCommonUniformElimPass());
-      } else if (0 == strcmp(cur_arg, "--eliminate-dead-const")) {
-        optimizer.RegisterPass(CreateEliminateDeadConstantPass());
-      } else if (0 == strcmp(cur_arg, "--fold-spec-const-op-composite")) {
-        optimizer.RegisterPass(CreateFoldSpecConstantOpAndCompositePass());
-      } else if (0 == strcmp(cur_arg, "--unify-const")) {
-        optimizer.RegisterPass(CreateUnifyConstantPass());
-      } else if (0 == strcmp(cur_arg, "--flatten-decorations")) {
-        optimizer.RegisterPass(CreateFlattenDecorationPass());
-      } else if (0 == strcmp(cur_arg, "--compact-ids")) {
-        optimizer.RegisterPass(CreateCompactIdsPass());
-      } else if ('\0' == cur_arg[1]) {
-        // Setting a filename of "-" to indicate stdin.
-        if (!in_file) {
-          in_file = cur_arg;
-        } else {
-          fprintf(stderr, "error: More than one input file specified\n");
-          return 1;
-        }
-      } else {
-        PrintUsage(argv[0]);
-        return 1;
-      }
-    } else {
-      if (!in_file) {
-        in_file = cur_arg;
-      } else {
-        fprintf(stderr, "error: More than one input file specified\n");
-        return 1;
-      }
-    }
+  OptStatus status = ParseFlags(argc, argv, &optimizer, &in_file, &out_file);
+  if (status.action == OPT_STOP) {
+    return status.code;
   }
 
   if (out_file == nullptr) {
@@ -234,7 +434,9 @@
   }
 
   std::vector<uint32_t> binary;
-  if (!ReadFile<uint32_t>(in_file, "rb", &binary)) return 1;
+  if (!ReadFile<uint32_t>(in_file, "rb", &binary)) {
+    return 1;
+  }
 
   // Let's do validation first.
   spv_context context = spvContextCreate(target_env);
diff --git a/tools/stats/stats_analyzer.cpp b/tools/stats/stats_analyzer.cpp
index 3f2e7ec..b9d2c66 100644
--- a/tools/stats/stats_analyzer.cpp
+++ b/tools/stats/stats_analyzer.cpp
@@ -19,18 +19,33 @@
 #include <cstring>
 #include <iostream>
 #include <sstream>
+#include <unordered_map>
+#include <unordered_set>
 #include <vector>
 
 #include "spirv/1.2/spirv.h"
 #include "source/enum_string_mapping.h"
+#include "source/comp/markv_model.h"
 #include "source/opcode.h"
 #include "source/operand.h"
 #include "source/spirv_constant.h"
+#include "source/util/huffman_codec.h"
 
 using libspirv::SpirvStats;
+using spvutils::HuffmanCodec;
 
 namespace {
 
+// Signals that the value is not in the coding scheme and a fallback method
+// needs to be used.
+const uint64_t kMarkvNoneOfTheAbove =
+    spvtools::MarkvModel::GetMarkvNoneOfTheAbove();
+
+inline uint32_t CombineOpcodeAndNumOperands(uint32_t opcode,
+                                            uint32_t num_operands) {
+  return opcode | (num_operands << 16);
+}
+
 // Returns all SPIR-V v1.2 opcodes.
 std::vector<uint32_t> GetAllOpcodes() {
   return std::vector<uint32_t>({
@@ -599,23 +614,30 @@
     total += kv.second;
   }
 
+  uint32_t left_out = 0;
+
   for (const auto& kv : stats_.opcode_and_num_operands_hist) {
     const uint32_t count = kv.second;
     const double kFrequentEnoughToAnalyze = 0.001;
-    if (double(count) / double(total) < kFrequentEnoughToAnalyze) continue;
     const uint32_t opcode_and_num_operands = kv.first;
     const uint32_t opcode = opcode_and_num_operands & 0xFFFF;
     const uint32_t num_operands = opcode_and_num_operands >> 16;
 
-    if (opcode == SpvOpTypeStruct)
+    if (opcode == SpvOpTypeStruct ||
+        double(count) / double(total) < kFrequentEnoughToAnalyze) {
+      left_out += count;
       continue;
+    }
 
     out << "    { CombineOpcodeAndNumOperands(SpvOp"
         << spvOpcodeString(SpvOp(opcode))
         << ", " << num_operands << "), " << count << " },\n";
   }
 
-  out << "    { kMarkvNoneOfTheAbove, " << 1 + int(total * 0.05) << " },\n";
+  // Heuristic.
+  const uint32_t none_of_the_above =
+      std::max(1, int(left_out + total * 0.01));
+  out << "    { kMarkvNoneOfTheAbove, " << none_of_the_above << " },\n";
   out << "  });\n}\n";
 }
 
@@ -638,10 +660,9 @@
       total += pair.second;
     }
 
-    out << "  {\n";
-    out << "    std::unique_ptr<HuffmanCodec<uint64_t>> "
-        << "codec(new HuffmanCodec<uint64_t>({\n";
+    uint32_t left_out = 0;
 
+    std::map<uint64_t, uint32_t> processed_hist;
     for (const auto& pair : hist) {
       const uint32_t opcode_and_num_operands = pair.first;
       const uint32_t opcode = opcode_and_num_operands & 0xFFFF;
@@ -654,17 +675,25 @@
       const double posterior_freq = double(count) / double(total);
 
       if (opcode_freq_[opcode] < kFrequentEnoughToAnalyze &&
-          posterior_freq < kFrequentEnoughToAnalyze) continue;
-
-      total += count;
-      out << "      { CombineOpcodeAndNumOperands(SpvOp"
-          << spvOpcodeString(SpvOp(opcode))
-          << ", " << num_operands << "), " << count << " },\n";
+          posterior_freq < kFrequentEnoughToAnalyze) {
+        left_out += count;
+        continue;
+      }
+      processed_hist.emplace(CombineOpcodeAndNumOperands(opcode, num_operands),
+                             count);
     }
 
-    out << "      { kMarkvNoneOfTheAbove, " << 1 + int(total * 0.05) << " },\n";
+    // Heuristic.
+    processed_hist.emplace(kMarkvNoneOfTheAbove,
+                           std::max(1, int(left_out + total * 0.01)));
 
-    out << "    }));\n" << std::endl;
+    HuffmanCodec<uint64_t> codec(processed_hist);
+
+    out << "  {\n";
+    out << "    std::unique_ptr<HuffmanCodec<uint64_t>> "
+        << "codec(new HuffmanCodec<uint64_t>";
+    out << codec.SerializeToText(4);
+    out << ");\n" << std::endl;
     out << "    codecs.emplace(SpvOp" << GetOpcodeString(prev_opcode)
         << ", std::move(codec));\n";
     out << "  }\n\n";
@@ -695,22 +724,31 @@
       total += pair.second;
     }
 
-    out << "  {\n";
-    out << "    std::unique_ptr<HuffmanCodec<std::string>> "
-        << "codec(new HuffmanCodec<std::string>({\n";
+    uint32_t left_out = 0;
+
+    std::map<std::string, uint32_t> processed_hist;
     for (const auto& pair : hist) {
       const uint32_t count = pair.second;
       const double freq = double(count) / double(total);
       const double kStringFrequentEnoughToAnalyze = 0.001;
-      if (freq < kStringFrequentEnoughToAnalyze) continue;
-      out << "      { std::string(\"" << pair.first << "\"), " << count
-          << " },\n";
+      if (freq < kStringFrequentEnoughToAnalyze) {
+        left_out += count;
+        continue;
+      }
+      processed_hist.emplace(pair.first, count);
     }
 
-    out << "      { std::string(\"kMarkvNoneOfTheAbove\"), "
-        << 1 + int(total * 0.05) << " },\n";
+    // Heuristic.
+    processed_hist.emplace("kMarkvNoneOfTheAbove",
+                           std::max(1, int(left_out + total * 0.01)));
 
-    out << "    }));\n" << std::endl;
+    HuffmanCodec<std::string> codec(processed_hist);
+
+    out << "  {\n";
+    out << "    std::unique_ptr<HuffmanCodec<std::string>> "
+        << "codec(new HuffmanCodec<std::string>";
+    out << codec.SerializeToText(4);
+    out << ");\n" << std::endl;
     out << "    codecs.emplace(SpvOp" << spvOpcodeString(SpvOp(opcode))
         << ", std::move(codec));\n";
     out << "  }\n\n";
@@ -741,21 +779,32 @@
       total += pair.second;
     }
 
-    out << "  {\n";
-    out << "    std::unique_ptr<HuffmanCodec<uint64_t>> "
-        << "codec(new HuffmanCodec<uint64_t>({\n";
+    uint32_t left_out = 0;
+
+    std::map<uint64_t, uint32_t> processed_hist;
     for (const auto& pair : hist) {
       const uint32_t word = pair.first;
       const uint32_t count = pair.second;
       const double freq = double(count) / double(total);
-      const double kWordFrequentEnoughToAnalyze = 0.001;
-      if (freq < kWordFrequentEnoughToAnalyze) continue;
-      out << "      { " << word << ", " << count << " },\n";
+      const double kWordFrequentEnoughToAnalyze = 0.003;
+      if (freq < kWordFrequentEnoughToAnalyze) {
+        left_out += count;
+        continue;
+      }
+      processed_hist.emplace(word, count);
     }
 
-    out << "      { kMarkvNoneOfTheAbove, " << 1 + int(total * 0.05) << " },\n";
+    // Heuristic.
+    processed_hist.emplace(kMarkvNoneOfTheAbove,
+                           std::max(1, int(left_out + total * 0.01)));
 
-    out << "    }));\n" << std::endl;
+    HuffmanCodec<uint64_t> codec(processed_hist);
+
+    out << "  {\n";
+    out << "    std::unique_ptr<HuffmanCodec<uint64_t>> "
+        << "codec(new HuffmanCodec<uint64_t>";
+    out << codec.SerializeToText(4);
+    out << ");\n" << std::endl;
     out << "    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOp"
         << spvOpcodeString(SpvOp(opcode))
         << ", " << index << "), std::move(codec));\n";
@@ -773,36 +822,52 @@
       << "  std::map<std::pair<uint32_t, uint32_t>, "
       << "std::unique_ptr<HuffmanCodec<uint64_t>>> codecs;\n";
 
+  std::unordered_set<uint32_t> descriptors_with_coding_scheme;
+
   for (const auto& kv : stats_.operand_slot_id_descriptor_hist) {
     const auto& opcode_and_index = kv.first;
     const uint32_t opcode = opcode_and_index.first;
     const uint32_t index = opcode_and_index.second;
 
-    const double kOpcodeFrequentEnoughToAnalyze = 0.001;
+    const double kOpcodeFrequentEnoughToAnalyze = 0.003;
     if (opcode_freq_[opcode] < kOpcodeFrequentEnoughToAnalyze) continue;
 
     const std::map<uint32_t, uint32_t>& hist = kv.second;
 
+
     uint32_t total = 0;
     for (const auto& pair : hist) {
       total += pair.second;
     }
 
-    out << "  {\n";
-    out << "    std::unique_ptr<HuffmanCodec<uint64_t>> "
-        << "codec(new HuffmanCodec<uint64_t>({\n";
+    uint32_t left_out = 0;
+
+    std::map<uint64_t, uint32_t> processed_hist;
     for (const auto& pair : hist) {
       const uint32_t descriptor = pair.first;
       const uint32_t count = pair.second;
       const double freq = double(count) / double(total);
-      const double kDescriptorFrequentEnoughToAnalyze = 0.005;
-      if (freq < kDescriptorFrequentEnoughToAnalyze) continue;
-      out << "      { " << descriptor << ", " << count << " },\n";
+      const double kDescriptorFrequentEnoughToAnalyze = 0.003;
+      if (freq < kDescriptorFrequentEnoughToAnalyze) {
+        left_out += count;
+        continue;
+      }
+      processed_hist.emplace(descriptor, count);
+      descriptors_with_coding_scheme.insert(descriptor);
     }
 
-    out << "      { kMarkvNoneOfTheAbove, " << 1 + int(total * 0.05) << " },\n";
+    // Heuristic.
+    processed_hist.emplace(kMarkvNoneOfTheAbove,
+                           std::max(1, int(left_out + total * 0.01)));
 
-    out << "    }));\n" << std::endl;
+
+    HuffmanCodec<uint64_t> codec(processed_hist);
+
+    out << "  {\n";
+    out << "    std::unique_ptr<HuffmanCodec<uint64_t>> "
+        << "codec(new HuffmanCodec<uint64_t>";
+    out << codec.SerializeToText(4);
+    out << ");\n" << std::endl;
     out << "    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOp"
         << spvOpcodeString(SpvOp(opcode))
         << ", " << index << "), std::move(codec));\n";
@@ -810,4 +875,12 @@
   }
 
   out << "  return codecs;\n}\n";
+
+  out << "\nstd::unordered_set<uint32_t> GetDescriptorsWithCodingScheme() {\n"
+      << "  std::unordered_set<uint32_t> descriptors_with_coding_scheme = {\n";
+  for (uint32_t descriptor : descriptors_with_coding_scheme) {
+    out << "    " << descriptor << ",\n";
+  }
+  out << "  };\n";
+  out << "  return descriptors_with_coding_scheme;\n}\n";
 }
diff --git a/utils/check_copyright.py b/utils/check_copyright.py
index 08fb082..cc24863 100755
--- a/utils/check_copyright.py
+++ b/utils/check_copyright.py
@@ -29,7 +29,8 @@
 # List of designated copyright owners.
 AUTHORS = ['The Khronos Group Inc.',
            'LunarG Inc.',
-           'Google Inc.']
+           'Google Inc.',
+           'Pierre Moreau']
 CURRENT_YEAR='2017'
 
 YEARS = '(2014-2016|2015-2016|2016|2016-2017|2017)'
diff --git a/utils/generate_grammar_tables.py b/utils/generate_grammar_tables.py
index 67d5210..e58d57b 100755
--- a/utils/generate_grammar_tables.py
+++ b/utils/generate_grammar_tables.py
@@ -17,7 +17,6 @@
 from __future__ import print_function
 
 import errno
-import functools
 import json
 import os.path
 import re
@@ -25,24 +24,38 @@
 # Prefix for all C variables generated by this script.
 PYGEN_VARIABLE_PREFIX = 'pygen_variable'
 
-# Extensions to recognize, but which don't come from the SPIRV-V core grammar.
-NONSTANDARD_EXTENSIONS = [
-    # TODO(dneto): Vendor extension names should really be derived from the
-    # content of .json files.
-    'SPV_AMD_gcn_shader',
-    'SPV_AMD_shader_ballot',
-    'SPV_AMD_shader_explicit_vertex_parameter',
-    'SPV_AMD_shader_trinary_minmax',
-    # The following don't have an extended instruction set grammar file.
-    'SPV_AMD_gpu_shader_half_float',
-    'SPV_AMD_texture_gather_bias_lod',
-    'SPV_AMD_gpu_shader_int16',
-    # Validator would ignore type declaration unique check. Should only be used
-    # for legacy autogenerated test files containing multiple instances of the
-    # same type declaration, if fixing the test by other methods is too
-    # difficult. Shouldn't be used for any other reasons.
-    'SPV_VALIDATOR_ignore_type_decl_unique',
-]
+# Extensions to recognize, but which don't necessarily come from the SPIRV-V
+# core grammar.  Get this list from the SPIR-V registery web page.
+EXTENSIONS_FROM_SPIRV_REGISTRY = """
+SPV_AMD_shader_explicit_vertex_parameter
+SPV_AMD_shader_trinary_minmax
+SPV_AMD_gcn_shader
+SPV_KHR_shader_ballot
+SPV_AMD_shader_ballot
+SPV_AMD_gpu_shader_half_float
+SPV_KHR_shader_draw_parameters
+SPV_KHR_subgroup_vote
+SPV_KHR_16bit_storage
+SPV_KHR_device_group
+SPV_KHR_multiview
+SPV_NVX_multiview_per_view_attributes
+SPV_NV_viewport_array2
+SPV_NV_stereo_view_rendering
+SPV_NV_sample_mask_override_coverage
+SPV_NV_geometry_shader_passthrough
+SPV_AMD_texture_gather_bias_lod
+SPV_KHR_storage_buffer_storage_class
+SPV_KHR_variable_pointers
+SPV_AMD_gpu_shader_int16
+SPV_KHR_post_depth_coverage
+SPV_KHR_shader_atomic_counter_ops
+SPV_EXT_shader_stencil_export
+SPV_EXT_shader_viewport_index_layer
+SPV_AMD_shader_image_load_store_lod
+SPV_AMD_shader_fragment_mask
+"""
+
+
 
 def make_path_to_file(f):
     """Makes all ancestor directories to the given file, if they
@@ -60,6 +73,7 @@
         else:
             raise
 
+
 def compose_capability_list(caps):
     """Returns a string containing a braced list of capabilities as enums.
 
@@ -81,7 +95,8 @@
     Returns:
       a string containing the braced list of extensions named by exts.
     """
-    return "{" + ", ".join(['libspirv::Extension::k{}'.format(e) for e in exts]) + "}"
+    return "{" + ", ".join(
+        ['libspirv::Extension::k{}'.format(e) for e in exts]) + "}"
 
 
 def convert_operand_kind(operand_tuple):
@@ -170,7 +185,6 @@
         self.ref_type_id = 'IdResultType' in operands
         self.def_result_id = 'IdResult' in operands
 
-
     def fix_syntax(self):
         """Fix an instruction's syntax, adjusting for differences between
         the officially released grammar and how SPIRV-Tools uses the grammar.
@@ -180,9 +194,8 @@
             https://github.com/KhronosGroup/SPIRV-Tools/issues/233
         """
         if (self.opname == 'ExtInst'
-            and self.operands[-1] == 'SPV_OPERAND_TYPE_VARIABLE_ID'):
-           self.operands.pop()
-
+                and self.operands[-1] == 'SPV_OPERAND_TYPE_VARIABLE_ID'):
+            self.operands.pop()
 
     def __str__(self):
         template = ['{{"{opname}"', 'SpvOp{opname}', '{caps_mask}',
@@ -317,12 +330,12 @@
     return str(EnumerantInitializer(enumerant, value, caps, exts, params))
 
 
-def generate_enum_operand_kind(enum):
+def generate_enum_operand_kind(enum, version):
     """Returns the C definition for the given operand kind."""
     kind = enum.get('kind')
     assert kind is not None
 
-    name = '{}_{}Entries'.format(PYGEN_VARIABLE_PREFIX, kind)
+    name = '{}_{}Entries_{}'.format(PYGEN_VARIABLE_PREFIX, kind, version)
     entries = ['  {}'.format(generate_enum_operand_kind_entry(e))
                for e in enum.get('enumerants', [])]
 
@@ -335,10 +348,10 @@
     return kind, name, entries
 
 
-def generate_operand_kind_table(enums):
+def generate_operand_kind_table(enums, version):
     """Returns the info table containing all SPIR-V operand kinds."""
     # We only need to output info tables for those operand kinds that are enums.
-    enums = [generate_enum_operand_kind(e)
+    enums = [generate_enum_operand_kind(e, version)
              for e in enums
              if e.get('category') in ['ValueEnum', 'BitEnum']]
     # We have three operand kinds that requires their optional counterpart to
@@ -359,10 +372,10 @@
                      for e in table_entries]
 
     template = [
-        'static const spv_operand_desc_group_t {p}_OperandInfoTable[] = {{',
+        'static const spv_operand_desc_group_t {p}_OperandInfoTable_{v}[] = {{',
         '{enums}', '}};']
     table = '\n'.join(template).format(
-        p=PYGEN_VARIABLE_PREFIX, enums=',\n'.join(table_entries))
+        p=PYGEN_VARIABLE_PREFIX, v=version, enums=',\n'.join(table_entries))
 
     return '\n\n'.join(enum_entries + (table,))
 
@@ -375,7 +388,13 @@
     extensions = sum([item.get('extensions', []) for item in enumerants
                       if item.get('extensions')], [])
 
-    extensions.extend(NONSTANDARD_EXTENSIONS)
+    extensions.extend(EXTENSIONS_FROM_SPIRV_REGISTRY.split())
+
+    # Validator would ignore type declaration unique check. Should only be used
+    # for legacy autogenerated test files containing multiple instances of the
+    # same type declaration, if fixing the test by other methods is too
+    # difficult. Shouldn't be used for any other reasons.
+    extensions.append('SPV_VALIDATOR_ignore_type_decl_unique')
 
     return sorted(set(extensions))
 
@@ -460,7 +479,7 @@
     function += '  switch (capability) {\n'
     template = '    case SpvCapability{capability}:\n' \
         '      return "{capability}";\n'
-    emitted = set() # The values of capabilities we already have emitted
+    emitted = set()  # The values of capabilities we already have emitted
     for capability in get_capabilities(operands):
         value = capability.get('value')
         if value not in emitted:
@@ -485,6 +504,7 @@
 def main():
     import argparse
     parser = argparse.ArgumentParser(description='Generate SPIR-V info tables')
+
     parser.add_argument('--spirv-core-grammar', metavar='<path>',
                         type=str, required=False,
                         help='input JSON grammar file for core SPIR-V '
@@ -497,6 +517,7 @@
                         type=str, required=False, default=None,
                         help='input JSON grammar file for OpenCL extended '
                         'instruction set')
+
     parser.add_argument('--core-insts-output', metavar='<path>',
                         type=str, required=False, default=None,
                         help='output file for core SPIR-V instructions')
@@ -559,9 +580,13 @@
             if args.core_insts_output is not None:
                 make_path_to_file(args.core_insts_output)
                 make_path_to_file(args.operand_kinds_output)
-                print(generate_instruction_table(grammar['instructions'], False),
+                print(generate_instruction_table(
+                        grammar['instructions'], False),
                       file=open(args.core_insts_output, 'w'))
-                print(generate_operand_kind_table(grammar['operand_kinds']),
+                version = '{}_{}'.format(grammar['major_version'],
+                                         grammar['minor_version'])
+                print(generate_operand_kind_table(
+                        grammar['operand_kinds'], version),
                       file=open(args.operand_kinds_output, 'w'))
             if args.extension_enum_output is not None:
                 make_path_to_file(args.extension_enum_output)