[weakrefs] Port FinalizationRegistry cleanup loop to Torque
To avoid shrinking the unregister token map on each pop of the cleared cell list, the Torque implementation of the cleanup loop avoids shrinking the map until the end of the loop. To support that, PopClearedCellHoldings is refactored to the Torque PopClearedCell which calls the JSFinalization::RemoveCellFromUnregisterTokenMap and the runtime ShrinkFinalizationRegistryUnregisterTokenMap. The former cannot GC is and is implemented in CSA as a fast C call. The latter can GC and is a runtime call. This also incidentally makes uses of FinalizationRegistry without unregister token a fast path that doesn't have to leave Torque. Bug: v8:8179 Change-Id: Ia0c3c5800d26e31319a818f164f6bd3267355aa6 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2137950 Commit-Queue: Shu-yu Guo <syg@chromium.org> Reviewed-by: Marja Hölttä <marja@chromium.org> Reviewed-by: Tobias Tebbi <tebbi@chromium.org> Reviewed-by: Ulan Degenbaev <ulan@chromium.org> Cr-Commit-Position: refs/heads/master@{#67161}
This commit is contained in:
parent
4821ca2cd1
commit
dbbacccaa3
2
BUILD.gn
2
BUILD.gn
@ -1036,6 +1036,7 @@ torque_files = [
|
||||
"src/builtins/convert.tq",
|
||||
"src/builtins/console.tq",
|
||||
"src/builtins/data-view.tq",
|
||||
"src/builtins/finalization-registry.tq",
|
||||
"src/builtins/frames.tq",
|
||||
"src/builtins/frame-arguments.tq",
|
||||
"src/builtins/growable-fixed-array.tq",
|
||||
@ -2915,6 +2916,7 @@ v8_source_set("v8_base_without_compiler") {
|
||||
"src/runtime/runtime-typedarray.cc",
|
||||
"src/runtime/runtime-utils.h",
|
||||
"src/runtime/runtime-wasm.cc",
|
||||
"src/runtime/runtime-weak-refs.cc",
|
||||
"src/runtime/runtime.cc",
|
||||
"src/runtime/runtime.h",
|
||||
"src/sanitizer/asan.h",
|
||||
|
@ -8429,9 +8429,13 @@ Maybe<bool> FinalizationGroup::Cleanup(
|
||||
ENTER_V8(isolate, context, FinalizationGroup, Cleanup, Nothing<bool>(),
|
||||
i::HandleScope);
|
||||
i::Handle<i::Object> callback(fr->cleanup(), isolate);
|
||||
i::Handle<i::Object> argv[] = {callback};
|
||||
fr->set_scheduled_for_cleanup(false);
|
||||
has_pending_exception =
|
||||
i::JSFinalizationRegistry::Cleanup(isolate, fr, callback).IsNothing();
|
||||
i::Execution::CallBuiltin(isolate,
|
||||
isolate->finalization_registry_cleanup_some(),
|
||||
fr, arraysize(argv), argv)
|
||||
.is_null();
|
||||
RETURN_ON_FAILED_EXECUTION_PRIMITIVE(bool);
|
||||
return Just(true);
|
||||
}
|
||||
@ -11245,8 +11249,11 @@ void InvokeFinalizationRegistryCleanupFromTask(
|
||||
Local<v8::Context> api_context = Utils::ToLocal(context);
|
||||
CallDepthScope<true> call_depth_scope(isolate, api_context);
|
||||
VMState<OTHER> state(isolate);
|
||||
if (JSFinalizationRegistry::Cleanup(isolate, finalization_registry, callback)
|
||||
.IsNothing()) {
|
||||
Handle<Object> argv[] = {callback};
|
||||
if (Execution::CallBuiltin(isolate,
|
||||
isolate->finalization_registry_cleanup_some(),
|
||||
finalization_registry, arraysize(argv), argv)
|
||||
.is_null()) {
|
||||
call_depth_scope.Escape();
|
||||
}
|
||||
}
|
||||
|
@ -303,6 +303,7 @@ extern enum MessageTemplate {
|
||||
kProxyGetPrototypeOfNonExtensible,
|
||||
kProxySetPrototypeOfNonExtensible,
|
||||
kProxyDeletePropertyNonExtensible,
|
||||
kWeakRefsCleanupMustBeCallable,
|
||||
...
|
||||
}
|
||||
|
||||
@ -701,7 +702,8 @@ macro Float64IsNaN(n: float64): bool {
|
||||
}
|
||||
|
||||
// The type of all tagged values that can safely be compared with TaggedEqual.
|
||||
type TaggedWithIdentity = JSReceiver|FixedArrayBase|Oddball|Map|EmptyString;
|
||||
type TaggedWithIdentity =
|
||||
JSReceiver|FixedArrayBase|Oddball|Map|WeakCell|EmptyString;
|
||||
|
||||
extern operator '==' macro TaggedEqual(TaggedWithIdentity, Object): bool;
|
||||
extern operator '==' macro TaggedEqual(Object, TaggedWithIdentity): bool;
|
||||
|
@ -967,7 +967,6 @@ namespace internal {
|
||||
CPP(Trace) \
|
||||
\
|
||||
/* Weak refs */ \
|
||||
CPP(FinalizationRegistryCleanupSome) \
|
||||
CPP(FinalizationRegistryConstructor) \
|
||||
CPP(FinalizationRegistryRegister) \
|
||||
CPP(FinalizationRegistryUnregister) \
|
||||
|
@ -122,43 +122,6 @@ BUILTIN(FinalizationRegistryUnregister) {
|
||||
return *isolate->factory()->ToBoolean(success);
|
||||
}
|
||||
|
||||
BUILTIN(FinalizationRegistryCleanupSome) {
|
||||
HandleScope scope(isolate);
|
||||
const char* method_name = "FinalizationRegistry.prototype.cleanupSome";
|
||||
|
||||
// 1. Let finalizationGroup be the this value.
|
||||
//
|
||||
// 2. If Type(finalizationGroup) is not Object, throw a TypeError
|
||||
// exception.
|
||||
//
|
||||
// 3. If finalizationGroup does not have a [[Cells]] internal slot,
|
||||
// throw a TypeError exception.
|
||||
CHECK_RECEIVER(JSFinalizationRegistry, finalization_registry, method_name);
|
||||
|
||||
Handle<Object> callback(finalization_registry->cleanup(), isolate);
|
||||
Handle<Object> callback_obj = args.atOrUndefined(isolate, 1);
|
||||
|
||||
// 4. If callback is not undefined and IsCallable(callback) is
|
||||
// false, throw a TypeError exception.
|
||||
if (!callback_obj->IsUndefined(isolate)) {
|
||||
if (!callback_obj->IsCallable()) {
|
||||
THROW_NEW_ERROR_RETURN_FAILURE(
|
||||
isolate,
|
||||
NewTypeError(MessageTemplate::kWeakRefsCleanupMustBeCallable));
|
||||
}
|
||||
callback = callback_obj;
|
||||
}
|
||||
|
||||
// Don't do set_scheduled_for_cleanup(false); we still have the task
|
||||
// scheduled.
|
||||
if (JSFinalizationRegistry::Cleanup(isolate, finalization_registry, callback)
|
||||
.IsNothing()) {
|
||||
DCHECK(isolate->has_pending_exception());
|
||||
return ReadOnlyRoots(isolate).exception();
|
||||
}
|
||||
return ReadOnlyRoots(isolate).undefined_value();
|
||||
}
|
||||
|
||||
BUILTIN(WeakRefConstructor) {
|
||||
HandleScope scope(isolate);
|
||||
Handle<JSFunction> target = args.target();
|
||||
|
106
src/builtins/finalization-registry.tq
Normal file
106
src/builtins/finalization-registry.tq
Normal file
@ -0,0 +1,106 @@
|
||||
// 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;
|
||||
}
|
||||
|
||||
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): {
|
||||
assert(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): {
|
||||
assert(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
|
||||
FinalizationRegistryCleanupLoop(implicit context: Context)(
|
||||
finalizationRegistry: JSFinalizationRegistry, callback: Callable) {
|
||||
while (true) {
|
||||
const weakCellHead = PopClearedCell(finalizationRegistry);
|
||||
typeswitch (weakCellHead) {
|
||||
case (Undefined): {
|
||||
break;
|
||||
}
|
||||
case (weakCell: WeakCell): {
|
||||
try {
|
||||
Call(context, callback, Undefined, weakCell.holdings);
|
||||
} catch (e) {
|
||||
runtime::ShrinkFinalizationRegistryUnregisterTokenMap(
|
||||
context, finalizationRegistry);
|
||||
ReThrow(context, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
runtime::ShrinkFinalizationRegistryUnregisterTokenMap(
|
||||
context, finalizationRegistry);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -13250,6 +13250,21 @@ TNode<String> CodeStubAssembler::TaggedToDirectString(TNode<Object> value,
|
||||
return CAST(value);
|
||||
}
|
||||
|
||||
void CodeStubAssembler::RemoveFinalizationRegistryCellFromUnregisterTokenMap(
|
||||
TNode<JSFinalizationRegistry> finalization_registry,
|
||||
TNode<WeakCell> weak_cell) {
|
||||
const TNode<ExternalReference> remove_cell = ExternalConstant(
|
||||
ExternalReference::
|
||||
js_finalization_registry_remove_cell_from_unregister_token_map());
|
||||
const TNode<ExternalReference> isolate_ptr =
|
||||
ExternalConstant(ExternalReference::isolate_address(isolate()));
|
||||
|
||||
CallCFunction(remove_cell, MachineType::Pointer(),
|
||||
std::make_pair(MachineType::Pointer(), isolate_ptr),
|
||||
std::make_pair(MachineType::AnyTagged(), finalization_registry),
|
||||
std::make_pair(MachineType::AnyTagged(), weak_cell));
|
||||
}
|
||||
|
||||
PrototypeCheckAssembler::PrototypeCheckAssembler(
|
||||
compiler::CodeAssemblerState* state, Flags flags,
|
||||
TNode<NativeContext> native_context, TNode<Map> initial_prototype_map,
|
||||
|
@ -3842,6 +3842,10 @@ class V8_EXPORT_PRIVATE CodeStubAssembler
|
||||
|
||||
TNode<Smi> RefillMathRandom(TNode<NativeContext> native_context);
|
||||
|
||||
void RemoveFinalizationRegistryCellFromUnregisterTokenMap(
|
||||
TNode<JSFinalizationRegistry> finalization_registry,
|
||||
TNode<WeakCell> weak_cell);
|
||||
|
||||
private:
|
||||
friend class CodeStubArguments;
|
||||
|
||||
|
@ -902,6 +902,10 @@ static int EnterMicrotaskContextWrapper(HandleScopeImplementer* hsi,
|
||||
|
||||
FUNCTION_REFERENCE(call_enter_context_function, EnterMicrotaskContextWrapper)
|
||||
|
||||
FUNCTION_REFERENCE(
|
||||
js_finalization_registry_remove_cell_from_unregister_token_map,
|
||||
JSFinalizationRegistry::RemoveCellFromUnregisterTokenMap)
|
||||
|
||||
bool operator==(ExternalReference lhs, ExternalReference rhs) {
|
||||
return lhs.address() == rhs.address();
|
||||
}
|
||||
|
@ -215,6 +215,8 @@ class StatsCounter;
|
||||
V(atomic_pair_exchange_function, "atomic_pair_exchange_function") \
|
||||
V(atomic_pair_compare_exchange_function, \
|
||||
"atomic_pair_compare_exchange_function") \
|
||||
V(js_finalization_registry_remove_cell_from_unregister_token_map, \
|
||||
"JSFinalizationRegistry::RemoveCellFromUnregisterTokenMap") \
|
||||
EXTERNAL_REFERENCE_LIST_INTL(V)
|
||||
|
||||
#ifdef V8_INTL_SUPPORT
|
||||
|
@ -4338,6 +4338,15 @@ void Genesis::InitializeGlobal_harmony_weak_refs() {
|
||||
SimpleInstallFunction(isolate(), finalization_registry_prototype,
|
||||
"unregister",
|
||||
Builtins::kFinalizationRegistryUnregister, 1, false);
|
||||
|
||||
// The cleanupSome function is created but not exposed, as it is used
|
||||
// internally by InvokeFinalizationRegistryCleanupFromTask.
|
||||
//
|
||||
// It is exposed by FLAG_harmony_weak_refs_with_cleanup_some.
|
||||
Handle<JSFunction> cleanup_some_fun = SimpleCreateFunction(
|
||||
isolate(), factory->InternalizeUtf8String("cleanupSome"),
|
||||
Builtins::kFinalizationRegistryPrototypeCleanupSome, 0, false);
|
||||
native_context()->set_finalization_registry_cleanup_some(*cleanup_some_fun);
|
||||
}
|
||||
{
|
||||
// Create %WeakRefPrototype%
|
||||
@ -4386,9 +4395,10 @@ void Genesis::InitializeGlobal_harmony_weak_refs_with_cleanup_some() {
|
||||
JSObject::cast(finalization_registry_fun->instance_prototype()),
|
||||
isolate());
|
||||
|
||||
SimpleInstallFunction(isolate(), finalization_registry_prototype,
|
||||
"cleanupSome",
|
||||
Builtins::kFinalizationRegistryCleanupSome, 0, false);
|
||||
JSObject::AddProperty(isolate(), finalization_registry_prototype,
|
||||
factory()->InternalizeUtf8String("cleanupSome"),
|
||||
isolate()->finalization_registry_cleanup_some(),
|
||||
DONT_ENUM);
|
||||
}
|
||||
|
||||
void Genesis::InitializeGlobal_harmony_promise_all_settled() {
|
||||
|
@ -346,6 +346,8 @@ enum ContextLookupFlags {
|
||||
V(MAP_GET_INDEX, JSFunction, map_get) \
|
||||
V(MAP_HAS_INDEX, JSFunction, map_has) \
|
||||
V(MAP_SET_INDEX, JSFunction, map_set) \
|
||||
V(FINALIZATION_REGISTRY_CLEANUP_SOME, JSFunction, \
|
||||
finalization_registry_cleanup_some) \
|
||||
V(FUNCTION_HAS_INSTANCE_INDEX, JSFunction, function_has_instance) \
|
||||
V(OBJECT_TO_STRING, JSFunction, object_to_string) \
|
||||
V(OBJECT_VALUE_OF_FUNCTION_INDEX, JSFunction, object_value_of_function) \
|
||||
|
@ -177,65 +177,6 @@ bool JSFinalizationRegistry::NeedsCleanup() const {
|
||||
return cleared_cells().IsWeakCell();
|
||||
}
|
||||
|
||||
Object JSFinalizationRegistry::PopClearedCellHoldings(
|
||||
Handle<JSFinalizationRegistry> finalization_registry, Isolate* isolate) {
|
||||
Handle<WeakCell> weak_cell =
|
||||
handle(WeakCell::cast(finalization_registry->cleared_cells()), isolate);
|
||||
DCHECK(weak_cell->prev().IsUndefined(isolate));
|
||||
finalization_registry->set_cleared_cells(weak_cell->next());
|
||||
weak_cell->set_next(ReadOnlyRoots(isolate).undefined_value());
|
||||
|
||||
if (finalization_registry->cleared_cells().IsWeakCell()) {
|
||||
WeakCell cleared_cells_head =
|
||||
WeakCell::cast(finalization_registry->cleared_cells());
|
||||
DCHECK_EQ(cleared_cells_head.prev(), *weak_cell);
|
||||
cleared_cells_head.set_prev(ReadOnlyRoots(isolate).undefined_value());
|
||||
} else {
|
||||
DCHECK(finalization_registry->cleared_cells().IsUndefined(isolate));
|
||||
}
|
||||
|
||||
// Also remove the WeakCell from the key_map (if it's there).
|
||||
if (!weak_cell->unregister_token().IsUndefined(isolate)) {
|
||||
if (weak_cell->key_list_prev().IsUndefined(isolate)) {
|
||||
Handle<SimpleNumberDictionary> key_map =
|
||||
handle(SimpleNumberDictionary::cast(finalization_registry->key_map()),
|
||||
isolate);
|
||||
Handle<Object> unregister_token =
|
||||
handle(weak_cell->unregister_token(), isolate);
|
||||
uint32_t key = Smi::ToInt(unregister_token->GetHash());
|
||||
InternalIndex entry = key_map->FindEntry(isolate, key);
|
||||
|
||||
if (weak_cell->key_list_next().IsUndefined(isolate)) {
|
||||
// weak_cell is the only one associated with its key; remove the key
|
||||
// from the hash table.
|
||||
DCHECK(entry.is_found());
|
||||
key_map = SimpleNumberDictionary::DeleteEntry(isolate, key_map, entry);
|
||||
finalization_registry->set_key_map(*key_map);
|
||||
} else {
|
||||
// weak_cell is the list head for its key; we need to change the value
|
||||
// of the key in the hash table.
|
||||
Handle<WeakCell> next =
|
||||
handle(WeakCell::cast(weak_cell->key_list_next()), isolate);
|
||||
DCHECK_EQ(next->key_list_prev(), *weak_cell);
|
||||
next->set_key_list_prev(ReadOnlyRoots(isolate).undefined_value());
|
||||
weak_cell->set_key_list_next(ReadOnlyRoots(isolate).undefined_value());
|
||||
key_map = SimpleNumberDictionary::Set(isolate, key_map, key, next);
|
||||
finalization_registry->set_key_map(*key_map);
|
||||
}
|
||||
} else {
|
||||
// weak_cell is somewhere in the middle of its key list.
|
||||
WeakCell prev = WeakCell::cast(weak_cell->key_list_prev());
|
||||
prev.set_key_list_next(weak_cell->key_list_next());
|
||||
if (!weak_cell->key_list_next().IsUndefined()) {
|
||||
WeakCell next = WeakCell::cast(weak_cell->key_list_next());
|
||||
next.set_key_list_prev(weak_cell->key_list_prev());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return weak_cell->holdings();
|
||||
}
|
||||
|
||||
template <typename GCNotifyUpdatedSlotCallback>
|
||||
void WeakCell::Nullify(Isolate* isolate,
|
||||
GCNotifyUpdatedSlotCallback gc_notify_updated_slot) {
|
||||
|
@ -61,17 +61,15 @@ class JSFinalizationRegistry : public JSObject {
|
||||
// Returns true if the cleared_cells list is non-empty.
|
||||
inline bool NeedsCleanup() const;
|
||||
|
||||
// Remove the first cleared WeakCell from the cleared_cells
|
||||
// list (assumes there is one) and return its holdings.
|
||||
inline static Object PopClearedCellHoldings(
|
||||
Handle<JSFinalizationRegistry> finalization_registry, Isolate* isolate);
|
||||
|
||||
// Call the user's cleanup function in a loop, once for each cleared cell.
|
||||
// Remove the already-popped weak_cell from its unregister token linked list,
|
||||
// as well as removing the entry from the key map if it is the only WeakCell
|
||||
// with its unregister token. This method cannot GC and does not shrink the
|
||||
// key map. Asserts that weak_cell has a non-undefined unregister token.
|
||||
//
|
||||
// Returns Nothing<bool> if exception occurs, otherwise returns Just(true).
|
||||
static V8_WARN_UNUSED_RESULT Maybe<bool> Cleanup(
|
||||
Isolate* isolate, Handle<JSFinalizationRegistry> finalization_registry,
|
||||
Handle<Object> callback);
|
||||
// It takes raw Addresses because it is called from CSA and Torque.
|
||||
V8_EXPORT_PRIVATE static void RemoveCellFromUnregisterTokenMap(
|
||||
Isolate* isolate, Address raw_finalization_registry,
|
||||
Address raw_weak_cell);
|
||||
|
||||
// Layout description.
|
||||
DEFINE_FIELD_OFFSET_CONSTANTS(
|
||||
|
@ -8306,35 +8306,50 @@ EXTERN_DEFINE_BASE_NAME_DICTIONARY(GlobalDictionary, GlobalDictionaryShape)
|
||||
#undef EXTERN_DEFINE_DICTIONARY
|
||||
#undef EXTERN_DEFINE_BASE_NAME_DICTIONARY
|
||||
|
||||
Maybe<bool> JSFinalizationRegistry::Cleanup(
|
||||
Isolate* isolate, Handle<JSFinalizationRegistry> finalization_registry,
|
||||
Handle<Object> cleanup) {
|
||||
DCHECK(cleanup->IsCallable());
|
||||
// Attempt to shrink key_map now, as unregister tokens are held weakly and the
|
||||
// map is not shrinkable when sweeping dead tokens during GC itself.
|
||||
if (!finalization_registry->key_map().IsUndefined(isolate)) {
|
||||
Handle<SimpleNumberDictionary> key_map(
|
||||
SimpleNumberDictionary::cast(finalization_registry->key_map()),
|
||||
isolate);
|
||||
key_map = SimpleNumberDictionary::Shrink(isolate, key_map);
|
||||
finalization_registry->set_key_map(*key_map);
|
||||
}
|
||||
void JSFinalizationRegistry::RemoveCellFromUnregisterTokenMap(
|
||||
Isolate* isolate, Address raw_finalization_registry,
|
||||
Address raw_weak_cell) {
|
||||
DisallowHeapAllocation no_gc;
|
||||
JSFinalizationRegistry finalization_registry =
|
||||
JSFinalizationRegistry::cast(Object(raw_finalization_registry));
|
||||
WeakCell weak_cell = WeakCell::cast(Object(raw_weak_cell));
|
||||
DCHECK(!weak_cell.unregister_token().IsUndefined(isolate));
|
||||
|
||||
// It's possible that the cleared_cells list is empty, since
|
||||
// FinalizationRegistry.unregister() removed all its elements before this task
|
||||
// ran. In that case, don't call the cleanup function.
|
||||
while (!finalization_registry->cleared_cells().IsUndefined(isolate)) {
|
||||
Handle<Object> holding(
|
||||
PopClearedCellHoldings(finalization_registry, isolate), isolate);
|
||||
Handle<Object> args[] = {holding};
|
||||
if (Execution::Call(
|
||||
isolate, cleanup,
|
||||
handle(ReadOnlyRoots(isolate).undefined_value(), isolate), 1, args)
|
||||
.is_null()) {
|
||||
return Nothing<bool>();
|
||||
// Remove weak_cell from the linked list of other WeakCells with the same
|
||||
// unregister token and remove its unregister token from key_map if necessary
|
||||
// without shrinking it. Since shrinking may allocate, it is performed by the
|
||||
// caller after looping, or on exception.
|
||||
if (weak_cell.key_list_prev().IsUndefined(isolate)) {
|
||||
SimpleNumberDictionary key_map =
|
||||
SimpleNumberDictionary::cast(finalization_registry.key_map());
|
||||
Object unregister_token = weak_cell.unregister_token();
|
||||
uint32_t key = Smi::ToInt(unregister_token.GetHash());
|
||||
InternalIndex entry = key_map.FindEntry(isolate, key);
|
||||
DCHECK(entry.is_found());
|
||||
|
||||
if (weak_cell.key_list_next().IsUndefined(isolate)) {
|
||||
// weak_cell is the only one associated with its key; remove the key
|
||||
// from the hash table.
|
||||
key_map.ClearEntry(entry);
|
||||
key_map.ElementRemoved();
|
||||
} else {
|
||||
// weak_cell is the list head for its key; we need to change the value
|
||||
// of the key in the hash table.
|
||||
WeakCell next = WeakCell::cast(weak_cell.key_list_next());
|
||||
DCHECK_EQ(next.key_list_prev(), weak_cell);
|
||||
next.set_key_list_prev(ReadOnlyRoots(isolate).undefined_value());
|
||||
weak_cell.set_key_list_next(ReadOnlyRoots(isolate).undefined_value());
|
||||
key_map.ValueAtPut(entry, next);
|
||||
}
|
||||
} else {
|
||||
// weak_cell is somewhere in the middle of its key list.
|
||||
WeakCell prev = WeakCell::cast(weak_cell.key_list_prev());
|
||||
prev.set_key_list_next(weak_cell.key_list_next());
|
||||
if (!weak_cell.key_list_next().IsUndefined()) {
|
||||
WeakCell next = WeakCell::cast(weak_cell.key_list_next());
|
||||
next.set_key_list_prev(weak_cell.key_list_prev());
|
||||
}
|
||||
}
|
||||
return Just(true);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
30
src/runtime/runtime-weak-refs.cc
Normal file
30
src/runtime/runtime-weak-refs.cc
Normal file
@ -0,0 +1,30 @@
|
||||
// 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.
|
||||
|
||||
#include "src/runtime/runtime-utils.h"
|
||||
|
||||
#include "src/execution/arguments-inl.h"
|
||||
#include "src/objects/js-weak-refs-inl.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_ShrinkFinalizationRegistryUnregisterTokenMap) {
|
||||
HandleScope scope(isolate);
|
||||
DCHECK_EQ(1, args.length());
|
||||
CONVERT_ARG_HANDLE_CHECKED(JSFinalizationRegistry, finalization_registry, 0);
|
||||
|
||||
if (!finalization_registry->key_map().IsUndefined(isolate)) {
|
||||
Handle<SimpleNumberDictionary> key_map =
|
||||
handle(SimpleNumberDictionary::cast(finalization_registry->key_map()),
|
||||
isolate);
|
||||
key_map = SimpleNumberDictionary::Shrink(isolate, key_map);
|
||||
finalization_registry->set_key_map(*key_map);
|
||||
}
|
||||
|
||||
return ReadOnlyRoots(isolate).undefined_value();
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
@ -578,6 +578,9 @@ namespace internal {
|
||||
F(WasmNewMultiReturnJSArray, 1, 1) \
|
||||
F(WasmDebugBreak, 0, 1)
|
||||
|
||||
#define FOR_EACH_INTRINSIC_WEAKREF(F, I) \
|
||||
F(ShrinkFinalizationRegistryUnregisterTokenMap, 1, 1)
|
||||
|
||||
#define FOR_EACH_INTRINSIC_RETURN_PAIR_IMPL(F, I) \
|
||||
F(DebugBreakOnBytecode, 1, 2) \
|
||||
F(LoadLookupSlotForCall, 1, 2)
|
||||
@ -636,7 +639,8 @@ namespace internal {
|
||||
FOR_EACH_INTRINSIC_SYMBOL(F, I) \
|
||||
FOR_EACH_INTRINSIC_TEST(F, I) \
|
||||
FOR_EACH_INTRINSIC_TYPEDARRAY(F, I) \
|
||||
FOR_EACH_INTRINSIC_WASM(F, I)
|
||||
FOR_EACH_INTRINSIC_WASM(F, I) \
|
||||
FOR_EACH_INTRINSIC_WEAKREF(F, I)
|
||||
|
||||
// Defines the list of all intrinsics, coming in 2 flavors, either returning an
|
||||
// object or a pair.
|
||||
|
@ -97,6 +97,33 @@ void NullifyWeakCell(Handle<WeakCell> weak_cell, Isolate* isolate) {
|
||||
#endif // VERIFY_HEAP
|
||||
}
|
||||
|
||||
Object PopClearedCellHoldings(
|
||||
Handle<JSFinalizationRegistry> finalization_registry, Isolate* isolate) {
|
||||
// PopClearedCell is implemented in Torque. Reproduce that implementation here
|
||||
// for testing.
|
||||
Handle<WeakCell> weak_cell =
|
||||
handle(WeakCell::cast(finalization_registry->cleared_cells()), isolate);
|
||||
DCHECK(weak_cell->prev().IsUndefined(isolate));
|
||||
finalization_registry->set_cleared_cells(weak_cell->next());
|
||||
weak_cell->set_next(ReadOnlyRoots(isolate).undefined_value());
|
||||
|
||||
if (finalization_registry->cleared_cells().IsWeakCell()) {
|
||||
WeakCell cleared_cells_head =
|
||||
WeakCell::cast(finalization_registry->cleared_cells());
|
||||
DCHECK_EQ(cleared_cells_head.prev(), *weak_cell);
|
||||
cleared_cells_head.set_prev(ReadOnlyRoots(isolate).undefined_value());
|
||||
} else {
|
||||
DCHECK(finalization_registry->cleared_cells().IsUndefined(isolate));
|
||||
}
|
||||
|
||||
if (!weak_cell->unregister_token().IsUndefined(isolate)) {
|
||||
JSFinalizationRegistry::RemoveCellFromUnregisterTokenMap(
|
||||
isolate, finalization_registry->ptr(), weak_cell->ptr());
|
||||
}
|
||||
|
||||
return weak_cell->holdings();
|
||||
}
|
||||
|
||||
// Usage: VerifyWeakCellChain(isolate, list_head, n, cell1, cell2, ..., celln);
|
||||
// verifies that list_head == cell1 and cell1, cell2, ..., celln. form a list.
|
||||
void VerifyWeakCellChain(Isolate* isolate, Object list_head, int n_args, ...) {
|
||||
@ -361,15 +388,13 @@ TEST(TestJSFinalizationRegistryPopClearedCellHoldings1) {
|
||||
NullifyWeakCell(weak_cell3, isolate);
|
||||
|
||||
CHECK(finalization_registry->NeedsCleanup());
|
||||
Object cleared1 = JSFinalizationRegistry::PopClearedCellHoldings(
|
||||
finalization_registry, isolate);
|
||||
Object cleared1 = PopClearedCellHoldings(finalization_registry, isolate);
|
||||
CHECK_EQ(cleared1, *holdings3);
|
||||
CHECK(weak_cell3->prev().IsUndefined(isolate));
|
||||
CHECK(weak_cell3->next().IsUndefined(isolate));
|
||||
|
||||
CHECK(finalization_registry->NeedsCleanup());
|
||||
Object cleared2 = JSFinalizationRegistry::PopClearedCellHoldings(
|
||||
finalization_registry, isolate);
|
||||
Object cleared2 = PopClearedCellHoldings(finalization_registry, isolate);
|
||||
CHECK_EQ(cleared2, *holdings2);
|
||||
CHECK(weak_cell2->prev().IsUndefined(isolate));
|
||||
CHECK(weak_cell2->next().IsUndefined(isolate));
|
||||
@ -379,8 +404,7 @@ TEST(TestJSFinalizationRegistryPopClearedCellHoldings1) {
|
||||
NullifyWeakCell(weak_cell1, isolate);
|
||||
|
||||
CHECK(finalization_registry->NeedsCleanup());
|
||||
Object cleared3 = JSFinalizationRegistry::PopClearedCellHoldings(
|
||||
finalization_registry, isolate);
|
||||
Object cleared3 = PopClearedCellHoldings(finalization_registry, isolate);
|
||||
CHECK_EQ(cleared3, *holdings1);
|
||||
CHECK(weak_cell1->prev().IsUndefined(isolate));
|
||||
CHECK(weak_cell1->next().IsUndefined(isolate));
|
||||
@ -424,8 +448,7 @@ TEST(TestJSFinalizationRegistryPopClearedCellHoldings2) {
|
||||
*weak_cell1);
|
||||
}
|
||||
|
||||
Object cleared1 = JSFinalizationRegistry::PopClearedCellHoldings(
|
||||
finalization_registry, isolate);
|
||||
Object cleared1 = PopClearedCellHoldings(finalization_registry, isolate);
|
||||
CHECK_EQ(cleared1, *holdings2);
|
||||
|
||||
{
|
||||
@ -434,8 +457,7 @@ TEST(TestJSFinalizationRegistryPopClearedCellHoldings2) {
|
||||
VerifyWeakCellKeyChain(isolate, key_map, *token1, 1, *weak_cell1);
|
||||
}
|
||||
|
||||
Object cleared2 = JSFinalizationRegistry::PopClearedCellHoldings(
|
||||
finalization_registry, isolate);
|
||||
Object cleared2 = PopClearedCellHoldings(finalization_registry, isolate);
|
||||
CHECK_EQ(cleared2, *holdings1);
|
||||
|
||||
{
|
||||
@ -621,8 +643,7 @@ TEST(TestWeakCellUnregisterPopped) {
|
||||
NullifyWeakCell(weak_cell1, isolate);
|
||||
|
||||
CHECK(finalization_registry->NeedsCleanup());
|
||||
Object cleared1 = JSFinalizationRegistry::PopClearedCellHoldings(
|
||||
finalization_registry, isolate);
|
||||
Object cleared1 = PopClearedCellHoldings(finalization_registry, isolate);
|
||||
CHECK_EQ(cleared1, *holdings1);
|
||||
|
||||
VerifyWeakCellChain(isolate, finalization_registry->active_cells(), 0);
|
||||
|
@ -3,10 +3,12 @@
|
||||
^
|
||||
Error: callback
|
||||
at callback (*%(basename)s:{NUMBER}:{NUMBER})
|
||||
at FinalizationRegistry.cleanupSome (<anonymous>)
|
||||
|
||||
*%(basename)s:{NUMBER}: Error: callback
|
||||
throw new Error('callback');
|
||||
^
|
||||
Error: callback
|
||||
at callback (*%(basename)s:{NUMBER}:{NUMBER})
|
||||
at FinalizationRegistry.cleanupSome (<anonymous>)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user