Quellcode durchsuchen

Updated spirv-tools.

Бранимир Караџић vor 7 Jahren
Ursprung
Commit
07191eb38b
30 geänderte Dateien mit 1579 neuen und 123 gelöschten Zeilen
  1. 1 0
      3rdparty/spirv-tools/Android.mk
  2. 2 0
      3rdparty/spirv-tools/BUILD.gn
  3. 1 1
      3rdparty/spirv-tools/include/generated/build-version.inc
  4. 4 0
      3rdparty/spirv-tools/include/spirv-tools/optimizer.hpp
  5. 2 0
      3rdparty/spirv-tools/source/opt/CMakeLists.txt
  6. 316 0
      3rdparty/spirv-tools/source/opt/code_sink.cpp
  7. 107 0
      3rdparty/spirv-tools/source/opt/code_sink.h
  8. 7 2
      3rdparty/spirv-tools/source/opt/fold.cpp
  9. 3 0
      3rdparty/spirv-tools/source/opt/ir_context.cpp
  10. 4 3
      3rdparty/spirv-tools/source/opt/loop_descriptor.cpp
  11. 5 3
      3rdparty/spirv-tools/source/opt/loop_descriptor.h
  12. 6 6
      3rdparty/spirv-tools/source/opt/loop_unroller.cpp
  13. 10 1
      3rdparty/spirv-tools/source/opt/optimizer.cpp
  14. 1 0
      3rdparty/spirv-tools/source/opt/passes.h
  15. 2 12
      3rdparty/spirv-tools/source/opt/scalar_replacement_pass.cpp
  16. 8 2
      3rdparty/spirv-tools/source/opt/vector_dce.cpp
  17. 1 0
      3rdparty/spirv-tools/source/opt/vector_dce.h
  18. 81 47
      3rdparty/spirv-tools/source/val/validate_memory.cpp
  19. 24 15
      3rdparty/spirv-tools/source/val/validate_scopes.cpp
  20. 26 17
      3rdparty/spirv-tools/source/val/validate_type.cpp
  21. 1 0
      3rdparty/spirv-tools/test/opt/CMakeLists.txt
  22. 533 0
      3rdparty/spirv-tools/test/opt/code_sink_test.cpp
  23. 9 1
      3rdparty/spirv-tools/test/opt/fold_test.cpp
  24. 5 6
      3rdparty/spirv-tools/test/opt/pass_manager_test.cpp
  25. 27 0
      3rdparty/spirv-tools/test/opt/scalar_replacement_test.cpp
  26. 36 0
      3rdparty/spirv-tools/test/opt/vector_dce_test.cpp
  27. 74 4
      3rdparty/spirv-tools/test/val/val_atomics_test.cpp
  28. 62 0
      3rdparty/spirv-tools/test/val/val_data_test.cpp
  29. 216 0
      3rdparty/spirv-tools/test/val/val_memory_test.cpp
  30. 5 3
      3rdparty/spirv-tools/tools/opt/opt.cpp

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

@@ -80,6 +80,7 @@ SPVTOOLS_OPT_SRC_FILES := \
 		source/opt/cfg.cpp \
 		source/opt/cfg_cleanup_pass.cpp \
 		source/opt/ccp_pass.cpp \
+		source/opt/code_sink.cpp \
 		source/opt/combine_access_chains.cpp \
 		source/opt/common_uniform_elim_pass.cpp \
 		source/opt/compact_ids_pass.cpp \

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

