зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
b7b220ece8
Коммит
f3b48418af
|
@ -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,
|
||||
|
|
Загрузка…
Ссылка в новой задаче