From 625a0e97594ee4e8ceb05f1c949d2625cad147c2 Mon Sep 17 00:00:00 2001 From: "adamk@chromium.org" Date: Sat, 13 Jul 2013 00:20:40 +0000 Subject: [PATCH] Add map transition for observed objects This patch enables objects to undergo a single transition when they become observed, avoiding the need to create a new map for every observed objects. Observed objects which become unobserved does not cause another map transition and unobserved does not clear the observed bit on the map. The unobserved object. R=verwaest@chromium.org Review URL: https://codereview.chromium.org/18221006 Patch from Rafael Weinstein . git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@15650 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- include/v8.h | 2 +- src/heap.cc | 5 ++++ src/heap.h | 3 +- src/object-observe.js | 12 +++----- src/objects.cc | 69 ++++++++++++++++++++++++++++++++++++++++++- src/objects.h | 6 ++++ src/runtime.cc | 20 ++----------- src/runtime.h | 2 +- 8 files changed, 90 insertions(+), 29 deletions(-) diff --git a/include/v8.h b/include/v8.h index d4d333e439..9ce05831b4 100644 --- a/include/v8.h +++ b/include/v8.h @@ -5399,7 +5399,7 @@ class Internals { static const int kNullValueRootIndex = 7; static const int kTrueValueRootIndex = 8; static const int kFalseValueRootIndex = 9; - static const int kEmptyStringRootIndex = 131; + static const int kEmptyStringRootIndex = 132; static const int kNodeClassIdOffset = 1 * kApiPointerSize; static const int kNodeFlagsOffset = 1 * kApiPointerSize + 3; diff --git a/src/heap.cc b/src/heap.cc index ebf1487e14..0b2fe16481 100644 --- a/src/heap.cc +++ b/src/heap.cc @@ -3172,6 +3172,11 @@ bool Heap::CreateInitialObjects() { SeededNumberDictionary::cast(obj)->set_requires_slow_elements(); set_empty_slow_element_dictionary(SeededNumberDictionary::cast(obj)); + { MaybeObject* maybe_obj = AllocateSymbol(); + if (!maybe_obj->ToObject(&obj)) return false; + } + set_observed_symbol(Symbol::cast(obj)); + // Handling of script id generation is in Factory::NewScript. set_last_script_id(Smi::FromInt(v8::Script::kNoScriptId)); diff --git a/src/heap.h b/src/heap.h index 4bc2d307eb..2c97fafcbc 100644 --- a/src/heap.h +++ b/src/heap.h @@ -187,7 +187,8 @@ namespace internal { V(Map, external_map, ExternalMap) \ V(Symbol, frozen_symbol, FrozenSymbol) \ V(SeededNumberDictionary, empty_slow_element_dictionary, \ - EmptySlowElementDictionary) + EmptySlowElementDictionary) \ + V(Symbol, observed_symbol, ObservedSymbol) #define ROOT_LIST(V) \ STRONG_ROOT_LIST(V) \ diff --git a/src/object-observe.js b/src/object-observe.js index ada7919d6d..90c9a699e0 100644 --- a/src/object-observe.js +++ b/src/object-observe.js @@ -216,8 +216,10 @@ function ObjectObserve(object, callback, accept) { } var objectInfo = objectInfoMap.get(object); - if (IS_UNDEFINED(objectInfo)) objectInfo = CreateObjectInfo(object); - %SetIsObserved(object, true); + if (IS_UNDEFINED(objectInfo)) { + objectInfo = CreateObjectInfo(object); + %SetIsObserved(object); + } EnsureObserverRemoved(objectInfo, callback); @@ -241,12 +243,6 @@ function ObjectUnobserve(object, callback) { return object; EnsureObserverRemoved(objectInfo, callback); - - if (objectInfo.changeObservers.length === 0 && - objectInfo.inactiveObservers.length === 0) { - %SetIsObserved(object, false); - } - return object; } diff --git a/src/objects.cc b/src/objects.cc index 3e7a5e9c42..7b06b8732a 100644 --- a/src/objects.cc +++ b/src/objects.cc @@ -4018,7 +4018,7 @@ MaybeObject* JSObject::SetLocalPropertyIgnoreAttributes( Handle old_value(isolate->heap()->the_hole_value(), isolate); PropertyAttributes old_attributes = ABSENT; bool is_observed = FLAG_harmony_observation && self->map()->is_observed(); - if (is_observed) { + if (is_observed && lookup.IsProperty()) { if (lookup.IsDataProperty()) old_value = Object::GetProperty(self, name); old_attributes = lookup.GetAttributes(); } @@ -5557,6 +5557,40 @@ MUST_USE_RESULT MaybeObject* JSObject::Freeze(Isolate* isolate) { } +MUST_USE_RESULT MaybeObject* JSObject::SetObserved(Isolate* isolate) { + if (map()->is_observed()) + return isolate->heap()->undefined_value(); + + Heap* heap = isolate->heap(); + + if (!HasExternalArrayElements()) { + // Go to dictionary mode, so that we don't skip map checks. + MaybeObject* maybe = NormalizeElements(); + if (maybe->IsFailure()) return maybe; + ASSERT(!HasFastElements()); + } + + LookupResult result(isolate); + map()->LookupTransition(this, heap->observed_symbol(), &result); + + Map* new_map; + if (result.IsTransition()) { + new_map = result.GetTransitionTarget(); + ASSERT(new_map->is_observed()); + } else if (map()->CanHaveMoreTransitions()) { + MaybeObject* maybe_new_map = map()->CopyForObserved(); + if (!maybe_new_map->To(&new_map)) return maybe_new_map; + } else { + MaybeObject* maybe_copy = map()->Copy(); + if (!maybe_copy->To(&new_map)) return maybe_copy; + new_map->set_is_observed(true); + } + set_map(new_map); + + return heap->undefined_value(); +} + + MUST_USE_RESULT MaybeObject* JSObject::DeepCopy(Isolate* isolate) { StackLimitCheck check(isolate); if (check.HasOverflowed()) return isolate->StackOverflow(); @@ -6680,6 +6714,39 @@ MaybeObject* Map::CopyAsElementsKind(ElementsKind kind, TransitionFlag flag) { } +MaybeObject* Map::CopyForObserved() { + ASSERT(!is_observed()); + + // 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; + if (owns_descriptors()) { + maybe_new_map = CopyDropDescriptors(); + } else { + maybe_new_map = Copy(); + } + if (!maybe_new_map->To(&new_map)) return maybe_new_map; + + TransitionArray* transitions; + MaybeObject* maybe_transitions = AddTransition(GetHeap()->observed_symbol(), + new_map, + FULL_TRANSITION); + if (!maybe_transitions->To(&transitions)) return maybe_transitions; + set_transitions(transitions); + + new_map->set_is_observed(true); + + if (owns_descriptors()) { + new_map->InitializeDescriptors(instance_descriptors()); + set_owns_descriptors(false); + } + + new_map->SetBackPointer(this); + return new_map; +} + + MaybeObject* Map::CopyWithPreallocatedFieldDescriptors() { if (pre_allocated_property_fields() == 0) return CopyDropDescriptors(); diff --git a/src/objects.h b/src/objects.h index a4e70eb8eb..bf04934605 100644 --- a/src/objects.h +++ b/src/objects.h @@ -2333,6 +2333,10 @@ class JSObject: public JSReceiver { // ES5 Object.freeze MUST_USE_RESULT MaybeObject* Freeze(Isolate* isolate); + + // Called the first time an object is observed with ES7 Object.observe. + MUST_USE_RESULT MaybeObject* SetObserved(Isolate* isolate); + // Copy object MUST_USE_RESULT MaybeObject* DeepCopy(Isolate* isolate); @@ -5477,8 +5481,10 @@ class Map: public HeapObject { int index, TransitionFlag flag); MUST_USE_RESULT MaybeObject* AsElementsKind(ElementsKind kind); + MUST_USE_RESULT MaybeObject* CopyAsElementsKind(ElementsKind kind, TransitionFlag flag); + MUST_USE_RESULT MaybeObject* CopyForObserved(); MUST_USE_RESULT MaybeObject* CopyNormalized(PropertyNormalizationMode mode, NormalizedMapSharingMode sharing); diff --git a/src/runtime.cc b/src/runtime.cc index fbad080346..3bc943947d 100644 --- a/src/runtime.cc +++ b/src/runtime.cc @@ -13786,9 +13786,8 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_IsObserved) { RUNTIME_FUNCTION(MaybeObject*, Runtime_SetIsObserved) { SealHandleScope shs(isolate); - ASSERT(args.length() == 2); + ASSERT(args.length() == 1); CONVERT_ARG_CHECKED(JSReceiver, obj, 0); - CONVERT_BOOLEAN_ARG_CHECKED(is_observed, 1); if (obj->IsJSGlobalProxy()) { Object* proto = obj->GetPrototype(); if (proto->IsNull()) return isolate->heap()->undefined_value(); @@ -13797,21 +13796,8 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_SetIsObserved) { } ASSERT(!(obj->map()->is_observed() && obj->IsJSObject() && JSObject::cast(obj)->HasFastElements())); - if (obj->map()->is_observed() != is_observed) { - if (is_observed && obj->IsJSObject() && - !JSObject::cast(obj)->HasExternalArrayElements()) { - // Go to dictionary mode, so that we don't skip map checks. - MaybeObject* maybe = JSObject::cast(obj)->NormalizeElements(); - if (maybe->IsFailure()) return maybe; - ASSERT(!JSObject::cast(obj)->HasFastElements()); - } - MaybeObject* maybe = obj->map()->Copy(); - Map* map; - if (!maybe->To(&map)) return maybe; - map->set_is_observed(is_observed); - obj->set_map(map); - } - return isolate->heap()->undefined_value(); + ASSERT(obj->IsJSObject()); + return JSObject::cast(obj)->SetObserved(isolate); } diff --git a/src/runtime.h b/src/runtime.h index 4777c3cbaf..f73082788d 100644 --- a/src/runtime.h +++ b/src/runtime.h @@ -352,7 +352,7 @@ namespace internal { \ /* Harmony observe */ \ F(IsObserved, 1, 1) \ - F(SetIsObserved, 2, 1) \ + F(SetIsObserved, 1, 1) \ F(SetObserverDeliveryPending, 0, 1) \ F(GetObservationState, 0, 1) \ F(ObservationWeakMapCreate, 0, 1) \