SPIRV-Tools/source/opt/vector_dce.cpp
Jaebaek Seo a687057a83
Preserve debug info in vector DCE pass (#3497)
This commit lets the vector DCE pass preserve the OpenCL.DebugInfo.100
information properly. When the vector DCE pass determines the liveness
of instructions, the debug instructions must not affect the decision. In
addition, when it kills some instructions, it has to kill DebugValue
instructions that use the killed instructions. When it updates some
composite values to meaningful values (not undef), it has to remove
DebugValue because the value information becomes incorrect.
2020-07-10 10:19:34 -04:00

420 lines
15 KiB
C++

// 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 <utility>
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<WorkListItem> 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<WorkListItem>* 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<VectorDCE::WorkListItem>* 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<WorkListItem>* 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<VectorDCE::WorkListItem>* 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<VectorDCE::WorkListItem>* 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<Instruction*> 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<Instruction*>* 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<Instruction*>* 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<WorkListItem>* 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