[vm/compiler] Merge cid ranges when generating receiver check

This change adds merging of cid ranges into Cids::Create if there are
gaps with the same target function. Previously, this optimization was
performed only when calculating targets via CallTargets::CreateAndExpand.
However, Cids::Create is widely used to check receiver type if there is
only one targets (e.g. in CallSpecializer::AddReceiverCheck /
CallSpecializer::AddChecksForArgNr).

This optimization is needed to shrink huge code for CheckClass instructions
in Flutter RenderObject.layout() method in JIT mode.

Issue: https://github.com/dart-lang/sdk/issues/36428
Change-Id: I97051b4213066b252ac3238df55ab4ed5c98f412
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/103402
Commit-Queue: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Vyacheslav Egorov <vegorov@google.com>
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
diff --git a/runtime/vm/compiler/backend/il.cc b/runtime/vm/compiler/backend/il.cc
index c2aeb5e..7edc129 100644
--- a/runtime/vm/compiler/backend/il.cc
+++ b/runtime/vm/compiler/backend/il.cc
@@ -649,24 +649,75 @@
   return cids;
 }
 
-Cids* Cids::Create(Zone* zone, const ICData& ic_data, int argument_number) {
+Cids* Cids::CreateAndExpand(Zone* zone,
+                            const ICData& ic_data,
+                            int argument_number) {
   Cids* cids = new (zone) Cids(zone);
   cids->CreateHelper(zone, ic_data, argument_number,
                      /* include_targets = */ false);
   cids->Sort(OrderById);
 
   // Merge adjacent class id ranges.
-  int dest = 0;
-  for (int src = 1; src < cids->length(); src++) {
-    if (cids->cid_ranges_[dest]->cid_end + 1 >=
-        cids->cid_ranges_[src]->cid_start) {
-      cids->cid_ranges_[dest]->cid_end = cids->cid_ranges_[src]->cid_end;
-    } else {
-      dest++;
-      if (src != dest) cids->cid_ranges_[dest] = cids->cid_ranges_[src];
+  {
+    int dest = 0;
+    for (int src = 1; src < cids->length(); src++) {
+      if (cids->cid_ranges_[dest]->cid_end + 1 >=
+          cids->cid_ranges_[src]->cid_start) {
+        cids->cid_ranges_[dest]->cid_end = cids->cid_ranges_[src]->cid_end;
+      } else {
+        dest++;
+        if (src != dest) cids->cid_ranges_[dest] = cids->cid_ranges_[src];
+      }
+    }
+    cids->SetLength(dest + 1);
+  }
+
+  // Merging/extending cid ranges is also done in CallTargets::CreateAndExpand.
+  // If changing this code, consider also adjusting CallTargets code.
+
+  if (cids->length() > 1 && argument_number == 0 && ic_data.HasOneTarget()) {
+    // Try harder to merge ranges if method lookups in the gaps result in the
+    // same target method.
+    const Function& target = Function::Handle(zone, ic_data.GetTargetAt(0));
+    if (!MethodRecognizer::PolymorphicTarget(target)) {
+      const auto& args_desc_array =
+          Array::Handle(zone, ic_data.arguments_descriptor());
+      ArgumentsDescriptor args_desc(args_desc_array);
+      const auto& name = String::Handle(zone, ic_data.target_name());
+      auto& fn = Function::Handle(zone);
+
+      intptr_t dest = 0;
+      for (intptr_t src = 1; src < cids->length(); src++) {
+        // Inspect all cids in the gap and see if they all resolve to the same
+        // target.
+        bool can_merge = true;
+        for (intptr_t cid = cids->cid_ranges_[dest]->cid_end + 1,
+                      end = cids->cid_ranges_[src]->cid_start;
+             cid < end; ++cid) {
+          bool class_is_abstract = false;
+          if (FlowGraphCompiler::LookupMethodFor(cid, name, args_desc, &fn,
+                                                 &class_is_abstract)) {
+            if (fn.raw() == target.raw()) {
+              continue;
+            }
+            if (class_is_abstract) {
+              continue;
+            }
+          }
+          can_merge = false;
+          break;
+        }
+
+        if (can_merge) {
+          cids->cid_ranges_[dest]->cid_end = cids->cid_ranges_[src]->cid_end;
+        } else {
+          dest++;
+          if (src != dest) cids->cid_ranges_[dest] = cids->cid_ranges_[src];
+        }
+      }
+      cids->SetLength(dest + 1);
     }
   }
-  cids->SetLength(dest + 1);
 
   return cids;
 }
@@ -3651,6 +3702,9 @@
 
   intptr_t length = targets.length();
 
+  // Merging/extending cid ranges is also done in Cids::CreateAndExpand.
+  // If changing this code, consider also adjusting Cids code.
+
   // Spread class-ids to preceding classes where a lookup yields the same
   // method.  A polymorphic target is not really the same method since its
   // behaviour depends on the receiver class-id, so we don't spread the
diff --git a/runtime/vm/compiler/backend/il.h b/runtime/vm/compiler/backend/il.h
index 3e91b50..f2be34a 100644
--- a/runtime/vm/compiler/backend/il.h
+++ b/runtime/vm/compiler/backend/il.h
@@ -555,7 +555,11 @@
   explicit Cids(Zone* zone) : zone_(zone) {}
   // Creates the off-heap Cids object that reflects the contents
   // of the on-VM-heap IC data.
-  static Cids* Create(Zone* zone, const ICData& ic_data, int argument_number);
+  // Ranges of Cids are merged if there is only one target function and
+  // it is used for all cids in the gaps between ranges.
+  static Cids* CreateAndExpand(Zone* zone,
+                               const ICData& ic_data,
+                               int argument_number);
   static Cids* CreateMonomorphic(Zone* zone, intptr_t cid);
 
   bool Equals(const Cids& other) const;
diff --git a/runtime/vm/compiler/backend/inliner.cc b/runtime/vm/compiler/backend/inliner.cc
index d3b03a9..7be5d0e 100644
--- a/runtime/vm/compiler/backend/inliner.cc
+++ b/runtime/vm/compiler/backend/inliner.cc
@@ -3322,10 +3322,10 @@
     // Insert receiver class or null check if needed.
     switch (check) {
       case FlowGraph::ToCheck::kCheckCid: {
-        Instruction* check_class =
-            flow_graph->CreateCheckClass(call->Receiver()->definition(),
-                                         *Cids::Create(Z, *call->ic_data(), 0),
-                                         call->deopt_id(), call->token_pos());
+        Instruction* check_class = flow_graph->CreateCheckClass(
+            call->Receiver()->definition(),
+            *Cids::CreateAndExpand(Z, *call->ic_data(), 0), call->deopt_id(),
+            call->token_pos());
         flow_graph->InsertBefore(call, check_class, call->env(),
                                  FlowGraph::kEffect);
         break;
diff --git a/runtime/vm/compiler/call_specializer.cc b/runtime/vm/compiler/call_specializer.cc
index 5f12916..1c405f9 100644
--- a/runtime/vm/compiler/call_specializer.cc
+++ b/runtime/vm/compiler/call_specializer.cc
@@ -366,7 +366,8 @@
 void CallSpecializer::AddChecksForArgNr(InstanceCallInstr* call,
                                         Definition* instr,
                                         int argument_number) {
-  const Cids* cids = Cids::Create(Z, *call->ic_data(), argument_number);
+  const Cids* cids =
+      Cids::CreateAndExpand(Z, *call->ic_data(), argument_number);
   AddCheckClass(instr, *cids, call->deopt_id(), call->env(), call);
 }
 
@@ -1502,7 +1503,7 @@
                 new (Z) Value(call->ArgumentAt(1)), call->deopt_id(),
                 result_cid);
             const Cids* cids =
-                Cids::Create(Z, ic_data, /* argument_number =*/0);
+                Cids::CreateAndExpand(Z, ic_data, /* argument_number =*/0);
             AddCheckClass(min_max->left()->definition(), *cids,
                           call->deopt_id(), call->env(), call);
             AddCheckClass(min_max->right()->definition(), *cids,