diff --git a/src/arm/interface-descriptors-arm.cc b/src/arm/interface-descriptors-arm.cc index 6e77ee474a..78febc3fd8 100644 --- a/src/arm/interface-descriptors-arm.cc +++ b/src/arm/interface-descriptors-arm.cc @@ -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); diff --git a/src/arm/lithium-codegen-arm.cc b/src/arm/lithium-codegen-arm.cc index e323e0d210..bb81960dec 100644 --- a/src/arm/lithium-codegen-arm.cc +++ b/src/arm/lithium-codegen-arm.cc @@ -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()); diff --git a/src/arm64/interface-descriptors-arm64.cc b/src/arm64/interface-descriptors-arm64.cc index 57eebcc3b5..f7e6c5f6d4 100644 --- a/src/arm64/interface-descriptors-arm64.cc +++ b/src/arm64/interface-descriptors-arm64.cc @@ -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 diff --git a/src/code-stubs-hydrogen.cc b/src/code-stubs-hydrogen.cc index f66670bfe9..a9cd5b4691 100644 --- a/src/code-stubs-hydrogen.cc +++ b/src/code-stubs-hydrogen.cc @@ -547,6 +547,31 @@ Handle StoreScriptContextFieldStub::GenerateCode() { } +template <> +HValue* CodeStubGraphBuilder::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(object, static_cast(NULL), + HObjectAccess::ForArrayLength(kind)) + : current_capacity; + + return BuildCheckAndGrowElementsCapacity(object, elements, kind, length, + current_capacity, key); +} + + +Handle GrowArrayElementsStub::GenerateCode() { + return DoGenerateCode(this); +} + + template <> HValue* CodeStubGraphBuilder::BuildCodeStub() { HInstruction* load = BuildUncheckedMonomorphicElementAccess( diff --git a/src/code-stubs.cc b/src/code-stubs.cc index 7442f3e134..6b69832586 100644 --- a/src/code-stubs.cc +++ b/src/code-stubs.cc @@ -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(); diff --git a/src/code-stubs.h b/src/code-stubs.h index 7a6ad6ad6e..bf87a0d157 100644 --- a/src/code-stubs.h +++ b/src/code-stubs.h @@ -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 {}; + class IsJsArrayBits : public BitField {}; + + DEFINE_CALL_INTERFACE_DESCRIPTOR(GrowArrayElements); + DEFINE_HYDROGEN_CODE_STUB(GrowArrayElements, HydrogenCodeStub); +}; + + class InstanceofStub: public PlatformCodeStub { public: enum Flags { diff --git a/src/hydrogen-instructions.cc b/src/hydrogen-instructions.cc index bc7b2ce3b5..0f28c31e39 100644 --- a/src/hydrogen-instructions.cc +++ b/src/hydrogen-instructions.cc @@ -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: diff --git a/src/hydrogen.cc b/src/hydrogen.cc index 21ef8c019f..d188cb886f 100644 --- a/src/hydrogen.cc +++ b/src/hydrogen.cc @@ -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(static_cast(JSObject::kMaxGap)); + HValue* max_capacity = AddUncasted(capacity, max_gap); + Add(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(static_cast(JSObject::kMaxGap)); - HValue* max_capacity = AddUncasted(current_capacity, max_gap); + // 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(stub.GetCode()); + HValue* op_vals[] = {context(), object, key, current_capacity}; + HValue* new_elements = Add( + target, 0, descriptor, Vector(op_vals, 4)); + // If the object changed to a dictionary, GrowArrayElements will return a + // smi to signal that deopt is required. + Add(new_elements); + environment()->Push(new_elements); + } - Add(key, max_capacity); - - HValue* new_capacity = BuildNewElementsCapacity(key); - HValue* new_elements = BuildGrowElementsCapacity(object, elements, - kind, kind, length, - new_capacity); - - environment()->Push(new_elements); capacity_checker.Else(); environment()->Push(elements); diff --git a/src/hydrogen.h b/src/hydrogen.h index 773ea8a3f4..43388464cb 100644 --- a/src/hydrogen.h +++ b/src/hydrogen.h @@ -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, diff --git a/src/ia32/interface-descriptors-ia32.cc b/src/ia32/interface-descriptors-ia32.cc index 6c77ef8f81..9404340b19 100644 --- a/src/ia32/interface-descriptors-ia32.cc +++ b/src/ia32/interface-descriptors-ia32.cc @@ -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); diff --git a/src/interface-descriptors.cc b/src/interface-descriptors.cc index b1ebc3faee..4c7a1159b5 100644 --- a/src/interface-descriptors.cc +++ b/src/interface-descriptors.cc @@ -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 diff --git a/src/interface-descriptors.h b/src/interface-descriptors.h index 5d02e84ebd..4ececd7b21 100644 --- a/src/interface-descriptors.h +++ b/src/interface-descriptors.h @@ -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 diff --git a/src/objects-inl.h b/src/objects-inl.h index de41d689b5..62f04b831a 100644 --- a/src/objects-inl.h +++ b/src/objects-inl.h @@ -1827,6 +1827,12 @@ void JSObject::EnsureCanContainElements(Handle object, } +bool JSObject::WouldConvertToSlowElements(Handle key) { + uint32_t index; + return key->ToArrayIndex(&index) && WouldConvertToSlowElements(index); +} + + void JSObject::SetMapAndElements(Handle object, Handle new_map, Handle value) { diff --git a/src/objects.cc b/src/objects.cc index 96a1aa19c9..ec31b72bd4 100644 --- a/src/objects.cc +++ b/src/objects.cc @@ -11267,10 +11267,8 @@ void Code::Disassemble(const char* name, std::ostream& os) { // NOLINT #endif // ENABLE_DISASSEMBLER -Handle JSObject::SetFastElementsCapacityAndLength( - Handle object, - int capacity, - int length, +Handle JSObject::SetFastElementsCapacity( + Handle 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 JSObject::SetFastElementsCapacityAndLength( object->GetElementsKind(), new_elements); } + return new_elements; +} + + +Handle JSObject::SetFastElementsCapacityAndLength( + Handle object, int capacity, int length, + SetFastElementsCapacitySmiMode smi_mode) { + Handle new_elements = + SetFastElementsCapacity(object, capacity, smi_mode); if (object->IsJSArray()) { Handle::cast(object)->set_length(Smi::FromInt(length)); } @@ -11329,9 +11336,8 @@ Handle JSObject::SetFastElementsCapacityAndLength( } -void JSObject::SetFastDoubleElementsCapacityAndLength(Handle object, - int capacity, - int length) { +Handle JSObject::SetFastDoubleElementsCapacity( + Handle 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 object, object->GetElementsKind(), elems); } + return elems; +} + + +Handle JSObject::SetFastDoubleElementsCapacityAndLength( + Handle object, int capacity, int length) { + Handle new_elements = + SetFastDoubleElementsCapacity(object, capacity); if (object->IsJSArray()) { Handle::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 key) { - uint32_t index; - if (HasFastElements() && key->ToArrayIndex(&index)) { +bool JSObject::WouldConvertToSlowElements(uint32_t index) { + if (HasFastElements()) { Handle backing_store(FixedArrayBase::cast(elements())); uint32_t capacity = static_cast(backing_store->length()); if (index >= capacity) { diff --git a/src/objects.h b/src/objects.h index 067f357822..b63b1c3b45 100644 --- a/src/objects.h +++ b/src/objects.h @@ -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 key); + bool WouldConvertToSlowElements(uint32_t index); + inline bool WouldConvertToSlowElements(Handle 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 SetFastElementsCapacity( + Handle object, int capacity, + SetFastElementsCapacitySmiMode smi_mode); + static Handle SetFastDoubleElementsCapacity( + Handle 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 object, - int capacity, - int length); + static Handle SetFastDoubleElementsCapacityAndLength( + Handle object, int capacity, int length); // Lookup interceptors are used for handling properties controlled by host // objects. diff --git a/src/runtime/runtime-array.cc b/src/runtime/runtime-array.cc index 523d8f5faa..29890b1558 100644 --- a/src/runtime/runtime-array.cc +++ b/src/runtime/runtime-array.cc @@ -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(object->elements()->length()); + uint32_t index = static_cast(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); diff --git a/src/runtime/runtime.h b/src/runtime/runtime.h index 4bc8fdf716..85ffece01a 100644 --- a/src/runtime/runtime.h +++ b/src/runtime/runtime.h @@ -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 */ \ diff --git a/src/x64/interface-descriptors-x64.cc b/src/x64/interface-descriptors-x64.cc index f19979d467..317a1e7a04 100644 --- a/src/x64/interface-descriptors-x64.cc +++ b/src/x64/interface-descriptors-x64.cc @@ -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 index 0000000000..32c7f64018 --- /dev/null +++ b/test/mjsunit/ensure-growing-store-learns.js @@ -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); +})();