opt/interdex/InterDexPass.cpp (243 lines of code) (raw):
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "InterDexPass.h"
#include "ConfigFiles.h"
#include "DexClass.h"
#include "DexUtil.h"
#include "PassManager.h"
#include "Show.h"
#include "StlUtil.h"
#include "WorkQueue.h"
namespace {
/**
* Generated stores need to be added to the root store.
* We achieve this, by adding all the dexes from those stores after the root
* store.
*/
void treat_generated_stores(DexStoresVector& stores,
interdex::InterDex* interdex) {
std20::erase_if(stores, [&](auto& s) {
if (s.is_generated()) {
interdex->add_dexes_from_store(s);
return true;
}
return false;
});
}
} // namespace
namespace interdex {
void InterDexPass::bind_config() {
bind("static_prune", false, m_static_prune);
bind("emit_canaries", true, m_emit_canaries);
bind("normal_primary_dex", false, m_normal_primary_dex);
bind("keep_primary_order", true, m_keep_primary_order);
always_assert_log(m_keep_primary_order || m_normal_primary_dex,
"We always need to respect primary dex order if we treat "
"the primary dex as a special dex.");
bind("linear_alloc_limit", 11600 * 1024, m_linear_alloc_limit);
bind("reserved_frefs", 0, m_reserved_frefs,
"A relief valve for field refs within each dex in case a legacy "
"optimization introduces a new field reference without declaring it "
"explicitly to the InterDex pass");
bind("reserved_trefs", 0, m_reserved_trefs,
"A relief valve for type refs within each dex in case a legacy "
"optimization introduces a new type reference without declaring it "
"explicitly to the InterDex pass");
bind("reserved_mrefs", 0, m_reserved_mrefs,
"A relief valve for methods refs within each dex in case a legacy "
"optimization introduces a new method reference without declaring it "
"explicitly to the InterDex pass");
bind("minimize_cross_dex_refs", false, m_minimize_cross_dex_refs);
bind("minimize_cross_dex_refs_method_ref_weight",
m_minimize_cross_dex_refs_config.method_ref_weight,
m_minimize_cross_dex_refs_config.method_ref_weight);
bind("minimize_cross_dex_refs_field_ref_weight",
m_minimize_cross_dex_refs_config.field_ref_weight,
m_minimize_cross_dex_refs_config.field_ref_weight);
bind("minimize_cross_dex_refs_type_ref_weight",
m_minimize_cross_dex_refs_config.type_ref_weight,
m_minimize_cross_dex_refs_config.type_ref_weight);
bind("minimize_cross_dex_refs_string_ref_weight",
m_minimize_cross_dex_refs_config.string_ref_weight,
m_minimize_cross_dex_refs_config.string_ref_weight);
bind("minimize_cross_dex_refs_method_seed_weight",
m_minimize_cross_dex_refs_config.method_seed_weight,
m_minimize_cross_dex_refs_config.method_seed_weight);
bind("minimize_cross_dex_refs_field_seed_weight",
m_minimize_cross_dex_refs_config.field_seed_weight,
m_minimize_cross_dex_refs_config.field_seed_weight);
bind("minimize_cross_dex_refs_type_ref_weight",
m_minimize_cross_dex_refs_config.type_seed_weight,
m_minimize_cross_dex_refs_config.type_seed_weight);
bind("minimize_cross_dex_refs_string_ref_weight",
m_minimize_cross_dex_refs_config.string_seed_weight,
m_minimize_cross_dex_refs_config.string_seed_weight);
bind("minimize_cross_dex_refs_relocate_static_methods", false,
m_cross_dex_relocator_config.relocate_static_methods);
bind("minimize_cross_dex_refs_relocate_non_static_direct_methods", false,
m_cross_dex_relocator_config.relocate_non_static_direct_methods);
bind("minimize_cross_dex_refs_relocate_virtual_methods", false,
m_cross_dex_relocator_config.relocate_virtual_methods);
bind("fill_last_coldstart_dex", m_fill_last_coldstart_dex,
m_fill_last_coldstart_dex);
// The actual number of relocated methods per class tends to be just a
// fraction of this number, as relocated methods get re-relocated back into
// their original class when they end up in the same dex.
bind("max_relocated_methods_per_class", 200,
m_cross_dex_relocator_config.max_relocated_methods_per_class);
bind("can_touch_coldstart_cls", false, m_can_touch_coldstart_cls);
bind("can_touch_coldstart_extended_cls", false,
m_can_touch_coldstart_extended_cls);
bind("expect_order_list", false, m_expect_order_list);
bind("sort_remaining_classes", false, m_sort_remaining_classes,
"Whether to sort classes in non-primary, non-perf-sensitive dexes "
"according to their inheritance hierarchies");
bind("methods_for_canary_clinit_reference", {},
m_methods_for_canary_clinit_reference,
"If set, canary classes will have a clinit generated which call the "
"specified methods, if they exist");
bind("transitively_close_interdex_order", m_transitively_close_interdex_order,
m_transitively_close_interdex_order);
trait(Traits::Pass::unique, true);
}
void InterDexPass::run_pass(
const Scope& original_scope,
const XStoreRefs& xstore_refs,
const init_classes::InitClassesWithSideEffects&
init_classes_with_side_effects,
DexStoresVector& stores,
DexClassesVector& dexen,
std::vector<std::unique_ptr<InterDexPassPlugin>>& plugins,
ConfigFiles& conf,
PassManager& mgr,
const ReserveRefsInfo& refs_info) {
mgr.set_metric(METRIC_LINEAR_ALLOC_LIMIT, m_linear_alloc_limit);
mgr.set_metric(METRIC_RESERVED_FREFS, refs_info.frefs);
mgr.set_metric(METRIC_RESERVED_TREFS, refs_info.trefs);
mgr.set_metric(METRIC_RESERVED_MREFS, refs_info.mrefs);
mgr.set_metric(METRIC_EMIT_CANARIES, m_emit_canaries);
bool force_single_dex = conf.get_json_config().get("force_single_dex", false);
InterDex interdex(
original_scope, dexen, mgr.asset_manager(), conf, plugins,
m_linear_alloc_limit, m_static_prune, m_normal_primary_dex,
m_keep_primary_order, force_single_dex, m_emit_canaries,
m_minimize_cross_dex_refs, m_fill_last_coldstart_dex,
m_minimize_cross_dex_refs_config, m_cross_dex_relocator_config,
refs_info.frefs, refs_info.trefs, refs_info.mrefs, &xstore_refs,
mgr.get_redex_options().min_sdk, m_sort_remaining_classes,
m_methods_for_canary_clinit_reference, init_classes_with_side_effects,
m_transitively_close_interdex_order);
if (m_expect_order_list) {
always_assert_log(
!interdex.get_interdex_types().empty(),
"Either no betamap was provided, or an empty list was passed in. FIX!");
}
interdex.run();
treat_generated_stores(stores, &interdex);
dexen = interdex.take_outdex();
auto final_scope = build_class_scope(stores);
interdex.cleanup(final_scope);
for (const auto& plugin : plugins) {
plugin->cleanup(final_scope);
}
mgr.set_metric(METRIC_COLD_START_SET_DEX_COUNT,
interdex.get_num_cold_start_set_dexes());
mgr.set_metric(METRIC_SCROLL_SET_DEX_COUNT, interdex.get_num_scroll_dexes());
mgr.set_metric("transitive_added", interdex.get_transitive_closure_added());
mgr.set_metric("transitive_moved", interdex.get_transitive_closure_moved());
plugins.clear();
const auto& cross_dex_ref_minimizer_stats =
interdex.get_cross_dex_ref_minimizer_stats();
mgr.set_metric(METRIC_REORDER_CLASSES, cross_dex_ref_minimizer_stats.classes);
mgr.set_metric(METRIC_REORDER_RESETS, cross_dex_ref_minimizer_stats.resets);
mgr.set_metric(METRIC_REORDER_REPRIORITIZATIONS,
cross_dex_ref_minimizer_stats.reprioritizations);
const auto& worst_classes = cross_dex_ref_minimizer_stats.worst_classes;
for (size_t i = 0; i < worst_classes.size(); ++i) {
auto& p = worst_classes.at(i);
std::string metric =
METRIC_REORDER_CLASSES_WORST + std::to_string(i) + "_" + SHOW(p.first);
mgr.set_metric(metric, p.second);
}
const auto cross_dex_relocator_stats =
interdex.get_cross_dex_relocator_stats();
mgr.set_metric(METRIC_CLASSES_ADDED_FOR_RELOCATED_METHODS,
cross_dex_relocator_stats.classes_added_for_relocated_methods);
mgr.set_metric(METRIC_RELOCATABLE_STATIC_METHODS,
cross_dex_relocator_stats.relocatable_static_methods);
mgr.set_metric(
METRIC_RELOCATABLE_NON_STATIC_DIRECT_METHODS,
cross_dex_relocator_stats.relocatable_non_static_direct_methods);
mgr.set_metric(METRIC_RELOCATABLE_VIRTUAL_METHODS,
cross_dex_relocator_stats.relocatable_virtual_methods);
mgr.set_metric(METRIC_RELOCATED_STATIC_METHODS,
cross_dex_relocator_stats.relocated_static_methods);
mgr.set_metric(METRIC_RELOCATED_NON_STATIC_DIRECT_METHODS,
cross_dex_relocator_stats.relocated_non_static_direct_methods);
mgr.set_metric(METRIC_RELOCATED_VIRTUAL_METHODS,
cross_dex_relocator_stats.relocated_virtual_methods);
mgr.set_metric(METRIC_CURRENT_CLASSES_WHEN_EMITTING_REMAINING,
interdex.get_current_classes_when_emitting_remaining());
}
void InterDexPass::run_pass_on_nonroot_store(
const Scope& original_scope,
const XStoreRefs& xstore_refs,
const init_classes::InitClassesWithSideEffects&
init_classes_with_side_effects,
DexClassesVector& dexen,
ConfigFiles& conf,
PassManager& mgr,
const ReserveRefsInfo& refs_info) {
// Setup default configs for non-root store
// For now, no plugins configured for non-root stores to run.
std::vector<std::unique_ptr<InterDexPassPlugin>> plugins;
// Cross dex ref minimizers are disabled for non-root stores
// TODO: Make this logic cleaner when these features get enabled for non-root
// stores. Would also need to clean up after it.
cross_dex_ref_minimizer::CrossDexRefMinimizerConfig cross_dex_refs_config;
CrossDexRelocatorConfig cross_dex_relocator_config;
// Initialize interdex and run for nonroot store
InterDex interdex(
original_scope, dexen, mgr.asset_manager(), conf, plugins,
m_linear_alloc_limit, m_static_prune, m_normal_primary_dex,
m_keep_primary_order, false /* force single dex */,
false /* emit canaries */, false /* minimize_cross_dex_refs */,
/* fill_last_coldstart_dex=*/false, cross_dex_refs_config,
cross_dex_relocator_config, refs_info.frefs, refs_info.trefs,
refs_info.mrefs, &xstore_refs, mgr.get_redex_options().min_sdk,
m_sort_remaining_classes, m_methods_for_canary_clinit_reference,
init_classes_with_side_effects, m_transitively_close_interdex_order);
interdex.run_on_nonroot_store();
dexen = interdex.take_outdex();
}
void InterDexPass::run_pass(DexStoresVector& stores,
ConfigFiles& conf,
PassManager& mgr) {
if (mgr.no_proguard_rules()) {
TRACE(
IDEX, 1,
"InterDexPass not run because no ProGuard configuration was provided.");
return;
}
Scope original_scope = build_class_scope(stores);
init_classes::InitClassesWithSideEffects init_classes_with_side_effects(
original_scope, conf.create_init_class_insns());
XStoreRefs xstore_refs(stores);
// Setup all external plugins.
InterDexRegistry* registry = static_cast<InterDexRegistry*>(
PluginRegistry::get().pass_registry(INTERDEX_PASS_NAME));
auto plugins = registry->create_plugins();
ReserveRefsInfo refs_info(m_reserved_frefs, m_reserved_trefs,
m_reserved_mrefs);
for (const auto& plugin : plugins) {
plugin->configure(original_scope, conf);
refs_info.frefs += plugin->reserve_frefs();
refs_info.trefs += plugin->reserve_trefs();
refs_info.mrefs += plugin->reserve_mrefs();
}
std::vector<DexStore*> parallel_stores;
for (auto& store : stores) {
if (store.is_root_store()) {
run_pass(original_scope, xstore_refs, init_classes_with_side_effects,
stores, store.get_dexen(), plugins, conf, mgr, refs_info);
} else if (!store.is_generated()) {
parallel_stores.push_back(&store);
}
}
workqueue_run<DexStore*>(
[&](DexStore* store) {
run_pass_on_nonroot_store(original_scope, xstore_refs,
init_classes_with_side_effects,
store->get_dexen(), conf, mgr, refs_info);
},
parallel_stores);
++m_run;
// For the last invocation, record that final interdex has been done.
if (m_eval == m_run) {
mgr.record_running_interdex();
}
}
static InterDexPass s_pass;
} // namespace interdex