Commit 78c6bbd9 authored by Igor Sheludko's avatar Igor Sheludko Committed by Commit Bot

[ic] Use Map as transition handlers instead of StoreHandler objects.

This eases transition handlers caching and avoids memory overhead of
respective StoreHandler objects. In addition, it allows to use such
transition handlers on runtime side to make Object.assign implementation
a bit faster.

Bug: v8:5988
Change-Id: Iba660a11d4b300cd5f80615fb7e2608e53da8fee
Reviewed-on: https://chromium-review.googlesource.com/931701Reviewed-by: 's avatarToon Verwaest <verwaest@chromium.org>
Commit-Queue: Igor Sheludko <ishell@chromium.org>
Cr-Commit-Position: refs/heads/master@{#52187}
parent 69040ced
......@@ -7,6 +7,7 @@
#include "src/builtins/builtins.h"
#include "src/code-stub-assembler.h"
#include "src/heap/heap-inl.h"
#include "src/ic/accessor-assembler.h"
#include "src/macro-assembler.h"
#include "src/objects/debug-objects.h"
#include "src/objects/shared-function-info.h"
......@@ -480,10 +481,10 @@ TF_BUILTIN(RecordWrite, RecordWriteCodeStubAssembler) {
Return(TrueConstant());
}
class DeletePropertyBaseAssembler : public CodeStubAssembler {
class DeletePropertyBaseAssembler : public AccessorAssembler {
public:
explicit DeletePropertyBaseAssembler(compiler::CodeAssemblerState* state)
: CodeStubAssembler(state) {}
: AccessorAssembler(state) {}
void DeleteDictionaryProperty(Node* receiver, Node* properties, Node* name,
Node* context, Label* dont_delete,
......@@ -570,6 +571,8 @@ TF_BUILTIN(DeleteProperty, DeletePropertyBaseAssembler) {
BIND(&dictionary);
{
InvalidateValidityCellIfPrototype(receiver_map);
Node* properties = LoadSlowProperties(receiver);
DeleteDictionaryProperty(receiver, properties, unique, context,
&dont_delete, &if_notfound);
......
......@@ -127,9 +127,9 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
return ParameterRepresentation(OptimalParameterMode());
}
Node* ParameterToIntPtr(Node* value, ParameterMode mode) {
TNode<IntPtrT> ParameterToIntPtr(Node* value, ParameterMode mode) {
if (mode == SMI_PARAMETERS) value = SmiUntag(value);
return value;
return UncheckedCast<IntPtrT>(value);
}
Node* IntPtrToParameter(SloppyTNode<IntPtrT> value, ParameterMode mode) {
......
This diff is collapsed.
......@@ -101,12 +101,26 @@ class AccessorAssembler : public CodeStubAssembler {
void HandleStoreICHandlerCase(
const StoreICParameters* p, Node* handler, Label* miss, ICMode ic_mode,
ElementSupport support_elements = kOnlyProperties);
void HandleStoreICTransitionMapHandlerCase(const StoreICParameters* p,
Node* transition_map, Label* miss,
bool validate_transition_handler);
void JumpIfDataProperty(Node* details, Label* writable, Label* readonly);
void BranchIfStrictMode(Node* vector, Node* slot, Label* if_strict);
void InvalidateValidityCellIfPrototype(Node* map, Node* bitfield2 = nullptr);
void OverwriteExistingFastDataProperty(Node* object, Node* object_map,
Node* descriptors,
Node* descriptor_name_index,
Node* details, Node* value,
Label* slow,
bool do_transitioning_store);
void CheckFieldType(Node* descriptors, Node* name_index, Node* representation,
Node* value, Label* bailout);
private:
// Stub generation entry points.
......@@ -187,15 +201,11 @@ class AccessorAssembler : public CodeStubAssembler {
void HandleStoreICProtoHandler(const StoreICParameters* p, Node* handler,
Label* miss, ICMode ic_mode,
ElementSupport support_elements);
// If |transition| is nullptr then the normal field store is generated or
// transitioning store otherwise.
void HandleStoreICSmiHandlerCase(Node* handler_word, Node* holder,
Node* value, Node* transition, Label* miss);
// If |transition| is nullptr then the normal field store is generated or
// transitioning store otherwise.
Node* value, Label* miss);
void HandleStoreFieldAndReturn(Node* handler_word, Node* holder,
Representation representation, Node* value,
Node* transition, Label* miss);
Label* miss);
void CheckPrototypeValidityCell(Node* maybe_validity_cell, Label* miss);
void HandleStoreICNativeDataProperty(const StoreICParameters* p, Node* holder,
......@@ -233,15 +243,16 @@ class AccessorAssembler : public CodeStubAssembler {
Node* GetLanguageMode(Node* vector, Node* slot);
Node* PrepareValueForStore(Node* handler_word, Node* holder,
Representation representation, Node* transition,
Node* value, Label* bailout);
Representation representation, Node* value,
Label* bailout);
// Extends properties backing store by JSObject::kFieldsAdded elements.
void ExtendPropertiesBackingStore(Node* object, Node* handler_word);
// Extends properties backing store by JSObject::kFieldsAdded elements,
// returns updated properties backing store.
Node* ExtendPropertiesBackingStore(Node* object, Node* index);
void StoreNamedField(Node* handler_word, Node* object, bool is_inobject,
Representation representation, Node* value,
bool transition_to_field, Label* bailout);
Label* bailout);
void EmitFastElementsBoundsCheck(Node* object, Node* elements,
Node* intptr_index,
......
......@@ -130,8 +130,7 @@ Handle<Smi> StoreHandler::StoreProxy(Isolate* isolate) {
Handle<Smi> StoreHandler::StoreField(Isolate* isolate, Kind kind,
int descriptor, FieldIndex field_index,
Representation representation,
bool extend_storage) {
Representation representation) {
FieldRepresentation field_rep;
switch (representation.kind()) {
case Representation::kSmi:
......@@ -150,13 +149,9 @@ Handle<Smi> StoreHandler::StoreField(Isolate* isolate, Kind kind,
UNREACHABLE();
}
DCHECK(kind == kField || kind == kTransitionToField ||
(kind == kConstField && FLAG_track_constant_fields));
DCHECK_IMPLIES(extend_storage, kind == kTransitionToField);
DCHECK_IMPLIES(field_index.is_inobject(), !extend_storage);
DCHECK(kind == kField || (kind == kConstField && FLAG_track_constant_fields));
int config = KindBits::encode(kind) |
ExtendStorageBits::encode(extend_storage) |
IsInobjectBits::encode(field_index.is_inobject()) |
FieldRepresentationBits::encode(field_rep) |
DescriptorBits::encode(descriptor) |
......@@ -170,24 +165,7 @@ Handle<Smi> StoreHandler::StoreField(Isolate* isolate, int descriptor,
Representation representation) {
DCHECK_IMPLIES(!FLAG_track_constant_fields, constness == kMutable);
Kind kind = constness == kMutable ? kField : kConstField;
return StoreField(isolate, kind, descriptor, field_index, representation,
false);
}
Handle<Smi> StoreHandler::TransitionToField(Isolate* isolate, int descriptor,
FieldIndex field_index,
Representation representation,
bool extend_storage) {
return StoreField(isolate, kTransitionToField, descriptor, field_index,
representation, extend_storage);
}
Handle<Smi> StoreHandler::TransitionToConstant(Isolate* isolate,
int descriptor) {
DCHECK(!FLAG_track_constant_fields);
int config = KindBits::encode(kTransitionToConstant) |
DescriptorBits::encode(descriptor);
return handle(Smi::FromInt(config), isolate);
return StoreField(isolate, kind, descriptor, field_index, representation);
}
Handle<Smi> StoreHandler::StoreNativeDataProperty(Isolate* isolate,
......
......@@ -202,28 +202,53 @@ Handle<Object> StoreHandler::StoreElementTransition(
return handler;
}
Handle<Smi> StoreHandler::StoreTransition(Isolate* isolate,
Handle<Map> transition_map) {
int descriptor = transition_map->LastAdded();
Handle<DescriptorArray> descriptors(transition_map->instance_descriptors());
PropertyDetails details = descriptors->GetDetails(descriptor);
Representation representation = details.representation();
DCHECK(!representation.IsNone());
Handle<Object> StoreHandler::StoreTransition(Isolate* isolate,
Handle<Map> transition_map) {
bool is_dictionary_map = transition_map->is_dictionary_map();
#ifdef DEBUG
if (!is_dictionary_map) {
int descriptor = transition_map->LastAdded();
Handle<DescriptorArray> descriptors(transition_map->instance_descriptors());
PropertyDetails details = descriptors->GetDetails(descriptor);
if (descriptors->GetKey(descriptor)->IsPrivate()) {
DCHECK_EQ(DONT_ENUM, details.attributes());
} else {
DCHECK_EQ(NONE, details.attributes());
}
Representation representation = details.representation();
DCHECK(!representation.IsNone());
}
#endif
// Declarative handlers don't support access checks.
DCHECK(!transition_map->is_access_check_needed());
DCHECK_EQ(kData, details.kind());
if (details.location() == PropertyLocation::kDescriptor) {
return TransitionToConstant(isolate, descriptor);
// Get validity cell value if it is necessary for the handler.
Handle<Object> validity_cell;
if (is_dictionary_map || !transition_map->IsPrototypeValidityCellValid()) {
validity_cell =
Map::GetOrCreatePrototypeChainValidityCell(transition_map, isolate);
if (validity_cell.is_null()) {
validity_cell = handle(Smi::FromInt(Map::kPrototypeChainValid), isolate);
}
}
DCHECK_EQ(PropertyLocation::kField, details.location());
bool extend_storage =
Map::cast(transition_map->GetBackPointer())->UnusedPropertyFields() == 0;
FieldIndex index = FieldIndex::ForDescriptor(*transition_map, descriptor);
return TransitionToField(isolate, descriptor, index, representation,
extend_storage);
if (is_dictionary_map) {
DCHECK(!transition_map->IsJSGlobalObjectMap());
Handle<StoreHandler> handler = isolate->factory()->NewStoreHandler(0);
// Store normal with enabled lookup on receiver.
int config = KindBits::encode(kNormal) | LookupOnReceiverBits::encode(true);
handler->set_smi_handler(Smi::FromInt(config));
handler->set_validity_cell(*validity_cell);
return handler;
} else {
// Ensure the transition map contains a valid prototype validity cell.
if (!validity_cell.is_null()) {
transition_map->set_prototype_validity_cell(*validity_cell);
}
Handle<WeakCell> cell = Map::WeakCellForMap(transition_map);
return cell;
}
}
// static
......
......@@ -192,9 +192,6 @@ class StoreHandler final : public DataHandler {
kElement,
kField,
kConstField,
// TODO(ishell): remove once constant field tracking is done.
kTransitionToConstant = kConstField,
kTransitionToField,
kAccessor,
kNativeDataProperty,
kApiSetter,
......@@ -236,8 +233,7 @@ class StoreHandler final : public DataHandler {
//
// Encoding when KindBits contains kField or kTransitionToField.
//
class ExtendStorageBits : public BitField<bool, DescriptorBits::kNext, 1> {};
class IsInobjectBits : public BitField<bool, ExtendStorageBits::kNext, 1> {};
class IsInobjectBits : public BitField<bool, DescriptorBits::kNext, 1> {};
class FieldRepresentationBits
: public BitField<FieldRepresentation, IsInobjectBits::kNext, 2> {};
// +1 here is to cover all possible JSObject header sizes.
......@@ -257,8 +253,8 @@ class StoreHandler final : public DataHandler {
PropertyConstness constness,
Representation representation);
static Handle<Smi> StoreTransition(Isolate* isolate,
Handle<Map> transition_map);
static Handle<Object> StoreTransition(Isolate* isolate,
Handle<Map> transition_map);
// Creates a Smi-handler for storing a native data property on a fast object.
static inline Handle<Smi> StoreNativeDataProperty(Isolate* isolate,
......@@ -303,19 +299,7 @@ class StoreHandler final : public DataHandler {
private:
static inline Handle<Smi> StoreField(Isolate* isolate, Kind kind,
int descriptor, FieldIndex field_index,
Representation representation,
bool extend_storage);
// Creates a Smi-handler for transitioning store to a field.
static inline Handle<Smi> TransitionToField(Isolate* isolate, int descriptor,
FieldIndex field_index,
Representation representation,
bool extend_storage);
// Creates a Smi-handler for transitioning store to a constant field (in this
// case the only thing that needs to be done is an update of a map).
static inline Handle<Smi> TransitionToConstant(Isolate* isolate,
int descriptor);
Representation representation);
};
} // namespace internal
......
......@@ -1252,56 +1252,58 @@ bool StoreIC::LookupForWrite(LookupIterator* it, Handle<Object> value,
Handle<JSObject> receiver = Handle<JSObject>::cast(object);
DCHECK(!receiver->map()->is_deprecated());
for (; it->IsFound(); it->Next()) {
switch (it->state()) {
case LookupIterator::NOT_FOUND:
case LookupIterator::TRANSITION:
UNREACHABLE();
case LookupIterator::JSPROXY:
return true;
case LookupIterator::INTERCEPTOR: {
Handle<JSObject> holder = it->GetHolder<JSObject>();
InterceptorInfo* info = holder->GetNamedInterceptor();
if (it->HolderIsReceiverOrHiddenPrototype()) {
return !info->non_masking() && receiver.is_identical_to(holder) &&
!info->setter()->IsUndefined(it->isolate());
} else if (!info->getter()->IsUndefined(it->isolate()) ||
!info->query()->IsUndefined(it->isolate())) {
return false;
}
break;
}
case LookupIterator::ACCESS_CHECK:
if (it->GetHolder<JSObject>()->IsAccessCheckNeeded()) return false;
break;
case LookupIterator::ACCESSOR:
return !it->IsReadOnly();
case LookupIterator::INTEGER_INDEXED_EXOTIC:
return false;
case LookupIterator::DATA: {
if (it->IsReadOnly()) return false;
Handle<JSObject> holder = it->GetHolder<JSObject>();
if (receiver.is_identical_to(holder)) {
it->PrepareForDataProperty(value);
// The previous receiver map might just have been deprecated,
// so reload it.
update_receiver_map(receiver);
if (it->state() != LookupIterator::TRANSITION) {
for (; it->IsFound(); it->Next()) {
switch (it->state()) {
case LookupIterator::NOT_FOUND:
case LookupIterator::TRANSITION:
UNREACHABLE();
case LookupIterator::JSPROXY:
return true;
case LookupIterator::INTERCEPTOR: {
Handle<JSObject> holder = it->GetHolder<JSObject>();
InterceptorInfo* info = holder->GetNamedInterceptor();
if (it->HolderIsReceiverOrHiddenPrototype()) {
return !info->non_masking() && receiver.is_identical_to(holder) &&
!info->setter()->IsUndefined(it->isolate());
} else if (!info->getter()->IsUndefined(it->isolate()) ||
!info->query()->IsUndefined(it->isolate())) {
return false;
}
break;
}
case LookupIterator::ACCESS_CHECK:
if (it->GetHolder<JSObject>()->IsAccessCheckNeeded()) return false;
break;
case LookupIterator::ACCESSOR:
return !it->IsReadOnly();
case LookupIterator::INTEGER_INDEXED_EXOTIC:
return false;
case LookupIterator::DATA: {
if (it->IsReadOnly()) return false;
Handle<JSObject> holder = it->GetHolder<JSObject>();
if (receiver.is_identical_to(holder)) {
it->PrepareForDataProperty(value);
// The previous receiver map might just have been deprecated,
// so reload it.
update_receiver_map(receiver);
return true;
}
// Receiver != holder.
if (receiver->IsJSGlobalProxy()) {
PrototypeIterator iter(it->isolate(), receiver);
return it->GetHolder<Object>().is_identical_to(
PrototypeIterator::GetCurrent(iter));
}
// Receiver != holder.
if (receiver->IsJSGlobalProxy()) {
PrototypeIterator iter(it->isolate(), receiver);
return it->GetHolder<Object>().is_identical_to(
PrototypeIterator::GetCurrent(iter));
}
if (it->HolderIsReceiverOrHiddenPrototype()) return false;
if (it->HolderIsReceiverOrHiddenPrototype()) return false;
if (it->ExtendingNonExtensible(receiver)) return false;
created_new_transition_ = it->PrepareTransitionToDataProperty(
receiver, value, NONE, store_mode);
return it->IsCacheableTransition();
if (it->ExtendingNonExtensible(receiver)) return false;
created_new_transition_ = it->PrepareTransitionToDataProperty(
receiver, value, NONE, store_mode);
return it->IsCacheableTransition();
}
}
}
}
......@@ -1388,19 +1390,15 @@ MaybeHandle<Object> StoreIC::Store(Handle<Object> object, Handle<Name> name,
if (state() != UNINITIALIZED) {
JSObject::MakePrototypesFast(object, kStartAtPrototype, isolate());
}
MaybeHandle<Object> cached_handler;
Handle<Map> transition_map;
MaybeHandle<Map> maybe_transition_map;
if (object->IsJSReceiver()) {
name = isolate()->factory()->InternalizeName(name);
TransitionsAccessor transitions(receiver_map());
Object* maybe_handler = transitions.SearchHandler(*name, &transition_map);
if (maybe_handler != nullptr) {
cached_handler = MaybeHandle<Object>(maybe_handler, isolate());
}
maybe_transition_map =
TransitionsAccessor(receiver_map()).FindTransitionToDataProperty(name);
}
LookupIterator it = LookupIterator::ForTransitionHandler(
isolate(), object, name, value, cached_handler, transition_map);
isolate(), object, name, value, maybe_transition_map);
bool use_ic = FLAG_use_ic;
......@@ -1416,8 +1414,7 @@ MaybeHandle<Object> StoreIC::Store(Handle<Object> object, Handle<Name> name,
use_ic = false;
}
}
if (use_ic) UpdateCaches(&it, value, store_mode, cached_handler);
if (use_ic) UpdateCaches(&it, value, store_mode);
MAYBE_RETURN_NULL(
Object::SetProperty(&it, value, language_mode(), store_mode));
......@@ -1425,8 +1422,7 @@ MaybeHandle<Object> StoreIC::Store(Handle<Object> object, Handle<Name> name,
}
void StoreIC::UpdateCaches(LookupIterator* lookup, Handle<Object> value,
JSReceiver::StoreFromKeyed store_mode,
MaybeHandle<Object> cached_handler) {
JSReceiver::StoreFromKeyed store_mode) {
if (state() == UNINITIALIZED && !IsStoreGlobalIC()) {
// This is the first time we execute this inline cache. Set the target to
// the pre monomorphic stub to delay setting the monomorphic state.
......@@ -1437,9 +1433,7 @@ void StoreIC::UpdateCaches(LookupIterator* lookup, Handle<Object> value,
}
Handle<Object> handler;
if (!cached_handler.is_null()) {
handler = cached_handler.ToHandleChecked();
} else if (LookupForWrite(lookup, value, store_mode)) {
if (LookupForWrite(lookup, value, store_mode)) {
if (IsStoreGlobalIC()) {
if (lookup->state() == LookupIterator::DATA &&
lookup->GetReceiver().is_identical_to(lookup->GetHolder<Object>())) {
......@@ -1470,16 +1464,17 @@ void StoreIC::UpdateCaches(LookupIterator* lookup, Handle<Object> value,
Handle<Object> StoreIC::ComputeHandler(LookupIterator* lookup) {
switch (lookup->state()) {
case LookupIterator::TRANSITION: {
Handle<JSObject> holder = lookup->GetHolder<JSObject>();
Handle<JSObject> store_target = lookup->GetStoreTarget<JSObject>();
if (store_target->IsJSGlobalObject()) {
TRACE_HANDLER_STATS(isolate(), StoreIC_StoreGlobalTransitionDH);
if (receiver_map()->IsJSGlobalObject()) {
DCHECK(IsStoreGlobalIC());
#ifdef DEBUG
Handle<JSObject> holder = lookup->GetHolder<JSObject>();
DCHECK_EQ(*lookup->GetReceiver(), *holder);
DCHECK_EQ(*store_target, *holder);
#endif
return StoreHandler::StoreGlobal(isolate(),
lookup->transition_cell());
}
......@@ -1491,31 +1486,13 @@ Handle<Object> StoreIC::ComputeHandler(LookupIterator* lookup) {
isolate(), receiver_map(), store_target, smi_handler, cell);
return handler;
}
// Currently not handled by CompileStoreTransition.
if (!holder->HasFastProperties()) {
set_slow_stub_reason("transition from slow");
TRACE_HANDLER_STATS(isolate(), StoreIC_SlowStub);
return slow_stub();
}
// Dictionary-to-fast transitions are not expected and not supported.
DCHECK_IMPLIES(!lookup->transition_map()->is_dictionary_map(),
!receiver_map()->is_dictionary_map());
DCHECK(lookup->IsCacheableTransition());
Handle<Map> transition = lookup->transition_map();
Handle<Smi> smi_handler;
if (transition->is_dictionary_map()) {
TRACE_HANDLER_STATS(isolate(), StoreIC_StoreNormalDH);
smi_handler = StoreHandler::StoreNormal(isolate());
} else {
TRACE_HANDLER_STATS(isolate(), StoreIC_StoreTransitionDH);
smi_handler = StoreHandler::StoreTransition(isolate(), transition);
}
Handle<WeakCell> cell = Map::WeakCellForMap(transition);
Handle<Object> handler = StoreHandler::StoreThroughPrototype(
isolate(), receiver_map(), holder, smi_handler, cell);
TransitionsAccessor(receiver_map())
.UpdateHandler(*lookup->name(), *handler);
return handler;
return StoreHandler::StoreTransition(isolate(), lookup->transition_map());
}
case LookupIterator::INTERCEPTOR: {
......
......@@ -323,8 +323,7 @@ class StoreIC : public IC {
// Update the inline cache and the global stub cache based on the
// lookup result.
void UpdateCaches(LookupIterator* lookup, Handle<Object> value,
JSReceiver::StoreFromKeyed store_mode,
MaybeHandle<Object> cached_handler);
JSReceiver::StoreFromKeyed store_mode);
private:
Handle<Object> ComputeHandler(LookupIterator* lookup);
......
......@@ -77,13 +77,6 @@ class KeyedStoreGenericAssembler : public AccessorAssembler {
Variable* var_accessor_pair,
Variable* var_accessor_holder,
Label* readonly, Label* bailout);
void CheckFieldType(Node* descriptors, Node* name_index, Node* representation,
Node* value, Label* bailout);
void OverwriteExistingFastProperty(Node* object, Node* object_map,
Node* properties, Node* descriptors,
Node* descriptor_name_index, Node* details,
Node* value, Label* slow);
};
void KeyedStoreGenericGenerator::Generate(compiler::CodeAssemblerState* state) {
......@@ -616,145 +609,6 @@ void KeyedStoreGenericAssembler::LookupPropertyOnPrototypeChain(
BIND(&ok_to_write);
}
void KeyedStoreGenericAssembler::CheckFieldType(Node* descriptors,
Node* name_index,
Node* representation,
Node* value, Label* bailout) {
Label r_smi(this), r_double(this), r_heapobject(this), all_fine(this);
// Ignore FLAG_track_fields etc. and always emit code for all checks,
// because this builtin is part of the snapshot and therefore should
// be flag independent.
GotoIf(Word32Equal(representation, Int32Constant(Representation::kSmi)),
&r_smi);
GotoIf(Word32Equal(representation, Int32Constant(Representation::kDouble)),
&r_double);
GotoIf(
Word32Equal(representation, Int32Constant(Representation::kHeapObject)),
&r_heapobject);
GotoIf(Word32Equal(representation, Int32Constant(Representation::kNone)),
bailout);
CSA_ASSERT(this, Word32Equal(representation,
Int32Constant(Representation::kTagged)));
Goto(&all_fine);
BIND(&r_smi);
{ Branch(TaggedIsSmi(value), &all_fine, bailout); }
BIND(&r_double);
{
GotoIf(TaggedIsSmi(value), &all_fine);
Node* value_map = LoadMap(value);
// While supporting mutable HeapNumbers would be straightforward, such
// objects should not end up here anyway.
CSA_ASSERT(this,
WordNotEqual(value_map,
LoadRoot(Heap::kMutableHeapNumberMapRootIndex)));
Branch(IsHeapNumberMap(value_map), &all_fine, bailout);
}
BIND(&r_heapobject);
{
GotoIf(TaggedIsSmi(value), bailout);
Node* field_type =
LoadValueByKeyIndex<DescriptorArray>(descriptors, name_index);
intptr_t kNoneType = reinterpret_cast<intptr_t>(FieldType::None());
intptr_t kAnyType = reinterpret_cast<intptr_t>(FieldType::Any());
// FieldType::None can't hold any value.
GotoIf(WordEqual(field_type, IntPtrConstant(kNoneType)), bailout);
// FieldType::Any can hold any value.
GotoIf(WordEqual(field_type, IntPtrConstant(kAnyType)), &all_fine);
CSA_ASSERT(this, IsWeakCell(field_type));
// Cleared WeakCells count as FieldType::None, which can't hold any value.
field_type = LoadWeakCellValue(field_type, bailout);
// FieldType::Class(...) performs a map check.
CSA_ASSERT(this, IsMap(field_type));
Branch(WordEqual(LoadMap(value), field_type), &all_fine, bailout);
}
BIND(&all_fine);
}
void KeyedStoreGenericAssembler::OverwriteExistingFastProperty(
Node* object, Node* object_map, Node* properties, Node* descriptors,
Node* descriptor_name_index, Node* details, Node* value, Label* slow) {
// Properties in descriptors can't be overwritten without map transition.
GotoIf(Word32NotEqual(DecodeWord32<PropertyDetails::LocationField>(details),
Int32Constant(kField)),
slow);
if (FLAG_track_constant_fields) {
// TODO(ishell): Taking the slow path is not necessary if new and old
// values are identical.
GotoIf(Word32Equal(DecodeWord32<PropertyDetails::ConstnessField>(details),
Int32Constant(kConst)),
slow);
}
Label done(this);
Node* representation =
DecodeWord32<PropertyDetails::RepresentationField>(details);
CheckFieldType(descriptors, descriptor_name_index, representation, value,
slow);
Node* field_index =
DecodeWordFromWord32<PropertyDetails::FieldIndexField>(details);
field_index =
IntPtrAdd(field_index, LoadMapInobjectPropertiesStartInWords(object_map));
Node* instance_size_in_words = LoadMapInstanceSizeInWords(object_map);
Label inobject(this), backing_store(this);
Branch(UintPtrLessThan(field_index, instance_size_in_words), &inobject,
&backing_store);
BIND(&inobject);
{
Node* field_offset = TimesPointerSize(field_index);
Label tagged_rep(this), double_rep(this);
Branch(Word32Equal(representation, Int32Constant(Representation::kDouble)),
&double_rep, &tagged_rep);
BIND(&double_rep);
{
Node* double_value = ChangeNumberToFloat64(value);
if (FLAG_unbox_double_fields) {
StoreObjectFieldNoWriteBarrier(object, field_offset, double_value,
MachineRepresentation::kFloat64);
} else {
Node* mutable_heap_number = LoadObjectField(object, field_offset);
StoreHeapNumberValue(mutable_heap_number, double_value);
}
Goto(&done);
}
BIND(&tagged_rep);
{
StoreObjectField(object, field_offset, value);
Goto(&done);
}
}
BIND(&backing_store);
{
Node* backing_store_index = IntPtrSub(field_index, instance_size_in_words);
Label tagged_rep(this), double_rep(this);
Branch(Word32Equal(representation, Int32Constant(Representation::kDouble)),
&double_rep, &tagged_rep);
BIND(&double_rep);
{
Node* double_value = ChangeNumberToFloat64(value);
Node* mutable_heap_number =
LoadFixedArrayElement(properties, backing_store_index);
StoreHeapNumberValue(mutable_heap_number, double_value);
Goto(&done);
}
BIND(&tagged_rep);
{
StoreFixedArrayElement(properties, backing_store_index, value);
Goto(&done);
}
}
BIND(&done);
}
void KeyedStoreGenericAssembler::EmitGenericPropertyStore(
Node* receiver, Node* receiver_map, const StoreICParameters* p, Label* slow,
UseStubCache use_stub_cache) {
......@@ -795,49 +649,29 @@ void KeyedStoreGenericAssembler::EmitGenericPropertyStore(
BIND(&data_property);
{
CheckForAssociatedProtector(p->name, slow);
Node* properties = LoadFastProperties(receiver);
OverwriteExistingFastProperty(receiver, receiver_map, properties,
descriptors, name_index, details,
p->value, slow);
OverwriteExistingFastDataProperty(receiver, receiver_map, descriptors,
name_index, details, p->value, slow,
false);
Return(p->value);
}
}
BIND(&lookup_transition);
{
Comment("lookup transition");
VARIABLE(var_handler, MachineRepresentation::kTagged);
Label check_key(this), found_handler(this, &var_handler);
Label found_simple_transition_handler(this);
Node* maybe_handler =
LoadObjectField(receiver_map, Map::kTransitionsOrPrototypeInfoOffset);
GotoIf(TaggedIsSmi(maybe_handler), notfound);
GotoIf(HasInstanceType(maybe_handler, STORE_HANDLER_TYPE), &check_key);
GotoIf(IsWeakCell(maybe_handler), &found_simple_transition_handler);
// TODO(jkummerow): Consider implementing TransitionArray search.
Goto(notfound);
BIND(&check_key);
{
Node* transition_cell =
LoadObjectField(maybe_handler, StoreHandler::kData1Offset);
Node* transition = LoadWeakCellValue(transition_cell, slow);
Node* transition_bitfield3 = LoadMapBitField3(transition);
GotoIf(IsSetWord32<Map::IsDeprecatedBit>(transition_bitfield3), slow);
Node* nof =
DecodeWord32<Map::NumberOfOwnDescriptorsBits>(transition_bitfield3);
Node* last_added = Int32Sub(nof, Int32Constant(1));
Node* transition_descriptors = LoadMapDescriptors(transition);
Node* key = DescriptorArrayGetKey(transition_descriptors, last_added);
GotoIf(WordNotEqual(key, p->name), slow);
var_handler.Bind(maybe_handler);
Goto(&found_handler);
}
BIND(&found_handler);
BIND(&found_simple_transition_handler);
{
Comment("KeyedStoreGeneric found transition handler");
HandleStoreICHandlerCase(p, var_handler.value(), notfound,
ICMode::kNonGlobalIC);
Node* transition_cell = maybe_handler;
Node* transition_map = LoadWeakCellValue(transition_cell, slow);
HandleStoreICTransitionMapHandlerCase(p, transition_map, slow, true);
}
}
}
......
......@@ -77,9 +77,13 @@ LookupIterator LookupIterator::PropertyOrElement(Isolate* isolate,
// static
LookupIterator LookupIterator::ForTransitionHandler(
Isolate* isolate, Handle<Object> receiver, Handle<Name> name,
Handle<Object> value, MaybeHandle<Object> handler,
Handle<Map> transition_map) {
if (handler.is_null()) return LookupIterator(receiver, name);
Handle<Object> value, MaybeHandle<Map> maybe_transition_map) {
Handle<Map> transition_map;
if (!maybe_transition_map.ToHandle(&transition_map) ||
!transition_map->IsPrototypeValidityCellValid()) {
// This map is not a valid transition handler, so full lookup is required.
return LookupIterator(receiver, name);
}
PropertyDetails details = PropertyDetails::Empty();
bool has_property;
......@@ -90,6 +94,13 @@ LookupIterator LookupIterator::ForTransitionHandler(
details = transition_map->GetLastDescriptorDetails();
has_property = true;
}
#ifdef DEBUG
if (name->IsPrivate()) {
DCHECK_EQ(DONT_ENUM, details.attributes());
} else {
DCHECK_EQ(NONE, details.attributes());
}
#endif
LookupIterator it(isolate, receiver, name, transition_map, details,
has_property);
......@@ -443,6 +454,14 @@ void LookupIterator::ReconfigureDataProperty(Handle<Object> value,
if (!IsElement() && !holder_obj->HasFastProperties()) {
PropertyDetails details(kData, attributes, PropertyCellType::kMutable);
if (holder_obj->map()->is_prototype_map() &&
(property_details_.attributes() & READ_ONLY) == 0 &&
(attributes & READ_ONLY) != 0) {
// Invalidate prototype validity cell when a property is reconfigured
// from writable to read-only as this may invalidate transitioning store
// IC handlers.
JSObject::InvalidatePrototypeChains(holder->map());
}
if (holder_obj->IsJSGlobalObject()) {
Handle<GlobalDictionary> dictionary(
JSGlobalObject::cast(*holder_obj)->global_dictionary());
......@@ -563,6 +582,19 @@ void LookupIterator::ApplyTransitionToDataProperty(
Handle<Map> transition = transition_map();
bool simple_transition = transition->GetBackPointer() == receiver->map();
if (configuration_ == DEFAULT && !transition->is_dictionary_map() &&
!transition->IsPrototypeValidityCellValid()) {
// Only LookupIterator instances with DEFAULT (full prototype chain)
// configuration can produce valid transition handler maps.
Handle<Object> validity_cell =
Map::GetOrCreatePrototypeChainValidityCell(transition, isolate());
if (validity_cell.is_null()) {
validity_cell =
handle(Smi::FromInt(Map::kPrototypeChainValid), isolate());
}
transition->set_prototype_validity_cell(*validity_cell);
}
if (!receiver->IsJSProxy()) {
JSObject::MigrateToMap(Handle<JSObject>::cast(receiver), transition);
}
......
......@@ -135,12 +135,9 @@ class V8_EXPORT_PRIVATE LookupIterator final BASE_EMBEDDED {
Isolate* isolate, Handle<Object> receiver, Handle<Object> key,
bool* success, Configuration configuration = DEFAULT);
static LookupIterator ForTransitionHandler(Isolate* isolate,
Handle<Object> receiver,
Handle<Name> name,
Handle<Object> value,
MaybeHandle<Object> handler,
Handle<Map> transition_map);
static LookupIterator ForTransitionHandler(
Isolate* isolate, Handle<Object> receiver, Handle<Name> name,
Handle<Object> value, MaybeHandle<Map> maybe_transition_map);
void Restart() {
InterceptorState state = InterceptorState::kUninitialized;
......
......@@ -2131,7 +2131,14 @@ MUST_USE_RESULT Maybe<bool> FastAssign(
}
if (use_set) {
LookupIterator it(target, next_key, target);
Handle<Map> target_map(target->map(), isolate);
MaybeHandle<Map> maybe_transition_map =
TransitionsAccessor(target_map)
.FindTransitionToDataProperty(next_key);
LookupIterator it = LookupIterator::ForTransitionHandler(
isolate, target, next_key, prop_value, maybe_transition_map);
Maybe<bool> result =
Object::SetProperty(&it, prop_value, LanguageMode::kStrict,
Object::CERTAINLY_NOT_STORE_FROM_KEYED);
......@@ -6623,6 +6630,11 @@ void JSReceiver::DeleteNormalizedProperty(Handle<JSReceiver> object,
dictionary = NameDictionary::DeleteEntry(dictionary, entry);
object->SetProperties(*dictionary);
}
if (object->map()->is_prototype_map()) {
// Invalidate prototype validity cell as this may invalidate transitioning
// store IC handlers.
JSObject::InvalidatePrototypeChains(object->map());
}
}
......@@ -9059,7 +9071,7 @@ Handle<Map> Map::Normalize(Handle<Map> fast_map, PropertyNormalizationMode mode,
#ifdef ENABLE_SLOW_DCHECKS
if (FLAG_enable_slow_asserts) {
// The cached map should match newly created normalized map bit-by-bit,
// except for the code cache, which can contain some ics which can be
// except for the code cache, which can contain some ICs which can be
// applied to the shared map, dependent code and weak cell cache.
Handle<Map> fresh = Map::CopyNormalized(fast_map, mode);
......@@ -9079,7 +9091,9 @@ Handle<Map> Map::Normalize(Handle<Map> fast_map, PropertyNormalizationMode mode,
}
STATIC_ASSERT(Map::kWeakCellCacheOffset ==
Map::kDependentCodeOffset + kPointerSize);
int offset = Map::kWeakCellCacheOffset + kPointerSize;
STATIC_ASSERT(Map::kPrototypeValidityCellOffset ==
Map::kWeakCellCacheOffset + kPointerSize);
int offset = Map::kPrototypeValidityCellOffset + kPointerSize;
DCHECK_EQ(0, memcmp(fresh->address() + offset,
new_map->address() + offset, Map::kSize - offset));
}
......
......@@ -164,20 +164,20 @@ class DescriptorArray : public FixedArray {
// Returns the fixed array length required to hold number_of_descriptors
// descriptors.
static int LengthFor(int number_of_descriptors) {
static constexpr int LengthFor(int number_of_descriptors) {
return ToKeyIndex(number_of_descriptors);
}
static int ToDetailsIndex(int descriptor_number) {
static constexpr int ToDetailsIndex(int descriptor_number) {
return kFirstIndex + (descriptor_number * kEntrySize) + kEntryDetailsIndex;
}
// Conversion from descriptor number to array indices.
static int ToKeyIndex(int descriptor_number) {
static constexpr int ToKeyIndex(int descriptor_number) {
return kFirstIndex + (descriptor_number * kEntrySize) + kEntryKeyIndex;
}
static int ToValueIndex(int descriptor_number) {
static constexpr int ToValueIndex(int descriptor_number) {
return kFirstIndex + (descriptor_number * kEntrySize) + kEntryValueIndex;
}
......
......@@ -666,6 +666,13 @@ ACCESSORS(Map, prototype_validity_cell, Object, kPrototypeValidityCellOffset)
ACCESSORS(Map, constructor_or_backpointer, Object,
kConstructorOrBackPointerOffset)
bool Map::IsPrototypeValidityCellValid() const {
Object* validity_cell = prototype_validity_cell();
Object* value = validity_cell->IsSmi() ? Smi::cast(validity_cell)
: Cell::cast(validity_cell)->value();
return value == Smi::FromInt(Map::kPrototypeChainValid);
}
Object* Map::GetConstructor() const {
Object* maybe_constructor = constructor_or_backpointer();
// Follow any back pointers.
......
......@@ -580,6 +580,10 @@ class Map : public HeapObject {
// prototype.
DECL_ACCESSORS(prototype_validity_cell, Object)
// Returns true if prototype validity cell value represents "valid" prototype
// chain state.
inline bool IsPrototypeValidityCellValid() const;
inline PropertyDetails GetLastDescriptorDetails() const;
inline int LastAdded() const;
......
......@@ -205,62 +205,6 @@ void TransitionsAccessor::Insert(Handle<Name> name, Handle<Map> target,
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 kHandler:
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 kHandler: {
Object* raw_handler = StoreHandler::ValidHandlerOrNull(
raw_transitions_, name, out_transition);
if (raw_handler == nullptr) return raw_handler;
// Check transition key.
WeakCell* target_cell = StoreHandler::GetTransitionCell(raw_handler);
if (!IsMatchingMap(target_cell, name, kData, NONE)) return nullptr;
return raw_handler;
}
case kFullTransitionArray: {
int transition = transitions()->Search(kData, name, NONE);
if (transition == kNotFound) return nullptr;
Object* raw_handler = transitions()->GetRawTarget(transition);
if (raw_handler->IsStoreHandler()) {
return StoreHandler::ValidHandlerOrNull(raw_handler, name,
out_transition);
}
return nullptr;
}
}
UNREACHABLE();
return nullptr; // Make GCC happy.
}
Map* TransitionsAccessor::SearchTransition(Name* name, PropertyKind kind,
PropertyAttributes attributes) {
DCHECK(name->IsUniqueName());
......
......@@ -55,13 +55,6 @@ class TransitionsAccessor {
Map* SearchTransition(Name* name, PropertyKind kind,
PropertyAttributes attributes);
// This TransitionsAccessor instance is unusable after this operation.
void UpdateHandler(Name* name, Object* handler);
// If a valid handler is found, returns the transition target in
// |out_transition|.
Object* SearchHandler(Name* name, Handle<Map>* out_transition);
Map* SearchSpecial(Symbol* name);
// Returns true for non-property transitions like elements kind, or
// or frozen/sealed transitions.
......@@ -133,6 +126,8 @@ class TransitionsAccessor {
kPrototypeInfo,
kUninitialized,
kWeakCell,
// TODO(ishell): drop support for kHandler encoding since we use maps
// as transition handlers.
kHandler,
kFullTransitionArray,
};
......
// Copyright 2018 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.
// Flags: --allow-natives-syntax
(function() {
function SetX(o, v) {
o.x = v;
}
function SetY(o, v) {
o.y = v;
}
var p = {};
function Create() {
var o = {__proto__:p, b:1, a:2};
delete o.b;
assertFalse(%HasFastProperties(o));
return o;
}
for (var i = 0; i < 10; i++) {
var o = Create();
SetX(o, 13);
SetY(o, 13);
}
Object.defineProperty(p, "x", {value:42, configurable: true, writable: false});
for (var i = 0; i < 10; i++) {
var o = Create();
SetY(o, 13);
}
var o = Create();
assertEquals(42, o.x);
SetX(o, 13);
assertEquals(42, o.x);
})();
(function() {
var p1 = {a:10};
Object.defineProperty(p1, "x", {value:42, configurable: true, writable: false});
var p2 = {__proto__: p1, x:153};
for (var i = 0; i < 2000; i++) {
p1["p" + i] = 0;
p2["p" + i] = 0;
}
assertFalse(%HasFastProperties(p1));
assertFalse(%HasFastProperties(p2));
function GetX(o) {
return o.x;
}
function SetX(o, v) {
o.x = v;
}
function Create() {
var o = {__proto__:p2, b:1, a:2};
return o;
}
for (var i = 0; i < 10; i++) {
var o = Create();
assertEquals(153, GetX(o));
SetX(o, 13);
assertEquals(13, GetX(o));
}
delete p2.x;
assertFalse(%HasFastProperties(p1));
assertFalse(%HasFastProperties(p2));
var o = Create();
assertEquals(42, GetX(o));
SetX(o, 13);
assertEquals(42, GetX(o));
})();
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment