Index: src/objects.cc |
diff --git a/src/objects.cc b/src/objects.cc |
index 414758f0e8350ec9c8be5c2cbd21453dca22a398..6527cae2cc2b76c8d6127fa12c43426c3682cf5e 100644 |
--- a/src/objects.cc |
+++ b/src/objects.cc |
@@ -2948,6 +2948,30 @@ Handle<Object> JSReceiver::SetPropertyWithDefinedSetter( |
} |
+bool Map::MayHaveIndexedCallbacksInPrototypeChain() { |
+ Heap* heap = GetHeap(); |
+ |
+ if (has_element_callbacks()) { |
+ return true; |
+ } |
+ |
+ for (Object* pt = prototype(); |
danno
2013/10/30 12:17:45
don't use the abbreviation, use the variable name
mvstanton
2013/10/30 18:22:28
Done.
|
+ pt != heap->null_value(); |
+ pt = pt->GetPrototype(GetIsolate())) { |
+ if (pt->IsJSProxy()) { |
+ // Be conservative, don't walk into proxies. |
+ return true; |
+ } |
+ |
+ if (JSObject::cast(pt)->map()->has_element_callbacks()) { |
+ return true; |
+ } |
+ } |
+ |
+ return false; |
+} |
+ |
+ |
MaybeObject* JSObject::SetElementWithCallbackSetterInPrototypes( |
uint32_t index, |
Object* value, |
@@ -4478,9 +4502,10 @@ Handle<Map> NormalizedMapCache::Get(Handle<NormalizedMapCache> cache, |
if (result->IsMap() && |
Handle<Map>::cast(result)->EquivalentToForNormalization(obj->map(), |
mode)) { |
+ Handle<Map> result_map = Handle<Map>::cast(result); |
#ifdef VERIFY_HEAP |
if (FLAG_verify_heap) { |
- Handle<Map>::cast(result)->SharedMapVerify(); |
+ result_map->SharedMapVerify(); |
} |
#endif |
#ifdef ENABLE_SLOW_ASSERTS |
@@ -4492,17 +4517,36 @@ Handle<Map> NormalizedMapCache::Get(Handle<NormalizedMapCache> cache, |
SHARED_NORMALIZED_MAP); |
ASSERT(memcmp(fresh->address(), |
- Handle<Map>::cast(result)->address(), |
- Map::kCodeCacheOffset) == 0); |
+ result_map->address(), |
+ Map::kTransitionsOrBackPointerOffset) == 0); |
STATIC_ASSERT(Map::kDependentCodeOffset == |
- Map::kCodeCacheOffset + kPointerSize); |
- int offset = Map::kDependentCodeOffset + kPointerSize; |
- ASSERT(memcmp(fresh->address() + offset, |
- Handle<Map>::cast(result)->address() + offset, |
- Map::kSize - offset) == 0); |
+ Map::kTransitionsOrBackPointerOffset + 3 * kPointerSize); |
+ |
+ // The final fields to look at are flag fields 3 and 4. Field 3 |
danno
2013/10/30 12:17:45
Is the logic needed? I thought the conclusion was
|
+ // needs special attention. |
+ STATIC_ASSERT(Map::kDependentCodeOffset + kPointerSize == |
+ Map::kBitField3Offset); |
+ STATIC_ASSERT(Map::kSize - (2 * kPointerSize) == Map::kBitField3Offset); |
+ STATIC_ASSERT(Map::kSize - kPointerSize == Map::kBitField4Offset); |
+ |
+ int mask = Map::EnumLengthBits::kMask | |
+ Map::NumberOfOwnDescriptorsBits::kMask | |
+ Map::IsShared::kMask | |
+ Map::FunctionWithPrototype::kMask | |
+ Map::DictionaryMap::kMask | |
+ // OwnsDescriptors::kMask | |
+ Map::IsObserved::kMask | |
+ Map::Deprecated::kMask | |
+ Map::IsFrozen::kMask | |
+ Map::IsUnstable::kMask | |
+ Map::IsMigrationTarget::kMask; |
+ int fresh_field3 = fresh->bit_field3() & mask; |
+ int result_field3 = result_map->bit_field3() & mask; |
+ ASSERT(fresh_field3 == result_field3); |
+ ASSERT(fresh->bit_field4() == result_map->bit_field4()); |
} |
#endif |
- return Handle<Map>::cast(result); |
+ return result_map; |
} |
Isolate* isolate = cache->GetIsolate(); |
@@ -4703,39 +4747,15 @@ Handle<SeededNumberDictionary> JSObject::NormalizeElements( |
MaybeObject* JSObject::NormalizeElements() { |
- ASSERT(!HasExternalArrayElements()); |
- |
- // Find the backing store. |
- FixedArrayBase* array = FixedArrayBase::cast(elements()); |
- Map* old_map = array->map(); |
- bool is_arguments = |
- (old_map == old_map->GetHeap()->non_strict_arguments_elements_map()); |
- if (is_arguments) { |
- array = FixedArrayBase::cast(FixedArray::cast(array)->get(1)); |
- } |
- if (array->IsDictionary()) return array; |
- |
- ASSERT(HasFastSmiOrObjectElements() || |
- HasFastDoubleElements() || |
- HasFastArgumentsElements()); |
- // Compute the effective length and allocate a new backing store. |
- int length = IsJSArray() |
- ? Smi::cast(JSArray::cast(this)->length())->value() |
- : array->length(); |
- int old_capacity = 0; |
- int used_elements = 0; |
- GetElementsCapacityAndUsage(&old_capacity, &used_elements); |
SeededNumberDictionary* dictionary; |
- MaybeObject* maybe_dictionary = |
- SeededNumberDictionary::Allocate(GetHeap(), used_elements); |
- if (!maybe_dictionary->To(&dictionary)) return maybe_dictionary; |
+ FixedArrayBase* array = GetElements(); |
danno
2013/10/30 12:17:45
I'd call this GetElementBackingStore(), and you mi
|
+ if (array->IsDictionary()) return array; |
- maybe_dictionary = CopyFastElementsToDictionary( |
- GetIsolate(), array, length, dictionary); |
+ MaybeObject* maybe_dictionary = CreateNormalizedElements(); |
if (!maybe_dictionary->To(&dictionary)) return maybe_dictionary; |
// Switch to using the dictionary as the backing storage for elements. |
- if (is_arguments) { |
+ if (HasNonStrictArgumentsElementsMap()) { |
FixedArray::cast(elements())->set(1, dictionary); |
} else { |
// Set the new map first to satify the elements type assert in |
@@ -4748,8 +4768,7 @@ MaybeObject* JSObject::NormalizeElements() { |
set_elements(dictionary); |
} |
- old_map->GetHeap()->isolate()->counters()->elements_to_dictionary()-> |
- Increment(); |
+ GetHeap()->isolate()->counters()->elements_to_dictionary()->Increment(); |
#ifdef DEBUG |
if (FLAG_trace_normalization) { |
@@ -4763,6 +4782,44 @@ MaybeObject* JSObject::NormalizeElements() { |
} |
+Handle<SeededNumberDictionary> JSObject::CreateNormalizedElements( |
+ Handle<JSObject> object) { |
+ CALL_HEAP_FUNCTION(object->GetIsolate(), |
+ object->CreateNormalizedElements(), |
+ SeededNumberDictionary); |
+} |
+ |
+ |
+MaybeObject* JSObject::CreateNormalizedElements() { |
+ ASSERT(!HasExternalArrayElements()); |
+ |
+ // Find the backing store. |
+ FixedArrayBase* array = GetElements(); |
+ ASSERT(!array->IsDictionary()); |
+ |
+ ASSERT(HasFastSmiOrObjectElements() || |
+ HasFastDoubleElements() || |
+ HasFastArgumentsElements()); |
+ // Compute the effective length and allocate a new backing store. |
+ int length = IsJSArray() |
+ ? Smi::cast(JSArray::cast(this)->length())->value() |
+ : array->length(); |
+ int old_capacity = 0; |
+ int used_elements = 0; |
+ GetElementsCapacityAndUsage(&old_capacity, &used_elements); |
+ SeededNumberDictionary* dictionary; |
+ MaybeObject* maybe_dictionary = |
+ SeededNumberDictionary::Allocate(GetHeap(), used_elements); |
+ if (!maybe_dictionary->To(&dictionary)) return maybe_dictionary; |
+ |
+ maybe_dictionary = CopyFastElementsToDictionary( |
+ GetIsolate(), array, length, dictionary); |
+ if (!maybe_dictionary->To(&dictionary)) return maybe_dictionary; |
+ |
+ return dictionary; |
+} |
+ |
+ |
Smi* JSReceiver::GenerateIdentityHash() { |
Isolate* isolate = GetIsolate(); |
@@ -5565,7 +5622,7 @@ Handle<Object> JSObject::Freeze(Handle<JSObject> object) { |
Map* transition_map = result.GetTransitionTarget(); |
ASSERT(transition_map->has_dictionary_elements()); |
ASSERT(transition_map->is_frozen()); |
- ASSERT(!transition_map->is_extensible()); |
+ ASSERT(!transition_map->is_extensible()); |
danno
2013/10/30 12:17:45
nit: remove stray whitespace change.
mvstanton
2013/10/30 18:22:28
Done.
|
object->set_map(transition_map); |
} else if (object->HasFastProperties() && old_map->CanHaveMoreTransitions()) { |
// Create a new descriptor array with fully-frozen properties |
@@ -6226,11 +6283,56 @@ void JSObject::SetElementCallback(Handle<JSObject> object, |
uint32_t index, |
Handle<Object> structure, |
PropertyAttributes attributes) { |
+ Isolate* isolate = object->GetIsolate(); |
Heap* heap = object->GetHeap(); |
PropertyDetails details = PropertyDetails(attributes, CALLBACKS, 0); |
+ bool is_arguments = object->HasNonStrictArgumentsElements(); |
+ |
+ // Do we need to create a new dictionary? |
+ Handle<SeededNumberDictionary> new_element_dictionary; |
danno
2013/10/30 12:17:45
I think NormalizeElements will do the right thing
mvstanton
2013/10/30 18:22:28
Yep, I thought combining the move to dictionary an
|
+ FixedArrayBase* array = object->GetElements(); |
+ if (!array->IsDictionary()) { |
+ new_element_dictionary = CreateNormalizedElements(object); |
+ } |
+ |
+ if (!object->map()->has_element_callbacks()) { |
+ LookupResult result(isolate); |
+ Handle<Map> old_map(object->map()); |
+ old_map->LookupTransition(*object, |
danno
2013/10/30 12:17:45
It looks like JSObject::SetObserve does almost exa
mvstanton
2013/10/30 18:22:28
addressed.
|
+ heap->element_callbacks_symbol(), |
+ &result); |
+ if (result.IsTransition()) { |
+ object->set_map(result.GetTransitionTarget()); |
+ } else { |
+ Handle<Map> new_map = Map::CopyAsElementCallbacksTransition(old_map, |
+ !is_arguments); |
+ object->set_map(*new_map); |
+ } |
+ |
+ ASSERT(object->map()->has_element_callbacks()); |
+ |
+ // Install the dictionary if required without doing an extra transition. |
+ if (!new_element_dictionary.is_null()) { |
+ if (is_arguments) { |
+ FixedArray::cast(object->elements())->set(1, *new_element_dictionary); |
+ } else { |
+ object->set_elements(*new_element_dictionary); |
+ } |
+ } |
+ |
+ // KeyedStoreICs (at least the non-generic ones) need a reset. |
+ // I *could* look at the maps and only reset the poly/mono ones that |
danno
2013/10/30 12:17:45
nit: we try to avoid using "I" or "we" in comments
mvstanton
2013/10/30 18:22:28
:) done.
|
+ // depend on a map in this prototype chain? |
+ heap->ClearAllICsByKind(Code::KEYED_STORE_IC); |
+ } else { |
+ // If our map is already flagged for element callbacks, then we should |
+ // not have had to create a new element dictionary. |
+ ASSERT(new_element_dictionary.is_null()); |
+ } |
+ |
+ Handle<SeededNumberDictionary> dictionary = Handle<SeededNumberDictionary>( |
+ SeededNumberDictionary::cast(object->GetElements()), isolate); |
- // Normalize elements to make this operation simple. |
- Handle<SeededNumberDictionary> dictionary = NormalizeElements(object); |
ASSERT(object->HasDictionaryElements() || |
object->HasDictionaryArgumentsElements()); |
@@ -6240,7 +6342,7 @@ void JSObject::SetElementCallback(Handle<JSObject> object, |
dictionary->set_requires_slow_elements(); |
// Update the dictionary backing store on the object. |
- if (object->elements()->map() == heap->non_strict_arguments_elements_map()) { |
+ if (is_arguments) { |
// Also delete any parameter alias. |
// |
// TODO(kmillikin): when deleting the last parameter alias we could |
@@ -6672,6 +6774,7 @@ MaybeObject* Map::RawCopy(int instance_size) { |
new_bit_field3 = Deprecated::update(new_bit_field3, false); |
new_bit_field3 = IsUnstable::update(new_bit_field3, false); |
result->set_bit_field3(new_bit_field3); |
+ result->set_bit_field4(bit_field4()); |
return result; |
} |
@@ -6923,6 +7026,60 @@ MaybeObject* Map::CopyAsElementsKind(ElementsKind kind, TransitionFlag flag) { |
} |
+MaybeObject* Map::CopyAsElementCallbacksTransition( |
danno
2013/10/30 12:17:45
I think with a little work, you can 100% share thi
mvstanton
2013/10/30 18:22:28
I this this in a basic way, with a protected funct
|
+ bool with_dictionary_elements) { |
+ ASSERT(!HasElementCallbacksTransition()); |
+ |
+ if (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_element_callbacks_map(new_map); |
+ if (added_elements->IsFailure()) return added_elements; |
+ |
+ new_map->InitializeDescriptors(instance_descriptors()); |
+ new_map->set_has_element_callbacks(true); |
+ if (with_dictionary_elements) { |
+ new_map->set_elements_kind(DICTIONARY_ELEMENTS); |
+ } |
+ new_map->SetBackPointer(this); |
+ set_owns_descriptors(false); |
+ return new_map; |
+ } |
+ |
+ // 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_has_element_callbacks(true); |
+ if (with_dictionary_elements) { |
+ new_map->set_elements_kind(DICTIONARY_ELEMENTS); |
+ } |
+ |
+ MaybeObject* added_elements = set_element_callbacks_map(new_map); |
+ if (added_elements->IsFailure()) return added_elements; |
+ new_map->SetBackPointer(this); |
+ |
+ return new_map; |
+} |
+ |
+ |
+Handle<Map> Map::CopyAsElementCallbacksTransition( |
+ Handle<Map> map, |
+ bool with_dictionary_elements) { |
+ Isolate* isolate = map->GetIsolate(); |
+ CALL_HEAP_FUNCTION(isolate, |
+ map->CopyAsElementCallbacksTransition( |
+ with_dictionary_elements), |
+ Map); |
+} |
+ |
+ |
Handle<Map> Map::CopyForObserved(Handle<Map> map) { |
ASSERT(!map->is_observed()); |
@@ -9431,6 +9588,7 @@ static bool CheckEquivalent(Map* first, Map* second) { |
first->instance_type() == second->instance_type() && |
first->bit_field() == second->bit_field() && |
first->bit_field2() == second->bit_field2() && |
+ first->bit_field4() == second->bit_field4() && |
first->is_observed() == second->is_observed() && |
first->function_with_prototype() == second->function_with_prototype(); |
} |
@@ -10565,6 +10723,16 @@ void Code::ReplaceNthCell(int n, Cell* replace_with) { |
void Code::ClearInlineCaches() { |
+ ClearInlineCaches(NULL); |
+} |
+ |
+ |
+void Code::ClearInlineCaches(Code::Kind kind) { |
+ ClearInlineCaches(&kind); |
+} |
+ |
+ |
+void Code::ClearInlineCaches(Code::Kind* kind) { |
int mask = RelocInfo::ModeMask(RelocInfo::CODE_TARGET) | |
RelocInfo::ModeMask(RelocInfo::CONSTRUCT_CALL) | |
RelocInfo::ModeMask(RelocInfo::CODE_TARGET_WITH_ID) | |
@@ -10573,7 +10741,9 @@ void Code::ClearInlineCaches() { |
RelocInfo* info = it.rinfo(); |
Code* target(Code::GetCodeFromTargetAddress(info->target_address())); |
if (target->is_inline_cache_stub()) { |
- IC::Clear(this->GetIsolate(), info->pc()); |
+ if (kind == NULL || *kind == target->kind()) { |
+ IC::Clear(this->GetIsolate(), info->pc()); |
+ } |
} |
} |
} |
@@ -11746,6 +11916,8 @@ Handle<Object> JSObject::SetPrototype(Handle<JSObject> object, |
} |
} |
+ bool has_element_callbacks_in_chain = |
+ object->map()->MayHaveIndexedCallbacksInPrototypeChain(); |
Handle<JSObject> real_receiver = object; |
if (skip_hidden_prototypes) { |
@@ -11778,6 +11950,13 @@ Handle<Object> JSObject::SetPrototype(Handle<JSObject> object, |
ASSERT(new_map->prototype() == *value); |
real_receiver->set_map(*new_map); |
+ if (!has_element_callbacks_in_chain && |
+ new_map->MayHaveIndexedCallbacksInPrototypeChain()) { |
+ // If our prototype chain didn't have element callbacks, and now we do |
+ // have them, then we need to clear KeyedStoreICs. |
+ object->GetHeap()->ClearAllICsByKind(Code::KEYED_STORE_IC); |
+ } |
+ |
heap->ClearInstanceofCache(); |
ASSERT(size == object->Size()); |
return value; |
@@ -12767,8 +12946,11 @@ MaybeObject* JSObject::TransitionElementsKind(ElementsKind to_kind) { |
if (from_kind == to_kind) return this; |
- MaybeObject* maybe_failure = UpdateAllocationSite(to_kind); |
- if (maybe_failure->IsFailure()) return maybe_failure; |
+ // Don't update the site if to_kind isn't fast |
+ if (IsFastElementsKind(to_kind)) { |
+ MaybeObject* maybe_failure = UpdateAllocationSite(to_kind); |
+ if (maybe_failure->IsFailure()) return maybe_failure; |
+ } |
Isolate* isolate = GetIsolate(); |
if (elements() == isolate->heap()->empty_fixed_array() || |