Add base and core bindless validation instrumentation classes (#2014)

* Add base and core bindless validation instrumentation classes

* Fix formatting.

* Few more formatting fixes

* Fix build failure

* More build fixes

* Need to call non-const functions in order.

Specifically, these are functions which call TakeNextId(). These need to
be called in a specific order to guarantee that tests which do exact
compares will work across all platforms. c++ pretty much does not
guarantee order of evaluation of operands, so any such functions need to
be called separately in individual statements to guarantee order.

* More ordering.

* And more ordering.

* And more formatting.

* Attempt to fix NDK build

* Another attempt to address NDK build problem.

* One more attempt at NDK build failure

* Add instrument.hpp to BUILD.gn

* Some name improvement in instrument.hpp

* Change all types in instrument.hpp to int.

* Improve documentation in instrument.hpp

* Format fixes

* Comment clean up in instrument.hpp

* imageInst -> image_inst

* Fix GetLabel() issue.
diff --git a/Android.mk b/Android.mk
index 4e6025d..79a79f6 100644
--- a/Android.mk
+++ b/Android.mk
@@ -102,8 +102,10 @@
 		source/opt/inline_pass.cpp \
 		source/opt/inline_exhaustive_pass.cpp \
 		source/opt/inline_opaque_pass.cpp \
+		source/opt/inst_bindless_check_pass.cpp \
 		source/opt/instruction.cpp \
 		source/opt/instruction_list.cpp \
+		source/opt/instrument_pass.cpp \
 		source/opt/ir_context.cpp \
 		source/opt/ir_loader.cpp \
 		source/opt/licm_pass.cpp \
diff --git a/BUILD.gn b/BUILD.gn
index cf6286f..5a5b476 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -297,6 +297,7 @@
     "include/spirv-tools/libspirv.hpp",
     "include/spirv-tools/linker.hpp",
     "include/spirv-tools/optimizer.hpp",
+    "include/spirv-tools/instrument.hpp",
   ]
 
   public_configs = [ ":spvtools_public_config" ]
@@ -511,10 +512,14 @@
     "source/opt/inline_opaque_pass.h",
     "source/opt/inline_pass.cpp",
     "source/opt/inline_pass.h",
+    "source/opt/inst_bindless_check_pass.cpp",
+    "source/opt/inst_bindless_check_pass.h",
     "source/opt/instruction.cpp",
     "source/opt/instruction.h",
     "source/opt/instruction_list.cpp",
     "source/opt/instruction_list.h",
+    "source/opt/instrument_pass.cpp",
+    "source/opt/instrument_pass.h",
     "source/opt/ir_builder.h",
     "source/opt/ir_context.cpp",
     "source/opt/ir_context.h",
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5872f92..8143123 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -239,6 +239,7 @@
       ${CMAKE_CURRENT_SOURCE_DIR}/include/spirv-tools/libspirv.hpp
       ${CMAKE_CURRENT_SOURCE_DIR}/include/spirv-tools/optimizer.hpp
       ${CMAKE_CURRENT_SOURCE_DIR}/include/spirv-tools/linker.hpp
+      ${CMAKE_CURRENT_SOURCE_DIR}/include/spirv-tools/instrument.hpp
     DESTINATION
       ${CMAKE_INSTALL_INCLUDEDIR}/spirv-tools/)
 endif(ENABLE_SPIRV_TOOLS_INSTALL)
diff --git a/include/spirv-tools/instrument.hpp b/include/spirv-tools/instrument.hpp
new file mode 100644
index 0000000..69d1ad2
--- /dev/null
+++ b/include/spirv-tools/instrument.hpp
@@ -0,0 +1,135 @@
+// Copyright (c) 2018 The Khronos Group Inc.
+// Copyright (c) 2018 Valve Corporation
+// Copyright (c) 2018 LunarG Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef INCLUDE_SPIRV_TOOLS_INSTRUMENT_HPP_
+#define INCLUDE_SPIRV_TOOLS_INSTRUMENT_HPP_
+
+// Shader Instrumentation Interface
+//
+// This file provides an external interface for applications that wish to
+// communicate with shaders instrumented by passes created by:
+//
+//   CreateInstBindlessCheckPass
+//
+// More detailed documentation of this routine can be found in optimizer.hpp
+
+namespace spvtools {
+
+// Stream Output Buffer Offsets
+//
+// The following values provide 32-bit word offsets into the output buffer
+// generated by InstrumentPass::GenDebugStreamWrite. This method is utilized
+// by InstBindlessCheckPass.
+//
+// The first word of the debug output buffer contains the next available word
+// in the data stream to be written. Shaders will atomically read and update
+// this value so as not to overwrite each others records. This value must be
+// initialized to zero
+static const int kDebugOutputSizeOffset = 0;
+
+// The second word of the output buffer is the start of the stream of records
+// written by the instrumented shaders. Each record represents a validation
+// error. The format of the records is documented below.
+static const int kDebugOutputDataOffset = 1;
+
+// Common Stream Record Offsets
+//
+// The following are offsets to fields which are common to all records written
+// to the output stream.
+//
+// Each record first contains the size of the record in 32-bit words, including
+// the size word.
+static const int kInstCommonOutSize = 0;
+
+// This is the shader id passed by the layer when the instrumentation pass is
+// created.
+static const int kInstCommonOutShaderId = 1;
+
+// This is the ordinal position of the instruction within the SPIR-V shader
+// which generated the validation error.
+static const int kInstCommonOutInstructionIdx = 2;
+
+// This is the stage which generated the validation error. This word is used
+// to determine the contents of the next two words in the record.
+// 0:Vert, 1:TessCtrl, 2:TessEval, 3:Geom, 4:Frag, 5:Compute
+static const int kInstCommonOutStageIdx = 3;
+static const int kInstCommonOutCnt = 4;
+
+// Stage-specific Stream Record Offsets
+//
+// Each stage will contain different values in the next two words of the record
+// used to identify which instantiation of the shader generated the validation
+// error.
+//
+// Vertex Shader Output Record Offsets
+static const int kInstVertOutVertexId = kInstCommonOutCnt;
+static const int kInstVertOutInstanceId = kInstCommonOutCnt + 1;
+
+// Frag Shader Output Record Offsets
+static const int kInstFragOutFragCoordX = kInstCommonOutCnt;
+static const int kInstFragOutFragCoordY = kInstCommonOutCnt + 1;
+
+// Compute Shader Output Record Offsets
+static const int kInstCompOutGlobalInvocationId = kInstCommonOutCnt;
+static const int kInstCompOutUnused = kInstCommonOutCnt + 1;
+
+// Tessellation Shader Output Record Offsets
+static const int kInstTessOutInvocationId = kInstCommonOutCnt;
+static const int kInstTessOutUnused = kInstCommonOutCnt + 1;
+
+// Geometry Shader Output Record Offsets
+static const int kInstGeomOutPrimitiveId = kInstCommonOutCnt;
+static const int kInstGeomOutInvocationId = kInstCommonOutCnt + 1;
+
+// Size of Common and Stage-specific Members
+static const int kInstStageOutCnt = kInstCommonOutCnt + 2;
+
+// Validation Error Code
+//
+// This identifies the validation error. It also helps to identify
+// how many words follow in the record and their meaning.
+static const int kInstValidationOutError = kInstStageOutCnt;
+
+// Validation-specific Output Record Offsets
+//
+// Each different validation will generate a potentially different
+// number of words at the end of the record giving more specifics
+// about the validation error.
+//
+// A bindless bounds error will output the index and the bound.
+static const int kInstBindlessOutDescIndex = kInstStageOutCnt + 1;
+static const int kInstBindlessOutDescBound = kInstStageOutCnt + 2;
+static const int kInstBindlessOutCnt = kInstStageOutCnt + 3;
+
+// Maximum Output Record Member Count
+static const int kInstMaxOutCnt = kInstStageOutCnt + 3;
+
+// Validation Error Codes
+//
+// These are the possible validation error codes.
+static const int kInstErrorBindlessBounds = 0;
+
+// Debug Buffer Bindings
+//
+// These are the bindings for the different buffers which are
+// read or written by the instrumentation passes.
+//
+// This is the output buffer written by InstBindlessCheckPass.
+static const int kDebugOutputBindingStream = 0;
+
+}  // namespace spvtools
+
+#endif  // INCLUDE_SPIRV_TOOLS_INSTRUMENT_HPP_
diff --git a/include/spirv-tools/optimizer.hpp b/include/spirv-tools/optimizer.hpp
index 5a3c52f..6f08efd 100644
--- a/include/spirv-tools/optimizer.hpp
+++ b/include/spirv-tools/optimizer.hpp
@@ -657,6 +657,32 @@
 // them into a single instruction where possible.
 Optimizer::PassToken CreateCombineAccessChainsPass();
 
+// Create a pass to instrument bindless descriptor checking
+// This pass instruments all bindless references to check that descriptor
+// array indices are inbounds. If the reference is invalid, a record is
+// written to the debug output buffer (if space allows) and a null value is
+// returned. This pass is designed to support bindless validation in the Vulkan
+// validation layers.
+//
+// Dead code elimination should be run after this pass as the original,
+// potentially invalid code is not removed and could cause undefined behavior,
+// including crashes. It may also be beneficial to run Simplification
+// (ie Constant Propagation), DeadBranchElim and BlockMerge after this pass to
+// optimize instrument code involving the testing of compile-time constants.
+// It is also generally recommended that this pass (and all
+// instrumentation passes) be run after any legalization and optimization
+// passes. This will give better analysis for the instrumentation and avoid
+// potentially de-optimizing the instrument code, for example, inlining
+// the debug record output function throughout the module.
+//
+// The instrumentation will read and write buffers in debug
+// descriptor set |desc_set|. It will write |shader_id| in each output record
+// to identify the shader module which generated the record.
+//
+// TODO(greg-lunarg): Add support for vk_ext_descriptor_indexing.
+Optimizer::PassToken CreateInstBindlessCheckPass(uint32_t desc_set,
+                                                 uint32_t shader_id);
+
 }  // namespace spvtools
 
 #endif  // INCLUDE_SPIRV_TOOLS_OPTIMIZER_HPP_
diff --git a/source/opt/CMakeLists.txt b/source/opt/CMakeLists.txt
index eac956c..3ab2606 100644
--- a/source/opt/CMakeLists.txt
+++ b/source/opt/CMakeLists.txt
@@ -46,8 +46,10 @@
   inline_exhaustive_pass.h
   inline_opaque_pass.h
   inline_pass.h
+  inst_bindless_check_pass.h
   instruction.h
   instruction_list.h
+  instrument_pass.h
   ir_builder.h
   ir_context.h
   ir_loader.h
@@ -134,8 +136,10 @@
   inline_exhaustive_pass.cpp
   inline_opaque_pass.cpp
   inline_pass.cpp
+  inst_bindless_check_pass.cpp
   instruction.cpp
   instruction_list.cpp
+  instrument_pass.cpp
   ir_context.cpp
   ir_loader.cpp
   licm_pass.cpp
diff --git a/source/opt/basic_block.h b/source/opt/basic_block.h
index 0971092..ff3a412 100644
--- a/source/opt/basic_block.h
+++ b/source/opt/basic_block.h
@@ -71,6 +71,9 @@
   // Appends all of block's instructions (except label) to this block
   inline void AddInstructions(BasicBlock* bp);
 
+  // The pointer to the label starting this basic block.
+  std::unique_ptr<Instruction>& GetLabel() { return label_; }
+
   // The label starting this basic block.
   Instruction* GetLabelInst() { return label_.get(); }
   const Instruction* GetLabelInst() const { return label_.get(); }
diff --git a/source/opt/decoration_manager.cpp b/source/opt/decoration_manager.cpp
index 185dcb7..9990661 100644
--- a/source/opt/decoration_manager.cpp
+++ b/source/opt/decoration_manager.cpp
@@ -261,6 +261,7 @@
     AddDecoration(&inst);
   }
 }
+
 void DecorationManager::AddDecoration(Instruction* inst) {
   switch (inst->opcode()) {
     case SpvOpDecorate:
@@ -289,6 +290,43 @@
   }
 }
 
+void DecorationManager::AddDecoration(SpvOp opcode,
+                                      std::vector<Operand> opnds) {
+  IRContext* ctx = module_->context();
+  std::unique_ptr<Instruction> newDecoOp(
+      new Instruction(ctx, opcode, 0, 0, opnds));
+  ctx->AddAnnotationInst(std::move(newDecoOp));
+}
+
+void DecorationManager::AddDecoration(uint32_t inst_id, uint32_t decoration) {
+  AddDecoration(
+      SpvOpDecorate,
+      {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {inst_id}},
+       {spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER, {decoration}}});
+}
+
+void DecorationManager::AddDecorationVal(uint32_t inst_id, uint32_t decoration,
+                                         uint32_t decoration_value) {
+  AddDecoration(
+      SpvOpDecorate,
+      {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {inst_id}},
+       {spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER, {decoration}},
+       {spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
+        {decoration_value}}});
+}
+
+void DecorationManager::AddMemberDecoration(uint32_t inst_id, uint32_t member,
+                                            uint32_t decoration,
+                                            uint32_t decoration_value) {
+  AddDecoration(
+      SpvOpMemberDecorate,
+      {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {inst_id}},
+       {spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER, {member}},
+       {spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER, {decoration}},
+       {spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
+        {decoration_value}}});
+}
+
 template <typename T>
 std::vector<T> DecorationManager::InternalGetDecorationsFor(
     uint32_t id, bool include_linkage) {
diff --git a/source/opt/decoration_manager.h b/source/opt/decoration_manager.h
index fb9cfb6..2d87f5f 100644
--- a/source/opt/decoration_manager.h
+++ b/source/opt/decoration_manager.h
@@ -111,6 +111,20 @@
   // Informs the decoration manager of a new decoration that it needs to track.
   void AddDecoration(Instruction* inst);
 
+  // Add decoration with |opcode| and operands |opnds|.
+  void AddDecoration(SpvOp opcode, const std::vector<Operand> opnds);
+
+  // Add |decoration| of |inst_id| to module.
+  void AddDecoration(uint32_t inst_id, uint32_t decoration);
+
+  // Add |decoration, decoration_value| of |inst_id| to module.
+  void AddDecorationVal(uint32_t inst_id, uint32_t decoration,
+                        uint32_t decoration_value);
+
+  // Add |decoration, decoration_value| of |inst_id, member| to module.
+  void AddMemberDecoration(uint32_t member, uint32_t inst_id,
+                           uint32_t decoration, uint32_t decoration_value);
+
  private:
   // Analyzes the defs and uses in the given |module| and populates data
   // structures in this class. Does nothing if |module| is nullptr.
diff --git a/source/opt/def_use_manager.cpp b/source/opt/def_use_manager.cpp
index 54f284c..0ec98ca 100644
--- a/source/opt/def_use_manager.cpp
+++ b/source/opt/def_use_manager.cpp
@@ -279,6 +279,16 @@
   }
 
   if (lhs.inst_to_used_ids_ != rhs.inst_to_used_ids_) {
+    for (auto p : lhs.inst_to_used_ids_) {
+      if (rhs.inst_to_used_ids_.count(p.first) == 0) {
+        return false;
+      }
+    }
+    for (auto p : rhs.inst_to_used_ids_) {
+      if (lhs.inst_to_used_ids_.count(p.first) == 0) {
+        return false;
+      }
+    }
     return false;
   }
   return true;
