in opt/kotlin-lambda/KotlinObjectInliner.cpp [316:562]
void KotlinObjectInliner::run_pass(DexStoresVector& stores,
ConfigFiles&,
PassManager& mgr) {
const auto scope = build_class_scope(stores);
ConcurrentMap<DexClass*, DexClass*> map;
ConcurrentSet<DexClass*> bad;
std::unordered_map<DexClass*, unsigned> outer_cls_count;
std::unordered_set<DexType*> do_not_inline_set;
Stats stats;
for (auto& p : m_do_not_inline_list) {
auto* do_not_inline_cls = DexType::get_type(p);
if (do_not_inline_cls) {
TRACE(KOTLIN_OBJ_INLINE,
2,
"do_not_inlin_cls : %s",
SHOW(do_not_inline_cls));
do_not_inline_set.insert(do_not_inline_cls);
}
}
// Collect candidates
walk::parallel::classes(scope, [&](DexClass* cls) {
if (is_native(cls) || root(cls) || !can_rename(cls) || !can_delete(cls) ||
cls->rstate.is_referenced_by_resource_xml() || cls->is_external() ||
do_not_inline_set.count(cls->get_type())) {
return;
}
auto outer_cls = candidate_for_companion_inlining(cls);
if (outer_cls && !outer_cls->rstate.is_referenced_by_resource_xml() &&
!do_not_inline_set.count(outer_cls->get_type())) {
// This is a candidate for inlining
map.insert(std::make_pair(cls, outer_cls));
TRACE(KOTLIN_OBJ_INLINE, 2, "Candidate cls : %s", SHOW(cls));
}
});
stats.kotlin_candidate_companion_objects = map.size();
for (auto& iter : map) {
outer_cls_count[iter.second]++;
}
for (auto iter : map) {
// We have mutiple companion objects.
if (outer_cls_count.find(iter.second)->second != 1) {
bad.insert(iter.first);
}
}
// Filter out any instance whose use is not tractable
walk::parallel::methods(scope, [&](DexMethod* method) {
auto code = method->get_code();
if (!code) {
return;
}
// we cannot relocate returning companion obect.
auto* rtype = type_class(method->get_proto()->get_rtype());
if (rtype && map.count(rtype)) {
bad.insert(rtype);
}
cfg::ScopedCFG cfg(code);
auto iterable = cfg::InstructionIterable(*cfg);
live_range::MoveAwareChains move_aware_chains(*cfg);
for (auto it = iterable.begin(); it != iterable.end(); it++) {
auto insn = it->insn;
switch (insn->opcode()) {
case OPCODE_SPUT_OBJECT: {
auto* from = type_class(insn->get_field()->get_type());
if (!from || !map.count(from) || bad.count(from)) {
break;
}
// Shold only be set from parent's <clinit>
// Otherwise add it to bad list.
if (method::is_clinit(method) &&
type_class(method->get_class()) == map.find(from)->second) {
break;
}
bad.insert(from);
break;
}
// If there is any instance field, add it to bad
case OPCODE_IPUT_OBJECT:
case OPCODE_IGET_OBJECT: {
auto* from = type_class(insn->get_field()->get_type());
if (!from || !map.count(from) || bad.count(from)) {
break;
}
bad.insert(from);
break;
}
case OPCODE_SGET_OBJECT: {
auto* from = type_class(insn->get_field()->get_type());
if (!from || !map.count(from) || bad.count(from)) {
break;
}
// Check we can track the uses of the Companion object instance.
// i.e. Companion object is only used to invoke methods
if (!is_def_tractable(insn, from, move_aware_chains)) {
bad.insert(from);
}
break;
}
case OPCODE_INSTANCE_OF:
case OPCODE_NEW_INSTANCE: {
auto* from = type_class(insn->get_type());
if (!from || !map.count(from) || bad.count(from)) {
break;
}
if (method::is_clinit(method) &&
type_class(method->get_class()) == map.find(from)->second) {
break;
}
bad.insert(from);
TRACE(KOTLIN_OBJ_INLINE,
2,
"Adding cls %s to bad list due to insn %s",
SHOW(from),
SHOW(insn));
break;
}
case OPCODE_CHECK_CAST: {
auto* from = type_class(insn->get_type());
if (!from || !map.count(from) || bad.count(from)) {
break;
}
bad.insert(from);
TRACE(KOTLIN_OBJ_INLINE,
2,
"Adding cls %s to bad list due to insn %s",
SHOW(from),
SHOW(insn));
break;
}
case OPCODE_INVOKE_DIRECT: {
auto* from = type_class(insn->get_method()->get_class());
if (!method::is_init(insn->get_method()) || !from || !map.count(from) ||
bad.count(from)) {
break;
}
if ((type_class(method->get_class()) == from &&
method::is_init(method)) ||
((type_class(method->get_class()) == map.find(from)->second) &&
method::is_clinit(method))) {
break;
}
bad.insert(from);
break;
}
default:
if (insn->has_type()) {
auto* from = type_class(insn->get_type());
if (!from || !map.count(from) || bad.count(from)) {
break;
}
bad.insert(from);
TRACE(KOTLIN_OBJ_INLINE,
2,
"Adding cls %s to bad list due to insn %s",
SHOW(from),
SHOW(insn));
break;
}
break;
}
}
});
stats.kotlin_untrackable_companion_objects = bad.size();
// Inline objects in candidate to maped class
//
std::unordered_set<DexMethodRef*> relocated_methods;
for (auto& p : map) {
auto* from_cls = p.first;
auto* to_cls = p.second;
if (!bad.count(from_cls)) {
TRACE(KOTLIN_OBJ_INLINE,
2,
"Relocate : %s -> %s",
SHOW(from_cls),
SHOW(to_cls));
relocate(from_cls, to_cls, relocated_methods);
stats.kotlin_companion_objects_inlined++;
}
}
// Fix virtual call arguments
walk::parallel::methods(scope, [&](DexMethod* method) {
auto code = method->get_code();
if (code == nullptr) {
return;
}
bool changed = false;
cfg::ScopedCFG cfg(method->get_code());
cfg::CFGMutation m(*cfg);
live_range::MoveAwareChains move_aware_chains(*cfg);
auto du_chains_move_aware = move_aware_chains.get_def_use_chains();
auto iterable = cfg::InstructionIterable(*cfg);
for (auto it = iterable.begin(); it != iterable.end(); it++) {
auto insn = it->insn;
if (opcode::is_an_sput(insn->opcode())) {
auto* from = type_class(insn->get_field()->get_type());
if (!from || !map.count(from) || bad.count(from)) {
continue;
}
auto mov_result_it = cfg->move_result_of(it);
auto init_null = new IRInstruction(OPCODE_CONST);
init_null->set_literal(0);
init_null->set_dest(mov_result_it->insn->dest());
m.replace(it, {init_null});
changed = true;
}
if (insn->opcode() == OPCODE_INVOKE_VIRTUAL) {
if (!relocated_methods.count(insn->get_method())) {
continue;
}
insn->set_opcode(OPCODE_INVOKE_STATIC);
size_t arg_count = insn->get_method()->get_proto()->get_args()->size();
auto nargs = insn->srcs_size();
if (arg_count != nargs) {
for (uint16_t i = 0; i < nargs - 1; i++) {
insn->set_src(i, insn->src(i + 1));
}
insn->set_srcs_size(nargs - 1);
}
always_assert(arg_count == insn->srcs_size());
changed = true;
}
}
if (changed) {
m.flush();
TRACE(KOTLIN_OBJ_INLINE, 5, "After : %s\n", SHOW(method));
TRACE(KOTLIN_OBJ_INLINE, 5, "%s\n", SHOW(*cfg));
}
});
stats.report(mgr);
}