a3ef2489f2
Many handlers are not used again, so we can improve the cache hit rate by caching fewer handlers. Specifically, in this CL, when a StoreIC miss causes a new map transition to be created, then the handler is not cached right away yet (it will be cached next time, when the transition exists already). Also, fix an embarrassing bug where growing a TransitionArray dropped cached handlers. That further improves the cache hit rate. ;-) Bug: chromium:752867, chromium:753819 Change-Id: Id8db5ca1e780a5fe8fc61db7f20996e61c65a90e Reviewed-on: https://chromium-review.googlesource.com/619851 Reviewed-by: Camillo Bruni <cbruni@chromium.org> Commit-Queue: Jakob Kummerow <jkummerow@chromium.org> Cr-Commit-Position: refs/heads/master@{#47433}
721 lines
26 KiB
C++
721 lines
26 KiB
C++
// 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<kWeakCell>()->value());
|
|
case kTuple3Handler:
|
|
return Map::cast(GetTargetCell<kTuple3Handler>()->value());
|
|
case kFixedArrayHandler:
|
|
return Map::cast(GetTargetCell<kFixedArrayHandler>()->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> name, Handle<Map> 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<TransitionArray> 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<TransitionArray> 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<TransitionArray> 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<Map>* 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<kWeakCell>();
|
|
break;
|
|
case kTuple3Handler:
|
|
cell = GetTargetCell<kTuple3Handler>();
|
|
break;
|
|
case kFixedArrayHandler:
|
|
cell = GetTargetCell<kFixedArrayHandler>();
|
|
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<Map> TransitionsAccessor::FindTransitionToField(Handle<Name> name) {
|
|
DCHECK(name->IsUniqueName());
|
|
DisallowHeapAllocation no_gc;
|
|
Map* target = SearchTransition(*name, kData, NONE);
|
|
if (target == NULL) return Handle<Map>::null();
|
|
PropertyDetails details = target->GetLastDescriptorDetails();
|
|
DCHECK_EQ(NONE, details.attributes());
|
|
if (details.location() != kField) return Handle<Map>::null();
|
|
DCHECK_EQ(kData, details.kind());
|
|
return Handle<Map>(target);
|
|
}
|
|
|
|
Handle<String> TransitionsAccessor::ExpectedTransitionKey() {
|
|
DisallowHeapAllocation no_gc;
|
|
WeakCell* cell = nullptr;
|
|
switch (encoding()) {
|
|
case kPrototypeInfo:
|
|
case kUninitialized:
|
|
case kFullTransitionArray:
|
|
return Handle<String>::null();
|
|
case kWeakCell:
|
|
cell = GetTargetCell<kWeakCell>();
|
|
break;
|
|
case kTuple3Handler:
|
|
cell = GetTargetCell<kTuple3Handler>();
|
|
break;
|
|
case kFixedArrayHandler:
|
|
cell = GetTargetCell<kFixedArrayHandler>();
|
|
break;
|
|
}
|
|
DCHECK(!cell->cleared());
|
|
Map* target = Map::cast(cell->value());
|
|
PropertyDetails details = GetSimpleTargetDetails(target);
|
|
if (details.location() != kField) return Handle<String>::null();
|
|
DCHECK_EQ(kData, details.kind());
|
|
if (details.attributes() != NONE) return Handle<String>::null();
|
|
Name* name = GetSimpleTransitionKey(target);
|
|
if (!name->IsString()) return Handle<String>::null();
|
|
return handle(String::cast(name));
|
|
}
|
|
|
|
Handle<Map> 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<FixedArray> TransitionArray::GrowPrototypeTransitionArray(
|
|
Handle<FixedArray> 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<Object> prototype,
|
|
Handle<Map> 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<FixedArray> 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<WeakCell> target_cell = Map::WeakCellForMap(target_map);
|
|
cache->set(entry, *target_cell);
|
|
TransitionArray::SetNumberOfPrototypeTransitions(*cache, last + 1);
|
|
}
|
|
|
|
Handle<Map> TransitionsAccessor::GetPrototypeTransition(
|
|
Handle<Object> 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<Map>();
|
|
}
|
|
|
|
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> TransitionArray::Allocate(Isolate* isolate,
|
|
int number_of_transitions,
|
|
int slack) {
|
|
Handle<FixedArray> array = isolate->factory()->NewTransitionArray(
|
|
LengthFor(number_of_transitions + slack));
|
|
array->set(kPrototypeTransitionsIndex, Smi::kZero);
|
|
array->set(kTransitionLengthIndex, Smi::FromInt(number_of_transitions));
|
|
return Handle<TransitionArray>::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<FixedArray> proto_transitions) {
|
|
EnsureHasFullTransitionArray();
|
|
transitions()->SetPrototypeTransitions(*proto_transitions);
|
|
}
|
|
|
|
void TransitionsAccessor::EnsureHasFullTransitionArray() {
|
|
if (encoding() == kFullTransitionArray) return;
|
|
int nof = encoding() == kUninitialized ? 0 : 1;
|
|
Handle<TransitionArray> 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<kWeakCell>()->value());
|
|
break;
|
|
case kTuple3Handler:
|
|
simple_target = Map::cast(GetTargetCell<kTuple3Handler>()->value());
|
|
break;
|
|
case kFixedArrayHandler:
|
|
simple_target = Map::cast(GetTargetCell<kFixedArrayHandler>()->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
|