Reland "[weakrefs] Clear unregister token-related fields when clearing weak cells"

This is a reland of 360c7afca5

Changes since revert:
  - Read the unregister token using a relaxed read during marking

Original change's description:
> [weakrefs] Clear unregister token-related fields when clearing weak cells
>
> Bug: chromium:1213770
> Change-Id: Ic063e79bfa8f3dabdd29d1cc9ed74c7af44d0c31
> Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2923294
> Reviewed-by: Michael Lippautz <mlippautz@chromium.org>
> Commit-Queue: Shu-yu Guo <syg@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#74890}

Bug: chromium:1213770
Change-Id: I8d0b946359b85a4760113e26dbaeaa9479e3b5fd
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2930554
Reviewed-by: Shu-yu Guo <syg@chromium.org>
Reviewed-by: Michael Lippautz <mlippautz@chromium.org>
Commit-Queue: Shu-yu Guo <syg@chromium.org>
Cr-Commit-Position: refs/heads/master@{#74925}
This commit is contained in:
Shu-yu Guo 2021-06-01 16:08:35 -07:00 committed by V8 LUCI CQ
parent 388c576f87
commit eb798db452
8 changed files with 34 additions and 24 deletions

View File

@ -143,21 +143,22 @@ FinalizationRegistryRegister(
ThrowTypeError( ThrowTypeError(
MessageTemplate::kWeakRefsRegisterTargetAndHoldingsMustNotBeSame); MessageTemplate::kWeakRefsRegisterTargetAndHoldingsMustNotBeSame);
} }
const unregisterToken = arguments[2];
// 5. If Type(unregisterToken) is not Object, // 5. If Type(unregisterToken) is not Object,
// a. If unregisterToken is not undefined, throw a TypeError exception. // a. If unregisterToken is not undefined, throw a TypeError exception.
// b. Set unregisterToken to empty. // b. Set unregisterToken to empty.
let hasUnregisterToken: bool = false; const unregisterTokenRaw = arguments[2];
typeswitch (unregisterToken) { let unregisterToken: JSReceiver|Undefined;
typeswitch (unregisterTokenRaw) {
case (Undefined): { case (Undefined): {
unregisterToken = Undefined;
} }
case (JSReceiver): { case (unregisterTokenObj: JSReceiver): {
hasUnregisterToken = true; unregisterToken = unregisterTokenObj;
} }
case (JSAny): deferred { case (JSAny): deferred {
ThrowTypeError( ThrowTypeError(
MessageTemplate::kWeakRefsUnregisterTokenMustBeObject, MessageTemplate::kWeakRefsUnregisterTokenMustBeObject,
unregisterToken); unregisterTokenRaw);
} }
} }
// 6. Let cell be the Record { [[WeakRefTarget]] : target, [[HeldValue]]: // 6. Let cell be the Record { [[WeakRefTarget]] : target, [[HeldValue]]:
@ -178,7 +179,7 @@ FinalizationRegistryRegister(
}; };
// 7. Append cell to finalizationRegistry.[[Cells]]. // 7. Append cell to finalizationRegistry.[[Cells]].
PushCell(finalizationRegistry, cell); PushCell(finalizationRegistry, cell);
if (hasUnregisterToken) { if (unregisterToken != Undefined) {
// If an unregister token is provided, a runtime call is needed to // If an unregister token is provided, a runtime call is needed to
// do some OrderedHashTable operations and register the mapping. // do some OrderedHashTable operations and register the mapping.
// See v8:10705. // See v8:10705.

View File

@ -2561,8 +2561,7 @@ void MarkCompactCollector::ClearJSWeakRefs() {
RecordSlot(weak_cell, slot, HeapObject::cast(*slot)); RecordSlot(weak_cell, slot, HeapObject::cast(*slot));
} }
HeapObject unregister_token = HeapObject unregister_token = weak_cell.unregister_token();
HeapObject::cast(weak_cell.unregister_token());
if (!non_atomic_marking_state()->IsBlackOrGrey(unregister_token)) { if (!non_atomic_marking_state()->IsBlackOrGrey(unregister_token)) {
// The unregister token is dead. Remove any corresponding entries in the // The unregister token is dead. Remove any corresponding entries in the
// key map. Multiple WeakCell with the same token will have all their // key map. Multiple WeakCell with the same token will have all their
@ -2578,11 +2577,6 @@ void MarkCompactCollector::ClearJSWeakRefs() {
matched_cell.set_unregister_token(undefined); matched_cell.set_unregister_token(undefined);
}, },
gc_notify_updated_slot); gc_notify_updated_slot);
// The following is necessary because in the case that weak_cell has
// already been popped and removed from the FinalizationRegistry, the call
// to JSFinalizationRegistry::RemoveUnregisterToken above will not find
// weak_cell itself to clear its unregister token.
weak_cell.set_unregister_token(undefined);
} else { } else {
// The unregister_token is alive. // The unregister_token is alive.
ObjectSlot slot = weak_cell.RawField(WeakCell::kUnregisterTokenOffset); ObjectSlot slot = weak_cell.RawField(WeakCell::kUnregisterTokenOffset);

View File

@ -330,7 +330,7 @@ int MarkingVisitorBase<ConcreteVisitor, MarkingState>::VisitWeakCell(
this->VisitMapPointer(weak_cell); this->VisitMapPointer(weak_cell);
WeakCell::BodyDescriptor::IterateBody(map, weak_cell, size, this); WeakCell::BodyDescriptor::IterateBody(map, weak_cell, size, this);
HeapObject target = weak_cell.relaxed_target(); HeapObject target = weak_cell.relaxed_target();
HeapObject unregister_token = HeapObject::cast(weak_cell.unregister_token()); HeapObject unregister_token = weak_cell.relaxed_unregister_token();
concrete_visitor()->SynchronizePageAccess(target); concrete_visitor()->SynchronizePageAccess(target);
concrete_visitor()->SynchronizePageAccess(unregister_token); concrete_visitor()->SynchronizePageAccess(unregister_token);
if (concrete_visitor()->marking_state()->IsBlackOrGrey(target) && if (concrete_visitor()->marking_state()->IsBlackOrGrey(target) &&

View File

@ -163,6 +163,10 @@ HeapObject WeakCell::relaxed_target() const {
return TaggedField<HeapObject>::Relaxed_Load(*this, kTargetOffset); return TaggedField<HeapObject>::Relaxed_Load(*this, kTargetOffset);
} }
HeapObject WeakCell::relaxed_unregister_token() const {
return TaggedField<HeapObject>::Relaxed_Load(*this, kUnregisterTokenOffset);
}
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) {

View File

@ -93,6 +93,9 @@ class WeakCell : public TorqueGeneratedWeakCell<WeakCell, HeapObject> {
// Provide relaxed load access to target field. // Provide relaxed load access to target field.
inline HeapObject relaxed_target() const; inline HeapObject relaxed_target() const;
// Provide relaxed load access to the unregister token field.
inline HeapObject relaxed_unregister_token() const;
// Nullify is called during GC and it modifies the pointers in WeakCell and // Nullify is called during GC and it modifies the pointers in WeakCell and
// JSFinalizationRegistry. Thus we need to tell the GC about the modified // JSFinalizationRegistry. Thus we need to tell the GC about the modified
// slots via the gc_notify_updated_slot function. The normal write barrier is // slots via the gc_notify_updated_slot function. The normal write barrier is

View File

@ -22,7 +22,7 @@ extern class JSFinalizationRegistry extends JSObject {
extern class WeakCell extends HeapObject { extern class WeakCell extends HeapObject {
finalization_registry: Undefined|JSFinalizationRegistry; finalization_registry: Undefined|JSFinalizationRegistry;
target: Undefined|JSReceiver; target: Undefined|JSReceiver;
unregister_token: JSAny; unregister_token: Undefined|JSReceiver;
holdings: JSAny; holdings: JSAny;
// For storing doubly linked lists of WeakCells in JSFinalizationRegistry's // For storing doubly linked lists of WeakCells in JSFinalizationRegistry's

View File

@ -6844,6 +6844,7 @@ void JSFinalizationRegistry::RemoveCellFromUnregisterTokenMap(
JSFinalizationRegistry::cast(Object(raw_finalization_registry)); JSFinalizationRegistry::cast(Object(raw_finalization_registry));
WeakCell weak_cell = WeakCell::cast(Object(raw_weak_cell)); WeakCell weak_cell = WeakCell::cast(Object(raw_weak_cell));
DCHECK(!weak_cell.unregister_token().IsUndefined(isolate)); DCHECK(!weak_cell.unregister_token().IsUndefined(isolate));
HeapObject undefined = ReadOnlyRoots(isolate).undefined_value();
// Remove weak_cell from the linked list of other WeakCells with the same // 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 // unregister token and remove its unregister token from key_map if necessary
@ -6852,7 +6853,7 @@ void JSFinalizationRegistry::RemoveCellFromUnregisterTokenMap(
if (weak_cell.key_list_prev().IsUndefined(isolate)) { if (weak_cell.key_list_prev().IsUndefined(isolate)) {
SimpleNumberDictionary key_map = SimpleNumberDictionary key_map =
SimpleNumberDictionary::cast(finalization_registry.key_map()); SimpleNumberDictionary::cast(finalization_registry.key_map());
Object unregister_token = weak_cell.unregister_token(); HeapObject unregister_token = weak_cell.unregister_token();
uint32_t key = Smi::ToInt(unregister_token.GetHash()); uint32_t key = Smi::ToInt(unregister_token.GetHash());
InternalIndex entry = key_map.FindEntry(isolate, key); InternalIndex entry = key_map.FindEntry(isolate, key);
DCHECK(entry.is_found()); DCHECK(entry.is_found());
@ -6867,8 +6868,7 @@ void JSFinalizationRegistry::RemoveCellFromUnregisterTokenMap(
// of the key in the hash table. // of the key in the hash table.
WeakCell next = WeakCell::cast(weak_cell.key_list_next()); WeakCell next = WeakCell::cast(weak_cell.key_list_next());
DCHECK_EQ(next.key_list_prev(), weak_cell); DCHECK_EQ(next.key_list_prev(), weak_cell);
next.set_key_list_prev(ReadOnlyRoots(isolate).undefined_value()); next.set_key_list_prev(undefined);
weak_cell.set_key_list_next(ReadOnlyRoots(isolate).undefined_value());
key_map.ValueAtPut(entry, next); key_map.ValueAtPut(entry, next);
} }
} else { } else {
@ -6880,6 +6880,12 @@ void JSFinalizationRegistry::RemoveCellFromUnregisterTokenMap(
next.set_key_list_prev(weak_cell.key_list_prev()); next.set_key_list_prev(weak_cell.key_list_prev());
} }
} }
// weak_cell is now removed from the unregister token map, so clear its
// unregister token-related fields for heap verification.
weak_cell.set_unregister_token(undefined);
weak_cell.set_key_list_prev(undefined);
weak_cell.set_key_list_next(undefined);
} }
} // namespace internal } // namespace internal

View File

@ -815,7 +815,7 @@ TEST(TestRemoveUnregisterToken) {
Handle<JSObject> token1 = CreateKey("token1", isolate); Handle<JSObject> token1 = CreateKey("token1", isolate);
Handle<JSObject> token2 = CreateKey("token2", isolate); Handle<JSObject> token2 = CreateKey("token2", isolate);
Handle<Object> undefined = Handle<HeapObject> undefined =
handle(ReadOnlyRoots(isolate).undefined_value(), isolate); handle(ReadOnlyRoots(isolate).undefined_value(), isolate);
Handle<WeakCell> weak_cell1a = FinalizationRegistryRegister( Handle<WeakCell> weak_cell1a = FinalizationRegistryRegister(
@ -979,15 +979,17 @@ TEST(UnregisterTokenHeapVerifier) {
v8::HandleScope outer_scope(isolate); v8::HandleScope outer_scope(isolate);
{ {
// Make a new FinalizationRegistry and register an object with an unregister // Make a new FinalizationRegistry and register two objects with the same
// token that's unreachable after the IIFE returns. // unregister token that's unreachable after the IIFE returns.
v8::HandleScope scope(isolate); v8::HandleScope scope(isolate);
CompileRun( CompileRun(
"var token = {}; " "var token = {}; "
"var registry = new FinalizationRegistry(function () {}); " "var registry = new FinalizationRegistry(function () {}); "
"(function () { " "(function () { "
" let o = {}; " " let o1 = {}; "
" registry.register(o, {}, token); " " let o2 = {}; "
" registry.register(o1, {}, token); "
" registry.register(o2, {}, token); "
"})();"); "})();");
} }