Squashed 'third_party/SPIRV-Tools/' changes from b930e734e..54cd5e196

54cd5e196 spirv-opt : SPV_NV_bindless_texture related changes (#4870)
b5d0bf285 Require ColMajor or RowMajor for matrices (#4878)
a90ccc240 Remove default copy constructor in header. (#4879)
4773879b6 Update structure layout validation (#4876)
8dc0030ec spirv-as: Avoid overflow when parsing exponents on hex floats (#4874)
cc5fca057 spirv-val: Fix Vulkan memory scope (#4869)
388ce0ee6 spirv-as: Avoid recursion when skipping whitespace (#4866)
e4cfa190d spirv-val: Add SPV_KHR_ray_query (#4848)
60615b8ec Implement SPV_NV_bindless_texture related changes (#4847)
93ebf698a spirv-val: Add OpConvertUToAccelerationStructureKHR (#4838)
e2cf76930 spirv-val: Label VUID 06925 (#4852)
faa8d6a65 Revert "Optimize DefUseManager allocations (#4709)" (#4846)
69e1deabc Fix small bug traversing users in interface_var_sroa (#4850)
bc5c8760a spirv-val: Add Vulkan decoration interface (#4831)
05de65037 Use structural reachability in CFG checks (#4849)
dcee3a5de Update validator diagnostics with "structurally dominated" (#4844)
5f4284aa7 Add limit for scalar replacment when fuzzing (#4843)
92fe420c8 Reduce load size does not work for array with spec const size (#4845)
d5a3bfcf2 Avoid undefined behaviour when getting debug opcode (#4842)
6803cc512 use exec_tools instead of tools for better RBE compatibility (#4837)
32622ba7c DCE: clean up the cfg for all functions that were processed. (#4840)
8ec4a0772 Fix unreachable loop increment (#4836)
286e9c118 Use structural dominance to validate cfg (#4832)
91572e769 Write binary files to stdout in binary on windows. (#4834)
0b824324b Fix segfault in `SpirvTools::Disassemble` when printing (#4833)
66d88508d Build struct order only for the section needed when unrolling. (#4830)
f2dfa53ae Avoid unrolling large loops while fuzzing (#4835)
37d2396ca Fix SplitLoopHeader to handle single block loop (#4829)
3c9fd7577 Avoid if-conversion if both predecessors are the same (#4826)
c4ed5157d Fixed crash unrolling loops with residual iterations (#4820)
76fe35219 Fail validation when RelaxedPrecision is applied to a type. (#4823)
845d98d46 Do not check if the binary changed if encoding is different (#4824)
4f321f862 Avoid undefined divide-by-0 (#4821)
2eff41e70 Remove stray output to stdout from tests (#4816)
fbcb6cf4c Ability to fold Constant Vector times Matrix and Matrix times vector instructions (#4818)
bfc611b03 spirv-val: Label 06807 and 06808 VUID (#4817)
76ebfb989 Avoid replacing access chain with OOB access (#4819)
8f7f5024f Simplify invocation of snprintf (#4815)
fad68a755 Fix usage of sprintf. (#4811)
044ff1aab spirv-val: Add support for SPV_AMD_shader_early_and_late_fragment_tests (#4812)
c94501352 spirv-val: Optimize struct field decoration lookup (#4809)

git-subtree-dir: third_party/SPIRV-Tools
git-subtree-split: 54cd5e1963b399e6c6d3c5b70da45583d9f9fed8
Change-Id: I3bf0848ac96c1aee7cf29d9428babe18b52af105
diff --git a/Android.mk b/Android.mk
index 6dd1834..cd1d7f8 100644
--- a/Android.mk
+++ b/Android.mk
@@ -68,6 +68,7 @@
 		source/val/validate_logicals.cpp \
 		source/val/validate_non_uniform.cpp \
 		source/val/validate_primitives.cpp \
+		source/val/validate_ray_query.cpp \
 		source/val/validate_scopes.cpp \
 		source/val/validate_small_type_uses.cpp \
 		source/val/validate_type.cpp
diff --git a/BUILD.bazel b/BUILD.bazel
index 914619a..35dfd66 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -82,7 +82,7 @@
     outs = ["generators.inc"],
     cmd = "$(location generate_registry_tables) --xml=$(location @spirv_headers//:spirv_xml_registry) --generator-output=$(location generators.inc)",
     cmd_bat = "$(location //:generate_registry_tables) --xml=$(location @spirv_headers//:spirv_xml_registry) --generator-output=$(location generators.inc)",
-    tools = [":generate_registry_tables"],
+    exec_tools = [":generate_registry_tables"],
 )
 
 py_binary(
@@ -96,7 +96,7 @@
     outs = ["build-version.inc"],
     cmd = "SOURCE_DATE_EPOCH=0 $(location update_build_version) $(location CHANGES) $(location build-version.inc)",
     cmd_bat = "set SOURCE_DATE_EPOCH=0  && $(location //:update_build_version) $(location CHANGES) $(location build-version.inc)",
-    tools = [":update_build_version"],
+    exec_tools = [":update_build_version"],
 )
 
 # Libraries
diff --git a/BUILD.gn b/BUILD.gn
index 9f96c24..9e9f6e5 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -466,7 +466,6 @@
     "source/util/make_unique.h",
     "source/util/parse_number.cpp",
     "source/util/parse_number.h",
-    "source/util/pooled_linked_list.h",
     "source/util/small_vector.h",
     "source/util/string_utils.cpp",
     "source/util/string_utils.h",
@@ -531,6 +530,7 @@
     "source/val/validate_mode_setting.cpp",
     "source/val/validate_non_uniform.cpp",
     "source/val/validate_primitives.cpp",
+    "source/val/validate_ray_query.cpp",
     "source/val/validate_scopes.cpp",
     "source/val/validate_scopes.h",
     "source/val/validate_small_type_uses.cpp",
diff --git a/DEPS b/DEPS
index 808ef6c..78c9dd3 100644
--- a/DEPS
+++ b/DEPS
@@ -6,7 +6,7 @@
   'effcee_revision': 'ddf5e2bb92957dc8a12c5392f8495333d6844133',
   'googletest_revision': '548b13dc3c02b93f60eeff9a0cc6e11c1ea722ca',
   're2_revision': '5723bb8950318135ed9cf4fc76bed988a087f536',
-  'spirv_headers_revision': '5a121866927a16ab9d49bed4788b532c7fcea766',
+  'spirv_headers_revision': 'b2a156e1c0434bc8c99aaebba1c7be98be7ac580',
 }
 
 deps = {
diff --git a/build_defs.bzl b/build_defs.bzl
index ef9a829..7189137 100644
--- a/build_defs.bzl
+++ b/build_defs.bzl
@@ -76,7 +76,7 @@
             "--core-insts-output=$(location {3}) " +
             "--operand-kinds-output=$(location {4})"
         ).format(*fmtargs),
-        tools = [":generate_grammar_tables"],
+        exec_tools = [":generate_grammar_tables"],
         visibility = ["//visibility:private"],
     )
 
@@ -113,7 +113,7 @@
             "--extension-enum-output=$(location {3}) " +
             "--enum-string-mapping-output=$(location {4})"
         ).format(*fmtargs),
-        tools = [":generate_grammar_tables"],
+        exec_tools = [":generate_grammar_tables"],
         visibility = ["//visibility:private"],
     )
 
@@ -139,7 +139,7 @@
             "--extinst-opencl-grammar=$(location {0}) " +
             "--opencl-insts-output=$(location {1})"
         ).format(*fmtargs),
-        tools = [":generate_grammar_tables"],
+        exec_tools = [":generate_grammar_tables"],
         visibility = ["//visibility:private"],
     )
 
@@ -165,7 +165,7 @@
             "--extinst-glsl-grammar=$(location {0}) " +
             "--glsl-insts-output=$(location {1})"
         ).format(*fmtargs),
-        tools = [":generate_grammar_tables"],
+        exec_tools = [":generate_grammar_tables"],
         visibility = ["//visibility:private"],
     )
 
@@ -193,7 +193,7 @@
             "--vendor-insts-output=$(location {1}) " +
             "--vendor-operand-kind-prefix={2}"
         ).format(*fmtargs),
-        tools = [":generate_grammar_tables"],
+        exec_tools = [":generate_grammar_tables"],
         visibility = ["//visibility:private"],
     )
 
@@ -216,7 +216,7 @@
             "--extinst-grammar=$< " +
             "--extinst-output-path=$(location {0})"
         ).format(*fmtargs),
-        tools = [":generate_language_headers"],
+        exec_tools = [":generate_language_headers"],
         visibility = ["//visibility:private"],
     )
 
diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt
index 98559b8..1ceb78f 100644
--- a/source/CMakeLists.txt
+++ b/source/CMakeLists.txt
@@ -228,7 +228,6 @@
   ${CMAKE_CURRENT_SOURCE_DIR}/util/hex_float.h
   ${CMAKE_CURRENT_SOURCE_DIR}/util/make_unique.h
   ${CMAKE_CURRENT_SOURCE_DIR}/util/parse_number.h
-  ${CMAKE_CURRENT_SOURCE_DIR}/util/pooled_linked_list.h
   ${CMAKE_CURRENT_SOURCE_DIR}/util/small_vector.h
   ${CMAKE_CURRENT_SOURCE_DIR}/util/string_utils.h
   ${CMAKE_CURRENT_SOURCE_DIR}/util/timer.h
@@ -323,6 +322,7 @@
   ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_mode_setting.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_non_uniform.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_primitives.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_ray_query.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_scopes.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_small_type_uses.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_type.cpp
diff --git a/source/cfa.h b/source/cfa.h
index 7cadf55..f55a7bd 100644
--- a/source/cfa.h
+++ b/source/cfa.h
@@ -68,6 +68,8 @@
   ///                       CFG following postorder traversal semantics
   /// @param[in] backedge   A function that will be called when a backedge is
   ///                       encountered during a traversal
+  /// @param[in] terminal   A function that will be called to determine if the
+  ///                       search should stop at the given node.
   /// 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
@@ -76,7 +78,8 @@
       const BB* entry, get_blocks_func successor_func,
       std::function<void(cbb_ptr)> preorder,
       std::function<void(cbb_ptr)> postorder,
-      std::function<void(cbb_ptr, cbb_ptr)> backedge);
+      std::function<void(cbb_ptr, cbb_ptr)> backedge,
+      std::function<bool(cbb_ptr)> terminal);
 
   /// @brief Calculates dominator edges for a set of blocks
   ///
@@ -138,7 +141,8 @@
     const BB* entry, get_blocks_func successor_func,
     std::function<void(cbb_ptr)> preorder,
     std::function<void(cbb_ptr)> postorder,
-    std::function<void(cbb_ptr, cbb_ptr)> backedge) {
+    std::function<void(cbb_ptr, cbb_ptr)> backedge,
+    std::function<bool(cbb_ptr)> terminal) {
   std::unordered_set<uint32_t> processed;
 
   /// NOTE: work_list is the sequence of nodes from the root node to the node
@@ -152,7 +156,7 @@
 
   while (!work_list.empty()) {
     block_info& top = work_list.back();
-    if (top.iter == end(*successor_func(top.block))) {
+    if (terminal(top.block) || top.iter == end(*successor_func(top.block))) {
       postorder(top.block);
       work_list.pop_back();
     } else {
@@ -266,11 +270,13 @@
   auto mark_visited = [&visited](const BB* b) { visited.insert(b); };
   auto ignore_block = [](const BB*) {};
   auto ignore_blocks = [](const BB*, const BB*) {};
+  auto no_terminal_blocks = [](const BB*) { return false; };
 
   auto traverse_from_root = [&mark_visited, &succ_func, &ignore_block,
-                             &ignore_blocks](const BB* entry) {
+                             &ignore_blocks,
+                             &no_terminal_blocks](const BB* entry) {
     DepthFirstTraversal(entry, succ_func, mark_visited, ignore_block,
-                        ignore_blocks);
+                        ignore_blocks, no_terminal_blocks);
   };
 
   std::vector<BB*> result;
diff --git a/source/disassemble.h b/source/disassemble.h
index 8eacb10..b520a1e 100644
--- a/source/disassemble.h
+++ b/source/disassemble.h
@@ -25,9 +25,10 @@
 
 // Decodes the given SPIR-V instruction binary representation to its assembly
 // text. The context is inferred from the provided module binary. The options
-// parameter is a bit field of spv_binary_to_text_options_t. Decoded text will
-// be stored into *text. Any error will be written into *diagnostic if
-// diagnostic is non-null.
+// parameter is a bit field of spv_binary_to_text_options_t (note: the option
+// SPV_BINARY_TO_TEXT_OPTION_PRINT will be ignored). Decoded text will be
+// stored into *text. Any error will be written into *diagnostic if diagnostic
+// is non-null.
 std::string spvInstructionBinaryToText(const spv_target_env env,
                                        const uint32_t* inst_binary,
                                        const size_t inst_word_count,
diff --git a/source/libspirv.cpp b/source/libspirv.cpp
index 0bc0935..be76caa 100644
--- a/source/libspirv.cpp
+++ b/source/libspirv.cpp
@@ -99,7 +99,9 @@
   spv_text spvtext = nullptr;
   spv_result_t status = spvBinaryToText(impl_->context, binary, binary_size,
                                         options, &spvtext, nullptr);
-  if (status == SPV_SUCCESS) {
+  if (status == SPV_SUCCESS &&
+      (options & SPV_BINARY_TO_TEXT_OPTION_PRINT) == 0) {
+    assert(spvtext);
     text->assign(spvtext->str, spvtext->str + spvtext->length);
   }
   spvTextDestroy(spvtext);
diff --git a/source/opt/aggressive_dead_code_elim_pass.cpp b/source/opt/aggressive_dead_code_elim_pass.cpp
index 2486242..ffb499f 100644
--- a/source/opt/aggressive_dead_code_elim_pass.cpp
+++ b/source/opt/aggressive_dead_code_elim_pass.cpp
@@ -659,9 +659,14 @@
 
   InitializeModuleScopeLiveInstructions();
 
-  // Process all entry point functions.
-  ProcessFunction pfn = [this](Function* fp) { return AggressiveDCE(fp); };
-  modified |= context()->ProcessReachableCallTree(pfn);
+  // Run |AggressiveDCE| on the remaining functions.  The order does not matter,
+  // since |AggressiveDCE| is intra-procedural.  This can mean that function
+  // will become dead if all function call to them are removed.  These dead
+  // function will still be in the module after this pass.  We expect this to be
+  // rare.
+  for (Function& fp : *context()->module()) {
+    modified |= AggressiveDCE(&fp);
+  }
 
   // If the decoration manager is kept live then the context will try to keep it
   // up to date.  ADCE deals with group decorations by changing the operands in
@@ -687,8 +692,9 @@
   }
 
   // Cleanup all CFG including all unreachable blocks.
-  ProcessFunction cleanup = [this](Function* f) { return CFGCleanup(f); };
-  modified |= context()->ProcessReachableCallTree(cleanup);
+  for (Function& fp : *context()->module()) {
+    modified |= CFGCleanup(&fp);
+  }
 
   return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
 }
diff --git a/source/opt/cfg.cpp b/source/opt/cfg.cpp
index ac0fcc3..66b1aed 100644
--- a/source/opt/cfg.cpp
+++ b/source/opt/cfg.cpp
@@ -74,6 +74,12 @@
 
 void CFG::ComputeStructuredOrder(Function* func, BasicBlock* root,
                                  std::list<BasicBlock*>* order) {
+  ComputeStructuredOrder(func, root, nullptr, order);
+}
+
+void CFG::ComputeStructuredOrder(Function* func, BasicBlock* root,
+                                 BasicBlock* end,
+                                 std::list<BasicBlock*>* order) {
   assert(module_->context()->get_feature_mgr()->HasCapability(
              SpvCapabilityShader) &&
          "This only works on structured control flow");
@@ -82,6 +88,8 @@
   ComputeStructuredSuccessors(func);
   auto ignore_block = [](cbb_ptr) {};
   auto ignore_edge = [](cbb_ptr, cbb_ptr) {};
+  auto terminal = [end](cbb_ptr bb) { return bb == end; };
+
   auto get_structured_successors = [this](const BasicBlock* b) {
     return &(block2structured_succs_[b]);
   };
@@ -92,7 +100,8 @@
     order->push_front(const_cast<BasicBlock*>(b));
   };
   CFA<BasicBlock>::DepthFirstTraversal(root, get_structured_successors,
-                                       ignore_block, post_order, ignore_edge);
+                                       ignore_block, post_order, ignore_edge,
+                                       terminal);
 }
 
 void CFG::ForEachBlockInPostOrder(BasicBlock* bb,
@@ -205,7 +214,7 @@
   // Find the back edge
   BasicBlock* latch_block = nullptr;
   Function::iterator latch_block_iter = header_it;
-  while (++latch_block_iter != fn->end()) {
+  for (; latch_block_iter != fn->end(); ++latch_block_iter) {
     // If blocks are in the proper order, then the only branch that appears
     // after the header is the latch.
     if (std::find(pred.begin(), pred.end(), latch_block_iter->id()) !=
@@ -237,6 +246,15 @@
     context->set_instr_block(inst, new_header);
   });
 
+  // If |bb| was the latch block, the branch back to the header is not in
+  // |new_header|.
+  if (latch_block == bb) {
+    if (new_header->ContinueBlockId() == bb->id()) {
+      new_header->GetLoopMergeInst()->SetInOperand(1, {new_header_id});
+    }
+    latch_block = new_header;
+  }
+
   // Adjust the OpPhi instructions as needed.
   bb->ForEachPhiInst([latch_block, bb, new_header, context](Instruction* phi) {
     std::vector<uint32_t> preheader_phi_ops;
diff --git a/source/opt/cfg.h b/source/opt/cfg.h
index 33412f1..fa4fef2 100644
--- a/source/opt/cfg.h
+++ b/source/opt/cfg.h
@@ -66,6 +66,14 @@
   void ComputeStructuredOrder(Function* func, BasicBlock* root,
                               std::list<BasicBlock*>* order);
 
+  // Compute structured block order into |order| for |func| starting at |root|
+  // and ending at |end|. This order has the property that dominators come
+  // before all blocks they dominate, merge blocks come after all blocks that
+  // are in the control constructs of their header, and continue blocks come
+  // after all the blocks in the body of their loop.
+  void ComputeStructuredOrder(Function* func, BasicBlock* root, BasicBlock* end,
+                              std::list<BasicBlock*>* order);
+
   // Applies |f| to all blocks that can be reach from |bb| in post order.
   void ForEachBlockInPostOrder(BasicBlock* bb,
                                const std::function<void(BasicBlock*)>& f);
diff --git a/source/opt/const_folding_rules.cpp b/source/opt/const_folding_rules.cpp
index 249e11e..cb36087 100644
--- a/source/opt/const_folding_rules.cpp
+++ b/source/opt/const_folding_rules.cpp
@@ -251,6 +251,193 @@
   };
 }
 
+ConstantFoldingRule FoldVectorTimesMatrix() {
+  return [](IRContext* context, Instruction* inst,
+            const std::vector<const analysis::Constant*>& constants)
+             -> const analysis::Constant* {
+    assert(inst->opcode() == SpvOpVectorTimesMatrix);
+    analysis::ConstantManager* const_mgr = context->get_constant_mgr();
+    analysis::TypeManager* type_mgr = context->get_type_mgr();
+
+    if (!inst->IsFloatingPointFoldingAllowed()) {
+      if (HasFloatingPoint(type_mgr->GetType(inst->type_id()))) {
+        return nullptr;
+      }
+    }
+
+    const analysis::Constant* c1 = constants[0];
+    const analysis::Constant* c2 = constants[1];
+
+    if (c1 == nullptr || c2 == nullptr) {
+      return nullptr;
+    }
+
+    // Check result type.
+    const analysis::Type* result_type = type_mgr->GetType(inst->type_id());
+    const analysis::Vector* vector_type = result_type->AsVector();
+    assert(vector_type != nullptr);
+    const analysis::Type* element_type = vector_type->element_type();
+    assert(element_type != nullptr);
+    const analysis::Float* float_type = element_type->AsFloat();
+    assert(float_type != nullptr);
+
+    // Check types of c1 and c2.
+    assert(c1->type()->AsVector() == vector_type);
+    assert(c1->type()->AsVector()->element_type() == element_type &&
+           c2->type()->AsMatrix()->element_type() == vector_type);
+
+    // Get a float vector that is the result of vector-times-matrix.
+    std::vector<const analysis::Constant*> c1_components =
+        c1->GetVectorComponents(const_mgr);
+    std::vector<const analysis::Constant*> c2_components =
+        c2->AsMatrixConstant()->GetComponents();
+    uint32_t resultVectorSize = result_type->AsVector()->element_count();
+
+    std::vector<uint32_t> ids;
+
+    if ((c1 && c1->IsZero()) || (c2 && c2->IsZero())) {
+      std::vector<uint32_t> words(float_type->width() / 32, 0);
+      for (uint32_t i = 0; i < resultVectorSize; ++i) {
+        const analysis::Constant* new_elem =
+            const_mgr->GetConstant(float_type, words);
+        ids.push_back(const_mgr->GetDefiningInstruction(new_elem)->result_id());
+      }
+      return const_mgr->GetConstant(vector_type, ids);
+    }
+
+    if (float_type->width() == 32) {
+      for (uint32_t i = 0; i < resultVectorSize; ++i) {
+        float result_scalar = 0.0f;
+        const analysis::VectorConstant* c2_vec =
+            c2_components[i]->AsVectorConstant();
+        for (uint32_t j = 0; j < c2_vec->GetComponents().size(); ++j) {
+          float c1_scalar = c1_components[j]->GetFloat();
+          float c2_scalar = c2_vec->GetComponents()[j]->GetFloat();
+          result_scalar += c1_scalar * c2_scalar;
+        }
+        utils::FloatProxy<float> result(result_scalar);
+        std::vector<uint32_t> words = result.GetWords();
+        const analysis::Constant* new_elem =
+            const_mgr->GetConstant(float_type, words);
+        ids.push_back(const_mgr->GetDefiningInstruction(new_elem)->result_id());
+      }
+      return const_mgr->GetConstant(vector_type, ids);
+    } else if (float_type->width() == 64) {
+      for (uint32_t i = 0; i < c2_components.size(); ++i) {
+        double result_scalar = 0.0;
+        const analysis::VectorConstant* c2_vec =
+            c2_components[i]->AsVectorConstant();
+        for (uint32_t j = 0; j < c2_vec->GetComponents().size(); ++j) {
+          double c1_scalar = c1_components[j]->GetDouble();
+          double c2_scalar = c2_vec->GetComponents()[j]->GetDouble();
+          result_scalar += c1_scalar * c2_scalar;
+        }
+        utils::FloatProxy<double> result(result_scalar);
+        std::vector<uint32_t> words = result.GetWords();
+        const analysis::Constant* new_elem =
+            const_mgr->GetConstant(float_type, words);
+        ids.push_back(const_mgr->GetDefiningInstruction(new_elem)->result_id());
+      }
+      return const_mgr->GetConstant(vector_type, ids);
+    }
+    return nullptr;
+  };
+}
+
+ConstantFoldingRule FoldMatrixTimesVector() {
+  return [](IRContext* context, Instruction* inst,
+            const std::vector<const analysis::Constant*>& constants)
+             -> const analysis::Constant* {
+    assert(inst->opcode() == SpvOpMatrixTimesVector);
+    analysis::ConstantManager* const_mgr = context->get_constant_mgr();
+    analysis::TypeManager* type_mgr = context->get_type_mgr();
+
+    if (!inst->IsFloatingPointFoldingAllowed()) {
+      if (HasFloatingPoint(type_mgr->GetType(inst->type_id()))) {
+        return nullptr;
+      }
+    }
+
+    const analysis::Constant* c1 = constants[0];
+    const analysis::Constant* c2 = constants[1];
+
+    if (c1 == nullptr || c2 == nullptr) {
+      return nullptr;
+    }
+
+    // Check result type.
+    const analysis::Type* result_type = type_mgr->GetType(inst->type_id());
+    const analysis::Vector* vector_type = result_type->AsVector();
+    assert(vector_type != nullptr);
+    const analysis::Type* element_type = vector_type->element_type();
+    assert(element_type != nullptr);
+    const analysis::Float* float_type = element_type->AsFloat();
+    assert(float_type != nullptr);
+
+    // Check types of c1 and c2.
+    assert(c1->type()->AsMatrix()->element_type() == vector_type);
+    assert(c2->type()->AsVector()->element_type() == element_type);
+
+    // Get a float vector that is the result of matrix-times-vector.
+    std::vector<const analysis::Constant*> c1_components =
+        c1->AsMatrixConstant()->GetComponents();
+    std::vector<const analysis::Constant*> c2_components =
+        c2->GetVectorComponents(const_mgr);
+    uint32_t resultVectorSize = result_type->AsVector()->element_count();
+
+    std::vector<uint32_t> ids;
+
+    if ((c1 && c1->IsZero()) || (c2 && c2->IsZero())) {
+      std::vector<uint32_t> words(float_type->width() / 32, 0);
+      for (uint32_t i = 0; i < resultVectorSize; ++i) {
+        const analysis::Constant* new_elem =
+            const_mgr->GetConstant(float_type, words);
+        ids.push_back(const_mgr->GetDefiningInstruction(new_elem)->result_id());
+      }
+      return const_mgr->GetConstant(vector_type, ids);
+    }
+
+    if (float_type->width() == 32) {
+      for (uint32_t i = 0; i < resultVectorSize; ++i) {
+        float result_scalar = 0.0f;
+        for (uint32_t j = 0; j < c1_components.size(); ++j) {
+          float c1_scalar = c1_components[j]
+                                ->AsVectorConstant()
+                                ->GetComponents()[i]
+                                ->GetFloat();
+          float c2_scalar = c2_components[j]->GetFloat();
+          result_scalar += c1_scalar * c2_scalar;
+        }
+        utils::FloatProxy<float> result(result_scalar);
+        std::vector<uint32_t> words = result.GetWords();
+        const analysis::Constant* new_elem =
+            const_mgr->GetConstant(float_type, words);
+        ids.push_back(const_mgr->GetDefiningInstruction(new_elem)->result_id());
+      }
+      return const_mgr->GetConstant(vector_type, ids);
+    } else if (float_type->width() == 64) {
+      for (uint32_t i = 0; i < resultVectorSize; ++i) {
+        double result_scalar = 0.0;
+        for (uint32_t j = 0; j < c1_components.size(); ++j) {
+          double c1_scalar = c1_components[j]
+                                 ->AsVectorConstant()
+                                 ->GetComponents()[i]
+                                 ->GetDouble();
+          double c2_scalar = c2_components[j]->GetDouble();
+          result_scalar += c1_scalar * c2_scalar;
+        }
+        utils::FloatProxy<double> result(result_scalar);
+        std::vector<uint32_t> words = result.GetWords();
+        const analysis::Constant* new_elem =
+            const_mgr->GetConstant(float_type, words);
+        ids.push_back(const_mgr->GetDefiningInstruction(new_elem)->result_id());
+      }
+      return const_mgr->GetConstant(vector_type, ids);
+    }
+    return nullptr;
+  };
+}
+
 ConstantFoldingRule FoldCompositeWithConstants() {
   // Folds an OpCompositeConstruct where all of the inputs are constants to a
   // constant.  A new constant is created if necessary.
@@ -1288,6 +1475,8 @@
 
   rules_[SpvOpVectorShuffle].push_back(FoldVectorShuffleWithConstants());
   rules_[SpvOpVectorTimesScalar].push_back(FoldVectorTimesScalar());
+  rules_[SpvOpVectorTimesMatrix].push_back(FoldVectorTimesMatrix());
+  rules_[SpvOpMatrixTimesVector].push_back(FoldMatrixTimesVector());
 
   rules_[SpvOpFNegate].push_back(FoldFNegate());
   rules_[SpvOpQuantizeToF16].push_back(FoldQuantizeToF16());
diff --git a/source/opt/def_use_manager.cpp b/source/opt/def_use_manager.cpp
index e1e441e..d54fdb6 100644
--- a/source/opt/def_use_manager.cpp
+++ b/source/opt/def_use_manager.cpp
@@ -13,23 +13,11 @@
 // limitations under the License.
 
 #include "source/opt/def_use_manager.h"
-#include "source/util/make_unique.h"
 
 namespace spvtools {
 namespace opt {
 namespace analysis {
 
-// Don't compact before we have a reasonable number of ids allocated (~32kb).
-static const size_t kCompactThresholdMinTotalIds = (8 * 1024);
-// Compact when fewer than this fraction of the storage is used (should be 2^n
-// for performance).
-static const size_t kCompactThresholdFractionFreeIds = 8;
-
-DefUseManager::DefUseManager() {
-  use_pool_ = MakeUnique<UseListPool>();
-  used_id_pool_ = MakeUnique<UsedIdListPool>();
-}
-
 void DefUseManager::AnalyzeInstDef(Instruction* inst) {
   const uint32_t def_id = inst->result_id();
   if (def_id != 0) {
@@ -46,15 +34,15 @@
 }
 
 void DefUseManager::AnalyzeInstUse(Instruction* inst) {
-  // It might have existed before.
-  EraseUseRecordsOfOperandIds(inst);
-
   // Create entry for the given instruction. Note that the instruction may
   // not have any in-operands. In such cases, we still need a entry for those
   // instructions so this manager knows it has seen the instruction later.
-  UsedIdList& used_ids =
-      inst_to_used_id_.insert({inst, UsedIdList(used_id_pool_.get())})
-          .first->second;
+  auto* used_ids = &inst_to_used_ids_[inst];
+  if (used_ids->size()) {
+    EraseUseRecordsOfOperandIds(inst);
+    used_ids = &inst_to_used_ids_[inst];
+  }
+  used_ids->clear();  // It might have existed before.
 
   for (uint32_t i = 0; i < inst->NumOperands(); ++i) {
     switch (inst->GetOperand(i).type) {
@@ -66,17 +54,8 @@
         uint32_t use_id = inst->GetSingleWordOperand(i);
         Instruction* def = GetDef(use_id);
         assert(def && "Definition is not registered.");
-
-        // Add to inst's use records
-        used_ids.push_back(use_id);
-
-        // Add to the users, taking care to avoid adding duplicates.  We know
-        // the duplicate for this instruction will always be at the tail.
-        UseList& list = inst_to_users_.insert({def, UseList(use_pool_.get())})
-                            .first->second;
-        if (list.empty() || list.back() != inst) {
-          list.push_back(inst);
-        }
+        id_to_users_.insert(UserEntry{def, inst});
+        used_ids->push_back(use_id);
       } break;
       default:
         break;
@@ -115,6 +94,23 @@
   return iter->second;
 }
 
+DefUseManager::IdToUsersMap::const_iterator DefUseManager::UsersBegin(
+    const Instruction* def) const {
+  return id_to_users_.lower_bound(
+      UserEntry{const_cast<Instruction*>(def), nullptr});
+}
+
+bool DefUseManager::UsersNotEnd(const IdToUsersMap::const_iterator& iter,
+                                const IdToUsersMap::const_iterator& cached_end,
+                                const Instruction* inst) const {
+  return (iter != cached_end && iter->def == inst);
+}
+
+bool DefUseManager::UsersNotEnd(const IdToUsersMap::const_iterator& iter,
+                                const Instruction* inst) const {
+  return UsersNotEnd(iter, id_to_users_.end(), inst);
+}
+
 bool DefUseManager::WhileEachUser(
     const Instruction* def, const std::function<bool(Instruction*)>& f) const {
   // Ensure that |def| has been registered.
@@ -122,11 +118,9 @@
          "Definition is not registered.");
   if (!def->HasResultId()) return true;
 
-  auto iter = inst_to_users_.find(def);
-  if (iter != inst_to_users_.end()) {
-    for (Instruction* user : iter->second) {
-      if (!f(user)) return false;
-    }
+  auto end = id_to_users_.end();
+  for (auto iter = UsersBegin(def); UsersNotEnd(iter, end, def); ++iter) {
+    if (!f(iter->user)) return false;
   }
   return true;
 }
@@ -157,15 +151,14 @@
          "Definition is not registered.");
   if (!def->HasResultId()) return true;
 
-  auto iter = inst_to_users_.find(def);
-  if (iter != inst_to_users_.end()) {
-    for (Instruction* user : iter->second) {
-      for (uint32_t idx = 0; idx != user->NumOperands(); ++idx) {
-        const Operand& op = user->GetOperand(idx);
-        if (op.type != SPV_OPERAND_TYPE_RESULT_ID && spvIsIdType(op.type)) {
-          if (def->result_id() == op.words[0]) {
-            if (!f(user, idx)) return false;
-          }
+  auto end = id_to_users_.end();
+  for (auto iter = UsersBegin(def); UsersNotEnd(iter, end, def); ++iter) {
+    Instruction* user = iter->user;
+    for (uint32_t idx = 0; idx != user->NumOperands(); ++idx) {
+      const Operand& op = user->GetOperand(idx);
+      if (op.type != SPV_OPERAND_TYPE_RESULT_ID && spvIsIdType(op.type)) {
+        if (def->result_id() == op.words[0]) {
+          if (!f(user, idx)) return false;
         }
       }
     }
@@ -237,18 +230,17 @@
 }
 
 void DefUseManager::ClearInst(Instruction* inst) {
-  if (inst_to_used_id_.find(inst) != inst_to_used_id_.end()) {
+  auto iter = inst_to_used_ids_.find(inst);
+  if (iter != inst_to_used_ids_.end()) {
     EraseUseRecordsOfOperandIds(inst);
-    uint32_t const result_id = inst->result_id();
-    if (result_id != 0) {
-      // For each using instruction, remove result_id from their used ids.
-      auto iter = inst_to_users_.find(inst);
-      if (iter != inst_to_users_.end()) {
-        for (Instruction* use : iter->second) {
-          inst_to_used_id_.at(use).remove_first(result_id);
-        }
-        inst_to_users_.erase(iter);
+    if (inst->result_id() != 0) {
+      // Remove all uses of this inst.
+      auto users_begin = UsersBegin(inst);
+      auto end = id_to_users_.end();
+      auto new_end = users_begin;
+      for (; UsersNotEnd(new_end, end, inst); ++new_end) {
       }
+      id_to_users_.erase(users_begin, new_end);
       id_to_def_.erase(inst->result_id());
     }
   }
@@ -257,48 +249,16 @@
 void DefUseManager::EraseUseRecordsOfOperandIds(const Instruction* inst) {
   // Go through all ids used by this instruction, remove this instruction's
   // uses of them.
-  auto iter = inst_to_used_id_.find(inst);
-  if (iter != inst_to_used_id_.end()) {
-    const UsedIdList& used_ids = iter->second;
-    for (uint32_t def_id : used_ids) {
-      auto def_iter = inst_to_users_.find(GetDef(def_id));
-      if (def_iter != inst_to_users_.end()) {
-        def_iter->second.remove_first(const_cast<Instruction*>(inst));
-      }
+  auto iter = inst_to_used_ids_.find(inst);
+  if (iter != inst_to_used_ids_.end()) {
+    for (auto use_id : iter->second) {
+      id_to_users_.erase(
+          UserEntry{GetDef(use_id), const_cast<Instruction*>(inst)});
     }
-    inst_to_used_id_.erase(inst);
-
-    // If we're using only a fraction of the space in used_ids_, compact storage
-    // to prevent memory usage from being unbounded.
-    if (used_id_pool_->total_nodes() > kCompactThresholdMinTotalIds &&
-        used_id_pool_->used_nodes() <
-            used_id_pool_->total_nodes() / kCompactThresholdFractionFreeIds) {
-      CompactStorage();
-    }
+    inst_to_used_ids_.erase(iter);
   }
 }
 
-void DefUseManager::CompactStorage() {
-  CompactUseRecords();
-  CompactUsedIds();
-}
-
-void DefUseManager::CompactUseRecords() {
-  std::unique_ptr<UseListPool> new_pool = MakeUnique<UseListPool>();
-  for (auto& iter : inst_to_users_) {
-    iter.second.move_nodes(new_pool.get());
-  }
-  use_pool_ = std::move(new_pool);
-}
-
-void DefUseManager::CompactUsedIds() {
-  std::unique_ptr<UsedIdListPool> new_pool = MakeUnique<UsedIdListPool>();
-  for (auto& iter : inst_to_used_id_) {
-    iter.second.move_nodes(new_pool.get());
-  }
-  used_id_pool_ = std::move(new_pool);
-}
-
 bool CompareAndPrintDifferences(const DefUseManager& lhs,
                                 const DefUseManager& rhs) {
   bool same = true;
@@ -317,52 +277,34 @@
     same = false;
   }
 
-  for (const auto& l : lhs.inst_to_used_id_) {
-    std::set<uint32_t> ul, ur;
-    lhs.ForEachUse(l.first,
-                   [&ul](Instruction*, uint32_t id) { ul.insert(id); });
-    rhs.ForEachUse(l.first,
-                   [&ur](Instruction*, uint32_t id) { ur.insert(id); });
-    if (ul.size() != ur.size()) {
-      printf(
-          "Diff in inst_to_used_id_: different number of used ids (%zu != %zu)",
-          ul.size(), ur.size());
-      same = false;
-    } else if (ul != ur) {
-      printf("Diff in inst_to_used_id_: different used ids\n");
-      same = false;
+  if (lhs.id_to_users_ != rhs.id_to_users_) {
+    for (auto p : lhs.id_to_users_) {
+      if (rhs.id_to_users_.count(p) == 0) {
+        printf("Diff in id_to_users: missing value in rhs\n");
+      }
     }
-  }
-  for (const auto& r : rhs.inst_to_used_id_) {
-    auto iter_l = lhs.inst_to_used_id_.find(r.first);
-    if (r.second.empty() &&
-        !(iter_l == lhs.inst_to_used_id_.end() || iter_l->second.empty())) {
-      printf("Diff in inst_to_used_id_: unexpected instr in rhs\n");
-      same = false;
+    for (auto p : rhs.id_to_users_) {
+      if (lhs.id_to_users_.count(p) == 0) {
+        printf("Diff in id_to_users: missing value in lhs\n");
+      }
     }
+    same = false;
   }
 
-  for (const auto& l : lhs.inst_to_users_) {
-    std::set<Instruction*> ul, ur;
-    lhs.ForEachUser(l.first, [&ul](Instruction* use) { ul.insert(use); });
-    rhs.ForEachUser(l.first, [&ur](Instruction* use) { ur.insert(use); });
-    if (ul.size() != ur.size()) {
-      printf("Diff in inst_to_users_: different number of users (%zu != %zu)",
-             ul.size(), ur.size());
-      same = false;
-    } else if (ul != ur) {
-      printf("Diff in inst_to_users_: different users\n");
-      same = false;
+  if (lhs.inst_to_used_ids_ != rhs.inst_to_used_ids_) {
+    for (auto p : lhs.inst_to_used_ids_) {
+      if (rhs.inst_to_used_ids_.count(p.first) == 0) {
+        printf("Diff in inst_to_used_ids: missing value in rhs\n");
+      }
     }
-  }
-  for (const auto& r : rhs.inst_to_users_) {
-    auto iter_l = lhs.inst_to_users_.find(r.first);
-    if (r.second.empty() &&
-        !(iter_l == lhs.inst_to_users_.end() || iter_l->second.empty())) {
-      printf("Diff in inst_to_users_: unexpected instr in rhs\n");
-      same = false;
+    for (auto p : rhs.inst_to_used_ids_) {
+      if (lhs.inst_to_used_ids_.count(p.first) == 0) {
+        printf("Diff in inst_to_used_ids: missing value in lhs\n");
+      }
     }
+    same = false;
   }
+
   return same;
 }
 
diff --git a/source/opt/def_use_manager.h b/source/opt/def_use_manager.h
index cf6cbdf..a8dbbc6 100644
--- a/source/opt/def_use_manager.h
+++ b/source/opt/def_use_manager.h
@@ -21,7 +21,6 @@
 
 #include "source/opt/instruction.h"
 #include "source/opt/module.h"
-#include "source/util/pooled_linked_list.h"
 #include "spirv-tools/libspirv.hpp"
 
 namespace spvtools {
@@ -50,6 +49,50 @@
   return lhs.operand_index < rhs.operand_index;
 }
 
+// Definition should never be null. User can be null, however, such an entry
+// should be used only for searching (e.g. all users of a particular definition)
+// and never stored in a container.
+struct UserEntry {
+  Instruction* def;
+  Instruction* user;
+};
+
+inline bool operator==(const UserEntry& lhs, const UserEntry& rhs) {
+  return lhs.def == rhs.def && lhs.user == rhs.user;
+}
+
+// Orders UserEntry for use in associative containers (i.e. less than ordering).
+//
+// The definition of an UserEntry is treated as the major key and the users as
+// the minor key so that all the users of a particular definition are
+// consecutive in a container.
+//
+// A null user always compares less than a real user. This is done to provide
+// easy values to search for the beginning of the users of a particular
+// definition (i.e. using {def, nullptr}).
+struct UserEntryLess {
+  bool operator()(const UserEntry& lhs, const UserEntry& rhs) const {
+    // If lhs.def and rhs.def are both null, fall through to checking the
+    // second entries.
+    if (!lhs.def && rhs.def) return true;
+    if (lhs.def && !rhs.def) return false;
+
+    // If neither definition is null, then compare unique ids.
+    if (lhs.def && rhs.def) {
+      if (lhs.def->unique_id() < rhs.def->unique_id()) return true;
+      if (rhs.def->unique_id() < lhs.def->unique_id()) return false;
+    }
+
+    // Return false on equality.
+    if (!lhs.user && !rhs.user) return false;
+    if (!lhs.user) return true;
+    if (!rhs.user) return false;
+
+    // If neither user is null then compare unique ids.
+    return lhs.user->unique_id() < rhs.user->unique_id();
+  }
+};
+
 // A class for analyzing and managing defs and uses in an Module.
 class DefUseManager {
  public:
@@ -59,7 +102,7 @@
   // will be communicated to the outside via the given message |consumer|. This
   // instance only keeps a reference to the |consumer|, so the |consumer| should
   // outlive this instance.
-  DefUseManager(Module* module) : DefUseManager() { AnalyzeDefUse(module); }
+  DefUseManager(Module* module) { AnalyzeDefUse(module); }
 
   DefUseManager(const DefUseManager&) = delete;
   DefUseManager(DefUseManager&&) = delete;
@@ -171,36 +214,35 @@
   // uses.
   void UpdateDefUse(Instruction* inst);
 
-  // Compacts any internal storage to save memory.
-  void CompactStorage();
-
  private:
-  using UseList = spvtools::utils::PooledLinkedList<Instruction*>;
-  using UseListPool = spvtools::utils::PooledLinkedListNodes<Instruction*>;
-  // Stores linked lists of Instructions using a def.
-  using InstToUsersMap = std::unordered_map<const Instruction*, UseList>;
+  using IdToUsersMap = std::set<UserEntry, UserEntryLess>;
+  using InstToUsedIdsMap =
+      std::unordered_map<const Instruction*, std::vector<uint32_t>>;
 
-  using UsedIdList = spvtools::utils::PooledLinkedList<uint32_t>;
-  using UsedIdListPool = spvtools::utils::PooledLinkedListNodes<uint32_t>;
-  // Stores mapping from instruction to their UsedIdRange.
-  using InstToUsedIdMap = std::unordered_map<const Instruction*, UsedIdList>;
+  // Returns the first location that {|def|, nullptr} could be inserted into the
+  // users map without violating ordering.
+  IdToUsersMap::const_iterator UsersBegin(const Instruction* def) const;
 
-  DefUseManager();
+  // Returns true if |iter| has not reached the end of |def|'s users.
+  //
+  // In the first version |iter| is compared against the end of the map for
+  // validity before other checks. In the second version, |iter| is compared
+  // against |cached_end| for validity before other checks. This allows caching
+  // the map's end which is a performance improvement on some platforms.
+  bool UsersNotEnd(const IdToUsersMap::const_iterator& iter,
+                   const Instruction* def) const;
+  bool UsersNotEnd(const IdToUsersMap::const_iterator& iter,
+                   const IdToUsersMap::const_iterator& cached_end,
+                   const Instruction* def) const;
 
   // Analyzes the defs and uses in the given |module| and populates data
   // structures in this class. Does nothing if |module| is nullptr.
   void AnalyzeDefUse(Module* module);
 
-  // Removes unused entries in used_records_ and used_ids_.
-  void CompactUseRecords();
-  void CompactUsedIds();
-
-  IdToDefMap id_to_def_;          // Mapping from ids to their definitions
-  InstToUsersMap inst_to_users_;  // Map from def to uses.
-  std::unique_ptr<UseListPool> use_pool_;
-
-  std::unique_ptr<UsedIdListPool> used_id_pool_;
-  InstToUsedIdMap inst_to_used_id_;  // Map from instruction to used ids.
+  IdToDefMap id_to_def_;      // Mapping from ids to their definitions
+  IdToUsersMap id_to_users_;  // Mapping from ids to their users
+  // Mapping from instructions to the ids used in the instruction.
+  InstToUsedIdsMap inst_to_used_ids_;
 };
 
 }  // namespace analysis
diff --git a/source/opt/dominator_tree.cpp b/source/opt/dominator_tree.cpp
index d86de15..d6017bb 100644
--- a/source/opt/dominator_tree.cpp
+++ b/source/opt/dominator_tree.cpp
@@ -59,7 +59,9 @@
                              PreLambda pre, PostLambda post) {
   // Ignore backedge operation.
   auto nop_backedge = [](const BBType*, const BBType*) {};
-  CFA<BBType>::DepthFirstTraversal(bb, successors, pre, post, nop_backedge);
+  auto no_terminal_blocks = [](const BBType*) { return false; };
+  CFA<BBType>::DepthFirstTraversal(bb, successors, pre, post, nop_backedge,
+                                   no_terminal_blocks);
 }
 
 // Wrapper around CFA::DepthFirstTraversal to provide an interface to perform
diff --git a/source/opt/folding_rules.cpp b/source/opt/folding_rules.cpp
index 0d8f7c8..2d778b9 100644
--- a/source/opt/folding_rules.cpp
+++ b/source/opt/folding_rules.cpp
@@ -277,6 +277,11 @@
   uint32_t width = c->type()->AsFloat()->width();
   assert(width == 32 || width == 64);
   std::vector<uint32_t> words;
+
+  if (c->IsZero()) {
+    return 0;
+  }
+
   if (width == 64) {
     spvtools::utils::FloatProxy<double> result(1.0 / c->GetDouble());
     if (!IsValidResult(result.getAsFloat())) return 0;
diff --git a/source/opt/if_conversion.cpp b/source/opt/if_conversion.cpp
index d1debd0..1232796 100644
--- a/source/opt/if_conversion.cpp
+++ b/source/opt/if_conversion.cpp
@@ -160,6 +160,11 @@
   BasicBlock* inc1 = context()->get_instr_block(preds[1]);
   if (dominators->Dominates(block, inc1)) return false;
 
+  if (inc0 == inc1) {
+    // If the predecessor blocks are the same, then there is only 1 value for
+    // the OpPhi.  Other transformation should be able to simplify that.
+    return false;
+  }
   // All phis will have the same common dominator, so cache the result
   // for this block. If there is no common dominator, then we cannot transform
   // any phi in this basic block.
diff --git a/source/opt/inline_pass.cpp b/source/opt/inline_pass.cpp
index 2cc3125..6e73f1c 100644
--- a/source/opt/inline_pass.cpp
+++ b/source/opt/inline_pass.cpp
@@ -508,6 +508,37 @@
   delete &*loop_merge_itr;
 }
 
+void InlinePass::UpdateSingleBlockLoopContinueTarget(
+    uint32_t new_id, std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
+  auto& header = new_blocks->front();
+  auto* merge_inst = header->GetLoopMergeInst();
+
+  // The back-edge block is split at the branch to create a new back-edge
+  // block. The old block is modified to branch to the new block. The loop
+  // merge instruction is updated to declare the new block as the continue
+  // target. This has the effect of changing the loop from being a large
+  // continue construct and an empty loop construct to being a loop with a loop
+  // construct and a trivial continue construct. This change is made to satisfy
+  // structural dominance.
+
+  // Add the new basic block.
+  std::unique_ptr<BasicBlock> new_block =
+      MakeUnique<BasicBlock>(NewLabel(new_id));
+  auto& old_backedge = new_blocks->back();
+  auto old_branch = old_backedge->tail();
+
+  // Move the old back edge into the new block.
+  std::unique_ptr<Instruction> br(&*old_branch);
+  new_block->AddInstruction(std::move(br));
+
+  // Add a branch to the new block from the old back-edge block.
+  AddBranch(new_id, &old_backedge);
+  new_blocks->push_back(std::move(new_block));
+
+  // Update the loop's continue target to the new block.
+  merge_inst->SetInOperand(1u, {new_id});
+}
+
 bool InlinePass::GenInlineCode(
     std::vector<std::unique_ptr<BasicBlock>>* new_blocks,
     std::vector<std::unique_ptr<Instruction>>* new_vars,
@@ -639,9 +670,19 @@
   // Finalize inline code.
   new_blocks->push_back(std::move(new_blk_ptr));
 
-  if (caller_is_loop_header && (new_blocks->size() > 1))
+  if (caller_is_loop_header && (new_blocks->size() > 1)) {
     MoveLoopMergeInstToFirstBlock(new_blocks);
 
+    // If the loop was a single basic block previously, update it's structure.
+    auto& header = new_blocks->front();
+    auto* merge_inst = header->GetLoopMergeInst();
+    if (merge_inst->GetSingleWordInOperand(1u) == header->id()) {
+      auto new_id = context()->TakeNextId();
+      if (new_id == 0) return false;
+      UpdateSingleBlockLoopContinueTarget(new_id, new_blocks);
+    }
+  }
+
   // Update block map given replacement blocks.
   for (auto& blk : *new_blocks) {
     id2block_[blk->id()] = &*blk;
diff --git a/source/opt/inline_pass.h b/source/opt/inline_pass.h
index 9a5429b..f204395 100644
--- a/source/opt/inline_pass.h
+++ b/source/opt/inline_pass.h
@@ -235,6 +235,12 @@
   // Move the OpLoopMerge from the last block back to the first.
   void MoveLoopMergeInstToFirstBlock(
       std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
+
+  // Update the structure of single block loops so that the inlined code ends
+  // up in the loop construct and a new continue target is added to satisfy
+  // structural dominance.
+  void UpdateSingleBlockLoopContinueTarget(
+      uint32_t new_id, std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
 };
 
 }  // namespace opt
diff --git a/source/opt/instruction.cpp b/source/opt/instruction.cpp
index 418f121..6a8daea 100644
--- a/source/opt/instruction.cpp
+++ b/source/opt/instruction.cpp
@@ -693,8 +693,12 @@
     return NonSemanticShaderDebugInfo100InstructionsMax;
   }
 
-  return NonSemanticShaderDebugInfo100Instructions(
-      GetSingleWordInOperand(kExtInstInstructionInIdx));
+  uint32_t opcode = GetSingleWordInOperand(kExtInstInstructionInIdx);
+  if (opcode >= NonSemanticShaderDebugInfo100InstructionsMax) {
+    return NonSemanticShaderDebugInfo100InstructionsMax;
+  }
+
+  return NonSemanticShaderDebugInfo100Instructions(opcode);
 }
 
 CommonDebugInfoInstructions Instruction::GetCommonDebugOpcode() const {
diff --git a/source/opt/interface_var_sroa.cpp b/source/opt/interface_var_sroa.cpp
index 58ed897..1b2cb36 100644
--- a/source/opt/interface_var_sroa.cpp
+++ b/source/opt/interface_var_sroa.cpp
@@ -212,8 +212,12 @@
     context()->KillInst(inst);
     return;
   }
+  std::vector<Instruction*> users;
   context()->get_def_use_mgr()->ForEachUser(
-      inst, [this](Instruction* user) { KillInstructionAndUsers(user); });
+      inst, [&users](Instruction* user) { users.push_back(user); });
+  for (auto user : users) {
+    context()->KillInst(user);
+  }
   context()->KillInst(inst);
 }
 
diff --git a/source/opt/ir_loader.cpp b/source/opt/ir_loader.cpp
index 97db9d8..734ad55 100644
--- a/source/opt/ir_loader.cpp
+++ b/source/opt/ir_loader.cpp
@@ -187,6 +187,8 @@
         module_->AddExtInstImport(std::move(spv_inst));
       } else if (opcode == SpvOpMemoryModel) {
         module_->SetMemoryModel(std::move(spv_inst));
+      } else if (opcode == SpvOpSamplerImageAddressingModeNV) {
+        module_->SetSampledImageAddressMode(std::move(spv_inst));
       } else if (opcode == SpvOpEntryPoint) {
         module_->AddEntryPoint(std::move(spv_inst));
       } else if (opcode == SpvOpExecutionMode ||
diff --git a/source/opt/local_access_chain_convert_pass.cpp b/source/opt/local_access_chain_convert_pass.cpp
index da4cac3..9491798 100644
--- a/source/opt/local_access_chain_convert_pass.cpp
+++ b/source/opt/local_access_chain_convert_pass.cpp
@@ -237,7 +237,8 @@
           }
           // Rule out variables with nested access chains
           // TODO(): Convert nested access chains
-          if (IsNonPtrAccessChain(op) && ptrInst->GetSingleWordInOperand(
+          bool is_non_ptr_access_chain = IsNonPtrAccessChain(op);
+          if (is_non_ptr_access_chain && ptrInst->GetSingleWordInOperand(
                                              kAccessChainPtrIdInIdx) != varId) {
             seen_non_target_vars_.insert(varId);
             seen_target_vars_.erase(varId);
@@ -249,6 +250,12 @@
             seen_target_vars_.erase(varId);
             break;
           }
+
+          if (is_non_ptr_access_chain && AnyIndexIsOutOfBounds(ptrInst)) {
+            seen_non_target_vars_.insert(varId);
+            seen_target_vars_.erase(varId);
+            break;
+          }
         } break;
         default:
           break;
@@ -446,5 +453,42 @@
   });
 }
 
+bool LocalAccessChainConvertPass::AnyIndexIsOutOfBounds(
+    const Instruction* access_chain_inst) {
+  assert(IsNonPtrAccessChain(access_chain_inst->opcode()));
+
+  analysis::TypeManager* type_mgr = context()->get_type_mgr();
+  analysis::ConstantManager* const_mgr = context()->get_constant_mgr();
+  auto constants = const_mgr->GetOperandConstants(access_chain_inst);
+  uint32_t base_pointer_id = access_chain_inst->GetSingleWordInOperand(0);
+  Instruction* base_pointer = get_def_use_mgr()->GetDef(base_pointer_id);
+  const analysis::Pointer* base_pointer_type =
+      type_mgr->GetType(base_pointer->type_id())->AsPointer();
+  assert(base_pointer_type != nullptr &&
+         "The base of the access chain is not a pointer.");
+  const analysis::Type* current_type = base_pointer_type->pointee_type();
+  for (uint32_t i = 1; i < access_chain_inst->NumInOperands(); ++i) {
+    if (IsIndexOutOfBounds(constants[i], current_type)) {
+      return true;
+    }
+
+    uint32_t index =
+        (constants[i]
+             ? static_cast<uint32_t>(constants[i]->GetZeroExtendedValue())
+             : 0);
+    current_type = type_mgr->GetMemberType(current_type, {index});
+  }
+
+  return false;
+}
+
+bool LocalAccessChainConvertPass::IsIndexOutOfBounds(
+    const analysis::Constant* index, const analysis::Type* type) const {
+  if (index == nullptr) {
+    return false;
+  }
+  return index->GetZeroExtendedValue() >= type->NumberOfComponents();
+}
+
 }  // namespace opt
 }  // namespace spvtools
diff --git a/source/opt/local_access_chain_convert_pass.h b/source/opt/local_access_chain_convert_pass.h
index 8548e16..eabf864 100644
--- a/source/opt/local_access_chain_convert_pass.h
+++ b/source/opt/local_access_chain_convert_pass.h
@@ -111,6 +111,17 @@
   // Returns a status to indicate success or failure, and change or no change.
   Status ConvertLocalAccessChains(Function* func);
 
+  // Returns true one of the indexes in the |access_chain_inst| is definitly out
+  // of bounds.  If the size of the type or the value of the index is unknown,
+  // then it will be considered in-bounds.
+  bool AnyIndexIsOutOfBounds(const Instruction* access_chain_inst);
+
+  // Returns true if getting element |index| from |type| would be out-of-bounds.
+  // If |index| is nullptr or the size of the type are unknown, then it will be
+  // considered in-bounds.
+  bool IsIndexOutOfBounds(const analysis::Constant* index,
+                          const analysis::Type* type) const;
+
   // Initialize extensions allowlist
   void InitExtensions();
 
diff --git a/source/opt/loop_descriptor.cpp b/source/opt/loop_descriptor.cpp
index 4feb64e..13982d1 100644
--- a/source/opt/loop_descriptor.cpp
+++ b/source/opt/loop_descriptor.cpp
@@ -497,7 +497,8 @@
     // continue blocks that must be copied to retain the structured order.
     // The structured order will include these.
     std::list<BasicBlock*> order;
-    cfg.ComputeStructuredOrder(loop_header_->GetParent(), loop_header_, &order);
+    cfg.ComputeStructuredOrder(loop_header_->GetParent(), loop_header_,
+                               loop_merge_, &order);
     for (BasicBlock* bb : order) {
       if (bb == GetMergeBlock()) {
         break;
diff --git a/source/opt/loop_unroller.cpp b/source/opt/loop_unroller.cpp
index 28ff072..6f4e6f4 100644
--- a/source/opt/loop_unroller.cpp
+++ b/source/opt/loop_unroller.cpp
@@ -384,6 +384,7 @@
   std::unique_ptr<Instruction> new_label{new Instruction(
       context_, SpvOp::SpvOpLabel, 0, context_->TakeNextId(), {})};
   std::unique_ptr<BasicBlock> new_exit_bb{new BasicBlock(std::move(new_label))};
+  new_exit_bb->SetParent(&function_);
 
   // Save the id of the block before we move it.
   uint32_t new_merge_id = new_exit_bb->id();
@@ -996,6 +997,20 @@
   if (!loop_->FindNumberOfIterations(induction, &*condition->ctail(), nullptr))
     return false;
 
+#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
+  // ClusterFuzz/OSS-Fuzz is likely to yield examples with very high loop
+  // iteration counts. This can cause timeouts and memouts during fuzzing that
+  // are not classed as bugs. To avoid this noise, loop unrolling is not applied
+  // to loops with large iteration counts when fuzzing.
+  const size_t kFuzzerIterationLimit = 100;
+  size_t num_iterations;
+  loop_->FindNumberOfIterations(induction, &*condition->ctail(),
+                                &num_iterations);
+  if (num_iterations > kFuzzerIterationLimit) {
+    return false;
+  }
+#endif
+
   // Make sure the latch block is a unconditional branch to the header
   // block.
   const Instruction& branch = *loop_->GetLatchBlock()->ctail();
diff --git a/source/opt/merge_return_pass.h b/source/opt/merge_return_pass.h
index a35cf26..d15db2f 100644
--- a/source/opt/merge_return_pass.h
+++ b/source/opt/merge_return_pass.h
@@ -118,8 +118,6 @@
     StructuredControlState(Instruction* break_merge, Instruction* merge)
         : break_merge_(break_merge), current_merge_(merge) {}
 
-    StructuredControlState(const StructuredControlState&) = default;
-
     bool InBreakable() const { return break_merge_; }
     bool InStructuredFlow() const { return CurrentMergeId() != 0; }
 
diff --git a/source/opt/module.cpp b/source/opt/module.cpp
index 5983abb..c98af8f 100644
--- a/source/opt/module.cpp
+++ b/source/opt/module.cpp
@@ -90,6 +90,8 @@
   DELEGATE(extensions_);
   DELEGATE(ext_inst_imports_);
   if (memory_model_) memory_model_->ForEachInst(f, run_on_debug_line_insts);
+  if (sampled_image_address_mode_)
+    sampled_image_address_mode_->ForEachInst(f, run_on_debug_line_insts);
   DELEGATE(entry_points_);
   DELEGATE(execution_modes_);
   DELEGATE(debugs1_);
@@ -114,6 +116,9 @@
   if (memory_model_)
     static_cast<const Instruction*>(memory_model_.get())
         ->ForEachInst(f, run_on_debug_line_insts);
+  if (sampled_image_address_mode_)
+    static_cast<const Instruction*>(sampled_image_address_mode_.get())
+        ->ForEachInst(f, run_on_debug_line_insts);
   for (auto& i : entry_points_) DELEGATE(i);
   for (auto& i : execution_modes_) DELEGATE(i);
   for (auto& i : debugs1_) DELEGATE(i);
diff --git a/source/opt/module.h b/source/opt/module.h
index 230be70..7a6be46 100644
--- a/source/opt/module.h
+++ b/source/opt/module.h
@@ -83,6 +83,9 @@
   // Set the memory model for this module.
   inline void SetMemoryModel(std::unique_ptr<Instruction> m);
 
+  // Set the sampled image addressing mode for this module.
+  inline void SetSampledImageAddressMode(std::unique_ptr<Instruction> m);
+
   // Appends an entry point instruction to this module.
   inline void AddEntryPoint(std::unique_ptr<Instruction> e);
 
@@ -158,12 +161,20 @@
   inline IteratorRange<inst_iterator> ext_inst_imports();
   inline IteratorRange<const_inst_iterator> ext_inst_imports() const;
 
-  // Return the memory model instruction contained inthis module.
+  // Return the memory model instruction contained in this module.
   inline Instruction* GetMemoryModel() { return memory_model_.get(); }
   inline const Instruction* GetMemoryModel() const {
     return memory_model_.get();
   }
 
+  // Return the sampled image address mode instruction contained in this module.
+  inline Instruction* GetSampledImageAddressMode() {
+    return sampled_image_address_mode_.get();
+  }
+  inline const Instruction* GetSampledImageAddressMode() const {
+    return sampled_image_address_mode_.get();
+  }
+
   // There are several kinds of debug instructions, according to where they can
   // appear in the logical layout of a module:
   //  - Section 7a:  OpString, OpSourceExtension, OpSource, OpSourceContinued
@@ -288,6 +299,8 @@
   InstructionList ext_inst_imports_;
   // A module only has one memory model instruction.
   std::unique_ptr<Instruction> memory_model_;
+  // A module can only have one optional sampled image addressing mode
+  std::unique_ptr<Instruction> sampled_image_address_mode_;
   InstructionList entry_points_;
   InstructionList execution_modes_;
   InstructionList debugs1_;
@@ -326,6 +339,10 @@
   memory_model_ = std::move(m);
 }
 
+inline void Module::SetSampledImageAddressMode(std::unique_ptr<Instruction> m) {
+  sampled_image_address_mode_ = std::move(m);
+}
+
 inline void Module::AddEntryPoint(std::unique_ptr<Instruction> e) {
   entry_points_.push_back(std::move(e));
 }
diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp
index 2976151..381589b 100644
--- a/source/opt/optimizer.cpp
+++ b/source/opt/optimizer.cpp
@@ -623,10 +623,16 @@
     assert(optimized_binary_with_nop.size() == original_binary_size &&
            "Binary size unexpectedly changed despite the optimizer saying "
            "there was no change");
-    assert(memcmp(optimized_binary_with_nop.data(), original_binary,
-                  original_binary_size) == 0 &&
-           "Binary content unexpectedly changed despite the optimizer saying "
-           "there was no change");
+
+    // Compare the magic number to make sure the binaries were encoded in the
+    // endianness.  If not, the contents of the binaries will be different, so
+    // do not check the contents.
+    if (optimized_binary_with_nop[0] == original_binary[0]) {
+      assert(memcmp(optimized_binary_with_nop.data(), original_binary,
+                    original_binary_size) == 0 &&
+             "Binary content unexpectedly changed despite the optimizer saying "
+             "there was no change");
+    }
   }
 #endif  // !NDEBUG
 
diff --git a/source/opt/reduce_load_size.cpp b/source/opt/reduce_load_size.cpp
index e9b8087..56491b2 100644
--- a/source/opt/reduce_load_size.cpp
+++ b/source/opt/reduce_load_size.cpp
@@ -161,8 +161,15 @@
       case analysis::Type::kArray: {
         const analysis::Constant* size_const =
             const_mgr->FindDeclaredConstant(load_type->AsArray()->LengthId());
-        assert(size_const->AsIntConstant());
-        total_size = size_const->GetU32();
+
+        if (size_const) {
+          assert(size_const->AsIntConstant());
+          total_size = size_const->GetU32();
+        } else {
+          // The size is spec constant, so it is unknown at this time.  Assume
+          // it is very large.
+          total_size = UINT32_MAX;
+        }
       } break;
       case analysis::Type::kStruct:
         total_size = static_cast<uint32_t>(
diff --git a/source/opt/scalar_replacement_pass.h b/source/opt/scalar_replacement_pass.h
index 76afc26..6a66dfb 100644
--- a/source/opt/scalar_replacement_pass.h
+++ b/source/opt/scalar_replacement_pass.h
@@ -15,6 +15,7 @@
 #ifndef SOURCE_OPT_SCALAR_REPLACEMENT_PASS_H_
 #define SOURCE_OPT_SCALAR_REPLACEMENT_PASS_H_
 
+#include <cassert>
 #include <cstdio>
 #include <memory>
 #include <queue>
@@ -37,9 +38,20 @@
  public:
   ScalarReplacementPass(uint32_t limit = kDefaultLimit)
       : max_num_elements_(limit) {
-    name_[0] = '\0';
-    strcat(name_, "scalar-replacement=");
-    sprintf(&name_[strlen(name_)], "%d", max_num_elements_);
+    const auto num_to_write = snprintf(
+        name_, sizeof(name_), "scalar-replacement=%u", max_num_elements_);
+    assert(size_t(num_to_write) < sizeof(name_));
+    (void)num_to_write;  // Mark as unused
+
+#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
+    // ClusterFuzz/OSS-Fuzz is likely to yield examples with very large arrays.
+    // This can cause timeouts and memouts during fuzzing that
+    // are not classed as bugs. To avoid this noise, we set the
+    // max_num_elements_ to a smaller value for fuzzing.
+    max_num_elements_ =
+        (max_num_elements_ > 0 && max_num_elements_ < 100 ? max_num_elements_
+                                                          : 100);
+#endif
   }
 
   const char* name() const override { return name_; }
@@ -253,7 +265,10 @@
   // Limit on the number of members in an object that will be replaced.
   // 0 means there is no limit.
   uint32_t max_num_elements_;
-  char name_[55];
+  // This has to be big enough to fit "scalar-replacement=" followed by a
+  // uint32_t number written in decimal (so 10 digits), and then a
+  // terminating nul.
+  char name_[30];
 };
 
 }  // namespace opt
diff --git a/source/opt/types.cpp b/source/opt/types.cpp
index ebbdc36..056aceb 100644
--- a/source/opt/types.cpp
+++ b/source/opt/types.cpp
@@ -16,6 +16,7 @@
 
 #include <algorithm>
 #include <cassert>
+#include <climits>
 #include <cstdint>
 #include <sstream>
 #include <string>
@@ -246,6 +247,35 @@
   return ComputeHashValue(0, &seen);
 }
 
+uint64_t Type::NumberOfComponents() const {
+  switch (kind()) {
+    case kVector:
+      return AsVector()->element_count();
+    case kMatrix:
+      return AsMatrix()->element_count();
+    case kArray: {
+      Array::LengthInfo length_info = AsArray()->length_info();
+      if (length_info.words[0] != Array::LengthInfo::kConstant) {
+        return UINT64_MAX;
+      }
+      assert(length_info.words.size() <= 3 &&
+             "The size of the array could not fit size_t.");
+      uint64_t length = 0;
+      length |= length_info.words[1];
+      if (length_info.words.size() > 2) {
+        length |= static_cast<uint64_t>(length_info.words[2]) << 32;
+      }
+      return length;
+    }
+    case kRuntimeArray:
+      return UINT64_MAX;
+    case kStruct:
+      return AsStruct()->element_types().size();
+    default:
+      return 0;
+  }
+}
+
 bool Integer::IsSameImpl(const Type* that, IsSameCache*) const {
   const Integer* it = that->AsInteger();
   return it && width_ == it->width_ && signed_ == it->signed_ &&
diff --git a/source/opt/types.h b/source/opt/types.h
index f5a4a6b..a92669e 100644
--- a/source/opt/types.h
+++ b/source/opt/types.h
@@ -160,6 +160,10 @@
 
   size_t ComputeHashValue(size_t hash, SeenTypes* seen) const;
 
+  // Returns the number of components in a composite type.  Returns 0 for a
+  // non-composite type.
+  uint64_t NumberOfComponents() const;
+
 // A bunch of methods for casting this type to a given type. Returns this if the
 // cast can be done, nullptr otherwise.
 // clang-format off
diff --git a/source/text_handler.cpp b/source/text_handler.cpp
index fe12a26..15c1741 100644
--- a/source/text_handler.cpp
+++ b/source/text_handler.cpp
@@ -62,28 +62,29 @@
 // parameters, its the users responsibility to ensure these are non null.
 spv_result_t advance(spv_text text, spv_position position) {
   // NOTE: Consume white space, otherwise don't advance.
-  if (position->index >= text->length) return SPV_END_OF_STREAM;
-  switch (text->str[position->index]) {
-    case '\0':
-      return SPV_END_OF_STREAM;
-    case ';':
-      if (spv_result_t error = advanceLine(text, position)) return error;
-      return advance(text, position);
-    case ' ':
-    case '\t':
-    case '\r':
-      position->column++;
-      position->index++;
-      return advance(text, position);
-    case '\n':
-      position->column = 0;
-      position->line++;
-      position->index++;
-      return advance(text, position);
-    default:
-      break;
+  while (true) {
+    if (position->index >= text->length) return SPV_END_OF_STREAM;
+    switch (text->str[position->index]) {
+      case '\0':
+        return SPV_END_OF_STREAM;
+      case ';':
+        if (spv_result_t error = advanceLine(text, position)) return error;
+        continue;
+      case ' ':
+      case '\t':
+      case '\r':
+        position->column++;
+        position->index++;
+        continue;
+      case '\n':
+        position->column = 0;
+        position->line++;
+        position->index++;
+        continue;
+      default:
+        return SPV_SUCCESS;
+    }
   }
-  return SPV_SUCCESS;
 }
 
 // Fetches the next word from the given text stream starting from the given
diff --git a/source/util/hex_float.h b/source/util/hex_float.h
index 903b628..06e3c57 100644
--- a/source/util/hex_float.h
+++ b/source/util/hex_float.h
@@ -209,9 +209,10 @@
 // be the default for any non-specialized type.
 template <typename T>
 struct HexFloatTraits {
-  // Integer type that can store this hex-float.
+  // Integer type that can store the bit representation of this hex-float.
   using uint_type = void;
-  // Signed integer type that can store this hex-float.
+  // Signed integer type that can store the bit representation of this
+  // hex-float.
   using int_type = void;
   // The numerical type that this HexFloat represents.
   using underlying_type = void;
@@ -958,9 +959,15 @@
   // This "looks" like a hex-float so treat it as one.
   bool seen_p = false;
   bool seen_dot = false;
+
+  // The mantissa bits, without the most significant 1 bit, and with the
+  // the most recently read bits in the least significant positions.
+  uint_type fraction = 0;
+  // The number of mantissa bits that have been read, including the leading 1
+  // bit that is not written into 'fraction'.
   uint_type fraction_index = 0;
 
-  uint_type fraction = 0;
+  // TODO(dneto): handle overflow and underflow
   int_type exponent = HF::exponent_bias;
 
   // Strip off leading zeros so we don't have to special-case them later.
@@ -968,11 +975,13 @@
     is.get();
   }
 
-  bool is_denorm =
-      true;  // Assume denorm "representation" until we hear otherwise.
-             // NB: This does not mean the value is actually denorm,
-             // it just means that it was written 0.
+  // Does the mantissa, as written, have non-zero digits to the left of
+  // the decimal point.  Assume no until proven otherwise.
+  bool has_integer_part = false;
   bool bits_written = false;  // Stays false until we write a bit.
+
+  // Scan the mantissa hex digits until we see a '.' or the 'p' that
+  // starts the exponent.
   while (!seen_p && !seen_dot) {
     // Handle characters that are left of the fractional part.
     if (next_char == '.') {
@@ -980,9 +989,8 @@
     } else if (next_char == 'p') {
       seen_p = true;
     } else if (::isxdigit(next_char)) {
-      // We know this is not denormalized since we have stripped all leading
-      // zeroes and we are not a ".".
-      is_denorm = false;
+      // We have stripped all leading zeroes and we have not yet seen a ".".
+      has_integer_part = true;
       int number = get_nibble_from_character(next_char);
       for (int i = 0; i < 4; ++i, number <<= 1) {
         uint_type write_bit = (number & 0x8) ? 0x1 : 0x0;
@@ -993,8 +1001,12 @@
               fraction |
               static_cast<uint_type>(
                   write_bit << (HF::top_bit_left_shift - fraction_index++)));
+          // TODO(dneto): Avoid overflow. Testing would require
+          // parameterization.
           exponent = static_cast<int_type>(exponent + 1);
         }
+        // Since this updated after setting fraction bits, this effectively
+        // drops the leading 1 bit.
         bits_written |= write_bit != 0;
       }
     } else {
@@ -1018,10 +1030,12 @@
       for (int i = 0; i < 4; ++i, number <<= 1) {
         uint_type write_bit = (number & 0x8) ? 0x01 : 0x00;
         bits_written |= write_bit != 0;
-        if (is_denorm && !bits_written) {
+        if ((!has_integer_part) && !bits_written) {
           // Handle modifying the exponent here this way we can handle
           // an arbitrary number of hex values without overflowing our
           // integer.
+          // TODO(dneto): Handle underflow. Testing would require extra
+          // parameterization.
           exponent = static_cast<int_type>(exponent - 1);
         } else {
           fraction = static_cast<uint_type>(
@@ -1043,25 +1057,40 @@
   // Finished reading the part preceding 'p'.
   // In hex floats syntax, the binary exponent is required.
 
-  bool seen_sign = false;
+  bool seen_exponent_sign = false;
   int8_t exponent_sign = 1;
   bool seen_written_exponent_digits = false;
+  // The magnitude of the exponent, as written, or the sentinel value to signal
+  // overflow.
   int_type written_exponent = 0;
+  // A sentinel value signalling overflow of the magnitude of the written
+  // exponent.  We'll assume that -written_exponent_overflow is valid for the
+  // type. Later we may add 1 or subtract 1 from the adjusted exponent, so leave
+  // room for an extra 1.
+  const int_type written_exponent_overflow =
+      std::numeric_limits<int_type>::max() - 1;
   while (true) {
     if (!seen_written_exponent_digits &&
         (next_char == '-' || next_char == '+')) {
-      if (seen_sign) {
+      if (seen_exponent_sign) {
         is.setstate(std::ios::failbit);
         return is;
       }
-      seen_sign = true;
+      seen_exponent_sign = true;
       exponent_sign = (next_char == '-') ? -1 : 1;
     } else if (::isdigit(next_char)) {
       seen_written_exponent_digits = true;
       // Hex-floats express their exponent as decimal.
-      written_exponent = static_cast<int_type>(written_exponent * 10);
-      written_exponent =
-          static_cast<int_type>(written_exponent + (next_char - '0'));
+      int_type digit =
+          static_cast<int_type>(static_cast<int_type>(next_char) - '0');
+      if (written_exponent >= (written_exponent_overflow - digit) / 10) {
+        // The exponent is very big. Saturate rather than overflow the exponent.
+        // signed integer, which would be undefined behaviour.
+        written_exponent = written_exponent_overflow;
+      } else {
+        written_exponent = static_cast<int_type>(
+            static_cast<int_type>(written_exponent * 10) + digit);
+      }
     } else {
       break;
     }
@@ -1075,10 +1104,29 @@
   }
 
   written_exponent = static_cast<int_type>(written_exponent * exponent_sign);
-  exponent = static_cast<int_type>(exponent + written_exponent);
+  // Now fold in the exponent bias into the written exponent, updating exponent.
+  // But avoid undefined behaviour that would result from overflowing int_type.
+  if (written_exponent >= 0 && exponent >= 0) {
+    // Saturate up to written_exponent_overflow.
+    if (written_exponent_overflow - exponent > written_exponent) {
+      exponent = static_cast<int_type>(written_exponent + exponent);
+    } else {
+      exponent = written_exponent_overflow;
+    }
+  } else if (written_exponent < 0 && exponent < 0) {
+    // Saturate down to -written_exponent_overflow.
+    if (written_exponent_overflow + exponent > -written_exponent) {
+      exponent = static_cast<int_type>(written_exponent + exponent);
+    } else {
+      exponent = static_cast<int_type>(-written_exponent_overflow);
+    }
+  } else {
+    // They're of opposing sign, so it's safe to add.
+    exponent = static_cast<int_type>(written_exponent + exponent);
+  }
 
-  bool is_zero = is_denorm && (fraction == 0);
-  if (is_denorm && !is_zero) {
+  bool is_zero = (!has_integer_part) && (fraction == 0);
+  if ((!has_integer_part) && !is_zero) {
     fraction = static_cast<uint_type>(fraction << 1);
     exponent = static_cast<int_type>(exponent - 1);
   } else if (is_zero) {
@@ -1095,7 +1143,7 @@
   const int_type max_exponent =
       SetBits<uint_type, 0, HF::num_exponent_bits>::get;
 
-  // Handle actual denorm numbers
+  // Handle denorm numbers
   while (exponent < 0 && !is_zero) {
     fraction = static_cast<uint_type>(fraction >> 1);
     exponent = static_cast<int_type>(exponent + 1);
diff --git a/source/util/pooled_linked_list.h b/source/util/pooled_linked_list.h
deleted file mode 100644
index faaa4c4..0000000
--- a/source/util/pooled_linked_list.h
+++ /dev/null
@@ -1,236 +0,0 @@
-// Copyright (c) 2021 The Khronos Group Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SOURCE_UTIL_POOLED_LINKED_LIST_H_
-#define SOURCE_UTIL_POOLED_LINKED_LIST_H_
-
-#include <cstdint>
-#include <vector>
-
-namespace spvtools {
-namespace utils {
-
-// Shared storage of nodes for PooledLinkedList.
-template <typename T>
-class PooledLinkedListNodes {
- public:
-  struct Node {
-    Node(T e, int32_t n = -1) : element(e), next(n) {}
-
-    T element = {};
-    int32_t next = -1;
-  };
-
-  PooledLinkedListNodes() = default;
-  PooledLinkedListNodes(const PooledLinkedListNodes&) = delete;
-  PooledLinkedListNodes& operator=(const PooledLinkedListNodes&) = delete;
-
-  PooledLinkedListNodes(PooledLinkedListNodes&& that) {
-    *this = std::move(that);
-  }
-
-  PooledLinkedListNodes& operator=(PooledLinkedListNodes&& that) {
-    vec_ = std::move(that.vec_);
-    free_nodes_ = that.free_nodes_;
-    return *this;
-  }
-
-  size_t total_nodes() { return vec_.size(); }
-  size_t free_nodes() { return free_nodes_; }
-  size_t used_nodes() { return total_nodes() - free_nodes(); }
-
- private:
-  template <typename ListT>
-  friend class PooledLinkedList;
-
-  Node& at(int32_t index) { return vec_[index]; }
-  const Node& at(int32_t index) const { return vec_[index]; }
-
-  int32_t insert(T element) {
-    int32_t index = int32_t(vec_.size());
-    vec_.emplace_back(element);
-    return index;
-  }
-
-  std::vector<Node> vec_;
-  size_t free_nodes_ = 0;
-};
-
-// Implements a linked-list where list nodes come from a shared pool. This is
-// meant to be used in scenarios where it is desirable to avoid many small
-// allocations.
-//
-// Instead of pointers, the list uses indices to allow the underlying storage
-// to be modified without needing to modify the list. When removing elements
-// from the list, nodes are not deleted or recycled: to reclaim unused space,
-// perform a sequence of |move_nodes| operations into a temporary pool, which
-// then is moved into the old pool.
-//
-// This does *not* attempt to implement a full stl-compatible interface.
-template <typename T>
-class PooledLinkedList {
- public:
-  using NodePool = PooledLinkedListNodes<T>;
-  using Node = typename NodePool::Node;
-
-  PooledLinkedList() = delete;
-  PooledLinkedList(NodePool* nodes) : nodes_(nodes) {}
-
-  // Shared iterator implementation (for iterator and const_iterator).
-  template <typename ElementT, typename PoolT>
-  class iterator_base {
-   public:
-    iterator_base(const iterator_base& i)
-        : nodes_(i.nodes_), index_(i.index_) {}
-
-    iterator_base& operator++() {
-      index_ = nodes_->at(index_).next;
-      return *this;
-    }
-
-    iterator_base& operator=(const iterator_base& i) {
-      nodes_ = i.nodes_;
-      index_ = i.index_;
-      return *this;
-    }
-
-    ElementT& operator*() const { return nodes_->at(index_).element; }
-    ElementT* operator->() const { return &nodes_->at(index_).element; }
-
-    friend inline bool operator==(const iterator_base& lhs,
-                                  const iterator_base& rhs) {
-      return lhs.nodes_ == rhs.nodes_ && lhs.index_ == rhs.index_;
-    }
-    friend inline bool operator!=(const iterator_base& lhs,
-                                  const iterator_base& rhs) {
-      return lhs.nodes_ != rhs.nodes_ || lhs.index_ != rhs.index_;
-    }
-
-    // Define standard iterator types needs so this class can be
-    // used with <algorithms>.
-    using iterator_category = std::forward_iterator_tag;
-    using difference_type = std::ptrdiff_t;
-    using value_type = ElementT;
-    using pointer = ElementT*;
-    using const_pointer = const ElementT*;
-    using reference = ElementT&;
-    using const_reference = const ElementT&;
-    using size_type = size_t;
-
-   private:
-    friend PooledLinkedList;
-
-    iterator_base(PoolT* pool, int32_t index) : nodes_(pool), index_(index) {}
-
-    PoolT* nodes_;
-    int32_t index_ = -1;
-  };
-
-  using iterator = iterator_base<T, std::vector<Node>>;
-  using const_iterator = iterator_base<const T, const std::vector<Node>>;
-
-  bool empty() const { return head_ == -1; }
-
-  T& front() { return nodes_->at(head_).element; }
-  T& back() { return nodes_->at(tail_).element; }
-  const T& front() const { return nodes_->at(head_).element; }
-  const T& back() const { return nodes_->at(tail_).element; }
-
-  iterator begin() { return iterator(&nodes_->vec_, head_); }
-  iterator end() { return iterator(&nodes_->vec_, -1); }
-  const_iterator begin() const { return const_iterator(&nodes_->vec_, head_); }
-  const_iterator end() const { return const_iterator(&nodes_->vec_, -1); }
-
-  // Inserts |element| at the back of the list.
-  void push_back(T element) {
-    int32_t new_tail = nodes_->insert(element);
-    if (head_ == -1) {
-      head_ = new_tail;
-      tail_ = new_tail;
-    } else {
-      nodes_->at(tail_).next = new_tail;
-      tail_ = new_tail;
-    }
-  }
-
-  // Removes the first occurrence of |element| from the list.
-  // Returns if |element| was removed.
-  bool remove_first(T element) {
-    int32_t* prev_next = &head_;
-    for (int32_t prev_index = -1, index = head_; index != -1; /**/) {
-      auto& node = nodes_->at(index);
-      if (node.element == element) {
-        // Snip from of the list, optionally fixing up tail pointer.
-        if (tail_ == index) {
-          assert(node.next == -1);
-          tail_ = prev_index;
-        }
-        *prev_next = node.next;
-        nodes_->free_nodes_++;
-        return true;
-      } else {
-        prev_next = &node.next;
-      }
-      prev_index = index;
-      index = node.next;
-    }
-    return false;
-  }
-
-  // Returns the PooledLinkedListNodes that owns this list's nodes.
-  NodePool* pool() { return nodes_; }
-
-  // Moves the nodes in this list into |new_pool|, providing a way to compact
-  // storage and reclaim unused space.
-  //
-  // Upon completing a sequence of |move_nodes| calls, you must ensure you
-  // retain ownership of the new storage your lists point to. Example usage:
-  //
-  //    unique_ptr<NodePool> new_pool = ...;
-  //    for (PooledLinkedList& list : lists) {
-  //        list.move_to(new_pool);
-  //    }
-  //    my_pool_ = std::move(new_pool);
-  void move_nodes(NodePool* new_pool) {
-    // Be sure to construct the list in the same order, instead of simply
-    // doing a sequence of push_backs.
-    int32_t prev_entry = -1;
-    int32_t nodes_freed = 0;
-    for (int32_t index = head_; index != -1; nodes_freed++) {
-      const auto& node = nodes_->at(index);
-      int32_t this_entry = new_pool->insert(node.element);
-      index = node.next;
-      if (prev_entry == -1) {
-        head_ = this_entry;
-      } else {
-        new_pool->at(prev_entry).next = this_entry;
-      }
-      prev_entry = this_entry;
-    }
-    tail_ = prev_entry;
-    // Update our old pool's free count, now we're a member of the new pool.
-    nodes_->free_nodes_ += nodes_freed;
-    nodes_ = new_pool;
-  }
-
- private:
-  NodePool* nodes_;
-  int32_t head_ = -1;
-  int32_t tail_ = -1;
-};
-
-}  // namespace utils
-}  // namespace spvtools
-
-#endif  // SOURCE_UTIL_POOLED_LINKED_LIST_H_
\ No newline at end of file
diff --git a/source/val/basic_block.cpp b/source/val/basic_block.cpp
index b2a8793..da05db3 100644
--- a/source/val/basic_block.cpp
+++ b/source/val/basic_block.cpp
@@ -24,11 +24,13 @@
 BasicBlock::BasicBlock(uint32_t label_id)
     : id_(label_id),
       immediate_dominator_(nullptr),
-      immediate_post_dominator_(nullptr),
+      immediate_structural_dominator_(nullptr),
+      immediate_structural_post_dominator_(nullptr),
       predecessors_(),
       successors_(),
       type_(0),
       reachable_(false),
+      structurally_reachable_(false),
       label_(nullptr),
       terminator_(nullptr) {}
 
@@ -36,21 +38,32 @@
   immediate_dominator_ = dom_block;
 }
 
-void BasicBlock::SetImmediatePostDominator(BasicBlock* pdom_block) {
-  immediate_post_dominator_ = pdom_block;
+void BasicBlock::SetImmediateStructuralDominator(BasicBlock* dom_block) {
+  immediate_structural_dominator_ = dom_block;
+}
+
+void BasicBlock::SetImmediateStructuralPostDominator(BasicBlock* pdom_block) {
+  immediate_structural_post_dominator_ = pdom_block;
 }
 
 const BasicBlock* BasicBlock::immediate_dominator() const {
   return immediate_dominator_;
 }
 
-const BasicBlock* BasicBlock::immediate_post_dominator() const {
-  return immediate_post_dominator_;
+const BasicBlock* BasicBlock::immediate_structural_dominator() const {
+  return immediate_structural_dominator_;
+}
+
+const BasicBlock* BasicBlock::immediate_structural_post_dominator() const {
+  return immediate_structural_post_dominator_;
 }
 
 BasicBlock* BasicBlock::immediate_dominator() { return immediate_dominator_; }
-BasicBlock* BasicBlock::immediate_post_dominator() {
-  return immediate_post_dominator_;
+BasicBlock* BasicBlock::immediate_structural_dominator() {
+  return immediate_structural_dominator_;
+}
+BasicBlock* BasicBlock::immediate_structural_post_dominator() {
+  return immediate_structural_post_dominator_;
 }
 
 void BasicBlock::RegisterSuccessors(
@@ -58,6 +71,10 @@
   for (auto& block : next_blocks) {
     block->predecessors_.push_back(this);
     successors_.push_back(block);
+
+    // Register structural successors/predecessors too.
+    block->structural_predecessors_.push_back(this);
+    structural_successors_.push_back(block);
   }
 }
 
@@ -67,10 +84,16 @@
            std::find(other.dom_begin(), other.dom_end(), this));
 }
 
-bool BasicBlock::postdominates(const BasicBlock& other) const {
-  return (this == &other) ||
-         !(other.pdom_end() ==
-           std::find(other.pdom_begin(), other.pdom_end(), this));
+bool BasicBlock::structurally_dominates(const BasicBlock& other) const {
+  return (this == &other) || !(other.structural_dom_end() ==
+                               std::find(other.structural_dom_begin(),
+                                         other.structural_dom_end(), this));
+}
+
+bool BasicBlock::structurally_postdominates(const BasicBlock& other) const {
+  return (this == &other) || !(other.structural_pdom_end() ==
+                               std::find(other.structural_pdom_begin(),
+                                         other.structural_pdom_end(), this));
 }
 
 BasicBlock::DominatorIterator::DominatorIterator() : current_(nullptr) {}
@@ -107,21 +130,43 @@
   return DominatorIterator();
 }
 
-const BasicBlock::DominatorIterator BasicBlock::pdom_begin() const {
-  return DominatorIterator(
-      this, [](const BasicBlock* b) { return b->immediate_post_dominator(); });
+const BasicBlock::DominatorIterator BasicBlock::structural_dom_begin() const {
+  return DominatorIterator(this, [](const BasicBlock* b) {
+    return b->immediate_structural_dominator();
+  });
 }
 
-BasicBlock::DominatorIterator BasicBlock::pdom_begin() {
-  return DominatorIterator(
-      this, [](const BasicBlock* b) { return b->immediate_post_dominator(); });
+BasicBlock::DominatorIterator BasicBlock::structural_dom_begin() {
+  return DominatorIterator(this, [](const BasicBlock* b) {
+    return b->immediate_structural_dominator();
+  });
 }
 
-const BasicBlock::DominatorIterator BasicBlock::pdom_end() const {
+const BasicBlock::DominatorIterator BasicBlock::structural_dom_end() const {
   return DominatorIterator();
 }
 
-BasicBlock::DominatorIterator BasicBlock::pdom_end() {
+BasicBlock::DominatorIterator BasicBlock::structural_dom_end() {
+  return DominatorIterator();
+}
+
+const BasicBlock::DominatorIterator BasicBlock::structural_pdom_begin() const {
+  return DominatorIterator(this, [](const BasicBlock* b) {
+    return b->immediate_structural_post_dominator();
+  });
+}
+
+BasicBlock::DominatorIterator BasicBlock::structural_pdom_begin() {
+  return DominatorIterator(this, [](const BasicBlock* b) {
+    return b->immediate_structural_post_dominator();
+  });
+}
+
+const BasicBlock::DominatorIterator BasicBlock::structural_pdom_end() const {
+  return DominatorIterator();
+}
+
+BasicBlock::DominatorIterator BasicBlock::structural_pdom_end() {
   return DominatorIterator();
 }
 
diff --git a/source/val/basic_block.h b/source/val/basic_block.h
index 47cd06d..be5657e 100644
--- a/source/val/basic_block.h
+++ b/source/val/basic_block.h
@@ -64,9 +64,32 @@
   /// Returns the successors of the BasicBlock
   std::vector<BasicBlock*>* successors() { return &successors_; }
 
-  /// Returns true if the block is reachable in the CFG
+  /// Returns the structural successors of the BasicBlock
+  std::vector<BasicBlock*>* structural_predecessors() {
+    return &structural_predecessors_;
+  }
+
+  /// Returns the structural predecessors of the BasicBlock
+  const std::vector<BasicBlock*>* structural_predecessors() const {
+    return &structural_predecessors_;
+  }
+
+  /// Returns the structural successors of the BasicBlock
+  std::vector<BasicBlock*>* structural_successors() {
+    return &structural_successors_;
+  }
+
+  /// Returns the structural predecessors of the BasicBlock
+  const std::vector<BasicBlock*>* structural_successors() const {
+    return &structural_successors_;
+  }
+
+  /// Returns true if the block is reachable in the CFG.
   bool reachable() const { return reachable_; }
 
+  /// Returns true if the block is structurally reachable in the CFG.
+  bool structurally_reachable() const { return structurally_reachable_; }
+
   /// Returns true if BasicBlock is of the given type
   bool is_type(BlockType type) const {
     if (type == kBlockTypeUndefined) return type_.none();
@@ -76,6 +99,11 @@
   /// Sets the reachability of the basic block in the CFG
   void set_reachable(bool reachability) { reachable_ = reachability; }
 
+  /// Sets the structural reachability of the basic block in the CFG
+  void set_structurally_reachable(bool reachability) {
+    structurally_reachable_ = reachability;
+  }
+
   /// Sets the type of the BasicBlock
   void set_type(BlockType type) {
     if (type == kBlockTypeUndefined)
@@ -89,10 +117,15 @@
   /// @param[in] dom_block The dominator block
   void SetImmediateDominator(BasicBlock* dom_block);
 
+  /// Sets the immediate dominator of this basic block
+  ///
+  /// @param[in] dom_block The dominator block
+  void SetImmediateStructuralDominator(BasicBlock* dom_block);
+
   /// Sets the immediate post dominator of this basic block
   ///
   /// @param[in] pdom_block The post dominator block
-  void SetImmediatePostDominator(BasicBlock* pdom_block);
+  void SetImmediateStructuralPostDominator(BasicBlock* pdom_block);
 
   /// Returns the immediate dominator of this basic block
   BasicBlock* immediate_dominator();
@@ -100,11 +133,17 @@
   /// Returns the immediate dominator of this basic block
   const BasicBlock* immediate_dominator() const;
 
-  /// Returns the immediate post dominator of this basic block
-  BasicBlock* immediate_post_dominator();
+  /// Returns the immediate dominator of this basic block
+  BasicBlock* immediate_structural_dominator();
+
+  /// Returns the immediate dominator of this basic block
+  const BasicBlock* immediate_structural_dominator() const;
 
   /// Returns the immediate post dominator of this basic block
-  const BasicBlock* immediate_post_dominator() const;
+  BasicBlock* immediate_structural_post_dominator();
+
+  /// Returns the immediate post dominator of this basic block
+  const BasicBlock* immediate_structural_post_dominator() const;
 
   /// Returns the label instruction for the block, or nullptr if not set.
   const Instruction* label() const { return label_; }
@@ -132,9 +171,18 @@
   /// 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;
+  /// Returns true if this block structurally dominates the other block.
+  /// Assumes structural dominators have been computed.
+  bool structurally_dominates(const BasicBlock& other) const;
+
+  /// Returns true if this block structurally postdominates the other block.
+  /// Assumes structural dominators have been computed.
+  bool structurally_postdominates(const BasicBlock& other) const;
+
+  void RegisterStructuralSuccessor(BasicBlock* block) {
+    block->structural_predecessors_.push_back(this);
+    structural_successors_.push_back(block);
+  }
 
   /// @brief A BasicBlock dominator iterator class
   ///
@@ -191,18 +239,32 @@
   /// block
   DominatorIterator dom_end();
 
+  /// Returns a dominator iterator which points to the current block
+  const DominatorIterator structural_dom_begin() const;
+
+  /// Returns a dominator iterator which points to the current block
+  DominatorIterator structural_dom_begin();
+
+  /// Returns a dominator iterator which points to one element past the first
+  /// block
+  const DominatorIterator structural_dom_end() const;
+
+  /// Returns a dominator iterator which points to one element past the first
+  /// block
+  DominatorIterator structural_dom_end();
+
   /// Returns a post dominator iterator which points to the current block
-  const DominatorIterator pdom_begin() const;
+  const DominatorIterator structural_pdom_begin() const;
   /// Returns a post dominator iterator which points to the current block
-  DominatorIterator pdom_begin();
+  DominatorIterator structural_pdom_begin();
 
   /// Returns a post dominator iterator which points to one element past the
   /// last block
-  const DominatorIterator pdom_end() const;
+  const DominatorIterator structural_pdom_end() const;
 
   /// Returns a post dominator iterator which points to one element past the
   /// last block
-  DominatorIterator pdom_end();
+  DominatorIterator structural_pdom_end();
 
  private:
   /// Id of the BasicBlock
@@ -211,8 +273,11 @@
   /// Pointer to the immediate dominator of the BasicBlock
   BasicBlock* immediate_dominator_;
 
-  /// Pointer to the immediate dominator of the BasicBlock
-  BasicBlock* immediate_post_dominator_;
+  /// Pointer to the immediate structural dominator of the BasicBlock
+  BasicBlock* immediate_structural_dominator_;
+
+  /// Pointer to the immediate structural post dominator of the BasicBlock
+  BasicBlock* immediate_structural_post_dominator_;
 
   /// The set of predecessors of the BasicBlock
   std::vector<BasicBlock*> predecessors_;
@@ -226,11 +291,17 @@
   /// True if the block is reachable in the CFG
   bool reachable_;
 
+  /// True if the block is structurally reachable in the CFG
+  bool structurally_reachable_;
+
   /// label of this block, if any.
   const Instruction* label_;
 
   /// Terminator of this block.
   const Instruction* terminator_;
+
+  std::vector<BasicBlock*> structural_predecessors_;
+  std::vector<BasicBlock*> structural_successors_;
 };
 
 /// @brief Returns true if the iterators point to the same element or if both
diff --git a/source/val/construct.cpp b/source/val/construct.cpp
index 251e2bb..52e61d5 100644
--- a/source/val/construct.cpp
+++ b/source/val/construct.cpp
@@ -70,60 +70,45 @@
 
 void Construct::set_exit(BasicBlock* block) { exit_block_ = block; }
 
-Construct::ConstructBlockSet Construct::blocks(Function* function) const {
-  auto header = entry_block();
-  auto merge = exit_block();
-  assert(header);
-  int header_depth = function->GetBlockDepth(const_cast<BasicBlock*>(header));
-  ConstructBlockSet construct_blocks;
-  std::unordered_set<BasicBlock*> corresponding_headers;
-  for (auto& other : corresponding_constructs()) {
-    // The corresponding header can be the same block as this construct's
-    // header for loops with no loop construct. In those cases, don't add the
-    // loop header as it prevents finding any blocks in the construct.
-    if (type() != ConstructType::kContinue || other->entry_block() != header) {
-      corresponding_headers.insert(other->entry_block());
-    }
+Construct::ConstructBlockSet Construct::blocks(Function* /*function*/) const {
+  const auto header = entry_block();
+  const auto exit = exit_block();
+  const bool is_continue = type() == ConstructType::kContinue;
+  const bool is_loop = type() == ConstructType::kLoop;
+  const BasicBlock* continue_header = nullptr;
+  if (is_loop) {
+    // The only corresponding construct for a loop is the continue.
+    continue_header = (*corresponding_constructs().begin())->entry_block();
   }
   std::vector<BasicBlock*> stack;
   stack.push_back(const_cast<BasicBlock*>(header));
+  ConstructBlockSet construct_blocks;
   while (!stack.empty()) {
-    BasicBlock* block = stack.back();
+    auto* block = stack.back();
     stack.pop_back();
 
-    if (merge == block && ExitBlockIsMergeBlock()) {
-      // Merge block is not part of the construct.
-      continue;
-    }
+    if (header->structurally_dominates(*block)) {
+      bool include = false;
+      if (is_continue && exit->structurally_postdominates(*block)) {
+        // Continue construct include blocks dominated by the continue target
+        // and post-dominated by the back-edge block.
+        include = true;
+      } else if (!exit->structurally_dominates(*block)) {
+        // Selection and loop constructs include blocks dominated by the header
+        // and not dominated by the merge.
+        include = true;
+        if (is_loop && continue_header->structurally_dominates(*block)) {
+          // Loop constructs have an additional constraint that they do not
+          // include blocks dominated by the continue construct. Since all
+          // blocks in the continue construct are dominated by the continue
+          // target, we just test for dominance by continue target.
+          include = false;
+        }
+      }
+      if (include) {
+        if (!construct_blocks.insert(block).second) continue;
 
-    if (corresponding_headers.count(block)) {
-      // Entered a corresponding construct.
-      continue;
-    }
-
-    int block_depth = function->GetBlockDepth(block);
-    if (block_depth < header_depth) {
-      // Broke to outer construct.
-      continue;
-    }
-
-    // In a loop, the continue target is at a depth of the loop construct + 1.
-    // A selection construct nested directly within the loop construct is also
-    // at the same depth. It is valid, however, to branch directly to the
-    // continue target from within the selection construct.
-    if (block != header && block_depth == header_depth &&
-        type() == ConstructType::kSelection &&
-        block->is_type(kBlockTypeContinue)) {
-      // Continued to outer construct.
-      continue;
-    }
-
-    if (!construct_blocks.insert(block).second) continue;
-
-    if (merge != block) {
-      for (auto succ : *block->successors()) {
-        // All blocks in the construct must be dominated by the header.
-        if (header->dominates(*succ)) {
+        for (auto succ : *block->structural_successors()) {
           stack.push_back(succ);
         }
       }
@@ -181,11 +166,12 @@
       for (auto& use : block->label()->uses()) {
         if ((use.first->opcode() == SpvOpLoopMerge ||
              use.first->opcode() == SpvOpSelectionMerge) &&
-            use.second == 1 && use.first->block()->dominates(*block)) {
+            use.second == 1 &&
+            use.first->block()->structurally_dominates(*block)) {
           return use.first->block();
         }
       }
-      return block->immediate_dominator();
+      return block->immediate_structural_dominator();
     };
 
     bool seen_switch = false;
@@ -201,7 +187,7 @@
            terminator->opcode() == SpvOpSwitch)) {
         auto merge_target = merge_inst->GetOperandAs<uint32_t>(0u);
         auto merge_block = merge_inst->function()->GetBlock(merge_target).first;
-        if (merge_block->dominates(*header)) {
+        if (merge_block->structurally_dominates(*header)) {
           block = NextBlock(block);
           continue;
         }
diff --git a/source/val/decoration.h b/source/val/decoration.h
index ed3320f..4f53f20 100644
--- a/source/val/decoration.h
+++ b/source/val/decoration.h
@@ -69,6 +69,15 @@
   std::vector<uint32_t>& params() { return params_; }
   const std::vector<uint32_t>& params() const { return params_; }
 
+  inline bool operator<(const Decoration& rhs) const {
+    // Note: Sort by struct_member_index_ first, then type, so look up can be
+    // efficient using lower_bound() and upper_bound().
+    if (struct_member_index_ < rhs.struct_member_index_) return true;
+    if (rhs.struct_member_index_ < struct_member_index_) return false;
+    if (dec_type_ < rhs.dec_type_) return true;
+    if (rhs.dec_type_ < dec_type_) return false;
+    return params_ < rhs.params_;
+  }
   inline bool operator==(const Decoration& rhs) const {
     return (dec_type_ == rhs.dec_type_ && params_ == rhs.params_ &&
             struct_member_index_ == rhs.struct_member_index_);
diff --git a/source/val/function.cpp b/source/val/function.cpp
index f3292b0..fc7ccd0 100644
--- a/source/val/function.cpp
+++ b/source/val/function.cpp
@@ -73,6 +73,8 @@
   BasicBlock& continue_target_block = blocks_.at(continue_id);
   assert(current_block_ &&
          "RegisterLoopMerge must be called when called within a block");
+  current_block_->RegisterStructuralSuccessor(&merge_block);
+  current_block_->RegisterStructuralSuccessor(&continue_target_block);
 
   current_block_->set_type(kBlockTypeLoop);
   merge_block.set_type(kBlockTypeMerge);
@@ -101,6 +103,7 @@
   current_block_->set_type(kBlockTypeSelection);
   merge_block.set_type(kBlockTypeMerge);
   merge_block_header_[&merge_block] = current_block_;
+  current_block_->RegisterStructuralSuccessor(&merge_block);
 
   AddConstruct({ConstructType::kSelection, current_block(), &merge_block});
 
@@ -251,16 +254,6 @@
   };
 }
 
-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);
@@ -269,11 +262,35 @@
   };
 }
 
+Function::GetBlocksFunction Function::AugmentedStructuralCFGSuccessorsFunction()
+    const {
+  return [this](const BasicBlock* block) {
+    auto where = augmented_successors_map_.find(block);
+    return where == augmented_successors_map_.end()
+               ? block->structural_successors()
+               : &(*where).second;
+  };
+}
+
+Function::GetBlocksFunction
+Function::AugmentedStructuralCFGPredecessorsFunction() const {
+  return [this](const BasicBlock* block) {
+    auto where = augmented_predecessors_map_.find(block);
+    return where == augmented_predecessors_map_.end()
+               ? block->structural_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 succ_func = [](const BasicBlock* b) {
+    return b->structural_successors();
+  };
+  auto pred_func = [](const BasicBlock* b) {
+    return b->structural_predecessors();
+  };
   CFA<BasicBlock>::ComputeAugmentedCFG(
       ordered_blocks_, &pseudo_entry_block_, &pseudo_exit_block_,
       &augmented_successors_map_, &augmented_predecessors_map_, succ_func,
diff --git a/source/val/function.h b/source/val/function.h
index 2fe30bd..126b1dc 100644
--- a/source/val/function.h
+++ b/source/val/function.h
@@ -184,12 +184,12 @@
       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;
+  /// Returns the block structural successors function for the augmented CFG.
+  GetBlocksFunction AugmentedStructuralCFGSuccessorsFunction() const;
+  /// Returns the block structural predecessors function for the augmented CFG.
+  GetBlocksFunction AugmentedStructuralCFGPredecessorsFunction() const;
 
   /// Returns the control flow nesting depth of the given basic block.
   /// This function only works when you have structured control flow.
diff --git a/source/val/validate.cpp b/source/val/validate.cpp
index ecc9fdb..55e9fd2 100644
--- a/source/val/validate.cpp
+++ b/source/val/validate.cpp
@@ -293,6 +293,11 @@
     return vstate->diag(SPV_ERROR_INVALID_LAYOUT, nullptr)
            << "Missing OpFunctionEnd at end of module.";
 
+  if (vstate->HasCapability(SpvCapabilityBindlessTextureNV) &&
+      !vstate->has_samplerimage_variable_address_mode_specified())
+    return vstate->diag(SPV_ERROR_INVALID_LAYOUT, nullptr)
+           << "Missing required OpSamplerImageAddressingModeNV instruction.";
+
   // Catch undefined forward references before performing further checks.
   if (auto error = ValidateForwardDecls(*vstate)) return error;
 
@@ -345,6 +350,7 @@
     if (auto error = NonUniformPass(*vstate, &instruction)) return error;
 
     if (auto error = LiteralsPass(*vstate, &instruction)) return error;
+    if (auto error = RayQueryPass(*vstate, &instruction)) return error;
   }
 
   // Validate the preconditions involving adjacent instructions. e.g. SpvOpPhi
diff --git a/source/val/validate.h b/source/val/validate.h
index cb1d05a..97d4683 100644
--- a/source/val/validate.h
+++ b/source/val/validate.h
@@ -197,6 +197,9 @@
 /// Validates correctness of miscellaneous instructions.
 spv_result_t MiscPass(ValidationState_t& _, const Instruction* inst);
 
+/// Validates correctness of ray query instructions.
+spv_result_t RayQueryPass(ValidationState_t& _, const Instruction* inst);
+
 /// Calculates the reachability of basic blocks.
 void ReachabilityPass(ValidationState_t& _);
 
diff --git a/source/val/validate_annotation.cpp b/source/val/validate_annotation.cpp
index 40f2118..c27c799 100644
--- a/source/val/validate_annotation.cpp
+++ b/source/val/validate_annotation.cpp
@@ -581,7 +581,7 @@
       // Word 1 is the group <id>. All subsequent words are target <id>s that
       // are going to be decorated with the decorations.
       const uint32_t decoration_group_id = inst->word(1);
-      std::vector<Decoration>& group_decorations =
+      std::set<Decoration>& group_decorations =
           _.id_decorations(decoration_group_id);
       for (size_t i = 2; i < inst->words().size(); ++i) {
         const uint32_t target_id = inst->word(i);
@@ -595,7 +595,7 @@
       // pairs. All decorations of the group should be applied to all the struct
       // members that are specified in the instructions.
       const uint32_t decoration_group_id = inst->word(1);
-      std::vector<Decoration>& group_decorations =
+      std::set<Decoration>& group_decorations =
           _.id_decorations(decoration_group_id);
       // Grammar checks ensures that the number of arguments to this instruction
       // is an odd number: 1 decoration group + (id,literal) pairs.
diff --git a/source/val/validate_cfg.cpp b/source/val/validate_cfg.cpp
index dd605d2..0220fcd 100644
--- a/source/val/validate_cfg.cpp
+++ b/source/val/validate_cfg.cpp
@@ -66,7 +66,8 @@
   assert(type_inst);
   const SpvOp type_opcode = type_inst->opcode();
 
-  if (!_.options()->before_hlsl_legalization) {
+  if (!_.options()->before_hlsl_legalization &&
+      !_.HasCapability(SpvCapabilityBindlessTextureNV)) {
     if (type_opcode == SpvOpTypeSampledImage ||
         (_.HasCapability(SpvCapabilityShader) &&
          (type_opcode == SpvOpTypeImage || type_opcode == SpvOpTypeSampler))) {
@@ -466,7 +467,7 @@
   std::vector<BasicBlock*> stack;
   stack.push_back(target_block);
   std::unordered_set<const BasicBlock*> visited;
-  bool target_reachable = target_block->reachable();
+  bool target_reachable = target_block->structurally_reachable();
   int target_depth = function->GetBlockDepth(target_block);
   while (!stack.empty()) {
     auto block = stack.back();
@@ -476,8 +477,8 @@
 
     if (!visited.insert(block).second) continue;
 
-    if (target_reachable && block->reachable() &&
-        target_block->dominates(*block)) {
+    if (target_reachable && block->structurally_reachable() &&
+        target_block->structurally_dominates(*block)) {
       // Still in the case construct.
       for (auto successor : *block->successors()) {
         stack.push_back(successor);
@@ -549,11 +550,12 @@
     if (seen_iter == seen_to_fall_through.end()) {
       const auto target_block = function->GetBlock(target).first;
       // OpSwitch must dominate all its case constructs.
-      if (header->reachable() && target_block->reachable() &&
-          !header->dominates(*target_block)) {
+      if (header->structurally_reachable() &&
+          target_block->structurally_reachable() &&
+          !header->structurally_dominates(*target_block)) {
         return _.diag(SPV_ERROR_INVALID_CFG, header->label())
                << "Selection header " << _.getIdName(header->id())
-               << " does not dominate its case construct "
+               << " does not structurally dominate its case construct "
                << _.getIdName(target);
       }
 
@@ -653,7 +655,7 @@
     }
 
     // Skip unreachable blocks.
-    if (!block->reachable()) continue;
+    if (!block->structurally_reachable()) continue;
 
     if (terminator->opcode() == SpvOpBranchConditional) {
       const auto true_label = terminator->GetOperandAs<uint32_t>(1);
@@ -708,7 +710,7 @@
 
   // 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->structurally_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();
@@ -723,9 +725,10 @@
   // Check construct rules
   for (const Construct& construct : function->constructs()) {
     auto header = construct.entry_block();
+    if (!header->structurally_reachable()) continue;
     auto merge = construct.exit_block();
 
-    if (header->reachable() && !merge) {
+    if (!merge) {
       std::string construct_name, header_name, exit_name;
       std::tie(construct_name, header_name, exit_name) =
           ConstructNames(construct.type());
@@ -735,32 +738,31 @@
                     exit_name + ". This may be a bug in the validator.";
     }
 
-    // If the exit block is reachable then it's dominated by the
-    // header.
-    if (merge && merge->reachable()) {
-      if (!header->dominates(*merge)) {
-        return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(merge->id()))
-               << ConstructErrorString(construct, _.getIdName(header->id()),
-                                       _.getIdName(merge->id()),
-                                       "does not dominate");
-      }
-      // If it's really a merge block for a selection or loop, then it must be
-      // *strictly* dominated by the header.
-      if (construct.ExitBlockIsMergeBlock() && (header == merge)) {
-        return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(merge->id()))
-               << ConstructErrorString(construct, _.getIdName(header->id()),
-                                       _.getIdName(merge->id()),
-                                       "does not strictly dominate");
-      }
+    // If the header is reachable, the merge is guaranteed to be structurally
+    // reachable.
+    if (!header->structurally_dominates(*merge)) {
+      return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(merge->id()))
+             << ConstructErrorString(construct, _.getIdName(header->id()),
+                                     _.getIdName(merge->id()),
+                                     "does not structurally dominate");
     }
+    // If it's really a merge block for a selection or loop, then it must be
+    // *strictly* structrually dominated by the header.
+    if (construct.ExitBlockIsMergeBlock() && (header == merge)) {
+      return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(merge->id()))
+             << ConstructErrorString(construct, _.getIdName(header->id()),
+                                     _.getIdName(merge->id()),
+                                     "does not strictly structurally dominate");
+    }
+
     // 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 (!merge->postdominates(*header)) {
+    if (construct.type() == ConstructType::kContinue) {
+      if (!merge->structurally_postdominates(*header)) {
         return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(merge->id()))
                << ConstructErrorString(construct, _.getIdName(header->id()),
                                        _.getIdName(merge->id()),
-                                       "is not post dominated by");
+                                       "is not structurally post dominated by");
       }
     }
 
@@ -771,7 +773,7 @@
     for (auto block : construct_blocks) {
       // Check that all exits from the construct are via structured exits.
       for (auto succ : *block->successors()) {
-        if (block->reachable() && !construct_blocks.count(succ) &&
+        if (!construct_blocks.count(succ) &&
             !construct.IsStructuredExit(_, succ)) {
           return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(block->id()))
                  << "block <ID> " << _.getIdName(block->id()) << " exits the "
@@ -784,7 +786,7 @@
       // Check that for all non-header blocks, all predecessors are within this
       // construct.
       for (auto pred : *block->predecessors()) {
-        if (pred->reachable() && !construct_blocks.count(pred)) {
+        if (pred->structurally_reachable() && !construct_blocks.count(pred)) {
           return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(pred->id()))
                  << "block <ID> " << pred->id() << " branches to the "
                  << construct_name << " construct, but not to the "
@@ -800,7 +802,7 @@
             merge_inst.opcode() == SpvOpLoopMerge) {
           uint32_t merge_id = merge_inst.GetOperandAs<uint32_t>(0);
           auto merge_block = function->GetBlock(merge_id).first;
-          if (merge_block->reachable() &&
+          if (merge_block->structurally_reachable() &&
               !construct_blocks.count(merge_block)) {
             return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(block->id()))
                    << "Header block " << _.getIdName(block->id())
@@ -813,6 +815,43 @@
       }
     }
 
+    if (construct.type() == ConstructType::kLoop) {
+      // If the continue target differs from the loop header, then check that
+      // all edges into the continue construct come from within the loop.
+      const auto index = header->terminator() - &_.ordered_instructions()[0];
+      const auto& merge_inst = _.ordered_instructions()[index - 1];
+      const auto continue_id = merge_inst.GetOperandAs<uint32_t>(1);
+      const auto* continue_inst = _.FindDef(continue_id);
+      // OpLabel instructions aren't stored as part of the basic block for
+      // legacy reaasons. Grab the next instruction and use it's block pointer
+      // instead.
+      const auto next_index =
+          (continue_inst - &_.ordered_instructions()[0]) + 1;
+      const auto& next_inst = _.ordered_instructions()[next_index];
+      const auto* continue_target = next_inst.block();
+      if (header->id() != continue_id) {
+        for (auto pred : *continue_target->predecessors()) {
+          // Ignore back-edges from within the continue construct.
+          bool is_back_edge = false;
+          for (auto back_edge : back_edges) {
+            uint32_t back_edge_block;
+            uint32_t header_block;
+            std::tie(back_edge_block, header_block) = back_edge;
+            if (header_block == continue_id && back_edge_block == pred->id())
+              is_back_edge = true;
+          }
+          if (!construct_blocks.count(pred) && !is_back_edge) {
+            return _.diag(SPV_ERROR_INVALID_CFG, pred->terminator())
+                   << "Block " << _.getIdName(pred->id())
+                   << " branches to the loop continue target "
+                   << _.getIdName(continue_id)
+                   << ", but is not contained in the associated loop construct "
+                   << _.getIdName(header->id());
+          }
+        }
+      }
+    }
+
     // Checks rules for case constructs.
     if (construct.type() == ConstructType::kSelection &&
         header->terminator()->opcode() == SpvOpSwitch) {
@@ -850,52 +889,28 @@
              << _.getIdName(function.id());
     }
 
-    // Set each block's immediate dominator and immediate postdominator,
-    // and find all back-edges.
+    // Set each block's immediate dominator.
     //
     // 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.
     std::vector<const BasicBlock*> postorder;
-    std::vector<const BasicBlock*> postdom_postorder;
-    std::vector<std::pair<uint32_t, uint32_t>> back_edges;
     auto ignore_block = [](const BasicBlock*) {};
     auto ignore_edge = [](const BasicBlock*, const BasicBlock*) {};
+    auto no_terminal_blocks = [](const BasicBlock*) { return false; };
     if (!function.ordered_blocks().empty()) {
       /// calculate dominators
       CFA<BasicBlock>::DepthFirstTraversal(
           function.first_block(), function.AugmentedCFGSuccessorsFunction(),
           ignore_block, [&](const BasicBlock* b) { postorder.push_back(b); },
-          ignore_edge);
+          ignore_edge, no_terminal_blocks);
       auto edges = CFA<BasicBlock>::CalculateDominators(
           postorder, function.AugmentedCFGPredecessorsFunction());
       for (auto edge : edges) {
         if (edge.first != edge.second)
           edge.first->SetImmediateDominator(edge.second);
       }
-
-      /// calculate post dominators
-      CFA<BasicBlock>::DepthFirstTraversal(
-          function.pseudo_exit_block(),
-          function.AugmentedCFGPredecessorsFunction(), ignore_block,
-          [&](const BasicBlock* b) { postdom_postorder.push_back(b); },
-          ignore_edge);
-      auto postdom_edges = CFA<BasicBlock>::CalculateDominators(
-          postdom_postorder, function.AugmentedCFGSuccessorsFunction());
-      for (auto edge : postdom_edges) {
-        edge.first->SetImmediatePostDominator(edge.second);
-      }
-      /// calculate back edges.
-      CFA<BasicBlock>::DepthFirstTraversal(
-          function.pseudo_entry_block(),
-          function
-              .AugmentedCFGSuccessorsFunctionIncludingHeaderToContinueEdge(),
-          ignore_block, ignore_block,
-          [&](const BasicBlock* from, const BasicBlock* to) {
-            back_edges.emplace_back(from->id(), to->id());
-          });
     }
-    UpdateContinueConstructExitBlocks(function, back_edges);
 
     auto& blocks = function.ordered_blocks();
     if (!blocks.empty()) {
@@ -929,6 +944,52 @@
 
     /// Structured control flow checks are only required for shader capabilities
     if (_.HasCapability(SpvCapabilityShader)) {
+      // Calculate structural dominance.
+      postorder.clear();
+      std::vector<const BasicBlock*> postdom_postorder;
+      std::vector<std::pair<uint32_t, uint32_t>> back_edges;
+      if (!function.ordered_blocks().empty()) {
+        /// calculate dominators
+        CFA<BasicBlock>::DepthFirstTraversal(
+            function.first_block(),
+            function.AugmentedStructuralCFGSuccessorsFunction(), ignore_block,
+            [&](const BasicBlock* b) { postorder.push_back(b); }, ignore_edge,
+            no_terminal_blocks);
+        auto edges = CFA<BasicBlock>::CalculateDominators(
+            postorder, function.AugmentedStructuralCFGPredecessorsFunction());
+        for (auto edge : edges) {
+          if (edge.first != edge.second)
+            edge.first->SetImmediateStructuralDominator(edge.second);
+        }
+
+        /// calculate post dominators
+        CFA<BasicBlock>::DepthFirstTraversal(
+            function.pseudo_exit_block(),
+            function.AugmentedStructuralCFGPredecessorsFunction(), ignore_block,
+            [&](const BasicBlock* b) { postdom_postorder.push_back(b); },
+            ignore_edge, no_terminal_blocks);
+        auto postdom_edges = CFA<BasicBlock>::CalculateDominators(
+            postdom_postorder,
+            function.AugmentedStructuralCFGSuccessorsFunction());
+        for (auto edge : postdom_edges) {
+          edge.first->SetImmediateStructuralPostDominator(edge.second);
+        }
+        /// calculate back edges.
+        CFA<BasicBlock>::DepthFirstTraversal(
+            function.pseudo_entry_block(),
+            function.AugmentedStructuralCFGSuccessorsFunction(), ignore_block,
+            ignore_block,
+            [&](const BasicBlock* from, const BasicBlock* to) {
+              // A back edge must be a real edge. Since the augmented successors
+              // contain structural edges, filter those from consideration.
+              for (const auto* succ : *(from->successors())) {
+                if (succ == to) back_edges.emplace_back(from->id(), to->id());
+              }
+            },
+            no_terminal_blocks);
+      }
+      UpdateContinueConstructExitBlocks(function, back_edges);
+
       if (auto error =
               StructuredControlFlowChecks(_, &function, back_edges, postorder))
         return error;
@@ -1054,6 +1115,26 @@
       }
     }
   }
+
+  // Repeat for structural reachability.
+  for (auto& f : _.functions()) {
+    std::vector<BasicBlock*> stack;
+    auto entry = f.first_block();
+    // Skip function declarations.
+    if (entry) stack.push_back(entry);
+
+    while (!stack.empty()) {
+      auto block = stack.back();
+      stack.pop_back();
+
+      if (block->structurally_reachable()) continue;
+
+      block->set_structurally_reachable(true);
+      for (auto succ : *block->structural_successors()) {
+        stack.push_back(succ);
+      }
+    }
+  }
 }
 
 spv_result_t ControlFlowPass(ValidationState_t& _, const Instruction* inst) {
diff --git a/source/val/validate_conversion.cpp b/source/val/validate_conversion.cpp
index b4e39cf..dc6b151 100644
--- a/source/val/validate_conversion.cpp
+++ b/source/val/validate_conversion.cpp
@@ -534,6 +534,24 @@
       break;
     }
 
+    case SpvOpConvertUToAccelerationStructureKHR: {
+      if (!_.IsAccelerationStructureType(result_type)) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Expected Result Type to be a Acceleration Structure: "
+               << spvOpcodeString(opcode);
+      }
+
+      const uint32_t input_type = _.GetOperandTypeId(inst, 2);
+      if (!input_type || !_.IsUnsigned64BitHandle(input_type)) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Expected 64-bit uint scalar or 2-component 32-bit uint "
+                  "vector as input: "
+               << spvOpcodeString(opcode);
+      }
+
+      break;
+    }
+
     default:
       break;
   }
diff --git a/source/val/validate_decorations.cpp b/source/val/validate_decorations.cpp
index 73d512a..4e4f108 100644
--- a/source/val/validate_decorations.cpp
+++ b/source/val/validate_decorations.cpp
@@ -190,6 +190,13 @@
   // Minimal alignment is byte-aligned.
   uint32_t baseAlignment = 1;
   switch (inst->opcode()) {
+    case SpvOpTypeSampledImage:
+    case SpvOpTypeSampler:
+    case SpvOpTypeImage:
+      if (vstate.HasCapability(SpvCapabilityBindlessTextureNV))
+        return baseAlignment = vstate.samplerimage_variable_address_mode() / 8;
+      assert(0);
+      return 0;
     case SpvOpTypeInt:
     case SpvOpTypeFloat:
       baseAlignment = words[2] / 8;
@@ -219,6 +226,7 @@
         baseAlignment =
             componentAlignment * (num_columns == 3 ? 4 : num_columns);
       }
+      if (roundUp) baseAlignment = align(baseAlignment, 16u);
     } break;
     case SpvOpTypeArray:
     case SpvOpTypeRuntimeArray:
@@ -256,6 +264,13 @@
   const auto inst = vstate.FindDef(type_id);
   const auto& words = inst->words();
   switch (inst->opcode()) {
+    case SpvOpTypeSampledImage:
+    case SpvOpTypeSampler:
+    case SpvOpTypeImage:
+      if (vstate.HasCapability(SpvCapabilityBindlessTextureNV))
+        return vstate.samplerimage_variable_address_mode() / 8;
+      assert(0);
+      return 0;
     case SpvOpTypeInt:
     case SpvOpTypeFloat:
       return words[2] / 8;
@@ -296,6 +311,13 @@
   const auto inst = vstate.FindDef(member_id);
   const auto& words = inst->words();
   switch (inst->opcode()) {
+    case SpvOpTypeSampledImage:
+    case SpvOpTypeSampler:
+    case SpvOpTypeImage:
+      if (vstate.HasCapability(SpvCapabilityBindlessTextureNV))
+        return vstate.samplerimage_variable_address_mode() / 8;
+      assert(0);
+      return 0;
     case SpvOpTypeInt:
     case SpvOpTypeFloat:
       return words[2] / 8;
@@ -346,10 +368,13 @@
       const auto& lastMember = members.back();
       uint32_t offset = 0xffffffff;
       // Find the offset of the last element and add the size.
-      for (auto& decoration : vstate.id_decorations(member_id)) {
-        if (SpvDecorationOffset == decoration.dec_type() &&
-            decoration.struct_member_index() == (int)lastIdx) {
-          offset = decoration.params()[0];
+      auto member_decorations =
+          vstate.id_member_decorations(member_id, lastIdx);
+      for (auto decoration = member_decorations.begin;
+           decoration != member_decorations.end; ++decoration) {
+        assert(decoration->struct_member_index() == (int)lastIdx);
+        if (SpvDecorationOffset == decoration->dec_type()) {
+          offset = decoration->params()[0];
         }
       }
       // This check depends on the fact that all members have offsets.  This
@@ -445,15 +470,17 @@
   for (uint32_t memberIdx = 0, numMembers = uint32_t(members.size());
        memberIdx < numMembers; memberIdx++) {
     uint32_t offset = 0xffffffff;
-    for (auto& decoration : vstate.id_decorations(struct_id)) {
-      if (decoration.struct_member_index() == (int)memberIdx) {
-        switch (decoration.dec_type()) {
-          case SpvDecorationOffset:
-            offset = decoration.params()[0];
-            break;
-          default:
-            break;
-        }
+    auto member_decorations =
+        vstate.id_member_decorations(struct_id, memberIdx);
+    for (auto decoration = member_decorations.begin;
+         decoration != member_decorations.end; ++decoration) {
+      assert(decoration->struct_member_index() == (int)memberIdx);
+      switch (decoration->dec_type()) {
+        case SpvDecorationOffset:
+          offset = decoration->params()[0];
+          break;
+        default:
+          break;
       }
     }
     member_offsets.push_back(
@@ -633,7 +660,8 @@
 }
 
 // Returns true if all ids of given type have a specified decoration.
-bool checkForRequiredDecoration(uint32_t struct_id, SpvDecoration decoration,
+bool checkForRequiredDecoration(uint32_t struct_id,
+                                std::function<bool(SpvDecoration)> checker,
                                 SpvOp type, ValidationState_t& vstate) {
   const auto& members = getStructMembers(struct_id, vstate);
   for (size_t memberIdx = 0; memberIdx < members.size(); memberIdx++) {
@@ -641,10 +669,10 @@
     if (type != vstate.FindDef(id)->opcode()) continue;
     bool found = false;
     for (auto& dec : vstate.id_decorations(id)) {
-      if (decoration == dec.dec_type()) found = true;
+      if (checker(dec.dec_type())) found = true;
     }
     for (auto& dec : vstate.id_decorations(struct_id)) {
-      if (decoration == dec.dec_type() &&
+      if (checker(dec.dec_type()) &&
           (int)memberIdx == dec.struct_member_index()) {
         found = true;
       }
@@ -654,7 +682,7 @@
     }
   }
   for (auto id : getStructMembers(struct_id, SpvOpTypeStruct, vstate)) {
-    if (!checkForRequiredDecoration(id, decoration, type, vstate)) {
+    if (!checkForRequiredDecoration(id, checker, type, vstate)) {
       return false;
     }
   }
@@ -806,6 +834,56 @@
               ++num_workgroup_variables_with_aliased;
           }
         }
+
+        if (spvIsVulkanEnv(vstate.context()->target_env)) {
+          const auto* models = vstate.GetExecutionModels(entry_point);
+          const bool has_frag =
+              models->find(SpvExecutionModelFragment) != models->end();
+          const bool has_vert =
+              models->find(SpvExecutionModelVertex) != models->end();
+          for (const auto& decoration :
+               vstate.id_decorations(var_instr->id())) {
+            if (decoration == SpvDecorationFlat ||
+                decoration == SpvDecorationNoPerspective ||
+                decoration == SpvDecorationSample ||
+                decoration == SpvDecorationCentroid) {
+              // VUID 04670 already validates these decorations are input/output
+              if (storage_class == SpvStorageClassInput &&
+                  (models->size() > 1 || has_vert)) {
+                return vstate.diag(SPV_ERROR_INVALID_ID, var_instr)
+                       << vstate.VkErrorID(6202)
+                       << "OpEntryPoint interfaces variable must not be vertex "
+                          "execution model with an input storage class for "
+                          "Entry Point id "
+                       << entry_point << ".";
+              } else if (storage_class == SpvStorageClassOutput &&
+                         (models->size() > 1 || has_frag)) {
+                return vstate.diag(SPV_ERROR_INVALID_ID, var_instr)
+                       << vstate.VkErrorID(6201)
+                       << "OpEntryPoint interfaces variable must not be "
+                          "fragment "
+                          "execution model with an output storage class for "
+                          "Entry Point id "
+                       << entry_point << ".";
+              }
+            }
+          }
+
+          const bool has_flat =
+              hasDecoration(var_instr->id(), SpvDecorationFlat, vstate);
+          if (has_frag && storage_class == SpvStorageClassInput && !has_flat &&
+              ((vstate.IsFloatScalarType(type_id) &&
+                vstate.GetBitWidth(type_id) == 64) ||
+               vstate.IsIntScalarOrVectorType(type_id))) {
+            return vstate.diag(SPV_ERROR_INVALID_ID, var_instr)
+                     << vstate.VkErrorID(4744)
+                     << "Fragment OpEntryPoint operand "
+                     << interface << " with Input interfaces with integer or "
+                                     "float type must have a Flat decoration "
+                                     "for Entry Point id "
+                     << entry_point << ".";
+          }
+        }
       }
       if (num_builtin_block_inputs > 1 || num_builtin_block_outputs > 1) {
         return vstate.diag(SPV_ERROR_INVALID_BINARY,
@@ -878,21 +956,23 @@
     LayoutConstraints& constraint =
         (*constraints)[std::make_pair(struct_id, memberIdx)];
     constraint = inherited;
-    for (auto& decoration : vstate.id_decorations(struct_id)) {
-      if (decoration.struct_member_index() == (int)memberIdx) {
-        switch (decoration.dec_type()) {
-          case SpvDecorationRowMajor:
-            constraint.majorness = kRowMajor;
-            break;
-          case SpvDecorationColMajor:
-            constraint.majorness = kColumnMajor;
-            break;
-          case SpvDecorationMatrixStride:
-            constraint.matrix_stride = decoration.params()[0];
-            break;
-          default:
-            break;
-        }
+    auto member_decorations =
+        vstate.id_member_decorations(struct_id, memberIdx);
+    for (auto decoration = member_decorations.begin;
+         decoration != member_decorations.end; ++decoration) {
+      assert(decoration->struct_member_index() == (int)memberIdx);
+      switch (decoration->dec_type()) {
+        case SpvDecorationRowMajor:
+          constraint.majorness = kRowMajor;
+          break;
+        case SpvDecorationColMajor:
+          constraint.majorness = kColumnMajor;
+          break;
+        case SpvDecorationMatrixStride:
+          constraint.matrix_stride = decoration->params()[0];
+          break;
+        default:
+          break;
       }
     }
 
@@ -1144,30 +1224,48 @@
               return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
                      << "Structure id " << id << " decorated as " << deco_str
                      << " must not use GLSLPacked decoration.";
-            } else if (!checkForRequiredDecoration(id, SpvDecorationArrayStride,
-                                                   SpvOpTypeArray, vstate)) {
+            } else if (!checkForRequiredDecoration(
+                           id,
+                           [](SpvDecoration d) {
+                             return d == SpvDecorationArrayStride;
+                           },
+                           SpvOpTypeArray, vstate)) {
               return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
                      << "Structure id " << id << " decorated as " << deco_str
                      << " must be explicitly laid out with ArrayStride "
                         "decorations.";
-            } else if (!checkForRequiredDecoration(id,
-                                                   SpvDecorationMatrixStride,
-                                                   SpvOpTypeMatrix, vstate)) {
+            } else if (!checkForRequiredDecoration(
+                           id,
+                           [](SpvDecoration d) {
+                             return d == SpvDecorationMatrixStride;
+                           },
+                           SpvOpTypeMatrix, vstate)) {
               return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
                      << "Structure id " << id << " decorated as " << deco_str
                      << " must be explicitly laid out with MatrixStride "
                         "decorations.";
+            } else if (!checkForRequiredDecoration(
+                           id,
+                           [](SpvDecoration d) {
+                             return d == SpvDecorationRowMajor ||
+                                    d == SpvDecorationColMajor;
+                           },
+                           SpvOpTypeMatrix, vstate)) {
+              return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
+                     << "Structure id " << id << " decorated as " << deco_str
+                     << " must be explicitly laid out with RowMajor or "
+                        "ColMajor decorations.";
             } else if (blockRules &&
-                       (SPV_SUCCESS != (recursive_status = checkLayout(
-                                            id, sc_str, deco_str, true,
-                                            scalar_block_layout, 0,
-                                            constraints, vstate)))) {
+                       (SPV_SUCCESS !=
+                        (recursive_status = checkLayout(
+                             id, sc_str, deco_str, true, scalar_block_layout, 0,
+                             constraints, vstate)))) {
               return recursive_status;
             } else if (bufferRules &&
-                       (SPV_SUCCESS != (recursive_status = checkLayout(
-                                            id, sc_str, deco_str, false,
-                                            scalar_block_layout, 0,
-                                            constraints, vstate)))) {
+                       (SPV_SUCCESS !=
+                        (recursive_status = checkLayout(
+                             id, sc_str, deco_str, false, scalar_block_layout,
+                             0, constraints, vstate)))) {
               return recursive_status;
             }
           }
@@ -1654,6 +1752,24 @@
             "of a structure type";
 }
 
+spv_result_t CheckRelaxPrecisionDecoration(ValidationState_t& vstate,
+                                           const Instruction& inst,
+                                           const Decoration& decoration) {
+  // This is not the most precise check, but the rules for RelaxPrecision are
+  // very general, and it will be difficult to implement precisely.  For now,
+  // I will only check for the cases that cause problems for the optimizer.
+  if (!spvOpcodeGeneratesType(inst.opcode())) {
+    return SPV_SUCCESS;
+  }
+
+  if (decoration.struct_member_index() != Decoration::kInvalidMember &&
+      inst.opcode() == SpvOpTypeStruct) {
+    return SPV_SUCCESS;
+  }
+  return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
+         << "RelaxPrecision decoration cannot be applied to a type";
+}
+
 #define PASS_OR_BAIL_AT_LINE(X, LINE)           \
   {                                             \
     spv_result_t e##LINE = (X);                 \
@@ -1708,6 +1824,10 @@
         case SpvDecorationLocation:
           PASS_OR_BAIL(CheckLocationDecoration(vstate, *inst, decoration));
           break;
+        case SpvDecorationRelaxedPrecision:
+          PASS_OR_BAIL(
+              CheckRelaxPrecisionDecoration(vstate, *inst, decoration));
+          break;
         default:
           break;
       }
diff --git a/source/val/validate_image.cpp b/source/val/validate_image.cpp
index f6d7d10..2d5e2c7 100644
--- a/source/val/validate_image.cpp
+++ b/source/val/validate_image.cpp
@@ -927,7 +927,7 @@
   return SPV_SUCCESS;
 }
 
-bool IsAllowedSampledImageOperand(SpvOp opcode) {
+bool IsAllowedSampledImageOperand(SpvOp opcode, ValidationState_t& _) {
   switch (opcode) {
     case SpvOpSampledImage:
     case SpvOpImageSampleImplicitLod:
@@ -950,6 +950,9 @@
     case SpvOpImageSparseDrefGather:
     case SpvOpCopyObject:
       return true;
+    case SpvOpStore:
+      if (_.HasCapability(SpvCapabilityBindlessTextureNV)) return true;
+      return false;
     default:
       return false;
   }
@@ -1035,7 +1038,7 @@
                << _.getIdName(consumer_instr->id()) << "'.";
       }
 
-      if (!IsAllowedSampledImageOperand(consumer_opcode)) {
+      if (!IsAllowedSampledImageOperand(consumer_opcode, _)) {
         return _.diag(SPV_ERROR_INVALID_ID, inst)
                << "Result <id> from OpSampledImage instruction must not appear "
                   "as operand for Op"
diff --git a/source/val/validate_instruction.cpp b/source/val/validate_instruction.cpp
index 3edf163..767c0ce 100644
--- a/source/val/validate_instruction.cpp
+++ b/source/val/validate_instruction.cpp
@@ -483,6 +483,22 @@
     if (auto error = LimitCheckNumVars(_, inst->id(), storage_class)) {
       return error;
     }
+  } else if (opcode == SpvOpSamplerImageAddressingModeNV) {
+    if (!_.HasCapability(SpvCapabilityBindlessTextureNV)) {
+      return _.diag(SPV_ERROR_MISSING_EXTENSION, inst)
+             << "OpSamplerImageAddressingModeNV supported only with extension "
+                "SPV_NV_bindless_texture";
+    }
+    uint32_t bitwidth = inst->GetOperandAs<uint32_t>(0);
+    if (_.samplerimage_variable_address_mode() != 0) {
+      return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
+             << "OpSamplerImageAddressingModeNV should only be provided once";
+    }
+    if (bitwidth != 32 && bitwidth != 64) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "OpSamplerImageAddressingModeNV bitwidth should be 64 or 32";
+    }
+    _.set_samplerimage_variable_address_mode(bitwidth);
   }
 
   if (auto error = ReservedCheck(_, inst)) return error;
diff --git a/source/val/validate_layout.cpp b/source/val/validate_layout.cpp
index d582321..6f95135 100644
--- a/source/val/validate_layout.cpp
+++ b/source/val/validate_layout.cpp
@@ -363,6 +363,7 @@
     case kLayoutExtensions:
     case kLayoutExtInstImport:
     case kLayoutMemoryModel:
+    case kLayoutSamplerImageAddressMode:
     case kLayoutEntryPoint:
     case kLayoutExecutionMode:
     case kLayoutDebug1:
diff --git a/source/val/validate_logicals.cpp b/source/val/validate_logicals.cpp
index 5307988..ec1e207 100644
--- a/source/val/validate_logicals.cpp
+++ b/source/val/validate_logicals.cpp
@@ -170,6 +170,16 @@
             break;
           }
 
+          case SpvOpTypeSampledImage:
+          case SpvOpTypeImage:
+          case SpvOpTypeSampler: {
+            if (!_.HasCapability(SpvCapabilityBindlessTextureNV))
+              return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                     << "Using image/sampler with OpSelect requires capability "
+                     << "BindlessTextureNV";
+            break;
+          }
+
           case SpvOpTypeVector: {
             dimension = type_inst->word(3);
             break;
diff --git a/source/val/validate_memory.cpp b/source/val/validate_memory.cpp
index af9da67..425a8d3 100644
--- a/source/val/validate_memory.cpp
+++ b/source/val/validate_memory.cpp
@@ -35,8 +35,8 @@
                                  const Instruction*);
 bool HaveSameLayoutDecorations(ValidationState_t&, const Instruction*,
                                const Instruction*);
-bool HasConflictingMemberOffsets(const std::vector<Decoration>&,
-                                 const std::vector<Decoration>&);
+bool HasConflictingMemberOffsets(const std::set<Decoration>&,
+                                 const std::set<Decoration>&);
 
 bool IsAllowedTypeOrArrayOfSame(ValidationState_t& _, const Instruction* type,
                                 std::initializer_list<uint32_t> allowed) {
@@ -105,10 +105,8 @@
          "type1 must be an OpTypeStruct instruction.");
   assert(type2->opcode() == SpvOpTypeStruct &&
          "type2 must be an OpTypeStruct instruction.");
-  const std::vector<Decoration>& type1_decorations =
-      _.id_decorations(type1->id());
-  const std::vector<Decoration>& type2_decorations =
-      _.id_decorations(type2->id());
+  const std::set<Decoration>& type1_decorations = _.id_decorations(type1->id());
+  const std::set<Decoration>& type2_decorations = _.id_decorations(type2->id());
 
   // TODO: Will have to add other check for arrays an matricies if we want to
   // handle them.
@@ -120,8 +118,8 @@
 }
 
 bool HasConflictingMemberOffsets(
-    const std::vector<Decoration>& type1_decorations,
-    const std::vector<Decoration>& type2_decorations) {
+    const std::set<Decoration>& type1_decorations,
+    const std::set<Decoration>& type2_decorations) {
   {
     // We are interested in conflicting decoration.  If a decoration is in one
     // list but not the other, then we will assume the code is correct.  We are
@@ -526,8 +524,8 @@
     if (storage_class == SpvStorageClassPushConstant) {
       if (pointee->opcode() != SpvOpTypeStruct) {
         return _.diag(SPV_ERROR_INVALID_ID, inst)
-               << "PushConstant OpVariable <id> '" << _.getIdName(inst->id())
-               << "' has illegal type.\n"
+               << _.VkErrorID(6808) << "PushConstant OpVariable <id> '"
+               << _.getIdName(inst->id()) << "' has illegal type.\n"
                << "From Vulkan spec, Push Constant Interface section:\n"
                << "Such variables must be typed as OpTypeStruct";
       }
@@ -554,9 +552,9 @@
     if (storage_class == SpvStorageClassUniform) {
       if (!IsAllowedTypeOrArrayOfSame(_, pointee, {SpvOpTypeStruct})) {
         return _.diag(SPV_ERROR_INVALID_ID, inst)
-               << "Uniform OpVariable <id> '" << _.getIdName(inst->id())
-               << "' has illegal type.\n"
-               << "From Vulkan spec, section 14.5.2:\n"
+               << _.VkErrorID(6807) << "Uniform OpVariable <id> '"
+               << _.getIdName(inst->id()) << "' has illegal type.\n"
+               << "From Vulkan spec:\n"
                << "Variables identified with the Uniform storage class are "
                << "used to access transparent buffer backed resources. Such "
                << "variables must be typed as OpTypeStruct, or an array of "
@@ -567,9 +565,9 @@
     if (storage_class == SpvStorageClassStorageBuffer) {
       if (!IsAllowedTypeOrArrayOfSame(_, pointee, {SpvOpTypeStruct})) {
         return _.diag(SPV_ERROR_INVALID_ID, inst)
-               << "StorageBuffer OpVariable <id> '" << _.getIdName(inst->id())
-               << "' has illegal type.\n"
-               << "From Vulkan spec, section 14.5.2:\n"
+               << _.VkErrorID(6807) << "StorageBuffer OpVariable <id> '"
+               << _.getIdName(inst->id()) << "' has illegal type.\n"
+               << "From Vulkan spec:\n"
                << "Variables identified with the StorageBuffer storage class "
                   "are used to access transparent buffer backed resources. "
                   "Such variables must be typed as OpTypeStruct, or an array "
@@ -982,6 +980,7 @@
         }
         if (_.HasDecoration(base_type->id(), SpvDecorationBlock)) {
           return _.diag(SPV_ERROR_INVALID_ID, inst)
+                 << _.VkErrorID(6925)
                  << "In the Vulkan environment, cannot store to Uniform Blocks";
         }
       }
diff --git a/source/val/validate_misc.cpp b/source/val/validate_misc.cpp
index 3bc15ca..5acc21e 100644
--- a/source/val/validate_misc.cpp
+++ b/source/val/validate_misc.cpp
@@ -59,10 +59,7 @@
   // a vector of two - components of 32 -
   // bit unsigned integer type
   const uint32_t result_type = inst->type_id();
-  if (!(_.IsUnsignedIntScalarType(result_type) &&
-        _.GetBitWidth(result_type) == 64) &&
-      !(_.IsUnsignedIntVectorType(result_type) &&
-        _.GetDimension(result_type) == 2 && _.GetBitWidth(result_type) == 32)) {
+  if (!_.IsUnsigned64BitHandle(result_type)) {
     return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Expected Value to be a "
                                                    "vector of two components"
                                                    " of unsigned integer"
diff --git a/source/val/validate_mode_setting.cpp b/source/val/validate_mode_setting.cpp
index 9635268..67b10c5 100644
--- a/source/val/validate_mode_setting.cpp
+++ b/source/val/validate_mode_setting.cpp
@@ -112,6 +112,44 @@
                  << "Fragment execution model entry points can specify at most "
                     "one fragment shader interlock execution mode.";
         }
+        if (execution_modes &&
+            1 < std::count_if(
+                    execution_modes->begin(), execution_modes->end(),
+                    [](const SpvExecutionMode& mode) {
+                      switch (mode) {
+                        case SpvExecutionModeStencilRefUnchangedFrontAMD:
+                        case SpvExecutionModeStencilRefLessFrontAMD:
+                        case SpvExecutionModeStencilRefGreaterFrontAMD:
+                          return true;
+                        default:
+                          return false;
+                      }
+                    })) {
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << "Fragment execution model entry points can specify at most "
+                    "one of StencilRefUnchangedFrontAMD, "
+                    "StencilRefLessFrontAMD or StencilRefGreaterFrontAMD "
+                    "execution modes.";
+        }
+        if (execution_modes &&
+            1 < std::count_if(
+                    execution_modes->begin(), execution_modes->end(),
+                    [](const SpvExecutionMode& mode) {
+                      switch (mode) {
+                        case SpvExecutionModeStencilRefUnchangedBackAMD:
+                        case SpvExecutionModeStencilRefLessBackAMD:
+                        case SpvExecutionModeStencilRefGreaterBackAMD:
+                          return true;
+                        default:
+                          return false;
+                      }
+                    })) {
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << "Fragment execution model entry points can specify at most "
+                    "one of StencilRefUnchangedBackAMD, "
+                    "StencilRefLessBackAMD or StencilRefGreaterBackAMD "
+                    "execution modes.";
+        }
         break;
       case SpvExecutionModelTessellationControl:
       case SpvExecutionModelTessellationEvaluation:
@@ -412,6 +450,13 @@
     case SpvExecutionModeSampleInterlockUnorderedEXT:
     case SpvExecutionModeShadingRateInterlockOrderedEXT:
     case SpvExecutionModeShadingRateInterlockUnorderedEXT:
+    case SpvExecutionModeEarlyAndLateFragmentTestsAMD:
+    case SpvExecutionModeStencilRefUnchangedFrontAMD:
+    case SpvExecutionModeStencilRefGreaterFrontAMD:
+    case SpvExecutionModeStencilRefLessFrontAMD:
+    case SpvExecutionModeStencilRefUnchangedBackAMD:
+    case SpvExecutionModeStencilRefGreaterBackAMD:
+    case SpvExecutionModeStencilRefLessBackAMD:
       if (!std::all_of(models->begin(), models->end(),
                        [](const SpvExecutionModel& model) {
                          return model == SpvExecutionModelFragment;
diff --git a/source/val/validate_ray_query.cpp b/source/val/validate_ray_query.cpp
new file mode 100644
index 0000000..f92bf01
--- /dev/null
+++ b/source/val/validate_ray_query.cpp
@@ -0,0 +1,271 @@
+// Copyright (c) 2022 The Khronos Group Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Validates ray query instructions from SPV_KHR_ray_query
+
+#include "source/opcode.h"
+#include "source/val/instruction.h"
+#include "source/val/validate.h"
+#include "source/val/validation_state.h"
+
+namespace spvtools {
+namespace val {
+namespace {
+
+spv_result_t ValidateRayQueryPointer(ValidationState_t& _,
+                                     const Instruction* inst,
+                                     uint32_t ray_query_index) {
+  const uint32_t ray_query_id = inst->GetOperandAs<uint32_t>(ray_query_index);
+  auto variable = _.FindDef(ray_query_id);
+  if (!variable || (variable->opcode() != SpvOpVariable &&
+                    variable->opcode() != SpvOpFunctionParameter)) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Ray Query must be a memory object declaration";
+  }
+  auto pointer = _.FindDef(variable->GetOperandAs<uint32_t>(0));
+  if (!pointer || pointer->opcode() != SpvOpTypePointer) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Ray Query must be a pointer";
+  }
+  auto type = _.FindDef(pointer->GetOperandAs<uint32_t>(2));
+  if (!type || type->opcode() != SpvOpTypeRayQueryKHR) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Ray Query must be a pointer to OpTypeRayQueryKHR";
+  }
+  return SPV_SUCCESS;
+}
+
+spv_result_t ValidateIntersectionId(ValidationState_t& _,
+                                    const Instruction* inst,
+                                    uint32_t intersection_index) {
+  const uint32_t intersection_id =
+      inst->GetOperandAs<uint32_t>(intersection_index);
+  const uint32_t intersection_type = _.GetTypeId(intersection_id);
+  const SpvOp intersection_opcode = _.GetIdOpcode(intersection_id);
+  if (!_.IsIntScalarType(intersection_type) ||
+      _.GetBitWidth(intersection_type) != 32 ||
+      !spvOpcodeIsConstant(intersection_opcode)) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "expected Intersection ID to be a constant 32-bit int scalar";
+  }
+
+  return SPV_SUCCESS;
+}
+
+}  // namespace
+
+spv_result_t RayQueryPass(ValidationState_t& _, const Instruction* inst) {
+  const SpvOp opcode = inst->opcode();
+  const uint32_t result_type = inst->type_id();
+
+  switch (opcode) {
+    case SpvOpRayQueryInitializeKHR: {
+      if (auto error = ValidateRayQueryPointer(_, inst, 0)) return error;
+
+      if (_.GetIdOpcode(_.GetOperandTypeId(inst, 1)) !=
+          SpvOpTypeAccelerationStructureKHR) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Expected Acceleration Structure to be of type "
+                  "OpTypeAccelerationStructureKHR";
+      }
+
+      const uint32_t ray_flags = _.GetOperandTypeId(inst, 2);
+      if (!_.IsIntScalarType(ray_flags) || _.GetBitWidth(ray_flags) != 32) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Ray Flags must be a 32-bit int scalar";
+      }
+
+      const uint32_t cull_mask = _.GetOperandTypeId(inst, 3);
+      if (!_.IsIntScalarType(cull_mask) || _.GetBitWidth(cull_mask) != 32) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Cull Mask must be a 32-bit int scalar";
+      }
+
+      const uint32_t ray_origin = _.GetOperandTypeId(inst, 4);
+      if (!_.IsFloatVectorType(ray_origin) || _.GetDimension(ray_origin) != 3 ||
+          _.GetBitWidth(ray_origin) != 32) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Ray Origin must be a 32-bit float 3-component vector";
+      }
+
+      const uint32_t ray_tmin = _.GetOperandTypeId(inst, 5);
+      if (!_.IsFloatScalarType(ray_tmin) || _.GetBitWidth(ray_tmin) != 32) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Ray TMin must be a 32-bit float scalar";
+      }
+
+      const uint32_t ray_direction = _.GetOperandTypeId(inst, 6);
+      if (!_.IsFloatVectorType(ray_direction) ||
+          _.GetDimension(ray_direction) != 3 ||
+          _.GetBitWidth(ray_direction) != 32) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Ray Direction must be a 32-bit float 3-component vector";
+      }
+
+      const uint32_t ray_tmax = _.GetOperandTypeId(inst, 7);
+      if (!_.IsFloatScalarType(ray_tmax) || _.GetBitWidth(ray_tmax) != 32) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Ray TMax must be a 32-bit float scalar";
+      }
+      break;
+    }
+
+    case SpvOpRayQueryTerminateKHR:
+    case SpvOpRayQueryConfirmIntersectionKHR: {
+      if (auto error = ValidateRayQueryPointer(_, inst, 0)) return error;
+      break;
+    }
+
+    case SpvOpRayQueryGenerateIntersectionKHR: {
+      if (auto error = ValidateRayQueryPointer(_, inst, 0)) return error;
+
+      const uint32_t hit_t_id = _.GetOperandTypeId(inst, 1);
+      if (!_.IsFloatScalarType(hit_t_id) || _.GetBitWidth(hit_t_id) != 32) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Hit T must be a 32-bit float scalar";
+      }
+
+      break;
+    }
+
+    case SpvOpRayQueryGetIntersectionFrontFaceKHR:
+    case SpvOpRayQueryProceedKHR:
+    case SpvOpRayQueryGetIntersectionCandidateAABBOpaqueKHR: {
+      if (auto error = ValidateRayQueryPointer(_, inst, 2)) return error;
+
+      if (!_.IsBoolScalarType(result_type)) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "expected Result Type to be bool scalar type";
+      }
+
+      if (opcode == SpvOpRayQueryGetIntersectionFrontFaceKHR) {
+        if (auto error = ValidateIntersectionId(_, inst, 3)) return error;
+      }
+
+      break;
+    }
+
+    case SpvOpRayQueryGetIntersectionTKHR:
+    case SpvOpRayQueryGetRayTMinKHR: {
+      if (auto error = ValidateRayQueryPointer(_, inst, 2)) return error;
+
+      if (!_.IsFloatScalarType(result_type) ||
+          _.GetBitWidth(result_type) != 32) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "expected Result Type to be 32-bit float scalar type";
+      }
+
+      if (opcode == SpvOpRayQueryGetIntersectionTKHR) {
+        if (auto error = ValidateIntersectionId(_, inst, 3)) return error;
+      }
+
+      break;
+    }
+
+    case SpvOpRayQueryGetIntersectionTypeKHR:
+    case SpvOpRayQueryGetIntersectionInstanceCustomIndexKHR:
+    case SpvOpRayQueryGetIntersectionInstanceIdKHR:
+    case SpvOpRayQueryGetIntersectionInstanceShaderBindingTableRecordOffsetKHR:
+    case SpvOpRayQueryGetIntersectionGeometryIndexKHR:
+    case SpvOpRayQueryGetIntersectionPrimitiveIndexKHR:
+    case SpvOpRayQueryGetRayFlagsKHR: {
+      if (auto error = ValidateRayQueryPointer(_, inst, 2)) return error;
+
+      if (!_.IsIntScalarType(result_type) || _.GetBitWidth(result_type) != 32) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "expected Result Type to be 32-bit int scalar type";
+      }
+
+      if (opcode != SpvOpRayQueryGetRayFlagsKHR) {
+        if (auto error = ValidateIntersectionId(_, inst, 3)) return error;
+      }
+
+      break;
+    }
+
+    case SpvOpRayQueryGetIntersectionObjectRayDirectionKHR:
+    case SpvOpRayQueryGetIntersectionObjectRayOriginKHR:
+    case SpvOpRayQueryGetWorldRayDirectionKHR:
+    case SpvOpRayQueryGetWorldRayOriginKHR: {
+      if (auto error = ValidateRayQueryPointer(_, inst, 2)) return error;
+
+      if (!_.IsFloatVectorType(result_type) ||
+          _.GetDimension(result_type) != 3 ||
+          _.GetBitWidth(result_type) != 32) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "expected Result Type to be 32-bit float 3-component "
+                  "vector type";
+      }
+
+      if (opcode == SpvOpRayQueryGetIntersectionObjectRayDirectionKHR ||
+          opcode == SpvOpRayQueryGetIntersectionObjectRayOriginKHR) {
+        if (auto error = ValidateIntersectionId(_, inst, 3)) return error;
+      }
+
+      break;
+    }
+
+    case SpvOpRayQueryGetIntersectionBarycentricsKHR: {
+      if (auto error = ValidateRayQueryPointer(_, inst, 2)) return error;
+      if (auto error = ValidateIntersectionId(_, inst, 3)) return error;
+
+      if (!_.IsFloatVectorType(result_type) ||
+          _.GetDimension(result_type) != 2 ||
+          _.GetBitWidth(result_type) != 32) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "expected Result Type to be 32-bit float 2-component "
+                  "vector type";
+      }
+
+      break;
+    }
+
+    case SpvOpRayQueryGetIntersectionObjectToWorldKHR:
+    case SpvOpRayQueryGetIntersectionWorldToObjectKHR: {
+      if (auto error = ValidateRayQueryPointer(_, inst, 2)) return error;
+      if (auto error = ValidateIntersectionId(_, inst, 3)) return error;
+
+      uint32_t num_rows = 0;
+      uint32_t num_cols = 0;
+      uint32_t col_type = 0;
+      uint32_t component_type = 0;
+      if (!_.GetMatrixTypeInfo(result_type, &num_rows, &num_cols, &col_type,
+                               &component_type)) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "expected matrix type as Result Type";
+      }
+
+      if (num_cols != 4) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "expected Result Type matrix to have a Column Count of 4";
+      }
+
+      if (!_.IsFloatScalarType(component_type) ||
+          _.GetBitWidth(result_type) != 32 || num_rows != 3) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "expected Result Type matrix to have a Column Type of "
+                  "3-component 32-bit float vectors";
+      }
+      break;
+    }
+
+    default:
+      break;
+  }
+
+  return SPV_SUCCESS;
+}
+
+}  // namespace val
+}  // namespace spvtools
diff --git a/source/val/validate_scopes.cpp b/source/val/validate_scopes.cpp
index 1c5f70a..887e8d1 100644
--- a/source/val/validate_scopes.cpp
+++ b/source/val/validate_scopes.cpp
@@ -220,30 +220,23 @@
 
   // Vulkan Specific rules
   if (spvIsVulkanEnv(_.context()->target_env)) {
-    if (value == SpvScopeCrossDevice) {
-      return _.diag(SPV_ERROR_INVALID_DATA, inst)
-             << _.VkErrorID(4638) << spvOpcodeString(opcode)
-             << ": in Vulkan environment, Memory Scope cannot be CrossDevice";
-    }
-    // Vulkan 1.0 specific rules
-    if (_.context()->target_env == SPV_ENV_VULKAN_1_0 &&
-        value != SpvScopeDevice && value != SpvScopeWorkgroup &&
-        value != SpvScopeInvocation) {
-      return _.diag(SPV_ERROR_INVALID_DATA, inst)
-             << _.VkErrorID(4638) << spvOpcodeString(opcode)
-             << ": in Vulkan 1.0 environment Memory Scope is limited to "
-             << "Device, Workgroup and Invocation";
-    }
-    // Vulkan 1.1 specific rules
-    if ((_.context()->target_env == SPV_ENV_VULKAN_1_1 ||
-         _.context()->target_env == SPV_ENV_VULKAN_1_2) &&
-        value != SpvScopeDevice && value != SpvScopeWorkgroup &&
+    if (value != SpvScopeDevice && value != SpvScopeWorkgroup &&
         value != SpvScopeSubgroup && value != SpvScopeInvocation &&
-        value != SpvScopeShaderCallKHR) {
+        value != SpvScopeShaderCallKHR && value != SpvScopeQueueFamily) {
       return _.diag(SPV_ERROR_INVALID_DATA, inst)
              << _.VkErrorID(4638) << spvOpcodeString(opcode)
-             << ": in Vulkan 1.1 and 1.2 environment Memory Scope is limited "
-             << "to Device, Workgroup, Invocation, and ShaderCall";
+             << ": in Vulkan environment Memory Scope is limited to Device, "
+                "QueueFamily, Workgroup, ShaderCallKHR, Subgroup, or "
+                "Invocation";
+    } else if (_.context()->target_env == SPV_ENV_VULKAN_1_0 &&
+               value == SpvScopeSubgroup &&
+               !_.HasCapability(SpvCapabilitySubgroupBallotKHR) &&
+               !_.HasCapability(SpvCapabilitySubgroupVoteKHR)) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << spvOpcodeString(opcode)
+             << ": in Vulkan 1.0 environment Memory Scope is can not be "
+                "Subgroup without SubgroupBallotKHR or SubgroupVoteKHR "
+                "declared";
     }
 
     if (value == SpvScopeShaderCallKHR) {
diff --git a/source/val/validate_type.cpp b/source/val/validate_type.cpp
index 2aded61..b0b6079 100644
--- a/source/val/validate_type.cpp
+++ b/source/val/validate_type.cpp
@@ -306,35 +306,6 @@
   return SPV_SUCCESS;
 }
 
-bool ContainsOpaqueType(ValidationState_t& _, const Instruction* str) {
-  const size_t elem_type_index = 1;
-  uint32_t elem_type_id;
-  Instruction* elem_type;
-
-  if (spvOpcodeIsBaseOpaqueType(str->opcode())) {
-    return true;
-  }
-
-  switch (str->opcode()) {
-    case SpvOpTypeArray:
-    case SpvOpTypeRuntimeArray:
-      elem_type_id = str->GetOperandAs<uint32_t>(elem_type_index);
-      elem_type = _.FindDef(elem_type_id);
-      return ContainsOpaqueType(_, elem_type);
-    case SpvOpTypeStruct:
-      for (size_t member_type_index = 1;
-           member_type_index < str->operands().size(); ++member_type_index) {
-        auto member_type_id = str->GetOperandAs<uint32_t>(member_type_index);
-        auto member_type = _.FindDef(member_type_id);
-        if (ContainsOpaqueType(_, member_type)) return true;
-      }
-      break;
-    default:
-      break;
-  }
-  return false;
-}
-
 spv_result_t ValidateTypeStruct(ValidationState_t& _, const Instruction* inst) {
   const uint32_t struct_id = inst->GetOperandAs<uint32_t>(0);
   for (size_t member_type_index = 1;
@@ -425,8 +396,21 @@
     _.RegisterStructTypeWithBuiltInMember(struct_id);
   }
 
+  const auto isOpaqueType = [&_](const Instruction* opaque_inst) {
+    auto opcode = opaque_inst->opcode();
+    if (_.HasCapability(SpvCapabilityBindlessTextureNV) &&
+        (opcode == SpvOpTypeImage || opcode == SpvOpTypeSampler ||
+         opcode == SpvOpTypeSampledImage)) {
+      return false;
+    } else if (spvOpcodeIsBaseOpaqueType(opcode)) {
+      return true;
+    }
+    return false;
+  };
+
   if (spvIsVulkanEnv(_.context()->target_env) &&
-      !_.options()->before_hlsl_legalization && ContainsOpaqueType(_, inst)) {
+      !_.options()->before_hlsl_legalization &&
+      _.ContainsType(inst->id(), isOpaqueType)) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
            << _.VkErrorID(4667) << "In "
            << spvLogStringForEnv(_.context()->target_env)
diff --git a/source/val/validation_state.cpp b/source/val/validation_state.cpp
index d9422b2..adfe75b 100644
--- a/source/val/validation_state.cpp
+++ b/source/val/validation_state.cpp
@@ -90,6 +90,8 @@
       if (current_section == kLayoutFunctionDeclarations)
         return kLayoutFunctionDeclarations;
       return kLayoutFunctionDefinitions;
+    case SpvOpSamplerImageAddressingModeNV:
+      return kLayoutSamplerImageAddressMode;
     default:
       break;
   }
@@ -161,6 +163,7 @@
       addressing_model_(SpvAddressingModelMax),
       memory_model_(SpvMemoryModelMax),
       pointer_size_and_alignment_(0),
+      sampler_image_addressing_mode_(0),
       in_function_(false),
       num_of_warnings_(0),
       max_num_of_warnings_(max_warnings) {
@@ -473,6 +476,15 @@
 
 SpvMemoryModel ValidationState_t::memory_model() const { return memory_model_; }
 
+void ValidationState_t::set_samplerimage_variable_address_mode(
+    uint32_t bit_width) {
+  sampler_image_addressing_mode_ = bit_width;
+}
+
+uint32_t ValidationState_t::samplerimage_variable_address_mode() const {
+  return sampler_image_addressing_mode_;
+}
+
 spv_result_t ValidationState_t::RegisterFunction(
     uint32_t id, uint32_t ret_type_id, SpvFunctionControlMask function_control,
     uint32_t function_type_id) {
@@ -965,6 +977,11 @@
   return true;
 }
 
+bool ValidationState_t::IsAccelerationStructureType(uint32_t id) const {
+  const Instruction* inst = FindDef(id);
+  return inst && inst->opcode() == SpvOpTypeAccelerationStructureKHR;
+}
+
 bool ValidationState_t::IsCooperativeMatrixType(uint32_t id) const {
   const Instruction* inst = FindDef(id);
   return inst && inst->opcode() == SpvOpTypeCooperativeMatrixNV;
@@ -985,6 +1002,13 @@
   return IsUnsignedIntScalarType(FindDef(id)->word(2));
 }
 
+// Either a 32 bit 2-component uint vector or a 64 bit uint scalar
+bool ValidationState_t::IsUnsigned64BitHandle(uint32_t id) const {
+  return ((IsUnsignedIntScalarType(id) && GetBitWidth(id) == 64) ||
+          (IsUnsignedIntVectorType(id) && GetDimension(id) == 2 &&
+           GetBitWidth(id) == 32));
+}
+
 spv_result_t ValidationState_t::CooperativeMatrixShapesMatch(
     const Instruction* inst, uint32_t m1, uint32_t m2) {
   const auto m1_type = FindDef(m1);
@@ -1878,7 +1902,7 @@
     case 4677:
       return VUID_WRAP(VUID-StandaloneSpirv-Invariant-04677);
     case 4680:
-      return VUID_WRAP( VUID-StandaloneSpirv-OpTypeRuntimeArray-04680);
+      return VUID_WRAP(VUID-StandaloneSpirv-OpTypeRuntimeArray-04680);
     case 4682:
       return VUID_WRAP(VUID-StandaloneSpirv-OpControlBarrier-04682);
     case 6426:
@@ -1903,6 +1927,8 @@
       return VUID_WRAP(VUID-StandaloneSpirv-OpMemoryBarrier-04733);
     case 4734:
       return VUID_WRAP(VUID-StandaloneSpirv-OpVariable-04734);
+    case 4744:
+      return VUID_WRAP(VUID-StandaloneSpirv-Flat-04744);
     case 4777:
       return VUID_WRAP(VUID-StandaloneSpirv-OpImage-04777);
     case 4780:
@@ -1919,6 +1945,10 @@
       return VUID_WRAP(VUID-StandaloneSpirv-Location-04918);
     case 4919:
       return VUID_WRAP(VUID-StandaloneSpirv-Location-04919);
+    case 6201:
+      return VUID_WRAP(VUID-StandaloneSpirv-Flat-06201);
+    case 6202:
+      return VUID_WRAP(VUID-StandaloneSpirv-Flat-06202);
     case 6214:
       return VUID_WRAP(VUID-StandaloneSpirv-OpTypeImage-06214);
     case 6491:
@@ -1941,6 +1971,12 @@
       return VUID_WRAP(VUID-StandaloneSpirv-PerVertexKHR-06777);
     case 6778:
       return VUID_WRAP(VUID-StandaloneSpirv-Input-06778);
+    case 6807:
+      return VUID_WRAP(VUID-StandaloneSpirv-Uniform-06807);
+    case 6808:
+      return VUID_WRAP(VUID-StandaloneSpirv-PushConstant-06808);
+    case 6925:
+      return VUID_WRAP(VUID-StandaloneSpirv-Uniform-06925);
     default:
       return "";  // unknown id
   }
diff --git a/source/val/validation_state.h b/source/val/validation_state.h
index 4888840..b4d343d 100644
--- a/source/val/validation_state.h
+++ b/source/val/validation_state.h
@@ -44,19 +44,20 @@
 /// of the SPIRV spec for additional details of the order. The enumerant values
 /// are in the same order as the vector returned by GetModuleOrder
 enum ModuleLayoutSection {
-  kLayoutCapabilities,          /// < Section 2.4 #1
-  kLayoutExtensions,            /// < Section 2.4 #2
-  kLayoutExtInstImport,         /// < Section 2.4 #3
-  kLayoutMemoryModel,           /// < Section 2.4 #4
-  kLayoutEntryPoint,            /// < Section 2.4 #5
-  kLayoutExecutionMode,         /// < Section 2.4 #6
-  kLayoutDebug1,                /// < Section 2.4 #7 > 1
-  kLayoutDebug2,                /// < Section 2.4 #7 > 2
-  kLayoutDebug3,                /// < Section 2.4 #7 > 3
-  kLayoutAnnotations,           /// < Section 2.4 #8
-  kLayoutTypes,                 /// < Section 2.4 #9
-  kLayoutFunctionDeclarations,  /// < Section 2.4 #10
-  kLayoutFunctionDefinitions    /// < Section 2.4 #11
+  kLayoutCapabilities,             /// < Section 2.4 #1
+  kLayoutExtensions,               /// < Section 2.4 #2
+  kLayoutExtInstImport,            /// < Section 2.4 #3
+  kLayoutMemoryModel,              /// < Section 2.4 #4
+  kLayoutSamplerImageAddressMode,  /// < Section 2.4 #5
+  kLayoutEntryPoint,               /// < Section 2.4 #6
+  kLayoutExecutionMode,            /// < Section 2.4 #7
+  kLayoutDebug1,                   /// < Section 2.4 #8 > 1
+  kLayoutDebug2,                   /// < Section 2.4 #8 > 2
+  kLayoutDebug3,                   /// < Section 2.4 #8 > 3
+  kLayoutAnnotations,              /// < Section 2.4 #9
+  kLayoutTypes,                    /// < Section 2.4 #10
+  kLayoutFunctionDeclarations,     /// < Section 2.4 #11
+  kLayoutFunctionDefinitions       /// < Section 2.4 #12
 };
 
 /// This class manages the state of the SPIR-V validation as it is being parsed.
@@ -360,6 +361,20 @@
   /// Returns the memory model of this module, or Simple if uninitialized.
   SpvMemoryModel memory_model() const;
 
+  /// Sets the bit width for sampler/image type variables. If not set, they are
+  /// considered opaque
+  void set_samplerimage_variable_address_mode(uint32_t bit_width);
+
+  /// Get the addressing mode currently set. If 0, it means addressing mode is
+  /// invalid Sampler/Image type variables must be considered opaque This mode
+  /// is only valid after the instruction has been read
+  uint32_t samplerimage_variable_address_mode() const;
+
+  /// Returns true if the OpSamplerImageAddressingModeNV was found.
+  bool has_samplerimage_variable_address_mode_specified() const {
+    return sampler_image_addressing_mode_ != 0;
+  }
+
   const AssemblyGrammar& grammar() const { return grammar_; }
 
   /// Inserts the instruction into the list of ordered instructions in the file.
@@ -375,17 +390,14 @@
   /// Registers the decoration for the given <id>
   void RegisterDecorationForId(uint32_t id, const Decoration& dec) {
     auto& dec_list = id_decorations_[id];
-    auto lb = std::find(dec_list.begin(), dec_list.end(), dec);
-    if (lb == dec_list.end()) {
-      dec_list.push_back(dec);
-    }
+    dec_list.insert(dec);
   }
 
   /// Registers the list of decorations for the given <id>
   template <class InputIt>
   void RegisterDecorationsForId(uint32_t id, InputIt begin, InputIt end) {
-    std::vector<Decoration>& cur_decs = id_decorations_[id];
-    cur_decs.insert(cur_decs.end(), begin, end);
+    std::set<Decoration>& cur_decs = id_decorations_[id];
+    cur_decs.insert(begin, end);
   }
 
   /// Registers the list of decorations for the given member of the given
@@ -394,21 +406,44 @@
   void RegisterDecorationsForStructMember(uint32_t struct_id,
                                           uint32_t member_index, InputIt begin,
                                           InputIt end) {
-    RegisterDecorationsForId(struct_id, begin, end);
-    for (auto& decoration : id_decorations_[struct_id]) {
-      decoration.set_struct_member_index(member_index);
+    std::set<Decoration>& cur_decs = id_decorations_[struct_id];
+    for (InputIt iter = begin; iter != end; ++iter) {
+      Decoration dec = *iter;
+      dec.set_struct_member_index(member_index);
+      cur_decs.insert(dec);
     }
   }
 
   /// Returns all the decorations for the given <id>. If no decorations exist
-  /// for the <id>, it registers an empty vector for it in the map and
-  /// returns the empty vector.
-  std::vector<Decoration>& id_decorations(uint32_t id) {
+  /// for the <id>, it registers an empty set for it in the map and
+  /// returns the empty set.
+  std::set<Decoration>& id_decorations(uint32_t id) {
     return id_decorations_[id];
   }
 
+  /// Returns the range of decorations for the given field of the given <id>.
+  struct FieldDecorationsIter {
+    std::set<Decoration>::const_iterator begin;
+    std::set<Decoration>::const_iterator end;
+  };
+  FieldDecorationsIter id_member_decorations(uint32_t id,
+                                             uint32_t member_index) {
+    const auto& decorations = id_decorations_[id];
+
+    // The decorations are sorted by member_index, so this look up will give the
+    // exact range of decorations for this member index.
+    Decoration min_decoration((SpvDecoration)0, {}, member_index);
+    Decoration max_decoration(SpvDecorationMax, {}, member_index);
+
+    FieldDecorationsIter result;
+    result.begin = decorations.lower_bound(min_decoration);
+    result.end = decorations.upper_bound(max_decoration);
+
+    return result;
+  }
+
   // Returns const pointer to the internal decoration container.
-  const std::map<uint32_t, std::vector<Decoration>>& id_decorations() const {
+  const std::map<uint32_t, std::set<Decoration>>& id_decorations() const {
     return id_decorations_;
   }
 
@@ -572,10 +607,12 @@
   bool IsBoolVectorType(uint32_t id) const;
   bool IsBoolScalarOrVectorType(uint32_t id) const;
   bool IsPointerType(uint32_t id) const;
+  bool IsAccelerationStructureType(uint32_t id) const;
   bool IsCooperativeMatrixType(uint32_t id) const;
   bool IsFloatCooperativeMatrixType(uint32_t id) const;
   bool IsIntCooperativeMatrixType(uint32_t id) const;
   bool IsUnsignedIntCooperativeMatrixType(uint32_t id) const;
+  bool IsUnsigned64BitHandle(uint32_t id) const;
 
   // Returns true if |id| is a type id that contains |type| (or integer or
   // floating point type) of |width| bits.
@@ -826,7 +863,7 @@
       struct_has_nested_blockorbufferblock_struct_;
 
   /// Stores the list of decorations for a given <id>
-  std::map<uint32_t, std::vector<Decoration>> id_decorations_;
+  std::map<uint32_t, std::set<Decoration>> id_decorations_;
 
   /// Stores type declarations which need to be unique (i.e. non-aggregates),
   /// in the form [opcode, operand words], result_id is not stored.
@@ -842,7 +879,10 @@
   // have the same pointer size (for physical pointer types).
   uint32_t pointer_size_and_alignment_;
 
-  /// NOTE: See corresponding getter functions
+  /// bit width of sampler/image type variables. Valid values are 32 and 64
+  uint32_t sampler_image_addressing_mode_;
+
+  /// NOTE: See correspoding getter functions
   bool in_function_;
 
   /// The state of optional features.  These are determined by capabilities
diff --git a/test/binary_to_text_test.cpp b/test/binary_to_text_test.cpp
index df703e5..44705f2 100644
--- a/test/binary_to_text_test.cpp
+++ b/test/binary_to_text_test.cpp
@@ -101,6 +101,17 @@
   spvTextDestroy(text);
 }
 
+TEST_F(BinaryToText, Print) {
+  spv_text text = nullptr;
+  spv_diagnostic diagnostic = nullptr;
+  ASSERT_EQ(
+      SPV_SUCCESS,
+      spvBinaryToText(context, binary->code, binary->wordCount,
+                      SPV_BINARY_TO_TEXT_OPTION_PRINT, &text, &diagnostic));
+  ASSERT_EQ(text, nullptr);
+  spvTextDestroy(text);
+}
+
 TEST_F(BinaryToText, MissingModule) {
   spv_text text;
   spv_diagnostic diagnostic = nullptr;
diff --git a/test/hex_float_test.cpp b/test/hex_float_test.cpp
index 7edfd43..25d3c70 100644
--- a/test/hex_float_test.cpp
+++ b/test/hex_float_test.cpp
@@ -1395,6 +1395,47 @@
         {"0x1.0p1+", true, "+", 2.0f},
         {"0x1.0p1-", true, "-", 2.0f}}));
 
+INSTANTIATE_TEST_SUITE_P(
+    HexFloatPositiveExponentOverflow, FloatStreamParseTest,
+    ::testing::ValuesIn(std::vector<StreamParseCase<float>>{
+        // Positive exponents
+        {"0x1.0p1", true, "", 2.0f},       // fine, a normal number
+        {"0x1.0p15", true, "", 32768.0f},  // fine, a normal number
+        {"0x1.0p127", true, "", float(ldexp(1.0f, 127))},   // good large number
+        {"0x0.8p128", true, "", float(ldexp(1.0f, 127))},   // good large number
+        {"0x0.1p131", true, "", float(ldexp(1.0f, 127))},   // good large number
+        {"0x0.01p135", true, "", float(ldexp(1.0f, 127))},  // good large number
+        {"0x1.0p128", true, "", float(ldexp(1.0f, 128))},   // infinity
+        {"0x1.0p4294967295", true, "", float(ldexp(1.0f, 128))},  // infinity
+        {"0x1.0p5000000000", true, "", float(ldexp(1.0f, 128))},  // infinity
+        {"0x0.0p5000000000", true, "", 0.0f},  // zero mantissa, zero result
+    }));
+
+INSTANTIATE_TEST_SUITE_P(
+    HexFloatNegativeExponentOverflow, FloatStreamParseTest,
+    ::testing::ValuesIn(std::vector<StreamParseCase<float>>{
+        // Positive results, digits before '.'
+        {"0x1.0p-126", true, "",
+         float(ldexp(1.0f, -126))},  // fine, a small normal number
+        {"0x1.0p-127", true, "", float(ldexp(1.0f, -127))},  // denorm number
+        {"0x1.0p-149", true, "",
+         float(ldexp(1.0f, -149))},  // smallest positive denormal
+        {"0x0.8p-148", true, "",
+         float(ldexp(1.0f, -149))},  // smallest positive denormal
+        {"0x0.1p-145", true, "",
+         float(ldexp(1.0f, -149))},  // smallest positive denormal
+        {"0x0.01p-141", true, "",
+         float(ldexp(1.0f, -149))},  // smallest positive denormal
+
+        // underflow rounds down to zero
+        {"0x1.0p-150", true, "", 0.0f},
+        {"0x1.0p-4294967296", true, "",
+         0.0f},  // avoid exponent overflow in parser
+        {"0x1.0p-5000000000", true, "",
+         0.0f},  // avoid exponent overflow in parser
+        {"0x0.0p-5000000000", true, "", 0.0f},  // zero mantissa, zero result
+    }));
+
 // TODO(awoloszyn): Add fp16 tests and HexFloatTraits.
 }  // namespace
 }  // namespace utils
diff --git a/test/opt/aggressive_dead_code_elim_test.cpp b/test/opt/aggressive_dead_code_elim_test.cpp
index 25f8541..89cb56f 100644
--- a/test/opt/aggressive_dead_code_elim_test.cpp
+++ b/test/opt/aggressive_dead_code_elim_test.cpp
@@ -3541,9 +3541,11 @@
 OpBranch %16
 %16 = OpLabel
 OpSelectionMerge %18 None
-OpSwitch %13 %18 0 %17 1 %15
+OpSwitch %13 %18 0 %17 1 %19
 %17 = OpLabel
 OpStore %3 %uint_1
+OpBranch %19
+%19 = OpLabel
 OpBranch %15
 %15 = OpLabel
 OpBranch %12
@@ -7626,6 +7628,55 @@
   SinglePassRunAndCheck<AggressiveDCEPass>(text, text, false);
 }
 
+TEST_F(AggressiveDCETest, FunctionBecomesUnreachableAfterDCE) {
+  const std::string text = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 320
+       %void = OpTypeVoid
+          %4 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+          %7 = OpTypeFunction %int
+     %int_n1 = OpConstant %int -1
+          %2 = OpFunction %void None %4
+          %9 = OpLabel
+               OpKill
+         %10 = OpLabel
+         %11 = OpFunctionCall %int %12
+               OpReturn
+               OpFunctionEnd
+; CHECK: {{%\w+}} = OpFunction %int DontInline|Pure
+         %12 = OpFunction %int DontInline|Pure %7
+; CHECK-NEXT: {{%\w+}} = OpLabel
+         %13 = OpLabel
+         %14 = OpVariable %_ptr_Function_int Function
+; CHECK-NEXT: OpBranch [[header:%\w+]]
+               OpBranch %15
+; CHECK-NEXT: [[header]] = OpLabel
+; CHECK-NEXT: OpBranch [[merge:%\w+]]
+         %15 = OpLabel
+               OpLoopMerge %16 %17 None
+               OpBranch %18
+         %18 = OpLabel
+         %19 = OpLoad %int %14
+               OpBranch %17
+         %17 = OpLabel
+               OpBranch %15
+; CHECK-NEXT: [[merge]] = OpLabel
+         %16 = OpLabel
+; CHECK-NEXT: OpReturnValue %int_n1
+               OpReturnValue %int_n1
+; CHECK-NEXT: OpFunctionEnd
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<AggressiveDCEPass>(text, true);
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/cfg_test.cpp b/test/opt/cfg_test.cpp
index 2cfc9f3..7dfd2bc 100644
--- a/test/opt/cfg_test.cpp
+++ b/test/opt/cfg_test.cpp
@@ -200,6 +200,125 @@
                            ContainerEq(expected_result2)));
 }
 
+TEST_F(CFGTest, SplitLoopHeaderForSingleBlockLoop) {
+  const std::string test = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+       %void = OpTypeVoid
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+          %6 = OpTypeFunction %void
+          %2 = OpFunction %void None %6
+          %7 = OpLabel
+               OpBranch %8
+          %8 = OpLabel
+          %9 = OpPhi %uint %uint_0 %7 %9 %8
+               OpLoopMerge %10 %8 None
+               OpBranch %8
+         %10 = OpLabel
+               OpUnreachable
+               OpFunctionEnd
+)";
+
+  const std::string expected_result = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%6 = OpTypeFunction %void
+%2 = OpFunction %void None %6
+%7 = OpLabel
+OpBranch %8
+%8 = OpLabel
+OpBranch %11
+%11 = OpLabel
+%9 = OpPhi %uint %9 %11 %uint_0 %8
+OpLoopMerge %10 %11 None
+OpBranch %11
+%10 = OpLabel
+OpUnreachable
+OpFunctionEnd
+)";
+
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, test,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  ASSERT_NE(nullptr, context);
+
+  BasicBlock* loop_header = context->get_instr_block(8);
+  ASSERT_TRUE(loop_header->GetLoopMergeInst() != nullptr);
+
+  CFG* cfg = context->cfg();
+  cfg->SplitLoopHeader(loop_header);
+
+  std::vector<uint32_t> binary;
+  bool skip_nop = false;
+  context->module()->ToBinary(&binary, skip_nop);
+
+  std::string optimized_asm;
+  SpirvTools tools(SPV_ENV_UNIVERSAL_1_1);
+  EXPECT_TRUE(tools.Disassemble(binary, &optimized_asm,
+                                SpirvTools::kDefaultDisassembleOption))
+      << "Disassembling failed for shader\n"
+      << std::endl;
+
+  EXPECT_EQ(optimized_asm, expected_result);
+}
+
+TEST_F(CFGTest, ComputeStructedOrderForLoop) {
+  const std::string test = R"(
+OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpName %main "main"
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%5 = OpConstant %uint 5
+%main = OpFunction %void None %4
+%8 = OpLabel
+OpBranch %9
+%9 = OpLabel
+OpLoopMerge %11 %10 None
+OpBranchConditional %true %11 %10
+%10 = OpLabel
+OpBranch %9
+%11 = OpLabel
+OpBranch %12
+%12 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, test,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  ASSERT_NE(nullptr, context);
+
+  CFG* cfg = context->cfg();
+  Module* module = context->module();
+  Function* function = &*module->begin();
+  std::list<BasicBlock*> order;
+  cfg->ComputeStructuredOrder(function, context->get_instr_block(9),
+                              context->get_instr_block(11), &order);
+
+  // Order should contain the loop header, the continue target, and the merge
+  // node.
+  std::list<BasicBlock*> expected_result = {context->get_instr_block(9),
+                                            context->get_instr_block(10),
+                                            context->get_instr_block(11)};
+  EXPECT_THAT(order, ContainerEq(expected_result));
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/dead_branch_elim_test.cpp b/test/opt/dead_branch_elim_test.cpp
index b04c8f5..1095d3b 100644
--- a/test/opt/dead_branch_elim_test.cpp
+++ b/test/opt/dead_branch_elim_test.cpp
@@ -2570,9 +2570,8 @@
 
 TEST_F(DeadBranchElimTest, SelectionMergeWithExitToLoop3) {
   // Checks that if a selection merge construct contains a conditional branch
-  // to the merge of a surrounding loop, the selection merge, and another block
-  // inside the selection merge, then we must keep the OpSelectionMerge
-  // instruction on that branch.
+  // to the selection merge, and another block inside the selection merge,
+  // then we must keep the OpSelectionMerge instruction on that branch.
   const std::string predefs = R"(
 OpCapability Shader
 %1 = OpExtInstImport "GLSL.std.450"
@@ -2586,6 +2585,7 @@
 %true = OpConstantTrue %bool
 %uint = OpTypeInt 32 0
 %undef_int = OpUndef %uint
+%undef_bool = OpUndef %bool
 )";
 
   const std::string body =
@@ -2596,7 +2596,7 @@
 ; CHECK-NEXT: OpBranch [[bb2:%\w+]]
 ; CHECK: [[bb2]] = OpLabel
 ; CHECK-NEXT: OpSelectionMerge [[sel_merge:%\w+]] None
-; CHECK-NEXT: OpSwitch {{%\w+}} [[sel_merge]] 0 [[loop_merge]] 1 [[bb3:%\w+]]
+; CHECK-NEXT: OpBranchConditional {{%\w+}} [[sel_merge]] [[bb3:%\w+]]
 ; CHECK: [[bb3]] = OpLabel
 ; CHECK-NEXT: OpBranch [[sel_merge]]
 ; CHECK: [[sel_merge]] = OpLabel
@@ -2613,7 +2613,8 @@
 OpSelectionMerge %sel_merge None
 OpBranchConditional %true %bb2 %bb4
 %bb2 = OpLabel
-OpSwitch %undef_int %sel_merge 0 %loop_merge 1 %bb3
+;OpSwitch %undef_int %sel_merge 0 %loop_merge 1 %bb3
+OpBranchConditional %undef_bool %sel_merge %bb3
 %bb3 = OpLabel
 OpBranch %sel_merge
 %bb4 = OpLabel
@@ -2632,9 +2633,8 @@
 
 TEST_F(DeadBranchElimTest, SelectionMergeWithExitToLoopContinue3) {
   // Checks that if a selection merge construct contains a conditional branch
-  // to the merge of a surrounding loop, the selection merge, and another block
-  // inside the selection merge, then we must keep the OpSelectionMerge
-  // instruction on that branch.
+  // the selection merge, and another block inside the selection merge, then we
+  // must keep the OpSelectionMerge instruction on that branch.
   const std::string predefs = R"(
 OpCapability Shader
 %1 = OpExtInstImport "GLSL.std.450"
@@ -2648,6 +2648,7 @@
 %true = OpConstantTrue %bool
 %uint = OpTypeInt 32 0
 %undef_int = OpUndef %uint
+%undef_bool = OpUndef %bool
 )";
 
   const std::string body =
@@ -2660,7 +2661,7 @@
 ; CHECK-NEXT: OpBranch [[bb2:%\w+]]
 ; CHECK: [[bb2]] = OpLabel
 ; CHECK-NEXT: OpSelectionMerge [[sel_merge:%\w+]] None
-; CHECK-NEXT: OpSwitch {{%\w+}} [[sel_merge]] 0 [[loop_continue]] 1 [[bb3:%\w+]]
+; CHECK-NEXT: OpBranchConditional {{%\w+}} [[sel_merge]] [[bb3:%\w+]]
 ; CHECK: [[bb3]] = OpLabel
 ; CHECK-NEXT: OpBranch [[sel_merge]]
 ; CHECK: [[sel_merge]] = OpLabel
@@ -2679,131 +2680,7 @@
 OpSelectionMerge %sel_merge None
 OpBranchConditional %true %bb2 %bb4
 %bb2 = OpLabel
-OpSwitch %undef_int %sel_merge 0 %cont 1 %bb3
-%bb3 = OpLabel
-OpBranch %sel_merge
-%bb4 = OpLabel
-OpBranch %sel_merge
-%sel_merge = OpLabel
-OpBranch %loop_merge
-%cont = OpLabel
-OpBranch %loop_header
-%loop_merge = OpLabel
-OpReturn
-OpFunctionEnd
-)";
-
-  SinglePassRunAndMatch<DeadBranchElimPass>(predefs + body, true);
-}
-
-TEST_F(DeadBranchElimTest, SelectionMergeWithExitToLoop4) {
-  // Same as |SelectionMergeWithExitToLoop|, except the branch in the selection
-  // construct is an |OpSwitch| instead of an |OpConditionalBranch|.  The
-  // OpSelectionMerge instruction is not needed in this case either.
-  const std::string predefs = R"(
-OpCapability Shader
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %main "main"
-OpExecutionMode %main OriginUpperLeft
-OpSource GLSL 140
-%void = OpTypeVoid
-%func_type = OpTypeFunction %void
-%bool = OpTypeBool
-%true = OpConstantTrue %bool
-%uint = OpTypeInt 32 0
-%undef_int = OpUndef %uint
-)";
-
-  const std::string body =
-      R"(
-; CHECK: OpLoopMerge [[loop_merge:%\w+]]
-; CHECK-NEXT: OpBranch [[bb1:%\w+]]
-; CHECK: [[bb1]] = OpLabel
-; CHECK-NEXT: OpBranch [[bb2:%\w+]]
-; CHECK: [[bb2]] = OpLabel
-; CHECK-NEXT: OpSelectionMerge
-; CHECK-NEXT: OpSwitch {{%\w+}} [[bb3:%\w+]] 0 [[loop_merge]] 1 [[bb3:%\w+]]
-; CHECK: [[bb3]] = OpLabel
-; CHECK-NEXT: OpBranch [[sel_merge:%\w+]]
-; CHECK: [[sel_merge]] = OpLabel
-; CHECK-NEXT: OpBranch [[loop_merge]]
-; CHECK: [[loop_merge]] = OpLabel
-; CHECK-NEXT: OpReturn
-%main = OpFunction %void None %func_type
-%entry_bb = OpLabel
-OpBranch %loop_header
-%loop_header = OpLabel
-OpLoopMerge %loop_merge %cont None
-OpBranch %bb1
-%bb1 = OpLabel
-OpSelectionMerge %sel_merge None
-OpBranchConditional %true %bb2 %bb4
-%bb2 = OpLabel
-OpSelectionMerge %bb3 None
-OpSwitch %undef_int %bb3 0 %loop_merge 1 %bb3
-%bb3 = OpLabel
-OpBranch %sel_merge
-%bb4 = OpLabel
-OpBranch %sel_merge
-%sel_merge = OpLabel
-OpBranch %loop_merge
-%cont = OpLabel
-OpBranch %loop_header
-%loop_merge = OpLabel
-OpReturn
-OpFunctionEnd
-)";
-
-  SinglePassRunAndMatch<DeadBranchElimPass>(predefs + body, true);
-}
-
-TEST_F(DeadBranchElimTest, SelectionMergeWithExitToLoopContinue4) {
-  // Same as |SelectionMergeWithExitToLoopContinue|, except the branch in the
-  // selection construct is an |OpSwitch| instead of an |OpConditionalBranch|.
-  // The OpSelectionMerge instruction is not needed in this case either.
-  const std::string predefs = R"(
-OpCapability Shader
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %main "main"
-OpExecutionMode %main OriginUpperLeft
-OpSource GLSL 140
-%void = OpTypeVoid
-%func_type = OpTypeFunction %void
-%bool = OpTypeBool
-%true = OpConstantTrue %bool
-%uint = OpTypeInt 32 0
-%undef_int = OpUndef %uint
-)";
-
-  const std::string body =
-      R"(
-; CHECK: OpLoopMerge [[loop_merge:%\w+]] [[loop_cont:%\w+]]
-; CHECK-NEXT: OpBranch [[bb1:%\w+]]
-; CHECK: [[bb1]] = OpLabel
-; CHECK-NEXT: OpBranch [[bb2:%\w+]]
-; CHECK: [[bb2]] = OpLabel
-; CHECK-NEXT: OpSelectionMerge
-; CHECK-NEXT: OpSwitch {{%\w+}} [[bb3:%\w+]] 0 [[loop_cont]] 1 [[bb3:%\w+]]
-; CHECK: [[bb3]] = OpLabel
-; CHECK-NEXT: OpBranch [[sel_merge:%\w+]]
-; CHECK: [[sel_merge]] = OpLabel
-; CHECK-NEXT: OpBranch [[loop_merge]]
-; CHECK: [[loop_merge]] = OpLabel
-; CHECK-NEXT: OpReturn
-%main = OpFunction %void None %func_type
-%entry_bb = OpLabel
-OpBranch %loop_header
-%loop_header = OpLabel
-OpLoopMerge %loop_merge %cont None
-OpBranch %bb1
-%bb1 = OpLabel
-OpSelectionMerge %sel_merge None
-OpBranchConditional %true %bb2 %bb4
-%bb2 = OpLabel
-OpSelectionMerge %bb3 None
-OpSwitch %undef_int %bb3 0 %cont 1 %bb3
+OpBranchConditional %undef_bool %sel_merge %bb3
 %bb3 = OpLabel
 OpBranch %sel_merge
 %bb4 = OpLabel
@@ -3036,9 +2913,11 @@
 ; CHECK-NEXT: OpLoopMerge [[outer_merge:%\w+]] [[outer_cont:%\w+]] None
 ; CHECK-NEXT: OpBranch [[inner:%\w+]]
 ; CHECK: [[inner]] = OpLabel
-; CHECK: OpLoopMerge [[outer_cont]] [[inner_cont:%\w+]] None
+; CHECK: OpLoopMerge [[inner_merge:%\w+]] [[inner_cont:%\w+]] None
 ; CHECK: [[inner_cont]] = OpLabel
 ; CHECK-NEXT: OpBranch [[inner]]
+; CHECK: [[inner_merge]] = OpLabel
+; CHECK-NEXT: OpUnreachable
 ; CHECK: [[outer_cont]] = OpLabel
 ; CHECK-NEXT: OpBranch [[outer]]
 ; CHECK: [[outer_merge]] = OpLabel
@@ -3058,7 +2937,7 @@
 OpLoopMerge %outer_merge %outer_continue None
 OpBranch %inner_loop
 %inner_loop = OpLabel
-OpLoopMerge %outer_continue %inner_continue None
+OpLoopMerge %inner_merge %inner_continue None
 OpBranch %inner_body
 %inner_body = OpLabel
 OpSelectionMerge %inner_continue None
@@ -3066,7 +2945,9 @@
 %ret = OpLabel
 OpReturn
 %inner_continue = OpLabel
-OpBranchConditional %true %outer_continue %inner_loop
+OpBranchConditional %true %inner_merge %inner_loop
+%inner_merge = OpLabel
+OpBranch %outer_continue
 %outer_continue = OpLabel
 OpBranchConditional %true %outer_merge %outer_loop
 %outer_merge = OpLabel
diff --git a/test/opt/def_use_test.cpp b/test/opt/def_use_test.cpp
index 48a485e..0210095 100644
--- a/test/opt/def_use_test.cpp
+++ b/test/opt/def_use_test.cpp
@@ -950,369 +950,6 @@
 );
 // clang-format on
 
-// We re-use the same replace usecases, we need to similarly exercise the
-// DefUseManager by replacing instructions and uses.
-using CompactIdempotence = ::testing::TestWithParam<ReplaceUseCase>;
-
-TEST_P(CompactIdempotence, Case) {
-  const auto& tc = GetParam();
-
-  // Build module.
-  const std::vector<const char*> text = {tc.before};
-  std::unique_ptr<IRContext> context =
-      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, JoinAllInsts(text),
-                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
-  ASSERT_NE(nullptr, context);
-
-  // Force a re-build of def-use manager.
-  context->InvalidateAnalyses(IRContext::Analysis::kAnalysisDefUse);
-  (void)context->get_def_use_mgr();
-
-  // Do the substitution.
-  for (const auto& candidate : tc.candidates) {
-    context->ReplaceAllUsesWith(candidate.first, candidate.second);
-  }
-
-  // Ensure new/uncompacted managers produce the same result
-  EXPECT_TRUE(CompareAndPrintDifferences(*context->get_def_use_mgr(),
-                                         DefUseManager(context->module())));
-
-  EXPECT_EQ(tc.after, DisassembleModule(context->module()));
-  CheckDef(tc.du, context->get_def_use_mgr()->id_to_defs());
-  CheckUse(tc.du, context->get_def_use_mgr(), context->module()->IdBound());
-
-  // Compare again after compacting the defuse manager's storage
-  context->get_def_use_mgr()->CompactStorage();
-
-  CheckDef(tc.du, context->get_def_use_mgr()->id_to_defs());
-  CheckUse(tc.du, context->get_def_use_mgr(), context->module()->IdBound());
-
-  EXPECT_TRUE(CompareAndPrintDifferences(*context->get_def_use_mgr(),
-                                         DefUseManager(context->module())));
-}
-
-// clang-format off
-INSTANTIATE_TEST_SUITE_P(
-    TestCase, CompactIdempotence,
-    ::testing::ValuesIn(std::vector<ReplaceUseCase>{
-      { // no use, no replace request
-        "", {}, "", {},
-      },
-      { // replace one use
-        "%1 = OpTypeBool "
-        "%2 = OpTypeVector %1 3 "
-        "%3 = OpTypeInt 32 0 ",
-        {{1, 3}},
-        "%1 = OpTypeBool\n"
-        "%2 = OpTypeVector %3 3\n"
-        "%3 = OpTypeInt 32 0",
-        {
-          { // defs
-            {1, "%1 = OpTypeBool"},
-            {2, "%2 = OpTypeVector %3 3"},
-            {3, "%3 = OpTypeInt 32 0"},
-          },
-          { // uses
-            {3, {"%2 = OpTypeVector %3 3"}},
-          },
-        },
-      },
-      { // replace and then replace back
-        "%1 = OpTypeBool "
-        "%2 = OpTypeVector %1 3 "
-        "%3 = OpTypeInt 32 0",
-        {{1, 3}, {3, 1}},
-        "%1 = OpTypeBool\n"
-        "%2 = OpTypeVector %1 3\n"
-        "%3 = OpTypeInt 32 0",
-        {
-          { // defs
-            {1, "%1 = OpTypeBool"},
-            {2, "%2 = OpTypeVector %1 3"},
-            {3, "%3 = OpTypeInt 32 0"},
-          },
-          { // 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 "
-        "%3 = OpTypeInt 32 0 "
-        "%4 = OpTypeInt 32 1 ",
-        {{1, 3}, {3, 4}},
-        "%1 = OpTypeBool\n"
-        "%2 = OpTypeVector %4 3\n"
-        "%3 = OpTypeInt 32 0\n"
-        "%4 = OpTypeInt 32 1",
-        {
-          { // defs
-            {1, "%1 = OpTypeBool"},
-            {2, "%2 = OpTypeVector %4 3"},
-            {3, "%3 = OpTypeInt 32 0"},
-            {4, "%4 = OpTypeInt 32 1"},
-          },
-          { // uses
-            {4, {"%2 = OpTypeVector %4 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 "
-        "%8 = OpTypeInt 32 0 "
-        "%9 = OpTypeInt 32 1 "
-        "%10 = OpTypeInt 64 0",
-        {{1, 8}, {2, 9}, {4, 10}},
-        "%1 = OpTypeBool\n"
-        "%2 = OpTypeVector %8 2\n"
-        "%3 = OpTypeVector %8 3\n"
-        "%4 = OpTypeVector %8 4\n"
-        "%5 = OpTypeMatrix %9 2\n"
-        "%6 = OpTypeMatrix %3 3\n"
-        "%7 = OpTypeMatrix %10 4\n"
-        "%8 = OpTypeInt 32 0\n"
-        "%9 = OpTypeInt 32 1\n"
-        "%10 = OpTypeInt 64 0",
-        {
-          { // defs
-            {1, "%1 = OpTypeBool"},
-            {2, "%2 = OpTypeVector %8 2"},
-            {3, "%3 = OpTypeVector %8 3"},
-            {4, "%4 = OpTypeVector %8 4"},
-            {5, "%5 = OpTypeMatrix %9 2"},
-            {6, "%6 = OpTypeMatrix %3 3"},
-            {7, "%7 = OpTypeMatrix %10 4"},
-            {8, "%8 = OpTypeInt 32 0"},
-            {9, "%9 = OpTypeInt 32 1"},
-            {10, "%10 = OpTypeInt 64 0"},
-          },
-          { // uses
-            {8,
-              {
-                "%2 = OpTypeVector %8 2",
-                "%3 = OpTypeVector %8 3",
-                "%4 = OpTypeVector %8 4",
-              }
-            },
-            {9, {"%5 = OpTypeMatrix %9 2"}},
-            {3, {"%6 = OpTypeMatrix %3 3"}},
-            {10, {"%7 = OpTypeMatrix %10 4"}},
-          },
-        },
-      },
-      { // OpPhi.
-        kOpPhiTestFunction,
-        // replace one id used by OpPhi, replace one id generated by OpPhi
-        {{9, 13}, {11, 9}},
-         "%1 = OpTypeVoid\n"
-         "%6 = OpTypeInt 32 0\n"
-         "%10 = OpTypeFloat 32\n"
-         "%16 = OpTypeBool\n"
-         "%3 = OpTypeFunction %1\n"
-         "%8 = OpConstant %6 0\n"
-         "%18 = OpConstant %6 1\n"
-         "%12 = OpConstant %10 1\n"
-         "%2 = OpFunction %1 None %3\n"
-         "%4 = OpLabel\n"
-               "OpBranch %5\n"
-
-         "%5 = OpLabel\n"
-         "%7 = OpPhi %6 %8 %4 %13 %5\n" // %9 -> %13
-        "%11 = OpPhi %10 %12 %4 %13 %5\n"
-         "%9 = OpIAdd %6 %7 %8\n"
-        "%13 = OpFAdd %10 %9 %12\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.
-            {1, "%1 = OpTypeVoid"},
-            {2, "%2 = OpFunction %1 None %3"},
-            {3, "%3 = OpTypeFunction %1"},
-            {4, "%4 = OpLabel"},
-            {5, "%5 = OpLabel"},
-            {6, "%6 = OpTypeInt 32 0"},
-            {7, "%7 = OpPhi %6 %8 %4 %13 %5"},
-            {8, "%8 = OpConstant %6 0"},
-            {9, "%9 = OpIAdd %6 %7 %8"},
-            {10, "%10 = OpTypeFloat 32"},
-            {11, "%11 = OpPhi %10 %12 %4 %13 %5"},
-            {12, "%12 = OpConstant %10 1.0"},
-            {13, "%13 = OpFAdd %10 %9 %12"},
-            {16, "%16 = OpTypeBool"},
-            {17, "%17 = OpSLessThan %16 %7 %18"},
-            {18, "%18 = OpConstant %6 1"},
-            {19, "%19 = OpLabel"},
-          },
-          { // uses
-            {1,
-              {
-                "%2 = OpFunction %1 None %3",
-                "%3 = OpTypeFunction %1",
-              }
-            },
-            {3, {"%2 = OpFunction %1 None %3"}},
-            {4,
-              {
-                "%7 = OpPhi %6 %8 %4 %13 %5",
-                "%11 = OpPhi %10 %12 %4 %13 %5",
-              }
-            },
-            {5,
-              {
-                "OpBranch %5",
-                "%7 = OpPhi %6 %8 %4 %13 %5",
-                "%11 = OpPhi %10 %12 %4 %13 %5",
-                "OpLoopMerge %19 %5 None",
-                "OpBranchConditional %17 %5 %19",
-              }
-            },
-            {6,
-              {
-                // Can't properly check constants
-                // "%8 = OpConstant %6 0",
-                // "%18 = OpConstant %6 1",
-                "%7 = OpPhi %6 %8 %4 %13 %5",
-                "%9 = OpIAdd %6 %7 %8"
-              }
-            },
-            {7,
-              {
-                "%9 = OpIAdd %6 %7 %8",
-                "%17 = OpSLessThan %16 %7 %18",
-              }
-            },
-            {8,
-              {
-                "%7 = OpPhi %6 %8 %4 %13 %5",
-                "%9 = OpIAdd %6 %7 %8",
-              }
-            },
-            {9, {"%13 = OpFAdd %10 %9 %12"}}, // uses of %9 changed from %7 to %13
-            {10,
-              {
-                "%11 = OpPhi %10 %12 %4 %13 %5",
-                // "%12 = OpConstant %10 1",
-                "%13 = OpFAdd %10 %9 %12"
-              }
-            },
-            // no more uses of %11
-            {12,
-              {
-                "%11 = OpPhi %10 %12 %4 %13 %5",
-                "%13 = OpFAdd %10 %9 %12"
-              }
-            },
-            {13, {
-                   "%7 = OpPhi %6 %8 %4 %13 %5",
-                   "%11 = OpPhi %10 %12 %4 %13 %5",
-                 }
-            },
-            {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 "
-        "%3 = OpTypeFunction %1 "
-        "%2 = OpConstantTrue %1 "
-
-        "%4 = OpFunction %3 None %1 "
-        "%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"
-        "%3 = OpTypeFunction %1\n"
-        "%2 = OpConstantTrue %1\n"
-
-        "%4 = OpFunction %3 None %1\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"},
-            {3, "%3 = OpTypeFunction %1"},
-            {4, "%4 = OpFunction %3 None %1"},
-            {6, "%6 = OpLabel"},
-            {7, "%7 = OpLabel"},
-            {8, "%8 = OpPhi %1 %2 %7 %2 %6"},
-          },
-          { // uses
-            {1,
-              {
-                "%2 = OpConstantTrue %1",
-                "%3 = OpTypeFunction %1",
-                "%4 = OpFunction %3 None %1",
-                "%8 = OpPhi %1 %2 %7 %2 %6",
-              }
-            },
-            {2,
-              {
-                // Only checking users
-                "%8 = OpPhi %1 %2 %7 %2 %6",
-              }
-            },
-            {3, {"%4 = OpFunction %3 None %1"}},
-            {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;
diff --git a/test/opt/fold_test.cpp b/test/opt/fold_test.cpp
index 8ee14a9..f6c94ca 100644
--- a/test/opt/fold_test.cpp
+++ b/test/opt/fold_test.cpp
@@ -148,6 +148,8 @@
 %v2half = OpTypeVector %half 2
 %v2bool = OpTypeVector %bool 2
 %m2x2int = OpTypeMatrix %v2int 2
+%mat4v4float = OpTypeMatrix %v4float 4
+%mat4v4double = OpTypeMatrix %v4double 4
 %struct_v2int_int_int = OpTypeStruct %v2int %int %int
 %_ptr_int = OpTypePointer Function %int
 %_ptr_uint = OpTypePointer Function %uint
@@ -276,13 +278,20 @@
 %v4float_0_0_0_1 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_1
 %v4float_0_1_0_0 = OpConstantComposite %v4float %float_0 %float_1 %float_null %float_0
 %v4float_1_1_1_1 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
+%v4float_1_2_3_4 = OpConstantComposite %v4float %float_1 %float_2 %float_3 %float_4
+%v4float_null = OpConstantNull %v4float
+%mat4v4float_null = OpConstantComposite %mat4v4float %v4float_null %v4float_null %v4float_null %v4float_null
+%mat4v4float_1_2_3_4 = OpConstantComposite %mat4v4float %v4float_1_2_3_4 %v4float_1_2_3_4 %v4float_1_2_3_4 %v4float_1_2_3_4
 %107 = OpConstantComposite %v4double %double_0 %double_0 %double_0 %double_0
 %v4double_0_0_0_0 = OpConstantComposite %v4double %double_0 %double_0 %double_0 %double_0
 %v4double_0_0_0_1 = OpConstantComposite %v4double %double_0 %double_0 %double_0 %double_1
 %v4double_0_1_0_0 = OpConstantComposite %v4double %double_0 %double_1 %double_null %double_0
 %v4double_1_1_1_1 = OpConstantComposite %v4double %double_1 %double_1 %double_1 %double_1
+%v4double_1_2_3_4 = OpConstantComposite %v4double %double_1 %double_2 %double_3 %double_4
 %v4double_1_1_1_0p5 = OpConstantComposite %v4double %double_1 %double_1 %double_1 %double_0p5
 %v4double_null = OpConstantNull %v4double
+%mat4v4double_null = OpConstantComposite %mat4v4double %v4double_null %v4double_null %v4double_null %v4double_null
+%mat4v4double_1_2_3_4 = OpConstantComposite %mat4v4double %v4double_1_2_3_4 %v4double_1_2_3_4 %v4double_1_2_3_4 %v4double_1_2_3_4
 %v4float_n1_2_1_3 = OpConstantComposite %v4float %float_n1 %float_2 %float_1 %float_3
 %uint_0x3f800000 = OpConstant %uint 0x3f800000
 %uint_0xbf800000 = OpConstant %uint 0xbf800000
@@ -912,7 +921,61 @@
            "%2 = OpBitcast %v2double %v4int_0x3FF00000_0x00000000_0xC05FD666_0x66666666\n" +
            "OpReturn\n" +
            "OpFunctionEnd",
-       2, {1.0,-127.35})
+       2, {1.0,-127.35}),
+   // Test case 1: OpVectorTimesMatrix Non-Zero Zero {{0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0}} {1.0, 2.0, 3.0, 4.0} {0.0, 0.0, 0.0, 0.0}
+   InstructionFoldingCase<std::vector<double>>(
+       Header() +
+       "%main = OpFunction %void None %void_func\n" +
+       "%main_lab = OpLabel\n" +
+       "%2 = OpVectorTimesMatrix %v4double %v4double_1_2_3_4 %mat4v4double_null\n" +
+       "OpReturn\n" +
+       "OpFunctionEnd",
+       2, {0.0,0.0,0.0,0.0}),
+   // Test case 2: OpVectorTimesMatrix Zero Non-Zero {{1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}} {0.0, 0.0, 0.0, 0.0} {0.0, 0.0, 0.0, 0.0}
+   InstructionFoldingCase<std::vector<double>>(
+       Header() +
+       "%main = OpFunction %void None %void_func\n" +
+       "%main_lab = OpLabel\n" +
+       "%2 = OpVectorTimesMatrix %v4double %v4double_null %mat4v4double_1_2_3_4\n" +
+       "OpReturn\n" +
+       "OpFunctionEnd",
+       2, {0.0,0.0,0.0,0.0}),
+   // Test case 3: OpVectorTimesMatrix Non-Zero Non-Zero {{1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}} {1.0, 2.0, 3.0, 4.0} {30.0, 30.0, 30.0, 30.0}
+   InstructionFoldingCase<std::vector<double>>(
+       Header() +
+       "%main = OpFunction %void None %void_func\n" +
+       "%main_lab = OpLabel\n" +
+       "%2 = OpVectorTimesMatrix %v4double %v4double_1_2_3_4 %mat4v4double_1_2_3_4\n" +
+       "OpReturn\n" +
+       "OpFunctionEnd",
+       2, {30.0,30.0,30.0,30.0}),
+   // Test case 4: OpMatrixTimesVector Zero Non-Zero {1.0, 2.0, 3.0, 4.0} {{0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0}} {0.0, 0.0, 0.0, 0.0}
+   InstructionFoldingCase<std::vector<double>>(
+       Header() +
+       "%main = OpFunction %void None %void_func\n" +
+       "%main_lab = OpLabel\n" +
+       "%2 = OpMatrixTimesVector %v4double %mat4v4double_null %v4double_1_2_3_4\n" +
+       "OpReturn\n" +
+       "OpFunctionEnd",
+       2, {0.0,0.0,0.0,0.0}),
+   // Test case 5: OpMatrixTimesVector Non-Zero Zero {{1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}} {0.0, 0.0, 0.0, 0.0} {0.0, 0.0, 0.0, 0.0}
+   InstructionFoldingCase<std::vector<double>>(
+       Header() +
+       "%main = OpFunction %void None %void_func\n" +
+       "%main_lab = OpLabel\n" +
+       "%2 = OpMatrixTimesVector %v4double %mat4v4double_1_2_3_4 %v4double_null\n" +
+       "OpReturn\n" +
+       "OpFunctionEnd",
+       2, {0.0,0.0,0.0,0.0}),
+   // Test case 6: OpMatrixTimesVector Non-Zero Non-Zero {1.0, 2.0, 3.0, 4.0} {{1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}} {10.0, 20.0, 30.0, 40.0}
+   InstructionFoldingCase<std::vector<double>>(
+       Header() +
+       "%main = OpFunction %void None %void_func\n" +
+       "%main_lab = OpLabel\n" +
+       "%2 = OpMatrixTimesVector %v4double %mat4v4double_1_2_3_4 %v4double_1_2_3_4\n" +
+       "OpReturn\n" +
+       "OpFunctionEnd",
+       2, {10.0,20.0,30.0,40.0})
 ));
 
 using FloatVectorInstructionFoldingTest =
@@ -981,7 +1044,61 @@
            "%2 = OpBitcast %v2float %long_0xbf8000003f800000\n" +
            "OpReturn\n" +
            "OpFunctionEnd",
-       2, {1.0f,-1.0f})
+       2, {1.0f,-1.0f}),
+   // Test case 3: OpVectorTimesMatrix Non-Zero Zero {{0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0}} {1.0, 2.0, 3.0, 4.0} {0.0, 0.0, 0.0, 0.0}
+   InstructionFoldingCase<std::vector<float>>(
+       Header() +
+       "%main = OpFunction %void None %void_func\n" +
+       "%main_lab = OpLabel\n" +
+       "%2 = OpVectorTimesMatrix %v4float %v4float_1_2_3_4 %mat4v4float_null\n" +
+       "OpReturn\n" +
+       "OpFunctionEnd",
+       2, {0.0f,0.0f,0.0f,0.0f}),
+   // Test case 4: OpVectorTimesMatrix Zero Non-Zero {{1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}} {0.0, 0.0, 0.0, 0.0} {0.0, 0.0, 0.0, 0.0}
+   InstructionFoldingCase<std::vector<float>>(
+       Header() +
+       "%main = OpFunction %void None %void_func\n" +
+       "%main_lab = OpLabel\n" +
+       "%2 = OpVectorTimesMatrix %v4float %v4float_null %mat4v4float_1_2_3_4\n" +
+       "OpReturn\n" +
+       "OpFunctionEnd",
+       2, {0.0f,0.0f,0.0f,0.0f}),
+   // Test case 5: OpVectorTimesMatrix Non-Zero Non-Zero {{1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}} {1.0, 2.0, 3.0, 4.0} {30.0, 30.0, 30.0, 30.0}
+   InstructionFoldingCase<std::vector<float>>(
+       Header() +
+       "%main = OpFunction %void None %void_func\n" +
+       "%main_lab = OpLabel\n" +
+       "%2 = OpVectorTimesMatrix %v4float %v4float_1_2_3_4 %mat4v4float_1_2_3_4\n" +
+       "OpReturn\n" +
+       "OpFunctionEnd",
+       2, {30.0f,30.0f,30.0f,30.0f}),
+   // Test case 6: OpMatrixTimesVector Zero Non-Zero {1.0, 2.0, 3.0, 4.0} {{0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0}} {0.0, 0.0, 0.0, 0.0}
+   InstructionFoldingCase<std::vector<float>>(
+       Header() +
+       "%main = OpFunction %void None %void_func\n" +
+       "%main_lab = OpLabel\n" +
+       "%2 = OpMatrixTimesVector %v4float %mat4v4float_null %v4float_1_2_3_4\n" +
+       "OpReturn\n" +
+       "OpFunctionEnd",
+       2, {0.0f,0.0f,0.0f,0.0f}),
+   // Test case 7: OpMatrixTimesVector Non-Zero Zero {{1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}} {0.0, 0.0, 0.0, 0.0} {0.0, 0.0, 0.0, 0.0}
+   InstructionFoldingCase<std::vector<float>>(
+       Header() +
+       "%main = OpFunction %void None %void_func\n" +
+       "%main_lab = OpLabel\n" +
+       "%2 = OpMatrixTimesVector %v4float %mat4v4float_1_2_3_4 %v4float_null\n" +
+       "OpReturn\n" +
+       "OpFunctionEnd",
+       2, {0.0f,0.0f,0.0f,0.0f}),
+   // Test case 8: OpMatrixTimesVector Non-Zero Non-Zero {1.0, 2.0, 3.0, 4.0} {{1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}} {10.0, 20.0, 30.0, 40.0}
+   InstructionFoldingCase<std::vector<float>>(
+       Header() +
+       "%main = OpFunction %void None %void_func\n" +
+       "%main_lab = OpLabel\n" +
+       "%2 = OpMatrixTimesVector %v4float %mat4v4float_1_2_3_4 %v4float_1_2_3_4\n" +
+       "OpReturn\n" +
+       "OpFunctionEnd",
+       2, {10.0f,20.0f,30.0f,40.0f})
 ));
 // clang-format on
 using BooleanInstructionFoldingTest =
diff --git a/test/opt/if_conversion_test.cpp b/test/opt/if_conversion_test.cpp
index 81e9bb2..dc7f831 100644
--- a/test/opt/if_conversion_test.cpp
+++ b/test/opt/if_conversion_test.cpp
@@ -591,6 +591,33 @@
   SinglePassRunAndMatch<IfConversion>(text, true);
 }
 
+TEST_F(IfConversionTest, MultipleEdgesFromSameBlock) {
+  // If a block has two out going edges that go to the same block, then there
+  // can be an OpPhi instruction with fewer entries than the number of incoming
+  // edges.  This must be handled.
+  const std::string text = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%true_0 = OpConstantTrue %bool
+%2 = OpFunction %void None %4
+%8 = OpLabel
+OpSelectionMerge %9 None
+OpBranchConditional %true_0 %9 %9
+%9 = OpLabel
+%10 = OpPhi %bool %true %8
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<IfConversion>(text, text, true, true);
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/inline_test.cpp b/test/opt/inline_test.cpp
index cefd8e5..d804f7c 100644
--- a/test/opt/inline_test.cpp
+++ b/test/opt/inline_test.cpp
@@ -1533,9 +1533,11 @@
 %9 = OpLabel
 OpBranch %10
 %10 = OpLabel
-OpLoopMerge %12 %10 None
+OpLoopMerge %12 %15 None
 OpBranch %14
 %14 = OpLabel
+OpBranch %15
+%15 = OpLabel
 OpBranchConditional %true %10 %12
 %12 = OpLabel
 OpReturn
@@ -1694,7 +1696,7 @@
 OpBranch %13
 %13 = OpLabel
 %14 = OpCopyObject %bool %false
-OpLoopMerge %16 %13 None
+OpLoopMerge %16 %22 None
 OpBranch %17
 %17 = OpLabel
 %19 = OpCopyObject %bool %true
@@ -1702,6 +1704,8 @@
 OpBranchConditional %true %20 %20
 %20 = OpLabel
 %21 = OpPhi %bool %19 %17
+OpBranch %22
+%22 = OpLabel
 OpBranchConditional %true %13 %16
 %16 = OpLabel
 OpReturn
diff --git a/test/opt/inst_bindless_check_test.cpp b/test/opt/inst_bindless_check_test.cpp
index 4c271de..c8eb6c1 100644
--- a/test/opt/inst_bindless_check_test.cpp
+++ b/test/opt/inst_bindless_check_test.cpp
@@ -8897,6 +8897,7 @@
  )";
 
   SetTargetEnv(SPV_ENV_VULKAN_1_2);
+  ValidatorOptions()->uniform_buffer_standard_layout = true;
   SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
   SinglePassRunAndMatch<InstBindlessCheckPass>(text, true, 7u, 23u, false,
                                                false, true, false, true);
diff --git a/test/opt/instruction_test.cpp b/test/opt/instruction_test.cpp
index 2a48134..dd749ab 100644
--- a/test/opt/instruction_test.cpp
+++ b/test/opt/instruction_test.cpp
@@ -1525,6 +1525,45 @@
   EXPECT_EQ(false, inst->IsVulkanStorageTexelBuffer());
 }
 
+TEST_F(DescriptorTypeTest, GetShader100DebugOpcode) {
+  const std::string text = R"(
+              OpCapability Shader
+         %1 = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+         %2 = OpString "ps.hlsl"
+         %3 = OpString "#line 1 \"ps.hlsl\""
+      %void = OpTypeVoid
+         %5 = OpExtInst %void %1 DebugExpression
+         %6 = OpExtInst %void %1 DebugSource %2 %3
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+  Instruction* debug_expression = context->get_def_use_mgr()->GetDef(5);
+  EXPECT_EQ(debug_expression->GetShader100DebugOpcode(),
+            NonSemanticShaderDebugInfo100DebugExpression);
+  Instruction* debug_source = context->get_def_use_mgr()->GetDef(6);
+  EXPECT_EQ(debug_source->GetShader100DebugOpcode(),
+            NonSemanticShaderDebugInfo100DebugSource);
+
+  // Test that an opcode larger than the max will return Max.  This instruction
+  // cannot be in the assembly above because the assembler expects the string
+  // for the opcode, so we cannot use an arbitrary number.  However, a binary
+  // file could have an arbitrary number.
+  std::unique_ptr<Instruction> past_max(debug_expression->Clone(context.get()));
+  const uint32_t kExtInstOpcodeInIndex = 1;
+  uint32_t large_opcode = NonSemanticShaderDebugInfo100InstructionsMax + 2;
+  past_max->SetInOperand(kExtInstOpcodeInIndex, {large_opcode});
+  EXPECT_EQ(past_max->GetShader100DebugOpcode(),
+            NonSemanticShaderDebugInfo100InstructionsMax);
+
+  // Test that an opcode without a value in the enum, but less than Max returns
+  // the same value.
+  uint32_t opcode = NonSemanticShaderDebugInfo100InstructionsMax - 2;
+  past_max->SetInOperand(kExtInstOpcodeInIndex, {opcode});
+  EXPECT_EQ(past_max->GetShader100DebugOpcode(), opcode);
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/local_access_chain_convert_test.cpp b/test/opt/local_access_chain_convert_test.cpp
index 2b3231c..6f5021c 100644
--- a/test/opt/local_access_chain_convert_test.cpp
+++ b/test/opt/local_access_chain_convert_test.cpp
@@ -1252,6 +1252,70 @@
                                                      true);
 }
 
+TEST_F(LocalAccessChainConvertTest, OutOfBoundsAccess) {
+  // The access chain indexes element 12 in an array of size 10.  Nothing should
+  // be done.
+  const std::string assembly =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main" %3
+OpExecutionMode %2 OriginUpperLeft
+%void = OpTypeVoid
+%5 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%int_10 = OpConstant %int 10
+%_arr_int_int_10 = OpTypeArray %int %int_10
+%_ptr_Function_int = OpTypePointer Function %int
+%int_12 = OpConstant %int 12
+%_ptr_Output_int = OpTypePointer Output %int
+%3 = OpVariable %_ptr_Output_int Output
+%_ptr_Function__arr_int_int_10 = OpTypePointer Function %_arr_int_int_10
+%2 = OpFunction %void None %5
+%13 = OpLabel
+%14 = OpVariable %_ptr_Function__arr_int_int_10 Function
+%15 = OpAccessChain %_ptr_Function_int %14 %int_12
+%16 = OpLoad %int %15
+OpStore %3 %16
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<LocalAccessChainConvertPass>(assembly, assembly, false,
+                                                     true);
+}
+
+TEST_F(LocalAccessChainConvertTest, OutOfBoundsAccessAtBoundary) {
+  // The access chain indexes element 10 in an array of size 10.  Nothing should
+  // be done.
+  const std::string assembly =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main" %3
+OpExecutionMode %2 OriginUpperLeft
+%void = OpTypeVoid
+%5 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%int_10 = OpConstant %int 10
+%_arr_int_int_10 = OpTypeArray %int %int_10
+%_ptr_Function_int = OpTypePointer Function %int
+%_ptr_Output_int = OpTypePointer Output %int
+%3 = OpVariable %_ptr_Output_int Output
+%_ptr_Function__arr_int_int_10 = OpTypePointer Function %_arr_int_int_10
+%2 = OpFunction %void None %5
+%12 = OpLabel
+%13 = OpVariable %_ptr_Function__arr_int_int_10 Function
+%14 = OpAccessChain %_ptr_Function_int %13 %int_10
+%15 = OpLoad %int %14
+OpStore %3 %15
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<LocalAccessChainConvertPass>(assembly, assembly, false,
+                                                     true);
+}
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 //
 //    Assorted vector and matrix types
diff --git a/test/opt/pass_merge_return_test.cpp b/test/opt/pass_merge_return_test.cpp
index 21960d1..04bd5d9 100644
--- a/test/opt/pass_merge_return_test.cpp
+++ b/test/opt/pass_merge_return_test.cpp
@@ -2231,7 +2231,7 @@
 
 TEST_F(MergeReturnPassTest, UnreachableMergeAndContinue) {
   // Make sure that the pass can handle a single block that is both a merge and
-  // a continue.
+  // a continue. Note that this is invalid SPIR-V.
   const std::string text =
       R"(
                OpCapability Shader
@@ -2265,7 +2265,7 @@
 )";
 
   SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
-  auto result = SinglePassRunAndDisassemble<MergeReturnPass>(text, true, true);
+  auto result = SinglePassRunAndDisassemble<MergeReturnPass>(text, true, false);
 
   // Not looking for any particular output.  Other tests do that.
   // Just want to make sure the check for unreachable blocks does not emit an
diff --git a/test/opt/reduce_load_size_test.cpp b/test/opt/reduce_load_size_test.cpp
index abb5cde..4546750 100644
--- a/test/opt/reduce_load_size_test.cpp
+++ b/test/opt/reduce_load_size_test.cpp
@@ -498,6 +498,45 @@
   SinglePassRunAndMatch<ReduceLoadSize>(test, false, 1.1);
 }
 
+TEST_F(ReduceLoadSizeTest, replace_array_with_spec_constant_size) {
+  const std::string test =
+      R"(
+               OpCapability ClipDistance
+               OpExtension "   "
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %1 "       "
+               OpExecutionMode %1 OriginUpperLeft
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+       %uint = OpTypeInt 32 0
+          %6 = OpSpecConstant %uint 538976288
+ %_arr_int_6 = OpTypeArray %int %6
+  %_struct_8 = OpTypeStruct %_arr_int_6
+  %_struct_9 = OpTypeStruct %_struct_8
+%_ptr_Uniform__struct_9 = OpTypePointer Uniform %_struct_9
+; CHECK: [[var:%\w+]] = OpVariable %_ptr_Uniform__struct_9 Uniform
+         %11 = OpVariable %_ptr_Uniform__struct_9 Uniform
+      %int_0 = OpConstant %int 0
+%_ptr_Uniform__arr_int_6 = OpTypePointer Uniform %_arr_int_6
+          %1 = OpFunction %void None %3
+         %14 = OpLabel
+; CHECK: [[ac:%\w+]] = OpAccessChain %_ptr_Uniform__arr_int_6 [[var]] %int_0 %int_0
+; CHECK: [[new_ac:%\w+]] = OpAccessChain %_ptr_Uniform_int [[ac]] %uint_538976288
+; CHECK: [[ld:%\w+]] = OpLoad %int [[new_ac]]
+; CHECK: %18 = OpIAdd %int [[ld]] [[ld]]
+         %15 = OpAccessChain %_ptr_Uniform__arr_int_6 %11 %int_0 %int_0
+         %16 = OpLoad %_arr_int_6 %15
+         %17 = OpCompositeExtract %int %16 538976288
+         %18 = OpIAdd %int %17 %17
+               OpUnreachable
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<ReduceLoadSize>(test, false,
+                                        kDefaultLoadReductionThreshold);
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/scalar_analysis.cpp b/test/opt/scalar_analysis.cpp
index 598d8c7..df2aa8f 100644
--- a/test/opt/scalar_analysis.cpp
+++ b/test/opt/scalar_analysis.cpp
@@ -1202,7 +1202,6 @@
   EXPECT_EQ(phis.size(), 2u);
   SENode* phi_node_1 = analysis.AnalyzeInstruction(phis[0]);
   SENode* phi_node_2 = analysis.AnalyzeInstruction(phis[1]);
-  phi_node_1->DumpDot(std::cout, true);
   EXPECT_NE(phi_node_1, nullptr);
   EXPECT_NE(phi_node_2, nullptr);
 
diff --git a/test/opt/scalar_replacement_test.cpp b/test/opt/scalar_replacement_test.cpp
index 7db997d..0c97c80 100644
--- a/test/opt/scalar_replacement_test.cpp
+++ b/test/opt/scalar_replacement_test.cpp
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "source/opt/scalar_replacement_pass.h"
+
 #include <string>
 
 #include "gmock/gmock.h"
@@ -23,6 +25,18 @@
 namespace opt {
 namespace {
 
+using ScalarReplacementPassName = ::testing::Test;
+
+TEST_F(ScalarReplacementPassName, Default) {
+  auto srp = ScalarReplacementPass();
+  EXPECT_STREQ(srp.name(), "scalar-replacement=100");
+}
+
+TEST_F(ScalarReplacementPassName, Large) {
+  auto srp = ScalarReplacementPass(0xffffffffu);
+  EXPECT_STREQ(srp.name(), "scalar-replacement=4294967295");
+}
+
 using ScalarReplacementTest = PassTest<::testing::Test>;
 
 TEST_F(ScalarReplacementTest, SimpleStruct) {
diff --git a/test/opt/types_test.cpp b/test/opt/types_test.cpp
index 82e4040..552ad97 100644
--- a/test/opt/types_test.cpp
+++ b/test/opt/types_test.cpp
@@ -266,6 +266,67 @@
   }
 }
 
+TEST(Types, TestNumberOfComponentsOnArrays) {
+  Float f32(32);
+  EXPECT_EQ(f32.NumberOfComponents(), 0);
+
+  Array array_size_42(
+      &f32, Array::LengthInfo{99u, {Array::LengthInfo::kConstant, 42u}});
+  EXPECT_EQ(array_size_42.NumberOfComponents(), 42);
+
+  Array array_size_0xDEADBEEF00C0FFEE(
+      &f32, Array::LengthInfo{
+                99u, {Array::LengthInfo::kConstant, 0xC0FFEE, 0xDEADBEEF}});
+  EXPECT_EQ(array_size_0xDEADBEEF00C0FFEE.NumberOfComponents(),
+            0xDEADBEEF00C0FFEEull);
+
+  Array array_size_unknown(
+      &f32,
+      Array::LengthInfo{99u, {Array::LengthInfo::kConstantWithSpecId, 10}});
+  EXPECT_EQ(array_size_unknown.NumberOfComponents(), UINT64_MAX);
+
+  RuntimeArray runtime_array(&f32);
+  EXPECT_EQ(runtime_array.NumberOfComponents(), UINT64_MAX);
+}
+
+TEST(Types, TestNumberOfComponentsOnVectors) {
+  Float f32(32);
+  EXPECT_EQ(f32.NumberOfComponents(), 0);
+
+  for (uint32_t vector_size = 1; vector_size < 4; ++vector_size) {
+    Vector vector(&f32, vector_size);
+    EXPECT_EQ(vector.NumberOfComponents(), vector_size);
+  }
+}
+
+TEST(Types, TestNumberOfComponentsOnMatrices) {
+  Float f32(32);
+  Vector vector(&f32, 2);
+
+  for (uint32_t number_of_columns = 1; number_of_columns < 4;
+       ++number_of_columns) {
+    Matrix matrix(&vector, number_of_columns);
+    EXPECT_EQ(matrix.NumberOfComponents(), number_of_columns);
+  }
+}
+
+TEST(Types, TestNumberOfComponentsOnStructs) {
+  Float f32(32);
+  Vector vector(&f32, 2);
+
+  Struct empty_struct({});
+  EXPECT_EQ(empty_struct.NumberOfComponents(), 0);
+
+  Struct struct_f32({&f32});
+  EXPECT_EQ(struct_f32.NumberOfComponents(), 1);
+
+  Struct struct_f32_vec({&f32, &vector});
+  EXPECT_EQ(struct_f32_vec.NumberOfComponents(), 2);
+
+  Struct struct_100xf32(std::vector<const Type*>(100, &f32));
+  EXPECT_EQ(struct_100xf32.NumberOfComponents(), 100);
+}
+
 TEST(Types, IntSignedness) {
   std::vector<bool> signednesses = {true, false, false, true};
   std::vector<std::unique_ptr<Integer>> types;
diff --git a/test/reduce/structured_loop_to_selection_test.cpp b/test/reduce/structured_loop_to_selection_test.cpp
index 0cfcfdf..d203f3e 100644
--- a/test/reduce/structured_loop_to_selection_test.cpp
+++ b/test/reduce/structured_loop_to_selection_test.cpp
@@ -2957,7 +2957,7 @@
                OpLoopMerge %12 %13 None
                OpBranch %12
          %13 = OpLabel
-               OpBranchConditional %6 %9 %11
+               OpBranch %11
          %12 = OpLabel
                OpBranch %10
          %10 = OpLabel
@@ -2999,7 +2999,7 @@
                OpLoopMerge %12 %13 None
                OpBranch %12
          %13 = OpLabel
-               OpBranchConditional %6 %9 %11
+               OpBranch %11
          %12 = OpLabel
                OpBranch %9
          %10 = OpLabel
@@ -3036,7 +3036,7 @@
                OpSelectionMerge %12 None
                OpBranchConditional %6 %12 %12
          %13 = OpLabel
-               OpBranchConditional %6 %9 %11
+               OpBranch %11
          %12 = OpLabel
                OpBranch %9
          %10 = OpLabel
@@ -3050,8 +3050,7 @@
 
 TEST(StructuredLoopToSelectionReductionPassTest,
      UnreachableInnerLoopContinueBranchingToOuterLoopMerge2) {
-  // In this test, the branch to the outer loop merge from the inner loop's
-  // continue is part of a structured selection.
+  // In this test, the unreachable continue is composed of multiple blocks.
   std::string shader = R"(
                OpCapability Shader
           %1 = OpExtInstImport "GLSL.std.450"
@@ -3073,8 +3072,7 @@
                OpLoopMerge %12 %13 None
                OpBranch %12
          %13 = OpLabel
-               OpSelectionMerge %14 None
-               OpBranchConditional %6 %9 %14
+               OpBranch %14
          %14 = OpLabel
                OpBranch %11
          %12 = OpLabel
@@ -3118,8 +3116,7 @@
                OpLoopMerge %12 %13 None
                OpBranch %12
          %13 = OpLabel
-               OpSelectionMerge %14 None
-               OpBranchConditional %6 %9 %14
+               OpBranch %14
          %14 = OpLabel
                OpBranch %11
          %12 = OpLabel
@@ -3158,8 +3155,7 @@
                OpSelectionMerge %12 None
                OpBranchConditional %6 %12 %12
          %13 = OpLabel
-               OpSelectionMerge %14 None
-               OpBranchConditional %6 %9 %14
+               OpBranch %14
          %14 = OpLabel
                OpBranch %11
          %12 = OpLabel
diff --git a/test/text_advance_test.cpp b/test/text_advance_test.cpp
index 9de77a8..0d23ab1 100644
--- a/test/text_advance_test.cpp
+++ b/test/text_advance_test.cpp
@@ -130,5 +130,14 @@
   EXPECT_EQ(2u, pos.line);
   EXPECT_EQ(4u, pos.index);
 }
+
+TEST(TextAdvance, HandleLotsOfWhitespace) {
+  std::string lots_of_spaces(10000, ' ');
+  lots_of_spaces += "Word";
+  const auto pos = PositionAfterAdvance(lots_of_spaces.c_str());
+  EXPECT_EQ(10000u, pos.column);
+  EXPECT_EQ(0u, pos.line);
+  EXPECT_EQ(10000u, pos.index);
+}
 }  // namespace
 }  // namespace spvtools
diff --git a/test/util/CMakeLists.txt b/test/util/CMakeLists.txt
index 6808783..20038f7 100644
--- a/test/util/CMakeLists.txt
+++ b/test/util/CMakeLists.txt
@@ -17,7 +17,6 @@
        bit_vector_test.cpp
        bitutils_test.cpp
        hash_combine_test.cpp
-       pooled_linked_list_test.cpp
        small_vector_test.cpp
   LIBS SPIRV-Tools-opt
 )
diff --git a/test/util/pooled_linked_list_test.cpp b/test/util/pooled_linked_list_test.cpp
deleted file mode 100644
index 82fb4ac..0000000
--- a/test/util/pooled_linked_list_test.cpp
+++ /dev/null
@@ -1,185 +0,0 @@
-// Copyright (c) 2021 The Khronos Group Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <algorithm>
-#include <list>
-#include <utility>
-#include <vector>
-
-#include "gmock/gmock.h"
-#include "source/util/pooled_linked_list.h"
-
-namespace spvtools {
-namespace utils {
-namespace {
-
-using PooledLinkedListTest = ::testing::Test;
-
-template <typename T>
-static std::vector<T> ToVector(const PooledLinkedList<T>& list) {
-  std::vector<T> vec;
-  for (auto it = list.begin(); it != list.end(); ++it) {
-    vec.push_back(*it);
-  }
-  return vec;
-}
-
-template <typename T>
-static void AppendVector(PooledLinkedList<T>& list, const std::vector<T>& vec) {
-  for (const T& t : vec) {
-    list.push_back(t);
-  }
-}
-
-TEST(PooledLinkedListTest, Empty) {
-  PooledLinkedListNodes<uint32_t> pool;
-  PooledLinkedList<uint32_t> ll(&pool);
-  EXPECT_TRUE(ll.empty());
-
-  ll.push_back(1u);
-  EXPECT_TRUE(!ll.empty());
-}
-
-TEST(PooledLinkedListTest, Iterator) {
-  PooledLinkedListNodes<uint32_t> pool;
-  PooledLinkedList<uint32_t> ll(&pool);
-
-  EXPECT_EQ(ll.begin(), ll.end());
-
-  ll.push_back(1);
-  EXPECT_NE(ll.begin(), ll.end());
-
-  auto it = ll.begin();
-  EXPECT_EQ(*it, 1);
-  ++it;
-  EXPECT_EQ(it, ll.end());
-}
-
-TEST(PooledLinkedListTest, Iterator_algorithms) {
-  PooledLinkedListNodes<uint32_t> pool;
-  PooledLinkedList<uint32_t> ll(&pool);
-
-  AppendVector(ll, {3, 2, 0, 1});
-  EXPECT_EQ(std::distance(ll.begin(), ll.end()), 4);
-  EXPECT_EQ(*std::min_element(ll.begin(), ll.end()), 0);
-  EXPECT_EQ(*std::max_element(ll.begin(), ll.end()), 3);
-}
-
-TEST(PooledLinkedListTest, FrontBack) {
-  PooledLinkedListNodes<uint32_t> pool;
-  PooledLinkedList<uint32_t> ll(&pool);
-
-  ll.push_back(1);
-  EXPECT_EQ(ll.front(), 1);
-  EXPECT_EQ(ll.back(), 1);
-
-  ll.push_back(2);
-  EXPECT_EQ(ll.front(), 1);
-  EXPECT_EQ(ll.back(), 2);
-}
-
-TEST(PooledLinkedListTest, PushBack) {
-  const std::vector<uint32_t> vec = {1, 2, 3, 4, 5, 6};
-
-  PooledLinkedListNodes<uint32_t> pool;
-  PooledLinkedList<uint32_t> ll(&pool);
-
-  AppendVector(ll, vec);
-  EXPECT_EQ(vec, ToVector(ll));
-}
-
-TEST(PooledLinkedListTest, RemoveFirst) {
-  const std::vector<uint32_t> vec = {1, 2, 3, 4, 5, 6};
-
-  PooledLinkedListNodes<uint32_t> pool;
-  PooledLinkedList<uint32_t> ll(&pool);
-
-  EXPECT_FALSE(ll.remove_first(0));
-  AppendVector(ll, vec);
-  EXPECT_FALSE(ll.remove_first(0));
-
-  std::vector<uint32_t> tmp = vec;
-  while (!tmp.empty()) {
-    size_t mid = tmp.size() / 2;
-    uint32_t elt = tmp[mid];
-    tmp.erase(tmp.begin() + mid);
-
-    EXPECT_TRUE(ll.remove_first(elt));
-    EXPECT_FALSE(ll.remove_first(elt));
-    EXPECT_EQ(tmp, ToVector(ll));
-  }
-  EXPECT_TRUE(ll.empty());
-}
-
-TEST(PooledLinkedListTest, RemoveFirst_Duplicates) {
-  const std::vector<uint32_t> vec = {3, 1, 2, 3, 3, 3, 3, 4, 3, 5, 3, 6, 3};
-
-  PooledLinkedListNodes<uint32_t> pool;
-  PooledLinkedList<uint32_t> ll(&pool);
-  AppendVector(ll, vec);
-
-  std::vector<uint32_t> tmp = vec;
-  while (!tmp.empty()) {
-    size_t mid = tmp.size() / 2;
-    uint32_t elt = tmp[mid];
-    tmp.erase(std::find(tmp.begin(), tmp.end(), elt));
-
-    EXPECT_TRUE(ll.remove_first(elt));
-    EXPECT_EQ(tmp, ToVector(ll));
-  }
-  EXPECT_TRUE(ll.empty());
-}
-
-TEST(PooledLinkedList, MoveTo) {
-  const std::vector<uint32_t> vec = {1, 2, 3, 4, 5, 6};
-
-  PooledLinkedListNodes<uint32_t> pool;
-  PooledLinkedList<uint32_t> ll1(&pool);
-  PooledLinkedList<uint32_t> ll2(&pool);
-  PooledLinkedList<uint32_t> ll3(&pool);
-
-  AppendVector(ll1, vec);
-  AppendVector(ll2, vec);
-  AppendVector(ll3, vec);
-  EXPECT_EQ(pool.total_nodes(), vec.size() * 3);
-  EXPECT_EQ(pool.total_nodes(), vec.size() * 3);
-  EXPECT_EQ(pool.free_nodes(), 0);
-
-  // Move two lists to the new pool
-  PooledLinkedListNodes<uint32_t> pool_new;
-  ll1.move_nodes(&pool_new);
-  ll2.move_nodes(&pool_new);
-
-  // Moved nodes should belong to new pool
-  EXPECT_EQ(ll1.pool(), &pool_new);
-  EXPECT_EQ(ll2.pool(), &pool_new);
-
-  // Old pool should be smaller & have free nodes.
-  EXPECT_EQ(pool.used_nodes(), vec.size());
-  EXPECT_EQ(pool.free_nodes(), vec.size() * 2);
-
-  // New pool should be sized exactly and no free nodes.
-  EXPECT_EQ(pool_new.total_nodes(), vec.size() * 2);
-  EXPECT_EQ(pool_new.used_nodes(), vec.size() * 2);
-  EXPECT_EQ(pool_new.free_nodes(), 0);
-
-  // All lists should be preserved
-  EXPECT_EQ(ToVector(ll1), vec);
-  EXPECT_EQ(ToVector(ll2), vec);
-  EXPECT_EQ(ToVector(ll3), vec);
-}
-
-}  // namespace
-}  // namespace utils
-}  // namespace spvtools
diff --git a/test/val/CMakeLists.txt b/test/val/CMakeLists.txt
index 65f2791..d02807a 100644
--- a/test/val/CMakeLists.txt
+++ b/test/val/CMakeLists.txt
@@ -88,8 +88,9 @@
   PCH_FILE pch_test_val
 )
 
-add_spvtools_unittest(TARGET val_stuvw
+add_spvtools_unittest(TARGET val_rstuvw
   SRCS
+       val_ray_query.cpp
        val_small_type_uses_test.cpp
        val_ssa_test.cpp
        val_state_test.cpp
diff --git a/test/val/val_barriers_test.cpp b/test/val/val_barriers_test.cpp
index 1178ca0..f27e467 100644
--- a/test/val/val_barriers_test.cpp
+++ b/test/val/val_barriers_test.cpp
@@ -359,12 +359,25 @@
 
   CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
-  EXPECT_THAT(getDiagnosticString(),
-              AnyVUID("VUID-StandaloneSpirv-None-04638"));
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("ControlBarrier: in Vulkan 1.0 environment Memory Scope is "
-                "limited to Device, Workgroup and Invocation"));
+      HasSubstr(
+          "ControlBarrier: in Vulkan 1.0 environment Memory Scope is can not "
+          "be Subgroup without SubgroupBallotKHR or SubgroupVoteKHR declared"));
+}
+
+TEST_F(ValidateBarriers, OpControlBarrierVulkanMemoryScopeSubgroupVoteKHR) {
+  const std::string capabilities = R"(
+OpCapability SubgroupVoteKHR
+OpExtension "SPV_KHR_subgroup_vote"
+)";
+  const std::string body = R"(
+OpControlBarrier %subgroup %subgroup %none
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body, capabilities),
+                      SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
 }
 
 TEST_F(ValidateBarriers, OpControlBarrierVulkan1p1MemoryScopeSubgroup) {
@@ -386,8 +399,9 @@
   EXPECT_THAT(getDiagnosticString(),
               AnyVUID("VUID-StandaloneSpirv-None-04638"));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("ControlBarrier: in Vulkan environment, Memory Scope "
-                        "cannot be CrossDevice"));
+              HasSubstr("ControlBarrier: in Vulkan environment Memory Scope is "
+                        "limited to Device, QueueFamily, Workgroup, "
+                        "ShaderCallKHR, Subgroup, or Invocation"));
 }
 
 TEST_F(ValidateBarriers,
@@ -751,12 +765,11 @@
 
   CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
-  EXPECT_THAT(getDiagnosticString(),
-              AnyVUID("VUID-StandaloneSpirv-None-04638"));
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("MemoryBarrier: in Vulkan 1.0 environment Memory Scope is "
-                "limited to Device, Workgroup and Invocation"));
+      HasSubstr(
+          "MemoryBarrier: in Vulkan 1.0 environment Memory Scope is can not be "
+          "Subgroup without SubgroupBallotKHR or SubgroupVoteKHR declared"));
 }
 
 TEST_F(ValidateBarriers, OpMemoryBarrierVulkan1p1MemoryScopeSubgroup) {
diff --git a/test/val/val_builtins_test.cpp b/test/val/val_builtins_test.cpp
index b76c163..2cbe9a8 100644
--- a/test/val/val_builtins_test.cpp
+++ b/test/val/val_builtins_test.cpp
@@ -394,6 +394,11 @@
   generator.before_types_ = "OpDecorate %built_in_var BuiltIn ";
   generator.before_types_ += built_in;
   generator.before_types_ += "\n";
+  if ((0 == std::strcmp(storage_class, "Input")) &&
+      (0 == std::strcmp(execution_model, "Fragment"))) {
+    // ensure any needed input types that might require Flat
+    generator.before_types_ += "OpDecorate %built_in_var Flat\n";
+  }
 
   std::ostringstream after_types;
   if (InitializerRequired(storage_class)) {
diff --git a/test/val/val_cfg_test.cpp b/test/val/val_cfg_test.cpp
index 7647746..ede51a9 100644
--- a/test/val/val_cfg_test.cpp
+++ b/test/val/val_cfg_test.cpp
@@ -637,41 +637,6 @@
                    "  %Main = OpFunction %void None %9\n"));
 }
 
-TEST_P(ValidateCFG, HeaderDoesntDominatesMergeBad) {
-  bool is_shader = GetParam() == SpvCapabilityShader;
-  Block entry("entry");
-  Block head("head", SpvOpBranchConditional);
-  Block f("f");
-  Block merge("merge", SpvOpReturn);
-
-  head.SetBody("%cond = OpSLessThan %boolt %one %two\n");
-
-  if (is_shader) head.AppendBody("OpSelectionMerge %merge None\n");
-
-  std::string str = GetDefaultHeader(GetParam()) +
-                    nameOps("head", "merge", std::make_pair("func", "Main")) +
-                    types_consts() +
-                    "%func    = OpFunction %voidt None %funct\n";
-
-  str += entry >> merge;
-  str += head >> std::vector<Block>({merge, f});
-  str += f >> merge;
-  str += merge;
-  str += "OpFunctionEnd\n";
-
-  CompileSuccessfully(str);
-  if (is_shader) {
-    ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
-    EXPECT_THAT(
-        getDiagnosticString(),
-        MatchesRegex("The selection construct with the selection header "
-                     ".\\[%head\\] does not dominate the merge block "
-                     ".\\[%merge\\]\n  %merge = OpLabel\n"));
-  } else {
-    ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
-  }
-}
-
 TEST_P(ValidateCFG, HeaderDoesntStrictlyDominateMergeBad) {
   // If a merge block is reachable, then it must be strictly dominated by
   // its header block.
@@ -698,7 +663,8 @@
     EXPECT_THAT(
         getDiagnosticString(),
         MatchesRegex("The selection construct with the selection header "
-                     ".\\[%head\\] does not strictly dominate the merge block "
+                     ".\\[%head\\] does not strictly structurally dominate the "
+                     "merge block "
                      ".\\[%head\\]\n  %head = OpLabel\n"));
   } else {
     ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()) << str;
@@ -907,16 +873,7 @@
 
 TEST_P(ValidateCFG, UnreachableContinueUnreachableLoopInst) {
   CompileSuccessfully(GetUnreachableContinueUnreachableLoopInst(GetParam()));
-  if (GetParam() == SpvCapabilityShader) {
-    // Shader causes additional structured CFG checks that cause a failure.
-    ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
-    EXPECT_THAT(getDiagnosticString(),
-                HasSubstr("Back-edges (1[%branch] -> 3[%target]) can only be "
-                          "formed between a block and a loop header."));
-
-  } else {
-    ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
-  }
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
 std::string GetUnreachableMergeWithComplexBody(SpvCapability cap) {
@@ -1070,12 +1027,10 @@
   std::string header = GetDefaultHeader(cap);
 
   Block entry("entry");
-  Block foo("foo", SpvOpBranch);
   Block branch("branch", SpvOpBranch);
   Block merge("merge", SpvOpReturn);
   Block target("target", SpvOpBranch);
 
-  foo >> target;
   target >> branch;
 
   entry.AppendBody("%placeholder   = OpVariable %intptrt Function\n");
@@ -1092,7 +1047,6 @@
   str += branch >> std::vector<Block>({merge});
   str += merge;
   str += target;
-  str += foo;
   str += "OpFunctionEnd\n";
 
   return str;
@@ -1156,6 +1110,7 @@
   Block body("body", SpvOpBranchConditional);
   Block t("t", SpvOpReturn);
   Block f("f", SpvOpReturn);
+  Block pre_target("pre_target", SpvOpBranch);
 
   target >> branch;
   body.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
@@ -1163,10 +1118,10 @@
   std::string str = header;
   if (cap == SpvCapabilityShader) {
     branch.AppendBody("OpLoopMerge %merge %target None\n");
-    body.AppendBody("OpSelectionMerge %target None\n");
+    body.AppendBody("OpSelectionMerge %pre_target None\n");
   }
 
-  str += nameOps("branch", "merge", "target", "body", "t", "f",
+  str += nameOps("branch", "merge", "pre_target", "target", "body", "t", "f",
                  std::make_pair("func", "Main"));
   str += types_consts();
   str += "%func    = OpFunction %voidt None %funct\n";
@@ -1176,6 +1131,7 @@
   str += t;
   str += f;
   str += merge;
+  str += pre_target >> target;
   str += target;
   str += "OpFunctionEnd\n";
 
@@ -1296,9 +1252,10 @@
     loop2.SetBody("OpLoopMerge %loop2_merge %loop2 None\n");
   }
 
-  std::string str = GetDefaultHeader(GetParam()) +
-                    nameOps("loop2", "loop2_merge") + types_consts() +
-                    "%func    = OpFunction %voidt None %funct\n";
+  std::string str =
+      GetDefaultHeader(GetParam()) +
+      nameOps("loop1", "loop1_cont_break_block", "loop2", "loop2_merge") +
+      types_consts() + "%func    = OpFunction %voidt None %funct\n";
 
   str += entry >> loop1;
   str += loop1 >> loop1_cont_break_block;
@@ -1389,11 +1346,13 @@
   CompileSuccessfully(str);
   if (GetParam() == SpvCapabilityShader) {
     ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
-    EXPECT_THAT(getDiagnosticString(),
-                MatchesRegex("The continue construct with the continue target "
-                             ".\\[%loop1_cont\\] is not post dominated by the "
-                             "back-edge block .\\[%be_block\\]\n"
-                             "  %be_block = OpLabel\n"));
+    EXPECT_THAT(
+        getDiagnosticString(),
+        MatchesRegex(
+            "The continue construct with the continue target "
+            ".\\[%loop1_cont\\] is not structurally post dominated by the "
+            "back-edge block .\\[%be_block\\]\n"
+            "  %be_block = OpLabel\n"));
   } else {
     ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
   }
@@ -1529,10 +1488,11 @@
   if (is_shader) {
     ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
     EXPECT_THAT(getDiagnosticString(),
-                MatchesRegex("The continue construct with the continue target "
-                             ".\\[%cheader\\] is not post dominated by the "
-                             "back-edge block .\\[%be_block\\]\n"
-                             "  %be_block = OpLabel\n"));
+                MatchesRegex(
+                    "The continue construct with the continue target "
+                    ".\\[%cheader\\] is not structurally post dominated by the "
+                    "back-edge block .\\[%be_block\\]\n"
+                    "  %be_block = OpLabel\n"));
   } else {
     ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
   }
@@ -1561,11 +1521,12 @@
   CompileSuccessfully(str);
   if (is_shader) {
     ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
-    EXPECT_THAT(getDiagnosticString(),
-                MatchesRegex("The continue construct with the continue target "
-                             ".\\[%loop\\] is not post dominated by the "
-                             "back-edge block .\\[%cont\\]\n"
-                             "  %cont = OpLabel\n"))
+    EXPECT_THAT(
+        getDiagnosticString(),
+        MatchesRegex("The continue construct with the continue target "
+                     ".\\[%loop\\] is not structurally post dominated by the "
+                     "back-edge block .\\[%cont\\]\n"
+                     "  %cont = OpLabel\n"))
         << str;
   } else {
     ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
@@ -1597,11 +1558,12 @@
   CompileSuccessfully(str);
   if (is_shader) {
     ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
-    EXPECT_THAT(getDiagnosticString(),
-                MatchesRegex("The continue construct with the continue target "
-                             ".\\[%loop\\] is not post dominated by the "
-                             "back-edge block .\\[%cont\\]\n"
-                             "  %cont = OpLabel\n"));
+    EXPECT_THAT(
+        getDiagnosticString(),
+        MatchesRegex("The continue construct with the continue target "
+                     ".\\[%loop\\] is not structurally post dominated by the "
+                     "back-edge block .\\[%cont\\]\n"
+                     "  %cont = OpLabel\n"));
   } else {
     ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
   }
@@ -1824,40 +1786,6 @@
       << 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 %boolt %one %two\n");
-  if (is_shader) {
-    loop.SetBody("OpLoopMerge %merge %loop None\n");
-  }
-
-  std::string str = GetDefaultHeader(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();
-}
-
 // Unit test to check the case where a basic block is the entry block of 2
 // different constructs. In this case, the basic block is the entry block of a
 // continue construct as well as a selection construct. See issue# 517 for more
@@ -2872,8 +2800,8 @@
   CompileSuccessfully(text);
   EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("block <ID> 9 branches to the loop construct, but not "
-                        "to the loop header <ID> 7"));
+              HasSubstr("Back-edges (10[%10] -> 9[%9]) can only be formed "
+                        "between a block and a loop header"));
 }
 
 TEST_F(ValidateCFG, LoopMergeMergeBlockNotLabel) {
@@ -3275,9 +3203,10 @@
 
   CompileSuccessfully(text);
   EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("block <ID> 13[%13] exits the selection headed by <ID> "
-                        "9[%9], but not via a structured exit"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("The continue construct with the continue target 9[%9] is not "
+                "structurally post dominated by the back-edge block 13[%13]"));
 }
 
 TEST_F(ValidateCFG, BreakFromSwitch) {
@@ -4285,9 +4214,11 @@
 
   CompileSuccessfully(text);
   EXPECT_NE(SPV_SUCCESS, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("The selection construct with the selection header "
-                        "8[%8] does not dominate the merge block 10[%10]\n"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "The selection construct with the selection header "
+          "8[%8] does not structurally dominate the merge block 10[%10]\n"));
 }
 
 TEST_F(ValidateCFG, UnreachableIsStaticallyReachable) {
@@ -4624,6 +4555,36 @@
                         "must be different labels"));
 }
 
+TEST_F(ValidateCFG, BadBackEdgeUnreachableContinue) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%1 = OpTypeVoid
+%2 = OpTypeFunction %1
+%3 = OpFunction %1 None %2
+%4 = OpLabel
+OpBranch %5
+%5 = OpLabel
+OpLoopMerge %6 %7 None
+OpBranch %8
+%8 = OpLabel
+OpBranch %5
+%7 = OpLabel
+OpUnreachable
+%6 = OpLabel
+OpUnreachable
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("The continue construct with the continue target 7[%7] "
+                "does not structurally dominate the back-edge block 8[%8]"));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_conversion_test.cpp b/test/val/val_conversion_test.cpp
index 94bd27d..f6f37b3 100644
--- a/test/val/val_conversion_test.cpp
+++ b/test/val/val_conversion_test.cpp
@@ -1641,6 +1641,131 @@
                 "integer type to have a 64-bit width for Vulkan environment."));
 }
 
+TEST_F(ValidateConversion, ConvertUToAccelerationStructureU32Vec2) {
+  const std::string extensions = R"(
+OpCapability RayQueryKHR
+OpExtension "SPV_KHR_ray_query"
+)";
+  const std::string types = R"(
+%u32vec2ptr_func = OpTypePointer Function %u32vec2
+%typeAS = OpTypeAccelerationStructureKHR
+)";
+  const std::string body = R"(
+%asHandle = OpVariable %u32vec2ptr_func Function
+%load = OpLoad %u32vec2 %asHandle
+%val = OpConvertUToAccelerationStructureKHR %typeAS %load
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body, extensions, "", types).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateConversion, ConvertUToAccelerationStructureSuccessU64) {
+  const std::string extensions = R"(
+OpCapability RayQueryKHR
+OpExtension "SPV_KHR_ray_query"
+)";
+  const std::string types = R"(
+%u64_func = OpTypePointer Function %u64
+%typeAS = OpTypeAccelerationStructureKHR
+)";
+  const std::string body = R"(
+%asHandle = OpVariable %u64_func Function
+%load = OpLoad %u64 %asHandle
+%val = OpConvertUToAccelerationStructureKHR %typeAS %load
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body, extensions, "", types).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateConversion, ConvertUToAccelerationStructureResult) {
+  const std::string extensions = R"(
+OpCapability RayQueryKHR
+OpExtension "SPV_KHR_ray_query"
+)";
+  const std::string types = R"(
+%u32vec2ptr_func = OpTypePointer Function %u32vec2
+%typeRQ = OpTypeRayQueryKHR
+)";
+  const std::string body = R"(
+%asHandle = OpVariable %u32vec2ptr_func Function
+%load = OpLoad %u32vec2 %asHandle
+%val = OpConvertUToAccelerationStructureKHR %typeRQ %load
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body, extensions, "", types).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Expected Result Type to be a Acceleration Structure"));
+}
+
+TEST_F(ValidateConversion, ConvertUToAccelerationStructureU32) {
+  const std::string extensions = R"(
+OpCapability RayQueryKHR
+OpExtension "SPV_KHR_ray_query"
+)";
+  const std::string types = R"(
+%u32ptr_func = OpTypePointer Function %u32
+%typeAS = OpTypeAccelerationStructureKHR
+)";
+  const std::string body = R"(
+%asHandle = OpVariable %u32ptr_func Function
+%load = OpLoad %u32 %asHandle
+%val = OpConvertUToAccelerationStructureKHR %typeAS %load
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body, extensions, "", types).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Expected 64-bit uint scalar or 2-component 32-bit "
+                        "uint vector as input"));
+}
+
+TEST_F(ValidateConversion, ConvertUToAccelerationStructureS64) {
+  const std::string extensions = R"(
+OpCapability RayQueryKHR
+OpExtension "SPV_KHR_ray_query"
+)";
+  const std::string types = R"(
+%s64ptr_func = OpTypePointer Function %s64
+%typeAS = OpTypeAccelerationStructureKHR
+)";
+  const std::string body = R"(
+%asHandle = OpVariable %s64ptr_func Function
+%load = OpLoad %s64 %asHandle
+%val = OpConvertUToAccelerationStructureKHR %typeAS %load
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body, extensions, "", types).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Expected 64-bit uint scalar or 2-component 32-bit "
+                        "uint vector as input"));
+}
+
+TEST_F(ValidateConversion, ConvertUToAccelerationStructureS32Vec2) {
+  const std::string extensions = R"(
+OpCapability RayQueryKHR
+OpExtension "SPV_KHR_ray_query"
+)";
+  const std::string types = R"(
+%s32vec2ptr_func = OpTypePointer Function %s32vec2
+%typeAS = OpTypeAccelerationStructureKHR
+)";
+  const std::string body = R"(
+%asHandle = OpVariable %s32vec2ptr_func Function
+%load = OpLoad %s32vec2 %asHandle
+%val = OpConvertUToAccelerationStructureKHR %typeAS %load
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body, extensions, "", types).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Expected 64-bit uint scalar or 2-component 32-bit "
+                        "uint vector as input"));
+}
+
 using ValidateSmallConversions = spvtest::ValidateBase<std::string>;
 
 CodeGenerator GetSmallConversionsCodeGenerator() {
diff --git a/test/val/val_decoration_test.cpp b/test/val/val_decoration_test.cpp
index e7ecb61..77526bf 100644
--- a/test/val/val_decoration_test.cpp
+++ b/test/val/val_decoration_test.cpp
@@ -42,6 +42,7 @@
 };
 
 using ValidateDecorations = spvtest::ValidateBase<bool>;
+using ValidateDecorationString = spvtest::ValidateBase<std::string>;
 using ValidateVulkanCombineDecorationResult =
     spvtest::ValidateBase<std::tuple<const char*, const char*, TestResult>>;
 
@@ -50,20 +51,20 @@
     OpCapability Shader
     OpCapability Linkage
     OpMemoryModel Logical GLSL450
-    OpDecorate %1 ArrayStride 4
-    OpDecorate %1 RelaxedPrecision
+    OpDecorate %1 Location 4
+    OpDecorate %1 Centroid
     %2 = OpTypeFloat 32
-    %1 = OpTypeRuntimeArray %2
+    %3 = OpTypePointer Output %2
+    %1 = OpVariable %3 Output
     ; Since %1 is used first in Decoration, it gets id 1.
 )";
   const uint32_t id = 1;
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
   // Must have 2 decorations.
-  EXPECT_THAT(
-      vstate_->id_decorations(id),
-      Eq(std::vector<Decoration>{Decoration(SpvDecorationArrayStride, {4}),
-                                 Decoration(SpvDecorationRelaxedPrecision)}));
+  EXPECT_THAT(vstate_->id_decorations(id),
+              Eq(std::set<Decoration>{Decoration(SpvDecorationLocation, {4}),
+                                      Decoration(SpvDecorationCentroid)}));
 }
 
 TEST_F(ValidateDecorations, ValidateOpMemberDecorateRegistration) {
@@ -88,15 +89,15 @@
   const uint32_t arr_id = 1;
   EXPECT_THAT(
       vstate_->id_decorations(arr_id),
-      Eq(std::vector<Decoration>{Decoration(SpvDecorationArrayStride, {4})}));
+      Eq(std::set<Decoration>{Decoration(SpvDecorationArrayStride, {4})}));
 
   // The struct must have 3 decorations.
   const uint32_t struct_id = 2;
   EXPECT_THAT(
       vstate_->id_decorations(struct_id),
-      Eq(std::vector<Decoration>{Decoration(SpvDecorationNonReadable, {}, 2),
-                                 Decoration(SpvDecorationOffset, {2}, 2),
-                                 Decoration(SpvDecorationBufferBlock)}));
+      Eq(std::set<Decoration>{Decoration(SpvDecorationNonReadable, {}, 2),
+                              Decoration(SpvDecorationOffset, {2}, 2),
+                              Decoration(SpvDecorationBufferBlock)}));
 }
 
 TEST_F(ValidateDecorations, ValidateOpMemberDecorateOutOfBound) {
@@ -151,9 +152,9 @@
 
   // Decoration group has 3 decorations.
   auto expected_decorations =
-      std::vector<Decoration>{Decoration(SpvDecorationDescriptorSet, {0}),
-                              Decoration(SpvDecorationRelaxedPrecision),
-                              Decoration(SpvDecorationRestrict)};
+      std::set<Decoration>{Decoration(SpvDecorationDescriptorSet, {0}),
+                           Decoration(SpvDecorationRelaxedPrecision),
+                           Decoration(SpvDecorationRestrict)};
 
   // Decoration group is applied to id 1, 2, 3, and 4. Note that id 1 (which is
   // the decoration group id) also has all the decorations.
@@ -181,7 +182,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
   // Decoration group has 1 decoration.
   auto expected_decorations =
-      std::vector<Decoration>{Decoration(SpvDecorationOffset, {3}, 3)};
+      std::set<Decoration>{Decoration(SpvDecorationOffset, {3}, 3)};
 
   // Decoration group is applied to id 2, 3, and 4.
   EXPECT_THAT(vstate_->id_decorations(2), Eq(expected_decorations));
@@ -8022,6 +8023,7 @@
 OpDecorate %block Block
 OpMemberDecorate %block 0 Offset 0
 OpMemberDecorate %block 0 MatrixStride 3
+OpMemberDecorate %block 0 ColMajor
 OpDecorate %var DescriptorSet 0
 OpDecorate %var Binding 0
 %void = OpTypeVoid
@@ -8058,6 +8060,7 @@
 OpDecorate %block Block
 OpMemberDecorate %block 0 Offset 0
 OpMemberDecorate %block 0 MatrixStride 3
+OpMemberDecorate %block 0 ColMajor
 OpDecorate %var DescriptorSet 0
 OpDecorate %var Binding 0
 %void = OpTypeVoid
@@ -8093,6 +8096,7 @@
 OpDecorate %block Block
 OpMemberDecorate %block 0 Offset 0
 OpMemberDecorate %block 0 MatrixStride 3
+OpMemberDecorate %block 0 ColMajor
 OpDecorate %var DescriptorSet 0
 OpDecorate %var Binding 0
 %void = OpTypeVoid
@@ -8129,6 +8133,7 @@
 OpDecorate %block Block
 OpMemberDecorate %block 0 Offset 0
 OpMemberDecorate %block 0 MatrixStride 3
+OpMemberDecorate %block 0 RowMajor
 OpDecorate %var DescriptorSet 0
 OpDecorate %var Binding 0
 %void = OpTypeVoid
@@ -8363,6 +8368,642 @@
               HasSubstr("PerVertexKHR must be declared as arrays"));
 }
 
+TEST_F(ValidateDecorations, RelaxedPrecisionDecorationOnNumericTypeBad) {
+  const spv_target_env env = SPV_ENV_VULKAN_1_0;
+  std::string spirv = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main"
+               OpExecutionMode %main OriginUpperLeft
+               OpDecorate %float RelaxedPrecision
+       %void = OpTypeVoid
+      %voidfn = OpTypeFunction %void
+      %float = OpTypeFloat 32
+       %main = OpFunction %void None %voidfn
+      %label = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, env);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState(env));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("RelaxPrecision decoration cannot be applied to a type"));
+}
+
+TEST_F(ValidateDecorations, RelaxedPrecisionDecorationOnStructMember) {
+  const spv_target_env env = SPV_ENV_VULKAN_1_0;
+  std::string spirv = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main"
+               OpExecutionMode %main OriginUpperLeft
+               OpMemberDecorate %struct 0 RelaxedPrecision
+       %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+      %float = OpTypeFloat 32
+     %struct = OpTypeStruct %float
+       %main = OpFunction %void None %voidfn
+      %label = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, env);
+  EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState(env));
+}
+
+TEST_F(ValidateDecorations, VulkanFlatMultipleInterfaceGood) {
+  std::string spirv = R"(
+               OpCapability Shader
+               OpCapability Geometry
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %layer %gl_Layer
+               OpExecutionMode %main OriginUpperLeft
+               OpSource GLSL 450
+               OpDecorate %layer Location 0
+               OpDecorate %gl_Layer Flat
+               OpDecorate %gl_Layer BuiltIn Layer
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+%_ptr_Output_int = OpTypePointer Output %int
+      %layer = OpVariable %_ptr_Output_int Output
+%_ptr_Input_int = OpTypePointer Input %int
+   %gl_Layer = OpVariable %_ptr_Input_int Input
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+         %11 = OpLoad %int %gl_Layer
+               OpStore %layer %11
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_SUCCESS,
+            ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_0));
+}
+
+TEST_F(ValidateDecorations, VulkanFlatMultipleInterfaceBad) {
+  std::string spirv = R"(
+               OpCapability Shader
+               OpCapability Geometry
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %layer %gl_Layer
+               OpExecutionMode %main OriginUpperLeft
+               OpSource GLSL 450
+               OpDecorate %layer Location 0
+               OpDecorate %gl_Layer BuiltIn Layer
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+%_ptr_Output_int = OpTypePointer Output %int
+      %layer = OpVariable %_ptr_Output_int Output
+%_ptr_Input_int = OpTypePointer Input %int
+   %gl_Layer = OpVariable %_ptr_Input_int Input
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+         %11 = OpLoad %int %gl_Layer
+               OpStore %layer %11
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID,
+            ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Flat-04744"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Fragment OpEntryPoint operand 4 with Input interfaces with integer "
+          "or float type must have a Flat decoration for Entry Point id 2."));
+}
+
+TEST_F(ValidateDecorations, VulkanNoFlatFloat32) {
+  std::string spirv = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %in
+               OpExecutionMode %main OriginUpperLeft
+               OpSource GLSL 450
+               OpDecorate %in Location 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+%_ptr_Function_float = OpTypePointer Function %float
+%_ptr_Input_float = OpTypePointer Input %float
+         %in = OpVariable %_ptr_Input_float Input
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+          %b = OpVariable %_ptr_Function_float Function
+         %11 = OpLoad %float %in
+               OpStore %b %11
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_SUCCESS,
+            ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_0));
+}
+
+TEST_F(ValidateDecorations, VulkanNoFlatFloat64) {
+  std::string spirv = R"(
+               OpCapability Shader
+               OpCapability Float64
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %in
+               OpExecutionMode %main OriginUpperLeft
+               OpSource GLSL 450
+               OpDecorate %in Location 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+     %double = OpTypeFloat 64
+%_ptr_Function_double = OpTypePointer Function %double
+%_ptr_Input_double = OpTypePointer Input %double
+         %in = OpVariable %_ptr_Input_double Input
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+          %b = OpVariable %_ptr_Function_double Function
+         %11 = OpLoad %double %in
+               OpStore %b %11
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID,
+            ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Flat-04744"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Fragment OpEntryPoint operand 3 with Input interfaces with integer "
+          "or float type must have a Flat decoration for Entry Point id 2."));
+}
+
+TEST_F(ValidateDecorations, VulkanNoFlatVectorFloat64) {
+  std::string spirv = R"(
+               OpCapability Shader
+               OpCapability Float64
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %in
+               OpExecutionMode %main OriginUpperLeft
+               OpSource GLSL 450
+               OpDecorate %in Location 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+     %double = OpTypeFloat 64
+   %v2double = OpTypeVector %double 2
+%_ptr_Function_v2double = OpTypePointer Function %v2double
+%_ptr_Input_v2double = OpTypePointer Input %v2double
+         %in = OpVariable %_ptr_Input_v2double Input
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+          %b = OpVariable %_ptr_Function_v2double Function
+         %11 = OpLoad %v2double %in
+               OpStore %b %11
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_SUCCESS,
+            ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_0));
+}
+
+TEST_F(ValidateDecorations, VulkanNoFlatIntVector) {
+  std::string spirv = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %in
+               OpExecutionMode %main OriginUpperLeft
+               OpSource GLSL 450
+               OpDecorate %in Location 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+      %v2int = OpTypeVector %int 2
+%_ptr_Function_v2int = OpTypePointer Function %v2int
+%_ptr_Input_v2int = OpTypePointer Input %v2int
+         %in = OpVariable %_ptr_Input_v2int Input
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+          %b = OpVariable %_ptr_Function_v2int Function
+         %12 = OpLoad %v2int %in
+               OpStore %b %12
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID,
+            ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Flat-04744"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Fragment OpEntryPoint operand 3 with Input interfaces with integer "
+          "or float type must have a Flat decoration for Entry Point id 2."));
+}
+
+TEST_P(ValidateDecorationString, VulkanOutputInvalidInterface) {
+  const std::string decoration = GetParam();
+  std::stringstream ss;
+  ss << R"(
+               OpCapability Shader
+               OpCapability SampleRateShading
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %out
+               OpExecutionMode %main OriginUpperLeft
+               OpSource GLSL 450
+               OpDecorate %out )"
+     << decoration << R"(
+               OpDecorate %out Location 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+%_ptr_Output_int = OpTypePointer Output %int
+        %out = OpVariable %_ptr_Output_int Output
+      %int_1 = OpConstant %int 1
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               OpStore %out %int_1
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(ss.str(), SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Flat-06201"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "OpEntryPoint interfaces variable must not be fragment execution "
+          "model with an output storage class for Entry Point id 2."));
+}
+
+TEST_P(ValidateDecorationString, VulkanVertexInputInvalidInterface) {
+  const std::string decoration = GetParam();
+  std::stringstream ss;
+  ss << R"(
+               OpCapability Shader
+               OpCapability SampleRateShading
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %out %in
+               OpSource GLSL 450
+               OpDecorate %in )"
+     << decoration << R"(
+               OpDecorate %out Location 0
+               OpDecorate %in Location 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+%_ptr_Output_int = OpTypePointer Output %int
+          %out = OpVariable %_ptr_Output_int Output
+%_ptr_Input_int = OpTypePointer Input %int
+          %in = OpVariable %_ptr_Input_int Input
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+         %11 = OpLoad %int %in
+               OpStore %out %11
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(ss.str(), SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Flat-06202"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("OpEntryPoint interfaces variable must not be vertex execution "
+                "model with an input storage class for Entry Point id 2."));
+}
+
+INSTANTIATE_TEST_SUITE_P(FragmentInputInterface, ValidateDecorationString,
+                         ::testing::Values("Flat", "NoPerspective", "Sample",
+                                           "Centroid"));
+
+TEST_F(ValidateDecorations, NVBindlessSamplerArrayInBlock) {
+  const std::string spirv = R"(
+               OpCapability Shader
+               OpCapability BindlessTextureNV
+               OpExtension "SPV_NV_bindless_texture"
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpSamplerImageAddressingModeNV 64
+               OpEntryPoint Fragment %main "main"
+               OpExecutionMode %main OriginUpperLeft
+               OpSource GLSL 450
+               OpName %main "main"
+               OpName %UBO "UBO"
+               OpMemberName %UBO 0 "uboSampler"
+               OpName %_ ""
+               OpDecorate %array ArrayStride 16
+               OpMemberDecorate %UBO 0 Offset 0
+               OpDecorate %UBO Block
+               OpDecorate %_ DescriptorSet 0
+               OpDecorate %_ Binding 2
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+          %7 = OpTypeImage %float 2D 0 0 0 1 Unknown
+          %8 = OpTypeSampledImage %7
+       %uint = OpTypeInt 32 0
+     %uint_3 = OpConstant %uint 3
+      %array = OpTypeArray %8 %uint_3
+        %UBO = OpTypeStruct %array
+    %pointer = OpTypePointer Uniform %UBO
+          %_ = OpVariable %pointer Uniform
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
+TEST_F(ValidateDecorations, Std140ColMajorMat2x2) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %block Block
+OpMemberDecorate %block 0 Offset 0
+OpMemberDecorate %block 0 ColMajor
+OpMemberDecorate %block 0 MatrixStride 8
+OpDecorate %var DescriptorSet 0
+OpDecorate %var Binding 0
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%float2 = OpTypeVector %float 2
+%matrix = OpTypeMatrix %float2 2
+%block = OpTypeStruct %matrix
+%ptr_block = OpTypePointer Uniform %block
+%var = OpVariable %ptr_block Uniform
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "member 0 is a matrix with stride 8 not satisfying alignment to 16"));
+}
+
+TEST_F(ValidateDecorations, Std140RowMajorMat2x2) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %block Block
+OpMemberDecorate %block 0 Offset 0
+OpMemberDecorate %block 0 RowMajor
+OpMemberDecorate %block 0 MatrixStride 8
+OpDecorate %var DescriptorSet 0
+OpDecorate %var Binding 0
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%float2 = OpTypeVector %float 2
+%matrix = OpTypeMatrix %float2 2
+%block = OpTypeStruct %matrix
+%ptr_block = OpTypePointer Uniform %block
+%var = OpVariable %ptr_block Uniform
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "member 0 is a matrix with stride 8 not satisfying alignment to 16"));
+}
+
+TEST_F(ValidateDecorations, Std140ColMajorMat4x2) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %block Block
+OpMemberDecorate %block 0 Offset 0
+OpMemberDecorate %block 0 ColMajor
+OpMemberDecorate %block 0 MatrixStride 8
+OpDecorate %var DescriptorSet 0
+OpDecorate %var Binding 0
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%float2 = OpTypeVector %float 2
+%matrix = OpTypeMatrix %float2 4
+%block = OpTypeStruct %matrix
+%ptr_block = OpTypePointer Uniform %block
+%var = OpVariable %ptr_block Uniform
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "member 0 is a matrix with stride 8 not satisfying alignment to 16"));
+}
+
+TEST_F(ValidateDecorations, Std140ColMajorMat2x3) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %block Block
+OpMemberDecorate %block 0 Offset 0
+OpMemberDecorate %block 0 ColMajor
+OpMemberDecorate %block 0 MatrixStride 12
+OpDecorate %var DescriptorSet 0
+OpDecorate %var Binding 0
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%float3 = OpTypeVector %float 3
+%matrix = OpTypeMatrix %float3 2
+%block = OpTypeStruct %matrix
+%ptr_block = OpTypePointer Uniform %block
+%var = OpVariable %ptr_block Uniform
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("member 0 is a matrix with stride 12 not satisfying "
+                        "alignment to 16"));
+}
+
+TEST_F(ValidateDecorations, MatrixMissingMajornessUniform) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %block Block
+OpMemberDecorate %block 0 Offset 0
+OpMemberDecorate %block 0 MatrixStride 16
+OpDecorate %var DescriptorSet 0
+OpDecorate %var Binding 0
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%float2 = OpTypeVector %float 2
+%matrix = OpTypeMatrix %float2 2
+%block = OpTypeStruct %matrix
+%ptr_block = OpTypePointer Uniform %block
+%var = OpVariable %ptr_block Uniform
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "must be explicitly laid out with RowMajor or ColMajor decorations"));
+}
+
+TEST_F(ValidateDecorations, MatrixMissingMajornessStorageBuffer) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %block Block
+OpMemberDecorate %block 0 Offset 0
+OpMemberDecorate %block 0 MatrixStride 16
+OpDecorate %var DescriptorSet 0
+OpDecorate %var Binding 0
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%float2 = OpTypeVector %float 2
+%matrix = OpTypeMatrix %float2 2
+%block = OpTypeStruct %matrix
+%ptr_block = OpTypePointer StorageBuffer %block
+%var = OpVariable %ptr_block StorageBuffer
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "must be explicitly laid out with RowMajor or ColMajor decorations"));
+}
+
+TEST_F(ValidateDecorations, MatrixMissingMajornessPushConstant) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %block Block
+OpMemberDecorate %block 0 Offset 0
+OpMemberDecorate %block 0 MatrixStride 16
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%float2 = OpTypeVector %float 2
+%matrix = OpTypeMatrix %float2 2
+%block = OpTypeStruct %matrix
+%ptr_block = OpTypePointer PushConstant %block
+%var = OpVariable %ptr_block PushConstant
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "must be explicitly laid out with RowMajor or ColMajor decorations"));
+}
+
+TEST_F(ValidateDecorations, StructWithRowAndColMajor) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %block Block
+OpMemberDecorate %block 0 Offset 0
+OpMemberDecorate %block 0 MatrixStride 16
+OpMemberDecorate %block 0 ColMajor
+OpMemberDecorate %block 1 Offset 32
+OpMemberDecorate %block 1 MatrixStride 16
+OpMemberDecorate %block 1 RowMajor
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%float2 = OpTypeVector %float 2
+%matrix = OpTypeMatrix %float2 2
+%block = OpTypeStruct %matrix %matrix
+%ptr_block = OpTypePointer PushConstant %block
+%var = OpVariable %ptr_block PushConstant
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_id_test.cpp b/test/val/val_id_test.cpp
index 69257a5..b7e5042 100644
--- a/test/val/val_id_test.cpp
+++ b/test/val/val_id_test.cpp
@@ -6570,6 +6570,35 @@
           "Operand 3[%_ptr_Uniform__struct_2] requires a previous definition"));
 }
 
+TEST_F(ValidateIdWithMessage, NVBindlessSamplerInStruct) {
+  std::string spirv = R"(
+            OpCapability Shader
+            OpCapability BindlessTextureNV
+            OpExtension "SPV_NV_bindless_texture"
+            OpMemoryModel Logical GLSL450
+            OpSamplerImageAddressingModeNV 64
+            OpEntryPoint Fragment %main "main"
+            OpExecutionMode %main OriginUpperLeft
+    %void = OpTypeVoid
+       %3 = OpTypeFunction %void
+   %float = OpTypeFloat 32
+       %7 = OpTypeImage %float 2D 0 0 0 1 Unknown
+       %8 = OpTypeSampledImage %7
+       %9 = OpTypeImage %float 2D 0 0 0 2 Rgba32f
+      %10 = OpTypeSampler
+     %UBO = OpTypeStruct %8 %9 %10
+%_ptr_Uniform_UBO = OpTypePointer Uniform %UBO
+       %_ = OpVariable %_ptr_Uniform_UBO Uniform
+    %main = OpFunction %void None %3
+       %5 = OpLabel
+            OpReturn
+            OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_image_test.cpp b/test/val/val_image_test.cpp
index 76af29c..c4de60a 100644
--- a/test/val/val_image_test.cpp
+++ b/test/val/val_image_test.cpp
@@ -6248,6 +6248,138 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_6));
 }
 
+TEST_F(ValidateImage, NVBindlessSamplerBuiltins) {
+  const std::string text = R"(
+              OpCapability Shader
+              OpCapability Int64
+              OpCapability Image1D
+              OpCapability BindlessTextureNV
+              OpExtension "SPV_NV_bindless_texture"
+         %1 = OpExtInstImport "GLSL.std.450"
+              OpMemoryModel Logical GLSL450
+              OpSamplerImageAddressingModeNV 64
+              OpEntryPoint Fragment %main "main"
+              OpExecutionMode %main OriginUpperLeft
+              OpSource GLSL 450
+              OpName %main "main"
+              OpName %s2D "s2D"
+              OpName %textureHandle "textureHandle"
+              OpName %i1D "i1D"
+              OpName %s "s"
+              OpName %temp "temp"
+      %void = OpTypeVoid
+         %3 = OpTypeFunction %void
+     %float = OpTypeFloat 32
+         %7 = OpTypeImage %float 2D 0 0 0 1 Unknown
+         %8 = OpTypeSampledImage %7
+%_ptr_Function_8 = OpTypePointer Function %8
+     %ulong = OpTypeInt 64 0
+%_ptr_Private_ulong = OpTypePointer Private %ulong
+%textureHandle = OpVariable %_ptr_Private_ulong Private
+        %16 = OpTypeImage %float 1D 0 0 0 2 Rgba32f
+%_ptr_Function_16 = OpTypePointer Function %16
+        %21 = OpTypeSampler
+%_ptr_Function_21 = OpTypePointer Function %21
+%_ptr_Function_ulong = OpTypePointer Function %ulong
+      %main = OpFunction %void None %3
+         %5 = OpLabel
+       %s2D = OpVariable %_ptr_Function_8 Function
+       %i1D = OpVariable %_ptr_Function_16 Function
+         %s = OpVariable %_ptr_Function_21 Function
+      %temp = OpVariable %_ptr_Function_ulong Function
+        %14 = OpLoad %ulong %textureHandle
+        %15 = OpConvertUToSampledImageNV %8 %14
+              OpStore %s2D %15
+        %19 = OpLoad %ulong %textureHandle
+        %20 = OpConvertUToImageNV %16 %19
+              OpStore %i1D %20
+        %24 = OpLoad %ulong %textureHandle
+        %25 = OpConvertUToSamplerNV %21 %24
+              OpStore %s %25
+        %28 = OpLoad %8 %s2D
+        %29 = OpConvertSampledImageToUNV %ulong %28
+              OpStore %temp %29
+        %30 = OpLoad %16 %i1D
+        %31 = OpConvertImageToUNV %ulong %30
+              OpStore %temp %31
+        %32 = OpLoad %21 %s
+        %33 = OpConvertSamplerToUNV %ulong %32
+              OpStore %temp %33
+              OpReturn
+              OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
+TEST_F(ValidateImage, NVBindlessAddressingMode64) {
+  std::string text = R"(
+         OpCapability Shader
+         OpCapability BindlessTextureNV
+         OpExtension "SPV_NV_bindless_texture"
+         OpMemoryModel Logical GLSL450
+         OpSamplerImageAddressingModeNV 64
+         OpEntryPoint GLCompute %func "main"
+%voidt = OpTypeVoid
+%uintt = OpTypeInt 32 0
+%funct = OpTypeFunction %voidt
+%func  = OpFunction %voidt None %funct
+%entry = OpLabel
+%udef  = OpUndef %uintt
+         OpReturn
+         OpFunctionEnd
+)";
+  CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
+TEST_F(ValidateImage, NVBindlessAddressingMode32) {
+  std::string text = R"(
+         OpCapability Shader
+         OpCapability BindlessTextureNV
+         OpExtension "SPV_NV_bindless_texture"
+         OpMemoryModel Logical GLSL450
+         OpSamplerImageAddressingModeNV 32
+         OpEntryPoint GLCompute %func "main"
+%voidt = OpTypeVoid
+%uintt = OpTypeInt 32 0
+%funct = OpTypeFunction %voidt
+%func  = OpFunction %voidt None %funct
+%entry = OpLabel
+%udef  = OpUndef %uintt
+         OpReturn
+         OpFunctionEnd
+)";
+  CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
+TEST_F(ValidateImage, NVBindlessInvalidAddressingMode) {
+  std::string text = R"(
+         OpCapability Shader
+         OpCapability BindlessTextureNV
+         OpExtension "SPV_NV_bindless_texture"
+         OpMemoryModel Logical GLSL450
+         OpSamplerImageAddressingModeNV 0
+         OpEntryPoint GLCompute %func "main"
+%voidt = OpTypeVoid
+%uintt = OpTypeInt 32 0
+%funct = OpTypeFunction %voidt
+%func  = OpFunction %voidt None %funct
+%entry = OpLabel
+%udef  = OpUndef %uintt
+         OpReturn
+         OpFunctionEnd
+)";
+  CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("OpSamplerImageAddressingModeNV bitwidth should be 64 or 32"));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_interfaces_test.cpp b/test/val/val_interfaces_test.cpp
index bec8d02..d9c3748 100644
--- a/test/val/val_interfaces_test.cpp
+++ b/test/val/val_interfaces_test.cpp
@@ -711,7 +711,9 @@
 OpEntryPoint Fragment %main "main" %var1 %var2
 OpExecutionMode %main OriginUpperLeft
 OpDecorate %var1 Location 0
+OpDecorate %var1 Flat
 OpDecorate %var2 Location 1
+OpDecorate %var2 Flat
 %void = OpTypeVoid
 %void_fn = OpTypeFunction %void
 %float = OpTypeFloat 32
diff --git a/test/val/val_layout_test.cpp b/test/val/val_layout_test.cpp
index 7ebd7c0..8cca96f 100644
--- a/test/val/val_layout_test.cpp
+++ b/test/val/val_layout_test.cpp
@@ -667,6 +667,98 @@
 
 // TODO(umar): Test optional instructions
 
+TEST_F(ValidateLayout, ValidNVBindlessTexturelayout) {
+  std::string str = R"(
+         OpCapability Shader
+         OpCapability BindlessTextureNV
+         OpExtension "SPV_NV_bindless_texture"
+         OpMemoryModel Logical GLSL450
+         OpSamplerImageAddressingModeNV 64
+         OpEntryPoint GLCompute %func "main"
+%voidt = OpTypeVoid
+%uintt = OpTypeInt 32 0
+%funct = OpTypeFunction %voidt
+%func  = OpFunction %voidt None %funct
+%entry = OpLabel
+%udef  = OpUndef %uintt
+         OpReturn
+         OpFunctionEnd
+)";
+
+  CompileSuccessfully(str);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateLayout, InvalidValidNVBindlessTexturelayout) {
+  std::string str = R"(
+         OpCapability Shader
+         OpCapability BindlessTextureNV
+         OpExtension "SPV_NV_bindless_texture"
+         OpMemoryModel Logical GLSL450
+         OpEntryPoint GLCompute %func "main"
+         OpSamplerImageAddressingModeNV 64
+%voidt = OpTypeVoid
+%uintt = OpTypeInt 32 0
+%funct = OpTypeFunction %voidt
+%func  = OpFunction %voidt None %funct
+%entry = OpLabel
+%udef  = OpUndef %uintt
+         OpReturn
+         OpFunctionEnd
+)";
+
+  CompileSuccessfully(str);
+  ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "SamplerImageAddressingModeNV is in an invalid layout section"));
+}
+
+TEST_F(ValidateLayout, MissingNVBindlessAddressModeFromLayout) {
+  std::string str = R"(
+         OpCapability Shader
+         OpCapability BindlessTextureNV
+         OpExtension "SPV_NV_bindless_texture"
+         OpMemoryModel Logical GLSL450
+         OpEntryPoint GLCompute %func "main"
+%voidt = OpTypeVoid
+%uintt = OpTypeInt 32 0
+%funct = OpTypeFunction %voidt
+%func  = OpFunction %voidt None %funct
+%entry = OpLabel
+%udef  = OpUndef %uintt
+         OpReturn
+         OpFunctionEnd
+)";
+
+  CompileSuccessfully(str);
+  ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Missing required OpSamplerImageAddressingModeNV instruction"));
+}
+
+TEST_F(ValidateLayout, NVBindlessAddressModeFromLayoutSpecifiedTwice) {
+  std::string str = R"(
+        OpCapability Shader
+        OpCapability BindlessTextureNV
+        OpExtension "SPV_NV_bindless_texture"
+        OpMemoryModel Logical GLSL450
+        OpSamplerImageAddressingModeNV 64
+        OpSamplerImageAddressingModeNV 64
+)";
+
+  CompileSuccessfully(str);
+  ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("OpSamplerImageAddressingModeNV should only be provided once"));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_logicals_test.cpp b/test/val/val_logicals_test.cpp
index 1b76c85..c140672 100644
--- a/test/val/val_logicals_test.cpp
+++ b/test/val/val_logicals_test.cpp
@@ -1159,6 +1159,61 @@
                         "condition to be equal: Select"));
 }
 
+TEST_F(ValidateLogicals, SelectNVBindlessSamplers) {
+  const std::string spirv = R"(
+               OpCapability Shader
+               OpCapability BindlessTextureNV
+               OpExtension "SPV_NV_bindless_texture"
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpSamplerImageAddressingModeNV 64
+               OpEntryPoint Fragment %main "main"
+               OpExecutionMode %main OriginUpperLeft
+               OpSource GLSL 450
+               OpSourceExtension "GL_NV_bindless_texture"
+               OpName %main "main"
+               OpName %s2D "s2D"
+               OpName %pickhandle "pickhandle"
+               OpName %Sampler1 "Sampler1"
+               OpName %Sampler2 "Sampler2"
+               OpDecorate %pickhandle Flat
+               OpDecorate %Sampler1 DescriptorSet 0
+               OpDecorate %Sampler1 Binding 0
+               OpDecorate %Sampler1 BindlessSamplerNV
+               OpDecorate %Sampler2 DescriptorSet 0
+               OpDecorate %Sampler2 Binding 1
+               OpDecorate %Sampler2 BindlessSamplerNV
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+          %7 = OpTypeImage %float 2D 0 0 0 1 Unknown
+          %8 = OpTypeSampledImage %7
+%_ptr_Function_8 = OpTypePointer Function %8
+        %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+      %int_0 = OpConstant %int 0
+       %bool = OpTypeBool
+%_ptr_UniformConstant_8 = OpTypePointer UniformConstant %8
+   %Sampler1 = OpVariable %_ptr_UniformConstant_8 UniformConstant
+   %Sampler2 = OpVariable %_ptr_UniformConstant_8 UniformConstant
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+        %s2D = OpVariable %_ptr_Function_8 Function
+ %pickhandle = OpVariable %_ptr_Function_int Function
+         %14 = OpLoad %int %pickhandle
+         %17 = OpIEqual %bool %14 %int_0
+         %20 = OpLoad %8 %Sampler1
+         %22 = OpLoad %8 %Sampler2
+         %23 = OpSelect %8 %17 %20 %22
+               OpStore %s2D %23
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_memory_test.cpp b/test/val/val_memory_test.cpp
index 5fb43f7..ec1a000 100644
--- a/test/val/val_memory_test.cpp
+++ b/test/val/val_memory_test.cpp
@@ -203,9 +203,11 @@
 )";
   CompileSuccessfully(src, SPV_ENV_VULKAN_1_1);
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Uniform-06807"));
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("From Vulkan spec, section 14.5.2:\n"
+      HasSubstr("From Vulkan spec:\n"
                 "Variables identified with the Uniform storage class are used "
                 "to access transparent buffer backed resources. Such variables "
                 "must be typed as OpTypeStruct, or an array of this type"));
@@ -277,9 +279,11 @@
 )";
   CompileSuccessfully(src, SPV_ENV_VULKAN_1_1);
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Uniform-06807"));
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("From Vulkan spec, section 14.5.2:\n"
+      HasSubstr("From Vulkan spec:\n"
                 "Variables identified with the Uniform storage class are used "
                 "to access transparent buffer backed resources. Such variables "
                 "must be typed as OpTypeStruct, or an array of this type"));
@@ -318,9 +322,11 @@
 )";
   CompileSuccessfully(src, SPV_ENV_VULKAN_1_1);
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Uniform-06807"));
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("From Vulkan spec, section 14.5.2:\n"
+      HasSubstr("From Vulkan spec:\n"
                 "Variables identified with the Uniform storage class are used "
                 "to access transparent buffer backed resources. Such variables "
                 "must be typed as OpTypeStruct, or an array of this type"));
@@ -833,6 +839,8 @@
 )";
   CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-PushConstant-06808"));
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("PushConstant OpVariable <id> '6[%6]' has illegal "
@@ -867,6 +875,8 @@
 )";
   CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-PushConstant-06808"));
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("PushConstant OpVariable <id> '10[%10]' has illegal "
@@ -3490,6 +3500,8 @@
 
   CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Uniform-06925"));
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("In the Vulkan environment, cannot store to Uniform Blocks"));
@@ -3531,6 +3543,8 @@
 
   CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Uniform-06925"));
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("In the Vulkan environment, cannot store to Uniform Blocks"));
@@ -3603,6 +3617,8 @@
 
   CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Uniform-06925"));
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("In the Vulkan environment, cannot store to Uniform Blocks"));
@@ -3647,6 +3663,8 @@
 
   CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Uniform-06925"));
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("In the Vulkan environment, cannot store to Uniform Blocks"));
@@ -3687,6 +3705,8 @@
 
   CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Uniform-06925"));
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("In the Vulkan environment, cannot store to Uniform Blocks"));
@@ -4212,9 +4232,11 @@
 
   CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Uniform-06807"));
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("From Vulkan spec, section 14.5.2:\nVariables identified with "
+      HasSubstr("From Vulkan spec:\nVariables identified with "
                 "the StorageBuffer storage class are used to access "
                 "transparent buffer backed resources. Such variables must be "
                 "typed as OpTypeStruct, or an array of this type"));
@@ -4243,9 +4265,11 @@
 
   CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Uniform-06807"));
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("From Vulkan spec, section 14.5.2:\nVariables identified with "
+      HasSubstr("From Vulkan spec:\nVariables identified with "
                 "the StorageBuffer storage class are used to access "
                 "transparent buffer backed resources. Such variables must be "
                 "typed as OpTypeStruct, or an array of this type"));
@@ -4273,9 +4297,11 @@
 
   CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Uniform-06807"));
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("From Vulkan spec, section 14.5.2:\nVariables identified with "
+      HasSubstr("From Vulkan spec:\nVariables identified with "
                 "the StorageBuffer storage class are used to access "
                 "transparent buffer backed resources. Such variables must be "
                 "typed as OpTypeStruct, or an array of this type"));
diff --git a/test/val/val_modes_test.cpp b/test/val/val_modes_test.cpp
index a37989b..689f0ba 100644
--- a/test/val/val_modes_test.cpp
+++ b/test/val/val_modes_test.cpp
@@ -1101,6 +1101,89 @@
   EXPECT_THAT(SPV_SUCCESS, ValidateInstructions());
 }
 
+
+TEST_F(ValidateMode, FragmentShaderStencilRefFrontTooManyModesBad) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability StencilExportEXT
+OpExtension "SPV_AMD_shader_early_and_late_fragment_tests"
+OpExtension "SPV_EXT_shader_stencil_export"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpExecutionMode %main EarlyAndLateFragmentTestsAMD
+OpExecutionMode %main StencilRefLessFrontAMD
+OpExecutionMode %main StencilRefGreaterFrontAMD
+)" + kVoidFunction;
+
+  CompileSuccessfully(spirv);
+  EXPECT_THAT(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Fragment execution model entry points can specify at most "
+                "one of StencilRefUnchangedFrontAMD, "
+                "StencilRefLessFrontAMD or StencilRefGreaterFrontAMD "
+                "execution modes."));
+}
+
+TEST_F(ValidateMode, FragmentShaderStencilRefBackTooManyModesBad) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability StencilExportEXT
+OpExtension "SPV_AMD_shader_early_and_late_fragment_tests"
+OpExtension "SPV_EXT_shader_stencil_export"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpExecutionMode %main EarlyAndLateFragmentTestsAMD
+OpExecutionMode %main StencilRefLessBackAMD
+OpExecutionMode %main StencilRefGreaterBackAMD
+)" + kVoidFunction;
+
+  CompileSuccessfully(spirv);
+  EXPECT_THAT(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Fragment execution model entry points can specify at most "
+                "one of StencilRefUnchangedBackAMD, "
+                "StencilRefLessBackAMD or StencilRefGreaterBackAMD "
+                "execution modes."));
+}
+
+TEST_F(ValidateMode, FragmentShaderStencilRefFrontGood) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability StencilExportEXT
+OpExtension "SPV_AMD_shader_early_and_late_fragment_tests"
+OpExtension "SPV_EXT_shader_stencil_export"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpExecutionMode %main EarlyAndLateFragmentTestsAMD
+OpExecutionMode %main StencilRefLessFrontAMD
+)" + kVoidFunction;
+
+  CompileSuccessfully(spirv);
+  EXPECT_THAT(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateMode, FragmentShaderStencilRefBackGood) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability StencilExportEXT
+OpExtension "SPV_AMD_shader_early_and_late_fragment_tests"
+OpExtension "SPV_EXT_shader_stencil_export"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpExecutionMode %main EarlyAndLateFragmentTestsAMD
+OpExecutionMode %main StencilRefLessBackAMD
+)" + kVoidFunction;
+
+  CompileSuccessfully(spirv);
+  EXPECT_THAT(SPV_SUCCESS, ValidateInstructions());
+}
+
 TEST_F(ValidateMode, FragmentShaderDemoteVertexBad) {
   const std::string spirv = R"(
 OpCapability Shader
diff --git a/test/val/val_ray_query.cpp b/test/val/val_ray_query.cpp
new file mode 100644
index 0000000..e9b9696
--- /dev/null
+++ b/test/val/val_ray_query.cpp
@@ -0,0 +1,578 @@
+// Copyright (c) 2022 The Khronos Group Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Tests ray query instructions from SPV_KHR_ray_query.
+
+#include <sstream>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "test/val/val_fixtures.h"
+
+namespace spvtools {
+namespace val {
+namespace {
+
+using ::testing::HasSubstr;
+using ::testing::Values;
+
+using ValidateRayQuery = spvtest::ValidateBase<bool>;
+
+std::string GenerateShaderCode(
+    const std::string& body,
+    const std::string& capabilities_and_extensions = "",
+    const std::string& declarations = "") {
+  std::ostringstream ss;
+  ss << R"(
+OpCapability Shader
+OpCapability Int64
+OpCapability Float64
+OpCapability RayQueryKHR
+OpExtension "SPV_KHR_ray_query"
+)";
+
+  ss << capabilities_and_extensions;
+
+  ss << R"(
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+
+OpDecorate %top_level_as DescriptorSet 0
+OpDecorate %top_level_as Binding 0
+
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%bool = OpTypeBool
+%f32 = OpTypeFloat 32
+%f64 = OpTypeFloat 64
+%u32 = OpTypeInt 32 0
+%s32 = OpTypeInt 32 1
+%u64 = OpTypeInt 64 0
+%s64 = OpTypeInt 64 1
+%type_rq = OpTypeRayQueryKHR
+%type_as = OpTypeAccelerationStructureKHR
+
+%s32vec2 = OpTypeVector %s32 2
+%u32vec2 = OpTypeVector %u32 2
+%f32vec2 = OpTypeVector %f32 2
+%u32vec3 = OpTypeVector %u32 3
+%s32vec3 = OpTypeVector %s32 3
+%f32vec3 = OpTypeVector %f32 3
+%u32vec4 = OpTypeVector %u32 4
+%s32vec4 = OpTypeVector %s32 4
+%f32vec4 = OpTypeVector %f32 4
+
+%mat4x3 = OpTypeMatrix %f32vec3 4
+
+%f32_0 = OpConstant %f32 0
+%f64_0 = OpConstant %f64 0
+%s32_0 = OpConstant %s32 0
+%u32_0 = OpConstant %u32 0
+%u64_0 = OpConstant %u64 0
+
+%u32vec3_0 = OpConstantComposite %u32vec3 %u32_0 %u32_0 %u32_0
+%f32vec3_0 = OpConstantComposite %f32vec3 %f32_0 %f32_0 %f32_0
+%f32vec4_0 = OpConstantComposite %f32vec4 %f32_0 %f32_0 %f32_0 %f32_0
+
+%ptr_rq = OpTypePointer Private %type_rq
+%ray_query = OpVariable %ptr_rq Private
+
+%ptr_as = OpTypePointer UniformConstant %type_as
+%top_level_as = OpVariable %ptr_as UniformConstant
+
+%ptr_function_u32 = OpTypePointer Function %u32
+%ptr_function_f32 = OpTypePointer Function %f32
+%ptr_function_f32vec3 = OpTypePointer Function %f32vec3
+)";
+
+  ss << declarations;
+
+  ss << R"(
+%main = OpFunction %void None %func
+%main_entry = OpLabel
+)";
+
+  ss << body;
+
+  ss << R"(
+OpReturn
+OpFunctionEnd)";
+  return ss.str();
+}
+
+std::string RayQueryResult(std::string opcode) {
+  if (opcode.compare("OpRayQueryProceedKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionTypeKHR") == 0 ||
+      opcode.compare("OpRayQueryGetRayTMinKHR") == 0 ||
+      opcode.compare("OpRayQueryGetRayFlagsKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionTKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionInstanceCustomIndexKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionInstanceIdKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionInstanceShaderBindingTableRecord"
+                     "OffsetKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionGeometryIndexKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionPrimitiveIndexKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionBarycentricsKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionFrontFaceKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionCandidateAABBOpaqueKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionObjectRayDirectionKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionObjectRayOriginKHR") == 0 ||
+      opcode.compare("OpRayQueryGetWorldRayDirectionKHR") == 0 ||
+      opcode.compare("OpRayQueryGetWorldRayOriginKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionObjectToWorldKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionWorldToObjectKHR") == 0) {
+    return "%result =";
+  }
+  return "";
+}
+
+std::string RayQueryResultType(std::string opcode, bool valid) {
+  if (opcode.compare("OpRayQueryGetIntersectionTypeKHR") == 0 ||
+      opcode.compare("OpRayQueryGetRayFlagsKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionInstanceCustomIndexKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionInstanceIdKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionInstanceShaderBindingTableRecord"
+                     "OffsetKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionGeometryIndexKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionPrimitiveIndexKHR") == 0) {
+    return valid ? "%u32" : "%f64";
+  }
+
+  if (opcode.compare("OpRayQueryGetRayTMinKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionTKHR") == 0) {
+    return valid ? "%f32" : "%f64";
+  }
+
+  if (opcode.compare("OpRayQueryGetIntersectionBarycentricsKHR") == 0) {
+    return valid ? "%f32vec2" : "%f64";
+  }
+
+  if (opcode.compare("OpRayQueryGetIntersectionObjectRayDirectionKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionObjectRayOriginKHR") == 0 ||
+      opcode.compare("OpRayQueryGetWorldRayDirectionKHR") == 0 ||
+      opcode.compare("OpRayQueryGetWorldRayOriginKHR") == 0) {
+    return valid ? "%f32vec3" : "%f64";
+  }
+
+  if (opcode.compare("OpRayQueryProceedKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionFrontFaceKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionCandidateAABBOpaqueKHR") == 0) {
+    return valid ? "%bool" : "%f64";
+  }
+
+  if (opcode.compare("OpRayQueryGetIntersectionObjectToWorldKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionWorldToObjectKHR") == 0) {
+    return valid ? "%mat4x3" : "%f64";
+  }
+  return "";
+}
+
+std::string RayQueryIntersection(std::string opcode, bool valid) {
+  if (opcode.compare("OpRayQueryGetIntersectionTypeKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionTKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionInstanceCustomIndexKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionInstanceIdKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionInstanceShaderBindingTableRecord"
+                     "OffsetKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionGeometryIndexKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionPrimitiveIndexKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionBarycentricsKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionFrontFaceKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionObjectRayDirectionKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionObjectRayOriginKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionObjectToWorldKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionWorldToObjectKHR") == 0) {
+    return valid ? "%s32_0" : "%f32_0";
+  }
+  return "";
+}
+
+using RayQueryCommon = spvtest::ValidateBase<std::string>;
+
+TEST_P(RayQueryCommon, Success) {
+  std::string opcode = GetParam();
+  std::ostringstream ss;
+  ss << RayQueryResult(opcode);
+  ss << " " << opcode << " ";
+  ss << RayQueryResultType(opcode, true);
+  ss << " %ray_query ";
+  ss << RayQueryIntersection(opcode, true);
+  CompileSuccessfully(GenerateShaderCode(ss.str()).c_str());
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(RayQueryCommon, BadQuery) {
+  std::string opcode = GetParam();
+  std::ostringstream ss;
+  ss << RayQueryResult(opcode);
+  ss << " " << opcode << " ";
+  ss << RayQueryResultType(opcode, true);
+  ss << " %top_level_as ";
+  ss << RayQueryIntersection(opcode, true);
+  CompileSuccessfully(GenerateShaderCode(ss.str()).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Ray Query must be a pointer to OpTypeRayQueryKHR"));
+}
+
+TEST_P(RayQueryCommon, BadResult) {
+  std::string opcode = GetParam();
+  std::string result_type = RayQueryResultType(opcode, false);
+  if (!result_type.empty()) {
+    std::ostringstream ss;
+    ss << RayQueryResult(opcode);
+    ss << " " << opcode << " ";
+    ss << result_type;
+    ss << " %ray_query ";
+    ss << RayQueryIntersection(opcode, true);
+    CompileSuccessfully(GenerateShaderCode(ss.str()).c_str());
+    EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+
+    std::string correct_result_type = RayQueryResultType(opcode, true);
+    if (correct_result_type.compare("%u32") == 0) {
+      EXPECT_THAT(
+          getDiagnosticString(),
+          HasSubstr("expected Result Type to be 32-bit int scalar type"));
+    } else if (correct_result_type.compare("%f32") == 0) {
+      EXPECT_THAT(
+          getDiagnosticString(),
+          HasSubstr("expected Result Type to be 32-bit float scalar type"));
+    } else if (correct_result_type.compare("%f32vec2") == 0) {
+      EXPECT_THAT(getDiagnosticString(),
+                  HasSubstr("expected Result Type to be 32-bit float "
+                            "2-component vector type"));
+    } else if (correct_result_type.compare("%f32vec3") == 0) {
+      EXPECT_THAT(getDiagnosticString(),
+                  HasSubstr("expected Result Type to be 32-bit float "
+                            "3-component vector type"));
+    } else if (correct_result_type.compare("%bool") == 0) {
+      EXPECT_THAT(getDiagnosticString(),
+                  HasSubstr("expected Result Type to be bool scalar type"));
+    } else if (correct_result_type.compare("%mat4x3") == 0) {
+      EXPECT_THAT(getDiagnosticString(),
+                  HasSubstr("expected matrix type as Result Type"));
+    }
+  }
+}
+
+TEST_P(RayQueryCommon, BadIntersection) {
+  std::string opcode = GetParam();
+  std::string intersection = RayQueryIntersection(opcode, false);
+  if (!intersection.empty()) {
+    std::ostringstream ss;
+    ss << RayQueryResult(opcode);
+    ss << " " << opcode << " ";
+    ss << RayQueryResultType(opcode, true);
+    ss << " %ray_query ";
+    ss << intersection;
+    CompileSuccessfully(GenerateShaderCode(ss.str()).c_str());
+    EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+    EXPECT_THAT(
+        getDiagnosticString(),
+        HasSubstr(
+            "expected Intersection ID to be a constant 32-bit int scalar"));
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    ValidateRayQueryCommon, RayQueryCommon,
+    Values("OpRayQueryTerminateKHR", "OpRayQueryConfirmIntersectionKHR",
+           "OpRayQueryProceedKHR", "OpRayQueryGetIntersectionTypeKHR",
+           "OpRayQueryGetRayTMinKHR", "OpRayQueryGetRayFlagsKHR",
+           "OpRayQueryGetWorldRayDirectionKHR",
+           "OpRayQueryGetWorldRayOriginKHR", "OpRayQueryGetIntersectionTKHR",
+           "OpRayQueryGetIntersectionInstanceCustomIndexKHR",
+           "OpRayQueryGetIntersectionInstanceIdKHR",
+           "OpRayQueryGetIntersectionInstanceShaderBindingTableRecordOffsetKHR",
+           "OpRayQueryGetIntersectionGeometryIndexKHR",
+           "OpRayQueryGetIntersectionPrimitiveIndexKHR",
+           "OpRayQueryGetIntersectionBarycentricsKHR",
+           "OpRayQueryGetIntersectionFrontFaceKHR",
+           "OpRayQueryGetIntersectionCandidateAABBOpaqueKHR",
+           "OpRayQueryGetIntersectionObjectRayDirectionKHR",
+           "OpRayQueryGetIntersectionObjectRayOriginKHR",
+           "OpRayQueryGetIntersectionObjectToWorldKHR",
+           "OpRayQueryGetIntersectionWorldToObjectKHR"));
+
+// tests various Intersection operand types
+TEST_F(ValidateRayQuery, IntersectionSuccess) {
+  const std::string body = R"(
+%result_1 = OpRayQueryGetIntersectionFrontFaceKHR %bool %ray_query %s32_0
+%result_2 = OpRayQueryGetIntersectionFrontFaceKHR %bool %ray_query %u32_0
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateRayQuery, IntersectionVector) {
+  const std::string body = R"(
+%result = OpRayQueryGetIntersectionFrontFaceKHR %bool %ray_query %u32vec3_0
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("expected Intersection ID to be a constant 32-bit int scalar"));
+}
+
+TEST_F(ValidateRayQuery, IntersectionNonConstantVariable) {
+  const std::string body = R"(
+%var = OpVariable %ptr_function_u32 Function
+%result = OpRayQueryGetIntersectionFrontFaceKHR %bool %ray_query %var
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("expected Intersection ID to be a constant 32-bit int scalar"));
+}
+
+TEST_F(ValidateRayQuery, IntersectionNonConstantLoad) {
+  const std::string body = R"(
+%var = OpVariable %ptr_function_u32 Function
+%load = OpLoad %u32 %var
+%result = OpRayQueryGetIntersectionFrontFaceKHR %bool %ray_query %load
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("expected Intersection ID to be a constant 32-bit int scalar"));
+}
+
+TEST_F(ValidateRayQuery, InitializeSuccess) {
+  const std::string body = R"(
+%var_u32 = OpVariable %ptr_function_u32 Function
+%var_f32 = OpVariable %ptr_function_f32 Function
+%var_f32vec3 = OpVariable %ptr_function_f32vec3 Function
+
+%as = OpLoad %type_as %top_level_as
+OpRayQueryInitializeKHR %ray_query %as %u32_0 %u32_0 %f32vec3_0 %f32_0 %f32vec3_0 %f32_0
+
+%_u32 = OpLoad %u32 %var_u32
+%_f32 = OpLoad %f32 %var_f32
+%_f32vec3 = OpLoad %f32vec3 %var_f32vec3
+OpRayQueryInitializeKHR %ray_query %as %_u32 %_u32 %_f32vec3 %_f32 %_f32vec3 %_f32
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateRayQuery, InitializeFunctionSuccess) {
+  const std::string declaration = R"(
+%rq_ptr = OpTypePointer Private %type_rq
+%rq_func_type = OpTypeFunction %void %rq_ptr
+%rq_var_1 = OpVariable %rq_ptr Private
+%rq_var_2 = OpVariable %rq_ptr Private
+)";
+
+  const std::string body = R"(
+%fcall_1 = OpFunctionCall %void %rq_func %rq_var_1
+%as_1 = OpLoad %type_as %top_level_as
+OpRayQueryInitializeKHR %rq_var_1 %as_1 %u32_0 %u32_0 %f32vec3_0 %f32_0 %f32vec3_0 %f32_0
+%fcall_2 = OpFunctionCall %void %rq_func %rq_var_2
+OpReturn
+OpFunctionEnd
+%rq_func = OpFunction %void None %rq_func_type
+%rq_param = OpFunctionParameter %rq_ptr
+%label = OpLabel
+%as_2 = OpLoad %type_as %top_level_as
+OpRayQueryInitializeKHR %rq_param %as_2 %u32_0 %u32_0 %f32vec3_0 %f32_0 %f32vec3_0 %f32_0
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body, "", declaration).c_str());
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateRayQuery, InitializeBadRayQuery) {
+  const std::string body = R"(
+%load = OpLoad %type_as %top_level_as
+OpRayQueryInitializeKHR %top_level_as %load %u32_0 %u32_0 %f32vec3_0 %f32_0 %f32vec3_0 %f32_0
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Ray Query must be a pointer to OpTypeRayQueryKHR"));
+}
+
+TEST_F(ValidateRayQuery, InitializeBadAS) {
+  const std::string body = R"(
+OpRayQueryInitializeKHR %ray_query %ray_query %u32_0 %u32_0 %f32vec3_0 %f32_0 %f32vec3_0 %f32_0
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Expected Acceleration Structure to be of type "
+                        "OpTypeAccelerationStructureKHR"));
+}
+
+TEST_F(ValidateRayQuery, InitializeBadRayFlags64) {
+  const std::string body = R"(
+%load = OpLoad %type_as %top_level_as
+OpRayQueryInitializeKHR %ray_query %load %u64_0 %u32_0 %f32vec3_0 %f32_0 %f32vec3_0 %f32_0
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Ray Flags must be a 32-bit int scalar"));
+}
+
+TEST_F(ValidateRayQuery, InitializeBadRayFlagsVector) {
+  const std::string body = R"(
+%load = OpLoad %type_as %top_level_as
+OpRayQueryInitializeKHR %ray_query %load %u32vec2 %u32_0 %f32vec3_0 %f32_0 %f32vec3_0 %f32_0
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Operand 15[%v2uint] cannot be a type"));
+}
+
+TEST_F(ValidateRayQuery, InitializeBadCullMask) {
+  const std::string body = R"(
+%load = OpLoad %type_as %top_level_as
+OpRayQueryInitializeKHR %ray_query %load %u32_0 %f32_0 %f32vec3_0 %f32_0 %f32vec3_0 %f32_0
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Cull Mask must be a 32-bit int scalar"));
+}
+
+TEST_F(ValidateRayQuery, InitializeBadRayOriginVec4) {
+  const std::string body = R"(
+%load = OpLoad %type_as %top_level_as
+OpRayQueryInitializeKHR %ray_query %load %u32_0 %u32_0 %f32vec4_0 %f32_0 %f32vec3_0 %f32_0
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Ray Origin must be a 32-bit float 3-component vector"));
+}
+
+TEST_F(ValidateRayQuery, InitializeBadRayOriginFloat) {
+  const std::string body = R"(
+%var_f32 = OpVariable %ptr_function_f32 Function
+%_f32 = OpLoad %f32 %var_f32
+%load = OpLoad %type_as %top_level_as
+OpRayQueryInitializeKHR %ray_query %load %u32_0 %u32_0 %_f32 %f32_0 %f32vec3_0 %f32_0
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Ray Origin must be a 32-bit float 3-component vector"));
+}
+
+TEST_F(ValidateRayQuery, InitializeBadRayOriginInt) {
+  const std::string body = R"(
+%load = OpLoad %type_as %top_level_as
+OpRayQueryInitializeKHR %ray_query %load %u32_0 %u32_0 %u32vec3_0 %f32_0 %f32vec3_0 %f32_0
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Ray Origin must be a 32-bit float 3-component vector"));
+}
+
+TEST_F(ValidateRayQuery, InitializeBadRayTMin) {
+  const std::string body = R"(
+%load = OpLoad %type_as %top_level_as
+OpRayQueryInitializeKHR %ray_query %load %u32_0 %u32_0 %f32vec3_0 %u32_0 %f32vec3_0 %f32_0
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Ray TMin must be a 32-bit float scalar"));
+}
+
+TEST_F(ValidateRayQuery, InitializeBadRayDirection) {
+  const std::string body = R"(
+%load = OpLoad %type_as %top_level_as
+OpRayQueryInitializeKHR %ray_query %load %u32_0 %u32_0 %f32vec3_0 %f32_0 %f32vec4_0 %f32_0
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Ray Direction must be a 32-bit float 3-component vector"));
+}
+
+TEST_F(ValidateRayQuery, InitializeBadRayTMax) {
+  const std::string body = R"(
+%load = OpLoad %type_as %top_level_as
+OpRayQueryInitializeKHR %ray_query %load %u32_0 %u32_0 %f32vec3_0 %f32_0 %f32vec3_0 %f64_0
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Ray TMax must be a 32-bit float scalar"));
+}
+
+TEST_F(ValidateRayQuery, GenerateIntersectionSuccess) {
+  const std::string body = R"(
+%var = OpVariable %ptr_function_f32 Function
+%load = OpLoad %f32 %var
+OpRayQueryGenerateIntersectionKHR %ray_query %f32_0
+OpRayQueryGenerateIntersectionKHR %ray_query %load
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateRayQuery, GenerateIntersectionBadRayQuery) {
+  const std::string body = R"(
+OpRayQueryGenerateIntersectionKHR %top_level_as %f32_0
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Ray Query must be a pointer to OpTypeRayQueryKHR"));
+}
+
+TEST_F(ValidateRayQuery, GenerateIntersectionBadHitT) {
+  const std::string body = R"(
+OpRayQueryGenerateIntersectionKHR %ray_query %u32_0
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Hit T must be a 32-bit float scalar"));
+}
+
+}  // namespace
+}  // namespace val
+}  // namespace spvtools
diff --git a/tools/io.h b/tools/io.h
index 83a85c1..9dc834e 100644
--- a/tools/io.h
+++ b/tools/io.h
@@ -26,9 +26,15 @@
 
 #define SET_STDIN_TO_BINARY_MODE() _setmode(_fileno(stdin), O_BINARY);
 #define SET_STDIN_TO_TEXT_MODE() _setmode(_fileno(stdin), O_TEXT);
+#define SET_STDOUT_TO_BINARY_MODE() _setmode(_fileno(stdout), O_BINARY);
+#define SET_STDOUT_TO_TEXT_MODE() _setmode(_fileno(stdout), O_TEXT);
+#define SET_STDOUT_MODE(mode) _setmode(_fileno(stdout), mode);
 #else
 #define SET_STDIN_TO_BINARY_MODE()
 #define SET_STDIN_TO_TEXT_MODE()
+#define SET_STDOUT_TO_BINARY_MODE() 0
+#define SET_STDOUT_TO_TEXT_MODE() 0
+#define SET_STDOUT_MODE(mode)
 #endif
 
 // Appends the contents of the |file| to |data|, assuming each element in the
@@ -115,6 +121,44 @@
   return succeeded;
 }
 
+namespace {
+// A class to create and manage a file for outputting data.
+class OutputFile {
+ public:
+  // Opens |filename| in the given mode.  If |filename| is nullptr, the empty
+  // string or "-", stdout will be set to the given mode.
+  OutputFile(const char* filename, const char* mode) {
+    const bool use_stdout =
+        !filename || (filename[0] == '-' && filename[1] == '\0');
+    if (use_stdout) {
+      if (strchr(mode, 'b')) {
+        old_mode_ = SET_STDOUT_TO_BINARY_MODE();
+      } else {
+        old_mode_ = SET_STDOUT_TO_TEXT_MODE();
+      }
+      fp_ = stdout;
+    } else {
+      fp_ = fopen(filename, mode);
+    }
+  }
+
+  ~OutputFile() {
+    if (fp_ == stdout) {
+      SET_STDOUT_MODE(old_mode_);
+    } else if (fp_ != nullptr) {
+      fclose(fp_);
+    }
+  }
+
+  // Returns a file handle to the file.
+  FILE* GetFileHandle() const { return fp_; }
+
+ private:
+  FILE* fp_;
+  int old_mode_;
+};
+}  // namespace
+
 // Writes the given |data| into the file named as |filename| using the given
 // |mode|, assuming |data| is an array of |count| elements of type |T|. If
 // |filename| is nullptr or "-", writes to standard output. If any error occurs,
@@ -122,20 +166,19 @@
 template <typename T>
 bool WriteFile(const char* filename, const char* mode, const T* data,
                size_t count) {
-  const bool use_stdout =
-      !filename || (filename[0] == '-' && filename[1] == '\0');
-  if (FILE* fp = (use_stdout ? stdout : fopen(filename, mode))) {
-    size_t written = fwrite(data, sizeof(T), count, fp);
-    if (count != written) {
-      fprintf(stderr, "error: could not write to file '%s'\n", filename);
-      if (!use_stdout) fclose(fp);
-      return false;
-    }
-    if (!use_stdout) fclose(fp);
-  } else {
+  OutputFile file(filename, mode);
+  FILE* fp = file.GetFileHandle();
+  if (fp == nullptr) {
     fprintf(stderr, "error: could not open file '%s'\n", filename);
     return false;
   }
+
+  size_t written = fwrite(data, sizeof(T), count, fp);
+  if (count != written) {
+    fprintf(stderr, "error: could not write to file '%s'\n", filename);
+    return false;
+  }
+
   return true;
 }