void change_visibility(DexMethod* method) { auto code = method->get_code(); always_assert(code != nullptr); editable_cfg_adapter::iterate(code, [](MethodItemEntry& mie) { auto insn = mie.insn; if (insn->has_field()) { auto cls = type_class(insn->get_field()->get_class()); if (cls != nullptr && !cls->is_external()) { set_public(cls); } auto field = resolve_field(insn->get_field(), is_sfield_op(insn->opcode()) ? FieldSearch::Static : FieldSearch::Instance); if (field != nullptr && field->is_concrete()) { set_public(field); set_public(type_class(field->get_class())); // FIXME no point in rewriting opcodes in the method insn->set_field(field); } } else if (insn->has_method()) { auto cls = type_class(insn->get_method()->get_class()); if (cls != nullptr && !cls->is_external()) { set_public(cls); } auto current_method = resolve_method( insn->get_method(), opcode_to_search(insn)); if (current_method != nullptr && current_method->is_concrete()) { set_public(current_method); set_public(type_class(current_method->get_class())); // FIXME no point in rewriting opcodes in the method insn->set_method(current_method); } } else if (insn->has_type()) { auto type = insn->get_type(); auto cls = type_class(type); if (cls != nullptr && !cls->is_external()) { set_public(cls); } } return editable_cfg_adapter::LOOP_CONTINUE; }); std::vector<DexType*> types; if (code->editable_cfg_built()) { code->cfg().gather_catch_types(types); } else { code->gather_catch_types(types); } for (auto type : types) { auto cls = type_class(type); if (cls != nullptr && !cls->is_external()) { set_public(cls); } } }
void SimpleInlinePass::run_pass(DexClassesVector& dexen, ConfigFiles& cfg, PassManager& mgr) { const auto no_inline = no_inline_annos(m_no_inline_annos, cfg); auto scope = build_class_scope(dexen); // gather all inlinable candidates auto methods = gather_non_virtual_methods(scope, no_inline); select_single_called(scope, methods); auto resolver = [&](DexMethod* method, MethodSearch search) { return resolve_method(method, search, resolved_refs); }; // inline candidates MultiMethodInliner inliner( scope, dexen[0], inlinable, resolver, m_inliner_config); inliner.inline_methods(); // delete all methods that can be deleted auto inlined = inliner.get_inlined(); size_t inlined_count = inlined.size(); size_t deleted = delete_methods(scope, inlined, resolver); TRACE(SINL, 3, "recursive %ld\n", inliner.get_info().recursive); TRACE(SINL, 3, "blacklisted meths %ld\n", inliner.get_info().blacklisted); TRACE(SINL, 3, "more than 16 regs %ld\n", inliner.get_info().more_than_16regs); TRACE(SINL, 3, "try/catch in callee %ld\n", inliner.get_info().try_catch_block); TRACE(SINL, 3, "try/catch in caller %ld\n", inliner.get_info().caller_tries); TRACE(SINL, 3, "virtualizing methods %ld\n", inliner.get_info().need_vmethod); TRACE(SINL, 3, "invoke super %ld\n", inliner.get_info().invoke_super); TRACE(SINL, 3, "override inputs %ld\n", inliner.get_info().write_over_ins); TRACE(SINL, 3, "escaped virtual %ld\n", inliner.get_info().escaped_virtual); TRACE(SINL, 3, "known non public virtual %ld\n", inliner.get_info().non_pub_virtual); TRACE(SINL, 3, "non public ctor %ld\n", inliner.get_info().non_pub_ctor); TRACE(SINL, 3, "unknown field %ld\n", inliner.get_info().escaped_field); TRACE(SINL, 3, "non public field %ld\n", inliner.get_info().non_pub_field); TRACE(SINL, 3, "throws %ld\n", inliner.get_info().throws); TRACE(SINL, 3, "array data %ld\n", inliner.get_info().array_data); TRACE(SINL, 3, "multiple returns %ld\n", inliner.get_info().multi_ret); TRACE(SINL, 3, "reference outside of primary %ld\n", inliner.get_info().not_in_primary); TRACE(SINL, 3, "not found %ld\n", inliner.get_info().not_found); TRACE(SINL, 1, "%ld inlined calls over %ld methods and %ld methods removed\n", inliner.get_info().calls_inlined, inlined_count, deleted); }
// Check that visibility / accessibility changes to the current method // won't need to change a referenced method into a virtual or static one. bool gather_invoked_methods_that_prevent_relocation( const DexMethod* method, std::unordered_set<DexMethodRef*>* methods_preventing_relocation) { auto code = method->get_code(); always_assert(code); bool can_relocate = true; for (const auto& mie : InstructionIterable(code)) { auto insn = mie.insn; auto opcode = insn->opcode(); if (is_invoke(opcode)) { auto meth = resolve_method(insn->get_method(), opcode_to_search(insn)); if (!meth && opcode == OPCODE_INVOKE_VIRTUAL && unknown_virtuals::is_method_known_to_be_public(insn->get_method())) { continue; } if (meth) { always_assert(meth->is_def()); if (meth->is_external() && !is_public(meth)) { meth = nullptr; } else if (opcode == OPCODE_INVOKE_DIRECT && !is_init(meth)) { meth = nullptr; } } if (!meth) { can_relocate = false; if (!methods_preventing_relocation) { break; } methods_preventing_relocation->emplace(insn->get_method()); } } } return can_relocate; }
/** * Add to the list the single called. */ void SimpleInlinePass::select_single_called( Scope& scope, std::unordered_set<DexMethod*>& methods) { std::unordered_map<DexMethod*, int> calls; for (const auto& method : methods) { calls[method] = 0; } // count call sites for each method walk_opcodes(scope, [](DexMethod* meth) { return true; }, [&](DexMethod* meth, DexInstruction* insn) { if (is_invoke(insn->opcode())) { auto mop = static_cast<DexOpcodeMethod*>(insn); auto callee = resolve_method( mop->get_method(), opcode_to_search(insn), resolved_refs); if (callee != nullptr && callee->is_concrete() && methods.count(callee) > 0) { calls[callee]++; } } }); // pick methods with a single call site and add to candidates. // This vector usage is only because of logging we should remove it // once the optimization is "closed" std::vector<std::vector<DexMethod*>> calls_group(MAX_COUNT); for (auto call_it : calls) { if (call_it.second >= MAX_COUNT) { calls_group[MAX_COUNT - 1].push_back(call_it.first); continue; } calls_group[call_it.second].push_back(call_it.first); } assert(method_breakup(calls_group)); for (auto callee : calls_group[1]) { inlinable.insert(callee); } }
bool UsedVarsFixpointIterator::is_required(const IRInstruction* insn, const UsedVarsSet& used_vars) const { auto op = insn->opcode(); switch (op) { case IOPCODE_LOAD_PARAM: case IOPCODE_LOAD_PARAM_OBJECT: case IOPCODE_LOAD_PARAM_WIDE: // Control-flow opcodes are always required. case OPCODE_RETURN_VOID: case OPCODE_RETURN: case OPCODE_RETURN_WIDE: case OPCODE_RETURN_OBJECT: case OPCODE_MONITOR_ENTER: case OPCODE_MONITOR_EXIT: case OPCODE_CHECK_CAST: case OPCODE_THROW: case OPCODE_GOTO: case OPCODE_PACKED_SWITCH: case OPCODE_SPARSE_SWITCH: case OPCODE_IF_EQ: case OPCODE_IF_NE: case OPCODE_IF_LT: case OPCODE_IF_GE: case OPCODE_IF_GT: case OPCODE_IF_LE: case OPCODE_IF_EQZ: case OPCODE_IF_NEZ: case OPCODE_IF_LTZ: case OPCODE_IF_GEZ: case OPCODE_IF_GTZ: case OPCODE_IF_LEZ: { return true; } case OPCODE_APUT: case OPCODE_APUT_WIDE: case OPCODE_APUT_OBJECT: case OPCODE_APUT_BOOLEAN: case OPCODE_APUT_BYTE: case OPCODE_APUT_CHAR: case OPCODE_APUT_SHORT: case OPCODE_IPUT: case OPCODE_IPUT_WIDE: case OPCODE_IPUT_OBJECT: case OPCODE_IPUT_BOOLEAN: case OPCODE_IPUT_BYTE: case OPCODE_IPUT_CHAR: case OPCODE_IPUT_SHORT: { const auto& env = m_insn_env_map.at(insn); return is_used_or_escaping_write(env, used_vars, insn->src(1)); } case OPCODE_FILL_ARRAY_DATA: { const auto& env = m_insn_env_map.at(insn); return is_used_or_escaping_write(env, used_vars, insn->src(0)); } case OPCODE_SPUT: case OPCODE_SPUT_WIDE: case OPCODE_SPUT_OBJECT: case OPCODE_SPUT_BOOLEAN: case OPCODE_SPUT_BYTE: case OPCODE_SPUT_CHAR: case OPCODE_SPUT_SHORT: { return true; } case OPCODE_INVOKE_DIRECT: case OPCODE_INVOKE_STATIC: case OPCODE_INVOKE_VIRTUAL: { auto method = resolve_method(insn->get_method(), opcode_to_search(insn)); if (method == nullptr) { return true; } if (assumenosideeffects(method)) { return used_vars.contains(RESULT_REGISTER); } // We could be smarter about virtual calls that may resolve to one of many // callees, but right now we just bail out. if (op == OPCODE_INVOKE_VIRTUAL && !m_non_overridden_virtuals.count(method)) { return true; } if (!m_effect_summaries.count(method)) { return true; } // A call is required if it has a side-effect, if its return value is used, // or if it mutates an argument that may later be read somewhere up the // callstack. auto& summary = m_effect_summaries.at(method); if (summary.effects != EFF_NONE || used_vars.contains(RESULT_REGISTER)) { return true; } const auto& env = m_insn_env_map.at(insn); const auto& mod_params = summary.modified_params; return std::any_of( mod_params.begin(), mod_params.end(), [&](param_idx_t idx) { return is_used_or_escaping_write(env, used_vars, insn->src(idx)); }); } case OPCODE_INVOKE_SUPER: case OPCODE_INVOKE_INTERFACE: { return true; } default: { if (insn->dests_size()) { return used_vars.contains(insn->dest()); } else if (insn->has_move_result()) { return used_vars.contains(RESULT_REGISTER); } return true; } } }