blob: 8b1bb6eaec4d97e9aa02ef31e4e42f2340388d0b [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/objects/js-weak-refs.h"
#include "src/api/api-inl.h"
#include "src/heap/heap-write-barrier-inl.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 {
TQ_OBJECT_CONSTRUCTORS_IMPL(WeakCell)
TQ_OBJECT_CONSTRUCTORS_IMPL(JSWeakRef)
OBJECT_CONSTRUCTORS_IMPL(JSFinalizationRegistry, JSObject)
ACCESSORS(JSFinalizationRegistry, native_context, NativeContext,
kNativeContextOffset)
ACCESSORS(JSFinalizationRegistry, cleanup, Object, kCleanupOffset)
ACCESSORS(JSFinalizationRegistry, active_cells, HeapObject, kActiveCellsOffset)
ACCESSORS(JSFinalizationRegistry, cleared_cells, HeapObject,
kClearedCellsOffset)
ACCESSORS(JSFinalizationRegistry, key_map, Object, kKeyMapOffset)
SMI_ACCESSORS(JSFinalizationRegistry, flags, kFlagsOffset)
ACCESSORS(JSFinalizationRegistry, next_dirty, Object, kNextDirtyOffset)
CAST_ACCESSOR(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 (finalization_registry->key_map().IsUndefined(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 = weak_cell->unregister_token().GetOrCreateHash(isolate).value();
InternalIndex entry = key_map->FindEntry(isolate, key);
if (entry.is_found()) {
Object value = key_map->ValueAt(entry);
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<JSReceiver> 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,
[isolate](WeakCell matched_cell) {
matched_cell.RemoveFromFinalizationRegistryCells(isolate);
},
[](HeapObject, ObjectSlot, Object) {});
}
template <typename MatchCallback, typename GCNotifyUpdatedSlotCallback>
bool JSFinalizationRegistry::RemoveUnregisterToken(
JSReceiver unregister_token, Isolate* isolate, MatchCallback match_callback,
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.
DisallowHeapAllocation no_gc;
if (key_map().IsUndefined(isolate)) {
return false;
}
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.
Object hash = unregister_token.GetHash();
if (hash.IsUndefined(isolate)) {
return false;
}
uint32_t key = Smi::ToInt(hash);
InternalIndex entry = key_map.FindEntry(isolate, key);
if (entry.is_not_found()) {
return false;
}
Object value = key_map.ValueAt(entry);
bool was_present = false;
HeapObject undefined = ReadOnlyRoots(isolate).undefined_value();
HeapObject new_key_list_head = undefined;
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 (!value.IsUndefined(isolate)) {
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.
match_callback(weak_cell);
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 (new_key_list_prev.IsUndefined(isolate)) {
new_key_list_head = weak_cell;
} else {
DCHECK(new_key_list_head.IsWeakCell());
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 (new_key_list_head.IsUndefined(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 cleared_cells().IsWeakCell();
}
HeapObject WeakCell::relaxed_target() const {
return TaggedField<HeapObject>::Relaxed_Load(*this, kTargetOffset);
}
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(target().IsJSReceiver());
set_target(ReadOnlyRoots(isolate).undefined_value());
JSFinalizationRegistry fr =
JSFinalizationRegistry::cast(finalization_registry());
if (prev().IsWeakCell()) {
DCHECK_NE(fr.active_cells(), *this);
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 (next().IsWeakCell()) {
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());
Object cleared_head = fr.cleared_cells();
if (cleared_head.IsWeakCell()) {
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(target().IsUndefined() || target().IsJSReceiver());
set_target(ReadOnlyRoots(isolate).undefined_value());
JSFinalizationRegistry fr =
JSFinalizationRegistry::cast(finalization_registry());
if (fr.active_cells() == *this) {
DCHECK(prev().IsUndefined(isolate));
fr.set_active_cells(next());
} else if (fr.cleared_cells() == *this) {
DCHECK(!prev().IsWeakCell());
fr.set_cleared_cells(next());
} else {
DCHECK(prev().IsWeakCell());
WeakCell prev_cell = WeakCell::cast(prev());
prev_cell.set_next(next());
}
if (next().IsWeakCell()) {
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_