void renumber_registers(IRCode* code, bool width_aware) { auto chains = calculate_ud_chains(code); Rank rank; Parent parent; DefSets def_sets((RankPMap(rank)), (ParentPMap(parent))); for (const auto& mie : InstructionIterable(code)) { if (mie.insn->dests_size()) { def_sets.make_set(mie.insn); } } unify_defs(chains, &def_sets); SymRegMapper sym_reg_mapper(width_aware); for (auto& mie : InstructionIterable(code)) { auto insn = mie.insn; if (insn->dests_size()) { auto sym_reg = sym_reg_mapper.make(def_sets.find_set(insn)); insn->set_dest(sym_reg); } } for (auto& mie : InstructionIterable(code)) { auto insn = mie.insn; for (size_t i = 0; i < insn->srcs_size(); ++i) { auto& defs = chains.at(Use{insn, insn->src(i)}); insn->set_src(i, sym_reg_mapper.at(def_sets.find_set(*defs.begin()))); } } code->set_registers_size(sym_reg_mapper.regs_size()); }
void DexInstruction::verify_encoding() const { auto test = m_count ? new DexInstruction(opcode()) : new DexInstruction(opcode(), 0); if (dests_size()) { test->set_dest(dest()); } for (unsigned i = 0; i < srcs_size(); i++) { test->set_src(i, src(i)); } if (has_range_base()) test->set_range_base(range_base()); if (has_range_size()) test->set_range_size(range_size()); if (has_arg_word_count()) test->set_arg_word_count(arg_word_count()); if (has_literal()) test->set_literal(literal()); if (has_offset()) test->set_offset(offset()); assert_log(m_opcode == test->m_opcode, "%x %x\n", m_opcode, test->m_opcode); for (unsigned i = 0; i < m_count; i++) { assert_log(m_arg[i] == test->m_arg[i], "(%x %x) (%x %x)", m_opcode, m_arg[i], test->m_opcode, test->m_arg[i]); } delete test; }
bool IRInstruction::src_is_wide(size_t i) const { always_assert(i < srcs_size()); if (is_invoke(m_opcode)) { return invoke_src_is_wide(i); } switch (opcode()) { case OPCODE_MOVE_WIDE: case OPCODE_RETURN_WIDE: return i == 0; case OPCODE_CMPL_DOUBLE: case OPCODE_CMPG_DOUBLE: case OPCODE_CMP_LONG: return i == 0 || i == 1; case OPCODE_APUT_WIDE: case OPCODE_IPUT_WIDE: case OPCODE_SPUT_WIDE: return i == 0; case OPCODE_NEG_LONG: case OPCODE_NOT_LONG: case OPCODE_NEG_DOUBLE: case OPCODE_LONG_TO_INT: case OPCODE_LONG_TO_FLOAT: case OPCODE_LONG_TO_DOUBLE: case OPCODE_DOUBLE_TO_INT: case OPCODE_DOUBLE_TO_LONG: case OPCODE_DOUBLE_TO_FLOAT: return i == 0; case OPCODE_ADD_LONG: case OPCODE_SUB_LONG: case OPCODE_MUL_LONG: case OPCODE_DIV_LONG: case OPCODE_REM_LONG: case OPCODE_AND_LONG: case OPCODE_OR_LONG: case OPCODE_XOR_LONG: case OPCODE_ADD_DOUBLE: case OPCODE_SUB_DOUBLE: case OPCODE_MUL_DOUBLE: case OPCODE_DIV_DOUBLE: case OPCODE_REM_DOUBLE: return i == 0 || i == 1; case OPCODE_SHL_LONG: case OPCODE_SHR_LONG: case OPCODE_USHR_LONG: return i == 0; default: return false; } }
void IRInstruction::normalize_registers() { if (is_invoke(opcode())) { auto& args = get_method()->get_proto()->get_args()->get_type_list(); size_t old_srcs_idx{0}; size_t srcs_idx{0}; if (m_opcode != OPCODE_INVOKE_STATIC) { ++srcs_idx; ++old_srcs_idx; } for (size_t args_idx = 0; args_idx < args.size(); ++args_idx) { always_assert_log( old_srcs_idx < srcs_size(), "Invalid arg indices in %s args_idx %d old_srcs_idx %d\n", SHOW(this), args_idx, old_srcs_idx); set_src(srcs_idx++, src(old_srcs_idx)); old_srcs_idx += is_wide_type(args.at(args_idx)) ? 2 : 1; } always_assert(old_srcs_idx == srcs_size()); set_arg_word_count(srcs_idx); } }
uint64_t IRInstruction::hash() const { std::vector<uint64_t> bits; bits.push_back(opcode()); for (size_t i = 0; i < srcs_size(); i++) { bits.push_back(src(i)); } if (dests_size() > 0) { bits.push_back(dest()); } if (has_data()) { size_t size = get_data()->data_size(); const auto& data = get_data()->data(); for (size_t i = 0; i < size; i++) { bits.push_back(data[i]); } } if (has_type()) { bits.push_back(reinterpret_cast<uint64_t>(get_type())); } if (has_field()) { bits.push_back(reinterpret_cast<uint64_t>(get_field())); } if (has_method()) { bits.push_back(reinterpret_cast<uint64_t>(get_method())); } if (has_string()) { bits.push_back(reinterpret_cast<uint64_t>(get_string())); } if (has_literal()) { bits.push_back(get_literal()); } uint64_t result = 0; for (uint64_t elem : bits) { result ^= elem; } return result; }
void GraphBuilder::update_node_constraints(IRList::iterator it, const RangeSet& range_set, Graph* graph) { auto insn = it->insn; auto op = insn->opcode(); if (insn->dests_size()) { auto dest = insn->dest(); auto& node = graph->m_nodes[dest]; if (opcode::is_load_param(op)) { node.m_props.set(Node::PARAM); } node.m_type_domain.meet_with(RegisterTypeDomain(dest_reg_type(insn))); auto max_vreg = max_unsigned_value(dest_bit_width(it)); node.m_max_vreg = std::min(node.m_max_vreg, max_vreg); node.m_width = insn->dest_is_wide() ? 2 : 1; if (max_vreg < max_unsigned_value(16)) { ++node.m_spill_cost; } } for (size_t i = 0; i < insn->srcs_size(); ++i) { auto src = insn->src(i); auto& node = graph->m_nodes[src]; auto type = src_reg_type(insn, i); node.m_type_domain.meet_with(RegisterTypeDomain(type)); reg_t max_vreg; if (range_set.contains(insn)) { max_vreg = max_unsigned_value(16); node.m_props.set(Node::RANGE); } else { max_vreg = max_value_for_src(insn, i, type == RegisterType::WIDE); } node.m_max_vreg = std::min(node.m_max_vreg, max_vreg); if (max_vreg < max_unsigned_value(16)) { ++node.m_spill_cost; } } }
/* * Build the interference graph by adding edges between nodes that are * simultaneously live. * * check-cast instructions have to be handled specially. They are represented * with both a dest (via a move-result-pseudo) and a src in our IR. However, in * actual Dex bytecode, it only takes a single operand which acts as both src * and dest. So when converting IR to Dex bytecode, we need to insert a move * instruction if the src and dest operands differ. We must insert the move * before, not after, the check-cast. Suppose we did not: * * IR | Dex * sget-object v0 LFoo; | sget-object v0 LFoo; * check-cast v0 LBar; | check-cast v0 LBar; * move-result-pseudo v1 | move-object v1 v0 * invoke-static v0 LFoo.a; | invoke-static v0 LFoo.a; // v0 is of type Bar! * * However, inserting before the check-cast is tricky to get right. If the * check-cast is in a try region, we must be careful to not clobber other * live registers. For example, if we had some IRCode like * * B0: * load-param v1 Ljava/lang/Object; * TRY_START * const v0 123 * check-cast v1 LFoo; * B1: * move-result-pseudo v0 * return v0 * TRY_END * B2: * CATCH * // handle failure of check-cast * // Note that v0 has the value of 123 here because the check-cast failed * add-int v0, v0, v0 * * Inserting the move before the check-cast would cause v0 to have an object * (instead of integer) type inside the exception handler. * * The solution is to have the interference graph make check-cast's dest * register interfere with the live registers in both B0 and B1, so that when * the move gets inserted, it does not clobber any live registers. */ Graph GraphBuilder::build(const LivenessFixpointIterator& fixpoint_iter, IRCode* code, reg_t initial_regs, const RangeSet& range_set) { Graph graph; auto ii = InstructionIterable(code); for (auto it = ii.begin(); it != ii.end(); ++it) { GraphBuilder::update_node_constraints(it.unwrap(), range_set, &graph); } auto& cfg = code->cfg(); for (cfg::Block* block : cfg.blocks()) { LivenessDomain live_out = fixpoint_iter.get_live_out_vars_at(block); for (auto it = block->rbegin(); it != block->rend(); ++it) { if (it->type != MFLOW_OPCODE) { continue; } auto insn = it->insn; auto op = insn->opcode(); if (opcode::has_range_form(op)) { graph.m_range_liveness.emplace(insn, live_out); } if (insn->dests_size()) { for (auto reg : live_out.elements()) { if (is_move(op) && reg == insn->src(0)) { continue; } graph.add_edge(insn->dest(), reg); } // We add interference edges between the wide src and dest operands of // an instruction even if the srcs are not live-out. This avoids // allocations like `xor-long v1, v0, v9`, where v1 and v0 overlap -- // even though this is not a verification error, we have observed bugs // in the ART interpreter when handling these sorts of instructions. // However, we still want to be able to coalesce these symregs if they // don't actually interfere based on liveness information, so that we // can remove move-wide opcodes and/or use /2addr encodings. As such, // we insert a specially marked edge that coalescing ignores but // coloring respects. if (insn->dest_is_wide()) { for (size_t i = 0; i < insn->srcs_size(); ++i) { if (insn->src_is_wide(i)) { graph.add_coalesceable_edge(insn->dest(), insn->src(i)); } } } } if (op == OPCODE_CHECK_CAST) { auto move_result_pseudo = std::prev(it)->insn; for (auto reg : live_out.elements()) { graph.add_edge(move_result_pseudo->dest(), reg); } } // adding containment edge between liverange defined in insn and elements // in live-out set of insn if (insn->dests_size()) { for (auto reg : live_out.elements()) { graph.add_containment_edge(insn->dest(), reg); } } fixpoint_iter.analyze_instruction(it->insn, &live_out); // adding containment edge between liverange used in insn and elements // in live-in set of insn for (size_t i = 0; i < insn->srcs_size(); ++i) { for (auto reg : live_out.elements()) { graph.add_containment_edge(insn->src(i), reg); } } } } for (auto& pair : graph.nodes()) { auto reg = pair.first; auto& node = pair.second; if (reg >= initial_regs) { node.m_props.set(Node::SPILL); } assert_log(!node.m_type_domain.is_bottom(), "Type violation of v%u in code:\n%s\n", reg, SHOW(code)); } return graph; }