@@ -460,6 +460,8 @@ static_library("spvtools_opt") {
     "source/opt/cfg.h",
     "source/opt/cfg_cleanup_pass.cpp",
     "source/opt/cfg_cleanup_pass.h",
+    "source/opt/code_sink.cpp",
+    "source/opt/code_sink.h",
     "source/opt/combine_access_chains.cpp",
     "source/opt/combine_access_chains.h",
     "source/opt/common_uniform_elim_pass.cpp",

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

@@ -1 +1 @@
-"v2019.2-dev", "SPIRV-Tools v2019.2-dev 3c8b3c81ef7f378717ef88916c06cb221eea890b"
+"v2019.2-dev", "SPIRV-Tools v2019.2-dev 7d149d91055744e5a9bc6368de7b3bb605d0e1a9"

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

@@ -722,6 +722,10 @@ Optimizer::PassToken CreateInstBindlessCheckPass(uint32_t desc_set,
 // conform to that model's requirements.
 Optimizer::PassToken CreateUpgradeMemoryModelPass();
 
+// Create a pass to do code sinking.  Code sinking is a transformation
+// where an instruction is moved into a more deeply nested construct.
+Optimizer::PassToken CreateCodeSinkingPass();
+
 }  // namespace spvtools
 
 #endif  // INCLUDE_SPIRV_TOOLS_OPTIMIZER_HPP_

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

@@ -19,6 +19,7 @@ set(SPIRV_TOOLS_OPT_SOURCES
   ccp_pass.h
   cfg_cleanup_pass.h
   cfg.h
+  code_sink.h
   combine_access_chains.h
   common_uniform_elim_pass.h
   compact_ids_pass.h
@@ -111,6 +112,7 @@ set(SPIRV_TOOLS_OPT_SOURCES
   ccp_pass.cpp
   cfg_cleanup_pass.cpp
   cfg.cpp
+  code_sink.cpp
   combine_access_chains.cpp
   common_uniform_elim_pass.cpp
   compact_ids_pass.cpp

+ 316 - 0
3rdparty/spirv-tools/source/opt/code_sink.cpp

@@ -0,0 +1,316 @@
+// 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 "code_sink.h"
+
+#include <set>
+#include <vector>
+
+#include "source/opt/instruction.h"
+#include "source/opt/ir_builder.h"
+#include "source/opt/ir_context.h"
+#include "source/util/bit_vector.h"
+
+namespace spvtools {
+namespace opt {
+
+Pass::Status CodeSinkingPass::Process() {
+  bool modified = false;
+  for (Function& function : *get_module()) {
+    cfg()->ForEachBlockInPostOrder(function.entry().get(),
+                                   [&modified, this](BasicBlock* bb) {
+                                     if (SinkInstructionsInBB(bb)) {
+                                       modified = true;
+                                     }
+                                   });
+  }
+  return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
+}
+
+bool CodeSinkingPass::SinkInstructionsInBB(BasicBlock* bb) {
+  bool modified = false;
+  for (auto inst = bb->rbegin(); inst != bb->rend(); ++inst) {
+    if (SinkInstruction(&*inst)) {
+      inst = bb->rbegin();
+      modified = true;
+    }
+  }
+  return modified;
+}
+
+bool CodeSinkingPass::SinkInstruction(Instruction* inst) {
+  if (inst->opcode() != SpvOpLoad && inst->opcode() != SpvOpAccessChain) {
+    return false;
+  }
+
+  if (ReferencesMutableMemory(inst)) {
+    return false;
+  }
+
+  if (BasicBlock* target_bb = FindNewBasicBlockFor(inst)) {
+    Instruction* pos = &*target_bb->begin();
+    while (pos->opcode() == SpvOpPhi) {
+      pos = pos->NextNode();
+    }
+
+    inst->InsertBefore(pos);
+    context()->set_instr_block(inst, target_bb);
+    return true;
+  }
+  return false;
+}
+
+BasicBlock* CodeSinkingPass::FindNewBasicBlockFor(Instruction* inst) {
+  assert(inst->result_id() != 0 && "Instruction should have a result.");
+  BasicBlock* original_bb = context()->get_instr_block(inst);
+  BasicBlock* bb = original_bb;
+
+  std::unordered_set<uint32_t> bbs_with_uses;
+  get_def_use_mgr()->ForEachUse(
+      inst, [&bbs_with_uses, this](Instruction* use, uint32_t idx) {
+        if (use->opcode() != SpvOpPhi) {
+          bbs_with_uses.insert(context()->get_instr_block(use)->id());
+        } else {
+          bbs_with_uses.insert(use->GetSingleWordOperand(idx + 1));
+        }
+      });
+
+  while (true) {
+    // If |inst| is used in |bb|, then |inst| cannot be moved any further.
+    if (bbs_with_uses.count(bb->id())) {
+      break;
+    }
+
+    // If |bb| has one successor (succ_bb), and |bb| is the only predecessor
+    // of succ_bb, then |inst| can be moved to succ_bb.  If succ_bb, has move
+    // then one predecessor, then moving |inst| into succ_bb could cause it to
+    // be executed more often, so the search has to stop.
+    if (bb->terminator()->opcode() == SpvOpBranch) {
+      uint32_t succ_bb_id = bb->terminator()->GetSingleWordInOperand(0);
+      if (cfg()->preds(succ_bb_id).size() == 1) {
+        bb = context()->get_instr_block(succ_bb_id);
+        continue;
+      } else {
+        break;
+      }
+    }
+
+    // The remaining checks need to know the merge node.  If there is no merge
+    // instruction or an OpLoopMerge, then it is a break or continue.  We could
+    // figure it out, but not worth doing it now.
+    Instruction* merge_inst = bb->GetMergeInst();
+    if (merge_inst == nullptr || merge_inst->opcode() != SpvOpSelectionMerge) {
+      break;
+    }
+
+    // Check all of the successors of |bb| it see which lead to a use of |inst|
+    // before reaching the merge node.
+    bool used_in_multiple_blocks = false;
+    uint32_t bb_used_in = 0;
+    bb->ForEachSuccessorLabel([this, bb, &bb_used_in, &used_in_multiple_blocks,
+                               &bbs_with_uses](uint32_t* succ_bb_id) {
+      if (IntersectsPath(*succ_bb_id, bb->MergeBlockIdIfAny(), bbs_with_uses)) {
+        if (bb_used_in == 0) {
+          bb_used_in = *succ_bb_id;
+        } else {
+          used_in_multiple_blocks = true;
+        }
+      }
+    });
+
+    // If more than one successor, which is not the merge block, uses |inst|
+    // then we have to leave |inst| in bb because there is none of the
+    // successors dominate all uses of |inst|.
+    if (used_in_multiple_blocks) {
+      break;
+    }
+
+    if (bb_used_in == 0) {
+      // If |inst| is not used before reaching the merge node, then we can move
+      // |inst| to the merge node.
+      bb = context()->get_instr_block(bb->MergeBlockIdIfAny());
+    } else {
+      // If the only successor that leads to a used of |inst| has more than 1
+      // predecessor, then moving |inst| could cause it to be executed more
+      // often, so we cannot move it.
+      if (cfg()->preds(bb_used_in).size() != 1) {
+        break;
+      }
+
+      // If |inst| is used after the merge block, then |bb_used_in| does not
+      // dominate all of the uses.  So we cannot move |inst| any further.
+      if (IntersectsPath(bb->MergeBlockIdIfAny(), original_bb->id(),
+                         bbs_with_uses)) {
+        break;
+      }
+
+      // Otherwise, |bb_used_in| dominates all uses, so move |inst| into that
+      // block.
+      bb = context()->get_instr_block(bb_used_in);
+    }
+    continue;
+  }
+  return (bb != original_bb ? bb : nullptr);
+}
+
+bool CodeSinkingPass::ReferencesMutableMemory(Instruction* inst) {
+  if (!inst->IsLoad()) {
+    return false;
+  }
+
+  Instruction* base_ptr = inst->GetBaseAddress();
+  if (base_ptr->opcode() != SpvOpVariable) {
+    return true;
+  }
+
+  if (base_ptr->IsReadOnlyVariable()) {
+    return false;
+  }
+
+  if (HasUniformMemorySync()) {
+    return true;
+  }
+
+  if (base_ptr->GetSingleWordInOperand(0) != SpvStorageClassUniform) {
+    return true;
+  }
+
+  return HasPossibleStore(base_ptr);
+}
+
+bool CodeSinkingPass::HasUniformMemorySync() {
+  if (checked_for_uniform_sync_) {
+    return has_uniform_sync_;
+  }
+
+  bool has_sync = false;
+  get_module()->ForEachInst([this, &has_sync](Instruction* inst) {
+    switch (inst->opcode()) {
+      case SpvOpMemoryBarrier: {
+        uint32_t mem_semantics_id = inst->GetSingleWordInOperand(1);
+        if (IsSyncOnUniform(mem_semantics_id)) {
+          has_sync = true;
+        }
+        break;
+      }
+      case SpvOpControlBarrier:
+      case SpvOpAtomicLoad:
+      case SpvOpAtomicStore:
+      case SpvOpAtomicExchange:
+      case SpvOpAtomicIIncrement:
+      case SpvOpAtomicIDecrement:
+      case SpvOpAtomicIAdd:
+      case SpvOpAtomicISub:
+      case SpvOpAtomicSMin:
+      case SpvOpAtomicUMin:
+      case SpvOpAtomicSMax:
+      case SpvOpAtomicUMax:
+      case SpvOpAtomicAnd:
+      case SpvOpAtomicOr:
+      case SpvOpAtomicXor:
+      case SpvOpAtomicFlagTestAndSet:
+      case SpvOpAtomicFlagClear: {
+        uint32_t mem_semantics_id = inst->GetSingleWordInOperand(2);
+        if (IsSyncOnUniform(mem_semantics_id)) {
+          has_sync = true;
+        }
+        break;
+      }
+      case SpvOpAtomicCompareExchange:
+      case SpvOpAtomicCompareExchangeWeak:
+        if (IsSyncOnUniform(inst->GetSingleWordInOperand(2)) ||
+            IsSyncOnUniform(inst->GetSingleWordInOperand(3))) {
+          has_sync = true;
+        }
+        break;
+      default:
+        break;
+    }
+  });
+  has_uniform_sync_ = has_sync;
+  return has_sync;
+}
+
+bool CodeSinkingPass::IsSyncOnUniform(uint32_t mem_semantics_id) const {
+  const analysis::Constant* mem_semantics_const =
+      context()->get_constant_mgr()->FindDeclaredConstant(mem_semantics_id);
+  assert(mem_semantics_const != nullptr &&
+         "Expecting memory semantics id to be a constant.");
+  assert(mem_semantics_const->AsIntConstant() &&
+         "Memory semantics should be an integer.");
+  uint32_t mem_semantics_int = mem_semantics_const->GetU32();
+
+  // If it does not affect uniform memory, then it is does not apply to uniform
+  // memory.
+  if ((mem_semantics_int & SpvMemorySemanticsUniformMemoryMask) == 0) {
+    return false;
+  }
+
+  // Check if there is an acquire or release.  If so not, this it does not add
+  // any memory constraints.
+  return (mem_semantics_int & (SpvMemorySemanticsAcquireMask |
+                               SpvMemorySemanticsAcquireReleaseMask |
+                               SpvMemorySemanticsReleaseMask)) != 0;
+}
+
+bool CodeSinkingPass::HasPossibleStore(Instruction* var_inst) {
+  assert(var_inst->opcode() == SpvOpVariable ||
+         var_inst->opcode() == SpvOpAccessChain ||
+         var_inst->opcode() == SpvOpPtrAccessChain);
+
+  return get_def_use_mgr()->WhileEachUser(var_inst, [this](Instruction* use) {
+    switch (use->opcode()) {
+      case SpvOpStore:
+        return true;
+      case SpvOpAccessChain:
+      case SpvOpPtrAccessChain:
+        return HasPossibleStore(use);
+      default:
+        return false;
+    }
+  });
+}
+
+bool CodeSinkingPass::IntersectsPath(uint32_t start, uint32_t end,
+                                     const std::unordered_set<uint32_t>& set) {
+  std::vector<uint32_t> worklist;
+  worklist.push_back(start);
+  std::unordered_set<uint32_t> already_done;
+  already_done.insert(start);
+
+  while (!worklist.empty()) {
+    BasicBlock* bb = context()->get_instr_block(worklist.back());
+    worklist.pop_back();
+
+    if (bb->id() == end) {
+      continue;
+    }
+
+    if (set.count(bb->id())) {
+      return true;
+    }
+
+    bb->ForEachSuccessorLabel([&already_done, &worklist](uint32_t* succ_bb_id) {
+      if (already_done.insert(*succ_bb_id).second) {
+        worklist.push_back(*succ_bb_id);
+      }
+    });
+  }
+  return false;
+}
+
+// namespace opt
+
+}  // namespace opt
+}  // namespace spvtools

+ 107 - 0
3rdparty/spirv-tools/source/opt/code_sink.h

@@ -0,0 +1,107 @@
+// 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_CODE_SINK_H_
+#define SOURCE_OPT_CODE_SINK_H_
+
+#include <unordered_map>
+
+#include "source/opt/ir_context.h"
+#include "source/opt/module.h"
+#include "source/opt/pass.h"
+
+namespace spvtools {
+namespace opt {
+
+// This pass does code sinking for OpAccessChain and OpLoad on variables in
+// uniform storage or in read only memory.  Code sinking is a transformation
+// where an instruction is moved into a more deeply nested construct.
+//
+// The goal is to move these instructions as close as possible to their uses
+// without having to execute them more often or to replicate the instruction.
+// Moving the instruction in this way can lead to shorter live ranges, which can
+// lead to less register pressure.  It can also cause instructions to be
+// executed less often because they could be moved into one path of a selection
+// construct.
+//
+// This optimization can cause register pressure to rise if the operands of the
+// instructions go dead after the instructions being moved. That is why we only
+// move certain OpLoad and OpAccessChain instructions.  They generally have
+// constants, loop induction variables, and global pointers as operands.  The
+// operands are live for a longer time in most cases.
+class CodeSinkingPass : public Pass {
+ public:
+  const char* name() const override { return "code-sink"; }
+  Status Process() override;
+
+  // Return the mask of preserved Analyses.
+  IRContext::Analysis GetPreservedAnalyses() override {
+    return IRContext::kAnalysisDefUse |
+           IRContext::kAnalysisInstrToBlockMapping |
+           IRContext::kAnalysisCombinators | IRContext::kAnalysisCFG |
+           IRContext::kAnalysisDominatorAnalysis |
+           IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap |
+           IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
+  }
+
+ private:
+  // Sinks the instructions in |bb| as much as possible.  Returns true if
+  // something changes.
+  bool SinkInstructionsInBB(BasicBlock* bb);
+
+  // Tries the sink |inst| as much as possible.  Returns true if the instruction
+  // is moved.
+  bool SinkInstruction(Instruction* inst);
+
+  // Returns the basic block in which to move |inst| to move is as close as
+  // possible to the uses of |inst| without increasing the number of times
+  // |inst| will be executed.  Return |nullptr| if there is no need to move
+  // |inst|.
+  BasicBlock* FindNewBasicBlockFor(Instruction* inst);
+
+  // Return true if |inst| reference memory and it is possible that the data in
+  // the memory changes at some point.
+  bool ReferencesMutableMemory(Instruction* inst);
+
+  // Returns true if the module contains an instruction that has a memory
+  // semantics id as an operand, and the memory semantics enforces a
+  // synchronization of uniform memory.  See section 3.25 of the SPIR-V
+  // specification.
+  bool HasUniformMemorySync();
+
+  // Returns true if there may be a store to the variable |var_inst|.
+  bool HasPossibleStore(Instruction* var_inst);
+
+  // Returns true if one of the basic blocks in |set| exists on a path from the
+  // basic block |start| to |end|.
+  bool IntersectsPath(uint32_t start, uint32_t end,
+                      const std::unordered_set<uint32_t>& set);
+
+  // Returns true if |mem_semantics_id| is the id of a constant that, when
+  // interpreted as a memory semantics mask enforces synchronization of uniform
+  // memory.  See section 3.25 of the SPIR-V specification.
+  bool IsSyncOnUniform(uint32_t mem_semantics_id) const;
+
+  // True if a check has for uniform storage has taken place.
+  bool checked_for_uniform_sync_;
+
+  // Cache of whether or not the module has a memory sync on uniform storage.
+  // only valid if |check_for_uniform_sync_| is true.
+  bool has_uniform_sync_;
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // SOURCE_OPT_CODE_SINK_H_

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

@@ -45,8 +45,13 @@ namespace {
 uint32_t InstructionFolder::UnaryOperate(SpvOp opcode, uint32_t operand) const {
   switch (opcode) {
     // Arthimetics
-    case SpvOp::SpvOpSNegate:
-      return -static_cast<int32_t>(operand);
+    case SpvOp::SpvOpSNegate: {
+      int32_t s_operand = static_cast<int32_t>(operand);
+      if (s_operand == std::numeric_limits<int32_t>::min()) {
+        return s_operand;
+      }
+      return -s_operand;
+    }
     case SpvOp::SpvOpNot:
       return ~operand;
     case SpvOp::SpvOpLogicalNot:

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

@@ -127,6 +127,9 @@ void IRContext::InvalidateAnalyses(IRContext::Analysis analyses_to_invalidate) {
     constant_mgr_.reset(nullptr);
   }
   if (analyses_to_invalidate & kAnalysisTypes) {
+    // The ConstantManager contains Type pointers. If the TypeManager goes
+    // away, the ConstantManager has to go away.
+    constant_mgr_.reset(nullptr);
     type_mgr_.reset(nullptr);
   }
 

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

@@ -937,22 +937,23 @@ void LoopDescriptor::PostModificationCleanup() {
 
   for (Loop* loop : loops_to_remove_) {
     loops_.erase(std::find(loops_.begin(), loops_.end(), loop));
+    delete loop;
   }
 
   for (auto& pair : loops_to_add_) {
     Loop* parent = pair.first;
-    Loop* loop = pair.second;
+    std::unique_ptr<Loop> loop = std::move(pair.second);
 
     if (parent) {
       loop->SetParent(nullptr);
-      parent->AddNestedLoop(loop);
+      parent->AddNestedLoop(loop.get());
 
       for (uint32_t block_id : loop->GetBlocks()) {
         parent->AddBasicBlock(block_id);
       }
     }
 
-    loops_.emplace_back(loop);
+    loops_.emplace_back(loop.release());
   }
 
   loops_to_add_.clear();

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

@@ -499,8 +499,8 @@ class LoopDescriptor {
 
   // Mark the loop |loop_to_add| as needing to be added when the user calls
   // PostModificationCleanup. |parent| may be null.
-  inline void AddLoop(Loop* loop_to_add, Loop* parent) {
-    loops_to_add_.emplace_back(std::make_pair(parent, loop_to_add));
+  inline void AddLoop(std::unique_ptr<Loop>&& loop_to_add, Loop* parent) {
+    loops_to_add_.emplace_back(std::make_pair(parent, std::move(loop_to_add)));
   }
 
   // Checks all loops in |this| and will create pre-headers for all loops
@@ -537,7 +537,9 @@ class LoopDescriptor {
   // TODO(dneto): This should be a vector of unique_ptr.  But VisualStudio 2013
   // is unable to compile it.
   using LoopContainerType = std::vector<Loop*>;
-  using LoopsToAddContainerType = std::vector<std::pair<Loop*, Loop*>>;
+
+  using LoopsToAddContainerType =
+      std::vector<std::pair<Loop*, std::unique_ptr<Loop>>>;
 
   // Creates loop descriptors for the function |f|.
   void PopulateList(IRContext* context, const Function* f);

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

@@ -394,12 +394,12 @@ void LoopUnrollerUtilsImpl::PartiallyUnrollResidualFactor(Loop* loop,
   // This is a naked new due to the VS2013 requirement of not having unique
   // pointers in vectors, as it will be inserted into a vector with
   // loop_descriptor.AddLoop.
-  Loop* new_loop = new Loop(*loop);
+  std::unique_ptr<Loop> new_loop = MakeUnique<Loop>(*loop);
 
   // Clear the basic blocks of the new loop.
   new_loop->ClearBlocks();
 
-  DuplicateLoop(loop, new_loop);
+  DuplicateLoop(loop, new_loop.get());
 
   // Add the blocks to the function.
   AddBlocksToFunction(loop->GetMergeBlock());
@@ -414,10 +414,10 @@ void LoopUnrollerUtilsImpl::PartiallyUnrollResidualFactor(Loop* loop,
   loop_induction_variable_ = state_.new_phi;
   // Unroll the new loop by the factor with the usual -1 to account for the
   // existing block iteration.
-  Unroll(new_loop, factor);
+  Unroll(new_loop.get(), factor);
 
-  LinkLastPhisToStart(new_loop);
-  AddBlocksToLoop(new_loop);
+  LinkLastPhisToStart(new_loop.get());
+  AddBlocksToLoop(new_loop.get());
 
   // Add the new merge block to the back of the list of blocks to be added. It
   // needs to be the last block added to maintain dominator order in the binary.
@@ -507,7 +507,7 @@ void LoopUnrollerUtilsImpl::PartiallyUnrollResidualFactor(Loop* loop,
 
   LoopDescriptor& loop_descriptor = *context_->GetLoopDescriptor(&function_);
 
-  loop_descriptor.AddLoop(new_loop, loop->GetParent());
+  loop_descriptor.AddLoop(std::move(new_loop), loop->GetParent());
 
   RemoveDeadInstructions();
 }

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

@@ -21,6 +21,7 @@
 #include <vector>
 
 #include <source/spirv_optimizer_options.h>
+#include "code_sink.h"
 #include "source/opt/build_module.h"
 #include "source/opt/log.h"
 #include "source/opt/pass_manager.h"
@@ -181,7 +182,8 @@ Optimizer& Optimizer::RegisterPerformancePasses() {
       .RegisterPass(CreateRedundancyEliminationPass())
       .RegisterPass(CreateDeadBranchElimPass())
       .RegisterPass(CreateBlockMergePass())
-      .RegisterPass(CreateSimplificationPass());
+      .RegisterPass(CreateSimplificationPass())
+      .RegisterPass(CreateCodeSinkingPass());
   // Currently exposing driver bugs resulting in crashes (#946)
   // .RegisterPass(CreateCommonUniformElimPass())
 }
@@ -439,6 +441,8 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag) {
     }
   } else if (pass_name == "ccp") {
     RegisterPass(CreateCCPPass());
+  } else if (pass_name == "code-sink") {
+    RegisterPass(CreateCodeSinkingPass());
   } else if (pass_name == "O") {
     RegisterPerformancePasses();
   } else if (pass_name == "Os") {
@@ -790,4 +794,9 @@ Optimizer::PassToken CreateInstBindlessCheckPass(uint32_t desc_set,
       MakeUnique<opt::InstBindlessCheckPass>(desc_set, shader_id));
 }
 
+Optimizer::PassToken CreateCodeSinkingPass() {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::CodeSinkingPass>());
+}
+
 }  // namespace spvtools

+ 1 - 0
3rdparty/spirv-tools/source/opt/passes.h

@@ -21,6 +21,7 @@
 #include "source/opt/block_merge_pass.h"
 #include "source/opt/ccp_pass.h"
 #include "source/opt/cfg_cleanup_pass.h"
+#include "source/opt/code_sink.h"
 #include "source/opt/combine_access_chains.h"
 #include "source/opt/common_uniform_elim_pass.h"
 #include "source/opt/compact_ids_pass.h"

+ 2 - 12
3rdparty/spirv-tools/source/opt/scalar_replacement_pass.cpp

@@ -638,8 +638,8 @@ bool ScalarReplacementPass::CheckUses(const Instruction* inst,
           switch (user->opcode()) {
             case SpvOpAccessChain:
             case SpvOpInBoundsAccessChain:
-              if (index == 2u) {
-                uint32_t id = user->GetSingleWordOperand(3u);
+              if (index == 2u && user->NumInOperands() > 1) {
+                uint32_t id = user->GetSingleWordInOperand(1u);
                 const Instruction* opInst = get_def_use_mgr()->GetDef(id);
                 if (!IsCompileTimeConstantInst(opInst->opcode())) {
                   ok = false;
@@ -783,16 +783,6 @@ ScalarReplacementPass::GetUsedComponents(Instruction* inst) {
           return false;
         }
       }
-      case SpvOpCopyObject: {
-        // Follow the copy to see which components are used.
-        auto t = GetUsedComponents(use);
-        if (!t) {
-          result.reset(nullptr);
-          return false;
-        }
-        result->insert(t->begin(), t->end());
-        return true;
-      }
       default:
         // We do not know what is happening.  Have to assume the worst.
         result.reset(nullptr);

+ 8 - 2
3rdparty/spirv-tools/source/opt/vector_dce.cpp

@@ -66,7 +66,8 @@ void VectorDCE::FindLiveComponents(Function* function,
 
     switch (current_inst->opcode()) {
       case SpvOpCompositeExtract:
-        MarkExtractUseAsLive(current_inst, live_components, &work_list);
+        MarkExtractUseAsLive(current_inst, current_item.components,
+                             live_components, &work_list);
         break;
       case SpvOpCompositeInsert:
         MarkInsertUsesAsLive(current_item, live_components, &work_list);
@@ -92,6 +93,7 @@ void VectorDCE::FindLiveComponents(Function* function,
 }
 
 void VectorDCE::MarkExtractUseAsLive(const Instruction* current_inst,
+                                     const utils::BitVector& live_elements,
                                      LiveComponentMap* live_components,
                                      std::vector<WorkListItem>* work_list) {
   analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
@@ -102,7 +104,11 @@ void VectorDCE::MarkExtractUseAsLive(const Instruction* current_inst,
   if (HasVectorOrScalarResult(operand_inst)) {
     WorkListItem new_item;
     new_item.instruction = operand_inst;
-    new_item.components.Set(current_inst->GetSingleWordInOperand(1));
+    if (current_inst->NumInOperands() < 2) {
+      new_item.components = live_elements;
+    } else {
+      new_item.components.Set(current_inst->GetSingleWordInOperand(1));
+    }
     AddItemToWorkListIfNeeded(new_item, live_components, work_list);
   }
 }

+ 1 - 0
3rdparty/spirv-tools/source/opt/vector_dce.h

@@ -129,6 +129,7 @@ class VectorDCE : public MemPass {
   // live. If anything becomes live they are added to |work_list| and
   // |live_components| is updated accordingly.
   void MarkExtractUseAsLive(const Instruction* current_inst,
+                            const utils::BitVector& live_elements,
                             LiveComponentMap* live_components,
                             std::vector<WorkListItem>* work_list);
 

+ 81 - 47
3rdparty/spirv-tools/source/val/validate_memory.cpp

@@ -276,6 +276,16 @@ uint32_t GetMakeVisibleScope(const Instruction* inst, uint32_t mask) {
   return scope_id;
 }
 
+bool DoesStructContainRTA(const ValidationState_t& _, const Instruction* inst) {
+  for (size_t member_index = 1; member_index < inst->operands().size();
+       ++member_index) {
+    const auto member_id = inst->GetOperandAs<uint32_t>(member_index);
+    const auto member_type = _.FindDef(member_id);
+    if (member_type->opcode() == SpvOpTypeRuntimeArray) return true;
+  }
+  return false;
+}
+
 spv_result_t CheckMemoryAccess(ValidationState_t& _, const Instruction* inst,
                                uint32_t index) {
   SpvStorageClass dst_sc, src_sc;
@@ -283,9 +293,9 @@ spv_result_t CheckMemoryAccess(ValidationState_t& _, const Instruction* inst,
   if (inst->operands().size() <= index) {
     if (src_sc == SpvStorageClassPhysicalStorageBufferEXT ||
         dst_sc == SpvStorageClassPhysicalStorageBufferEXT) {
-      return _.diag(SPV_ERROR_INVALID_ID, inst) << "Memory accesses with "
-                                                   "PhysicalStorageBufferEXT "
-                                                   "must use Aligned.";
+      return _.diag(SPV_ERROR_INVALID_ID, inst)
+             << "Memory accesses with PhysicalStorageBufferEXT must use "
+                "Aligned.";
     }
     return SPV_SUCCESS;
   }
@@ -318,7 +328,7 @@ spv_result_t CheckMemoryAccess(ValidationState_t& _, const Instruction* inst,
     if (!(mask & SpvMemoryAccessNonPrivatePointerKHRMask)) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
              << "NonPrivatePointerKHR must be specified if "
-                "MakePointerVisibleKHR is specified.";
+             << "MakePointerVisibleKHR is specified.";
     }
 
     // Check the associated scope for MakeVisibleKHR.
@@ -335,8 +345,8 @@ spv_result_t CheckMemoryAccess(ValidationState_t& _, const Instruction* inst,
         dst_sc != SpvStorageClassPhysicalStorageBufferEXT) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
              << "NonPrivatePointerKHR requires a pointer in Uniform, "
-                "Workgroup, CrossWorkgroup, Generic, Image or StorageBuffer "
-                "storage classes.";
+             << "Workgroup, CrossWorkgroup, Generic, Image or StorageBuffer "
+             << "storage classes.";
     }
     if (src_sc != SpvStorageClassMax && src_sc != SpvStorageClassUniform &&
         src_sc != SpvStorageClassWorkgroup &&
@@ -346,17 +356,17 @@ spv_result_t CheckMemoryAccess(ValidationState_t& _, const Instruction* inst,
         src_sc != SpvStorageClassPhysicalStorageBufferEXT) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
              << "NonPrivatePointerKHR requires a pointer in Uniform, "
-                "Workgroup, CrossWorkgroup, Generic, Image or StorageBuffer "
-                "storage classes.";
+             << "Workgroup, CrossWorkgroup, Generic, Image or StorageBuffer "
+             << "storage classes.";
     }
   }
 
   if (!(mask & SpvMemoryAccessAlignedMask)) {
     if (src_sc == SpvStorageClassPhysicalStorageBufferEXT ||
         dst_sc == SpvStorageClassPhysicalStorageBufferEXT) {
-      return _.diag(SPV_ERROR_INVALID_ID, inst) << "Memory accesses with "
-                                                   "PhysicalStorageBufferEXT "
-                                                   "must use Aligned.";
+      return _.diag(SPV_ERROR_INVALID_ID, inst)
+             << "Memory accesses with PhysicalStorageBufferEXT must use "
+                "Aligned.";
     }
   }
 
@@ -442,15 +452,15 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
     // VariablePointersStorageBuffer is implied by VariablePointers.
     if (pointee->opcode() == SpvOpTypePointer) {
       if (!_.HasCapability(SpvCapabilityVariablePointersStorageBuffer)) {
-        return _.diag(SPV_ERROR_INVALID_ID, inst) << "In Logical addressing, "
-                                                     "variables may not "
-                                                     "allocate a pointer type";
+        return _.diag(SPV_ERROR_INVALID_ID, inst)
+               << "In Logical addressing, variables may not allocate a pointer "
+               << "type";
       } else if (storage_class != SpvStorageClassFunction &&
                  storage_class != SpvStorageClassPrivate) {
         return _.diag(SPV_ERROR_INVALID_ID, inst)
                << "In Logical addressing with variable pointers, variables "
-                  "that allocate pointers must be in Function or Private "
-                  "storage classes";
+               << "that allocate pointers must be in Function or Private "
+               << "storage classes";
       }
     }
   }
@@ -493,10 +503,9 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
                << "' has illegal type.\n"
                << "From Vulkan spec, section 14.5.2:\n"
                << "Variables identified with the Uniform storage class are "
-                  "used "
-               << "to access transparent buffer backed resources. Such "
-                  "variables "
-               << "must be typed as OpTypeStruct, or an array of this type";
+               << "used to access transparent buffer backed resources. Such "
+               << "variables must be typed as OpTypeStruct, or an array of "
+               << "this type";
       }
     }
   }
@@ -564,13 +573,13 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
         return _.diag(SPV_ERROR_INVALID_ID, inst)
                << "OpVariable " << inst->id()
                << ": expected AliasedPointerEXT or RestrictPointerEXT for "
-                  "PhysicalStorageBufferEXT pointer.";
+               << "PhysicalStorageBufferEXT pointer.";
       }
       if (foundAliased && foundRestrict) {
         return _.diag(SPV_ERROR_INVALID_ID, inst)
                << "OpVariable " << inst->id()
                << ": can't specify both AliasedPointerEXT and "
-                  "RestrictPointerEXT for PhysicalStorageBufferEXT pointer.";
+               << "RestrictPointerEXT for PhysicalStorageBufferEXT pointer.";
       }
     }
   }
@@ -588,9 +597,9 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
         return _.diag(SPV_ERROR_INVALID_ID, inst)
                << "OpVariable, <id> '" << _.getIdName(inst->id())
                << "', is attempting to create memory for an illegal type, "
-                  "OpTypeRuntimeArray.\nFor Vulkan OpTypeRuntimeArray can only "
-                  "appear as the final member of an OpTypeStruct, thus cannot "
-                  "be instantiated via OpVariable";
+               << "OpTypeRuntimeArray.\nFor Vulkan OpTypeRuntimeArray can only "
+               << "appear as the final member of an OpTypeStruct, thus cannot "
+               << "be instantiated via OpVariable";
       } else {
         // A bare variable OpTypeRuntimeArray is allowed in this context, but
         // still need to check the storage class.
@@ -599,8 +608,8 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
             storage_class != SpvStorageClassUniformConstant) {
           return _.diag(SPV_ERROR_INVALID_ID, inst)
                  << "For Vulkan with RuntimeDescriptorArrayEXT, a variable "
-                    "containing OpTypeRuntimeArray must have storage class of "
-                    "StorageBuffer, Uniform, or UniformConstant.";
+                 << "containing OpTypeRuntimeArray must have storage class of "
+                 << "StorageBuffer, Uniform, or UniformConstant.";
         }
       }
     }
@@ -610,39 +619,64 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
     // with Block, or it must be in the Uniform storage class and be decorated
     // as BufferBlock.
     if (value_type && value_type->opcode() == SpvOpTypeStruct) {
-      bool contains_RTA = false;
-      for (size_t member_type_index = 1;
-           member_type_index < value_type->operands().size();
-           ++member_type_index) {
-        const auto member_type_id =
-            value_type->GetOperandAs<uint32_t>(member_type_index);
-        const auto member_type = _.FindDef(member_type_id);
-        if (member_type->opcode() == SpvOpTypeRuntimeArray) {
-          contains_RTA = true;
-          break;
-        }
-      }
-
-      if (contains_RTA) {
+      if (DoesStructContainRTA(_, value_type)) {
         if (storage_class == SpvStorageClassStorageBuffer) {
           if (!_.HasDecoration(value_id, SpvDecorationBlock)) {
             return _.diag(SPV_ERROR_INVALID_ID, inst)
                    << "For Vulkan, an OpTypeStruct variable containing an "
-                      "OpTypeRuntimeArray must be decorated with Block if it "
-                      "has storage class StorageBuffer.";
+                   << "OpTypeRuntimeArray must be decorated with Block if it "
+                   << "has storage class StorageBuffer.";
           }
         } else if (storage_class == SpvStorageClassUniform) {
           if (!_.HasDecoration(value_id, SpvDecorationBufferBlock)) {
             return _.diag(SPV_ERROR_INVALID_ID, inst)
                    << "For Vulkan, an OpTypeStruct variable containing an "
-                      "OpTypeRuntimeArray must be decorated with BufferBlock "
-                      "if it has storage class Uniform.";
+                   << "OpTypeRuntimeArray must be decorated with BufferBlock "
+                   << "if it has storage class Uniform.";
           }
         } else {
           return _.diag(SPV_ERROR_INVALID_ID, inst)
                  << "For Vulkan, OpTypeStruct variables containing "
-                    "OpTypeRuntimeArray must have storage class of "
-                    "StorageBuffer or Uniform.";
+                 << "OpTypeRuntimeArray must have storage class of "
+                 << "StorageBuffer or Uniform.";
+        }
+      }
+    }
+  }
+
+  // WebGPU specific validation rules for OpTypeRuntimeArray
+  if (spvIsWebGPUEnv(_.context()->target_env)) {
+    const auto type_index = 2;
+    const auto value_id = result_type->GetOperandAs<uint32_t>(type_index);
+    auto value_type = _.FindDef(value_id);
+    // OpTypeRuntimeArray should only ever be in an OpTypeStruct,
+    // so should never appear as a bare variable.
+    if (value_type && value_type->opcode() == SpvOpTypeRuntimeArray) {
+      return _.diag(SPV_ERROR_INVALID_ID, inst)
+             << "OpVariable, <id> '" << _.getIdName(inst->id())
+             << "', is attempting to create memory for an illegal type, "
+             << "OpTypeRuntimeArray.\nFor WebGPU OpTypeRuntimeArray can only "
+             << "appear as the final member of an OpTypeStruct, thus cannot "
+             << "be instantiated via OpVariable";
+    }
+
+    // If an OpStruct has an OpTypeRuntimeArray somewhere within it, then it
+    // must have the storage class StorageBuffer and be decorated
+    // with Block.
+    if (value_type && value_type->opcode() == SpvOpTypeStruct) {
+      if (DoesStructContainRTA(_, value_type)) {
+        if (storage_class == SpvStorageClassStorageBuffer) {
+          if (!_.HasDecoration(value_id, SpvDecorationBlock)) {
+            return _.diag(SPV_ERROR_INVALID_ID, inst)
+                   << "For WebGPU, an OpTypeStruct variable containing an "
+                   << "OpTypeRuntimeArray must be decorated with Block if it "
+                   << "has storage class StorageBuffer.";
+          }
+        } else {
+          return _.diag(SPV_ERROR_INVALID_ID, inst)
+                 << "For WebGPU, OpTypeStruct variables containing "
+                 << "OpTypeRuntimeArray must have storage class of "
+                 << "StorageBuffer";
         }
       }
     }

+ 24 - 15
3rdparty/spirv-tools/source/val/validate_scopes.cpp

@@ -37,9 +37,9 @@ spv_result_t ValidateExecutionScope(ValidationState_t& _,
 
   if (!is_const_int32) {
     if (_.HasCapability(SpvCapabilityShader)) {
-      return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Scope ids must be "
-                                                     "OpConstant when Shader "
-                                                     "capability is present";
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "Scope ids must be OpConstant when Shader capability is "
+             << "present";
     }
     return SPV_SUCCESS;
   }
@@ -54,7 +54,7 @@ spv_result_t ValidateExecutionScope(ValidationState_t& _,
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
                << spvOpcodeString(opcode)
                << ": in Vulkan environment Execution scope is limited to "
-                  "Subgroup";
+               << "Subgroup";
       }
     }
 
@@ -86,7 +86,7 @@ spv_result_t ValidateExecutionScope(ValidationState_t& _,
       return _.diag(SPV_ERROR_INVALID_DATA, inst)
              << spvOpcodeString(opcode)
              << ": in Vulkan environment Execution Scope is limited to "
-                "Workgroup and Subgroup";
+             << "Workgroup and Subgroup";
     }
   }
 
@@ -97,7 +97,7 @@ spv_result_t ValidateExecutionScope(ValidationState_t& _,
       return _.diag(SPV_ERROR_INVALID_DATA, inst)
              << spvOpcodeString(opcode)
              << ": in WebGPU environment Execution Scope is limited to "
-                "Workgroup and Subgroup";
+             << "Workgroup and Subgroup";
     }
   }
 
@@ -131,9 +131,9 @@ spv_result_t ValidateMemoryScope(ValidationState_t& _, const Instruction* inst,
 
   if (!is_const_int32) {
     if (_.HasCapability(SpvCapabilityShader)) {
-      return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Scope ids must be "
-                                                     "OpConstant when Shader "
-                                                     "capability is present";
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "Scope ids must be OpConstant when Shader capability is "
+             << "present";
     }
     return SPV_SUCCESS;
   }
@@ -145,7 +145,7 @@ spv_result_t ValidateMemoryScope(ValidationState_t& _, const Instruction* inst,
       return _.diag(SPV_ERROR_INVALID_DATA, inst)
              << spvOpcodeString(opcode)
              << ": Memory Scope QueueFamilyKHR requires capability "
-                "VulkanMemoryModelKHR";
+             << "VulkanMemoryModelKHR";
     }
   }
 
@@ -154,7 +154,7 @@ spv_result_t ValidateMemoryScope(ValidationState_t& _, const Instruction* inst,
       !_.HasCapability(SpvCapabilityVulkanMemoryModelDeviceScopeKHR)) {
     return _.diag(SPV_ERROR_INVALID_DATA, inst)
            << "Use of device scope with VulkanKHR memory model requires the "
-              "VulkanMemoryModelDeviceScopeKHR capability";
+           << "VulkanMemoryModelDeviceScopeKHR capability";
   }
 
   // Vulkan Specific rules
@@ -171,8 +171,7 @@ spv_result_t ValidateMemoryScope(ValidationState_t& _, const Instruction* inst,
       return _.diag(SPV_ERROR_INVALID_DATA, inst)
              << spvOpcodeString(opcode)
              << ": in Vulkan 1.0 environment Memory Scope is limited to "
-                "Device, "
-                "Workgroup and Invocation";
+             << "Device, Workgroup and Invocation";
     }
     // Vulkan 1.1 specifc rules
     if (_.context()->target_env == SPV_ENV_VULKAN_1_1 &&
@@ -181,8 +180,18 @@ spv_result_t ValidateMemoryScope(ValidationState_t& _, const Instruction* inst,
       return _.diag(SPV_ERROR_INVALID_DATA, inst)
              << spvOpcodeString(opcode)
              << ": in Vulkan 1.1 environment Memory Scope is limited to "
-                "Device, "
-                "Workgroup and Invocation";
+             << "Device, Workgroup and Invocation";
+    }
+  }
+
+  // WebGPU specific rules
+  if (spvIsWebGPUEnv(_.context()->target_env)) {
+    if (value != SpvScopeWorkgroup && value != SpvScopeSubgroup &&
+        value != SpvScopeQueueFamilyKHR) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << spvOpcodeString(opcode)
+             << ": in WebGPU environment Memory Scope is limited to "
+             << "Workgroup, Subgroup and QueuFamilyKHR";
     }
   }
 

+ 26 - 17
3rdparty/spirv-tools/source/val/validate_type.cpp

@@ -107,11 +107,14 @@ spv_result_t ValidateTypeArray(ValidationState_t& _, const Instruction* inst) {
            << "' is a void type.";
   }
 
-  if (spvIsVulkanEnv(_.context()->target_env) &&
+  if ((spvIsVulkanEnv(_.context()->target_env) ||
+       spvIsWebGPUEnv(_.context()->target_env)) &&
       element_type->opcode() == SpvOpTypeRuntimeArray) {
+    const char* env_text =
+        spvIsVulkanEnv(_.context()->target_env) ? "Vulkan" : "WebGPU";
     return _.diag(SPV_ERROR_INVALID_ID, inst)
            << "OpTypeArray Element Type <id> '" << _.getIdName(element_type_id)
-           << "' is not valid in Vulkan environment.";
+           << "' is not valid in " << env_text << " environment.";
   }
 
   const auto length_index = 2;
@@ -169,12 +172,15 @@ spv_result_t ValidateTypeRuntimeArray(ValidationState_t& _,
            << _.getIdName(element_id) << "' is a void type.";
   }
 
-  if (spvIsVulkanEnv(_.context()->target_env) &&
+  if ((spvIsVulkanEnv(_.context()->target_env) ||
+       spvIsWebGPUEnv(_.context()->target_env)) &&
       element_type->opcode() == SpvOpTypeRuntimeArray) {
+    const char* env_text =
+        spvIsVulkanEnv(_.context()->target_env) ? "Vulkan" : "WebGPU";
     return _.diag(SPV_ERROR_INVALID_ID, inst)
            << "OpTypeRuntimeArray Element Type <id> '"
-           << _.getIdName(element_id)
-           << "' is not valid in Vulkan environment.";
+           << _.getIdName(element_id) << "' is not valid in " << env_text
+           << " environment.";
   }
 
   return SPV_SUCCESS;
@@ -200,11 +206,11 @@ spv_result_t ValidateTypeStruct(ValidationState_t& _, const Instruction* inst) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
              << "Structure <id> " << _.getIdName(member_type_id)
              << " contains members with BuiltIn decoration. Therefore this "
-                "structure may not be contained as a member of another "
-                "structure "
-                "type. Structure <id> "
-             << _.getIdName(struct_id) << " contains structure <id> "
-             << _.getIdName(member_type_id) << ".";
+             << "structure may not be contained as a member of another "
+             << "structure "
+             << "type. Structure <id> " << _.getIdName(struct_id)
+             << " contains structure <id> " << _.getIdName(member_type_id)
+             << ".";
     }
     if (_.IsForwardPointer(member_type_id)) {
       // If we're dealing with a forward pointer:
@@ -223,14 +229,17 @@ spv_result_t ValidateTypeStruct(ValidationState_t& _, const Instruction* inst) {
       }
     }
 
-    if (spvIsVulkanEnv(_.context()->target_env) &&
+    if ((spvIsVulkanEnv(_.context()->target_env) ||
+         spvIsWebGPUEnv(_.context()->target_env)) &&
         member_type->opcode() == SpvOpTypeRuntimeArray) {
       const bool is_last_member =
           member_type_index == inst->operands().size() - 1;
       if (!is_last_member) {
+        const char* env_text =
+            spvIsVulkanEnv(_.context()->target_env) ? "Vulkan" : "WebGPU";
         return _.diag(SPV_ERROR_INVALID_ID, inst)
-               << "In Vulkan, OpTypeRuntimeArray must only be used for the "
-                  "last member of an OpTypeStruct";
+               << "In " << env_text << ", OpTypeRuntimeArray must only be used "
+               << "for the last member of an OpTypeStruct";
       }
     }
   }
@@ -247,9 +256,9 @@ spv_result_t ValidateTypeStruct(ValidationState_t& _, const Instruction* inst) {
   if (num_builtin_members > 0 && num_builtin_members != num_struct_members) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
            << "When BuiltIn decoration is applied to a structure-type member, "
-              "all members of that structure type must also be decorated with "
-              "BuiltIn (No allowed mixing of built-in variables and "
-              "non-built-in variables within a single structure). Structure id "
+           << "all members of that structure type must also be decorated with "
+           << "BuiltIn (No allowed mixing of built-in variables and "
+           << "non-built-in variables within a single structure). Structure id "
            << struct_id << " does not meet this requirement.";
   }
   if (num_builtin_members > 0) {
@@ -332,7 +341,7 @@ spv_result_t ValidateTypeForwardPointer(ValidationState_t& _,
       pointer_type_inst->GetOperandAs<uint32_t>(1)) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
            << "Storage class in OpTypeForwardPointer does not match the "
-              "pointer definition.";
+           << "pointer definition.";
   }
 
   return SPV_SUCCESS;

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

@@ -21,6 +21,7 @@ add_spvtools_unittest(TARGET opt
        block_merge_test.cpp
        ccp_test.cpp
        cfg_cleanup_test.cpp
+       code_sink_test.cpp
        combine_access_chains_test.cpp
        common_uniform_elim_test.cpp
        compact_ids_test.cpp

+ 533 - 0
3rdparty/spirv-tools/test/opt/code_sink_test.cpp

@@ -0,0 +1,533 @@
+// 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 <string>
+
+#include "gmock/gmock.h"
+#include "test/opt/assembly_builder.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using CodeSinkTest = PassTest<::testing::Test>;
+
+TEST_F(CodeSinkTest, MoveToNextBlock) {
+  const std::string text = R"(
+;CHECK: OpFunction
+;CHECK: OpLabel
+;CHECK: OpLabel
+;CHECK: [[ac:%\w+]] = OpAccessChain
+;CHECK: [[ld:%\w+]] = OpLoad %uint [[ac]]
+;CHECK: OpCopyObject %uint [[ld]]
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+       %void = OpTypeVoid
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_4 = OpConstant %uint 4
+%_arr_uint_uint_4 = OpTypeArray %uint %uint_4
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+          %9 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+         %10 = OpTypeFunction %void
+          %1 = OpFunction %void None %10
+         %11 = OpLabel
+         %12 = OpAccessChain %_ptr_Uniform_uint %9 %uint_0
+         %13 = OpLoad %uint %12
+               OpBranch %14
+         %14 = OpLabel
+         %15 = OpCopyObject %uint %13
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<CodeSinkingPass>(text, true);
+}
+
+TEST_F(CodeSinkTest, MovePastSelection) {
+  const std::string text = R"(
+;CHECK: OpFunction
+;CHECK: OpLabel
+;CHECK: OpSelectionMerge [[merge_bb:%\w+]]
+;CHECK: [[merge_bb]] = OpLabel
+;CHECK: [[ac:%\w+]] = OpAccessChain
+;CHECK: [[ld:%\w+]] = OpLoad %uint [[ac]]
+;CHECK: OpCopyObject %uint [[ld]]
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+       %void = OpTypeVoid
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_4 = OpConstant %uint 4
+%_arr_uint_uint_4 = OpTypeArray %uint %uint_4
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+         %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+         %12 = OpTypeFunction %void
+          %1 = OpFunction %void None %12
+         %13 = OpLabel
+         %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+         %15 = OpLoad %uint %14
+               OpSelectionMerge %16 None
+               OpBranchConditional %true %17 %16
+         %17 = OpLabel
+               OpBranch %16
+         %16 = OpLabel
+         %18 = OpCopyObject %uint %15
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<CodeSinkingPass>(text, true);
+}
+
+TEST_F(CodeSinkTest, MoveIntoSelection) {
+  const std::string text = R"(
+;CHECK: OpFunction
+;CHECK: OpLabel
+;CHECK: OpSelectionMerge [[merge_bb:%\w+]]
+;CHECK-NEXT: OpBranchConditional %true [[bb:%\w+]] [[merge_bb]]
+;CHECK: [[bb]] = OpLabel
+;CHECK-NEXT: [[ac:%\w+]] = OpAccessChain
+;CHECK-NEXT: [[ld:%\w+]] = OpLoad %uint [[ac]]
+;CHECK-NEXT: OpCopyObject %uint [[ld]]
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+       %void = OpTypeVoid
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_4 = OpConstant %uint 4
+%_arr_uint_uint_4 = OpTypeArray %uint %uint_4
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+         %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+         %12 = OpTypeFunction %void
+          %1 = OpFunction %void None %12
+         %13 = OpLabel
+         %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+         %15 = OpLoad %uint %14
+               OpSelectionMerge %16 None
+               OpBranchConditional %true %17 %16
+         %17 = OpLabel
+         %18 = OpCopyObject %uint %15
+               OpBranch %16
+         %16 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<CodeSinkingPass>(text, true);
+}
+
+TEST_F(CodeSinkTest, LeaveBeforeSelection) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+       %void = OpTypeVoid
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_4 = OpConstant %uint 4
+%_arr_uint_uint_4 = OpTypeArray %uint %uint_4
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+         %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+         %12 = OpTypeFunction %void
+          %1 = OpFunction %void None %12
+         %13 = OpLabel
+         %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+         %15 = OpLoad %uint %14
+               OpSelectionMerge %16 None
+               OpBranchConditional %true %17 %20
+         %20 = OpLabel
+               OpBranch %16
+         %17 = OpLabel
+         %18 = OpCopyObject %uint %15
+               OpBranch %16
+         %16 = OpLabel
+         %19 = OpCopyObject %uint %15
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<CodeSinkingPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(CodeSinkTest, LeaveAloneUseInSameBlock) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+       %void = OpTypeVoid
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_4 = OpConstant %uint 4
+%_arr_uint_uint_4 = OpTypeArray %uint %uint_4
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+         %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+         %12 = OpTypeFunction %void
+          %1 = OpFunction %void None %12
+         %13 = OpLabel
+         %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+         %15 = OpLoad %uint %14
+       %cond = OpIEqual %bool %15 %uint_0
+               OpSelectionMerge %16 None
+               OpBranchConditional %cond %17 %16
+         %17 = OpLabel
+               OpBranch %16
+         %16 = OpLabel
+         %19 = OpCopyObject %uint %15
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<CodeSinkingPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(CodeSinkTest, DontMoveIntoLoop) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+       %void = OpTypeVoid
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_4 = OpConstant %uint 4
+%_arr_uint_uint_4 = OpTypeArray %uint %uint_4
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+         %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+         %12 = OpTypeFunction %void
+          %1 = OpFunction %void None %12
+         %13 = OpLabel
+         %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+         %15 = OpLoad %uint %14
+               OpBranch %17
+         %17 = OpLabel
+               OpLoopMerge %merge %cont None
+               OpBranch %cont
+       %cont = OpLabel
+       %cond = OpIEqual %bool %15 %uint_0
+               OpBranchConditional %cond %merge %17
+      %merge = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<CodeSinkingPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(CodeSinkTest, DontMoveIntoLoop2) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+       %void = OpTypeVoid
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_4 = OpConstant %uint 4
+%_arr_uint_uint_4 = OpTypeArray %uint %uint_4
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+         %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+         %12 = OpTypeFunction %void
+          %1 = OpFunction %void None %12
+         %13 = OpLabel
+         %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+         %15 = OpLoad %uint %14
+               OpSelectionMerge %16 None
+               OpBranchConditional %true %17 %16
+         %17 = OpLabel
+               OpLoopMerge %merge %cont None
+               OpBranch %cont
+       %cont = OpLabel
+       %cond = OpIEqual %bool %15 %uint_0
+               OpBranchConditional %cond %merge %17
+      %merge = OpLabel
+               OpBranch %16
+         %16 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<CodeSinkingPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(CodeSinkTest, DontMoveSelectionUsedInBothSides) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+       %void = OpTypeVoid
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_4 = OpConstant %uint 4
+%_arr_uint_uint_4 = OpTypeArray %uint %uint_4
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+         %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+         %12 = OpTypeFunction %void
+          %1 = OpFunction %void None %12
+         %13 = OpLabel
+         %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+         %15 = OpLoad %uint %14
+               OpSelectionMerge %16 None
+               OpBranchConditional %true %17 %20
+         %20 = OpLabel
+         %19 = OpCopyObject %uint %15
+               OpBranch %16
+         %17 = OpLabel
+         %18 = OpCopyObject %uint %15
+               OpBranch %16
+         %16 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<CodeSinkingPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(CodeSinkTest, DontMoveBecauseOfStore) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+       %void = OpTypeVoid
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_4 = OpConstant %uint 4
+%_arr_uint_uint_4 = OpTypeArray %uint %uint_4
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+         %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+         %12 = OpTypeFunction %void
+          %1 = OpFunction %void None %12
+         %13 = OpLabel
+         %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+         %15 = OpLoad %uint %14
+               OpStore %14 %15
+               OpSelectionMerge %16 None
+               OpBranchConditional %true %17 %20
+         %20 = OpLabel
+               OpBranch %16
+         %17 = OpLabel
+         %18 = OpCopyObject %uint %15
+               OpBranch %16
+         %16 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<CodeSinkingPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(CodeSinkTest, MoveReadOnlyLoadWithSync) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+       %void = OpTypeVoid
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_4 = OpConstant %uint 4
+%mem_semantics = OpConstant %uint 0x42 ; Uniform memeory arquire
+%_arr_uint_uint_4 = OpTypeArray %uint %uint_4
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+         %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+         %12 = OpTypeFunction %void
+          %1 = OpFunction %void None %12
+         %13 = OpLabel
+         %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+         %15 = OpLoad %uint %14
+               OpMemoryBarrier %uint_4 %mem_semantics
+               OpSelectionMerge %16 None
+               OpBranchConditional %true %17 %20
+         %20 = OpLabel
+               OpBranch %16
+         %17 = OpLabel
+         %18 = OpCopyObject %uint %15
+               OpBranch %16
+         %16 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<CodeSinkingPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(Pass::Status::SuccessWithChange, std::get<1>(result));
+}
+
+TEST_F(CodeSinkTest, DontMoveBecauseOfSync) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+               OpDecorate %_arr_uint_uint_4 BufferBlock
+               OpMemberDecorate %_arr_uint_uint_4 0 Offset 0
+       %void = OpTypeVoid
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_4 = OpConstant %uint 4
+%mem_semantics = OpConstant %uint 0x42 ; Uniform memeory arquire
+%_arr_uint_uint_4 = OpTypeStruct %uint
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+         %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+         %12 = OpTypeFunction %void
+          %1 = OpFunction %void None %12
+         %13 = OpLabel
+         %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+         %15 = OpLoad %uint %14
+               OpMemoryBarrier %uint_4 %mem_semantics
+               OpSelectionMerge %16 None
+               OpBranchConditional %true %17 %20
+         %20 = OpLabel
+               OpBranch %16
+         %17 = OpLabel
+         %18 = OpCopyObject %uint %15
+               OpBranch %16
+         %16 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<CodeSinkingPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(CodeSinkTest, DontMoveBecauseOfAtomicWithSync) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+               OpDecorate %_arr_uint_uint_4 BufferBlock
+               OpMemberDecorate %_arr_uint_uint_4 0 Offset 0
+       %void = OpTypeVoid
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_4 = OpConstant %uint 4
+%mem_semantics = OpConstant %uint 0x42 ; Uniform memeory arquire
+%_arr_uint_uint_4 = OpTypeStruct %uint
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+         %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+         %12 = OpTypeFunction %void
+          %1 = OpFunction %void None %12
+         %13 = OpLabel
+         %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+         %15 = OpLoad %uint %14
+         %al = OpAtomicLoad %uint %14 %uint_4 %mem_semantics
+               OpSelectionMerge %16 None
+               OpBranchConditional %true %17 %20
+         %20 = OpLabel
+               OpBranch %16
+         %17 = OpLabel
+         %18 = OpCopyObject %uint %15
+               OpBranch %16
+         %16 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<CodeSinkingPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(CodeSinkTest, MoveWithAtomicWithoutSync) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+               OpDecorate %_arr_uint_uint_4 BufferBlock
+               OpMemberDecorate %_arr_uint_uint_4 0 Offset 0
+       %void = OpTypeVoid
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_4 = OpConstant %uint 4
+%_arr_uint_uint_4 = OpTypeStruct %uint
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+         %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+         %12 = OpTypeFunction %void
+          %1 = OpFunction %void None %12
+         %13 = OpLabel
+         %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+         %15 = OpLoad %uint %14
+         %al = OpAtomicLoad %uint %14 %uint_4 %uint_0
+               OpSelectionMerge %16 None
+               OpBranchConditional %true %17 %20
+         %20 = OpLabel
+               OpBranch %16
+         %17 = OpLabel
+         %18 = OpCopyObject %uint %15
+               OpBranch %16
+         %16 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<CodeSinkingPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(Pass::Status::SuccessWithChange, std::get<1>(result));
+}
+
+}  // namespace
+}  // namespace opt
+}  // namespace spvtools

+ 9 - 1
3rdparty/spirv-tools/test/opt/fold_test.cpp

@@ -538,7 +538,15 @@ INSTANTIATE_TEST_CASE_P(TestCase, IntegerInstructionFoldingTest,
           "%2 = OpShiftLeftLogical %int %int_2 %uint_32\n" +
           "OpReturn\n" +
           "OpFunctionEnd",
-      2, 0)
+      2, 0),
+  // Test case 29: fold -INT_MIN
+  InstructionFoldingCase<uint32_t>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%2 = OpSNegate %int %int_min\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, std::numeric_limits<int32_t>::min())
 ));
 // clang-format on
 

