diff --git a/js/public/GCAPI.h b/js/public/GCAPI.h index e2543df260c9..11dd53221273 100644 --- a/js/public/GCAPI.h +++ b/js/public/GCAPI.h @@ -379,6 +379,12 @@ typedef enum JSGCParamKey { */ JSGC_HELPER_THREAD_COUNT = 41, + /** + * If the percentage of the tenured strings exceeds this threshold, string + * will be allocated in tenured heap instead. (Default is allocated in + * nursery.) + */ + JSGC_PRETENURE_STRING_THRESHOLD = 42, } JSGCParamKey; /* diff --git a/js/src/gc/GC.cpp b/js/src/gc/GC.cpp index cb3c6a32a09f..ada86b9f315f 100644 --- a/js/src/gc/GC.cpp +++ b/js/src/gc/GC.cpp @@ -1527,6 +1527,8 @@ uint32_t GCRuntime::getParameter(JSGCParamKey key, const AutoLockGC& lock) { return uint32_t(tunables.pretenureThreshold() * 100); case JSGC_PRETENURE_GROUP_THRESHOLD: return tunables.pretenureGroupThreshold(); + case JSGC_PRETENURE_STRING_THRESHOLD: + return uint32_t(tunables.pretenureStringThreshold() * 100); case JSGC_MIN_LAST_DITCH_GC_PERIOD: return tunables.minLastDitchGCPeriod().ToSeconds(); case JSGC_ZONE_ALLOC_DELAY_KB: diff --git a/js/src/gc/Nursery.cpp b/js/src/gc/Nursery.cpp index 40c8669778eb..1608e7690c9e 100644 --- a/js/src/gc/Nursery.cpp +++ b/js/src/gc/Nursery.cpp @@ -475,6 +475,14 @@ Cell* js::Nursery::allocateCell(Zone* zone, size_t size, JS::TraceKind kind) { return cell; } +Cell* js::Nursery::allocateString(JS::Zone* zone, size_t size) { + Cell* cell = allocateCell(zone, size, JS::TraceKind::String); + if (cell) { + zone->nurseryAllocatedStrings++; + } + return cell; +} + inline void* js::Nursery::allocate(size_t size) { MOZ_ASSERT(isEnabled()); MOZ_ASSERT(!JS::RuntimeHeapIsBusy()); @@ -1274,8 +1282,18 @@ size_t js::Nursery::doPretenuring(JSRuntime* rt, JS::GCReason reason, uint32_t numBigIntsTenured = 0; uint32_t numNurseryBigIntRealmsDisabled = 0; for (ZonesIter zone(gc, SkipAtoms); !zone.done(); zone.next()) { - bool disableNurseryStrings = pretenureStr && zone->allocNurseryStrings && - zone->tenuredStrings >= 30 * 1000; + // For some tests in JetStream2 and Kranken, the tenuredRate is high but the + // number of allocated strings is low. So we calculate the tenuredRate only + // if the number of string allocations is enough. + bool allocThreshold = zone->nurseryAllocatedStrings > 30000; + double tenuredRate = allocThreshold + ? double(zone->tenuredStrings) / + double(zone->nurseryAllocatedStrings) + : 0.0; + + bool disableNurseryStrings = + pretenureStr && zone->allocNurseryStrings && + tenuredRate > tunables().pretenureStringThreshold(); bool disableNurseryBigInts = pretenureBigInt && zone->allocNurseryBigInts && zone->tenuredBigInts >= 30 * 1000; if (disableNurseryStrings || disableNurseryBigInts) { @@ -1310,6 +1328,7 @@ size_t js::Nursery::doPretenuring(JSRuntime* rt, JS::GCReason reason, zone->tenuredStrings = 0; numBigIntsTenured += zone->tenuredBigInts; zone->tenuredBigInts = 0; + zone->nurseryAllocatedStrings = 0; } session.reset(); // End the minor GC session, if running one. stats().setStat(gcstats::STAT_NURSERY_STRING_REALMS_DISABLED, diff --git a/js/src/gc/Nursery.h b/js/src/gc/Nursery.h index 5d08ae6fc981..98cee37728c6 100644 --- a/js/src/gc/Nursery.h +++ b/js/src/gc/Nursery.h @@ -246,9 +246,7 @@ class Nursery { gc::Cell* allocateBigInt(JS::Zone* zone, size_t size) { return allocateCell(zone, size, JS::TraceKind::BigInt); } - gc::Cell* allocateString(JS::Zone* zone, size_t size) { - return allocateCell(zone, size, JS::TraceKind::String); - } + gc::Cell* allocateString(JS::Zone* zone, size_t size); static size_t nurseryCellHeaderSize() { return sizeof(gc::NurseryCellHeader); diff --git a/js/src/gc/Scheduling.cpp b/js/src/gc/Scheduling.cpp index bb93b50674c3..eab1665e8214 100644 --- a/js/src/gc/Scheduling.cpp +++ b/js/src/gc/Scheduling.cpp @@ -63,6 +63,7 @@ GCSchedulingTunables::GCSchedulingTunables() TuningDefaults::NurseryFreeThresholdForIdleCollectionFraction), pretenureThreshold_(TuningDefaults::PretenureThreshold), pretenureGroupThreshold_(TuningDefaults::PretenureGroupThreshold), + pretenureStringThreshold_(TuningDefaults::PretenureStringThreshold), minLastDitchGCPeriod_( TimeDuration::FromSeconds(TuningDefaults::MinLastDitchGCPeriod)), mallocThresholdBase_(TuningDefaults::MallocThresholdBase), @@ -192,6 +193,13 @@ bool GCSchedulingTunables::setParameter(JSGCParamKey key, uint32_t value, } pretenureGroupThreshold_ = value; break; + case JSGC_PRETENURE_STRING_THRESHOLD: + // 100 disables pretenuring + if (value == 0 || value > 100) { + return false; + } + pretenureStringThreshold_ = value / 100.0; + break; case JSGC_MIN_LAST_DITCH_GC_PERIOD: minLastDitchGCPeriod_ = TimeDuration::FromSeconds(value); break; @@ -333,6 +341,9 @@ void GCSchedulingTunables::resetParameter(JSGCParamKey key, case JSGC_PRETENURE_GROUP_THRESHOLD: pretenureGroupThreshold_ = TuningDefaults::PretenureGroupThreshold; break; + case JSGC_PRETENURE_STRING_THRESHOLD: + pretenureStringThreshold_ = TuningDefaults::PretenureStringThreshold; + break; case JSGC_MIN_LAST_DITCH_GC_PERIOD: minLastDitchGCPeriod_ = TimeDuration::FromSeconds(TuningDefaults::MinLastDitchGCPeriod); diff --git a/js/src/gc/Scheduling.h b/js/src/gc/Scheduling.h index 6fd9ffe61432..8a3772ebd2e5 100644 --- a/js/src/gc/Scheduling.h +++ b/js/src/gc/Scheduling.h @@ -412,6 +412,9 @@ static const double PretenureThreshold = 0.6; /* JSGC_PRETENURE_GROUP_THRESHOLD */ static const double PretenureGroupThreshold = 3000; +/* JSGC_PRETENURE_STRING_THRESHOLD */ +static const double PretenureStringThreshold = 0.55; + /* JSGC_MIN_LAST_DITCH_GC_PERIOD */ static const auto MinLastDitchGCPeriod = 60; // in seconds @@ -549,6 +552,15 @@ class GCSchedulingTunables { */ UnprotectedData pretenureGroupThreshold_; + /* + * JSGC_PRETENURE_STRING_THRESHOLD + * + * If the percentage of the tenured strings exceeds this threshold, string + * will be allocated in tenured heap instead. (Default is allocated in + * nursery.) + */ + MainThreadData pretenureStringThreshold_; + /* * JSGC_MIN_LAST_DITCH_GC_PERIOD * @@ -611,6 +623,7 @@ class GCSchedulingTunables { bool attemptPretenuring() const { return pretenureThreshold_ < 1.0; } double pretenureThreshold() const { return pretenureThreshold_; } uint32_t pretenureGroupThreshold() const { return pretenureGroupThreshold_; } + double pretenureStringThreshold() const { return pretenureStringThreshold_; } mozilla::TimeDuration minLastDitchGCPeriod() const { return minLastDitchGCPeriod_; diff --git a/js/src/gc/Zone.cpp b/js/src/gc/Zone.cpp index 889f7275c05e..6bcfeef477db 100644 --- a/js/src/gc/Zone.cpp +++ b/js/src/gc/Zone.cpp @@ -150,6 +150,7 @@ JS::Zone::Zone(JSRuntime* rt) data(this, nullptr), tenuredStrings(this, 0), tenuredBigInts(this, 0), + nurseryAllocatedStrings(this, 0), allocNurseryStrings(this, true), allocNurseryBigInts(this, true), suppressAllocationMetadataBuilder(this, false), diff --git a/js/src/gc/Zone.h b/js/src/gc/Zone.h index 16005c0b1ddb..1d8ce313739c 100644 --- a/js/src/gc/Zone.h +++ b/js/src/gc/Zone.h @@ -202,6 +202,8 @@ class Zone : public js::ZoneAllocator, public js::gc::GraphNodeBase { js::ZoneData tenuredStrings; js::ZoneData tenuredBigInts; + js::ZoneOrIonCompileData nurseryAllocatedStrings; + js::ZoneData allocNurseryStrings; js::ZoneData allocNurseryBigInts; diff --git a/js/src/jit/CompileWrappers.h b/js/src/jit/CompileWrappers.h index 81df98099ded..e15374f3159c 100644 --- a/js/src/jit/CompileWrappers.h +++ b/js/src/jit/CompileWrappers.h @@ -90,6 +90,7 @@ class CompileRuntime { }; class CompileZone { + friend class MacroAssembler; JS::Zone* zone(); public: diff --git a/js/src/jit/MacroAssembler.cpp b/js/src/jit/MacroAssembler.cpp index 6d3d4b3f770f..99bbcf165169 100644 --- a/js/src/jit/MacroAssembler.cpp +++ b/js/src/jit/MacroAssembler.cpp @@ -823,6 +823,8 @@ void MacroAssembler::nurseryAllocateString(Register result, Register temp, // with the nursery's end will always fail in such cases. CompileZone* zone = GetJitContext()->realm()->zone(); + uint64_t* allocStrsPtr = &zone->zone()->nurseryAllocatedStrings.ref(); + inc64(AbsoluteAddress(allocStrsPtr)); size_t thingSize = gc::Arena::thingSize(allocKind); bumpPointerAllocate(result, temp, fail, zone,