Use a stub in crankshaft for grow store arrays.
authorMichael Stanton <mvstanton@chromium.org>
Tue, 18 Nov 2014 12:32:05 +0000 (13:32 +0100)
committerMichael Stanton <mvstanton@chromium.org>
Tue, 18 Nov 2014 12:32:24 +0000 (12:32 +0000)
We were deopting without learning anything.

BUG=v8:3417
LOG=N
R=danno@chromium.org

Review URL: https://codereview.chromium.org/368263003

Cr-Commit-Position: refs/heads/master@{#25392}

19 files changed:
src/arm/interface-descriptors-arm.cc
src/arm/lithium-codegen-arm.cc
src/arm64/interface-descriptors-arm64.cc
src/code-stubs-hydrogen.cc
src/code-stubs.cc
src/code-stubs.h
src/hydrogen-instructions.cc
src/hydrogen.cc
src/hydrogen.h
src/ia32/interface-descriptors-ia32.cc
src/interface-descriptors.cc
src/interface-descriptors.h
src/objects-inl.h
src/objects.cc
src/objects.h
src/runtime/runtime-array.cc
src/runtime/runtime.h
src/x64/interface-descriptors-x64.cc
test/mjsunit/ensure-growing-store-learns.js [new file with mode: 0644]

index 6e77ee4..78febc3 100644 (file)
@@ -54,6 +54,12 @@ const Register MathPowIntegerDescriptor::exponent() {
 }
 
 
+// IC register specifications
+const Register GrowArrayElementsDescriptor::ObjectRegister() { return r0; }
+const Register GrowArrayElementsDescriptor::KeyRegister() { return r1; }
+const Register GrowArrayElementsDescriptor::CapacityRegister() { return r2; }
+
+
 void FastNewClosureDescriptor::Initialize(CallInterfaceDescriptorData* data) {
   Register registers[] = {cp, r2};
   data->Initialize(arraysize(registers), registers, NULL);
index e323e0d..bb81960 100644 (file)
@@ -4015,8 +4015,12 @@ void LCodeGen::DoCallWithDescriptor(LCallWithDescriptor* instr) {
     generator.BeforeCall(__ CallSize(code, RelocInfo::CODE_TARGET));
     PlatformInterfaceDescriptor* call_descriptor =
         instr->descriptor().platform_specific_descriptor();
-    __ Call(code, RelocInfo::CODE_TARGET, TypeFeedbackId::None(), al,
-            call_descriptor->storage_mode());
+    if (call_descriptor != NULL) {
+      __ Call(code, RelocInfo::CODE_TARGET, TypeFeedbackId::None(), al,
+              call_descriptor->storage_mode());
+    } else {
+      __ Call(code, RelocInfo::CODE_TARGET, TypeFeedbackId::None(), al);
+    }
   } else {
     DCHECK(instr->target()->IsRegister());
     Register target = ToRegister(instr->target());
index 57eebcc..f7e6c5f 100644 (file)
@@ -60,6 +60,12 @@ const Register MathPowTaggedDescriptor::exponent() { return x11; }
 const Register MathPowIntegerDescriptor::exponent() { return x12; }
 
 
+// IC register specifications
+const Register GrowArrayElementsDescriptor::ObjectRegister() { return x0; }
+const Register GrowArrayElementsDescriptor::KeyRegister() { return x1; }
+const Register GrowArrayElementsDescriptor::CapacityRegister() { return x2; }
+
+
 void FastNewClosureDescriptor::Initialize(CallInterfaceDescriptorData* data) {
   // cp: context
   // x2: function info
index f66670b..a9cd5b4 100644 (file)
@@ -548,6 +548,31 @@ Handle<Code> StoreScriptContextFieldStub::GenerateCode() {
 
 
 template <>
+HValue* CodeStubGraphBuilder<GrowArrayElementsStub>::BuildCodeStub() {
+  HValue* object = GetParameter(GrowArrayElementsDescriptor::kObjectIndex);
+  HValue* key = GetParameter(GrowArrayElementsDescriptor::kKeyIndex);
+  HValue* current_capacity =
+      GetParameter(GrowArrayElementsDescriptor::kCapacityIndex);
+  ElementsKind kind = casted_stub()->elements_kind();
+
+  HValue* elements = AddLoadElements(object);
+  HValue* length =
+      casted_stub()->is_js_array()
+          ? Add<HLoadNamedField>(object, static_cast<HValue*>(NULL),
+                                 HObjectAccess::ForArrayLength(kind))
+          : current_capacity;
+
+  return BuildCheckAndGrowElementsCapacity(object, elements, kind, length,
+                                           current_capacity, key);
+}
+
+
+Handle<Code> GrowArrayElementsStub::GenerateCode() {
+  return DoGenerateCode(this);
+}
+
+
+template <>
 HValue* CodeStubGraphBuilder<LoadFastElementStub>::BuildCodeStub() {
   HInstruction* load = BuildUncheckedMonomorphicElementAccess(
       GetParameter(LoadDescriptor::kReceiverIndex),
index 7442f3e..6b69832 100644 (file)
@@ -733,6 +733,13 @@ void StringAddStub::InitializeDescriptor(CodeStubDescriptor* descriptor) {
 }
 
 
+void GrowArrayElementsStub::InitializeDescriptor(
+    CodeStubDescriptor* descriptor) {
+  descriptor->Initialize(
+      Runtime::FunctionForId(Runtime::kGrowArrayElements)->entry);
+}
+
+
 void CreateAllocationSiteStub::GenerateAheadOfTime(Isolate* isolate) {
   CreateAllocationSiteStub stub(isolate);
   stub.GetCode();
index 7a6ad6a..bf87a0d 100644 (file)
@@ -65,6 +65,7 @@ namespace internal {
   V(FastCloneShallowObject)                 \
   V(FastNewClosure)                         \
   V(FastNewContext)                         \
+  V(GrowArrayElements)                      \
   V(InternalArrayNArgumentsConstructor)     \
   V(InternalArrayNoArgumentConstructor)     \
   V(InternalArraySingleArgumentConstructor) \
@@ -662,6 +663,29 @@ class CreateAllocationSiteStub : public HydrogenCodeStub {
 };
 
 
+class GrowArrayElementsStub : public HydrogenCodeStub {
+ public:
+  GrowArrayElementsStub(Isolate* isolate, bool is_js_array, ElementsKind kind)
+      : HydrogenCodeStub(isolate) {
+    set_sub_minor_key(ElementsKindBits::encode(kind) |
+                      IsJsArrayBits::encode(is_js_array));
+  }
+
+  ElementsKind elements_kind() const {
+    return ElementsKindBits::decode(sub_minor_key());
+  }
+
+  bool is_js_array() const { return IsJsArrayBits::decode(sub_minor_key()); }
+
+ private:
+  class ElementsKindBits : public BitField<ElementsKind, 0, 8> {};
+  class IsJsArrayBits : public BitField<bool, ElementsKindBits::kNext, 1> {};
+
+  DEFINE_CALL_INTERFACE_DESCRIPTOR(GrowArrayElements);
+  DEFINE_HYDROGEN_CODE_STUB(GrowArrayElements, HydrogenCodeStub);
+};
+
+
 class InstanceofStub: public PlatformCodeStub {
  public:
   enum Flags {
index bc7b2ce..0f28c31 100644 (file)
@@ -783,6 +783,9 @@ void HInstruction::Verify() {
 
 bool HInstruction::CanDeoptimize() {
   // TODO(titzer): make this a virtual method?
+  // TODO(all): Some of these may be incorrect, since any method that can
+  // collect can provoke lazy deoptimization. Methods like CallNew can
+  // certainly do that.
   switch (opcode()) {
     case HValue::kAbnormalExit:
     case HValue::kAccessArgumentsAt:
@@ -796,7 +799,6 @@ bool HInstruction::CanDeoptimize() {
     case HValue::kCallNew:
     case HValue::kCallNewArray:
     case HValue::kCallStub:
-    case HValue::kCallWithDescriptor:
     case HValue::kCapturedObject:
     case HValue::kClassOfTestAndBranch:
     case HValue::kCompareGeneric:
@@ -863,6 +865,7 @@ bool HInstruction::CanDeoptimize() {
     case HValue::kBranch:
     case HValue::kCallJSFunction:
     case HValue::kCallRuntime:
+    case HValue::kCallWithDescriptor:
     case HValue::kChange:
     case HValue::kCheckHeapObject:
     case HValue::kCheckInstanceType:
index 21ef8c0..d188cb8 100644 (file)
@@ -1297,6 +1297,20 @@ HValue* HGraphBuilder::BuildWrapReceiver(HValue* object, HValue* function) {
 }
 
 
+HValue* HGraphBuilder::BuildCheckAndGrowElementsCapacity(
+    HValue* object, HValue* elements, ElementsKind kind, HValue* length,
+    HValue* capacity, HValue* key) {
+  HValue* max_gap = Add<HConstant>(static_cast<int32_t>(JSObject::kMaxGap));
+  HValue* max_capacity = AddUncasted<HAdd>(capacity, max_gap);
+  Add<HBoundsCheck>(key, max_capacity);
+
+  HValue* new_capacity = BuildNewElementsCapacity(key);
+  HValue* new_elements = BuildGrowElementsCapacity(object, elements, kind, kind,
+                                                   length, new_capacity);
+  return new_elements;
+}
+
+
 HValue* HGraphBuilder::BuildCheckForCapacityGrow(
     HValue* object,
     HValue* elements,
@@ -1320,17 +1334,26 @@ HValue* HGraphBuilder::BuildCheckForCapacityGrow(
                                                 Token::GTE);
   capacity_checker.Then();
 
-  HValue* max_gap = Add<HConstant>(static_cast<int32_t>(JSObject::kMaxGap));
-  HValue* max_capacity = AddUncasted<HAdd>(current_capacity, max_gap);
-
-  Add<HBoundsCheck>(key, max_capacity);
-
-  HValue* new_capacity = BuildNewElementsCapacity(key);
-  HValue* new_elements = BuildGrowElementsCapacity(object, elements,
-                                                   kind, kind, length,
-                                                   new_capacity);
+  // BuildCheckAndGrowElementsCapacity could de-opt without profitable feedback,
+  // therefore we defer calling it to a stub in optimized functions. It is
+  // okay to call directly in a code stub though, because a bailout to the
+  // runtime is tolerable in the corner cases.
+  if (top_info()->IsStub()) {
+    environment()->Push(BuildCheckAndGrowElementsCapacity(
+        object, elements, kind, length, current_capacity, key));
+  } else {
+    GrowArrayElementsStub stub(isolate(), is_js_array, kind);
+    GrowArrayElementsDescriptor descriptor(isolate());
+    HConstant* target = Add<HConstant>(stub.GetCode());
+    HValue* op_vals[] = {context(), object, key, current_capacity};
+    HValue* new_elements = Add<HCallWithDescriptor>(
+        target, 0, descriptor, Vector<HValue*>(op_vals, 4));
+    // If the object changed to a dictionary, GrowArrayElements will return a
+    // smi to signal that deopt is required.
+    Add<HCheckHeapObject>(new_elements);
+    environment()->Push(new_elements);
+  }
 
-  environment()->Push(new_elements);
   capacity_checker.Else();
 
   environment()->Push(elements);
index 773ea8a..4338846 100644 (file)
@@ -1335,6 +1335,10 @@ class HGraphBuilder {
                                     bool is_js_array,
                                     PropertyAccessType access_type);
 
+  HValue* BuildCheckAndGrowElementsCapacity(HValue* object, HValue* elements,
+                                            ElementsKind kind, HValue* length,
+                                            HValue* capacity, HValue* key);
+
   HValue* BuildCopyElementsOnWrite(HValue* object,
                                    HValue* elements,
                                    ElementsKind kind,
index 6c77ef8..9404340 100644 (file)
@@ -56,6 +56,11 @@ const Register MathPowIntegerDescriptor::exponent() {
 }
 
 
+const Register GrowArrayElementsDescriptor::ObjectRegister() { return eax; }
+const Register GrowArrayElementsDescriptor::KeyRegister() { return ebx; }
+const Register GrowArrayElementsDescriptor::CapacityRegister() { return ecx; }
+
+
 void FastNewClosureDescriptor::Initialize(CallInterfaceDescriptorData* data) {
   Register registers[] = {esi, ebx};
   data->Initialize(arraysize(registers), registers, NULL);
index b1ebc3f..4c7a115 100644 (file)
@@ -146,5 +146,12 @@ void ContextOnlyDescriptor::Initialize(CallInterfaceDescriptorData* data) {
   data->Initialize(arraysize(registers), registers, NULL);
 }
 
+
+void GrowArrayElementsDescriptor::Initialize(
+    CallInterfaceDescriptorData* data) {
+  Register registers[] = {ContextRegister(), ObjectRegister(), KeyRegister(),
+                          CapacityRegister()};
+  data->Initialize(arraysize(registers), registers, NULL);
+}
 }
 }  // namespace v8::internal
index 5d02e84..4ececd7 100644 (file)
@@ -53,7 +53,8 @@ class PlatformInterfaceDescriptor;
   V(StoreArrayLiteralElement)                 \
   V(MathPowTagged)                            \
   V(MathPowInteger)                           \
-  V(ContextOnly)
+  V(ContextOnly)                              \
+  V(GrowArrayElements)
 
 
 class CallInterfaceDescriptorData {
@@ -488,6 +489,17 @@ class ContextOnlyDescriptor : public CallInterfaceDescriptor {
   DECLARE_DESCRIPTOR(ContextOnlyDescriptor, CallInterfaceDescriptor)
 };
 
+
+class GrowArrayElementsDescriptor : public CallInterfaceDescriptor {
+ public:
+  DECLARE_DESCRIPTOR(GrowArrayElementsDescriptor, CallInterfaceDescriptor)
+
+  enum RegisterInfo { kObjectIndex, kKeyIndex, kCapacityIndex };
+  static const Register ObjectRegister();
+  static const Register KeyRegister();
+  static const Register CapacityRegister();
+};
+
 #undef DECLARE_DESCRIPTOR
 
 
index de41d68..62f04b8 100644 (file)
@@ -1827,6 +1827,12 @@ void JSObject::EnsureCanContainElements(Handle<JSObject> object,
 }
 
 
+bool JSObject::WouldConvertToSlowElements(Handle<Object> key) {
+  uint32_t index;
+  return key->ToArrayIndex(&index) && WouldConvertToSlowElements(index);
+}
+
+
 void JSObject::SetMapAndElements(Handle<JSObject> object,
                                  Handle<Map> new_map,
                                  Handle<FixedArrayBase> value) {
index 96a1aa1..ec31b72 100644 (file)
@@ -11267,10 +11267,8 @@ void Code::Disassemble(const char* name, std::ostream& os) {  // NOLINT
 #endif  // ENABLE_DISASSEMBLER
 
 
-Handle<FixedArray> JSObject::SetFastElementsCapacityAndLength(
-    Handle<JSObject> object,
-    int capacity,
-    int length,
+Handle<FixedArray> JSObject::SetFastElementsCapacity(
+    Handle<JSObject> object, int capacity,
     SetFastElementsCapacitySmiMode smi_mode) {
   // We should never end in here with a pixel or external array.
   DCHECK(!object->HasExternalArrayElements());
@@ -11322,6 +11320,15 @@ Handle<FixedArray> JSObject::SetFastElementsCapacityAndLength(
                             object->GetElementsKind(), new_elements);
   }
 
+  return new_elements;
+}
+
+
+Handle<FixedArray> JSObject::SetFastElementsCapacityAndLength(
+    Handle<JSObject> object, int capacity, int length,
+    SetFastElementsCapacitySmiMode smi_mode) {
+  Handle<FixedArray> new_elements =
+      SetFastElementsCapacity(object, capacity, smi_mode);
   if (object->IsJSArray()) {
     Handle<JSArray>::cast(object)->set_length(Smi::FromInt(length));
   }
@@ -11329,9 +11336,8 @@ Handle<FixedArray> JSObject::SetFastElementsCapacityAndLength(
 }
 
 
-void JSObject::SetFastDoubleElementsCapacityAndLength(Handle<JSObject> object,
-                                                      int capacity,
-                                                      int length) {
+Handle<FixedArrayBase> JSObject::SetFastDoubleElementsCapacity(
+    Handle<JSObject> object, int capacity) {
   // We should never end in here with a pixel or external array.
   DCHECK(!object->HasExternalArrayElements());
 
@@ -11361,9 +11367,18 @@ void JSObject::SetFastDoubleElementsCapacityAndLength(Handle<JSObject> object,
                             object->GetElementsKind(), elems);
   }
 
+  return elems;
+}
+
+
+Handle<FixedArrayBase> JSObject::SetFastDoubleElementsCapacityAndLength(
+    Handle<JSObject> object, int capacity, int length) {
+  Handle<FixedArrayBase> new_elements =
+      SetFastDoubleElementsCapacity(object, capacity);
   if (object->IsJSArray()) {
     Handle<JSArray>::cast(object)->set_length(Smi::FromInt(length));
   }
+  return new_elements;
 }
 
 
@@ -13288,9 +13303,8 @@ void JSObject::GetElementsCapacityAndUsage(int* capacity, int* used) {
 }
 
 
-bool JSObject::WouldConvertToSlowElements(Handle<Object> key) {
-  uint32_t index;
-  if (HasFastElements() && key->ToArrayIndex(&index)) {
+bool JSObject::WouldConvertToSlowElements(uint32_t index) {
+  if (HasFastElements()) {
     Handle<FixedArrayBase> backing_store(FixedArrayBase::cast(elements()));
     uint32_t capacity = static_cast<uint32_t>(backing_store->length());
     if (index >= capacity) {
index 067f357..b63b1c3 100644 (file)
@@ -1906,7 +1906,8 @@ class JSObject: public JSReceiver {
 
   // Would we convert a fast elements array to dictionary mode given
   // an access at key?
-  bool WouldConvertToSlowElements(Handle<Object> key);
+  bool WouldConvertToSlowElements(uint32_t index);
+  inline bool WouldConvertToSlowElements(Handle<Object> key);
   // Do we want to keep the elements in fast case when increasing the
   // capacity?
   bool ShouldConvertToSlowElements(int new_capacity);
@@ -1966,6 +1967,12 @@ class JSObject: public JSReceiver {
     kDontAllowSmiElements
   };
 
+  static Handle<FixedArray> SetFastElementsCapacity(
+      Handle<JSObject> object, int capacity,
+      SetFastElementsCapacitySmiMode smi_mode);
+  static Handle<FixedArrayBase> SetFastDoubleElementsCapacity(
+      Handle<JSObject> object, int capacity);
+
   // Replace the elements' backing store with fast elements of the given
   // capacity.  Update the length for JSArrays.  Returns the new backing
   // store.
@@ -1974,10 +1981,8 @@ class JSObject: public JSReceiver {
       int capacity,
       int length,
       SetFastElementsCapacitySmiMode smi_mode);
-  static void SetFastDoubleElementsCapacityAndLength(
-      Handle<JSObject> object,
-      int capacity,
-      int length);
+  static Handle<FixedArrayBase> SetFastDoubleElementsCapacityAndLength(
+      Handle<JSObject> object, int capacity, int length);
 
   // Lookup interceptors are used for handling properties controlled by host
   // objects.
index 523d8f5..29890b1 100644 (file)
@@ -1055,6 +1055,44 @@ RUNTIME_FUNCTION(Runtime_NormalizeElements) {
 }
 
 
+// GrowArrayElements returns a sentinel Smi if the object was normalized.
+RUNTIME_FUNCTION(Runtime_GrowArrayElements) {
+  HandleScope scope(isolate);
+  DCHECK(args.length() == 3);
+  CONVERT_ARG_HANDLE_CHECKED(JSObject, object, 0);
+  CONVERT_SMI_ARG_CHECKED(key, 1);
+
+  if (key < 0) {
+    return object->elements();
+  }
+
+  uint32_t capacity = static_cast<uint32_t>(object->elements()->length());
+  uint32_t index = static_cast<uint32_t>(key);
+
+  if (index >= capacity) {
+    if (object->WouldConvertToSlowElements(index)) {
+      JSObject::NormalizeElements(object);
+      return Smi::FromInt(0);
+    }
+
+    uint32_t new_capacity = JSObject::NewElementsCapacity(index + 1);
+    ElementsKind kind = object->GetElementsKind();
+    if (IsFastDoubleElementsKind(kind)) {
+      JSObject::SetFastDoubleElementsCapacity(object, new_capacity);
+    } else {
+      JSObject::SetFastElementsCapacitySmiMode set_capacity_mode =
+          object->HasFastSmiElements() ? JSObject::kAllowSmiElements
+                                       : JSObject::kDontAllowSmiElements;
+      JSObject::SetFastElementsCapacity(object, new_capacity,
+                                        set_capacity_mode);
+    }
+  }
+
+  // On success, return the fixed array elements.
+  return object->elements();
+}
+
+
 RUNTIME_FUNCTION(Runtime_HasComplexElements) {
   HandleScope scope(isolate);
   DCHECK(args.length() == 1);
index 4bc8fdf..85ffece 100644 (file)
@@ -272,6 +272,7 @@ namespace internal {
   F(MoveArrayContents, 2, 1)                           \
   F(EstimateNumberOfElements, 1, 1)                    \
   F(NormalizeElements, 1, 1)                           \
+  F(GrowArrayElements, 3, 1)                           \
   F(HasComplexElements, 1, 1)                          \
                                                        \
   /* Getters and Setters */                            \
index f19979d..317a1e7 100644 (file)
@@ -56,6 +56,12 @@ const Register MathPowIntegerDescriptor::exponent() {
 }
 
 
+// IC register specifications
+const Register GrowArrayElementsDescriptor::ObjectRegister() { return rax; }
+const Register GrowArrayElementsDescriptor::KeyRegister() { return rbx; }
+const Register GrowArrayElementsDescriptor::CapacityRegister() { return rcx; }
+
+
 void FastNewClosureDescriptor::Initialize(CallInterfaceDescriptorData* data) {
   Register registers[] = {rsi, rbx};
   data->Initialize(arraysize(registers), registers, NULL);
diff --git a/test/mjsunit/ensure-growing-store-learns.js b/test/mjsunit/ensure-growing-store-learns.js
new file mode 100644 (file)
index 0000000..32c7f64
--- /dev/null
@@ -0,0 +1,109 @@
+// Copyright 2010 the V8 project authors. All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+//       notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+//       copyright notice, this list of conditions and the following
+//       disclaimer in the documentation and/or other materials provided
+//       with the distribution.
+//     * Neither the name of Google Inc. nor the names of its
+//       contributors may be used to endorse or promote products derived
+//       from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Flags: --allow-natives-syntax --noverify-heap --noenable-slow-asserts
+
+// --noverify-heap and --noenable-slow-asserts are set because the test is too
+// slow with it on.
+
+// Ensure that keyed stores work, and optimized functions learn if the
+// store required change to dictionary mode. Verify that stores that grow
+// the array into large object space don't cause a deopt.
+(function() {
+  var a = [];
+
+  function foo(a, i) {
+    a[i] = 5.3;
+  }
+
+  foo(a, 1);
+  foo(a, 2);
+  foo(a, 3);
+  %OptimizeFunctionOnNextCall(foo);
+  a[3] = 0;
+  foo(a, 3);
+  assertEquals(a[3], 5.3);
+  foo(a, 50000);
+  assertUnoptimized(foo);
+  assertTrue(%HasDictionaryElements(a));
+
+  var b = [];
+  foo(b, 1);
+  foo(b, 2);
+  // Put b in dictionary mode.
+  b[10000] = 5;
+  assertTrue(%HasDictionaryElements(b));
+  foo(b, 3);
+  %OptimizeFunctionOnNextCall(foo);
+  foo(b, 50000);
+  assertOptimized(foo);
+  assertTrue(%HasDictionaryElements(b));
+
+  // Clearing feedback for the StoreIC in foo is important for runs with
+  // flag --stress-opt.
+  %ClearFunctionTypeFeedback(foo);
+})();
+
+
+(function() {
+  var a = new Array(10);
+
+  function foo2(a, i) {
+    a[i] = 50;
+  }
+
+  // The KeyedStoreIC will learn GROW_MODE.
+  foo2(a, 10);
+  foo2(a, 12);
+  foo2(a, 31);
+  %OptimizeFunctionOnNextCall(foo2);
+  foo2(a, 40);
+
+  // This test is way too slow without crankshaft.
+  if (4 != %GetOptimizationStatus(foo2)) {
+    assertOptimized(foo2);
+    assertTrue(%HasFastSmiElements(a));
+
+    // Grow a large array into large object space through the keyed store
+    // without deoptimizing. Grow by 10s. If we set elements too sparsely, the
+    // array will convert to dictionary mode.
+    a = new Array(99999);
+    assertTrue(%HasFastSmiElements(a));
+    for (var i = 0; i < 263000; i += 10) {
+      foo2(a, i);
+    }
+
+    // Verify that we are over 1 page in size, and foo2 remains optimized.
+    // This means we've smoothly transitioned to allocating in large object
+    // space.
+    assertTrue(%HasFastSmiElements(a));
+    assertTrue(a.length * 4 > (1024 * 1024));
+    assertOptimized(foo2);
+  }
+
+  %ClearFunctionTypeFeedback(foo2);
+})();