+ 5 - 6
3rdparty/spirv-tools/test/opt/pass_manager_test.cpp

@@ -107,8 +107,7 @@ class DuplicateInstPass : public Pass {
  public:
   const char* name() const override { return "DuplicateInst"; }
   Status Process() override {
-    auto inst =
-        MakeUnique<Instruction>(*(--context()->debug1_end())->Clone(context()));
+    auto inst = MakeUnique<Instruction>(*(--context()->debug1_end()));
     context()->AddDebug1Inst(std::move(inst));
     return Status::SuccessWithChange;
   }
@@ -121,21 +120,21 @@ TEST_F(PassManagerTest, Run) {
 
   AddPass<AppendOpNopPass>();
   AddPass<AppendOpNopPass>();
-  RunAndCheck(text.c_str(), (text + "OpNop\nOpNop\n").c_str());
+  RunAndCheck(text, text + "OpNop\nOpNop\n");
 
   RenewPassManger();
   AddPass<AppendOpNopPass>();
   AddPass<DuplicateInstPass>();
-  RunAndCheck(text.c_str(), (text + "OpNop\nOpNop\n").c_str());
+  RunAndCheck(text, text + "OpNop\nOpNop\n");
 
   RenewPassManger();
   AddPass<DuplicateInstPass>();
   AddPass<AppendOpNopPass>();
-  RunAndCheck(text.c_str(), (text + "OpSource ESSL 310\nOpNop\n").c_str());
+  RunAndCheck(text, text + "OpSource ESSL 310\nOpNop\n");
 
   RenewPassManger();
   AddPass<AppendMultipleOpNopPass>(3);
-  RunAndCheck(text.c_str(), (text + "OpNop\nOpNop\nOpNop\n").c_str());
+  RunAndCheck(text, text + "OpNop\nOpNop\nOpNop\n");
 }
 
 // A pass that appends an OpTypeVoid instruction that uses a given id.

+ 27 - 0
3rdparty/spirv-tools/test/opt/scalar_replacement_test.cpp

@@ -1593,6 +1593,33 @@ TEST_F(ScalarReplacementTest, AmbigousPointer) {
   SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
 }
 
+// Test that scalar replacement does not crash when there is an OpAccessChain
+// with no index.  If we choose to handle this case in the future, then the
+// result can change.
+TEST_F(ScalarReplacementTest, TestAccessChainWithNoIndexes) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %1 "main"
+               OpExecutionMode %1 OriginLowerLeft
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+  %_struct_5 = OpTypeStruct %float
+%_ptr_Function__struct_5 = OpTypePointer Function %_struct_5
+          %1 = OpFunction %void None %3
+          %7 = OpLabel
+          %8 = OpVariable %_ptr_Function__struct_5 Function
+          %9 = OpAccessChain %_ptr_Function__struct_5 %8
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  auto result =
+      SinglePassRunAndDisassemble<ScalarReplacementPass>(text, true, false);
+  EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools

+ 36 - 0
3rdparty/spirv-tools/test/opt/vector_dce_test.cpp

@@ -1190,6 +1190,42 @@ TEST_F(VectorDCETest, InsertWithNoIndices) {
   SinglePassRunAndMatch<VectorDCE>(text, true);
 }
 
+TEST_F(VectorDCETest, ExtractWithNoIndices) {
+  const std::string text = R"(
+; CHECK: OpLoad %float
+; CHECK: [[ld:%\w+]] = OpLoad %v4float
+; CHECK: [[ex1:%\w+]] = OpCompositeExtract %v4float [[ld]]
+; CHECK: [[ex2:%\w+]] = OpCompositeExtract %float [[ex1]] 1
+; CHECK: OpStore {{%\w+}} [[ex2]]
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %1 "PSMain" %2 %14 %3
+               OpExecutionMode %1 OriginUpperLeft
+       %void = OpTypeVoid
+          %5 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%_ptr_Input_float = OpTypePointer Input %float
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Output_float = OpTypePointer Output %float
+          %2 = OpVariable %_ptr_Input_v4float Input
+          %14 = OpVariable %_ptr_Input_float Input
+          %3 = OpVariable %_ptr_Output_float Output
+          %1 = OpFunction %void None %5
+         %10 = OpLabel
+         %13 = OpLoad %float %14
+         %11 = OpLoad %v4float %2
+         %12 = OpCompositeInsert %v4float %13 %11 0
+         %20 = OpCompositeExtract %v4float %12
+         %21 = OpCompositeExtract %float %20 1
+               OpStore %3 %21
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<VectorDCE>(text, true);
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools

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

@@ -340,7 +340,7 @@ TEST_F(ValidateAtomics, AtomicLoadVulkanInt64) {
 
 TEST_F(ValidateAtomics, AtomicLoadWebGPUShaderSuccess) {
   const std::string body = R"(
-%val1 = OpAtomicLoad %u32 %u32_var %device %relaxed
+%val1 = OpAtomicLoad %u32 %u32_var %queuefamily %relaxed
 %val2 = OpAtomicLoad %u32 %u32_var %workgroup %acquire
 )";
 
@@ -544,7 +544,7 @@ OpAtomicStore %u32_var %device %sequentially_consistent %u32_1
 
 TEST_F(ValidateAtomics, AtomicStoreWebGPUSuccess) {
   const std::string body = R"(
-OpAtomicStore %u32_var %device %release %u32_1
+OpAtomicStore %u32_var %queuefamily %release %u32_1
 )";
 
   CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
@@ -553,7 +553,7 @@ OpAtomicStore %u32_var %device %release %u32_1
 
 TEST_F(ValidateAtomics, AtomicStoreWebGPUSequentiallyConsistent) {
   const std::string body = R"(
-OpAtomicStore %u32_var %device %sequentially_consistent %u32_1
+OpAtomicStore %u32_var %queuefamily %sequentially_consistent %u32_1
 )";
 
   CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
@@ -564,7 +564,8 @@ OpAtomicStore %u32_var %device %sequentially_consistent %u32_1
           "WebGPU spec disallows any bit masks in Memory Semantics that are "
           "not Acquire, Release, AcquireRelease, UniformMemory, "
           "WorkgroupMemory, ImageMemory, OutputMemoryKHR, MakeAvailableKHR, or "
-          "MakeVisibleKHR\n  OpAtomicStore %29 %uint_1_0 %uint_16 %uint_1\n"));
+          "MakeVisibleKHR\n"
+          "  OpAtomicStore %29 %uint_5 %uint_16 %uint_1\n"));
 }
 
 TEST_F(ValidateAtomics, AtomicStoreWrongPointerType) {
@@ -1858,6 +1859,75 @@ OpExtension "SPV_KHR_vulkan_memory_model"
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
 }
 
