[Turbofan] Inline Array.prototype.map

Bug: v8:1956
Change-Id: I41af0cf5eb2fbb9f1d9d4172f3f546bcc2a715dc
Reviewed-on: https://chromium-review.googlesource.com/548639
Commit-Queue: Michael Stanton <mvstanton@chromium.org>
Reviewed-by: Benedikt Meurer <bmeurer@chromium.org>
Reviewed-by: Daniel Clifford <danno@chromium.org>
Cr-Commit-Position: refs/heads/master@{#46618}
diff --git a/src/builtins/builtins-array-gen.cc b/src/builtins/builtins-array-gen.cc
index 5eb9dab..63f9fa9 100644
--- a/src/builtins/builtins-array-gen.cc
+++ b/src/builtins/builtins-array-gen.cc
@@ -1586,6 +1586,47 @@
       &ArrayBuiltinCodeStubAssembler::NullPostLoopAction);
 }
 
+TF_BUILTIN(ArrayMapLoopEagerDeoptContinuation, ArrayBuiltinCodeStubAssembler) {
+  Node* context = Parameter(Descriptor::kContext);
+  Node* receiver = Parameter(Descriptor::kReceiver);
+  Node* callbackfn = Parameter(Descriptor::kCallbackFn);
+  Node* this_arg = Parameter(Descriptor::kThisArg);
+  Node* array = Parameter(Descriptor::kArray);
+  Node* initial_k = Parameter(Descriptor::kInitialK);
+  Node* len = Parameter(Descriptor::kLength);
+
+  Callable stub(
+      Builtins::CallableFor(isolate(), Builtins::kArrayMapLoopContinuation));
+  Return(CallStub(stub, context, receiver, callbackfn, this_arg, array,
+                  receiver, initial_k, len, UndefinedConstant()));
+}
+
+TF_BUILTIN(ArrayMapLoopLazyDeoptContinuation, ArrayBuiltinCodeStubAssembler) {
+  Node* context = Parameter(Descriptor::kContext);
+  Node* receiver = Parameter(Descriptor::kReceiver);
+  Node* callbackfn = Parameter(Descriptor::kCallbackFn);
+  Node* this_arg = Parameter(Descriptor::kThisArg);
+  Node* array = Parameter(Descriptor::kArray);
+  Node* initial_k = Parameter(Descriptor::kInitialK);
+  Node* len = Parameter(Descriptor::kLength);
+  Node* result = Parameter(Descriptor::kResult);
+
+  // This custom lazy deopt point is right after the callback. map() needs
+  // to pick up at the next step, which is setting the callback result in
+  // the output array. After incrementing k, we can glide into the loop
+  // continuation builtin.
+
+  // iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue).
+  CallRuntime(Runtime::kCreateDataProperty, context, array, initial_k, result);
+  // Then we have to increment k before going on.
+  initial_k = NumberInc(initial_k);
+
+  Callable stub(
+      Builtins::CallableFor(isolate(), Builtins::kArrayMapLoopContinuation));
+  Return(CallStub(stub, context, receiver, callbackfn, this_arg, array,
+                  receiver, initial_k, len, UndefinedConstant()));
+}
+
 TF_BUILTIN(ArrayMap, ArrayBuiltinCodeStubAssembler) {
   Node* argc =
       ChangeInt32ToIntPtr(Parameter(BuiltinDescriptor::kArgumentsCount));
diff --git a/src/builtins/builtins-definitions.h b/src/builtins/builtins-definitions.h
index 7dea7bc..ccb10e3 100644
--- a/src/builtins/builtins-definitions.h
+++ b/src/builtins/builtins-definitions.h
@@ -318,6 +318,10 @@
   /* ES6 #sec-array.prototype.foreach */                                       \
   TFS(ArrayMapLoopContinuation, kReceiver, kCallbackFn, kThisArg, kArray,      \
       kObject, kInitialK, kLength, kTo)                                        \
+  TFJ(ArrayMapLoopEagerDeoptContinuation, 5, kCallbackFn, kThisArg, kArray,    \
+      kInitialK, kLength)                                                      \
+  TFJ(ArrayMapLoopLazyDeoptContinuation, 6, kCallbackFn, kThisArg, kArray,     \
+      kInitialK, kLength, kResult)                                             \
   TFJ(ArrayMap, SharedFunctionInfo::kDontAdaptArgumentsSentinel)               \
   /* ES6 #sec-array.prototype.reduce */                                        \
   TFS(ArrayReduceLoopContinuation, kReceiver, kCallbackFn, kThisArg,           \
diff --git a/src/builtins/builtins.cc b/src/builtins/builtins.cc
index e1cda44..3f98d4f 100644
--- a/src/builtins/builtins.cc
+++ b/src/builtins/builtins.cc
@@ -169,6 +169,16 @@
           isolate->builtins()->ArrayForEachLoopLazyDeoptContinuation();
       return Callable(code, BuiltinDescriptor(isolate));
     }
+    case kArrayMapLoopEagerDeoptContinuation: {
+      Handle<Code> code =
+          isolate->builtins()->ArrayMapLoopEagerDeoptContinuation();
+      return Callable(code, BuiltinDescriptor(isolate));
+    }
+    case kArrayMapLoopLazyDeoptContinuation: {
+      Handle<Code> code =
+          isolate->builtins()->ArrayMapLoopLazyDeoptContinuation();
+      return Callable(code, BuiltinDescriptor(isolate));
+    }
     default:
       UNREACHABLE();
   }
diff --git a/src/compiler/access-builder.cc b/src/compiler/access-builder.cc
index 33bdc76..b2ef194 100644
--- a/src/compiler/access-builder.cc
+++ b/src/compiler/access-builder.cc
@@ -501,6 +501,14 @@
   return access;
 }
 
