// 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. #include "source/opt/vector_dce.h" #include namespace spvtools { namespace opt { namespace { const uint32_t kExtractCompositeIdInIdx = 0; const uint32_t kInsertObjectIdInIdx = 0; const uint32_t kInsertCompositeIdInIdx = 1; } // namespace Pass::Status VectorDCE::Process() { bool modified = false; for (Function& function : *get_module()) { modified |= VectorDCEFunction(&function); } return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange); } bool VectorDCE::VectorDCEFunction(Function* function) { LiveComponentMap live_components; FindLiveComponents(function, &live_components); return RewriteInstructions(function, live_components); } void VectorDCE::FindLiveComponents(Function* function, LiveComponentMap* live_components) { std::vector work_list; // Prime the work list. We will assume that any instruction that does // not result in a vector is live. // // Extending to structures and matrices is not as straight forward because of // the nesting. We cannot simply us a bit vector to keep track of which // components are live because of arbitrary nesting of structs. function->ForEachInst( [&work_list, this, live_components](Instruction* current_inst) { if (current_inst->IsOpenCL100DebugInstr()) { return; } if (!HasVectorOrScalarResult(current_inst) || !context()->IsCombinatorInstruction(current_inst)) { MarkUsesAsLive(current_inst, all_components_live_, live_components, &work_list); } }); // Process the work list propagating liveness. for (uint32_t i = 0; i < work_list.size(); i++) { WorkListItem current_item = work_list[i]; Instruction* current_inst = current_item.instruction; switch (current_inst->opcode()) { case SpvOpCompositeExtract: MarkExtractUseAsLive(current_inst, current_item.components, live_components, &work_list); break; case SpvOpCompositeInsert: MarkInsertUsesAsLive(current_item, live_components, &work_list); break; case SpvOpVectorShuffle: MarkVectorShuffleUsesAsLive(current_item, live_components, &work_list); break; case SpvOpCompositeConstruct: MarkCompositeContructUsesAsLive(current_item, live_components, &work_list); break; default: if (current_inst->IsScalarizable()) { MarkUsesAsLive(current_inst, current_item.components, live_components, &work_list); } else { MarkUsesAsLive(current_inst, all_components_live_, live_components, &work_list); } break; } } } void VectorDCE::MarkExtractUseAsLive(const Instruction* current_inst, const utils::BitVector& live_elements, LiveComponentMap* live_components, std::vector* work_list) { analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr(); uint32_t operand_id = current_inst->GetSingleWordInOperand(kExtractCompositeIdInIdx); Instruction* operand_inst = def_use_mgr->GetDef(operand_id); if (HasVectorOrScalarResult(operand_inst)) { WorkListItem new_item; new_item.instruction = operand_inst; if (current_inst->NumInOperands() < 2) { new_item.components = live_elements; } else { new_item.components.Set(current_inst->GetSingleWordInOperand(1)); } AddItemToWorkListIfNeeded(new_item, live_components, work_list); } } void VectorDCE::MarkInsertUsesAsLive( const VectorDCE::WorkListItem& current_item, LiveComponentMap* live_components, std::vector* work_list) { analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr(); if (current_item.instruction->NumInOperands() > 2) { uint32_t insert_position = current_item.instruction->GetSingleWordInOperand(2); // Add the elements of the composite object that are used. uint32_t operand_id = current_item.instruction->GetSingleWordInOperand( kInsertCompositeIdInIdx); Instruction* operand_inst = def_use_mgr->GetDef(operand_id); WorkListItem new_item; new_item.instruction = operand_inst; new_item.components = current_item.components; new_item.components.Clear(insert_position); AddItemToWorkListIfNeeded(new_item, live_components, work_list); // Add the element being inserted if it is used. if (current_item.components.Get(insert_position)) { uint32_t obj_operand_id = current_item.instruction->GetSingleWordInOperand( kInsertObjectIdInIdx); Instruction* obj_operand_inst = def_use_mgr->GetDef(obj_operand_id); WorkListItem new_item_for_obj; new_item_for_obj.instruction = obj_operand_inst; new_item_for_obj.components.Set(0); AddItemToWorkListIfNeeded(new_item_for_obj, live_components, work_list); } } else { // If there are no indices, then this is a copy of the object being // inserted. uint32_t object_id = current_item.instruction->GetSingleWordInOperand(kInsertObjectIdInIdx); Instruction* object_inst = def_use_mgr->GetDef(object_id); WorkListItem new_item; new_item.instruction = object_inst; new_item.components = current_item.components; AddItemToWorkListIfNeeded(new_item, live_components, work_list); } } void VectorDCE::MarkVectorShuffleUsesAsLive( const WorkListItem& current_item, VectorDCE::LiveComponentMap* live_components, std::vector* work_list) { analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr(); WorkListItem first_operand; first_operand.instruction = def_use_mgr->GetDef(current_item.instruction->GetSingleWordInOperand(0)); WorkListItem second_operand; second_operand.instruction = def_use_mgr->GetDef(current_item.instruction->GetSingleWordInOperand(1)); analysis::TypeManager* type_mgr = context()->get_type_mgr(); analysis::Vector* first_type = type_mgr->GetType(first_operand.instruction->type_id())->AsVector(); uint32_t size_of_first_operand = first_type->element_count(); for (uint32_t in_op = 2; in_op < current_item.instruction->NumInOperands(); ++in_op) { uint32_t index = current_item.instruction->GetSingleWordInOperand(in_op); if (current_item.components.Get(in_op - 2)) { if (index < size_of_first_operand) { first_operand.components.Set(index); } else { second_operand.components.Set(index - size_of_first_operand); } } } AddItemToWorkListIfNeeded(first_operand, live_components, work_list); AddItemToWorkListIfNeeded(second_operand, live_components, work_list); } void VectorDCE::MarkCompositeContructUsesAsLive( VectorDCE::WorkListItem work_item, VectorDCE::LiveComponentMap* live_components, std::vector* work_list) { analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr(); analysis::TypeManager* type_mgr = context()->get_type_mgr(); uint32_t current_component = 0; Instruction* current_inst = work_item.instruction; uint32_t num_in_operands = current_inst->NumInOperands(); for (uint32_t i = 0; i < num_in_operands; ++i) { uint32_t id = current_inst->GetSingleWordInOperand(i); Instruction* op_inst = def_use_mgr->GetDef(id); if (HasScalarResult(op_inst)) { WorkListItem new_work_item; new_work_item.instruction = op_inst; if (work_item.components.Get(current_component)) { new_work_item.components.Set(0); } AddItemToWorkListIfNeeded(new_work_item, live_components, work_list); current_component++; } else { assert(HasVectorResult(op_inst)); WorkListItem new_work_item; new_work_item.instruction = op_inst; uint32_t op_vector_size = type_mgr->GetType(op_inst->type_id())->AsVector()->element_count(); for (uint32_t op_vector_idx = 0; op_vector_idx < op_vector_size; op_vector_idx++, current_component++) { if (work_item.components.Get(current_component)) { new_work_item.components.Set(op_vector_idx); } } AddItemToWorkListIfNeeded(new_work_item, live_components, work_list); } } } void VectorDCE::MarkUsesAsLive( Instruction* current_inst, const utils::BitVector& live_elements, LiveComponentMap* live_components, std::vector* work_list) { analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr(); current_inst->ForEachInId([&work_list, &live_elements, this, live_components, def_use_mgr](uint32_t* operand_id) { Instruction* operand_inst = def_use_mgr->GetDef(*operand_id); if (HasVectorResult(operand_inst)) { WorkListItem new_item; new_item.instruction = operand_inst; new_item.components = live_elements; AddItemToWorkListIfNeeded(new_item, live_components, work_list); } else if (HasScalarResult(operand_inst)) { WorkListItem new_item; new_item.instruction = operand_inst; new_item.components.Set(0); AddItemToWorkListIfNeeded(new_item, live_components, work_list); } }); } bool VectorDCE::HasVectorOrScalarResult(const Instruction* inst) const { return HasScalarResult(inst) || HasVectorResult(inst); } bool VectorDCE::HasVectorResult(const Instruction* inst) const { analysis::TypeManager* type_mgr = context()->get_type_mgr(); if (inst->type_id() == 0) { return false; } const analysis::Type* current_type = type_mgr->GetType(inst->type_id()); switch (current_type->kind()) { case analysis::Type::kVector: return true; default: return false; } } bool VectorDCE::HasScalarResult(const Instruction* inst) const { analysis::TypeManager* type_mgr = context()->get_type_mgr(); if (inst->type_id() == 0) { return false; } const analysis::Type* current_type = type_mgr->GetType(inst->type_id()); switch (current_type->kind()) { case analysis::Type::kBool: case analysis::Type::kInteger: case analysis::Type::kFloat: return true; default: return false; } } bool VectorDCE::RewriteInstructions( Function* function, const VectorDCE::LiveComponentMap& live_components) { bool modified = false; // Kill DebugValue in the middle of the instruction iteration will result // in accessing a dangling pointer. We keep dead DebugValue instructions // in |dead_dbg_value| to kill them once after the iteration. std::vector dead_dbg_value; function->ForEachInst([&modified, this, live_components, &dead_dbg_value](Instruction* current_inst) { if (!context()->IsCombinatorInstruction(current_inst)) { return; } auto live_component = live_components.find(current_inst->result_id()); if (live_component == live_components.end()) { // If this instruction is not in live_components then it does not // produce a vector, or it is never referenced and ADCE will remove // it. No point in trying to differentiate. return; } // If no element in the current instruction is used replace it with an // OpUndef. if (live_component->second.Empty()) { modified = true; MarkDebugValueUsesAsDead(current_inst, &dead_dbg_value); uint32_t undef_id = this->Type2Undef(current_inst->type_id()); context()->KillNamesAndDecorates(current_inst); context()->ReplaceAllUsesWith(current_inst->result_id(), undef_id); context()->KillInst(current_inst); return; } switch (current_inst->opcode()) { case SpvOpCompositeInsert: modified |= RewriteInsertInstruction( current_inst, live_component->second, &dead_dbg_value); break; case SpvOpCompositeConstruct: // TODO: The members that are not live can be replaced by an undef // or constant. This will remove uses of those values, and possibly // create opportunities for ADCE. break; default: // Do nothing. break; } }); for (auto* i : dead_dbg_value) context()->KillInst(i); return modified; } bool VectorDCE::RewriteInsertInstruction( Instruction* current_inst, const utils::BitVector& live_components, std::vector* dead_dbg_value) { // If the value being inserted is not live, then we can skip the insert. if (current_inst->NumInOperands() == 2) { // If there are no indices, then this is the same as a copy. context()->KillNamesAndDecorates(current_inst->result_id()); uint32_t object_id = current_inst->GetSingleWordInOperand(kInsertObjectIdInIdx); context()->ReplaceAllUsesWith(current_inst->result_id(), object_id); return true; } uint32_t insert_index = current_inst->GetSingleWordInOperand(2); if (!live_components.Get(insert_index)) { MarkDebugValueUsesAsDead(current_inst, dead_dbg_value); context()->KillNamesAndDecorates(current_inst->result_id()); uint32_t composite_id = current_inst->GetSingleWordInOperand(kInsertCompositeIdInIdx); context()->ReplaceAllUsesWith(current_inst->result_id(), composite_id); return true; } // If the values already in the composite are not used, then replace it with // an undef. utils::BitVector temp = live_components; temp.Clear(insert_index); if (temp.Empty()) { context()->ForgetUses(current_inst); uint32_t undef_id = Type2Undef(current_inst->type_id()); current_inst->SetInOperand(kInsertCompositeIdInIdx, {undef_id}); context()->AnalyzeUses(current_inst); return true; } return false; } void VectorDCE::MarkDebugValueUsesAsDead( Instruction* composite, std::vector* dead_dbg_value) { context()->get_def_use_mgr()->ForEachUser( composite, [&dead_dbg_value](Instruction* use) { if (use->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugValue) dead_dbg_value->push_back(use); }); } void VectorDCE::AddItemToWorkListIfNeeded( WorkListItem work_item, VectorDCE::LiveComponentMap* live_components, std::vector* work_list) { Instruction* current_inst = work_item.instruction; auto it = live_components->find(current_inst->result_id()); if (it == live_components->end()) { live_components->emplace( std::make_pair(current_inst->result_id(), work_item.components)); work_list->emplace_back(work_item); } else { if (it->second.Or(work_item.components)) { work_list->emplace_back(work_item); } } } } // namespace opt } // namespace spvtools