in service/method-inliner/MethodInliner.cpp [634:945]
void run_inliner(DexStoresVector& stores,
PassManager& mgr,
ConfigFiles& conf,
bool intra_dex /* false */,
InlineForSpeed* inline_for_speed /* nullptr */,
bool inline_bridge_synth_only /* false */) {
always_assert_log(
!mgr.init_class_lowering_has_run(),
"Implementation limitation: The inliner could introduce new "
"init-class instructions.");
if (mgr.no_proguard_rules()) {
TRACE(INLINE, 1,
"MethodInlinePass not run because no ProGuard configuration was "
"provided.");
return;
}
auto scope = build_class_scope(stores);
auto inliner_config = conf.get_inliner_config();
int32_t min_sdk = mgr.get_redex_options().min_sdk;
const api::AndroidSDK* min_sdk_api{nullptr};
if (inliner_config.check_min_sdk_refs) {
mgr.incr_metric("min_sdk", min_sdk);
TRACE(INLINE, 2, "min_sdk: %d", min_sdk);
auto min_sdk_api_file = conf.get_android_sdk_api_file(min_sdk);
if (!min_sdk_api_file) {
mgr.incr_metric("min_sdk_no_file", 1);
TRACE(INLINE, 2, "Android SDK API %d file cannot be found.", min_sdk);
} else {
min_sdk_api = &conf.get_android_sdk_api(min_sdk);
}
}
CalleeCallerInsns true_virtual_callers;
bool cross_dex_penalty = true;
// Gather all inlinable candidates.
if (intra_dex) {
inliner_config.apply_intradex_allowlist();
cross_dex_penalty = false;
}
if (inline_for_speed != nullptr) {
inliner_config.shrink_other_methods = false;
}
if (inline_bridge_synth_only) {
inliner_config.true_virtual_inline = false;
inliner_config.virtual_inline = false;
inliner_config.use_call_site_summaries = false;
inliner_config.shrink_other_methods = false;
inliner_config.intermediate_shrinking = false;
inliner_config.multiple_callers = true;
inliner_config.delete_non_virtuals = true;
inliner_config.shrinker = shrinker::ShrinkerConfig();
inliner_config.shrinker.compute_pure_methods = false;
inliner_config.shrinker.run_const_prop = true;
cross_dex_penalty = false;
}
inliner_config.unique_inlined_registers = false;
std::unique_ptr<const mog::Graph> method_override_graph;
if (inliner_config.virtual_inline) {
method_override_graph = mog::build_graph(scope);
}
init_classes::InitClassesWithSideEffects init_classes_with_side_effects(
scope, conf.create_init_class_insns(), method_override_graph.get());
auto candidates = gather_non_virtual_methods(
scope, method_override_graph.get(), conf.get_do_not_devirt_anon());
// The candidates list computed above includes all constructors, regardless of
// whether it's safe to inline them or not. We'll let the inliner decide
// what to do with constructors.
bool analyze_and_prune_inits = true;
if (inliner_config.virtual_inline && inliner_config.true_virtual_inline) {
gather_true_virtual_methods(*method_override_graph, scope,
&true_virtual_callers);
for (auto& p : true_virtual_callers) {
candidates.insert(p.first);
}
}
// keep a map from refs to defs or nullptr if no method was found
ConcurrentMethodRefCache concurrent_resolved_refs;
auto concurrent_resolver = [&concurrent_resolved_refs](DexMethodRef* method,
MethodSearch search) {
return resolve_method(method, search, concurrent_resolved_refs);
};
walk::parallel::code(scope, [](DexMethod*, IRCode& code) {
code.build_cfg(/* editable */ true);
});
if (inline_bridge_synth_only) {
filter_candidates_bridge_synth_only(mgr, scope, candidates, "initial_");
}
inliner_config.shrinker.analyze_constructors =
inliner_config.shrinker.run_const_prop;
// inline candidates
MultiMethodInliner inliner(scope, init_classes_with_side_effects, stores,
candidates, concurrent_resolver, inliner_config,
min_sdk, intra_dex ? IntraDex : InterDex,
true_virtual_callers, inline_for_speed,
analyze_and_prune_inits, conf.get_pure_methods(),
min_sdk_api, cross_dex_penalty);
inliner.inline_methods(/* need_deconstruct */ false);
walk::parallel::code(scope,
[](DexMethod*, IRCode& code) { code.clear_cfg(); });
// delete all methods that can be deleted
auto inlined = inliner.get_inlined();
size_t inlined_count = inlined.size();
size_t inlined_init_count = 0;
for (DexMethod* m : inlined) {
if (method::is_init(m)) {
inlined_init_count++;
}
}
std::vector<DexMethod*> deleted;
if (inliner_config.delete_non_virtuals) {
// Do not erase true virtual methods that are inlined because we are only
// inlining callsites that are monomorphic, for polymorphic callsite we
// didn't inline, but in run time the callsite may still be resolved to
// those methods that are inlined. We are relying on RMU to clean up
// true virtual methods that are not referenced.
for (const auto& pair : true_virtual_callers) {
inlined.erase(pair.first);
}
deleted = delete_methods(scope, inlined, concurrent_resolver);
}
if (inline_bridge_synth_only) {
std::unordered_set<DexMethod*> deleted_set(deleted.begin(), deleted.end());
std20::erase_if(candidates,
[&](auto method) { return deleted_set.count(method); });
filter_candidates_bridge_synth_only(mgr, scope, candidates, "remaining_");
}
if (!deleted.empty()) {
// Can't really delete because of possible deob links. At least let's erase
// any code.
for (auto* m : deleted) {
m->release_code();
}
}
TRACE(INLINE, 3, "recursive %ld", inliner.get_info().recursive);
TRACE(INLINE, 3, "max_call_stack_depth %ld",
inliner.get_info().max_call_stack_depth);
TRACE(INLINE, 3, "waited seconds %ld", inliner.get_info().waited_seconds);
TRACE(INLINE, 3, "blocklisted meths %ld",
(size_t)inliner.get_info().blocklisted);
TRACE(INLINE, 3, "virtualizing methods %ld",
(size_t)inliner.get_info().need_vmethod);
TRACE(INLINE, 3, "invoke super %ld", (size_t)inliner.get_info().invoke_super);
TRACE(INLINE, 3, "escaped virtual %ld",
(size_t)inliner.get_info().escaped_virtual);
TRACE(INLINE, 3, "known non public virtual %ld",
(size_t)inliner.get_info().non_pub_virtual);
TRACE(INLINE, 3, "non public ctor %ld",
(size_t)inliner.get_info().non_pub_ctor);
TRACE(INLINE, 3, "unknown field %ld",
(size_t)inliner.get_info().escaped_field);
TRACE(INLINE, 3, "non public field %ld",
(size_t)inliner.get_info().non_pub_field);
TRACE(INLINE, 3, "throws %ld", (size_t)inliner.get_info().throws);
TRACE(INLINE, 3, "multiple returns %ld",
(size_t)inliner.get_info().multi_ret);
TRACE(INLINE, 3, "references cross stores %ld",
(size_t)inliner.get_info().cross_store);
TRACE(INLINE, 3, "api level mismatches %ld",
(size_t)inliner.get_info().api_level_mismatch);
TRACE(INLINE, 3, "illegal references %ld",
(size_t)inliner.get_info().problematic_refs);
TRACE(INLINE, 3, "not found %ld", (size_t)inliner.get_info().not_found);
TRACE(INLINE, 3, "caller too large %ld",
(size_t)inliner.get_info().caller_too_large);
TRACE(INLINE, 3, "inlined ctors %zu", inlined_init_count);
TRACE(INLINE, 1, "%ld inlined calls over %ld methods and %ld methods removed",
(size_t)inliner.get_info().calls_inlined, inlined_count,
deleted.size());
const auto& shrinker = inliner.get_shrinker();
mgr.incr_metric("recursive", inliner.get_info().recursive);
mgr.incr_metric("max_call_stack_depth",
inliner.get_info().max_call_stack_depth);
mgr.incr_metric("cross_store", inliner.get_info().cross_store);
mgr.incr_metric("api_level_mismatch", inliner.get_info().api_level_mismatch);
mgr.incr_metric("problematic_refs", inliner.get_info().problematic_refs);
mgr.incr_metric("caller_too_large", inliner.get_info().caller_too_large);
mgr.incr_metric("inlined_init_count", inlined_init_count);
mgr.incr_metric("init_classes", inliner.get_info().init_classes);
mgr.incr_metric("calls_inlined", inliner.get_info().calls_inlined);
mgr.incr_metric("kotlin_lambda_inlined",
inliner.get_info().kotlin_lambda_inlined);
mgr.incr_metric("calls_not_inlinable",
inliner.get_info().calls_not_inlinable);
mgr.incr_metric("no_returns", inliner.get_info().no_returns);
mgr.incr_metric("intermediate_shrinkings",
inliner.get_info().intermediate_shrinkings);
mgr.incr_metric("intermediate_remove_unreachable_blocks",
inliner.get_info().intermediate_remove_unreachable_blocks);
mgr.incr_metric("calls_not_inlined", inliner.get_info().calls_not_inlined);
mgr.incr_metric("methods_removed", deleted.size());
mgr.incr_metric("escaped_virtual", inliner.get_info().escaped_virtual);
mgr.incr_metric("unresolved_methods", inliner.get_info().unresolved_methods);
mgr.incr_metric("known_public_methods",
inliner.get_info().known_public_methods);
mgr.incr_metric(
"constant_invoke_callers_analyzed",
inliner.get_info()
.call_site_summary_stats.constant_invoke_callers_analyzed);
mgr.incr_metric(
"constant_invoke_callers_unreachable",
inliner.get_info()
.call_site_summary_stats.constant_invoke_callers_unreachable);
mgr.incr_metric(
"constant_invoke_callers_unreachable_blocks",
inliner.get_info()
.call_site_summary_stats.constant_invoke_callers_unreachable_blocks);
mgr.incr_metric("constant_invoke_callers_critical_path_length",
inliner.get_info()
.call_site_summary_stats
.constant_invoke_callers_critical_path_length);
mgr.incr_metric("constant_invoke_callees_analyzed",
inliner.get_info().constant_invoke_callees_analyzed);
mgr.incr_metric("constant_invoke_callees_no_return",
inliner.get_info().constant_invoke_callees_no_return);
mgr.incr_metric("constant_invoke_callees_unused_results",
inliner.get_info().constant_invoke_callees_unused_results);
mgr.incr_metric("critical_path_length",
inliner.get_info().critical_path_length);
mgr.incr_metric("methods_shrunk", shrinker.get_methods_shrunk());
mgr.incr_metric("callers", inliner.get_callers());
if (intra_dex) {
mgr.incr_metric("x-dex-callees", inliner.get_x_dex_callees());
}
mgr.incr_metric(
"instructions_eliminated_const_prop",
shrinker.get_const_prop_stats().branches_removed +
shrinker.get_const_prop_stats().unreachable_instructions_removed +
shrinker.get_const_prop_stats().branches_forwarded +
shrinker.get_const_prop_stats().materialized_consts +
shrinker.get_const_prop_stats().added_param_const +
shrinker.get_const_prop_stats().throws +
shrinker.get_const_prop_stats().null_checks);
{
ScopedMetrics sm(mgr);
auto sm_scope = sm.scope("inliner");
shrinker.log_metrics(sm);
}
mgr.incr_metric("instructions_eliminated_cse",
shrinker.get_cse_stats().instructions_eliminated);
mgr.incr_metric("instructions_eliminated_copy_prop",
shrinker.get_copy_prop_stats().moves_eliminated);
mgr.incr_metric(
"instructions_eliminated_localdce",
shrinker.get_local_dce_stats().dead_instruction_count +
shrinker.get_local_dce_stats().unreachable_instruction_count);
mgr.incr_metric("instructions_eliminated_unreachable",
inliner.get_info().unreachable_insns);
mgr.incr_metric("instructions_eliminated_dedup_blocks",
shrinker.get_dedup_blocks_stats().insns_removed);
mgr.incr_metric("blocks_eliminated_by_dedup_blocks",
shrinker.get_dedup_blocks_stats().blocks_removed);
mgr.incr_metric("methods_reg_alloced", shrinker.get_methods_reg_alloced());
mgr.incr_metric("localdce_init_class_instructions_added",
shrinker.get_local_dce_stats().init_class_instructions_added);
mgr.incr_metric(
"localdce_init_class_instructions",
shrinker.get_local_dce_stats().init_classes.init_class_instructions);
mgr.incr_metric("localdce_init_class_instructions_removed",
shrinker.get_local_dce_stats()
.init_classes.init_class_instructions_removed);
mgr.incr_metric("localdce_init_class_instructions_refined",
shrinker.get_local_dce_stats()
.init_classes.init_class_instructions_refined);
// Expose the shrinking timers as Timers.
Timer::add_timer("Inliner.Shrinking.ConstantPropagation",
shrinker.get_const_prop_seconds());
Timer::add_timer("Inliner.Shrinking.CSE", shrinker.get_cse_seconds());
Timer::add_timer("Inliner.Shrinking.CopyPropagation",
shrinker.get_copy_prop_seconds());
Timer::add_timer("Inliner.Shrinking.LocalDCE",
shrinker.get_local_dce_seconds());
Timer::add_timer("Inliner.Shrinking.DedupBlocks",
shrinker.get_dedup_blocks_seconds());
Timer::add_timer("Inliner.Shrinking.RegAlloc",
shrinker.get_reg_alloc_seconds());
Timer::add_timer("Inliner.Inlining.inline_callees",
inliner.get_inline_callees_seconds());
Timer::add_timer("Inliner.Inlining.inline_callees_should_inline",
inliner.get_inline_callees_should_inline_seconds());
Timer::add_timer("Inliner.Inlining.inline_callees_init",
inliner.get_inline_callees_init_seconds());
Timer::add_timer("Inliner.Inlining.inline_inlinables",
inliner.get_inline_inlinables_seconds());
Timer::add_timer("Inliner.Inlining.inline_with_cfg",
inliner.get_inline_with_cfg_seconds());
Timer::add_timer("Inliner.Inlining.call_site_inlined_cost",
inliner.get_call_site_inlined_cost_seconds());
Timer::add_timer("Inliner.Inlining.cannot_inline_sketchy_code",
inliner.get_cannot_inline_sketchy_code_timer_seconds());
}