// 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, uint32_t target_function) const { if (target_function) { // Removing an unused struct member is a global change, as struct types are // global. We thus do not consider such opportunities if we are targeting // a specific function. return {}; } 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() != spv::Op::OpTypeStruct) { 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 spv::Op::OpMemberName: 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 spv::Op::OpAccessChain: case spv::Op::OpInBoundsAccessChain: { 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 spv::Op::OpPtrAccessChain: case spv::Op::OpInBoundsPtrAccessChain: { 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 spv::Op::OpCompositeExtract: { 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 spv::Op::OpCompositeInsert: { 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 unused, and we make a reduction // opportunity for each of them. By mapping each relevant member index to the // structs in which it is unused, 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 spv::Op::OpTypeArray: case spv::Op::OpTypeMatrix: case spv::Op::OpTypeRuntimeArray: case spv::Op::OpTypeVector: next_type = type_inst->GetSingleWordInOperand(0); break; case spv::Op::OpTypeStruct: { 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