[builtins] Port WeakMap.p.delete and WeakSet.p.delete to CSA from JS
- Add WeakMapPrototypeDelete and WeakSetPrototypeDelete TFJ builtins - Fast paths when it's not necessary to shrink the table - Add WeakCollectionDelete TFS Some quick benchmarks shows 1.4x - 2.15x gains in performance. https://github.com/peterwmwong/v8-perf/blob/master/weakcollection-delete/README.md Bug: v8:5049, v8:6604 Change-Id: I14036df153f3a0242f9083d751658b868b16660a Reviewed-on: https://chromium-review.googlesource.com/743864 Reviewed-by: Jakob Gruber <jgruber@chromium.org> Commit-Queue: Jakob Gruber <jgruber@chromium.org> Cr-Commit-Position: refs/heads/master@{#49076}
This commit is contained in:
parent
d9c5e5d0fc
commit
43858375cf
@ -3292,6 +3292,8 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
|
|||||||
// Setup %WeakMapPrototype%.
|
// Setup %WeakMapPrototype%.
|
||||||
Handle<JSObject> prototype(JSObject::cast(cons->instance_prototype()));
|
Handle<JSObject> prototype(JSObject::cast(cons->instance_prototype()));
|
||||||
|
|
||||||
|
SimpleInstallFunction(prototype, "delete",
|
||||||
|
Builtins::kWeakMapPrototypeDelete, 1, true);
|
||||||
SimpleInstallFunction(prototype, "get", Builtins::kWeakMapGet, 1, true);
|
SimpleInstallFunction(prototype, "get", Builtins::kWeakMapGet, 1, true);
|
||||||
SimpleInstallFunction(prototype, "has", Builtins::kWeakMapHas, 1, true);
|
SimpleInstallFunction(prototype, "has", Builtins::kWeakMapHas, 1, true);
|
||||||
SimpleInstallFunction(prototype, "set", Builtins::kWeakMapPrototypeSet, 2,
|
SimpleInstallFunction(prototype, "set", Builtins::kWeakMapPrototypeSet, 2,
|
||||||
@ -3312,6 +3314,8 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
|
|||||||
// Setup %WeakSetPrototype%.
|
// Setup %WeakSetPrototype%.
|
||||||
Handle<JSObject> prototype(JSObject::cast(cons->instance_prototype()));
|
Handle<JSObject> prototype(JSObject::cast(cons->instance_prototype()));
|
||||||
|
|
||||||
|
SimpleInstallFunction(prototype, "delete",
|
||||||
|
Builtins::kWeakSetPrototypeDelete, 1, true);
|
||||||
SimpleInstallFunction(prototype, "has", Builtins::kWeakSetHas, 1, true);
|
SimpleInstallFunction(prototype, "has", Builtins::kWeakSetHas, 1, true);
|
||||||
SimpleInstallFunction(prototype, "add", Builtins::kWeakSetPrototypeAdd, 1,
|
SimpleInstallFunction(prototype, "add", Builtins::kWeakSetPrototypeAdd, 1,
|
||||||
true);
|
true);
|
||||||
|
@ -1901,6 +1901,22 @@ TNode<Word32T> WeakCollectionsBuiltinsAssembler::InsufficientCapacityToAdd(
|
|||||||
capacity));
|
capacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WeakCollectionsBuiltinsAssembler::RemoveEntry(
|
||||||
|
TNode<Object> table, TNode<IntPtrT> key_index,
|
||||||
|
TNode<IntPtrT> number_of_elements) {
|
||||||
|
// See ObjectHashTable::RemoveEntry().
|
||||||
|
TNode<IntPtrT> value_index = ValueIndexFromKeyIndex(key_index);
|
||||||
|
StoreFixedArrayElement(table, key_index, TheHoleConstant());
|
||||||
|
StoreFixedArrayElement(table, value_index, TheHoleConstant());
|
||||||
|
|
||||||
|
// See HashTableBase::ElementRemoved().
|
||||||
|
TNode<IntPtrT> number_of_deleted = LoadNumberOfDeleted(table, 1);
|
||||||
|
StoreFixedArrayElement(table, ObjectHashTable::kNumberOfElementsIndex,
|
||||||
|
SmiFromWord(number_of_elements), SKIP_WRITE_BARRIER);
|
||||||
|
StoreFixedArrayElement(table, ObjectHashTable::kNumberOfDeletedElementsIndex,
|
||||||
|
SmiFromWord(number_of_deleted), SKIP_WRITE_BARRIER);
|
||||||
|
}
|
||||||
|
|
||||||
TNode<BoolT> WeakCollectionsBuiltinsAssembler::ShouldRehash(
|
TNode<BoolT> WeakCollectionsBuiltinsAssembler::ShouldRehash(
|
||||||
TNode<IntPtrT> number_of_elements, TNode<IntPtrT> number_of_deleted) {
|
TNode<IntPtrT> number_of_elements, TNode<IntPtrT> number_of_deleted) {
|
||||||
// Rehash if more than 33% of the entries are deleted.
|
// Rehash if more than 33% of the entries are deleted.
|
||||||
@ -1908,6 +1924,22 @@ TNode<BoolT> WeakCollectionsBuiltinsAssembler::ShouldRehash(
|
|||||||
number_of_elements);
|
number_of_elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TNode<Word32T> WeakCollectionsBuiltinsAssembler::ShouldShrink(
|
||||||
|
TNode<IntPtrT> capacity, TNode<IntPtrT> number_of_elements) {
|
||||||
|
// See HashTable::Shrink().
|
||||||
|
TNode<IntPtrT> quarter_capacity = WordShr(capacity, 2);
|
||||||
|
return Word32And(
|
||||||
|
// Shrink to fit the number of elements if only a quarter of the
|
||||||
|
// capacity is filled with elements.
|
||||||
|
IntPtrLessThanOrEqual(number_of_elements, quarter_capacity),
|
||||||
|
|
||||||
|
// Allocate a new dictionary with room for at least the current
|
||||||
|
// number of elements. The allocation method will make sure that
|
||||||
|
// there is extra room in the dictionary for additions. Don't go
|
||||||
|
// lower than room for 16 elements.
|
||||||
|
IntPtrGreaterThanOrEqual(number_of_elements, IntPtrConstant(16)));
|
||||||
|
}
|
||||||
|
|
||||||
TNode<IntPtrT> WeakCollectionsBuiltinsAssembler::ValueIndexFromKeyIndex(
|
TNode<IntPtrT> WeakCollectionsBuiltinsAssembler::ValueIndexFromKeyIndex(
|
||||||
TNode<IntPtrT> key_index) {
|
TNode<IntPtrT> key_index) {
|
||||||
return IntPtrAdd(key_index,
|
return IntPtrAdd(key_index,
|
||||||
@ -1978,6 +2010,37 @@ TF_BUILTIN(WeakMapHas, WeakCollectionsBuiltinsAssembler) {
|
|||||||
Return(FalseConstant());
|
Return(FalseConstant());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper that removes the entry with a given key from the backing store
|
||||||
|
// (ObjectHashTable) of a WeakMap or WeakSet.
|
||||||
|
TF_BUILTIN(WeakCollectionDelete, WeakCollectionsBuiltinsAssembler) {
|
||||||
|
TNode<Context> context = CAST(Parameter(Descriptor::kContext));
|
||||||
|
TNode<Object> collection = CAST(Parameter(Descriptor::kCollection));
|
||||||
|
TNode<Object> key = CAST(Parameter(Descriptor::kKey));
|
||||||
|
|
||||||
|
Label call_runtime(this), if_not_found(this);
|
||||||
|
|
||||||
|
GotoIf(TaggedIsSmi(key), &if_not_found);
|
||||||
|
GotoIfNot(IsJSReceiver(key), &if_not_found);
|
||||||
|
|
||||||
|
TNode<IntPtrT> hash = LoadJSReceiverIdentityHash(key, &if_not_found);
|
||||||
|
TNode<Object> table = LoadTable(collection);
|
||||||
|
TNode<IntPtrT> capacity = LoadTableCapacity(table);
|
||||||
|
TNode<IntPtrT> key_index =
|
||||||
|
FindKeyIndexForKey(table, key, hash, EntryMask(capacity), &if_not_found);
|
||||||
|
TNode<IntPtrT> number_of_elements = LoadNumberOfElements(table, -1);
|
||||||
|
GotoIf(ShouldShrink(capacity, number_of_elements), &call_runtime);
|
||||||
|
|
||||||
|
RemoveEntry(table, key_index, number_of_elements);
|
||||||
|
Return(TrueConstant());
|
||||||
|
|
||||||
|
BIND(&if_not_found);
|
||||||
|
Return(FalseConstant());
|
||||||
|
|
||||||
|
BIND(&call_runtime);
|
||||||
|
Return(CallRuntime(Runtime::kWeakCollectionDelete, context, collection, key,
|
||||||
|
SmiTag(hash)));
|
||||||
|
}
|
||||||
|
|
||||||
// Helper that sets the key and value to the backing store (ObjectHashTable) of
|
// Helper that sets the key and value to the backing store (ObjectHashTable) of
|
||||||
// a WeakMap or WeakSet.
|
// a WeakMap or WeakSet.
|
||||||
TF_BUILTIN(WeakCollectionSet, WeakCollectionsBuiltinsAssembler) {
|
TF_BUILTIN(WeakCollectionSet, WeakCollectionsBuiltinsAssembler) {
|
||||||
@ -2030,6 +2093,17 @@ TF_BUILTIN(WeakCollectionSet, WeakCollectionsBuiltinsAssembler) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TF_BUILTIN(WeakMapPrototypeDelete, CodeStubAssembler) {
|
||||||
|
TNode<Context> context = CAST(Parameter(Descriptor::kContext));
|
||||||
|
TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver));
|
||||||
|
TNode<Object> key = CAST(Parameter(Descriptor::kKey));
|
||||||
|
|
||||||
|
ThrowIfNotInstanceType(context, receiver, JS_WEAK_MAP_TYPE,
|
||||||
|
"WeakMap.prototype.delete");
|
||||||
|
|
||||||
|
Return(CallBuiltin(Builtins::kWeakCollectionDelete, context, receiver, key));
|
||||||
|
}
|
||||||
|
|
||||||
TF_BUILTIN(WeakMapPrototypeSet, CodeStubAssembler) {
|
TF_BUILTIN(WeakMapPrototypeSet, CodeStubAssembler) {
|
||||||
TNode<Context> context = CAST(Parameter(Descriptor::kContext));
|
TNode<Context> context = CAST(Parameter(Descriptor::kContext));
|
||||||
TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver));
|
TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver));
|
||||||
@ -2069,6 +2143,18 @@ TF_BUILTIN(WeakSetPrototypeAdd, CodeStubAssembler) {
|
|||||||
ThrowTypeError(context, MessageTemplate::kInvalidWeakSetValue, value);
|
ThrowTypeError(context, MessageTemplate::kInvalidWeakSetValue, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TF_BUILTIN(WeakSetPrototypeDelete, CodeStubAssembler) {
|
||||||
|
TNode<Context> context = CAST(Parameter(Descriptor::kContext));
|
||||||
|
TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver));
|
||||||
|
TNode<Object> value = CAST(Parameter(Descriptor::kValue));
|
||||||
|
|
||||||
|
ThrowIfNotInstanceType(context, receiver, JS_WEAK_SET_TYPE,
|
||||||
|
"WeakSet.prototype.delete");
|
||||||
|
|
||||||
|
Return(
|
||||||
|
CallBuiltin(Builtins::kWeakCollectionDelete, context, receiver, value));
|
||||||
|
}
|
||||||
|
|
||||||
TF_BUILTIN(WeakSetHas, WeakCollectionsBuiltinsAssembler) {
|
TF_BUILTIN(WeakSetHas, WeakCollectionsBuiltinsAssembler) {
|
||||||
Node* const receiver = Parameter(Descriptor::kReceiver);
|
Node* const receiver = Parameter(Descriptor::kReceiver);
|
||||||
Node* const key = Parameter(Descriptor::kKey);
|
Node* const key = Parameter(Descriptor::kKey);
|
||||||
|
@ -1090,12 +1090,15 @@ namespace internal {
|
|||||||
TFJ(WeakMapGet, 1, kKey) \
|
TFJ(WeakMapGet, 1, kKey) \
|
||||||
TFJ(WeakMapHas, 1, kKey) \
|
TFJ(WeakMapHas, 1, kKey) \
|
||||||
TFJ(WeakMapPrototypeSet, 2, kKey, kValue) \
|
TFJ(WeakMapPrototypeSet, 2, kKey, kValue) \
|
||||||
|
TFJ(WeakMapPrototypeDelete, 1, kKey) \
|
||||||
\
|
\
|
||||||
/* WeakSet */ \
|
/* WeakSet */ \
|
||||||
TFJ(WeakSetHas, 1, kKey) \
|
TFJ(WeakSetHas, 1, kKey) \
|
||||||
TFJ(WeakSetPrototypeAdd, 1, kValue) \
|
TFJ(WeakSetPrototypeAdd, 1, kValue) \
|
||||||
|
TFJ(WeakSetPrototypeDelete, 1, kValue) \
|
||||||
\
|
\
|
||||||
/* WeakSet / WeakMap Helpers */ \
|
/* WeakSet / WeakMap Helpers */ \
|
||||||
|
TFS(WeakCollectionDelete, kCollection, kKey) \
|
||||||
TFS(WeakCollectionSet, kCollection, kKey, kValue) \
|
TFS(WeakCollectionSet, kCollection, kKey, kValue) \
|
||||||
\
|
\
|
||||||
/* AsyncGenerator */ \
|
/* AsyncGenerator */ \
|
||||||
|
@ -40,23 +40,6 @@ function WeakMapConstructor(iterable) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Set up the non-enumerable functions on the WeakMap prototype object.
|
|
||||||
DEFINE_METHODS(
|
|
||||||
GlobalWeakMap.prototype,
|
|
||||||
{
|
|
||||||
delete(key) {
|
|
||||||
if (!IS_WEAKMAP(this)) {
|
|
||||||
throw %make_type_error(kIncompatibleMethodReceiver,
|
|
||||||
'WeakMap.prototype.delete', this);
|
|
||||||
}
|
|
||||||
if (!IS_RECEIVER(key)) return false;
|
|
||||||
var hash = %GetExistingHash(key);
|
|
||||||
if (IS_UNDEFINED(hash)) return false;
|
|
||||||
return %WeakCollectionDelete(this, key, hash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
%SetCode(GlobalWeakMap, WeakMapConstructor);
|
%SetCode(GlobalWeakMap, WeakMapConstructor);
|
||||||
@ -84,23 +67,6 @@ function WeakSetConstructor(iterable) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Set up the non-enumerable functions on the WeakSet prototype object.
|
|
||||||
DEFINE_METHODS(
|
|
||||||
GlobalWeakSet.prototype,
|
|
||||||
{
|
|
||||||
delete(value) {
|
|
||||||
if (!IS_WEAKSET(this)) {
|
|
||||||
throw %make_type_error(kIncompatibleMethodReceiver,
|
|
||||||
'WeakSet.prototype.delete', this);
|
|
||||||
}
|
|
||||||
if (!IS_RECEIVER(value)) return false;
|
|
||||||
var hash = %GetExistingHash(value);
|
|
||||||
if (IS_UNDEFINED(hash)) return false;
|
|
||||||
return %WeakCollectionDelete(this, value, hash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
%SetCode(GlobalWeakSet, WeakSetConstructor);
|
%SetCode(GlobalWeakSet, WeakSetConstructor);
|
||||||
|
@ -116,10 +116,18 @@ RUNTIME_FUNCTION(Runtime_WeakCollectionDelete) {
|
|||||||
CONVERT_ARG_HANDLE_CHECKED(JSWeakCollection, weak_collection, 0);
|
CONVERT_ARG_HANDLE_CHECKED(JSWeakCollection, weak_collection, 0);
|
||||||
CONVERT_ARG_HANDLE_CHECKED(Object, key, 1);
|
CONVERT_ARG_HANDLE_CHECKED(Object, key, 1);
|
||||||
CONVERT_SMI_ARG_CHECKED(hash, 2)
|
CONVERT_SMI_ARG_CHECKED(hash, 2)
|
||||||
CHECK(key->IsJSReceiver() || key->IsSymbol());
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
DCHECK(key->IsJSReceiver());
|
||||||
|
DCHECK(ObjectHashTableShape::IsLive(isolate, *key));
|
||||||
Handle<ObjectHashTable> table(
|
Handle<ObjectHashTable> table(
|
||||||
ObjectHashTable::cast(weak_collection->table()));
|
ObjectHashTable::cast(weak_collection->table()));
|
||||||
CHECK(table->IsKey(isolate, *key));
|
// Should only be called when shrinking the table is necessary. See
|
||||||
|
// HashTable::Shrink().
|
||||||
|
DCHECK(table->NumberOfElements() - 1 <= (table->Capacity() >> 2) &&
|
||||||
|
table->NumberOfElements() - 1 >= 16);
|
||||||
|
#endif
|
||||||
|
|
||||||
bool was_present = JSWeakCollection::Delete(weak_collection, key, hash);
|
bool was_present = JSWeakCollection::Delete(weak_collection, key, hash);
|
||||||
return isolate->heap()->ToBoolean(was_present);
|
return isolate->heap()->ToBoolean(was_present);
|
||||||
}
|
}
|
||||||
|
@ -180,11 +180,21 @@ test(function() {
|
|||||||
}, "Method WeakSet.prototype.add called on incompatible receiver [object Array]",
|
}, "Method WeakSet.prototype.add called on incompatible receiver [object Array]",
|
||||||
TypeError);
|
TypeError);
|
||||||
|
|
||||||
|
test(function() {
|
||||||
|
WeakSet.prototype.delete.call([]);
|
||||||
|
}, "Method WeakSet.prototype.delete called on incompatible receiver [object Array]",
|
||||||
|
TypeError);
|
||||||
|
|
||||||
test(function() {
|
test(function() {
|
||||||
WeakMap.prototype.set.call([]);
|
WeakMap.prototype.set.call([]);
|
||||||
}, "Method WeakMap.prototype.set called on incompatible receiver [object Array]",
|
}, "Method WeakMap.prototype.set called on incompatible receiver [object Array]",
|
||||||
TypeError);
|
TypeError);
|
||||||
|
|
||||||
|
test(function() {
|
||||||
|
WeakMap.prototype.delete.call([]);
|
||||||
|
}, "Method WeakMap.prototype.delete called on incompatible receiver [object Array]",
|
||||||
|
TypeError);
|
||||||
|
|
||||||
// kNonCallableInInstanceOfCheck
|
// kNonCallableInInstanceOfCheck
|
||||||
test(function() {
|
test(function() {
|
||||||
1 instanceof {};
|
1 instanceof {};
|
||||||
|
Loading…
Reference in New Issue
Block a user