+// static
+FieldAccess AccessBuilder::ForMapBitField2() {
+  FieldAccess access = {
+      kTaggedBase,        Map::kBitField2Offset,   Handle<Name>(),
+      MaybeHandle<Map>(), TypeCache::Get().kUint8, MachineType::Uint8(),
+      kNoWriteBarrier};
+  return access;
+}
 
 // static
 FieldAccess AccessBuilder::ForMapBitField3() {
diff --git a/src/compiler/access-builder.h b/src/compiler/access-builder.h
index 7637d8c..31e48b9 100644
--- a/src/compiler/access-builder.h
+++ b/src/compiler/access-builder.h
@@ -167,6 +167,9 @@
   // Provides access to Map::bit_field() byte.
   static FieldAccess ForMapBitField();
 
+  // Provides access to Map::bit_field2() byte.
+  static FieldAccess ForMapBitField2();
+
   // Provides access to Map::bit_field3() field.
   static FieldAccess ForMapBitField3();
 
diff --git a/src/compiler/effect-control-linearizer.cc b/src/compiler/effect-control-linearizer.cc
index ef11ba8..f82133a 100644
--- a/src/compiler/effect-control-linearizer.cc
+++ b/src/compiler/effect-control-linearizer.cc
@@ -828,6 +828,8 @@
       break;
     case IrOpcode::kLoadHashMapValue:
       result = LowerLoadHashMapValue(node);
+    case IrOpcode::kTransitionAndStoreElement:
+      LowerTransitionAndStoreElement(node);
       break;
     case IrOpcode::kFloat64RoundUp:
       if (!LowerFloat64RoundUp(node).To(&result)) {
@@ -2814,6 +2816,164 @@
                   storage, index, value);
 }
 