diff --git a/source/opt/inst_bindless_check_pass.cpp b/source/opt/inst_bindless_check_pass.cpp
new file mode 100644
index 0000000..1901f76
--- /dev/null
+++ b/source/opt/inst_bindless_check_pass.cpp
@@ -0,0 +1,263 @@
+// Copyright (c) 2018 The Khronos Group Inc.
+// Copyright (c) 2018 Valve Corporation
+// Copyright (c) 2018 LunarG 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 "inst_bindless_check_pass.h"
+
+namespace {
+
+// Input Operand Indices
+static const int kSpvImageSampleImageIdInIdx = 0;
+static const int kSpvSampledImageImageIdInIdx = 0;
+static const int kSpvSampledImageSamplerIdInIdx = 1;
+static const int kSpvImageSampledImageIdInIdx = 0;
+static const int kSpvLoadPtrIdInIdx = 0;
+static const int kSpvAccessChainBaseIdInIdx = 0;
+static const int kSpvAccessChainIndex0IdInIdx = 1;
+static const int kSpvTypePointerTypeIdInIdx = 1;
+static const int kSpvTypeArrayLengthIdInIdx = 1;
+static const int kSpvConstantValueInIdx = 0;
+
+}  // anonymous namespace
+
+namespace spvtools {
+namespace opt {
+
+void InstBindlessCheckPass::GenBindlessCheckCode(
+    BasicBlock::iterator ref_inst_itr,
+    UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t instruction_idx,
+    uint32_t stage_idx, std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
+  // Look for reference through bindless descriptor. If not, return.
+  std::unique_ptr<BasicBlock> new_blk_ptr;
+  uint32_t image_id;
+  switch (ref_inst_itr->opcode()) {
+    case SpvOp::SpvOpImageSampleImplicitLod:
+    case SpvOp::SpvOpImageSampleExplicitLod:
+    case SpvOp::SpvOpImageSampleDrefImplicitLod:
+    case SpvOp::SpvOpImageSampleDrefExplicitLod:
+    case SpvOp::SpvOpImageSampleProjImplicitLod:
+    case SpvOp::SpvOpImageSampleProjExplicitLod:
+    case SpvOp::SpvOpImageSampleProjDrefImplicitLod:
+    case SpvOp::SpvOpImageSampleProjDrefExplicitLod:
+    case SpvOp::SpvOpImageGather:
+    case SpvOp::SpvOpImageDrefGather:
+    case SpvOp::SpvOpImageQueryLod:
+    case SpvOp::SpvOpImageSparseSampleImplicitLod:
+    case SpvOp::SpvOpImageSparseSampleExplicitLod:
+    case SpvOp::SpvOpImageSparseSampleDrefImplicitLod:
+    case SpvOp::SpvOpImageSparseSampleDrefExplicitLod:
+    case SpvOp::SpvOpImageSparseSampleProjImplicitLod:
+    case SpvOp::SpvOpImageSparseSampleProjExplicitLod:
+    case SpvOp::SpvOpImageSparseSampleProjDrefImplicitLod:
+    case SpvOp::SpvOpImageSparseSampleProjDrefExplicitLod:
+    case SpvOp::SpvOpImageSparseGather:
+    case SpvOp::SpvOpImageSparseDrefGather:
+    case SpvOp::SpvOpImageFetch:
+    case SpvOp::SpvOpImageRead:
+    case SpvOp::SpvOpImageQueryFormat:
+    case SpvOp::SpvOpImageQueryOrder:
+    case SpvOp::SpvOpImageQuerySizeLod:
+    case SpvOp::SpvOpImageQuerySize:
+    case SpvOp::SpvOpImageQueryLevels:
+    case SpvOp::SpvOpImageQuerySamples:
+    case SpvOp::SpvOpImageSparseFetch:
+    case SpvOp::SpvOpImageSparseRead:
+    case SpvOp::SpvOpImageWrite:
+      image_id =
+          ref_inst_itr->GetSingleWordInOperand(kSpvImageSampleImageIdInIdx);
+      break;
+    default:
+      return;
+  }
+  Instruction* image_inst = get_def_use_mgr()->GetDef(image_id);
+  uint32_t load_id;
+  Instruction* load_inst;
+  if (image_inst->opcode() == SpvOp::SpvOpSampledImage) {
+    load_id = image_inst->GetSingleWordInOperand(kSpvSampledImageImageIdInIdx);
+    load_inst = get_def_use_mgr()->GetDef(load_id);
+  } else if (image_inst->opcode() == SpvOp::SpvOpImage) {
+    load_id = image_inst->GetSingleWordInOperand(kSpvImageSampledImageIdInIdx);
+    load_inst = get_def_use_mgr()->GetDef(load_id);
+  } else {
+    load_id = image_id;
+    load_inst = image_inst;
+    image_id = 0;
+  }
+  if (load_inst->opcode() != SpvOp::SpvOpLoad) {
+    // TODO(greg-lunarg): Handle additional possibilities
+    return;
+  }
+  uint32_t ptr_id = load_inst->GetSingleWordInOperand(kSpvLoadPtrIdInIdx);
+  Instruction* ptr_inst = get_def_use_mgr()->GetDef(ptr_id);
+  if (ptr_inst->opcode() != SpvOp::SpvOpAccessChain) return;
+  if (ptr_inst->NumInOperands() != 2) {
+    assert(false && "unexpected bindless index number");
+    return;
+  }
+  uint32_t index_id =
+      ptr_inst->GetSingleWordInOperand(kSpvAccessChainIndex0IdInIdx);
+  ptr_id = ptr_inst->GetSingleWordInOperand(kSpvAccessChainBaseIdInIdx);
+  ptr_inst = get_def_use_mgr()->GetDef(ptr_id);
+  if (ptr_inst->opcode() != SpvOpVariable) {
+    assert(false && "unexpected bindless base");
+    return;
+  }
+  uint32_t var_type_id = ptr_inst->type_id();
+  Instruction* var_type_inst = get_def_use_mgr()->GetDef(var_type_id);
+  uint32_t ptr_type_id =
+      var_type_inst->GetSingleWordInOperand(kSpvTypePointerTypeIdInIdx);
+  Instruction* ptr_type_inst = get_def_use_mgr()->GetDef(ptr_type_id);
+  // TODO(greg-lunarg): Handle RuntimeArray. Will need to pull length
+  // out of debug input buffer.
+  if (ptr_type_inst->opcode() != SpvOpTypeArray) return;
+  // If index and bound both compile-time constants and index < bound,
+  // return without changing
+  uint32_t length_id =
+      ptr_type_inst->GetSingleWordInOperand(kSpvTypeArrayLengthIdInIdx);
+  Instruction* index_inst = get_def_use_mgr()->GetDef(index_id);
+  Instruction* length_inst = get_def_use_mgr()->GetDef(length_id);
+  if (index_inst->opcode() == SpvOpConstant &&
+      length_inst->opcode() == SpvOpConstant &&
+      index_inst->GetSingleWordInOperand(kSpvConstantValueInIdx) <
+          length_inst->GetSingleWordInOperand(kSpvConstantValueInIdx))
+    return;
+  // Generate full runtime bounds test code with true branch
+  // being full reference and false branch being debug output and zero
+  // for the referenced value.
+  MovePreludeCode(ref_inst_itr, ref_block_itr, &new_blk_ptr);
+  InstructionBuilder builder(
+      context(), &*new_blk_ptr,
+      IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
+  uint32_t error_id = builder.GetUintConstantId(kInstErrorBindlessBounds);
+  Instruction* ult_inst =
+      builder.AddBinaryOp(GetBoolId(), SpvOpULessThan, index_id, length_id);
+  uint32_t merge_blk_id = TakeNextId();
+  uint32_t valid_blk_id = TakeNextId();
+  uint32_t invalid_blk_id = TakeNextId();
+  std::unique_ptr<Instruction> merge_label(NewLabel(merge_blk_id));
+  std::unique_ptr<Instruction> valid_label(NewLabel(valid_blk_id));
+  std::unique_ptr<Instruction> invalid_label(NewLabel(invalid_blk_id));
+  (void)builder.AddConditionalBranch(ult_inst->result_id(), valid_blk_id,
+                                     invalid_blk_id, merge_blk_id,
+                                     SpvSelectionControlMaskNone);
+  // Close selection block and gen valid reference block
+  new_blocks->push_back(std::move(new_blk_ptr));
+  new_blk_ptr.reset(new BasicBlock(std::move(valid_label)));
+  builder.SetInsertPoint(&*new_blk_ptr);
+  // Clone descriptor load
+  Instruction* new_load_inst =
+      builder.AddLoad(load_inst->type_id(),
+                      load_inst->GetSingleWordInOperand(kSpvLoadPtrIdInIdx));
+  uint32_t new_load_id = new_load_inst->result_id();
+  get_decoration_mgr()->CloneDecorations(load_inst->result_id(), new_load_id);
+  uint32_t new_image_id = new_load_id;
+  // Clone Image/SampledImage with new load, if needed
+  if (image_id != 0) {
+    if (image_inst->opcode() == SpvOp::SpvOpSampledImage) {
+      Instruction* new_image_inst = builder.AddBinaryOp(
+          image_inst->type_id(), SpvOpSampledImage, new_load_id,
+          image_inst->GetSingleWordInOperand(kSpvSampledImageSamplerIdInIdx));
+      new_image_id = new_image_inst->result_id();
+    } else {
+      assert(image_inst->opcode() == SpvOp::SpvOpImage && "expecting OpImage");
+      Instruction* new_image_inst =
+          builder.AddUnaryOp(image_inst->type_id(), SpvOpImage, new_load_id);
+      new_image_id = new_image_inst->result_id();
+    }
+    get_decoration_mgr()->CloneDecorations(image_id, new_image_id);
+  }
+  // Clone original reference using new image code
+  std::unique_ptr<Instruction> new_ref_inst(ref_inst_itr->Clone(context()));
+  uint32_t ref_result_id = ref_inst_itr->result_id();
+  uint32_t new_ref_id = 0;
+  if (ref_result_id != 0) {
+    new_ref_id = TakeNextId();
+    new_ref_inst->SetResultId(new_ref_id);
+  }
+  new_ref_inst->SetInOperand(kSpvImageSampleImageIdInIdx, {new_image_id});
+  // Register new reference and add to new block
+  builder.AddInstruction(std::move(new_ref_inst));
+  if (new_ref_id != 0)
+    get_decoration_mgr()->CloneDecorations(ref_result_id, new_ref_id);
+  // Close valid block and gen invalid block
+  (void)builder.AddBranch(merge_blk_id);
+  new_blocks->push_back(std::move(new_blk_ptr));
+  new_blk_ptr.reset(new BasicBlock(std::move(invalid_label)));
+  builder.SetInsertPoint(&*new_blk_ptr);
+  uint32_t u_index_id = GenUintCastCode(index_id, &builder);
+  GenDebugStreamWrite(instruction_idx, stage_idx,
+                      {error_id, u_index_id, length_id}, &builder);
+  // Remember last invalid block id
+  uint32_t last_invalid_blk_id = new_blk_ptr->GetLabelInst()->result_id();
+  // Gen zero for invalid  reference
+  uint32_t ref_type_id = ref_inst_itr->type_id();
+  // Close invalid block and gen merge block
+  (void)builder.AddBranch(merge_blk_id);
+  new_blocks->push_back(std::move(new_blk_ptr));
+  new_blk_ptr.reset(new BasicBlock(std::move(merge_label)));
+  builder.SetInsertPoint(&*new_blk_ptr);
+  // Gen phi of new reference and zero, if necessary, and replace the
+  // result id of the original reference with that of the Phi. Kill original
+  // reference and move in remainder of original block.
+  if (new_ref_id != 0) {
+    Instruction* phi_inst = builder.AddPhi(
+        ref_type_id, {new_ref_id, valid_blk_id, builder.GetNullId(ref_type_id),
+                      last_invalid_blk_id});
+    context()->ReplaceAllUsesWith(ref_result_id, phi_inst->result_id());
+  }
+  context()->KillInst(&*ref_inst_itr);
+  MovePostludeCode(ref_block_itr, &new_blk_ptr);
+  // Add remainder/merge block to new blocks
+  new_blocks->push_back(std::move(new_blk_ptr));
+}
+
+void InstBindlessCheckPass::InitializeInstBindlessCheck() {
+  // Initialize base class
+  InitializeInstrument();
+  // Look for related extensions
+  ext_descriptor_indexing_defined_ = false;
+  for (auto& ei : get_module()->extensions()) {
+    const char* ext_name =
+        reinterpret_cast<const char*>(&ei.GetInOperand(0).words[0]);
+    if (strcmp(ext_name, "SPV_EXT_descriptor_indexing") == 0) {
+      ext_descriptor_indexing_defined_ = true;
+      break;
+    }
+  }
+}
+
+Pass::Status InstBindlessCheckPass::ProcessImpl() {
+  // Perform instrumentation on each entry point function in module
+  InstProcessFunction pfn =
+      [this](BasicBlock::iterator ref_inst_itr,
+             UptrVectorIterator<BasicBlock> ref_block_itr,
+             uint32_t instruction_idx, uint32_t stage_idx,
+             std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
+        return GenBindlessCheckCode(ref_inst_itr, ref_block_itr,
+                                    instruction_idx, stage_idx, new_blocks);
+      };
+  bool modified = InstProcessEntryPointCallTree(pfn);
+  // This pass does not update inst->blk info
+  context()->InvalidateAnalyses(IRContext::kAnalysisInstrToBlockMapping);
+  return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
+}
+
+Pass::Status InstBindlessCheckPass::Process() {
+  InitializeInstBindlessCheck();
+  return ProcessImpl();
+}
+
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/inst_bindless_check_pass.h b/source/opt/inst_bindless_check_pass.h
new file mode 100644
index 0000000..eaa1dd2
--- /dev/null
+++ b/source/opt/inst_bindless_check_pass.h
@@ -0,0 +1,93 @@
+// Copyright (c) 2018 The Khronos Group Inc.
+// Copyright (c) 2018 Valve Corporation
+// Copyright (c) 2018 LunarG Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef LIBSPIRV_OPT_INST_BINDLESS_CHECK_PASS_H_
+#define LIBSPIRV_OPT_INST_BINDLESS_CHECK_PASS_H_
+
+#include "instrument_pass.h"
+
+namespace spvtools {
+namespace opt {
+
+// This class/pass is designed to support the bindless (descriptor indexing)
+// GPU-assisted validation layer of
+// https://github.com/KhronosGroup/Vulkan-ValidationLayers. Its internal and
+// external design may change as the layer evolves.
+class InstBindlessCheckPass : public InstrumentPass {
+ public:
+  // For test harness only
+  InstBindlessCheckPass() : InstrumentPass(7, 23, kInstValidationIdBindless) {}
+  // For all other interfaces
+  InstBindlessCheckPass(uint32_t desc_set, uint32_t shader_id)
+      : InstrumentPass(desc_set, shader_id, kInstValidationIdBindless) {}
+
+  ~InstBindlessCheckPass() = default;
+
+  // See optimizer.hpp for pass user documentation.
+  Status Process() override;
+
+  const char* name() const override { return "inst-bindless-check-pass"; }
+
+ private:
+  // Initialize state for instrumenting bindless checking
+  void InitializeInstBindlessCheck();
+
+  // This function does bindless checking instrumentation on a single
+  // instruction. It is designed to be passed to
+  // InstrumentPass::InstProcessEntryPointCallTree(), which applies the
+  // function to each instruction in a module and replaces the instruction
+  // if warranted.
+  //
+  // If |ref_inst_itr| is a bindless reference, return in |new_blocks| the
+  // result of instrumenting it with validation code within its block at
+  // |ref_block_itr|. Specifically, generate code to check that the index
+  // into the descriptor array is in-bounds. If the check passes, execute
+  // the remainder of the reference, otherwise write a record to the debug
+  // output buffer stream including |function_idx, instruction_idx, stage_idx|
+  // and replace the reference with the null value of the original type. The
+  // block at |ref_block_itr| can just be replaced with the blocks in
+  // |new_blocks|, which will contain at least two blocks. The last block will
+  // comprise all instructions following |ref_inst_itr|,
+  // preceded by a phi instruction.
+  //
+  // This instrumentation pass utilizes GenDebugStreamWrite() to write its
+  // error records. The validation-specific part of the error record will
+  // have the format:
+  //
+  //    Validation Error Code (=kInstErrorBindlessBounds)
+  //    Descriptor Index
+  //    Descriptor Array Size
+  //
+  // The Descriptor Index is the index which has been determined to be
+  // out-of-bounds.
+  //
+  // The Descriptor Array Size is the size of the descriptor array which was
+  // indexed.
+  void GenBindlessCheckCode(
+      BasicBlock::iterator ref_inst_itr,
+      UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t instruction_idx,
+      uint32_t stage_idx, std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
+
+  Pass::Status ProcessImpl();
+
+  // True if VK_EXT_descriptor_indexing is defined
+  bool ext_descriptor_indexing_defined_;
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // LIBSPIRV_OPT_INST_BINDLESS_CHECK_PASS_H_
diff --git a/source/opt/instrument_pass.cpp b/source/opt/instrument_pass.cpp
new file mode 100644
index 0000000..3291bbb
--- /dev/null
+++ b/source/opt/instrument_pass.cpp
@@ -0,0 +1,710 @@
+// Copyright (c) 2018 The Khronos Group Inc.
+// Copyright (c) 2018 Valve Corporation
+// Copyright (c) 2018 LunarG 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 "instrument_pass.h"
+
+#include "source/cfa.h"
+
+namespace {
+
+// Common Parameter Positions
+static const int kInstCommonParamInstIdx = 0;
+static const int kInstCommonParamCnt = 1;
+
+// Indices of operands in SPIR-V instructions
+static const int kEntryPointExecutionModelInIdx = 0;
+static const int kEntryPointFunctionIdInIdx = 1;
+
+}  // anonymous namespace
+
+namespace spvtools {
+namespace opt {
+
+void InstrumentPass::MovePreludeCode(
+    BasicBlock::iterator ref_inst_itr,
+    UptrVectorIterator<BasicBlock> ref_block_itr,
+    std::unique_ptr<BasicBlock>* new_blk_ptr) {
+  same_block_pre_.clear();
+  same_block_post_.clear();
+  // Initialize new block. Reuse label from original block.
+  new_blk_ptr->reset(new BasicBlock(std::move(ref_block_itr->GetLabel())));
+  // Move contents of original ref block up to ref instruction.
+  for (auto cii = ref_block_itr->begin(); cii != ref_inst_itr;
+       cii = ref_block_itr->begin()) {
+    Instruction* inst = &*cii;
+    inst->RemoveFromList();
+    std::unique_ptr<Instruction> mv_ptr(inst);
+    // Remember same-block ops for possible regeneration.
+    if (IsSameBlockOp(&*mv_ptr)) {
+      auto* sb_inst_ptr = mv_ptr.get();
+      same_block_pre_[mv_ptr->result_id()] = sb_inst_ptr;
+    }
+    (*new_blk_ptr)->AddInstruction(std::move(mv_ptr));
+  }
+}
+
+void InstrumentPass::MovePostludeCode(
+    UptrVectorIterator<BasicBlock> ref_block_itr,
+    std::unique_ptr<BasicBlock>* new_blk_ptr) {
+  // new_blk_ptr->reset(new BasicBlock(NewLabel(ref_block_itr->id())));
+  // Move contents of original ref block.
+  for (auto cii = ref_block_itr->begin(); cii != ref_block_itr->end();
+       cii = ref_block_itr->begin()) {
+    Instruction* inst = &*cii;
+    inst->RemoveFromList();
+    std::unique_ptr<Instruction> mv_inst(inst);
+    // Regenerate any same-block instruction that has not been seen in the
+    // current block.
+    if (same_block_pre_.size() > 0) {
+      CloneSameBlockOps(&mv_inst, &same_block_post_, &same_block_pre_,
+                        new_blk_ptr);
+      // Remember same-block ops in this block.
+      if (IsSameBlockOp(&*mv_inst)) {
+        const uint32_t rid = mv_inst->result_id();
+        same_block_post_[rid] = rid;
+      }
+    }
+    (*new_blk_ptr)->AddInstruction(std::move(mv_inst));
+  }
+}
+
+std::unique_ptr<Instruction> InstrumentPass::NewLabel(uint32_t label_id) {
+  std::unique_ptr<Instruction> newLabel(
+      new Instruction(context(), SpvOpLabel, 0, label_id, {}));
+  get_def_use_mgr()->AnalyzeInstDefUse(&*newLabel);
+  return newLabel;
+}
+
+uint32_t InstrumentPass::GenUintCastCode(uint32_t val_id,
+                                         InstructionBuilder* builder) {
+  // Cast value to 32-bit unsigned if necessary
+  if (get_def_use_mgr()->GetDef(val_id)->type_id() == GetUintId())
+    return val_id;
+  return builder->AddUnaryOp(GetUintId(), SpvOpBitcast, val_id)->result_id();
+}
+
+void InstrumentPass::GenDebugOutputFieldCode(uint32_t base_offset_id,
+                                             uint32_t field_offset,
+                                             uint32_t field_value_id,
+                                             InstructionBuilder* builder) {
+  // Cast value to 32-bit unsigned if necessary
+  uint32_t val_id = GenUintCastCode(field_value_id, builder);
+  // Store value
+  Instruction* data_idx_inst =
+      builder->AddBinaryOp(GetUintId(), SpvOpIAdd, base_offset_id,
+                           builder->GetUintConstantId(field_offset));
+  uint32_t buf_id = GetOutputBufferId();
+  uint32_t buf_uint_ptr_id = GetOutputBufferUintPtrId();
+  Instruction* achain_inst =
+      builder->AddTernaryOp(buf_uint_ptr_id, SpvOpAccessChain, buf_id,
+                            builder->GetUintConstantId(kDebugOutputDataOffset),
+                            data_idx_inst->result_id());
+  (void)builder->AddBinaryOp(0, SpvOpStore, achain_inst->result_id(), val_id);
+}
+
+void InstrumentPass::GenCommonStreamWriteCode(uint32_t record_sz,
+                                              uint32_t inst_id,
+                                              uint32_t stage_idx,
+                                              uint32_t base_offset_id,
+                                              InstructionBuilder* builder) {
+  // Store record size
+  GenDebugOutputFieldCode(base_offset_id, kInstCommonOutSize,
+                          builder->GetUintConstantId(record_sz), builder);
+  // Store Shader Id
+  GenDebugOutputFieldCode(base_offset_id, kInstCommonOutShaderId,
+                          builder->GetUintConstantId(shader_id_), builder);
+  // Store Instruction Idx
+  GenDebugOutputFieldCode(base_offset_id, kInstCommonOutInstructionIdx, inst_id,
+                          builder);
+  // Store Stage Idx
+  GenDebugOutputFieldCode(base_offset_id, kInstCommonOutStageIdx,
+                          builder->GetUintConstantId(stage_idx), builder);
+}
+
+void InstrumentPass::GenFragCoordEltDebugOutputCode(
+    uint32_t base_offset_id, uint32_t uint_frag_coord_id, uint32_t element,
+    InstructionBuilder* builder) {
+  Instruction* element_val_inst = builder->AddIdLiteralOp(
+      GetUintId(), SpvOpCompositeExtract, uint_frag_coord_id, element);
+  GenDebugOutputFieldCode(base_offset_id, kInstFragOutFragCoordX + element,
+                          element_val_inst->result_id(), builder);
+}
+
+void InstrumentPass::GenBuiltinOutputCode(uint32_t builtin_id,
+                                          uint32_t builtin_off,
+                                          uint32_t base_offset_id,
+                                          InstructionBuilder* builder) {
+  // Load and store builtin
+  Instruction* load_inst =
+      builder->AddUnaryOp(GetUintId(), SpvOpLoad, builtin_id);
+  GenDebugOutputFieldCode(base_offset_id, builtin_off, load_inst->result_id(),
+                          builder);
+}
+
+void InstrumentPass::GenUintNullOutputCode(uint32_t field_off,
+                                           uint32_t base_offset_id,
+                                           InstructionBuilder* builder) {
+  GenDebugOutputFieldCode(base_offset_id, field_off,
+                          builder->GetNullId(GetUintId()), builder);
+}
+
+void InstrumentPass::GenStageStreamWriteCode(uint32_t stage_idx,
+                                             uint32_t base_offset_id,
+                                             InstructionBuilder* builder) {
+  // TODO(greg-lunarg): Add support for all stages
+  switch (stage_idx) {
+    case SpvExecutionModelVertex: {
+      // Load and store VertexId and InstanceId
+      GenBuiltinOutputCode(context()->GetBuiltinVarId(SpvBuiltInVertexId),
+                           kInstVertOutVertexId, base_offset_id, builder);
+      GenBuiltinOutputCode(context()->GetBuiltinVarId(SpvBuiltInInstanceId),
+                           kInstVertOutInstanceId, base_offset_id, builder);
+    } break;
+    case SpvExecutionModelGLCompute: {
+      // Load and store GlobalInvocationId. Second word is unused; store zero.
+      GenBuiltinOutputCode(
+          context()->GetBuiltinVarId(SpvBuiltInGlobalInvocationId),
+          kInstCompOutGlobalInvocationId, base_offset_id, builder);
+      GenUintNullOutputCode(kInstCompOutUnused, base_offset_id, builder);
+    } break;
+    case SpvExecutionModelGeometry: {
+      // Load and store PrimitiveId and InvocationId.
+      GenBuiltinOutputCode(context()->GetBuiltinVarId(SpvBuiltInPrimitiveId),
+                           kInstGeomOutPrimitiveId, base_offset_id, builder);
+      GenBuiltinOutputCode(context()->GetBuiltinVarId(SpvBuiltInInvocationId),
+                           kInstGeomOutInvocationId, base_offset_id, builder);
+    } break;
+    case SpvExecutionModelTessellationControl:
+    case SpvExecutionModelTessellationEvaluation: {
+      // Load and store InvocationId. Second word is unused; store zero.
+      GenBuiltinOutputCode(context()->GetBuiltinVarId(SpvBuiltInInvocationId),
+                           kInstTessOutInvocationId, base_offset_id, builder);
+      GenUintNullOutputCode(kInstTessOutUnused, base_offset_id, builder);
+    } break;
+    case SpvExecutionModelFragment: {
+      // Load FragCoord and convert to Uint
+      Instruction* frag_coord_inst =
+          builder->AddUnaryOp(GetVec4FloatId(), SpvOpLoad,
+                              context()->GetBuiltinVarId(SpvBuiltInFragCoord));
+      Instruction* uint_frag_coord_inst = builder->AddUnaryOp(
+          GetVec4UintId(), SpvOpBitcast, frag_coord_inst->result_id());
+      for (uint32_t u = 0; u < 2u; ++u)
+        GenFragCoordEltDebugOutputCode(
+            base_offset_id, uint_frag_coord_inst->result_id(), u, builder);
+    } break;
+    default: { assert(false && "unsupported stage"); } break;
+  }
+}
+
+void InstrumentPass::GenDebugStreamWrite(
+    uint32_t instruction_idx, uint32_t stage_idx,
+    const std::vector<uint32_t>& validation_ids, InstructionBuilder* builder) {
+  // Call debug output function. Pass func_idx, instruction_idx and
+  // validation ids as args.
+  uint32_t val_id_cnt = static_cast<uint32_t>(validation_ids.size());
+  uint32_t output_func_id = GetStreamWriteFunctionId(stage_idx, val_id_cnt);
+  std::vector<uint32_t> args = {output_func_id,
+                                builder->GetUintConstantId(instruction_idx)};
+  (void)args.insert(args.end(), validation_ids.begin(), validation_ids.end());
+  (void)builder->AddNaryOp(GetVoidId(), SpvOpFunctionCall, args);
+}
+
+bool InstrumentPass::IsSameBlockOp(const Instruction* inst) const {
+  return inst->opcode() == SpvOpSampledImage || inst->opcode() == SpvOpImage;
+}
+
+void InstrumentPass::CloneSameBlockOps(
+    std::unique_ptr<Instruction>* inst,
+    std::unordered_map<uint32_t, uint32_t>* same_blk_post,
+    std::unordered_map<uint32_t, Instruction*>* same_blk_pre,
+    std::unique_ptr<BasicBlock>* block_ptr) {
+  (*inst)->ForEachInId(
+      [&same_blk_post, &same_blk_pre, &block_ptr, this](uint32_t* iid) {
+        const auto map_itr = (*same_blk_post).find(*iid);
+        if (map_itr == (*same_blk_post).end()) {
+          const auto map_itr2 = (*same_blk_pre).find(*iid);
+          if (map_itr2 != (*same_blk_pre).end()) {
+            // Clone pre-call same-block ops, map result id.
+            const Instruction* in_inst = map_itr2->second;
+            std::unique_ptr<Instruction> sb_inst(in_inst->Clone(context()));
+            CloneSameBlockOps(&sb_inst, same_blk_post, same_blk_pre, block_ptr);
+            const uint32_t rid = sb_inst->result_id();
+            const uint32_t nid = this->TakeNextId();
+            get_decoration_mgr()->CloneDecorations(rid, nid);
+            sb_inst->SetResultId(nid);
+            (*same_blk_post)[rid] = nid;
+            *iid = nid;
+            (*block_ptr)->AddInstruction(std::move(sb_inst));
+          }
+        } else {
+          // Reset same-block op operand.
+          *iid = map_itr->second;
+        }
+      });
+}
+
+void InstrumentPass::UpdateSucceedingPhis(
+    std::vector<std::unique_ptr<BasicBlock>>& new_blocks) {
+  const auto first_blk = new_blocks.begin();
+  const auto last_blk = new_blocks.end() - 1;
+  const uint32_t first_id = (*first_blk)->id();
+  const uint32_t last_id = (*last_blk)->id();
+  const BasicBlock& const_last_block = *last_blk->get();
+  const_last_block.ForEachSuccessorLabel(
+      [&first_id, &last_id, this](const uint32_t succ) {
+        BasicBlock* sbp = this->id2block_[succ];
+        sbp->ForEachPhiInst([&first_id, &last_id, this](Instruction* phi) {
+          bool changed = false;
+          phi->ForEachInId([&first_id, &last_id, &changed](uint32_t* id) {
+            if (*id == first_id) {
+              *id = last_id;
+              changed = true;
+            }
+          });
+          if (changed) get_def_use_mgr()->AnalyzeInstUse(phi);
+        });
+      });
+}
+
+// Return id for output buffer uint ptr type
+uint32_t InstrumentPass::GetOutputBufferUintPtrId() {
+  if (output_buffer_uint_ptr_id_ == 0) {
+    output_buffer_uint_ptr_id_ = context()->get_type_mgr()->FindPointerToType(
+        GetUintId(), SpvStorageClassStorageBuffer);
+  }
+  return output_buffer_uint_ptr_id_;
+}
+
+uint32_t InstrumentPass::GetOutputBufferBinding() {
+  switch (validation_id_) {
+    case kInstValidationIdBindless:
+      return kDebugOutputBindingStream;
+    default:
+      assert(false && "unexpected validation id");
+  }
+  return 0;
+}
+
+// Return id for output buffer
+uint32_t InstrumentPass::GetOutputBufferId() {
+  if (output_buffer_id_ == 0) {
+    // If not created yet, create one
+    analysis::DecorationManager* deco_mgr = get_decoration_mgr();
+    analysis::TypeManager* type_mgr = context()->get_type_mgr();
+    analysis::Integer uint_ty(32, false);
+    analysis::Type* reg_uint_ty = type_mgr->GetRegisteredType(&uint_ty);
+    analysis::RuntimeArray uint_rarr_ty(reg_uint_ty);
+    analysis::Type* reg_uint_rarr_ty =
+        type_mgr->GetRegisteredType(&uint_rarr_ty);
+    analysis::Struct obuf_ty({reg_uint_ty, reg_uint_rarr_ty});
+    analysis::Type* reg_obuf_ty = type_mgr->GetRegisteredType(&obuf_ty);
+    uint32_t obufTyId = type_mgr->GetTypeInstruction(reg_obuf_ty);
+    deco_mgr->AddDecoration(obufTyId, SpvDecorationBlock);
+    deco_mgr->AddMemberDecoration(obufTyId, kDebugOutputSizeOffset,
+                                  SpvDecorationOffset, 0);
+    deco_mgr->AddMemberDecoration(obufTyId, kDebugOutputDataOffset,
+                                  SpvDecorationOffset, 4);
+    uint32_t obufTyPtrId_ =
+        type_mgr->FindPointerToType(obufTyId, SpvStorageClassStorageBuffer);
+    output_buffer_id_ = TakeNextId();
+    std::unique_ptr<Instruction> newVarOp(new Instruction(
+        context(), SpvOpVariable, obufTyPtrId_, output_buffer_id_,
+        {{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
+          {SpvStorageClassStorageBuffer}}}));
+    context()->AddGlobalValue(std::move(newVarOp));
+    deco_mgr->AddDecorationVal(output_buffer_id_, SpvDecorationDescriptorSet,
+                               desc_set_);
+    deco_mgr->AddDecorationVal(output_buffer_id_, SpvDecorationBinding,
+                               GetOutputBufferBinding());
+    // Look for storage buffer extension. If none, create one.
+    if (!get_feature_mgr()->HasExtension(
+            kSPV_KHR_storage_buffer_storage_class)) {
+      const std::string ext_name("SPV_KHR_storage_buffer_storage_class");
+      const auto num_chars = ext_name.size();
+      // Compute num words, accommodate the terminating null character.
+      const auto num_words = (num_chars + 1 + 3) / 4;
+      std::vector<uint32_t> ext_words(num_words, 0u);
+      std::memcpy(ext_words.data(), ext_name.data(), num_chars);
+      context()->AddExtension(std::unique_ptr<Instruction>(
+          new Instruction(context(), SpvOpExtension, 0u, 0u,
+                          {{SPV_OPERAND_TYPE_LITERAL_STRING, ext_words}})));
+    }
+  }
+  return output_buffer_id_;
+}
+
+uint32_t InstrumentPass::GetVec4FloatId() {
+  if (v4float_id_ == 0) {
+    analysis::TypeManager* type_mgr = context()->get_type_mgr();
+    analysis::Float float_ty(32);
+    analysis::Type* reg_float_ty = type_mgr->GetRegisteredType(&float_ty);
+    analysis::Vector v4float_ty(reg_float_ty, 4);
+    analysis::Type* reg_v4float_ty = type_mgr->GetRegisteredType(&v4float_ty);
+    v4float_id_ = type_mgr->GetTypeInstruction(reg_v4float_ty);
+  }
+  return v4float_id_;
+}
+
+uint32_t InstrumentPass::GetUintId() {
+  if (uint_id_ == 0) {
+    analysis::TypeManager* type_mgr = context()->get_type_mgr();
+    analysis::Integer uint_ty(32, false);
+    analysis::Type* reg_uint_ty = type_mgr->GetRegisteredType(&uint_ty);
+    uint_id_ = type_mgr->GetTypeInstruction(reg_uint_ty);
+  }
+  return uint_id_;
+}
+
+uint32_t InstrumentPass::GetVec4UintId() {
+  if (v4uint_id_ == 0) {
+    analysis::TypeManager* type_mgr = context()->get_type_mgr();
+    analysis::Integer uint_ty(32, false);
+    analysis::Type* reg_uint_ty = type_mgr->GetRegisteredType(&uint_ty);
+    analysis::Vector v4uint_ty(reg_uint_ty, 4);
+    analysis::Type* reg_v4uint_ty = type_mgr->GetRegisteredType(&v4uint_ty);
+    v4uint_id_ = type_mgr->GetTypeInstruction(reg_v4uint_ty);
+  }
+  return v4uint_id_;
+}
+
+uint32_t InstrumentPass::GetBoolId() {
+  if (bool_id_ == 0) {
+    analysis::TypeManager* type_mgr = context()->get_type_mgr();
+    analysis::Bool bool_ty;
+    analysis::Type* reg_bool_ty = type_mgr->GetRegisteredType(&bool_ty);
+    bool_id_ = type_mgr->GetTypeInstruction(reg_bool_ty);
+  }
+  return bool_id_;
+}
+
+uint32_t InstrumentPass::GetVoidId() {
+  if (void_id_ == 0) {
+    analysis::TypeManager* type_mgr = context()->get_type_mgr();
+    analysis::Void void_ty;
+    analysis::Type* reg_void_ty = type_mgr->GetRegisteredType(&void_ty);
+    void_id_ = type_mgr->GetTypeInstruction(reg_void_ty);
+  }
+  return void_id_;
+}
+
+uint32_t InstrumentPass::GetStreamWriteFunctionId(uint32_t stage_idx,
+                                                  uint32_t val_spec_param_cnt) {
+  // Total param count is common params plus validation-specific
+  // params
+  uint32_t param_cnt = kInstCommonParamCnt + val_spec_param_cnt;
+  if (output_func_id_ == 0) {
+    // Create function
+    output_func_id_ = TakeNextId();
+    analysis::TypeManager* type_mgr = context()->get_type_mgr();
+    std::vector<const analysis::Type*> param_types;
+    for (uint32_t c = 0; c < param_cnt; ++c)
+      param_types.push_back(type_mgr->GetType(GetUintId()));
+    analysis::Function func_ty(type_mgr->GetType(GetVoidId()), param_types);
+    analysis::Type* reg_func_ty = type_mgr->GetRegisteredType(&func_ty);
+    std::unique_ptr<Instruction> func_inst(new Instruction(
+        get_module()->context(), SpvOpFunction, GetVoidId(), output_func_id_,
+        {{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
+          {SpvFunctionControlMaskNone}},
+         {spv_operand_type_t::SPV_OPERAND_TYPE_ID,
+          {type_mgr->GetTypeInstruction(reg_func_ty)}}}));
+    get_def_use_mgr()->AnalyzeInstDefUse(&*func_inst);
+    std::unique_ptr<Function> output_func =
+        MakeUnique<Function>(std::move(func_inst));
+    // Add parameters
+    std::vector<uint32_t> param_vec;
+    for (uint32_t c = 0; c < param_cnt; ++c) {
+      uint32_t pid = TakeNextId();
+      param_vec.push_back(pid);
+      std::unique_ptr<Instruction> param_inst(
+          new Instruction(get_module()->context(), SpvOpFunctionParameter,
+                          GetUintId(), pid, {}));
+      get_def_use_mgr()->AnalyzeInstDefUse(&*param_inst);
+      output_func->AddParameter(std::move(param_inst));
+    }
+    // Create first block
+    uint32_t test_blk_id = TakeNextId();
+    std::unique_ptr<Instruction> test_label(NewLabel(test_blk_id));
+    std::unique_ptr<BasicBlock> new_blk_ptr =
+        MakeUnique<BasicBlock>(std::move(test_label));
+    InstructionBuilder builder(
+        context(), &*new_blk_ptr,
+        IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
+    // Gen test if debug output buffer size will not be exceeded.
+    uint32_t obuf_record_sz = kInstStageOutCnt + val_spec_param_cnt;
+    uint32_t buf_id = GetOutputBufferId();
+    uint32_t buf_uint_ptr_id = GetOutputBufferUintPtrId();
+    Instruction* obuf_curr_sz_ac_inst =
+        builder.AddBinaryOp(buf_uint_ptr_id, SpvOpAccessChain, buf_id,
+                            builder.GetUintConstantId(kDebugOutputSizeOffset));
+    // Fetch the current debug buffer written size atomically, adding the
+    // size of the record to be written.
+    uint32_t obuf_record_sz_id = builder.GetUintConstantId(obuf_record_sz);
+    uint32_t mask_none_id = builder.GetUintConstantId(SpvMemoryAccessMaskNone);
+    uint32_t scope_invok_id = builder.GetUintConstantId(SpvScopeInvocation);
+    Instruction* obuf_curr_sz_inst = builder.AddQuadOp(
+        GetUintId(), SpvOpAtomicIAdd, obuf_curr_sz_ac_inst->result_id(),
+        scope_invok_id, mask_none_id, obuf_record_sz_id);
+    uint32_t obuf_curr_sz_id = obuf_curr_sz_inst->result_id();
+    // Compute new written size
+    Instruction* obuf_new_sz_inst =
+        builder.AddBinaryOp(GetUintId(), SpvOpIAdd, obuf_curr_sz_id,
+                            builder.GetUintConstantId(obuf_record_sz));
+    // Fetch the data bound
+    Instruction* obuf_bnd_inst =
+        builder.AddIdLiteralOp(GetUintId(), SpvOpArrayLength,
+                               GetOutputBufferId(), kDebugOutputDataOffset);
+    // Test that new written size is less than or equal to debug output
+    // data bound
+    Instruction* obuf_safe_inst = builder.AddBinaryOp(
+        GetBoolId(), SpvOpULessThanEqual, obuf_new_sz_inst->result_id(),
+        obuf_bnd_inst->result_id());
+    uint32_t merge_blk_id = TakeNextId();
+    uint32_t write_blk_id = TakeNextId();
+    std::unique_ptr<Instruction> merge_label(NewLabel(merge_blk_id));
+    std::unique_ptr<Instruction> write_label(NewLabel(write_blk_id));
+    (void)builder.AddConditionalBranch(obuf_safe_inst->result_id(),
+                                       write_blk_id, merge_blk_id, merge_blk_id,
+                                       SpvSelectionControlMaskNone);
+    // Close safety test block and gen write block
+    new_blk_ptr->SetParent(&*output_func);
+    output_func->AddBasicBlock(std::move(new_blk_ptr));
+    new_blk_ptr = MakeUnique<BasicBlock>(std::move(write_label));
+    builder.SetInsertPoint(&*new_blk_ptr);
+    // Generate common and stage-specific debug record members
+    GenCommonStreamWriteCode(obuf_record_sz, param_vec[kInstCommonParamInstIdx],
+                             stage_idx, obuf_curr_sz_id, &builder);
+    GenStageStreamWriteCode(stage_idx, obuf_curr_sz_id, &builder);
+    // Gen writes of validation specific data
+    for (uint32_t i = 0; i < val_spec_param_cnt; ++i) {
+      GenDebugOutputFieldCode(obuf_curr_sz_id, kInstStageOutCnt + i,
+                              param_vec[kInstCommonParamCnt + i], &builder);
+    }
+    // Close write block and gen merge block
+    (void)builder.AddBranch(merge_blk_id);
+    new_blk_ptr->SetParent(&*output_func);
+    output_func->AddBasicBlock(std::move(new_blk_ptr));
+    new_blk_ptr = MakeUnique<BasicBlock>(std::move(merge_label));
+    builder.SetInsertPoint(&*new_blk_ptr);
+    // Close merge block and function and add function to module
+    (void)builder.AddNullaryOp(0, SpvOpReturn);
+    new_blk_ptr->SetParent(&*output_func);
+    output_func->AddBasicBlock(std::move(new_blk_ptr));
+    std::unique_ptr<Instruction> func_end_inst(
+        new Instruction(get_module()->context(), SpvOpFunctionEnd, 0, 0, {}));
+    get_def_use_mgr()->AnalyzeInstDefUse(&*func_end_inst);
+    output_func->SetFunctionEnd(std::move(func_end_inst));
+    context()->AddFunction(std::move(output_func));
+    output_func_param_cnt_ = param_cnt;
+  }
+  assert(param_cnt == output_func_param_cnt_ && "bad arg count");
+  return output_func_id_;
+}
+
+bool InstrumentPass::InstrumentFunction(Function* func, uint32_t stage_idx,
+                                        InstProcessFunction& pfn) {
+  bool modified = false;
+  // Compute function index
+  uint32_t function_idx = 0;
+  for (auto fii = get_module()->begin(); fii != get_module()->end(); ++fii) {
+    if (&*fii == func) break;
+    ++function_idx;
+  }
+  std::vector<std::unique_ptr<BasicBlock>> new_blks;
+  // Start count after function instruction
+  uint32_t instruction_idx = funcIdx2offset_[function_idx] + 1;
+  // Using block iterators here because of block erasures and insertions.
+  for (auto bi = func->begin(); bi != func->end(); ++bi) {
+    // Count block's label
+    ++instruction_idx;
+    for (auto ii = bi->begin(); ii != bi->end(); ++instruction_idx) {
+      // Bump instruction count if debug instructions
+      instruction_idx += static_cast<uint32_t>(ii->dbg_line_insts().size());
+      // Generate instrumentation if warranted
+      pfn(ii, bi, instruction_idx, stage_idx, &new_blks);
+      if (new_blks.size() == 0) {
+        ++ii;
+        continue;
+      }
+      // If there are new blocks we know there will always be two or
+      // more, so update succeeding phis with label of new last block.
+      size_t newBlocksSize = new_blks.size();
+      assert(newBlocksSize > 1);
+      UpdateSucceedingPhis(new_blks);
+      // Replace original block with new block(s)
+      bi = bi.Erase();
+      for (auto& bb : new_blks) {
+        bb->SetParent(func);
+      }
+      bi = bi.InsertBefore(&new_blks);
+      // Reset block iterator to last new block
+      for (size_t i = 0; i < newBlocksSize - 1; i++) ++bi;
+      modified = true;
+      // Restart instrumenting at beginning of last new block,
+      // but skip over any new phi or copy instruction.
+      ii = bi->begin();
+      if (ii->opcode() == SpvOpPhi || ii->opcode() == SpvOpCopyObject) ++ii;
+      new_blks.clear();
+    }
+  }
+  return modified;
+}
+
+bool InstrumentPass::InstProcessCallTreeFromRoots(InstProcessFunction& pfn,
+                                                  std::queue<uint32_t>* roots,
+                                                  uint32_t stage_idx) {
+  bool modified = false;
+  std::unordered_set<uint32_t> done;
+  // Process all functions from roots
+  while (!roots->empty()) {
+    const uint32_t fi = roots->front();
+    roots->pop();
+    if (done.insert(fi).second) {
+      Function* fn = id2function_.at(fi);
+      // Add calls first so we don't add new output function
+      AddCalls(fn, roots);
+      modified = InstrumentFunction(fn, stage_idx, pfn) || modified;
+    }
+  }
+  return modified;
+}
+
+bool InstrumentPass::InstProcessEntryPointCallTree(InstProcessFunction& pfn) {
+  // Make sure all entry points have the same execution model. Do not
+  // instrument if they do not.
+  // TODO(greg-lunarg): Handle mixed stages. Technically, a shader module
+  // can contain entry points with different execution models, although
+  // such modules will likely be rare as GLSL and HLSL are geared toward
+  // one model per module. In such cases we will need
+  // to clone any functions which are in the call trees of entrypoints
+  // with differing execution models.
+  uint32_t ecnt = 0;
+  uint32_t stage = SpvExecutionModelMax;
+  for (auto& e : get_module()->entry_points()) {
+    if (ecnt == 0)
+      stage = e.GetSingleWordInOperand(kEntryPointExecutionModelInIdx);
+    else if (e.GetSingleWordInOperand(kEntryPointExecutionModelInIdx) != stage)
+      return false;
+    ++ecnt;
+  }
+  // Only supporting vertex, fragment and compute shaders at the moment.
+  // TODO(greg-lunarg): Handle all stages.
+  if (stage != SpvExecutionModelVertex && stage != SpvExecutionModelFragment &&
+      stage != SpvExecutionModelGeometry &&
+      stage != SpvExecutionModelGLCompute &&
+      stage != SpvExecutionModelTessellationControl &&
+      stage != SpvExecutionModelTessellationEvaluation)
+    return false;
+  // Add together the roots of all entry points
+  std::queue<uint32_t> roots;
+  for (auto& e : get_module()->entry_points()) {
+    roots.push(e.GetSingleWordInOperand(kEntryPointFunctionIdInIdx));
+  }
+  bool modified = InstProcessCallTreeFromRoots(pfn, &roots, stage);
+  return modified;
+}
+
+void InstrumentPass::InitializeInstrument() {
+  output_buffer_id_ = 0;
+  output_buffer_uint_ptr_id_ = 0;
+  output_func_id_ = 0;
+  output_func_param_cnt_ = 0;
+  v4float_id_ = 0;
+  uint_id_ = 0;
+  v4uint_id_ = 0;
+  bool_id_ = 0;
+  void_id_ = 0;
+
+  // clear collections
+  id2function_.clear();
+  id2block_.clear();
+
+  // Initialize function and block maps.
+  for (auto& fn : *get_module()) {
+    id2function_[fn.result_id()] = &fn;
+    for (auto& blk : fn) {
+      id2block_[blk.id()] = &blk;
+    }
+  }
+
+  // Calculate instruction offset of first function
+  uint32_t pre_func_size = 0;
+  Module* module = get_module();
+  for (auto& i : context()->capabilities()) {
+    (void)i;
+    ++pre_func_size;
+  }
+  for (auto& i : module->extensions()) {
+    (void)i;
+    ++pre_func_size;
+  }
+  for (auto& i : module->ext_inst_imports()) {
+    (void)i;
+    ++pre_func_size;
+  }
+  ++pre_func_size;  // memory_model
+  for (auto& i : module->entry_points()) {
+    (void)i;
+    ++pre_func_size;
+  }
+  for (auto& i : module->execution_modes()) {
+    (void)i;
+    ++pre_func_size;
+  }
+  for (auto& i : module->debugs1()) {
+    (void)i;
+    ++pre_func_size;
+  }
+  for (auto& i : module->debugs2()) {
+    (void)i;
+    ++pre_func_size;
+  }
+  for (auto& i : module->debugs3()) {
+    (void)i;
+    ++pre_func_size;
+  }
+  for (auto& i : module->annotations()) {
+    (void)i;
+    ++pre_func_size;
+  }
+  for (auto& i : module->types_values()) {
+    pre_func_size += 1;
+    pre_func_size += static_cast<uint32_t>(i.dbg_line_insts().size());
+  }
+  funcIdx2offset_[0] = pre_func_size;
+
+  // Set instruction offsets for all other functions.
+  uint32_t func_idx = 1;
+  auto prev_fn = get_module()->begin();
+  auto curr_fn = prev_fn;
+  for (++curr_fn; curr_fn != get_module()->end(); ++curr_fn) {
+    // Count function and end instructions
+    uint32_t func_size = 2;
+    for (auto& blk : *prev_fn) {
+      // Count label
+      func_size += 1;
+      for (auto& inst : blk) {
+        func_size += 1;
+        func_size += static_cast<uint32_t>(inst.dbg_line_insts().size());
+      }
+    }
+    funcIdx2offset_[func_idx] = func_size;
+    ++prev_fn;
+    ++func_idx;
+  }
+}
+
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/instrument_pass.h b/source/opt/instrument_pass.h
new file mode 100644
index 0000000..476a894
--- /dev/null
+++ b/source/opt/instrument_pass.h
@@ -0,0 +1,356 @@
+// Copyright (c) 2018 The Khronos Group Inc.
+// Copyright (c) 2018 Valve Corporation
+// Copyright (c) 2018 LunarG Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef LIBSPIRV_OPT_INSTRUMENT_PASS_H_
+#define LIBSPIRV_OPT_INSTRUMENT_PASS_H_
+
+#include <list>
+#include <memory>
+#include <vector>
+
+#include "source/opt/ir_builder.h"
+#include "source/opt/pass.h"
+#include "spirv-tools/instrument.hpp"
+
+// This is a base class to assist in the creation of passes which instrument
+// shader modules. More specifically, passes which replace instructions with a
+// larger and more capable set of instructions. Commonly, these new
+// instructions will add testing of operands and execute different
+// instructions depending on the outcome, including outputting of debug
+// information into a buffer created especially for that purpose.
+//
+// This class contains helper functions to create an InstProcessFunction,
+// which is the heart of any derived class implementing a specific
+// instrumentation pass. It takes an instruction as an argument, decides
+// if it should be instrumented, and generates code to replace it. This class
+// also supplies function InstProcessEntryPointCallTree which applies the
+// InstProcessFunction to every reachable instruction in a module and replaces
+// the instruction with new instructions if generated.
+//
+// Chief among the helper functions are output code generation functions,
+// used to generate code in the shader which writes data to output buffers
+// associated with that validation. Currently one such function,
+// GenDebugStreamWrite, exists. Other such functions may be added in the
+// future. Each is accompanied by documentation describing the format of
+// its output buffer.
+//
+// A validation pass may read or write multiple buffers. All such buffers
+// are located in a single debug descriptor set whose index is passed at the
+// creation of the instrumentation pass. The bindings of the buffers used by
+// a validation pass are permanantly assigned and fixed and documented by
+// the kDebugOutput* static consts.
+
+namespace spvtools {
+namespace opt {
+
+// Validation Ids
+// These are used to identify the general validation being done and map to
+// its output buffers.
+static const uint32_t kInstValidationIdBindless = 0;
+
+class InstrumentPass : public Pass {
+  using cbb_ptr = const BasicBlock*;
+
+ public:
+  using InstProcessFunction = std::function<void(
+      BasicBlock::iterator, UptrVectorIterator<BasicBlock>, uint32_t, uint32_t,
+      std::vector<std::unique_ptr<BasicBlock>>*)>;
+
+  virtual ~InstrumentPass() = default;
+
+  IRContext::Analysis GetPreservedAnalyses() override {
+    return IRContext::kAnalysisDefUse |
+           IRContext::kAnalysisInstrToBlockMapping |
+           IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
+           IRContext::kAnalysisNameMap | IRContext::kAnalysisBuiltinVarId;
+  }
+
+ protected:
+  // Create instrumentation pass which utilizes descriptor set |desc_set|
+  // for debug input and output buffers and writes |shader_id| into debug
+  // output records.
+  InstrumentPass(uint32_t desc_set, uint32_t shader_id, uint32_t validation_id)
+      : Pass(),
+        desc_set_(desc_set),
+        shader_id_(shader_id),
+        validation_id_(validation_id) {}
+
+  // Initialize state for instrumentation of module by |validation_id|.
+  void InitializeInstrument();
+
+  // Call |pfn| on all instructions in all functions in the call tree of the
+  // entry points in |module|. If code is generated for an instruction, replace
+  // the instruction's block with the new blocks that are generated. Continue
+  // processing at the top of the last new block.
+  bool InstProcessEntryPointCallTree(InstProcessFunction& pfn);
+
+  // Move all code in |ref_block_itr| preceding the instruction |ref_inst_itr|
+  // to be instrumented into block |new_blk_ptr|.
+  void MovePreludeCode(BasicBlock::iterator ref_inst_itr,
+                       UptrVectorIterator<BasicBlock> ref_block_itr,
+                       std::unique_ptr<BasicBlock>* new_blk_ptr);
+
+  // Move all code in |ref_block_itr| succeeding the instruction |ref_inst_itr|
+  // to be instrumented into block |new_blk_ptr|.
+  void MovePostludeCode(UptrVectorIterator<BasicBlock> ref_block_itr,
+                        std::unique_ptr<BasicBlock>* new_blk_ptr);
+
+  // Generate instructions in |builder| which will atomically fetch and
+  // increment the size of the debug output buffer stream of the current
+  // validation and write a record to the end of the stream, if enough space
+  // in the buffer remains. The record will contain the index of the function
+  // and instruction within that function |func_idx, instruction_idx| which
+  // generated the record. It will also contain additional information to
+  // identify the instance of the shader, depending on the stage |stage_idx|
+  // of the shader. Finally, the record will contain validation-specific
+  // data contained in |validation_ids| which will identify the validation
+  // error as well as the values involved in the error.
+  //
+  // The output buffer binding written to by the code generated by the function
+  // is determined by the validation id specified when each specific
+  // instrumentation pass is created.
+  //
+  // The output buffer is a sequence of 32-bit values with the following
+  // format (where all elements are unsigned 32-bit unless otherwise noted):
+  //
+  //     Size
+  //     Record0
+  //     Record1
+  //     Record2
+  //     ...
+  //
+  // Size is the number of 32-bit values that have been written or
+  // attempted to be written to the output buffer, excluding the Size. It is
+  // initialized to 0. If the size of attempts to write the buffer exceeds
+  // the actual size of the buffer, it is possible that this field can exceed
+  // the actual size of the buffer.
+  //
+  // Each Record* is a variable-length sequence of 32-bit values with the
+  // following format defined using static const offsets in the .cpp file:
+  //
+  //     Record Size
+  //     Shader ID
+  //     Instruction Index
+  //     Stage
+  //     Stage-specific Word 0
+  //     Stage-specific Word 1
+  //     Validation Error Code
+  //     Validation-specific Word 0
+  //     Validation-specific Word 1
+  //     Validation-specific Word 2
+  //     ...
+  //
+  // Each record consists of three subsections: members common across all
+  // validation, members specific to the stage, and members specific to a
+  // validation.
+  //
+  // The Record Size is the number of 32-bit words in the record, including
+  // the Record Size word.
+  //
+  // Shader ID is a value that identifies which shader has generated the
+  // validation error. It is passed when the instrumentation pass is created.
+  //
+  // The Instruction Index is the position of the instruction within the
+  // SPIR-V file which is in error.
+  //
+  // The Stage is the pipeline stage which has generated the error as defined
+  // by the SpvExecutionModel_ enumeration. This is used to interpret the
+  // following Stage-specific words.
+  //
+  // The Stage-specific Words identify which invocation of the shader generated
+  // the error. Every stage will write two words, although in some cases the
+  // second word is unused and so zero is written. Vertex shaders will write
+  // the Vertex and Instance ID. Fragment shaders will write FragCoord.xy.
+  // Compute shaders will write the Global Invocation ID and zero (unused).
+  // Both tesselation shaders will write the Invocation Id and zero (unused).
+  // The geometry shader will write the Primitive ID and Invocation ID.
+  //
+  // The Validation Error Code specifies the exact error which has occurred.
+  // These are enumerated with the kInstError* static consts. This allows
+  // multiple validation layers to use the same, single output buffer.
+  //
+  // The Validation-specific Words are a validation-specific number of 32-bit
+  // words which give further information on the validation error that
+  // occurred. These are documented further in each file containing the
+  // validation-specific class which derives from this base class.
+  //
+  // Because the code that is generated checks against the size of the buffer
+  // before writing, the size of the debug out buffer can be used by the
+  // validation layer to control the number of error records that are written.
+  void GenDebugStreamWrite(uint32_t instruction_idx, uint32_t stage_idx,
+                           const std::vector<uint32_t>& validation_ids,
+                           InstructionBuilder* builder);
+
+  // Generate code to cast |value_id| to unsigned, if needed. Return
+  // an id to the unsigned equivalent.
+  uint32_t GenUintCastCode(uint32_t value_id, InstructionBuilder* builder);
+
+  // Return new label.
+  std::unique_ptr<Instruction> NewLabel(uint32_t label_id);
+
+  // Return id for 32-bit unsigned type
+  uint32_t GetUintId();
+
+  // Return id for 32-bit unsigned type
+  uint32_t GetBoolId();
+
+  // Return id for void type
+  uint32_t GetVoidId();
+
+  // Return id for output buffer uint type
+  uint32_t GetOutputBufferUintPtrId();
+
+  // Return binding for output buffer for current validation.
+  uint32_t GetOutputBufferBinding();
+
+  // Return id for debug output buffer
+  uint32_t GetOutputBufferId();
+
+  // Return id for v4float type
+  uint32_t GetVec4FloatId();
+
+  // Return id for v4uint type
+  uint32_t GetVec4UintId();
+
+  // Return id for output function. Define if it doesn't exist with
+  // |val_spec_arg_cnt| validation-specific uint32 arguments.
+  uint32_t GetStreamWriteFunctionId(uint32_t stage_idx,
+                                    uint32_t val_spec_param_cnt);
+
+  // Apply instrumentation function |pfn| to every instruction in |func|.
+  // If code is generated for an instruction, replace the instruction's
+  // block with the new blocks that are generated. Continue processing at the
+  // top of the last new block.
+  bool InstrumentFunction(Function* func, uint32_t stage_idx,
+                          InstProcessFunction& pfn);
+
+  // Call |pfn| on all functions in the call tree of the function
+  // ids in |roots|.
+  bool InstProcessCallTreeFromRoots(InstProcessFunction& pfn,
+                                    std::queue<uint32_t>* roots,
+                                    uint32_t stage_idx);
+
+  // Gen code into |builder| to write |field_value_id| into debug output
+  // buffer at |base_offset_id| + |field_offset|.
+  void GenDebugOutputFieldCode(uint32_t base_offset_id, uint32_t field_offset,
+                               uint32_t field_value_id,
+                               InstructionBuilder* builder);
+
+  // Generate instructions into |builder| which will write the members
+  // of the debug output record common for all stages and validations at
+  // |base_off|.
+  void GenCommonStreamWriteCode(uint32_t record_sz, uint32_t instruction_idx,
+                                uint32_t stage_idx, uint32_t base_off,
+                                InstructionBuilder* builder);
+
+  // Generate instructions into |builder| which will write
+  // |uint_frag_coord_id| at |component| of the record at |base_offset_id| of
+  // the debug output buffer .
+  void GenFragCoordEltDebugOutputCode(uint32_t base_offset_id,
+                                      uint32_t uint_frag_coord_id,
+                                      uint32_t component,
+                                      InstructionBuilder* builder);
+
+  // Generate instructions into |builder| which will load the uint |builtin_id|
+  // and write it into the debug output buffer at |base_off| + |builtin_off|.
+  void GenBuiltinOutputCode(uint32_t builtin_id, uint32_t builtin_off,
+                            uint32_t base_off, InstructionBuilder* builder);
+
+  // Generate instructions into |builder| which will write a uint null into
+  // the debug output buffer at |base_off| + |builtin_off|.
+  void GenUintNullOutputCode(uint32_t field_off, uint32_t base_off,
+                             InstructionBuilder* builder);
+
+  // Generate instructions into |builder| which will write the |stage_idx|-
+  // specific members of the debug output stream at |base_off|.
+  void GenStageStreamWriteCode(uint32_t stage_idx, uint32_t base_off,
+                               InstructionBuilder* builder);
+
+  // Return true if instruction must be in the same block that its result
+  // is used.
+  bool IsSameBlockOp(const Instruction* inst) const;
+
+  // Clone operands which must be in same block as consumer instructions.
+  // Look in same_blk_pre for instructions that need cloning. Look in
+  // same_blk_post for instructions already cloned. Add cloned instruction
+  // to same_blk_post.
+  void CloneSameBlockOps(
+      std::unique_ptr<Instruction>* inst,
+      std::unordered_map<uint32_t, uint32_t>* same_blk_post,
+      std::unordered_map<uint32_t, Instruction*>* same_blk_pre,
+      std::unique_ptr<BasicBlock>* block_ptr);
+
+  // Update phis in succeeding blocks to point to new last block
+  void UpdateSucceedingPhis(
+      std::vector<std::unique_ptr<BasicBlock>>& new_blocks);
+
+  // Debug descriptor set index
+  uint32_t desc_set_;
+
+  // Shader module ID written into output record
+  uint32_t shader_id_;
+
+  // Map from function id to function pointer.
+  std::unordered_map<uint32_t, Function*> id2function_;
+
+  // Map from block's label id to block. TODO(dnovillo): This is superfluous wrt
+  // CFG. It has functionality not present in CFG. Consolidate.
+  std::unordered_map<uint32_t, BasicBlock*> id2block_;
+
+  // Map from function's position index to the offset of its first instruction
+  std::unordered_map<uint32_t, uint32_t> funcIdx2offset_;
+
+  // result id for OpConstantFalse
+  uint32_t validation_id_;
+
+  // id for output buffer variable
+  uint32_t output_buffer_id_;
+
+  // type id for output buffer element
+  uint32_t output_buffer_uint_ptr_id_;
+
+  // id for debug output function
+  uint32_t output_func_id_;
+
+  // param count for output function
+  uint32_t output_func_param_cnt_;
+
+  // id for v4float type
+  uint32_t v4float_id_;
+
+  // id for v4float type
+  uint32_t v4uint_id_;
+
+  // id for 32-bit unsigned type
+  uint32_t uint_id_;
+
+  // id for bool type
+  uint32_t bool_id_;
+
+  // id for void type
+  uint32_t void_id_;
+
+  // Pre-instrumentation same-block insts
+  std::unordered_map<uint32_t, Instruction*> same_block_pre_;
+
+  // Post-instrumentation same-block op ids
+  std::unordered_map<uint32_t, uint32_t> same_block_post_;
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // LIBSPIRV_OPT_INSTRUMENT_PASS_H_
diff --git a/source/opt/ir_builder.h b/source/opt/ir_builder.h
index a07b255..434c380 100644
--- a/source/opt/ir_builder.h
+++ b/source/opt/ir_builder.h
@@ -58,6 +58,78 @@
       : InstructionBuilder(context, parent_block, parent_block->end(),
                            preserved_analyses) {}
 
+  Instruction* AddNullaryOp(uint32_t type_id, SpvOp opcode) {
+    std::unique_ptr<Instruction> newUnOp(new Instruction(
+        GetContext(), opcode, type_id,
+        opcode == SpvOpReturn ? 0 : GetContext()->TakeNextId(), {}));
+    return AddInstruction(std::move(newUnOp));
+  }
+
+  Instruction* AddUnaryOp(uint32_t type_id, SpvOp opcode, uint32_t operand1) {
+    std::unique_ptr<Instruction> newUnOp(new Instruction(
+        GetContext(), opcode, type_id, GetContext()->TakeNextId(),
+        {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand1}}}));
+    return AddInstruction(std::move(newUnOp));
+  }
+
+  Instruction* AddBinaryOp(uint32_t type_id, SpvOp opcode, uint32_t operand1,
+                           uint32_t operand2) {
+    std::unique_ptr<Instruction> newBinOp(new Instruction(
+        GetContext(), opcode, type_id,
+        opcode == SpvOpStore ? 0 : GetContext()->TakeNextId(),
+        {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand1}},
+         {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand2}}}));
+    return AddInstruction(std::move(newBinOp));
+  }
+
+  Instruction* AddTernaryOp(uint32_t type_id, SpvOp opcode, uint32_t operand1,
+                            uint32_t operand2, uint32_t operand3) {
+    std::unique_ptr<Instruction> newTernOp(new Instruction(
+        GetContext(), opcode, type_id, GetContext()->TakeNextId(),
+        {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand1}},
+         {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand2}},
+         {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand3}}}));
+    return AddInstruction(std::move(newTernOp));
+  }
+
+  Instruction* AddQuadOp(uint32_t type_id, SpvOp opcode, uint32_t operand1,
+                         uint32_t operand2, uint32_t operand3,
+                         uint32_t operand4) {
+    std::unique_ptr<Instruction> newQuadOp(new Instruction(
+        GetContext(), opcode, type_id, GetContext()->TakeNextId(),
+        {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand1}},
+         {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand2}},
+         {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand3}},
+         {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand4}}}));
+    return AddInstruction(std::move(newQuadOp));
+  }
+
+  Instruction* AddIdLiteralOp(uint32_t type_id, SpvOp opcode, uint32_t operand1,
+                              uint32_t operand2) {
+    std::unique_ptr<Instruction> newBinOp(new Instruction(
+        GetContext(), opcode, type_id, GetContext()->TakeNextId(),
+        {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand1}},
+         {spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER, {operand2}}}));
+    return AddInstruction(std::move(newBinOp));
+  }
+
+  // Creates an N-ary instruction of |opcode|.
+  // |typid| must be the id of the instruction's type.
+  // |operands| must be a sequence of operand ids.
+  // Use |result| for the result id if non-zero.
+  Instruction* AddNaryOp(uint32_t type_id, SpvOp opcode,
+                         const std::vector<uint32_t>& operands,
+                         uint32_t result = 0) {
+    std::vector<Operand> ops;
+    for (size_t i = 0; i < operands.size(); i++) {
+      ops.push_back({SPV_OPERAND_TYPE_ID, {operands[i]}});
+    }
+    std::unique_ptr<Instruction> new_inst(new Instruction(
+        GetContext(), opcode, type_id,
+        result != 0 ? result : GetContext()->TakeNextId(), ops));
+    return AddInstruction(std::move(new_inst));
+  }
+
   // Creates a new selection merge instruction.
   // The id |merge_id| is the merge basic block id.
   Instruction* AddSelectionMerge(
@@ -167,15 +239,10 @@
   // The id |type| must be the id of the phi instruction's type.
   // The vector |incomings| must be a sequence of pairs of <definition id,
   // parent id>.
-  Instruction* AddPhi(uint32_t type, const std::vector<uint32_t>& incomings) {
+  Instruction* AddPhi(uint32_t type, const std::vector<uint32_t>& incomings,
+                      uint32_t result = 0) {
     assert(incomings.size() % 2 == 0 && "A sequence of pairs is expected");
-    std::vector<Operand> phi_ops;
-    for (size_t i = 0; i < incomings.size(); i++) {
-      phi_ops.push_back({SPV_OPERAND_TYPE_ID, {incomings[i]}});
-    }
-    std::unique_ptr<Instruction> phi_inst(new Instruction(
-        GetContext(), SpvOpPhi, type, GetContext()->TakeNextId(), phi_ops));
-    return AddInstruction(std::move(phi_inst));
+    return AddNaryOp(type, SpvOpPhi, incomings, result);
   }
 
   // Creates an addition instruction.
@@ -249,8 +316,8 @@
 
   // Adds a signed int32 constant to the binary.
   // The |value| parameter is the constant value to be added.
-  Instruction* Add32BitSignedIntegerConstant(int32_t value) {
-    return Add32BitConstantInteger<int32_t>(value, true);
+  Instruction* GetSintConstant(int32_t value) {
+    return GetIntConstant<int32_t>(value, true);
   }
 
   // Create a composite construct.
@@ -270,8 +337,23 @@
   }
   // Adds an unsigned int32 constant to the binary.
   // The |value| parameter is the constant value to be added.
-  Instruction* Add32BitUnsignedIntegerConstant(uint32_t value) {
-    return Add32BitConstantInteger<uint32_t>(value, false);
+  Instruction* GetUintConstant(uint32_t value) {
+    return GetIntConstant<uint32_t>(value, false);
+  }
+
+  uint32_t GetUintConstantId(uint32_t value) {
+    Instruction* uint_inst = GetUintConstant(value);
+    return uint_inst->result_id();
+  }
+
+  uint32_t GetNullId(uint32_t type_id) {
+    analysis::TypeManager* type_mgr = GetContext()->get_type_mgr();
+    analysis::ConstantManager* const_mgr = GetContext()->get_constant_mgr();
+    const analysis::Type* type = type_mgr->GetType(type_id);
+    const analysis::Constant* null_const = const_mgr->GetConstant(type, {});
+    Instruction* null_inst =
+        const_mgr->GetDefiningInstruction(null_const, type_id);
+    return null_inst->result_id();
   }
 
   // Adds either a signed or unsigned 32 bit integer constant to the binary
@@ -279,7 +361,7 @@
   // signed constant otherwise as an unsigned constant. If |sign| is false the
   // value must not be a negative number.
   template <typename T>
-  Instruction* Add32BitConstantInteger(T value, bool sign) {
+  Instruction* GetIntConstant(T value, bool sign) {
     // Assert that we are not trying to store a negative number in an unsigned
     // type.
     if (!sign)
diff --git a/source/opt/ir_context.cpp b/source/opt/ir_context.cpp
index 4097484..038ad6d 100644
--- a/source/opt/ir_context.cpp
+++ b/source/opt/ir_context.cpp
@@ -21,6 +21,15 @@
 #include "source/opt/mem_pass.h"
 #include "source/opt/reflect.h"
 
+namespace {
+
+static const int kSpvDecorateTargetIdInIdx = 0;
+static const int kSpvDecorateDecorationInIdx = 1;
+static const int kSpvDecorateBuiltinInIdx = 2;
+static const int kEntryPointInterfaceInIdx = 3;
+
+}  // anonymous namespace
+
 namespace spvtools {
 namespace opt {
 
@@ -43,6 +52,9 @@
   if (set & kAnalysisLoopAnalysis) {
     ResetLoopAnalysis();
   }
+  if (set & kAnalysisBuiltinVarId) {
+    ResetBuiltinAnalysis();
+  }
   if (set & kAnalysisNameMap) {
     BuildIdToNameMap();
   }
@@ -79,6 +91,9 @@
   if (analyses_to_invalidate & kAnalysisCombinators) {
     combinator_ops_.clear();
   }
+  if (analyses_to_invalidate & kAnalysisBuiltinVarId) {
+    builtin_var_id_map_.clear();
+  }
   if (analyses_to_invalidate & kAnalysisCFG) {
     cfg_.reset(nullptr);
   }
@@ -573,6 +588,91 @@
   return &it->second;
 }
 
+uint32_t IRContext::FindBuiltinVar(uint32_t builtin) {
+  for (auto& a : module_->annotations()) {
+    if (a.opcode() != SpvOpDecorate) continue;
+    if (a.GetSingleWordInOperand(kSpvDecorateDecorationInIdx) !=
+        SpvDecorationBuiltIn)
+      continue;
+    if (a.GetSingleWordInOperand(kSpvDecorateBuiltinInIdx) != builtin) continue;
+    uint32_t target_id = a.GetSingleWordInOperand(kSpvDecorateTargetIdInIdx);
+    Instruction* b_var = get_def_use_mgr()->GetDef(target_id);
+    if (b_var->opcode() != SpvOpVariable) continue;
+    return target_id;
+  }
+  return 0;
+}
+
+void IRContext::AddVarToEntryPoints(uint32_t var_id) {
+  uint32_t ocnt = 0;
+  for (auto& e : module()->entry_points()) {
+    bool found = false;
+    e.ForEachInOperand([&ocnt, &found, &var_id](const uint32_t* idp) {
+      if (ocnt >= kEntryPointInterfaceInIdx) {
+        if (*idp == var_id) found = true;
+      }
+      ++ocnt;
+    });
+    if (!found) {
+      e.AddOperand({SPV_OPERAND_TYPE_ID, {var_id}});
+      get_def_use_mgr()->AnalyzeInstDefUse(&e);
+    }
+  }
+}
+
+uint32_t IRContext::GetBuiltinVarId(uint32_t builtin) {
+  if (!AreAnalysesValid(kAnalysisBuiltinVarId)) ResetBuiltinAnalysis();
+  // If cached, return it.
+  std::unordered_map<uint32_t, uint32_t>::iterator it =
+      builtin_var_id_map_.find(builtin);
+  if (it != builtin_var_id_map_.end()) return it->second;
+  // Look for one in shader
+  uint32_t var_id = FindBuiltinVar(builtin);
+  if (var_id == 0) {
+    // If not found, create it
+    // TODO(greg-lunarg): Add support for all builtins
+    analysis::TypeManager* type_mgr = get_type_mgr();
+    analysis::Type* reg_type;
+    switch (builtin) {
+      case SpvBuiltInFragCoord: {
+        analysis::Float float_ty(32);
+        analysis::Type* reg_float_ty = type_mgr->GetRegisteredType(&float_ty);
+        analysis::Vector v4float_ty(reg_float_ty, 4);
+        reg_type = type_mgr->GetRegisteredType(&v4float_ty);
+        break;
+      }
+      case SpvBuiltInVertexId:
+      case SpvBuiltInInstanceId:
+      case SpvBuiltInPrimitiveId:
+      case SpvBuiltInInvocationId:
+      case SpvBuiltInGlobalInvocationId: {
+        analysis::Integer uint_ty(32, false);
+        reg_type = type_mgr->GetRegisteredType(&uint_ty);
+        break;
+      }
+      default: {
+        assert(false && "unhandled builtin");
+        return 0;
+      }
+    }
+    uint32_t type_id = type_mgr->GetTypeInstruction(reg_type);
+    uint32_t varTyPtrId =
+        type_mgr->FindPointerToType(type_id, SpvStorageClassInput);
+    var_id = TakeNextId();
+    std::unique_ptr<Instruction> newVarOp(
+        new Instruction(this, SpvOpVariable, varTyPtrId, var_id,
+                        {{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
+                          {SpvStorageClassInput}}}));
+    get_def_use_mgr()->AnalyzeInstDefUse(&*newVarOp);
+    module()->AddGlobalValue(std::move(newVarOp));
+    get_decoration_mgr()->AddDecorationVal(var_id, SpvDecorationBuiltIn,
+                                           builtin);
+    AddVarToEntryPoints(var_id);
+  }
+  builtin_var_id_map_[builtin] = var_id;
+  return var_id;
+}
+
 // Gets the dominator analysis for function |f|.
 DominatorAnalysis* IRContext::GetDominatorAnalysis(const Function* f) {
   if (!AreAnalysesValid(kAnalysisDominatorAnalysis)) {
diff --git a/source/opt/ir_context.h b/source/opt/ir_context.h
index 742c868..6e9eda8 100644
--- a/source/opt/ir_context.h
+++ b/source/opt/ir_context.h
@@ -73,7 +73,8 @@
     kAnalysisRegisterPressure = 1 << 9,
     kAnalysisValueNumberTable = 1 << 10,
     kAnalysisStructuredCFG = 1 << 11,
-    kAnalysisEnd = 1 << 12
+    kAnalysisBuiltinVarId = 1 << 12,
+    kAnalysisEnd = 1 << 13
   };
 
   friend inline Analysis operator|(Analysis lhs, Analysis rhs);
@@ -472,6 +473,11 @@
   uint32_t max_id_bound() const { return max_id_bound_; }
   void set_max_id_bound(uint32_t new_bound) { max_id_bound_ = new_bound; }
 
+  // Return id of variable only decorated with |builtin|, if in module.
+  // Create variable and return its id otherwise. If builtin not currently
+  // supported, return 0.
+  uint32_t GetBuiltinVarId(uint32_t builtin);
+
  private:
   // Builds the def-use manager from scratch, even if it was already valid.
   void BuildDefUseManager() {
@@ -543,6 +549,13 @@
     valid_analyses_ = valid_analyses_ | kAnalysisLoopAnalysis;
   }
 
+  // Removes all computed loop descriptors.
+  void ResetBuiltinAnalysis() {
+    // Clear the cache.
+    builtin_var_id_map_.clear();
+    valid_analyses_ = valid_analyses_ | kAnalysisBuiltinVarId;
+  }
+
   // Analyzes the features in the owned module. Builds the manager if required.
   void AnalyzeFeatures() {
     feature_mgr_ = MakeUnique<FeatureManager>(grammar_);
@@ -566,6 +579,13 @@
   // true if the cfg is invalidated.
   bool CheckCFG();
 
+  // Return id of variable only decorated with |builtin|, if in module.
+  // Return 0 otherwise.
+  uint32_t FindBuiltinVar(uint32_t builtin);
+
+  // Add |var_id| to all entry points in module.
+  void AddVarToEntryPoints(uint32_t var_id);
+
   // The SPIR-V syntax context containing grammar tables for opcodes and
   // operands.
   spv_context syntax_context_;
@@ -607,6 +627,10 @@
   // without side-effect.
   std::unordered_map<uint32_t, std::unordered_set<uint32_t>> combinator_ops_;
 
+  // Opcodes of shader capability core executable instructions
+  // without side-effect.
+  std::unordered_map<uint32_t, uint32_t> builtin_var_id_map_;
+
   // The CFG for all the functions in |module_|.
   std::unique_ptr<CFG> cfg_;
 
@@ -786,6 +810,9 @@
 }
 
 void IRContext::AddExtension(std::unique_ptr<Instruction>&& e) {
+  if (AreAnalysesValid(kAnalysisDefUse)) {
+    get_def_use_mgr()->AnalyzeInstDefUse(e.get());
+  }
   module()->AddExtension(std::move(e));
 }
 
@@ -827,6 +854,9 @@
   if (AreAnalysesValid(kAnalysisDecorations)) {
     get_decoration_mgr()->AddDecoration(a.get());
   }
+  if (AreAnalysesValid(kAnalysisDefUse)) {
+    get_def_use_mgr()->AnalyzeInstDefUse(a.get());
+  }
   module()->AddAnnotationInst(std::move(a));
 }
 
@@ -838,10 +868,10 @@
 }
 
 void IRContext::AddGlobalValue(std::unique_ptr<Instruction>&& v) {
-  module()->AddGlobalValue(std::move(v));
   if (AreAnalysesValid(kAnalysisDefUse)) {
-    get_def_use_mgr()->AnalyzeInstDef(&*(--types_values_end()));
+    get_def_use_mgr()->AnalyzeInstDefUse(&*v);
   }
+  module()->AddGlobalValue(std::move(v));
 }
 
 void IRContext::AddFunction(std::unique_ptr<Function>&& f) {
diff --git a/source/opt/loop_peeling.cpp b/source/opt/loop_peeling.cpp
index 7d27480..227ba4a 100644
--- a/source/opt/loop_peeling.cpp
+++ b/source/opt/loop_peeling.cpp
@@ -151,7 +151,7 @@
       context_, &*insert_point,
       IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
   Instruction* uint_1_cst =
-      builder.Add32BitConstantInteger<uint32_t>(1, int_type_->IsSigned());
+      builder.GetIntConstant<uint32_t>(1, int_type_->IsSigned());
   // Create the increment.
   // Note that we do "1 + 1" here, one of the operand should the phi
   // value but we don't have it yet. The operand will be set latter.
@@ -162,8 +162,7 @@
 
   canonical_induction_variable_ = builder.AddPhi(
       uint_1_cst->type_id(),
-      {builder.Add32BitConstantInteger<uint32_t>(0, int_type_->IsSigned())
-           ->result_id(),
+      {builder.GetIntConstant<uint32_t>(0, int_type_->IsSigned())->result_id(),
        GetClonedLoop()->GetPreHeaderBlock()->id(), iv_inc->result_id(),
        GetClonedLoop()->GetLatchBlock()->id()});
   // Connect everything.
@@ -422,7 +421,7 @@
       context_, &*cloned_loop_->GetPreHeaderBlock()->tail(),
       IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
   Instruction* factor =
-      builder.Add32BitConstantInteger(peel_factor, int_type_->IsSigned());
+      builder.GetIntConstant(peel_factor, int_type_->IsSigned());
 
   Instruction* has_remaining_iteration = builder.AddLessThan(
       factor->result_id(), loop_iteration_count_->result_id());
@@ -484,7 +483,7 @@
       context_, &*cloned_loop_->GetPreHeaderBlock()->tail(),
       IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
   Instruction* factor =
-      builder.Add32BitConstantInteger(peel_factor, int_type_->IsSigned());
+      builder.GetIntConstant(peel_factor, int_type_->IsSigned());
 
   Instruction* has_remaining_iteration = builder.AddLessThan(
       factor->result_id(), loop_iteration_count_->result_id());
@@ -677,8 +676,8 @@
       InstructionBuilder(
           context(), loop->GetHeaderBlock(),
           IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping)
-          .Add32BitConstantInteger<uint32_t>(static_cast<uint32_t>(iterations),
-                                             is_signed),
+          .GetIntConstant<uint32_t>(static_cast<uint32_t>(iterations),
+                                    is_signed),
       canonical_induction_variable);
 
   if (!peeler.CanPeelLoop()) {
diff --git a/source/opt/loop_unroller.cpp b/source/opt/loop_unroller.cpp
index 96ee94d..d3e733a 100644
--- a/source/opt/loop_unroller.cpp
+++ b/source/opt/loop_unroller.cpp
@@ -452,11 +452,9 @@
   // If the remainder is negative then we add a signed constant, otherwise just
   // add an unsigned constant.
   if (remainder < 0) {
-    new_constant =
-        builder.Add32BitSignedIntegerConstant(static_cast<int32_t>(remainder));
+    new_constant = builder.GetSintConstant(static_cast<int32_t>(remainder));
   } else {
-    new_constant = builder.Add32BitUnsignedIntegerConstant(
-        static_cast<int32_t>(remainder));
+    new_constant = builder.GetUintConstant(static_cast<int32_t>(remainder));
   }
 
   uint32_t constant_id = new_constant->result_id();
diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp
index 5a1b314..b1bb345 100644
--- a/source/opt/optimizer.cpp
+++ b/source/opt/optimizer.cpp
@@ -368,6 +368,12 @@
     RegisterPass(CreateWorkaround1209Pass());
   } else if (pass_name == "replace-invalid-opcode") {
     RegisterPass(CreateReplaceInvalidOpcodePass());
+  } else if (pass_name == "inst-bindless-check") {
+    RegisterPass(CreateInstBindlessCheckPass(7, 23));
+    RegisterPass(CreateSimplificationPass());
+    RegisterPass(CreateDeadBranchElimPass());
+    RegisterPass(CreateBlockMergePass());
+    RegisterPass(CreateAggressiveDCEPass());
   } else if (pass_name == "simplify-instructions") {
     RegisterPass(CreateSimplificationPass());
   } else if (pass_name == "ssa-rewrite") {
@@ -747,4 +753,11 @@
   return MakeUnique<Optimizer::PassToken::Impl>(
       MakeUnique<opt::CombineAccessChains>());
 }
+
+Optimizer::PassToken CreateInstBindlessCheckPass(uint32_t desc_set,
+                                                 uint32_t shader_id) {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::InstBindlessCheckPass>(desc_set, shader_id));
+}
+
 }  // namespace spvtools
diff --git a/source/opt/pass.cpp b/source/opt/pass.cpp
index 4c4a232..4bf719d 100644
--- a/source/opt/pass.cpp
+++ b/source/opt/pass.cpp
@@ -44,8 +44,9 @@
 
   // Collect all of the entry points as the roots.
   std::queue<uint32_t> roots;
-  for (auto& e : module->entry_points())
+  for (auto& e : module->entry_points()) {
     roots.push(e.GetSingleWordInOperand(kEntryPointFunctionIdInIdx));
+  }
   return ProcessCallTreeFromRoots(pfn, id2function, &roots);
 }
 
diff --git a/source/opt/passes.h b/source/opt/passes.h
index 987af1e..25e3a47 100644
--- a/source/opt/passes.h
+++ b/source/opt/passes.h
@@ -36,6 +36,7 @@
 #include "source/opt/if_conversion.h"
 #include "source/opt/inline_exhaustive_pass.h"
 #include "source/opt/inline_opaque_pass.h"
+#include "source/opt/inst_bindless_check_pass.h"
 #include "source/opt/licm_pass.h"
 #include "source/opt/local_access_chain_convert_pass.h"
 #include "source/opt/local_redundancy_elimination.h"
diff --git a/source/opt/types.cpp b/source/opt/types.cpp
index 15cff54..0cbe5c8 100644
--- a/source/opt/types.cpp
+++ b/source/opt/types.cpp
@@ -555,6 +555,14 @@
   }
 }
 
+Function::Function(Type* ret_type, std::vector<const Type*>& params)
+    : Type(kFunction), return_type_(ret_type), param_types_(params) {
+  for (auto* t : params) {
+    (void)t;
+    assert(!t->AsVoid());
+  }
+}
+
 bool Function::IsSameImpl(const Type* that, IsSameCache* seen) const {
   const Function* ft = that->AsFunction();
   if (!ft) return false;
diff --git a/source/opt/types.h b/source/opt/types.h
index 625f342..28731c6 100644
--- a/source/opt/types.h
+++ b/source/opt/types.h
@@ -491,6 +491,7 @@
 class Function : public Type {
  public:
   Function(Type* ret_type, const std::vector<const Type*>& params);
+  Function(Type* ret_type, std::vector<const Type*>& params);
   Function(const Function&) = default;
 
   std::string str() const override;
diff --git a/test/opt/CMakeLists.txt b/test/opt/CMakeLists.txt
index 240aed1..05957b2 100644
--- a/test/opt/CMakeLists.txt
+++ b/test/opt/CMakeLists.txt
@@ -42,6 +42,7 @@
        inline_opaque_test.cpp
        inline_test.cpp
        insert_extract_elim_test.cpp
+       inst_bindless_check_test.cpp
        instruction_list_test.cpp
        instruction_test.cpp
        ir_builder.cpp
@@ -86,4 +87,3 @@
   LIBS SPIRV-Tools-opt
   PCH_FILE pch_test_opt
 )
-
diff --git a/test/opt/inst_bindless_check_test.cpp b/test/opt/inst_bindless_check_test.cpp
new file mode 100644
index 0000000..1a1a194
--- /dev/null
+++ b/test/opt/inst_bindless_check_test.cpp
@@ -0,0 +1,1850 @@
+// Copyright (c) 2017 Valve Corporation
+// Copyright (c) 2017 LunarG Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <string>
+#include <vector>
+
+#include "test/opt/assembly_builder.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using InstBindlessTest = PassTest<::testing::Test>;
+
+TEST_F(InstBindlessTest, Simple) {
+  // Texture2D g_tColor[128];
+  //
+  // layout(push_constant) cbuffer PerViewConstantBuffer_t
+  // {
+  //   uint g_nDataIdx;
+  // };
+  //
+  // SamplerState g_sAniso;
+  //
+  // struct PS_INPUT
+  // {
+  //   float2 vTextureCoords : TEXCOORD2;
+  // };
+  //
+  // struct PS_OUTPUT
+  // {
+  //   float4 vColor : SV_Target0;
+  // };
+  //
+  // PS_OUTPUT MainPs(PS_INPUT i)
+  // {
+  //   PS_OUTPUT ps_output;
+  //   ps_output.vColor =
+  //       g_tColor[ g_nDataIdx ].Sample(g_sAniso, i.vTextureCoords.xy);
+  //   return ps_output;
+  // }
+
+  const std::string entry_before =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+)";
+
+  const std::string entry_after =
+      R"(OpCapability Shader
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+)";
+
+  const std::string names_annots =
+      R"(OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %g_sAniso "g_sAniso"
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 3
+OpDecorate %g_tColor Binding 0
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %g_sAniso DescriptorSet 0
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+)";
+
+  const std::string new_annots =
+      R"(OpDecorate %_struct_55 Block
+OpMemberDecorate %_struct_55 0 Offset 0
+OpMemberDecorate %_struct_55 1 Offset 4
+OpDecorate %57 DescriptorSet 7
+OpDecorate %57 Binding 0
+OpDecorate %gl_FragCoord BuiltIn FragCoord
+)";
+
+  const std::string consts_types_vars =
+      R"(%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%16 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%_arr_16_uint_128 = OpTypeArray %16 %uint_128
+%_ptr_UniformConstant__arr_16_uint_128 = OpTypePointer UniformConstant %_arr_16_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_16_uint_128 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_16 = OpTypePointer UniformConstant %16
+%24 = OpTypeSampler
+%_ptr_UniformConstant_24 = OpTypePointer UniformConstant %24
+%g_sAniso = OpVariable %_ptr_UniformConstant_24 UniformConstant
+%26 = OpTypeSampledImage %16
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+  const std::string new_consts_types_vars =
+      R"(%uint_0 = OpConstant %uint 0
+%bool = OpTypeBool
+%48 = OpTypeFunction %void %uint %uint %uint %uint
+%_runtimearr_uint = OpTypeRuntimeArray %uint
+%_struct_55 = OpTypeStruct %uint %_runtimearr_uint
+%_ptr_StorageBuffer__struct_55 = OpTypePointer StorageBuffer %_struct_55
+%57 = OpVariable %_ptr_StorageBuffer__struct_55 StorageBuffer
+%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+%uint_9 = OpConstant %uint 9
+%uint_4 = OpConstant %uint 4
+%uint_1 = OpConstant %uint 1
+%uint_23 = OpConstant %uint 23
+%uint_2 = OpConstant %uint 2
+%uint_3 = OpConstant %uint 3
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+%v4uint = OpTypeVector %uint 4
+%uint_5 = OpConstant %uint 5
+%uint_6 = OpConstant %uint 6
+%uint_7 = OpConstant %uint 7
+%uint_8 = OpConstant %uint 8
+%uint_56 = OpConstant %uint 56
+%103 = OpConstantNull %v4float
+)";
+
+  const std::string func_pt1 =
+      R"(%MainPs = OpFunction %void None %10
+%29 = OpLabel
+%30 = OpLoad %v2float %i_vTextureCoords
+%31 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%32 = OpLoad %uint %31
+%33 = OpAccessChain %_ptr_UniformConstant_16 %g_tColor %32
+%34 = OpLoad %16 %33
+%35 = OpLoad %24 %g_sAniso
+%36 = OpSampledImage %26 %34 %35
+)";
+
+  const std::string func_pt2_before =
+      R"(%37 = OpImageSampleImplicitLod %v4float %36 %30
+OpStore %_entryPointOutput_vColor %37
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string func_pt2_after =
+      R"(%40 = OpULessThan %bool %32 %uint_128
+OpSelectionMerge %41 None
+OpBranchConditional %40 %42 %43
+%42 = OpLabel
+%44 = OpLoad %16 %33
+%45 = OpSampledImage %26 %44 %35
+%46 = OpImageSampleImplicitLod %v4float %45 %30
+OpBranch %41
+%43 = OpLabel
+%102 = OpFunctionCall %void %47 %uint_56 %uint_0 %32 %uint_128
+OpBranch %41
+%41 = OpLabel
+%104 = OpPhi %v4float %46 %42 %103 %43
+OpStore %_entryPointOutput_vColor %104
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string output_func =
+      R"(%47 = OpFunction %void None %48
+%49 = OpFunctionParameter %uint
+%50 = OpFunctionParameter %uint
+%51 = OpFunctionParameter %uint
+%52 = OpFunctionParameter %uint
+%53 = OpLabel
+%59 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_0
+%62 = OpAtomicIAdd %uint %59 %uint_4 %uint_0 %uint_9
+%63 = OpIAdd %uint %62 %uint_9
+%64 = OpArrayLength %uint %57 1
+%65 = OpULessThanEqual %bool %63 %64
+OpSelectionMerge %66 None
+OpBranchConditional %65 %67 %66
+%67 = OpLabel
+%68 = OpIAdd %uint %62 %uint_0
+%70 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %68
+OpStore %70 %uint_9
+%72 = OpIAdd %uint %62 %uint_1
+%73 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %72
+OpStore %73 %uint_23
+%75 = OpIAdd %uint %62 %uint_2
+%76 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %75
+OpStore %76 %49
+%78 = OpIAdd %uint %62 %uint_3
+%79 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %78
+OpStore %79 %uint_4
+%82 = OpLoad %v4float %gl_FragCoord
+%84 = OpBitcast %v4uint %82
+%85 = OpCompositeExtract %uint %84 0
+%86 = OpIAdd %uint %62 %uint_4
+%87 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %86
+OpStore %87 %85
+%88 = OpCompositeExtract %uint %84 1
+%90 = OpIAdd %uint %62 %uint_5
+%91 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %90
+OpStore %91 %88
+%93 = OpIAdd %uint %62 %uint_6
+%94 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %93
+OpStore %94 %50
+%96 = OpIAdd %uint %62 %uint_7
+%97 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %96
+OpStore %97 %51
+%99 = OpIAdd %uint %62 %uint_8
+%100 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %99
+OpStore %100 %52
+OpBranch %66
+%66 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<InstBindlessCheckPass>(
+      entry_before + names_annots + consts_types_vars + func_pt1 +
+          func_pt2_before,
+      entry_after + names_annots + new_annots + consts_types_vars +
+          new_consts_types_vars + func_pt1 + func_pt2_after + output_func,
+      true, true);
+}
+
+TEST_F(InstBindlessTest, NoInstrumentConstIndexInbounds) {
+  // Texture2D g_tColor[128];
+  //
+  // SamplerState g_sAniso;
+  //
+  // struct PS_INPUT
+  // {
+  //   float2 vTextureCoords : TEXCOORD2;
+  // };
+  //
+  // struct PS_OUTPUT
+  // {
+  //   float4 vColor : SV_Target0;
+  // };
+  //
+  // PS_OUTPUT MainPs(PS_INPUT i)
+  // {
+  //   PS_OUTPUT ps_output;
+  //
+  //   ps_output.vColor = g_tColor[ 37 ].Sample(g_sAniso, i.vTextureCoords.xy);
+  //   return ps_output;
+  // }
+
+  const std::string before =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %g_sAniso "g_sAniso"
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 3
+OpDecorate %g_tColor Binding 0
+OpDecorate %g_sAniso DescriptorSet 0
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+%void = OpTypeVoid
+%8 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%int_37 = OpConstant %int 37
+%15 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%_arr_15_uint_128 = OpTypeArray %15 %uint_128
+%_ptr_UniformConstant__arr_15_uint_128 = OpTypePointer UniformConstant %_arr_15_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_15_uint_128 UniformConstant
+%_ptr_UniformConstant_15 = OpTypePointer UniformConstant %15
+%21 = OpTypeSampler
+%_ptr_UniformConstant_21 = OpTypePointer UniformConstant %21
+%g_sAniso = OpVariable %_ptr_UniformConstant_21 UniformConstant
+%23 = OpTypeSampledImage %15
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+%MainPs = OpFunction %void None %8
+%26 = OpLabel
+%27 = OpLoad %v2float %i_vTextureCoords
+%28 = OpAccessChain %_ptr_UniformConstant_15 %g_tColor %int_37
+%29 = OpLoad %15 %28
+%30 = OpLoad %21 %g_sAniso
+%31 = OpSampledImage %23 %29 %30
+%32 = OpImageSampleImplicitLod %v4float %31 %27
+OpStore %_entryPointOutput_vColor %32
+OpReturn
+OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<InstBindlessCheckPass>(before, before, true, true);
+}
+
+TEST_F(InstBindlessTest, InstrumentMultipleInstructions) {
+  // Texture2D g_tColor[128];
+  //
+  // layout(push_constant) cbuffer PerViewConstantBuffer_t
+  // {
+  //   uint g_nDataIdx;
+  //   uint g_nDataIdx2;
+  // };
+  //
+  // SamplerState g_sAniso;
+  //
+  // struct PS_INPUT
+  // {
+  //   float2 vTextureCoords : TEXCOORD2;
+  // };
+  //
+  // struct PS_OUTPUT
+  // {
+  //   float4 vColor : SV_Target0;
+  // };
+  //
+  // PS_OUTPUT MainPs(PS_INPUT i)
+  // {
+  //   PS_OUTPUT ps_output;
+  //
+  //   float t  = g_tColor[g_nDataIdx ].Sample(g_sAniso, i.vTextureCoords.xy);
+  //   float t2 = g_tColor[g_nDataIdx2].Sample(g_sAniso, i.vTextureCoords.xy);
+  //   ps_output.vColor = t + t2;
+  //   return ps_output;
+  // }
+
+  const std::string defs_before =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %g_sAniso "g_sAniso"
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 3
+OpDecorate %g_tColor Binding 0
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpMemberDecorate %PerViewConstantBuffer_t 1 Offset 4
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %g_sAniso DescriptorSet 0
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%17 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%_arr_17_uint_128 = OpTypeArray %17 %uint_128
+%_ptr_UniformConstant__arr_17_uint_128 = OpTypePointer UniformConstant %_arr_17_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_17_uint_128 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_17 = OpTypePointer UniformConstant %17
+%25 = OpTypeSampler
+%_ptr_UniformConstant_25 = OpTypePointer UniformConstant %25
+%g_sAniso = OpVariable %_ptr_UniformConstant_25 UniformConstant
+%27 = OpTypeSampledImage %17
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+  const std::string defs_after =
+      R"(OpCapability Shader
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %g_sAniso "g_sAniso"
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 3
+OpDecorate %g_tColor Binding 0
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpMemberDecorate %PerViewConstantBuffer_t 1 Offset 4
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %g_sAniso DescriptorSet 0
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+OpDecorate %_struct_63 Block
+OpMemberDecorate %_struct_63 0 Offset 0
+OpMemberDecorate %_struct_63 1 Offset 4
+OpDecorate %65 DescriptorSet 7
+OpDecorate %65 Binding 0
+OpDecorate %gl_FragCoord BuiltIn FragCoord
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%17 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%_arr_17_uint_128 = OpTypeArray %17 %uint_128
+%_ptr_UniformConstant__arr_17_uint_128 = OpTypePointer UniformConstant %_arr_17_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_17_uint_128 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_17 = OpTypePointer UniformConstant %17
+%25 = OpTypeSampler
+%_ptr_UniformConstant_25 = OpTypePointer UniformConstant %25
+%g_sAniso = OpVariable %_ptr_UniformConstant_25 UniformConstant
+%27 = OpTypeSampledImage %17
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+%uint_0 = OpConstant %uint 0
+%bool = OpTypeBool
+%56 = OpTypeFunction %void %uint %uint %uint %uint
+%_runtimearr_uint = OpTypeRuntimeArray %uint
+%_struct_63 = OpTypeStruct %uint %_runtimearr_uint
+%_ptr_StorageBuffer__struct_63 = OpTypePointer StorageBuffer %_struct_63
+%65 = OpVariable %_ptr_StorageBuffer__struct_63 StorageBuffer
+%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+%uint_9 = OpConstant %uint 9
+%uint_4 = OpConstant %uint 4
+%uint_1 = OpConstant %uint 1
+%uint_23 = OpConstant %uint 23
+%uint_2 = OpConstant %uint 2
+%uint_3 = OpConstant %uint 3
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+%v4uint = OpTypeVector %uint 4
+%uint_5 = OpConstant %uint 5
+%uint_6 = OpConstant %uint 6
+%uint_7 = OpConstant %uint 7
+%uint_8 = OpConstant %uint 8
+%uint_58 = OpConstant %uint 58
+%111 = OpConstantNull %v4float
+%uint_64 = OpConstant %uint 64
+)";
+
+  const std::string func_before =
+      R"(%MainPs = OpFunction %void None %10
+%30 = OpLabel
+%31 = OpLoad %v2float %i_vTextureCoords
+%32 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%33 = OpLoad %uint %32
+%34 = OpAccessChain %_ptr_UniformConstant_17 %g_tColor %33
+%35 = OpLoad %17 %34
+%36 = OpLoad %25 %g_sAniso
+%37 = OpSampledImage %27 %35 %36
+%38 = OpImageSampleImplicitLod %v4float %37 %31
+%39 = OpAccessChain %_ptr_PushConstant_uint %_ %int_1
+%40 = OpLoad %uint %39
+%41 = OpAccessChain %_ptr_UniformConstant_17 %g_tColor %40
+%42 = OpLoad %17 %41
+%43 = OpSampledImage %27 %42 %36
+%44 = OpImageSampleImplicitLod %v4float %43 %31
+%45 = OpFAdd %v4float %38 %44
+OpStore %_entryPointOutput_vColor %45
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string func_after =
+      R"(%MainPs = OpFunction %void None %10
+%30 = OpLabel
+%31 = OpLoad %v2float %i_vTextureCoords
+%32 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%33 = OpLoad %uint %32
+%34 = OpAccessChain %_ptr_UniformConstant_17 %g_tColor %33
+%35 = OpLoad %17 %34
+%36 = OpLoad %25 %g_sAniso
+%37 = OpSampledImage %27 %35 %36
+%48 = OpULessThan %bool %33 %uint_128
+OpSelectionMerge %49 None
+OpBranchConditional %48 %50 %51
+%50 = OpLabel
+%52 = OpLoad %17 %34
+%53 = OpSampledImage %27 %52 %36
+%54 = OpImageSampleImplicitLod %v4float %53 %31
+OpBranch %49
+%51 = OpLabel
+%110 = OpFunctionCall %void %55 %uint_58 %uint_0 %33 %uint_128
+OpBranch %49
+%49 = OpLabel
+%112 = OpPhi %v4float %54 %50 %111 %51
+%39 = OpAccessChain %_ptr_PushConstant_uint %_ %int_1
+%40 = OpLoad %uint %39
+%41 = OpAccessChain %_ptr_UniformConstant_17 %g_tColor %40
+%42 = OpLoad %17 %41
+%43 = OpSampledImage %27 %42 %36
+%113 = OpULessThan %bool %40 %uint_128
+OpSelectionMerge %114 None
+OpBranchConditional %113 %115 %116
+%115 = OpLabel
+%117 = OpLoad %17 %41
+%118 = OpSampledImage %27 %117 %36
+%119 = OpImageSampleImplicitLod %v4float %118 %31
+OpBranch %114
+%116 = OpLabel
+%121 = OpFunctionCall %void %55 %uint_64 %uint_0 %40 %uint_128
+OpBranch %114
+%114 = OpLabel
+%122 = OpPhi %v4float %119 %115 %111 %116
+%45 = OpFAdd %v4float %112 %122
+OpStore %_entryPointOutput_vColor %45
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string output_func =
+      R"(%55 = OpFunction %void None %56
+%57 = OpFunctionParameter %uint
+%58 = OpFunctionParameter %uint
+%59 = OpFunctionParameter %uint
+%60 = OpFunctionParameter %uint
+%61 = OpLabel
+%67 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_0
+%70 = OpAtomicIAdd %uint %67 %uint_4 %uint_0 %uint_9
+%71 = OpIAdd %uint %70 %uint_9
+%72 = OpArrayLength %uint %65 1
+%73 = OpULessThanEqual %bool %71 %72
+OpSelectionMerge %74 None
+OpBranchConditional %73 %75 %74
+%75 = OpLabel
+%76 = OpIAdd %uint %70 %uint_0
+%78 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %76
+OpStore %78 %uint_9
+%80 = OpIAdd %uint %70 %uint_1
+%81 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %80
+OpStore %81 %uint_23
+%83 = OpIAdd %uint %70 %uint_2
+%84 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %83
+OpStore %84 %57
+%86 = OpIAdd %uint %70 %uint_3
+%87 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %86
+OpStore %87 %uint_4
+%90 = OpLoad %v4float %gl_FragCoord
+%92 = OpBitcast %v4uint %90
+%93 = OpCompositeExtract %uint %92 0
+%94 = OpIAdd %uint %70 %uint_4
+%95 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %94
+OpStore %95 %93
+%96 = OpCompositeExtract %uint %92 1
+%98 = OpIAdd %uint %70 %uint_5
+%99 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %98
+OpStore %99 %96
+%101 = OpIAdd %uint %70 %uint_6
+%102 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %101
+OpStore %102 %58
+%104 = OpIAdd %uint %70 %uint_7
+%105 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %104
+OpStore %105 %59
+%107 = OpIAdd %uint %70 %uint_8
+%108 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %107
+OpStore %108 %60
+OpBranch %74
+%74 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<InstBindlessCheckPass>(
+      defs_before + func_before, defs_after + func_after + output_func, true,
+      true);
+}
+
+TEST_F(InstBindlessTest, ReuseConstsTypesBuiltins) {
+  // This test verifies that the pass resuses existing constants, types
+  // and builtin variables.  This test was created by editing the SPIR-V
+  // from the Simple test.
+
+  const std::string defs_before =
+      R"(OpCapability Shader
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %g_sAniso "g_sAniso"
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 3
+OpDecorate %g_tColor Binding 0
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %g_sAniso DescriptorSet 0
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+OpDecorate %85 DescriptorSet 7
+OpDecorate %85 Binding 0
+OpDecorate %gl_FragCoord BuiltIn FragCoord
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%20 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%_arr_20_uint_128 = OpTypeArray %20 %uint_128
+%_ptr_UniformConstant__arr_20_uint_128 = OpTypePointer UniformConstant %_arr_20_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_20_uint_128 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_20 = OpTypePointer UniformConstant %20
+%35 = OpTypeSampler
+%_ptr_UniformConstant_35 = OpTypePointer UniformConstant %35
+%g_sAniso = OpVariable %_ptr_UniformConstant_35 UniformConstant
+%39 = OpTypeSampledImage %20
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+%uint_0 = OpConstant %uint 0
+%bool = OpTypeBool
+%_runtimearr_uint = OpTypeRuntimeArray %uint
+%_struct_83 = OpTypeStruct %uint %_runtimearr_uint
+%_ptr_StorageBuffer__struct_83 = OpTypePointer StorageBuffer %_struct_83
+%85 = OpVariable %_ptr_StorageBuffer__struct_83 StorageBuffer
+%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+%uint_10 = OpConstant %uint 10
+%uint_4 = OpConstant %uint 4
+%uint_1 = OpConstant %uint 1
+%uint_23 = OpConstant %uint 23
+%uint_2 = OpConstant %uint 2
+%uint_9 = OpConstant %uint 9
+%uint_3 = OpConstant %uint 3
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+%v4uint = OpTypeVector %uint 4
+%uint_5 = OpConstant %uint 5
+%uint_6 = OpConstant %uint 6
+%uint_7 = OpConstant %uint 7
+%uint_8 = OpConstant %uint 8
+%131 = OpConstantNull %v4float
+)";
+
+  const std::string defs_after =
+      R"(OpCapability Shader
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %g_sAniso "g_sAniso"
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 3
+OpDecorate %g_tColor Binding 0
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %g_sAniso DescriptorSet 0
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+OpDecorate %10 DescriptorSet 7
+OpDecorate %10 Binding 0
+OpDecorate %gl_FragCoord BuiltIn FragCoord
+OpDecorate %_struct_34 Block
+OpMemberDecorate %_struct_34 0 Offset 0
+OpMemberDecorate %_struct_34 1 Offset 4
+OpDecorate %74 DescriptorSet 7
+OpDecorate %74 Binding 0
+%void = OpTypeVoid
+%12 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%18 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%_arr_18_uint_128 = OpTypeArray %18 %uint_128
+%_ptr_UniformConstant__arr_18_uint_128 = OpTypePointer UniformConstant %_arr_18_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_18_uint_128 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_18 = OpTypePointer UniformConstant %18
+%26 = OpTypeSampler
+%_ptr_UniformConstant_26 = OpTypePointer UniformConstant %26
+%g_sAniso = OpVariable %_ptr_UniformConstant_26 UniformConstant
+%28 = OpTypeSampledImage %18
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+%uint_0 = OpConstant %uint 0
+%bool = OpTypeBool
+%_runtimearr_uint = OpTypeRuntimeArray %uint
+%_struct_34 = OpTypeStruct %uint %_runtimearr_uint
+%_ptr_StorageBuffer__struct_34 = OpTypePointer StorageBuffer %_struct_34
+%10 = OpVariable %_ptr_StorageBuffer__struct_34 StorageBuffer
+%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+%uint_10 = OpConstant %uint 10
+%uint_4 = OpConstant %uint 4
+%uint_1 = OpConstant %uint 1
+%uint_23 = OpConstant %uint 23
+%uint_2 = OpConstant %uint 2
+%uint_9 = OpConstant %uint 9
+%uint_3 = OpConstant %uint 3
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+%v4uint = OpTypeVector %uint 4
+%uint_5 = OpConstant %uint 5
+%uint_6 = OpConstant %uint 6
+%uint_7 = OpConstant %uint 7
+%uint_8 = OpConstant %uint 8
+%50 = OpConstantNull %v4float
+%68 = OpTypeFunction %void %uint %uint %uint %uint
+%74 = OpVariable %_ptr_StorageBuffer__struct_34 StorageBuffer
+%uint_82 = OpConstant %uint 82
+)";
+
+  const std::string func_before =
+      R"(%MainPs = OpFunction %void None %3
+%5 = OpLabel
+%53 = OpLoad %v2float %i_vTextureCoords
+%63 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%64 = OpLoad %uint %63
+%65 = OpAccessChain %_ptr_UniformConstant_20 %g_tColor %64
+%67 = OpLoad %35 %g_sAniso
+%78 = OpLoad %20 %65
+%79 = OpSampledImage %39 %78 %67
+%71 = OpImageSampleImplicitLod %v4float %79 %53
+OpStore %_entryPointOutput_vColor %71
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string func_after =
+      R"(%MainPs = OpFunction %void None %12
+%51 = OpLabel
+%52 = OpLoad %v2float %i_vTextureCoords
+%53 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%54 = OpLoad %uint %53
+%55 = OpAccessChain %_ptr_UniformConstant_18 %g_tColor %54
+%56 = OpLoad %26 %g_sAniso
+%57 = OpLoad %18 %55
+%58 = OpSampledImage %28 %57 %56
+%60 = OpULessThan %bool %54 %uint_128
+OpSelectionMerge %61 None
+OpBranchConditional %60 %62 %63
+%62 = OpLabel
+%64 = OpLoad %18 %55
+%65 = OpSampledImage %28 %64 %56
+%66 = OpImageSampleImplicitLod %v4float %65 %52
+OpBranch %61
+%63 = OpLabel
+%105 = OpFunctionCall %void %67 %uint_82 %uint_0 %54 %uint_128
+OpBranch %61
+%61 = OpLabel
+%106 = OpPhi %v4float %66 %62 %50 %63
+OpStore %_entryPointOutput_vColor %106
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string output_func =
+      R"(%67 = OpFunction %void None %68
+%69 = OpFunctionParameter %uint
+%70 = OpFunctionParameter %uint
+%71 = OpFunctionParameter %uint
+%72 = OpFunctionParameter %uint
+%73 = OpLabel
+%75 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_0
+%76 = OpAtomicIAdd %uint %75 %uint_4 %uint_0 %uint_9
+%77 = OpIAdd %uint %76 %uint_9
+%78 = OpArrayLength %uint %74 1
+%79 = OpULessThanEqual %bool %77 %78
+OpSelectionMerge %80 None
+OpBranchConditional %79 %81 %80
+%81 = OpLabel
+%82 = OpIAdd %uint %76 %uint_0
+%83 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %82
+OpStore %83 %uint_9
+%84 = OpIAdd %uint %76 %uint_1
+%85 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %84
+OpStore %85 %uint_23
+%86 = OpIAdd %uint %76 %uint_2
+%87 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %86
+OpStore %87 %69
+%88 = OpIAdd %uint %76 %uint_3
+%89 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %88
+OpStore %89 %uint_4
+%90 = OpLoad %v4float %gl_FragCoord
+%91 = OpBitcast %v4uint %90
+%92 = OpCompositeExtract %uint %91 0
+%93 = OpIAdd %uint %76 %uint_4
+%94 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %93
+OpStore %94 %92
+%95 = OpCompositeExtract %uint %91 1
+%96 = OpIAdd %uint %76 %uint_5
+%97 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %96
+OpStore %97 %95
+%98 = OpIAdd %uint %76 %uint_6
+%99 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %98
+OpStore %99 %70
+%100 = OpIAdd %uint %76 %uint_7
+%101 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %100
+OpStore %101 %71
+%102 = OpIAdd %uint %76 %uint_8
+%103 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %102
+OpStore %103 %72
+OpBranch %80
+%80 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<InstBindlessCheckPass>(
+      defs_before + func_before, defs_after + func_after + output_func, true,
+      true);
+}
+
+TEST_F(InstBindlessTest, InstrumentOpImage) {
+  // This test verifies that the pass will correctly instrument shader
+  // using OpImage. This test was created by editing the SPIR-V
+  // from the Simple test.
+
+  const std::string defs_before =
+      R"(OpCapability Shader
+OpCapability StorageImageReadWithoutFormat
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 3
+OpDecorate %g_tColor Binding 0
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%v2int = OpTypeVector %int 2
+%int_0 = OpConstant %int 0
+%20 = OpTypeImage %float 2D 0 0 0 0 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%39 = OpTypeSampledImage %20
+%_arr_39_uint_128 = OpTypeArray %39 %uint_128
+%_ptr_UniformConstant__arr_39_uint_128 = OpTypePointer UniformConstant %_arr_39_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_39_uint_128 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_39 = OpTypePointer UniformConstant %39
+%_ptr_Input_v2int = OpTypePointer Input %v2int
+%i_vTextureCoords = OpVariable %_ptr_Input_v2int Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+  const std::string defs_after =
+      R"(OpCapability Shader
+OpCapability StorageImageReadWithoutFormat
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 3
+OpDecorate %g_tColor Binding 0
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+OpDecorate %_struct_51 Block
+OpMemberDecorate %_struct_51 0 Offset 0
+OpMemberDecorate %_struct_51 1 Offset 4
+OpDecorate %53 DescriptorSet 7
+OpDecorate %53 Binding 0
+OpDecorate %gl_FragCoord BuiltIn FragCoord
+%void = OpTypeVoid
+%9 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%v2int = OpTypeVector %int 2
+%int_0 = OpConstant %int 0
+%15 = OpTypeImage %float 2D 0 0 0 0 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%18 = OpTypeSampledImage %15
+%_arr_18_uint_128 = OpTypeArray %18 %uint_128
+%_ptr_UniformConstant__arr_18_uint_128 = OpTypePointer UniformConstant %_arr_18_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_18_uint_128 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_18 = OpTypePointer UniformConstant %18
+%_ptr_Input_v2int = OpTypePointer Input %v2int
+%i_vTextureCoords = OpVariable %_ptr_Input_v2int Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+%uint_0 = OpConstant %uint 0
+%bool = OpTypeBool
+%44 = OpTypeFunction %void %uint %uint %uint %uint
+%_runtimearr_uint = OpTypeRuntimeArray %uint
+%_struct_51 = OpTypeStruct %uint %_runtimearr_uint
+%_ptr_StorageBuffer__struct_51 = OpTypePointer StorageBuffer %_struct_51
+%53 = OpVariable %_ptr_StorageBuffer__struct_51 StorageBuffer
+%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+%uint_9 = OpConstant %uint 9
+%uint_4 = OpConstant %uint 4
+%uint_1 = OpConstant %uint 1
+%uint_23 = OpConstant %uint 23
+%uint_2 = OpConstant %uint 2
+%uint_3 = OpConstant %uint 3
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+%v4uint = OpTypeVector %uint 4
+%uint_5 = OpConstant %uint 5
+%uint_6 = OpConstant %uint 6
+%uint_7 = OpConstant %uint 7
+%uint_8 = OpConstant %uint 8
+%uint_51 = OpConstant %uint 51
+%99 = OpConstantNull %v4float
+)";
+
+  const std::string func_before =
+      R"(%MainPs = OpFunction %void None %3
+%5 = OpLabel
+%53 = OpLoad %v2int %i_vTextureCoords
+%63 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%64 = OpLoad %uint %63
+%65 = OpAccessChain %_ptr_UniformConstant_39 %g_tColor %64
+%66 = OpLoad %39 %65
+%75 = OpImage %20 %66
+%71 = OpImageRead %v4float %75 %53
+OpStore %_entryPointOutput_vColor %71
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string func_after =
+      R"(%MainPs = OpFunction %void None %9
+%26 = OpLabel
+%27 = OpLoad %v2int %i_vTextureCoords
+%28 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%29 = OpLoad %uint %28
+%30 = OpAccessChain %_ptr_UniformConstant_18 %g_tColor %29
+%31 = OpLoad %18 %30
+%32 = OpImage %15 %31
+%36 = OpULessThan %bool %29 %uint_128
+OpSelectionMerge %37 None
+OpBranchConditional %36 %38 %39
+%38 = OpLabel
+%40 = OpLoad %18 %30
+%41 = OpImage %15 %40
+%42 = OpImageRead %v4float %41 %27
+OpBranch %37
+%39 = OpLabel
+%98 = OpFunctionCall %void %43 %uint_51 %uint_0 %29 %uint_128
+OpBranch %37
+%37 = OpLabel
+%100 = OpPhi %v4float %42 %38 %99 %39
+OpStore %_entryPointOutput_vColor %100
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string output_func =
+      R"(%43 = OpFunction %void None %44
+%45 = OpFunctionParameter %uint
+%46 = OpFunctionParameter %uint
+%47 = OpFunctionParameter %uint
+%48 = OpFunctionParameter %uint
+%49 = OpLabel
+%55 = OpAccessChain %_ptr_StorageBuffer_uint %53 %uint_0
+%58 = OpAtomicIAdd %uint %55 %uint_4 %uint_0 %uint_9
+%59 = OpIAdd %uint %58 %uint_9
+%60 = OpArrayLength %uint %53 1
+%61 = OpULessThanEqual %bool %59 %60
+OpSelectionMerge %62 None
+OpBranchConditional %61 %63 %62
+%63 = OpLabel
+%64 = OpIAdd %uint %58 %uint_0
+%66 = OpAccessChain %_ptr_StorageBuffer_uint %53 %uint_1 %64
+OpStore %66 %uint_9
+%68 = OpIAdd %uint %58 %uint_1
+%69 = OpAccessChain %_ptr_StorageBuffer_uint %53 %uint_1 %68
+OpStore %69 %uint_23
+%71 = OpIAdd %uint %58 %uint_2
+%72 = OpAccessChain %_ptr_StorageBuffer_uint %53 %uint_1 %71
+OpStore %72 %45
+%74 = OpIAdd %uint %58 %uint_3
+%75 = OpAccessChain %_ptr_StorageBuffer_uint %53 %uint_1 %74
+OpStore %75 %uint_4
+%78 = OpLoad %v4float %gl_FragCoord
+%80 = OpBitcast %v4uint %78
+%81 = OpCompositeExtract %uint %80 0
+%82 = OpIAdd %uint %58 %uint_4
+%83 = OpAccessChain %_ptr_StorageBuffer_uint %53 %uint_1 %82
+OpStore %83 %81
+%84 = OpCompositeExtract %uint %80 1
+%86 = OpIAdd %uint %58 %uint_5
+%87 = OpAccessChain %_ptr_StorageBuffer_uint %53 %uint_1 %86
+OpStore %87 %84
+%89 = OpIAdd %uint %58 %uint_6
+%90 = OpAccessChain %_ptr_StorageBuffer_uint %53 %uint_1 %89
+OpStore %90 %46
+%92 = OpIAdd %uint %58 %uint_7
+%93 = OpAccessChain %_ptr_StorageBuffer_uint %53 %uint_1 %92
+OpStore %93 %47
+%95 = OpIAdd %uint %58 %uint_8
+%96 = OpAccessChain %_ptr_StorageBuffer_uint %53 %uint_1 %95
+OpStore %96 %48
+OpBranch %62
+%62 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<InstBindlessCheckPass>(
+      defs_before + func_before, defs_after + func_after + output_func, true,
+      true);
+}
+
+TEST_F(InstBindlessTest, InstrumentSampledImage) {
+  // This test verifies that the pass will correctly instrument shader
+  // using sampled image. This test was created by editing the SPIR-V
+  // from the Simple test.
+
+  const std::string defs_before =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 3
+OpDecorate %g_tColor Binding 0
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%20 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%39 = OpTypeSampledImage %20
+%_arr_39_uint_128 = OpTypeArray %39 %uint_128
+%_ptr_UniformConstant__arr_39_uint_128 = OpTypePointer UniformConstant %_arr_39_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_39_uint_128 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_39 = OpTypePointer UniformConstant %39
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+  const std::string defs_after =
+      R"(OpCapability Shader
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 3
+OpDecorate %g_tColor Binding 0
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+OpDecorate %_struct_49 Block
+OpMemberDecorate %_struct_49 0 Offset 0
+OpMemberDecorate %_struct_49 1 Offset 4
+OpDecorate %51 DescriptorSet 7
+OpDecorate %51 Binding 0
+OpDecorate %gl_FragCoord BuiltIn FragCoord
+%void = OpTypeVoid
+%9 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%15 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%18 = OpTypeSampledImage %15
+%_arr_18_uint_128 = OpTypeArray %18 %uint_128
+%_ptr_UniformConstant__arr_18_uint_128 = OpTypePointer UniformConstant %_arr_18_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_18_uint_128 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_18 = OpTypePointer UniformConstant %18
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+%uint_0 = OpConstant %uint 0
+%bool = OpTypeBool
+%42 = OpTypeFunction %void %uint %uint %uint %uint
+%_runtimearr_uint = OpTypeRuntimeArray %uint
+%_struct_49 = OpTypeStruct %uint %_runtimearr_uint
+%_ptr_StorageBuffer__struct_49 = OpTypePointer StorageBuffer %_struct_49
+%51 = OpVariable %_ptr_StorageBuffer__struct_49 StorageBuffer
+%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+%uint_9 = OpConstant %uint 9
+%uint_4 = OpConstant %uint 4
+%uint_1 = OpConstant %uint 1
+%uint_23 = OpConstant %uint 23
+%uint_2 = OpConstant %uint 2
+%uint_3 = OpConstant %uint 3
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+%v4uint = OpTypeVector %uint 4
+%uint_5 = OpConstant %uint 5
+%uint_6 = OpConstant %uint 6
+%uint_7 = OpConstant %uint 7
+%uint_8 = OpConstant %uint 8
+%uint_49 = OpConstant %uint 49
+%97 = OpConstantNull %v4float
+)";
+
+  const std::string func_before =
+      R"(%MainPs = OpFunction %void None %3
+%5 = OpLabel
+%53 = OpLoad %v2float %i_vTextureCoords
+%63 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%64 = OpLoad %uint %63
+%65 = OpAccessChain %_ptr_UniformConstant_39 %g_tColor %64
+%66 = OpLoad %39 %65
+%71 = OpImageSampleImplicitLod %v4float %66 %53
+OpStore %_entryPointOutput_vColor %71
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string func_after =
+      R"(%MainPs = OpFunction %void None %9
+%26 = OpLabel
+%27 = OpLoad %v2float %i_vTextureCoords
+%28 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%29 = OpLoad %uint %28
+%30 = OpAccessChain %_ptr_UniformConstant_18 %g_tColor %29
+%31 = OpLoad %18 %30
+%35 = OpULessThan %bool %29 %uint_128
+OpSelectionMerge %36 None
+OpBranchConditional %35 %37 %38
+%37 = OpLabel
+%39 = OpLoad %18 %30
+%40 = OpImageSampleImplicitLod %v4float %39 %27
+OpBranch %36
+%38 = OpLabel
+%96 = OpFunctionCall %void %41 %uint_49 %uint_0 %29 %uint_128
+OpBranch %36
+%36 = OpLabel
+%98 = OpPhi %v4float %40 %37 %97 %38
+OpStore %_entryPointOutput_vColor %98
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string output_func =
+      R"(%41 = OpFunction %void None %42
+%43 = OpFunctionParameter %uint
+%44 = OpFunctionParameter %uint
+%45 = OpFunctionParameter %uint
+%46 = OpFunctionParameter %uint
+%47 = OpLabel
+%53 = OpAccessChain %_ptr_StorageBuffer_uint %51 %uint_0
+%56 = OpAtomicIAdd %uint %53 %uint_4 %uint_0 %uint_9
+%57 = OpIAdd %uint %56 %uint_9
+%58 = OpArrayLength %uint %51 1
+%59 = OpULessThanEqual %bool %57 %58
+OpSelectionMerge %60 None
+OpBranchConditional %59 %61 %60
+%61 = OpLabel
+%62 = OpIAdd %uint %56 %uint_0
+%64 = OpAccessChain %_ptr_StorageBuffer_uint %51 %uint_1 %62
+OpStore %64 %uint_9
+%66 = OpIAdd %uint %56 %uint_1
+%67 = OpAccessChain %_ptr_StorageBuffer_uint %51 %uint_1 %66
+OpStore %67 %uint_23
+%69 = OpIAdd %uint %56 %uint_2
+%70 = OpAccessChain %_ptr_StorageBuffer_uint %51 %uint_1 %69
+OpStore %70 %43
+%72 = OpIAdd %uint %56 %uint_3
+%73 = OpAccessChain %_ptr_StorageBuffer_uint %51 %uint_1 %72
+OpStore %73 %uint_4
+%76 = OpLoad %v4float %gl_FragCoord
+%78 = OpBitcast %v4uint %76
+%79 = OpCompositeExtract %uint %78 0
+%80 = OpIAdd %uint %56 %uint_4
+%81 = OpAccessChain %_ptr_StorageBuffer_uint %51 %uint_1 %80
+OpStore %81 %79
+%82 = OpCompositeExtract %uint %78 1
+%84 = OpIAdd %uint %56 %uint_5
+%85 = OpAccessChain %_ptr_StorageBuffer_uint %51 %uint_1 %84
+OpStore %85 %82
+%87 = OpIAdd %uint %56 %uint_6
+%88 = OpAccessChain %_ptr_StorageBuffer_uint %51 %uint_1 %87
+OpStore %88 %44
+%90 = OpIAdd %uint %56 %uint_7
+%91 = OpAccessChain %_ptr_StorageBuffer_uint %51 %uint_1 %90
+OpStore %91 %45
+%93 = OpIAdd %uint %56 %uint_8
+%94 = OpAccessChain %_ptr_StorageBuffer_uint %51 %uint_1 %93
+OpStore %94 %46
+OpBranch %60
+%60 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<InstBindlessCheckPass>(
+      defs_before + func_before, defs_after + func_after + output_func, true,
+      true);
+}
+
+TEST_F(InstBindlessTest, InstrumentImageWrite) {
+  // This test verifies that the pass will correctly instrument shader
+  // doing bindless image write. This test was created by editing the SPIR-V
+  // from the Simple test.
+
+  const std::string defs_before =
+      R"(OpCapability Shader
+OpCapability StorageImageWriteWithoutFormat
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 3
+OpDecorate %g_tColor Binding 0
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%v2int = OpTypeVector %int 2
+%int_0 = OpConstant %int 0
+%20 = OpTypeImage %float 2D 0 0 0 0 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%80 = OpConstantNull %v4float
+%_arr_20_uint_128 = OpTypeArray %20 %uint_128
+%_ptr_UniformConstant__arr_20_uint_128 = OpTypePointer UniformConstant %_arr_20_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_20_uint_128 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_20 = OpTypePointer UniformConstant %20
+%_ptr_Input_v2int = OpTypePointer Input %v2int
+%i_vTextureCoords = OpVariable %_ptr_Input_v2int Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+  const std::string defs_after =
+      R"(OpCapability Shader
+OpCapability StorageImageWriteWithoutFormat
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 3
+OpDecorate %g_tColor Binding 0
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+OpDecorate %_struct_48 Block
+OpMemberDecorate %_struct_48 0 Offset 0
+OpMemberDecorate %_struct_48 1 Offset 4
+OpDecorate %50 DescriptorSet 7
+OpDecorate %50 Binding 0
+OpDecorate %gl_FragCoord BuiltIn FragCoord
+%void = OpTypeVoid
+%9 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%v2int = OpTypeVector %int 2
+%int_0 = OpConstant %int 0
+%16 = OpTypeImage %float 2D 0 0 0 0 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%19 = OpConstantNull %v4float
+%_arr_16_uint_128 = OpTypeArray %16 %uint_128
+%_ptr_UniformConstant__arr_16_uint_128 = OpTypePointer UniformConstant %_arr_16_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_16_uint_128 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_16 = OpTypePointer UniformConstant %16
+%_ptr_Input_v2int = OpTypePointer Input %v2int
+%i_vTextureCoords = OpVariable %_ptr_Input_v2int Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+%uint_0 = OpConstant %uint 0
+%bool = OpTypeBool
+%41 = OpTypeFunction %void %uint %uint %uint %uint
+%_runtimearr_uint = OpTypeRuntimeArray %uint
+%_struct_48 = OpTypeStruct %uint %_runtimearr_uint
+%_ptr_StorageBuffer__struct_48 = OpTypePointer StorageBuffer %_struct_48
+%50 = OpVariable %_ptr_StorageBuffer__struct_48 StorageBuffer
+%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+%uint_9 = OpConstant %uint 9
+%uint_4 = OpConstant %uint 4
+%uint_1 = OpConstant %uint 1
+%uint_23 = OpConstant %uint 23
+%uint_2 = OpConstant %uint 2
+%uint_3 = OpConstant %uint 3
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+%v4uint = OpTypeVector %uint 4
+%uint_5 = OpConstant %uint 5
+%uint_6 = OpConstant %uint 6
+%uint_7 = OpConstant %uint 7
+%uint_8 = OpConstant %uint 8
+%uint_51 = OpConstant %uint 51
+)";
+
+  const std::string func_before =
+      R"(%MainPs = OpFunction %void None %3
+%5 = OpLabel
+%53 = OpLoad %v2int %i_vTextureCoords
+%63 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%64 = OpLoad %uint %63
+%65 = OpAccessChain %_ptr_UniformConstant_20 %g_tColor %64
+%66 = OpLoad %20 %65
+OpImageWrite %66 %53 %80
+OpStore %_entryPointOutput_vColor %80
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string func_after =
+      R"(%MainPs = OpFunction %void None %9
+%27 = OpLabel
+%28 = OpLoad %v2int %i_vTextureCoords
+%29 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%30 = OpLoad %uint %29
+%31 = OpAccessChain %_ptr_UniformConstant_16 %g_tColor %30
+%32 = OpLoad %16 %31
+%35 = OpULessThan %bool %30 %uint_128
+OpSelectionMerge %36 None
+OpBranchConditional %35 %37 %38
+%37 = OpLabel
+%39 = OpLoad %16 %31
+OpImageWrite %39 %28 %19
+OpBranch %36
+%38 = OpLabel
+%95 = OpFunctionCall %void %40 %uint_51 %uint_0 %30 %uint_128
+OpBranch %36
+%36 = OpLabel
+OpStore %_entryPointOutput_vColor %19
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string output_func =
+      R"(%40 = OpFunction %void None %41
+%42 = OpFunctionParameter %uint
+%43 = OpFunctionParameter %uint
+%44 = OpFunctionParameter %uint
+%45 = OpFunctionParameter %uint
+%46 = OpLabel
+%52 = OpAccessChain %_ptr_StorageBuffer_uint %50 %uint_0
+%55 = OpAtomicIAdd %uint %52 %uint_4 %uint_0 %uint_9
+%56 = OpIAdd %uint %55 %uint_9
+%57 = OpArrayLength %uint %50 1
+%58 = OpULessThanEqual %bool %56 %57
+OpSelectionMerge %59 None
+OpBranchConditional %58 %60 %59
+%60 = OpLabel
+%61 = OpIAdd %uint %55 %uint_0
+%63 = OpAccessChain %_ptr_StorageBuffer_uint %50 %uint_1 %61
+OpStore %63 %uint_9
+%65 = OpIAdd %uint %55 %uint_1
+%66 = OpAccessChain %_ptr_StorageBuffer_uint %50 %uint_1 %65
+OpStore %66 %uint_23
+%68 = OpIAdd %uint %55 %uint_2
+%69 = OpAccessChain %_ptr_StorageBuffer_uint %50 %uint_1 %68
+OpStore %69 %42
+%71 = OpIAdd %uint %55 %uint_3
+%72 = OpAccessChain %_ptr_StorageBuffer_uint %50 %uint_1 %71
+OpStore %72 %uint_4
+%75 = OpLoad %v4float %gl_FragCoord
+%77 = OpBitcast %v4uint %75
+%78 = OpCompositeExtract %uint %77 0
+%79 = OpIAdd %uint %55 %uint_4
+%80 = OpAccessChain %_ptr_StorageBuffer_uint %50 %uint_1 %79
+OpStore %80 %78
+%81 = OpCompositeExtract %uint %77 1
+%83 = OpIAdd %uint %55 %uint_5
+%84 = OpAccessChain %_ptr_StorageBuffer_uint %50 %uint_1 %83
+OpStore %84 %81
+%86 = OpIAdd %uint %55 %uint_6
+%87 = OpAccessChain %_ptr_StorageBuffer_uint %50 %uint_1 %86
+OpStore %87 %43
+%89 = OpIAdd %uint %55 %uint_7
+%90 = OpAccessChain %_ptr_StorageBuffer_uint %50 %uint_1 %89
+OpStore %90 %44
+%92 = OpIAdd %uint %55 %uint_8
+%93 = OpAccessChain %_ptr_StorageBuffer_uint %50 %uint_1 %92
+OpStore %93 %45
+OpBranch %59
+%59 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<InstBindlessCheckPass>(
+      defs_before + func_before, defs_after + func_after + output_func, true,
+      true);
+}
+
+TEST_F(InstBindlessTest, InstrumentVertexSimple) {
+  // This test verifies that the pass will correctly instrument shader
+  // doing bindless image write. This test was created by editing the SPIR-V
+  // from the Simple test.
+
+  const std::string defs_before =
+      R"(OpCapability Shader
+OpCapability Sampled1D
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main" %_ %coords2D
+OpSource GLSL 450
+OpName %main "main"
+OpName %lod "lod"
+OpName %coords1D "coords1D"
+OpName %gl_PerVertex "gl_PerVertex"
+OpMemberName %gl_PerVertex 0 "gl_Position"
+OpMemberName %gl_PerVertex 1 "gl_PointSize"
+OpMemberName %gl_PerVertex 2 "gl_ClipDistance"
+OpMemberName %gl_PerVertex 3 "gl_CullDistance"
+OpName %_ ""
+OpName %texSampler1D "texSampler1D"
+OpName %foo "foo"
+OpMemberName %foo 0 "g_idx"
+OpName %__0 ""
+OpName %coords2D "coords2D"
+OpMemberDecorate %gl_PerVertex 0 BuiltIn Position
+OpMemberDecorate %gl_PerVertex 1 BuiltIn PointSize
+OpMemberDecorate %gl_PerVertex 2 BuiltIn ClipDistance
+OpMemberDecorate %gl_PerVertex 3 BuiltIn CullDistance
+OpDecorate %gl_PerVertex Block
+OpDecorate %texSampler1D DescriptorSet 0
+OpDecorate %texSampler1D Binding 3
+OpMemberDecorate %foo 0 Offset 0
+OpDecorate %foo Block
+OpDecorate %__0 DescriptorSet 0
+OpDecorate %__0 Binding 5
+OpDecorate %coords2D Location 0
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Function_float = OpTypePointer Function %float
+%float_3 = OpConstant %float 3
+%float_1_78900003 = OpConstant %float 1.78900003
+%v4float = OpTypeVector %float 4
+%uint = OpTypeInt 32 0
+%uint_1 = OpConstant %uint 1
+%_arr_float_uint_1 = OpTypeArray %float %uint_1
+%gl_PerVertex = OpTypeStruct %v4float %float %_arr_float_uint_1 %_arr_float_uint_1
+%_ptr_Output_gl_PerVertex = OpTypePointer Output %gl_PerVertex
+%_ = OpVariable %_ptr_Output_gl_PerVertex Output
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%21 = OpTypeImage %float 1D 0 0 0 1 Unknown
+%22 = OpTypeSampledImage %21
+%uint_128 = OpConstant %uint 128
+%_arr_22_uint_128 = OpTypeArray %22 %uint_128
+%_ptr_UniformConstant__arr_22_uint_128 = OpTypePointer UniformConstant %_arr_22_uint_128
+%texSampler1D = OpVariable %_ptr_UniformConstant__arr_22_uint_128 UniformConstant
+%foo = OpTypeStruct %int
+%_ptr_Uniform_foo = OpTypePointer Uniform %foo
+%__0 = OpVariable %_ptr_Uniform_foo Uniform
+%_ptr_Uniform_int = OpTypePointer Uniform %int
+%_ptr_UniformConstant_22 = OpTypePointer UniformConstant %22
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%v2float = OpTypeVector %float 2
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%coords2D = OpVariable %_ptr_Input_v2float Input
+)";
+
+  const std::string defs_after =
+      R"(OpCapability Shader
+OpCapability Sampled1D
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main" %_ %coords2D %gl_VertexID %gl_InstanceID
+OpSource GLSL 450
+OpName %main "main"
+OpName %lod "lod"
+OpName %coords1D "coords1D"
+OpName %gl_PerVertex "gl_PerVertex"
+OpMemberName %gl_PerVertex 0 "gl_Position"
+OpMemberName %gl_PerVertex 1 "gl_PointSize"
+OpMemberName %gl_PerVertex 2 "gl_ClipDistance"
+OpMemberName %gl_PerVertex 3 "gl_CullDistance"
+OpName %_ ""
+OpName %texSampler1D "texSampler1D"
+OpName %foo "foo"
+OpMemberName %foo 0 "g_idx"
+OpName %__0 ""
+OpName %coords2D "coords2D"
+OpMemberDecorate %gl_PerVertex 0 BuiltIn Position
+OpMemberDecorate %gl_PerVertex 1 BuiltIn PointSize
+OpMemberDecorate %gl_PerVertex 2 BuiltIn ClipDistance
+OpMemberDecorate %gl_PerVertex 3 BuiltIn CullDistance
+OpDecorate %gl_PerVertex Block
+OpDecorate %texSampler1D DescriptorSet 0
+OpDecorate %texSampler1D Binding 3
+OpMemberDecorate %foo 0 Offset 0
+OpDecorate %foo Block
+OpDecorate %__0 DescriptorSet 0
+OpDecorate %__0 Binding 5
+OpDecorate %coords2D Location 0
+OpDecorate %_struct_61 Block
+OpMemberDecorate %_struct_61 0 Offset 0
+OpMemberDecorate %_struct_61 1 Offset 4
+OpDecorate %63 DescriptorSet 7
+OpDecorate %63 Binding 0
+OpDecorate %gl_VertexID BuiltIn VertexId
+OpDecorate %gl_InstanceID BuiltIn InstanceId
+%void = OpTypeVoid
+%12 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Function_float = OpTypePointer Function %float
+%float_3 = OpConstant %float 3
+%float_1_78900003 = OpConstant %float 1.78900003
+%v4float = OpTypeVector %float 4
+%uint = OpTypeInt 32 0
+%uint_1 = OpConstant %uint 1
+%_arr_float_uint_1 = OpTypeArray %float %uint_1
+%gl_PerVertex = OpTypeStruct %v4float %float %_arr_float_uint_1 %_arr_float_uint_1
+%_ptr_Output_gl_PerVertex = OpTypePointer Output %gl_PerVertex
+%_ = OpVariable %_ptr_Output_gl_PerVertex Output
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%24 = OpTypeImage %float 1D 0 0 0 1 Unknown
+%25 = OpTypeSampledImage %24
+%uint_128 = OpConstant %uint 128
+%_arr_25_uint_128 = OpTypeArray %25 %uint_128
+%_ptr_UniformConstant__arr_25_uint_128 = OpTypePointer UniformConstant %_arr_25_uint_128
+%texSampler1D = OpVariable %_ptr_UniformConstant__arr_25_uint_128 UniformConstant
+%foo = OpTypeStruct %int
+%_ptr_Uniform_foo = OpTypePointer Uniform %foo
+%__0 = OpVariable %_ptr_Uniform_foo Uniform
+%_ptr_Uniform_int = OpTypePointer Uniform %int
+%_ptr_UniformConstant_25 = OpTypePointer UniformConstant %25
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%v2float = OpTypeVector %float 2
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%coords2D = OpVariable %_ptr_Input_v2float Input
+%uint_0 = OpConstant %uint 0
+%bool = OpTypeBool
+%54 = OpTypeFunction %void %uint %uint %uint %uint
+%_runtimearr_uint = OpTypeRuntimeArray %uint
+%_struct_61 = OpTypeStruct %uint %_runtimearr_uint
+%_ptr_StorageBuffer__struct_61 = OpTypePointer StorageBuffer %_struct_61
+%63 = OpVariable %_ptr_StorageBuffer__struct_61 StorageBuffer
+%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+%uint_9 = OpConstant %uint 9
+%uint_4 = OpConstant %uint 4
+%uint_23 = OpConstant %uint 23
+%uint_2 = OpConstant %uint 2
+%uint_3 = OpConstant %uint 3
+%_ptr_Input_uint = OpTypePointer Input %uint
+%gl_VertexID = OpVariable %_ptr_Input_uint Input
+%gl_InstanceID = OpVariable %_ptr_Input_uint Input
+%uint_5 = OpConstant %uint 5
+%uint_6 = OpConstant %uint 6
+%uint_7 = OpConstant %uint 7
+%uint_8 = OpConstant %uint 8
+%uint_74 = OpConstant %uint 74
+%106 = OpConstantNull %v4float
+)";
+
+  const std::string func_before =
+      R"(%main = OpFunction %void None %3
+%5 = OpLabel
+%lod = OpVariable %_ptr_Function_float Function
+%coords1D = OpVariable %_ptr_Function_float Function
+OpStore %lod %float_3
+OpStore %coords1D %float_1_78900003
+%31 = OpAccessChain %_ptr_Uniform_int %__0 %int_0
+%32 = OpLoad %int %31
+%34 = OpAccessChain %_ptr_UniformConstant_22 %texSampler1D %32
+%35 = OpLoad %22 %34
+%36 = OpLoad %float %coords1D
+%37 = OpLoad %float %lod
+%38 = OpImageSampleExplicitLod %v4float %35 %36 Lod %37
+%40 = OpAccessChain %_ptr_Output_v4float %_ %int_0
+OpStore %40 %38
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string func_after =
+      R"(%main = OpFunction %void None %12
+%35 = OpLabel
+%lod = OpVariable %_ptr_Function_float Function
+%coords1D = OpVariable %_ptr_Function_float Function
+OpStore %lod %float_3
+OpStore %coords1D %float_1_78900003
+%36 = OpAccessChain %_ptr_Uniform_int %__0 %int_0
+%37 = OpLoad %int %36
+%38 = OpAccessChain %_ptr_UniformConstant_25 %texSampler1D %37
+%39 = OpLoad %25 %38
+%40 = OpLoad %float %coords1D
+%41 = OpLoad %float %lod
+%46 = OpULessThan %bool %37 %uint_128
+OpSelectionMerge %47 None
+OpBranchConditional %46 %48 %49
+%48 = OpLabel
+%50 = OpLoad %25 %38
+%51 = OpImageSampleExplicitLod %v4float %50 %40 Lod %41
+OpBranch %47
+%49 = OpLabel
+%52 = OpBitcast %uint %37
+%105 = OpFunctionCall %void %53 %uint_74 %uint_0 %52 %uint_128
+OpBranch %47
+%47 = OpLabel
+%107 = OpPhi %v4float %51 %48 %106 %49
+%43 = OpAccessChain %_ptr_Output_v4float %_ %int_0
+OpStore %43 %107
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string output_func =
+      R"(%53 = OpFunction %void None %54
+%55 = OpFunctionParameter %uint
+%56 = OpFunctionParameter %uint
+%57 = OpFunctionParameter %uint
+%58 = OpFunctionParameter %uint
+%59 = OpLabel
+%65 = OpAccessChain %_ptr_StorageBuffer_uint %63 %uint_0
+%68 = OpAtomicIAdd %uint %65 %uint_4 %uint_0 %uint_9
+%69 = OpIAdd %uint %68 %uint_9
+%70 = OpArrayLength %uint %63 1
+%71 = OpULessThanEqual %bool %69 %70
+OpSelectionMerge %72 None
+OpBranchConditional %71 %73 %72
+%73 = OpLabel
+%74 = OpIAdd %uint %68 %uint_0
+%75 = OpAccessChain %_ptr_StorageBuffer_uint %63 %uint_1 %74
+OpStore %75 %uint_9
+%77 = OpIAdd %uint %68 %uint_1
+%78 = OpAccessChain %_ptr_StorageBuffer_uint %63 %uint_1 %77
+OpStore %78 %uint_23
+%80 = OpIAdd %uint %68 %uint_2
+%81 = OpAccessChain %_ptr_StorageBuffer_uint %63 %uint_1 %80
+OpStore %81 %55
+%83 = OpIAdd %uint %68 %uint_3
+%84 = OpAccessChain %_ptr_StorageBuffer_uint %63 %uint_1 %83
+OpStore %84 %uint_0
+%87 = OpLoad %uint %gl_VertexID
+%88 = OpIAdd %uint %68 %uint_4
+%89 = OpAccessChain %_ptr_StorageBuffer_uint %63 %uint_1 %88
+OpStore %89 %87
+%91 = OpLoad %uint %gl_InstanceID
+%93 = OpIAdd %uint %68 %uint_5
+%94 = OpAccessChain %_ptr_StorageBuffer_uint %63 %uint_1 %93
+OpStore %94 %91
+%96 = OpIAdd %uint %68 %uint_6
+%97 = OpAccessChain %_ptr_StorageBuffer_uint %63 %uint_1 %96
+OpStore %97 %56
+%99 = OpIAdd %uint %68 %uint_7
+%100 = OpAccessChain %_ptr_StorageBuffer_uint %63 %uint_1 %99
+OpStore %100 %57
+%102 = OpIAdd %uint %68 %uint_8
+%103 = OpAccessChain %_ptr_StorageBuffer_uint %63 %uint_1 %102
+OpStore %103 %58
+OpBranch %72
+%72 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<InstBindlessCheckPass>(
+      defs_before + func_before, defs_after + func_after + output_func, true,
+      true);
+}
+
+// TODO(greg-lunarg): Add tests to verify handling of these cases:
+//
+// TODO(greg-lunarg): Come up with cases to put here :)
+
+}  // namespace
+}  // namespace opt
+}  // namespace spvtools
diff --git a/test/opt/ir_builder.cpp b/test/opt/ir_builder.cpp
index 41d7f57..8231bdc 100644
--- a/test/opt/ir_builder.cpp
+++ b/test/opt/ir_builder.cpp
@@ -317,20 +317,20 @@
 
   InstructionBuilder builder(context.get(),
                              &*context->module()->begin()->begin()->begin());
-  EXPECT_NE(nullptr, builder.Add32BitUnsignedIntegerConstant(13));
-  EXPECT_NE(nullptr, builder.Add32BitSignedIntegerConstant(-1));
+  EXPECT_NE(nullptr, builder.GetUintConstant(13));
+  EXPECT_NE(nullptr, builder.GetSintConstant(-1));
 
   // Try adding the same constants again to make sure they aren't added.
-  EXPECT_NE(nullptr, builder.Add32BitUnsignedIntegerConstant(13));
-  EXPECT_NE(nullptr, builder.Add32BitSignedIntegerConstant(-1));
+  EXPECT_NE(nullptr, builder.GetUintConstant(13));
+  EXPECT_NE(nullptr, builder.GetSintConstant(-1));
 
   // Try adding different constants to make sure the type is reused.
-  EXPECT_NE(nullptr, builder.Add32BitUnsignedIntegerConstant(1));
-  EXPECT_NE(nullptr, builder.Add32BitSignedIntegerConstant(34));
+  EXPECT_NE(nullptr, builder.GetUintConstant(1));
+  EXPECT_NE(nullptr, builder.GetSintConstant(34));
 
   // Try adding 0 as both signed and unsigned.
-  EXPECT_NE(nullptr, builder.Add32BitUnsignedIntegerConstant(0));
-  EXPECT_NE(nullptr, builder.Add32BitSignedIntegerConstant(0));
+  EXPECT_NE(nullptr, builder.GetUintConstant(0));
+  EXPECT_NE(nullptr, builder.GetSintConstant(0));
 
   Match(text, context.get());
 }
