Chromium Code Reviews| Index: src/objects.cc |
| diff --git a/src/objects.cc b/src/objects.cc |
| index 41f2ab6d8823f2e85ee1ec636e0016a495273fa6..6d026ae3c19216bcec6f74983e09fb007f25040b 100644 |
| --- a/src/objects.cc |
| +++ b/src/objects.cc |
| @@ -4413,7 +4413,11 @@ MaybeObject* JSObject::CreateAccessorPairFor(String* name) { |
| LookupResult result(GetHeap()->isolate()); |
| LocalLookupRealNamedProperty(name, &result); |
| if (result.IsProperty() && result.type() == CALLBACKS) { |
| - ASSERT(!result.IsDontDelete()); |
| + // 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. |
| Object* obj = result.GetCallbackObject(); |
| if (obj->IsAccessorPair()) { |
| return AccessorPair::cast(obj)->CopyWithoutTransitions(); |
| @@ -4427,6 +4431,26 @@ MaybeObject* JSObject::DefinePropertyAccessor(String* name, |
| Object* getter, |
| Object* setter, |
| PropertyAttributes attributes) { |
| + bool only_attribute_changes = getter->IsNull() && setter->IsNull(); |
| + if (HasFastProperties() && !only_attribute_changes) { |
| + MaybeObject* getterOk = GetHeap()->undefined_value(); |
|
Michael Starzinger
2012/05/02 13:30:14
Can we move the result of "GetHeap()" into a local
Sven Panne
2012/05/03 09:39:02
Done for the occurrences where it is actually used
|
| + if (!getter->IsNull()) { |
| + getterOk = DefineFastAccessor(name, ACCESSOR_GETTER, getter, attributes); |
| + if (getterOk->IsFailure()) return getterOk; |
| + } |
| + |
| + MaybeObject* setterOk = GetHeap()->undefined_value(); |
| + if (getterOk != GetHeap()->null_value() && !setter->IsNull()) { |
| + setterOk = DefineFastAccessor(name, ACCESSOR_SETTER, setter, attributes); |
| + if (setterOk->IsFailure()) return setterOk; |
| + } |
| + |
| + if (getterOk != GetHeap()->null_value() && |
| + setterOk != GetHeap()->null_value()) { |
| + return GetHeap()->undefined_value(); |
| + } |
| + } |
| + |
| AccessorPair* accessors; |
| { MaybeObject* maybe_accessors = CreateAccessorPairFor(name); |
| if (!maybe_accessors->To(&accessors)) return maybe_accessors; |
| @@ -4576,6 +4600,148 @@ MaybeObject* JSObject::DefineAccessor(String* name, |
| } |
| +static bool TransitionToSameAccessor(Object* map, |
| + String* name, |
| + AccessorComponent component, |
| + Object* accessor, |
| + PropertyAttributes attributes ) { |
| + DescriptorArray* descs = Map::cast(map)->instance_descriptors(); |
| + int number = descs->SearchWithCache(name); |
| + ASSERT(number != DescriptorArray::kNotFound); |
| + Object* target_accessor = |
| + AccessorPair::cast(descs->GetCallbacksObject(number))->get(component); |
| + PropertyAttributes target_attributes = descs->GetDetails(number).attributes(); |
| + return target_accessor == accessor && target_attributes == attributes; |
| +} |
| + |
| + |
| +MaybeObject* JSObject::DefineFastAccessor(String* name, |
| + AccessorComponent component, |
| + Object* accessor, |
| + PropertyAttributes attributes) { |
| + LookupResult result(GetIsolate()); |
| + LocalLookup(name, &result); |
| + |
| + // If we have a new property, create a fresh accessor plus a transition to it. |
| + if (!result.IsFound()) { |
| + return CreateFreshAccessor(name, component, accessor, attributes); |
| + } |
| + |
| + // If the property is not a JavaScript accessor, fall back to the slow case. |
| + if (result.type() != CALLBACKS) return GetHeap()->null_value(); |
| + Object* callback_value = result.GetValue(); |
| + if (!callback_value->IsAccessorPair()) return GetHeap()->null_value(); |
| + AccessorPair* accessors = AccessorPair::cast(callback_value); |
| + |
| + // Follow a callback transition, if there is a fitting one. |
| + Object* entry = accessors->get(component); |
| + if (entry->IsMap() && |
| + TransitionToSameAccessor(entry, name, component, accessor, attributes)) { |
| + set_map(Map::cast(entry)); |
| + return this; |
| + } |
| + |
| + // When we re-add the same accessor again, there is nothing to do. |
| + if (entry == accessor && result.GetAttributes() == attributes) return this; |
| + |
| + // Only the other accessor has been set so far, create a new transition. |
| + if (entry->IsTheHole()) { |
| + return NewCallbackTrans(name, component, accessor, attributes, accessors); |
|
Michael Starzinger
2012/05/02 13:30:14
Can we add an assertion to the top of this method
Sven Panne
2012/05/03 09:39:02
I'll add a slightly stronger assertion, checking t
|
| + } |
| + |
| + // Nothing from the above worked, so we have to fall back to the slow case. |
| + return GetHeap()->null_value(); |
| +} |
| + |
| + |
| +MaybeObject* JSObject::CreateFreshAccessor(String* name, |
|
Michael Starzinger
2012/05/02 13:30:14
Can we turn this method into a static helper that
Sven Panne
2012/05/03 09:39:02
As discussed offline, let's just simply pass 'this
|
| + AccessorComponent component, |
| + Object* accessor, |
| + PropertyAttributes attributes) { |
| + // step 1: create a new getter/setter pair with only the accessor in it |
| + AccessorPair* accessors2; |
| + { MaybeObject* maybe_accessors2 = GetHeap()->AllocateAccessorPair(); |
| + if (!maybe_accessors2->To(&accessors2)) return maybe_accessors2; |
| + } |
| + accessors2->set(component, accessor); |
| + |
| + // step 2: create a copy of the descriptors, incl. the new getter/setter pair |
| + Map* map1 = map(); |
| + CallbacksDescriptor callbacks_descr2(name, accessors2, attributes); |
| + DescriptorArray* descriptors2; |
| + { MaybeObject* maybe_descriptors2 = |
| + map1->instance_descriptors()->CopyInsert(&callbacks_descr2, |
| + REMOVE_TRANSITIONS); |
| + if (!maybe_descriptors2->To(&descriptors2)) return maybe_descriptors2; |
| + } |
| + |
| + // step 3: create a new map with the new descriptors |
| + Map* map2; |
| + { MaybeObject* maybe_map2 = map1->CopyDropDescriptors(); |
| + if (!maybe_map2->To(&map2)) return maybe_map2; |
| + } |
| + map2->set_instance_descriptors(descriptors2); |
| + |
| + // step 4: create a new getter/setter pair with a transition to the new map |
| + AccessorPair* accessors1; |
| + { MaybeObject* maybe_accessors1 = GetHeap()->AllocateAccessorPair(); |
| + if (!maybe_accessors1->To(&accessors1)) return maybe_accessors1; |
| + } |
| + accessors1->set(component, map2); |
| + |
| + // step 5: create a copy of the descriptors, incl. the new getter/setter pair |
| + // with the transition |
| + CallbacksDescriptor callbacks_descr1(name, accessors1, attributes); |
| + DescriptorArray* descriptors1; |
| + { MaybeObject* maybe_descriptors1 = |
| + map1->instance_descriptors()->CopyInsert(&callbacks_descr1, |
| + KEEP_TRANSITIONS); |
| + if (!maybe_descriptors1->To(&descriptors1)) return maybe_descriptors1; |
| + } |
| + |
| + // step 6: everything went well so far, so we make our changes visible |
| + set_map(map2); |
| + map1->set_instance_descriptors(descriptors1); |
| + return this; |
| +} |
| + |
| + |
| +MaybeObject* JSObject::NewCallbackTrans(String* name, |
|
Michael Starzinger
2012/05/02 13:30:14
Likewise. Also the full name "NewCallbackTransitio
Sven Panne
2012/05/03 09:39:02
It was because of our 80-column limit. :-) Done.
|
| + AccessorComponent component, |
| + Object* accessor, |
| + PropertyAttributes attributes, |
| + AccessorPair* accessors2) { |
| + // step 1: copy the old getter/setter pair and set the new accessor |
| + AccessorPair* accessors3; |
| + { MaybeObject* maybe_accessors3 = accessors2->CopyWithoutTransitions(); |
| + if (!maybe_accessors3->To(&accessors3)) return maybe_accessors3; |
| + } |
| + accessors3->set(component, accessor); |
| + |
| + // step 2: create a copy of the descriptors, incl. the new getter/setter pair |
| + Map* map2 = map(); |
| + CallbacksDescriptor callbacks_descr3(name, accessors3, attributes); |
| + DescriptorArray* descriptors3; |
| + { MaybeObject* maybe_descriptors3 = |
| + map2->instance_descriptors()->CopyInsert(&callbacks_descr3, |
| + REMOVE_TRANSITIONS); |
| + if (!maybe_descriptors3->To(&descriptors3)) return maybe_descriptors3; |
| + } |
| + |
| + // step 3: create a new map with the new descriptors |
| + Map* map3; |
| + { MaybeObject* maybe_map3 = map2->CopyDropDescriptors(); |
| + if (!maybe_map3->To(&map3)) return maybe_map3; |
| + } |
| + map3->set_instance_descriptors(descriptors3); |
| + |
| + // step 4: everything went well so far, so we make our changes visible |
| + set_map(map3); |
| + accessors2->set(component, map3); |
| + return this; |
| +} |
| + |
| + |
| MaybeObject* JSObject::DefineAccessor(AccessorInfo* info) { |
| Isolate* isolate = GetIsolate(); |
| String* name = String::cast(info->name()); |
| @@ -5939,8 +6105,8 @@ MaybeObject* AccessorPair::CopyWithoutTransitions() { |
| 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; |
| } |