Skip to content

Commit

Permalink
structural equals
Browse files Browse the repository at this point in the history
Summary: This diff support cfg structural_equals.

Reviewed By: thezhangwei

Differential Revision: D49624046

fbshipit-source-id: 3fe5a8381a68b99a84103da80a2d67ce41ffa3fa
  • Loading branch information
beicy authored and facebook-github-bot committed Nov 1, 2023
1 parent 64fad48 commit 9687260
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 33 deletions.
154 changes: 152 additions & 2 deletions libredex/ControlFlow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <boost/dynamic_bitset.hpp>
#include <boost/numeric/conversion/cast.hpp>
#include <iterator>
#include <queue>
#include <stack>
#include <utility>

Expand All @@ -29,6 +30,33 @@
std::atomic<size_t> build_cfg_counter{0};
namespace {

bool edge_type_structural_equals(std::vector<cfg::Edge*> e1,
std::vector<cfg::Edge*> e2) {
if (e1.empty() && e2.empty()) {
return true;
}

if (e1.empty() || e2.empty()) {
return false;
}

if (e1.size() != e2.size()) {
return false;
}
std::unordered_map<cfg::EdgeType, int> edge_types;
for (size_t i = 0; i < e1.size(); i++) {
edge_types[e1[i]->type()] += 1;
edge_types[e2[i]->type()] -= 1;
}

for (auto pair : edge_types) {
if (pair.second != 0) {
return false;
}
}
return true;
}

// return true if `it` should be the last instruction of this block
bool end_of_block(const IRList* ir, const IRList::iterator& it, bool in_try) {
auto next = std::next(it);
Expand Down Expand Up @@ -616,6 +644,17 @@ std::vector<Edge*> Block::get_outgoing_throws_in_order() const {
return result;
}

std::vector<Edge*> Block::get_outgoing_branches_in_order() const {
std::vector<Edge*> result =
m_parent->get_succ_edges_of_type(this, EDGE_BRANCH);
if (result.size() > 1) {
std::sort(result.begin(), result.end(), [](const Edge* e1, const Edge* e2) {
return e1->case_key() < e2->case_key();
});
}
return result;
}

// These assume that the iterator is inside this block
cfg::InstructionIterator Block::to_cfg_instruction_iterator(
const ir_list::InstructionIterator& list_it, bool next_on_end) {
Expand Down Expand Up @@ -682,8 +721,12 @@ bool Block::push_back(const std::vector<IRInstruction*>& insns) {
bool Block::push_back(IRInstruction* insn) {
return m_parent->push_back(this, insn);
}

bool Block::structural_equals(const Block* other) const {
return this->structural_equals(other, std::equal_to<const IRInstruction&>());
}

bool Block::structural_equals(
const Block* other, const InstructionEquality& instruction_equals) const {
auto iterable1 = ir_list::ConstInstructionIterable(this);
auto iterable2 = ir_list::ConstInstructionIterable(other);
auto it1 = iterable1.begin();
Expand All @@ -693,14 +736,23 @@ bool Block::structural_equals(const Block* other) const {
auto& mie1 = *it1;
auto& mie2 = *it2;

if (*mie1.insn != *mie2.insn) {
if (!instruction_equals(*mie1.insn, *mie2.insn)) {
return false;
}
}

return it1 == iterable1.end() && it2 == iterable2.end();
}

bool Block::extended_structural_equals(
const Block* other, const InstructionEquality& instruction_equals) const {
if (!edge_type_structural_equals(this->preds(), other->preds()) ||
!edge_type_structural_equals(this->succs(), other->succs())) {
return false;
}
return this->structural_equals(other, instruction_equals);
}

std::ostream& operator<<(std::ostream& os, const Edge& e) {
switch (e.type()) {
case EDGE_GOTO:
Expand Down Expand Up @@ -3097,6 +3149,104 @@ ControlFlowGraph::EdgeSet ControlFlowGraph::remove_pred_edges(Block* b,
cleanup);
}

bool ControlFlowGraph::structural_equals(const ControlFlowGraph& other) const {
return this->structural_equals(other, std::equal_to<const IRInstruction&>());
}

bool ControlFlowGraph::structural_equals(
const ControlFlowGraph& other,
const InstructionEquality& instruction_equals) const {
if (this->num_blocks() != other.num_blocks() ||
this->num_edges() != other.num_edges()) {
return false;
}

std::queue<Block*> this_blocks;
std::queue<Block*> other_blocks;
std::unordered_set<BlockId> block_visited;
// Check entry block.
if (!this->entry_block()->extended_structural_equals(other.entry_block(),
instruction_equals)) {
return false;
}

// Check exit_block. Then no need to check EDGE_GHOST later.
if (this->exit_block()) {
if (!this->exit_block()->extended_structural_equals(other.exit_block(),
instruction_equals)) {
return false;
}
} else if (other.exit_block()) {
return false;
}

this_blocks.push(this->entry_block());
other_blocks.push(other.entry_block());
block_visited.emplace(this->entry_block()->id());
while (!this_blocks.empty()) {
always_assert(!other_blocks.empty());
auto b1 = this_blocks.front();
auto b2 = other_blocks.front();
this_blocks.pop();
other_blocks.pop();
// Push b1, b2's GOTO succes into queue;
auto goto1 = b1->goes_to();
auto goto2 = b2->goes_to();
if (goto1) {
if (!goto1->extended_structural_equals(goto2, instruction_equals)) {
return false;
}
if (block_visited.count(goto1->id()) == 0) {
this_blocks.push(goto1);
other_blocks.push(goto2);
block_visited.emplace(goto1->id());
}
} else if (goto2) {
return false;
}

// Push b1, b2's THROW succes into queue;
auto throw1_edges = b1->get_outgoing_throws_in_order();
auto throw2_edges = b2->get_outgoing_throws_in_order();
if (throw1_edges.size() != throw2_edges.size()) {
return false;
}
for (size_t i = 0; i < throw1_edges.size(); i++) {
auto throw1 = throw1_edges[i]->target();
auto throw2 = throw2_edges[i]->target();
if (!throw1->extended_structural_equals(throw2, instruction_equals)) {
return false;
}
if (block_visited.count(throw1->id()) == 0) {
this_blocks.push(throw1);
other_blocks.push(throw2);
block_visited.emplace(throw1->id());
}
}

// Push b1, b2's BRANCH into queue;
auto branch1_edges = b1->get_outgoing_branches_in_order();
auto branch2_edges = b2->get_outgoing_branches_in_order();
if (branch1_edges.size() != branch2_edges.size()) {
return false;
}
for (size_t i = 0; i < branch1_edges.size(); i++) {
auto branch1 = branch1_edges[i]->target();
auto branch2 = branch2_edges[i]->target();
if (!branch1->extended_structural_equals(branch2, instruction_equals)) {
return false;
}
if (block_visited.count(branch1->id()) == 0) {
this_blocks.push(branch1);
other_blocks.push(branch2);
block_visited.emplace(branch1->id());
}
}
}

return true;
}

DexPosition* ControlFlowGraph::get_dbg_pos(const cfg::InstructionIterator& it) {
always_assert(&it.cfg() == this);
auto search_block = [](Block* b,
Expand Down
12 changes: 12 additions & 0 deletions libredex/ControlFlow.h
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,8 @@ class Block final {
// TODO?: Should we just always store the throws in index order?
std::vector<Edge*> get_outgoing_throws_in_order() const;

std::vector<Edge*> get_outgoing_branches_in_order() const;

// These assume that the iterator is inside this block
InstructionIterator to_cfg_instruction_iterator(
const ir_list::InstructionIterator& list_it, bool next_on_end = false);
Expand Down Expand Up @@ -417,6 +419,11 @@ class Block final {
std::unique_ptr<SourceBlock> sb);

bool structural_equals(const Block* other) const;
bool structural_equals(const Block* other,
const InstructionEquality& instruction_equals) const;

bool extended_structural_equals(
const Block* other, const InstructionEquality& instruction_equals) const;

private:
friend class ControlFlowGraph;
Expand Down Expand Up @@ -1029,6 +1036,11 @@ class ControlFlowGraph {
return std::move(m_removed_insns);
}

bool structural_equals(const ControlFlowGraph& other) const;

bool structural_equals(const ControlFlowGraph& other,
const InstructionEquality& instruction_equals) const;

private:
friend class Block;

Expand Down
23 changes: 0 additions & 23 deletions service/class-merging/ModelMethodMerger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -370,22 +370,10 @@ std::map<SwitchIndices, DexMethod*> ModelMethodMerger::get_dedupped_indices_map(
const std::vector<DexMethod*>& targets) {
always_assert(targets.size());
std::map<SwitchIndices, DexMethod*> indices_to_callee;

// TODO "structural_equals" feature of editable cfg hasn't been implenmented
// yet. Currently, we still need to use irlist::structural_equals. Therefore,
// we need to clear_cfg before finding equivalent methods. Once
// structural_equals of editable cfg is added, the following clear_cfg will be
// removed.
for (size_t i = 0; i < targets.size(); i++) {
targets[i]->get_code()->clear_cfg();
}
// Find equivalent methods.
std::vector<MethodOrderedSet> duplicates =
method_dedup::group_identical_methods(
targets, m_model_spec.dedup_fill_in_stack_trace);
for (size_t i = 0; i < targets.size(); i++) {
targets[i]->get_code()->build_cfg();
}
for (const auto& duplicate : duplicates) {
SwitchIndices switch_indices;
for (auto& meth : duplicate) {
Expand Down Expand Up @@ -789,20 +777,9 @@ void ModelMethodMerger::dedup_non_ctor_non_virt_methods() {
auto new_to_old_optional =
boost::optional<std::unordered_map<DexMethod*, MethodOrderedSet>>(
new_to_old);
// TODO "structural_equals" feature of editable cfg hasn't been implenmented
// yet. Currently, we still need to use irlist::structural_equals.
// Therefore, we need to clear_cfg before finding equivalent methods. Once
// structural_equals of editable cfg is added, the following clear_cfg will
// be removed.
for (size_t i = 0; i < to_dedup.size(); i++) {
to_dedup[i]->get_code()->clear_cfg();
}
m_stats.m_num_static_non_virt_dedupped += method_dedup::dedup_methods(
m_scope, to_dedup, m_model_spec.dedup_fill_in_stack_trace, replacements,
new_to_old_optional);
for (size_t i = 0; i < replacements.size(); i++) {
replacements[i]->get_code()->build_cfg();
}
// Relocate the remainders.
std::set<DexMethod*, dexmethods_comparator> to_relocate(
replacements.begin(), replacements.end());
Expand Down
17 changes: 9 additions & 8 deletions service/method-dedup/MethodDedup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@
namespace {

struct CodeAsKey {
const IRCode* code;
const cfg::ControlFlowGraph& cfg;
const bool dedup_fill_in_stack_trace;

CodeAsKey(const IRCode* c, bool dedup_fill_in_stack_trace)
: code(c), dedup_fill_in_stack_trace(dedup_fill_in_stack_trace) {}
CodeAsKey(cfg::ControlFlowGraph& c, bool dedup_fill_in_stack_trace)
: cfg(c), dedup_fill_in_stack_trace(dedup_fill_in_stack_trace) {}

static bool non_throw_instruction_equal(const IRInstruction& left,
const IRInstruction& right) {
Expand All @@ -33,16 +33,16 @@ struct CodeAsKey {

bool operator==(const CodeAsKey& other) const {
return dedup_fill_in_stack_trace
? code->structural_equals(*other.code)
: code->structural_equals(*other.code,
non_throw_instruction_equal);
? cfg.structural_equals(other.cfg)
: cfg.structural_equals(other.cfg, non_throw_instruction_equal);
}
};

struct CodeHasher {
size_t operator()(const CodeAsKey& key) const {
size_t result = 0;
for (auto& mie : InstructionIterable(key.code)) {
for (auto& mie : cfg::InstructionIterable(
const_cast<cfg::ControlFlowGraph&>(key.cfg))) {
result ^= mie.insn->hash();
}
return result;
Expand All @@ -57,7 +57,8 @@ std::vector<MethodOrderedSet> get_duplicate_methods_simple(
DuplicateMethods duplicates;
for (DexMethod* method : methods) {
always_assert(method->get_code());
duplicates[CodeAsKey(method->get_code(), dedup_fill_in_stack_trace)]
always_assert(method->get_code()->editable_cfg_built());
duplicates[CodeAsKey(method->get_code()->cfg(), dedup_fill_in_stack_trace)]
.emplace(method);
}

Expand Down
3 changes: 3 additions & 0 deletions test/unit/ControlFlowTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1042,6 +1042,7 @@ TEST_F(ControlFlowTest, deep_copy1) {

cfg::ControlFlowGraph copy;
orig.deep_copy(&copy);
EXPECT_TRUE(orig.structural_equals(copy));
IRList* orig_list = orig.linearize();
IRList* copy_list = copy.linearize();

Expand Down Expand Up @@ -1071,6 +1072,7 @@ TEST_F(ControlFlowTest, deep_copy2) {

cfg::ControlFlowGraph copy;
orig.deep_copy(&copy);
EXPECT_TRUE(orig.structural_equals(copy));
IRList* orig_list = orig.linearize();
IRList* copy_list = copy.linearize();

Expand Down Expand Up @@ -1109,6 +1111,7 @@ TEST_F(ControlFlowTest, deep_copy3) {

cfg::ControlFlowGraph copy;
orig.deep_copy(&copy);
EXPECT_TRUE(orig.structural_equals(copy));
IRList* orig_list = orig.linearize();
IRList* copy_list = copy.linearize();

Expand Down

0 comments on commit 9687260

Please sign in to comment.