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