+void EffectControlLinearizer::TransitionElementsTo(Node* node, Node* array,
+                                                   ElementsKind from,
+                                                   ElementsKind to) {
+  DCHECK(IsMoreGeneralElementsKindTransition(from, to));
+  DCHECK(to == HOLEY_ELEMENTS || to == HOLEY_DOUBLE_ELEMENTS);
+
+  Handle<Map> target(to == HOLEY_ELEMENTS ? FastMapParameterOf(node->op())
+                                          : DoubleMapParameterOf(node->op()));
+  Node* target_map = __ HeapConstant(target);
+
+  if (IsSimpleMapChangeTransition(from, to)) {
+    __ StoreField(AccessBuilder::ForMap(), array, target_map);
+  } else {
+    // Instance migration, call out to the runtime for {array}.
+    Operator::Properties properties = Operator::kNoDeopt | Operator::kNoThrow;
+    Runtime::FunctionId id = Runtime::kTransitionElementsKind;
+    CallDescriptor const* desc = Linkage::GetRuntimeCallDescriptor(
+        graph()->zone(), id, 2, properties, CallDescriptor::kNoFlags);
+    __ Call(desc, __ CEntryStubConstant(1), array, target_map,
+            __ ExternalConstant(ExternalReference(id, isolate())),
+            __ Int32Constant(2), __ NoContextConstant());
+  }
+}
+
+Node* EffectControlLinearizer::IsElementsKindGreaterThan(
+    Node* kind, ElementsKind reference_kind) {
+  Node* ref_kind = __ Int32Constant(reference_kind);
+  Node* ret = __ Int32LessThanOrEqual(ref_kind, kind);
+  return ret;
+}
+
+void EffectControlLinearizer::LowerTransitionAndStoreElement(Node* node) {
+  Node* array = node->InputAt(0);
+  Node* index = node->InputAt(1);
+  Node* value = node->InputAt(2);
+
+  // Possibly transition array based on input and store.
+  //
+  //   -- TRANSITION PHASE -----------------
+  //   kind = ElementsKind(array)
+  //   if value is not smi {
+  //     if kind == HOLEY_SMI_ELEMENTS {
+  //       if value is heap number {
+  //         Transition array to HOLEY_DOUBLE_ELEMENTS
+  //       } else {
+  //         Transition array to HOLEY_ELEMENTS
+  //       }
+  //     } else if kind == HOLEY_DOUBLE_ELEMENTS {
+  //       if value is not heap number {
+  //         Transition array to HOLEY_ELEMENTS
+  //       }
+  //     }
+  //   }
+  //
+  //   -- STORE PHASE ----------------------
+  //   if kind == HOLEY_DOUBLE_ELEMENTS {
+  //     if value is smi {
+  //       float_value = convert smi to float
+  //       Store array[index] = float_value
+  //     } else {
+  //       float_value = value
+  //       Store array[index] = float_value
+  //     }
+  //   } else {
+  //     // kind is HOLEY_SMI_ELEMENTS or HOLEY_ELEMENTS
+  //     Store array[index] = value
+  //   }
+  //
+  Node* map = __ LoadField(AccessBuilder::ForMap(), array);
+  Node* kind;
+  {
+    Node* bit_field2 = __ LoadField(AccessBuilder::ForMapBitField2(), map);
+    Node* mask = __ Int32Constant(Map::ElementsKindBits::kMask);
+    Node* andit = __ Word32And(bit_field2, mask);
+    Node* shift = __ Int32Constant(Map::ElementsKindBits::kShift);
+    kind = __ Word32Shr(andit, shift);
+  }
+
+  auto do_store = __ MakeLabel<6>();
+  Node* check1 = ObjectIsSmi(value);
+  __ GotoIf(check1, &do_store);
+  {
+    // {value} is a HeapObject.
+    Node* check2 = IsElementsKindGreaterThan(kind, HOLEY_SMI_ELEMENTS);
+    auto if_array_not_fast_smi = __ MakeLabel<1>();
+    __ GotoIf(check2, &if_array_not_fast_smi);
+    {
+      // Transition {array} from HOLEY_SMI_ELEMENTS to HOLEY_DOUBLE_ELEMENTS or
+      // to HOLEY_ELEMENTS.
+      Node* value_map = __ LoadField(AccessBuilder::ForMap(), value);
+      Node* heap_number_map = __ HeapNumberMapConstant();
+      Node* check3 = __ WordEqual(value_map, heap_number_map);
+      auto if_value_not_heap_number = __ MakeLabel<1>();
+      __ GotoUnless(check3, &if_value_not_heap_number);
+      {
+        // {value} is a HeapNumber.
+        TransitionElementsTo(node, array, HOLEY_SMI_ELEMENTS,
+                             HOLEY_DOUBLE_ELEMENTS);
+        __ Goto(&do_store);
+      }
+      __ Bind(&if_value_not_heap_number);
+      {
+        TransitionElementsTo(node, array, HOLEY_SMI_ELEMENTS, HOLEY_ELEMENTS);
+        __ Goto(&do_store);
+      }
+    }
+    __ Bind(&if_array_not_fast_smi);
+    {
+      Node* check3 = IsElementsKindGreaterThan(kind, HOLEY_ELEMENTS);
+      __ GotoUnless(check3, &do_store);
+      // We have double elements kind.
+      Node* value_map = __ LoadField(AccessBuilder::ForMap(), value);
+      Node* heap_number_map = __ HeapNumberMapConstant();
+      Node* check4 = __ WordEqual(value_map, heap_number_map);
+      __ GotoIf(check4, &do_store);
+      // But the value is not a heap number, so we must transition.
+      TransitionElementsTo(node, array, HOLEY_DOUBLE_ELEMENTS, HOLEY_ELEMENTS);
+      __ Goto(&do_store);
+    }
+  }
+
+  __ Bind(&do_store);
+  Node* elements = __ LoadField(AccessBuilder::ForJSObjectElements(), array);
+  Node* check2 = IsElementsKindGreaterThan(kind, HOLEY_ELEMENTS);
+  auto if_kind_is_double = __ MakeLabel<1>();
+  auto done = __ MakeLabel<3>();
+  __ GotoIf(check2, &if_kind_is_double);
+  {
+    // Our ElementsKind is HOLEY_SMI_ELEMENTS or HOLEY_ELEMENTS.
+    __ StoreElement(AccessBuilder::ForFixedArrayElement(HOLEY_ELEMENTS),
+                    elements, index, value);
+    __ Goto(&done);
+  }
+  __ Bind(&if_kind_is_double);
+  {
+    // Our ElementsKind is HOLEY_DOUBLE_ELEMENTS.
+    Node* check1 = ObjectIsSmi(value);
+    auto do_double_store = __ MakeLabel<1>();
+    __ GotoUnless(check1, &do_double_store);
+    {
+      Node* int_value = ChangeSmiToInt32(value);
+      Node* float_value = __ ChangeInt32ToFloat64(int_value);
+      __ StoreElement(AccessBuilder::ForFixedDoubleArrayElement(), elements,
+                      index, float_value);
+      __ Goto(&done);
+    }
+    __ Bind(&do_double_store);
+    {
+      Node* float_value =
+          __ LoadField(AccessBuilder::ForHeapNumberValue(), value);
+      __ StoreElement(AccessBuilder::ForFixedDoubleArrayElement(), elements,
+                      index, float_value);
+      __ Goto(&done);
+    }
+  }
+  __ Bind(&done);
+}
+
 Maybe<Node*> EffectControlLinearizer::LowerFloat64RoundUp(Node* node) {
   // Nothing to be done if a fast hardware instruction is available.
   if (machine()->Float64RoundUp().IsSupported()) {
diff --git a/src/compiler/effect-control-linearizer.h b/src/compiler/effect-control-linearizer.h
index 7e619a9..3cde8d7 100644
--- a/src/compiler/effect-control-linearizer.h
+++ b/src/compiler/effect-control-linearizer.h
@@ -121,6 +121,7 @@
   void LowerStoreTypedElement(Node* node);
   Node* LowerLookupHashStorageIndex(Node* node);
   Node* LowerLoadHashMapValue(Node* node);
+  void LowerTransitionAndStoreElement(Node* node);
 
   // Lowering of optional operators.
   Maybe<Node*> LowerFloat64RoundUp(Node* node);
@@ -136,6 +137,7 @@
                                                  Node* frame_state);
   Node* BuildFloat64RoundDown(Node* value);
   Node* LowerStringComparison(Callable const& callable, Node* node);
+  Node* IsElementsKindGreaterThan(Node* kind, ElementsKind reference_kind);
 
   Node* ChangeInt32ToSmi(Node* value);
   Node* ChangeUint32ToSmi(Node* value);
@@ -144,6 +146,8 @@
 
   Node* SmiMaxValueConstant();
   Node* SmiShiftBitsConstant();
+  void TransitionElementsTo(Node* node, Node* array, ElementsKind from,
+                            ElementsKind to);
 
   Factory* factory() const;
   Isolate* isolate() const;
diff --git a/src/compiler/js-call-reducer.cc b/src/compiler/js-call-reducer.cc
index 6129728..e05c956 100644
--- a/src/compiler/js-call-reducer.cc
+++ b/src/compiler/js-call-reducer.cc
@@ -721,6 +721,176 @@
   return Replace(jsgraph()->UndefinedConstant());
 }
 
