Commit dbbaccca authored by Shu-yu Guo's avatar Shu-yu Guo Committed by Commit Bot

[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: 's avatarMarja Hölttä <marja@chromium.org>
Reviewed-by: 's avatarTobias Tebbi <tebbi@chromium.org>
Reviewed-by: 's avatarUlan Degenbaev <ulan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#67161}
parent 4821ca2c
...@@ -1036,6 +1036,7 @@ torque_files = [ ...@@ -1036,6 +1036,7 @@ torque_files = [
"src/builtins/convert.tq", "src/builtins/convert.tq",
"src/builtins/console.tq", "src/builtins/console.tq",
"src/builtins/data-view.tq", "src/builtins/data-view.tq",
"src/builtins/finalization-registry.tq",
"src/builtins/frames.tq", "src/builtins/frames.tq",
"src/builtins/frame-arguments.tq", "src/builtins/frame-arguments.tq",
"src/builtins/growable-fixed-array.tq", "src/builtins/growable-fixed-array.tq",
...@@ -2915,6 +2916,7 @@ v8_source_set("v8_base_without_compiler") { ...@@ -2915,6 +2916,7 @@ v8_source_set("v8_base_without_compiler") {
"src/runtime/runtime-typedarray.cc", "src/runtime/runtime-typedarray.cc",
"src/runtime/runtime-utils.h", "src/runtime/runtime-utils.h",
"src/runtime/runtime-wasm.cc", "src/runtime/runtime-wasm.cc",
"src/runtime/runtime-weak-refs.cc",
"src/runtime/runtime.cc", "src/runtime/runtime.cc",
"src/runtime/runtime.h", "src/runtime/runtime.h",
"src/sanitizer/asan.h", "src/sanitizer/asan.h",
......
...@@ -8429,9 +8429,13 @@ Maybe<bool> FinalizationGroup::Cleanup( ...@@ -8429,9 +8429,13 @@ Maybe<bool> FinalizationGroup::Cleanup(
ENTER_V8(isolate, context, FinalizationGroup, Cleanup, Nothing<bool>(), ENTER_V8(isolate, context, FinalizationGroup, Cleanup, Nothing<bool>(),
i::HandleScope); i::HandleScope);
i::Handle<i::Object> callback(fr->cleanup(), isolate); i::Handle<i::Object> callback(fr->cleanup(), isolate);
i::Handle<i::Object> argv[] = {callback};
fr->set_scheduled_for_cleanup(false); fr->set_scheduled_for_cleanup(false);
has_pending_exception = 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_ON_FAILED_EXECUTION_PRIMITIVE(bool);
return Just(true); return Just(true);
} }
...@@ -11245,8 +11249,11 @@ void InvokeFinalizationRegistryCleanupFromTask( ...@@ -11245,8 +11249,11 @@ void InvokeFinalizationRegistryCleanupFromTask(
Local<v8::Context> api_context = Utils::ToLocal(context); Local<v8::Context> api_context = Utils::ToLocal(context);
CallDepthScope<true> call_depth_scope(isolate, api_context); CallDepthScope<true> call_depth_scope(isolate, api_context);
VMState<OTHER> state(isolate); VMState<OTHER> state(isolate);
if (JSFinalizationRegistry::Cleanup(isolate, finalization_registry, callback) Handle<Object> argv[] = {callback};
.IsNothing()) { if (Execution::CallBuiltin(isolate,
isolate->finalization_registry_cleanup_some(),
finalization_registry, arraysize(argv), argv)
.is_null()) {
call_depth_scope.Escape(); call_depth_scope.Escape();
} }
} }
......
...@@ -303,6 +303,7 @@ extern enum MessageTemplate { ...@@ -303,6 +303,7 @@ extern enum MessageTemplate {
kProxyGetPrototypeOfNonExtensible, kProxyGetPrototypeOfNonExtensible,
kProxySetPrototypeOfNonExtensible, kProxySetPrototypeOfNonExtensible,
kProxyDeletePropertyNonExtensible, kProxyDeletePropertyNonExtensible,
kWeakRefsCleanupMustBeCallable,
... ...
} }
...@@ -701,7 +702,8 @@ macro Float64IsNaN(n: float64): bool { ...@@ -701,7 +702,8 @@ macro Float64IsNaN(n: float64): bool {
} }
// The type of all tagged values that can safely be compared with TaggedEqual. // 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(TaggedWithIdentity, Object): bool;
extern operator '==' macro TaggedEqual(Object, TaggedWithIdentity): bool; extern operator '==' macro TaggedEqual(Object, TaggedWithIdentity): bool;
......
...@@ -967,7 +967,6 @@ namespace internal { ...@@ -967,7 +967,6 @@ namespace internal {
CPP(Trace) \ CPP(Trace) \
\ \
/* Weak refs */ \ /* Weak refs */ \
CPP(FinalizationRegistryCleanupSome) \
CPP(FinalizationRegistryConstructor) \ CPP(FinalizationRegistryConstructor) \
CPP(FinalizationRegistryRegister) \ CPP(FinalizationRegistryRegister) \
CPP(FinalizationRegistryUnregister) \ CPP(FinalizationRegistryUnregister) \
......
...@@ -122,43 +122,6 @@ BUILTIN(FinalizationRegistryUnregister) { ...@@ -122,43 +122,6 @@ BUILTIN(FinalizationRegistryUnregister) {
return *isolate->factory()->ToBoolean(success); 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) { BUILTIN(WeakRefConstructor) {
HandleScope scope(isolate); HandleScope scope(isolate);
Handle<JSFunction> target = args.target(); Handle<JSFunction> target = args.target();
......
// 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, ...@@ -13250,6 +13250,21 @@ TNode<String> CodeStubAssembler::TaggedToDirectString(TNode<Object> value,
return CAST(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( PrototypeCheckAssembler::PrototypeCheckAssembler(
compiler::CodeAssemblerState* state, Flags flags, compiler::CodeAssemblerState* state, Flags flags,
TNode<NativeContext> native_context, TNode<Map> initial_prototype_map, TNode<NativeContext> native_context, TNode<Map> initial_prototype_map,
......
...@@ -3842,6 +3842,10 @@ class V8_EXPORT_PRIVATE CodeStubAssembler ...@@ -3842,6 +3842,10 @@ class V8_EXPORT_PRIVATE CodeStubAssembler
TNode<Smi> RefillMathRandom(TNode<NativeContext> native_context); TNode<Smi> RefillMathRandom(TNode<NativeContext> native_context);
void RemoveFinalizationRegistryCellFromUnregisterTokenMap(
TNode<JSFinalizationRegistry> finalization_registry,
TNode<WeakCell> weak_cell);
private: private:
friend class CodeStubArguments; friend class CodeStubArguments;
......
...@@ -902,6 +902,10 @@ static int EnterMicrotaskContextWrapper(HandleScopeImplementer* hsi, ...@@ -902,6 +902,10 @@ static int EnterMicrotaskContextWrapper(HandleScopeImplementer* hsi,
FUNCTION_REFERENCE(call_enter_context_function, EnterMicrotaskContextWrapper) 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) { bool operator==(ExternalReference lhs, ExternalReference rhs) {
return lhs.address() == rhs.address(); return lhs.address() == rhs.address();
} }
......
...@@ -215,6 +215,8 @@ class StatsCounter; ...@@ -215,6 +215,8 @@ class StatsCounter;
V(atomic_pair_exchange_function, "atomic_pair_exchange_function") \ V(atomic_pair_exchange_function, "atomic_pair_exchange_function") \
V(atomic_pair_compare_exchange_function, \ V(atomic_pair_compare_exchange_function, \
"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) EXTERNAL_REFERENCE_LIST_INTL(V)
#ifdef V8_INTL_SUPPORT #ifdef V8_INTL_SUPPORT
......
...@@ -4338,6 +4338,15 @@ void Genesis::InitializeGlobal_harmony_weak_refs() { ...@@ -4338,6 +4338,15 @@ void Genesis::InitializeGlobal_harmony_weak_refs() {
SimpleInstallFunction(isolate(), finalization_registry_prototype, SimpleInstallFunction(isolate(), finalization_registry_prototype,
"unregister", "unregister",
Builtins::kFinalizationRegistryUnregister, 1, false); 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% // Create %WeakRefPrototype%
...@@ -4386,9 +4395,10 @@ void Genesis::InitializeGlobal_harmony_weak_refs_with_cleanup_some() { ...@@ -4386,9 +4395,10 @@ void Genesis::InitializeGlobal_harmony_weak_refs_with_cleanup_some() {
JSObject::cast(finalization_registry_fun->instance_prototype()), JSObject::cast(finalization_registry_fun->instance_prototype()),
isolate()); isolate());
SimpleInstallFunction(isolate(), finalization_registry_prototype, JSObject::AddProperty(isolate(), finalization_registry_prototype,
"cleanupSome", factory()->InternalizeUtf8String("cleanupSome"),
Builtins::kFinalizationRegistryCleanupSome, 0, false); isolate()->finalization_registry_cleanup_some(),
DONT_ENUM);
} }
void Genesis::InitializeGlobal_harmony_promise_all_settled() { void Genesis::InitializeGlobal_harmony_promise_all_settled() {
......
...@@ -346,6 +346,8 @@ enum ContextLookupFlags { ...@@ -346,6 +346,8 @@ enum ContextLookupFlags {
V(MAP_GET_INDEX, JSFunction, map_get) \ V(MAP_GET_INDEX, JSFunction, map_get) \
V(MAP_HAS_INDEX, JSFunction, map_has) \ V(MAP_HAS_INDEX, JSFunction, map_has) \
V(MAP_SET_INDEX, JSFunction, map_set) \ 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(FUNCTION_HAS_INSTANCE_INDEX, JSFunction, function_has_instance) \
V(OBJECT_TO_STRING, JSFunction, object_to_string) \ V(OBJECT_TO_STRING, JSFunction, object_to_string) \
V(OBJECT_VALUE_OF_FUNCTION_INDEX, JSFunction, object_value_of_function) \ V(OBJECT_VALUE_OF_FUNCTION_INDEX, JSFunction, object_value_of_function) \
......
...@@ -177,65 +177,6 @@ bool JSFinalizationRegistry::NeedsCleanup() const { ...@@ -177,65 +177,6 @@ bool JSFinalizationRegistry::NeedsCleanup() const {
return cleared_cells().IsWeakCell(); 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> template <typename GCNotifyUpdatedSlotCallback>
void WeakCell::Nullify(Isolate* isolate, void WeakCell::Nullify(Isolate* isolate,
GCNotifyUpdatedSlotCallback gc_notify_updated_slot) { GCNotifyUpdatedSlotCallback gc_notify_updated_slot) {
......
...@@ -61,17 +61,15 @@ class JSFinalizationRegistry : public JSObject { ...@@ -61,17 +61,15 @@ class JSFinalizationRegistry : public JSObject {
// Returns true if the cleared_cells list is non-empty. // Returns true if the cleared_cells list is non-empty.
inline bool NeedsCleanup() const; inline bool NeedsCleanup() const;
// Remove the first cleared WeakCell from the cleared_cells // Remove the already-popped weak_cell from its unregister token linked list,
// list (assumes there is one) and return its holdings. // as well as removing the entry from the key map if it is the only WeakCell
inline static Object PopClearedCellHoldings( // with its unregister token. This method cannot GC and does not shrink the
Handle<JSFinalizationRegistry> finalization_registry, Isolate* isolate); // key map. Asserts that weak_cell has a non-undefined unregister token.
// Call the user's cleanup function in a loop, once for each cleared cell.
// //
// Returns Nothing<bool> if exception occurs, otherwise returns Just(true). // It takes raw Addresses because it is called from CSA and Torque.
static V8_WARN_UNUSED_RESULT Maybe<bool> Cleanup( V8_EXPORT_PRIVATE static void RemoveCellFromUnregisterTokenMap(
Isolate* isolate, Handle<JSFinalizationRegistry> finalization_registry, Isolate* isolate, Address raw_finalization_registry,
Handle<Object> callback); Address raw_weak_cell);
// Layout description. // Layout description.
DEFINE_FIELD_OFFSET_CONSTANTS( DEFINE_FIELD_OFFSET_CONSTANTS(
......
...@@ -8306,35 +8306,50 @@ EXTERN_DEFINE_BASE_NAME_DICTIONARY(GlobalDictionary, GlobalDictionaryShape) ...@@ -8306,35 +8306,50 @@ EXTERN_DEFINE_BASE_NAME_DICTIONARY(GlobalDictionary, GlobalDictionaryShape)
#undef EXTERN_DEFINE_DICTIONARY #undef EXTERN_DEFINE_DICTIONARY
#undef EXTERN_DEFINE_BASE_NAME_DICTIONARY #undef EXTERN_DEFINE_BASE_NAME_DICTIONARY
Maybe<bool> JSFinalizationRegistry::Cleanup( void JSFinalizationRegistry::RemoveCellFromUnregisterTokenMap(
Isolate* isolate, Handle<JSFinalizationRegistry> finalization_registry, Isolate* isolate, Address raw_finalization_registry,
Handle<Object> cleanup) { Address raw_weak_cell) {
DCHECK(cleanup->IsCallable()); DisallowHeapAllocation no_gc;
// Attempt to shrink key_map now, as unregister tokens are held weakly and the JSFinalizationRegistry finalization_registry =
// map is not shrinkable when sweeping dead tokens during GC itself. JSFinalizationRegistry::cast(Object(raw_finalization_registry));
if (!finalization_registry->key_map().IsUndefined(isolate)) { WeakCell weak_cell = WeakCell::cast(Object(raw_weak_cell));
Handle<SimpleNumberDictionary> key_map( DCHECK(!weak_cell.unregister_token().IsUndefined(isolate));
SimpleNumberDictionary::cast(finalization_registry->key_map()),
isolate); // Remove weak_cell from the linked list of other WeakCells with the same
key_map = SimpleNumberDictionary::Shrink(isolate, key_map); // unregister token and remove its unregister token from key_map if necessary
finalization_registry->set_key_map(*key_map); // 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)) {
// It's possible that the cleared_cells list is empty, since SimpleNumberDictionary key_map =
// FinalizationRegistry.unregister() removed all its elements before this task SimpleNumberDictionary::cast(finalization_registry.key_map());
// ran. In that case, don't call the cleanup function. Object unregister_token = weak_cell.unregister_token();
while (!finalization_registry->cleared_cells().IsUndefined(isolate)) { uint32_t key = Smi::ToInt(unregister_token.GetHash());
Handle<Object> holding( InternalIndex entry = key_map.FindEntry(isolate, key);
PopClearedCellHoldings(finalization_registry, isolate), isolate); DCHECK(entry.is_found());
Handle<Object> args[] = {holding};
if (Execution::Call( if (weak_cell.key_list_next().IsUndefined(isolate)) {
isolate, cleanup, // weak_cell is the only one associated with its key; remove the key
handle(ReadOnlyRoots(isolate).undefined_value(), isolate), 1, args) // from the hash table.
.is_null()) { key_map.ClearEntry(entry);
return Nothing<bool>(); 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 } // namespace internal
......
// 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 { ...@@ -578,6 +578,9 @@ namespace internal {
F(WasmNewMultiReturnJSArray, 1, 1) \ F(WasmNewMultiReturnJSArray, 1, 1) \
F(WasmDebugBreak, 0, 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) \ #define FOR_EACH_INTRINSIC_RETURN_PAIR_IMPL(F, I) \
F(DebugBreakOnBytecode, 1, 2) \ F(DebugBreakOnBytecode, 1, 2) \
F(LoadLookupSlotForCall, 1, 2) F(LoadLookupSlotForCall, 1, 2)
...@@ -636,7 +639,8 @@ namespace internal { ...@@ -636,7 +639,8 @@ namespace internal {
FOR_EACH_INTRINSIC_SYMBOL(F, I) \ FOR_EACH_INTRINSIC_SYMBOL(F, I) \
FOR_EACH_INTRINSIC_TEST(F, I) \ FOR_EACH_INTRINSIC_TEST(F, I) \
FOR_EACH_INTRINSIC_TYPEDARRAY(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 // Defines the list of all intrinsics, coming in 2 flavors, either returning an
// object or a pair. // object or a pair.
......
...@@ -97,6 +97,33 @@ void NullifyWeakCell(Handle<WeakCell> weak_cell, Isolate* isolate) { ...@@ -97,6 +97,33 @@ void NullifyWeakCell(Handle<WeakCell> weak_cell, Isolate* isolate) {
#endif // VERIFY_HEAP #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); // Usage: VerifyWeakCellChain(isolate, list_head, n, cell1, cell2, ..., celln);
// verifies that list_head == cell1 and cell1, cell2, ..., celln. form a list. // verifies that list_head == cell1 and cell1, cell2, ..., celln. form a list.
void VerifyWeakCellChain(Isolate* isolate, Object list_head, int n_args, ...) { void VerifyWeakCellChain(Isolate* isolate, Object list_head, int n_args, ...) {
...@@ -361,15 +388,13 @@ TEST(TestJSFinalizationRegistryPopClearedCellHoldings1) { ...@@ -361,15 +388,13 @@ TEST(TestJSFinalizationRegistryPopClearedCellHoldings1) {
NullifyWeakCell(weak_cell3, isolate); NullifyWeakCell(weak_cell3, isolate);
CHECK(finalization_registry->NeedsCleanup()); CHECK(finalization_registry->NeedsCleanup());
Object cleared1 = JSFinalizationRegistry::PopClearedCellHoldings( Object cleared1 = PopClearedCellHoldings(finalization_registry, isolate);
finalization_registry, isolate);
CHECK_EQ(cleared1, *holdings3); CHECK_EQ(cleared1, *holdings3);
CHECK(weak_cell3->prev().IsUndefined(isolate)); CHECK(weak_cell3->prev().IsUndefined(isolate));
CHECK(weak_cell3->next().IsUndefined(isolate)); CHECK(weak_cell3->next().IsUndefined(isolate));
CHECK(finalization_registry->NeedsCleanup()); CHECK(finalization_registry->NeedsCleanup());
Object cleared2 = JSFinalizationRegistry::PopClearedCellHoldings( Object cleared2 = PopClearedCellHoldings(finalization_registry, isolate);
finalization_registry, isolate);
CHECK_EQ(cleared2, *holdings2); CHECK_EQ(cleared2, *holdings2);
CHECK(weak_cell2->prev().IsUndefined(isolate)); CHECK(weak_cell2->prev().IsUndefined(isolate));
CHECK(weak_cell2->next().IsUndefined(isolate)); CHECK(weak_cell2->next().IsUndefined(isolate));
...@@ -379,8 +404,7 @@ TEST(TestJSFinalizationRegistryPopClearedCellHoldings1) { ...@@ -379,8 +404,7 @@ TEST(TestJSFinalizationRegistryPopClearedCellHoldings1) {
NullifyWeakCell(weak_cell1, isolate); NullifyWeakCell(weak_cell1, isolate);
CHECK(finalization_registry->NeedsCleanup()); CHECK(finalization_registry->NeedsCleanup());
Object cleared3 = JSFinalizationRegistry::PopClearedCellHoldings( Object cleared3 = PopClearedCellHoldings(finalization_registry, isolate);
finalization_registry, isolate);
CHECK_EQ(cleared3, *holdings1); CHECK_EQ(cleared3, *holdings1);
CHECK(weak_cell1->prev().IsUndefined(isolate)); CHECK(weak_cell1->prev().IsUndefined(isolate));
CHECK(weak_cell1->next().IsUndefined(isolate)); CHECK(weak_cell1->next().IsUndefined(isolate));
...@@ -424,8 +448,7 @@ TEST(TestJSFinalizationRegistryPopClearedCellHoldings2) { ...@@ -424,8 +448,7 @@ TEST(TestJSFinalizationRegistryPopClearedCellHoldings2) {
*weak_cell1); *weak_cell1);
} }
Object cleared1 = JSFinalizationRegistry::PopClearedCellHoldings( Object cleared1 = PopClearedCellHoldings(finalization_registry, isolate);
finalization_registry, isolate);
CHECK_EQ(cleared1, *holdings2); CHECK_EQ(cleared1, *holdings2);
{ {
...@@ -434,8 +457,7 @@ TEST(TestJSFinalizationRegistryPopClearedCellHoldings2) { ...@@ -434,8 +457,7 @@ TEST(TestJSFinalizationRegistryPopClearedCellHoldings2) {
VerifyWeakCellKeyChain(isolate, key_map, *token1, 1, *weak_cell1); VerifyWeakCellKeyChain(isolate, key_map, *token1, 1, *weak_cell1);
} }
Object cleared2 = JSFinalizationRegistry::PopClearedCellHoldings( Object cleared2 = PopClearedCellHoldings(finalization_registry, isolate);
finalization_registry, isolate);
CHECK_EQ(cleared2, *holdings1); CHECK_EQ(cleared2, *holdings1);
{ {
...@@ -621,8 +643,7 @@ TEST(TestWeakCellUnregisterPopped) { ...@@ -621,8 +643,7 @@ TEST(TestWeakCellUnregisterPopped) {
NullifyWeakCell(weak_cell1, isolate); NullifyWeakCell(weak_cell1, isolate);
CHECK(finalization_registry->NeedsCleanup()); CHECK(finalization_registry->NeedsCleanup());
Object cleared1 = JSFinalizationRegistry::PopClearedCellHoldings( Object cleared1 = PopClearedCellHoldings(finalization_registry, isolate);
finalization_registry, isolate);
CHECK_EQ(cleared1, *holdings1); CHECK_EQ(cleared1, *holdings1);
VerifyWeakCellChain(isolate, finalization_registry->active_cells(), 0); VerifyWeakCellChain(isolate, finalization_registry->active_cells(), 0);
......
...@@ -3,10 +3,12 @@ ...@@ -3,10 +3,12 @@
^ ^
Error: callback Error: callback
at callback (*%(basename)s:{NUMBER}:{NUMBER}) at callback (*%(basename)s:{NUMBER}:{NUMBER})
at FinalizationRegistry.cleanupSome (<anonymous>)
*%(basename)s:{NUMBER}: Error: callback *%(basename)s:{NUMBER}: Error: callback
throw new Error('callback'); throw new Error('callback');
^ ^
Error: callback Error: callback
at callback (*%(basename)s:{NUMBER}:{NUMBER}) at callback (*%(basename)s:{NUMBER}:{NUMBER})
at FinalizationRegistry.cleanupSome (<anonymous>)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment