diff --git a/src/objects/js-objects.cc b/src/objects/js-objects.cc index b5439ce940..529d02976f 100644 --- a/src/objects/js-objects.cc +++ b/src/objects/js-objects.cc @@ -4402,6 +4402,11 @@ void InvalidateOnePrototypeValidityCellInternal(Map map) { Cell cell = Cell::cast(maybe_cell); cell.set_value(Smi::FromInt(Map::kPrototypeChainInvalid)); } + Object maybe_prototype_info = map.prototype_info(); + if (maybe_prototype_info.IsPrototypeInfo()) { + PrototypeInfo prototype_info = PrototypeInfo::cast(maybe_prototype_info); + prototype_info.set_prototype_chain_enum_cache(Object()); + } } void InvalidatePrototypeChainsInternal(Map map) { diff --git a/src/objects/keys.cc b/src/objects/keys.cc index cfb2fa135d..b3cf8fdafb 100644 --- a/src/objects/keys.cc +++ b/src/objects/keys.cc @@ -44,6 +44,42 @@ static bool ContainsOnlyValidKeys(Handle array) { return true; } +static int AddKey(Object key, Handle combined_keys, + Handle descs, int nof_descriptors, + int target) { + for (InternalIndex i : InternalIndex::Range(nof_descriptors)) { + if (descs->GetKey(i) == key) return 0; + } + combined_keys->set(target, key); + return 1; +} + +static Handle CombineKeys(Isolate* isolate, + Handle own_keys, + Handle prototype_chain_keys, + Handle receiver) { + int prototype_chain_keys_length = prototype_chain_keys->length(); + if (prototype_chain_keys_length == 0) return own_keys; + + Map map = receiver->map(); + int nof_descriptors = map.NumberOfOwnDescriptors(); + if (nof_descriptors == 0) return prototype_chain_keys; + + Handle descs(map.instance_descriptors(), isolate); + int own_keys_length = own_keys.is_null() ? 0 : own_keys->length(); + Handle combined_keys = isolate->factory()->NewFixedArray( + own_keys_length + prototype_chain_keys_length); + if (own_keys_length != 0) { + own_keys->CopyTo(0, *combined_keys, 0, own_keys_length); + } + int target_keys_length = own_keys_length; + for (int i = 0; i < prototype_chain_keys_length; i++) { + target_keys_length += AddKey(prototype_chain_keys->get(i), combined_keys, + descs, nof_descriptors, target_keys_length); + } + return FixedArray::ShrinkOrEmpty(isolate, combined_keys, target_keys_length); +} + } // namespace // static @@ -68,6 +104,14 @@ Handle KeyAccumulator::GetKeys(GetKeysConversion convert) { Handle result = OrderedHashSet::ConvertToKeysArray(isolate(), keys(), convert); DCHECK(ContainsOnlyValidKeys(result)); + + if (try_prototype_info_cache_ && !first_prototype_map_.is_null()) { + PrototypeInfo::cast(first_prototype_map_->prototype_info()) + .set_prototype_chain_enum_cache(*result); + Map::GetOrCreatePrototypeChainValidityCell( + Handle(receiver_->map(), isolate_), isolate_); + DCHECK(first_prototype_map_->IsPrototypeValidityCellValid()); + } return result; } @@ -291,6 +335,9 @@ void FastKeyAccumulator::Prepare() { last_prototype = current; has_empty_prototype_ = false; } + // Check if we should try to create/use prototype info cache. + try_prototype_info_cache_ = TryPrototypeInfoCache(receiver_); + if (has_prototype_info_cache_) return; if (has_empty_prototype_) { is_receiver_simple_enum_ = receiver_->map().EnumLength() != kInvalidEnumCacheSentinel && @@ -432,6 +479,9 @@ MaybeHandle FastKeyAccumulator::GetKeys( if (isolate_->has_pending_exception()) return MaybeHandle(); } + if (try_prototype_info_cache_) { + return GetKeysWithPrototypeInfoCache(keys_conversion); + } return GetKeysSlow(keys_conversion); } @@ -503,12 +553,41 @@ MaybeHandle FastKeyAccumulator::GetKeysSlow( accumulator.set_skip_indices(skip_indices_); accumulator.set_last_non_empty_prototype(last_non_empty_prototype_); accumulator.set_may_have_elements(may_have_elements_); + accumulator.set_first_prototype_map(first_prototype_map_); + accumulator.set_try_prototype_info_cache(try_prototype_info_cache_); MAYBE_RETURN(accumulator.CollectKeys(receiver_, receiver_), MaybeHandle()); return accumulator.GetKeys(keys_conversion); } +MaybeHandle FastKeyAccumulator::GetKeysWithPrototypeInfoCache( + GetKeysConversion keys_conversion) { + Handle own_keys = KeyAccumulator::GetOwnEnumPropertyKeys( + isolate_, Handle::cast(receiver_)); + Handle prototype_chain_keys; + if (has_prototype_info_cache_) { + prototype_chain_keys = + handle(FixedArray::cast( + PrototypeInfo::cast(first_prototype_map_->prototype_info()) + .prototype_chain_enum_cache()), + isolate_); + } else { + KeyAccumulator accumulator(isolate_, mode_, filter_); + accumulator.set_is_for_in(is_for_in_); + accumulator.set_skip_indices(skip_indices_); + accumulator.set_last_non_empty_prototype(last_non_empty_prototype_); + accumulator.set_may_have_elements(may_have_elements_); + accumulator.set_receiver(receiver_); + accumulator.set_first_prototype_map(first_prototype_map_); + accumulator.set_try_prototype_info_cache(try_prototype_info_cache_); + MAYBE_RETURN(accumulator.CollectKeys(first_prototype_, first_prototype_), + MaybeHandle()); + prototype_chain_keys = accumulator.GetKeys(keys_conversion); + } + return CombineKeys(isolate_, own_keys, prototype_chain_keys, receiver_); +} + bool FastKeyAccumulator::MayHaveElements(JSReceiver receiver) { if (!receiver.IsJSObject()) return true; JSObject object = JSObject::cast(receiver); @@ -517,6 +596,28 @@ bool FastKeyAccumulator::MayHaveElements(JSReceiver receiver) { return false; } +bool FastKeyAccumulator::TryPrototypeInfoCache(Handle receiver) { + if (may_have_elements_) return false; + Handle object = Handle::cast(receiver); + if (!object->HasFastProperties()) return false; + if (object->HasNamedInterceptor()) return false; + if (object->IsAccessCheckNeeded() && + !isolate_->MayAccess(handle(isolate_->context(), isolate_), object)) { + return false; + } + HeapObject prototype = receiver->map().prototype(); + if (prototype.is_null()) return false; + if (!prototype.map().is_prototype_map()) return false; + first_prototype_ = handle(JSReceiver::cast(prototype), isolate_); + Handle map(prototype.map(), isolate_); + first_prototype_map_ = map; + has_prototype_info_cache_ = map->IsPrototypeValidityCellValid() && + PrototypeInfo::cast(map->prototype_info()) + .prototype_chain_enum_cache() + .IsFixedArray(); + return true; +} + namespace { enum IndexedOrNamed { kIndexed, kNamed }; diff --git a/src/objects/keys.h b/src/objects/keys.h index 4c2307a20b..aa99ae4723 100644 --- a/src/objects/keys.h +++ b/src/objects/keys.h @@ -87,6 +87,13 @@ class KeyAccumulator final { // indices. void set_is_for_in(bool value) { is_for_in_ = value; } void set_skip_indices(bool value) { skip_indices_ = value; } + void set_first_prototype_map(Handle value) { + first_prototype_map_ = value; + } + void set_try_prototype_info_cache(bool value) { + try_prototype_info_cache_ = value; + } + void set_receiver(Handle object) { receiver_ = object; } // The last_non_empty_prototype is used to limit the prototypes for which // we have to keep track of non-enumerable keys that can shadow keys // repeated on the prototype chain. @@ -117,6 +124,8 @@ class KeyAccumulator final { // keys a Handle. The OrderedHashSet is in-place converted to the // result list, a FixedArray containing all collected keys. Handle keys_; + Handle first_prototype_map_; + Handle receiver_; Handle last_non_empty_prototype_; Handle shadowing_keys_; KeyCollectionMode mode_; @@ -127,6 +136,7 @@ class KeyAccumulator final { // the shadow check. bool skip_shadow_check_ = true; bool may_have_elements_ = true; + bool try_prototype_info_cache_ = false; DISALLOW_COPY_AND_ASSIGN(KeyAccumulator); }; @@ -160,13 +170,18 @@ class FastKeyAccumulator { void Prepare(); MaybeHandle GetKeysFast(GetKeysConversion convert); MaybeHandle GetKeysSlow(GetKeysConversion convert); + MaybeHandle GetKeysWithPrototypeInfoCache( + GetKeysConversion convert); MaybeHandle GetOwnKeysWithUninitializedEnumCache(); bool MayHaveElements(JSReceiver receiver); + bool TryPrototypeInfoCache(Handle receiver); Isolate* isolate_; Handle receiver_; + Handle first_prototype_map_; + Handle first_prototype_; Handle last_non_empty_prototype_; KeyCollectionMode mode_; PropertyFilter filter_; @@ -175,6 +190,8 @@ class FastKeyAccumulator { bool is_receiver_simple_enum_ = false; bool has_empty_prototype_ = false; bool may_have_elements_ = true; + bool has_prototype_info_cache_ = false; + bool try_prototype_info_cache_ = false; DISALLOW_COPY_AND_ASSIGN(FastKeyAccumulator); }; diff --git a/src/objects/prototype-info-inl.h b/src/objects/prototype-info-inl.h index b83bb1346a..80efa862c2 100644 --- a/src/objects/prototype-info-inl.h +++ b/src/objects/prototype-info-inl.h @@ -41,6 +41,8 @@ bool PrototypeInfo::HasObjectCreateMap() { ACCESSORS(PrototypeInfo, module_namespace, Object, kJsModuleNamespaceOffset) ACCESSORS(PrototypeInfo, prototype_users, Object, kPrototypeUsersOffset) +ACCESSORS(PrototypeInfo, prototype_chain_enum_cache, Object, + kPrototypeChainEnumCacheOffset) WEAK_ACCESSORS(PrototypeInfo, object_create_map, kObjectCreateMapOffset) SMI_ACCESSORS(PrototypeInfo, registry_slot, kRegistrySlotOffset) SMI_ACCESSORS(PrototypeInfo, bit_field, kBitFieldOffset) diff --git a/src/objects/prototype-info.h b/src/objects/prototype-info.h index 94d86d2e19..60850cf929 100644 --- a/src/objects/prototype-info.h +++ b/src/objects/prototype-info.h @@ -30,6 +30,8 @@ class PrototypeInfo : public Struct { // this prototype, or Smi(0) if uninitialized. DECL_ACCESSORS(prototype_users, Object) + DECL_ACCESSORS(prototype_chain_enum_cache, Object) + // [object_create_map]: A field caching the map for Object.create(prototype). static inline void SetObjectCreateMap(Handle info, Handle map); diff --git a/src/objects/prototype-info.tq b/src/objects/prototype-info.tq index d303fa67b1..77ffa4358d 100644 --- a/src/objects/prototype-info.tq +++ b/src/objects/prototype-info.tq @@ -5,6 +5,7 @@ extern class PrototypeInfo extends Struct { js_module_namespace: JSModuleNamespace|Undefined; prototype_users: WeakArrayList|Zero; + prototype_chain_enum_cache: FixedArray|Object|Undefined; registry_slot: Smi; validity_cell: Object; object_create_map: Weak|Undefined; diff --git a/test/mjsunit/for-in-special-cases.js b/test/mjsunit/for-in-special-cases.js index 27129e1aac..744c948b94 100644 --- a/test/mjsunit/for-in-special-cases.js +++ b/test/mjsunit/for-in-special-cases.js @@ -141,3 +141,149 @@ for_in_string_prototype(); assertEquals(['prop2', 'prop1'], Accumulate(derived2)); } })(); + +(function for_in_prototype_itself_change() { + let prototype1 = {prop: 0, prop1: 1}; + let derived1 = {prop2: 2, prop3: 3}; + + Object.setPrototypeOf(derived1, prototype1); + for (let i = 0; i < 3; i++) { + assertEquals(['prop2', 'prop3', 'prop', 'prop1'], Accumulate(derived1)); + } + + prototype1.prop3 = 3; + let derived2 = {prop4: 4, prop5: 5}; + Object.setPrototypeOf(derived2, prototype1); + for (let i = 0; i < 3; i++) { + assertEquals(['prop4', 'prop5', 'prop', 'prop1', 'prop3'], Accumulate(derived2)); + } +})(); + +(function for_in_prototype_change_property() { + let prototype1 = {prop: 0, prop1: 1}; + let derived1 = {prop2: 2, prop3: 3}; + + Object.setPrototypeOf(derived1, prototype1); + for (let i = 0; i < 3; i++) { + assertEquals(['prop2', 'prop3', 'prop', 'prop1'], Accumulate(derived1)); + } + + prototype1.__proto__ = {prop4: 4, prop5: 5}; + for (let i = 0; i < 3; i++) { + assertEquals(['prop2', 'prop3', 'prop', 'prop1', 'prop4', 'prop5'], Accumulate(derived1)); + } + + derived1.__proto__ = {prop6: 6, prop7: 7}; + for (let i = 0; i < 3; i++) { + assertEquals(['prop2', 'prop3', 'prop6', 'prop7'], Accumulate(derived1)); + } +})(); + +(function for_in_prototype_change_element() { + let prototype1 = {prop: 0, prop1: 1}; + let derived1 = {prop2: 2, prop3: 3}; + + Object.setPrototypeOf(derived1, prototype1); + for (let i = 0; i < 3; i++) { + assertEquals(['prop2', 'prop3', 'prop', 'prop1'], Accumulate(derived1)); + } + + prototype1[0] = 4; + for (let i = 0; i < 3; i++) { + assertEquals(['prop2', 'prop3', '0', 'prop', 'prop1'], Accumulate(derived1)); + } + + derived1.__proto__ = {1: 1, 3: 3}; + for (let i = 0; i < 3; i++) { + assertEquals(['prop2', 'prop3', '1', '3'], Accumulate(derived1)); + } +})(); + +(function for_in_non_enumerable1() { + let prototype1 = {prop: 0}; + let derived1 = Object.create(prototype1, { + prop1: {enumerable: false, configurable: true, value: 1}, + }); + Object.setPrototypeOf(derived1, prototype1); + for (let i = 0; i < 3; i++) { + assertEquals(['prop'], Accumulate(derived1)); + } + + let derived2 = {prop2: 2}; + Object.setPrototypeOf(derived2, prototype1); + for (let i = 0; i < 3; i++) { + assertEquals(['prop2', 'prop'], Accumulate(derived2)); + } +})(); + +(function for_in_non_enumerable2() { + let prototype1 = {prop: 0}; + let derived1 = {prop1: 1}; + Object.setPrototypeOf(derived1, prototype1); + for (let i = 0; i < 3; i++) { + assertEquals(['prop1', 'prop'], Accumulate(derived1)); + } + + let derived2 = Object.create(prototype1, { + prop: {enumerable: false, configurable: true, value: 0}, + prop2: {enumerable: true, configurable: true, value: 2} + }); + for (let i = 0; i < 3; i++) { + assertEquals(['prop2'], Accumulate(derived2)); + } +})(); + +(function for_in_same_key1() { + let prototype1 = {prop: 0, prop1: 1}; + let derived1 = {prop: 0, prop2: 1}; + Object.setPrototypeOf(derived1, prototype1); + for (let i = 0; i < 3; i++) { + assertEquals(['prop', 'prop2', 'prop1'], Accumulate(derived1)); + } + + let derived2 = {prop3: 3, prop4: 4}; + Object.setPrototypeOf(derived2, prototype1); + for (let i = 0; i < 3; i++) { + assertEquals(['prop3', 'prop4', 'prop', 'prop1'], Accumulate(derived2)); + } +})(); + +(function for_in_same_key2() { + let prototype1 = {prop: 0, prop1: 1}; + let derived1 = {prop2: 2, prop3: 3}; + Object.setPrototypeOf(derived1, prototype1); + for (let i = 0; i < 3; i++) { + assertEquals(['prop2', 'prop3', 'prop', 'prop1'], Accumulate(derived1)); + } + + let derived2 = {prop: 0, prop4: 4}; + Object.setPrototypeOf(derived2, prototype1); + for (let i = 0; i < 3; i++) { + assertEquals(['prop', 'prop4', 'prop1'], Accumulate(derived2)); + } +})(); + +(function for_in_redefine_property() { + Object.prototype.prop = 0; + let object1 = {prop1: 1, prop2: 2}; + for (let i = 0; i < 3; i++) { + assertEquals(['prop1', 'prop2', 'prop'], Accumulate(object1)); + } + + let object2 = {prop3: 3, prop4: 4}; + Object.defineProperty(object2, + 'prop', {enumerable: false, configurable: true, value: 0}); + for (let i = 0; i < 3; i++) { + assertEquals(['prop3', 'prop4'], Accumulate(object2)); + } +})(); + +(function for_in_empty_property() { + let prototype1 = {prop: 0}; + let derived1 = Object.create(prototype1, { + prop: {enumerable: false, configurable: true, value: 0} + }); + for (let i = 0; i < 3; i++) { + assertEquals([], Accumulate(derived1)); + } +})();