blob: efb2ed15be19a52ee61cabb480f5fbe382775100 [file] [log] [blame]
// Copyright 2018 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef V8_OBJECTS_JS_WEAK_REFS_INL_H_
#define V8_OBJECTS_JS_WEAK_REFS_INL_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):
#include "src/objects/object-macros.h"
namespace v8 {
namespace internal {
#include "torque-generated/src/objects/js-weak-refs-tq-inl.inc"
TQ_OBJECT_CONSTRUCTORS_IMPL(WeakCell)
TQ_OBJECT_CONSTRUCTORS_IMPL(JSWeakRef)
TQ_OBJECT_CONSTRUCTORS_IMPL(JSFinalizationRegistry)
BIT_FIELD_ACCESSORS(JSFinalizationRegistry, flags, scheduled_for_cleanup,
JSFinalizationRegistry::ScheduledForCleanupBit)
void JSFinalizationRegistry::RegisterWeakCellWithUnregisterToken(
Handle<JSFinalizationRegistry> finalization_registry,
Handle<WeakCell> weak_cell, Isolate* isolate) {
Handle<SimpleNumberDictionary> key_map;
if (IsUndefined(finalization_registry->key_map(), isolate)) {
key_map = SimpleNumberDictionary::New(isolate, 1);
} else {
key_map =
handle(SimpleNumberDictionary::cast(finalization_registry->key_map()),
isolate);
}
// Unregister tokens are held weakly as objects are often their own
// unregister token. To avoid using an ephemeron map, the map for token
// lookup is keyed on the token's identity hash instead of the token itself.
uint32_t key =
Object::GetOrCreateHash(weak_cell->unregister_token(), isolate).value();
InternalIndex entry = key_map->FindEntry(isolate, key);
if (entry.is_found()) {
Tagged<Object> value = key_map->ValueAt(entry);
Tagged<WeakCell> existing_weak_cell = WeakCell::cast(value);
existing_weak_cell->set_key_list_prev(*weak_cell);
weak_cell->set_key_list_next(existing_weak_cell);
}
key_map = SimpleNumberDictionary::Set(isolate, key_map, key, weak_cell);
finalization_registry->set_key_map(*key_map);
}
bool JSFinalizationRegistry::Unregister(
Handle<JSFinalizationRegistry> finalization_registry,
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.
return finalization_registry->RemoveUnregisterToken(
*unregister_token, isolate, kRemoveMatchedCellsFromRegistry,
[](Tagged<HeapObject>, ObjectSlot, Tagged<Object>) {});
}
template <typename GCNotifyUpdatedSlotCallback>
bool JSFinalizationRegistry::RemoveUnregisterToken(
Tagged<HeapObject> unregister_token, Isolate* isolate,
RemoveUnregisterTokenMode removal_mode,
GCNotifyUpdatedSlotCallback gc_notify_updated_slot) {
// This method is called from both FinalizationRegistry#unregister and for
// removing weakly-held dead unregister tokens. The latter is during GC so
// this function cannot GC.
DisallowGarbageCollection no_gc;
if (IsUndefined(key_map(), isolate)) {
return false;
}
Tagged<SimpleNumberDictionary> key_map =
SimpleNumberDictionary::cast(this->key_map());
// If the token doesn't have a hash, it was not used as a key inside any hash
// tables.
Tagged<Object> hash = Object::GetHash(unregister_token);
if (IsUndefined(hash, isolate)) {
return false;
}
uint32_t key = Smi::ToInt(hash);
InternalIndex entry = key_map->FindEntry(isolate, key);
if (entry.is_not_found()) {
return false;
}
Tagged<Object> value = key_map->ValueAt(entry);
bool was_present = false;
Tagged<HeapObject> undefined = ReadOnlyRoots(isolate).undefined_value();
Tagged<HeapObject> new_key_list_head = undefined;
Tagged<HeapObject> new_key_list_prev = undefined;
// Compute a new key list that doesn't have unregister_token. Because
// unregister tokens are held weakly, key_map is keyed using the tokens'
// identity hashes, and identity hashes may collide.
while (!IsUndefined(value, isolate)) {
Tagged<WeakCell> weak_cell = WeakCell::cast(value);
DCHECK(!ObjectInYoungGeneration(weak_cell));
value = weak_cell->key_list_next();
if (weak_cell->unregister_token() == unregister_token) {
// weak_cell has the same unregister token; remove it from the key list.
switch (removal_mode) {
case kRemoveMatchedCellsFromRegistry:
weak_cell->RemoveFromFinalizationRegistryCells(isolate);
break;
case kKeepMatchedCellsInRegistry:
// Do nothing.
break;
}
// Clear unregister token-related fields.
weak_cell->set_unregister_token(undefined);
weak_cell->set_key_list_prev(undefined);
weak_cell->set_key_list_next(undefined);
was_present = true;
} else {
// weak_cell has a different unregister token with the same key (hash
// collision); fix up the list.
weak_cell->set_key_list_prev(new_key_list_prev);
gc_notify_updated_slot(weak_cell,
weak_cell->RawField(WeakCell::kKeyListPrevOffset),
new_key_list_prev);
weak_cell->set_key_list_next(undefined);
if (IsUndefined(new_key_list_prev, isolate)) {
new_key_list_head = weak_cell;
} else {
DCHECK(IsWeakCell(new_key_list_head));
Tagged<WeakCell> prev_cell = WeakCell::cast(new_key_list_prev);
prev_cell->set_key_list_next(weak_cell);
gc_notify_updated_slot(
prev_cell, prev_cell->RawField(WeakCell::kKeyListNextOffset),
weak_cell);
}
new_key_list_prev = weak_cell;
}
}
if (IsUndefined(new_key_list_head, isolate)) {
DCHECK(was_present);
key_map->ClearEntry(entry);
key_map->ElementRemoved();
} else {
key_map->ValueAtPut(entry, new_key_list_head);
gc_notify_updated_slot(key_map, key_map->RawFieldOfValueAt(entry),
new_key_list_head);
}
return was_present;
}
bool JSFinalizationRegistry::NeedsCleanup() const {
return IsWeakCell(cleared_cells());
}
Tagged<HeapObject> WeakCell::relaxed_target() const {
return TaggedField<HeapObject>::Relaxed_Load(*this, kTargetOffset);
}
Tagged<HeapObject> WeakCell::relaxed_unregister_token() const {
return TaggedField<HeapObject>::Relaxed_Load(*this, kUnregisterTokenOffset);
}
template <typename GCNotifyUpdatedSlotCallback>
void WeakCell::Nullify(Isolate* isolate,
GCNotifyUpdatedSlotCallback gc_notify_updated_slot) {
// Remove from the WeakCell from the "active_cells" list of its
// JSFinalizationRegistry and insert it into the "cleared_cells" list. This is
// 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(Object::CanBeHeldWeakly(target()));
set_target(ReadOnlyRoots(isolate).undefined_value());
Tagged<JSFinalizationRegistry> fr =
JSFinalizationRegistry::cast(finalization_registry());
if (IsWeakCell(prev())) {
DCHECK_NE(fr->active_cells(), *this);
Tagged<WeakCell> prev_cell = WeakCell::cast(prev());
prev_cell->set_next(next());
gc_notify_updated_slot(prev_cell,
prev_cell->RawField(WeakCell::kNextOffset), next());
} else {
DCHECK_EQ(fr->active_cells(), *this);
fr->set_active_cells(next());
gc_notify_updated_slot(
fr, fr->RawField(JSFinalizationRegistry::kActiveCellsOffset), next());
}
if (IsWeakCell(next())) {
Tagged<WeakCell> next_cell = WeakCell::cast(next());
next_cell->set_prev(prev());
gc_notify_updated_slot(next_cell,
next_cell->RawField(WeakCell::kPrevOffset), prev());
}
set_prev(ReadOnlyRoots(isolate).undefined_value());
Tagged<Object> cleared_head = fr->cleared_cells();
if (IsWeakCell(cleared_head)) {
Tagged<WeakCell> cleared_head_cell = WeakCell::cast(cleared_head);
cleared_head_cell->set_prev(*this);
gc_notify_updated_slot(cleared_head_cell,
cleared_head_cell->RawField(WeakCell::kPrevOffset),
*this);
}
set_next(fr->cleared_cells());
gc_notify_updated_slot(*this, RawField(WeakCell::kNextOffset), next());
fr->set_cleared_cells(*this);
gc_notify_updated_slot(
fr, fr->RawField(JSFinalizationRegistry::kClearedCellsOffset), *this);
}
void WeakCell::RemoveFromFinalizationRegistryCells(Isolate* isolate) {
// Remove the WeakCell from the list it's in (either "active_cells" or
// "cleared_cells" of its JSFinalizationRegistry).
// It's important to set_target to undefined here. This guards that we won't
// call Nullify (which assumes that the WeakCell is in active_cells).
DCHECK(IsUndefined(target()) || Object::CanBeHeldWeakly(target()));
set_target(ReadOnlyRoots(isolate).undefined_value());
Tagged<JSFinalizationRegistry> fr =
JSFinalizationRegistry::cast(finalization_registry());
if (fr->active_cells() == *this) {
DCHECK(IsUndefined(prev(), isolate));
fr->set_active_cells(next());
} else if (fr->cleared_cells() == *this) {
DCHECK(!IsWeakCell(prev()));
fr->set_cleared_cells(next());
} else {
DCHECK(IsWeakCell(prev()));
Tagged<WeakCell> prev_cell = WeakCell::cast(prev());
prev_cell->set_next(next());
}
if (IsWeakCell(next())) {
Tagged<WeakCell> next_cell = WeakCell::cast(next());
next_cell->set_prev(prev());
}
set_prev(ReadOnlyRoots(isolate).undefined_value());
set_next(ReadOnlyRoots(isolate).undefined_value());
}
} // namespace internal
} // namespace v8
#include "src/objects/object-macros-undef.h"
#endif // V8_OBJECTS_JS_WEAK_REFS_INL_H_