7a3631e7e1
Currently we have V8 RuntimeCallStats that is independently from tracing when running d8 with flag --runtime_call_stats. This patch embeds V8 runtime call stats into tracing, by having a global table of runtime call counters each isolate, resetting the table each time we enter a top level trace event, and dumping the table for each top level trace event. This will make trace file more compat, as well as enable runtime call stats in tracing system. This patch adds ~5% overhead to V8 when the category is enabled, we measure the overhead by running a script when category is enabled. BUG=v8:5089 Committed: https://crrev.com/d014866173eaa2b548c566217b2c94b1d49385fa Committed: https://crrev.com/1ca3b73bba4a7253ca8eeef39321d70e7d414331 Committed: https://crrev.com/3f936a5b17754783e92d2146eaf66c88a78ee45b Review-Url: https://codereview.chromium.org/2187693002 Cr-Original-Original-Original-Commit-Position: refs/heads/master@{#38270} Cr-Original-Original-Commit-Position: refs/heads/master@{#38314} Cr-Original-Commit-Position: refs/heads/master@{#38403} Cr-Commit-Position: refs/heads/master@{#38510}
846 lines
30 KiB
C++
846 lines
30 KiB
C++
// Copyright 2014 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/lookup.h"
|
|
|
|
#include "src/bootstrapper.h"
|
|
#include "src/deoptimizer.h"
|
|
#include "src/elements.h"
|
|
#include "src/field-type.h"
|
|
#include "src/isolate-inl.h"
|
|
|
|
namespace v8 {
|
|
namespace internal {
|
|
|
|
|
|
// static
|
|
LookupIterator LookupIterator::PropertyOrElement(Isolate* isolate,
|
|
Handle<Object> receiver,
|
|
Handle<Object> key,
|
|
bool* success,
|
|
Configuration configuration) {
|
|
uint32_t index = 0;
|
|
if (key->ToArrayIndex(&index)) {
|
|
*success = true;
|
|
return LookupIterator(isolate, receiver, index, configuration);
|
|
}
|
|
|
|
Handle<Name> name;
|
|
*success = Object::ToName(isolate, key).ToHandle(&name);
|
|
if (!*success) {
|
|
DCHECK(isolate->has_pending_exception());
|
|
// Return an unusable dummy.
|
|
return LookupIterator(receiver, isolate->factory()->empty_string());
|
|
}
|
|
|
|
if (name->AsArrayIndex(&index)) {
|
|
LookupIterator it(isolate, receiver, index, configuration);
|
|
// Here we try to avoid having to rebuild the string later
|
|
// by storing it on the indexed LookupIterator.
|
|
it.name_ = name;
|
|
return it;
|
|
}
|
|
|
|
return LookupIterator(receiver, name, configuration);
|
|
}
|
|
|
|
template <bool is_element>
|
|
void LookupIterator::Start() {
|
|
DisallowHeapAllocation no_gc;
|
|
|
|
has_property_ = false;
|
|
state_ = NOT_FOUND;
|
|
holder_ = initial_holder_;
|
|
|
|
JSReceiver* holder = *holder_;
|
|
Map* map = holder->map();
|
|
|
|
state_ = LookupInHolder<is_element>(map, holder);
|
|
if (IsFound()) return;
|
|
|
|
NextInternal<is_element>(map, holder);
|
|
}
|
|
|
|
template void LookupIterator::Start<true>();
|
|
template void LookupIterator::Start<false>();
|
|
|
|
void LookupIterator::Next() {
|
|
DCHECK_NE(JSPROXY, state_);
|
|
DCHECK_NE(TRANSITION, state_);
|
|
DisallowHeapAllocation no_gc;
|
|
has_property_ = false;
|
|
|
|
JSReceiver* holder = *holder_;
|
|
Map* map = holder->map();
|
|
|
|
if (map->instance_type() <= LAST_SPECIAL_RECEIVER_TYPE) {
|
|
state_ = IsElement() ? LookupInSpecialHolder<true>(map, holder)
|
|
: LookupInSpecialHolder<false>(map, holder);
|
|
if (IsFound()) return;
|
|
}
|
|
|
|
IsElement() ? NextInternal<true>(map, holder)
|
|
: NextInternal<false>(map, holder);
|
|
}
|
|
|
|
template <bool is_element>
|
|
void LookupIterator::NextInternal(Map* map, JSReceiver* holder) {
|
|
do {
|
|
JSReceiver* maybe_holder = NextHolder(map);
|
|
if (maybe_holder == nullptr) {
|
|
if (interceptor_state_ == InterceptorState::kSkipNonMasking) {
|
|
RestartLookupForNonMaskingInterceptors<is_element>();
|
|
return;
|
|
}
|
|
state_ = NOT_FOUND;
|
|
if (holder != *holder_) holder_ = handle(holder, isolate_);
|
|
return;
|
|
}
|
|
holder = maybe_holder;
|
|
map = holder->map();
|
|
state_ = LookupInHolder<is_element>(map, holder);
|
|
} while (!IsFound());
|
|
|
|
holder_ = handle(holder, isolate_);
|
|
}
|
|
|
|
template <bool is_element>
|
|
void LookupIterator::RestartInternal(InterceptorState interceptor_state) {
|
|
interceptor_state_ = interceptor_state;
|
|
property_details_ = PropertyDetails::Empty();
|
|
number_ = DescriptorArray::kNotFound;
|
|
Start<is_element>();
|
|
}
|
|
|
|
template void LookupIterator::RestartInternal<true>(InterceptorState);
|
|
template void LookupIterator::RestartInternal<false>(InterceptorState);
|
|
|
|
// static
|
|
Handle<JSReceiver> LookupIterator::GetRootForNonJSReceiver(
|
|
Isolate* isolate, Handle<Object> receiver, uint32_t index) {
|
|
// Strings are the only objects with properties (only elements) directly on
|
|
// the wrapper. Hence we can skip generating the wrapper for all other cases.
|
|
if (index != kMaxUInt32 && receiver->IsString() &&
|
|
index < static_cast<uint32_t>(String::cast(*receiver)->length())) {
|
|
// TODO(verwaest): Speed this up. Perhaps use a cached wrapper on the native
|
|
// context, ensuring that we don't leak it into JS?
|
|
Handle<JSFunction> constructor = isolate->string_function();
|
|
Handle<JSObject> result = isolate->factory()->NewJSObject(constructor);
|
|
Handle<JSValue>::cast(result)->set_value(*receiver);
|
|
return result;
|
|
}
|
|
auto root = handle(receiver->GetRootMap(isolate)->prototype(), isolate);
|
|
if (root->IsNull(isolate)) {
|
|
unsigned int magic = 0xbbbbbbbb;
|
|
isolate->PushStackTraceAndDie(magic, *receiver, NULL, magic);
|
|
}
|
|
return Handle<JSReceiver>::cast(root);
|
|
}
|
|
|
|
|
|
Handle<Map> LookupIterator::GetReceiverMap() const {
|
|
if (receiver_->IsNumber()) return factory()->heap_number_map();
|
|
return handle(Handle<HeapObject>::cast(receiver_)->map(), isolate_);
|
|
}
|
|
|
|
bool LookupIterator::HasAccess() const {
|
|
DCHECK_EQ(ACCESS_CHECK, state_);
|
|
return isolate_->MayAccess(handle(isolate_->context()),
|
|
GetHolder<JSObject>());
|
|
}
|
|
|
|
template <bool is_element>
|
|
void LookupIterator::ReloadPropertyInformation() {
|
|
state_ = BEFORE_PROPERTY;
|
|
interceptor_state_ = InterceptorState::kUninitialized;
|
|
state_ = LookupInHolder<is_element>(holder_->map(), *holder_);
|
|
DCHECK(IsFound() || !holder_->HasFastProperties());
|
|
}
|
|
|
|
void LookupIterator::InternalUpdateProtector() {
|
|
if (isolate_->bootstrapper()->IsActive()) return;
|
|
|
|
if (*name_ == heap()->constructor_string()) {
|
|
if (!isolate_->IsArraySpeciesLookupChainIntact()) return;
|
|
// Setting the constructor property could change an instance's @@species
|
|
if (holder_->IsJSArray()) {
|
|
isolate_->CountUsage(
|
|
v8::Isolate::UseCounterFeature::kArrayInstanceConstructorModified);
|
|
isolate_->InvalidateArraySpeciesProtector();
|
|
} else if (holder_->map()->is_prototype_map()) {
|
|
DisallowHeapAllocation no_gc;
|
|
// Setting the constructor of Array.prototype of any realm also needs
|
|
// to invalidate the species protector
|
|
if (isolate_->IsInAnyContext(*holder_,
|
|
Context::INITIAL_ARRAY_PROTOTYPE_INDEX)) {
|
|
isolate_->CountUsage(v8::Isolate::UseCounterFeature::
|
|
kArrayPrototypeConstructorModified);
|
|
isolate_->InvalidateArraySpeciesProtector();
|
|
}
|
|
}
|
|
} else if (*name_ == heap()->species_symbol()) {
|
|
if (!isolate_->IsArraySpeciesLookupChainIntact()) return;
|
|
// Setting the Symbol.species property of any Array constructor invalidates
|
|
// the species protector
|
|
if (isolate_->IsInAnyContext(*holder_, Context::ARRAY_FUNCTION_INDEX)) {
|
|
isolate_->CountUsage(
|
|
v8::Isolate::UseCounterFeature::kArraySpeciesModified);
|
|
isolate_->InvalidateArraySpeciesProtector();
|
|
}
|
|
} else if (*name_ == heap()->is_concat_spreadable_symbol()) {
|
|
if (!isolate_->IsIsConcatSpreadableLookupChainIntact()) return;
|
|
isolate_->InvalidateIsConcatSpreadableProtector();
|
|
} else if (*name_ == heap()->has_instance_symbol()) {
|
|
if (!isolate_->IsHasInstanceLookupChainIntact()) return;
|
|
isolate_->InvalidateHasInstanceProtector();
|
|
}
|
|
}
|
|
|
|
void LookupIterator::PrepareForDataProperty(Handle<Object> value) {
|
|
DCHECK(state_ == DATA || state_ == ACCESSOR);
|
|
DCHECK(HolderIsReceiverOrHiddenPrototype());
|
|
|
|
Handle<JSObject> holder = GetHolder<JSObject>();
|
|
|
|
if (IsElement()) {
|
|
ElementsKind kind = holder->GetElementsKind();
|
|
ElementsKind to = value->OptimalElementsKind();
|
|
if (IsHoleyElementsKind(kind)) to = GetHoleyElementsKind(to);
|
|
to = GetMoreGeneralElementsKind(kind, to);
|
|
|
|
if (kind != to) {
|
|
JSObject::TransitionElementsKind(holder, to);
|
|
}
|
|
|
|
// Copy the backing store if it is copy-on-write.
|
|
if (IsFastSmiOrObjectElementsKind(to)) {
|
|
JSObject::EnsureWritableFastElements(holder);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (holder->IsJSGlobalObject()) {
|
|
Handle<GlobalDictionary> dictionary(holder->global_dictionary());
|
|
Handle<PropertyCell> cell(
|
|
PropertyCell::cast(dictionary->ValueAt(dictionary_entry())));
|
|
DCHECK(!cell->IsTheHole(isolate_));
|
|
property_details_ = cell->property_details();
|
|
PropertyCell::PrepareForValue(dictionary, dictionary_entry(), value,
|
|
property_details_);
|
|
return;
|
|
}
|
|
if (!holder->HasFastProperties()) return;
|
|
|
|
Handle<Map> old_map(holder->map(), isolate_);
|
|
Handle<Map> new_map =
|
|
Map::PrepareForDataProperty(old_map, descriptor_number(), value);
|
|
|
|
if (old_map.is_identical_to(new_map)) {
|
|
// Update the property details if the representation was None.
|
|
if (representation().IsNone()) {
|
|
property_details_ =
|
|
new_map->instance_descriptors()->GetDetails(descriptor_number());
|
|
}
|
|
return;
|
|
}
|
|
|
|
JSObject::MigrateToMap(holder, new_map);
|
|
ReloadPropertyInformation<false>();
|
|
}
|
|
|
|
|
|
void LookupIterator::ReconfigureDataProperty(Handle<Object> value,
|
|
PropertyAttributes attributes) {
|
|
DCHECK(state_ == DATA || state_ == ACCESSOR);
|
|
DCHECK(HolderIsReceiverOrHiddenPrototype());
|
|
Handle<JSObject> holder = GetHolder<JSObject>();
|
|
if (IsElement()) {
|
|
DCHECK(!holder->HasFixedTypedArrayElements());
|
|
DCHECK(attributes != NONE || !holder->HasFastElements());
|
|
Handle<FixedArrayBase> elements(holder->elements());
|
|
holder->GetElementsAccessor()->Reconfigure(holder, elements, number_, value,
|
|
attributes);
|
|
ReloadPropertyInformation<true>();
|
|
} else if (holder->HasFastProperties()) {
|
|
Handle<Map> old_map(holder->map(), isolate_);
|
|
Handle<Map> new_map = Map::ReconfigureExistingProperty(
|
|
old_map, descriptor_number(), i::kData, attributes);
|
|
new_map = Map::PrepareForDataProperty(new_map, descriptor_number(), value);
|
|
JSObject::MigrateToMap(holder, new_map);
|
|
ReloadPropertyInformation<false>();
|
|
} else {
|
|
PropertyDetails details(attributes, v8::internal::DATA, 0,
|
|
PropertyCellType::kMutable);
|
|
if (holder->IsJSGlobalObject()) {
|
|
Handle<GlobalDictionary> dictionary(holder->global_dictionary());
|
|
|
|
Handle<PropertyCell> cell = PropertyCell::PrepareForValue(
|
|
dictionary, dictionary_entry(), value, details);
|
|
cell->set_value(*value);
|
|
property_details_ = cell->property_details();
|
|
} else {
|
|
Handle<NameDictionary> dictionary(holder->property_dictionary());
|
|
PropertyDetails original_details =
|
|
dictionary->DetailsAt(dictionary_entry());
|
|
int enumeration_index = original_details.dictionary_index();
|
|
DCHECK(enumeration_index > 0);
|
|
details = details.set_index(enumeration_index);
|
|
dictionary->SetEntry(dictionary_entry(), name(), value, details);
|
|
property_details_ = details;
|
|
}
|
|
state_ = DATA;
|
|
}
|
|
|
|
WriteDataValue(value);
|
|
|
|
#if VERIFY_HEAP
|
|
if (FLAG_verify_heap) {
|
|
holder->JSObjectVerify();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Can only be called when the receiver is a JSObject. JSProxy has to be handled
|
|
// via a trap. Adding properties to primitive values is not observable.
|
|
void LookupIterator::PrepareTransitionToDataProperty(
|
|
Handle<JSObject> receiver, Handle<Object> value,
|
|
PropertyAttributes attributes, Object::StoreFromKeyed store_mode) {
|
|
DCHECK(receiver.is_identical_to(GetStoreTarget()));
|
|
if (state_ == TRANSITION) return;
|
|
DCHECK(state_ != LookupIterator::ACCESSOR ||
|
|
(GetAccessors()->IsAccessorInfo() &&
|
|
AccessorInfo::cast(*GetAccessors())->is_special_data_property()));
|
|
DCHECK_NE(INTEGER_INDEXED_EXOTIC, state_);
|
|
DCHECK(state_ == NOT_FOUND || !HolderIsReceiverOrHiddenPrototype());
|
|
|
|
Handle<Map> map(receiver->map(), isolate_);
|
|
|
|
// Dictionary maps can always have additional data properties.
|
|
if (map->is_dictionary_map()) {
|
|
state_ = TRANSITION;
|
|
if (map->IsJSGlobalObjectMap()) {
|
|
// Install a property cell.
|
|
Handle<JSGlobalObject> global = Handle<JSGlobalObject>::cast(receiver);
|
|
int entry;
|
|
Handle<PropertyCell> cell = JSGlobalObject::EnsureEmptyPropertyCell(
|
|
global, name(), PropertyCellType::kUninitialized, &entry);
|
|
Handle<GlobalDictionary> dictionary(global->global_dictionary(),
|
|
isolate_);
|
|
DCHECK(cell->value()->IsTheHole(isolate_));
|
|
DCHECK(!value->IsTheHole(isolate_));
|
|
transition_ = cell;
|
|
// Assign an enumeration index to the property and update
|
|
// SetNextEnumerationIndex.
|
|
int index = dictionary->NextEnumerationIndex();
|
|
dictionary->SetNextEnumerationIndex(index + 1);
|
|
property_details_ = PropertyDetails(attributes, i::DATA, index,
|
|
PropertyCellType::kUninitialized);
|
|
PropertyCellType new_type =
|
|
PropertyCell::UpdatedType(cell, value, property_details_);
|
|
property_details_ = property_details_.set_cell_type(new_type);
|
|
cell->set_property_details(property_details_);
|
|
number_ = entry;
|
|
has_property_ = true;
|
|
} else {
|
|
// Don't set enumeration index (it will be set during value store).
|
|
property_details_ =
|
|
PropertyDetails(attributes, i::DATA, 0, PropertyCellType::kNoCell);
|
|
transition_ = map;
|
|
}
|
|
return;
|
|
}
|
|
|
|
Handle<Map> transition =
|
|
Map::TransitionToDataProperty(map, name_, value, attributes, store_mode);
|
|
state_ = TRANSITION;
|
|
transition_ = transition;
|
|
|
|
if (transition->is_dictionary_map()) {
|
|
// Don't set enumeration index (it will be set during value store).
|
|
property_details_ =
|
|
PropertyDetails(attributes, i::DATA, 0, PropertyCellType::kNoCell);
|
|
} else {
|
|
property_details_ = transition->GetLastDescriptorDetails();
|
|
has_property_ = true;
|
|
}
|
|
}
|
|
|
|
void LookupIterator::ApplyTransitionToDataProperty(Handle<JSObject> receiver) {
|
|
DCHECK_EQ(TRANSITION, state_);
|
|
|
|
DCHECK(receiver.is_identical_to(GetStoreTarget()));
|
|
holder_ = receiver;
|
|
if (receiver->IsJSGlobalObject()) {
|
|
state_ = DATA;
|
|
return;
|
|
}
|
|
Handle<Map> transition = transition_map();
|
|
bool simple_transition = transition->GetBackPointer() == receiver->map();
|
|
JSObject::MigrateToMap(receiver, transition);
|
|
|
|
if (simple_transition) {
|
|
int number = transition->LastAdded();
|
|
number_ = static_cast<uint32_t>(number);
|
|
property_details_ = transition->GetLastDescriptorDetails();
|
|
state_ = DATA;
|
|
} else if (receiver->map()->is_dictionary_map()) {
|
|
Handle<NameDictionary> dictionary(receiver->property_dictionary(),
|
|
isolate_);
|
|
int entry;
|
|
dictionary = NameDictionary::Add(dictionary, name(),
|
|
isolate_->factory()->uninitialized_value(),
|
|
property_details_, &entry);
|
|
receiver->set_properties(*dictionary);
|
|
// Reload details containing proper enumeration index value.
|
|
property_details_ = dictionary->DetailsAt(entry);
|
|
number_ = entry;
|
|
has_property_ = true;
|
|
state_ = DATA;
|
|
|
|
} else {
|
|
ReloadPropertyInformation<false>();
|
|
}
|
|
}
|
|
|
|
|
|
void LookupIterator::Delete() {
|
|
Handle<JSReceiver> holder = Handle<JSReceiver>::cast(holder_);
|
|
if (IsElement()) {
|
|
Handle<JSObject> object = Handle<JSObject>::cast(holder);
|
|
ElementsAccessor* accessor = object->GetElementsAccessor();
|
|
accessor->Delete(object, number_);
|
|
} else {
|
|
bool is_prototype_map = holder->map()->is_prototype_map();
|
|
RuntimeCallTimerScope stats_scope(
|
|
isolate_, is_prototype_map
|
|
? &RuntimeCallStats::PrototypeObject_DeleteProperty
|
|
: &RuntimeCallStats::Object_DeleteProperty);
|
|
TRACE_EVENT_RUNTIME_CALL_STATS_TRACING_SCOPED(
|
|
isolate_,
|
|
(is_prototype_map
|
|
? &tracing::TraceEventStatsTable::PrototypeObject_DeleteProperty
|
|
: &tracing::TraceEventStatsTable::Object_DeleteProperty));
|
|
|
|
PropertyNormalizationMode mode =
|
|
is_prototype_map ? KEEP_INOBJECT_PROPERTIES : CLEAR_INOBJECT_PROPERTIES;
|
|
|
|
if (holder->HasFastProperties()) {
|
|
JSObject::NormalizeProperties(Handle<JSObject>::cast(holder), mode, 0,
|
|
"DeletingProperty");
|
|
ReloadPropertyInformation<false>();
|
|
}
|
|
// TODO(verwaest): Get rid of the name_ argument.
|
|
JSReceiver::DeleteNormalizedProperty(holder, name_, number_);
|
|
if (holder->IsJSObject()) {
|
|
JSObject::ReoptimizeIfPrototype(Handle<JSObject>::cast(holder));
|
|
}
|
|
}
|
|
state_ = NOT_FOUND;
|
|
}
|
|
|
|
void LookupIterator::TransitionToAccessorProperty(
|
|
Handle<Object> getter, Handle<Object> setter,
|
|
PropertyAttributes attributes) {
|
|
DCHECK(!getter->IsNull(isolate_) || !setter->IsNull(isolate_));
|
|
// Can only be called when the receiver is a JSObject. JSProxy has to be
|
|
// handled via a trap. Adding properties to primitive values is not
|
|
// observable.
|
|
Handle<JSObject> receiver = GetStoreTarget();
|
|
|
|
if (!IsElement() && !receiver->map()->is_dictionary_map()) {
|
|
Handle<Map> old_map(receiver->map(), isolate_);
|
|
|
|
if (!holder_.is_identical_to(receiver)) {
|
|
holder_ = receiver;
|
|
state_ = NOT_FOUND;
|
|
} else if (state_ == INTERCEPTOR) {
|
|
LookupInRegularHolder<false>(*old_map, *holder_);
|
|
}
|
|
int descriptor =
|
|
IsFound() ? static_cast<int>(number_) : DescriptorArray::kNotFound;
|
|
|
|
Handle<Map> new_map = Map::TransitionToAccessorProperty(
|
|
isolate_, old_map, name_, descriptor, getter, setter, attributes);
|
|
bool simple_transition = new_map->GetBackPointer() == receiver->map();
|
|
JSObject::MigrateToMap(receiver, new_map);
|
|
|
|
if (simple_transition) {
|
|
int number = new_map->LastAdded();
|
|
number_ = static_cast<uint32_t>(number);
|
|
property_details_ = new_map->GetLastDescriptorDetails();
|
|
state_ = ACCESSOR;
|
|
return;
|
|
}
|
|
|
|
ReloadPropertyInformation<false>();
|
|
if (!new_map->is_dictionary_map()) return;
|
|
}
|
|
|
|
Handle<AccessorPair> pair;
|
|
if (state() == ACCESSOR && GetAccessors()->IsAccessorPair()) {
|
|
pair = Handle<AccessorPair>::cast(GetAccessors());
|
|
// If the component and attributes are identical, nothing has to be done.
|
|
if (pair->Equals(*getter, *setter)) {
|
|
if (property_details().attributes() == attributes) {
|
|
if (!IsElement()) JSObject::ReoptimizeIfPrototype(receiver);
|
|
return;
|
|
}
|
|
} else {
|
|
pair = AccessorPair::Copy(pair);
|
|
pair->SetComponents(*getter, *setter);
|
|
}
|
|
} else {
|
|
pair = factory()->NewAccessorPair();
|
|
pair->SetComponents(*getter, *setter);
|
|
}
|
|
|
|
TransitionToAccessorPair(pair, attributes);
|
|
|
|
#if VERIFY_HEAP
|
|
if (FLAG_verify_heap) {
|
|
receiver->JSObjectVerify();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
void LookupIterator::TransitionToAccessorPair(Handle<Object> pair,
|
|
PropertyAttributes attributes) {
|
|
Handle<JSObject> receiver = GetStoreTarget();
|
|
holder_ = receiver;
|
|
|
|
PropertyDetails details(attributes, ACCESSOR_CONSTANT, 0,
|
|
PropertyCellType::kMutable);
|
|
|
|
if (IsElement()) {
|
|
// TODO(verwaest): Move code into the element accessor.
|
|
Handle<SeededNumberDictionary> dictionary =
|
|
JSObject::NormalizeElements(receiver);
|
|
|
|
// We unconditionally pass used_as_prototype=false here because the call
|
|
// to RequireSlowElements takes care of the required IC clearing and
|
|
// we don't want to walk the heap twice.
|
|
dictionary =
|
|
SeededNumberDictionary::Set(dictionary, index_, pair, details, false);
|
|
receiver->RequireSlowElements(*dictionary);
|
|
|
|
if (receiver->HasSlowArgumentsElements()) {
|
|
FixedArray* parameter_map = FixedArray::cast(receiver->elements());
|
|
uint32_t length = parameter_map->length() - 2;
|
|
if (number_ < length) {
|
|
parameter_map->set(number_ + 2, heap()->the_hole_value());
|
|
}
|
|
FixedArray::cast(receiver->elements())->set(1, *dictionary);
|
|
} else {
|
|
receiver->set_elements(*dictionary);
|
|
}
|
|
|
|
ReloadPropertyInformation<true>();
|
|
} else {
|
|
PropertyNormalizationMode mode = receiver->map()->is_prototype_map()
|
|
? KEEP_INOBJECT_PROPERTIES
|
|
: CLEAR_INOBJECT_PROPERTIES;
|
|
// Normalize object to make this operation simple.
|
|
JSObject::NormalizeProperties(receiver, mode, 0,
|
|
"TransitionToAccessorPair");
|
|
|
|
JSObject::SetNormalizedProperty(receiver, name_, pair, details);
|
|
JSObject::ReoptimizeIfPrototype(receiver);
|
|
|
|
ReloadPropertyInformation<false>();
|
|
}
|
|
}
|
|
|
|
|
|
bool LookupIterator::HolderIsReceiverOrHiddenPrototype() const {
|
|
DCHECK(has_property_ || state_ == INTERCEPTOR || state_ == JSPROXY);
|
|
// Optimization that only works if configuration_ is not mutable.
|
|
if (!check_prototype_chain()) return true;
|
|
DisallowHeapAllocation no_gc;
|
|
if (*receiver_ == *holder_) return true;
|
|
if (!receiver_->IsJSReceiver()) return false;
|
|
JSReceiver* current = JSReceiver::cast(*receiver_);
|
|
JSReceiver* object = *holder_;
|
|
if (!current->map()->has_hidden_prototype()) return false;
|
|
// JSProxy do not occur as hidden prototypes.
|
|
if (object->IsJSProxy()) return false;
|
|
PrototypeIterator iter(isolate(), current, kStartAtPrototype,
|
|
PrototypeIterator::END_AT_NON_HIDDEN);
|
|
while (!iter.IsAtEnd()) {
|
|
if (iter.GetCurrent<JSReceiver>() == object) return true;
|
|
iter.Advance();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
Handle<Object> LookupIterator::FetchValue() const {
|
|
Object* result = NULL;
|
|
if (IsElement()) {
|
|
Handle<JSObject> holder = GetHolder<JSObject>();
|
|
ElementsAccessor* accessor = holder->GetElementsAccessor();
|
|
return accessor->Get(holder, number_);
|
|
} else if (holder_->IsJSGlobalObject()) {
|
|
Handle<JSObject> holder = GetHolder<JSObject>();
|
|
result = holder->global_dictionary()->ValueAt(number_);
|
|
DCHECK(result->IsPropertyCell());
|
|
result = PropertyCell::cast(result)->value();
|
|
} else if (!holder_->HasFastProperties()) {
|
|
result = holder_->property_dictionary()->ValueAt(number_);
|
|
} else if (property_details_.type() == v8::internal::DATA) {
|
|
Handle<JSObject> holder = GetHolder<JSObject>();
|
|
FieldIndex field_index = FieldIndex::ForDescriptor(holder->map(), number_);
|
|
return JSObject::FastPropertyAt(holder, property_details_.representation(),
|
|
field_index);
|
|
} else {
|
|
result = holder_->map()->instance_descriptors()->GetValue(number_);
|
|
}
|
|
return handle(result, isolate_);
|
|
}
|
|
|
|
|
|
int LookupIterator::GetAccessorIndex() const {
|
|
DCHECK(has_property_);
|
|
DCHECK(holder_->HasFastProperties());
|
|
DCHECK_EQ(v8::internal::ACCESSOR_CONSTANT, property_details_.type());
|
|
return descriptor_number();
|
|
}
|
|
|
|
|
|
int LookupIterator::GetConstantIndex() const {
|
|
DCHECK(has_property_);
|
|
DCHECK(holder_->HasFastProperties());
|
|
DCHECK_EQ(v8::internal::DATA_CONSTANT, property_details_.type());
|
|
DCHECK(!IsElement());
|
|
return descriptor_number();
|
|
}
|
|
|
|
|
|
FieldIndex LookupIterator::GetFieldIndex() const {
|
|
DCHECK(has_property_);
|
|
DCHECK(holder_->HasFastProperties());
|
|
DCHECK_EQ(v8::internal::DATA, property_details_.type());
|
|
DCHECK(!IsElement());
|
|
Map* holder_map = holder_->map();
|
|
int index =
|
|
holder_map->instance_descriptors()->GetFieldIndex(descriptor_number());
|
|
bool is_double = representation().IsDouble();
|
|
return FieldIndex::ForPropertyIndex(holder_map, index, is_double);
|
|
}
|
|
|
|
Handle<FieldType> LookupIterator::GetFieldType() const {
|
|
DCHECK(has_property_);
|
|
DCHECK(holder_->HasFastProperties());
|
|
DCHECK_EQ(v8::internal::DATA, property_details_.type());
|
|
return handle(
|
|
holder_->map()->instance_descriptors()->GetFieldType(descriptor_number()),
|
|
isolate_);
|
|
}
|
|
|
|
|
|
Handle<PropertyCell> LookupIterator::GetPropertyCell() const {
|
|
DCHECK(!IsElement());
|
|
Handle<JSGlobalObject> holder = GetHolder<JSGlobalObject>();
|
|
Object* value = holder->global_dictionary()->ValueAt(dictionary_entry());
|
|
DCHECK(value->IsPropertyCell());
|
|
return handle(PropertyCell::cast(value), isolate_);
|
|
}
|
|
|
|
|
|
Handle<Object> LookupIterator::GetAccessors() const {
|
|
DCHECK_EQ(ACCESSOR, state_);
|
|
return FetchValue();
|
|
}
|
|
|
|
|
|
Handle<Object> LookupIterator::GetDataValue() const {
|
|
DCHECK_EQ(DATA, state_);
|
|
Handle<Object> value = FetchValue();
|
|
return value;
|
|
}
|
|
|
|
|
|
void LookupIterator::WriteDataValue(Handle<Object> value) {
|
|
DCHECK_EQ(DATA, state_);
|
|
Handle<JSReceiver> holder = GetHolder<JSReceiver>();
|
|
if (IsElement()) {
|
|
Handle<JSObject> object = Handle<JSObject>::cast(holder);
|
|
ElementsAccessor* accessor = object->GetElementsAccessor();
|
|
accessor->Set(object, number_, *value);
|
|
} else if (holder->HasFastProperties()) {
|
|
if (property_details_.type() == v8::internal::DATA) {
|
|
JSObject::cast(*holder)->WriteToField(descriptor_number(),
|
|
property_details_, *value);
|
|
} else {
|
|
DCHECK_EQ(v8::internal::DATA_CONSTANT, property_details_.type());
|
|
}
|
|
} else if (holder->IsJSGlobalObject()) {
|
|
GlobalDictionary* dictionary = JSObject::cast(*holder)->global_dictionary();
|
|
Object* cell = dictionary->ValueAt(dictionary_entry());
|
|
DCHECK(cell->IsPropertyCell());
|
|
PropertyCell::cast(cell)->set_value(*value);
|
|
} else {
|
|
NameDictionary* dictionary = holder->property_dictionary();
|
|
dictionary->ValueAtPut(dictionary_entry(), *value);
|
|
}
|
|
}
|
|
|
|
template <bool is_element>
|
|
bool LookupIterator::SkipInterceptor(JSObject* holder) {
|
|
auto info = GetInterceptor<is_element>(holder);
|
|
// TODO(dcarney): check for symbol/can_intercept_symbols here as well.
|
|
if (info->non_masking()) {
|
|
switch (interceptor_state_) {
|
|
case InterceptorState::kUninitialized:
|
|
interceptor_state_ = InterceptorState::kSkipNonMasking;
|
|
// Fall through.
|
|
case InterceptorState::kSkipNonMasking:
|
|
return true;
|
|
case InterceptorState::kProcessNonMasking:
|
|
return false;
|
|
}
|
|
}
|
|
return interceptor_state_ == InterceptorState::kProcessNonMasking;
|
|
}
|
|
|
|
JSReceiver* LookupIterator::NextHolder(Map* map) {
|
|
DisallowHeapAllocation no_gc;
|
|
if (map->prototype() == heap()->null_value()) return NULL;
|
|
if (!check_prototype_chain() && !map->has_hidden_prototype()) return NULL;
|
|
return JSReceiver::cast(map->prototype());
|
|
}
|
|
|
|
LookupIterator::State LookupIterator::NotFound(JSReceiver* const holder) const {
|
|
DCHECK(!IsElement());
|
|
if (!holder->IsJSTypedArray() || !name_->IsString()) return NOT_FOUND;
|
|
|
|
Handle<String> name_string = Handle<String>::cast(name_);
|
|
if (name_string->length() == 0) return NOT_FOUND;
|
|
|
|
return IsSpecialIndex(isolate_->unicode_cache(), *name_string)
|
|
? INTEGER_INDEXED_EXOTIC
|
|
: NOT_FOUND;
|
|
}
|
|
|
|
namespace {
|
|
|
|
template <bool is_element>
|
|
bool HasInterceptor(Map* map) {
|
|
return is_element ? map->has_indexed_interceptor()
|
|
: map->has_named_interceptor();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
template <bool is_element>
|
|
LookupIterator::State LookupIterator::LookupInSpecialHolder(
|
|
Map* const map, JSReceiver* const holder) {
|
|
STATIC_ASSERT(INTERCEPTOR == BEFORE_PROPERTY);
|
|
switch (state_) {
|
|
case NOT_FOUND:
|
|
if (map->IsJSProxyMap()) {
|
|
if (is_element || !name_->IsPrivate()) return JSPROXY;
|
|
}
|
|
if (map->is_access_check_needed()) {
|
|
if (is_element || !name_->IsPrivate()) return ACCESS_CHECK;
|
|
}
|
|
// Fall through.
|
|
case ACCESS_CHECK:
|
|
if (check_interceptor() && HasInterceptor<is_element>(map) &&
|
|
!SkipInterceptor<is_element>(JSObject::cast(holder))) {
|
|
if (is_element || !name_->IsPrivate()) return INTERCEPTOR;
|
|
}
|
|
// Fall through.
|
|
case INTERCEPTOR:
|
|
if (!is_element && map->IsJSGlobalObjectMap()) {
|
|
GlobalDictionary* dict = JSObject::cast(holder)->global_dictionary();
|
|
int number = dict->FindEntry(name_);
|
|
if (number == GlobalDictionary::kNotFound) return NOT_FOUND;
|
|
number_ = static_cast<uint32_t>(number);
|
|
DCHECK(dict->ValueAt(number_)->IsPropertyCell());
|
|
PropertyCell* cell = PropertyCell::cast(dict->ValueAt(number_));
|
|
if (cell->value()->IsTheHole(isolate_)) return NOT_FOUND;
|
|
property_details_ = cell->property_details();
|
|
has_property_ = true;
|
|
switch (property_details_.kind()) {
|
|
case v8::internal::kData:
|
|
return DATA;
|
|
case v8::internal::kAccessor:
|
|
return ACCESSOR;
|
|
}
|
|
}
|
|
return LookupInRegularHolder<is_element>(map, holder);
|
|
case ACCESSOR:
|
|
case DATA:
|
|
return NOT_FOUND;
|
|
case INTEGER_INDEXED_EXOTIC:
|
|
case JSPROXY:
|
|
case TRANSITION:
|
|
UNREACHABLE();
|
|
}
|
|
UNREACHABLE();
|
|
return NOT_FOUND;
|
|
}
|
|
|
|
template <bool is_element>
|
|
LookupIterator::State LookupIterator::LookupInRegularHolder(
|
|
Map* const map, JSReceiver* const holder) {
|
|
DisallowHeapAllocation no_gc;
|
|
if (interceptor_state_ == InterceptorState::kProcessNonMasking) {
|
|
return NOT_FOUND;
|
|
}
|
|
|
|
if (is_element) {
|
|
JSObject* js_object = JSObject::cast(holder);
|
|
ElementsAccessor* accessor = js_object->GetElementsAccessor();
|
|
FixedArrayBase* backing_store = js_object->elements();
|
|
number_ = accessor->GetEntryForIndex(js_object, backing_store, index_);
|
|
if (number_ == kMaxUInt32) {
|
|
return holder->IsJSTypedArray() ? INTEGER_INDEXED_EXOTIC : NOT_FOUND;
|
|
}
|
|
property_details_ = accessor->GetDetails(js_object, number_);
|
|
} else if (!map->is_dictionary_map()) {
|
|
DescriptorArray* descriptors = map->instance_descriptors();
|
|
int number = descriptors->SearchWithCache(isolate_, *name_, map);
|
|
if (number == DescriptorArray::kNotFound) return NotFound(holder);
|
|
number_ = static_cast<uint32_t>(number);
|
|
property_details_ = descriptors->GetDetails(number_);
|
|
} else {
|
|
NameDictionary* dict = holder->property_dictionary();
|
|
int number = dict->FindEntry(name_);
|
|
if (number == NameDictionary::kNotFound) return NotFound(holder);
|
|
number_ = static_cast<uint32_t>(number);
|
|
property_details_ = dict->DetailsAt(number_);
|
|
}
|
|
has_property_ = true;
|
|
switch (property_details_.kind()) {
|
|
case v8::internal::kData:
|
|
return DATA;
|
|
case v8::internal::kAccessor:
|
|
return ACCESSOR;
|
|
}
|
|
|
|
UNREACHABLE();
|
|
return state_;
|
|
}
|
|
|
|
Handle<InterceptorInfo> LookupIterator::GetInterceptorForFailedAccessCheck()
|
|
const {
|
|
DCHECK_EQ(ACCESS_CHECK, state_);
|
|
DisallowHeapAllocation no_gc;
|
|
AccessCheckInfo* access_check_info =
|
|
AccessCheckInfo::Get(isolate_, Handle<JSObject>::cast(holder_));
|
|
if (access_check_info) {
|
|
Object* interceptor = IsElement() ? access_check_info->indexed_interceptor()
|
|
: access_check_info->named_interceptor();
|
|
if (interceptor) {
|
|
return handle(InterceptorInfo::cast(interceptor), isolate_);
|
|
}
|
|
}
|
|
return Handle<InterceptorInfo>();
|
|
}
|
|
|
|
} // namespace internal
|
|
} // namespace v8
|