spirv-reduce: Remove unused struct members (#3329)
Adds a reduction pass to remove unused members from structs.
diff --git a/source/reduce/CMakeLists.txt b/source/reduce/CMakeLists.txt
index b173ac0..d945bd2 100644
--- a/source/reduce/CMakeLists.txt
+++ b/source/reduce/CMakeLists.txt
@@ -31,7 +31,9 @@
remove_instruction_reduction_opportunity.h
remove_selection_reduction_opportunity.h
remove_selection_reduction_opportunity_finder.h
+ remove_struct_member_reduction_opportunity.h
remove_unused_instruction_reduction_opportunity_finder.h
+ remove_unused_struct_member_reduction_opportunity_finder.h
structured_loop_to_selection_reduction_opportunity.h
structured_loop_to_selection_reduction_opportunity_finder.h
conditional_branch_to_simple_conditional_branch_opportunity_finder.h
@@ -57,7 +59,9 @@
remove_instruction_reduction_opportunity.cpp
remove_selection_reduction_opportunity.cpp
remove_selection_reduction_opportunity_finder.cpp
+ remove_struct_member_reduction_opportunity.cpp
remove_unused_instruction_reduction_opportunity_finder.cpp
+ remove_unused_struct_member_reduction_opportunity_finder.cpp
structured_loop_to_selection_reduction_opportunity.cpp
structured_loop_to_selection_reduction_opportunity_finder.cpp
conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp
diff --git a/source/reduce/reducer.cpp b/source/reduce/reducer.cpp
index 0179382..092d409 100644
--- a/source/reduce/reducer.cpp
+++ b/source/reduce/reducer.cpp
@@ -26,6 +26,7 @@
#include "source/reduce/remove_function_reduction_opportunity_finder.h"
#include "source/reduce/remove_selection_reduction_opportunity_finder.h"
#include "source/reduce/remove_unused_instruction_reduction_opportunity_finder.h"
+#include "source/reduce/remove_unused_struct_member_reduction_opportunity_finder.h"
#include "source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h"
#include "source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h"
#include "source/spirv_reducer_options.h"
@@ -126,6 +127,8 @@
ConditionalBranchToSimpleConditionalBranchOpportunityFinder>());
AddReductionPass(
spvtools::MakeUnique<SimpleConditionalBranchToBranchOpportunityFinder>());
+ AddReductionPass(spvtools::MakeUnique<
+ RemoveUnusedStructMemberReductionOpportunityFinder>());
// Cleanup passes.
diff --git a/source/reduce/remove_struct_member_reduction_opportunity.cpp b/source/reduce/remove_struct_member_reduction_opportunity.cpp
new file mode 100644
index 0000000..787c629
--- /dev/null
+++ b/source/reduce/remove_struct_member_reduction_opportunity.cpp
@@ -0,0 +1,208 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/remove_struct_member_reduction_opportunity.h"
+
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace reduce {
+
+bool RemoveStructMemberReductionOpportunity::PreconditionHolds() {
+ return struct_type_->NumInOperands() == original_number_of_members_;
+}
+
+void RemoveStructMemberReductionOpportunity::Apply() {
+ std::set<opt::Instruction*> decorations_to_kill;
+
+ // We need to remove decorations that target the removed struct member, and
+ // adapt decorations that target later struct members by decrementing the
+ // member identifier. We also need to adapt composite construction
+ // instructions so that no id is provided for the member being removed.
+ //
+ // To do this, we consider every use of the struct type.
+ struct_type_->context()->get_def_use_mgr()->ForEachUse(
+ struct_type_, [this, &decorations_to_kill](opt::Instruction* user,
+ uint32_t /*operand_index*/) {
+ switch (user->opcode()) {
+ case SpvOpCompositeConstruct:
+ case SpvOpConstantComposite:
+ // This use is constructing a composite of the struct type, so we
+ // must remove the id that was provided for the member we are
+ // removing.
+ user->RemoveInOperand(member_index_);
+ break;
+ case SpvOpMemberDecorate:
+ // This use is decorating a member of the struct.
+ if (user->GetSingleWordInOperand(1) == member_index_) {
+ // The member we are removing is being decorated, so we record
+ // that we need to get rid of the decoration.
+ decorations_to_kill.insert(user);
+ } else if (user->GetSingleWordInOperand(1) > member_index_) {
+ // A member beyond the one we are removing is being decorated, so
+ // we adjust the index that identifies the member.
+ user->SetInOperand(1, {user->GetSingleWordInOperand(1) - 1});
+ }
+ break;
+ default:
+ break;
+ }
+ });
+
+ // Get rid of all the decorations that were found to target the member being
+ // removed.
+ for (auto decoration_to_kill : decorations_to_kill) {
+ decoration_to_kill->context()->KillInst(decoration_to_kill);
+ }
+
+ // We now look through all instructions that access composites via sequences
+ // of indices. Every time we find an index into the struct whose member is
+ // being removed, and if the member being accessed comes after the member
+ // being removed, we need to adjust the index accordingly.
+ //
+ // We go through every relevant instruction in every block of every function,
+ // and invoke a helper to adjust it.
+ auto context = struct_type_->context();
+ for (auto& function : *context->module()) {
+ for (auto& block : function) {
+ for (auto& inst : block) {
+ switch (inst.opcode()) {
+ case SpvOpAccessChain:
+ case SpvOpInBoundsAccessChain: {
+ // These access chain instructions take sequences of ids for
+ // indexing, starting from input operand 1.
+ auto composite_type_id =
+ context->get_def_use_mgr()
+ ->GetDef(context->get_def_use_mgr()
+ ->GetDef(inst.GetSingleWordInOperand(0))
+ ->type_id())
+ ->GetSingleWordInOperand(1);
+ AdjustAccessedIndices(composite_type_id, 1, false, context, &inst);
+ } break;
+ case SpvOpPtrAccessChain:
+ case SpvOpInBoundsPtrAccessChain: {
+ // These access chain instructions take sequences of ids for
+ // indexing, starting from input operand 2.
+ auto composite_type_id =
+ context->get_def_use_mgr()
+ ->GetDef(context->get_def_use_mgr()
+ ->GetDef(inst.GetSingleWordInOperand(1))
+ ->type_id())
+ ->GetSingleWordInOperand(1);
+ AdjustAccessedIndices(composite_type_id, 2, false, context, &inst);
+ } break;
+ case SpvOpCompositeExtract: {
+ // OpCompositeExtract uses literals for indexing, starting at input
+ // operand 1.
+ auto composite_type_id =
+ context->get_def_use_mgr()
+ ->GetDef(inst.GetSingleWordInOperand(0))
+ ->type_id();
+ AdjustAccessedIndices(composite_type_id, 1, true, context, &inst);
+ } break;
+ case SpvOpCompositeInsert: {
+ // OpCompositeInsert uses literals for indexing, starting at input
+ // operand 2.
+ auto composite_type_id =
+ context->get_def_use_mgr()
+ ->GetDef(inst.GetSingleWordInOperand(1))
+ ->type_id();
+ AdjustAccessedIndices(composite_type_id, 2, true, context, &inst);
+ } break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+
+ // Remove the member from the struct type.
+ struct_type_->RemoveInOperand(member_index_);
+}
+
+void RemoveStructMemberReductionOpportunity::AdjustAccessedIndices(
+ uint32_t composite_type_id, uint32_t first_index_input_operand,
+ bool literal_indices, opt::IRContext* context,
+ opt::Instruction* composite_access_instruction) const {
+ // Walk the series of types that are encountered by following the
+ // instruction's sequence of indices. For all types except structs, this is
+ // routine: the type of the composite dictates what the next type will be
+ // regardless of the specific index value.
+ uint32_t next_type = composite_type_id;
+ for (uint32_t i = first_index_input_operand;
+ i < composite_access_instruction->NumInOperands(); i++) {
+ auto type_inst = context->get_def_use_mgr()->GetDef(next_type);
+ switch (type_inst->opcode()) {
+ case SpvOpTypeArray:
+ case SpvOpTypeMatrix:
+ case SpvOpTypeRuntimeArray:
+ case SpvOpTypeVector:
+ next_type = type_inst->GetSingleWordInOperand(0);
+ break;
+ case SpvOpTypeStruct: {
+ // Struct types are special becuase (a) we may need to adjust the index
+ // being used, if the struct type is the one from which we are removing
+ // a member, and (b) the type encountered by following the current index
+ // is dependent on the value of the index.
+
+ // Work out the member being accessed. If literal indexing is used this
+ // is simple; otherwise we need to look up the id of the constant
+ // instruction being used as an index and get the value of the constant.
+ uint32_t index_operand =
+ composite_access_instruction->GetSingleWordInOperand(i);
+ uint32_t member = literal_indices ? index_operand
+ : context->get_def_use_mgr()
+ ->GetDef(index_operand)
+ ->GetSingleWordInOperand(0);
+
+ // The next type we will consider is obtained by looking up the struct
+ // type at |member|.
+ next_type = type_inst->GetSingleWordInOperand(member);
+
+ if (type_inst == struct_type_ && member > member_index_) {
+ // The struct type is the struct from which we are removing a member,
+ // and the member being accessed is beyond the member we are removing.
+ // We thus need to decrement the index by 1.
+ uint32_t new_in_operand;
+ if (literal_indices) {
+ // With literal indexing this is straightforward.
+ new_in_operand = member - 1;
+ } else {
+ // With id-based indexing this is more tricky: we need to find or
+ // create a constant instruction whose value is one less than
+ // |member|, and use the id of this constant as the replacement
+ // input operand.
+ auto constant_inst =
+ context->get_def_use_mgr()->GetDef(index_operand);
+ auto int_type = context->get_type_mgr()
+ ->GetType(constant_inst->type_id())
+ ->AsInteger();
+ auto new_index_constant =
+ opt::analysis::IntConstant(int_type, {member - 1});
+ new_in_operand = context->get_constant_mgr()
+ ->GetDefiningInstruction(&new_index_constant)
+ ->result_id();
+ }
+ composite_access_instruction->SetInOperand(i, {new_in_operand});
+ }
+ } break;
+ default:
+ assert(0 && "Unknown composite type.");
+ break;
+ }
+ }
+}
+
+} // namespace reduce
+} // namespace spvtools
diff --git a/source/reduce/remove_struct_member_reduction_opportunity.h b/source/reduce/remove_struct_member_reduction_opportunity.h
new file mode 100644
index 0000000..899e5ea
--- /dev/null
+++ b/source/reduce/remove_struct_member_reduction_opportunity.h
@@ -0,0 +1,84 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_REMOVE_STRUCT_MEMBER_REDUCTION_OPPORTUNITY_H_
+#define SOURCE_REDUCE_REMOVE_STRUCT_MEMBER_REDUCTION_OPPORTUNITY_H_
+
+#include "source/reduce/reduction_opportunity.h"
+
+#include "source/opt/instruction.h"
+
+namespace spvtools {
+namespace reduce {
+
+// An opportunity for removing a member from a struct type, adjusting all uses
+// of the struct accordingly.
+class RemoveStructMemberReductionOpportunity : public ReductionOpportunity {
+ public:
+ // Constructs a reduction opportunity from the struct type |struct_type|, for
+ // removal of member |member_index|.
+ RemoveStructMemberReductionOpportunity(opt::Instruction* struct_type,
+ uint32_t member_index)
+ : struct_type_(struct_type),
+ member_index_(member_index),
+ original_number_of_members_(struct_type->NumInOperands()) {}
+
+ // Opportunities to remove fields from a common struct type mutually
+ // invalidate each other. We guard against this by requiring that the struct
+ // still has the number of members it had when the opportunity was created.
+ bool PreconditionHolds() override;
+
+ protected:
+ void Apply() override;
+
+ private:
+ // |composite_access_instruction| is an instruction that accesses a composite
+ // id using either a series of literal indices (e.g. in the case of
+ // OpCompositeInsert) or a series of index ids (e.g. in the case of
+ // OpAccessChain).
+ //
+ // This function adjusts the indices that are used by
+ // |composite_access_instruction| to that whenever an index is accessing a
+ // member of |struct_type_|, it is decremented if the member is beyond
+ // |member_index_|, to account for the removal of the |member_index_|-th
+ // member.
+ //
+ // |composite_type_id| is the id of the composite type that the series of
+ // indices is to be applied to.
+ //
+ // |first_index_input_operand| specifies the first input operand that is an
+ // index.
+ //
+ // |literal_indices| specifies whether indices are given as literals (true),
+ // or as ids (false).
+ //
+ // If id-based indexing is used, this function will add a constant for
+ // |member_index_| - 1 to the module if needed.
+ void AdjustAccessedIndices(
+ uint32_t composite_type_id, uint32_t first_index_input_operand,
+ bool literal_indices, opt::IRContext* context,
+ opt::Instruction* composite_access_instruction) const;
+
+ // The struct type from which a member is to be removed.
+ opt::Instruction* struct_type_;
+
+ uint32_t member_index_;
+
+ uint32_t original_number_of_members_;
+};
+
+} // namespace reduce
+} // namespace spvtools
+
+#endif // SOURCE_REDUCE_REMOVE_STRUCT_MEMBER_REDUCTION_OPPORTUNITY_H_
diff --git a/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.cpp b/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.cpp
new file mode 100644
index 0000000..39ce47f
--- /dev/null
+++ b/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.cpp
@@ -0,0 +1,193 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/remove_unused_struct_member_reduction_opportunity_finder.h"
+
+#include <map>
+#include <set>
+
+#include "source/reduce/remove_struct_member_reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+std::vector<std::unique_ptr<ReductionOpportunity>>
+RemoveUnusedStructMemberReductionOpportunityFinder::GetAvailableOpportunities(
+ opt::IRContext* context) const {
+ std::vector<std::unique_ptr<ReductionOpportunity>> result;
+
+ // We track those struct members that are never accessed. We do this by
+ // associating a member index to all the structs that have this member index
+ // but do not use it. This representation is designed to allow reduction
+ // opportunities to be provided in a useful manner, so that opportunities
+ // associated with the same struct are unlikely to be adjacent.
+ std::map<uint32_t, std::set<opt::Instruction*>> unused_member_to_structs;
+
+ // Consider every struct type in the module.
+ for (auto& type_or_value : context->types_values()) {
+ if (type_or_value.opcode() != SpvOpTypeStruct) {
+ continue;
+ }
+
+ // Initially, we assume that *every* member of the struct is unused. We
+ // then refine this based on observed uses.
+ std::set<uint32_t> unused_members;
+ for (uint32_t i = 0; i < type_or_value.NumInOperands(); i++) {
+ unused_members.insert(i);
+ }
+
+ // A separate reduction pass deals with removal of names. If a struct
+ // member is still named, we treat it as being used.
+ context->get_def_use_mgr()->ForEachUse(
+ &type_or_value,
+ [&unused_members](opt::Instruction* user, uint32_t /*operand_index*/) {
+ switch (user->opcode()) {
+ case SpvOpMemberName:
+ unused_members.erase(user->GetSingleWordInOperand(1));
+ break;
+ default:
+ break;
+ }
+ });
+
+ for (uint32_t member : unused_members) {
+ if (!unused_member_to_structs.count(member)) {
+ unused_member_to_structs.insert(
+ {member, std::set<opt::Instruction*>()});
+ }
+ unused_member_to_structs.at(member).insert(&type_or_value);
+ }
+ }
+
+ // We now go through every instruction that might index into a struct, and
+ // refine our tracking of which struct members are used based on the struct
+ // indexing we observe. We cannot just go through all uses of a struct type
+ // because the type is not necessarily even referenced, e.g. when walking
+ // arrays of structs.
+ for (auto& function : *context->module()) {
+ for (auto& block : function) {
+ for (auto& inst : block) {
+ switch (inst.opcode()) {
+ // For each indexing operation we observe, we invoke a helper to
+ // remove from our map those struct indices that are found to be used.
+ // The way the helper is invoked depends on whether the instruction
+ // uses literal or id indices, and the offset into the instruction's
+ // input operands from which index operands are provided.
+ case SpvOpAccessChain:
+ case SpvOpInBoundsAccessChain: {
+ auto composite_type_id =
+ context->get_def_use_mgr()
+ ->GetDef(context->get_def_use_mgr()
+ ->GetDef(inst.GetSingleWordInOperand(0))
+ ->type_id())
+ ->GetSingleWordInOperand(1);
+ MarkAccessedMembersAsUsed(context, composite_type_id, 1, false,
+ inst, &unused_member_to_structs);
+ } break;
+ case SpvOpPtrAccessChain:
+ case SpvOpInBoundsPtrAccessChain: {
+ auto composite_type_id =
+ context->get_def_use_mgr()
+ ->GetDef(context->get_def_use_mgr()
+ ->GetDef(inst.GetSingleWordInOperand(1))
+ ->type_id())
+ ->GetSingleWordInOperand(1);
+ MarkAccessedMembersAsUsed(context, composite_type_id, 2, false,
+ inst, &unused_member_to_structs);
+ } break;
+ case SpvOpCompositeExtract: {
+ auto composite_type_id =
+ context->get_def_use_mgr()
+ ->GetDef(inst.GetSingleWordInOperand(0))
+ ->type_id();
+ MarkAccessedMembersAsUsed(context, composite_type_id, 1, true, inst,
+ &unused_member_to_structs);
+ } break;
+ case SpvOpCompositeInsert: {
+ auto composite_type_id =
+ context->get_def_use_mgr()
+ ->GetDef(inst.GetSingleWordInOperand(1))
+ ->type_id();
+ MarkAccessedMembersAsUsed(context, composite_type_id, 2, true, inst,
+ &unused_member_to_structs);
+ } break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+
+ // We now know those struct indices that are unsed, and we make a reduction
+ // opportunity for each of them. By mapping each relevant member index to the
+ // structs in which it is unsed, we will group all opportunities to remove
+ // member k of a struct (for some k) together. This reduces the likelihood
+ // that opportunities to remove members from the same struct will be adjacent,
+ // which is good because such opportunities mutually disable one another.
+ for (auto& entry : unused_member_to_structs) {
+ for (auto struct_type : entry.second) {
+ result.push_back(MakeUnique<RemoveStructMemberReductionOpportunity>(
+ struct_type, entry.first));
+ }
+ }
+ return result;
+}
+
+void RemoveUnusedStructMemberReductionOpportunityFinder::
+ MarkAccessedMembersAsUsed(
+ opt::IRContext* context, uint32_t composite_type_id,
+ uint32_t first_index_in_operand, bool literal_indices,
+ const opt::Instruction& composite_access_instruction,
+ std::map<uint32_t, std::set<opt::Instruction*>>*
+ unused_member_to_structs) const {
+ uint32_t next_type = composite_type_id;
+ for (uint32_t i = first_index_in_operand;
+ i < composite_access_instruction.NumInOperands(); i++) {
+ auto type_inst = context->get_def_use_mgr()->GetDef(next_type);
+ switch (type_inst->opcode()) {
+ case SpvOpTypeArray:
+ case SpvOpTypeMatrix:
+ case SpvOpTypeRuntimeArray:
+ case SpvOpTypeVector:
+ next_type = type_inst->GetSingleWordInOperand(0);
+ break;
+ case SpvOpTypeStruct: {
+ uint32_t index_operand =
+ composite_access_instruction.GetSingleWordInOperand(i);
+ uint32_t member = literal_indices ? index_operand
+ : context->get_def_use_mgr()
+ ->GetDef(index_operand)
+ ->GetSingleWordInOperand(0);
+ // Remove the struct type from the struct types associated with this
+ // member index, but only if a set of struct types is known to be
+ // associated with this member index.
+ if (unused_member_to_structs->count(member)) {
+ unused_member_to_structs->at(member).erase(type_inst);
+ }
+ next_type = type_inst->GetSingleWordInOperand(member);
+ } break;
+ default:
+ assert(0 && "Unknown composite type.");
+ break;
+ }
+ }
+}
+
+std::string RemoveUnusedStructMemberReductionOpportunityFinder::GetName()
+ const {
+ return "RemoveUnusedStructMemberReductionOpportunityFinder";
+}
+
+} // namespace reduce
+} // namespace spvtools
diff --git a/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.h b/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.h
new file mode 100644
index 0000000..13f4017
--- /dev/null
+++ b/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.h
@@ -0,0 +1,61 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_REMOVE_UNUSED_STRUCT_MEMBER_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_REMOVE_UNUSED_STRUCT_MEMBER_REDUCTION_OPPORTUNITY_FINDER_H_
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder for opportunities to remove struct members that are not explicitly
+// used by extract, insert or access chain instructions.
+class RemoveUnusedStructMemberReductionOpportunityFinder
+ : public ReductionOpportunityFinder {
+ public:
+ RemoveUnusedStructMemberReductionOpportunityFinder() = default;
+
+ ~RemoveUnusedStructMemberReductionOpportunityFinder() override = default;
+
+ std::string GetName() const final;
+
+ std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+ opt::IRContext* context) const final;
+
+ private:
+ // A helper method to update |unused_members_to_structs| by removing from it
+ // all struct member accesses that take place in
+ // |composite_access_instruction|.
+ //
+ // |composite_type_id| is the type of the root object indexed into by the
+ // instruction.
+ //
+ // |first_index_in_operand| provides indicates where in the input operands the
+ // sequence of indices begins.
+ //
+ // |literal_indices| indicates whether indices are literals (true) or ids
+ // (false).
+ void MarkAccessedMembersAsUsed(
+ opt::IRContext* context, uint32_t composite_type_id,
+ uint32_t first_index_in_operand, bool literal_indices,
+ const opt::Instruction& composite_access_instruction,
+ std::map<uint32_t, std::set<opt::Instruction*>>* unused_member_to_structs)
+ const;
+};
+
+} // namespace reduce
+} // namespace spvtools
+
+#endif // SOURCE_REDUCE_REMOVE_UNUSED_STRUCT_MEMBER_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/test/reduce/CMakeLists.txt b/test/reduce/CMakeLists.txt
index fc8aee1..652f0ab 100644
--- a/test/reduce/CMakeLists.txt
+++ b/test/reduce/CMakeLists.txt
@@ -25,6 +25,7 @@
remove_function_test.cpp
remove_selection_test.cpp
remove_unused_instruction_test.cpp
+ remove_unused_struct_member_test.cpp
structured_loop_to_selection_test.cpp
validation_during_reduction_test.cpp
conditional_branch_to_simple_conditional_branch_test.cpp
diff --git a/test/reduce/remove_unused_struct_member_test.cpp b/test/reduce/remove_unused_struct_member_test.cpp
new file mode 100644
index 0000000..402ef2d
--- /dev/null
+++ b/test/reduce/remove_unused_struct_member_test.cpp
@@ -0,0 +1,238 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/remove_unused_struct_member_reduction_opportunity_finder.h"
+
+#include "source/opt/build_module.h"
+#include "source/reduce/reduction_opportunity.h"
+#include "test/reduce/reduce_test_util.h"
+
+namespace spvtools {
+namespace reduce {
+namespace {
+
+TEST(RemoveUnusedStructMemberTest, RemoveOneMember) {
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypeStruct %6 %6
+ %8 = OpTypePointer Function %7
+ %50 = OpConstant %6 0
+ %10 = OpConstant %6 1
+ %11 = OpConstant %6 2
+ %12 = OpConstantComposite %7 %10 %11
+ %13 = OpConstant %6 4
+ %14 = OpTypePointer Function %6
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %9 = OpVariable %8 Function
+ OpStore %9 %12
+ %15 = OpAccessChain %14 %9 %10
+ %22 = OpInBoundsAccessChain %14 %9 %10
+ %20 = OpLoad %7 %9
+ %21 = OpCompositeExtract %6 %20 1
+ %23 = OpCompositeInsert %7 %10 %20 1
+ OpStore %15 %13
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_3;
+ const auto consumer = nullptr;
+ const auto context =
+ BuildModule(env, consumer, shader, kReduceAssembleOption);
+
+ auto ops = RemoveUnusedStructMemberReductionOpportunityFinder()
+ .GetAvailableOpportunities(context.get());
+ ASSERT_EQ(1, ops.size());
+ ASSERT_TRUE(ops[0]->PreconditionHolds());
+ ops[0]->TryToApply();
+
+ CheckValid(env, context.get());
+
+ std::string expected = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypeStruct %6
+ %8 = OpTypePointer Function %7
+ %50 = OpConstant %6 0
+ %10 = OpConstant %6 1
+ %11 = OpConstant %6 2
+ %12 = OpConstantComposite %7 %11
+ %13 = OpConstant %6 4
+ %14 = OpTypePointer Function %6
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %9 = OpVariable %8 Function
+ OpStore %9 %12
+ %15 = OpAccessChain %14 %9 %50
+ %22 = OpInBoundsAccessChain %14 %9 %50
+ %20 = OpLoad %7 %9
+ %21 = OpCompositeExtract %6 %20 0
+ %23 = OpCompositeInsert %7 %10 %20 0
+ OpStore %15 %13
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ CheckEqual(env, expected, context.get());
+}
+
+TEST(RemoveUnusedStructMemberTest, RemoveUniformBufferMember) {
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpMemberDecorate %10 0 Offset 0
+ OpMemberDecorate %10 1 Offset 4
+ OpDecorate %10 Block
+ OpDecorate %12 DescriptorSet 0
+ OpDecorate %12 Binding 0
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeFloat 32
+ %7 = OpTypePointer Function %6
+ %9 = OpTypeInt 32 1
+ %10 = OpTypeStruct %9 %6
+ %11 = OpTypePointer Uniform %10
+ %12 = OpVariable %11 Uniform
+ %13 = OpConstant %9 1
+ %20 = OpConstant %9 0
+ %14 = OpTypePointer Uniform %6
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %15 = OpAccessChain %14 %12 %13
+ %16 = OpLoad %6 %15
+ OpStore %8 %16
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_3;
+ const auto consumer = nullptr;
+ const auto context =
+ BuildModule(env, consumer, shader, kReduceAssembleOption);
+
+ auto ops = RemoveUnusedStructMemberReductionOpportunityFinder()
+ .GetAvailableOpportunities(context.get());
+ ASSERT_EQ(1, ops.size());
+ ASSERT_TRUE(ops[0]->PreconditionHolds());
+ ops[0]->TryToApply();
+
+ CheckValid(env, context.get());
+
+ std::string expected = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpMemberDecorate %10 0 Offset 4
+ OpDecorate %10 Block
+ OpDecorate %12 DescriptorSet 0
+ OpDecorate %12 Binding 0
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeFloat 32
+ %7 = OpTypePointer Function %6
+ %9 = OpTypeInt 32 1
+ %10 = OpTypeStruct %6
+ %11 = OpTypePointer Uniform %10
+ %12 = OpVariable %11 Uniform
+ %13 = OpConstant %9 1
+ %20 = OpConstant %9 0
+ %14 = OpTypePointer Uniform %6
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %15 = OpAccessChain %14 %12 %20
+ %16 = OpLoad %6 %15
+ OpStore %8 %16
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ CheckEqual(env, expected, context.get());
+}
+
+TEST(RemoveUnusedStructMemberTest, DoNotRemoveNamedMemberRemoveOneMember) {
+ // This illustrates that naming a member is enough to prevent its removal.
+ // Removal of names is done by a different pass.
+
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpMemberName %7 0 "someName"
+ OpMemberName %7 1 "someOtherName"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypeStruct %6 %6
+ %8 = OpTypePointer Function %7
+ %50 = OpConstant %6 0
+ %10 = OpConstant %6 1
+ %11 = OpConstant %6 2
+ %12 = OpConstantComposite %7 %10 %11
+ %13 = OpConstant %6 4
+ %14 = OpTypePointer Function %6
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %9 = OpVariable %8 Function
+ OpStore %9 %12
+ %15 = OpAccessChain %14 %9 %10
+ %22 = OpInBoundsAccessChain %14 %9 %10
+ %20 = OpLoad %7 %9
+ %21 = OpCompositeExtract %6 %20 1
+ %23 = OpCompositeInsert %7 %10 %20 1
+ OpStore %15 %13
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_3;
+ const auto consumer = nullptr;
+ const auto context =
+ BuildModule(env, consumer, shader, kReduceAssembleOption);
+
+ auto ops = RemoveUnusedStructMemberReductionOpportunityFinder()
+ .GetAvailableOpportunities(context.get());
+ ASSERT_EQ(0, ops.size());
+}
+
+} // namespace
+} // namespace reduce
+} // namespace spvtools