Bug 1587096 - Part 3: Implmement FinalizationGroup r=sfink

Differential Revision: https://phabricator.services.mozilla.com/D49946

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Jon Coppeard 2019-11-01 10:37:47 +00:00
Родитель b7b220ece8
Коммит f3b48418af
18 изменённых файлов: 613 добавлений и 7 удалений

Просмотреть файл

@ -746,7 +746,7 @@ static const uint32_t JSCLASS_FOREGROUND_FINALIZE =
// application.
static const uint32_t JSCLASS_GLOBAL_APPLICATION_SLOTS = 5;
static const uint32_t JSCLASS_GLOBAL_SLOT_COUNT =
JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 25;
JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 26;
static constexpr uint32_t JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(uint32_t n) {
return JSCLASS_IS_GLOBAL |

Просмотреть файл

@ -8,6 +8,8 @@
#include "builtin/FinalizationGroupObject.h"
#include "mozilla/ScopeExit.h"
#include "gc/Zone.h"
#include "vm/GlobalObject.h"
@ -175,7 +177,10 @@ const ClassSpec FinalizationGroupObject::classSpec_ = {
methods_,
properties_};
const JSFunctionSpec FinalizationGroupObject::methods_[] = {JS_FS_END};
const JSFunctionSpec FinalizationGroupObject::methods_[] = {
JS_FN(js_register_str, register_, 2, 0),
JS_FN(js_unregister_str, unregister, 1, 0),
JS_FN(js_cleanupSome_str, cleanupSome, 0, 0), JS_FS_END};
const JSPropertySpec FinalizationGroupObject::properties_[] = {
JS_STRING_SYM_PS(toStringTag, "FinalizationGroup", JSPROP_READONLY),
@ -299,6 +304,301 @@ void FinalizationGroupObject::setCleanupJobActive(bool value) {
setReservedSlot(IsCleanupJobActiveSlot, BooleanValue(value));
}
// FinalizationGroup.prototype.register(target , holdings [, unregisterToken ])
// https://tc39.es/proposal-weakrefs/#sec-finalization-group.prototype.register
/* static */
bool FinalizationGroupObject::register_(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
// 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.
if (!args.thisv().isObject() ||
!args.thisv().toObject().is<FinalizationGroupObject>()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_NOT_A_FINALIZATION_GROUP,
"Receiver of FinalizationGroup.register call");
return false;
}
RootedFinalizationGroupObject group(
cx, &args.thisv().toObject().as<FinalizationGroupObject>());
// 4. If Type(target) is not Object, throw a TypeError exception.
if (!args.get(0).isObject()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_OBJECT_REQUIRED,
"target argument to FinalizationGroup.register");
return false;
}
RootedObject target(cx, &args[0].toObject());
// 5. If SameValue(target, holdings), throw a TypeError exception.
if (args.get(1).isObject() && &args.get(1).toObject() == target) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_HOLDINGS);
return false;
}
RootedValue holdings(cx, args.get(1));
// 6. If Type(unregisterToken) is not Object,
// a. If unregisterToken is not undefined, throw a TypeError exception.
if (!args.get(2).isUndefined() && !args.get(2).isObject()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_BAD_UNREGISTER_TOKEN,
"FinalizationGroup.register");
return false;
}
RootedObject unregisterToken(cx);
if (!args.get(2).isUndefined()) {
unregisterToken = &args[2].toObject();
}
// Create the finalization record representing this target and holdings.
Rooted<FinalizationRecordObject*> record(
cx, FinalizationRecordObject::create(cx, group, holdings));
if (!record) {
return false;
}
if (unregisterToken && !addRegistration(cx, group, unregisterToken, record)) {
return false;
}
auto guard = mozilla::MakeScopeExit([&] {
if (unregisterToken) {
removeRegistrationOnError(group, unregisterToken, record);
}
});
// Fully unwrap the target to pass it to the GC.
RootedObject unwrappedTarget(cx);
unwrappedTarget = CheckedUnwrapDynamic(target, cx);
if (!unwrappedTarget) {
ReportAccessDenied(cx);
return false;
}
// Wrap the record into the compartment of the target.
RootedObject wrappedRecord(cx, record);
AutoRealm ar(cx, unwrappedTarget);
if (!JS_WrapObject(cx, &wrappedRecord)) {
return false;
}
// Register the record with the target.
gc::GCRuntime* gc = &cx->runtime()->gc;
if (!gc->registerWithFinalizationGroup(cx, unwrappedTarget, wrappedRecord)) {
return false;
}
guard.release();
args.rval().setUndefined();
return true;
}
/* static */
bool FinalizationGroupObject::addRegistration(
JSContext* cx, HandleFinalizationGroupObject group,
HandleObject unregisterToken, HandleFinalizationRecordObject record) {
// Add the record to the list of records associated with this unregister
// token.
MOZ_ASSERT(unregisterToken);
MOZ_ASSERT(group->registrations());
auto& map = *group->registrations();
Rooted<FinalizationRecordVectorObject*> recordsObject(cx);
JSObject* obj = map.lookup(unregisterToken);
if (obj) {
recordsObject = &obj->as<FinalizationRecordVectorObject>();
} else {
recordsObject = FinalizationRecordVectorObject::create(cx);
if (!recordsObject || !map.add(cx, unregisterToken, recordsObject)) {
return false;
}
}
return recordsObject->append(record);
}
/* static */ void FinalizationGroupObject::removeRegistrationOnError(
HandleFinalizationGroupObject group, HandleObject unregisterToken,
HandleFinalizationRecordObject record) {
// Remove a registration if something went wrong before we added it to the
// target zone's map. Note that this can't remove a registration after that
// point.
MOZ_ASSERT(unregisterToken);
MOZ_ASSERT(group->registrations());
JS::AutoAssertNoGC nogc;
auto& map = *group->registrations();
JSObject* obj = map.lookup(unregisterToken);
MOZ_ASSERT(obj);
auto records = &obj->as<FinalizationRecordVectorObject>();
records->remove(record);
if (records->empty()) {
map.remove(unregisterToken);
}
}
// FinalizationGroup.prototype.unregister ( unregisterToken )
// https://tc39.es/proposal-weakrefs/#sec-finalization-group.prototype.unregister
/* static */
bool FinalizationGroupObject::unregister(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
// 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.
if (!args.thisv().isObject() ||
!args.thisv().toObject().is<FinalizationGroupObject>()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_NOT_A_FINALIZATION_GROUP,
"Receiver of FinalizationGroup.unregister call");
return false;
}
RootedFinalizationGroupObject group(
cx, &args.thisv().toObject().as<FinalizationGroupObject>());
// 4. If Type(unregisterToken) is not Object, throw a TypeError exception.
if (!args.get(0).isObject()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_BAD_UNREGISTER_TOKEN,
"FinalizationGroup.unregister");
return false;
}
RootedObject unregisterToken(cx, &args[0].toObject());
RootedObject obj(cx, group->registrations()->lookup(unregisterToken));
if (obj) {
auto& records = obj->as<FinalizationRecordVectorObject>().records();
MOZ_ASSERT(!records.empty());
for (FinalizationRecordObject* record : records) {
// Clear the fields of this record; it will be removed from the target's
// list when it is next swept.
record->clear();
}
group->registrations()->remove(unregisterToken);
}
args.rval().setBoolean(bool(obj));
return true;
}
// FinalizationGroup.prototype.cleanupSome ( [ callback ] )
// https://tc39.es/proposal-weakrefs/#sec-finalization-group.prototype.cleanupSome
bool FinalizationGroupObject::cleanupSome(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
// 1. Let finalizationGroup be the this value.
// 2. If Type(finalizationGroup) is not Object, throw a TypeError exception.
// 3. If finalizationGroup does not have [[Cells]] and
// [[IsFinalizationGroupCleanupJobActive]] internal slots, throw a
// TypeError exception.
if (!args.thisv().isObject() ||
!args.thisv().toObject().is<FinalizationGroupObject>()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_NOT_A_FINALIZATION_GROUP,
"Receiver of FinalizationGroup.cleanupSome call");
return false;
}
// 4. If finalizationGroup.[[IsFinalizationGroupCleanupJobActive]] is true,
// throw a TypeError exception.
RootedFinalizationGroupObject group(
cx, &args.thisv().toObject().as<FinalizationGroupObject>());
if (group->isCleanupJobActive()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_BAD_CLEANUP_STATE);
return false;
}
// 5. If callback is not undefined and IsCallable(callback) is false, throw a
// TypeError exception.
RootedObject cleanupCallback(cx);
if (!args.get(0).isUndefined()) {
cleanupCallback = ValueToCallable(cx, args.get(0), -1, NO_CONSTRUCT);
if (!cleanupCallback) {
return false;
}
}
if (!cleanupQueuedHoldings(cx, group, cleanupCallback)) {
return false;
}
args.rval().setUndefined();
return true;
}
// CleanupFinalizationGroup ( finalizationGroup [ , callback ] )
// https://tc39.es/proposal-weakrefs/#sec-cleanup-finalization-group
/* static */
bool FinalizationGroupObject::cleanupQueuedHoldings(
JSContext* cx, HandleFinalizationGroupObject group,
HandleObject callbackArg) {
MOZ_ASSERT(cx->realm() == group->realm());
// 2. If CheckForEmptyCells(finalizationGroup) is false, return.
HoldingsVector* holdings = group->holdingsToBeCleanedUp();
size_t initialLength = holdings->length();
if (initialLength == 0) {
return true;
}
// 3. Let iterator be
// !CreateFinalizationGroupCleanupIterator(finalizationGroup).
Rooted<FinalizationIteratorObject*> iterator(
cx, FinalizationIteratorObject::create(cx, group));
if (!iterator) {
return false;
}
// 4. If callback is undefined, set callback to
// finalizationGroup.[[CleanupCallback]].
RootedObject callback(cx, callbackArg);
if (!callbackArg) {
callback = group->cleanupCallback();
}
// 5. Set finalizationGroup.[[IsFinalizationGroupCleanupJobActive]] to true.
group->setCleanupJobActive(true);
// 6. Let result be Call(callback, undefined, iterator).
RootedValue rval(cx);
JS::AutoValueArray<1> args(cx);
args[0].setObject(*iterator);
bool ok = JS::Call(cx, UndefinedHandleValue, callback, args, &rval);
// Remove holdings that were iterated over.
size_t index = iterator->index();
MOZ_ASSERT(index <= initialLength);
MOZ_ASSERT(initialLength <= holdings->length());
if (index > 0) {
holdings->erase(holdings->begin(), holdings->begin() + index);
}
// 7. Set finalizationGroup.[[IsFinalizationGroupCleanupJobActive]] to false.
group->setCleanupJobActive(false);
// 8. Set iterator.[[FinalizationGroup]] to empty.
iterator->clearFinalizationGroup();
return ok;
}
///////////////////////////////////////////////////////////////////////////
// FinalizationIteratorObject
@ -315,14 +615,130 @@ const JSPropertySpec FinalizationIteratorObject::properties_[] = {
JS_PS_END};
/* static */
bool FinalizationIteratorObject::next(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setUndefined();
bool GlobalObject::initFinalizationIteratorProto(JSContext* cx,
Handle<GlobalObject*> global) {
Rooted<JSObject*> base(
cx, GlobalObject::getOrCreateIteratorPrototype(cx, global));
if (!base) {
return false;
}
RootedPlainObject proto(cx, NewObjectWithGivenProto<PlainObject>(cx, base));
if (!proto) {
return false;
}
if (!JS_DefineFunctions(cx, proto, FinalizationIteratorObject::methods_) ||
!JS_DefineProperties(cx, proto,
FinalizationIteratorObject::properties_)) {
return false;
}
global->setReservedSlot(FINALIZATION_ITERATOR_PROTO, ObjectValue(*proto));
return true;
}
/* static */ FinalizationIteratorObject* FinalizationIteratorObject::create(
JSContext* cx, HandleFinalizationGroupObject group) {
MOZ_ASSERT(group);
RootedObject proto(cx, GlobalObject::getOrCreateFinalizationIteratorPrototype(
cx, cx->global()));
if (!proto) {
return nullptr;
}
FinalizationIteratorObject* iterator =
NewObjectWithClassProto<FinalizationIteratorObject>(cx, proto);
if (!iterator) {
return nullptr;
}
iterator->initReservedSlot(FinalizationGroupSlot, ObjectValue(*group));
iterator->initReservedSlot(IndexSlot, Int32Value(0));
return iterator;
}
FinalizationGroupObject* FinalizationIteratorObject::finalizationGroup() const {
Value value = getReservedSlot(FinalizationGroupSlot);
if (value.isUndefined()) {
return nullptr;
}
return &value.toObject().as<FinalizationGroupObject>();
}
size_t FinalizationIteratorObject::index() const {
int32_t i = getReservedSlot(IndexSlot).toInt32();
MOZ_ASSERT(i >= 0);
return size_t(i);
}
void FinalizationIteratorObject::incIndex() {
int32_t i = index();
MOZ_ASSERT(i < INT32_MAX);
setReservedSlot(IndexSlot, Int32Value(i + 1));
}
void FinalizationIteratorObject::clearFinalizationGroup() {
MOZ_ASSERT(finalizationGroup());
setReservedSlot(FinalizationGroupSlot, UndefinedValue());
}
// %FinalizationGroupCleanupIteratorPrototype%.next()
// https://tc39.es/proposal-weakrefs/#sec-%finalizationgroupcleanupiterator%.next
/* static */
bool FinalizationIteratorObject::next(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
// 1. Let iterator be the this value.
// 2. If Type(iterator) is not Object, throw a TypeError exception.
// 3. If iterator does not have a [[FinalizationGroup]] internal slot, throw a
// TypeError exception.
if (!args.thisv().isObject() ||
!args.thisv().toObject().is<FinalizationIteratorObject>()) {
JS_ReportErrorNumberASCII(
cx, GetErrorMessage, nullptr, JSMSG_NOT_A_FINALIZATION_ITERATOR,
"Receiver of FinalizationGroupCleanupIterator.next call");
return false;
}
RootedFinalizationIteratorObject iterator(
cx, &args.thisv().toObject().as<FinalizationIteratorObject>());
// 4. If iterator.[[FinalizationGroup]] is empty, throw a TypeError exception.
// 5. Let finalizationGroup be iterator.[[FinalizationGroup]].
RootedFinalizationGroupObject group(cx, iterator->finalizationGroup());
if (!group) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_STALE_FINALIZATION_GROUP_ITERATOR);
return false;
}
// 8. If finalizationGroup.[[Cells]] contains a Record cell such that
// cell.[[Target]] is empty,
// a. Choose any such cell.
// b. Remove cell from finalizationGroup.[[Cells]].
// c. Return CreateIterResultObject(cell.[[Holdings]], false).
auto* holdings = group->holdingsToBeCleanedUp();
size_t index = iterator->index();
MOZ_ASSERT(index <= holdings->length());
if (index < holdings->length() && index < INT32_MAX) {
RootedValue value(cx, (*holdings)[index]);
JSObject* result = CreateIterResultObject(cx, value, false);
if (!result) {
return false;
}
iterator->incIndex();
args.rval().setObject(*result);
return true;
}
// 9. Otherwise, return CreateIterResultObject(undefined, true).
JSObject* result = CreateIterResultObject(cx, UndefinedHandleValue, true);
if (!result) {
return false;
}
args.rval().setObject(*result);
return true;
}

Просмотреть файл

@ -173,6 +173,17 @@ class FinalizationGroupObject : public NativeObject {
static const JSPropertySpec properties_[];
static bool construct(JSContext* cx, unsigned argc, Value* vp);
static bool register_(JSContext* cx, unsigned argc, Value* vp);
static bool unregister(JSContext* cx, unsigned argc, Value* vp);
static bool cleanupSome(JSContext* cx, unsigned argc, Value* vp);
static bool addRegistration(JSContext* cx,
HandleFinalizationGroupObject group,
HandleObject unregisterToken,
HandleFinalizationRecordObject record);
static void removeRegistrationOnError(HandleFinalizationGroupObject group,
HandleObject unregisterToken,
HandleFinalizationRecordObject record);
static void trace(JSTracer* trc, JSObject* obj);
static void finalize(JSFreeOp* fop, JSObject* obj);
@ -192,6 +203,9 @@ class FinalizationIteratorObject : public NativeObject {
FinalizationGroupObject* finalizationGroup() const;
size_t index() const;
void incIndex();
void clearFinalizationGroup();
private:
friend class GlobalObject;
static const JSFunctionSpec methods_[];

Просмотреть файл

@ -0,0 +1,94 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
* Finalization group GC implementation.
*/
#include "builtin/FinalizationGroupObject.h"
#include "gc/GCRuntime.h"
#include "gc/Zone.h"
#include "gc/PrivateIterators-inl.h"
using namespace js;
using namespace js::gc;
bool GCRuntime::registerWithFinalizationGroup(JSContext* cx,
HandleObject target,
HandleObject record) {
MOZ_ASSERT(!IsCrossCompartmentWrapper(target));
MOZ_ASSERT(
UncheckedUnwrapWithoutExpose(record)->is<FinalizationRecordObject>());
MOZ_ASSERT(target->compartment() == record->compartment());
auto& map = target->zone()->finalizationRecordMap();
auto ptr = map.lookupForAdd(target);
if (!ptr) {
if (!map.add(ptr, target, FinalizationRecordVector(target->zone()))) {
return false;
}
}
return ptr->value().append(record);
}
void GCRuntime::markFinalizationGroupData(JSTracer* trc) {
// The finalization groups and holdings for all targets are marked as roots.
for (GCZonesIter zone(this); !zone.done(); zone.next()) {
auto& map = zone->finalizationRecordMap();
for (Zone::FinalizationRecordMap::Enum e(map); !e.empty(); e.popFront()) {
e.front().value().trace(trc);
}
}
}
void GCRuntime::sweepFinalizationGroups(Zone* zone) {
// Queue holdings for cleanup for any entries whose target is dying and remove
// them from the map. Sweep remaining unregister tokens.
auto& map = zone->finalizationRecordMap();
for (Zone::FinalizationRecordMap::Enum e(map); !e.empty(); e.popFront()) {
auto& records = e.front().value();
if (IsAboutToBeFinalized(&e.front().mutableKey())) {
// Queue holdings for targets that are dying.
for (JSObject* obj : records) {
obj = UncheckedUnwrapWithoutExpose(obj);
auto record = &obj->as<FinalizationRecordObject>();
FinalizationGroupObject* group = record->group();
if (group) {
group->queueHoldingsToBeCleanedUp(record->holdings());
queueFinalizationGroupForCleanup(group);
}
}
e.removeFront();
} else {
// Update any pointers moved by the GC.
records.sweep();
// Remove records that have been unregistered.
records.eraseIf([](JSObject* obj) {
obj = UncheckedUnwrapWithoutExpose(obj);
auto record = &obj->as<FinalizationRecordObject>();
return !record->group();
});
}
}
}
void GCRuntime::queueFinalizationGroupForCleanup(
FinalizationGroupObject* group) {
// Prod the embedding to call us back later to run the finalization callbacks.
if (!group->isQueuedForCleanup()) {
callHostCleanupFinalizationGroupCallback(group);
group->setQueuedForCleanup(true);
}
}
bool GCRuntime::cleanupQueuedFinalizationGroup(
JSContext* cx, HandleFinalizationGroupObject group) {
group->setQueuedForCleanup(false);
bool ok = FinalizationGroupObject::cleanupQueuedHoldings(cx, group);
return ok;
}

Просмотреть файл

@ -2107,6 +2107,7 @@ void GCRuntime::sweepTypesAfterCompacting(Zone* zone) {
void GCRuntime::sweepZoneAfterCompacting(MovingTracer* trc, Zone* zone) {
MOZ_ASSERT(zone->isCollecting());
sweepTypesAfterCompacting(zone);
sweepFinalizationGroups(zone);
zone->sweepWeakMaps();
for (auto* cache : zone->weakCaches()) {
cache->sweep();
@ -5208,6 +5209,13 @@ static void SweepUniqueIds(GCParallelTask* task) {
}
}
void js::gc::SweepFinalizationGroups(GCParallelTask* task) {
for (SweepGroupZonesIter zone(task->gc); !zone.done(); zone.next()) {
AutoSetThreadIsSweeping threadIsSweeping(zone);
task->gc->sweepFinalizationGroups(zone);
}
}
void GCRuntime::startTask(GCParallelTask& task, gcstats::PhaseKind phase,
AutoLockHelperThreadState& lock) {
if (!CanUseExtraThreads() || !task.startWithLockHeld(lock)) {
@ -5466,6 +5474,9 @@ IncrementalProgress GCRuntime::beginSweepingSweepGroup(JSFreeOp* fop,
PhaseKind::SWEEP_WEAKMAPS, lock);
AutoRunParallelTask sweepUniqueIds(this, SweepUniqueIds,
PhaseKind::SWEEP_UNIQUEIDS, lock);
AutoRunParallelTask sweepFinalizationGroups(
this, SweepFinalizationGroups, PhaseKind::SWEEP_FINALIZATION_GROUPS,
lock);
WeakCacheTaskVector sweepCacheTasks;
if (!PrepareWeakCacheTasks(rt, &sweepCacheTasks)) {

Просмотреть файл

@ -248,6 +248,8 @@ class ZoneList {
ZoneList& operator=(const ZoneList& other) = delete;
};
void SweepFinalizationGroups(GCParallelTask* task);
class GCRuntime {
friend GCMarker::MarkQueueProgress GCMarker::processMarkQueue();
@ -412,6 +414,11 @@ class GCRuntime {
JS::DoCycleCollectionCallback setDoCycleCollectionCallback(
JS::DoCycleCollectionCallback callback);
bool registerWithFinalizationGroup(JSContext* cx, HandleObject target,
HandleObject record);
bool cleanupQueuedFinalizationGroup(JSContext* cx,
Handle<FinalizationGroupObject*> group);
void setFullCompartmentChecks(bool enable);
JS::Zone* getCurrentSweepGroup() { return currentSweepGroup; }
@ -650,6 +657,7 @@ class GCRuntime {
void traceRuntimeCommon(JSTracer* trc, TraceOrMarkRuntime traceOrMark);
void traceEmbeddingBlackRoots(JSTracer* trc);
void traceEmbeddingGrayRoots(JSTracer* trc);
void markFinalizationGroupData(JSTracer* trc);
void checkNoRuntimeRoots(AutoGCSession& session);
void maybeDoCycleCollection();
void findDeadCompartments();
@ -682,6 +690,9 @@ class GCRuntime {
void updateAtomsBitmap();
void sweepDebuggerOnMainThread(JSFreeOp* fop);
void sweepJitDataOnMainThread(JSFreeOp* fop);
void sweepFinalizationGroups(Zone* zone);
friend void SweepFinalizationGroups(GCParallelTask* task);
void queueFinalizationGroupForCleanup(FinalizationGroupObject* group);
IncrementalProgress endSweepingSweepGroup(JSFreeOp* fop, SliceBudget& budget);
IncrementalProgress performSweepActions(SliceBudget& sliceBudget);
IncrementalProgress sweepTypeInformation(JSFreeOp* fop, SliceBudget& budget);

Просмотреть файл

@ -138,6 +138,7 @@ PhaseKindGraphRoots = [
PhaseKind("SWEEP_LAZYSCRIPTS", "Sweep LazyScripts", 71),
PhaseKind("SWEEP_WEAKMAPS", "Sweep WeakMaps", 63),
PhaseKind("SWEEP_UNIQUEIDS", "Sweep Unique IDs", 64),
PhaseKind("SWEEP_FINALIZATION_GROUPS", "Sweep FinalizationGroups", 74),
PhaseKind("SWEEP_JIT_DATA", "Sweep JIT Data", 65),
PhaseKind("SWEEP_WEAK_CACHES", "Sweep Weak Caches", 66),
PhaseKind("SWEEP_MISC", "Sweep Miscellaneous", 29),

Просмотреть файл

@ -285,6 +285,8 @@ void js::gc::GCRuntime::traceRuntimeForMajorGC(JSTracer* trc,
trc, Compartment::NonGrayEdges);
}
markFinalizationGroupData(trc);
traceRuntimeCommon(trc, MarkRuntime);
}
@ -477,8 +479,8 @@ void js::gc::GCRuntime::finishRoots() {
rt->finishSelfHosting();
for (RealmsIter r(rt); !r.done(); r.next()) {
r->finishRoots();
for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
zone->finishRoots();
}
#ifdef JS_GC_ZEAL

Просмотреть файл

@ -182,6 +182,11 @@ bool ObjectWeakMap::add(JSContext* cx, JSObject* obj, JSObject* target) {
return true;
}
void ObjectWeakMap::remove(JSObject* key) {
MOZ_ASSERT(key);
map.remove(key);
}
void ObjectWeakMap::clear() { map.clear(); }
void ObjectWeakMap::trace(JSTracer* trc) { map.trace(trc); }

Просмотреть файл

@ -292,6 +292,7 @@ class ObjectWeakMap {
JSObject* lookup(const JSObject* obj);
bool add(JSContext* cx, JSObject* obj, JSObject* target);
void remove(JSObject* key);
void clear();
void trace(JSTracer* trc);

Просмотреть файл

@ -854,3 +854,12 @@ void Zone::clearScriptLCov(Realm* realm) {
}
}
}
void Zone::finishRoots() {
for (RealmsInZoneIter r(this); !r.done(); r.next()) {
r->finishRoots();
}
// Finalization callbacks are not called if we're shutting down.
finalizationRecordMap().clear();
}

Просмотреть файл

@ -631,6 +631,8 @@ class Zone : public js::ZoneAllocator, public js::gc::GraphNodeBase<JS::Zone> {
// a non-zero value since bug 1458011.
uint32_t detachedTypedObjects = 0;
void finishRoots();
private:
// A map from finalization group targets to a list of finalization records
// representing groups that the target is registered with and their associated

Просмотреть файл

@ -26,6 +26,7 @@ UNIFIED_SOURCES += [
'Allocator.cpp',
'AtomMarking.cpp',
'Barrier.cpp',
'FinalizationGroup.cpp',
'GC.cpp',
'GCTrace.cpp',
'Marking.cpp',

Просмотреть файл

@ -714,3 +714,11 @@ MSG_DEF(JSMSG_SC_BIGINT_DISABLED, 0, JSEXN_ERR, "BigInt not cloned - feature dis
// BinAST
MSG_DEF(JSMSG_BINAST, 1, JSEXN_SYNTAXERR, "BinAST Parsing Error: {0}")
// FinalizationGroup
MSG_DEF(JSMSG_NOT_A_FINALIZATION_GROUP, 1, JSEXN_TYPEERR, "{0} is not a FinalizationGroup")
MSG_DEF(JSMSG_NOT_A_FINALIZATION_ITERATOR, 1, JSEXN_TYPEERR, "{0} is not a FinalizationGroupCleanupIterator")
MSG_DEF(JSMSG_BAD_HOLDINGS, 0, JSEXN_TYPEERR, "The holdings passed to FinalizationGroup.register must not be the same as its target object")
MSG_DEF(JSMSG_BAD_UNREGISTER_TOKEN, 1, JSEXN_TYPEERR, "Invalid unregister token passed to {0}")
MSG_DEF(JSMSG_STALE_FINALIZATION_GROUP_ITERATOR, 0, JSEXN_TYPEERR, "Can't use stale finalization group iterator")
MSG_DEF(JSMSG_BAD_CLEANUP_STATE, 0, JSEXN_TYPEERR, "Can't call FinalizeGroup.cleanupSome while cleanup is in progress")

Просмотреть файл

@ -33,6 +33,7 @@
#include "builtin/AtomicsObject.h"
#include "builtin/Boolean.h"
#include "builtin/Eval.h"
#include "builtin/FinalizationGroupObject.h"
#include "builtin/JSON.h"
#include "builtin/MapObject.h"
#include "builtin/Promise.h"
@ -1291,6 +1292,15 @@ JS_PUBLIC_API void JS::SetHostCleanupFinalizationGroupCallback(
cx->runtime()->gc.setHostCleanupFinalizationGroupCallback(cb, data);
}
JS_PUBLIC_API bool JS::CleanupQueuedFinalizationGroup(JSContext* cx,
HandleObject group) {
AssertHeapIsIdle();
CHECK_THREAD(cx);
cx->check(group);
return cx->runtime()->gc.cleanupQueuedFinalizationGroup(
cx, group.as<FinalizationGroupObject>());
}
JS_PUBLIC_API bool JS_AddWeakPointerZonesCallback(JSContext* cx,
JSWeakPointerZonesCallback cb,
void* data) {

Просмотреть файл

@ -3246,6 +3246,13 @@ extern JS_PUBLIC_API bool IsMaybeWrappedSavedFrame(JSObject* obj);
*/
extern JS_PUBLIC_API bool IsUnwrappedSavedFrame(JSObject* obj);
/**
* Clean up a finalization group in response to the engine calling the
* HostCleanupFinalizationGroup callback.
*/
extern JS_PUBLIC_API bool CleanupQueuedFinalizationGroup(JSContext* cx,
HandleObject group);
} /* namespace JS */
namespace js {

Просмотреть файл

@ -74,6 +74,7 @@
MACRO(caseFirst, caseFirst, "caseFirst") \
MACRO(catch, catch_, "catch") \
MACRO(class, class_, "class") \
MACRO(cleanupSome, cleanupSome, "cleanupSome") \
MACRO(close, close, "close") \
MACRO(collation, collation, "collation") \
MACRO(collections, collections, "collections") \
@ -362,6 +363,7 @@
MACRO(RegExpTester, RegExpTester, "RegExpTester") \
MACRO(RegExp_prototype_Exec, RegExp_prototype_Exec, "RegExp_prototype_Exec") \
MACRO(region, region, "region") \
MACRO(register, register_, "register") \
MACRO(Reify, Reify, "Reify") \
MACRO(reject, reject, "reject") \
MACRO(rejected, rejected, "rejected") \
@ -446,6 +448,7 @@
MACRO(unitDisplay, unitDisplay, "unitDisplay") \
MACRO(uninitialized, uninitialized, "uninitialized") \
MACRO(unknown, unknown, "unknown") \
MACRO(unregister, unregister, "unregister") \
MACRO(unsized, unsized, "unsized") \
MACRO(unwatch, unwatch, "unwatch") \
MACRO(UnwrapAndCallRegExpBuiltinExec, UnwrapAndCallRegExpBuiltinExec, \

Просмотреть файл

@ -85,6 +85,7 @@ class GlobalObject : public NativeObject {
IMPORT_ENTRY_PROTO,
EXPORT_ENTRY_PROTO,
REQUESTED_MODULE_PROTO,
FINALIZATION_ITERATOR_PROTO,
REGEXP_STATICS,
RUNTIME_CODEGEN_ENABLED,
INTRINSICS,
@ -521,6 +522,12 @@ class GlobalObject : public NativeObject {
return &global->getPrototype(JSProto_FinalizationGroup).toObject();
}
static JSObject* getOrCreateFinalizationIteratorPrototype(
JSContext* cx, Handle<GlobalObject*> global) {
return getOrCreateObject(cx, global, FINALIZATION_ITERATOR_PROTO,
initFinalizationIteratorProto);
}
private:
typedef bool (*ObjectInitOp)(JSContext* cx, Handle<GlobalObject*> global);
@ -822,6 +829,10 @@ class GlobalObject : public NativeObject {
static bool initTypedObjectModule(JSContext* cx,
Handle<GlobalObject*> global);
// Implemented in builtin/FinalizationGroup.cpp
static bool initFinalizationIteratorProto(JSContext* cx,
Handle<GlobalObject*> global);
static bool initStandardClasses(JSContext* cx, Handle<GlobalObject*> global);
static bool initSelfHostingBuiltins(JSContext* cx,
Handle<GlobalObject*> global,