opt/class-merging/ClassMergingPass.cpp (227 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 "ClassMergingPass.h"
#include "ClassAssemblingUtils.h"
#include "ClassMerging.h"
#include "ConfigFiles.h"
#include "ConfigUtils.h"
#include "DexUtil.h"
#include "MergingStrategies.h"
#include "Show.h"
#include "Trace.h"
using namespace class_merging;
namespace {
template <typename Types>
void load_types(const std::vector<std::string>& type_names, Types& types) {
std::vector<DexType*> ts = utils::get_types(type_names);
for (const auto& t : ts) {
const auto& cls = type_class(t);
if (cls == nullptr) {
TRACE(CLMG, 2, "Missing definition for type %s", SHOW(t));
types.clear();
return;
}
types.insert(t);
}
}
/**
* Verify model specs are consistent
*/
bool verify_model_spec(const ModelSpec& model_spec) {
always_assert_log(
!model_spec.name.empty(),
"[ClassMerging] Wrong specification: model must have \"name\"");
always_assert_log(!model_spec.class_name_prefix.empty(),
"[ClassMerging] Wrong specification: model %s must have "
"\"class_name_prefix\"",
model_spec.name.c_str());
if (model_spec.roots.empty()) {
// To share the configurations easily across apps, we ignore the models
// without roots.
TRACE(CLMG, 2,
"[ClassMerging] Wrong specification: model %s must have \"roots\"",
model_spec.name.c_str());
return false;
}
for (const auto root : model_spec.roots) {
always_assert_log(
root,
"[ClassMerging] Wrong specification: model %s must have \"roots\"",
model_spec.name.c_str());
}
return true;
}
strategy::Strategy get_merging_strategy(const std::string& merging_strategy) {
const static std::unordered_map<std::string, strategy::Strategy>
string_to_strategy = {
{"by_cls_count", strategy::Strategy::BY_CLASS_COUNT},
{"by_code_size", strategy::Strategy::BY_CODE_SIZE},
{"by_refs", strategy::Strategy::BY_REFS}};
always_assert_log(string_to_strategy.count(merging_strategy) > 0,
"Merging strategy %s not found. Please check the list"
" of accepted values.",
merging_strategy.c_str());
return string_to_strategy.at(merging_strategy);
}
TypeTagConfig get_type_tag_config(const std::string& type_tag_config) {
const static std::unordered_map<std::string, TypeTagConfig> string_to_config =
{{"none", TypeTagConfig::NONE},
{"generate", TypeTagConfig::GENERATE},
{"input-pass-type-tag-to-ctor",
TypeTagConfig::INPUT_PASS_TYPE_TAG_TO_CTOR},
{"input-handled", TypeTagConfig::INPUT_HANDLED}};
always_assert_log(string_to_config.count(type_tag_config) > 0,
"Type tag config type %s not found. Please check the list"
" of accepted values.",
type_tag_config.c_str());
TRACE(CLMG, 5, "type tag config %s %d", type_tag_config.c_str(),
string_to_config.at(type_tag_config));
return string_to_config.at(type_tag_config);
}
} // namespace
namespace class_merging {
void ClassMergingPass::bind_config() {
bool process_method_meta;
bind("process_method_meta", false, process_method_meta);
int64_t max_num_dispatch_target;
bind("max_num_dispatch_target", 0, max_num_dispatch_target);
trait(Traits::Pass::unique, true);
// load model specifications
std::vector<Json::Value> models;
bind("models", {}, models);
std::string dflt_interdex_grouping_inferring_mode;
bind("default_interdex_grouping_inferring_mode", "",
dflt_interdex_grouping_inferring_mode);
after_configuration([=] {
if (max_num_dispatch_target > 0) {
m_max_num_dispatch_target =
boost::optional<size_t>(static_cast<size_t>(max_num_dispatch_target));
}
if (models.empty()) return;
auto parse_grouping_inferring_mode =
[](const std::string& s,
ModelSpec::InterDexGroupingInferringMode dflt) {
if (s.empty()) {
return dflt;
}
if (s == "all-types") {
return ModelSpec::InterDexGroupingInferringMode::kAllTypeRefs;
} else if (s == "class-loads") {
return ModelSpec::InterDexGroupingInferringMode::kClassLoads;
} else if (s == "class-loads-bb") {
return ModelSpec::InterDexGroupingInferringMode::
kClassLoadsBasicBlockFiltering;
} else {
always_assert_log(false,
"Unknown interdex-grouping-inferring-mode %s",
s.c_str());
}
};
ModelSpec::InterDexGroupingInferringMode default_mode =
parse_grouping_inferring_mode(
dflt_interdex_grouping_inferring_mode,
ModelSpec().interdex_grouping_inferring_mode);
// load each model spec for erasure
for (auto it = models.begin(); it != models.end(); ++it) {
const auto& value = *it;
always_assert_log(
value.isObject(),
"[ClassMerging] Wrong specification: model in array not an object");
JsonWrapper model_spec = JsonWrapper(value);
ModelSpec model;
model_spec.get("enabled", true, model.enabled);
std::string type_tag_config;
model_spec.get("type_tag_config", "generate", type_tag_config);
model.type_tag_config = get_type_tag_config(type_tag_config);
size_t min_count;
model_spec.get("min_count", 2, min_count);
model.min_count = min_count > 0 ? min_count : 0;
model_spec.get("name", "", model.name);
std::vector<std::string> root_names;
model_spec.get("roots", {}, root_names);
load_types(root_names, model.roots);
std::vector<std::string> excl_names;
model_spec.get("exclude", {}, excl_names);
utils::load_types_and_prefixes(excl_names, model.exclude_types,
model.exclude_prefixes);
model_spec.get("class_name_prefix", "", model.class_name_prefix);
Json::Value generated;
model_spec.get("generated", Json::Value(), generated);
if (!generated.isNull()) {
if (!generated.isObject()) {
fprintf(stderr,
"[ClassMerging] Wrong specification: model in array not an "
"object\n");
m_model_specs.clear();
return;
}
JsonWrapper gen_spec = JsonWrapper(generated);
std::vector<std::string> gen_names;
gen_spec.get("other_roots", {}, gen_names);
load_types(gen_names, model.gen_types);
std::vector<std::string> gen_anno_names;
gen_spec.get("annos", {}, gen_anno_names);
load_types(gen_anno_names, model.gen_annos);
}
std::vector<std::string> const_class_safe_names;
model_spec.get("const_class_safe_types", {}, const_class_safe_names);
load_types(const_class_safe_names, model.const_class_safe_types);
model_spec.get("include_primary_dex", false, model.include_primary_dex);
// Merging strategy is by default `by_cls_count`.
std::string merging_strategy;
model_spec.get("merging_strategy", "by_cls_count", merging_strategy);
model.strategy = get_merging_strategy(merging_strategy);
// InterDex grouping option is by default `non-ordered-set`.
std::string interdex_grouping;
model_spec.get("interdex_grouping", "non-ordered-set", interdex_grouping);
model.interdex_grouping = get_merge_per_interdex_type(interdex_grouping);
always_assert_log(!model.interdex_grouping ||
(model.type_tag_config != TypeTagConfig::NONE),
"Cannot group %s when type tag is not needed.",
model.name.c_str());
size_t max_count;
model_spec.get("max_count", 0, max_count);
Json::Value approximate_shaping;
model_spec.get("approximate_shape_merging", Json::Value(),
model.approximate_shape_merging);
model_spec.get("merge_types_with_static_fields", false,
model.merge_types_with_static_fields);
model_spec.get("keep_debug_info", false, model.keep_debug_info);
model_spec.get("replace_type_like_const_strings", true,
model.replace_type_like_const_strings);
model_spec.get("type_like_const_strings_unsafe", false,
model.type_like_const_strings_unsafe);
if (max_count > 0) {
model.max_count = boost::optional<size_t>(max_count);
}
model.process_method_meta = process_method_meta;
model.max_num_dispatch_target = m_max_num_dispatch_target;
// Assume config based models are all generated code.
model.is_generated_code = true;
std::string usage_mode_str =
model_spec.get("type_usage_mode", std::string(""));
model.interdex_grouping_inferring_mode =
parse_grouping_inferring_mode(usage_mode_str, default_mode);
if (!verify_model_spec(model)) {
continue;
}
m_model_specs.emplace_back(std::move(model));
}
TRACE(CLMG, 2, "[ClassMerging] valid model specs %ld",
m_model_specs.size());
});
}
void ClassMergingPass::run_pass(DexStoresVector& stores,
ConfigFiles& conf,
PassManager& mgr) {
if (m_model_specs.empty()) {
return;
}
auto scope = build_class_scope(stores);
ModelStats total_stats;
for (ModelSpec& model_spec : m_model_specs) {
if (!model_spec.enabled) {
continue;
}
if (conf.force_single_dex() && !model_spec.include_primary_dex) {
TRACE(CLMG, 2,
"Change include_primary_dex to true because the apk will be single "
"dex");
model_spec.include_primary_dex = true;
}
total_stats +=
class_merging::merge_model(scope, conf, mgr, stores, model_spec);
}
post_dexen_changes(scope, stores);
total_stats.update_redex_stats(" total", mgr);
}
static ClassMergingPass s_pass;
} // namespace class_merging