Merge commit 'e0292c269d6f5c8481afb9f2d043c74ee11ca24f' into goog/master

Bug: 123431604
Test: CtsDeqpTestCases
Change-Id: Ie664ea90afc4dff0b4c662a77d2bd4051f8ad4ba
diff --git a/BUILD.gn b/BUILD.gn
index ee897b3..5f0eba9 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -421,6 +421,7 @@
     "source/val/validate_literals.cpp",
     "source/val/validate_logicals.cpp",
     "source/val/validate_memory.cpp",
+    "source/val/validate_memory_semantics.cpp",
     "source/val/validate_mode_setting.cpp",
     "source/val/validate_non_uniform.cpp",
     "source/val/validate_primitives.cpp",
diff --git a/CHANGES b/CHANGES
index f9012fc..7f9008f 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,7 +1,43 @@
 Revision history for SPIRV-Tools
 
-v2018.7-dev 2018-11-07
- - Start v2018.7-dev
+v2018.7-dev 2018-12-10
+ - General:
+   - Created a new tool called spirv-reduce.
+   - Add cmake option to turn off SPIRV_TIMER_ENABLED (#2103)
+   - New optimization pass to update the memory model from GLSL450 to VulkanKHR.
+ - Optimizer
+   - Added the instrumentation passes for bindless validation.
+   - Added passes to help preserve OpLine information (#2027)
+   - Add basic support for EXT_fragment_invocation_density (#2100)
+   - Fix invalid OpPhi generated by merge-return. (#2172)
+   Fixes:
+   - #2018: Don't inline functions with a return in a structured CFG contstruct.
+   - #2047: Fix bug in folding when volatile stores are present.
+   - #2053: Fix check for when folding floating pointer values is allowed.
+   - #2130: Don't inline recursive functions.
+ - Validator
+   - Changed the naming convention of outputing ids with names in diagnostic messages.
+   - Added validation rules for UniformConstant variables in Vulkan.
+   - #1949: Validate uniform variable type in Vulkan
+   - Ensure for OpVariable that result type and storage class operand agree (#2052)
+   - Validator: Support VK_EXT_scalar_block_layout
+   - Added Vulkan memory model semantics validation
+   - Added validation checkes spefic to WebGPU environment.
+   - Add support for VK_EXT_Transform_feedback capabilities (#2088)
+   - Add validation for OpArrayLength. (#2117)
+   - Ensure that function parameter's type is not void (#2118)
+   - Validate pointer variables (#2111)
+   - Add check for QueueFamilyKHMR memory scope (#2144)
+   - Validate PushConstants annotation and type (#2140)
+   - Allow Float16/Int8 for Vulkan 1.0 (#2153)
+   - Check binding annotations in resource variables (#2151, #2167)
+   - Validate OpForwardPointer (#2156)
+   Fixes:
+   - #2049: Allow InstanceId for NV ray tracing
+ - Reduce
+   - Initial commit wit a few passes to reduce test cases.
+   Fixes:
+ 
 
 v2018.6 2018-11-07
  - General:
diff --git a/README.md b/README.md
index 903200a..534664f 100644
--- a/README.md
+++ b/README.md
@@ -273,7 +273,7 @@
   See [`CMakeLists.txt`](CMakeLists.txt) for details.
 * `SPIRV_WERROR={ON|OFF}`, default `ON` - Forces a compilation error on any
   warnings encountered by enabling the compiler-specific compiler front-end
-  option.
+  option.  No compiler front-end options are enabled when this option is OFF.
 
 Additionally, you can pass additional C preprocessor definitions to SPIRV-Tools
 via setting `SPIRV_TOOLS_EXTRA_DEFINITIONS`. For example, by setting it to
diff --git a/include/spirv-tools/optimizer.hpp b/include/spirv-tools/optimizer.hpp
index 8c6b396..f8769c9 100644
--- a/include/spirv-tools/optimizer.hpp
+++ b/include/spirv-tools/optimizer.hpp
@@ -148,6 +148,10 @@
   // returns false.
   bool FlagHasValidForm(const std::string& flag) const;
 
+  // Allows changing, after creation time, the target environment to be
+  // optimized for.  Should be called before calling Run().
+  void SetTargetEnv(const spv_target_env env);
+
   // Optimizes the given SPIR-V module |original_binary| and writes the
   // optimized binary into |optimized_binary|.
   // Returns true on successful optimization, whether or not the module is
diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt
index da3c1cb..efd6330 100644
--- a/source/CMakeLists.txt
+++ b/source/CMakeLists.txt
@@ -316,6 +316,7 @@
   ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_literals.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_logicals.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_memory.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_memory_semantics.cpp
   ${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
diff --git a/source/opt/aggressive_dead_code_elim_pass.cpp b/source/opt/aggressive_dead_code_elim_pass.cpp
index 2793312..82d7499 100644
--- a/source/opt/aggressive_dead_code_elim_pass.cpp
+++ b/source/opt/aggressive_dead_code_elim_pass.cpp
@@ -555,6 +555,14 @@
   // return unmodified.
   if (!AllExtensionsSupported()) return Status::SuccessWithoutChange;
 
+  // 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
+  // |OpGroupDecorate| instruction directly without informing the decoration
+  // manager.  This can put it in an invalid state which will cause an error
+  // when the context tries to update it.  To avoid this problem invalidate
+  // the decoration manager upfront.
+  context()->InvalidateAnalyses(IRContext::Analysis::kAnalysisDecorations);
+
   // Eliminate Dead functions.
   bool modified = EliminateDeadFunctions();
 
diff --git a/source/opt/cfg.cpp b/source/opt/cfg.cpp
index 778c527..7e1097e 100644
--- a/source/opt/cfg.cpp
+++ b/source/opt/cfg.cpp
@@ -167,6 +167,13 @@
   Function* fn = bb->GetParent();
   IRContext* context = module_->context();
 
+  // Get the new header id up front.  If we are out of ids, then we cannot split
+  // the loop.
+  uint32_t new_header_id = context->TakeNextId();
+  if (new_header_id == 0) {
+    return nullptr;
+  }
+
   // Find the insertion point for the new bb.
   Function::iterator header_it = std::find_if(
       fn->begin(), fn->end(),
@@ -197,10 +204,7 @@
     ++iter;
   }
 
-  BasicBlock* new_header =
-      bb->SplitBasicBlock(context, context->TakeNextId(), iter);
-
-  uint32_t new_header_id = new_header->id();
+  BasicBlock* new_header = bb->SplitBasicBlock(context, new_header_id, iter);
   context->AnalyzeDefUse(new_header->GetLabelInst());
 
   // Update cfg
diff --git a/source/opt/cfg.h b/source/opt/cfg.h
index 7bb8ecb..5ff3aa0 100644
--- a/source/opt/cfg.h
+++ b/source/opt/cfg.h
@@ -128,7 +128,8 @@
   // id as |block| and will become a preheader for the loop.  The other block
   // is a new block that will be the new loop header.
   //
-  // Returns a pointer to the new loop header.
+  // Returns a pointer to the new loop header.  Returns |nullptr| if the new
+  // loop pointer could not be created.
   BasicBlock* SplitLoopHeader(BasicBlock* bb);
 
  private:
diff --git a/source/opt/constants.cpp b/source/opt/constants.cpp
index ecb5f97..768364b 100644
--- a/source/opt/constants.cpp
+++ b/source/opt/constants.cpp
@@ -165,6 +165,7 @@
 
 Instruction* ConstantManager::BuildInstructionAndAddToModule(
     const Constant* new_const, Module::inst_iterator* pos, uint32_t type_id) {
+  // TODO(1841): Handle id overflow.
   uint32_t new_id = context()->TakeNextId();
   auto new_inst = CreateInstruction(new_id, new_const, type_id);
   if (!new_inst) {
diff --git a/source/opt/decoration_manager.cpp b/source/opt/decoration_manager.cpp
index 9990661..a12326b 100644
--- a/source/opt/decoration_manager.cpp
+++ b/source/opt/decoration_manager.cpp
@@ -517,6 +517,11 @@
       break;
   }
 }
+
+bool operator==(const DecorationManager& lhs, const DecorationManager& rhs) {
+  return lhs.id_to_decoration_insts_ == rhs.id_to_decoration_insts_;
+}
+
 }  // namespace analysis
 }  // namespace opt
 }  // namespace spvtools
diff --git a/source/opt/decoration_manager.h b/source/opt/decoration_manager.h
index 2d87f5f..a5fb4c8 100644
--- a/source/opt/decoration_manager.h
+++ b/source/opt/decoration_manager.h
@@ -125,6 +125,12 @@
   void AddMemberDecoration(uint32_t member, uint32_t inst_id,
                            uint32_t decoration, uint32_t decoration_value);
 
+  friend bool operator==(const DecorationManager&, const DecorationManager&);
+  friend bool operator!=(const DecorationManager& lhs,
+                         const DecorationManager& rhs) {
+    return !(lhs == rhs);
+  }
+
  private:
   // Analyzes the defs and uses in the given |module| and populates data
   // structures in this class. Does nothing if |module| is nullptr.
@@ -150,6 +156,25 @@
                                                // group.
   };
 
+  friend bool operator==(const TargetData& lhs, const TargetData& rhs) {
+    if (!std::is_permutation(lhs.direct_decorations.begin(),
+                             lhs.direct_decorations.end(),
+                             rhs.direct_decorations.begin())) {
+      return false;
+    }
+    if (!std::is_permutation(lhs.indirect_decorations.begin(),
+                             lhs.indirect_decorations.end(),
+                             rhs.indirect_decorations.begin())) {
+      return false;
+    }
+    if (!std::is_permutation(lhs.decorate_insts.begin(),
+                             lhs.decorate_insts.end(),
+                             rhs.decorate_insts.begin())) {
+      return false;
+    }
+    return true;
+  }
+
   // Mapping from ids to the instructions applying a decoration to those ids.
   // In other words, for each id you get all decoration instructions
   // referencing that id, be it directly (SpvOpDecorate, SpvOpMemberDecorate
diff --git a/source/opt/fold.cpp b/source/opt/fold.cpp
index 0cda772..d6b583f 100644
--- a/source/opt/fold.cpp
+++ b/source/opt/fold.cpp
@@ -114,12 +114,23 @@
       }
 
     // Shifting
-    case SpvOp::SpvOpShiftRightLogical: {
+    case SpvOp::SpvOpShiftRightLogical:
+      if (b > 32) {
+        // This is undefined behaviour.  Choose 0 for consistency.
+        return 0;
+      }
       return a >> b;
-    }
     case SpvOp::SpvOpShiftRightArithmetic:
+      if (b > 32) {
+        // This is undefined behaviour.  Choose 0 for consistency.
+        return 0;
+      }
       return (static_cast<int32_t>(a)) >> b;
     case SpvOp::SpvOpShiftLeftLogical:
+      if (b > 32) {
+        // This is undefined behaviour.  Choose 0 for consistency.
+        return 0;
+      }
       return a << b;
 
     // Bitwise operations
diff --git a/source/opt/ir_builder.h b/source/opt/ir_builder.h
index 434c380..2f741d8 100644
--- a/source/opt/ir_builder.h
+++ b/source/opt/ir_builder.h
@@ -59,6 +59,7 @@
                            preserved_analyses) {}
 
   Instruction* AddNullaryOp(uint32_t type_id, SpvOp opcode) {
+    // TODO(1841): Handle id overflow.
     std::unique_ptr<Instruction> newUnOp(new Instruction(
         GetContext(), opcode, type_id,
         opcode == SpvOpReturn ? 0 : GetContext()->TakeNextId(), {}));
@@ -66,6 +67,7 @@
   }
 
   Instruction* AddUnaryOp(uint32_t type_id, SpvOp opcode, uint32_t operand1) {
+    // TODO(1841): Handle id overflow.
     std::unique_ptr<Instruction> newUnOp(new Instruction(
         GetContext(), opcode, type_id, GetContext()->TakeNextId(),
         {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand1}}}));
@@ -74,6 +76,7 @@
 
   Instruction* AddBinaryOp(uint32_t type_id, SpvOp opcode, uint32_t operand1,
                            uint32_t operand2) {
+    // TODO(1841): Handle id overflow.
     std::unique_ptr<Instruction> newBinOp(new Instruction(
         GetContext(), opcode, type_id,
         opcode == SpvOpStore ? 0 : GetContext()->TakeNextId(),
@@ -84,6 +87,7 @@
 
   Instruction* AddTernaryOp(uint32_t type_id, SpvOp opcode, uint32_t operand1,
                             uint32_t operand2, uint32_t operand3) {
+    // TODO(1841): Handle id overflow.
     std::unique_ptr<Instruction> newTernOp(new Instruction(
         GetContext(), opcode, type_id, GetContext()->TakeNextId(),
         {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand1}},
@@ -95,6 +99,7 @@
   Instruction* AddQuadOp(uint32_t type_id, SpvOp opcode, uint32_t operand1,
                          uint32_t operand2, uint32_t operand3,
                          uint32_t operand4) {
+    // TODO(1841): Handle id overflow.
     std::unique_ptr<Instruction> newQuadOp(new Instruction(
         GetContext(), opcode, type_id, GetContext()->TakeNextId(),
         {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand1}},
@@ -106,6 +111,7 @@
 
   Instruction* AddIdLiteralOp(uint32_t type_id, SpvOp opcode, uint32_t operand1,
                               uint32_t operand2) {
+    // TODO(1841): Handle id overflow.
     std::unique_ptr<Instruction> newBinOp(new Instruction(
         GetContext(), opcode, type_id, GetContext()->TakeNextId(),
         {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand1}},
@@ -124,6 +130,7 @@
     for (size_t i = 0; i < operands.size(); i++) {
       ops.push_back({SPV_OPERAND_TYPE_ID, {operands[i]}});
     }
+    // TODO(1841): Handle id overflow.
     std::unique_ptr<Instruction> new_inst(new Instruction(
         GetContext(), opcode, type_id,
         result != 0 ? result : GetContext()->TakeNextId(), ops));
@@ -251,6 +258,7 @@
   // The id |op1| is the left hand side of the operation.
   // The id |op2| is the right hand side of the operation.
   Instruction* AddIAdd(uint32_t type, uint32_t op1, uint32_t op2) {
+    // TODO(1841): Handle id overflow.
     std::unique_ptr<Instruction> inst(new Instruction(
         GetContext(), SpvOpIAdd, type, GetContext()->TakeNextId(),
         {{SPV_OPERAND_TYPE_ID, {op1}}, {SPV_OPERAND_TYPE_ID, {op2}}}));
@@ -264,6 +272,7 @@
   Instruction* AddULessThan(uint32_t op1, uint32_t op2) {
     analysis::Bool bool_type;
     uint32_t type = GetContext()->get_type_mgr()->GetId(&bool_type);
+    // TODO(1841): Handle id overflow.
     std::unique_ptr<Instruction> inst(new Instruction(
         GetContext(), SpvOpULessThan, type, GetContext()->TakeNextId(),
         {{SPV_OPERAND_TYPE_ID, {op1}}, {SPV_OPERAND_TYPE_ID, {op2}}}));
@@ -277,6 +286,7 @@
   Instruction* AddSLessThan(uint32_t op1, uint32_t op2) {
     analysis::Bool bool_type;
     uint32_t type = GetContext()->get_type_mgr()->GetId(&bool_type);
+    // TODO(1841): Handle id overflow.
     std::unique_ptr<Instruction> inst(new Instruction(
         GetContext(), SpvOpSLessThan, type, GetContext()->TakeNextId(),
         {{SPV_OPERAND_TYPE_ID, {op1}}, {SPV_OPERAND_TYPE_ID, {op2}}}));
@@ -306,6 +316,7 @@
   // bool) for |type|.
   Instruction* AddSelect(uint32_t type, uint32_t cond, uint32_t true_value,
                          uint32_t false_value) {
+    // TODO(1841): Handle id overflow.
     std::unique_ptr<Instruction> select(new Instruction(
         GetContext(), SpvOpSelect, type, GetContext()->TakeNextId(),
         std::initializer_list<Operand>{{SPV_OPERAND_TYPE_ID, {cond}},
@@ -330,6 +341,7 @@
       ops.emplace_back(SPV_OPERAND_TYPE_ID,
                        std::initializer_list<uint32_t>{id});
     }
+    // TODO(1841): Handle id overflow.
     std::unique_ptr<Instruction> construct(
         new Instruction(GetContext(), SpvOpCompositeConstruct, type,
                         GetContext()->TakeNextId(), ops));
@@ -401,6 +413,7 @@
       operands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {index}});
     }
 
+    // TODO(1841): Handle id overflow.
     std::unique_ptr<Instruction> new_inst(
         new Instruction(GetContext(), SpvOpCompositeExtract, type,
                         GetContext()->TakeNextId(), operands));
@@ -424,6 +437,7 @@
       operands.push_back({SPV_OPERAND_TYPE_ID, {index_id}});
     }
 
+    // TODO(1841): Handle id overflow.
     std::unique_ptr<Instruction> new_inst(
         new Instruction(GetContext(), SpvOpAccessChain, type_id,
                         GetContext()->TakeNextId(), operands));
@@ -434,6 +448,7 @@
     std::vector<Operand> operands;
     operands.push_back({SPV_OPERAND_TYPE_ID, {base_ptr_id}});
 
+    // TODO(1841): Handle id overflow.
     std::unique_ptr<Instruction> new_inst(
         new Instruction(GetContext(), SpvOpLoad, type_id,
                         GetContext()->TakeNextId(), operands));
diff --git a/source/opt/ir_context.cpp b/source/opt/ir_context.cpp
index c1158e7..af9ac1a 100644
--- a/source/opt/ir_context.cpp
+++ b/source/opt/ir_context.cpp
@@ -252,6 +252,14 @@
     return false;
   }
 
+  if (AreAnalysesValid(kAnalysisDecorations)) {
+    analysis::DecorationManager* dec_mgr = get_decoration_mgr();
+    analysis::DecorationManager current(module());
+
+    if (*dec_mgr != current) {
+      return false;
+    }
+  }
   return true;
 }
 
@@ -665,6 +673,7 @@
     uint32_t type_id = type_mgr->GetTypeInstruction(reg_type);
     uint32_t varTyPtrId =
         type_mgr->FindPointerToType(type_id, SpvStorageClassInput);
+    // TODO(1841): Handle id overflow.
     var_id = TakeNextId();
     std::unique_ptr<Instruction> newVarOp(
         new Instruction(this, SpvOpVariable, varTyPtrId, var_id,
diff --git a/source/opt/ir_context.h b/source/opt/ir_context.h
index 83e06b8..94bfd01 100644
--- a/source/opt/ir_context.h
+++ b/source/opt/ir_context.h
@@ -450,7 +450,8 @@
     post_dominator_trees_.erase(f);
   }
 
-  // Return the next available SSA id and increment it.
+  // Return the next available SSA id and increment it.  Returns 0 if the
+  // maximum SSA id has been reached.
   inline uint32_t TakeNextId() { return module()->TakeNextIdBound(); }
 
   FeatureManager* get_feature_mgr() {
diff --git a/source/opt/licm_pass.cpp b/source/opt/licm_pass.cpp
index d825667..c553221 100644
--- a/source/opt/licm_pass.cpp
+++ b/source/opt/licm_pass.cpp
@@ -23,70 +23,81 @@
 namespace spvtools {
 namespace opt {
 
-Pass::Status LICMPass::Process() {
-  return ProcessIRContext() ? Status::SuccessWithChange
-                            : Status::SuccessWithoutChange;
-}
+Pass::Status LICMPass::Process() { return ProcessIRContext(); }
 
-bool LICMPass::ProcessIRContext() {
-  bool modified = false;
+Pass::Status LICMPass::ProcessIRContext() {
+  Status status = Status::SuccessWithoutChange;
   Module* module = get_module();
 
   // Process each function in the module
-  for (Function& f : *module) {
-    modified |= ProcessFunction(&f);
+  for (auto func = module->begin();
+       func != module->end() && status != Status::Failure; ++func) {
+    status = CombineStatus(status, ProcessFunction(&*func));
   }
-  return modified;
+  return status;
 }
 
-bool LICMPass::ProcessFunction(Function* f) {
-  bool modified = false;
+Pass::Status LICMPass::ProcessFunction(Function* f) {
+  Status status = Status::SuccessWithoutChange;
   LoopDescriptor* loop_descriptor = context()->GetLoopDescriptor(f);
 
   // Process each loop in the function
-  for (Loop& loop : *loop_descriptor) {
+  for (auto it = loop_descriptor->begin();
+       it != loop_descriptor->end() && status != Status::Failure; ++it) {
+    Loop& loop = *it;
     // Ignore nested loops, as we will process them in order in ProcessLoop
     if (loop.IsNested()) {
       continue;
     }
-    modified |= ProcessLoop(&loop, f);
+    status = CombineStatus(status, ProcessLoop(&loop, f));
   }
-  return modified;
+  return status;
 }
 
-bool LICMPass::ProcessLoop(Loop* loop, Function* f) {
-  bool modified = false;
+Pass::Status LICMPass::ProcessLoop(Loop* loop, Function* f) {
+  Status status = Status::SuccessWithoutChange;
 
   // Process all nested loops first
-  for (Loop* nested_loop : *loop) {
-    modified |= ProcessLoop(nested_loop, f);
+  for (auto nl = loop->begin(); nl != loop->end() && status != Status::Failure;
+       ++nl) {
+    Loop* nested_loop = *nl;
+    status = CombineStatus(status, ProcessLoop(nested_loop, f));
   }
 
   std::vector<BasicBlock*> loop_bbs{};
-  modified |= AnalyseAndHoistFromBB(loop, f, loop->GetHeaderBlock(), &loop_bbs);
+  status = CombineStatus(
+      status,
+      AnalyseAndHoistFromBB(loop, f, loop->GetHeaderBlock(), &loop_bbs));
 
-  for (size_t i = 0; i < loop_bbs.size(); ++i) {
+  for (size_t i = 0; i < loop_bbs.size() && status != Status::Failure; ++i) {
     BasicBlock* bb = loop_bbs[i];
     // do not delete the element
-    modified |= AnalyseAndHoistFromBB(loop, f, bb, &loop_bbs);
+    status =
+        CombineStatus(status, AnalyseAndHoistFromBB(loop, f, bb, &loop_bbs));
   }
 
-  return modified;
+  return status;
 }
 
-bool LICMPass::AnalyseAndHoistFromBB(Loop* loop, Function* f, BasicBlock* bb,
-                                     std::vector<BasicBlock*>* loop_bbs) {
+Pass::Status LICMPass::AnalyseAndHoistFromBB(
+    Loop* loop, Function* f, BasicBlock* bb,
+    std::vector<BasicBlock*>* loop_bbs) {
   bool modified = false;
-  std::function<void(Instruction*)> hoist_inst =
+  std::function<bool(Instruction*)> hoist_inst =
       [this, &loop, &modified](Instruction* inst) {
         if (loop->ShouldHoistInstruction(this->context(), inst)) {
-          HoistInstruction(loop, inst);
+          if (!HoistInstruction(loop, inst)) {
+            return false;
+          }
           modified = true;
         }
+        return true;
       };
 
   if (IsImmediatelyContainedInLoop(loop, f, bb)) {
-    bb->ForEachInst(hoist_inst, false);
+    if (!bb->WhileEachInst(hoist_inst, false)) {
+      return Status::Failure;
+    }
   }
 
   DominatorAnalysis* dom_analysis = context()->GetDominatorAnalysis(f);
@@ -98,7 +109,7 @@
     }
   }
 
-  return modified;
+  return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange);
 }
 
 bool LICMPass::IsImmediatelyContainedInLoop(Loop* loop, Function* f,
@@ -107,10 +118,15 @@
   return loop == (*loop_descriptor)[bb->id()];
 }
 
-void LICMPass::HoistInstruction(Loop* loop, Instruction* inst) {
+bool LICMPass::HoistInstruction(Loop* loop, Instruction* inst) {
+  // TODO(1841): Handle failure to create pre-header.
   BasicBlock* pre_header_bb = loop->GetOrCreatePreHeaderBlock();
+  if (!pre_header_bb) {
+    return false;
+  }
   inst->InsertBefore(std::move(&(*pre_header_bb->tail())));
   context()->set_instr_block(inst, pre_header_bb);
+  return true;
 }
 
 }  // namespace opt
diff --git a/source/opt/licm_pass.h b/source/opt/licm_pass.h
index a174500..a94ae11 100644
--- a/source/opt/licm_pass.h
+++ b/source/opt/licm_pass.h
@@ -35,30 +35,35 @@
 
  private:
   // Searches the IRContext for functions and processes each, moving invariants
-  // outside loops within the function where possible
-  // Returns true if a change was made to a function within the IRContext
-  bool ProcessIRContext();
+  // outside loops within the function where possible.
+  // Returns the status depending on whether or not there was a failure or
+  // change.
+  Pass::Status ProcessIRContext();
 
   // Checks the function for loops, calling ProcessLoop on each one found.
-  // Returns true if a change was made to the function, false otherwise.
-  bool ProcessFunction(Function* f);
+  // Returns the status depending on whether or not there was a failure or
+  // change.
+  Pass::Status ProcessFunction(Function* f);
 
   // Checks for invariants in the loop and attempts to move them to the loops
   // preheader. Works from inner loop to outer when nested loops are found.
-  // Returns true if a change was made to the loop, false otherwise.
-  bool ProcessLoop(Loop* loop, Function* f);
+  // Returns the status depending on whether or not there was a failure or
+  // change.
+  Pass::Status ProcessLoop(Loop* loop, Function* f);
 
   // Analyses each instruction in |bb|, hoisting invariants to |pre_header_bb|.
   // Each child of |bb| wrt to |dom_tree| is pushed to |loop_bbs|
-  bool AnalyseAndHoistFromBB(Loop* loop, Function* f, BasicBlock* bb,
-                             std::vector<BasicBlock*>* loop_bbs);
+  // Returns the status depending on whether or not there was a failure or
+  // change.
+  Pass::Status AnalyseAndHoistFromBB(Loop* loop, Function* f, BasicBlock* bb,
+                                     std::vector<BasicBlock*>* loop_bbs);
 
   // Returns true if |bb| is immediately contained in |loop|
   bool IsImmediatelyContainedInLoop(Loop* loop, Function* f, BasicBlock* bb);
 
   // Move the instruction to the given BasicBlock
   // This method will update the instruction to block mapping for the context
-  void HoistInstruction(Loop* loop, Instruction* inst);
+  bool HoistInstruction(Loop* loop, Instruction* inst);
 };
 
 }  // namespace opt
diff --git a/source/opt/loop_descriptor.cpp b/source/opt/loop_descriptor.cpp
index efc56bd..5aff34c 100644
--- a/source/opt/loop_descriptor.cpp
+++ b/source/opt/loop_descriptor.cpp
@@ -914,6 +914,7 @@
   for (auto& loop : *this) {
     if (!loop.GetPreHeaderBlock()) {
       modified = true;
+      // TODO(1841): Handle failure to create pre-header.
       loop.GetOrCreatePreHeaderBlock();
     }
   }
diff --git a/source/opt/loop_descriptor.h b/source/opt/loop_descriptor.h
index 45a175a..38f017b 100644
--- a/source/opt/loop_descriptor.h
+++ b/source/opt/loop_descriptor.h
@@ -132,7 +132,7 @@
   void SetPreHeaderBlock(BasicBlock* preheader);
 
   // Returns the loop pre-header, if there is no suitable preheader it will be
-  // created.
+  // created.  Returns |nullptr| if it fails to create the preheader.
   BasicBlock* GetOrCreatePreHeaderBlock();
 
   // Returns true if this loop contains any nested loops.
diff --git a/source/opt/loop_fission.cpp b/source/opt/loop_fission.cpp
index 0052406..0678113 100644
--- a/source/opt/loop_fission.cpp
+++ b/source/opt/loop_fission.cpp
@@ -367,6 +367,7 @@
   cloned_loop->UpdateLoopMergeInst();
 
   // Add the loop_ to the module.
+  // TODO(1841): Handle failure to create pre-header.
   Function::iterator it =
       util.GetFunction()->FindBlock(loop_->GetOrCreatePreHeaderBlock()->id());
   util.GetFunction()->AddBasicBlocks(clone_results.cloned_bb_.begin(),
diff --git a/source/opt/loop_peeling.cpp b/source/opt/loop_peeling.cpp
index 227ba4a..b640542 100644
--- a/source/opt/loop_peeling.cpp
+++ b/source/opt/loop_peeling.cpp
@@ -39,6 +39,7 @@
   assert(CanPeelLoop() && "Cannot peel loop!");
 
   std::vector<BasicBlock*> ordered_loop_blocks;
+  // TODO(1841): Handle failure to create pre-header.
   BasicBlock* pre_header = loop_->GetOrCreatePreHeaderBlock();
 
   loop_->ComputeLoopStructuredOrder(&ordered_loop_blocks);
@@ -131,6 +132,7 @@
 
   // Force the creation of a new preheader for the original loop and set it as
   // the merge block for the cloned loop.
+  // TODO(1841): Handle failure to create pre-header.
   cloned_loop_->SetMergeBlock(loop_->GetOrCreatePreHeaderBlock());
 }
 
@@ -345,6 +347,7 @@
   CFG& cfg = *context_->cfg();
   assert(cfg.preds(bb->id()).size() == 1 && "More than one predecessor");
 
+  // TODO(1841): Handle id overflow.
   std::unique_ptr<BasicBlock> new_bb =
       MakeUnique<BasicBlock>(std::unique_ptr<Instruction>(new Instruction(
           context_, SpvOpLabel, 0, context_->TakeNextId(), {})));
@@ -391,6 +394,7 @@
 
 BasicBlock* LoopPeeling::ProtectLoop(Loop* loop, Instruction* condition,
                                      BasicBlock* if_merge) {
+  // TODO(1841): Handle failure to create pre-header.
   BasicBlock* if_block = loop->GetOrCreatePreHeaderBlock();
   // Will no longer be a pre-header because of the if.
   loop->SetPreHeaderBlock(nullptr);
diff --git a/source/opt/loop_unroller.cpp b/source/opt/loop_unroller.cpp
index d3e733a..0d49d88 100644
--- a/source/opt/loop_unroller.cpp
+++ b/source/opt/loop_unroller.cpp
@@ -377,6 +377,7 @@
 // number of bodies.
 void LoopUnrollerUtilsImpl::PartiallyUnrollResidualFactor(Loop* loop,
                                                           size_t factor) {
+  // TODO(1841): Handle id overflow.
   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))};
@@ -834,6 +835,7 @@
 
   // Label instructions aren't covered by normal traversal of the
   // instructions.
+  // TODO(1841): Handle id overflow.
   uint32_t new_label_id = context_->TakeNextId();
 
   // Assign a new id to the label.
@@ -850,6 +852,7 @@
     }
 
     // Give the instruction a new id.
+    // TODO(1841): Handle id overflow.
     inst.SetResultId(context_->TakeNextId());
     def_use_mgr->AnalyzeInstDef(&inst);
 
diff --git a/source/opt/loop_unswitch_pass.cpp b/source/opt/loop_unswitch_pass.cpp
index 59a0cbc..7e374d9 100644
--- a/source/opt/loop_unswitch_pass.cpp
+++ b/source/opt/loop_unswitch_pass.cpp
@@ -99,6 +99,7 @@
   BasicBlock* CreateBasicBlock(Function::iterator ip) {
     analysis::DefUseManager* def_use_mgr = context_->get_def_use_mgr();
 
+    // TODO(1841): Handle id overflow.
     BasicBlock* bb = &*ip.InsertBefore(std::unique_ptr<BasicBlock>(
         new BasicBlock(std::unique_ptr<Instruction>(new Instruction(
             context_, SpvOpLabel, 0, context_->TakeNextId(), {})))));
@@ -459,7 +460,10 @@
   std::vector<BasicBlock*> ordered_loop_blocks_;
 
   // Returns the next usable id for the context.
-  uint32_t TakeNextId() { return context_->TakeNextId(); }
+  uint32_t TakeNextId() {
+    // TODO(1841): Handle id overflow.
+    return context_->TakeNextId();
+  }
 
   // Patches |bb|'s phi instruction by removing incoming value from unexisting
   // or tagged as dead branches.
@@ -474,28 +478,28 @@
       return dead_blocks.count(id) ||
              std::find(bb_preds.begin(), bb_preds.end(), id) == bb_preds.end();
     };
-    bb->ForEachPhiInst([&phi_to_kill, &is_branch_dead, preserve_phi,
-                        this](Instruction* insn) {
-      uint32_t i = 0;
-      while (i < insn->NumInOperands()) {
-        uint32_t incoming_id = insn->GetSingleWordInOperand(i + 1);
-        if (is_branch_dead(incoming_id)) {
-          // Remove the incoming block id operand.
-          insn->RemoveInOperand(i + 1);
-          // Remove the definition id operand.
-          insn->RemoveInOperand(i);
-          continue;
-        }
-        i += 2;
-      }
-      // If there is only 1 remaining edge, propagate the value and
-      // kill the instruction.
-      if (insn->NumInOperands() == 2 && !preserve_phi) {
-        phi_to_kill.push_back(insn);
-        context_->ReplaceAllUsesWith(insn->result_id(),
-                                     insn->GetSingleWordInOperand(0));
-      }
-    });
+    bb->ForEachPhiInst(
+        [&phi_to_kill, &is_branch_dead, preserve_phi, this](Instruction* insn) {
+          uint32_t i = 0;
+          while (i < insn->NumInOperands()) {
+            uint32_t incoming_id = insn->GetSingleWordInOperand(i + 1);
+            if (is_branch_dead(incoming_id)) {
+              // Remove the incoming block id operand.
+              insn->RemoveInOperand(i + 1);
+              // Remove the definition id operand.
+              insn->RemoveInOperand(i);
+              continue;
+            }
+            i += 2;
+          }
+          // If there is only 1 remaining edge, propagate the value and
+          // kill the instruction.
+          if (insn->NumInOperands() == 2 && !preserve_phi) {
+            phi_to_kill.push_back(insn);
+            context_->ReplaceAllUsesWith(insn->result_id(),
+                                         insn->GetSingleWordInOperand(0));
+          }
+        });
     for (Instruction* insn : phi_to_kill) {
       context_->KillInst(insn);
     }
diff --git a/source/opt/loop_utils.cpp b/source/opt/loop_utils.cpp
index 482335f..8c6d355 100644
--- a/source/opt/loop_utils.cpp
+++ b/source/opt/loop_utils.cpp
@@ -352,6 +352,7 @@
     assert(insert_pt != function->end() && "Basic Block not found");
 
     // Create the dedicate exit basic block.
+    // TODO(1841): Handle id overflow.
     BasicBlock& exit = *insert_pt.InsertBefore(std::unique_ptr<BasicBlock>(
         new BasicBlock(std::unique_ptr<Instruction>(new Instruction(
             context_, SpvOpLabel, 0, context_->TakeNextId(), {})))));
@@ -491,6 +492,7 @@
   Loop* new_loop = CloneLoop(cloning_result);
 
   // Create a new exit block/label for the new loop.
+  // TODO(1841): Handle id overflow.
   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))};
@@ -528,6 +530,7 @@
                           inst->SetOperand(operand, {new_header});
                       });
 
+  // TODO(1841): Handle failure to create pre-header.
   def_use->ForEachUse(
       loop_->GetOrCreatePreHeaderBlock()->id(),
       [new_merge_block, this](Instruction* inst, uint32_t operand) {
@@ -560,6 +563,7 @@
     // between old and new ids.
     BasicBlock* new_bb = old_bb->Clone(context_);
     new_bb->SetParent(&function_);
+    // TODO(1841): Handle id overflow.
     new_bb->GetLabelInst()->SetResultId(context_->TakeNextId());
     def_use_mgr->AnalyzeInstDef(new_bb->GetLabelInst());
     context_->set_instr_block(new_bb->GetLabelInst(), new_bb);
@@ -575,6 +579,7 @@
          new_inst != new_bb->end(); ++new_inst, ++old_inst) {
       cloning_result->ptr_map_[&*new_inst] = &*old_inst;
       if (new_inst->HasResultId()) {
+        // TODO(1841): Handle id overflow.
         new_inst->SetResultId(context_->TakeNextId());
         cloning_result->value_map_[old_inst->result_id()] =
             new_inst->result_id();
diff --git a/source/opt/merge_return_pass.cpp b/source/opt/merge_return_pass.cpp
index 820760c..a13540c 100644
--- a/source/opt/merge_return_pass.cpp
+++ b/source/opt/merge_return_pass.cpp
@@ -121,7 +121,9 @@
     // Predicate successors of the original return blocks as necessary.
     if (std::find(return_blocks.begin(), return_blocks.end(), block) !=
         return_blocks.end()) {
-      PredicateBlocks(block, &predicated, &order);
+      if (!PredicateBlocks(block, &predicated, &order)) {
+        return false;
+      }
     }
 
     // Generate state for next block
@@ -204,7 +206,11 @@
     RecordReturned(block);
     RecordReturnValue(block);
   }
+
   BasicBlock* target_block = context()->get_instr_block(target);
+  if (target_block->GetLoopMergeInst()) {
+    cfg()->SplitLoopHeader(target_block);
+  }
   UpdatePhiNodes(block, target_block);
 
   Instruction* return_inst = block->terminator();
@@ -239,8 +245,22 @@
   if (inst.result_id() != 0) {
     std::vector<Instruction*> users_to_update;
     context()->get_def_use_mgr()->ForEachUser(
-        &inst, [&users_to_update, &dom_tree, inst_bb, this](Instruction* user) {
-          BasicBlock* user_bb = context()->get_instr_block(user);
+        &inst,
+        [&users_to_update, &dom_tree, &inst, inst_bb, this](Instruction* user) {
+          BasicBlock* user_bb = nullptr;
+          if (user->opcode() != SpvOpPhi) {
+            user_bb = context()->get_instr_block(user);
+          } else {
+            // For OpPhi, the use should be considered to be in the predecessor.
+            for (uint32_t i = 0; i < user->NumInOperands(); i += 2) {
+              if (user->GetSingleWordInOperand(i) == inst.result_id()) {
+                uint32_t user_bb_id = user->GetSingleWordInOperand(i + 1);
+                user_bb = context()->get_instr_block(user_bb_id);
+                break;
+              }
+            }
+          }
+
           // If |user_bb| is nullptr, then |user| is not in the function.  It is
           // something like an OpName or decoration, which should not be
           // replaced with the result of the OpPhi.
@@ -288,14 +308,14 @@
   }
 }
 
-void MergeReturnPass::PredicateBlocks(
+bool MergeReturnPass::PredicateBlocks(
     BasicBlock* return_block, std::unordered_set<BasicBlock*>* predicated,
     std::list<BasicBlock*>* order) {
   // The CFG is being modified as the function proceeds so avoid caching
   // successors.
 
   if (predicated->count(return_block)) {
-    return;
+    return true;
   }
 
   BasicBlock* block = nullptr;
@@ -328,12 +348,15 @@
     while (state->LoopMergeId() == next->id()) {
       state++;
     }
-    BreakFromConstruct(block, next, predicated, order);
+    if (!BreakFromConstruct(block, next, predicated, order)) {
+      return false;
+    }
     block = next;
   }
+  return true;
 }
 
-void MergeReturnPass::BreakFromConstruct(
+bool MergeReturnPass::BreakFromConstruct(
     BasicBlock* block, BasicBlock* merge_block,
     std::unordered_set<BasicBlock*>* predicated,
     std::list<BasicBlock*>* order) {
@@ -353,7 +376,12 @@
   // If |block| is a loop header, then the back edge must jump to the original
   // code, not the new header.
   if (block->GetLoopMergeInst()) {
-    cfg()->SplitLoopHeader(block);
+    if (cfg()->SplitLoopHeader(block) == nullptr) {
+      return false;
+    }
+  }
+  if (merge_block->GetLoopMergeInst()) {
+    cfg()->SplitLoopHeader(merge_block);
   }
 
   // Leave the phi instructions behind.
@@ -407,6 +435,7 @@
 
   assert(old_body->begin() != old_body->end());
   assert(block->begin() != block->end());
+  return true;
 }
 
 void MergeReturnPass::RecordReturned(BasicBlock* block) {
diff --git a/source/opt/merge_return_pass.h b/source/opt/merge_return_pass.h
index f27332f..264e8b7 100644
--- a/source/opt/merge_return_pass.h
+++ b/source/opt/merge_return_pass.h
@@ -212,7 +212,9 @@
   //
   // If new blocks that are created will be added to |order|.  This way a call
   // can traverse these new block in structured order.
-  void PredicateBlocks(BasicBlock* return_block,
+  //
+  // Returns true if successful.
+  bool PredicateBlocks(BasicBlock* return_block,
                        std::unordered_set<BasicBlock*>* pSet,
                        std::list<BasicBlock*>* order);
 
@@ -222,7 +224,9 @@
   //
   // If new blocks that are created will be added to |order|.  This way a call
   // can traverse these new block in structured order.
-  void BreakFromConstruct(BasicBlock* block, BasicBlock* merge_block,
+  //
+  // Returns true if successful.
+  bool BreakFromConstruct(BasicBlock* block, BasicBlock* merge_block,
                           std::unordered_set<BasicBlock*>* predicated,
                           std::list<BasicBlock*>* order);
 
diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp
index d30528b..856ede7 100644
--- a/source/opt/optimizer.cpp
+++ b/source/opt/optimizer.cpp
@@ -56,7 +56,7 @@
 struct Optimizer::Impl {
   explicit Impl(spv_target_env env) : target_env(env), pass_manager() {}
 
-  const spv_target_env target_env;  // Target environment.
+  spv_target_env target_env;        // Target environment.
   opt::PassManager pass_manager;    // Internal implementation pass manager.
 };
 
@@ -450,6 +450,10 @@
   return true;
 }
 
+void Optimizer::SetTargetEnv(const spv_target_env env) {
+  impl_->target_env = env;
+}
+
 bool Optimizer::Run(const uint32_t* original_binary,
                     const size_t original_binary_size,
                     std::vector<uint32_t>* optimized_binary) const {
diff --git a/source/opt/pass.h b/source/opt/pass.h
index aabc645..c95f502 100644
--- a/source/opt/pass.h
+++ b/source/opt/pass.h
@@ -122,6 +122,7 @@
   virtual Status Process() = 0;
 
   // Return the next available SSA id and increment it.
+  // TODO(1841): Handle id overflow.
   uint32_t TakeNextId() { return context_->TakeNextId(); }
 
  private:
@@ -136,6 +137,10 @@
   bool already_run_;
 };
 
+inline Pass::Status CombineStatus(Pass::Status a, Pass::Status b) {
+  return std::min(a, b);
+}
+
 }  // namespace opt
 }  // namespace spvtools
 
diff --git a/source/opt/reflect.h b/source/opt/reflect.h
index fb2de7b..79d90bd 100644
--- a/source/opt/reflect.h
+++ b/source/opt/reflect.h
@@ -44,7 +44,8 @@
 }
 inline bool IsTypeInst(SpvOp opcode) {
   return (opcode >= SpvOpTypeVoid && opcode <= SpvOpTypeForwardPointer) ||
-         opcode == SpvOpTypePipeStorage || opcode == SpvOpTypeNamedBarrier;
+         opcode == SpvOpTypePipeStorage || opcode == SpvOpTypeNamedBarrier ||
+         opcode == SpvOpTypeAccelerationStructureNV;
 }
 inline bool IsConstantInst(SpvOp opcode) {
   return opcode >= SpvOpConstantTrue && opcode <= SpvOpSpecConstantOp;
diff --git a/source/opt/ssa_rewrite_pass.cpp b/source/opt/ssa_rewrite_pass.cpp
index 83d2433..0a5d390 100644
--- a/source/opt/ssa_rewrite_pass.cpp
+++ b/source/opt/ssa_rewrite_pass.cpp
@@ -90,6 +90,7 @@
 
 SSARewriter::PhiCandidate& SSARewriter::CreatePhiCandidate(uint32_t var_id,
                                                            BasicBlock* bb) {
+  // TODO(1841): Handle id overflow.
   uint32_t phi_result_id = pass_->context()->TakeNextId();
   auto result = phi_candidates_.emplace(
       phi_result_id, PhiCandidate(var_id, phi_result_id, bb));
diff --git a/source/opt/type_manager.cpp b/source/opt/type_manager.cpp
index cb19cca..722b9b9 100644
--- a/source/opt/type_manager.cpp
+++ b/source/opt/type_manager.cpp
@@ -205,6 +205,7 @@
   if (id != 0) return id;
 
   std::unique_ptr<Instruction> typeInst;
+  // TODO(1841): Handle id overflow.
   id = context()->TakeNextId();
   RegisterType(id, *type);
   switch (type->kind()) {
@@ -222,6 +223,7 @@
     DefineParameterlessCase(Queue);
     DefineParameterlessCase(PipeStorage);
     DefineParameterlessCase(NamedBarrier);
+    DefineParameterlessCase(AccelerationStructureNV);
 #undef DefineParameterlessCase
     case Type::kInteger:
       typeInst = MakeUnique<Instruction>(
@@ -397,6 +399,7 @@
   }
 
   // Must create the pointer type.
+  // TODO(1841): Handle id overflow.
   uint32_t resultId = context()->TakeNextId();
   std::unique_ptr<Instruction> type_inst(
       new Instruction(context(), SpvOpTypePointer, 0, resultId,
@@ -467,6 +470,7 @@
     DefineNoSubtypeCase(Pipe);
     DefineNoSubtypeCase(PipeStorage);
     DefineNoSubtypeCase(NamedBarrier);
+    DefineNoSubtypeCase(AccelerationStructureNV);
 #undef DefineNoSubtypeCase
     case Type::kVector: {
       const Vector* vec_ty = type.AsVector();
diff --git a/source/opt/types.cpp b/source/opt/types.cpp
index 96644c5..cfafc7d 100644
--- a/source/opt/types.cpp
+++ b/source/opt/types.cpp
@@ -123,6 +123,7 @@
     DeclareKindCase(ForwardPointer);
     DeclareKindCase(PipeStorage);
     DeclareKindCase(NamedBarrier);
+    DeclareKindCase(AccelerationStructureNV);
 #undef DeclareKindCase
     default:
       assert(false && "Unhandled type");
@@ -166,6 +167,7 @@
     DeclareKindCase(ForwardPointer);
     DeclareKindCase(PipeStorage);
     DeclareKindCase(NamedBarrier);
+    DeclareKindCase(AccelerationStructureNV);
 #undef DeclareKindCase
     default:
       assert(false && "Unhandled type");
@@ -214,6 +216,7 @@
     DeclareKindCase(ForwardPointer);
     DeclareKindCase(PipeStorage);
     DeclareKindCase(NamedBarrier);
+    DeclareKindCase(AccelerationStructureNV);
 #undef DeclareKindCase
     default:
       assert(false && "Unhandled type");
diff --git a/source/opt/types.h b/source/opt/types.h
index 28731c6..d77117d 100644
--- a/source/opt/types.h
+++ b/source/opt/types.h
@@ -56,6 +56,7 @@
 class ForwardPointer;
 class PipeStorage;
 class NamedBarrier;
+class AccelerationStructureNV;
 
 // Abstract class for a SPIR-V type. It has a bunch of As<sublcass>() methods,
 // which is used as a way to probe the actual <subclass>.
@@ -90,6 +91,7 @@
     kForwardPointer,
     kPipeStorage,
     kNamedBarrier,
+    kAccelerationStructureNV,
   };
 
   Type(Kind k) : kind_(k) {}
@@ -170,6 +172,7 @@
   DeclareCastMethod(ForwardPointer);
   DeclareCastMethod(PipeStorage);
   DeclareCastMethod(NamedBarrier);
+  DeclareCastMethod(AccelerationStructureNV);
 #undef DeclareCastMethod
 
   bool operator==(const Type& other) const;
@@ -595,6 +598,7 @@
 DefineParameterlessType(Queue, queue);
 DefineParameterlessType(PipeStorage, pipe_storage);
 DefineParameterlessType(NamedBarrier, named_barrier);
+DefineParameterlessType(AccelerationStructureNV, acceleration_structure);
 #undef DefineParameterlessType
 
 }  // namespace analysis
diff --git a/source/reduce/CMakeLists.txt b/source/reduce/CMakeLists.txt
index fc7369f..57475ac 100644
--- a/source/reduce/CMakeLists.txt
+++ b/source/reduce/CMakeLists.txt
@@ -19,7 +19,10 @@
         reduction_opportunity.h
         reduction_pass.h
         remove_instruction_reduction_opportunity.h
+        remove_opname_instruction_reduction_pass.h
         remove_unreferenced_instruction_reduction_pass.h
+        structured_loop_to_selection_reduction_opportunity.h
+        structured_loop_to_selection_reduction_pass.h
 
         change_operand_reduction_opportunity.cpp
         operand_to_const_reduction_pass.cpp
@@ -29,6 +32,9 @@
         reduction_pass.cpp
         remove_instruction_reduction_opportunity.cpp
         remove_unreferenced_instruction_reduction_pass.cpp
+        remove_opname_instruction_reduction_pass.cpp
+        structured_loop_to_selection_reduction_opportunity.cpp
+        structured_loop_to_selection_reduction_pass.cpp
         )
 
 if(MSVC)
diff --git a/source/reduce/reducer.cpp b/source/reduce/reducer.cpp
index a751b71..4f4429a 100644
--- a/source/reduce/reducer.cpp
+++ b/source/reduce/reducer.cpp
@@ -81,6 +81,11 @@
 
     // Iterate through the available passes
     for (auto& pass : impl_->passes) {
+      // If this pass hasn't reached its minimum granularity then it's
+      // worth eventually doing another round of reductions, in order to
+      // try this pass at a finer granularity.
+      another_round_worthwhile |= !pass->ReachedMinimumGranularity();
+
       // Keep applying this pass at its current granularity until it stops
       // working or we hit the reduction step limit.
       impl_->consumer(SPV_MSG_INFO, nullptr, {},
@@ -89,14 +94,10 @@
         auto maybe_result = pass->TryApplyReduction(current_binary);
         if (maybe_result.empty()) {
           // This pass did not have any impact, so move on to the next pass.
-          // If this pass hasn't reached its minimum granularity then it's
-          // worth eventually doing another round of reductions, in order to
-          // try this pass at a finer granularity.
           impl_->consumer(
               SPV_MSG_INFO, nullptr, {},
               ("Pass " + pass->GetName() + " did not make a reduction step.")
                   .c_str());
-          another_round_worthwhile |= !pass->ReachedMinimumGranularity();
           break;
         }
         std::stringstream stringstream;
@@ -105,7 +106,14 @@
                      << reductions_applied << ".";
         impl_->consumer(SPV_MSG_INFO, nullptr, {},
                         (stringstream.str().c_str()));
-        if (impl_->interestingness_function(maybe_result, reductions_applied)) {
+        if (!spvtools::SpirvTools(impl_->target_env).Validate(maybe_result)) {
+          // The reduction step went wrong and an invalid binary was produced.
+          // By design, this shouldn't happen; this is a safeguard to stop an
+          // invalid binary from being regarded as interesting.
+          impl_->consumer(SPV_MSG_INFO, nullptr, {},
+                          "Reduction step produced an invalid binary.");
+        } else if (impl_->interestingness_function(maybe_result,
+                                                   reductions_applied)) {
           // Success!  The binary produced by this reduction step is
           // interesting, so make it the binary of interest henceforth, and
           // note that it's worth doing another round of reduction passes.
diff --git a/source/reduce/remove_opname_instruction_reduction_pass.cpp b/source/reduce/remove_opname_instruction_reduction_pass.cpp
new file mode 100644
index 0000000..bf99bc5
--- /dev/null
+++ b/source/reduce/remove_opname_instruction_reduction_pass.cpp
@@ -0,0 +1,44 @@
+// Copyright (c) 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "remove_opname_instruction_reduction_pass.h"
+#include "remove_instruction_reduction_opportunity.h"
+#include "source/opcode.h"
+#include "source/opt/instruction.h"
+
+namespace spvtools {
+namespace reduce {
+
+using namespace opt;
+
+std::vector<std::unique_ptr<ReductionOpportunity>>
+RemoveOpNameInstructionReductionPass::GetAvailableOpportunities(
+    opt::IRContext* context) const {
+  std::vector<std::unique_ptr<ReductionOpportunity>> result;
+
+  for (auto& inst : context->module()->debugs2()) {
+    if (inst.opcode() == SpvOpName || inst.opcode() == SpvOpMemberName) {
+      result.push_back(
+          MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
+    }
+  }
+  return result;
+}
+
+std::string RemoveOpNameInstructionReductionPass::GetName() const {
+  return "RemoveOpNameInstructionReductionPass";
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/remove_opname_instruction_reduction_pass.h b/source/reduce/remove_opname_instruction_reduction_pass.h
new file mode 100644
index 0000000..d20b6e1
--- /dev/null
+++ b/source/reduce/remove_opname_instruction_reduction_pass.h
@@ -0,0 +1,50 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_REMOVE_OPNAME_INSTRUCTION_REDUCTION_PASS_H_
+#define SOURCE_REDUCE_REMOVE_OPNAME_INSTRUCTION_REDUCTION_PASS_H_
+
+#include "reduction_pass.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A reduction pass for removing OpName instructions.  As well as making the
+// module smaller, removing an OpName instruction may create opportunities to
+// remove the instruction that create the id to which the OpName applies.
+class RemoveOpNameInstructionReductionPass : public ReductionPass {
+ public:
+  // Creates the reduction pass in the context of the given target environment
+  // |target_env|
+  explicit RemoveOpNameInstructionReductionPass(const spv_target_env target_env)
+      : ReductionPass(target_env) {}
+
+  ~RemoveOpNameInstructionReductionPass() override = default;
+
+  // The name of this pass.
+  std::string GetName() const final;
+
+ protected:
+  // Finds all opportunities for removing opName instructions in the
+  // given module.
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      opt::IRContext* context) const final;
+
+ private:
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_REMOVE_OpName_INSTRUCTION_REDUCTION_PASS_H_
diff --git a/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp b/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp
new file mode 100644
index 0000000..679cfc1
--- /dev/null
+++ b/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp
@@ -0,0 +1,377 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "structured_loop_to_selection_reduction_opportunity.h"
+#include "source/opt/aggressive_dead_code_elim_pass.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace reduce {
+
+namespace {
+const uint32_t kMergeNodeIndex = 0;
+const uint32_t kContinueNodeIndex = 1;
+}  // namespace
+
+bool StructuredLoopToSelectionReductionOpportunity::PreconditionHolds() {
+  // Is the loop header reachable?
+  return loop_construct_header_->GetLabel()
+      ->context()
+      ->GetDominatorAnalysis(enclosing_function_)
+      ->IsReachable(loop_construct_header_);
+}
+
+void StructuredLoopToSelectionReductionOpportunity::Apply() {
+  // Force computation of dominator analysis, CFG and structured CFG analysis
+  // before we start to mess with edges in the function.
+  context_->GetDominatorAnalysis(enclosing_function_);
+  context_->cfg();
+  context_->GetStructuredCFGAnalysis();
+
+  // (1) Redirect edges that point to the loop's continue target to their
+  // closest merge block.
+  RedirectToClosestMergeBlock(
+      loop_construct_header_->GetLoopMergeInst()->GetSingleWordOperand(
+          kContinueNodeIndex));
+
+  // (2) Redirect edges that point to the loop's merge block to their closest
+  // merge block (which might be that of an enclosing selection, for instance).
+  RedirectToClosestMergeBlock(
+      loop_construct_header_->GetLoopMergeInst()->GetSingleWordOperand(
+          kMergeNodeIndex));
+
+  // (3) Turn the loop construct header into a selection.
+  ChangeLoopToSelection();
+
+  // We have made control flow changes that do not preserve the analyses that
+  // were performed.
+  context_->InvalidateAnalysesExceptFor(IRContext::Analysis::kAnalysisNone);
+
+  // (4) By changing CFG edges we may have created scenarios where ids are used
+  // without being dominated; we fix instances of this.
+  FixNonDominatedIdUses();
+
+  // Invalidate the analyses we just used.
+  context_->InvalidateAnalysesExceptFor(IRContext::Analysis::kAnalysisNone);
+}
+
+void StructuredLoopToSelectionReductionOpportunity::RedirectToClosestMergeBlock(
+    uint32_t original_target_id) {
+  // Consider every predecessor of the node with respect to which edges should
+  // be redirected.
+  std::set<uint32_t> already_seen;
+  for (auto pred : context_->cfg()->preds(original_target_id)) {
+    if (already_seen.find(pred) != already_seen.end()) {
+      // We have already handled this predecessor (this scenario can arise if
+      // there are multiple edges from a block b to original_target_id).
+      continue;
+    }
+    already_seen.insert(pred);
+
+    if (!context_->GetDominatorAnalysis(enclosing_function_)
+             ->IsReachable(pred)) {
+      // We do not care about unreachable predecessors (and dominance
+      // information, and thus the notion of structured control flow, makes
+      // little sense for unreachable blocks).
+      continue;
+    }
+    // Find the merge block of the structured control construct that most
+    // tightly encloses the predecessor.
+    uint32_t new_merge_target;
+    // The structured CFG analysis deliberately does not regard a header as
+    // belonging to the structure that it heads. We want it to, so handle this
+    // case specially.
+    if (context_->cfg()->block(pred)->MergeBlockIdIfAny()) {
+      new_merge_target = context_->cfg()->block(pred)->MergeBlockIdIfAny();
+    } else {
+      new_merge_target = context_->GetStructuredCFGAnalysis()->MergeBlock(pred);
+    }
+    assert(new_merge_target != pred);
+
+    if (!new_merge_target) {
+      // If the loop being transformed is outermost, and the predecessor is
+      // part of that loop's continue construct, there will be no such
+      // enclosing control construct.  In this case, the continue construct
+      // will become unreachable anyway, so it is fine not to redirect the
+      // edge.
+      continue;
+    }
+
+    if (new_merge_target != original_target_id) {
+      // Redirect the edge if it doesn't already point to the desired block.
+      RedirectEdge(pred, original_target_id, new_merge_target);
+    }
+  }
+}
+
+void StructuredLoopToSelectionReductionOpportunity::RedirectEdge(
+    uint32_t source_id, uint32_t original_target_id, uint32_t new_target_id) {
+  // Redirect edge source_id->original_target_id to edge
+  // source_id->new_target_id, where the blocks involved are all different.
+  assert(source_id != original_target_id);
+  assert(source_id != new_target_id);
+  assert(original_target_id != new_target_id);
+
+  // original_target_id must either be the merge target or continue construct
+  // for the loop being operated on.
+  assert(original_target_id ==
+             loop_construct_header_->GetMergeInst()->GetSingleWordOperand(
+                 kMergeNodeIndex) ||
+         original_target_id ==
+             loop_construct_header_->GetMergeInst()->GetSingleWordOperand(
+                 kContinueNodeIndex));
+
+  auto terminator = context_->cfg()->block(source_id)->terminator();
+
+  // Figure out which operands of the terminator need to be considered for
+  // redirection.
+  std::vector<uint32_t> operand_indices;
+  if (terminator->opcode() == SpvOpBranch) {
+    operand_indices = {0};
+  } else if (terminator->opcode() == SpvOpBranchConditional) {
+    operand_indices = {1, 2};
+  } else {
+    assert(terminator->opcode() == SpvOpSwitch);
+    for (uint32_t label_index = 1; label_index < terminator->NumOperands();
+         label_index += 2) {
+      operand_indices.push_back(label_index);
+    }
+  }
+
+  // Redirect the relevant operands, asserting that at least one redirection is
+  // made.
+  bool redirected = false;
+  for (auto operand_index : operand_indices) {
+    if (terminator->GetSingleWordOperand(operand_index) == original_target_id) {
+      terminator->SetOperand(operand_index, {new_target_id});
+      redirected = true;
+    }
+  }
+  (void)(redirected);
+  assert(redirected);
+
+  // The old and new targets may have phi instructions; these will need to
+  // respect the change in edges.
+  AdaptPhiInstructionsForRemovedEdge(
+      source_id, context_->cfg()->block(original_target_id));
+  AdaptPhiInstructionsForAddedEdge(source_id,
+                                   context_->cfg()->block(new_target_id));
+}
+
+void StructuredLoopToSelectionReductionOpportunity::
+    AdaptPhiInstructionsForRemovedEdge(uint32_t from_id, BasicBlock* to_block) {
+  to_block->ForEachPhiInst([&from_id](Instruction* phi_inst) {
+    Instruction::OperandList new_in_operands;
+    // Go through the OpPhi's input operands in (variable, parent) pairs.
+    for (uint32_t index = 0; index < phi_inst->NumInOperands(); index += 2) {
+      // Keep all pairs where the parent is not the block from which the edge
+      // is being removed.
+      if (phi_inst->GetInOperand(index + 1).words[0] != from_id) {
+        new_in_operands.push_back(phi_inst->GetInOperand(index));
+        new_in_operands.push_back(phi_inst->GetInOperand(index + 1));
+      }
+    }
+    phi_inst->SetInOperands(std::move(new_in_operands));
+  });
+}
+
+void StructuredLoopToSelectionReductionOpportunity::
+    AdaptPhiInstructionsForAddedEdge(uint32_t from_id, BasicBlock* to_block) {
+  to_block->ForEachPhiInst([this, &from_id](Instruction* phi_inst) {
+    // Add to the phi operand an (undef, from_id) pair to reflect the added
+    // edge.
+    auto undef_id = FindOrCreateGlobalUndef(phi_inst->type_id());
+    phi_inst->AddOperand(Operand(SPV_OPERAND_TYPE_ID, {undef_id}));
+    phi_inst->AddOperand(Operand(SPV_OPERAND_TYPE_ID, {from_id}));
+  });
+}
+
+void StructuredLoopToSelectionReductionOpportunity::ChangeLoopToSelection() {
+  // Change the merge instruction from OpLoopMerge to OpSelectionMerge, with
+  // the same merge block.
+  auto loop_merge_inst = loop_construct_header_->GetLoopMergeInst();
+  auto const loop_merge_block_id =
+      loop_merge_inst->GetSingleWordOperand(kMergeNodeIndex);
+  loop_merge_inst->SetOpcode(SpvOpSelectionMerge);
+  loop_merge_inst->ReplaceOperands(
+      {{loop_merge_inst->GetOperand(kMergeNodeIndex).type,
+        {loop_merge_block_id}},
+       {SPV_OPERAND_TYPE_SELECTION_CONTROL, {SpvSelectionControlMaskNone}}});
+
+  // The loop header either finishes with OpBranch or OpBranchConditional.
+  // The latter is fine for a selection.  In the former case we need to turn
+  // it into OpBranchConditional.  We use "true" as the condition, and make
+  // the "else" branch be the merge block.
+  auto terminator = loop_construct_header_->terminator();
+  if (terminator->opcode() == SpvOpBranch) {
+    analysis::Bool temp;
+    const analysis::Bool* bool_type =
+        context_->get_type_mgr()->GetRegisteredType(&temp)->AsBool();
+    auto const_mgr = context_->get_constant_mgr();
+    auto true_const = const_mgr->GetConstant(bool_type, {true});
+    auto true_const_result_id =
+        const_mgr->GetDefiningInstruction(true_const)->result_id();
+    auto original_branch_id = terminator->GetSingleWordOperand(0);
+    terminator->SetOpcode(SpvOpBranchConditional);
+    terminator->ReplaceOperands({{SPV_OPERAND_TYPE_ID, {true_const_result_id}},
+                                 {SPV_OPERAND_TYPE_ID, {original_branch_id}},
+                                 {SPV_OPERAND_TYPE_ID, {loop_merge_block_id}}});
+    if (original_branch_id != loop_merge_block_id) {
+      AdaptPhiInstructionsForAddedEdge(
+          loop_construct_header_->id(),
+          context_->cfg()->block(loop_merge_block_id));
+    }
+  }
+}
+
+void StructuredLoopToSelectionReductionOpportunity::FixNonDominatedIdUses() {
+  // Consider each instruction in the function.
+  for (auto& block : *enclosing_function_) {
+    for (auto& def : block) {
+      if (def.opcode() == SpvOpVariable) {
+        // Variables are defined at the start of the function, and can be
+        // accessed by all blocks, even by unreachable blocks that have no
+        // dominators, so we do not need to worry about them.
+        continue;
+      }
+      context_->get_def_use_mgr()->ForEachUse(&def, [this, &block, &def](
+                                                        Instruction* use,
+                                                        uint32_t index) {
+        // If a use is not appropriately dominated by its definition,
+        // replace the use with an OpUndef, unless the definition is an
+        // access chain, in which case replace it with some (possibly fresh)
+        // variable (as we cannot load from / store to OpUndef).
+        if (!DefinitionSufficientlyDominatesUse(&def, use, index, block)) {
+          if (def.opcode() == SpvOpAccessChain) {
+            auto pointer_type =
+                context_->get_type_mgr()->GetType(def.type_id())->AsPointer();
+            switch (pointer_type->storage_class()) {
+              case SpvStorageClassFunction:
+                use->SetOperand(
+                    index, {FindOrCreateFunctionVariable(
+                               context_->get_type_mgr()->GetId(pointer_type))});
+                break;
+              default:
+                // TODO(2183) Need to think carefully about whether it makes
+                // sense to add new variables for all storage classes; it's fine
+                // for Private but might not be OK for input/output storage
+                // classes for example.
+                use->SetOperand(
+                    index, {FindOrCreateGlobalVariable(
+                               context_->get_type_mgr()->GetId(pointer_type))});
+                break;
+            }
+          } else {
+            use->SetOperand(index, {FindOrCreateGlobalUndef(def.type_id())});
+          }
+        }
+      });
+    }
+  }
+}
+
+bool StructuredLoopToSelectionReductionOpportunity::
+    DefinitionSufficientlyDominatesUse(Instruction* def, Instruction* use,
+                                       uint32_t use_index,
+                                       BasicBlock& def_block) {
+  if (use->opcode() == SpvOpPhi) {
+    // A use in a phi doesn't need to be dominated by its definition, but the
+    // associated parent block does need to be dominated by the definition.
+    return context_->GetDominatorAnalysis(enclosing_function_)
+        ->Dominates(def_block.id(), use->GetSingleWordOperand(use_index + 1));
+  }
+  // In non-phi cases, a use needs to be dominated by its definition.
+  return context_->GetDominatorAnalysis(enclosing_function_)
+      ->Dominates(def, use);
+}
+
+uint32_t StructuredLoopToSelectionReductionOpportunity::FindOrCreateGlobalUndef(
+    uint32_t type_id) {
+  for (auto& inst : context_->module()->types_values()) {
+    if (inst.opcode() != SpvOpUndef) {
+      continue;
+    }
+    if (inst.type_id() == type_id) {
+      return inst.result_id();
+    }
+  }
+  // TODO(2182): this is adapted from MemPass::Type2Undef.  In due course it
+  // would be good to factor out this duplication.
+  const uint32_t undef_id = context_->TakeNextId();
+  std::unique_ptr<Instruction> undef_inst(
+      new Instruction(context_, SpvOpUndef, type_id, undef_id, {}));
+  assert(undef_id == undef_inst->result_id());
+  context_->module()->AddGlobalValue(std::move(undef_inst));
+  return undef_id;
+}
+
+uint32_t
+StructuredLoopToSelectionReductionOpportunity::FindOrCreateGlobalVariable(
+    uint32_t pointer_type_id) {
+  for (auto& inst : context_->module()->types_values()) {
+    if (inst.opcode() != SpvOpVariable) {
+      continue;
+    }
+    if (inst.type_id() == pointer_type_id) {
+      return inst.result_id();
+    }
+  }
+  const uint32_t variable_id = context_->TakeNextId();
+  std::unique_ptr<Instruction> variable_inst(
+      new Instruction(context_, SpvOpVariable, pointer_type_id, variable_id,
+                      {{SPV_OPERAND_TYPE_STORAGE_CLASS,
+                        {(uint32_t)context_->get_type_mgr()
+                             ->GetType(pointer_type_id)
+                             ->AsPointer()
+                             ->storage_class()}}}));
+  context_->module()->AddGlobalValue(std::move(variable_inst));
+  return variable_id;
+}
+
+uint32_t
+StructuredLoopToSelectionReductionOpportunity::FindOrCreateFunctionVariable(
+    uint32_t pointer_type_id) {
+  // The pointer type of a function variable must have Function storage class.
+  assert(context_->get_type_mgr()
+             ->GetType(pointer_type_id)
+             ->AsPointer()
+             ->storage_class() == SpvStorageClassFunction);
+
+  // Go through the instructions in the function's first block until we find a
+  // suitable variable, or go past all the variables.
+  BasicBlock::iterator iter = enclosing_function_->begin()->begin();
+  for (;; ++iter) {
+    // We will either find a suitable variable, or find a non-variable
+    // instruction; we won't exhaust all instructions.
+    assert(iter != enclosing_function_->begin()->end());
+    if (iter->opcode() != SpvOpVariable) {
+      // If we see a non-variable, we have gone through all the variables.
+      break;
+    }
+    if (iter->type_id() == pointer_type_id) {
+      return iter->result_id();
+    }
+  }
+  // At this point, iter refers to the first non-function instruction of the
+  // function's entry block.
+  const uint32_t variable_id = context_->TakeNextId();
+  std::unique_ptr<Instruction> variable_inst(new Instruction(
+      context_, SpvOpVariable, pointer_type_id, variable_id,
+      {{SPV_OPERAND_TYPE_STORAGE_CLASS, {SpvStorageClassFunction}}}));
+  iter->InsertBefore(std::move(variable_inst));
+  return variable_id;
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/structured_loop_to_selection_reduction_opportunity.h b/source/reduce/structured_loop_to_selection_reduction_opportunity.h
new file mode 100644
index 0000000..b139016
--- /dev/null
+++ b/source/reduce/structured_loop_to_selection_reduction_opportunity.h
@@ -0,0 +1,125 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_CUT_LOOP_REDUCTION_OPPORTUNITY_H_
+#define SOURCE_REDUCE_CUT_LOOP_REDUCTION_OPPORTUNITY_H_
+
+#include <source/opt/def_use_manager.h>
+#include "reduction_opportunity.h"
+#include "source/opt/dominator_analysis.h"
+#include "source/opt/function.h"
+
+namespace spvtools {
+namespace reduce {
+
+using namespace opt;
+
+// Captures an opportunity to replace a structured loop with a selection.
+class StructuredLoopToSelectionReductionOpportunity
+    : public ReductionOpportunity {
+ public:
+  // Constructs an opportunity from a loop header block and the function that
+  // encloses it.
+  explicit StructuredLoopToSelectionReductionOpportunity(
+      IRContext* context, BasicBlock* loop_construct_header,
+      Function* enclosing_function)
+      : context_(context),
+        loop_construct_header_(loop_construct_header),
+        enclosing_function_(enclosing_function) {}
+
+  // We require the loop header to be reachable.  A structured loop might
+  // become unreachable as a result of turning another structured loop into
+  // a selection.
+  bool PreconditionHolds() override;
+
+ protected:
+  // Perform the structured loop to selection transformation.
+  void Apply() override;
+
+ private:
+  // Parameter |original_target_id| is the id of the loop's merge block or
+  // continue target.  This method considers each edge of the form
+  // b->original_target_id and transforms it into an edge of the form b->c,
+  // where c is the merge block of the structured control flow construct that
+  // most tightly contains b.
+  void RedirectToClosestMergeBlock(uint32_t original_target_id);
+
+  // |source_id|, |original_target_id| and |new_target_id| are required to all
+  // be distinct, with a CFG edge existing from |source_id| to
+  // |original_target_id|, and |original_target_id| being either the merge block
+  // or continue target for the loop being operated on.
+  // The method removes this edge and adds an edge from
+  // |source_id| to |new_target_id|.  It takes care of fixing up any OpPhi
+  // instructions associated with |original_target_id| and |new_target_id|.
+  void RedirectEdge(uint32_t source_id, uint32_t original_target_id,
+                    uint32_t new_target_id);
+
+  // Removes any components of |to_block|'s phi instructions relating to
+  // |from_id|.
+  void AdaptPhiInstructionsForRemovedEdge(uint32_t from_id,
+                                          BasicBlock* to_block);
+
+  // Adds components to |to_block|'s phi instructions to account for a new
+  // incoming edge from |from_id|.
+  void AdaptPhiInstructionsForAddedEdge(uint32_t from_id, BasicBlock* to_block);
+
+  // Turns the OpLoopMerge for the loop into OpSelectionMerge, and adapts the
+  // following branch instruction accordingly.
+  void ChangeLoopToSelection();
+
+  // Fixes any scenarios where, due to CFG changes, ids have uses not dominated
+  // by their definitions, by changing such uses to uses of OpUndef or of dummy
+  // variables.
+  void FixNonDominatedIdUses();
+
+  // Returns true if and only if at least one of the following holds:
+  // 1) |def| dominates |use|
+  // 2) |def| is an OpVariable
+  // 3) |use| is part of an OpPhi, with associated incoming block b, and |def|
+  // dominates b.
+  bool DefinitionSufficientlyDominatesUse(Instruction* def, Instruction* use,
+                                          uint32_t use_index,
+                                          BasicBlock& def_block);
+
+  // Checks whether the global value list has an OpUndef of the given type,
+  // adding one if not, and returns the id of such an OpUndef.
+  //
+  // TODO(2184): This will likely be used by other reduction passes, so should
+  // be factored out in due course.  Parts of the spirv-opt framework provide
+  // similar functionality, so there may be a case for further refactoring.
+  uint32_t FindOrCreateGlobalUndef(uint32_t type_id);
+
+  // Checks whether the global value list has an OpVariable of the given pointer
+  // type, adding one if not, and returns the id of such an OpVariable.
+  //
+  // TODO(2184): This will likely be used by other reduction passes, so should
+  // be factored out in due course.
+  uint32_t FindOrCreateGlobalVariable(uint32_t pointer_type_id);
+
+  // Checks whether the enclosing function has an OpVariable of the given
+  // pointer type, adding one if not, and returns the id of such an OpVariable.
+  //
+  // TODO(2184): This will likely be used by other reduction passes, so should
+  // be factored out in due course.
+  uint32_t FindOrCreateFunctionVariable(uint32_t pointer_type_id);
+
+  IRContext* context_;
+  BasicBlock* loop_construct_header_;
+  Function* enclosing_function_;
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_CUT_LOOP_REDUCTION_OPPORTUNITY_H_
diff --git a/source/reduce/structured_loop_to_selection_reduction_pass.cpp b/source/reduce/structured_loop_to_selection_reduction_pass.cpp
new file mode 100644
index 0000000..768a2e8
--- /dev/null
+++ b/source/reduce/structured_loop_to_selection_reduction_pass.cpp
@@ -0,0 +1,95 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "structured_loop_to_selection_reduction_pass.h"
+#include "structured_loop_to_selection_reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+using namespace opt;
+
+namespace {
+const uint32_t kMergeNodeIndex = 0;
+const uint32_t kContinueNodeIndex = 1;
+}  // namespace
+
+std::vector<std::unique_ptr<ReductionOpportunity>>
+StructuredLoopToSelectionReductionPass::GetAvailableOpportunities(
+    opt::IRContext* context) const {
+  std::vector<std::unique_ptr<ReductionOpportunity>> result;
+
+  std::set<uint32_t> merge_block_ids;
+  for (auto& function : *context->module()) {
+    for (auto& block : function) {
+      auto merge_inst = block.GetMergeInst();
+      if (merge_inst) {
+        merge_block_ids.insert(
+            merge_inst->GetSingleWordOperand(kMergeNodeIndex));
+      }
+    }
+  }
+
+  // Consider each loop construct header in the module.
+  for (auto& function : *context->module()) {
+    for (auto& block : function) {
+      auto loop_merge_inst = block.GetLoopMergeInst();
+      if (!loop_merge_inst) {
+        // This is not a loop construct header.
+        continue;
+      }
+
+      // Check whether the loop construct's continue target is the merge block
+      // of some structured control flow construct.  If it is, we cautiously do
+      // not consider applying a transformation.
+      if (merge_block_ids.find(loop_merge_inst->GetSingleWordOperand(
+              kContinueNodeIndex)) != merge_block_ids.end()) {
+        continue;
+      }
+
+      // Check whether the loop construct header dominates its merge block.
+      // If not, the merge block must be unreachable in the control flow graph
+      // so we cautiously do not consider applying a transformation.
+      auto merge_block_id =
+          loop_merge_inst->GetSingleWordInOperand(kMergeNodeIndex);
+      if (!context->GetDominatorAnalysis(&function)->Dominates(
+              block.id(), merge_block_id)) {
+        continue;
+      }
+
+      // Check whether the loop construct merge block postdominates the loop
+      // construct header.  If not (e.g. because the loop contains OpReturn,
+      // OpKill or OpUnreachable), we cautiously do not consider applying
+      // a transformation.
+      if (!context->GetPostDominatorAnalysis(&function)->Dominates(
+              merge_block_id, block.id())) {
+        continue;
+      }
+
+      // We can turn this structured loop into a selection, so add the
+      // opportunity to do so.
+      result.push_back(
+          MakeUnique<StructuredLoopToSelectionReductionOpportunity>(
+              context, &block, &function));
+    }
+  }
+  return result;
+}
+
+std::string StructuredLoopToSelectionReductionPass::GetName() const {
+  return "StructuredLoopToSelectionReductionPass";
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/structured_loop_to_selection_reduction_pass.h b/source/reduce/structured_loop_to_selection_reduction_pass.h
new file mode 100644
index 0000000..9c4d4ca
--- /dev/null
+++ b/source/reduce/structured_loop_to_selection_reduction_pass.h
@@ -0,0 +1,64 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_CUT_LOOP_REDUCTION_PASS_H_
+#define SOURCE_REDUCE_CUT_LOOP_REDUCTION_PASS_H_
+
+#include "reduction_pass.h"
+
+namespace spvtools {
+namespace reduce {
+
+// Turns structured loops into selections, generalizing from a human-writable
+// language the idea of turning a loop:
+//
+// while (c) {
+//   body;
+// }
+//
+// into:
+//
+// if (c) {
+//   body;
+// }
+//
+// The pass results in continue constructs of transformed loops becoming
+// unreachable; another pass for eliminating blocks may end up being able to
+// remove them.
+class StructuredLoopToSelectionReductionPass : public ReductionPass {
+ public:
+  // Creates the reduction pass in the context of the given target environment
+  // |target_env|
+  explicit StructuredLoopToSelectionReductionPass(
+      const spv_target_env target_env)
+      : ReductionPass(target_env) {}
+
+  ~StructuredLoopToSelectionReductionPass() override = default;
+
+  // The name of this pass.
+  std::string GetName() const final;
+
+ protected:
+  // Finds all opportunities for transforming a structured loop to a selection
+  // in the given module.
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      opt::IRContext* context) const final;
+
+ private:
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_CUT_LOOP_REDUCTION_PASS_H_
diff --git a/source/text_handler.cpp b/source/text_handler.cpp
index 5f6e8c4..c31f34a 100644
--- a/source/text_handler.cpp
+++ b/source/text_handler.cpp
@@ -313,7 +313,7 @@
   pInst->words.back() = 0;
 
   char* dest = (char*)&pInst->words[oldWordCount];
-  strncpy(dest, value, length);
+  strncpy(dest, value, length + 1);
 
   return SPV_SUCCESS;
 }
diff --git a/source/val/validate.cpp b/source/val/validate.cpp
index a88ea92..5d0c624 100644
--- a/source/val/validate.cpp
+++ b/source/val/validate.cpp
@@ -116,18 +116,18 @@
     block_string += "end ";
   } else {
     for (auto block : *other.successors()) {
-      block_string += _.getIdOrName(block->id()) + " ";
+      block_string += _.getIdName(block->id()) + " ";
     }
   }
-  printf("%10s -> {%s\b}\n", _.getIdOrName(other.id()).c_str(),
+  printf("%10s -> {%s\b}\n", _.getIdName(other.id()).c_str(),
          block_string.c_str());
 }
 
 void PrintBlocks(ValidationState_t& _, Function func) {
   assert(func.first_block());
 
-  printf("%10s -> %s\n", _.getIdOrName(func.id()).c_str(),
-         _.getIdOrName(func.first_block()->id()).c_str());
+  printf("%10s -> %s\n", _.getIdName(func.id()).c_str(),
+         _.getIdName(func.first_block()->id()).c_str());
   for (const auto& block : func.ordered_blocks()) {
     printDot(_, *block);
   }
@@ -145,7 +145,7 @@
 
 UNUSED(void PrintDotGraph(ValidationState_t& _, Function func)) {
   if (func.first_block()) {
-    std::string func_name(_.getIdOrName(func.id()));
+    std::string func_name(_.getIdName(func.id()));
     printf("digraph %s {\n", func_name.c_str());
     PrintBlocks(_, func);
     printf("}\n");
@@ -175,14 +175,19 @@
 //   capability is being used.
 // * No function can be targeted by both an OpEntryPoint instruction and an
 //   OpFunctionCall instruction.
+//
+// Additionally enforces that entry points for Vulkan and WebGPU should not have
+// recursion.
 spv_result_t ValidateEntryPoints(ValidationState_t& _) {
   _.ComputeFunctionToEntryPointMapping();
+  _.ComputeRecursiveEntryPoints();
 
   if (_.entry_points().empty() && !_.HasCapability(SpvCapabilityLinkage)) {
     return _.diag(SPV_ERROR_INVALID_BINARY, nullptr)
            << "No OpEntryPoint instruction was found. This is only allowed if "
               "the Linkage capability is being used.";
   }
+
   for (const auto& entry_point : _.entry_points()) {
     if (_.IsFunctionCallTarget(entry_point)) {
       return _.diag(SPV_ERROR_INVALID_BINARY, _.FindDef(entry_point))
@@ -190,6 +195,17 @@
              << ") may not be targeted by both an OpEntryPoint instruction and "
                 "an OpFunctionCall instruction.";
     }
+
+    // For Vulkan and WebGPU, the static function-call graph for an entry point
+    // must not contain cycles.
+    if (spvIsWebGPUEnv(_.context()->target_env) ||
+        spvIsVulkanEnv(_.context()->target_env)) {
+      if (_.recursive_entry_points().find(entry_point) !=
+          _.recursive_entry_points().end()) {
+        return _.diag(SPV_ERROR_INVALID_BINARY, _.FindDef(entry_point))
+               << "Entry points may not have a call graph with cycles.";
+      }
+    }
   }
 
   return SPV_SUCCESS;
@@ -279,7 +295,15 @@
                  << "A FunctionCall must happen within a function body.";
         }
 
-        vstate->AddFunctionCallTarget(inst->GetOperandAs<uint32_t>(2));
+        const auto called_id = inst->GetOperandAs<uint32_t>(2);
+        if (spvIsWebGPUEnv(context.target_env) &&
+            !vstate->IsFunctionCallDefined(called_id)) {
+          return vstate->diag(SPV_ERROR_INVALID_LAYOUT, &instruction)
+                 << "For WebGPU, functions need to be defined before being "
+                    "called.";
+        }
+
+        vstate->AddFunctionCallTarget(called_id);
       }
 
       if (vstate->in_function_body()) {
diff --git a/source/val/validate.h b/source/val/validate.h
index 6418839..fe357a2 100644
--- a/source/val/validate.h
+++ b/source/val/validate.h
@@ -131,7 +131,8 @@
 /// Performs instruction validation.
 spv_result_t InstructionPass(ValidationState_t& _, const Instruction* inst);
 
-/// Performs decoration validation.
+/// Performs decoration validation.  Assumes each decoration on a group
+/// has been propagated down to the group members.
 spv_result_t ValidateDecorations(ValidationState_t& _);
 
 /// Performs validation of built-in variables.
diff --git a/source/val/validate_atomics.cpp b/source/val/validate_atomics.cpp
index ecc74d6..6bfd06d 100644
--- a/source/val/validate_atomics.cpp
+++ b/source/val/validate_atomics.cpp
@@ -21,175 +21,13 @@
 #include "source/spirv_target_env.h"
 #include "source/util/bitutils.h"
 #include "source/val/instruction.h"
+#include "source/val/validate_memory_semantics.h"
 #include "source/val/validate_scopes.h"
 #include "source/val/validation_state.h"
 
 namespace spvtools {
 namespace val {
 
-// Validates a Memory Semantics operand.
-spv_result_t ValidateMemorySemantics(ValidationState_t& _,
-                                     const Instruction* inst,
-                                     uint32_t operand_index) {
-  const SpvOp opcode = inst->opcode();
-  bool is_int32 = false, is_const_int32 = false;
-  uint32_t flags = 0;
-  auto memory_semantics_id = inst->GetOperandAs<const uint32_t>(operand_index);
-  std::tie(is_int32, is_const_int32, flags) =
-      _.EvalInt32IfConst(memory_semantics_id);
-
-  if (!is_int32) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << spvOpcodeString(opcode)
-           << ": expected Memory Semantics to be 32-bit int";
-  }
-
-  if (!is_const_int32) {
-    if (_.HasCapability(SpvCapabilityShader)) {
-      return _.diag(SPV_ERROR_INVALID_DATA, inst)
-             << "Memory Semantics ids must be OpConstant when Shader "
-                "capability is present";
-    }
-    return SPV_SUCCESS;
-  }
-
-  if (_.memory_model() == SpvMemoryModelVulkanKHR &&
-      flags & SpvMemorySemanticsSequentiallyConsistentMask) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << "SequentiallyConsistent memory "
-              "semantics cannot be used with "
-              "the VulkanKHR memory model.";
-  }
-
-  if (spvtools::utils::CountSetBits(
-          flags &
-          (SpvMemorySemanticsAcquireMask | SpvMemorySemanticsReleaseMask |
-           SpvMemorySemanticsAcquireReleaseMask |
-           SpvMemorySemanticsSequentiallyConsistentMask)) > 1) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << spvOpcodeString(opcode)
-           << ": no more than one of the following Memory Semantics bits can "
-              "be set at the same time: Acquire, Release, AcquireRelease or "
-              "SequentiallyConsistent";
-  }
-
-  if (flags & SpvMemorySemanticsUniformMemoryMask &&
-      !_.HasCapability(SpvCapabilityShader)) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << spvOpcodeString(opcode)
-           << ": Memory Semantics UniformMemory requires capability Shader";
-  }
-
-  if (flags & SpvMemorySemanticsAtomicCounterMemoryMask &&
-      !_.HasCapability(SpvCapabilityAtomicStorage)) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << spvOpcodeString(opcode)
-           << ": Memory Semantics UniformMemory requires capability "
-              "AtomicStorage";
-  }
-
-  if (flags & SpvMemorySemanticsOutputMemoryKHRMask &&
-      !_.HasCapability(SpvCapabilityVulkanMemoryModelKHR)) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << spvOpcodeString(opcode)
-           << ": Memory Semantics OutputMemoryKHR requires capability "
-           << "VulkanMemoryModelKHR";
-  }
-
-  if (flags & SpvMemorySemanticsMakeAvailableKHRMask &&
-      !_.HasCapability(SpvCapabilityVulkanMemoryModelKHR)) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << spvOpcodeString(opcode)
-           << ": Memory Semantics MakeAvailableKHR requires capability "
-           << "VulkanMemoryModelKHR";
-  }
-
-  if (flags & SpvMemorySemanticsMakeVisibleKHRMask &&
-      !_.HasCapability(SpvCapabilityVulkanMemoryModelKHR)) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << spvOpcodeString(opcode)
-           << ": Memory Semantics MakeVisibleKHR requires capability "
-           << "VulkanMemoryModelKHR";
-  }
-
-  if (opcode == SpvOpAtomicFlagClear &&
-      (flags & SpvMemorySemanticsAcquireMask ||
-       flags & SpvMemorySemanticsAcquireReleaseMask)) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << "Memory Semantics Acquire and AcquireRelease cannot be used with "
-           << spvOpcodeString(opcode);
-  }
-
-  if (opcode == SpvOpAtomicCompareExchange && operand_index == 5 &&
-      (flags & SpvMemorySemanticsReleaseMask ||
-       flags & SpvMemorySemanticsAcquireReleaseMask)) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << spvOpcodeString(opcode)
-           << ": Memory Semantics Release and AcquireRelease cannot be used "
-              "for operand Unequal";
-  }
-
-  if (spvIsVulkanEnv(_.context()->target_env)) {
-    if (opcode == SpvOpAtomicLoad &&
-        (flags & SpvMemorySemanticsReleaseMask ||
-         flags & SpvMemorySemanticsAcquireReleaseMask ||
-         flags & SpvMemorySemanticsSequentiallyConsistentMask)) {
-      return _.diag(SPV_ERROR_INVALID_DATA, inst)
-             << "Vulkan spec disallows OpAtomicLoad with Memory Semantics "
-                "Release, AcquireRelease and SequentiallyConsistent";
-    }
-
-    if (opcode == SpvOpAtomicStore &&
-        (flags & SpvMemorySemanticsAcquireMask ||
-         flags & SpvMemorySemanticsAcquireReleaseMask ||
-         flags & SpvMemorySemanticsSequentiallyConsistentMask)) {
-      return _.diag(SPV_ERROR_INVALID_DATA, inst)
-             << "Vulkan spec disallows OpAtomicStore with Memory Semantics "
-                "Acquire, AcquireRelease and SequentiallyConsistent";
-    }
-  }
-
-  if (flags & SpvMemorySemanticsMakeAvailableKHRMask &&
-      !(flags & (SpvMemorySemanticsReleaseMask |
-                 SpvMemorySemanticsAcquireReleaseMask))) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << spvOpcodeString(opcode)
-           << ": MakeAvailableKHR Memory Semantics also requires either "
-              "Release or AcquireRelease Memory Semantics";
-  }
-
-  if (flags & SpvMemorySemanticsMakeVisibleKHRMask &&
-      !(flags & (SpvMemorySemanticsAcquireMask |
-                 SpvMemorySemanticsAcquireReleaseMask))) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << spvOpcodeString(opcode)
-           << ": MakeVisibleKHR Memory Semantics also requires either Acquire "
-              "or AcquireRelease Memory Semantics";
-  }
-
-  if (flags & (SpvMemorySemanticsMakeAvailableKHRMask |
-               SpvMemorySemanticsMakeVisibleKHRMask)) {
-    const bool includes_storage_class =
-        flags & (SpvMemorySemanticsUniformMemoryMask |
-                 SpvMemorySemanticsSubgroupMemoryMask |
-                 SpvMemorySemanticsWorkgroupMemoryMask |
-                 SpvMemorySemanticsCrossWorkgroupMemoryMask |
-                 SpvMemorySemanticsAtomicCounterMemoryMask |
-                 SpvMemorySemanticsImageMemoryMask |
-                 SpvMemorySemanticsOutputMemoryKHRMask);
-
-    if (!includes_storage_class) {
-      return _.diag(SPV_ERROR_INVALID_DATA, inst)
-             << spvOpcodeString(opcode)
-             << ": expected Memory Semantics to include a storage class";
-    }
-  }
-
-  // TODO(atgoo@github.com) Add checks for OpenCL and OpenGL environments.
-
-  return SPV_SUCCESS;
-}
-
 // Validates correctness of atomic instructions.
 spv_result_t AtomicsPass(ValidationState_t& _, const Instruction* inst) {
   const SpvOp opcode = inst->opcode();
diff --git a/source/val/validate_barriers.cpp b/source/val/validate_barriers.cpp
index 7948325..4fbe9c9 100644
--- a/source/val/validate_barriers.cpp
+++ b/source/val/validate_barriers.cpp
@@ -24,155 +24,12 @@
 #include "source/spirv_target_env.h"
 #include "source/util/bitutils.h"
 #include "source/val/instruction.h"
+#include "source/val/validate_memory_semantics.h"
 #include "source/val/validate_scopes.h"
 #include "source/val/validation_state.h"
 
 namespace spvtools {
 namespace val {
-namespace {
-
-// Validates Memory Semantics operand.
-spv_result_t ValidateMemorySemantics(ValidationState_t& _,
-                                     const Instruction* inst, uint32_t id) {
-  const SpvOp opcode = inst->opcode();
-  bool is_int32 = false, is_const_int32 = false;
-  uint32_t value = 0;
-  std::tie(is_int32, is_const_int32, value) = _.EvalInt32IfConst(id);
-
-  if (!is_int32) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << spvOpcodeString(opcode)
-           << ": expected Memory Semantics to be a 32-bit int";
-  }
-
-  if (!is_const_int32) {
-    if (_.HasCapability(SpvCapabilityShader)) {
-      return _.diag(SPV_ERROR_INVALID_DATA, inst)
-             << "Memory Semantics ids must be OpConstant when Shader "
-                "capability is present";
-    }
-    return SPV_SUCCESS;
-  }
-
-  if (_.memory_model() == SpvMemoryModelVulkanKHR &&
-      value & SpvMemorySemanticsSequentiallyConsistentMask) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << "SequentiallyConsistent memory "
-              "semantics cannot be used with "
-              "the VulkanKHR memory model.";
-  }
-
-  if (value & SpvMemorySemanticsOutputMemoryKHRMask &&
-      !_.HasCapability(SpvCapabilityVulkanMemoryModelKHR)) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << spvOpcodeString(opcode)
-           << ": Memory Semantics OutputMemoryKHR requires capability "
-           << "VulkanMemoryModelKHR";
-  }
-
-  if (value & SpvMemorySemanticsMakeAvailableKHRMask &&
-      !_.HasCapability(SpvCapabilityVulkanMemoryModelKHR)) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << spvOpcodeString(opcode)
-           << ": Memory Semantics MakeAvailableKHR requires capability "
-           << "VulkanMemoryModelKHR";
-  }
-
-  if (value & SpvMemorySemanticsMakeVisibleKHRMask &&
-      !_.HasCapability(SpvCapabilityVulkanMemoryModelKHR)) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << spvOpcodeString(opcode)
-           << ": Memory Semantics MakeVisibleKHR requires capability "
-           << "VulkanMemoryModelKHR";
-  }
-
-  const size_t num_memory_order_set_bits = spvtools::utils::CountSetBits(
-      value & (SpvMemorySemanticsAcquireMask | SpvMemorySemanticsReleaseMask |
-               SpvMemorySemanticsAcquireReleaseMask |
-               SpvMemorySemanticsSequentiallyConsistentMask));
-
-  if (num_memory_order_set_bits > 1) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << spvOpcodeString(opcode)
-           << ": Memory Semantics can have at most one of the following bits "
-              "set: Acquire, Release, AcquireRelease or SequentiallyConsistent";
-  }
-
-  if (value & SpvMemorySemanticsMakeAvailableKHRMask &&
-      !(value & (SpvMemorySemanticsReleaseMask |
-                 SpvMemorySemanticsAcquireReleaseMask))) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << spvOpcodeString(opcode)
-           << ": MakeAvailableKHR Memory Semantics also requires either "
-              "Release or AcquireRelease Memory Semantics";
-  }
-
-  if (value & SpvMemorySemanticsMakeVisibleKHRMask &&
-      !(value & (SpvMemorySemanticsAcquireMask |
-                 SpvMemorySemanticsAcquireReleaseMask))) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << spvOpcodeString(opcode)
-           << ": MakeVisibleKHR Memory Semantics also requires either Acquire "
-              "or AcquireRelease Memory Semantics";
-  }
-
-  if (spvIsVulkanEnv(_.context()->target_env)) {
-    const bool includes_storage_class =
-        value & (SpvMemorySemanticsUniformMemoryMask |
-                 SpvMemorySemanticsWorkgroupMemoryMask |
-                 SpvMemorySemanticsImageMemoryMask |
-                 SpvMemorySemanticsOutputMemoryKHRMask);
-
-    if (opcode == SpvOpMemoryBarrier && !num_memory_order_set_bits) {
-      return _.diag(SPV_ERROR_INVALID_DATA, inst)
-             << spvOpcodeString(opcode)
-             << ": Vulkan specification requires Memory Semantics to have one "
-                "of the following bits set: Acquire, Release, AcquireRelease "
-                "or SequentiallyConsistent";
-    }
-
-    if (opcode == SpvOpMemoryBarrier && !includes_storage_class) {
-      return _.diag(SPV_ERROR_INVALID_DATA, inst)
-             << spvOpcodeString(opcode)
-             << ": expected Memory Semantics to include a Vulkan-supported "
-                "storage class";
-    }
-
-#if 0
-    // TODO(atgoo@github.com): this check fails Vulkan CTS, reenable once fixed.
-    if (opcode == SpvOpControlBarrier && value && !includes_storage_class) {
-      return _.diag(SPV_ERROR_INVALID_DATA, inst)
-             << spvOpcodeString(opcode)
-             << ": expected Memory Semantics to include a Vulkan-supported "
-                "storage class if Memory Semantics is not None";
-    }
-#endif
-  }
-
-  if (value & (SpvMemorySemanticsMakeAvailableKHRMask |
-               SpvMemorySemanticsMakeVisibleKHRMask)) {
-    const bool includes_storage_class =
-        value & (SpvMemorySemanticsUniformMemoryMask |
-                 SpvMemorySemanticsSubgroupMemoryMask |
-                 SpvMemorySemanticsWorkgroupMemoryMask |
-                 SpvMemorySemanticsCrossWorkgroupMemoryMask |
-                 SpvMemorySemanticsAtomicCounterMemoryMask |
-                 SpvMemorySemanticsImageMemoryMask |
-                 SpvMemorySemanticsOutputMemoryKHRMask);
-
-    if (!includes_storage_class) {
-      return _.diag(SPV_ERROR_INVALID_DATA, inst)
-             << spvOpcodeString(opcode)
-             << ": expected Memory Semantics to include a storage class";
-    }
-  }
-
-  // TODO(atgoo@github.com) Add checks for OpenCL and OpenGL environments.
-
-  return SPV_SUCCESS;
-}
-
-}  // namespace
 
 // Validates correctness of barrier instructions.
 spv_result_t BarriersPass(ValidationState_t& _, const Instruction* inst) {
@@ -205,7 +62,6 @@
 
       const uint32_t execution_scope = inst->word(1);
       const uint32_t memory_scope = inst->word(2);
-      const uint32_t memory_semantics = inst->word(3);
 
       if (auto error = ValidateExecutionScope(_, inst, execution_scope)) {
         return error;
@@ -215,7 +71,7 @@
         return error;
       }
 
-      if (auto error = ValidateMemorySemantics(_, inst, memory_semantics)) {
+      if (auto error = ValidateMemorySemantics(_, inst, 2)) {
         return error;
       }
       break;
@@ -223,13 +79,12 @@
 
     case SpvOpMemoryBarrier: {
       const uint32_t memory_scope = inst->word(1);
-      const uint32_t memory_semantics = inst->word(2);
 
       if (auto error = ValidateMemoryScope(_, inst, memory_scope)) {
         return error;
       }
 
-      if (auto error = ValidateMemorySemantics(_, inst, memory_semantics)) {
+      if (auto error = ValidateMemorySemantics(_, inst, 1)) {
         return error;
       }
       break;
@@ -261,13 +116,12 @@
       }
 
       const uint32_t memory_scope = inst->word(2);
-      const uint32_t memory_semantics = inst->word(3);
 
       if (auto error = ValidateMemoryScope(_, inst, memory_scope)) {
         return error;
       }
 
-      if (auto error = ValidateMemorySemantics(_, inst, memory_semantics)) {
+      if (auto error = ValidateMemorySemantics(_, inst, 2)) {
         return error;
       }
       break;
diff --git a/source/val/validate_decorations.cpp b/source/val/validate_decorations.cpp
index 96dd33d..582d11c 100644
--- a/source/val/validate_decorations.cpp
+++ b/source/val/validate_decorations.cpp
@@ -17,7 +17,9 @@
 #include <algorithm>
 #include <cassert>
 #include <string>
+#include <tuple>
 #include <unordered_map>
+#include <unordered_set>
 #include <utility>
 #include <vector>
 
@@ -44,6 +46,13 @@
   }
 };
 
+// A functor for hashing decoration types.
+struct SpvDecorationHash {
+  std::size_t operator()(SpvDecoration dec) const {
+    return static_cast<std::size_t>(dec);
+  }
+};
+
 // Struct member layout attributes that are inherited through arrays.
 struct LayoutConstraints {
   explicit LayoutConstraints(
@@ -520,15 +529,18 @@
   return SPV_SUCCESS;
 }
 
-// Returns true if structure id has given decoration. Handles also nested
-// structures.
-bool hasDecoration(uint32_t struct_id, SpvDecoration decoration,
+// Returns true if variable or structure id has given decoration. Handles also
+// nested structures.
+bool hasDecoration(uint32_t id, SpvDecoration decoration,
                    ValidationState_t& vstate) {
-  for (auto& dec : vstate.id_decorations(struct_id)) {
+  for (auto& dec : vstate.id_decorations(id)) {
     if (decoration == dec.dec_type()) return true;
   }
-  for (auto id : getStructMembers(struct_id, SpvOpTypeStruct, vstate)) {
-    if (hasDecoration(id, decoration, vstate)) {
+  if (SpvOpTypeStruct != vstate.FindDef(id)->opcode()) {
+    return false;
+  }
+  for (auto member_id : getStructMembers(id, SpvOpTypeStruct, vstate)) {
+    if (hasDecoration(member_id, decoration, vstate)) {
       return true;
     }
   }
@@ -778,15 +790,63 @@
 }
 
 spv_result_t CheckDecorationsOfBuffers(ValidationState_t& vstate) {
+  // Set of entry points that are known to use a push constant.
+  std::unordered_set<uint32_t> uses_push_constant;
   for (const auto& inst : vstate.ordered_instructions()) {
     const auto& words = inst.words();
     if (SpvOpVariable == inst.opcode()) {
+      const auto var_id = inst.id();
       // For storage class / decoration combinations, see Vulkan 14.5.4 "Offset
       // and Stride Assignment".
       const auto storageClass = words[3];
       const bool uniform = storageClass == SpvStorageClassUniform;
+      const bool uniform_constant =
+          storageClass == SpvStorageClassUniformConstant;
       const bool push_constant = storageClass == SpvStorageClassPushConstant;
       const bool storage_buffer = storageClass == SpvStorageClassStorageBuffer;
+
+      if (spvIsVulkanEnv(vstate.context()->target_env)) {
+        // Vulkan 14.5.1: There must be no more than one PushConstant block
+        // per entry point.
+        if (push_constant) {
+          auto entry_points = vstate.EntryPointReferences(var_id);
+          for (auto ep_id : entry_points) {
+            const bool already_used = !uses_push_constant.insert(ep_id).second;
+            if (already_used) {
+              return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(var_id))
+                     << "Entry point id '" << ep_id
+                     << "' uses more than one PushConstant interface.\n"
+                     << "From Vulkan spec, section 14.5.1:\n"
+                     << "There must be no more than one push constant block "
+                     << "statically used per shader entry point.";
+            }
+          }
+        }
+        // Vulkan 14.5.2: Check DescriptorSet and Binding decoration for
+        // UniformConstant which cannot be a struct.
+        if (uniform_constant) {
+          auto entry_points = vstate.EntryPointReferences(var_id);
+          if (!entry_points.empty() &&
+              !hasDecoration(var_id, SpvDecorationDescriptorSet, vstate)) {
+            return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(var_id))
+                   << "UniformConstant id '" << var_id
+                   << "' is missing DescriptorSet decoration.\n"
+                   << "From Vulkan spec, section 14.5.2:\n"
+                   << "These variables must have DescriptorSet and Binding "
+                      "decorations specified";
+          }
+          if (!entry_points.empty() &&
+              !hasDecoration(var_id, SpvDecorationBinding, vstate)) {
+            return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(var_id))
+                   << "UniformConstant id '" << var_id
+                   << "' is missing Binding decoration.\n"
+                   << "From Vulkan spec, section 14.5.2:\n"
+                   << "These variables must have DescriptorSet and Binding "
+                      "decorations specified";
+          }
+        }
+      }
+
       if (uniform || push_constant || storage_buffer) {
         const auto ptrInst = vstate.FindDef(words[1]);
         assert(SpvOpTypePointer == ptrInst->opcode());
@@ -795,9 +855,13 @@
         MemberConstraints constraints;
         ComputeMemberConstraintsForStruct(&constraints, id, LayoutConstraints(),
                                           vstate);
+        // Prepare for messages
+        const char* sc_str =
+            uniform ? "Uniform"
+                    : (push_constant ? "PushConstant" : "StorageBuffer");
 
-        // Vulkan 14.5.1: Check Block annotation for PushConstant variables.
         if (spvIsVulkanEnv(vstate.context()->target_env)) {
+          // Vulkan 14.5.1: Check Block decoration for PushConstant variables.
           if (push_constant && !hasDecoration(id, SpvDecorationBlock, vstate)) {
             return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
                    << "PushConstant id '" << id
@@ -806,12 +870,31 @@
                    << "Such variables must be identified with a Block "
                       "decoration";
           }
+          // Vulkan 14.5.2: Check DescriptorSet and Binding decoration for
+          // Uniform and StorageBuffer variables.
+          if (uniform || storage_buffer) {
+            auto entry_points = vstate.EntryPointReferences(var_id);
+            if (!entry_points.empty() &&
+                !hasDecoration(var_id, SpvDecorationDescriptorSet, vstate)) {
+              return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(var_id))
+                     << sc_str << " id '" << var_id
+                     << "' is missing DescriptorSet decoration.\n"
+                     << "From Vulkan spec, section 14.5.2:\n"
+                     << "These variables must have DescriptorSet and Binding "
+                        "decorations specified";
+            }
+            if (!entry_points.empty() &&
+                !hasDecoration(var_id, SpvDecorationBinding, vstate)) {
+              return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(var_id))
+                     << sc_str << " id '" << var_id
+                     << "' is missing Binding decoration.\n"
+                     << "From Vulkan spec, section 14.5.2:\n"
+                     << "These variables must have DescriptorSet and Binding "
+                        "decorations specified";
+            }
+          }
         }
 
-        // Prepare for messages
-        const char* sc_str =
-            uniform ? "Uniform"
-                    : (push_constant ? "PushConstant" : "StorageBuffer");
         for (const auto& dec : vstate.id_decorations(id)) {
           const bool blockDeco = SpvDecorationBlock == dec.dec_type();
           const bool bufferDeco = SpvDecorationBufferBlock == dec.dec_type();
@@ -825,7 +908,8 @@
             if (isMissingOffsetInStruct(id, vstate)) {
               return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
                      << "Structure id " << id << " decorated as " << deco_str
-                     << " must be explicitly laid out with Offset decorations.";
+                     << " must be explicitly laid out with Offset "
+                        "decorations.";
             } else if (hasDecoration(id, SpvDecorationGLSLShared, vstate)) {
               return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
                      << "Structure id " << id << " decorated as " << deco_str
@@ -866,6 +950,109 @@
   return SPV_SUCCESS;
 }
 
+spv_result_t CheckDecorationsCompatibility(ValidationState_t& vstate) {
+  using AtMostOnceSet = std::unordered_set<SpvDecoration, SpvDecorationHash>;
+  using MutuallyExclusiveSets =
+      std::vector<std::unordered_set<SpvDecoration, SpvDecorationHash>>;
+  using PerIDKey = std::tuple<SpvDecoration, uint32_t>;
+  using PerMemberKey = std::tuple<SpvDecoration, uint32_t, uint32_t>;
+  using DecorationNameTable =
+      std::unordered_map<SpvDecoration, std::string, SpvDecorationHash>;
+
+  static const auto* const at_most_once_per_id = new AtMostOnceSet{
+      SpvDecorationArrayStride,
+  };
+  static const auto* const at_most_once_per_member = new AtMostOnceSet{
+      SpvDecorationOffset,
+      SpvDecorationMatrixStride,
+      SpvDecorationRowMajor,
+      SpvDecorationColMajor,
+  };
+  static const auto* const mutually_exclusive_per_id =
+      new MutuallyExclusiveSets{
+          {SpvDecorationBlock, SpvDecorationBufferBlock},
+      };
+  static const auto* const mutually_exclusive_per_member =
+      new MutuallyExclusiveSets{
+          {SpvDecorationRowMajor, SpvDecorationColMajor},
+      };
+  // For printing the decoration name.
+  static const auto* const decoration_name = new DecorationNameTable{
+      {SpvDecorationArrayStride, "ArrayStride"},
+      {SpvDecorationOffset, "Offset"},
+      {SpvDecorationMatrixStride, "MatrixStride"},
+      {SpvDecorationRowMajor, "RowMajor"},
+      {SpvDecorationColMajor, "ColMajor"},
+      {SpvDecorationBlock, "Block"},
+      {SpvDecorationBufferBlock, "BufferBlock"},
+  };
+
+  std::set<PerIDKey> seen_per_id;
+  std::set<PerMemberKey> seen_per_member;
+
+  for (const auto& inst : vstate.ordered_instructions()) {
+    const auto& words = inst.words();
+    if (SpvOpDecorate == inst.opcode()) {
+      const auto id = words[1];
+      const auto dec_type = static_cast<SpvDecoration>(words[2]);
+      const auto k = PerIDKey(dec_type, id);
+      const auto already_used = !seen_per_id.insert(k).second;
+      if (already_used &&
+          at_most_once_per_id->find(dec_type) != at_most_once_per_id->end()) {
+        return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
+               << "ID '" << id << "' decorated with "
+               << decoration_name->at(dec_type)
+               << " multiple times is not allowed.";
+      }
+      // Verify certain mutually exclusive decorations are not both applied on
+      // an ID.
+      for (const auto& s : *mutually_exclusive_per_id) {
+        if (s.find(dec_type) == s.end()) continue;
+        for (auto excl_dec_type : s) {
+          if (excl_dec_type == dec_type) continue;
+          const auto excl_k = PerIDKey(excl_dec_type, id);
+          if (seen_per_id.find(excl_k) != seen_per_id.end()) {
+            return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
+                   << "ID '" << id << "' decorated with both "
+                   << decoration_name->at(dec_type) << " and "
+                   << decoration_name->at(excl_dec_type) << " is not allowed.";
+          }
+        }
+      }
+    } else if (SpvOpMemberDecorate == inst.opcode()) {
+      const auto id = words[1];
+      const auto member_id = words[2];
+      const auto dec_type = static_cast<SpvDecoration>(words[3]);
+      const auto k = PerMemberKey(dec_type, id, member_id);
+      const auto already_used = !seen_per_member.insert(k).second;
+      if (already_used && at_most_once_per_member->find(dec_type) !=
+                              at_most_once_per_member->end()) {
+        return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
+               << "ID '" << id << "', member '" << member_id
+               << "' decorated with " << decoration_name->at(dec_type)
+               << " multiple times is not allowed.";
+      }
+      // Verify certain mutually exclusive decorations are not both applied on
+      // a (ID, member) tuple.
+      for (const auto& s : *mutually_exclusive_per_member) {
+        if (s.find(dec_type) == s.end()) continue;
+        for (auto excl_dec_type : s) {
+          if (excl_dec_type == dec_type) continue;
+          const auto excl_k = PerMemberKey(excl_dec_type, id, member_id);
+          if (seen_per_member.find(excl_k) != seen_per_member.end()) {
+            return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
+                   << "ID '" << id << "', member '" << member_id
+                   << "' decorated with both " << decoration_name->at(dec_type)
+                   << " and " << decoration_name->at(excl_dec_type)
+                   << " is not allowed.";
+          }
+        }
+      }
+    }
+  }
+  return SPV_SUCCESS;
+}
+
 spv_result_t CheckVulkanMemoryModelDeprecatedDecorations(
     ValidationState_t& vstate) {
   if (vstate.memory_model() != SpvMemoryModelVulkanKHR) return SPV_SUCCESS;
@@ -893,76 +1080,134 @@
   return SPV_SUCCESS;
 }
 
-spv_result_t CheckDecorationsOfConversions(ValidationState_t& vstate) {
-  // Validates FPRoundingMode decoration for Shader Capabilities
-  if (!vstate.HasCapability(SpvCapabilityShader)) return SPV_SUCCESS;
+// Returns SPV_SUCCESS if validation rules are satisfied for FPRoundingMode
+// decorations.  Otherwise emits a diagnostic and returns something other than
+// SPV_SUCCESS.
+spv_result_t CheckFPRoundingModeForShaders(ValidationState_t& vstate,
+                                           const Instruction& inst) {
+  // Validates width-only conversion instruction for floating-point object
+  // i.e., OpFConvert
+  if (inst.opcode() != SpvOpFConvert) {
+    return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
+           << "FPRoundingMode decoration can be applied only to a "
+              "width-only conversion instruction for floating-point "
+              "object.";
+  }
+
+  // Validates Object operand of an OpStore
+  for (const auto& use : inst.uses()) {
+    const auto store = use.first;
+    if (store->opcode() != SpvOpStore) {
+      return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
+             << "FPRoundingMode decoration can be applied only to the "
+                "Object operand of an OpStore.";
+    }
+
+    if (use.second != 2) {
+      return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
+             << "FPRoundingMode decoration can be applied only to the "
+                "Object operand of an OpStore.";
+    }
+
+    const auto ptr_inst = vstate.FindDef(store->GetOperandAs<uint32_t>(0));
+    const auto ptr_type = vstate.FindDef(ptr_inst->GetOperandAs<uint32_t>(0));
+
+    const auto half_float_id = ptr_type->GetOperandAs<uint32_t>(2);
+    if (!vstate.IsFloatScalarOrVectorType(half_float_id) ||
+        vstate.GetBitWidth(half_float_id) != 16) {
+      return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
+             << "FPRoundingMode decoration can be applied only to the "
+                "Object operand of an OpStore storing through a pointer "
+                "to "
+                "a 16-bit floating-point scalar or vector object.";
+    }
+
+    // Validates storage class of the pointer to the OpStore
+    const auto storage = ptr_type->GetOperandAs<uint32_t>(1);
+    if (storage != SpvStorageClassStorageBuffer &&
+        storage != SpvStorageClassUniform &&
+        storage != SpvStorageClassPushConstant &&
+        storage != SpvStorageClassInput && storage != SpvStorageClassOutput) {
+      return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
+             << "FPRoundingMode decoration can be applied only to the "
+                "Object operand of an OpStore in the StorageBuffer, "
+                "Uniform, PushConstant, Input, or Output Storage "
+                "Classes.";
+    }
+  }
+  return SPV_SUCCESS;
+}
+
+// Returns SPV_SUCCESS if validation rules are satisfied for Uniform
+// decorations. Otherwise emits a diagnostic and returns something other than
+// SPV_SUCCESS. Assumes each decoration on a group has been propagated down to
+// the group members.
+spv_result_t CheckUniformDecoration(ValidationState_t& vstate,
+                                    const Instruction& inst,
+                                    const Decoration&) {
+  // Uniform must decorate an "object"
+  //  - has a result ID
+  //  - is an instantiation of a non-void type.  So it has a type ID, and that
+  //  type is not void.
+
+  // We already know the result ID is non-zero.
+
+  if (inst.type_id() == 0) {
+    return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
+           << "Uniform decoration applied to a non-object";
+  }
+  if (Instruction* type_inst = vstate.FindDef(inst.type_id())) {
+    if (type_inst->opcode() == SpvOpTypeVoid) {
+      return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
+             << "Uniform decoration applied to a value with void type";
+    }
+  } else {
+    // We might never get here because this would have been rejected earlier in
+    // the flow.
+    return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
+           << "Uniform decoration applied to an object with invalid type";
+  }
+  return SPV_SUCCESS;
+}
+
+#define PASS_OR_BAIL_AT_LINE(X, LINE)           \
+  {                                             \
+    spv_result_t e##LINE = (X);                 \
+    if (e##LINE != SPV_SUCCESS) return e##LINE; \
+  }
+#define PASS_OR_BAIL(X) PASS_OR_BAIL_AT_LINE(X, __LINE__)
+
+// Check rules for decorations where we start from the decoration rather
+// than the decorated object.  Assumes each decoration on a group have been
+// propagated down to the group members.
+spv_result_t CheckDecorationsFromDecoration(ValidationState_t& vstate) {
+  // Some rules are only checked for shaders.
+  const bool is_shader = vstate.HasCapability(SpvCapabilityShader);
 
   for (const auto& kv : vstate.id_decorations()) {
     const uint32_t id = kv.first;
     const auto& decorations = kv.second;
-    if (decorations.empty()) {
-      continue;
-    }
+    if (decorations.empty()) continue;
 
     const Instruction* inst = vstate.FindDef(id);
     assert(inst);
 
+    // We assume the decorations applied to a decoration group have already
+    // been propagated down to the group members.
+    if (inst->opcode() == SpvOpDecorationGroup) continue;
+
     // Validates FPRoundingMode decoration
     for (const auto& decoration : decorations) {
-      if (decoration.dec_type() != SpvDecorationFPRoundingMode) {
-        continue;
-      }
-
-      // Validates width-only conversion instruction for floating-point object
-      // i.e., OpFConvert
-      if (inst->opcode() != SpvOpFConvert) {
-        return vstate.diag(SPV_ERROR_INVALID_ID, inst)
-               << "FPRoundingMode decoration can be applied only to a "
-                  "width-only conversion instruction for floating-point "
-                  "object.";
-      }
-
-      // Validates Object operand of an OpStore
-      for (const auto& use : inst->uses()) {
-        const auto store = use.first;
-        if (store->opcode() != SpvOpStore) {
-          return vstate.diag(SPV_ERROR_INVALID_ID, inst)
-                 << "FPRoundingMode decoration can be applied only to the "
-                    "Object operand of an OpStore.";
-        }
-
-        if (use.second != 2) {
-          return vstate.diag(SPV_ERROR_INVALID_ID, inst)
-                 << "FPRoundingMode decoration can be applied only to the "
-                    "Object operand of an OpStore.";
-        }
-
-        const auto ptr_inst = vstate.FindDef(store->GetOperandAs<uint32_t>(0));
-        const auto ptr_type =
-            vstate.FindDef(ptr_inst->GetOperandAs<uint32_t>(0));
-
-        const auto half_float_id = ptr_type->GetOperandAs<uint32_t>(2);
-        if (!vstate.IsFloatScalarOrVectorType(half_float_id) ||
-            vstate.GetBitWidth(half_float_id) != 16) {
-          return vstate.diag(SPV_ERROR_INVALID_ID, inst)
-                 << "FPRoundingMode decoration can be applied only to the "
-                    "Object operand of an OpStore storing through a pointer to "
-                    "a 16-bit floating-point scalar or vector object.";
-        }
-
-        // Validates storage class of the pointer to the OpStore
-        const auto storage = ptr_type->GetOperandAs<uint32_t>(1);
-        if (storage != SpvStorageClassStorageBuffer &&
-            storage != SpvStorageClassUniform &&
-            storage != SpvStorageClassPushConstant &&
-            storage != SpvStorageClassInput &&
-            storage != SpvStorageClassOutput) {
-          return vstate.diag(SPV_ERROR_INVALID_ID, inst)
-                 << "FPRoundingMode decoration can be applied only to the "
-                    "Object operand of an OpStore in the StorageBuffer, "
-                    "Uniform, PushConstant, Input, or Output Storage "
-                    "Classes.";
-        }
+      switch (decoration.dec_type()) {
+        case SpvDecorationFPRoundingMode:
+          if (is_shader)
+            PASS_OR_BAIL(CheckFPRoundingModeForShaders(vstate, *inst));
+          break;
+        case SpvDecorationUniform:
+          PASS_OR_BAIL(CheckUniformDecoration(vstate, *inst, decoration));
+          break;
+        default:
+          break;
       }
     }
   }
@@ -971,15 +1216,15 @@
 
 }  // namespace
 
-// Validates that decorations have been applied properly.
 spv_result_t ValidateDecorations(ValidationState_t& vstate) {
   if (auto error = CheckImportedVariableInitialization(vstate)) return error;
   if (auto error = CheckDecorationsOfEntryPoints(vstate)) return error;
   if (auto error = CheckDecorationsOfBuffers(vstate)) return error;
+  if (auto error = CheckDecorationsCompatibility(vstate)) return error;
   if (auto error = CheckLinkageAttrOfFunctions(vstate)) return error;
   if (auto error = CheckVulkanMemoryModelDeprecatedDecorations(vstate))
     return error;
-  if (auto error = CheckDecorationsOfConversions(vstate)) return error;
+  if (auto error = CheckDecorationsFromDecoration(vstate)) return error;
   return SPV_SUCCESS;
 }
 
diff --git a/source/val/validate_image.cpp b/source/val/validate_image.cpp
index 67da3f7..8a357ff 100644
--- a/source/val/validate_image.cpp
+++ b/source/val/validate_image.cpp
@@ -23,6 +23,7 @@
 #include "source/spirv_target_env.h"
 #include "source/util/bitutils.h"
 #include "source/val/instruction.h"
+#include "source/val/validate_scopes.h"
 #include "source/val/validation_state.h"
 
 namespace spvtools {
@@ -513,6 +514,10 @@
                 "NonPrivateTexelKHR is also specified: Op"
              << spvOpcodeString(opcode);
     }
+
+    const auto available_scope = inst->word(word_index++);
+    if (auto error = ValidateMemoryScope(_, inst, available_scope))
+      return error;
   }
 
   if (mask & SpvImageOperandsMakeTexelVisibleKHRMask) {
@@ -531,6 +536,9 @@
                 "is also specified: Op"
              << spvOpcodeString(opcode);
     }
+
+    const auto visible_scope = inst->word(word_index++);
+    if (auto error = ValidateMemoryScope(_, inst, visible_scope)) return error;
   }
 
   return SPV_SUCCESS;
@@ -1512,7 +1520,7 @@
   ImageTypeInfo info;
   if (!GetImageTypeInfo(_, image_type, &info)) {
     return _.diag(SPV_ERROR_INVALID_DATA, inst)
-        << "Corrupt image type definition";
+           << "Corrupt image type definition";
   }
 
   uint32_t expected_num_components = info.arrayed;
@@ -1545,8 +1553,8 @@
   uint32_t result_num_components = _.GetDimension(result_type);
   if (result_num_components != expected_num_components) {
     return _.diag(SPV_ERROR_INVALID_DATA, inst)
-        << "Result Type has " << result_num_components << " components, "
-        << "but " << expected_num_components << " expected";
+           << "Result Type has " << result_num_components << " components, "
+           << "but " << expected_num_components << " expected";
   }
 
   return SPV_SUCCESS;
diff --git a/source/val/validate_memory.cpp b/source/val/validate_memory.cpp
index 21b8c8b..6e7c9e8 100644
--- a/source/val/validate_memory.cpp
+++ b/source/val/validate_memory.cpp
@@ -21,6 +21,7 @@
 #include "source/opcode.h"
 #include "source/spirv_target_env.h"
 #include "source/val/instruction.h"
+#include "source/val/validate_scopes.h"
 #include "source/val/validation_state.h"
 
 namespace spvtools {
@@ -230,6 +231,51 @@
   return std::make_pair(dst_sc, src_sc);
 }
 
+// This function is only called for OpLoad, OpStore, OpCopyMemory and
+// OpCopyMemorySized.
+uint32_t GetMakeAvailableScope(const Instruction* inst, uint32_t mask) {
+  uint32_t offset = 1;
+  if (mask & SpvMemoryAccessAlignedMask) ++offset;
+
+  uint32_t scope_id = 0;
+  switch (inst->opcode()) {
+    case SpvOpLoad:
+    case SpvOpCopyMemorySized:
+      return inst->GetOperandAs<uint32_t>(3 + offset);
+    case SpvOpStore:
+    case SpvOpCopyMemory:
+      return inst->GetOperandAs<uint32_t>(2 + offset);
+    default:
+      assert(false && "unexpected opcode");
+      break;
+  }
+
+  return scope_id;
+}
+
+// This function is only called for OpLoad, OpStore, OpCopyMemory and
+// OpCopyMemorySized.
+uint32_t GetMakeVisibleScope(const Instruction* inst, uint32_t mask) {
+  uint32_t offset = 1;
+  if (mask & SpvMemoryAccessAlignedMask) ++offset;
+  if (mask & SpvMemoryAccessMakePointerAvailableKHRMask) ++offset;
+
+  uint32_t scope_id = 0;
+  switch (inst->opcode()) {
+    case SpvOpLoad:
+    case SpvOpCopyMemorySized:
+      return inst->GetOperandAs<uint32_t>(3 + offset);
+    case SpvOpStore:
+    case SpvOpCopyMemory:
+      return inst->GetOperandAs<uint32_t>(2 + offset);
+    default:
+      assert(false && "unexpected opcode");
+      break;
+  }
+
+  return scope_id;
+}
+
 spv_result_t CheckMemoryAccess(ValidationState_t& _, const Instruction* inst,
                                uint32_t mask) {
   if (mask & SpvMemoryAccessMakePointerAvailableKHRMask) {
@@ -243,6 +289,11 @@
              << "NonPrivatePointerKHR must be specified if "
                 "MakePointerAvailableKHR is specified.";
     }
+
+    // Check the associated scope for MakeAvailableKHR.
+    const auto available_scope = GetMakeAvailableScope(inst, mask);
+    if (auto error = ValidateMemoryScope(_, inst, available_scope))
+      return error;
   }
 
   if (mask & SpvMemoryAccessMakePointerVisibleKHRMask) {
@@ -256,6 +307,10 @@
              << "NonPrivatePointerKHR must be specified if "
                 "MakePointerVisibleKHR is specified.";
     }
+
+    // Check the associated scope for MakeVisibleKHR.
+    const auto visible_scope = GetMakeVisibleScope(inst, mask);
+    if (auto error = ValidateMemoryScope(_, inst, visible_scope)) return error;
   }
 
   if (mask & SpvMemoryAccessNonPrivatePointerKHRMask) {
diff --git a/source/val/validate_memory_semantics.cpp b/source/val/validate_memory_semantics.cpp
new file mode 100644
index 0000000..ec6df38
--- /dev/null
+++ b/source/val/validate_memory_semantics.cpp
@@ -0,0 +1,248 @@
+// Copyright (c) 2018 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/val/validate_memory_semantics.h"
+
+#include "source/diagnostic.h"
+#include "source/spirv_target_env.h"
+#include "source/util/bitutils.h"
+#include "source/val/instruction.h"
+#include "source/val/validation_state.h"
+
+namespace spvtools {
+namespace val {
+
+spv_result_t ValidateMemorySemantics(ValidationState_t& _,
+                                     const Instruction* inst,
+                                     uint32_t operand_index) {
+  const SpvOp opcode = inst->opcode();
+  const auto id = inst->GetOperandAs<const uint32_t>(operand_index);
+  bool is_int32 = false, is_const_int32 = false;
+  uint32_t value = 0;
+  std::tie(is_int32, is_const_int32, value) = _.EvalInt32IfConst(id);
+
+  if (!is_int32) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << spvOpcodeString(opcode)
+           << ": expected Memory Semantics to be a 32-bit int";
+  }
+
+  if (!is_const_int32) {
+    if (_.HasCapability(SpvCapabilityShader)) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "Memory Semantics ids must be OpConstant when Shader "
+                "capability is present";
+    }
+    return SPV_SUCCESS;
+  }
+
+  if (spvIsWebGPUEnv(_.context()->target_env)) {
+    uint32_t valid_bits = SpvMemorySemanticsAcquireMask |
+                          SpvMemorySemanticsReleaseMask |
+                          SpvMemorySemanticsAcquireReleaseMask |
+                          SpvMemorySemanticsUniformMemoryMask |
+                          SpvMemorySemanticsWorkgroupMemoryMask |
+                          SpvMemorySemanticsImageMemoryMask |
+                          SpvMemorySemanticsOutputMemoryKHRMask |
+                          SpvMemorySemanticsMakeAvailableKHRMask |
+                          SpvMemorySemanticsMakeVisibleKHRMask;
+    if (value & ~valid_bits) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "WebGPU spec disallows any bit masks in Memory Semantics that "
+                "are not Acquire, Release, AcquireRelease, UniformMemory, "
+                "WorkgroupMemory, ImageMemory, OutputMemoryKHR, "
+                "MakeAvailableKHR, or MakeVisibleKHR";
+    }
+  }
+
+  const size_t num_memory_order_set_bits = spvtools::utils::CountSetBits(
+      value & (SpvMemorySemanticsAcquireMask | SpvMemorySemanticsReleaseMask |
+               SpvMemorySemanticsAcquireReleaseMask |
+               SpvMemorySemanticsSequentiallyConsistentMask));
+
+  if (num_memory_order_set_bits > 1) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << spvOpcodeString(opcode)
+           << ": Memory Semantics can have at most one of the following "
+              "bits "
+              "set: Acquire, Release, AcquireRelease or "
+              "SequentiallyConsistent";
+  }
+
+  if (_.memory_model() == SpvMemoryModelVulkanKHR &&
+      value & SpvMemorySemanticsSequentiallyConsistentMask) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "SequentiallyConsistent memory "
+              "semantics cannot be used with "
+              "the VulkanKHR memory model.";
+  }
+
+  if (value & SpvMemorySemanticsMakeAvailableKHRMask &&
+      !_.HasCapability(SpvCapabilityVulkanMemoryModelKHR)) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << spvOpcodeString(opcode)
+           << ": Memory Semantics MakeAvailableKHR requires capability "
+           << "VulkanMemoryModelKHR";
+  }
+
+  if (value & SpvMemorySemanticsMakeVisibleKHRMask &&
+      !_.HasCapability(SpvCapabilityVulkanMemoryModelKHR)) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << spvOpcodeString(opcode)
+           << ": Memory Semantics MakeVisibleKHR requires capability "
+           << "VulkanMemoryModelKHR";
+  }
+
+  if (value & SpvMemorySemanticsOutputMemoryKHRMask &&
+      !_.HasCapability(SpvCapabilityVulkanMemoryModelKHR)) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << spvOpcodeString(opcode)
+           << ": Memory Semantics OutputMemoryKHR requires capability "
+           << "VulkanMemoryModelKHR";
+  }
+
+  if (value & SpvMemorySemanticsUniformMemoryMask &&
+      !_.HasCapability(SpvCapabilityShader)) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << spvOpcodeString(opcode)
+           << ": Memory Semantics UniformMemory requires capability Shader";
+  }
+
+  // Disabling this check until
+  // https://github.com/KhronosGroup/glslang/issues/1618 is resolved.
+  //   if (value & SpvMemorySemanticsAtomicCounterMemoryMask &&
+  //      !_.HasCapability(SpvCapabilityAtomicStorage)) {
+  //    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+  //           << spvOpcodeString(opcode)
+  //           << ": Memory Semantics AtomicCounterMemory requires capability "
+  //              "AtomicStorage";
+  //  }
+
+  if (value & (SpvMemorySemanticsMakeAvailableKHRMask |
+               SpvMemorySemanticsMakeVisibleKHRMask)) {
+    const bool includes_storage_class =
+        value & (SpvMemorySemanticsUniformMemoryMask |
+                 SpvMemorySemanticsSubgroupMemoryMask |
+                 SpvMemorySemanticsWorkgroupMemoryMask |
+                 SpvMemorySemanticsCrossWorkgroupMemoryMask |
+                 SpvMemorySemanticsAtomicCounterMemoryMask |
+                 SpvMemorySemanticsImageMemoryMask |
+                 SpvMemorySemanticsOutputMemoryKHRMask);
+
+    if (!includes_storage_class) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << spvOpcodeString(opcode)
+             << ": expected Memory Semantics to include a storage class";
+    }
+  }
+
+  if (value & SpvMemorySemanticsMakeVisibleKHRMask &&
+      !(value & (SpvMemorySemanticsAcquireMask |
+                 SpvMemorySemanticsAcquireReleaseMask))) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << spvOpcodeString(opcode)
+           << ": MakeVisibleKHR Memory Semantics also requires either Acquire "
+              "or AcquireRelease Memory Semantics";
+  }
+
+  if (value & SpvMemorySemanticsMakeAvailableKHRMask &&
+      !(value & (SpvMemorySemanticsReleaseMask |
+                 SpvMemorySemanticsAcquireReleaseMask))) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << spvOpcodeString(opcode)
+           << ": MakeAvailableKHR Memory Semantics also requires either "
+              "Release or AcquireRelease Memory Semantics";
+  }
+
+  if (spvIsVulkanEnv(_.context()->target_env)) {
+    const bool includes_storage_class =
+        value & (SpvMemorySemanticsUniformMemoryMask |
+                 SpvMemorySemanticsWorkgroupMemoryMask |
+                 SpvMemorySemanticsImageMemoryMask |
+                 SpvMemorySemanticsOutputMemoryKHRMask);
+
+    if (opcode == SpvOpMemoryBarrier && !num_memory_order_set_bits) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << spvOpcodeString(opcode)
+             << ": Vulkan specification requires Memory Semantics to have "
+                "one "
+                "of the following bits set: Acquire, Release, "
+                "AcquireRelease "
+                "or SequentiallyConsistent";
+    }
+
+    if (opcode == SpvOpMemoryBarrier && !includes_storage_class) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << spvOpcodeString(opcode)
+             << ": expected Memory Semantics to include a Vulkan-supported "
+                "storage class";
+    }
+
+#if 0
+    // TODO(atgoo@github.com): this check fails Vulkan CTS, reenable once fixed.
+    if (opcode == SpvOpControlBarrier && value && !includes_storage_class) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << spvOpcodeString(opcode)
+             << ": expected Memory Semantics to include a Vulkan-supported "
+                "storage class if Memory Semantics is not None";
+    }
+#endif
+  }
+
+  if (opcode == SpvOpAtomicFlagClear &&
+      (value & SpvMemorySemanticsAcquireMask ||
+       value & SpvMemorySemanticsAcquireReleaseMask)) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Memory Semantics Acquire and AcquireRelease cannot be used "
+              "with "
+           << spvOpcodeString(opcode);
+  }
+
+  if (opcode == SpvOpAtomicCompareExchange && operand_index == 5 &&
+      (value & SpvMemorySemanticsReleaseMask ||
+       value & SpvMemorySemanticsAcquireReleaseMask)) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << spvOpcodeString(opcode)
+           << ": Memory Semantics Release and AcquireRelease cannot be "
+              "used "
+              "for operand Unequal";
+  }
+
+  if (spvIsVulkanEnv(_.context()->target_env)) {
+    if (opcode == SpvOpAtomicLoad &&
+        (value & SpvMemorySemanticsReleaseMask ||
+         value & SpvMemorySemanticsAcquireReleaseMask ||
+         value & SpvMemorySemanticsSequentiallyConsistentMask)) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "Vulkan spec disallows OpAtomicLoad with Memory Semantics "
+                "Release, AcquireRelease and SequentiallyConsistent";
+    }
+
+    if (opcode == SpvOpAtomicStore &&
+        (value & SpvMemorySemanticsAcquireMask ||
+         value & SpvMemorySemanticsAcquireReleaseMask ||
+         value & SpvMemorySemanticsSequentiallyConsistentMask)) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "Vulkan spec disallows OpAtomicStore with Memory Semantics "
+                "Acquire, AcquireRelease and SequentiallyConsistent";
+    }
+  }
+
+  // TODO(atgoo@github.com) Add checks for OpenCL and OpenGL environments.
+
+  return SPV_SUCCESS;
+}
+
+}  // namespace val
+}  // namespace spvtools
diff --git a/source/val/validate_memory_semantics.h b/source/val/validate_memory_semantics.h
new file mode 100644
index 0000000..72a3e10
--- /dev/null
+++ b/source/val/validate_memory_semantics.h
@@ -0,0 +1,28 @@
+// Copyright (c) 2018 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Validates correctness of memory semantics for SPIR-V instructions.
+
+#include "source/opcode.h"
+#include "source/val/validate.h"
+
+namespace spvtools {
+namespace val {
+
+spv_result_t ValidateMemorySemantics(ValidationState_t& _,
+                                     const Instruction* inst,
+                                     uint32_t operand_index);
+
+}  // namespace val
+}  // namespace spvtools
diff --git a/source/val/validate_scopes.cpp b/source/val/validate_scopes.cpp
index 8f29ae0..3ba8f3b 100644
--- a/source/val/validate_scopes.cpp
+++ b/source/val/validate_scopes.cpp
@@ -149,6 +149,14 @@
     }
   }
 
+  if (value == SpvScopeDevice &&
+      _.HasCapability(SpvCapabilityVulkanMemoryModelKHR) &&
+      !_.HasCapability(SpvCapabilityVulkanMemoryModelDeviceScopeKHR)) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Use of device scope with VulkanKHR memory model requires the "
+              "VulkanMemoryModelDeviceScopeKHR capability";
+  }
+
   // Vulkan Specific rules
   if (spvIsVulkanEnv(_.context()->target_env)) {
     if (value == SpvScopeCrossDevice) {
diff --git a/source/val/validate_type.cpp b/source/val/validate_type.cpp
index 3bbdb87..e0f2786 100644
--- a/source/val/validate_type.cpp
+++ b/source/val/validate_type.cpp
@@ -191,11 +191,6 @@
              << _.getIdName(member_type_id) << ".";
     }
     if (_.IsForwardPointer(member_type_id)) {
-      if (member_type->opcode() != SpvOpTypePointer) {
-        return _.diag(SPV_ERROR_INVALID_ID, inst)
-               << "Found a forward reference to a non-pointer "
-                  "type in OpTypeStruct instruction.";
-      }
       // If we're dealing with a forward pointer:
       // Find out the type that the pointer is pointing to (must be struct)
       // word 3 is the <id> of the type being pointed to.
@@ -296,10 +291,32 @@
   return SPV_SUCCESS;
 }
 
+spv_result_t ValidateTypeForwardPointer(ValidationState_t& _,
+                                        const Instruction* inst) {
+  const auto pointer_type_id = inst->GetOperandAs<uint32_t>(0);
+  const auto pointer_type_inst = _.FindDef(pointer_type_id);
+  if (pointer_type_inst->opcode() != SpvOpTypePointer) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Pointer type in OpTypeForwardPointer is not a pointer type.";
+  }
+
+  if (inst->GetOperandAs<uint32_t>(1) !=
+      pointer_type_inst->GetOperandAs<uint32_t>(1)) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Storage class in OpTypeForwardPointer does not match the "
+              "pointer definition.";
+  }
+
+  return SPV_SUCCESS;
+}
+
 }  // namespace
 
 spv_result_t TypePass(ValidationState_t& _, const Instruction* inst) {
-  if (!spvOpcodeGeneratesType(inst->opcode())) return SPV_SUCCESS;
+  if (!spvOpcodeGeneratesType(inst->opcode()) &&
+      inst->opcode() != SpvOpTypeForwardPointer) {
+    return SPV_SUCCESS;
+  }
 
   if (auto error = ValidateUniqueness(_, inst)) return error;
 
@@ -325,6 +342,9 @@
     case SpvOpTypeFunction:
       if (auto error = ValidateTypeFunction(_, inst)) return error;
       break;
+    case SpvOpTypeForwardPointer:
+      if (auto error = ValidateTypeForwardPointer(_, inst)) return error;
+      break;
     default:
       break;
   }
diff --git a/source/val/validation_state.cpp b/source/val/validation_state.cpp
index b8f991f..a10186f 100644
--- a/source/val/validation_state.cpp
+++ b/source/val/validation_state.cpp
@@ -208,6 +208,10 @@
                    /* diagnostic = */ nullptr);
     preallocateStorage();
   }
+
+  friendly_mapper_ = spvtools::MakeUnique<spvtools::FriendlyNameMapper>(
+      context_, words_, num_words_);
+  name_mapper_ = friendly_mapper_->GetNameMapper();
 }
 
 void ValidationState_t::preallocateStorage() {
@@ -239,21 +243,10 @@
 }
 
 std::string ValidationState_t::getIdName(uint32_t id) const {
-  std::stringstream out;
-  out << id;
-  if (operand_names_.find(id) != end(operand_names_)) {
-    out << "[" << operand_names_.at(id) << "]";
-  }
-  return out.str();
-}
+  const std::string id_name = name_mapper_(id);
 
-std::string ValidationState_t::getIdOrName(uint32_t id) const {
   std::stringstream out;
-  if (operand_names_.find(id) != std::end(operand_names_)) {
-    out << operand_names_.at(id);
-  } else {
-    out << id;
-  }
+  out << id << "[%" << id_name << "]";
   return out.str();
 }
 
@@ -926,6 +919,39 @@
   }
 }
 
+void ValidationState_t::ComputeRecursiveEntryPoints() {
+  for (const Function func : functions()) {
+    std::stack<uint32_t> call_stack;
+    std::set<uint32_t> visited;
+
+    for (const uint32_t new_call : func.function_call_targets()) {
+      call_stack.push(new_call);
+    }
+
+    while (!call_stack.empty()) {
+      const uint32_t called_func_id = call_stack.top();
+      call_stack.pop();
+
+      if (!visited.insert(called_func_id).second) continue;
+
+      if (called_func_id == func.id()) {
+        for (const uint32_t entry_point :
+             function_to_entry_points_[called_func_id])
+          recursive_entry_points_.insert(entry_point);
+        break;
+      }
+
+      const Function* called_func = function(called_func_id);
+      if (called_func) {
+        // Other checks should error out on this invalid SPIR-V.
+        for (const uint32_t new_call : called_func->function_call_targets()) {
+          call_stack.push(new_call);
+        }
+      }
+    }
+  }
+}
+
 const std::vector<uint32_t>& ValidationState_t::FunctionEntryPoints(
     uint32_t func) const {
   auto iter = function_to_entry_points_.find(func);
@@ -936,6 +962,34 @@
   }
 }
 
+std::set<uint32_t> ValidationState_t::EntryPointReferences(uint32_t id) const {
+  std::set<uint32_t> referenced_entry_points;
+  const auto inst = FindDef(id);
+  if (!inst) return referenced_entry_points;
+
+  std::vector<const Instruction*> stack;
+  stack.push_back(inst);
+  while (!stack.empty()) {
+    const auto current_inst = stack.back();
+    stack.pop_back();
+
+    if (const auto func = current_inst->function()) {
+      // Instruction lives in a function, we can stop searching.
+      const auto function_entry_points = FunctionEntryPoints(func->id());
+      referenced_entry_points.insert(function_entry_points.begin(),
+                                     function_entry_points.end());
+    } else {
+      // Instruction is in the global scope, keep searching its uses.
+      for (auto pair : current_inst->uses()) {
+        const auto next_inst = pair.first;
+        stack.push_back(next_inst);
+      }
+    }
+  }
+
+  return referenced_entry_points;
+}
+
 std::string ValidationState_t::Disassemble(const Instruction& inst) const {
   const spv_parsed_instruction_t& c_inst(inst.c_inst());
   return Disassemble(c_inst.words, c_inst.num_words);
diff --git a/source/val/validation_state.h b/source/val/validation_state.h
index d5b996c..85229f2 100644
--- a/source/val/validation_state.h
+++ b/source/val/validation_state.h
@@ -28,6 +28,7 @@
 #include "source/disassemble.h"
 #include "source/enum_set.h"
 #include "source/latest_version_spirv_header.h"
+#include "source/name_mapper.h"
 #include "source/spirv_definition.h"
 #include "source/spirv_validator_options.h"
 #include "source/val/decoration.h"
@@ -154,9 +155,6 @@
   /// Mutator function for ID bound.
   void setIdBound(uint32_t bound);
 
-  /// Like getIdName but does not display the id if the \p id has a name
-  std::string getIdOrName(uint32_t id) const;
-
   /// Returns the number of ID which have been forward referenced but not
   /// defined
   size_t unresolved_forward_id_count() const;
@@ -224,6 +222,12 @@
   /// Returns a list of entry point function ids
   const std::vector<uint32_t>& entry_points() const { return entry_points_; }
 
+  /// Returns the set of entry points that root call graphs that contain
+  /// recursion.
+  const std::set<uint32_t>& recursive_entry_points() const {
+    return recursive_entry_points_;
+  }
+
   /// Registers execution mode for the given entry point.
   void RegisterExecutionModeForEntryPoint(uint32_t entry_point,
                                           SpvExecutionMode execution_mode) {
@@ -263,9 +267,19 @@
   /// Note: called after fully parsing the binary.
   void ComputeFunctionToEntryPointMapping();
 
+  /// Traverse call tree and computes recursive_entry_points_.
+  /// Note: called after fully parsing the binary and calling
+  /// ComputeFunctionToEntryPointMapping.
+  void ComputeRecursiveEntryPoints();
+
   /// Returns all the entry points that can call |func|.
   const std::vector<uint32_t>& FunctionEntryPoints(uint32_t func) const;
 
+  /// Returns all the entry points that statically use |id|.
+  ///
+  /// Note: requires ComputeFunctionToEntryPointMapping to have been called.
+  std::set<uint32_t> EntryPointReferences(uint32_t id) const;
+
   /// Inserts an <id> to the set of functions that are target of OpFunctionCall.
   void AddFunctionCallTarget(const uint32_t id) {
     function_call_targets_.insert(id);
@@ -277,6 +291,9 @@
     return (function_call_targets_.find(id) != function_call_targets_.end());
   }
 
+  bool IsFunctionCallDefined(const uint32_t id) {
+    return (id_to_function_.find(id) != id_to_function_.end());
+  }
   /// Registers the capability and its dependent capabilities
   void RegisterCapability(SpvCapability cap);
 
@@ -604,6 +621,10 @@
   std::unordered_map<uint32_t, std::vector<EntryPointDescription>>
       entry_point_descriptions_;
 
+  /// IDs that are entry points, ie, arguments to OpEntryPoint, and root a call
+  /// graph that recurses.
+  std::set<uint32_t> recursive_entry_points_;
+
   /// Functions IDs that are target of OpFunctionCall.
   std::unordered_set<uint32_t> function_call_targets_;
 
@@ -661,6 +682,10 @@
   std::unordered_map<uint32_t, std::vector<uint32_t>> function_to_entry_points_;
   const std::vector<uint32_t> empty_ids_;
 
+  /// Maps ids to friendly names.
+  std::unique_ptr<spvtools::FriendlyNameMapper> friendly_mapper_;
+  spvtools::NameMapper name_mapper_;
+
   /// Variables used to reduce the number of diagnostic messages.
   uint32_t num_of_warnings_;
   uint32_t max_num_of_warnings_;
diff --git a/test/cpp_interface_test.cpp b/test/cpp_interface_test.cpp
index 463d969..538d40f 100644
--- a/test/cpp_interface_test.cpp
+++ b/test/cpp_interface_test.cpp
@@ -58,7 +58,8 @@
     EXPECT_EQ(0u, position.line);
     EXPECT_EQ(0u, position.column);
     EXPECT_EQ(1u, position.index);
-    EXPECT_STREQ("ID 1 has not been defined\n  %2 = OpSizeOf %1 %3\n", message);
+    EXPECT_STREQ("ID 1[%1] has not been defined\n  %2 = OpSizeOf %1 %3\n",
+                 message);
   });
   EXPECT_FALSE(t.Validate(binary));
 
diff --git a/test/opt/CMakeLists.txt b/test/opt/CMakeLists.txt
index 56414ae..1e32df3 100644
--- a/test/opt/CMakeLists.txt
+++ b/test/opt/CMakeLists.txt
@@ -92,4 +92,5 @@
 add_spvtools_unittest(TARGET upgrade_memory_model
   SRCS upgrade_memory_model_test.cpp pass_utils.cpp
   LIBS SPIRV-Tools-opt
+  PCH_FILE pch_test_opt
 )
diff --git a/test/opt/aggressive_dead_code_elim_test.cpp b/test/opt/aggressive_dead_code_elim_test.cpp
index a53418c..e58a76f 100644
--- a/test/opt/aggressive_dead_code_elim_test.cpp
+++ b/test/opt/aggressive_dead_code_elim_test.cpp
@@ -5111,6 +5111,43 @@
   SinglePassRunAndMatch<AggressiveDCEPass>(text, true);
 }
 
+TEST_F(AggressiveDCETest, DeadDecorationGroupAndValidDecorationMgr) {
+  // The decoration group should be eliminated because the target of group
+  // decorate is dead.
+  const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %1 Restrict
+OpDecorate %1 Aliased
+%1 = OpDecorationGroup
+OpGroupDecorate %1 %var
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%uint_ptr = OpTypePointer Function %uint
+%main = OpFunction %void None %func
+%2 = OpLabel
+%var = OpVariable %uint_ptr Function
+OpReturn
+OpFunctionEnd
+  )";
+
+  auto pass = MakeUnique<AggressiveDCEPass>();
+  auto consumer = [](spv_message_level_t, const char*, const spv_position_t&,
+                     const char* message) {
+    std::cerr << message << std::endl;
+  };
+  auto context = BuildModule(SPV_ENV_UNIVERSAL_1_1, consumer, text);
+
+  // Build the decoration manager before the pass.
+  context->get_decoration_mgr();
+
+  const auto status = pass->Run(context.get());
+  EXPECT_EQ(status, Pass::Status::SuccessWithChange);
+}
+
 TEST_F(AggressiveDCETest, ParitallyDeadDecorationGroup) {
   const std::string text = R"(
 ; CHECK: OpDecorate [[grp:%\w+]] Restrict
@@ -5211,7 +5248,7 @@
 TEST_F(AggressiveDCETest, PartiallyDeadGroupMemberDecorate) {
   const std::string text = R"(
 ; CHECK: OpDecorate [[grp:%\w+]] Offset 0
-; CHECK: OpDecorate [[grp]] Uniform
+; CHECK: OpDecorate [[grp]] RelaxedPrecision
 ; CHECK: [[grp]] = OpDecorationGroup
 ; CHECK: OpGroupMemberDecorate [[grp]] [[output:%\w+]] 1
 ; CHECK: [[output]] = OpTypeStruct
@@ -5221,7 +5258,7 @@
 OpEntryPoint Fragment %main "main" %output
 OpExecutionMode %main OriginUpperLeft
 OpDecorate %1 Offset 0
-OpDecorate %1 Uniform
+OpDecorate %1 RelaxedPrecision
 %1 = OpDecorationGroup
 OpGroupMemberDecorate %1 %var_struct 0 %output_struct 1
 %void = OpTypeVoid
@@ -5250,7 +5287,7 @@
        PartiallyDeadGroupMemberDecorateDifferentGroupDecorate) {
   const std::string text = R"(
 ; CHECK: OpDecorate [[grp:%\w+]] Offset 0
-; CHECK: OpDecorate [[grp]] Uniform
+; CHECK: OpDecorate [[grp]] RelaxedPrecision
 ; CHECK: [[grp]] = OpDecorationGroup
 ; CHECK: OpGroupMemberDecorate [[grp]] [[output:%\w+]] 1
 ; CHECK-NOT: OpGroupMemberDecorate
@@ -5261,7 +5298,7 @@
 OpEntryPoint Fragment %main "main" %output
 OpExecutionMode %main OriginUpperLeft
 OpDecorate %1 Offset 0
-OpDecorate %1 Uniform
+OpDecorate %1 RelaxedPrecision
 %1 = OpDecorationGroup
 OpGroupMemberDecorate %1 %var_struct 0
 OpGroupMemberDecorate %1 %output_struct 1
diff --git a/test/opt/fold_test.cpp b/test/opt/fold_test.cpp
index 9aae338..1a54421 100644
--- a/test/opt/fold_test.cpp
+++ b/test/opt/fold_test.cpp
@@ -179,6 +179,7 @@
 %uint_3 = OpConstant %uint 3
 %uint_4 = OpConstant %uint 4
 %uint_32 = OpConstant %uint 32
+%uint_42 = OpConstant %uint 42
 %uint_max = OpConstant %uint 4294967295
 %v2int_undef = OpUndef %v2int
 %v2int_0_0 = OpConstantComposite %v2int %int_0 %int_0
@@ -474,6 +475,36 @@
           "%2 = OpUMod %uint %uint_1 %uint_0\n" +
           "OpReturn\n" +
           "OpFunctionEnd",
+      2, 0),
+  // Test case 22: fold unsigned n >> 42 (undefined, so set to zero).
+  InstructionFoldingCase<uint32_t>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_uint Function\n" +
+          "%load = OpLoad %uint %n\n" +
+          "%2 = OpShiftRightLogical %uint %load %uint_42\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, 0),
+  // Test case 21: fold signed n >> 42 (undefined, so set to zero).
+  InstructionFoldingCase<uint32_t>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_int Function\n" +
+          "%load = OpLoad %int %n\n" +
+          "%2 = OpShiftRightLogical %int %load %uint_42\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, 0),
+  // Test case 22: fold n << 42 (undefined, so set to zero).
+  InstructionFoldingCase<uint32_t>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_int Function\n" +
+          "%load = OpLoad %int %n\n" +
+          "%2 = OpShiftLeftLogical %int %load %uint_42\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
       2, 0)
 ));
 // clang-format on
diff --git a/test/opt/ir_builder.cpp b/test/opt/ir_builder.cpp
index 3fd792e..f800ca4 100644
--- a/test/opt/ir_builder.cpp
+++ b/test/opt/ir_builder.cpp
@@ -198,6 +198,7 @@
 
     BasicBlock& bb_merge = *fn.begin();
 
+    // TODO(1841): Handle id overflow.
     fn.begin().InsertBefore(std::unique_ptr<BasicBlock>(
         new BasicBlock(std::unique_ptr<Instruction>(new Instruction(
             context.get(), SpvOpLabel, 0, context->TakeNextId(), {})))));
@@ -207,6 +208,7 @@
       builder.AddBranch(bb_merge.id());
     }
 
+    // TODO(1841): Handle id overflow.
     fn.begin().InsertBefore(std::unique_ptr<BasicBlock>(
         new BasicBlock(std::unique_ptr<Instruction>(new Instruction(
             context.get(), SpvOpLabel, 0, context->TakeNextId(), {})))));
@@ -404,6 +406,34 @@
   Match(text, context.get());
 }
 
+TEST_F(IRBuilderTest, AccelerationStructureNV) {
+  const std::string text = R"(
+; CHECK: OpTypeAccelerationStructureNV
+OpCapability Shader
+OpCapability RayTracingNV
+OpExtension "SPV_NV_ray_tracing"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %8 "main"
+OpExecutionMode %8 OriginUpperLeft
+%1 = OpTypeVoid
+%2 = OpTypeBool
+%3 = OpTypeAccelerationStructureNV
+%7 = OpTypeFunction %1
+%8 = OpFunction %1 None %7
+%9 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+  EXPECT_NE(nullptr, context);
+
+  InstructionBuilder builder(context.get(),
+                             &*context->module()->begin()->begin()->begin());
+  Match(text, context.get());
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/ir_context_test.cpp b/test/opt/ir_context_test.cpp
index f66b16e..4e2f5b2 100644
--- a/test/opt/ir_context_test.cpp
+++ b/test/opt/ir_context_test.cpp
@@ -567,6 +567,103 @@
   EXPECT_THAT(processed, UnorderedElementsAre(10));
 }
 
+TEST_F(IRContextTest, IdBoundTestAtLimit) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%1 = OpTypeVoid
+%2 = OpTypeFunction %1
+%3 = OpFunction %1 None %2
+%4 = OpLabel
+OpReturn
+OpFunctionEnd)";
+
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  uint32_t current_bound = context->module()->id_bound();
+  context->set_max_id_bound(current_bound);
+  uint32_t next_id_bound = context->TakeNextId();
+  EXPECT_EQ(next_id_bound, 0);
+  EXPECT_EQ(current_bound, context->module()->id_bound());
+  next_id_bound = context->TakeNextId();
+  EXPECT_EQ(next_id_bound, 0);
+}
+
+TEST_F(IRContextTest, IdBoundTestBelowLimit) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%1 = OpTypeVoid
+%2 = OpTypeFunction %1
+%3 = OpFunction %1 None %2
+%4 = OpLabel
+OpReturn
+OpFunctionEnd)";
+
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  uint32_t current_bound = context->module()->id_bound();
+  context->set_max_id_bound(current_bound + 100);
+  uint32_t next_id_bound = context->TakeNextId();
+  EXPECT_EQ(next_id_bound, current_bound);
+  EXPECT_EQ(current_bound + 1, context->module()->id_bound());
+  next_id_bound = context->TakeNextId();
+  EXPECT_EQ(next_id_bound, current_bound + 1);
+}
+
+TEST_F(IRContextTest, IdBoundTestNearLimit) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%1 = OpTypeVoid
+%2 = OpTypeFunction %1
+%3 = OpFunction %1 None %2
+%4 = OpLabel
+OpReturn
+OpFunctionEnd)";
+
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  uint32_t current_bound = context->module()->id_bound();
+  context->set_max_id_bound(current_bound + 1);
+  uint32_t next_id_bound = context->TakeNextId();
+  EXPECT_EQ(next_id_bound, current_bound);
+  EXPECT_EQ(current_bound + 1, context->module()->id_bound());
+  next_id_bound = context->TakeNextId();
+  EXPECT_EQ(next_id_bound, 0);
+}
+
+TEST_F(IRContextTest, IdBoundTestUIntMax) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%1 = OpTypeVoid
+%2 = OpTypeFunction %1
+%3 = OpFunction %1 None %2
+%4294967294 = OpLabel ; ID is UINT_MAX-1
+OpReturn
+OpFunctionEnd)";
+
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  uint32_t current_bound = context->module()->id_bound();
+
+  // Expecting |BuildModule| to preserve the numeric ids.
+  EXPECT_EQ(current_bound, std::numeric_limits<uint32_t>::max());
+
+  context->set_max_id_bound(current_bound);
+  uint32_t next_id_bound = context->TakeNextId();
+  EXPECT_EQ(next_id_bound, 0);
+  EXPECT_EQ(current_bound, context->module()->id_bound());
+}
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/loop_optimizations/hoist_without_preheader.cpp b/test/opt/loop_optimizations/hoist_without_preheader.cpp
index 9a14996..2e34b01 100644
--- a/test/opt/loop_optimizations/hoist_without_preheader.cpp
+++ b/test/opt/loop_optimizations/hoist_without_preheader.cpp
@@ -117,6 +117,81 @@
   SinglePassRunAndMatch<LICMPass>(text, false);
 }
 
+TEST_F(PassClassTest, HoistWithoutPreheaderAtIdBound) {
+  const std::string text = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 440
+OpName %main "main"
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%int_1 = OpConstant %int 1
+%int_2 = OpConstant %int 2
+%int_0 = OpConstant %int 0
+%int_10 = OpConstant %int 10
+%bool = OpTypeBool
+%int_5 = OpConstant %int 5
+%main = OpFunction %void None %4
+%13 = OpLabel
+OpBranch %14
+%14 = OpLabel
+%15 = OpPhi %int %int_0 %13 %16 %17
+OpLoopMerge %25 %17 None
+OpBranch %19
+%19 = OpLabel
+%20 = OpSLessThan %bool %15 %int_10
+OpBranchConditional %20 %21 %25
+%21 = OpLabel
+%22 = OpIEqual %bool %15 %int_5
+OpSelectionMerge %23 None
+OpBranchConditional %22 %24 %23
+%24 = OpLabel
+OpBranch %25
+%23 = OpLabel
+OpBranch %17
+%17 = OpLabel
+%16 = OpIAdd %int %15 %int_1
+OpBranch %14
+%25 = OpLabel
+%26 = OpPhi %int %int_0 %24 %int_0 %19 %27 %28
+%29 = OpPhi %int %int_0 %24 %int_0 %19 %30 %28
+OpLoopMerge %31 %28 None
+OpBranch %32
+%32 = OpLabel
+%33 = OpSLessThan %bool %29 %int_10
+OpBranchConditional %33 %34 %31
+%34 = OpLabel
+%27 = OpIAdd %int %int_1 %int_2
+OpBranch %28
+%28 = OpLabel
+%30 = OpIAdd %int %29 %int_1
+OpBranch %25
+%31 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  uint32_t current_bound = context->module()->id_bound();
+  context->set_max_id_bound(current_bound);
+
+  auto pass = MakeUnique<LICMPass>();
+  auto result = pass->Run(context.get());
+  EXPECT_EQ(result, Pass::Status::Failure);
+
+  std::vector<uint32_t> binary;
+  context->module()->ToBinary(&binary, false);
+  std::string optimized_asm;
+  SpirvTools tools_(SPV_ENV_UNIVERSAL_1_1);
+  tools_.Disassemble(binary, &optimized_asm);
+  std::cout << optimized_asm << std::endl;
+}
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/pass_merge_return_test.cpp b/test/opt/pass_merge_return_test.cpp
index 7f2c058..e49fec9 100644
--- a/test/opt/pass_merge_return_test.cpp
+++ b/test/opt/pass_merge_return_test.cpp
@@ -1145,6 +1145,115 @@
   EXPECT_TRUE(messages.empty());
 }
 
+TEST_F(MergeReturnPassTest, StructuredControlFlowDontChangeEntryPhi) {
+  const std::string before =
+      R"(
+; CHECK: OpFunction %void
+; CHECK: OpLabel
+; CHECK: OpLabel
+; CHECK: [[pre_header:%\w+]] = OpLabel
+; CHECK: [[header:%\w+]] = OpLabel
+; CHECK-NEXT: OpPhi %bool {{%\w+}} [[pre_header]] [[iv:%\w+]] [[continue:%\w+]]
+; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]]
+; CHECK: [[continue]] = OpLabel
+; CHECK-NEXT: [[iv]] = Op
+; CHECK: [[merge]] = OpLabel
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %1 "main"
+       %void = OpTypeVoid
+       %bool = OpTypeBool
+          %4 = OpTypeFunction %void
+          %1 = OpFunction %void None %4
+          %5 = OpLabel
+          %6 = OpUndef %bool
+               OpBranch %7
+          %7 = OpLabel
+          %8 = OpPhi %bool %6 %5 %9 %10
+               OpLoopMerge %11 %10 None
+               OpBranch %12
+         %12 = OpLabel
+         %13 = OpUndef %bool
+               OpSelectionMerge %10 DontFlatten
+               OpBranchConditional %13 %10 %14
+         %14 = OpLabel
+               OpReturn
+         %10 = OpLabel
+          %9 = OpUndef %bool
+               OpBranchConditional %13 %7 %11
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<MergeReturnPass>(before, false);
+}
+
+TEST_F(MergeReturnPassTest, StructuredControlFlowPartialReplacePhi) {
+  const std::string before =
+      R"(
+; CHECK: OpFunction %void
+; CHECK: OpLabel
+; CHECK: OpLabel
+; CHECK: [[pre_header:%\w+]] = OpLabel
+; CHECK: [[header:%\w+]] = OpLabel
+; CHECK-NEXT: OpPhi
+; CHECK-NEXT: OpLoopMerge [[merge:%\w+]]
+; CHECK: OpLabel
+; CHECK: [[old_ret_block:%\w+]] = OpLabel
+; CHECK: [[bb:%\w+]] = OpLabel
+; CHECK-NEXT: [[val:%\w+]] = OpUndef %bool
+; CHECK: [[merge]] = OpLabel
+; CHECK-NEXT: [[phi1:%\w+]] = OpPhi %bool [[val]] [[bb]] {{%\w+}} [[old_ret_block]]
+; CHECK: OpBranchConditional {{%\w+}} {{%\w+}} [[bb2:%\w+]]
+; CHECK: [[bb2]] = OpLabel
+; CHECK: OpBranch [[header2:%\w+]]
+; CHECK: [[header2]] = OpLabel
+; CHECK-NEXT: [[phi2:%\w+]] = OpPhi %bool [[phi1]] [[continue2:%\w+]] [[phi1]] [[bb2]]
+; CHECK-NEXT: OpLoopMerge {{%\w+}} [[continue2]]
+; CHECK: [[continue2]] = OpLabel
+; CHECK-NEXT: OpBranch [[header2]]
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %1 "main"
+       %void = OpTypeVoid
+       %bool = OpTypeBool
+          %4 = OpTypeFunction %void
+          %1 = OpFunction %void None %4
+          %5 = OpLabel
+          %6 = OpUndef %bool
+               OpBranch %7
+          %7 = OpLabel
+          %8 = OpPhi %bool %6 %5 %9 %10
+               OpLoopMerge %11 %10 None
+               OpBranch %12
+         %12 = OpLabel
+         %13 = OpUndef %bool
+               OpSelectionMerge %10 DontFlatten
+               OpBranchConditional %13 %10 %14
+         %14 = OpLabel
+               OpReturn
+         %10 = OpLabel
+          %9 = OpUndef %bool
+               OpBranchConditional %13 %7 %11
+         %11 = OpLabel
+          %phi = OpPhi %bool %9 %10 %9 %cont
+               OpLoopMerge %ret %cont None
+               OpBranch %bb
+         %bb = OpLabel
+               OpBranchConditional %13 %ret %cont
+         %cont = OpLabel
+               OpBranch %11
+         %ret = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<MergeReturnPass>(before, false);
+}
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/type_manager_test.cpp b/test/opt/type_manager_test.cpp
index 07a2ac4..fc9089e 100644
--- a/test/opt/type_manager_test.cpp
+++ b/test/opt/type_manager_test.cpp
@@ -156,7 +156,7 @@
   types.emplace_back(new ReserveId());
   types.emplace_back(new Queue());
 
-  // Pipe, Forward Pointer, PipeStorage, NamedBarrier
+  // Pipe, Forward Pointer, PipeStorage, NamedBarrier, AccelerationStructureNV
   types.emplace_back(new Pipe(SpvAccessQualifierReadWrite));
   types.emplace_back(new Pipe(SpvAccessQualifierReadOnly));
   types.emplace_back(new ForwardPointer(1, SpvStorageClassInput));
@@ -164,6 +164,7 @@
   types.emplace_back(new ForwardPointer(2, SpvStorageClassUniform));
   types.emplace_back(new PipeStorage());
   types.emplace_back(new NamedBarrier());
+  types.emplace_back(new AccelerationStructureNV());
 
   return types;
 }
@@ -1035,6 +1036,7 @@
 ; CHECK: OpTypeForwardPointer [[uniform_ptr]] Uniform
 ; CHECK: OpTypePipeStorage
 ; CHECK: OpTypeNamedBarrier
+; CHECK: OpTypeAccelerationStructureNV
 OpCapability Shader
 OpCapability Int64
 OpCapability Linkage
diff --git a/test/opt/types_test.cpp b/test/opt/types_test.cpp
index c11187e..7426ed7 100644
--- a/test/opt/types_test.cpp
+++ b/test/opt/types_test.cpp
@@ -88,6 +88,7 @@
 TestMultipleInstancesOfTheSameType(ForwardPointer, 10, SpvStorageClassUniform);
 TestMultipleInstancesOfTheSameType(PipeStorage);
 TestMultipleInstancesOfTheSameType(NamedBarrier);
+TestMultipleInstancesOfTheSameType(AccelerationStructureNV);
 #undef TestMultipleInstanceOfTheSameType
 
 std::vector<std::unique_ptr<Type>> GenerateAllTypes() {
diff --git a/test/reduce/CMakeLists.txt b/test/reduce/CMakeLists.txt
index 828bf68..cdd27b9 100644
--- a/test/reduce/CMakeLists.txt
+++ b/test/reduce/CMakeLists.txt
@@ -18,7 +18,10 @@
         reduce_test_util.cpp
         reduce_test_util.h
         reducer_test.cpp
+        remove_opname_instruction_reduction_pass_test.cpp
         remove_unreferenced_instruction_reduction_pass_test.cpp
+        structured_loop_to_selection_reduction_pass_test.cpp
+        validation_during_reduction_test.cpp
         LIBS SPIRV-Tools-reduce
         )
 
diff --git a/test/reduce/reduce_test_util.cpp b/test/reduce/reduce_test_util.cpp
index 022e7e3..19ef749 100644
--- a/test/reduce/reduce_test_util.cpp
+++ b/test/reduce/reduce_test_util.cpp
@@ -48,5 +48,25 @@
   CheckEqual(env, expected_text, actual_binary);
 }
 
+void CheckValid(spv_target_env env, const opt::IRContext* ir) {
+  std::vector<uint32_t> binary;
+  ir->module()->ToBinary(&binary, false);
+  SpirvTools t(env);
+  ASSERT_TRUE(t.Validate(binary));
+}
+
+std::string ToString(spv_target_env env, const opt::IRContext* ir) {
+  std::vector<uint32_t> binary;
+  ir->module()->ToBinary(&binary, false);
+  SpirvTools t(env);
+  std::string result;
+  t.Disassemble(binary, &result, kReduceDisassembleOption);
+  return result;
+}
+
+void NopDiagnostic(spv_message_level_t /*level*/, const char* /*source*/,
+                   const spv_position_t& /*position*/,
+                   const char* /*message*/) {}
+
 }  // namespace reduce
 }  // namespace spvtools
diff --git a/test/reduce/reduce_test_util.h b/test/reduce/reduce_test_util.h
index 0331799..499c774 100644
--- a/test/reduce/reduce_test_util.h
+++ b/test/reduce/reduce_test_util.h
@@ -56,6 +56,14 @@
 void CheckEqual(spv_target_env env, const std::string& expected_text,
                 const opt::IRContext* actual_ir);
 
+// Assembles the given IR context and checks whether the resulting binary is
+// valid.
+void CheckValid(spv_target_env env, const opt::IRContext* ir);
+
+// Assembles the given IR context, then returns its disassembly as a string.
+// Useful for debugging.
+std::string ToString(spv_target_env env, const opt::IRContext* ir);
+
 // Assembly options for writing reduction tests.  It simplifies matters if
 // numeric ids do not change.
 const uint32_t kReduceAssembleOption =
@@ -64,6 +72,10 @@
 const uint32_t kReduceDisassembleOption =
     SPV_BINARY_TO_TEXT_OPTION_NO_HEADER | SPV_BINARY_TO_TEXT_OPTION_INDENT;
 
+// Don't print reducer info during testing.
+void NopDiagnostic(spv_message_level_t /*level*/, const char* /*source*/,
+                   const spv_position_t& /*position*/, const char* /*message*/);
+
 }  // namespace reduce
 }  // namespace spvtools
 
diff --git a/test/reduce/reducer_test.cpp b/test/reduce/reducer_test.cpp
index 5441a56..88fc5e4 100644
--- a/test/reduce/reducer_test.cpp
+++ b/test/reduce/reducer_test.cpp
@@ -16,18 +16,14 @@
 
 #include "source/reduce/operand_to_const_reduction_pass.h"
 #include "source/reduce/reducer.h"
+#include "source/reduce/remove_opname_instruction_reduction_pass.h"
 #include "source/reduce/remove_unreferenced_instruction_reduction_pass.h"
 
 namespace spvtools {
 namespace reduce {
 namespace {
 
-// Don't print reducer info during testing.
-void NopDiagnostic(spv_message_level_t /*level*/, const char* /*source*/,
-                   const spv_position_t& /*position*/,
-                   const char* /*message*/) {}
-
-// This changes is its mind each time IsInteresting is invoked as to whether the
+// This changes its mind each time IsInteresting is invoked as to whether the
 // binary is interesting, until some limit is reached after which the binary is
 // always deemed interesting.  This is useful to test that reduction passes
 // interleave in interesting ways for a while, and then always succeed after
@@ -238,6 +234,77 @@
   CheckEqual(env, expected, binary_out);
 }
 
+TEST(ReducerTest, RemoveOpnameAndRemoveUnreferenced) {
+  const std::string original = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+               OpName %3 "a"
+               OpName %4 "this-name-counts-as-usage-for-load-instruction"
+          %5 = OpTypeVoid
+          %6 = OpTypeFunction %5
+          %7 = OpTypeFloat 32
+          %8 = OpTypePointer Function %7
+          %9 = OpConstant %7 1
+          %2 = OpFunction %5 None %6
+         %10 = OpLabel
+          %3 = OpVariable %8 Function
+          %4 = OpLoad %7 %3
+               OpStore %3 %7
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const std::string expected = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %5 = OpTypeVoid
+          %6 = OpTypeFunction %5
+          %7 = OpTypeFloat 32
+          %8 = OpTypePointer Function %7
+          %9 = OpConstant %7 1
+          %2 = OpFunction %5 None %6
+         %10 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
+  Reducer reducer(env);
+  // Make ping-pong interesting very quickly, as there are not much
+  // opportunities.
+  PingPongInteresting ping_pong_interesting(1);
+  reducer.SetMessageConsumer(NopDiagnostic);
+  reducer.SetInterestingnessFunction(
+      [&](const std::vector<uint32_t>& binary, uint32_t) -> bool {
+        return ping_pong_interesting.IsInteresting(binary);
+      });
+  reducer.AddReductionPass(
+      MakeUnique<RemoveOpNameInstructionReductionPass>(env));
+  reducer.AddReductionPass(
+      MakeUnique<RemoveUnreferencedInstructionReductionPass>(env));
+
+  std::vector<uint32_t> binary_in;
+  SpirvTools t(env);
+
+  ASSERT_TRUE(t.Assemble(original, &binary_in, kReduceAssembleOption));
+  std::vector<uint32_t> binary_out;
+  spvtools::ReducerOptions reducer_options;
+  reducer_options.set_step_limit(500);
+
+  reducer.Run(std::move(binary_in), &binary_out, reducer_options);
+
+  CheckEqual(env, expected, binary_out);
+}
+
 }  // namespace
 }  // namespace reduce
 }  // namespace spvtools
\ No newline at end of file
diff --git a/test/reduce/remove_opname_instruction_reduction_pass_test.cpp b/test/reduce/remove_opname_instruction_reduction_pass_test.cpp
new file mode 100644
index 0000000..38a2d7f
--- /dev/null
+++ b/test/reduce/remove_opname_instruction_reduction_pass_test.cpp
@@ -0,0 +1,216 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "reduce_test_util.h"
+
+#include "source/opt/build_module.h"
+#include "source/reduce/reduction_opportunity.h"
+#include "source/reduce/remove_opname_instruction_reduction_pass.h"
+
+namespace spvtools {
+namespace reduce {
+namespace {
+
+TEST(RemoveOpnameInstructionReductionPassTest, NothingToRemove) {
+  const std::string source = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, source, kReduceAssembleOption);
+  const auto pass = TestSubclass<RemoveOpNameInstructionReductionPass>(env);
+  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(RemoveOpnameInstructionReductionPassTest, RemoveSingleOpName) {
+  const std::string prologue = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+  )";
+
+  const std::string epilogue = R"(
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const std::string original = prologue + R"(
+               OpName %4 "main"
+  )" + epilogue;
+
+  const std::string expected = prologue + epilogue;
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, original, kReduceAssembleOption);
+  const auto pass = TestSubclass<RemoveOpNameInstructionReductionPass>(env);
+  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  ASSERT_EQ(1, ops.size());
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+
+  CheckEqual(env, expected, context.get());
+}
+
+TEST(RemoveOpnameInstructionReductionPassTest, TryApplyRemovesAllOpName) {
+  const std::string prologue = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+  )";
+
+  const std::string epilogue = R"(
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %11 = OpVariable %7 Function
+         %12 = OpVariable %7 Function
+               OpStore %8 %9
+               OpStore %10 %9
+               OpStore %11 %9
+               OpStore %12 %9
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const std::string original = prologue + R"(
+               OpName %4 "main"
+               OpName %8 "a"
+               OpName %10 "b"
+               OpName %11 "c"
+               OpName %12 "d"
+  )" + epilogue;
+
+  const std::string expected = prologue + epilogue;
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  auto pass = TestSubclass<RemoveOpNameInstructionReductionPass>(env);
+
+  {
+    // Check the right number of opportunities is detected
+    const auto consumer = nullptr;
+    const auto context =
+        BuildModule(env, consumer, original, kReduceAssembleOption);
+    const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+    ASSERT_EQ(5, ops.size());
+  }
+
+  {
+    // The reduction should remove all OpName
+    std::vector<uint32_t> binary;
+    SpirvTools t(env);
+    ASSERT_TRUE(t.Assemble(original, &binary, kReduceAssembleOption));
+    auto reduced_binary = pass.TryApplyReduction(binary);
+    CheckEqual(env, expected, reduced_binary);
+  }
+}
+
+TEST(RemoveOpnameInstructionReductionPassTest,
+     TryApplyRemovesAllOpNameAndOpMemberName) {
+  const std::string prologue = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+  )";
+
+  const std::string epilogue = R"(
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeInt 32 1
+          %8 = OpTypeVector %6 3
+          %9 = OpTypeStruct %6 %7 %8
+         %10 = OpTypePointer Function %9
+         %12 = OpConstant %7 0
+         %13 = OpConstant %6 1
+         %14 = OpTypePointer Function %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %11 = OpVariable %10 Function
+         %15 = OpAccessChain %14 %11 %12
+               OpStore %15 %13
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const std::string original = prologue + R"(
+               OpName %4 "main"
+               OpName %9 "S"
+               OpMemberName %9 0 "f"
+               OpMemberName %9 1 "i"
+               OpMemberName %9 2 "v"
+               OpName %11 "s"
+  )" + epilogue;
+
+  const std::string expected = prologue + epilogue;
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  auto pass = TestSubclass<RemoveOpNameInstructionReductionPass>(env);
+
+  {
+    // Check the right number of opportunities is detected
+    const auto consumer = nullptr;
+    const auto context =
+        BuildModule(env, consumer, original, kReduceAssembleOption);
+    const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+    ASSERT_EQ(6, ops.size());
+  }
+
+  {
+    // The reduction should remove all OpName
+    std::vector<uint32_t> binary;
+    SpirvTools t(env);
+    ASSERT_TRUE(t.Assemble(original, &binary, kReduceAssembleOption));
+    auto reduced_binary = pass.TryApplyReduction(binary);
+    CheckEqual(env, expected, reduced_binary);
+  }
+}
+
+}  // namespace
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/test/reduce/structured_loop_to_selection_reduction_pass_test.cpp b/test/reduce/structured_loop_to_selection_reduction_pass_test.cpp
new file mode 100644
index 0000000..8388cb2
--- /dev/null
+++ b/test/reduce/structured_loop_to_selection_reduction_pass_test.cpp
@@ -0,0 +1,3440 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/structured_loop_to_selection_reduction_pass.h"
+#include "reduce_test_util.h"
+#include "source/opt/build_module.h"
+
+namespace spvtools {
+namespace reduce {
+namespace {
+
+TEST(StructuredLoopToSelectionReductionPassTest, LoopyShader1) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 0
+         %16 = OpConstant %6 100
+         %17 = OpTypeBool
+         %20 = OpConstant %6 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpStore %8 %9
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %12 %13 None
+               OpBranch %14
+         %14 = OpLabel
+         %15 = OpLoad %6 %8
+         %18 = OpSLessThan %17 %15 %16
+               OpBranchConditional %18 %11 %12
+         %11 = OpLabel
+               OpBranch %13
+         %13 = OpLabel
+         %19 = OpLoad %6 %8
+         %21 = OpIAdd %6 %19 %20
+               OpStore %8 %21
+               OpBranch %10
+         %12 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+  CheckValid(env, context.get());
+
+  std::string after_op_0 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 0
+         %16 = OpConstant %6 100
+         %17 = OpTypeBool
+         %20 = OpConstant %6 1
+         %22 = OpConstantTrue %17
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpStore %8 %9
+               OpBranch %10
+         %10 = OpLabel
+               OpSelectionMerge %12 None
+               OpBranchConditional %22 %14 %12
+         %14 = OpLabel
+         %15 = OpLoad %6 %8
+         %18 = OpSLessThan %17 %15 %16
+               OpBranchConditional %18 %11 %12
+         %11 = OpLabel
+               OpBranch %12
+         %13 = OpLabel
+         %19 = OpLoad %6 %8
+         %21 = OpIAdd %6 %19 %20
+               OpStore %8 %21
+               OpBranch %10
+         %12 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  CheckEqual(env, after_op_0, context.get());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest, LoopyShader2) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 0
+         %16 = OpConstant %6 100
+         %17 = OpTypeBool
+         %28 = OpConstant %6 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %19 = OpVariable %7 Function
+         %32 = OpVariable %7 Function
+         %40 = OpVariable %7 Function
+               OpStore %8 %9
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %12 %13 None
+               OpBranch %14
+         %14 = OpLabel
+         %15 = OpLoad %6 %8
+         %18 = OpSLessThan %17 %15 %16
+               OpBranchConditional %18 %11 %12
+         %11 = OpLabel
+               OpStore %19 %9
+               OpBranch %20
+         %20 = OpLabel
+               OpLoopMerge %22 %23 None
+               OpBranch %24
+         %24 = OpLabel
+         %25 = OpLoad %6 %19
+         %26 = OpSLessThan %17 %25 %16
+               OpBranchConditional %26 %21 %22
+         %21 = OpLabel
+               OpBranch %23
+         %23 = OpLabel
+         %27 = OpLoad %6 %19
+         %29 = OpIAdd %6 %27 %28
+               OpStore %19 %29
+               OpBranch %20
+         %22 = OpLabel
+               OpBranch %13
+         %13 = OpLabel
+         %30 = OpLoad %6 %8
+         %31 = OpIAdd %6 %30 %28
+               OpStore %8 %31
+               OpBranch %10
+         %12 = OpLabel
+               OpStore %32 %9
+               OpBranch %33
+         %33 = OpLabel
+               OpLoopMerge %35 %36 None
+               OpBranch %37
+         %37 = OpLabel
+         %38 = OpLoad %6 %32
+         %39 = OpSLessThan %17 %38 %16
+               OpBranchConditional %39 %34 %35
+         %34 = OpLabel
+               OpStore %40 %9
+               OpBranch %41
+         %41 = OpLabel
+               OpLoopMerge %43 %44 None
+               OpBranch %45
+         %45 = OpLabel
+         %46 = OpLoad %6 %40
+         %47 = OpSLessThan %17 %46 %16
+               OpBranchConditional %47 %42 %43
+         %42 = OpLabel
+               OpBranch %44
+         %44 = OpLabel
+         %48 = OpLoad %6 %40
+         %49 = OpIAdd %6 %48 %28
+               OpStore %40 %49
+               OpBranch %41
+         %43 = OpLabel
+               OpBranch %36
+         %36 = OpLabel
+         %50 = OpLoad %6 %32
+         %51 = OpIAdd %6 %50 %28
+               OpStore %32 %51
+               OpBranch %33
+         %35 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  ASSERT_EQ(4, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+  CheckValid(env, context.get());
+  std::string after_op_0 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 0
+         %16 = OpConstant %6 100
+         %17 = OpTypeBool
+         %28 = OpConstant %6 1
+         %52 = OpConstantTrue %17
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %19 = OpVariable %7 Function
+         %32 = OpVariable %7 Function
+         %40 = OpVariable %7 Function
+               OpStore %8 %9
+               OpBranch %10
+         %10 = OpLabel
+               OpSelectionMerge %12 None
+               OpBranchConditional %52 %14 %12
+         %14 = OpLabel
+         %15 = OpLoad %6 %8
+         %18 = OpSLessThan %17 %15 %16
+               OpBranchConditional %18 %11 %12
+         %11 = OpLabel
+               OpStore %19 %9
+               OpBranch %20
+         %20 = OpLabel
+               OpLoopMerge %22 %23 None
+               OpBranch %24
+         %24 = OpLabel
+         %25 = OpLoad %6 %19
+         %26 = OpSLessThan %17 %25 %16
+               OpBranchConditional %26 %21 %22
+         %21 = OpLabel
+               OpBranch %23
+         %23 = OpLabel
+         %27 = OpLoad %6 %19
+         %29 = OpIAdd %6 %27 %28
+               OpStore %19 %29
+               OpBranch %20
+         %22 = OpLabel
+               OpBranch %12
+         %13 = OpLabel
+         %30 = OpLoad %6 %8
+         %31 = OpIAdd %6 %30 %28
+               OpStore %8 %31
+               OpBranch %10
+         %12 = OpLabel
+               OpStore %32 %9
+               OpBranch %33
+         %33 = OpLabel
+               OpLoopMerge %35 %36 None
+               OpBranch %37
+         %37 = OpLabel
+         %38 = OpLoad %6 %32
+         %39 = OpSLessThan %17 %38 %16
+               OpBranchConditional %39 %34 %35
+         %34 = OpLabel
+               OpStore %40 %9
+               OpBranch %41
+         %41 = OpLabel
+               OpLoopMerge %43 %44 None
+               OpBranch %45
+         %45 = OpLabel
+         %46 = OpLoad %6 %40
+         %47 = OpSLessThan %17 %46 %16
+               OpBranchConditional %47 %42 %43
+         %42 = OpLabel
+               OpBranch %44
+         %44 = OpLabel
+         %48 = OpLoad %6 %40
+         %49 = OpIAdd %6 %48 %28
+               OpStore %40 %49
+               OpBranch %41
+         %43 = OpLabel
+               OpBranch %36
+         %36 = OpLabel
+         %50 = OpLoad %6 %32
+         %51 = OpIAdd %6 %50 %28
+               OpStore %32 %51
+               OpBranch %33
+         %35 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  CheckEqual(env, after_op_0, context.get());
+
+  ASSERT_TRUE(ops[1]->PreconditionHolds());
+  ops[1]->TryToApply();
+  CheckValid(env, context.get());
+  std::string after_op_1 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 0
+         %16 = OpConstant %6 100
+         %17 = OpTypeBool
+         %28 = OpConstant %6 1
+         %52 = OpConstantTrue %17
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %19 = OpVariable %7 Function
+         %32 = OpVariable %7 Function
+         %40 = OpVariable %7 Function
+               OpStore %8 %9
+               OpBranch %10
+         %10 = OpLabel
+               OpSelectionMerge %12 None
+               OpBranchConditional %52 %14 %12
+         %14 = OpLabel
+         %15 = OpLoad %6 %8
+         %18 = OpSLessThan %17 %15 %16
+               OpBranchConditional %18 %11 %12
+         %11 = OpLabel
+               OpStore %19 %9
+               OpBranch %20
+         %20 = OpLabel
+               OpSelectionMerge %22 None
+               OpBranchConditional %52 %24 %22
+         %24 = OpLabel
+         %25 = OpLoad %6 %19
+         %26 = OpSLessThan %17 %25 %16
+               OpBranchConditional %26 %21 %22
+         %21 = OpLabel
+               OpBranch %22
+         %23 = OpLabel
+         %27 = OpLoad %6 %19
+         %29 = OpIAdd %6 %27 %28
+               OpStore %19 %29
+               OpBranch %20
+         %22 = OpLabel
+               OpBranch %12
+         %13 = OpLabel
+         %30 = OpLoad %6 %8
+         %31 = OpIAdd %6 %30 %28
+               OpStore %8 %31
+               OpBranch %10
+         %12 = OpLabel
+               OpStore %32 %9
+               OpBranch %33
+         %33 = OpLabel
+               OpLoopMerge %35 %36 None
+               OpBranch %37
+         %37 = OpLabel
+         %38 = OpLoad %6 %32
+         %39 = OpSLessThan %17 %38 %16
+               OpBranchConditional %39 %34 %35
+         %34 = OpLabel
+               OpStore %40 %9
+               OpBranch %41
+         %41 = OpLabel
+               OpLoopMerge %43 %44 None
+               OpBranch %45
+         %45 = OpLabel
+         %46 = OpLoad %6 %40
+         %47 = OpSLessThan %17 %46 %16
+               OpBranchConditional %47 %42 %43
+         %42 = OpLabel
+               OpBranch %44
+         %44 = OpLabel
+         %48 = OpLoad %6 %40
+         %49 = OpIAdd %6 %48 %28
+               OpStore %40 %49
+               OpBranch %41
+         %43 = OpLabel
+               OpBranch %36
+         %36 = OpLabel
+         %50 = OpLoad %6 %32
+         %51 = OpIAdd %6 %50 %28
+               OpStore %32 %51
+               OpBranch %33
+         %35 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  CheckEqual(env, after_op_1, context.get());
+
+  ASSERT_TRUE(ops[2]->PreconditionHolds());
+  ops[2]->TryToApply();
+  CheckValid(env, context.get());
+  std::string after_op_2 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 0
+         %16 = OpConstant %6 100
+         %17 = OpTypeBool
+         %28 = OpConstant %6 1
+         %52 = OpConstantTrue %17
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %19 = OpVariable %7 Function
+         %32 = OpVariable %7 Function
+         %40 = OpVariable %7 Function
+               OpStore %8 %9
+               OpBranch %10
+         %10 = OpLabel
+               OpSelectionMerge %12 None
+               OpBranchConditional %52 %14 %12
+         %14 = OpLabel
+         %15 = OpLoad %6 %8
+         %18 = OpSLessThan %17 %15 %16
+               OpBranchConditional %18 %11 %12
+         %11 = OpLabel
+               OpStore %19 %9
+               OpBranch %20
+         %20 = OpLabel
+               OpSelectionMerge %22 None
+               OpBranchConditional %52 %24 %22
+         %24 = OpLabel
+         %25 = OpLoad %6 %19
+         %26 = OpSLessThan %17 %25 %16
+               OpBranchConditional %26 %21 %22
+         %21 = OpLabel
+               OpBranch %22
+         %23 = OpLabel
+         %27 = OpLoad %6 %19
+         %29 = OpIAdd %6 %27 %28
+               OpStore %19 %29
+               OpBranch %20
+         %22 = OpLabel
+               OpBranch %12
+         %13 = OpLabel
+         %30 = OpLoad %6 %8
+         %31 = OpIAdd %6 %30 %28
+               OpStore %8 %31
+               OpBranch %10
+         %12 = OpLabel
+               OpStore %32 %9
+               OpBranch %33
+         %33 = OpLabel
+               OpSelectionMerge %35 None
+               OpBranchConditional %52 %37 %35
+         %37 = OpLabel
+         %38 = OpLoad %6 %32
+         %39 = OpSLessThan %17 %38 %16
+               OpBranchConditional %39 %34 %35
+         %34 = OpLabel
+               OpStore %40 %9
+               OpBranch %41
+         %41 = OpLabel
+               OpLoopMerge %43 %44 None
+               OpBranch %45
+         %45 = OpLabel
+         %46 = OpLoad %6 %40
+         %47 = OpSLessThan %17 %46 %16
+               OpBranchConditional %47 %42 %43
+         %42 = OpLabel
+               OpBranch %44
+         %44 = OpLabel
+         %48 = OpLoad %6 %40
+         %49 = OpIAdd %6 %48 %28
+               OpStore %40 %49
+               OpBranch %41
+         %43 = OpLabel
+               OpBranch %35
+         %36 = OpLabel
+         %50 = OpLoad %6 %32
+         %51 = OpIAdd %6 %50 %28
+               OpStore %32 %51
+               OpBranch %33
+         %35 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  CheckEqual(env, after_op_2, context.get());
+
+  ASSERT_TRUE(ops[3]->PreconditionHolds());
+  ops[3]->TryToApply();
+  CheckValid(env, context.get());
+  std::string after_op_3 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 0
+         %16 = OpConstant %6 100
+         %17 = OpTypeBool
+         %28 = OpConstant %6 1
+         %52 = OpConstantTrue %17
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %19 = OpVariable %7 Function
+         %32 = OpVariable %7 Function
+         %40 = OpVariable %7 Function
+               OpStore %8 %9
+               OpBranch %10
+         %10 = OpLabel
+               OpSelectionMerge %12 None
+               OpBranchConditional %52 %14 %12
+         %14 = OpLabel
+         %15 = OpLoad %6 %8
+         %18 = OpSLessThan %17 %15 %16
+               OpBranchConditional %18 %11 %12
+         %11 = OpLabel
+               OpStore %19 %9
+               OpBranch %20
+         %20 = OpLabel
+               OpSelectionMerge %22 None
+               OpBranchConditional %52 %24 %22
+         %24 = OpLabel
+         %25 = OpLoad %6 %19
+         %26 = OpSLessThan %17 %25 %16
+               OpBranchConditional %26 %21 %22
+         %21 = OpLabel
+               OpBranch %22
+         %23 = OpLabel
+         %27 = OpLoad %6 %19
+         %29 = OpIAdd %6 %27 %28
+               OpStore %19 %29
+               OpBranch %20
+         %22 = OpLabel
+               OpBranch %12
+         %13 = OpLabel
+         %30 = OpLoad %6 %8
+         %31 = OpIAdd %6 %30 %28
+               OpStore %8 %31
+               OpBranch %10
+         %12 = OpLabel
+               OpStore %32 %9
+               OpBranch %33
+         %33 = OpLabel
+               OpSelectionMerge %35 None
+               OpBranchConditional %52 %37 %35
+         %37 = OpLabel
+         %38 = OpLoad %6 %32
+         %39 = OpSLessThan %17 %38 %16
+               OpBranchConditional %39 %34 %35
+         %34 = OpLabel
+               OpStore %40 %9
+               OpBranch %41
+         %41 = OpLabel
+               OpSelectionMerge %43 None
+               OpBranchConditional %52 %45 %43
+         %45 = OpLabel
+         %46 = OpLoad %6 %40
+         %47 = OpSLessThan %17 %46 %16
+               OpBranchConditional %47 %42 %43
+         %42 = OpLabel
+               OpBranch %43
+         %44 = OpLabel
+         %48 = OpLoad %6 %40
+         %49 = OpIAdd %6 %48 %28
+               OpStore %40 %49
+               OpBranch %41
+         %43 = OpLabel
+               OpBranch %35
+         %36 = OpLabel
+         %50 = OpLoad %6 %32
+         %51 = OpIAdd %6 %50 %28
+               OpStore %32 %51
+               OpBranch %33
+         %35 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  CheckEqual(env, after_op_3, context.get());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest, LoopyShader3) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 10
+         %16 = OpConstant %6 0
+         %17 = OpTypeBool
+         %20 = OpConstant %6 1
+         %23 = OpConstant %6 3
+         %40 = OpConstant %6 5
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpStore %8 %9
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %12 %13 None
+               OpBranch %14
+         %14 = OpLabel
+         %15 = OpLoad %6 %8
+         %18 = OpSGreaterThan %17 %15 %16
+               OpBranchConditional %18 %11 %12
+         %11 = OpLabel
+         %19 = OpLoad %6 %8
+         %21 = OpISub %6 %19 %20
+               OpStore %8 %21
+         %22 = OpLoad %6 %8
+         %24 = OpSLessThan %17 %22 %23
+               OpSelectionMerge %26 None
+               OpBranchConditional %24 %25 %26
+         %25 = OpLabel
+               OpBranch %13
+         %26 = OpLabel
+               OpBranch %28
+         %28 = OpLabel
+               OpLoopMerge %30 %31 None
+               OpBranch %29
+         %29 = OpLabel
+         %32 = OpLoad %6 %8
+         %33 = OpISub %6 %32 %20
+               OpStore %8 %33
+         %34 = OpLoad %6 %8
+         %35 = OpIEqual %17 %34 %20
+               OpSelectionMerge %37 None
+               OpBranchConditional %35 %36 %37
+         %36 = OpLabel
+               OpReturn ; This return spoils everything: it means the merge does not post-dominate the header.
+         %37 = OpLabel
+               OpBranch %31
+         %31 = OpLabel
+         %39 = OpLoad %6 %8
+         %41 = OpSGreaterThan %17 %39 %40
+               OpBranchConditional %41 %28 %30
+         %30 = OpLabel
+               OpBranch %13
+         %13 = OpLabel
+               OpBranch %10
+         %12 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest, LoopyShader4) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %8 = OpTypeFunction %6 %7
+         %13 = OpConstant %6 0
+         %22 = OpTypeBool
+         %25 = OpConstant %6 1
+         %39 = OpConstant %6 100
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %45 = OpVariable %7 Function
+         %46 = OpVariable %7 Function
+         %47 = OpVariable %7 Function
+         %32 = OpVariable %7 Function
+         %42 = OpVariable %7 Function
+               OpStore %32 %13
+               OpBranch %33
+         %33 = OpLabel
+               OpLoopMerge %35 %36 None
+               OpBranch %37
+         %37 = OpLabel
+         %38 = OpLoad %6 %32
+         %40 = OpSLessThan %22 %38 %39
+               OpBranchConditional %40 %34 %35
+         %34 = OpLabel
+               OpBranch %36
+         %36 = OpLabel
+         %41 = OpLoad %6 %32
+               OpStore %42 %25
+               OpStore %45 %13
+               OpStore %46 %13
+               OpBranch %48
+         %48 = OpLabel
+               OpLoopMerge %49 %50 None
+               OpBranch %51
+         %51 = OpLabel
+         %52 = OpLoad %6 %46
+         %53 = OpLoad %6 %42
+         %54 = OpSLessThan %22 %52 %53
+               OpBranchConditional %54 %55 %49
+         %55 = OpLabel
+         %56 = OpLoad %6 %45
+         %57 = OpIAdd %6 %56 %25
+               OpStore %45 %57
+               OpBranch %50
+         %50 = OpLabel
+         %58 = OpLoad %6 %46
+         %59 = OpIAdd %6 %58 %25
+               OpStore %46 %59
+               OpBranch %48
+         %49 = OpLabel
+         %60 = OpLoad %6 %45
+               OpStore %47 %60
+         %43 = OpLoad %6 %47
+         %44 = OpIAdd %6 %41 %43
+               OpStore %32 %44
+               OpBranch %33
+         %35 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+
+  // Initially there are two opportunities.
+  ASSERT_EQ(2, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+  CheckValid(env, context.get());
+  std::string after_op_0 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %8 = OpTypeFunction %6 %7
+         %13 = OpConstant %6 0
+         %22 = OpTypeBool
+         %25 = OpConstant %6 1
+         %39 = OpConstant %6 100
+         %61 = OpConstantTrue %22
+         %62 = OpUndef %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %45 = OpVariable %7 Function
+         %46 = OpVariable %7 Function
+         %47 = OpVariable %7 Function
+         %32 = OpVariable %7 Function
+         %42 = OpVariable %7 Function
+               OpStore %32 %13
+               OpBranch %33
+         %33 = OpLabel
+               OpSelectionMerge %35 None
+               OpBranchConditional %61 %37 %35
+         %37 = OpLabel
+         %38 = OpLoad %6 %32
+         %40 = OpSLessThan %22 %38 %39
+               OpBranchConditional %40 %34 %35
+         %34 = OpLabel
+               OpBranch %35
+         %36 = OpLabel
+         %41 = OpLoad %6 %32
+               OpStore %42 %25
+               OpStore %45 %13
+               OpStore %46 %13
+               OpBranch %48
+         %48 = OpLabel
+               OpLoopMerge %49 %50 None
+               OpBranch %51
+         %51 = OpLabel
+         %52 = OpLoad %6 %46
+         %53 = OpLoad %6 %42
+         %54 = OpSLessThan %22 %52 %53
+               OpBranchConditional %54 %55 %49
+         %55 = OpLabel
+         %56 = OpLoad %6 %45
+         %57 = OpIAdd %6 %56 %25
+               OpStore %45 %57
+               OpBranch %50
+         %50 = OpLabel
+         %58 = OpLoad %6 %46
+         %59 = OpIAdd %6 %58 %25
+               OpStore %46 %59
+               OpBranch %48
+         %49 = OpLabel
+         %60 = OpLoad %6 %45
+               OpStore %47 %60
+         %43 = OpLoad %6 %47
+         %44 = OpIAdd %6 %62 %43
+               OpStore %32 %44
+               OpBranch %33
+         %35 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  CheckEqual(env, after_op_0, context.get());
+
+  // Applying the first opportunity has killed the second opportunity, because
+  // there was a loop embedded in the continue target of the loop we have just
+  // eliminated; the continue-embedded loop is now unreachable.
+  ASSERT_FALSE(ops[1]->PreconditionHolds());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest, ConditionalBreak1) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %10 = OpTypeBool
+         %11 = OpConstantFalse %10
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpBranch %6
+          %6 = OpLabel
+               OpLoopMerge %8 %9 None
+               OpBranch %7
+          %7 = OpLabel
+               OpSelectionMerge %13 None
+               OpBranchConditional %11 %12 %13
+         %12 = OpLabel
+               OpBranch %8
+         %13 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+               OpBranchConditional %11 %6 %8
+          %8 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+  CheckValid(env, context.get());
+  std::string after_op_0 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %10 = OpTypeBool
+         %11 = OpConstantFalse %10
+         %14 = OpConstantTrue %10
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpBranch %6
+          %6 = OpLabel
+               OpSelectionMerge %8 None
+               OpBranchConditional %14 %7 %8
+          %7 = OpLabel
+               OpSelectionMerge %13 None
+               OpBranchConditional %11 %12 %13
+         %12 = OpLabel
+               OpBranch %13
+         %13 = OpLabel
+               OpBranch %8
+          %9 = OpLabel
+               OpBranchConditional %11 %6 %8
+          %8 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  CheckEqual(env, after_op_0, context.get());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest, ConditionalBreak2) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %10 = OpTypeBool
+         %11 = OpConstantFalse %10
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpBranch %6
+          %6 = OpLabel
+               OpLoopMerge %8 %9 None
+               OpBranch %7
+          %7 = OpLabel
+               OpSelectionMerge %13 None
+               OpBranchConditional %11 %8 %13
+         %13 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+               OpBranchConditional %11 %6 %8
+          %8 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+  CheckValid(env, context.get());
+  std::string after_op_0 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %10 = OpTypeBool
+         %11 = OpConstantFalse %10
+         %14 = OpConstantTrue %10
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpBranch %6
+          %6 = OpLabel
+               OpSelectionMerge %8 None
+               OpBranchConditional %14 %7 %8
+          %7 = OpLabel
+               OpSelectionMerge %13 None
+               OpBranchConditional %11 %13 %13
+         %13 = OpLabel
+               OpBranch %8
+          %9 = OpLabel
+               OpBranchConditional %11 %6 %8
+          %8 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  CheckEqual(env, after_op_0, context.get());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest, UnconditionalBreak) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpBranch %6
+          %6 = OpLabel
+               OpLoopMerge %8 %9 None
+               OpBranch %7
+          %7 = OpLabel
+               OpBranch %8
+          %9 = OpLabel
+               OpBranch %6
+          %8 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+  CheckValid(env, context.get());
+  std::string after_op_0 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %10 = OpTypeBool
+         %11 = OpConstantTrue %10
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpBranch %6
+          %6 = OpLabel
+               OpSelectionMerge %8 None
+               OpBranchConditional %11 %7 %8
+          %7 = OpLabel
+               OpBranch %8
+          %9 = OpLabel
+               OpBranch %6
+          %8 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  CheckEqual(env, after_op_0, context.get());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest, Complex) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main" %3
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpMemberDecorate %4 0 Offset 0
+               OpMemberDecorate %4 1 Offset 4
+               OpMemberDecorate %4 2 Offset 8
+               OpMemberDecorate %4 3 Offset 12
+               OpDecorate %4 Block
+               OpDecorate %5 DescriptorSet 0
+               OpDecorate %5 Binding 0
+               OpDecorate %3 Location 0
+          %6 = OpTypeVoid
+          %7 = OpTypeFunction %6
+          %8 = OpTypeBool
+          %9 = OpTypePointer Function %8
+         %10 = OpTypeInt 32 1
+          %4 = OpTypeStruct %10 %10 %10 %10
+         %11 = OpTypePointer Uniform %4
+          %5 = OpVariable %11 Uniform
+         %12 = OpConstant %10 0
+         %13 = OpTypePointer Uniform %10
+         %14 = OpTypeInt 32 0
+         %15 = OpConstant %14 0
+         %16 = OpConstant %10 1
+         %17 = OpConstant %10 2
+         %18 = OpConstant %10 3
+         %19 = OpTypePointer Function %10
+         %20 = OpConstantFalse %8
+         %21 = OpTypeFloat 32
+         %22 = OpTypeVector %21 4
+         %23 = OpTypePointer Output %22
+          %3 = OpVariable %23 Output
+          %2 = OpFunction %6 None %7
+         %24 = OpLabel
+         %25 = OpVariable %9 Function
+         %26 = OpVariable %9 Function
+         %27 = OpVariable %9 Function
+         %28 = OpVariable %9 Function
+         %29 = OpVariable %9 Function
+         %30 = OpVariable %19 Function
+         %31 = OpAccessChain %13 %5 %12
+         %32 = OpLoad %10 %31
+         %33 = OpINotEqual %8 %32 %15
+               OpStore %25 %33
+         %34 = OpAccessChain %13 %5 %16
+         %35 = OpLoad %10 %34
+         %36 = OpINotEqual %8 %35 %15
+               OpStore %26 %36
+         %37 = OpAccessChain %13 %5 %17
+         %38 = OpLoad %10 %37
+         %39 = OpINotEqual %8 %38 %15
+               OpStore %27 %39
+         %40 = OpAccessChain %13 %5 %18
+         %41 = OpLoad %10 %40
+         %42 = OpINotEqual %8 %41 %15
+               OpStore %28 %42
+         %43 = OpLoad %8 %25
+               OpStore %29 %43
+               OpStore %30 %12
+               OpBranch %44
+         %44 = OpLabel
+               OpLoopMerge %45 %46 None
+               OpBranch %47
+         %47 = OpLabel
+         %48 = OpLoad %8 %29
+               OpBranchConditional %48 %49 %45
+         %49 = OpLabel
+         %50 = OpLoad %8 %25
+               OpSelectionMerge %51 None
+               OpBranchConditional %50 %52 %51
+         %52 = OpLabel
+         %53 = OpLoad %8 %26
+               OpStore %29 %53
+         %54 = OpLoad %10 %30
+         %55 = OpIAdd %10 %54 %16
+               OpStore %30 %55
+               OpBranch %51
+         %51 = OpLabel
+         %56 = OpLoad %8 %26
+               OpSelectionMerge %57 None
+               OpBranchConditional %56 %58 %57
+         %58 = OpLabel
+         %59 = OpLoad %10 %30
+         %60 = OpIAdd %10 %59 %16
+               OpStore %30 %60
+         %61 = OpLoad %8 %29
+         %62 = OpLoad %8 %25
+         %63 = OpLogicalOr %8 %61 %62
+               OpStore %29 %63
+         %64 = OpLoad %8 %27
+               OpSelectionMerge %65 None
+               OpBranchConditional %64 %66 %65
+         %66 = OpLabel
+         %67 = OpLoad %10 %30
+         %68 = OpIAdd %10 %67 %17
+               OpStore %30 %68
+         %69 = OpLoad %8 %29
+         %70 = OpLogicalNot %8 %69
+               OpStore %29 %70
+               OpBranch %46
+         %65 = OpLabel
+         %71 = OpLoad %8 %29
+         %72 = OpLogicalOr %8 %71 %20
+               OpStore %29 %72
+               OpBranch %46
+         %57 = OpLabel
+               OpBranch %73
+         %73 = OpLabel
+               OpLoopMerge %74 %75 None
+               OpBranch %76
+         %76 = OpLabel
+         %77 = OpLoad %8 %28
+               OpSelectionMerge %78 None
+               OpBranchConditional %77 %79 %80
+         %79 = OpLabel
+         %81 = OpLoad %10 %30
+               OpSelectionMerge %82 None
+               OpSwitch %81 %83 1 %84 2 %85
+         %83 = OpLabel
+               OpBranch %82
+         %84 = OpLabel
+         %86 = OpLoad %8 %29
+         %87 = OpSelect %10 %86 %16 %17
+         %88 = OpLoad %10 %30
+         %89 = OpIAdd %10 %88 %87
+               OpStore %30 %89
+               OpBranch %82
+         %85 = OpLabel
+               OpBranch %75
+         %82 = OpLabel
+         %90 = OpLoad %8 %27
+               OpSelectionMerge %91 None
+               OpBranchConditional %90 %92 %91
+         %92 = OpLabel
+               OpBranch %75
+         %91 = OpLabel
+               OpBranch %78
+         %80 = OpLabel
+               OpBranch %74
+         %78 = OpLabel
+               OpBranch %75
+         %75 = OpLabel
+         %93 = OpLoad %8 %29
+               OpBranchConditional %93 %73 %74
+         %74 = OpLabel
+               OpBranch %46
+         %46 = OpLabel
+               OpBranch %44
+         %45 = OpLabel
+         %94 = OpLoad %10 %30
+         %95 = OpConvertSToF %21 %94
+         %96 = OpCompositeConstruct %22 %95 %95 %95 %95
+               OpStore %3 %96
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+
+  ASSERT_EQ(2, ops.size());
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+  CheckValid(env, context.get());
+  std::string after_op_0 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main" %3
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpMemberDecorate %4 0 Offset 0
+               OpMemberDecorate %4 1 Offset 4
+               OpMemberDecorate %4 2 Offset 8
+               OpMemberDecorate %4 3 Offset 12
+               OpDecorate %4 Block
+               OpDecorate %5 DescriptorSet 0
+               OpDecorate %5 Binding 0
+               OpDecorate %3 Location 0
+          %6 = OpTypeVoid
+          %7 = OpTypeFunction %6
+          %8 = OpTypeBool
+          %9 = OpTypePointer Function %8
+         %10 = OpTypeInt 32 1
+          %4 = OpTypeStruct %10 %10 %10 %10
+         %11 = OpTypePointer Uniform %4
+          %5 = OpVariable %11 Uniform
+         %12 = OpConstant %10 0
+         %13 = OpTypePointer Uniform %10
+         %14 = OpTypeInt 32 0
+         %15 = OpConstant %14 0
+         %16 = OpConstant %10 1
+         %17 = OpConstant %10 2
+         %18 = OpConstant %10 3
+         %19 = OpTypePointer Function %10
+         %20 = OpConstantFalse %8
+         %21 = OpTypeFloat 32
+         %22 = OpTypeVector %21 4
+         %23 = OpTypePointer Output %22
+          %3 = OpVariable %23 Output
+         %97 = OpConstantTrue %8
+          %2 = OpFunction %6 None %7
+         %24 = OpLabel
+         %25 = OpVariable %9 Function
+         %26 = OpVariable %9 Function
+         %27 = OpVariable %9 Function
+         %28 = OpVariable %9 Function
+         %29 = OpVariable %9 Function
+         %30 = OpVariable %19 Function
+         %31 = OpAccessChain %13 %5 %12
+         %32 = OpLoad %10 %31
+         %33 = OpINotEqual %8 %32 %15
+               OpStore %25 %33
+         %34 = OpAccessChain %13 %5 %16
+         %35 = OpLoad %10 %34
+         %36 = OpINotEqual %8 %35 %15
+               OpStore %26 %36
+         %37 = OpAccessChain %13 %5 %17
+         %38 = OpLoad %10 %37
+         %39 = OpINotEqual %8 %38 %15
+               OpStore %27 %39
+         %40 = OpAccessChain %13 %5 %18
+         %41 = OpLoad %10 %40
+         %42 = OpINotEqual %8 %41 %15
+               OpStore %28 %42
+         %43 = OpLoad %8 %25
+               OpStore %29 %43
+               OpStore %30 %12
+               OpBranch %44
+         %44 = OpLabel
+               OpSelectionMerge %45 None ; Was OpLoopMerge %45 %46 None
+               OpBranchConditional %97 %47 %45		 ; Was OpBranch %47
+         %47 = OpLabel
+         %48 = OpLoad %8 %29
+               OpBranchConditional %48 %49 %45
+         %49 = OpLabel
+         %50 = OpLoad %8 %25
+               OpSelectionMerge %51 None
+               OpBranchConditional %50 %52 %51
+         %52 = OpLabel
+         %53 = OpLoad %8 %26
+               OpStore %29 %53
+         %54 = OpLoad %10 %30
+         %55 = OpIAdd %10 %54 %16
+               OpStore %30 %55
+               OpBranch %51
+         %51 = OpLabel
+         %56 = OpLoad %8 %26
+               OpSelectionMerge %57 None
+               OpBranchConditional %56 %58 %57
+         %58 = OpLabel
+         %59 = OpLoad %10 %30
+         %60 = OpIAdd %10 %59 %16
+               OpStore %30 %60
+         %61 = OpLoad %8 %29
+         %62 = OpLoad %8 %25
+         %63 = OpLogicalOr %8 %61 %62
+               OpStore %29 %63
+         %64 = OpLoad %8 %27
+               OpSelectionMerge %65 None
+               OpBranchConditional %64 %66 %65
+         %66 = OpLabel
+         %67 = OpLoad %10 %30
+         %68 = OpIAdd %10 %67 %17
+               OpStore %30 %68
+         %69 = OpLoad %8 %29
+         %70 = OpLogicalNot %8 %69
+               OpStore %29 %70
+               OpBranch %65 	; Was OpBranch %46
+         %65 = OpLabel
+         %71 = OpLoad %8 %29
+         %72 = OpLogicalOr %8 %71 %20
+               OpStore %29 %72
+               OpBranch %57 	; Was OpBranch %46
+         %57 = OpLabel
+               OpBranch %73
+         %73 = OpLabel
+               OpLoopMerge %74 %75 None
+               OpBranch %76
+         %76 = OpLabel
+         %77 = OpLoad %8 %28
+               OpSelectionMerge %78 None
+               OpBranchConditional %77 %79 %80
+         %79 = OpLabel
+         %81 = OpLoad %10 %30
+               OpSelectionMerge %82 None
+               OpSwitch %81 %83 1 %84 2 %85
+         %83 = OpLabel
+               OpBranch %82
+         %84 = OpLabel
+         %86 = OpLoad %8 %29
+         %87 = OpSelect %10 %86 %16 %17
+         %88 = OpLoad %10 %30
+         %89 = OpIAdd %10 %88 %87
+               OpStore %30 %89
+               OpBranch %82
+         %85 = OpLabel
+               OpBranch %75
+         %82 = OpLabel
+         %90 = OpLoad %8 %27
+               OpSelectionMerge %91 None
+               OpBranchConditional %90 %92 %91
+         %92 = OpLabel
+               OpBranch %75
+         %91 = OpLabel
+               OpBranch %78
+         %80 = OpLabel
+               OpBranch %74
+         %78 = OpLabel
+               OpBranch %75
+         %75 = OpLabel
+         %93 = OpLoad %8 %29
+               OpBranchConditional %93 %73 %74
+         %74 = OpLabel
+               OpBranch %45 	; Was OpBranch %46
+         %46 = OpLabel
+               OpBranch %44
+         %45 = OpLabel
+         %94 = OpLoad %10 %30
+         %95 = OpConvertSToF %21 %94
+         %96 = OpCompositeConstruct %22 %95 %95 %95 %95
+               OpStore %3 %96
+               OpReturn
+               OpFunctionEnd
+  )";
+  CheckEqual(env, after_op_0, context.get());
+  ASSERT_TRUE(ops[1]->PreconditionHolds());
+  ops[1]->TryToApply();
+  CheckValid(env, context.get());
+
+  std::string after_op_1 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main" %3
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpMemberDecorate %4 0 Offset 0
+               OpMemberDecorate %4 1 Offset 4
+               OpMemberDecorate %4 2 Offset 8
+               OpMemberDecorate %4 3 Offset 12
+               OpDecorate %4 Block
+               OpDecorate %5 DescriptorSet 0
+               OpDecorate %5 Binding 0
+               OpDecorate %3 Location 0
+          %6 = OpTypeVoid
+          %7 = OpTypeFunction %6
+          %8 = OpTypeBool
+          %9 = OpTypePointer Function %8
+         %10 = OpTypeInt 32 1
+          %4 = OpTypeStruct %10 %10 %10 %10
+         %11 = OpTypePointer Uniform %4
+          %5 = OpVariable %11 Uniform
+         %12 = OpConstant %10 0
+         %13 = OpTypePointer Uniform %10
+         %14 = OpTypeInt 32 0
+         %15 = OpConstant %14 0
+         %16 = OpConstant %10 1
+         %17 = OpConstant %10 2
+         %18 = OpConstant %10 3
+         %19 = OpTypePointer Function %10
+         %20 = OpConstantFalse %8
+         %21 = OpTypeFloat 32
+         %22 = OpTypeVector %21 4
+         %23 = OpTypePointer Output %22
+          %3 = OpVariable %23 Output
+         %97 = OpConstantTrue %8
+          %2 = OpFunction %6 None %7
+         %24 = OpLabel
+         %25 = OpVariable %9 Function
+         %26 = OpVariable %9 Function
+         %27 = OpVariable %9 Function
+         %28 = OpVariable %9 Function
+         %29 = OpVariable %9 Function
+         %30 = OpVariable %19 Function
+         %31 = OpAccessChain %13 %5 %12
+         %32 = OpLoad %10 %31
+         %33 = OpINotEqual %8 %32 %15
+               OpStore %25 %33
+         %34 = OpAccessChain %13 %5 %16
+         %35 = OpLoad %10 %34
+         %36 = OpINotEqual %8 %35 %15
+               OpStore %26 %36
+         %37 = OpAccessChain %13 %5 %17
+         %38 = OpLoad %10 %37
+         %39 = OpINotEqual %8 %38 %15
+               OpStore %27 %39
+         %40 = OpAccessChain %13 %5 %18
+         %41 = OpLoad %10 %40
+         %42 = OpINotEqual %8 %41 %15
+               OpStore %28 %42
+         %43 = OpLoad %8 %25
+               OpStore %29 %43
+               OpStore %30 %12
+               OpBranch %44
+         %44 = OpLabel
+               OpSelectionMerge %45 None ; Was OpLoopMerge %45 %46 None
+               OpBranchConditional %97 %47 %45		 ; Was OpBranch %47
+         %47 = OpLabel
+         %48 = OpLoad %8 %29
+               OpBranchConditional %48 %49 %45
+         %49 = OpLabel
+         %50 = OpLoad %8 %25
+               OpSelectionMerge %51 None
+               OpBranchConditional %50 %52 %51
+         %52 = OpLabel
+         %53 = OpLoad %8 %26
+               OpStore %29 %53
+         %54 = OpLoad %10 %30
+         %55 = OpIAdd %10 %54 %16
+               OpStore %30 %55
+               OpBranch %51
+         %51 = OpLabel
+         %56 = OpLoad %8 %26
+               OpSelectionMerge %57 None
+               OpBranchConditional %56 %58 %57
+         %58 = OpLabel
+         %59 = OpLoad %10 %30
+         %60 = OpIAdd %10 %59 %16
+               OpStore %30 %60
+         %61 = OpLoad %8 %29
+         %62 = OpLoad %8 %25
+         %63 = OpLogicalOr %8 %61 %62
+               OpStore %29 %63
+         %64 = OpLoad %8 %27
+               OpSelectionMerge %65 None
+               OpBranchConditional %64 %66 %65
+         %66 = OpLabel
+         %67 = OpLoad %10 %30
+         %68 = OpIAdd %10 %67 %17
+               OpStore %30 %68
+         %69 = OpLoad %8 %29
+         %70 = OpLogicalNot %8 %69
+               OpStore %29 %70
+               OpBranch %65 	; Was OpBranch %46
+         %65 = OpLabel
+         %71 = OpLoad %8 %29
+         %72 = OpLogicalOr %8 %71 %20
+               OpStore %29 %72
+               OpBranch %57 	; Was OpBranch %46
+         %57 = OpLabel
+               OpBranch %73
+         %73 = OpLabel
+               OpSelectionMerge %74 None ; Was OpLoopMerge %74 %75 None
+               OpBranchConditional %97 %76 %74 ; Was OpBranch %76
+         %76 = OpLabel
+         %77 = OpLoad %8 %28
+               OpSelectionMerge %78 None
+               OpBranchConditional %77 %79 %80
+         %79 = OpLabel
+         %81 = OpLoad %10 %30
+               OpSelectionMerge %82 None
+               OpSwitch %81 %83 1 %84 2 %85
+         %83 = OpLabel
+               OpBranch %82
+         %84 = OpLabel
+         %86 = OpLoad %8 %29
+         %87 = OpSelect %10 %86 %16 %17
+         %88 = OpLoad %10 %30
+         %89 = OpIAdd %10 %88 %87
+               OpStore %30 %89
+               OpBranch %82
+         %85 = OpLabel
+               OpBranch %82
+         %82 = OpLabel
+         %90 = OpLoad %8 %27
+               OpSelectionMerge %91 None
+               OpBranchConditional %90 %92 %91
+         %92 = OpLabel
+               OpBranch %91
+         %91 = OpLabel
+               OpBranch %78
+         %80 = OpLabel
+               OpBranch %78 ; Was OpBranch %74
+         %78 = OpLabel
+               OpBranch %74
+         %75 = OpLabel
+         %93 = OpLoad %8 %29
+               OpBranchConditional %93 %73 %74
+         %74 = OpLabel
+               OpBranch %45 	; Was OpBranch %46
+         %46 = OpLabel
+               OpBranch %44
+         %45 = OpLabel
+         %94 = OpLoad %10 %30
+         %95 = OpConvertSToF %21 %94
+         %96 = OpCompositeConstruct %22 %95 %95 %95 %95
+               OpStore %3 %96
+               OpReturn
+               OpFunctionEnd
+  )";
+  CheckEqual(env, after_op_1, context.get());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest, ComplexOptimized) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main" %3
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpMemberDecorate %4 0 Offset 0
+               OpMemberDecorate %4 1 Offset 4
+               OpMemberDecorate %4 2 Offset 8
+               OpMemberDecorate %4 3 Offset 12
+               OpDecorate %4 Block
+               OpDecorate %5 DescriptorSet 0
+               OpDecorate %5 Binding 0
+               OpDecorate %3 Location 0
+          %6 = OpTypeVoid
+          %7 = OpTypeFunction %6
+          %8 = OpTypeBool
+         %10 = OpTypeInt 32 1
+          %4 = OpTypeStruct %10 %10 %10 %10
+         %11 = OpTypePointer Uniform %4
+          %5 = OpVariable %11 Uniform
+         %12 = OpConstant %10 0
+         %13 = OpTypePointer Uniform %10
+         %14 = OpTypeInt 32 0
+         %15 = OpConstant %14 0
+         %16 = OpConstant %10 1
+         %17 = OpConstant %10 2
+         %18 = OpConstant %10 3
+         %20 = OpConstantFalse %8
+         %21 = OpTypeFloat 32
+         %22 = OpTypeVector %21 4
+         %23 = OpTypePointer Output %22
+          %3 = OpVariable %23 Output
+          %2 = OpFunction %6 None %7
+         %24 = OpLabel
+         %31 = OpAccessChain %13 %5 %12
+         %32 = OpLoad %10 %31
+         %33 = OpINotEqual %8 %32 %15
+         %34 = OpAccessChain %13 %5 %16
+         %35 = OpLoad %10 %34
+         %36 = OpINotEqual %8 %35 %15
+         %37 = OpAccessChain %13 %5 %17
+         %38 = OpLoad %10 %37
+         %39 = OpINotEqual %8 %38 %15
+         %40 = OpAccessChain %13 %5 %18
+         %41 = OpLoad %10 %40
+         %42 = OpINotEqual %8 %41 %15
+               OpBranch %44
+         %44 = OpLabel
+         %98 = OpPhi %10 %12 %24 %107 %46
+         %97 = OpPhi %8 %33 %24 %105 %46
+               OpLoopMerge %45 %46 None
+               OpBranchConditional %97 %49 %45
+         %49 = OpLabel
+               OpSelectionMerge %51 None
+               OpBranchConditional %33 %52 %51
+         %52 = OpLabel
+         %55 = OpIAdd %10 %98 %16
+               OpBranch %51
+         %51 = OpLabel
+        %100 = OpPhi %10 %98 %49 %55 %52
+        %113 = OpSelect %8 %33 %36 %97
+               OpSelectionMerge %57 None
+               OpBranchConditional %36 %58 %57
+         %58 = OpLabel
+         %60 = OpIAdd %10 %100 %16
+         %63 = OpLogicalOr %8 %113 %33
+               OpSelectionMerge %65 None
+               OpBranchConditional %39 %66 %65
+         %66 = OpLabel
+         %68 = OpIAdd %10 %100 %18
+         %70 = OpLogicalNot %8 %63
+               OpBranch %46
+         %65 = OpLabel
+         %72 = OpLogicalOr %8 %63 %20
+               OpBranch %46
+         %57 = OpLabel
+               OpBranch %73
+         %73 = OpLabel
+         %99 = OpPhi %10 %100 %57 %109 %75
+               OpLoopMerge %74 %75 None
+               OpBranch %76
+         %76 = OpLabel
+               OpSelectionMerge %78 None
+               OpBranchConditional %42 %79 %80
+         %79 = OpLabel
+               OpSelectionMerge %82 None
+               OpSwitch %99 %83 1 %84 2 %85
+         %83 = OpLabel
+               OpBranch %82
+         %84 = OpLabel
+         %87 = OpSelect %10 %113 %16 %17
+         %89 = OpIAdd %10 %99 %87
+               OpBranch %82
+         %85 = OpLabel
+               OpBranch %75
+         %82 = OpLabel
+        %110 = OpPhi %10 %99 %83 %89 %84
+               OpSelectionMerge %91 None
+               OpBranchConditional %39 %92 %91
+         %92 = OpLabel
+               OpBranch %75
+         %91 = OpLabel
+               OpBranch %78
+         %80 = OpLabel
+               OpBranch %74
+         %78 = OpLabel
+               OpBranch %75
+         %75 = OpLabel
+        %109 = OpPhi %10 %99 %85 %110 %92 %110 %78
+               OpBranchConditional %113 %73 %74
+         %74 = OpLabel
+        %108 = OpPhi %10 %99 %80 %109 %75
+               OpBranch %46
+         %46 = OpLabel
+        %107 = OpPhi %10 %68 %66 %60 %65 %108 %74
+        %105 = OpPhi %8 %70 %66 %72 %65 %113 %74
+               OpBranch %44
+         %45 = OpLabel
+         %95 = OpConvertSToF %21 %98
+         %96 = OpCompositeConstruct %22 %95 %95 %95 %95
+               OpStore %3 %96
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+
+  ASSERT_EQ(2, ops.size());
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+  CheckValid(env, context.get());
+  std::string after_op_0 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main" %3
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpMemberDecorate %4 0 Offset 0
+               OpMemberDecorate %4 1 Offset 4
+               OpMemberDecorate %4 2 Offset 8
+               OpMemberDecorate %4 3 Offset 12
+               OpDecorate %4 Block
+               OpDecorate %5 DescriptorSet 0
+               OpDecorate %5 Binding 0
+               OpDecorate %3 Location 0
+          %6 = OpTypeVoid
+          %7 = OpTypeFunction %6
+          %8 = OpTypeBool
+         %10 = OpTypeInt 32 1
+          %4 = OpTypeStruct %10 %10 %10 %10
+         %11 = OpTypePointer Uniform %4
+          %5 = OpVariable %11 Uniform
+         %12 = OpConstant %10 0
+         %13 = OpTypePointer Uniform %10
+         %14 = OpTypeInt 32 0
+         %15 = OpConstant %14 0
+         %16 = OpConstant %10 1
+         %17 = OpConstant %10 2
+         %18 = OpConstant %10 3
+         %20 = OpConstantFalse %8
+         %21 = OpTypeFloat 32
+         %22 = OpTypeVector %21 4
+         %23 = OpTypePointer Output %22
+          %3 = OpVariable %23 Output
+        %114 = OpUndef %10
+        %115 = OpUndef %8
+          %2 = OpFunction %6 None %7
+         %24 = OpLabel
+         %31 = OpAccessChain %13 %5 %12
+         %32 = OpLoad %10 %31
+         %33 = OpINotEqual %8 %32 %15
+         %34 = OpAccessChain %13 %5 %16
+         %35 = OpLoad %10 %34
+         %36 = OpINotEqual %8 %35 %15
+         %37 = OpAccessChain %13 %5 %17
+         %38 = OpLoad %10 %37
+         %39 = OpINotEqual %8 %38 %15
+         %40 = OpAccessChain %13 %5 %18
+         %41 = OpLoad %10 %40
+         %42 = OpINotEqual %8 %41 %15
+               OpBranch %44
+         %44 = OpLabel
+         %98 = OpPhi %10 %12 %24 %114 %46
+         %97 = OpPhi %8 %33 %24 %115 %46
+               OpSelectionMerge %45 None	; Was OpLoopMerge %45 %46 None
+               OpBranchConditional %97 %49 %45
+         %49 = OpLabel
+               OpSelectionMerge %51 None
+               OpBranchConditional %33 %52 %51
+         %52 = OpLabel
+         %55 = OpIAdd %10 %98 %16
+               OpBranch %51
+         %51 = OpLabel
+        %100 = OpPhi %10 %98 %49 %55 %52
+        %113 = OpSelect %8 %33 %36 %97
+               OpSelectionMerge %57 None
+               OpBranchConditional %36 %58 %57
+         %58 = OpLabel
+         %60 = OpIAdd %10 %100 %16
+         %63 = OpLogicalOr %8 %113 %33
+               OpSelectionMerge %65 None
+               OpBranchConditional %39 %66 %65
+         %66 = OpLabel
+         %68 = OpIAdd %10 %100 %18
+         %70 = OpLogicalNot %8 %63
+               OpBranch %65 	; Was OpBranch %46
+         %65 = OpLabel
+         %72 = OpLogicalOr %8 %63 %20
+               OpBranch %57     ; Was OpBranch %46
+         %57 = OpLabel
+               OpBranch %73
+         %73 = OpLabel
+         %99 = OpPhi %10 %100 %57 %109 %75
+               OpLoopMerge %74 %75 None
+               OpBranch %76
+         %76 = OpLabel
+               OpSelectionMerge %78 None
+               OpBranchConditional %42 %79 %80
+         %79 = OpLabel
+               OpSelectionMerge %82 None
+               OpSwitch %99 %83 1 %84 2 %85
+         %83 = OpLabel
+               OpBranch %82
+         %84 = OpLabel
+         %87 = OpSelect %10 %113 %16 %17
+         %89 = OpIAdd %10 %99 %87
+               OpBranch %82
+         %85 = OpLabel
+               OpBranch %75
+         %82 = OpLabel
+        %110 = OpPhi %10 %99 %83 %89 %84
+               OpSelectionMerge %91 None
+               OpBranchConditional %39 %92 %91
+         %92 = OpLabel
+               OpBranch %75
+         %91 = OpLabel
+               OpBranch %78
+         %80 = OpLabel
+               OpBranch %74
+         %78 = OpLabel
+               OpBranch %75
+         %75 = OpLabel
+        %109 = OpPhi %10 %99 %85 %110 %92 %110 %78
+               OpBranchConditional %113 %73 %74
+         %74 = OpLabel
+        %108 = OpPhi %10 %99 %80 %109 %75
+               OpBranch %45 	; Was OpBranch %46
+         %46 = OpLabel
+        %107 = OpPhi %10      ; Was OpPhi %10 %68 %66 %60 %65 %108 %74
+        %105 = OpPhi %8       ; Was OpPhi %8 %70 %66 %72 %65 %113 %74
+               OpBranch %44
+         %45 = OpLabel
+         %95 = OpConvertSToF %21 %98
+         %96 = OpCompositeConstruct %22 %95 %95 %95 %95
+               OpStore %3 %96
+               OpReturn
+               OpFunctionEnd
+  )";
+  CheckEqual(env, after_op_0, context.get());
+
+  ASSERT_TRUE(ops[1]->PreconditionHolds());
+  ops[1]->TryToApply();
+  CheckValid(env, context.get());
+  std::string after_op_1 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main" %3
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpMemberDecorate %4 0 Offset 0
+               OpMemberDecorate %4 1 Offset 4
+               OpMemberDecorate %4 2 Offset 8
+               OpMemberDecorate %4 3 Offset 12
+               OpDecorate %4 Block
+               OpDecorate %5 DescriptorSet 0
+               OpDecorate %5 Binding 0
+               OpDecorate %3 Location 0
+          %6 = OpTypeVoid
+          %7 = OpTypeFunction %6
+          %8 = OpTypeBool
+         %10 = OpTypeInt 32 1
+          %4 = OpTypeStruct %10 %10 %10 %10
+         %11 = OpTypePointer Uniform %4
+          %5 = OpVariable %11 Uniform
+         %12 = OpConstant %10 0
+         %13 = OpTypePointer Uniform %10
+         %14 = OpTypeInt 32 0
+         %15 = OpConstant %14 0
+         %16 = OpConstant %10 1
+         %17 = OpConstant %10 2
+         %18 = OpConstant %10 3
+         %20 = OpConstantFalse %8
+         %21 = OpTypeFloat 32
+         %22 = OpTypeVector %21 4
+         %23 = OpTypePointer Output %22
+          %3 = OpVariable %23 Output
+        %114 = OpUndef %10
+        %115 = OpUndef %8
+        %116 = OpConstantTrue %8
+          %2 = OpFunction %6 None %7
+         %24 = OpLabel
+         %31 = OpAccessChain %13 %5 %12
+         %32 = OpLoad %10 %31
+         %33 = OpINotEqual %8 %32 %15
+         %34 = OpAccessChain %13 %5 %16
+         %35 = OpLoad %10 %34
+         %36 = OpINotEqual %8 %35 %15
+         %37 = OpAccessChain %13 %5 %17
+         %38 = OpLoad %10 %37
+         %39 = OpINotEqual %8 %38 %15
+         %40 = OpAccessChain %13 %5 %18
+         %41 = OpLoad %10 %40
+         %42 = OpINotEqual %8 %41 %15
+               OpBranch %44
+         %44 = OpLabel
+         %98 = OpPhi %10 %12 %24 %114 %46
+         %97 = OpPhi %8 %33 %24 %115 %46
+               OpSelectionMerge %45 None	; Was OpLoopMerge %45 %46 None
+               OpBranchConditional %97 %49 %45
+         %49 = OpLabel
+               OpSelectionMerge %51 None
+               OpBranchConditional %33 %52 %51
+         %52 = OpLabel
+         %55 = OpIAdd %10 %98 %16
+               OpBranch %51
+         %51 = OpLabel
+        %100 = OpPhi %10 %98 %49 %55 %52
+        %113 = OpSelect %8 %33 %36 %97
+               OpSelectionMerge %57 None
+               OpBranchConditional %36 %58 %57
+         %58 = OpLabel
+         %60 = OpIAdd %10 %100 %16
+         %63 = OpLogicalOr %8 %113 %33
+               OpSelectionMerge %65 None
+               OpBranchConditional %39 %66 %65
+         %66 = OpLabel
+         %68 = OpIAdd %10 %100 %18
+         %70 = OpLogicalNot %8 %63
+               OpBranch %65 	; Was OpBranch %46
+         %65 = OpLabel
+         %72 = OpLogicalOr %8 %63 %20
+               OpBranch %57     ; Was OpBranch %46
+         %57 = OpLabel
+               OpBranch %73
+         %73 = OpLabel
+         %99 = OpPhi %10 %100 %57 %114 %75
+               OpSelectionMerge %74 None ; Was OpLoopMerge %74 %75 None
+               OpBranchConditional %116 %76 %74
+         %76 = OpLabel
+               OpSelectionMerge %78 None
+               OpBranchConditional %42 %79 %80
+         %79 = OpLabel
+               OpSelectionMerge %82 None
+               OpSwitch %99 %83 1 %84 2 %85
+         %83 = OpLabel
+               OpBranch %82
+         %84 = OpLabel
+         %87 = OpSelect %10 %113 %16 %17
+         %89 = OpIAdd %10 %99 %87
+               OpBranch %82
+         %85 = OpLabel
+               OpBranch %82 	; Was OpBranch %75
+         %82 = OpLabel
+        %110 = OpPhi %10 %99 %83 %89 %84 %114 %85 ; Was OpPhi %10 %99 %83 %89 %84
+               OpSelectionMerge %91 None
+               OpBranchConditional %39 %92 %91
+         %92 = OpLabel
+               OpBranch %91 	; OpBranch %75
+         %91 = OpLabel
+               OpBranch %78
+         %80 = OpLabel
+               OpBranch %78 	; Was OpBranch %74
+         %78 = OpLabel
+               OpBranch %74     ; Was OpBranch %75
+         %75 = OpLabel
+        %109 = OpPhi %10 ; Was OpPhi %10 %99 %85 %110 %92 %110 %78
+               OpBranchConditional %115 %73 %74
+         %74 = OpLabel
+        %108 = OpPhi %10 %114 %75 %114 %78 %114 %73 ; Was OpPhi %10 %99 %80 %109 %75
+               OpBranch %45 	; Was OpBranch %46
+         %46 = OpLabel
+        %107 = OpPhi %10      ; Was OpPhi %10 %68 %66 %60 %65 %108 %74
+        %105 = OpPhi %8       ; Was OpPhi %8 %70 %66 %72 %65 %113 %74
+               OpBranch %44
+         %45 = OpLabel
+         %95 = OpConvertSToF %21 %98
+         %96 = OpCompositeConstruct %22 %95 %95 %95 %95
+               OpStore %3 %96
+               OpReturn
+               OpFunctionEnd
+  )";
+  CheckEqual(env, after_op_1, context.get());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest, DominanceIssue) {
+  // Exposes a scenario where redirecting edges results in uses of ids being
+  // non-dominated.  We replace such uses with OpUndef to account for this.
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %5 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %5
+          %6 = OpTypeBool
+          %8 = OpConstantTrue %6
+          %9 = OpConstant %5 10
+         %10 = OpConstant %5 20
+         %11 = OpConstant %5 30
+          %4 = OpFunction %2 None %3
+         %12 = OpLabel
+               OpBranch %13
+         %13 = OpLabel
+               OpLoopMerge %14 %15 None
+               OpBranch %16
+         %16 = OpLabel
+               OpSelectionMerge %17 None
+               OpBranchConditional %8 %18 %19
+         %18 = OpLabel
+               OpBranch %14
+         %19 = OpLabel
+         %20 = OpIAdd %5 %9 %10
+               OpBranch %17
+         %17 = OpLabel
+         %21 = OpIAdd %5 %20 %11
+               OpBranchConditional %8 %14 %15
+         %15 = OpLabel
+               OpBranch %13
+         %14 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+  CheckValid(env, context.get());
+
+  std::string expected = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %5 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %5
+          %6 = OpTypeBool
+          %8 = OpConstantTrue %6
+          %9 = OpConstant %5 10
+         %10 = OpConstant %5 20
+         %11 = OpConstant %5 30
+         %22 = OpUndef %5
+          %4 = OpFunction %2 None %3
+         %12 = OpLabel
+               OpBranch %13
+         %13 = OpLabel
+               OpSelectionMerge %14 None
+               OpBranchConditional %8 %16 %14
+         %16 = OpLabel
+               OpSelectionMerge %17 None
+               OpBranchConditional %8 %18 %19
+         %18 = OpLabel
+               OpBranch %17
+         %19 = OpLabel
+         %20 = OpIAdd %5 %9 %10
+               OpBranch %17
+         %17 = OpLabel
+         %21 = OpIAdd %5 %22 %11
+               OpBranchConditional %8 %14 %14
+         %15 = OpLabel
+               OpBranch %13
+         %14 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  CheckEqual(env, expected, context.get());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest, AccessChainIssue) {
+  // Exposes a scenario where redirecting edges results in a use of an id
+  // generated by an access chain being non-dominated.
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %56
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpMemberDecorate %28 0 Offset 0
+               OpDecorate %28 Block
+               OpDecorate %30 DescriptorSet 0
+               OpDecorate %30 Binding 0
+               OpDecorate %56 Location 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeVector %6 2
+          %8 = OpTypePointer Function %7
+         %60 = OpTypePointer Private %7
+         %10 = OpConstant %6 0
+         %11 = OpConstantComposite %7 %10 %10
+         %12 = OpTypePointer Function %6
+         %59 = OpTypePointer Private %6
+         %14 = OpTypeInt 32 1
+         %15 = OpTypePointer Function %14
+         %17 = OpConstant %14 0
+         %24 = OpConstant %14 100
+         %25 = OpTypeBool
+         %28 = OpTypeStruct %6
+         %29 = OpTypePointer Uniform %28
+         %30 = OpVariable %29 Uniform
+         %31 = OpTypePointer Uniform %6
+         %39 = OpTypeInt 32 0
+         %40 = OpConstant %39 1
+         %45 = OpConstant %39 0
+         %52 = OpConstant %14 1
+         %54 = OpTypeVector %6 4
+         %55 = OpTypePointer Output %54
+         %56 = OpVariable %55 Output
+          %9 = OpVariable %60 Private
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %13 = OpVariable %12 Function
+         %16 = OpVariable %15 Function
+         %38 = OpVariable %12 Function
+               OpStore %9 %11
+               OpStore %13 %10
+               OpStore %16 %17
+               OpBranch %18
+         %18 = OpLabel
+               OpLoopMerge %20 %21 None
+               OpBranch %22
+         %22 = OpLabel
+         %23 = OpLoad %14 %16
+         %26 = OpSLessThan %25 %23 %24
+               OpBranchConditional %26 %19 %20
+         %19 = OpLabel
+         %27 = OpLoad %14 %16
+         %32 = OpAccessChain %31 %30 %17
+         %33 = OpLoad %6 %32
+         %34 = OpConvertFToS %14 %33
+         %35 = OpSLessThan %25 %27 %34
+               OpSelectionMerge %37 None
+               OpBranchConditional %35 %36 %44
+         %36 = OpLabel
+         %41 = OpAccessChain %59 %9 %40
+         %42 = OpLoad %6 %41
+               OpStore %38 %42
+               OpBranch %20
+         %44 = OpLabel
+         %46 = OpAccessChain %59 %9 %45
+               OpBranch %37
+         %37 = OpLabel
+         %47 = OpLoad %6 %46
+               OpStore %38 %47
+         %48 = OpLoad %6 %38
+         %49 = OpLoad %6 %13
+         %50 = OpFAdd %6 %49 %48
+               OpStore %13 %50
+               OpBranch %21
+         %21 = OpLabel
+         %51 = OpLoad %14 %16
+         %53 = OpIAdd %14 %51 %52
+               OpStore %16 %53
+               OpBranch %18
+         %20 = OpLabel
+         %57 = OpLoad %6 %13
+         %58 = OpCompositeConstruct %54 %57 %57 %57 %57
+               OpStore %56 %58
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+  CheckValid(env, context.get());
+
+  std::string expected = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %56
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpMemberDecorate %28 0 Offset 0
+               OpDecorate %28 Block
+               OpDecorate %30 DescriptorSet 0
+               OpDecorate %30 Binding 0
+               OpDecorate %56 Location 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeVector %6 2
+          %8 = OpTypePointer Function %7
+         %60 = OpTypePointer Private %7
+         %10 = OpConstant %6 0
+         %11 = OpConstantComposite %7 %10 %10
+         %12 = OpTypePointer Function %6
+         %59 = OpTypePointer Private %6
+         %14 = OpTypeInt 32 1
+         %15 = OpTypePointer Function %14
+         %17 = OpConstant %14 0
+         %24 = OpConstant %14 100
+         %25 = OpTypeBool
+         %28 = OpTypeStruct %6
+         %29 = OpTypePointer Uniform %28
+         %30 = OpVariable %29 Uniform
+         %31 = OpTypePointer Uniform %6
+         %39 = OpTypeInt 32 0
+         %40 = OpConstant %39 1
+         %45 = OpConstant %39 0
+         %52 = OpConstant %14 1
+         %54 = OpTypeVector %6 4
+         %55 = OpTypePointer Output %54
+         %56 = OpVariable %55 Output
+          %9 = OpVariable %60 Private
+         %61 = OpConstantTrue %25
+         %62 = OpVariable %59 Private
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %13 = OpVariable %12 Function
+         %16 = OpVariable %15 Function
+         %38 = OpVariable %12 Function
+               OpStore %9 %11
+               OpStore %13 %10
+               OpStore %16 %17
+               OpBranch %18
+         %18 = OpLabel
+               OpSelectionMerge %20 None
+               OpBranchConditional %61 %22 %20
+         %22 = OpLabel
+         %23 = OpLoad %14 %16
+         %26 = OpSLessThan %25 %23 %24
+               OpBranchConditional %26 %19 %20
+         %19 = OpLabel
+         %27 = OpLoad %14 %16
+         %32 = OpAccessChain %31 %30 %17
+         %33 = OpLoad %6 %32
+         %34 = OpConvertFToS %14 %33
+         %35 = OpSLessThan %25 %27 %34
+               OpSelectionMerge %37 None
+               OpBranchConditional %35 %36 %44
+         %36 = OpLabel
+         %41 = OpAccessChain %59 %9 %40
+         %42 = OpLoad %6 %41
+               OpStore %38 %42
+               OpBranch %37
+         %44 = OpLabel
+         %46 = OpAccessChain %59 %9 %45
+               OpBranch %37
+         %37 = OpLabel
+         %47 = OpLoad %6 %62
+               OpStore %38 %47
+         %48 = OpLoad %6 %38
+         %49 = OpLoad %6 %13
+         %50 = OpFAdd %6 %49 %48
+               OpStore %13 %50
+               OpBranch %20
+         %21 = OpLabel
+         %51 = OpLoad %14 %16
+         %53 = OpIAdd %14 %51 %52
+               OpStore %16 %53
+               OpBranch %18
+         %20 = OpLabel
+         %57 = OpLoad %6 %13
+         %58 = OpCompositeConstruct %54 %57 %57 %57 %57
+               OpStore %56 %58
+               OpReturn
+               OpFunctionEnd
+  )";
+  CheckEqual(env, expected, context.get());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest, DominanceAndPhiIssue) {
+  // Exposes an interesting scenario where a use in a phi stops being dominated
+  // by the block with which it is associated in the phi.
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %17 = OpTypeBool
+         %18 = OpConstantTrue %17
+         %19 = OpConstantFalse %17
+         %20 = OpTypeInt 32 1
+         %21 = OpConstant %20 5
+         %22 = OpConstant %20 6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpBranch %6
+         %6 = OpLabel
+              OpLoopMerge %16 %15 None
+              OpBranch %7
+         %7 = OpLabel
+              OpSelectionMerge %13 None
+              OpBranchConditional %18 %8 %9
+         %8 = OpLabel
+              OpSelectionMerge %12 None
+              OpBranchConditional %18 %10 %11
+         %9 = OpLabel
+              OpBranch %16
+        %10 = OpLabel
+              OpBranch %16
+        %11 = OpLabel
+        %23 = OpIAdd %20 %21 %22
+              OpBranch %12
+        %12 = OpLabel
+              OpBranch %13
+        %13 = OpLabel
+              OpBranch %14
+        %14 = OpLabel
+        %24 = OpPhi %20 %23 %13
+              OpBranchConditional %19 %15 %16
+        %15 = OpLabel
+              OpBranch %6
+        %16 = OpLabel
+              OpReturn
+              OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+
+  CheckValid(env, context.get());
+
+  std::string expected = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %17 = OpTypeBool
+         %18 = OpConstantTrue %17
+         %19 = OpConstantFalse %17
+         %20 = OpTypeInt 32 1
+         %21 = OpConstant %20 5
+         %22 = OpConstant %20 6
+         %25 = OpUndef %20
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpBranch %6
+         %6 = OpLabel
+              OpSelectionMerge %16 None
+              OpBranchConditional %18 %7 %16
+         %7 = OpLabel
+              OpSelectionMerge %13 None
+              OpBranchConditional %18 %8 %9
+         %8 = OpLabel
+              OpSelectionMerge %12 None
+              OpBranchConditional %18 %10 %11
+         %9 = OpLabel
+              OpBranch %13
+        %10 = OpLabel
+              OpBranch %12
+        %11 = OpLabel
+        %23 = OpIAdd %20 %21 %22
+              OpBranch %12
+        %12 = OpLabel
+              OpBranch %13
+        %13 = OpLabel
+              OpBranch %14
+        %14 = OpLabel
+        %24 = OpPhi %20 %25 %13
+              OpBranchConditional %19 %16 %16
+        %15 = OpLabel
+              OpBranch %6
+        %16 = OpLabel
+              OpReturn
+              OpFunctionEnd
+  )";
+  CheckEqual(env, expected, context.get());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest, OpLineBeforeOpPhi) {
+  // Test to ensure the pass knows OpLine and OpPhi instructions can be
+  // interleaved.
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpString "somefile"
+          %4 = OpTypeVoid
+          %5 = OpTypeFunction %4
+          %6 = OpTypeInt 32 1
+          %7 = OpConstant %6 10
+          %8 = OpConstant %6 20
+          %9 = OpConstant %6 30
+         %10 = OpTypeBool
+         %11 = OpConstantTrue %10
+          %2 = OpFunction %4 None %5
+         %12 = OpLabel
+               OpBranch %13
+         %13 = OpLabel
+               OpLoopMerge %14 %15 None
+               OpBranch %16
+         %16 = OpLabel
+               OpSelectionMerge %17 None
+               OpBranchConditional %11 %18 %19
+         %18 = OpLabel
+         %20 = OpIAdd %6 %7 %8
+         %21 = OpIAdd %6 %7 %9
+               OpBranch %17
+         %19 = OpLabel
+               OpBranch %14
+         %17 = OpLabel
+         %22 = OpPhi %6 %20 %18
+               OpLine %3 0 0
+         %23 = OpPhi %6 %21 %18
+               OpBranch %15
+         %15 = OpLabel
+               OpBranch %13
+         %14 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+
+  CheckValid(env, context.get());
+
+  std::string expected = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpString "somefile"
+          %4 = OpTypeVoid
+          %5 = OpTypeFunction %4
+          %6 = OpTypeInt 32 1
+          %7 = OpConstant %6 10
+          %8 = OpConstant %6 20
+          %9 = OpConstant %6 30
+         %10 = OpTypeBool
+         %11 = OpConstantTrue %10
+         %24 = OpUndef %6
+          %2 = OpFunction %4 None %5
+         %12 = OpLabel
+               OpBranch %13
+         %13 = OpLabel
+               OpSelectionMerge %14 None
+               OpBranchConditional %11 %16 %14
+         %16 = OpLabel
+               OpSelectionMerge %17 None
+               OpBranchConditional %11 %18 %19
+         %18 = OpLabel
+         %20 = OpIAdd %6 %7 %8
+         %21 = OpIAdd %6 %7 %9
+               OpBranch %17
+         %19 = OpLabel
+               OpBranch %17
+         %17 = OpLabel
+         %22 = OpPhi %6 %20 %18 %24 %19
+               OpLine %3 0 0
+         %23 = OpPhi %6 %21 %18 %24 %19
+               OpBranch %14
+         %15 = OpLabel
+               OpBranch %13
+         %14 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  CheckEqual(env, expected, context.get());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest,
+     SelectionMergeIsContinueTarget) {
+  // Example where a loop's continue target is also the target of a selection.
+  // In this scenario we cautiously do not apply the transformation.
+  std::string shader = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %1 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeBool
+          %4 = OpTypeFunction %2
+          %1 = OpFunction %2 None %4
+          %5 = OpLabel
+          %6 = OpUndef %3
+               OpBranch %7
+          %7 = OpLabel
+          %8 = OpPhi %3 %6 %5 %9 %10
+               OpLoopMerge %11 %10 None
+               OpBranch %12
+         %12 = OpLabel
+         %13 = OpUndef %3
+               OpSelectionMerge %10 None
+               OpBranchConditional %13 %14 %10
+         %14 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+          %9 = OpUndef %3
+               OpBranchConditional %9 %7 %11
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+
+  // There should be no opportunities.
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest,
+     SwitchSelectionMergeIsContinueTarget) {
+  // Another example where a loop's continue target is also the target of a
+  // selection; this time a selection associated with an OpSwitch.  We
+  // cautiously do not apply the transformation.
+  std::string shader = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %1 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeBool
+          %5 = OpTypeInt 32 1
+          %4 = OpTypeFunction %2
+          %6 = OpConstant %5 2
+          %7 = OpConstantTrue %3
+          %1 = OpFunction %2 None %4
+          %8 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+               OpLoopMerge %14 %15 None
+               OpBranchConditional %7 %10 %14
+         %10 = OpLabel
+               OpSelectionMerge %15 None
+               OpSwitch %6 %12 1 %11 2 %11 3 %15
+         %11 = OpLabel
+               OpBranch %12
+         %12 = OpLabel
+               OpBranch %15
+         %15 = OpLabel
+               OpBranch %9
+         %14 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+
+  // There should be no opportunities.
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest, ContinueTargetIsSwitchTarget) {
+  std::string shader = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %1 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeBool
+          %5 = OpTypeInt 32 1
+          %4 = OpTypeFunction %2
+          %6 = OpConstant %5 2
+          %7 = OpConstantTrue %3
+          %1 = OpFunction %2 None %4
+          %8 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+               OpLoopMerge %14 %12 None
+               OpBranchConditional %7 %10 %14
+         %10 = OpLabel
+               OpSelectionMerge %15 None
+               OpSwitch %6 %12 1 %11 2 %11 3 %15
+         %11 = OpLabel
+               OpBranch %12
+         %12 = OpLabel
+               OpBranch %9
+         %15 = OpLabel
+               OpBranch %14
+         %14 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+
+  ASSERT_EQ(1, ops.size());
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+
+  CheckValid(env, context.get());
+
+  std::string expected = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %1 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeBool
+          %5 = OpTypeInt 32 1
+          %4 = OpTypeFunction %2
+          %6 = OpConstant %5 2
+          %7 = OpConstantTrue %3
+          %1 = OpFunction %2 None %4
+          %8 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+               OpSelectionMerge %14 None
+               OpBranchConditional %7 %10 %14
+         %10 = OpLabel
+               OpSelectionMerge %15 None
+               OpSwitch %6 %15 1 %11 2 %11 3 %15
+         %11 = OpLabel
+               OpBranch %15
+         %12 = OpLabel
+               OpBranch %9
+         %15 = OpLabel
+               OpBranch %14
+         %14 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  CheckEqual(env, expected, context.get());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest,
+     MultipleSwitchTargetsAreContinueTarget) {
+  std::string shader = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %1 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeBool
+          %5 = OpTypeInt 32 1
+          %4 = OpTypeFunction %2
+          %6 = OpConstant %5 2
+          %7 = OpConstantTrue %3
+          %1 = OpFunction %2 None %4
+          %8 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+               OpLoopMerge %14 %12 None
+               OpBranchConditional %7 %10 %14
+         %10 = OpLabel
+               OpSelectionMerge %15 None
+               OpSwitch %6 %11 1 %12 2 %12 3 %15
+         %11 = OpLabel
+               OpBranch %12
+         %12 = OpLabel
+               OpBranch %9
+         %15 = OpLabel
+               OpBranch %14
+         %14 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+
+  ASSERT_EQ(1, ops.size());
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+
+  CheckValid(env, context.get());
+
+  std::string expected = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %1 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeBool
+          %5 = OpTypeInt 32 1
+          %4 = OpTypeFunction %2
+          %6 = OpConstant %5 2
+          %7 = OpConstantTrue %3
+          %1 = OpFunction %2 None %4
+          %8 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+               OpSelectionMerge %14 None
+               OpBranchConditional %7 %10 %14
+         %10 = OpLabel
+               OpSelectionMerge %15 None
+               OpSwitch %6 %11 1 %15 2 %15 3 %15
+         %11 = OpLabel
+               OpBranch %15
+         %12 = OpLabel
+               OpBranch %9
+         %15 = OpLabel
+               OpBranch %14
+         %14 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  CheckEqual(env, expected, context.get());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest, LoopBranchesStraightToMerge) {
+  std::string shader = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %1 "main"
+          %2 = OpTypeVoid
+          %4 = OpTypeFunction %2
+          %1 = OpFunction %2 None %4
+          %8 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+               OpLoopMerge %14 %12 None
+               OpBranch %14
+         %12 = OpLabel
+               OpBranch %9
+         %14 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+
+  ASSERT_EQ(1, ops.size());
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+
+  CheckValid(env, context.get());
+
+  std::string expected = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %1 "main"
+          %2 = OpTypeVoid
+          %4 = OpTypeFunction %2
+         %15 = OpTypeBool
+         %16 = OpConstantTrue %15
+          %1 = OpFunction %2 None %4
+          %8 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+               OpSelectionMerge %14 None
+               OpBranchConditional %16 %14 %14
+         %12 = OpLabel
+               OpBranch %9
+         %14 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  CheckEqual(env, expected, context.get());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest,
+     LoopConditionallyJumpsToMergeOrContinue) {
+  std::string shader = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %1 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeBool
+          %4 = OpTypeFunction %2
+          %7 = OpConstantTrue %3
+          %1 = OpFunction %2 None %4
+          %8 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+               OpLoopMerge %14 %12 None
+               OpBranchConditional %7 %14 %12
+         %12 = OpLabel
+               OpBranch %9
+         %14 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+
+  ASSERT_EQ(1, ops.size());
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+
+  CheckValid(env, context.get());
+
+  std::string expected = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %1 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeBool
+          %4 = OpTypeFunction %2
+          %7 = OpConstantTrue %3
+          %1 = OpFunction %2 None %4
+          %8 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+               OpSelectionMerge %14 None
+               OpBranchConditional %7 %14 %14
+         %12 = OpLabel
+               OpBranch %9
+         %14 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  CheckEqual(env, expected, context.get());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest, MultipleAccessChains) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeStruct %6
+          %8 = OpTypeStruct %7
+          %9 = OpTypePointer Function %8
+         %11 = OpConstant %6 3
+         %12 = OpConstantComposite %7 %11
+         %13 = OpConstantComposite %8 %12
+         %14 = OpTypePointer Function %7
+         %16 = OpConstant %6 0
+         %19 = OpTypePointer Function %6
+         %15 = OpTypeBool
+         %18 = OpConstantTrue %15
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %10 = OpVariable %9 Function
+         %20 = OpVariable %19 Function
+               OpStore %10 %13
+               OpBranch %23
+         %23 = OpLabel
+               OpLoopMerge %25 %26 None
+               OpBranch %27
+         %27 = OpLabel
+               OpSelectionMerge %28 None
+               OpBranchConditional %18 %29 %25
+         %29 = OpLabel
+         %17 = OpAccessChain %14 %10 %16
+               OpBranch %28
+         %28 = OpLabel
+         %21 = OpAccessChain %19 %17 %16
+         %22 = OpLoad %6 %21
+         %24 = OpAccessChain %19 %10 %16 %16
+               OpStore %24 %22
+               OpBranch %25
+         %26 = OpLabel
+               OpBranch %23
+         %25 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+
+  ASSERT_EQ(1, ops.size());
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+
+  CheckValid(env, context.get());
+
+  std::string expected = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeStruct %6
+          %8 = OpTypeStruct %7
+          %9 = OpTypePointer Function %8
+         %11 = OpConstant %6 3
+         %12 = OpConstantComposite %7 %11
+         %13 = OpConstantComposite %8 %12
+         %14 = OpTypePointer Function %7
+         %16 = OpConstant %6 0
+         %19 = OpTypePointer Function %6
+         %15 = OpTypeBool
+         %18 = OpConstantTrue %15
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %10 = OpVariable %9 Function
+         %20 = OpVariable %19 Function
+         %30 = OpVariable %14 Function
+               OpStore %10 %13
+               OpBranch %23
+         %23 = OpLabel
+               OpSelectionMerge %25 None
+               OpBranchConditional %18 %27 %25
+         %27 = OpLabel
+               OpSelectionMerge %28 None
+               OpBranchConditional %18 %29 %28
+         %29 = OpLabel
+         %17 = OpAccessChain %14 %10 %16
+               OpBranch %28
+         %28 = OpLabel
+         %21 = OpAccessChain %19 %30 %16
+         %22 = OpLoad %6 %21
+         %24 = OpAccessChain %19 %10 %16 %16
+               OpStore %24 %22
+               OpBranch %25
+         %26 = OpLabel
+               OpBranch %23
+         %25 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  CheckEqual(env, expected, context.get());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest,
+     UnreachableInnerLoopContinueBranchingToOuterLoopMerge) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %2 = OpFunction %3 None %4
+          %7 = OpLabel
+               OpBranch %8
+          %8 = OpLabel
+               OpLoopMerge %9 %10 None
+               OpBranch %11
+         %11 = OpLabel
+               OpLoopMerge %12 %13 None
+               OpBranch %12
+         %13 = OpLabel
+               OpBranchConditional %6 %9 %11
+         %12 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpBranchConditional %6 %9 %8
+          %9 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+
+  ASSERT_EQ(2, ops.size());
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+
+  CheckValid(env, context.get());
+
+  std::string after_op_0 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %2 = OpFunction %3 None %4
+          %7 = OpLabel
+               OpBranch %8
+          %8 = OpLabel
+               OpSelectionMerge %9 None
+               OpBranchConditional %6 %11 %9
+         %11 = OpLabel
+               OpLoopMerge %12 %13 None
+               OpBranch %12
+         %13 = OpLabel
+               OpBranchConditional %6 %9 %11
+         %12 = OpLabel
+               OpBranch %9
+         %10 = OpLabel
+               OpBranchConditional %6 %9 %8
+          %9 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  CheckEqual(env, after_op_0, context.get());
+
+  ASSERT_TRUE(ops[1]->PreconditionHolds());
+  ops[1]->TryToApply();
+
+  CheckValid(env, context.get());
+
+  std::string after_op_1 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %2 = OpFunction %3 None %4
+          %7 = OpLabel
+               OpBranch %8
+          %8 = OpLabel
+               OpSelectionMerge %9 None
+               OpBranchConditional %6 %11 %9
+         %11 = OpLabel
+               OpSelectionMerge %12 None
+               OpBranchConditional %6 %12 %12
+         %13 = OpLabel
+               OpBranchConditional %6 %9 %11
+         %12 = OpLabel
+               OpBranch %9
+         %10 = OpLabel
+               OpBranchConditional %6 %9 %8
+          %9 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  CheckEqual(env, after_op_1, context.get());
+}
+
+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.
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %2 = OpFunction %3 None %4
+          %7 = OpLabel
+               OpBranch %8
+          %8 = OpLabel
+               OpLoopMerge %9 %10 None
+               OpBranch %11
+         %11 = OpLabel
+               OpLoopMerge %12 %13 None
+               OpBranch %12
+         %13 = OpLabel
+               OpSelectionMerge %14 None
+               OpBranchConditional %6 %9 %14
+         %14 = OpLabel
+               OpBranch %11
+         %12 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpBranchConditional %6 %9 %8
+          %9 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+
+  ASSERT_EQ(2, ops.size());
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+
+  CheckValid(env, context.get());
+
+  std::string after_op_0 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %2 = OpFunction %3 None %4
+          %7 = OpLabel
+               OpBranch %8
+          %8 = OpLabel
+               OpSelectionMerge %9 None
+               OpBranchConditional %6 %11 %9
+         %11 = OpLabel
+               OpLoopMerge %12 %13 None
+               OpBranch %12
+         %13 = OpLabel
+               OpSelectionMerge %14 None
+               OpBranchConditional %6 %9 %14
+         %14 = OpLabel
+               OpBranch %11
+         %12 = OpLabel
+               OpBranch %9
+         %10 = OpLabel
+               OpBranchConditional %6 %9 %8
+          %9 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  CheckEqual(env, after_op_0, context.get());
+
+  ASSERT_TRUE(ops[1]->PreconditionHolds());
+  ops[1]->TryToApply();
+
+  CheckValid(env, context.get());
+
+  std::string after_op_1 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %2 = OpFunction %3 None %4
+          %7 = OpLabel
+               OpBranch %8
+          %8 = OpLabel
+               OpSelectionMerge %9 None
+               OpBranchConditional %6 %11 %9
+         %11 = OpLabel
+               OpSelectionMerge %12 None
+               OpBranchConditional %6 %12 %12
+         %13 = OpLabel
+               OpSelectionMerge %14 None
+               OpBranchConditional %6 %9 %14
+         %14 = OpLabel
+               OpBranch %11
+         %12 = OpLabel
+               OpBranch %9
+         %10 = OpLabel
+               OpBranchConditional %6 %9 %8
+          %9 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  CheckEqual(env, after_op_1, context.get());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest,
+     InnerLoopHeaderBranchesToOuterLoopMerge) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %2 = OpFunction %3 None %4
+          %7 = OpLabel
+               OpBranch %8
+          %8 = OpLabel
+               OpLoopMerge %9 %10 None
+               OpBranch %11
+         %11 = OpLabel
+               OpLoopMerge %12 %13 None
+               OpBranchConditional %6 %9 %13
+         %13 = OpLabel
+               OpBranchConditional %6 %11 %12
+         %12 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpBranchConditional %6 %9 %8
+          %9 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+  auto ops = pass.WrapGetAvailableOpportunities(context.get());
+
+  // We cannot transform the inner loop due to its header jumping straight to
+  // the outer loop merge (the inner loop's merge does not post-dominate its
+  // header).
+  ASSERT_EQ(1, ops.size());
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+
+  CheckValid(env, context.get());
+
+  std::string after_op_0 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %2 = OpFunction %3 None %4
+          %7 = OpLabel
+               OpBranch %8
+          %8 = OpLabel
+               OpSelectionMerge %9 None
+               OpBranchConditional %6 %11 %9
+         %11 = OpLabel
+               OpLoopMerge %12 %13 None
+               OpBranchConditional %6 %12 %13
+         %13 = OpLabel
+               OpBranchConditional %6 %11 %12
+         %12 = OpLabel
+               OpBranch %9
+         %10 = OpLabel
+               OpBranchConditional %6 %9 %8
+          %9 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  CheckEqual(env, after_op_0, context.get());
+
+  // Now look again for more opportunities.
+  ops = pass.WrapGetAvailableOpportunities(context.get());
+
+  // What was the inner loop should now be transformable, as the jump to the
+  // outer loop's merge has been redirected.
+  ASSERT_EQ(1, ops.size());
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+
+  CheckValid(env, context.get());
+
+  std::string after_another_op_0 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %2 = OpFunction %3 None %4
+          %7 = OpLabel
+               OpBranch %8
+          %8 = OpLabel
+               OpSelectionMerge %9 None
+               OpBranchConditional %6 %11 %9
+         %11 = OpLabel
+               OpSelectionMerge %12 None
+               OpBranchConditional %6 %12 %12
+         %13 = OpLabel
+               OpBranchConditional %6 %11 %12
+         %12 = OpLabel
+               OpBranch %9
+         %10 = OpLabel
+               OpBranchConditional %6 %9 %8
+          %9 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  CheckEqual(env, after_another_op_0, context.get());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest, LongAccessChains) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypeInt 32 0
+          %7 = OpConstant %6 5
+          %8 = OpTypeArray %5 %7
+          %9 = OpTypeStruct %8
+         %10 = OpTypeStruct %9 %9
+         %11 = OpConstant %6 2
+         %12 = OpTypeArray %10 %11
+         %13 = OpTypeStruct %12
+         %14 = OpTypePointer Function %13
+         %15 = OpConstant %5 0
+         %16 = OpConstant %5 1
+         %17 = OpConstant %5 2
+         %18 = OpConstant %5 3
+         %19 = OpConstant %5 4
+         %20 = OpConstantComposite %8 %15 %16 %17 %18 %19
+         %21 = OpConstantComposite %9 %20
+         %22 = OpConstant %5 5
+         %23 = OpConstant %5 6
+         %24 = OpConstant %5 7
+         %25 = OpConstant %5 8
+         %26 = OpConstant %5 9
+         %27 = OpConstantComposite %8 %22 %23 %24 %25 %26
+         %28 = OpConstantComposite %9 %27
+         %29 = OpConstantComposite %10 %21 %28
+         %30 = OpConstant %5 10
+         %31 = OpConstant %5 11
+         %32 = OpConstant %5 12
+         %33 = OpConstant %5 13
+         %34 = OpConstant %5 14
+         %35 = OpConstantComposite %8 %30 %31 %32 %33 %34
+         %36 = OpConstantComposite %9 %35
+         %37 = OpConstant %5 15
+         %38 = OpConstant %5 16
+         %39 = OpConstant %5 17
+         %40 = OpConstant %5 18
+         %41 = OpConstant %5 19
+         %42 = OpConstantComposite %8 %37 %38 %39 %40 %41
+         %43 = OpConstantComposite %9 %42
+         %44 = OpConstantComposite %10 %36 %43
+         %45 = OpConstantComposite %12 %29 %44
+         %46 = OpConstantComposite %13 %45
+         %47 = OpTypePointer Function %12
+         %48 = OpTypePointer Function %10
+         %49 = OpTypePointer Function %9
+         %50 = OpTypePointer Function %8
+         %51 = OpTypePointer Function %5
+         %52 = OpTypeBool
+         %53 = OpConstantTrue %52
+          %2 = OpFunction %3 None %4
+         %54 = OpLabel
+         %55 = OpVariable %14 Function
+               OpStore %55 %46
+               OpBranch %56
+         %56 = OpLabel
+               OpLoopMerge %57 %58 None
+               OpBranchConditional %53 %57 %59
+         %59 = OpLabel
+               OpSelectionMerge %60 None
+               OpBranchConditional %53 %61 %57
+         %61 = OpLabel
+         %62 = OpAccessChain %47 %55 %15
+               OpBranch %63
+         %63 = OpLabel
+               OpSelectionMerge %64 None
+               OpBranchConditional %53 %65 %57
+         %65 = OpLabel
+         %66 = OpAccessChain %48 %62 %16
+               OpBranch %67
+         %67 = OpLabel
+               OpSelectionMerge %68 None
+               OpBranchConditional %53 %69 %57
+         %69 = OpLabel
+         %70 = OpAccessChain %49 %66 %16
+               OpBranch %71
+         %71 = OpLabel
+               OpSelectionMerge %72 None
+               OpBranchConditional %53 %73 %57
+         %73 = OpLabel
+         %74 = OpAccessChain %50 %70 %15
+               OpBranch %75
+         %75 = OpLabel
+               OpSelectionMerge %76 None
+               OpBranchConditional %53 %77 %57
+         %77 = OpLabel
+         %78 = OpAccessChain %51 %74 %17
+               OpBranch %79
+         %79 = OpLabel
+               OpSelectionMerge %80 None
+               OpBranchConditional %53 %81 %57
+         %81 = OpLabel
+         %82 = OpLoad %5 %78
+               OpBranch %80
+         %80 = OpLabel
+               OpBranch %76
+         %76 = OpLabel
+               OpBranch %72
+         %72 = OpLabel
+               OpBranch %68
+         %68 = OpLabel
+               OpBranch %64
+         %64 = OpLabel
+               OpBranch %60
+         %60 = OpLabel
+               OpBranch %58
+         %58 = OpLabel
+               OpBranch %56
+         %57 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+  auto ops = pass.WrapGetAvailableOpportunities(context.get());
+
+  ASSERT_EQ(1, ops.size());
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+
+  CheckValid(env, context.get());
+
+  // TODO(2183): When we have a more general solution for handling access
+  // chains, write an expected result for this test.
+  // std::string expected = R"(
+  // Expected text for transformed shader
+  //)";
+  // CheckEqual(env, expected, context.get());
+}
+
+}  // namespace
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/test/reduce/validation_during_reduction_test.cpp b/test/reduce/validation_during_reduction_test.cpp
new file mode 100644
index 0000000..bb7d14e
--- /dev/null
+++ b/test/reduce/validation_during_reduction_test.cpp
@@ -0,0 +1,376 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "reduce_test_util.h"
+
+#include "source/reduce/reducer.h"
+#include "source/reduce/reduction_pass.h"
+#include "source/reduce/remove_instruction_reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+namespace {
+
+// A dumb reduction pass that removes global values regardless of whether they
+// are referenced. This is very likely to make the resulting module invalid.  We
+// use this to test the reducer's behavior in the scenario where a bad reduction
+// pass leads to an invalid module.
+class BlindlyRemoveGlobalValuesPass : public ReductionPass {
+ public:
+  // Creates the reduction pass in the context of the given target environment
+  // |target_env|
+  explicit BlindlyRemoveGlobalValuesPass(const spv_target_env target_env)
+      : ReductionPass(target_env) {}
+
+  ~BlindlyRemoveGlobalValuesPass() override = default;
+
+  // The name of this pass.
+  std::string GetName() const final { return "BlindlyRemoveGlobalValuesPass"; };
+
+ protected:
+  // Adds opportunities to remove all global values.  Assuming they are all
+  // referenced (directly or indirectly) from elsewhere in the module, each such
+  // opportunity will make the module invalid.
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      opt::IRContext* context) const final {
+    std::vector<std::unique_ptr<ReductionOpportunity>> result;
+    for (auto& inst : context->module()->types_values()) {
+      if (inst.HasResultId()) {
+        result.push_back(
+            MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
+      }
+    }
+    return result;
+  }
+};
+
+TEST(ValidationDuringReductionTest, CheckInvalidPassMakesNoProgress) {
+  // A module whose global values are all referenced, so that any application of
+  // MakeModuleInvalidPass will make the module invalid.
+  std::string original = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %60
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %16 "buf2"
+               OpMemberName %16 0 "i"
+               OpName %18 ""
+               OpName %25 "buf1"
+               OpMemberName %25 0 "f"
+               OpName %27 ""
+               OpName %60 "_GLF_color"
+               OpMemberDecorate %16 0 Offset 0
+               OpDecorate %16 Block
+               OpDecorate %18 DescriptorSet 0
+               OpDecorate %18 Binding 2
+               OpMemberDecorate %25 0 Offset 0
+               OpDecorate %25 Block
+               OpDecorate %27 DescriptorSet 0
+               OpDecorate %27 Binding 1
+               OpDecorate %60 Location 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %9 = OpConstant %6 0
+         %16 = OpTypeStruct %6
+         %17 = OpTypePointer Uniform %16
+         %18 = OpVariable %17 Uniform
+         %19 = OpTypePointer Uniform %6
+         %22 = OpTypeBool
+         %24 = OpTypeFloat 32
+         %25 = OpTypeStruct %24
+         %26 = OpTypePointer Uniform %25
+         %27 = OpVariable %26 Uniform
+         %28 = OpTypePointer Uniform %24
+         %31 = OpConstant %24 2
+         %56 = OpConstant %6 1
+         %58 = OpTypeVector %24 4
+         %59 = OpTypePointer Output %58
+         %60 = OpVariable %59 Output
+         %72 = OpUndef %24
+         %74 = OpUndef %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+         %73 = OpPhi %6 %74 %5 %77 %34
+         %71 = OpPhi %24 %72 %5 %76 %34
+         %70 = OpPhi %6 %9 %5 %57 %34
+         %20 = OpAccessChain %19 %18 %9
+         %21 = OpLoad %6 %20
+         %23 = OpSLessThan %22 %70 %21
+               OpLoopMerge %12 %34 None
+               OpBranchConditional %23 %11 %12
+         %11 = OpLabel
+         %29 = OpAccessChain %28 %27 %9
+         %30 = OpLoad %24 %29
+         %32 = OpFOrdGreaterThan %22 %30 %31
+               OpSelectionMerge %34 None
+               OpBranchConditional %32 %33 %46
+         %33 = OpLabel
+         %40 = OpFAdd %24 %71 %30
+         %45 = OpISub %6 %73 %21
+               OpBranch %34
+         %46 = OpLabel
+         %50 = OpFMul %24 %71 %30
+         %54 = OpSDiv %6 %73 %21
+               OpBranch %34
+         %34 = OpLabel
+         %77 = OpPhi %6 %45 %33 %54 %46
+         %76 = OpPhi %24 %40 %33 %50 %46
+         %57 = OpIAdd %6 %70 %56
+               OpBranch %10
+         %12 = OpLabel
+         %61 = OpAccessChain %28 %27 %9
+         %62 = OpLoad %24 %61
+         %66 = OpConvertSToF %24 %21
+         %68 = OpConvertSToF %24 %73
+         %69 = OpCompositeConstruct %58 %62 %71 %66 %68
+               OpStore %60 %69
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
+  Reducer reducer(env);
+  reducer.SetMessageConsumer(NopDiagnostic);
+
+  // Say that every module is interesting.
+  reducer.SetInterestingnessFunction(
+      [](const std::vector<uint32_t>&, uint32_t) -> bool { return true; });
+
+  reducer.AddReductionPass(MakeUnique<BlindlyRemoveGlobalValuesPass>(env));
+
+  std::vector<uint32_t> binary_in;
+  SpirvTools t(env);
+
+  ASSERT_TRUE(t.Assemble(original, &binary_in, kReduceAssembleOption));
+  std::vector<uint32_t> binary_out;
+  spvtools::ReducerOptions reducer_options;
+  reducer_options.set_step_limit(500);
+
+  reducer.Run(std::move(binary_in), &binary_out, reducer_options);
+
+  // The reducer should have no impact.
+  CheckEqual(env, original, binary_out);
+}
+
+TEST(ValidationDuringReductionTest, CheckNotAlwaysInvalidCanMakeProgress) {
+  // A module with just one unreferenced global value.  All but one application
+  // of MakeModuleInvalidPass will make the module invalid.
+  std::string original = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %60
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %16 "buf2"
+               OpMemberName %16 0 "i"
+               OpName %18 ""
+               OpName %25 "buf1"
+               OpMemberName %25 0 "f"
+               OpName %27 ""
+               OpName %60 "_GLF_color"
+               OpMemberDecorate %16 0 Offset 0
+               OpDecorate %16 Block
+               OpDecorate %18 DescriptorSet 0
+               OpDecorate %18 Binding 2
+               OpMemberDecorate %25 0 Offset 0
+               OpDecorate %25 Block
+               OpDecorate %27 DescriptorSet 0
+               OpDecorate %27 Binding 1
+               OpDecorate %60 Location 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %9 = OpConstant %6 0
+         %16 = OpTypeStruct %6
+         %17 = OpTypePointer Uniform %16
+         %18 = OpVariable %17 Uniform
+         %19 = OpTypePointer Uniform %6
+         %22 = OpTypeBool
+         %24 = OpTypeFloat 32
+         %25 = OpTypeStruct %24
+         %26 = OpTypePointer Uniform %25
+         %27 = OpVariable %26 Uniform
+         %28 = OpTypePointer Uniform %24
+         %31 = OpConstant %24 2
+         %56 = OpConstant %6 1
+       %1000 = OpConstant %6 1000 ; It should be possible to remove this instruction without making the module invalid.
+         %58 = OpTypeVector %24 4
+         %59 = OpTypePointer Output %58
+         %60 = OpVariable %59 Output
+         %72 = OpUndef %24
+         %74 = OpUndef %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+         %73 = OpPhi %6 %74 %5 %77 %34
+         %71 = OpPhi %24 %72 %5 %76 %34
+         %70 = OpPhi %6 %9 %5 %57 %34
+         %20 = OpAccessChain %19 %18 %9
+         %21 = OpLoad %6 %20
+         %23 = OpSLessThan %22 %70 %21
+               OpLoopMerge %12 %34 None
+               OpBranchConditional %23 %11 %12
+         %11 = OpLabel
+         %29 = OpAccessChain %28 %27 %9
+         %30 = OpLoad %24 %29
+         %32 = OpFOrdGreaterThan %22 %30 %31
+               OpSelectionMerge %34 None
+               OpBranchConditional %32 %33 %46
+         %33 = OpLabel
+         %40 = OpFAdd %24 %71 %30
+         %45 = OpISub %6 %73 %21
+               OpBranch %34
+         %46 = OpLabel
+         %50 = OpFMul %24 %71 %30
+         %54 = OpSDiv %6 %73 %21
+               OpBranch %34
+         %34 = OpLabel
+         %77 = OpPhi %6 %45 %33 %54 %46
+         %76 = OpPhi %24 %40 %33 %50 %46
+         %57 = OpIAdd %6 %70 %56
+               OpBranch %10
+         %12 = OpLabel
+         %61 = OpAccessChain %28 %27 %9
+         %62 = OpLoad %24 %61
+         %66 = OpConvertSToF %24 %21
+         %68 = OpConvertSToF %24 %73
+         %69 = OpCompositeConstruct %58 %62 %71 %66 %68
+               OpStore %60 %69
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  // This is the same as the original, except that the constant declaration of
+  // 1000 is gone.
+  std::string expected = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %60
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %16 "buf2"
+               OpMemberName %16 0 "i"
+               OpName %18 ""
+               OpName %25 "buf1"
+               OpMemberName %25 0 "f"
+               OpName %27 ""
+               OpName %60 "_GLF_color"
+               OpMemberDecorate %16 0 Offset 0
+               OpDecorate %16 Block
+               OpDecorate %18 DescriptorSet 0
+               OpDecorate %18 Binding 2
+               OpMemberDecorate %25 0 Offset 0
+               OpDecorate %25 Block
+               OpDecorate %27 DescriptorSet 0
+               OpDecorate %27 Binding 1
+               OpDecorate %60 Location 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %9 = OpConstant %6 0
+         %16 = OpTypeStruct %6
+         %17 = OpTypePointer Uniform %16
+         %18 = OpVariable %17 Uniform
+         %19 = OpTypePointer Uniform %6
+         %22 = OpTypeBool
+         %24 = OpTypeFloat 32
+         %25 = OpTypeStruct %24
+         %26 = OpTypePointer Uniform %25
+         %27 = OpVariable %26 Uniform
+         %28 = OpTypePointer Uniform %24
+         %31 = OpConstant %24 2
+         %56 = OpConstant %6 1
+         %58 = OpTypeVector %24 4
+         %59 = OpTypePointer Output %58
+         %60 = OpVariable %59 Output
+         %72 = OpUndef %24
+         %74 = OpUndef %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+         %73 = OpPhi %6 %74 %5 %77 %34
+         %71 = OpPhi %24 %72 %5 %76 %34
+         %70 = OpPhi %6 %9 %5 %57 %34
+         %20 = OpAccessChain %19 %18 %9
+         %21 = OpLoad %6 %20
+         %23 = OpSLessThan %22 %70 %21
+               OpLoopMerge %12 %34 None
+               OpBranchConditional %23 %11 %12
+         %11 = OpLabel
+         %29 = OpAccessChain %28 %27 %9
+         %30 = OpLoad %24 %29
+         %32 = OpFOrdGreaterThan %22 %30 %31
+               OpSelectionMerge %34 None
+               OpBranchConditional %32 %33 %46
+         %33 = OpLabel
+         %40 = OpFAdd %24 %71 %30
+         %45 = OpISub %6 %73 %21
+               OpBranch %34
+         %46 = OpLabel
+         %50 = OpFMul %24 %71 %30
+         %54 = OpSDiv %6 %73 %21
+               OpBranch %34
+         %34 = OpLabel
+         %77 = OpPhi %6 %45 %33 %54 %46
+         %76 = OpPhi %24 %40 %33 %50 %46
+         %57 = OpIAdd %6 %70 %56
+               OpBranch %10
+         %12 = OpLabel
+         %61 = OpAccessChain %28 %27 %9
+         %62 = OpLoad %24 %61
+         %66 = OpConvertSToF %24 %21
+         %68 = OpConvertSToF %24 %73
+         %69 = OpCompositeConstruct %58 %62 %71 %66 %68
+               OpStore %60 %69
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
+  Reducer reducer(env);
+  reducer.SetMessageConsumer(NopDiagnostic);
+
+  // Say that every module is interesting.
+  reducer.SetInterestingnessFunction(
+      [](const std::vector<uint32_t>&, uint32_t) -> bool { return true; });
+
+  reducer.AddReductionPass(MakeUnique<BlindlyRemoveGlobalValuesPass>(env));
+
+  std::vector<uint32_t> binary_in;
+  SpirvTools t(env);
+
+  ASSERT_TRUE(t.Assemble(original, &binary_in, kReduceAssembleOption));
+  std::vector<uint32_t> binary_out;
+  spvtools::ReducerOptions reducer_options;
+  reducer_options.set_step_limit(500);
+
+  reducer.Run(std::move(binary_in), &binary_out, reducer_options);
+  CheckEqual(env, expected, binary_out);
+}
+
+}  // namespace
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/test/val/val_adjacency_test.cpp b/test/val/val_adjacency_test.cpp
index 10002ef..5c1124a 100644
--- a/test/val/val_adjacency_test.cpp
+++ b/test/val/val_adjacency_test.cpp
@@ -53,7 +53,8 @@
 
   CompileSuccessfully(module);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("ID 1 has not been defined"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ID 1[%bool] has not been defined"));
 }
 
 TEST_F(ValidateAdjacency, OpLoopMergeEndsModuleFail) {
diff --git a/test/val/val_arithmetics_test.cpp b/test/val/val_arithmetics_test.cpp
index c43d101..87e006c 100644
--- a/test/val/val_arithmetics_test.cpp
+++ b/test/val/val_arithmetics_test.cpp
@@ -606,7 +606,8 @@
 
   CompileSuccessfully(GenerateCode(body).c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 6 cannot be a type"));
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 6[%float] cannot be a "
+                                               "type"));
 }
 
 TEST_F(ValidateArithmetics, DotNotVectorTypeOperand2) {
diff --git a/test/val/val_atomics_test.cpp b/test/val/val_atomics_test.cpp
index a493633..98a2149 100644
--- a/test/val/val_atomics_test.cpp
+++ b/test/val/val_atomics_test.cpp
@@ -28,16 +28,13 @@
 
 using ValidateAtomics = spvtest::ValidateBase<bool>;
 
-std::string GenerateShaderCode(
-    const std::string& body,
-    const std::string& capabilities_and_extensions = "",
-    const std::string& memory_model = "GLSL450") {
+std::string GenerateShaderCodeImpl(
+    const std::string& body, const std::string& capabilities_and_extensions,
+    const std::string& definitions, const std::string& memory_model) {
   std::ostringstream ss;
   ss << R"(
 OpCapability Shader
-OpCapability Int64
 )";
-
   ss << capabilities_and_extensions;
   ss << "OpMemoryModel Logical " << memory_model << "\n";
   ss << R"(
@@ -48,16 +45,12 @@
 %bool = OpTypeBool
 %f32 = OpTypeFloat 32
 %u32 = OpTypeInt 32 0
-%u64 = OpTypeInt 64 0
-%s64 = OpTypeInt 64 1
 %f32vec4 = OpTypeVector %f32 4
 
 %f32_0 = OpConstant %f32 0
 %f32_1 = OpConstant %f32 1
 %u32_0 = OpConstant %u32 0
 %u32_1 = OpConstant %u32 1
-%u64_1 = OpConstant %u64 1
-%s64_1 = OpConstant %s64 1
 %f32vec4_0000 = OpConstantComposite %f32vec4 %f32_0 %f32_0 %f32_0 %f32_0
 
 %cross_device = OpConstant %u32 0
@@ -81,22 +74,17 @@
 %u32_ptr = OpTypePointer Workgroup %u32
 %u32_var = OpVariable %u32_ptr Workgroup
 
-%u64_ptr = OpTypePointer Workgroup %u64
-%s64_ptr = OpTypePointer Workgroup %s64
-%u64_var = OpVariable %u64_ptr Workgroup
-%s64_var = OpVariable %s64_ptr Workgroup
-
 %f32vec4_ptr = OpTypePointer Workgroup %f32vec4
 %f32vec4_var = OpVariable %f32vec4_ptr Workgroup
 
 %f32_ptr_function = OpTypePointer Function %f32
-
+)";
+  ss << definitions;
+  ss << R"(
 %main = OpFunction %void None %func
 %main_entry = OpLabel
 )";
-
   ss << body;
-
   ss << R"(
 OpReturn
 OpFunctionEnd)";
@@ -104,6 +92,44 @@
   return ss.str();
 }
 
+std::string GenerateShaderCode(
+    const std::string& body,
+    const std::string& capabilities_and_extensions = "",
+    const std::string& memory_model = "GLSL450") {
+  const std::string defintions = R"(
+%u64 = OpTypeInt 64 0
+%s64 = OpTypeInt 64 1
+
+%u64_1 = OpConstant %u64 1
+%s64_1 = OpConstant %s64 1
+
+%u64_ptr = OpTypePointer Workgroup %u64
+%s64_ptr = OpTypePointer Workgroup %s64
+%u64_var = OpVariable %u64_ptr Workgroup
+%s64_var = OpVariable %s64_ptr Workgroup
+)";
+  return GenerateShaderCodeImpl(
+      body, "OpCapability Int64\n" + capabilities_and_extensions, defintions,
+      memory_model);
+}
+
+std::string GenerateWebGPUShaderCode(
+    const std::string& body,
+    const std::string& capabilities_and_extensions = "") {
+  const std::string vulkan_memory_capability = R"(
+OpCapability VulkanMemoryModelDeviceScopeKHR
+OpCapability VulkanMemoryModelKHR
+)";
+  const std::string vulkan_memory_extension = R"(
+OpExtension "SPV_KHR_vulkan_memory_model"
+)";
+  return GenerateShaderCodeImpl(body,
+                                vulkan_memory_capability +
+                                    capabilities_and_extensions +
+                                    vulkan_memory_extension,
+                                "", "VulkanKHR");
+}
+
 std::string GenerateKernelCode(
     const std::string& body,
     const std::string& capabilities_and_extensions = "") {
@@ -312,6 +338,32 @@
           "AtomicLoad: 64-bit atomics require the Int64Atomics capability"));
 }
 
+TEST_F(ValidateAtomics, AtomicLoadWebGPUShaderSuccess) {
+  const std::string body = R"(
+%val1 = OpAtomicLoad %u32 %u32_var %device %relaxed
+%val2 = OpAtomicLoad %u32 %u32_var %workgroup %acquire
+)";
+
+  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
+}
+
+TEST_F(ValidateAtomics, AtomicLoadWebGPUShaderSequentiallyConsistentFailure) {
+  const std::string body = R"(
+%val3 = OpAtomicLoad %u32 %u32_var %subgroup %sequentially_consistent
+)";
+
+  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "WebGPU spec disallows any bit masks in Memory Semantics that are "
+          "not Acquire, Release, AcquireRelease, UniformMemory, "
+          "WorkgroupMemory, ImageMemory, OutputMemoryKHR, MakeAvailableKHR, or "
+          "MakeVisibleKHR\n  %34 = OpAtomicLoad %uint %29 %uint_3 %uint_16\n"));
+}
+
 TEST_F(ValidateAtomics, VK_KHR_shader_atomic_int64Success) {
   const std::string body = R"(
 %val1 = OpAtomicUMin %u64 %u64_var %device %relaxed %u64_1
@@ -380,7 +432,8 @@
 
   CompileSuccessfully(GenerateKernelCode(body));
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 27 cannot be a type"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Operand 27[%_ptr_Workgroup_float] cannot be a type"));
 }
 
 TEST_F(ValidateAtomics, AtomicLoadWrongPointerDataType) {
@@ -418,7 +471,7 @@
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("AtomicLoad: expected Memory Semantics to be 32-bit int"));
+      HasSubstr("AtomicLoad: expected Memory Semantics to be a 32-bit int"));
 }
 
 TEST_F(ValidateAtomics, AtomicStoreKernelSuccess) {
@@ -489,6 +542,31 @@
                 "Acquire, AcquireRelease and SequentiallyConsistent"));
 }
 
+TEST_F(ValidateAtomics, AtomicStoreWebGPUSuccess) {
+  const std::string body = R"(
+OpAtomicStore %u32_var %device %release %u32_1
+)";
+
+  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
+}
+
+TEST_F(ValidateAtomics, AtomicStoreWebGPUSequentiallyConsistent) {
+  const std::string body = R"(
+OpAtomicStore %u32_var %device %sequentially_consistent %u32_1
+)";
+
+  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "WebGPU spec disallows any bit masks in Memory Semantics that are "
+          "not Acquire, Release, AcquireRelease, UniformMemory, "
+          "WorkgroupMemory, ImageMemory, OutputMemoryKHR, MakeAvailableKHR, or "
+          "MakeVisibleKHR\n  OpAtomicStore %29 %uint_1_0 %uint_16 %uint_1\n"));
+}
+
 TEST_F(ValidateAtomics, AtomicStoreWrongPointerType) {
   const std::string body = R"(
 OpAtomicStore %f32_1 %device %relaxed %f32_1
@@ -551,7 +629,7 @@
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("AtomicStore: expected Memory Semantics to be 32-bit int"));
+      HasSubstr("AtomicStore: expected Memory Semantics to be a 32-bit int"));
 }
 
 TEST_F(ValidateAtomics, AtomicStoreWrongValueType) {
@@ -623,7 +701,9 @@
 
   CompileSuccessfully(GenerateKernelCode(body));
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 33 cannot be a type"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Operand 33[%_ptr_Workgroup_v4float] cannot be a "
+                        "type"));
 }
 
 TEST_F(ValidateAtomics, AtomicExchangeWrongPointerDataType) {
@@ -665,7 +745,8 @@
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("AtomicExchange: expected Memory Semantics to be 32-bit int"));
+      HasSubstr(
+          "AtomicExchange: expected Memory Semantics to be a 32-bit int"));
 }
 
 TEST_F(ValidateAtomics, AtomicExchangeWrongValueType) {
@@ -736,7 +817,9 @@
 
   CompileSuccessfully(GenerateKernelCode(body));
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 33 cannot be a type"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Operand 33[%_ptr_Workgroup_v4float] cannot be a "
+                        "type"));
 }
 
 TEST_F(ValidateAtomics, AtomicCompareExchangeWrongPointerDataType) {
@@ -776,10 +859,9 @@
 
   CompileSuccessfully(GenerateKernelCode(body));
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr(
-          "AtomicCompareExchange: expected Memory Semantics to be 32-bit int"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("AtomicCompareExchange: expected Memory Semantics to "
+                        "be a 32-bit int"));
 }
 
 TEST_F(ValidateAtomics, AtomicCompareExchangeWrongMemorySemanticsType2) {
@@ -790,10 +872,9 @@
 
   CompileSuccessfully(GenerateKernelCode(body));
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr(
-          "AtomicCompareExchange: expected Memory Semantics to be 32-bit int"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("AtomicCompareExchange: expected Memory Semantics to "
+                        "be a 32-bit int"));
 }
 
 TEST_F(ValidateAtomics, AtomicCompareExchangeUnequalRelease) {
@@ -961,7 +1042,7 @@
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("AtomicFlagTestAndSet: "
-                        "expected Memory Semantics to be 32-bit int"));
+                        "expected Memory Semantics to be a 32-bit int"));
 }
 
 TEST_F(ValidateAtomics, AtomicFlagClearAcquire) {
@@ -1035,7 +1116,8 @@
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("AtomicFlagClear: expected Memory Semantics to be 32-bit int"));
+      HasSubstr(
+          "AtomicFlagClear: expected Memory Semantics to be a 32-bit int"));
 }
 
 TEST_F(ValidateAtomics, AtomicIIncrementAcquireAndRelease) {
@@ -1046,11 +1128,11 @@
 
   CompileSuccessfully(GenerateKernelCode(body));
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("AtomicIIncrement: no more than one of the following Memory "
-                "Semantics bits can be set at the same time: Acquire, Release, "
-                "AcquireRelease or SequentiallyConsistent"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("AtomicIIncrement: Memory Semantics can have at most "
+                        "one of the following bits set: Acquire, Release, "
+                        "AcquireRelease or SequentiallyConsistent\n  %40 = "
+                        "OpAtomicIIncrement %uint %30 %uint_1_0 %uint_6\n"));
 }
 
 TEST_F(ValidateAtomics, AtomicUniformMemorySemanticsShader) {
@@ -1076,18 +1158,24 @@
                         "requires capability Shader"));
 }
 
-TEST_F(ValidateAtomics, AtomicCounterMemorySemanticsNoCapability) {
-  const std::string body = R"(
-OpAtomicStore %u32_var %device %relaxed %u32_1
-%val1 = OpAtomicIIncrement %u32 %u32_var %device %acquire_release_atomic_counter_workgroup
-)";
-
-  CompileSuccessfully(GenerateKernelCode(body));
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("AtomicIIncrement: Memory Semantics UniformMemory "
-                        "requires capability AtomicStorage"));
-}
+// Disabling this test until
+// https://github.com/KhronosGroup/glslang/issues/1618 is resolved.
+// TEST_F(ValidateAtomics, AtomicCounterMemorySemanticsNoCapability) {
+//  const std::string body = R"(
+// OpAtomicStore %u32_var %device %relaxed %u32_1
+//%val1 = OpAtomicIIncrement %u32 %u32_var %device
+//%acquire_release_atomic_counter_workgroup
+//)";
+//
+//  CompileSuccessfully(GenerateKernelCode(body));
+//  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+//  EXPECT_THAT(
+//      getDiagnosticString(),
+//      HasSubstr("AtomicIIncrement: Memory Semantics AtomicCounterMemory "
+//                "requires capability AtomicStorage\n  %40 = OpAtomicIIncrement
+//                "
+//                "%uint %30 %uint_1_0 %uint_1288\n"));
+//}
 
 TEST_F(ValidateAtomics, AtomicCounterMemorySemanticsWithCapability) {
   const std::string body = R"(
@@ -1638,7 +1726,7 @@
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("AtomicAnd: Memory Scope QueueFamilyKHR requires "
                         "capability VulkanMemoryModelKHR\n  %42 = OpAtomicAnd "
-                        "%uint %33 %uint_5 %uint_0_1 %uint_1\n"));
+                        "%uint %29 %uint_5 %uint_0_1 %uint_1\n"));
 }
 
 TEST_F(ValidateAtomics, SemanticsSpecConstantShader) {
@@ -1742,6 +1830,40 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
+TEST_F(ValidateAtomics, VulkanMemoryModelDeviceScopeBad) {
+  const std::string body = R"(
+%val = OpAtomicAnd %u32 %u32_var %device %relaxed %u32_1
+)";
+
+  const std::string extra = R"(OpCapability VulkanMemoryModelKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body, extra, "VulkanKHR"),
+                      SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Use of device scope with VulkanKHR memory model requires the "
+                "VulkanMemoryModelDeviceScopeKHR capability"));
+}
+
+TEST_F(ValidateAtomics, VulkanMemoryModelDeviceScopeGood) {
+  const std::string body = R"(
+%val = OpAtomicAnd %u32 %u32_var %device %relaxed %u32_1
+)";
+
+  const std::string extra = R"(OpCapability VulkanMemoryModelKHR
+OpCapability VulkanMemoryModelDeviceScopeKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body, extra, "VulkanKHR"),
+                      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_barriers_test.cpp b/test/val/val_barriers_test.cpp
index a886a05..264d130 100644
--- a/test/val/val_barriers_test.cpp
+++ b/test/val/val_barriers_test.cpp
@@ -69,6 +69,7 @@
 %workgroup = OpConstant %u32 2
 %subgroup = OpConstant %u32 3
 %invocation = OpConstant %u32 4
+%queuefamily = OpConstant %u32 5
 
 %none = OpConstant %u32 0
 %acquire = OpConstant %u32 2
@@ -174,9 +175,7 @@
 %acquire_release = OpConstant %u32 8
 %acquire_and_release = OpConstant %u32 6
 %sequentially_consistent = OpConstant %u32 16
-%acquire_release_uniform_workgroup = OpConstant %u32 328
-%acquire_and_release_uniform = OpConstant %u32 70
-%uniform = OpConstant %u32 64
+%acquire_release_workgroup = OpConstant %u32 264
 
 %named_barrier = OpTypeNamedBarrier
 
@@ -214,7 +213,7 @@
 OpControlBarrier %workgroup %device %release
 OpControlBarrier %cross_device %cross_device %acquire_release
 OpControlBarrier %cross_device %cross_device %sequentially_consistent
-OpControlBarrier %cross_device %cross_device %acquire_release_uniform_workgroup
+OpControlBarrier %cross_device %cross_device %acquire_release_workgroup
 )";
 
   CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1);
@@ -248,7 +247,7 @@
 
 TEST_F(ValidateBarriers, OpControlBarrierWebGPUSuccess) {
   const std::string body = R"(
-OpControlBarrier %workgroup %device %none
+OpControlBarrier %workgroup %queuefamily %none
 OpControlBarrier %workgroup %workgroup %acquire_release_uniform_workgroup
 )";
 
@@ -614,8 +613,8 @@
 
 TEST_F(ValidateBarriers, OpMemoryBarrierKernelSuccess) {
   const std::string body = R"(
-OpMemoryBarrier %cross_device %acquire_release_uniform_workgroup
-OpMemoryBarrier %device %uniform
+OpMemoryBarrier %cross_device %acquire_release_workgroup
+OpMemoryBarrier %device %none
 )";
 
   CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1);
@@ -803,7 +802,7 @@
 TEST_F(ValidateBarriers, OpMemoryNamedBarrierSuccess) {
   const std::string body = R"(
 %barrier = OpNamedBarrierInitialize %named_barrier %u32_4
-OpMemoryNamedBarrier %barrier %workgroup %acquire_release_uniform_workgroup
+OpMemoryNamedBarrier %barrier %workgroup %acquire_release_workgroup
 )";
 
   CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1);
@@ -812,7 +811,7 @@
 
 TEST_F(ValidateBarriers, OpMemoryNamedBarrierNotNamedBarrier) {
   const std::string body = R"(
-OpMemoryNamedBarrier %u32_1 %workgroup %acquire_release_uniform_workgroup
+OpMemoryNamedBarrier %u32_1 %workgroup %acquire_release_workgroup
 )";
 
   CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1);
@@ -826,7 +825,7 @@
 TEST_F(ValidateBarriers, OpMemoryNamedBarrierFloatMemoryScope) {
   const std::string body = R"(
 %barrier = OpNamedBarrierInitialize %named_barrier %u32_4
-OpMemoryNamedBarrier %barrier %f32_1 %acquire_release_uniform_workgroup
+OpMemoryNamedBarrier %barrier %f32_1 %acquire_release_workgroup
 )";
 
   CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1);
@@ -856,7 +855,7 @@
 TEST_F(ValidateBarriers, OpMemoryNamedBarrierAcquireAndRelease) {
   const std::string body = R"(
 %barrier = OpNamedBarrierInitialize %named_barrier %u32_4
-OpMemoryNamedBarrier %barrier %workgroup %acquire_and_release_uniform
+OpMemoryNamedBarrier %barrier %workgroup %acquire_and_release
 )";
 
   CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1);
@@ -875,7 +874,8 @@
 
   CompileSuccessfully(GenerateKernelCode(body));
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 5 cannot be a type"));
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 5[%uint] cannot be a "
+                                               "type"));
 }
 
 TEST_F(ValidateBarriers,
@@ -891,7 +891,7 @@
 %3 = OpTypeInt 32 0
 %4 = OpConstant %3 16
 %5 = OpTypeFunction %2
-%6 = OpConstant %3 1
+%6 = OpConstant %3 5
 %1 = OpFunction %2 None %5
 %7 = OpLabel
 OpControlBarrier %6 %6 %4
@@ -920,7 +920,7 @@
 %3 = OpTypeInt 32 0
 %4 = OpConstant %3 16
 %5 = OpTypeFunction %2
-%6 = OpConstant %3 1
+%6 = OpConstant %3 5
 %1 = OpFunction %2 None %5
 %7 = OpLabel
 OpMemoryBarrier %6 %4
@@ -1225,6 +1225,60 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
+TEST_F(ValidateBarriers, VulkanMemoryModelDeviceScopeBad) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%semantics = OpConstant %int 0
+%functy = OpTypeFunction %void
+%func = OpFunction %void None %functy
+%1 = OpLabel
+OpMemoryBarrier %device %semantics
+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("Use of device scope with VulkanKHR memory model requires the "
+                "VulkanMemoryModelDeviceScopeKHR capability"));
+}
+
+TEST_F(ValidateBarriers, VulkanMemoryModelDeviceScopeGood) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability VulkanMemoryModelDeviceScopeKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%semantics = OpConstant %int 0
+%functy = OpTypeFunction %void
+%func = OpFunction %void None %functy
+%1 = OpLabel
+OpMemoryBarrier %device %semantics
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, 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_builtins_test.cpp b/test/val/val_builtins_test.cpp
index b1458c9..ec07582 100644
--- a/test/val/val_builtins_test.cpp
+++ b/test/val/val_builtins_test.cpp
@@ -1926,6 +1926,37 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
 }
 
+TEST_F(ValidateBuiltIns, WorkgroupIdNotVec3) {
+  CodeGenerator generator = GetDefaultShaderCodeGenerator();
+  generator.before_types_ = R"(
+OpDecorate %workgroup_size BuiltIn WorkgroupSize
+OpDecorate %workgroup_id BuiltIn WorkgroupId
+)";
+
+  generator.after_types_ = R"(
+%workgroup_size = OpConstantComposite %u32vec3 %u32_1 %u32_1 %u32_1
+     %input_ptr = OpTypePointer Input %u32vec2
+  %workgroup_id = OpVariable %input_ptr Input
+)";
+
+  EntryPoint entry_point;
+  entry_point.name = "main";
+  entry_point.execution_model = "GLCompute";
+  entry_point.interfaces = "%workgroup_id";
+  entry_point.body = R"(
+%copy_size = OpCopyObject %u32vec3 %workgroup_size
+  %load_id = OpLoad %u32vec2 %workgroup_id
+)";
+  generator.entry_points_.push_back(std::move(entry_point));
+
+  CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("According to the Vulkan spec BuiltIn WorkgroupId "
+                        "variable needs to be a 3-component 32-bit int vector. "
+                        "ID <2> (OpVariable) has 2 components."));
+}
+
 TEST_F(ValidateBuiltIns, TwoBuiltInsFirstFails) {
   CodeGenerator generator = GetDefaultShaderCodeGenerator();
 
diff --git a/test/val/val_capability_test.cpp b/test/val/val_capability_test.cpp
index f5650ab..488e957 100644
--- a/test/val/val_capability_test.cpp
+++ b/test/val/val_capability_test.cpp
@@ -1139,9 +1139,12 @@
           "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
           AllCapabilities()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
+          // Uniform must target a non-void value.
           "OpEntryPoint Kernel %func \"compute\" \n"
-          "OpDecorate %intt Uniform\n"
-          "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
+          "OpDecorate %int0 Uniform\n"
+          "%intt = OpTypeInt 32 0\n" +
+          "%int0 = OpConstantNull %intt"
+          + std::string(kVoidFVoid),
           ShaderDependencies()),
 std::make_pair(std::string(kGLSL450MemoryModel) +
           "OpEntryPoint Vertex %func \"shader\" \n"
diff --git a/test/val/val_cfg_test.cpp b/test/val/val_cfg_test.cpp
index f741ca2..aed0a57 100644
--- a/test/val/val_cfg_test.cpp
+++ b/test/val/val_cfg_test.cpp
@@ -378,8 +378,8 @@
   CompileSuccessfully(str);
   ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              MatchesRegex("Block .\\[cont\\] appears in the binary "
-                           "before its dominator .\\[branch\\]\n"
+              MatchesRegex("Block .\\[%cont\\] appears in the binary "
+                           "before its dominator .\\[%branch\\]\n"
                            "  %branch = OpLabel\n"));
 }
 
@@ -410,7 +410,7 @@
   if (is_shader) {
     ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
     EXPECT_THAT(getDiagnosticString(),
-                MatchesRegex("Block .\\[merge\\] is already a merge block "
+                MatchesRegex("Block .\\[%merge\\] is already a merge block "
                              "for another header\n"
                              "  %Main = OpFunction %void None %9\n"));
   } else {
@@ -445,7 +445,7 @@
   if (is_shader) {
     ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
     EXPECT_THAT(getDiagnosticString(),
-                MatchesRegex("Block .\\[merge\\] is already a merge block "
+                MatchesRegex("Block .\\[%merge\\] is already a merge block "
                              "for another header\n"
                              "  %Main = OpFunction %void None %9\n"));
   } else {
@@ -470,8 +470,8 @@
   CompileSuccessfully(str);
   ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              MatchesRegex("First block .\\[entry\\] of function .\\[Main\\] "
-                           "is targeted by block .\\[bad\\]\n"
+              MatchesRegex("First block .\\[%entry\\] of function "
+                           ".\\[%Main\\] is targeted by block .\\[%bad\\]\n"
                            "  %Main = OpFunction %void None %10\n"));
 }
 
@@ -494,10 +494,11 @@
 
   CompileSuccessfully(str);
   ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              MatchesRegex("Block\\(s\\) \\{..\\} are referenced but not "
-                           "defined in function .\\[Main\\]\n"
-                           "  %Main = OpFunction %void None %10\n"))
+  EXPECT_THAT(
+      getDiagnosticString(),
+      MatchesRegex("Block\\(s\\) \\{11\\[%11\\]\\} are referenced but not "
+                   "defined in function .\\[%Main\\]\n  %Main = OpFunction "
+                   "%void None %10\n"))
       << str;
 }
 
@@ -522,8 +523,8 @@
   CompileSuccessfully(str);
   ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              MatchesRegex("First block .\\[entry\\] of function .\\[Main\\] "
-                           "is targeted by block .\\[bad\\]\n"
+              MatchesRegex("First block .\\[%entry\\] of function .\\[%Main\\] "
+                           "is targeted by block .\\[%bad\\]\n"
                            "  %Main = OpFunction %void None %10\n"));
 }
 
@@ -551,8 +552,8 @@
   CompileSuccessfully(str);
   ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              MatchesRegex("First block .\\[entry\\] of function .\\[Main\\] "
-                           "is targeted by block .\\[bad\\]\n"
+              MatchesRegex("First block .\\[%entry\\] of function .\\[%Main\\] "
+                           "is targeted by block .\\[%bad\\]\n"
                            "  %Main = OpFunction %void None %10\n"));
 }
 
@@ -587,8 +588,8 @@
   CompileSuccessfully(str);
   ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              MatchesRegex("First block .\\[entry\\] of function .\\[Main\\] "
-                           "is targeted by block .\\[bad\\]\n"
+              MatchesRegex("First block .\\[%entry\\] of function .\\[%Main\\] "
+                           "is targeted by block .\\[%bad\\]\n"
                            "  %Main = OpFunction %void None %10\n"));
 }
 
@@ -623,8 +624,8 @@
   ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      MatchesRegex("Block\\(s\\) \\{.\\[middle2\\]\\} are referenced but not "
-                   "defined in function .\\[Main\\]\n"
+      MatchesRegex("Block\\(s\\) \\{.\\[%middle2\\]\\} are referenced but not "
+                   "defined in function .\\[%Main\\]\n"
                    "  %Main = OpFunction %void None %9\n"));
 }
 
@@ -656,8 +657,8 @@
     EXPECT_THAT(
         getDiagnosticString(),
         MatchesRegex("The selection construct with the selection header "
-                     ".\\[head\\] does not dominate the merge block "
-                     ".\\[merge\\]\n  %merge = OpLabel\n"));
+                     ".\\[%head\\] does not dominate the merge block "
+                     ".\\[%merge\\]\n  %merge = OpLabel\n"));
   } else {
     ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
   }
@@ -689,8 +690,8 @@
     EXPECT_THAT(
         getDiagnosticString(),
         MatchesRegex("The selection construct with the selection header "
-                     ".\\[head\\] does not strictly dominate the merge block "
-                     ".\\[head\\]\n  %head = OpLabel\n"));
+                     ".\\[%head\\] does not strictly dominate the merge block "
+                     ".\\[%head\\]\n  %head = OpLabel\n"));
   } else {
     ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()) << str;
   }
@@ -940,8 +941,8 @@
     ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
     EXPECT_THAT(getDiagnosticString(),
                 MatchesRegex("The continue construct with the continue target "
-                             ".\\[loop2_merge\\] is not post dominated by the "
-                             "back-edge block .\\[be_block\\]\n"
+                             ".\\[%loop2_merge\\] is not post dominated by the "
+                             "back-edge block .\\[%be_block\\]\n"
                              "  %be_block = OpLabel\n"));
   } else {
     ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
@@ -975,7 +976,7 @@
     ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
     EXPECT_THAT(
         getDiagnosticString(),
-        MatchesRegex("Back-edges \\(.\\[f\\] -> .\\[split\\]\\) can only "
+        MatchesRegex("Back-edges \\(.\\[%f\\] -> .\\[%split\\]\\) can only "
                      "be formed between a block and a loop header.\n"
                      "  %f = OpLabel\n"));
   } else {
@@ -1003,11 +1004,11 @@
   CompileSuccessfully(str);
   if (is_shader) {
     ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
-    EXPECT_THAT(getDiagnosticString(),
-                MatchesRegex(
-                    "Back-edges \\(.\\[split\\] -> .\\[split\\]\\) can only be "
-                    "formed between a block and a loop header.\n"
-                    "  %split = OpLabel\n"));
+    EXPECT_THAT(
+        getDiagnosticString(),
+        MatchesRegex(
+            "Back-edges \\(.\\[%split\\] -> .\\[%split\\]\\) can only be "
+            "formed between a block and a loop header.\n  %split = OpLabel\n"));
   } else {
     ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
   }
@@ -1038,11 +1039,11 @@
   CompileSuccessfully(str);
   if (is_shader) {
     ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
-    EXPECT_THAT(getDiagnosticString(),
-                MatchesRegex(
-                    "Loop header .\\[loop\\] is targeted by 2 back-edge blocks "
-                    "but the standard requires exactly one\n"
-                    "  %loop = OpLabel\n"))
+    EXPECT_THAT(
+        getDiagnosticString(),
+        MatchesRegex(
+            "Loop header .\\[%loop\\] is targeted by 2 back-edge blocks but "
+            "the standard requires exactly one\n  %loop = OpLabel\n"))
         << str;
   } else {
     ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
@@ -1078,8 +1079,8 @@
     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"
+                             ".\\[%cheader\\] is not post dominated by the "
+                             "back-edge block .\\[%be_block\\]\n"
                              "  %be_block = OpLabel\n"));
   } else {
     ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
@@ -1111,8 +1112,8 @@
     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"
+                             ".\\[%loop\\] is not post dominated by the "
+                             "back-edge block .\\[%cont\\]\n"
                              "  %cont = OpLabel\n"))
         << str;
   } else {
@@ -1147,8 +1148,8 @@
     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"
+                             ".\\[%loop\\] is not post dominated by the "
+                             "back-edge block .\\[%cont\\]\n"
                              "  %cont = OpLabel\n"));
   } else {
     ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
@@ -1222,7 +1223,7 @@
   ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      MatchesRegex("Loop header .\\[loop\\] is targeted by "
+      MatchesRegex("Loop header .\\[%loop\\] is targeted by "
                    "0 back-edge blocks but the standard requires exactly "
                    "one\n  %loop = OpLabel\n"));
 }
@@ -1567,8 +1568,8 @@
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr(
-          "Case construct that targets 10 has branches to multiple other case "
-          "construct targets 12 and 11\n  %10 = OpLabel"));
+          "Case construct that targets 10[%10] has branches to multiple other "
+          "case construct targets 12[%12] and 11[%11]\n  %10 = OpLabel"));
 }
 
 TEST_F(ValidateCFG, MultipleFallThroughToDefault) {
@@ -1602,7 +1603,7 @@
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("Multiple case constructs have branches to the case construct "
-                "that targets 10\n  %10 = OpLabel"));
+                "that targets 10[%10]\n  %10 = OpLabel"));
 }
 
 TEST_F(ValidateCFG, MultipleFallThroughToNonDefault) {
@@ -1636,7 +1637,7 @@
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("Multiple case constructs have branches to the case construct "
-                "that targets 12\n  %12 = OpLabel"));
+                "that targets 12[%12]\n  %12 = OpLabel"));
 }
 
 TEST_F(ValidateCFG, DuplicateTargetWithFallThrough) {
@@ -1697,8 +1698,8 @@
   ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("Case construct that targets 12 has branches to the case "
-                "construct that targets 11, but does not immediately "
+      HasSubstr("Case construct that targets 12[%12] has branches to the case "
+                "construct that targets 11[%11], but does not immediately "
                 "precede it in the OpSwitch's target list\n"
                 "  OpSwitch %uint_0 %10 0 %11 1 %12"));
 }
@@ -1733,8 +1734,8 @@
   ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("Case construct that targets 12 has branches to the case "
-                "construct that targets 11, but does not immediately "
+      HasSubstr("Case construct that targets 12[%12] has branches to the case "
+                "construct that targets 11[%11], but does not immediately "
                 "precede it in the OpSwitch's target list\n"
                 "  OpSwitch %uint_0 %10 0 %11 1 %12"));
 }
@@ -1771,8 +1772,8 @@
   ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("Case construct that targets 12 has branches to the case "
-                "construct that targets 11, but does not immediately "
+      HasSubstr("Case construct that targets 12[%12] has branches to the case "
+                "construct that targets 11[%11], but does not immediately "
                 "precede it in the OpSwitch's target list\n"
                 "  OpSwitch %uint_0 %10 0 %11 1 %12 2 %13"));
 }
@@ -1839,9 +1840,10 @@
   CompileSuccessfully(text);
   ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Case construct that targets 8 has invalid branch to "
-                        "block 10 (not another case construct, corresponding "
-                        "merge, outer loop merge or outer loop continue"));
+              HasSubstr("Case construct that targets 8[%8] has invalid branch "
+                        "to block 10[%10] (not another case construct, "
+                        "corresponding merge, outer loop merge or outer loop "
+                        "continue)"));
 }
 
 TEST_F(ValidateCFG, GoodCaseExitsToOuterConstructs) {
diff --git a/test/val/val_composites_test.cpp b/test/val/val_composites_test.cpp
index ec97d30..92c4cc3 100644
--- a/test/val/val_composites_test.cpp
+++ b/test/val/val_composites_test.cpp
@@ -321,7 +321,8 @@
 
   CompileSuccessfully(GenerateShaderCode(body).c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 5 cannot be a type"));
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 5[%float] cannot be a "
+                                               "type"));
 }
 
 TEST_F(ValidateComposites, CompositeConstructVectorWrongConsituent2) {
@@ -537,7 +538,8 @@
 
   CompileSuccessfully(GenerateShaderCode(body).c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("ID 19 is not a type id"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ID 19[%float_0] is not a type id"));
 }
 
 TEST_F(ValidateComposites, CopyObjectWrongOperandType) {
@@ -658,7 +660,8 @@
 
   CompileSuccessfully(GenerateShaderCode(body));
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 11 cannot be a type"));
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 11[%v4float] cannot "
+                                               "be a type"));
 }
 
 TEST_F(ValidateComposites, CompositeExtractNotComposite) {
diff --git a/test/val/val_conversion_test.cpp b/test/val/val_conversion_test.cpp
index 1c96870..4161c74 100644
--- a/test/val/val_conversion_test.cpp
+++ b/test/val/val_conversion_test.cpp
@@ -973,7 +973,8 @@
 
   CompileSuccessfully(GenerateKernelCode(body).c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 4 cannot be a type"));
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 4[%float] cannot be a "
+                                               "type"));
 }
 
 TEST_F(ValidateConversion, PtrCastToGenericWrongInputStorageClass) {
@@ -1208,7 +1209,8 @@
 
   CompileSuccessfully(GenerateKernelCode(body).c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 4 cannot be a type"));
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 4[%float] cannot be a "
+                                               "type"));
 }
 
 TEST_F(ValidateConversion, BitcastWrongResultType) {
@@ -1296,7 +1298,8 @@
 
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 1 cannot be a type"));
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 1[%uint] cannot be a "
+                                               "type"));
 }
 
 }  // namespace
diff --git a/test/val/val_data_test.cpp b/test/val/val_data_test.cpp
index e6bb673..fcf447a 100644
--- a/test/val/val_data_test.cpp
+++ b/test/val/val_data_test.cpp
@@ -386,7 +386,8 @@
 )";
   CompileSuccessfully(str.c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("ID 3 has not been defined"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ID 3[%3] has not been defined"));
 }
 
 TEST_F(ValidateData, matrix_bad_column_type) {
@@ -573,8 +574,8 @@
   CompileSuccessfully(str.c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Found a forward reference to a non-pointer type in "
-                        "OpTypeStruct instruction."));
+              HasSubstr("Pointer type in OpTypeForwardPointer is not a pointer "
+                        "type.\n  OpTypeForwardPointer %float Generic\n"));
 }
 
 TEST_F(ValidateData, forward_ref_points_to_non_struct) {
@@ -684,8 +685,9 @@
 
   CompileSuccessfully(str.c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpTypeArray Element Type <id> '1' is a void type."));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("OpTypeArray Element Type <id> '1[%void]' is a void type."));
 }
 
 TEST_F(ValidateData, void_runtime_array) {
@@ -698,7 +700,8 @@
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("OpTypeRuntimeArray Element Type <id> '1' is a void type."));
+      HasSubstr(
+          "OpTypeRuntimeArray Element Type <id> '1[%void]' is a void type."));
 }
 }  // namespace
 }  // namespace val
diff --git a/test/val/val_decoration_test.cpp b/test/val/val_decoration_test.cpp
index c67cf45..3a4320d 100644
--- a/test/val/val_decoration_test.cpp
+++ b/test/val/val_decoration_test.cpp
@@ -37,7 +37,7 @@
     OpCapability Linkage
     OpMemoryModel Logical GLSL450
     OpDecorate %1 ArrayStride 4
-    OpDecorate %1 Uniform
+    OpDecorate %1 RelaxedPrecision
     %2 = OpTypeFloat 32
     %1 = OpTypeRuntimeArray %2
     ; Since %1 is used first in Decoration, it gets id 1.
@@ -49,7 +49,7 @@
   EXPECT_THAT(
       vstate_->id_decorations(id),
       Eq(std::vector<Decoration>{Decoration(SpvDecorationArrayStride, {4}),
-                                 Decoration(SpvDecorationUniform)}));
+                                 Decoration(SpvDecorationRelaxedPrecision)}));
 }
 
 TEST_F(ValidateDecorations, ValidateOpMemberDecorateRegistration) {
@@ -105,8 +105,8 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("Index 1 provided in OpMemberDecorate for struct <id> "
-                        "2 is out of bounds. The structure has 1 members. "
-                        "Largest valid index is 0."));
+                        "2[%_struct_2] is out of bounds. The structure has 1 "
+                        "members. Largest valid index is 0."));
 }
 
 TEST_F(ValidateDecorations, ValidateGroupDecorateRegistration) {
@@ -300,10 +300,11 @@
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Structure <id> 1 contains members with BuiltIn "
-                        "decoration. Therefore this structure may not be "
-                        "contained as a member of another structure type. "
-                        "Structure <id> 4 contains structure <id> 1."));
+              HasSubstr("Structure <id> 1[%_struct_1] contains members with "
+                        "BuiltIn decoration. Therefore this structure may not "
+                        "be contained as a member of another structure type. "
+                        "Structure <id> 4[%_struct_4] contains structure <id> "
+                        "1[%_struct_1]."));
 }
 
 TEST_F(ValidateDecorations, StructContainsNonBuiltInStructGood) {
@@ -2012,6 +2013,8 @@
                OpEntryPoint Vertex %main "main"
                OpSource GLSL 450
                OpDecorate %_arr_float_uint_2 ArrayStride 16
+               OpDecorate %u DescriptorSet 0
+               OpDecorate %u Binding 0
                OpMemberDecorate %S 0 Offset 0
                OpMemberDecorate %S 1 Offset 8
                OpDecorate %S Block
@@ -2038,7 +2041,7 @@
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr(
-          "Structure id 3 decorated as Block for variable in Uniform "
+          "Structure id 4 decorated as Block for variable in Uniform "
           "storage class must follow standard uniform buffer layout rules: "
           "member 1 at offset 8 is not aligned to 16"));
 }
@@ -2052,6 +2055,8 @@
                OpEntryPoint Vertex %main "main"
                OpSource GLSL 450
                OpDecorate %_arr_float_uint_2 ArrayStride 16
+               OpDecorate %u DescriptorSet 0
+               OpDecorate %u Binding 0
                OpMemberDecorate %S 0 Offset 0
                OpMemberDecorate %S 1 Offset 8
                OpDecorate %S Block
@@ -2077,7 +2082,7 @@
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr(
-          "Structure id 3 decorated as Block for variable in Uniform "
+          "Structure id 4 decorated as Block for variable in Uniform "
           "storage class must follow relaxed uniform buffer layout rules: "
           "member 1 at offset 8 is not aligned to 16"));
 }
@@ -2278,6 +2283,738 @@
                         "decoration"));
 }
 
+TEST_F(ValidateDecorations, MultiplePushConstantsSingleEntryPointGood) {
+  std::string spirv = R"(
+                OpCapability Shader
+                OpMemoryModel Logical GLSL450
+                OpEntryPoint Fragment %1 "main"
+                OpExecutionMode %1 OriginUpperLeft
+    
+                OpDecorate %struct Block
+                OpMemberDecorate %struct 0 Offset 0
+    
+        %void = OpTypeVoid
+      %voidfn = OpTypeFunction %void
+       %float = OpTypeFloat 32
+         %int = OpTypeInt 32 0
+       %int_0 = OpConstant %int 0
+      %struct = OpTypeStruct %float
+         %ptr = OpTypePointer PushConstant %struct
+   %ptr_float = OpTypePointer PushConstant %float 
+         %pc1 = OpVariable %ptr PushConstant
+         %pc2 = OpVariable %ptr PushConstant
+
+           %1 = OpFunction %void None %voidfn
+       %label = OpLabel
+           %2 = OpAccessChain %ptr_float %pc1 %int_0
+           %3 = OpLoad %float %2
+           %4 = OpAccessChain %ptr_float %pc2 %int_0
+           %5 = OpLoad %float %4
+                OpReturn
+                OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState())
+      << getDiagnosticString();
+}
+
+TEST_F(ValidateDecorations,
+       VulkanMultiplePushConstantsDifferentEntryPointGood) {
+  std::string spirv = R"(
+                OpCapability Shader
+                OpMemoryModel Logical GLSL450
+                OpEntryPoint Vertex %1 "func1"
+                OpEntryPoint Fragment %2 "func2"
+                OpExecutionMode %2 OriginUpperLeft
+    
+                OpDecorate %struct Block
+                OpMemberDecorate %struct 0 Offset 0
+    
+        %void = OpTypeVoid
+      %voidfn = OpTypeFunction %void
+       %float = OpTypeFloat 32
+         %int = OpTypeInt 32 0
+       %int_0 = OpConstant %int 0
+      %struct = OpTypeStruct %float
+         %ptr = OpTypePointer PushConstant %struct
+   %ptr_float = OpTypePointer PushConstant %float 
+         %pc1 = OpVariable %ptr PushConstant
+         %pc2 = OpVariable %ptr PushConstant
+
+           %1 = OpFunction %void None %voidfn
+      %label1 = OpLabel
+           %3 = OpAccessChain %ptr_float %pc1 %int_0
+           %4 = OpLoad %float %3
+                OpReturn
+                OpFunctionEnd
+
+           %2 = OpFunction %void None %voidfn
+      %label2 = OpLabel
+           %5 = OpAccessChain %ptr_float %pc2 %int_0
+           %6 = OpLoad %float %5
+                OpReturn
+                OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1))
+      << getDiagnosticString();
+}
+
+TEST_F(ValidateDecorations,
+       VulkanMultiplePushConstantsUnusedSingleEntryPointGood) {
+  std::string spirv = R"(
+                OpCapability Shader
+                OpMemoryModel Logical GLSL450
+                OpEntryPoint Fragment %1 "main"
+                OpExecutionMode %1 OriginUpperLeft
+    
+                OpDecorate %struct Block
+                OpMemberDecorate %struct 0 Offset 0
+    
+        %void = OpTypeVoid
+      %voidfn = OpTypeFunction %void
+       %float = OpTypeFloat 32
+         %int = OpTypeInt 32 0
+       %int_0 = OpConstant %int 0
+      %struct = OpTypeStruct %float
+         %ptr = OpTypePointer PushConstant %struct
+   %ptr_float = OpTypePointer PushConstant %float 
+         %pc1 = OpVariable %ptr PushConstant
+         %pc2 = OpVariable %ptr PushConstant
+
+           %1 = OpFunction %void None %voidfn
+       %label = OpLabel
+                OpReturn
+                OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1))
+      << getDiagnosticString();
+}
+
+TEST_F(ValidateDecorations, VulkanMultiplePushConstantsSingleEntryPointBad) {
+  std::string spirv = R"(
+                OpCapability Shader
+                OpMemoryModel Logical GLSL450
+                OpEntryPoint Fragment %1 "main"
+                OpExecutionMode %1 OriginUpperLeft
+    
+                OpDecorate %struct Block
+                OpMemberDecorate %struct 0 Offset 0
+    
+        %void = OpTypeVoid
+      %voidfn = OpTypeFunction %void
+       %float = OpTypeFloat 32
+         %int = OpTypeInt 32 0
+       %int_0 = OpConstant %int 0
+      %struct = OpTypeStruct %float
+         %ptr = OpTypePointer PushConstant %struct
+   %ptr_float = OpTypePointer PushConstant %float 
+         %pc1 = OpVariable %ptr PushConstant
+         %pc2 = OpVariable %ptr PushConstant
+
+           %1 = OpFunction %void None %voidfn
+       %label = OpLabel
+           %2 = OpAccessChain %ptr_float %pc1 %int_0
+           %3 = OpLoad %float %2
+           %4 = OpAccessChain %ptr_float %pc2 %int_0
+           %5 = OpLoad %float %4
+                OpReturn
+                OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID,
+            ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Entry point id '1' uses more than one PushConstant interface.\n"
+          "From Vulkan spec, section 14.5.1:\n"
+          "There must be no more than one push constant block "
+          "statically used per shader entry point."));
+}
+
+TEST_F(ValidateDecorations,
+       VulkanMultiplePushConstantsDifferentEntryPointSubFunctionGood) {
+  std::string spirv = R"(
+                OpCapability Shader
+                OpMemoryModel Logical GLSL450
+                OpEntryPoint Vertex %1 "func1"
+                OpEntryPoint Fragment %2 "func2"
+                OpExecutionMode %2 OriginUpperLeft
+    
+                OpDecorate %struct Block
+                OpMemberDecorate %struct 0 Offset 0
+    
+        %void = OpTypeVoid
+      %voidfn = OpTypeFunction %void
+       %float = OpTypeFloat 32
+         %int = OpTypeInt 32 0
+       %int_0 = OpConstant %int 0
+      %struct = OpTypeStruct %float
+         %ptr = OpTypePointer PushConstant %struct
+   %ptr_float = OpTypePointer PushConstant %float 
+         %pc1 = OpVariable %ptr PushConstant
+         %pc2 = OpVariable %ptr PushConstant
+ 
+        %sub1 = OpFunction %void None %voidfn
+  %label_sub1 = OpLabel
+           %3 = OpAccessChain %ptr_float %pc1 %int_0
+           %4 = OpLoad %float %3
+                OpReturn
+                OpFunctionEnd
+
+        %sub2 = OpFunction %void None %voidfn
+  %label_sub2 = OpLabel
+           %5 = OpAccessChain %ptr_float %pc2 %int_0
+           %6 = OpLoad %float %5
+                OpReturn
+                OpFunctionEnd
+
+           %1 = OpFunction %void None %voidfn
+      %label1 = OpLabel
+       %call1 = OpFunctionCall %void %sub1
+                OpReturn
+                OpFunctionEnd
+
+           %2 = OpFunction %void None %voidfn
+      %label2 = OpLabel
+       %call2 = OpFunctionCall %void %sub2
+                OpReturn
+                OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1))
+      << getDiagnosticString();
+}
+
+TEST_F(ValidateDecorations,
+       VulkanMultiplePushConstantsSingleEntryPointSubFunctionBad) {
+  std::string spirv = R"(
+                OpCapability Shader
+                OpMemoryModel Logical GLSL450
+                OpEntryPoint Fragment %1 "main"
+                OpExecutionMode %1 OriginUpperLeft
+    
+                OpDecorate %struct Block
+                OpMemberDecorate %struct 0 Offset 0
+    
+        %void = OpTypeVoid
+      %voidfn = OpTypeFunction %void
+       %float = OpTypeFloat 32
+         %int = OpTypeInt 32 0
+       %int_0 = OpConstant %int 0
+      %struct = OpTypeStruct %float
+         %ptr = OpTypePointer PushConstant %struct
+   %ptr_float = OpTypePointer PushConstant %float 
+         %pc1 = OpVariable %ptr PushConstant
+         %pc2 = OpVariable %ptr PushConstant
+
+        %sub1 = OpFunction %void None %voidfn
+  %label_sub1 = OpLabel
+           %3 = OpAccessChain %ptr_float %pc1 %int_0
+           %4 = OpLoad %float %3
+                OpReturn
+                OpFunctionEnd
+
+        %sub2 = OpFunction %void None %voidfn
+  %label_sub2 = OpLabel
+           %5 = OpAccessChain %ptr_float %pc2 %int_0
+           %6 = OpLoad %float %5
+                OpReturn
+                OpFunctionEnd
+
+           %1 = OpFunction %void None %voidfn
+      %label1 = OpLabel
+       %call1 = OpFunctionCall %void %sub1
+       %call2 = OpFunctionCall %void %sub2
+                OpReturn
+                OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID,
+            ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Entry point id '1' uses more than one PushConstant interface.\n"
+          "From Vulkan spec, section 14.5.1:\n"
+          "There must be no more than one push constant block "
+          "statically used per shader entry point."));
+}
+
+TEST_F(ValidateDecorations, VulkanUniformMissingDescriptorSetBad) {
+  std::string spirv = R"(
+            OpCapability Shader
+            OpMemoryModel Logical GLSL450
+            OpEntryPoint Fragment %1 "main"
+            OpExecutionMode %1 OriginUpperLeft
+
+            OpDecorate %struct Block
+            OpMemberDecorate %struct 0 Offset 0
+            OpDecorate %var Binding 0
+
+    %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+   %float = OpTypeFloat 32
+  %struct = OpTypeStruct %float
+     %ptr = OpTypePointer Uniform %struct
+%ptr_float = OpTypePointer Uniform %float
+     %var = OpVariable %ptr Uniform
+     %int = OpTypeInt 32 0
+   %int_0 = OpConstant %int 0
+
+       %1 = OpFunction %void None %voidfn
+   %label = OpLabel
+       %2 = OpAccessChain %ptr_float %var %int_0
+       %3 = OpLoad %float %2
+            OpReturn
+            OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID,
+            ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Uniform id '3' is missing DescriptorSet decoration.\n"
+                        "From Vulkan spec, section 14.5.2:\n"
+                        "These variables must have DescriptorSet and Binding "
+                        "decorations specified"));
+}
+
+TEST_F(ValidateDecorations, VulkanUniformMissingBindingBad) {
+  std::string spirv = R"(
+            OpCapability Shader
+            OpMemoryModel Logical GLSL450
+            OpEntryPoint Fragment %1 "main"
+            OpExecutionMode %1 OriginUpperLeft
+
+            OpDecorate %struct Block
+            OpMemberDecorate %struct 0 Offset 0
+            OpDecorate %var DescriptorSet 0
+
+    %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+   %float = OpTypeFloat 32
+  %struct = OpTypeStruct %float
+     %ptr = OpTypePointer Uniform %struct
+%ptr_float = OpTypePointer Uniform %float
+     %var = OpVariable %ptr Uniform
+     %int = OpTypeInt 32 0
+   %int_0 = OpConstant %int 0
+
+       %1 = OpFunction %void None %voidfn
+   %label = OpLabel
+       %2 = OpAccessChain %ptr_float %var %int_0
+       %3 = OpLoad %float %2
+            OpReturn
+            OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID,
+            ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Uniform id '3' is missing Binding decoration.\n"
+                        "From Vulkan spec, section 14.5.2:\n"
+                        "These variables must have DescriptorSet and Binding "
+                        "decorations specified"));
+}
+
+TEST_F(ValidateDecorations, VulkanUniformConstantMissingDescriptorSetBad) {
+  std::string spirv = R"(
+            OpCapability Shader
+            OpMemoryModel Logical GLSL450
+            OpEntryPoint Fragment %1 "main"
+            OpExecutionMode %1 OriginUpperLeft
+
+            OpDecorate %var Binding 0
+
+    %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+ %sampler = OpTypeSampler
+     %ptr = OpTypePointer UniformConstant %sampler
+     %var = OpVariable %ptr UniformConstant
+
+       %1 = OpFunction %void None %voidfn
+   %label = OpLabel
+       %2 = OpLoad %sampler %var
+            OpReturn
+            OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID,
+            ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("UniformConstant id '2' is missing DescriptorSet decoration.\n"
+                "From Vulkan spec, section 14.5.2:\n"
+                "These variables must have DescriptorSet and Binding "
+                "decorations specified"));
+}
+
+TEST_F(ValidateDecorations, VulkanUniformConstantMissingBindingBad) {
+  std::string spirv = R"(
+            OpCapability Shader
+            OpMemoryModel Logical GLSL450
+            OpEntryPoint Fragment %1 "main"
+            OpExecutionMode %1 OriginUpperLeft
+
+            OpDecorate %var DescriptorSet 0
+
+    %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+ %sampler = OpTypeSampler
+     %ptr = OpTypePointer UniformConstant %sampler
+     %var = OpVariable %ptr UniformConstant
+
+       %1 = OpFunction %void None %voidfn
+   %label = OpLabel
+       %2 = OpLoad %sampler %var
+            OpReturn
+            OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID,
+            ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("UniformConstant id '2' is missing Binding decoration.\n"
+                "From Vulkan spec, section 14.5.2:\n"
+                "These variables must have DescriptorSet and Binding "
+                "decorations specified"));
+}
+
+TEST_F(ValidateDecorations, VulkanStorageBufferMissingDescriptorSetBad) {
+  std::string spirv = R"(
+            OpCapability Shader
+            OpExtension "SPV_KHR_storage_buffer_storage_class"
+            OpMemoryModel Logical GLSL450
+            OpEntryPoint Fragment %1 "main"
+            OpExecutionMode %1 OriginUpperLeft
+
+            OpDecorate %struct Block
+            OpDecorate %var Binding 0
+
+    %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+   %float = OpTypeFloat 32
+  %struct = OpTypeStruct %float
+     %ptr = OpTypePointer StorageBuffer %struct
+     %var = OpVariable %ptr StorageBuffer
+%ptr_float = OpTypePointer StorageBuffer %float
+     %int = OpTypeInt 32 0
+   %int_0 = OpConstant %int 0
+
+       %1 = OpFunction %void None %voidfn
+   %label = OpLabel
+       %2 = OpAccessChain %ptr_float %var %int_0
+       %3 = OpLoad %float %2
+            OpReturn
+            OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID,
+            ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("StorageBuffer id '3' is missing DescriptorSet decoration.\n"
+                "From Vulkan spec, section 14.5.2:\n"
+                "These variables must have DescriptorSet and Binding "
+                "decorations specified"));
+}
+
+TEST_F(ValidateDecorations, VulkanStorageBufferMissingBindingBad) {
+  std::string spirv = R"(
+            OpCapability Shader
+            OpExtension "SPV_KHR_storage_buffer_storage_class"
+            OpMemoryModel Logical GLSL450
+            OpEntryPoint Fragment %1 "main"
+            OpExecutionMode %1 OriginUpperLeft
+
+            OpDecorate %struct Block
+            OpDecorate %var DescriptorSet 0
+
+    %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+   %float = OpTypeFloat 32
+  %struct = OpTypeStruct %float
+     %ptr = OpTypePointer StorageBuffer %struct
+     %var = OpVariable %ptr StorageBuffer
+%ptr_float = OpTypePointer StorageBuffer %float
+     %int = OpTypeInt 32 0
+   %int_0 = OpConstant %int 0
+
+       %1 = OpFunction %void None %voidfn
+   %label = OpLabel
+       %2 = OpAccessChain %ptr_float %var %int_0
+       %3 = OpLoad %float %2
+            OpReturn
+            OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID,
+            ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("StorageBuffer id '3' is missing Binding decoration.\n"
+                        "From Vulkan spec, section 14.5.2:\n"
+                        "These variables must have DescriptorSet and Binding "
+                        "decorations specified"));
+}
+
+TEST_F(ValidateDecorations,
+       VulkanStorageBufferMissingDescriptorSetSubFunctionBad) {
+  std::string spirv = R"(
+            OpCapability Shader
+            OpExtension "SPV_KHR_storage_buffer_storage_class"
+            OpMemoryModel Logical GLSL450
+            OpEntryPoint Fragment %1 "main"
+            OpExecutionMode %1 OriginUpperLeft
+
+            OpDecorate %struct Block
+            OpDecorate %var Binding 0
+
+    %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+   %float = OpTypeFloat 32
+  %struct = OpTypeStruct %float
+     %ptr = OpTypePointer StorageBuffer %struct
+     %var = OpVariable %ptr StorageBuffer
+%ptr_float = OpTypePointer StorageBuffer %float
+     %int = OpTypeInt 32 0
+   %int_0 = OpConstant %int 0
+
+       %1 = OpFunction %void None %voidfn
+   %label = OpLabel
+    %call = OpFunctionCall %void %2
+            OpReturn
+            OpFunctionEnd
+       %2 = OpFunction %void None %voidfn
+  %label2 = OpLabel
+       %3 = OpAccessChain %ptr_float %var %int_0
+       %4 = OpLoad %float %3
+            OpReturn
+            OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID,
+            ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("StorageBuffer id '3' is missing DescriptorSet decoration.\n"
+                "From Vulkan spec, section 14.5.2:\n"
+                "These variables must have DescriptorSet and Binding "
+                "decorations specified"));
+}
+
+TEST_F(ValidateDecorations,
+       VulkanStorageBufferMissingDescriptorAndBindingUnusedGood) {
+  std::string spirv = R"(
+            OpCapability Shader
+            OpExtension "SPV_KHR_storage_buffer_storage_class"
+            OpMemoryModel Logical GLSL450
+            OpEntryPoint Fragment %1 "main"
+            OpExecutionMode %1 OriginUpperLeft
+
+            OpDecorate %struct BufferBlock
+
+    %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+   %float = OpTypeFloat 32
+  %struct = OpTypeStruct %float
+     %ptr = OpTypePointer StorageBuffer %struct
+     %var = OpVariable %ptr StorageBuffer
+
+       %1 = OpFunction %void None %voidfn
+   %label = OpLabel
+            OpReturn
+            OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_SUCCESS,
+            ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
+}
+
+TEST_F(ValidateDecorations, UniformMissingDescriptorSetGood) {
+  std::string spirv = R"(
+            OpCapability Shader
+            OpMemoryModel Logical GLSL450
+            OpEntryPoint Fragment %1 "main"
+            OpExecutionMode %1 OriginUpperLeft
+
+            OpDecorate %struct Block
+            OpMemberDecorate %struct 0 Offset 0
+            OpDecorate %var Binding 0
+
+    %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+   %float = OpTypeFloat 32
+  %struct = OpTypeStruct %float
+     %ptr = OpTypePointer Uniform %struct
+     %var = OpVariable %ptr Uniform
+
+       %1 = OpFunction %void None %voidfn
+   %label = OpLabel
+            OpReturn
+            OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState())
+      << getDiagnosticString();
+}
+
+TEST_F(ValidateDecorations, UniformMissingBindingGood) {
+  std::string spirv = R"(
+            OpCapability Shader
+            OpMemoryModel Logical GLSL450
+            OpEntryPoint Fragment %1 "main"
+            OpExecutionMode %1 OriginUpperLeft
+
+            OpDecorate %struct Block
+            OpMemberDecorate %struct 0 Offset 0
+            OpDecorate %var DescriptorSet 0
+
+    %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+   %float = OpTypeFloat 32
+  %struct = OpTypeStruct %float
+     %ptr = OpTypePointer Uniform %struct
+     %var = OpVariable %ptr Uniform
+
+       %1 = OpFunction %void None %voidfn
+   %label = OpLabel
+            OpReturn
+            OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState())
+      << getDiagnosticString();
+}
+
+TEST_F(ValidateDecorations, UniformConstantMissingDescriptorSetGood) {
+  std::string spirv = R"(
+            OpCapability Shader
+            OpMemoryModel Logical GLSL450
+            OpEntryPoint Fragment %1 "main"
+            OpExecutionMode %1 OriginUpperLeft
+
+            OpDecorate %var Binding 0
+
+    %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+ %sampler = OpTypeSampler
+     %ptr = OpTypePointer UniformConstant %sampler
+     %var = OpVariable %ptr UniformConstant
+
+       %1 = OpFunction %void None %voidfn
+   %label = OpLabel
+            OpReturn
+            OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState())
+      << getDiagnosticString();
+}
+
+TEST_F(ValidateDecorations, UniformConstantMissingBindingGood) {
+  std::string spirv = R"(
+            OpCapability Shader
+            OpMemoryModel Logical GLSL450
+            OpEntryPoint Fragment %1 "main"
+            OpExecutionMode %1 OriginUpperLeft
+
+            OpDecorate %var DescriptorSet 0
+
+    %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+ %sampler = OpTypeSampler
+     %ptr = OpTypePointer UniformConstant %sampler
+     %var = OpVariable %ptr UniformConstant
+
+       %1 = OpFunction %void None %voidfn
+   %label = OpLabel
+            OpReturn
+            OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState())
+      << getDiagnosticString();
+}
+
+TEST_F(ValidateDecorations, StorageBufferMissingDescriptorSetGood) {
+  std::string spirv = R"(
+            OpCapability Shader
+            OpExtension "SPV_KHR_storage_buffer_storage_class"
+            OpMemoryModel Logical GLSL450
+            OpEntryPoint Fragment %1 "main"
+            OpExecutionMode %1 OriginUpperLeft
+
+            OpDecorate %struct BufferBlock
+            OpDecorate %var Binding 0
+
+    %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+   %float = OpTypeFloat 32
+  %struct = OpTypeStruct %float
+     %ptr = OpTypePointer StorageBuffer %struct
+     %var = OpVariable %ptr StorageBuffer
+
+       %1 = OpFunction %void None %voidfn
+   %label = OpLabel
+            OpReturn
+            OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState())
+      << getDiagnosticString();
+}
+
+TEST_F(ValidateDecorations, StorageBufferMissingBindingGood) {
+  std::string spirv = R"(
+            OpCapability Shader
+            OpExtension "SPV_KHR_storage_buffer_storage_class"
+            OpMemoryModel Logical GLSL450
+            OpEntryPoint Fragment %1 "main"
+            OpExecutionMode %1 OriginUpperLeft
+
+            OpDecorate %struct BufferBlock
+            OpDecorate %var DescriptorSet 0
+
+    %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+   %float = OpTypeFloat 32
+  %struct = OpTypeStruct %float
+     %ptr = OpTypePointer StorageBuffer %struct
+     %var = OpVariable %ptr StorageBuffer
+
+       %1 = OpFunction %void None %voidfn
+   %label = OpLabel
+            OpReturn
+            OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState())
+      << getDiagnosticString();
+}
+
 TEST_F(ValidateDecorations, StorageBufferStorageClassArrayBaseAlignmentGood) {
   // Spot check buffer rules when using StorageBuffer storage class with Block
   // decoration.
@@ -3322,8 +4059,8 @@
   CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Coherent decoration targeting 1 is banned when using "
-                        "the Vulkan memory model."));
+              HasSubstr("Coherent decoration targeting 1[%1] is "
+                        "banned when using the Vulkan memory model."));
 }
 
 TEST_F(ValidateDecorations, VulkanMemoryModelNoCoherentMember) {
@@ -3340,10 +4077,10 @@
 
   CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Coherent decoration targeting 1 (member index 0) is "
-                        "banned when using "
-                        "the Vulkan memory model."));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Coherent decoration targeting 1[%_struct_1] (member index 0) "
+                "is banned when using the Vulkan memory model."));
 }
 
 TEST_F(ValidateDecorations, VulkanMemoryModelNoVolatile) {
@@ -3363,8 +4100,8 @@
   CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Volatile decoration targeting 1 is banned when using "
-                        "the Vulkan memory model."));
+              HasSubstr("Volatile decoration targeting 1[%1] is banned when "
+                        "using the Vulkan memory model."));
 }
 
 TEST_F(ValidateDecorations, VulkanMemoryModelNoVolatileMember) {
@@ -3382,9 +4119,9 @@
   CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Volatile decoration targeting 1 (member index 1) is "
-                        "banned when using "
-                        "the Vulkan memory model."));
+              HasSubstr("Volatile decoration targeting 1[%_struct_1] (member "
+                        "index 1) is banned when using the Vulkan memory "
+                        "model."));
 }
 
 TEST_F(ValidateDecorations, FPRoundingModeGood) {
@@ -3696,9 +4433,9 @@
 
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("OpGroupDecorate may not target OpDecorationGroup <id> '1'"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("OpGroupDecorate may not target OpDecorationGroup <id> "
+                        "'1[%1]'"));
 }
 
 TEST_F(ValidateDecorations, GroupDecorateTargetsDecorationGroup2) {
@@ -3713,9 +4450,9 @@
 
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("OpGroupDecorate may not target OpDecorationGroup <id> '1'"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("OpGroupDecorate may not target OpDecorationGroup <id> "
+                        "'1[%1]'"));
 }
 
 TEST_F(ValidateDecorations, RecurseThroughRuntimeArray) {
@@ -3773,6 +4510,306 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
+// Uniform decoration
+
+TEST_F(ValidateDecorations, UniformDecorationGood) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical Simple
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %int0 Uniform
+OpDecorate %var Uniform
+OpDecorate %val Uniform
+%void = OpTypeVoid
+%int = OpTypeInt 32 1
+%int0 = OpConstantNull %int
+%intptr = OpTypePointer Private %int
+%var = OpVariable %intptr Private
+%fn = OpTypeFunction %void
+%main = OpFunction %void None %fn
+%entry = OpLabel
+%val = OpLoad %int %var
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, UniformDecorationTargetsTypeBad) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical Simple
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %fn Uniform
+%void = OpTypeVoid
+%fn = OpTypeFunction %void
+%main = OpFunction %void None %fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Uniform decoration applied to a non-object"));
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("%2 = OpTypeFunction %void"));
+}
+
+TEST_F(ValidateDecorations, UniformDecorationTargetsVoidValueBad) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical Simple
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpName %call "call"
+OpName %myfunc "myfunc"
+OpDecorate %call Uniform
+%void = OpTypeVoid
+%fnty = OpTypeFunction %void
+%myfunc = OpFunction %void None %fnty
+%myfuncentry = OpLabel
+OpReturn
+OpFunctionEnd
+%main = OpFunction %void None %fnty
+%entry = OpLabel
+%call = OpFunctionCall %void %myfunc
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Uniform decoration applied to a value with void type\n"
+                        "  %call = OpFunctionCall %void %myfunc"));
+}
+
+TEST_F(ValidateDecorations, MultipleOffsetDecorationsOnSameID) {
+  std::string spirv = R"(
+            OpCapability Shader
+            OpMemoryModel Logical GLSL450
+            OpEntryPoint Fragment %1 "main"
+            OpExecutionMode %1 OriginUpperLeft
+
+            OpMemberDecorate %struct 0 Offset 0
+            OpMemberDecorate %struct 0 Offset 0
+
+    %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+   %float = OpTypeFloat 32
+  %struct = OpTypeStruct %float
+
+       %1 = OpFunction %void None %voidfn
+   %label = OpLabel
+            OpReturn
+            OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ID '2', member '0' decorated with Offset multiple "
+                        "times is not allowed."));
+}
+
+TEST_F(ValidateDecorations, MultipleArrayStrideDecorationsOnSameID) {
+  std::string spirv = R"(
+            OpCapability Shader
+            OpMemoryModel Logical GLSL450
+            OpEntryPoint Fragment %1 "main"
+            OpExecutionMode %1 OriginUpperLeft
+
+            OpDecorate %array ArrayStride 4
+            OpDecorate %array ArrayStride 4
+
+    %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+   %float = OpTypeFloat 32
+    %uint = OpTypeInt 32 0
+  %uint_4 = OpConstant %uint 4
+   %array = OpTypeArray %float %uint_4
+
+       %1 = OpFunction %void None %voidfn
+   %label = OpLabel
+            OpReturn
+            OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ID '2' decorated with ArrayStride multiple "
+                        "times is not allowed."));
+}
+
+TEST_F(ValidateDecorations, MultipleMatrixStrideDecorationsOnSameID) {
+  std::string spirv = R"(
+            OpCapability Shader
+            OpMemoryModel Logical GLSL450
+            OpEntryPoint Fragment %1 "main"
+            OpExecutionMode %1 OriginUpperLeft
+
+            OpMemberDecorate %struct 0 Offset 0
+            OpMemberDecorate %struct 0 ColMajor
+            OpMemberDecorate %struct 0 MatrixStride 16
+            OpMemberDecorate %struct 0 MatrixStride 16
+
+    %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+   %float = OpTypeFloat 32
+   %fvec4 = OpTypeVector %float 4
+   %fmat4 = OpTypeMatrix %fvec4 4
+  %struct = OpTypeStruct %fmat4
+
+       %1 = OpFunction %void None %voidfn
+   %label = OpLabel
+            OpReturn
+            OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ID '2', member '0' decorated with MatrixStride "
+                        "multiple times is not allowed."));
+}
+
+TEST_F(ValidateDecorations, MultipleRowMajorDecorationsOnSameID) {
+  std::string spirv = R"(
+            OpCapability Shader
+            OpMemoryModel Logical GLSL450
+            OpEntryPoint Fragment %1 "main"
+            OpExecutionMode %1 OriginUpperLeft
+
+            OpMemberDecorate %struct 0 Offset 0
+            OpMemberDecorate %struct 0 MatrixStride 16
+            OpMemberDecorate %struct 0 RowMajor
+            OpMemberDecorate %struct 0 RowMajor
+
+    %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+   %float = OpTypeFloat 32
+   %fvec4 = OpTypeVector %float 4
+   %fmat4 = OpTypeMatrix %fvec4 4
+  %struct = OpTypeStruct %fmat4
+
+       %1 = OpFunction %void None %voidfn
+   %label = OpLabel
+            OpReturn
+            OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ID '2', member '0' decorated with RowMajor multiple "
+                        "times is not allowed."));
+}
+
+TEST_F(ValidateDecorations, MultipleColMajorDecorationsOnSameID) {
+  std::string spirv = R"(
+            OpCapability Shader
+            OpMemoryModel Logical GLSL450
+            OpEntryPoint Fragment %1 "main"
+            OpExecutionMode %1 OriginUpperLeft
+
+            OpMemberDecorate %struct 0 Offset 0
+            OpMemberDecorate %struct 0 MatrixStride 16
+            OpMemberDecorate %struct 0 ColMajor
+            OpMemberDecorate %struct 0 ColMajor
+
+    %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+   %float = OpTypeFloat 32
+   %fvec4 = OpTypeVector %float 4
+   %fmat4 = OpTypeMatrix %fvec4 4
+  %struct = OpTypeStruct %fmat4
+
+       %1 = OpFunction %void None %voidfn
+   %label = OpLabel
+            OpReturn
+            OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ID '2', member '0' decorated with ColMajor multiple "
+                        "times is not allowed."));
+}
+
+TEST_F(ValidateDecorations, RowMajorAndColMajorDecorationsOnSameID) {
+  std::string spirv = R"(
+            OpCapability Shader
+            OpMemoryModel Logical GLSL450
+            OpEntryPoint Fragment %1 "main"
+            OpExecutionMode %1 OriginUpperLeft
+
+            OpMemberDecorate %struct 0 Offset 0
+            OpMemberDecorate %struct 0 MatrixStride 16
+            OpMemberDecorate %struct 0 ColMajor
+            OpMemberDecorate %struct 0 RowMajor
+
+    %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+   %float = OpTypeFloat 32
+   %fvec4 = OpTypeVector %float 4
+   %fmat4 = OpTypeMatrix %fvec4 4
+  %struct = OpTypeStruct %fmat4
+
+       %1 = OpFunction %void None %voidfn
+   %label = OpLabel
+            OpReturn
+            OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ID '2', member '0' decorated with both RowMajor and "
+                        "ColMajor is not allowed."));
+}
+
+TEST_F(ValidateDecorations, BlockAndBufferBlockDecorationsOnSameID) {
+  std::string spirv = R"(
+            OpCapability Shader
+            OpMemoryModel Logical GLSL450
+            OpEntryPoint Fragment %1 "main"
+            OpExecutionMode %1 OriginUpperLeft
+
+            OpDecorate %struct Block
+            OpDecorate %struct BufferBlock
+            OpMemberDecorate %struct 0 Offset 0
+            OpMemberDecorate %struct 0 MatrixStride 16
+            OpMemberDecorate %struct 0 RowMajor
+
+    %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+   %float = OpTypeFloat 32
+   %fvec4 = OpTypeVector %float 4
+   %fmat4 = OpTypeMatrix %fvec4 4
+  %struct = OpTypeStruct %fmat4
+
+       %1 = OpFunction %void None %voidfn
+   %label = OpLabel
+            OpReturn
+            OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "ID '2' decorated with both BufferBlock and Block is not allowed."));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_derivatives_test.cpp b/test/val/val_derivatives_test.cpp
index 19e1cf3..480042a 100644
--- a/test/val/val_derivatives_test.cpp
+++ b/test/val/val_derivatives_test.cpp
@@ -120,7 +120,8 @@
 
   CompileSuccessfully(GenerateShaderCode(body).c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 10 cannot be a type"));
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 10[%v4float] cannot "
+                                               "be a type"));
 }
 
 TEST_F(ValidateDerivatives, OpDPdxWrongPType) {
diff --git a/test/val/val_ext_inst_test.cpp b/test/val/val_ext_inst_test.cpp
index 94a97a3..d1505da 100644
--- a/test/val/val_ext_inst_test.cpp
+++ b/test/val/val_ext_inst_test.cpp
@@ -4134,7 +4134,8 @@
 
   CompileSuccessfully(GenerateKernelCode(ss.str()));
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 89 cannot be a type"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Operand 89[%_ptr_Workgroup_half] cannot be a type"));
 }
 
 TEST_P(ValidateOpenCLStdVStoreHalfLike, ConstPointer) {
@@ -4305,7 +4306,8 @@
 
   CompileSuccessfully(GenerateKernelCode(ss.str()));
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 89 cannot be a type"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Operand 89[%_ptr_Workgroup_half] cannot be a type"));
 }
 
 TEST_P(ValidateOpenCLStdVLoadHalfLike, OffsetWrongStorageType) {
@@ -4476,7 +4478,9 @@
 
   CompileSuccessfully(GenerateKernelCode(ss.str()));
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 120 cannot be a type"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Operand 120[%_ptr_UniformConstant_float] cannot be a "
+                        "type"));
 }
 
 TEST_F(ValidateExtInst, VLoadNWrongStorageClass) {
@@ -4587,7 +4591,9 @@
 
   CompileSuccessfully(GenerateKernelCode(ss.str()));
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 114 cannot be a type"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Operand 114[%_ptr_UniformConstant_half] cannot be a "
+                        "type"));
 }
 
 TEST_F(ValidateExtInst, VLoadHalfWrongStorageClass) {
@@ -4739,7 +4745,8 @@
 
   CompileSuccessfully(GenerateKernelCode(ss.str()));
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 124 cannot be a type"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Operand 124[%_ptr_Generic_float] cannot be a type"));
 }
 
 TEST_F(ValidateExtInst, VStoreNPNotGeneric) {
@@ -5052,7 +5059,9 @@
 
   CompileSuccessfully(GenerateKernelCode(body));
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 134 cannot be a type"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Operand 134[%_ptr_UniformConstant_uchar] cannot be a "
+                        "type"));
 }
 
 TEST_F(ValidateExtInst, OpenCLStdPrintfFormatNotUniformConstStorageClass) {
@@ -5143,7 +5152,9 @@
 
   CompileSuccessfully(GenerateKernelCode(body));
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 99 cannot be a type"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Operand 99[%_ptr_CrossWorkgroup_uint] cannot be a "
+                        "type"));
 }
 
 TEST_F(ValidateExtInst, OpenCLStdPrefetchPtrNotCrossWorkgroup) {
diff --git a/test/val/val_id_test.cpp b/test/val/val_id_test.cpp
index c96aa17..a162648 100644
--- a/test/val/val_id_test.cpp
+++ b/test/val/val_id_test.cpp
@@ -205,7 +205,7 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("OpMemberName Type <id> '1[foo]' is not a struct type."));
+      HasSubstr("OpMemberName Type <id> '1[%uint]' is not a struct type."));
 }
 TEST_F(ValidateIdWithMessage, OpMemberNameMemberBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
@@ -216,8 +216,8 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("OpMemberName Member <id> '1[foo]' index is larger than "
-                "Type <id> '1[foo]'s member count."));
+      HasSubstr("OpMemberName Member <id> '1[%_struct_1]' index is larger "
+                "than Type <id> '1[%_struct_1]'s member count."));
 }
 
 TEST_F(ValidateIdWithMessage, OpLineGood) {
@@ -239,7 +239,7 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpLine Target <id> '1' is not an OpString."));
+              HasSubstr("OpLine Target <id> '1[%uint]' is not an OpString."));
 }
 
 TEST_F(ValidateIdWithMessage, OpDecorateGood) {
@@ -261,7 +261,7 @@
 
 TEST_F(ValidateIdWithMessage, OpMemberDecorateGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
-     OpMemberDecorate %2 0 Uniform
+     OpMemberDecorate %2 0 RelaxedPrecision
 %1 = OpTypeInt 32 0
 %2 = OpTypeStruct %1 %1)";
   CompileSuccessfully(spirv.c_str());
@@ -269,32 +269,31 @@
 }
 TEST_F(ValidateIdWithMessage, OpMemberDecorateBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
-     OpMemberDecorate %1 0 Uniform
+     OpMemberDecorate %1 0 RelaxedPrecision
 %1 = OpTypeInt 32 0)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr(
-          "OpMemberDecorate Structure type <id> '1' is not a struct type."));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("OpMemberDecorate Structure type <id> '1[%uint]' is "
+                        "not a struct type."));
 }
 TEST_F(ValidateIdWithMessage, OpMemberDecorateMemberBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
-     OpMemberDecorate %1 3 Uniform
+     OpMemberDecorate %1 3 RelaxedPrecision
 %int = OpTypeInt 32 0
 %1 = OpTypeStruct %int %int)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("Index 3 provided in OpMemberDecorate for struct <id> "
-                        "1 is out of bounds. The structure has 2 members. "
-                        "Largest valid index is 1."));
+                        "1[%_struct_1] is out of bounds. The structure has 2 "
+                        "members. Largest valid index is 1."));
 }
 
 TEST_F(ValidateIdWithMessage, OpGroupDecorateGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpDecorationGroup
-     OpDecorate %1 Uniform
+     OpDecorate %1 RelaxedPrecision
      OpDecorate %1 GLSLShared
      OpGroupDecorate %1 %3 %4
 %2 = OpTypeInt 32 0
@@ -306,7 +305,7 @@
 TEST_F(ValidateIdWithMessage, OpDecorationGroupBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpDecorationGroup
-     OpDecorate %1 Uniform
+     OpDecorate %1 RelaxedPrecision
      OpDecorate %1 GLSLShared
      OpMemberDecorate %1 0 Constant
     )";
@@ -329,13 +328,13 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpGroupDecorate Decoration group <id> '1' is not a "
-                        "decoration group."));
+              HasSubstr("OpGroupDecorate Decoration group <id> '1[%1]' is not "
+                        "a decoration group."));
 }
 TEST_F(ValidateIdWithMessage, OpGroupDecorateTargetBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpDecorationGroup
-     OpDecorate %1 Uniform
+     OpDecorate %1 RelaxedPrecision
      OpDecorate %1 GLSLShared
      OpGroupDecorate %1 %3
 %2 = OpTypeInt 32 0)";
@@ -355,8 +354,8 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpGroupMemberDecorate Decoration group <id> '1' is "
-                        "not a decoration group."));
+              HasSubstr("OpGroupMemberDecorate Decoration group <id> '1[%1]' "
+                        "is not a decoration group."));
 }
 TEST_F(ValidateIdWithMessage, OpGroupMemberDecorateIdNotStructBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
@@ -366,8 +365,8 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpGroupMemberDecorate Structure type <id> '2' is not "
-                        "a struct type."));
+              HasSubstr("OpGroupMemberDecorate Structure type <id> '2[%uint]' "
+                        "is not a struct type."));
 }
 TEST_F(ValidateIdWithMessage, OpGroupMemberDecorateIndexOutOfBoundBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
@@ -381,8 +380,8 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("Index 3 provided in OpGroupMemberDecorate for struct "
-                        "<id> 2 is out of bounds. The structure has 3 members. "
-                        "Largest valid index is 2."));
+                        "<id> 2[%_struct_2] is out of bounds. The structure "
+                        "has 3 members. Largest valid index is 2."));
 }
 
 // TODO: OpExtInst
@@ -406,9 +405,9 @@
 %1 = OpTypeVoid)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("OpEntryPoint Entry Point <id> '1' is not a function."));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("OpEntryPoint Entry Point <id> '1[%void]' is not a "
+                        "function."));
 }
 TEST_F(ValidateIdWithMessage, OpEntryPointParameterCountBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
@@ -422,8 +421,8 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpEntryPoint Entry Point <id> '1's function parameter "
-                        "count is not zero"));
+              HasSubstr("OpEntryPoint Entry Point <id> '1[%1]'s function "
+                        "parameter count is not zero"));
 }
 TEST_F(ValidateIdWithMessage, OpEntryPointReturnTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
@@ -438,8 +437,8 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpEntryPoint Entry Point <id> '1's function return "
-                        "type is not void."));
+              HasSubstr("OpEntryPoint Entry Point <id> '1[%1]'s function "
+                        "return type is not void."));
 }
 
 TEST_F(ValidateIdWithMessage, OpEntryPointInterfaceIsNotVariableTypeBad) {
@@ -522,8 +521,8 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpExecutionMode Entry Point <id> '1' is not the Entry "
-                        "Point operand of an OpEntryPoint."));
+              HasSubstr("OpExecutionMode Entry Point <id> '1[%1]' is not the "
+                        "Entry Point operand of an OpEntryPoint."));
 }
 
 TEST_F(ValidateIdWithMessage, OpExecutionModeEntryPointBad) {
@@ -541,8 +540,8 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpExecutionMode Entry Point <id> '2' is not the Entry "
-                        "Point operand of an OpEntryPoint."));
+              HasSubstr("OpExecutionMode Entry Point <id> '2[%2]' is not the "
+                        "Entry Point operand of an OpEntryPoint."));
 }
 
 TEST_F(ValidateIdWithMessage, OpTypeVectorFloat) {
@@ -586,7 +585,8 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("OpTypeVector Component Type <id> '2' is not a scalar type."));
+      HasSubstr("OpTypeVector Component Type <id> "
+                "'2[%_ptr_UniformConstant_float]' is not a scalar type."));
 }
 
 TEST_F(ValidateIdWithMessage, OpTypeVectorColumnCountLessThanTwoBad) {
@@ -742,7 +742,8 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpTypeArray Element Type <id> '2' is not a type."));
+              HasSubstr("OpTypeArray Element Type <id> '2[%uint_1]' is not a "
+                        "type."));
 }
 
 // Signed or unsigned.
@@ -783,7 +784,8 @@
         spvValidate(ScopedContext().context, &cbinary, &diagnostic_);
     if (status != SPV_SUCCESS) {
       spvDiagnosticPrint(diagnostic_);
-      EXPECT_THAT(std::string(diagnostic_->error), HasSubstr(expected_err));
+      EXPECT_THAT(std::string(diagnostic_->error),
+                  testing::ContainsRegex(expected_err));
     }
     return status;
   }
@@ -817,35 +819,35 @@
 
 TEST_P(OpTypeArrayLengthTest, LengthZero) {
   const int width = GetParam();
-  EXPECT_EQ(
-      SPV_ERROR_INVALID_ID,
-      Val(CompileSuccessfully(MakeArrayLength("0", kSigned, width)),
-          "OpTypeArray Length <id> '2' default value must be at least 1."));
-  EXPECT_EQ(
-      SPV_ERROR_INVALID_ID,
-      Val(CompileSuccessfully(MakeArrayLength("0", kUnsigned, width)),
-          "OpTypeArray Length <id> '2' default value must be at least 1."));
+  EXPECT_EQ(SPV_ERROR_INVALID_ID,
+            Val(CompileSuccessfully(MakeArrayLength("0", kSigned, width)),
+                "OpTypeArray Length <id> '2\\[%.*\\]' default value must be at "
+                "least 1."));
+  EXPECT_EQ(SPV_ERROR_INVALID_ID,
+            Val(CompileSuccessfully(MakeArrayLength("0", kUnsigned, width)),
+                "OpTypeArray Length <id> '2\\[%.*\\]' default value must be at "
+                "least 1."));
 }
 
 TEST_P(OpTypeArrayLengthTest, LengthNegative) {
   const int width = GetParam();
-  EXPECT_EQ(
-      SPV_ERROR_INVALID_ID,
-      Val(CompileSuccessfully(MakeArrayLength("-1", kSigned, width)),
-          "OpTypeArray Length <id> '2' default value must be at least 1."));
-  EXPECT_EQ(
-      SPV_ERROR_INVALID_ID,
-      Val(CompileSuccessfully(MakeArrayLength("-2", kSigned, width)),
-          "OpTypeArray Length <id> '2' default value must be at least 1."));
-  EXPECT_EQ(
-      SPV_ERROR_INVALID_ID,
-      Val(CompileSuccessfully(MakeArrayLength("-123", kSigned, width)),
-          "OpTypeArray Length <id> '2' default value must be at least 1."));
+  EXPECT_EQ(SPV_ERROR_INVALID_ID,
+            Val(CompileSuccessfully(MakeArrayLength("-1", kSigned, width)),
+                "OpTypeArray Length <id> '2\\[%.*\\]' default value must be at "
+                "least 1."));
+  EXPECT_EQ(SPV_ERROR_INVALID_ID,
+            Val(CompileSuccessfully(MakeArrayLength("-2", kSigned, width)),
+                "OpTypeArray Length <id> '2\\[%.*\\]' default value must be at "
+                "least 1."));
+  EXPECT_EQ(SPV_ERROR_INVALID_ID,
+            Val(CompileSuccessfully(MakeArrayLength("-123", kSigned, width)),
+                "OpTypeArray Length <id> '2\\[%.*\\]' default value must be at "
+                "least 1."));
   const std::string neg_max = "0x8" + std::string(width / 4 - 1, '0');
-  EXPECT_EQ(
-      SPV_ERROR_INVALID_ID,
-      Val(CompileSuccessfully(MakeArrayLength(neg_max, kSigned, width)),
-          "OpTypeArray Length <id> '2' default value must be at least 1."));
+  EXPECT_EQ(SPV_ERROR_INVALID_ID,
+            Val(CompileSuccessfully(MakeArrayLength(neg_max, kSigned, width)),
+                "OpTypeArray Length <id> '2\\[%.*\\]' default value must be at "
+                "least 1."));
 }
 
 // The only valid widths for integers are 8, 16, 32, and 64.
@@ -866,7 +868,7 @@
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr(
-          "OpTypeArray Length <id> '2' default value must be at least 1."));
+          "OpTypeArray Length <id> '2[%2]' default value must be at least 1."));
 }
 
 TEST_F(ValidateIdWithMessage, OpTypeArrayLengthSpecConst) {
@@ -905,7 +907,8 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("OpTypeRuntimeArray Element Type <id> '2' is not a type."));
+      HasSubstr("OpTypeRuntimeArray Element Type <id> '2[%uint_0]' is not a "
+                "type."));
 }
 // TODO: Object of this type can only be created with OpVariable using the
 // Unifrom Storage Class
@@ -928,7 +931,8 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpTypeStruct Member Type <id> '3' is not a type."));
+              HasSubstr("OpTypeStruct Member Type <id> '3[%double_0]' is not "
+                        "a type."));
 }
 
 TEST_F(ValidateIdWithMessage, OpTypePointerGood) {
@@ -946,7 +950,8 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpTypePointer Type <id> '2' is not a type."));
+              HasSubstr("OpTypePointer Type <id> '2[%uint_0]' is not a "
+                        "type."));
 }
 
 TEST_F(ValidateIdWithMessage, OpTypeFunctionGood) {
@@ -964,7 +969,8 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpTypeFunction Return Type <id> '2' is not a type."));
+              HasSubstr("OpTypeFunction Return Type <id> '2[%uint_0]' is not "
+                        "a type."));
 }
 TEST_F(ValidateIdWithMessage, OpTypeFunctionParameterBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
@@ -976,7 +982,8 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("OpTypeFunction Parameter Type <id> '3' is not a type."));
+      HasSubstr("OpTypeFunction Parameter Type <id> '3[%uint_0]' is not a "
+                "type."));
 }
 
 TEST_F(ValidateIdWithMessage, OpTypeFunctionParameterTypeVoidBad) {
@@ -987,8 +994,8 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpTypeFunction Parameter Type <id> '1' cannot be "
-                        "OpTypeVoid."));
+              HasSubstr("OpTypeFunction Parameter Type <id> '1[%void]' cannot "
+                        "be OpTypeVoid."));
 }
 
 TEST_F(ValidateIdWithMessage, OpTypePipeGood) {
@@ -1015,7 +1022,8 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("OpConstantTrue Result Type <id> '1' is not a boolean type."));
+      HasSubstr("OpConstantTrue Result Type <id> '1[%void]' is not a boolean "
+                "type."));
 }
 
 TEST_F(ValidateIdWithMessage, OpConstantFalseGood) {
@@ -1033,7 +1041,8 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("OpConstantFalse Result Type <id> '1' is not a boolean type."));
+      HasSubstr("OpConstantFalse Result Type <id> '1[%void]' is not a boolean "
+                "type."));
 }
 
 TEST_F(ValidateIdWithMessage, OpConstantGood) {
@@ -1083,8 +1092,8 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr(
-          "OpConstantComposite Result Type <id> '1' is not a composite type."));
+      HasSubstr("OpConstantComposite Result Type <id> '1[%float]' is not a "
+                "composite type."));
 }
 TEST_F(ValidateIdWithMessage, OpConstantCompositeVectorConstituentTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
@@ -1098,8 +1107,9 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("OpConstantComposite Constituent <id> '5's type does not match "
-                "Result Type <id> '2's vector element type."));
+      HasSubstr("OpConstantComposite Constituent <id> '5[%uint_42]'s type "
+                "does not match Result Type <id> '2[%v4float]'s vector "
+                "element type."));
 }
 TEST_F(ValidateIdWithMessage,
        OpConstantCompositeVectorConstituentUndefTypeBad) {
@@ -1114,8 +1124,8 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("OpConstantComposite Constituent <id> '5's type does not match "
-                "Result Type <id> '2's vector element type."));
+      HasSubstr("OpConstantComposite Constituent <id> '5[%5]'s type does not "
+                "match Result Type <id> '2[%v4float]'s vector element type."));
 }
 TEST_F(ValidateIdWithMessage, OpConstantCompositeMatrixGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
@@ -1163,9 +1173,9 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpConstantComposite Constituent <id> '10' vector "
-                        "component count does not match Result Type <id> '4's "
-                        "vector component count."));
+              HasSubstr("OpConstantComposite Constituent <id> '10[%10]' vector "
+                        "component count does not match Result Type <id> "
+                        "'4[%mat4v4float]'s vector component count."));
 }
 TEST_F(ValidateIdWithMessage,
        OpConstantCompositeMatrixConstituentUndefTypeBad) {
@@ -1184,9 +1194,9 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpConstantComposite Constituent <id> '10' vector "
-                        "component count does not match Result Type <id> '4's "
-                        "vector component count."));
+              HasSubstr("OpConstantComposite Constituent <id> '10[%10]' vector "
+                        "component count does not match Result Type <id> "
+                        "'4[%mat4v4float]'s vector component count."));
 }
 TEST_F(ValidateIdWithMessage, OpConstantCompositeArrayGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
@@ -1207,6 +1217,7 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
+
 TEST_F(ValidateIdWithMessage, OpConstantCompositeArrayConstConstituentTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeInt 32 0
@@ -1215,7 +1226,8 @@
 %4 = OpConstantComposite %3 %2 %2 %2 %1)";  // Uses a type as operand
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 1 cannot be a type"));
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 1[%uint] cannot be a "
+                                               "type"));
 }
 TEST_F(ValidateIdWithMessage, OpConstantCompositeArrayConstConstituentBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
@@ -1228,7 +1240,7 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpConstantComposite Constituent <id> '5' is not a "
+              HasSubstr("OpConstantComposite Constituent <id> '5[%5]' is not a "
                         "constant or undef."));
 }
 TEST_F(ValidateIdWithMessage, OpConstantCompositeArrayConstituentTypeBad) {
@@ -1242,8 +1254,10 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpConstantComposite Constituent <id> '5's type does "
-                        "not match Result Type <id> '3's array element type."));
+              HasSubstr("OpConstantComposite Constituent <id> "
+                        "'5[%float_3_1400001]'s type does not match Result "
+                        "Type <id> '3[%_arr_uint_uint_4]'s array element "
+                        "type."));
 }
 TEST_F(ValidateIdWithMessage, OpConstantCompositeArrayConstituentUndefTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
@@ -1256,8 +1270,10 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpConstantComposite Constituent <id> '5's type does "
-                        "not match Result Type <id> '3's array element type."));
+              HasSubstr("OpConstantComposite Constituent <id> "
+                        "'5[%5]'s type does not match Result "
+                        "Type <id> '3[%_arr_uint_uint_4]'s array element "
+                        "type."));
 }
 TEST_F(ValidateIdWithMessage, OpConstantCompositeStructGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
@@ -1292,8 +1308,9 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpConstantComposite Constituent <id> '5' type does "
-                        "not match the Result Type <id> '3's member type."));
+              HasSubstr("OpConstantComposite Constituent <id> "
+                        "'5[%ulong_4300000000]' type does not match the "
+                        "Result Type <id> '3[%_struct_3]'s member type."));
 }
 
 TEST_F(ValidateIdWithMessage, OpConstantCompositeStructMemberUndefTypeBad) {
@@ -1307,8 +1324,9 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpConstantComposite Constituent <id> '5' type does "
-                        "not match the Result Type <id> '3's member type."));
+              HasSubstr("OpConstantComposite Constituent <id> '5[%5]' type "
+                        "does not match the Result Type <id> '3[%_struct_3]'s "
+                        "member type."));
 }
 
 TEST_F(ValidateIdWithMessage, OpConstantSamplerGood) {
@@ -1328,7 +1346,8 @@
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr(
-          "OpConstantSampler Result Type <id> '1' is not a sampler type."));
+          "OpConstantSampler Result Type <id> '1[%float]' is not a sampler "
+          "type."));
 }
 
 TEST_F(ValidateIdWithMessage, OpConstantNullGood) {
@@ -1375,8 +1394,8 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr(
-          "OpConstantNull Result Type <id> '1' cannot have a null value."));
+      HasSubstr("OpConstantNull Result Type <id> '1[%void]' cannot have a null "
+                "value."));
 }
 
 TEST_F(ValidateIdWithMessage, OpConstantNullArrayBad) {
@@ -1391,7 +1410,8 @@
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr(
-          "OpConstantNull Result Type <id> '4' cannot have a null value."));
+          "OpConstantNull Result Type <id> '4[%_arr_2_uint_4]' cannot have a "
+          "null value."));
 }
 
 TEST_F(ValidateIdWithMessage, OpConstantNullStructBad) {
@@ -1401,10 +1421,9 @@
 %4 = OpConstantNull %3)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr(
-          "OpConstantNull Result Type <id> '2' cannot have a null value."));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("OpConstantNull Result Type <id> '2[%_struct_2]' "
+                        "cannot have a null value."));
 }
 
 TEST_F(ValidateIdWithMessage, OpConstantNullRuntimeArrayBad) {
@@ -1417,7 +1436,8 @@
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr(
-          "OpConstantNull Result Type <id> '2' cannot have a null value."));
+          "OpConstantNull Result Type <id> '2[%_runtimearr_bool]' cannot have "
+          "a null value."));
 }
 
 TEST_F(ValidateIdWithMessage, OpSpecConstantTrueGood) {
@@ -1523,9 +1543,9 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpSpecConstantComposite Constituent <id> '5's type "
-                        "does not match Result Type <id> '2's vector element "
-                        "type."));
+              HasSubstr("OpSpecConstantComposite Constituent <id> "
+                        "'5[%uint_42]'s type does not match Result Type <id> "
+                        "'2[%v4float]'s vector element type."));
 }
 
 // Invalid: Constituent is not a constant
@@ -1542,8 +1562,8 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpSpecConstantComposite Constituent <id> '6' is not a "
-                        "constant or undef."));
+              HasSubstr("OpSpecConstantComposite Constituent <id> '6[%6]' is "
+                        "not a constant or undef."));
 }
 
 // Invalid: Vector contains a mix of Undef-int and Float.
@@ -1559,9 +1579,9 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpSpecConstantComposite Constituent <id> '5's type "
-                        "does not match Result Type <id> '2's vector element "
-                        "type."));
+              HasSubstr("OpSpecConstantComposite Constituent <id> '5[%5]'s "
+                        "type does not match Result Type <id> '2[%v4float]'s "
+                        "vector element type."));
 }
 
 // Invalid: Vector expects 3 components, but 4 specified.
@@ -1576,8 +1596,8 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("OpSpecConstantComposite Constituent <id> count does "
-                        "not match Result Type <id> '2's vector component "
-                        "count."));
+                        "not match Result Type <id> '2[%v3float]'s vector "
+                        "component count."));
 }
 
 // Valid: 4x4 matrix of floats
@@ -1631,9 +1651,9 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpSpecConstantComposite Constituent <id> '10' vector "
-                        "component count does not match Result Type <id> '4's "
-                        "vector component count."));
+              HasSubstr("OpSpecConstantComposite Constituent <id> '10[%10]' "
+                        "vector component count does not match Result Type "
+                        "<id> '4[%mat4v4float]'s vector component count."));
 }
 
 // Invalid: Matrix type expects 4 columns but only 3 specified.
@@ -1653,7 +1673,8 @@
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("OpSpecConstantComposite Constituent <id> count does "
-                "not match Result Type <id> '3's matrix column count."));
+                "not match Result Type <id> '3[%mat4v4float]'s matrix column "
+                "count."));
 }
 
 // Invalid: Composite contains a non-const/undef component
@@ -1671,8 +1692,8 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpSpecConstantComposite Constituent <id> '7' is not a "
-                        "constant composite or undef."));
+              HasSubstr("OpSpecConstantComposite Constituent <id> '7[%7]' is "
+                        "not a constant composite or undef."));
 }
 
 // Invalid: Composite contains a column that is *not* a vector (it's an array)
@@ -1691,9 +1712,9 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpSpecConstantComposite Constituent <id> '8' type "
-                        "does not match Result Type <id> '7's matrix column "
-                        "type."));
+              HasSubstr("OpSpecConstantComposite Constituent <id> '8[%8]' type "
+                        "does not match Result Type <id> '7[%mat4v4float]'s "
+                        "matrix column type."));
 }
 
 // Invalid: Matrix with an Undef column of the wrong size.
@@ -1714,9 +1735,9 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpSpecConstantComposite Constituent <id> '10' vector "
-                        "component count does not match Result Type <id> '4's "
-                        "vector component count."));
+              HasSubstr("OpSpecConstantComposite Constituent <id> '10[%10]' "
+                        "vector component count does not match Result Type "
+                        "<id> '4[%mat4v4float]'s vector component count."));
 }
 
 // Invalid: Matrix in which some columns are Int and some are Float.
@@ -1735,9 +1756,9 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpSpecConstantComposite Constituent <id> '8' "
-                        "component type does not match Result Type <id> '5's "
-                        "matrix column component type."));
+              HasSubstr("OpSpecConstantComposite Constituent <id> '8[%8]' "
+                        "component type does not match Result Type <id> "
+                        "'5[%mat2v2float]'s matrix column component type."));
 }
 
 // Valid: Array of integers
@@ -1765,7 +1786,8 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("OpSpecConstantComposite Constituent count does not "
-                        "match Result Type <id> '3's array length."));
+                        "match Result Type <id> '3[%_arr_uint_uint_4]'s array "
+                        "length."));
 }
 
 // Valid: Array of Integers and Undef-int
@@ -1792,8 +1814,8 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpSpecConstantComposite Constituent <id> '5' is not a "
-                        "constant or undef."));
+              HasSubstr("OpSpecConstantComposite Constituent <id> '5[%5]' is "
+                        "not a constant or undef."));
 }
 
 // Invalid: Array has a mix of Int and Float components.
@@ -1808,9 +1830,9 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpSpecConstantComposite Constituent <id> '5's type "
-                        "does not match Result Type <id> '3's array element "
-                        "type."));
+              HasSubstr("OpSpecConstantComposite Constituent <id> '5[%5]'s "
+                        "type does not match Result Type <id> "
+                        "'3[%_arr_uint_uint_4]'s array element type."));
 }
 
 // Invalid: Array has a mix of Int and Undef-float.
@@ -1826,9 +1848,9 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpSpecConstantComposite Constituent <id> '5's type "
-                        "does not match Result Type <id> '3's array element "
-                        "type."));
+              HasSubstr("OpSpecConstantComposite Constituent <id> '5[%5]'s "
+                        "type does not match Result Type <id> "
+                        "'3[%_arr_uint_2]'s array element type."));
 }
 
 // Valid: Struct of {Int32,Int32,Int64}.
@@ -1856,9 +1878,9 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpSpecConstantComposite Constituent <id> '2' count "
-                        "does not match Result Type <id> '2's struct member "
-                        "count."));
+              HasSubstr("OpSpecConstantComposite Constituent <id> "
+                        "'2[%_struct_2]' count does not match Result Type "
+                        "<id> '2[%_struct_2]'s struct member count."));
 }
 
 // Valid: Struct uses Undef-int64.
@@ -1888,8 +1910,8 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpSpecConstantComposite Constituent <id> '7' is not a "
-                        "constant or undef."));
+              HasSubstr("OpSpecConstantComposite Constituent <id> '7[%7]' is "
+                        "not a constant or undef."));
 }
 
 // Invalid: Struct component type does not match expected specialization type.
@@ -1905,9 +1927,9 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpSpecConstantComposite Constituent <id> '5' type "
-                        "does not match the Result Type <id> '3's member "
-                        "type."));
+              HasSubstr("OpSpecConstantComposite Constituent <id> '5[%5]' type "
+                        "does not match the Result Type <id> '3[%_struct_3]'s "
+                        "member type."));
 }
 
 // Invalid: Undef-int64 used when Int32 was expected.
@@ -1922,9 +1944,9 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpSpecConstantComposite Constituent <id> '5' type "
-                        "does not match the Result Type <id> '3's member "
-                        "type."));
+              HasSubstr("OpSpecConstantComposite Constituent <id> '5[%5]' type "
+                        "does not match the Result Type <id> '3[%_struct_3]'s "
+                        "member type."));
 }
 
 // TODO: OpSpecConstantOp
@@ -1966,7 +1988,8 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("OpVariable Result Type <id> '1' is not a pointer type."));
+      HasSubstr("OpVariable Result Type <id> '1[%uint]' is not a pointer "
+                "type."));
 }
 TEST_F(ValidateIdWithMessage, OpVariableInitializerIsTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
@@ -1975,7 +1998,8 @@
 %3 = OpVariable %2 Input %2)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 2 cannot be a type"));
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 2[%_ptr_Input_uint] "
+                                               "cannot be a type"));
 }
 
 TEST_F(ValidateIdWithMessage, OpVariableInitializerIsFunctionVarBad) {
@@ -1995,8 +2019,8 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpVariable Initializer <id> '8' is not a constant or "
-                        "module-scope variable"));
+              HasSubstr("OpVariable Initializer <id> '8[%8]' is not a constant "
+                        "or module-scope variable"));
 }
 
 TEST_F(ValidateIdWithMessage, OpVariableInitializerIsModuleVarGood) {
@@ -2389,8 +2413,9 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpLoad Result Type <id> '3' does not match Pointer "
-                        "<id> '5's type."));
+              HasSubstr("OpLoad Result Type <id> "
+                        "'3[%_ptr_UniformConstant_uint]' does not match "
+                        "Pointer <id> '5[%5]'s type."));
 }
 
 TEST_F(ValidateIdWithMessage, OpLoadPointerBad) {
@@ -2409,7 +2434,8 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   // Prove that SSA checks trigger for a bad Id value.
   // The next test case show the not-a-logical-pointer case.
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("ID 8 has not been defined"));
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("ID 8[%8] has not been "
+                                               "defined"));
 }
 
 // Disabled as bitcasting type to object is now not valid.
@@ -2471,7 +2497,8 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpStore Pointer <id> '7' is not a logical pointer."));
+              HasSubstr("OpStore Pointer <id> '7[%uint_0]' is not a logical "
+                        "pointer."));
 }
 
 // Disabled as bitcasting type to object is now not valid.
@@ -2551,7 +2578,7 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpStore Object <id> '9's type is void."));
+              HasSubstr("OpStore Object <id> '9[%9]'s type is void."));
 }
 TEST_F(ValidateIdWithMessage, OpStoreTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
@@ -2570,8 +2597,8 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpStore Pointer <id> '7's type does not match Object "
-                        "<id> '6's type."));
+              HasSubstr("OpStore Pointer <id> '7[%7]'s type does not match "
+                        "Object <id> '6[%float_3_1400001]'s type."));
 }
 
 // The next series of test check test a relaxation of the rules for stores to
@@ -2603,8 +2630,8 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpStore Pointer <id> '8's type does not match Object "
-                        "<id> '11's type."));
+              HasSubstr("OpStore Pointer <id> '8[%8]'s type does not match "
+                        "Object <id> '11[%11]'s type."));
 }
 
 // Same code as the last test.  The difference is that we relax the rule.
@@ -2734,8 +2761,8 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("OpStore Pointer <id> '13's layout does not match Object "
-                "<id> '16's layout."));
+      HasSubstr("OpStore Pointer <id> '13[%13]'s layout does not match Object "
+                "<id> '16[%16]'s layout."));
 }
 
 // This test check that the even with the relaxed rules an error is identified
@@ -2774,8 +2801,8 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("OpStore Pointer <id> '13's layout does not match Object "
-                "<id> '16's layout."));
+      HasSubstr("OpStore Pointer <id> '13[%13]'s layout does not match Object "
+                "<id> '16[%16]'s layout."));
 }
 
 TEST_F(ValidateIdWithMessage, OpStoreTypeRelaxedLogicalPointerReturnPointer) {
@@ -2836,7 +2863,7 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpStore Object <id> '8's type is void."));
+              HasSubstr("OpStore Object <id> '8[%8]'s type is void."));
 }
 
 TEST_F(ValidateIdWithMessage, OpStoreLabel) {
@@ -2854,7 +2881,7 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpStore Object <id> '7' is not an object."));
+              HasSubstr("OpStore Object <id> '7[%7]' is not an object."));
 }
 
 // TODO: enable when this bug is fixed:
@@ -2951,7 +2978,7 @@
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Target operand <id> '6' is not a pointer."));
+              HasSubstr("Target operand <id> '6[%6]' is not a pointer."));
 }
 
 TEST_F(ValidateIdWithMessage, OpCopyMemoryNonPointerSource) {
@@ -2972,7 +2999,7 @@
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Source operand <id> '6' is not a pointer."));
+              HasSubstr("Source operand <id> '6[%6]' is not a pointer."));
 }
 
 TEST_F(ValidateIdWithMessage, OpCopyMemoryBad) {
@@ -2995,8 +3022,8 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Target <id> '5's type does not match "
-                        "Source <id> '2's type."));
+              HasSubstr("Target <id> '5[%5]'s type does not match "
+                        "Source <id> '2[%uint]'s type."));
 }
 
 TEST_F(ValidateIdWithMessage, OpCopyMemoryVoidTarget) {
@@ -3018,7 +3045,8 @@
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Target operand <id> '7' cannot be a void pointer."));
+              HasSubstr("Target operand <id> '7[%7]' cannot be a void "
+                        "pointer."));
 }
 
 TEST_F(ValidateIdWithMessage, OpCopyMemoryVoidSource) {
@@ -3040,7 +3068,8 @@
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Source operand <id> '7' cannot be a void pointer."));
+              HasSubstr("Source operand <id> '7[%7]' cannot be a void "
+                        "pointer."));
 }
 
 TEST_F(ValidateIdWithMessage, OpCopyMemorySizedGood) {
@@ -3078,7 +3107,7 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Target operand <id> '9' is not a pointer."));
+              HasSubstr("Target operand <id> '9[%9]' is not a pointer."));
 }
 TEST_F(ValidateIdWithMessage, OpCopyMemorySizedSourceBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
@@ -3097,7 +3126,7 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Source operand <id> '5' is not a pointer."));
+              HasSubstr("Source operand <id> '5[%uint_4]' is not a pointer."));
 }
 TEST_F(ValidateIdWithMessage, OpCopyMemorySizedSizeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
@@ -3118,7 +3147,7 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("Size operand <id> '6' must be a scalar integer type."));
+      HasSubstr("Size operand <id> '6[%6]' must be a scalar integer type."));
 }
 TEST_F(ValidateIdWithMessage, OpCopyMemorySizedSizeTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
@@ -3141,7 +3170,8 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("Size operand <id> '9' must be a scalar integer type."));
+      HasSubstr("Size operand <id> '9[%float_1]' must be a scalar integer "
+                "type."));
 }
 
 TEST_F(ValidateIdWithMessage, OpCopyMemorySizedSizeConstantNull) {
@@ -3165,7 +3195,8 @@
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Size operand <id> '3' cannot be a constant zero."));
+              HasSubstr("Size operand <id> '3[%3]' cannot be a constant "
+                        "zero."));
 }
 
 TEST_F(ValidateIdWithMessage, OpCopyMemorySizedSizeConstantZero) {
@@ -3189,7 +3220,8 @@
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Size operand <id> '3' cannot be a constant zero."));
+              HasSubstr("Size operand <id> '3[%uint_0]' cannot be a constant "
+                        "zero."));
 }
 
 TEST_F(ValidateIdWithMessage, OpCopyMemorySizedSizeConstantZero64) {
@@ -3213,7 +3245,8 @@
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Size operand <id> '3' cannot be a constant zero."));
+              HasSubstr("Size operand <id> '3[%ulong_0]' cannot be a constant "
+                        "zero."));
 }
 
 TEST_F(ValidateIdWithMessage, OpCopyMemorySizedSizeConstantNegative) {
@@ -3238,7 +3271,8 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("Size operand <id> '3' cannot have the sign bit set to 1."));
+      HasSubstr("Size operand <id> '3[%int_n1]' cannot have the sign bit set "
+                "to 1."));
 }
 
 TEST_F(ValidateIdWithMessage, OpCopyMemorySizedSizeConstantNegative64) {
@@ -3263,7 +3297,8 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("Size operand <id> '3' cannot have the sign bit set to 1."));
+      HasSubstr("Size operand <id> '3[%long_n1]' cannot have the sign bit set "
+                "to 1."));
 }
 
 TEST_F(ValidateIdWithMessage, OpCopyMemorySizedSizeUnsignedNegative) {
@@ -3403,7 +3438,7 @@
   )";
 
   const std::string expected_err = "The Result Type of " + instr +
-                                   " <id> '36' must be "
+                                   " <id> '36[%36]' must be "
                                    "OpTypePointer. Found OpTypeFloat.";
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
@@ -3423,7 +3458,8 @@
   )";
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 1 cannot be a type"));
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 1[%void] cannot be a "
+                                               "type"));
 }
 
 // Invalid. The base type of an access chain instruction must be a pointer.
@@ -3440,7 +3476,8 @@
   )";
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 8 cannot be a type"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Operand 8[%_ptr_Private_float] cannot be a type"));
 }
 
 // Invalid: The storage class of Base and Result do not match.
@@ -3743,8 +3780,8 @@
   )";
   const std::string expected_err = "Index is out of bounds: " + instr +
                                    " can not find index 3 into the structure "
-                                   "<id> '25'. This structure has 3 members. "
-                                   "Largest valid index is 2.";
+                                   "<id> '25[%_struct_25]'. This structure "
+                                   "has 3 members. Largest valid index is 2.";
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(), HasSubstr(expected_err));
@@ -3890,8 +3927,9 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpFunction Result Type <id> '2' does not match the "
-                        "Function Type's return type <id> '1'."));
+              HasSubstr("OpFunction Result Type <id> '2[%uint]' does not "
+                        "match the Function Type's return type <id> "
+                        "'1[%void]'."));
 }
 TEST_F(ValidateIdWithMessage, OpReturnValueTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
@@ -3906,8 +3944,8 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpReturnValue Value <id> '3's type does not match "
-                        "OpFunction's return type."));
+              HasSubstr("OpReturnValue Value <id> '3[%float_0]'s type does "
+                        "not match OpFunction's return type."));
 }
 TEST_F(ValidateIdWithMessage, OpFunctionFunctionTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
@@ -3921,7 +3959,8 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("OpFunction Function Type <id> '2' is not a function type."));
+      HasSubstr("OpFunction Function Type <id> '2[%uint]' is not a function "
+                "type."));
 }
 
 TEST_F(ValidateIdWithMessage, OpFunctionUseBad) {
@@ -3937,7 +3976,7 @@
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Invalid use of function result id 3."));
+              HasSubstr("Invalid use of function result id 3[%3]."));
 }
 
 TEST_F(ValidateIdWithMessage, OpFunctionParameterGood) {
@@ -3981,8 +4020,8 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("OpFunctionParameter Result Type <id> '1' does not match the "
-                "OpTypeFunction parameter type of the same index."));
+      HasSubstr("OpFunctionParameter Result Type <id> '1[%void]' does not "
+                "match the OpTypeFunction parameter type of the same index."));
 }
 
 TEST_F(ValidateIdWithMessage, OpFunctionCallGood) {
@@ -4030,8 +4069,9 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpFunctionCall Result Type <id> '1's type does not "
-                        "match Function <id> '2's return type."));
+              HasSubstr("OpFunctionCall Result Type <id> '1[%void]'s type "
+                        "does not match Function <id> '2[%uint]'s return "
+                        "type."));
 }
 TEST_F(ValidateIdWithMessage, OpFunctionCallFunctionBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
@@ -4049,7 +4089,8 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpFunctionCall Function <id> '5' is not a function."));
+              HasSubstr("OpFunctionCall Function <id> '5[%uint_42]' is not a "
+                        "function."));
 }
 TEST_F(ValidateIdWithMessage, OpFunctionCallArgumentTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
@@ -4077,8 +4118,9 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpFunctionCall Argument <id> '7's type does not match "
-                        "Function <id> '2's parameter type."));
+              HasSubstr("OpFunctionCall Argument <id> '7[%float_3_1400001]'s "
+                        "type does not match Function <id> '2[%uint]'s "
+                        "parameter type."));
 }
 
 // Valid: OpSampledImage result <id> is used in the same block by
@@ -4109,8 +4151,8 @@
       getDiagnosticString(),
       HasSubstr("All OpSampledImage instructions must be in the same block in "
                 "which their Result <id> are consumed. OpSampledImage Result "
-                "Type <id> '23' has a consumer in a different basic block. The "
-                "consumer instruction <id> is '25'."));
+                "Type <id> '23[%23]' has a consumer in a different basic "
+                "block. The consumer instruction <id> is '25[%25]'."));
 }
 
 // Invalid: OpSampledImage result <id> is used by OpSelect
@@ -4314,7 +4356,7 @@
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("OpVectorShuffle component literals count does not match "
-                "Result Type <id> '2's vector component count."));
+                "Result Type <id> '2[%v3uint]'s vector component count."));
 }
 
 TEST_F(ValidateIdWithMessage, OpVectorShuffleVector1Type) {
@@ -4591,7 +4633,8 @@
 
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("ID 3 is not a type id"));
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("ID 3[%true] is not a type "
+                                               "id"));
 }
 
 TEST_F(ValidateIdWithMessage, OpPhiSamePredecessor) {
@@ -4703,8 +4746,9 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpPhi's result type <id> 2 does not match incoming "
-                        "value <id> 6 type <id> 5."));
+              HasSubstr("OpPhi's result type <id> 2[%bool] does not match "
+                        "incoming value <id> 6[%uint_0] type <id> "
+                        "5[%uint]."));
 }
 
 TEST_F(ValidateIdWithMessage, OpPhiPredecessorNotABlock) {
@@ -4728,9 +4772,9 @@
 
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("OpPhi's incoming basic block <id> 3 is not an OpLabel."));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("OpPhi's incoming basic block <id> 3[%true] is not an "
+                        "OpLabel."));
 }
 
 TEST_F(ValidateIdWithMessage, OpPhiNotAPredecessor) {
@@ -4755,8 +4799,8 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpPhi's incoming basic block <id> 9 is not a "
-                        "predecessor of <id> 8."));
+              HasSubstr("OpPhi's incoming basic block <id> 9[%9] is not a "
+                        "predecessor of <id> 8[%8]."));
 }
 
 TEST_F(ValidateIdWithMessage, OpBranchConditionalGood) {
@@ -4856,7 +4900,8 @@
 
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 3 cannot be a type"));
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 3[%bool] cannot be a "
+                                               "type"));
 }
 
 // TODO: OpSwitch
@@ -4918,7 +4963,8 @@
      OpFunctionEnd)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 1 cannot be a type"));
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 1[%void] cannot be a "
+                                               "type"));
 }
 
 TEST_F(ValidateIdWithMessage, OpReturnValueIsLabel) {
@@ -4932,9 +4978,9 @@
      OpFunctionEnd)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("OpReturnValue Value <id> '5' does not represent a value."));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("OpReturnValue Value <id> '5[%5]' does not represent a "
+                        "value."));
 }
 
 TEST_F(ValidateIdWithMessage, OpReturnValueIsVoid) {
@@ -4951,7 +4997,8 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("OpReturnValue value's type <id> '1' is missing or void."));
+      HasSubstr("OpReturnValue value's type <id> '1[%void]' is missing or "
+                "void."));
 }
 
 TEST_F(ValidateIdWithMessage, OpReturnValueIsVariableInPhysical) {
@@ -4987,8 +5034,9 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpReturnValue value's type <id> '3' is a pointer, "
-                        "which is invalid in the Logical addressing model."));
+              HasSubstr("OpReturnValue value's type <id> "
+                        "'3[%_ptr_Function_uint]' is a pointer, which is "
+                        "invalid in the Logical addressing model."));
 }
 
 // With the VariablePointer Capability, the return value of a function is
@@ -5060,7 +5108,8 @@
 )";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("ID 7 has not been defined"));
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("ID 7[%7] has not been "
+                                               "defined"));
 }
 
 TEST_F(ValidateIdWithMessage, UndefinedIdMemSem) {
@@ -5077,7 +5126,8 @@
 )";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("ID 7 has not been defined"));
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("ID 7[%7] has not been "
+                                               "defined"));
 }
 
 TEST_F(ValidateIdWithMessage,
@@ -5212,7 +5262,8 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("OpLoad type for pointer <id> '11' is not a pointer type."));
+      HasSubstr("OpLoad type for pointer <id> '11[%11]' is not a pointer "
+                "type."));
 }
 TEST_F(ValidateIdWithMessage, OpStoreBitcastPointerGood) {
   std::string spirv = kOpenCLMemoryModel64 + R"(
@@ -5252,7 +5303,8 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("OpStore type for pointer <id> '11' is not a pointer type."));
+      HasSubstr("OpStore type for pointer <id> '11[%11]' is not a pointer "
+                "type."));
 }
 
 // Result <id> resulting from an instruction within a function may not be used
@@ -5279,7 +5331,8 @@
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr(
-          "ID 7 defined in block 6 does not dominate its use in block 9"));
+          "ID 7[%7] defined in block 6[%6] does not dominate its use in block "
+          "9[%9]"));
 }
 
 TEST_F(ValidateIdWithMessage, SpecIdTargetNotSpecializationConstant) {
@@ -5297,8 +5350,9 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpDecorate SpecId decoration target <id> '1' is not a "
-                        "scalar specialization constant."));
+              HasSubstr("OpDecorate SpecId decoration target <id> "
+                        "'1[%uint_3]' is not a scalar specialization "
+                        "constant."));
 }
 
 TEST_F(ValidateIdWithMessage, SpecIdTargetOpSpecConstantOpBad) {
@@ -5318,8 +5372,8 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpDecorate SpecId decoration target <id> '1' is not a "
-                        "scalar specialization constant."));
+              HasSubstr("OpDecorate SpecId decoration target <id> '1[%1]' is "
+                        "not a scalar specialization constant."));
 }
 
 TEST_F(ValidateIdWithMessage, SpecIdTargetOpSpecConstantCompositeBad) {
@@ -5338,8 +5392,8 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpDecorate SpecId decoration target <id> '1' is not a "
-                        "scalar specialization constant."));
+              HasSubstr("OpDecorate SpecId decoration target <id> '1[%1]' is "
+                        "not a scalar specialization constant."));
 }
 
 TEST_F(ValidateIdWithMessage, SpecIdTargetGood) {
@@ -5415,7 +5469,7 @@
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Invalid use of function type result id 2."));
+              HasSubstr("Invalid use of function type result id 2[%2]."));
 }
 
 TEST_F(ValidateIdWithMessage, BadTypeId) {
@@ -5433,7 +5487,8 @@
 
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("ID 4 is not a type id"));
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("ID 4[%float_0] is not a type "
+                                               "id"));
 }
 
 TEST_F(ValidateIdWithMessage, VulkanMemoryModelLoadMakePointerVisibleGood) {
@@ -6094,8 +6149,8 @@
   CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("ID 7 defined in block 6 does not dominate its use in "
-                        "block 9\n  %9 = OpLabel"));
+              HasSubstr("ID 7[%7] defined in block 6[%6] does not dominate its "
+                        "use in block 9[%9]\n  %9 = OpLabel"));
 }
 
 TEST_F(ValidateIdWithMessage, IdDefInUnreachableBlock2) {
@@ -6120,8 +6175,8 @@
   CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("ID 8 defined in block 7 does not dominate its use in "
-                        "block 10\n  %10 = OpLabel"));
+              HasSubstr("ID 8[%8] defined in block 7[%7] does not dominate its "
+                        "use in block 10[%10]\n  %10 = OpLabel"));
 }
 
 TEST_F(ValidateIdWithMessage, IdDefInUnreachableBlock3) {
@@ -6146,8 +6201,8 @@
   CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("ID 8 defined in block 7 does not dominate its use in "
-                        "block 10\n  %10 = OpLabel"));
+              HasSubstr("ID 8[%8] defined in block 7[%7] does not dominate its "
+                        "use in block 10[%10]\n  %10 = OpLabel"));
 }
 
 TEST_F(ValidateIdWithMessage, IdDefInUnreachableBlock4) {
@@ -6213,8 +6268,8 @@
   CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("ID 9 defined in block 8 does not dominate its use in "
-                        "block 7\n  %7 = OpLabel"));
+              HasSubstr("ID 9[%9] defined in block 8[%8] does not dominate its "
+                        "use in block 7[%7]\n  %7 = OpLabel"));
 }
 
 TEST_F(ValidateIdWithMessage, ReachableDefUnreachableUse) {
@@ -6265,8 +6320,59 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("In OpPhi instruction 14, ID 13 definition does not dominate "
-                "its parent 7\n  %14 = OpPhi %float %11 %10 %13 %7"));
+      HasSubstr("In OpPhi instruction 14[%14], ID 13[%13] definition does not "
+                "dominate its parent 7[%7]\n  %14 = OpPhi %float %11 %10 %13 "
+                "%7"));
+}
+
+TEST_F(ValidateIdWithMessage, OpTypeForwardPointerNotAPointerType) {
+  std::string spirv = R"(
+     OpCapability GenericPointer
+     OpCapability VariablePointersStorageBuffer
+     OpMemoryModel Logical GLSL450
+     OpEntryPoint Fragment %1 "main"
+     OpExecutionMode %1 OriginLowerLeft
+     OpTypeForwardPointer %2 CrossWorkgroup
+%2 = OpTypeVoid
+%3 = OpTypeFunction %2
+%1 = OpFunction %2 DontInline %3
+%4 = OpLabel
+     OpReturn
+     OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Pointer type in OpTypeForwardPointer is not a pointer "
+                        "type.\n  OpTypeForwardPointer %void CrossWorkgroup"));
+}
+
+TEST_F(ValidateIdWithMessage, OpTypeForwardPointerWrongStorageClass) {
+  std::string spirv = R"(
+     OpCapability GenericPointer
+     OpCapability VariablePointersStorageBuffer
+     OpMemoryModel Logical GLSL450
+     OpEntryPoint Fragment %1 "main"
+     OpExecutionMode %1 OriginLowerLeft
+     OpTypeForwardPointer %2 CrossWorkgroup
+%int = OpTypeInt 32 1
+%2 = OpTypePointer Function %int
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%1 = OpFunction %void None %3
+%4 = OpLabel
+     OpReturn
+     OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Storage class in OpTypeForwardPointer does not match the "
+                "pointer definition.\n  OpTypeForwardPointer "
+                "%_ptr_Function_int CrossWorkgroup"));
 }
 }  // namespace
 }  // namespace val
diff --git a/test/val/val_image_test.cpp b/test/val/val_image_test.cpp
index a84b401..79aecb2 100644
--- a/test/val/val_image_test.cpp
+++ b/test/val/val_image_test.cpp
@@ -61,6 +61,37 @@
     ss << "OpExecutionMode %main OriginUpperLeft\n";
   }
 
+  if (env == SPV_ENV_VULKAN_1_0) {
+    ss << R"(
+OpDecorate %uniform_image_f32_1d_0001 DescriptorSet 0
+OpDecorate %uniform_image_f32_1d_0001 Binding 0
+OpDecorate %uniform_image_f32_1d_0002_rgba32f DescriptorSet 0
+OpDecorate %uniform_image_f32_1d_0002_rgba32f Binding 1
+OpDecorate %uniform_image_f32_2d_0001 DescriptorSet 0
+OpDecorate %uniform_image_f32_2d_0001 Binding 2
+OpDecorate %uniform_image_f32_2d_0010 DescriptorSet 0
+OpDecorate %uniform_image_f32_2d_0010 Binding 3
+OpDecorate %uniform_image_u32_2d_0001 DescriptorSet 1
+OpDecorate %uniform_image_u32_2d_0001 Binding 0
+OpDecorate %uniform_image_u32_2d_0000 DescriptorSet 1
+OpDecorate %uniform_image_u32_2d_0000 Binding 1
+OpDecorate %uniform_image_s32_3d_0001 DescriptorSet 1
+OpDecorate %uniform_image_s32_3d_0001 Binding 2
+OpDecorate %uniform_image_f32_2d_0002 DescriptorSet 1
+OpDecorate %uniform_image_f32_2d_0002 Binding 3
+OpDecorate %uniform_image_f32_spd_0002 DescriptorSet 2
+OpDecorate %uniform_image_f32_spd_0002 Binding 0
+OpDecorate %uniform_image_f32_3d_0111 DescriptorSet 2
+OpDecorate %uniform_image_f32_3d_0111 Binding 1
+OpDecorate %uniform_image_f32_cube_0101 DescriptorSet 2
+OpDecorate %uniform_image_f32_cube_0101 Binding 2
+OpDecorate %uniform_image_f32_cube_0102_rgba32f DescriptorSet 2
+OpDecorate %uniform_image_f32_cube_0102_rgba32f Binding 3
+OpDecorate %uniform_sampler DescriptorSet 3
+OpDecorate %uniform_sampler Binding 0
+)";
+  }
+
   ss << R"(
 %void = OpTypeVoid
 %func = OpTypeFunction %void
@@ -672,7 +703,8 @@
 
   CompileSuccessfully(GenerateShaderCode(body).c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 136 cannot be a type"));
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 136[%136] cannot be a "
+                                               "type"));
 }
 
 TEST_F(ValidateImage, ImageTexelPointerImageNotImage) {
@@ -4203,7 +4235,7 @@
 TEST_F(ValidateImage, MakeTexelVisibleKHRSuccessImageRead) {
   const std::string body = R"(
 %img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000
-%res1 = OpImageRead %u32vec4 %img %u32vec2_01 MakeTexelVisibleKHR|NonPrivateTexelKHR %u32_1
+%res1 = OpImageRead %u32vec4 %img %u32vec2_01 MakeTexelVisibleKHR|NonPrivateTexelKHR %u32_2
 )";
 
   const std::string extra = R"(
@@ -4220,7 +4252,7 @@
 TEST_F(ValidateImage, MakeTexelVisibleKHRSuccessImageSparseRead) {
   const std::string body = R"(
 %img = OpLoad %type_image_f32_2d_0002 %uniform_image_f32_2d_0002
-%res1 = OpImageSparseRead %struct_u32_f32vec4 %img %u32vec2_01 MakeTexelVisibleKHR|NonPrivateTexelKHR %u32_1
+%res1 = OpImageSparseRead %struct_u32_f32vec4 %img %u32vec2_01 MakeTexelVisibleKHR|NonPrivateTexelKHR %u32_2
 )";
 
   const std::string extra = R"(
@@ -4282,7 +4314,7 @@
 TEST_F(ValidateImage, MakeTexelAvailableKHRSuccessImageWrite) {
   const std::string body = R"(
 %img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000
-%res1 = OpImageWrite %img %u32vec2_01 %u32vec4_0123 MakeTexelAvailableKHR|NonPrivateTexelKHR %u32_1
+%res1 = OpImageWrite %img %u32vec2_01 %u32vec4_0123 MakeTexelAvailableKHR|NonPrivateTexelKHR %u32_2
 )";
 
   const std::string extra = R"(
@@ -4340,6 +4372,86 @@
                         "NonPrivateTexelKHR is also specified: OpImageWrite"));
 }
 
+TEST_F(ValidateImage, VulkanMemoryModelDeviceScopeImageWriteBad) {
+  const std::string body = R"(
+%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000
+%res1 = OpImageWrite %img %u32vec2_01 %u32vec4_0123 MakeTexelAvailableKHR|NonPrivateTexelKHR %u32_1
+)";
+
+  const std::string extra = R"(
+OpCapability StorageImageWriteWithoutFormat
+OpCapability VulkanMemoryModelKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+)";
+  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment",
+                                         SPV_ENV_UNIVERSAL_1_3, "VulkanKHR")
+                          .c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Use of device scope with VulkanKHR memory model requires the "
+                "VulkanMemoryModelDeviceScopeKHR capability"));
+}
+
+TEST_F(ValidateImage, VulkanMemoryModelDeviceScopeImageWriteGood) {
+  const std::string body = R"(
+%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000
+%res1 = OpImageWrite %img %u32vec2_01 %u32vec4_0123 MakeTexelAvailableKHR|NonPrivateTexelKHR %u32_1
+)";
+
+  const std::string extra = R"(
+OpCapability StorageImageWriteWithoutFormat
+OpCapability VulkanMemoryModelKHR
+OpCapability VulkanMemoryModelDeviceScopeKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+)";
+  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment",
+                                         SPV_ENV_UNIVERSAL_1_3, "VulkanKHR")
+                          .c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
+TEST_F(ValidateImage, VulkanMemoryModelDeviceScopeImageReadBad) {
+  const std::string body = R"(
+%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000
+%res1 = OpImageRead %u32vec4 %img %u32vec2_01 MakeTexelVisibleKHR|NonPrivateTexelKHR %u32_1
+)";
+
+  const std::string extra = R"(
+OpCapability StorageImageReadWithoutFormat
+OpCapability VulkanMemoryModelKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+)";
+  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment",
+                                         SPV_ENV_UNIVERSAL_1_3, "VulkanKHR")
+                          .c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Use of device scope with VulkanKHR memory model requires the "
+                "VulkanMemoryModelDeviceScopeKHR capability"));
+}
+
+TEST_F(ValidateImage, VulkanMemoryModelDeviceScopeImageReadGood) {
+  const std::string body = R"(
+%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000
+%res1 = OpImageRead %u32vec4 %img %u32vec2_01 MakeTexelVisibleKHR|NonPrivateTexelKHR %u32_1
+)";
+
+  const std::string extra = R"(
+OpCapability StorageImageReadWithoutFormat
+OpCapability VulkanMemoryModelKHR
+OpCapability VulkanMemoryModelDeviceScopeKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+)";
+  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment",
+                                         SPV_ENV_UNIVERSAL_1_3, "VulkanKHR")
+                          .c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_layout_test.cpp b/test/val/val_layout_test.cpp
index e67c840..e502d8c 100644
--- a/test/val/val_layout_test.cpp
+++ b/test/val/val_layout_test.cpp
@@ -648,6 +648,57 @@
       HasSubstr("ModuleProcessed cannot appear in a function declaration"));
 }
 
+TEST_F(ValidateLayout, WebGPUCallerBeforeCalleeBad) {
+  char str[] = R"(
+           OpCapability Shader
+           OpCapability VulkanMemoryModelKHR
+           OpExtension "SPV_KHR_vulkan_memory_model"
+           OpMemoryModel Logical VulkanKHR
+           OpEntryPoint GLCompute %main "main"
+%void    = OpTypeVoid
+%voidfn  = OpTypeFunction %void
+%main    = OpFunction %void None %voidfn
+%1       = OpLabel
+%2       = OpFunctionCall %void %callee
+           OpReturn
+           OpFunctionEnd
+%callee  = OpFunction %void None %voidfn
+%3       = OpLabel
+           OpReturn
+           OpFunctionEnd
+)";
+
+  CompileSuccessfully(str, SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("For WebGPU, functions need to be defined before being "
+                        "called.\n  %5 = OpFunctionCall %void %6\n"));
+}
+
+TEST_F(ValidateLayout, WebGPUCalleeBeforeCallerGood) {
+  char str[] = R"(
+           OpCapability Shader
+           OpCapability VulkanMemoryModelKHR
+           OpExtension "SPV_KHR_vulkan_memory_model"
+           OpMemoryModel Logical VulkanKHR
+           OpEntryPoint GLCompute %main "main"
+%void    = OpTypeVoid
+%voidfn  = OpTypeFunction %void
+%callee  = OpFunction %void None %voidfn
+%3       = OpLabel
+           OpReturn
+           OpFunctionEnd
+%main    = OpFunction %void None %voidfn
+%1       = OpLabel
+%2       = OpFunctionCall %void %callee
+           OpReturn
+           OpFunctionEnd
+)";
+
+  CompileSuccessfully(str, SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
+}
+
 // TODO(umar): Test optional instructions
 
 }  // namespace
diff --git a/test/val/val_limits_test.cpp b/test/val/val_limits_test.cpp
index 1368109..791b709 100644
--- a/test/val/val_limits_test.cpp
+++ b/test/val/val_limits_test.cpp
@@ -353,7 +353,7 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("OpTypeFunction may not take more than 255 arguments. "
-                        "OpTypeFunction <id> '2' has 256 arguments."));
+                        "OpTypeFunction <id> '2[%2]' has 256 arguments."));
 }
 
 // Valid: OpTypeFunction with 100 arguments (Custom limit: 100)
@@ -389,7 +389,7 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("OpTypeFunction may not take more than 100 arguments. "
-                        "OpTypeFunction <id> '2' has 101 arguments."));
+                        "OpTypeFunction <id> '2[%2]' has 101 arguments."));
 }
 
 // Valid: module has 65,535 global variables.
diff --git a/test/val/val_memory_test.cpp b/test/val/val_memory_test.cpp
index 9d35531..296ea43 100644
--- a/test/val/val_memory_test.cpp
+++ b/test/val/val_memory_test.cpp
@@ -64,6 +64,8 @@
 OpMemoryModel Logical GLSL450
 OpEntryPoint Fragment %func "func"
 OpExecutionMode %func OriginUpperLeft
+OpDecorate %2 DescriptorSet 0
+OpDecorate %2 Binding 0
 %sampler = OpTypeSampler
 %sampler_ptr = OpTypePointer UniformConstant %sampler
 %2 = OpVariable %sampler_ptr UniformConstant
@@ -115,6 +117,8 @@
 OpMemoryModel Logical GLSL450
 OpEntryPoint Fragment %func "func"
 OpExecutionMode %func OriginUpperLeft
+OpDecorate %2 DescriptorSet 0
+OpDecorate %2 Binding 0
 %sampler = OpTypeSampler
 %uint = OpTypeInt 32 0
 %array_size = OpConstant %uint 5
@@ -138,6 +142,8 @@
 OpMemoryModel Logical GLSL450
 OpEntryPoint Fragment %func "func"
 OpExecutionMode %func OriginUpperLeft
+OpDecorate %2 DescriptorSet 0
+OpDecorate %2 Binding 0
 %sampler = OpTypeSampler
 %uint = OpTypeInt 32 0
 %array = OpTypeRuntimeArray %sampler
@@ -442,9 +448,8 @@
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr(
-          "OpVariable, <id> '5', has a disallowed initializer & storage class "
-          "combination.\n"
-          "From WebGPU execution environment spec:\n"
+          "OpVariable, <id> '5[%5]', has a disallowed initializer & storage "
+          "class combination.\nFrom WebGPU execution environment spec:\n"
           "Variable declarations that include initializers must have one of "
           "the following storage classes: Output, Private, or Function\n"
           "  %5 = OpVariable %_ptr_Uniform_float Uniform %float_1\n"));
@@ -535,9 +540,8 @@
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr(
-          "OpVariable, <id> '5', has a disallowed initializer & storage class "
-          "combination.\n"
-          "From Vulkan spec, Appendix A:\n"
+          "OpVariable, <id> '5[%5]', has a disallowed initializer & storage "
+          "class combination.\nFrom Vulkan spec, Appendix A:\n"
           "Variable declarations that include initializers must have one of "
           "the following storage classes: Output, Private, or Function\n  "
           "%5 = OpVariable %_ptr_Input_float Input %float_1\n"));
@@ -620,8 +624,9 @@
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr(
-          "The Result Type of OpArrayLength <id> '10' must be OpTypeInt with "
-          "width 32 and signedness 0.\n  %10 = OpArrayLength %float %9 0\n"));
+          "The Result Type of OpArrayLength <id> '10[%10]' must be OpTypeInt "
+          "with width 32 and signedness 0.\n  %10 = OpArrayLength %float %9 "
+          "0\n"));
 }
 
 TEST_F(ValidateMemory, ArrayLenResultNot32bits) {
@@ -652,8 +657,9 @@
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr(
-          "The Result Type of OpArrayLength <id> '11' must be OpTypeInt with "
-          "width 32 and signedness 0.\n  %11 = OpArrayLength %ushort %10 0\n"));
+          "The Result Type of OpArrayLength <id> '11[%11]' must be OpTypeInt "
+          "with width 32 and signedness 0.\n  %11 = OpArrayLength %ushort %10 "
+          "0\n"));
 }
 
 TEST_F(ValidateMemory, ArrayLenResultSigned) {
@@ -683,8 +689,9 @@
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr(
-          "The Result Type of OpArrayLength <id> '11' must be OpTypeInt with "
-          "width 32 and signedness 0.\n  %11 = OpArrayLength %int %10 0\n"));
+          "The Result Type of OpArrayLength <id> '11[%11]' must be OpTypeInt "
+          "with width 32 and signedness 0.\n  %11 = OpArrayLength %int %10 "
+          "0\n"));
 }
 
 TEST_F(ValidateMemory, ArrayLenInputNotStruct) {
@@ -712,8 +719,8 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("The Struture's type in OpArrayLength <id> '11' must "
-                        "be a pointer to an OpTypeStruct."));
+              HasSubstr("The Struture's type in OpArrayLength <id> '11[%11]' "
+                        "must be a pointer to an OpTypeStruct."));
 }
 
 TEST_F(ValidateMemory, ArrayLenInputLastMemberNoRTA) {
@@ -742,8 +749,9 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("The Struture's last member in OpArrayLength <id> '11' must be "
-                "an OpTypeRuntimeArray.\n  %11 = OpArrayLength %uint %10 0\n"));
+      HasSubstr("The Struture's last member in OpArrayLength <id> '11[%11]' "
+                "must be an OpTypeRuntimeArray.\n  %11 = OpArrayLength %uint "
+                "%10 0\n"));
 }
 
 TEST_F(ValidateMemory, ArrayLenInputLastMemberNoRTA2) {
@@ -772,8 +780,9 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("The Struture's last member in OpArrayLength <id> '11' must be "
-                "an OpTypeRuntimeArray.\n  %11 = OpArrayLength %uint %10 1\n"));
+      HasSubstr("The Struture's last member in OpArrayLength <id> '11[%11]' "
+                "must be an OpTypeRuntimeArray.\n  %11 = OpArrayLength %uint "
+                "%10 1\n"));
 }
 
 TEST_F(ValidateMemory, ArrayLenIndexNotLastMember) {
@@ -803,8 +812,8 @@
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr(
-          "The array member in OpArrayLength <id> '11' must be an the last "
-          "member of the struct.\n  %11 = OpArrayLength %uint %10 0\n"));
+          "The array member in OpArrayLength <id> '11[%11]' must be an the "
+          "last member of the struct.\n  %11 = OpArrayLength %uint %10 0\n"));
 }
 
 TEST_F(ValidateMemory, ArrayLenIndexNotPointerToStruct) {
@@ -835,8 +844,8 @@
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr(
-          "The Struture's type in OpArrayLength <id> '12' must be a pointer to "
-          "an OpTypeStruct.\n  %12 = OpArrayLength %uint %11 0\n"));
+          "The Struture's type in OpArrayLength <id> '12[%12]' must be a "
+          "pointer to an OpTypeStruct.\n  %12 = OpArrayLength %uint %11 0\n"));
 }
 
 TEST_F(ValidateMemory, ArrayLenPointerIsAType) {
@@ -859,7 +868,8 @@
 
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 4 cannot be a type"));
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 4[%float] cannot be a "
+                                               "type"));
 }
 
 TEST_F(ValidateMemory, PushConstantNotStructGood) {
@@ -905,8 +915,8 @@
   CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("PushConstant OpVariable <id> '6' has illegal type.\n"
-                        "From Vulkan spec, section 14.5.1:\n"
+              HasSubstr("PushConstant OpVariable <id> '6[%6]' has illegal "
+                        "type.\nFrom Vulkan spec, section 14.5.1:\n"
                         "Such variables must be typed as OpTypeStruct, "
                         "or an array of this type"));
 }
@@ -937,6 +947,572 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1));
 }
 
+TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeLoadBad1) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability Linkage
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+%load = OpLoad %int %var MakePointerVisibleKHR|NonPrivatePointerKHR %device
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Use of device scope with VulkanKHR memory model requires the "
+                "VulkanMemoryModelDeviceScopeKHR capability"));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeLoadBad2) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability Linkage
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+%load = OpLoad %int %var Aligned|MakePointerVisibleKHR|NonPrivatePointerKHR 4 %device
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Use of device scope with VulkanKHR memory model requires the "
+                "VulkanMemoryModelDeviceScopeKHR capability"));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeLoadGood1) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability VulkanMemoryModelDeviceScopeKHR
+OpCapability Linkage
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+%load = OpLoad %int %var MakePointerVisibleKHR|NonPrivatePointerKHR %device
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeLoadGood2) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability VulkanMemoryModelDeviceScopeKHR
+OpCapability Linkage
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+%load = OpLoad %int %var Aligned|MakePointerVisibleKHR|NonPrivatePointerKHR 4 %device
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeStoreBad1) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability Linkage
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpStore %var %device MakePointerAvailableKHR|NonPrivatePointerKHR %device
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Use of device scope with VulkanKHR memory model requires the "
+                "VulkanMemoryModelDeviceScopeKHR capability"));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeStoreBad2) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability Linkage
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpStore %var %device Aligned|MakePointerAvailableKHR|NonPrivatePointerKHR 4 %device
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Use of device scope with VulkanKHR memory model requires the "
+                "VulkanMemoryModelDeviceScopeKHR capability"));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeStoreGood1) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability VulkanMemoryModelDeviceScopeKHR
+OpCapability Linkage
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpStore %var %device MakePointerAvailableKHR|NonPrivatePointerKHR %device
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeStoreGood2) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability VulkanMemoryModelDeviceScopeKHR
+OpCapability Linkage
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpStore %var %device Aligned|MakePointerAvailableKHR|NonPrivatePointerKHR 4 %device
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemoryBad1) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability Linkage
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var1 = OpVariable %int_ptr_ssbo StorageBuffer
+%var2 = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemory %var1 %var2 MakePointerAvailableKHR|NonPrivatePointerKHR %device
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Use of device scope with VulkanKHR memory model requires the "
+                "VulkanMemoryModelDeviceScopeKHR capability"));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemoryBad2) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability Linkage
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%workgroup = OpConstant %int 1
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var1 = OpVariable %int_ptr_ssbo StorageBuffer
+%var2 = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemory %var1 %var2 Aligned|MakePointerVisibleKHR|MakePointerAvailableKHR|NonPrivatePointerKHR 4 %device %workgroup
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Use of device scope with VulkanKHR memory model requires the "
+                "VulkanMemoryModelDeviceScopeKHR capability"));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemoryBad3) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability Linkage
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%workgroup = OpConstant %int 1
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var1 = OpVariable %int_ptr_ssbo StorageBuffer
+%var2 = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemory %var1 %var2 Aligned|MakePointerVisibleKHR|MakePointerAvailableKHR|NonPrivatePointerKHR 4 %workgroup %device
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Use of device scope with VulkanKHR memory model requires the "
+                "VulkanMemoryModelDeviceScopeKHR capability"));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemoryGood1) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability VulkanMemoryModelDeviceScopeKHR
+OpCapability Linkage
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var1 = OpVariable %int_ptr_ssbo StorageBuffer
+%var2 = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemory %var1 %var2 MakePointerAvailableKHR|NonPrivatePointerKHR %device
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemoryGood2) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability VulkanMemoryModelDeviceScopeKHR
+OpCapability Linkage
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%workgroup = OpConstant %int 2
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var1 = OpVariable %int_ptr_ssbo StorageBuffer
+%var2 = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemory %var1 %var2 Aligned|MakePointerVisibleKHR|MakePointerAvailableKHR|NonPrivatePointerKHR 4 %device %workgroup
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemoryGood3) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability VulkanMemoryModelDeviceScopeKHR
+OpCapability Linkage
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%workgroup = OpConstant %int 2
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var1 = OpVariable %int_ptr_ssbo StorageBuffer
+%var2 = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemory %var1 %var2 Aligned|MakePointerVisibleKHR|MakePointerAvailableKHR|NonPrivatePointerKHR 4 %workgroup %device
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemorySizedBad1) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability Linkage
+OpCapability Addresses
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var1 = OpVariable %int_ptr_ssbo StorageBuffer
+%var2 = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemorySized %var1 %var2 %device MakePointerAvailableKHR|NonPrivatePointerKHR %device
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Use of device scope with VulkanKHR memory model requires the "
+                "VulkanMemoryModelDeviceScopeKHR capability"));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemorySizedBad2) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability Linkage
+OpCapability Addresses
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%workgroup = OpConstant %int 1
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var1 = OpVariable %int_ptr_ssbo StorageBuffer
+%var2 = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemorySized %var1 %var2 %device Aligned|MakePointerVisibleKHR|MakePointerAvailableKHR|NonPrivatePointerKHR 4 %device %workgroup
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Use of device scope with VulkanKHR memory model requires the "
+                "VulkanMemoryModelDeviceScopeKHR capability"));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemorySizedBad3) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability Linkage
+OpCapability Addresses
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%workgroup = OpConstant %int 1
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var1 = OpVariable %int_ptr_ssbo StorageBuffer
+%var2 = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemorySized %var1 %var2 %device Aligned|MakePointerVisibleKHR|MakePointerAvailableKHR|NonPrivatePointerKHR 4 %workgroup %device
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Use of device scope with VulkanKHR memory model requires the "
+                "VulkanMemoryModelDeviceScopeKHR capability"));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemorySizedGood1) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability VulkanMemoryModelDeviceScopeKHR
+OpCapability Linkage
+OpCapability Addresses
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var1 = OpVariable %int_ptr_ssbo StorageBuffer
+%var2 = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemorySized %var1 %var2 %device MakePointerAvailableKHR|NonPrivatePointerKHR %device
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemorySizedGood2) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability VulkanMemoryModelDeviceScopeKHR
+OpCapability Linkage
+OpCapability Addresses
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%workgroup = OpConstant %int 2
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var1 = OpVariable %int_ptr_ssbo StorageBuffer
+%var2 = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemorySized %var1 %var2 %device Aligned|MakePointerVisibleKHR|MakePointerAvailableKHR|NonPrivatePointerKHR 4 %device %workgroup
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemorySizedGood3) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability VulkanMemoryModelDeviceScopeKHR
+OpCapability Linkage
+OpCapability Addresses
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%workgroup = OpConstant %int 2
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var1 = OpVariable %int_ptr_ssbo StorageBuffer
+%var2 = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemorySized %var1 %var2 %device Aligned|MakePointerVisibleKHR|MakePointerAvailableKHR|NonPrivatePointerKHR 4 %workgroup %device
+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_ssa_test.cpp b/test/val/val_ssa_test.cpp
index 9da80c0..5d8fa4b 100644
--- a/test/val/val_ssa_test.cpp
+++ b/test/val/val_ssa_test.cpp
@@ -118,7 +118,7 @@
   CompileSuccessfully(str);
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              MatchesRegex("ID .\\[bad\\] has not been defined\n"
+              MatchesRegex("ID .\\[%bad\\] has not been defined\n"
                            "  %8 = OpIAdd %uint %uint_1 %bad\n"));
 }
 
@@ -141,7 +141,7 @@
   CompileSuccessfully(str);
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              MatchesRegex("ID .\\[sum\\] has not been defined\n"
+              MatchesRegex("ID .\\[%sum\\] has not been defined\n"
                            "  %sum = OpIAdd %uint %uint_1 %sum\n"));
 }
 
@@ -202,7 +202,9 @@
 )";
   CompileSuccessfully(str);
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("size"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("The following forward referenced IDs have not been "
+                        "defined:\n2[%2]"));
 }
 
 TEST_F(ValidateSSA, ForwardDecorateGood) {
@@ -1124,8 +1126,8 @@
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      MatchesRegex("ID .\\[eleven\\] defined in block .\\[true_block\\] does "
-                   "not dominate its use in block .\\[false_block\\]\n"
+      MatchesRegex("ID .\\[%eleven\\] defined in block .\\[%true_block\\] "
+                   "does not dominate its use in block .\\[%false_block\\]\n"
                    "  %false_block = OpLabel\n"));
 }
 
@@ -1185,7 +1187,7 @@
   CompileSuccessfully(str);
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              MatchesRegex("ID .\\[inew\\] has not been defined\n"
+              MatchesRegex("ID .\\[%inew\\] has not been defined\n"
                            "  %19 = OpIAdd %uint %inew %uint_1\n"));
 }
 
@@ -1268,8 +1270,8 @@
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      MatchesRegex("In OpPhi instruction .\\[phi\\], ID .\\[true_copy\\] "
-                   "definition does not dominate its parent .\\[if_false\\]\n"
+      MatchesRegex("In OpPhi instruction .\\[%phi\\], ID .\\[%true_copy\\] "
+                   "definition does not dominate its parent .\\[%if_false\\]\n"
                    "  %phi = OpPhi %bool %true_copy %if_false %false_copy "
                    "%if_true\n"));
 }
@@ -1396,8 +1398,8 @@
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      MatchesRegex("ID .\\[first\\] used in function .\\[func2\\] is used "
-                   "outside of it's defining function .\\[func\\]\n"
+      MatchesRegex("ID .\\[%first\\] used in function .\\[%func2\\] is used "
+                   "outside of it's defining function .\\[%func\\]\n"
                    "  %func = OpFunction %void None %14\n"));
 }
 
diff --git a/test/val/val_validation_state_test.cpp b/test/val/val_validation_state_test.cpp
index 68504c5..e010fe9 100644
--- a/test/val/val_validation_state_test.cpp
+++ b/test/val/val_validation_state_test.cpp
@@ -29,11 +29,17 @@
 
 using ValidationStateTest = spvtest::ValidateBase<bool>;
 
-const char header[] =
+const char kHeader[] =
     " OpCapability Shader"
     " OpCapability Linkage"
     " OpMemoryModel Logical GLSL450 ";
 
+const char kVulkanMemoryHeader[] =
+    " OpCapability Shader"
+    " OpCapability VulkanMemoryModelKHR"
+    " OpExtension \"SPV_KHR_vulkan_memory_model\""
+    " OpMemoryModel Logical VulkanKHR ";
+
 const char kVoidFVoid[] =
     " %void   = OpTypeVoid"
     " %void_f = OpTypeFunction %void"
@@ -42,9 +48,79 @@
     "           OpReturn"
     "           OpFunctionEnd ";
 
+// k*RecursiveBody examples originally from test/opt/function_test.cpp
+const char* kNonRecursiveBody = R"(
+OpEntryPoint Fragment %1 "main"
+OpExecutionMode %1 OriginUpperLeft
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_struct_6 = OpTypeStruct %float %float
+%7 = OpTypeFunction %_struct_6
+%12 = OpFunction %_struct_6 None %7
+%13 = OpLabel
+OpUnreachable
+OpFunctionEnd
+%9 = OpFunction %_struct_6 None %7
+%10 = OpLabel
+%11 = OpFunctionCall %_struct_6 %12
+OpUnreachable
+OpFunctionEnd
+%1 = OpFunction %void Pure|Const %4
+%8 = OpLabel
+%2 = OpFunctionCall %_struct_6 %9
+OpKill
+OpFunctionEnd
+)";
+
+const char* kDirectlyRecursiveBody = R"(
+OpEntryPoint Fragment %1 "main"
+OpExecutionMode %1 OriginUpperLeft
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_struct_6 = OpTypeStruct %float %float
+%7 = OpTypeFunction %_struct_6
+%9 = OpFunction %_struct_6 None %7
+%10 = OpLabel
+%11 = OpFunctionCall %_struct_6 %9
+OpKill
+OpFunctionEnd
+%1 = OpFunction %void Pure|Const %4
+%8 = OpLabel
+%2 = OpFunctionCall %_struct_6 %9
+OpUnreachable
+OpFunctionEnd
+)";
+
+const char* kIndirectlyRecursiveBody = R"(
+OpEntryPoint Fragment %1 "main"
+OpExecutionMode %1 OriginUpperLeft
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_struct_6 = OpTypeStruct %float %float
+%7 = OpTypeFunction %_struct_6
+%9 = OpFunction %_struct_6 None %7
+%10 = OpLabel
+%11 = OpFunctionCall %_struct_6 %12
+OpUnreachable
+OpFunctionEnd
+%12 = OpFunction %_struct_6 None %7
+%13 = OpLabel
+%14 = OpFunctionCall %_struct_6 %9
+OpUnreachable
+OpFunctionEnd
+%1 = OpFunction %void Pure|Const %4
+%8 = OpLabel
+%2 = OpFunctionCall %_struct_6 %9
+OpKill
+OpFunctionEnd
+)";
+
 // Tests that the instruction count in ValidationState is correct.
 TEST_F(ValidationStateTest, CheckNumInstructions) {
-  std::string spirv = std::string(header) + "%int = OpTypeInt 32 0";
+  std::string spirv = std::string(kHeader) + "%int = OpTypeInt 32 0";
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
   EXPECT_EQ(size_t(4), vstate_->ordered_instructions().size());
@@ -52,7 +128,7 @@
 
 // Tests that the number of global variables in ValidationState is correct.
 TEST_F(ValidationStateTest, CheckNumGlobalVars) {
-  std::string spirv = std::string(header) + R"(
+  std::string spirv = std::string(kHeader) + R"(
      %int = OpTypeInt 32 0
 %_ptr_int = OpTypePointer Input %int
    %var_1 = OpVariable %_ptr_int Input
@@ -65,7 +141,7 @@
 
 // Tests that the number of local variables in ValidationState is correct.
 TEST_F(ValidationStateTest, CheckNumLocalVars) {
-  std::string spirv = std::string(header) + R"(
+  std::string spirv = std::string(kHeader) + R"(
  %int      = OpTypeInt 32 0
  %_ptr_int = OpTypePointer Function %int
  %voidt    = OpTypeVoid
@@ -85,7 +161,7 @@
 
 // Tests that the "id bound" in ValidationState is correct.
 TEST_F(ValidationStateTest, CheckIdBound) {
-  std::string spirv = std::string(header) + R"(
+  std::string spirv = std::string(kHeader) + R"(
  %int      = OpTypeInt 32 0
  %voidt    = OpTypeVoid
   )";
@@ -96,7 +172,7 @@
 
 // Tests that the entry_points in ValidationState is correct.
 TEST_F(ValidationStateTest, CheckEntryPoints) {
-  std::string spirv = std::string(header) +
+  std::string spirv = std::string(kHeader) +
                       " OpEntryPoint Vertex %func \"shader\"" +
                       std::string(kVoidFVoid);
   CompileSuccessfully(spirv);
@@ -154,6 +230,82 @@
   EXPECT_EQ(100u, options_->universal_limits_.max_access_chain_indexes);
 }
 
+TEST_F(ValidationStateTest, CheckNonRecursiveBodyGood) {
+  std::string spirv = std::string(kHeader) + kNonRecursiveBody;
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
+}
+
+TEST_F(ValidationStateTest, CheckVulkanNonRecursiveBodyGood) {
+  std::string spirv = std::string(kVulkanMemoryHeader) + kNonRecursiveBody;
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_SUCCESS,
+            ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
+}
+
+TEST_F(ValidationStateTest, CheckWebGPUNonRecursiveBodyGood) {
+  std::string spirv = std::string(kVulkanMemoryHeader) + kNonRecursiveBody;
+  CompileSuccessfully(spirv, SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState(SPV_ENV_WEBGPU_0));
+}
+
+TEST_F(ValidationStateTest, CheckDirectlyRecursiveBodyGood) {
+  std::string spirv = std::string(kHeader) + kDirectlyRecursiveBody;
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
+}
+
+TEST_F(ValidationStateTest, CheckVulkanDirectlyRecursiveBodyBad) {
+  std::string spirv = std::string(kVulkanMemoryHeader) + kDirectlyRecursiveBody;
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_ERROR_INVALID_BINARY,
+            ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Entry points may not have a call graph with cycles.\n "
+                        " %1 = OpFunction %void Pure|Const %3\n"));
+}
+
+TEST_F(ValidationStateTest, CheckWebGPUDirectlyRecursiveBodyBad) {
+  std::string spirv = std::string(kVulkanMemoryHeader) + kDirectlyRecursiveBody;
+  CompileSuccessfully(spirv, SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_BINARY,
+            ValidateAndRetrieveValidationState(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Entry points may not have a call graph with cycles.\n "
+                        " %1 = OpFunction %void Pure|Const %3\n"));
+}
+
+TEST_F(ValidationStateTest, CheckIndirectlyRecursiveBodyGood) {
+  std::string spirv = std::string(kHeader) + kIndirectlyRecursiveBody;
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
+}
+
+TEST_F(ValidationStateTest, CheckVulkanIndirectlyRecursiveBodyBad) {
+  std::string spirv =
+      std::string(kVulkanMemoryHeader) + kIndirectlyRecursiveBody;
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_ERROR_INVALID_BINARY,
+            ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Entry points may not have a call graph with cycles.\n "
+                        " %1 = OpFunction %void Pure|Const %3\n"));
+}
+
+// Indirectly recursive functions are caught by the function definition layout
+// rules, because they cause a situation where there are 2 functions that have
+// to be before each other, and layout is checked earlier.
+TEST_F(ValidationStateTest, CheckWebGPUIndirectlyRecursiveBodyBad) {
+  std::string spirv =
+      std::string(kVulkanMemoryHeader) + kIndirectlyRecursiveBody;
+  CompileSuccessfully(spirv, SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_LAYOUT,
+            ValidateAndRetrieveValidationState(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("For WebGPU, functions need to be defined before being "
+                        "called.\n  %9 = OpFunctionCall %_struct_5 %10\n"));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/tools/opt/opt.cpp b/tools/opt/opt.cpp
index 32b4b23..0e8088f 100644
--- a/tools/opt/opt.cpp
+++ b/tools/opt/opt.cpp
@@ -23,6 +23,7 @@
 #include <vector>
 
 #include "source/opt/log.h"
+#include "source/spirv_target_env.h"
 #include "source/util/string_utils.h"
 #include "spirv-tools/libspirv.hpp"
 #include "spirv-tools/optimizer.hpp"
@@ -189,9 +190,9 @@
                Looks for instructions in the same basic block that compute the
                same value, and deletes the redundant ones.
   --loop-fission
-               Splits any top level loops in which the register pressure has exceeded
-               a given threshold. The threshold must follow the use of this flag and
-               must be a positive integer value.
+               Splits any top level loops in which the register pressure has
+               exceeded a given threshold. The threshold must follow the use of
+               this flag and must be a positive integer value.
   --loop-fusion
                Identifies adjacent loops with the same lower and upper bound.
                If this is legal, then merge the loops into a single loop.
@@ -199,6 +200,9 @@
                registers too much, while reducing the number of loads from
                memory. Takes an additional positive integer argument to set
                the maximum number of registers.
+  --loop-invariant-code-motion
+               Identifies code in loops that has the same value for every
+               iteration of the loop, and move it to the loop pre-header.
   --loop-unroll
                Fully unrolls loops marked with the Unroll flag
   --loop-unroll-partial
@@ -335,6 +339,11 @@
   --strip-reflect
                Remove all reflection information.  For now, this covers
                reflection information defined by SPV_GOOGLE_hlsl_functionality1.
+  --target-env=<env>
+               Set the target environment. Without this flag the target
+               enviroment defaults to spv1.3.
+               <env> must be one of vulkan1.0, vulkan1.1, opencl2.2, spv1.0,
+               spv1.1, spv1.2, spv1.3, or webgpu0.
   --time-report
                Print the resource utilization of each pass (e.g., CPU time,
                RSS) to standard error output. Currently it supports only Unix
@@ -563,6 +572,17 @@
         optimizer_options->set_max_id_bound(max_id_bound);
         validator_options->SetUniversalLimit(spv_validator_limit_max_id_bound,
                                              max_id_bound);
+      } else if (0 == strncmp(cur_arg,
+                              "--target-env=", sizeof("--target-env=") - 1)) {
+        const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
+        const auto target_env_str = split_flag.second.c_str();
+        spv_target_env target_env;
+        if (!spvParseTargetEnv(target_env_str, &target_env)) {
+          spvtools::Error(opt_diagnostic, nullptr, {},
+                          "Invalid value passed to --target-env");
+          return {OPT_STOP, 1};
+        }
+        optimizer->SetTargetEnv(target_env);
       } else {
         // Some passes used to accept the form '--pass arg', canonicalize them
         // to '--pass=arg'.
diff --git a/tools/reduce/reduce.cpp b/tools/reduce/reduce.cpp
index 6b0af09..390724a 100644
--- a/tools/reduce/reduce.cpp
+++ b/tools/reduce/reduce.cpp
@@ -23,7 +23,9 @@
 #include "source/reduce/operand_to_const_reduction_pass.h"
 #include "source/reduce/operand_to_dominating_id_reduction_pass.h"
 #include "source/reduce/reducer.h"
+#include "source/reduce/remove_opname_instruction_reduction_pass.h"
 #include "source/reduce/remove_unreferenced_instruction_reduction_pass.h"
+#include "source/reduce/structured_loop_to_selection_reduction_pass.h"
 #include "source/spirv_reducer_options.h"
 #include "source/util/make_unique.h"
 #include "source/util/string_utils.h"
@@ -204,12 +206,16 @@
       });
 
   reducer.AddReductionPass(
+      spvtools::MakeUnique<RemoveOpNameInstructionReductionPass>(target_env));
+  reducer.AddReductionPass(
       spvtools::MakeUnique<OperandToConstReductionPass>(target_env));
   reducer.AddReductionPass(
       spvtools::MakeUnique<OperandToDominatingIdReductionPass>(target_env));
   reducer.AddReductionPass(
       spvtools::MakeUnique<RemoveUnreferencedInstructionReductionPass>(
           target_env));
+  reducer.AddReductionPass(
+      spvtools::MakeUnique<StructuredLoopToSelectionReductionPass>(target_env));
 
   reducer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);