// 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; const uint32_t kSpecConstOpOpcodeIdx = 0; constexpr uint32_t kArrayElementTypeIdx = 0; } // namespace 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 rewriting 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) { switch (inst.GetSingleWordInOperand(kSpecConstOpOpcodeIdx)) { case SpvOpCompositeExtract: MarkMembersAsLiveForExtract(&inst); break; case SpvOpCompositeInsert: // Nothing specific to do. break; case SpvOpAccessChain: case SpvOpInBoundsAccessChain: case SpvOpPtrAccessChain: case SpvOpInBoundsPtrAccessChain: assert(false && "Not implemented yet."); break; default: break; } } else if (inst.opcode() == SpvOpVariable) { switch (inst.GetSingleWordInOperand(0)) { case SpvStorageClassInput: case SpvStorageClassOutput: MarkPointeeTypeAsFullUsed(inst.type_id()); break; default: // Ignore structured buffers as layout(offset) qualifiers cannot be // applied to structure fields if (inst.IsVulkanStorageBufferVariable()) MarkPointeeTypeAsFullUsed(inst.type_id()); break; } } } 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: case SpvOpCompositeConstruct: 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); switch (type_inst->opcode()) { case SpvOpTypeStruct: // Mark every member and its type as fully used. for (uint32_t i = 0; i < type_inst->NumInOperands(); ++i) { used_members_[type_id].insert(i); MarkTypeAsFullyUsed(type_inst->GetSingleWordInOperand(i)); } break; case SpvOpTypeArray: case SpvOpTypeRuntimeArray: MarkTypeAsFullyUsed( type_inst->GetSingleWordInOperand(kArrayElementTypeIdx)); break; default: break; } } void EliminateDeadMembersPass::MarkPointeeTypeAsFullUsed(uint32_t ptr_type_id) { Instruction* ptr_type_inst = get_def_use_mgr()->GetDef(ptr_type_id); assert(ptr_type_inst->opcode() == SpvOpTypePointer); MarkTypeAsFullyUsed(ptr_type_inst->GetSingleWordInOperand(1)); } 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 || (inst->opcode() == SpvOpSpecConstantOp && inst->GetSingleWordInOperand(kSpecConstOpOpcodeIdx) == SpvOpCompositeExtract)); uint32_t first_operand = (inst->opcode() == SpvOpSpecConstantOp ? 1 : 0); uint32_t composite_id = inst->GetSingleWordInOperand(first_operand); Instruction* composite_inst = get_def_use_mgr()->GetDef(composite_id); uint32_t type_id = composite_inst->type_id(); for (uint32_t i = first_operand + 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); uint32_t index = static_cast(member_idx->GetZeroExtendedValue()); used_members_[type_id].insert(index); type_id = type_inst->GetSingleWordInOperand(index); } 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: switch (inst->GetSingleWordInOperand(kSpecConstOpOpcodeIdx)) { case SpvOpCompositeExtract: modified |= UpdateCompsiteExtract(inst); break; case SpvOpCompositeInsert: modified |= UpdateCompositeInsert(inst); break; case SpvOpAccessChain: case SpvOpInBoundsAccessChain: case SpvOpPtrAccessChain: case SpvOpInBoundsPtrAccessChain: assert(false && "Not implemented yet."); break; default: break; } 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() == SpvOpSpecConstantComposite || 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(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 = static_cast(member_idx->GetZeroExtendedValue()); 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( std::distance(live_members->second.begin(), current_member)); } bool EliminateDeadMembersPass::UpdateCompsiteExtract(Instruction* inst) { assert(inst->opcode() == SpvOpCompositeExtract || (inst->opcode() == SpvOpSpecConstantOp && inst->GetSingleWordInOperand(kSpecConstOpOpcodeIdx) == SpvOpCompositeExtract)); uint32_t first_operand = 0; if (inst->opcode() == SpvOpSpecConstantOp) { first_operand = 1; } uint32_t object_id = inst->GetSingleWordInOperand(first_operand); 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; for (uint32_t i = 0; i < first_operand + 1; i++) { new_operands.emplace_back(inst->GetInOperand(i)); } for (uint32_t i = first_operand + 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: // 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::UpdateCompositeInsert(Instruction* inst) { assert(inst->opcode() == SpvOpCompositeInsert || (inst->opcode() == SpvOpSpecConstantOp && inst->GetSingleWordInOperand(kSpecConstOpOpcodeIdx) == SpvOpCompositeInsert)); uint32_t first_operand = 0; if (inst->opcode() == SpvOpSpecConstantOp) { first_operand = 1; } uint32_t composite_id = inst->GetSingleWordInOperand(first_operand + 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; for (uint32_t i = 0; i < first_operand + 2; ++i) { new_operands.emplace_back(inst->GetInOperand(i)); } for (uint32_t i = first_operand + 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) { if (inst->type_id() != 0) { MarkTypeAsFullyUsed(inst->type_id()); } 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