opt/bridge/Bridge.cpp (354 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 "Bridge.h"
#include <stdio.h>
#include <string>
#include <tuple>
#include <unordered_map>
#include <utility>
#include <vector>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/functional/hash.hpp>
#include "ClassHierarchy.h"
#include "Debug.h"
#include "DexClass.h"
#include "DexLoader.h"
#include "DexOutput.h"
#include "DexUtil.h"
#include "IRCode.h"
#include "IRInstruction.h"
#include "LegacyInliner.h"
#include "PassManager.h"
#include "ReachableClasses.h"
#include "RefChecker.h"
#include "Show.h"
#include "Trace.h"
#include "Walkers.h"
namespace {
constexpr const char* METRIC_BRIDGES_REMOVED = "bridges_removed_count";
constexpr const char* METRIC_ILLEGAL_REFS = "bridges_illegal_refs";
constexpr const char* METRIC_BRIDGES_TO_OPTIMIZE = "bridges_to_optimize_count";
DexMethodRef* match_pattern(DexMethod* bridge) {
auto code = bridge->get_code();
if (!code) return nullptr;
auto ii = InstructionIterable(code);
auto it = ii.begin();
auto end = ii.end();
while (it != end && opcode::is_a_load_param(it->insn->opcode())) {
++it;
}
while (it != end) {
if (it->insn->opcode() != OPCODE_CHECK_CAST) break;
// skip past the move-result-pseudo opcode
std::advance(it, 2);
}
always_assert_log(it != end, "In %s", SHOW(bridge));
if (it->insn->opcode() != OPCODE_INVOKE_DIRECT &&
it->insn->opcode() != OPCODE_INVOKE_STATIC) {
TRACE(BRIDGE, 5, "Rejecting unhandled pattern: `%s'", SHOW(bridge));
return nullptr;
}
auto invoke = it->insn;
++it;
if (opcode::is_a_move_result(it->insn->opcode())) {
++it;
}
if (!opcode::is_a_return(it->insn->opcode())) {
TRACE(BRIDGE, 5, "Rejecting unhandled pattern: `%s'", SHOW(bridge));
return nullptr;
}
++it;
if (it != end) return nullptr;
auto bridgee_ref = invoke->get_method();
if (bridgee_ref->get_class() != bridge->get_class()) {
TRACE(BRIDGE, 5, "Rejecting unhandled pattern: `%s'", SHOW(bridge));
return nullptr;
}
return bridgee_ref;
}
bool is_optimization_candidate(DexMethod* bridge, DexMethod* bridgee) {
if (!can_delete(bridgee)) {
TRACE(BRIDGE, 5, "Cannot delete bridgee! bridge: %s\n bridgee: %s",
SHOW(bridge), SHOW(bridgee));
return false;
}
if (!bridgee->get_code()) {
TRACE(BRIDGE, 5, "Rejecting, bridgee has no code: `%s'", SHOW(bridge));
return false;
}
return true;
}
DexMethod* find_bridgee(DexMethod* bridge) {
auto bridgee_ref = match_pattern(bridge);
if (!bridgee_ref) {
return nullptr;
}
auto bridgee = bridgee_ref->as_def();
if (!bridgee) {
return nullptr;
}
if (!is_optimization_candidate(bridge, bridgee)) {
return nullptr;
}
return bridgee;
}
bool signature_matches(DexMethod* a, DexMethod* b) {
return a->get_name() == b->get_name() && a->get_proto() == b->get_proto();
}
bool has_bridgelike_access(DexMethod* m) {
return m->is_virtual() &&
(is_bridge(m) ||
(is_synthetic(m) && !is_static(m) && !method::is_constructor(m)));
}
void do_inlining(DexMethod* bridge, DexMethod* bridgee) {
bridge->set_access(bridge->get_access() & ~(ACC_BRIDGE | ACC_SYNTHETIC));
auto code = bridge->get_code();
auto invoke =
std::find_if(code->begin(), code->end(), [](const MethodItemEntry& mie) {
return mie.type == MFLOW_OPCODE &&
opcode::is_an_invoke(mie.insn->opcode());
});
legacy_inliner::inline_tail_call(bridge, bridgee, invoke);
}
} // namespace
////////////////////////////////////////////////////////////////////////////////
/*
* Synthetic bridge removal optimization.
*
* This pass removes bridge methods that javac creates to provide argument and
* return-type covariance. Bridge methods take the general form:
*
* check-cast* (for checking covariant arg types)
* invoke-{direct,virtual,static} bridged-method
* move-result
* return
*
* For conciseness we refer to the bridged method as the "bridgee". To
* optimize this pattern we inline the bridgee into the bridge, by replacing
* the invoke- and adjusting the check-casts as necessary. We can then delete
* the bridgee.
*
* If the bridgee is referenced directly by any method other than the bridge,
* we don't apply this optimization. In this case we couldn't safely remove
* the bridgee, so inlining it somewhere would simply bloat the code.
*
* NB: The BRIDGE access flag isn't used for synthetic wrappers that implement
* args/return of generics, but it's the same concept.
*/
class BridgeRemover {
using MethodRef =
std::tuple<const DexType*, const DexString*, const DexProto*>;
struct MethodRefHash {
size_t operator()(const MethodRef& m) const {
size_t seed = 0;
boost::hash_combine(seed, std::get<0>(m));
boost::hash_combine(seed, std::get<1>(m));
boost::hash_combine(seed, std::get<2>(m));
return seed;
}
};
const XStoreRefs& m_xstores;
const std::vector<std::unique_ptr<RefChecker>>& m_ref_checkers;
const std::vector<DexClass*>* m_scope;
ClassHierarchy m_ch;
PassManager& m_mgr;
std::unordered_map<DexMethod*, DexMethod*> m_bridges_to_bridgees;
std::unordered_multimap<MethodRef, DexMethod*, MethodRefHash>
m_potential_bridgee_refs;
size_t m_illegal_refs{0};
void find_bridges() {
walk::methods(*m_scope, [&](DexMethod* m) {
if (has_bridgelike_access(m)) {
auto bridgee = find_bridgee(m);
if (!bridgee) return;
m_bridges_to_bridgees.emplace(m, bridgee);
TRACE(BRIDGE,
5,
"Bridge:%p:%s\nBridgee:%p:%s",
m,
SHOW(m),
bridgee,
SHOW(bridgee));
}
});
}
void search_hierarchy_for_matches(DexMethod* bridge, DexMethod* bridgee) {
/*
* Direct reference. The only one if it's non-virtual.
*/
auto clstype = bridgee->get_class();
auto name = bridgee->get_name();
auto proto = bridgee->get_proto();
TRACE(BRIDGE, 5, " %s %s %s", SHOW(clstype), SHOW(name), SHOW(proto));
m_potential_bridgee_refs.emplace(MethodRef(clstype, name, proto), bridge);
if (!bridgee->is_virtual()) return;
/*
* Search super classes
*
* A bridge method in a derived class may be referred to using the name
* of a super class if a method with a matching signature is defined in
* that super class.
*
* To build the set of potential matches, we accumulate potential refs in
* maybe_refs, and when we find a matching signature in a super class, we
* add everything in maybe_refs to the set.
*/
std::vector<std::pair<MethodRef, DexMethod*>> maybe_refs;
for (auto super = type_class(type_class(clstype)->get_super_class());
super != nullptr;
super = type_class(super->get_super_class())) {
maybe_refs.emplace_back(MethodRef(super->get_type(), name, proto),
bridge);
for (auto vmethod : const_cast<const DexClass*>(super)->get_vmethods()) {
if (signature_matches(bridgee, vmethod)) {
for (auto DEBUG_ONLY refp : maybe_refs) {
TRACE(BRIDGE,
5,
" %s %s %s",
SHOW(std::get<0>(refp.first)),
SHOW(std::get<1>(refp.first)),
SHOW(std::get<2>(refp.first)));
}
m_potential_bridgee_refs.insert(maybe_refs.begin(), maybe_refs.end());
maybe_refs.clear();
}
}
}
/*
* Search sub classes
*
* Easy. Any subclass can refer to the bridgee.
*/
auto subclasses = get_all_children(m_ch, clstype);
for (auto subclass : subclasses) {
m_potential_bridgee_refs.emplace(MethodRef(subclass, name, proto),
bridge);
TRACE(BRIDGE, 5, " %s %s %s", SHOW(subclass), SHOW(name), SHOW(proto));
}
}
void find_potential_bridgee_refs() {
for (auto bpair : m_bridges_to_bridgees) {
auto bridge = bpair.first;
auto bridgee = bpair.second;
TRACE(BRIDGE, 5, "Bridge method: %s", SHOW(bridge));
TRACE(BRIDGE, 5, " Bridgee: %s", SHOW(bridgee));
TRACE(BRIDGE, 5, " Potential references:");
search_hierarchy_for_matches(bridge, bridgee);
}
}
void exclude_referenced_bridgee(DexMethod* code_method, IRCode& code) {
for (auto& mie : InstructionIterable(&code)) {
auto inst = mie.insn;
if (!opcode::is_an_invoke(inst->opcode())) continue;
auto method = inst->get_method();
auto range = m_potential_bridgee_refs.equal_range(MethodRef(
method->get_class(), method->get_name(), method->get_proto()));
for (auto it = range.first; it != range.second; ++it) {
auto referenced_bridge = it->second;
// Don't count the bridge itself
if (referenced_bridge == code_method) continue;
TRACE(BRIDGE,
5,
"Rejecting, reference `%s.%s.%s' in `%s' blocks `%s'",
SHOW(method->get_class()),
SHOW(method->get_name()),
SHOW(method->get_proto()),
SHOW(code_method),
SHOW(referenced_bridge));
m_bridges_to_bridgees.erase(referenced_bridge);
}
}
}
void exclude_referenced_bridgees() {
std::vector<DexMethodRef*> refs;
auto visit_methods = [&refs](DexMethod* m) {
auto const& anno = m->get_anno_set();
if (anno) anno->gather_methods(refs);
auto const& param_anno = m->get_param_anno();
if (param_anno) {
for (auto const& pair : *param_anno) {
pair.second->gather_methods(refs);
}
}
};
for (auto const& cls : *m_scope) {
auto const& anno = cls->get_anno_set();
if (anno) anno->gather_methods(refs);
for (auto const& m : cls->get_dmethods()) {
visit_methods(m);
}
for (auto const& m : cls->get_vmethods()) {
visit_methods(m);
}
for (auto const& f : cls->get_sfields()) {
f->gather_methods(refs);
}
for (auto const& f : cls->get_ifields()) {
f->gather_methods(refs);
}
}
std::unordered_set<DexMethod*> refs_set;
for (const auto& ref : refs) {
auto method = ref->as_def();
if (!method) {
continue;
}
refs_set.insert(method);
}
std::vector<DexMethod*> kill_me;
for (auto const& p : m_bridges_to_bridgees) {
if (refs_set.count(p.second)) {
kill_me.push_back(p.first);
}
}
for (auto const& kill : kill_me) {
m_bridges_to_bridgees.erase(kill);
}
walk::code(
*m_scope,
[](DexMethod*) { return true; },
[&](DexMethod* m, IRCode& code) {
exclude_referenced_bridgee(m, code);
});
}
void inline_bridges() {
for (auto it = m_bridges_to_bridgees.begin();
it != m_bridges_to_bridgees.end();) {
auto& bpair = *it;
auto bridge = bpair.first;
auto bridgee = bpair.second;
auto bridge_store_idx = m_xstores.get_store_idx(bridge->get_class());
auto& ref_checker = m_ref_checkers.at(bridge_store_idx);
if (ref_checker->check_method_and_code(bridgee)) {
TRACE(BRIDGE, 5, "Inlining %s", SHOW(bridge));
do_inlining(bridge, bridgee);
it++;
continue;
}
TRACE(BRIDGE, 5, "Not inlining %s due to illegal refs", SHOW(bridge));
m_illegal_refs++;
it = m_bridges_to_bridgees.erase(it);
}
}
void delete_unused_bridgees() {
for (auto bpair : m_bridges_to_bridgees) {
auto bridge = bpair.first;
auto bridgee = bpair.second;
always_assert_log(bridge->is_virtual(),
"bridge: %s\nbridgee: %s",
SHOW(bridge),
SHOW(bridgee));
// TODO: Bridgee won't necessarily be direct once we expand this
// optimization
redex_assert(!bridgee->is_virtual());
auto cls = type_class(bridgee->get_class());
cls->remove_method(bridgee);
DexMethod::erase_method(bridgee);
}
}
public:
BridgeRemover(const XStoreRefs& xstores,
const std::vector<std::unique_ptr<RefChecker>>& ref_checkers,
const std::vector<DexClass*>& scope,
PassManager& mgr)
: m_xstores(xstores),
m_ref_checkers(ref_checkers),
m_scope(&scope),
m_mgr(mgr) {
m_ch = build_type_hierarchy(scope);
}
void run() {
find_bridges();
find_potential_bridgee_refs();
exclude_referenced_bridgees();
TRACE(BRIDGE, 5, "%lu bridges to optimize", m_bridges_to_bridgees.size());
m_mgr.incr_metric(METRIC_BRIDGES_TO_OPTIMIZE, m_bridges_to_bridgees.size());
inline_bridges();
delete_unused_bridgees();
TRACE(BRIDGE, 1, "Inlined and removed %lu bridges",
m_bridges_to_bridgees.size());
m_mgr.incr_metric(METRIC_BRIDGES_REMOVED, m_bridges_to_bridgees.size());
m_mgr.incr_metric(METRIC_ILLEGAL_REFS, m_illegal_refs);
}
};
////////////////////////////////////////////////////////////////////////////////
void BridgePass::run_pass(DexStoresVector& stores,
ConfigFiles& conf,
PassManager& mgr) {
if (mgr.no_proguard_rules()) {
TRACE(BRIDGE, 1,
"BridgePass not run because no ProGuard configuration was provided.");
return;
}
int32_t min_sdk = mgr.get_redex_options().min_sdk;
mgr.incr_metric("min_sdk", min_sdk);
TRACE(BRIDGE, 2, "min_sdk: %d", min_sdk);
auto min_sdk_api_file = conf.get_android_sdk_api_file(min_sdk);
const api::AndroidSDK* min_sdk_api{nullptr};
if (!min_sdk_api_file) {
mgr.incr_metric("min_sdk_no_file", 1);
TRACE(BRIDGE, 2, "Android SDK API %d file cannot be found.", min_sdk);
} else {
min_sdk_api = &conf.get_android_sdk_api(min_sdk);
}
XStoreRefs xstores(stores);
std::vector<std::unique_ptr<RefChecker>> ref_checkers;
ref_checkers.reserve(xstores.size());
for (size_t store_idx = 0; store_idx < xstores.size(); store_idx++) {
ref_checkers.emplace_back(
std::make_unique<RefChecker>(&xstores, store_idx, min_sdk_api));
}
Scope scope = build_class_scope(stores);
BridgeRemover(xstores, ref_checkers, scope, mgr).run();
}
static BridgePass s_pass;