+TEST_F(ValidateAtomics, WebGPUCrossDeviceMemoryScopeBad) {
+  const std::string body = R"(
+%val1 = OpAtomicLoad %u32 %u32_var %cross_device %relaxed
+)";
+
+  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("AtomicLoad: in WebGPU environment Memory Scope is limited to "
+                "Workgroup, Subgroup and QueuFamilyKHR\n"
+                "  %34 = OpAtomicLoad %uint %29 %uint_0_0 %uint_0_1\n"));
+}
+
+TEST_F(ValidateAtomics, WebGPUDeviceMemoryScopeBad) {
+  const std::string body = R"(
+%val1 = OpAtomicLoad %u32 %u32_var %device %relaxed
+)";
+
+  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("AtomicLoad: in WebGPU environment Memory Scope is limited to "
+                "Workgroup, Subgroup and QueuFamilyKHR\n"
+                "  %34 = OpAtomicLoad %uint %29 %uint_1_0 %uint_0_1\n"));
+}
+
+TEST_F(ValidateAtomics, WebGPUWorkgroupMemoryScopeGood) {
+  const std::string body = R"(
+%val1 = OpAtomicLoad %u32 %u32_var %workgroup %relaxed
+)";
+
+  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
+}
+
+TEST_F(ValidateAtomics, WebGPUSubgroupMemoryScopeGood) {
+  const std::string body = R"(
+%val1 = OpAtomicLoad %u32 %u32_var %subgroup %relaxed
+)";
+
+  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
+}
+
+TEST_F(ValidateAtomics, WebGPUInvocationMemoryScopeBad) {
+  const std::string body = R"(
+%val1 = OpAtomicLoad %u32 %u32_var %invocation %relaxed
+)";
+
+  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("AtomicLoad: in WebGPU environment Memory Scope is limited to "
+                "Workgroup, Subgroup and QueuFamilyKHR\n"
+                "  %34 = OpAtomicLoad %uint %29 %uint_4 %uint_0_1\n"));
+}
+
+TEST_F(ValidateAtomics, WebGPUQueueFamilyMemoryScopeGood) {
+  const std::string body = R"(
+%val1 = OpAtomicLoad %u32 %u32_var %queuefamily %relaxed
+)";
+
+  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools

