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 <rafaelw@chromium.org>.

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@15650 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
adamk@chromium.org 2013-07-13 00:20:40 +00:00
parent f5ab1b2a4a
commit 625a0e9759
8 changed files with 90 additions and 29 deletions

View File

@ -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;

View File

@ -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));

View File

@ -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) \

View File

@ -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;
}

View File

@ -4018,7 +4018,7 @@ MaybeObject* JSObject::SetLocalPropertyIgnoreAttributes(
Handle<Object> 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();

View File

@ -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);

View File

@ -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);
}

View File

@ -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) \