int count_ifs(cfg::ControlFlowGraph& cfg) { size_t num_ifs = 0; for (const auto& mie : InstructionIterable(cfg)) { if (is_conditional_branch(mie.insn->opcode())) { num_ifs++; } } return num_ifs; }
/** * Fill `m_prologue_blocks` and return the register that we're "switching" on * (even if it's not a real switch statement) */ boost::optional<uint16_t> SwitchMethodPartitioning::compute_prologue_blocks( cfg::ControlFlowGraph* cfg, const cp::intraprocedural::FixpointIterator& fixpoint, bool verify_default_case) { for (const cfg::Block* b : cfg->blocks()) { always_assert_log(!b->is_catch(), "SwitchMethodPartitioning does not support methods with " "catch blocks. %d has a catch block in %s", b->id(), SHOW(*cfg)); } // First, add all the prologue blocks that forma a linear chain before the // case block selection blocks (a switch or an if-else tree) begin. for (cfg::Block* b = cfg->entry_block(); b != nullptr; b = b->follow_goto()) { m_prologue_blocks.push_back(b); } { auto last_prologue_block = m_prologue_blocks.back(); auto last_prologue_insn_it = last_prologue_block->get_last_insn(); always_assert(last_prologue_insn_it != last_prologue_block->end()); auto last_prologue_insn = last_prologue_insn_it->insn; // If this method was compiled from a default-case-only switch, there will // be no branch opcode -- the method will always throw an // IllegalArgumentException. auto op = last_prologue_insn->opcode(); always_assert(!verify_default_case || is_branch(op) || op == OPCODE_THROW); if (!is_branch(op)) { return boost::none; } else if (is_switch(op)) { // switch or if-else tree. Not both. return last_prologue_insn->src(0); } } // Handle a tree of if statements in the prologue. d8 emits this // when it would be smaller than a switch statement. The non-leaf nodes of the // tree are prologue blocks. The leaf nodes of the tree are case blocks. // // For example: // load-param v0 // const v1 1 // if-eq v0 v1 CASE_1 // goto EXIT_BLOCK ; or return // const v1 2 // if-eq v0 v1 CASE_2 // goto EXIT_BLOCK ; or return // ... // // Traverse the tree in starting at the end of the linear chain of prologue // blocks and stopping before we reach a leaf. boost::optional<uint16_t> determining_reg; std::queue<cfg::Block*> to_visit; to_visit.push(m_prologue_blocks.back()); while (!to_visit.empty()) { auto b = to_visit.front(); to_visit.pop(); // Leaf nodes have 0 or 1 successors (return or goto the epilogue blocks). // Throw edges are disallowed. if (b->succs().size() >= 2) { // The linear check above and this tree check both account for the // top-most node in the tree. Make sure we don't duplicate it if (b != m_prologue_blocks.back()) { m_prologue_blocks.push_back(b); // Verify there aren't extra instructions in here that we may lose track // of for (const auto& mie : InstructionIterable(b)) { auto insn = mie.insn; auto op = insn->opcode(); always_assert_log(is_const(op) || is_conditional_branch(op), "Unexpected instruction in if-else tree %s", SHOW(insn)); } } for (auto succ : b->succs()) { to_visit.push(succ->target()); } // Make sure all blocks agree on which register is the determiner uint16_t candidate_reg = ::find_determining_reg(b, fixpoint); if (determining_reg == boost::none) { determining_reg = candidate_reg; } else { always_assert_log( *determining_reg == candidate_reg, "Conflict: which register are we switching on? %d != %d in %s", *determining_reg, candidate_reg, SHOW(*cfg)); } } } always_assert_log(determining_reg != boost::none, "Couldn't find determining register in %s", SHOW(*cfg)); return determining_reg; }
void MethodTransform::build_cfg() { // Find the block boundaries std::unordered_map<MethodItemEntry*, std::vector<Block*>> branch_to_targets; std::vector<std::pair<DexTryItem*, Block*>> try_ends; std::unordered_map<DexTryItem*, std::vector<Block*>> try_catches; size_t id = 0; bool in_try = false; m_blocks.emplace_back(new Block(id++)); m_blocks.back()->m_begin = m_fmethod->begin(); // The first block can be a branch target. auto begin = m_fmethod->begin(); if (begin->type == MFLOW_TARGET) { branch_to_targets[begin->target->src].push_back(m_blocks.back()); } for (auto it = m_fmethod->begin(); it != m_fmethod->end(); ++it) { if (it->type == MFLOW_TRY) { if (it->tentry->type == TRY_START) { in_try = true; } else if (it->tentry->type == TRY_END) { in_try = false; } } if (!end_of_block(m_fmethod, it, in_try)) { continue; } // End the current block. auto next = std::next(it); if (next == m_fmethod->end()) { m_blocks.back()->m_end = next; continue; } // Start a new block at the next MethodItem. auto next_block = new Block(id++); if (next->type == MFLOW_OPCODE) { insert_fallthrough(m_fmethod, &*next); next = std::next(it); } m_blocks.back()->m_end = next; next_block->m_begin = next; m_blocks.emplace_back(next_block); // Record branch targets to add edges in the next pass. if (next->type == MFLOW_TARGET) { branch_to_targets[next->target->src].push_back(next_block); continue; } // Record try/catch blocks to add edges in the next pass. if (next->type == MFLOW_TRY) { if (next->tentry->type == TRY_END) { try_ends.emplace_back(next->tentry->tentry, next_block); } else if (next->tentry->type == TRY_CATCH) { try_catches[next->tentry->tentry].push_back(next_block); } } } // Link the blocks together with edges for (auto it = m_blocks.begin(); it != m_blocks.end(); ++it) { // Set outgoing edge if last MIE falls through auto lastmei = (*it)->rbegin(); bool fallthrough = true; if (lastmei->type == MFLOW_OPCODE) { auto lastop = lastmei->insn->opcode(); if (is_goto(lastop) || is_conditional_branch(lastop) || is_multi_branch(lastop)) { fallthrough = !is_goto(lastop); auto const& targets = branch_to_targets[&*lastmei]; for (auto target : targets) { (*it)->m_succs.push_back(target); target->m_preds.push_back(*it); } } else if (is_return(lastop) || lastop == OPCODE_THROW) { fallthrough = false; } } if (fallthrough && std::next(it) != m_blocks.end()) { Block* next = *std::next(it); (*it)->m_succs.push_back(next); next->m_preds.push_back(*it); } } /* * Now add the catch edges. Every block inside a try-start/try-end region * gets an edge to every catch block. This simplifies dataflow analysis * since you can always get the exception state by looking at successors, * without any additional analysis. * * NB: This algorithm assumes that a try-start/try-end region will consist of * sequentially-numbered blocks, which is guaranteed because catch regions * are contiguous in the bytecode, and we generate blocks in bytecode order. */ for (auto tep : try_ends) { auto tryitem = tep.first; auto tryendblock = tep.second; size_t bid = tryendblock->id(); always_assert(bid > 0); --bid; while (true) { auto block = m_blocks[bid]; if (ends_with_may_throw(block)) { auto& catches = try_catches[tryitem]; for (auto catchblock : catches) { block->m_succs.push_back(catchblock); catchblock->m_preds.push_back(block); } } auto begin = block->begin(); if (begin->type == MFLOW_TRY) { auto tentry = begin->tentry; if (tentry->type == TRY_START && tentry->tentry == tryitem) { break; } } always_assert_log(bid > 0, "No beginning of try region found"); --bid; } } TRACE(CFG, 5, "%s\n", show(m_method).c_str()); TRACE(CFG, 5, "%s", show(m_blocks).c_str()); }