Skip to content

Commit

Permalink
Dead constant elimination
Browse files Browse the repository at this point in the history
A pass to remove dead constants, including both front-end constants and spec
constants.

This pass does not handle dead variables and types.
  • Loading branch information
Qining committed Aug 8, 2016
1 parent fd965c9 commit 51a2484
Show file tree
Hide file tree
Showing 7 changed files with 977 additions and 0 deletions.
9 changes: 9 additions & 0 deletions source/opt/module.cpp
Expand Up @@ -39,6 +39,15 @@ std::vector<Instruction*> Module::types() {
return insts;
};

std::vector<Instruction*> Module::GetConstants() {
std::vector<Instruction*> insts;
for (uint32_t i = 0; i < types_values_.size(); ++i) {
if (IsConstantInst(types_values_[i].opcode()))
insts.push_back(&types_values_[i]);
}
return insts;
};

void Module::ForEachInst(const std::function<void(Instruction*)>& f) {
for (auto& i : capabilities_) f(&i);
for (auto& i : extensions_) f(&i);
Expand Down
2 changes: 2 additions & 0 deletions source/opt/module.h
Expand Up @@ -94,6 +94,8 @@ class Module {
// Returns a vector of pointers to type-declaration instructions in this
// module.
std::vector<Instruction*> types();
// Returns the constant-defining instructions.
std::vector<Instruction*> GetConstants();
const std::vector<Instruction>& debugs() const { return debugs_; }
std::vector<Instruction>& debugs() { return debugs_; }
const std::vector<Instruction>& annotations() const { return annotations_; }
Expand Down
96 changes: 96 additions & 0 deletions source/opt/passes.cpp
Expand Up @@ -26,6 +26,14 @@

#include "passes.h"

#include <algorithm>
#include <iostream>
#include <unordered_map>
#include <unordered_set>

#include "def_use_manager.h"
#include "reflect.h"

namespace spvtools {
namespace opt {

Expand Down Expand Up @@ -71,5 +79,93 @@ bool FreezeSpecConstantValuePass::Process(ir::Module* module) {
return modified;
}

bool EliminateDeadConstantPass::Process(ir::Module* module) {
analysis::DefUseManager def_use;
def_use.AnalyzeDefUse(module);
std::unordered_set<ir::Instruction*> working_list;
// Traverse all the instructions to get the initial set of dead constants as
// working list and count number of real uses for constants. Uses in
// annotation instructions do not count.
std::unordered_map<ir::Instruction*, size_t> use_counts;
std::vector<ir::Instruction*> constants = module->GetConstants();
for (auto* c : constants) {
uint32_t const_id = c->result_id();
size_t count = 0;
if (analysis::UseList* uses = def_use.GetUses(const_id)) {
count =
std::count_if(uses->begin(), uses->end(), [](const analysis::Use& u) {
return !(ir::IsAnnotationInst(u.inst->opcode()) ||
ir::IsDebugInst(u.inst->opcode()));
});
}
use_counts[c] = count;
if (!count) {
working_list.insert(c);
}
}

// Start from the constants with 0 uses, back trace through the def-use chain
// to find all dead constants.
std::unordered_set<ir::Instruction*> dead_consts;
while (!working_list.empty()) {
ir::Instruction* inst = *working_list.begin();
// Back propagate if the instruction contains IDs in its operands.
switch (inst->opcode()) {
case SpvOp::SpvOpConstantComposite:
case SpvOp::SpvOpSpecConstantComposite:
case SpvOp::SpvOpSpecConstantOp:
for (uint32_t i = 0; i < inst->NumInOperands(); i++) {
// SpecConstantOp instruction contains 'opcode' as its operand. Need
// to exclude such operands when decreasing uses.
if (inst->GetInOperand(i).type != SPV_OPERAND_TYPE_ID) {
continue;
}
uint32_t operand_id = inst->GetSingleWordInOperand(i);
ir::Instruction* def_inst = def_use.GetDef(operand_id);
// If the use_count does not have any count for the def_inst,
// def_inst must not be a constant, and should be ignored here.
if (!use_counts.count(def_inst)) {
continue;
}
// The number of uses should never be less then 0, so it can not be
// less than 1 before it decreases.
assert(use_counts[def_inst] > 0);
--use_counts[def_inst];
if (!use_counts[def_inst]) {
working_list.insert(def_inst);
}
}
break;
default:
break;
}
dead_consts.insert(inst);
working_list.erase(inst);
}

// Find all annotation and debug instructions that are referencing dead
// constants.
std::unordered_set<ir::Instruction*> dead_others;
for (auto* dc : dead_consts) {
if (analysis::UseList* uses = def_use.GetUses(dc->result_id())) {
for (const auto& u : *uses) {
if (ir::IsAnnotationInst(u.inst->opcode()) ||
ir::IsDebugInst(u.inst->opcode())) {
dead_others.insert(u.inst);
}
}
}
}

// Turn all dead instructions and uses of them to nop
for (auto* dc : dead_consts) {
def_use.KillDef(dc->result_id());
}
for (auto* da : dead_others) {
da->ToNop();
}
return !dead_consts.empty();
}

} // namespace opt
} // namespace spvtools
11 changes: 11 additions & 0 deletions source/opt/passes.h
Expand Up @@ -72,6 +72,17 @@ class FreezeSpecConstantValuePass : public Pass {
bool Process(ir::Module*) override;
};

// The optimization pass to remove dead constants, including front-end
// contants: defined by OpConstant, OpConstantComposite, OpConstantTrue and
// OpConstantFalse; and spec constants: defined by OpSpecConstant,
// OpSpecConstantComposite, OpSpecConstantTrue, OpSpecConstantFalse and
// OpSpecConstantOp.
class EliminateDeadConstantPass : public Pass {
public:
const char* name() const override { return "eliminate-dead-const"; }
bool Process(ir::Module*) override;
};

} // namespace opt
} // namespace spvtools

Expand Down
5 changes: 5 additions & 0 deletions test/opt/CMakeLists.txt
Expand Up @@ -44,6 +44,11 @@ add_spvtools_unittest(TARGET pass_freeze_spec_const
LIBS SPIRV-Tools-opt ${SPIRV_TOOLS}
)

add_spvtools_unittest(TARGET pass_eliminate_dead_const
SRCS test_eliminate_dead_const.cpp pass_utils.cpp
LIBS SPIRV-Tools-opt ${SPIRV_TOOLS}
)

add_spvtools_unittest(TARGET pass_utils
SRCS test_utils.cpp pass_utils.cpp
LIBS SPIRV-Tools-opt ${SPIRV_TOOLS}
Expand Down

0 comments on commit 51a2484

Please sign in to comment.