size_t delete_methods( std::vector<DexClass*>& scope, std::unordered_set<DexMethod*>& removable, std::function<DexMethod*(DexMethod*, MethodSearch search)> resolver) { // if a removable candidate is invoked do not delete walk_opcodes(scope, [](DexMethod* meth) { return true; }, [&](DexMethod* meth, DexInstruction* insn) { if (is_invoke(insn->opcode())) { const auto mop = static_cast<DexOpcodeMethod*>(insn); auto callee = resolver(mop->get_method(), opcode_to_search(insn)); if (callee != nullptr) { removable.erase(callee); } } }); size_t deleted = 0; for (auto callee : removable) { if (!callee->is_concrete()) continue; if (do_not_strip(callee)) continue; auto cls = type_class(callee->get_class()); always_assert_log(cls != nullptr, "%s is concrete but does not have a DexClass\n", SHOW(callee)); if (callee->is_virtual()) { cls->get_vmethods().remove(callee); } else { cls->get_dmethods().remove(callee); } deleted++; TRACE(DELMET, 4, "removing %s\n", SHOW(callee)); } return deleted; }
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 MultiMethodInliner::inline_callees( InlineContext& inline_context, std::vector<DexMethod*>& callees) { size_t found = 0; auto caller = inline_context.caller; auto insns = caller->get_code()->get_instructions(); // walk the caller opcodes collecting all candidates to inline // Build a callee to opcode map std::vector<std::pair<DexMethod*, DexOpcodeMethod*>> inlinables; for (auto insn = insns.begin(); insn != insns.end(); ++insn) { if (!is_invoke((*insn)->opcode())) continue; auto mop = static_cast<DexOpcodeMethod*>(*insn); auto callee = resolver(mop->get_method(), opcode_to_search(*insn)); if (callee == nullptr) continue; if (std::find(callees.begin(), callees.end(), callee) == callees.end()) { continue; } always_assert(callee->is_concrete()); found++; inlinables.push_back(std::make_pair(callee, mop)); if (found == callees.size()) break; } if (found != callees.size()) { always_assert(found <= callees.size()); info.not_found += callees.size() - found; } // attempt to inline all inlinable candidates for (auto inlinable : inlinables) { auto callee = inlinable.first; auto mop = inlinable.second; if (!is_inlinable(callee, caller)) continue; auto op = mop->opcode(); if (is_invoke_range(op)) { info.invoke_range++; continue; } TRACE(MMINL, 4, "inline %s (%d) in %s (%d)\n", SHOW(callee), caller->get_code()->get_registers_size(), SHOW(caller), callee->get_code()->get_registers_size() - callee->get_code()->get_ins_size()); change_visibility(callee); MethodTransform::inline_16regs(inline_context, callee, mop); info.calls_inlined++; inlined.insert(callee); } }
/** * Change the visibility of members accessed in a callee as they are moved * to the caller context. * We make everything public but we could be more precise and only * relax visibility as needed. */ void MultiMethodInliner::change_visibility(DexMethod* callee) { TRACE(MMINL, 6, "checking visibility usage of members in %s\n", SHOW(callee)); for (auto insn : callee->get_code()->get_instructions()) { if (insn->has_fields()) { auto fop = static_cast<DexOpcodeField*>(insn); auto field = fop->field(); field = resolve_field(field, is_sfield_op(insn->opcode()) ? FieldSearch::Static : FieldSearch::Instance); if (field != nullptr && field->is_concrete()) { TRACE(MMINL, 6, "changing visibility of %s.%s %s\n", SHOW(field->get_class()), SHOW(field->get_name()), SHOW(field->get_type())); set_public(field); set_public(type_class(field->get_class())); fop->rewrite_field(field); } continue; } if (insn->has_methods()) { auto mop = static_cast<DexOpcodeMethod*>(insn); auto method = mop->get_method(); method = resolver(method, opcode_to_search(insn)); if (method != nullptr && method->is_concrete()) { TRACE(MMINL, 6, "changing visibility of %s.%s: %s\n", SHOW(method->get_class()), SHOW(method->get_name()), SHOW(method->get_proto())); set_public(method); set_public(type_class(method->get_class())); mop->rewrite_method(method); } continue; } if (insn->has_types()) { auto type = static_cast<DexOpcodeType*>(insn)->get_type(); auto cls = type_class(type); if (cls != nullptr && !cls->is_external()) { TRACE(MMINL, 6, "changing visibility of %s\n", SHOW(type)); set_public(cls); } continue; } } }
// 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); } }
MultiMethodInliner::MultiMethodInliner( std::vector<DexClass*>& scope, DexClasses& primary_dex, std::unordered_set<DexMethod*>& candidates, std::function<DexMethod*(DexMethod*, MethodSearch)> resolver) : resolver(resolver) { for (const auto& cls : primary_dex) { primary.insert(cls->get_type()); } // walk every opcode in scope looking for calls to inlinable candidates // and build a map of callers to callees and the reverse callees to callers walk_opcodes(scope, [](DexMethod* meth) { return true; }, [&](DexMethod* meth, DexInstruction* opcode) { if (is_invoke(opcode->opcode())) { auto mop = static_cast<DexOpcodeMethod*>(opcode); auto callee = resolver(mop->get_method(), opcode_to_search(opcode)); if (callee != nullptr && callee->is_concrete() && candidates.find(callee) != candidates.end()) { callee_caller[callee].push_back(meth); caller_callee[meth].push_back(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; } } }