+Reduction JSCallReducer::ReduceArrayMap(Handle<JSFunction> function,
+                                        Node* node) {
+  if (!FLAG_turbo_inline_array_builtins) return NoChange();
+  DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
+  Node* outer_frame_state = NodeProperties::GetFrameStateInput(node);
+  Node* effect = NodeProperties::GetEffectInput(node);
+  Node* control = NodeProperties::GetControlInput(node);
+  Node* context = NodeProperties::GetContextInput(node);
+  CallParameters const& p = CallParametersOf(node->op());
+
+  // Try to determine the {receiver} map.
+  Node* receiver = NodeProperties::GetValueInput(node, 1);
+  Node* fncallback = node->op()->ValueInputCount() > 2
+                         ? NodeProperties::GetValueInput(node, 2)
+                         : jsgraph()->UndefinedConstant();
+  Node* this_arg = node->op()->ValueInputCount() > 3
+                       ? NodeProperties::GetValueInput(node, 3)
+                       : jsgraph()->UndefinedConstant();
+  ZoneHandleSet<Map> receiver_maps;
+  NodeProperties::InferReceiverMapsResult result =
+      NodeProperties::InferReceiverMaps(receiver, effect, &receiver_maps);
+  if (result != NodeProperties::kReliableReceiverMaps) {
+    return NoChange();
+  }
+  if (receiver_maps.size() != 1) return NoChange();
+  Handle<Map> receiver_map(receiver_maps[0]);
+  ElementsKind kind = receiver_map->elements_kind();
+  // TODO(danno): Handle holey Smi and Object fast elements kinds and double
+  // packed.
+  if (!IsFastPackedElementsKind(kind) || IsDoubleElementsKind(kind)) {
+    return NoChange();
+  }
+
+  // TODO(danno): map can throw. Hook up exceptional edges.
+  if (NodeProperties::IsExceptionalCall(node)) return NoChange();
+
+  // We want the input to be a generic Array.
+  const int map_index = Context::ArrayMapIndex(kind);
+  Handle<JSFunction> handle_constructor(
+      JSFunction::cast(
+          Map::cast(native_context()->get(map_index))->GetConstructor()),
+      isolate());
+  Node* array_constructor = jsgraph()->HeapConstant(handle_constructor);
+  if (receiver_map->prototype() !=
+      native_context()->get(Context::INITIAL_ARRAY_PROTOTYPE_INDEX)) {
+    return NoChange();
+  }
+
+  // And ensure that any changes to the Array species constructor cause deopt.
+  if (!isolate()->IsArraySpeciesLookupChainIntact()) return NoChange();
+
+  dependencies()->AssumePropertyCell(factory()->species_protector());
+
+  Node* k = jsgraph()->ZeroConstant();
+  Node* orig_map = jsgraph()->HeapConstant(receiver_map);
+
+  // Make sure the map hasn't changed before we construct the output array.
+  {
+    Node* array_map = effect =
+        graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()),
+                         receiver, effect, control);
+    Node* check_map =
+        graph()->NewNode(simplified()->ReferenceEqual(), array_map, orig_map);
+    effect =
+        graph()->NewNode(simplified()->CheckIf(), check_map, effect, control);
+  }
+
+  Node* original_length = graph()->NewNode(
+      simplified()->LoadField(AccessBuilder::ForJSArrayLength(PACKED_ELEMENTS)),
+      receiver, effect, control);
+
+  // This array should be HOLEY_SMI_ELEMENTS because of the non-zero length.
+  Node* a = control = effect = graph()->NewNode(
+      javascript()->CreateArray(1, Handle<AllocationSite>::null()),
+      array_constructor, array_constructor, original_length, context,
+      outer_frame_state, effect, control);
+
+  Node* loop = control = graph()->NewNode(common()->Loop(2), control, control);
+  Node* eloop = effect =
+      graph()->NewNode(common()->EffectPhi(2), effect, effect, loop);
+  Node* vloop = k = graph()->NewNode(
+      common()->Phi(MachineRepresentation::kTagged, 2), k, k, loop);
+
+  control = loop;
+  effect = eloop;
+
+  Node* continue_test =
+      graph()->NewNode(simplified()->NumberLessThan(), k, original_length);
+  Node* continue_branch = graph()->NewNode(common()->Branch(BranchHint::kTrue),
+                                           continue_test, control);
+
+  Node* if_true = graph()->NewNode(common()->IfTrue(), continue_branch);
+  Node* if_false = graph()->NewNode(common()->IfFalse(), continue_branch);
+  control = if_true;
+
+  std::vector<Node*> checkpoint_params(
+      {receiver, fncallback, this_arg, a, k, original_length});
+  const int stack_parameters = static_cast<int>(checkpoint_params.size());
+
+  Node* frame_state = CreateJavaScriptBuiltinContinuationFrameState(
+      jsgraph(), function, Builtins::kArrayMapLoopEagerDeoptContinuation,
+      node->InputAt(0), context, &checkpoint_params[0], stack_parameters,
+      outer_frame_state, ContinuationFrameStateMode::EAGER);
+
+  effect =
+      graph()->NewNode(common()->Checkpoint(), frame_state, effect, control);
+
+  // Make sure the map hasn't changed during the iteration
+  Node* array_map = effect =
+      graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()),
+                       receiver, effect, control);
+  Node* check_map =
+      graph()->NewNode(simplified()->ReferenceEqual(), array_map, orig_map);
+  effect =
+      graph()->NewNode(simplified()->CheckIf(), check_map, effect, control);
+
+  // Make sure that the access is still in bounds, since the callback could have
+  // changed the array's size.
+  Node* length = graph()->NewNode(
+      simplified()->LoadField(AccessBuilder::ForJSArrayLength(PACKED_ELEMENTS)),
+      receiver, effect, control);
+  k = effect =
+      graph()->NewNode(simplified()->CheckBounds(), k, length, effect, control);
+
+  // Reload the elements pointer before calling the callback, since the previous
+  // callback might have resized the array causing the elements buffer to be
+  // re-allocated.
+  Node* elements = graph()->NewNode(
+      simplified()->LoadField(AccessBuilder::ForJSObjectElements()), receiver,
+      effect, control);
+
+  Node* element = graph()->NewNode(
+      simplified()->LoadElement(AccessBuilder::ForFixedArrayElement()),
+      elements, k, effect, control);
+
+  Node* next_k =
+      graph()->NewNode(simplified()->NumberAdd(), k, jsgraph()->OneConstant());
+
+  // This frame state is dealt with by hand in
+  // ArrayMapLoopLazyDeoptContinuation.
+  frame_state = CreateJavaScriptBuiltinContinuationFrameState(
+      jsgraph(), function, Builtins::kArrayMapLoopLazyDeoptContinuation,
+      node->InputAt(0), context, &checkpoint_params[0], stack_parameters,
+      outer_frame_state, ContinuationFrameStateMode::LAZY);
+
+  Node* callback_value = control = effect = graph()->NewNode(
+      javascript()->Call(5, p.frequency()), fncallback, this_arg, element, k,
+      receiver, context, frame_state, effect, control);
+
+  Handle<Map> double_map(Map::cast(
+      native_context()->get(Context::ArrayMapIndex(HOLEY_DOUBLE_ELEMENTS))));
+  Handle<Map> fast_map(
+      Map::cast(native_context()->get(Context::ArrayMapIndex(HOLEY_ELEMENTS))));
+  effect = graph()->NewNode(
+      simplified()->TransitionAndStoreElement(double_map, fast_map), a, k,
+      callback_value, effect, control);
+
+  k = next_k;
+
+  loop->ReplaceInput(1, control);
+  vloop->ReplaceInput(1, k);
+  eloop->ReplaceInput(1, effect);
+
+  control = if_false;
+  effect = eloop;
+
+  ReplaceWithValue(node, a, effect, control);
+  return Replace(a);
+}
+
 Reduction JSCallReducer::ReduceCallApiFunction(
     Node* node, Handle<FunctionTemplateInfo> function_template_info) {
   DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
@@ -1077,6 +1247,8 @@
           return ReduceSetPrototypeGetSize(node);
         case Builtins::kArrayForEach:
           return ReduceArrayForEach(function, node);
+        case Builtins::kArrayMap:
+          return ReduceArrayMap(function, node);
         case Builtins::kReturnReceiver:
           return ReduceReturnReceiver(node);
         default:
diff --git a/src/compiler/js-call-reducer.h b/src/compiler/js-call-reducer.h
index fd8cfdf..d8a0490 100644
--- a/src/compiler/js-call-reducer.h
+++ b/src/compiler/js-call-reducer.h
@@ -69,6 +69,7 @@
   Reduction ReduceReflectGetPrototypeOf(Node* node);
   Reduction ReduceSetPrototypeGetSize(Node* node);
   Reduction ReduceArrayForEach(Handle<JSFunction> function, Node* node);
+  Reduction ReduceArrayMap(Handle<JSFunction> function, Node* node);
   Reduction ReduceCallOrConstructWithArrayLikeOrSpread(
       Node* node, int arity, CallFrequency const& frequency);
   Reduction ReduceJSConstruct(Node* node);
diff --git a/src/compiler/js-create-lowering.h b/src/compiler/js-create-lowering.h
index b14c111..e122d4c 100644
--- a/src/compiler/js-create-lowering.h
+++ b/src/compiler/js-create-lowering.h
@@ -75,6 +75,8 @@
                          ElementsKind elements_kind, int capacity,
                          PretenureFlag pretenure);
   Node* AllocateElements(Node* effect, Node* control,
+                         ElementsKind elements_kind, Node* capacity_and_length);
+  Node* AllocateElements(Node* effect, Node* control,
                          ElementsKind elements_kind,
                          std::vector<Node*> const& values,
                          PretenureFlag pretenure);
