Skip to content

VM Inlining curiosity #64284

Closed as duplicate
Closed as duplicate

Description

@jensjoha

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

  1. added
    area-vmUse area-vm for VM related issues, including code coverage, and the AOT and JIT backends.
    on Sep 17, 2026
  2. biggs0125 commented on Sep 23, 2026

    @biggs0125

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-vmUse area-vm for VM related issues, including code coverage, and the AOT and JIT backends.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions