[vm/compiler] Improved type analysis for check class.

Rationale:
Improves the analysis if instance calls need checks
(check class or check null) combined with CHA.

Bug: https://github.com/dart-lang/sdk/issues/33664


Change-Id: I0a4761f8816bf5a5aaf5d17ca56d8bf4e3b79fc3
Reviewed-on: https://dart-review.googlesource.com/64440
Commit-Queue: Aart Bik <ajcbik@google.com>
Reviewed-by: Vyacheslav Egorov <vegorov@google.com>
Reviewed-by: Alexander Markov <alexmarkov@google.com>
diff --git a/runtime/vm/compiler/aot/aot_call_specializer.cc b/runtime/vm/compiler/aot/aot_call_specializer.cc
index 93bcf21bf..b41ad00 100644
--- a/runtime/vm/compiler/aot/aot_call_specializer.cc
+++ b/runtime/vm/compiler/aot/aot_call_specializer.cc
@@ -800,7 +800,8 @@
   if (has_one_target) {
     RawFunction::Kind function_kind =
         Function::Handle(Z, unary_checks.GetTargetAt(0)).kind();
-    if (!flow_graph()->InstanceCallNeedsClassCheck(instr, function_kind)) {
+    if (flow_graph()->CheckForInstanceCall(instr, function_kind) ==
+        FlowGraph::ToCheck::kNoCheck) {
       CallTargets* targets = CallTargets::Create(Z, unary_checks);
       ASSERT(targets->HasSingleTarget());
       const Function& target = targets->FirstTarget();
diff --git a/runtime/vm/compiler/backend/flow_graph.cc b/runtime/vm/compiler/backend/flow_graph.cc
index 1f079f6..02d00b3 100644
--- a/runtime/vm/compiler/backend/flow_graph.cc
+++ b/runtime/vm/compiler/backend/flow_graph.cc
@@ -15,6 +15,7 @@
 #include "vm/compiler/frontend/flow_graph_builder.h"
 #include "vm/growable_array.h"
 #include "vm/object_store.h"
+#include "vm/resolver.h"
 
 namespace dart {
 
@@ -416,38 +417,91 @@
   return (phi->is_receiver() == PhiInstr::kReceiver);
 }
 
-// Use CHA to determine if the call needs a class check: if the callee's
-// receiver is the same as the caller's receiver and there are no overridden
-// callee functions, then no class check is needed.
-bool FlowGraph::InstanceCallNeedsClassCheck(InstanceCallInstr* call,
-                                            RawFunction::Kind kind) const {
+FlowGraph::ToCheck FlowGraph::CheckForInstanceCall(
+    InstanceCallInstr* call,
+    RawFunction::Kind kind) const {
   if (!FLAG_use_cha_deopt && !isolate()->all_classes_finalized()) {
     // Even if class or function are private, lazy class finalization
     // may later add overriding methods.
-    return true;
+    return ToCheck::kCheckCid;
   }
-  Definition* callee_receiver = call->Receiver()->definition();
-  ASSERT(callee_receiver != NULL);
-  if (function().IsDynamicFunction() && IsReceiver(callee_receiver)) {
-    const String& name =
-        (kind == RawFunction::kMethodExtractor)
-            ? String::Handle(zone(),
-                             Field::NameFromGetter(call->function_name()))
-            : call->function_name();
-    const Class& cls = Class::Handle(zone(), function().Owner());
-    intptr_t subclass_count = 0;
-    if (!thread()->cha()->HasOverride(cls, name, &subclass_count)) {
-      if (FLAG_trace_cha) {
-        THR_Print(
-            "  **(CHA) Instance call needs no check, "
-            "no overrides of '%s' '%s'\n",
-            name.ToCString(), cls.ToCString());
+
+  // Best effort to get the receiver class.
+  Value* receiver = call->Receiver();
+  Class& receiver_class = Class::Handle(zone());
+  bool receiver_maybe_null = false;
+  if (function().IsDynamicFunction() && IsReceiver(receiver->definition())) {
+    // Call receiver is callee receiver: calling "this.g()" in f().
+    receiver_class = function().Owner();
+  } else if (FLAG_use_strong_mode_types && isolate()->strong()) {
+    // In strong mode, get the receiver's compile type. Note that
+    // we allow nullable types, which may result in just generating
+    // a null check rather than the more elaborate class check
+    CompileType* type = receiver->Type();
+    const AbstractType* atype = type->ToAbstractType();
+    if (atype->HasResolvedTypeClass() && !atype->IsDynamicType()) {
+      if (type->is_nullable()) {
+        receiver_maybe_null = true;
       }
-      thread()->cha()->AddToGuardedClasses(cls, subclass_count);
-      return false;
+      receiver_class = atype->type_class();
+      if (receiver_class.is_implemented()) {
+        receiver_class = Class::null();
+      }
     }
   }
-  return true;
+
+  // Useful receiver class information?
+  if (receiver_class.IsNull()) {
+    return ToCheck::kCheckCid;
+  } else if (FLAG_precompiled_mode && call->HasICData() &&
+             !call->ic_data()->HasReceiverClassId(receiver_class.id())) {
+    // In AOT, if the static class type does not match information
+    // found in ICData (which may be "guessed"), then bail, since
+    // subsequent code generation for inlining uses the latter.
+    // TODO(ajcbik): improve this by using the static class in AOT.
+    return ToCheck::kCheckCid;
+  }
+
+  const String& method_name =
+      (kind == RawFunction::kMethodExtractor)
+          ? String::Handle(zone(), Field::NameFromGetter(call->function_name()))
+          : call->function_name();
+
+  // If the receiver can have the null value, exclude any method
+  // that is actually valid on a null receiver.
+  if (receiver_maybe_null) {
+#ifdef TARGET_ARCH_DBC
+    // TODO(ajcbik): DBC does not support null check at all yet.
+    return ToCheck::kCheckCid;
+#else
+    const Class& null_class =
+        Class::Handle(zone(), isolate()->object_store()->null_class());
+    const Function& target = Function::Handle(
+        zone(),
+        Resolver::ResolveDynamicAnyArgs(zone(), null_class, method_name));
+    if (!target.IsNull()) {
+      return ToCheck::kCheckCid;
+    }
+#endif
+  }
+
+  // Use CHA to determine if the method is not overridden by any subclass
+  // of the receiver class. Any methods that are valid when the receiver
+  // has a null value are excluded above (to avoid throwing an exception
+  // on something valid, like null.hashCode).
+  intptr_t subclass_count = 0;
+  if (!thread()->cha()->HasOverride(receiver_class, method_name,
+                                    &subclass_count)) {
+    if (FLAG_trace_cha) {
+      THR_Print(
+          "  **(CHA) Instance call needs no class check since there "
+          "are no overrides of method '%s' on '%s'\n",
+          method_name.ToCString(), receiver_class.ToCString());
+    }
+    thread()->cha()->AddToGuardedClasses(receiver_class, subclass_count);
+    return receiver_maybe_null ? ToCheck::kCheckNull : ToCheck::kNoCheck;
+  }
+  return ToCheck::kCheckCid;
 }
 
 Instruction* FlowGraph::CreateCheckClass(Definition* to_check,
diff --git a/runtime/vm/compiler/backend/flow_graph.h b/runtime/vm/compiler/backend/flow_graph.h
index a583585..4a8e86c 100644
--- a/runtime/vm/compiler/backend/flow_graph.h
+++ b/runtime/vm/compiler/backend/flow_graph.h
@@ -183,8 +183,13 @@
     return current_ssa_temp_index();
   }
 
-  bool InstanceCallNeedsClassCheck(InstanceCallInstr* call,
-                                   RawFunction::Kind kind) const;
+  enum class ToCheck { kNoCheck, kCheckNull, kCheckCid };
+
+  // Uses CHA to determine if the called method can be overridden.
+  // Return value indicates that the call needs no check at all,
+  // just a null check, or a full class check.
+  ToCheck CheckForInstanceCall(InstanceCallInstr* call,
+                               RawFunction::Kind kind) const;
 
   Thread* thread() const { return thread_; }
   Zone* zone() const { return thread()->zone(); }
diff --git a/runtime/vm/compiler/backend/inliner.cc b/runtime/vm/compiler/backend/inliner.cc
index f041b41..07c8847 100644
--- a/runtime/vm/compiler/backend/inliner.cc
+++ b/runtime/vm/compiler/backend/inliner.cc
@@ -3117,13 +3117,35 @@
           flow_graph, receiver_cid, target, call,
           call->Receiver()->definition(), call->token_pos(), call->ic_data(),
           &entry, &last, policy)) {
-    // Insert receiver class check if needed.
-    if (MethodRecognizer::PolymorphicTarget(target) ||
-        flow_graph->InstanceCallNeedsClassCheck(call, target.kind())) {
-      Instruction* check = flow_graph->CreateCheckClass(
-          call->Receiver()->definition(), *Cids::Create(Z, *call->ic_data(), 0),
-          call->deopt_id(), call->token_pos());
-      flow_graph->InsertBefore(call, check, call->env(), FlowGraph::kEffect);
+    // Determine if inlining instance methods needs a check.
+    FlowGraph::ToCheck check = FlowGraph::ToCheck::kNoCheck;
+    if (MethodRecognizer::PolymorphicTarget(target)) {
+      check = FlowGraph::ToCheck::kCheckCid;
+    } else {
+      check = flow_graph->CheckForInstanceCall(call, target.kind());
+    }
+
+    // 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());
+        flow_graph->InsertBefore(call, check_class, call->env(),
+                                 FlowGraph::kEffect);
+        break;
+      }
+      case FlowGraph::ToCheck::kCheckNull: {
+        Instruction* check_null = new (Z) CheckNullInstr(
+            call->Receiver()->CopyWithType(Z), call->function_name(),
+            call->deopt_id(), call->token_pos());
+        flow_graph->InsertBefore(call, check_null, call->env(),
+                                 FlowGraph::kEffect);
+        break;
+      }
+      case FlowGraph::ToCheck::kNoCheck:
+        break;
     }
 
     // Remove the original push arguments.
diff --git a/runtime/vm/compiler/call_specializer.cc b/runtime/vm/compiler/call_specializer.cc
index e6b1481..38c4791 100644
--- a/runtime/vm/compiler/call_specializer.cc
+++ b/runtime/vm/compiler/call_specializer.cc
@@ -879,13 +879,20 @@
   const Field& field = Field::ZoneHandle(Z, GetField(class_ids[0], field_name));
   ASSERT(!field.IsNull());
 
-  if (flow_graph()->InstanceCallNeedsClassCheck(call,
-                                                RawFunction::kImplicitGetter)) {
-    if (FLAG_precompiled_mode) {
-      return false;
-    }
-
-    AddReceiverCheck(call);
+  switch (
+      flow_graph()->CheckForInstanceCall(call, RawFunction::kImplicitGetter)) {
+    case FlowGraph::ToCheck::kCheckNull:
+      AddCheckNull(call->Receiver(), call->function_name(), call->deopt_id(),
+                   call->env(), call);
+      break;
+    case FlowGraph::ToCheck::kCheckCid:
+      if (FLAG_precompiled_mode) {
+        return false;  // AOT cannot class check
+      }
+      AddReceiverCheck(call);
+      break;
+    case FlowGraph::ToCheck::kNoCheck:
+      break;
   }
   InlineImplicitInstanceGetter(call, field);
   return true;
@@ -942,13 +949,20 @@
   const Field& field = Field::ZoneHandle(Z, GetField(class_id, field_name));
   ASSERT(!field.IsNull());
 
-  if (flow_graph()->InstanceCallNeedsClassCheck(instr,
-                                                RawFunction::kImplicitSetter)) {
-    if (FLAG_precompiled_mode) {
-      return false;
-    }
-
-    AddReceiverCheck(instr);
+  switch (
+      flow_graph()->CheckForInstanceCall(instr, RawFunction::kImplicitSetter)) {
+    case FlowGraph::ToCheck::kCheckNull:
+      AddCheckNull(instr->Receiver(), instr->function_name(), instr->deopt_id(),
+                   instr->env(), instr);
+      break;
+    case FlowGraph::ToCheck::kCheckCid:
+      if (FLAG_precompiled_mode) {
+        return false;  // AOT cannot class check
+      }
+      AddReceiverCheck(instr);
+      break;
+    case FlowGraph::ToCheck::kNoCheck:
+      break;
   }
 
   if (I->use_field_guards()) {
diff --git a/runtime/vm/compiler/jit/jit_call_specializer.cc b/runtime/vm/compiler/jit/jit_call_specializer.cc
index fa0ae29..aabe9ac 100644
--- a/runtime/vm/compiler/jit/jit_call_specializer.cc
+++ b/runtime/vm/compiler/jit/jit_call_specializer.cc
@@ -132,8 +132,8 @@
   if (has_one_target) {
     const Function& target =
         Function::ZoneHandle(Z, unary_checks.GetTargetAt(0));
-    const RawFunction::Kind function_kind = target.kind();
-    if (!flow_graph()->InstanceCallNeedsClassCheck(instr, function_kind)) {
+    if (flow_graph()->CheckForInstanceCall(instr, target.kind()) ==
+        FlowGraph::ToCheck::kNoCheck) {
       StaticCallInstr* call = StaticCallInstr::FromCall(Z, instr, target);
       instr->ReplaceWith(call, current_iterator());
       return;
diff --git a/tests/standalone_2/check_class_cha_test.dart b/tests/standalone_2/check_class_cha_test.dart
new file mode 100644
index 0000000..19ed029
--- /dev/null
+++ b/tests/standalone_2/check_class_cha_test.dart
@@ -0,0 +1,66 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import "package:expect/expect.dart";
+
+// Class hierarchy on an abstract class
+// that defines a "next" structure.
+
+abstract class A {
+  A next;
+}
+
+class B extends A {
+  B(A n) {
+    this.next = n;
+  }
+}
+
+// Method that counts length of list.
+// With only Bs, the getter can be
+// inlined without check class.
+int countMe(A i) {
+  int x = 0;
+  while (i != null) {
+    A next = i.next;
+    x++;
+    i = next;
+  }
+  return x;
+}
+
+int doitHot(A a) {
+  // Warm up the JIT.
+  int d = 0;
+  for (int i = 0; i < 1000; i++) {
+    d += countMe(a);
+  }
+  return d;
+}
+
+// Nasty class that overrides the getter.
+class C extends A {
+  C(A n) {
+    this.next = n;
+  }
+  // New override.
+  A get next => null;
+}
+
+int bringInC(A a) {
+  // Introduce C to compiler.
+  a = new C(a);
+  return doitHot(a);
+}
+
+main() {
+  // Make a list with just Bs.
+  A a = null;
+  for (int i = 0; i < 1000; i++) {
+    a = new B(a);
+  }
+
+  Expect.equals(1000 * 1000, doitHot(a));
+  Expect.equals(1000, bringInC(a));
+}
diff --git a/tests/standalone_2/check_null_cha_test.dart b/tests/standalone_2/check_null_cha_test.dart
new file mode 100644
index 0000000..fc9c17d
--- /dev/null
+++ b/tests/standalone_2/check_null_cha_test.dart
@@ -0,0 +1,58 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import "package:expect/expect.dart";
+
+// A class the has a getter also provided by Object
+// (higher in the class hierarchy) and thus also the
+// Null class (besides X in the class hierarchy).
+class X {
+  int hashCode;
+  X() {
+    hashCode = 1;
+  }
+}
+
+// Use this getter on X receiver.
+int hashMe(X x) {
+  int d = 0;
+  for (int i = 0; i < 10; i++) {
+    d += x.hashCode;
+  }
+  return d;
+}
+
+// Use this getter on Null class receiver.
+// Only possible value is null.
+int hashNull(Null x) {
+  int d = 0;
+  for (int i = 0; i < 10; i++) {
+    d += x.hashCode;
+  }
+  return d;
+}
+
+main() {
+  // Warm up the JIT with just an X object. Having a single receiver
+  // of type X with nothing below in the hierarchy that overrides
+  // hashCode could tempt the JIT to inline the getter with CHA
+  // that deopts when X is subclassed in the future.
+  X x = new X();
+  for (int i = 0; i < 1000; i++) {
+    Expect.equals(10, hashMe(x));
+  }
+
+  // However, this is a special case that also works on null
+  // (calling Object's hashCode). So this should not throw an
+  // exception. Had we inlined, this would have hit the null
+  // check and thrown an exception.
+  Expect.notEquals(0, hashMe(null));
+
+  // Also warm up the JIT on a direct Null receiver.
+  int d = 0;
+  for (int i = 0; i < 1000; i++) {
+    d += hashNull(null);
+  }
+  Expect.notEquals(0, d);
+}