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