+ 62 - 0
3rdparty/spirv-tools/test/val/val_data_test.cpp

@@ -872,6 +872,68 @@ TEST_F(ValidateData, vulkan_RTA_not_at_end_of_struct) {
                         "OpTypeStruct %_runtimearr_uint %uint\n"));
 }
 
+TEST_F(ValidateData, webgpu_RTA_array_at_end_of_struct) {
+  std::string str = R"(
+              OpCapability Shader
+              OpCapability VulkanMemoryModelKHR
+              OpExtension "SPV_KHR_vulkan_memory_model"
+              OpMemoryModel Logical VulkanKHR
+              OpEntryPoint Fragment %func "func"
+              OpExecutionMode %func OriginUpperLeft
+              OpDecorate %array_t ArrayStride 4
+              OpMemberDecorate %struct_t 0 Offset 0
+              OpMemberDecorate %struct_t 1 Offset 4
+              OpDecorate %struct_t Block
+     %uint_t = OpTypeInt 32 0
+   %array_t = OpTypeRuntimeArray %uint_t
+  %struct_t = OpTypeStruct %uint_t %array_t
+%struct_ptr = OpTypePointer StorageBuffer %struct_t
+         %2 = OpVariable %struct_ptr StorageBuffer
+      %void = OpTypeVoid
+    %func_t = OpTypeFunction %void
+      %func = OpFunction %void None %func_t
+         %1 = OpLabel
+              OpReturn
+              OpFunctionEnd
+)";
+
+  CompileSuccessfully(str.c_str(), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
+}
+
+TEST_F(ValidateData, webgpu_RTA_not_at_end_of_struct) {
+  std::string str = R"(
+              OpCapability Shader
+              OpCapability VulkanMemoryModelKHR
+              OpExtension "SPV_KHR_vulkan_memory_model"
+              OpMemoryModel Logical VulkanKHR
+              OpEntryPoint Fragment %func "func"
+              OpExecutionMode %func OriginUpperLeft
+              OpDecorate %array_t ArrayStride 4
+              OpMemberDecorate %struct_t 0 Offset 0
+              OpMemberDecorate %struct_t 1 Offset 4
+              OpDecorate %struct_t Block
+     %uint_t = OpTypeInt 32 0
+   %array_t = OpTypeRuntimeArray %uint_t
+  %struct_t = OpTypeStruct %array_t %uint_t
+%struct_ptr = OpTypePointer StorageBuffer %struct_t
+         %2 = OpVariable %struct_ptr StorageBuffer
+      %void = OpTypeVoid
+    %func_t = OpTypeFunction %void
+      %func = OpFunction %void None %func_t
+         %1 = OpLabel
+              OpReturn
+              OpFunctionEnd
+)";
+
+  CompileSuccessfully(str.c_str(), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("In WebGPU, OpTypeRuntimeArray must only be used for "
+                        "the last member of an OpTypeStruct\n  %_struct_3 = "
+                        "OpTypeStruct %_runtimearr_uint %uint\n"));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools

