Add pass to remove dead members. (#2379)

Add a pass that looks for members of structs whose values do not affects
the output of the shader. Those members are then removed and just
treated like padding in the struct.
diff --git a/Android.mk b/Android.mk
index 986f39c..8f4f5fe 100644
--- a/Android.mk
+++ b/Android.mk
@@ -99,6 +99,7 @@
 		source/opt/eliminate_dead_constant_pass.cpp \
 		source/opt/eliminate_dead_functions_pass.cpp \
 		source/opt/eliminate_dead_functions_util.cpp \
+		source/opt/eliminate_dead_members_pass.cpp \
 		source/opt/feature_manager.cpp \
 		source/opt/flatten_decoration_pass.cpp \
 		source/opt/fold.cpp \
diff --git a/BUILD.gn b/BUILD.gn
index 82ce2df..2d63ca1 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -498,6 +498,8 @@
     "source/opt/eliminate_dead_functions_pass.h",
     "source/opt/eliminate_dead_functions_util.cpp",
     "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.h",
     "source/opt/flatten_decoration_pass.cpp",
diff --git a/include/spirv-tools/optimizer.hpp b/include/spirv-tools/optimizer.hpp
index a496f9d..e2d5e46 100644
--- a/include/spirv-tools/optimizer.hpp
+++ b/include/spirv-tools/optimizer.hpp
@@ -226,6 +226,11 @@
 // functions are not needed because they will never be called.
 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
 // to the default values in the form of string.
 // A set-spec-constant-default-value pass sets the default values for the
diff --git a/source/opt/CMakeLists.txt b/source/opt/CMakeLists.txt
index e1c26f0..f2ffcca 100644
--- a/source/opt/CMakeLists.txt
+++ b/source/opt/CMakeLists.txt
@@ -38,6 +38,7 @@
   eliminate_dead_constant_pass.h
   eliminate_dead_functions_pass.h
   eliminate_dead_functions_util.h
+  eliminate_dead_members_pass.h
   feature_manager.h
   flatten_decoration_pass.h
   fold.h
@@ -133,6 +134,7 @@
   eliminate_dead_constant_pass.cpp
   eliminate_dead_functions_pass.cpp
   eliminate_dead_functions_util.cpp
+  eliminate_dead_members_pass.cpp
   feature_manager.cpp
   flatten_decoration_pass.cpp
   fold.cpp
diff --git a/source/opt/eliminate_dead_members_pass.cpp b/source/opt/eliminate_dead_members_pass.cpp
new file mode 100644
index 0000000..c3931f7
--- /dev/null
+++ b/source/opt/eliminate_dead_members_pass.cpp
@@ -0,0 +1,619 @@
+// 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:
+      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) {
+  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
diff --git a/source/opt/eliminate_dead_members_pass.h b/source/opt/eliminate_dead_members_pass.h
new file mode 100644
index 0000000..8729f51
--- /dev/null
+++ b/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_
diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp
index 9d3cad7..344f3dc 100644
--- a/source/opt/optimizer.cpp
+++ b/source/opt/optimizer.cpp
@@ -332,6 +332,8 @@
     RegisterPass(CreateDeadInsertElimPass());
   } else if (pass_name == "eliminate-dead-variables") {
     RegisterPass(CreateDeadVariableEliminationPass());
+  } else if (pass_name == "eliminate-dead-members") {
+    RegisterPass(CreateEliminateDeadMembersPass());
   } else if (pass_name == "fold-spec-const-op-composite") {
     RegisterPass(CreateFoldSpecConstantOpAndCompositePass());
   } else if (pass_name == "loop-unswitch") {
@@ -542,6 +544,11 @@
       MakeUnique<opt::EliminateDeadFunctionsPass>());
 }
 
+Optimizer::PassToken CreateEliminateDeadMembersPass() {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::EliminateDeadMembersPass>());
+}
+
 Optimizer::PassToken CreateSetSpecConstantDefaultValuePass(
     const std::unordered_map<uint32_t, std::string>& id_value_map) {
   return MakeUnique<Optimizer::PassToken::Impl>(
diff --git a/source/opt/passes.h b/source/opt/passes.h
index f7b675e..6f8081c 100644
--- a/source/opt/passes.h
+++ b/source/opt/passes.h
@@ -31,6 +31,7 @@
 #include "source/opt/dead_variable_elimination.h"
 #include "source/opt/eliminate_dead_constant_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/fold_spec_constant_op_and_composite_pass.h"
 #include "source/opt/freeze_spec_constant_value_pass.h"
diff --git a/test/opt/CMakeLists.txt b/test/opt/CMakeLists.txt
index 6315385..c8deb39 100644
--- a/test/opt/CMakeLists.txt
+++ b/test/opt/CMakeLists.txt
@@ -34,6 +34,7 @@
        def_use_test.cpp
        eliminate_dead_const_test.cpp
        eliminate_dead_functions_test.cpp
+       eliminate_dead_member_test.cpp
        feature_manager_test.cpp
        flatten_decoration_test.cpp
        fold_spec_const_op_composite_test.cpp
diff --git a/test/opt/eliminate_dead_member_test.cpp b/test/opt/eliminate_dead_member_test.cpp
new file mode 100644
index 0000000..501dc72
--- /dev/null
+++ b/test/opt/eliminate_dead_member_test.cpp
@@ -0,0 +1,1005 @@
+// 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);
+}
+
+}  // namespace