From 3118276370489ccf2ae496f43170fa042c813f81 Mon Sep 17 00:00:00 2001 From: Alastair Donaldson Date: Thu, 14 May 2020 15:36:38 +0100 Subject: [PATCH] spirv-reduce: Remove unused struct members (#3329) Adds a reduction pass to remove unused members from structs. --- source/reduce/CMakeLists.txt | 4 + source/reduce/reducer.cpp | 3 + ...ve_struct_member_reduction_opportunity.cpp | 208 +++++++++++++++ ...move_struct_member_reduction_opportunity.h | 84 +++++++ ...ct_member_reduction_opportunity_finder.cpp | 193 ++++++++++++++ ...ruct_member_reduction_opportunity_finder.h | 61 +++++ test/reduce/CMakeLists.txt | 1 + .../remove_unused_struct_member_test.cpp | 238 ++++++++++++++++++ 8 files changed, 792 insertions(+) create mode 100644 source/reduce/remove_struct_member_reduction_opportunity.cpp create mode 100644 source/reduce/remove_struct_member_reduction_opportunity.h create mode 100644 source/reduce/remove_unused_struct_member_reduction_opportunity_finder.cpp create mode 100644 source/reduce/remove_unused_struct_member_reduction_opportunity_finder.h create mode 100644 test/reduce/remove_unused_struct_member_test.cpp diff --git a/source/reduce/CMakeLists.txt b/source/reduce/CMakeLists.txt index b173ac0a..d945bd20 100644 --- a/source/reduce/CMakeLists.txt +++ b/source/reduce/CMakeLists.txt @@ -31,7 +31,9 @@ set(SPIRV_TOOLS_REDUCE_SOURCES 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 @@ set(SPIRV_TOOLS_REDUCE_SOURCES 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 01793829..092d4090 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 @@ void Reducer::AddDefaultReductionPasses() { ConditionalBranchToSimpleConditionalBranchOpportunityFinder>()); AddReductionPass( spvtools::MakeUnique()); + 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 00000000..787c629b --- /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 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 00000000..899e5ea7 --- /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 00000000..39ce47f3 --- /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 +#include + +#include "source/reduce/remove_struct_member_reduction_opportunity.h" + +namespace spvtools { +namespace reduce { + +std::vector> +RemoveUnusedStructMemberReductionOpportunityFinder::GetAvailableOpportunities( + opt::IRContext* context) const { + std::vector> 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> 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 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()}); + } + 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( + 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>* + 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 00000000..13f40172 --- /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> 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>* 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 fc8aee12..652f0ab5 100644 --- a/test/reduce/CMakeLists.txt +++ b/test/reduce/CMakeLists.txt @@ -25,6 +25,7 @@ add_spvtools_unittest(TARGET reduce 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 00000000..402ef2d8 --- /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