opt/singleimpl/SingleImpl.cpp (122 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 "SingleImpl.h"
#include <functional>
#include <memory>
#include <stdio.h>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include "ClassHierarchy.h"
#include "Debug.h"
#include "DexLoader.h"
#include "DexOutput.h"
#include "DexUtil.h"
#include "PassManager.h"
#include "SingleImplDefs.h"
#include "Trace.h"
#include "Walkers.h"
size_t SingleImplPass::s_invoke_intf_count = 0;
namespace {
constexpr const char* METRIC_REMOVED_INTERFACES = "num_removed_interfaces";
constexpr const char* METRIC_INVOKE_INT_TO_VIRT = "num_invoke_intf_to_virt";
constexpr const char* METRIC_INSERTED_CHECK_CASTS = "num_inserted_check_casts";
constexpr const char* METRIC_RETAINED_CHECK_CASTS = "num_retained_check_casts";
constexpr const char* METRIC_POST_PROCESS_REMOVED_CASTS =
"num_post_process_removed_casts";
constexpr const char* METRIC_POST_PROCESS_REPLACED_CASTS =
"num_post_process_replaced_casts";
constexpr const char* METRIC_POST_PROCESS_WEAKENED_CASTS =
"num_post_process_weakened_casts";
constexpr const char* METRIC_DELETED_REMOVED_INSTRUCTIONS =
"num_deleted_removed_instructions";
/**
* Build a map from interface to the type implementing that
* interface. We also walk up the interface chain and for every interface
* in scope (defined in the DEXes) we add an entry as well. So
* interface B {}
* interface A extends B {}
* class C implements A {}
* generates 2 entries in the map (assuming A, B and C are in the DEXes)
* { A => C, B => C }
* whereas if B was outside the DEXes (i.e. java or android interface)
* we will only have one entry { A => C }
* keep that in mind when using this map
*/
void map_interfaces(const DexTypeList* intf_list,
DexClass* cls,
TypeToTypes& intfs_to_classes) {
for (auto& intf : *intf_list) {
const auto intf_cls = type_class(intf);
if (intf_cls == nullptr || intf_cls->is_external()) continue;
if (std::find(intfs_to_classes[intf].begin(), intfs_to_classes[intf].end(),
cls->get_type()) == intfs_to_classes[intf].end()) {
intfs_to_classes[intf].push_back(cls->get_type());
auto intfs = intf_cls->get_interfaces();
map_interfaces(intfs, cls, intfs_to_classes);
}
}
}
/**
* Collect all interfaces.
*/
void build_type_maps(const Scope& scope,
TypeToTypes& intfs_to_classes,
TypeSet& interfs) {
for (const auto& cls : scope) {
if (is_interface(cls)) {
interfs.insert(cls->get_type());
continue;
}
auto intfs = cls->get_interfaces();
map_interfaces(intfs, cls, intfs_to_classes);
}
}
void collect_single_impl(const TypeToTypes& intfs_to_classes,
TypeMap& single_impl) {
for (const auto& intf_it : intfs_to_classes) {
if (intf_it.second.size() != 1) continue;
auto intf = intf_it.first;
auto intf_cls = type_class(intf);
always_assert(intf_cls && !intf_cls->is_external());
if (intf_cls->get_access() & DexAccessFlags::ACC_ANNOTATION) continue;
auto impl = intf_it.second[0];
auto impl_cls = type_class(impl);
always_assert(impl_cls && !impl_cls->is_external());
// I don't know if it's possible but it's cheap enough to check
if (impl_cls->get_access() & DexAccessFlags::ACC_ANNOTATION) continue;
single_impl[intf] = impl;
}
}
} // namespace
const int MAX_PASSES = 8;
void SingleImplPass::run_pass(DexStoresVector& stores,
ConfigFiles& conf,
PassManager& mgr) {
auto scope = build_class_scope(stores);
ClassHierarchy ch = build_type_hierarchy(scope);
int max_steps = 0;
size_t previous_invoke_intf_count = s_invoke_intf_count;
OptimizeStats stats;
const auto& pg_map = conf.get_proguard_map();
while (true) {
Timer t{std::string("Iteration ").append(std::to_string(max_steps + 1))};
TRACE(INTF, 9, "\tOPTIMIZE ROUND %d", max_steps);
DEBUG_ONLY size_t scope_size = scope.size();
TypeToTypes intfs_to_classes;
TypeSet intfs;
build_type_maps(scope, intfs_to_classes, intfs);
TypeMap single_impl;
collect_single_impl(intfs_to_classes, single_impl);
std::unique_ptr<SingleImplAnalysis> single_impls =
SingleImplAnalysis::analyze(scope, stores, single_impl, intfs, pg_map,
m_pass_config);
auto optimized_stats =
optimize(std::move(single_impls), ch, scope, m_pass_config);
stats += optimized_stats;
if (optimized_stats.removed_interfaces == 0 || ++max_steps >= MAX_PASSES) {
break;
}
redex_assert(scope_size > scope.size());
}
TRACE(INTF, 2, "\ttotal steps %d", max_steps);
TRACE(INTF, 1, "Removed interfaces %ld", removed_count);
TRACE(INTF, 1, "Updated invoke-interface to invoke-virtual %ld",
s_invoke_intf_count - previous_invoke_intf_count);
mgr.incr_metric(METRIC_REMOVED_INTERFACES, stats.removed_interfaces);
mgr.incr_metric(METRIC_INVOKE_INT_TO_VIRT,
s_invoke_intf_count - previous_invoke_intf_count);
mgr.set_metric(METRIC_INSERTED_CHECK_CASTS, stats.inserted_check_casts);
mgr.set_metric(METRIC_DELETED_REMOVED_INSTRUCTIONS,
stats.deleted_removed_instructions);
mgr.set_metric(METRIC_RETAINED_CHECK_CASTS, stats.retained_check_casts);
mgr.set_metric(METRIC_POST_PROCESS_REMOVED_CASTS,
stats.post_process.removed_casts);
mgr.set_metric(METRIC_POST_PROCESS_REPLACED_CASTS,
stats.post_process.replaced_casts);
mgr.set_metric(METRIC_POST_PROCESS_WEAKENED_CASTS,
stats.post_process.weakened_casts);
post_dexen_changes(scope, stores);
}
static SingleImplPass s_pass;