Browse Source

Updated spirv-tools.

Бранимир Караџић 6 years ago
parent
commit
a91c5a877e
37 changed files with 1741 additions and 351 deletions
  1. 1 0
      3rdparty/spirv-tools/Android.mk
  2. 2 0
      3rdparty/spirv-tools/BUILD.gn
  3. 12 0
      3rdparty/spirv-tools/README.md
  4. 1 1
      3rdparty/spirv-tools/include/generated/build-version.inc
  5. 35 4
      3rdparty/spirv-tools/include/spirv-tools/instrument.hpp
  6. 4 0
      3rdparty/spirv-tools/include/spirv-tools/libspirv.h
  7. 4 2
      3rdparty/spirv-tools/include/spirv-tools/optimizer.hpp
  8. 1 1
      3rdparty/spirv-tools/kokoro/shaderc-smoketest/build.sh
  9. 2 0
      3rdparty/spirv-tools/source/opt/CMakeLists.txt
  10. 3 7
      3rdparty/spirv-tools/source/opt/eliminate_dead_functions_pass.cpp
  11. 32 0
      3rdparty/spirv-tools/source/opt/eliminate_dead_functions_util.cpp
  12. 36 0
      3rdparty/spirv-tools/source/opt/eliminate_dead_functions_util.h
  13. 45 11
      3rdparty/spirv-tools/source/opt/inst_bindless_check_pass.cpp
  14. 20 3
      3rdparty/spirv-tools/source/opt/inst_bindless_check_pass.h
  15. 120 28
      3rdparty/spirv-tools/source/opt/instrument_pass.cpp
  16. 33 4
      3rdparty/spirv-tools/source/opt/instrument_pass.h
  17. 10 6
      3rdparty/spirv-tools/source/opt/optimizer.cpp
  18. 4 0
      3rdparty/spirv-tools/source/reduce/CMakeLists.txt
  19. 40 0
      3rdparty/spirv-tools/source/reduce/remove_function_reduction_opportunity.cpp
  20. 49 0
      3rdparty/spirv-tools/source/reduce/remove_function_reduction_opportunity.h
  21. 42 0
      3rdparty/spirv-tools/source/reduce/remove_function_reduction_opportunity_finder.cpp
  22. 42 0
      3rdparty/spirv-tools/source/reduce/remove_function_reduction_opportunity_finder.h
  23. 0 4
      3rdparty/spirv-tools/source/spirv_target_env.h
  24. 43 0
      3rdparty/spirv-tools/source/val/validate_function.cpp
  25. 11 5
      3rdparty/spirv-tools/source/val/validate_type.cpp
  26. 255 268
      3rdparty/spirv-tools/test/opt/inst_bindless_check_test.cpp
  27. 127 0
      3rdparty/spirv-tools/test/opt/optimizer_test.cpp
  28. 1 0
      3rdparty/spirv-tools/test/reduce/CMakeLists.txt
  29. 294 0
      3rdparty/spirv-tools/test/reduce/remove_function_test.cpp
  30. 2 1
      3rdparty/spirv-tools/test/val/CMakeLists.txt
  31. 1 1
      3rdparty/spirv-tools/test/val/val_builtins_test.cpp
  32. 24 0
      3rdparty/spirv-tools/test/val/val_decoration_test.cpp
  33. 7 0
      3rdparty/spirv-tools/test/val/val_fixtures.h
  34. 424 0
      3rdparty/spirv-tools/test/val/val_function_test.cpp
  35. 4 3
      3rdparty/spirv-tools/test/val/val_id_test.cpp
  36. 7 2
      3rdparty/spirv-tools/tools/CMakeLists.txt
  37. 3 0
      3rdparty/spirv-tools/tools/reduce/reduce.cpp

+ 1 - 0
3rdparty/spirv-tools/Android.mk

@@ -98,6 +98,7 @@ SPVTOOLS_OPT_SRC_FILES := \
 		source/opt/dominator_tree.cpp \
 		source/opt/eliminate_dead_constant_pass.cpp \
 		source/opt/eliminate_dead_functions_pass.cpp \
+		source/opt/eliminate_dead_functions_util.cpp \
 		source/opt/feature_manager.cpp \
 		source/opt/flatten_decoration_pass.cpp \
 		source/opt/fold.cpp \

+ 2 - 0
3rdparty/spirv-tools/BUILD.gn