diff --git a/src/compiler/load-elimination.cc b/src/compiler/load-elimination.cc
index 282122d..6dd27d3 100644
--- a/src/compiler/load-elimination.cc
+++ b/src/compiler/load-elimination.cc
@@ -114,6 +114,8 @@
       return ReduceLoadElement(node);
     case IrOpcode::kStoreElement:
       return ReduceStoreElement(node);
+    case IrOpcode::kTransitionAndStoreElement:
+      return ReduceTransitionAndStoreElement(node);
     case IrOpcode::kStoreTypedElement:
       return ReduceStoreTypedElement(node);
     case IrOpcode::kLookupHashStorageIndex:
@@ -784,6 +786,30 @@
   return UpdateState(node, state);
 }
 
+Reduction LoadElimination::ReduceTransitionAndStoreElement(Node* node) {
+  Node* const object = NodeProperties::GetValueInput(node, 0);
+  Handle<Map> double_map(DoubleMapParameterOf(node->op()));
+  Handle<Map> fast_map(FastMapParameterOf(node->op()));
+  Node* const effect = NodeProperties::GetEffectInput(node);
+  AbstractState const* state = node_states_.Get(effect);
+  if (state == nullptr) return NoChange();
+
+  // We need to add the double and fast maps to the set of possible maps for
+  // this object, because we don't know which of those we'll transition to.
+  // Additionally, we should kill all alias information.
+  ZoneHandleSet<Map> object_maps;
+  if (state->LookupMaps(object, &object_maps)) {
+    object_maps.insert(double_map, zone());
+    object_maps.insert(fast_map, zone());
+    state = state->KillMaps(object, zone());
+    state = state->AddMaps(object, object_maps, zone());
+  }
+  // Kill the elements as well.
+  state =
+      state->KillField(object, FieldIndexOf(JSObject::kElementsOffset), zone());
+  return UpdateState(node, state);
+}
+
 Reduction LoadElimination::ReduceLoadField(Node* node) {
   FieldAccess const& access = FieldAccessOf(node->op());
   Node* const object = NodeProperties::GetValueInput(node, 0);
@@ -1110,6 +1136,15 @@
             }
             break;
           }
+          case IrOpcode::kTransitionAndStoreElement: {
+            Node* const object = NodeProperties::GetValueInput(current, 0);
+            // Invalidate what we know about the {object}s map.
+            state = state->KillMaps(object, zone());
+            // Kill the elements as well.
+            state = state->KillField(
+                object, FieldIndexOf(JSObject::kElementsOffset), zone());
+            break;
+          }
           case IrOpcode::kStoreField: {
             FieldAccess const& access = FieldAccessOf(current->op());
             Node* const object = NodeProperties::GetValueInput(current, 0);
diff --git a/src/compiler/load-elimination.h b/src/compiler/load-elimination.h
index fb32c6f..dc65a12 100644
--- a/src/compiler/load-elimination.h
+++ b/src/compiler/load-elimination.h
@@ -320,6 +320,7 @@
   Reduction ReduceStoreField(Node* node);
   Reduction ReduceLoadElement(Node* node);
   Reduction ReduceStoreElement(Node* node);
+  Reduction ReduceTransitionAndStoreElement(Node* node);
   Reduction ReduceStoreTypedElement(Node* node);
   Reduction ReduceLookupHashStorageIndex(Node* node);
   Reduction ReduceEffectPhi(Node* node);
diff --git a/src/compiler/opcodes.h b/src/compiler/opcodes.h
index cda1a7a..c829a39 100644
--- a/src/compiler/opcodes.h
+++ b/src/compiler/opcodes.h
@@ -346,6 +346,7 @@
   V(StoreBuffer)                    \
   V(StoreElement)                   \
   V(StoreTypedElement)              \
+  V(TransitionAndStoreElement)      \
   V(ObjectIsDetectableCallable)     \
   V(ObjectIsNaN)                    \
   V(ObjectIsNonCallable)            \
diff --git a/src/compiler/simplified-lowering.cc b/src/compiler/simplified-lowering.cc
index f525c28..19578fc 100644
--- a/src/compiler/simplified-lowering.cc
+++ b/src/compiler/simplified-lowering.cc
@@ -2591,6 +2591,14 @@
         }
         return;
       }