+ 216 - 0
3rdparty/spirv-tools/test/val/val_memory_test.cpp

@@ -1806,6 +1806,38 @@ OpFunctionEnd
           "%_ptr_UniformConstant__runtimearr_2 UniformConstant\n"));
 }
 
+TEST_F(ValidateMemory, WebGPURTAOutsideOfStructBad) {
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%sampler_t = OpTypeSampler
+%array_t = OpTypeRuntimeArray %sampler_t
+%array_ptr = OpTypePointer UniformConstant %array_t
+%2 = OpVariable %array_ptr UniformConstant
+%void = OpTypeVoid
+%func_t = OpTypeFunction %void
+%func = OpFunction %void None %func_t
+%1 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "OpVariable, <id> '5[%5]', is attempting to create memory for an "
+          "illegal type, OpTypeRuntimeArray.\nFor WebGPU OpTypeRuntimeArray "
+          "can only appear as the final member of an OpTypeStruct, thus cannot "
+          "be instantiated via OpVariable\n  %5 = OpVariable "
+          "%_ptr_UniformConstant__runtimearr_2 UniformConstant\n"));
+}
+
 TEST_F(ValidateMemory, VulkanRTAOutsideOfStructWithRuntimeDescriptorArrayGood) {
   std::string spirv = R"(
 OpCapability Shader
@@ -1890,6 +1922,34 @@ OpFunctionEnd
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1));
 }
 
