Use a stub in crankshaft for grow store arrays.
authormvstanton <mvstanton@chromium.org>
Thu, 30 Apr 2015 12:34:02 +0000 (05:34 -0700)
committerCommit bot <commit-bot@chromium.org>
Thu, 30 Apr 2015 12:34:10 +0000 (12:34 +0000)
We were deopting without learning anything.

This is a rebase/reland of https://codereview.chromium.org/368263003

BUG=v8:3417
LOG=N

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

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

20 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.cc
src/hydrogen.h
src/ia32/interface-descriptors-ia32.cc
src/interface-descriptors.cc
src/interface-descriptors.h
src/mips/interface-descriptors-mips.cc
src/mips64/interface-descriptors-mips64.cc
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 64d0055..caff56d 100644 (file)
@@ -54,6 +54,11 @@ const Register MathPowIntegerDescriptor::exponent() {
 }
 
 
+const Register GrowArrayElementsDescriptor::ObjectRegister() { return r0; }
+const Register GrowArrayElementsDescriptor::KeyRegister() { return r3; }
+const Register GrowArrayElementsDescriptor::CapacityRegister() { return r2; }
+
+
 void FastNewClosureDescriptor::Initialize(CallInterfaceDescriptorData* data) {
   Register registers[] = {cp, r2};
   data->Initialize(arraysize(registers), registers, NULL);
index fc33139..be7958e 100644 (file)
@@ -4017,8 +4017,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 773dec4..7dff69c 100644 (file)
@@ -60,6 +60,11 @@ const Register MathPowTaggedDescriptor::exponent() { return x11; }
 const Register MathPowIntegerDescriptor::exponent() { return x12; }
 
 
+const Register GrowArrayElementsDescriptor::ObjectRegister() { return x0; }
+const Register GrowArrayElementsDescriptor::KeyRegister() { return x3; }
+const Register GrowArrayElementsDescriptor::CapacityRegister() { return x2; }
+
+
 void FastNewClosureDescriptor::Initialize(CallInterfaceDescriptorData* data) {
   // cp: context
   // x2: function info
index 64736a2..d346e95 100644 (file)
@@ -577,6 +577,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() {
   LoadKeyedHoleMode hole_mode = casted_stub()->convert_hole_to_undefined()
                                     ? CONVERT_HOLE_TO_UNDEFINED
index e3940da..46d8342 100644 (file)
@@ -715,6 +715,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 6bfe8b5..c6767e9 100644 (file)
@@ -69,6 +69,7 @@ namespace internal {
   V(FastCloneShallowObject)                 \
   V(FastNewClosure)                         \
   V(FastNewContext)                         \
+  V(GrowArrayElements)                      \
   V(InternalArrayNArgumentsConstructor)     \
   V(InternalArrayNoArgumentConstructor)     \
   V(InternalArraySingleArgumentConstructor) \
@@ -709,6 +710,28 @@ class CreateWeakCellStub : 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 f6a6d8d..5827059 100644 (file)
@@ -1300,6 +1300,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,
@@ -1323,17 +1337,27 @@ 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()) {
+    HValue* new_elements = BuildCheckAndGrowElementsCapacity(
+        object, elements, kind, length, current_capacity, key);
+    environment()->Push(new_elements);
+  } 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 a5b72c3..e229b04 100644 (file)
@@ -1316,6 +1316,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 407b1c7..fc5c5c3 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 a100123..6ce5faa 100644 (file)
@@ -57,7 +57,8 @@ class PlatformInterfaceDescriptor;
   V(StoreArrayLiteralElement)                 \
   V(MathPowTagged)                            \
   V(MathPowInteger)                           \
-  V(ContextOnly)
+  V(ContextOnly)                              \
+  V(GrowArrayElements)
 
 
 class CallInterfaceDescriptorData {
@@ -525,6 +526,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 ec6c63f..96bb2af 100644 (file)
@@ -54,6 +54,11 @@ const Register MathPowIntegerDescriptor::exponent() {
 }
 
 
+const Register GrowArrayElementsDescriptor::ObjectRegister() { return a0; }
+const Register GrowArrayElementsDescriptor::KeyRegister() { return a3; }
+const Register GrowArrayElementsDescriptor::CapacityRegister() { return a2; }
+
+
 void FastNewClosureDescriptor::Initialize(CallInterfaceDescriptorData* data) {
   Register registers[] = {cp, a2};
   data->Initialize(arraysize(registers), registers, NULL);
index b9003af..50ad745 100644 (file)
@@ -54,6 +54,11 @@ const Register MathPowIntegerDescriptor::exponent() {
 }
 
 
+const Register GrowArrayElementsDescriptor::ObjectRegister() { return a0; }
+const Register GrowArrayElementsDescriptor::KeyRegister() { return a3; }
+const Register GrowArrayElementsDescriptor::CapacityRegister() { return a2; }
+
+
 void FastNewClosureDescriptor::Initialize(CallInterfaceDescriptorData* data) {
   Register registers[] = {cp, a2};
   data->Initialize(arraysize(registers), registers, NULL);
index 39f5bf0..c0be396 100644 (file)
@@ -1849,6 +1849,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 bac6cf7..6798d0f 100644 (file)
@@ -11986,10 +11986,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());
@@ -12044,6 +12042,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));
   }
@@ -12051,9 +12058,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());
 
@@ -12083,9 +12089,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;
 }
 
 
@@ -13844,9 +13859,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 76c2c29..dcac692 100644 (file)
@@ -1942,7 +1942,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);
@@ -1996,6 +1997,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.
@@ -2004,10 +2011,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 6f80443..8391f45 100644 (file)
@@ -1218,6 +1218,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 af3e135..0f56247 100644 (file)
@@ -43,6 +43,7 @@ namespace internal {
   F(ArrayConstructorWithSubclassing, -1, 1)                           \
   F(InternalArrayConstructor, -1, 1)                                  \
   F(NormalizeElements, 1, 1)                                          \
+  F(GrowArrayElements, 3, 1)                                          \
   F(HasComplexElements, 1, 1)                                         \
   F(ForInCacheArrayLength, 2, 1) /* TODO(turbofan): Only temporary */ \
   F(IsArray, 1, 1)                                                    \
index 0cef86e..fe7789b 100644 (file)
@@ -56,6 +56,11 @@ const Register MathPowIntegerDescriptor::exponent() {
 }
 
 
+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..e64fe93
--- /dev/null
@@ -0,0 +1,86 @@
+// Copyright 2015 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 --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);
+})();