Browse Source

Updated spirv-tools.

Бранимир Караџић 6 years ago
parent
commit
90af4a2b9c

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

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

+ 3 - 1
3rdparty/spirv-tools/BUILD.gn

@@ -305,7 +305,6 @@ source_set("spvtools_headers") {
 
 
 static_library("spvtools") {
 static_library("spvtools") {
   deps = [
   deps = [
-    ":spvtools_core_enums_unified1",
     ":spvtools_core_tables_unified1",
     ":spvtools_core_tables_unified1",
     ":spvtools_generators_inc",
     ":spvtools_generators_inc",
     ":spvtools_glsl_tables_glsl1-0",
     ":spvtools_glsl_tables_glsl1-0",
@@ -376,6 +375,7 @@ static_library("spvtools") {
   ]
   ]
 
 
   public_deps = [
   public_deps = [
+    ":spvtools_core_enums_unified1",
     ":spvtools_headers",
     ":spvtools_headers",
   ]
   ]
 
 
@@ -498,6 +498,8 @@ static_library("spvtools_opt") {
     "source/opt/eliminate_dead_functions_pass.h",
     "source/opt/eliminate_dead_functions_pass.h",
     "source/opt/eliminate_dead_functions_util.cpp",
     "source/opt/eliminate_dead_functions_util.cpp",
     "source/opt/eliminate_dead_functions_util.h",
     "source/opt/eliminate_dead_functions_util.h",
+    "source/opt/eliminate_dead_members_pass.cpp",
+    "source/opt/eliminate_dead_members_pass.h",
     "source/opt/feature_manager.cpp",
     "source/opt/feature_manager.cpp",
     "source/opt/feature_manager.h",
     "source/opt/feature_manager.h",
     "source/opt/flatten_decoration_pass.cpp",
     "source/opt/flatten_decoration_pass.cpp",

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

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

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

@@ -226,6 +226,11 @@ Optimizer::PassToken CreateStripReflectInfoPass();
 // functions are not needed because they will never be called.
 // functions are not needed because they will never be called.
 Optimizer::PassToken CreateEliminateDeadFunctionsPass();
 Optimizer::PassToken CreateEliminateDeadFunctionsPass();
 
 
+// Creates an eliminate-dead-members pass.
+// An eliminate-dead-members pass will remove all unused members of structures.
+// This will not affect the data layout of the remaining members.
+Optimizer::PassToken CreateEliminateDeadMembersPass();
+
 // Creates a set-spec-constant-default-value pass from a mapping from spec-ids
 // Creates a set-spec-constant-default-value pass from a mapping from spec-ids
 // to the default values in the form of string.
 // to the default values in the form of string.
 // A set-spec-constant-default-value pass sets the default values for the
 // A set-spec-constant-default-value pass sets the default values for the

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

@@ -38,6 +38,7 @@ set(SPIRV_TOOLS_OPT_SOURCES
   eliminate_dead_constant_pass.h
   eliminate_dead_constant_pass.h
   eliminate_dead_functions_pass.h
   eliminate_dead_functions_pass.h
   eliminate_dead_functions_util.h
   eliminate_dead_functions_util.h
+  eliminate_dead_members_pass.h
   feature_manager.h
   feature_manager.h
   flatten_decoration_pass.h
   flatten_decoration_pass.h
   fold.h
   fold.h
@@ -133,6 +134,7 @@ set(SPIRV_TOOLS_OPT_SOURCES
   eliminate_dead_constant_pass.cpp
   eliminate_dead_constant_pass.cpp
   eliminate_dead_functions_pass.cpp
   eliminate_dead_functions_pass.cpp
   eliminate_dead_functions_util.cpp
   eliminate_dead_functions_util.cpp
+  eliminate_dead_members_pass.cpp
   feature_manager.cpp
   feature_manager.cpp
   flatten_decoration_pass.cpp
   flatten_decoration_pass.cpp
   fold.cpp
   fold.cpp

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

@@ -86,6 +86,11 @@ bool CanMergeWithSuccessor(IRContext* context, BasicBlock* block) {
     return false;
     return false;
   }
   }
 
 
+  // Don't bother trying to merge unreachable blocks.
+  if (auto dominators = context->GetDominatorAnalysis(block->GetParent())) {
+    if (!dominators->IsReachable(block)) return false;
+  }
+
   Instruction* merge_inst = block->GetMergeInst();
   Instruction* merge_inst = block->GetMergeInst();
   const bool pred_is_header = IsHeader(block);
   const bool pred_is_header = IsHeader(block);
   if (pred_is_header && lab_id != merge_inst->GetSingleWordInOperand(0u)) {
   if (pred_is_header && lab_id != merge_inst->GetSingleWordInOperand(0u)) {

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

@@ -0,0 +1,624 @@
+// 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 "source/opt/eliminate_dead_members_pass.h"
+
+#include "ir_builder.h"
+#include "source/opt/ir_context.h"
+
+namespace {
+const uint32_t kRemovedMember = 0xFFFFFFFF;
+}
+
+namespace spvtools {
+namespace opt {
+
+Pass::Status EliminateDeadMembersPass::Process() {
+  if (!context()->get_feature_mgr()->HasCapability(SpvCapabilityShader))
+    return Status::SuccessWithoutChange;
+
+  FindLiveMembers();
+  if (RemoveDeadMembers()) {
+    return Status::SuccessWithChange;
+  }
+  return Status::SuccessWithoutChange;
+}
+
+void EliminateDeadMembersPass::FindLiveMembers() {
+  // Until we have implemented the rewritting of OpSpecConsantOp instructions,
+  // we have to mark them as fully used just to be safe.
+  for (auto& inst : get_module()->types_values()) {
+    if (inst.opcode() != SpvOpSpecConstantOp) {
+      continue;
+    }
+    MarkTypeAsFullyUsed(inst.type_id());
+  }
+
+  for (const Function& func : *get_module()) {
+    FindLiveMembers(func);
+  }
+}
+
+void EliminateDeadMembersPass::FindLiveMembers(const Function& function) {
+  function.ForEachInst(
+      [this](const Instruction* inst) { FindLiveMembers(inst); });
+}
+
+void EliminateDeadMembersPass::FindLiveMembers(const Instruction* inst) {
+  switch (inst->opcode()) {
+    case SpvOpStore:
+      MarkMembersAsLiveForStore(inst);
+      break;
+    case SpvOpCopyMemory:
+    case SpvOpCopyMemorySized:
+      MarkMembersAsLiveForCopyMemory(inst);
+      break;
+    case SpvOpCompositeExtract:
+      MarkMembersAsLiveForExtract(inst);
+      break;
+    case SpvOpAccessChain:
+    case SpvOpInBoundsAccessChain:
+    case SpvOpPtrAccessChain:
+    case SpvOpInBoundsPtrAccessChain:
+      MarkMembersAsLiveForAccessChain(inst);
+      break;
+    case SpvOpReturnValue:
+      // This should be an issue only if we are returning from the entry point.
+      // However, for now I will keep it more conservative because functions are
+      // often inlined leaving only the entry points.
+      MarkOperandTypeAsFullyUsed(inst, 0);
+      break;
+    case SpvOpArrayLength:
+      MarkMembersAsLiveForArrayLength(inst);
+      break;
+    case SpvOpLoad:
+    case SpvOpCompositeInsert:
+    case SpvOpCompositeConstruct:
+      break;
+    default:
+      // This path is here for safety.  All instructions that can reference
+      // structs in a function body should be handled above.  However, this will
+      // keep the pass valid, but not optimal, as new instructions get added
+      // or if something was missed.
+      MarkStructOperandsAsFullyUsed(inst);
+      break;
+  }
+}
+
+void EliminateDeadMembersPass::MarkMembersAsLiveForStore(
+    const Instruction* inst) {
+  // We should only have to mark the members as live if the store is to
+  // memory that is read outside of the shader.  Other passes can remove all
+  // store to memory that is not visible outside of the shader, so we do not
+  // complicate the code for now.
+  assert(inst->opcode() == SpvOpStore);
+  uint32_t object_id = inst->GetSingleWordInOperand(1);
+  Instruction* object_inst = context()->get_def_use_mgr()->GetDef(object_id);
+  uint32_t object_type_id = object_inst->type_id();
+  MarkTypeAsFullyUsed(object_type_id);
+}
+
+void EliminateDeadMembersPass::MarkTypeAsFullyUsed(uint32_t type_id) {
+  Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
+  assert(type_inst != nullptr);
+  if (type_inst->opcode() != SpvOpTypeStruct) {
+    return;
+  }
+
+  // Mark every member of the current struct as used.
+  for (uint32_t i = 0; i < type_inst->NumInOperands(); ++i) {
+    used_members_[type_id].insert(i);
+  }
+
+  // Mark any sub struct as fully used.
+  for (uint32_t i = 0; i < type_inst->NumInOperands(); ++i) {
+    MarkTypeAsFullyUsed(type_inst->GetSingleWordInOperand(i));
+  }
+}
+
+void EliminateDeadMembersPass::MarkMembersAsLiveForCopyMemory(
+    const Instruction* inst) {
+  uint32_t target_id = inst->GetSingleWordInOperand(0);
+  Instruction* target_inst = get_def_use_mgr()->GetDef(target_id);
+  uint32_t pointer_type_id = target_inst->type_id();
+  Instruction* pointer_type_inst = get_def_use_mgr()->GetDef(pointer_type_id);
+  uint32_t type_id = pointer_type_inst->GetSingleWordInOperand(1);
+  MarkTypeAsFullyUsed(type_id);
+}
+
+void EliminateDeadMembersPass::MarkMembersAsLiveForExtract(
+    const Instruction* inst) {
+  assert(inst->opcode() == SpvOpCompositeExtract);
+
+  uint32_t composite_id = inst->GetSingleWordInOperand(0);
+  Instruction* composite_inst = get_def_use_mgr()->GetDef(composite_id);
+  uint32_t type_id = composite_inst->type_id();
+
+  for (uint32_t i = 1; i < inst->NumInOperands(); ++i) {
+    Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
+    uint32_t member_idx = inst->GetSingleWordInOperand(i);
+    switch (type_inst->opcode()) {
+      case SpvOpTypeStruct:
+        used_members_[type_id].insert(member_idx);
+        type_id = type_inst->GetSingleWordInOperand(member_idx);
+        break;
+      case SpvOpTypeArray:
+      case SpvOpTypeRuntimeArray:
+      case SpvOpTypeVector:
+      case SpvOpTypeMatrix:
+        type_id = type_inst->GetSingleWordInOperand(0);
+        break;
+      default:
+        assert(false);
+    }
+  }
+}
+
+void EliminateDeadMembersPass::MarkMembersAsLiveForAccessChain(
+    const Instruction* inst) {
+  assert(inst->opcode() == SpvOpAccessChain ||
+         inst->opcode() == SpvOpInBoundsAccessChain ||
+         inst->opcode() == SpvOpPtrAccessChain ||
+         inst->opcode() == SpvOpInBoundsPtrAccessChain);
+
+  uint32_t pointer_id = inst->GetSingleWordInOperand(0);
+  Instruction* pointer_inst = get_def_use_mgr()->GetDef(pointer_id);
+  uint32_t pointer_type_id = pointer_inst->type_id();
+  Instruction* pointer_type_inst = get_def_use_mgr()->GetDef(pointer_type_id);
+  uint32_t type_id = pointer_type_inst->GetSingleWordInOperand(1);
+
+  analysis::ConstantManager* const_mgr = context()->get_constant_mgr();
+
+  // For a pointer access chain, we need to skip the |element| index.  It is not
+  // a reference to the member of a struct, and it does not change the type.
+  uint32_t i = (inst->opcode() == SpvOpAccessChain ||
+                        inst->opcode() == SpvOpInBoundsAccessChain
+                    ? 1
+                    : 2);
+  for (; i < inst->NumInOperands(); ++i) {
+    Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
+    switch (type_inst->opcode()) {
+      case SpvOpTypeStruct: {
+        const analysis::IntConstant* member_idx =
+            const_mgr->FindDeclaredConstant(inst->GetSingleWordInOperand(i))
+                ->AsIntConstant();
+        assert(member_idx);
+        if (member_idx->type()->AsInteger()->width() == 32) {
+          used_members_[type_id].insert(member_idx->GetU32());
+          type_id = type_inst->GetSingleWordInOperand(member_idx->GetU32());
+        } else {
+          used_members_[type_id].insert(
+              static_cast<uint32_t>(member_idx->GetU64()));
+          type_id = type_inst->GetSingleWordInOperand(
+              static_cast<uint32_t>(member_idx->GetU64()));
+        }
+      } break;
+      case SpvOpTypeArray:
+      case SpvOpTypeRuntimeArray:
+      case SpvOpTypeVector:
+      case SpvOpTypeMatrix:
+        type_id = type_inst->GetSingleWordInOperand(0);
+        break;
+      default:
+        assert(false);
+    }
+  }
+}
+
+void EliminateDeadMembersPass::MarkOperandTypeAsFullyUsed(
+    const Instruction* inst, uint32_t in_idx) {
+  uint32_t op_id = inst->GetSingleWordInOperand(in_idx);
+  Instruction* op_inst = get_def_use_mgr()->GetDef(op_id);
+  MarkTypeAsFullyUsed(op_inst->type_id());
+}
+
+void EliminateDeadMembersPass::MarkMembersAsLiveForArrayLength(
+    const Instruction* inst) {
+  assert(inst->opcode() == SpvOpArrayLength);
+  uint32_t object_id = inst->GetSingleWordInOperand(0);
+  Instruction* object_inst = get_def_use_mgr()->GetDef(object_id);
+  uint32_t pointer_type_id = object_inst->type_id();
+  Instruction* pointer_type_inst = get_def_use_mgr()->GetDef(pointer_type_id);
+  uint32_t type_id = pointer_type_inst->GetSingleWordInOperand(1);
+  used_members_[type_id].insert(inst->GetSingleWordInOperand(1));
+}
+
+bool EliminateDeadMembersPass::RemoveDeadMembers() {
+  bool modified = false;
+
+  // First update all of the OpTypeStruct instructions.
+  get_module()->ForEachInst([&modified, this](Instruction* inst) {
+    switch (inst->opcode()) {
+      case SpvOpTypeStruct:
+        modified |= UpdateOpTypeStruct(inst);
+        break;
+      default:
+        break;
+    }
+  });
+
+  // Now update all of the instructions that reference the OpTypeStructs.
+  get_module()->ForEachInst([&modified, this](Instruction* inst) {
+    switch (inst->opcode()) {
+      case SpvOpMemberName:
+        modified |= UpdateOpMemberNameOrDecorate(inst);
+        break;
+      case SpvOpMemberDecorate:
+        modified |= UpdateOpMemberNameOrDecorate(inst);
+        break;
+      case SpvOpGroupMemberDecorate:
+        modified |= UpdateOpGroupMemberDecorate(inst);
+        break;
+      case SpvOpSpecConstantComposite:
+      case SpvOpConstantComposite:
+      case SpvOpCompositeConstruct:
+        modified |= UpdateConstantComposite(inst);
+        break;
+      case SpvOpAccessChain:
+      case SpvOpInBoundsAccessChain:
+      case SpvOpPtrAccessChain:
+      case SpvOpInBoundsPtrAccessChain:
+        modified |= UpdateAccessChain(inst);
+        break;
+      case SpvOpCompositeExtract:
+        modified |= UpdateCompsiteExtract(inst);
+        break;
+      case SpvOpCompositeInsert:
+        modified |= UpdateCompositeInsert(inst);
+        break;
+      case SpvOpArrayLength:
+        modified |= UpdateOpArrayLength(inst);
+        break;
+      case SpvOpSpecConstantOp:
+        assert(false && "Not yet implemented.");
+        // with OpCompositeExtract, OpCompositeInsert
+        // For kernels: OpAccessChain, OpInBoundsAccessChain, OpPtrAccessChain,
+        // OpInBoundsPtrAccessChain
+        break;
+      default:
+        break;
+    }
+  });
+  return modified;
+}
+
+bool EliminateDeadMembersPass::UpdateOpTypeStruct(Instruction* inst) {
+  assert(inst->opcode() == SpvOpTypeStruct);
+
+  const auto& live_members = used_members_[inst->result_id()];
+  if (live_members.size() == inst->NumInOperands()) {
+    return false;
+  }
+
+  Instruction::OperandList new_operands;
+  for (uint32_t idx : live_members) {
+    new_operands.emplace_back(inst->GetInOperand(idx));
+  }
+
+  inst->SetInOperands(std::move(new_operands));
+  context()->UpdateDefUse(inst);
+  return true;
+}
+
+bool EliminateDeadMembersPass::UpdateOpMemberNameOrDecorate(Instruction* inst) {
+  assert(inst->opcode() == SpvOpMemberName ||
+         inst->opcode() == SpvOpMemberDecorate);
+
+  uint32_t type_id = inst->GetSingleWordInOperand(0);
+  auto live_members = used_members_.find(type_id);
+  if (live_members == used_members_.end()) {
+    return false;
+  }
+
+  uint32_t orig_member_idx = inst->GetSingleWordInOperand(1);
+  uint32_t new_member_idx = GetNewMemberIndex(type_id, orig_member_idx);
+
+  if (new_member_idx == kRemovedMember) {
+    context()->KillInst(inst);
+    return true;
+  }
+
+  if (new_member_idx == orig_member_idx) {
+    return false;
+  }
+
+  inst->SetInOperand(1, {new_member_idx});
+  return true;
+}
+
+bool EliminateDeadMembersPass::UpdateOpGroupMemberDecorate(Instruction* inst) {
+  assert(inst->opcode() == SpvOpGroupMemberDecorate);
+
+  bool modified = false;
+
+  Instruction::OperandList new_operands;
+  new_operands.emplace_back(inst->GetInOperand(0));
+  for (uint32_t i = 1; i < inst->NumInOperands(); i += 2) {
+    uint32_t type_id = inst->GetSingleWordInOperand(i);
+    uint32_t member_idx = inst->GetSingleWordInOperand(i + 1);
+    uint32_t new_member_idx = GetNewMemberIndex(type_id, member_idx);
+
+    if (new_member_idx == kRemovedMember) {
+      modified = true;
+      continue;
+    }
+
+    new_operands.emplace_back(inst->GetOperand(i));
+    if (new_member_idx != member_idx) {
+      new_operands.emplace_back(
+          Operand({SPV_OPERAND_TYPE_LITERAL_INTEGER, {new_member_idx}}));
+      modified = true;
+    } else {
+      new_operands.emplace_back(inst->GetOperand(i + 1));
+    }
+  }
+
+  if (!modified) {
+    return false;
+  }
+
+  if (new_operands.size() == 1) {
+    context()->KillInst(inst);
+    return true;
+  }
+
+  inst->SetInOperands(std::move(new_operands));
+  context()->UpdateDefUse(inst);
+  return true;
+}
+
+bool EliminateDeadMembersPass::UpdateConstantComposite(Instruction* inst) {
+  assert(inst->opcode() == SpvOpConstantComposite ||
+         inst->opcode() == SpvOpCompositeConstruct);
+  uint32_t type_id = inst->type_id();
+
+  bool modified = false;
+  Instruction::OperandList new_operands;
+  for (uint32_t i = 0; i < inst->NumInOperands(); ++i) {
+    uint32_t new_idx = GetNewMemberIndex(type_id, i);
+    if (new_idx == kRemovedMember) {
+      modified = true;
+    } else {
+      new_operands.emplace_back(inst->GetInOperand(i));
+    }
+  }
+  inst->SetInOperands(std::move(new_operands));
+  context()->UpdateDefUse(inst);
+  return modified;
+}
+
+bool EliminateDeadMembersPass::UpdateAccessChain(Instruction* inst) {
+  assert(inst->opcode() == SpvOpAccessChain ||
+         inst->opcode() == SpvOpInBoundsAccessChain ||
+         inst->opcode() == SpvOpPtrAccessChain ||
+         inst->opcode() == SpvOpInBoundsPtrAccessChain);
+
+  uint32_t pointer_id = inst->GetSingleWordInOperand(0);
+  Instruction* pointer_inst = get_def_use_mgr()->GetDef(pointer_id);
+  uint32_t pointer_type_id = pointer_inst->type_id();
+  Instruction* pointer_type_inst = get_def_use_mgr()->GetDef(pointer_type_id);
+  uint32_t type_id = pointer_type_inst->GetSingleWordInOperand(1);
+
+  analysis::ConstantManager* const_mgr = context()->get_constant_mgr();
+  Instruction::OperandList new_operands;
+  bool modified = false;
+  new_operands.emplace_back(inst->GetInOperand(0));
+
+  // For pointer access chains we want to copy the element operand.
+  if (inst->opcode() == SpvOpPtrAccessChain ||
+      inst->opcode() == SpvOpInBoundsPtrAccessChain) {
+    new_operands.emplace_back(inst->GetInOperand(1));
+  }
+
+  for (uint32_t i = static_cast<uint32_t>(new_operands.size());
+       i < inst->NumInOperands(); ++i) {
+    Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
+    switch (type_inst->opcode()) {
+      case SpvOpTypeStruct: {
+        const analysis::IntConstant* member_idx =
+            const_mgr->FindDeclaredConstant(inst->GetSingleWordInOperand(i))
+                ->AsIntConstant();
+        assert(member_idx);
+        uint32_t orig_member_idx;
+        if (member_idx->type()->AsInteger()->width() == 32) {
+          orig_member_idx = member_idx->GetU32();
+        } else {
+          orig_member_idx = static_cast<uint32_t>(member_idx->GetU64());
+        }
+        uint32_t new_member_idx = GetNewMemberIndex(type_id, orig_member_idx);
+        assert(new_member_idx != kRemovedMember);
+        if (orig_member_idx != new_member_idx) {
+          InstructionBuilder ir_builder(
+              context(), inst,
+              IRContext::kAnalysisDefUse |
+                  IRContext::kAnalysisInstrToBlockMapping);
+          uint32_t const_id =
+              ir_builder.GetUintConstant(new_member_idx)->result_id();
+          new_operands.emplace_back(Operand({SPV_OPERAND_TYPE_ID, {const_id}}));
+          modified = true;
+        } else {
+          new_operands.emplace_back(inst->GetInOperand(i));
+        }
+        // The type will have already been rewritten, so use the new member
+        // index.
+        type_id = type_inst->GetSingleWordInOperand(new_member_idx);
+      } break;
+      case SpvOpTypeArray:
+      case SpvOpTypeRuntimeArray:
+      case SpvOpTypeVector:
+      case SpvOpTypeMatrix:
+        new_operands.emplace_back(inst->GetInOperand(i));
+        type_id = type_inst->GetSingleWordInOperand(0);
+        break;
+      default:
+        assert(false);
+        break;
+    }
+  }
+
+  if (!modified) {
+    return false;
+  }
+  inst->SetInOperands(std::move(new_operands));
+  context()->UpdateDefUse(inst);
+  return true;
+}
+
+uint32_t EliminateDeadMembersPass::GetNewMemberIndex(uint32_t type_id,
+                                                     uint32_t member_idx) {
+  auto live_members = used_members_.find(type_id);
+  if (live_members == used_members_.end()) {
+    return member_idx;
+  }
+
+  auto current_member = live_members->second.find(member_idx);
+  if (current_member == live_members->second.end()) {
+    return kRemovedMember;
+  }
+
+  return static_cast<uint32_t>(
+      std::distance(live_members->second.begin(), current_member));
+}
+
+bool EliminateDeadMembersPass::UpdateCompsiteExtract(Instruction* inst) {
+  uint32_t object_id = inst->GetSingleWordInOperand(0);
+  Instruction* object_inst = get_def_use_mgr()->GetDef(object_id);
+  uint32_t type_id = object_inst->type_id();
+
+  Instruction::OperandList new_operands;
+  bool modified = false;
+  new_operands.emplace_back(inst->GetInOperand(0));
+  for (uint32_t i = 1; i < inst->NumInOperands(); ++i) {
+    uint32_t member_idx = inst->GetSingleWordInOperand(i);
+    uint32_t new_member_idx = GetNewMemberIndex(type_id, member_idx);
+    assert(new_member_idx != kRemovedMember);
+    if (member_idx != new_member_idx) {
+      modified = true;
+    }
+    new_operands.emplace_back(
+        Operand({SPV_OPERAND_TYPE_LITERAL_INTEGER, {new_member_idx}}));
+
+    Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
+    switch (type_inst->opcode()) {
+      case SpvOpTypeStruct:
+        assert(i != 1 || (inst->opcode() != SpvOpPtrAccessChain &&
+                          inst->opcode() != SpvOpInBoundsPtrAccessChain));
+        // The type will have already been rewriten, so use the new member
+        // index.
+        type_id = type_inst->GetSingleWordInOperand(new_member_idx);
+        break;
+      case SpvOpTypeArray:
+      case SpvOpTypeRuntimeArray:
+      case SpvOpTypeVector:
+      case SpvOpTypeMatrix:
+        type_id = type_inst->GetSingleWordInOperand(0);
+        break;
+      default:
+        assert(false);
+    }
+  }
+
+  if (!modified) {
+    return false;
+  }
+  inst->SetInOperands(std::move(new_operands));
+  context()->UpdateDefUse(inst);
+  return true;
+}
+
+bool EliminateDeadMembersPass::UpdateCompositeInsert(Instruction* inst) {
+  uint32_t composite_id = inst->GetSingleWordInOperand(1);
+  Instruction* composite_inst = get_def_use_mgr()->GetDef(composite_id);
+  uint32_t type_id = composite_inst->type_id();
+
+  Instruction::OperandList new_operands;
+  bool modified = false;
+  new_operands.emplace_back(inst->GetInOperand(0));
+  new_operands.emplace_back(inst->GetInOperand(1));
+  for (uint32_t i = 2; i < inst->NumInOperands(); ++i) {
+    uint32_t member_idx = inst->GetSingleWordInOperand(i);
+    uint32_t new_member_idx = GetNewMemberIndex(type_id, member_idx);
+    if (new_member_idx == kRemovedMember) {
+      context()->KillInst(inst);
+      return true;
+    }
+
+    if (member_idx != new_member_idx) {
+      modified = true;
+    }
+    new_operands.emplace_back(
+        Operand({SPV_OPERAND_TYPE_LITERAL_INTEGER, {new_member_idx}}));
+
+    Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
+    switch (type_inst->opcode()) {
+      case SpvOpTypeStruct:
+        // The type will have already been rewritten, so use the new member
+        // index.
+        type_id = type_inst->GetSingleWordInOperand(new_member_idx);
+        break;
+      case SpvOpTypeArray:
+      case SpvOpTypeRuntimeArray:
+      case SpvOpTypeVector:
+      case SpvOpTypeMatrix:
+        type_id = type_inst->GetSingleWordInOperand(0);
+        break;
+      default:
+        assert(false);
+    }
+  }
+
+  if (!modified) {
+    return false;
+  }
+  inst->SetInOperands(std::move(new_operands));
+  context()->UpdateDefUse(inst);
+  return true;
+}
+
+bool EliminateDeadMembersPass::UpdateOpArrayLength(Instruction* inst) {
+  uint32_t struct_id = inst->GetSingleWordInOperand(0);
+  Instruction* struct_inst = get_def_use_mgr()->GetDef(struct_id);
+  uint32_t pointer_type_id = struct_inst->type_id();
+  Instruction* pointer_type_inst = get_def_use_mgr()->GetDef(pointer_type_id);
+  uint32_t type_id = pointer_type_inst->GetSingleWordInOperand(1);
+
+  uint32_t member_idx = inst->GetSingleWordInOperand(1);
+  uint32_t new_member_idx = GetNewMemberIndex(type_id, member_idx);
+  assert(new_member_idx != kRemovedMember);
+
+  if (member_idx == new_member_idx) {
+    return false;
+  }
+
+  inst->SetInOperand(1, {new_member_idx});
+  context()->UpdateDefUse(inst);
+  return true;
+}
+
+void EliminateDeadMembersPass::MarkStructOperandsAsFullyUsed(
+    const Instruction* inst) {
+  if (inst->type_id() != 0) {
+    MarkTypeAsFullyUsed(inst->type_id());
+  }
+
+  inst->ForEachInId([this](const uint32_t* id) {
+    Instruction* instruction = get_def_use_mgr()->GetDef(*id);
+    if (instruction->type_id() != 0) {
+      MarkTypeAsFullyUsed(instruction->type_id());
+    }
+  });
+}
+
+}  // namespace opt
+}  // namespace spvtools

+ 145 - 0
3rdparty/spirv-tools/source/opt/eliminate_dead_members_pass.h

@@ -0,0 +1,145 @@
+// 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_MEMBERS_PASS_H_
+#define SOURCE_OPT_ELIMINATE_DEAD_MEMBERS_PASS_H_
+
+#include "source/opt/def_use_manager.h"
+#include "source/opt/function.h"
+#include "source/opt/mem_pass.h"
+#include "source/opt/module.h"
+
+namespace spvtools {
+namespace opt {
+
+// Remove unused members from structures.  The remaining members will remain at
+// the same offset.
+class EliminateDeadMembersPass : public MemPass {
+ public:
+  const char* name() const override { return "eliminate-dead-members"; }
+  Status Process() override;
+
+  IRContext::Analysis GetPreservedAnalyses() override {
+    return IRContext::kAnalysisDefUse |
+           IRContext::kAnalysisInstrToBlockMapping |
+           IRContext::kAnalysisCombinators | IRContext::kAnalysisCFG |
+           IRContext::kAnalysisDominatorAnalysis |
+           IRContext::kAnalysisLoopAnalysis |
+           IRContext::kAnalysisScalarEvolution |
+           IRContext::kAnalysisRegisterPressure |
+           IRContext::kAnalysisValueNumberTable |
+           IRContext::kAnalysisStructuredCFG |
+           IRContext::kAnalysisBuiltinVarId |
+           IRContext::kAnalysisIdToFuncMapping;
+  }
+
+ private:
+  // Populate |used_members_| with the member of structures that are live in the
+  // current context.
+  void FindLiveMembers();
+
+  // Add to |used_members_| the member of structures that are live in
+  // |function|.
+  void FindLiveMembers(const Function& function);
+  // Add to |used_members_| the member of structures that are live in |inst|.
+  void FindLiveMembers(const Instruction* inst);
+
+  // Add to |used_members_| the members that are live in the |OpStore|
+  // instruction |inst|.
+  void MarkMembersAsLiveForStore(const Instruction* inst);
+
+  // Add to |used_members_| the members that are live in the |OpCopyMemory*|
+  // instruction |inst|.
+  void MarkMembersAsLiveForCopyMemory(const Instruction* inst);
+
+  // Add to |used_members_| the members that are live in the
+  // |OpCompositeExtract| instruction |inst|.
+  void MarkMembersAsLiveForExtract(const Instruction* inst);
+
+  // Add to |used_members_| the members that are live in the |Op*AccessChain|
+  // instruction |inst|.
+  void MarkMembersAsLiveForAccessChain(const Instruction* inst);
+
+  // Add the member referenced by the OpArrayLength instruction |inst| to
+  // |uses_members_|.
+  void MarkMembersAsLiveForArrayLength(const Instruction* inst);
+
+  // Remove dead members from structs and updates any instructions that need to
+  // be updated as a consequence.  Return true if something changed.
+  bool RemoveDeadMembers();
+
+  // Update |inst|, which must be an |OpMemberName| or |OpMemberDecorate|
+  // instruction, so it references the correct member after the struct is
+  // updated.  Return true if something changed.
+  bool UpdateOpMemberNameOrDecorate(Instruction* inst);
+
+  // Update |inst|, which must be an |OpGroupMemberDecorate| instruction, so it
+  // references the correct member after the struct is updated.  Return true if
+  // something changed.
+  bool UpdateOpGroupMemberDecorate(Instruction* inst);
+
+  // Update the |OpTypeStruct| instruction |inst| my removing the members that
+  // are not live.  Return true if something changed.
+  bool UpdateOpTypeStruct(Instruction* inst);
+
+  // Update the |OpConstantComposite| instruction |inst| to match the change
+  // made to the type that was being generated.  Return true if something
+  // changed.
+  bool UpdateConstantComposite(Instruction* inst);
+
+  // Update the |Op*AccessChain| instruction |inst| to reference the correct
+  // members. All members referenced in the access chain must be live.  This
+  // function must be called after the |OpTypeStruct| instruction for the type
+  // has been updated.  Return true if something changed.
+  bool UpdateAccessChain(Instruction* inst);
+
+  // Update the |OpCompositeExtract| instruction |inst| to reference the correct
+  // members. All members referenced in the instruction must be live.  This
+  // function must be called after the |OpTypeStruct| instruction for the type
+  // has been updated.  Return true if something changed.
+  bool UpdateCompsiteExtract(Instruction* inst);
+
+  // Update the |OpCompositeInsert| instruction |inst| to reference the correct
+  // members. If the member being inserted is not live, then |inst| is killed.
+  // This function must be called after the |OpTypeStruct| instruction for the
+  // type has been updated.  Return true if something changed.
+  bool UpdateCompositeInsert(Instruction* inst);
+
+  // Update the |OpArrayLength| instruction |inst| to reference the correct
+  // member. The member referenced in the instruction must be live.  Return true
+  // if something changed.
+  bool UpdateOpArrayLength(Instruction* inst);
+
+  // Add all of the members of type |type_id| and members of any subtypes to
+  // |used_members_|.
+  void MarkTypeAsFullyUsed(uint32_t type_id);
+
+  // Add all of the members of the type of the operand |in_idx| in |inst| and
+  // members of any subtypes to |uses_members_|.
+  void MarkOperandTypeAsFullyUsed(const Instruction* inst, uint32_t in_idx);
+
+  // Return the index of the member that use to be the |member_idx|th member of
+  // |type_id|.  If the member has been removed, |kRemovedMember| is returned.
+  uint32_t GetNewMemberIndex(uint32_t type_id, uint32_t member_idx);
+
+  // A map from a type id to a set of indices representing the members of the
+  // type that are used, and must be kept.
+  std::unordered_map<uint32_t, std::set<uint32_t>> used_members_;
+  void MarkStructOperandsAsFullyUsed(const Instruction* inst);
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // SOURCE_OPT_ELIMINATE_DEAD_MEMBERS_PASS_H_

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

@@ -614,8 +614,14 @@ bool InstrumentPass::InstrumentFunction(Function* func, uint32_t stage_idx,
     ++function_idx;
     ++function_idx;
   }
   }
   std::vector<std::unique_ptr<BasicBlock>> new_blks;
   std::vector<std::unique_ptr<BasicBlock>> new_blks;
-  // Start count after function instruction
+  // Start count after function and param instructions
   uint32_t instruction_idx = funcIdx2offset_[function_idx] + 1;
   uint32_t instruction_idx = funcIdx2offset_[function_idx] + 1;
+  func->ForEachParam(
+      [this, &instruction_idx](const Instruction* i) {
+        (void)i;
+        ++instruction_idx;
+      },
+      true);
   // Using block iterators here because of block erasures and insertions.
   // Using block iterators here because of block erasures and insertions.
   for (auto bi = func->begin(); bi != func->end(); ++bi) {
   for (auto bi = func->begin(); bi != func->end(); ++bi) {
     // Count block's label
     // Count block's label
@@ -784,8 +790,14 @@ void InstrumentPass::InitializeInstrument() {
   auto prev_fn = get_module()->begin();
   auto prev_fn = get_module()->begin();
   auto curr_fn = prev_fn;
   auto curr_fn = prev_fn;
   for (++curr_fn; curr_fn != get_module()->end(); ++curr_fn) {
   for (++curr_fn; curr_fn != get_module()->end(); ++curr_fn) {
-    // Count function and end instructions
+    // Count function, end and param instructions
     uint32_t func_size = 2;
     uint32_t func_size = 2;
+    prev_fn->ForEachParam(
+        [this, &func_size](const Instruction* i) {
+          (void)i;
+          ++func_size;
+        },
+        true);
     for (auto& blk : *prev_fn) {
     for (auto& blk : *prev_fn) {
       // Count label
       // Count label
       func_size += 1;
       func_size += 1;
@@ -794,7 +806,7 @@ void InstrumentPass::InitializeInstrument() {
         func_size += static_cast<uint32_t>(inst.dbg_line_insts().size());
         func_size += static_cast<uint32_t>(inst.dbg_line_insts().size());
       }
       }
     }
     }
-    funcIdx2offset_[func_idx] = func_size;
+    funcIdx2offset_[func_idx] = funcIdx2offset_[func_idx - 1] + func_size;
     ++prev_fn;
     ++prev_fn;
     ++func_idx;
     ++func_idx;
   }
   }

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

@@ -332,6 +332,8 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag) {
     RegisterPass(CreateDeadInsertElimPass());
     RegisterPass(CreateDeadInsertElimPass());
   } else if (pass_name == "eliminate-dead-variables") {
   } else if (pass_name == "eliminate-dead-variables") {
     RegisterPass(CreateDeadVariableEliminationPass());
     RegisterPass(CreateDeadVariableEliminationPass());
+  } else if (pass_name == "eliminate-dead-members") {
+    RegisterPass(CreateEliminateDeadMembersPass());
   } else if (pass_name == "fold-spec-const-op-composite") {
   } else if (pass_name == "fold-spec-const-op-composite") {
     RegisterPass(CreateFoldSpecConstantOpAndCompositePass());
     RegisterPass(CreateFoldSpecConstantOpAndCompositePass());
   } else if (pass_name == "loop-unswitch") {
   } else if (pass_name == "loop-unswitch") {
@@ -542,6 +544,11 @@ Optimizer::PassToken CreateEliminateDeadFunctionsPass() {
       MakeUnique<opt::EliminateDeadFunctionsPass>());
       MakeUnique<opt::EliminateDeadFunctionsPass>());
 }
 }
 
 
+Optimizer::PassToken CreateEliminateDeadMembersPass() {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::EliminateDeadMembersPass>());
+}
+
 Optimizer::PassToken CreateSetSpecConstantDefaultValuePass(
 Optimizer::PassToken CreateSetSpecConstantDefaultValuePass(
     const std::unordered_map<uint32_t, std::string>& id_value_map) {
     const std::unordered_map<uint32_t, std::string>& id_value_map) {
   return MakeUnique<Optimizer::PassToken::Impl>(
   return MakeUnique<Optimizer::PassToken::Impl>(

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

@@ -31,6 +31,7 @@
 #include "source/opt/dead_variable_elimination.h"
 #include "source/opt/dead_variable_elimination.h"
 #include "source/opt/eliminate_dead_constant_pass.h"
 #include "source/opt/eliminate_dead_constant_pass.h"
 #include "source/opt/eliminate_dead_functions_pass.h"
 #include "source/opt/eliminate_dead_functions_pass.h"
+#include "source/opt/eliminate_dead_members_pass.h"
 #include "source/opt/flatten_decoration_pass.h"
 #include "source/opt/flatten_decoration_pass.h"
 #include "source/opt/fold_spec_constant_op_and_composite_pass.h"
 #include "source/opt/fold_spec_constant_op_and_composite_pass.h"
 #include "source/opt/freeze_spec_constant_value_pass.h"
 #include "source/opt/freeze_spec_constant_value_pass.h"

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

@@ -34,9 +34,21 @@ MergeBlocksReductionOpportunity::MergeBlocksReductionOpportunity(
 }
 }
 
 
 bool MergeBlocksReductionOpportunity::PreconditionHolds() {
 bool MergeBlocksReductionOpportunity::PreconditionHolds() {
-  // By construction, it is not possible for the merging of A->B to disable the
-  // merging of C->D, even when B and C are the same block.
-  return true;
+  // Merge block opportunities can disable each other.
+  // Example: Given blocks: A->B->C.
+  // A is a loop header; B and C are blocks in the loop; C ends with OpReturn.
+  // There are two opportunities: B and C can be merged with their predecessors.
+  // Merge C. B now ends with OpReturn. We now just have: A->B.
+  // Merge B is now disabled, as this would lead to A, a loop header, ending
+  // with an OpReturn, which is invalid.
+
+  const auto predecessors = context_->cfg()->preds(successor_block_->id());
+  assert(1 == predecessors.size() &&
+         "For a successor to be merged into its predecessor, exactly one "
+         "predecessor must be present.");
+  const uint32_t predecessor_id = predecessors[0];
+  BasicBlock* predecessor_block = context_->get_instr_block(predecessor_id);
+  return blockmergeutil::CanMergeWithSuccessor(context_, predecessor_block);
 }
 }
 
 
 void MergeBlocksReductionOpportunity::Apply() {
 void MergeBlocksReductionOpportunity::Apply() {
@@ -50,6 +62,7 @@ void MergeBlocksReductionOpportunity::Apply() {
          "predecessor must be present.");
          "predecessor must be present.");
   const uint32_t predecessor_id = predecessors[0];
   const uint32_t predecessor_id = predecessors[0];
 
 
+  // We need an iterator pointing to the predecessor, hence the loop.
   for (auto bi = function_->begin(); bi != function_->end(); ++bi) {
   for (auto bi = function_->begin(); bi != function_->end(); ++bi) {
     if (bi->id() == predecessor_id) {
     if (bi->id() == predecessor_id) {
       blockmergeutil::MergeWithSuccessor(context_, function_, bi);
       blockmergeutil::MergeWithSuccessor(context_, function_, bi);

+ 13 - 1
3rdparty/spirv-tools/source/val/validate.cpp

@@ -385,7 +385,6 @@ spv_result_t ValidateBinaryUsingContextAndValidationState(
       Instruction* inst = const_cast<Instruction*>(&instruction);
       Instruction* inst = const_cast<Instruction*>(&instruction);
       vstate->RegisterInstruction(inst);
       vstate->RegisterInstruction(inst);
     }
     }
-    if (auto error = UpdateIdUse(*vstate, &instruction)) return error;
   }
   }
 
 
   if (!vstate->has_memory_model_specified())
   if (!vstate->has_memory_model_specified())
@@ -399,6 +398,19 @@ spv_result_t ValidateBinaryUsingContextAndValidationState(
   // Catch undefined forward references before performing further checks.
   // Catch undefined forward references before performing further checks.
   if (auto error = ValidateForwardDecls(*vstate)) return error;
   if (auto error = ValidateForwardDecls(*vstate)) return error;
 
 
+  // ID usage needs be handled in its own iteration of the instructions,
+  // between the two others. It depends on the first loop to have been
+  // finished, so that all instructions have been registered. And the following
+  // loop depends on all of the usage data being populated. Thus it cannot live
+  // in either of those iterations.
+  // It should also live after the forward declaration check, since it will
+  // have problems with missing forward declarations, but give less useful error
+  // messages.
+  for (size_t i = 0; i < vstate->ordered_instructions().size(); ++i) {
+    auto& instruction = vstate->ordered_instructions()[i];
+    if (auto error = UpdateIdUse(*vstate, &instruction)) return error;
+  }
+
   // Validate individual opcodes.
   // Validate individual opcodes.
   for (size_t i = 0; i < vstate->ordered_instructions().size(); ++i) {
   for (size_t i = 0; i < vstate->ordered_instructions().size(); ++i) {
     auto& instruction = vstate->ordered_instructions()[i];
     auto& instruction = vstate->ordered_instructions()[i];

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

@@ -1185,6 +1185,9 @@ spv_result_t CheckFPRoundingModeForShaders(ValidationState_t& vstate,
   // Validates Object operand of an OpStore
   // Validates Object operand of an OpStore
   for (const auto& use : inst.uses()) {
   for (const auto& use : inst.uses()) {
     const auto store = use.first;
     const auto store = use.first;
+    if (store->opcode() == SpvOpFConvert) continue;
+    if (spvOpcodeIsDebug(store->opcode())) continue;
+    if (spvOpcodeIsDecoration(store->opcode())) continue;
     if (store->opcode() != SpvOpStore) {
     if (store->opcode() != SpvOpStore) {
       return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
       return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
              << "FPRoundingMode decoration can be applied only to the "
              << "FPRoundingMode decoration can be applied only to the "

+ 14 - 10
3rdparty/spirv-tools/source/val/validate_function.cpp

@@ -41,18 +41,22 @@ spv_result_t ValidateFunction(ValidationState_t& _, const Instruction* inst) {
            << _.getIdName(return_id) << "'.";
            << _.getIdName(return_id) << "'.";
   }
   }
 
 
+  const std::vector<SpvOp> acceptable = {
+      SpvOpDecorate,
+      SpvOpEnqueueKernel,
+      SpvOpEntryPoint,
+      SpvOpExecutionMode,
+      SpvOpExecutionModeId,
+      SpvOpFunctionCall,
+      SpvOpGetKernelNDrangeSubGroupCount,
+      SpvOpGetKernelNDrangeMaxSubGroupSize,
+      SpvOpGetKernelWorkGroupSize,
+      SpvOpGetKernelPreferredWorkGroupSizeMultiple,
+      SpvOpGetKernelLocalSizeForSubgroupCount,
+      SpvOpGetKernelMaxNumSubgroups,
+      SpvOpName};
   for (auto& pair : inst->uses()) {
   for (auto& pair : inst->uses()) {
     const auto* use = pair.first;
     const auto* use = pair.first;
-    const std::vector<SpvOp> acceptable = {
-        SpvOpFunctionCall,
-        SpvOpEntryPoint,
-        SpvOpEnqueueKernel,
-        SpvOpGetKernelNDrangeSubGroupCount,
-        SpvOpGetKernelNDrangeMaxSubGroupSize,
-        SpvOpGetKernelWorkGroupSize,
-        SpvOpGetKernelPreferredWorkGroupSizeMultiple,
-        SpvOpGetKernelLocalSizeForSubgroupCount,
-        SpvOpGetKernelMaxNumSubgroups};
     if (std::find(acceptable.begin(), acceptable.end(), use->opcode()) ==
     if (std::find(acceptable.begin(), acceptable.end(), use->opcode()) ==
         acceptable.end()) {
         acceptable.end()) {
       return _.diag(SPV_ERROR_INVALID_ID, use)
       return _.diag(SPV_ERROR_INVALID_ID, use)

+ 4 - 2
3rdparty/spirv-tools/source/val/validate_type.cpp

@@ -324,10 +324,12 @@ spv_result_t ValidateTypeFunction(ValidationState_t& _,
            << num_args << " arguments.";
            << num_args << " arguments.";
   }
   }
 
 
-  // The only valid uses of OpTypeFunction are in an OpFunction instruction.
+  // The only valid uses of OpTypeFunction are in an OpFunction, debugging, or
+  // decoration instruction.
   for (auto& pair : inst->uses()) {
   for (auto& pair : inst->uses()) {
     const auto* use = pair.first;
     const auto* use = pair.first;
-    if (use->opcode() != SpvOpFunction) {
+    if (use->opcode() != SpvOpFunction && !spvOpcodeIsDebug(use->opcode()) &&
+        !spvOpcodeIsDecoration(use->opcode())) {
       return _.diag(SPV_ERROR_INVALID_ID, use)
       return _.diag(SPV_ERROR_INVALID_ID, use)
              << "Invalid use of function type result id "
              << "Invalid use of function type result id "
              << _.getIdName(inst->id()) << ".";
              << _.getIdName(inst->id()) << ".";

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

@@ -34,6 +34,7 @@ add_spvtools_unittest(TARGET opt
        def_use_test.cpp
        def_use_test.cpp
        eliminate_dead_const_test.cpp
        eliminate_dead_const_test.cpp
        eliminate_dead_functions_test.cpp
        eliminate_dead_functions_test.cpp
+       eliminate_dead_member_test.cpp
        feature_manager_test.cpp
        feature_manager_test.cpp
        flatten_decoration_test.cpp
        flatten_decoration_test.cpp
        fold_spec_const_op_composite_test.cpp
        fold_spec_const_op_composite_test.cpp

+ 43 - 0
3rdparty/spirv-tools/test/opt/block_merge_test.cpp

@@ -483,6 +483,8 @@ TEST_F(BlockMergeTest, RemoveStructuredDeclaration) {
 ; CHECK-NOT: OpLoopMerge
 ; CHECK-NOT: OpLoopMerge
 ; CHECK: OpReturn
 ; CHECK: OpReturn
 ; CHECK: [[continue:%\w+]] = OpLabel
 ; CHECK: [[continue:%\w+]] = OpLabel
+; CHECK-NEXT: OpBranch [[block:%\w+]]
+; CHECK: [[block]] = OpLabel
 ; CHECK-NEXT: OpBranch [[header]]
 ; CHECK-NEXT: OpBranch [[header]]
 OpCapability Shader
 OpCapability Shader
 %1 = OpExtInstImport "GLSL.std.450"
 %1 = OpExtInstImport "GLSL.std.450"
@@ -880,6 +882,47 @@ OpFunctionEnd
                                         prefix + suffix_after, true, true);
                                         prefix + suffix_after, true, true);
 }
 }
 
 
+TEST_F(BlockMergeTest, UnreachableLoop) {
+  const std::string spirv = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource ESSL 310
+OpName %main "main"
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%bool = OpTypeBool
+%false = OpConstantFalse %bool
+%main = OpFunction %void None %4
+%9 = OpLabel
+OpBranch %10
+%11 = OpLabel
+OpLoopMerge %12 %13 None
+OpBranchConditional %false %13 %14
+%13 = OpLabel
+OpSelectionMerge %15 None
+OpBranchConditional %false %16 %17
+%16 = OpLabel
+OpBranch %15
+%17 = OpLabel
+OpBranch %15
+%15 = OpLabel
+OpBranch %11
+%14 = OpLabel
+OpReturn
+%12 = OpLabel
+OpBranch %10
+%10 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<BlockMergePass>(spirv, spirv, true, true);
+}
+
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 //
 //
 //    More complex control flow
 //    More complex control flow

+ 1031 - 0
3rdparty/spirv-tools/test/opt/eliminate_dead_member_test.cpp

@@ -0,0 +1,1031 @@
+// 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 "assembly_builder.h"
+#include "gmock/gmock.h"
+#include "pass_fixture.h"
+#include "pass_utils.h"
+
+namespace {
+
+using namespace spvtools;
+
+using EliminateDeadMemberTest = opt::PassTest<::testing::Test>;
+
+TEST_F(EliminateDeadMemberTest, RemoveMember1) {
+  // Test that the member "y" is removed.
+  // Update OpMemberName for |y| and |z|.
+  // Update OpMemberDecorate for |y| and |z|.
+  // Update OpAccessChain for access to |z|.
+  const std::string text = R"(
+; CHECK: OpName
+; CHECK-NEXT: OpMemberName %type__Globals 0 "x"
+; CHECK-NEXT: OpMemberName %type__Globals 1 "z"
+; CHECK-NOT: OpMemberName
+; CHECK: OpMemberDecorate %type__Globals 0 Offset 0
+; CHECK: OpMemberDecorate %type__Globals 1 Offset 8
+; CHECK: %type__Globals = OpTypeStruct %float %float
+; CHECK: OpAccessChain %_ptr_Uniform_float %_Globals %int_0
+; CHECK: OpAccessChain %_ptr_Uniform_float %_Globals %uint_1
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %in_var_Position %gl_Position
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %in_var_Position "in.var.Position"
+               OpName %main "main"
+               OpDecorate %gl_Position BuiltIn Position
+               OpDecorate %in_var_Position Location 0
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 4
+               OpMemberDecorate %type__Globals 2 Offset 8
+               OpDecorate %type__Globals Block
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+      %float = OpTypeFloat 32
+      %int_2 = OpConstant %int 2
+%type__Globals = OpTypeStruct %float %float %float
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+    %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+       %void = OpTypeVoid
+         %15 = OpTypeFunction %void
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+   %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform
+%in_var_Position = OpVariable %_ptr_Input_v4float Input
+%gl_Position = OpVariable %_ptr_Output_v4float Output
+       %main = OpFunction %void None %15
+         %17 = OpLabel
+         %18 = OpLoad %v4float %in_var_Position
+         %19 = OpAccessChain %_ptr_Uniform_float %_Globals %int_0
+         %20 = OpLoad %float %19
+         %21 = OpCompositeExtract %float %18 0
+         %22 = OpFAdd %float %21 %20
+         %23 = OpCompositeInsert %v4float %22 %18 0
+         %24 = OpCompositeExtract %float %18 1
+         %25 = OpCompositeInsert %v4float %24 %23 1
+         %26 = OpAccessChain %_ptr_Uniform_float %_Globals %int_2
+         %27 = OpLoad %float %26
+         %28 = OpCompositeExtract %float %18 2
+         %29 = OpFAdd %float %28 %27
+         %30 = OpCompositeInsert %v4float %29 %25 2
+               OpStore %gl_Position %30
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::EliminateDeadMembersPass>(text, true);
+}
+
+TEST_F(EliminateDeadMemberTest, RemoveMemberWithGroupDecorations) {
+  // Test that the member "y" is removed.
+  // Update OpGroupMemberDecorate for %type__Globals member 1 and 2.
+  // Update OpAccessChain for access to %type__Globals member 2.
+  const std::string text = R"(
+; CHECK: OpDecorate [[gr1:%\w+]] Offset 0
+; CHECK: OpDecorate [[gr2:%\w+]] Offset 4
+; CHECK: OpDecorate [[gr3:%\w+]] Offset 8
+; CHECK: [[gr1]] = OpDecorationGroup
+; CHECK: [[gr2]] = OpDecorationGroup
+; CHECK: [[gr3]] = OpDecorationGroup
+; CHECK: OpGroupMemberDecorate [[gr1]] %type__Globals 0
+; CHECK-NOT: OpGroupMemberDecorate [[gr2]]
+; CHECK: OpGroupMemberDecorate [[gr3]] %type__Globals 1
+; CHECK: %type__Globals = OpTypeStruct %float %float
+; CHECK: OpAccessChain %_ptr_Uniform_float %_Globals %int_0
+; CHECK: OpAccessChain %_ptr_Uniform_float %_Globals %uint_1
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %in_var_Position %gl_Position
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpName %_Globals "$Globals"
+               OpDecorate %gl_Position BuiltIn Position
+               OpDecorate %in_var_Position Location 0
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpDecorate %gr1 Offset 0
+               OpDecorate %gr2 Offset 4
+               OpDecorate %gr3 Offset 8
+               OpDecorate %type__Globals Block
+        %gr1 = OpDecorationGroup
+        %gr2 = OpDecorationGroup
+        %gr3 = OpDecorationGroup
+               OpGroupMemberDecorate %gr1 %type__Globals 0
+               OpGroupMemberDecorate %gr2 %type__Globals 1
+               OpGroupMemberDecorate %gr3 %type__Globals 2
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+      %float = OpTypeFloat 32
+      %int_2 = OpConstant %int 2
+%type__Globals = OpTypeStruct %float %float %float
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+    %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+       %void = OpTypeVoid
+         %15 = OpTypeFunction %void
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+   %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform
+%in_var_Position = OpVariable %_ptr_Input_v4float Input
+%gl_Position = OpVariable %_ptr_Output_v4float Output
+       %main = OpFunction %void None %15
+         %17 = OpLabel
+         %18 = OpLoad %v4float %in_var_Position
+         %19 = OpAccessChain %_ptr_Uniform_float %_Globals %int_0
+         %20 = OpLoad %float %19
+         %21 = OpCompositeExtract %float %18 0
+         %22 = OpFAdd %float %21 %20
+         %23 = OpCompositeInsert %v4float %22 %18 0
+         %24 = OpCompositeExtract %float %18 1
+         %25 = OpCompositeInsert %v4float %24 %23 1
+         %26 = OpAccessChain %_ptr_Uniform_float %_Globals %int_2
+         %27 = OpLoad %float %26
+         %28 = OpCompositeExtract %float %18 2
+         %29 = OpFAdd %float %28 %27
+         %30 = OpCompositeInsert %v4float %29 %25 2
+               OpStore %gl_Position %30
+               OpReturn
+               OpFunctionEnd
+)";
+
+  // Skipping validation because of a bug in the validator.  See issue #2376.
+  SinglePassRunAndMatch<opt::EliminateDeadMembersPass>(text, false);
+}
+
+TEST_F(EliminateDeadMemberTest, RemoveMemberUpdateConstant) {
+  // Test that the member "x" is removed.
+  // Update the OpConstantComposite instruction.
+  const std::string text = R"(
+; CHECK: OpName
+; CHECK-NEXT: OpMemberName %type__Globals 0 "y"
+; CHECK-NEXT: OpMemberName %type__Globals 1 "z"
+; CHECK-NOT: OpMemberName
+; CHECK: OpMemberDecorate %type__Globals 0 Offset 4
+; CHECK: OpMemberDecorate %type__Globals 1 Offset 8
+; CHECK: %type__Globals = OpTypeStruct %float %float
+; CHECK: OpConstantComposite %type__Globals %float_1 %float_2
+; CHECK: OpAccessChain %_ptr_Uniform_float %_Globals %uint_0
+; CHECK: OpAccessChain %_ptr_Uniform_float %_Globals %uint_1
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %in_var_Position %gl_Position
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %in_var_Position "in.var.Position"
+               OpName %main "main"
+               OpDecorate %gl_Position BuiltIn Position
+               OpDecorate %in_var_Position Location 0
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 4
+               OpMemberDecorate %type__Globals 2 Offset 8
+               OpDecorate %type__Globals Block
+        %int = OpTypeInt 32 1
+      %int_1 = OpConstant %int 1
+      %float = OpTypeFloat 32
+    %float_0 = OpConstant %float 0
+    %float_1 = OpConstant %float 1
+    %float_2 = OpConstant %float 2
+      %int_2 = OpConstant %int 2
+%type__Globals = OpTypeStruct %float %float %float
+         %13 = OpConstantComposite %type__Globals %float_0 %float_1 %float_2
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+    %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+       %void = OpTypeVoid
+         %19 = OpTypeFunction %void
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+   %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform
+%in_var_Position = OpVariable %_ptr_Input_v4float Input
+%gl_Position = OpVariable %_ptr_Output_v4float Output
+       %main = OpFunction %void None %19
+         %21 = OpLabel
+         %22 = OpLoad %v4float %in_var_Position
+         %23 = OpAccessChain %_ptr_Uniform_float %_Globals %int_1
+         %24 = OpLoad %float %23
+         %25 = OpCompositeExtract %float %22 0
+         %26 = OpFAdd %float %25 %24
+         %27 = OpCompositeInsert %v4float %26 %22 0
+         %28 = OpCompositeExtract %float %22 1
+         %29 = OpCompositeInsert %v4float %28 %27 1
+         %30 = OpAccessChain %_ptr_Uniform_float %_Globals %int_2
+         %31 = OpLoad %float %30
+         %32 = OpCompositeExtract %float %22 2
+         %33 = OpFAdd %float %32 %31
+         %34 = OpCompositeInsert %v4float %33 %29 2
+               OpStore %gl_Position %34
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::EliminateDeadMembersPass>(text, true);
+}
+
+TEST_F(EliminateDeadMemberTest, RemoveMemberUpdateCompositeConstruct) {
+  // Test that the member "x" is removed.
+  // Update the OpConstantComposite instruction.
+  const std::string text = R"(
+; CHECK: OpName
+; CHECK-NEXT: OpMemberName %type__Globals 0 "y"
+; CHECK-NEXT: OpMemberName %type__Globals 1 "z"
+; CHECK-NOT: OpMemberName
+; CHECK: OpMemberDecorate %type__Globals 0 Offset 4
+; CHECK: OpMemberDecorate %type__Globals 1 Offset 8
+; CHECK: %type__Globals = OpTypeStruct %float %float
+; CHECK: OpCompositeConstruct %type__Globals %float_1 %float_2
+; CHECK: OpAccessChain %_ptr_Uniform_float %_Globals %uint_0
+; CHECK: OpAccessChain %_ptr_Uniform_float %_Globals %uint_1
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %in_var_Position %gl_Position
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %in_var_Position "in.var.Position"
+               OpName %main "main"
+               OpDecorate %gl_Position BuiltIn Position
+               OpDecorate %in_var_Position Location 0
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 4
+               OpMemberDecorate %type__Globals 2 Offset 8
+               OpDecorate %type__Globals Block
+        %int = OpTypeInt 32 1
+      %int_1 = OpConstant %int 1
+      %float = OpTypeFloat 32
+    %float_0 = OpConstant %float 0
+    %float_1 = OpConstant %float 1
+    %float_2 = OpConstant %float 2
+      %int_2 = OpConstant %int 2
+%type__Globals = OpTypeStruct %float %float %float
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+    %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+       %void = OpTypeVoid
+         %19 = OpTypeFunction %void
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+   %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform
+%in_var_Position = OpVariable %_ptr_Input_v4float Input
+%gl_Position = OpVariable %_ptr_Output_v4float Output
+       %main = OpFunction %void None %19
+         %21 = OpLabel
+         %13 = OpCompositeConstruct %type__Globals %float_0 %float_1 %float_2
+         %22 = OpLoad %v4float %in_var_Position
+         %23 = OpAccessChain %_ptr_Uniform_float %_Globals %int_1
+         %24 = OpLoad %float %23
+         %25 = OpCompositeExtract %float %22 0
+         %26 = OpFAdd %float %25 %24
+         %27 = OpCompositeInsert %v4float %26 %22 0
+         %28 = OpCompositeExtract %float %22 1
+         %29 = OpCompositeInsert %v4float %28 %27 1
+         %30 = OpAccessChain %_ptr_Uniform_float %_Globals %int_2
+         %31 = OpLoad %float %30
+         %32 = OpCompositeExtract %float %22 2
+         %33 = OpFAdd %float %32 %31
+         %34 = OpCompositeInsert %v4float %33 %29 2
+               OpStore %gl_Position %34
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::EliminateDeadMembersPass>(text, true);
+}
+
+TEST_F(EliminateDeadMemberTest, RemoveMembersUpdateInserExtract1) {
+  // Test that the members "x" and "z" are removed.
+  // Update the OpCompositeExtract instruction.
+  // Remove the OpCompositeInsert instruction since the member being inserted is
+  // dead.
+  const std::string text = R"(
+; CHECK: OpName
+; CHECK-NEXT: OpMemberName %type__Globals 0 "y"
+; CHECK-NOT: OpMemberName
+; CHECK: OpMemberDecorate %type__Globals 0 Offset 4
+; CHECK-NOT: OpMemberDecorate %type__Globals 1 Offset
+; CHECK: %type__Globals = OpTypeStruct %float
+; CHECK: [[ld:%\w+]] = OpLoad %type__Globals %_Globals
+; CHECK: OpCompositeExtract %float [[ld]] 0
+; CHECK-NOT: OpCompositeInsert
+; CHECK: OpReturn
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %main "main"
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 4
+               OpMemberDecorate %type__Globals 2 Offset 8
+               OpDecorate %type__Globals Block
+      %float = OpTypeFloat 32
+%type__Globals = OpTypeStruct %float %float %float
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+       %void = OpTypeVoid
+          %7 = OpTypeFunction %void
+   %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform
+       %main = OpFunction %void None %7
+          %8 = OpLabel
+          %9 = OpLoad %type__Globals %_Globals
+         %10 = OpCompositeExtract %float %9 1
+         %11 = OpCompositeInsert %type__Globals %10 %9 2
+               OpReturn
+               OpFunctionEnd
+
+)";
+
+  SinglePassRunAndMatch<opt::EliminateDeadMembersPass>(text, true);
+}
+
+TEST_F(EliminateDeadMemberTest, RemoveMembersUpdateInserExtract2) {
+  // Test that the members "x" and "z" are removed.
+  // Update the OpCompositeExtract instruction.
+  // Update the OpCompositeInsert instruction.
+  const std::string text = R"(
+; CHECK: OpName
+; CHECK-NEXT: OpMemberName %type__Globals 0 "y"
+; CHECK-NOT: OpMemberName
+; CHECK: OpMemberDecorate %type__Globals 0 Offset 4
+; CHECK-NOT: OpMemberDecorate %type__Globals 1 Offset
+; CHECK: %type__Globals = OpTypeStruct %float
+; CHECK: [[ld:%\w+]] = OpLoad %type__Globals %_Globals
+; CHECK: [[ex:%\w+]] = OpCompositeExtract %float [[ld]] 0
+; CHECK: OpCompositeInsert %type__Globals [[ex]] [[ld]] 0
+; CHECK: OpReturn
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %main "main"
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 4
+               OpMemberDecorate %type__Globals 2 Offset 8
+               OpDecorate %type__Globals Block
+      %float = OpTypeFloat 32
+%type__Globals = OpTypeStruct %float %float %float
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+       %void = OpTypeVoid
+          %7 = OpTypeFunction %void
+   %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform
+       %main = OpFunction %void None %7
+          %8 = OpLabel
+          %9 = OpLoad %type__Globals %_Globals
+         %10 = OpCompositeExtract %float %9 1
+         %11 = OpCompositeInsert %type__Globals %10 %9 1
+               OpReturn
+               OpFunctionEnd
+
+)";
+
+  SinglePassRunAndMatch<opt::EliminateDeadMembersPass>(text, true);
+}
+
+TEST_F(EliminateDeadMemberTest, RemoveMembersUpdateInserExtract3) {
+  // Test that the members "x" and "z" are removed, and one member from the
+  // substruct. Update the OpCompositeExtract instruction. Update the
+  // OpCompositeInsert instruction.
+  const std::string text = R"(
+; CHECK: OpName
+; CHECK-NEXT: OpMemberName %type__Globals 0 "y"
+; CHECK-NOT: OpMemberName
+; CHECK: OpMemberDecorate %type__Globals 0 Offset 16
+; CHECK-NOT: OpMemberDecorate %type__Globals 1 Offset
+; CHECK: OpMemberDecorate [[struct:%\w+]] 0 Offset 4
+; CHECK: [[struct:%\w+]] = OpTypeStruct %float
+; CHECK: %type__Globals = OpTypeStruct [[struct]]
+; CHECK: [[ld:%\w+]] = OpLoad %type__Globals %_Globals
+; CHECK: [[ex:%\w+]] = OpCompositeExtract %float [[ld]] 0 0
+; CHECK: OpCompositeInsert %type__Globals [[ex]] [[ld]] 0 0
+; CHECK: OpReturn
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %main "main"
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 16
+               OpMemberDecorate %type__Globals 2 Offset 24
+               OpMemberDecorate %_struct_6 0 Offset 0
+               OpMemberDecorate %_struct_6 1 Offset 4
+               OpDecorate %type__Globals Block
+      %float = OpTypeFloat 32
+  %_struct_6 = OpTypeStruct %float %float
+%type__Globals = OpTypeStruct %float %_struct_6 %float
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+       %void = OpTypeVoid
+          %7 = OpTypeFunction %void
+   %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform
+       %main = OpFunction %void None %7
+          %8 = OpLabel
+          %9 = OpLoad %type__Globals %_Globals
+         %10 = OpCompositeExtract %float %9 1 1
+         %11 = OpCompositeInsert %type__Globals %10 %9 1 1
+               OpReturn
+               OpFunctionEnd
+
+)";
+
+  SinglePassRunAndMatch<opt::EliminateDeadMembersPass>(text, true);
+}
+
+TEST_F(EliminateDeadMemberTest, RemoveMembersUpdateInserExtract4) {
+  // Test that the members "x" and "z" are removed, and one member from the
+  // substruct. Update the OpCompositeExtract instruction. Update the
+  // OpCompositeInsert instruction.
+  const std::string text = R"(
+; CHECK: OpName
+; CHECK-NEXT: OpMemberName %type__Globals 0 "y"
+; CHECK-NOT: OpMemberName
+; CHECK: OpMemberDecorate %type__Globals 0 Offset 16
+; CHECK-NOT: OpMemberDecorate %type__Globals 1 Offset
+; CHECK: OpMemberDecorate [[struct:%\w+]] 0 Offset 4
+; CHECK: [[struct:%\w+]] = OpTypeStruct %float
+; CHECK: [[array:%\w+]] = OpTypeArray [[struct]]
+; CHECK: %type__Globals = OpTypeStruct [[array]]
+; CHECK: [[ld:%\w+]] = OpLoad %type__Globals %_Globals
+; CHECK: [[ex:%\w+]] = OpCompositeExtract %float [[ld]] 0 1 0
+; CHECK: OpCompositeInsert %type__Globals [[ex]] [[ld]] 0 1 0
+; CHECK: OpReturn
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %main "main"
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 16
+               OpMemberDecorate %type__Globals 2 Offset 80
+               OpMemberDecorate %_struct_6 0 Offset 0
+               OpMemberDecorate %_struct_6 1 Offset 4
+               OpDecorate %array ArrayStride 16
+               OpDecorate %type__Globals Block
+       %uint = OpTypeInt 32 0                         ; 32-bit int, sign-less
+     %uint_4 = OpConstant %uint 4
+      %float = OpTypeFloat 32
+  %_struct_6 = OpTypeStruct %float %float
+  %array = OpTypeArray %_struct_6 %uint_4
+%type__Globals = OpTypeStruct %float %array %float
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+       %void = OpTypeVoid
+          %7 = OpTypeFunction %void
+   %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform
+       %main = OpFunction %void None %7
+          %8 = OpLabel
+          %9 = OpLoad %type__Globals %_Globals
+         %10 = OpCompositeExtract %float %9 1 1 1
+         %11 = OpCompositeInsert %type__Globals %10 %9 1 1 1
+               OpReturn
+               OpFunctionEnd
+
+)";
+
+  SinglePassRunAndMatch<opt::EliminateDeadMembersPass>(text, true);
+}
+
+TEST_F(EliminateDeadMemberTest, RemoveMembersUpdateArrayLength) {
+  // Test that the members "x" and "y" are removed.
+  // Member "z" is live because of the OpArrayLength instruction.
+  // Update the OpArrayLength instruction.
+  const std::string text = R"(
+; CHECK: OpName
+; CHECK-NEXT: OpMemberName %type__Globals 0 "z"
+; CHECK-NOT: OpMemberName
+; CHECK: OpMemberDecorate %type__Globals 0 Offset 16
+; CHECK-NOT: OpMemberDecorate %type__Globals 1 Offset
+; CHECK: %type__Globals = OpTypeStruct %_runtimearr_float
+; CHECK: OpArrayLength %uint %_Globals 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %main "main"
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 4
+               OpMemberDecorate %type__Globals 2 Offset 16
+               OpDecorate %type__Globals Block
+       %uint = OpTypeInt 32 0
+      %float = OpTypeFloat 32
+%_runtimearr_float = OpTypeRuntimeArray %float
+%type__Globals = OpTypeStruct %float %float %_runtimearr_float
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+       %void = OpTypeVoid
+          %9 = OpTypeFunction %void
+   %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform
+       %main = OpFunction %void None %9
+         %10 = OpLabel
+         %11 = OpLoad %type__Globals %_Globals
+         %12 = OpArrayLength %uint %_Globals 2
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::EliminateDeadMembersPass>(text, true);
+}
+
+TEST_F(EliminateDeadMemberTest, KeepMembersOpStore) {
+  // Test that all members are kept because of an OpStore.
+  // No change expected.
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %_Globals "$Globals2"
+               OpName %main "main"
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 4
+               OpMemberDecorate %type__Globals 2 Offset 16
+               OpDecorate %type__Globals Block
+       %uint = OpTypeInt 32 0
+      %float = OpTypeFloat 32
+%type__Globals = OpTypeStruct %float %float %float
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+       %void = OpTypeVoid
+          %9 = OpTypeFunction %void
+   %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform
+   %_Globals2 = OpVariable %_ptr_Uniform_type__Globals Uniform
+       %main = OpFunction %void None %9
+         %10 = OpLabel
+         %11 = OpLoad %type__Globals %_Globals
+               OpStore %_Globals2 %11
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<opt::EliminateDeadMembersPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(EliminateDeadMemberTest, KeepMembersOpCopyMemory) {
+  // Test that all members are kept because of an OpCopyMemory.
+  // No change expected.
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %_Globals "$Globals2"
+               OpName %main "main"
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 4
+               OpMemberDecorate %type__Globals 2 Offset 16
+               OpDecorate %type__Globals Block
+       %uint = OpTypeInt 32 0
+      %float = OpTypeFloat 32
+%type__Globals = OpTypeStruct %float %float %float
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+       %void = OpTypeVoid
+          %9 = OpTypeFunction %void
+   %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform
+   %_Globals2 = OpVariable %_ptr_Uniform_type__Globals Uniform
+       %main = OpFunction %void None %9
+         %10 = OpLabel
+               OpCopyMemory %_Globals2 %_Globals
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<opt::EliminateDeadMembersPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(EliminateDeadMemberTest, KeepMembersOpCopyMemorySized) {
+  // Test that all members are kept because of an OpCopyMemorySized.
+  // No change expected.
+  const std::string text = R"(
+               OpCapability Shader
+               OpCapability Addresses
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %_Globals "$Globals2"
+               OpName %main "main"
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 4
+               OpMemberDecorate %type__Globals 2 Offset 16
+               OpDecorate %type__Globals Block
+       %uint = OpTypeInt 32 0
+    %uint_20 = OpConstant %uint 20
+      %float = OpTypeFloat 32
+%type__Globals = OpTypeStruct %float %float %float
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+       %void = OpTypeVoid
+          %9 = OpTypeFunction %void
+   %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform
+   %_Globals2 = OpVariable %_ptr_Uniform_type__Globals Uniform
+       %main = OpFunction %void None %9
+         %10 = OpLabel
+               OpCopyMemorySized %_Globals2 %_Globals %uint_20
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<opt::EliminateDeadMembersPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(EliminateDeadMemberTest, KeepMembersOpReturnValue) {
+  // Test that all members are kept because of an OpCopyMemorySized.
+  // No change expected.
+  const std::string text = R"(
+               OpCapability Shader
+               OpCapability Linkage
+               OpMemoryModel Logical GLSL450
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %_Globals "$Globals2"
+               OpName %main "main"
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 4
+               OpMemberDecorate %type__Globals 2 Offset 16
+               OpDecorate %type__Globals Block
+       %uint = OpTypeInt 32 0
+    %uint_20 = OpConstant %uint 20
+      %float = OpTypeFloat 32
+%type__Globals = OpTypeStruct %float %float %float
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+       %void = OpTypeVoid
+          %9 = OpTypeFunction %type__Globals
+   %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform
+   %_Globals2 = OpVariable %_ptr_Uniform_type__Globals Uniform
+       %main = OpFunction %type__Globals None %9
+         %10 = OpLabel
+         %11 = OpLoad %type__Globals %_Globals
+               OpReturnValue %11
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<opt::EliminateDeadMembersPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(EliminateDeadMemberTest, RemoveMemberAccessChainWithArrays) {
+  // Leave only 1 member in each of the structs.
+  // Update OpMemberName, OpMemberDecorate, and OpAccessChain.
+  const std::string text = R"(
+; CHECK: OpName
+; CHECK-NEXT: OpMemberName %type__Globals 0 "y"
+; CHECK-NOT: OpMemberName
+; CHECK: OpMemberDecorate %type__Globals 0 Offset 16
+; CHECK: OpMemberDecorate [[struct:%\w+]] 0 Offset 4
+; CHECK: [[struct]] = OpTypeStruct %float
+; CHECK: [[array:%\w+]] = OpTypeArray [[struct]]
+; CHECK: %type__Globals = OpTypeStruct [[array]]
+; CHECK: [[undef:%\w+]] = OpUndef %uint
+; CHECK: OpAccessChain %_ptr_Uniform_float %_Globals [[undef]] %uint_0 [[undef]] %uint_0
+               OpCapability Shader
+               OpCapability VariablePointersStorageBuffer
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %main "main"
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 16
+               OpMemberDecorate %type__Globals 2 Offset 48
+               OpMemberDecorate %_struct_4 0 Offset 0
+               OpMemberDecorate %_struct_4 1 Offset 4
+               OpDecorate %_arr__struct_4_uint_2 ArrayStride 16
+               OpDecorate %type__Globals Block
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_1 = OpConstant %uint 1
+     %uint_2 = OpConstant %uint 2
+     %uint_3 = OpConstant %uint 3
+      %float = OpTypeFloat 32
+  %_struct_4 = OpTypeStruct %float %float
+%_arr__struct_4_uint_2 = OpTypeArray %_struct_4 %uint_2
+%type__Globals = OpTypeStruct %float %_arr__struct_4_uint_2 %float
+%_arr_type__Globals_uint_3 = OpTypeArray %type__Globals %uint_3
+%_ptr_Uniform__arr_type__Globals_uint_3 = OpTypePointer Uniform %_arr_type__Globals_uint_3
+       %void = OpTypeVoid
+         %15 = OpTypeFunction %void
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+   %_Globals = OpVariable %_ptr_Uniform__arr_type__Globals_uint_3 Uniform
+       %main = OpFunction %void None %15
+         %17 = OpLabel
+         %18 = OpUndef %uint
+         %19 = OpAccessChain %_ptr_Uniform_float %_Globals %18 %uint_1 %18 %uint_1
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::EliminateDeadMembersPass>(text, true);
+}
+
+TEST_F(EliminateDeadMemberTest, RemoveMemberInboundsAccessChain) {
+  // Test that the member "y" is removed.
+  // Update OpMemberName for |y| and |z|.
+  // Update OpMemberDecorate for |y| and |z|.
+  // Update OpInboundsAccessChain for access to |z|.
+  const std::string text = R"(
+; CHECK: OpName
+; CHECK-NEXT: OpMemberName %type__Globals 0 "x"
+; CHECK-NEXT: OpMemberName %type__Globals 1 "z"
+; CHECK-NOT: OpMemberName
+; CHECK: OpMemberDecorate %type__Globals 0 Offset 0
+; CHECK: OpMemberDecorate %type__Globals 1 Offset 8
+; CHECK: %type__Globals = OpTypeStruct %float %float
+; CHECK: OpInBoundsAccessChain %_ptr_Uniform_float %_Globals %int_0
+; CHECK: OpInBoundsAccessChain %_ptr_Uniform_float %_Globals %uint_1
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %in_var_Position %gl_Position
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %in_var_Position "in.var.Position"
+               OpName %main "main"
+               OpDecorate %gl_Position BuiltIn Position
+               OpDecorate %in_var_Position Location 0
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 4
+               OpMemberDecorate %type__Globals 2 Offset 8
+               OpDecorate %type__Globals Block
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+      %float = OpTypeFloat 32
+      %int_2 = OpConstant %int 2
+%type__Globals = OpTypeStruct %float %float %float
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+    %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+       %void = OpTypeVoid
+         %15 = OpTypeFunction %void
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+   %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform
+%in_var_Position = OpVariable %_ptr_Input_v4float Input
+%gl_Position = OpVariable %_ptr_Output_v4float Output
+       %main = OpFunction %void None %15
+         %17 = OpLabel
+         %18 = OpLoad %v4float %in_var_Position
+         %19 = OpInBoundsAccessChain %_ptr_Uniform_float %_Globals %int_0
+         %20 = OpLoad %float %19
+         %21 = OpCompositeExtract %float %18 0
+         %22 = OpFAdd %float %21 %20
+         %23 = OpCompositeInsert %v4float %22 %18 0
+         %24 = OpCompositeExtract %float %18 1
+         %25 = OpCompositeInsert %v4float %24 %23 1
+         %26 = OpInBoundsAccessChain %_ptr_Uniform_float %_Globals %int_2
+         %27 = OpLoad %float %26
+         %28 = OpCompositeExtract %float %18 2
+         %29 = OpFAdd %float %28 %27
+         %30 = OpCompositeInsert %v4float %29 %25 2
+               OpStore %gl_Position %30
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::EliminateDeadMembersPass>(text, true);
+}
+
+TEST_F(EliminateDeadMemberTest, RemoveMemberPtrAccessChain) {
+  // Test that the member "y" is removed.
+  // Update OpMemberName for |y| and |z|.
+  // Update OpMemberDecorate for |y| and |z|.
+  // Update OpInboundsAccessChain for access to |z|.
+  const std::string text = R"(
+; CHECK: OpName
+; CHECK-NEXT: OpMemberName %type__Globals 0 "x"
+; CHECK-NEXT: OpMemberName %type__Globals 1 "z"
+; CHECK-NOT: OpMemberName
+; CHECK: OpMemberDecorate %type__Globals 0 Offset 0
+; CHECK: OpMemberDecorate %type__Globals 1 Offset 16
+; CHECK: %type__Globals = OpTypeStruct %float %float
+; CHECK: [[ac:%\w+]] = OpAccessChain %_ptr_Uniform_type__Globals %_Globals %uint_0
+; CHECK: OpPtrAccessChain %_ptr_Uniform_float [[ac]] %uint_1 %uint_0
+; CHECK: OpPtrAccessChain %_ptr_Uniform_float [[ac]] %uint_0 %uint_1
+               OpCapability Shader
+               OpCapability VariablePointersStorageBuffer
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %main "main"
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 4
+               OpMemberDecorate %type__Globals 2 Offset 16
+               OpDecorate %type__Globals Block
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_1 = OpConstant %uint 1
+     %uint_2 = OpConstant %uint 2
+     %uint_3 = OpConstant %uint 3
+      %float = OpTypeFloat 32
+%type__Globals = OpTypeStruct %float %float %float
+%_arr_type__Globals_uint_3 = OpTypeArray %type__Globals %uint_3
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+%_ptr_Uniform__arr_type__Globals_uint_3 = OpTypePointer Uniform %_arr_type__Globals_uint_3
+       %void = OpTypeVoid
+         %14 = OpTypeFunction %void
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+   %_Globals = OpVariable %_ptr_Uniform__arr_type__Globals_uint_3 Uniform
+       %main = OpFunction %void None %14
+         %16 = OpLabel
+         %17 = OpAccessChain %_ptr_Uniform_type__Globals %_Globals %uint_0
+         %18 = OpPtrAccessChain %_ptr_Uniform_float %17 %uint_1 %uint_0
+         %19 = OpPtrAccessChain %_ptr_Uniform_float %17 %uint_0 %uint_2
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::EliminateDeadMembersPass>(text, true);
+}
+
+TEST_F(EliminateDeadMemberTest, RemoveMemberInBoundsPtrAccessChain) {
+  // Test that the member "y" is removed.
+  // Update OpMemberName for |y| and |z|.
+  // Update OpMemberDecorate for |y| and |z|.
+  // Update OpInboundsAccessChain for access to |z|.
+  const std::string text = R"(
+; CHECK: OpName
+; CHECK-NEXT: OpMemberName %type__Globals 0 "x"
+; CHECK-NEXT: OpMemberName %type__Globals 1 "z"
+; CHECK-NOT: OpMemberName
+; CHECK: OpMemberDecorate %type__Globals 0 Offset 0
+; CHECK: OpMemberDecorate %type__Globals 1 Offset 16
+; CHECK: %type__Globals = OpTypeStruct %float %float
+; CHECK: [[ac:%\w+]] = OpAccessChain %_ptr_Uniform_type__Globals %_Globals %uint_0
+; CHECK: OpInBoundsPtrAccessChain %_ptr_Uniform_float [[ac]] %uint_1 %uint_0
+; CHECK: OpInBoundsPtrAccessChain %_ptr_Uniform_float [[ac]] %uint_0 %uint_1
+               OpCapability Shader
+               OpCapability Addresses
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %main "main"
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 4
+               OpMemberDecorate %type__Globals 2 Offset 16
+               OpDecorate %type__Globals Block
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_1 = OpConstant %uint 1
+     %uint_2 = OpConstant %uint 2
+     %uint_3 = OpConstant %uint 3
+      %float = OpTypeFloat 32
+%type__Globals = OpTypeStruct %float %float %float
+%_arr_type__Globals_uint_3 = OpTypeArray %type__Globals %uint_3
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+%_ptr_Uniform__arr_type__Globals_uint_3 = OpTypePointer Uniform %_arr_type__Globals_uint_3
+       %void = OpTypeVoid
+         %14 = OpTypeFunction %void
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+   %_Globals = OpVariable %_ptr_Uniform__arr_type__Globals_uint_3 Uniform
+       %main = OpFunction %void None %14
+         %16 = OpLabel
+         %17 = OpAccessChain %_ptr_Uniform_type__Globals %_Globals %uint_0
+         %18 = OpInBoundsPtrAccessChain %_ptr_Uniform_float %17 %uint_1 %uint_0
+         %19 = OpInBoundsPtrAccessChain %_ptr_Uniform_float %17 %uint_0 %uint_2
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::EliminateDeadMembersPass>(text, true);
+}
+
+TEST_F(EliminateDeadMemberTest, DontRemoveModfStructResultTypeMembers) {
+  const std::string text = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %PS_TerrainElevation "PS_TerrainElevation"
+               OpExecutionMode %PS_TerrainElevation OriginUpperLeft
+               OpSource HLSL 600
+      %float = OpTypeFloat 32
+       %void = OpTypeVoid
+         %21 = OpTypeFunction %void
+%ModfStructType = OpTypeStruct %float %float
+%PS_TerrainElevation = OpFunction %void None %21
+         %22 = OpLabel
+         %23 = OpUndef %float
+         %24 = OpExtInst %ModfStructType %1 ModfStruct %23
+         %25 = OpCompositeExtract %float %24 1
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<opt::EliminateDeadMembersPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+}  // namespace

+ 323 - 0
3rdparty/spirv-tools/test/opt/inst_bindless_check_test.cpp

@@ -1580,6 +1580,329 @@ OpFunctionEnd
       true);
       true);
 }
 }
 
 
+TEST_F(InstBindlessTest, MultipleDebugFunctions) {
+  // Same source as Simple, but compiled -g and not optimized, especially not
+  // inlined. The OpSource has had the source extracted for the sake of brevity.
+
+  const std::string defs_before =
+      R"(OpCapability Shader
+%2 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
+OpExecutionMode %MainPs OriginUpperLeft
+%1 = OpString "foo5.frag"
+OpSource HLSL 500 %1
+OpName %MainPs "MainPs"
+OpName %PS_INPUT "PS_INPUT"
+OpMemberName %PS_INPUT 0 "vTextureCoords"
+OpName %PS_OUTPUT "PS_OUTPUT"
+OpMemberName %PS_OUTPUT 0 "vColor"
+OpName %_MainPs_struct_PS_INPUT_vf21_ "@MainPs(struct-PS_INPUT-vf21;"
+OpName %i "i"
+OpName %ps_output "ps_output"
+OpName %g_tColor "g_tColor"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %g_sAniso "g_sAniso"
+OpName %i_0 "i"
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpName %param "param"
+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
+%4 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%PS_INPUT = OpTypeStruct %v2float
+%_ptr_Function_PS_INPUT = OpTypePointer Function %PS_INPUT
+%v4float = OpTypeVector %float 4
+%PS_OUTPUT = OpTypeStruct %v4float
+%13 = OpTypeFunction %PS_OUTPUT %_ptr_Function_PS_INPUT
+%_ptr_Function_PS_OUTPUT = OpTypePointer Function %PS_OUTPUT
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%21 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%_arr_21_uint_128 = OpTypeArray %21 %uint_128
+%_ptr_UniformConstant__arr_21_uint_128 = OpTypePointer UniformConstant %_arr_21_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_21_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_21 = OpTypePointer UniformConstant %21
+%36 = OpTypeSampler
+%_ptr_UniformConstant_36 = OpTypePointer UniformConstant %36
+%g_sAniso = OpVariable %_ptr_UniformConstant_36 UniformConstant
+%40 = OpTypeSampledImage %21
+%_ptr_Function_v2float = OpTypePointer Function %v2float
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+  const std::string defs_after =
+      R"(OpCapability Shader
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord
+OpExecutionMode %MainPs OriginUpperLeft
+%5 = OpString "foo5.frag"
+OpSource HLSL 500 %5
+OpName %MainPs "MainPs"
+OpName %PS_INPUT "PS_INPUT"
+OpMemberName %PS_INPUT 0 "vTextureCoords"
+OpName %PS_OUTPUT "PS_OUTPUT"
+OpMemberName %PS_OUTPUT 0 "vColor"
+OpName %_MainPs_struct_PS_INPUT_vf21_ "@MainPs(struct-PS_INPUT-vf21;"
+OpName %i "i"
+OpName %ps_output "ps_output"
+OpName %g_tColor "g_tColor"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %g_sAniso "g_sAniso"
+OpName %i_0 "i"
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpName %param "param"
+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_77 Block
+OpMemberDecorate %_struct_77 0 Offset 0
+OpMemberDecorate %_struct_77 1 Offset 4
+OpDecorate %79 DescriptorSet 7
+OpDecorate %79 Binding 0
+OpDecorate %gl_FragCoord BuiltIn FragCoord
+%void = OpTypeVoid
+%18 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%PS_INPUT = OpTypeStruct %v2float
+%_ptr_Function_PS_INPUT = OpTypePointer Function %PS_INPUT
+%v4float = OpTypeVector %float 4
+%PS_OUTPUT = OpTypeStruct %v4float
+%23 = OpTypeFunction %PS_OUTPUT %_ptr_Function_PS_INPUT
+%_ptr_Function_PS_OUTPUT = OpTypePointer Function %PS_OUTPUT
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%27 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%_arr_27_uint_128 = OpTypeArray %27 %uint_128
+%_ptr_UniformConstant__arr_27_uint_128 = OpTypePointer UniformConstant %_arr_27_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_27_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_27 = OpTypePointer UniformConstant %27
+%35 = OpTypeSampler
+%_ptr_UniformConstant_35 = OpTypePointer UniformConstant %35
+%g_sAniso = OpVariable %_ptr_UniformConstant_35 UniformConstant
+%37 = OpTypeSampledImage %27
+%_ptr_Function_v2float = OpTypePointer Function %v2float
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_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
+%70 = OpTypeFunction %void %uint %uint %uint %uint
+%_runtimearr_uint = OpTypeRuntimeArray %uint
+%_struct_77 = OpTypeStruct %uint %_runtimearr_uint
+%_ptr_StorageBuffer__struct_77 = OpTypePointer StorageBuffer %_struct_77
+%79 = OpVariable %_ptr_StorageBuffer__struct_77 StorageBuffer
+%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+%uint_9 = OpConstant %uint 9
+%uint_4 = OpConstant %uint 4
+%uint_1 = OpConstant %uint 1
+%uint_23 = OpConstant %uint 23
+%uint_2 = OpConstant %uint 2
+%uint_3 = OpConstant %uint 3
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+%v4uint = OpTypeVector %uint 4
+%uint_5 = OpConstant %uint 5
+%uint_6 = OpConstant %uint 6
+%uint_7 = OpConstant %uint 7
+%uint_8 = OpConstant %uint 8
+%uint_93 = OpConstant %uint 93
+%125 = OpConstantNull %v4float
+)";
+
+  const std::string func1_before =
+      R"(%MainPs = OpFunction %void None %4
+%6 = OpLabel
+%i_0 = OpVariable %_ptr_Function_PS_INPUT Function
+%param = OpVariable %_ptr_Function_PS_INPUT Function
+OpLine %1 21 0
+%54 = OpLoad %v2float %i_vTextureCoords
+%55 = OpAccessChain %_ptr_Function_v2float %i_0 %int_0
+OpStore %55 %54
+%59 = OpLoad %PS_INPUT %i_0
+OpStore %param %59
+%60 = OpFunctionCall %PS_OUTPUT %_MainPs_struct_PS_INPUT_vf21_ %param
+%61 = OpCompositeExtract %v4float %60 0
+OpStore %_entryPointOutput_vColor %61
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string func1_after =
+      R"(%MainPs = OpFunction %void None %18
+%42 = OpLabel
+%i_0 = OpVariable %_ptr_Function_PS_INPUT Function
+%param = OpVariable %_ptr_Function_PS_INPUT Function
+OpLine %5 21 0
+%43 = OpLoad %v2float %i_vTextureCoords
+%44 = OpAccessChain %_ptr_Function_v2float %i_0 %int_0
+OpStore %44 %43
+%45 = OpLoad %PS_INPUT %i_0
+OpStore %param %45
+%46 = OpFunctionCall %PS_OUTPUT %_MainPs_struct_PS_INPUT_vf21_ %param
+%47 = OpCompositeExtract %v4float %46 0
+OpStore %_entryPointOutput_vColor %47
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string func2_before =
+      R"(%_MainPs_struct_PS_INPUT_vf21_ = OpFunction %PS_OUTPUT None %13
+%i = OpFunctionParameter %_ptr_Function_PS_INPUT
+%16 = OpLabel
+%ps_output = OpVariable %_ptr_Function_PS_OUTPUT Function
+OpLine %1 24 0
+%31 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%32 = OpLoad %uint %31
+%34 = OpAccessChain %_ptr_UniformConstant_21 %g_tColor %32
+%35 = OpLoad %21 %34
+%39 = OpLoad %36 %g_sAniso
+%41 = OpSampledImage %40 %35 %39
+%43 = OpAccessChain %_ptr_Function_v2float %i %int_0
+%44 = OpLoad %v2float %43
+%45 = OpImageSampleImplicitLod %v4float %41 %44
+%47 = OpAccessChain %_ptr_Function_v4float %ps_output %int_0
+OpStore %47 %45
+OpLine %1 25 0
+%48 = OpLoad %PS_OUTPUT %ps_output
+OpReturnValue %48
+OpFunctionEnd
+)";
+
+  const std::string func2_after =
+      R"(%_MainPs_struct_PS_INPUT_vf21_ = OpFunction %PS_OUTPUT None %23
+%i = OpFunctionParameter %_ptr_Function_PS_INPUT
+%48 = OpLabel
+%ps_output = OpVariable %_ptr_Function_PS_OUTPUT Function
+OpLine %5 24 0
+%49 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%50 = OpLoad %uint %49
+%51 = OpAccessChain %_ptr_UniformConstant_27 %g_tColor %50
+%52 = OpLoad %27 %51
+%53 = OpLoad %35 %g_sAniso
+%54 = OpSampledImage %37 %52 %53
+%55 = OpAccessChain %_ptr_Function_v2float %i %int_0
+%56 = OpLoad %v2float %55
+%62 = OpULessThan %bool %50 %uint_128
+OpSelectionMerge %63 None
+OpBranchConditional %62 %64 %65
+%64 = OpLabel
+%66 = OpLoad %27 %51
+%67 = OpSampledImage %37 %66 %53
+%68 = OpImageSampleImplicitLod %v4float %67 %56
+OpBranch %63
+%65 = OpLabel
+%124 = OpFunctionCall %void %69 %uint_93 %uint_0 %50 %uint_128
+OpBranch %63
+%63 = OpLabel
+%126 = OpPhi %v4float %68 %64 %125 %65
+%58 = OpAccessChain %_ptr_Function_v4float %ps_output %int_0
+OpStore %58 %126
+OpLine %5 25 0
+%59 = OpLoad %PS_OUTPUT %ps_output
+OpReturnValue %59
+OpFunctionEnd
+)";
+
+  const std::string output_func =
+      R"(%69 = OpFunction %void None %70
+%71 = OpFunctionParameter %uint
+%72 = OpFunctionParameter %uint
+%73 = OpFunctionParameter %uint
+%74 = OpFunctionParameter %uint
+%75 = OpLabel
+%81 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_0
+%84 = OpAtomicIAdd %uint %81 %uint_4 %uint_0 %uint_9
+%85 = OpIAdd %uint %84 %uint_9
+%86 = OpArrayLength %uint %79 1
+%87 = OpULessThanEqual %bool %85 %86
+OpSelectionMerge %88 None
+OpBranchConditional %87 %89 %88
+%89 = OpLabel
+%90 = OpIAdd %uint %84 %uint_0
+%92 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %90
+OpStore %92 %uint_9
+%94 = OpIAdd %uint %84 %uint_1
+%95 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %94
+OpStore %95 %uint_23
+%97 = OpIAdd %uint %84 %uint_2
+%98 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %97
+OpStore %98 %71
+%100 = OpIAdd %uint %84 %uint_3
+%101 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %100
+OpStore %101 %uint_4
+%104 = OpLoad %v4float %gl_FragCoord
+%106 = OpBitcast %v4uint %104
+%107 = OpCompositeExtract %uint %106 0
+%108 = OpIAdd %uint %84 %uint_4
+%109 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %108
+OpStore %109 %107
+%110 = OpCompositeExtract %uint %106 1
+%112 = OpIAdd %uint %84 %uint_5
+%113 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %112
+OpStore %113 %110
+%115 = OpIAdd %uint %84 %uint_6
+%116 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %115
+OpStore %116 %72
+%118 = OpIAdd %uint %84 %uint_7
+%119 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %118
+OpStore %119 %73
+%121 = OpIAdd %uint %84 %uint_8
+%122 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %121
+OpStore %122 %74
+OpBranch %88
+%88 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<InstBindlessCheckPass>(
+      defs_before + func1_before + func2_before,
+      defs_after + func1_after + func2_after + output_func, true, true);
+}
+
 TEST_F(InstBindlessTest, RuntimeArray) {
 TEST_F(InstBindlessTest, RuntimeArray) {
   // This test verifies that the pass will correctly instrument shader
   // This test verifies that the pass will correctly instrument shader
   // with runtime descriptor array. This test was created by editing the
   // with runtime descriptor array. This test was created by editing the

+ 90 - 91
3rdparty/spirv-tools/test/opt/optimizer_test.cpp

@@ -242,112 +242,111 @@ TEST(Optimizer, WebGPUModeSetsCorrectPasses) {
     EXPECT_EQ(registered_passes[i], expected_passes[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
-)";
-
+struct WebGPUPassCase {
+  // Input SPIR-V
+  std::string input;
+  // Expected result SPIR-V
+  std::string expected;
+  // Specific pass under test, used for logging messages.
+  std::string pass;
+};
+
+using WebGPUPassTest = PassTest<::testing::TestWithParam<WebGPUPassCase>>;
+
+TEST_P(WebGPUPassTest, Ran) {
   SpirvTools tools(SPV_ENV_WEBGPU_0);
   SpirvTools tools(SPV_ENV_WEBGPU_0);
   std::vector<uint32_t> binary;
   std::vector<uint32_t> binary;
-  tools.Assemble(input, &binary);
+  tools.Assemble(GetParam().input, &binary);
 
 
   Optimizer opt(SPV_ENV_WEBGPU_0);
   Optimizer opt(SPV_ENV_WEBGPU_0);
   opt.RegisterWebGPUPasses();
   opt.RegisterWebGPUPasses();
 
 
   std::vector<uint32_t> optimized;
   std::vector<uint32_t> optimized;
-  ValidatorOptions validator_options;
+  class ValidatorOptions validator_options;
   ASSERT_TRUE(opt.Run(binary.data(), binary.size(), &optimized,
   ASSERT_TRUE(opt.Run(binary.data(), binary.size(), &optimized,
                       validator_options, true));
                       validator_options, true));
 
 
   std::string disassembly;
   std::string disassembly;
   tools.Disassemble(optimized.data(), optimized.size(), &disassembly);
   tools.Disassemble(optimized.data(), optimized.size(), &disassembly);
 
 
-  EXPECT_EQ(expected, disassembly);
+  EXPECT_EQ(GetParam().expected, disassembly)
+      << "Was expecting pass '" << GetParam().pass << "' to have been run.\n";
 }
 }
 
 
-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);
-}
+INSTANTIATE_TEST_SUITE_P(
+    WebGPU, WebGPUPassTest,
+    ::testing::ValuesIn(std::vector<WebGPUPassCase>{
+        // FlattenDecorations
+        {// input
+         "OpCapability Shader\n"
+         "OpCapability VulkanMemoryModelKHR\n"
+         "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+         "OpMemoryModel Logical VulkanKHR\n"
+         "OpEntryPoint Fragment %main \"main\" %hue %saturation %value\n"
+         "OpExecutionMode %main OriginUpperLeft\n"
+         "OpDecorate %group Flat\n"
+         "OpDecorate %group NoPerspective\n"
+         "%group = OpDecorationGroup\n"
+         "%void = OpTypeVoid\n"
+         "%void_fn = OpTypeFunction %void\n"
+         "%float = OpTypeFloat 32\n"
+         "%_ptr_Input_float = OpTypePointer Input %float\n"
+         "%hue = OpVariable %_ptr_Input_float Input\n"
+         "%saturation = OpVariable %_ptr_Input_float Input\n"
+         "%value = OpVariable %_ptr_Input_float Input\n"
+         "%main = OpFunction %void None %void_fn\n"
+         "%entry = OpLabel\n"
+         "OpReturn\n"
+         "OpFunctionEnd\n",
+         // expected
+         "OpCapability Shader\n"
+         "OpCapability VulkanMemoryModelKHR\n"
+         "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+         "OpMemoryModel Logical VulkanKHR\n"
+         "OpEntryPoint Fragment %1 \"main\" %2 %3 %4\n"
+         "OpExecutionMode %1 OriginUpperLeft\n"
+         "%void = OpTypeVoid\n"
+         "%7 = OpTypeFunction %void\n"
+         "%float = OpTypeFloat 32\n"
+         "%_ptr_Input_float = OpTypePointer Input %float\n"
+         "%2 = OpVariable %_ptr_Input_float Input\n"
+         "%3 = OpVariable %_ptr_Input_float Input\n"
+         "%4 = OpVariable %_ptr_Input_float Input\n"
+         "%1 = OpFunction %void None %7\n"
+         "%10 = OpLabel\n"
+         "OpReturn\n"
+         "OpFunctionEnd\n",
+         // pass
+         "flatten-decorations"},
+        // Strip Debug
+        {// input
+         "OpCapability Shader\n"
+         "OpCapability VulkanMemoryModelKHR\n"
+         "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+         "OpMemoryModel Logical VulkanKHR\n"
+         "OpEntryPoint Vertex %func \"shader\"\n"
+         "OpName %main \"main\"\n"
+         "OpName %void_fn \"void_fn\"\n"
+         "%void = OpTypeVoid\n"
+         "%void_f = OpTypeFunction %void\n"
+         "%func = OpFunction %void None %void_f\n"
+         "%label = OpLabel\n"
+         "OpReturn\n"
+         "OpFunctionEnd\n",
+         // expected
+         "OpCapability Shader\n"
+         "OpCapability VulkanMemoryModelKHR\n"
+         "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+         "OpMemoryModel Logical VulkanKHR\n"
+         "OpEntryPoint Vertex %1 \"shader\"\n"
+         "%void = OpTypeVoid\n"
+         "%5 = OpTypeFunction %void\n"
+         "%1 = OpFunction %void None %5\n"
+         "%6 = OpLabel\n"
+         "OpReturn\n"
+         "OpFunctionEnd\n",
+         // pass
+         "strip-debug"}}));
 
 
 }  // namespace
 }  // namespace
 }  // namespace opt
 }  // namespace opt

+ 138 - 0
3rdparty/spirv-tools/test/reduce/merge_blocks_test.cpp

@@ -508,6 +508,144 @@ TEST(MergeBlocksReductionPassTest, MergeWithOpPhi) {
   CheckEqual(env, after, context.get());
   CheckEqual(env, after, context.get());
 }
 }
 
 
+void MergeBlocksReductionPassTest_LoopReturn_Helper(bool reverse) {
+  // A merge block opportunity stores a block that can be merged with its
+  // predecessor.
+  // Given blocks A -> B -> C:
+  // This test demonstrates how merging B->C can invalidate
+  // the opportunity of merging A->B, and vice-versa. E.g.
+  // B->C are merged: B is now terminated with OpReturn.
+  // A->B can now no longer be merged because A is a loop header, which
+  // cannot be terminated with OpReturn.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantFalse %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel                   ; A (loop header)
+               OpLoopMerge %13 %12 None
+               OpBranch %11
+         %12 = OpLabel                   ; (unreachable continue block)
+               OpBranch %10
+         %11 = OpLabel                   ; B
+               OpBranch %15
+         %15 = OpLabel                   ; C
+               OpReturn
+         %13 = OpLabel                   ; (unreachable merge block)
+               OpReturn
+               OpFunctionEnd
+  )";
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, shader, kReduceAssembleOption);
+  ASSERT_NE(context.get(), nullptr);
+  auto opportunities =
+      MergeBlocksReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+
+  // A->B and B->C
+  ASSERT_EQ(opportunities.size(), 2);
+
+  // Test applying opportunities in both orders.
+  if (reverse) {
+    std::reverse(opportunities.begin(), opportunities.end());
+  }
+
+  size_t num_applied = 0;
+  for (auto& ri : opportunities) {
+    if (ri->PreconditionHolds()) {
+      ri->TryToApply();
+      ++num_applied;
+    }
+  }
+
+  // Only 1 opportunity can be applied, as both disable each other.
+  ASSERT_EQ(num_applied, 1);
+
+  std::string after = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantFalse %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel                   ; A-B (loop header)
+               OpLoopMerge %13 %12 None
+               OpBranch %15
+         %12 = OpLabel                   ; (unreachable continue block)
+               OpBranch %10
+         %15 = OpLabel                   ; C
+               OpReturn
+         %13 = OpLabel                   ; (unreachable merge block)
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  // The only difference is the labels.
+  std::string after_reversed = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantFalse %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel                   ; A (loop header)
+               OpLoopMerge %13 %12 None
+               OpBranch %11
+         %12 = OpLabel                   ; (unreachable continue block)
+               OpBranch %10
+         %11 = OpLabel                   ; B-C
+               OpReturn
+         %13 = OpLabel                   ; (unreachable merge block)
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, reverse ? after_reversed : after, context.get());
+}
+
+TEST(MergeBlocksReductionPassTest, LoopReturn) {
+  MergeBlocksReductionPassTest_LoopReturn_Helper(false);
+}
+
+TEST(MergeBlocksReductionPassTest, LoopReturnReverse) {
+  MergeBlocksReductionPassTest_LoopReturn_Helper(true);
+}
+
 }  // namespace
 }  // namespace
 }  // namespace reduce
 }  // namespace reduce
 }  // namespace spvtools
 }  // namespace spvtools