Repository navigation
VM Inlining curiosity #64284
Description
On 74896e3 I've applied this change to the VM:
diff --git a/pkg/vm/lib/transformations/type_flow/config.dart b/pkg/vm/lib/transformations/type_flow/config.dart
index 2aa1ff7ac70..f457fe08596 100644
--- a/pkg/vm/lib/transformations/type_flow/config.dart
+++ b/pkg/vm/lib/transformations/type_flow/config.dart
@@ -33,7 +33,7 @@ class TFAConfiguration {
final int maxCallStackDepth;
const TFAConfiguration({
- this.maxInterfaceInvocationsPerSelector = 1000,
+ this.maxInterfaceInvocationsPerSelector = 10000,
this.largeSummarySize = 300,
this.maxDirectInvocationsPerSelector = 10,
this.maxAllocatedTypesInSetSpecialization = 128,
diff --git a/runtime/vm/compiler/backend/inliner.cc b/runtime/vm/compiler/backend/inliner.cc
index d2012d51244..583225c081e 100644
--- a/runtime/vm/compiler/backend/inliner.cc
+++ b/runtime/vm/compiler/backend/inliner.cc
@@ -38,7 +38,7 @@ DEFINE_FLAG(charp, inlining_filter, nullptr, "Inline only in named function");
// Flags for inlining heuristics.
DEFINE_FLAG(int,
inline_getters_setters_smaller_than,
- 10,
+ 15,
"Always inline getters and setters that have fewer instructions");
DEFINE_FLAG(int,
inlining_depth_threshold,
@@ -1319,6 +1319,15 @@ class CallSiteInliner : public ValueObject {
{
COMPILER_TIMINGS_TIMER_SCOPE(thread(), BuildGraph);
callee_graph = builder.BuildGraph();
+ {
+ const Function& top = inliner_->flow_graph_->function();
+ if (strcmp(top.QualifiedUserVisibleNameCString(),
+ "InferenceVisitorBase.storeExpressionInfo") == 0) {
+ FlowGraphPrinter printer(*callee_graph);
+ THR_Print(" -- Flowgraph for %s\n", function.QualifiedUserVisibleNameCString());
+ printer.PrintBlocks();
+ }
+ }
// Make sure SSA temp indices in the callee graph
// do not intersect with SSA temp indices in the caller.
ASSERT(callee_graph->current_ssa_temp_index() == 0);
@@ -2494,9 +2503,17 @@ bool FlowGraphInliner::FunctionHasAlwaysConsiderInliningPragma(
}
bool FlowGraphInliner::AlwaysInline(const Function& function) {
+ if (trace_inlining()) {
+ String& name = String::Handle(function.QualifiedUserVisibleName());
+ THR_Print("AlwaysInline for %s\n", name.ToCString());
+ }
+
if (FunctionHasPreferInlinePragma(function)) {
TRACE_INLINING(
THR_Print("vm:prefer-inline pragma for %s\n", function.ToCString()));
+ if (trace_inlining()) {
+ THR_Print(" AlwaysInline #1 (true)\n");
+ }
return true;
}
@@ -2508,16 +2525,25 @@ bool FlowGraphInliner::AlwaysInline(const Function& function) {
!(function.kind() == UntaggedFunction::kDynamicInvocationForwarder &&
function.IsRecognized())) {
// Smaller or same size as the call.
+ if (trace_inlining()) {
+ THR_Print(" AlwaysInline #2 (true)\n");
+ }
return true;
}
if (function.is_const()) {
// Inlined const fields are smaller than a call.
+ if (trace_inlining()) {
+ THR_Print(" AlwaysInline #3 (true)\n");
+ }
return true;
}
if (function.IsMethodExtractor()) {
// Tear-off closure allocation has about the same size as the call.
+ if (trace_inlining()) {
+ THR_Print(" AlwaysInline #4 (true)\n");
+ }
return true;
}
@@ -2526,9 +2552,19 @@ bool FlowGraphInliner::AlwaysInline(const Function& function) {
(function.kind() == UntaggedFunction::kConstructor)) {
const intptr_t count = function.optimized_instruction_count();
if ((count != 0) && (count < FLAG_inline_getters_setters_smaller_than)) {
+ if (trace_inlining()) {
+ THR_Print(" AlwaysInline #5 (true) because %ld\n", count);
+ }
return true;
+ } else {
+ if (trace_inlining()) {
+ THR_Print(" AlwaysInline #5 (false) because %ld\n", count);
+ }
}
}
+ if (trace_inlining()) {
+ THR_Print(" AlwaysInline #6 (false)\n");
+ }
return false;
}
@@ -2550,7 +2586,9 @@ int FlowGraphInliner::Inline() {
return 0;
}
- if (trace_inlining()) {
+ if (trace_inlining() ||
+ strcmp(top.QualifiedUserVisibleNameCString(),
+ "InferenceVisitorBase.storeExpressionInfo") == 0) {
String& name = String::Handle(top.QualifiedUserVisibleName());
THR_Print("Inlining calls in %s\n", name.ToCString());
}Compiled the sdk, then run out/ReleaseX64/dart-sdk/bin/dart compile aot-snapshot --verbose --extra-gen-snapshot-options="--print-flow-graph-filter=storeExpressionInfo,--trace_inlining" pkg/front_end/tool/compile.dart. I get this output:
Inlining calls in InferenceVisitorBase.storeExpressionInfo
Depth 1 ----------
Polymorphic Instance Calls (0)
Static Calls (1)
AlwaysInline for _LinkedHashMapMixin.[]=
AlwaysInline #5 (true) because 14
AlwaysInline for _LinkedHashMapMixin.[]=
AlwaysInline #5 (true) because 14
=> _LinkedHashMapMixin.[]= (deopt count 0)
AlwaysInline for _LinkedHashMapMixin.[]=
AlwaysInline #5 (true) because 14
-- Flowgraph for _LinkedHashMapMixin.[]=
==== dart:_compact_hash___Map&_LinkedHashBase&MapMixin&_HashBase&_OperatorEqualsAndHashCode&_LinkedHashMapMixin@3099033_[]= (RegularFunction)
B0[graph]:0
B6[function entry]:24
Constant(#1)
goto:28 B5
B5[join]:22 pred(B6)
t0 <- LoadLocal(this @3)
t1 <- LoadLocal(key @2)
t0 <- StaticCall:14( _hashCode@3099033<0> t0, t1, using unchecked entrypoint, result_type = T{<:int})
StoreLocal(fullHash @-2, t0)
t0 <- LoadLocal(this @3)
t1 <- LoadLocal(key @2)
t2 <- LoadLocal(value @1)
t3 <- LoadLocal(fullHash @-2)
StaticCall:16( _set@3099033<0> t0, t1, t2, t3, using unchecked entrypoint)
t0 <- Constant(#null)
DartReturn:18(t0)
AlwaysInline for _LinkedHashMapMixin.[]=
AlwaysInline #5 (true) because 14
Success
with reason AlwaysInline, code size 14, call sites: 2
Closure Calls (0)
Depth 2 ----------
Polymorphic Instance Calls (0)
Static Calls (2)
AlwaysInline for _OperatorEqualsAndHashCode._hashCode
vm:prefer-inline pragma for Function '_hashCode@3099033':.
AlwaysInline #1 (true)
AlwaysInline for _OperatorEqualsAndHashCode._hashCode
vm:prefer-inline pragma for Function '_hashCode@3099033':.
AlwaysInline #1 (true)
=> _OperatorEqualsAndHashCode._hashCode (deopt count 0)
AlwaysInline for _OperatorEqualsAndHashCode._hashCode
vm:prefer-inline pragma for Function '_hashCode@3099033':.
AlwaysInline #1 (true)
-- Flowgraph for _OperatorEqualsAndHashCode._hashCode
==== dart:_compact_hash___Map&_LinkedHashBase&MapMixin&_HashBase&_OperatorEqualsAndHashCode@3099033__hashCode@3099033 (RegularFunction)
B0[graph]:0
B7[function entry]:2
t0 <- LoadLocal(e @1)
t0 <- InstanceCall:10( get:hashCode<0>, t0, result_type = T{<:int})
DartReturn:12(t0)
AlwaysInline for _OperatorEqualsAndHashCode._hashCode
vm:prefer-inline pragma for Function '_hashCode@3099033':.
AlwaysInline #1 (true)
Success
with reason AlwaysInline, code size 3, call sites: 1
AlwaysInline for _LinkedHashMapMixin._set
AlwaysInline #6 (false)
AlwaysInline for _LinkedHashMapMixin._set
AlwaysInline #6 (false)
=> _LinkedHashMapMixin._set (deopt count 0)
Bailout: not inlinable due to !function.CanBeInlined()
Closure Calls (0)
Inlining growth factor: 4.250000
So when looking at InferenceVisitorBase.storeExpressionInfo it decides to inline _LinkedHashMapMixin.[]= because it has a size of 14 which is below the updated max of 15. The actual flowgraph for _LinkedHashMapMixin.[]= is also in there.
I then further apply this change:
diff --git a/pkg/front_end/tool/entry_points.dart b/pkg/front_end/tool/entry_points.dart
index 68d8aeb6d51..a19bbff79a0 100644
--- a/pkg/front_end/tool/entry_points.dart
+++ b/pkg/front_end/tool/entry_points.dart
@@ -403,10 +403,11 @@ Future<Uri> compile(
}
Component component;
if (useIncrementalCompiler) {
- IncrementalCompiler incrementalCompiler = new IncrementalCompiler(c);
- IncrementalCompilerResult compilerResult = await incrementalCompiler
- .computeDelta();
- component = compilerResult.component;
+ // IncrementalCompiler incrementalCompiler = new IncrementalCompiler(c);
+ // IncrementalCompilerResult compilerResult = await incrementalCompiler
+ // .computeDelta();
+ // component = compilerResult.component;
+ throw "No";
} else {
CompilerResult compilerResult = await generateKernelInternal(
c,
then run out/ReleaseX64/dart-sdk/bin/dart compile aot-snapshot --verbose --extra-gen-snapshot-options="--print-flow-graph-filter=storeExpressionInfo,--trace_inlining" pkg/front_end/tool/compile.dart. I get this output:
Inlining calls in InferenceVisitorBase.storeExpressionInfo
Depth 1 ----------
Polymorphic Instance Calls (0)
Static Calls (1)
AlwaysInline for _LinkedHashMapMixin.[]=
AlwaysInline #5 (true) because 9
AlwaysInline for _LinkedHashMapMixin.[]=
AlwaysInline #5 (true) because 9
=> _LinkedHashMapMixin.[]= (deopt count 0)
AlwaysInline for _LinkedHashMapMixin.[]=
AlwaysInline #5 (true) because 9
-- Flowgraph for _LinkedHashMapMixin.[]=
==== dart:_compact_hash___Map&_LinkedHashBase&MapMixin&_HashBase&_OperatorEqualsAndHashCode&_LinkedHashMapMixin@3099033_[]= (RegularFunction)
B0[graph]:0
B6[function entry]:24
Constant(#1)
goto:28 B5
B5[join]:22 pred(B6)
t0 <- LoadLocal(this @3)
t1 <- LoadLocal(key @2)
t0 <- StaticCall:14( _hashCode@3099033<0> t0, t1, using unchecked entrypoint, result_type = T{<:int})
StoreLocal(fullHash @-2, t0)
t0 <- LoadLocal(this @3)
t1 <- LoadLocal(key @2)
t2 <- LoadLocal(value @1)
t3 <- LoadLocal(fullHash @-2)
StaticCall:16( _set@3099033<0> t0, t1, t2, t3, using unchecked entrypoint)
t0 <- Constant(#null)
DartReturn:18(t0)
AlwaysInline for _LinkedHashMapMixin.[]=
AlwaysInline #5 (true) because 9
Success
with reason AlwaysInline, code size 9, call sites: 2
Closure Calls (0)
Depth 2 ----------
Polymorphic Instance Calls (0)
Static Calls (2)
AlwaysInline for _OperatorEqualsAndHashCode._hashCode
vm:prefer-inline pragma for Function '_hashCode@3099033':.
AlwaysInline #1 (true)
AlwaysInline for _OperatorEqualsAndHashCode._hashCode
vm:prefer-inline pragma for Function '_hashCode@3099033':.
AlwaysInline #1 (true)
=> _OperatorEqualsAndHashCode._hashCode (deopt count 0)
AlwaysInline for _OperatorEqualsAndHashCode._hashCode
vm:prefer-inline pragma for Function '_hashCode@3099033':.
AlwaysInline #1 (true)
-- Flowgraph for _OperatorEqualsAndHashCode._hashCode
==== dart:_compact_hash___Map&_LinkedHashBase&MapMixin&_HashBase&_OperatorEqualsAndHashCode@3099033__hashCode@3099033 (RegularFunction)
B0[graph]:0
B7[function entry]:2
t0 <- LoadLocal(e @1)
t0 <- InstanceCall:10( get:hashCode<0>, t0, result_type = T{<:int})
DartReturn:12(t0)
AlwaysInline for _OperatorEqualsAndHashCode._hashCode
vm:prefer-inline pragma for Function '_hashCode@3099033':.
AlwaysInline #1 (true)
Success
with reason AlwaysInline, code size 3, call sites: 1
AlwaysInline for _LinkedHashMapMixin._set
AlwaysInline #6 (false)
AlwaysInline for _LinkedHashMapMixin._set
AlwaysInline #6 (false)
=> _LinkedHashMapMixin._set (deopt count 0)
Bailout: not inlinable due to !function.CanBeInlined()
Closure Calls (0)
Inlining growth factor: 3.000000
So when looking at InferenceVisitorBase.storeExpressionInfo it decides to inline _LinkedHashMapMixin.[]= because it has a size of 9 which is below the max. The actual flowgraph for _LinkedHashMapMixin.[]= is also in there.
Now notice that the two flowgraphs are exactly the same even though one is allegedly of size 9 and one of size 14...
If adding some printing of the result I get:
inlined_size_ 12 initial_size_ 4
Inlining growth factor: 3.000000
After Inlining of package:front_end/src/type_inference/inference_visitor_base.dart_InferenceVisitorBase_storeExpressionInfo
==== package:front_end/src/type_inference/inference_visitor_base.dart_InferenceVisitorBase_storeExpressionInfo (RegularFunction)
B0[graph]:0 {
v0 <- Constant(#null) T{Null?}
v1 <- Constant(#<optimized out>) T{Sentinel~}
}
B1[function entry]:2 {
v2 <- Parameter(0 @rdi) T{<:InferenceVisitorBase}
v3 <- Parameter(1 @rsi) T{<:Expression}
v4 <- Parameter(2 @rdx) T{<:ExpressionInfo??}
}
CheckStackOverflow:8(stack=0, loop=0)
v5 <- LoadField(v2 . _expressionInfoMap@222269664 {final}) T{==_Map<Object, ExpressionInfo?>}
v21 <- LoadField(v3 . hashCode {final}) int64
StaticCall:16( _set@3099033<0> v5 T{==_Map<Object, ExpressionInfo?>}, v3 T{<:Expression}, v4 T{<:ExpressionInfo??}, v21 T{<:int}, using unchecked entrypoint)
DartReturn:14(v0)
vs
inlined_size_ 17 initial_size_ 4
Inlining growth factor: 4.250000
After Inlining of package:front_end/src/type_inference/inference_visitor_base.dart_InferenceVisitorBase_storeExpressionInfo
==== package:front_end/src/type_inference/inference_visitor_base.dart_InferenceVisitorBase_storeExpressionInfo (RegularFunction)
B0[graph]:0 {
v0 <- Constant(#null) T{Null?}
v1 <- Constant(#<optimized out>) T{Sentinel~}
}
B1[function entry]:2 {
v2 <- Parameter(0 @rdi) T{<:InferenceVisitorBase}
v3 <- Parameter(1 @rsi) T{<:Expression}
v4 <- Parameter(2 @rdx) T{<:ExpressionInfo??}
}
CheckStackOverflow:8(stack=0, loop=0)
v5 <- LoadField(v2 . _expressionInfoMap@232269664 {final}) T{==_Map<Object, ExpressionInfo?>}
v21 <- LoadField(v3 . hashCode {final}) int64
StaticCall:16( _set@3099033<0> v5 T{==_Map<Object, ExpressionInfo?>}, v3 T{<:Expression}, v4 T{<:ExpressionInfo??}, v21 T{<:int}, using unchecked entrypoint)
DartReturn:14(v0)
which except for @222269664 vs @232269664 and the (seemingly bogus) growth factor thing is also the same...
For the normal cutoff of 10 this means that one is inlined and one is not.
/cc @mraleph
Activity
biggs0125 commentedon Sep 23, 2026
According to @alexmarkov inlining is order dependent. And whether or not the target code was optimized. So when the code size heuristic was calculated may have been at different points in the compilation and therefore yielded different results.