diff --git a/js/src/gc/Allocator.cpp b/js/src/gc/Allocator.cpp index 30816e567f73..49dc3f3e2890 100644 --- a/js/src/gc/Allocator.cpp +++ b/js/src/gc/Allocator.cpp @@ -264,6 +264,7 @@ GCRuntime::tryNewTenuredThing(JSContext* cx, AllocKind kind, size_t thingSize) checkIncrementalZoneState(cx, t); gcTracer.traceTenuredAlloc(t, kind); + cx->noteTenuredAlloc(); return t; } diff --git a/js/src/gc/GC.cpp b/js/src/gc/GC.cpp index c0983d99ecad..6ae1b5c41f56 100644 --- a/js/src/gc/GC.cpp +++ b/js/src/gc/GC.cpp @@ -7935,6 +7935,14 @@ GCRuntime::minorGC(JS::gcreason::Reason reason, gcstats::PhaseKind phase) if (rt->mainContextFromOwnThread()->suppressGC) return; + // Note that we aren't collecting the updated alloc counts from any helper + // threads. We should be but I'm not sure where to add that + // synchronisation. + uint32_t numAllocs = rt->mainContextFromOwnThread()->getAndResetAllocsThisZoneSinceMinorGC(); + for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) + numAllocs += zone->getAndResetTenuredAllocsSinceMinorGC(); + rt->gc.stats().setAllocsSinceMinorGCTenured(numAllocs); + gcstats::AutoPhase ap(rt->gc.stats(), phase); nursery().clearMinorGCRequest(); @@ -8220,6 +8228,8 @@ GCRuntime::mergeRealms(Realm* source, Realm* target) // Merge the allocator, stats and UIDs in source's zone into target's zone. target->zone()->arenas.adoptArenas(&source->zone()->arenas, targetZoneIsCollecting); + target->zone()->addTenuredAllocsSinceMinorGC( + source->zone()->getAndResetTenuredAllocsSinceMinorGC()); target->zone()->usage.adopt(source->zone()->usage); target->zone()->adoptUniqueIds(source->zone()); target->zone()->adoptMallocBytes(source->zone()); diff --git a/js/src/gc/GCRuntime.h b/js/src/gc/GCRuntime.h index be6f0d699d53..b677ce44bcfb 100644 --- a/js/src/gc/GCRuntime.h +++ b/js/src/gc/GCRuntime.h @@ -1023,6 +1023,9 @@ class GCRuntime const void* addressOfStringNurseryCurrentEnd() { return nursery_.refNoCheck().addressOfCurrentStringEnd(); } + uint32_t* addressOfNurseryAllocCount() { + return stats().addressOfAllocsSinceMinorGCNursery(); + } void minorGC(JS::gcreason::Reason reason, gcstats::PhaseKind phase = gcstats::PhaseKind::MINOR_GC) JS_HAZ_GC_CALL; diff --git a/js/src/gc/Nursery.cpp b/js/src/gc/Nursery.cpp index 3daa0a24d8de..baa5b984b47a 100644 --- a/js/src/gc/Nursery.cpp +++ b/js/src/gc/Nursery.cpp @@ -396,6 +396,7 @@ js::Nursery::allocate(size_t size) void* thing = (void*)position(); position_ = position() + size; + runtime()->gc.stats().noteNurseryAlloc(); JS_EXTRA_POISON(thing, JS_ALLOCATED_NURSERY_PATTERN, size, MemCheckKind::MakeUndefined); @@ -620,6 +621,8 @@ js::Nursery::renderProfileJSON(JSONPrinter& json) const json.property("lazy_capacity", previousGC.nurseryLazyCapacity); if (!timeInChunkAlloc_.IsZero()) json.property("chunk_alloc_us", timeInChunkAlloc_, json.MICROSECONDS); + json.property("cells_allocated_nursery", runtime()->gc.stats().allocsSinceMinorGCNursery()); + json.property("cells_allocated_tenured", runtime()->gc.stats().allocsSinceMinorGCTenured()); json.beginObjectProperty("phase_times"); diff --git a/js/src/gc/Statistics.cpp b/js/src/gc/Statistics.cpp index 0ce40937b011..b0602c5e87c0 100644 --- a/js/src/gc/Statistics.cpp +++ b/js/src/gc/Statistics.cpp @@ -752,6 +752,7 @@ Statistics::Statistics(JSRuntime* rt) gcTimerFile(nullptr), gcDebugFile(nullptr), nonincrementalReason_(gc::AbortReason::None), + allocsSinceMinorGC({0, 0}), preBytes(0), thresholdTriggered(false), triggerAmount(0.0), @@ -1046,6 +1047,8 @@ Statistics::endNurseryCollection(JS::gcreason::Reason reason) JS::GCNurseryProgress::GC_NURSERY_COLLECTION_END, reason); } + + allocsSinceMinorGC = {0, 0}; } void diff --git a/js/src/gc/Statistics.h b/js/src/gc/Statistics.h index 153ba6df5967..30d7178a7f3a 100644 --- a/js/src/gc/Statistics.h +++ b/js/src/gc/Statistics.h @@ -192,6 +192,27 @@ struct Statistics thresholdTriggered = true; } + void noteNurseryAlloc() { + allocsSinceMinorGC.nursery++; + } + + // tenured allocs don't include nursery evictions. + void setAllocsSinceMinorGCTenured(uint32_t allocs) { + allocsSinceMinorGC.tenured = allocs; + } + + uint32_t allocsSinceMinorGCNursery() { + return allocsSinceMinorGC.nursery; + } + + uint32_t allocsSinceMinorGCTenured() { + return allocsSinceMinorGC.tenured; + } + + uint32_t* addressOfAllocsSinceMinorGCNursery() { + return &allocsSinceMinorGC.nursery; + } + void beginNurseryCollection(JS::gcreason::Reason reason); void endNurseryCollection(JS::gcreason::Reason reason); @@ -315,6 +336,15 @@ struct Statistics mozilla::Atomic> counts; + /* + * These events cannot be kept in the above array, we need to take their + * address. + */ + struct { + uint32_t nursery; + uint32_t tenured; + } allocsSinceMinorGC; + /* Allocated space before the GC started. */ size_t preBytes; diff --git a/js/src/gc/Zone.cpp b/js/src/gc/Zone.cpp index 24c1b2cf47b4..5c4a87c3c961 100644 --- a/js/src/gc/Zone.cpp +++ b/js/src/gc/Zone.cpp @@ -34,6 +34,7 @@ JS::Zone::Zone(JSRuntime* rt) uniqueIds_(this), suppressAllocationMetadataBuilder(this, false), arenas(this), + tenuredAllocsSinceMinorGC_(0), types(this), gcWeakMapList_(this), compartments_(), diff --git a/js/src/gc/Zone.h b/js/src/gc/Zone.h index 3ad829971fd1..7f2529638c44 100644 --- a/js/src/gc/Zone.h +++ b/js/src/gc/Zone.h @@ -340,6 +340,19 @@ class Zone : public JS::shadow::Zone, js::gc::ArenaLists arenas; + private: + // Number of allocations since the most recent minor GC for this thread. + mozilla::Atomic tenuredAllocsSinceMinorGC_; + + public: + void addTenuredAllocsSinceMinorGC(uint32_t allocs) { + tenuredAllocsSinceMinorGC_ += allocs; + } + + uint32_t getAndResetTenuredAllocsSinceMinorGC() { + return tenuredAllocsSinceMinorGC_.exchange(0); + } + js::TypeZone types; private: diff --git a/js/src/jit/CompileWrappers.cpp b/js/src/jit/CompileWrappers.cpp index 2a2363f4b6fa..74a07f79ea4f 100644 --- a/js/src/jit/CompileWrappers.cpp +++ b/js/src/jit/CompileWrappers.cpp @@ -214,6 +214,12 @@ CompileZone::addressOfStringNurseryCurrentEnd() return zone()->runtimeFromAnyThread()->gc.addressOfStringNurseryCurrentEnd(); } +uint32_t* +CompileZone::addressOfNurseryAllocCount() +{ + return zone()->runtimeFromAnyThread()->gc.addressOfNurseryAllocCount(); +} + bool CompileZone::canNurseryAllocateStrings() { diff --git a/js/src/jit/CompileWrappers.h b/js/src/jit/CompileWrappers.h index b349e4ea90e3..39221bb02347 100644 --- a/js/src/jit/CompileWrappers.h +++ b/js/src/jit/CompileWrappers.h @@ -82,6 +82,8 @@ class CompileZone const void* addressOfNurseryCurrentEnd(); const void* addressOfStringNurseryCurrentEnd(); + uint32_t* addressOfNurseryAllocCount(); + bool nurseryExists(); bool canNurseryAllocateStrings(); void setMinorGCShouldCancelIonCompilations(); diff --git a/js/src/jit/MacroAssembler.cpp b/js/src/jit/MacroAssembler.cpp index d09e88d97ea1..358030be4de1 100644 --- a/js/src/jit/MacroAssembler.cpp +++ b/js/src/jit/MacroAssembler.cpp @@ -831,6 +831,12 @@ MacroAssembler::freeListAllocate(Register result, Register temp, gc::AllocKind a Pop(result); bind(&success); + + if (GetJitContext()->cx) { + uint32_t* countAddress = GetJitContext()->cx->addressOfTenuredAllocCount(); + movePtr(ImmPtr(countAddress), temp); + add32(Imm32(1), Address(temp, 0)); + } } void @@ -993,6 +999,17 @@ MacroAssembler::bumpPointerAllocate(Register result, Register temp, Label* fail, branchPtr(Assembler::Below, Address(temp, endOffset.value()), result, fail); storePtr(result, Address(temp, 0)); subPtr(Imm32(size), result); + + CompileZone* zone = GetJitContext()->realm->zone(); + uint32_t* countAddress = zone->addressOfNurseryAllocCount(); + CheckedInt counterOffset = (CheckedInt(uintptr_t(countAddress)) - + CheckedInt(uintptr_t(posAddr))).toChecked(); + if (counterOffset.isValid()) { + add32(Imm32(1), Address(temp, counterOffset.value())); + } else { + movePtr(ImmPtr(countAddress), temp); + add32(Imm32(1), Address(temp, 0)); + } } // Inlined equivalent of gc::AllocateString, jumping to fail if nursery diff --git a/js/src/vm/JSContext-inl.h b/js/src/vm/JSContext-inl.h index 4b6d0569c762..567550a8a911 100644 --- a/js/src/vm/JSContext-inl.h +++ b/js/src/vm/JSContext-inl.h @@ -422,6 +422,11 @@ JSContext::enterAtomsZone() inline void JSContext::setZone(js::Zone *zone, JSContext::IsAtomsZone isAtomsZone) { + if (zone_) + zone_->addTenuredAllocsSinceMinorGC(allocsThisZoneSinceMinorGC_); + + allocsThisZoneSinceMinorGC_ = 0; + zone_ = zone; if (zone == nullptr) { freeLists_ = nullptr; diff --git a/js/src/vm/JSContext.h b/js/src/vm/JSContext.h index 0bec95837d4b..7cf32c3b9d80 100644 --- a/js/src/vm/JSContext.h +++ b/js/src/vm/JSContext.h @@ -130,6 +130,11 @@ struct JSContext : public JS::RootingContext, // Free lists for allocating in the current zone. js::ThreadData freeLists_; + // This is reset each time we switch zone, then added to the variable in the + // zone when we switch away from it. This would be a js::ThreadData but we + // need to take its address. + uint32_t allocsThisZoneSinceMinorGC_; + // Free lists for parallel allocation in the atoms zone on helper threads. js::ThreadData atomsZoneFreeLists_; @@ -198,6 +203,20 @@ struct JSContext : public JS::RootingContext, js::ReportAllocationOverflow(this); } + void noteTenuredAlloc() { + allocsThisZoneSinceMinorGC_++; + } + + uint32_t* addressOfTenuredAllocCount() { + return &allocsThisZoneSinceMinorGC_; + } + + uint32_t getAndResetAllocsThisZoneSinceMinorGC() { + uint32_t allocs = allocsThisZoneSinceMinorGC_; + allocsThisZoneSinceMinorGC_ = 0; + return allocs; + } + // Accessors for immutable runtime data. JSAtomState& names() { return *runtime_->commonNames; } js::StaticStrings& staticStrings() { return *runtime_->staticStrings; }