void run_inliner()

in service/method-inliner/MethodInliner.cpp [634:945]


void run_inliner(DexStoresVector& stores,
                 PassManager& mgr,
                 ConfigFiles& conf,
                 bool intra_dex /* false */,
                 InlineForSpeed* inline_for_speed /* nullptr */,
                 bool inline_bridge_synth_only /* false */) {
  always_assert_log(
      !mgr.init_class_lowering_has_run(),
      "Implementation limitation: The inliner could introduce new "
      "init-class instructions.");
  if (mgr.no_proguard_rules()) {
    TRACE(INLINE, 1,
          "MethodInlinePass not run because no ProGuard configuration was "
          "provided.");
    return;
  }

  auto scope = build_class_scope(stores);

  auto inliner_config = conf.get_inliner_config();
  int32_t min_sdk = mgr.get_redex_options().min_sdk;
  const api::AndroidSDK* min_sdk_api{nullptr};
  if (inliner_config.check_min_sdk_refs) {
    mgr.incr_metric("min_sdk", min_sdk);
    TRACE(INLINE, 2, "min_sdk: %d", min_sdk);
    auto min_sdk_api_file = conf.get_android_sdk_api_file(min_sdk);
    if (!min_sdk_api_file) {
      mgr.incr_metric("min_sdk_no_file", 1);
      TRACE(INLINE, 2, "Android SDK API %d file cannot be found.", min_sdk);
    } else {
      min_sdk_api = &conf.get_android_sdk_api(min_sdk);
    }
  }

  CalleeCallerInsns true_virtual_callers;
  bool cross_dex_penalty = true;
  // Gather all inlinable candidates.
  if (intra_dex) {
    inliner_config.apply_intradex_allowlist();
    cross_dex_penalty = false;
  }

  if (inline_for_speed != nullptr) {
    inliner_config.shrink_other_methods = false;
  }

  if (inline_bridge_synth_only) {
    inliner_config.true_virtual_inline = false;
    inliner_config.virtual_inline = false;
    inliner_config.use_call_site_summaries = false;
    inliner_config.shrink_other_methods = false;
    inliner_config.intermediate_shrinking = false;
    inliner_config.multiple_callers = true;
    inliner_config.delete_non_virtuals = true;
    inliner_config.shrinker = shrinker::ShrinkerConfig();
    inliner_config.shrinker.compute_pure_methods = false;
    inliner_config.shrinker.run_const_prop = true;
    cross_dex_penalty = false;
  }

  inliner_config.unique_inlined_registers = false;

  std::unique_ptr<const mog::Graph> method_override_graph;
  if (inliner_config.virtual_inline) {
    method_override_graph = mog::build_graph(scope);
  }

  init_classes::InitClassesWithSideEffects init_classes_with_side_effects(
      scope, conf.create_init_class_insns(), method_override_graph.get());

  auto candidates = gather_non_virtual_methods(
      scope, method_override_graph.get(), conf.get_do_not_devirt_anon());

  // The candidates list computed above includes all constructors, regardless of
  // whether it's safe to inline them or not. We'll let the inliner decide
  // what to do with constructors.
  bool analyze_and_prune_inits = true;

  if (inliner_config.virtual_inline && inliner_config.true_virtual_inline) {
    gather_true_virtual_methods(*method_override_graph, scope,
                                &true_virtual_callers);
    for (auto& p : true_virtual_callers) {
      candidates.insert(p.first);
    }
  }
  // keep a map from refs to defs or nullptr if no method was found
  ConcurrentMethodRefCache concurrent_resolved_refs;
  auto concurrent_resolver = [&concurrent_resolved_refs](DexMethodRef* method,
                                                         MethodSearch search) {
    return resolve_method(method, search, concurrent_resolved_refs);
  };

  walk::parallel::code(scope, [](DexMethod*, IRCode& code) {
    code.build_cfg(/* editable */ true);
  });

  if (inline_bridge_synth_only) {
    filter_candidates_bridge_synth_only(mgr, scope, candidates, "initial_");
  }

  inliner_config.shrinker.analyze_constructors =
      inliner_config.shrinker.run_const_prop;

  // inline candidates
  MultiMethodInliner inliner(scope, init_classes_with_side_effects, stores,
                             candidates, concurrent_resolver, inliner_config,
                             min_sdk, intra_dex ? IntraDex : InterDex,
                             true_virtual_callers, inline_for_speed,
                             analyze_and_prune_inits, conf.get_pure_methods(),
                             min_sdk_api, cross_dex_penalty);
  inliner.inline_methods(/* need_deconstruct */ false);

  walk::parallel::code(scope,
                       [](DexMethod*, IRCode& code) { code.clear_cfg(); });

  // delete all methods that can be deleted
  auto inlined = inliner.get_inlined();
  size_t inlined_count = inlined.size();
  size_t inlined_init_count = 0;
  for (DexMethod* m : inlined) {
    if (method::is_init(m)) {
      inlined_init_count++;
    }
  }

  std::vector<DexMethod*> deleted;
  if (inliner_config.delete_non_virtuals) {
    // Do not erase true virtual methods that are inlined because we are only
    // inlining callsites that are monomorphic, for polymorphic callsite we
    // didn't inline, but in run time the callsite may still be resolved to
    // those methods that are inlined. We are relying on RMU to clean up
    // true virtual methods that are not referenced.
    for (const auto& pair : true_virtual_callers) {
      inlined.erase(pair.first);
    }
    deleted = delete_methods(scope, inlined, concurrent_resolver);
  }

  if (inline_bridge_synth_only) {
    std::unordered_set<DexMethod*> deleted_set(deleted.begin(), deleted.end());
    std20::erase_if(candidates,
                    [&](auto method) { return deleted_set.count(method); });
    filter_candidates_bridge_synth_only(mgr, scope, candidates, "remaining_");
  }

  if (!deleted.empty()) {
    // Can't really delete because of possible deob links. At least let's erase
    // any code.
    for (auto* m : deleted) {
      m->release_code();
    }
  }

  TRACE(INLINE, 3, "recursive %ld", inliner.get_info().recursive);
  TRACE(INLINE, 3, "max_call_stack_depth %ld",
        inliner.get_info().max_call_stack_depth);
  TRACE(INLINE, 3, "waited seconds %ld", inliner.get_info().waited_seconds);
  TRACE(INLINE, 3, "blocklisted meths %ld",
        (size_t)inliner.get_info().blocklisted);
  TRACE(INLINE, 3, "virtualizing methods %ld",
        (size_t)inliner.get_info().need_vmethod);
  TRACE(INLINE, 3, "invoke super %ld", (size_t)inliner.get_info().invoke_super);
  TRACE(INLINE, 3, "escaped virtual %ld",
        (size_t)inliner.get_info().escaped_virtual);
  TRACE(INLINE, 3, "known non public virtual %ld",
        (size_t)inliner.get_info().non_pub_virtual);
  TRACE(INLINE, 3, "non public ctor %ld",
        (size_t)inliner.get_info().non_pub_ctor);
  TRACE(INLINE, 3, "unknown field %ld",
        (size_t)inliner.get_info().escaped_field);
  TRACE(INLINE, 3, "non public field %ld",
        (size_t)inliner.get_info().non_pub_field);
  TRACE(INLINE, 3, "throws %ld", (size_t)inliner.get_info().throws);
  TRACE(INLINE, 3, "multiple returns %ld",
        (size_t)inliner.get_info().multi_ret);
  TRACE(INLINE, 3, "references cross stores %ld",
        (size_t)inliner.get_info().cross_store);
  TRACE(INLINE, 3, "api level mismatches %ld",
        (size_t)inliner.get_info().api_level_mismatch);
  TRACE(INLINE, 3, "illegal references %ld",
        (size_t)inliner.get_info().problematic_refs);
  TRACE(INLINE, 3, "not found %ld", (size_t)inliner.get_info().not_found);
  TRACE(INLINE, 3, "caller too large %ld",
        (size_t)inliner.get_info().caller_too_large);
  TRACE(INLINE, 3, "inlined ctors %zu", inlined_init_count);
  TRACE(INLINE, 1, "%ld inlined calls over %ld methods and %ld methods removed",
        (size_t)inliner.get_info().calls_inlined, inlined_count,
        deleted.size());

  const auto& shrinker = inliner.get_shrinker();
  mgr.incr_metric("recursive", inliner.get_info().recursive);
  mgr.incr_metric("max_call_stack_depth",
                  inliner.get_info().max_call_stack_depth);
  mgr.incr_metric("cross_store", inliner.get_info().cross_store);
  mgr.incr_metric("api_level_mismatch", inliner.get_info().api_level_mismatch);
  mgr.incr_metric("problematic_refs", inliner.get_info().problematic_refs);
  mgr.incr_metric("caller_too_large", inliner.get_info().caller_too_large);
  mgr.incr_metric("inlined_init_count", inlined_init_count);
  mgr.incr_metric("init_classes", inliner.get_info().init_classes);
  mgr.incr_metric("calls_inlined", inliner.get_info().calls_inlined);
  mgr.incr_metric("kotlin_lambda_inlined",
                  inliner.get_info().kotlin_lambda_inlined);
  mgr.incr_metric("calls_not_inlinable",
                  inliner.get_info().calls_not_inlinable);
  mgr.incr_metric("no_returns", inliner.get_info().no_returns);
  mgr.incr_metric("intermediate_shrinkings",
                  inliner.get_info().intermediate_shrinkings);
  mgr.incr_metric("intermediate_remove_unreachable_blocks",
                  inliner.get_info().intermediate_remove_unreachable_blocks);
  mgr.incr_metric("calls_not_inlined", inliner.get_info().calls_not_inlined);
  mgr.incr_metric("methods_removed", deleted.size());
  mgr.incr_metric("escaped_virtual", inliner.get_info().escaped_virtual);
  mgr.incr_metric("unresolved_methods", inliner.get_info().unresolved_methods);
  mgr.incr_metric("known_public_methods",
                  inliner.get_info().known_public_methods);
  mgr.incr_metric(
      "constant_invoke_callers_analyzed",
      inliner.get_info()
          .call_site_summary_stats.constant_invoke_callers_analyzed);
  mgr.incr_metric(
      "constant_invoke_callers_unreachable",
      inliner.get_info()
          .call_site_summary_stats.constant_invoke_callers_unreachable);
  mgr.incr_metric(
      "constant_invoke_callers_unreachable_blocks",
      inliner.get_info()
          .call_site_summary_stats.constant_invoke_callers_unreachable_blocks);
  mgr.incr_metric("constant_invoke_callers_critical_path_length",
                  inliner.get_info()
                      .call_site_summary_stats
                      .constant_invoke_callers_critical_path_length);
  mgr.incr_metric("constant_invoke_callees_analyzed",
                  inliner.get_info().constant_invoke_callees_analyzed);
  mgr.incr_metric("constant_invoke_callees_no_return",
                  inliner.get_info().constant_invoke_callees_no_return);
  mgr.incr_metric("constant_invoke_callees_unused_results",
                  inliner.get_info().constant_invoke_callees_unused_results);
  mgr.incr_metric("critical_path_length",
                  inliner.get_info().critical_path_length);
  mgr.incr_metric("methods_shrunk", shrinker.get_methods_shrunk());
  mgr.incr_metric("callers", inliner.get_callers());
  if (intra_dex) {
    mgr.incr_metric("x-dex-callees", inliner.get_x_dex_callees());
  }
  mgr.incr_metric(
      "instructions_eliminated_const_prop",
      shrinker.get_const_prop_stats().branches_removed +
          shrinker.get_const_prop_stats().unreachable_instructions_removed +
          shrinker.get_const_prop_stats().branches_forwarded +
          shrinker.get_const_prop_stats().materialized_consts +
          shrinker.get_const_prop_stats().added_param_const +
          shrinker.get_const_prop_stats().throws +
          shrinker.get_const_prop_stats().null_checks);
  {
    ScopedMetrics sm(mgr);
    auto sm_scope = sm.scope("inliner");
    shrinker.log_metrics(sm);
  }
  mgr.incr_metric("instructions_eliminated_cse",
                  shrinker.get_cse_stats().instructions_eliminated);
  mgr.incr_metric("instructions_eliminated_copy_prop",
                  shrinker.get_copy_prop_stats().moves_eliminated);
  mgr.incr_metric(
      "instructions_eliminated_localdce",
      shrinker.get_local_dce_stats().dead_instruction_count +
          shrinker.get_local_dce_stats().unreachable_instruction_count);
  mgr.incr_metric("instructions_eliminated_unreachable",
                  inliner.get_info().unreachable_insns);
  mgr.incr_metric("instructions_eliminated_dedup_blocks",
                  shrinker.get_dedup_blocks_stats().insns_removed);
  mgr.incr_metric("blocks_eliminated_by_dedup_blocks",
                  shrinker.get_dedup_blocks_stats().blocks_removed);
  mgr.incr_metric("methods_reg_alloced", shrinker.get_methods_reg_alloced());
  mgr.incr_metric("localdce_init_class_instructions_added",
                  shrinker.get_local_dce_stats().init_class_instructions_added);
  mgr.incr_metric(
      "localdce_init_class_instructions",
      shrinker.get_local_dce_stats().init_classes.init_class_instructions);
  mgr.incr_metric("localdce_init_class_instructions_removed",
                  shrinker.get_local_dce_stats()
                      .init_classes.init_class_instructions_removed);
  mgr.incr_metric("localdce_init_class_instructions_refined",
                  shrinker.get_local_dce_stats()
                      .init_classes.init_class_instructions_refined);

  // Expose the shrinking timers as Timers.
  Timer::add_timer("Inliner.Shrinking.ConstantPropagation",
                   shrinker.get_const_prop_seconds());
  Timer::add_timer("Inliner.Shrinking.CSE", shrinker.get_cse_seconds());
  Timer::add_timer("Inliner.Shrinking.CopyPropagation",
                   shrinker.get_copy_prop_seconds());
  Timer::add_timer("Inliner.Shrinking.LocalDCE",
                   shrinker.get_local_dce_seconds());
  Timer::add_timer("Inliner.Shrinking.DedupBlocks",
                   shrinker.get_dedup_blocks_seconds());
  Timer::add_timer("Inliner.Shrinking.RegAlloc",
                   shrinker.get_reg_alloc_seconds());
  Timer::add_timer("Inliner.Inlining.inline_callees",
                   inliner.get_inline_callees_seconds());
  Timer::add_timer("Inliner.Inlining.inline_callees_should_inline",
                   inliner.get_inline_callees_should_inline_seconds());
  Timer::add_timer("Inliner.Inlining.inline_callees_init",
                   inliner.get_inline_callees_init_seconds());
  Timer::add_timer("Inliner.Inlining.inline_inlinables",
                   inliner.get_inline_inlinables_seconds());
  Timer::add_timer("Inliner.Inlining.inline_with_cfg",
                   inliner.get_inline_with_cfg_seconds());
  Timer::add_timer("Inliner.Inlining.call_site_inlined_cost",
                   inliner.get_call_site_inlined_cost_seconds());
  Timer::add_timer("Inliner.Inlining.cannot_inline_sketchy_code",
                   inliner.get_cannot_inline_sketchy_code_timer_seconds());
}