@@ -496,6 +496,8 @@ static_library("spvtools_opt") {
     "source/opt/eliminate_dead_constant_pass.h",
     "source/opt/eliminate_dead_functions_pass.cpp",
     "source/opt/eliminate_dead_functions_pass.h",
+    "source/opt/eliminate_dead_functions_util.cpp",
+    "source/opt/eliminate_dead_functions_util.h",
     "source/opt/feature_manager.cpp",
     "source/opt/feature_manager.h",
     "source/opt/flatten_decoration_pass.cpp",

+ 12 - 0
3rdparty/spirv-tools/README.md

@@ -432,6 +432,18 @@ The validator operates on the binary form.
 * `spirv-val` - the standalone validator
   * `<spirv-dir>/tools/val`
 
+### Reducer tool
+
+The reducer shrinks a SPIR-V binary module, guided by a user-supplied
+*interestingness test*.
+
+This is a work in progress, with initially only shrinks a module in a few ways.
+
+* `spirv-reduce` - the standalone reducer
+  * `<spirv-dir>/tools/reduce`
+
+Run `spirv-reduce --help` to see how to specify interestingness.
+
 ### Control flow dumper tool
 
 The control flow dumper prints the control flow graph for a SPIR-V module as a

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

@@ -1 +1 @@
-"v2019.2-dev", "SPIRV-Tools v2019.2-dev 6bc0c3fb0a18b013fc222ac6173abaff22f65a39"
+"v2019.2-dev", "SPIRV-Tools v2019.2-dev c3405c13ff287fcc881a3244dafd761d40edaa50"

+ 35 - 4
3rdparty/spirv-tools/include/spirv-tools/instrument.hpp

@@ -30,17 +30,17 @@ namespace spvtools {
 
 // Stream Output Buffer Offsets
 //
-// The following values provide 32-bit word offsets into the output buffer
+// The following values provide offsets into the output buffer struct
 // generated by InstrumentPass::GenDebugStreamWrite. This method is utilized
 // by InstBindlessCheckPass.
 //
-// The first word of the debug output buffer contains the next available word
+// 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
 // this value so as not to overwrite each others records. This value must be
 // initialized to zero
 static const int kDebugOutputSizeOffset = 0;
 
-// The second word of the output buffer is the start of the stream of records
+// The second member of the output buffer is the start of the stream of records
 // written by the instrumented shaders. Each record represents a validation
 // error. The format of the records is documented below.
 static const int kDebugOutputDataOffset = 1;
@@ -122,14 +122,45 @@ static const int kInstMaxOutCnt = kInstStageOutCnt + 3;
 // These are the possible validation error codes.
 static const int kInstErrorBindlessBounds = 0;
 
+// Direct Input Buffer Offsets
+//
+// The following values provide member offsets into the input buffers
+// consumed by InstrumentPass::GenDebugDirectRead(). This method is utilized
+// by InstBindlessCheckPass.
+//
+// The only object in an input buffer is a runtime array of unsigned
+// integers. Each validation will have its own formatting of this array.
+static const int kDebugInputDataOffset = 0;
+
 // Debug Buffer Bindings
 //
 // These are the bindings for the different buffers which are
 // read or written by the instrumentation passes.
 //
-// This is the output buffer written by InstBindlessCheckPass.
+// This is the output buffer written by InstBindlessCheckPass
+// and possibly other future validations.
 static const int kDebugOutputBindingStream = 0;
 
+// The binding for the input buffer for InstBindlessCheckPass. The input
+// buffer needs only be created if the shaders being validated contain a
+// descriptor array of runtime size, and validation of runtime size descriptor
+// arrays have been enabled at the time of the bindless validation pass
+// creation.
+static const int kDebugInputBindingBindless = 1;
+
+// Bindless Validation Input Buffer Format
+//
+// An input buffer for bindless validation consists of a single array of
+// unsigned integers we will call Data[]. This array is formatted as follows.
+//
+// At the beginning of the array is a single uint reserved for future use.
+static const int kDebugInputBindlessOffsetReserved = 0;
+
+// Following the reserved uint is some number of uints such that the following
+// is true: the number of descriptors at (set=s, binding=b) is:
+// Data[ Data[ s + kDebugInputBindlessOffsetLengths ] + b ]
+static const int kDebugInputBindlessOffsetLengths = 1;
+
 }  // namespace spvtools
 
 #endif  // INCLUDE_SPIRV_TOOLS_INSTRUMENT_HPP_

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

@@ -445,6 +445,10 @@ typedef enum {
 // Returns a string describing the given SPIR-V target environment.
 SPIRV_TOOLS_EXPORT const char* spvTargetEnvDescription(spv_target_env env);
 
+// Parses s into *env and returns true if successful.  If unparsable, returns
+// false and sets *env to SPV_ENV_UNIVERSAL_1_0.
+SPIRV_TOOLS_EXPORT bool spvParseTargetEnv(const char* s, spv_target_env* env);
+
 // Creates a context object.  Returns null if env is invalid.
 SPIRV_TOOLS_EXPORT spv_context spvContextCreate(spv_target_env env);
 

+ 4 - 2
3rdparty/spirv-tools/include/spirv-tools/optimizer.hpp

@@ -711,10 +711,12 @@ Optimizer::PassToken CreateCombineAccessChainsPass();
 // The instrumentation will read and write buffers in debug
 // descriptor set |desc_set|. It will write |shader_id| in each output record
 // to identify the shader module which generated the record.
+// |runtime_array_enable| controls instrumentation of runtime arrays which
+// require input buffer support.
 //
 // TODO(greg-lunarg): Add support for vk_ext_descriptor_indexing.
-Optimizer::PassToken CreateInstBindlessCheckPass(uint32_t desc_set,
-                                                 uint32_t shader_id);
+Optimizer::PassToken CreateInstBindlessCheckPass(
+    uint32_t desc_set, uint32_t shader_id, bool runtime_array_enable = false);
 
 // Create a pass to upgrade to the VulkanKHR memory model.
 // This pass upgrades the Logical GLSL450 memory model to Logical VulkanKHR.

+ 1 - 1
3rdparty/spirv-tools/kokoro/shaderc-smoketest/build.sh

@@ -37,7 +37,7 @@ cd $SHADERC_DIR/third_party
 
 # Get shaderc dependencies. Link the appropriate SPIRV-Tools.
 git clone https://github.com/google/googletest.git
-git clone https://github.com/google/glslang.git
+git clone https://github.com/KhronosGroup/glslang.git
 ln -s $GITHUB_DIR/SPIRV-Tools spirv-tools
 git clone https://github.com/KhronosGroup/SPIRV-Headers.git spirv-headers
 git clone https://github.com/google/re2

+ 2 - 0
3rdparty/spirv-tools/source/opt/CMakeLists.txt

@@ -37,6 +37,7 @@ set(SPIRV_TOOLS_OPT_SOURCES
   dominator_tree.h
   eliminate_dead_constant_pass.h
   eliminate_dead_functions_pass.h
+  eliminate_dead_functions_util.h
   feature_manager.h
   flatten_decoration_pass.h
   fold.h
@@ -131,6 +132,7 @@ set(SPIRV_TOOLS_OPT_SOURCES
   dominator_tree.cpp
   eliminate_dead_constant_pass.cpp
   eliminate_dead_functions_pass.cpp
+  eliminate_dead_functions_util.cpp
   feature_manager.cpp
   flatten_decoration_pass.cpp
   fold.cpp

+ 3 - 7
3rdparty/spirv-tools/source/opt/eliminate_dead_functions_pass.cpp

@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #include "source/opt/eliminate_dead_functions_pass.h"
+#include "source/opt/eliminate_dead_functions_util.h"
 
 #include <unordered_set>
 
@@ -36,8 +37,8 @@ Pass::Status EliminateDeadFunctionsPass::Process() {
        funcIter != get_module()->end();) {
     if (live_function_set.count(&*funcIter) == 0) {
       modified = true;
-      EliminateFunction(&*funcIter);
-      funcIter = funcIter.Erase();
+      funcIter =
+          eliminatedeadfunctionsutil::EliminateFunction(context(), &funcIter);
     } else {
       ++funcIter;
     }
@@ -47,10 +48,5 @@ Pass::Status EliminateDeadFunctionsPass::Process() {
                   : Pass::Status::SuccessWithoutChange;
 }
 
-void EliminateDeadFunctionsPass::EliminateFunction(Function* func) {
-  // Remove all of the instruction in the function body
-  func->ForEachInst([this](Instruction* inst) { context()->KillInst(inst); },
-                    true);
-}
 }  // namespace opt
 }  // namespace spvtools

+ 32 - 0
3rdparty/spirv-tools/source/opt/eliminate_dead_functions_util.cpp

@@ -0,0 +1,32 @@
+// Copyright (c) 2019 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 "eliminate_dead_functions_util.h"
+
+namespace spvtools {
+namespace opt {
+
+namespace eliminatedeadfunctionsutil {
+
+Module::iterator EliminateFunction(IRContext* context,
+                                   Module::iterator* func_iter) {
+  (*func_iter)
+      ->ForEachInst([context](Instruction* inst) { context->KillInst(inst); },
+                    true);
+  return func_iter->Erase();
+}
+
+}  // namespace eliminatedeadfunctionsutil
+}  // namespace opt
+}  // namespace spvtools

+ 36 - 0
3rdparty/spirv-tools/source/opt/eliminate_dead_functions_util.h

@@ -0,0 +1,36 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_ELIMINATE_DEAD_FUNCTIONS_UTIL_H_
+#define SOURCE_OPT_ELIMINATE_DEAD_FUNCTIONS_UTIL_H_
+
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace opt {
+
+// Provides functionality for eliminating functions that are not needed, for use
+// by various analyses and passes.
+namespace eliminatedeadfunctionsutil {
+
+// Removes all of the function's instructions, removes the function from the
+// module, and returns the next iterator.
+Module::iterator EliminateFunction(IRContext* context,
+                                   Module::iterator* func_iter);
+
+}  // namespace eliminatedeadfunctionsutil
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  //  SOURCE_OPT_ELIMINATE_DEAD_FUNCTIONS_UTIL_H_

+ 45 - 11
3rdparty/spirv-tools/source/opt/inst_bindless_check_pass.cpp

@@ -35,6 +35,18 @@ static const int kSpvConstantValueInIdx = 0;
 namespace spvtools {
 namespace opt {
 
+uint32_t InstBindlessCheckPass::GenDebugReadLength(
+    uint32_t image_id, InstructionBuilder* builder) {
+  uint32_t desc_set_idx =
+      var2desc_set_[image_id] + kDebugInputBindlessOffsetLengths;
+  uint32_t desc_set_idx_id = builder->GetUintConstantId(desc_set_idx);
+  uint32_t desc_set_offset_id = GenDebugDirectRead(desc_set_idx_id, builder);
+  Instruction* binding_idx_inst =
+      builder->AddBinaryOp(GetUintId(), SpvOpIAdd, desc_set_offset_id,
+                           builder->GetUintConstantId(var2binding_[image_id]));
+  return GenDebugDirectRead(binding_idx_inst->result_id(), builder);
+}
+
 void InstBindlessCheckPass::GenBindlessCheckCode(
     BasicBlock::iterator ref_inst_itr,
     UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t instruction_idx,
@@ -119,20 +131,23 @@ void InstBindlessCheckPass::GenBindlessCheckCode(
   uint32_t ptr_type_id =
       var_type_inst->GetSingleWordInOperand(kSpvTypePointerTypeIdInIdx);
   Instruction* ptr_type_inst = get_def_use_mgr()->GetDef(ptr_type_id);
-  // TODO(greg-lunarg): Handle RuntimeArray. Will need to pull length
-  // out of debug input buffer.
-  if (ptr_type_inst->opcode() != SpvOpTypeArray) return;
   // If index and bound both compile-time constants and index < bound,
   // return without changing
-  uint32_t length_id =
-      ptr_type_inst->GetSingleWordInOperand(kSpvTypeArrayLengthIdInIdx);
-  Instruction* index_inst = get_def_use_mgr()->GetDef(index_id);
-  Instruction* length_inst = get_def_use_mgr()->GetDef(length_id);
-  if (index_inst->opcode() == SpvOpConstant &&
-      length_inst->opcode() == SpvOpConstant &&
-      index_inst->GetSingleWordInOperand(kSpvConstantValueInIdx) <
-          length_inst->GetSingleWordInOperand(kSpvConstantValueInIdx))
+  uint32_t length_id = 0;
+  if (ptr_type_inst->opcode() == SpvOpTypeArray) {
+    length_id =
+        ptr_type_inst->GetSingleWordInOperand(kSpvTypeArrayLengthIdInIdx);
+    Instruction* index_inst = get_def_use_mgr()->GetDef(index_id);
+    Instruction* length_inst = get_def_use_mgr()->GetDef(length_id);
+    if (index_inst->opcode() == SpvOpConstant &&
+        length_inst->opcode() == SpvOpConstant &&
+        index_inst->GetSingleWordInOperand(kSpvConstantValueInIdx) <
+            length_inst->GetSingleWordInOperand(kSpvConstantValueInIdx))
+      return;
+  } else if (!runtime_array_enabled_ ||
+             ptr_type_inst->opcode() != SpvOpTypeRuntimeArray) {
     return;
+  }
   // Generate full runtime bounds test code with true branch
   // being full reference and false branch being debug output and zero
   // for the referenced value.
@@ -141,6 +156,13 @@ void InstBindlessCheckPass::GenBindlessCheckCode(
       context(), &*new_blk_ptr,
       IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
   uint32_t error_id = builder.GetUintConstantId(kInstErrorBindlessBounds);
+  // If length id not yet set, descriptor array is runtime size so
+  // generate load of length from stage's debug input buffer.
+  if (length_id == 0) {
+    assert(ptr_type_inst->opcode() == SpvOpTypeRuntimeArray &&
+           "unexpected bindless type");
+    length_id = GenDebugReadLength(image_id, &builder);
+  }
   Instruction* ult_inst =
       builder.AddBinaryOp(GetBoolId(), SpvOpULessThan, index_id, length_id);
   uint32_t merge_blk_id = TakeNextId();
@@ -236,6 +258,18 @@ void InstBindlessCheckPass::InitializeInstBindlessCheck() {
       break;
     }
   }
+  // If descriptor indexing extension and runtime array support enabled,
+  // create variable to descriptor set mapping.
+  if (ext_descriptor_indexing_defined_ && runtime_array_enabled_)
+    for (auto& anno : get_module()->annotations())
+      if (anno.opcode() == SpvOpDecorate) {
+        if (anno.GetSingleWordInOperand(1u) == SpvDecorationDescriptorSet)
+          var2desc_set_[anno.GetSingleWordInOperand(0u)] =
+              anno.GetSingleWordInOperand(2u);
+        else if (anno.GetSingleWordInOperand(1u) == SpvDecorationBinding)
+          var2binding_[anno.GetSingleWordInOperand(0u)] =
+              anno.GetSingleWordInOperand(2u);
+      }
 }
 
 Pass::Status InstBindlessCheckPass::ProcessImpl() {

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

@@ -29,10 +29,14 @@ namespace opt {
 class InstBindlessCheckPass : public InstrumentPass {
  public:
   // For test harness only
-  InstBindlessCheckPass() : InstrumentPass(7, 23, kInstValidationIdBindless) {}
+  InstBindlessCheckPass()
+      : InstrumentPass(7, 23, kInstValidationIdBindless),
+        runtime_array_enabled_(true) {}
   // For all other interfaces
-  InstBindlessCheckPass(uint32_t desc_set, uint32_t shader_id)
-      : InstrumentPass(desc_set, shader_id, kInstValidationIdBindless) {}
+  InstBindlessCheckPass(uint32_t desc_set, uint32_t shader_id,
+                        bool runtime_array_enable)
+      : InstrumentPass(desc_set, shader_id, kInstValidationIdBindless),
+        runtime_array_enabled_(runtime_array_enable) {}
 
   ~InstBindlessCheckPass() override = default;
 
@@ -42,6 +46,10 @@ class InstBindlessCheckPass : public InstrumentPass {
   const char* name() const override { return "inst-bindless-check-pass"; }
 
  private:
+  // Generate instructions into |builder| to read length of runtime descriptor
+  // array |image_id| from debug input buffer and return id of value.
+  uint32_t GenDebugReadLength(uint32_t image_id, InstructionBuilder* builder);
+
   // Initialize state for instrumenting bindless checking
   void InitializeInstBindlessCheck();
 
@@ -85,6 +93,15 @@ class InstBindlessCheckPass : public InstrumentPass {
 
   // True if VK_EXT_descriptor_indexing is defined
   bool ext_descriptor_indexing_defined_;
+
+  // Enable instrumentation of runtime arrays
+  bool runtime_array_enabled_;
+
+  // Mapping from variable to descriptor set
+  std::unordered_map<uint32_t, uint32_t> var2desc_set_;
+
+  // Mapping from variable to binding
+  std::unordered_map<uint32_t, uint32_t> var2binding_;
 };
 
 }  // namespace opt

+ 120 - 28
3rdparty/spirv-tools/source/opt/instrument_pass.cpp

@@ -107,7 +107,7 @@ void InstrumentPass::GenDebugOutputFieldCode(uint32_t base_offset_id,
       builder->AddBinaryOp(GetUintId(), SpvOpIAdd, base_offset_id,
                            builder->GetUintConstantId(field_offset));
   uint32_t buf_id = GetOutputBufferId();
-  uint32_t buf_uint_ptr_id = GetOutputBufferUintPtrId();
+  uint32_t buf_uint_ptr_id = GetBufferUintPtrId();
   Instruction* achain_inst =
       builder->AddTernaryOp(buf_uint_ptr_id, SpvOpAccessChain, buf_id,
                             builder->GetUintConstantId(kDebugOutputDataOffset),
@@ -222,6 +222,18 @@ void InstrumentPass::GenDebugStreamWrite(
   (void)builder->AddNaryOp(GetVoidId(), SpvOpFunctionCall, args);
 }
 
+uint32_t InstrumentPass::GenDebugDirectRead(uint32_t idx_id,
+                                            InstructionBuilder* builder) {
+  uint32_t input_buf_id = GetInputBufferId();
+  uint32_t buf_uint_ptr_id = GetBufferUintPtrId();
+  Instruction* ibuf_ac_inst = builder->AddTernaryOp(
+      buf_uint_ptr_id, SpvOpAccessChain, input_buf_id,
+      builder->GetUintConstantId(kDebugInputDataOffset), idx_id);
+  Instruction* load_inst =
+      builder->AddUnaryOp(GetUintId(), SpvOpLoad, ibuf_ac_inst->result_id());
+  return load_inst->result_id();
+}
+
 bool InstrumentPass::IsSameBlockOp(const Instruction* inst) const {
   return inst->opcode() == SpvOpSampledImage || inst->opcode() == SpvOpImage;
 }
@@ -280,12 +292,12 @@ void InstrumentPass::UpdateSucceedingPhis(
 }
 
 // Return id for output buffer uint ptr type
-uint32_t InstrumentPass::GetOutputBufferUintPtrId() {
-  if (output_buffer_uint_ptr_id_ == 0) {
-    output_buffer_uint_ptr_id_ = context()->get_type_mgr()->FindPointerToType(
+uint32_t InstrumentPass::GetBufferUintPtrId() {
+  if (buffer_uint_ptr_id_ == 0) {
+    buffer_uint_ptr_id_ = context()->get_type_mgr()->FindPointerToType(
         GetUintId(), SpvStorageClassStorageBuffer);
   }
-  return output_buffer_uint_ptr_id_;
+  return buffer_uint_ptr_id_;
 }
 
 uint32_t InstrumentPass::GetOutputBufferBinding() {
@@ -298,22 +310,74 @@ uint32_t InstrumentPass::GetOutputBufferBinding() {
   return 0;
 }
 
+uint32_t InstrumentPass::GetInputBufferBinding() {
+  switch (validation_id_) {
+    case kInstValidationIdBindless:
+      return kDebugInputBindingBindless;
+    default:
+      assert(false && "unexpected validation id");
+  }
+  return 0;
+}
+
+analysis::Type* InstrumentPass::GetUintRuntimeArrayType(
+    analysis::DecorationManager* deco_mgr, analysis::TypeManager* type_mgr) {
+  if (uint_rarr_ty_ == nullptr) {
+    analysis::Integer uint_ty(32, false);
+    analysis::Type* reg_uint_ty = type_mgr->GetRegisteredType(&uint_ty);
+    analysis::RuntimeArray uint_rarr_ty_tmp(reg_uint_ty);
+    uint_rarr_ty_ = type_mgr->GetRegisteredType(&uint_rarr_ty_tmp);
+    uint32_t uint_arr_ty_id = type_mgr->GetTypeInstruction(uint_rarr_ty_);
+    // By the Vulkan spec, a pre-existing RuntimeArray of uint must be part of
+    // a block, and will therefore be decorated with an ArrayStride. Therefore
+    // the undecorated type returned here will not be pre-existing and can
+    // safely be decorated. Since this type is now decorated, it is out of
+    // sync with the TypeManager and therefore the TypeManager must be
+    // invalidated after this pass.
+    assert(context()->get_def_use_mgr()->NumUses(uint_arr_ty_id) == 0 &&
+           "used RuntimeArray type returned");
+    deco_mgr->AddDecorationVal(uint_arr_ty_id, SpvDecorationArrayStride, 4u);
+  }
+  return uint_rarr_ty_;
+}
+
+void InstrumentPass::AddStorageBufferExt() {
+  if (storage_buffer_ext_defined_) return;
+  if (!get_feature_mgr()->HasExtension(kSPV_KHR_storage_buffer_storage_class)) {
+    const std::string ext_name("SPV_KHR_storage_buffer_storage_class");
+    const auto num_chars = ext_name.size();
+    // Compute num words, accommodate the terminating null character.
+    const auto num_words = (num_chars + 1 + 3) / 4;
+    std::vector<uint32_t> ext_words(num_words, 0u);
+    std::memcpy(ext_words.data(), ext_name.data(), num_chars);
+    context()->AddExtension(std::unique_ptr<Instruction>(
+        new Instruction(context(), SpvOpExtension, 0u, 0u,
+                        {{SPV_OPERAND_TYPE_LITERAL_STRING, ext_words}})));
+  }
+  storage_buffer_ext_defined_ = true;
+}
+
 // Return id for output buffer
 uint32_t InstrumentPass::GetOutputBufferId() {
   if (output_buffer_id_ == 0) {
     // If not created yet, create one
     analysis::DecorationManager* deco_mgr = get_decoration_mgr();
     analysis::TypeManager* type_mgr = context()->get_type_mgr();
+    analysis::Type* reg_uint_rarr_ty =
+        GetUintRuntimeArrayType(deco_mgr, type_mgr);
     analysis::Integer uint_ty(32, false);
     analysis::Type* reg_uint_ty = type_mgr->GetRegisteredType(&uint_ty);
-    analysis::RuntimeArray uint_rarr_ty(reg_uint_ty);
-    analysis::Type* reg_uint_rarr_ty =
-        type_mgr->GetRegisteredType(&uint_rarr_ty);
-    uint32_t uint_arr_ty_id = type_mgr->GetTypeInstruction(reg_uint_rarr_ty);
-    deco_mgr->AddDecorationVal(uint_arr_ty_id, SpvDecorationArrayStride, 4u);
-    analysis::Struct obuf_ty({reg_uint_ty, reg_uint_rarr_ty});
-    analysis::Type* reg_obuf_ty = type_mgr->GetRegisteredType(&obuf_ty);
-    uint32_t obufTyId = type_mgr->GetTypeInstruction(reg_obuf_ty);
+    analysis::Struct buf_ty({reg_uint_ty, reg_uint_rarr_ty});
+    analysis::Type* reg_buf_ty = type_mgr->GetRegisteredType(&buf_ty);
+    uint32_t obufTyId = type_mgr->GetTypeInstruction(reg_buf_ty);
+    // By the Vulkan spec, a pre-existing struct containing a RuntimeArray
+    // must be a block, and will therefore be decorated with Block. Therefore
+    // the undecorated type returned here will not be pre-existing and can
+    // safely be decorated. Since this type is now decorated, it is out of
+    // sync with the TypeManager and therefore the TypeManager must be
+    // invalidated after this pass.
+    assert(context()->get_def_use_mgr()->NumUses(obufTyId) == 0 &&
+           "used struct type returned");
     deco_mgr->AddDecoration(obufTyId, SpvDecorationBlock);
     deco_mgr->AddMemberDecoration(obufTyId, kDebugOutputSizeOffset,
                                   SpvDecorationOffset, 0);
@@ -331,23 +395,48 @@ uint32_t InstrumentPass::GetOutputBufferId() {
                                desc_set_);
     deco_mgr->AddDecorationVal(output_buffer_id_, SpvDecorationBinding,
                                GetOutputBufferBinding());
-    // Look for storage buffer extension. If none, create one.
-    if (!get_feature_mgr()->HasExtension(
-            kSPV_KHR_storage_buffer_storage_class)) {
-      const std::string ext_name("SPV_KHR_storage_buffer_storage_class");
-      const auto num_chars = ext_name.size();
-      // Compute num words, accommodate the terminating null character.
-      const auto num_words = (num_chars + 1 + 3) / 4;
-      std::vector<uint32_t> ext_words(num_words, 0u);
-      std::memcpy(ext_words.data(), ext_name.data(), num_chars);
-      context()->AddExtension(std::unique_ptr<Instruction>(
-          new Instruction(context(), SpvOpExtension, 0u, 0u,
-                          {{SPV_OPERAND_TYPE_LITERAL_STRING, ext_words}})));
-    }
+    AddStorageBufferExt();
   }
   return output_buffer_id_;
 }
 
+uint32_t InstrumentPass::GetInputBufferId() {
+  if (input_buffer_id_ == 0) {
+    // If not created yet, create one
+    analysis::DecorationManager* deco_mgr = get_decoration_mgr();
+    analysis::TypeManager* type_mgr = context()->get_type_mgr();
+    analysis::Type* reg_uint_rarr_ty =
+        GetUintRuntimeArrayType(deco_mgr, type_mgr);
+    analysis::Struct buf_ty({reg_uint_rarr_ty});
+    analysis::Type* reg_buf_ty = type_mgr->GetRegisteredType(&buf_ty);
+    uint32_t ibufTyId = type_mgr->GetTypeInstruction(reg_buf_ty);
+    // By the Vulkan spec, a pre-existing struct containing a RuntimeArray
+    // must be a block, and will therefore be decorated with Block. Therefore
+    // the undecorated type returned here will not be pre-existing and can
+    // safely be decorated. Since this type is now decorated, it is out of
+    // sync with the TypeManager and therefore the TypeManager must be
+    // invalidated after this pass.
+    assert(context()->get_def_use_mgr()->NumUses(ibufTyId) == 0 &&
+           "used struct type returned");
+    deco_mgr->AddDecoration(ibufTyId, SpvDecorationBlock);
+    deco_mgr->AddMemberDecoration(ibufTyId, 0, SpvDecorationOffset, 0);
+    uint32_t ibufTyPtrId_ =
+        type_mgr->FindPointerToType(ibufTyId, SpvStorageClassStorageBuffer);
+    input_buffer_id_ = TakeNextId();
+    std::unique_ptr<Instruction> newVarOp(new Instruction(
+        context(), SpvOpVariable, ibufTyPtrId_, input_buffer_id_,
+        {{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
+          {SpvStorageClassStorageBuffer}}}));
+    context()->AddGlobalValue(std::move(newVarOp));
+    deco_mgr->AddDecorationVal(input_buffer_id_, SpvDecorationDescriptorSet,
+                               desc_set_);
+    deco_mgr->AddDecorationVal(input_buffer_id_, SpvDecorationBinding,
+                               GetInputBufferBinding());
+    AddStorageBufferExt();
+  }
+  return input_buffer_id_;
+}
+
 uint32_t InstrumentPass::GetVec4FloatId() {
   if (v4float_id_ == 0) {
     analysis::TypeManager* type_mgr = context()->get_type_mgr();
@@ -447,7 +536,7 @@ uint32_t InstrumentPass::GetStreamWriteFunctionId(uint32_t stage_idx,
     // Gen test if debug output buffer size will not be exceeded.
     uint32_t obuf_record_sz = kInstStageOutCnt + val_spec_param_cnt;
     uint32_t buf_id = GetOutputBufferId();
-    uint32_t buf_uint_ptr_id = GetOutputBufferUintPtrId();
+    uint32_t buf_uint_ptr_id = GetBufferUintPtrId();
     Instruction* obuf_curr_sz_ac_inst =
         builder.AddBinaryOp(buf_uint_ptr_id, SpvOpAccessChain, buf_id,
                             builder.GetUintConstantId(kDebugOutputSizeOffset));
@@ -620,14 +709,17 @@ bool InstrumentPass::InstProcessEntryPointCallTree(InstProcessFunction& pfn) {
 
 void InstrumentPass::InitializeInstrument() {
   output_buffer_id_ = 0;
-  output_buffer_uint_ptr_id_ = 0;
+  buffer_uint_ptr_id_ = 0;
   output_func_id_ = 0;
   output_func_param_cnt_ = 0;
+  input_buffer_id_ = 0;
   v4float_id_ = 0;
   uint_id_ = 0;
   v4uint_id_ = 0;
   bool_id_ = 0;
   void_id_ = 0;
+  storage_buffer_ext_defined_ = false;
+  uint_rarr_ty_ = nullptr;
 
   // clear collections
   id2function_.clear();

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

@@ -76,7 +76,7 @@ class InstrumentPass : public Pass {
            IRContext::kAnalysisInstrToBlockMapping |
            IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
            IRContext::kAnalysisNameMap | IRContext::kAnalysisBuiltinVarId |
-           IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
+           IRContext::kAnalysisConstants;
   }
 
  protected:
@@ -195,6 +195,13 @@ class InstrumentPass : public Pass {
                            const std::vector<uint32_t>& validation_ids,
                            InstructionBuilder* builder);
 
+  // Generate in |builder| instructions to read the unsigned integer from the
+  // input buffer at offset |idx_id|. Return the result id.
+  //
+  // The binding and the format of the input buffer is determined by each
+  // specific validation, which is specified at the creation of the pass.
+  uint32_t GenDebugDirectRead(uint32_t idx_id, InstructionBuilder* builder);
+
   // Generate code to cast |value_id| to unsigned, if needed. Return
   // an id to the unsigned equivalent.
   uint32_t GenUintCastCode(uint32_t value_id, InstructionBuilder* builder);
@@ -211,15 +218,28 @@ class InstrumentPass : public Pass {
   // Return id for void type
   uint32_t GetVoidId();
 
-  // Return id for output buffer uint type
-  uint32_t GetOutputBufferUintPtrId();
+  // Return pointer to type for runtime array of uint
+  analysis::Type* GetUintRuntimeArrayType(analysis::DecorationManager* deco_mgr,
+                                          analysis::TypeManager* type_mgr);
+
+  // Return id for buffer uint type
+  uint32_t GetBufferUintPtrId();
 
   // Return binding for output buffer for current validation.
   uint32_t GetOutputBufferBinding();
 
+  // Return binding for input buffer for current validation.
+  uint32_t GetInputBufferBinding();
+
+  // Add storage buffer extension if needed
+  void AddStorageBufferExt();
+
   // Return id for debug output buffer
   uint32_t GetOutputBufferId();
 
+  // Return id for debug input buffer
+  uint32_t GetInputBufferId();
+
   // Return id for v4float type
   uint32_t GetVec4FloatId();
 
@@ -321,7 +341,7 @@ class InstrumentPass : public Pass {
   uint32_t output_buffer_id_;
 
   // type id for output buffer element
-  uint32_t output_buffer_uint_ptr_id_;
+  uint32_t buffer_uint_ptr_id_;
 
   // id for debug output function
   uint32_t output_func_id_;
@@ -329,6 +349,9 @@ class InstrumentPass : public Pass {
   // param count for output function
   uint32_t output_func_param_cnt_;
 
+  // id for input buffer variable
+  uint32_t input_buffer_id_;
+
   // id for v4float type
   uint32_t v4float_id_;
 
@@ -344,6 +367,12 @@ class InstrumentPass : public Pass {
   // id for void type
   uint32_t void_id_;
 
+  // boolean to remember storage buffer extension
+  bool storage_buffer_ext_defined_;
+
+  // runtime array of uint type
+  analysis::Type* uint_rarr_ty_;
+
   // Pre-instrumentation same-block insts
   std::unordered_map<uint32_t, Instruction*> same_block_pre_;
 

+ 10 - 6
3rdparty/spirv-tools/source/opt/optimizer.cpp

@@ -57,8 +57,8 @@ Optimizer::PassToken::~PassToken() {}
 struct Optimizer::Impl {
   explicit Impl(spv_target_env env) : target_env(env), pass_manager() {}
 
-  spv_target_env target_env;        // Target environment.
-  opt::PassManager pass_manager;    // Internal implementation pass manager.
+  spv_target_env target_env;      // Target environment.
+  opt::PassManager pass_manager;  // Internal implementation pass manager.
 };
 
 Optimizer::Optimizer(spv_target_env env) : impl_(new Impl(env)) {}
@@ -218,7 +218,9 @@ Optimizer& Optimizer::RegisterSizePasses() {
 }
 
 Optimizer& Optimizer::RegisterWebGPUPasses() {
-  return RegisterPass(CreateAggressiveDCEPass())
+  return RegisterPass(CreateStripDebugInfoPass())
+      .RegisterPass(CreateFlattenDecorationPass())
+      .RegisterPass(CreateAggressiveDCEPass())
       .RegisterPass(CreateDeadBranchElimPass());
 }
 
@@ -379,7 +381,7 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag) {
   } else if (pass_name == "replace-invalid-opcode") {
     RegisterPass(CreateReplaceInvalidOpcodePass());
   } else if (pass_name == "inst-bindless-check") {
-    RegisterPass(CreateInstBindlessCheckPass(7, 23));
+    RegisterPass(CreateInstBindlessCheckPass(7, 23, true));
     RegisterPass(CreateSimplificationPass());
     RegisterPass(CreateDeadBranchElimPass());
     RegisterPass(CreateBlockMergePass());
@@ -788,9 +790,11 @@ Optimizer::PassToken CreateUpgradeMemoryModelPass() {
 }
 
 Optimizer::PassToken CreateInstBindlessCheckPass(uint32_t desc_set,
-                                                 uint32_t shader_id) {
+                                                 uint32_t shader_id,
+                                                 bool runtime_array_enable) {
   return MakeUnique<Optimizer::PassToken::Impl>(
-      MakeUnique<opt::InstBindlessCheckPass>(desc_set, shader_id));
+      MakeUnique<opt::InstBindlessCheckPass>(desc_set, shader_id,
+                                             runtime_array_enable));
 }
 
 Optimizer::PassToken CreateCodeSinkingPass() {

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

@@ -25,6 +25,8 @@ set(SPIRV_TOOLS_REDUCE_SOURCES
         reduction_pass.h
         reduction_util.h
         remove_instruction_reduction_opportunity.h
+        remove_function_reduction_opportunity.h
+        remove_function_reduction_opportunity_finder.h
         remove_opname_instruction_reduction_opportunity_finder.h
         remove_unreferenced_instruction_reduction_opportunity_finder.h
         structured_loop_to_selection_reduction_opportunity.h
@@ -41,6 +43,8 @@ set(SPIRV_TOOLS_REDUCE_SOURCES
         reduction_opportunity.cpp
         reduction_pass.cpp
         reduction_util.cpp
+        remove_function_reduction_opportunity.cpp
+        remove_function_reduction_opportunity_finder.cpp
         remove_instruction_reduction_opportunity.cpp
         remove_unreferenced_instruction_reduction_opportunity_finder.cpp
         remove_opname_instruction_reduction_opportunity_finder.cpp

+ 40 - 0
3rdparty/spirv-tools/source/reduce/remove_function_reduction_opportunity.cpp

@@ -0,0 +1,40 @@
+// Copyright (c) 2019 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 "remove_function_reduction_opportunity.h"
+#include "source/opt/eliminate_dead_functions_util.h"
+
+namespace spvtools {
+namespace reduce {
+
+bool RemoveFunctionReductionOpportunity::PreconditionHolds() {
+  // Removing one function cannot influence whether another function can be
+  // removed.
+  return true;
+}
+
+void RemoveFunctionReductionOpportunity::Apply() {
+  for (opt::Module::iterator function_it = context_->module()->begin();
+       function_it != context_->module()->end(); ++function_it) {
+    if (&*function_it == function_) {
+      opt::eliminatedeadfunctionsutil::EliminateFunction(context_,
+                                                         &function_it);
+      return;
+    }
+  }
+  assert(0 && "Function to be removed was not found.");
+}
+
+}  // namespace reduce
+}  // namespace spvtools

+ 49 - 0
3rdparty/spirv-tools/source/reduce/remove_function_reduction_opportunity.h

@@ -0,0 +1,49 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_REMOVE_FUNCTION_REDUCTION_OPPORTUNITY_H_
+#define SOURCE_REDUCE_REMOVE_FUNCTION_REDUCTION_OPPORTUNITY_H_
+
+#include "reduction_opportunity.h"
+#include "source/opt/function.h"
+
+namespace spvtools {
+namespace reduce {
+
+// An opportunity to remove an unreferenced function.
+class RemoveFunctionReductionOpportunity : public ReductionOpportunity {
+ public:
+  // Creates an opportunity to remove |function| from the module represented by
+  // |context|.
+  RemoveFunctionReductionOpportunity(opt::IRContext* context,
+                                     opt::Function* function)
+      : context_(context), function_(function) {}
+
+  bool PreconditionHolds() override;
+
+ protected:
+  void Apply() override;
+
+ private:
+  // The IR context for the module under analysis.
+  opt::IRContext* context_;
+
+  // The function that can be removed.
+  opt::Function* function_;
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  //   SOURCE_REDUCE_REMOVE_FUNCTION_REDUCTION_OPPORTUNITY_H_

+ 42 - 0
3rdparty/spirv-tools/source/reduce/remove_function_reduction_opportunity_finder.cpp

@@ -0,0 +1,42 @@
+// Copyright (c) 2019 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 "remove_function_reduction_opportunity_finder.h"
+#include "remove_function_reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+std::vector<std::unique_ptr<ReductionOpportunity>>
+RemoveFunctionReductionOpportunityFinder::GetAvailableOpportunities(
+    opt::IRContext* context) const {
+  std::vector<std::unique_ptr<ReductionOpportunity>> result;
+  // Consider each function.
+  for (auto& function : *context->module()) {
+    if (context->get_def_use_mgr()->NumUses(function.result_id()) > 0) {
+      // If the function is referenced, ignore it.
+      continue;
+    }
+    result.push_back(
+        MakeUnique<RemoveFunctionReductionOpportunity>(context, &function));
+  }
+  return result;
+}
+
+std::string RemoveFunctionReductionOpportunityFinder::GetName() const {
+  return "RemoveFunctionReductionOpportunityFinder";
+}
+
+}  // namespace reduce
+}  // namespace spvtools

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

@@ -0,0 +1,42 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_REMOVE_FUNCTION_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_REMOVE_FUNCTION_REDUCTION_OPPORTUNITY_FINDER_H_
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder of opportunities to remove unreferenced functions.
+class RemoveFunctionReductionOpportunityFinder
+    : public ReductionOpportunityFinder {
+ public:
+  RemoveFunctionReductionOpportunityFinder() = default;
+
+  ~RemoveFunctionReductionOpportunityFinder() override = default;
+
+  std::string GetName() const final;
+
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      opt::IRContext* context) const final;
+
+ private:
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  //   SOURCE_REDUCE_REMOVE_FUNCTION_REDUCTION_OPPORTUNITY_FINDER_H_

+ 0 - 4
3rdparty/spirv-tools/source/spirv_target_env.h

@@ -19,10 +19,6 @@
 
 #include "spirv-tools/libspirv.h"
 
-// Parses s into *env and returns true if successful.  If unparsable, returns
-// false and sets *env to SPV_ENV_UNIVERSAL_1_0.
-bool spvParseTargetEnv(const char* s, spv_target_env* env);
-
 // Returns true if |env| is a VULKAN environment, false otherwise.
 bool spvIsVulkanEnv(spv_target_env env);
 

+ 43 - 0
3rdparty/spirv-tools/source/val/validate_function.cpp

@@ -247,6 +247,49 @@ spv_result_t ValidateFunctionCall(ValidationState_t& _,
              << "'s type does not match Function <id> '"
              << _.getIdName(parameter_type_id) << "'s parameter type.";
     }
+
+    if (_.addressing_model() == SpvAddressingModelLogical) {
+      if (parameter_type->opcode() == SpvOpTypePointer) {
+        SpvStorageClass sc = parameter_type->GetOperandAs<SpvStorageClass>(1u);
+        // Validate which storage classes can be pointer operands.
+        switch (sc) {
+          case SpvStorageClassUniformConstant:
+          case SpvStorageClassFunction:
+          case SpvStorageClassPrivate:
+          case SpvStorageClassWorkgroup:
+          case SpvStorageClassAtomicCounter:
+            // These are always allowed.
+            break;
+          case SpvStorageClassStorageBuffer:
+            if (!_.features().variable_pointers_storage_buffer) {
+              return _.diag(SPV_ERROR_INVALID_ID, inst)
+                     << "StorageBuffer pointer operand "
+                     << _.getIdName(argument_id)
+                     << " requires a variable pointers capability";
+            }
+            break;
+          default:
+            return _.diag(SPV_ERROR_INVALID_ID, inst)
+                   << "Invalid storage class for pointer operand "
+                   << _.getIdName(argument_id);
+        }
+
+        // Validate memory object declaration requirements.
+        if (argument->opcode() != SpvOpVariable &&
+            argument->opcode() != SpvOpFunctionParameter) {
+          const bool ssbo_vptr =
+              _.features().variable_pointers_storage_buffer &&
+              sc == SpvStorageClassStorageBuffer;
+          const bool wg_vptr =
+              _.features().variable_pointers && sc == SpvStorageClassWorkgroup;
+          if (!ssbo_vptr && !wg_vptr) {
+            return _.diag(SPV_ERROR_INVALID_ID, inst)
+                   << "Pointer operand " << _.getIdName(argument_id)
+                   << " must be a memory object declaration";
+          }
+        }
+      }
+    }
   }
   return SPV_SUCCESS;
 }

+ 11 - 5
3rdparty/spirv-tools/source/val/validate_type.cpp

@@ -262,17 +262,23 @@ spv_result_t ValidateTypeStruct(ValidationState_t& _, const Instruction* inst) {
 
 spv_result_t ValidateTypePointer(ValidationState_t& _,
                                  const Instruction* inst) {
-  const auto type_id = inst->GetOperandAs<uint32_t>(2);
-  const auto type = _.FindDef(type_id);
+  auto type_id = inst->GetOperandAs<uint32_t>(2);
+  auto type = _.FindDef(type_id);
   if (!type || !spvOpcodeGeneratesType(type->opcode())) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
            << "OpTypePointer Type <id> '" << _.getIdName(type_id)
            << "' is not a type.";
   }
   // See if this points to a storage image.
-  if (type->opcode() == SpvOpTypeImage) {
-    const auto storage_class = inst->GetOperandAs<uint32_t>(1);
-    if (storage_class == SpvStorageClassUniformConstant) {
+  const auto storage_class = inst->GetOperandAs<SpvStorageClass>(1);
+  if (storage_class == SpvStorageClassUniformConstant) {
+    // Unpack an optional level of arraying.
+    if (type->opcode() == SpvOpTypeArray ||
+        type->opcode() == SpvOpTypeRuntimeArray) {
+      type_id = type->GetOperandAs<uint32_t>(1);
+      type = _.FindDef(type_id);
+    }
+    if (type->opcode() == SpvOpTypeImage) {
       const auto sampled = type->GetOperandAs<uint32_t>(6);
       // 2 indicates this image is known to be be used without a sampler, i.e.
       // a storage image.

+ 255 - 268
3rdparty/spirv-tools/test/opt/inst_bindless_check_test.cpp

@@ -630,274 +630,6 @@ OpFunctionEnd
       true);
 }
 
-TEST_F(InstBindlessTest, ReuseConstsTypesBuiltins) {
-  // This test verifies that the pass resuses existing constants, types
-  // and builtin variables.  This test was created by editing the SPIR-V
-  // from the Simple test.
-
-  const std::string defs_before =
-      R"(OpCapability Shader
-OpExtension "SPV_KHR_storage_buffer_storage_class"
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord
-OpExecutionMode %MainPs OriginUpperLeft
-OpSource HLSL 500
-OpName %MainPs "MainPs"
-OpName %g_tColor "g_tColor"
-OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
-OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
-OpName %_ ""
-OpName %g_sAniso "g_sAniso"
-OpName %i_vTextureCoords "i.vTextureCoords"
-OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
-OpDecorate %g_tColor DescriptorSet 3
-OpDecorate %g_tColor Binding 0
-OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
-OpDecorate %PerViewConstantBuffer_t Block
-OpDecorate %g_sAniso DescriptorSet 0
-OpDecorate %i_vTextureCoords Location 0
-OpDecorate %_entryPointOutput_vColor Location 0
-OpDecorate %85 DescriptorSet 7
-OpDecorate %85 Binding 0
-OpDecorate %gl_FragCoord BuiltIn FragCoord
-%void = OpTypeVoid
-%3 = OpTypeFunction %void
-%float = OpTypeFloat 32
-%v2float = OpTypeVector %float 2
-%v4float = OpTypeVector %float 4
-%int = OpTypeInt 32 1
-%int_0 = OpConstant %int 0
-%20 = OpTypeImage %float 2D 0 0 0 1 Unknown
-%uint = OpTypeInt 32 0
-%uint_128 = OpConstant %uint 128
-%_arr_20_uint_128 = OpTypeArray %20 %uint_128
-%_ptr_UniformConstant__arr_20_uint_128 = OpTypePointer UniformConstant %_arr_20_uint_128
-%g_tColor = OpVariable %_ptr_UniformConstant__arr_20_uint_128 UniformConstant
-%PerViewConstantBuffer_t = OpTypeStruct %uint
-%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
-%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
-%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
-%_ptr_UniformConstant_20 = OpTypePointer UniformConstant %20
-%35 = OpTypeSampler
-%_ptr_UniformConstant_35 = OpTypePointer UniformConstant %35
-%g_sAniso = OpVariable %_ptr_UniformConstant_35 UniformConstant
-%39 = OpTypeSampledImage %20
-%_ptr_Input_v2float = OpTypePointer Input %v2float
-%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
-%_ptr_Output_v4float = OpTypePointer Output %v4float
-%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
-%uint_0 = OpConstant %uint 0
-%bool = OpTypeBool
-%_runtimearr_uint = OpTypeRuntimeArray %uint
-%_struct_83 = OpTypeStruct %uint %_runtimearr_uint
-%_ptr_StorageBuffer__struct_83 = OpTypePointer StorageBuffer %_struct_83
-%85 = OpVariable %_ptr_StorageBuffer__struct_83 StorageBuffer
-%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
-%uint_10 = OpConstant %uint 10
-%uint_4 = OpConstant %uint 4
-%uint_1 = OpConstant %uint 1
-%uint_23 = OpConstant %uint 23
-%uint_2 = OpConstant %uint 2
-%uint_9 = OpConstant %uint 9
-%uint_3 = OpConstant %uint 3
-%_ptr_Input_v4float = OpTypePointer Input %v4float
-%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
-%v4uint = OpTypeVector %uint 4
-%uint_5 = OpConstant %uint 5
-%uint_6 = OpConstant %uint 6
-%uint_7 = OpConstant %uint 7
-%uint_8 = OpConstant %uint 8
-%131 = OpConstantNull %v4float
-)";
-
-  const std::string defs_after =
-      R"(OpCapability Shader
-OpExtension "SPV_KHR_storage_buffer_storage_class"
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord
-OpExecutionMode %MainPs OriginUpperLeft
-OpSource HLSL 500
-OpName %MainPs "MainPs"
-OpName %g_tColor "g_tColor"
-OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
-OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
-OpName %_ ""
-OpName %g_sAniso "g_sAniso"
-OpName %i_vTextureCoords "i.vTextureCoords"
-OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
-OpDecorate %g_tColor DescriptorSet 3
-OpDecorate %g_tColor Binding 0
-OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
-OpDecorate %PerViewConstantBuffer_t Block
-OpDecorate %g_sAniso DescriptorSet 0
-OpDecorate %i_vTextureCoords Location 0
-OpDecorate %_entryPointOutput_vColor Location 0
-OpDecorate %10 DescriptorSet 7
-OpDecorate %10 Binding 0
-OpDecorate %gl_FragCoord BuiltIn FragCoord
-OpDecorate %_runtimearr_uint ArrayStride 4
-OpDecorate %_struct_34 Block
-OpMemberDecorate %_struct_34 0 Offset 0
-OpMemberDecorate %_struct_34 1 Offset 4
-OpDecorate %74 DescriptorSet 7
-OpDecorate %74 Binding 0
-%void = OpTypeVoid
-%12 = OpTypeFunction %void
-%float = OpTypeFloat 32
-%v2float = OpTypeVector %float 2
-%v4float = OpTypeVector %float 4
-%int = OpTypeInt 32 1
-%int_0 = OpConstant %int 0
-%18 = OpTypeImage %float 2D 0 0 0 1 Unknown
-%uint = OpTypeInt 32 0
-%uint_128 = OpConstant %uint 128
-%_arr_18_uint_128 = OpTypeArray %18 %uint_128
-%_ptr_UniformConstant__arr_18_uint_128 = OpTypePointer UniformConstant %_arr_18_uint_128
-%g_tColor = OpVariable %_ptr_UniformConstant__arr_18_uint_128 UniformConstant
-%PerViewConstantBuffer_t = OpTypeStruct %uint
-%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
-%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
-%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
-%_ptr_UniformConstant_18 = OpTypePointer UniformConstant %18
-%26 = OpTypeSampler
-%_ptr_UniformConstant_26 = OpTypePointer UniformConstant %26
-%g_sAniso = OpVariable %_ptr_UniformConstant_26 UniformConstant
-%28 = OpTypeSampledImage %18
-%_ptr_Input_v2float = OpTypePointer Input %v2float
-%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
-%_ptr_Output_v4float = OpTypePointer Output %v4float
-%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
-%uint_0 = OpConstant %uint 0
-%bool = OpTypeBool
-%_runtimearr_uint = OpTypeRuntimeArray %uint
-%_struct_34 = OpTypeStruct %uint %_runtimearr_uint
-%_ptr_StorageBuffer__struct_34 = OpTypePointer StorageBuffer %_struct_34
-%10 = OpVariable %_ptr_StorageBuffer__struct_34 StorageBuffer
-%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
-%uint_10 = OpConstant %uint 10
-%uint_4 = OpConstant %uint 4
-%uint_1 = OpConstant %uint 1
-%uint_23 = OpConstant %uint 23
-%uint_2 = OpConstant %uint 2
-%uint_9 = OpConstant %uint 9
-%uint_3 = OpConstant %uint 3
-%_ptr_Input_v4float = OpTypePointer Input %v4float
-%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
-%v4uint = OpTypeVector %uint 4
-%uint_5 = OpConstant %uint 5
-%uint_6 = OpConstant %uint 6
-%uint_7 = OpConstant %uint 7
-%uint_8 = OpConstant %uint 8
-%50 = OpConstantNull %v4float
-%68 = OpTypeFunction %void %uint %uint %uint %uint
-%74 = OpVariable %_ptr_StorageBuffer__struct_34 StorageBuffer
-%uint_82 = OpConstant %uint 82
-)";
-
-  const std::string func_before =
-      R"(%MainPs = OpFunction %void None %3
-%5 = OpLabel
-%53 = OpLoad %v2float %i_vTextureCoords
-%63 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
-%64 = OpLoad %uint %63
-%65 = OpAccessChain %_ptr_UniformConstant_20 %g_tColor %64
-%67 = OpLoad %35 %g_sAniso
-%78 = OpLoad %20 %65
-%79 = OpSampledImage %39 %78 %67
-%71 = OpImageSampleImplicitLod %v4float %79 %53
-OpStore %_entryPointOutput_vColor %71
-OpReturn
-OpFunctionEnd
-)";
-
-  const std::string func_after =
-      R"(%MainPs = OpFunction %void None %12
-%51 = OpLabel
-%52 = OpLoad %v2float %i_vTextureCoords
-%53 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
-%54 = OpLoad %uint %53
-%55 = OpAccessChain %_ptr_UniformConstant_18 %g_tColor %54
-%56 = OpLoad %26 %g_sAniso
-%57 = OpLoad %18 %55
-%58 = OpSampledImage %28 %57 %56
-%60 = OpULessThan %bool %54 %uint_128
-OpSelectionMerge %61 None
-OpBranchConditional %60 %62 %63
-%62 = OpLabel
-%64 = OpLoad %18 %55
-%65 = OpSampledImage %28 %64 %56
-%66 = OpImageSampleImplicitLod %v4float %65 %52
-OpBranch %61
-%63 = OpLabel
-%105 = OpFunctionCall %void %67 %uint_82 %uint_0 %54 %uint_128
-OpBranch %61
-%61 = OpLabel
-%106 = OpPhi %v4float %66 %62 %50 %63
-OpStore %_entryPointOutput_vColor %106
-OpReturn
-OpFunctionEnd
-)";
-
-  const std::string output_func =
-      R"(%67 = OpFunction %void None %68
-%69 = OpFunctionParameter %uint
-%70 = OpFunctionParameter %uint
-%71 = OpFunctionParameter %uint
-%72 = OpFunctionParameter %uint
-%73 = OpLabel
-%75 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_0
-%76 = OpAtomicIAdd %uint %75 %uint_4 %uint_0 %uint_9
-%77 = OpIAdd %uint %76 %uint_9
-%78 = OpArrayLength %uint %74 1
-%79 = OpULessThanEqual %bool %77 %78
-OpSelectionMerge %80 None
-OpBranchConditional %79 %81 %80
-%81 = OpLabel
-%82 = OpIAdd %uint %76 %uint_0
-%83 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %82
-OpStore %83 %uint_9
-%84 = OpIAdd %uint %76 %uint_1
-%85 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %84
-OpStore %85 %uint_23
-%86 = OpIAdd %uint %76 %uint_2
-%87 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %86
-OpStore %87 %69
-%88 = OpIAdd %uint %76 %uint_3
-%89 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %88
-OpStore %89 %uint_4
-%90 = OpLoad %v4float %gl_FragCoord
-%91 = OpBitcast %v4uint %90
-%92 = OpCompositeExtract %uint %91 0
-%93 = OpIAdd %uint %76 %uint_4
-%94 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %93
-OpStore %94 %92
-%95 = OpCompositeExtract %uint %91 1
-%96 = OpIAdd %uint %76 %uint_5
-%97 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %96
-OpStore %97 %95
-%98 = OpIAdd %uint %76 %uint_6
-%99 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %98
-OpStore %99 %70
-%100 = OpIAdd %uint %76 %uint_7
-%101 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %100
-OpStore %101 %71
-%102 = OpIAdd %uint %76 %uint_8
-%103 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %102
-OpStore %103 %72
-OpBranch %80
-%80 = OpLabel
-OpReturn
-OpFunctionEnd
-)";
-
-  // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
-  SinglePassRunAndCheck<InstBindlessCheckPass>(
-      defs_before + func_before, defs_after + func_after + output_func, true,
-      true);
-}
-
 TEST_F(InstBindlessTest, InstrumentOpImage) {
   // This test verifies that the pass will correctly instrument shader
   // using OpImage. This test was created by editing the SPIR-V
@@ -1848,6 +1580,261 @@ OpFunctionEnd
       true);
 }
 
+TEST_F(InstBindlessTest, RuntimeArray) {
+  // This test verifies that the pass will correctly instrument shader
+  // with runtime descriptor array. This test was created by editing the
+  // SPIR-V from the Simple test.
+
+  const std::string defs_before =
+      R"(OpCapability Shader
+OpCapability RuntimeDescriptorArrayEXT
+OpExtension "SPV_EXT_descriptor_indexing"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %g_sAniso "g_sAniso"
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 0
+OpDecorate %g_tColor Binding 0
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %g_sAniso DescriptorSet 0
+OpDecorate %g_sAniso Binding 1
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%20 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%uint = OpTypeInt 32 0
+%uint_1 = OpConstant %uint 1
+%_rarr_20 = OpTypeRuntimeArray %20
+%_ptr_UniformConstant__arr_20 = OpTypePointer UniformConstant %_rarr_20
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_20 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_20 = OpTypePointer UniformConstant %20
+%35 = OpTypeSampler
+%_ptr_UniformConstant_35 = OpTypePointer UniformConstant %35
+%g_sAniso = OpVariable %_ptr_UniformConstant_35 UniformConstant
+%39 = OpTypeSampledImage %20
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+  const std::string defs_after =
+      R"(OpCapability Shader
+OpCapability RuntimeDescriptorArrayEXT
+OpExtension "SPV_EXT_descriptor_indexing"
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %g_sAniso "g_sAniso"
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 0
+OpDecorate %g_tColor Binding 0
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %g_sAniso DescriptorSet 0
+OpDecorate %g_sAniso Binding 1
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+OpDecorate %_runtimearr_uint ArrayStride 4
+OpDecorate %_struct_40 Block
+OpMemberDecorate %_struct_40 0 Offset 0
+OpDecorate %42 DescriptorSet 7
+OpDecorate %42 Binding 1
+OpDecorate %_struct_64 Block
+OpMemberDecorate %_struct_64 0 Offset 0
+OpMemberDecorate %_struct_64 1 Offset 4
+OpDecorate %66 DescriptorSet 7
+OpDecorate %66 Binding 0
+OpDecorate %gl_FragCoord BuiltIn FragCoord
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%16 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%uint = OpTypeInt 32 0
+%uint_1 = OpConstant %uint 1
+%_runtimearr_16 = OpTypeRuntimeArray %16
+%_ptr_UniformConstant__runtimearr_16 = OpTypePointer UniformConstant %_runtimearr_16
+%g_tColor = OpVariable %_ptr_UniformConstant__runtimearr_16 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_16 = OpTypePointer UniformConstant %16
+%24 = OpTypeSampler
+%_ptr_UniformConstant_24 = OpTypePointer UniformConstant %24
+%g_sAniso = OpVariable %_ptr_UniformConstant_24 UniformConstant
+%26 = OpTypeSampledImage %16
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+%uint_0 = OpConstant %uint 0
+%_runtimearr_uint = OpTypeRuntimeArray %uint
+%_struct_40 = OpTypeStruct %_runtimearr_uint
+%_ptr_StorageBuffer__struct_40 = OpTypePointer StorageBuffer %_struct_40
+%42 = OpVariable %_ptr_StorageBuffer__struct_40 StorageBuffer
+%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+%bool = OpTypeBool
+%58 = OpTypeFunction %void %uint %uint %uint %uint
+%_struct_64 = OpTypeStruct %uint %_runtimearr_uint
+%_ptr_StorageBuffer__struct_64 = OpTypePointer StorageBuffer %_struct_64
+%66 = OpVariable %_ptr_StorageBuffer__struct_64 StorageBuffer
+%uint_9 = OpConstant %uint 9
+%uint_4 = OpConstant %uint 4
+%uint_23 = OpConstant %uint 23
+%uint_2 = OpConstant %uint 2
+%uint_3 = OpConstant %uint 3
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+%v4uint = OpTypeVector %uint 4
+%uint_5 = OpConstant %uint 5
+%uint_6 = OpConstant %uint 6
+%uint_7 = OpConstant %uint 7
+%uint_8 = OpConstant %uint 8
+%uint_59 = OpConstant %uint 59
+%110 = OpConstantNull %v4float
+)";
+
+  const std::string func_before =
+      R"(%MainPs = OpFunction %void None %3
+%5 = OpLabel
+%53 = OpLoad %v2float %i_vTextureCoords
+%63 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%64 = OpLoad %uint %63
+%65 = OpAccessChain %_ptr_UniformConstant_20 %g_tColor %64
+%66 = OpLoad %20 %65
+%67 = OpLoad %35 %g_sAniso
+%68 = OpSampledImage %39 %66 %67
+%71 = OpImageSampleImplicitLod %v4float %68 %53
+OpStore %_entryPointOutput_vColor %71
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string func_after =
+      R"(%MainPs = OpFunction %void None %10
+%29 = OpLabel
+%30 = OpLoad %v2float %i_vTextureCoords
+%31 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%32 = OpLoad %uint %31
+%33 = OpAccessChain %_ptr_UniformConstant_16 %g_tColor %32
+%34 = OpLoad %16 %33
+%35 = OpLoad %24 %g_sAniso
+%36 = OpSampledImage %26 %34 %35
+%44 = OpAccessChain %_ptr_StorageBuffer_uint %42 %uint_0 %uint_1
+%45 = OpLoad %uint %44
+%46 = OpIAdd %uint %45 %uint_0
+%47 = OpAccessChain %_ptr_StorageBuffer_uint %42 %uint_0 %46
+%48 = OpLoad %uint %47
+%50 = OpULessThan %bool %32 %48
+OpSelectionMerge %51 None
+OpBranchConditional %50 %52 %53
+%52 = OpLabel
+%54 = OpLoad %16 %33
+%55 = OpSampledImage %26 %54 %35
+%56 = OpImageSampleImplicitLod %v4float %55 %30
+OpBranch %51
+%53 = OpLabel
+%109 = OpFunctionCall %void %57 %uint_59 %uint_0 %32 %48
+OpBranch %51
+%51 = OpLabel
+%111 = OpPhi %v4float %56 %52 %110 %53
+OpStore %_entryPointOutput_vColor %111
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string output_func =
+      R"(%57 = OpFunction %void None %58
+%59 = OpFunctionParameter %uint
+%60 = OpFunctionParameter %uint
+%61 = OpFunctionParameter %uint
+%62 = OpFunctionParameter %uint
+%63 = OpLabel
+%67 = OpAccessChain %_ptr_StorageBuffer_uint %66 %uint_0
+%70 = OpAtomicIAdd %uint %67 %uint_4 %uint_0 %uint_9
+%71 = OpIAdd %uint %70 %uint_9
+%72 = OpArrayLength %uint %66 1
+%73 = OpULessThanEqual %bool %71 %72
+OpSelectionMerge %74 None
+OpBranchConditional %73 %75 %74
+%75 = OpLabel
+%76 = OpIAdd %uint %70 %uint_0
+%77 = OpAccessChain %_ptr_StorageBuffer_uint %66 %uint_1 %76
+OpStore %77 %uint_9
+%79 = OpIAdd %uint %70 %uint_1
+%80 = OpAccessChain %_ptr_StorageBuffer_uint %66 %uint_1 %79
+OpStore %80 %uint_23
+%82 = OpIAdd %uint %70 %uint_2
+%83 = OpAccessChain %_ptr_StorageBuffer_uint %66 %uint_1 %82
+OpStore %83 %59
+%85 = OpIAdd %uint %70 %uint_3
+%86 = OpAccessChain %_ptr_StorageBuffer_uint %66 %uint_1 %85
+OpStore %86 %uint_4
+%89 = OpLoad %v4float %gl_FragCoord
+%91 = OpBitcast %v4uint %89
+%92 = OpCompositeExtract %uint %91 0
+%93 = OpIAdd %uint %70 %uint_4
+%94 = OpAccessChain %_ptr_StorageBuffer_uint %66 %uint_1 %93
+OpStore %94 %92
+%95 = OpCompositeExtract %uint %91 1
+%97 = OpIAdd %uint %70 %uint_5
+%98 = OpAccessChain %_ptr_StorageBuffer_uint %66 %uint_1 %97
+OpStore %98 %95
+%100 = OpIAdd %uint %70 %uint_6
+%101 = OpAccessChain %_ptr_StorageBuffer_uint %66 %uint_1 %100
+OpStore %101 %60
+%103 = OpIAdd %uint %70 %uint_7
+%104 = OpAccessChain %_ptr_StorageBuffer_uint %66 %uint_1 %103
+OpStore %104 %61
+%106 = OpIAdd %uint %70 %uint_8
+%107 = OpAccessChain %_ptr_StorageBuffer_uint %66 %uint_1 %106
+OpStore %107 %62
+OpBranch %74
+%74 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<InstBindlessCheckPass>(
+      defs_before + func_before, defs_after + func_after + output_func, true,
+      true);
+}
+
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 //
 // TODO(greg-lunarg): Come up with cases to put here :)

+ 127 - 0
3rdparty/spirv-tools/test/opt/optimizer_test.cpp

@@ -222,6 +222,133 @@ TEST(Optimizer, CanRegisterPassesFromFlags) {
   EXPECT_EQ(msg_level, SPV_MSG_ERROR);
 }
 
+TEST(Optimizer, WebGPUModeSetsCorrectPasses) {
+  Optimizer opt(SPV_ENV_WEBGPU_0);
+  opt.RegisterWebGPUPasses();
+  std::vector<const char*> pass_names = opt.GetPassNames();
+
+  std::vector<std::string> registered_passes;
+  for (auto name = pass_names.begin(); name != pass_names.end(); ++name)
+    registered_passes.push_back(*name);
+
+  std::vector<std::string> expected_passes = {
+      "eliminate-dead-branches", "eliminate-dead-code-aggressive",
+      "flatten-decorations", "strip-debug"};
+  std::sort(registered_passes.begin(), registered_passes.end());
+  std::sort(expected_passes.begin(), expected_passes.end());
+
+  ASSERT_EQ(registered_passes.size(), expected_passes.size());
+  for (size_t i = 0; i < registered_passes.size(); i++)
+    EXPECT_EQ(registered_passes[i], expected_passes[i]);
+}
+
+TEST(Optimizer, WebGPUModeFlattenDecorationsRuns) {
+  const std::string input = R"(OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+OpEntryPoint Fragment %main "main" %hue %saturation %value
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %group Flat
+OpDecorate %group NoPerspective
+%group = OpDecorationGroup
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Input_float = OpTypePointer Input %float
+%hue = OpVariable %_ptr_Input_float Input
+%saturation = OpVariable %_ptr_Input_float Input
+%value = OpVariable %_ptr_Input_float Input
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string expected = R"(OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+OpEntryPoint Fragment %1 "main" %2 %3 %4
+OpExecutionMode %1 OriginUpperLeft
+%void = OpTypeVoid
+%7 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Input_float = OpTypePointer Input %float
+%2 = OpVariable %_ptr_Input_float Input
+%3 = OpVariable %_ptr_Input_float Input
+%4 = OpVariable %_ptr_Input_float Input
+%1 = OpFunction %void None %7
+%10 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SpirvTools tools(SPV_ENV_WEBGPU_0);
+  std::vector<uint32_t> binary;
+  tools.Assemble(input, &binary);
+
+  Optimizer opt(SPV_ENV_WEBGPU_0);
+  opt.RegisterWebGPUPasses();
+
+  std::vector<uint32_t> optimized;
+  ValidatorOptions validator_options;
+  ASSERT_TRUE(opt.Run(binary.data(), binary.size(), &optimized,
+                      validator_options, true));
+
+  std::string disassembly;
+  tools.Disassemble(optimized.data(), optimized.size(), &disassembly);
+
+  EXPECT_EQ(expected, disassembly);
+}
+
+TEST(Optimizer, WebGPUModeStripDebugRuns) {
+  const std::string input = R"(OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+OpEntryPoint Vertex %func "shader"
+OpName %main "main"
+OpName %void_fn "void_fn"
+%void = OpTypeVoid
+%void_f = OpTypeFunction %void
+%func = OpFunction %void None %void_f
+%label = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string expected = R"(OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+OpEntryPoint Vertex %1 "shader"
+%void = OpTypeVoid
+%5 = OpTypeFunction %void
+%1 = OpFunction %void None %5
+%6 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SpirvTools tools(SPV_ENV_WEBGPU_0);
+  std::vector<uint32_t> binary;
+  tools.Assemble(input, &binary);
+
+  Optimizer opt(SPV_ENV_WEBGPU_0);
+  opt.RegisterWebGPUPasses();
+
+  std::vector<uint32_t> optimized;
+  ValidatorOptions validator_options;
+  ASSERT_TRUE(opt.Run(binary.data(), binary.size(), &optimized,
+                      validator_options, true));
+
+  std::string disassembly;
+  tools.Disassemble(optimized.data(), optimized.size(), &disassembly);
+
+  EXPECT_EQ(expected, disassembly);
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools

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

@@ -20,6 +20,7 @@ add_spvtools_unittest(TARGET reduce
         reduce_test_util.cpp
         reduce_test_util.h
         reducer_test.cpp
+        remove_function_test.cpp
         remove_opname_instruction_test.cpp
         remove_unreferenced_instruction_test.cpp
         structured_loop_to_selection_test.cpp

+ 294 - 0
3rdparty/spirv-tools/test/reduce/remove_function_test.cpp

@@ -0,0 +1,294 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "reduce_test_util.h"
+#include "source/opt/build_module.h"
+#include "source/reduce/reduction_opportunity.h"
+#include "source/reduce/remove_function_reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+namespace {
+
+// Helper to count the number of functions in the module.
+// Remove if there turns out to be a more direct way to do this.
+uint32_t count_functions(opt::IRContext* context) {
+  uint32_t result = 0;
+  for (auto& function : *context->module()) {
+    (void)(function);
+    ++result;
+  }
+  return result;
+}
+
+TEST(RemoveFunctionTest, BasicCheck) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+               OpReturn
+               OpFunctionEnd
+          %8 = OpFunction %2 None %3
+          %9 = OpLabel
+         %10 = OpFunctionCall %2 %6
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, shader, kReduceAssembleOption);
+
+  ASSERT_EQ(3, count_functions(context.get()));
+
+  auto ops =
+      RemoveFunctionReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+
+  ASSERT_EQ(2, count_functions(context.get()));
+
+  std::string after_first = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, after_first, context.get());
+
+  ops = RemoveFunctionReductionOpportunityFinder().GetAvailableOpportunities(
+      context.get());
+
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+
+  ASSERT_EQ(1, count_functions(context.get()));
+
+  std::string after_second = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, after_second, context.get());
+}
+
+TEST(RemoveFunctionTest, NothingToRemove) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %11 = OpFunctionCall %2 %8
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+               OpReturn
+               OpFunctionEnd
+          %8 = OpFunction %2 None %3
+          %9 = OpLabel
+         %10 = OpFunctionCall %2 %6
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, shader, kReduceAssembleOption);
+  auto ops =
+      RemoveFunctionReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(RemoveFunctionTest, TwoRemovableFunctions) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+               OpReturn
+               OpFunctionEnd
+          %8 = OpFunction %2 None %3
+          %9 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, shader, kReduceAssembleOption);
+
+  ASSERT_EQ(3, count_functions(context.get()));
+
+  auto ops =
+      RemoveFunctionReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+  ASSERT_EQ(2, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+  ASSERT_EQ(2, count_functions(context.get()));
+  ASSERT_TRUE(ops[1]->PreconditionHolds());
+  ops[1]->TryToApply();
+  ASSERT_EQ(1, count_functions(context.get()));
+
+  std::string after = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, after, context.get());
+}
+
+TEST(RemoveFunctionTest, NoRemovalsDueToOpName) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %6 "foo("
+               OpName %8 "bar("
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+               OpReturn
+               OpFunctionEnd
+          %8 = OpFunction %2 None %3
+          %9 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, shader, kReduceAssembleOption);
+  auto ops =
+      RemoveFunctionReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(RemoveFunctionTest, NoRemovalDueToLinkageDecoration) {
+  // The non-entry point function is not removable because it is referenced by a
+  // linkage decoration. Thus no function can be removed.
+  std::string shader = R"(
+               OpCapability Shader
+               OpCapability Linkage
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %1 "main"
+               OpName %1 "main"
+               OpDecorate %2 LinkageAttributes "ExportedFunc" Export
+          %4 = OpTypeVoid
+          %5 = OpTypeFunction %4
+          %1 = OpFunction %4 None %5
+          %6 = OpLabel
+               OpReturn
+               OpFunctionEnd
+          %2 = OpFunction %4 None %5
+          %7 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, shader, kReduceAssembleOption);
+  auto ops =
+      RemoveFunctionReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+}  // namespace
+}  // namespace reduce
+}  // namespace spvtools

+ 2 - 1
3rdparty/spirv-tools/test/val/CMakeLists.txt

@@ -51,8 +51,9 @@ add_spvtools_unittest(TARGET val_limits
   PCH_FILE pch_test_val
 )
 
-add_spvtools_unittest(TARGET val_ijklmnop
+add_spvtools_unittest(TARGET val_fghijklmnop
   SRCS
+       val_function_test.cpp
        val_id_test.cpp
        val_image_test.cpp
        val_interfaces_test.cpp

+ 1 - 1
3rdparty/spirv-tools/test/val/val_builtins_test.cpp

@@ -1995,7 +1995,7 @@ INSTANTIATE_TEST_SUITE_P(
                               "needs to be a 4-component 32-bit float vector",
                               "is not a float vector"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PositionArrayedF32Vec4Vertex, ValidateWebGPUCombineBuiltInArrayedVariable,
     Combine(Values("Position"), Values("Vertex"), Values("Output"),
             Values("%f32vec4"),

+ 24 - 0
3rdparty/spirv-tools/test/val/val_decoration_test.cpp

@@ -5517,6 +5517,8 @@ std::string ShaderWithNonWritableTarget(const std::string& target,
 
   return std::string(R"(
             OpCapability Shader
+            OpCapability RuntimeDescriptorArrayEXT
+            OpExtension "SPV_EXT_descriptor_indexing"
             OpExtension "SPV_KHR_storage_buffer_storage_class"
             OpMemoryModel Logical GLSL450
             OpEntryPoint Vertex %main "main"
@@ -5546,6 +5548,8 @@ std::string ShaderWithNonWritableTarget(const std::string& target,
    %void_fn = OpTypeFunction %void
      %float = OpTypeFloat 32
    %float_0 = OpConstant %float 0
+   %int     = OpTypeInt 32 0
+   %int_2   = OpConstant %int 2
   %struct_b = OpTypeStruct %float
  %struct_bb = OpTypeStruct %float
  %rtarr = OpTypeRuntimeArray %float
@@ -5555,6 +5559,8 @@ std::string ShaderWithNonWritableTarget(const std::string& target,
  %imstor = OpTypeImage %float 2D 0 0 0 2 R32f
  ; sampled image
  %imsam = OpTypeImage %float 2D 0 0 0 1 R32f
+%array_imstor = OpTypeArray %imstor %int_2
+%rta_imstor = OpTypeRuntimeArray %imstor
 
 %_ptr_Uniform_stb        = OpTypePointer Uniform %struct_b
 %_ptr_Uniform_stbb       = OpTypePointer Uniform %struct_bb
@@ -5565,6 +5571,8 @@ std::string ShaderWithNonWritableTarget(const std::string& target,
 %_ptr_Function           = OpTypePointer Function %float
 %_ptr_imstor             = OpTypePointer UniformConstant %imstor
 %_ptr_imsam              = OpTypePointer UniformConstant %imsam
+%_ptr_array_imstor       = OpTypePointer UniformConstant %array_imstor
+%_ptr_rta_imstor         = OpTypePointer UniformConstant %rta_imstor
 
 %extra_fn = OpTypeFunction %void %float %_ptr_Private %_ptr_imstor
 
@@ -5576,6 +5584,8 @@ std::string ShaderWithNonWritableTarget(const std::string& target,
 %var_priv = OpVariable %_ptr_Private Private
 %var_imstor = OpVariable %_ptr_imstor UniformConstant
 %var_imsam = OpVariable %_ptr_imsam UniformConstant
+%var_array_imstor = OpVariable %_ptr_array_imstor UniformConstant
+%var_rta_imstor = OpVariable %_ptr_rta_imstor UniformConstant
 
   %helper = OpFunction %void None %extra_fn
  %param_f = OpFunctionParameter %float
@@ -5754,6 +5764,20 @@ TEST_F(ValidateDecorations, NonWritableVarFunctionBad) {
                         "buffer\n  %var_func"));
 }
 
+TEST_F(ValidateDecorations, NonWritableArrayGood) {
+  std::string spirv = ShaderWithNonWritableTarget("%var_array_imstor");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateDecorations, NonWritableRuntimeArrayGood) {
+  std::string spirv = ShaderWithNonWritableTarget("%var_rta_imstor");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
 TEST_P(ValidateWebGPUCombineDecorationResult, Decorate) {
   const char* const decoration = std::get<0>(GetParam());
   const TestResult& test_result = std::get<1>(GetParam());

+ 7 - 0
3rdparty/spirv-tools/test/val/val_fixtures.h

@@ -125,6 +125,13 @@ void ValidateBase<T>::OverwriteAssembledBinary(uint32_t index, uint32_t word) {
 template <typename T>
 spv_result_t ValidateBase<T>::ValidateInstructions(spv_target_env env) {
   DestroyDiagnostic();
+  if (binary_ == nullptr) {
+    fprintf(stderr,
+            "ERROR: Attempting to validate a null binary, did you forget to "
+            "call CompileSuccessfully?");
+    fflush(stderr);
+  }
+  assert(binary_ != nullptr);
   return spvValidateWithOptions(ScopedContext(env).context, options_,
                                 get_const_binary(), &diagnostic_);
 }

+ 424 - 0
3rdparty/spirv-tools/test/val/val_function_test.cpp

@@ -0,0 +1,424 @@
+// Copyright (c) 2019 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 <sstream>
+#include <string>
+#include <tuple>
+
+#include "gmock/gmock.h"
+#include "test/test_fixture.h"
+#include "test/unit_spirv.h"
+#include "test/val/val_fixtures.h"
+
+namespace spvtools {
+namespace val {
+namespace {
+
+using ::testing::Combine;
+using ::testing::HasSubstr;
+using ::testing::Values;
+
+using ValidateFunctionCall = spvtest::ValidateBase<std::string>;
+
+std::string GenerateShader(const std::string& storage_class,
+                           const std::string& capabilities,
+                           const std::string& extensions) {
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability AtomicStorage
+)" + capabilities + R"(
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+)" +
+                      extensions + R"(
+OpMemoryModel Logical GLSL450
+OpName %var "var"
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%ptr = OpTypePointer )" + storage_class + R"( %int
+%caller_ty = OpTypeFunction %void
+%callee_ty = OpTypeFunction %void %ptr
+)";
+
+  if (storage_class != "Function") {
+    spirv += "%var = OpVariable %ptr " + storage_class;
+  }
+
+  spirv += R"(
+%caller = OpFunction %void None %caller_ty
+%1 = OpLabel
+)";
+
+  if (storage_class == "Function") {
+    spirv += "%var = OpVariable %ptr Function";
+  }
+
+  spirv += R"(
+%call = OpFunctionCall %void %callee %var
+OpReturn
+OpFunctionEnd
+%callee = OpFunction %void None %callee_ty
+%param = OpFunctionParameter %ptr
+%2 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  return spirv;
+}
+
+std::string GenerateShaderParameter(const std::string& storage_class,
+                                    const std::string& capabilities,
+                                    const std::string& extensions) {
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability AtomicStorage
+)" + capabilities + R"(
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+)" +
+                      extensions + R"(
+OpMemoryModel Logical GLSL450
+OpName %p "p"
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%ptr = OpTypePointer )" + storage_class + R"( %int
+%func_ty = OpTypeFunction %void %ptr
+%caller = OpFunction %void None %func_ty
+%p = OpFunctionParameter %ptr
+%1 = OpLabel
+%call = OpFunctionCall %void %callee %p
+OpReturn
+OpFunctionEnd
+%callee = OpFunction %void None %func_ty
+%param = OpFunctionParameter %ptr
+%2 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  return spirv;
+}
+
+std::string GenerateShaderAccessChain(const std::string& storage_class,
+                                      const std::string& capabilities,
+                                      const std::string& extensions) {
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability AtomicStorage
+)" + capabilities + R"(
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+)" +
+                      extensions + R"(
+OpMemoryModel Logical GLSL450
+OpName %var "var"
+OpName %gep "gep"
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int2 = OpTypeVector %int 2
+%int_0 = OpConstant %int 0
+%ptr = OpTypePointer )" + storage_class + R"( %int2
+%ptr2 = OpTypePointer )" +
+                      storage_class + R"( %int
+%caller_ty = OpTypeFunction %void
+%callee_ty = OpTypeFunction %void %ptr2
+)";
+
+  if (storage_class != "Function") {
+    spirv += "%var = OpVariable %ptr " + storage_class;
+  }
+
+  spirv += R"(
+%caller = OpFunction %void None %caller_ty
+%1 = OpLabel
+)";
+
+  if (storage_class == "Function") {
+    spirv += "%var = OpVariable %ptr Function";
+  }
+
+  spirv += R"(
+%gep = OpAccessChain %ptr2 %var %int_0
+%call = OpFunctionCall %void %callee %gep
+OpReturn
+OpFunctionEnd
+%callee = OpFunction %void None %callee_ty
+%param = OpFunctionParameter %ptr2
+%2 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  return spirv;
+}
+
+TEST_P(ValidateFunctionCall, VariableNoVariablePointers) {
+  const std::string storage_class = GetParam();
+
+  std::string spirv = GenerateShader(storage_class, "", "");
+
+  const std::vector<std::string> valid_storage_classes = {
+      "UniformConstant", "Function", "Private", "Workgroup", "AtomicCounter"};
+  bool valid =
+      std::find(valid_storage_classes.begin(), valid_storage_classes.end(),
+                storage_class) != valid_storage_classes.end();
+
+  CompileSuccessfully(spirv);
+  if (valid) {
+    EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  } else {
+    EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+    if (storage_class == "StorageBuffer") {
+      EXPECT_THAT(getDiagnosticString(),
+                  HasSubstr("StorageBuffer pointer operand 1[%var] requires a "
+                            "variable pointers capability"));
+    } else {
+      EXPECT_THAT(
+          getDiagnosticString(),
+          HasSubstr("Invalid storage class for pointer operand 1[%var]"));
+    }
+  }
+}
+
+TEST_P(ValidateFunctionCall, VariableVariablePointersStorageClass) {
+  const std::string storage_class = GetParam();
+
+  std::string spirv = GenerateShader(
+      storage_class, "OpCapability VariablePointersStorageBuffer",
+      "OpExtension \"SPV_KHR_variable_pointers\"");
+
+  const std::vector<std::string> valid_storage_classes = {
+      "UniformConstant", "Function",      "Private",
+      "Workgroup",       "StorageBuffer", "AtomicCounter"};
+  bool valid =
+      std::find(valid_storage_classes.begin(), valid_storage_classes.end(),
+                storage_class) != valid_storage_classes.end();
+
+  CompileSuccessfully(spirv);
+  if (valid) {
+    EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  } else {
+    EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+    EXPECT_THAT(getDiagnosticString(),
+                HasSubstr("Invalid storage class for pointer operand 1[%var]"));
+  }
+}
+
+TEST_P(ValidateFunctionCall, VariableVariablePointers) {
+  const std::string storage_class = GetParam();
+
+  std::string spirv =
+      GenerateShader(storage_class, "OpCapability VariablePointers",
+                     "OpExtension \"SPV_KHR_variable_pointers\"");
+
+  const std::vector<std::string> valid_storage_classes = {
+      "UniformConstant", "Function",      "Private",
+      "Workgroup",       "StorageBuffer", "AtomicCounter"};
+  bool valid =
+      std::find(valid_storage_classes.begin(), valid_storage_classes.end(),
+                storage_class) != valid_storage_classes.end();
+
+  CompileSuccessfully(spirv);
+  if (valid) {
+    EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  } else {
+    EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+    EXPECT_THAT(getDiagnosticString(),
+                HasSubstr("Invalid storage class for pointer operand 1[%var]"));
+  }
+}
+
+TEST_P(ValidateFunctionCall, ParameterNoVariablePointers) {
+  const std::string storage_class = GetParam();
+
+  std::string spirv = GenerateShaderParameter(storage_class, "", "");
+
+  const std::vector<std::string> valid_storage_classes = {
+      "UniformConstant", "Function", "Private", "Workgroup", "AtomicCounter"};
+  bool valid =
+      std::find(valid_storage_classes.begin(), valid_storage_classes.end(),
+                storage_class) != valid_storage_classes.end();
+
+  CompileSuccessfully(spirv);
+  if (valid) {
+    EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  } else {
+    EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+    if (storage_class == "StorageBuffer") {
+      EXPECT_THAT(getDiagnosticString(),
+                  HasSubstr("StorageBuffer pointer operand 1[%p] requires a "
+                            "variable pointers capability"));
+    } else {
+      EXPECT_THAT(getDiagnosticString(),
+                  HasSubstr("Invalid storage class for pointer operand 1[%p]"));
+    }
+  }
+}
+
+TEST_P(ValidateFunctionCall, ParameterVariablePointersStorageBuffer) {
+  const std::string storage_class = GetParam();
+
+  std::string spirv = GenerateShaderParameter(
+      storage_class, "OpCapability VariablePointersStorageBuffer",
+      "OpExtension \"SPV_KHR_variable_pointers\"");
+
+  const std::vector<std::string> valid_storage_classes = {
+      "UniformConstant", "Function",      "Private",
+      "Workgroup",       "StorageBuffer", "AtomicCounter"};
+  bool valid =
+      std::find(valid_storage_classes.begin(), valid_storage_classes.end(),
+                storage_class) != valid_storage_classes.end();
+
+  CompileSuccessfully(spirv);
+  if (valid) {
+    EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  } else {
+    EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+    EXPECT_THAT(getDiagnosticString(),
+                HasSubstr("Invalid storage class for pointer operand 1[%p]"));
+  }
+}
+
+TEST_P(ValidateFunctionCall, ParameterVariablePointers) {
+  const std::string storage_class = GetParam();
+
+  std::string spirv =
+      GenerateShaderParameter(storage_class, "OpCapability VariablePointers",
+                              "OpExtension \"SPV_KHR_variable_pointers\"");
+
+  const std::vector<std::string> valid_storage_classes = {
+      "UniformConstant", "Function",      "Private",
+      "Workgroup",       "StorageBuffer", "AtomicCounter"};
+  bool valid =
+      std::find(valid_storage_classes.begin(), valid_storage_classes.end(),
+                storage_class) != valid_storage_classes.end();
+
+  CompileSuccessfully(spirv);
+  if (valid) {
+    EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  } else {
+    EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+    EXPECT_THAT(getDiagnosticString(),
+                HasSubstr("Invalid storage class for pointer operand 1[%p]"));
+  }
+}
+
+TEST_P(ValidateFunctionCall, NonMemoryObjectDeclarationNoVariablePointers) {
+  const std::string storage_class = GetParam();
+
+  std::string spirv = GenerateShaderAccessChain(storage_class, "", "");
+
+  const std::vector<std::string> valid_storage_classes = {
+      "UniformConstant", "Function", "Private", "Workgroup", "AtomicCounter"};
+  bool valid_sc =
+      std::find(valid_storage_classes.begin(), valid_storage_classes.end(),
+                storage_class) != valid_storage_classes.end();
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  if (valid_sc) {
+    EXPECT_THAT(
+        getDiagnosticString(),
+        HasSubstr(
+            "Pointer operand 2[%gep] must be a memory object declaration"));
+  } else {
+    if (storage_class == "StorageBuffer") {
+      EXPECT_THAT(getDiagnosticString(),
+                  HasSubstr("StorageBuffer pointer operand 2[%gep] requires a "
+                            "variable pointers capability"));
+    } else {
+      EXPECT_THAT(
+          getDiagnosticString(),
+          HasSubstr("Invalid storage class for pointer operand 2[%gep]"));
+    }
+  }
+}
+
+TEST_P(ValidateFunctionCall,
+       NonMemoryObjectDeclarationVariablePointersStorageBuffer) {
+  const std::string storage_class = GetParam();
+
+  std::string spirv = GenerateShaderAccessChain(
+      storage_class, "OpCapability VariablePointersStorageBuffer",
+      "OpExtension \"SPV_KHR_variable_pointers\"");
+
+  const std::vector<std::string> valid_storage_classes = {
+      "UniformConstant", "Function",      "Private",
+      "Workgroup",       "StorageBuffer", "AtomicCounter"};
+  bool valid_sc =
+      std::find(valid_storage_classes.begin(), valid_storage_classes.end(),
+                storage_class) != valid_storage_classes.end();
+  bool validate = storage_class == "StorageBuffer";
+
+  CompileSuccessfully(spirv);
+  if (validate) {
+    EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  } else {
+    EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+    if (valid_sc) {
+      EXPECT_THAT(
+          getDiagnosticString(),
+          HasSubstr(
+              "Pointer operand 2[%gep] must be a memory object declaration"));
+    } else {
+      EXPECT_THAT(
+          getDiagnosticString(),
+          HasSubstr("Invalid storage class for pointer operand 2[%gep]"));
+    }
+  }
+}
+
+TEST_P(ValidateFunctionCall, NonMemoryObjectDeclarationVariablePointers) {
+  const std::string storage_class = GetParam();
+
+  std::string spirv =
+      GenerateShaderAccessChain(storage_class, "OpCapability VariablePointers",
+                                "OpExtension \"SPV_KHR_variable_pointers\"");
+
+  const std::vector<std::string> valid_storage_classes = {
+      "UniformConstant", "Function",      "Private",
+      "Workgroup",       "StorageBuffer", "AtomicCounter"};
+  bool valid_sc =
+      std::find(valid_storage_classes.begin(), valid_storage_classes.end(),
+                storage_class) != valid_storage_classes.end();
+  bool validate =
+      storage_class == "StorageBuffer" || storage_class == "Workgroup";
+
+  CompileSuccessfully(spirv);
+  if (validate) {
+    EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  } else {
+    EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+    if (valid_sc) {
+      EXPECT_THAT(
+          getDiagnosticString(),
+          HasSubstr(
+              "Pointer operand 2[%gep] must be a memory object declaration"));
+    } else {
+      EXPECT_THAT(
+          getDiagnosticString(),
+          HasSubstr("Invalid storage class for pointer operand 2[%gep]"));
+    }
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(StorageClass, ValidateFunctionCall,
+                         Values("UniformConstant", "Input", "Uniform", "Output",
+                                "Workgroup", "Private", "Function",
+                                "PushConstant", "Image", "StorageBuffer",
+                                "AtomicCounter"));
+}  // namespace
+}  // namespace val
+}  // namespace spvtools

+ 4 - 3
3rdparty/spirv-tools/test/val/val_id_test.cpp

@@ -2274,6 +2274,7 @@ void createVariablePointerSpirvProgram(std::ostringstream* spirv,
     *spirv << "OpCapability VariablePointers ";
     *spirv << "OpExtension \"SPV_KHR_variable_pointers\" ";
   }
+  *spirv << "OpExtension \"SPV_KHR_storage_buffer_storage_class\" ";
   *spirv << R"(
     OpMemoryModel Logical GLSL450
     OpEntryPoint GLCompute %main "main"
@@ -2282,12 +2283,12 @@ void createVariablePointerSpirvProgram(std::ostringstream* spirv,
     %bool      = OpTypeBool
     %i32       = OpTypeInt 32 1
     %f32       = OpTypeFloat 32
-    %f32ptr    = OpTypePointer Uniform %f32
+    %f32ptr    = OpTypePointer StorageBuffer %f32
     %i         = OpConstant %i32 1
     %zero      = OpConstant %i32 0
     %float_1   = OpConstant %f32 1.0
-    %ptr1      = OpVariable %f32ptr Uniform
-    %ptr2      = OpVariable %f32ptr Uniform
+    %ptr1      = OpVariable %f32ptr StorageBuffer
+    %ptr2      = OpVariable %f32ptr StorageBuffer
   )";
   if (add_helper_function) {
     *spirv << R"(

+ 7 - 2
3rdparty/spirv-tools/tools/CMakeLists.txt

@@ -42,7 +42,9 @@ if (NOT ${SPIRV_SKIP_EXECUTABLES})
   add_spvtools_tool(TARGET spirv-dis SRCS dis/dis.cpp LIBS ${SPIRV_TOOLS})
   add_spvtools_tool(TARGET spirv-val SRCS val/val.cpp util/cli_consumer.cpp LIBS ${SPIRV_TOOLS})
   add_spvtools_tool(TARGET spirv-opt SRCS opt/opt.cpp util/cli_consumer.cpp LIBS SPIRV-Tools-opt ${SPIRV_TOOLS})
-  add_spvtools_tool(TARGET spirv-reduce SRCS reduce/reduce.cpp util/cli_consumer.cpp LIBS SPIRV-Tools-reduce ${SPIRV_TOOLS})
+  if (NOT DEFINED IOS_PLATFORM) # iOS does not allow std::system calls which spirv-reduce requires
+    add_spvtools_tool(TARGET spirv-reduce SRCS reduce/reduce.cpp util/cli_consumer.cpp LIBS SPIRV-Tools-reduce ${SPIRV_TOOLS})
+  endif()
   add_spvtools_tool(TARGET spirv-link SRCS link/linker.cpp LIBS SPIRV-Tools-link ${SPIRV_TOOLS})
   add_spvtools_tool(TARGET spirv-stats
 	            SRCS stats/stats.cpp
@@ -62,7 +64,10 @@ if (NOT ${SPIRV_SKIP_EXECUTABLES})
                                                  ${SPIRV_HEADER_INCLUDE_DIR})
 
   set(SPIRV_INSTALL_TARGETS spirv-as spirv-dis spirv-val spirv-opt spirv-stats
-                            spirv-cfg spirv-link spirv-reduce)
+                            spirv-cfg spirv-link)
+  if(NOT DEFINED IOS_PLATFORM)
+    set(SPIRV_INSTALL_TARGETS ${SPIRV_INSTALL_TARGETS} spirv-reduce)
+  endif()
 
   if(SPIRV_BUILD_COMPRESSION)
     add_spvtools_tool(TARGET spirv-markv

+ 3 - 0
3rdparty/spirv-tools/tools/reduce/reduce.cpp

@@ -25,6 +25,7 @@
 #include "source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h"
 #include "source/reduce/operand_to_undef_reduction_opportunity_finder.h"
 #include "source/reduce/reducer.h"
+#include "source/reduce/remove_function_reduction_opportunity_finder.h"
 #include "source/reduce/remove_opname_instruction_reduction_opportunity_finder.h"
 #include "source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h"
 #include "source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h"
@@ -243,6 +244,8 @@ int main(int argc, const char** argv) {
           StructuredLoopToSelectionReductionOpportunityFinder>());
   reducer.AddReductionPass(
       spvtools::MakeUnique<MergeBlocksReductionOpportunityFinder>());
+  reducer.AddReductionPass(
+      spvtools::MakeUnique<RemoveFunctionReductionOpportunityFinder>());
 
   reducer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);