blob: 0b73b2dbcb0c47f3fe81dc5284cb648e6ae89bd6 [file] [log] [blame]
// 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;
}
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 rewritting 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) {
MarkTypeAsFullyUsed(inst.type_id());
} else if (inst.opcode() == SpvOpVariable) {
switch (inst.GetSingleWordInOperand(0)) {
case SpvStorageClassInput:
case SpvStorageClassOutput:
MarkPointeeTypeAsFullUsed(inst.type_id());
break;
default:
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);
if (type_inst->opcode() != SpvOpTypeStruct) {
return;
}
// Mark every member of the current struct as used.
for (uint32_t i = 0; i < type_inst->NumInOperands(); ++i) {
used_members_[type_id].insert(i);
}
// Mark any sub struct as fully used.
for (uint32_t i = 0; i < type_inst->NumInOperands(); ++i) {
MarkTypeAsFullyUsed(type_inst->GetSingleWordInOperand(i));
}
}
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);
uint32_t composite_id = inst->GetSingleWordInOperand(0);
Instruction* composite_inst = get_def_use_mgr()->GetDef(composite_id);
uint32_t type_id = composite_inst->type_id();
for (uint32_t i = 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);
if (member_idx->type()->AsInteger()->width() == 32) {
used_members_[type_id].insert(member_idx->GetU32());
type_id = type_inst->GetSingleWordInOperand(member_idx->GetU32());
} else {
used_members_[type_id].insert(
static_cast<uint32_t>(member_idx->GetU64()));
type_id = type_inst->GetSingleWordInOperand(
static_cast<uint32_t>(member_idx->GetU64()));
}
} 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:
assert(false && "Not yet implemented.");
// with OpCompositeExtract, OpCompositeInsert
// For kernels: OpAccessChain, OpInBoundsAccessChain, OpPtrAccessChain,
// OpInBoundsPtrAccessChain
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() == 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<uint32_t>(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;
if (member_idx->type()->AsInteger()->width() == 32) {
orig_member_idx = member_idx->GetU32();
} else {
orig_member_idx = static_cast<uint32_t>(member_idx->GetU64());
}
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<uint32_t>(
std::distance(live_members->second.begin(), current_member));
}
bool EliminateDeadMembersPass::UpdateCompsiteExtract(Instruction* inst) {
uint32_t object_id = inst->GetSingleWordInOperand(0);
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;
new_operands.emplace_back(inst->GetInOperand(0));
for (uint32_t i = 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:
assert(i != 1 || (inst->opcode() != SpvOpPtrAccessChain &&
inst->opcode() != SpvOpInBoundsPtrAccessChain));
// The type will have already been rewriten, 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) {
uint32_t composite_id = inst->GetSingleWordInOperand(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;
new_operands.emplace_back(inst->GetInOperand(0));
new_operands.emplace_back(inst->GetInOperand(1));
for (uint32_t i = 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