diff options
Diffstat (limited to 'src/3rdparty/v8/src/objects.cc')
-rw-r--r-- | src/3rdparty/v8/src/objects.cc | 5007 |
1 files changed, 2927 insertions, 2080 deletions
diff --git a/src/3rdparty/v8/src/objects.cc b/src/3rdparty/v8/src/objects.cc index 7373384..0d3836b 100644 --- a/src/3rdparty/v8/src/objects.cc +++ b/src/3rdparty/v8/src/objects.cc @@ -56,11 +56,6 @@ namespace v8 { namespace internal { -void PrintElementsKind(FILE* out, ElementsKind kind) { - ElementsAccessor* accessor = ElementsAccessor::ForKind(kind); - PrintF(out, "%s", accessor->name()); -} - MUST_USE_RESULT static MaybeObject* CreateJSValue(JSFunction* constructor, Object* value) { @@ -74,13 +69,13 @@ MUST_USE_RESULT static MaybeObject* CreateJSValue(JSFunction* constructor, } -MaybeObject* Object::ToObject(Context* global_context) { +MaybeObject* Object::ToObject(Context* native_context) { if (IsNumber()) { - return CreateJSValue(global_context->number_function(), this); + return CreateJSValue(native_context->number_function(), this); } else if (IsBoolean()) { - return CreateJSValue(global_context->boolean_function(), this); + return CreateJSValue(native_context->boolean_function(), this); } else if (IsString()) { - return CreateJSValue(global_context->string_function(), this); + return CreateJSValue(native_context->string_function(), this); } ASSERT(IsJSObject()); return this; @@ -92,16 +87,16 @@ MaybeObject* Object::ToObject() { return this; } else if (IsNumber()) { Isolate* isolate = Isolate::Current(); - Context* global_context = isolate->context()->global_context(); - return CreateJSValue(global_context->number_function(), this); + Context* native_context = isolate->context()->native_context(); + return CreateJSValue(native_context->number_function(), this); } else if (IsBoolean()) { Isolate* isolate = HeapObject::cast(this)->GetIsolate(); - Context* global_context = isolate->context()->global_context(); - return CreateJSValue(global_context->boolean_function(), this); + Context* native_context = isolate->context()->native_context(); + return CreateJSValue(native_context->boolean_function(), this); } else if (IsString()) { Isolate* isolate = HeapObject::cast(this)->GetIsolate(); - Context* global_context = isolate->context()->global_context(); - return CreateJSValue(global_context->string_function(), this); + Context* native_context = isolate->context()->native_context(); + return CreateJSValue(native_context->string_function(), this); } // Throw a type error. @@ -139,13 +134,16 @@ void Object::Lookup(String* name, LookupResult* result) { if (IsJSReceiver()) { holder = this; } else { - Context* global_context = Isolate::Current()->context()->global_context(); + Context* native_context = Isolate::Current()->context()->native_context(); if (IsNumber()) { - holder = global_context->number_function()->instance_prototype(); + holder = native_context->number_function()->instance_prototype(); } else if (IsString()) { - holder = global_context->string_function()->instance_prototype(); + holder = native_context->string_function()->instance_prototype(); } else if (IsBoolean()) { - holder = global_context->boolean_function()->instance_prototype(); + holder = native_context->boolean_function()->instance_prototype(); + } else { + Isolate::Current()->PushStackTraceAndDie( + 0xDEAD0000, this, JSReceiver::cast(this)->map(), 0xDEAD0001); } } ASSERT(holder != NULL); // Cannot handle null or undefined. @@ -183,8 +181,19 @@ MaybeObject* JSObject::GetPropertyWithCallback(Object* receiver, // api style callbacks. if (structure->IsAccessorInfo()) { AccessorInfo* data = AccessorInfo::cast(structure); + if (!data->IsCompatibleReceiver(receiver)) { + Handle<Object> name_handle(name); + Handle<Object> receiver_handle(receiver); + Handle<Object> args[2] = { name_handle, receiver_handle }; + Handle<Object> error = + isolate->factory()->NewTypeError("incompatible_method_receiver", + HandleVector(args, + ARRAY_SIZE(args))); + return isolate->Throw(*error); + } Object* fun_obj = data->getter(); v8::AccessorGetter call_fun = v8::ToCData<v8::AccessorGetter>(fun_obj); + if (call_fun == NULL) return isolate->heap()->undefined_value(); HandleScope scope(isolate); JSObject* self = JSObject::cast(receiver); Handle<String> key(name); @@ -201,7 +210,9 @@ MaybeObject* JSObject::GetPropertyWithCallback(Object* receiver, if (result.IsEmpty()) { return isolate->heap()->undefined_value(); } - return *v8::Utils::OpenHandle(*result); + Object* return_value = *v8::Utils::OpenHandle(*result); + return_value->VerifyApiCallResultType(); + return return_value; } // __defineGetter__ callback @@ -253,13 +264,14 @@ MaybeObject* JSProxy::GetElementWithHandler(Object* receiver, } -MaybeObject* JSProxy::SetElementWithHandler(uint32_t index, +MaybeObject* JSProxy::SetElementWithHandler(JSReceiver* receiver, + uint32_t index, Object* value, StrictModeFlag strict_mode) { String* name; MaybeObject* maybe = GetHeap()->Uint32ToString(index); if (!maybe->To<String>(&name)) return maybe; - return SetPropertyWithHandler(name, value, NONE, strict_mode); + return SetPropertyWithHandler(receiver, name, value, NONE, strict_mode); } @@ -400,16 +412,16 @@ PropertyAttributes JSObject::GetPropertyAttributeWithFailedAccessCheck( } else { result->holder()->LocalLookupRealNamedProperty(name, &r); } - if (r.IsProperty()) { - return GetPropertyAttributeWithFailedAccessCheck(receiver, - &r, - name, - continue_search); - } - break; + if (!r.IsFound()) break; + return GetPropertyAttributeWithFailedAccessCheck(receiver, + &r, + name, + continue_search); } - default: + case HANDLER: + case TRANSITION: + case NONEXISTENT: UNREACHABLE(); } } @@ -475,10 +487,21 @@ MaybeObject* JSObject::SetNormalizedProperty(String* name, set_properties(StringDictionary::cast(dict)); return value; } - // Preserve enumeration index. - details = PropertyDetails(details.attributes(), - details.type(), - property_dictionary()->DetailsAt(entry).index()); + + PropertyDetails original_details = property_dictionary()->DetailsAt(entry); + int enumeration_index; + // Preserve the enumeration index unless the property was deleted. + if (original_details.IsDeleted()) { + enumeration_index = property_dictionary()->NextEnumerationIndex(); + property_dictionary()->SetNextEnumerationIndex(enumeration_index + 1); + } else { + enumeration_index = original_details.dictionary_index(); + ASSERT(enumeration_index > 0); + } + + details = PropertyDetails( + details.attributes(), details.type(), enumeration_index); + if (IsGlobalObject()) { JSGlobalPropertyCell* cell = JSGlobalPropertyCell::cast(property_dictionary()->ValueAt(entry)); @@ -506,11 +529,12 @@ MaybeObject* JSObject::DeleteNormalizedProperty(String* name, DeleteMode mode) { // map change to invalidate any ICs that think they can load // from the DontDelete cell without checking if it contains // the hole value. - Object* new_map; - { MaybeObject* maybe_new_map = map()->CopyDropDescriptors(); - if (!maybe_new_map->ToObject(&new_map)) return maybe_new_map; - } - set_map(Map::cast(new_map)); + Map* new_map; + MaybeObject* maybe_new_map = map()->CopyDropDescriptors(); + if (!maybe_new_map->To(&new_map)) return maybe_new_map; + + ASSERT(new_map->is_dictionary_map()); + set_map(new_map); } JSGlobalPropertyCell* cell = JSGlobalPropertyCell::cast(dictionary->ValueAt(entry)); @@ -543,7 +567,7 @@ bool JSObject::IsDirty() { // If the object is fully fast case and has the same map it was // created with then no changes can have been made to it. return map() != fun->initial_map() - || !HasFastElements() + || !HasFastObjectElements() || !HasFastProperties(); } @@ -627,15 +651,12 @@ MaybeObject* Object::GetProperty(Object* receiver, receiver, result->GetCallbackObject(), name); case HANDLER: return result->proxy()->GetPropertyWithHandler(receiver, name); - case INTERCEPTOR: { - JSObject* recvr = JSObject::cast(receiver); + case INTERCEPTOR: return result->holder()->GetPropertyWithInterceptor( - recvr, name, attributes); - } - case MAP_TRANSITION: - case ELEMENTS_TRANSITION: - case CONSTANT_TRANSITION: - case NULL_DESCRIPTOR: + receiver, name, attributes); + case TRANSITION: + case NONEXISTENT: + UNREACHABLE(); break; } UNREACHABLE(); @@ -656,13 +677,13 @@ MaybeObject* Object::GetElementWithReceiver(Object* receiver, uint32_t index) { holder = holder->GetPrototype()) { if (!holder->IsJSObject()) { Isolate* isolate = heap->isolate(); - Context* global_context = isolate->context()->global_context(); + Context* native_context = isolate->context()->native_context(); if (holder->IsNumber()) { - holder = global_context->number_function()->instance_prototype(); + holder = native_context->number_function()->instance_prototype(); } else if (holder->IsString()) { - holder = global_context->string_function()->instance_prototype(); + holder = native_context->string_function()->instance_prototype(); } else if (holder->IsBoolean()) { - holder = global_context->boolean_function()->instance_prototype(); + holder = native_context->boolean_function()->instance_prototype(); } else if (holder->IsJSProxy()) { return JSProxy::cast(holder)->GetElementWithHandler(receiver, index); } else { @@ -704,7 +725,7 @@ MaybeObject* Object::GetElementWithReceiver(Object* receiver, uint32_t index) { Object* Object::GetPrototype() { if (IsSmi()) { Heap* heap = Isolate::Current()->heap(); - Context* context = heap->isolate()->context()->global_context(); + Context* context = heap->isolate()->context()->native_context(); return context->number_function()->instance_prototype(); } @@ -716,7 +737,7 @@ Object* Object::GetPrototype() { return heap_object->map()->prototype(); } Heap* heap = heap_object->GetHeap(); - Context* context = heap->isolate()->context()->global_context(); + Context* context = heap->isolate()->context()->native_context(); if (heap_object->IsHeapNumber()) { return context->number_function()->instance_prototype(); @@ -758,7 +779,6 @@ MaybeObject* Object::GetHash(CreationFlag flag) { bool Object::SameValue(Object* other) { if (other == this) return true; - if (!IsHeapObject() || !other->IsHeapObject()) return false; // The object is either a number, a string, an odd-ball, // a real JS object, or a Harmony proxy. @@ -795,7 +815,7 @@ void Object::ShortPrint(StringStream* accumulator) { void Smi::SmiPrint(FILE* out) { - PrintF(out, "%d", value()); + FPrintF(out, "%d", value()); } @@ -810,7 +830,7 @@ void Failure::FailurePrint(StringStream* accumulator) { void Failure::FailurePrint(FILE* out) { - PrintF(out, "Failure(%p)", reinterpret_cast<void*>(value())); + FPrintF(out, "Failure(%p)", reinterpret_cast<void*>(value())); } @@ -1067,7 +1087,9 @@ void String::StringShortPrint(StringStream* accumulator) { void JSObject::JSObjectShortPrint(StringStream* accumulator) { switch (map()->instance_type()) { case JS_ARRAY_TYPE: { - double length = JSArray::cast(this)->length()->Number(); + double length = JSArray::cast(this)->length()->IsUndefined() + ? 0 + : JSArray::cast(this)->length()->Number(); accumulator->Add("<JS Array[%u]>", static_cast<uint32_t>(length)); break; } @@ -1146,19 +1168,19 @@ void JSObject::PrintElementsTransition( FILE* file, ElementsKind from_kind, FixedArrayBase* from_elements, ElementsKind to_kind, FixedArrayBase* to_elements) { if (from_kind != to_kind) { - PrintF(file, "elements transition ["); + FPrintF(file, "elements transition ["); PrintElementsKind(file, from_kind); - PrintF(file, " -> "); + FPrintF(file, " -> "); PrintElementsKind(file, to_kind); - PrintF(file, "] in "); + FPrintF(file, "] in "); JavaScriptFrame::PrintTop(file, false, true); - PrintF(file, " for "); + FPrintF(file, " for "); ShortPrint(file); - PrintF(file, " from "); + FPrintF(file, " from "); from_elements->ShortPrint(file); - PrintF(file, " to "); + FPrintF(file, " to "); to_elements->ShortPrint(file); - PrintF(file, "\n"); + FPrintF(file, "\n"); } } @@ -1318,7 +1340,7 @@ void HeapObject::IterateBody(InstanceType type, int object_size, SlicedString::BodyDescriptor::IterateBody(this, v); break; case kExternalStringTag: - if ((type & kStringEncodingMask) == kAsciiStringTag) { + if ((type & kStringEncodingMask) == kOneByteStringTag) { reinterpret_cast<ExternalAsciiString*>(this)-> ExternalAsciiStringIterateBody(v); } else { @@ -1392,8 +1414,7 @@ void HeapObject::IterateBody(InstanceType type, int object_size, case EXTERNAL_DOUBLE_ARRAY_TYPE: break; case SHARED_FUNCTION_INFO_TYPE: { - SharedFunctionInfo* shared = reinterpret_cast<SharedFunctionInfo*>(this); - shared->SharedFunctionInfoIterateBody(v); + SharedFunctionInfo::BodyDescriptor::IterateBody(this, v); break; } @@ -1433,7 +1454,7 @@ Object* HeapNumber::HeapNumberToBoolean() { void HeapNumber::HeapNumberPrint(FILE* out) { - PrintF(out, "%.16g", Number()); + FPrintF(out, "%.16g", Number()); } @@ -1481,20 +1502,19 @@ String* JSReceiver::constructor_name() { MaybeObject* JSObject::AddFastPropertyUsingMap(Map* new_map, String* name, - Object* value) { - int index = new_map->PropertyIndexFor(name); + Object* value, + int field_index) { if (map()->unused_property_fields() == 0) { - ASSERT(map()->unused_property_fields() == 0); int new_unused = new_map->unused_property_fields(); - Object* values; + FixedArray* values; { MaybeObject* maybe_values = properties()->CopySize(properties()->length() + new_unused + 1); - if (!maybe_values->ToObject(&values)) return maybe_values; + if (!maybe_values->To(&values)) return maybe_values; } - set_properties(FixedArray::cast(values)); + set_properties(values); } set_map(new_map); - return FastPropertyAtPut(index, value); + return FastPropertyAtPut(field_index, value); } @@ -1516,96 +1536,66 @@ static bool IsIdentifier(UnicodeCache* cache, MaybeObject* JSObject::AddFastProperty(String* name, Object* value, - PropertyAttributes attributes) { + PropertyAttributes attributes, + StoreFromKeyed store_mode) { ASSERT(!IsJSGlobalProxy()); + ASSERT(DescriptorArray::kNotFound == + map()->instance_descriptors()->Search( + name, map()->NumberOfOwnDescriptors())); // Normalize the object if the name is an actual string (not the // hidden symbols) and is not a real identifier. + // Normalize the object if it will have too many fast properties. Isolate* isolate = GetHeap()->isolate(); StringInputBuffer buffer(name); - if (!IsIdentifier(isolate->unicode_cache(), &buffer) - && name != isolate->heap()->hidden_symbol()) { + if ((!IsIdentifier(isolate->unicode_cache(), &buffer) + && name != isolate->heap()->hidden_symbol()) || + (map()->unused_property_fields() == 0 && + TooManyFastProperties(properties()->length(), store_mode))) { Object* obj; - { MaybeObject* maybe_obj = - NormalizeProperties(CLEAR_INOBJECT_PROPERTIES, 0); - if (!maybe_obj->ToObject(&obj)) return maybe_obj; - } + MaybeObject* maybe_obj = + NormalizeProperties(CLEAR_INOBJECT_PROPERTIES, 0); + if (!maybe_obj->ToObject(&obj)) return maybe_obj; + return AddSlowProperty(name, value, attributes); } - DescriptorArray* old_descriptors = map()->instance_descriptors(); // Compute the new index for new field. int index = map()->NextFreePropertyIndex(); // Allocate new instance descriptors with (name, index) added - FieldDescriptor new_field(name, index, attributes); - Object* new_descriptors; - { MaybeObject* maybe_new_descriptors = - old_descriptors->CopyInsert(&new_field, REMOVE_TRANSITIONS); - if (!maybe_new_descriptors->ToObject(&new_descriptors)) { - return maybe_new_descriptors; - } - } - - // Only allow map transition if the object isn't the global object and there - // is not a transition for the name, or there's a transition for the name but - // it's unrelated to properties. - int descriptor_index = old_descriptors->Search(name); - - // Element transitions are stored in the descriptor for property "", which is - // not a identifier and should have forced a switch to slow properties above. - ASSERT(descriptor_index == DescriptorArray::kNotFound || - old_descriptors->GetType(descriptor_index) != ELEMENTS_TRANSITION); - bool can_insert_transition = descriptor_index == DescriptorArray::kNotFound || - old_descriptors->GetType(descriptor_index) == ELEMENTS_TRANSITION; - bool allow_map_transition = - can_insert_transition && - (isolate->context()->global_context()->object_function()->map() != map()); + FieldDescriptor new_field(name, index, attributes, 0); ASSERT(index < map()->inobject_properties() || (index - map()->inobject_properties()) < properties()->length() || map()->unused_property_fields() == 0); - // Allocate a new map for the object. - Object* r; - { MaybeObject* maybe_r = map()->CopyDropDescriptors(); - if (!maybe_r->ToObject(&r)) return maybe_r; - } - Map* new_map = Map::cast(r); - if (allow_map_transition) { - // Allocate new instance descriptors for the old map with map transition. - MapTransitionDescriptor d(name, Map::cast(new_map), attributes); - Object* r; - { MaybeObject* maybe_r = old_descriptors->CopyInsert(&d, KEEP_TRANSITIONS); - if (!maybe_r->ToObject(&r)) return maybe_r; - } - old_descriptors = DescriptorArray::cast(r); - } + + FixedArray* values = NULL; if (map()->unused_property_fields() == 0) { - if (properties()->length() > MaxFastProperties()) { - Object* obj; - { MaybeObject* maybe_obj = - NormalizeProperties(CLEAR_INOBJECT_PROPERTIES, 0); - if (!maybe_obj->ToObject(&obj)) return maybe_obj; - } - return AddSlowProperty(name, value, attributes); - } // Make room for the new value - Object* values; - { MaybeObject* maybe_values = - properties()->CopySize(properties()->length() + kFieldsAdded); - if (!maybe_values->ToObject(&values)) return maybe_values; - } - set_properties(FixedArray::cast(values)); + MaybeObject* maybe_values = + properties()->CopySize(properties()->length() + kFieldsAdded); + if (!maybe_values->To(&values)) return maybe_values; + } + + // Only allow map transition if the object isn't the global object. + TransitionFlag flag = isolate->empty_object_map() != map() + ? INSERT_TRANSITION + : OMIT_TRANSITION; + + Map* new_map; + MaybeObject* maybe_new_map = map()->CopyAddDescriptor(&new_field, flag); + if (!maybe_new_map->To(&new_map)) return maybe_new_map; + + if (map()->unused_property_fields() == 0) { + ASSERT(values != NULL); + set_properties(values); new_map->set_unused_property_fields(kFieldsAdded - 1); } else { new_map->set_unused_property_fields(map()->unused_property_fields() - 1); } - // We have now allocated all the necessary objects. - // All the changes can be applied at once, so they are atomic. - map()->set_instance_descriptors(old_descriptors); - new_map->SetBackPointer(map()); - new_map->set_instance_descriptors(DescriptorArray::cast(new_descriptors)); + set_map(new_map); return FastPropertyAtPut(index, value); } @@ -1616,57 +1606,26 @@ MaybeObject* JSObject::AddConstantFunctionProperty( JSFunction* function, PropertyAttributes attributes) { // Allocate new instance descriptors with (name, function) added - ConstantFunctionDescriptor d(name, function, attributes); - Object* new_descriptors; - { MaybeObject* maybe_new_descriptors = - map()->instance_descriptors()->CopyInsert(&d, REMOVE_TRANSITIONS); - if (!maybe_new_descriptors->ToObject(&new_descriptors)) { - return maybe_new_descriptors; - } - } - - // Allocate a new map for the object. - Object* new_map; - { MaybeObject* maybe_new_map = map()->CopyDropDescriptors(); - if (!maybe_new_map->ToObject(&new_map)) return maybe_new_map; - } - - DescriptorArray* descriptors = DescriptorArray::cast(new_descriptors); - Map::cast(new_map)->set_instance_descriptors(descriptors); - Map* old_map = map(); - set_map(Map::cast(new_map)); + ConstantFunctionDescriptor d(name, function, attributes, 0); - // If the old map is the global object map (from new Object()), - // then transitions are not added to it, so we are done. Heap* heap = GetHeap(); - if (old_map == heap->isolate()->context()->global_context()-> - object_function()->map()) { - return function; - } - - // Do not add CONSTANT_TRANSITIONS to global objects - if (IsGlobalObject()) { - return function; - } + TransitionFlag flag = + // Do not add transitions to the empty object map (map of "new Object()"), + // nor to global objects. + (map() == heap->isolate()->empty_object_map() || IsGlobalObject() || + // Don't add transitions to special properties with non-trivial + // attributes. + // TODO(verwaest): Once we support attribute changes, these transitions + // should be kept as well. + attributes != NONE) + ? OMIT_TRANSITION + : INSERT_TRANSITION; - // Add a CONSTANT_TRANSITION descriptor to the old map, - // so future assignments to this property on other objects - // of the same type will create a normal field, not a constant function. - // Don't do this for special properties, with non-trival attributes. - if (attributes != NONE) { - return function; - } - ConstTransitionDescriptor mark(name, Map::cast(new_map)); - { MaybeObject* maybe_new_descriptors = - old_map->instance_descriptors()->CopyInsert(&mark, KEEP_TRANSITIONS); - if (!maybe_new_descriptors->ToObject(&new_descriptors)) { - // We have accomplished the main goal, so return success. - return function; - } - } - old_map->set_instance_descriptors(DescriptorArray::cast(new_descriptors)); - Map::cast(new_map)->SetBackPointer(old_map); + Map* new_map; + MaybeObject* maybe_new_map = map()->CopyAddDescriptor(&d, flag); + if (!maybe_new_map->To(&new_map)) return maybe_new_map; + set_map(new_map); return function; } @@ -1712,11 +1671,15 @@ MaybeObject* JSObject::AddSlowProperty(String* name, MaybeObject* JSObject::AddProperty(String* name, Object* value, PropertyAttributes attributes, - StrictModeFlag strict_mode) { + StrictModeFlag strict_mode, + JSReceiver::StoreFromKeyed store_mode, + ExtensibilityCheck extensibility_check) { ASSERT(!IsJSGlobalProxy()); Map* map_of_this = map(); Heap* heap = GetHeap(); - if (!map_of_this->is_extensible()) { + MaybeObject* result; + if (extensibility_check == PERFORM_EXTENSIBILITY_CHECK && + !map_of_this->is_extensible()) { if (strict_mode == kNonStrictMode) { return value; } else { @@ -1726,28 +1689,70 @@ MaybeObject* JSObject::AddProperty(String* name, HandleVector(args, 1))); } } + if (HasFastProperties()) { // Ensure the descriptor array does not get too big. - if (map_of_this->instance_descriptors()->number_of_descriptors() < + if (map_of_this->NumberOfOwnDescriptors() < DescriptorArray::kMaxNumberOfDescriptors) { if (value->IsJSFunction()) { - return AddConstantFunctionProperty(name, - JSFunction::cast(value), - attributes); + result = AddConstantFunctionProperty(name, + JSFunction::cast(value), + attributes); } else { - return AddFastProperty(name, value, attributes); + result = AddFastProperty(name, value, attributes, store_mode); } } else { // Normalize the object to prevent very large instance descriptors. // This eliminates unwanted N^2 allocation and lookup behavior. Object* obj; - { MaybeObject* maybe_obj = - NormalizeProperties(CLEAR_INOBJECT_PROPERTIES, 0); - if (!maybe_obj->ToObject(&obj)) return maybe_obj; - } + MaybeObject* maybe = NormalizeProperties(CLEAR_INOBJECT_PROPERTIES, 0); + if (!maybe->To(&obj)) return maybe; + result = AddSlowProperty(name, value, attributes); } + } else { + result = AddSlowProperty(name, value, attributes); + } + + Handle<Object> hresult; + if (!result->ToHandle(&hresult)) return result; + + if (FLAG_harmony_observation && map()->is_observed()) { + EnqueueChangeRecord(handle(this), "new", handle(name), + handle(heap->the_hole_value())); } - return AddSlowProperty(name, value, attributes); + + return *hresult; +} + + +void JSObject::EnqueueChangeRecord(Handle<JSObject> object, + const char* type_str, + Handle<String> name, + Handle<Object> old_value) { + Isolate* isolate = object->GetIsolate(); + HandleScope scope; + Handle<String> type = isolate->factory()->LookupAsciiSymbol(type_str); + Handle<Object> args[] = { type, object, name, old_value }; + bool threw; + Execution::Call(Handle<JSFunction>(isolate->observers_notify_change()), + Handle<Object>(isolate->heap()->undefined_value()), + old_value->IsTheHole() ? 3 : 4, args, + &threw); + ASSERT(!threw); +} + + +void JSObject::DeliverChangeRecords(Isolate* isolate) { + ASSERT(isolate->observer_delivery_pending()); + bool threw = false; + Execution::Call( + isolate->observers_deliver_changes(), + isolate->factory()->undefined_value(), + 0, + NULL, + &threw); + ASSERT(!threw); + isolate->set_observer_delivery_pending(false); } @@ -1755,25 +1760,25 @@ MaybeObject* JSObject::SetPropertyPostInterceptor( String* name, Object* value, PropertyAttributes attributes, - StrictModeFlag strict_mode) { + StrictModeFlag strict_mode, + ExtensibilityCheck extensibility_check) { // Check local property, ignore interceptor. LookupResult result(GetIsolate()); LocalLookupRealNamedProperty(name, &result); + if (!result.IsFound()) map()->LookupTransition(this, name, &result); if (result.IsFound()) { - // An existing property, a map transition or a null descriptor was - // found. Use set property to handle all these cases. + // An existing property or a map transition was found. Use set property to + // handle all these cases. return SetProperty(&result, name, value, attributes, strict_mode); } - bool found = false; + bool done = false; MaybeObject* result_object; - result_object = SetPropertyWithCallbackSetterInPrototypes(name, - value, - attributes, - &found, - strict_mode); - if (found) return result_object; + result_object = + SetPropertyViaPrototypes(name, value, attributes, strict_mode, &done); + if (done) return result_object; // Add a new real property. - return AddProperty(name, value, attributes, strict_mode); + return AddProperty(name, value, attributes, strict_mode, + MAY_BE_STORE_FROM_KEYED, extensibility_check); } @@ -1785,8 +1790,7 @@ MaybeObject* JSObject::ReplaceSlowProperty(String* name, int new_enumeration_index = 0; // 0 means "Use the next available index." if (old_index != -1) { // All calls to ReplaceSlowProperty have had all transitions removed. - ASSERT(!dictionary->ContainsTransition(old_index)); - new_enumeration_index = dictionary->DetailsAt(old_index).index(); + new_enumeration_index = dictionary->DetailsAt(old_index).dictionary_index(); } PropertyDetails new_details(attributes, NORMAL, new_enumeration_index); @@ -1794,39 +1798,52 @@ MaybeObject* JSObject::ReplaceSlowProperty(String* name, } -MaybeObject* JSObject::ConvertDescriptorToFieldAndMapTransition( +MaybeObject* JSObject::ConvertTransitionToMapTransition( + int transition_index, String* name, Object* new_value, PropertyAttributes attributes) { Map* old_map = map(); + Map* old_target = old_map->GetTransition(transition_index); Object* result; - { MaybeObject* maybe_result = - ConvertDescriptorToField(name, new_value, attributes); - if (!maybe_result->ToObject(&result)) return maybe_result; - } - // If we get to this point we have succeeded - do not return failure - // after this point. Later stuff is optional. - if (!HasFastProperties()) { - return result; - } - // Do not add transitions to the map of "new Object()". - if (map() == GetIsolate()->context()->global_context()-> - object_function()->map()) { - return result; - } - MapTransitionDescriptor transition(name, - map(), - attributes); - Object* new_descriptors; - { MaybeObject* maybe_new_descriptors = old_map->instance_descriptors()-> - CopyInsert(&transition, KEEP_TRANSITIONS); - if (!maybe_new_descriptors->ToObject(&new_descriptors)) { - return result; // Yes, return _result_. - } - } - old_map->set_instance_descriptors(DescriptorArray::cast(new_descriptors)); - map()->SetBackPointer(old_map); + MaybeObject* maybe_result = + ConvertDescriptorToField(name, new_value, attributes); + if (!maybe_result->To(&result)) return maybe_result; + + if (!HasFastProperties()) return result; + + // This method should only be used to convert existing transitions. Objects + // with the map of "new Object()" cannot have transitions in the first place. + Map* new_map = map(); + ASSERT(new_map != GetIsolate()->empty_object_map()); + + // TODO(verwaest): From here on we lose existing map transitions, causing + // invalid back pointers. This will change once we can store multiple + // transitions with the same key. + + bool owned_descriptors = old_map->owns_descriptors(); + if (owned_descriptors || + old_target->instance_descriptors() == old_map->instance_descriptors()) { + // Since the conversion above generated a new fast map with an additional + // property which can be shared as well, install this descriptor pointer + // along the entire chain of smaller maps. + Map* map; + DescriptorArray* new_descriptors = new_map->instance_descriptors(); + DescriptorArray* old_descriptors = old_map->instance_descriptors(); + for (Object* current = old_map; + !current->IsUndefined(); + current = map->GetBackPointer()) { + map = Map::cast(current); + if (map->instance_descriptors() != old_descriptors) break; + map->SetEnumLength(Map::kInvalidEnumCache); + map->set_instance_descriptors(new_descriptors); + } + old_map->set_owns_descriptors(false); + } + + old_map->SetTransition(transition_index, new_map); + new_map->SetBackPointer(old_map); return result; } @@ -1835,59 +1852,38 @@ MaybeObject* JSObject::ConvertDescriptorToField(String* name, Object* new_value, PropertyAttributes attributes) { if (map()->unused_property_fields() == 0 && - properties()->length() > MaxFastProperties()) { + TooManyFastProperties(properties()->length(), MAY_BE_STORE_FROM_KEYED)) { Object* obj; - { MaybeObject* maybe_obj = - NormalizeProperties(CLEAR_INOBJECT_PROPERTIES, 0); - if (!maybe_obj->ToObject(&obj)) return maybe_obj; - } + MaybeObject* maybe_obj = NormalizeProperties(CLEAR_INOBJECT_PROPERTIES, 0); + if (!maybe_obj->ToObject(&obj)) return maybe_obj; return ReplaceSlowProperty(name, new_value, attributes); } int index = map()->NextFreePropertyIndex(); - FieldDescriptor new_field(name, index, attributes); - // Make a new DescriptorArray replacing an entry with FieldDescriptor. - Object* descriptors_unchecked; - { MaybeObject* maybe_descriptors_unchecked = map()->instance_descriptors()-> - CopyInsert(&new_field, REMOVE_TRANSITIONS); - if (!maybe_descriptors_unchecked->ToObject(&descriptors_unchecked)) { - return maybe_descriptors_unchecked; - } - } - DescriptorArray* new_descriptors = - DescriptorArray::cast(descriptors_unchecked); + FieldDescriptor new_field(name, index, attributes, 0); // Make a new map for the object. - Object* new_map_unchecked; - { MaybeObject* maybe_new_map_unchecked = map()->CopyDropDescriptors(); - if (!maybe_new_map_unchecked->ToObject(&new_map_unchecked)) { - return maybe_new_map_unchecked; - } - } - Map* new_map = Map::cast(new_map_unchecked); - new_map->set_instance_descriptors(new_descriptors); + Map* new_map; + MaybeObject* maybe_new_map = map()->CopyInsertDescriptor(&new_field, + OMIT_TRANSITION); + if (!maybe_new_map->To(&new_map)) return maybe_new_map; // Make new properties array if necessary. - FixedArray* new_properties = 0; // Will always be NULL or a valid pointer. + FixedArray* new_properties = NULL; int new_unused_property_fields = map()->unused_property_fields() - 1; if (map()->unused_property_fields() == 0) { new_unused_property_fields = kFieldsAdded - 1; - Object* new_properties_object; - { MaybeObject* maybe_new_properties_object = - properties()->CopySize(properties()->length() + kFieldsAdded); - if (!maybe_new_properties_object->ToObject(&new_properties_object)) { - return maybe_new_properties_object; - } - } - new_properties = FixedArray::cast(new_properties_object); + MaybeObject* maybe_new_properties = + properties()->CopySize(properties()->length() + kFieldsAdded); + if (!maybe_new_properties->To(&new_properties)) return maybe_new_properties; } // Update pointers to commit changes. // Object points to the new map. new_map->set_unused_property_fields(new_unused_property_fields); set_map(new_map); - if (new_properties) { - set_properties(FixedArray::cast(new_properties)); + if (new_properties != NULL) { + set_properties(new_properties); } return FastPropertyAtPut(index, new_value); } @@ -1930,7 +1926,8 @@ MaybeObject* JSObject::SetPropertyWithInterceptor( this_handle->SetPropertyPostInterceptor(*name_handle, *value_handle, attributes, - strict_mode); + strict_mode, + PERFORM_EXTENSIBILITY_CHECK); RETURN_IF_SCHEDULED_EXCEPTION(isolate); return raw_result; } @@ -1944,7 +1941,7 @@ Handle<Object> JSReceiver::SetProperty(Handle<JSReceiver> object, bool skip_fallback_interceptor) { CALL_HEAP_FUNCTION(object->GetIsolate(), object->SetProperty(*key, *value, attributes, strict_mode, - skip_fallback_interceptor), + MAY_BE_STORE_FROM_KEYED, skip_fallback_interceptor), Object); } @@ -1953,10 +1950,14 @@ MaybeObject* JSReceiver::SetProperty(String* name, Object* value, PropertyAttributes attributes, StrictModeFlag strict_mode, + JSReceiver::StoreFromKeyed store_mode, bool skip_fallback_interceptor) { LookupResult result(GetIsolate()); LocalLookup(name, &result, skip_fallback_interceptor); - return SetProperty(&result, name, value, attributes, strict_mode); + if (!result.IsFound()) { + map()->LookupTransition(JSObject::cast(this), name, &result); + } + return SetProperty(&result, name, value, attributes, strict_mode, store_mode); } @@ -1989,6 +1990,16 @@ MaybeObject* JSObject::SetPropertyWithCallback(Object* structure, if (structure->IsAccessorInfo()) { // api style callbacks AccessorInfo* data = AccessorInfo::cast(structure); + if (!data->IsCompatibleReceiver(this)) { + Handle<Object> name_handle(name); + Handle<Object> receiver_handle(this); + Handle<Object> args[2] = { name_handle, receiver_handle }; + Handle<Object> error = + isolate->factory()->NewTypeError("incompatible_method_receiver", + HandleVector(args, + ARRAY_SIZE(args))); + return isolate->Throw(*error); + } Object* call_obj = data->setter(); v8::AccessorSetter call_fun = v8::ToCData<v8::AccessorSetter>(call_obj); if (call_fun == NULL) return value; @@ -2054,26 +2065,6 @@ MaybeObject* JSReceiver::SetPropertyWithDefinedSetter(JSReceiver* setter, } -void JSObject::LookupCallbackSetterInPrototypes(String* name, - LookupResult* result) { - Heap* heap = GetHeap(); - for (Object* pt = GetPrototype(); - pt != heap->null_value(); - pt = pt->GetPrototype()) { - if (pt->IsJSProxy()) { - return result->HandlerResult(JSProxy::cast(pt)); - } - JSObject::cast(pt)->LocalLookupRealNamedProperty(name, result); - if (result->IsProperty()) { - if (result->type() == CALLBACKS && !result->IsReadOnly()) return; - // Found non-callback or read-only callback, stop looking. - break; - } - } - result->NotFound(); -} - - MaybeObject* JSObject::SetElementWithCallbackSetterInPrototypes( uint32_t index, Object* value, @@ -2090,8 +2081,8 @@ MaybeObject* JSObject::SetElementWithCallbackSetterInPrototypes( *found = true; // Force abort return maybe; } - return JSProxy::cast(pt)->SetPropertyWithHandlerIfDefiningSetter( - name, value, NONE, strict_mode, found); + return JSProxy::cast(pt)->SetPropertyViaPrototypesWithHandler( + this, name, value, NONE, strict_mode, found); } if (!JSObject::cast(pt)->HasDictionaryElements()) { continue; @@ -2115,76 +2106,167 @@ MaybeObject* JSObject::SetElementWithCallbackSetterInPrototypes( return heap->the_hole_value(); } -MaybeObject* JSObject::SetPropertyWithCallbackSetterInPrototypes( +MaybeObject* JSObject::SetPropertyViaPrototypes( String* name, Object* value, PropertyAttributes attributes, - bool* found, - StrictModeFlag strict_mode) { + StrictModeFlag strict_mode, + bool* done) { Heap* heap = GetHeap(); + Isolate* isolate = heap->isolate(); + + *done = false; // We could not find a local property so let's check whether there is an - // accessor that wants to handle the property. - LookupResult accessor_result(heap->isolate()); - LookupCallbackSetterInPrototypes(name, &accessor_result); - if (accessor_result.IsFound()) { - *found = true; - if (accessor_result.type() == CALLBACKS) { - return SetPropertyWithCallback(accessor_result.GetCallbackObject(), - name, - value, - accessor_result.holder(), - strict_mode); - } else if (accessor_result.type() == HANDLER) { - // There is a proxy in the prototype chain. Invoke its - // getPropertyDescriptor trap. - bool found = false; - // SetPropertyWithHandlerIfDefiningSetter can cause GC, - // make sure to use the handlified references after calling - // the function. - Handle<JSObject> self(this); - Handle<String> hname(name); - Handle<Object> hvalue(value); - MaybeObject* result = - accessor_result.proxy()->SetPropertyWithHandlerIfDefiningSetter( - name, value, attributes, strict_mode, &found); - if (found) return result; - // The proxy does not define the property as an accessor. - // Consequently, it has no effect on setting the receiver. - return self->AddProperty(*hname, *hvalue, attributes, strict_mode); + // accessor that wants to handle the property, or whether the property is + // read-only on the prototype chain. + LookupResult result(isolate); + LookupRealNamedPropertyInPrototypes(name, &result); + if (result.IsFound()) { + switch (result.type()) { + case NORMAL: + case FIELD: + case CONSTANT_FUNCTION: + *done = result.IsReadOnly(); + break; + case INTERCEPTOR: { + PropertyAttributes attr = + result.holder()->GetPropertyAttributeWithInterceptor( + this, name, true); + *done = !!(attr & READ_ONLY); + break; + } + case CALLBACKS: { + if (!FLAG_es5_readonly && result.IsReadOnly()) break; + *done = true; + return SetPropertyWithCallback(result.GetCallbackObject(), + name, value, result.holder(), strict_mode); + } + case HANDLER: { + return result.proxy()->SetPropertyViaPrototypesWithHandler( + this, name, value, attributes, strict_mode, done); + } + case TRANSITION: + case NONEXISTENT: + UNREACHABLE(); + break; } } - *found = false; + + // If we get here with *done true, we have encountered a read-only property. + if (!FLAG_es5_readonly) *done = false; + if (*done) { + if (strict_mode == kNonStrictMode) return value; + Handle<Object> args[] = { Handle<Object>(name), Handle<Object>(this)}; + return isolate->Throw(*isolate->factory()->NewTypeError( + "strict_read_only_property", HandleVector(args, ARRAY_SIZE(args)))); + } return heap->the_hole_value(); } -void JSObject::LookupInDescriptor(String* name, LookupResult* result) { - DescriptorArray* descriptors = map()->instance_descriptors(); - int number = descriptors->SearchWithCache(name); - if (number != DescriptorArray::kNotFound) { - result->DescriptorResult(this, descriptors->GetDetails(number), number); - } else { - result->NotFound(); +enum RightTrimMode { FROM_GC, FROM_MUTATOR }; + + +static void ZapEndOfFixedArray(Address new_end, int to_trim) { + // If we are doing a big trim in old space then we zap the space. + Object** zap = reinterpret_cast<Object**>(new_end); + zap++; // Header of filler must be at least one word so skip that. + for (int i = 1; i < to_trim; i++) { + *zap++ = Smi::FromInt(0); } } -void Map::LookupInDescriptors(JSObject* holder, - String* name, - LookupResult* result) { - DescriptorArray* descriptors = instance_descriptors(); - DescriptorLookupCache* cache = - GetHeap()->isolate()->descriptor_lookup_cache(); - int number = cache->Lookup(descriptors, name); - if (number == DescriptorLookupCache::kAbsent) { - number = descriptors->Search(name); - cache->Update(descriptors, name, number); - } - if (number != DescriptorArray::kNotFound) { - result->DescriptorResult(holder, descriptors->GetDetails(number), number); - } else { - result->NotFound(); +template<RightTrimMode trim_mode> +static void RightTrimFixedArray(Heap* heap, FixedArray* elms, int to_trim) { + ASSERT(elms->map() != HEAP->fixed_cow_array_map()); + // For now this trick is only applied to fixed arrays in new and paged space. + ASSERT(!HEAP->lo_space()->Contains(elms)); + + const int len = elms->length(); + + ASSERT(to_trim < len); + + Address new_end = elms->address() + FixedArray::SizeFor(len - to_trim); + + if (trim_mode != FROM_GC || Heap::ShouldZapGarbage()) { + ZapEndOfFixedArray(new_end, to_trim); } + + int size_delta = to_trim * kPointerSize; + + // Technically in new space this write might be omitted (except for + // debug mode which iterates through the heap), but to play safer + // we still do it. + heap->CreateFillerObjectAt(new_end, size_delta); + + elms->set_length(len - to_trim); + + // Maintain marking consistency for IncrementalMarking. + if (Marking::IsBlack(Marking::MarkBitFrom(elms))) { + if (trim_mode == FROM_GC) { + MemoryChunk::IncrementLiveBytesFromGC(elms->address(), -size_delta); + } else { + MemoryChunk::IncrementLiveBytesFromMutator(elms->address(), -size_delta); + } + } +} + + +void Map::EnsureDescriptorSlack(Handle<Map> map, int slack) { + Handle<DescriptorArray> descriptors(map->instance_descriptors()); + if (slack <= descriptors->NumberOfSlackDescriptors()) return; + int number_of_descriptors = descriptors->number_of_descriptors(); + Isolate* isolate = map->GetIsolate(); + Handle<DescriptorArray> new_descriptors = + isolate->factory()->NewDescriptorArray(number_of_descriptors, slack); + DescriptorArray::WhitenessWitness witness(*new_descriptors); + + for (int i = 0; i < number_of_descriptors; ++i) { + new_descriptors->CopyFrom(i, *descriptors, i, witness); + } + + map->set_instance_descriptors(*new_descriptors); +} + + +void Map::AppendCallbackDescriptors(Handle<Map> map, + Handle<Object> descriptors) { + Isolate* isolate = map->GetIsolate(); + Handle<DescriptorArray> array(map->instance_descriptors()); + NeanderArray callbacks(descriptors); + int nof_callbacks = callbacks.length(); + + ASSERT(array->NumberOfSlackDescriptors() >= nof_callbacks); + + // Ensure the keys are symbols before writing them into the instance + // descriptor. Since it may cause a GC, it has to be done before we + // temporarily put the heap in an invalid state while appending descriptors. + for (int i = 0; i < nof_callbacks; ++i) { + Handle<AccessorInfo> entry(AccessorInfo::cast(callbacks.get(i))); + Handle<String> key = + isolate->factory()->SymbolFromString( + Handle<String>(String::cast(entry->name()))); + entry->set_name(*key); + } + + int nof = map->NumberOfOwnDescriptors(); + + // Fill in new callback descriptors. Process the callbacks from + // back to front so that the last callback with a given name takes + // precedence over previously added callbacks with that name. + for (int i = nof_callbacks - 1; i >= 0; i--) { + AccessorInfo* entry = AccessorInfo::cast(callbacks.get(i)); + String* key = String::cast(entry->name()); + // Check if a descriptor with this name already exists before writing. + if (array->Search(key, nof) == DescriptorArray::kNotFound) { + CallbacksDescriptor desc(key, entry, entry->property_attributes()); + array->Append(&desc); + nof += 1; + } + } + + map->SetNumberOfOwnDescriptors(nof); } @@ -2205,217 +2287,88 @@ static Handle<T> MaybeNull(T* p) { Handle<Map> Map::FindTransitionedMap(MapHandleList* candidates) { - ElementsKind elms_kind = elements_kind(); - if (elms_kind == FAST_DOUBLE_ELEMENTS) { - bool dummy = true; - Handle<Map> fast_map = - MaybeNull(LookupElementsTransitionMap(FAST_ELEMENTS, &dummy)); - if (!fast_map.is_null() && ContainsMap(candidates, fast_map)) { - return fast_map; - } - return Handle<Map>::null(); - } - if (elms_kind == FAST_SMI_ONLY_ELEMENTS) { - bool dummy = true; - Handle<Map> double_map = - MaybeNull(LookupElementsTransitionMap(FAST_DOUBLE_ELEMENTS, &dummy)); - // In the current implementation, if the DOUBLE map doesn't exist, the - // FAST map can't exist either. - if (double_map.is_null()) return Handle<Map>::null(); - Handle<Map> fast_map = - MaybeNull(double_map->LookupElementsTransitionMap(FAST_ELEMENTS, - &dummy)); - if (!fast_map.is_null() && ContainsMap(candidates, fast_map)) { - return fast_map; - } - if (ContainsMap(candidates, double_map)) return double_map; - } - return Handle<Map>::null(); -} - -static Map* GetElementsTransitionMapFromDescriptor(Object* descriptor_contents, - ElementsKind elements_kind) { - if (descriptor_contents->IsMap()) { - Map* map = Map::cast(descriptor_contents); - if (map->elements_kind() == elements_kind) { - return map; - } - return NULL; - } - - FixedArray* map_array = FixedArray::cast(descriptor_contents); - for (int i = 0; i < map_array->length(); ++i) { - Object* current = map_array->get(i); - // Skip undefined slots, they are sentinels for reclaimed maps. - if (!current->IsUndefined()) { - Map* current_map = Map::cast(map_array->get(i)); - if (current_map->elements_kind() == elements_kind) { - return current_map; + ElementsKind kind = elements_kind(); + Handle<Map> transitioned_map = Handle<Map>::null(); + Handle<Map> current_map(this); + bool packed = IsFastPackedElementsKind(kind); + if (IsTransitionableFastElementsKind(kind)) { + while (CanTransitionToMoreGeneralFastElementsKind(kind, false)) { + kind = GetNextMoreGeneralFastElementsKind(kind, false); + Handle<Map> maybe_transitioned_map = + MaybeNull(current_map->LookupElementsTransitionMap(kind)); + if (maybe_transitioned_map.is_null()) break; + if (ContainsMap(candidates, maybe_transitioned_map) && + (packed || !IsFastPackedElementsKind(kind))) { + transitioned_map = maybe_transitioned_map; + if (!IsFastPackedElementsKind(kind)) packed = false; } + current_map = maybe_transitioned_map; } } - - return NULL; + return transitioned_map; } -static MaybeObject* AddElementsTransitionMapToDescriptor( - Object* descriptor_contents, - Map* new_map) { - // Nothing was in the descriptor for an ELEMENTS_TRANSITION, - // simply add the map. - if (descriptor_contents == NULL) { - return new_map; - } - - // There was already a map in the descriptor, create a 2-element FixedArray - // to contain the existing map plus the new one. - FixedArray* new_array; - Heap* heap = new_map->GetHeap(); - if (descriptor_contents->IsMap()) { - // Must tenure, DescriptorArray expects no new-space objects. - MaybeObject* maybe_new_array = heap->AllocateFixedArray(2, TENURED); - if (!maybe_new_array->To<FixedArray>(&new_array)) { - return maybe_new_array; - } - new_array->set(0, descriptor_contents); - new_array->set(1, new_map); - return new_array; - } +static Map* FindClosestElementsTransition(Map* map, ElementsKind to_kind) { + Map* current_map = map; + int index = GetSequenceIndexFromFastElementsKind(map->elements_kind()); + int to_index = IsFastElementsKind(to_kind) + ? GetSequenceIndexFromFastElementsKind(to_kind) + : GetSequenceIndexFromFastElementsKind(TERMINAL_FAST_ELEMENTS_KIND); - // The descriptor already contained a list of maps for different ElementKinds - // of ELEMENTS_TRANSITION, first check the existing array for an undefined - // slot, and if that's not available, create a FixedArray to hold the existing - // maps plus the new one and fill it in. - FixedArray* array = FixedArray::cast(descriptor_contents); - for (int i = 0; i < array->length(); ++i) { - if (array->get(i)->IsUndefined()) { - array->set(i, new_map); - return array; - } - } + ASSERT(index <= to_index); - // Must tenure, DescriptorArray expects no new-space objects. - MaybeObject* maybe_new_array = - heap->AllocateFixedArray(array->length() + 1, TENURED); - if (!maybe_new_array->To<FixedArray>(&new_array)) { - return maybe_new_array; + for (; index < to_index; ++index) { + if (!current_map->HasElementsTransition()) return current_map; + current_map = current_map->elements_transition_map(); } - int i = 0; - while (i < array->length()) { - new_array->set(i, array->get(i)); - ++i; + if (!IsFastElementsKind(to_kind) && current_map->HasElementsTransition()) { + Map* next_map = current_map->elements_transition_map(); + if (next_map->elements_kind() == to_kind) return next_map; } - new_array->set(i, new_map); - return new_array; + ASSERT(IsFastElementsKind(to_kind) + ? current_map->elements_kind() == to_kind + : current_map->elements_kind() == TERMINAL_FAST_ELEMENTS_KIND); + return current_map; } -String* Map::elements_transition_sentinel_name() { - return GetHeap()->empty_symbol(); +Map* Map::LookupElementsTransitionMap(ElementsKind to_kind) { + Map* to_map = FindClosestElementsTransition(this, to_kind); + if (to_map->elements_kind() == to_kind) return to_map; + return NULL; } -Object* Map::GetDescriptorContents(String* sentinel_name, - bool* safe_to_add_transition) { - // Get the cached index for the descriptors lookup, or find and cache it. - DescriptorArray* descriptors = instance_descriptors(); - DescriptorLookupCache* cache = GetIsolate()->descriptor_lookup_cache(); - int index = cache->Lookup(descriptors, sentinel_name); - if (index == DescriptorLookupCache::kAbsent) { - index = descriptors->Search(sentinel_name); - cache->Update(descriptors, sentinel_name, index); - } - // If the transition already exists, return its descriptor. - if (index != DescriptorArray::kNotFound) { - PropertyDetails details = descriptors->GetDetails(index); - if (details.type() == ELEMENTS_TRANSITION) { - return descriptors->GetValue(index); - } else { - if (safe_to_add_transition != NULL) { - *safe_to_add_transition = false; - } - } - } - return NULL; -} +static MaybeObject* AddMissingElementsTransitions(Map* map, + ElementsKind to_kind) { + ASSERT(IsFastElementsKind(map->elements_kind())); + int index = GetSequenceIndexFromFastElementsKind(map->elements_kind()); + int to_index = IsFastElementsKind(to_kind) + ? GetSequenceIndexFromFastElementsKind(to_kind) + : GetSequenceIndexFromFastElementsKind(TERMINAL_FAST_ELEMENTS_KIND); + ASSERT(index <= to_index); -Map* Map::LookupElementsTransitionMap(ElementsKind elements_kind, - bool* safe_to_add_transition) { - // Special case: indirect SMI->FAST transition (cf. comment in - // AddElementsTransition()). - if (this->elements_kind() == FAST_SMI_ONLY_ELEMENTS && - elements_kind == FAST_ELEMENTS) { - Map* double_map = this->LookupElementsTransitionMap(FAST_DOUBLE_ELEMENTS, - safe_to_add_transition); - if (double_map == NULL) return double_map; - return double_map->LookupElementsTransitionMap(FAST_ELEMENTS, - safe_to_add_transition); - } - Object* descriptor_contents = GetDescriptorContents( - elements_transition_sentinel_name(), safe_to_add_transition); - if (descriptor_contents != NULL) { - Map* maybe_transition_map = - GetElementsTransitionMapFromDescriptor(descriptor_contents, - elements_kind); - ASSERT(maybe_transition_map == NULL || maybe_transition_map->IsMap()); - return maybe_transition_map; + Map* current_map = map; + + for (; index < to_index; ++index) { + ElementsKind next_kind = GetFastElementsKindFromSequenceIndex(index + 1); + MaybeObject* maybe_next_map = + current_map->CopyAsElementsKind(next_kind, INSERT_TRANSITION); + if (!maybe_next_map->To(¤t_map)) return maybe_next_map; } - return NULL; -} + // In case we are exiting the fast elements kind system, just add the map in + // the end. + if (!IsFastElementsKind(to_kind)) { + MaybeObject* maybe_next_map = + current_map->CopyAsElementsKind(to_kind, INSERT_TRANSITION); + if (!maybe_next_map->To(¤t_map)) return maybe_next_map; + } -MaybeObject* Map::AddElementsTransition(ElementsKind elements_kind, - Map* transitioned_map) { - // The map transition graph should be a tree, therefore the transition - // from SMI to FAST elements is not done directly, but by going through - // DOUBLE elements first. - if (this->elements_kind() == FAST_SMI_ONLY_ELEMENTS && - elements_kind == FAST_ELEMENTS) { - bool safe_to_add = true; - Map* double_map = this->LookupElementsTransitionMap( - FAST_DOUBLE_ELEMENTS, &safe_to_add); - // This method is only called when safe_to_add_transition has been found - // to be true earlier. - ASSERT(safe_to_add); - - if (double_map == NULL) { - MaybeObject* maybe_map = this->CopyDropTransitions(); - if (!maybe_map->To(&double_map)) return maybe_map; - double_map->set_elements_kind(FAST_DOUBLE_ELEMENTS); - MaybeObject* maybe_double_transition = this->AddElementsTransition( - FAST_DOUBLE_ELEMENTS, double_map); - if (maybe_double_transition->IsFailure()) return maybe_double_transition; - } - return double_map->AddElementsTransition(FAST_ELEMENTS, transitioned_map); - } - - bool safe_to_add_transition = true; - Object* descriptor_contents = GetDescriptorContents( - elements_transition_sentinel_name(), &safe_to_add_transition); - // This method is only called when safe_to_add_transition has been found - // to be true earlier. - ASSERT(safe_to_add_transition); - MaybeObject* maybe_new_contents = - AddElementsTransitionMapToDescriptor(descriptor_contents, - transitioned_map); - Object* new_contents; - if (!maybe_new_contents->ToObject(&new_contents)) { - return maybe_new_contents; - } - - ElementsTransitionDescriptor desc(elements_transition_sentinel_name(), - new_contents); - Object* new_descriptors; - MaybeObject* maybe_new_descriptors = - instance_descriptors()->CopyInsert(&desc, KEEP_TRANSITIONS); - if (!maybe_new_descriptors->ToObject(&new_descriptors)) { - return maybe_new_descriptors; - } - set_instance_descriptors(DescriptorArray::cast(new_descriptors)); - transitioned_map->SetBackPointer(this); - return this; + ASSERT(current_map->elements_kind() == to_kind); + return current_map; } @@ -2429,56 +2382,39 @@ Handle<Map> JSObject::GetElementsTransitionMap(Handle<JSObject> object, MaybeObject* JSObject::GetElementsTransitionMapSlow(ElementsKind to_kind) { - Map* current_map = map(); - ElementsKind from_kind = current_map->elements_kind(); + Map* start_map = map(); + ElementsKind from_kind = start_map->elements_kind(); - if (from_kind == to_kind) return current_map; + if (from_kind == to_kind) { + return start_map; + } - // Only objects with FastProperties can have DescriptorArrays and can track - // element-related maps. Also don't add descriptors to maps that are shared. - bool safe_to_add_transition = HasFastProperties() && - !current_map->IsUndefined() && - !current_map->is_shared(); + bool allow_store_transition = + // Only remember the map transition if the object's map is NOT equal to + // the global object_function's map and there is not an already existing + // non-matching element transition. + (GetIsolate()->empty_object_map() != map()) && + !start_map->IsUndefined() && !start_map->is_shared() && + IsFastElementsKind(from_kind); - // Prevent long chains of DICTIONARY -> FAST_ELEMENTS maps caused by objects - // with elements that switch back and forth between dictionary and fast - // element mode. - if (from_kind == DICTIONARY_ELEMENTS && to_kind == FAST_ELEMENTS) { - safe_to_add_transition = false; + // Only store fast element maps in ascending generality. + if (IsFastElementsKind(to_kind)) { + allow_store_transition &= + IsTransitionableFastElementsKind(from_kind) && + IsMoreGeneralElementsKindTransition(from_kind, to_kind); } - if (safe_to_add_transition) { - // It's only safe to manipulate the descriptor array if it would be - // safe to add a transition. - Map* maybe_transition_map = current_map->LookupElementsTransitionMap( - to_kind, &safe_to_add_transition); - if (maybe_transition_map != NULL) { - return maybe_transition_map; - } + if (!allow_store_transition) { + return start_map->CopyAsElementsKind(to_kind, OMIT_TRANSITION); } - Map* new_map = NULL; + Map* closest_map = FindClosestElementsTransition(start_map, to_kind); - // No transition to an existing map for the given ElementsKind. Make a new - // one. - { MaybeObject* maybe_map = current_map->CopyDropTransitions(); - if (!maybe_map->To(&new_map)) return maybe_map; + if (closest_map->elements_kind() == to_kind) { + return closest_map; } - new_map->set_elements_kind(to_kind); - - // Only remember the map transition if the object's map is NOT equal to the - // global object_function's map and there is not an already existing - // non-matching element transition. - Context* global_context = GetIsolate()->context()->global_context(); - bool allow_map_transition = safe_to_add_transition && - (global_context->object_function()->map() != map()); - if (allow_map_transition) { - MaybeObject* maybe_transition = - current_map->AddElementsTransition(to_kind, new_map); - if (maybe_transition->IsFailure()) return maybe_transition; - } - return new_map; + return AddMissingElementsTransitions(closest_map, to_kind); } @@ -2493,47 +2429,47 @@ void JSObject::LocalLookupRealNamedProperty(String* name, } if (HasFastProperties()) { - LookupInDescriptor(name, result); - if (result->IsFound()) { - // A property, a map transition or a null descriptor was found. - // We return all of these result types because - // LocalLookupRealNamedProperty is used when setting properties - // where map transitions and null descriptors are handled. - ASSERT(result->holder() == this && result->type() != NORMAL); - // Disallow caching for uninitialized constants. These can only - // occur as fields. - if (result->IsReadOnly() && result->type() == FIELD && - FastPropertyAt(result->GetFieldIndex())->IsTheHole()) { - result->DisallowCaching(); - } - return; + map()->LookupDescriptor(this, name, result); + // A property or a map transition was found. We return all of these result + // types because LocalLookupRealNamedProperty is used when setting + // properties where map transitions are handled. + ASSERT(!result->IsFound() || + (result->holder() == this && result->IsFastPropertyType())); + // Disallow caching for uninitialized constants. These can only + // occur as fields. + if (result->IsField() && + result->IsReadOnly() && + FastPropertyAt(result->GetFieldIndex())->IsTheHole()) { + result->DisallowCaching(); } - } else { - int entry = property_dictionary()->FindEntry(name); - if (entry != StringDictionary::kNotFound) { - Object* value = property_dictionary()->ValueAt(entry); - if (IsGlobalObject()) { - PropertyDetails d = property_dictionary()->DetailsAt(entry); - if (d.IsDeleted()) { - result->NotFound(); - return; - } - value = JSGlobalPropertyCell::cast(value)->value(); + return; + } + + int entry = property_dictionary()->FindEntry(name); + if (entry != StringDictionary::kNotFound) { + Object* value = property_dictionary()->ValueAt(entry); + if (IsGlobalObject()) { + PropertyDetails d = property_dictionary()->DetailsAt(entry); + if (d.IsDeleted()) { + result->NotFound(); + return; } - // Make sure to disallow caching for uninitialized constants - // found in the dictionary-mode objects. - if (value->IsTheHole()) result->DisallowCaching(); - result->DictionaryResult(this, entry); - return; + value = JSGlobalPropertyCell::cast(value)->value(); } + // Make sure to disallow caching for uninitialized constants + // found in the dictionary-mode objects. + if (value->IsTheHole()) result->DisallowCaching(); + result->DictionaryResult(this, entry); + return; } + result->NotFound(); } void JSObject::LookupRealNamedProperty(String* name, LookupResult* result) { LocalLookupRealNamedProperty(name, result); - if (result->IsProperty()) return; + if (result->IsFound()) return; LookupRealNamedPropertyInPrototypes(name, result); } @@ -2544,9 +2480,13 @@ void JSObject::LookupRealNamedPropertyInPrototypes(String* name, Heap* heap = GetHeap(); for (Object* pt = GetPrototype(); pt != heap->null_value(); - pt = JSObject::cast(pt)->GetPrototype()) { + pt = pt->GetPrototype()) { + if (pt->IsJSProxy()) { + return result->HandlerResult(JSProxy::cast(pt)); + } JSObject::cast(pt)->LocalLookupRealNamedProperty(name, result); - if (result->IsProperty() && (result->type() != INTERCEPTOR)) return; + ASSERT(!(result->IsFound() && result->type() == INTERCEPTOR)); + if (result->IsFound()) return; } result->NotFound(); } @@ -2560,7 +2500,7 @@ MaybeObject* JSObject::SetPropertyWithFailedAccessCheck( bool check_prototype, StrictModeFlag strict_mode) { if (check_prototype && !result->IsProperty()) { - LookupCallbackSetterInPrototypes(name, result); + LookupRealNamedPropertyInPrototypes(name, result); } if (result->IsProperty()) { @@ -2613,13 +2553,14 @@ MaybeObject* JSReceiver::SetProperty(LookupResult* result, String* key, Object* value, PropertyAttributes attributes, - StrictModeFlag strict_mode) { - if (result->IsFound() && result->type() == HANDLER) { + StrictModeFlag strict_mode, + JSReceiver::StoreFromKeyed store_mode) { + if (result->IsHandler()) { return result->proxy()->SetPropertyWithHandler( - key, value, attributes, strict_mode); + this, key, value, attributes, strict_mode); } else { return JSObject::cast(this)->SetPropertyForResult( - result, key, value, attributes, strict_mode); + result, key, value, attributes, strict_mode, store_mode); } } @@ -2633,20 +2574,21 @@ bool JSProxy::HasPropertyWithHandler(String* name_raw) { Handle<Object> args[] = { name }; Handle<Object> result = CallTrap( "has", isolate->derived_has_trap(), ARRAY_SIZE(args), args); - if (isolate->has_pending_exception()) return Failure::Exception(); + if (isolate->has_pending_exception()) return false; return result->ToBoolean()->IsTrue(); } MUST_USE_RESULT MaybeObject* JSProxy::SetPropertyWithHandler( + JSReceiver* receiver_raw, String* name_raw, Object* value_raw, PropertyAttributes attributes, StrictModeFlag strict_mode) { Isolate* isolate = GetIsolate(); HandleScope scope(isolate); - Handle<Object> receiver(this); + Handle<JSReceiver> receiver(receiver_raw); Handle<Object> name(name_raw); Handle<Object> value(value_raw); @@ -2658,77 +2600,92 @@ MUST_USE_RESULT MaybeObject* JSProxy::SetPropertyWithHandler( } -MUST_USE_RESULT MaybeObject* JSProxy::SetPropertyWithHandlerIfDefiningSetter( +MUST_USE_RESULT MaybeObject* JSProxy::SetPropertyViaPrototypesWithHandler( + JSReceiver* receiver_raw, String* name_raw, Object* value_raw, PropertyAttributes attributes, StrictModeFlag strict_mode, - bool* found) { - *found = true; // except where defined otherwise... - Isolate* isolate = GetHeap()->isolate(); + bool* done) { + Isolate* isolate = GetIsolate(); Handle<JSProxy> proxy(this); - Handle<Object> handler(this->handler()); // Trap might morph proxy. + Handle<JSReceiver> receiver(receiver_raw); Handle<String> name(name_raw); Handle<Object> value(value_raw); + Handle<Object> handler(this->handler()); // Trap might morph proxy. + + *done = true; // except where redefined... Handle<Object> args[] = { name }; Handle<Object> result = proxy->CallTrap( "getPropertyDescriptor", Handle<Object>(), ARRAY_SIZE(args), args); if (isolate->has_pending_exception()) return Failure::Exception(); - if (!result->IsUndefined()) { - // The proxy handler cares about this property. - // Check whether it is virtualized as an accessor. - // Emulate [[GetProperty]] semantics for proxies. - bool has_pending_exception; - Handle<Object> argv[] = { result }; - Handle<Object> desc = - Execution::Call(isolate->to_complete_property_descriptor(), result, - ARRAY_SIZE(argv), argv, &has_pending_exception); - if (has_pending_exception) return Failure::Exception(); - - Handle<String> conf_name = - isolate->factory()->LookupAsciiSymbol("configurable_"); - Handle<Object> configurable(v8::internal::GetProperty(desc, conf_name)); - ASSERT(!isolate->has_pending_exception()); - if (configurable->IsFalse()) { - Handle<String> trap = - isolate->factory()->LookupAsciiSymbol("getPropertyDescriptor"); - Handle<Object> args[] = { handler, trap, name }; - Handle<Object> error = isolate->factory()->NewTypeError( - "proxy_prop_not_configurable", HandleVector(args, ARRAY_SIZE(args))); - return isolate->Throw(*error); - } - ASSERT(configurable->IsTrue()); + if (result->IsUndefined()) { + *done = false; + return GetHeap()->the_hole_value(); + } + + // Emulate [[GetProperty]] semantics for proxies. + bool has_pending_exception; + Handle<Object> argv[] = { result }; + Handle<Object> desc = + Execution::Call(isolate->to_complete_property_descriptor(), result, + ARRAY_SIZE(argv), argv, &has_pending_exception); + if (has_pending_exception) return Failure::Exception(); - // Check for AccessorDescriptor. - Handle<String> set_name = isolate->factory()->LookupAsciiSymbol("set_"); - Handle<Object> setter(v8::internal::GetProperty(desc, set_name)); + // [[GetProperty]] requires to check that all properties are configurable. + Handle<String> configurable_name = + isolate->factory()->LookupAsciiSymbol("configurable_"); + Handle<Object> configurable( + v8::internal::GetProperty(desc, configurable_name)); + ASSERT(!isolate->has_pending_exception()); + ASSERT(configurable->IsTrue() || configurable->IsFalse()); + if (configurable->IsFalse()) { + Handle<String> trap = + isolate->factory()->LookupAsciiSymbol("getPropertyDescriptor"); + Handle<Object> args[] = { handler, trap, name }; + Handle<Object> error = isolate->factory()->NewTypeError( + "proxy_prop_not_configurable", HandleVector(args, ARRAY_SIZE(args))); + return isolate->Throw(*error); + } + ASSERT(configurable->IsTrue()); + + // Check for DataDescriptor. + Handle<String> hasWritable_name = + isolate->factory()->LookupAsciiSymbol("hasWritable_"); + Handle<Object> hasWritable(v8::internal::GetProperty(desc, hasWritable_name)); + ASSERT(!isolate->has_pending_exception()); + ASSERT(hasWritable->IsTrue() || hasWritable->IsFalse()); + if (hasWritable->IsTrue()) { + Handle<String> writable_name = + isolate->factory()->LookupAsciiSymbol("writable_"); + Handle<Object> writable(v8::internal::GetProperty(desc, writable_name)); ASSERT(!isolate->has_pending_exception()); - if (!setter->IsUndefined()) { - // We have a setter -- invoke it. - // TODO(rossberg): nicer would be to cast to some JSCallable here... - return proxy->SetPropertyWithDefinedSetter( - JSReceiver::cast(*setter), *value); - } else { - Handle<String> get_name = isolate->factory()->LookupAsciiSymbol("get_"); - Handle<Object> getter(v8::internal::GetProperty(desc, get_name)); - ASSERT(!isolate->has_pending_exception()); - if (!getter->IsUndefined()) { - // We have a getter but no setter -- the property may not be - // written. In strict mode, throw an error. - if (strict_mode == kNonStrictMode) return *value; - Handle<Object> args[] = { name, proxy }; - Handle<Object> error = isolate->factory()->NewTypeError( - "no_setter_in_callback", HandleVector(args, ARRAY_SIZE(args))); - return isolate->Throw(*error); - } - } - // Fall-through. + ASSERT(writable->IsTrue() || writable->IsFalse()); + *done = writable->IsFalse(); + if (!*done) return GetHeap()->the_hole_value(); + if (strict_mode == kNonStrictMode) return *value; + Handle<Object> args[] = { name, receiver }; + Handle<Object> error = isolate->factory()->NewTypeError( + "strict_read_only_property", HandleVector(args, ARRAY_SIZE(args))); + return isolate->Throw(*error); } - // The proxy does not define the property as an accessor. - *found = false; - return *value; + // We have an AccessorDescriptor. + Handle<String> set_name = isolate->factory()->LookupAsciiSymbol("set_"); + Handle<Object> setter(v8::internal::GetProperty(desc, set_name)); + ASSERT(!isolate->has_pending_exception()); + if (!setter->IsUndefined()) { + // TODO(rossberg): nicer would be to cast to some JSCallable here... + return receiver->SetPropertyWithDefinedSetter( + JSReceiver::cast(*setter), *value); + } + + if (strict_mode == kNonStrictMode) return *value; + Handle<Object> args2[] = { name, proxy }; + Handle<Object> error = isolate->factory()->NewTypeError( + "no_setter_in_callback", HandleVector(args2, ARRAY_SIZE(args2))); + return isolate->Throw(*error); } @@ -2736,7 +2693,7 @@ MUST_USE_RESULT MaybeObject* JSProxy::DeletePropertyWithHandler( String* name_raw, DeleteMode mode) { Isolate* isolate = GetIsolate(); HandleScope scope(isolate); - Handle<Object> receiver(this); + Handle<JSProxy> receiver(this); Handle<Object> name(name_raw); Handle<Object> args[] = { name }; @@ -2746,8 +2703,9 @@ MUST_USE_RESULT MaybeObject* JSProxy::DeletePropertyWithHandler( Object* bool_result = result->ToBoolean(); if (mode == STRICT_DELETION && bool_result == GetHeap()->false_value()) { + Handle<Object> handler(receiver->handler()); Handle<String> trap_name = isolate->factory()->LookupAsciiSymbol("delete"); - Handle<Object> args[] = { Handle<Object>(handler()), trap_name }; + Handle<Object> args[] = { handler, trap_name }; Handle<Object> error = isolate->factory()->NewTypeError( "handler_failed", HandleVector(args, ARRAY_SIZE(args))); isolate->Throw(*error); @@ -2821,12 +2779,14 @@ MUST_USE_RESULT PropertyAttributes JSProxy::GetPropertyAttributeWithHandler( MUST_USE_RESULT PropertyAttributes JSProxy::GetElementAttributeWithHandler( - JSReceiver* receiver, + JSReceiver* receiver_raw, uint32_t index) { Isolate* isolate = GetIsolate(); HandleScope scope(isolate); + Handle<JSProxy> proxy(this); + Handle<JSReceiver> receiver(receiver_raw); Handle<String> name = isolate->factory()->Uint32ToString(index); - return GetPropertyAttributeWithHandler(receiver, *name); + return proxy->GetPropertyAttributeWithHandler(*receiver, *name); } @@ -2850,7 +2810,7 @@ void JSProxy::Fix() { Object* hash; if (maybe_hash->To<Object>(&hash) && hash->IsSmi()) { Handle<JSObject> new_self(JSObject::cast(*self)); - isolate->factory()->SetIdentityHash(new_self, hash); + isolate->factory()->SetIdentityHash(new_self, Smi::cast(hash)); } } @@ -2882,11 +2842,20 @@ MUST_USE_RESULT Handle<Object> JSProxy::CallTrap(const char* name, } -MaybeObject* JSObject::SetPropertyForResult(LookupResult* result, - String* name, - Object* value, +void JSObject::AddFastPropertyUsingMap(Handle<JSObject> object, + Handle<Map> map) { + CALL_HEAP_FUNCTION_VOID( + object->GetIsolate(), + object->AddFastPropertyUsingMap(*map)); +} + + +MaybeObject* JSObject::SetPropertyForResult(LookupResult* lookup, + String* name_raw, + Object* value_raw, PropertyAttributes attributes, - StrictModeFlag strict_mode) { + StrictModeFlag strict_mode, + StoreFromKeyed store_mode) { Heap* heap = GetHeap(); // Make sure that the top context does not change when doing callbacks or // interceptor calls. @@ -2895,116 +2864,156 @@ MaybeObject* JSObject::SetPropertyForResult(LookupResult* result, // Optimization for 2-byte strings often used as keys in a decompression // dictionary. We make these short keys into symbols to avoid constantly // reallocating them. - if (!name->IsSymbol() && name->length() <= 2) { + if (!name_raw->IsSymbol() && name_raw->length() <= 2) { Object* symbol_version; - { MaybeObject* maybe_symbol_version = heap->LookupSymbol(name); + { MaybeObject* maybe_symbol_version = heap->LookupSymbol(name_raw); if (maybe_symbol_version->ToObject(&symbol_version)) { - name = String::cast(symbol_version); + name_raw = String::cast(symbol_version); } } } // Check access rights if needed. if (IsAccessCheckNeeded()) { - if (!heap->isolate()->MayNamedAccess(this, name, v8::ACCESS_SET)) { + if (!heap->isolate()->MayNamedAccess(this, name_raw, v8::ACCESS_SET)) { return SetPropertyWithFailedAccessCheck( - result, name, value, true, strict_mode); + lookup, name_raw, value_raw, true, strict_mode); } } if (IsJSGlobalProxy()) { Object* proto = GetPrototype(); - if (proto->IsNull()) return value; + if (proto->IsNull()) return value_raw; ASSERT(proto->IsJSGlobalObject()); return JSObject::cast(proto)->SetPropertyForResult( - result, name, value, attributes, strict_mode); + lookup, name_raw, value_raw, attributes, strict_mode, store_mode); } - if (!result->IsProperty() && !IsJSContextExtensionObject()) { - bool found = false; - MaybeObject* result_object; - result_object = SetPropertyWithCallbackSetterInPrototypes(name, - value, - attributes, - &found, - strict_mode); - if (found) return result_object; - } + // From this point on everything needs to be handlified, because + // SetPropertyViaPrototypes might call back into JavaScript. + HandleScope scope(GetIsolate()); + Handle<JSObject> self(this); + Handle<String> name(name_raw); + Handle<Object> value(value_raw); - // At this point, no GC should have happened, as this would invalidate - // 'result', which we cannot handlify! + if (!lookup->IsProperty() && !self->IsJSContextExtensionObject()) { + bool done = false; + MaybeObject* result_object = self->SetPropertyViaPrototypes( + *name, *value, attributes, strict_mode, &done); + if (done) return result_object; + } - if (!result->IsFound()) { + if (!lookup->IsFound()) { // Neither properties nor transitions found. - return AddProperty(name, value, attributes, strict_mode); + return self->AddProperty( + *name, *value, attributes, strict_mode, store_mode); } - if (result->IsReadOnly() && result->IsProperty()) { + + if (lookup->IsProperty() && lookup->IsReadOnly()) { if (strict_mode == kStrictMode) { - Handle<JSObject> self(this); - Handle<String> hname(name); - Handle<Object> args[] = { hname, self }; + Handle<Object> args[] = { name, self }; return heap->isolate()->Throw(*heap->isolate()->factory()->NewTypeError( "strict_read_only_property", HandleVector(args, ARRAY_SIZE(args)))); } else { - return value; + return *value; } } + + Handle<Object> old_value(heap->the_hole_value()); + if (FLAG_harmony_observation && map()->is_observed()) { + old_value = handle(lookup->GetLazyValue()); + } + // This is a real property that is not read-only, or it is a // transition or null descriptor and there are no setters in the prototypes. - switch (result->type()) { + MaybeObject* result = *value; + switch (lookup->type()) { case NORMAL: - return SetNormalizedProperty(result, value); + result = self->SetNormalizedProperty(lookup, *value); + break; case FIELD: - return FastPropertyAtPut(result->GetFieldIndex(), value); - case MAP_TRANSITION: - if (attributes == result->GetAttributes()) { - // Only use map transition if the attributes match. - return AddFastPropertyUsingMap(result->GetTransitionMap(), - name, - value); - } - return ConvertDescriptorToField(name, value, attributes); + result = self->FastPropertyAtPut(lookup->GetFieldIndex(), *value); + break; case CONSTANT_FUNCTION: // Only replace the function if necessary. - if (value == result->GetConstantFunction()) return value; + if (*value == lookup->GetConstantFunction()) return *value; // Preserve the attributes of this existing property. - attributes = result->GetAttributes(); - return ConvertDescriptorToField(name, value, attributes); - case CALLBACKS: - return SetPropertyWithCallback(result->GetCallbackObject(), - name, - value, - result->holder(), - strict_mode); + attributes = lookup->GetAttributes(); + result = self->ConvertDescriptorToField(*name, *value, attributes); + break; + case CALLBACKS: { + Object* callback_object = lookup->GetCallbackObject(); + return self->SetPropertyWithCallback(callback_object, + *name, + *value, + lookup->holder(), + strict_mode); + } case INTERCEPTOR: - return SetPropertyWithInterceptor(name, value, attributes, strict_mode); - case CONSTANT_TRANSITION: { - // If the same constant function is being added we can simply - // transition to the target map. - Map* target_map = result->GetTransitionMap(); - DescriptorArray* target_descriptors = target_map->instance_descriptors(); - int number = target_descriptors->SearchWithCache(name); - ASSERT(number != DescriptorArray::kNotFound); - ASSERT(target_descriptors->GetType(number) == CONSTANT_FUNCTION); - JSFunction* function = - JSFunction::cast(target_descriptors->GetValue(number)); - if (value == function) { - set_map(target_map); - return value; + result = self->SetPropertyWithInterceptor(*name, + *value, + attributes, + strict_mode); + break; + case TRANSITION: { + Map* transition_map = lookup->GetTransitionTarget(); + int descriptor = transition_map->LastAdded(); + + DescriptorArray* descriptors = transition_map->instance_descriptors(); + PropertyDetails details = descriptors->GetDetails(descriptor); + + if (details.type() == FIELD) { + if (attributes == details.attributes()) { + int field_index = descriptors->GetFieldIndex(descriptor); + result = self->AddFastPropertyUsingMap(transition_map, + *name, + *value, + field_index); + } else { + result = self->ConvertDescriptorToField(*name, *value, attributes); + } + } else if (details.type() == CALLBACKS) { + result = self->ConvertDescriptorToField(*name, *value, attributes); + } else { + ASSERT(details.type() == CONSTANT_FUNCTION); + + Object* constant_function = descriptors->GetValue(descriptor); + if (constant_function == *value) { + // If the same constant function is being added we can simply + // transition to the target map. + self->set_map(transition_map); + result = constant_function; + } else { + // Otherwise, replace with a map transition to a new map with a FIELD, + // even if the value is a constant function. + result = self->ConvertTransitionToMapTransition( + lookup->GetTransitionIndex(), *name, *value, attributes); + } } - // Otherwise, replace with a MAP_TRANSITION to a new map with a - // FIELD, even if the value is a constant function. - return ConvertDescriptorToFieldAndMapTransition(name, value, attributes); + break; } - case NULL_DESCRIPTOR: - case ELEMENTS_TRANSITION: - return ConvertDescriptorToFieldAndMapTransition(name, value, attributes); case HANDLER: + case NONEXISTENT: UNREACHABLE(); - return value; } - UNREACHABLE(); // keep the compiler happy - return value; + + Handle<Object> hresult; + if (!result->ToHandle(&hresult)) return result; + + if (FLAG_harmony_observation && map()->is_observed()) { + if (lookup->IsTransition()) { + EnqueueChangeRecord(self, "new", name, old_value); + } else { + LookupResult new_lookup(self->GetIsolate()); + self->LocalLookup(*name, &new_lookup); + ASSERT(!new_lookup.GetLazyValue()->IsTheHole()); + if (!new_lookup.GetLazyValue()->SameValue(*old_value)) { + EnqueueChangeRecord(self, "updated", name, old_value); + } + } + } + + return *hresult; } @@ -3030,21 +3039,22 @@ Handle<Object> JSObject::SetLocalPropertyIgnoreAttributes( MaybeObject* JSObject::SetLocalPropertyIgnoreAttributes( - String* name, - Object* value, + String* name_raw, + Object* value_raw, PropertyAttributes attributes) { // Make sure that the top context does not change when doing callbacks or // interceptor calls. AssertNoContextChange ncc; Isolate* isolate = GetIsolate(); - LookupResult result(isolate); - LocalLookup(name, &result); + LookupResult lookup(isolate); + LocalLookup(name_raw, &lookup); + if (!lookup.IsFound()) map()->LookupTransition(this, name_raw, &lookup); // Check access rights if needed. if (IsAccessCheckNeeded()) { - if (!isolate->MayNamedAccess(this, name, v8::ACCESS_SET)) { - return SetPropertyWithFailedAccessCheck(&result, - name, - value, + if (!isolate->MayNamedAccess(this, name_raw, v8::ACCESS_SET)) { + return SetPropertyWithFailedAccessCheck(&lookup, + name_raw, + value_raw, false, kNonStrictMode); } @@ -3052,58 +3062,109 @@ MaybeObject* JSObject::SetLocalPropertyIgnoreAttributes( if (IsJSGlobalProxy()) { Object* proto = GetPrototype(); - if (proto->IsNull()) return value; + if (proto->IsNull()) return value_raw; ASSERT(proto->IsJSGlobalObject()); return JSObject::cast(proto)->SetLocalPropertyIgnoreAttributes( - name, - value, + name_raw, + value_raw, attributes); } // Check for accessor in prototype chain removed here in clone. - if (!result.IsFound()) { + if (!lookup.IsFound()) { // Neither properties nor transitions found. - return AddProperty(name, value, attributes, kNonStrictMode); + return AddProperty(name_raw, value_raw, attributes, kNonStrictMode); } - PropertyDetails details = PropertyDetails(attributes, NORMAL); + // From this point on everything needs to be handlified. + HandleScope scope(GetIsolate()); + Handle<JSObject> self(this); + Handle<String> name(name_raw); + Handle<Object> value(value_raw); + + Handle<Object> old_value(isolate->heap()->the_hole_value()); + PropertyAttributes old_attributes = ABSENT; + if (FLAG_harmony_observation && map()->is_observed()) { + old_value = handle(lookup.GetLazyValue()); + old_attributes = lookup.GetAttributes(); + } // Check of IsReadOnly removed from here in clone. - switch (result.type()) { - case NORMAL: - return SetNormalizedProperty(name, value, details); + MaybeObject* result = *value; + switch (lookup.type()) { + case NORMAL: { + PropertyDetails details = PropertyDetails(attributes, NORMAL); + result = self->SetNormalizedProperty(*name, *value, details); + break; + } case FIELD: - return FastPropertyAtPut(result.GetFieldIndex(), value); - case MAP_TRANSITION: - if (attributes == result.GetAttributes()) { - // Only use map transition if the attributes match. - return AddFastPropertyUsingMap(result.GetTransitionMap(), - name, - value); - } - return ConvertDescriptorToField(name, value, attributes); + result = self->FastPropertyAtPut(lookup.GetFieldIndex(), *value); + break; case CONSTANT_FUNCTION: // Only replace the function if necessary. - if (value == result.GetConstantFunction()) return value; - // Preserve the attributes of this existing property. - attributes = result.GetAttributes(); - return ConvertDescriptorToField(name, value, attributes); + if (*value != lookup.GetConstantFunction()) { + // Preserve the attributes of this existing property. + attributes = lookup.GetAttributes(); + result = self->ConvertDescriptorToField(*name, *value, attributes); + } + break; case CALLBACKS: case INTERCEPTOR: // Override callback in clone - return ConvertDescriptorToField(name, value, attributes); - case CONSTANT_TRANSITION: - // Replace with a MAP_TRANSITION to a new map with a FIELD, even - // if the value is a function. - return ConvertDescriptorToFieldAndMapTransition(name, value, attributes); - case NULL_DESCRIPTOR: - case ELEMENTS_TRANSITION: - return ConvertDescriptorToFieldAndMapTransition(name, value, attributes); + result = self->ConvertDescriptorToField(*name, *value, attributes); + break; + case TRANSITION: { + Map* transition_map = lookup.GetTransitionTarget(); + int descriptor = transition_map->LastAdded(); + + DescriptorArray* descriptors = transition_map->instance_descriptors(); + PropertyDetails details = descriptors->GetDetails(descriptor); + + if (details.type() == FIELD) { + if (attributes == details.attributes()) { + int field_index = descriptors->GetFieldIndex(descriptor); + result = self->AddFastPropertyUsingMap( + transition_map, *name, *value, field_index); + } else { + result = self->ConvertDescriptorToField(*name, *value, attributes); + } + } else if (details.type() == CALLBACKS) { + result = self->ConvertDescriptorToField(*name, *value, attributes); + } else { + ASSERT(details.type() == CONSTANT_FUNCTION); + + // Replace transition to CONSTANT FUNCTION with a map transition to a + // new map with a FIELD, even if the value is a function. + result = self->ConvertTransitionToMapTransition( + lookup.GetTransitionIndex(), *name, *value, attributes); + } + break; + } case HANDLER: + case NONEXISTENT: UNREACHABLE(); } - UNREACHABLE(); // keep the compiler happy - return value; + + Handle<Object> hresult; + if (!result->ToHandle(&hresult)) return result; + + if (FLAG_harmony_observation && map()->is_observed()) { + if (lookup.IsTransition()) { + EnqueueChangeRecord(self, "new", name, old_value); + } else { + LookupResult new_lookup(isolate); + self->LocalLookup(*name, &new_lookup); + ASSERT(!new_lookup.GetLazyValue()->IsTheHole()); + if (old_value->IsTheHole() || + new_lookup.GetAttributes() != old_attributes) { + EnqueueChangeRecord(self, "reconfigured", name, old_value); + } else if (!new_lookup.GetLazyValue()->SameValue(*old_value)) { + EnqueueChangeRecord(self, "updated", name, old_value); + } + } + } + + return *hresult; } @@ -3114,7 +3175,7 @@ PropertyAttributes JSObject::GetPropertyAttributePostInterceptor( // Check local property, ignore interceptor. LookupResult result(GetIsolate()); LocalLookupRealNamedProperty(name, &result); - if (result.IsProperty()) return result.GetAttributes(); + if (result.IsFound()) return result.GetAttributes(); if (continue_search) { // Continue searching via the prototype chain. @@ -3184,44 +3245,46 @@ PropertyAttributes JSReceiver::GetPropertyAttributeWithReceiver( String* key) { uint32_t index = 0; if (IsJSObject() && key->AsArrayIndex(&index)) { - return JSObject::cast(this)->HasElementWithReceiver(receiver, index) - ? NONE : ABSENT; + return JSObject::cast(this)->GetElementAttributeWithReceiver( + receiver, index, true); } // Named property. - LookupResult result(GetIsolate()); - Lookup(key, &result); - return GetPropertyAttribute(receiver, &result, key, true); + LookupResult lookup(GetIsolate()); + Lookup(key, &lookup); + return GetPropertyAttributeForResult(receiver, &lookup, key, true); } -PropertyAttributes JSReceiver::GetPropertyAttribute(JSReceiver* receiver, - LookupResult* result, - String* name, - bool continue_search) { +PropertyAttributes JSReceiver::GetPropertyAttributeForResult( + JSReceiver* receiver, + LookupResult* lookup, + String* name, + bool continue_search) { // Check access rights if needed. if (IsAccessCheckNeeded()) { JSObject* this_obj = JSObject::cast(this); Heap* heap = GetHeap(); if (!heap->isolate()->MayNamedAccess(this_obj, name, v8::ACCESS_HAS)) { return this_obj->GetPropertyAttributeWithFailedAccessCheck( - receiver, result, name, continue_search); + receiver, lookup, name, continue_search); } } - if (result->IsProperty()) { - switch (result->type()) { + if (lookup->IsFound()) { + switch (lookup->type()) { case NORMAL: // fall through case FIELD: case CONSTANT_FUNCTION: case CALLBACKS: - return result->GetAttributes(); + return lookup->GetAttributes(); case HANDLER: { - return JSProxy::cast(result->proxy())->GetPropertyAttributeWithHandler( + return JSProxy::cast(lookup->proxy())->GetPropertyAttributeWithHandler( receiver, name); } case INTERCEPTOR: - return result->holder()->GetPropertyAttributeWithInterceptor( + return lookup->holder()->GetPropertyAttributeWithInterceptor( JSObject::cast(receiver), name, continue_search); - default: + case TRANSITION: + case NONEXISTENT: UNREACHABLE(); } } @@ -3233,13 +3296,113 @@ PropertyAttributes JSReceiver::GetLocalPropertyAttribute(String* name) { // Check whether the name is an array index. uint32_t index = 0; if (IsJSObject() && name->AsArrayIndex(&index)) { - if (JSObject::cast(this)->HasLocalElement(index)) return NONE; - return ABSENT; + return GetLocalElementAttribute(index); } // Named property. - LookupResult result(GetIsolate()); - LocalLookup(name, &result); - return GetPropertyAttribute(this, &result, name, false); + LookupResult lookup(GetIsolate()); + LocalLookup(name, &lookup); + return GetPropertyAttributeForResult(this, &lookup, name, false); +} + + +PropertyAttributes JSObject::GetElementAttributeWithReceiver( + JSReceiver* receiver, uint32_t index, bool continue_search) { + Isolate* isolate = GetIsolate(); + + // Check access rights if needed. + if (IsAccessCheckNeeded()) { + if (!isolate->MayIndexedAccess(this, index, v8::ACCESS_HAS)) { + isolate->ReportFailedAccessCheck(this, v8::ACCESS_HAS); + return ABSENT; + } + } + + if (IsJSGlobalProxy()) { + Object* proto = GetPrototype(); + if (proto->IsNull()) return ABSENT; + ASSERT(proto->IsJSGlobalObject()); + return JSObject::cast(proto)->GetElementAttributeWithReceiver( + receiver, index, continue_search); + } + + // Check for lookup interceptor except when bootstrapping. + if (HasIndexedInterceptor() && !isolate->bootstrapper()->IsActive()) { + return GetElementAttributeWithInterceptor(receiver, index, continue_search); + } + + return GetElementAttributeWithoutInterceptor( + receiver, index, continue_search); +} + + +PropertyAttributes JSObject::GetElementAttributeWithInterceptor( + JSReceiver* receiver, uint32_t index, bool continue_search) { + Isolate* isolate = GetIsolate(); + // Make sure that the top context does not change when doing + // callbacks or interceptor calls. + AssertNoContextChange ncc; + HandleScope scope(isolate); + Handle<InterceptorInfo> interceptor(GetIndexedInterceptor()); + Handle<JSReceiver> hreceiver(receiver); + Handle<JSObject> holder(this); + CustomArguments args(isolate, interceptor->data(), receiver, this); + v8::AccessorInfo info(args.end()); + if (!interceptor->query()->IsUndefined()) { + v8::IndexedPropertyQuery query = + v8::ToCData<v8::IndexedPropertyQuery>(interceptor->query()); + LOG(isolate, + ApiIndexedPropertyAccess("interceptor-indexed-has", this, index)); + v8::Handle<v8::Integer> result; + { + // Leaving JavaScript. + VMState state(isolate, EXTERNAL); + result = query(index, info); + } + if (!result.IsEmpty()) + return static_cast<PropertyAttributes>(result->Int32Value()); + } else if (!interceptor->getter()->IsUndefined()) { + v8::IndexedPropertyGetter getter = + v8::ToCData<v8::IndexedPropertyGetter>(interceptor->getter()); + LOG(isolate, + ApiIndexedPropertyAccess("interceptor-indexed-get-has", this, index)); + v8::Handle<v8::Value> result; + { + // Leaving JavaScript. + VMState state(isolate, EXTERNAL); + result = getter(index, info); + } + if (!result.IsEmpty()) return DONT_ENUM; + } + + return holder->GetElementAttributeWithoutInterceptor( + *hreceiver, index, continue_search); +} + + +PropertyAttributes JSObject::GetElementAttributeWithoutInterceptor( + JSReceiver* receiver, uint32_t index, bool continue_search) { + Isolate* isolate = GetIsolate(); + HandleScope scope(isolate); + Handle<JSReceiver> hreceiver(receiver); + Handle<JSObject> holder(this); + PropertyAttributes attr = holder->GetElementsAccessor()->GetAttributes( + *hreceiver, *holder, index); + if (attr != ABSENT) return attr; + + if (holder->IsStringObjectWithCharacterAt(index)) { + return static_cast<PropertyAttributes>(READ_ONLY | DONT_DELETE); + } + + if (!continue_search) return ABSENT; + + Object* pt = holder->GetPrototype(); + if (pt->IsJSProxy()) { + // We need to follow the spec and simulate a call to [[GetOwnProperty]]. + return JSProxy::cast(pt)->GetElementAttributeWithHandler(*hreceiver, index); + } + if (pt->IsNull()) return ABSENT; + return JSObject::cast(pt)->GetElementAttributeWithReceiver( + *hreceiver, index, true); } @@ -3251,20 +3414,27 @@ MaybeObject* NormalizedMapCache::Get(JSObject* obj, Object* result = get(index); if (result->IsMap() && Map::cast(result)->EquivalentToForNormalization(fast, mode)) { -#ifdef DEBUG +#ifdef VERIFY_HEAP if (FLAG_verify_heap) { Map::cast(result)->SharedMapVerify(); } +#endif +#ifdef DEBUG if (FLAG_enable_slow_asserts) { - // The cached map should match newly created normalized map bit-by-bit. + // The cached map should match newly created normalized map bit-by-bit, + // except for the code cache, which can contain some ics which can be + // applied to the shared map. Object* fresh; - { MaybeObject* maybe_fresh = - fast->CopyNormalized(mode, SHARED_NORMALIZED_MAP); - if (maybe_fresh->ToObject(&fresh)) { - ASSERT(memcmp(Map::cast(fresh)->address(), - Map::cast(result)->address(), - Map::kSize) == 0); - } + MaybeObject* maybe_fresh = + fast->CopyNormalized(mode, SHARED_NORMALIZED_MAP); + if (maybe_fresh->ToObject(&fresh)) { + ASSERT(memcmp(Map::cast(fresh)->address(), + Map::cast(result)->address(), + Map::kCodeCacheOffset) == 0); + int offset = Map::kCodeCacheOffset + kPointerSize; + ASSERT(memcmp(Map::cast(fresh)->address() + offset, + Map::cast(result)->address() + offset, + Map::kSize - offset) == 0); } } #endif @@ -3275,6 +3445,7 @@ MaybeObject* NormalizedMapCache::Get(JSObject* obj, fast->CopyNormalized(mode, SHARED_NORMALIZED_MAP); if (!maybe_result->ToObject(&result)) return maybe_result; } + ASSERT(Map::cast(result)->is_dictionary_map()); set(index, result); isolate->counters()->normalized_maps()->Increment(); @@ -3338,24 +3509,25 @@ MaybeObject* JSObject::NormalizeProperties(PropertyNormalizationMode mode, Map* map_of_this = map(); // Allocate new content. - int property_count = map_of_this->NumberOfDescribedProperties(); + int real_size = map_of_this->NumberOfOwnDescriptors(); + int property_count = real_size; if (expected_additional_properties > 0) { property_count += expected_additional_properties; } else { property_count += 2; // Make space for two more properties. } StringDictionary* dictionary; - { MaybeObject* maybe_dictionary = StringDictionary::Allocate(property_count); - if (!maybe_dictionary->To(&dictionary)) return maybe_dictionary; - } + MaybeObject* maybe_dictionary = StringDictionary::Allocate(property_count); + if (!maybe_dictionary->To(&dictionary)) return maybe_dictionary; DescriptorArray* descs = map_of_this->instance_descriptors(); - for (int i = 0; i < descs->number_of_descriptors(); i++) { + for (int i = 0; i < real_size; i++) { PropertyDetails details = descs->GetDetails(i); switch (details.type()) { case CONSTANT_FUNCTION: { - PropertyDetails d = - PropertyDetails(details.attributes(), NORMAL, details.index()); + PropertyDetails d = PropertyDetails(details.attributes(), + NORMAL, + details.descriptor_index()); Object* value = descs->GetConstantFunction(i); MaybeObject* maybe_dictionary = dictionary->Add(descs->GetKey(i), value, d); @@ -3363,8 +3535,9 @@ MaybeObject* JSObject::NormalizeProperties(PropertyNormalizationMode mode, break; } case FIELD: { - PropertyDetails d = - PropertyDetails(details.attributes(), NORMAL, details.index()); + PropertyDetails d = PropertyDetails(details.attributes(), + NORMAL, + details.descriptor_index()); Object* value = FastPropertyAt(descs->GetFieldIndex(i)); MaybeObject* maybe_dictionary = dictionary->Add(descs->GetKey(i), value, d); @@ -3372,26 +3545,19 @@ MaybeObject* JSObject::NormalizeProperties(PropertyNormalizationMode mode, break; } case CALLBACKS: { - if (!descs->IsProperty(i)) break; Object* value = descs->GetCallbacksObject(i); - if (value->IsAccessorPair()) { - MaybeObject* maybe_copy = - AccessorPair::cast(value)->CopyWithoutTransitions(); - if (!maybe_copy->To(&value)) return maybe_copy; - } + details = details.set_pointer(0); MaybeObject* maybe_dictionary = dictionary->Add(descs->GetKey(i), value, details); if (!maybe_dictionary->To(&dictionary)) return maybe_dictionary; break; } - case MAP_TRANSITION: - case CONSTANT_TRANSITION: - case NULL_DESCRIPTOR: case INTERCEPTOR: - case ELEMENTS_TRANSITION: break; case HANDLER: case NORMAL: + case TRANSITION: + case NONEXISTENT: UNREACHABLE(); break; } @@ -3400,15 +3566,14 @@ MaybeObject* JSObject::NormalizeProperties(PropertyNormalizationMode mode, Heap* current_heap = GetHeap(); // Copy the next enumeration index from instance descriptor. - int index = map_of_this->instance_descriptors()->NextEnumerationIndex(); - dictionary->SetNextEnumerationIndex(index); + dictionary->SetNextEnumerationIndex(real_size + 1); Map* new_map; - { MaybeObject* maybe_map = - current_heap->isolate()->context()->global_context()-> - normalized_map_cache()->Get(this, mode); - if (!maybe_map->To(&new_map)) return maybe_map; - } + MaybeObject* maybe_map = + current_heap->isolate()->context()->native_context()-> + normalized_map_cache()->Get(this, mode); + if (!maybe_map->To(&new_map)) return maybe_map; + ASSERT(new_map->is_dictionary_map()); // We have now successfully allocated all the necessary objects. // Changes can now be made with the guarantee that all of them take effect. @@ -3424,9 +3589,7 @@ MaybeObject* JSObject::NormalizeProperties(PropertyNormalizationMode mode, -instance_size_delta); } - set_map(new_map); - new_map->clear_instance_descriptors(); set_properties(dictionary); @@ -3479,8 +3642,7 @@ MaybeObject* JSObject::NormalizeElements() { } if (array->IsDictionary()) return array; - ASSERT(HasFastElements() || - HasFastSmiOnlyElements() || + ASSERT(HasFastSmiOrObjectElements() || HasFastDoubleElements() || HasFastArgumentsElements()); // Compute the effective length and allocate a new backing store. @@ -3515,8 +3677,7 @@ MaybeObject* JSObject::NormalizeElements() { if (!maybe_value_object->ToObject(&value)) return maybe_value_object; } } else { - ASSERT(old_map->has_fast_elements() || - old_map->has_fast_smi_only_elements()); + ASSERT(old_map->has_fast_smi_or_object_elements()); value = FixedArray::cast(array)->get(i); } PropertyDetails details = PropertyDetails(NONE, NORMAL); @@ -3575,7 +3736,7 @@ Smi* JSReceiver::GenerateIdentityHash() { } -MaybeObject* JSObject::SetIdentityHash(Object* hash, CreationFlag flag) { +MaybeObject* JSObject::SetIdentityHash(Smi* hash, CreationFlag flag) { MaybeObject* maybe = SetHiddenProperty(GetHeap()->identity_hash_symbol(), hash); if (maybe->IsFailure()) return maybe; @@ -3621,6 +3782,7 @@ MaybeObject* JSProxy::GetIdentityHash(CreationFlag flag) { Object* JSObject::GetHiddenProperty(String* key) { + ASSERT(key->IsSymbol()); if (IsJSGlobalProxy()) { // For a proxy, use the prototype as target object. Object* proxy_parent = GetPrototype(); @@ -3630,22 +3792,31 @@ Object* JSObject::GetHiddenProperty(String* key) { return JSObject::cast(proxy_parent)->GetHiddenProperty(key); } ASSERT(!IsJSGlobalProxy()); - MaybeObject* hidden_lookup = GetHiddenPropertiesDictionary(false); - ASSERT(!hidden_lookup->IsFailure()); // No failure when passing false as arg. - if (hidden_lookup->ToObjectUnchecked()->IsUndefined()) { - return GetHeap()->undefined_value(); + MaybeObject* hidden_lookup = + GetHiddenPropertiesHashTable(ONLY_RETURN_INLINE_VALUE); + Object* inline_value = hidden_lookup->ToObjectUnchecked(); + + if (inline_value->IsSmi()) { + // Handle inline-stored identity hash. + if (key == GetHeap()->identity_hash_symbol()) { + return inline_value; + } else { + return GetHeap()->undefined_value(); + } } - StringDictionary* dictionary = - StringDictionary::cast(hidden_lookup->ToObjectUnchecked()); - int entry = dictionary->FindEntry(key); - if (entry == StringDictionary::kNotFound) return GetHeap()->undefined_value(); - return dictionary->ValueAt(entry); + + if (inline_value->IsUndefined()) return GetHeap()->undefined_value(); + + ObjectHashTable* hashtable = ObjectHashTable::cast(inline_value); + Object* entry = hashtable->Lookup(key); + if (entry->IsTheHole()) return GetHeap()->undefined_value(); + return entry; } Handle<Object> JSObject::SetHiddenProperty(Handle<JSObject> obj, - Handle<String> key, - Handle<Object> value) { + Handle<String> key, + Handle<Object> value) { CALL_HEAP_FUNCTION(obj->GetIsolate(), obj->SetHiddenProperty(*key, *value), Object); @@ -3653,6 +3824,7 @@ Handle<Object> JSObject::SetHiddenProperty(Handle<JSObject> obj, MaybeObject* JSObject::SetHiddenProperty(String* key, Object* value) { + ASSERT(key->IsSymbol()); if (IsJSGlobalProxy()) { // For a proxy, use the prototype as target object. Object* proxy_parent = GetPrototype(); @@ -3662,27 +3834,29 @@ MaybeObject* JSObject::SetHiddenProperty(String* key, Object* value) { return JSObject::cast(proxy_parent)->SetHiddenProperty(key, value); } ASSERT(!IsJSGlobalProxy()); - MaybeObject* hidden_lookup = GetHiddenPropertiesDictionary(true); - StringDictionary* dictionary; - if (!hidden_lookup->To<StringDictionary>(&dictionary)) return hidden_lookup; + MaybeObject* hidden_lookup = + GetHiddenPropertiesHashTable(ONLY_RETURN_INLINE_VALUE); + Object* inline_value = hidden_lookup->ToObjectUnchecked(); - // If it was found, check if the key is already in the dictionary. - int entry = dictionary->FindEntry(key); - if (entry != StringDictionary::kNotFound) { - // If key was found, just update the value. - dictionary->ValueAtPut(entry, value); - return this; + // If there is no backing store yet, store the identity hash inline. + if (value->IsSmi() && + key == GetHeap()->identity_hash_symbol() && + (inline_value->IsUndefined() || inline_value->IsSmi())) { + return SetHiddenPropertiesHashTable(value); } - // Key was not already in the dictionary, so add the entry. - MaybeObject* insert_result = dictionary->Add(key, - value, - PropertyDetails(NONE, NORMAL)); - StringDictionary* new_dict; - if (!insert_result->To<StringDictionary>(&new_dict)) return insert_result; - if (new_dict != dictionary) { + + hidden_lookup = GetHiddenPropertiesHashTable(CREATE_NEW_IF_ABSENT); + ObjectHashTable* hashtable; + if (!hidden_lookup->To(&hashtable)) return hidden_lookup; + + // If it was found, check if the key is already in the dictionary. + MaybeObject* insert_result = hashtable->Put(key, value); + ObjectHashTable* new_table; + if (!insert_result->To(&new_table)) return insert_result; + if (new_table != hashtable) { // If adding the key expanded the dictionary (i.e., Add returned a new // dictionary), store it back to the object. - MaybeObject* store_result = SetHiddenPropertiesDictionary(new_dict); + MaybeObject* store_result = SetHiddenPropertiesHashTable(new_table); if (store_result->IsFailure()) return store_result; } // Return this to mark success. @@ -3691,6 +3865,7 @@ MaybeObject* JSObject::SetHiddenProperty(String* key, Object* value) { void JSObject::DeleteHiddenProperty(String* key) { + ASSERT(key->IsSymbol()); if (IsJSGlobalProxy()) { // For a proxy, use the prototype as target object. Object* proxy_parent = GetPrototype(); @@ -3700,18 +3875,19 @@ void JSObject::DeleteHiddenProperty(String* key) { JSObject::cast(proxy_parent)->DeleteHiddenProperty(key); return; } - MaybeObject* hidden_lookup = GetHiddenPropertiesDictionary(false); - ASSERT(!hidden_lookup->IsFailure()); // No failure when passing false as arg. - if (hidden_lookup->ToObjectUnchecked()->IsUndefined()) return; - StringDictionary* dictionary = - StringDictionary::cast(hidden_lookup->ToObjectUnchecked()); - int entry = dictionary->FindEntry(key); - if (entry == StringDictionary::kNotFound) { - // Key wasn't in dictionary. Deletion is a success. - return; - } - // Key was in the dictionary. Remove it. - dictionary->DeleteProperty(entry, JSReceiver::FORCE_DELETION); + ASSERT(!IsJSGlobalProxy()); + MaybeObject* hidden_lookup = + GetHiddenPropertiesHashTable(ONLY_RETURN_INLINE_VALUE); + Object* inline_value = hidden_lookup->ToObjectUnchecked(); + + // We never delete (inline-stored) identity hashes. + ASSERT(key != GetHeap()->identity_hash_symbol()); + if (inline_value->IsUndefined() || inline_value->IsSmi()) return; + + ObjectHashTable* hashtable = ObjectHashTable::cast(inline_value); + MaybeObject* delete_result = hashtable->Put(key, GetHeap()->the_hole_value()); + USE(delete_result); + ASSERT(!delete_result->IsFailure()); // Delete does not cause GC. } @@ -3722,77 +3898,102 @@ bool JSObject::HasHiddenProperties() { } -MaybeObject* JSObject::GetHiddenPropertiesDictionary(bool create_if_absent) { +MaybeObject* JSObject::GetHiddenPropertiesHashTable( + InitializeHiddenProperties init_option) { ASSERT(!IsJSGlobalProxy()); + Object* inline_value; if (HasFastProperties()) { // If the object has fast properties, check whether the first slot // in the descriptor array matches the hidden symbol. Since the // hidden symbols hash code is zero (and no other string has hash // code zero) it will always occupy the first entry if present. DescriptorArray* descriptors = this->map()->instance_descriptors(); - if ((descriptors->number_of_descriptors() > 0) && - (descriptors->GetKey(0) == GetHeap()->hidden_symbol())) { - if (descriptors->GetType(0) == FIELD) { - Object* hidden_store = - this->FastPropertyAt(descriptors->GetFieldIndex(0)); - return StringDictionary::cast(hidden_store); + if (descriptors->number_of_descriptors() > 0) { + int sorted_index = descriptors->GetSortedKeyIndex(0); + if (descriptors->GetKey(sorted_index) == GetHeap()->hidden_symbol() && + sorted_index < map()->NumberOfOwnDescriptors()) { + ASSERT(descriptors->GetType(sorted_index) == FIELD); + inline_value = + this->FastPropertyAt(descriptors->GetFieldIndex(sorted_index)); } else { - ASSERT(descriptors->GetType(0) == NULL_DESCRIPTOR || - descriptors->GetType(0) == MAP_TRANSITION); + inline_value = GetHeap()->undefined_value(); } + } else { + inline_value = GetHeap()->undefined_value(); } } else { PropertyAttributes attributes; // You can't install a getter on a property indexed by the hidden symbol, // so we can be sure that GetLocalPropertyPostInterceptor returns a real // object. - Object* lookup = + inline_value = GetLocalPropertyPostInterceptor(this, GetHeap()->hidden_symbol(), &attributes)->ToObjectUnchecked(); - if (!lookup->IsUndefined()) { - return StringDictionary::cast(lookup); - } } - if (!create_if_absent) return GetHeap()->undefined_value(); - const int kInitialSize = 5; - MaybeObject* dict_alloc = StringDictionary::Allocate(kInitialSize); - StringDictionary* dictionary; - if (!dict_alloc->To<StringDictionary>(&dictionary)) return dict_alloc; - // Using AddProperty or SetPropertyPostInterceptor here could fail, because - // object might be non-extensible. - return HasFastProperties() - ? AddFastProperty(GetHeap()->hidden_symbol(), dictionary, DONT_ENUM) - : AddSlowProperty(GetHeap()->hidden_symbol(), dictionary, DONT_ENUM); + + if (init_option == ONLY_RETURN_INLINE_VALUE || + inline_value->IsHashTable()) { + return inline_value; + } + + ObjectHashTable* hashtable; + static const int kInitialCapacity = 4; + MaybeObject* maybe_obj = + ObjectHashTable::Allocate(kInitialCapacity, + ObjectHashTable::USE_CUSTOM_MINIMUM_CAPACITY); + if (!maybe_obj->To<ObjectHashTable>(&hashtable)) return maybe_obj; + + if (inline_value->IsSmi()) { + // We were storing the identity hash inline and now allocated an actual + // dictionary. Put the identity hash into the new dictionary. + MaybeObject* insert_result = + hashtable->Put(GetHeap()->identity_hash_symbol(), inline_value); + ObjectHashTable* new_table; + if (!insert_result->To(&new_table)) return insert_result; + // We expect no resizing for the first insert. + ASSERT_EQ(hashtable, new_table); + } + + MaybeObject* store_result = + SetPropertyPostInterceptor(GetHeap()->hidden_symbol(), + hashtable, + DONT_ENUM, + kNonStrictMode, + OMIT_EXTENSIBILITY_CHECK); + if (store_result->IsFailure()) return store_result; + return hashtable; } -MaybeObject* JSObject::SetHiddenPropertiesDictionary( - StringDictionary* dictionary) { +MaybeObject* JSObject::SetHiddenPropertiesHashTable(Object* value) { ASSERT(!IsJSGlobalProxy()); - ASSERT(HasHiddenProperties()); + // We can store the identity hash inline iff there is no backing store + // for hidden properties yet. + ASSERT(HasHiddenProperties() != value->IsSmi()); if (HasFastProperties()) { // If the object has fast properties, check whether the first slot // in the descriptor array matches the hidden symbol. Since the // hidden symbols hash code is zero (and no other string has hash // code zero) it will always occupy the first entry if present. DescriptorArray* descriptors = this->map()->instance_descriptors(); - if ((descriptors->number_of_descriptors() > 0) && - (descriptors->GetKey(0) == GetHeap()->hidden_symbol())) { - if (descriptors->GetType(0) == FIELD) { - this->FastPropertyAtPut(descriptors->GetFieldIndex(0), dictionary); + if (descriptors->number_of_descriptors() > 0) { + int sorted_index = descriptors->GetSortedKeyIndex(0); + if (descriptors->GetKey(sorted_index) == GetHeap()->hidden_symbol() && + sorted_index < map()->NumberOfOwnDescriptors()) { + ASSERT(descriptors->GetType(sorted_index) == FIELD); + this->FastPropertyAtPut(descriptors->GetFieldIndex(sorted_index), + value); return this; - } else { - ASSERT(descriptors->GetType(0) == NULL_DESCRIPTOR || - descriptors->GetType(0) == MAP_TRANSITION); } } } MaybeObject* store_result = SetPropertyPostInterceptor(GetHeap()->hidden_symbol(), - dictionary, + value, DONT_ENUM, - kNonStrictMode); + kNonStrictMode, + OMIT_EXTENSIBILITY_CHECK); if (store_result->IsFailure()) return store_result; return this; } @@ -3803,7 +4004,7 @@ MaybeObject* JSObject::DeletePropertyPostInterceptor(String* name, // Check local property, ignore interceptor. LookupResult result(GetIsolate()); LocalLookupRealNamedProperty(name, &result); - if (!result.IsProperty()) return GetHeap()->true_value(); + if (!result.IsFound()) return GetHeap()->true_value(); // Normalize object if needed. Object* obj; @@ -3837,7 +4038,9 @@ MaybeObject* JSObject::DeletePropertyWithInterceptor(String* name) { RETURN_IF_SCHEDULED_EXCEPTION(isolate); if (!result.IsEmpty()) { ASSERT(result->IsBoolean()); - return *v8::Utils::OpenHandle(*result); + Handle<Object> result_internal = v8::Utils::OpenHandle(*result); + result_internal->VerifyApiCallResultType(); + return *result_internal; } } MaybeObject* raw_result = @@ -3872,7 +4075,9 @@ MaybeObject* JSObject::DeleteElementWithInterceptor(uint32_t index) { RETURN_IF_SCHEDULED_EXCEPTION(isolate); if (!result.IsEmpty()) { ASSERT(result->IsBoolean()); - return *v8::Utils::OpenHandle(*result); + Handle<Object> result_internal = v8::Utils::OpenHandle(*result); + result_internal->VerifyApiCallResultType(); + return *result_internal; } MaybeObject* raw_result = this_handle->GetElementsAccessor()->Delete( *this_handle, @@ -3922,15 +4127,39 @@ MaybeObject* JSObject::DeleteElement(uint32_t index, DeleteMode mode) { return JSGlobalObject::cast(proto)->DeleteElement(index, mode); } - if (HasIndexedInterceptor()) { - // Skip interceptor if forcing deletion. - if (mode != FORCE_DELETION) { - return DeleteElementWithInterceptor(index); + // From this point on everything needs to be handlified. + HandleScope scope(isolate); + Handle<JSObject> self(this); + + Handle<String> name; + Handle<Object> old_value(isolate->heap()->the_hole_value()); + bool preexists = false; + if (FLAG_harmony_observation && map()->is_observed()) { + name = isolate->factory()->Uint32ToString(index); + preexists = self->HasLocalElement(index); + if (preexists) { + // TODO(observe): only read & set old_value if it's not an accessor + old_value = Object::GetElement(self, index); } - mode = JSReceiver::FORCE_DELETION; } - return GetElementsAccessor()->Delete(this, index, mode); + MaybeObject* result; + // Skip interceptor if forcing deletion. + if (self->HasIndexedInterceptor() && mode != FORCE_DELETION) { + result = self->DeleteElementWithInterceptor(index); + } else { + result = self->GetElementsAccessor()->Delete(*self, index, mode); + } + + Handle<Object> hresult; + if (!result->ToHandle(&hresult)) return result; + + if (FLAG_harmony_observation && map()->is_observed()) { + if (preexists && !self->HasLocalElement(index)) + EnqueueChangeRecord(self, "deleted", name, old_value); + } + + return *hresult; } @@ -3964,38 +4193,60 @@ MaybeObject* JSObject::DeleteProperty(String* name, DeleteMode mode) { uint32_t index = 0; if (name->AsArrayIndex(&index)) { return DeleteElement(index, mode); - } else { - LookupResult result(isolate); - LocalLookup(name, &result); - if (!result.IsProperty()) return isolate->heap()->true_value(); - // Ignore attributes if forcing a deletion. - if (result.IsDontDelete() && mode != FORCE_DELETION) { - if (mode == STRICT_DELETION) { - // Deleting a non-configurable property in strict mode. - HandleScope scope(isolate); - Handle<Object> args[2] = { Handle<Object>(name), Handle<Object>(this) }; - return isolate->Throw(*isolate->factory()->NewTypeError( - "strict_delete_property", HandleVector(args, 2))); - } - return isolate->heap()->false_value(); - } - // Check for interceptor. - if (result.type() == INTERCEPTOR) { - // Skip interceptor if forcing a deletion. - if (mode == FORCE_DELETION) { - return DeletePropertyPostInterceptor(name, mode); - } - return DeletePropertyWithInterceptor(name); + } + + LookupResult lookup(isolate); + LocalLookup(name, &lookup); + if (!lookup.IsFound()) return isolate->heap()->true_value(); + // Ignore attributes if forcing a deletion. + if (lookup.IsDontDelete() && mode != FORCE_DELETION) { + if (mode == STRICT_DELETION) { + // Deleting a non-configurable property in strict mode. + HandleScope scope(isolate); + Handle<Object> args[2] = { Handle<Object>(name), Handle<Object>(this) }; + return isolate->Throw(*isolate->factory()->NewTypeError( + "strict_delete_property", HandleVector(args, 2))); } + return isolate->heap()->false_value(); + } + + // From this point on everything needs to be handlified. + HandleScope scope(isolate); + Handle<JSObject> self(this); + Handle<String> hname(name); + + Handle<Object> old_value(isolate->heap()->the_hole_value()); + if (FLAG_harmony_observation && map()->is_observed()) { + old_value = handle(lookup.GetLazyValue()); + } + MaybeObject* result; + + // Check for interceptor. + if (lookup.IsInterceptor()) { + // Skip interceptor if forcing a deletion. + if (mode == FORCE_DELETION) { + result = self->DeletePropertyPostInterceptor(*hname, mode); + } else { + result = self->DeletePropertyWithInterceptor(*hname); + } + } else { // Normalize object if needed. Object* obj; - { MaybeObject* maybe_obj = - NormalizeProperties(CLEAR_INOBJECT_PROPERTIES, 0); - if (!maybe_obj->ToObject(&obj)) return maybe_obj; - } + result = self->NormalizeProperties(CLEAR_INOBJECT_PROPERTIES, 0); + if (!result->To(&obj)) return result; // Make sure the properties are normalized before removing the entry. - return DeleteNormalizedProperty(name, mode); + result = self->DeleteNormalizedProperty(*hname, mode); } + + Handle<Object> hresult; + if (!result->ToHandle(&hresult)) return result; + + if (FLAG_harmony_observation && map()->is_observed()) { + if (!self->HasLocalProperty(*hname)) + EnqueueChangeRecord(self, "deleted", hname, old_value); + } + + return *hresult; } @@ -4018,9 +4269,9 @@ MaybeObject* JSReceiver::DeleteProperty(String* name, DeleteMode mode) { bool JSObject::ReferencesObjectFromElements(FixedArray* elements, ElementsKind kind, Object* object) { - ASSERT(kind == FAST_ELEMENTS || + ASSERT(IsFastObjectElementsKind(kind) || kind == DICTIONARY_ELEMENTS); - if (kind == FAST_ELEMENTS) { + if (IsFastObjectElementsKind(kind)) { int length = IsJSArray() ? Smi::cast(JSArray::cast(this)->length())->value() : elements->length(); @@ -4072,12 +4323,15 @@ bool JSObject::ReferencesObject(Object* obj) { case EXTERNAL_FLOAT_ELEMENTS: case EXTERNAL_DOUBLE_ELEMENTS: case FAST_DOUBLE_ELEMENTS: + case FAST_HOLEY_DOUBLE_ELEMENTS: // Raw pixels and external arrays do not reference other // objects. break; - case FAST_SMI_ONLY_ELEMENTS: + case FAST_SMI_ELEMENTS: + case FAST_HOLEY_SMI_ELEMENTS: break; case FAST_ELEMENTS: + case FAST_HOLEY_ELEMENTS: case DICTIONARY_ELEMENTS: { FixedArray* elements = FixedArray::cast(this->elements()); if (ReferencesObjectFromElements(elements, kind, obj)) return true; @@ -4093,7 +4347,8 @@ bool JSObject::ReferencesObject(Object* obj) { } // Check the arguments. FixedArray* arguments = FixedArray::cast(parameter_map->get(1)); - kind = arguments->IsDictionary() ? DICTIONARY_ELEMENTS : FAST_ELEMENTS; + kind = arguments->IsDictionary() ? DICTIONARY_ELEMENTS : + FAST_HOLEY_ELEMENTS; if (ReferencesObjectFromElements(arguments, kind, obj)) return true; break; } @@ -4103,15 +4358,15 @@ bool JSObject::ReferencesObject(Object* obj) { if (IsJSFunction()) { // Get the constructor function for arguments array. JSObject* arguments_boilerplate = - heap->isolate()->context()->global_context()-> + heap->isolate()->context()->native_context()-> arguments_boilerplate(); JSFunction* arguments_function = JSFunction::cast(arguments_boilerplate->map()->constructor()); - // Get the context and don't check if it is the global context. + // Get the context and don't check if it is the native context. JSFunction* f = JSFunction::cast(this); Context* context = f->context(); - if (context->IsGlobalContext()) { + if (context->IsNativeContext()) { return false; } @@ -4187,9 +4442,9 @@ MaybeObject* JSObject::PreventExtensions() { // Do a map transition, other objects with this map may still // be extensible. Map* new_map; - { MaybeObject* maybe = map()->CopyDropTransitions(); - if (!maybe->To<Map>(&new_map)) return maybe; - } + MaybeObject* maybe = map()->Copy(); + if (!maybe->To(&new_map)) return maybe; + new_map->set_is_extensible(false); set_map(new_map); ASSERT(!map()->is_extensible()); @@ -4209,29 +4464,27 @@ bool JSReceiver::IsSimpleEnum() { o = JSObject::cast(o)->GetPrototype()) { if (!o->IsJSObject()) return false; JSObject* curr = JSObject::cast(o); - if (!curr->map()->instance_descriptors()->HasEnumCache()) return false; + int enum_length = curr->map()->EnumLength(); + if (enum_length == Map::kInvalidEnumCache) return false; ASSERT(!curr->HasNamedInterceptor()); ASSERT(!curr->HasIndexedInterceptor()); ASSERT(!curr->IsAccessCheckNeeded()); if (curr->NumberOfEnumElements() > 0) return false; - if (curr != this) { - FixedArray* curr_fixed_array = - FixedArray::cast(curr->map()->instance_descriptors()->GetEnumCache()); - if (curr_fixed_array->length() > 0) return false; - } + if (curr != this && enum_length != 0) return false; } return true; } -int Map::NumberOfDescribedProperties(PropertyAttributes filter) { +int Map::NumberOfDescribedProperties(DescriptorFlag which, + PropertyAttributes filter) { int result = 0; DescriptorArray* descs = instance_descriptors(); - for (int i = 0; i < descs->number_of_descriptors(); i++) { - PropertyDetails details = descs->GetDetails(i); - if (descs->IsProperty(i) && (details.attributes() & filter) == 0) { - result++; - } + int limit = which == ALL_DESCRIPTORS + ? descs->number_of_descriptors() + : NumberOfOwnDescriptors(); + for (int i = 0; i < limit; i++) { + if ((descs->GetDetails(i).attributes() & filter) == 0) result++; } return result; } @@ -4239,10 +4492,9 @@ int Map::NumberOfDescribedProperties(PropertyAttributes filter) { int Map::PropertyIndexFor(String* name) { DescriptorArray* descs = instance_descriptors(); - for (int i = 0; i < descs->number_of_descriptors(); i++) { - if (name->Equals(descs->GetKey(i)) && !descs->IsNullDescriptor(i)) { - return descs->GetFieldIndex(i); - } + int limit = NumberOfOwnDescriptors(); + for (int i = 0; i < limit; i++) { + if (name->Equals(descs->GetKey(i))) return descs->GetFieldIndex(i); } return -1; } @@ -4250,8 +4502,9 @@ int Map::PropertyIndexFor(String* name) { int Map::NextFreePropertyIndex() { int max_index = -1; + int number_of_own_descriptors = NumberOfOwnDescriptors(); DescriptorArray* descs = instance_descriptors(); - for (int i = 0; i < descs->number_of_descriptors(); i++) { + for (int i = 0; i < number_of_own_descriptors; i++) { if (descs->GetType(i) == FIELD) { int current_index = descs->GetFieldIndex(i); if (current_index > max_index) max_index = current_index; @@ -4263,8 +4516,9 @@ int Map::NextFreePropertyIndex() { AccessorDescriptor* Map::FindAccessor(String* name) { DescriptorArray* descs = instance_descriptors(); - for (int i = 0; i < descs->number_of_descriptors(); i++) { - if (name->Equals(descs->GetKey(i)) && descs->GetType(i) == CALLBACKS) { + int number_of_own_descriptors = NumberOfOwnDescriptors(); + for (int i = 0; i < number_of_own_descriptors; i++) { + if (descs->GetType(i) == CALLBACKS && name->Equals(descs->GetKey(i))) { return descs->GetCallbacks(i); } } @@ -4322,7 +4576,8 @@ void JSReceiver::LocalLookup(String* name, LookupResult* result, } -void JSReceiver::Lookup(String* name, LookupResult* result, +void JSReceiver::Lookup(String* name, + LookupResult* result, bool skip_fallback_interceptor) { // Ecma-262 3rd 8.6.2.4 Heap* heap = GetHeap(); @@ -4332,20 +4587,20 @@ void JSReceiver::Lookup(String* name, LookupResult* result, JSReceiver::cast(current)->LocalLookup(name, result, skip_fallback_interceptor); - if (result->IsProperty()) return; + if (result->IsFound()) return; } result->NotFound(); } -// Search object and it's prototype chain for callback properties. -void JSObject::LookupCallback(String* name, LookupResult* result) { +// Search object and its prototype chain for callback properties. +void JSObject::LookupCallbackProperty(String* name, LookupResult* result) { Heap* heap = GetHeap(); for (Object* current = this; current != heap->null_value() && current->IsJSObject(); current = JSObject::cast(current)->GetPrototype()) { JSObject::cast(current)->LocalLookupRealNamedProperty(name, result); - if (result->IsFound() && result->type() == CALLBACKS) return; + if (result->IsPropertyCallbacks()) return; } result->NotFound(); } @@ -4382,9 +4637,12 @@ MaybeObject* JSObject::DefineElementAccessor(uint32_t index, Object* setter, PropertyAttributes attributes) { switch (GetElementsKind()) { - case FAST_SMI_ONLY_ELEMENTS: + case FAST_SMI_ELEMENTS: case FAST_ELEMENTS: case FAST_DOUBLE_ELEMENTS: + case FAST_HOLEY_SMI_ELEMENTS: + case FAST_HOLEY_ELEMENTS: + case FAST_HOLEY_DOUBLE_ELEMENTS: break; case EXTERNAL_PIXEL_ELEMENTS: case EXTERNAL_BYTE_ELEMENTS: @@ -4445,11 +4703,16 @@ MaybeObject* JSObject::DefineElementAccessor(uint32_t index, MaybeObject* JSObject::CreateAccessorPairFor(String* name) { LookupResult result(GetHeap()->isolate()); LocalLookupRealNamedProperty(name, &result); - if (result.IsProperty() && result.type() == CALLBACKS) { - ASSERT(!result.IsDontDelete()); + if (result.IsPropertyCallbacks()) { + // Note that the result can actually have IsDontDelete() == true when we + // e.g. have to fall back to the slow case while adding a setter after + // successfully reusing a map transition for a getter. Nevertheless, this is + // OK, because the assertion only holds for the whole addition of both + // accessors, not for the addition of each part. See first comment in + // DefinePropertyAccessor below. Object* obj = result.GetCallbackObject(); if (obj->IsAccessorPair()) { - return AccessorPair::cast(obj)->CopyWithoutTransitions(); + return AccessorPair::cast(obj)->Copy(); } } return GetHeap()->AllocateAccessorPair(); @@ -4460,10 +4723,34 @@ MaybeObject* JSObject::DefinePropertyAccessor(String* name, Object* getter, Object* setter, PropertyAttributes attributes) { - AccessorPair* accessors; - { MaybeObject* maybe_accessors = CreateAccessorPairFor(name); - if (!maybe_accessors->To(&accessors)) return maybe_accessors; + // We could assert that the property is configurable here, but we would need + // to do a lookup, which seems to be a bit of overkill. + Heap* heap = GetHeap(); + bool only_attribute_changes = getter->IsNull() && setter->IsNull(); + if (HasFastProperties() && !only_attribute_changes && + (map()->NumberOfOwnDescriptors() < + DescriptorArray::kMaxNumberOfDescriptors)) { + MaybeObject* getterOk = heap->undefined_value(); + if (!getter->IsNull()) { + getterOk = DefineFastAccessor(name, ACCESSOR_GETTER, getter, attributes); + if (getterOk->IsFailure()) return getterOk; + } + + MaybeObject* setterOk = heap->undefined_value(); + if (getterOk != heap->null_value() && !setter->IsNull()) { + setterOk = DefineFastAccessor(name, ACCESSOR_SETTER, setter, attributes); + if (setterOk->IsFailure()) return setterOk; + } + + if (getterOk != heap->null_value() && setterOk != heap->null_value()) { + return heap->undefined_value(); + } } + + AccessorPair* accessors; + MaybeObject* maybe_accessors = CreateAccessorPairFor(name); + if (!maybe_accessors->To(&accessors)) return maybe_accessors; + accessors->SetComponents(getter, setter); return SetPropertyCallback(name, accessors, attributes); } @@ -4474,14 +4761,14 @@ bool JSObject::CanSetCallback(String* name) { GetIsolate()->MayNamedAccess(this, name, v8::ACCESS_SET)); // Check if there is an API defined callback object which prohibits - // callback overwriting in this object or it's prototype chain. + // callback overwriting in this object or its prototype chain. // This mechanism is needed for instance in a browser setting, where // certain accessors such as window.location should not be allowed // to be overwritten because allowing overwriting could potentially // cause security problems. LookupResult callback_result(GetIsolate()); - LookupCallback(name, &callback_result); - if (callback_result.IsProperty()) { + LookupCallbackProperty(name, &callback_result); + if (callback_result.IsFound()) { Object* obj = callback_result.GetCallbackObject(); if (obj->IsAccessorInfo() && AccessorInfo::cast(obj)->prohibits_overwriting()) { @@ -4535,17 +4822,17 @@ MaybeObject* JSObject::SetPropertyCallback(String* name, Object* structure, PropertyAttributes attributes) { // Normalize object to make this operation simple. - { MaybeObject* maybe_ok = NormalizeProperties(CLEAR_INOBJECT_PROPERTIES, 0); - if (maybe_ok->IsFailure()) return maybe_ok; - } + MaybeObject* maybe_ok = NormalizeProperties(CLEAR_INOBJECT_PROPERTIES, 0); + if (maybe_ok->IsFailure()) return maybe_ok; // For the global object allocate a new map to invalidate the global inline // caches which have a global property cell reference directly in the code. if (IsGlobalObject()) { Map* new_map; - { MaybeObject* maybe_new_map = map()->CopyDropDescriptors(); - if (!maybe_new_map->To(&new_map)) return maybe_new_map; - } + MaybeObject* maybe_new_map = map()->CopyDropDescriptors(); + if (!maybe_new_map->To(&new_map)) return maybe_new_map; + ASSERT(new_map->is_dictionary_map()); + set_map(new_map); // When running crankshaft, changing the map is not enough. We // need to deoptimize all functions that rely on this global @@ -4555,9 +4842,8 @@ MaybeObject* JSObject::SetPropertyCallback(String* name, // Update the dictionary with the new CALLBACKS property. PropertyDetails details = PropertyDetails(attributes, CALLBACKS); - { MaybeObject* maybe_ok = SetNormalizedProperty(name, structure, details); - if (maybe_ok->IsFailure()) return maybe_ok; - } + maybe_ok = SetNormalizedProperty(name, structure, details); + if (maybe_ok->IsFailure()) return maybe_ok; return GetHeap()->undefined_value(); } @@ -4573,14 +4859,14 @@ void JSObject::DefineAccessor(Handle<JSObject> object, object->DefineAccessor(*name, *getter, *setter, attributes)); } -MaybeObject* JSObject::DefineAccessor(String* name, - Object* getter, - Object* setter, +MaybeObject* JSObject::DefineAccessor(String* name_raw, + Object* getter_raw, + Object* setter_raw, PropertyAttributes attributes) { Isolate* isolate = GetIsolate(); // Check access rights if needed. if (IsAccessCheckNeeded() && - !isolate->MayNamedAccess(this, name, v8::ACCESS_SET)) { + !isolate->MayNamedAccess(this, name_raw, v8::ACCESS_SET)) { isolate->ReportFailedAccessCheck(this, v8::ACCESS_SET); return isolate->heap()->undefined_value(); } @@ -4590,7 +4876,7 @@ MaybeObject* JSObject::DefineAccessor(String* name, if (proto->IsNull()) return this; ASSERT(proto->IsJSGlobalObject()); return JSObject::cast(proto)->DefineAccessor( - name, getter, setter, attributes); + name_raw, getter_raw, setter_raw, attributes); } // Make sure that the top context does not change when doing callbacks or @@ -4598,14 +4884,160 @@ MaybeObject* JSObject::DefineAccessor(String* name, AssertNoContextChange ncc; // Try to flatten before operating on the string. - name->TryFlatten(); + name_raw->TryFlatten(); - if (!CanSetCallback(name)) return isolate->heap()->undefined_value(); + if (!CanSetCallback(name_raw)) return isolate->heap()->undefined_value(); + + // From this point on everything needs to be handlified. + HandleScope scope(GetIsolate()); + Handle<JSObject> self(this); + Handle<String> name(name_raw); + Handle<Object> getter(getter_raw); + Handle<Object> setter(setter_raw); uint32_t index = 0; - return name->AsArrayIndex(&index) ? - DefineElementAccessor(index, getter, setter, attributes) : - DefinePropertyAccessor(name, getter, setter, attributes); + bool is_element = name->AsArrayIndex(&index); + + Handle<Object> old_value(isolate->heap()->the_hole_value()); + bool preexists = false; + if (FLAG_harmony_observation && map()->is_observed()) { + if (is_element) { + preexists = HasLocalElement(index); + if (preexists) { + // TODO(observe): distinguish the case where it's an accessor + old_value = Object::GetElement(self, index); + } + } else { + LookupResult lookup(isolate); + LocalLookup(*name, &lookup); + preexists = lookup.IsProperty(); + if (preexists) old_value = handle(lookup.GetLazyValue()); + } + } + + MaybeObject* result = is_element ? + self->DefineElementAccessor(index, *getter, *setter, attributes) : + self->DefinePropertyAccessor(*name, *getter, *setter, attributes); + + Handle<Object> hresult; + if (!result->ToHandle(&hresult)) return result; + + if (FLAG_harmony_observation && map()->is_observed()) { + const char* type = preexists ? "reconfigured" : "new"; + EnqueueChangeRecord(self, type, name, old_value); + } + + return *hresult; +} + + +static MaybeObject* TryAccessorTransition(JSObject* self, + Map* transitioned_map, + int target_descriptor, + AccessorComponent component, + Object* accessor, + PropertyAttributes attributes) { + DescriptorArray* descs = transitioned_map->instance_descriptors(); + PropertyDetails details = descs->GetDetails(target_descriptor); + + // If the transition target was not callbacks, fall back to the slow case. + if (details.type() != CALLBACKS) return self->GetHeap()->null_value(); + Object* descriptor = descs->GetCallbacksObject(target_descriptor); + if (!descriptor->IsAccessorPair()) return self->GetHeap()->null_value(); + + Object* target_accessor = AccessorPair::cast(descriptor)->get(component); + PropertyAttributes target_attributes = details.attributes(); + + // Reuse transition if adding same accessor with same attributes. + if (target_accessor == accessor && target_attributes == attributes) { + self->set_map(transitioned_map); + return self; + } + + // If either not the same accessor, or not the same attributes, fall back to + // the slow case. + return self->GetHeap()->null_value(); +} + + +MaybeObject* JSObject::DefineFastAccessor(String* name, + AccessorComponent component, + Object* accessor, + PropertyAttributes attributes) { + ASSERT(accessor->IsSpecFunction() || accessor->IsUndefined()); + LookupResult result(GetIsolate()); + LocalLookup(name, &result); + + if (result.IsFound() + && !result.IsPropertyCallbacks() + && !result.IsTransition()) return GetHeap()->null_value(); + + // Return success if the same accessor with the same attributes already exist. + AccessorPair* source_accessors = NULL; + if (result.IsPropertyCallbacks()) { + Object* callback_value = result.GetCallbackObject(); + if (callback_value->IsAccessorPair()) { + source_accessors = AccessorPair::cast(callback_value); + Object* entry = source_accessors->get(component); + if (entry == accessor && result.GetAttributes() == attributes) { + return this; + } + } else { + return GetHeap()->null_value(); + } + + int descriptor_number = result.GetDescriptorIndex(); + + map()->LookupTransition(this, name, &result); + + if (result.IsFound()) { + Map* target = result.GetTransitionTarget(); + ASSERT(target->NumberOfOwnDescriptors() == + map()->NumberOfOwnDescriptors()); + // This works since descriptors are sorted in order of addition. + ASSERT(map()->instance_descriptors()->GetKey(descriptor_number) == name); + return TryAccessorTransition( + this, target, descriptor_number, component, accessor, attributes); + } + } else { + // If not, lookup a transition. + map()->LookupTransition(this, name, &result); + + // If there is a transition, try to follow it. + if (result.IsFound()) { + Map* target = result.GetTransitionTarget(); + int descriptor_number = target->LastAdded(); + ASSERT(target->instance_descriptors()->GetKey(descriptor_number) + ->Equals(name)); + return TryAccessorTransition( + this, target, descriptor_number, component, accessor, attributes); + } + } + + // If there is no transition yet, add a transition to the a new accessor pair + // containing the accessor. + AccessorPair* accessors; + MaybeObject* maybe_accessors; + + // Allocate a new pair if there were no source accessors. Otherwise, copy the + // pair and modify the accessor. + if (source_accessors != NULL) { + maybe_accessors = source_accessors->Copy(); + } else { + maybe_accessors = GetHeap()->AllocateAccessorPair(); + } + if (!maybe_accessors->To(&accessors)) return maybe_accessors; + accessors->set(component, accessor); + + CallbacksDescriptor new_accessors_desc(name, accessors, attributes); + + Map* new_map; + MaybeObject* maybe_new_map = + map()->CopyInsertDescriptor(&new_accessors_desc, INSERT_TRANSITION); + if (!maybe_new_map->To(&new_map)) return maybe_new_map; + + set_map(new_map); + return this; } @@ -4633,9 +5065,7 @@ MaybeObject* JSObject::DefineAccessor(AccessorInfo* info) { // Try to flatten before operating on the string. name->TryFlatten(); - if (!CanSetCallback(name)) { - return isolate->heap()->undefined_value(); - } + if (!CanSetCallback(name)) return isolate->heap()->undefined_value(); uint32_t index = 0; bool is_element = name->AsArrayIndex(&index); @@ -4645,9 +5075,12 @@ MaybeObject* JSObject::DefineAccessor(AccessorInfo* info) { // Accessors overwrite previous callbacks (cf. with getters/setters). switch (GetElementsKind()) { - case FAST_SMI_ONLY_ELEMENTS: + case FAST_SMI_ELEMENTS: case FAST_ELEMENTS: case FAST_DOUBLE_ELEMENTS: + case FAST_HOLEY_SMI_ELEMENTS: + case FAST_HOLEY_ELEMENTS: + case FAST_HOLEY_DOUBLE_ELEMENTS: break; case EXTERNAL_PIXEL_ELEMENTS: case EXTERNAL_BYTE_ELEMENTS: @@ -4668,23 +5101,22 @@ MaybeObject* JSObject::DefineAccessor(AccessorInfo* info) { break; } - { MaybeObject* maybe_ok = - SetElementCallback(index, info, info->property_attributes()); - if (maybe_ok->IsFailure()) return maybe_ok; - } + MaybeObject* maybe_ok = + SetElementCallback(index, info, info->property_attributes()); + if (maybe_ok->IsFailure()) return maybe_ok; } else { // Lookup the name. LookupResult result(isolate); LocalLookup(name, &result); // ES5 forbids turning a property into an accessor if it's not // configurable (that is IsDontDelete in ES3 and v8), see 8.6.1 (Table 5). - if (result.IsProperty() && (result.IsReadOnly() || result.IsDontDelete())) { + if (result.IsFound() && (result.IsReadOnly() || result.IsDontDelete())) { return isolate->heap()->undefined_value(); } - { MaybeObject* maybe_ok = - SetPropertyCallback(name, info, info->property_attributes()); - if (maybe_ok->IsFailure()) return maybe_ok; - } + + MaybeObject* maybe_ok = + SetPropertyCallback(name, info, info->property_attributes()); + if (maybe_ok->IsFailure()) return maybe_ok; } return this; @@ -4710,9 +5142,9 @@ Object* JSObject::LookupAccessor(String* name, AccessorComponent component) { if (name->AsArrayIndex(&index)) { for (Object* obj = this; obj != heap->null_value(); - obj = JSObject::cast(obj)->GetPrototype()) { - JSObject* js_object = JSObject::cast(obj); - if (js_object->HasDictionaryElements()) { + obj = JSReceiver::cast(obj)->GetPrototype()) { + if (obj->IsJSObject() && JSObject::cast(obj)->HasDictionaryElements()) { + JSObject* js_object = JSObject::cast(obj); SeededNumberDictionary* dictionary = js_object->element_dictionary(); int entry = dictionary->FindEntry(index); if (entry != SeededNumberDictionary::kNotFound) { @@ -4727,12 +5159,12 @@ Object* JSObject::LookupAccessor(String* name, AccessorComponent component) { } else { for (Object* obj = this; obj != heap->null_value(); - obj = JSObject::cast(obj)->GetPrototype()) { + obj = JSReceiver::cast(obj)->GetPrototype()) { LookupResult result(heap->isolate()); - JSObject::cast(obj)->LocalLookup(name, &result); - if (result.IsProperty()) { + JSReceiver::cast(obj)->LocalLookup(name, &result); + if (result.IsFound()) { if (result.IsReadOnly()) return heap->undefined_value(); - if (result.type() == CALLBACKS) { + if (result.IsPropertyCallbacks()) { Object* obj = result.GetCallbackObject(); if (obj->IsAccessorPair()) { return AccessorPair::cast(obj)->GetComponent(component); @@ -4747,8 +5179,9 @@ Object* JSObject::LookupAccessor(String* name, AccessorComponent component) { Object* JSObject::SlowReverseLookup(Object* value) { if (HasFastProperties()) { + int number_of_own_descriptors = map()->NumberOfOwnDescriptors(); DescriptorArray* descs = map()->instance_descriptors(); - for (int i = 0; i < descs->number_of_descriptors(); i++) { + for (int i = 0; i < number_of_own_descriptors; i++) { if (descs->GetType(i) == FIELD) { if (FastPropertyAt(descs->GetFieldIndex(i)) == value) { return descs->GetKey(i); @@ -4766,45 +5199,21 @@ Object* JSObject::SlowReverseLookup(Object* value) { } -MaybeObject* Map::CopyDropDescriptors() { - Heap* heap = GetHeap(); - Object* result; - { MaybeObject* maybe_result = - heap->AllocateMap(instance_type(), instance_size()); - if (!maybe_result->ToObject(&result)) return maybe_result; - } - Map::cast(result)->set_prototype(prototype()); - Map::cast(result)->set_constructor(constructor()); - // Don't copy descriptors, so map transitions always remain a forest. - // If we retained the same descriptors we would have two maps - // pointing to the same transition which is bad because the garbage - // collector relies on being able to reverse pointers from transitions - // to maps. If properties need to be retained use CopyDropTransitions. - Map::cast(result)->clear_instance_descriptors(); - // Please note instance_type and instance_size are set when allocated. - Map::cast(result)->set_inobject_properties(inobject_properties()); - Map::cast(result)->set_unused_property_fields(unused_property_fields()); - - // If the map has pre-allocated properties always start out with a descriptor - // array describing these properties. - if (pre_allocated_property_fields() > 0) { - ASSERT(constructor()->IsJSFunction()); - JSFunction* ctor = JSFunction::cast(constructor()); - Object* descriptors; - { MaybeObject* maybe_descriptors = - ctor->initial_map()->instance_descriptors()->RemoveTransitions(); - if (!maybe_descriptors->ToObject(&descriptors)) return maybe_descriptors; - } - Map::cast(result)->set_instance_descriptors( - DescriptorArray::cast(descriptors)); - Map::cast(result)->set_pre_allocated_property_fields( - pre_allocated_property_fields()); - } - Map::cast(result)->set_bit_field(bit_field()); - Map::cast(result)->set_bit_field2(bit_field2()); - Map::cast(result)->set_bit_field3(bit_field3()); - Map::cast(result)->set_is_shared(false); - Map::cast(result)->ClearCodeCache(heap); +MaybeObject* Map::RawCopy(int instance_size) { + Map* result; + MaybeObject* maybe_result = + GetHeap()->AllocateMap(instance_type(), instance_size); + if (!maybe_result->To(&result)) return maybe_result; + + result->set_prototype(prototype()); + result->set_constructor(constructor()); + result->set_bit_field(bit_field()); + result->set_bit_field2(bit_field2()); + int new_bit_field3 = bit_field3(); + new_bit_field3 = OwnsDescriptors::update(new_bit_field3, true); + new_bit_field3 = NumberOfOwnDescriptorsBits::update(new_bit_field3, 0); + new_bit_field3 = EnumLengthBits::update(new_bit_field3, kInvalidEnumCache); + result->set_bit_field3(new_bit_field3); return result; } @@ -4816,49 +5225,351 @@ MaybeObject* Map::CopyNormalized(PropertyNormalizationMode mode, new_instance_size -= inobject_properties() * kPointerSize; } - Object* result; - { MaybeObject* maybe_result = - GetHeap()->AllocateMap(instance_type(), new_instance_size); - if (!maybe_result->ToObject(&result)) return maybe_result; - } + Map* result; + MaybeObject* maybe_result = RawCopy(new_instance_size); + if (!maybe_result->To(&result)) return maybe_result; if (mode != CLEAR_INOBJECT_PROPERTIES) { - Map::cast(result)->set_inobject_properties(inobject_properties()); + result->set_inobject_properties(inobject_properties()); } - Map::cast(result)->set_prototype(prototype()); - Map::cast(result)->set_constructor(constructor()); + result->set_code_cache(code_cache()); + result->set_is_shared(sharing == SHARED_NORMALIZED_MAP); + result->set_dictionary_map(true); - Map::cast(result)->set_bit_field(bit_field()); - Map::cast(result)->set_bit_field2(bit_field2()); - Map::cast(result)->set_bit_field3(bit_field3()); +#ifdef VERIFY_HEAP + if (FLAG_verify_heap && result->is_shared()) { + result->SharedMapVerify(); + } +#endif - Map::cast(result)->set_is_shared(sharing == SHARED_NORMALIZED_MAP); + return result; +} -#ifdef DEBUG - if (FLAG_verify_heap && Map::cast(result)->is_shared()) { - Map::cast(result)->SharedMapVerify(); + +MaybeObject* Map::CopyDropDescriptors() { + Map* result; + MaybeObject* maybe_result = RawCopy(instance_size()); + if (!maybe_result->To(&result)) return maybe_result; + + // Please note instance_type and instance_size are set when allocated. + result->set_inobject_properties(inobject_properties()); + result->set_unused_property_fields(unused_property_fields()); + + result->set_pre_allocated_property_fields(pre_allocated_property_fields()); + result->set_is_shared(false); + result->ClearCodeCache(GetHeap()); + return result; +} + + +MaybeObject* Map::ShareDescriptor(DescriptorArray* descriptors, + Descriptor* descriptor) { + // Sanity check. This path is only to be taken if the map owns its descriptor + // array, implying that its NumberOfOwnDescriptors equals the number of + // descriptors in the descriptor array. + ASSERT(NumberOfOwnDescriptors() == + instance_descriptors()->number_of_descriptors()); + Map* result; + MaybeObject* maybe_result = CopyDropDescriptors(); + if (!maybe_result->To(&result)) return maybe_result; + + String* name = descriptor->GetKey(); + + TransitionArray* transitions; + MaybeObject* maybe_transitions = + AddTransition(name, result, SIMPLE_TRANSITION); + if (!maybe_transitions->To(&transitions)) return maybe_transitions; + + int old_size = descriptors->number_of_descriptors(); + + DescriptorArray* new_descriptors; + + if (descriptors->NumberOfSlackDescriptors() > 0) { + new_descriptors = descriptors; + new_descriptors->Append(descriptor); + } else { + // Descriptor arrays grow by 50%. + MaybeObject* maybe_descriptors = DescriptorArray::Allocate( + old_size, old_size < 4 ? 1 : old_size / 2); + if (!maybe_descriptors->To(&new_descriptors)) return maybe_descriptors; + + DescriptorArray::WhitenessWitness witness(new_descriptors); + + // Copy the descriptors, inserting a descriptor. + for (int i = 0; i < old_size; ++i) { + new_descriptors->CopyFrom(i, descriptors, i, witness); + } + + new_descriptors->Append(descriptor, witness); + + if (old_size > 0) { + // If the source descriptors had an enum cache we copy it. This ensures + // that the maps to which we push the new descriptor array back can rely + // on a cache always being available once it is set. If the map has more + // enumerated descriptors than available in the original cache, the cache + // will be lazily replaced by the extended cache when needed. + if (descriptors->HasEnumCache()) { + new_descriptors->CopyEnumCacheFrom(descriptors); + } + + Map* map; + // Replace descriptors by new_descriptors in all maps that share it. + for (Object* current = GetBackPointer(); + !current->IsUndefined(); + current = map->GetBackPointer()) { + map = Map::cast(current); + if (map->instance_descriptors() != descriptors) break; + map->set_instance_descriptors(new_descriptors); + } + + set_instance_descriptors(new_descriptors); + } } -#endif + + result->SetBackPointer(this); + result->InitializeDescriptors(new_descriptors); + ASSERT(result->NumberOfOwnDescriptors() == NumberOfOwnDescriptors() + 1); + + set_transitions(transitions); + set_owns_descriptors(false); return result; } -MaybeObject* Map::CopyDropTransitions() { - Object* new_map; - { MaybeObject* maybe_new_map = CopyDropDescriptors(); - if (!maybe_new_map->ToObject(&new_map)) return maybe_new_map; +MaybeObject* Map::CopyReplaceDescriptors(DescriptorArray* descriptors, + String* name, + TransitionFlag flag, + int descriptor_index) { + ASSERT(descriptors->IsSortedNoDuplicates()); + + Map* result; + MaybeObject* maybe_result = CopyDropDescriptors(); + if (!maybe_result->To(&result)) return maybe_result; + + result->InitializeDescriptors(descriptors); + + if (flag == INSERT_TRANSITION && CanHaveMoreTransitions()) { + TransitionArray* transitions; + SimpleTransitionFlag simple_flag = + (descriptor_index == descriptors->number_of_descriptors() - 1) + ? SIMPLE_TRANSITION + : FULL_TRANSITION; + MaybeObject* maybe_transitions = AddTransition(name, result, simple_flag); + if (!maybe_transitions->To(&transitions)) return maybe_transitions; + + set_transitions(transitions); + result->SetBackPointer(this); } - Object* descriptors; - { MaybeObject* maybe_descriptors = - instance_descriptors()->RemoveTransitions(); - if (!maybe_descriptors->ToObject(&descriptors)) return maybe_descriptors; + + return result; +} + + +MaybeObject* Map::CopyAsElementsKind(ElementsKind kind, TransitionFlag flag) { + if (flag == INSERT_TRANSITION) { + ASSERT(!HasElementsTransition() || + ((elements_transition_map()->elements_kind() == DICTIONARY_ELEMENTS || + IsExternalArrayElementsKind( + elements_transition_map()->elements_kind())) && + (kind == DICTIONARY_ELEMENTS || + IsExternalArrayElementsKind(kind)))); + ASSERT(!IsFastElementsKind(kind) || + IsMoreGeneralElementsKindTransition(elements_kind(), kind)); + ASSERT(kind != elements_kind()); + } + + bool insert_transition = + flag == INSERT_TRANSITION && !HasElementsTransition(); + + if (insert_transition && owns_descriptors()) { + // In case the map owned its own descriptors, share the descriptors and + // transfer ownership to the new map. + Map* new_map; + MaybeObject* maybe_new_map = CopyDropDescriptors(); + if (!maybe_new_map->To(&new_map)) return maybe_new_map; + + MaybeObject* added_elements = set_elements_transition_map(new_map); + if (added_elements->IsFailure()) return added_elements; + + new_map->set_elements_kind(kind); + new_map->InitializeDescriptors(instance_descriptors()); + new_map->SetBackPointer(this); + set_owns_descriptors(false); + return new_map; } - cast(new_map)->set_instance_descriptors(DescriptorArray::cast(descriptors)); + + // In case the map did not own its own descriptors, a split is forced by + // copying the map; creating a new descriptor array cell. + // Create a new free-floating map only if we are not allowed to store it. + Map* new_map; + MaybeObject* maybe_new_map = Copy(); + if (!maybe_new_map->To(&new_map)) return maybe_new_map; + + new_map->set_elements_kind(kind); + + if (insert_transition) { + MaybeObject* added_elements = set_elements_transition_map(new_map); + if (added_elements->IsFailure()) return added_elements; + new_map->SetBackPointer(this); + } + return new_map; } + +MaybeObject* Map::CopyWithPreallocatedFieldDescriptors() { + if (pre_allocated_property_fields() == 0) return CopyDropDescriptors(); + + // If the map has pre-allocated properties always start out with a descriptor + // array describing these properties. + ASSERT(constructor()->IsJSFunction()); + JSFunction* ctor = JSFunction::cast(constructor()); + Map* map = ctor->initial_map(); + DescriptorArray* descriptors = map->instance_descriptors(); + + int number_of_own_descriptors = map->NumberOfOwnDescriptors(); + DescriptorArray* new_descriptors; + MaybeObject* maybe_descriptors = + descriptors->CopyUpTo(number_of_own_descriptors); + if (!maybe_descriptors->To(&new_descriptors)) return maybe_descriptors; + + return CopyReplaceDescriptors(new_descriptors, NULL, OMIT_TRANSITION, 0); +} + + +MaybeObject* Map::Copy() { + DescriptorArray* descriptors = instance_descriptors(); + DescriptorArray* new_descriptors; + int number_of_own_descriptors = NumberOfOwnDescriptors(); + MaybeObject* maybe_descriptors = + descriptors->CopyUpTo(number_of_own_descriptors); + if (!maybe_descriptors->To(&new_descriptors)) return maybe_descriptors; + + return CopyReplaceDescriptors(new_descriptors, NULL, OMIT_TRANSITION, 0); +} + + +MaybeObject* Map::CopyAddDescriptor(Descriptor* descriptor, + TransitionFlag flag) { + DescriptorArray* descriptors = instance_descriptors(); + + // Ensure the key is a symbol. + MaybeObject* maybe_failure = descriptor->KeyToSymbol(); + if (maybe_failure->IsFailure()) return maybe_failure; + + int old_size = NumberOfOwnDescriptors(); + int new_size = old_size + 1; + descriptor->SetEnumerationIndex(new_size); + + if (flag == INSERT_TRANSITION && + owns_descriptors() && + CanHaveMoreTransitions()) { + return ShareDescriptor(descriptors, descriptor); + } + + DescriptorArray* new_descriptors; + MaybeObject* maybe_descriptors = DescriptorArray::Allocate(old_size, 1); + if (!maybe_descriptors->To(&new_descriptors)) return maybe_descriptors; + + DescriptorArray::WhitenessWitness witness(new_descriptors); + + // Copy the descriptors, inserting a descriptor. + for (int i = 0; i < old_size; ++i) { + new_descriptors->CopyFrom(i, descriptors, i, witness); + } + + if (old_size != descriptors->number_of_descriptors()) { + new_descriptors->SetNumberOfDescriptors(new_size); + new_descriptors->Set(old_size, descriptor, witness); + new_descriptors->Sort(); + } else { + new_descriptors->Append(descriptor, witness); + } + + String* key = descriptor->GetKey(); + int insertion_index = new_descriptors->number_of_descriptors() - 1; + + return CopyReplaceDescriptors(new_descriptors, key, flag, insertion_index); +} + + +MaybeObject* Map::CopyInsertDescriptor(Descriptor* descriptor, + TransitionFlag flag) { + DescriptorArray* old_descriptors = instance_descriptors(); + + // Ensure the key is a symbol. + MaybeObject* maybe_result = descriptor->KeyToSymbol(); + if (maybe_result->IsFailure()) return maybe_result; + + // We replace the key if it is already present. + int index = old_descriptors->SearchWithCache(descriptor->GetKey(), this); + if (index != DescriptorArray::kNotFound) { + return CopyReplaceDescriptor(old_descriptors, descriptor, index, flag); + } + return CopyAddDescriptor(descriptor, flag); +} + + +MaybeObject* DescriptorArray::CopyUpTo(int enumeration_index) { + if (enumeration_index == 0) return GetHeap()->empty_descriptor_array(); + + int size = enumeration_index; + + DescriptorArray* descriptors; + MaybeObject* maybe_descriptors = Allocate(size); + if (!maybe_descriptors->To(&descriptors)) return maybe_descriptors; + DescriptorArray::WhitenessWitness witness(descriptors); + + for (int i = 0; i < size; ++i) { + descriptors->CopyFrom(i, this, i, witness); + } + + if (number_of_descriptors() != enumeration_index) descriptors->Sort(); + + return descriptors; +} + + +MaybeObject* Map::CopyReplaceDescriptor(DescriptorArray* descriptors, + Descriptor* descriptor, + int insertion_index, + TransitionFlag flag) { + // Ensure the key is a symbol. + MaybeObject* maybe_failure = descriptor->KeyToSymbol(); + if (maybe_failure->IsFailure()) return maybe_failure; + + String* key = descriptor->GetKey(); + ASSERT(key == descriptors->GetKey(insertion_index)); + + int new_size = NumberOfOwnDescriptors(); + ASSERT(0 <= insertion_index && insertion_index < new_size); + + PropertyDetails details = descriptors->GetDetails(insertion_index); + ASSERT_LE(details.descriptor_index(), new_size); + descriptor->SetEnumerationIndex(details.descriptor_index()); + + DescriptorArray* new_descriptors; + MaybeObject* maybe_descriptors = DescriptorArray::Allocate(new_size); + if (!maybe_descriptors->To(&new_descriptors)) return maybe_descriptors; + DescriptorArray::WhitenessWitness witness(new_descriptors); + + for (int i = 0; i < new_size; ++i) { + if (i == insertion_index) { + new_descriptors->Set(i, descriptor, witness); + } else { + new_descriptors->CopyFrom(i, descriptors, i, witness); + } + } + + // Re-sort if descriptors were removed. + if (new_size != descriptors->length()) new_descriptors->Sort(); + + return CopyReplaceDescriptors(new_descriptors, key, flag, insertion_index); +} + + void Map::UpdateCodeCache(Handle<Map> map, Handle<String> name, Handle<Code> code) { @@ -4867,7 +5578,10 @@ void Map::UpdateCodeCache(Handle<Map> map, map->UpdateCodeCache(*name, *code)); } + MaybeObject* Map::UpdateCodeCache(String* name, Code* code) { + ASSERT(!is_shared() || code->allowed_in_shared_map_code_cache()); + // Allocate the code cache if not present. if (code_cache()->IsFixedArray()) { Object* result; @@ -4913,78 +5627,43 @@ void Map::RemoveFromCodeCache(String* name, Code* code, int index) { // field of the contens array while it is running. class IntrusiveMapTransitionIterator { public: - explicit IntrusiveMapTransitionIterator(DescriptorArray* descriptor_array) - : descriptor_array_(descriptor_array) { } + explicit IntrusiveMapTransitionIterator(TransitionArray* transition_array) + : transition_array_(transition_array) { } void Start() { ASSERT(!IsIterating()); - if (HasContentArray()) *ContentHeader() = Smi::FromInt(0); + *TransitionArrayHeader() = Smi::FromInt(0); } bool IsIterating() { - return HasContentArray() && (*ContentHeader())->IsSmi(); + return (*TransitionArrayHeader())->IsSmi(); } Map* Next() { ASSERT(IsIterating()); - FixedArray* contents = ContentArray(); - // Attention, tricky index manipulation ahead: Every entry in the contents - // array consists of a value/details pair, so the index is typically even. - // An exception is made for CALLBACKS entries: An even index means we look - // at its getter, and an odd index means we look at its setter. - int index = Smi::cast(*ContentHeader())->value(); - while (index < contents->length()) { - PropertyDetails details(Smi::cast(contents->get(index | 1))); - switch (details.type()) { - case MAP_TRANSITION: - case CONSTANT_TRANSITION: - case ELEMENTS_TRANSITION: - // We definitely have a map transition. - *ContentHeader() = Smi::FromInt(index + 2); - return static_cast<Map*>(contents->get(index)); - case CALLBACKS: { - // We might have a map transition in a getter or in a setter. - AccessorPair* accessors = - static_cast<AccessorPair*>(contents->get(index & ~1)); - Object* accessor = - ((index & 1) == 0) ? accessors->getter() : accessors->setter(); - index++; - if (accessor->IsMap()) { - *ContentHeader() = Smi::FromInt(index); - return static_cast<Map*>(accessor); - } - break; - } - case NORMAL: - case FIELD: - case CONSTANT_FUNCTION: - case HANDLER: - case INTERCEPTOR: - case NULL_DESCRIPTOR: - // We definitely have no map transition. - index += 2; - break; - } + int index = Smi::cast(*TransitionArrayHeader())->value(); + int number_of_transitions = transition_array_->number_of_transitions(); + while (index < number_of_transitions) { + *TransitionArrayHeader() = Smi::FromInt(index + 1); + return transition_array_->GetTarget(index); } - *ContentHeader() = descriptor_array_->GetHeap()->fixed_array_map(); + + if (index == number_of_transitions && + transition_array_->HasElementsTransition()) { + Map* elements_transition = transition_array_->elements_transition(); + *TransitionArrayHeader() = Smi::FromInt(index + 1); + return elements_transition; + } + *TransitionArrayHeader() = transition_array_->GetHeap()->fixed_array_map(); return NULL; } private: - bool HasContentArray() { - return descriptor_array_-> length() > DescriptorArray::kContentArrayIndex; - } - - FixedArray* ContentArray() { - Object* array = descriptor_array_->get(DescriptorArray::kContentArrayIndex); - return static_cast<FixedArray*>(array); - } - - Object** ContentHeader() { - return HeapObject::RawField(ContentArray(), DescriptorArray::kMapOffset); + Object** TransitionArrayHeader() { + return HeapObject::RawField(transition_array_, TransitionArray::kMapOffset); } - DescriptorArray* descriptor_array_; + TransitionArray* transition_array_; }; @@ -4997,11 +5676,11 @@ class IntrusivePrototypeTransitionIterator { void Start() { ASSERT(!IsIterating()); - if (HasTransitions()) *Header() = Smi::FromInt(0); + *Header() = Smi::FromInt(0); } bool IsIterating() { - return HasTransitions() && (*Header())->IsSmi(); + return (*Header())->IsSmi(); } Map* Next() { @@ -5016,23 +5695,17 @@ class IntrusivePrototypeTransitionIterator { } private: - bool HasTransitions() { - return proto_trans_->map()->IsSmi() || proto_trans_->IsFixedArray(); - } - Object** Header() { return HeapObject::RawField(proto_trans_, FixedArray::kMapOffset); } int NumberOfTransitions() { - ASSERT(HasTransitions()); FixedArray* proto_trans = reinterpret_cast<FixedArray*>(proto_trans_); Object* num = proto_trans->get(Map::kProtoTransitionNumberOfEntriesOffset); return Smi::cast(num)->value(); } Map* GetTransition(int transitionNumber) { - ASSERT(HasTransitions()); FixedArray* proto_trans = reinterpret_cast<FixedArray*>(proto_trans_); return Map::cast(proto_trans->get(IndexFor(transitionNumber))); } @@ -5085,25 +5758,40 @@ class TraversableMap : public Map { // Start iterating over this map's children, possibly destroying a FixedArray // map (see explanation above). void ChildIteratorStart() { - IntrusiveMapTransitionIterator(instance_descriptors()).Start(); - IntrusivePrototypeTransitionIterator( - unchecked_prototype_transitions()).Start(); + if (HasTransitionArray()) { + if (HasPrototypeTransitions()) { + IntrusivePrototypeTransitionIterator(GetPrototypeTransitions()).Start(); + } + + IntrusiveMapTransitionIterator(transitions()).Start(); + } } // If we have an unvisited child map, return that one and advance. If we have // none, return NULL and reset any destroyed FixedArray maps. TraversableMap* ChildIteratorNext() { - IntrusiveMapTransitionIterator descriptor_iterator(instance_descriptors()); - if (descriptor_iterator.IsIterating()) { - Map* next = descriptor_iterator.Next(); - if (next != NULL) return static_cast<TraversableMap*>(next); + TransitionArray* transition_array = unchecked_transition_array(); + if (!transition_array->map()->IsSmi() && + !transition_array->IsTransitionArray()) { + return NULL; } - IntrusivePrototypeTransitionIterator - proto_iterator(unchecked_prototype_transitions()); - if (proto_iterator.IsIterating()) { - Map* next = proto_iterator.Next(); + + if (transition_array->HasPrototypeTransitions()) { + HeapObject* proto_transitions = + transition_array->UncheckedPrototypeTransitions(); + IntrusivePrototypeTransitionIterator proto_iterator(proto_transitions); + if (proto_iterator.IsIterating()) { + Map* next = proto_iterator.Next(); + if (next != NULL) return static_cast<TraversableMap*>(next); + } + } + + IntrusiveMapTransitionIterator transition_iterator(transition_array); + if (transition_iterator.IsIterating()) { + Map* next = transition_iterator.Next(); if (next != NULL) return static_cast<TraversableMap*>(next); } + return NULL; } }; @@ -5134,7 +5822,7 @@ MaybeObject* CodeCache::Update(String* name, Code* code) { // The number of monomorphic stubs for normal load/store/call IC's can grow to // a large number and therefore they need to go into a hash table. They are // used to load global properties from cells. - if (code->type() == NORMAL) { + if (code->type() == Code::NORMAL) { // Make sure that a hash table is allocated for the normal load code cache. if (normal_type_cache()->IsUndefined()) { Object* result; @@ -5225,7 +5913,7 @@ MaybeObject* CodeCache::UpdateNormalTypeCache(String* name, Code* code) { Object* CodeCache::Lookup(String* name, Code::Flags flags) { - if (Code::ExtractTypeFromFlags(flags) == NORMAL) { + if (Code::ExtractTypeFromFlags(flags) == Code::NORMAL) { return LookupNormalTypeCache(name, flags); } else { return LookupDefaultCache(name, flags); @@ -5263,7 +5951,7 @@ Object* CodeCache::LookupNormalTypeCache(String* name, Code::Flags flags) { int CodeCache::GetIndex(Object* name, Code* code) { - if (code->type() == NORMAL) { + if (code->type() == Code::NORMAL) { if (normal_type_cache()->IsUndefined()) return -1; CodeCacheHashTable* cache = CodeCacheHashTable::cast(normal_type_cache()); return cache->GetIndex(String::cast(name), code->flags()); @@ -5279,7 +5967,7 @@ int CodeCache::GetIndex(Object* name, Code* code) { void CodeCache::RemoveByIndex(Object* name, Code* code, int index) { - if (code->type() == NORMAL) { + if (code->type() == Code::NORMAL) { ASSERT(!normal_type_cache()->IsUndefined()); CodeCacheHashTable* cache = CodeCacheHashTable::cast(normal_type_cache()); ASSERT(cache->GetIndex(String::cast(name), code->flags()) == index); @@ -5597,7 +6285,7 @@ MaybeObject* FixedArray::UnionOfKeys(FixedArray* other) { MaybeObject* maybe_result = accessor->AddElementsToFixedArray(NULL, NULL, this, other); FixedArray* result; - if (!maybe_result->To<FixedArray>(&result)) return maybe_result; + if (!maybe_result->To(&result)) return maybe_result; #ifdef DEBUG if (FLAG_enable_slow_asserts) { for (int i = 0; i < result->length(); i++) { @@ -5653,234 +6341,80 @@ bool FixedArray::IsEqualTo(FixedArray* other) { #endif -MaybeObject* DescriptorArray::Allocate(int number_of_descriptors) { +MaybeObject* DescriptorArray::Allocate(int number_of_descriptors, int slack) { Heap* heap = Isolate::Current()->heap(); - if (number_of_descriptors == 0) { - return heap->empty_descriptor_array(); - } - // Allocate the array of keys. - Object* array; - { MaybeObject* maybe_array = - heap->AllocateFixedArray(ToKeyIndex(number_of_descriptors)); - if (!maybe_array->ToObject(&array)) return maybe_array; - } // Do not use DescriptorArray::cast on incomplete object. - FixedArray* result = FixedArray::cast(array); + int size = number_of_descriptors + slack; + if (size == 0) return heap->empty_descriptor_array(); + FixedArray* result; + // Allocate the array of keys. + MaybeObject* maybe_array = heap->AllocateFixedArray(LengthFor(size)); + if (!maybe_array->To(&result)) return maybe_array; - // Allocate the content array and set it in the descriptor array. - { MaybeObject* maybe_array = - heap->AllocateFixedArray(number_of_descriptors << 1); - if (!maybe_array->ToObject(&array)) return maybe_array; - } - result->set(kBitField3StorageIndex, Smi::FromInt(0)); - result->set(kContentArrayIndex, array); - result->set(kEnumerationIndexIndex, - Smi::FromInt(PropertyDetails::kInitialIndex)); + result->set(kDescriptorLengthIndex, Smi::FromInt(number_of_descriptors)); + result->set(kEnumCacheIndex, Smi::FromInt(0)); return result; } +void DescriptorArray::ClearEnumCache() { + set(kEnumCacheIndex, Smi::FromInt(0)); +} + + void DescriptorArray::SetEnumCache(FixedArray* bridge_storage, FixedArray* new_cache, Object* new_index_cache) { ASSERT(bridge_storage->length() >= kEnumCacheBridgeLength); ASSERT(new_index_cache->IsSmi() || new_index_cache->IsFixedArray()); - if (HasEnumCache()) { - FixedArray::cast(get(kEnumerationIndexIndex))-> - set(kEnumCacheBridgeCacheIndex, new_cache); - FixedArray::cast(get(kEnumerationIndexIndex))-> - set(kEnumCacheBridgeIndicesCacheIndex, new_index_cache); - } else { - if (IsEmpty()) return; // Do nothing for empty descriptor array. - FixedArray::cast(bridge_storage)-> - set(kEnumCacheBridgeCacheIndex, new_cache); - FixedArray::cast(bridge_storage)-> - set(kEnumCacheBridgeIndicesCacheIndex, new_index_cache); - NoWriteBarrierSet(FixedArray::cast(bridge_storage), - kEnumCacheBridgeEnumIndex, - get(kEnumerationIndexIndex)); - set(kEnumerationIndexIndex, bridge_storage); - } + ASSERT(!IsEmpty()); + ASSERT(!HasEnumCache() || new_cache->length() > GetEnumCache()->length()); + FixedArray::cast(bridge_storage)-> + set(kEnumCacheBridgeCacheIndex, new_cache); + FixedArray::cast(bridge_storage)-> + set(kEnumCacheBridgeIndicesCacheIndex, new_index_cache); + set(kEnumCacheIndex, bridge_storage); } -static bool InsertionPointFound(String* key1, String* key2) { - return key1->Hash() > key2->Hash() || key1 == key2; -} - - -void DescriptorArray::CopyFrom(Handle<DescriptorArray> dst, - int dst_index, - Handle<DescriptorArray> src, +void DescriptorArray::CopyFrom(int dst_index, + DescriptorArray* src, int src_index, const WhitenessWitness& witness) { - CALL_HEAP_FUNCTION_VOID(dst->GetIsolate(), - dst->CopyFrom(dst_index, *src, src_index, witness)); -} - - -MaybeObject* DescriptorArray::CopyFrom(int dst_index, - DescriptorArray* src, - int src_index, - const WhitenessWitness& witness) { Object* value = src->GetValue(src_index); PropertyDetails details = src->GetDetails(src_index); - if (details.type() == CALLBACKS && value->IsAccessorPair()) { - MaybeObject* maybe_copy = - AccessorPair::cast(value)->CopyWithoutTransitions(); - if (!maybe_copy->To(&value)) return maybe_copy; - } Descriptor desc(src->GetKey(src_index), value, details); Set(dst_index, &desc, witness); - return this; -} - - -MaybeObject* DescriptorArray::CopyInsert(Descriptor* descriptor, - TransitionFlag transition_flag) { - // Transitions are only kept when inserting another transition. - // This precondition is not required by this function's implementation, but - // is currently required by the semantics of maps, so we check it. - // Conversely, we filter after replacing, so replacing a transition and - // removing all other transitions is not supported. - bool remove_transitions = transition_flag == REMOVE_TRANSITIONS; - ASSERT(remove_transitions == !descriptor->ContainsTransition()); - ASSERT(descriptor->GetDetails().type() != NULL_DESCRIPTOR); - - // Ensure the key is a symbol. - { MaybeObject* maybe_result = descriptor->KeyToSymbol(); - if (maybe_result->IsFailure()) return maybe_result; - } - - int new_size = 0; - for (int i = 0; i < number_of_descriptors(); i++) { - if (IsNullDescriptor(i)) continue; - if (remove_transitions && IsTransitionOnly(i)) continue; - new_size++; - } - - // If key is in descriptor, we replace it in-place when filtering. - // Count a null descriptor for key as inserted, not replaced. - int index = Search(descriptor->GetKey()); - const bool replacing = (index != kNotFound); - bool keep_enumeration_index = false; - if (replacing) { - // We are replacing an existing descriptor. We keep the enumeration - // index of a visible property. - PropertyType t = GetDetails(index).type(); - if (t == CONSTANT_FUNCTION || - t == FIELD || - t == CALLBACKS || - t == INTERCEPTOR) { - keep_enumeration_index = true; - } else if (remove_transitions) { - // Replaced descriptor has been counted as removed if it is - // a transition that will be replaced. Adjust count in this case. - ++new_size; - } - } else { - ++new_size; - } - - DescriptorArray* new_descriptors; - { MaybeObject* maybe_result = Allocate(new_size); - if (!maybe_result->To(&new_descriptors)) return maybe_result; - } - - DescriptorArray::WhitenessWitness witness(new_descriptors); - - // Set the enumeration index in the descriptors and set the enumeration index - // in the result. - int enumeration_index = NextEnumerationIndex(); - if (!descriptor->ContainsTransition()) { - if (keep_enumeration_index) { - descriptor->SetEnumerationIndex(GetDetails(index).index()); - } else { - descriptor->SetEnumerationIndex(enumeration_index); - ++enumeration_index; - } - } - new_descriptors->SetNextEnumerationIndex(enumeration_index); - - // Copy the descriptors, filtering out transitions and null descriptors, - // and inserting or replacing a descriptor. - int to_index = 0; - int insertion_index = -1; - int from_index = 0; - while (from_index < number_of_descriptors()) { - if (insertion_index < 0 && - InsertionPointFound(GetKey(from_index), descriptor->GetKey())) { - insertion_index = to_index++; - if (replacing) from_index++; - } else { - if (!(IsNullDescriptor(from_index) || - (remove_transitions && IsTransitionOnly(from_index)))) { - MaybeObject* copy_result = - new_descriptors->CopyFrom(to_index++, this, from_index, witness); - if (copy_result->IsFailure()) return copy_result; - } - from_index++; - } - } - if (insertion_index < 0) insertion_index = to_index++; - new_descriptors->Set(insertion_index, descriptor, witness); - - ASSERT(to_index == new_descriptors->number_of_descriptors()); - SLOW_ASSERT(new_descriptors->IsSortedNoDuplicates()); - - return new_descriptors; } -MaybeObject* DescriptorArray::RemoveTransitions() { - // Allocate the new descriptor array. - int new_number_of_descriptors = 0; - for (int i = 0; i < number_of_descriptors(); i++) { - if (IsProperty(i)) new_number_of_descriptors++; - } - DescriptorArray* new_descriptors; - { MaybeObject* maybe_result = Allocate(new_number_of_descriptors); - if (!maybe_result->To(&new_descriptors)) return maybe_result; - } - - // Copy the content. - DescriptorArray::WhitenessWitness witness(new_descriptors); - int next_descriptor = 0; - for (int i = 0; i < number_of_descriptors(); i++) { - if (IsProperty(i)) { - MaybeObject* copy_result = - new_descriptors->CopyFrom(next_descriptor++, this, i, witness); - if (copy_result->IsFailure()) return copy_result; - } - } - ASSERT(next_descriptor == new_descriptors->number_of_descriptors()); - - return new_descriptors; -} - - -void DescriptorArray::SortUnchecked(const WhitenessWitness& witness) { +// We need the whiteness witness since sort will reshuffle the entries in the +// descriptor array. If the descriptor array were to be black, the shuffling +// would move a slot that was already recorded as pointing into an evacuation +// candidate. This would result in missing updates upon evacuation. +void DescriptorArray::Sort() { // In-place heap sort. int len = number_of_descriptors(); - + // Reset sorting since the descriptor array might contain invalid pointers. + for (int i = 0; i < len; ++i) SetSortedKey(i, i); // Bottom-up max-heap construction. // Index of the last node with children const int max_parent_index = (len / 2) - 1; for (int i = max_parent_index; i >= 0; --i) { int parent_index = i; - const uint32_t parent_hash = GetKey(i)->Hash(); + const uint32_t parent_hash = GetSortedKey(i)->Hash(); while (parent_index <= max_parent_index) { int child_index = 2 * parent_index + 1; - uint32_t child_hash = GetKey(child_index)->Hash(); + uint32_t child_hash = GetSortedKey(child_index)->Hash(); if (child_index + 1 < len) { - uint32_t right_child_hash = GetKey(child_index + 1)->Hash(); + uint32_t right_child_hash = GetSortedKey(child_index + 1)->Hash(); if (right_child_hash > child_hash) { child_index++; child_hash = right_child_hash; } } if (child_hash <= parent_hash) break; - NoIncrementalWriteBarrierSwapDescriptors(parent_index, child_index); + SwapSortedKeys(parent_index, child_index); // Now element at child_index could be < its children. parent_index = child_index; // parent_hash remains correct. } @@ -5889,95 +6423,45 @@ void DescriptorArray::SortUnchecked(const WhitenessWitness& witness) { // Extract elements and create sorted array. for (int i = len - 1; i > 0; --i) { // Put max element at the back of the array. - NoIncrementalWriteBarrierSwapDescriptors(0, i); + SwapSortedKeys(0, i); // Shift down the new top element. int parent_index = 0; - const uint32_t parent_hash = GetKey(parent_index)->Hash(); + const uint32_t parent_hash = GetSortedKey(parent_index)->Hash(); const int max_parent_index = (i / 2) - 1; while (parent_index <= max_parent_index) { int child_index = parent_index * 2 + 1; - uint32_t child_hash = GetKey(child_index)->Hash(); + uint32_t child_hash = GetSortedKey(child_index)->Hash(); if (child_index + 1 < i) { - uint32_t right_child_hash = GetKey(child_index + 1)->Hash(); + uint32_t right_child_hash = GetSortedKey(child_index + 1)->Hash(); if (right_child_hash > child_hash) { child_index++; child_hash = right_child_hash; } } if (child_hash <= parent_hash) break; - NoIncrementalWriteBarrierSwapDescriptors(parent_index, child_index); + SwapSortedKeys(parent_index, child_index); parent_index = child_index; } } + ASSERT(IsSortedNoDuplicates()); } -void DescriptorArray::Sort(const WhitenessWitness& witness) { - SortUnchecked(witness); - SLOW_ASSERT(IsSortedNoDuplicates()); -} - - -int DescriptorArray::BinarySearch(String* name, int low, int high) { - uint32_t hash = name->Hash(); - - while (low <= high) { - int mid = (low + high) / 2; - String* mid_name = GetKey(mid); - uint32_t mid_hash = mid_name->Hash(); - - if (mid_hash > hash) { - high = mid - 1; - continue; - } - if (mid_hash < hash) { - low = mid + 1; - continue; - } - // Found an element with the same hash-code. - ASSERT(hash == mid_hash); - // There might be more, so we find the first one and - // check them all to see if we have a match. - if (name == mid_name && !IsNullDescriptor(mid)) return mid; - while ((mid > low) && (GetKey(mid - 1)->Hash() == hash)) mid--; - for (; (mid <= high) && (GetKey(mid)->Hash() == hash); mid++) { - if (GetKey(mid)->Equals(name) && !IsNullDescriptor(mid)) return mid; - } - break; - } - return kNotFound; -} - - -int DescriptorArray::LinearSearch(String* name, int len) { - uint32_t hash = name->Hash(); - for (int number = 0; number < len; number++) { - String* entry = GetKey(number); - if ((entry->Hash() == hash) && - name->Equals(entry) && - !IsNullDescriptor(number)) { - return number; - } - } - return kNotFound; -} - - -MaybeObject* AccessorPair::CopyWithoutTransitions() { +MaybeObject* AccessorPair::Copy() { Heap* heap = GetHeap(); AccessorPair* copy; - { MaybeObject* maybe_copy = heap->AllocateAccessorPair(); - if (!maybe_copy->To(©)) return maybe_copy; - } - copy->set_getter(getter()->IsMap() ? heap->the_hole_value() : getter()); - copy->set_setter(setter()->IsMap() ? heap->the_hole_value() : setter()); + MaybeObject* maybe_copy = heap->AllocateAccessorPair(); + if (!maybe_copy->To(©)) return maybe_copy; + + copy->set_getter(getter()); + copy->set_setter(setter()); return copy; } Object* AccessorPair::GetComponent(AccessorComponent component) { - Object* accessor = (component == ACCESSOR_GETTER) ? getter() : setter(); - return accessor->IsTheHole() ? GetHeap()->undefined_value() : accessor; + Object* accessor = get(component); + return accessor->IsTheHole() ? GetHeap()->undefined_value() : accessor; } @@ -6003,9 +6487,9 @@ bool DescriptorArray::IsEqualTo(DescriptorArray* other) { if (other->IsEmpty()) return false; if (length() != other->length()) return false; for (int i = 0; i < length(); ++i) { - if (get(i) != other->get(i) && i != kContentArrayIndex) return false; + if (get(i) != other->get(i)) return false; } - return GetContentArray()->IsEqualTo(other->GetContentArray()); + return true; } #endif @@ -6037,7 +6521,7 @@ String::FlatContent String::GetFlatContent() { ASSERT(shape.representation_tag() != kConsStringTag && shape.representation_tag() != kSlicedStringTag); } - if (shape.encoding_tag() == kAsciiStringTag) { + if (shape.encoding_tag() == kOneByteStringTag) { const char* start; if (shape.representation_tag() == kSeqStringTag) { start = SeqAsciiString::cast(string)->GetChars(); @@ -6726,7 +7210,7 @@ void String::WriteToFlat(String* src, while (true) { ASSERT(0 <= from && from <= to && to <= source->length()); switch (StringShape(source).full_representation_tag()) { - case kAsciiStringTag | kExternalStringTag: { + case kOneByteStringTag | kExternalStringTag: { CopyChars(sink, ExternalAsciiString::cast(source)->GetChars() + from, to - from); @@ -6740,7 +7224,7 @@ void String::WriteToFlat(String* src, to - from); return; } - case kAsciiStringTag | kSeqStringTag: { + case kOneByteStringTag | kSeqStringTag: { CopyChars(sink, SeqAsciiString::cast(source)->GetChars() + from, to - from); @@ -6752,7 +7236,7 @@ void String::WriteToFlat(String* src, to - from); return; } - case kAsciiStringTag | kConsStringTag: + case kOneByteStringTag | kConsStringTag: case kTwoByteStringTag | kConsStringTag: { ConsString* cons_string = ConsString::cast(source); String* first = cons_string->first(); @@ -6793,7 +7277,7 @@ void String::WriteToFlat(String* src, } break; } - case kAsciiStringTag | kSlicedStringTag: + case kOneByteStringTag | kSlicedStringTag: case kTwoByteStringTag | kSlicedStringTag: { SlicedString* slice = SlicedString::cast(source); unsigned offset = slice->offset(); @@ -6883,72 +7367,6 @@ static inline bool CompareStringContentsPartial(Isolate* isolate, } -bool String::SlowEqualsExternal(uc16 *string, int length) { - int len = this->length(); - if (len != length) return false; - if (len == 0) return true; - - // We know the strings are both non-empty. Compare the first chars - // before we try to flatten the strings. - if (this->Get(0) != string[0]) return false; - - String* lhs = this->TryFlattenGetString(); - - if (lhs->IsFlat()) { - String::FlatContent lhs_content = lhs->GetFlatContent(); - if (lhs->IsAsciiRepresentation()) { - Vector<const char> vec1 = lhs_content.ToAsciiVector(); - VectorIterator<char> buf1(vec1); - VectorIterator<uc16> ib(string, length); - return CompareStringContents(&buf1, &ib); - } else { - Vector<const uc16> vec1 = lhs_content.ToUC16Vector(); - Vector<const uc16> vec2(string, length); - return CompareRawStringContents(vec1, vec2); - } - } else { - Isolate* isolate = GetIsolate(); - isolate->objects_string_compare_buffer_a()->Reset(0, lhs); - VectorIterator<uc16> ib(string, length); - return CompareStringContents(isolate->objects_string_compare_buffer_a(), - &ib); - } -} - - -bool String::SlowEqualsExternal(char *string, int length) { - int len = this->length(); - if (len != length) return false; - if (len == 0) return true; - - // We know the strings are both non-empty. Compare the first chars - // before we try to flatten the strings. - if (this->Get(0) != string[0]) return false; - - String* lhs = this->TryFlattenGetString(); - - if (StringShape(lhs).IsSequentialAscii()) { - const char* str1 = SeqAsciiString::cast(lhs)->GetChars(); - return CompareRawStringContents(Vector<const char>(str1, len), - Vector<const char>(string, len)); - } - - if (lhs->IsFlat()) { - String::FlatContent lhs_content = lhs->GetFlatContent(); - Vector<const uc16> vec1 = lhs_content.ToUC16Vector(); - VectorIterator<const uc16> buf1(vec1); - VectorIterator<char> buf2(string, length); - return CompareStringContents(&buf1, &buf2); - } else { - Isolate* isolate = GetIsolate(); - isolate->objects_string_compare_buffer_a()->Reset(0, lhs); - VectorIterator<char> ib(string, length); - return CompareStringContents(isolate->objects_string_compare_buffer_a(), - &ib); - } -} - - bool String::SlowEquals(String* other) { // Fast check: negative check with lengths. int len = length(); @@ -7216,7 +7634,6 @@ void StringHasher::AddSurrogatePairNoIndex(uc32 c) { uint32_t StringHasher::GetHashField() { - ASSERT(is_valid()); if (length_ <= String::kMaxHashCalcLength) { if (is_array_index()) { return MakeArrayIndexHash(array_index(), length_); @@ -7271,89 +7688,116 @@ void String::PrintOn(FILE* file) { } +static void TrimEnumCache(Heap* heap, Map* map, DescriptorArray* descriptors) { + int live_enum = map->EnumLength(); + if (live_enum == Map::kInvalidEnumCache) { + live_enum = map->NumberOfDescribedProperties(OWN_DESCRIPTORS, DONT_ENUM); + } + if (live_enum == 0) return descriptors->ClearEnumCache(); + + FixedArray* enum_cache = descriptors->GetEnumCache(); + + int to_trim = enum_cache->length() - live_enum; + if (to_trim <= 0) return; + RightTrimFixedArray<FROM_GC>(heap, descriptors->GetEnumCache(), to_trim); + + if (!descriptors->HasEnumIndicesCache()) return; + FixedArray* enum_indices_cache = descriptors->GetEnumIndicesCache(); + RightTrimFixedArray<FROM_GC>(heap, enum_indices_cache, to_trim); +} + + +static void TrimDescriptorArray(Heap* heap, + Map* map, + DescriptorArray* descriptors, + int number_of_own_descriptors) { + int number_of_descriptors = descriptors->number_of_descriptors(); + int to_trim = number_of_descriptors - number_of_own_descriptors; + if (to_trim <= 0) return; + + RightTrimFixedArray<FROM_GC>(heap, descriptors, to_trim); + descriptors->SetNumberOfDescriptors(number_of_own_descriptors); + + if (descriptors->HasEnumCache()) TrimEnumCache(heap, map, descriptors); + descriptors->Sort(); +} + + // Clear a possible back pointer in case the transition leads to a dead map. // Return true in case a back pointer has been cleared and false otherwise. -// Set *keep_entry to true when a live map transition has been found. -static bool ClearBackPointer(Heap* heap, Object* target, bool* keep_entry) { - if (!target->IsMap()) return false; - Map* map = Map::cast(target); - if (Marking::MarkBitFrom(map).Get()) { - *keep_entry = true; - return false; - } else { - map->SetBackPointer(heap->undefined_value(), SKIP_WRITE_BARRIER); - return true; - } +static bool ClearBackPointer(Heap* heap, Map* target) { + if (Marking::MarkBitFrom(target).Get()) return false; + target->SetBackPointer(heap->undefined_value(), SKIP_WRITE_BARRIER); + return true; } +// TODO(mstarzinger): This method should be moved into MarkCompactCollector, +// because it cannot be called from outside the GC and we already have methods +// depending on the transitions layout in the GC anyways. void Map::ClearNonLiveTransitions(Heap* heap) { - DescriptorArray* d = DescriptorArray::cast( - *RawField(this, Map::kInstanceDescriptorsOrBitField3Offset)); - if (d->IsEmpty()) return; - Smi* NullDescriptorDetails = - PropertyDetails(NONE, NULL_DESCRIPTOR).AsSmi(); - FixedArray* contents = FixedArray::cast( - d->get(DescriptorArray::kContentArrayIndex)); - ASSERT(contents->length() >= 2); - for (int i = 0; i < contents->length(); i += 2) { - // If the pair (value, details) is a map transition, check if the target is - // live. If not, null the descriptor. Also drop the back pointer for that - // map transition, so that this map is not reached again by following a back - // pointer from that non-live map. - bool keep_entry = false; - PropertyDetails details(Smi::cast(contents->get(i + 1))); - switch (details.type()) { - case MAP_TRANSITION: - case CONSTANT_TRANSITION: - ClearBackPointer(heap, contents->get(i), &keep_entry); - break; - case ELEMENTS_TRANSITION: { - Object* object = contents->get(i); - if (object->IsMap()) { - ClearBackPointer(heap, object, &keep_entry); - } else { - FixedArray* array = FixedArray::cast(object); - for (int j = 0; j < array->length(); ++j) { - if (ClearBackPointer(heap, array->get(j), &keep_entry)) { - array->set_undefined(j); - } - } - } - break; + // If there are no transitions to be cleared, return. + // TODO(verwaest) Should be an assert, otherwise back pointers are not + // properly cleared. + if (!HasTransitionArray()) return; + + TransitionArray* t = transitions(); + MarkCompactCollector* collector = heap->mark_compact_collector(); + + int transition_index = 0; + + DescriptorArray* descriptors = instance_descriptors(); + bool descriptors_owner_died = false; + + // Compact all live descriptors to the left. + for (int i = 0; i < t->number_of_transitions(); ++i) { + Map* target = t->GetTarget(i); + if (ClearBackPointer(heap, target)) { + if (target->instance_descriptors() == descriptors) { + descriptors_owner_died = true; } - case CALLBACKS: { - Object* object = contents->get(i); - if (object->IsAccessorPair()) { - AccessorPair* accessors = AccessorPair::cast(object); - if (ClearBackPointer(heap, accessors->getter(), &keep_entry)) { - accessors->set_getter(heap->the_hole_value()); - } - if (ClearBackPointer(heap, accessors->setter(), &keep_entry)) { - accessors->set_setter(heap->the_hole_value()); - } - } else { - keep_entry = true; - } - break; + } else { + if (i != transition_index) { + String* key = t->GetKey(i); + t->SetKey(transition_index, key); + Object** key_slot = t->GetKeySlot(transition_index); + collector->RecordSlot(key_slot, key_slot, key); + // Target slots do not need to be recorded since maps are not compacted. + t->SetTarget(transition_index, t->GetTarget(i)); } - case NORMAL: - case FIELD: - case CONSTANT_FUNCTION: - case HANDLER: - case INTERCEPTOR: - case NULL_DESCRIPTOR: - keep_entry = true; - break; + transition_index++; + } + } + + if (t->HasElementsTransition() && + ClearBackPointer(heap, t->elements_transition())) { + if (t->elements_transition()->instance_descriptors() == descriptors) { + descriptors_owner_died = true; } - // Make sure that an entry containing only dead transitions gets collected. - // What we *really* want to do here is removing this entry completely, but - // for technical reasons we can't do this, so we zero it out instead. - if (!keep_entry) { - contents->set_unchecked(i + 1, NullDescriptorDetails); - contents->set_null_unchecked(heap, i); + t->ClearElementsTransition(); + } else { + // If there are no transitions to be cleared, return. + // TODO(verwaest) Should be an assert, otherwise back pointers are not + // properly cleared. + if (transition_index == t->number_of_transitions()) return; + } + + int number_of_own_descriptors = NumberOfOwnDescriptors(); + + if (descriptors_owner_died) { + if (number_of_own_descriptors > 0) { + TrimDescriptorArray(heap, this, descriptors, number_of_own_descriptors); + ASSERT(descriptors->number_of_descriptors() == number_of_own_descriptors); + } else { + ASSERT(descriptors == GetHeap()->empty_descriptor_array()); } } + + int trim = t->number_of_transitions() - transition_index; + if (trim > 0) { + RightTrimFixedArray<FROM_GC>(heap, t, t->IsSimpleTransition() + ? trim : trim * TransitionArray::kTransitionSize); + } } @@ -7386,8 +7830,8 @@ bool Map::EquivalentToForNormalization(Map* other, instance_type() == other->instance_type() && bit_field() == other->bit_field() && bit_field2() == other->bit_field2() && - (bit_field3() & ~(1<<Map::kIsShared)) == - (other->bit_field3() & ~(1<<Map::kIsShared)); + is_observed() == other->is_observed() && + function_with_prototype() == other->function_with_prototype(); } @@ -7408,13 +7852,19 @@ void JSFunction::MarkForLazyRecompilation() { ReplaceCode(builtins->builtin(Builtins::kLazyRecompile)); } +void JSFunction::MarkForParallelRecompilation() { + ASSERT(is_compiled() && !IsOptimized()); + ASSERT(shared()->allows_lazy_compilation() || code()->optimizable()); + Builtins* builtins = GetIsolate()->builtins(); + ReplaceCode(builtins->builtin(Builtins::kParallelRecompile)); -bool SharedFunctionInfo::EnsureCompiled(Handle<SharedFunctionInfo> shared, - ClearExceptionFlag flag) { - return shared->is_compiled() || CompileLazy(shared, flag); + // Unlike MarkForLazyRecompilation, after queuing a function for + // recompilation on the compiler thread, we actually tail-call into + // the full code. We reset the profiler ticks here so that the + // function doesn't bother the runtime profiler too much. + shared()->code()->set_profiler_ticks(0); } - static bool CompileLazyHelper(CompilationInfo* info, ClearExceptionFlag flag) { // Compile the source information to a code object. @@ -7431,11 +7881,78 @@ static bool CompileLazyHelper(CompilationInfo* info, bool SharedFunctionInfo::CompileLazy(Handle<SharedFunctionInfo> shared, ClearExceptionFlag flag) { - CompilationInfo info(shared); + ASSERT(shared->allows_lazy_compilation_without_context()); + CompilationInfoWithZone info(shared); return CompileLazyHelper(&info, flag); } +void SharedFunctionInfo::ClearOptimizedCodeMap() { + set_optimized_code_map(Smi::FromInt(0)); +} + + +void SharedFunctionInfo::AddToOptimizedCodeMap( + Handle<SharedFunctionInfo> shared, + Handle<Context> native_context, + Handle<Code> code, + Handle<FixedArray> literals) { + ASSERT(code->kind() == Code::OPTIMIZED_FUNCTION); + ASSERT(native_context->IsNativeContext()); + STATIC_ASSERT(kEntryLength == 3); + Object* value = shared->optimized_code_map(); + Handle<FixedArray> new_code_map; + if (value->IsSmi()) { + // No optimized code map. + ASSERT_EQ(0, Smi::cast(value)->value()); + // Crate 3 entries per context {context, code, literals}. + new_code_map = FACTORY->NewFixedArray(kEntryLength); + new_code_map->set(0, *native_context); + new_code_map->set(1, *code); + new_code_map->set(2, *literals); + } else { + // Copy old map and append one new entry. + Handle<FixedArray> old_code_map(FixedArray::cast(value)); + ASSERT_EQ(-1, shared->SearchOptimizedCodeMap(*native_context)); + int old_length = old_code_map->length(); + int new_length = old_length + kEntryLength; + new_code_map = FACTORY->NewFixedArray(new_length); + old_code_map->CopyTo(0, *new_code_map, 0, old_length); + new_code_map->set(old_length, *native_context); + new_code_map->set(old_length + 1, *code); + new_code_map->set(old_length + 2, *literals); + } +#ifdef DEBUG + for (int i = 0; i < new_code_map->length(); i += kEntryLength) { + ASSERT(new_code_map->get(i)->IsNativeContext()); + ASSERT(new_code_map->get(i + 1)->IsCode()); + ASSERT(Code::cast(new_code_map->get(i + 1))->kind() == + Code::OPTIMIZED_FUNCTION); + ASSERT(new_code_map->get(i + 2)->IsFixedArray()); + } +#endif + shared->set_optimized_code_map(*new_code_map); +} + + +void SharedFunctionInfo::InstallFromOptimizedCodeMap(JSFunction* function, + int index) { + ASSERT(index > 0); + ASSERT(optimized_code_map()->IsFixedArray()); + FixedArray* code_map = FixedArray::cast(optimized_code_map()); + if (!bound()) { + FixedArray* cached_literals = FixedArray::cast(code_map->get(index + 1)); + ASSERT(cached_literals != NULL); + function->set_literals(cached_literals); + } + Code* code = Code::cast(code_map->get(index)); + ASSERT(code != NULL); + ASSERT(function->context()->native_context() == code_map->get(index - 1)); + function->ReplaceCode(code); + code->MakeYoung(); +} + + bool JSFunction::CompileLazy(Handle<JSFunction> function, ClearExceptionFlag flag) { bool result = true; @@ -7443,7 +7960,8 @@ bool JSFunction::CompileLazy(Handle<JSFunction> function, function->ReplaceCode(function->shared()->code()); function->shared()->set_code_age(0); } else { - CompilationInfo info(function); + ASSERT(function->shared()->allows_lazy_compilation()); + CompilationInfoWithZone info(function); result = CompileLazyHelper(&info, flag); ASSERT(!result || function->is_compiled()); } @@ -7452,14 +7970,20 @@ bool JSFunction::CompileLazy(Handle<JSFunction> function, bool JSFunction::CompileOptimized(Handle<JSFunction> function, - int osr_ast_id, + BailoutId osr_ast_id, ClearExceptionFlag flag) { - CompilationInfo info(function); + CompilationInfoWithZone info(function); info.SetOptimizing(osr_ast_id); return CompileLazyHelper(&info, flag); } +bool JSFunction::EnsureCompiled(Handle<JSFunction> function, + ClearExceptionFlag flag) { + return function->is_compiled() || CompileLazy(function, flag); +} + + bool JSFunction::IsInlineable() { if (IsBuiltin()) return false; SharedFunctionInfo* shared_info = shared(); @@ -7474,19 +7998,86 @@ bool JSFunction::IsInlineable() { } +MaybeObject* JSObject::OptimizeAsPrototype() { + if (IsGlobalObject()) return this; + + // Make sure prototypes are fast objects and their maps have the bit set + // so they remain fast. + if (!HasFastProperties()) { + MaybeObject* new_proto = TransformToFastProperties(0); + if (new_proto->IsFailure()) return new_proto; + ASSERT(new_proto == this); + } + return this; +} + + +MUST_USE_RESULT static MaybeObject* CacheInitialJSArrayMaps( + Context* native_context, Map* initial_map) { + // Replace all of the cached initial array maps in the native context with + // the appropriate transitioned elements kind maps. + Heap* heap = native_context->GetHeap(); + MaybeObject* maybe_maps = + heap->AllocateFixedArrayWithHoles(kElementsKindCount); + FixedArray* maps; + if (!maybe_maps->To(&maps)) return maybe_maps; + + Map* current_map = initial_map; + ElementsKind kind = current_map->elements_kind(); + ASSERT(kind == GetInitialFastElementsKind()); + maps->set(kind, current_map); + for (int i = GetSequenceIndexFromFastElementsKind(kind) + 1; + i < kFastElementsKindCount; ++i) { + Map* new_map; + ElementsKind next_kind = GetFastElementsKindFromSequenceIndex(i); + MaybeObject* maybe_new_map = + current_map->CopyAsElementsKind(next_kind, INSERT_TRANSITION); + if (!maybe_new_map->To(&new_map)) return maybe_new_map; + maps->set(next_kind, new_map); + current_map = new_map; + } + native_context->set_js_array_maps(maps); + return initial_map; +} + + MaybeObject* JSFunction::SetInstancePrototype(Object* value) { ASSERT(value->IsJSReceiver()); Heap* heap = GetHeap(); + + // First some logic for the map of the prototype to make sure it is in fast + // mode. + if (value->IsJSObject()) { + MaybeObject* ok = JSObject::cast(value)->OptimizeAsPrototype(); + if (ok->IsFailure()) return ok; + } + + // Now some logic for the maps of the objects that are created by using this + // function as a constructor. if (has_initial_map()) { - // If the function has allocated the initial map - // replace it with a copy containing the new prototype. + // If the function has allocated the initial map replace it with a + // copy containing the new prototype. Also complete any in-object + // slack tracking that is in progress at this point because it is + // still tracking the old copy. + if (shared()->IsInobjectSlackTrackingInProgress()) { + shared()->CompleteInobjectSlackTracking(); + } Map* new_map; - MaybeObject* maybe_new_map = initial_map()->CopyDropTransitions(); - if (!maybe_new_map->To(&new_map)) return maybe_new_map; + MaybeObject* maybe_object = initial_map()->Copy(); + if (!maybe_object->To(&new_map)) return maybe_object; new_map->set_prototype(value); - MaybeObject* maybe_object = - set_initial_map_and_cache_transitions(new_map); - if (maybe_object->IsFailure()) return maybe_object; + + // If the function is used as the global Array function, cache the + // initial map (and transitioned versions) in the native context. + Context* native_context = context()->native_context(); + Object* array_function = native_context->get(Context::ARRAY_FUNCTION_INDEX); + if (array_function->IsJSFunction() && + this == JSFunction::cast(array_function)) { + MaybeObject* ok = CacheInitialJSArrayMaps(native_context, new_map); + if (ok->IsFailure()) return ok; + } + + set_initial_map(new_map); } else { // Put the value in the initial map field until an initial map is // needed. At that point, a new initial map is created and the @@ -7511,15 +8102,15 @@ MaybeObject* JSFunction::SetPrototype(Object* value) { // Remove map transitions because they point to maps with a // different prototype. Map* new_map; - { MaybeObject* maybe_new_map = map()->CopyDropTransitions(); - if (!maybe_new_map->To(&new_map)) return maybe_new_map; - } + MaybeObject* maybe_new_map = map()->Copy(); + if (!maybe_new_map->To(&new_map)) return maybe_new_map; + Heap* heap = new_map->GetHeap(); set_map(new_map); new_map->set_constructor(value); new_map->set_non_instance_prototype(true); construct_prototype = - heap->isolate()->context()->global_context()-> + heap->isolate()->context()->native_context()-> initial_object_prototype(); } else { map()->set_non_instance_prototype(false); @@ -7529,41 +8120,36 @@ MaybeObject* JSFunction::SetPrototype(Object* value) { } -Object* JSFunction::RemovePrototype() { - Context* global_context = context()->global_context(); +void JSFunction::RemovePrototype() { + Context* native_context = context()->native_context(); Map* no_prototype_map = shared()->is_classic_mode() - ? global_context->function_without_prototype_map() - : global_context->strict_mode_function_without_prototype_map(); + ? native_context->function_without_prototype_map() + : native_context->strict_mode_function_without_prototype_map(); - if (map() == no_prototype_map) { - // Be idempotent. - return this; - } + if (map() == no_prototype_map) return; ASSERT(map() == (shared()->is_classic_mode() - ? global_context->function_map() - : global_context->strict_mode_function_map())); + ? native_context->function_map() + : native_context->strict_mode_function_map())); set_map(no_prototype_map); set_prototype_or_initial_map(no_prototype_map->GetHeap()->the_hole_value()); - return this; } -Object* JSFunction::SetInstanceClassName(String* name) { +void JSFunction::SetInstanceClassName(String* name) { shared()->set_instance_class_name(name); - return this; } void JSFunction::PrintName(FILE* out) { SmartArrayPointer<char> name = shared()->DebugName()->ToCString(); - PrintF(out, "%s", *name); + FPrintF(out, "%s", *name); } -Context* JSFunction::GlobalContextFromLiterals(FixedArray* literals) { - return Context::cast(literals->get(JSFunction::kLiteralGlobalContextIndex)); +Context* JSFunction::NativeContextFromLiterals(FixedArray* literals) { + return Context::cast(literals->get(JSFunction::kLiteralNativeContextIndex)); } @@ -7631,26 +8217,33 @@ bool SharedFunctionInfo::CanGenerateInlineConstructor(Object* prototype) { return false; } - // If the prototype is null inline constructors cause no problems. - if (!prototype->IsJSObject()) { - ASSERT(prototype->IsNull()); - return true; - } - Heap* heap = GetHeap(); - // Traverse the proposed prototype chain looking for setters for properties of - // the same names as are set by the inline constructor. + // Traverse the proposed prototype chain looking for properties of the + // same names as are set by the inline constructor. for (Object* obj = prototype; obj != heap->null_value(); obj = obj->GetPrototype()) { - JSObject* js_object = JSObject::cast(obj); + JSReceiver* receiver = JSReceiver::cast(obj); for (int i = 0; i < this_property_assignments_count(); i++) { LookupResult result(heap->isolate()); String* name = GetThisPropertyAssignmentName(i); - js_object->LocalLookupRealNamedProperty(name, &result); - if (result.IsFound() && result.type() == CALLBACKS) { - return false; + receiver->LocalLookup(name, &result); + if (result.IsFound()) { + switch (result.type()) { + case NORMAL: + case FIELD: + case CONSTANT_FUNCTION: + break; + case INTERCEPTOR: + case CALLBACKS: + case HANDLER: + return false; + case TRANSITION: + case NONEXISTENT: + UNREACHABLE(); + break; + } } } } @@ -7795,7 +8388,7 @@ void SharedFunctionInfo::EnableDeoptimizationSupport(Code* recompiled) { } -void SharedFunctionInfo::DisableOptimization() { +void SharedFunctionInfo::DisableOptimization(const char* reason) { // Disable optimization for the shared function info and mark the // code as non-optimizable. The marker on the shared function info // is there because we flush non-optimized code thereby loosing the @@ -7811,13 +8404,14 @@ void SharedFunctionInfo::DisableOptimization() { code()->set_optimizable(false); } if (FLAG_trace_opt) { - PrintF("[disabled optimization for %s]\n", *DebugName()->ToCString()); + PrintF("[disabled optimization for %s, reason: %s]\n", + *DebugName()->ToCString(), reason); } } -bool SharedFunctionInfo::VerifyBailoutId(int id) { - ASSERT(id != AstNode::kNoNumber); +bool SharedFunctionInfo::VerifyBailoutId(BailoutId id) { + ASSERT(!id.IsNone()); Code* unoptimized = code(); DeoptimizationOutputData* data = DeoptimizationOutputData::cast(unoptimized->deoptimization_data()); @@ -7859,8 +8453,7 @@ void SharedFunctionInfo::DetachInitialMap() { Map* map = reinterpret_cast<Map*>(initial_map()); // Make the map remember to restore the link if it survives the GC. - map->set_bit_field3( - map->bit_field3() | (1 << Map::kAttachedToSharedFunctionInfo)); + map->set_attached_to_shared_function_info(true); // Undo state changes made by StartInobjectTracking (except the // construction_count). This way if the initial map does not survive the GC @@ -7868,7 +8461,7 @@ void SharedFunctionInfo::DetachInitialMap() { // constructor is called. The countdown will continue and (possibly after // several more GCs) CompleteInobjectSlackTracking will eventually be called. Heap* heap = map->GetHeap(); - set_initial_map(heap->raw_unchecked_undefined_value()); + set_initial_map(heap->undefined_value()); Builtins* builtins = heap->isolate()->builtins(); ASSERT_EQ(builtins->builtin(Builtins::kJSConstructStubCountdown), *RawField(this, kConstructStubOffset)); @@ -7880,8 +8473,7 @@ void SharedFunctionInfo::DetachInitialMap() { // Called from GC, hence reinterpret_cast and unchecked accessors. void SharedFunctionInfo::AttachInitialMap(Map* map) { - map->set_bit_field3( - map->bit_field3() & ~(1 << Map::kAttachedToSharedFunctionInfo)); + map->set_attached_to_shared_function_info(false); // Resume inobject slack tracking. set_initial_map(map); @@ -7900,12 +8492,13 @@ void SharedFunctionInfo::ResetForNewContext(int new_ic_age) { if (code()->kind() == Code::FUNCTION) { code()->set_profiler_ticks(0); if (optimization_disabled() && - opt_count() >= Compiler::kDefaultMaxOptCount) { + opt_count() >= FLAG_max_opt_count) { // Re-enable optimizations if they were disabled due to opt_count limit. set_optimization_disabled(false); code()->set_optimizable(true); } set_opt_count(0); + set_deopt_count(0); } } @@ -7953,9 +8546,20 @@ void SharedFunctionInfo::CompleteInobjectSlackTracking() { } -void SharedFunctionInfo::SharedFunctionInfoIterateBody(ObjectVisitor* v) { - v->VisitSharedFunctionInfo(this); - SharedFunctionInfo::BodyDescriptor::IterateBody(this, v); +int SharedFunctionInfo::SearchOptimizedCodeMap(Context* native_context) { + ASSERT(native_context->IsNativeContext()); + if (!FLAG_cache_optimized_code) return -1; + Object* value = optimized_code_map(); + if (!value->IsSmi()) { + FixedArray* optimized_code_map = FixedArray::cast(value); + int length = optimized_code_map->length(); + for (int i = 0; i < length; i += 3) { + if (optimized_code_map->get(i) == native_context) { + return i + 1; + } + } + } + return -1; } @@ -7984,6 +8588,15 @@ void ObjectVisitor::VisitCodeTarget(RelocInfo* rinfo) { } +void ObjectVisitor::VisitCodeAgeSequence(RelocInfo* rinfo) { + ASSERT(RelocInfo::IsCodeAgeSequence(rinfo->rmode())); + Object* stub = rinfo->code_age_stub(); + if (stub) { + VisitPointer(&stub); + } +} + + void ObjectVisitor::VisitCodeEntry(Address entry_address) { Object* code = Code::GetObjectFromEntryAddress(entry_address); Object* old_code = code; @@ -8183,7 +8796,6 @@ void Code::ClearTypeFeedbackCells(Heap* heap) { TypeFeedbackCells* type_feedback_cells = TypeFeedbackInfo::cast(raw_info)->type_feedback_cells(); for (int i = 0; i < type_feedback_cells->CellCount(); i++) { - ASSERT(type_feedback_cells->AstId(i)->IsSmi()); JSGlobalPropertyCell* cell = type_feedback_cells->Cell(i); cell->set_value(TypeFeedbackCells::RawUninitializedSentinel(heap)); } @@ -8191,25 +8803,124 @@ void Code::ClearTypeFeedbackCells(Heap* heap) { } +bool Code::allowed_in_shared_map_code_cache() { + return is_keyed_load_stub() || is_keyed_store_stub() || + (is_compare_ic_stub() && compare_state() == CompareIC::KNOWN_OBJECTS); +} + + +void Code::MakeCodeAgeSequenceYoung(byte* sequence) { + PatchPlatformCodeAge(sequence, kNoAge, NO_MARKING_PARITY); +} + + +void Code::MakeYoung() { + byte* sequence = FindCodeAgeSequence(); + if (sequence != NULL) { + PatchPlatformCodeAge(sequence, kNoAge, NO_MARKING_PARITY); + } +} + + +void Code::MakeOlder(MarkingParity current_parity) { + byte* sequence = FindCodeAgeSequence(); + if (sequence != NULL) { + Age age; + MarkingParity code_parity; + GetCodeAgeAndParity(sequence, &age, &code_parity); + if (age != kLastCodeAge && code_parity != current_parity) { + PatchPlatformCodeAge(sequence, static_cast<Age>(age + 1), + current_parity); + } + } +} + + +bool Code::IsOld() { + byte* sequence = FindCodeAgeSequence(); + if (sequence == NULL) return false; + Age age; + MarkingParity parity; + GetCodeAgeAndParity(sequence, &age, &parity); + return age >= kSexagenarianCodeAge; +} + + +byte* Code::FindCodeAgeSequence() { + return FLAG_age_code && + strlen(FLAG_stop_at) == 0 && + !ProfileEntryHookStub::HasEntryHook() && + (kind() == OPTIMIZED_FUNCTION || + (kind() == FUNCTION && !has_debug_break_slots())) + ? FindPlatformCodeAgeSequence() + : NULL; +} + + +void Code::GetCodeAgeAndParity(Code* code, Age* age, + MarkingParity* parity) { + Isolate* isolate = Isolate::Current(); + Builtins* builtins = isolate->builtins(); + Code* stub = NULL; +#define HANDLE_CODE_AGE(AGE) \ + stub = *builtins->Make##AGE##CodeYoungAgainEvenMarking(); \ + if (code == stub) { \ + *age = k##AGE##CodeAge; \ + *parity = EVEN_MARKING_PARITY; \ + return; \ + } \ + stub = *builtins->Make##AGE##CodeYoungAgainOddMarking(); \ + if (code == stub) { \ + *age = k##AGE##CodeAge; \ + *parity = ODD_MARKING_PARITY; \ + return; \ + } + CODE_AGE_LIST(HANDLE_CODE_AGE) +#undef HANDLE_CODE_AGE + UNREACHABLE(); +} + + +Code* Code::GetCodeAgeStub(Age age, MarkingParity parity) { + Isolate* isolate = Isolate::Current(); + Builtins* builtins = isolate->builtins(); + switch (age) { +#define HANDLE_CODE_AGE(AGE) \ + case k##AGE##CodeAge: { \ + Code* stub = parity == EVEN_MARKING_PARITY \ + ? *builtins->Make##AGE##CodeYoungAgainEvenMarking() \ + : *builtins->Make##AGE##CodeYoungAgainOddMarking(); \ + return stub; \ + } + CODE_AGE_LIST(HANDLE_CODE_AGE) +#undef HANDLE_CODE_AGE + default: + UNREACHABLE(); + break; + } + return NULL; +} + + #ifdef ENABLE_DISASSEMBLER void DeoptimizationInputData::DeoptimizationInputDataPrint(FILE* out) { disasm::NameConverter converter; int deopt_count = DeoptCount(); - PrintF(out, "Deoptimization Input Data (deopt points = %d)\n", deopt_count); + FPrintF(out, "Deoptimization Input Data (deopt points = %d)\n", deopt_count); if (0 == deopt_count) return; - PrintF(out, "%6s %6s %6s %6s %12s\n", "index", "ast id", "argc", "pc", + FPrintF(out, "%6s %6s %6s %6s %12s\n", "index", "ast id", "argc", "pc", FLAG_print_code_verbose ? "commands" : ""); for (int i = 0; i < deopt_count; i++) { - PrintF(out, "%6d %6d %6d %6d", + FPrintF(out, "%6d %6d %6d %6d", i, - AstId(i)->value(), + AstId(i).ToInt(), ArgumentsStackHeight(i)->value(), Pc(i)->value()); if (!FLAG_print_code_verbose) { - PrintF(out, "\n"); + FPrintF(out, "\n"); continue; } // Print details of the frame translation. @@ -8220,7 +8931,7 @@ void DeoptimizationInputData::DeoptimizationInputDataPrint(FILE* out) { ASSERT(Translation::BEGIN == opcode); int frame_count = iterator.Next(); int jsframe_count = iterator.Next(); - PrintF(out, " %s {frame count=%d, js frame count=%d}\n", + FPrintF(out, " %s {frame count=%d, js frame count=%d}\n", Translation::StringFor(opcode), frame_count, jsframe_count); @@ -8228,7 +8939,7 @@ void DeoptimizationInputData::DeoptimizationInputDataPrint(FILE* out) { while (iterator.HasNext() && Translation::BEGIN != (opcode = static_cast<Translation::Opcode>(iterator.Next()))) { - PrintF(out, "%24s %s ", "", Translation::StringFor(opcode)); + FPrintF(out, "%24s %s ", "", Translation::StringFor(opcode)); switch (opcode) { case Translation::BEGIN: @@ -8238,12 +8949,15 @@ void DeoptimizationInputData::DeoptimizationInputDataPrint(FILE* out) { case Translation::JS_FRAME: { int ast_id = iterator.Next(); int function_id = iterator.Next(); - JSFunction* function = - JSFunction::cast(LiteralArray()->get(function_id)); unsigned height = iterator.Next(); - PrintF(out, "{ast_id=%d, function=", ast_id); - function->PrintName(out); - PrintF(out, ", height=%u}", height); + FPrintF(out, "{ast_id=%d, function=", ast_id); + if (function_id != Translation::kSelfLiteralId) { + Object* function = LiteralArray()->get(function_id); + JSFunction::cast(function)->PrintName(out); + } else { + FPrintF(out, "<self>"); + } + FPrintF(out, ", height=%u}", height); break; } @@ -8253,9 +8967,20 @@ void DeoptimizationInputData::DeoptimizationInputDataPrint(FILE* out) { JSFunction* function = JSFunction::cast(LiteralArray()->get(function_id)); unsigned height = iterator.Next(); - PrintF(out, "{function="); + FPrintF(out, "{function="); + function->PrintName(out); + FPrintF(out, ", height=%u}", height); + break; + } + + case Translation::GETTER_STUB_FRAME: + case Translation::SETTER_STUB_FRAME: { + int function_id = iterator.Next(); + JSFunction* function = + JSFunction::cast(LiteralArray()->get(function_id)); + FPrintF(out, "{function="); function->PrintName(out); - PrintF(out, ", height=%u}", height); + FPrintF(out, "}"); break; } @@ -8264,58 +8989,72 @@ void DeoptimizationInputData::DeoptimizationInputDataPrint(FILE* out) { case Translation::REGISTER: { int reg_code = iterator.Next(); - PrintF(out, "{input=%s}", converter.NameOfCPURegister(reg_code)); + FPrintF(out, "{input=%s}", converter.NameOfCPURegister(reg_code)); break; } case Translation::INT32_REGISTER: { int reg_code = iterator.Next(); - PrintF(out, "{input=%s}", converter.NameOfCPURegister(reg_code)); + FPrintF(out, "{input=%s}", converter.NameOfCPURegister(reg_code)); + break; + } + + case Translation::UINT32_REGISTER: { + int reg_code = iterator.Next(); + FPrintF(out, + "{input=%s (unsigned)}", + converter.NameOfCPURegister(reg_code)); break; } case Translation::DOUBLE_REGISTER: { int reg_code = iterator.Next(); - PrintF(out, "{input=%s}", + FPrintF(out, "{input=%s}", DoubleRegister::AllocationIndexToString(reg_code)); break; } case Translation::STACK_SLOT: { int input_slot_index = iterator.Next(); - PrintF(out, "{input=%d}", input_slot_index); + FPrintF(out, "{input=%d}", input_slot_index); break; } case Translation::INT32_STACK_SLOT: { int input_slot_index = iterator.Next(); - PrintF(out, "{input=%d}", input_slot_index); + FPrintF(out, "{input=%d}", input_slot_index); + break; + } + + case Translation::UINT32_STACK_SLOT: { + int input_slot_index = iterator.Next(); + FPrintF(out, "{input=%d (unsigned)}", input_slot_index); break; } case Translation::DOUBLE_STACK_SLOT: { int input_slot_index = iterator.Next(); - PrintF(out, "{input=%d}", input_slot_index); + FPrintF(out, "{input=%d}", input_slot_index); break; } case Translation::LITERAL: { unsigned literal_index = iterator.Next(); - PrintF(out, "{literal_id=%u}", literal_index); + FPrintF(out, "{literal_id=%u}", literal_index); break; } case Translation::ARGUMENTS_OBJECT: break; } - PrintF(out, "\n"); + FPrintF(out, "\n"); } } } void DeoptimizationOutputData::DeoptimizationOutputDataPrint(FILE* out) { - PrintF(out, "Deoptimization Output Data (deopt points = %d)\n", + FPrintF(out, "Deoptimization Output Data (deopt points = %d)\n", this->DeoptPoints()); if (this->DeoptPoints() == 0) return; @@ -8323,7 +9062,7 @@ void DeoptimizationOutputData::DeoptimizationOutputDataPrint(FILE* out) { for (int i = 0; i < this->DeoptPoints(); i++) { int pc_and_state = this->PcAndState(i)->value(); PrintF("%6d %8d %s\n", - this->AstId(i)->value(), + this->AstId(i).ToInt(), FullCodeGenerator::PcField::decode(pc_and_state), FullCodeGenerator::State2String( FullCodeGenerator::StateField::decode(pc_and_state))); @@ -8369,18 +9108,15 @@ const char* Code::ICState2String(InlineCacheState state) { } -const char* Code::PropertyType2String(PropertyType type) { +const char* Code::StubType2String(StubType type) { switch (type) { case NORMAL: return "NORMAL"; case FIELD: return "FIELD"; case CONSTANT_FUNCTION: return "CONSTANT_FUNCTION"; case CALLBACKS: return "CALLBACKS"; - case HANDLER: return "HANDLER"; case INTERCEPTOR: return "INTERCEPTOR"; case MAP_TRANSITION: return "MAP_TRANSITION"; - case ELEMENTS_TRANSITION: return "ELEMENTS_TRANSITION"; - case CONSTANT_TRANSITION: return "CONSTANT_TRANSITION"; - case NULL_DESCRIPTOR: return "NULL_DESCRIPTOR"; + case NONEXISTENT: return "NONEXISTENT"; } UNREACHABLE(); // keep the compiler happy return NULL; @@ -8405,43 +9141,43 @@ void Code::PrintExtraICState(FILE* out, Kind kind, ExtraICState extra) { break; } if (name != NULL) { - PrintF(out, "extra_ic_state = %s\n", name); + FPrintF(out, "extra_ic_state = %s\n", name); } else { - PrintF(out, "extra_ic_state = %d\n", extra); + FPrintF(out, "extra_ic_state = %d\n", extra); } } void Code::Disassemble(const char* name, FILE* out) { - PrintF(out, "kind = %s\n", Kind2String(kind())); + FPrintF(out, "kind = %s\n", Kind2String(kind())); if (is_inline_cache_stub()) { - PrintF(out, "ic_state = %s\n", ICState2String(ic_state())); + FPrintF(out, "ic_state = %s\n", ICState2String(ic_state())); PrintExtraICState(out, kind(), extra_ic_state()); if (ic_state() == MONOMORPHIC) { - PrintF(out, "type = %s\n", PropertyType2String(type())); + FPrintF(out, "type = %s\n", StubType2String(type())); } if (is_call_stub() || is_keyed_call_stub()) { - PrintF(out, "argc = %d\n", arguments_count()); + FPrintF(out, "argc = %d\n", arguments_count()); } if (is_compare_ic_stub()) { CompareIC::State state = CompareIC::ComputeState(this); - PrintF(out, "compare_state = %s\n", CompareIC::GetStateName(state)); + FPrintF(out, "compare_state = %s\n", CompareIC::GetStateName(state)); } if (is_compare_ic_stub() && major_key() == CodeStub::CompareIC) { Token::Value op = CompareIC::ComputeOperation(this); - PrintF(out, "compare_operation = %s\n", Token::Name(op)); + FPrintF(out, "compare_operation = %s\n", Token::Name(op)); } } if ((name != NULL) && (name[0] != '\0')) { - PrintF(out, "name = %s\n", name); + FPrintF(out, "name = %s\n", name); } if (kind() == OPTIMIZED_FUNCTION) { - PrintF(out, "stack_slots = %d\n", stack_slots()); + FPrintF(out, "stack_slots = %d\n", stack_slots()); } - PrintF(out, "Instructions (size = %d)\n", instruction_size()); + FPrintF(out, "Instructions (size = %d)\n", instruction_size()); Disassembler::Decode(out, this); - PrintF(out, "\n"); + FPrintF(out, "\n"); if (kind() == FUNCTION) { DeoptimizationOutputData* data = @@ -8456,24 +9192,26 @@ void Code::Disassemble(const char* name, FILE* out) { if (kind() == OPTIMIZED_FUNCTION) { SafepointTable table(this); - PrintF(out, "Safepoints (size = %u)\n", table.size()); + FPrintF(out, "Safepoints (size = %u)\n", table.size()); for (unsigned i = 0; i < table.length(); i++) { unsigned pc_offset = table.GetPcOffset(i); - PrintF(out, "%p %4d ", (instruction_start() + pc_offset), pc_offset); + FPrintF(out, "%p %4d ", (instruction_start() + pc_offset), pc_offset); table.PrintEntry(i); - PrintF(out, " (sp -> fp)"); + FPrintF(out, " (sp -> fp)"); SafepointEntry entry = table.GetEntry(i); if (entry.deoptimization_index() != Safepoint::kNoDeoptimizationIndex) { - PrintF(out, " %6d", entry.deoptimization_index()); + FPrintF(out, " %6d", entry.deoptimization_index()); } else { - PrintF(out, " <none>"); + FPrintF(out, " <none>"); } if (entry.argument_count() > 0) { - PrintF(out, " argc: %d", entry.argument_count()); + FPrintF(out, " argc: %d", entry.argument_count()); } - PrintF(out, "\n"); + FPrintF(out, "\n"); } - PrintF(out, "\n"); + FPrintF(out, "\n"); + // Just print if type feedback info is ever used for optimized code. + ASSERT(type_feedback_info()->IsUndefined()); } else if (kind() == FUNCTION) { unsigned offset = stack_check_table_offset(); // If there is no stack check table, the "table start" will at or after @@ -8482,19 +9220,25 @@ void Code::Disassemble(const char* name, FILE* out) { unsigned* address = reinterpret_cast<unsigned*>(instruction_start() + offset); unsigned length = address[0]; - PrintF(out, "Stack checks (size = %u)\n", length); - PrintF(out, "ast_id pc_offset\n"); + FPrintF(out, "Stack checks (size = %u)\n", length); + FPrintF(out, "ast_id pc_offset\n"); for (unsigned i = 0; i < length; ++i) { unsigned index = (2 * i) + 1; - PrintF(out, "%6u %9u\n", address[index], address[index + 1]); + FPrintF(out, "%6u %9u\n", address[index], address[index + 1]); } - PrintF(out, "\n"); + FPrintF(out, "\n"); } +#ifdef OBJECT_PRINT + if (!type_feedback_info()->IsUndefined()) { + TypeFeedbackInfo::cast(type_feedback_info())->TypeFeedbackInfoPrint(out); + FPrintF(out, "\n"); + } +#endif } PrintF("RelocInfo (size = %d)\n", relocation_size()); for (RelocIterator it(this); !it.done(); it.next()) it.rinfo()->Print(out); - PrintF(out, "\n"); + FPrintF(out, "\n"); } #endif // ENABLE_DISASSEMBLER @@ -8502,7 +9246,7 @@ void Code::Disassemble(const char* name, FILE* out) { MaybeObject* JSObject::SetFastElementsCapacityAndLength( int capacity, int length, - SetFastElementsCapacityMode set_capacity_mode) { + SetFastElementsCapacitySmiMode smi_mode) { Heap* heap = GetHeap(); // We should never end in here with a pixel or external array. ASSERT(!HasExternalArrayElements()); @@ -8513,34 +9257,40 @@ MaybeObject* JSObject::SetFastElementsCapacityAndLength( if (!maybe->To(&new_elements)) return maybe; } - // Find the new map to use for this object if there is a map change. - Map* new_map = NULL; - if (elements()->map() != heap->non_strict_arguments_elements_map()) { - // The resized array has FAST_SMI_ONLY_ELEMENTS if the capacity mode forces - // it, or if it's allowed and the old elements array contained only SMIs. - bool has_fast_smi_only_elements = - (set_capacity_mode == kForceSmiOnlyElements) || - ((set_capacity_mode == kAllowSmiOnlyElements) && - (elements()->map()->has_fast_smi_only_elements() || - elements() == heap->empty_fixed_array())); - ElementsKind elements_kind = has_fast_smi_only_elements - ? FAST_SMI_ONLY_ELEMENTS - : FAST_ELEMENTS; - MaybeObject* maybe = GetElementsTransitionMap(GetIsolate(), elements_kind); - if (!maybe->To(&new_map)) return maybe; + ElementsKind elements_kind = GetElementsKind(); + ElementsKind new_elements_kind; + // The resized array has FAST_*_SMI_ELEMENTS if the capacity mode forces it, + // or if it's allowed and the old elements array contained only SMIs. + bool has_fast_smi_elements = + (smi_mode == kForceSmiElements) || + ((smi_mode == kAllowSmiElements) && HasFastSmiElements()); + if (has_fast_smi_elements) { + if (IsHoleyElementsKind(elements_kind)) { + new_elements_kind = FAST_HOLEY_SMI_ELEMENTS; + } else { + new_elements_kind = FAST_SMI_ELEMENTS; + } + } else { + if (IsHoleyElementsKind(elements_kind)) { + new_elements_kind = FAST_HOLEY_ELEMENTS; + } else { + new_elements_kind = FAST_ELEMENTS; + } } - FixedArrayBase* old_elements = elements(); - ElementsKind elements_kind = GetElementsKind(); ElementsAccessor* accessor = ElementsAccessor::ForKind(elements_kind); - ElementsKind to_kind = (elements_kind == FAST_SMI_ONLY_ELEMENTS) - ? FAST_SMI_ONLY_ELEMENTS - : FAST_ELEMENTS; { MaybeObject* maybe_obj = - accessor->CopyElements(this, new_elements, to_kind); + accessor->CopyElements(this, new_elements, new_elements_kind); if (maybe_obj->IsFailure()) return maybe_obj; } if (elements_kind != NON_STRICT_ARGUMENTS_ELEMENTS) { + Map* new_map = map(); + if (new_elements_kind != elements_kind) { + MaybeObject* maybe = + GetElementsTransitionMap(GetIsolate(), new_elements_kind); + if (!maybe->To(&new_map)) return maybe; + } + ValidateElements(); set_map_and_elements(new_map, new_elements); } else { FixedArray* parameter_map = FixedArray::cast(old_elements); @@ -8552,11 +9302,9 @@ MaybeObject* JSObject::SetFastElementsCapacityAndLength( GetElementsKind(), new_elements); } - // Update the length if necessary. if (IsJSArray()) { JSArray::cast(this)->set_length(Smi::FromInt(length)); } - return new_elements; } @@ -8574,20 +9322,28 @@ MaybeObject* JSObject::SetFastDoubleElementsCapacityAndLength( if (!maybe_obj->To(&elems)) return maybe_obj; } + ElementsKind elements_kind = GetElementsKind(); + ElementsKind new_elements_kind = elements_kind; + if (IsHoleyElementsKind(elements_kind)) { + new_elements_kind = FAST_HOLEY_DOUBLE_ELEMENTS; + } else { + new_elements_kind = FAST_DOUBLE_ELEMENTS; + } + Map* new_map; { MaybeObject* maybe_obj = - GetElementsTransitionMap(heap->isolate(), FAST_DOUBLE_ELEMENTS); + GetElementsTransitionMap(heap->isolate(), new_elements_kind); if (!maybe_obj->To(&new_map)) return maybe_obj; } FixedArrayBase* old_elements = elements(); - ElementsKind elements_kind = GetElementsKind(); ElementsAccessor* accessor = ElementsAccessor::ForKind(elements_kind); { MaybeObject* maybe_obj = accessor->CopyElements(this, elems, FAST_DOUBLE_ELEMENTS); if (maybe_obj->IsFailure()) return maybe_obj; } if (elements_kind != NON_STRICT_ARGUMENTS_ELEMENTS) { + ValidateElements(); set_map_and_elements(new_map, elems); } else { FixedArray* parameter_map = FixedArray::cast(old_elements); @@ -8596,7 +9352,7 @@ MaybeObject* JSObject::SetFastDoubleElementsCapacityAndLength( if (FLAG_trace_elements_transitions) { PrintElementsTransition(stdout, elements_kind, old_elements, - FAST_DOUBLE_ELEMENTS, elems); + GetElementsKind(), elems); } if (IsJSArray()) { @@ -8636,8 +9392,8 @@ MaybeObject* JSArray::SetElementsLength(Object* len) { } -Object* Map::GetPrototypeTransition(Object* prototype) { - FixedArray* cache = prototype_transitions(); +Map* Map::GetPrototypeTransition(Object* prototype) { + FixedArray* cache = GetPrototypeTransitions(); int number_of_transitions = NumberOfProtoTransitions(); const int proto_offset = kProtoTransitionHeaderSize + kProtoTransitionPrototypeOffset; @@ -8646,8 +9402,7 @@ Object* Map::GetPrototypeTransition(Object* prototype) { for (int i = 0; i < number_of_transitions; i++) { if (cache->get(proto_offset + i * step) == prototype) { Object* map = cache->get(map_offset + i * step); - ASSERT(map->IsMap()); - return map; + return Map::cast(map); } } return NULL; @@ -8660,7 +9415,7 @@ MaybeObject* Map::PutPrototypeTransition(Object* prototype, Map* map) { // Don't cache prototype transition if this map is shared. if (is_shared() || !FLAG_cache_prototype_transitions) return this; - FixedArray* cache = prototype_transitions(); + FixedArray* cache = GetPrototypeTransitions(); const int step = kProtoTransitionElementsPerEntry; const int header = kProtoTransitionHeaderSize; @@ -8683,7 +9438,8 @@ MaybeObject* Map::PutPrototypeTransition(Object* prototype, Map* map) { new_cache->set(i + header, cache->get(i + header)); } cache = new_cache; - set_prototype_transitions(cache); + MaybeObject* set_result = SetPrototypeTransitions(cache); + if (set_result->IsFailure()) return set_result; } int last = transitions - 1; @@ -8696,6 +9452,22 @@ MaybeObject* Map::PutPrototypeTransition(Object* prototype, Map* map) { } +void Map::ZapTransitions() { + TransitionArray* transition_array = transitions(); + MemsetPointer(transition_array->data_start(), + GetHeap()->the_hole_value(), + transition_array->length()); +} + + +void Map::ZapPrototypeTransitions() { + FixedArray* proto_transitions = GetPrototypeTransitions(); + MemsetPointer(proto_transitions->data_start(), + GetHeap()->the_hole_value(), + proto_transitions->length()); +} + + MaybeObject* JSReceiver::SetPrototype(Object* value, bool skip_hidden_prototypes) { #ifdef DEBUG @@ -8755,21 +9527,24 @@ MaybeObject* JSReceiver::SetPrototype(Object* value, // Nothing to do if prototype is already set. if (map->prototype() == value) return value; - Object* new_map = map->GetPrototypeTransition(value); + if (value->IsJSObject()) { + MaybeObject* ok = JSObject::cast(value)->OptimizeAsPrototype(); + if (ok->IsFailure()) return ok; + } + + Map* new_map = map->GetPrototypeTransition(value); if (new_map == NULL) { - { MaybeObject* maybe_new_map = map->CopyDropTransitions(); - if (!maybe_new_map->ToObject(&new_map)) return maybe_new_map; - } + MaybeObject* maybe_new_map = map->Copy(); + if (!maybe_new_map->To(&new_map)) return maybe_new_map; - { MaybeObject* maybe_new_cache = - map->PutPrototypeTransition(value, Map::cast(new_map)); - if (maybe_new_cache->IsFailure()) return maybe_new_cache; - } + MaybeObject* maybe_new_cache = + map->PutPrototypeTransition(value, new_map); + if (maybe_new_cache->IsFailure()) return maybe_new_cache; - Map::cast(new_map)->set_prototype(value); + new_map->set_prototype(value); } - ASSERT(Map::cast(new_map)->prototype() == value); - real_receiver->set_map(Map::cast(new_map)); + ASSERT(new_map->prototype() == value); + real_receiver->set_map(new_map); heap->ClearInstanceofCache(); ASSERT(size == Size()); @@ -8790,64 +9565,7 @@ MaybeObject* JSObject::EnsureCanContainElements(Arguments* args, } -bool JSObject::HasElementWithInterceptor(JSReceiver* receiver, uint32_t index) { - Isolate* isolate = GetIsolate(); - // Make sure that the top context does not change when doing - // callbacks or interceptor calls. - AssertNoContextChange ncc; - HandleScope scope(isolate); - Handle<InterceptorInfo> interceptor(GetIndexedInterceptor()); - Handle<JSReceiver> receiver_handle(receiver); - Handle<JSObject> holder_handle(this); - CustomArguments args(isolate, interceptor->data(), receiver, this); - v8::AccessorInfo info(args.end()); - if (!interceptor->query()->IsUndefined()) { - v8::IndexedPropertyQuery query = - v8::ToCData<v8::IndexedPropertyQuery>(interceptor->query()); - LOG(isolate, - ApiIndexedPropertyAccess("interceptor-indexed-has", this, index)); - v8::Handle<v8::Integer> result; - { - // Leaving JavaScript. - VMState state(isolate, EXTERNAL); - result = query(index, info); - } - if (!result.IsEmpty()) { - ASSERT(result->IsInt32()); - return true; // absence of property is signaled by empty handle. - } - } else if (!interceptor->getter()->IsUndefined()) { - v8::IndexedPropertyGetter getter = - v8::ToCData<v8::IndexedPropertyGetter>(interceptor->getter()); - LOG(isolate, - ApiIndexedPropertyAccess("interceptor-indexed-has-get", this, index)); - v8::Handle<v8::Value> result; - { - // Leaving JavaScript. - VMState state(isolate, EXTERNAL); - result = getter(index, info); - } - if (!result.IsEmpty()) return true; - } - - if (holder_handle->GetElementsAccessor()->HasElement( - *receiver_handle, *holder_handle, index)) { - return true; - } - - if (holder_handle->IsStringObjectWithCharacterAt(index)) return true; - Object* pt = holder_handle->GetPrototype(); - if (pt->IsJSProxy()) { - // We need to follow the spec and simulate a call to [[GetOwnProperty]]. - return JSProxy::cast(pt)->GetElementAttributeWithHandler( - receiver, index) != ABSENT; - } - if (pt->IsNull()) return false; - return JSObject::cast(pt)->HasElementWithReceiver(*receiver_handle, index); -} - - -JSObject::LocalElementType JSObject::HasLocalElement(uint32_t index) { +JSObject::LocalElementType JSObject::GetLocalElementType(uint32_t index) { // Check access rights if needed. if (IsAccessCheckNeeded()) { Heap* heap = GetHeap(); @@ -8861,13 +9579,13 @@ JSObject::LocalElementType JSObject::HasLocalElement(uint32_t index) { Object* proto = GetPrototype(); if (proto->IsNull()) return UNDEFINED_ELEMENT; ASSERT(proto->IsJSGlobalObject()); - return JSObject::cast(proto)->HasLocalElement(index); + return JSObject::cast(proto)->GetLocalElementType(index); } // Check for lookup interceptor if (HasIndexedInterceptor()) { - return HasElementWithInterceptor(this, index) ? INTERCEPTED_ELEMENT - : UNDEFINED_ELEMENT; + return GetElementAttributeWithInterceptor(this, index, false) != ABSENT + ? INTERCEPTED_ELEMENT : UNDEFINED_ELEMENT; } // Handle [] on String objects. @@ -8876,8 +9594,10 @@ JSObject::LocalElementType JSObject::HasLocalElement(uint32_t index) { } switch (GetElementsKind()) { - case FAST_SMI_ONLY_ELEMENTS: - case FAST_ELEMENTS: { + case FAST_SMI_ELEMENTS: + case FAST_ELEMENTS: + case FAST_HOLEY_SMI_ELEMENTS: + case FAST_HOLEY_ELEMENTS: { uint32_t length = IsJSArray() ? static_cast<uint32_t> (Smi::cast(JSArray::cast(this)->length())->value()) : @@ -8888,7 +9608,8 @@ JSObject::LocalElementType JSObject::HasLocalElement(uint32_t index) { } break; } - case FAST_DOUBLE_ELEMENTS: { + case FAST_DOUBLE_ELEMENTS: + case FAST_HOLEY_DOUBLE_ELEMENTS: { uint32_t length = IsJSArray() ? static_cast<uint32_t> (Smi::cast(JSArray::cast(this)->length())->value()) : @@ -8953,40 +9674,6 @@ JSObject::LocalElementType JSObject::HasLocalElement(uint32_t index) { } -bool JSObject::HasElementWithReceiver(JSReceiver* receiver, uint32_t index) { - // Check access rights if needed. - if (IsAccessCheckNeeded()) { - Heap* heap = GetHeap(); - if (!heap->isolate()->MayIndexedAccess(this, index, v8::ACCESS_HAS)) { - heap->isolate()->ReportFailedAccessCheck(this, v8::ACCESS_HAS); - return false; - } - } - - // Check for lookup interceptor - if (HasIndexedInterceptor()) { - return HasElementWithInterceptor(receiver, index); - } - - ElementsAccessor* accessor = GetElementsAccessor(); - if (accessor->HasElement(receiver, this, index)) { - return true; - } - - // Handle [] on String objects. - if (this->IsStringObjectWithCharacterAt(index)) return true; - - Object* pt = GetPrototype(); - if (pt->IsNull()) return false; - if (pt->IsJSProxy()) { - // We need to follow the spec and simulate a call to [[GetOwnProperty]]. - return JSProxy::cast(pt)->GetElementAttributeWithHandler( - receiver, index) != ABSENT; - } - return JSObject::cast(pt)->HasElementWithReceiver(receiver, index); -} - - MaybeObject* JSObject::SetElementWithInterceptor(uint32_t index, Object* value, PropertyAttributes attributes, @@ -9041,6 +9728,7 @@ MaybeObject* JSObject::GetElementWithCallback(Object* receiver, Handle<AccessorInfo> data(AccessorInfo::cast(structure)); Object* fun_obj = data->getter(); v8::AccessorGetter call_fun = v8::ToCData<v8::AccessorGetter>(fun_obj); + if (call_fun == NULL) return isolate->heap()->undefined_value(); HandleScope scope(isolate); Handle<JSObject> self(JSObject::cast(receiver)); Handle<JSObject> holder_handle(JSObject::cast(holder)); @@ -9057,7 +9745,9 @@ MaybeObject* JSObject::GetElementWithCallback(Object* receiver, } RETURN_IF_SCHEDULED_EXCEPTION(isolate); if (result.IsEmpty()) return isolate->heap()->undefined_value(); - return *v8::Utils::OpenHandle(*result); + Handle<Object> result_internal = v8::Utils::OpenHandle(*result); + result_internal->VerifyApiCallResultType(); + return *result_internal; } // __defineGetter__ callback @@ -9172,7 +9862,7 @@ MaybeObject* JSObject::SetFastElement(uint32_t index, Object* value, StrictModeFlag strict_mode, bool check_prototype) { - ASSERT(HasFastTypeElements() || + ASSERT(HasFastSmiOrObjectElements() || HasFastArgumentsElements()); FixedArray* backing_store = FixedArray::cast(elements()); @@ -9198,13 +9888,29 @@ MaybeObject* JSObject::SetFastElement(uint32_t index, // Check if the length property of this object needs to be updated. uint32_t array_length = 0; bool must_update_array_length = false; + bool introduces_holes = true; if (IsJSArray()) { CHECK(JSArray::cast(this)->length()->ToArrayIndex(&array_length)); + introduces_holes = index > array_length; if (index >= array_length) { must_update_array_length = true; array_length = index + 1; } + } else { + introduces_holes = index >= capacity; + } + + // If the array is growing, and it's not growth by a single element at the + // end, make sure that the ElementsKind is HOLEY. + ElementsKind elements_kind = GetElementsKind(); + if (introduces_holes && + IsFastElementsKind(elements_kind) && + !IsFastHoleyElementsKind(elements_kind)) { + ElementsKind transitioned_kind = GetHoleyElementsKind(elements_kind); + MaybeObject* maybe = TransitionElementsKind(transitioned_kind); + if (maybe->IsFailure()) return maybe; } + // Check if the capacity of the backing store needs to be increased, or if // a transition to slow elements is necessary. if (index >= capacity) { @@ -9224,42 +9930,44 @@ MaybeObject* JSObject::SetFastElement(uint32_t index, } } // Convert to fast double elements if appropriate. - if (HasFastSmiOnlyElements() && !value->IsSmi() && value->IsNumber()) { + if (HasFastSmiElements() && !value->IsSmi() && value->IsNumber()) { MaybeObject* maybe = SetFastDoubleElementsCapacityAndLength(new_capacity, array_length); if (maybe->IsFailure()) return maybe; FixedDoubleArray::cast(elements())->set(index, value->Number()); + ValidateElements(); return value; } - // Change elements kind from SMI_ONLY to generic FAST if necessary. - if (HasFastSmiOnlyElements() && !value->IsSmi()) { + // Change elements kind from Smi-only to generic FAST if necessary. + if (HasFastSmiElements() && !value->IsSmi()) { Map* new_map; - { MaybeObject* maybe_new_map = GetElementsTransitionMap(GetIsolate(), - FAST_ELEMENTS); - if (!maybe_new_map->To(&new_map)) return maybe_new_map; - } + ElementsKind kind = HasFastHoleyElements() + ? FAST_HOLEY_ELEMENTS + : FAST_ELEMENTS; + MaybeObject* maybe_new_map = GetElementsTransitionMap(GetIsolate(), + kind); + if (!maybe_new_map->To(&new_map)) return maybe_new_map; + set_map(new_map); - if (FLAG_trace_elements_transitions) { - PrintElementsTransition(stdout, FAST_SMI_ONLY_ELEMENTS, elements(), - FAST_ELEMENTS, elements()); - } } // Increase backing store capacity if that's been decided previously. if (new_capacity != capacity) { FixedArray* new_elements; - SetFastElementsCapacityMode set_capacity_mode = - value->IsSmi() && HasFastSmiOnlyElements() - ? kAllowSmiOnlyElements - : kDontAllowSmiOnlyElements; + SetFastElementsCapacitySmiMode smi_mode = + value->IsSmi() && HasFastSmiElements() + ? kAllowSmiElements + : kDontAllowSmiElements; { MaybeObject* maybe = SetFastElementsCapacityAndLength(new_capacity, array_length, - set_capacity_mode); + smi_mode); if (!maybe->To(&new_elements)) return maybe; } new_elements->set(index, value); + ValidateElements(); return value; } + // Finally, set the new element and length. ASSERT(elements()->IsFixedArray()); backing_store->set(index, value); @@ -9303,7 +10011,8 @@ MaybeObject* JSObject::SetDictionaryElement(uint32_t index, // is read-only (a declared const that has not been initialized). If a // value is being defined we skip attribute checks completely. if (set_mode == DEFINE_PROPERTY) { - details = PropertyDetails(attributes, NORMAL, details.index()); + details = PropertyDetails( + attributes, NORMAL, details.dictionary_index()); dictionary->DetailsAtPut(entry, details); } else if (details.IsReadOnly() && !element->IsTheHole()) { if (strict_mode == kNonStrictMode) { @@ -9383,20 +10092,21 @@ MaybeObject* JSObject::SetDictionaryElement(uint32_t index, } else { new_length = dictionary->max_number_key() + 1; } - SetFastElementsCapacityMode set_capacity_mode = FLAG_smi_only_arrays - ? kAllowSmiOnlyElements - : kDontAllowSmiOnlyElements; + SetFastElementsCapacitySmiMode smi_mode = FLAG_smi_only_arrays + ? kAllowSmiElements + : kDontAllowSmiElements; bool has_smi_only_elements = false; bool should_convert_to_fast_double_elements = ShouldConvertToFastDoubleElements(&has_smi_only_elements); if (has_smi_only_elements) { - set_capacity_mode = kForceSmiOnlyElements; + smi_mode = kForceSmiElements; } MaybeObject* result = should_convert_to_fast_double_elements ? SetFastDoubleElementsCapacityAndLength(new_length, new_length) : SetFastElementsCapacityAndLength(new_length, new_length, - set_capacity_mode); + smi_mode); + ValidateElements(); if (result->IsFailure()) return result; #ifdef DEBUG if (FLAG_trace_normalization) { @@ -9435,27 +10145,40 @@ MUST_USE_RESULT MaybeObject* JSObject::SetFastDoubleElement( // If the value object is not a heap number, switch to fast elements and try // again. bool value_is_smi = value->IsSmi(); + bool introduces_holes = true; + uint32_t length = elms_length; + if (IsJSArray()) { + CHECK(JSArray::cast(this)->length()->ToArrayIndex(&length)); + introduces_holes = index > length; + } else { + introduces_holes = index >= elms_length; + } + if (!value->IsNumber()) { - Object* obj; - uint32_t length = elms_length; - if (IsJSArray()) { - CHECK(JSArray::cast(this)->length()->ToArrayIndex(&length)); - } MaybeObject* maybe_obj = SetFastElementsCapacityAndLength( elms_length, length, - kDontAllowSmiOnlyElements); - if (!maybe_obj->ToObject(&obj)) return maybe_obj; - return SetFastElement(index, - value, - strict_mode, - check_prototype); + kDontAllowSmiElements); + if (maybe_obj->IsFailure()) return maybe_obj; + maybe_obj = SetFastElement(index, value, strict_mode, check_prototype); + if (maybe_obj->IsFailure()) return maybe_obj; + ValidateElements(); + return maybe_obj; } double double_value = value_is_smi ? static_cast<double>(Smi::cast(value)->value()) : HeapNumber::cast(value)->value(); + // If the array is growing, and it's not growth by a single element at the + // end, make sure that the ElementsKind is HOLEY. + ElementsKind elements_kind = GetElementsKind(); + if (introduces_holes && !IsFastHoleyElementsKind(elements_kind)) { + ElementsKind transitioned_kind = GetHoleyElementsKind(elements_kind); + MaybeObject* maybe = TransitionElementsKind(transitioned_kind); + if (maybe->IsFailure()) return maybe; + } + // Check whether there is extra space in the fixed array. if (index < elms_length) { FixedDoubleArray* elms = FixedDoubleArray::cast(elements()); @@ -9477,13 +10200,11 @@ MUST_USE_RESULT MaybeObject* JSObject::SetFastDoubleElement( int new_capacity = NewElementsCapacity(index+1); if (!ShouldConvertToSlowElements(new_capacity)) { ASSERT(static_cast<uint32_t>(new_capacity) > index); - Object* obj; - { MaybeObject* maybe_obj = - SetFastDoubleElementsCapacityAndLength(new_capacity, - index + 1); - if (!maybe_obj->ToObject(&obj)) return maybe_obj; - } + MaybeObject* maybe_obj = + SetFastDoubleElementsCapacityAndLength(new_capacity, index + 1); + if (maybe_obj->IsFailure()) return maybe_obj; FixedDoubleArray::cast(elements())->set(index, double_value); + ValidateElements(); return value; } } @@ -9508,7 +10229,7 @@ MaybeObject* JSReceiver::SetElement(uint32_t index, bool check_proto) { if (IsJSProxy()) { return JSProxy::cast(this)->SetElementWithHandler( - index, value, strict_mode); + this, index, value, strict_mode); } else { return JSObject::cast(this)->SetElement( index, value, attributes, strict_mode, check_proto); @@ -9550,28 +10271,31 @@ Handle<Object> JSObject::SetElement(Handle<JSObject> object, MaybeObject* JSObject::SetElement(uint32_t index, - Object* value, + Object* value_raw, PropertyAttributes attributes, StrictModeFlag strict_mode, bool check_prototype, SetPropertyMode set_mode) { + Isolate* isolate = GetIsolate(); + HandleScope scope(isolate); + Handle<JSObject> self(this); + Handle<Object> value(value_raw); + // Check access rights if needed. if (IsAccessCheckNeeded()) { Heap* heap = GetHeap(); - if (!heap->isolate()->MayIndexedAccess(this, index, v8::ACCESS_SET)) { - HandleScope scope(heap->isolate()); - Handle<Object> value_handle(value); - heap->isolate()->ReportFailedAccessCheck(this, v8::ACCESS_SET); - return *value_handle; + if (!heap->isolate()->MayIndexedAccess(*self, index, v8::ACCESS_SET)) { + heap->isolate()->ReportFailedAccessCheck(*self, v8::ACCESS_SET); + return *value; } } if (IsJSGlobalProxy()) { Object* proto = GetPrototype(); - if (proto->IsNull()) return value; + if (proto->IsNull()) return *value; ASSERT(proto->IsJSGlobalObject()); return JSObject::cast(proto)->SetElement(index, - value, + *value, attributes, strict_mode, check_prototype, @@ -9580,9 +10304,8 @@ MaybeObject* JSObject::SetElement(uint32_t index, // Don't allow element properties to be redefined for external arrays. if (HasExternalArrayElements() && set_mode == DEFINE_PROPERTY) { - Isolate* isolate = GetHeap()->isolate(); Handle<Object> number = isolate->factory()->NewNumberFromUint(index); - Handle<Object> args[] = { Handle<Object>(this), number }; + Handle<Object> args[] = { self, number }; Handle<Object> error = isolate->factory()->NewTypeError( "redef_external_array_element", HandleVector(args, ARRAY_SIZE(args))); return isolate->Throw(*error); @@ -9597,22 +10320,55 @@ MaybeObject* JSObject::SetElement(uint32_t index, dictionary->set_requires_slow_elements(); } + // From here on, everything has to be handlified. + Handle<String> name; + Handle<Object> old_value(isolate->heap()->the_hole_value()); + Handle<Object> old_array_length; + PropertyAttributes old_attributes = ABSENT; + bool preexists = false; + if (FLAG_harmony_observation && map()->is_observed()) { + name = isolate->factory()->Uint32ToString(index); + preexists = self->HasLocalElement(index); + if (preexists) { + old_attributes = self->GetLocalPropertyAttribute(*name); + // TODO(observe): only read & set old_value if we have a data property + old_value = Object::GetElement(self, index); + } else if (self->IsJSArray()) { + // Store old array length in case adding an element grows the array. + old_array_length = handle(Handle<JSArray>::cast(self)->length()); + } + } + // Check for lookup interceptor - if (HasIndexedInterceptor()) { - return SetElementWithInterceptor(index, - value, - attributes, - strict_mode, - check_prototype, - set_mode); + MaybeObject* result = self->HasIndexedInterceptor() + ? self->SetElementWithInterceptor( + index, *value, attributes, strict_mode, check_prototype, set_mode) + : self->SetElementWithoutInterceptor( + index, *value, attributes, strict_mode, check_prototype, set_mode); + + Handle<Object> hresult; + if (!result->ToHandle(&hresult)) return result; + + if (FLAG_harmony_observation && map()->is_observed()) { + PropertyAttributes new_attributes = self->GetLocalPropertyAttribute(*name); + if (!preexists) { + EnqueueChangeRecord(self, "new", name, old_value); + if (self->IsJSArray() && + !old_array_length->SameValue(Handle<JSArray>::cast(self)->length())) { + EnqueueChangeRecord(self, "updated", + isolate->factory()->length_symbol(), + old_array_length); + } + } else if (new_attributes != old_attributes || old_value->IsTheHole()) { + EnqueueChangeRecord(self, "reconfigured", name, old_value); + } else { + Handle<Object> new_value = Object::GetElement(self, index); + if (!new_value->SameValue(*old_value)) + EnqueueChangeRecord(self, "updated", name, old_value); + } } - return SetElementWithoutInterceptor(index, - value, - attributes, - strict_mode, - check_prototype, - set_mode); + return *hresult; } @@ -9627,10 +10383,13 @@ MaybeObject* JSObject::SetElementWithoutInterceptor(uint32_t index, (attr & (DONT_DELETE | DONT_ENUM | READ_ONLY)) == 0); Isolate* isolate = GetIsolate(); switch (GetElementsKind()) { - case FAST_SMI_ONLY_ELEMENTS: + case FAST_SMI_ELEMENTS: case FAST_ELEMENTS: + case FAST_HOLEY_SMI_ELEMENTS: + case FAST_HOLEY_ELEMENTS: return SetFastElement(index, value, strict_mode, check_prototype); case FAST_DOUBLE_ELEMENTS: + case FAST_HOLEY_DOUBLE_ELEMENTS: return SetFastDoubleElement(index, value, strict_mode, check_prototype); case EXTERNAL_PIXEL_ELEMENTS: { ExternalPixelArray* pixels = ExternalPixelArray::cast(elements()); @@ -9721,11 +10480,19 @@ Handle<Object> JSObject::TransitionElementsKind(Handle<JSObject> object, MaybeObject* JSObject::TransitionElementsKind(ElementsKind to_kind) { ElementsKind from_kind = map()->elements_kind(); + if (IsFastHoleyElementsKind(from_kind)) { + to_kind = GetHoleyElementsKind(to_kind); + } + Isolate* isolate = GetIsolate(); - if ((from_kind == FAST_SMI_ONLY_ELEMENTS || - elements() == isolate->heap()->empty_fixed_array()) && - to_kind == FAST_ELEMENTS) { - ASSERT(from_kind != FAST_ELEMENTS); + if (elements() == isolate->heap()->empty_fixed_array() || + (IsFastSmiOrObjectElementsKind(from_kind) && + IsFastSmiOrObjectElementsKind(to_kind)) || + (from_kind == FAST_DOUBLE_ELEMENTS && + to_kind == FAST_HOLEY_DOUBLE_ELEMENTS)) { + ASSERT(from_kind != TERMINAL_FAST_ELEMENTS_KIND); + // No change is needed to the elements() buffer, the transition + // only requires a map change. MaybeObject* maybe_new_map = GetElementsTransitionMap(isolate, to_kind); Map* new_map; if (!maybe_new_map->To(&new_map)) return maybe_new_map; @@ -9752,18 +10519,21 @@ MaybeObject* JSObject::TransitionElementsKind(ElementsKind to_kind) { } } - if (from_kind == FAST_SMI_ONLY_ELEMENTS && - to_kind == FAST_DOUBLE_ELEMENTS) { + if (IsFastSmiElementsKind(from_kind) && + IsFastDoubleElementsKind(to_kind)) { MaybeObject* maybe_result = SetFastDoubleElementsCapacityAndLength(capacity, length); if (maybe_result->IsFailure()) return maybe_result; + ValidateElements(); return this; } - if (from_kind == FAST_DOUBLE_ELEMENTS && to_kind == FAST_ELEMENTS) { + if (IsFastDoubleElementsKind(from_kind) && + IsFastObjectElementsKind(to_kind)) { MaybeObject* maybe_result = SetFastElementsCapacityAndLength( - capacity, length, kDontAllowSmiOnlyElements); + capacity, length, kDontAllowSmiElements); if (maybe_result->IsFailure()) return maybe_result; + ValidateElements(); return this; } @@ -9777,10 +10547,14 @@ MaybeObject* JSObject::TransitionElementsKind(ElementsKind to_kind) { // static bool Map::IsValidElementsTransition(ElementsKind from_kind, ElementsKind to_kind) { - return - (from_kind == FAST_SMI_ONLY_ELEMENTS && - (to_kind == FAST_DOUBLE_ELEMENTS || to_kind == FAST_ELEMENTS)) || - (from_kind == FAST_DOUBLE_ELEMENTS && to_kind == FAST_ELEMENTS); + // Transitions can't go backwards. + if (!IsMoreGeneralElementsKindTransition(from_kind, to_kind)) { + return false; + } + + // Transitions from HOLEY -> PACKED are not allowed. + return !IsFastHoleyElementsKind(from_kind) || + IsFastHoleyElementsKind(to_kind); } @@ -9826,7 +10600,11 @@ MaybeObject* JSObject::GetElementWithInterceptor(Object* receiver, result = getter(index, info); } RETURN_IF_SCHEDULED_EXCEPTION(isolate); - if (!result.IsEmpty()) return *v8::Utils::OpenHandle(*result); + if (!result.IsEmpty()) { + Handle<Object> result_internal = v8::Utils::OpenHandle(*result); + result_internal->VerifyApiCallResultType(); + return *result_internal; + } } Heap* heap = holder_handle->GetHeap(); @@ -9871,8 +10649,16 @@ void JSObject::GetElementsCapacityAndUsage(int* capacity, int* used) { break; } // Fall through. - case FAST_SMI_ONLY_ELEMENTS: + case FAST_SMI_ELEMENTS: case FAST_ELEMENTS: + if (IsJSArray()) { + *capacity = backing_store_base->length(); + *used = Smi::cast(JSArray::cast(this)->length())->value(); + break; + } + // Fall through if packing is not guaranteed. + case FAST_HOLEY_SMI_ELEMENTS: + case FAST_HOLEY_ELEMENTS: backing_store = FixedArray::cast(backing_store_base); *capacity = backing_store->length(); for (int i = 0; i < *capacity; ++i) { @@ -9886,7 +10672,14 @@ void JSObject::GetElementsCapacityAndUsage(int* capacity, int* used) { *used = dictionary->NumberOfElements(); break; } - case FAST_DOUBLE_ELEMENTS: { + case FAST_DOUBLE_ELEMENTS: + if (IsJSArray()) { + *capacity = backing_store_base->length(); + *used = Smi::cast(JSArray::cast(this)->length())->value(); + break; + } + // Fall through if packing is not guaranteed. + case FAST_HOLEY_DOUBLE_ELEMENTS: { FixedDoubleArray* elms = FixedDoubleArray::cast(elements()); *capacity = elms->length(); for (int i = 0; i < *capacity; i++) { @@ -10003,15 +10796,15 @@ void Dictionary<Shape, Key>::Print(FILE* out) { for (int i = 0; i < capacity; i++) { Object* k = HashTable<Shape, Key>::KeyAt(i); if (HashTable<Shape, Key>::IsKey(k)) { - PrintF(out, " "); + FPrintF(out, " "); if (k->IsString()) { String::cast(k)->StringPrint(out); } else { k->ShortPrint(out); } - PrintF(out, ": "); + FPrintF(out, ": "); ValueAt(i)->ShortPrint(out); - PrintF(out, "\n"); + FPrintF(out, "\n"); } } } @@ -10055,13 +10848,13 @@ InterceptorInfo* JSObject::GetIndexedInterceptor() { MaybeObject* JSObject::GetPropertyPostInterceptor( - JSReceiver* receiver, + Object* receiver, String* name, PropertyAttributes* attributes) { // Check local property in holder, ignore interceptor. LookupResult result(GetIsolate()); LocalLookupRealNamedProperty(name, &result); - if (result.IsProperty()) { + if (result.IsFound()) { return GetProperty(receiver, &result, name, attributes); } // Continue searching via the prototype chain. @@ -10073,13 +10866,13 @@ MaybeObject* JSObject::GetPropertyPostInterceptor( MaybeObject* JSObject::GetLocalPropertyPostInterceptor( - JSReceiver* receiver, + Object* receiver, String* name, PropertyAttributes* attributes) { // Check local property in holder, ignore interceptor. LookupResult result(GetIsolate()); LocalLookupRealNamedProperty(name, &result); - if (result.IsProperty()) { + if (result.IsFound()) { return GetProperty(receiver, &result, name, attributes); } return GetHeap()->undefined_value(); @@ -10087,13 +10880,13 @@ MaybeObject* JSObject::GetLocalPropertyPostInterceptor( MaybeObject* JSObject::GetPropertyWithInterceptor( - JSReceiver* receiver, + Object* receiver, String* name, PropertyAttributes* attributes) { Isolate* isolate = GetIsolate(); InterceptorInfo* interceptor = GetNamedInterceptor(); HandleScope scope(isolate); - Handle<JSReceiver> receiver_handle(receiver); + Handle<Object> receiver_handle(receiver); Handle<JSObject> holder_handle(this); Handle<String> name_handle(name); @@ -10113,7 +10906,9 @@ MaybeObject* JSObject::GetPropertyWithInterceptor( RETURN_IF_SCHEDULED_EXCEPTION(isolate); if (!result.IsEmpty()) { *attributes = NONE; - return *v8::Utils::OpenHandle(*result); + Handle<Object> result_internal = v8::Utils::OpenHandle(*result); + result_internal->VerifyApiCallResultType(); + return *result_internal; } } @@ -10138,7 +10933,7 @@ bool JSObject::HasRealNamedProperty(String* key) { LookupResult result(isolate); LocalLookupRealNamedProperty(key, &result); - return result.IsProperty() && (result.type() != INTERCEPTOR); + return result.IsFound() && !result.IsInterceptor(); } @@ -10156,16 +10951,19 @@ bool JSObject::HasRealElementProperty(uint32_t index) { if (this->IsStringObjectWithCharacterAt(index)) return true; switch (GetElementsKind()) { - case FAST_SMI_ONLY_ELEMENTS: - case FAST_ELEMENTS: { - uint32_t length = IsJSArray() ? + case FAST_SMI_ELEMENTS: + case FAST_ELEMENTS: + case FAST_HOLEY_SMI_ELEMENTS: + case FAST_HOLEY_ELEMENTS: { + uint32_t length = IsJSArray() ? static_cast<uint32_t>( Smi::cast(JSArray::cast(this)->length())->value()) : static_cast<uint32_t>(FixedArray::cast(elements())->length()); return (index < length) && !FixedArray::cast(elements())->get(index)->IsTheHole(); } - case FAST_DOUBLE_ELEMENTS: { + case FAST_DOUBLE_ELEMENTS: + case FAST_HOLEY_DOUBLE_ELEMENTS: { uint32_t length = IsJSArray() ? static_cast<uint32_t>( Smi::cast(JSArray::cast(this)->length())->value()) : @@ -10215,14 +11013,21 @@ bool JSObject::HasRealNamedCallbackProperty(String* key) { LookupResult result(isolate); LocalLookupRealNamedProperty(key, &result); - return result.IsFound() && (result.type() == CALLBACKS); + return result.IsPropertyCallbacks(); } int JSObject::NumberOfLocalProperties(PropertyAttributes filter) { - return HasFastProperties() ? - map()->NumberOfDescribedProperties(filter) : - property_dictionary()->NumberOfElementsFilterAttributes(filter); + if (HasFastProperties()) { + Map* map = this->map(); + if (filter == NONE) return map->NumberOfOwnDescriptors(); + if (filter == DONT_ENUM) { + int result = map->EnumLength(); + if (result != Map::kInvalidEnumCache) return result; + } + return map->NumberOfDescribedProperties(OWN_DESCRIPTORS, filter); + } + return property_dictionary()->NumberOfElementsFilterAttributes(filter); } @@ -10345,11 +11150,12 @@ void FixedArray::SortPairs(FixedArray* numbers, uint32_t len) { void JSObject::GetLocalPropertyNames(FixedArray* storage, int index) { ASSERT(storage->length() >= (NumberOfLocalProperties() - index)); if (HasFastProperties()) { + int real_size = map()->NumberOfOwnDescriptors(); DescriptorArray* descs = map()->instance_descriptors(); - for (int i = 0; i < descs->number_of_descriptors(); i++) { - if (descs->IsProperty(i)) storage->set(index++, descs->GetKey(i)); + ASSERT(storage->length() >= index + real_size); + for (int i = 0; i < real_size; i++) { + storage->set(index + i, descs->GetKey(i)); } - ASSERT(storage->length() >= index); } else { property_dictionary()->CopyKeysTo(storage, index, @@ -10365,7 +11171,7 @@ int JSObject::NumberOfLocalElements(PropertyAttributes filter) { int JSObject::NumberOfEnumElements() { // Fast case for objects with no elements. - if (!IsJSValue() && HasFastElements()) { + if (!IsJSValue() && HasFastObjectElements()) { uint32_t length = IsJSArray() ? static_cast<uint32_t>( Smi::cast(JSArray::cast(this)->length())->value()) : @@ -10381,8 +11187,10 @@ int JSObject::GetLocalElementKeys(FixedArray* storage, PropertyAttributes filter) { int counter = 0; switch (GetElementsKind()) { - case FAST_SMI_ONLY_ELEMENTS: - case FAST_ELEMENTS: { + case FAST_SMI_ELEMENTS: + case FAST_ELEMENTS: + case FAST_HOLEY_SMI_ELEMENTS: + case FAST_HOLEY_ELEMENTS: { int length = IsJSArray() ? Smi::cast(JSArray::cast(this)->length())->value() : FixedArray::cast(elements())->length(); @@ -10397,7 +11205,8 @@ int JSObject::GetLocalElementKeys(FixedArray* storage, ASSERT(!storage || storage->length() >= counter); break; } - case FAST_DOUBLE_ELEMENTS: { + case FAST_DOUBLE_ELEMENTS: + case FAST_HOLEY_DOUBLE_ELEMENTS: { int length = IsJSArray() ? Smi::cast(JSArray::cast(this)->length())->value() : FixedDoubleArray::cast(elements())->length(); @@ -10934,8 +11743,12 @@ void HashTable<Shape, Key>::IterateElements(ObjectVisitor* v) { template<typename Shape, typename Key> MaybeObject* HashTable<Shape, Key>::Allocate(int at_least_space_for, + MinimumCapacity capacity_option, PretenureFlag pretenure) { - int capacity = ComputeCapacity(at_least_space_for); + ASSERT(!capacity_option || IS_POWER_OF_TWO(at_least_space_for)); + int capacity = (capacity_option == USE_CUSTOM_MINIMUM_CAPACITY) + ? at_least_space_for + : ComputeCapacity(at_least_space_for); if (capacity > HashTable::kMaxCapacity) { return Failure::OutOfMemoryException(); } @@ -10992,31 +11805,6 @@ int StringDictionary::FindEntry(String* key) { } -bool StringDictionary::ContainsTransition(int entry) { - switch (DetailsAt(entry).type()) { - case MAP_TRANSITION: - case CONSTANT_TRANSITION: - case ELEMENTS_TRANSITION: - return true; - case CALLBACKS: { - Object* value = ValueAt(entry); - if (!value->IsAccessorPair()) return false; - AccessorPair* accessors = AccessorPair::cast(value); - return accessors->getter()->IsMap() || accessors->setter()->IsMap(); - } - case NORMAL: - case FIELD: - case CONSTANT_FUNCTION: - case HANDLER: - case INTERCEPTOR: - case NULL_DESCRIPTOR: - return false; - } - UNREACHABLE(); // Keep the compiler happy. - return false; -} - - template<typename Shape, typename Key> MaybeObject* HashTable<Shape, Key>::Rehash(HashTable* new_table, Key key) { ASSERT(NumberOfElements() < new_table->Capacity()); @@ -11069,7 +11857,9 @@ MaybeObject* HashTable<Shape, Key>::EnsureCapacity(int n, Key key) { (capacity > kMinCapacityForPretenure) && !GetHeap()->InNewSpace(this); Object* obj; { MaybeObject* maybe_obj = - Allocate(nof * 2, pretenure ? TENURED : NOT_TENURED); + Allocate(nof * 2, + USE_DEFAULT_MINIMUM_CAPACITY, + pretenure ? TENURED : NOT_TENURED); if (!maybe_obj->ToObject(&obj)) return maybe_obj; } @@ -11098,7 +11888,9 @@ MaybeObject* HashTable<Shape, Key>::Shrink(Key key) { !GetHeap()->InNewSpace(this); Object* obj; { MaybeObject* maybe_obj = - Allocate(at_least_room_for, pretenure ? TENURED : NOT_TENURED); + Allocate(at_least_room_for, + USE_DEFAULT_MINIMUM_CAPACITY, + pretenure ? TENURED : NOT_TENURED); if (!maybe_obj->ToObject(&obj)) return maybe_obj; } @@ -11349,10 +12141,9 @@ MaybeObject* JSObject::PrepareElementsForSort(uint32_t limit) { // Convert to fast elements. Object* obj; - { MaybeObject* maybe_obj = GetElementsTransitionMap(GetIsolate(), - FAST_ELEMENTS); - if (!maybe_obj->ToObject(&obj)) return maybe_obj; - } + MaybeObject* maybe_obj = GetElementsTransitionMap(GetIsolate(), + FAST_HOLEY_ELEMENTS); + if (!maybe_obj->ToObject(&obj)) return maybe_obj; Map* new_map = Map::cast(obj); PretenureFlag tenure = heap->InNewSpace(this) ? NOT_TENURED: TENURED; @@ -11363,9 +12154,9 @@ MaybeObject* JSObject::PrepareElementsForSort(uint32_t limit) { } FixedArray* fast_elements = FixedArray::cast(new_array); dict->CopyValuesTo(fast_elements); + ValidateElements(); - set_map(new_map); - set_elements(fast_elements); + set_map_and_elements(new_map, fast_elements); } else if (HasExternalArrayElements()) { // External arrays cannot have holes or undefined elements. return Smi::FromInt(ExternalArray::cast(elements())->length()); @@ -11375,7 +12166,7 @@ MaybeObject* JSObject::PrepareElementsForSort(uint32_t limit) { if (!maybe_obj->ToObject(&obj)) return maybe_obj; } } - ASSERT(HasFastTypeElements() || HasFastDoubleElements()); + ASSERT(HasFastSmiOrObjectElements() || HasFastDoubleElements()); // Collect holes at the end, undefined before that and the rest at the // start, and return the number of non-hole, non-undefined values. @@ -11505,7 +12296,7 @@ Object* ExternalPixelArray::SetValue(uint32_t index, Object* value) { clamped_value = 255; } else { // Other doubles are rounded to the nearest integer. - clamped_value = static_cast<uint8_t>(double_value + 0.5); + clamped_value = static_cast<uint8_t>(lrint(double_value)); } } else { // Clamp undefined to zero (default). All other types have been @@ -11709,7 +12500,7 @@ class TwoCharHashTableKey : public HashTableKey { hash += hash << 3; hash ^= hash >> 11; hash += hash << 15; - if ((hash & String::kHashBitMask) == 0) hash = String::kZeroHash; + if ((hash & String::kHashBitMask) == 0) hash = StringHasher::kZeroHash; #ifdef DEBUG StringHasher hasher(2, seed); hasher.AddCharacter(c1); @@ -11845,8 +12636,23 @@ MaybeObject* SymbolTable::LookupKey(HashTableKey* key, Object** s) { } -Object* CompilationCacheTable::Lookup(String* src) { - StringKey key(src); +// The key for the script compilation cache is dependent on the mode flags, +// because they change the global language mode and thus binding behaviour. +// If flags change at some point, we must ensure that we do not hit the cache +// for code compiled with different settings. +static LanguageMode CurrentGlobalLanguageMode() { + return FLAG_use_strict + ? (FLAG_harmony_scoping ? EXTENDED_MODE : STRICT_MODE) + : CLASSIC_MODE; +} + + +Object* CompilationCacheTable::Lookup(String* src, Context* context) { + SharedFunctionInfo* shared = context->closure()->shared(); + StringSharedKey key(src, + shared, + CurrentGlobalLanguageMode(), + RelocInfo::kNoPosition); int entry = FindEntry(&key); if (entry == kNotFound) return GetHeap()->undefined_value(); return get(EntryToIndex(entry) + 1); @@ -11876,17 +12682,24 @@ Object* CompilationCacheTable::LookupRegExp(String* src, } -MaybeObject* CompilationCacheTable::Put(String* src, Object* value) { - StringKey key(src); - Object* obj; - { MaybeObject* maybe_obj = EnsureCapacity(1, &key); - if (!maybe_obj->ToObject(&obj)) return maybe_obj; - } +MaybeObject* CompilationCacheTable::Put(String* src, + Context* context, + Object* value) { + SharedFunctionInfo* shared = context->closure()->shared(); + StringSharedKey key(src, + shared, + CurrentGlobalLanguageMode(), + RelocInfo::kNoPosition); + CompilationCacheTable* cache; + MaybeObject* maybe_cache = EnsureCapacity(1, &key); + if (!maybe_cache->To(&cache)) return maybe_cache; + + Object* k; + MaybeObject* maybe_k = key.AsObject(); + if (!maybe_k->To(&k)) return maybe_k; - CompilationCacheTable* cache = - reinterpret_cast<CompilationCacheTable*>(obj); int entry = cache->FindInsertionEntry(key.Hash()); - cache->set(EntryToIndex(entry), src); + cache->set(EntryToIndex(entry), k); cache->set(EntryToIndex(entry) + 1, value); cache->ElementAdded(); return cache; @@ -12030,6 +12843,12 @@ MaybeObject* Dictionary<Shape, Key>::Allocate(int at_least_space_for) { } +void StringDictionary::DoGenerateNewEnumerationIndices( + Handle<StringDictionary> dictionary) { + CALL_HEAP_FUNCTION_VOID(dictionary->GetIsolate(), + dictionary->GenerateNewEnumerationIndices()); +} + template<typename Shape, typename Key> MaybeObject* Dictionary<Shape, Key>::GenerateNewEnumerationIndices() { Heap* heap = Dictionary<Shape, Key>::GetHeap(); @@ -12056,7 +12875,8 @@ MaybeObject* Dictionary<Shape, Key>::GenerateNewEnumerationIndices() { int pos = 0; for (int i = 0; i < capacity; i++) { if (Dictionary<Shape, Key>::IsKey(Dictionary<Shape, Key>::KeyAt(i))) { - enumeration_order->set(pos++, Smi::FromInt(DetailsAt(i).index())); + int index = DetailsAt(i).dictionary_index(); + enumeration_order->set(pos++, Smi::FromInt(index)); } } @@ -12155,6 +12975,8 @@ template<typename Shape, typename Key> MaybeObject* Dictionary<Shape, Key>::Add(Key key, Object* value, PropertyDetails details) { + ASSERT(details.dictionary_index() == details.descriptor_index()); + // Valdate key is absent. SLOW_ASSERT((this->FindEntry(key) == Dictionary<Shape, Key>::kNotFound)); // Check whether the dictionary should be extended. @@ -12182,7 +13004,9 @@ MaybeObject* Dictionary<Shape, Key>::AddEntry(Key key, uint32_t entry = Dictionary<Shape, Key>::FindInsertionEntry(hash); // Insert element at empty or deleted entry - if (!details.IsDeleted() && details.index() == 0 && Shape::kIsEnumerable) { + if (!details.IsDeleted() && + details.dictionary_index() == 0 && + Shape::kIsEnumerable) { // Assign an enumeration index to the property and update // SetNextEnumerationIndex. int index = NextEnumerationIndex(); @@ -12273,7 +13097,7 @@ MaybeObject* SeededNumberDictionary::Set(uint32_t key, // Preserve enumeration index. details = PropertyDetails(details.attributes(), details.type(), - DetailsAt(entry).index()); + DetailsAt(entry).dictionary_index()); MaybeObject* maybe_object_key = SeededNumberDictionaryShape::AsObject(key); Object* object_key; if (!maybe_object_key->ToObject(&object_key)) return maybe_object_key; @@ -12344,23 +13168,45 @@ void Dictionary<Shape, Key>::CopyKeysTo( } -void StringDictionary::CopyEnumKeysTo(FixedArray* storage, - FixedArray* sort_array) { - ASSERT(storage->length() >= NumberOfEnumElements()); +FixedArray* StringDictionary::CopyEnumKeysTo(FixedArray* storage) { + int length = storage->length(); + ASSERT(length >= NumberOfEnumElements()); + Heap* heap = GetHeap(); + Object* undefined_value = heap->undefined_value(); int capacity = Capacity(); - int index = 0; + int properties = 0; + + // Fill in the enumeration array by assigning enumerable keys at their + // enumeration index. This will leave holes in the array if there are keys + // that are deleted or not enumerable. for (int i = 0; i < capacity; i++) { Object* k = KeyAt(i); if (IsKey(k)) { PropertyDetails details = DetailsAt(i); if (details.IsDeleted() || details.IsDontEnum()) continue; - storage->set(index, k); - sort_array->set(index, Smi::FromInt(details.index())); - index++; + properties++; + storage->set(details.dictionary_index() - 1, k); + if (properties == length) break; } } - storage->SortPairs(sort_array, sort_array->length()); - ASSERT(storage->length() >= index); + + // There are holes in the enumeration array if less properties were assigned + // than the length of the array. If so, crunch all the existing properties + // together by shifting them to the left (maintaining the enumeration order), + // and trimming of the right side of the array. + if (properties < length) { + if (properties == 0) return heap->empty_fixed_array(); + properties = 0; + for (int i = 0; i < length; ++i) { + Object* value = storage->get(i); + if (value != undefined_value) { + storage->set(properties, value); + ++properties; + } + } + RightTrimFixedArray<FROM_MUTATOR>(heap, storage, length - properties); + } + return storage; } @@ -12410,18 +13256,12 @@ MaybeObject* StringDictionary::TransformPropertiesToFastFor( JSObject* obj, int unused_property_fields) { // Make sure we preserve dictionary representation if there are too many // descriptors. - if (NumberOfElements() > DescriptorArray::kMaxNumberOfDescriptors) return obj; - - // Figure out if it is necessary to generate new enumeration indices. - int max_enumeration_index = - NextEnumerationIndex() + - (DescriptorArray::kMaxNumberOfDescriptors - - NumberOfElements()); - if (!PropertyDetails::IsValidIndex(max_enumeration_index)) { - Object* result; - { MaybeObject* maybe_result = GenerateNewEnumerationIndices(); - if (!maybe_result->ToObject(&result)) return maybe_result; - } + int number_of_elements = NumberOfElements(); + if (number_of_elements > DescriptorArray::kMaxNumberOfDescriptors) return obj; + + if (number_of_elements != NextEnumerationIndex()) { + MaybeObject* maybe_result = GenerateNewEnumerationIndices(); + if (maybe_result->IsFailure()) return maybe_result; } int instance_descriptor_length = 0; @@ -12445,18 +13285,35 @@ MaybeObject* StringDictionary::TransformPropertiesToFastFor( } } + int inobject_props = obj->map()->inobject_properties(); + + // Allocate new map. + Map* new_map; + MaybeObject* maybe_new_map = obj->map()->CopyDropDescriptors(); + if (!maybe_new_map->To(&new_map)) return maybe_new_map; + new_map->set_dictionary_map(false); + + if (instance_descriptor_length == 0) { + ASSERT_LE(unused_property_fields, inobject_props); + // Transform the object. + new_map->set_unused_property_fields(inobject_props); + obj->set_map(new_map); + obj->set_properties(heap->empty_fixed_array()); + // Check that it really works. + ASSERT(obj->HasFastProperties()); + return obj; + } + // Allocate the instance descriptor. DescriptorArray* descriptors; - { MaybeObject* maybe_descriptors = - DescriptorArray::Allocate(instance_descriptor_length); - if (!maybe_descriptors->To<DescriptorArray>(&descriptors)) { - return maybe_descriptors; - } + MaybeObject* maybe_descriptors = + DescriptorArray::Allocate(instance_descriptor_length); + if (!maybe_descriptors->To(&descriptors)) { + return maybe_descriptors; } DescriptorArray::WhitenessWitness witness(descriptors); - int inobject_props = obj->map()->inobject_properties(); int number_of_allocated_fields = number_of_fields + unused_property_fields - inobject_props; if (number_of_allocated_fields < 0) { @@ -12466,33 +13323,33 @@ MaybeObject* StringDictionary::TransformPropertiesToFastFor( } // Allocate the fixed array for the fields. - Object* fields; - { MaybeObject* maybe_fields = - heap->AllocateFixedArray(number_of_allocated_fields); - if (!maybe_fields->ToObject(&fields)) return maybe_fields; - } + FixedArray* fields; + MaybeObject* maybe_fields = + heap->AllocateFixedArray(number_of_allocated_fields); + if (!maybe_fields->To(&fields)) return maybe_fields; // Fill in the instance descriptor and the fields. - int next_descriptor = 0; int current_offset = 0; for (int i = 0; i < capacity; i++) { Object* k = KeyAt(i); if (IsKey(k)) { Object* value = ValueAt(i); // Ensure the key is a symbol before writing into the instance descriptor. - Object* key; - { MaybeObject* maybe_key = heap->LookupSymbol(String::cast(k)); - if (!maybe_key->ToObject(&key)) return maybe_key; - } + String* key; + MaybeObject* maybe_key = heap->LookupSymbol(String::cast(k)); + if (!maybe_key->To(&key)) return maybe_key; + PropertyDetails details = DetailsAt(i); + ASSERT(details.descriptor_index() == details.dictionary_index()); + int enumeration_index = details.descriptor_index(); PropertyType type = details.type(); if (value->IsJSFunction() && !heap->InNewSpace(value)) { - ConstantFunctionDescriptor d(String::cast(key), + ConstantFunctionDescriptor d(key, JSFunction::cast(value), details.attributes(), - details.index()); - descriptors->Set(next_descriptor++, &d, witness); + enumeration_index); + descriptors->Set(enumeration_index - 1, &d, witness); } else if (type == NORMAL) { if (current_offset < inobject_props) { obj->InObjectPropertyAtPut(current_offset, @@ -12500,24 +13357,19 @@ MaybeObject* StringDictionary::TransformPropertiesToFastFor( UPDATE_WRITE_BARRIER); } else { int offset = current_offset - inobject_props; - FixedArray::cast(fields)->set(offset, value); + fields->set(offset, value); } - FieldDescriptor d(String::cast(key), + FieldDescriptor d(key, current_offset++, details.attributes(), - details.index()); - descriptors->Set(next_descriptor++, &d, witness); + enumeration_index); + descriptors->Set(enumeration_index - 1, &d, witness); } else if (type == CALLBACKS) { - if (value->IsAccessorPair()) { - MaybeObject* maybe_copy = - AccessorPair::cast(value)->CopyWithoutTransitions(); - if (!maybe_copy->To(&value)) return maybe_copy; - } - CallbacksDescriptor d(String::cast(key), + CallbacksDescriptor d(key, value, details.attributes(), - details.index()); - descriptors->Set(next_descriptor++, &d, witness); + enumeration_index); + descriptors->Set(enumeration_index - 1, &d, witness); } else { UNREACHABLE(); } @@ -12525,22 +13377,17 @@ MaybeObject* StringDictionary::TransformPropertiesToFastFor( } ASSERT(current_offset == number_of_fields); - descriptors->Sort(witness); - // Allocate new map. - Object* new_map; - { MaybeObject* maybe_new_map = obj->map()->CopyDropDescriptors(); - if (!maybe_new_map->ToObject(&new_map)) return maybe_new_map; - } + descriptors->Sort(); + + new_map->InitializeDescriptors(descriptors); + new_map->set_unused_property_fields(unused_property_fields); // Transform the object. - obj->set_map(Map::cast(new_map)); - obj->map()->set_instance_descriptors(descriptors); - obj->map()->set_unused_property_fields(unused_property_fields); + obj->set_map(new_map); - obj->set_properties(FixedArray::cast(fields)); + obj->set_properties(fields); ASSERT(obj->IsJSObject()); - descriptors->SetNextEnumerationIndex(NextEnumerationIndex()); // Check that it really works. ASSERT(obj->HasFastProperties()); @@ -12611,11 +13458,11 @@ Object* ObjectHashTable::Lookup(Object* key) { // If the object does not have an identity hash, it was never used as a key. { MaybeObject* maybe_hash = key->GetHash(OMIT_CREATION); if (maybe_hash->ToObjectUnchecked()->IsUndefined()) { - return GetHeap()->undefined_value(); + return GetHeap()->the_hole_value(); } } int entry = FindEntry(key); - if (entry == kNotFound) return GetHeap()->undefined_value(); + if (entry == kNotFound) return GetHeap()->the_hole_value(); return get(EntryToIndex(entry) + 1); } @@ -12632,7 +13479,7 @@ MaybeObject* ObjectHashTable::Put(Object* key, Object* value) { int entry = FindEntry(key); // Check whether to perform removal operation. - if (value->IsUndefined()) { + if (value->IsTheHole()) { if (entry == kNotFound) return this; RemoveEntry(entry); return Shrink(key); |