| // 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 spvtools { |
| namespace opt { |
| namespace { |
| constexpr uint32_t kRemovedMember = 0xFFFFFFFF; |
| constexpr uint32_t kSpecConstOpOpcodeIdx = 0; |
| constexpr uint32_t kArrayElementTypeIdx = 0; |
| } // namespace |
| |
| Pass::Status EliminateDeadMembersPass::Process() { |
| if (!context()->get_feature_mgr()->HasCapability(spv::Capability::Shader)) |
| 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() == spv::Op::OpSpecConstantOp) { |
| switch (spv::Op(inst.GetSingleWordInOperand(kSpecConstOpOpcodeIdx))) { |
| case spv::Op::OpCompositeExtract: |
| MarkMembersAsLiveForExtract(&inst); |
| break; |
| case spv::Op::OpCompositeInsert: |
| // Nothing specific to do. |
| break; |
| case spv::Op::OpAccessChain: |
| case spv::Op::OpInBoundsAccessChain: |
| case spv::Op::OpPtrAccessChain: |
| case spv::Op::OpInBoundsPtrAccessChain: |
| assert(false && "Not implemented yet."); |
| break; |
| default: |
| break; |
| } |
| } else if (inst.opcode() == spv::Op::OpVariable) { |
| switch (spv::StorageClass(inst.GetSingleWordInOperand(0))) { |
| case spv::StorageClass::Input: |
| case spv::StorageClass::Output: |
| 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 spv::Op::OpStore: |
| MarkMembersAsLiveForStore(inst); |
| break; |
| case spv::Op::OpCopyMemory: |
| case spv::Op::OpCopyMemorySized: |
| MarkMembersAsLiveForCopyMemory(inst); |
| break; |
| case spv::Op::OpCompositeExtract: |
| MarkMembersAsLiveForExtract(inst); |
| break; |
| case spv::Op::OpAccessChain: |
| case spv::Op::OpInBoundsAccessChain: |
| case spv::Op::OpPtrAccessChain: |
| case spv::Op::OpInBoundsPtrAccessChain: |
| MarkMembersAsLiveForAccessChain(inst); |
| break; |
| case spv::Op::OpReturnValue: |
| // 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 spv::Op::OpArrayLength: |
| MarkMembersAsLiveForArrayLength(inst); |
| break; |
| case spv::Op::OpLoad: |
| case spv::Op::OpCompositeInsert: |
| case spv::Op::OpCompositeConstruct: |
| 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() == spv::Op::OpStore); |
| 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 spv::Op::OpTypeStruct: |
| // 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 spv::Op::OpTypeArray: |
| case spv::Op::OpTypeRuntimeArray: |
| 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() == spv::Op::OpTypePointer); |
| 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() == spv::Op::OpCompositeExtract || |
| (inst->opcode() == spv::Op::OpSpecConstantOp && |
| spv::Op(inst->GetSingleWordInOperand(kSpecConstOpOpcodeIdx)) == |
| spv::Op::OpCompositeExtract)); |
| |
| uint32_t first_operand = |
| (inst->opcode() == spv::Op::OpSpecConstantOp ? 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 spv::Op::OpTypeStruct: |
| used_members_[type_id].insert(member_idx); |
| type_id = type_inst->GetSingleWordInOperand(member_idx); |
| break; |
| case spv::Op::OpTypeArray: |
| case spv::Op::OpTypeRuntimeArray: |
| case spv::Op::OpTypeVector: |
| case spv::Op::OpTypeMatrix: |
| type_id = type_inst->GetSingleWordInOperand(0); |
| break; |
| default: |
| assert(false); |
| } |
| } |
| } |
| |
| void EliminateDeadMembersPass::MarkMembersAsLiveForAccessChain( |
| const Instruction* inst) { |
| assert(inst->opcode() == spv::Op::OpAccessChain || |
| inst->opcode() == spv::Op::OpInBoundsAccessChain || |
| inst->opcode() == spv::Op::OpPtrAccessChain || |
| inst->opcode() == spv::Op::OpInBoundsPtrAccessChain); |
| |
| 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() == spv::Op::OpAccessChain || |
| inst->opcode() == spv::Op::OpInBoundsAccessChain |
| ? 1 |
| : 2); |
| for (; i < inst->NumInOperands(); ++i) { |
| Instruction* type_inst = get_def_use_mgr()->GetDef(type_id); |
| switch (type_inst->opcode()) { |
| case spv::Op::OpTypeStruct: { |
| const analysis::IntConstant* member_idx = |
| const_mgr->FindDeclaredConstant(inst->GetSingleWordInOperand(i)) |
| ->AsIntConstant(); |
| assert(member_idx); |
| uint32_t index = |
| static_cast<uint32_t>(member_idx->GetZeroExtendedValue()); |
| used_members_[type_id].insert(index); |
| type_id = type_inst->GetSingleWordInOperand(index); |
| } break; |
| case spv::Op::OpTypeArray: |
| case spv::Op::OpTypeRuntimeArray: |
| case spv::Op::OpTypeVector: |
| case spv::Op::OpTypeMatrix: |
| 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() == spv::Op::OpArrayLength); |
| 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 spv::Op::OpTypeStruct: |
| 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 spv::Op::OpMemberName: |
| modified |= UpdateOpMemberNameOrDecorate(inst); |
| break; |
| case spv::Op::OpMemberDecorate: |
| modified |= UpdateOpMemberNameOrDecorate(inst); |
| break; |
| case spv::Op::OpGroupMemberDecorate: |
| modified |= UpdateOpGroupMemberDecorate(inst); |
| break; |
| case spv::Op::OpSpecConstantComposite: |
| case spv::Op::OpConstantComposite: |
| case spv::Op::OpCompositeConstruct: |
| modified |= UpdateConstantComposite(inst); |
| break; |
| case spv::Op::OpAccessChain: |
| case spv::Op::OpInBoundsAccessChain: |
| case spv::Op::OpPtrAccessChain: |
| case spv::Op::OpInBoundsPtrAccessChain: |
| modified |= UpdateAccessChain(inst); |
| break; |
| case spv::Op::OpCompositeExtract: |
| modified |= UpdateCompsiteExtract(inst); |
| break; |
| case spv::Op::OpCompositeInsert: |
| modified |= UpdateCompositeInsert(inst); |
| break; |
| case spv::Op::OpArrayLength: |
| modified |= UpdateOpArrayLength(inst); |
| break; |
| case spv::Op::OpSpecConstantOp: |
| switch (spv::Op(inst->GetSingleWordInOperand(kSpecConstOpOpcodeIdx))) { |
| case spv::Op::OpCompositeExtract: |
| modified |= UpdateCompsiteExtract(inst); |
| break; |
| case spv::Op::OpCompositeInsert: |
| modified |= UpdateCompositeInsert(inst); |
| break; |
| case spv::Op::OpAccessChain: |
| case spv::Op::OpInBoundsAccessChain: |
| case spv::Op::OpPtrAccessChain: |
| case spv::Op::OpInBoundsPtrAccessChain: |
| assert(false && "Not implemented yet."); |
| break; |
| default: |
| break; |
| } |
| break; |
| default: |
| break; |
| } |
| }); |
| return modified; |
| } |
| |
| bool EliminateDeadMembersPass::UpdateOpTypeStruct(Instruction* inst) { |
| assert(inst->opcode() == spv::Op::OpTypeStruct); |
| |
| 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() == spv::Op::OpMemberName || |
| inst->opcode() == spv::Op::OpMemberDecorate); |
| |
| 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() == spv::Op::OpGroupMemberDecorate); |
| |
| 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() == spv::Op::OpSpecConstantComposite || |
| inst->opcode() == spv::Op::OpConstantComposite || |
| inst->opcode() == spv::Op::OpCompositeConstruct); |
| 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() == spv::Op::OpAccessChain || |
| inst->opcode() == spv::Op::OpInBoundsAccessChain || |
| inst->opcode() == spv::Op::OpPtrAccessChain || |
| inst->opcode() == spv::Op::OpInBoundsPtrAccessChain); |
| |
| 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() == spv::Op::OpPtrAccessChain || |
| inst->opcode() == spv::Op::OpInBoundsPtrAccessChain) { |
| new_operands.emplace_back(inst->GetInOperand(1)); |
| } |
| |
| for (uint32_t i = static_cast<uint32_t>(new_operands.size()); |
| i < inst->NumInOperands(); ++i) { |
| Instruction* type_inst = get_def_use_mgr()->GetDef(type_id); |
| switch (type_inst->opcode()) { |
| case spv::Op::OpTypeStruct: { |
| const analysis::IntConstant* member_idx = |
| const_mgr->FindDeclaredConstant(inst->GetSingleWordInOperand(i)) |
| ->AsIntConstant(); |
| assert(member_idx); |
| uint32_t orig_member_idx = |
| static_cast<uint32_t>(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 spv::Op::OpTypeArray: |
| case spv::Op::OpTypeRuntimeArray: |
| case spv::Op::OpTypeVector: |
| case spv::Op::OpTypeMatrix: |
| 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<uint32_t>( |
| std::distance(live_members->second.begin(), current_member)); |
| } |
| |
| bool EliminateDeadMembersPass::UpdateCompsiteExtract(Instruction* inst) { |
| assert(inst->opcode() == spv::Op::OpCompositeExtract || |
| (inst->opcode() == spv::Op::OpSpecConstantOp && |
| spv::Op(inst->GetSingleWordInOperand(kSpecConstOpOpcodeIdx)) == |
| spv::Op::OpCompositeExtract)); |
| |
| uint32_t first_operand = 0; |
| if (inst->opcode() == spv::Op::OpSpecConstantOp) { |
| 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 spv::Op::OpTypeStruct: |
| // The type will have already been rewritten, so use the new member |
| // index. |
| type_id = type_inst->GetSingleWordInOperand(new_member_idx); |
| break; |
| case spv::Op::OpTypeArray: |
| case spv::Op::OpTypeRuntimeArray: |
| case spv::Op::OpTypeVector: |
| case spv::Op::OpTypeMatrix: |
| 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() == spv::Op::OpCompositeInsert || |
| (inst->opcode() == spv::Op::OpSpecConstantOp && |
| spv::Op(inst->GetSingleWordInOperand(kSpecConstOpOpcodeIdx)) == |
| spv::Op::OpCompositeInsert)); |
| |
| uint32_t first_operand = 0; |
| if (inst->opcode() == spv::Op::OpSpecConstantOp) { |
| 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 spv::Op::OpTypeStruct: |
| // The type will have already been rewritten, so use the new member |
| // index. |
| type_id = type_inst->GetSingleWordInOperand(new_member_idx); |
| break; |
| case spv::Op::OpTypeArray: |
| case spv::Op::OpTypeRuntimeArray: |
| case spv::Op::OpTypeVector: |
| case spv::Op::OpTypeMatrix: |
| 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 |