1// Copyright 2020 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#include "src/builtins/builtins-collections-gen.h" 5 6namespace runtime { 7extern runtime 8ShrinkFinalizationRegistryUnregisterTokenMap( 9 Context, JSFinalizationRegistry): void; 10extern runtime JSFinalizationRegistryRegisterWeakCellWithUnregisterToken( 11 implicit context: Context)(JSFinalizationRegistry, WeakCell): void; 12} 13 14namespace weakref { 15extern transitioning macro 16RemoveFinalizationRegistryCellFromUnregisterTokenMap( 17 JSFinalizationRegistry, WeakCell): void; 18 19extern macro WeakCollectionsBuiltinsAssembler::GotoIfCannotBeHeldWeakly(JSAny): 20 void labels NotWeakKey; 21 22macro SplitOffTail(weakCell: WeakCell): WeakCell|Undefined { 23 const weakCellTail = weakCell.next; 24 weakCell.next = Undefined; 25 typeswitch (weakCellTail) { 26 case (Undefined): { 27 } 28 case (tailIsNowAHead: WeakCell): { 29 dcheck(tailIsNowAHead.prev == weakCell); 30 tailIsNowAHead.prev = Undefined; 31 } 32 } 33 return weakCellTail; 34} 35 36transitioning macro 37PopClearedCell(finalizationRegistry: JSFinalizationRegistry): WeakCell| 38 Undefined { 39 typeswitch (finalizationRegistry.cleared_cells) { 40 case (Undefined): { 41 return Undefined; 42 } 43 case (weakCell: WeakCell): { 44 dcheck(weakCell.prev == Undefined); 45 finalizationRegistry.cleared_cells = SplitOffTail(weakCell); 46 47 // If the WeakCell has an unregister token, remove the cell from the 48 // unregister token linked lists and and the unregister token from 49 // key_map. This doesn't shrink key_map, which is done manually after 50 // the cleanup loop to avoid a runtime call. 51 if (weakCell.unregister_token != Undefined) { 52 RemoveFinalizationRegistryCellFromUnregisterTokenMap( 53 finalizationRegistry, weakCell); 54 } 55 56 return weakCell; 57 } 58 } 59} 60 61transitioning macro PushCell( 62 finalizationRegistry: JSFinalizationRegistry, cell: WeakCell): void { 63 cell.next = finalizationRegistry.active_cells; 64 typeswitch (finalizationRegistry.active_cells) { 65 case (Undefined): { 66 } 67 case (oldHead: WeakCell): { 68 oldHead.prev = cell; 69 } 70 } 71 finalizationRegistry.active_cells = cell; 72} 73 74transitioning macro 75FinalizationRegistryCleanupLoop(implicit context: Context)( 76 finalizationRegistry: JSFinalizationRegistry, callback: Callable): void { 77 while (true) { 78 const weakCellHead = PopClearedCell(finalizationRegistry); 79 typeswitch (weakCellHead) { 80 case (Undefined): { 81 break; 82 } 83 case (weakCell: WeakCell): { 84 try { 85 Call(context, callback, Undefined, weakCell.holdings); 86 } catch (e, message) { 87 runtime::ShrinkFinalizationRegistryUnregisterTokenMap( 88 context, finalizationRegistry); 89 ReThrowWithMessage(context, e, message); 90 } 91 } 92 } 93 } 94 95 runtime::ShrinkFinalizationRegistryUnregisterTokenMap( 96 context, finalizationRegistry); 97} 98 99transitioning javascript builtin 100FinalizationRegistryConstructor( 101 js-implicit context: NativeContext, receiver: JSAny, newTarget: JSAny, 102 target: JSFunction)(cleanupCallback: JSAny): JSFinalizationRegistry { 103 // 1. If NewTarget is undefined, throw a TypeError exception. 104 if (newTarget == Undefined) { 105 ThrowTypeError( 106 MessageTemplate::kConstructorNotFunction, 'FinalizationRegistry'); 107 } 108 // 2. If IsCallable(cleanupCallback) is false, throw a TypeError exception. 109 const cleanupCallback = Cast<Callable>(cleanupCallback) otherwise 110 ThrowTypeError(MessageTemplate::kWeakRefsCleanupMustBeCallable); 111 // 3. Let finalizationRegistry be ? OrdinaryCreateFromConstructor(NewTarget, 112 // "%FinalizationRegistryPrototype%", « [[Realm]], [[CleanupCallback]], 113 // [[Cells]] »). 114 const map = GetDerivedMap(target, UnsafeCast<JSReceiver>(newTarget)); 115 const finalizationRegistry = UnsafeCast<JSFinalizationRegistry>( 116 AllocateFastOrSlowJSObjectFromMap(map)); 117 // 4. Let fn be the active function object. 118 // 5. Set finalizationRegistry.[[Realm]] to fn.[[Realm]]. 119 finalizationRegistry.native_context = context; 120 // 6. Set finalizationRegistry.[[CleanupCallback]] to cleanupCallback. 121 finalizationRegistry.cleanup = cleanupCallback; 122 finalizationRegistry.flags = 123 SmiTag(FinalizationRegistryFlags{scheduled_for_cleanup: false}); 124 // 7. Set finalizationRegistry.[[Cells]] to be an empty List. 125 dcheck(finalizationRegistry.active_cells == Undefined); 126 dcheck(finalizationRegistry.cleared_cells == Undefined); 127 dcheck(finalizationRegistry.key_map == Undefined); 128 // 8. Return finalizationRegistry. 129 return finalizationRegistry; 130} 131 132// https://tc39.es/ecma262/#sec-finalization-registry.prototype.register 133transitioning javascript builtin 134FinalizationRegistryRegister( 135 js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny { 136 // 1. Let finalizationRegistry be the this value. 137 // 2. Perform ? RequireInternalSlot(finalizationRegistry, [[Cells]]). 138 const finalizationRegistry = Cast<JSFinalizationRegistry>(receiver) otherwise 139 ThrowTypeError( 140 MessageTemplate::kIncompatibleMethodReceiver, 141 'FinalizationRegistry.prototype.register', receiver); 142 // 3. If CanBeHeldWeakly(target) is false, throw a TypeError exception. 143 GotoIfCannotBeHeldWeakly(arguments[0]) 144 otherwise ThrowTypeError(MessageTemplate::kInvalidWeakRefsRegisterTarget); 145 146 const target = UnsafeCast<(JSReceiver | Symbol)>(arguments[0]); 147 const heldValue = arguments[1]; 148 // 4. If SameValue(target, heldValue), throw a TypeError exception. 149 if (target == heldValue) { 150 ThrowTypeError( 151 MessageTemplate::kWeakRefsRegisterTargetAndHoldingsMustNotBeSame); 152 } 153 // 5. If CanBeHeldWeakly(unregisterToken) is false, 154 // a. If unregisterToken is not undefined, throw a TypeError exception. 155 // b. Set unregisterToken to empty. 156 const unregisterTokenRaw = arguments[2]; 157 let unregisterToken: JSReceiver|Undefined|Symbol; 158 159 if (IsUndefined(unregisterTokenRaw)) { 160 unregisterToken = Undefined; 161 } else { 162 GotoIfCannotBeHeldWeakly(unregisterTokenRaw) 163 otherwise ThrowTypeError( 164 MessageTemplate::kInvalidWeakRefsUnregisterToken, unregisterTokenRaw); 165 unregisterToken = UnsafeCast<(JSReceiver | Symbol)>(unregisterTokenRaw); 166 } 167 168 // 6. Let cell be the Record { [[WeakRefTarget]] : target, [[HeldValue]]: 169 // heldValue, [[UnregisterToken]]: unregisterToken }. 170 // Allocate the WeakCell object in the old space, because 1) WeakCell weakness 171 // handling is only implemented in the old space 2) they're supposedly 172 // long-living. TODO(marja, gsathya): Support WeakCells in Scavenger. 173 const cell = new (Pretenured) WeakCell{ 174 map: GetWeakCellMap(), 175 finalization_registry: finalizationRegistry, 176 target: target, 177 unregister_token: unregisterToken, 178 holdings: heldValue, 179 prev: Undefined, 180 next: Undefined, 181 key_list_prev: Undefined, 182 key_list_next: Undefined 183 }; 184 // 7. Append cell to finalizationRegistry.[[Cells]]. 185 PushCell(finalizationRegistry, cell); 186 if (unregisterToken != Undefined) { 187 // If an unregister token is provided, a runtime call is needed to 188 // do some OrderedHashTable operations and register the mapping. 189 // See v8:10705. 190 runtime::JSFinalizationRegistryRegisterWeakCellWithUnregisterToken( 191 finalizationRegistry, cell); 192 } 193 // 8. Return undefined. 194 return Undefined; 195} 196 197transitioning javascript builtin 198FinalizationRegistryPrototypeCleanupSome( 199 js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny { 200 // 1. Let finalizationRegistry be the this value. 201 // 202 // 2. Perform ? RequireInternalSlot(finalizationRegistry, [[Cells]]). 203 const methodName: constexpr string = 204 'FinalizationRegistry.prototype.cleanupSome'; 205 const finalizationRegistry = 206 Cast<JSFinalizationRegistry>(receiver) otherwise ThrowTypeError( 207 MessageTemplate::kIncompatibleMethodReceiver, methodName, receiver); 208 209 let callback: Callable; 210 if (arguments[0] != Undefined) { 211 // 4. If callback is not undefined and IsCallable(callback) is 212 // false, throw a TypeError exception. 213 callback = Cast<Callable>(arguments[0]) otherwise ThrowTypeError( 214 MessageTemplate::kWeakRefsCleanupMustBeCallable, arguments[0]); 215 } else { 216 callback = finalizationRegistry.cleanup; 217 } 218 219 FinalizationRegistryCleanupLoop(finalizationRegistry, callback); 220 return Undefined; 221} 222} 223