+TEST_F(ValidateMemory, WebGPURTAInsideStorageBufferStructGood) {
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+OpDecorate %array_t ArrayStride 4
+OpMemberDecorate %struct_t 0 Offset 0
+OpDecorate %struct_t Block
+%uint_t = OpTypeInt 32 0
+%array_t = OpTypeRuntimeArray %uint_t
+%struct_t = OpTypeStruct %array_t
+%struct_ptr = OpTypePointer StorageBuffer %struct_t
+%2 = OpVariable %struct_ptr StorageBuffer
+%void = OpTypeVoid
+%func_t = OpTypeFunction %void
+%func = OpFunction %void None %func_t
+%1 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
+}
+
 TEST_F(ValidateMemory, VulkanRTAInsideWrongStorageClassStructBad) {
   std::string spirv = R"(
 OpCapability Shader
@@ -1919,6 +1979,36 @@ OpFunctionEnd
           "OpVariable %_ptr_Workgroup__struct_4 Workgroup\n"));
 }
 
+TEST_F(ValidateMemory, WebGPURTAInsideWrongStorageClassStructBad) {
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%uint_t = OpTypeInt 32 0
+%array_t = OpTypeRuntimeArray %uint_t
+%struct_t = OpTypeStruct %array_t
+%struct_ptr = OpTypePointer Workgroup %struct_t
+%2 = OpVariable %struct_ptr Workgroup
+%void = OpTypeVoid
+%func_t = OpTypeFunction %void
+%func = OpFunction %void None %func_t
+%1 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("For WebGPU, OpTypeStruct variables containing "
+                "OpTypeRuntimeArray must have storage class of StorageBuffer\n "
+                " %6 = OpVariable %_ptr_Workgroup__struct_4 Workgroup\n"));
+}
+
 TEST_F(ValidateMemory, VulkanRTAInsideStorageBufferStructWithoutBlockBad) {
   std::string spirv = R"(
 OpCapability Shader
@@ -1947,6 +2037,36 @@ OpFunctionEnd
                         "%_ptr_StorageBuffer__struct_4 StorageBuffer\n"));
 }
 
