Commit 5253d7bf authored by Zhang, Shiyu's avatar Zhang, Shiyu Committed by Commit Bot

[runtime] Cache prototype chain enumerable keys in PrototypeInfo

This CL adds a prototype_chain_enum_cache to cache the enumeration of a
prototype and its entire chain on the PrototypeInfo. It can improve for-in
performance via simply merging the receiver enumeration with this cache.

It improves the score of JetStream2-tagcloud-SP case by ~9% on IA Chromebook.

Contributed by tao.pan@intel.com

Change-Id: Ib40bfe41e772672337155584672f06fa1ba1e70d
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1870844
Commit-Queue: Shiyu Zhang <shiyu.zhang@intel.com>
Reviewed-by: 's avatarToon Verwaest <verwaest@chromium.org>
Cr-Commit-Position: refs/heads/master@{#65224}
parent 1d895572
......@@ -4398,6 +4398,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) {
......
......@@ -44,6 +44,42 @@ static bool ContainsOnlyValidKeys(Handle<FixedArray> array) {
return true;
}
static int AddKey(Object key, Handle<FixedArray> combined_keys,
Handle<DescriptorArray> 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<FixedArray> CombineKeys(Isolate* isolate,
Handle<FixedArray> own_keys,
Handle<FixedArray> prototype_chain_keys,
Handle<JSReceiver> 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<DescriptorArray> descs(map.instance_descriptors(), isolate);
int own_keys_length = own_keys.is_null() ? 0 : own_keys->length();
Handle<FixedArray> 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<FixedArray> KeyAccumulator::GetKeys(GetKeysConversion convert) {
Handle<FixedArray> 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<Map>(receiver_->map(), isolate_), isolate_);
DCHECK(first_prototype_map_->IsPrototypeValidityCellValid());
}
return result;
}
......@@ -275,6 +319,9 @@ void FastKeyAccumulator::Prepare() {
DisallowHeapAllocation no_gc;
// Directly go for the fast path for OWN_ONLY keys.
if (mode_ == KeyCollectionMode::kOwnOnly) return;
// Check if we should try to create/use prototype info cache.
try_prototype_info_cache_ = TryPrototypeInfoCache(receiver_);
if (has_prototype_info_cache_) return;
// Fully walk the prototype chain and find the last prototype with keys.
is_receiver_simple_enum_ = false;
has_empty_prototype_ = true;
......@@ -432,6 +479,9 @@ MaybeHandle<FixedArray> FastKeyAccumulator::GetKeys(
if (isolate_->has_pending_exception()) return MaybeHandle<FixedArray>();
}
if (try_prototype_info_cache_) {
return GetKeysWithPrototypeInfoCache(keys_conversion);
}
return GetKeysSlow(keys_conversion);
}
......@@ -503,12 +553,41 @@ MaybeHandle<FixedArray> 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<FixedArray>());
return accumulator.GetKeys(keys_conversion);
}
MaybeHandle<FixedArray> FastKeyAccumulator::GetKeysWithPrototypeInfoCache(
GetKeysConversion keys_conversion) {
Handle<FixedArray> own_keys = KeyAccumulator::GetOwnEnumPropertyKeys(
isolate_, Handle<JSObject>::cast(receiver_));
Handle<FixedArray> 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<FixedArray>());
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<JSReceiver> receiver) {
if (MayHaveElements(*receiver)) return false;
Handle<JSObject> object = Handle<JSObject>::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> 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 };
......
......@@ -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<Map> value) {
first_prototype_map_ = value;
}
void set_try_prototype_info_cache(bool value) {
try_prototype_info_cache_ = value;
}
void set_receiver(Handle<JSReceiver> 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<FixedArray>. The OrderedHashSet is in-place converted to the
// result list, a FixedArray containing all collected keys.
Handle<FixedArray> keys_;
Handle<Map> first_prototype_map_;
Handle<JSReceiver> receiver_;
Handle<JSReceiver> last_non_empty_prototype_;
Handle<ObjectHashSet> 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<FixedArray> GetKeysFast(GetKeysConversion convert);
MaybeHandle<FixedArray> GetKeysSlow(GetKeysConversion convert);
MaybeHandle<FixedArray> GetKeysWithPrototypeInfoCache(
GetKeysConversion convert);
MaybeHandle<FixedArray> GetOwnKeysWithUninitializedEnumCache();
bool MayHaveElements(JSReceiver receiver);
bool TryPrototypeInfoCache(Handle<JSReceiver> receiver);
Isolate* isolate_;
Handle<JSReceiver> receiver_;
Handle<Map> first_prototype_map_;
Handle<JSReceiver> first_prototype_;
Handle<JSReceiver> 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);
};
......
......@@ -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)
......
......@@ -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<PrototypeInfo> info,
Handle<Map> map);
......
......@@ -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<Map>|Undefined;
......
......@@ -141,3 +141,129 @@ 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() {
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_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));
}
})();
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