+      case IrOpcode::kTransitionAndStoreElement: {
+        ProcessInput(node, 0, UseInfo::AnyTagged());         // array
+        ProcessInput(node, 1, UseInfo::TruncatingWord32());  // index
+        ProcessInput(node, 2, UseInfo::AnyTagged());         // value
+        ProcessRemainingInputs(node, 3);
+        SetOutput(node, MachineRepresentation::kNone);
+        return;
+      }
       case IrOpcode::kLoadTypedElement: {
         MachineRepresentation const rep =
             MachineRepresentationFromArrayType(ExternalArrayTypeOf(node->op()));
diff --git a/src/compiler/simplified-operator.cc b/src/compiler/simplified-operator.cc
index 98d1642..29e4669 100644
--- a/src/compiler/simplified-operator.cc
+++ b/src/compiler/simplified-operator.cc
@@ -348,6 +348,55 @@
   return OpParameter<ElementsTransition>(op);
 }
 
+namespace {
+
+// Parameters for the TransitionAndStoreElement opcode.
+class TransitionAndStoreElementParameters final {
+ public:
+  TransitionAndStoreElementParameters(Handle<Map> double_map,
+                                      Handle<Map> fast_map);
+
+  Handle<Map> double_map() const { return double_map_; }
+  Handle<Map> fast_map() const { return fast_map_; }
+
+ private:
+  Handle<Map> const double_map_;
+  Handle<Map> const fast_map_;
+};
+
+TransitionAndStoreElementParameters::TransitionAndStoreElementParameters(
+    Handle<Map> double_map, Handle<Map> fast_map)
+    : double_map_(double_map), fast_map_(fast_map) {}
+
+bool operator==(TransitionAndStoreElementParameters const& lhs,
+                TransitionAndStoreElementParameters const& rhs) {
+  return lhs.fast_map().address() == rhs.fast_map().address() &&
+         lhs.double_map().address() == rhs.double_map().address();
+}
+
+size_t hash_value(TransitionAndStoreElementParameters parameters) {
+  return base::hash_combine(parameters.fast_map().address(),
+                            parameters.double_map().address());
+}
+
+std::ostream& operator<<(std::ostream& os,
+                         TransitionAndStoreElementParameters parameters) {
+  return os << "fast-map" << Brief(*parameters.fast_map()) << " double-map"
+            << Brief(*parameters.double_map());
+}
+
+}  // namespace
+
+Handle<Map> DoubleMapParameterOf(const Operator* op) {
+  DCHECK(op->opcode() == IrOpcode::kTransitionAndStoreElement);
+  return OpParameter<TransitionAndStoreElementParameters>(op).double_map();
+}
+
+Handle<Map> FastMapParameterOf(const Operator* op) {
+  DCHECK(op->opcode() == IrOpcode::kTransitionAndStoreElement);
+  return OpParameter<TransitionAndStoreElementParameters>(op).fast_map();
+}
+
 std::ostream& operator<<(std::ostream& os, NumberOperationHint hint) {
   switch (hint) {
     case NumberOperationHint::kSignedSmall:
@@ -1053,6 +1102,15 @@
 ACCESS_OP_LIST(ACCESS)
 #undef ACCESS
 
+const Operator* SimplifiedOperatorBuilder::TransitionAndStoreElement(
+    Handle<Map> double_map, Handle<Map> fast_map) {
+  TransitionAndStoreElementParameters parameters(double_map, fast_map);
+  return new (zone()) Operator1<TransitionAndStoreElementParameters>(
+      IrOpcode::kTransitionAndStoreElement,
+      Operator::kNoDeopt | Operator::kNoThrow, "TransitionAndStoreElement", 3,
+      1, 1, 0, 1, 0, parameters);
+}
+
 }  // namespace compiler
 }  // namespace internal
 }  // namespace v8
diff --git a/src/compiler/simplified-operator.h b/src/compiler/simplified-operator.h
index e071420..f2739ace 100644
--- a/src/compiler/simplified-operator.h
+++ b/src/compiler/simplified-operator.h
@@ -228,6 +228,10 @@
 ElementsTransition const& ElementsTransitionOf(const Operator* op)
     WARN_UNUSED_RESULT;
 
+// Parameters for TransitionAndStoreElement.
+Handle<Map> DoubleMapParameterOf(const Operator* op);
+Handle<Map> FastMapParameterOf(const Operator* op);
+
 // A hint for speculative number operations.
 enum class NumberOperationHint : uint8_t {
   kSignedSmall,      // Inputs were always Smi so far, output was in Smi range.
@@ -493,6 +497,10 @@
   // store-element [base + index], value
   const Operator* StoreElement(ElementAccess const&);
 
+  // store-element [base + index], value, only with fast arrays.
+  const Operator* TransitionAndStoreElement(Handle<Map> double_map,
+                                            Handle<Map> fast_map);
+
   // load-typed-element buffer, [base + external + index]
   const Operator* LoadTypedElement(ExternalArrayType const&);
 
diff --git a/src/compiler/typer.cc b/src/compiler/typer.cc
index 18fbefd..973b7fe 100644
--- a/src/compiler/typer.cc
+++ b/src/compiler/typer.cc
@@ -1963,6 +1963,10 @@
   UNREACHABLE();
 }
 