+TEST_F(ValidateMemory, WebGPURTAInsideStorageBufferStructWithoutBlockBad) {
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%uint_t = OpTypeInt 32 0
+%array_t = OpTypeRuntimeArray %uint_t
+%struct_t = OpTypeStruct %array_t
+%struct_ptr = OpTypePointer StorageBuffer %struct_t
+%2 = OpVariable %struct_ptr StorageBuffer
+%void = OpTypeVoid
+%func_t = OpTypeFunction %void
+%func = OpFunction %void None %func_t
+%1 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("For WebGPU, an OpTypeStruct variable containing an "
+                        "OpTypeRuntimeArray must be decorated with Block if it "
+                        "has storage class StorageBuffer.\n  %6 = OpVariable "
+                        "%_ptr_StorageBuffer__struct_4 StorageBuffer\n"));
+}
+
 TEST_F(ValidateMemory, VulkanRTAInsideUniformStructGood) {
   std::string spirv = R"(
 OpCapability Shader
@@ -1973,6 +2093,39 @@ OpFunctionEnd
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1));
 }
 
+TEST_F(ValidateMemory, WebGPURTAInsideUniformStructBad) {
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+OpDecorate %array_t ArrayStride 4
+OpMemberDecorate %struct_t 0 Offset 0
+OpDecorate %struct_t BufferBlock
+%uint_t = OpTypeInt 32 0
+%array_t = OpTypeRuntimeArray %uint_t
+%struct_t = OpTypeStruct %array_t
+%struct_ptr = OpTypePointer Uniform %struct_t
+%2 = OpVariable %struct_ptr Uniform
+%void = OpTypeVoid
+%func_t = OpTypeFunction %void
+%func = OpFunction %void None %func_t
+%1 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("For WebGPU, OpTypeStruct variables containing "
+                "OpTypeRuntimeArray must have storage class of StorageBuffer\n "
+                " %6 = OpVariable %_ptr_Uniform__struct_3 Uniform\n"));
+}
+
 TEST_F(ValidateMemory, VulkanRTAInsideUniformStructWithoutBufferBlockBad) {
   std::string spirv = R"(
 OpCapability Shader
@@ -2030,6 +2183,37 @@ OpFunctionEnd
           "OpTypeRuntimeArray %_runtimearr_2\n"));
 }
 
+TEST_F(ValidateMemory, WebGPURTAInsideRTABad) {
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%sampler_t = OpTypeSampler
+%inner_array_t = OpTypeRuntimeArray %sampler_t
+%array_t = OpTypeRuntimeArray %inner_array_t
+%array_ptr = OpTypePointer UniformConstant %array_t
+%2 = OpVariable %array_ptr UniformConstant
+%void = OpTypeVoid
+%func_t = OpTypeFunction %void
+%func = OpFunction %void None %func_t
+%1 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "OpTypeRuntimeArray Element Type <id> '3[%_runtimearr_2]' is not "
+          "valid in WebGPU environment.\n  %_runtimearr__runtimearr_2 = "
+          "OpTypeRuntimeArray %_runtimearr_2\n"));
+}
+
 TEST_F(ValidateMemory, VulkanRTAInsideRTAWithRuntimeDescriptorArrayBad) {
   std::string spirv = R"(
 OpCapability RuntimeDescriptorArrayEXT
@@ -2190,6 +2374,38 @@ OpFunctionEnd
                 "OpTypeArray %_runtimearr_4 %uint_1\n"));
 }
 
+TEST_F(ValidateMemory, WebGPURTAInsideArrayBad) {
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%uint_t = OpTypeInt 32 0
+%dim = OpConstant %uint_t 1
+%sampler_t = OpTypeSampler
+%inner_array_t = OpTypeRuntimeArray %sampler_t
+%array_t = OpTypeArray %inner_array_t %dim
+%array_ptr = OpTypePointer UniformConstant %array_t
+%2 = OpVariable %array_ptr UniformConstant
+%void = OpTypeVoid
+%func_t = OpTypeFunction %void
+%func = OpFunction %void None %func_t
+%1 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("OpTypeArray Element Type <id> '5[%_runtimearr_4]' is not "
+                "valid in WebGPU environment.\n  %_arr__runtimearr_4_uint_1 = "
+                "OpTypeArray %_runtimearr_4 %uint_1\n"));
+}
+
 TEST_F(ValidateMemory, VulkanRTAInsideArrayWithRuntimeDescriptorArrayBad) {
   std::string spirv = R"(
 OpCapability RuntimeDescriptorArrayEXT

+ 5 - 3
3rdparty/spirv-tools/tools/opt/opt.cpp

@@ -478,8 +478,11 @@ OptStatus ParseOconfigFlag(const char* prog_name, const char* opt_flag,
     new_argv[i] = flags[i].c_str();
   }
 
-  return ParseFlags(static_cast<int>(flags.size()), new_argv, optimizer,
-                    in_file, out_file, validator_options, optimizer_options);
+  auto ret_val =
+      ParseFlags(static_cast<int>(flags.size()), new_argv, optimizer, in_file,
+                 out_file, validator_options, optimizer_options);
+  delete[] new_argv;
+  return ret_val;
 }
 
 // Canonicalize the flag in |argv[argi]| of the form '--pass arg' into
@@ -660,7 +663,6 @@ int main(int argc, const char** argv) {
 
   spv_target_env target_env = kDefaultEnvironment;
 
-
   spvtools::Optimizer optimizer(target_env);
   optimizer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);