[symbol-as-weakmap-key] Implement Symbol as WeakMap Keys
Allow non-registered symbols as keys in weakmap and weakset. Allow non-registered symbols as target and unregisterToken in WeakRef and FinalizationRegistry. Bug: v8:12947 Change-Id: Ieb63bda66e3cc378879ac651e23300b71caed627 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3865056 Reviewed-by: Dominik Inführ <dinfuehr@chromium.org> Commit-Queue: Marja Hölttä <marja@chromium.org> Reviewed-by: Jakob Linke <jgruber@chromium.org> Cr-Commit-Position: refs/heads/main@{#83313}
This commit is contained in:
parent
4ad770242b
commit
c400af48b5
@ -439,9 +439,9 @@ extern enum MessageTemplate {
|
||||
kWasmTrapStringOffsetOutOfBounds,
|
||||
kWasmObjectsAreOpaque,
|
||||
kWeakRefsRegisterTargetAndHoldingsMustNotBeSame,
|
||||
kWeakRefsRegisterTargetMustBeObject,
|
||||
kWeakRefsUnregisterTokenMustBeObject,
|
||||
kWeakRefsWeakRefConstructorTargetMustBeObject,
|
||||
kInvalidWeakRefsRegisterTarget,
|
||||
kInvalidWeakRefsUnregisterToken,
|
||||
kInvalidWeakRefsWeakRefConstructorTarget,
|
||||
...
|
||||
}
|
||||
|
||||
@ -918,10 +918,10 @@ macro Float64IsNaN(n: float64): bool {
|
||||
// The type of all tagged values that can safely be compared with TaggedEqual.
|
||||
@if(V8_ENABLE_WEBASSEMBLY)
|
||||
type TaggedWithIdentity = JSReceiver | FixedArrayBase | Oddball | Map |
|
||||
WeakCell | Context | EmptyString | WasmInternalFunction;
|
||||
WeakCell | Context | EmptyString | Symbol | WasmInternalFunction;
|
||||
@ifnot(V8_ENABLE_WEBASSEMBLY)
|
||||
type TaggedWithIdentity = JSReceiver | FixedArrayBase | Oddball | Map |
|
||||
WeakCell | Context | EmptyString;
|
||||
WeakCell | Context | EmptyString | Symbol;
|
||||
|
||||
extern operator '==' macro TaggedEqual(TaggedWithIdentity, Object): bool;
|
||||
extern operator '==' macro TaggedEqual(Object, TaggedWithIdentity): bool;
|
||||
|
@ -22,130 +22,6 @@ namespace internal {
|
||||
template <class T>
|
||||
using TVariable = compiler::TypedCodeAssemblerVariable<T>;
|
||||
|
||||
class BaseCollectionsAssembler : public CodeStubAssembler {
|
||||
public:
|
||||
explicit BaseCollectionsAssembler(compiler::CodeAssemblerState* state)
|
||||
: CodeStubAssembler(state) {}
|
||||
|
||||
virtual ~BaseCollectionsAssembler() = default;
|
||||
|
||||
protected:
|
||||
enum Variant { kMap, kSet, kWeakMap, kWeakSet };
|
||||
|
||||
// Adds an entry to a collection. For Maps, properly handles extracting the
|
||||
// key and value from the entry (see LoadKeyValue()).
|
||||
void AddConstructorEntry(Variant variant, TNode<Context> context,
|
||||
TNode<Object> collection, TNode<Object> add_function,
|
||||
TNode<Object> key_value,
|
||||
Label* if_may_have_side_effects = nullptr,
|
||||
Label* if_exception = nullptr,
|
||||
TVariable<Object>* var_exception = nullptr);
|
||||
|
||||
// Adds constructor entries to a collection. Choosing a fast path when
|
||||
// possible.
|
||||
void AddConstructorEntries(Variant variant, TNode<Context> context,
|
||||
TNode<Context> native_context,
|
||||
TNode<HeapObject> collection,
|
||||
TNode<Object> initial_entries);
|
||||
|
||||
// Fast path for adding constructor entries. Assumes the entries are a fast
|
||||
// JS array (see CodeStubAssembler::BranchIfFastJSArray()).
|
||||
void AddConstructorEntriesFromFastJSArray(Variant variant,
|
||||
TNode<Context> context,
|
||||
TNode<Context> native_context,
|
||||
TNode<Object> collection,
|
||||
TNode<JSArray> fast_jsarray,
|
||||
Label* if_may_have_side_effects);
|
||||
|
||||
// Adds constructor entries to a collection using the iterator protocol.
|
||||
void AddConstructorEntriesFromIterable(Variant variant,
|
||||
TNode<Context> context,
|
||||
TNode<Context> native_context,
|
||||
TNode<Object> collection,
|
||||
TNode<Object> iterable);
|
||||
|
||||
// Constructs a collection instance. Choosing a fast path when possible.
|
||||
TNode<JSObject> AllocateJSCollection(TNode<Context> context,
|
||||
TNode<JSFunction> constructor,
|
||||
TNode<JSReceiver> new_target);
|
||||
|
||||
// Fast path for constructing a collection instance if the constructor
|
||||
// function has not been modified.
|
||||
TNode<JSObject> AllocateJSCollectionFast(TNode<JSFunction> constructor);
|
||||
|
||||
// Fallback for constructing a collection instance if the constructor function
|
||||
// has been modified.
|
||||
TNode<JSObject> AllocateJSCollectionSlow(TNode<Context> context,
|
||||
TNode<JSFunction> constructor,
|
||||
TNode<JSReceiver> new_target);
|
||||
|
||||
// Allocates the backing store for a collection.
|
||||
virtual TNode<HeapObject> AllocateTable(
|
||||
Variant variant, TNode<IntPtrT> at_least_space_for) = 0;
|
||||
|
||||
// Main entry point for a collection constructor builtin.
|
||||
void GenerateConstructor(Variant variant,
|
||||
Handle<String> constructor_function_name,
|
||||
TNode<Object> new_target, TNode<IntPtrT> argc,
|
||||
TNode<Context> context);
|
||||
|
||||
// Retrieves the collection function that adds an entry. `set` for Maps and
|
||||
// `add` for Sets.
|
||||
TNode<Object> GetAddFunction(Variant variant, TNode<Context> context,
|
||||
TNode<Object> collection);
|
||||
|
||||
// Retrieves the collection constructor function.
|
||||
TNode<JSFunction> GetConstructor(Variant variant,
|
||||
TNode<Context> native_context);
|
||||
|
||||
// Retrieves the initial collection function that adds an entry. Should only
|
||||
// be called when it is certain that a collection prototype's map hasn't been
|
||||
// changed.
|
||||
TNode<JSFunction> GetInitialAddFunction(Variant variant,
|
||||
TNode<Context> native_context);
|
||||
|
||||
// Checks whether {collection}'s initial add/set function has been modified
|
||||
// (depending on {variant}, loaded from {native_context}).
|
||||
void GotoIfInitialAddFunctionModified(Variant variant,
|
||||
TNode<NativeContext> native_context,
|
||||
TNode<HeapObject> collection,
|
||||
Label* if_modified);
|
||||
|
||||
// Gets root index for the name of the add/set function.
|
||||
RootIndex GetAddFunctionNameIndex(Variant variant);
|
||||
|
||||
// Retrieves the offset to access the backing table from the collection.
|
||||
int GetTableOffset(Variant variant);
|
||||
|
||||
// Estimates the number of entries the collection will have after adding the
|
||||
// entries passed in the constructor. AllocateTable() can use this to avoid
|
||||
// the time of growing/rehashing when adding the constructor entries.
|
||||
TNode<IntPtrT> EstimatedInitialSize(TNode<Object> initial_entries,
|
||||
TNode<BoolT> is_fast_jsarray);
|
||||
|
||||
void GotoIfCannotBeWeakKey(const TNode<Object> obj,
|
||||
Label* if_cannot_be_weak_key);
|
||||
|
||||
// Determines whether the collection's prototype has been modified.
|
||||
TNode<BoolT> HasInitialCollectionPrototype(Variant variant,
|
||||
TNode<Context> native_context,
|
||||
TNode<Object> collection);
|
||||
|
||||
// Gets the initial prototype map for given collection {variant}.
|
||||
TNode<Map> GetInitialCollectionPrototype(Variant variant,
|
||||
TNode<Context> native_context);
|
||||
|
||||
// Loads an element from a fixed array. If the element is the hole, returns
|
||||
// `undefined`.
|
||||
TNode<Object> LoadAndNormalizeFixedArrayElement(TNode<FixedArray> elements,
|
||||
TNode<IntPtrT> index);
|
||||
|
||||
// Loads an element from a fixed double array. If the element is the hole,
|
||||
// returns `undefined`.
|
||||
TNode<Object> LoadAndNormalizeFixedDoubleArrayElement(
|
||||
TNode<HeapObject> elements, TNode<IntPtrT> index);
|
||||
};
|
||||
|
||||
void BaseCollectionsAssembler::AddConstructorEntry(
|
||||
Variant variant, TNode<Context> context, TNode<Object> collection,
|
||||
TNode<Object> add_function, TNode<Object> key_value,
|
||||
@ -523,16 +399,28 @@ TNode<IntPtrT> BaseCollectionsAssembler::EstimatedInitialSize(
|
||||
[=] { return IntPtrConstant(0); });
|
||||
}
|
||||
|
||||
// https://tc39.es/proposal-symbols-as-weakmap-keys/#sec-canbeheldweakly-abstract-operation
|
||||
void BaseCollectionsAssembler::GotoIfCannotBeWeakKey(
|
||||
const TNode<Object> obj, Label* if_cannot_be_weak_key) {
|
||||
Label check_symbol_key(this);
|
||||
Label end(this);
|
||||
GotoIf(TaggedIsSmi(obj), if_cannot_be_weak_key);
|
||||
TNode<Uint16T> instance_type = LoadMapInstanceType(LoadMap(CAST(obj)));
|
||||
GotoIfNot(IsJSReceiverInstanceType(instance_type), if_cannot_be_weak_key);
|
||||
GotoIfNot(IsJSReceiverInstanceType(instance_type), &check_symbol_key);
|
||||
// TODO(v8:12547) Shared structs and arrays should only be able to point
|
||||
// to shared values in weak collections. For now, disallow them as weak
|
||||
// collection keys.
|
||||
GotoIf(IsJSSharedStructInstanceType(instance_type), if_cannot_be_weak_key);
|
||||
GotoIf(IsJSSharedArrayInstanceType(instance_type), if_cannot_be_weak_key);
|
||||
Goto(&end);
|
||||
Bind(&check_symbol_key);
|
||||
GotoIfNot(HasHarmonySymbolAsWeakmapKeyFlag(), if_cannot_be_weak_key);
|
||||
GotoIfNot(IsSymbolInstanceType(instance_type), if_cannot_be_weak_key);
|
||||
TNode<Uint32T> flags = LoadSymbolFlags(CAST(obj));
|
||||
GotoIf(Word32And(flags, Symbol::IsInPublicSymbolTableBit::kMask),
|
||||
if_cannot_be_weak_key);
|
||||
Goto(&end);
|
||||
Bind(&end);
|
||||
}
|
||||
|
||||
TNode<Map> BaseCollectionsAssembler::GetInitialCollectionPrototype(
|
||||
@ -2414,67 +2302,6 @@ TF_BUILTIN(FindOrderedHashSetEntry, CollectionsBuiltinsAssembler) {
|
||||
Return(SmiConstant(-1));
|
||||
}
|
||||
|
||||
class WeakCollectionsBuiltinsAssembler : public BaseCollectionsAssembler {
|
||||
public:
|
||||
explicit WeakCollectionsBuiltinsAssembler(compiler::CodeAssemblerState* state)
|
||||
: BaseCollectionsAssembler(state) {}
|
||||
|
||||
protected:
|
||||
void AddEntry(TNode<EphemeronHashTable> table, TNode<IntPtrT> key_index,
|
||||
TNode<Object> key, TNode<Object> value,
|
||||
TNode<IntPtrT> number_of_elements);
|
||||
|
||||
TNode<HeapObject> AllocateTable(Variant variant,
|
||||
TNode<IntPtrT> at_least_space_for) override;
|
||||
|
||||
// Generates and sets the identity for a JSRececiver.
|
||||
TNode<Smi> CreateIdentityHash(TNode<Object> receiver);
|
||||
TNode<IntPtrT> EntryMask(TNode<IntPtrT> capacity);
|
||||
|
||||
// Builds code that finds the EphemeronHashTable entry for a {key} using the
|
||||
// comparison code generated by {key_compare}. The key index is returned if
|
||||
// the {key} is found.
|
||||
using KeyComparator =
|
||||
std::function<void(TNode<Object> entry_key, Label* if_same)>;
|
||||
TNode<IntPtrT> FindKeyIndex(TNode<HeapObject> table, TNode<IntPtrT> key_hash,
|
||||
TNode<IntPtrT> entry_mask,
|
||||
const KeyComparator& key_compare);
|
||||
|
||||
// Builds code that finds an EphemeronHashTable entry available for a new
|
||||
// entry.
|
||||
TNode<IntPtrT> FindKeyIndexForInsertion(TNode<HeapObject> table,
|
||||
TNode<IntPtrT> key_hash,
|
||||
TNode<IntPtrT> entry_mask);
|
||||
|
||||
// Builds code that finds the EphemeronHashTable entry with key that matches
|
||||
// {key} and returns the entry's key index. If {key} cannot be found, jumps to
|
||||
// {if_not_found}.
|
||||
TNode<IntPtrT> FindKeyIndexForKey(TNode<HeapObject> table, TNode<Object> key,
|
||||
TNode<IntPtrT> hash,
|
||||
TNode<IntPtrT> entry_mask,
|
||||
Label* if_not_found);
|
||||
|
||||
TNode<Word32T> InsufficientCapacityToAdd(TNode<IntPtrT> capacity,
|
||||
TNode<IntPtrT> number_of_elements,
|
||||
TNode<IntPtrT> number_of_deleted);
|
||||
TNode<IntPtrT> KeyIndexFromEntry(TNode<IntPtrT> entry);
|
||||
|
||||
TNode<IntPtrT> LoadNumberOfElements(TNode<EphemeronHashTable> table,
|
||||
int offset);
|
||||
TNode<IntPtrT> LoadNumberOfDeleted(TNode<EphemeronHashTable> table,
|
||||
int offset = 0);
|
||||
TNode<EphemeronHashTable> LoadTable(TNode<JSWeakCollection> collection);
|
||||
TNode<IntPtrT> LoadTableCapacity(TNode<EphemeronHashTable> table);
|
||||
|
||||
void RemoveEntry(TNode<EphemeronHashTable> table, TNode<IntPtrT> key_index,
|
||||
TNode<IntPtrT> number_of_elements);
|
||||
TNode<BoolT> ShouldRehash(TNode<IntPtrT> number_of_elements,
|
||||
TNode<IntPtrT> number_of_deleted);
|
||||
TNode<Word32T> ShouldShrink(TNode<IntPtrT> capacity,
|
||||
TNode<IntPtrT> number_of_elements);
|
||||
TNode<IntPtrT> ValueIndexFromKeyIndex(TNode<IntPtrT> key_index);
|
||||
};
|
||||
|
||||
void WeakCollectionsBuiltinsAssembler::AddEntry(
|
||||
TNode<EphemeronHashTable> table, TNode<IntPtrT> key_index,
|
||||
TNode<Object> key, TNode<Object> value, TNode<IntPtrT> number_of_elements) {
|
||||
@ -2490,6 +2317,25 @@ void WeakCollectionsBuiltinsAssembler::AddEntry(
|
||||
SmiFromIntPtr(number_of_elements));
|
||||
}
|
||||
|
||||
TNode<IntPtrT> WeakCollectionsBuiltinsAssembler::GetHash(
|
||||
const TNode<HeapObject> key, Label* if_no_hash) {
|
||||
TVARIABLE(IntPtrT, var_hash);
|
||||
Label if_symbol(this);
|
||||
Label return_result(this);
|
||||
GotoIfNot(IsJSReceiver(key), &if_symbol);
|
||||
var_hash = LoadJSReceiverIdentityHash(CAST(key), if_no_hash);
|
||||
Goto(&return_result);
|
||||
Bind(&if_symbol);
|
||||
CSA_DCHECK(this, IsSymbol(key));
|
||||
CSA_DCHECK(this, Word32BinaryNot(
|
||||
Word32And(LoadSymbolFlags(CAST(key)),
|
||||
Symbol::IsInPublicSymbolTableBit::kMask)));
|
||||
var_hash = ChangeInt32ToIntPtr(LoadNameHash(CAST(key), nullptr));
|
||||
Goto(&return_result);
|
||||
Bind(&return_result);
|
||||
return var_hash.value();
|
||||
}
|
||||
|
||||
TNode<HeapObject> WeakCollectionsBuiltinsAssembler::AllocateTable(
|
||||
Variant variant, TNode<IntPtrT> at_least_space_for) {
|
||||
// See HashTable::New().
|
||||
@ -2719,8 +2565,7 @@ TF_BUILTIN(WeakMapLookupHashIndex, WeakCollectionsBuiltinsAssembler) {
|
||||
|
||||
GotoIfCannotBeWeakKey(key, &if_cannot_be_weak_key);
|
||||
|
||||
TNode<IntPtrT> hash =
|
||||
LoadJSReceiverIdentityHash(CAST(key), &if_cannot_be_weak_key);
|
||||
TNode<IntPtrT> hash = GetHash(CAST(key), &if_cannot_be_weak_key);
|
||||
TNode<IntPtrT> capacity = LoadTableCapacity(table);
|
||||
TNode<IntPtrT> key_index = FindKeyIndexForKey(
|
||||
table, key, hash, EntryMask(capacity), &if_cannot_be_weak_key);
|
||||
@ -2785,8 +2630,7 @@ TF_BUILTIN(WeakCollectionDelete, WeakCollectionsBuiltinsAssembler) {
|
||||
|
||||
GotoIfCannotBeWeakKey(key, &if_cannot_be_weak_key);
|
||||
|
||||
TNode<IntPtrT> hash =
|
||||
LoadJSReceiverIdentityHash(CAST(key), &if_cannot_be_weak_key);
|
||||
TNode<IntPtrT> hash = GetHash(CAST(key), &if_cannot_be_weak_key);
|
||||
TNode<EphemeronHashTable> table = LoadTable(collection);
|
||||
TNode<IntPtrT> capacity = LoadTableCapacity(table);
|
||||
TNode<IntPtrT> key_index = FindKeyIndexForKey(
|
||||
@ -2810,10 +2654,10 @@ TF_BUILTIN(WeakCollectionDelete, WeakCollectionsBuiltinsAssembler) {
|
||||
TF_BUILTIN(WeakCollectionSet, WeakCollectionsBuiltinsAssembler) {
|
||||
auto context = Parameter<Context>(Descriptor::kContext);
|
||||
auto collection = Parameter<JSWeakCollection>(Descriptor::kCollection);
|
||||
auto key = Parameter<JSReceiver>(Descriptor::kKey);
|
||||
auto key = Parameter<HeapObject>(Descriptor::kKey);
|
||||
auto value = Parameter<Object>(Descriptor::kValue);
|
||||
|
||||
CSA_DCHECK(this, IsJSReceiver(key));
|
||||
CSA_DCHECK(this, Word32Or(IsJSReceiver(key), IsSymbol(key)));
|
||||
|
||||
Label call_runtime(this), if_no_hash(this), if_not_found(this);
|
||||
|
||||
@ -2821,7 +2665,7 @@ TF_BUILTIN(WeakCollectionSet, WeakCollectionsBuiltinsAssembler) {
|
||||
TNode<IntPtrT> capacity = LoadTableCapacity(table);
|
||||
TNode<IntPtrT> entry_mask = EntryMask(capacity);
|
||||
|
||||
TVARIABLE(IntPtrT, var_hash, LoadJSReceiverIdentityHash(key, &if_no_hash));
|
||||
TVARIABLE(IntPtrT, var_hash, GetHash(key, &if_no_hash));
|
||||
TNode<IntPtrT> key_index = FindKeyIndexForKey(table, key, var_hash.value(),
|
||||
entry_mask, &if_not_found);
|
||||
|
||||
@ -2830,6 +2674,7 @@ TF_BUILTIN(WeakCollectionSet, WeakCollectionsBuiltinsAssembler) {
|
||||
|
||||
BIND(&if_no_hash);
|
||||
{
|
||||
CSA_DCHECK(this, IsJSReceiver(key));
|
||||
var_hash = SmiUntag(CreateIdentityHash(key));
|
||||
Goto(&if_not_found);
|
||||
}
|
||||
|
@ -20,6 +20,192 @@ void BranchIfIterableWithOriginalValueSetIterator(
|
||||
TNode<Context> context, compiler::CodeAssemblerLabel* if_true,
|
||||
compiler::CodeAssemblerLabel* if_false);
|
||||
|
||||
class BaseCollectionsAssembler : public CodeStubAssembler {
|
||||
public:
|
||||
explicit BaseCollectionsAssembler(compiler::CodeAssemblerState* state)
|
||||
: CodeStubAssembler(state) {}
|
||||
|
||||
virtual ~BaseCollectionsAssembler() = default;
|
||||
|
||||
void GotoIfCannotBeWeakKey(const TNode<Object> obj,
|
||||
Label* if_cannot_be_weak_key);
|
||||
|
||||
protected:
|
||||
enum Variant { kMap, kSet, kWeakMap, kWeakSet };
|
||||
|
||||
// Adds an entry to a collection. For Maps, properly handles extracting the
|
||||
// key and value from the entry (see LoadKeyValue()).
|
||||
void AddConstructorEntry(Variant variant, TNode<Context> context,
|
||||
TNode<Object> collection, TNode<Object> add_function,
|
||||
TNode<Object> key_value,
|
||||
Label* if_may_have_side_effects = nullptr,
|
||||
Label* if_exception = nullptr,
|
||||
TVariable<Object>* var_exception = nullptr);
|
||||
|
||||
// Adds constructor entries to a collection. Choosing a fast path when
|
||||
// possible.
|
||||
void AddConstructorEntries(Variant variant, TNode<Context> context,
|
||||
TNode<Context> native_context,
|
||||
TNode<HeapObject> collection,
|
||||
TNode<Object> initial_entries);
|
||||
|
||||
// Fast path for adding constructor entries. Assumes the entries are a fast
|
||||
// JS array (see CodeStubAssembler::BranchIfFastJSArray()).
|
||||
void AddConstructorEntriesFromFastJSArray(Variant variant,
|
||||
TNode<Context> context,
|
||||
TNode<Context> native_context,
|
||||
TNode<Object> collection,
|
||||
TNode<JSArray> fast_jsarray,
|
||||
Label* if_may_have_side_effects);
|
||||
|
||||
// Adds constructor entries to a collection using the iterator protocol.
|
||||
void AddConstructorEntriesFromIterable(Variant variant,
|
||||
TNode<Context> context,
|
||||
TNode<Context> native_context,
|
||||
TNode<Object> collection,
|
||||
TNode<Object> iterable);
|
||||
|
||||
// Constructs a collection instance. Choosing a fast path when possible.
|
||||
TNode<JSObject> AllocateJSCollection(TNode<Context> context,
|
||||
TNode<JSFunction> constructor,
|
||||
TNode<JSReceiver> new_target);
|
||||
|
||||
// Fast path for constructing a collection instance if the constructor
|
||||
// function has not been modified.
|
||||
TNode<JSObject> AllocateJSCollectionFast(TNode<JSFunction> constructor);
|
||||
|
||||
// Fallback for constructing a collection instance if the constructor function
|
||||
// has been modified.
|
||||
TNode<JSObject> AllocateJSCollectionSlow(TNode<Context> context,
|
||||
TNode<JSFunction> constructor,
|
||||
TNode<JSReceiver> new_target);
|
||||
|
||||
// Allocates the backing store for a collection.
|
||||
virtual TNode<HeapObject> AllocateTable(
|
||||
Variant variant, TNode<IntPtrT> at_least_space_for) = 0;
|
||||
|
||||
// Main entry point for a collection constructor builtin.
|
||||
void GenerateConstructor(Variant variant,
|
||||
Handle<String> constructor_function_name,
|
||||
TNode<Object> new_target, TNode<IntPtrT> argc,
|
||||
TNode<Context> context);
|
||||
|
||||
// Retrieves the collection function that adds an entry. `set` for Maps and
|
||||
// `add` for Sets.
|
||||
TNode<Object> GetAddFunction(Variant variant, TNode<Context> context,
|
||||
TNode<Object> collection);
|
||||
|
||||
// Retrieves the collection constructor function.
|
||||
TNode<JSFunction> GetConstructor(Variant variant,
|
||||
TNode<Context> native_context);
|
||||
|
||||
// Retrieves the initial collection function that adds an entry. Should only
|
||||
// be called when it is certain that a collection prototype's map hasn't been
|
||||
// changed.
|
||||
TNode<JSFunction> GetInitialAddFunction(Variant variant,
|
||||
TNode<Context> native_context);
|
||||
|
||||
// Checks whether {collection}'s initial add/set function has been modified
|
||||
// (depending on {variant}, loaded from {native_context}).
|
||||
void GotoIfInitialAddFunctionModified(Variant variant,
|
||||
TNode<NativeContext> native_context,
|
||||
TNode<HeapObject> collection,
|
||||
Label* if_modified);
|
||||
|
||||
// Gets root index for the name of the add/set function.
|
||||
RootIndex GetAddFunctionNameIndex(Variant variant);
|
||||
|
||||
// Retrieves the offset to access the backing table from the collection.
|
||||
int GetTableOffset(Variant variant);
|
||||
|
||||
// Estimates the number of entries the collection will have after adding the
|
||||
// entries passed in the constructor. AllocateTable() can use this to avoid
|
||||
// the time of growing/rehashing when adding the constructor entries.
|
||||
TNode<IntPtrT> EstimatedInitialSize(TNode<Object> initial_entries,
|
||||
TNode<BoolT> is_fast_jsarray);
|
||||
|
||||
// Determines whether the collection's prototype has been modified.
|
||||
TNode<BoolT> HasInitialCollectionPrototype(Variant variant,
|
||||
TNode<Context> native_context,
|
||||
TNode<Object> collection);
|
||||
|
||||
// Gets the initial prototype map for given collection {variant}.
|
||||
TNode<Map> GetInitialCollectionPrototype(Variant variant,
|
||||
TNode<Context> native_context);
|
||||
|
||||
// Loads an element from a fixed array. If the element is the hole, returns
|
||||
// `undefined`.
|
||||
TNode<Object> LoadAndNormalizeFixedArrayElement(TNode<FixedArray> elements,
|
||||
TNode<IntPtrT> index);
|
||||
|
||||
// Loads an element from a fixed double array. If the element is the hole,
|
||||
// returns `undefined`.
|
||||
TNode<Object> LoadAndNormalizeFixedDoubleArrayElement(
|
||||
TNode<HeapObject> elements, TNode<IntPtrT> index);
|
||||
};
|
||||
|
||||
class WeakCollectionsBuiltinsAssembler : public BaseCollectionsAssembler {
|
||||
public:
|
||||
explicit WeakCollectionsBuiltinsAssembler(compiler::CodeAssemblerState* state)
|
||||
: BaseCollectionsAssembler(state) {}
|
||||
|
||||
protected:
|
||||
void AddEntry(TNode<EphemeronHashTable> table, TNode<IntPtrT> key_index,
|
||||
TNode<Object> key, TNode<Object> value,
|
||||
TNode<IntPtrT> number_of_elements);
|
||||
|
||||
TNode<HeapObject> AllocateTable(Variant variant,
|
||||
TNode<IntPtrT> at_least_space_for) override;
|
||||
|
||||
TNode<IntPtrT> GetHash(const TNode<HeapObject> key, Label* if_no_hash);
|
||||
// Generates and sets the identity for a JSRececiver.
|
||||
TNode<Smi> CreateIdentityHash(TNode<Object> receiver);
|
||||
TNode<IntPtrT> EntryMask(TNode<IntPtrT> capacity);
|
||||
|
||||
// Builds code that finds the EphemeronHashTable entry for a {key} using the
|
||||
// comparison code generated by {key_compare}. The key index is returned if
|
||||
// the {key} is found.
|
||||
using KeyComparator =
|
||||
std::function<void(TNode<Object> entry_key, Label* if_same)>;
|
||||
TNode<IntPtrT> FindKeyIndex(TNode<HeapObject> table, TNode<IntPtrT> key_hash,
|
||||
TNode<IntPtrT> entry_mask,
|
||||
const KeyComparator& key_compare);
|
||||
|
||||
// Builds code that finds an EphemeronHashTable entry available for a new
|
||||
// entry.
|
||||
TNode<IntPtrT> FindKeyIndexForInsertion(TNode<HeapObject> table,
|
||||
TNode<IntPtrT> key_hash,
|
||||
TNode<IntPtrT> entry_mask);
|
||||
|
||||
// Builds code that finds the EphemeronHashTable entry with key that matches
|
||||
// {key} and returns the entry's key index. If {key} cannot be found, jumps to
|
||||
// {if_not_found}.
|
||||
TNode<IntPtrT> FindKeyIndexForKey(TNode<HeapObject> table, TNode<Object> key,
|
||||
TNode<IntPtrT> hash,
|
||||
TNode<IntPtrT> entry_mask,
|
||||
Label* if_not_found);
|
||||
|
||||
TNode<Word32T> InsufficientCapacityToAdd(TNode<IntPtrT> capacity,
|
||||
TNode<IntPtrT> number_of_elements,
|
||||
TNode<IntPtrT> number_of_deleted);
|
||||
TNode<IntPtrT> KeyIndexFromEntry(TNode<IntPtrT> entry);
|
||||
|
||||
TNode<IntPtrT> LoadNumberOfElements(TNode<EphemeronHashTable> table,
|
||||
int offset);
|
||||
TNode<IntPtrT> LoadNumberOfDeleted(TNode<EphemeronHashTable> table,
|
||||
int offset = 0);
|
||||
TNode<EphemeronHashTable> LoadTable(TNode<JSWeakCollection> collection);
|
||||
TNode<IntPtrT> LoadTableCapacity(TNode<EphemeronHashTable> table);
|
||||
|
||||
void RemoveEntry(TNode<EphemeronHashTable> table, TNode<IntPtrT> key_index,
|
||||
TNode<IntPtrT> number_of_elements);
|
||||
TNode<BoolT> ShouldRehash(TNode<IntPtrT> number_of_elements,
|
||||
TNode<IntPtrT> number_of_deleted);
|
||||
TNode<Word32T> ShouldShrink(TNode<IntPtrT> capacity,
|
||||
TNode<IntPtrT> number_of_elements);
|
||||
TNode<IntPtrT> ValueIndexFromKeyIndex(TNode<IntPtrT> key_index);
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
||||
|
@ -9,6 +9,7 @@
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
|
||||
// https://tc39.es/proposal-symbols-as-weakmap-keys/#sec-finalization-registry.prototype.unregister
|
||||
BUILTIN(FinalizationRegistryUnregister) {
|
||||
HandleScope scope(isolate);
|
||||
const char* method_name = "FinalizationRegistry.prototype.unregister";
|
||||
@ -24,16 +25,29 @@ BUILTIN(FinalizationRegistryUnregister) {
|
||||
|
||||
Handle<Object> unregister_token = args.atOrUndefined(isolate, 1);
|
||||
|
||||
// 4. If CanBeHeldWeakly(unregisterToken) is false, throw a TypeError
|
||||
// exception.
|
||||
if (v8_flags.harmony_symbol_as_weakmap_key) {
|
||||
if (!unregister_token->IsJSReceiver() &&
|
||||
(!unregister_token->IsSymbol() ||
|
||||
Handle<Symbol>::cast(unregister_token)->is_in_public_symbol_table())) {
|
||||
THROW_NEW_ERROR_RETURN_FAILURE(
|
||||
isolate,
|
||||
NewTypeError(MessageTemplate::kInvalidWeakRefsUnregisterToken,
|
||||
unregister_token));
|
||||
}
|
||||
} else {
|
||||
// 4. If Type(unregisterToken) is not Object, throw a TypeError exception.
|
||||
if (!unregister_token->IsJSReceiver()) {
|
||||
THROW_NEW_ERROR_RETURN_FAILURE(
|
||||
isolate,
|
||||
NewTypeError(MessageTemplate::kWeakRefsUnregisterTokenMustBeObject,
|
||||
NewTypeError(MessageTemplate::kInvalidWeakRefsUnregisterToken,
|
||||
unregister_token));
|
||||
}
|
||||
}
|
||||
|
||||
bool success = JSFinalizationRegistry::Unregister(
|
||||
finalization_registry, Handle<JSReceiver>::cast(unregister_token),
|
||||
finalization_registry, Handle<HeapObject>::cast(unregister_token),
|
||||
isolate);
|
||||
|
||||
return *isolate->factory()->ToBoolean(success);
|
||||
|
@ -697,6 +697,21 @@ Cast<JSReceiver|Null>(o: HeapObject): JSReceiver|Null
|
||||
}
|
||||
}
|
||||
|
||||
Cast<JSReceiver|Symbol>(implicit context: Context)(o: Object): JSReceiver|Symbol
|
||||
labels CastError {
|
||||
typeswitch (o) {
|
||||
case (o: JSReceiver): {
|
||||
return o;
|
||||
}
|
||||
case (o: Symbol): {
|
||||
return o;
|
||||
}
|
||||
case (Object): {
|
||||
goto CastError;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Cast<Smi|PromiseReaction>(o: Object): Smi|PromiseReaction labels CastError {
|
||||
typeswitch (o) {
|
||||
case (o: Smi): {
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright 2020 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/builtins/builtins-collections-gen.h"
|
||||
|
||||
namespace runtime {
|
||||
extern runtime
|
||||
@ -15,6 +16,9 @@ extern transitioning macro
|
||||
RemoveFinalizationRegistryCellFromUnregisterTokenMap(
|
||||
JSFinalizationRegistry, WeakCell): void;
|
||||
|
||||
extern macro WeakCollectionsBuiltinsAssembler::GotoIfCannotBeWeakKey(JSAny):
|
||||
void labels NotWeakKey;
|
||||
|
||||
macro SplitOffTail(weakCell: WeakCell): WeakCell|Undefined {
|
||||
const weakCellTail = weakCell.next;
|
||||
weakCell.next = Undefined;
|
||||
@ -125,6 +129,7 @@ FinalizationRegistryConstructor(
|
||||
return finalizationRegistry;
|
||||
}
|
||||
|
||||
// https://tc39.es/proposal-symbols-as-weakmap-keys/#sec-finalization-registry.prototype.register
|
||||
transitioning javascript builtin
|
||||
FinalizationRegistryRegister(
|
||||
js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
|
||||
@ -134,33 +139,32 @@ FinalizationRegistryRegister(
|
||||
ThrowTypeError(
|
||||
MessageTemplate::kIncompatibleMethodReceiver,
|
||||
'FinalizationRegistry.prototype.register', receiver);
|
||||
// 3. If Type(target) is not Object, throw a TypeError exception.
|
||||
const target = Cast<JSReceiver>(arguments[0]) otherwise ThrowTypeError(
|
||||
MessageTemplate::kWeakRefsRegisterTargetMustBeObject);
|
||||
// 3. If CanBeHeldWeakly(target) is false, throw a TypeError exception.
|
||||
GotoIfCannotBeWeakKey(arguments[0])
|
||||
otherwise ThrowTypeError(MessageTemplate::kInvalidWeakRefsRegisterTarget);
|
||||
|
||||
const target = UnsafeCast<(JSReceiver | Symbol)>(arguments[0]);
|
||||
const heldValue = arguments[1];
|
||||
// 4. If SameValue(target, heldValue), throw a TypeError exception.
|
||||
if (target == heldValue) {
|
||||
ThrowTypeError(
|
||||
MessageTemplate::kWeakRefsRegisterTargetAndHoldingsMustNotBeSame);
|
||||
}
|
||||
// 5. If Type(unregisterToken) is not Object,
|
||||
// 5. If CanBeHeldWeakly(unregisterToken) is false,
|
||||
// a. If unregisterToken is not undefined, throw a TypeError exception.
|
||||
// b. Set unregisterToken to empty.
|
||||
const unregisterTokenRaw = arguments[2];
|
||||
let unregisterToken: JSReceiver|Undefined;
|
||||
typeswitch (unregisterTokenRaw) {
|
||||
case (Undefined): {
|
||||
let unregisterToken: JSReceiver|Undefined|Symbol;
|
||||
|
||||
if (IsUndefined(unregisterTokenRaw)) {
|
||||
unregisterToken = Undefined;
|
||||
} else {
|
||||
GotoIfCannotBeWeakKey(unregisterTokenRaw)
|
||||
otherwise ThrowTypeError(
|
||||
MessageTemplate::kInvalidWeakRefsUnregisterToken, unregisterTokenRaw);
|
||||
unregisterToken = UnsafeCast<(JSReceiver | Symbol)>(unregisterTokenRaw);
|
||||
}
|
||||
case (unregisterTokenObj: JSReceiver): {
|
||||
unregisterToken = unregisterTokenObj;
|
||||
}
|
||||
case (JSAny): deferred {
|
||||
ThrowTypeError(
|
||||
MessageTemplate::kWeakRefsUnregisterTokenMustBeObject,
|
||||
unregisterTokenRaw);
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Let cell be the Record { [[WeakRefTarget]] : target, [[HeldValue]]:
|
||||
// heldValue, [[UnregisterToken]]: unregisterToken }.
|
||||
// Allocate the WeakCell object in the old space, because 1) WeakCell weakness
|
||||
|
@ -2,15 +2,18 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include 'src/builtins/builtins-collections-gen.h'
|
||||
|
||||
namespace runtime {
|
||||
|
||||
extern runtime JSWeakRefAddToKeptObjects(implicit context: Context)(JSReceiver):
|
||||
void;
|
||||
extern runtime JSWeakRefAddToKeptObjects(implicit context: Context)(
|
||||
JSReceiver | Symbol): void;
|
||||
|
||||
} // namespace runtime
|
||||
|
||||
namespace weakref {
|
||||
|
||||
// https://tc39.es/proposal-symbols-as-weakmap-keys/#sec-weak-ref-target
|
||||
transitioning javascript builtin
|
||||
WeakRefConstructor(
|
||||
js-implicit context: NativeContext, receiver: JSAny, newTarget: JSAny,
|
||||
@ -19,15 +22,17 @@ WeakRefConstructor(
|
||||
if (newTarget == Undefined) {
|
||||
ThrowTypeError(MessageTemplate::kConstructorNotFunction, 'WeakRef');
|
||||
}
|
||||
// 2. If Type(target) is not Object, throw a TypeError exception.
|
||||
const weakTarget = Cast<JSReceiver>(weakTarget) otherwise
|
||||
ThrowTypeError(
|
||||
MessageTemplate::kWeakRefsWeakRefConstructorTargetMustBeObject);
|
||||
|
||||
// 2. If CanBeHeldWeakly(weakTarget) is false, throw a TypeError exception.
|
||||
GotoIfCannotBeWeakKey(weakTarget) otherwise ThrowTypeError(
|
||||
MessageTemplate::kInvalidWeakRefsWeakRefConstructorTarget);
|
||||
|
||||
// 3. Let weakRef be ? OrdinaryCreateFromConstructor(NewTarget,
|
||||
// "%WeakRefPrototype%", « [[WeakRefTarget]] »).
|
||||
const map = GetDerivedMap(target, UnsafeCast<JSReceiver>(newTarget));
|
||||
const weakRef = UnsafeCast<JSWeakRef>(AllocateFastOrSlowJSObjectFromMap(map));
|
||||
// 4. Perfom ! AddToKeptObjects(target).
|
||||
const weakTarget = UnsafeCast<(JSReceiver | Symbol)>(weakTarget);
|
||||
runtime::JSWeakRefAddToKeptObjects(weakTarget);
|
||||
// 5. Set weakRef.[[WeakRefTarget]] to target.
|
||||
weakRef.target = weakTarget;
|
||||
@ -52,7 +57,8 @@ WeakRefDeref(js-implicit context: NativeContext, receiver: JSAny)(): JSAny {
|
||||
if (target != Undefined) {
|
||||
// JSWeakRefAddToKeptObjects might allocate and cause a GC, but it
|
||||
// won't clear `target` since we hold it here on the stack.
|
||||
runtime::JSWeakRefAddToKeptObjects(UnsafeCast<JSReceiver>(target));
|
||||
runtime::JSWeakRefAddToKeptObjects(
|
||||
UnsafeCast<(JSReceiver | Symbol)>(target));
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
@ -2728,6 +2728,12 @@ class V8_EXPORT_PRIVATE CodeStubAssembler
|
||||
ExternalReference::address_of_shared_string_table_flag());
|
||||
}
|
||||
|
||||
TNode<BoolT> HasHarmonySymbolAsWeakmapKeyFlag() {
|
||||
return LoadRuntimeFlag(
|
||||
ExternalReference::
|
||||
address_of_FLAG_harmony_symbol_as_weakmap_key());
|
||||
}
|
||||
|
||||
// True iff |object| is a Smi or a HeapNumber or a BigInt.
|
||||
TNode<BoolT> IsNumeric(TNode<Object> object);
|
||||
|
||||
|
@ -585,6 +585,11 @@ ExternalReference ExternalReference::address_of_log_or_trace_osr() {
|
||||
return ExternalReference(&v8_flags.log_or_trace_osr);
|
||||
}
|
||||
|
||||
ExternalReference
|
||||
ExternalReference::address_of_FLAG_harmony_symbol_as_weakmap_key() {
|
||||
return ExternalReference(&FLAG_harmony_symbol_as_weakmap_key);
|
||||
}
|
||||
|
||||
ExternalReference ExternalReference::address_of_builtin_subclassing_flag() {
|
||||
return ExternalReference(&v8_flags.builtin_subclassing);
|
||||
}
|
||||
|
@ -98,7 +98,9 @@ class StatsCounter;
|
||||
V(abort_with_reason, "abort_with_reason") \
|
||||
V(address_of_log_or_trace_osr, "v8_flags.log_or_trace_osr") \
|
||||
V(address_of_FLAG_harmony_regexp_unicode_sets, \
|
||||
"v8_flags.harmony_regexp_unicdoe_sets") \
|
||||
"v8_flags.harmony_regexp_unicode_sets") \
|
||||
V(address_of_FLAG_harmony_symbol_as_weakmap_key, \
|
||||
"v8_flags.harmony_symbol_as_weakmap_key") \
|
||||
V(address_of_builtin_subclassing_flag, "v8_flags.builtin_subclassing") \
|
||||
V(address_of_double_abs_constant, "double_absolute_constant") \
|
||||
V(address_of_double_neg_constant, "double_negate_constant") \
|
||||
|
@ -686,17 +686,15 @@ namespace internal {
|
||||
T(TraceEventPhaseError, "Trace event phase must be a number.") \
|
||||
T(TraceEventIDError, "Trace event id must be a number.") \
|
||||
/* Weak refs */ \
|
||||
T(WeakRefsUnregisterTokenMustBeObject, \
|
||||
"unregisterToken ('%') must be an object") \
|
||||
T(InvalidWeakRefsUnregisterToken, "Invalid unregisterToken ('%')") \
|
||||
T(WeakRefsCleanupMustBeCallable, \
|
||||
"FinalizationRegistry: cleanup must be callable") \
|
||||
T(WeakRefsRegisterTargetMustBeObject, \
|
||||
"FinalizationRegistry.prototype.register: target must be an object") \
|
||||
T(InvalidWeakRefsRegisterTarget, \
|
||||
"FinalizationRegistry.prototype.register: invalid target") \
|
||||
T(WeakRefsRegisterTargetAndHoldingsMustNotBeSame, \
|
||||
"FinalizationRegistry.prototype.register: target and holdings must not " \
|
||||
"be same") \
|
||||
T(WeakRefsWeakRefConstructorTargetMustBeObject, \
|
||||
"WeakRef: target must be an object") \
|
||||
T(InvalidWeakRefsWeakRefConstructorTarget, "WeakRef: invalid target") \
|
||||
T(OptionalChainingNoNew, "Invalid optional chain from new expression") \
|
||||
T(OptionalChainingNoSuper, "Invalid optional chain from super property") \
|
||||
T(OptionalChainingNoTemplate, "Invalid tagged template on optional chain") \
|
||||
|
@ -1299,7 +1299,9 @@ void JSSharedArray::JSSharedArrayVerify(Isolate* isolate) {
|
||||
void WeakCell::WeakCellVerify(Isolate* isolate) {
|
||||
CHECK(IsWeakCell());
|
||||
|
||||
CHECK(target().IsJSReceiver() || target().IsUndefined(isolate));
|
||||
CHECK(target().IsJSReceiver() || target().IsUndefined(isolate) ||
|
||||
(target().IsSymbol() &&
|
||||
!Symbol::cast(target()).is_in_public_symbol_table()));
|
||||
|
||||
CHECK(prev().IsWeakCell() || prev().IsUndefined(isolate));
|
||||
if (prev().IsWeakCell()) {
|
||||
@ -1327,7 +1329,9 @@ void WeakCell::WeakCellVerify(Isolate* isolate) {
|
||||
void JSWeakRef::JSWeakRefVerify(Isolate* isolate) {
|
||||
CHECK(IsJSWeakRef());
|
||||
JSObjectVerify(isolate);
|
||||
CHECK(target().IsUndefined(isolate) || target().IsJSReceiver());
|
||||
CHECK(target().IsUndefined(isolate) || target().IsJSReceiver() ||
|
||||
(target().IsSymbol() &&
|
||||
!Symbol::cast(target()).is_in_public_symbol_table()));
|
||||
}
|
||||
|
||||
void JSFinalizationRegistry::JSFinalizationRegistryVerify(Isolate* isolate) {
|
||||
|
@ -230,7 +230,8 @@ DEFINE_BOOL(harmony_shipping, true, "enable all shipped harmony features")
|
||||
V(harmony_shadow_realm, "harmony ShadowRealm") \
|
||||
V(harmony_struct, "harmony structs, shared structs, and shared arrays") \
|
||||
V(harmony_change_array_by_copy, "harmony change-Array-by-copy") \
|
||||
V(harmony_regexp_unicode_sets, "harmony RegExp Unicode Sets")
|
||||
V(harmony_regexp_unicode_sets, "harmony RegExp Unicode Sets") \
|
||||
V(harmony_symbol_as_weakmap_key, "harmony symbols as weakmap keys")
|
||||
|
||||
#ifdef V8_INTL_SUPPORT
|
||||
#define HARMONY_INPROGRESS(V) \
|
||||
|
@ -6652,7 +6652,7 @@ void Heap::RemoveDirtyFinalizationRegistriesOnContext(NativeContext context) {
|
||||
set_dirty_js_finalization_registries_list_tail(prev);
|
||||
}
|
||||
|
||||
void Heap::KeepDuringJob(Handle<JSReceiver> target) {
|
||||
void Heap::KeepDuringJob(Handle<HeapObject> target) {
|
||||
DCHECK(weak_refs_keep_during_job().IsUndefined() ||
|
||||
weak_refs_keep_during_job().IsOrderedHashSet());
|
||||
Handle<OrderedHashSet> table;
|
||||
|
@ -998,7 +998,7 @@ class Heap {
|
||||
return is_finalization_registry_cleanup_task_posted_;
|
||||
}
|
||||
|
||||
V8_EXPORT_PRIVATE void KeepDuringJob(Handle<JSReceiver> target);
|
||||
V8_EXPORT_PRIVATE void KeepDuringJob(Handle<HeapObject> target);
|
||||
void ClearKeptObjects();
|
||||
|
||||
// ===========================================================================
|
||||
|
@ -4506,6 +4506,7 @@ void Genesis::InitializeConsole(Handle<JSObject> extras_binding) {
|
||||
|
||||
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_import_assertions)
|
||||
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_class_static_blocks)
|
||||
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_symbol_as_weakmap_key)
|
||||
|
||||
#ifdef V8_INTL_SUPPORT
|
||||
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_intl_best_fit_matcher)
|
||||
|
@ -5,10 +5,9 @@
|
||||
#ifndef V8_OBJECTS_JS_WEAK_REFS_INL_H_
|
||||
#define V8_OBJECTS_JS_WEAK_REFS_INL_H_
|
||||
|
||||
#include "src/objects/js-weak-refs.h"
|
||||
|
||||
#include "src/api/api-inl.h"
|
||||
#include "src/heap/heap-write-barrier-inl.h"
|
||||
#include "src/objects/js-weak-refs.h"
|
||||
#include "src/objects/smi-inl.h"
|
||||
|
||||
// Has to be the last include (doesn't have include guards):
|
||||
@ -55,7 +54,7 @@ void JSFinalizationRegistry::RegisterWeakCellWithUnregisterToken(
|
||||
|
||||
bool JSFinalizationRegistry::Unregister(
|
||||
Handle<JSFinalizationRegistry> finalization_registry,
|
||||
Handle<JSReceiver> unregister_token, Isolate* isolate) {
|
||||
Handle<HeapObject> unregister_token, Isolate* isolate) {
|
||||
// Iterate through the doubly linked list of WeakCells associated with the
|
||||
// key. Each WeakCell will be in the "active_cells" or "cleared_cells" list of
|
||||
// its FinalizationRegistry; remove it from there.
|
||||
@ -66,7 +65,7 @@ bool JSFinalizationRegistry::Unregister(
|
||||
|
||||
template <typename GCNotifyUpdatedSlotCallback>
|
||||
bool JSFinalizationRegistry::RemoveUnregisterToken(
|
||||
JSReceiver unregister_token, Isolate* isolate,
|
||||
HeapObject unregister_token, Isolate* isolate,
|
||||
RemoveUnregisterTokenMode removal_mode,
|
||||
GCNotifyUpdatedSlotCallback gc_notify_updated_slot) {
|
||||
// This method is called from both FinalizationRegistry#unregister and for
|
||||
@ -171,7 +170,9 @@ void WeakCell::Nullify(Isolate* isolate,
|
||||
// only called for WeakCells which haven't been unregistered yet, so they will
|
||||
// be in the active_cells list. (The caller must guard against calling this
|
||||
// for unregistered WeakCells by checking that the target is not undefined.)
|
||||
DCHECK(target().IsJSReceiver());
|
||||
DCHECK(target().IsJSReceiver() ||
|
||||
(target().IsSymbol() &&
|
||||
!Symbol::cast(target()).is_in_public_symbol_table()));
|
||||
set_target(ReadOnlyRoots(isolate).undefined_value());
|
||||
|
||||
JSFinalizationRegistry fr =
|
||||
|
@ -37,7 +37,7 @@ class JSFinalizationRegistry
|
||||
Handle<WeakCell> weak_cell, Isolate* isolate);
|
||||
inline static bool Unregister(
|
||||
Handle<JSFinalizationRegistry> finalization_registry,
|
||||
Handle<JSReceiver> unregister_token, Isolate* isolate);
|
||||
Handle<HeapObject> unregister_token, Isolate* isolate);
|
||||
|
||||
// RemoveUnregisterToken is called from both Unregister and during GC. Since
|
||||
// it modifies slots in key_map and WeakCells and the normal write barrier is
|
||||
@ -49,7 +49,7 @@ class JSFinalizationRegistry
|
||||
};
|
||||
template <typename GCNotifyUpdatedSlotCallback>
|
||||
inline bool RemoveUnregisterToken(
|
||||
JSReceiver unregister_token, Isolate* isolate,
|
||||
HeapObject unregister_token, Isolate* isolate,
|
||||
RemoveUnregisterTokenMode removal_mode,
|
||||
GCNotifyUpdatedSlotCallback gc_notify_updated_slot);
|
||||
|
||||
|
@ -20,8 +20,8 @@ extern class JSFinalizationRegistry extends JSObject {
|
||||
|
||||
extern class WeakCell extends HeapObject {
|
||||
finalization_registry: Undefined|JSFinalizationRegistry;
|
||||
target: Undefined|JSReceiver;
|
||||
unregister_token: Undefined|JSReceiver;
|
||||
target: Undefined|JSReceiver|Symbol;
|
||||
unregister_token: Undefined|JSReceiver|Symbol;
|
||||
holdings: JSAny;
|
||||
|
||||
// For storing doubly linked lists of WeakCells in JSFinalizationRegistry's
|
||||
@ -40,5 +40,5 @@ extern class WeakCell extends HeapObject {
|
||||
}
|
||||
|
||||
extern class JSWeakRef extends JSObject {
|
||||
target: Undefined|JSReceiver;
|
||||
target: Undefined|JSReceiver|Symbol;
|
||||
}
|
||||
|
@ -2,10 +2,9 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "src/runtime/runtime-utils.h"
|
||||
|
||||
#include "src/execution/arguments-inl.h"
|
||||
#include "src/objects/js-weak-refs-inl.h"
|
||||
#include "src/runtime/runtime-utils.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
@ -44,7 +43,14 @@ RUNTIME_FUNCTION(
|
||||
RUNTIME_FUNCTION(Runtime_JSWeakRefAddToKeptObjects) {
|
||||
HandleScope scope(isolate);
|
||||
DCHECK_EQ(1, args.length());
|
||||
Handle<JSReceiver> object = args.at<JSReceiver>(0);
|
||||
Handle<HeapObject> object = args.at<HeapObject>(0);
|
||||
if (FLAG_harmony_symbol_as_weakmap_key) {
|
||||
DCHECK(object->IsJSReceiver() ||
|
||||
(object->IsSymbol() &&
|
||||
!(Handle<Symbol>::cast(object))->is_in_public_symbol_table()));
|
||||
} else {
|
||||
DCHECK(object->IsJSReceiver());
|
||||
}
|
||||
|
||||
isolate->heap()->KeepDuringJob(object);
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
*%(basename)s:*: TypeError: FinalizationRegistry.prototype.register: target must be an object
|
||||
*%(basename)s:*: TypeError: FinalizationRegistry.prototype.register: invalid target
|
||||
fg.register(1);
|
||||
^
|
||||
TypeError: FinalizationRegistry.prototype.register: target must be an object
|
||||
TypeError: FinalizationRegistry.prototype.register: invalid target
|
||||
at FinalizationRegistry.register (<anonymous>)
|
||||
at *%(basename)s:*:4
|
||||
|
@ -1,6 +1,6 @@
|
||||
*%(basename)s:*: TypeError: unregisterToken ('1') must be an object
|
||||
*%(basename)s:*: TypeError: Invalid unregisterToken ('1')
|
||||
fg.unregister(1);
|
||||
^
|
||||
TypeError: unregisterToken ('1') must be an object
|
||||
TypeError: Invalid unregisterToken ('1')
|
||||
at FinalizationRegistry.unregister (<anonymous>)
|
||||
at *%(basename)s:*:4
|
||||
|
99
test/mjsunit/harmony/symbol-as-weakmap-key.js
Normal file
99
test/mjsunit/harmony/symbol-as-weakmap-key.js
Normal file
@ -0,0 +1,99 @@
|
||||
// Copyright 2022 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: --harmony-symbol-as-weakmap-key --expose-gc
|
||||
|
||||
(function TestWeakMapWithNonRegisteredSymbolKey() {
|
||||
const key = Symbol('123');
|
||||
const value = 1;
|
||||
const map = new WeakMap();
|
||||
assertFalse(map.has(key));
|
||||
assertSame(undefined, map.get(key));
|
||||
assertFalse(map.delete(key));
|
||||
assertSame(map, map.set(key, value));
|
||||
assertSame(value, map.get(key));
|
||||
assertTrue(map.has(key));
|
||||
assertTrue(map.delete(key));
|
||||
assertFalse(map.has(key));
|
||||
assertSame(undefined, map.get(key));
|
||||
assertFalse(map.delete(key));
|
||||
assertFalse(map.has(key));
|
||||
assertSame(undefined, map.get(key));
|
||||
})();
|
||||
|
||||
(function TestWeakMapWithNonRegisteredSymbolKeyGC() {
|
||||
const map = new WeakMap();
|
||||
|
||||
const outerKey = Symbol('234');
|
||||
const outerValue = 1;
|
||||
map.set(outerKey, outerValue);
|
||||
{
|
||||
const innerKey = Symbol('123');
|
||||
const innerValue = 1;
|
||||
map.set(innerKey, innerValue);
|
||||
assertTrue(map.has(innerKey));
|
||||
assertSame(innerValue, map.get(innerKey));
|
||||
}
|
||||
gc();
|
||||
assertTrue(map.has(outerKey));
|
||||
assertSame(outerValue, map.get(outerKey));
|
||||
})();
|
||||
|
||||
(function TestWeakMapWithRegisteredSymbolKey() {
|
||||
const key = Symbol.for('123');
|
||||
const value = 1;
|
||||
const map = new WeakMap();
|
||||
assertFalse(map.has(key));
|
||||
assertSame(undefined, map.get(key));
|
||||
assertFalse(map.delete(key));
|
||||
assertThrows(() => {
|
||||
map.set(key, value);
|
||||
}, TypeError, 'Invalid value used as weak map key');
|
||||
assertFalse(map.has(key));
|
||||
assertSame(undefined, map.get(key));
|
||||
assertFalse(map.delete(key));
|
||||
assertFalse(map.has(key));
|
||||
assertSame(undefined, map.get(key));
|
||||
})();
|
||||
|
||||
(function TestWeakSetWithNonRegisteredSymbolKey() {
|
||||
const key = Symbol('123');
|
||||
const set = new WeakSet();
|
||||
assertFalse(set.has(key));
|
||||
assertFalse(set.delete(key));
|
||||
assertSame(set, set.add(key));
|
||||
assertTrue(set.has(key));
|
||||
assertTrue(set.delete(key));
|
||||
assertFalse(set.has(key));
|
||||
assertFalse(set.delete(key));
|
||||
assertFalse(set.has(key));
|
||||
})();
|
||||
|
||||
(function TestWeakSetWithNonRegisteredSymbolKeyGC() {
|
||||
const set = new WeakSet();
|
||||
const outerKey = Symbol('234');
|
||||
set.add(outerKey);
|
||||
{
|
||||
const innerKey = Symbol('123');
|
||||
set.add(innerKey);
|
||||
assertTrue(set.has(innerKey));
|
||||
}
|
||||
gc();
|
||||
assertTrue(set.has(outerKey));
|
||||
})();
|
||||
|
||||
(function TestWeakSetWithRegisteredSymbolKey() {
|
||||
const key = Symbol.for('123');
|
||||
const set = new WeakSet();
|
||||
assertFalse(set.has(key));
|
||||
assertFalse(set.delete(key));
|
||||
|
||||
assertThrows(() => {
|
||||
assertSame(set, set.add(key));
|
||||
}, TypeError, 'Invalid value used in weak set');
|
||||
|
||||
assertFalse(set.has(key));
|
||||
assertFalse(set.delete(key));
|
||||
assertFalse(set.has(key));
|
||||
})();
|
@ -47,7 +47,7 @@
|
||||
|
||||
(function TestRegisterWithNonObjectTarget() {
|
||||
let fg = new FinalizationRegistry(() => {});
|
||||
let message = "FinalizationRegistry.prototype.register: target must be an object";
|
||||
let message = "FinalizationRegistry.prototype.register: invalid target";
|
||||
assertThrows(() => fg.register(1, "holdings"), TypeError, message);
|
||||
assertThrows(() => fg.register(false, "holdings"), TypeError, message);
|
||||
assertThrows(() => fg.register("foo", "holdings"), TypeError, message);
|
||||
@ -116,7 +116,7 @@
|
||||
})();
|
||||
|
||||
(function TestWeakRefConstructorWithNonObject() {
|
||||
let message = "WeakRef: target must be an object";
|
||||
let message = "WeakRef: invalid target";
|
||||
assertThrows(() => new WeakRef(), TypeError, message);
|
||||
assertThrows(() => new WeakRef(1), TypeError, message);
|
||||
assertThrows(() => new WeakRef(false), TypeError, message);
|
||||
|
39
test/mjsunit/harmony/weakrefs/symbol-as-weakref-target-gc.js
Normal file
39
test/mjsunit/harmony/weakrefs/symbol-as-weakref-target-gc.js
Normal file
@ -0,0 +1,39 @@
|
||||
// Copyright 2022 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: --harmony-symbol-as-weakmap-key --expose-gc --noincremental-marking
|
||||
|
||||
(function TestWeakRefWithSymbolGC() {
|
||||
let weakRef;
|
||||
{
|
||||
const innerKey = Symbol('123');
|
||||
weakRef = new WeakRef(innerKey);
|
||||
}
|
||||
// Since the WeakRef was created during this turn, it is not cleared by GC.
|
||||
gc();
|
||||
assertNotEquals(undefined, weakRef.deref());
|
||||
// Next task.
|
||||
setTimeout(() => {
|
||||
gc();
|
||||
assertEquals(undefined, weakRef.deref());
|
||||
}, 0);
|
||||
})();
|
||||
|
||||
(function TestFinalizationRegistryWithSymbolGC() {
|
||||
let cleanUpCalled = false;
|
||||
const fg = new FinalizationRegistry((target) => {
|
||||
assertEquals('123', target);
|
||||
cleanUpCalled = true;
|
||||
});
|
||||
(function () {
|
||||
const innerKey = Symbol('123');
|
||||
fg.register(innerKey, '123');
|
||||
})();
|
||||
gc();
|
||||
assertFalse(cleanUpCalled);
|
||||
// Check that cleanup callback was called in a follow up task.
|
||||
setTimeout(() => {
|
||||
assertTrue(cleanUpCalled);
|
||||
}, 0);
|
||||
})();
|
41
test/mjsunit/harmony/weakrefs/symbol-as-weakref-target.js
Normal file
41
test/mjsunit/harmony/weakrefs/symbol-as-weakref-target.js
Normal file
@ -0,0 +1,41 @@
|
||||
// Copyright 2022 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: --harmony-symbol-as-weakmap-key
|
||||
|
||||
(function TestRegisterWithSymbolTarget() {
|
||||
const fg = new FinalizationRegistry(() => { });
|
||||
fg.register(Symbol('123'), 'holdings');
|
||||
// Registered symbols cannot be the target.
|
||||
assertThrows(() => fg.register(Symbol.for('123'), 'holdings'), TypeError);
|
||||
})();
|
||||
|
||||
(function TestRegisterWithSymbolUnregisterToken() {
|
||||
const fg = new FinalizationRegistry(() => { });
|
||||
fg.register({}, 'holdings', Symbol('123'));
|
||||
// Registered symbols cannot be the unregister token.
|
||||
assertThrows(() => fg.register({}, 'holdings', Symbol.for('123')), TypeError);
|
||||
})();
|
||||
|
||||
(function TestRegisterSymbolAndHoldingsSameValue() {
|
||||
const fg = new FinalizationRegistry(() => {});
|
||||
const obj = Symbol('123');
|
||||
// SameValue(target, holdings) not ok.
|
||||
assertThrows(() => fg.register(obj, obj), TypeError);
|
||||
const holdings = {a: 1};
|
||||
fg.register(obj, holdings);
|
||||
})();
|
||||
|
||||
(function TestUnregisterWithSymbolUnregisterToken() {
|
||||
const fg = new FinalizationRegistry(() => {});
|
||||
fg.unregister(Symbol('123'));
|
||||
// Registered symbols cannot be the unregister token.
|
||||
assertThrows(() => fg.unregister(Symbol.for('123')), TypeError);
|
||||
})();
|
||||
|
||||
(function TestWeakRefConstructorWithSymbol() {
|
||||
new WeakRef(Symbol('123'));
|
||||
// Registered symbols cannot be the WeakRef target.
|
||||
assertThrows(() => new WeakRef(Symbol.for('123')), TypeError);
|
||||
})();
|
Loading…
Reference in New Issue
Block a user