Forráskód Böngészése

Updated spirv-tools.

Бранимир Караџић 5 éve
szülő
commit
88fdc62d2b
67 módosított fájl, 1155 hozzáadás és 387 törlés
  1. 1 4
      3rdparty/spirv-tools/README.md
  2. 1 1
      3rdparty/spirv-tools/include/generated/build-version.inc
  3. 2 1
      3rdparty/spirv-tools/include/generated/generators.inc
  4. 20 6
      3rdparty/spirv-tools/include/spirv-tools/instrument.hpp
  5. 12 0
      3rdparty/spirv-tools/include/spirv-tools/libspirv.h
  6. 8 0
      3rdparty/spirv-tools/include/spirv-tools/libspirv.hpp
  7. 1 1
      3rdparty/spirv-tools/include/spirv-tools/optimizer.hpp
  8. 17 0
      3rdparty/spirv-tools/source/opcode.cpp
  9. 6 3
      3rdparty/spirv-tools/source/opcode.h
  10. 1 0
      3rdparty/spirv-tools/source/opt/aggressive_dead_code_elim_pass.cpp
  11. 1 1
      3rdparty/spirv-tools/source/opt/ccp_pass.cpp
  12. 4 0
      3rdparty/spirv-tools/source/opt/graphics_robust_access_pass.cpp
  13. 235 48
      3rdparty/spirv-tools/source/opt/inst_bindless_check_pass.cpp
  14. 51 28
      3rdparty/spirv-tools/source/opt/inst_bindless_check_pass.h
  15. 70 11
      3rdparty/spirv-tools/source/opt/instrument_pass.cpp
  16. 38 3
      3rdparty/spirv-tools/source/opt/instrument_pass.h
  17. 1 0
      3rdparty/spirv-tools/source/opt/local_access_chain_convert_pass.cpp
  18. 1 0
      3rdparty/spirv-tools/source/opt/local_single_block_elim_pass.cpp
  19. 1 0
      3rdparty/spirv-tools/source/opt/local_single_store_elim_pass.cpp
  20. 1 1
      3rdparty/spirv-tools/source/opt/loop_peeling.cpp
  21. 12 4
      3rdparty/spirv-tools/source/opt/optimizer.cpp
  22. 13 0
      3rdparty/spirv-tools/source/opt/struct_cfg_analysis.cpp
  23. 5 0
      3rdparty/spirv-tools/source/opt/struct_cfg_analysis.h
  24. 1 0
      3rdparty/spirv-tools/source/reduce/CMakeLists.txt
  25. 6 8
      3rdparty/spirv-tools/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp
  26. 1 1
      3rdparty/spirv-tools/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h
  27. 4 6
      3rdparty/spirv-tools/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.cpp
  28. 5 7
      3rdparty/spirv-tools/source/reduce/merge_blocks_reduction_opportunity.cpp
  29. 4 6
      3rdparty/spirv-tools/source/reduce/merge_blocks_reduction_opportunity_finder.cpp
  30. 1 1
      3rdparty/spirv-tools/source/reduce/merge_blocks_reduction_opportunity_finder.h
  31. 3 5
      3rdparty/spirv-tools/source/reduce/operand_to_const_reduction_opportunity_finder.cpp
  32. 1 1
      3rdparty/spirv-tools/source/reduce/operand_to_const_reduction_opportunity_finder.h
  33. 10 15
      3rdparty/spirv-tools/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.cpp
  34. 1 1
      3rdparty/spirv-tools/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h
  35. 3 5
      3rdparty/spirv-tools/source/reduce/operand_to_undef_reduction_opportunity_finder.cpp
  36. 1 1
      3rdparty/spirv-tools/source/reduce/operand_to_undef_reduction_opportunity_finder.h
  37. 2 1
      3rdparty/spirv-tools/source/reduce/reducer.cpp
  38. 34 0
      3rdparty/spirv-tools/source/reduce/reduction_opportunity_finder.cpp
  39. 17 2
      3rdparty/spirv-tools/source/reduce/reduction_opportunity_finder.h
  40. 2 2
      3rdparty/spirv-tools/source/reduce/reduction_pass.cpp
  41. 6 1
      3rdparty/spirv-tools/source/reduce/reduction_pass.h
  42. 64 10
      3rdparty/spirv-tools/source/reduce/reduction_util.cpp
  43. 10 0
      3rdparty/spirv-tools/source/reduce/reduction_util.h
  44. 1 4
      3rdparty/spirv-tools/source/reduce/remove_block_reduction_opportunity.cpp
  45. 18 21
      3rdparty/spirv-tools/source/reduce/remove_block_reduction_opportunity_finder.cpp
  46. 3 3
      3rdparty/spirv-tools/source/reduce/remove_block_reduction_opportunity_finder.h
  47. 8 1
      3rdparty/spirv-tools/source/reduce/remove_function_reduction_opportunity_finder.cpp
  48. 1 1
      3rdparty/spirv-tools/source/reduce/remove_function_reduction_opportunity_finder.h
  49. 6 10
      3rdparty/spirv-tools/source/reduce/remove_selection_reduction_opportunity_finder.cpp
  50. 1 1
      3rdparty/spirv-tools/source/reduce/remove_selection_reduction_opportunity_finder.h
  51. 51 40
      3rdparty/spirv-tools/source/reduce/remove_unused_instruction_reduction_opportunity_finder.cpp
  52. 1 1
      3rdparty/spirv-tools/source/reduce/remove_unused_instruction_reduction_opportunity_finder.h
  53. 8 1
      3rdparty/spirv-tools/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.cpp
  54. 1 1
      3rdparty/spirv-tools/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.h
  55. 4 7
      3rdparty/spirv-tools/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.cpp
  56. 1 1
      3rdparty/spirv-tools/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h
  57. 1 3
      3rdparty/spirv-tools/source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.cpp
  58. 19 74
      3rdparty/spirv-tools/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp
  59. 0 14
      3rdparty/spirv-tools/source/reduce/structured_loop_to_selection_reduction_opportunity.h
  60. 9 11
      3rdparty/spirv-tools/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.cpp
  61. 1 1
      3rdparty/spirv-tools/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h
  62. 7 1
      3rdparty/spirv-tools/source/spirv_fuzzer_options.cpp
  63. 3 0
      3rdparty/spirv-tools/source/spirv_fuzzer_options.h
  64. 8 1
      3rdparty/spirv-tools/source/spirv_reducer_options.cpp
  65. 3 0
      3rdparty/spirv-tools/source/spirv_reducer_options.h
  66. 294 5
      3rdparty/spirv-tools/source/val/validate_builtins.cpp
  67. 28 0
      3rdparty/spirv-tools/source/val/validation_state.cpp

+ 1 - 4
3rdparty/spirv-tools/README.md

