// Copyright 2020 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. namespace runtime { extern runtime ShrinkFinalizationRegistryUnregisterTokenMap( Context, JSFinalizationRegistry): void; extern runtime JSFinalizationRegistryRegisterWeakCellWithUnregisterToken( implicit context: Context)(JSFinalizationRegistry, WeakCell): void; } namespace weakref { extern transitioning macro RemoveFinalizationRegistryCellFromUnregisterTokenMap( JSFinalizationRegistry, WeakCell): void; macro SplitOffTail(weakCell: WeakCell): WeakCell|Undefined { const weakCellTail = weakCell.next; weakCell.next = Undefined; typeswitch (weakCellTail) { case (Undefined): { } case (tailIsNowAHead: WeakCell): { dcheck(tailIsNowAHead.prev == weakCell); tailIsNowAHead.prev = Undefined; } } return weakCellTail; } transitioning macro PopClearedCell(finalizationRegistry: JSFinalizationRegistry): WeakCell| Undefined { typeswitch (finalizationRegistry.cleared_cells) { case (Undefined): { return Undefined; } case (weakCell: WeakCell): { dcheck(weakCell.prev == Undefined); finalizationRegistry.cleared_cells = SplitOffTail(weakCell); // If the WeakCell has an unregister token, remove the cell from the // unregister token linked lists and and the unregister token from // key_map. This doesn't shrink key_map, which is done manually after // the cleanup loop to avoid a runtime call. if (weakCell.unregister_token != Undefined) { RemoveFinalizationRegistryCellFromUnregisterTokenMap( finalizationRegistry, weakCell); } return weakCell; } } } transitioning macro PushCell( finalizationRegistry: JSFinalizationRegistry, cell: WeakCell): void { cell.next = finalizationRegistry.active_cells; typeswitch (finalizationRegistry.active_cells) { case (Undefined): { } case (oldHead: WeakCell): { oldHead.prev = cell; } } finalizationRegistry.active_cells = cell; } transitioning macro FinalizationRegistryCleanupLoop(implicit context: Context)( finalizationRegistry: JSFinalizationRegistry, callback: Callable): void { while (true) { const weakCellHead = PopClearedCell(finalizationRegistry); typeswitch (weakCellHead) { case (Undefined): { break; } case (weakCell: WeakCell): { try { Call(context, callback, Undefined, weakCell.holdings); } catch (e, message) { runtime::ShrinkFinalizationRegistryUnregisterTokenMap( context, finalizationRegistry); ReThrowWithMessage(context, e, message); } } } } runtime::ShrinkFinalizationRegistryUnregisterTokenMap( context, finalizationRegistry); } transitioning javascript builtin FinalizationRegistryConstructor( js-implicit context: NativeContext, receiver: JSAny, newTarget: JSAny, target: JSFunction)(cleanupCallback: JSAny): JSFinalizationRegistry { // 1. If NewTarget is undefined, throw a TypeError exception. if (newTarget == Undefined) { ThrowTypeError( MessageTemplate::kConstructorNotFunction, 'FinalizationRegistry'); } // 2. If IsCallable(cleanupCallback) is false, throw a TypeError exception. const cleanupCallback = Cast<Callable>(cleanupCallback) otherwise ThrowTypeError(MessageTemplate::kWeakRefsCleanupMustBeCallable); // 3. Let finalizationRegistry be ? OrdinaryCreateFromConstructor(NewTarget, // "%FinalizationRegistryPrototype%", « [[Realm]], [[CleanupCallback]], // [[Cells]] »). const map = GetDerivedMap(target, UnsafeCast<JSReceiver>(newTarget)); const finalizationRegistry = UnsafeCast<JSFinalizationRegistry>( AllocateFastOrSlowJSObjectFromMap(map)); // 4. Let fn be the active function object. // 5. Set finalizationRegistry.[[Realm]] to fn.[[Realm]]. finalizationRegistry.native_context = context; // 6. Set finalizationRegistry.[[CleanupCallback]] to cleanupCallback. finalizationRegistry.cleanup = cleanupCallback; finalizationRegistry.flags = SmiTag(FinalizationRegistryFlags{scheduled_for_cleanup: false}); // 7. Set finalizationRegistry.[[Cells]] to be an empty List. dcheck(finalizationRegistry.active_cells == Undefined); dcheck(finalizationRegistry.cleared_cells == Undefined); dcheck(finalizationRegistry.key_map == Undefined); // 8. Return finalizationRegistry. return finalizationRegistry; } transitioning javascript builtin FinalizationRegistryRegister( js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny { // 1. Let finalizationRegistry be the this value. // 2. Perform ? RequireInternalSlot(finalizationRegistry, [[Cells]]). const finalizationRegistry = Cast<JSFinalizationRegistry>(receiver) otherwise ThrowTypeError( MessageTemplate::kIncompatibleMethodReceiver, 'FinalizationRegistry.prototype.register', receiver); // 3. If Type(target) is not Object, throw a TypeError exception. const target = Cast<JSReceiver>(arguments[0]) otherwise ThrowTypeError( MessageTemplate::kWeakRefsRegisterTargetMustBeObject); const heldValue = arguments[1]; // 4. If SameValue(target, heldValue), throw a TypeError exception. if (target == heldValue) { ThrowTypeError( MessageTemplate::kWeakRefsRegisterTargetAndHoldingsMustNotBeSame); } // 5. If Type(unregisterToken) is not Object, // a. If unregisterToken is not undefined, throw a TypeError exception. // b. Set unregisterToken to empty. const unregisterTokenRaw = arguments[2]; let unregisterToken: JSReceiver|Undefined; typeswitch (unregisterTokenRaw) { case (Undefined): { unregisterToken = Undefined; } case (unregisterTokenObj: JSReceiver): { unregisterToken = unregisterTokenObj; } case (JSAny): deferred { ThrowTypeError( MessageTemplate::kWeakRefsUnregisterTokenMustBeObject, unregisterTokenRaw); } } // 6. Let cell be the Record { [[WeakRefTarget]] : target, [[HeldValue]]: // heldValue, [[UnregisterToken]]: unregisterToken }. // Allocate the WeakCell object in the old space, because 1) WeakCell weakness // handling is only implemented in the old space 2) they're supposedly // long-living. TODO(marja, gsathya): Support WeakCells in Scavenger. const cell = new (Pretenured) WeakCell{ map: GetWeakCellMap(), finalization_registry: finalizationRegistry, target: target, unregister_token: unregisterToken, holdings: heldValue, prev: Undefined, next: Undefined, key_list_prev: Undefined, key_list_next: Undefined }; // 7. Append cell to finalizationRegistry.[[Cells]]. PushCell(finalizationRegistry, cell); if (unregisterToken != Undefined) { // If an unregister token is provided, a runtime call is needed to // do some OrderedHashTable operations and register the mapping. // See v8:10705. runtime::JSFinalizationRegistryRegisterWeakCellWithUnregisterToken( finalizationRegistry, cell); } // 8. Return undefined. return Undefined; } transitioning javascript builtin FinalizationRegistryPrototypeCleanupSome( js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny { // 1. Let finalizationRegistry be the this value. // // 2. Perform ? RequireInternalSlot(finalizationRegistry, [[Cells]]). const methodName: constexpr string = 'FinalizationRegistry.prototype.cleanupSome'; const finalizationRegistry = Cast<JSFinalizationRegistry>(receiver) otherwise ThrowTypeError( MessageTemplate::kIncompatibleMethodReceiver, methodName, receiver); let callback: Callable; if (arguments[0] != Undefined) { // 4. If callback is not undefined and IsCallable(callback) is // false, throw a TypeError exception. callback = Cast<Callable>(arguments[0]) otherwise ThrowTypeError( MessageTemplate::kWeakRefsCleanupMustBeCallable, arguments[0]); } else { callback = finalizationRegistry.cleanup; } FinalizationRegistryCleanupLoop(finalizationRegistry, callback); return Undefined; } }