Skip to content

Commit 770ae33

Browse files
ssj933facebook-github-bot
authored andcommitted
improve perf check for unfinalize init inline
Summary: Be more conservative that avoid inlining constructor that need write barrier into method that are perf sensitive. Also expand this check to not only method that need to add write barrier, but also method that were added write barrier in previous or current inliner run. Reviewed By: wsanville Differential Revision: D71438750 fbshipit-source-id: 9b7b50ff998c93285a6ac9586818f4788a77464e
1 parent fc5bacc commit 770ae33

File tree

6 files changed

+98
-38
lines changed

6 files changed

+98
-38
lines changed

libredex/SourceBlocks.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,24 @@ inline bool is_hot(cfg::Block* b) {
341341
return sb->foreach_val_early([](const auto& v) { return v && v->val > 0; });
342342
}
343343

344+
// If a method's entry block is hot, consider this method is hot.
345+
inline bool method_is_hot(DexMethod* method) {
346+
auto& cfg = method->get_code()->cfg();
347+
return is_hot(cfg.entry_block());
348+
}
349+
350+
// If a method's entry block may be hot, consider this method may be hot.
351+
inline bool method_maybe_hot(DexMethod* method) {
352+
auto& cfg = method->get_code()->cfg();
353+
return maybe_hot(cfg.entry_block());
354+
}
355+
356+
// If a method's entry block is not cold, consider this method is not cold.
357+
inline bool method_is_not_cold(DexMethod* method) {
358+
auto& cfg = method->get_code()->cfg();
359+
return is_not_cold(cfg.entry_block());
360+
}
361+
344362
template <typename Iterator>
345363
inline SourceBlock* find_between(const Iterator& start, const Iterator& end) {
346364
auto it = std::find_if(

opt/write_barrier/WriteBarrierLoweringPass.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@
1010
#include "CFGMutation.h"
1111
#include "ControlFlow.h"
1212
#include "DexAsm.h"
13+
#include "DexStructure.h"
1314
#include "Show.h"
1415
#include "SourceBlocks.h"
1516
#include "Walkers.h"
16-
#include <DexStructure.h>
1717

1818
namespace {
1919
using namespace dex_asm;

service/method-inliner/Inliner.cpp

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,8 @@ MultiMethodInliner::MultiMethodInliner(
134134
bool local_only,
135135
bool consider_hot_cold,
136136
InlinerCostConfig inliner_cost_config,
137-
const std::unordered_set<const DexMethod*>* unfinalized_init_methods)
137+
const std::unordered_set<const DexMethod*>* unfinalized_init_methods,
138+
InsertOnlyConcurrentSet<DexMethod*>* methods_with_write_barrier)
138139
: m_concurrent_resolver(std::move(concurrent_resolve_fn)),
139140
m_scheduler(
140141
[this](DexMethod* method) {
@@ -173,7 +174,8 @@ MultiMethodInliner::MultiMethodInliner(
173174
m_local_only(local_only),
174175
m_consider_hot_cold(consider_hot_cold),
175176
m_inliner_cost_config(inliner_cost_config),
176-
m_unfinalized_init_methods(unfinalized_init_methods) {
177+
m_unfinalized_init_methods(unfinalized_init_methods),
178+
m_methods_with_write_barrier(methods_with_write_barrier) {
177179
Timer t("MultiMethodInliner construction");
178180
for (const auto& callee_callers : true_virtual_callers) {
179181
auto callee = callee_callers.first;
@@ -699,7 +701,8 @@ size_t MultiMethodInliner::inline_inlinables(
699701
cfg_next_caller_reg = caller->cfg().get_registers_size();
700702
}
701703
size_t calls_not_inlinable{0}, calls_not_inlined{0}, no_returns{0},
702-
unreachable_insns{0}, caller_too_large{0};
704+
unreachable_insns{0}, caller_too_large{0},
705+
calls_not_inlined_with_perf_sensitive_constructors{0};
703706

704707
size_t intermediate_shrinkings{0};
705708
size_t intermediate_remove_unreachable_blocks{0};
@@ -869,6 +872,23 @@ size_t MultiMethodInliner::inline_inlinables(
869872
auto needs_init_class = get_needs_init_class(callee_method);
870873
auto needs_constructor_fence =
871874
get_needs_constructor_fence(caller_method, callee_method);
875+
auto callee_has_constructor_fence =
876+
m_config.unfinalize_relaxed_init_inline &&
877+
m_methods_with_write_barrier &&
878+
m_methods_with_write_barrier->count(callee_method) > 0;
879+
if (needs_constructor_fence || callee_has_constructor_fence) {
880+
if ((m_config.unfinalize_perf_mode ==
881+
inliner::UnfinalizePerfMode::NOT_COLD &&
882+
source_blocks::method_is_not_cold(caller_method)) ||
883+
(m_config.unfinalize_perf_mode ==
884+
inliner::UnfinalizePerfMode::MAYBE_HOT &&
885+
source_blocks::method_maybe_hot(caller_method)) ||
886+
(m_config.unfinalize_perf_mode == inliner::UnfinalizePerfMode::HOT &&
887+
source_blocks::method_is_hot(caller_method))) {
888+
calls_not_inlined_with_perf_sensitive_constructors++;
889+
continue;
890+
}
891+
}
872892
bool success = inliner::inline_with_cfg(
873893
caller_method, callee_method, callsite_insn,
874894
inlinable.needs_receiver_cast, needs_init_class, *cfg_next_caller_reg,
@@ -899,16 +919,23 @@ size_t MultiMethodInliner::inline_inlinables(
899919
m_inlined_with_fence.insert(m);
900920
}
901921
constructor_fences++;
902-
} else if (get_needs_constructor_fence(/* caller */ nullptr,
903-
callee_method)) {
904-
// Inlining callee in any other context would need a constructor fence;
905-
// that means that final fields are involved, and we just didn't need a
906-
// constructor fence here because we inlined into an init overload. Record
907-
// this.
908-
always_assert(method::is_init(caller_method));
909-
always_assert(method::is_init(callee_method));
910-
always_assert(caller_method->get_class() == callee_method->get_class());
911-
m_unfinalized_overloads.emplace(caller_method, callee_method);
922+
if (m_methods_with_write_barrier) {
923+
m_methods_with_write_barrier->insert(caller_method);
924+
}
925+
} else {
926+
if (callee_has_constructor_fence) {
927+
m_methods_with_write_barrier->insert(caller_method);
928+
}
929+
if (get_needs_constructor_fence(/* caller */ nullptr, callee_method)) {
930+
// Inlining callee in any other context would need a constructor fence;
931+
// that means that final fields are involved, and we just didn't need a
932+
// constructor fence here because we inlined into an init overload.
933+
// Record this.
934+
always_assert(method::is_init(caller_method));
935+
always_assert(method::is_init(callee_method));
936+
always_assert(caller_method->get_class() == callee_method->get_class());
937+
m_unfinalized_overloads.emplace(caller_method, callee_method);
938+
}
912939
}
913940

914941
inlined_callees.push_back(callee_method);
@@ -944,6 +971,10 @@ size_t MultiMethodInliner::inline_inlinables(
944971
if (calls_not_inlined) {
945972
info.calls_not_inlined += calls_not_inlined;
946973
}
974+
if (calls_not_inlined_with_perf_sensitive_constructors) {
975+
info.calls_not_inlined_with_perf_sensitive_constructors +=
976+
calls_not_inlined_with_perf_sensitive_constructors;
977+
}
947978
if (no_returns) {
948979
info.no_returns += no_returns;
949980
}

service/method-inliner/Inliner.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,8 @@ class MultiMethodInliner {
274274
bool consider_hot_cold = false,
275275
InlinerCostConfig inliner_cost_config = DEFAULT_COST_CONFIG,
276276
const std::unordered_set<const DexMethod*>* unfinalized_init_methods =
277+
nullptr,
278+
InsertOnlyConcurrentSet<DexMethod*>* methods_with_write_barrier =
277279
nullptr);
278280

279281
/*
@@ -781,6 +783,7 @@ class MultiMethodInliner {
781783
std::atomic<size_t> constructor_fences{0};
782784
std::atomic<size_t> calls_not_inlinable{0};
783785
std::atomic<size_t> calls_not_inlined{0};
786+
std::atomic<size_t> calls_not_inlined_with_perf_sensitive_constructors{0};
784787
std::atomic<size_t> no_returns{0};
785788
std::atomic<size_t> unreachable_insns{0};
786789
std::atomic<size_t> intermediate_shrinkings{0};
@@ -848,6 +851,8 @@ class MultiMethodInliner {
848851
InsertOnlyConcurrentMap<const DexMethod*, const DexMethod*>
849852
m_unfinalized_overloads;
850853

854+
InsertOnlyConcurrentSet<DexMethod*>* m_methods_with_write_barrier;
855+
851856
public:
852857
const InliningInfo& get_info() { return info; }
853858

service/method-inliner/MethodInliner.cpp

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -720,24 +720,6 @@ void gather_true_virtual_methods(
720720
}
721721
}
722722

723-
// If a method's entry block is hot, consider this method is hot.
724-
bool is_hot(DexMethod* method) {
725-
auto& cfg = method->get_code()->cfg();
726-
return source_blocks::is_hot(cfg.entry_block());
727-
}
728-
729-
// If a method's entry block may be hot, consider this method may be hot.
730-
bool maybe_hot(DexMethod* method) {
731-
auto& cfg = method->get_code()->cfg();
732-
return source_blocks::maybe_hot(cfg.entry_block());
733-
}
734-
735-
// If a method's entry block is not cold, consider this method is not cold.
736-
bool is_not_cold(DexMethod* method) {
737-
auto& cfg = method->get_code()->cfg();
738-
return source_blocks::is_not_cold(cfg.entry_block());
739-
}
740-
741723
// If a constructor cannot be inlined because it writes to final fields, then we
742724
// can "simply" remove the final modifier. We typically run the
743725
// AccessMarkingMarking pass multiple times, including after the final inliner,
@@ -752,8 +734,25 @@ void unfinalize_fields_if_beneficial_for_relaxed_init_inlining(
752734
const Scope& scope,
753735
const inliner::UnfinalizePerfMode& unfinalize_perf_mode,
754736
InsertOnlyConcurrentMap<DexField*, std::vector<DexMethod*>>*
755-
unfinalized_fields) {
737+
unfinalized_fields,
738+
InsertOnlyConcurrentSet<DexMethod*>* methods_with_write_barrier) {
756739
walk::parallel::classes(scope, [&](DexClass* cls) {
740+
// Since we run inliner several times, in previous inliner run we might
741+
// unfinalized some fields and added write barrier in method already.
742+
// We need to take these method into consideration when we check
743+
// inlining eligibility.
744+
for (auto method : cls->get_all_methods()) {
745+
if (method->get_code() == nullptr) {
746+
continue;
747+
}
748+
for (const auto& mie : InstructionIterable(method->get_code()->cfg())) {
749+
if (opcode::is_write_barrier(mie.insn->opcode())) {
750+
methods_with_write_barrier->insert(method);
751+
break;
752+
}
753+
}
754+
}
755+
757756
// We also exclude possibly anonymous classes as those may
758757
// regress the effectiveness of the class-merging passes.
759758
// TODO T184662680: While this is not a correctness issue,
@@ -777,11 +776,11 @@ void unfinalize_fields_if_beneficial_for_relaxed_init_inlining(
777776
bool perf_sensitive = false;
778777
for (auto* init_method : cls->get_ctors()) {
779778
if ((unfinalize_perf_mode == inliner::UnfinalizePerfMode::NOT_COLD &&
780-
is_not_cold(init_method)) ||
779+
source_blocks::method_is_not_cold(init_method)) ||
781780
(unfinalize_perf_mode == inliner::UnfinalizePerfMode::MAYBE_HOT &&
782-
maybe_hot(init_method)) ||
781+
source_blocks::method_maybe_hot(init_method)) ||
783782
(unfinalize_perf_mode == inliner::UnfinalizePerfMode::HOT &&
784-
is_hot(init_method))) {
783+
source_blocks::method_is_hot(init_method))) {
785784
perf_sensitive = true;
786785
break;
787786
}
@@ -906,11 +905,13 @@ void run_inliner(
906905

907906
InsertOnlyConcurrentMap<DexField*, std::vector<DexMethod*>>
908907
unfinalized_fields;
908+
InsertOnlyConcurrentSet<DexMethod*> methods_with_write_barrier;
909909
std::unordered_set<const DexMethod*> unfinalized_init_methods;
910910
if (inliner_config.relaxed_init_inline &&
911911
inliner_config.unfinalize_relaxed_init_inline && min_sdk >= 21) {
912912
unfinalize_fields_if_beneficial_for_relaxed_init_inlining(
913-
scope, inliner_config.unfinalize_perf_mode, &unfinalized_fields);
913+
scope, inliner_config.unfinalize_perf_mode, &unfinalized_fields,
914+
&methods_with_write_barrier);
914915
mgr.set_metric("unfinalized_fields", unfinalized_fields.size());
915916
TRACE(INLINE, 3, "unfinalized %zu fields", unfinalized_fields.size());
916917
for (auto&& [_, init_methods] : unfinalized_fields) {
@@ -982,7 +983,8 @@ void run_inliner(
982983
analyze_and_prune_inits, conf.get_pure_methods(), min_sdk_api,
983984
cross_dex_penalty,
984985
/* configured_finalish_field_names */ {}, local_only, consider_hot_cold,
985-
inliner_cost_config, &unfinalized_init_methods);
986+
inliner_cost_config, &unfinalized_init_methods,
987+
&methods_with_write_barrier);
986988
inliner.inline_methods();
987989

988990
// refinalize where possible
@@ -1140,6 +1142,9 @@ void run_inliner(
11401142
mgr.incr_metric("intermediate_remove_unreachable_blocks",
11411143
inliner.get_info().intermediate_remove_unreachable_blocks);
11421144
mgr.incr_metric("calls_not_inlined", inliner.get_info().calls_not_inlined);
1145+
mgr.incr_metric(
1146+
"calls_not_inlined_with_perf_sensitive_constructors",
1147+
inliner.get_info().calls_not_inlined_with_perf_sensitive_constructors);
11431148
mgr.incr_metric("methods_removed", deleted.size());
11441149
mgr.incr_metric("escaped_virtual", inliner.get_info().escaped_virtual);
11451150
mgr.incr_metric("unresolved_methods", inliner.get_info().unresolved_methods);

test/unit/MethodInlineTest.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2675,6 +2675,7 @@ TEST_F(MethodInlineTest, inline_init_unfinalized_relaxed) {
26752675
inliner_config.shrinker.run_local_dce = true;
26762676
inliner_config.shrinker.compute_pure_methods = false;
26772677
inliner_config.relaxed_init_inline = true;
2678+
inliner_config.unfinalize_perf_mode = inliner::UnfinalizePerfMode::NONE;
26782679

26792680
caller->get_code()->build_cfg();
26802681
init->get_code()->build_cfg();

0 commit comments

Comments
 (0)