@@ -349,10 +349,7 @@ option, like so:
 
 ```sh
 # In <spirv-dir> (the SPIRV-Tools repo root):
-git clone https://github.com/protocolbuffers/protobuf external/protobuf
-pushd external/protobuf
-git checkout v3.7.1
-popd
+git clone --depth=1 --branch v3.13.0 https://github.com/protocolbuffers/protobuf external/protobuf
 
 # In your build directory:
 cmake [-G <platform-generator>] <spirv-dir> -DSPIRV_BUILD_FUZZER=ON

+ 1 - 1
3rdparty/spirv-tools/include/generated/build-version.inc

@@ -1 +1 @@
-"v2020.5", "SPIRV-Tools v2020.5 86402fec24134b6d4103b6ad1e689fb5840f720f"
+"v2020.5", "SPIRV-Tools v2020.5 b2259d7c11768aa4bcafa242d63802e9ca427764"

+ 2 - 1
3rdparty/spirv-tools/include/generated/generators.inc

@@ -21,4 +21,5 @@
 {20, "W3C WebGPU Group", "WHLSL Shader Translator", "W3C WebGPU Group WHLSL Shader Translator"},
 {21, "Google", "Clspv", "Google Clspv"},
 {22, "Google", "MLIR SPIR-V Serializer", "Google MLIR SPIR-V Serializer"},
-{23, "Google", "Tint Compiler", "Google Tint Compiler"},
+{23, "Google", "Tint Compiler", "Google Tint Compiler"},
+{24, "Google", "ANGLE Shader Compiler", "Google ANGLE Shader Compiler"},

+ 20 - 6
3rdparty/spirv-tools/include/spirv-tools/instrument.hpp

@@ -24,6 +24,7 @@
 //
 //   CreateInstBindlessCheckPass
 //   CreateInstBuffAddrCheckPass
+//   CreateInstDebugPrintfPass
 //
 // More detailed documentation of these routines can be found in optimizer.hpp
 
@@ -33,7 +34,7 @@ namespace spvtools {
 //
 // The following values provide offsets into the output buffer struct
 // generated by InstrumentPass::GenDebugStreamWrite. This method is utilized
-// by InstBindlessCheckPass.
+// by InstBindlessCheckPass, InstBuffAddrCheckPass, and InstDebugPrintfPass.
 //
 // The first member of the debug output buffer contains the next available word
 // in the data stream to be written. Shaders will atomically read and update
@@ -138,12 +139,21 @@ static const int kInstValidationOutError = kInstStageOutCnt;
 // A bindless bounds error will output the index and the bound.
 static const int kInstBindlessBoundsOutDescIndex = kInstStageOutCnt + 1;
 static const int kInstBindlessBoundsOutDescBound = kInstStageOutCnt + 2;
-static const int kInstBindlessBoundsOutCnt = kInstStageOutCnt + 3;
+static const int kInstBindlessBoundsOutUnused = kInstStageOutCnt + 3;
+static const int kInstBindlessBoundsOutCnt = kInstStageOutCnt + 4;
 
-// A bindless uninitialized error will output the index.
+// A descriptor uninitialized error will output the index.
 static const int kInstBindlessUninitOutDescIndex = kInstStageOutCnt + 1;
 static const int kInstBindlessUninitOutUnused = kInstStageOutCnt + 2;
-static const int kInstBindlessUninitOutCnt = kInstStageOutCnt + 3;
+static const int kInstBindlessUninitOutUnused2 = kInstStageOutCnt + 3;
+static const int kInstBindlessUninitOutCnt = kInstStageOutCnt + 4;
+
+// A buffer out-of-bounds error will output the descriptor
+// index, the buffer offset and the buffer size
+static const int kInstBindlessBuffOOBOutDescIndex = kInstStageOutCnt + 1;
+static const int kInstBindlessBuffOOBOutBuffOff = kInstStageOutCnt + 2;
+static const int kInstBindlessBuffOOBOutBuffSize = kInstStageOutCnt + 3;
+static const int kInstBindlessBuffOOBOutCnt = kInstStageOutCnt + 4;
 
 // A buffer address unalloc error will output the 64-bit pointer in
 // two 32-bit pieces, lower bits first.
@@ -152,7 +162,7 @@ static const int kInstBuffAddrUnallocOutDescPtrHi = kInstStageOutCnt + 2;
 static const int kInstBuffAddrUnallocOutCnt = kInstStageOutCnt + 3;
 
 // Maximum Output Record Member Count
-static const int kInstMaxOutCnt = kInstStageOutCnt + 3;
+static const int kInstMaxOutCnt = kInstStageOutCnt + 4;
 
 // Validation Error Codes
 //
@@ -160,6 +170,7 @@ static const int kInstMaxOutCnt = kInstStageOutCnt + 3;
 static const int kInstErrorBindlessBounds = 0;
 static const int kInstErrorBindlessUninit = 1;
 static const int kInstErrorBuffAddrUnallocRef = 2;
+static const int kInstErrorBindlessBuffOOB = 3;
 
 // Direct Input Buffer Offsets
 //
@@ -197,7 +208,10 @@ static const int kDebugOutputPrintfStream = 3;
 // At offset kDebugInputBindlessInitOffset in Data[] is a single uint which
 // gives an offset to the start of the bindless initialization data. More
 // specifically, if the following value is zero, we know that the descriptor at
-// (set = s, binding = b, index = i) is not initialized:
+// (set = s, binding = b, index = i) is not initialized; if the value is
+// non-zero, and the descriptor points to a buffer, the value is the length of
+// the buffer in bytes and can be used to check for out-of-bounds buffer
+// references:
 // Data[ i + Data[ b + Data[ s + Data[ kDebugInputBindlessInitOffset ] ] ] ]
 static const int kDebugInputBindlessInitOffset = 0;
 

+ 12 - 0
3rdparty/spirv-tools/include/spirv-tools/libspirv.h

@@ -675,6 +675,13 @@ SPIRV_TOOLS_EXPORT void spvReducerOptionsSetStepLimit(
 SPIRV_TOOLS_EXPORT void spvReducerOptionsSetFailOnValidationError(
     spv_reducer_options options, bool fail_on_validation_error);
 
+// Sets the function that the reducer should target.  If set to zero the reducer
+// will target all functions as well as parts of the module that lie outside
+// functions.  Otherwise the reducer will restrict reduction to the function
+// with result id |target_function|, which is required to exist.
+SPIRV_TOOLS_EXPORT void spvReducerOptionsSetTargetFunction(
+    spv_reducer_options options, uint32_t target_function);
+
 // Creates a fuzzer options object with default options. Returns a valid
 // options object. The object remains valid until it is passed into
 // |spvFuzzerOptionsDestroy|.
@@ -709,6 +716,11 @@ SPIRV_TOOLS_EXPORT void spvFuzzerOptionsSetShrinkerStepLimit(
 SPIRV_TOOLS_EXPORT void spvFuzzerOptionsEnableFuzzerPassValidation(
     spv_fuzzer_options options);
 
+// Enables all fuzzer passes during a fuzzing run (instead of a random subset
+// of passes).
+SPIRV_TOOLS_EXPORT void spvFuzzerOptionsEnableAllPasses(
+    spv_fuzzer_options options);
+
 // Encodes the given SPIR-V assembly text to its binary representation. The
 // length parameter specifies the number of bytes for text. Encoded binary will
 // be stored into *binary. Any error will be written into *diagnostic if

+ 8 - 0
3rdparty/spirv-tools/include/spirv-tools/libspirv.hpp

@@ -202,6 +202,11 @@ class ReducerOptions {
                                               fail_on_validation_error);
   }
 
+  // See spvReducerOptionsSetTargetFunction.
+  void set_target_function(uint32_t target_function) {
+    spvReducerOptionsSetTargetFunction(options_, target_function);
+  }
+
  private:
   spv_reducer_options options_;
 };
@@ -242,6 +247,9 @@ class FuzzerOptions {
     spvFuzzerOptionsEnableFuzzerPassValidation(options_);
   }
 
+  // See spvFuzzerOptionsEnableAllPasses.
+  void enable_all_passes() { spvFuzzerOptionsEnableAllPasses(options_); }
+
  private:
   spv_fuzzer_options options_;
 };

+ 1 - 1
3rdparty/spirv-tools/include/spirv-tools/optimizer.hpp

@@ -764,7 +764,7 @@ Optimizer::PassToken CreateCombineAccessChainsPass();
 // initialization checking, both of which require input buffer support.
 Optimizer::PassToken CreateInstBindlessCheckPass(
     uint32_t desc_set, uint32_t shader_id, bool input_length_enable = false,
-    bool input_init_enable = false);
+    bool input_init_enable = false, bool input_buff_oob_enable = false);
 
 // Create a pass to instrument physical buffer address checking
 // This pass instruments all physical buffer address references to check that

+ 17 - 0
3rdparty/spirv-tools/source/opcode.cpp

@@ -731,3 +731,20 @@ bool spvOpcodeIsAccessChain(SpvOp opcode) {
       return false;
   }
 }
+
+bool spvOpcodeIsBit(SpvOp opcode) {
+  switch (opcode) {
+    case SpvOpShiftRightLogical:
+    case SpvOpShiftRightArithmetic:
+    case SpvOpShiftLeftLogical:
+    case SpvOpBitwiseOr:
+    case SpvOpBitwiseXor:
+    case SpvOpBitwiseAnd:
+    case SpvOpNot:
+    case SpvOpBitReverse:
+    case SpvOpBitCount:
+      return true;
+    default:
+      return false;
+  }
+}

+ 6 - 3
3rdparty/spirv-tools/source/opcode.h

@@ -134,17 +134,20 @@ bool spvOpcodeIsDebug(SpvOp opcode);
 // where the order of the operands is irrelevant.
 bool spvOpcodeIsCommutativeBinaryOperator(SpvOp opcode);
 
-// Returns true for opcodes that represents linear algebra instructions.
+// Returns true for opcodes that represent linear algebra instructions.
 bool spvOpcodeIsLinearAlgebra(SpvOp opcode);
 
-// Returns true for opcodes that represents an image sample instruction.
+// Returns true for opcodes that represent image sample instructions.
 bool spvOpcodeIsImageSample(SpvOp opcode);
 
 // Returns a vector containing the indices of the memory semantics <id>
 // operands for |opcode|.
 std::vector<uint32_t> spvOpcodeMemorySemanticsOperandIndices(SpvOp opcode);
 
-// Returns true for opcodes that represents access chain instructions.
+// Returns true for opcodes that represent access chain instructions.
 bool spvOpcodeIsAccessChain(SpvOp opcode);
 
+// Returns true for opcodes that represent bit instructions.
+bool spvOpcodeIsBit(SpvOp opcode);
+
 #endif  // SOURCE_OPCODE_H_

+ 1 - 0
3rdparty/spirv-tools/source/opt/aggressive_dead_code_elim_pass.cpp

@@ -956,6 +956,7 @@ void AggressiveDCEPass::InitExtensions() {
       "SPV_AMD_gpu_shader_half_float",
       "SPV_KHR_shader_draw_parameters",
       "SPV_KHR_subgroup_vote",
+      "SPV_KHR_8bit_storage",
       "SPV_KHR_16bit_storage",
       "SPV_KHR_device_group",
       "SPV_KHR_multiview",

+ 1 - 1
3rdparty/spirv-tools/source/opt/ccp_pass.cpp

@@ -150,7 +150,7 @@ SSAPropagator::PropStatus CCPPass::VisitAssignment(Instruction* instr) {
     // that CCP has modified the IR, independently of whether the constant is
     // actually propagated. See
     // https://github.com/KhronosGroup/SPIRV-Tools/issues/3636 for details.
-    if (folded_inst->result_id() == next_id) created_new_constant_ = true;
+    if (folded_inst->result_id() >= next_id) created_new_constant_ = true;
 
     return SSAPropagator::kInteresting;
   }

+ 4 - 0
3rdparty/spirv-tools/source/opt/graphics_robust_access_pass.cpp

@@ -320,9 +320,13 @@ void GraphicsRobustAccessPass::ClampIndicesForAccessChain(
       maxval_width *= 2;
     }
     // Determine the type for |maxval|.
+    uint32_t next_id = context()->module()->IdBound();
     analysis::Integer signed_type_for_query(maxval_width, true);
     auto* maxval_type =
         type_mgr->GetRegisteredType(&signed_type_for_query)->AsInteger();
+    if (next_id != context()->module()->IdBound()) {
+      module_status_.modified = true;
+    }
     // Access chain indices are treated as signed, so limit the maximum value
     // of the index so it will always be positive for a signed clamp operation.
     maxval = std::min(maxval, ((uint64_t(1) << (maxval_width - 1)) - 1));

+ 235 - 48
3rdparty/spirv-tools/source/opt/inst_bindless_check_pass.cpp

@@ -26,13 +26,19 @@ 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;
 static const int kSpvVariableStorageClassInIdx = 0;
 
 }  // anonymous namespace
 
+// Avoid unused variable warning/error on Linux
+#ifndef NDEBUG
+#define USE_ASSERT(x) assert(x)
+#else
+#define USE_ASSERT(x) ((void)(x))
+#endif
+
 namespace spvtools {
 namespace opt {
 
@@ -48,14 +54,25 @@ uint32_t InstBindlessCheckPass::GenDebugReadLength(
 uint32_t InstBindlessCheckPass::GenDebugReadInit(uint32_t var_id,
                                                  uint32_t desc_idx_id,
                                                  InstructionBuilder* builder) {
-  uint32_t desc_set_base_id =
-      builder->GetUintConstantId(kDebugInputBindlessInitOffset);
-  uint32_t desc_set_idx_id = builder->GetUintConstantId(var2desc_set_[var_id]);
   uint32_t binding_idx_id = builder->GetUintConstantId(var2binding_[var_id]);
   uint32_t u_desc_idx_id = GenUintCastCode(desc_idx_id, builder);
-  return GenDebugDirectRead(
-      {desc_set_base_id, desc_set_idx_id, binding_idx_id, u_desc_idx_id},
-      builder);
+  // If desc index checking is not enabled, we know the offset of initialization
+  // entries is 1, so we can avoid loading this value and just add 1 to the
+  // descriptor set.
+  if (!desc_idx_enabled_) {
+    uint32_t desc_set_idx_id =
+        builder->GetUintConstantId(var2desc_set_[var_id] + 1);
+    return GenDebugDirectRead({desc_set_idx_id, binding_idx_id, u_desc_idx_id},
+                              builder);
+  } else {
+    uint32_t desc_set_base_id =
+        builder->GetUintConstantId(kDebugInputBindlessInitOffset);
+    uint32_t desc_set_idx_id =
+        builder->GetUintConstantId(var2desc_set_[var_id]);
+    return GenDebugDirectRead(
+        {desc_set_base_id, desc_set_idx_id, binding_idx_id, u_desc_idx_id},
+        builder);
+  }
 }
 
 uint32_t InstBindlessCheckPass::CloneOriginalReference(
@@ -156,13 +173,9 @@ uint32_t InstBindlessCheckPass::GetImageId(Instruction* inst) {
   return 0;
 }
 
-Instruction* InstBindlessCheckPass::GetDescriptorTypeInst(
-    Instruction* var_inst) {
-  uint32_t var_type_id = var_inst->type_id();
-  Instruction* var_type_inst = get_def_use_mgr()->GetDef(var_type_id);
-  uint32_t desc_type_id =
-      var_type_inst->GetSingleWordInOperand(kSpvTypePointerTypeIdInIdx);
-  return get_def_use_mgr()->GetDef(desc_type_id);
+Instruction* InstBindlessCheckPass::GetPointeeTypeInst(Instruction* ptr_inst) {
+  uint32_t pte_ty_id = GetPointeeTypeId(ptr_inst);
+  return get_def_use_mgr()->GetDef(pte_ty_id);
 }
 
 bool InstBindlessCheckPass::AnalyzeDescriptorReference(Instruction* ref_inst,
@@ -187,7 +200,7 @@ bool InstBindlessCheckPass::AnalyzeDescriptorReference(Instruction* ref_inst,
         return false;
         break;
     }
-    Instruction* desc_type_inst = GetDescriptorTypeInst(var_inst);
+    Instruction* desc_type_inst = GetPointeeTypeInst(var_inst);
     switch (desc_type_inst->opcode()) {
       case SpvOpTypeArray:
       case SpvOpTypeRuntimeArray:
@@ -195,11 +208,11 @@ bool InstBindlessCheckPass::AnalyzeDescriptorReference(Instruction* ref_inst,
         // do not want to instrument loads of descriptors here which are part of
         // an image-based reference.
         if (ptr_inst->NumInOperands() < 3) return false;
-        ref->index_id =
+        ref->desc_idx_id =
             ptr_inst->GetSingleWordInOperand(kSpvAccessChainIndex0IdInIdx);
         break;
       default:
-        ref->index_id = 0;
+        ref->desc_idx_id = 0;
         break;
     }
     return true;
@@ -229,14 +242,14 @@ bool InstBindlessCheckPass::AnalyzeDescriptorReference(Instruction* ref_inst,
   ref->ptr_id = desc_load_inst->GetSingleWordInOperand(kSpvLoadPtrIdInIdx);
   Instruction* ptr_inst = get_def_use_mgr()->GetDef(ref->ptr_id);
   if (ptr_inst->opcode() == SpvOp::SpvOpVariable) {
-    ref->index_id = 0;
+    ref->desc_idx_id = 0;
     ref->var_id = ref->ptr_id;
   } else if (ptr_inst->opcode() == SpvOp::SpvOpAccessChain) {
     if (ptr_inst->NumInOperands() != 2) {
       assert(false && "unexpected bindless index number");
       return false;
     }
-    ref->index_id =
+    ref->desc_idx_id =
         ptr_inst->GetSingleWordInOperand(kSpvAccessChainIndex0IdInIdx);
     ref->var_id = ptr_inst->GetSingleWordInOperand(kSpvAccessChainBaseIdInIdx);
     Instruction* var_inst = get_def_use_mgr()->GetDef(ref->var_id);
@@ -251,9 +264,150 @@ bool InstBindlessCheckPass::AnalyzeDescriptorReference(Instruction* ref_inst,
   return true;
 }
 
+uint32_t InstBindlessCheckPass::FindStride(uint32_t ty_id,
+                                           uint32_t stride_deco) {
+  uint32_t stride = 0xdeadbeef;
+  bool found = !get_decoration_mgr()->WhileEachDecoration(
+      ty_id, stride_deco, [&stride](const Instruction& deco_inst) {
+        stride = deco_inst.GetSingleWordInOperand(2u);
+        return false;
+      });
+  USE_ASSERT(found && "stride not found");
+  return stride;
+}
+
+uint32_t InstBindlessCheckPass::ByteSize(uint32_t ty_id) {
+  analysis::TypeManager* type_mgr = context()->get_type_mgr();
+  const analysis::Type* sz_ty = type_mgr->GetType(ty_id);
+  if (sz_ty->kind() == analysis::Type::kPointer) {
+    // Assuming PhysicalStorageBuffer pointer
+    return 8;
+  }
+  uint32_t size = 1;
+  if (sz_ty->kind() == analysis::Type::kMatrix) {
+    const analysis::Matrix* m_ty = sz_ty->AsMatrix();
+    size = m_ty->element_count() * size;
+    uint32_t stride = FindStride(ty_id, SpvDecorationMatrixStride);
+    if (stride != 0) return size * stride;
+    sz_ty = m_ty->element_type();
+  }
+  if (sz_ty->kind() == analysis::Type::kVector) {
+    const analysis::Vector* v_ty = sz_ty->AsVector();
+    size = v_ty->element_count() * size;
+    sz_ty = v_ty->element_type();
+  }
+  switch (sz_ty->kind()) {
+    case analysis::Type::kFloat: {
+      const analysis::Float* f_ty = sz_ty->AsFloat();
+      size *= f_ty->width();
+    } break;
+    case analysis::Type::kInteger: {
+      const analysis::Integer* i_ty = sz_ty->AsInteger();
+      size *= i_ty->width();
+    } break;
+    default: { assert(false && "unexpected type"); } break;
+  }
+  size /= 8;
+  return size;
+}
+
+uint32_t InstBindlessCheckPass::GenLastByteIdx(ref_analysis* ref,
+                                               InstructionBuilder* builder) {
+  // Find outermost buffer type and its access chain index
+  Instruction* var_inst = get_def_use_mgr()->GetDef(ref->var_id);
+  Instruction* desc_ty_inst = GetPointeeTypeInst(var_inst);
+  uint32_t buff_ty_id;
+  uint32_t ac_in_idx = 1;
+  switch (desc_ty_inst->opcode()) {
+    case SpvOpTypeArray:
+    case SpvOpTypeRuntimeArray:
+      buff_ty_id = desc_ty_inst->GetSingleWordInOperand(0);
+      ++ac_in_idx;
+      break;
+    default:
+      assert(desc_ty_inst->opcode() == SpvOpTypeStruct &&
+             "unexpected descriptor type");
+      buff_ty_id = desc_ty_inst->result_id();
+      break;
+  }
+  // Process remaining access chain indices
+  Instruction* ac_inst = get_def_use_mgr()->GetDef(ref->ptr_id);
+  uint32_t curr_ty_id = buff_ty_id;
+  uint32_t sum_id = 0;
+  while (ac_in_idx < ac_inst->NumInOperands()) {
+    uint32_t curr_idx_id = ac_inst->GetSingleWordInOperand(ac_in_idx);
+    Instruction* curr_idx_inst = get_def_use_mgr()->GetDef(curr_idx_id);
+    Instruction* curr_ty_inst = get_def_use_mgr()->GetDef(curr_ty_id);
+    uint32_t curr_offset_id = 0;
+    switch (curr_ty_inst->opcode()) {
+      case SpvOpTypeArray:
+      case SpvOpTypeRuntimeArray:
+      case SpvOpTypeMatrix: {
+        // Get array/matrix stride and multiply by current index
+        uint32_t stride_deco = (curr_ty_inst->opcode() == SpvOpTypeMatrix)
+                                   ? SpvDecorationMatrixStride
+                                   : SpvDecorationArrayStride;
+        uint32_t arr_stride = FindStride(curr_ty_id, stride_deco);
+        uint32_t arr_stride_id = builder->GetUintConstantId(arr_stride);
+        Instruction* curr_offset_inst = builder->AddBinaryOp(
+            GetUintId(), SpvOpIMul, arr_stride_id, curr_idx_id);
+        curr_offset_id = curr_offset_inst->result_id();
+        // Get element type for next step
+        curr_ty_id = curr_ty_inst->GetSingleWordInOperand(0);
+      } break;
+      case SpvOpTypeVector: {
+        // Stride is size of component type
+        uint32_t comp_ty_id = curr_ty_inst->GetSingleWordInOperand(0u);
+        uint32_t vec_stride = ByteSize(comp_ty_id);
+        uint32_t vec_stride_id = builder->GetUintConstantId(vec_stride);
+        Instruction* curr_offset_inst = builder->AddBinaryOp(
+            GetUintId(), SpvOpIMul, vec_stride_id, curr_idx_id);
+        curr_offset_id = curr_offset_inst->result_id();
+        // Get element type for next step
+        curr_ty_id = comp_ty_id;
+      } break;
+      case SpvOpTypeStruct: {
+        // Get buffer byte offset for the referenced member
+        assert(curr_idx_inst->opcode() == SpvOpConstant &&
+               "unexpected struct index");
+        uint32_t member_idx = curr_idx_inst->GetSingleWordInOperand(0);
+        uint32_t member_offset = 0xdeadbeef;
+        bool found = !get_decoration_mgr()->WhileEachDecoration(
+            curr_ty_id, SpvDecorationOffset,
+            [&member_idx, &member_offset](const Instruction& deco_inst) {
+              if (deco_inst.GetSingleWordInOperand(1u) != member_idx)
+                return true;
+              member_offset = deco_inst.GetSingleWordInOperand(3u);
+              return false;
+            });
+        USE_ASSERT(found && "member offset not found");
+        curr_offset_id = builder->GetUintConstantId(member_offset);
+        // Get element type for next step
+        curr_ty_id = curr_ty_inst->GetSingleWordInOperand(member_idx);
+      } break;
+      default: { assert(false && "unexpected non-composite type"); } break;
+    }
+    if (sum_id == 0)
+      sum_id = curr_offset_id;
+    else {
+      Instruction* sum_inst =
+          builder->AddBinaryOp(GetUintId(), SpvOpIAdd, sum_id, curr_offset_id);
+      sum_id = sum_inst->result_id();
+    }
+    ++ac_in_idx;
+  }
+  // Add in offset of last byte of referenced object
+  uint32_t bsize = ByteSize(curr_ty_id);
+  uint32_t last = bsize - 1;
+  uint32_t last_id = builder->GetUintConstantId(last);
+  Instruction* sum_inst =
+      builder->AddBinaryOp(GetUintId(), SpvOpIAdd, sum_id, last_id);
+  return sum_inst->result_id();
+}
+
 void InstBindlessCheckPass::GenCheckCode(
-    uint32_t check_id, uint32_t error_id, uint32_t length_id,
-    uint32_t stage_idx, ref_analysis* ref,
+    uint32_t check_id, uint32_t error_id, uint32_t offset_id,
+    uint32_t length_id, uint32_t stage_idx, ref_analysis* ref,
     std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
   BasicBlock* back_blk_ptr = &*new_blocks->back();
   InstructionBuilder builder(
@@ -279,9 +433,19 @@ void InstBindlessCheckPass::GenCheckCode(
   // Gen invalid block
   new_blk_ptr.reset(new BasicBlock(std::move(invalid_label)));
   builder.SetInsertPoint(&*new_blk_ptr);
-  uint32_t u_index_id = GenUintCastCode(ref->index_id, &builder);
-  GenDebugStreamWrite(uid2offset_[ref->ref_inst->unique_id()], stage_idx,
-                      {error_id, u_index_id, length_id}, &builder);
+  uint32_t u_index_id = GenUintCastCode(ref->desc_idx_id, &builder);
+  if (offset_id != 0)
+    GenDebugStreamWrite(uid2offset_[ref->ref_inst->unique_id()], stage_idx,
+                        {error_id, u_index_id, offset_id, length_id}, &builder);
+  else if (buffer_bounds_enabled_)
+    // So all error modes will use same debug stream write function
+    GenDebugStreamWrite(
+        uid2offset_[ref->ref_inst->unique_id()], stage_idx,
+        {error_id, u_index_id, length_id, builder.GetUintConstantId(0)},
+        &builder);
+  else
+    GenDebugStreamWrite(uid2offset_[ref->ref_inst->unique_id()], 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
@@ -305,7 +469,7 @@ void InstBindlessCheckPass::GenCheckCode(
   context()->KillInst(ref->ref_inst);
 }
 
-void InstBindlessCheckPass::GenBoundsCheckCode(
+void InstBindlessCheckPass::GenDescIdxCheckCode(
     BasicBlock::iterator ref_inst_itr,
     UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
     std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
@@ -318,19 +482,19 @@ void InstBindlessCheckPass::GenBoundsCheckCode(
   // If index and bound both compile-time constants and index < bound,
   // return without changing
   Instruction* var_inst = get_def_use_mgr()->GetDef(ref.var_id);
-  Instruction* desc_type_inst = GetDescriptorTypeInst(var_inst);
+  Instruction* desc_type_inst = GetPointeeTypeInst(var_inst);
   uint32_t length_id = 0;
   if (desc_type_inst->opcode() == SpvOpTypeArray) {
     length_id =
         desc_type_inst->GetSingleWordInOperand(kSpvTypeArrayLengthIdInIdx);
-    Instruction* index_inst = get_def_use_mgr()->GetDef(ref.index_id);
+    Instruction* index_inst = get_def_use_mgr()->GetDef(ref.desc_idx_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;
-  } else if (!input_length_enabled_ ||
+  } else if (!desc_idx_enabled_ ||
              desc_type_inst->opcode() != SpvOpTypeRuntimeArray) {
     return;
   }
@@ -352,9 +516,9 @@ void InstBindlessCheckPass::GenBoundsCheckCode(
   // Generate full runtime bounds test code with true branch
   // being full reference and false branch being debug output and zero
   // for the referenced value.
-  Instruction* ult_inst =
-      builder.AddBinaryOp(GetBoolId(), SpvOpULessThan, ref.index_id, length_id);
-  GenCheckCode(ult_inst->result_id(), error_id, length_id, stage_idx, &ref,
+  Instruction* ult_inst = builder.AddBinaryOp(GetBoolId(), SpvOpULessThan,
+                                              ref.desc_idx_id, length_id);
+  GenCheckCode(ult_inst->result_id(), error_id, 0u, length_id, stage_idx, &ref,
                new_blocks);
   // Move original block's remaining code into remainder/merge block and add
   // to new blocks
@@ -362,13 +526,30 @@ void InstBindlessCheckPass::GenBoundsCheckCode(
   MovePostludeCode(ref_block_itr, back_blk_ptr);
 }
 
-void InstBindlessCheckPass::GenInitCheckCode(
+void InstBindlessCheckPass::GenDescInitCheckCode(
     BasicBlock::iterator ref_inst_itr,
     UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
     std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
   // Look for reference through descriptor. If not, return.
   ref_analysis ref;
   if (!AnalyzeDescriptorReference(&*ref_inst_itr, &ref)) return;
+  // Determine if we can only do initialization check
+  bool init_check = false;
+  if (ref.desc_load_id != 0 || !buffer_bounds_enabled_) {
+    init_check = true;
+  } else {
+    // For now, only do bounds check for non-aggregate types. Otherwise
+    // just do descriptor initialization check.
+    // TODO(greg-lunarg): Do bounds check for aggregate loads and stores
+    Instruction* ref_ptr_inst = get_def_use_mgr()->GetDef(ref.ptr_id);
+    Instruction* pte_type_inst = GetPointeeTypeInst(ref_ptr_inst);
+    uint32_t pte_type_op = pte_type_inst->opcode();
+    if (pte_type_op == SpvOpTypeArray || pte_type_op == SpvOpTypeRuntimeArray ||
+        pte_type_op == SpvOpTypeStruct)
+      init_check = true;
+  }
+  // If initialization check and not enabled, return
+  if (init_check && !desc_init_enabled_) return;
   // Move original block's preceding instructions into first new block
   std::unique_ptr<BasicBlock> new_blk_ptr;
   MovePreludeCode(ref_inst_itr, ref_block_itr, &new_blk_ptr);
@@ -376,19 +557,25 @@ void InstBindlessCheckPass::GenInitCheckCode(
       context(), &*new_blk_ptr,
       IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
   new_blocks->push_back(std::move(new_blk_ptr));
-  // Read initialization status from debug input buffer. If index id not yet
+  // If initialization check, use reference value of zero.
+  // Else use the index of the last byte referenced.
+  uint32_t ref_id = init_check ? builder.GetUintConstantId(0u)
+                               : GenLastByteIdx(&ref, &builder);
+  // Read initialization/bounds from debug input buffer. If index id not yet
   // set, binding is single descriptor, so set index to constant 0.
-  uint32_t zero_id = builder.GetUintConstantId(0u);
-  if (ref.index_id == 0) ref.index_id = zero_id;
-  uint32_t init_id = GenDebugReadInit(ref.var_id, ref.index_id, &builder);
-  // Generate full runtime non-zero init test code with true branch
+  if (ref.desc_idx_id == 0) ref.desc_idx_id = builder.GetUintConstantId(0u);
+  uint32_t init_id = GenDebugReadInit(ref.var_id, ref.desc_idx_id, &builder);
+  // Generate runtime initialization/bounds test code with true branch
   // being full reference and false branch being debug output and zero
   // for the referenced value.
-  Instruction* uneq_inst =
-      builder.AddBinaryOp(GetBoolId(), SpvOpINotEqual, init_id, zero_id);
-  uint32_t error_id = builder.GetUintConstantId(kInstErrorBindlessUninit);
-  GenCheckCode(uneq_inst->result_id(), error_id, zero_id, stage_idx, &ref,
-               new_blocks);
+  Instruction* ult_inst =
+      builder.AddBinaryOp(GetBoolId(), SpvOpULessThan, ref_id, init_id);
+  uint32_t error =
+      init_check ? kInstErrorBindlessUninit : kInstErrorBindlessBuffOOB;
+  uint32_t error_id = builder.GetUintConstantId(error);
+  GenCheckCode(ult_inst->result_id(), error_id, init_check ? 0 : ref_id,
+               init_check ? builder.GetUintConstantId(0u) : init_id, stage_idx,
+               &ref, new_blocks);
   // Move original block's remaining code into remainder/merge block and add
   // to new blocks
   BasicBlock* back_blk_ptr = &*new_blocks->back();
@@ -400,7 +587,7 @@ void InstBindlessCheckPass::InitializeInstBindlessCheck() {
   InitializeInstrument();
   // If runtime array length support enabled, create variable mappings. Length
   // support is always enabled if descriptor init check is enabled.
-  if (input_length_enabled_)
+  if (desc_idx_enabled_ || buffer_bounds_enabled_)
     for (auto& anno : get_module()->annotations())
       if (anno.opcode() == SpvOpDecorate) {
         if (anno.GetSingleWordInOperand(1u) == SpvDecorationDescriptorSet)
@@ -418,19 +605,19 @@ Pass::Status InstBindlessCheckPass::ProcessImpl() {
       [this](BasicBlock::iterator ref_inst_itr,
              UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
              std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
-        return GenBoundsCheckCode(ref_inst_itr, ref_block_itr, stage_idx,
-                                  new_blocks);
+        return GenDescIdxCheckCode(ref_inst_itr, ref_block_itr, stage_idx,
+                                   new_blocks);
       };
   bool modified = InstProcessEntryPointCallTree(pfn);
-  if (input_init_enabled_) {
+  if (desc_init_enabled_ || buffer_bounds_enabled_) {
     // Perform descriptor initialization check on each entry point function in
     // module
     pfn = [this](BasicBlock::iterator ref_inst_itr,
                  UptrVectorIterator<BasicBlock> ref_block_itr,
                  uint32_t stage_idx,
                  std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
-      return GenInitCheckCode(ref_inst_itr, ref_block_itr, stage_idx,
-                              new_blocks);
+      return GenDescInitCheckCode(ref_inst_itr, ref_block_itr, stage_idx,
+                                  new_blocks);
     };
     modified |= InstProcessEntryPointCallTree(pfn);
   }

+ 51 - 28
3rdparty/spirv-tools/source/opt/inst_bindless_check_pass.h

@@ -28,12 +28,24 @@ namespace opt {
 // external design may change as the layer evolves.
 class InstBindlessCheckPass : public InstrumentPass {
  public:
-  // Preferred Interface
+  // Old interface to support testing pre-buffer-overrun capability
   InstBindlessCheckPass(uint32_t desc_set, uint32_t shader_id,
-                        bool input_length_enable, bool input_init_enable)
-      : InstrumentPass(desc_set, shader_id, kInstValidationIdBindless),
-        input_length_enabled_(input_length_enable),
-        input_init_enabled_(input_init_enable) {}
+                        bool desc_idx_enable, bool desc_init_enable)
+      : InstrumentPass(desc_set, shader_id, kInstValidationIdBindless, false),
+        desc_idx_enabled_(desc_idx_enable),
+        desc_init_enabled_(desc_init_enable),
+        buffer_bounds_enabled_(false) {}
+
+  // New interface supporting buffer overrun checking
+  InstBindlessCheckPass(uint32_t desc_set, uint32_t shader_id,
+                        bool desc_idx_enable, bool desc_init_enable,
+                        bool buffer_bounds_enable)
+      : InstrumentPass(
+            desc_set, shader_id, kInstValidationIdBindless,
+            desc_idx_enable || desc_init_enable || buffer_bounds_enable),
+        desc_idx_enabled_(desc_idx_enable),
+        desc_init_enabled_(desc_init_enable),
+        buffer_bounds_enabled_(buffer_bounds_enable) {}
 
   ~InstBindlessCheckPass() override = default;
 
@@ -46,13 +58,11 @@ class InstBindlessCheckPass : public InstrumentPass {
   // These functions do bindless checking instrumentation on a single
   // instruction which references through a descriptor (ie references into an
   // image or buffer). Refer to Vulkan API for further information on
-  // descriptors. GenBoundsCheckCode checks that an index into a descriptor
-  // array (array of images or buffers) is in-bounds. GenInitCheckCode
+  // descriptors. GenDescIdxCheckCode checks that an index into a descriptor
+  // array (array of images or buffers) is in-bounds. GenDescInitCheckCode
   // checks that the referenced descriptor has been initialized, if the
-  // SPV_EXT_descriptor_indexing extension is enabled.
-  //
-  // TODO(greg-lunarg): Add support for buffers. Currently only does
-  // checking of references of images.
+  // SPV_EXT_descriptor_indexing extension is enabled, and initialized large
+  // enough to handle the reference, if RobustBufferAccess is disabled.
   //
   // The functions are designed to be passed to
   // InstrumentPass::InstProcessEntryPointCallTree(), which applies the
@@ -89,15 +99,15 @@ class InstBindlessCheckPass : public InstrumentPass {
   //
   // The Descriptor Array Size is the size of the descriptor array which was
   // indexed.
-  void GenBoundsCheckCode(BasicBlock::iterator ref_inst_itr,
-                          UptrVectorIterator<BasicBlock> ref_block_itr,
-                          uint32_t stage_idx,
-                          std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
+  void GenDescIdxCheckCode(
+      BasicBlock::iterator ref_inst_itr,
+      UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
+      std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
 
-  void GenInitCheckCode(BasicBlock::iterator ref_inst_itr,
-                        UptrVectorIterator<BasicBlock> ref_block_itr,
-                        uint32_t stage_idx,
-                        std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
+  void GenDescInitCheckCode(
+      BasicBlock::iterator ref_inst_itr,
+      UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
+      std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
 
   // Generate instructions into |builder| to read length of runtime descriptor
   // array |var_id| from debug input buffer and return id of value.
@@ -118,10 +128,20 @@ class InstBindlessCheckPass : public InstrumentPass {
     uint32_t load_id;
     uint32_t ptr_id;
     uint32_t var_id;
-    uint32_t index_id;
+    uint32_t desc_idx_id;
     Instruction* ref_inst;
   } ref_analysis;
 
+  // Return size of type |ty_id| in bytes.
+  uint32_t ByteSize(uint32_t ty_id);
+
+  // Return stride of type |ty_id| with decoration |stride_deco|. Return 0
+  // if not found
+  uint32_t FindStride(uint32_t ty_id, uint32_t stride_deco);
+
+  // Generate index of last byte referenced by buffer reference |ref|
+  uint32_t GenLastByteIdx(ref_analysis* ref, InstructionBuilder* builder);
+
   // Clone original original reference encapsulated by |ref| into |builder|.
   // This may generate more than one instruction if neccessary.
   uint32_t CloneOriginalReference(ref_analysis* ref,
@@ -131,8 +151,8 @@ class InstBindlessCheckPass : public InstrumentPass {
   // references through. Else return 0.
   uint32_t GetImageId(Instruction* inst);
 
-  // Get descriptor type inst of variable |var_inst|.
-  Instruction* GetDescriptorTypeInst(Instruction* var_inst);
+  // Get pointee type inst of pointer value |ptr_inst|.
+  Instruction* GetPointeeTypeInst(Instruction* ptr_inst);
 
   // Analyze descriptor reference |ref_inst| and save components into |ref|.
   // Return true if |ref_inst| is a descriptor reference, false otherwise.
@@ -145,22 +165,25 @@ class InstBindlessCheckPass : public InstrumentPass {
   // writes debug error output utilizing |ref|, |error_id|, |length_id| and
   // |stage_idx|. Generate merge block for valid and invalid branches. Kill
   // original reference.
-  void GenCheckCode(uint32_t check_id, uint32_t error_id, uint32_t length_id,
-                    uint32_t stage_idx, ref_analysis* ref,
+  void GenCheckCode(uint32_t check_id, uint32_t error_id, uint32_t offset_id,
+                    uint32_t length_id, uint32_t stage_idx, ref_analysis* ref,
                     std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
 
   // Initialize state for instrumenting bindless checking
   void InitializeInstBindlessCheck();
 
-  // Apply GenBoundsCheckCode to every instruction in module. Then apply
-  // GenInitCheckCode to every instruction in module.
+  // Apply GenDescIdxCheckCode to every instruction in module. Then apply
+  // GenDescInitCheckCode to every instruction in module.
   Pass::Status ProcessImpl();
 
   // Enable instrumentation of runtime array length checking
-  bool input_length_enabled_;
+  bool desc_idx_enabled_;
 
   // Enable instrumentation of descriptor initialization checking
-  bool input_init_enabled_;
+  bool desc_init_enabled_;
+
+  // Enable instrumentation of buffer overrun checking
+  bool buffer_bounds_enabled_;
 
   // Mapping from variable to descriptor set
   std::unordered_map<uint32_t, uint32_t> var2desc_set_;

+ 70 - 11
3rdparty/spirv-tools/source/opt/instrument_pass.cpp

@@ -281,14 +281,42 @@ void InstrumentPass::GenDebugStreamWrite(
   (void)builder->AddNaryOp(GetVoidId(), SpvOpFunctionCall, args);
 }
 
+bool InstrumentPass::AllConstant(const std::vector<uint32_t>& ids) {
+  for (auto& id : ids) {
+    Instruction* id_inst = context()->get_def_use_mgr()->GetDef(id);
+    if (!spvOpcodeIsConstant(id_inst->opcode())) return false;
+  }
+  return true;
+}
+
 uint32_t InstrumentPass::GenDebugDirectRead(
-    const std::vector<uint32_t>& offset_ids, InstructionBuilder* builder) {
+    const std::vector<uint32_t>& offset_ids, InstructionBuilder* ref_builder) {
   // Call debug input function. Pass func_idx and offset ids as args.
   uint32_t off_id_cnt = static_cast<uint32_t>(offset_ids.size());
   uint32_t input_func_id = GetDirectReadFunctionId(off_id_cnt);
   std::vector<uint32_t> args = {input_func_id};
   (void)args.insert(args.end(), offset_ids.begin(), offset_ids.end());
-  return builder->AddNaryOp(GetUintId(), SpvOpFunctionCall, args)->result_id();
+  // If optimizing direct reads and the call has already been generated,
+  // use its result
+  if (opt_direct_reads_) {
+    uint32_t res_id = call2id_[args];
+    if (res_id != 0) return res_id;
+  }
+  // If the offsets are all constants, the call can be moved to the first block
+  // of the function where its result can be reused. One example where this is
+  // profitable is for uniform buffer references, of which there are often many.
+  InstructionBuilder builder(ref_builder->GetContext(),
+                             &*ref_builder->GetInsertPoint(),
+                             ref_builder->GetPreservedAnalysis());
+  bool insert_in_first_block = opt_direct_reads_ && AllConstant(offset_ids);
+  if (insert_in_first_block) {
+    Instruction* insert_before = &*curr_func_->begin()->tail();
+    builder.SetInsertPoint(insert_before);
+  }
+  uint32_t res_id =
+      builder.AddNaryOp(GetUintId(), SpvOpFunctionCall, args)->result_id();
+  if (insert_in_first_block) call2id_[args] = res_id;
+  return res_id;
 }
 
 bool InstrumentPass::IsSameBlockOp(const Instruction* inst) const {
@@ -819,21 +847,52 @@ uint32_t InstrumentPass::GetDirectReadFunctionId(uint32_t param_cnt) {
   return func_id;
 }
 
+void InstrumentPass::SplitBlock(
+    BasicBlock::iterator inst_itr, UptrVectorIterator<BasicBlock> block_itr,
+    std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
+  // Make sure def/use analysis is done before we start moving instructions
+  // out of function
+  (void)get_def_use_mgr();
+  // Move original block's preceding instructions into first new block
+  std::unique_ptr<BasicBlock> first_blk_ptr;
+  MovePreludeCode(inst_itr, block_itr, &first_blk_ptr);
+  InstructionBuilder builder(
+      context(), &*first_blk_ptr,
+      IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
+  uint32_t split_blk_id = TakeNextId();
+  std::unique_ptr<Instruction> split_label(NewLabel(split_blk_id));
+  (void)builder.AddBranch(split_blk_id);
+  new_blocks->push_back(std::move(first_blk_ptr));
+  // Move remaining instructions into split block and add to new blocks
+  std::unique_ptr<BasicBlock> split_blk_ptr(
+      new BasicBlock(std::move(split_label)));
+  MovePostludeCode(block_itr, &*split_blk_ptr);
+  new_blocks->push_back(std::move(split_blk_ptr));
+}
+
 bool InstrumentPass::InstrumentFunction(Function* func, uint32_t stage_idx,
                                         InstProcessFunction& pfn) {
+  curr_func_ = func;
+  call2id_.clear();
+  bool first_block_split = false;
   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;
+  // Apply instrumentation function to each instruction.
   // Using block iterators here because of block erasures and insertions.
+  std::vector<std::unique_ptr<BasicBlock>> new_blks;
   for (auto bi = func->begin(); bi != func->end(); ++bi) {
     for (auto ii = bi->begin(); ii != bi->end();) {
-      // Generate instrumentation if warranted
-      pfn(ii, bi, stage_idx, &new_blks);
+      // Split all executable instructions out of first block into a following
+      // block. This will allow function calls to be inserted into the first
+      // block without interfering with the instrumentation algorithm.
+      if (opt_direct_reads_ && !first_block_split) {
+        if (ii->opcode() != SpvOpVariable) {
+          SplitBlock(ii, bi, &new_blks);
+          first_block_split = true;
+        }
+      } else {
+        pfn(ii, bi, stage_idx, &new_blks);
+      }
+      // If no new code, continue
       if (new_blks.size() == 0) {
         ++ii;
         continue;

+ 38 - 3
3rdparty/spirv-tools/source/opt/instrument_pass.h

@@ -82,12 +82,15 @@ class InstrumentPass : public Pass {
  protected:
   // Create instrumentation pass for |validation_id| 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)
+  // into debug output records. |opt_direct_reads| indicates that the pass
+  // will see direct input buffer reads and should prepare to optimize them.
+  InstrumentPass(uint32_t desc_set, uint32_t shader_id, uint32_t validation_id,
+                 bool opt_direct_reads = false)
       : Pass(),
         desc_set_(desc_set),
         shader_id_(shader_id),
-        validation_id_(validation_id) {}
+        validation_id_(validation_id),
+        opt_direct_reads_(opt_direct_reads) {}
 
   // Initialize state for instrumentation of module.
   void InitializeInstrument();
@@ -196,6 +199,9 @@ class InstrumentPass : public Pass {
                            const std::vector<uint32_t>& validation_ids,
                            InstructionBuilder* builder);
 
+  // Return true if all instructions in |ids| are constants or spec constants.
+  bool AllConstant(const std::vector<uint32_t>& ids);
+
   // Generate in |builder| instructions to read the unsigned integer from the
   // input buffer specified by the offsets in |offset_ids|. Given offsets
   // o0, o1, ... oN, and input buffer ibuf, return the id for the value:
@@ -284,6 +290,12 @@ class InstrumentPass : public Pass {
   // if it doesn't exist.
   uint32_t GetDirectReadFunctionId(uint32_t param_cnt);
 
+  // Split block |block_itr| into two new blocks where the second block
+  // contains |inst_itr| and place in |new_blocks|.
+  void SplitBlock(BasicBlock::iterator inst_itr,
+                  UptrVectorIterator<BasicBlock> block_itr,
+                  std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
+
   // 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
@@ -428,6 +440,29 @@ class InstrumentPass : public Pass {
 
   // Post-instrumentation same-block op ids
   std::unordered_map<uint32_t, uint32_t> same_block_post_;
+
+  // Map function calls to result id. Clear for every function.
+  // This is for debug input reads with constant arguments that
+  // have been generated into the first block of the function.
+  // This mechanism is used to avoid multiple identical debug
+  // input buffer reads.
+  struct vector_hash_ {
+    std::size_t operator()(const std::vector<uint32_t>& v) const {
+      std::size_t hash = v.size();
+      for (auto& u : v) {
+        hash ^= u + 0x9e3779b9 + (hash << 11) + (hash >> 21);
+      }
+      return hash;
+    }
+  };
+  std::unordered_map<std::vector<uint32_t>, uint32_t, vector_hash_> call2id_;
+
+  // Function currently being instrumented
+  Function* curr_func_;
+
+  // Optimize direct debug input buffer reads. Specifically, move all such
+  // reads with constant args to first block and reuse them.
+  bool opt_direct_reads_;
 };
 
 }  // namespace opt

+ 1 - 0
3rdparty/spirv-tools/source/opt/local_access_chain_convert_pass.cpp

@@ -369,6 +369,7 @@ void LocalAccessChainConvertPass::InitExtensions() {
       "SPV_AMD_gpu_shader_half_float",
       "SPV_KHR_shader_draw_parameters",
       "SPV_KHR_subgroup_vote",
+      "SPV_KHR_8bit_storage",
       "SPV_KHR_16bit_storage",
       "SPV_KHR_device_group",
       "SPV_KHR_multiview",

+ 1 - 0
3rdparty/spirv-tools/source/opt/local_single_block_elim_pass.cpp

@@ -232,6 +232,7 @@ void LocalSingleBlockLoadStoreElimPass::InitExtensions() {
       "SPV_AMD_gpu_shader_half_float",
       "SPV_KHR_shader_draw_parameters",
       "SPV_KHR_subgroup_vote",
+      "SPV_KHR_8bit_storage",
       "SPV_KHR_16bit_storage",
       "SPV_KHR_device_group",
       "SPV_KHR_multiview",

+ 1 - 0
3rdparty/spirv-tools/source/opt/local_single_store_elim_pass.cpp

@@ -88,6 +88,7 @@ void LocalSingleStoreElimPass::InitExtensionAllowList() {
       "SPV_AMD_gpu_shader_half_float",
       "SPV_KHR_shader_draw_parameters",
       "SPV_KHR_subgroup_vote",
+      "SPV_KHR_8bit_storage",
       "SPV_KHR_16bit_storage",
       "SPV_KHR_device_group",
       "SPV_KHR_multiview",

+ 1 - 1
3rdparty/spirv-tools/source/opt/loop_peeling.cpp

@@ -1063,7 +1063,7 @@ LoopPeelingPass::LoopPeelingInfo::HandleInequality(CmpOperator cmp_op,
   }
 
   uint32_t cast_iteration = 0;
-  // coherence check: can we fit |iteration| in a uint32_t ?
+  // Integrity check: can we fit |iteration| in a uint32_t ?
   if (static_cast<uint64_t>(iteration) < std::numeric_limits<uint32_t>::max()) {
     cast_iteration = static_cast<uint32_t>(iteration);
   }

+ 12 - 4
3rdparty/spirv-tools/source/opt/optimizer.cpp

@@ -427,6 +427,12 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag) {
     RegisterPass(CreateDeadBranchElimPass());
     RegisterPass(CreateBlockMergePass());
     RegisterPass(CreateAggressiveDCEPass());
+  } else if (pass_name == "inst-buff-oob-check") {
+    RegisterPass(CreateInstBindlessCheckPass(7, 23, false, false, true));
+    RegisterPass(CreateSimplificationPass());
+    RegisterPass(CreateDeadBranchElimPass());
+    RegisterPass(CreateBlockMergePass());
+    RegisterPass(CreateAggressiveDCEPass());
   } else if (pass_name == "inst-buff-addr-check") {
     RegisterPass(CreateInstBuffAddrCheckPass(7, 23));
     RegisterPass(CreateAggressiveDCEPass());
@@ -579,8 +585,8 @@ bool Optimizer::Run(const uint32_t* original_binary,
 
 #ifndef NDEBUG
   // We do not keep the result id of DebugScope in struct DebugScope.
-  // Instead, we assign random ids for them, which results in coherence
-  // check failures. We want to skip the coherence check when the module
+  // Instead, we assign random ids for them, which results in integrity
+  // check failures. We want to skip the integrity check when the module
   // contains DebugScope instructions.
   if (status == opt::Pass::Status::SuccessWithoutChange &&
       !context->module()->ContainsDebugScope()) {
@@ -894,10 +900,12 @@ Optimizer::PassToken CreateUpgradeMemoryModelPass() {
 Optimizer::PassToken CreateInstBindlessCheckPass(uint32_t desc_set,
                                                  uint32_t shader_id,
                                                  bool input_length_enable,
-                                                 bool input_init_enable) {
+                                                 bool input_init_enable,
+                                                 bool input_buff_oob_enable) {
   return MakeUnique<Optimizer::PassToken::Impl>(
       MakeUnique<opt::InstBindlessCheckPass>(
-          desc_set, shader_id, input_length_enable, input_init_enable));
+          desc_set, shader_id, input_length_enable, input_init_enable,
+          input_buff_oob_enable));
 }
 
 Optimizer::PassToken CreateInstDebugPrintfPass(uint32_t desc_set,

+ 13 - 0
3rdparty/spirv-tools/source/opt/struct_cfg_analysis.cpp

@@ -128,6 +128,19 @@ uint32_t StructuredCFGAnalysis::MergeBlock(uint32_t bb_id) {
   return merge_inst->GetSingleWordInOperand(kMergeNodeIndex);
 }
 
+uint32_t StructuredCFGAnalysis::NestingDepth(uint32_t bb_id) {
+  uint32_t result = 0;
+
+  // Find the merge block of the current merge construct as long as the block is
+  // inside a merge construct, exiting one for each iteration.
+  for (uint32_t merge_block_id = MergeBlock(bb_id); merge_block_id != 0;
+       merge_block_id = MergeBlock(merge_block_id)) {
+    result++;
+  }
+
+  return result;
+}
+
 uint32_t StructuredCFGAnalysis::LoopMergeBlock(uint32_t bb_id) {
   uint32_t header_id = ContainingLoop(bb_id);
   if (header_id == 0) {

+ 5 - 0
3rdparty/spirv-tools/source/opt/struct_cfg_analysis.h

@@ -53,6 +53,11 @@ class StructuredCFGAnalysis {
   // merge construct.
   uint32_t MergeBlock(uint32_t bb_id);
 
+  // Returns the nesting depth of the given block, i.e. the number of merge
+  // constructs containing it. Headers and merge blocks are not considered part
+  // of the corresponding merge constructs.
+  uint32_t NestingDepth(uint32_t block_id);
+
   // Returns the id of the header of the innermost loop construct
   // that contains |bb_id|.  Return |0| if |bb_id| is not contained in any loop
   // construct.

+ 1 - 0
3rdparty/spirv-tools/source/reduce/CMakeLists.txt

@@ -50,6 +50,7 @@ set(SPIRV_TOOLS_REDUCE_SOURCES
         operand_to_dominating_id_reduction_opportunity_finder.cpp
         reducer.cpp
         reduction_opportunity.cpp
+        reduction_opportunity_finder.cpp
         reduction_pass.cpp
         reduction_util.cpp
         remove_block_reduction_opportunity.cpp

+ 6 - 8
3rdparty/spirv-tools/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp

@@ -20,12 +20,10 @@
 namespace spvtools {
 namespace reduce {
 
-using opt::IRContext;
-using opt::Instruction;
-
 std::vector<std::unique_ptr<ReductionOpportunity>>
 ConditionalBranchToSimpleConditionalBranchOpportunityFinder::
-    GetAvailableOpportunities(IRContext* context) const {
+    GetAvailableOpportunities(opt::IRContext* context,
+                              uint32_t target_function) const {
   std::vector<std::unique_ptr<ReductionOpportunity>> result;
 
   // Find the opportunities for redirecting all false targets before the
@@ -34,12 +32,12 @@ ConditionalBranchToSimpleConditionalBranchOpportunityFinder::
   // reducer is improved by avoiding contiguous opportunities that disable one
   // another.
   for (bool redirect_to_true : {true, false}) {
-    // Consider every function.
-    for (auto& function : *context->module()) {
+    // Consider every relevant function.
+    for (auto* function : GetTargetFunctions(context, target_function)) {
       // Consider every block in the function.
-      for (auto& block : function) {
+      for (auto& block : *function) {
         // The terminator must be SpvOpBranchConditional.
-        Instruction* terminator = block.terminator();
+        opt::Instruction* terminator = block.terminator();
         if (terminator->opcode() != SpvOpBranchConditional) {
           continue;
         }

+ 1 - 1
3rdparty/spirv-tools/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h

@@ -26,7 +26,7 @@ class ConditionalBranchToSimpleConditionalBranchOpportunityFinder
     : public ReductionOpportunityFinder {
  public:
   std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
-      opt::IRContext* context) const override;
+      opt::IRContext* context, uint32_t target_function) const override;
 
   std::string GetName() const override;
 };

+ 4 - 6
3rdparty/spirv-tools/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.cpp

@@ -19,13 +19,10 @@
 namespace spvtools {
 namespace reduce {
 
-using opt::IRContext;
-using opt::Instruction;
-
 ConditionalBranchToSimpleConditionalBranchReductionOpportunity::
     ConditionalBranchToSimpleConditionalBranchReductionOpportunity(
-        IRContext* context, Instruction* conditional_branch_instruction,
-        bool redirect_to_true)
+        opt::IRContext* context,
+        opt::Instruction* conditional_branch_instruction, bool redirect_to_true)
     : context_(context),
       conditional_branch_instruction_(conditional_branch_instruction),
       redirect_to_true_(redirect_to_true) {}
@@ -63,7 +60,8 @@ void ConditionalBranchToSimpleConditionalBranchReductionOpportunity::Apply() {
       context_->cfg()->block(old_successor_block_id));
 
   // We have changed the CFG.
-  context_->InvalidateAnalysesExceptFor(IRContext::Analysis::kAnalysisNone);
+  context_->InvalidateAnalysesExceptFor(
+      opt::IRContext::Analysis::kAnalysisNone);
 }
 
 }  // namespace reduce

+ 5 - 7
3rdparty/spirv-tools/source/reduce/merge_blocks_reduction_opportunity.cpp

@@ -20,12 +20,8 @@
 namespace spvtools {
 namespace reduce {
 
-using opt::BasicBlock;
-using opt::Function;
-using opt::IRContext;
-
 MergeBlocksReductionOpportunity::MergeBlocksReductionOpportunity(
-    IRContext* context, Function* function, BasicBlock* block) {
+    opt::IRContext* context, opt::Function* function, opt::BasicBlock* block) {
   // Precondition: the terminator has to be OpBranch.
   assert(block->terminator()->opcode() == SpvOpBranch);
   context_ = context;
@@ -49,7 +45,8 @@ bool MergeBlocksReductionOpportunity::PreconditionHolds() {
          "For a successor to be merged into its predecessor, exactly one "
          "predecessor must be present.");
   const uint32_t predecessor_id = predecessors[0];
-  BasicBlock* predecessor_block = context_->get_instr_block(predecessor_id);
+  opt::BasicBlock* predecessor_block =
+      context_->get_instr_block(predecessor_id);
   return opt::blockmergeutil::CanMergeWithSuccessor(context_,
                                                     predecessor_block);
 }
@@ -70,7 +67,8 @@ void MergeBlocksReductionOpportunity::Apply() {
     if (bi->id() == predecessor_id) {
       opt::blockmergeutil::MergeWithSuccessor(context_, function_, bi);
       // Block merging changes the control flow graph, so invalidate it.
-      context_->InvalidateAnalysesExceptFor(IRContext::Analysis::kAnalysisNone);
+      context_->InvalidateAnalysesExceptFor(
+          opt::IRContext::Analysis::kAnalysisNone);
       return;
     }
   }

+ 4 - 6
3rdparty/spirv-tools/source/reduce/merge_blocks_reduction_opportunity_finder.cpp

@@ -19,25 +19,23 @@
 namespace spvtools {
 namespace reduce {
 
-using opt::IRContext;
-
 std::string MergeBlocksReductionOpportunityFinder::GetName() const {
   return "MergeBlocksReductionOpportunityFinder";
 }
 
 std::vector<std::unique_ptr<ReductionOpportunity>>
 MergeBlocksReductionOpportunityFinder::GetAvailableOpportunities(
-    IRContext* context) const {
+    opt::IRContext* context, uint32_t target_function) const {
   std::vector<std::unique_ptr<ReductionOpportunity>> result;
 
   // Consider every block in every function.
-  for (auto& function : *context->module()) {
-    for (auto& block : function) {
+  for (auto* function : GetTargetFunctions(context, target_function)) {
+    for (auto& block : *function) {
       // See whether it is possible to merge this block with its successor.
       if (opt::blockmergeutil::CanMergeWithSuccessor(context, &block)) {
         // It is, so record an opportunity to do this.
         result.push_back(spvtools::MakeUnique<MergeBlocksReductionOpportunity>(
-            context, &function, &block));
+            context, function, &block));
       }
     }
   }

+ 1 - 1
3rdparty/spirv-tools/source/reduce/merge_blocks_reduction_opportunity_finder.h

@@ -31,7 +31,7 @@ class MergeBlocksReductionOpportunityFinder
   std::string GetName() const final;
 
   std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
-      opt::IRContext* context) const final;
+      opt::IRContext* context, uint32_t target_function) const final;
 
  private:
 };

+ 3 - 5
3rdparty/spirv-tools/source/reduce/operand_to_const_reduction_opportunity_finder.cpp

@@ -20,11 +20,9 @@
 namespace spvtools {
 namespace reduce {
 
-using opt::IRContext;
-
 std::vector<std::unique_ptr<ReductionOpportunity>>
 OperandToConstReductionOpportunityFinder::GetAvailableOpportunities(
-    IRContext* context) const {
+    opt::IRContext* context, uint32_t target_function) const {
   std::vector<std::unique_ptr<ReductionOpportunity>> result;
   assert(result.empty());
 
@@ -37,8 +35,8 @@ OperandToConstReductionOpportunityFinder::GetAvailableOpportunities(
   // contiguous blocks of opportunities early on, and we want to avoid having a
   // large block of incompatible opportunities if possible.
   for (const auto& constant : context->GetConstants()) {
-    for (auto& function : *context->module()) {
-      for (auto& block : function) {
+    for (auto* function : GetTargetFunctions(context, target_function)) {
+      for (auto& block : *function) {
         for (auto& inst : block) {
           // We iterate through the operands using an explicit index (rather
           // than using a lambda) so that we use said index in the construction

+ 1 - 1
3rdparty/spirv-tools/source/reduce/operand_to_const_reduction_opportunity_finder.h

@@ -33,7 +33,7 @@ class OperandToConstReductionOpportunityFinder
   std::string GetName() const final;
 
   std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
-      opt::IRContext* context) const final;
+      opt::IRContext* context, uint32_t target_function) const final;
 
  private:
 };

+ 10 - 15
3rdparty/spirv-tools/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.cpp

@@ -20,13 +20,9 @@
 namespace spvtools {
 namespace reduce {
 
-using opt::Function;
-using opt::IRContext;
-using opt::Instruction;
-
 std::vector<std::unique_ptr<ReductionOpportunity>>
 OperandToDominatingIdReductionOpportunityFinder::GetAvailableOpportunities(
-    IRContext* context) const {
+    opt::IRContext* context, uint32_t target_function) const {
   std::vector<std::unique_ptr<ReductionOpportunity>> result;
 
   // Go through every instruction in every block, considering it as a potential
@@ -42,15 +38,15 @@ OperandToDominatingIdReductionOpportunityFinder::GetAvailableOpportunities(
   // to prioritise replacing e with its smallest sub-expressions; generalising
   // this idea to dominating ids this roughly corresponds to more distant
   // dominators.
-  for (auto& function : *context->module()) {
-    for (auto dominating_block = function.begin();
-         dominating_block != function.end(); ++dominating_block) {
+  for (auto* function : GetTargetFunctions(context, target_function)) {
+    for (auto dominating_block = function->begin();
+         dominating_block != function->end(); ++dominating_block) {
       for (auto& dominating_inst : *dominating_block) {
         if (dominating_inst.HasResultId() && dominating_inst.type_id()) {
           // Consider replacing any operand with matching type in a dominated
           // instruction with the id generated by this instruction.
           GetOpportunitiesForDominatingInst(
-              &result, &dominating_inst, dominating_block, &function, context);
+              &result, &dominating_inst, dominating_block, function, context);
         }
       }
     }
@@ -61,9 +57,9 @@ OperandToDominatingIdReductionOpportunityFinder::GetAvailableOpportunities(
 void OperandToDominatingIdReductionOpportunityFinder::
     GetOpportunitiesForDominatingInst(
         std::vector<std::unique_ptr<ReductionOpportunity>>* opportunities,
-        Instruction* candidate_dominator,
-        Function::iterator candidate_dominator_block, Function* function,
-        IRContext* context) const {
+        opt::Instruction* candidate_dominator,
+        opt::Function::iterator candidate_dominator_block,
+        opt::Function* function, opt::IRContext* context) const {
   assert(candidate_dominator->HasResultId());
   assert(candidate_dominator->type_id());
   auto dominator_analysis = context->GetDominatorAnalysis(function);
@@ -91,9 +87,8 @@ void OperandToDominatingIdReductionOpportunityFinder::
             // constant.  It is thus not relevant to this pass.
             continue;
           }
-          // Coherence check: we should not get here if the argument is a
-          // constant.
-          assert(!context->get_constant_mgr()->GetConstantFromInst(def));
+          assert(!context->get_constant_mgr()->GetConstantFromInst(def) &&
+                 "We should not get here if the argument is a constant.");
           if (def->type_id() != candidate_dominator->type_id()) {
             // The types need to match.
             continue;

+ 1 - 1
3rdparty/spirv-tools/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h

@@ -40,7 +40,7 @@ class OperandToDominatingIdReductionOpportunityFinder
   std::string GetName() const final;
 
   std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
-      opt::IRContext* context) const final;
+      opt::IRContext* context, uint32_t target_function) const final;
 
  private:
   void GetOpportunitiesForDominatingInst(

+ 3 - 5
3rdparty/spirv-tools/source/reduce/operand_to_undef_reduction_opportunity_finder.cpp

@@ -20,15 +20,13 @@
 namespace spvtools {
 namespace reduce {
 
-using opt::IRContext;
-
 std::vector<std::unique_ptr<ReductionOpportunity>>
 OperandToUndefReductionOpportunityFinder::GetAvailableOpportunities(
-    IRContext* context) const {
+    opt::IRContext* context, uint32_t target_function) const {
   std::vector<std::unique_ptr<ReductionOpportunity>> result;
 
-  for (auto& function : *context->module()) {
-    for (auto& block : function) {
+  for (auto* function : GetTargetFunctions(context, target_function)) {
+    for (auto& block : *function) {
       for (auto& inst : block) {
         // Skip instructions that result in a pointer type.
         auto type_id = inst.type_id();

+ 1 - 1
3rdparty/spirv-tools/source/reduce/operand_to_undef_reduction_opportunity_finder.h

@@ -32,7 +32,7 @@ class OperandToUndefReductionOpportunityFinder
   std::string GetName() const final;
 
   std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
-      opt::IRContext* context) const final;
+      opt::IRContext* context, uint32_t target_function) const final;
 
  private:
 };

+ 2 - 1
3rdparty/spirv-tools/source/reduce/reducer.cpp

@@ -183,7 +183,8 @@ Reducer::ReductionResultStatus Reducer::RunPasses(
       consumer_(SPV_MSG_INFO, nullptr, {},
                 ("Trying pass " + pass->GetName() + ".").c_str());
       do {
-        auto maybe_result = pass->TryApplyReduction(*current_binary);
+        auto maybe_result =
+            pass->TryApplyReduction(*current_binary, options->target_function);
         if (maybe_result.empty()) {
           // For this round, the pass has no more opportunities (chunks) to
           // apply, so move on to the next pass.

+ 34 - 0
3rdparty/spirv-tools/source/reduce/reduction_opportunity_finder.cpp

@@ -0,0 +1,34 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+std::vector<opt::Function*> ReductionOpportunityFinder::GetTargetFunctions(
+    opt::IRContext* ir_context, uint32_t target_function) {
+  std::vector<opt::Function*> result;
+  for (auto& function : *ir_context->module()) {
+    if (!target_function || function.result_id() == target_function) {
+      result.push_back(&function);
+    }
+  }
+  assert((!target_function || !result.empty()) &&
+         "Requested target function must exist.");
+  return result;
+}
+
+}  // namespace reduce
+}  // namespace spvtools

+ 17 - 2
3rdparty/spirv-tools/source/reduce/reduction_opportunity_finder.h

@@ -15,6 +15,8 @@
 #ifndef SOURCE_REDUCE_REDUCTION_OPPORTUNITY_FINDER_H_
 #define SOURCE_REDUCE_REDUCTION_OPPORTUNITY_FINDER_H_
 
+#include <vector>
+
 #include "source/opt/ir_context.h"
 #include "source/reduce/reduction_opportunity.h"
 
@@ -29,12 +31,25 @@ class ReductionOpportunityFinder {
   virtual ~ReductionOpportunityFinder() = default;
 
   // Finds and returns the reduction opportunities relevant to this pass that
-  // could be applied to the given SPIR-V module.
+  // could be applied to SPIR-V module |context|.
+  //
+  // If |target_function| is non-zero then the available opportunities will be
+  // restricted to only those opportunities that modify the function with result
+  // id |target_function|.
   virtual std::vector<std::unique_ptr<ReductionOpportunity>>
-  GetAvailableOpportunities(opt::IRContext* context) const = 0;
+  GetAvailableOpportunities(opt::IRContext* context,
+                            uint32_t target_function) const = 0;
 
   // Provides a name for the finder.
   virtual std::string GetName() const = 0;
+
+ protected:
+  // Requires that |target_function| is zero or the id of a function in
+  // |ir_context|.  If |target_function| is zero, returns all the functions in
+  // |ir_context|.  Otherwise, returns the function with id |target_function|.
+  // This allows fuzzer passes to restrict attention to a single function.
+  static std::vector<opt::Function*> GetTargetFunctions(
+      opt::IRContext* ir_context, uint32_t target_function);
 };
 
 }  // namespace reduce

+ 2 - 2
3rdparty/spirv-tools/source/reduce/reduction_pass.cpp

@@ -22,7 +22,7 @@ namespace spvtools {
 namespace reduce {
 
 std::vector<uint32_t> ReductionPass::TryApplyReduction(
-    const std::vector<uint32_t>& binary) {
+    const std::vector<uint32_t>& binary, uint32_t target_function) {
   // We represent modules as binaries because (a) attempts at reduction need to
   // end up in binary form to be passed on to SPIR-V-consuming tools, and (b)
   // when we apply a reduction step we need to do it on a fresh version of the
@@ -34,7 +34,7 @@ std::vector<uint32_t> ReductionPass::TryApplyReduction(
   assert(context);
 
   std::vector<std::unique_ptr<ReductionOpportunity>> opportunities =
-      finder_->GetAvailableOpportunities(context.get());
+      finder_->GetAvailableOpportunities(context.get(), target_function);
 
   // There is no point in having a granularity larger than the number of
   // opportunities, so reduce the granularity in this case.

+ 6 - 1
3rdparty/spirv-tools/source/reduce/reduction_pass.h

@@ -49,7 +49,12 @@ class ReductionPass {
   // Returns an empty vector if there are no more chunks left to apply; in this
   // case, the index will be reset and the granularity lowered for the next
   // round.
-  std::vector<uint32_t> TryApplyReduction(const std::vector<uint32_t>& binary);
+  //
+  // If |target_function| is non-zero, only reduction opportunities that
+  // simplify the internals of the function with result id |target_function|
+  // will be applied.
+  std::vector<uint32_t> TryApplyReduction(const std::vector<uint32_t>& binary,
+                                          uint32_t target_function);
 
   // Notifies the reduction pass whether the binary returned from
   // TryApplyReduction is interesting, so that the next call to

+ 64 - 10
3rdparty/spirv-tools/source/reduce/reduction_util.cpp

@@ -15,17 +15,73 @@
 #include "source/reduce/reduction_util.h"
 
 #include "source/opt/ir_context.h"
+#include "source/util/make_unique.h"
 
 namespace spvtools {
 namespace reduce {
 
-using opt::IRContext;
-using opt::Instruction;
-
 const uint32_t kTrueBranchOperandIndex = 1;
 const uint32_t kFalseBranchOperandIndex = 2;
 
-uint32_t FindOrCreateGlobalUndef(IRContext* context, uint32_t type_id) {
+uint32_t FindOrCreateGlobalVariable(opt::IRContext* context,
+                                    uint32_t pointer_type_id) {
+  for (auto& inst : context->module()->types_values()) {
+    if (inst.opcode() != SpvOpVariable) {
+      continue;
+    }
+    if (inst.type_id() == pointer_type_id) {
+      return inst.result_id();
+    }
+  }
+  const uint32_t variable_id = context->TakeNextId();
+  auto variable_inst = MakeUnique<opt::Instruction>(
+      context, SpvOpVariable, pointer_type_id, variable_id,
+      opt::Instruction::OperandList(
+          {{SPV_OPERAND_TYPE_STORAGE_CLASS,
+            {static_cast<uint32_t>(context->get_type_mgr()
+                                       ->GetType(pointer_type_id)
+                                       ->AsPointer()
+                                       ->storage_class())}}}));
+  context->module()->AddGlobalValue(std::move(variable_inst));
+  return variable_id;
+}
+
+uint32_t FindOrCreateFunctionVariable(opt::IRContext* context,
+                                      opt::Function* function,
+                                      uint32_t pointer_type_id) {
+  // The pointer type of a function variable must have Function storage class.
+  assert(context->get_type_mgr()
+             ->GetType(pointer_type_id)
+             ->AsPointer()
+             ->storage_class() == SpvStorageClassFunction);
+
+  // Go through the instructions in the function's first block until we find a
+  // suitable variable, or go past all the variables.
+  opt::BasicBlock::iterator iter = function->begin()->begin();
+  for (;; ++iter) {
+    // We will either find a suitable variable, or find a non-variable
+    // instruction; we won't exhaust all instructions.
+    assert(iter != function->begin()->end());
+    if (iter->opcode() != SpvOpVariable) {
+      // If we see a non-variable, we have gone through all the variables.
+      break;
+    }
+    if (iter->type_id() == pointer_type_id) {
+      return iter->result_id();
+    }
+  }
+  // At this point, iter refers to the first non-function instruction of the
+  // function's entry block.
+  const uint32_t variable_id = context->TakeNextId();
+  auto variable_inst = MakeUnique<opt::Instruction>(
+      context, SpvOpVariable, pointer_type_id, variable_id,
+      opt::Instruction::OperandList(
+          {{SPV_OPERAND_TYPE_STORAGE_CLASS, {SpvStorageClassFunction}}}));
+  iter->InsertBefore(std::move(variable_inst));
+  return variable_id;
+}
+
+uint32_t FindOrCreateGlobalUndef(opt::IRContext* context, uint32_t type_id) {
   for (auto& inst : context->module()->types_values()) {
     if (inst.opcode() != SpvOpUndef) {
       continue;
@@ -34,11 +90,9 @@ uint32_t FindOrCreateGlobalUndef(IRContext* context, uint32_t type_id) {
       return inst.result_id();
     }
   }
-  // TODO(2182): this is adapted from MemPass::Type2Undef.  In due course it
-  // would be good to factor out this duplication.
   const uint32_t undef_id = context->TakeNextId();
-  std::unique_ptr<Instruction> undef_inst(
-      new Instruction(context, SpvOpUndef, type_id, undef_id, {}));
+  auto undef_inst = MakeUnique<opt::Instruction>(
+      context, SpvOpUndef, type_id, undef_id, opt::Instruction::OperandList());
   assert(undef_id == undef_inst->result_id());
   context->module()->AddGlobalValue(std::move(undef_inst));
   return undef_id;
@@ -46,8 +100,8 @@ uint32_t FindOrCreateGlobalUndef(IRContext* context, uint32_t type_id) {
 
 void AdaptPhiInstructionsForRemovedEdge(uint32_t from_id,
                                         opt::BasicBlock* to_block) {
-  to_block->ForEachPhiInst([&from_id](Instruction* phi_inst) {
-    Instruction::OperandList new_in_operands;
+  to_block->ForEachPhiInst([&from_id](opt::Instruction* phi_inst) {
+    opt::Instruction::OperandList new_in_operands;
     // Go through the OpPhi's input operands in (variable, parent) pairs.
     for (uint32_t index = 0; index < phi_inst->NumInOperands(); index += 2) {
       // Keep all pairs where the parent is not the block from which the edge

+ 10 - 0
3rdparty/spirv-tools/source/reduce/reduction_util.h

@@ -26,6 +26,16 @@ namespace reduce {
 extern const uint32_t kTrueBranchOperandIndex;
 extern const uint32_t kFalseBranchOperandIndex;
 
+// Returns a global OpVariable of type |pointer_type_id|, adding one if none
+// exist.
+uint32_t FindOrCreateGlobalVariable(opt::IRContext* context,
+                                    uint32_t pointer_type_id);
+
+// Returns an OpVariable of type |pointer_type_id| declared in |function|,
+// adding one if none exist.
+uint32_t FindOrCreateFunctionVariable(opt::IRContext* context, opt::Function*,
+                                      uint32_t pointer_type_id);
+
 // Returns an OpUndef id from the global value list that is of the given type,
 // adding one if it does not exist.
 uint32_t FindOrCreateGlobalUndef(opt::IRContext* context, uint32_t type_id);

+ 1 - 4
3rdparty/spirv-tools/source/reduce/remove_block_reduction_opportunity.cpp

@@ -19,11 +19,8 @@
 namespace spvtools {
 namespace reduce {
 
-using opt::BasicBlock;
-using opt::Function;
-
 RemoveBlockReductionOpportunity::RemoveBlockReductionOpportunity(
-    Function* function, BasicBlock* block)
+    opt::Function* function, opt::BasicBlock* block)
     : function_(function), block_(block) {
   // precondition:
   assert(block_->begin() != block_->end() &&

+ 18 - 21
3rdparty/spirv-tools/source/reduce/remove_block_reduction_opportunity_finder.cpp

@@ -19,25 +19,21 @@
 namespace spvtools {
 namespace reduce {
 
-using opt::Function;
-using opt::IRContext;
-using opt::Instruction;
-
 std::string RemoveBlockReductionOpportunityFinder::GetName() const {
   return "RemoveBlockReductionOpportunityFinder";
 }
 
 std::vector<std::unique_ptr<ReductionOpportunity>>
 RemoveBlockReductionOpportunityFinder::GetAvailableOpportunities(
-    IRContext* context) const {
+    opt::IRContext* context, uint32_t target_function) const {
   std::vector<std::unique_ptr<ReductionOpportunity>> result;
 
-  // Consider every block in every function.
-  for (auto& function : *context->module()) {
-    for (auto bi = function.begin(); bi != function.end(); ++bi) {
-      if (IsBlockValidOpportunity(context, function, bi)) {
-        result.push_back(spvtools::MakeUnique<RemoveBlockReductionOpportunity>(
-            &function, &*bi));
+  // Consider every block in every relevant function.
+  for (auto* function : GetTargetFunctions(context, target_function)) {
+    for (auto bi = function->begin(); bi != function->end(); ++bi) {
+      if (IsBlockValidOpportunity(context, function, &bi)) {
+        result.push_back(
+            MakeUnique<RemoveBlockReductionOpportunity>(function, &*bi));
       }
     }
   }
@@ -45,21 +41,22 @@ RemoveBlockReductionOpportunityFinder::GetAvailableOpportunities(
 }
 
 bool RemoveBlockReductionOpportunityFinder::IsBlockValidOpportunity(
-    IRContext* context, Function& function, Function::iterator& bi) {
-  assert(bi != function.end() && "Block iterator was out of bounds");
+    opt::IRContext* context, opt::Function* function,
+    opt::Function::iterator* bi) {
+  assert(*bi != function->end() && "Block iterator was out of bounds");
 
   // Don't remove first block; we don't want to end up with no blocks.
-  if (bi == function.begin()) {
+  if (*bi == function->begin()) {
     return false;
   }
 
   // Don't remove blocks with references.
-  if (context->get_def_use_mgr()->NumUsers(bi->id()) > 0) {
+  if (context->get_def_use_mgr()->NumUsers((*bi)->id()) > 0) {
     return false;
   }
 
   // Don't remove blocks whose instructions have outside references.
-  if (!BlockInstructionsHaveNoOutsideReferences(context, bi)) {
+  if (!BlockInstructionsHaveNoOutsideReferences(context, *bi)) {
     return false;
   }
 
@@ -67,19 +64,19 @@ bool RemoveBlockReductionOpportunityFinder::IsBlockValidOpportunity(
 }
 
 bool RemoveBlockReductionOpportunityFinder::
-    BlockInstructionsHaveNoOutsideReferences(IRContext* context,
-                                             const Function::iterator& bi) {
+    BlockInstructionsHaveNoOutsideReferences(
+        opt::IRContext* context, const opt::Function::iterator& bi) {
   // Get all instructions in block.
   std::unordered_set<uint32_t> instructions_in_block;
-  for (const Instruction& instruction : *bi) {
+  for (const opt::Instruction& instruction : *bi) {
     instructions_in_block.insert(instruction.unique_id());
   }
 
   // For each instruction...
-  for (const Instruction& instruction : *bi) {
+  for (const opt::Instruction& instruction : *bi) {
     // For each use of the instruction...
     bool no_uses_outside_block = context->get_def_use_mgr()->WhileEachUser(
-        &instruction, [&instructions_in_block](Instruction* user) -> bool {
+        &instruction, [&instructions_in_block](opt::Instruction* user) -> bool {
           // If the use is in this block, continue (return true). Otherwise, we
           // found an outside use; return false (and stop).
           return instructions_in_block.find(user->unique_id()) !=

+ 3 - 3
3rdparty/spirv-tools/source/reduce/remove_block_reduction_opportunity_finder.h

@@ -34,14 +34,14 @@ class RemoveBlockReductionOpportunityFinder
   std::string GetName() const final;
 
   std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
-      opt::IRContext* context) const final;
+      opt::IRContext* context, uint32_t target_function) const final;
 
  private:
   // Returns true if the block |bi| in function |function| is a valid
   // opportunity according to various restrictions.
   static bool IsBlockValidOpportunity(opt::IRContext* context,
-                                      opt::Function& function,
-                                      opt::Function::iterator& bi);
+                                      opt::Function* function,
+                                      opt::Function::iterator* bi);
 
   // Returns true if the instructions (definitions) in block |bi| have no
   // references, except for references from inside the block itself.

+ 8 - 1
3rdparty/spirv-tools/source/reduce/remove_function_reduction_opportunity_finder.cpp

@@ -21,7 +21,14 @@ namespace reduce {
 
 std::vector<std::unique_ptr<ReductionOpportunity>>
 RemoveFunctionReductionOpportunityFinder::GetAvailableOpportunities(
-    opt::IRContext* context) const {
+    opt::IRContext* context, uint32_t target_function) const {
+  if (target_function) {
+    // If we are targeting a specific function then we are only interested in
+    // opportunities that simplify the internals of that function; removing
+    // whole functions does not fit the bill.
+    return {};
+  }
+
   std::vector<std::unique_ptr<ReductionOpportunity>> result;
   // Consider each function.
   for (auto& function : *context->module()) {

+ 1 - 1
3rdparty/spirv-tools/source/reduce/remove_function_reduction_opportunity_finder.h

@@ -31,7 +31,7 @@ class RemoveFunctionReductionOpportunityFinder
   std::string GetName() const final;
 
   std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
-      opt::IRContext* context) const final;
+      opt::IRContext* context, uint32_t target_function) const final;
 
  private:
 };

+ 6 - 10
3rdparty/spirv-tools/source/reduce/remove_selection_reduction_opportunity_finder.cpp

@@ -19,10 +19,6 @@
 namespace spvtools {
 namespace reduce {
 
-using opt::BasicBlock;
-using opt::IRContext;
-using opt::Instruction;
-
 namespace {
 const uint32_t kMergeNodeIndex = 0;
 const uint32_t kContinueNodeIndex = 1;
@@ -34,11 +30,11 @@ std::string RemoveSelectionReductionOpportunityFinder::GetName() const {
 
 std::vector<std::unique_ptr<ReductionOpportunity>>
 RemoveSelectionReductionOpportunityFinder::GetAvailableOpportunities(
-    IRContext* context) const {
+    opt::IRContext* context, uint32_t target_function) const {
   // Get all loop merge and continue blocks so we can check for these later.
   std::unordered_set<uint32_t> merge_and_continue_blocks_from_loops;
-  for (auto& function : *context->module()) {
-    for (auto& block : function) {
+  for (auto* function : GetTargetFunctions(context, target_function)) {
+    for (auto& block : *function) {
       if (auto merge_instruction = block.GetMergeInst()) {
         if (merge_instruction->opcode() == SpvOpLoopMerge) {
           uint32_t merge_block_id =
@@ -73,8 +69,8 @@ RemoveSelectionReductionOpportunityFinder::GetAvailableOpportunities(
 }
 
 bool RemoveSelectionReductionOpportunityFinder::CanOpSelectionMergeBeRemoved(
-    IRContext* context, const BasicBlock& header_block,
-    Instruction* merge_instruction,
+    opt::IRContext* context, const opt::BasicBlock& header_block,
+    opt::Instruction* merge_instruction,
     std::unordered_set<uint32_t> merge_and_continue_blocks_from_loops) {
   assert(header_block.GetMergeInst() == merge_instruction &&
          "CanOpSelectionMergeBeRemoved(...): header block and merge "
@@ -122,7 +118,7 @@ bool RemoveSelectionReductionOpportunityFinder::CanOpSelectionMergeBeRemoved(
         merge_instruction->GetSingleWordOperand(kMergeNodeIndex);
     for (uint32_t predecessor_block_id :
          context->cfg()->preds(merge_block_id)) {
-      const BasicBlock* predecessor_block =
+      const opt::BasicBlock* predecessor_block =
           context->cfg()->block(predecessor_block_id);
       assert(predecessor_block);
       bool found_divergent_successor = false;

+ 1 - 1
3rdparty/spirv-tools/source/reduce/remove_selection_reduction_opportunity_finder.h

@@ -33,7 +33,7 @@ class RemoveSelectionReductionOpportunityFinder
   std::string GetName() const final;
 
   std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
-      opt::IRContext* context) const final;
+      opt::IRContext* context, uint32_t target_function) const final;
 
   // Returns true if the OpSelectionMerge instruction |merge_instruction| in
   // block |header_block| can be removed.

+ 51 - 40
3rdparty/spirv-tools/source/reduce/remove_unused_instruction_reduction_opportunity_finder.cpp

@@ -28,61 +28,72 @@ RemoveUnusedInstructionReductionOpportunityFinder::
 
 std::vector<std::unique_ptr<ReductionOpportunity>>
 RemoveUnusedInstructionReductionOpportunityFinder::GetAvailableOpportunities(
-    opt::IRContext* context) const {
+    opt::IRContext* context, uint32_t target_function) const {
   std::vector<std::unique_ptr<ReductionOpportunity>> result;
 
-  for (auto& inst : context->module()->debugs1()) {
-    if (context->get_def_use_mgr()->NumUses(&inst) > 0) {
-      continue;
-    }
-    result.push_back(MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
-  }
+  if (!target_function) {
+    // We are not restricting reduction to a specific function, so we consider
+    // unused instructions defined outside functions.
 
-  for (auto& inst : context->module()->debugs2()) {
-    if (context->get_def_use_mgr()->NumUses(&inst) > 0) {
-      continue;
+    for (auto& inst : context->module()->debugs1()) {
+      if (context->get_def_use_mgr()->NumUses(&inst) > 0) {
+        continue;
+      }
+      result.push_back(
+          MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
     }
-    result.push_back(MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
-  }
 
-  for (auto& inst : context->module()->debugs3()) {
-    if (context->get_def_use_mgr()->NumUses(&inst) > 0) {
-      continue;
+    for (auto& inst : context->module()->debugs2()) {
+      if (context->get_def_use_mgr()->NumUses(&inst) > 0) {
+        continue;
+      }
+      result.push_back(
+          MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
     }
-    result.push_back(MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
-  }
 
-  for (auto& inst : context->module()->ext_inst_debuginfo()) {
-    if (context->get_def_use_mgr()->NumUses(&inst) > 0) {
-      continue;
+    for (auto& inst : context->module()->debugs3()) {
+      if (context->get_def_use_mgr()->NumUses(&inst) > 0) {
+        continue;
+      }
+      result.push_back(
+          MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
     }
-    result.push_back(MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
-  }
 
-  for (auto& inst : context->module()->types_values()) {
-    if (!remove_constants_and_undefs_ &&
-        spvOpcodeIsConstantOrUndef(inst.opcode())) {
-      continue;
-    }
-    if (!OnlyReferencedByIntimateDecorationOrEntryPointInterface(context,
-                                                                 inst)) {
-      continue;
+    for (auto& inst : context->module()->ext_inst_debuginfo()) {
+      if (context->get_def_use_mgr()->NumUses(&inst) > 0) {
+        continue;
+      }
+      result.push_back(
+          MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
     }
-    result.push_back(MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
-  }
 
-  for (auto& inst : context->module()->annotations()) {
-    if (context->get_def_use_mgr()->NumUsers(&inst) > 0) {
-      continue;
+    for (auto& inst : context->module()->types_values()) {
+      if (!remove_constants_and_undefs_ &&
+          spvOpcodeIsConstantOrUndef(inst.opcode())) {
+        continue;
+      }
+      if (!OnlyReferencedByIntimateDecorationOrEntryPointInterface(context,
+                                                                   inst)) {
+        continue;
+      }
+      result.push_back(
+          MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
     }
-    if (!IsIndependentlyRemovableDecoration(inst)) {
-      continue;
+
+    for (auto& inst : context->module()->annotations()) {
+      if (context->get_def_use_mgr()->NumUsers(&inst) > 0) {
+        continue;
+      }
+      if (!IsIndependentlyRemovableDecoration(inst)) {
+        continue;
+      }
+      result.push_back(
+          MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
     }
-    result.push_back(MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
   }
 
-  for (auto& function : *context->module()) {
-    for (auto& block : function) {
+  for (auto* function : GetTargetFunctions(context, target_function)) {
+    for (auto& block : *function) {
       for (auto& inst : block) {
         if (context->get_def_use_mgr()->NumUses(&inst) > 0) {
           continue;

+ 1 - 1
3rdparty/spirv-tools/source/reduce/remove_unused_instruction_reduction_opportunity_finder.h

@@ -38,7 +38,7 @@ class RemoveUnusedInstructionReductionOpportunityFinder
   std::string GetName() const final;
 
   std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
-      opt::IRContext* context) const final;
+      opt::IRContext* context, uint32_t target_function) const final;
 
  private:
   // Returns true if and only if the only uses of |inst| are by decorations that

+ 8 - 1
3rdparty/spirv-tools/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.cpp

@@ -24,7 +24,14 @@ namespace reduce {
 
 std::vector<std::unique_ptr<ReductionOpportunity>>
 RemoveUnusedStructMemberReductionOpportunityFinder::GetAvailableOpportunities(
-    opt::IRContext* context) const {
+    opt::IRContext* context, uint32_t target_function) const {
+  if (target_function) {
+    // Removing an unused struct member is a global change, as struct types are
+    // global.  We thus do not consider such opportunities if we are targeting
+    // a specific function.
+    return {};
+  }
+
   std::vector<std::unique_ptr<ReductionOpportunity>> result;
 
   // We track those struct members that are never accessed.  We do this by

+ 1 - 1
3rdparty/spirv-tools/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.h

@@ -32,7 +32,7 @@ class RemoveUnusedStructMemberReductionOpportunityFinder
   std::string GetName() const final;
 
   std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
-      opt::IRContext* context) const final;
+      opt::IRContext* context, uint32_t target_function) const final;
 
  private:
   // A helper method to update |unused_members_to_structs| by removing from it

+ 4 - 7
3rdparty/spirv-tools/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.cpp

@@ -20,20 +20,17 @@
 namespace spvtools {
 namespace reduce {
 
-using opt::IRContext;
-using opt::Instruction;
-
 std::vector<std::unique_ptr<ReductionOpportunity>>
 SimpleConditionalBranchToBranchOpportunityFinder::GetAvailableOpportunities(
-    IRContext* context) const {
+    opt::IRContext* context, uint32_t target_function) const {
   std::vector<std::unique_ptr<ReductionOpportunity>> result;
 
   // Consider every function.
-  for (auto& function : *context->module()) {
+  for (auto* function : GetTargetFunctions(context, target_function)) {
     // Consider every block in the function.
-    for (auto& block : function) {
+    for (auto& block : *function) {
       // The terminator must be SpvOpBranchConditional.
-      Instruction* terminator = block.terminator();
+      opt::Instruction* terminator = block.terminator();
       if (terminator->opcode() != SpvOpBranchConditional) {
         continue;
       }

+ 1 - 1
3rdparty/spirv-tools/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h

@@ -26,7 +26,7 @@ class SimpleConditionalBranchToBranchOpportunityFinder
     : public ReductionOpportunityFinder {
  public:
   std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
-      opt::IRContext* context) const override;
+      opt::IRContext* context, uint32_t target_function) const override;
 
   std::string GetName() const override;
 };

+ 1 - 3
3rdparty/spirv-tools/source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.cpp

@@ -19,11 +19,9 @@
 namespace spvtools {
 namespace reduce {
 
-using namespace opt;
-
 SimpleConditionalBranchToBranchReductionOpportunity::
     SimpleConditionalBranchToBranchReductionOpportunity(
-        Instruction* conditional_branch_instruction)
+        opt::Instruction* conditional_branch_instruction)
     : conditional_branch_instruction_(conditional_branch_instruction) {}
 
 bool SimpleConditionalBranchToBranchReductionOpportunity::PreconditionHolds() {

+ 19 - 74
3rdparty/spirv-tools/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp

@@ -21,11 +21,6 @@
 namespace spvtools {
 namespace reduce {
 
-using opt::BasicBlock;
-using opt::IRContext;
-using opt::Instruction;
-using opt::Operand;
-
 namespace {
 const uint32_t kMergeNodeIndex = 0;
 }  // namespace
@@ -58,14 +53,16 @@ void StructuredLoopToSelectionReductionOpportunity::Apply() {
 
   // We have made control flow changes that do not preserve the analyses that
   // were performed.
-  context_->InvalidateAnalysesExceptFor(IRContext::Analysis::kAnalysisNone);
+  context_->InvalidateAnalysesExceptFor(
+      opt::IRContext::Analysis::kAnalysisNone);
 
   // (4) By changing CFG edges we may have created scenarios where ids are used
   // without being dominated; we fix instances of this.
   FixNonDominatedIdUses();
 
   // Invalidate the analyses we just used.
-  context_->InvalidateAnalysesExceptFor(IRContext::Analysis::kAnalysisNone);
+  context_->InvalidateAnalysesExceptFor(
+      opt::IRContext::Analysis::kAnalysisNone);
 }
 
 void StructuredLoopToSelectionReductionOpportunity::RedirectToClosestMergeBlock(
@@ -168,13 +165,14 @@ void StructuredLoopToSelectionReductionOpportunity::RedirectEdge(
 }
 
 void StructuredLoopToSelectionReductionOpportunity::
-    AdaptPhiInstructionsForAddedEdge(uint32_t from_id, BasicBlock* to_block) {
-  to_block->ForEachPhiInst([this, &from_id](Instruction* phi_inst) {
+    AdaptPhiInstructionsForAddedEdge(uint32_t from_id,
+                                     opt::BasicBlock* to_block) {
+  to_block->ForEachPhiInst([this, &from_id](opt::Instruction* phi_inst) {
     // Add to the phi operand an (undef, from_id) pair to reflect the added
     // edge.
     auto undef_id = FindOrCreateGlobalUndef(context_, phi_inst->type_id());
-    phi_inst->AddOperand(Operand(SPV_OPERAND_TYPE_ID, {undef_id}));
-    phi_inst->AddOperand(Operand(SPV_OPERAND_TYPE_ID, {from_id}));
+    phi_inst->AddOperand(opt::Operand(SPV_OPERAND_TYPE_ID, {undef_id}));
+    phi_inst->AddOperand(opt::Operand(SPV_OPERAND_TYPE_ID, {from_id}));
   });
 }
 
@@ -227,7 +225,7 @@ void StructuredLoopToSelectionReductionOpportunity::FixNonDominatedIdUses() {
         continue;
       }
       context_->get_def_use_mgr()->ForEachUse(&def, [this, &block, &def](
-                                                        Instruction* use,
+                                                        opt::Instruction* use,
                                                         uint32_t index) {
         // Ignore uses outside of blocks, such as in OpDecorate.
         if (context_->get_instr_block(use) == nullptr) {
@@ -245,17 +243,20 @@ void StructuredLoopToSelectionReductionOpportunity::FixNonDominatedIdUses() {
               case SpvStorageClassFunction:
                 use->SetOperand(
                     index, {FindOrCreateFunctionVariable(
+                               context_, enclosing_function_,
                                context_->get_type_mgr()->GetId(pointer_type))});
                 break;
               default:
                 // TODO(2183) Need to think carefully about whether it makes
-                // sense to add new variables for all storage classes; it's fine
-                // for Private but might not be OK for input/output storage
-                // classes for example.
+                //  sense to add new variables for all storage classes; it's
+                //  fine for Private but might not be OK for input/output
+                //  storage classes for example.
                 use->SetOperand(
                     index, {FindOrCreateGlobalVariable(
+                               context_,
                                context_->get_type_mgr()->GetId(pointer_type))});
                 break;
+                break;
             }
           } else {
             use->SetOperand(index,
@@ -268,9 +269,10 @@ void StructuredLoopToSelectionReductionOpportunity::FixNonDominatedIdUses() {
 }
 
 bool StructuredLoopToSelectionReductionOpportunity::
-    DefinitionSufficientlyDominatesUse(Instruction* def, Instruction* use,
+    DefinitionSufficientlyDominatesUse(opt::Instruction* def,
+                                       opt::Instruction* use,
                                        uint32_t use_index,
-                                       BasicBlock& def_block) {
+                                       opt::BasicBlock& def_block) {
   if (use->opcode() == SpvOpPhi) {
     // A use in a phi doesn't need to be dominated by its definition, but the
     // associated parent block does need to be dominated by the definition.
@@ -282,62 +284,5 @@ bool StructuredLoopToSelectionReductionOpportunity::
       ->Dominates(def, use);
 }
 
-uint32_t
-StructuredLoopToSelectionReductionOpportunity::FindOrCreateGlobalVariable(
-    uint32_t pointer_type_id) {
-  for (auto& inst : context_->module()->types_values()) {
-    if (inst.opcode() != SpvOpVariable) {
-      continue;
-    }
-    if (inst.type_id() == pointer_type_id) {
-      return inst.result_id();
-    }
-  }
-  const uint32_t variable_id = context_->TakeNextId();
-  std::unique_ptr<Instruction> variable_inst(
-      new Instruction(context_, SpvOpVariable, pointer_type_id, variable_id,
-                      {{SPV_OPERAND_TYPE_STORAGE_CLASS,
-                        {(uint32_t)context_->get_type_mgr()
-                             ->GetType(pointer_type_id)
-                             ->AsPointer()
-                             ->storage_class()}}}));
-  context_->module()->AddGlobalValue(std::move(variable_inst));
-  return variable_id;
-}
-
-uint32_t
-StructuredLoopToSelectionReductionOpportunity::FindOrCreateFunctionVariable(
-    uint32_t pointer_type_id) {
-  // The pointer type of a function variable must have Function storage class.
-  assert(context_->get_type_mgr()
-             ->GetType(pointer_type_id)
-             ->AsPointer()
-             ->storage_class() == SpvStorageClassFunction);
-
-  // Go through the instructions in the function's first block until we find a
-  // suitable variable, or go past all the variables.
-  BasicBlock::iterator iter = enclosing_function_->begin()->begin();
-  for (;; ++iter) {
-    // We will either find a suitable variable, or find a non-variable
-    // instruction; we won't exhaust all instructions.
-    assert(iter != enclosing_function_->begin()->end());
-    if (iter->opcode() != SpvOpVariable) {
-      // If we see a non-variable, we have gone through all the variables.
-      break;
-    }
-    if (iter->type_id() == pointer_type_id) {
-      return iter->result_id();
-    }
-  }
-  // At this point, iter refers to the first non-function instruction of the
-  // function's entry block.
-  const uint32_t variable_id = context_->TakeNextId();
-  std::unique_ptr<Instruction> variable_inst(new Instruction(
-      context_, SpvOpVariable, pointer_type_id, variable_id,
-      {{SPV_OPERAND_TYPE_STORAGE_CLASS, {SpvStorageClassFunction}}}));
-  iter->InsertBefore(std::move(variable_inst));
-  return variable_id;
-}
-
 }  // namespace reduce
 }  // namespace spvtools

+ 0 - 14
3rdparty/spirv-tools/source/reduce/structured_loop_to_selection_reduction_opportunity.h

@@ -86,20 +86,6 @@ class StructuredLoopToSelectionReductionOpportunity
                                           uint32_t use_index,
                                           opt::BasicBlock& def_block);
 
-  // Checks whether the global value list has an OpVariable of the given pointer
-  // type, adding one if not, and returns the id of such an OpVariable.
-  //
-  // TODO(2184): This will likely be used by other reduction passes, so should
-  // be factored out in due course.
-  uint32_t FindOrCreateGlobalVariable(uint32_t pointer_type_id);
-
-  // Checks whether the enclosing function has an OpVariable of the given
-  // pointer type, adding one if not, and returns the id of such an OpVariable.
-  //
-  // TODO(2184): This will likely be used by other reduction passes, so should
-  // be factored out in due course.
-  uint32_t FindOrCreateFunctionVariable(uint32_t pointer_type_id);
-
   opt::IRContext* context_;
   opt::BasicBlock* loop_construct_header_;
   opt::Function* enclosing_function_;

+ 9 - 11
3rdparty/spirv-tools/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.cpp

@@ -19,8 +19,6 @@
 namespace spvtools {
 namespace reduce {
 
-using opt::IRContext;
-
 namespace {
 const uint32_t kMergeNodeIndex = 0;
 const uint32_t kContinueNodeIndex = 1;
@@ -28,12 +26,12 @@ const uint32_t kContinueNodeIndex = 1;
 
 std::vector<std::unique_ptr<ReductionOpportunity>>
 StructuredLoopToSelectionReductionOpportunityFinder::GetAvailableOpportunities(
-    IRContext* context) const {
+    opt::IRContext* context, uint32_t target_function) const {
   std::vector<std::unique_ptr<ReductionOpportunity>> result;
 
   std::set<uint32_t> merge_block_ids;
-  for (auto& function : *context->module()) {
-    for (auto& block : function) {
+  for (auto* function : GetTargetFunctions(context, target_function)) {
+    for (auto& block : *function) {
       auto merge_block_id = block.MergeBlockIdIfAny();
       if (merge_block_id) {
         merge_block_ids.insert(merge_block_id);
@@ -42,8 +40,8 @@ StructuredLoopToSelectionReductionOpportunityFinder::GetAvailableOpportunities(
   }
 
   // Consider each loop construct header in the module.
-  for (auto& function : *context->module()) {
-    for (auto& block : function) {
+  for (auto* function : GetTargetFunctions(context, target_function)) {
+    for (auto& block : *function) {
       auto loop_merge_inst = block.GetLoopMergeInst();
       if (!loop_merge_inst) {
         // This is not a loop construct header.
@@ -71,8 +69,8 @@ StructuredLoopToSelectionReductionOpportunityFinder::GetAvailableOpportunities(
       // so we cautiously do not consider applying a transformation.
       auto merge_block_id =
           loop_merge_inst->GetSingleWordInOperand(kMergeNodeIndex);
-      if (!context->GetDominatorAnalysis(&function)->Dominates(
-              block.id(), merge_block_id)) {
+      if (!context->GetDominatorAnalysis(function)->Dominates(block.id(),
+                                                              merge_block_id)) {
         continue;
       }
 
@@ -80,7 +78,7 @@ StructuredLoopToSelectionReductionOpportunityFinder::GetAvailableOpportunities(
       // construct header.  If not (e.g. because the loop contains OpReturn,
       // OpKill or OpUnreachable), we cautiously do not consider applying
       // a transformation.
-      if (!context->GetPostDominatorAnalysis(&function)->Dominates(
+      if (!context->GetPostDominatorAnalysis(function)->Dominates(
               merge_block_id, block.id())) {
         continue;
       }
@@ -89,7 +87,7 @@ StructuredLoopToSelectionReductionOpportunityFinder::GetAvailableOpportunities(
       // opportunity to do so.
       result.push_back(
           MakeUnique<StructuredLoopToSelectionReductionOpportunity>(
-              context, &block, &function));
+              context, &block, function));
     }
   }
   return result;

+ 1 - 1
3rdparty/spirv-tools/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h

@@ -46,7 +46,7 @@ class StructuredLoopToSelectionReductionOpportunityFinder
   std::string GetName() const final;
 
   std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
-      opt::IRContext* context) const final;
+      opt::IRContext* context, uint32_t target_function) const final;
 
  private:
 };

+ 7 - 1
3rdparty/spirv-tools/source/spirv_fuzzer_options.cpp

@@ -25,7 +25,8 @@ spv_fuzzer_options_t::spv_fuzzer_options_t()
       replay_range(0),
       replay_validation_enabled(false),
       shrinker_step_limit(kDefaultStepLimit),
-      fuzzer_pass_validation_enabled(false) {}
+      fuzzer_pass_validation_enabled(false),
+      all_passes_enabled(false) {}
 
 SPIRV_TOOLS_EXPORT spv_fuzzer_options spvFuzzerOptionsCreate() {
   return new spv_fuzzer_options_t();
@@ -60,3 +61,8 @@ SPIRV_TOOLS_EXPORT void spvFuzzerOptionsEnableFuzzerPassValidation(
     spv_fuzzer_options options) {
   options->fuzzer_pass_validation_enabled = true;
 }
+
+SPIRV_TOOLS_EXPORT void spvFuzzerOptionsEnableAllPasses(
+    spv_fuzzer_options options) {
+  options->all_passes_enabled = true;
+}

+ 3 - 0
3rdparty/spirv-tools/source/spirv_fuzzer_options.h

@@ -40,6 +40,9 @@ struct spv_fuzzer_options_t {
 
   // See spvFuzzerOptionsValidateAfterEveryPass.
   bool fuzzer_pass_validation_enabled;
+
+  // See spvFuzzerOptionsEnableAllPasses.
+  bool all_passes_enabled;
 };
 
 #endif  // SOURCE_SPIRV_FUZZER_OPTIONS_H_

+ 8 - 1
3rdparty/spirv-tools/source/spirv_reducer_options.cpp

@@ -23,7 +23,9 @@ const uint32_t kDefaultStepLimit = 2500;
 }  // namespace
 
 spv_reducer_options_t::spv_reducer_options_t()
-    : step_limit(kDefaultStepLimit), fail_on_validation_error(false) {}
+    : step_limit(kDefaultStepLimit),
+      fail_on_validation_error(false),
+      target_function(0) {}
 
 SPIRV_TOOLS_EXPORT spv_reducer_options spvReducerOptionsCreate() {
   return new spv_reducer_options_t();
@@ -42,3 +44,8 @@ SPIRV_TOOLS_EXPORT void spvReducerOptionsSetFailOnValidationError(
     spv_reducer_options options, bool fail_on_validation_error) {
   options->fail_on_validation_error = fail_on_validation_error;
 }
+
+SPIRV_TOOLS_EXPORT void spvReducerOptionsSetTargetFunction(
+    spv_reducer_options options, uint32_t target_function) {
+  options->target_function = target_function;
+}

+ 3 - 0
3rdparty/spirv-tools/source/spirv_reducer_options.h

@@ -30,6 +30,9 @@ struct spv_reducer_options_t {
 
   // See spvReducerOptionsSetFailOnValidationError.
   bool fail_on_validation_error;
+
+  // See spvReducerOptionsSetTargetFunction.
+  uint32_t target_function;
 };
 
 #endif  // SOURCE_SPIRV_REDUCER_OPTIONS_H_

+ 294 - 5
3rdparty/spirv-tools/source/val/validate_builtins.cpp

@@ -205,6 +205,14 @@ class BuiltInsValidator {
       const Decoration& decoration, const Instruction& inst);
   spv_result_t ValidateWorkgroupSizeAtDefinition(const Decoration& decoration,
                                                  const Instruction& inst);
+  spv_result_t ValidateBaseInstanceOrVertexAtDefinition(
+      const Decoration& decoration, const Instruction& inst);
+  spv_result_t ValidateDrawIndexAtDefinition(const Decoration& decoration,
+                                             const Instruction& inst);
+  spv_result_t ValidateViewIndexAtDefinition(const Decoration& decoration,
+                                             const Instruction& inst);
+  spv_result_t ValidateDeviceIndexAtDefinition(const Decoration& decoration,
+                                               const Instruction& inst);
   // Used for GlobalInvocationId, LocalInvocationId, NumWorkgroups, WorkgroupId.
   spv_result_t ValidateComputeShaderI32Vec3InputAtDefinition(
       const Decoration& decoration, const Instruction& inst);
@@ -339,6 +347,26 @@ class BuiltInsValidator {
       const Instruction& referenced_inst,
       const Instruction& referenced_from_inst);
 
+  spv_result_t ValidateBaseInstanceOrVertexAtReference(
+      const Decoration& decoration, const Instruction& built_in_inst,
+      const Instruction& referenced_inst,
+      const Instruction& referenced_from_inst);
+
+  spv_result_t ValidateDrawIndexAtReference(
+      const Decoration& decoration, const Instruction& built_in_inst,
+      const Instruction& referenced_inst,
+      const Instruction& referenced_from_inst);
+
+  spv_result_t ValidateViewIndexAtReference(
+      const Decoration& decoration, const Instruction& built_in_inst,
+      const Instruction& referenced_inst,
+      const Instruction& referenced_from_inst);
+
+  spv_result_t ValidateDeviceIndexAtReference(
+      const Decoration& decoration, const Instruction& built_in_inst,
+      const Instruction& referenced_inst,
+      const Instruction& referenced_from_inst);
+
   // Used for GlobalInvocationId, LocalInvocationId, NumWorkgroups, WorkgroupId.
   spv_result_t ValidateComputeShaderI32Vec3InputAtReference(
       const Decoration& decoration, const Instruction& built_in_inst,
@@ -2978,6 +3006,259 @@ spv_result_t BuiltInsValidator::ValidateWorkgroupSizeAtReference(
   return SPV_SUCCESS;
 }
 
+spv_result_t BuiltInsValidator::ValidateBaseInstanceOrVertexAtDefinition(
+    const Decoration& decoration, const Instruction& inst) {
+  if (spvIsVulkanEnv(_.context()->target_env)) {
+    if (spv_result_t error = ValidateI32(
+            decoration, inst,
+            [this, &inst,
+             &decoration](const std::string& message) -> spv_result_t {
+              uint32_t vuid = (decoration.params()[0] == SpvBuiltInBaseInstance)
+                                  ? 4183
+                                  : 4186;
+              return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+                     << _.VkErrorID(vuid)
+                     << "According to the Vulkan spec BuiltIn "
+                     << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                                      decoration.params()[0])
+                     << " variable needs to be a 32-bit int scalar. "
+                     << message;
+            })) {
+      return error;
+    }
+  }
+
+  return ValidateBaseInstanceOrVertexAtReference(decoration, inst, inst, inst);
+}
+
+spv_result_t BuiltInsValidator::ValidateBaseInstanceOrVertexAtReference(
+    const Decoration& decoration, const Instruction& built_in_inst,
+    const Instruction& referenced_inst,
+    const Instruction& referenced_from_inst) {
+  uint32_t operand = decoration.params()[0];
+  if (spvIsVulkanEnv(_.context()->target_env)) {
+    const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst);
+    if (storage_class != SpvStorageClassMax &&
+        storage_class != SpvStorageClassInput) {
+      uint32_t vuid = (operand == SpvBuiltInBaseInstance) ? 4182 : 4185;
+      return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+             << _.VkErrorID(vuid) << "Vulkan spec allows BuiltIn "
+             << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                              operand)
+             << " to be only used for variables with Input storage class. "
+             << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
+                                 referenced_from_inst)
+             << " " << GetStorageClassDesc(referenced_from_inst);
+    }
+
+    for (const SpvExecutionModel execution_model : execution_models_) {
+      if (execution_model != SpvExecutionModelVertex) {
+        uint32_t vuid = (operand == SpvBuiltInBaseInstance) ? 4181 : 4184;
+        return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+               << _.VkErrorID(vuid) << "Vulkan spec allows BuiltIn "
+               << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                                operand)
+               << " to be used only with Vertex execution model. "
+               << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
+                                   referenced_from_inst, execution_model);
+      }
+    }
+  }
+
+  if (function_id_ == 0) {
+    // Propagate this rule to all dependant ids in the global scope.
+    id_to_at_reference_checks_[referenced_from_inst.id()].push_back(
+        std::bind(&BuiltInsValidator::ValidateBaseInstanceOrVertexAtReference,
+                  this, decoration, built_in_inst, referenced_from_inst,
+                  std::placeholders::_1));
+  }
+
+  return SPV_SUCCESS;
+}
+
+spv_result_t BuiltInsValidator::ValidateDrawIndexAtDefinition(
+    const Decoration& decoration, const Instruction& inst) {
+  if (spvIsVulkanEnv(_.context()->target_env)) {
+    if (spv_result_t error = ValidateI32(
+            decoration, inst,
+            [this, &inst,
+             &decoration](const std::string& message) -> spv_result_t {
+              return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+                     << _.VkErrorID(4209)
+                     << "According to the Vulkan spec BuiltIn "
+                     << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                                      decoration.params()[0])
+                     << " variable needs to be a 32-bit int scalar. "
+                     << message;
+            })) {
+      return error;
+    }
+  }
+
+  return ValidateDrawIndexAtReference(decoration, inst, inst, inst);
+}
+
+spv_result_t BuiltInsValidator::ValidateDrawIndexAtReference(
+    const Decoration& decoration, const Instruction& built_in_inst,
+    const Instruction& referenced_inst,
+    const Instruction& referenced_from_inst) {
+  uint32_t operand = decoration.params()[0];
+  if (spvIsVulkanEnv(_.context()->target_env)) {
+    const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst);
+    if (storage_class != SpvStorageClassMax &&
+        storage_class != SpvStorageClassInput) {
+      return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+             << _.VkErrorID(4208) << "Vulkan spec allows BuiltIn "
+             << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                              operand)
+             << " to be only used for variables with Input storage class. "
+             << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
+                                 referenced_from_inst)
+             << " " << GetStorageClassDesc(referenced_from_inst);
+    }
+
+    for (const SpvExecutionModel execution_model : execution_models_) {
+      if (execution_model != SpvExecutionModelVertex &&
+          execution_model != SpvExecutionModelMeshNV &&
+          execution_model != SpvExecutionModelTaskNV) {
+        return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+               << _.VkErrorID(4207) << "Vulkan spec allows BuiltIn "
+               << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                                operand)
+               << " to be used only with Vertex, MeshNV, or TaskNV execution "
+                  "model. "
+               << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
+                                   referenced_from_inst, execution_model);
+      }
+    }
+  }
+
+  if (function_id_ == 0) {
+    // Propagate this rule to all dependant ids in the global scope.
+    id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind(
+        &BuiltInsValidator::ValidateDrawIndexAtReference, this, decoration,
+        built_in_inst, referenced_from_inst, std::placeholders::_1));
+  }
+
+  return SPV_SUCCESS;
+}
+
+spv_result_t BuiltInsValidator::ValidateViewIndexAtDefinition(
+    const Decoration& decoration, const Instruction& inst) {
+  if (spvIsVulkanEnv(_.context()->target_env)) {
+    if (spv_result_t error = ValidateI32(
+            decoration, inst,
+            [this, &inst,
+             &decoration](const std::string& message) -> spv_result_t {
+              return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+                     << _.VkErrorID(4403)
+                     << "According to the Vulkan spec BuiltIn "
+                     << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                                      decoration.params()[0])
+                     << " variable needs to be a 32-bit int scalar. "
+                     << message;
+            })) {
+      return error;
+    }
+  }
+
+  return ValidateViewIndexAtReference(decoration, inst, inst, inst);
+}
+
+spv_result_t BuiltInsValidator::ValidateViewIndexAtReference(
+    const Decoration& decoration, const Instruction& built_in_inst,
+    const Instruction& referenced_inst,
+    const Instruction& referenced_from_inst) {
+  uint32_t operand = decoration.params()[0];
+  if (spvIsVulkanEnv(_.context()->target_env)) {
+    const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst);
+    if (storage_class != SpvStorageClassMax &&
+        storage_class != SpvStorageClassInput) {
+      return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+             << _.VkErrorID(4402) << "Vulkan spec allows BuiltIn "
+             << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                              operand)
+             << " to be only used for variables with Input storage class. "
+             << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
+                                 referenced_from_inst)
+             << " " << GetStorageClassDesc(referenced_from_inst);
+    }
+
+    for (const SpvExecutionModel execution_model : execution_models_) {
+      if (execution_model == SpvExecutionModelGLCompute) {
+        return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+               << _.VkErrorID(4401) << "Vulkan spec allows BuiltIn "
+               << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                                operand)
+               << " to be not be used with GLCompute execution model. "
+               << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
+                                   referenced_from_inst, execution_model);
+      }
+    }
+  }
+
+  if (function_id_ == 0) {
+    // Propagate this rule to all dependant ids in the global scope.
+    id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind(
+        &BuiltInsValidator::ValidateViewIndexAtReference, this, decoration,
+        built_in_inst, referenced_from_inst, std::placeholders::_1));
+  }
+
+  return SPV_SUCCESS;
+}
+
+spv_result_t BuiltInsValidator::ValidateDeviceIndexAtDefinition(
+    const Decoration& decoration, const Instruction& inst) {
+  if (spvIsVulkanEnv(_.context()->target_env)) {
+    if (spv_result_t error = ValidateI32(
+            decoration, inst,
+            [this, &inst,
+             &decoration](const std::string& message) -> spv_result_t {
+              return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+                     << _.VkErrorID(4206)
+                     << "According to the Vulkan spec BuiltIn "
+                     << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                                      decoration.params()[0])
+                     << " variable needs to be a 32-bit int scalar. "
+                     << message;
+            })) {
+      return error;
+    }
+  }
+
+  return ValidateDeviceIndexAtReference(decoration, inst, inst, inst);
+}
+
+spv_result_t BuiltInsValidator::ValidateDeviceIndexAtReference(
+    const Decoration& decoration, const Instruction& built_in_inst,
+    const Instruction& referenced_inst,
+    const Instruction& referenced_from_inst) {
+  uint32_t operand = decoration.params()[0];
+  if (spvIsVulkanEnv(_.context()->target_env)) {
+    const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst);
+    if (storage_class != SpvStorageClassMax &&
+        storage_class != SpvStorageClassInput) {
+      return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+             << _.VkErrorID(4205) << "Vulkan spec allows BuiltIn "
+             << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                              operand)
+             << " to be only used for variables with Input storage class. "
+             << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
+                                 referenced_from_inst)
+             << " " << GetStorageClassDesc(referenced_from_inst);
+    }
+  }
+
+  if (function_id_ == 0) {
+    // Propagate this rule to all dependant ids in the global scope.
+    id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind(
+        &BuiltInsValidator::ValidateDeviceIndexAtReference, this, decoration,
+        built_in_inst, referenced_from_inst, std::placeholders::_1));
+  }
+
+  return SPV_SUCCESS;
+}
+
 spv_result_t BuiltInsValidator::ValidateSMBuiltinsAtDefinition(
     const Decoration& decoration, const Instruction& inst) {
   if (spvIsVulkanEnv(_.context()->target_env)) {
@@ -3168,6 +3449,19 @@ spv_result_t BuiltInsValidator::ValidateSingleBuiltInAtDefinition(
     case SpvBuiltInSMIDNV: {
       return ValidateSMBuiltinsAtDefinition(decoration, inst);
     }
+    case SpvBuiltInBaseInstance:
+    case SpvBuiltInBaseVertex: {
+      return ValidateBaseInstanceOrVertexAtDefinition(decoration, inst);
+    }
+    case SpvBuiltInDrawIndex: {
+      return ValidateDrawIndexAtDefinition(decoration, inst);
+    }
+    case SpvBuiltInViewIndex: {
+      return ValidateViewIndexAtDefinition(decoration, inst);
+    }
+    case SpvBuiltInDeviceIndex: {
+      return ValidateDeviceIndexAtDefinition(decoration, inst);
+    }
     case SpvBuiltInWorkDim:
     case SpvBuiltInGlobalSize:
     case SpvBuiltInEnqueuedWorkgroupSize:
@@ -3175,11 +3469,6 @@ spv_result_t BuiltInsValidator::ValidateSingleBuiltInAtDefinition(
     case SpvBuiltInGlobalLinearId:
     case SpvBuiltInSubgroupMaxSize:
     case SpvBuiltInNumEnqueuedSubgroups:
-    case SpvBuiltInBaseVertex:
-    case SpvBuiltInBaseInstance:
-    case SpvBuiltInDrawIndex:
-    case SpvBuiltInDeviceIndex:
-    case SpvBuiltInViewIndex:
     case SpvBuiltInBaryCoordNoPerspAMD:
     case SpvBuiltInBaryCoordNoPerspCentroidAMD:
     case SpvBuiltInBaryCoordNoPerspSampleAMD:

+ 28 - 0
3rdparty/spirv-tools/source/val/validation_state.cpp

@@ -1335,6 +1335,18 @@ std::string ValidationState_t::VkErrorID(uint32_t id,
   // Clang format adds spaces between hyphens
   // clang-format off
   switch (id) {
+    case 4181:
+      return VUID_WRAP(VUID-BaseInstance-BaseInstance-04181);
+    case 4182:
+      return VUID_WRAP(VUID-BaseInstance-BaseInstance-04182);
+    case 4183:
+      return VUID_WRAP(VUID-BaseInstance-BaseInstance-04183);
+    case 4184:
+      return VUID_WRAP(VUID-BaseVertex-BaseVertex-04184);
+    case 4185:
+      return VUID_WRAP(VUID-BaseVertex-BaseVertex-04185);
+    case 4186:
+      return VUID_WRAP(VUID-BaseVertex-BaseVertex-04186);
     case 4187:
       return VUID_WRAP(VUID-ClipDistance-ClipDistance-04187);
     case 4191:
@@ -1343,6 +1355,16 @@ std::string ValidationState_t::VkErrorID(uint32_t id,
       return VUID_WRAP(VUID-CullDistance-CullDistance-04196);
     case 4200:
       return VUID_WRAP(VUID-CullDistance-CullDistance-04200);
+    case 4205:
+      return VUID_WRAP(VUID-DeviceIndex-DeviceIndex-04205);
+    case 4206:
+      return VUID_WRAP(VUID-DeviceIndex-DeviceIndex-04206);
+    case 4207:
+      return VUID_WRAP(VUID-DrawIndex-DrawIndex-04207);
+    case 4208:
+      return VUID_WRAP(VUID-DrawIndex-DrawIndex-04208);
+    case 4209:
+      return VUID_WRAP(VUID-DrawIndex-DrawIndex-04209);
     case 4210:
       return VUID_WRAP(VUID-FragCoord-FragCoord-04210);
     case 4211:
@@ -1473,6 +1495,12 @@ std::string ValidationState_t::VkErrorID(uint32_t id,
       return VUID_WRAP(VUID-VertexIndex-VertexIndex-04399);
     case 4400:
       return VUID_WRAP(VUID-VertexIndex-VertexIndex-04400);
+    case 4401:
+      return VUID_WRAP(VUID-ViewIndex-ViewIndex-04401);
+    case 4402:
+      return VUID_WRAP(VUID-ViewIndex-ViewIndex-04402);
+    case 4403:
+      return VUID_WRAP(VUID-ViewIndex-ViewIndex-04403);
     case 4404:
       return VUID_WRAP(VUID-ViewportIndex-ViewportIndex-04404);
     case 4408: