in service/method-inliner/Inliner.cpp [519:822]
size_t MultiMethodInliner::inline_inlinables(
DexMethod* caller_method,
const std::vector<Inlinable>& inlinables,
std::vector<IRInstruction*>* deleted_insns) {
auto timer = m_inline_inlinables_timer.scope();
if (for_speed() && m_ab_experiment_context->use_control()) {
return 0;
}
auto caller = caller_method->get_code();
std::unordered_set<IRCode*> need_deconstruct;
if (inline_inlinables_need_deconstruct(caller_method)) {
need_deconstruct.reserve(1 + inlinables.size());
need_deconstruct.insert(caller);
for (const auto& inlinable : inlinables) {
need_deconstruct.insert(inlinable.callee->get_code());
}
for (auto code : need_deconstruct) {
always_assert(!code->editable_cfg_built());
code->build_cfg(/* editable */ true);
// if (deleted_insns != nullptr) {
// code->cfg().set_removed_insn_ownerhsip(false);
// }
}
}
// attempt to inline all inlinable candidates
size_t estimated_caller_size = caller->editable_cfg_built()
? caller->cfg().sum_opcode_sizes()
: caller->sum_opcode_sizes();
// Prefer inlining smaller methods first, so that we are less likely to hit
// overall size limit.
std::vector<Inlinable> ordered_inlinables(inlinables.begin(),
inlinables.end());
std::stable_sort(ordered_inlinables.begin(),
ordered_inlinables.end(),
[&](const Inlinable& a, const Inlinable& b) {
// First, prefer no-return inlinable, as they cut off
// control-flow and thus other inlinables.
if (a.no_return != b.no_return) {
return a.no_return > b.no_return;
}
// Second, prefer smaller methods, to avoid hitting size
// limits too soon
return a.insn_size < b.insn_size;
});
std::vector<DexMethod*> inlined_callees;
boost::optional<reg_t> cfg_next_caller_reg;
if (!m_config.unique_inlined_registers) {
cfg_next_caller_reg = caller->cfg().get_registers_size();
}
size_t calls_not_inlinable{0}, calls_not_inlined{0}, no_returns{0},
unreachable_insns{0}, caller_too_large{0};
size_t intermediate_shrinkings{0};
size_t intermediate_remove_unreachable_blocks{0};
// We only try intermediate remove-unreachable-blocks or shrinking when using
// the cfg-inliner, as it will invalidate irlist iterators, which are used
// with the legacy non-cfg-inliner.
size_t last_intermediate_inlined_callees{0};
// Once blocks might have been freed, which can happen via
// remove_unreachable_blocks and shrinking, callsite pointers are no longer
// valid.
std::unique_ptr<std::unordered_set<const IRInstruction*>> remaining_callsites;
auto recompute_remaining_callsites = [caller, &remaining_callsites,
&ordered_inlinables]() {
if (!remaining_callsites) {
remaining_callsites =
std::make_unique<std::unordered_set<const IRInstruction*>>();
for (const auto& inlinable : ordered_inlinables) {
remaining_callsites->insert(inlinable.insn);
}
}
std::unordered_set<const IRInstruction*> new_remaining_callsites;
for (auto& mie : InstructionIterable(caller->cfg())) {
if (mie.insn->has_method() && remaining_callsites->count(mie.insn)) {
new_remaining_callsites.insert(mie.insn);
}
}
always_assert(new_remaining_callsites.size() <=
remaining_callsites->size());
*remaining_callsites = std::move(new_remaining_callsites);
};
VisibilityChanges visibility_changes;
std::unordered_set<DexMethod*> visibility_changes_for;
size_t init_classes = 0;
for (const auto& inlinable : ordered_inlinables) {
auto callee_method = inlinable.callee;
auto callee = callee_method->get_code();
auto callsite_insn = inlinable.insn;
if (remaining_callsites && !remaining_callsites->count(callsite_insn)) {
if (!inlinable.no_return) {
calls_not_inlined++;
}
continue;
}
if (inlinable.no_return) {
if (!m_config.throw_after_no_return) {
continue;
}
// we are not actually inlining, but just cutting off control-flow
// afterwards, inserting an (unreachable) "throw null" instruction
// sequence.
auto& caller_cfg = caller->cfg();
auto callsite_it = caller_cfg.find_insn(callsite_insn);
if (!callsite_it.is_end()) {
if (m_config.unique_inlined_registers) {
cfg_next_caller_reg = caller_cfg.get_registers_size();
}
auto temp_reg = *cfg_next_caller_reg;
if (temp_reg >= caller_cfg.get_registers_size()) {
caller_cfg.set_registers_size(temp_reg + 1);
}
// Copying to avoid cfg limitation
auto* callsite_copy = new IRInstruction(*callsite_it->insn);
auto* const_insn = (new IRInstruction(OPCODE_CONST))
->set_dest(temp_reg)
->set_literal(0);
auto* throw_insn =
(new IRInstruction(OPCODE_THROW))->set_src(0, temp_reg);
caller_cfg.replace_insns(callsite_it,
{callsite_copy, const_insn, throw_insn});
auto p = caller_cfg.remove_unreachable_blocks();
auto unreachable_insn_count = p.first;
auto registers_size_possibly_reduced = p.second;
if (registers_size_possibly_reduced &&
m_config.unique_inlined_registers) {
caller_cfg.recompute_registers_size();
cfg_next_caller_reg = caller_cfg.get_registers_size();
}
if (unreachable_insn_count) {
unreachable_insns += unreachable_insn_count;
recompute_remaining_callsites();
}
estimated_caller_size = caller_cfg.sum_opcode_sizes();
no_returns++;
}
continue;
}
if (for_speed()) {
// This is expensive, but with shrinking/non-cfg inlining prep there's no
// better way. Needs an explicit check to see whether the instruction has
// already been shrunk away.
auto callsite_it = caller->cfg().find_insn(callsite_insn);
if (!callsite_it.is_end()) {
auto* block = callsite_it.block();
if (!m_inline_for_speed->should_inline_callsite(caller_method,
callee_method, block)) {
calls_not_inlinable++;
continue;
}
}
}
bool caller_too_large_;
auto not_inlinable = !is_inlinable(caller_method, callee_method,
callsite_insn, estimated_caller_size,
inlinable.insn_size, &caller_too_large_);
if (not_inlinable && caller_too_large_ &&
inlined_callees.size() > last_intermediate_inlined_callees) {
intermediate_remove_unreachable_blocks++;
last_intermediate_inlined_callees = inlined_callees.size();
auto p = caller->cfg().remove_unreachable_blocks();
unreachable_insns += p.first;
auto registers_size_possibly_reduced = p.second;
if (registers_size_possibly_reduced &&
m_config.unique_inlined_registers) {
caller->cfg().recompute_registers_size();
cfg_next_caller_reg = caller->cfg().get_registers_size();
}
estimated_caller_size = caller->cfg().sum_opcode_sizes();
recompute_remaining_callsites();
if (!remaining_callsites->count(callsite_insn)) {
calls_not_inlined++;
continue;
}
not_inlinable = !is_inlinable(caller_method, callee_method, callsite_insn,
estimated_caller_size, inlinable.insn_size,
&caller_too_large_);
if (!not_inlinable && m_config.intermediate_shrinking &&
m_shrinker.enabled()) {
intermediate_shrinkings++;
m_shrinker.shrink_method(caller_method);
cfg_next_caller_reg = caller->cfg().get_registers_size();
estimated_caller_size = caller->cfg().sum_opcode_sizes();
recompute_remaining_callsites();
if (!remaining_callsites->count(callsite_insn)) {
calls_not_inlined++;
continue;
}
not_inlinable = !is_inlinable(caller_method, callee_method,
callsite_insn, estimated_caller_size,
inlinable.insn_size, &caller_too_large_);
}
}
if (not_inlinable) {
if (caller_too_large_) {
caller_too_large++;
} else {
calls_not_inlinable++;
}
continue;
}
TRACE(MMINL, 4, "%s",
create_inlining_trace_msg(caller_method, callee_method, callsite_insn)
.c_str());
if (for_speed()) {
std::lock_guard<std::mutex> lock(ab_exp_mutex);
m_ab_experiment_context->try_register_method(caller_method);
}
if (m_config.unique_inlined_registers) {
cfg_next_caller_reg = caller->cfg().get_registers_size();
}
auto timer2 = m_inline_with_cfg_timer.scope();
auto it = m_inlined_invokes_need_cast.find(callsite_insn);
auto needs_receiver_cast =
it == m_inlined_invokes_need_cast.end() ? nullptr : it->second;
auto needs_init_class = get_needs_init_class(callee_method);
bool success = inliner::inline_with_cfg(
caller_method, callee_method, callsite_insn, needs_receiver_cast,
needs_init_class, *cfg_next_caller_reg, inlinable.reduced_cfg);
if (!success) {
calls_not_inlined++;
continue;
}
TRACE(INL, 2, "caller: %s\tcallee: %s",
caller->cfg_built() ? SHOW(caller->cfg()) : SHOW(caller),
SHOW(callee));
estimated_caller_size += inlinable.insn_size;
if (inlinable.reduced_cfg) {
visibility_changes.insert(get_visibility_changes(
*inlinable.reduced_cfg, caller_method->get_class(), callee_method));
} else {
visibility_changes_for.insert(callee_method);
}
if (is_static(callee_method)) {
init_classes++;
}
inlined_callees.push_back(callee_method);
if (type::is_kotlin_lambda(type_class(callee_method->get_class()))) {
info.kotlin_lambda_inlined++;
}
}
if (!inlined_callees.empty()) {
for (auto callee_method : visibility_changes_for) {
visibility_changes.insert(
get_visibility_changes(callee_method, caller_method->get_class()));
}
if (!visibility_changes.empty()) {
std::lock_guard<std::mutex> guard(m_visibility_changes_mutex);
if (m_delayed_visibility_changes) {
m_delayed_visibility_changes->insert(visibility_changes);
} else {
visibility_changes_apply_and_record_make_static(visibility_changes);
}
}
m_inlined.insert(inlined_callees.begin(), inlined_callees.end());
}
for (IRCode* code : need_deconstruct) {
code->clear_cfg(nullptr, deleted_insns);
}
info.calls_inlined += inlined_callees.size();
if (calls_not_inlinable) {
info.calls_not_inlinable += calls_not_inlinable;
}
if (calls_not_inlined) {
info.calls_not_inlined += calls_not_inlined;
}
if (no_returns) {
info.no_returns += no_returns;
}
if (unreachable_insns) {
info.unreachable_insns += unreachable_insns;
}
if (intermediate_shrinkings) {
info.intermediate_shrinkings += intermediate_shrinkings;
}
if (intermediate_remove_unreachable_blocks) {
info.intermediate_remove_unreachable_blocks +=
intermediate_remove_unreachable_blocks;
}
if (caller_too_large) {
info.caller_too_large += caller_too_large;
}
if (init_classes) {
info.init_classes += init_classes;
}
return inlined_callees.size();
}