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