// Copyright 2012 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "src/transitions.h" #include "src/objects-inl.h" #include "src/transitions-inl.h" #include "src/utils.h" namespace v8 { namespace internal { void TransitionsAccessor::Initialize() { raw_transitions_ = map_->raw_transitions(); if (raw_transitions_->IsSmi()) { encoding_ = kUninitialized; } else if (HeapObject::cast(raw_transitions_)->IsWeakCell()) { encoding_ = kWeakCell; } else if (HeapObject::cast(raw_transitions_)->IsTuple3()) { encoding_ = kTuple3Handler; } else if (HeapObject::cast(raw_transitions_)->IsFixedArray()) { encoding_ = kFixedArrayHandler; } else if (HeapObject::cast(raw_transitions_)->IsTransitionArray()) { encoding_ = kFullTransitionArray; } else { DCHECK(HeapObject::cast(raw_transitions_)->IsPrototypeInfo()); encoding_ = kPrototypeInfo; } target_cell_ = nullptr; #if DEBUG needs_reload_ = false; #endif } Map* TransitionsAccessor::GetSimpleTransition() { switch (encoding()) { case kWeakCell: return Map::cast(GetTargetCell()->value()); case kTuple3Handler: return Map::cast(GetTargetCell()->value()); case kFixedArrayHandler: return Map::cast(GetTargetCell()->value()); default: return nullptr; } } bool TransitionsAccessor::HasSimpleTransitionTo(WeakCell* cell) { DCHECK(cell->value()->IsMap()); switch (encoding()) { case kWeakCell: return raw_transitions_ == cell; case kTuple3Handler: return StoreHandler::GetTuple3TransitionCell(raw_transitions_) == cell; case kFixedArrayHandler: return StoreHandler::GetArrayTransitionCell(raw_transitions_) == cell; case kPrototypeInfo: case kUninitialized: case kFullTransitionArray: return false; } UNREACHABLE(); return false; // Make GCC happy. } void TransitionsAccessor::Insert(Handle name, Handle target, SimpleTransitionFlag flag) { DCHECK(!map_handle_.is_null()); Isolate* isolate = map_->GetIsolate(); target->SetBackPointer(map_); // If the map doesn't have any transitions at all yet, install the new one. if (encoding() == kUninitialized) { if (flag == SIMPLE_PROPERTY_TRANSITION) { ReplaceTransitions(*Map::WeakCellForMap(target)); return; } // If the flag requires a full TransitionArray, allocate one. Handle result = TransitionArray::Allocate(isolate, 0, 1); ReplaceTransitions(*result); Reload(); } bool is_special_transition = flag == SPECIAL_TRANSITION; // If the map has a simple transition, check if it should be overwritten. Map* simple_transition = GetSimpleTransition(); if (simple_transition != nullptr) { Name* key = GetSimpleTransitionKey(simple_transition); PropertyDetails old_details = GetSimpleTargetDetails(simple_transition); PropertyDetails new_details = is_special_transition ? PropertyDetails::Empty() : GetTargetDetails(*name, *target); if (flag == SIMPLE_PROPERTY_TRANSITION && key->Equals(*name) && old_details.kind() == new_details.kind() && old_details.attributes() == new_details.attributes()) { ReplaceTransitions(*Map::WeakCellForMap(target)); return; } // Otherwise allocate a full TransitionArray with slack for a new entry. Handle result = TransitionArray::Allocate(isolate, 1, 1); // Reload state; the allocation might have caused it to be cleared. Reload(); simple_transition = GetSimpleTransition(); if (simple_transition != nullptr) { Object* value = raw_transitions_->IsWeakCell() ? WeakCell::cast(raw_transitions_)->value() : raw_transitions_; result->Set(0, GetSimpleTransitionKey(simple_transition), value); } else { result->SetNumberOfTransitions(0); } ReplaceTransitions(*result); Reload(); } // At this point, we know that the map has a full TransitionArray. DCHECK_EQ(kFullTransitionArray, encoding()); int number_of_transitions = 0; int new_nof = 0; int insertion_index = kNotFound; DCHECK_EQ(is_special_transition, IsSpecialTransition(*name)); PropertyDetails details = is_special_transition ? PropertyDetails::Empty() : GetTargetDetails(*name, *target); { DisallowHeapAllocation no_gc; TransitionArray* array = transitions(); number_of_transitions = array->number_of_transitions(); new_nof = number_of_transitions; int index = is_special_transition ? array->SearchSpecial(Symbol::cast(*name), &insertion_index) : array->Search(details.kind(), *name, details.attributes(), &insertion_index); // If an existing entry was found, overwrite it and return. if (index != kNotFound) { array->SetTarget(index, *target); return; } ++new_nof; CHECK(new_nof <= kMaxNumberOfTransitions); DCHECK(insertion_index >= 0 && insertion_index <= number_of_transitions); // If there is enough capacity, insert new entry into the existing array. if (new_nof <= array->Capacity()) { array->SetNumberOfTransitions(new_nof); for (index = number_of_transitions; index > insertion_index; --index) { array->SetKey(index, array->GetKey(index - 1)); array->SetTarget(index, array->GetRawTarget(index - 1)); } array->SetKey(index, *name); array->SetTarget(index, *target); SLOW_DCHECK(array->IsSortedNoDuplicates()); return; } } // We're gonna need a bigger TransitionArray. Handle result = TransitionArray::Allocate( isolate, new_nof, Map::SlackForArraySize(number_of_transitions, kMaxNumberOfTransitions)); // The map's transition array may have shrunk during the allocation above as // it was weakly traversed, though it is guaranteed not to disappear. Trim the // result copy if needed, and recompute variables. Reload(); DisallowHeapAllocation no_gc; TransitionArray* array = transitions(); if (array->number_of_transitions() != number_of_transitions) { DCHECK(array->number_of_transitions() < number_of_transitions); number_of_transitions = array->number_of_transitions(); new_nof = number_of_transitions; insertion_index = kNotFound; int index = is_special_transition ? array->SearchSpecial(Symbol::cast(*name), &insertion_index) : array->Search(details.kind(), *name, details.attributes(), &insertion_index); if (index == kNotFound) { ++new_nof; } else { insertion_index = index; } DCHECK(insertion_index >= 0 && insertion_index <= number_of_transitions); result->Shrink(TransitionArray::ToKeyIndex(new_nof)); result->SetNumberOfTransitions(new_nof); } if (array->HasPrototypeTransitions()) { result->SetPrototypeTransitions(array->GetPrototypeTransitions()); } DCHECK_NE(kNotFound, insertion_index); for (int i = 0; i < insertion_index; ++i) { result->Set(i, array->GetKey(i), array->GetRawTarget(i)); } result->Set(insertion_index, *name, *target); for (int i = insertion_index; i < number_of_transitions; ++i) { result->Set(i + 1, array->GetKey(i), array->GetRawTarget(i)); } SLOW_DCHECK(result->IsSortedNoDuplicates()); ReplaceTransitions(*result); } void TransitionsAccessor::UpdateHandler(Name* name, Object* handler) { if (map_->is_dictionary_map()) return; switch (encoding()) { case kPrototypeInfo: case kUninitialized: UNREACHABLE(); return; case kWeakCell: case kTuple3Handler: case kFixedArrayHandler: DCHECK_EQ(GetSimpleTransition(), GetTargetFromRaw(handler)); ReplaceTransitions(handler); return; case kFullTransitionArray: { PropertyAttributes attributes = name->IsPrivate() ? DONT_ENUM : NONE; int transition = transitions()->Search(kData, name, attributes); DCHECK_NE(kNotFound, transition); DCHECK_EQ(transitions()->GetTarget(transition), GetTargetFromRaw(handler)); transitions()->SetTarget(transition, handler); return; } } } Object* TransitionsAccessor::SearchHandler(Name* name, Handle* out_transition) { switch (encoding()) { case kPrototypeInfo: case kUninitialized: case kWeakCell: return nullptr; case kTuple3Handler: return StoreHandler::ValidTuple3HandlerOrNull(raw_transitions_, name, out_transition); case kFixedArrayHandler: return StoreHandler::ValidFixedArrayHandlerOrNull(raw_transitions_, name, out_transition); case kFullTransitionArray: { int transition = transitions()->Search(kData, name, NONE); if (transition == kNotFound) return nullptr; Object* raw_handler = transitions()->GetRawTarget(transition); if (raw_handler->IsTuple3()) { return StoreHandler::ValidTuple3HandlerOrNull(raw_handler, nullptr, out_transition); } if (raw_handler->IsFixedArray()) { return StoreHandler::ValidFixedArrayHandlerOrNull(raw_handler, nullptr, out_transition); } return nullptr; } } UNREACHABLE(); return nullptr; // Make GCC happy. } Map* TransitionsAccessor::SearchTransition(Name* name, PropertyKind kind, PropertyAttributes attributes) { DCHECK(name->IsUniqueName()); WeakCell* cell = nullptr; switch (encoding()) { case kPrototypeInfo: case kUninitialized: return nullptr; case kWeakCell: cell = GetTargetCell(); break; case kTuple3Handler: cell = GetTargetCell(); break; case kFixedArrayHandler: cell = GetTargetCell(); break; case kFullTransitionArray: { int transition = transitions()->Search(kind, name, attributes); if (transition == kNotFound) return nullptr; return transitions()->GetTarget(transition); } } DCHECK(!cell->cleared()); if (!IsMatchingMap(cell, name, kind, attributes)) return nullptr; return Map::cast(cell->value()); } Map* TransitionsAccessor::SearchSpecial(Symbol* name) { if (encoding() != kFullTransitionArray) return nullptr; int transition = transitions()->SearchSpecial(name); if (transition == kNotFound) return NULL; return transitions()->GetTarget(transition); } // static bool TransitionsAccessor::IsSpecialTransition(Name* name) { if (!name->IsSymbol()) return false; Heap* heap = name->GetHeap(); return name == heap->nonextensible_symbol() || name == heap->sealed_symbol() || name == heap->frozen_symbol() || name == heap->elements_transition_symbol() || name == heap->strict_function_transition_symbol(); } Handle TransitionsAccessor::FindTransitionToField(Handle name) { DCHECK(name->IsUniqueName()); DisallowHeapAllocation no_gc; Map* target = SearchTransition(*name, kData, NONE); if (target == NULL) return Handle::null(); PropertyDetails details = target->GetLastDescriptorDetails(); DCHECK_EQ(NONE, details.attributes()); if (details.location() != kField) return Handle::null(); DCHECK_EQ(kData, details.kind()); return Handle(target); } Handle TransitionsAccessor::ExpectedTransitionKey() { DisallowHeapAllocation no_gc; WeakCell* cell = nullptr; switch (encoding()) { case kPrototypeInfo: case kUninitialized: case kFullTransitionArray: return Handle::null(); case kWeakCell: cell = GetTargetCell(); break; case kTuple3Handler: cell = GetTargetCell(); break; case kFixedArrayHandler: cell = GetTargetCell(); break; } DCHECK(!cell->cleared()); Map* target = Map::cast(cell->value()); PropertyDetails details = GetSimpleTargetDetails(target); if (details.location() != kField) return Handle::null(); DCHECK_EQ(kData, details.kind()); if (details.attributes() != NONE) return Handle::null(); Name* name = GetSimpleTransitionKey(target); if (!name->IsString()) return Handle::null(); return handle(String::cast(name)); } Handle TransitionsAccessor::ExpectedTransitionTarget() { DCHECK(!ExpectedTransitionKey().is_null()); return handle(GetTarget(0)); } bool TransitionsAccessor::CanHaveMoreTransitions() { if (map_->is_dictionary_map()) return false; if (encoding() == kFullTransitionArray) { return transitions()->number_of_transitions() < kMaxNumberOfTransitions; } return true; } // static bool TransitionsAccessor::IsMatchingMap(WeakCell* target_cell, Name* name, PropertyKind kind, PropertyAttributes attributes) { Map* target = Map::cast(target_cell->value()); int descriptor = target->LastAdded(); DescriptorArray* descriptors = target->instance_descriptors(); Name* key = descriptors->GetKey(descriptor); if (key != name) return false; PropertyDetails details = descriptors->GetDetails(descriptor); return (details.kind() == kind && details.attributes() == attributes); } // static bool TransitionArray::CompactPrototypeTransitionArray(FixedArray* array) { const int header = kProtoTransitionHeaderSize; int number_of_transitions = NumberOfPrototypeTransitions(array); if (number_of_transitions == 0) { // Empty array cannot be compacted. return false; } int new_number_of_transitions = 0; for (int i = 0; i < number_of_transitions; i++) { Object* cell = array->get(header + i); if (!WeakCell::cast(cell)->cleared()) { if (new_number_of_transitions != i) { array->set(header + new_number_of_transitions, cell); } new_number_of_transitions++; } } // Fill slots that became free with undefined value. for (int i = new_number_of_transitions; i < number_of_transitions; i++) { array->set_undefined(header + i); } if (number_of_transitions != new_number_of_transitions) { SetNumberOfPrototypeTransitions(array, new_number_of_transitions); } return new_number_of_transitions < number_of_transitions; } // static Handle TransitionArray::GrowPrototypeTransitionArray( Handle array, int new_capacity, Isolate* isolate) { // Grow array by factor 2 up to MaxCachedPrototypeTransitions. int capacity = array->length() - kProtoTransitionHeaderSize; new_capacity = Min(kMaxCachedPrototypeTransitions, new_capacity); DCHECK_GT(new_capacity, capacity); int grow_by = new_capacity - capacity; array = isolate->factory()->CopyFixedArrayAndGrow(array, grow_by, TENURED); if (capacity < 0) { // There was no prototype transitions array before, so the size // couldn't be copied. Initialize it explicitly. SetNumberOfPrototypeTransitions(*array, 0); } return array; } void TransitionsAccessor::PutPrototypeTransition(Handle prototype, Handle target_map) { DCHECK(HeapObject::cast(*prototype)->map()->IsMap()); // Don't cache prototype transition if this map is either shared, or a map of // a prototype. if (map_->is_prototype_map()) return; if (map_->is_dictionary_map() || !FLAG_cache_prototype_transitions) return; const int header = TransitionArray::kProtoTransitionHeaderSize; Handle cache(GetPrototypeTransitions()); int capacity = cache->length() - header; int transitions = TransitionArray::NumberOfPrototypeTransitions(*cache) + 1; if (transitions > capacity) { // Grow the array if compacting it doesn't free space. if (!TransitionArray::CompactPrototypeTransitionArray(*cache)) { if (capacity == TransitionArray::kMaxCachedPrototypeTransitions) return; cache = TransitionArray::GrowPrototypeTransitionArray( cache, 2 * transitions, target_map->GetIsolate()); Reload(); SetPrototypeTransitions(cache); } } // Reload number of transitions as they might have been compacted. int last = TransitionArray::NumberOfPrototypeTransitions(*cache); int entry = header + last; Handle target_cell = Map::WeakCellForMap(target_map); cache->set(entry, *target_cell); TransitionArray::SetNumberOfPrototypeTransitions(*cache, last + 1); } Handle TransitionsAccessor::GetPrototypeTransition( Handle prototype) { DisallowHeapAllocation no_gc; FixedArray* cache = GetPrototypeTransitions(); int length = TransitionArray::NumberOfPrototypeTransitions(cache); for (int i = 0; i < length; i++) { WeakCell* target_cell = WeakCell::cast( cache->get(TransitionArray::kProtoTransitionHeaderSize + i)); if (!target_cell->cleared() && Map::cast(target_cell->value())->prototype() == *prototype) { return handle(Map::cast(target_cell->value())); } } return Handle(); } FixedArray* TransitionsAccessor::GetPrototypeTransitions() { if (encoding() != kFullTransitionArray || !transitions()->HasPrototypeTransitions()) { return map_->GetHeap()->empty_fixed_array(); } return transitions()->GetPrototypeTransitions(); } // static void TransitionArray::SetNumberOfPrototypeTransitions( FixedArray* proto_transitions, int value) { DCHECK(proto_transitions->length() != 0); proto_transitions->set(kProtoTransitionNumberOfEntriesOffset, Smi::FromInt(value)); } int TransitionsAccessor::NumberOfTransitions() { switch (encoding()) { case kPrototypeInfo: case kUninitialized: return 0; case kWeakCell: case kTuple3Handler: case kFixedArrayHandler: return 1; case kFullTransitionArray: return transitions()->number_of_transitions(); } UNREACHABLE(); return 0; // Make GCC happy. } Handle TransitionArray::Allocate(Isolate* isolate, int number_of_transitions, int slack) { Handle array = isolate->factory()->NewTransitionArray( LengthFor(number_of_transitions + slack)); array->set(kPrototypeTransitionsIndex, Smi::kZero); array->set(kTransitionLengthIndex, Smi::FromInt(number_of_transitions)); return Handle::cast(array); } void TransitionArray::Zap() { MemsetPointer(data_start() + kPrototypeTransitionsIndex, GetHeap()->the_hole_value(), length() - kPrototypeTransitionsIndex); SetNumberOfTransitions(0); } void TransitionsAccessor::ReplaceTransitions(Object* new_transitions) { if (encoding() == kFullTransitionArray) { TransitionArray* old_transitions = transitions(); #if DEBUG CheckNewTransitionsAreConsistent(old_transitions, new_transitions); DCHECK(old_transitions != new_transitions); #endif // Transition arrays are not shared. When one is replaced, it should not // keep referenced objects alive, so we zap it. // When there is another reference to the array somewhere (e.g. a handle), // not zapping turns from a waste of memory into a source of crashes. old_transitions->Zap(); } map_->set_raw_transitions(new_transitions); MarkNeedsReload(); } void TransitionsAccessor::SetPrototypeTransitions( Handle proto_transitions) { EnsureHasFullTransitionArray(); transitions()->SetPrototypeTransitions(*proto_transitions); } void TransitionsAccessor::EnsureHasFullTransitionArray() { if (encoding() == kFullTransitionArray) return; int nof = encoding() == kUninitialized ? 0 : 1; Handle result = TransitionArray::Allocate(map_->GetIsolate(), nof); DisallowHeapAllocation no_gc; Reload(); // Reload after possible GC. if (nof == 1) { Map* target = GetSimpleTransition(); if (target == nullptr) { // If allocation caused GC and cleared the target, trim the new array. result->Shrink(TransitionArray::ToKeyIndex(0)); result->SetNumberOfTransitions(0); } else { // Otherwise populate the new array. Name* key = GetSimpleTransitionKey(target); result->Set(0, key, target); } } ReplaceTransitions(*result); Reload(); // Reload after replacing transitions. } void TransitionsAccessor::TraverseTransitionTreeInternal( TraverseCallback callback, void* data, DisallowHeapAllocation* no_gc) { Map* simple_target = nullptr; switch (encoding()) { case kPrototypeInfo: case kUninitialized: break; case kWeakCell: simple_target = Map::cast(GetTargetCell()->value()); break; case kTuple3Handler: simple_target = Map::cast(GetTargetCell()->value()); break; case kFixedArrayHandler: simple_target = Map::cast(GetTargetCell()->value()); break; case kFullTransitionArray: { if (transitions()->HasPrototypeTransitions()) { FixedArray* proto_trans = transitions()->GetPrototypeTransitions(); int length = TransitionArray::NumberOfPrototypeTransitions(proto_trans); for (int i = 0; i < length; ++i) { int index = TransitionArray::kProtoTransitionHeaderSize + i; WeakCell* cell = WeakCell::cast(proto_trans->get(index)); if (cell->cleared()) continue; TransitionsAccessor(Map::cast(cell->value()), no_gc) .TraverseTransitionTreeInternal(callback, data, no_gc); } } for (int i = 0; i < transitions()->number_of_transitions(); ++i) { TransitionsAccessor(transitions()->GetTarget(i), no_gc) .TraverseTransitionTreeInternal(callback, data, no_gc); } break; } } if (simple_target != nullptr) { TransitionsAccessor(simple_target, no_gc) .TraverseTransitionTreeInternal(callback, data, no_gc); } callback(map_, data); } #ifdef DEBUG void TransitionsAccessor::CheckNewTransitionsAreConsistent( TransitionArray* old_transitions, Object* transitions) { // This function only handles full transition arrays. DCHECK_EQ(kFullTransitionArray, encoding()); TransitionArray* new_transitions = TransitionArray::cast(transitions); for (int i = 0; i < old_transitions->number_of_transitions(); i++) { Map* target = old_transitions->GetTarget(i); if (target->instance_descriptors() == map_->instance_descriptors()) { Name* key = old_transitions->GetKey(i); int new_target_index; if (IsSpecialTransition(key)) { new_target_index = new_transitions->SearchSpecial(Symbol::cast(key)); } else { PropertyDetails details = GetTargetDetails(key, target); new_target_index = new_transitions->Search(details.kind(), key, details.attributes()); } DCHECK_NE(TransitionArray::kNotFound, new_target_index); DCHECK_EQ(target, new_transitions->GetTarget(new_target_index)); } } } #endif // Private non-static helper functions (operating on full transition arrays). int TransitionArray::SearchDetails(int transition, PropertyKind kind, PropertyAttributes attributes, int* out_insertion_index) { int nof_transitions = number_of_transitions(); DCHECK(transition < nof_transitions); Name* key = GetKey(transition); for (; transition < nof_transitions && GetKey(transition) == key; transition++) { Map* target = GetTarget(transition); PropertyDetails target_details = TransitionsAccessor::GetTargetDetails(key, target); int cmp = CompareDetails(kind, attributes, target_details.kind(), target_details.attributes()); if (cmp == 0) { return transition; } else if (cmp < 0) { break; } } if (out_insertion_index != NULL) *out_insertion_index = transition; return kNotFound; } int TransitionArray::Search(PropertyKind kind, Name* name, PropertyAttributes attributes, int* out_insertion_index) { int transition = SearchName(name, out_insertion_index); if (transition == kNotFound) return kNotFound; return SearchDetails(transition, kind, attributes, out_insertion_index); } void TransitionArray::Sort() { DisallowHeapAllocation no_gc; // In-place insertion sort. int length = number_of_transitions(); for (int i = 1; i < length; i++) { Name* key = GetKey(i); Map* target = GetTarget(i); PropertyKind kind = kData; PropertyAttributes attributes = NONE; if (!TransitionsAccessor::IsSpecialTransition(key)) { PropertyDetails details = TransitionsAccessor::GetTargetDetails(key, target); kind = details.kind(); attributes = details.attributes(); } int j; for (j = i - 1; j >= 0; j--) { Name* temp_key = GetKey(j); Map* temp_target = GetTarget(j); PropertyKind temp_kind = kData; PropertyAttributes temp_attributes = NONE; if (!TransitionsAccessor::IsSpecialTransition(temp_key)) { PropertyDetails details = TransitionsAccessor::GetTargetDetails(temp_key, temp_target); temp_kind = details.kind(); temp_attributes = details.attributes(); } int cmp = CompareKeys(temp_key, temp_key->Hash(), temp_kind, temp_attributes, key, key->Hash(), kind, attributes); if (cmp > 0) { SetKey(j + 1, temp_key); SetTarget(j + 1, temp_target); } else { break; } } SetKey(j + 1, key); SetTarget(j + 1, target); } DCHECK(IsSortedNoDuplicates()); } } // namespace internal } // namespace v8