+Type* Typer::Visitor::TypeTransitionAndStoreElement(Node* node) {
+  UNREACHABLE();
+}
+
 Type* Typer::Visitor::TypeStoreTypedElement(Node* node) {
   UNREACHABLE();
 }
diff --git a/src/compiler/verifier.cc b/src/compiler/verifier.cc
index 9ef842a..dbb0546 100644
--- a/src/compiler/verifier.cc
+++ b/src/compiler/verifier.cc
@@ -1286,6 +1286,9 @@
       // CheckValueInputIs(node, 1, ElementAccessOf(node->op()).type));
       CheckNotTyped(node);
       break;
+    case IrOpcode::kTransitionAndStoreElement:
+      CheckNotTyped(node);
+      break;
     case IrOpcode::kStoreTypedElement:
       CheckNotTyped(node);
       break;
diff --git a/test/mjsunit/optimized-map.js b/test/mjsunit/optimized-map.js
new file mode 100644
index 0000000..604b8b3d
--- /dev/null
+++ b/test/mjsunit/optimized-map.js
@@ -0,0 +1,379 @@
+// Copyright 2017 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Flags: --allow-natives-syntax --expose-gc --turbo-inline-array-builtins
+// Flags: --opt --no-always-opt
+
+var a = [0, 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,0,0];
+var b = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25];
+var c = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25];
+
+// Unknown field access leads to soft-deopt unrelated to map, should still
+// lead to correct result.
+(function() {
+  var result = 0;
+  var eagerDeoptInCalled = function(deopt) {
+    var callback = function(v,i,o) {
+      result += v;
+      if (i == 13 && deopt) {
+        a.abc = 25;
+      }
+      return v;
+    }
+    a.map(callback);
+  }
+  eagerDeoptInCalled();
+  eagerDeoptInCalled();
+  %OptimizeFunctionOnNextCall(eagerDeoptInCalled);
+  eagerDeoptInCalled();
+  eagerDeoptInCalled(true);
+  eagerDeoptInCalled();
+  assertEquals(1500, result);
+})();
+
+// Length change detected during loop, must cause properly handled eager deopt.
+(function() {
+  var result = 0;
+  var eagerDeoptInCalled = function(deopt) {
+    var callback = function(v,i,o) {
+      result += v;
+      a.length = (i == 13 && deopt) ? 25 : 27;
+      return v;
+    }
+    a.map(callback);
+  }
+  eagerDeoptInCalled();
+  eagerDeoptInCalled();
+  %OptimizeFunctionOnNextCall(eagerDeoptInCalled);
+  eagerDeoptInCalled();
+  eagerDeoptInCalled(true);
+  eagerDeoptInCalled();
+  assertEquals(1500, result);
+})();
+
+// Escape analyzed array
+(function() {
+  var result = 0;
+  var eagerDeoptInCalled = function(deopt) {
+    var a_noescape = [0,1,2,3,4,5];
+    var callback = function(v,i,o) {
+      result += v;
+      if (i == 13 && deopt) {
+        a_noescape.length = 25;
+      }
+      return v;
+    }
+    a_noescape.map(callback);
+  }
+  eagerDeoptInCalled();
+  eagerDeoptInCalled();
+  %OptimizeFunctionOnNextCall(eagerDeoptInCalled);
+  eagerDeoptInCalled();
+  eagerDeoptInCalled(true);
+  eagerDeoptInCalled();
+  assertEquals(75, result);
+})();
+
+// Escape analyzed array where callback function isn't inlined, forcing a lazy
+// deopt with GC that relies on the stashed-away return result fro the lazy
+// deopt being properly stored in a place on the stack that gets GC'ed.
+(function() {
+  var result = 0;
+  var lazyDeopt = function(deopt) {
+    var b = [1,2,3];
+    var callback = function(v,i,o) {
+      result += i;
+      if (i == 1 && deopt) {
+        %DeoptimizeFunction(lazyDeopt);
+      }
+      gc(); gc();
+      return v;
+    };
+    %NeverOptimizeFunction(callback);
+    b.map(callback);
+  }
+  lazyDeopt();
+  lazyDeopt();
+  %OptimizeFunctionOnNextCall(lazyDeopt);
+  lazyDeopt();
+  lazyDeopt(true);
+  lazyDeopt();
+})();
+
+// Lazy deopt from runtime call from inlined callback function.
+(function() {
+  var result = 0;
+  var lazyDeopt = function(deopt) {
+    var callback = function(v,i,o) {
+      result += i;
+      if (i == 13 && deopt) {
+          %DeoptimizeNow();
+      }
+      return v;
+    }
+    b.map(callback);
+  }
+  lazyDeopt();
+  lazyDeopt();
+  %OptimizeFunctionOnNextCall(lazyDeopt);
+  lazyDeopt();
+  lazyDeopt(true);
+  lazyDeopt();
+  assertEquals(1500, result);
+})();
+
+// Lazy deopt from runtime call from non-inline callback function.
+(function() {
+  var result = 0;
+  var lazyDeopt = function(deopt) {
+    var callback = function(v,i,o) {
+      result += i;
+      if (i == 13 && deopt) {
+          %DeoptimizeNow();
+      }
+      return v;
+    };
+    %NeverOptimizeFunction(callback);
+    b.map(callback);
+  }
+  lazyDeopt();
+  lazyDeopt();
+  %OptimizeFunctionOnNextCall(lazyDeopt);
+  lazyDeopt();
+  lazyDeopt(true);
+  lazyDeopt();
+  assertEquals(1500, result);
+})();
+
+(function() {
+  var result = 0;
+  var lazyDeopt = function(deopt) {
+    var callback = function(v,i,o) {
+      result += i;
+      if (i == 13 && deopt) {
+          %DeoptimizeNow();
+          gc();
+          gc();
+          gc();
+      }
+      return v;
+    }
+    c.map(callback);
+  }
+  lazyDeopt();
+  lazyDeopt();
+  %OptimizeFunctionOnNextCall(lazyDeopt);
+  lazyDeopt();
+  lazyDeopt(true);
+  lazyDeopt();
+  assertEquals(1500, result);
+})();
+
+// Call to a.map is done inside a try-catch block and the callback function
+// being called actually throws.
+(function() {
+  var caught = false;
+  var result = 0;
+  var lazyDeopt = function(deopt) {
+    var callback = function(v,i,o) {
+      result += i;
+      if (i == 1 && deopt) {
+        throw("a");
+      }
+      return v;
+    }
+    try {
+      c.map(callback);
+    } catch (e) {
+      caught = true;
+    }
+  }
+  lazyDeopt();
+  lazyDeopt();
+  %OptimizeFunctionOnNextCall(lazyDeopt);
+  lazyDeopt();
+  assertDoesNotThrow(lazyDeopt.bind(this, true));
+  assertTrue(caught);
+  lazyDeopt();
+})();
+
+// Call to a.map is done inside a try-catch block and the callback function
+// being called actually throws, but the callback is not inlined.
+(function() {
+  var caught = false;
+  var result = 0;
+  var lazyDeopt = function(deopt) {
+    var callback = function(v,i,o) {
+      result += i;
+      if (i == 1 && deopt) {
+        throw("a");
+      }
+      return v;
+    };
+    %NeverOptimizeFunction(callback);
+    try {
+      c.map(callback);
+    } catch (e) {
+      caught = true;
+    }
+  }
+  lazyDeopt();
+  lazyDeopt();
+  %OptimizeFunctionOnNextCall(lazyDeopt);
+  lazyDeopt();
+  assertDoesNotThrow(lazyDeopt.bind(this, true));
+  assertTrue(caught);
+  lazyDeopt();
+})();
+
+(function() {
+  var re = /Array\.map/;
+  var lazyDeopt = function(deopt) {
+    var b = [1,2,3];
+    var result = 0;
+    var callback = function(v,i,o) {
+      result += v;
+      if (i == 1) {
+        var e = new Error();
+        assertTrue(re.exec(e.stack) !== null);
+      }
+      return v;
+    };
+    var o = [1,2,3];
+    b.map(callback);
+  }
+  lazyDeopt();
+  lazyDeopt();
+  %OptimizeFunctionOnNextCall(lazyDeopt);
+  lazyDeopt();
+})();
+
+(function() {
+  var re = /Array\.map/;
+  var lazyDeopt = function(deopt) {
+    var b = [1,2,3];
+    var result = 0;
+    var callback = function(v,i,o) {
+      result += v;
+      if (i == 1) {
+        var e = new Error();
+        assertTrue(re.exec(e.stack) !== null);
+      }
+      return v;
+    };
+    %NeverOptimizeFunction(callback);
+    var o = [1,2,3];
+    b.map(callback);
+  }
+  lazyDeopt();
+  lazyDeopt();
+  %OptimizeFunctionOnNextCall(lazyDeopt);
+  lazyDeopt();
+})();
+
+(function() {
+  var re = /Array\.map/;
+  var lazyDeopt = function(deopt) {
+    var b = [1,2,3];
+    var result = 0;
+    var callback = function(v,i,o) {
+      result += v;
+      if (i == 1) {
+        %DeoptimizeNow();
+      } else if (i == 2) {
+        var e = new Error();
+        assertTrue(re.exec(e.stack) !== null);
+      }
+      return v;
+    };
+    var o = [1,2,3];
+    b.map(callback);
+  }
+  lazyDeopt();
+  lazyDeopt();
+  %OptimizeFunctionOnNextCall(lazyDeopt);
+  lazyDeopt();
+})();
+
+(function() {
+  var re = /Array\.map/;
+  var a = [1,2,3];
+  var result = 0;
+  var lazyDeopt = function() {
+    var callback = function(v,i,o) {
+      result += i;
+      if (i == 1) {
+        %DeoptimizeFunction(lazyDeopt);
+        throw new Error();
+      }
+      return v;
+    };
+    a.map(callback);
+  }
+  assertThrows(() => lazyDeopt());
+  assertThrows(() => lazyDeopt());
+  try {
+    lazyDeopt();
+  } catch (e) {
+    assertTrue(re.exec(e.stack) !== null);
+  }
+  %OptimizeFunctionOnNextCall(lazyDeopt);
+  try {
+    lazyDeopt();
+  } catch (e) {
+    assertTrue(re.exec(e.stack) !== null);
+  }
+})();
+
+// Verify that we remain in optimized code despite transitions in the output
+// array.
+(function() {
+  var result = 0;
+  var to_double = function() {
+    var callback = function(v,i,o) {
+      result += v;
+      if (i < 5) {
+        // First transition the output array to PACKED_DOUBLE_ELEMENTS.
+        return v + 0.5;
+      } else if (i < 10) {
+        // Then return smi values and make sure they can live in the double
+        // array.
+        return v;
+      } else {
+        // Later, to PACKED_ELEMENTS.
+        return v + 'hello';
+      }
+    }
+    return c.map(callback);
+  }
+  to_double();
+  to_double();
+  %OptimizeFunctionOnNextCall(to_double);
+  var output = to_double();
+  assertEquals(975, result);
+  assertEquals("11hello", output[10]);
+  assertOptimized(to_double);
+})();
+
+// Messing with the Array species constructor causes deoptimization.
+(function() {
+  var result = 0;
+  var a = [1,2,3];
+  var species_breakage = function() {
+    var callback = function(v,i,o) {
+      result += v;
+      return v;
+    }
+    a.map(callback);
+  }
+  species_breakage();
+  species_breakage();
+  %OptimizeFunctionOnNextCall(species_breakage);
+  species_breakage();
+  a.constructor = {};
+  a.constructor[Symbol.species] = function() {};
+  species_breakage();
+  assertUnoptimized(species_breakage);
+  assertEquals(24, result);
+})();