void KotlinObjectInliner::run_pass()

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);
}