opt/builder_pattern/BuilderTransform.cpp (218 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 "BuilderTransform.h"
#include "DexClass.h"
namespace builder_pattern {
BuilderTransform::BuilderTransform(
const Scope& scope,
const TypeSystem& type_system,
const DexType* root,
const init_classes::InitClassesWithSideEffects&
init_classes_with_side_effects,
const inliner::InlinerConfig& inliner_config,
DexStoresVector& stores)
: m_type_system(type_system),
m_root(root),
m_inliner_config(inliner_config) {
auto concurrent_resolver = [&](DexMethodRef* method, MethodSearch search) {
return resolve_method(method, search, m_concurrent_resolved_refs);
};
std::unordered_set<DexMethod*> no_default_inlinables;
// customize shrinking options
m_inliner_config.shrinker = shrinker::ShrinkerConfig();
m_inliner_config.shrinker.run_const_prop = true;
m_inliner_config.shrinker.run_cse = true;
m_inliner_config.shrinker.run_copy_prop = true;
m_inliner_config.shrinker.run_local_dce = true;
m_inliner_config.shrinker.compute_pure_methods = false;
int min_sdk = 0;
m_inliner = std::unique_ptr<MultiMethodInliner>(new MultiMethodInliner(
scope, init_classes_with_side_effects, stores, no_default_inlinables,
concurrent_resolver, m_inliner_config, min_sdk,
MultiMethodInlinerMode::None));
}
std::unordered_set<const IRInstruction*> BuilderTransform::try_inline_calls(
DexMethod* caller,
const std::unordered_set<IRInstruction*>& insns,
std::vector<IRInstruction*>* deleted_insns) {
always_assert(caller && caller->get_code());
m_inliner->inline_callees(caller, insns, deleted_insns);
std::unordered_set<const IRInstruction*> not_inlined_insns;
// Check if everything was inlined.
auto* code = caller->get_code();
for (const auto& mie : InstructionIterable(code)) {
auto insn = mie.insn;
if (insns.count(insn)) {
not_inlined_insns.emplace(insn);
}
}
return not_inlined_insns;
}
BuilderTransform::~BuilderTransform() {}
/**
* For all the methods of the given type, try inlining all super calls and
* constructors of the super type. If any of them fails, return false.
*/
bool BuilderTransform::inline_super_calls_and_ctors(const DexType* type) {
auto cls = type_class(type);
std::vector<DexMethod*> methods = cls->get_dmethods();
const std::vector<DexMethod*>& vmethods = cls->get_vmethods();
methods.insert(methods.end(), vmethods.begin(), vmethods.end());
const std::vector<DexMethod*>& super_ctors_list =
type_class(m_root)->get_ctors();
std::unordered_set<DexMethod*> super_ctors(super_ctors_list.begin(),
super_ctors_list.end());
for (DexMethod* method : methods) {
if (!method->get_code()) {
continue;
}
std::unordered_set<IRInstruction*> inlinable_insns;
for (const auto& mie : InstructionIterable(method->get_code())) {
auto insn = mie.insn;
if (insn->opcode() == OPCODE_INVOKE_SUPER) {
inlinable_insns.emplace(insn);
} else if (opcode::is_invoke_direct(insn->opcode())) {
auto callee = resolve_method(insn->get_method(), MethodSearch::Direct);
if (super_ctors.count(callee)) {
inlinable_insns.emplace(insn);
}
}
}
if (!inlinable_insns.empty()) {
TRACE(BLD_PATTERN, 8, "Creating a copy of %s", SHOW(method));
const std::string& name_str = method->get_name()->str();
DexMethod* method_copy = DexMethod::make_method_from(
method,
method->get_class(),
DexString::make_string(name_str + "$redex_builder"));
m_method_copy[method] = method_copy;
size_t num_insns_not_inlined =
try_inline_calls(method, inlinable_insns, nullptr).size();
if (num_insns_not_inlined > 0) {
return false;
}
}
}
return true;
}
/**
* Bind virtual calls to the actual implementation.
*/
void BuilderTransform::update_virtual_calls(
const std::unordered_map<IRInstruction*, DexType*>& insn_to_type) {
for (const auto& pair : insn_to_type) {
auto insn = pair.first;
auto current_instance = pair.second;
if (opcode::is_invoke_virtual(insn->opcode())) {
auto method = resolve_method(insn->get_method(), MethodSearch::Virtual);
if (!method) {
continue;
}
if (method->get_class() == m_root) {
// replace it with the actual implementation if any provided.
auto virtual_scope = m_type_system.find_virtual_scope(method);
for (const auto& v_pair : virtual_scope->methods) {
auto m = v_pair.first;
if (m->get_class() == current_instance && m->is_def()) {
TRACE(BLD_PATTERN, 3,
"Replace virtual method %s with the current implementation "
"%s",
SHOW(method), SHOW(m));
insn->set_method(m);
break;
}
}
}
}
}
}
namespace {
void initialize_regs(
const std::map<DexField*, size_t, dexfields_comparator>& field_to_reg,
IRCode* code) {
auto params = code->get_param_instructions();
for (const auto& pair : field_to_reg) {
auto field = pair.first;
auto reg = pair.second;
IRInstruction* initialization_insn = nullptr;
if (type::is_wide_type(field->get_type())) {
initialization_insn = new IRInstruction(OPCODE_CONST_WIDE);
} else {
initialization_insn = new IRInstruction(OPCODE_CONST);
}
initialization_insn->set_dest(reg);
initialization_insn->set_literal(0);
code->insert_before(params.end(), initialization_insn);
}
}
} // namespace
void BuilderTransform::replace_fields(const InstantiationToUsage& usage,
DexMethod* method) {
auto code = method->get_code();
std::vector<std::pair<IRList::iterator, IRInstruction*>> to_replace;
for (const auto& mie : InstructionIterable(code)) {
auto instantiation_insn = mie.insn;
if (!usage.count(instantiation_insn)) {
continue;
}
always_assert_log(instantiation_insn->opcode() == OPCODE_NEW_INSTANCE,
"Only accept new_instance opcodes for builder "
"initializations, but got %s\n",
SHOW(instantiation_insn));
// Replace builder instance creation with Object creation.
// This value should be only used for comparison with NULL.
instantiation_insn->set_type(type::java_lang_Object());
std::map<DexField*, size_t, dexfields_comparator> field_to_reg;
for (const auto& it : usage.at(instantiation_insn)) {
auto* insn = it->insn;
if (opcode::is_an_iput(insn->opcode()) ||
opcode::is_an_iget(insn->opcode())) {
auto field = resolve_field(insn->get_field(), FieldSearch::Instance);
always_assert(field);
if (field_to_reg.count(field) == 0) {
field_to_reg[field] = type::is_wide_type(field->get_type())
? code->allocate_wide_temp()
: code->allocate_temp();
}
// Replace usage.
IRInstruction* new_insn = nullptr;
if (type::is_wide_type(field->get_type())) {
new_insn = new IRInstruction(OPCODE_MOVE_WIDE);
} else if (type::is_primitive(field->get_type())) {
new_insn = new IRInstruction(OPCODE_MOVE);
} else {
new_insn = new IRInstruction(OPCODE_MOVE_OBJECT);
}
if (opcode::is_an_iput(insn->opcode())) {
new_insn->set_dest(field_to_reg[field]);
new_insn->set_src(0, insn->src(0));
} else {
new_insn->set_dest(std::next(it)->insn->dest());
new_insn->set_src(0, field_to_reg[field]);
}
to_replace.emplace_back(it, new_insn);
} else if (insn->opcode() == OPCODE_MOVE_OBJECT ||
insn->opcode() == IOPCODE_MOVE_RESULT_PSEUDO_OBJECT ||
opcode::is_a_conditional_branch(insn->opcode())) {
// Keep these instructions as is because we might not be able to clean
// up all the paths where Object created instead of builder could
// be used for checking if the builder was created or not.
// ConstProp + DCE will take care of safe clean up for us.
} else {
always_assert_log(insn->opcode() == OPCODE_INVOKE_DIRECT ||
insn->opcode() == OPCODE_CHECK_CAST,
"Different insn %s", SHOW(insn));
if (insn->opcode() == OPCODE_INVOKE_DIRECT) {
auto invoked =
resolve_method(insn->get_method(), MethodSearch::Direct);
// We only accept `Object.<init>()` here, since we can't inline it
// any further. Keep Object.<init> in place to avoid confusing
// dex2oat.
always_assert(invoked->get_class() == type::java_lang_Object() &&
method::is_init(invoked));
} else {
// Replace check-cast with move.
IRInstruction* new_move = new IRInstruction(OPCODE_MOVE_OBJECT);
new_move->set_src(0, insn->src(0));
new_move->set_dest(std::next(it)->insn->dest());
to_replace.emplace_back(it, new_move);
}
}
}
initialize_regs(field_to_reg, code);
}
for (const auto& pair : to_replace) {
code->replace_opcode(pair.first, {pair.second});
}
}
void BuilderTransform::cleanup() {
for (const auto& pair : m_method_copy) {
auto method = pair.first;
auto copy = pair.second;
TRACE(BLD_PATTERN,
8,
"Replacing method with its original version %s",
SHOW(method));
method->set_code(copy->release_code());
DexMethod::erase_method(copy);
DexMethod::delete_method_DO_NOT_USE(copy);
}
}
} // namespace builder_pattern