@@ -362,25 +362,25 @@
 
   InstructionBuilder builder(context.get(),
                              &*context->module()->begin()->begin()->begin());
-  Instruction* const_1 = builder.Add32BitUnsignedIntegerConstant(13);
-  Instruction* const_2 = builder.Add32BitSignedIntegerConstant(-1);
+  Instruction* const_1 = builder.GetUintConstant(13);
+  Instruction* const_2 = builder.GetSintConstant(-1);
 
   EXPECT_NE(nullptr, const_1);
   EXPECT_NE(nullptr, const_2);
 
   // Try adding the same constants again to make sure they aren't added.
-  EXPECT_EQ(const_1, builder.Add32BitUnsignedIntegerConstant(13));
-  EXPECT_EQ(const_2, builder.Add32BitSignedIntegerConstant(-1));
+  EXPECT_EQ(const_1, builder.GetUintConstant(13));
+  EXPECT_EQ(const_2, builder.GetSintConstant(-1));
 
-  Instruction* const_3 = builder.Add32BitUnsignedIntegerConstant(1);
-  Instruction* const_4 = builder.Add32BitSignedIntegerConstant(34);
+  Instruction* const_3 = builder.GetUintConstant(1);
+  Instruction* const_4 = builder.GetSintConstant(34);
 
   // Try adding different constants to make sure the type is reused.
   EXPECT_NE(nullptr, const_3);
   EXPECT_NE(nullptr, const_4);
 
