1// Copyright 2018 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#ifndef V8_OBJECTS_JS_WEAK_REFS_INL_H_
6#define V8_OBJECTS_JS_WEAK_REFS_INL_H_
7
8#include "src/api/api-inl.h"
9#include "src/heap/heap-write-barrier-inl.h"
10#include "src/objects/js-weak-refs.h"
11#include "src/objects/smi-inl.h"
12
13// Has to be the last include (doesn't have include guards):
14#include "src/objects/object-macros.h"
15
16namespace v8 {
17namespace internal {
18
19#include "torque-generated/src/objects/js-weak-refs-tq-inl.inc"
20
21TQ_OBJECT_CONSTRUCTORS_IMPL(WeakCell)
22TQ_OBJECT_CONSTRUCTORS_IMPL(JSWeakRef)
23TQ_OBJECT_CONSTRUCTORS_IMPL(JSFinalizationRegistry)
24
25BIT_FIELD_ACCESSORS(JSFinalizationRegistry, flags, scheduled_for_cleanup,
26                    JSFinalizationRegistry::ScheduledForCleanupBit)
27
28void JSFinalizationRegistry::RegisterWeakCellWithUnregisterToken(
29    Handle<JSFinalizationRegistry> finalization_registry,
30    Handle<WeakCell> weak_cell, Isolate* isolate) {
31  Handle<SimpleNumberDictionary> key_map;
32  if (finalization_registry->key_map().IsUndefined(isolate)) {
33    key_map = SimpleNumberDictionary::New(isolate, 1);
34  } else {
35    key_map =
36        handle(SimpleNumberDictionary::cast(finalization_registry->key_map()),
37               isolate);
38  }
39
40  // Unregister tokens are held weakly as objects are often their own
41  // unregister token. To avoid using an ephemeron map, the map for token
42  // lookup is keyed on the token's identity hash instead of the token itself.
43  uint32_t key = weak_cell->unregister_token().GetOrCreateHash(isolate).value();
44  InternalIndex entry = key_map->FindEntry(isolate, key);
45  if (entry.is_found()) {
46    Object value = key_map->ValueAt(entry);
47    WeakCell existing_weak_cell = WeakCell::cast(value);
48    existing_weak_cell.set_key_list_prev(*weak_cell);
49    weak_cell->set_key_list_next(existing_weak_cell);
50  }
51  key_map = SimpleNumberDictionary::Set(isolate, key_map, key, weak_cell);
52  finalization_registry->set_key_map(*key_map);
53}
54
55bool JSFinalizationRegistry::Unregister(
56    Handle<JSFinalizationRegistry> finalization_registry,
57    Handle<HeapObject> unregister_token, Isolate* isolate) {
58  // Iterate through the doubly linked list of WeakCells associated with the
59  // key. Each WeakCell will be in the "active_cells" or "cleared_cells" list of
60  // its FinalizationRegistry; remove it from there.
61  return finalization_registry->RemoveUnregisterToken(
62      *unregister_token, isolate, kRemoveMatchedCellsFromRegistry,
63      [](HeapObject, ObjectSlot, Object) {});
64}
65
66template <typename GCNotifyUpdatedSlotCallback>
67bool JSFinalizationRegistry::RemoveUnregisterToken(
68    HeapObject unregister_token, Isolate* isolate,
69    RemoveUnregisterTokenMode removal_mode,
70    GCNotifyUpdatedSlotCallback gc_notify_updated_slot) {
71  // This method is called from both FinalizationRegistry#unregister and for
72  // removing weakly-held dead unregister tokens. The latter is during GC so
73  // this function cannot GC.
74  DisallowGarbageCollection no_gc;
75  if (key_map().IsUndefined(isolate)) {
76    return false;
77  }
78
79  SimpleNumberDictionary key_map =
80      SimpleNumberDictionary::cast(this->key_map());
81  // If the token doesn't have a hash, it was not used as a key inside any hash
82  // tables.
83  Object hash = unregister_token.GetHash();
84  if (hash.IsUndefined(isolate)) {
85    return false;
86  }
87  uint32_t key = Smi::ToInt(hash);
88  InternalIndex entry = key_map.FindEntry(isolate, key);
89  if (entry.is_not_found()) {
90    return false;
91  }
92
93  Object value = key_map.ValueAt(entry);
94  bool was_present = false;
95  HeapObject undefined = ReadOnlyRoots(isolate).undefined_value();
96  HeapObject new_key_list_head = undefined;
97  HeapObject new_key_list_prev = undefined;
98  // Compute a new key list that doesn't have unregister_token. Because
99  // unregister tokens are held weakly, key_map is keyed using the tokens'
100  // identity hashes, and identity hashes may collide.
101  while (!value.IsUndefined(isolate)) {
102    WeakCell weak_cell = WeakCell::cast(value);
103    DCHECK(!ObjectInYoungGeneration(weak_cell));
104    value = weak_cell.key_list_next();
105    if (weak_cell.unregister_token() == unregister_token) {
106      // weak_cell has the same unregister token; remove it from the key list.
107      switch (removal_mode) {
108        case kRemoveMatchedCellsFromRegistry:
109          weak_cell.RemoveFromFinalizationRegistryCells(isolate);
110          break;
111        case kKeepMatchedCellsInRegistry:
112          // Do nothing.
113          break;
114      }
115      // Clear unregister token-related fields.
116      weak_cell.set_unregister_token(undefined);
117      weak_cell.set_key_list_prev(undefined);
118      weak_cell.set_key_list_next(undefined);
119      was_present = true;
120    } else {
121      // weak_cell has a different unregister token with the same key (hash
122      // collision); fix up the list.
123      weak_cell.set_key_list_prev(new_key_list_prev);
124      gc_notify_updated_slot(weak_cell,
125                             weak_cell.RawField(WeakCell::kKeyListPrevOffset),
126                             new_key_list_prev);
127      weak_cell.set_key_list_next(undefined);
128      if (new_key_list_prev.IsUndefined(isolate)) {
129        new_key_list_head = weak_cell;
130      } else {
131        DCHECK(new_key_list_head.IsWeakCell());
132        WeakCell prev_cell = WeakCell::cast(new_key_list_prev);
133        prev_cell.set_key_list_next(weak_cell);
134        gc_notify_updated_slot(prev_cell,
135                               prev_cell.RawField(WeakCell::kKeyListNextOffset),
136                               weak_cell);
137      }
138      new_key_list_prev = weak_cell;
139    }
140  }
141  if (new_key_list_head.IsUndefined(isolate)) {
142    DCHECK(was_present);
143    key_map.ClearEntry(entry);
144    key_map.ElementRemoved();
145  } else {
146    key_map.ValueAtPut(entry, new_key_list_head);
147    gc_notify_updated_slot(key_map, key_map.RawFieldOfValueAt(entry),
148                           new_key_list_head);
149  }
150  return was_present;
151}
152
153bool JSFinalizationRegistry::NeedsCleanup() const {
154  return cleared_cells().IsWeakCell();
155}
156
157HeapObject WeakCell::relaxed_target() const {
158  return TaggedField<HeapObject>::Relaxed_Load(*this, kTargetOffset);
159}
160
161HeapObject WeakCell::relaxed_unregister_token() const {
162  return TaggedField<HeapObject>::Relaxed_Load(*this, kUnregisterTokenOffset);
163}
164
165template <typename GCNotifyUpdatedSlotCallback>
166void WeakCell::Nullify(Isolate* isolate,
167                       GCNotifyUpdatedSlotCallback gc_notify_updated_slot) {
168  // Remove from the WeakCell from the "active_cells" list of its
169  // JSFinalizationRegistry and insert it into the "cleared_cells" list. This is
170  // only called for WeakCells which haven't been unregistered yet, so they will
171  // be in the active_cells list. (The caller must guard against calling this
172  // for unregistered WeakCells by checking that the target is not undefined.)
173  DCHECK(target().CanBeHeldWeakly());
174  set_target(ReadOnlyRoots(isolate).undefined_value());
175
176  JSFinalizationRegistry fr =
177      JSFinalizationRegistry::cast(finalization_registry());
178  if (prev().IsWeakCell()) {
179    DCHECK_NE(fr.active_cells(), *this);
180    WeakCell prev_cell = WeakCell::cast(prev());
181    prev_cell.set_next(next());
182    gc_notify_updated_slot(prev_cell, prev_cell.RawField(WeakCell::kNextOffset),
183                           next());
184  } else {
185    DCHECK_EQ(fr.active_cells(), *this);
186    fr.set_active_cells(next());
187    gc_notify_updated_slot(
188        fr, fr.RawField(JSFinalizationRegistry::kActiveCellsOffset), next());
189  }
190  if (next().IsWeakCell()) {
191    WeakCell next_cell = WeakCell::cast(next());
192    next_cell.set_prev(prev());
193    gc_notify_updated_slot(next_cell, next_cell.RawField(WeakCell::kPrevOffset),
194                           prev());
195  }
196
197  set_prev(ReadOnlyRoots(isolate).undefined_value());
198  Object cleared_head = fr.cleared_cells();
199  if (cleared_head.IsWeakCell()) {
200    WeakCell cleared_head_cell = WeakCell::cast(cleared_head);
201    cleared_head_cell.set_prev(*this);
202    gc_notify_updated_slot(cleared_head_cell,
203                           cleared_head_cell.RawField(WeakCell::kPrevOffset),
204                           *this);
205  }
206  set_next(fr.cleared_cells());
207  gc_notify_updated_slot(*this, RawField(WeakCell::kNextOffset), next());
208  fr.set_cleared_cells(*this);
209  gc_notify_updated_slot(
210      fr, fr.RawField(JSFinalizationRegistry::kClearedCellsOffset), *this);
211}
212
213void WeakCell::RemoveFromFinalizationRegistryCells(Isolate* isolate) {
214  // Remove the WeakCell from the list it's in (either "active_cells" or
215  // "cleared_cells" of its JSFinalizationRegistry).
216
217  // It's important to set_target to undefined here. This guards that we won't
218  // call Nullify (which assumes that the WeakCell is in active_cells).
219  DCHECK(target().IsUndefined() || target().CanBeHeldWeakly());
220  set_target(ReadOnlyRoots(isolate).undefined_value());
221
222  JSFinalizationRegistry fr =
223      JSFinalizationRegistry::cast(finalization_registry());
224  if (fr.active_cells() == *this) {
225    DCHECK(prev().IsUndefined(isolate));
226    fr.set_active_cells(next());
227  } else if (fr.cleared_cells() == *this) {
228    DCHECK(!prev().IsWeakCell());
229    fr.set_cleared_cells(next());
230  } else {
231    DCHECK(prev().IsWeakCell());
232    WeakCell prev_cell = WeakCell::cast(prev());
233    prev_cell.set_next(next());
234  }
235  if (next().IsWeakCell()) {
236    WeakCell next_cell = WeakCell::cast(next());
237    next_cell.set_prev(prev());
238  }
239  set_prev(ReadOnlyRoots(isolate).undefined_value());
240  set_next(ReadOnlyRoots(isolate).undefined_value());
241}
242
243}  // namespace internal
244}  // namespace v8
245
246#include "src/objects/object-macros-undef.h"
247
248#endif  // V8_OBJECTS_JS_WEAK_REFS_INL_H_
249