Sync with NDK master.
diff --git a/CHANGES b/CHANGES
index 0e76412..653e6df 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,7 +1,28 @@
 Revision history for SPIRV-Tools
 
-v2016.2-dev 2016-07-19
- - Start v2016.2
+v2016.3-dev 2016-08-05
+ - Start v2016.3
+ - Add target environment enums for OpenCL 2.1, OpenCL 2.2,
+   OpenGL 4.0, OpenGL 4.1, OpenGL 4.2, OpenGL 4.3, OpenGL 4.5.
+
+v2016.2 2016-08-05
+ - Validator is incomplete
+   - Checks ID use block is dominated by definition block
+ - Add optimization passes (in API and spirv-opt command)
+   - Strip debug info instructions
+   - Freeze spec constant to their default values
+ - Allow INotEqual as operation for OpSpecConstantOp
+ - Fixes bugs:
+   #270: validator: crash when continue construct is unreachable
+   #279: validator: infinite loop when analyzing some degenerate control
+     flow graphs
+   #286: validator: don't incorrectly generate def-use error for
+         (variable,parent) parameters to OpPhi
+   #290: disassembler: never generate bare % for an identifier
+   #295: validator: def-use dominance check should ignore unreachable uses
+   #276: validator: allow unreachable continue constructs
+   #297: validator: allow an unreachable block to branch to a reachable
+         merge block
 
 v2016.1 2016-07-19
  - Fix https://github.com/KhronosGroup/SPIRV-Tools/issues/261
diff --git a/CMakeLists.txt b/CMakeLists.txt
index bdb9f3b..bb9fe7d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -116,8 +116,8 @@
   # But it still depends on MSVCRT.dll.
   if (${CMAKE_SYSTEM_NAME} MATCHES "Windows")
     if (${CMAKE_CXX_COMPILER_ID} MATCHES "GNU")
-      set_target_properties(${TARGET} PROPERTIES LINK_FLAGS
-	                    -static -static-libgcc -static-libstdc++)
+      set_target_properties(${TARGET} PROPERTIES
+        LINK_FLAGS -static -static-libgcc -static-libstdc++)
     endif()
   endif()
 endfunction()
@@ -138,8 +138,13 @@
 
 # Defaults to OFF if the user didn't set it.
 option(SPIRV_SKIP_EXECUTABLES
-       "Skip building the executable and tests along with the library"
-       ${SPIRV_SKIP_EXECUTABLES})
+  "Skip building the executable and tests along with the library"
+  ${SPIRV_SKIP_EXECUTABLES})
+option(SPIRV_SKIP_TESTS
+  "Skip building tests along with the library" ${SPIRV_SKIP_TESTS})
+if ("${SPIRV_SKIP_EXECUTABLES}")
+  set(SPIRV_SKIP_TESTS ON)
+endif()
 
 add_subdirectory(external)
 
diff --git a/include/spirv-tools/libspirv.h b/include/spirv-tools/libspirv.h
index a3a1647..dc11c2a 100644
--- a/include/spirv-tools/libspirv.h
+++ b/include/spirv-tools/libspirv.h
@@ -352,6 +352,14 @@
   SPV_ENV_UNIVERSAL_1_0,  // SPIR-V 1.0 latest revision, no other restrictions.
   SPV_ENV_VULKAN_1_0,     // Vulkan 1.0 latest revision.
   SPV_ENV_UNIVERSAL_1_1,  // SPIR-V 1.1 latest revision, no other restrictions.
+  SPV_ENV_OPENCL_2_1, // OpenCL 2.1 latest revision.
+  SPV_ENV_OPENCL_2_2, // OpenCL 2.2 latest revision.
+  SPV_ENV_OPENGL_4_0, // OpenGL 4.0 plus GL_ARB_gl_spirv, latest revisions.
+  SPV_ENV_OPENGL_4_1, // OpenGL 4.1 plus GL_ARB_gl_spirv, latest revisions.
+  SPV_ENV_OPENGL_4_2, // OpenGL 4.2 plus GL_ARB_gl_spirv, latest revisions.
+  SPV_ENV_OPENGL_4_3, // OpenGL 4.3 plus GL_ARB_gl_spirv, latest revisions.
+  // There is no variant for OpenGL 4.4.
+  SPV_ENV_OPENGL_4_5, // OpenGL 4.5 plus GL_ARB_gl_spirv, latest revisions.
 } spv_target_env;
 
 // Returns a string describing the given SPIR-V target environment.
diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt
index de227d6..316645e 100644
--- a/source/CMakeLists.txt
+++ b/source/CMakeLists.txt
@@ -103,11 +103,11 @@
   PROPERTIES OBJECT_DEPENDS "${EXTINST_CPP_DEPENDS}")
 
 set(SPIRV_TOOLS_BUILD_VERSION_INC
-	${spirv-tools_BINARY_DIR}/build-version.inc)
+  ${spirv-tools_BINARY_DIR}/build-version.inc)
 set(SPIRV_TOOLS_BUILD_VERSION_INC_GENERATOR
-	${spirv-tools_SOURCE_DIR}/utils/update_build_version.py)
+  ${spirv-tools_SOURCE_DIR}/utils/update_build_version.py)
 set(SPIRV_TOOLS_CHANGES_FILE
-	${spirv-tools_SOURCE_DIR}/CHANGES)
+  ${spirv-tools_SOURCE_DIR}/CHANGES)
 add_custom_command(OUTPUT ${SPIRV_TOOLS_BUILD_VERSION_INC}
    COMMAND ${PYTHON_EXECUTABLE}
            ${SPIRV_TOOLS_BUILD_VERSION_INC_GENERATOR}
@@ -167,10 +167,10 @@
   ${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_ssa.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/val/BasicBlock.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/val/Construct.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/val/Function.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/val/Id.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/val/ValidationState.cpp)
 
 # The software_version.cpp file includes build-version.inc.
diff --git a/source/assembly_grammar.cpp b/source/assembly_grammar.cpp
index b85aa03..11f9bc3 100644
--- a/source/assembly_grammar.cpp
+++ b/source/assembly_grammar.cpp
@@ -150,6 +150,7 @@
     CASE(Select),
     // Comparison
     CASE(IEqual),
+    CASE(INotEqual),
     CASE(ULessThan),
     CASE(SLessThan),
     CASE(UGreaterThan),
@@ -165,8 +166,8 @@
     CASE(InBoundsPtrAccessChain),
 };
 
-// The 58 is determined by counting the opcodes listed in the spec.
-static_assert(58 == sizeof(kOpSpecConstantOpcodes)/sizeof(kOpSpecConstantOpcodes[0]),
+// The 59 is determined by counting the opcodes listed in the spec.
+static_assert(59 == sizeof(kOpSpecConstantOpcodes)/sizeof(kOpSpecConstantOpcodes[0]),
               "OpSpecConstantOp opcode table is incomplete");
 #undef CASE
 // clang-format on
diff --git a/source/binary.h b/source/binary.h
index 923a834..0d9ef28 100644
--- a/source/binary.h
+++ b/source/binary.h
@@ -28,7 +28,8 @@
 #define LIBSPIRV_BINARY_H_
 
 #include "spirv-tools/libspirv.h"
-#include "table.h"
+#include "spirv/1.1/spirv.h"
+#include "spirv_definition.h"
 
 // Functions
 
diff --git a/source/ext_inst.cpp b/source/ext_inst.cpp
index e92c99c..0851fcf 100644
--- a/source/ext_inst.cpp
+++ b/source/ext_inst.cpp
@@ -62,6 +62,13 @@
     case SPV_ENV_UNIVERSAL_1_0:
     case SPV_ENV_VULKAN_1_0:
     case SPV_ENV_UNIVERSAL_1_1:
+    case SPV_ENV_OPENCL_2_1:
+    case SPV_ENV_OPENCL_2_2:
+    case SPV_ENV_OPENGL_4_0:
+    case SPV_ENV_OPENGL_4_1:
+    case SPV_ENV_OPENGL_4_2:
+    case SPV_ENV_OPENGL_4_3:
+    case SPV_ENV_OPENGL_4_5:
       *pExtInstTable = &table_1_0;
       return SPV_SUCCESS;
     default:
diff --git a/source/instruction.h b/source/instruction.h
index 37b35b2..f203dd0 100644
--- a/source/instruction.h
+++ b/source/instruction.h
@@ -30,10 +30,9 @@
 #include <cstdint>
 #include <vector>
 
+#include "spirv-tools/libspirv.h"
 #include "spirv/1.1/spirv.h"
 
-#include "table.h"
-
 // Describes an instruction.
 struct spv_instruction_t {
   // Normally, both opcode and extInstType contain valid data.
diff --git a/source/name_mapper.cpp b/source/name_mapper.cpp
index f2c03ca..0dac99a 100644
--- a/source/name_mapper.cpp
+++ b/source/name_mapper.cpp
@@ -76,7 +76,8 @@
 }
 
 std::string FriendlyNameMapper::Sanitize(const std::string& suggested_name) {
-  // Just replace invalid characters by '_'.
+  if (suggested_name.empty()) return "_";
+  // Otherwise, replace invalid characters by '_'.
   std::string result;
   std::string valid =
       "abcdefghijklmnopqrstuvwxyz"
diff --git a/source/opcode.cpp b/source/opcode.cpp
index 22a0df6..2e6c7da 100644
--- a/source/opcode.cpp
+++ b/source/opcode.cpp
@@ -101,9 +101,16 @@
   switch (env) {
     case SPV_ENV_UNIVERSAL_1_0:
     case SPV_ENV_VULKAN_1_0:
+    case SPV_ENV_OPENCL_2_1:
+    case SPV_ENV_OPENGL_4_0:
+    case SPV_ENV_OPENGL_4_1:
+    case SPV_ENV_OPENGL_4_2:
+    case SPV_ENV_OPENGL_4_3:
+    case SPV_ENV_OPENGL_4_5:
       *pInstTable = &table_1_0;
       return SPV_SUCCESS;
     case SPV_ENV_UNIVERSAL_1_1:
+    case SPV_ENV_OPENCL_2_2:
       *pInstTable = &table_1_1;
       return SPV_SUCCESS;
   }
diff --git a/source/operand.cpp b/source/operand.cpp
index a909380..b6dacc5 100644
--- a/source/operand.cpp
+++ b/source/operand.cpp
@@ -53,9 +53,16 @@
   switch (env) {
     case SPV_ENV_UNIVERSAL_1_0:
     case SPV_ENV_VULKAN_1_0:
+    case SPV_ENV_OPENCL_2_1:
+    case SPV_ENV_OPENGL_4_0:
+    case SPV_ENV_OPENGL_4_1:
+    case SPV_ENV_OPENGL_4_2:
+    case SPV_ENV_OPENGL_4_3:
+    case SPV_ENV_OPENGL_4_5:
       *pOperandTable = &table_1_0;
       return SPV_SUCCESS;
     case SPV_ENV_UNIVERSAL_1_1:
+    case SPV_ENV_OPENCL_2_2:
       *pOperandTable = &table_1_1;
       return SPV_SUCCESS;
   }
diff --git a/source/opt/CMakeLists.txt b/source/opt/CMakeLists.txt
index 24a201d..b4e23e4 100644
--- a/source/opt/CMakeLists.txt
+++ b/source/opt/CMakeLists.txt
@@ -1,5 +1,6 @@
 add_library(SPIRV-Tools-opt
   basic_block.h
+  def_use_manager.h
   function.h
   instruction.h
   ir_loader.h
@@ -9,6 +10,7 @@
   passes.h
   pass_manager.h
 
+  def_use_manager.cpp
   function.cpp
   instruction.cpp
   ir_loader.cpp
diff --git a/source/opt/def_use_manager.cpp b/source/opt/def_use_manager.cpp
new file mode 100644
index 0000000..4775421
--- /dev/null
+++ b/source/opt/def_use_manager.cpp
@@ -0,0 +1,121 @@
+// Copyright (c) 2016 Google Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and/or associated documentation files (the
+// "Materials"), to deal in the Materials without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Materials, and to
+// permit persons to whom the Materials are furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Materials.
+//
+// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS
+// KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS
+// SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT
+//    https://www.khronos.org/registry/
+//
+// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+
+#include "def_use_manager.h"
+
+#include <cassert>
+#include <functional>
+
+#include "instruction.h"
+#include "module.h"
+
+namespace spvtools {
+namespace opt {
+namespace analysis {
+
+void DefUseManager::AnalyzeDefUse(ir::Module* module) {
+  module->ForEachInst(std::bind(&DefUseManager::AnalyzeInstDefUse, this,
+                                std::placeholders::_1));
+}
+
+ir::Instruction* DefUseManager::GetDef(uint32_t id) {
+  if (id_to_def_.count(id) == 0) return nullptr;
+  return id_to_def_.at(id);
+}
+
+UseList* DefUseManager::GetUses(uint32_t id) {
+  if (id_to_uses_.count(id) == 0) return nullptr;
+  return &id_to_uses_.at(id);
+}
+
+bool DefUseManager::KillDef(uint32_t id) {
+  if (id_to_def_.count(id) == 0) return false;
+
+  // Go through all ids usd by this instruction, remove this instruction's uses
+  // of them.
+  for (const auto use_id : result_id_to_used_ids_[id]) {
+    if (id_to_uses_.count(use_id) == 0) continue;
+    auto& uses = id_to_uses_[use_id];
+    for (auto it = uses.begin(); it != uses.end();) {
+      if (it->inst->result_id() == id) {
+        it = uses.erase(it);
+      } else {
+        ++it;
+      }
+    }
+    if (uses.empty()) id_to_uses_.erase(use_id);
+  }
+  result_id_to_used_ids_.erase(id);
+
+  id_to_uses_.erase(id);  // Remove all uses of this id.
+  // This must happen at the last since we use information inside the instuction
+  // in the above.
+  id_to_def_[id]->ToNop();
+  id_to_def_.erase(id);
+  return true;
+}
+
+bool DefUseManager::ReplaceAllUsesWith(uint32_t before, uint32_t after) {
+  if (before == after) return false;
+  if (id_to_uses_.count(before) == 0) return false;
+
+  for (auto it = id_to_uses_[before].cbegin(); it != id_to_uses_[before].cend();
+       ++it) {
+    // Make the modification in the instruction.
+    it->inst->SetOperand(it->operand_index, {after});
+    // Register the use of |after| id into id_to_uses_.
+    // TODO(antiagainst): de-duplication.
+    id_to_uses_[after].push_back({it->inst, it->operand_index});
+  }
+  id_to_uses_.erase(before);
+  return true;
+}
+
+void DefUseManager::AnalyzeInstDefUse(ir::Instruction* inst) {
+  const uint32_t def_id = inst->result_id();
+  if (def_id != 0) id_to_def_[def_id] = inst;
+
+  for (uint32_t i = 0; i < inst->NumOperands(); ++i) {
+    switch (inst->GetOperand(i).type) {
+      // For any id type but result id type
+      case SPV_OPERAND_TYPE_ID:
+      case SPV_OPERAND_TYPE_TYPE_ID:
+      case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID:
+      case SPV_OPERAND_TYPE_SCOPE_ID: {
+        uint32_t use_id = inst->GetSingleWordOperand(i);
+        // use_id is used by the instruction generating def_id.
+        id_to_uses_[use_id].push_back({inst, i});
+        if (def_id != 0) result_id_to_used_ids_[def_id].push_back(use_id);
+      } break;
+      default:
+        break;
+    }
+  }
+}
+
+}  // namespace analysis
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/def_use_manager.h b/source/opt/def_use_manager.h
new file mode 100644
index 0000000..d7745d7
--- /dev/null
+++ b/source/opt/def_use_manager.h
@@ -0,0 +1,108 @@
+// Copyright (c) 2016 Google Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and/or associated documentation files (the
+// "Materials"), to deal in the Materials without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Materials, and to
+// permit persons to whom the Materials are furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Materials.
+//
+// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS
+// KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS
+// SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT
+//    https://www.khronos.org/registry/
+//
+// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+
+#ifndef LIBSPIRV_OPT_DEF_USE_MANAGER_H_
+#define LIBSPIRV_OPT_DEF_USE_MANAGER_H_
+
+#include <list>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include "instruction.h"
+#include "module.h"
+
+namespace spvtools {
+namespace opt {
+namespace analysis {
+
+// Class for representing a use of id. Note that result:
+// * Type id is a use.
+// * Ids referenced in OpSectionMerge & OpLoopMerge are considered as use.
+// * Ids referenced in OpPhi's in operands are considered as use.
+struct Use {
+  ir::Instruction* inst;   // Instruction using the id.
+  uint32_t operand_index;  // logical operand index of the id use. This can be
+                           // the index of result type id.
+};
+
+using UseList = std::list<Use>;
+
+// A class for analyzing and managing defs and uses in an ir::Module.
+class DefUseManager {
+ public:
+  using IdToDefMap = std::unordered_map<uint32_t, ir::Instruction*>;
+  using IdToUsesMap = std::unordered_map<uint32_t, UseList>;
+
+  // Analyzes the defs and uses in the given |module| and populates data
+  // structures in this class.
+  // TODO(antiagainst): This method should not modify the given module. Create
+  // const overload for ForEachInst().
+  void AnalyzeDefUse(ir::Module* module);
+
+  // Returns the def instruction for the given |id|. If there is no instruction
+  // defining |id|, returns nullptr.
+  ir::Instruction* GetDef(uint32_t id);
+  // Returns the use instructions for the given |id|. If there is no uses of
+  // |id|, returns nullptr.
+  UseList* GetUses(uint32_t id);
+
+  // Returns the map from ids to their def instructions.
+  const IdToDefMap& id_to_defs() const { return id_to_def_; }
+  // Returns the map from ids to their uses in instructions.
+  const IdToUsesMap& id_to_uses() const { return id_to_uses_; }
+
+  // Turns the instruction defining the given |id| into a Nop. Returns true on
+  // success, false if the given |id| is not defined at all. This method also
+  // erases both the uses of |id| and the |id|-generating instruction's use
+  // information kept in this manager, but not the operands in the original
+  // instructions.
+  bool KillDef(uint32_t id);
+  // Replaces all uses of |before| id with |after| id. Returns true if any
+  // replacement happens. This method does not kill the definition of the
+  // |before| id. If |after| is the same as |before|, does nothing and returns
+  // false.
+  bool ReplaceAllUsesWith(uint32_t before, uint32_t after);
+
+ private:
+  using ResultIdToUsedIdsMap =
+      std::unordered_map<uint32_t, std::vector<uint32_t>>;
+
+  // Analyzes the defs and uses in the given |inst|.
+  void AnalyzeInstDefUse(ir::Instruction* inst);
+
+  IdToDefMap id_to_def_;    // Mapping from ids to their definitions
+  IdToUsesMap id_to_uses_;  // Mapping from ids to their uses
+  // Mapping from result ids to the ids used in the instructions generating the
+  // result ids.
+  ResultIdToUsedIdsMap result_id_to_used_ids_;
+};
+
+}  // namespace analysis
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // LIBSPIRV_OPT_DEF_USE_MANAGER_H_
diff --git a/source/opt/instruction.h b/source/opt/instruction.h
index 0bbcd6e..18447ca 100644
--- a/source/opt/instruction.h
+++ b/source/opt/instruction.h
@@ -85,7 +85,18 @@
   Instruction(const spv_parsed_instruction_t& inst,
               std::vector<Instruction>&& dbg_line = {});
 
+  Instruction(const Instruction&) = default;
+  Instruction& operator=(const Instruction&) = default;
+
+  Instruction(Instruction&&) = default;
+  Instruction& operator=(Instruction&&) = default;
+
   SpvOp opcode() const { return opcode_; }
+  // Sets the opcode of this instruction to a specific opcode. Note this may
+  // invalidate the instruction.
+  // TODO(qining): Remove this function when instruction building and insertion
+  // is well implemented.
+  void SetOpcode(SpvOp op) { opcode_ = op; }
   uint32_t type_id() const { return type_id_; }
   uint32_t result_id() const { return result_id_; }
   // Returns the vector of line-related debug instructions attached to this
diff --git a/source/opt/libspirv.cpp b/source/opt/libspirv.cpp
index fe1b2b5..c076762 100644
--- a/source/opt/libspirv.cpp
+++ b/source/opt/libspirv.cpp
@@ -68,13 +68,12 @@
 }
 
 spv_result_t SpvTools::Disassemble(const std::vector<uint32_t>& binary,
-                                   std::string* text) {
+                                   std::string* text, uint32_t options) {
   spv_text spvtext = nullptr;
   spv_diagnostic diagnostic = nullptr;
 
   spv_result_t status = spvBinaryToText(context_, binary.data(), binary.size(),
-                                        SPV_BINARY_TO_TEXT_OPTION_NO_HEADER,
-                                        &spvtext, &diagnostic);
+                                        options, &spvtext, &diagnostic);
   if (status == SPV_SUCCESS) {
     text->assign(spvtext->str, spvtext->str + spvtext->length);
   }
diff --git a/source/opt/libspirv.hpp b/source/opt/libspirv.hpp
index 667276f..e039c17 100644
--- a/source/opt/libspirv.hpp
+++ b/source/opt/libspirv.hpp
@@ -54,10 +54,14 @@
   // Returns SPV_SUCCESS on successful assembling.
   spv_result_t Assemble(const std::string& text, std::vector<uint32_t>* binary);
 
-  // Disassembles the given SPIR-V |binary| and returns the assembly. Returns
-  // SPV_SUCCESS on successful disassembling.
-  spv_result_t Disassemble(const std::vector<uint32_t>& binary,
-                           std::string* text);
+  // Disassembles the given SPIR-V |binary| with the given options and returns
+  // the assembly. By default the options are set to generate assembly with
+  // friendly variable names and no SPIR-V assembly header. Returns SPV_SUCCESS
+  // on successful disassembling.
+  spv_result_t Disassemble(
+      const std::vector<uint32_t>& binary, std::string* text,
+      uint32_t options = SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
+                         SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
 
   // Builds and returns a Module from the given SPIR-V |binary|.
   std::unique_ptr<ir::Module> BuildModule(const std::vector<uint32_t>& binary);
diff --git a/source/opt/module.cpp b/source/opt/module.cpp
index c6bab3a..85d1f0e 100644
--- a/source/opt/module.cpp
+++ b/source/opt/module.cpp
@@ -43,7 +43,7 @@
   for (auto& i : capabilities_) f(&i);
   for (auto& i : extensions_) f(&i);
   for (auto& i : ext_inst_imports_) f(&i);
-  f(&memory_model_);
+  if (memory_model_) f(memory_model_.get());
   for (auto& i : entry_points_) f(&i);
   for (auto& i : execution_modes_) f(&i);
   for (auto& i : debugs_) f(&i);
@@ -64,7 +64,7 @@
   for (const auto& c : capabilities_) c.ToBinary(binary, skip_nop);
   for (const auto& e : extensions_) e.ToBinary(binary, skip_nop);
   for (const auto& e : ext_inst_imports_) e.ToBinary(binary, skip_nop);
-  memory_model_.ToBinary(binary, skip_nop);
+  if (memory_model_) memory_model_->ToBinary(binary, skip_nop);
   for (const auto& e : entry_points_) e.ToBinary(binary, skip_nop);
   for (const auto& e : execution_modes_) e.ToBinary(binary, skip_nop);
   for (const auto& d : debugs_) d.ToBinary(binary, skip_nop);
diff --git a/source/opt/module.h b/source/opt/module.h
index f4bfbbc..df02dbd 100644
--- a/source/opt/module.h
+++ b/source/opt/module.h
@@ -28,6 +28,7 @@
 #define LIBSPIRV_OPT_MODULE_H_
 
 #include <functional>
+#include <memory>
 #include <utility>
 #include <vector>
 
@@ -63,8 +64,10 @@
   void AddExtInstImport(Instruction&& e) {
     ext_inst_imports_.push_back(std::move(e));
   }
-  // Appends a memory model instruction to this module.
-  void SetMemoryModel(Instruction&& m) { memory_model_ = std::move(m); }
+  // Adds a memory model instruction to this module.
+  void SetMemoryModel(Instruction&& m) {
+    memory_model_.reset(new Instruction(std::move(m)));
+  }
   // Appends an entry point instruction to this module.
   void AddEntryPoint(Instruction&& e) { entry_points_.push_back(std::move(e)); }
   // Appends an execution mode instruction to this module.
@@ -113,7 +116,8 @@
   std::vector<Instruction> capabilities_;
   std::vector<Instruction> extensions_;
   std::vector<Instruction> ext_inst_imports_;
-  Instruction memory_model_;  // A module only has one memory model instruction.
+  std::unique_ptr<Instruction>
+      memory_model_;  // A module only has one memory model instruction.
   std::vector<Instruction> entry_points_;
   std::vector<Instruction> execution_modes_;
   std::vector<Instruction> debugs_;
diff --git a/source/opt/passes.cpp b/source/opt/passes.cpp
index 3749c53..65ab769 100644
--- a/source/opt/passes.cpp
+++ b/source/opt/passes.cpp
@@ -41,5 +41,35 @@
   return modified;
 }
 
+bool FreezeSpecConstantValuePass::Process(ir::Module* module) {
+  bool modified = false;
+  module->ForEachInst([&modified](ir::Instruction* inst) {
+    switch (inst->opcode()) {
+      case SpvOp::SpvOpSpecConstant:
+        inst->SetOpcode(SpvOp::SpvOpConstant);
+        modified = true;
+        break;
+      case SpvOp::SpvOpSpecConstantTrue:
+        inst->SetOpcode(SpvOp::SpvOpConstantTrue);
+        modified = true;
+        break;
+      case SpvOp::SpvOpSpecConstantFalse:
+        inst->SetOpcode(SpvOp::SpvOpConstantFalse);
+        modified = true;
+        break;
+      case SpvOp::SpvOpDecorate:
+        if (inst->GetSingleWordInOperand(1) ==
+            SpvDecoration::SpvDecorationSpecId) {
+          inst->ToNop();
+          modified = true;
+        }
+        break;
+      default:
+        break;
+    }
+  });
+  return modified;
+}
+
 }  // namespace opt
 }  // namespace spvtools
diff --git a/source/opt/passes.h b/source/opt/passes.h
index cb07e0b..23873a8 100644
--- a/source/opt/passes.h
+++ b/source/opt/passes.h
@@ -46,7 +46,7 @@
 
 // A null pass that does nothing.
 class NullPass : public Pass {
-  const char* name() const override { return "Null"; }
+  const char* name() const override { return "null"; }
   bool Process(ir::Module*) override { return false; }
 };
 
@@ -54,10 +54,24 @@
 // Section 3.32.2 of the SPIR-V spec).
 class StripDebugInfoPass : public Pass {
  public:
-  const char* name() const override { return "StripDebugInfo"; }
+  const char* name() const override { return "strip-debug"; }
   bool Process(ir::Module* module) override;
 };
 
+// The transformation pass that specializes the value of spec constants to
+// their default values. This pass only processes the spec constants that have
+// Spec ID decorations (defined by OpSpecConstant, OpSpecConstantTrue and
+// OpSpecConstantFalse instructions) and replaces them with their front-end
+// version counterparts (OpConstant, OpConstantTrue and OpConstantFalse). The
+// corresponding Spec ID annotation instructions will also be removed. This
+// pass does not fold the newly added front-end constants and does not process
+// other spec constants defined by OpSpecConstantComposite or OpSpecConstantOp.
+class FreezeSpecConstantValuePass : public Pass {
+ public:
+  const char* name() const override { return "freeze-spec-const"; }
+  bool Process(ir::Module*) override;
+};
+
 }  // namespace opt
 }  // namespace spvtools
 
diff --git a/source/spirv_definition.h b/source/spirv_definition.h
index 59f8509..9cd3f36 100644
--- a/source/spirv_definition.h
+++ b/source/spirv_definition.h
@@ -29,7 +29,7 @@
 
 #include <cstdint>
 
-#include "spirv-tools/libspirv.h"
+#include "spirv/1.1/spirv.h"
 
 #define spvIsInBitfield(value, bitfield) ((value) == ((value)&bitfield))
 
diff --git a/source/spirv_target_env.cpp b/source/spirv_target_env.cpp
index 116d6b5..e95ce9e 100644
--- a/source/spirv_target_env.cpp
+++ b/source/spirv_target_env.cpp
@@ -38,8 +38,20 @@
       return "SPIR-V 1.0 (under Vulkan 1.0 semantics)";
     case SPV_ENV_UNIVERSAL_1_1:
       return "SPIR-V 1.1";
-    default:
-      break;
+    case SPV_ENV_OPENCL_2_1:
+      return "SPIR-V 1.0 (under OpenCL 2.1 semantics)";
+    case SPV_ENV_OPENCL_2_2:
+      return "SPIR-V 1.1 (under OpenCL 2.2 semantics)";
+    case SPV_ENV_OPENGL_4_0:
+      return "SPIR-V 1.0 (under OpenCL 4.0 semantics)";
+    case SPV_ENV_OPENGL_4_1:
+      return "SPIR-V 1.0 (under OpenCL 4.1 semantics)";
+    case SPV_ENV_OPENGL_4_2:
+      return "SPIR-V 1.0 (under OpenCL 4.2 semantics)";
+    case SPV_ENV_OPENGL_4_3:
+      return "SPIR-V 1.0 (under OpenCL 4.3 semantics)";
+    case SPV_ENV_OPENGL_4_5:
+      return "SPIR-V 1.0 (under OpenCL 4.5 semantics)";
   }
   assert(0 && "Unhandled SPIR-V target environment");
   return "";
@@ -49,26 +61,55 @@
   switch (env) {
     case SPV_ENV_UNIVERSAL_1_0:
     case SPV_ENV_VULKAN_1_0:
+    case SPV_ENV_OPENCL_2_1:
+    case SPV_ENV_OPENGL_4_0:
+    case SPV_ENV_OPENGL_4_1:
+    case SPV_ENV_OPENGL_4_2:
+    case SPV_ENV_OPENGL_4_3:
+    case SPV_ENV_OPENGL_4_5:
       return SPV_SPIRV_VERSION_WORD(1, 0);
     case SPV_ENV_UNIVERSAL_1_1:
+    case SPV_ENV_OPENCL_2_2:
       return SPV_SPIRV_VERSION_WORD(1, 1);
-    default:
-      break;
   }
   assert(0 && "Unhandled SPIR-V target environment");
   return SPV_SPIRV_VERSION_WORD(0, 0);
 }
 
 bool spvParseTargetEnv(const char* s, spv_target_env* env) {
-  if (!strncmp(s, "vulkan1.0", strlen("vulkan1.0"))) {
+  auto match = [s](const char* b) {
+    return s && (0 == strncmp(s, b, strlen(b)));
+  };
+  if (match("vulkan1.0")) {
     if (env) *env = SPV_ENV_VULKAN_1_0;
     return true;
-  } else if (!strncmp(s, "spv1.0", strlen("spv1.0"))) {
+  } else if (match("spv1.0")) {
     if (env) *env = SPV_ENV_UNIVERSAL_1_0;
     return true;
-  } else if (!strncmp(s, "spv1.1", strlen("spv1.1"))) {
+  } else if (match("spv1.1")) {
     if (env) *env = SPV_ENV_UNIVERSAL_1_1;
     return true;
+  } else if (match("opencl2.1")) {
+    if (env) *env = SPV_ENV_OPENCL_2_1;
+    return true;
+  } else if (match("opencl2.2")) {
+    if (env) *env = SPV_ENV_OPENCL_2_2;
+    return true;
+  } else if (match("opengl4.0")) {
+    if (env) *env = SPV_ENV_OPENGL_4_0;
+    return true;
+  } else if (match("opengl4.1")) {
+    if (env) *env = SPV_ENV_OPENGL_4_1;
+    return true;
+  } else if (match("opengl4.2")) {
+    if (env) *env = SPV_ENV_OPENGL_4_2;
+    return true;
+  } else if (match("opengl4.3")) {
+    if (env) *env = SPV_ENV_OPENGL_4_3;
+    return true;
+  } else if (match("opengl4.5")) {
+    if (env) *env = SPV_ENV_OPENGL_4_5;
+    return true;
   } else {
     if (env) *env = SPV_ENV_UNIVERSAL_1_0;
     return false;
diff --git a/source/table.cpp b/source/table.cpp
index 3b142a2..2312ceb 100644
--- a/source/table.cpp
+++ b/source/table.cpp
@@ -33,6 +33,13 @@
     case SPV_ENV_UNIVERSAL_1_0:
     case SPV_ENV_VULKAN_1_0:
     case SPV_ENV_UNIVERSAL_1_1:
+    case SPV_ENV_OPENCL_2_1:
+    case SPV_ENV_OPENCL_2_2:
+    case SPV_ENV_OPENGL_4_0:
+    case SPV_ENV_OPENGL_4_1:
+    case SPV_ENV_OPENGL_4_2:
+    case SPV_ENV_OPENGL_4_3:
+    case SPV_ENV_OPENGL_4_5:
       break;
     default:
       return nullptr;
diff --git a/source/val/BasicBlock.cpp b/source/val/BasicBlock.cpp
index eb54f2a..3e95618 100644
--- a/source/val/BasicBlock.cpp
+++ b/source/val/BasicBlock.cpp
@@ -26,6 +26,7 @@
 
 #include "BasicBlock.h"
 
+#include <algorithm>
 #include <utility>
 #include <vector>
 
@@ -76,12 +77,16 @@
   return;
 }
 
-void BasicBlock::SetSuccessorsUnsafe(std::vector<BasicBlock*>&& others) {
-  successors_ = std::move(others);
+bool BasicBlock::dominates(const BasicBlock& other) const {
+  return (this == &other) ||
+         !(other.dom_end() ==
+           std::find(other.dom_begin(), other.dom_end(), this));
 }
 
-void BasicBlock::SetPredecessorsUnsafe(std::vector<BasicBlock*>&& others) {
-  predecessors_ = std::move(others);
+bool BasicBlock::postdominates(const BasicBlock& other) const {
+  return (this == &other) ||
+         !(other.pdom_end() ==
+           std::find(other.pdom_begin(), other.pdom_end(), this));
 }
 
 BasicBlock::DominatorIterator::DominatorIterator() : current_(nullptr) {}
diff --git a/source/val/BasicBlock.h b/source/val/BasicBlock.h
index 9be655c..21b0e39 100644
--- a/source/val/BasicBlock.h
+++ b/source/val/BasicBlock.h
@@ -121,20 +121,20 @@
   /// Adds @p next BasicBlocks as successors of this BasicBlock
   void RegisterSuccessors(const std::vector<BasicBlock*>& next = {});
 
-  /// Set the successors to this block, without updating other internal state,
-  /// and without updating the other blocks.
-  void SetSuccessorsUnsafe(std::vector<BasicBlock*>&& others);
-
-  /// Set the predecessors to this block, without updating other internal state,
-  /// and without updating the other blocks.
-  void SetPredecessorsUnsafe(std::vector<BasicBlock*>&& others);
-
   /// Returns true if the id of the BasicBlock matches
   bool operator==(const BasicBlock& other) const { return other.id_ == id_; }
 
   /// Returns true if the id of the BasicBlock matches
   bool operator==(const uint32_t& other_id) const { return other_id == id_; }
 
+  /// Returns true if this block dominates the other block.
+  /// Assumes dominators have been computed.
+  bool dominates(const BasicBlock& other) const;
+
+  /// Returns true if this block postdominates the other block.
+  /// Assumes dominators have been computed.
+  bool postdominates(const BasicBlock& other) const;
+
   /// @brief A BasicBlock dominator iterator class
   ///
   /// This iterator will iterate over the (post)dominators of the block
diff --git a/source/val/Function.cpp b/source/val/Function.cpp
index d13915e..0240d68 100644
--- a/source/val/Function.cpp
+++ b/source/val/Function.cpp
@@ -29,42 +29,79 @@
 #include <cassert>
 
 #include <algorithm>
+#include <unordered_set>
+#include <unordered_map>
 #include <utility>
 
 #include "val/BasicBlock.h"
 #include "val/Construct.h"
-#include "val/ValidationState.h"
+#include "validate.h"
 
 using std::ignore;
 using std::list;
 using std::make_pair;
 using std::pair;
-using std::string;
 using std::tie;
 using std::vector;
 
-namespace libspirv {
 namespace {
 
-void printDot(const BasicBlock& other, const ValidationState_t& module) {
-  string block_string;
-  if (other.successors()->empty()) {
-    block_string += "end ";
-  } else {
-    for (auto block : *other.successors()) {
-      block_string += module.getIdOrName(block->id()) + " ";
+using libspirv::BasicBlock;
+
+// Computes a minimal set of root nodes required to traverse, in the forward
+// direction, the CFG represented by the given vector of blocks, and successor
+// and predecessor functions.  When considering adding two nodes, each having
+// predecessors, favour using the one that appears earlier on the input blocks
+// list.
+std::vector<BasicBlock*> TraversalRoots(const std::vector<BasicBlock*>& blocks,
+                                        libspirv::get_blocks_func succ_func,
+                                        libspirv::get_blocks_func pred_func) {
+  // The set of nodes which have been visited from any of the roots so far.
+  std::unordered_set<const BasicBlock*> visited;
+
+  auto mark_visited = [&visited](const BasicBlock* b) { visited.insert(b); };
+  auto ignore_block = [](const BasicBlock*) {};
+  auto ignore_blocks = [](const BasicBlock*, const BasicBlock*) {};
+
+  auto traverse_from_root = [&mark_visited, &succ_func, &ignore_block,
+                             &ignore_blocks](const BasicBlock* entry) {
+    DepthFirstTraversal(entry, succ_func, mark_visited, ignore_block,
+                        ignore_blocks);
+  };
+
+  std::vector<BasicBlock*> result;
+
+  // First collect nodes without predecessors.
+  for (auto block : blocks) {
+    if (pred_func(block)->empty()) {
+      assert(visited.count(block) == 0 && "Malformed graph!");
+      result.push_back(block);
+      traverse_from_root(block);
     }
   }
-  printf("%10s -> {%s\b}\n", module.getIdOrName(other.id()).c_str(),
-         block_string.c_str());
+
+  // Now collect other stranded nodes.  These must be in unreachable cycles.
+  for (auto block : blocks) {
+    if (visited.count(block) == 0) {
+      result.push_back(block);
+      traverse_from_root(block);
+    }
+  }
+
+  return result;
 }
-}  /// namespace
+
+} // anonymous namespace
+
+namespace libspirv {
+
+// Universal Limit of ResultID + 1
+static const uint32_t kInvalidId = 0x400000;
 
 Function::Function(uint32_t function_id, uint32_t result_type_id,
                    SpvFunctionControlMask function_control,
-                   uint32_t function_type_id, ValidationState_t& module)
-    : module_(module),
-      id_(function_id),
+                   uint32_t function_type_id)
+    : id_(function_id),
       function_type_id_(function_type_id),
       result_type_id_(result_type_id),
       function_control_(function_control),
@@ -74,8 +111,6 @@
       current_block_(nullptr),
       pseudo_entry_block_(0),
       pseudo_exit_block_(kInvalidId),
-      pseudo_entry_blocks_({&pseudo_entry_block_}),
-      pseudo_exit_blocks_({&pseudo_exit_block_}),
       cfg_constructs_(),
       variable_ids_(),
       parameter_ids_() {}
@@ -86,9 +121,6 @@
 
 spv_result_t Function::RegisterFunctionParameter(uint32_t parameter_id,
                                                  uint32_t type_id) {
-  assert(module_.in_function_body() == true &&
-         "RegisterFunctionParameter can only be called when parsing the binary "
-         "outside of another function");
   assert(current_block_ == nullptr &&
          "RegisterFunctionParameter can only be called when parsing the binary "
          "ouside of a block");
@@ -104,18 +136,17 @@
   RegisterBlock(merge_id, false);
   RegisterBlock(continue_id, false);
   BasicBlock& merge_block = blocks_.at(merge_id);
-  BasicBlock& continue_block = blocks_.at(continue_id);
+  BasicBlock& continue_target_block = blocks_.at(continue_id);
   assert(current_block_ &&
          "RegisterLoopMerge must be called when called within a block");
 
   current_block_->set_type(kBlockTypeLoop);
   merge_block.set_type(kBlockTypeMerge);
-  continue_block.set_type(kBlockTypeContinue);
-  cfg_constructs_.emplace_back(ConstructType::kLoop, current_block_,
-                               &merge_block);
-  Construct& loop_construct = cfg_constructs_.back();
-  cfg_constructs_.emplace_back(ConstructType::kContinue, &continue_block);
-  Construct& continue_construct = cfg_constructs_.back();
+  continue_target_block.set_type(kBlockTypeContinue);
+  Construct& loop_construct =
+      AddConstruct({ConstructType::kLoop, current_block_, &merge_block});
+  Construct& continue_construct =
+      AddConstruct({ConstructType::kContinue, &continue_target_block});
   continue_construct.set_corresponding_constructs({&loop_construct});
   loop_construct.set_corresponding_constructs({&continue_construct});
 
@@ -128,30 +159,11 @@
   current_block_->set_type(kBlockTypeHeader);
   merge_block.set_type(kBlockTypeMerge);
 
-  cfg_constructs_.emplace_back(ConstructType::kSelection, current_block(),
-                               &merge_block);
+  AddConstruct({ConstructType::kSelection, current_block(), &merge_block});
+
   return SPV_SUCCESS;
 }
 
-void Function::PrintDotGraph() const {
-  if (first_block()) {
-    string func_name(module_.getIdOrName(id_));
-    printf("digraph %s {\n", func_name.c_str());
-    PrintBlocks();
-    printf("}\n");
-  }
-}
-
-void Function::PrintBlocks() const {
-  if (first_block()) {
-    printf("%10s -> %s\n", module_.getIdOrName(id_).c_str(),
-           module_.getIdOrName(first_block()->id()).c_str());
-    for (const auto& block : blocks_) {
-      printDot(block.second, module_);
-    }
-  }
-}
-
 spv_result_t Function::RegisterSetFunctionDeclType(FunctionDecl type) {
   assert(declaration_type_ == FunctionDecl::kFunctionDeclUnknown);
   declaration_type_ = type;
@@ -159,12 +171,6 @@
 }
 
 spv_result_t Function::RegisterBlock(uint32_t block_id, bool is_definition) {
-  assert(module_.in_function_body() == true &&
-         "RegisterBlocks can only be called when parsing a binary inside of a "
-         "function");
-  assert(module_.current_layout_section() !=
-             ModuleLayoutSection::kLayoutFunctionDeclarations &&
-         "RegisterBlocks cannot be called within a function declaration");
   assert(
       declaration_type_ == FunctionDecl::kFunctionDeclDefinition &&
       "RegisterBlocks can only be called after declaration_type_ is defined");
@@ -191,13 +197,9 @@
 
 void Function::RegisterBlockEnd(vector<uint32_t> next_list,
                                 SpvOp branch_instruction) {
-  assert(module_.in_function_body() == true &&
-         "RegisterBlockEnd can only be called when parsing a binary in a "
-         "function");
   assert(
       current_block_ &&
       "RegisterBlockEnd can only be called when parsing a binary in a block");
-
   vector<BasicBlock*> next_blocks;
   next_blocks.reserve(next_list.size());
 
@@ -212,6 +214,22 @@
     next_blocks.push_back(&inserted_block->second);
   }
 
+  if (current_block_->is_type(kBlockTypeLoop)) {
+    // For each loop header, record the set of its successors, and include
+    // its continue target if the continue target is not the loop header
+    // itself.
+    std::vector<BasicBlock*>& next_blocks_plus_continue_target =
+        loop_header_successors_plus_continue_target_map_[current_block_];
+    next_blocks_plus_continue_target = next_blocks;
+    auto continue_target = FindConstructForEntryBlock(current_block_)
+                               .corresponding_constructs()
+                               .back()
+                               ->entry_block();
+    if (continue_target != current_block_) {
+      next_blocks_plus_continue_target.push_back(continue_target);
+    }
+  }
+
   current_block_->RegisterBranchInstruction(branch_instruction);
   current_block_->RegisterSuccessors(next_blocks);
   current_block_ = nullptr;
@@ -222,16 +240,7 @@
   if (!end_has_been_registered_) {
     end_has_been_registered_ = true;
 
-    // Compute the successors of the pseudo-entry block, and
-    // the predecessors of the pseudo exit block.
-    vector<BasicBlock*> sources;
-    vector<BasicBlock*> sinks;
-    for (const auto b : ordered_blocks_) {
-      if (b->predecessors()->empty()) sources.push_back(b);
-      if (b->successors()->empty()) sinks.push_back(b);
-    }
-    pseudo_entry_block_.SetSuccessorsUnsafe(std::move(sources));
-    pseudo_exit_block_.SetPredecessorsUnsafe(std::move(sinks));
+    ComputeAugmentedCFG();
   }
 }
 
@@ -249,16 +258,6 @@
 const BasicBlock* Function::current_block() const { return current_block_; }
 BasicBlock* Function::current_block() { return current_block_; }
 
-BasicBlock* Function::pseudo_entry_block() { return &pseudo_entry_block_; }
-const BasicBlock* Function::pseudo_entry_block() const {
-  return &pseudo_entry_block_;
-}
-
-BasicBlock* Function::pseudo_exit_block() { return &pseudo_exit_block_; }
-const BasicBlock* Function::pseudo_exit_block() const {
-  return &pseudo_exit_block_;
-}
-
 const list<Construct>& Function::constructs() const { return cfg_constructs_; }
 list<Construct>& Function::constructs() { return cfg_constructs_; }
 
@@ -299,4 +298,89 @@
   tie(out, defined) = const_cast<const Function*>(this)->GetBlock(block_id);
   return make_pair(const_cast<BasicBlock*>(out), defined);
 }
+
+Function::GetBlocksFunction Function::AugmentedCFGSuccessorsFunction() const {
+  return [this](const BasicBlock* block) {
+    auto where = augmented_successors_map_.find(block);
+    return where == augmented_successors_map_.end() ? block->successors()
+                                                    : &(*where).second;
+  };
+}
+
+Function::GetBlocksFunction
+Function::AugmentedCFGSuccessorsFunctionIncludingHeaderToContinueEdge() const {
+  return [this](const BasicBlock* block) {
+    auto where = loop_header_successors_plus_continue_target_map_.find(block);
+    return where == loop_header_successors_plus_continue_target_map_.end()
+               ? AugmentedCFGSuccessorsFunction()(block)
+               : &(*where).second;
+  };
+}
+
+Function::GetBlocksFunction Function::AugmentedCFGPredecessorsFunction() const {
+  return [this](const BasicBlock* block) {
+    auto where = augmented_predecessors_map_.find(block);
+    return where == augmented_predecessors_map_.end() ? block->predecessors()
+                                                      : &(*where).second;
+  };
+}
+
+void Function::ComputeAugmentedCFG() {
+  // Compute the successors of the pseudo-entry block, and
+  // the predecessors of the pseudo exit block.
+  auto succ_func = [](const BasicBlock* b) { return b->successors(); };
+  auto pred_func = [](const BasicBlock* b) { return b->predecessors(); };
+  auto sources = TraversalRoots(ordered_blocks_, succ_func, pred_func);
+
+  // For the predecessor traversals, reverse the order of blocks.  This
+  // will affect the post-dominance calculation as follows:
+  //  - Suppose you have blocks A and B, with A appearing before B in
+  //    the list of blocks.
+  //  - Also, A branches only to B, and B branches only to A.
+  //  - We want to compute A as dominating B, and B as post-dominating B.
+  // By using reversed blocks for predecessor traversal roots discovery,
+  // we'll add an edge from B to the pseudo-exit node, rather than from A.
+  // All this is needed to correctly process the dominance/post-dominance
+  // constraint when A is a loop header that points to itself as its
+  // own continue target, and B is the latch block for the loop.
+  std::vector<BasicBlock*> reversed_blocks(ordered_blocks_.rbegin(),
+                                           ordered_blocks_.rend());
+  auto sinks = TraversalRoots(reversed_blocks, pred_func, succ_func);
+
+  // Wire up the pseudo entry block.
+  augmented_successors_map_[&pseudo_entry_block_] = sources;
+  for (auto block : sources) {
+    auto& augmented_preds = augmented_predecessors_map_[block];
+    const auto& preds = *block->predecessors();
+    augmented_preds.reserve(1 + preds.size());
+    augmented_preds.push_back(&pseudo_entry_block_);
+    augmented_preds.insert(augmented_preds.end(), preds.begin(), preds.end());
+  }
+
+  // Wire up the pseudo exit block.
+  augmented_predecessors_map_[&pseudo_exit_block_] = sinks;
+  for (auto block : sinks) {
+    auto& augmented_succ = augmented_successors_map_[block];
+    const auto& succ = *block->successors();
+    augmented_succ.reserve(1 + succ.size());
+    augmented_succ.push_back(&pseudo_exit_block_);
+    augmented_succ.insert(augmented_succ.end(), succ.begin(), succ.end());
+  }
+};
+
+Construct& Function::AddConstruct(const Construct& new_construct) {
+  cfg_constructs_.push_back(new_construct);
+  auto& result = cfg_constructs_.back();
+  entry_block_to_construct_[new_construct.entry_block()] = &result;
+  return result;
+}
+
+Construct& Function::FindConstructForEntryBlock(const BasicBlock* entry_block) {
+  auto where = entry_block_to_construct_.find(entry_block);
+  assert(where != entry_block_to_construct_.end());
+  auto construct_ptr = (*where).second;
+  assert(construct_ptr);
+  return *construct_ptr;
+}
+
 }  /// namespace libspirv
diff --git a/source/val/Function.h b/source/val/Function.h
index a8175e9..1fbe113 100644
--- a/source/val/Function.h
+++ b/source/val/Function.h
@@ -28,6 +28,7 @@
 #define LIBSPIRV_VAL_FUNCTION_H_
 
 #include <list>
+#include <functional>
 #include <unordered_map>
 #include <unordered_set>
 #include <vector>
@@ -35,6 +36,7 @@
 #include "spirv-tools/libspirv.h"
 #include "spirv/1.1/spirv.h"
 #include "val/BasicBlock.h"
+#include "val/Construct.h"
 
 namespace libspirv {
 
@@ -44,17 +46,13 @@
   kFunctionDeclDefinition    /// < Function definition
 };
 
-class Construct;
-class ValidationState_t;
-
 /// This class manages all function declaration and definitions in a module. It
 /// handles the state and id information while parsing a function in the SPIR-V
 /// binary.
 class Function {
  public:
   Function(uint32_t id, uint32_t result_type_id,
-           SpvFunctionControlMask function_control, uint32_t function_type_id,
-           ValidationState_t& module);
+           SpvFunctionControlMask function_control, uint32_t function_type_id);
 
   /// Registers a function parameter in the current function
   /// @return Returns SPV_SUCCESS if the call was successful
@@ -152,31 +150,41 @@
   /// Returns the block that is currently being parsed in the binary
   const BasicBlock* current_block() const;
 
-  /// Returns the pseudo exit block
-  BasicBlock* pseudo_entry_block();
+  // For dominance calculations, we want to analyze all the
+  // blocks in the function, even in degenerate control flow cases
+  // including unreachable blocks.  We therefore make an "augmented CFG"
+  // which is the same as the ordinary CFG but adds:
+  //  - A pseudo-entry node.
+  //  - A pseudo-exit node.
+  //  - A minimal set of edges so that a forward traversal from the
+  //    pseudo-entry node will visit all nodes.
+  //  - A minimal set of edges so that a backward traversal from the
+  //    pseudo-exit node will visit all nodes.
+  // In particular, the pseudo-entry node is the unique source of the
+  // augmented CFG, and the psueo-exit node is the unique sink of the
+  // augmented CFG.
 
   /// Returns the pseudo exit block
-  const BasicBlock* pseudo_entry_block() const;
+  BasicBlock* pseudo_entry_block() { return &pseudo_entry_block_; }
 
   /// Returns the pseudo exit block
-  BasicBlock* pseudo_exit_block();
+  const BasicBlock* pseudo_entry_block() const { return &pseudo_entry_block_; }
 
   /// Returns the pseudo exit block
-  const BasicBlock* pseudo_exit_block() const;
+  BasicBlock* pseudo_exit_block() { return &pseudo_exit_block_; }
 
-  /// Returns a vector with just the pseudo entry block.
-  /// This serves as the predecessors of each source node in the CFG when
-  /// computing dominators.
-  const std::vector<BasicBlock*>* pseudo_entry_blocks() const {
-    return &pseudo_entry_blocks_;
-  }
+  /// Returns the pseudo exit block
+  const BasicBlock* pseudo_exit_block() const { return &pseudo_exit_block_; }
 
-  /// Returns a vector with just the pseudo exit block.
-  /// This serves as the successors of each sink node in the CFG when computing
-  /// dominators.
-  const std::vector<BasicBlock*>* pseudo_exit_blocks() const {
-    return &pseudo_exit_blocks_;
-  }
+  using GetBlocksFunction =
+      std::function<const std::vector<BasicBlock*>*(const BasicBlock*)>;
+  /// Returns the block successors function for the augmented CFG.
+  GetBlocksFunction AugmentedCFGSuccessorsFunction() const;
+  /// Like AugmentedCFGSuccessorsFunction, but also includes a forward edge from
+  /// a loop header block to its continue target, if they are different blocks.
+  GetBlocksFunction AugmentedCFGSuccessorsFunctionIncludingHeaderToContinueEdge() const;
+  /// Returns the block predecessors function for the augmented CFG.
+  GetBlocksFunction AugmentedCFGPredecessorsFunction() const;
 
   /// Prints a GraphViz digraph of the CFG of the current funciton
   void PrintDotGraph() const;
@@ -185,8 +193,17 @@
   void PrintBlocks() const;
 
  private:
-  /// Parent module
-  ValidationState_t& module_;
+  // Computes the representation of the augmented CFG.
+  // Populates augmented_successors_map_ and augmented_predecessors_map_.
+  void ComputeAugmentedCFG();
+
+  // Adds a copy of the given Construct, and tracks it by its entry block.
+  // Returns a reference to the stored construct.
+  Construct& AddConstruct(const Construct& new_construct);
+
+  // Returns a reference to the construct corresponding to the given entry
+  // block.
+  Construct& FindConstructForEntryBlock(const BasicBlock* entry_block);
 
   /// The result id of the OpLabel that defined this block
   uint32_t id_;
@@ -218,28 +235,49 @@
   /// The block that is currently being parsed
   BasicBlock* current_block_;
 
-  /// A pseudo entry block that, for the purposes of dominance analysis,
-  /// is considered the predecessor to any ordinary block without predecessors.
-  /// After the function end has been registered, its successor list consists
-  /// of all ordinary blocks without predecessors.  It has no predecessors.
-  /// It does not appear in the predecessor or successor list of any
-  /// ordinary block.
+  /// A pseudo entry node used in dominance analysis.
+  /// After the function end has been registered, the successor list of the
+  /// pseudo entry node is the minimal set of nodes such that all nodes in the
+  /// CFG can be reached by following successor lists.  That is, the successors
+  /// will be:
+  ///   - Any basic block without predecessors.  This includes the entry
+  ///     block to the function.
+  ///   - A single node from each otherwise unreachable cycle in the CFG, if
+  ///     such cycles exist.
+  /// The pseudo entry node does not appear in the predecessor or successor
+  /// list of any ordinary block.
+  /// It has no predecessors.
   /// It has Id 0.
   BasicBlock pseudo_entry_block_;
 
-  /// A pseudo exit block that, for the purposes of dominance analysis,
-  /// is considered the successor to any ordinary block without successors.
-  /// After the function end has been registered, its predecessor list consists
-  /// of all ordinary blocks without successors.  It has no successors.
-  /// It does not appear in the predecessor or successor list of any
-  /// ordinary block.
+  /// A pseudo exit block used in dominance analysis.
+  /// After the function end has been registered, the predecessor list of the
+  /// pseudo exit node is the minimal set of nodes such that all nodes in the
+  /// CFG can be reached by following predecessor lists.  That is, the
+  /// predecessors will be:
+  ///   - Any basic block without successors.  This includes any basic block
+  ///     ending with an OpReturn, OpReturnValue or similar instructions.
+  ///   - A single node from each otherwise unreachable cycle in the CFG, if
+  ///     such cycles exist.
+  /// The pseudo exit node does not appear in the predecessor or successor
+  /// list of any ordinary block.
+  /// It has no successors.
   BasicBlock pseudo_exit_block_;
 
-  // A vector containing pseudo_entry_block_.
-  const std::vector<BasicBlock*> pseudo_entry_blocks_;
+  // Maps a block to its successors in the augmented CFG, if that set is
+  // different from its successors in the ordinary CFG.
+  std::unordered_map<const BasicBlock*, std::vector<BasicBlock*>>
+      augmented_successors_map_;
+  // Maps a block to its predecessors in the augmented CFG, if that set is
+  // different from its predecessors in the ordinary CFG.
+  std::unordered_map<const BasicBlock*, std::vector<BasicBlock*>>
+      augmented_predecessors_map_;
 
-  // A vector containing pseudo_exit_block_.
-  const std::vector<BasicBlock*> pseudo_exit_blocks_;
+  // Maps a structured loop header to its CFG successors and also its
+  // continue target if that continue target is not the loop header
+  // itself. This might have duplicates.
+  std::unordered_map<const BasicBlock*, std::vector<BasicBlock*>>
+      loop_header_successors_plus_continue_target_map_;
 
   /// The constructs that are available in this function
   std::list<Construct> cfg_constructs_;
@@ -249,6 +287,9 @@
 
   /// The function parameter ids of the functions
   std::vector<uint32_t> parameter_ids_;
+
+  /// Maps a construct's entry block to the construct.
+  std::unordered_map<const BasicBlock*, Construct*> entry_block_to_construct_;
 };
 
 }  /// namespace libspirv
diff --git a/source/val/Id.cpp b/source/val/Id.cpp
new file mode 100644
index 0000000..2a0e01a
--- /dev/null
+++ b/source/val/Id.cpp
@@ -0,0 +1,62 @@
+// Copyright (c) 2015-2016 The Khronos Group Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and/or associated documentation files (the
+// "Materials"), to deal in the Materials without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Materials, and to
+// permit persons to whom the Materials are furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Materials.
+//
+// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS
+// KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS
+// SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT
+//    https://www.khronos.org/registry/
+//
+// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+
+#include "val/Id.h"
+
+namespace libspirv {
+#define OPERATOR(OP)                               \
+  bool operator OP(const Id& lhs, const Id& rhs) { \
+    return lhs.id_ OP rhs.id_;                     \
+  }                                                \
+  bool operator OP(const Id& lhs, uint32_t rhs) { return lhs.id_ OP rhs; }
+
+OPERATOR(<)
+OPERATOR(==)
+#undef OPERATOR
+
+Id::Id(const uint32_t result_id)
+    : id_(result_id),
+      type_id_(0),
+      opcode_(SpvOpNop),
+      defining_function_(nullptr),
+      defining_block_(nullptr),
+      uses_(),
+      words_(0) {}
+
+Id::Id(const spv_parsed_instruction_t* inst, Function* function,
+       BasicBlock* block)
+    : id_(inst->result_id),
+      type_id_(inst->type_id),
+      opcode_(static_cast<SpvOp>(inst->opcode)),
+      defining_function_(function),
+      defining_block_(block),
+      uses_(),
+      words_(inst->words, inst->words + inst->num_words) {}
+
+void Id::RegisterUse(const BasicBlock* block) {
+  if (block) { uses_.insert(block); }
+}
+}  // namespace libspirv
diff --git a/source/val/Id.h b/source/val/Id.h
new file mode 100644
index 0000000..c6e36cc
--- /dev/null
+++ b/source/val/Id.h
@@ -0,0 +1,135 @@
+// Copyright (c) 2015-2016 The Khronos Group Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and/or associated documentation files (the
+// "Materials"), to deal in the Materials without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Materials, and to
+// permit persons to whom the Materials are furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Materials.
+//
+// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS
+// KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS
+// SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT
+//    https://www.khronos.org/registry/
+//
+// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+
+#ifndef LIBSPIRV_VAL_ID_H_
+#define LIBSPIRV_VAL_ID_H_
+
+#include <cstdint>
+
+#include <functional>
+#include <set>
+
+#include "spirv-tools/libspirv.h"
+#include "val/Function.h"
+
+namespace libspirv {
+
+class BasicBlock;
+
+/// Represents a definition of any ID
+///
+/// This class represents the a definition of an Id in a module. This object can
+/// be formed as a complete, or incomplete Id. A complete Id allows you to
+/// reference all of the properties of this class and forms a fully defined
+/// object in the module. The incomplete Id is defined only by its integer value
+/// in a module and can only be used to search in a data structure.
+class Id {
+ public:
+  /// This constructor creates an incomplete Id. This constructor can be used to
+  /// create Id that are used to find other Ids
+  explicit Id(const uint32_t result_id = 0);
+
+  /// This constructor creates a complete Id.
+  explicit Id(const spv_parsed_instruction_t* inst,
+              Function* function = nullptr, BasicBlock* block = nullptr);
+
+  /// Registers a use of the Id
+  void RegisterUse(const BasicBlock* block = nullptr);
+
+  /// returns the id of the Id
+  operator uint32_t() const { return id_; }
+
+  uint32_t id() const { return id_; }
+  uint32_t type_id() const { return type_id_; }
+  SpvOp opcode() const { return opcode_; }
+
+  /// returns the Function where the id was defined. nullptr if it was defined
+  /// outside of a Function
+  const Function* defining_function() const { return defining_function_; }
+
+  /// returns the BasicBlock where the id was defined. nullptr if it was defined
+  /// outside of a BasicBlock
+  const BasicBlock* defining_block() const { return defining_block_; }
+
+  /// Returns the set of blocks where this Id was used
+  const std::set<const BasicBlock*>& uses() const { return uses_; }
+
+  /// The words used to define the Id
+  const std::vector<uint32_t>& words() const { return words_; }
+
+ private:
+  /// The integer that identifies the Id
+  uint32_t id_;
+
+  /// The type of the Id
+  uint32_t type_id_;
+
+  /// The opcode used to define the Id
+  SpvOp opcode_;
+
+  /// The function in which the Id was defined
+  Function* defining_function_;
+
+  /// The block in which the Id was defined
+  BasicBlock* defining_block_;
+
+  /// The blocks in which the Id was used
+  std::set<const BasicBlock*> uses_;
+
+  /// The words of the instuction that defined the Id
+  std::vector<uint32_t> words_;
+
+#define OPERATOR(OP)                                     \
+  friend bool operator OP(const Id& lhs, const Id& rhs); \
+  friend bool operator OP(const Id& lhs, uint32_t rhs)
+  OPERATOR(<);
+  OPERATOR(==);
+#undef OPERATOR
+};
+
+#define OPERATOR(OP)                              \
+  bool operator OP(const Id& lhs, const Id& rhs); \
+  bool operator OP(const Id& lhs, uint32_t rhs)
+
+OPERATOR(<);
+OPERATOR(==);
+#undef OPERATOR
+
+}  // namespace libspirv
+
+// custom specialization of std::hash for Id
+namespace std {
+template <>
+struct hash<libspirv::Id> {
+  typedef libspirv::Id argument_type;
+  typedef std::size_t result_type;
+  result_type operator()(const argument_type& id) const {
+    return hash<uint32_t>()(id);
+  }
+};
+}  /// namespace std
+
+#endif  // LIBSPIRV_VAL_ID_H_
diff --git a/source/val/ValidationState.cpp b/source/val/ValidationState.cpp
index 217b488..eba0d7f 100644
--- a/source/val/ValidationState.cpp
+++ b/source/val/ValidationState.cpp
@@ -33,7 +33,10 @@
 #include "val/Function.h"
 
 using std::list;
+using std::make_pair;
+using std::pair;
 using std::string;
+using std::unordered_map;
 using std::vector;
 
 namespace libspirv {
@@ -248,7 +251,18 @@
 }
 
 bool ValidationState_t::IsDefinedId(uint32_t id) const {
-  return usedefs_.FindDef(id).first;
+  return all_definitions_.find(Id{id}) != end(all_definitions_);
+}
+
+const Id* ValidationState_t::FindDef(uint32_t id) const {
+  if (all_definitions_.count(Id{id}) == 0) {
+    return nullptr;
+  } else {
+    /// We are in a const function, so we cannot use defs.operator[]().
+    /// Luckily we know the key exists, so defs_.at() won't throw an
+    /// exception.
+    return &all_definitions_.at(id);
+  }
 }
 
 // Increments the instruction count. Used for diagnostic
@@ -327,9 +341,7 @@
   memory_model_ = mm;
 }
 
-SpvMemoryModel ValidationState_t::memory_model() const {
-  return memory_model_;
-}
+SpvMemoryModel ValidationState_t::memory_model() const { return memory_model_; }
 
 spv_result_t ValidationState_t::RegisterFunction(
     uint32_t id, uint32_t ret_type_id, SpvFunctionControlMask function_control,
@@ -339,7 +351,7 @@
          "of another function");
   in_function_ = true;
   module_functions_.emplace_back(id, ret_type_id, function_control,
-                                 function_type_id, *this);
+                                 function_type_id);
 
   // TODO(umar): validate function type and type_id
 
@@ -358,4 +370,26 @@
   return SPV_SUCCESS;
 }
 
+void ValidationState_t::AddId(const spv_parsed_instruction_t& inst) {
+  if (in_function_body()) {
+    if (in_block()) {
+      all_definitions_[inst.result_id] =
+          Id{&inst, &current_function(), current_function().current_block()};
+    } else {
+      all_definitions_[inst.result_id] = Id{&inst, &current_function()};
+    }
+  } else {
+    all_definitions_[inst.result_id] = Id{&inst};
+  }
+}
+
+void ValidationState_t::RegisterUseId(uint32_t used_id) {
+  auto used = all_definitions_.find(used_id);
+  if (used != end(all_definitions_)) {
+    if (in_function_body())
+      used->second.RegisterUse(current_function().current_block());
+    else
+      used->second.RegisterUse(nullptr);
+  }
+}
 }  /// namespace libspirv
diff --git a/source/val/ValidationState.h b/source/val/ValidationState.h
index af5e54c..6a43c90 100644
--- a/source/val/ValidationState.h
+++ b/source/val/ValidationState.h
@@ -39,24 +39,11 @@
 #include "spirv-tools/libspirv.h"
 #include "spirv/1.1/spirv.h"
 #include "spirv_definition.h"
+#include "val/Function.h"
+#include "val/Id.h"
 
 namespace libspirv {
 
-// Universal Limit of ResultID + 1
-static const uint32_t kInvalidId = 0x400000;
-
-// Info about a result ID.
-typedef struct spv_id_info_t {
-  /// Id value.
-  uint32_t id;
-  /// Type id, or 0 if no type.
-  uint32_t type_id;
-  /// Opcode of the instruction defining the id.
-  SpvOp opcode;
-  /// Binary words of the instruction defining the id.
-  std::vector<uint32_t> words;
-} spv_id_info_t;
-
 /// This enum represents the sections of a SPIRV module. See section 2.4
 /// of the SPIRV spec for additional details of the order. The enumerant values
 /// are in the same order as the vector returned by GetModuleOrder
@@ -75,8 +62,6 @@
   kLayoutFunctionDefinitions    /// < Section 2.4 #11
 };
 
-class Function;
-
 /// This class manages the state of the SPIR-V validation as it is being parsed.
 class ValidationState_t {
  public:
@@ -138,41 +123,6 @@
   /// instruction
   bool in_block() const;
 
-  /// Keeps track of ID definitions and uses.
-  class UseDefTracker {
-   public:
-    void AddDef(const spv_id_info_t& def) { defs_[def.id] = def; }
-
-    void AddUse(uint32_t id) { uses_.insert(id); }
-
-    /// Finds id's def, if it exists.  If found, returns <true, def>. Otherwise,
-    /// returns <false, something>.
-    std::pair<bool, spv_id_info_t> FindDef(uint32_t id) const {
-      if (defs_.count(id) == 0) {
-        return std::make_pair(false, spv_id_info_t{});
-      } else {
-        /// We are in a const function, so we cannot use defs.operator[]().
-        /// Luckily we know the key exists, so defs_.at() won't throw an
-        /// exception.
-        return std::make_pair(true, defs_.at(id));
-      }
-    }
-
-    /// Returns uses of IDs lacking defs.
-    std::unordered_set<uint32_t> FindUsesWithoutDefs() const {
-      auto diff = uses_;
-      for (const auto d : defs_) diff.erase(d.first);
-      return diff;
-    }
-
-   private:
-    std::unordered_set<uint32_t> uses_;
-    std::unordered_map<uint32_t, spv_id_info_t> defs_;
-  };
-
-  UseDefTracker& usedefs() { return usedefs_; }
-  const UseDefTracker& usedefs() const { return usedefs_; }
-
   /// Returns a list of entry point function ids
   std::vector<uint32_t>& entry_points() { return entry_points_; }
   const std::vector<uint32_t>& entry_points() const { return entry_points_; }
@@ -210,7 +160,23 @@
 
   AssemblyGrammar& grammar() { return grammar_; }
 
+  /// Adds an id to the module
+  void AddId(const spv_parsed_instruction_t& inst);
+
+  /// Register Id use
+  void RegisterUseId(uint32_t used_id);
+
+  /// Finds id's def, if it exists.  If found, returns the definition otherwise
+  /// nullptr
+  const Id* FindDef(uint32_t id) const;
+
+  const std::unordered_map<uint32_t, Id>& all_definitions() const {
+    return all_definitions_;
+  }
+
  private:
+  ValidationState_t(const ValidationState_t&);
+
   spv_diagnostic* diagnostic_;
   /// Tracks the number of instructions evaluated by the validator
   int instruction_counter_;
@@ -231,8 +197,7 @@
   spv_capability_mask_t
       module_capabilities_;  /// Module's declared capabilities.
 
-  /// Definitions and uses of all the IDs in the module.
-  UseDefTracker usedefs_;
+  std::unordered_map<uint32_t, Id> all_definitions_;
 
   /// IDs that are entry points, ie, arguments to OpEntryPoint.
   std::vector<uint32_t> entry_points_;
diff --git a/source/validate.cpp b/source/validate.cpp
index 6e10a8a..836bea1 100644
--- a/source/validate.cpp
+++ b/source/validate.cpp
@@ -59,7 +59,7 @@
 using libspirv::CfgPass;
 using libspirv::InstructionPass;
 using libspirv::ModuleLayoutPass;
-using libspirv::SsaPass;
+using libspirv::IdPass;
 using libspirv::ValidationState_t;
 
 spv_result_t spvValidateIDs(
@@ -67,15 +67,11 @@
     const spv_opcode_table opcodeTable, const spv_operand_table operandTable,
     const spv_ext_inst_table extInstTable, const ValidationState_t& state,
     spv_position position, spv_diagnostic* pDiagnostic) {
-  auto undefd = state.usedefs().FindUsesWithoutDefs();
-  for (auto id : undefd) {
-    DIAGNOSTIC << "Undefined ID: " << id;
-  }
   position->index = SPV_INDEX_INSTRUCTION;
   spvCheckReturn(spvValidateInstructionIDs(pInsts, count, opcodeTable,
                                            operandTable, extInstTable, state,
                                            position, pDiagnostic));
-  return undefd.empty() ? SPV_SUCCESS : SPV_ERROR_INVALID_ID;
+  return SPV_SUCCESS;
 }
 
 namespace {
@@ -127,18 +123,6 @@
   }
 }
 
-// Collects use-def info about an instruction's IDs.
-void ProcessIds(ValidationState_t& _, const spv_parsed_instruction_t& inst) {
-  if (inst.result_id) {
-    _.usedefs().AddDef(
-        {inst.result_id, inst.type_id, static_cast<SpvOp>(inst.opcode),
-         std::vector<uint32_t>(inst.words, inst.words + inst.num_words)});
-  }
-  for (auto op = inst.operands; op != inst.operands + inst.num_operands; ++op) {
-    if (spvIsIdType(op->type)) _.usedefs().AddUse(inst.words[op->offset]);
-  }
-}
-
 spv_result_t ProcessInstruction(void* user_data,
                                 const spv_parsed_instruction_t* inst) {
   ValidationState_t& _ = *(reinterpret_cast<ValidationState_t*>(user_data));
@@ -148,15 +132,53 @@
 
   DebugInstructionPass(_, inst);
   // TODO(umar): Perform data rules pass
-  ProcessIds(_, *inst);
+  spvCheckReturn(IdPass(_, inst));
   spvCheckReturn(ModuleLayoutPass(_, inst));
   spvCheckReturn(CfgPass(_, inst));
-  spvCheckReturn(SsaPass(_, inst));
   spvCheckReturn(InstructionPass(_, inst));
 
   return SPV_SUCCESS;
 }
 
+void printDot(const ValidationState_t& _, const libspirv::BasicBlock& other) {
+  string block_string;
+  if (other.successors()->empty()) {
+    block_string += "end ";
+  } else {
+    for (auto block : *other.successors()) {
+      block_string += _.getIdOrName(block->id()) + " ";
+    }
+  }
+  printf("%10s -> {%s\b}\n", _.getIdOrName(other.id()).c_str(),
+         block_string.c_str());
+}
+
+void PrintBlocks(ValidationState_t& _, libspirv::Function func) {
+  assert(func.first_block());
+
+  printf("%10s -> %s\n", _.getIdOrName(func.id()).c_str(),
+         _.getIdOrName(func.first_block()->id()).c_str());
+  for (const auto& block : func.ordered_blocks()) {
+    printDot(_, *block);
+  }
+}
+
+#ifdef __clang__
+#define UNUSED(func) [[gnu::unused]] func
+#elif defined(__GNUC__)
+#define UNUSED(func) func __attribute__((unused)); func
+#elif defined(_MSC_VER)
+#define UNUSED(func) func
+#endif
+
+UNUSED(void PrintDotGraph(ValidationState_t& _, libspirv::Function func)) {
+  if (func.first_block()) {
+    string func_name(_.getIdOrName(func.id()));
+    printf("digraph %s {\n", func_name.c_str());
+    PrintBlocks(_, func);
+    printf("}\n");
+  }
+}
 }  // anonymous namespace
 
 spv_result_t spvValidate(const spv_const_context context,
@@ -196,7 +218,7 @@
     vector<uint32_t> ids = vstate.UnresolvedForwardIds();
 
     transform(begin(ids), end(ids), ostream_iterator<string>(ss, " "),
-              bind(&ValidationState_t::getIdName, vstate, _1));
+              bind(&ValidationState_t::getIdName, std::ref(vstate), _1));
 
     auto id_str = ss.str();
     return vstate.diag(SPV_ERROR_INVALID_ID)
@@ -207,6 +229,7 @@
   // CFG checks are performed after the binary has been parsed
   // and the CFGPass has collected information about the control flow
   spvCheckReturn(PerformCfgChecks(vstate));
+  spvCheckReturn(CheckIdDefinitionDominateUse(vstate));
 
   // NOTE: Copy each instruction for easier processing
   std::vector<spv_instruction_t> instructions;
diff --git a/source/validate.h b/source/validate.h
index 35ec9a6..c6ffac0 100644
--- a/source/validate.h
+++ b/source/validate.h
@@ -27,37 +27,49 @@
 #ifndef LIBSPIRV_VALIDATE_H_
 #define LIBSPIRV_VALIDATE_H_
 
-#include <algorithm>
-#include <array>
 #include <functional>
-#include <list>
-#include <map>
-#include <string>
-#include <unordered_map>
-#include <unordered_set>
 #include <utility>
 #include <vector>
 
-#include "assembly_grammar.h"
-#include "binary.h"
-#include "diagnostic.h"
 #include "instruction.h"
 #include "spirv-tools/libspirv.h"
-#include "spirv_definition.h"
 #include "table.h"
-#include "val/BasicBlock.h"
-
-// Structures
 
 namespace libspirv {
 
 class ValidationState_t;
+class BasicBlock;
 
 /// A function that returns a vector of BasicBlocks given a BasicBlock. Used to
 /// get the successor and predecessor nodes of a CFG block
 using get_blocks_func =
     std::function<const std::vector<BasicBlock*>*(const BasicBlock*)>;
 
+/// @brief Depth first traversal starting from the \p entry BasicBlock
+///
+/// This function performs a depth first traversal from the \p entry
+/// BasicBlock and calls the pre/postorder functions when it needs to process
+/// the node in pre order, post order. It also calls the backedge function
+/// when a back edge is encountered.
+///
+/// @param[in] entry      The root BasicBlock of a CFG
+/// @param[in] successor_func  A function which will return a pointer to the
+///                            successor nodes
+/// @param[in] preorder   A function that will be called for every block in a
+///                       CFG following preorder traversal semantics
+/// @param[in] postorder  A function that will be called for every block in a
+///                       CFG following postorder traversal semantics
+/// @param[in] backedge   A function that will be called when a backedge is
+///                       encountered during a traversal
+/// NOTE: The @p successor_func and predecessor_func each return a pointer to a
+/// collection such that iterators to that collection remain valid for the
+/// lifetime of the algorithm.
+void DepthFirstTraversal(
+    const BasicBlock* entry, get_blocks_func successor_func,
+    std::function<void(const BasicBlock*)> preorder,
+    std::function<void(const BasicBlock*)> postorder,
+    std::function<void(const BasicBlock*, const BasicBlock*)> backedge);
+
 /// @brief Calculates dominator edges for a set of blocks
 ///
 /// Computes dominators using the algorithm of Cooper, Harvey, and Kennedy
@@ -89,6 +101,18 @@
 /// @return SPV_SUCCESS if no errors are found. SPV_ERROR_INVALID_CFG otherwise
 spv_result_t PerformCfgChecks(ValidationState_t& _);
 
+/// @brief This function checks all ID definitions dominate their use in the
+/// CFG.
+///
+/// This function will iterate over all ID definitions that are defined in the
+/// functions of a module and make sure that the definitions appear in a
+/// block that dominates their use.
+///
+/// @param[in] _ the validation state of the module
+///
+/// @return SPV_SUCCESS if no errors are found. SPV_ERROR_INVALID_ID otherwise
+spv_result_t CheckIdDefinitionDominateUse(const ValidationState_t& _);
+
 /// @brief Updates the immediate dominator for each of the block edges
 ///
 /// Updates the immediate dominator of the blocks for each of the edges
@@ -115,9 +139,8 @@
 spv_result_t CfgPass(ValidationState_t& _,
                      const spv_parsed_instruction_t* inst);
 
-/// Performs SSA validation of a module
-spv_result_t SsaPass(ValidationState_t& _,
-                     const spv_parsed_instruction_t* inst);
+/// Performs Id and SSA validation of a module
+spv_result_t IdPass(ValidationState_t& _, const spv_parsed_instruction_t* inst);
 
 /// Performs instruction validation.
 spv_result_t InstructionPass(ValidationState_t& _,
diff --git a/source/validate_cfg.cpp b/source/validate_cfg.cpp
index a95f2cd..6342a91 100644
--- a/source/validate_cfg.cpp
+++ b/source/validate_cfg.cpp
@@ -30,8 +30,9 @@
 
 #include <algorithm>
 #include <functional>
-#include <set>
+#include <map>
 #include <string>
+#include <tuple>
 #include <unordered_map>
 #include <unordered_set>
 #include <utility>
@@ -47,12 +48,14 @@
 using std::get;
 using std::ignore;
 using std::make_pair;
+using std::make_tuple;
 using std::numeric_limits;
 using std::pair;
 using std::set;
 using std::string;
 using std::tie;
 using std::transform;
+using std::tuple;
 using std::unordered_map;
 using std::unordered_set;
 using std::vector;
@@ -86,26 +89,8 @@
   return false;
 }
 
-/// @brief Depth first traversal starting from the \p entry BasicBlock
-///
-/// This function performs a depth first traversal from the \p entry
-/// BasicBlock and calls the pre/postorder functions when it needs to process
-/// the node in pre order, post order. It also calls the backedge function
-/// when a back edge is encountered.
-///
-/// @param[in] entry      The root BasicBlock of a CFG
-/// @param[in] successor_func  A function which will return a pointer to the
-///                            successor nodes
-/// @param[in] preorder   A function that will be called for every block in a
-///                       CFG following preorder traversal semantics
-/// @param[in] postorder  A function that will be called for every block in a
-///                       CFG following postorder traversal semantics
-/// @param[in] backedge   A function that will be called when a backedge is
-///                       encountered during a traversal
-/// NOTE: The @p successor_func and predecessor_func each return a pointer to a
-/// collection such that iterators to that collection remain valid for the
-/// lifetime
-/// of the algorithm
+}  // namespace
+
 void DepthFirstTraversal(const BasicBlock* entry,
                          get_blocks_func successor_func,
                          function<void(cbb_ptr)> preorder,
@@ -143,8 +128,6 @@
   }
 }
 
-}  // namespace
-
 vector<pair<BasicBlock*, BasicBlock*>> CalculateDominators(
     const vector<cbb_ptr>& postorder, get_blocks_func predecessor_func) {
   struct block_detail {
@@ -164,10 +147,12 @@
     changed = false;
     for (auto b = postorder.rbegin() + 1; b != postorder.rend(); ++b) {
       const vector<BasicBlock*>& predecessors = *predecessor_func(*b);
-      // first processed/reachable predecessor
+      // Find the first processed/reachable predecessor that is reachable
+      // in the forward traversal.
       auto res = find_if(begin(predecessors), end(predecessors),
                          [&idoms, undefined_dom](BasicBlock* pred) {
-                           return idoms[pred].dominator != undefined_dom;
+                           return idoms.count(pred) &&
+                                  idoms[pred].dominator != undefined_dom;
                          });
       if (res == end(predecessors)) continue;
       const BasicBlock* idom = *res;
@@ -176,6 +161,10 @@
       // all other predecessors
       for (const auto* p : predecessors) {
         if (idom == p) continue;
+        // Only consider nodes reachable in the forward traversal.
+        // Otherwise the intersection doesn't make sense and will never
+        // terminate.
+        if (!idoms.count(p)) continue;
         if (idoms[p].dominator != undefined_dom) {
           size_t finger1 = idoms[p].postorder_index;
           size_t finger2 = idom_idx;
@@ -248,7 +237,6 @@
     uint32_t back_edge_block_id;
     uint32_t loop_header_block_id;
     tie(back_edge_block_id, loop_header_block_id) = edge;
-
     auto is_this_header = [=](Construct& c) {
       return c.type() == ConstructType::kLoop &&
              c.entry_block()->id() == loop_header_block_id;
@@ -268,22 +256,10 @@
   }
 }
 
-/// Constructs an error message for construct validation errors
-string ConstructErrorString(const Construct& construct,
-                            const string& header_string,
-                            const string& exit_string,
-                            bool post_dominate = false) {
-  string construct_name;
-  string header_name;
-  string exit_name;
-  string dominate_text;
-  if (post_dominate) {
-    dominate_text = "is not post dominated by";
-  } else {
-    dominate_text = "does not dominate";
-  }
+tuple<string, string, string> ConstructNames(ConstructType type) {
+  string construct_name, header_name, exit_name;
 
-  switch (construct.type()) {
+  switch (type) {
     case ConstructType::kSelection:
       construct_name = "selection";
       header_name = "selection header";
@@ -301,12 +277,31 @@
       break;
     case ConstructType::kCase:
       construct_name = "case";
-      header_name = "case block";
-      exit_name = "exit block";  // TODO(umar): there has to be a better name
+      header_name = "case entry block";
+      exit_name = "case exit block";
       break;
     default:
       assert(1 == 0 && "Not defined type");
   }
+
+  return make_tuple(construct_name, header_name, exit_name);
+}
+
+/// Constructs an error message for construct validation errors
+string ConstructErrorString(const Construct& construct,
+                            const string& header_string,
+                            const string& exit_string,
+                            bool post_dominate = false) {
+  string construct_name, header_name, exit_name, dominate_text;
+  if (post_dominate) {
+    dominate_text = "is not post dominated by";
+  } else {
+    dominate_text = "does not dominate";
+  }
+
+  tie(construct_name, header_name, exit_name) =
+      ConstructNames(construct.type());
+
   // TODO(umar): Add header block for continue constructs to error message
   return "The " + construct_name + " construct with the " + header_name + " " +
          header_string + " " + dominate_text + " the " + exit_name + " " +
@@ -318,7 +313,9 @@
     const vector<pair<uint32_t, uint32_t>>& back_edges) {
   /// Check all backedges target only loop headers and have exactly one
   /// back-edge branching to it
-  set<uint32_t> loop_headers;
+
+  // Map a loop header to blocks with back-edges to the loop header.
+  std::map<uint32_t, std::unordered_set<uint32_t>> loop_latch_blocks;
   for (auto back_edge : back_edges) {
     uint32_t back_edge_block;
     uint32_t header_block;
@@ -329,14 +326,20 @@
              << _.getIdName(header_block)
              << ") can only be formed between a block and a loop header.";
     }
-    bool success;
-    tie(ignore, success) = loop_headers.insert(header_block);
-    if (!success) {
-      // TODO(umar): List the back-edge blocks that are branching to loop
-      // header
+    loop_latch_blocks[header_block].insert(back_edge_block);
+  }
+
+  // Check the loop headers have exactly one back-edge branching to it
+  for (BasicBlock* loop_header : function.ordered_blocks()) {
+    if (!loop_header->reachable()) continue;
+    if (!loop_header->is_type(kBlockTypeLoop)) continue;
+    auto loop_header_id = loop_header->id();
+    auto num_latch_blocks = loop_latch_blocks[loop_header_id].size();
+    if (num_latch_blocks != 1) {
       return _.diag(SPV_ERROR_INVALID_CFG)
-             << "Loop header " << _.getIdName(header_block)
-             << " targeted by multiple back-edges";
+             << "Loop header " << _.getIdName(loop_header_id)
+             << " is targeted by " << num_latch_blocks
+             << " back-edge blocks but the standard requires exactly one";
     }
   }
 
@@ -345,15 +348,27 @@
     auto header = construct.entry_block();
     auto merge = construct.exit_block();
 
-    // if the merge block is reachable then it's dominated by the header
-    if (merge->reachable() &&
+    if (header->reachable() && !merge) {
+      string construct_name, header_name, exit_name;
+      tie(construct_name, header_name, exit_name) =
+          ConstructNames(construct.type());
+      return _.diag(SPV_ERROR_INTERNAL)
+             << "Construct " + construct_name + " with " + header_name + " " +
+                    _.getIdName(header->id()) + " does not have a " +
+                    exit_name + ". This may be a bug in the validator.";
+    }
+
+    // If the merge block is reachable then it's dominated by the header.
+    if (merge && merge->reachable() &&
         find(merge->dom_begin(), merge->dom_end(), header) ==
             merge->dom_end()) {
       return _.diag(SPV_ERROR_INVALID_CFG)
              << ConstructErrorString(construct, _.getIdName(header->id()),
                                      _.getIdName(merge->id()));
     }
-    if (construct.type() == ConstructType::kContinue) {
+    // Check post-dominance for continue constructs.  But dominance and
+    // post-dominance only make sense when the construct is reachable.
+    if (header->reachable() && construct.type() == ConstructType::kContinue) {
       if (find(header->pdom_begin(), header->pdom_end(), merge) ==
           merge->pdom_end()) {
         return _.diag(SPV_ERROR_INVALID_CFG)
@@ -389,70 +404,49 @@
              << _.getIdName(function.id());
     }
 
-    // Prepare for dominance calculations.  We want to analyze all the
-    // blocks in the function, even in degenerate control flow cases
-    // including unreachable blocks.  For this calculation, we create an
-    // agumented CFG by adding a pseudo-entry node that is considered the
-    // predecessor of each source in the original CFG, and a pseudo-exit
-    // node that is considered the successor to each sink in the original
-    // CFG.  The augmented CFG is guaranteed to have a single source node
-    // (i.e. not having predecessors) and a single exit node (i.e. not
-    // having successors).  However, there might be isolated strongly
-    // connected components that are not reachable by following successors
-    // from the pseudo entry node, and not reachable by following
-    // predecessors from the pseudo exit node.
-
-    auto* pseudo_entry = function.pseudo_entry_block();
-    auto* pseudo_exit = function.pseudo_exit_block();
-    // We need vectors to use as the predecessors (in the augmented CFG)
-    // for the source nodes of the original CFG.  It must have a stable
-    // address for the duration of the calculation.
-    auto* pseudo_entry_vec = function.pseudo_entry_blocks();
-    // Similarly, we need a vector to be used as the successors (in the
-    // augmented CFG) for sinks in the original CFG.
-    auto* pseudo_exit_vec = function.pseudo_exit_blocks();
-    // Returns the predecessors of a block in the augmented CFG.
-    auto augmented_predecessor_fn = [pseudo_entry, pseudo_entry_vec](
-        const BasicBlock* block) {
-      auto predecessors = block->predecessors();
-      return (block != pseudo_entry && predecessors->empty()) ? pseudo_entry_vec
-                                                              : predecessors;
-    };
-    // Returns the successors of a block in the augmented CFG.
-    auto augmented_successor_fn = [pseudo_exit,
-                                   pseudo_exit_vec](const BasicBlock* block) {
-      auto successors = block->successors();
-      return (block != pseudo_exit && successors->empty()) ? pseudo_exit_vec
-                                                           : successors;
-    };
-
     // Set each block's immediate dominator and immediate postdominator,
     // and find all back-edges.
+    //
+    // We want to analyze all the blocks in the function, even in degenerate
+    // control flow cases including unreachable blocks.  So use the augmented
+    // CFG to ensure we cover all the blocks.
     vector<const BasicBlock*> postorder;
     vector<const BasicBlock*> postdom_postorder;
     vector<pair<uint32_t, uint32_t>> back_edges;
+    auto ignore_block = [](cbb_ptr) {};
+    auto ignore_edge = [](cbb_ptr, cbb_ptr) {};
     if (!function.ordered_blocks().empty()) {
       /// calculate dominators
-      DepthFirstTraversal(pseudo_entry, augmented_successor_fn, [](cbb_ptr) {},
+      DepthFirstTraversal(function.first_block(),
+                          function.AugmentedCFGSuccessorsFunction(),
+                          ignore_block,
                           [&](cbb_ptr b) { postorder.push_back(b); },
-                          [&](cbb_ptr from, cbb_ptr to) {
-                            back_edges.emplace_back(from->id(), to->id());
-                          });
-      auto edges =
-          libspirv::CalculateDominators(postorder, augmented_predecessor_fn);
+                          ignore_edge);
+      auto edges = libspirv::CalculateDominators(
+          postorder, function.AugmentedCFGPredecessorsFunction());
       for (auto edge : edges) {
         edge.first->SetImmediateDominator(edge.second);
       }
 
       /// calculate post dominators
-      DepthFirstTraversal(pseudo_exit, augmented_predecessor_fn, [](cbb_ptr) {},
+      DepthFirstTraversal(function.pseudo_exit_block(),
+                          function.AugmentedCFGPredecessorsFunction(),
+                          ignore_block,
                           [&](cbb_ptr b) { postdom_postorder.push_back(b); },
-                          [&](cbb_ptr, cbb_ptr) {});
+                          ignore_edge);
       auto postdom_edges = libspirv::CalculateDominators(
-          postdom_postorder, augmented_successor_fn);
+          postdom_postorder, function.AugmentedCFGSuccessorsFunction());
       for (auto edge : postdom_edges) {
         edge.first->SetImmediatePostDominator(edge.second);
       }
+      /// calculate back edges.
+      DepthFirstTraversal(
+          function.pseudo_entry_block(),
+          function
+              .AugmentedCFGSuccessorsFunctionIncludingHeaderToContinueEdge(),
+          ignore_block, ignore_block, [&](cbb_ptr from, cbb_ptr to) {
+            back_edges.emplace_back(from->id(), to->id());
+          });
     }
     UpdateContinueConstructExitBlocks(function, back_edges);
 
@@ -462,7 +456,7 @@
     if (blocks.empty() == false) {
       for (auto block = begin(blocks) + 1; block != end(blocks); ++block) {
         if (auto idom = (*block)->immediate_dominator()) {
-          if (idom != pseudo_entry &&
+          if (idom != function.pseudo_entry_block() &&
               block == std::find(begin(blocks), block, idom)) {
             return _.diag(SPV_ERROR_INVALID_CFG)
                    << "Block " << _.getIdName((*block)->id())
diff --git a/source/validate_id.cpp b/source/validate_id.cpp
index e98b339..b94e861 100644
--- a/source/validate_id.cpp
+++ b/source/validate_id.cpp
@@ -28,6 +28,7 @@
 
 #include <cassert>
 
+#include <algorithm>
 #include <iostream>
 #include <unordered_map>
 #include <vector>
@@ -43,7 +44,9 @@
     action;                         \
   }
 
-using UseDefTracker = libspirv::ValidationState_t::UseDefTracker;
+using libspirv::ValidationState_t;
+using std::function;
+using std::ignore;
 
 namespace {
 
@@ -55,7 +58,7 @@
           const spv_instruction_t* pInsts, const uint64_t instCountArg,
           const SpvMemoryModel memoryModelArg,
           const SpvAddressingModel addressingModelArg,
-          const UseDefTracker& usedefs,
+          const ValidationState_t& module,
           const std::vector<uint32_t>& entry_points, spv_position positionArg,
           spv_diagnostic* pDiagnosticArg)
       : opcodeTable(opcodeTableArg),
@@ -67,7 +70,7 @@
         addressingModel(addressingModelArg),
         position(positionArg),
         pDiagnostic(pDiagnosticArg),
-        usedefs_(usedefs),
+        module_(module),
         entry_points_(entry_points) {}
 
   bool isValid(const spv_instruction_t* inst);
@@ -85,7 +88,7 @@
   const SpvAddressingModel addressingModel;
   spv_position position;
   spv_diagnostic* pDiagnostic;
-  UseDefTracker usedefs_;
+  const ValidationState_t& module_;
   std::vector<uint32_t> entry_points_;
 };
 
@@ -106,20 +109,20 @@
 bool idUsage::isValid<SpvOpMemberName>(const spv_instruction_t* inst,
                                        const spv_opcode_desc) {
   auto typeIndex = 1;
-  auto type = usedefs_.FindDef(inst->words[typeIndex]);
-  if (!type.first || SpvOpTypeStruct != type.second.opcode) {
+  auto type = module_.FindDef(inst->words[typeIndex]);
+  if (!type || SpvOpTypeStruct != type->opcode()) {
     DIAG(typeIndex) << "OpMemberName Type <id> '" << inst->words[typeIndex]
                     << "' is not a struct type.";
     return false;
   }
   auto memberIndex = 2;
   auto member = inst->words[memberIndex];
-  auto memberCount = (uint32_t)(type.second.words.size() - 2);
+  auto memberCount = (uint32_t)(type->words().size() - 2);
   spvCheck(memberCount <= member, DIAG(memberIndex)
                                       << "OpMemberName Member <id> '"
                                       << inst->words[memberIndex]
                                       << "' index is larger than Type <id> '"
-                                      << type.second.id << "'s member count.";
+                                      << type->id() << "'s member count.";
            return false);
   return true;
 }
@@ -128,8 +131,8 @@
 bool idUsage::isValid<SpvOpLine>(const spv_instruction_t* inst,
                                  const spv_opcode_desc) {
   auto fileIndex = 1;
-  auto file = usedefs_.FindDef(inst->words[fileIndex]);
-  if (!file.first || SpvOpString != file.second.opcode) {
+  auto file = module_.FindDef(inst->words[fileIndex]);
+  if (!file || SpvOpString != file->opcode()) {
     DIAG(fileIndex) << "OpLine Target <id> '" << inst->words[fileIndex]
                     << "' is not an OpString.";
     return false;
@@ -141,8 +144,8 @@
 bool idUsage::isValid<SpvOpMemberDecorate>(const spv_instruction_t* inst,
                                            const spv_opcode_desc) {
   auto structTypeIndex = 1;
-  auto structType = usedefs_.FindDef(inst->words[structTypeIndex]);
-  if (!structType.first || SpvOpTypeStruct != structType.second.opcode) {
+  auto structType = module_.FindDef(inst->words[structTypeIndex]);
+  if (!structType || SpvOpTypeStruct != structType->opcode()) {
     DIAG(structTypeIndex) << "OpMemberDecorate Structure type <id> '"
                           << inst->words[structTypeIndex]
                           << "' is not a struct type.";
@@ -150,7 +153,7 @@
   }
   auto memberIndex = 2;
   auto member = inst->words[memberIndex];
-  auto memberCount = static_cast<uint32_t>(structType.second.words.size() - 2);
+  auto memberCount = static_cast<uint32_t>(structType->words().size() - 2);
   spvCheck(memberCount < member, DIAG(memberIndex)
                                      << "OpMemberDecorate Structure type <id> '"
                                      << inst->words[memberIndex]
@@ -163,9 +166,8 @@
 bool idUsage::isValid<SpvOpGroupDecorate>(const spv_instruction_t* inst,
                                           const spv_opcode_desc) {
   auto decorationGroupIndex = 1;
-  auto decorationGroup = usedefs_.FindDef(inst->words[decorationGroupIndex]);
-  if (!decorationGroup.first ||
-      SpvOpDecorationGroup != decorationGroup.second.opcode) {
+  auto decorationGroup = module_.FindDef(inst->words[decorationGroupIndex]);
+  if (!decorationGroup || SpvOpDecorationGroup != decorationGroup->opcode()) {
     DIAG(decorationGroupIndex) << "OpGroupDecorate Decoration group <id> '"
                                << inst->words[decorationGroupIndex]
                                << "' is not a decoration group.";
@@ -190,8 +192,8 @@
 bool idUsage::isValid<SpvOpEntryPoint>(const spv_instruction_t* inst,
                                        const spv_opcode_desc) {
   auto entryPointIndex = 2;
-  auto entryPoint = usedefs_.FindDef(inst->words[entryPointIndex]);
-  if (!entryPoint.first || SpvOpFunction != entryPoint.second.opcode) {
+  auto entryPoint = module_.FindDef(inst->words[entryPointIndex]);
+  if (!entryPoint || SpvOpFunction != entryPoint->opcode()) {
     DIAG(entryPointIndex) << "OpEntryPoint Entry Point <id> '"
                           << inst->words[entryPointIndex]
                           << "' is not a function.";
@@ -202,16 +204,16 @@
   if (executionModel != SpvExecutionModelKernel) {
     // TODO: Check the entry point signature is void main(void), may be subject
     // to change
-    auto entryPointType = usedefs_.FindDef(entryPoint.second.words[4]);
-    if (!entryPointType.first || 3 != entryPointType.second.words.size()) {
+    auto entryPointType = module_.FindDef(entryPoint->words()[4]);
+    if (!entryPointType || 3 != entryPointType->words().size()) {
       DIAG(entryPointIndex) << "OpEntryPoint Entry Point <id> '"
                             << inst->words[entryPointIndex]
                             << "'s function parameter count is not zero.";
       return false;
     }
   }
-  auto returnType = usedefs_.FindDef(entryPoint.second.type_id);
-  if (!returnType.first || SpvOpTypeVoid != returnType.second.opcode) {
+  auto returnType = module_.FindDef(entryPoint->type_id());
+  if (!returnType || SpvOpTypeVoid != returnType->opcode()) {
     DIAG(entryPointIndex) << "OpEntryPoint Entry Point <id> '"
                           << inst->words[entryPointIndex]
                           << "'s function return type is not void.";
@@ -241,9 +243,8 @@
 bool idUsage::isValid<SpvOpTypeVector>(const spv_instruction_t* inst,
                                        const spv_opcode_desc) {
   auto componentIndex = 2;
-  auto componentType = usedefs_.FindDef(inst->words[componentIndex]);
-  if (!componentType.first ||
-      !spvOpcodeIsScalarType(componentType.second.opcode)) {
+  auto componentType = module_.FindDef(inst->words[componentIndex]);
+  if (!componentType || !spvOpcodeIsScalarType(componentType->opcode())) {
     DIAG(componentIndex) << "OpTypeVector Component Type <id> '"
                          << inst->words[componentIndex]
                          << "' is not a scalar type.";
@@ -256,8 +257,8 @@
 bool idUsage::isValid<SpvOpTypeMatrix>(const spv_instruction_t* inst,
                                        const spv_opcode_desc) {
   auto columnTypeIndex = 2;
-  auto columnType = usedefs_.FindDef(inst->words[columnTypeIndex]);
-  if (!columnType.first || SpvOpTypeVector != columnType.second.opcode) {
+  auto columnType = module_.FindDef(inst->words[columnTypeIndex]);
+  if (!columnType || SpvOpTypeVector != columnType->opcode()) {
     DIAG(columnTypeIndex) << "OpTypeMatrix Column Type <id> '"
                           << inst->words[columnTypeIndex]
                           << "' is not a vector.";
@@ -297,36 +298,35 @@
 bool idUsage::isValid<SpvOpTypeArray>(const spv_instruction_t* inst,
                                       const spv_opcode_desc) {
   auto elementTypeIndex = 2;
-  auto elementType = usedefs_.FindDef(inst->words[elementTypeIndex]);
-  if (!elementType.first ||
-      !spvOpcodeGeneratesType(elementType.second.opcode)) {
+  auto elementType = module_.FindDef(inst->words[elementTypeIndex]);
+  if (!elementType || !spvOpcodeGeneratesType(elementType->opcode())) {
     DIAG(elementTypeIndex) << "OpTypeArray Element Type <id> '"
                            << inst->words[elementTypeIndex]
                            << "' is not a type.";
     return false;
   }
   auto lengthIndex = 3;
-  auto length = usedefs_.FindDef(inst->words[lengthIndex]);
-  if (!length.first || !spvOpcodeIsConstant(length.second.opcode)) {
+  auto length = module_.FindDef(inst->words[lengthIndex]);
+  if (!length || !spvOpcodeIsConstant(length->opcode())) {
     DIAG(lengthIndex) << "OpTypeArray Length <id> '" << inst->words[lengthIndex]
                       << "' is not a scalar constant type.";
     return false;
   }
 
   // NOTE: Check the initialiser value of the constant
-  auto constInst = length.second.words;
+  auto constInst = length->words();
   auto constResultTypeIndex = 1;
-  auto constResultType = usedefs_.FindDef(constInst[constResultTypeIndex]);
-  if (!constResultType.first || SpvOpTypeInt != constResultType.second.opcode) {
+  auto constResultType = module_.FindDef(constInst[constResultTypeIndex]);
+  if (!constResultType || SpvOpTypeInt != constResultType->opcode()) {
     DIAG(lengthIndex) << "OpTypeArray Length <id> '" << inst->words[lengthIndex]
                       << "' is not a constant integer type.";
     return false;
   }
 
-  switch (length.second.opcode) {
+  switch (length->opcode()) {
     case SpvOpSpecConstant:
     case SpvOpConstant:
-      if (aboveZero(length.second.words, constResultType.second.words)) break;
+      if (aboveZero(length->words(), constResultType->words())) break;
     // Else fall through!
     case SpvOpConstantNull: {
       DIAG(lengthIndex) << "OpTypeArray Length <id> '"
@@ -347,9 +347,8 @@
 bool idUsage::isValid<SpvOpTypeRuntimeArray>(const spv_instruction_t* inst,
                                              const spv_opcode_desc) {
   auto elementTypeIndex = 2;
-  auto elementType = usedefs_.FindDef(inst->words[elementTypeIndex]);
-  if (!elementType.first ||
-      !spvOpcodeGeneratesType(elementType.second.opcode)) {
+  auto elementType = module_.FindDef(inst->words[elementTypeIndex]);
+  if (!elementType || !spvOpcodeGeneratesType(elementType->opcode())) {
     DIAG(elementTypeIndex) << "OpTypeRuntimeArray Element Type <id> '"
                            << inst->words[elementTypeIndex]
                            << "' is not a type.";
@@ -363,9 +362,8 @@
                                        const spv_opcode_desc) {
   for (size_t memberTypeIndex = 2; memberTypeIndex < inst->words.size();
        ++memberTypeIndex) {
-    auto memberType = usedefs_.FindDef(inst->words[memberTypeIndex]);
-    if (!memberType.first ||
-        !spvOpcodeGeneratesType(memberType.second.opcode)) {
+    auto memberType = module_.FindDef(inst->words[memberTypeIndex]);
+    if (!memberType || !spvOpcodeGeneratesType(memberType->opcode())) {
       DIAG(memberTypeIndex) << "OpTypeStruct Member Type <id> '"
                             << inst->words[memberTypeIndex]
                             << "' is not a type.";
@@ -379,8 +377,8 @@
 bool idUsage::isValid<SpvOpTypePointer>(const spv_instruction_t* inst,
                                         const spv_opcode_desc) {
   auto typeIndex = 3;
-  auto type = usedefs_.FindDef(inst->words[typeIndex]);
-  if (!type.first || !spvOpcodeGeneratesType(type.second.opcode)) {
+  auto type = module_.FindDef(inst->words[typeIndex]);
+  if (!type || !spvOpcodeGeneratesType(type->opcode())) {
     DIAG(typeIndex) << "OpTypePointer Type <id> '" << inst->words[typeIndex]
                     << "' is not a type.";
     return false;
@@ -392,16 +390,16 @@
 bool idUsage::isValid<SpvOpTypeFunction>(const spv_instruction_t* inst,
                                          const spv_opcode_desc) {
   auto returnTypeIndex = 2;
-  auto returnType = usedefs_.FindDef(inst->words[returnTypeIndex]);
-  if (!returnType.first || !spvOpcodeGeneratesType(returnType.second.opcode)) {
+  auto returnType = module_.FindDef(inst->words[returnTypeIndex]);
+  if (!returnType || !spvOpcodeGeneratesType(returnType->opcode())) {
     DIAG(returnTypeIndex) << "OpTypeFunction Return Type <id> '"
                           << inst->words[returnTypeIndex] << "' is not a type.";
     return false;
   }
   for (size_t paramTypeIndex = 3; paramTypeIndex < inst->words.size();
        ++paramTypeIndex) {
-    auto paramType = usedefs_.FindDef(inst->words[paramTypeIndex]);
-    if (!paramType.first || !spvOpcodeGeneratesType(paramType.second.opcode)) {
+    auto paramType = module_.FindDef(inst->words[paramTypeIndex]);
+    if (!paramType || !spvOpcodeGeneratesType(paramType->opcode())) {
       DIAG(paramTypeIndex) << "OpTypeFunction Parameter Type <id> '"
                            << inst->words[paramTypeIndex] << "' is not a type.";
       return false;
@@ -421,8 +419,8 @@
 bool idUsage::isValid<SpvOpConstantTrue>(const spv_instruction_t* inst,
                                          const spv_opcode_desc) {
   auto resultTypeIndex = 1;
-  auto resultType = usedefs_.FindDef(inst->words[resultTypeIndex]);
-  if (!resultType.first || SpvOpTypeBool != resultType.second.opcode) {
+  auto resultType = module_.FindDef(inst->words[resultTypeIndex]);
+  if (!resultType || SpvOpTypeBool != resultType->opcode()) {
     DIAG(resultTypeIndex) << "OpConstantTrue Result Type <id> '"
                           << inst->words[resultTypeIndex]
                           << "' is not a boolean type.";
@@ -435,8 +433,8 @@
 bool idUsage::isValid<SpvOpConstantFalse>(const spv_instruction_t* inst,
                                           const spv_opcode_desc) {
   auto resultTypeIndex = 1;
-  auto resultType = usedefs_.FindDef(inst->words[resultTypeIndex]);
-  if (!resultType.first || SpvOpTypeBool != resultType.second.opcode) {
+  auto resultType = module_.FindDef(inst->words[resultTypeIndex]);
+  if (!resultType || SpvOpTypeBool != resultType->opcode()) {
     DIAG(resultTypeIndex) << "OpConstantFalse Result Type <id> '"
                           << inst->words[resultTypeIndex]
                           << "' is not a boolean type.";
@@ -449,8 +447,8 @@
 bool idUsage::isValid<SpvOpConstantComposite>(const spv_instruction_t* inst,
                                               const spv_opcode_desc) {
   auto resultTypeIndex = 1;
-  auto resultType = usedefs_.FindDef(inst->words[resultTypeIndex]);
-  if (!resultType.first || !spvOpcodeIsComposite(resultType.second.opcode)) {
+  auto resultType = module_.FindDef(inst->words[resultTypeIndex]);
+  if (!resultType || !spvOpcodeIsComposite(resultType->opcode())) {
     DIAG(resultTypeIndex) << "OpConstantComposite Result Type <id> '"
                           << inst->words[resultTypeIndex]
                           << "' is not a composite type.";
@@ -458,163 +456,155 @@
   }
 
   auto constituentCount = inst->words.size() - 3;
-  switch (resultType.second.opcode) {
+  switch (resultType->opcode()) {
     case SpvOpTypeVector: {
-      auto componentCount = resultType.second.words[3];
+      auto componentCount = resultType->words()[3];
       spvCheck(
           componentCount != constituentCount,
           // TODO: Output ID's on diagnostic
           DIAG(inst->words.size() - 1)
               << "OpConstantComposite Constituent <id> count does not match "
                  "Result Type <id> '"
-              << resultType.second.id << "'s vector component count.";
+              << resultType->id() << "'s vector component count.";
           return false);
-      auto componentType = usedefs_.FindDef(resultType.second.words[2]);
-      assert(componentType.first);
+      auto componentType = module_.FindDef(resultType->words()[2]);
+      assert(componentType);
       for (size_t constituentIndex = 3; constituentIndex < inst->words.size();
            constituentIndex++) {
-        auto constituent = usedefs_.FindDef(inst->words[constituentIndex]);
-        if (!constituent.first ||
-            !spvOpcodeIsConstant(constituent.second.opcode)) {
+        auto constituent = module_.FindDef(inst->words[constituentIndex]);
+        if (!constituent || !spvOpcodeIsConstant(constituent->opcode())) {
           DIAG(constituentIndex) << "OpConstantComposite Constituent <id> '"
                                  << inst->words[constituentIndex]
                                  << "' is not a constant.";
           return false;
         }
-        auto constituentResultType =
-            usedefs_.FindDef(constituent.second.type_id);
-        if (!constituentResultType.first ||
-            componentType.second.opcode !=
-                constituentResultType.second.opcode) {
+        auto constituentResultType = module_.FindDef(constituent->type_id());
+        if (!constituentResultType ||
+            componentType->opcode() != constituentResultType->opcode()) {
           DIAG(constituentIndex) << "OpConstantComposite Constituent <id> '"
                                  << inst->words[constituentIndex]
                                  << "'s type does not match Result Type <id> '"
-                                 << resultType.second.id
+                                 << resultType->id()
                                  << "'s vector element type.";
           return false;
         }
       }
     } break;
     case SpvOpTypeMatrix: {
-      auto columnCount = resultType.second.words[3];
+      auto columnCount = resultType->words()[3];
       spvCheck(
           columnCount != constituentCount,
           // TODO: Output ID's on diagnostic
           DIAG(inst->words.size() - 1)
               << "OpConstantComposite Constituent <id> count does not match "
                  "Result Type <id> '"
-              << resultType.second.id << "'s matrix column count.";
+              << resultType->id() << "'s matrix column count.";
           return false);
 
-      auto columnType = usedefs_.FindDef(resultType.second.words[2]);
-      assert(columnType.first);
-      auto componentCount = columnType.second.words[3];
-      auto componentType = usedefs_.FindDef(columnType.second.words[2]);
-      assert(componentType.first);
+      auto columnType = module_.FindDef(resultType->words()[2]);
+      assert(columnType);
+      auto componentCount = columnType->words()[3];
+      auto componentType = module_.FindDef(columnType->words()[2]);
+      assert(componentType);
 
       for (size_t constituentIndex = 3; constituentIndex < inst->words.size();
            constituentIndex++) {
-        auto constituent = usedefs_.FindDef(inst->words[constituentIndex]);
-        if (!constituent.first ||
-            SpvOpConstantComposite != constituent.second.opcode) {
+        auto constituent = module_.FindDef(inst->words[constituentIndex]);
+        if (!constituent || SpvOpConstantComposite != constituent->opcode()) {
           DIAG(constituentIndex) << "OpConstantComposite Constituent <id> '"
                                  << inst->words[constituentIndex]
                                  << "' is not a constant composite.";
           return false;
         }
-        auto vector = usedefs_.FindDef(constituent.second.type_id);
-        assert(vector.first);
-        spvCheck(columnType.second.opcode != vector.second.opcode,
+        auto vector = module_.FindDef(constituent->type_id());
+        assert(vector);
+        spvCheck(columnType->opcode() != vector->opcode(),
                  DIAG(constituentIndex)
                      << "OpConstantComposite Constituent <id> '"
                      << inst->words[constituentIndex]
                      << "' type does not match Result Type <id> '"
-                     << resultType.second.id << "'s matrix column type.";
+                     << resultType->id() << "'s matrix column type.";
                  return false);
-        auto vectorComponentType = usedefs_.FindDef(vector.second.words[2]);
-        assert(vectorComponentType.first);
-        spvCheck(componentType.second.id != vectorComponentType.second.id,
+        auto vectorComponentType = module_.FindDef(vector->words()[2]);
+        assert(vectorComponentType);
+        spvCheck(componentType->id() != vectorComponentType->id(),
                  DIAG(constituentIndex)
                      << "OpConstantComposite Constituent <id> '"
                      << inst->words[constituentIndex]
                      << "' component type does not match Result Type <id> '"
-                     << resultType.second.id
-                     << "'s matrix column component type.";
+                     << resultType->id() << "'s matrix column component type.";
                  return false);
         spvCheck(
-            componentCount != vector.second.words[3],
+            componentCount != vector->words()[3],
             DIAG(constituentIndex)
                 << "OpConstantComposite Constituent <id> '"
                 << inst->words[constituentIndex]
                 << "' vector component count does not match Result Type <id> '"
-                << resultType.second.id << "'s vector component count.";
+                << resultType->id() << "'s vector component count.";
             return false);
       }
     } break;
     case SpvOpTypeArray: {
-      auto elementType = usedefs_.FindDef(resultType.second.words[2]);
-      assert(elementType.first);
-      auto length = usedefs_.FindDef(resultType.second.words[3]);
-      assert(length.first);
-      spvCheck(length.second.words[3] != constituentCount,
+      auto elementType = module_.FindDef(resultType->words()[2]);
+      assert(elementType);
+      auto length = module_.FindDef(resultType->words()[3]);
+      assert(length);
+      spvCheck(length->words()[3] != constituentCount,
                DIAG(inst->words.size() - 1)
                    << "OpConstantComposite Constituent count does not match "
                       "Result Type <id> '"
-                   << resultType.second.id << "'s array length.";
+                   << resultType->id() << "'s array length.";
                return false);
       for (size_t constituentIndex = 3; constituentIndex < inst->words.size();
            constituentIndex++) {
-        auto constituent = usedefs_.FindDef(inst->words[constituentIndex]);
-        if (!constituent.first ||
-            !spvOpcodeIsConstant(constituent.second.opcode)) {
+        auto constituent = module_.FindDef(inst->words[constituentIndex]);
+        if (!constituent || !spvOpcodeIsConstant(constituent->opcode())) {
           DIAG(constituentIndex) << "OpConstantComposite Constituent <id> '"
                                  << inst->words[constituentIndex]
                                  << "' is not a constant.";
           return false;
         }
-        auto constituentType = usedefs_.FindDef(constituent.second.type_id);
-        assert(constituentType.first);
-        spvCheck(elementType.second.id != constituentType.second.id,
+        auto constituentType = module_.FindDef(constituent->type_id());
+        assert(constituentType);
+        spvCheck(elementType->id() != constituentType->id(),
                  DIAG(constituentIndex)
                      << "OpConstantComposite Constituent <id> '"
                      << inst->words[constituentIndex]
                      << "'s type does not match Result Type <id> '"
-                     << resultType.second.id << "'s array element type.";
+                     << resultType->id() << "'s array element type.";
                  return false);
       }
     } break;
     case SpvOpTypeStruct: {
-      auto memberCount = resultType.second.words.size() - 2;
+      auto memberCount = resultType->words().size() - 2;
       spvCheck(memberCount != constituentCount,
                DIAG(resultTypeIndex)
                    << "OpConstantComposite Constituent <id> '"
                    << inst->words[resultTypeIndex]
                    << "' count does not match Result Type <id> '"
-                   << resultType.second.id << "'s struct member count.";
+                   << resultType->id() << "'s struct member count.";
                return false);
       for (uint32_t constituentIndex = 3, memberIndex = 2;
            constituentIndex < inst->words.size();
            constituentIndex++, memberIndex++) {
-        auto constituent = usedefs_.FindDef(inst->words[constituentIndex]);
-        if (!constituent.first ||
-            !spvOpcodeIsConstant(constituent.second.opcode)) {
+        auto constituent = module_.FindDef(inst->words[constituentIndex]);
+        if (!constituent || !spvOpcodeIsConstant(constituent->opcode())) {
           DIAG(constituentIndex) << "OpConstantComposite Constituent <id> '"
                                  << inst->words[constituentIndex]
                                  << "' is not a constant.";
           return false;
         }
-        auto constituentType = usedefs_.FindDef(constituent.second.type_id);
-        assert(constituentType.first);
+        auto constituentType = module_.FindDef(constituent->type_id());
+        assert(constituentType);
 
-        auto memberType =
-            usedefs_.FindDef(resultType.second.words[memberIndex]);
-        assert(memberType.first);
-        spvCheck(memberType.second.id != constituentType.second.id,
+        auto memberType = module_.FindDef(resultType->words()[memberIndex]);
+        assert(memberType);
+        spvCheck(memberType->id() != constituentType->id(),
                  DIAG(constituentIndex)
                      << "OpConstantComposite Constituent <id> '"
                      << inst->words[constituentIndex]
                      << "' type does not match the Result Type <id> '"
-                     << resultType.second.id << "'s member type.";
+                     << resultType->id() << "'s member type.";
                  return false);
       }
     } break;
@@ -627,8 +617,8 @@
 bool idUsage::isValid<SpvOpConstantSampler>(const spv_instruction_t* inst,
                                             const spv_opcode_desc) {
   auto resultTypeIndex = 1;
-  auto resultType = usedefs_.FindDef(inst->words[resultTypeIndex]);
-  if (!resultType.first || SpvOpTypeSampler != resultType.second.opcode) {
+  auto resultType = module_.FindDef(inst->words[resultTypeIndex]);
+  if (!resultType || SpvOpTypeSampler != resultType->opcode()) {
     DIAG(resultTypeIndex) << "OpConstantSampler Result Type <id> '"
                           << inst->words[resultTypeIndex]
                           << "' is not a sampler type.";
@@ -638,10 +628,10 @@
 }
 
 // True if instruction defines a type that can have a null value, as defined by
-// the SPIR-V spec.  Tracks composite-type components through usedefs to check
+// the SPIR-V spec.  Tracks composite-type components through module to check
 // nullability transitively.
 bool IsTypeNullable(const std::vector<uint32_t>& instruction,
-                    const UseDefTracker& usedefs) {
+                    const ValidationState_t& module) {
   uint16_t opcode;
   uint16_t word_count;
   spvOpcodeSplit(instruction[0], &word_count, &opcode);
@@ -658,15 +648,14 @@
     case SpvOpTypeArray:
     case SpvOpTypeMatrix:
     case SpvOpTypeVector: {
-      auto base_type = usedefs.FindDef(instruction[2]);
-      return base_type.first && IsTypeNullable(base_type.second.words, usedefs);
+      auto base_type = module.FindDef(instruction[2]);
+      return base_type && IsTypeNullable(base_type->words(), module);
     }
     case SpvOpTypeStruct: {
       for (size_t elementIndex = 2; elementIndex < instruction.size();
            ++elementIndex) {
-        auto element = usedefs.FindDef(instruction[elementIndex]);
-        if (!element.first || !IsTypeNullable(element.second.words, usedefs))
-          return false;
+        auto element = module.FindDef(instruction[elementIndex]);
+        if (!element || !IsTypeNullable(element->words(), module)) return false;
       }
       return true;
     }
@@ -679,8 +668,8 @@
 bool idUsage::isValid<SpvOpConstantNull>(const spv_instruction_t* inst,
                                          const spv_opcode_desc) {
   auto resultTypeIndex = 1;
-  auto resultType = usedefs_.FindDef(inst->words[resultTypeIndex]);
-  if (!resultType.first || !IsTypeNullable(resultType.second.words, usedefs_)) {
+  auto resultType = module_.FindDef(inst->words[resultTypeIndex]);
+  if (!resultType || !IsTypeNullable(resultType->words(), module_)) {
     DIAG(resultTypeIndex) << "OpConstantNull Result Type <id> '"
                           << inst->words[resultTypeIndex]
                           << "' cannot have a null value.";
@@ -693,8 +682,8 @@
 bool idUsage::isValid<SpvOpSpecConstantTrue>(const spv_instruction_t* inst,
                                              const spv_opcode_desc) {
   auto resultTypeIndex = 1;
-  auto resultType = usedefs_.FindDef(inst->words[resultTypeIndex]);
-  if (!resultType.first || SpvOpTypeBool != resultType.second.opcode) {
+  auto resultType = module_.FindDef(inst->words[resultTypeIndex]);
+  if (!resultType || SpvOpTypeBool != resultType->opcode()) {
     DIAG(resultTypeIndex) << "OpSpecConstantTrue Result Type <id> '"
                           << inst->words[resultTypeIndex]
                           << "' is not a boolean type.";
@@ -707,8 +696,8 @@
 bool idUsage::isValid<SpvOpSpecConstantFalse>(const spv_instruction_t* inst,
                                               const spv_opcode_desc) {
   auto resultTypeIndex = 1;
-  auto resultType = usedefs_.FindDef(inst->words[resultTypeIndex]);
-  if (!resultType.first || SpvOpTypeBool != resultType.second.opcode) {
+  auto resultType = module_.FindDef(inst->words[resultTypeIndex]);
+  if (!resultType || SpvOpTypeBool != resultType->opcode()) {
     DIAG(resultTypeIndex) << "OpSpecConstantFalse Result Type <id> '"
                           << inst->words[resultTypeIndex]
                           << "' is not a boolean type.";
@@ -732,8 +721,8 @@
 bool idUsage::isValid<SpvOpVariable>(const spv_instruction_t* inst,
                                      const spv_opcode_desc opcodeEntry) {
   auto resultTypeIndex = 1;
-  auto resultType = usedefs_.FindDef(inst->words[resultTypeIndex]);
-  if (!resultType.first || SpvOpTypePointer != resultType.second.opcode) {
+  auto resultType = module_.FindDef(inst->words[resultTypeIndex]);
+  if (!resultType || SpvOpTypePointer != resultType->opcode()) {
     DIAG(resultTypeIndex) << "OpVariable Result Type <id> '"
                           << inst->words[resultTypeIndex]
                           << "' is not a pointer type.";
@@ -741,8 +730,8 @@
   }
   if (opcodeEntry->numTypes < inst->words.size()) {
     auto initialiserIndex = 4;
-    auto initialiser = usedefs_.FindDef(inst->words[initialiserIndex]);
-    if (!initialiser.first || !spvOpcodeIsConstant(initialiser.second.opcode)) {
+    auto initialiser = module_.FindDef(inst->words[initialiserIndex]);
+    if (!initialiser || !spvOpcodeIsConstant(initialiser->opcode())) {
       DIAG(initialiserIndex) << "OpVariable Initializer <id> '"
                              << inst->words[initialiserIndex]
                              << "' is not a constant.";
@@ -756,34 +745,32 @@
 bool idUsage::isValid<SpvOpLoad>(const spv_instruction_t* inst,
                                  const spv_opcode_desc) {
   auto resultTypeIndex = 1;
-  auto resultType = usedefs_.FindDef(inst->words[resultTypeIndex]);
-  spvCheck(!resultType.first, DIAG(resultTypeIndex)
-                                  << "OpLoad Result Type <id> '"
-                                  << inst->words[resultTypeIndex]
-                                  << "' is not defind.";
+  auto resultType = module_.FindDef(inst->words[resultTypeIndex]);
+  spvCheck(!resultType, DIAG(resultTypeIndex) << "OpLoad Result Type <id> '"
+                                              << inst->words[resultTypeIndex]
+                                              << "' is not defind.";
            return false);
   auto pointerIndex = 3;
-  auto pointer = usedefs_.FindDef(inst->words[pointerIndex]);
-  if (!pointer.first ||
-      (addressingModel == SpvAddressingModelLogical &&
-       !spvOpcodeReturnsLogicalPointer(pointer.second.opcode))) {
+  auto pointer = module_.FindDef(inst->words[pointerIndex]);
+  if (!pointer || (addressingModel == SpvAddressingModelLogical &&
+                   !spvOpcodeReturnsLogicalPointer(pointer->opcode()))) {
     DIAG(pointerIndex) << "OpLoad Pointer <id> '" << inst->words[pointerIndex]
                        << "' is not a pointer.";
     return false;
   }
-  auto pointerType = usedefs_.FindDef(pointer.second.type_id);
-  if (!pointerType.first || pointerType.second.opcode != SpvOpTypePointer) {
+  auto pointerType = module_.FindDef(pointer->type_id());
+  if (!pointerType || pointerType->opcode() != SpvOpTypePointer) {
     DIAG(pointerIndex) << "OpLoad type for pointer <id> '"
                        << inst->words[pointerIndex]
                        << "' is not a pointer type.";
     return false;
   }
-  auto pointeeType = usedefs_.FindDef(pointerType.second.words[3]);
-  if (!pointeeType.first || resultType.second.id != pointeeType.second.id) {
+  auto pointeeType = module_.FindDef(pointerType->words()[3]);
+  if (!pointeeType || resultType->id() != pointeeType->id()) {
     DIAG(resultTypeIndex) << "OpLoad Result Type <id> '"
                           << inst->words[resultTypeIndex]
-                          << "' does not match Pointer <id> '"
-                          << pointer.second.id << "'s type.";
+                          << "' does not match Pointer <id> '" << pointer->id()
+                          << "'s type.";
     return false;
   }
   return true;
@@ -793,47 +780,46 @@
 bool idUsage::isValid<SpvOpStore>(const spv_instruction_t* inst,
                                   const spv_opcode_desc) {
   auto pointerIndex = 1;
-  auto pointer = usedefs_.FindDef(inst->words[pointerIndex]);
-  if (!pointer.first ||
-      (addressingModel == SpvAddressingModelLogical &&
-       !spvOpcodeReturnsLogicalPointer(pointer.second.opcode))) {
+  auto pointer = module_.FindDef(inst->words[pointerIndex]);
+  if (!pointer || (addressingModel == SpvAddressingModelLogical &&
+                   !spvOpcodeReturnsLogicalPointer(pointer->opcode()))) {
     DIAG(pointerIndex) << "OpStore Pointer <id> '" << inst->words[pointerIndex]
                        << "' is not a pointer.";
     return false;
   }
-  auto pointerType = usedefs_.FindDef(pointer.second.type_id);
-  if (!pointer.first || pointerType.second.opcode != SpvOpTypePointer) {
+  auto pointerType = module_.FindDef(pointer->type_id());
+  if (!pointer || pointerType->opcode() != SpvOpTypePointer) {
     DIAG(pointerIndex) << "OpStore type for pointer <id> '"
                        << inst->words[pointerIndex]
                        << "' is not a pointer type.";
     return false;
   }
-  auto type = usedefs_.FindDef(pointerType.second.words[3]);
-  assert(type.first);
-  spvCheck(SpvOpTypeVoid == type.second.opcode, DIAG(pointerIndex)
-                                                    << "OpStore Pointer <id> '"
-                                                    << inst->words[pointerIndex]
-                                                    << "'s type is void.";
+  auto type = module_.FindDef(pointerType->words()[3]);
+  assert(type);
+  spvCheck(SpvOpTypeVoid == type->opcode(), DIAG(pointerIndex)
+                                                << "OpStore Pointer <id> '"
+                                                << inst->words[pointerIndex]
+                                                << "'s type is void.";
            return false);
 
   auto objectIndex = 2;
-  auto object = usedefs_.FindDef(inst->words[objectIndex]);
-  if (!object.first || !object.second.type_id) {
+  auto object = module_.FindDef(inst->words[objectIndex]);
+  if (!object || !object->type_id()) {
     DIAG(objectIndex) << "OpStore Object <id> '" << inst->words[objectIndex]
                       << "' is not an object.";
     return false;
   }
-  auto objectType = usedefs_.FindDef(object.second.type_id);
-  assert(objectType.first);
-  spvCheck(SpvOpTypeVoid == objectType.second.opcode,
+  auto objectType = module_.FindDef(object->type_id());
+  assert(objectType);
+  spvCheck(SpvOpTypeVoid == objectType->opcode(),
            DIAG(objectIndex) << "OpStore Object <id> '"
                              << inst->words[objectIndex] << "'s type is void.";
            return false);
 
-  spvCheck(type.second.id != objectType.second.id,
+  spvCheck(type->id() != objectType->id(),
            DIAG(pointerIndex)
                << "OpStore Pointer <id> '" << inst->words[pointerIndex]
-               << "'s type does not match Object <id> '" << objectType.second.id
+               << "'s type does not match Object <id> '" << objectType->id()
                << "'s type.";
            return false);
   return true;
@@ -843,23 +829,23 @@
 bool idUsage::isValid<SpvOpCopyMemory>(const spv_instruction_t* inst,
                                        const spv_opcode_desc) {
   auto targetIndex = 1;
-  auto target = usedefs_.FindDef(inst->words[targetIndex]);
-  if (!target.first) return false;
+  auto target = module_.FindDef(inst->words[targetIndex]);
+  if (!target) return false;
   auto sourceIndex = 2;
-  auto source = usedefs_.FindDef(inst->words[sourceIndex]);
-  if (!source.first) return false;
-  auto targetPointerType = usedefs_.FindDef(target.second.type_id);
-  assert(targetPointerType.first);
-  auto targetType = usedefs_.FindDef(targetPointerType.second.words[3]);
-  assert(targetType.first);
-  auto sourcePointerType = usedefs_.FindDef(source.second.type_id);
-  assert(sourcePointerType.first);
-  auto sourceType = usedefs_.FindDef(sourcePointerType.second.words[3]);
-  assert(sourceType.first);
-  spvCheck(targetType.second.id != sourceType.second.id,
+  auto source = module_.FindDef(inst->words[sourceIndex]);
+  if (!source) return false;
+  auto targetPointerType = module_.FindDef(target->type_id());
+  assert(targetPointerType);
+  auto targetType = module_.FindDef(targetPointerType->words()[3]);
+  assert(targetType);
+  auto sourcePointerType = module_.FindDef(source->type_id());
+  assert(sourcePointerType);
+  auto sourceType = module_.FindDef(sourcePointerType->words()[3]);
+  assert(sourceType);
+  spvCheck(targetType->id() != sourceType->id(),
            DIAG(sourceIndex)
                << "OpCopyMemory Target <id> '" << inst->words[sourceIndex]
-               << "'s type does not match Source <id> '" << sourceType.second.id
+               << "'s type does not match Source <id> '" << sourceType->id()
                << "'s type.";
            return false);
   return true;
@@ -869,47 +855,45 @@
 bool idUsage::isValid<SpvOpCopyMemorySized>(const spv_instruction_t* inst,
                                             const spv_opcode_desc) {
   auto targetIndex = 1;
-  auto target = usedefs_.FindDef(inst->words[targetIndex]);
-  if (!target.first) return false;
+  auto target = module_.FindDef(inst->words[targetIndex]);
+  if (!target) return false;
   auto sourceIndex = 2;
-  auto source = usedefs_.FindDef(inst->words[sourceIndex]);
-  if (!source.first) return false;
+  auto source = module_.FindDef(inst->words[sourceIndex]);
+  if (!source) return false;
   auto sizeIndex = 3;
-  auto size = usedefs_.FindDef(inst->words[sizeIndex]);
-  if (!size.first) return false;
-  auto targetPointerType = usedefs_.FindDef(target.second.type_id);
-  spvCheck(!targetPointerType.first ||
-               SpvOpTypePointer != targetPointerType.second.opcode,
-           DIAG(targetIndex) << "OpCopyMemorySized Target <id> '"
-                             << inst->words[targetIndex]
-                             << "' is not a pointer.";
-           return false);
-  auto sourcePointerType = usedefs_.FindDef(source.second.type_id);
-  spvCheck(!sourcePointerType.first ||
-               SpvOpTypePointer != sourcePointerType.second.opcode,
-           DIAG(sourceIndex) << "OpCopyMemorySized Source <id> '"
-                             << inst->words[sourceIndex]
-                             << "' is not a pointer.";
-           return false);
-  switch (size.second.opcode) {
+  auto size = module_.FindDef(inst->words[sizeIndex]);
+  if (!size) return false;
+  auto targetPointerType = module_.FindDef(target->type_id());
+  spvCheck(
+      !targetPointerType || SpvOpTypePointer != targetPointerType->opcode(),
+      DIAG(targetIndex) << "OpCopyMemorySized Target <id> '"
+                        << inst->words[targetIndex] << "' is not a pointer.";
+      return false);
+  auto sourcePointerType = module_.FindDef(source->type_id());
+  spvCheck(
+      !sourcePointerType || SpvOpTypePointer != sourcePointerType->opcode(),
+      DIAG(sourceIndex) << "OpCopyMemorySized Source <id> '"
+                        << inst->words[sourceIndex] << "' is not a pointer.";
+      return false);
+  switch (size->opcode()) {
     // TODO: The following opcode's are assumed to be valid, refer to the
     // following bug https://cvs.khronos.org/bugzilla/show_bug.cgi?id=13871 for
     // clarification
     case SpvOpConstant:
     case SpvOpSpecConstant: {
-      auto sizeType = usedefs_.FindDef(size.second.type_id);
-      assert(sizeType.first);
-      spvCheck(SpvOpTypeInt != sizeType.second.opcode,
+      auto sizeType = module_.FindDef(size->type_id());
+      assert(sizeType);
+      spvCheck(SpvOpTypeInt != sizeType->opcode(),
                DIAG(sizeIndex) << "OpCopyMemorySized Size <id> '"
                                << inst->words[sizeIndex]
                                << "'s type is not an integer type.";
                return false);
     } break;
     case SpvOpVariable: {
-      auto pointerType = usedefs_.FindDef(size.second.type_id);
-      assert(pointerType.first);
-      auto sizeType = usedefs_.FindDef(pointerType.second.type_id);
-      spvCheck(!sizeType.first || SpvOpTypeInt != sizeType.second.opcode,
+      auto pointerType = module_.FindDef(size->type_id());
+      assert(pointerType);
+      auto sizeType = module_.FindDef(pointerType->type_id());
+      spvCheck(!sizeType || SpvOpTypeInt != sizeType->opcode(),
                DIAG(sizeIndex) << "OpCopyMemorySized Size <id> '"
                                << inst->words[sizeIndex]
                                << "'s variable type is not an integer type.";
@@ -960,23 +944,23 @@
 bool idUsage::isValid<SpvOpFunction>(const spv_instruction_t* inst,
                                      const spv_opcode_desc) {
   auto resultTypeIndex = 1;
-  auto resultType = usedefs_.FindDef(inst->words[resultTypeIndex]);
-  if (!resultType.first) return false;
+  auto resultType = module_.FindDef(inst->words[resultTypeIndex]);
+  if (!resultType) return false;
   auto functionTypeIndex = 4;
-  auto functionType = usedefs_.FindDef(inst->words[functionTypeIndex]);
-  if (!functionType.first || SpvOpTypeFunction != functionType.second.opcode) {
+  auto functionType = module_.FindDef(inst->words[functionTypeIndex]);
+  if (!functionType || SpvOpTypeFunction != functionType->opcode()) {
     DIAG(functionTypeIndex) << "OpFunction Function Type <id> '"
                             << inst->words[functionTypeIndex]
                             << "' is not a function type.";
     return false;
   }
-  auto returnType = usedefs_.FindDef(functionType.second.words[2]);
-  assert(returnType.first);
-  spvCheck(returnType.second.id != resultType.second.id,
+  auto returnType = module_.FindDef(functionType->words()[2]);
+  assert(returnType);
+  spvCheck(returnType->id() != resultType->id(),
            DIAG(resultTypeIndex) << "OpFunction Result Type <id> '"
                                  << inst->words[resultTypeIndex]
                                  << "' does not match the Function Type <id> '"
-                                 << resultType.second.id << "'s return type.";
+                                 << resultType->id() << "'s return type.";
            return false);
   return true;
 }
@@ -985,8 +969,8 @@
 bool idUsage::isValid<SpvOpFunctionParameter>(const spv_instruction_t* inst,
                                               const spv_opcode_desc) {
   auto resultTypeIndex = 1;
-  auto resultType = usedefs_.FindDef(inst->words[resultTypeIndex]);
-  if (!resultType.first) return false;
+  auto resultType = module_.FindDef(inst->words[resultTypeIndex]);
+  if (!resultType) return false;
   // NOTE: Find OpFunction & ensure OpFunctionParameter is not out of place.
   size_t paramIndex = 0;
   assert(firstInst < inst && "Invalid instruction pointer");
@@ -997,17 +981,17 @@
       paramIndex++;
     }
   }
-  auto functionType = usedefs_.FindDef(inst->words[4]);
-  assert(functionType.first);
-  if (paramIndex >= functionType.second.words.size() - 3) {
+  auto functionType = module_.FindDef(inst->words[4]);
+  assert(functionType);
+  if (paramIndex >= functionType->words().size() - 3) {
     DIAG(0) << "Too many OpFunctionParameters for " << inst->words[2]
-            << ": expected " << functionType.second.words.size() - 3
+            << ": expected " << functionType->words().size() - 3
             << " based on the function's type";
     return false;
   }
-  auto paramType = usedefs_.FindDef(functionType.second.words[paramIndex + 3]);
-  assert(paramType.first);
-  spvCheck(resultType.second.id != paramType.second.id,
+  auto paramType = module_.FindDef(functionType->words()[paramIndex + 3]);
+  assert(paramType);
+  spvCheck(resultType->id() != paramType->id(),
            DIAG(resultTypeIndex)
                << "OpFunctionParameter Result Type <id> '"
                << inst->words[resultTypeIndex]
@@ -1021,27 +1005,27 @@
 bool idUsage::isValid<SpvOpFunctionCall>(const spv_instruction_t* inst,
                                          const spv_opcode_desc) {
   auto resultTypeIndex = 1;
-  auto resultType = usedefs_.FindDef(inst->words[resultTypeIndex]);
-  if (!resultType.first) return false;
+  auto resultType = module_.FindDef(inst->words[resultTypeIndex]);
+  if (!resultType) return false;
   auto functionIndex = 3;
-  auto function = usedefs_.FindDef(inst->words[functionIndex]);
-  if (!function.first || SpvOpFunction != function.second.opcode) {
+  auto function = module_.FindDef(inst->words[functionIndex]);
+  if (!function || SpvOpFunction != function->opcode()) {
     DIAG(functionIndex) << "OpFunctionCall Function <id> '"
                         << inst->words[functionIndex] << "' is not a function.";
     return false;
   }
-  auto returnType = usedefs_.FindDef(function.second.type_id);
-  assert(returnType.first);
-  spvCheck(returnType.second.id != resultType.second.id,
+  auto returnType = module_.FindDef(function->type_id());
+  assert(returnType);
+  spvCheck(returnType->id() != resultType->id(),
            DIAG(resultTypeIndex) << "OpFunctionCall Result Type <id> '"
                                  << inst->words[resultTypeIndex]
                                  << "'s type does not match Function <id> '"
-                                 << returnType.second.id << "'s return type.";
+                                 << returnType->id() << "'s return type.";
            return false);
-  auto functionType = usedefs_.FindDef(function.second.words[4]);
-  assert(functionType.first);
+  auto functionType = module_.FindDef(function->words()[4]);
+  assert(functionType);
   auto functionCallArgCount = inst->words.size() - 4;
-  auto functionParamCount = functionType.second.words.size() - 3;
+  auto functionParamCount = functionType->words().size() - 3;
   spvCheck(
       functionParamCount != functionCallArgCount,
       DIAG(inst->words.size() - 1)
@@ -1050,19 +1034,17 @@
       return false);
   for (size_t argumentIndex = 4, paramIndex = 3;
        argumentIndex < inst->words.size(); argumentIndex++, paramIndex++) {
-    auto argument = usedefs_.FindDef(inst->words[argumentIndex]);
-    if (!argument.first) return false;
-    auto argumentType = usedefs_.FindDef(argument.second.type_id);
-    assert(argumentType.first);
-    auto parameterType =
-        usedefs_.FindDef(functionType.second.words[paramIndex]);
-    assert(parameterType.first);
-    spvCheck(argumentType.second.id != parameterType.second.id,
+    auto argument = module_.FindDef(inst->words[argumentIndex]);
+    if (!argument) return false;
+    auto argumentType = module_.FindDef(argument->type_id());
+    assert(argumentType);
+    auto parameterType = module_.FindDef(functionType->words()[paramIndex]);
+    assert(parameterType);
+    spvCheck(argumentType->id() != parameterType->id(),
              DIAG(argumentIndex) << "OpFunctionCall Argument <id> '"
                                  << inst->words[argumentIndex]
                                  << "'s type does not match Function <id> '"
-                                 << parameterType.second.id
-                                 << "'s parameter type.";
+                                 << parameterType->id() << "'s parameter type.";
              return false);
   }
   return true;
@@ -1698,22 +1680,22 @@
 bool idUsage::isValid<SpvOpReturnValue>(const spv_instruction_t* inst,
                                         const spv_opcode_desc) {
   auto valueIndex = 1;
-  auto value = usedefs_.FindDef(inst->words[valueIndex]);
-  if (!value.first || !value.second.type_id) {
+  auto value = module_.FindDef(inst->words[valueIndex]);
+  if (!value || !value->type_id()) {
     DIAG(valueIndex) << "OpReturnValue Value <id> '" << inst->words[valueIndex]
                      << "' does not represent a value.";
     return false;
   }
-  auto valueType = usedefs_.FindDef(value.second.type_id);
-  if (!valueType.first || SpvOpTypeVoid == valueType.second.opcode) {
-    DIAG(valueIndex) << "OpReturnValue value's type <id> '"
-                     << value.second.type_id << "' is missing or void.";
+  auto valueType = module_.FindDef(value->type_id());
+  if (!valueType || SpvOpTypeVoid == valueType->opcode()) {
+    DIAG(valueIndex) << "OpReturnValue value's type <id> '" << value->type_id()
+                     << "' is missing or void.";
     return false;
   }
   if (addressingModel == SpvAddressingModelLogical &&
-      SpvOpTypePointer == valueType.second.opcode) {
+      SpvOpTypePointer == valueType->opcode()) {
     DIAG(valueIndex)
-        << "OpReturnValue value's type <id> '" << value.second.type_id
+        << "OpReturnValue value's type <id> '" << value->type_id()
         << "' is a pointer, which is invalid in the Logical addressing model.";
     return false;
   }
@@ -1726,8 +1708,8 @@
   spvCheck(SpvOpFunction != function->opcode,
            DIAG(valueIndex) << "OpReturnValue is not in a basic block.";
            return false);
-  auto returnType = usedefs_.FindDef(function->words[1]);
-  spvCheck(!returnType.first || returnType.second.id != valueType.second.id,
+  auto returnType = module_.FindDef(function->words[1]);
+  spvCheck(!returnType || returnType->id() != valueType->id(),
            DIAG(valueIndex)
                << "OpReturnValue Value <id> '" << inst->words[valueIndex]
                << "'s type does not match OpFunction's return type.";
@@ -2338,8 +2320,176 @@
 #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 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:
+      out = [](unsigned index) { return index == 2; };
+      break;
+
+    case SpvOpPhi:
+      out = [](unsigned index) { return index > 1; };
+      break;
+
+    case SpvOpEnqueueKernel:
+      out = [](unsigned index) { return index == 8; };
+      break;
+
+    case SpvOpGetKernelNDrangeSubGroupCount:
+    case SpvOpGetKernelNDrangeMaxSubGroupSize:
+      out = [](unsigned index) { return index == 3; };
+      break;
+
+    case SpvOpGetKernelWorkGroupSize:
+    case SpvOpGetKernelPreferredWorkGroupSizeMultiple:
+      out = [](unsigned index) { return index == 2; };
+      break;
+
+    default:
+      out = [](unsigned) { return false; };
+      break;
+  }
+  return out;
+}
 }  // anonymous namespace
 
+namespace libspirv {
+
+/// This function checks all ID definitions dominate their use in the CFG.
+///
+/// This function will iterate over all ID definitions that are defined in the
+/// functions of a module and make sure that the definitions appear in a
+/// block that dominates their use.
+///
+/// NOTE: This function does NOT check module scoped functions which are
+/// checked during the initial binary parse in the IdPass below
+spv_result_t CheckIdDefinitionDominateUse(const ValidationState_t& _) {
+  for (const auto& definition : _.all_definitions()) {
+    // Check only those blocks defined in a function
+    if (const Function* func = definition.second.defining_function()) {
+      if (const BasicBlock* block = definition.second.defining_block()) {
+        // If the Id is defined within a block then make sure all references to
+        // that Id appear in a blocks that are dominated by the defining block
+        for (auto use : definition.second.uses()) {
+          if (!use->reachable()) continue;
+          if (use->dom_end() == find(use->dom_begin(), use->dom_end(), block)) {
+            return _.diag(SPV_ERROR_INVALID_ID)
+                   << "ID " << _.getIdName(definition.first)
+                   << " defined in block " << _.getIdName(block->id())
+                   << " does not dominate its use in block "
+                   << _.getIdName(use->id());
+          }
+        }
+      } else {
+        // If the Ids defined within a function but not in a block(i.e. function
+        // parameters, block ids), then make sure all references to that Id
+        // appear within the same function
+        bool found = false;
+        for (auto use : definition.second.uses()) {
+          tie(ignore, found) = func->GetBlock(use->id());
+          if (!found) {
+            return _.diag(SPV_ERROR_INVALID_ID)
+                   << "ID " << _.getIdName(definition.first)
+                   << " used in block " << _.getIdName(use->id())
+                   << " is used outside of it's defining function "
+                   << _.getIdName(func->id());
+          }
+        }
+      }
+    }
+    // NOTE: Ids defined outside of functions must appear before they are used
+    // This check is being performed in the IdPass function
+  }
+  // TODO(dneto): We don't track check of IDs by phi nodes.  We should check
+  // that for each (variable, predecessor) pair in an OpPhi, that the variable
+  // is defined in a block that dominates that predecessor block.
+  return SPV_SUCCESS;
+}
+
+// Performs SSA validation on the IDs of an instruction. The
+// can_have_forward_declared_ids  functor should return true if the
+// instruction operand's ID can be forward referenced.
+spv_result_t IdPass(ValidationState_t& _,
+                    const spv_parsed_instruction_t* inst) {
+  auto can_have_forward_declared_ids =
+      getCanBeForwardDeclaredFunction(static_cast<SpvOp>(inst->opcode));
+
+  for (unsigned i = 0; i < inst->num_operands; i++) {
+    const spv_parsed_operand_t& operand = inst->operands[i];
+    const spv_operand_type_t& type = operand.type;
+    const uint32_t* operand_ptr = inst->words + operand.offset;
+
+    auto ret = SPV_ERROR_INTERNAL;
+    switch (type) {
+      case SPV_OPERAND_TYPE_RESULT_ID:
+        // NOTE: Multiple Id definitions are being checked by the binary parser
+        // NOTE: result Id is added *after* all of the other Ids have been
+        // checked to avoid premature use in the same instruction
+        _.RemoveIfForwardDeclared(*operand_ptr);
+        ret = SPV_SUCCESS;
+        break;
+      case SPV_OPERAND_TYPE_ID:
+      case SPV_OPERAND_TYPE_TYPE_ID:
+      case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID:
+      case SPV_OPERAND_TYPE_SCOPE_ID:
+        if (_.IsDefinedId(*operand_ptr)) {
+          if (inst->opcode == SpvOpPhi && i > 1) {
+            // For now, ignore uses of IDs as arguments to OpPhi, since
+            // the job of an OpPhi is to allow a block to use an ID from a
+            // block that doesn't dominate the use.
+            // We only track usage by a particular block, rather than
+            // which instruction and operand number is using the value, so
+            // we have to just bluntly avod tracking the use here.
+            // Fixes https://github.com/KhronosGroup/SPIRV-Tools/issues/286
+          } else {
+            _.RegisterUseId(*operand_ptr);
+          }
+          ret = SPV_SUCCESS;
+        } else if (can_have_forward_declared_ids(i)) {
+          ret = _.ForwardDeclareId(*operand_ptr);
+        } else {
+          ret = _.diag(SPV_ERROR_INVALID_ID) << "ID "
+                                             << _.getIdName(*operand_ptr)
+                                             << " has not been defined";
+        }
+        break;
+      default:
+        ret = SPV_SUCCESS;
+        break;
+    }
+    if (SPV_SUCCESS != ret) {
+      return ret;
+    }
+  }
+  if (inst->result_id) {
+    _.AddId(*inst);
+  }
+  return SPV_SUCCESS;
+}
+}  // namespace libspirv
+
 spv_result_t spvValidateInstructionIDs(const spv_instruction_t* pInsts,
                                        const uint64_t instCount,
                                        const spv_opcode_table opcodeTable,
@@ -2349,8 +2499,8 @@
                                        spv_position position,
                                        spv_diagnostic* pDiag) {
   idUsage idUsage(opcodeTable, operandTable, extInstTable, pInsts, instCount,
-                  state.memory_model(), state.addressing_model(),
-                  state.usedefs(), state.entry_points(), position, pDiag);
+                  state.memory_model(), state.addressing_model(), state,
+                  state.entry_points(), position, pDiag);
   for (uint64_t instIndex = 0; instIndex < instCount; ++instIndex) {
     spvCheck(!idUsage.isValid(&pInsts[instIndex]), return SPV_ERROR_INVALID_ID);
     position->index += pInsts[instIndex].words.size();
diff --git a/source/validate_ssa.cpp b/source/validate_ssa.cpp
deleted file mode 100644
index 914d5ca..0000000
--- a/source/validate_ssa.cpp
+++ /dev/null
@@ -1,142 +0,0 @@
-// Copyright (c) 2015-2016 The Khronos Group Inc.
-//
-// Permission is hereby granted, free of charge, to any person obtaining a
-// copy of this software and/or associated documentation files (the
-// "Materials"), to deal in the Materials without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Materials, and to
-// permit persons to whom the Materials are furnished to do so, subject to
-// the following conditions:
-//
-// The above copyright notice and this permission notice shall be included
-// in all copies or substantial portions of the Materials.
-//
-// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS
-// KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS
-// SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT
-//    https://www.khronos.org/registry/
-//
-// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
-// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-// MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
-
-#include "validate.h"
-
-#include <functional>
-
-#include "opcode.h"
-#include "val/ValidationState.h"
-
-using std::function;
-using libspirv::ValidationState_t;
-
-namespace {
-// 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 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:
-      out = [](unsigned index) { return index == 2; };
-      break;
-
-    case SpvOpPhi:
-      out = [](unsigned index) { return index > 1; };
-      break;
-
-    case SpvOpEnqueueKernel:
-      out = [](unsigned index) { return index == 8; };
-      break;
-
-    case SpvOpGetKernelNDrangeSubGroupCount:
-    case SpvOpGetKernelNDrangeMaxSubGroupSize:
-      out = [](unsigned index) { return index == 3; };
-      break;
-
-    case SpvOpGetKernelWorkGroupSize:
-    case SpvOpGetKernelPreferredWorkGroupSizeMultiple:
-      out = [](unsigned index) { return index == 2; };
-      break;
-
-    default:
-      out = [](unsigned) { return false; };
-      break;
-  }
-  return out;
-}
-}  // namespace
-
-namespace libspirv {
-
-// Performs SSA validation on the IDs of an instruction. The
-// can_have_forward_declared_ids  functor should return true if the
-// instruction operand's ID can be forward referenced.
-//
-// TODO(umar): Use dominators to correctly validate SSA. For example, the result
-// id from a 'then' block cannot dominate its usage in the 'else' block. This
-// is not yet performed by this function.
-spv_result_t SsaPass(ValidationState_t& _,
-                     const spv_parsed_instruction_t* inst) {
-  auto can_have_forward_declared_ids =
-      getCanBeForwardDeclaredFunction(static_cast<SpvOp>(inst->opcode));
-
-  for (unsigned i = 0; i < inst->num_operands; i++) {
-    const spv_parsed_operand_t& operand = inst->operands[i];
-    const spv_operand_type_t& type = operand.type;
-    const uint32_t* operand_ptr = inst->words + operand.offset;
-
-    auto ret = SPV_ERROR_INTERNAL;
-    switch (type) {
-      case SPV_OPERAND_TYPE_RESULT_ID:
-        _.RemoveIfForwardDeclared(*operand_ptr);
-        ret = SPV_SUCCESS;
-        break;
-      case SPV_OPERAND_TYPE_ID:
-      case SPV_OPERAND_TYPE_TYPE_ID:
-      case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID:
-      case SPV_OPERAND_TYPE_SCOPE_ID:
-        if (_.IsDefinedId(*operand_ptr)) {
-          ret = SPV_SUCCESS;
-        } else if (can_have_forward_declared_ids(i)) {
-          ret = _.ForwardDeclareId(*operand_ptr);
-        } else {
-          ret = _.diag(SPV_ERROR_INVALID_ID) << "ID "
-                                             << _.getIdName(*operand_ptr)
-                                             << " has not been defined";
-        }
-        break;
-      default:
-        ret = SPV_SUCCESS;
-        break;
-    }
-    if (SPV_SUCCESS != ret) {
-      return ret;
-    }
-  }
-  return SPV_SUCCESS;
-}
-}  // namespace libspirv
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 971e5ac..1ac7a6b 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -30,118 +30,122 @@
 #     SRCS   src_file.h src_file.cpp
 #     LIBS   lib1 lib2
 #   )
-function(add_spvtools_unittest)
-  set(one_value_args TARGET)
-  set(multi_value_args SRCS LIBS)
-  cmake_parse_arguments(
-    ARG "" "${one_value_args}" "${multi_value_args}" ${ARGN})
-  add_executable(${ARG_TARGET} ${ARG_SRCS})
-  spvtools_default_compile_options(${ARG_TARGET})
-  if(${COMPILER_IS_LIKE_GNU})
-    target_compile_options(${ARG_TARGET} PRIVATE -Wno-undef)
-  endif()
-  if(${CMAKE_CXX_COMPILER_ID} STREQUAL "MSVC")
-    # Disable C4503 "decorated name length exceeded" warning,
-    # triggered by some heavily templated types.
-    # We don't care much about that in test code.
-    # Important to do since we have warnings-as-errors.
-    target_compile_options(${ARG_TARGET} PRIVATE /wd4503)
-  endif()
-  target_include_directories(${ARG_TARGET} PRIVATE
-    ${spirv-tools_SOURCE_DIR}
-    ${spirv-tools_SOURCE_DIR}/include
-    ${SPIRV_HEADER_INCLUDE_DIR}
-    ${gtest_SOURCE_DIR}/include
-    ${gmock_SOURCE_DIR}/include
-  )
-  target_link_libraries(${ARG_TARGET} PRIVATE ${ARG_LIBS})
-  target_link_libraries(${ARG_TARGET} PRIVATE gmock_main)
-  add_test(NAME spirv-tools-${ARG_TARGET} COMMAND ${ARG_TARGET})
-endfunction()
 
-if (NOT ${SPIRV_SKIP_EXECUTABLES})
-  if (TARGET gmock)
+if (NOT "${SPIRV_SKIP_TESTS}")
+  if (TARGET gmock_main)
     message(STATUS "Found Google Mock, building tests.")
-
-    set(TEST_SOURCES
-      ${CMAKE_CURRENT_SOURCE_DIR}/TestFixture.h
-      ${CMAKE_CURRENT_SOURCE_DIR}/UnitSPIRV.h
-
-      ${CMAKE_CURRENT_SOURCE_DIR}/AssemblyContext.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/AssemblyFormat.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/BinaryDestroy.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/BinaryEndianness.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/BinaryHeaderGet.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/BinaryParse.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/BinaryToText.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/BinaryToText.Literal.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/Comment.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/DiagnosticDestroy.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/DiagnosticPrint.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/DiagnosticStream.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/ExtInstGLSLstd450.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/ExtInst.OpenCL.std.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/FixWord.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/GeneratorMagicNumber.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/HexFloat.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/ImmediateInt.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/LibspirvMacros.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/NamedId.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/NameMapper.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/OpcodeMake.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/OpcodeRequiresCapabilities.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/OpcodeSplit.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/OpcodeTableGet.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/OperandCapabilities.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/Operand.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/OperandPattern.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/SoftwareVersion.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/TextAdvance.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/TextDestroy.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/TextLiteral.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/TextStartsNewInst.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Annotation.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Barrier.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Constant.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.ControlFlow.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Debug.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.DeviceSideEnqueue.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Extension.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Function.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Group.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Image.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Literal.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Memory.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Miscellaneous.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.ModeSetting.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.PipeStorage.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.TypeDeclaration.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.SubgroupDispatch.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/TextWordGet.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/UnitSPIRV.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/ValidateFixtures.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/Validate.Capability.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/Validate.CFG.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/Validate.Layout.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/Validate.Storage.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/Validate.SSA.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/ValidateID.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/ValidationState.cpp
-    )
-    add_spvtools_unittest(
-      TARGET UnitSPIRV
-      SRCS ${TEST_SOURCES}
-      LIBS ${SPIRV_TOOLS})
-
-    add_spvtools_unittest(
-      TARGET cpp_interface
-      SRCS cpp_interface.cpp
-      LIBS SPIRV-Tools-opt ${SPIRV_TOOLS})
-
-    add_subdirectory(opt)
   else()
     message(STATUS "Did not find googletest, tests will not be built."
       "To enable tests place googletest in '<spirv-dir>/external/googletest'.")
   endif()
 endif()
+
+function(add_spvtools_unittest)
+  if (NOT "${SPIRV_SKIP_TESTS}" AND TARGET gmock_main)
+    set(one_value_args TARGET)
+    set(multi_value_args SRCS LIBS)
+    cmake_parse_arguments(
+      ARG "" "${one_value_args}" "${multi_value_args}" ${ARGN})
+    add_executable(${ARG_TARGET} ${ARG_SRCS})
+    spvtools_default_compile_options(${ARG_TARGET})
+    if(${COMPILER_IS_LIKE_GNU})
+      target_compile_options(${ARG_TARGET} PRIVATE -Wno-undef)
+    endif()
+    if(${CMAKE_CXX_COMPILER_ID} STREQUAL "MSVC")
+      # Disable C4503 "decorated name length exceeded" warning,
+      # triggered by some heavily templated types.
+      # We don't care much about that in test code.
+      # Important to do since we have warnings-as-errors.
+      target_compile_options(${ARG_TARGET} PRIVATE /wd4503)
+    endif()
+    target_include_directories(${ARG_TARGET} PRIVATE
+      ${spirv-tools_SOURCE_DIR}
+      ${spirv-tools_SOURCE_DIR}/include
+      ${SPIRV_HEADER_INCLUDE_DIR}
+      ${gtest_SOURCE_DIR}/include
+      ${gmock_SOURCE_DIR}/include
+    )
+    target_link_libraries(${ARG_TARGET} PRIVATE ${ARG_LIBS})
+    target_link_libraries(${ARG_TARGET} PRIVATE gmock_main)
+    add_test(NAME spirv-tools-${ARG_TARGET} COMMAND ${ARG_TARGET})
+  endif()
+endfunction()
+
+set(TEST_SOURCES
+  ${CMAKE_CURRENT_SOURCE_DIR}/TestFixture.h
+  ${CMAKE_CURRENT_SOURCE_DIR}/UnitSPIRV.h
+
+  ${CMAKE_CURRENT_SOURCE_DIR}/AssemblyContext.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/AssemblyFormat.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/BinaryDestroy.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/BinaryEndianness.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/BinaryHeaderGet.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/BinaryParse.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/BinaryToText.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/BinaryToText.Literal.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/Comment.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/DiagnosticDestroy.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/DiagnosticPrint.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/DiagnosticStream.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/ExtInstGLSLstd450.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/ExtInst.OpenCL.std.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/FixWord.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/GeneratorMagicNumber.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/HexFloat.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/ImmediateInt.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/LibspirvMacros.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/NamedId.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/NameMapper.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/OpcodeMake.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/OpcodeRequiresCapabilities.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/OpcodeSplit.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/OpcodeTableGet.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/OperandCapabilities.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/Operand.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/OperandPattern.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/SoftwareVersion.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/TargetEnv.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/TextAdvance.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/TextDestroy.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/TextLiteral.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/TextStartsNewInst.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Annotation.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Barrier.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Constant.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.ControlFlow.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Debug.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.DeviceSideEnqueue.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Extension.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Function.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Group.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Image.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Literal.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Memory.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Miscellaneous.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.ModeSetting.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.PipeStorage.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.TypeDeclaration.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.SubgroupDispatch.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/TextWordGet.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/UnitSPIRV.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/ValidateFixtures.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/Validate.Capability.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/Validate.CFG.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/Validate.Layout.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/Validate.Storage.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/Validate.SSA.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/ValidateID.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/ValidationState.cpp
+)
+add_spvtools_unittest(
+  TARGET UnitSPIRV
+  SRCS ${TEST_SOURCES}
+  LIBS ${SPIRV_TOOLS})
+
+add_spvtools_unittest(
+  TARGET cpp_interface
+  SRCS cpp_interface.cpp
+  LIBS SPIRV-Tools-opt ${SPIRV_TOOLS})
+
+add_subdirectory(opt)
diff --git a/test/NameMapper.cpp b/test/NameMapper.cpp
index e3332cf..fdb709b 100644
--- a/test/NameMapper.cpp
+++ b/test/NameMapper.cpp
@@ -134,6 +134,13 @@
         {"OpName %1 \"abcdefg\"", 1, "abcdefg"},
         {"OpName %1 \"Hello world!\"", 1, "Hello_world_"},
         {"OpName %1 \"0123456789\"", 1, "0123456789"},
+        {"OpName %1 \"_\"", 1, "_"},
+        // An empty string is not valid for SPIR-V assembly IDs.
+        {"OpName %1 \"\"", 1, "_"},
+        // Test uniqueness when presented with things mapping to "_"
+        {"OpName %1 \"\" OpName %2 \"\"", 1, "_"},
+        {"OpName %1 \"\" OpName %2 \"\"", 2, "__0"},
+        {"OpName %1 \"\" OpName %2 \"\" OpName %3 \"_\"", 3, "__1"},
         // Test uniqueness of names that are forced to be
         // numbers.
         {"OpName %1 \"2\" OpName %2 \"2\"", 1, "2"},
diff --git a/test/OpcodeTableGet.cpp b/test/OpcodeTableGet.cpp
index b9fe3c1..5e220b6 100644
--- a/test/OpcodeTableGet.cpp
+++ b/test/OpcodeTableGet.cpp
@@ -30,24 +30,21 @@
 
 namespace {
 
-using GetTargetTest = ::testing::TestWithParam<spv_target_env>;
+using GetTargetOpcodeTableGetTest = ::testing::TestWithParam<spv_target_env>;
 using ::testing::ValuesIn;
-using std::vector;
 
-TEST_P(GetTargetTest, SanityCheck) {
+TEST_P(GetTargetOpcodeTableGetTest, SanityCheck) {
   spv_opcode_table table;
   ASSERT_EQ(SPV_SUCCESS, spvOpcodeTableGet(&table, GetParam()));
   ASSERT_NE(0u, table->count);
   ASSERT_NE(nullptr, table->entries);
 }
 
-TEST_P(GetTargetTest, InvalidPointerTable) {
+TEST_P(GetTargetOpcodeTableGetTest, InvalidPointerTable) {
   ASSERT_EQ(SPV_ERROR_INVALID_POINTER, spvOpcodeTableGet(nullptr, GetParam()));
 }
 
-INSTANTIATE_TEST_CASE_P(OpcodeTableGet, GetTargetTest,
-                        ValuesIn(vector<spv_target_env>{SPV_ENV_UNIVERSAL_1_0,
-                                                        SPV_ENV_UNIVERSAL_1_1,
-                                                        SPV_ENV_VULKAN_1_0}), );
+INSTANTIATE_TEST_CASE_P(OpcodeTableGet, GetTargetOpcodeTableGetTest,
+                        ValuesIn(spvtest::AllTargetEnvironments()));
 
 }  // anonymous namespace
diff --git a/test/TargetEnv.cpp b/test/TargetEnv.cpp
new file mode 100644
index 0000000..235e6d7
--- /dev/null
+++ b/test/TargetEnv.cpp
@@ -0,0 +1,100 @@
+// Copyright (c) 2016 Google Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and/or associated documentation files (the
+// "Materials"), to deal in the Materials without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Materials, and to
+// permit persons to whom the Materials are furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Materials.
+//
+// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS
+// KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS
+// SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT
+//    https://www.khronos.org/registry/
+//
+// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+
+#include <gmock/gmock.h>
+
+#include "UnitSPIRV.h"
+
+#include "source/spirv_target_env.h"
+
+namespace {
+
+using ::testing::AnyOf;
+using ::testing::Eq;
+using ::testing::ValuesIn;
+using ::testing::StartsWith;
+
+using TargetEnvTest = ::testing::TestWithParam<spv_target_env>;
+TEST_P(TargetEnvTest, CreateContext) {
+  spv_target_env env = GetParam();
+  spv_context context = spvContextCreate(env);
+  ASSERT_NE(nullptr, context);
+  spvContextDestroy(context); // Avoid leaking
+}
+
+TEST_P(TargetEnvTest, ValidDescription) {
+  const char* description = spvTargetEnvDescription(GetParam());
+  ASSERT_NE(nullptr, description);
+  ASSERT_THAT(description, StartsWith("SPIR-V "));
+}
+
+TEST_P(TargetEnvTest, ValidSpirvVersion) {
+  auto spirv_version = spvVersionForTargetEnv(GetParam());
+  ASSERT_THAT(spirv_version, AnyOf(0x10000, 0x10100));
+}
+
+INSTANTIATE_TEST_CASE_P(AllTargetEnvs, TargetEnvTest,
+                        ValuesIn(spvtest::AllTargetEnvironments()));
+
+TEST(GetContextTest, InvalidTargetEnvProducesNull) {
+  spv_context context = spvContextCreate((spv_target_env)10);
+  EXPECT_EQ(context, nullptr);
+}
+
+// A test case for parsing an environment string.
+struct ParseCase {
+  const char* input;
+  bool success;        // Expect to successfully parse?
+  spv_target_env env;  // The parsed environment, if successful.
+};
+
+using TargetParseTest = ::testing::TestWithParam<ParseCase>;
+
+TEST_P(TargetParseTest, InvalidTargetEnvProducesNull) {
+  spv_target_env env;
+  bool parsed = spvParseTargetEnv(GetParam().input, &env);
+  EXPECT_THAT(parsed, Eq(GetParam().success));
+  EXPECT_THAT(env, Eq(GetParam().env));
+}
+
+INSTANTIATE_TEST_CASE_P(TargetParsing, TargetParseTest,
+                        ValuesIn(std::vector<ParseCase>{
+                            {"spv1.0", true, SPV_ENV_UNIVERSAL_1_0},
+                            {"spv1.1", true, SPV_ENV_UNIVERSAL_1_1},
+                            {"vulkan1.0", true, SPV_ENV_VULKAN_1_0},
+                            {"opencl2.1", true, SPV_ENV_OPENCL_2_1},
+                            {"opencl2.2", true, SPV_ENV_OPENCL_2_2},
+                            {"opengl4.0", true, SPV_ENV_OPENGL_4_0},
+                            {"opengl4.1", true, SPV_ENV_OPENGL_4_1},
+                            {"opengl4.2", true, SPV_ENV_OPENGL_4_2},
+                            {"opengl4.3", true, SPV_ENV_OPENGL_4_3},
+                            {"opengl4.5", true, SPV_ENV_OPENGL_4_5},
+                            {nullptr, false, SPV_ENV_UNIVERSAL_1_0},
+                            {"", false, SPV_ENV_UNIVERSAL_1_0},
+                            {"abc", false, SPV_ENV_UNIVERSAL_1_0},
+                        }));
+
+}  // anonymous namespace
diff --git a/test/UnitSPIRV.h b/test/UnitSPIRV.h
index 93bf791..5456672 100644
--- a/test/UnitSPIRV.h
+++ b/test/UnitSPIRV.h
@@ -216,5 +216,13 @@
   return result;
 }
 
+// Returns a vector of all valid target environment enums.
+inline std::vector<spv_target_env> AllTargetEnvironments() {
+  return {SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1, SPV_ENV_OPENCL_2_1,
+          SPV_ENV_OPENCL_2_2,    SPV_ENV_VULKAN_1_0,    SPV_ENV_OPENGL_4_0,
+          SPV_ENV_OPENGL_4_1,    SPV_ENV_OPENGL_4_2,    SPV_ENV_OPENGL_4_3,
+          SPV_ENV_OPENGL_4_5};
+}
+
 }  // namespace spvtest
 #endif  // LIBSPIRV_TEST_UNITSPIRV_H_
diff --git a/test/Validate.CFG.cpp b/test/Validate.CFG.cpp
index 4027946..6a1920e 100644
--- a/test/Validate.CFG.cpp
+++ b/test/Validate.CFG.cpp
@@ -91,8 +91,13 @@
       : label_(label), body_(), type_(type), successors_() {}
 
   /// Sets the instructions which will appear in the body of the block
-  Block& setBody(std::string body) {
-    body_ = body;
+  Block& SetBody(std::string body) {
+      body_ = body;
+    return *this;
+  }
+
+  Block& AppendBody(std::string body) {
+      body_ += body;
     return *this;
   }
 
@@ -189,6 +194,87 @@
                         ::testing::Values(SpvCapabilityShader,
                                           SpvCapabilityKernel));
 
+TEST_P(ValidateCFG, LoopReachableFromEntryButNeverLeadingToReturn) {
+  // In this case, the loop is reachable from a node without a predecessor,
+  // but never reaches a node with a return.
+  //
+  // This motivates the need for the pseudo-exit node to have a node
+  // from a cycle in its predecessors list.  Otherwise the validator's
+  // post-dominance calculation will go into an infinite loop.
+  //
+  // For more motivation, see
+  // https://github.com/KhronosGroup/SPIRV-Tools/issues/279
+  string str = R"(
+           OpCapability Shader
+           OpMemoryModel Logical GLSL450
+
+           OpName %entry "entry"
+           OpName %loop "loop"
+           OpName %exit "exit"
+
+%voidt   = OpTypeVoid
+%funct   = OpTypeFunction %voidt
+
+%main    = OpFunction %voidt None %funct
+%entry   = OpLabel
+           OpBranch %loop
+%loop    = OpLabel
+           OpLoopMerge %exit %loop None
+           OpBranch %loop
+%exit    = OpLabel
+           OpReturn
+           OpFunctionEnd
+  )";
+  CompileSuccessfully(str);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()) << str;
+}
+
+TEST_P(ValidateCFG, LoopUnreachableFromEntryButLeadingToReturn) {
+  // In this case, the loop is not reachable from a node without a
+  // predecessor, but eventually reaches a node with a return.
+  //
+  // This motivates the need for the pseudo-entry node to have a node
+  // from a cycle in its successors list.  Otherwise the validator's
+  // dominance calculation will go into an infinite loop.
+  //
+  // For more motivation, see
+  // https://github.com/KhronosGroup/SPIRV-Tools/issues/279
+  // Before that fix, we'd have an infinite loop when calculating
+  // post-dominators.
+  string str = R"(
+           OpCapability Shader
+           OpMemoryModel Logical GLSL450
+
+           OpName %entry "entry"
+           OpName %loop "loop"
+           OpName %cont "cont"
+           OpName %exit "exit"
+
+%voidt   = OpTypeVoid
+%funct   = OpTypeFunction %voidt
+%boolt   = OpTypeBool
+%false   = OpConstantFalse %boolt
+
+%main    = OpFunction %voidt None %funct
+%entry   = OpLabel
+           OpReturn
+
+%loop    = OpLabel
+           OpLoopMerge %exit %cont None
+           OpBranch %cont
+
+%cont    = OpLabel
+           OpBranchConditional %false %loop %exit
+
+%exit    = OpLabel
+           OpReturn
+           OpFunctionEnd
+  )";
+  CompileSuccessfully(str);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()) << str
+                                                 << getDiagnosticString();
+}
+
 TEST_P(ValidateCFG, Simple) {
   bool is_shader = GetParam() == SpvCapabilityShader;
   Block entry("entry");
@@ -196,9 +282,9 @@
   Block cont("cont");
   Block merge("merge", SpvOpReturn);
 
-  entry.setBody("%cond    = OpSLessThan %intt %one %two\n");
+  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
   if (is_shader) {
-    loop.setBody("OpLoopMerge %merge %cont None\n");
+    loop.SetBody("OpLoopMerge %merge %cont None\n");
   }
 
   string str = header(GetParam()) + nameOps("loop", "entry", "cont", "merge",
@@ -220,7 +306,7 @@
   Block cont("cont");
   Block exit("exit", SpvOpReturn);
 
-  entry.setBody("%var = OpVariable %ptrt Function\n");
+  entry.SetBody("%var = OpVariable %ptrt Function\n");
 
   string str = header(GetParam()) + nameOps(make_pair("func", "Main")) +
                types_consts() + " %func    = OpFunction %voidt None %funct\n";
@@ -239,7 +325,7 @@
   Block exit("exit", SpvOpReturn);
 
   // This operation should only be performed in the entry block
-  cont.setBody("%var = OpVariable %ptrt Function\n");
+  cont.SetBody("%var = OpVariable %ptrt Function\n");
 
   string str = header(GetParam()) + nameOps(make_pair("func", "Main")) +
                types_consts() + " %func    = OpFunction %voidt None %funct\n";
@@ -257,6 +343,29 @@
           "Variables can only be defined in the first block of a function"));
 }
 
+TEST_P(ValidateCFG, BlockSelfLoopIsOk) {
+  bool is_shader = GetParam() == SpvCapabilityShader;
+  Block entry("entry");
+  Block loop("loop", SpvOpBranchConditional);
+  Block merge("merge", SpvOpReturn);
+
+  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  if (is_shader) loop.SetBody("OpLoopMerge %merge %loop None\n");
+
+  string str = header(GetParam()) +
+               nameOps("loop", "merge", make_pair("func", "Main")) +
+               types_consts() + "%func    = OpFunction %voidt None %funct\n";
+
+  str += entry >> loop;
+  // loop branches to itself, but does not trigger an error.
+  str += loop >> vector<Block>({merge, loop});
+  str += merge;
+  str += "OpFunctionEnd\n";
+
+  CompileSuccessfully(str);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()) << getDiagnosticString();
+}
+
 TEST_P(ValidateCFG, BlockAppearsBeforeDominatorBad) {
   bool is_shader = GetParam() == SpvCapabilityShader;
   Block entry("entry");
@@ -264,8 +373,8 @@
   Block branch("branch", SpvOpBranchConditional);
   Block merge("merge", SpvOpReturn);
 
-  entry.setBody("%cond    = OpSLessThan %intt %one %two\n");
-  if (is_shader) branch.setBody("OpSelectionMerge %merge None\n");
+  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  if (is_shader) branch.SetBody("OpSelectionMerge %merge None\n");
 
   string str = header(GetParam()) +
                nameOps("cont", "branch", make_pair("func", "Main")) +
@@ -291,11 +400,11 @@
   Block selection("selection", SpvOpBranchConditional);
   Block merge("merge", SpvOpReturn);
 
-  entry.setBody("%cond    = OpSLessThan %intt %one %two\n");
-  if (is_shader) loop.setBody(" OpLoopMerge %merge %loop None\n");
+  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  if (is_shader) loop.SetBody(" OpLoopMerge %merge %loop None\n");
 
   // cannot share the same merge
-  if (is_shader) selection.setBody("OpSelectionMerge %merge None\n");
+  if (is_shader) selection.SetBody("OpSelectionMerge %merge None\n");
 
   string str = header(GetParam()) +
                nameOps("merge", make_pair("func", "Main")) + types_consts() +
@@ -325,11 +434,11 @@
   Block selection("selection", SpvOpBranchConditional);
   Block merge("merge", SpvOpReturn);
 
-  entry.setBody("%cond    = OpSLessThan %intt %one %two\n");
-  if (is_shader) selection.setBody(" OpSelectionMerge %merge None\n");
+  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  if (is_shader) selection.SetBody(" OpSelectionMerge %merge None\n");
 
   // cannot share the same merge
-  if (is_shader) loop.setBody(" OpLoopMerge %merge %loop None\n");
+  if (is_shader) loop.SetBody(" OpLoopMerge %merge %loop None\n");
 
   string str = header(GetParam()) +
                nameOps("merge", make_pair("func", "Main")) + types_consts() +
@@ -377,8 +486,8 @@
   Block bad("bad", SpvOpBranchConditional);
   Block exit("exit", SpvOpReturn);
 
-  entry.setBody("%cond    = OpSLessThan %intt %one %two\n");
-  bad.setBody(" OpLoopMerge %entry %exit None\n");
+  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  bad.SetBody(" OpLoopMerge %entry %exit None\n");
 
   string str = header(GetParam()) +
                nameOps("entry", "bad", make_pair("func", "Main")) +
@@ -403,8 +512,8 @@
   Block merge("merge");
   Block end("end", SpvOpReturn);
 
-  entry.setBody("%cond    = OpSLessThan %intt %one %two\n");
-  bad.setBody("OpLoopMerge %merge %cont None\n");
+  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  bad.SetBody("OpLoopMerge %merge %cont None\n");
 
   string str = header(GetParam()) +
                nameOps("entry", "bad", make_pair("func", "Main")) +
@@ -433,8 +542,8 @@
   Block merge("merge");
   Block end("end", SpvOpReturn);
 
-  entry.setBody("%cond    = OpSLessThan %intt %one %two\n");
-  bad.setBody("OpSelectionMerge %merge None\n");
+  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  bad.SetBody("OpSelectionMerge %merge None\n");
 
   string str = header(GetParam()) +
                nameOps("entry", "bad", make_pair("func", "Main")) +
@@ -462,8 +571,8 @@
   Block middle("middle", SpvOpBranchConditional);
   Block end("end", SpvOpReturn);
 
-  entry.setBody("%cond    = OpSLessThan %intt %one %two\n");
-  middle.setBody("OpSelectionMerge %end None\n");
+  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  middle.SetBody("OpSelectionMerge %end None\n");
 
   Block entry2("entry2");
   Block middle2("middle2");
@@ -499,9 +608,9 @@
   Block f("f");
   Block merge("merge", SpvOpReturn);
 
-  entry.setBody("%cond = OpSLessThan %intt %one %two\n");
+  head.SetBody("%cond = OpSLessThan %intt %one %two\n");
 
-  if (is_shader) head.setBody("OpSelectionMerge %merge None\n");
+  if (is_shader) head.AppendBody("OpSelectionMerge %merge None\n");
 
   string str = header(GetParam()) +
                nameOps("head", "merge", make_pair("func", "Main")) +
@@ -514,7 +623,6 @@
   str += "OpFunctionEnd\n";
 
   CompileSuccessfully(str);
-
   if (is_shader) {
     ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
     EXPECT_THAT(
@@ -535,8 +643,8 @@
   Block f("f", SpvOpReturn);
   Block merge("merge", SpvOpReturn);
 
-  entry.setBody("%cond    = OpSLessThan %intt %one %two\n");
-  if (is_shader) branch.setBody("OpSelectionMerge %merge None\n");
+  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  if (is_shader) branch.AppendBody("OpSelectionMerge %merge None\n");
 
   string str = header(GetParam()) +
                nameOps("branch", "merge", make_pair("func", "Main")) +
@@ -561,8 +669,8 @@
   Block f("f", SpvOpReturn);
   Block merge("merge", SpvOpUnreachable);
 
-  entry.setBody("%cond    = OpSLessThan %intt %one %two\n");
-  if (is_shader) branch.setBody("OpSelectionMerge %merge None\n");
+  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  if (is_shader) branch.AppendBody("OpSelectionMerge %merge None\n");
 
   string str = header(GetParam()) +
                nameOps("branch", "merge", make_pair("func", "Main")) +
@@ -606,8 +714,8 @@
   Block merge("merge");
   Block exit("exit", SpvOpReturn);
 
-  entry.setBody("%cond    = OpSLessThan %intt %one %two\n");
-  if (is_shader) unreachable.setBody("OpSelectionMerge %merge None\n");
+  unreachable.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  if (is_shader) unreachable.AppendBody("OpSelectionMerge %merge None\n");
   string str = header(GetParam()) +
                nameOps("unreachable", "exit", make_pair("func", "Main")) +
                types_consts() + "%func    = OpFunction %voidt None %funct\n";
@@ -638,8 +746,8 @@
   Block loop("loop", SpvOpBranchConditional);
   Block exit("exit", SpvOpReturn);
 
-  entry.setBody("%cond    = OpSLessThan %intt %one %two\n");
-  if (is_shader) loop.setBody("OpLoopMerge %exit %loop None\n");
+  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  if (is_shader) loop.AppendBody("OpLoopMerge %exit %loop None\n");
 
   string str = header(GetParam()) + string(types_consts()) +
                "%func    = OpFunction %voidt None %funct\n";
@@ -664,10 +772,10 @@
   Block loop1_merge("loop1_merge");
   Block exit("exit", SpvOpReturn);
 
-  entry.setBody("%cond    = OpSLessThan %intt %one %two\n");
+  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
   if (is_shader) {
-    loop1.setBody("OpLoopMerge %loop1_merge %loop2 None\n");
-    loop2.setBody("OpLoopMerge %loop2_merge %loop2 None\n");
+    loop1.SetBody("OpLoopMerge %loop1_merge %loop2 None\n");
+    loop2.SetBody("OpLoopMerge %loop2_merge %loop2 None\n");
   }
 
   string str = header(GetParam()) + nameOps("loop2", "loop2_merge") +
@@ -694,11 +802,11 @@
   vector<Block> merge_blocks;
   Block inner("inner");
 
-  entry.setBody("%cond    = OpSLessThan %intt %one %two\n");
+  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
 
   if_blocks.emplace_back("if0", SpvOpBranchConditional);
 
-  if (is_shader) if_blocks[0].setBody("OpSelectionMerge %if_merge0 None\n");
+  if (is_shader) if_blocks[0].SetBody("OpSelectionMerge %if_merge0 None\n");
   merge_blocks.emplace_back("if_merge0", SpvOpReturn);
 
   for (int i = 1; i < N; i++) {
@@ -706,7 +814,7 @@
     ss << i;
     if_blocks.emplace_back("if" + ss.str(), SpvOpBranchConditional);
     if (is_shader)
-      if_blocks[i].setBody("OpSelectionMerge %if_merge" + ss.str() + " None\n");
+      if_blocks[i].SetBody("OpSelectionMerge %if_merge" + ss.str() + " None\n");
     merge_blocks.emplace_back("if_merge" + ss.str(), SpvOpBranch);
   }
   string str = header(GetParam()) + string(types_consts()) +
@@ -737,10 +845,10 @@
   Block be_block("be_block");
   Block exit("exit", SpvOpReturn);
 
-  entry.setBody("%cond    = OpSLessThan %intt %one %two\n");
+  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
   if (is_shader) {
-    loop1.setBody("OpLoopMerge %exit %loop2_merge None\n");
-    loop2.setBody("OpLoopMerge %loop2_merge %loop2 None\n");
+    loop1.SetBody("OpLoopMerge %exit %loop2_merge None\n");
+    loop2.SetBody("OpLoopMerge %loop2_merge %loop2 None\n");
   }
 
   string str = header(GetParam()) +
@@ -775,8 +883,8 @@
   Block f("f");
   Block exit("exit", SpvOpReturn);
 
-  entry.setBody("%cond    = OpSLessThan %intt %one %two\n");
-  if (is_shader) split.setBody("OpSelectionMerge %exit None\n");
+  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  if (is_shader) split.SetBody("OpSelectionMerge %exit None\n");
 
   string str = header(GetParam()) + nameOps("split", "f") + types_consts() +
                "%func    = OpFunction %voidt None %funct\n";
@@ -806,8 +914,8 @@
   Block split("split", SpvOpBranchConditional);
   Block exit("exit", SpvOpReturn);
 
-  entry.setBody("%cond    = OpSLessThan %intt %one %two\n");
-  if (is_shader) split.setBody("OpSelectionMerge %exit None\n");
+  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  if (is_shader) split.SetBody("OpSelectionMerge %exit None\n");
 
   string str = header(GetParam()) + nameOps("split") + types_consts() +
                "%func    = OpFunction %voidt None %funct\n";
@@ -829,22 +937,24 @@
   }
 }
 
-TEST_P(ValidateCFG, MultipleBackEdgesToLoopHeaderBad) {
+TEST_P(ValidateCFG, MultipleBackEdgeBlocksToLoopHeaderBad) {
   bool is_shader = GetParam() == SpvCapabilityShader;
   Block entry("entry");
   Block loop("loop", SpvOpBranchConditional);
-  Block cont("cont", SpvOpBranchConditional);
+  Block back0("back0");
+  Block back1("back1");
   Block merge("merge", SpvOpReturn);
 
-  entry.setBody("%cond    = OpSLessThan %intt %one %two\n");
-  if (is_shader) loop.setBody("OpLoopMerge %merge %loop None\n");
+  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  if (is_shader) loop.SetBody("OpLoopMerge %merge %back0 None\n");
 
-  string str = header(GetParam()) + nameOps("cont", "loop") + types_consts() +
-               "%func    = OpFunction %voidt None %funct\n";
+  string str = header(GetParam()) + nameOps("loop", "back0", "back1") +
+               types_consts() + "%func    = OpFunction %voidt None %funct\n";
 
   str += entry >> loop;
-  str += loop >> vector<Block>({cont, merge});
-  str += cont >> vector<Block>({loop, loop});
+  str += loop >> vector<Block>({back0, back1});
+  str += back0 >> loop;
+  str += back1 >> loop;
   str += merge;
   str += "OpFunctionEnd";
 
@@ -853,7 +963,9 @@
     ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
     EXPECT_THAT(getDiagnosticString(),
                 MatchesRegex(
-                    "Loop header .\\[loop\\] targeted by multiple back-edges"));
+                    "Loop header .\\[loop\\] is targeted by 2 back-edge blocks "
+                    "but the standard requires exactly one"))
+        << str;
   } else {
     ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
   }
@@ -868,8 +980,8 @@
   Block merge("merge", SpvOpReturn);
   Block exit("exit", SpvOpReturn);
 
-  entry.setBody("%cond    = OpSLessThan %intt %one %two\n");
-  if (is_shader) loop.setBody("OpLoopMerge %merge %cheader None\n");
+  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  if (is_shader) loop.SetBody("OpLoopMerge %merge %cheader None\n");
 
   string str = header(GetParam()) + nameOps("cheader", "be_block") +
                types_consts() + "%func    = OpFunction %voidt None %funct\n";
@@ -901,8 +1013,8 @@
   Block cont("cont", SpvOpBranchConditional);
   Block merge("merge", SpvOpReturn);
 
-  entry.setBody("%cond    = OpSLessThan %intt %one %two\n");
-  if (is_shader) loop.setBody("OpLoopMerge %merge %loop None\n");
+  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  if (is_shader) loop.SetBody("OpLoopMerge %merge %loop None\n");
 
   string str = header(GetParam()) + nameOps("cont", "loop") + types_consts() +
                "%func    = OpFunction %voidt None %funct\n";
@@ -919,7 +1031,7 @@
     EXPECT_THAT(getDiagnosticString(),
                 MatchesRegex("The continue construct with the continue target "
                              ".\\[loop\\] is not post dominated by the "
-                             "back-edge block .\\[cont\\]"));
+                             "back-edge block .\\[cont\\]")) << str;
   } else {
     ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
   }
@@ -933,8 +1045,8 @@
   Block merge("merge");
   Block exit("exit", SpvOpReturn);
 
-  entry.setBody("%cond    = OpSLessThan %intt %one %two\n");
-  if (is_shader) loop.setBody("OpLoopMerge %merge %loop None\n");
+  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  if (is_shader) loop.SetBody("OpLoopMerge %merge %loop None\n");
 
   string str = header(GetParam()) + nameOps("cont", "loop") + types_consts() +
                "%func    = OpFunction %voidt None %funct\n";
@@ -987,11 +1099,11 @@
 %main      = OpFunction %void None %voidf
 )";
 
-  entry.setBody(
-    "%idval    = OpLoad %uvec3 %id\n"
-    "%x        = OpCompositeExtract %u32 %idval 0\n"
-    "%selector = OpUMod %u32 %x %three\n"
-    "OpSelectionMerge %phi None\n");
+  entry.SetBody(
+      "%idval    = OpLoad %uvec3 %id\n"
+      "%x        = OpCompositeExtract %u32 %idval 0\n"
+      "%selector = OpUMod %u32 %x %three\n"
+      "OpSelectionMerge %phi None\n");
   str += entry >> vector<Block>({def, case0, case1, case2});
   str += case1 >> phi;
   str += def;
@@ -1001,7 +1113,198 @@
   str += "OpFunctionEnd";
 
   CompileSuccessfully(str);
-  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, LoopWithZeroBackEdgesBad) {
+  string str = R"(
+           OpCapability Shader
+           OpMemoryModel Logical GLSL450
+           OpEntryPoint Fragment %main "main"
+           OpName %loop "loop"
+%voidt   = OpTypeVoid
+%funct   = OpTypeFunction %voidt
+%main    = OpFunction %voidt None %funct
+%loop    = OpLabel
+           OpLoopMerge %exit %exit None
+           OpBranch %exit
+%exit    = OpLabel
+           OpReturn
+           OpFunctionEnd
+)";
+  CompileSuccessfully(str);
+  ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      MatchesRegex("Loop header .\\[loop\\] is targeted by "
+                   "0 back-edge blocks but the standard requires exactly "
+                   "one"));
+}
+
+TEST_F(ValidateCFG, LoopWithBackEdgeFromUnreachableContinueConstructGood) {
+  string str = R"(
+           OpCapability Shader
+           OpMemoryModel Logical GLSL450
+           OpEntryPoint Fragment %main "main"
+           OpName %loop "loop"
+%voidt   = OpTypeVoid
+%funct   = OpTypeFunction %voidt
+%floatt  = OpTypeFloat 32
+%boolt   = OpTypeBool
+%one     = OpConstant %floatt 1
+%two     = OpConstant %floatt 2
+%main    = OpFunction %voidt None %funct
+%entry   = OpLabel
+           OpBranch %loop
+%loop    = OpLabel
+           OpLoopMerge %exit %cont None
+           OpBranch %16
+%16      = OpLabel
+%cond    = OpFOrdLessThan %boolt %one %two
+           OpBranchConditional %cond %body %exit
+%body    = OpLabel
+           OpReturn
+%cont    = OpLabel   ; Reachable only from OpLoopMerge ContinueTarget parameter
+           OpBranch %loop ; Should be considered a back-edge
+%exit    = OpLabel
+           OpReturn
+           OpFunctionEnd
+)";
+
+  CompileSuccessfully(str);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()) << getDiagnosticString();
+}
+
+TEST_P(ValidateCFG,
+       NestedConstructWithUnreachableMergeBlockBranchingToOuterMergeBlock) {
+  // Test for https://github.com/KhronosGroup/SPIRV-Tools/issues/297
+  // The nested construct has an unreachable merge block.  In the
+  // augmented CFG that merge block
+  // we still determine that the
+  bool is_shader = GetParam() == SpvCapabilityShader;
+  Block entry("entry", SpvOpBranchConditional);
+  Block inner_head("inner_head", SpvOpBranchConditional);
+  Block inner_true("inner_true", SpvOpReturn);
+  Block inner_false("inner_false", SpvOpReturn);
+  Block inner_merge("inner_merge");
+  Block exit("exit", SpvOpReturn);
+
+  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  if (is_shader) {
+    entry.AppendBody("OpSelectionMerge %exit None\n");
+    inner_head.SetBody("OpSelectionMerge %inner_merge None\n");
+  }
+
+  string str = header(GetParam()) + nameOps("entry", "inner_merge", "exit") +
+               types_consts() + "%func    = OpFunction %voidt None %funct\n";
+
+  str += entry >> vector<Block>({inner_head, exit});
+  str += inner_head >> vector<Block>({inner_true, inner_false});
+  str += inner_true;
+  str += inner_false;
+  str += inner_merge >> exit;
+  str += exit;
+  str += "OpFunctionEnd";
+
+  CompileSuccessfully(str);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()) << getDiagnosticString();
+}
+
+TEST_P(ValidateCFG, ContinueTargetCanBeMergeBlockForNestedStructureGood) {
+  // This example is valid.  It shows that the validator can't just add
+  // an edge from the loop head to the continue target.  If that edge
+  // is added, then the "if_merge" block is both the continue target
+  // for the loop and also the merge block for the nested selection, but
+  // then it wouldn't be dominated by "if_head", the header block for the
+  // nested selection.
+  bool is_shader = GetParam() == SpvCapabilityShader;
+  Block entry("entry");
+  Block loop("loop");
+  Block if_head("if_head", SpvOpBranchConditional);
+  Block if_true("if_true");
+  Block if_merge("if_merge", SpvOpBranchConditional);
+  Block merge("merge", SpvOpReturn);
+
+  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  if (is_shader) {
+    loop.SetBody("OpLoopMerge %merge %if_merge None\n");
+    if_head.SetBody("OpSelectionMerge %if_merge None\n");
+  }
+
+  string str = header(GetParam()) + nameOps("entry", "loop", "if_head",
+                                            "if_true", "if_merge", "merge") +
+               types_consts() + "%func    = OpFunction %voidt None %funct\n";
+
+  str += entry >> loop;
+  str += loop >> if_head;
+  str += if_head >> vector<Block>({if_true, if_merge});
+  str += if_true >> if_merge;
+  str += if_merge >> vector<Block>({loop, merge});
+  str += merge;
+  str += "OpFunctionEnd";
+
+  CompileSuccessfully(str);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()) << getDiagnosticString();
+}
+
+TEST_P(ValidateCFG, SingleLatchBlockMultipleBranchesToLoopHeader) {
+  // This test case ensures we allow both branches of a loop latch block
+  // to go back to the loop header.  It still counts as a single back edge.
+  bool is_shader = GetParam() == SpvCapabilityShader;
+  Block entry("entry");
+  Block loop("loop", SpvOpBranchConditional);
+  Block latch("latch", SpvOpBranchConditional);
+  Block merge("merge", SpvOpReturn);
+
+  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  if (is_shader) {
+    loop.SetBody("OpLoopMerge %merge %latch None\n");
+  }
+
+  string str = header(GetParam()) + nameOps("entry", "loop", "latch", "merge") +
+               types_consts() + "%func    = OpFunction %voidt None %funct\n";
+
+  str += entry >> loop;
+  str += loop >> vector<Block>({latch, merge});
+  str += latch >> vector<Block>({loop, loop}); // This is the key
+  str += merge;
+  str += "OpFunctionEnd";
+
+  CompileSuccessfully(str);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()) << str
+                                                 << getDiagnosticString();
+}
+
+TEST_P(ValidateCFG, SingleLatchBlockHeaderContinueTargetIsItselfGood) {
+  // This test case ensures we don't count a Continue Target from a loop
+  // header to itself as a self-loop when computing back edges.
+  // Also, it detects that there is an edge from %latch to the pseudo-exit
+  // node, rather than from %loop.  In particular, it detects that we
+  // have used the *reverse* textual order of blocks when computing
+  // predecessor traversal roots.
+  bool is_shader = GetParam() == SpvCapabilityShader;
+  Block entry("entry");
+  Block loop("loop");
+  Block latch("latch");
+  Block merge("merge", SpvOpReturn);
+
+  entry.SetBody("%cond    = OpSLessThan %intt %one %two\n");
+  if (is_shader) {
+    loop.SetBody("OpLoopMerge %merge %loop None\n");
+  }
+
+  string str = header(GetParam()) + nameOps("entry", "loop", "latch", "merge") +
+               types_consts() + "%func    = OpFunction %voidt None %funct\n";
+
+  str += entry >> loop;
+  str += loop >> latch;
+  str += latch >> loop;
+  str += merge;
+  str += "OpFunctionEnd";
+
+  CompileSuccessfully(str);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()) << str
+                                                 << getDiagnosticString();
 }
 
 /// TODO(umar): Switch instructions
diff --git a/test/Validate.SSA.cpp b/test/Validate.SSA.cpp
index 09f0533..5e2267b 100644
--- a/test/Validate.SSA.cpp
+++ b/test/Validate.SSA.cpp
@@ -35,15 +35,16 @@
 #include <utility>
 
 using ::testing::HasSubstr;
+using ::testing::MatchesRegex;
 
 using std::string;
 using std::pair;
 using std::stringstream;
 
 namespace {
-using Validate = spvtest::ValidateBase<pair<string, bool>>;
+using ValidateSSA = spvtest::ValidateBase<pair<string, bool>>;
 
-TEST_F(Validate, Default) {
+TEST_F(ValidateSSA, Default) {
   char str[] = R"(
      OpCapability Shader
      OpMemoryModel Logical GLSL450
@@ -60,7 +61,7 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(Validate, IdUndefinedBad) {
+TEST_F(ValidateSSA, IdUndefinedBad) {
   char str[] = R"(
           OpCapability Shader
           OpMemoryModel Logical GLSL450
@@ -77,7 +78,7 @@
   EXPECT_THAT(getDiagnosticString(), HasSubstr("missing"));
 }
 
-TEST_F(Validate, IdRedefinedBad) {
+TEST_F(ValidateSSA, IdRedefinedBad) {
   char str[] = R"(
      OpCapability Shader
      OpMemoryModel Logical GLSL450
@@ -93,7 +94,7 @@
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
 }
 
-TEST_F(Validate, DominateUsageBad) {
+TEST_F(ValidateSSA, DominateUsageBad) {
   char str[] = R"(
      OpCapability Shader
      OpMemoryModel Logical GLSL450
@@ -106,7 +107,50 @@
   EXPECT_THAT(getDiagnosticString(), HasSubstr("not_dominant"));
 }
 
-TEST_F(Validate, ForwardNameGood) {
+TEST_F(ValidateSSA, DominateUsageWithinBlockBad) {
+  char str[] = R"(
+     OpCapability Shader
+     OpMemoryModel Logical GLSL450
+     OpName %bad "bad"
+%voidt = OpTypeVoid
+%funct = OpTypeFunction %voidt
+%uintt = OpTypeInt 32 0
+%one   = OpConstant %uintt 1
+%func  = OpFunction %voidt None %funct
+%entry = OpLabel
+%sum   = OpIAdd %uintt %one %bad
+%bad   = OpCopyObject %uintt %sum
+         OpReturn
+         OpFunctionEnd
+)";
+  CompileSuccessfully(str);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              MatchesRegex("ID .\\[bad\\] has not been defined"));
+}
+
+TEST_F(ValidateSSA, DominateUsageSameInstructionBad) {
+  char str[] = R"(
+     OpCapability Shader
+     OpMemoryModel Logical GLSL450
+     OpName %sum "sum"
+%voidt = OpTypeVoid
+%funct = OpTypeFunction %voidt
+%uintt = OpTypeInt 32 0
+%one   = OpConstant %uintt 1
+%func  = OpFunction %voidt None %funct
+%entry = OpLabel
+%sum   = OpIAdd %uintt %one %sum
+         OpReturn
+         OpFunctionEnd
+)";
+  CompileSuccessfully(str);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              MatchesRegex("ID .\\[sum\\] has not been defined"));
+}
+
+TEST_F(ValidateSSA, ForwardNameGood) {
   char str[] = R"(
      OpCapability Shader
      OpMemoryModel Logical GLSL450
@@ -122,7 +166,7 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(Validate, ForwardNameMissingTargetBad) {
+TEST_F(ValidateSSA, ForwardNameMissingTargetBad) {
   char str[] = R"(
       OpCapability Shader
       OpMemoryModel Logical GLSL450
@@ -133,7 +177,7 @@
   EXPECT_THAT(getDiagnosticString(), HasSubstr("main"));
 }
 
-TEST_F(Validate, ForwardMemberNameGood) {
+TEST_F(ValidateSSA, ForwardMemberNameGood) {
   char str[] = R"(
            OpCapability Shader
            OpMemoryModel Logical GLSL450
@@ -147,7 +191,7 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(Validate, ForwardMemberNameMissingTargetBad) {
+TEST_F(ValidateSSA, ForwardMemberNameMissingTargetBad) {
   char str[] = R"(
            OpCapability Shader
            OpMemoryModel Logical GLSL450
@@ -162,7 +206,7 @@
   EXPECT_THAT(getDiagnosticString(), HasSubstr("size"));
 }
 
-TEST_F(Validate, ForwardDecorateGood) {
+TEST_F(ValidateSSA, ForwardDecorateGood) {
   char str[] = R"(
            OpCapability Shader
            OpMemoryModel Logical GLSL450
@@ -175,7 +219,7 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(Validate, ForwardDecorateInvalidIDBad) {
+TEST_F(ValidateSSA, ForwardDecorateInvalidIDBad) {
   char str[] = R"(
            OpCapability Shader
            OpMemoryModel Logical GLSL450
@@ -196,7 +240,7 @@
   EXPECT_THAT(getDiagnosticString(), HasSubstr("missing"));
 }
 
-TEST_F(Validate, ForwardMemberDecorateGood) {
+TEST_F(ValidateSSA, ForwardMemberDecorateGood) {
   char str[] = R"(
            OpCapability Shader
            OpMemoryModel Logical GLSL450
@@ -210,7 +254,7 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(Validate, ForwardMemberDecorateInvalidIdBad) {
+TEST_F(ValidateSSA, ForwardMemberDecorateInvalidIdBad) {
   char str[] = R"(
            OpCapability Shader
            OpMemoryModel Logical GLSL450
@@ -226,7 +270,7 @@
   EXPECT_THAT(getDiagnosticString(), HasSubstr("missing"));
 }
 
-TEST_F(Validate, ForwardGroupDecorateGood) {
+TEST_F(ValidateSSA, ForwardGroupDecorateGood) {
   char str[] = R"(
           OpCapability Shader
           OpMemoryModel Logical GLSL450
@@ -243,7 +287,7 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(Validate, ForwardGroupDecorateMissingGroupBad) {
+TEST_F(ValidateSSA, ForwardGroupDecorateMissingGroupBad) {
   char str[] = R"(
            OpCapability Shader
            OpMemoryModel Logical GLSL450
@@ -262,7 +306,7 @@
   EXPECT_THAT(getDiagnosticString(), HasSubstr("missing"));
 }
 
-TEST_F(Validate, ForwardGroupDecorateMissingTargetBad) {
+TEST_F(ValidateSSA, ForwardGroupDecorateMissingTargetBad) {
   char str[] = R"(
            OpCapability Shader
            OpMemoryModel Logical GLSL450
@@ -281,7 +325,7 @@
   EXPECT_THAT(getDiagnosticString(), HasSubstr("missing"));
 }
 
-TEST_F(Validate, ForwardGroupDecorateDecorationGroupDominateBad) {
+TEST_F(ValidateSSA, ForwardGroupDecorateDecorationGroupDominateBad) {
   char str[] = R"(
            OpCapability Shader
            OpMemoryModel Logical GLSL450
@@ -300,7 +344,7 @@
   EXPECT_THAT(getDiagnosticString(), HasSubstr("group"));
 }
 
-TEST_F(Validate, ForwardDecorateInvalidIdBad) {
+TEST_F(ValidateSSA, ForwardDecorateInvalidIdBad) {
   char str[] = R"(
            OpCapability Shader
            OpMemoryModel Logical GLSL450
@@ -321,7 +365,7 @@
   EXPECT_THAT(getDiagnosticString(), HasSubstr("missing"));
 }
 
-TEST_F(Validate, FunctionCallGood) {
+TEST_F(ValidateSSA, FunctionCallGood) {
   char str[] = R"(
          OpCapability Shader
          OpMemoryModel Logical GLSL450
@@ -348,7 +392,7 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(Validate, ForwardFunctionCallGood) {
+TEST_F(ValidateSSA, ForwardFunctionCallGood) {
   char str[] = R"(
          OpCapability Shader
          OpMemoryModel Logical GLSL450
@@ -375,7 +419,7 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(Validate, ForwardBranchConditionalGood) {
+TEST_F(ValidateSSA, ForwardBranchConditionalGood) {
   char str[] = R"(
             OpCapability Shader
             OpMemoryModel Logical GLSL450
@@ -401,7 +445,7 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(Validate, ForwardBranchConditionalWithWeightsGood) {
+TEST_F(ValidateSSA, ForwardBranchConditionalWithWeightsGood) {
   char str[] = R"(
            OpCapability Shader
            OpMemoryModel Logical GLSL450
@@ -427,7 +471,7 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(Validate, ForwardBranchConditionalNonDominantConditionBad) {
+TEST_F(ValidateSSA, ForwardBranchConditionalNonDominantConditionBad) {
   char str[] = R"(
            OpCapability Shader
            OpMemoryModel Logical GLSL450
@@ -456,7 +500,7 @@
   EXPECT_THAT(getDiagnosticString(), HasSubstr("conditional"));
 }
 
-TEST_F(Validate, ForwardBranchConditionalMissingTargetBad) {
+TEST_F(ValidateSSA, ForwardBranchConditionalMissingTargetBad) {
   char str[] = R"(
            OpCapability Shader
            OpMemoryModel Logical GLSL450
@@ -497,6 +541,11 @@
 %intt   =  OpTypeInt 32 1
 %uintt  =  OpTypeInt 32 0
 %vfunct =  OpTypeFunction %voidt
+%intptrt = OpTypePointer UniformConstant %intt
+%zero      = OpConstant %intt 0
+%one       = OpConstant %intt 1
+%ten       = OpConstant %intt 10
+%false     = OpConstantFalse %boolt
 )";
 
 const string kKernelTypesAndConstants = R"(
@@ -507,7 +556,6 @@
 %ndt     = OpTypeStruct %intt %arr3t %arr3t %arr3t
 
 %eventt  = OpTypeEvent
-%intptrt = OpTypePointer UniformConstant %int8t
 
 %offset = OpConstant %intt 0
 %local  = OpConstant %intt 1
@@ -541,7 +589,7 @@
           OpFunctionEnd
 )";
 
-TEST_F(Validate, EnqueueKernelGood) {
+TEST_F(ValidateSSA, EnqueueKernelGood) {
   string str = kHeader + kBasicTypes + kKernelTypesAndConstants +
                kKernelDefinition + R"(
                 %main   = OpFunction %voidt None %vfunct
@@ -558,7 +606,7 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(Validate, ForwardEnqueueKernelGood) {
+TEST_F(ValidateSSA, ForwardEnqueueKernelGood) {
   string str = kHeader + kBasicTypes + kKernelTypesAndConstants + R"(
                 %main   = OpFunction %voidt None %vfunct
                 %mainl  = OpLabel
@@ -575,7 +623,7 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(Validate, EnqueueMissingFunctionBad) {
+TEST_F(ValidateSSA, EnqueueMissingFunctionBad) {
   string str = kHeader + "OpName %kfunc \"kfunc\"" + kBasicTypes +
                kKernelTypesAndConstants + R"(
                 %main   = OpFunction %voidt None %vfunct
@@ -610,7 +658,7 @@
   return out;
 }
 
-TEST_F(Validate, ForwardEnqueueKernelMissingParameter1Bad) {
+TEST_F(ValidateSSA, ForwardEnqueueKernelMissingParameter1Bad) {
   string str = forwardKernelNonDominantParameterBaseCode("missing") + R"(
                 %err    = OpEnqueueKernel %missing %dqueue %flags %ndval
                                         %nevent %event %revent %kfunc %firstp
@@ -623,7 +671,7 @@
   EXPECT_THAT(getDiagnosticString(), HasSubstr("missing"));
 }
 
-TEST_F(Validate, ForwardEnqueueKernelNonDominantParameter2Bad) {
+TEST_F(ValidateSSA, ForwardEnqueueKernelNonDominantParameter2Bad) {
   string str = forwardKernelNonDominantParameterBaseCode("dqueue2") + R"(
                 %err     = OpEnqueueKernel %uintt %dqueue2 %flags %ndval
                                             %nevent %event %revent %kfunc
@@ -637,7 +685,7 @@
   EXPECT_THAT(getDiagnosticString(), HasSubstr("dqueue2"));
 }
 
-TEST_F(Validate, ForwardEnqueueKernelNonDominantParameter3Bad) {
+TEST_F(ValidateSSA, ForwardEnqueueKernelNonDominantParameter3Bad) {
   string str = forwardKernelNonDominantParameterBaseCode("ndval2") + R"(
                 %err    = OpEnqueueKernel %uintt %dqueue %flags %ndval2
                                         %nevent %event %revent %kfunc %firstp
@@ -651,7 +699,7 @@
   EXPECT_THAT(getDiagnosticString(), HasSubstr("ndval2"));
 }
 
-TEST_F(Validate, ForwardEnqueueKernelNonDominantParameter4Bad) {
+TEST_F(ValidateSSA, ForwardEnqueueKernelNonDominantParameter4Bad) {
   string str = forwardKernelNonDominantParameterBaseCode("nevent2") + R"(
               %err    = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent2
                                         %event %revent %kfunc %firstp %psize
@@ -665,7 +713,7 @@
   EXPECT_THAT(getDiagnosticString(), HasSubstr("nevent2"));
 }
 
-TEST_F(Validate, ForwardEnqueueKernelNonDominantParameter5Bad) {
+TEST_F(ValidateSSA, ForwardEnqueueKernelNonDominantParameter5Bad) {
   string str = forwardKernelNonDominantParameterBaseCode("event2") + R"(
               %err     = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent
                                         %event2 %revent %kfunc %firstp %psize
@@ -679,7 +727,7 @@
   EXPECT_THAT(getDiagnosticString(), HasSubstr("event2"));
 }
 
-TEST_F(Validate, ForwardEnqueueKernelNonDominantParameter6Bad) {
+TEST_F(ValidateSSA, ForwardEnqueueKernelNonDominantParameter6Bad) {
   string str = forwardKernelNonDominantParameterBaseCode("revent2") + R"(
               %err     = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent
                                         %event %revent2 %kfunc %firstp %psize
@@ -693,7 +741,7 @@
   EXPECT_THAT(getDiagnosticString(), HasSubstr("revent2"));
 }
 
-TEST_F(Validate, ForwardEnqueueKernelNonDominantParameter8Bad) {
+TEST_F(ValidateSSA, ForwardEnqueueKernelNonDominantParameter8Bad) {
   string str = forwardKernelNonDominantParameterBaseCode("firstp2") + R"(
               %err     = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent
                                         %event %revent %kfunc %firstp2 %psize
@@ -707,7 +755,7 @@
   EXPECT_THAT(getDiagnosticString(), HasSubstr("firstp2"));
 }
 
-TEST_F(Validate, ForwardEnqueueKernelNonDominantParameter9Bad) {
+TEST_F(ValidateSSA, ForwardEnqueueKernelNonDominantParameter9Bad) {
   string str = forwardKernelNonDominantParameterBaseCode("psize2") + R"(
               %err    = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent
                                         %event %revent %kfunc %firstp %psize2
@@ -721,7 +769,7 @@
   EXPECT_THAT(getDiagnosticString(), HasSubstr("psize2"));
 }
 
-TEST_F(Validate, ForwardEnqueueKernelNonDominantParameter10Bad) {
+TEST_F(ValidateSSA, ForwardEnqueueKernelNonDominantParameter10Bad) {
   string str = forwardKernelNonDominantParameterBaseCode("palign2") + R"(
               %err     = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent
                                         %event %revent %kfunc %firstp %psize
@@ -735,7 +783,7 @@
   EXPECT_THAT(getDiagnosticString(), HasSubstr("palign2"));
 }
 
-TEST_F(Validate, ForwardEnqueueKernelNonDominantParameter11Bad) {
+TEST_F(ValidateSSA, ForwardEnqueueKernelNonDominantParameter11Bad) {
   string str = forwardKernelNonDominantParameterBaseCode("lsize2") + R"(
               %err     = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent
                                         %event %revent %kfunc %firstp %psize
@@ -758,14 +806,14 @@
     {"OpGetKernelWorkGroupSize", kNoNDrange},
     {"OpGetKernelPreferredWorkGroupSizeMultiple", kNoNDrange}};
 
-INSTANTIATE_TEST_CASE_P(KernelArgs, Validate, ::testing::ValuesIn(cases),);
+INSTANTIATE_TEST_CASE_P(KernelArgs, ValidateSSA, ::testing::ValuesIn(cases), );
 
 static const string return_instructions = R"(
   OpReturn
   OpFunctionEnd
 )";
 
-TEST_P(Validate, GetKernelGood) {
+TEST_P(ValidateSSA, GetKernelGood) {
   string instruction = GetParam().first;
   bool with_ndrange = GetParam().second;
   string ndrange_param = with_ndrange ? " %ndval " : " ";
@@ -781,7 +829,7 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_P(Validate, ForwardGetKernelGood) {
+TEST_P(ValidateSSA, ForwardGetKernelGood) {
   string instruction = GetParam().first;
   bool with_ndrange = GetParam().second;
   string ndrange_param = with_ndrange ? " %ndval " : " ";
@@ -801,7 +849,7 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_P(Validate, ForwardGetKernelMissingDefinitionBad) {
+TEST_P(ValidateSSA, ForwardGetKernelMissingDefinitionBad) {
   string instruction = GetParam().first;
   bool with_ndrange = GetParam().second;
   string ndrange_param = with_ndrange ? " %ndval " : " ";
@@ -818,7 +866,7 @@
   EXPECT_THAT(getDiagnosticString(), HasSubstr("missing"));
 }
 
-TEST_P(Validate, ForwardGetKernelNDrangeSubGroupCountMissingParameter1Bad) {
+TEST_P(ValidateSSA, ForwardGetKernelNDrangeSubGroupCountMissingParameter1Bad) {
   string instruction = GetParam().first;
   bool with_ndrange = GetParam().second;
   string ndrange_param = with_ndrange ? " %ndval " : " ";
@@ -835,7 +883,8 @@
   EXPECT_THAT(getDiagnosticString(), HasSubstr("missing"));
 }
 
-TEST_P(Validate, ForwardGetKernelNDrangeSubGroupCountNonDominantParameter2Bad) {
+TEST_P(ValidateSSA,
+       ForwardGetKernelNDrangeSubGroupCountNonDominantParameter2Bad) {
   string instruction = GetParam().first;
   bool with_ndrange = GetParam().second;
   string ndrange_param = with_ndrange ? " %ndval2 " : " ";
@@ -855,7 +904,8 @@
   }
 }
 
-TEST_P(Validate, ForwardGetKernelNDrangeSubGroupCountNonDominantParameter4Bad) {
+TEST_P(ValidateSSA,
+       ForwardGetKernelNDrangeSubGroupCountNonDominantParameter4Bad) {
   string instruction = GetParam().first;
   bool with_ndrange = GetParam().second;
   string ndrange_param = with_ndrange ? " %ndval " : " ";
@@ -873,7 +923,8 @@
   EXPECT_THAT(getDiagnosticString(), HasSubstr("firstp2"));
 }
 
-TEST_P(Validate, ForwardGetKernelNDrangeSubGroupCountNonDominantParameter5Bad) {
+TEST_P(ValidateSSA,
+       ForwardGetKernelNDrangeSubGroupCountNonDominantParameter5Bad) {
   string instruction = GetParam().first;
   bool with_ndrange = GetParam().second;
   string ndrange_param = with_ndrange ? " %ndval " : " ";
@@ -891,7 +942,8 @@
   EXPECT_THAT(getDiagnosticString(), HasSubstr("psize2"));
 }
 
-TEST_P(Validate, ForwardGetKernelNDrangeSubGroupCountNonDominantParameter6Bad) {
+TEST_P(ValidateSSA,
+       ForwardGetKernelNDrangeSubGroupCountNonDominantParameter6Bad) {
   string instruction = GetParam().first;
   bool with_ndrange = GetParam().second;
   string ndrange_param = with_ndrange ? " %ndval " : " ";
@@ -911,12 +963,9 @@
   }
 }
 
-TEST_F(Validate, PhiGood) {
+TEST_F(ValidateSSA, PhiGood) {
   string str = kHeader + kBasicTypes +
                R"(
-%zero      = OpConstant %intt 0
-%one       = OpConstant %intt 1
-%ten       = OpConstant %intt 10
 %func      = OpFunction %voidt None %vfunct
 %preheader = OpLabel
 %init      = OpCopyObject %intt %zero
@@ -937,12 +986,9 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(Validate, PhiMissingTypeBad) {
+TEST_F(ValidateSSA, PhiMissingTypeBad) {
   string str = kHeader + "OpName %missing \"missing\"" + kBasicTypes +
                R"(
-%zero      = OpConstant %intt 0
-%one       = OpConstant %intt 1
-%ten       = OpConstant %intt 10
 %func      = OpFunction %voidt None %vfunct
 %preheader = OpLabel
 %init      = OpCopyObject %intt %zero
@@ -964,12 +1010,9 @@
   EXPECT_THAT(getDiagnosticString(), HasSubstr("missing"));
 }
 
-TEST_F(Validate, PhiMissingIdBad) {
+TEST_F(ValidateSSA, PhiMissingIdBad) {
   string str = kHeader + "OpName %missing \"missing\"" + kBasicTypes +
                R"(
-%zero      = OpConstant %intt 0
-%one       = OpConstant %intt 1
-%ten       = OpConstant %intt 10
 %func      = OpFunction %voidt None %vfunct
 %preheader = OpLabel
 %init      = OpCopyObject %intt %zero
@@ -991,12 +1034,9 @@
   EXPECT_THAT(getDiagnosticString(), HasSubstr("missing"));
 }
 
-TEST_F(Validate, PhiMissingLabelBad) {
+TEST_F(ValidateSSA, PhiMissingLabelBad) {
   string str = kHeader + "OpName %missing \"missing\"" + kBasicTypes +
                R"(
-%zero      = OpConstant %intt 0
-%one       = OpConstant %intt 1
-%ten       = OpConstant %intt 10
 %func      = OpFunction %voidt None %vfunct
 %preheader = OpLabel
 %init      = OpCopyObject %intt %zero
@@ -1018,5 +1058,247 @@
   EXPECT_THAT(getDiagnosticString(), HasSubstr("missing"));
 }
 
-// TODO(umar): OpGroupMemberDecorate
+TEST_F(ValidateSSA, IdDominatesItsUseGood) {
+  string str = kHeader + kBasicTypes +
+               R"(
+%func      = OpFunction %voidt None %vfunct
+%entry     = OpLabel
+%cond      = OpSLessThan %intt %one %ten
+%eleven    = OpIAdd %intt %one %ten
+             OpSelectionMerge %merge None
+             OpBranchConditional %cond %t %f
+%t         = OpLabel
+%twelve    = OpIAdd %intt %eleven %one
+             OpBranch %merge
+%f         = OpLabel
+%twentytwo = OpIAdd %intt %eleven %ten
+             OpBranch %merge
+%merge     = OpLabel
+             OpReturn
+             OpFunctionEnd
+)";
+
+  CompileSuccessfully(str);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
+
+TEST_F(ValidateSSA, IdDoesNotDominateItsUseBad) {
+  string str = kHeader +
+               "OpName %eleven \"eleven\"\n"
+               "OpName %true_block \"true_block\"\n"
+               "OpName %false_block \"false_block\"" +
+               kBasicTypes +
+               R"(
+%func        = OpFunction %voidt None %vfunct
+%entry       = OpLabel
+%cond        = OpSLessThan %intt %one %ten
+               OpSelectionMerge %merge None
+               OpBranchConditional %cond %true_block %false_block
+%true_block  = OpLabel
+%eleven      = OpIAdd %intt %one %ten
+%twelve      = OpIAdd %intt %eleven %one
+               OpBranch %merge
+%false_block = OpLabel
+%twentytwo   = OpIAdd %intt %eleven %ten
+               OpBranch %merge
+%merge       = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+  CompileSuccessfully(str);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      MatchesRegex("ID .\\[eleven\\] defined in block .\\[true_block\\] does "
+                   "not dominate its use in block .\\[false_block\\]"));
+}
+
+TEST_F(ValidateSSA, PhiUseDoesntDominateDefinitionGood) {
+  string str = kHeader + kBasicTypes +
+               R"(
+%func        = OpFunction %voidt None %vfunct
+%entry       = OpLabel
+%var_one     = OpVariable %intptrt Function %one
+%one_val     = OpLoad %intt %var_one
+               OpBranch %loop
+%loop        = OpLabel
+%i           = OpPhi %intt %one_val %entry %inew %cont
+%cond        = OpSLessThan %intt %one %ten
+               OpLoopMerge %merge %cont None
+               OpBranchConditional %cond %body %merge
+%body        = OpLabel
+               OpBranch %cont
+%cont        = OpLabel
+%inew        = OpIAdd %intt %i %one
+               OpBranch %loop
+%merge       = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(str);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateSSA, PhiUseDoesntDominateUseOfPhiOperandUsedBeforeDefinitionBad) {
+  string str = kHeader
+      + "OpName %inew \"inew\""
+      + kBasicTypes +
+               R"(
+%func        = OpFunction %voidt None %vfunct
+%entry       = OpLabel
+%var_one     = OpVariable %intptrt Function %one
+%one_val     = OpLoad %intt %var_one
+               OpBranch %loop
+%loop        = OpLabel
+%i           = OpPhi %intt %one_val %entry %inew %cont
+%bad         = OpIAdd %intt %inew %one
+%cond        = OpSLessThan %intt %one %ten
+               OpLoopMerge %merge %cont None
+               OpBranchConditional %cond %body %merge
+%body        = OpLabel
+               OpBranch %cont
+%cont        = OpLabel
+%inew        = OpIAdd %intt %i %one
+               OpBranch %loop
+%merge       = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(str);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+    getDiagnosticString(),
+    MatchesRegex("ID .\\[inew\\] has not been defined"));
+}
+
+TEST_F(ValidateSSA, PhiUseMayComeFromNonDominatingBlockGood) {
+  string str = kHeader
+      + "OpName %if_true \"if_true\"\n"
+      + "OpName %exit \"exit\"\n"
+      + "OpName %copy \"copy\"\n"
+      + kBasicTypes +
+               R"(
+%func        = OpFunction %voidt None %vfunct
+%entry       = OpLabel
+               OpBranchConditional %false %if_true %exit
+
+%if_true     = OpLabel
+%copy        = OpCopyObject %boolt %false
+               OpBranch %exit
+
+; The use of %copy here is ok, even though it was defined
+; in a block that does not dominate %exit.  That's the point
+; of an OpPhi.
+%exit        = OpLabel
+%value       = OpPhi %boolt %false %entry %copy %if_true
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(str);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()) << getDiagnosticString();
+}
+
+TEST_F(ValidateSSA, DISABLED_PhiVariableDefMustComeFromBlockDominatingThePredecessorBad) {
+  string str = kHeader
+      + "OpName %if_true \"if_true\"\n"
+      + "OpName %if_false \"if_false\"\n"
+      + "OpName %exit \"exit\"\n"
+      + "OpName %true_copy \"true_copy\"\n"
+      + "OpName %false_copy \"false_copy\"\n"
+      + kBasicTypes +
+               R"(
+%func        = OpFunction %voidt None %vfunct
+%entry       = OpLabel
+               OpBranchConditional %false %if_true %if_false
+
+%if_true     = OpLabel
+%true_copy   = OpCopyObject %boolt %false
+               OpBranch %exit
+
+%if_false    = OpLabel
+%false_copy  = OpCopyObject %boolt %false
+               OpBranch %exit
+
+; The (variable,Id) pairs are swapped.
+%exit        = OpLabel
+%value       = OpPhi %boolt %true_copy %if_false %false_copy %if_true
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(str);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  // TODO(dneto): Check for a good error message
+}
+
+TEST_F(ValidateSSA, DominanceCheckIgnoresUsesInUnreachableBlocksDefInBlockGood) {
+  string str = kHeader
+      + kBasicTypes +
+               R"(
+%func        = OpFunction %voidt None %vfunct
+%entry       = OpLabel
+%def         = OpCopyObject %boolt %false
+               OpReturn
+
+%unreach     = OpLabel
+%use         = OpCopyObject %boolt %def
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(str);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()) << getDiagnosticString();
+}
+
+TEST_F(ValidateSSA, DominanceCheckIgnoresUsesInUnreachableBlocksDefIsParamGood) {
+  string str = kHeader + kBasicTypes +
+               R"(
+%void_fn_int = OpTypeFunction %voidt %intt
+%func        = OpFunction %voidt None %void_fn_int
+%int_param   = OpFunctionParameter %intt
+%entry       = OpLabel
+               OpReturn
+
+%unreach     = OpLabel
+%use         = OpCopyObject %intt %int_param
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(str);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()) << getDiagnosticString();
+}
+
+TEST_F(ValidateSSA, UseFunctionParameterFromOtherFunctionBad) {
+  string str = kHeader +
+               "OpName %first \"first\"\n"
+               "OpName %entry2 \"entry2\"\n"
+               "OpName %func \"func\"\n" +
+               kBasicTypes +
+               R"(
+%viifunct  = OpTypeFunction %voidt %intt %intt
+%func      = OpFunction %voidt None %viifunct
+%first     = OpFunctionParameter %intt
+%second    = OpFunctionParameter %intt
+             OpFunctionEnd
+%func2     = OpFunction %voidt None %viifunct
+%first2    = OpFunctionParameter %intt
+%second2   = OpFunctionParameter %intt
+%entry2    = OpLabel
+%baduse    = OpIAdd %intt %first %first2
+             OpReturn
+             OpFunctionEnd
+)";
+
+  CompileSuccessfully(str);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      MatchesRegex("ID .\\[first\\] used in block .\\[entry2\\] is used "
+                   "outside of it's defining function .\\[func\\]"));
+}
+// TODO(umar): OpGroupMemberDecorate
+}  // namespace
diff --git a/test/opt/CMakeLists.txt b/test/opt/CMakeLists.txt
index f83b793..90bd472 100644
--- a/test/opt/CMakeLists.txt
+++ b/test/opt/CMakeLists.txt
@@ -39,7 +39,22 @@
   LIBS SPIRV-Tools-opt ${SPIRV_TOOLS}
 )
 
+add_spvtools_unittest(TARGET pass_freeze_spec_const
+  SRCS test_freeze_spec_const.cpp pass_utils.cpp
+  LIBS SPIRV-Tools-opt ${SPIRV_TOOLS}
+)
+
 add_spvtools_unittest(TARGET pass_utils
   SRCS test_utils.cpp pass_utils.cpp
   LIBS SPIRV-Tools-opt ${SPIRV_TOOLS}
 )
+
+add_spvtools_unittest(TARGET def_use
+  SRCS test_def_use.cpp pass_utils.cpp
+  LIBS SPIRV-Tools-opt ${SPIRV_TOOLS}
+)
+
+add_spvtools_unittest(TARGET assembly_builder
+  SRCS test_assembly_builder.cpp pass_utils.cpp
+  LIBS SPIRV-Tools-opt ${SPIRV_TOOLS}
+)
diff --git a/test/opt/assembly_builder.h b/test/opt/assembly_builder.h
new file mode 100644
index 0000000..855a546
--- /dev/null
+++ b/test/opt/assembly_builder.h
@@ -0,0 +1,250 @@
+// Copyright (c) 2016 Google Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and/or associated documentation files (the
+// "Materials"), to deal in the Materials without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Materials, and to
+// permit persons to whom the Materials are furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Materials.
+//
+// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS
+// KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS
+// SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT
+//    https://www.khronos.org/registry/
+//
+// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+
+#ifndef LIBSPIRV_TEST_OPT_ASSEMBLY_BUILDER
+#define LIBSPIRV_TEST_OPT_ASSEMBLY_BUILDER
+
+#include <algorithm>
+#include <sstream>
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+namespace spvtools {
+
+// A simple SPIR-V assembly code builder for test uses. It builds an SPIR-V
+// assembly module from vectors of assembly strings. It allows users to add
+// instructions to the main function and the type-constants-globals section
+// directly. It relies on OpName instructions and friendly-name disassembling
+// to keep the ID names unchanged after assembling.
+//
+// An assembly module is divided into several sections, matching with the
+// SPIR-V Logical Layout:
+//  Global Preamble:
+//    OpCapability instructions;
+//    OpExtension instructions and OpExtInstImport instructions;
+//    OpMemoryModel instruction;
+//    OpEntryPoint and OpExecutionMode instruction;
+//    OpString, OpSourceExtension, OpSource and OpSourceContinued instructions.
+//  Names:
+//    OpName instructions.
+//  Annotations:
+//    OpDecorate, OpMemberDecorate, OpGroupDecorate, OpGroupMemberDecorate and
+//    OpDecorationGroup.
+//  Types, Constants and Global variables:
+//    Types, constants and global variables declaration instructions.
+//  Main Function:
+//    Main function instructions.
+//  Main Function Postamble:
+//    The return and function end instructions.
+//
+// The assembly code is built by concatenating all the strings in the above
+// sections.
+//
+// Users define the contents in section <Type, Constants and Global Variables>
+// and <Main Function>. The <Names> section is to hold the names for IDs to
+// keep them unchanged before and after assembling. All defined IDs to be added
+// to this code builder will be assigned with a global name through OpName
+// instruction. The name is extracted from the definition instruction.
+//  E.g. adding instruction: %var_a = OpConstant %int 2, will also add an
+//  instruction: OpName %var_a, "var_a".
+//
+// Note that the name must not be used on more than one defined IDs and
+// friendly-name disassembling must be enabled so that OpName instructions will
+// be respected.
+class AssemblyBuilder {
+  // The base ID value for spec constants.
+  static const uint32_t SPEC_ID_BASE = 200;
+
+ public:
+  // Initalize a minimal SPIR-V assembly code as the template. The minimal
+  // module contains an empty main function and some predefined names for the
+  // main function.
+  AssemblyBuilder()
+      : spec_id_counter_(SPEC_ID_BASE),
+        global_preamble_({
+            // clang-format off
+                  "OpCapability Shader",
+                  "OpCapability Float64",
+             "%1 = OpExtInstImport \"GLSL.std.450\"",
+                  "OpMemoryModel Logical GLSL450",
+                  "OpEntryPoint Vertex %main \"main\"",
+            // clang-format on
+        }),
+        names_(),
+        annotations_(),
+        types_consts_globals_(),
+        main_func_(),
+        main_func_postamble_({
+            "OpReturn", "OpFunctionEnd",
+        }) {
+    AppendTypesConstantsGlobals({
+        "%void = OpTypeVoid", "%main_func_type = OpTypeFunction %void",
+    });
+    AppendInMain({
+        "%main = OpFunction %void None %main_func_type",
+        "%main_func_entry_block = OpLabel",
+    });
+  }
+
+  // Appends instructions to the types-constants-globals section and returns
+  // the reference of this assembly builder. IDs defined in the given code will
+  // be added to the Names section and then be registered with OpName
+  // instruction. Corresponding decoration instruction will be added for spec
+  // constants defined with opcode: 'OpSpecConstant'.
+  AssemblyBuilder& AppendTypesConstantsGlobals(
+      const std::vector<std::string>& vec_asm_code) {
+    AddNamesForResultIDsIn(vec_asm_code);
+    // Check spec constants defined with OpSpecConstant.
+    for (auto& inst_str : vec_asm_code) {
+      if (inst_str.find("= OpSpecConstant ") != std::string::npos ||
+          inst_str.find("= OpSpecConstantTrue ") != std::string::npos ||
+          inst_str.find("= OpSpecConstantFalse ") != std::string::npos) {
+        AddSpecIDFor(GetResultIDName(inst_str));
+      }
+    }
+    types_consts_globals_.insert(types_consts_globals_.end(),
+                                 vec_asm_code.begin(), vec_asm_code.end());
+    return *this;
+  }
+
+  // Appends instructions to the main function block, which is already labelled
+  // with "main_func_entry_block". Returns the reference of this assembly
+  // builder. IDs defined in the given code will be added to the Names section
+  // and then be registered with OpName instruction.
+  AssemblyBuilder& AppendInMain(const std::vector<std::string>& vec_asm_code) {
+    AddNamesForResultIDsIn(vec_asm_code);
+    main_func_.insert(main_func_.end(), vec_asm_code.begin(),
+                      vec_asm_code.end());
+    return *this;
+  }
+
+  // Appends annotation instructions to the annotation section, and returns the
+  // reference of this assembly builder.
+  AssemblyBuilder& AppendAnnotations(
+      const std::vector<std::string>& vec_annotations) {
+    annotations_.insert(annotations_.end(), vec_annotations.begin(),
+                        vec_annotations.end());
+    return *this;
+  }
+
+  // Get the SPIR-V assembly code as string.
+  std::string GetCode() const {
+    std::ostringstream ss;
+    for (const auto& line : global_preamble_) {
+      ss << line << std::endl;
+    }
+    for (const auto& line : names_) {
+      ss << line << std::endl;
+    }
+    for (const auto& line : annotations_) {
+      ss << line << std::endl;
+    }
+    for (const auto& line : types_consts_globals_) {
+      ss << line << std::endl;
+    }
+    for (const auto& line : main_func_) {
+      ss << line << std::endl;
+    }
+    for (const auto& line : main_func_postamble_) {
+      ss << line << std::endl;
+    }
+    return ss.str();
+  }
+
+ private:
+  // Adds a given name to the Name section with OpName. If the given name has
+  // been added before, does nothing.
+  void AddOpNameIfNotExist(const std::string& id_name) {
+    if (!used_names_.count(id_name)) {
+      std::stringstream opname_inst;
+      opname_inst << "OpName "
+                  << "%" << id_name << " \"" << id_name << "\"";
+      names_.emplace_back(opname_inst.str());
+      used_names_.insert(id_name);
+    }
+  }
+
+  // Adds the names in a vector of assembly code strings to the Names section.
+  // If a '=' sign is found in an instruction, this instruction will be treated
+  // as an ID defining instruction. The ID name used in the instruction will be
+  // extracted and added to the Names section.
+  void AddNamesForResultIDsIn(const std::vector<std::string>& vec_asm_code) {
+    for (const auto& line : vec_asm_code) {
+      std::string name = GetResultIDName(line);
+      if (!name.empty()) {
+        AddOpNameIfNotExist(name);
+      }
+    }
+  }
+
+  // Adds an OpDecorate SpecId instruction for the given ID name.
+  void AddSpecIDFor(const std::string& id_name) {
+    std::stringstream decorate_inst;
+    decorate_inst << "OpDecorate "
+                  << "%" << id_name << " SpecId " << spec_id_counter_;
+    spec_id_counter_ += 1;
+    annotations_.emplace_back(decorate_inst.str());
+  }
+
+  // Extracts the ID name from a SPIR-V assembly instruction string. If the
+  // instruction is an ID-defining instruction (has result ID), returns the
+  // name of the result ID in string. If the instruction does not have result
+  // ID, returns an empty string.
+  std::string GetResultIDName(const std::string inst_str) {
+    std::string name;
+    if (inst_str.find('=') != std::string::npos) {
+      size_t assign_sign = inst_str.find('=');
+      name = inst_str.substr(0, assign_sign);
+      name.erase(remove_if(name.begin(), name.end(),
+                           [](char c) { return c == ' ' || c == '%'; }),
+                 name.end());
+    }
+    return name;
+  }
+
+  uint32_t spec_id_counter_;
+  // The vector that contains common preambles shared across all test SPIR-V
+  // code.
+  std::vector<std::string> global_preamble_;
+  // The vector that contains OpName instructions.
+  std::vector<std::string> names_;
+  // The vector that contains annotation instructions.
+  std::vector<std::string> annotations_;
+  // The vector that contains the code to declare types, constants and global
+  // variables (aka. the Types-Constants-Globals section).
+  std::vector<std::string> types_consts_globals_;
+  // The vector that contains the code in main function's entry block.
+  std::vector<std::string> main_func_;
+  // The vector that contains the postamble of main function body.
+  std::vector<std::string> main_func_postamble_;
+  // All of the defined variable names.
+  std::unordered_set<std::string> used_names_;
+};
+
+}  // namespace spvtools
+
+#endif  // LIBSPIRV_TEST_OPT_ASSEMBLY_BUILDER
diff --git a/test/opt/pass_fixture.h b/test/opt/pass_fixture.h
index 0c3b2df..1247184 100644
--- a/test/opt/pass_fixture.h
+++ b/test/opt/pass_fixture.h
@@ -29,6 +29,7 @@
 
 #include <memory>
 #include <string>
+#include <tuple>
 #include <vector>
 
 #include <gtest/gtest.h>
@@ -52,26 +53,50 @@
   PassTest()
       : tools_(SPV_ENV_UNIVERSAL_1_1), manager_(new opt::PassManager()) {}
 
+  // Runs the given |pass| on the binary assembled from the |assembly|, and
+  // disassebles the optimized binary. Returns a tuple of disassembly string
+  // and the boolean value returned from pass Process() function.
+  std::tuple<std::string, bool> OptimizeAndDisassemble(
+      opt::Pass* pass, const std::string& original, bool skip_nop = false) {
+    std::unique_ptr<ir::Module> module = tools_.BuildModule(original);
+    EXPECT_NE(nullptr, module);
+    if (!module) {
+      return std::make_tuple(std::string(), false);
+    }
+
+    const bool modified = pass->Process(module.get());
+
+    std::vector<uint32_t> binary;
+    module->ToBinary(&binary, skip_nop);
+    std::string optimized;
+    EXPECT_EQ(SPV_SUCCESS, tools_.Disassemble(binary, &optimized));
+    return std::make_tuple(optimized, modified);
+  }
+
+  // Runs a single pass of class |PassT| on the binary assembled from the
+  // |assembly|, disassembles the optimized binary. Returns a tuple of
+  // disassembly string and the boolean value from the pass Process() function.
+  template <typename PassT>
+  std::tuple<std::string, bool> SinglePassRunAndDisassemble(
+      const std::string& assembly, bool skip_nop = false) {
+    auto pass = std::unique_ptr<PassT>(new PassT);
+    return OptimizeAndDisassemble(pass.get(), assembly, skip_nop);
+  }
+
   // Runs a single pass of class |PassT| on the binary assembled from the
   // |original| assembly, and checks whether the optimized binary can be
   // disassembled to the |expected| assembly. This does *not* involve pass
   // manager. Callers are suggested to use SCOPED_TRACE() for better messages.
   template <typename PassT>
   void SinglePassRunAndCheck(const std::string& original,
-                             const std::string& expected) {
-    std::unique_ptr<ir::Module> module = tools_.BuildModule(original);
-    ASSERT_NE(nullptr, module);
-
-    const bool modified =
-        std::unique_ptr<PassT>(new PassT)->Process(module.get());
+                             const std::string& expected,
+                             bool skip_nop = false) {
+    std::string optimized;
+    bool modified = false;
+    std::tie(optimized, modified) =
+        SinglePassRunAndDisassemble<PassT>(original, skip_nop);
     // Check whether the pass returns the correct modification indication.
     EXPECT_EQ(original != expected, modified);
-
-    std::vector<uint32_t> binary;
-    module->ToBinary(&binary, /* skip_nop = */ false);
-
-    std::string optimized;
-    EXPECT_EQ(SPV_SUCCESS, tools_.Disassemble(binary, &optimized));
     EXPECT_EQ(expected, optimized);
   }
 
diff --git a/test/opt/pass_utils.cpp b/test/opt/pass_utils.cpp
index a6f2374..68bc1a4 100644
--- a/test/opt/pass_utils.cpp
+++ b/test/opt/pass_utils.cpp
@@ -43,7 +43,27 @@
     // clang-format on
 };
 
-// Returns true if the given string contains any debug opcode substring.
+}  // anonymous namespace
+
+namespace spvtools {
+
+bool FindAndReplace(std::string* process_str, const std::string find_str,
+                    const std::string replace_str) {
+  if (process_str->empty() || find_str.empty()) {
+    return false;
+  }
+  bool replaced = false;
+  // Note this algorithm has quadratic time complexity. It is OK for test cases
+  // with short strings, but might not fit in other contexts.
+  for (size_t pos = process_str->find(find_str, 0); pos != std::string::npos;
+       pos = process_str->find(find_str, pos)) {
+    process_str->replace(pos, find_str.length(), replace_str);
+    pos += replace_str.length();
+    replaced = true;
+  }
+  return replaced;
+}
+
 bool ContainsDebugOpcode(const char* inst) {
   return std::any_of(std::begin(kDebugOpcodes), std::end(kDebugOpcodes),
                      [inst](const char* op) {
@@ -51,10 +71,6 @@
                      });
 }
 
-}  // anonymous namespace
-
-namespace spvtools {
-
 std::string SelectiveJoin(const std::vector<const char*>& strings,
                           const std::function<bool(const char*)>& skip_dictator,
                           char delimiter) {
diff --git a/test/opt/pass_utils.h b/test/opt/pass_utils.h
index 6e047db..f504eb5 100644
--- a/test/opt/pass_utils.h
+++ b/test/opt/pass_utils.h
@@ -33,6 +33,18 @@
 
 namespace spvtools {
 
+// In-place substring replacement. Finds the |find_str| in the |process_str|
+// and replaces the found substring with |replace_str|. Returns true if at
+// least one replacement is done successfully, returns false otherwise. The
+// replaced substring won't be processed again, which means: If the
+// |replace_str| has |find_str| as its substring, that newly replaced part of
+// |process_str| won't be processed again.
+bool FindAndReplace(std::string* process_str, const std::string find_str,
+                    const std::string replace_str);
+
+// Returns true if the given string contains any debug opcode substring.
+bool ContainsDebugOpcode(const char* inst);
+
 // Returns the concatenated string from a vector of |strings|, with postfixing
 // each string with the given |delimiter|. if the |skip_dictator| returns true
 // for an original string, that string will be omitted.
diff --git a/test/opt/test_assembly_builder.cpp b/test/opt/test_assembly_builder.cpp
new file mode 100644
index 0000000..7303047
--- /dev/null
+++ b/test/opt/test_assembly_builder.cpp
@@ -0,0 +1,256 @@
+// Copyright (c) 2016 Google Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and/or associated documentation files (the
+// "Materials"), to deal in the Materials without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Materials, and to
+// permit persons to whom the Materials are furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Materials.
+//
+// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS
+// KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS
+// SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT
+//    https://www.khronos.org/registry/
+//
+// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+
+#include "assembly_builder.h"
+
+#include "pass_fixture.h"
+#include "pass_utils.h"
+
+namespace {
+
+using namespace spvtools;
+using AssemblyBuilderTest = PassTest<::testing::Test>;
+
+TEST_F(AssemblyBuilderTest, MinimalShader) {
+  AssemblyBuilder builder;
+  std::vector<const char*> expected = {
+      // clang-format off
+                    "OpCapability Shader",
+                    "OpCapability Float64",
+               "%1 = OpExtInstImport \"GLSL.std.450\"",
+                    "OpMemoryModel Logical GLSL450",
+                    "OpEntryPoint Vertex %main \"main\"",
+                    "OpName %void \"void\"",
+                    "OpName %main_func_type \"main_func_type\"",
+                    "OpName %main \"main\"",
+                    "OpName %main_func_entry_block \"main_func_entry_block\"",
+            "%void = OpTypeVoid",
+  "%main_func_type = OpTypeFunction %void",
+            "%main = OpFunction %void None %main_func_type",
+"%main_func_entry_block = OpLabel",
+                    "OpReturn",
+                    "OpFunctionEnd",
+      // clang-format on
+  };
+
+  SinglePassRunAndCheck<opt::NullPass>(builder.GetCode(),
+                                       JoinAllInsts(expected));
+}
+
+TEST_F(AssemblyBuilderTest, ShaderWithConstants) {
+  AssemblyBuilder builder;
+  builder
+      .AppendTypesConstantsGlobals({
+          // clang-format off
+          "%bool = OpTypeBool",
+      "%_PF_bool = OpTypePointer Function %bool",
+            "%bt = OpConstantTrue %bool",
+            "%bf = OpConstantFalse %bool",
+           "%int = OpTypeInt 32 1",
+       "%_PF_int = OpTypePointer Function %int",
+            "%si = OpConstant %int 1",
+          "%uint = OpTypeInt 32 0",
+      "%_PF_uint = OpTypePointer Function %uint",
+            "%ui = OpConstant %uint 2",
+         "%float = OpTypeFloat 32",
+     "%_PF_float = OpTypePointer Function %float",
+             "%f = OpConstant %float 3.14",
+        "%double = OpTypeFloat 64",
+    "%_PF_double = OpTypePointer Function %double",
+             "%d = OpConstant %double 3.14159265358979",
+          // clang-format on
+      })
+      .AppendInMain({
+          // clang-format off
+          "%btv = OpVariable %_PF_bool Function",
+          "%bfv = OpVariable %_PF_bool Function",
+           "%iv = OpVariable %_PF_int Function",
+           "%uv = OpVariable %_PF_uint Function",
+           "%fv = OpVariable %_PF_float Function",
+           "%dv = OpVariable %_PF_double Function",
+                 "OpStore %btv %bt",
+                 "OpStore %bfv %bf",
+                 "OpStore %iv %si",
+                 "OpStore %uv %ui",
+                 "OpStore %fv %f",
+                 "OpStore %dv %d",
+          // clang-format on
+      });
+
+  std::vector<const char*> expected = {
+      // clang-format off
+                "OpCapability Shader",
+                "OpCapability Float64",
+           "%1 = OpExtInstImport \"GLSL.std.450\"",
+                "OpMemoryModel Logical GLSL450",
+                "OpEntryPoint Vertex %main \"main\"",
+                "OpName %void \"void\"",
+                "OpName %main_func_type \"main_func_type\"",
+                "OpName %main \"main\"",
+                "OpName %main_func_entry_block \"main_func_entry_block\"",
+                "OpName %bool \"bool\"",
+                "OpName %_PF_bool \"_PF_bool\"",
+                "OpName %bt \"bt\"",
+                "OpName %bf \"bf\"",
+                "OpName %int \"int\"",
+                "OpName %_PF_int \"_PF_int\"",
+                "OpName %si \"si\"",
+                "OpName %uint \"uint\"",
+                "OpName %_PF_uint \"_PF_uint\"",
+                "OpName %ui \"ui\"",
+                "OpName %float \"float\"",
+                "OpName %_PF_float \"_PF_float\"",
+                "OpName %f \"f\"",
+                "OpName %double \"double\"",
+                "OpName %_PF_double \"_PF_double\"",
+                "OpName %d \"d\"",
+                "OpName %btv \"btv\"",
+                "OpName %bfv \"bfv\"",
+                "OpName %iv \"iv\"",
+                "OpName %uv \"uv\"",
+                "OpName %fv \"fv\"",
+                "OpName %dv \"dv\"",
+        "%void = OpTypeVoid",
+"%main_func_type = OpTypeFunction %void",
+        "%bool = OpTypeBool",
+ "%_PF_bool = OpTypePointer Function %bool",
+          "%bt = OpConstantTrue %bool",
+          "%bf = OpConstantFalse %bool",
+         "%int = OpTypeInt 32 1",
+     "%_PF_int = OpTypePointer Function %int",
+          "%si = OpConstant %int 1",
+        "%uint = OpTypeInt 32 0",
+    "%_PF_uint = OpTypePointer Function %uint",
+          "%ui = OpConstant %uint 2",
+       "%float = OpTypeFloat 32",
+   "%_PF_float = OpTypePointer Function %float",
+           "%f = OpConstant %float 3.14",
+      "%double = OpTypeFloat 64",
+  "%_PF_double = OpTypePointer Function %double",
+           "%d = OpConstant %double 3.14159265358979",
+        "%main = OpFunction %void None %main_func_type",
+"%main_func_entry_block = OpLabel",
+         "%btv = OpVariable %_PF_bool Function",
+         "%bfv = OpVariable %_PF_bool Function",
+          "%iv = OpVariable %_PF_int Function",
+          "%uv = OpVariable %_PF_uint Function",
+          "%fv = OpVariable %_PF_float Function",
+          "%dv = OpVariable %_PF_double Function",
+                "OpStore %btv %bt",
+                "OpStore %bfv %bf",
+                "OpStore %iv %si",
+                "OpStore %uv %ui",
+                "OpStore %fv %f",
+                "OpStore %dv %d",
+                "OpReturn",
+                "OpFunctionEnd",
+      // clang-format on
+  };
+  SinglePassRunAndCheck<opt::NullPass>(builder.GetCode(),
+                                       JoinAllInsts(expected));
+}
+
+TEST_F(AssemblyBuilderTest, SpecConstants) {
+  AssemblyBuilder builder;
+  builder.AppendTypesConstantsGlobals({
+      "%bool = OpTypeBool", "%uint = OpTypeInt 32 0", "%int = OpTypeInt 32 1",
+      "%float = OpTypeFloat 32", "%double = OpTypeFloat 64",
+      "%v2int = OpTypeVector %int 2",
+
+      "%spec_true = OpSpecConstantTrue %bool",
+      "%spec_false = OpSpecConstantFalse %bool",
+      "%spec_uint = OpSpecConstant %uint 1",
+      "%spec_int = OpSpecConstant %int 1",
+      "%spec_float = OpSpecConstant %float 1.2",
+      "%spec_double = OpSpecConstant %double 1.23456789",
+
+      // Spec constants defined below should not have SpecID.
+      "%spec_add_op = OpSpecConstantOp %int IAdd %spec_int %spec_int",
+      "%spec_vec = OpSpecConstantComposite %v2int %spec_int %spec_int",
+      "%spec_vec_x = OpSpecConstantOp %int CompositeExtract %spec_vec 0",
+  });
+  std::vector<const char*> expected = {
+      // clang-format off
+                    "OpCapability Shader",
+                    "OpCapability Float64",
+               "%1 = OpExtInstImport \"GLSL.std.450\"",
+                    "OpMemoryModel Logical GLSL450",
+                    "OpEntryPoint Vertex %main \"main\"",
+                    "OpName %void \"void\"",
+                    "OpName %main_func_type \"main_func_type\"",
+                    "OpName %main \"main\"",
+                    "OpName %main_func_entry_block \"main_func_entry_block\"",
+                    "OpName %bool \"bool\"",
+                    "OpName %uint \"uint\"",
+                    "OpName %int \"int\"",
+                    "OpName %float \"float\"",
+                    "OpName %double \"double\"",
+                    "OpName %v2int \"v2int\"",
+                    "OpName %spec_true \"spec_true\"",
+                    "OpName %spec_false \"spec_false\"",
+                    "OpName %spec_uint \"spec_uint\"",
+                    "OpName %spec_int \"spec_int\"",
+                    "OpName %spec_float \"spec_float\"",
+                    "OpName %spec_double \"spec_double\"",
+                    "OpName %spec_add_op \"spec_add_op\"",
+                    "OpName %spec_vec \"spec_vec\"",
+                    "OpName %spec_vec_x \"spec_vec_x\"",
+                    "OpDecorate %spec_true SpecId 200",
+                    "OpDecorate %spec_false SpecId 201",
+                    "OpDecorate %spec_uint SpecId 202",
+                    "OpDecorate %spec_int SpecId 203",
+                    "OpDecorate %spec_float SpecId 204",
+                    "OpDecorate %spec_double SpecId 205",
+            "%void = OpTypeVoid",
+  "%main_func_type = OpTypeFunction %void",
+            "%bool = OpTypeBool",
+            "%uint = OpTypeInt 32 0",
+             "%int = OpTypeInt 32 1",
+           "%float = OpTypeFloat 32",
+          "%double = OpTypeFloat 64",
+           "%v2int = OpTypeVector %int 2",
+       "%spec_true = OpSpecConstantTrue %bool",
+      "%spec_false = OpSpecConstantFalse %bool",
+       "%spec_uint = OpSpecConstant %uint 1",
+        "%spec_int = OpSpecConstant %int 1",
+      "%spec_float = OpSpecConstant %float 1.2",
+     "%spec_double = OpSpecConstant %double 1.23456789",
+     "%spec_add_op = OpSpecConstantOp %int IAdd %spec_int %spec_int",
+        "%spec_vec = OpSpecConstantComposite %v2int %spec_int %spec_int",
+      "%spec_vec_x = OpSpecConstantOp %int CompositeExtract %spec_vec 0",
+            "%main = OpFunction %void None %main_func_type",
+"%main_func_entry_block = OpLabel",
+                    "OpReturn",
+                    "OpFunctionEnd",
+
+      // clang-format on
+  };
+
+  SinglePassRunAndCheck<opt::NullPass>(builder.GetCode(),
+                                       JoinAllInsts(expected));
+}
+
+}  // anonymous namespace
diff --git a/test/opt/test_def_use.cpp b/test/opt/test_def_use.cpp
new file mode 100644
index 0000000..9bcc114
--- /dev/null
+++ b/test/opt/test_def_use.cpp
@@ -0,0 +1,1158 @@
+// Copyright (c) 2016 Google Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and/or associated documentation files (the
+// "Materials"), to deal in the Materials without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Materials, and to
+// permit persons to whom the Materials are furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Materials.
+//
+// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS
+// KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS
+// SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT
+//    https://www.khronos.org/registry/
+//
+// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+
+#include <memory>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "opt/def_use_manager.h"
+#include "opt/libspirv.hpp"
+#include "pass_utils.h"
+
+namespace {
+
+using ::testing::ElementsAre;
+
+using namespace spvtools;
+using spvtools::opt::analysis::DefUseManager;
+
+// Disassembles the given |inst| and returns the disassembly.
+std::string DisassembleInst(ir::Instruction* inst) {
+  SpvTools tools(SPV_ENV_UNIVERSAL_1_1);
+
+  std::vector<uint32_t> binary;
+  // We need this to generate the necessary header in the binary.
+  tools.Assemble("", &binary);
+  inst->ToBinary(&binary, /* skip_nop = */ false);
+
+  std::string text;
+  // We'll need to check the underlying id numbers.
+  // So turn off friendly names for ids.
+  tools.Disassemble(binary, &text, SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+  while (!text.empty() && text.back() == '\n') text.pop_back();
+  return text;
+}
+
+// A struct for holding expected id defs and uses.
+struct InstDefUse {
+  using IdInstPair = std::pair<uint32_t, const char*>;
+  using IdInstsPair = std::pair<uint32_t, std::vector<const char*>>;
+
+  // Ids and their corresponding def instructions.
+  std::vector<IdInstPair> defs;
+  // Ids and their corresponding use instructions.
+  std::vector<IdInstsPair> uses;
+};
+
+// Checks that the |actual_defs| and |actual_uses| are in accord with
+// |expected_defs_uses|.
+void CheckDef(const InstDefUse& expected_defs_uses,
+              const DefUseManager::IdToDefMap& actual_defs) {
+  // Check defs.
+  ASSERT_EQ(expected_defs_uses.defs.size(), actual_defs.size());
+  for (uint32_t i = 0; i < expected_defs_uses.defs.size(); ++i) {
+    const auto id = expected_defs_uses.defs[i].first;
+    const auto expected_def = expected_defs_uses.defs[i].second;
+    ASSERT_EQ(1u, actual_defs.count(id)) << "expected to def id [" << id << "]";
+    EXPECT_EQ(expected_def, DisassembleInst(actual_defs.at(id)));
+  }
+}
+
+void CheckUse(const InstDefUse& expected_defs_uses,
+              const DefUseManager::IdToUsesMap& actual_uses) {
+  // Check uses.
+  ASSERT_EQ(expected_defs_uses.uses.size(), actual_uses.size());
+  for (uint32_t i = 0; i < expected_defs_uses.uses.size(); ++i) {
+    const auto id = expected_defs_uses.uses[i].first;
+    const auto& expected_uses = expected_defs_uses.uses[i].second;
+
+    ASSERT_EQ(1u, actual_uses.count(id)) << "expected to use id [" << id << "]";
+    const auto& uses = actual_uses.at(id);
+
+    ASSERT_EQ(expected_uses.size(), uses.size())
+        << "id [" << id << "] # uses: expected: " << expected_uses.size()
+        << " actual: " << uses.size();
+    auto it = uses.cbegin();
+    for (const auto expected_use : expected_uses) {
+      EXPECT_EQ(expected_use, DisassembleInst(it->inst))
+          << "id [" << id << "] use instruction mismatch";
+      ++it;
+    }
+  }
+}
+
+// The following test case mimics how LLVM handles induction variables.
+// But, yeah, it's not very readable. However, we only care about the id
+// defs and uses. So, no need to make sure this is valid OpPhi construct.
+const char kOpPhiTestFunction[] =
+    " %2 = OpFunction %1 None %3 "
+    " %4 = OpLabel "
+    "      OpBranch %5 "
+
+    " %5 = OpLabel "
+    " %7 = OpPhi %6 %8 %4 %9 %5 "
+    "%11 = OpPhi %10 %12 %4 %13 %5 "
+    " %9 = OpIAdd %6 %7 %14 "
+    "%13 = OpFAdd %10 %11 %15 "
+    "%17 = OpSLessThan %16 %7 %18 "
+    "      OpLoopMerge %19 %5 None "
+    "      OpBranchConditional %17 %5 %19 "
+
+    "%19 = OpLabel "
+    "      OpReturn "
+    "      OpFunctionEnd";
+
+struct ParseDefUseCase {
+  const char* text;
+  InstDefUse du;
+};
+
+using ParseDefUseTest = ::testing::TestWithParam<ParseDefUseCase>;
+
+TEST_P(ParseDefUseTest, Case) {
+  const auto& tc = GetParam();
+
+  // Build module.
+  const std::vector<const char*> text = {tc.text};
+  std::unique_ptr<ir::Module> module =
+      SpvTools(SPV_ENV_UNIVERSAL_1_1).BuildModule(JoinAllInsts(text));
+  ASSERT_NE(nullptr, module);
+
+  // Analyze def and use.
+  opt::analysis::DefUseManager manager;
+  manager.AnalyzeDefUse(module.get());
+
+  CheckDef(tc.du, manager.id_to_defs());
+  CheckUse(tc.du, manager.id_to_uses());
+}
+
+// clang-format off
+INSTANTIATE_TEST_CASE_P(
+    TestCase, ParseDefUseTest,
+    ::testing::ValuesIn(std::vector<ParseDefUseCase>{
+        {"", {{}, {}}},                              // no instruction
+        {"OpMemoryModel Logical GLSL450", {{}, {}}}, // no def and use
+        { // single def, no use
+          "%1 = OpString \"wow\"",
+          {
+            {{1, "%1 = OpString \"wow\""}}, // defs
+            {}                              // uses
+          }
+        },
+        { // multiple def, no use
+          "%1 = OpString \"hello\" "
+          "%2 = OpString \"world\" "
+          "%3 = OpTypeVoid",
+          {
+            {  // defs
+              {1, "%1 = OpString \"hello\""},
+              {2, "%2 = OpString \"world\""},
+              {3, "%3 = OpTypeVoid"},
+            },
+            {} // uses
+          }
+        },
+        { // single use, no def
+          "OpTypeForwardPointer %1 Input",
+          {
+            {}, // defs
+            {   // uses
+              {1, {"OpTypeForwardPointer %1 Input"}},
+            }
+          }
+        },
+        { // multiple use, no def
+          "OpEntryPoint Fragment %1 \"main\" "
+          "OpTypeForwardPointer %2 Input "
+          "OpTypeForwardPointer %3 Output",
+          {
+            {}, // defs
+            {   // uses
+              {1, {"OpEntryPoint Fragment %1 \"main\""}},
+              {2, {"OpTypeForwardPointer %2 Input"}},
+              {3, {"OpTypeForwardPointer %3 Output"}},
+            }
+          }
+        },
+        { // multiple def, multiple use
+          "%1 = OpTypeBool "
+          "%2 = OpTypeVector %1 3 "
+          "%3 = OpTypeMatrix %2 3",
+          {
+            { // defs
+              {1, "%1 = OpTypeBool"},
+              {2, "%2 = OpTypeVector %1 3"},
+              {3, "%3 = OpTypeMatrix %2 3"},
+            },
+            { // uses
+              {1, {"%2 = OpTypeVector %1 3"}},
+              {2, {"%3 = OpTypeMatrix %2 3"}},
+            }
+          }
+        },
+        { // multiple use of the same id
+          "%1 = OpTypeBool "
+          "%2 = OpTypeVector %1 2 "
+          "%3 = OpTypeVector %1 3 "
+          "%4 = OpTypeVector %1 4",
+          {
+            { // defs
+              {1, "%1 = OpTypeBool"},
+              {2, "%2 = OpTypeVector %1 2"},
+              {3, "%3 = OpTypeVector %1 3"},
+              {4, "%4 = OpTypeVector %1 4"},
+            },
+            { // uses
+              {1,
+                {
+                  "%2 = OpTypeVector %1 2",
+                  "%3 = OpTypeVector %1 3",
+                  "%4 = OpTypeVector %1 4",
+                }
+              },
+            }
+          }
+        },
+        { // labels
+          "%2 = OpFunction %1 None %3 "
+
+          "%4 = OpLabel "
+          "OpBranchConditional %5 %6 %7 "
+
+          "%6 = OpLabel "
+          "OpBranch %7 "
+
+          "%7 = OpLabel "
+          "OpReturn "
+
+          "OpFunctionEnd",
+          {
+            { // defs
+              {2, "%2 = OpFunction %1 None %3"},
+              {4, "%4 = OpLabel"},
+              {6, "%6 = OpLabel"},
+              {7, "%7 = OpLabel"},
+            },
+            { // uses
+              {1, {"%2 = OpFunction %1 None %3"}},
+              {3, {"%2 = OpFunction %1 None %3"}},
+              {5, {"OpBranchConditional %5 %6 %7"}},
+              {6, {"OpBranchConditional %5 %6 %7"}},
+              {7,
+                {
+                  "OpBranchConditional %5 %6 %7",
+                  "OpBranch %7",
+                }
+              },
+            }
+          }
+        },
+        { // cross function
+          "%1 = OpTypeBool "
+
+          "%2 = OpFunction %1 None %3 "
+
+          "%4 = OpLabel "
+          "%5 = OpVariable %1 Function "
+          "%6 = OpFunctionCall %1 %2 %5 "
+          "OpReturnValue %6 "
+
+          "OpFunctionEnd",
+          {
+            { // defs
+              {1, "%1 = OpTypeBool"},
+              {2, "%2 = OpFunction %1 None %3"},
+              {4, "%4 = OpLabel"},
+              {5, "%5 = OpVariable %1 Function"},
+              {6, "%6 = OpFunctionCall %1 %2 %5"},
+            },
+            { // uses
+              {1,
+                {
+                  "%2 = OpFunction %1 None %3",
+                  "%5 = OpVariable %1 Function",
+                  "%6 = OpFunctionCall %1 %2 %5",
+                }
+              },
+              {2, {"%6 = OpFunctionCall %1 %2 %5"}},
+              {5, {"%6 = OpFunctionCall %1 %2 %5"}},
+              {3, {"%2 = OpFunction %1 None %3"}},
+              {6, {"OpReturnValue %6"}},
+            }
+          }
+        },
+        { // selection merge and loop merge
+          "%2 = OpFunction %1 None %3 "
+
+          "%4 = OpLabel "
+          "OpLoopMerge %5 %4 None "
+          "OpBranch %6 "
+
+          "%5 = OpLabel "
+          "OpReturn "
+
+          "%6 = OpLabel "
+          "OpSelectionMerge %7 None "
+          "OpBranchConditional %8 %9 %7 "
+
+          "%7 = OpLabel "
+          "OpReturn "
+
+          "%9 = OpLabel "
+          "OpReturn "
+
+          "OpFunctionEnd",
+          {
+            { // defs
+              {2, "%2 = OpFunction %1 None %3"},
+              {4, "%4 = OpLabel"},
+              {5, "%5 = OpLabel"},
+              {6, "%6 = OpLabel"},
+              {7, "%7 = OpLabel"},
+              {9, "%9 = OpLabel"},
+            },
+            { // uses
+              {1, {"%2 = OpFunction %1 None %3"}},
+              {3, {"%2 = OpFunction %1 None %3"}},
+              {4, {"OpLoopMerge %5 %4 None"}},
+              {5, {"OpLoopMerge %5 %4 None"}},
+              {6, {"OpBranch %6"}},
+              {7,
+                {
+                  "OpSelectionMerge %7 None",
+                  "OpBranchConditional %8 %9 %7",
+                }
+              },
+              {8, {"OpBranchConditional %8 %9 %7"}},
+              {9, {"OpBranchConditional %8 %9 %7"}},
+            }
+          }
+        },
+        { // Forward reference
+          "OpDecorate %1 Block "
+          "OpTypeForwardPointer %2 Input "
+          "%3 = OpTypeInt 32 0 "
+          "%1 = OpTypeStruct %3 "
+          "%2 = OpTypePointer Input %3",
+          {
+            { // defs
+              {1, "%1 = OpTypeStruct %3"},
+              {2, "%2 = OpTypePointer Input %3"},
+              {3, "%3 = OpTypeInt 32 0"},
+            },
+            { // uses
+              {1, {"OpDecorate %1 Block"}},
+              {2, {"OpTypeForwardPointer %2 Input"}},
+              {3,
+                {
+                  "%1 = OpTypeStruct %3",
+                  "%2 = OpTypePointer Input %3",
+                }
+              }
+            },
+          },
+        },
+        { // OpPhi
+          kOpPhiTestFunction,
+          {
+            { // defs
+              {2, "%2 = OpFunction %1 None %3"},
+              {4, "%4 = OpLabel"},
+              {5, "%5 = OpLabel"},
+              {7, "%7 = OpPhi %6 %8 %4 %9 %5"},
+              {9, "%9 = OpIAdd %6 %7 %14"},
+              {11, "%11 = OpPhi %10 %12 %4 %13 %5"},
+              {13, "%13 = OpFAdd %10 %11 %15"},
+              {17, "%17 = OpSLessThan %16 %7 %18"},
+              {19, "%19 = OpLabel"},
+            },
+            { // uses
+              {1, {"%2 = OpFunction %1 None %3"}},
+              {3, {"%2 = OpFunction %1 None %3"}},
+              {4,
+                {
+                  "%7 = OpPhi %6 %8 %4 %9 %5",
+                  "%11 = OpPhi %10 %12 %4 %13 %5",
+                }
+              },
+              {5,
+                {
+                  "OpBranch %5",
+                  "%7 = OpPhi %6 %8 %4 %9 %5",
+                  "%11 = OpPhi %10 %12 %4 %13 %5",
+                  "OpLoopMerge %19 %5 None",
+                  "OpBranchConditional %17 %5 %19",
+                }
+              },
+              {6,
+                {
+                  "%7 = OpPhi %6 %8 %4 %9 %5",
+                  "%9 = OpIAdd %6 %7 %14",
+                }
+              },
+              {7,
+                {
+                  "%9 = OpIAdd %6 %7 %14",
+                  "%17 = OpSLessThan %16 %7 %18",
+                }
+              },
+              {8, {"%7 = OpPhi %6 %8 %4 %9 %5"}},
+              {9, {"%7 = OpPhi %6 %8 %4 %9 %5"}},
+              {10,
+                {
+                  "%11 = OpPhi %10 %12 %4 %13 %5",
+                  "%13 = OpFAdd %10 %11 %15",
+                }
+              },
+              {11, {"%13 = OpFAdd %10 %11 %15"}},
+              {12, {"%11 = OpPhi %10 %12 %4 %13 %5"}},
+              {13, {"%11 = OpPhi %10 %12 %4 %13 %5"}},
+              {14, {"%9 = OpIAdd %6 %7 %14"}},
+              {15, {"%13 = OpFAdd %10 %11 %15"}},
+              {16, {"%17 = OpSLessThan %16 %7 %18"}},
+              {17, {"OpBranchConditional %17 %5 %19"}},
+              {18, {"%17 = OpSLessThan %16 %7 %18"}},
+              {19,
+                {
+                  "OpLoopMerge %19 %5 None",
+                  "OpBranchConditional %17 %5 %19",
+                }
+              },
+            },
+          },
+        },
+        { // OpPhi defining and referencing the same id.
+          "%1 = OpTypeBool "
+          "%2 = OpConstantTrue %1 "
+
+          "%4 = OpFunction %3 None %5 "
+          "%6 = OpLabel "
+          "     OpBranch %7 "
+          "%7 = OpLabel "
+          "%8 = OpPhi %1   %8 %7   %2 %6 " // both defines and uses %8
+          "     OpBranch %7 "
+          "     OpFunctionEnd",
+          {
+            { // defs
+              {1, "%1 = OpTypeBool"},
+              {2, "%2 = OpConstantTrue %1"},
+              {4, "%4 = OpFunction %3 None %5"},
+              {6, "%6 = OpLabel"},
+              {7, "%7 = OpLabel"},
+              {8, "%8 = OpPhi %1 %8 %7 %2 %6"},
+            },
+            { // uses
+              {1,
+                {
+                  "%2 = OpConstantTrue %1",
+                  "%8 = OpPhi %1 %8 %7 %2 %6",
+                }
+              },
+              {2, {"%8 = OpPhi %1 %8 %7 %2 %6"}},
+              {3, {"%4 = OpFunction %3 None %5"}},
+              {5, {"%4 = OpFunction %3 None %5"}},
+              {6, {"%8 = OpPhi %1 %8 %7 %2 %6"}},
+              {7,
+                {
+                  "OpBranch %7",
+                  "%8 = OpPhi %1 %8 %7 %2 %6",
+                  "OpBranch %7",
+                }
+              },
+              {8, {"%8 = OpPhi %1 %8 %7 %2 %6"}},
+            },
+          },
+        },
+    })
+);
+// clang-format on
+
+struct ReplaceUseCase {
+  const char* before;
+  std::vector<std::pair<uint32_t, uint32_t>> candidates;
+  const char* after;
+  InstDefUse du;
+};
+
+using ReplaceUseTest = ::testing::TestWithParam<ReplaceUseCase>;
+
+// Disassembles the given |module| and returns the disassembly.
+std::string DisassembleModule(ir::Module* module) {
+  SpvTools tools(SPV_ENV_UNIVERSAL_1_1);
+
+  std::vector<uint32_t> binary;
+  module->ToBinary(&binary, /* skip_nop = */ false);
+
+  std::string text;
+  // We'll need to check the underlying id numbers.
+  // So turn off friendly names for ids.
+  tools.Disassemble(binary, &text, SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+  while (!text.empty() && text.back() == '\n') text.pop_back();
+  return text;
+}
+
+TEST_P(ReplaceUseTest, Case) {
+  const auto& tc = GetParam();
+
+  // Build module.
+  const std::vector<const char*> text = {tc.before};
+  std::unique_ptr<ir::Module> module =
+      SpvTools(SPV_ENV_UNIVERSAL_1_1).BuildModule(JoinAllInsts(text));
+  ASSERT_NE(nullptr, module);
+
+  // Analyze def and use.
+  opt::analysis::DefUseManager manager;
+  manager.AnalyzeDefUse(module.get());
+
+  // Do the substitution.
+  for (const auto& candiate : tc.candidates) {
+    manager.ReplaceAllUsesWith(candiate.first, candiate.second);
+  }
+
+  EXPECT_EQ(tc.after, DisassembleModule(module.get()));
+  CheckDef(tc.du, manager.id_to_defs());
+  CheckUse(tc.du, manager.id_to_uses());
+}
+
+// clang-format off
+INSTANTIATE_TEST_CASE_P(
+    TestCase, ReplaceUseTest,
+    ::testing::ValuesIn(std::vector<ReplaceUseCase>{
+      { // no use, no replace request
+        "", {}, "", {},
+      },
+      { // no use, some replace requests
+        "OpMemoryModel Logical GLSL450",
+        {{1, 2}, {3, 4}, {7, 8}, {7, 9}, {7, 10}, {2, 10}, {3, 10}},
+        "OpMemoryModel Logical GLSL450",
+        {},
+      },
+      { // replace one use
+        "%1 = OpTypeBool "
+        "%2 = OpTypeVector %1 3",
+        {{1, 3}},
+        "%1 = OpTypeBool\n"
+        "%2 = OpTypeVector %3 3",
+        {
+          { // defs
+            {1, "%1 = OpTypeBool"},
+            {2, "%2 = OpTypeVector %3 3"},
+          },
+          { // uses
+            {3, {"%2 = OpTypeVector %3 3"}},
+          },
+        },
+      },
+      { // replace and then replace back
+        "%1 = OpTypeBool "
+        "%2 = OpTypeVector %1 3",
+        {{1, 3}, {3, 1}},
+        "%1 = OpTypeBool\n"
+        "%2 = OpTypeVector %1 3",
+        {
+          { // defs
+            {1, "%1 = OpTypeBool"},
+            {2, "%2 = OpTypeVector %1 3"},
+          },
+          { // uses
+            {1, {"%2 = OpTypeVector %1 3"}},
+          },
+        },
+      },
+      { // replace with the same id
+        "%1 = OpTypeBool "
+        "%2 = OpTypeVector %1 3",
+        {{1, 1}, {2, 2}, {3, 3}},
+        "%1 = OpTypeBool\n"
+        "%2 = OpTypeVector %1 3",
+        {
+          { // defs
+            {1, "%1 = OpTypeBool"},
+            {2, "%2 = OpTypeVector %1 3"},
+          },
+          { // uses
+            {1, {"%2 = OpTypeVector %1 3"}},
+          },
+        },
+      },
+      { // replace in sequence
+        "%1 = OpTypeBool "
+        "%2 = OpTypeVector %1 3",
+        {{1, 3}, {3, 4}, {4, 5}, {5, 100}},
+        "%1 = OpTypeBool\n"
+        "%2 = OpTypeVector %100 3",
+        {
+          { // defs
+            {1, "%1 = OpTypeBool"},
+            {2, "%2 = OpTypeVector %100 3"},
+          },
+          { // uses
+            {100, {"%2 = OpTypeVector %100 3"}},
+          },
+        },
+      },
+      { // replace multiple uses
+        "%1 = OpTypeBool "
+        "%2 = OpTypeVector %1 2 "
+        "%3 = OpTypeVector %1 3 "
+        "%4 = OpTypeVector %1 4 "
+        "%5 = OpTypeMatrix %2 2 "
+        "%6 = OpTypeMatrix %3 3 "
+        "%7 = OpTypeMatrix %4 4",
+        {{1, 10}, {2, 20}, {4, 40}},
+        "%1 = OpTypeBool\n"
+        "%2 = OpTypeVector %10 2\n"
+        "%3 = OpTypeVector %10 3\n"
+        "%4 = OpTypeVector %10 4\n"
+        "%5 = OpTypeMatrix %20 2\n"
+        "%6 = OpTypeMatrix %3 3\n"
+        "%7 = OpTypeMatrix %40 4",
+        {
+          { // defs
+            {1, "%1 = OpTypeBool"},
+            {2, "%2 = OpTypeVector %10 2"},
+            {3, "%3 = OpTypeVector %10 3"},
+            {4, "%4 = OpTypeVector %10 4"},
+            {5, "%5 = OpTypeMatrix %20 2"},
+            {6, "%6 = OpTypeMatrix %3 3"},
+            {7, "%7 = OpTypeMatrix %40 4"},
+          },
+          { // uses
+            {10,
+              {
+                "%2 = OpTypeVector %10 2",
+                "%3 = OpTypeVector %10 3",
+                "%4 = OpTypeVector %10 4",
+              }
+            },
+            {20, {"%5 = OpTypeMatrix %20 2"}},
+            {3, {"%6 = OpTypeMatrix %3 3"}},
+            {40, {"%7 = OpTypeMatrix %40 4"}},
+          },
+        },
+      },
+      { // OpPhi.
+        kOpPhiTestFunction,
+        // replace one id used by OpPhi, replace one id generated by OpPhi
+        {{9, 9000}, {11, 9}},
+         "%2 = OpFunction %1 None %3\n"
+         "%4 = OpLabel\n"
+               "OpBranch %5\n"
+
+         "%5 = OpLabel\n"
+         "%7 = OpPhi %6 %8 %4 %9000 %5\n" // %9 -> %9000
+        "%11 = OpPhi %10 %12 %4 %13 %5\n"
+         "%9 = OpIAdd %6 %7 %14\n"
+        "%13 = OpFAdd %10 %9 %15\n"       // %11 -> %9
+        "%17 = OpSLessThan %16 %7 %18\n"
+              "OpLoopMerge %19 %5 None\n"
+              "OpBranchConditional %17 %5 %19\n"
+
+        "%19 = OpLabel\n"
+              "OpReturn\n"
+              "OpFunctionEnd",
+        {
+          { // defs.
+            {2, "%2 = OpFunction %1 None %3"},
+            {4, "%4 = OpLabel"},
+            {5, "%5 = OpLabel"},
+            {7, "%7 = OpPhi %6 %8 %4 %9000 %5"},
+            {9, "%9 = OpIAdd %6 %7 %14"},
+            {11, "%11 = OpPhi %10 %12 %4 %13 %5"},
+            {13, "%13 = OpFAdd %10 %9 %15"},
+            {17, "%17 = OpSLessThan %16 %7 %18"},
+            {19, "%19 = OpLabel"},
+          },
+          { // uses
+            {1, {"%2 = OpFunction %1 None %3"}},
+            {3, {"%2 = OpFunction %1 None %3"}},
+            {4,
+              {
+                "%7 = OpPhi %6 %8 %4 %9000 %5",
+                "%11 = OpPhi %10 %12 %4 %13 %5",
+              }
+            },
+            {5,
+              {
+                "OpBranch %5",
+                "%7 = OpPhi %6 %8 %4 %9000 %5",
+                "%11 = OpPhi %10 %12 %4 %13 %5",
+                "OpLoopMerge %19 %5 None",
+                "OpBranchConditional %17 %5 %19",
+              }
+            },
+            {6,
+              {
+                "%7 = OpPhi %6 %8 %4 %9000 %5",
+                "%9 = OpIAdd %6 %7 %14",
+              }
+            },
+            {7,
+              {
+                "%9 = OpIAdd %6 %7 %14",
+                "%17 = OpSLessThan %16 %7 %18",
+              }
+            },
+            {8, {"%7 = OpPhi %6 %8 %4 %9000 %5"}},
+            {9, {"%13 = OpFAdd %10 %9 %15"}}, // uses of %9 changed from %7 to %13
+            {10,
+              {
+                "%11 = OpPhi %10 %12 %4 %13 %5",
+                "%13 = OpFAdd %10 %9 %15",
+              }
+            },
+            // no more uses of %11
+            {12, {"%11 = OpPhi %10 %12 %4 %13 %5"}},
+            {13, {"%11 = OpPhi %10 %12 %4 %13 %5"}},
+            {14, {"%9 = OpIAdd %6 %7 %14"}},
+            {15, {"%13 = OpFAdd %10 %9 %15"}},
+            {16, {"%17 = OpSLessThan %16 %7 %18"}},
+            {17, {"OpBranchConditional %17 %5 %19"}},
+            {18, {"%17 = OpSLessThan %16 %7 %18"}},
+            {19,
+              {
+                "OpLoopMerge %19 %5 None",
+                "OpBranchConditional %17 %5 %19",
+              }
+            },
+            // new uses of %9000
+            {9000, {"%7 = OpPhi %6 %8 %4 %9000 %5"}},
+          },
+        },
+      },
+      { // OpPhi defining and referencing the same id.
+        "%1 = OpTypeBool "
+        "%2 = OpConstantTrue %1 "
+
+        "%4 = OpFunction %3 None %5 "
+        "%6 = OpLabel "
+        "     OpBranch %7 "
+        "%7 = OpLabel "
+        "%8 = OpPhi %1   %8 %7   %2 %6 " // both defines and uses %8
+        "     OpBranch %7 "
+        "     OpFunctionEnd",
+        {{8, 2}},
+        "%1 = OpTypeBool\n"
+        "%2 = OpConstantTrue %1\n"
+
+        "%4 = OpFunction %3 None %5\n"
+        "%6 = OpLabel\n"
+             "OpBranch %7\n"
+        "%7 = OpLabel\n"
+        "%8 = OpPhi %1 %2 %7 %2 %6\n" // use of %8 changed to %2
+             "OpBranch %7\n"
+             "OpFunctionEnd",
+        {
+          { // defs
+            {1, "%1 = OpTypeBool"},
+            {2, "%2 = OpConstantTrue %1"},
+            {4, "%4 = OpFunction %3 None %5"},
+            {6, "%6 = OpLabel"},
+            {7, "%7 = OpLabel"},
+            {8, "%8 = OpPhi %1 %2 %7 %2 %6"},
+          },
+          { // uses
+            {1,
+              {
+                "%2 = OpConstantTrue %1",
+                "%8 = OpPhi %1 %2 %7 %2 %6",
+              }
+            },
+            {2,
+              {
+                // TODO(antiagainst): address this.
+                // We have duplication here because we didn't check existence
+                // before inserting uses.
+                "%8 = OpPhi %1 %2 %7 %2 %6",
+                "%8 = OpPhi %1 %2 %7 %2 %6",
+              }
+            },
+            {3, {"%4 = OpFunction %3 None %5"}},
+            {5, {"%4 = OpFunction %3 None %5"}},
+            {6, {"%8 = OpPhi %1 %2 %7 %2 %6"}},
+            {7,
+              {
+                "OpBranch %7",
+                "%8 = OpPhi %1 %2 %7 %2 %6",
+                "OpBranch %7",
+              }
+            },
+            // {8, {"%8 = OpPhi %1 %8 %7 %2 %6"}},
+          },
+        },
+      },
+    })
+);
+// clang-format on
+
+struct KillDefCase {
+  const char* before;
+  std::vector<uint32_t> ids_to_kill;
+  const char* after;
+  InstDefUse du;
+};
+
+using KillDefTest = ::testing::TestWithParam<KillDefCase>;
+
+TEST_P(KillDefTest, Case) {
+  const auto& tc = GetParam();
+
+  // Build module.
+  const std::vector<const char*> text = {tc.before};
+  std::unique_ptr<ir::Module> module =
+      SpvTools(SPV_ENV_UNIVERSAL_1_1).BuildModule(JoinAllInsts(text));
+  ASSERT_NE(nullptr, module);
+
+  // Analyze def and use.
+  opt::analysis::DefUseManager manager;
+  manager.AnalyzeDefUse(module.get());
+
+  // Do the substitution.
+  for (const auto id : tc.ids_to_kill) manager.KillDef(id);
+
+  EXPECT_EQ(tc.after, DisassembleModule(module.get()));
+  CheckDef(tc.du, manager.id_to_defs());
+  CheckUse(tc.du, manager.id_to_uses());
+}
+
+// clang-format off
+INSTANTIATE_TEST_CASE_P(
+    TestCase, KillDefTest,
+    ::testing::ValuesIn(std::vector<KillDefCase>{
+      { // no def, no use, no kill
+        "", {}, "", {}
+      },
+      { // kill nothing
+        "%1 = OpTypeBool "
+        "%2 = OpTypeVector %1 2 "
+        "%3 = OpTypeVector %1 3 ",
+        {},
+        "%1 = OpTypeBool\n"
+        "%2 = OpTypeVector %1 2\n"
+        "%3 = OpTypeVector %1 3",
+        {
+          { // defs
+            {1, "%1 = OpTypeBool"},
+            {2, "%2 = OpTypeVector %1 2"},
+            {3, "%3 = OpTypeVector %1 3"},
+          },
+          { // uses
+            {1,
+              {
+                "%2 = OpTypeVector %1 2",
+                "%3 = OpTypeVector %1 3",
+              }
+            },
+          },
+        },
+      },
+      { // kill id used, kill id not used, kill id not defined
+        "%1 = OpTypeBool "
+        "%2 = OpTypeVector %1 2 "
+        "%3 = OpTypeVector %1 3 "
+        "%4 = OpTypeVector %1 4 "
+        "%5 = OpTypeMatrix %3 3 "
+        "%6 = OpTypeMatrix %2 3",
+        {1, 3, 5, 10}, // ids to kill
+        "OpNop\n"
+        "%2 = OpTypeVector %1 2\n"
+        "OpNop\n"
+        "%4 = OpTypeVector %1 4\n"
+        "OpNop\n"
+        "%6 = OpTypeMatrix %2 3",
+        {
+          { // defs
+            {2, "%2 = OpTypeVector %1 2"},
+            {4, "%4 = OpTypeVector %1 4"},
+            {6, "%6 = OpTypeMatrix %2 3"},
+          },
+          { // uses. %1 and %3 are both killed, so no uses
+            // recorded for them anymore.
+            {2, {"%6 = OpTypeMatrix %2 3"}},
+          }
+        },
+      },
+      { // OpPhi.
+        kOpPhiTestFunction,
+        {9, 11}, // kill one id used by OpPhi, kill one id generated by OpPhi
+         "%2 = OpFunction %1 None %3\n"
+         "%4 = OpLabel\n"
+               "OpBranch %5\n"
+
+         "%5 = OpLabel\n"
+         "%7 = OpPhi %6 %8 %4 %9 %5\n"
+              "OpNop\n"
+              "OpNop\n"
+        "%13 = OpFAdd %10 %11 %15\n"
+        "%17 = OpSLessThan %16 %7 %18\n"
+              "OpLoopMerge %19 %5 None\n"
+              "OpBranchConditional %17 %5 %19\n"
+
+        "%19 = OpLabel\n"
+              "OpReturn\n"
+              "OpFunctionEnd",
+        {
+          { // defs. %9 & %11 are killed.
+            {2, "%2 = OpFunction %1 None %3"},
+            {4, "%4 = OpLabel"},
+            {5, "%5 = OpLabel"},
+            {7, "%7 = OpPhi %6 %8 %4 %9 %5"},
+            {13, "%13 = OpFAdd %10 %11 %15"},
+            {17, "%17 = OpSLessThan %16 %7 %18"},
+            {19, "%19 = OpLabel"},
+          },
+          { // uses
+            {1, {"%2 = OpFunction %1 None %3"}},
+            {3, {"%2 = OpFunction %1 None %3"}},
+            {4,
+              {
+                "%7 = OpPhi %6 %8 %4 %9 %5",
+                // "%11 = OpPhi %10 %12 %4 %13 %5",
+              }
+            },
+            {5,
+              {
+                "OpBranch %5",
+                "%7 = OpPhi %6 %8 %4 %9 %5",
+                // "%11 = OpPhi %10 %12 %4 %13 %5",
+                "OpLoopMerge %19 %5 None",
+                "OpBranchConditional %17 %5 %19",
+              }
+            },
+            {6,
+              {
+                "%7 = OpPhi %6 %8 %4 %9 %5",
+                // "%9 = OpIAdd %6 %7 %14",
+              }
+            },
+            {7,
+              {
+                // "%9 = OpIAdd %6 %7 %14",
+                "%17 = OpSLessThan %16 %7 %18",
+              }
+            },
+            {8, {"%7 = OpPhi %6 %8 %4 %9 %5"}},
+            // {9, {"%7 = OpPhi %6 %8 %4 %9 %5"}},
+            {10,
+              {
+                // "%11 = OpPhi %10 %12 %4 %13 %5",
+                "%13 = OpFAdd %10 %11 %15",
+              }
+            },
+            // {11, {"%13 = OpFAdd %10 %11 %15"}},
+            // {12, {"%11 = OpPhi %10 %12 %4 %13 %5"}},
+            // {13, {"%11 = OpPhi %10 %12 %4 %13 %5"}},
+            // {14, {"%9 = OpIAdd %6 %7 %14"}},
+            {15, {"%13 = OpFAdd %10 %11 %15"}},
+            {16, {"%17 = OpSLessThan %16 %7 %18"}},
+            {17, {"OpBranchConditional %17 %5 %19"}},
+            {18, {"%17 = OpSLessThan %16 %7 %18"}},
+            {19,
+              {
+                "OpLoopMerge %19 %5 None",
+                "OpBranchConditional %17 %5 %19",
+              }
+            },
+          },
+        },
+      },
+      { // OpPhi defining and referencing the same id.
+        "%1 = OpTypeBool "
+        "%2 = OpConstantTrue %1 "
+
+        "%4 = OpFunction %3 None %5 "
+        "%6 = OpLabel "
+        "     OpBranch %7 "
+        "%7 = OpLabel "
+        "%8 = OpPhi %1   %8 %7   %2 %6 " // both defines and uses %8
+        "     OpBranch %7 "
+        "     OpFunctionEnd",
+        {8},
+        "%1 = OpTypeBool\n"
+        "%2 = OpConstantTrue %1\n"
+
+        "%4 = OpFunction %3 None %5\n"
+        "%6 = OpLabel\n"
+             "OpBranch %7\n"
+        "%7 = OpLabel\n"
+             "OpNop\n"
+             "OpBranch %7\n"
+             "OpFunctionEnd",
+        {
+          { // defs
+            {1, "%1 = OpTypeBool"},
+            {2, "%2 = OpConstantTrue %1"},
+            {4, "%4 = OpFunction %3 None %5"},
+            {6, "%6 = OpLabel"},
+            {7, "%7 = OpLabel"},
+            // {8, "%8 = OpPhi %1 %8 %7 %2 %6"},
+          },
+          { // uses
+            {1,
+              {
+                "%2 = OpConstantTrue %1",
+                // "%8 = OpPhi %1 %8 %7 %2 %6",
+              }
+            },
+            // {2, {"%8 = OpPhi %1 %8 %7 %2 %6"}},
+            {3, {"%4 = OpFunction %3 None %5"}},
+            {5, {"%4 = OpFunction %3 None %5"}},
+            // {6, {"%8 = OpPhi %1 %8 %7 %2 %6"}},
+            {7,
+              {
+                "OpBranch %7",
+                // "%8 = OpPhi %1 %8 %7 %2 %6",
+                "OpBranch %7",
+              }
+            },
+            // {8, {"%8 = OpPhi %1 %8 %7 %2 %6"}},
+          },
+        },
+      },
+    })
+);
+// clang-format on
+//
+TEST(DefUseTest, OpSwitch) {
+  // Because disassembler has basic type check for OpSwitch's selector, we
+  // cannot use the DisassembleInst() in the above. Thus, this special spotcheck
+  // test case.
+
+  const char original_text[] =
+      // int64 f(int64 v) {
+      //   switch (v) {
+      //     case 1:                   break;
+      //     case -4294967296:         break;
+      //     case 9223372036854775807: break;
+      //     default:                  break;
+      //   }
+      //   return v;
+      // }
+      " %1 = OpTypeInt 64 1 "
+      " %2 = OpFunction %1 None %3 "  // %3 is int64(int64)*
+      " %4 = OpFunctionParameter %1 "
+      " %5 = OpLabel "
+      " %6 = OpLoad %1 %4 "  // selector value
+      "      OpSelectionMerge %7 None "
+      "      OpSwitch %6 %8 "
+      "                  1                    %9 "  // 1
+      "                  -4294967296         %10 "  // -2^32
+      "                  9223372036854775807 %11 "  // 2^63-1
+      " %8 = OpLabel "                              // default
+      "      OpBranch %7 "
+      " %9 = OpLabel "
+      "      OpBranch %7 "
+      "%10 = OpLabel "
+      "      OpBranch %7 "
+      "%11 = OpLabel "
+      "      OpBranch %7 "
+      " %7 = OpLabel "
+      "      OpReturnValue %6 "
+      "      OpFunctionEnd";
+
+  std::unique_ptr<ir::Module> module =
+      SpvTools(SPV_ENV_UNIVERSAL_1_1).BuildModule(original_text);
+  ASSERT_NE(nullptr, module);
+
+  // Analyze def and use.
+  opt::analysis::DefUseManager manager;
+  manager.AnalyzeDefUse(module.get());
+
+  // Do a bunch replacements.
+  manager.ReplaceAllUsesWith(9, 900);    // to unused id
+  manager.ReplaceAllUsesWith(10, 1000);  // to unused id
+  manager.ReplaceAllUsesWith(11, 7);     // to existing id
+
+  // clang-format off
+  const char modified_text[] =
+       "%1 = OpTypeInt 64 1\n"
+       "%2 = OpFunction %1 None %3\n" // %3 is int64(int64)*
+       "%4 = OpFunctionParameter %1\n"
+       "%5 = OpLabel\n"
+       "%6 = OpLoad %1 %4\n" // selector value
+            "OpSelectionMerge %7 None\n"
+            "OpSwitch %6 %8 1 %900 -4294967296 %1000 9223372036854775807 %7\n" // changed!
+       "%8 = OpLabel\n"      // default
+            "OpBranch %7\n"
+       "%9 = OpLabel\n"
+            "OpBranch %7\n"
+      "%10 = OpLabel\n"
+            "OpBranch %7\n"
+      "%11 = OpLabel\n"
+            "OpBranch %7\n"
+       "%7 = OpLabel\n"
+            "OpReturnValue %6\n"
+            "OpFunctionEnd";
+  // clang-format on
+
+  EXPECT_EQ(modified_text, DisassembleModule(module.get()));
+
+  InstDefUse def_uses = {};
+  def_uses.defs = {
+      {1, "%1 = OpTypeInt 64 1"},
+      {2, "%2 = OpFunction %1 None %3"},
+      {4, "%4 = OpFunctionParameter %1"},
+      {5, "%5 = OpLabel"},
+      {6, "%6 = OpLoad %1 %4"},
+      {7, "%7 = OpLabel"},
+      {8, "%8 = OpLabel"},
+      {9, "%9 = OpLabel"},
+      {10, "%10 = OpLabel"},
+      {11, "%11 = OpLabel"},
+  };
+  CheckDef(def_uses, manager.id_to_defs());
+
+  {
+    auto* use_list = manager.GetUses(6);
+    ASSERT_NE(nullptr, use_list);
+    EXPECT_EQ(2u, use_list->size());
+    EXPECT_EQ(SpvOpSwitch, use_list->front().inst->opcode());
+    EXPECT_EQ(SpvOpReturnValue, use_list->back().inst->opcode());
+  }
+  {
+    auto* use_list = manager.GetUses(7);
+    ASSERT_NE(nullptr, use_list);
+    EXPECT_EQ(6u, use_list->size());
+    std::vector<SpvOp> opcodes;
+    for (const auto& use : *use_list) {
+      opcodes.push_back(use.inst->opcode());
+    }
+    // OpSwitch is now a user of %7.
+    EXPECT_THAT(opcodes,
+                ElementsAre(SpvOpSelectionMerge, SpvOpBranch, SpvOpBranch,
+                            SpvOpBranch, SpvOpBranch, SpvOpSwitch));
+  }
+  // Check all ids only used by OpSwitch after replacement.
+  for (const auto id : {8, 900, 1000}) {
+    auto* use_list = manager.GetUses(id);
+    ASSERT_NE(nullptr, use_list);
+    EXPECT_EQ(1u, use_list->size());
+    EXPECT_EQ(SpvOpSwitch, use_list->front().inst->opcode());
+  }
+}
+
+}  // anonymous namespace
diff --git a/test/opt/test_freeze_spec_const.cpp b/test/opt/test_freeze_spec_const.cpp
new file mode 100644
index 0000000..2be826b
--- /dev/null
+++ b/test/opt/test_freeze_spec_const.cpp
@@ -0,0 +1,136 @@
+// Copyright (c) 2016 Google Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and/or associated documentation files (the
+// "Materials"), to deal in the Materials without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Materials, and to
+// permit persons to whom the Materials are furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Materials.
+//
+// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS
+// KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS
+// SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT
+//    https://www.khronos.org/registry/
+//
+// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+
+#include "pass_fixture.h"
+#include "pass_utils.h"
+
+#include <algorithm>
+#include <tuple>
+#include <vector>
+
+namespace {
+
+using namespace spvtools;
+
+struct FreezeSpecConstantValueTypeTestCase {
+  const char* type_decl;
+  const char* spec_const;
+  const char* expected_frozen_const;
+};
+
+using FreezeSpecConstantValueTypeTest =
+    PassTest<::testing::TestWithParam<FreezeSpecConstantValueTypeTestCase>>;
+
+TEST_P(FreezeSpecConstantValueTypeTest, PrimaryType) {
+  auto& test_case = GetParam();
+  std::vector<const char*> text = {"OpCapability Shader",
+                                   "OpMemoryModel Logical GLSL450",
+                                   test_case.type_decl, test_case.spec_const};
+  std::vector<const char*> expected = {
+      "OpCapability Shader", "OpMemoryModel Logical GLSL450",
+      test_case.type_decl, test_case.expected_frozen_const};
+  SinglePassRunAndCheck<opt::FreezeSpecConstantValuePass>(
+      JoinAllInsts(text), JoinAllInsts(expected));
+}
+
+// Test each primary type.
+INSTANTIATE_TEST_CASE_P(
+    PrimaryTypeSpecConst, FreezeSpecConstantValueTypeTest,
+    ::testing::ValuesIn(std::vector<FreezeSpecConstantValueTypeTestCase>({
+        // Type declaration, original spec constant definition, expected frozen
+        // spec constants.
+        {"%int = OpTypeInt 32 1", "%2 = OpSpecConstant %int 1",
+         "%2 = OpConstant %int 1"},
+        {"%uint = OpTypeInt 32 0", "%2 = OpSpecConstant %uint 1",
+         "%2 = OpConstant %uint 1"},
+        {"%float = OpTypeFloat 32", "%2 = OpSpecConstant %float 3.14",
+         "%2 = OpConstant %float 3.14"},
+        {"%double = OpTypeFloat 64", "%2 = OpSpecConstant %double 3.1415926",
+         "%2 = OpConstant %double 3.1415926"},
+        {"%bool = OpTypeBool", "%2 = OpSpecConstantTrue %bool",
+         "%2 = OpConstantTrue %bool"},
+        {"%bool = OpTypeBool", "%2 = OpSpecConstantFalse %bool",
+         "%2 = OpConstantFalse %bool"},
+    })));
+
+using FreezeSpecConstantValueRemoveDecorationTest = PassTest<::testing::Test>;
+
+TEST_F(FreezeSpecConstantValueRemoveDecorationTest,
+       RemoveDecorationInstWithSpecId) {
+  std::vector<const char*> text = {
+      // clang-format off
+               "OpCapability Shader",
+               "OpCapability Float64",
+          "%1 = OpExtInstImport \"GLSL.std.450\"",
+               "OpMemoryModel Logical GLSL450",
+               "OpEntryPoint Vertex %main \"main\"",
+               "OpSource GLSL 450",
+               "OpSourceExtension \"GL_GOOGLE_cpp_style_line_directive\"",
+               "OpSourceExtension \"GL_GOOGLE_include_directive\"",
+               "OpName %main \"main\"",
+               "OpDecorate %3 SpecId 200",
+               "OpDecorate %4 SpecId 201",
+               "OpDecorate %5 SpecId 202",
+               "OpDecorate %6 SpecId 203",
+       "%void = OpTypeVoid",
+          "%8 = OpTypeFunction %void",
+        "%int = OpTypeInt 32 1",
+          "%3 = OpSpecConstant %int 3",
+      "%float = OpTypeFloat 32",
+          "%4 = OpSpecConstant %float 3.14",
+     "%double = OpTypeFloat 64",
+          "%5 = OpSpecConstant %double 3.14159265358979",
+       "%bool = OpTypeBool",
+          "%6 = OpSpecConstantTrue %bool",
+          "%13 = OpSpecConstantFalse %bool",
+       "%main = OpFunction %void None %8",
+         "%14 = OpLabel",
+               "OpReturn",
+               "OpFunctionEnd",
+      // clang-format on
+  };
+  std::string expected_disassembly = SelectiveJoin(text, [](const char* line) {
+    return std::string(line).find("SpecId") != std::string::npos;
+  });
+  std::vector<std::pair<const char*, const char*>> opcode_replacement_pairs = {
+      {" OpSpecConstant ", " OpConstant "},
+      {" OpSpecConstantTrue ", " OpConstantTrue "},
+      {" OpSpecConstantFalse ", " OpConstantFalse "},
+  };
+  for (auto& p : opcode_replacement_pairs) {
+    EXPECT_TRUE(FindAndReplace(&expected_disassembly, p.first, p.second))
+        << "text:\n"
+        << expected_disassembly << "\n"
+        << "find_str:\n"
+        << p.first << "\n"
+        << "replace_str:\n"
+        << p.second << "\n";
+  }
+  SinglePassRunAndCheck<opt::FreezeSpecConstantValuePass>(
+      JoinAllInsts(text), expected_disassembly,
+      /* skip_nop = */ true);
+}
+}  // anonymous namespace
diff --git a/test/opt/test_ir_loader.cpp b/test/opt/test_ir_loader.cpp
index bb788f5..c5ccd5e 100644
--- a/test/opt/test_ir_loader.cpp
+++ b/test/opt/test_ir_loader.cpp
@@ -37,44 +37,46 @@
   // int add(int a, int b) { return a + b; }
   // void main() { add(1, 2); }
   const std::string text =
-      "OpCapability Shader\n"
-      "%1 = OpExtInstImport \"GLSL.std.450\"\n"
-      "OpMemoryModel Logical GLSL450\n"
-      "OpEntryPoint Vertex %2 \"main\"\n"
-      "OpSource ESSL 310\n"
-      "OpSourceExtension \"GL_GOOGLE_cpp_style_line_directive\"\n"
-      "OpSourceExtension \"GL_GOOGLE_include_directive\"\n"
-      "OpName %2 \"main\"\n"
-      "OpName %3 \"add(i1;i1;\"\n"
-      "OpName %4 \"a\"\n"
-      "OpName %5 \"b\"\n"
-      "OpName %6 \"param\"\n"
-      "OpName %7 \"param\"\n"
-      "%8 = OpTypeVoid\n"
-      "%9 = OpTypeFunction %8\n"
-      "%10 = OpTypeInt 32 1\n"
-      "%11 = OpTypePointer Function %10\n"
-      "%12 = OpTypeFunction %10 %11 %11\n"
-      "%13 = OpConstant %10 1\n"
-      "%14 = OpConstant %10 2\n"
-      "%2 = OpFunction %8 None %9\n"
-      "%15 = OpLabel\n"
-      "%6 = OpVariable %11 Function\n"
-      "%7 = OpVariable %11 Function\n"
-      "OpStore %6 %13\n"
-      "OpStore %7 %14\n"
-      "%16 = OpFunctionCall %10 %3 %6 %7\n"
-      "OpReturn\n"
-      "OpFunctionEnd\n"
-      "%3 = OpFunction %10 None %12\n"
-      "%4 = OpFunctionParameter %11\n"
-      "%5 = OpFunctionParameter %11\n"
-      "%17 = OpLabel\n"
-      "%18 = OpLoad %10 %4\n"
-      "%19 = OpLoad %10 %5\n"
-      "%20 = OpIAdd %10 %18 %19\n"
-      "OpReturnValue %20\n"
-      "OpFunctionEnd\n";
+      // clang-format off
+               "OpCapability Shader\n"
+          "%1 = OpExtInstImport \"GLSL.std.450\"\n"
+               "OpMemoryModel Logical GLSL450\n"
+               "OpEntryPoint Vertex %main \"main\"\n"
+               "OpSource ESSL 310\n"
+               "OpSourceExtension \"GL_GOOGLE_cpp_style_line_directive\"\n"
+               "OpSourceExtension \"GL_GOOGLE_include_directive\"\n"
+               "OpName %main \"main\"\n"
+               "OpName %add_i1_i1_ \"add(i1;i1;\"\n"
+               "OpName %a \"a\"\n"
+               "OpName %b \"b\"\n"
+               "OpName %param \"param\"\n"
+               "OpName %param_0 \"param\"\n"
+       "%void = OpTypeVoid\n"
+          "%9 = OpTypeFunction %void\n"
+        "%int = OpTypeInt 32 1\n"
+ "%_ptr_Function_int = OpTypePointer Function %int\n"
+         "%12 = OpTypeFunction %int %_ptr_Function_int %_ptr_Function_int\n"
+         "%13 = OpConstant %int 1\n"
+         "%14 = OpConstant %int 2\n"
+       "%main = OpFunction %void None %9\n"
+         "%15 = OpLabel\n"
+      "%param = OpVariable %_ptr_Function_int Function\n"
+    "%param_0 = OpVariable %_ptr_Function_int Function\n"
+               "OpStore %param %13\n"
+               "OpStore %param_0 %14\n"
+         "%16 = OpFunctionCall %int %add_i1_i1_ %param %param_0\n"
+               "OpReturn\n"
+               "OpFunctionEnd\n"
+ "%add_i1_i1_ = OpFunction %int None %12\n"
+          "%a = OpFunctionParameter %_ptr_Function_int\n"
+          "%b = OpFunctionParameter %_ptr_Function_int\n"
+         "%17 = OpLabel\n"
+         "%18 = OpLoad %int %a\n"
+         "%19 = OpLoad %int %b\n"
+         "%20 = OpIAdd %int %18 %19\n"
+               "OpReturnValue %20\n"
+               "OpFunctionEnd\n";
+  // clang-format on
 
   SpvTools t(SPV_ENV_UNIVERSAL_1_1);
   std::unique_ptr<ir::Module> module = t.BuildModule(text);
@@ -92,27 +94,29 @@
   // #version 310 es
   // void main() {}
   const std::string text =
-      "OpCapability Shader\n"
-      "%1 = OpExtInstImport \"GLSL.std.450\"\n"
-      "OpMemoryModel Logical GLSL450\n"
-      "OpEntryPoint Vertex %2 \"main\"\n"
-      "%3 = OpString \"minimal.vert\"\n"
-      "OpSource ESSL 310\n"
-      "OpName %2 \"main\"\n"
-      "OpLine %3 10 10\n"
-      "%4 = OpTypeVoid\n"
-      "OpLine %3 100 100\n"
-      "%5 = OpTypeFunction %4\n"
-      "%2 = OpFunction %4 None %5\n"
-      "OpLine %3 1 1\n"
-      "OpNoLine\n"
-      "OpLine %3 2 2\n"
-      "OpLine %3 3 3\n"
-      "%6 = OpLabel\n"
-      "OpLine %3 4 4\n"
-      "OpNoLine\n"
-      "OpReturn\n"
-      "OpFunctionEnd\n";
+      // clang-format off
+               "OpCapability Shader\n"
+          "%1 = OpExtInstImport \"GLSL.std.450\"\n"
+               "OpMemoryModel Logical GLSL450\n"
+               "OpEntryPoint Vertex %main \"main\"\n"
+          "%3 = OpString \"minimal.vert\"\n"
+               "OpSource ESSL 310\n"
+               "OpName %main \"main\"\n"
+               "OpLine %3 10 10\n"
+       "%void = OpTypeVoid\n"
+               "OpLine %3 100 100\n"
+          "%5 = OpTypeFunction %void\n"
+       "%main = OpFunction %void None %5\n"
+               "OpLine %3 1 1\n"
+               "OpNoLine\n"
+               "OpLine %3 2 2\n"
+               "OpLine %3 3 3\n"
+          "%6 = OpLabel\n"
+               "OpLine %3 4 4\n"
+               "OpNoLine\n"
+               "OpReturn\n"
+               "OpFunctionEnd\n";
+  // clang-format on
 
   SpvTools t(SPV_ENV_UNIVERSAL_1_1);
   std::unique_ptr<ir::Module> module = t.BuildModule(text);
@@ -142,56 +146,58 @@
   //   float lv1 = gv1 - gv2;
   // }
   const std::string text =
-      "OpCapability Shader\n"
-      "%1 = OpExtInstImport \"GLSL.std.450\"\n"
-      "OpMemoryModel Logical GLSL450\n"
-      "OpEntryPoint Vertex %2 \"main\"\n"
-      "OpSource ESSL 310\n"
-      "OpName %2 \"main\"\n"
-      "OpName %3 \"f(\"\n"
-      "OpName %4 \"gv1\"\n"
-      "OpName %5 \"gv2\"\n"
-      "OpName %6 \"lv1\"\n"
-      "OpName %7 \"lv2\"\n"
-      "OpName %8 \"lv1\"\n"
-      "%9 = OpTypeVoid\n"
-      "%10 = OpTypeFunction %9\n"
-      "%11 = OpTypeFloat 32\n"
-      "%12 = OpTypeFunction %11\n"
-      "%13 = OpTypePointer Private %11\n"
-      "%4 = OpVariable %13 Private\n"
-      "%14 = OpConstant %11 10\n"
-      "%5 = OpVariable %13 Private\n"
-      "%15 = OpConstant %11 100\n"
-      "%16 = OpTypePointer Function %11\n"
-      "%2 = OpFunction %9 None %10\n"
-      "%17 = OpLabel\n"
-      "%8 = OpVariable %16 Function\n"
-      "OpStore %4 %14\n"
-      "OpStore %5 %15\n"
-      "%18 = OpLoad %11 %4\n"
-      "%19 = OpLoad %11 %5\n"
-      "%20 = OpFSub %11 %18 %19\n"
-      "OpStore %8 %20\n"
-      "OpReturn\n"
-      "OpFunctionEnd\n"
-      "%3 = OpFunction %11 None %12\n"
-      "%21 = OpLabel\n"
-      "%6 = OpVariable %16 Function\n"
-      "%7 = OpVariable %16 Function\n"
-      "%22 = OpLoad %11 %4\n"
-      "%23 = OpLoad %11 %5\n"
-      "%24 = OpFAdd %11 %22 %23\n"
-      "OpStore %6 %24\n"
-      "%25 = OpLoad %11 %4\n"
-      "%26 = OpLoad %11 %5\n"
-      "%27 = OpFMul %11 %25 %26\n"
-      "OpStore %7 %27\n"
-      "%28 = OpLoad %11 %6\n"
-      "%29 = OpLoad %11 %7\n"
-      "%30 = OpFDiv %11 %28 %29\n"
-      "OpReturnValue %30\n"
-      "OpFunctionEnd\n";
+      // clang-format off
+               "OpCapability Shader\n"
+          "%1 = OpExtInstImport \"GLSL.std.450\"\n"
+               "OpMemoryModel Logical GLSL450\n"
+               "OpEntryPoint Vertex %main \"main\"\n"
+               "OpSource ESSL 310\n"
+               "OpName %main \"main\"\n"
+               "OpName %f_ \"f(\"\n"
+               "OpName %gv1 \"gv1\"\n"
+               "OpName %gv2 \"gv2\"\n"
+               "OpName %lv1 \"lv1\"\n"
+               "OpName %lv2 \"lv2\"\n"
+               "OpName %lv1_0 \"lv1\"\n"
+       "%void = OpTypeVoid\n"
+         "%10 = OpTypeFunction %void\n"
+      "%float = OpTypeFloat 32\n"
+         "%12 = OpTypeFunction %float\n"
+ "%_ptr_Private_float = OpTypePointer Private %float\n"
+        "%gv1 = OpVariable %_ptr_Private_float Private\n"
+         "%14 = OpConstant %float 10\n"
+        "%gv2 = OpVariable %_ptr_Private_float Private\n"
+         "%15 = OpConstant %float 100\n"
+ "%_ptr_Function_float = OpTypePointer Function %float\n"
+       "%main = OpFunction %void None %10\n"
+         "%17 = OpLabel\n"
+      "%lv1_0 = OpVariable %_ptr_Function_float Function\n"
+               "OpStore %gv1 %14\n"
+               "OpStore %gv2 %15\n"
+         "%18 = OpLoad %float %gv1\n"
+         "%19 = OpLoad %float %gv2\n"
+         "%20 = OpFSub %float %18 %19\n"
+               "OpStore %lv1_0 %20\n"
+               "OpReturn\n"
+               "OpFunctionEnd\n"
+         "%f_ = OpFunction %float None %12\n"
+         "%21 = OpLabel\n"
+        "%lv1 = OpVariable %_ptr_Function_float Function\n"
+        "%lv2 = OpVariable %_ptr_Function_float Function\n"
+         "%22 = OpLoad %float %gv1\n"
+         "%23 = OpLoad %float %gv2\n"
+         "%24 = OpFAdd %float %22 %23\n"
+               "OpStore %lv1 %24\n"
+         "%25 = OpLoad %float %gv1\n"
+         "%26 = OpLoad %float %gv2\n"
+         "%27 = OpFMul %float %25 %26\n"
+               "OpStore %lv2 %27\n"
+         "%28 = OpLoad %float %lv1\n"
+         "%29 = OpLoad %float %lv2\n"
+         "%30 = OpFDiv %float %28 %29\n"
+               "OpReturnValue %30\n"
+               "OpFunctionEnd\n";
+  // clang-format on
 
   SpvTools t(SPV_ENV_UNIVERSAL_1_1);
   std::unique_ptr<ir::Module> module = t.BuildModule(text);
diff --git a/test/opt/test_pass_manager.cpp b/test/opt/test_pass_manager.cpp
index cf2f2f6..1ecde39 100644
--- a/test/opt/test_pass_manager.cpp
+++ b/test/opt/test_pass_manager.cpp
@@ -36,18 +36,18 @@
 
   manager.AddPass<opt::StripDebugInfoPass>();
   EXPECT_EQ(1u, manager.NumPasses());
-  EXPECT_STREQ("StripDebugInfo", manager.GetPass(0)->name());
+  EXPECT_STREQ("strip-debug", manager.GetPass(0)->name());
 
   manager.AddPass(std::unique_ptr<opt::NullPass>(new opt::NullPass));
   EXPECT_EQ(2u, manager.NumPasses());
-  EXPECT_STREQ("StripDebugInfo", manager.GetPass(0)->name());
-  EXPECT_STREQ("Null", manager.GetPass(1)->name());
+  EXPECT_STREQ("strip-debug", manager.GetPass(0)->name());
+  EXPECT_STREQ("null", manager.GetPass(1)->name());
 
   manager.AddPass<opt::StripDebugInfoPass>();
   EXPECT_EQ(3u, manager.NumPasses());
-  EXPECT_STREQ("StripDebugInfo", manager.GetPass(0)->name());
-  EXPECT_STREQ("Null", manager.GetPass(1)->name());
-  EXPECT_STREQ("StripDebugInfo", manager.GetPass(2)->name());
+  EXPECT_STREQ("strip-debug", manager.GetPass(0)->name());
+  EXPECT_STREQ("null", manager.GetPass(1)->name());
+  EXPECT_STREQ("strip-debug", manager.GetPass(2)->name());
 }
 
 // A pass that appends an OpNop instruction to the debug section.
diff --git a/test/opt/test_strip_debug_info.cpp b/test/opt/test_strip_debug_info.cpp
index 964b33c..d9b35e1 100644
--- a/test/opt/test_strip_debug_info.cpp
+++ b/test/opt/test_strip_debug_info.cpp
@@ -35,28 +35,30 @@
 
 TEST_F(StripLineDebugInfoTest, LineNoLine) {
   std::vector<const char*> text = {
-      "OpCapability Shader",
-      "%1 = OpExtInstImport \"GLSL.std.450\"",
-      "OpMemoryModel Logical GLSL450",
-      "OpEntryPoint Vertex %2 \"main\"",
-      "%3 = OpString \"minimal.vert\"",
-      "OpNoLine",
-      "OpLine %3 10 10",
-      "%4 = OpTypeVoid",
-      "OpLine %3 100 100",
-      "%5 = OpTypeFunction %4",
-      "%2 = OpFunction %4 None %5",
-      "OpLine %3 1 1",
-      "OpNoLine",
-      "OpLine %3 2 2",
-      "OpLine %3 3 3",
-      "%6 = OpLabel",
-      "OpLine %3 4 4",
-      "OpNoLine",
-      "OpReturn",
-      "OpLine %3 4 4",
-      "OpNoLine",
-      "OpFunctionEnd",
+      // clang-format off
+               "OpCapability Shader",
+          "%1 = OpExtInstImport \"GLSL.std.450\"",
+               "OpMemoryModel Logical GLSL450",
+               "OpEntryPoint Vertex %2 \"main\"",
+          "%3 = OpString \"minimal.vert\"",
+               "OpNoLine",
+               "OpLine %3 10 10",
+       "%void = OpTypeVoid",
+               "OpLine %3 100 100",
+          "%5 = OpTypeFunction %void",
+          "%2 = OpFunction %void None %5",
+               "OpLine %3 1 1",
+               "OpNoLine",
+               "OpLine %3 2 2",
+               "OpLine %3 3 3",
+          "%6 = OpLabel",
+               "OpLine %3 4 4",
+               "OpNoLine",
+               "OpReturn",
+               "OpLine %3 4 4",
+               "OpNoLine",
+               "OpFunctionEnd",
+      // clang-format on
   };
   SinglePassRunAndCheck<opt::StripDebugInfoPass>(JoinAllInsts(text),
                                                  JoinNonDebugInsts(text));
diff --git a/test/opt/test_utils.cpp b/test/opt/test_utils.cpp
index 4a96559..e9ebb31 100644
--- a/test/opt/test_utils.cpp
+++ b/test/opt/test_utils.cpp
@@ -55,4 +55,64 @@
                  "the only remaining string"}));
 }
 
+namespace {
+struct SubstringReplacementTestCase {
+  const char* orig_str;
+  const char* find_substr;
+  const char* replace_substr;
+  const char* expected_str;
+  bool replace_should_succeed;
+};
+}
+using FindAndReplaceTest =
+    ::testing::TestWithParam<SubstringReplacementTestCase>;
+
+TEST_P(FindAndReplaceTest, SubstringReplacement) {
+  auto process = std::string(GetParam().orig_str);
+  EXPECT_EQ(GetParam().replace_should_succeed,
+            FindAndReplace(&process, GetParam().find_substr,
+                           GetParam().replace_substr))
+      << "Original string: " << GetParam().orig_str
+      << " replace: " << GetParam().find_substr
+      << " to: " << GetParam().replace_substr
+      << " should returns: " << GetParam().replace_should_succeed;
+  EXPECT_STREQ(GetParam().expected_str, process.c_str())
+      << "Original string: " << GetParam().orig_str
+      << " replace: " << GetParam().find_substr
+      << " to: " << GetParam().replace_substr
+      << " expected string: " << GetParam().expected_str;
+}
+
+INSTANTIATE_TEST_CASE_P(
+    SubstringReplacement, FindAndReplaceTest,
+    ::testing::ValuesIn(std::vector<SubstringReplacementTestCase>({
+        // orig string, find substring, replace substring, expected string,
+        // replacement happened
+        {"", "", "", "", false},
+        {"", "b", "", "", false},
+        {"", "", "c", "", false},
+        {"", "a", "b", "", false},
+
+        {"a", "", "c", "a", false},
+        {"a", "b", "c", "a", false},
+        {"a", "b", "", "a", false},
+        {"a", "a", "", "", true},
+        {"a", "a", "b", "b", true},
+
+        {"ab", "a", "b", "bb", true},
+        {"ab", "a", "", "b", true},
+        {"ab", "b", "", "a", true},
+        {"ab", "ab", "", "", true},
+        {"ab", "ab", "cd", "cd", true},
+        {"bc", "abc", "efg", "bc", false},
+
+        {"abc", "ab", "bc", "bcc", true},
+        {"abc", "ab", "", "c", true},
+        {"abc", "bc", "", "a", true},
+        {"abc", "bc", "d", "ad", true},
+        {"abc", "a", "123", "123bc", true},
+        {"abc", "ab", "a", "ac", true},
+        {"abc", "a", "aab", "aabbc", true},
+        {"abc", "abcd", "efg", "abc", false},
+    })));
 }  // anonymous namespace
diff --git a/tools/opt/opt.cpp b/tools/opt/opt.cpp
index e3246a1..3c3f79c 100644
--- a/tools/opt/opt.cpp
+++ b/tools/opt/opt.cpp
@@ -51,6 +51,9 @@
 Options:
   --strip-debug
                Remove all debug instructions.
+  --freeze-spec-const
+               Freeze the values of specialization constants to their default
+               values.
   -h, --help   Print this help.
   --version    Display optimizer version information.
 )",
@@ -83,6 +86,8 @@
         }
       } else if (0 == strcmp(cur_arg, "--strip-debug")) {
         pass_manager.AddPass<opt::StripDebugInfoPass>();
+      } else if (0 == strcmp(cur_arg, "--freeze-spec-const")) {
+        pass_manager.AddPass<opt::FreezeSpecConstantValuePass>();
       } else if ('\0' == cur_arg[1]) {
         // Setting a filename of "-" to indicate stdin.
         if (!in_file) {
diff --git a/utils/update_build_version.py b/utils/update_build_version.py
index 98ab5b1..9a3d5ac 100755
--- a/utils/update_build_version.py
+++ b/utils/update_build_version.py
@@ -25,7 +25,8 @@
 #  - A longer string with the project name, the software version number, and
 #    git commit information for the directory.  The commit information
 #    is the output of "git describe" if that succeeds, or "git rev-parse HEAD"
-#    if that succeeds, or otherwise a message containing the phrase "unknown hash".
+#    if that succeeds, or otherwise a message containing the phrase
+#    "unknown hash".
 # The string contents are escaped as necessary.
 
 from __future__ import print_function
@@ -37,7 +38,7 @@
 import sys
 
 
-def command_output(cmd, dir):
+def command_output(cmd, directory):
     """Runs a command in a directory and returns its standard output stream.
 
     Captures the standard error stream.
@@ -45,24 +46,24 @@
     Raises a RuntimeError if the command fails to launch or otherwise fails.
     """
     p = subprocess.Popen(cmd,
-                         cwd=dir,
+                         cwd=directory,
                          stdout=subprocess.PIPE,
                          stderr=subprocess.PIPE)
     (stdout, _) = p.communicate()
     if p.returncode != 0:
-        raise RuntimeError('Failed to run %s in %s' % (cmd, dir))
+        raise RuntimeError('Failed to run %s in %s' % (cmd, directory))
     return stdout
 
 
-def deduce_software_version(dir):
+def deduce_software_version(directory):
     """Returns a software version number parsed from the CHANGES file
-    in the given dir.
+    in the given directory.
 
     The CHANGES file describes most recent versions first.
     """
 
-    pattern = re.compile('(v\d+\.\d+(-dev)?) \d\d\d\d-\d\d-\d\d$')
-    changes_file = os.path.join(dir, 'CHANGES')
+    pattern = re.compile(r'(v\d+\.\d+(-dev)?) \d\d\d\d-\d\d-\d\d$')
+    changes_file = os.path.join(directory, 'CHANGES')
     with open(changes_file) as f:
         for line in f.readlines():
             match = pattern.match(line)
@@ -71,11 +72,11 @@
     raise Exception('No version number found in {}'.format(changes_file))
 
 
-def describe(dir):
+def describe(directory):
     """Returns a string describing the current Git HEAD version as descriptively
     as possible.
 
-    Runs 'git describe', or alternately 'git rev-parse HEAD', in dir.  If
+    Runs 'git describe', or alternately 'git rev-parse HEAD', in directory.  If
     successful, returns the output; otherwise returns 'unknown hash, <date>'."""
     try:
         # decode() is needed here for Python3 compatibility. In Python2,
@@ -83,29 +84,37 @@
         # Popen.communicate() returns a bytes instance, which needs to be
         # decoded into text data first in Python3. And this decode() won't
         # hurt Python2.
-        return command_output(['git', 'describe'], dir).rstrip().decode()
+        return command_output(['git', 'describe'], directory).rstrip().decode()
     except:
         try:
             return command_output(
-                ['git', 'rev-parse', 'HEAD'], dir).rstrip().decode()
+                ['git', 'rev-parse', 'HEAD'], directory).rstrip().decode()
         except:
             return 'unknown hash, {}'.format(datetime.date.today().isoformat())
 
 
 def main():
     if len(sys.argv) != 3:
-        print('usage: {0} <spirv-tools_dir> <output-file>'.format(sys.argv[0]))
+        print('usage: {} <spirv-tools-dir> <output-file>'.format(sys.argv[0]))
         sys.exit(1)
 
     output_file = sys.argv[2]
+    output_dir = os.path.dirname(output_file)
+    if not os.path.isdir(output_dir):
+        os.makedirs(output_dir)
 
     software_version = deduce_software_version(sys.argv[1])
     new_content = '"{}", "SPIRV-Tools {} {}"\n'.format(
         software_version, software_version,
         describe(sys.argv[1]).replace('"', '\\"'))
-    if os.path.isfile(output_file) and new_content == open(output_file, 'r').read():
-        sys.exit(0)
-    open(output_file, 'w').write(new_content)
+
+    if os.path.isfile(output_file):
+        with open(output_file, 'r') as f:
+            if new_content == f.read():
+                return
+
+    with open(output_file, 'w') as f:
+        f.write(new_content)
 
 if __name__ == '__main__':
     main()