[turbofan] Refactor the JSCreateArray lowering to support more cases.

Array (subclass) constructor calls with 0 parameters are now properly
turned into inline allocations, also Array (subclass) constructor calls
with exactly one parameter, which is either known to not be a Number
or which is a known integer in the valid loop unrolling range.

Also refactor the general JSCreateArray lowering logic to properly
support Array subclasses, i.e. deal with inobject properties and
initial maps correctly.

This boosts performance of those cases significantly (and will allow
us to reduce the complexity of the Array constructor significantly
long-term). For example the simple case

  new Array("a", "b", "c", "d", "e", "f", "g")

is now around 10x faster than before.

Bug: v8:6399
Change-Id: I70661971398524ee0c6a349ee559b98a962a6266
Reviewed-on: https://chromium-review.googlesource.com/703134
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Reviewed-by: Jaroslav Sevcik <jarin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#48325}
diff --git a/src/compiler/js-create-lowering.cc b/src/compiler/js-create-lowering.cc
index 727a7b9..c231ae2 100644
--- a/src/compiler/js-create-lowering.cc
+++ b/src/compiler/js-create-lowering.cc
@@ -581,27 +581,20 @@
 
 Reduction JSCreateLowering::ReduceNewArray(Node* node, Node* length,
                                            int capacity,
-                                           Handle<AllocationSite> site) {
+                                           Handle<Map> initial_map,
+                                           PretenureFlag pretenure) {
   DCHECK(node->opcode() == IrOpcode::kJSCreateArray ||
          node->opcode() == IrOpcode::kJSCreateEmptyLiteralArray);
   Node* effect = NodeProperties::GetEffectInput(node);
   Node* control = NodeProperties::GetControlInput(node);
 
-  // Extract transition and tenuring feedback from the {site} and add
-  // appropriate code dependencies on the {site} if deoptimization is
-  // enabled.
-  PretenureFlag pretenure = site->GetPretenureMode();
-  ElementsKind elements_kind = site->GetElementsKind();
-  DCHECK(IsFastElementsKind(elements_kind));
-  if (NodeProperties::GetType(length)->Max() > 0) {
+  // Determine the appropriate elements kind.
+  ElementsKind elements_kind = initial_map->elements_kind();
+  if (NodeProperties::GetType(length)->Max() > 0.0) {
     elements_kind = GetHoleyElementsKind(elements_kind);
+    initial_map = Map::AsElementsKind(initial_map, elements_kind);
   }
-  dependencies()->AssumeTenuringDecision(site);
-  dependencies()->AssumeTransitionStable(site);
-
-  // Retrieve the initial map for the array.
-  Node* js_array_map = jsgraph()->HeapConstant(
-      handle(native_context()->GetInitialJSArrayMap(elements_kind), isolate()));
+  DCHECK(IsFastElementsKind(elements_kind));
 
   // Setup elements and properties.
   Node* elements;
@@ -615,11 +608,15 @@
 
   // Perform the allocation of the actual JSArray object.
   AllocationBuilder a(jsgraph(), effect, control);
-  a.Allocate(JSArray::kSize, pretenure);
-  a.Store(AccessBuilder::ForMap(), js_array_map);
+  a.Allocate(initial_map->instance_size(), pretenure);
+  a.Store(AccessBuilder::ForMap(), initial_map);
   a.Store(AccessBuilder::ForJSObjectPropertiesOrHash(), properties);
   a.Store(AccessBuilder::ForJSObjectElements(), elements);
   a.Store(AccessBuilder::ForJSArrayLength(elements_kind), length);
+  for (int i = 0; i < initial_map->GetInObjectProperties(); ++i) {
+    a.Store(AccessBuilder::ForJSObjectInObjectProperty(initial_map, i),
+            jsgraph()->UndefinedConstant());
+  }
   RelaxControls(node);
   a.FinishAndChange(node);
   return Changed(node);
@@ -627,19 +624,15 @@
 
 Reduction JSCreateLowering::ReduceNewArray(Node* node,
                                            std::vector<Node*> values,
-                                           Handle<AllocationSite> site) {
+                                           Handle<Map> initial_map,
+                                           PretenureFlag pretenure) {
   DCHECK_EQ(IrOpcode::kJSCreateArray, node->opcode());
   Node* effect = NodeProperties::GetEffectInput(node);
   Node* control = NodeProperties::GetControlInput(node);
 
-  // Extract transition and tenuring feedback from the {site} and add
-  // appropriate code dependencies on the {site} if deoptimization is
-  // enabled.
-  PretenureFlag pretenure = site->GetPretenureMode();
-  ElementsKind elements_kind = site->GetElementsKind();
+  // Determine the appropriate elements kind.
+  ElementsKind elements_kind = initial_map->elements_kind();
   DCHECK(IsFastElementsKind(elements_kind));
-  dependencies()->AssumeTenuringDecision(site);
-  dependencies()->AssumeTransitionStable(site);
 
   // Check {values} based on the {elements_kind}. These checks are guarded
   // by the {elements_kind} feedback on the {site}, so it's safe to just
@@ -662,10 +655,6 @@
     }
   }
 
-  // Retrieve the initial map for the array.
-  Node* js_array_map = jsgraph()->HeapConstant(
-      handle(native_context()->GetInitialJSArrayMap(elements_kind), isolate()));
-
   // Setup elements, properties and length.
   Node* elements = effect =
       AllocateElements(effect, control, elements_kind, values, pretenure);
@@ -674,11 +663,15 @@
 
   // Perform the allocation of the actual JSArray object.
   AllocationBuilder a(jsgraph(), effect, control);
-  a.Allocate(JSArray::kSize, pretenure);
-  a.Store(AccessBuilder::ForMap(), js_array_map);
+  a.Allocate(initial_map->instance_size(), pretenure);
+  a.Store(AccessBuilder::ForMap(), initial_map);
   a.Store(AccessBuilder::ForJSObjectPropertiesOrHash(), properties);
   a.Store(AccessBuilder::ForJSObjectElements(), elements);
   a.Store(AccessBuilder::ForJSArrayLength(elements_kind), length);
+  for (int i = 0; i < initial_map->GetInObjectProperties(); ++i) {
+    a.Store(AccessBuilder::ForJSObjectInObjectProperty(initial_map, i),
+            jsgraph()->UndefinedConstant());
+  }
   RelaxControls(node);
   a.FinishAndChange(node);
   return Changed(node);
@@ -749,49 +742,124 @@
 Reduction JSCreateLowering::ReduceJSCreateArray(Node* node) {
   DCHECK_EQ(IrOpcode::kJSCreateArray, node->opcode());
   CreateArrayParameters const& p = CreateArrayParametersOf(node->op());
+  int const arity = static_cast<int>(p.arity());
+  Handle<AllocationSite> const site = p.site();
+  PretenureFlag pretenure = NOT_TENURED;
+  Handle<JSFunction> constructor(native_context()->array_function(), isolate());
   Node* target = NodeProperties::GetValueInput(node, 0);
   Node* new_target = NodeProperties::GetValueInput(node, 1);
+  Type* new_target_type = (target == new_target)
+                              ? Type::HeapConstant(constructor, zone())
+                              : NodeProperties::GetType(new_target);
 
-  // TODO(bmeurer): Optimize the subclassing case.
-  if (target != new_target) return NoChange();
+  // Extract original constructor function.
+  if (new_target_type->IsHeapConstant() &&
+      new_target_type->AsHeapConstant()->Value()->IsJSFunction()) {
+    Handle<JSFunction> original_constructor =
+        Handle<JSFunction>::cast(new_target_type->AsHeapConstant()->Value());
+    DCHECK(constructor->IsConstructor());
+    DCHECK(original_constructor->IsConstructor());
 
-  // Check if we have a feedback {site} on the {node}.
-  Handle<AllocationSite> site = p.site();
-  if (!site.is_null()) {
-    // Attempt to inline calls to the Array constructor for the relevant cases
-    // where either no arguments are provided, or exactly one unsigned number
-    // argument is given.
-    if (site->CanInlineCall()) {
-      if (p.arity() == 0) {
+    // Check if we can inline the allocation.
+    if (IsAllocationInlineable(constructor, original_constructor)) {
+      // Force completion of inobject slack tracking before
+      // generating code to finalize the instance size.
+      original_constructor->CompleteInobjectSlackTrackingIfActive();
+      Handle<Map> initial_map(original_constructor->initial_map(), isolate());
+
+      // Add a dependency on the {initial_map} to make sure that this code is
+      // deoptimized whenever the {initial_map} changes.
+      dependencies()->AssumeInitialMapCantChange(initial_map);
+
+      // Check if we have a feedback {site} on the {node}.
+      if (!site.is_null()) {
+        ElementsKind elements_kind = site->GetElementsKind();
+        if (initial_map->elements_kind() != elements_kind) {
+          initial_map = Map::AsElementsKind(initial_map, elements_kind);
+        }
+        pretenure = site->GetPretenureMode();
+
+        dependencies()->AssumeTransitionStable(site);
+        dependencies()->AssumeTenuringDecision(site);
+      }
+
+      if (arity == 0) {
         Node* length = jsgraph()->ZeroConstant();
         int capacity = JSArray::kPreallocatedArrayElements;
-        return ReduceNewArray(node, length, capacity, site);
-      } else if (p.arity() == 1) {
+        return ReduceNewArray(node, length, capacity, initial_map, pretenure);
+      } else if (arity == 1) {
         Node* length = NodeProperties::GetValueInput(node, 2);
         Type* length_type = NodeProperties::GetType(length);
         if (!length_type->Maybe(Type::Number())) {
           // Handle the single argument case, where we know that the value
           // cannot be a valid Array length.
-          return ReduceNewArray(node, {length}, site);
+          ElementsKind elements_kind = initial_map->elements_kind();
+          elements_kind = GetMoreGeneralElementsKind(
+              elements_kind, IsHoleyElementsKind(elements_kind)
+                                 ? HOLEY_ELEMENTS
+                                 : PACKED_ELEMENTS);
+          initial_map = Map::AsElementsKind(initial_map, elements_kind);
+          return ReduceNewArray(node, {length}, initial_map, pretenure);
         }
         if (length_type->Is(Type::SignedSmall()) && length_type->Min() >= 0 &&
             length_type->Max() <= kElementLoopUnrollLimit &&
             length_type->Min() == length_type->Max()) {
           int capacity = static_cast<int>(length_type->Max());
-          return ReduceNewArray(node, length, capacity, site);
+          return ReduceNewArray(node, length, capacity, initial_map, pretenure);
         }
-      } else if (p.arity() <= JSArray::kInitialMaxFastElementArray) {
+      } else if (arity <= JSArray::kInitialMaxFastElementArray) {
+        // Gather the values to store into the newly created array.
+        bool values_all_smis = true, values_all_numbers = true,
+             values_any_nonnumber = false;
         std::vector<Node*> values;
         values.reserve(p.arity());
-        for (size_t i = 0; i < p.arity(); ++i) {
-          values.push_back(
-              NodeProperties::GetValueInput(node, static_cast<int>(2 + i)));
+        for (int i = 0; i < arity; ++i) {
+          Node* value = NodeProperties::GetValueInput(node, 2 + i);
+          Type* value_type = NodeProperties::GetType(value);
+          if (!value_type->Is(Type::SignedSmall())) {
+            values_all_smis = false;
+          }
+          if (!value_type->Is(Type::Number())) {
+            values_all_numbers = false;
+          }
+          if (!value_type->Maybe(Type::Number())) {
+            values_any_nonnumber = true;
+          }
+          values.push_back(value);
         }
-        return ReduceNewArray(node, values, site);
+
+        // Try to figure out the ideal elements kind statically.
+        ElementsKind elements_kind = initial_map->elements_kind();
+        if (values_all_smis) {
+          // Smis can be stored with any elements kind.
+        } else if (values_all_numbers) {
+          elements_kind = GetMoreGeneralElementsKind(
+              elements_kind, IsHoleyElementsKind(elements_kind)
+                                 ? HOLEY_DOUBLE_ELEMENTS
+                                 : PACKED_DOUBLE_ELEMENTS);
+        } else if (values_any_nonnumber) {
+          elements_kind = GetMoreGeneralElementsKind(
+              elements_kind, IsHoleyElementsKind(elements_kind)
+                                 ? HOLEY_ELEMENTS
+                                 : PACKED_ELEMENTS);
+        } else if (site.is_null() || !site->CanInlineCall()) {
+          // We have some crazy combination of types for the {values} where
+          // there's no clear decision on the elements kind statically. And
+          // we don't have a protection against deoptimization loops for the
+          // checks that are introduced in the call to ReduceNewArray, so
+          // we cannot inline this invocation of the Array constructor here.
+          return NoChange();
+        }
+        initial_map = Map::AsElementsKind(initial_map, elements_kind);
+
+        return ReduceNewArray(node, values, initial_map, pretenure);
       }
     }
   }
 
+  // TODO(bmeurer): Optimize the subclassing case.
+  if (target != new_target) return NoChange();
+
   return ReduceNewArrayToStubCall(node, site);
 }
 
@@ -931,8 +999,14 @@
   if (feedback->IsAllocationSite()) {
     Handle<AllocationSite> site = Handle<AllocationSite>::cast(feedback);
     DCHECK(!site->PointsToLiteral());
+    Handle<Map> const initial_map(
+        native_context()->GetInitialJSArrayMap(site->GetElementsKind()),
+        isolate());
+    PretenureFlag const pretenure = site->GetPretenureMode();
+    dependencies()->AssumeTransitionStable(site);
+    dependencies()->AssumeTenuringDecision(site);
     Node* length = jsgraph()->ZeroConstant();
-    return ReduceNewArray(node, length, 0, site);
+    return ReduceNewArray(node, length, 0, initial_map, pretenure);
   }
   return NoChange();
 }
diff --git a/src/compiler/js-create-lowering.h b/src/compiler/js-create-lowering.h
index e8dc99c..95b6e10 100644
--- a/src/compiler/js-create-lowering.h
+++ b/src/compiler/js-create-lowering.h
@@ -63,9 +63,9 @@
   Reduction ReduceJSCreateBlockContext(Node* node);
   Reduction ReduceJSCreateGeneratorObject(Node* node);
   Reduction ReduceNewArray(Node* node, Node* length, int capacity,
-                           Handle<AllocationSite> site);
+                           Handle<Map> initial_map, PretenureFlag pretenure);
   Reduction ReduceNewArray(Node* node, std::vector<Node*> values,
-                           Handle<AllocationSite> site);
+                           Handle<Map> initial_map, PretenureFlag pretenure);
 
   Node* AllocateArguments(Node* effect, Node* control, Node* frame_state);
   Node* AllocateRestArguments(Node* effect, Node* control, Node* frame_state,