-  Instruction* const_5 = builder.Add32BitUnsignedIntegerConstant(0);
-  Instruction* const_6 = builder.Add32BitSignedIntegerConstant(0);
+  Instruction* const_5 = builder.GetUintConstant(0);
+  Instruction* const_6 = builder.GetSintConstant(0);
 
   // Try adding 0 as both signed and unsigned.
   EXPECT_NE(nullptr, const_5);
diff --git a/test/opt/loop_optimizations/peeling.cpp b/test/opt/loop_optimizations/peeling.cpp
index 7c6a84e..10d8add 100644
--- a/test/opt/loop_optimizations/peeling.cpp
+++ b/test/opt/loop_optimizations/peeling.cpp
@@ -132,7 +132,7 @@
     } else {
       InstructionBuilder builder(context.get(), &*f.begin());
       // Exit condition.
-      loop_count = builder.Add32BitSignedIntegerConstant(10);
+      loop_count = builder.GetSintConstant(10);
     }
 
     LoopPeeling peel(&*ld.begin(), loop_count);
@@ -494,7 +494,7 @@
 
     InstructionBuilder builder(context.get(), &*f.begin());
     // Exit condition.
-    Instruction* ten_cst = builder.Add32BitSignedIntegerConstant(10);
+    Instruction* ten_cst = builder.GetSintConstant(10);
 
     LoopPeeling peel(&*ld.begin(), ten_cst);
     EXPECT_TRUE(peel.CanPeelLoop());
