diff --git a/js/src/gc/GCRuntime.h b/js/src/gc/GCRuntime.h index a4ad292cce20..c2ff7fe72bb9 100644 --- a/js/src/gc/GCRuntime.h +++ b/js/src/gc/GCRuntime.h @@ -937,7 +937,8 @@ class GCRuntime void startTask(GCParallelTask& task, gcstats::PhaseKind phase, AutoLockHelperThreadState& locked); void joinTask(GCParallelTask& task, gcstats::PhaseKind phase, AutoLockHelperThreadState& locked); - private: + // Delete an empty zone group after its contents have been merged. + void deleteEmptyZoneGroup(ZoneGroup* group); private: enum IncrementalResult @@ -1096,7 +1097,10 @@ class GCRuntime UnprotectedData systemZoneGroup; // List of all zone groups (protected by the GC lock). - ActiveThreadOrGCTaskData groups; + private: + ActiveThreadOrGCTaskData groups_; + public: + ZoneGroupVector& groups() { return groups_.ref(); } // The unique atoms zone, which has no zone group. WriteOnceData atomsZone; diff --git a/js/src/gc/Zone.cpp b/js/src/gc/Zone.cpp index 42fb083cec47..343864a1fdd7 100644 --- a/js/src/gc/Zone.cpp +++ b/js/src/gc/Zone.cpp @@ -73,6 +73,8 @@ JS::Zone::Zone(JSRuntime* rt, ZoneGroup* group) Zone::~Zone() { + MOZ_ASSERT(compartments_.ref().empty()); + JSRuntime* rt = runtimeFromAnyThread(); if (this == rt->gc.systemZone) rt->gc.systemZone = nullptr; @@ -87,7 +89,8 @@ Zone::~Zone() #endif } -bool Zone::init(bool isSystemArg) +bool +Zone::init(bool isSystemArg) { isSystem = isSystemArg; return uniqueIds().init() && @@ -376,6 +379,21 @@ Zone::addTypeDescrObject(JSContext* cx, HandleObject obj) return true; } +void +Zone::deleteEmptyCompartment(JSCompartment* comp) +{ + MOZ_ASSERT(comp->zone() == this); + MOZ_ASSERT(arenas.checkEmptyArenaLists()); + for (auto& i : compartments()) { + if (i == comp) { + compartments().erase(&i); + comp->destroy(runtimeFromActiveCooperatingThread()->defaultFreeOp()); + return; + } + } + MOZ_CRASH("Compartment not found"); +} + ZoneList::ZoneList() : head(nullptr), tail(nullptr) {} diff --git a/js/src/gc/Zone.h b/js/src/gc/Zone.h index 6a9b37ebf985..17f884f03062 100644 --- a/js/src/gc/Zone.h +++ b/js/src/gc/Zone.h @@ -161,6 +161,7 @@ struct Zone : public JS::shadow::Zone, explicit Zone(JSRuntime* rt, js::ZoneGroup* group); ~Zone(); MOZ_MUST_USE bool init(bool isSystem); + void destroy(js::FreeOp *fop); private: js::ZoneGroup* const group_; @@ -622,6 +623,9 @@ struct Zone : public JS::shadow::Zone, keepShapeTables_ = b; } + // Delete an empty compartment after its contents have been merged. + void deleteEmptyCompartment(JSCompartment* comp); + private: js::ZoneGroupData jitZone_; @@ -654,8 +658,8 @@ class ZoneGroupsIter public: explicit ZoneGroupsIter(JSRuntime* rt) : iterMarker(&rt->gc) { - it = rt->gc.groups.ref().begin(); - end = rt->gc.groups.ref().end(); + it = rt->gc.groups().begin(); + end = rt->gc.groups().end(); if (!done() && (*it)->usedByHelperThread) next(); diff --git a/js/src/gc/ZoneGroup.cpp b/js/src/gc/ZoneGroup.cpp index 11e53204100a..7ecd7927b607 100644 --- a/js/src/gc/ZoneGroup.cpp +++ b/js/src/gc/ZoneGroup.cpp @@ -130,4 +130,20 @@ ZoneGroup::ionLazyLinkListAdd(jit::IonBuilder* builder) ionLazyLinkListSize_++; } +void +ZoneGroup::deleteEmptyZone(Zone* zone) +{ + MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime)); + MOZ_ASSERT(zone->group() == this); + MOZ_ASSERT(zone->compartments().empty()); + for (auto& i : zones()) { + if (i == zone) { + zones().erase(&i); + zone->destroy(runtime->defaultFreeOp()); + return; + } + } + MOZ_CRASH("Zone not found"); +} + } // namespace js diff --git a/js/src/gc/ZoneGroup.h b/js/src/gc/ZoneGroup.h index de49b5dcf0f8..7901793d7973 100644 --- a/js/src/gc/ZoneGroup.h +++ b/js/src/gc/ZoneGroup.h @@ -76,6 +76,9 @@ class ZoneGroup // See the useExclusiveLocking field above. void setUseExclusiveLocking() { useExclusiveLocking = true; } + // Delete an empty zone after its contents have been merged. + void deleteEmptyZone(Zone* zone); + #ifdef DEBUG private: // The number of possible bailing places encounters before forcefully bailing diff --git a/js/src/jscompartment.h b/js/src/jscompartment.h index 714462b91c80..6e07cef4e4a6 100644 --- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -863,6 +863,7 @@ struct JSCompartment ~JSCompartment(); MOZ_MUST_USE bool init(JSContext* maybecx); + void destroy(js::FreeOp* fop); MOZ_MUST_USE inline bool wrap(JSContext* cx, JS::MutableHandleValue vp); diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index c3ca45bcda39..6a187c53e09d 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -1220,11 +1220,12 @@ GCRuntime::finish() for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) { for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next()) js_delete(comp.get()); + zone->compartments().clear(); js_delete(zone.get()); } } - groups.ref().clear(); + groups().clear(); FreeChunkPool(rt, fullChunks_.ref()); FreeChunkPool(rt, availableChunks_.ref()); @@ -3483,6 +3484,25 @@ JS::Zone::sweepUniqueIds(js::FreeOp* fop) uniqueIds().sweep(); } +void +JSCompartment::destroy(FreeOp* fop) +{ + JSRuntime* rt = fop->runtime(); + if (auto callback = rt->destroyCompartmentCallback) + callback(fop, this); + if (principals()) + JS_DropPrincipals(TlsContext.get(), principals()); + fop->delete_(this); + rt->gc.stats().sweptCompartment(); +} + +void +Zone::destroy(FreeOp* fop) +{ + fop->delete_(this); + fop->runtime()->gc.stats().sweptZone(); +} + /* * It's simpler if we preserve the invariant that every zone has at least one * compartment. If we know we're deleting the entire zone, then @@ -3495,8 +3515,9 @@ JS::Zone::sweepUniqueIds(js::FreeOp* fop) void Zone::sweepCompartments(FreeOp* fop, bool keepAtleastOne, bool destroyingRuntime) { - JSRuntime* rt = runtimeFromActiveCooperatingThread(); - JSDestroyCompartmentCallback callback = rt->destroyCompartmentCallback; + MOZ_ASSERT(!compartments().empty()); + + mozilla::DebugOnly rt = runtimeFromActiveCooperatingThread(); JSCompartment** read = compartments().begin(); JSCompartment** end = compartments().end(); @@ -3512,12 +3533,7 @@ Zone::sweepCompartments(FreeOp* fop, bool keepAtleastOne, bool destroyingRuntime */ bool dontDelete = read == end && !foundOne && keepAtleastOne; if ((!comp->marked && !dontDelete) || destroyingRuntime) { - if (callback) - callback(fop, comp); - if (comp->principals()) - JS_DropPrincipals(TlsContext.get(), comp->principals()); - js_delete(comp); - rt->gc.stats().sweptCompartment(); + comp->destroy(fop); } else { *write++ = comp; foundOne = true; @@ -3530,6 +3546,8 @@ Zone::sweepCompartments(FreeOp* fop, bool keepAtleastOne, bool destroyingRuntime void GCRuntime::sweepZones(FreeOp* fop, ZoneGroup* group, bool destroyingRuntime) { + MOZ_ASSERT(!group->zones().empty()); + Zone** read = group->zones().begin(); Zone** end = group->zones().end(); Zone** write = read; @@ -3558,8 +3576,7 @@ GCRuntime::sweepZones(FreeOp* fop, ZoneGroup* group, bool destroyingRuntime) zone->sweepCompartments(fop, false, destroyingRuntime); MOZ_ASSERT(zone->compartments().empty()); MOZ_ASSERT_IF(arenasEmptyAtShutdown, zone->typeDescrObjects().empty()); - fop->delete_(zone); - stats().sweptZone(); + zone->destroy(fop); continue; } zone->sweepCompartments(fop, true, destroyingRuntime); @@ -3580,8 +3597,8 @@ GCRuntime::sweepZoneGroups(FreeOp* fop, bool destroyingRuntime) assertBackgroundSweepingFinished(); - ZoneGroup** read = groups.ref().begin(); - ZoneGroup** end = groups.ref().end(); + ZoneGroup** read = groups().begin(); + ZoneGroup** end = groups().end(); ZoneGroup** write = read; while (read < end) { @@ -3595,7 +3612,7 @@ GCRuntime::sweepZoneGroups(FreeOp* fop, bool destroyingRuntime) *write++ = group; } } - groups.ref().shrinkTo(write - groups.ref().begin()); + groups().shrinkTo(write - groups().begin()); } #ifdef DEBUG @@ -7367,7 +7384,7 @@ js::NewCompartment(JSContext* cx, JSPrincipals* principals, } if (groupHolder) { - if (!rt->gc.groups.ref().append(group)) { + if (!rt->gc.groups().append(group)) { ReportOutOfMemory(cx); return nullptr; } @@ -7397,6 +7414,10 @@ gc::MergeCompartments(JSCompartment* source, JSCompartment* target) MOZ_ASSERT(source->creationOptions().addonIdOrNull() == target->creationOptions().addonIdOrNull()); + MOZ_ASSERT(!source->hasBeenEntered()); + MOZ_ASSERT(source->zone()->compartments().length() == 1); + MOZ_ASSERT(source->zone()->group()->zones().length() == 1); + JSContext* cx = source->runtimeFromActiveCooperatingThread()->activeContextFromOwnThread(); MOZ_ASSERT(!source->zone()->wasGCStarted()); @@ -7489,6 +7510,32 @@ gc::MergeCompartments(JSCompartment* source, JSCompartment* target) source->scriptNameMap->clear(); } + + // The source compartment is now completely empty, and is the only + // compartment in its zone, which is the only zone in its group. Delete + // compartment, zone and group without waiting for this to be cleaned up by + // a full GC. + + Zone* sourceZone = source->zone(); + ZoneGroup* sourceGroup = sourceZone->group(); + sourceZone->deleteEmptyCompartment(source); + sourceGroup->deleteEmptyZone(sourceZone); + cx->runtime()->gc.deleteEmptyZoneGroup(sourceGroup); +} + +void +GCRuntime::deleteEmptyZoneGroup(ZoneGroup* group) +{ + MOZ_ASSERT(group->zones().empty()); + MOZ_ASSERT(groups().length() > 1); + for (auto& i : groups()) { + if (i == group) { + groups().erase(&i); + js_delete(group); + return; + } + } + MOZ_CRASH("ZoneGroup not found"); } void diff --git a/js/src/vm/HelperThreads.cpp b/js/src/vm/HelperThreads.cpp index 4fd85ba48a17..af56832ab007 100644 --- a/js/src/vm/HelperThreads.cpp +++ b/js/src/vm/HelperThreads.cpp @@ -1598,9 +1598,10 @@ GlobalHelperThreadState::mergeParseTaskCompartment(JSContext* cx, ParseTask* par JS::AutoAssertNoGC nogc(cx); LeaveParseTaskZone(cx->runtime(), parseTask); - AutoCompartment ac(cx, parseTask->parseGlobal); { + AutoCompartment ac(cx, parseTask->parseGlobal); + // Generator functions don't have Function.prototype as prototype but a // different function object, so the IdentifyStandardPrototype trick // below won't work. Just special-case it.