Skip to content

Commit

Permalink
Add tests for individual instruction def-use analysis
Browse files Browse the repository at this point in the history
Refactor the ClearDef() and ClearInst() implementation.
  • Loading branch information
Qining committed Aug 16, 2016
1 parent 1c848fa commit a54c2a9
Show file tree
Hide file tree
Showing 3 changed files with 284 additions and 26 deletions.
67 changes: 46 additions & 21 deletions source/opt/def_use_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,14 @@ namespace analysis {

void DefUseManager::AnalyzeInstDefUse(ir::Instruction* inst) {
const uint32_t def_id = inst->result_id();
// Clear the records of def_id first if it has been recorded before.
ClearDef(def_id);
if (def_id != 0) {
ClearDef(def_id);
id_to_def_[def_id] = inst;
} else {
ClearInst(inst);
}

if (def_id != 0) id_to_def_[def_id] = inst;
inst_to_used_ids_[inst].reserve(inst->NumOperands());

for (uint32_t i = 0; i < inst->NumOperands(); ++i) {
switch (inst->GetOperand(i).type) {
Expand All @@ -53,7 +57,7 @@ void DefUseManager::AnalyzeInstDefUse(ir::Instruction* inst) {
uint32_t use_id = inst->GetSingleWordOperand(i);
// use_id is used by the instruction generating def_id.
id_to_uses_[use_id].push_back({inst, i});
if (def_id != 0) result_id_to_used_ids_[def_id].push_back(use_id);
inst_to_used_ids_[inst].push_back(use_id);
} break;
default:
break;
Expand All @@ -73,13 +77,16 @@ UseList* DefUseManager::GetUses(uint32_t id) {

bool DefUseManager::KillDef(uint32_t id) {
if (id_to_def_.count(id) == 0) return false;

ir::Instruction* defining_inst = id_to_def_.at(id);
ClearDef(id);
defining_inst->ToNop();
KillInst(id_to_def_[id]);
return true;
}

void DefUseManager::KillInst(ir::Instruction* inst) {
if (!inst) return;
ClearInst(inst);
inst->ToNop();
}

bool DefUseManager::ReplaceAllUsesWith(uint32_t before, uint32_t after) {
if (before == after) return false;
if (id_to_uses_.count(before) == 0) return false;
Expand All @@ -106,30 +113,48 @@ bool DefUseManager::ReplaceAllUsesWith(uint32_t before, uint32_t after) {
}

void DefUseManager::AnalyzeDefUse(ir::Module* module) {
if (!module) return;
module->ForEachInst(std::bind(&DefUseManager::AnalyzeInstDefUse, this,
std::placeholders::_1));
}

void DefUseManager::ClearDef(uint32_t def_id) {
if (id_to_def_.count(def_id) == 0) return;
auto iter = id_to_def_.find(def_id);
if (iter != id_to_def_.end()) {
ClearInst(iter->second);
}
}

void DefUseManager::ClearInst(ir::Instruction* inst) {
auto iter = inst_to_used_ids_.find(inst);
if (iter != inst_to_used_ids_.end()) {
EraseUseRecordsOfOperandIds(inst);
if (inst->result_id() != 0) {
id_to_uses_.erase(inst->result_id()); // Remove all uses of this id.
id_to_def_.erase(inst->result_id());
}
}
}

void DefUseManager::EraseUseRecordsOfOperandIds(const ir::Instruction* inst) {
// Go through all ids used by this instruction, remove this instruction's
// uses of them.
for (const auto use_id : result_id_to_used_ids_[def_id]) {
if (id_to_uses_.count(use_id) == 0) continue;
auto& uses = id_to_uses_[use_id];
for (auto it = uses.begin(); it != uses.end();) {
if (it->inst->result_id() == def_id) {
it = uses.erase(it);
} else {
++it;
auto iter = inst_to_used_ids_.find(inst);
if (iter != inst_to_used_ids_.end()) {
for (const auto use_id : iter->second) {
if (id_to_uses_.count(use_id) == 0) continue;
auto& uses = id_to_uses_[use_id];
for (auto it = uses.begin(); it != uses.end();) {
if (it->inst == inst) {
it = uses.erase(it);
} else {
++it;
}
}
if (uses.empty()) id_to_uses_.erase(use_id);
}
if (uses.empty()) id_to_uses_.erase(use_id);
inst_to_used_ids_.erase(inst);
}
result_id_to_used_ids_.erase(def_id);
id_to_uses_.erase(def_id); // Remove all uses of this id.
id_to_def_.erase(def_id);
}

} // namespace analysis
Expand Down
25 changes: 20 additions & 5 deletions source/opt/def_use_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ class DefUseManager {
// information kept in this manager, but not the operands in the original
// instructions.
bool KillDef(uint32_t id);
// Turns the given instruction |inst| to a Nop, also erases both the use
// records of the result id of |inst| (if any) and the corresponding use
// records of: |inst| useing |inst|'s operand ids.
void KillInst(ir::Instruction* inst);
// Replaces all uses of |before| id with |after| id. Returns true if any
// replacement happens. This method does not kill the definition of the
// |before| id. If |after| is the same as |before|, does nothing and returns
Expand All @@ -92,23 +96,34 @@ class DefUseManager {

private:
// Analyzes the defs and uses in the given |module| and populates data
// structures in this class.
// structures in this class. Does nothing if |module| is nullptr.
void AnalyzeDefUse(ir::Module* module);

// Clear the internal def-use record of a defined id if the given |def_id| is
// recorded by this manager. This method will erase both the uses of |def_id|
// and the |def_id|-generating instruction's use information kept in this
// manager, but not the operands in the original instructions.
// manager, but not the operands in the original instructions. Does nothing
// if |def_id| was not recorded before.
void ClearDef(uint32_t def_id);

using ResultIdToUsedIdsMap =
std::unordered_map<uint32_t, std::vector<uint32_t>>;
// Clear the internal def-use record of the given instruction |inst|. This
// method will update the use information of the operand ids of |inst|. The
// record: |inst| uses an |id|, will be removed from the use records of |id|.
// If |inst| defines an result id, the use record of this result id will also
// be removed. Does nothing if |inst| was not analyzed before.
void ClearInst(ir::Instruction* inst);

// Erases the records that a given instruction |inst| uses its operand ids.
void EraseUseRecordsOfOperandIds(const ir::Instruction* inst);

using InstToUsedIdsMap =
std::unordered_map<const ir::Instruction*, std::vector<uint32_t>>;

IdToDefMap id_to_def_; // Mapping from ids to their definitions
IdToUsesMap id_to_uses_; // Mapping from ids to their uses
// Mapping from result ids to the ids used in the instructions generating the
// result ids.
ResultIdToUsedIdsMap result_id_to_used_ids_;
InstToUsedIdsMap inst_to_used_ids_;
};

} // namespace analysis
Expand Down
218 changes: 218 additions & 0 deletions test/opt/test_def_use.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
// MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.

#include <memory>
#include <unordered_set>

#include <gmock/gmock.h>
#include <gtest/gtest.h>
Expand Down Expand Up @@ -1151,4 +1152,221 @@ TEST(DefUseTest, OpSwitch) {
}
}

// Creates an |result_id| = OpTypeInt 32 1 instruction.
ir::Instruction OpTypeIntInstruction(uint32_t result_id) {
return ir::Instruction(SpvOp::SpvOpTypeInt, 0, result_id,
{ir::Operand(SPV_OPERAND_TYPE_LITERAL_INTEGER, {32}),
ir::Operand(SPV_OPERAND_TYPE_LITERAL_INTEGER, {1})});
}

// Creates an |result_id| = OpConstantTrue/Flase |type_id| instruction.
ir::Instruction ConstantBoolInstruction(bool value, uint32_t type_id,
uint32_t result_id) {
return ir::Instruction(
value ? SpvOp::SpvOpConstantTrue : SpvOp::SpvOpConstantFalse, type_id,
result_id, {});
}

// Creates an |result_id| = OpLabel instruction.
ir::Instruction LabelInstruction(uint32_t result_id) {
return ir::Instruction(SpvOp::SpvOpLabel, 0, result_id, {});
}

// Creates an OpBranch |target_id| instruction.
ir::Instruction BranchInstruction(uint32_t target_id) {
return ir::Instruction(SpvOp::SpvOpBranch, 0, 0,
{
ir::Operand(SPV_OPERAND_TYPE_ID, {target_id}),
});
}

struct AnalyzeInstDefUseTestCase {
std::vector<ir::Instruction> insts; // instrutions to be analyzed in order.
InstDefUse expected_define_use; // expected analysis result.
};

using AnalyzeInstDefUseTest =
::testing::TestWithParam<AnalyzeInstDefUseTestCase>;

// Test the analyzing result for individual instructions.
TEST_P(AnalyzeInstDefUseTest, Case) {
auto tc = GetParam();

// Analyze the instructions.
opt::analysis::DefUseManager manager(nullptr);
for (ir::Instruction& inst : tc.insts) {
manager.AnalyzeInstDefUse(&inst);
}

CheckDef(tc.expected_define_use, manager.id_to_defs());
CheckUse(tc.expected_define_use, manager.id_to_uses());
}

// clang-format off
INSTANTIATE_TEST_CASE_P(
TestCase, AnalyzeInstDefUseTest,
::testing::ValuesIn(std::vector<AnalyzeInstDefUseTestCase>{
{ // A type declaring instruction.
{OpTypeIntInstruction(1)},
{
// defs
{{1, "%1 = OpTypeInt 32 1"}},
{}, // no uses
},
},
{ // A type declaring instruction and a constant value.
{
OpTypeIntInstruction(1),
ConstantBoolInstruction(true, 1, 2),
},
{
{ // defs
{1, "%1 = OpTypeInt 32 1"},
{2, "%2 = OpConstantTrue %1"},
},
{ // uses
{1,{"%2 = OpConstantTrue %1"}},
},
},
},
{ // Analyze two instrutions that have same result id. The def use info
// of the result id from the first instruction should be overwritten by
// the second instruction.
{
ConstantBoolInstruction(true, 1, 2),
// The def-use info of the following instruction should overwrite the
// records of the above one.
ConstantBoolInstruction(false, 3, 2),
},
{
// defs
{{2, "%2 = OpConstantFalse %3"}},
// uses
{{3, {"%2 = OpConstantFalse %3"}}}
}
},
{ // Analyze forward reference instruction, also instruction that does
// not have result id.
{
BranchInstruction(2),
LabelInstruction(2),
},
{
// defs
{{2, "%2 = OpLabel"}},
// uses
{{2, {"OpBranch %2"}}},
}
}
}));
// clang-format on

struct KillInstTestCase {
const char* before;
std::unordered_set<uint32_t> indices_inst_to_kill;
const char* after;
InstDefUse expected_define_use;
};

using KillInstTest = ::testing::TestWithParam<KillInstTestCase>;

TEST_P(KillInstTest, Case) {
auto tc = GetParam();

// Build module.
std::unique_ptr<ir::Module> module =
SpvTools(SPV_ENV_UNIVERSAL_1_1).BuildModule(tc.before);
ASSERT_NE(nullptr, module);

// KillInst
uint32_t index = 0;
opt::analysis::DefUseManager manager(module.get());
module->ForEachInst([&index, &tc, &manager](ir::Instruction* inst) {
if (tc.indices_inst_to_kill.count(index) != 0) {
manager.KillInst(inst);
}
index++;
});

EXPECT_EQ(tc.after, DisassembleModule(module.get()));
CheckDef(tc.expected_define_use, manager.id_to_defs());
CheckUse(tc.expected_define_use, manager.id_to_uses());
}

// clang-format off
INSTANTIATE_TEST_CASE_P(
TestCase, KillInstTest,
::testing::ValuesIn(std::vector<KillInstTestCase>{
// Kill id defining instructions.
{
"%2 = OpFunction %1 None %3 "
"%4 = OpLabel "
" OpBranch %5 "
"%5 = OpLabel "
" OpBranch %6 "
"%6 = OpLabel "
" OpBranch %4 "
"%7 = OpLabel "
" OpReturn "
" OpFunctionEnd",
{0, 1, 3, 5, 7},
"OpNop\n"
"OpNop\n"
"OpBranch %5\n"
"OpNop\n"
"OpBranch %6\n"
"OpNop\n"
"OpBranch %4\n"
"OpNop\n"
"OpReturn\n"
"OpFunctionEnd",
{
// no defs
{},
// no uses
{}
}
},
// Kill instructions that do not have result ids.
{
"%2 = OpFunction %1 None %3 "
"%4 = OpLabel "
" OpBranch %5 "
"%5 = OpLabel "
" OpBranch %6 "
"%6 = OpLabel "
" OpBranch %4 "
"%7 = OpLabel "
" OpReturn "
" OpFunctionEnd",
{2, 4, 6},
"%2 = OpFunction %1 None %3\n"
"%4 = OpLabel\n"
"OpNop\n"
"%5 = OpLabel\n"
"OpNop\n"
"%6 = OpLabel\n"
"OpNop\n"
"%7 = OpLabel\n"
"OpReturn\n"
"OpFunctionEnd",
{
// defs
{
{2, "%2 = OpFunction %1 None %3"},
{4, "%4 = OpLabel"},
{5, "%5 = OpLabel"},
{6, "%6 = OpLabel"},
{7, "%7 = OpLabel"},
},
// uses
{
{1, {"%2 = OpFunction %1 None %3"}},
{3, {"%2 = OpFunction %1 None %3"}},
}
}
},
}));
// clang-format on

} // anonymous namespace

0 comments on commit a54c2a9

Please sign in to comment.