@@ -548,7 +548,7 @@
 
     InstructionBuilder builder(context.get(), &*f.begin());
     // Exit condition.
-    Instruction* ten_cst = builder.Add32BitSignedIntegerConstant(10);
+    Instruction* ten_cst = builder.GetSintConstant(10);
 
     LoopPeeling peel(&*ld.begin(), ten_cst);
     EXPECT_TRUE(peel.CanPeelLoop());
@@ -604,7 +604,7 @@
 
     InstructionBuilder builder(context.get(), &*f.begin());
     // Exit condition.
-    Instruction* ten_cst = builder.Add32BitSignedIntegerConstant(10);
+    Instruction* ten_cst = builder.GetSintConstant(10);
 
     LoopPeeling peel(&*ld.begin(), ten_cst,
                      context->get_def_use_mgr()->GetDef(22));
@@ -657,7 +657,7 @@
 
     InstructionBuilder builder(context.get(), &*f.begin());
     // Exit condition.
-    Instruction* ten_cst = builder.Add32BitSignedIntegerConstant(10);
+    Instruction* ten_cst = builder.GetSintConstant(10);
 
     LoopPeeling peel(&*ld.begin(), ten_cst,
                      context->get_def_use_mgr()->GetDef(22));
@@ -918,7 +918,7 @@
     EXPECT_EQ(ld.NumLoops(), 1u);
     InstructionBuilder builder(context.get(), &*f.begin());
     // Exit condition.
-    Instruction* ten_cst = builder.Add32BitUnsignedIntegerConstant(10);
+    Instruction* ten_cst = builder.GetUintConstant(10);
 
     LoopPeeling peel(&*ld.begin(), ten_cst);
     EXPECT_TRUE(peel.CanPeelLoop());
@@ -968,7 +968,7 @@
 
     InstructionBuilder builder(context.get(), &*f.begin());
     // Exit condition.
-    Instruction* ten_cst = builder.Add32BitUnsignedIntegerConstant(10);
+    Instruction* ten_cst = builder.GetUintConstant(10);
 
     LoopPeeling peel(&*ld.begin(), ten_cst);
     EXPECT_TRUE(peel.CanPeelLoop());