Bug 1473213 (Part 4) - Add cells allocated statistics r=sfink
authorPaul Bone <pbone@mozilla.com>
Wed, 08 Aug 2018 12:54:50 +1000
changeset 829016 f3796f1a571b6e11e247f366dc45cecd97d77729
parent 829015 5f35c86a0d22072c5287f46f338bcbd36ea5d76c
child 829017 e496e1a130b4c3fdca5d99f90dde3da7a3b91770
push id118741
push userbmo:kshvmdn@gmail.com
push dateTue, 14 Aug 2018 18:31:47 +0000
reviewerssfink
bugs1473213
milestone63.0a1
Bug 1473213 (Part 4) - Add cells allocated statistics r=sfink
js/src/gc/Allocator.cpp
js/src/gc/GC.cpp
js/src/gc/GCRuntime.h
js/src/gc/Nursery.cpp
js/src/gc/Statistics.cpp
js/src/gc/Statistics.h
js/src/gc/Zone.cpp
js/src/gc/Zone.h
js/src/jit/CompileWrappers.cpp
js/src/jit/CompileWrappers.h
js/src/jit/MacroAssembler.cpp
js/src/vm/JSContext-inl.h
js/src/vm/JSContext.h
--- a/js/src/gc/Allocator.cpp
+++ b/js/src/gc/Allocator.cpp
@@ -259,16 +259,17 @@ GCRuntime::tryNewTenuredThing(JSContext*
             }
             if (!t)
                 ReportOutOfMemory(cx);
         }
     }
 
     checkIncrementalZoneState(cx, t);
     gcTracer.traceTenuredAlloc(t, kind);
+    cx->noteTenuredAlloc();
     return t;
 }
 
 template <AllowGC allowGC>
 bool
 GCRuntime::checkAllocatorState(JSContext* cx, AllocKind kind)
 {
     if (allowGC) {
--- a/js/src/gc/GC.cpp
+++ b/js/src/gc/GC.cpp
@@ -7947,16 +7947,24 @@ GCRuntime::minorGC(JS::gcreason::Reason 
 {
     MOZ_ASSERT(!JS::RuntimeHeapIsBusy());
 
     MOZ_ASSERT_IF(reason == JS::gcreason::EVICT_NURSERY,
                   !rt->mainContextFromOwnThread()->suppressGC);
     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();
     TraceLoggerThread* logger = TraceLoggerForCurrentThread();
     AutoTraceLog logMinorGC(logger, TraceLogger_MinorGC);
     nursery().collect(reason);
     MOZ_ASSERT(nursery().isEmpty());
 
@@ -8234,16 +8242,18 @@ GCRuntime::mergeRealms(Realm* source, Re
     }
 
     // The source should be the only realm in its zone.
     for (RealmsInZoneIter r(source->zone()); !r.done(); r.next())
         MOZ_ASSERT(r.get() == source);
 
     // 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());
 
     // Merge other info in source's zone into target's zone.
     target->zone()->types.typeLifoAlloc().transferFrom(&source->zone()->types.typeLifoAlloc());
     MOZ_RELEASE_ASSERT(source->zone()->types.sweepTypeLifoAlloc.ref().isEmpty());
 
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -1018,16 +1018,19 @@ class GCRuntime
         return nursery_.refNoCheck().addressOfPosition();
     }
     const void* addressOfNurseryCurrentEnd() {
         return nursery_.refNoCheck().addressOfCurrentEnd();
     }
     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;
     void evictNursery(JS::gcreason::Reason reason = JS::gcreason::EVICT_NURSERY) {
         minorGC(reason, gcstats::PhaseKind::EVICT_NURSERY);
     }
     void freeAllLifoBlocksAfterMinorGC(LifoAlloc* lifo);
 
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -387,16 +387,17 @@ js::Nursery::allocate(size_t size)
             timeInChunkAlloc_ += ReallyNow() - start;
             MOZ_ASSERT(chunkno < allocatedChunkCount());
         }
         setCurrentChunk(chunkno);
     }
 
     void* thing = (void*)position();
     position_ = position() + size;
+    runtime()->gc.stats().noteNurseryAlloc();
 
     JS_EXTRA_POISON(thing, JS_ALLOCATED_NURSERY_PATTERN, size, MemCheckKind::MakeUndefined);
 
 #ifdef JS_GC_ZEAL
     if (runtime()->gc.hasZealMode(ZealMode::CheckNursery)) {
         auto canary = reinterpret_cast<Canary*>(position() - CanarySize);
         canary->magicValue = CanaryMagicValue;
         canary->next = nullptr;
@@ -607,16 +608,18 @@ js::Nursery::renderProfileJSON(JSONPrint
     json.property("cur_capacity", previousGC.nurseryCapacity);
     const size_t newCapacity = spaceToEnd(maxChunkCount());
     if (newCapacity != previousGC.nurseryCapacity)
         json.property("new_capacity", newCapacity);
     if (previousGC.nurseryLazyCapacity != previousGC.nurseryCapacity)
         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");
 
 #define EXTRACT_NAME(name, text) #name,
     static const char* names[] = {
 FOR_EACH_NURSERY_PROFILE_TIME(EXTRACT_NAME)
 #undef EXTRACT_NAME
     "" };
--- a/js/src/gc/Statistics.cpp
+++ b/js/src/gc/Statistics.cpp
@@ -747,16 +747,17 @@ Statistics::formatJsonPhaseTimes(const P
     }
 }
 
 Statistics::Statistics(JSRuntime* rt)
   : runtime(rt),
     gcTimerFile(nullptr),
     gcDebugFile(nullptr),
     nonincrementalReason_(gc::AbortReason::None),
+    allocsSinceMinorGC({0, 0}),
     preBytes(0),
     thresholdTriggered(false),
     triggerAmount(0.0),
     triggerThreshold(0.0),
     startingMinorGCNumber(0),
     startingMajorGCNumber(0),
     startingSliceNumber(0),
     maxPauseInInterval(0),
@@ -1041,16 +1042,18 @@ Statistics::beginNurseryCollection(JS::g
 void
 Statistics::endNurseryCollection(JS::gcreason::Reason reason)
 {
     if (nurseryCollectionCallback) {
         (*nurseryCollectionCallback)(runtime->mainContextFromOwnThread(),
                                      JS::GCNurseryProgress::GC_NURSERY_COLLECTION_END,
                                      reason);
     }
+
+    allocsSinceMinorGC = {0, 0};
 }
 
 void
 Statistics::beginSlice(const ZoneGCStats& zoneStats, JSGCInvocationKind gckind,
                        SliceBudget budget, JS::gcreason::Reason reason)
 {
     MOZ_ASSERT(phaseStack.empty() ||
                (phaseStack.length() == 1 && phaseStack[0] == Phase::MUTATOR));
--- a/js/src/gc/Statistics.h
+++ b/js/src/gc/Statistics.h
@@ -187,16 +187,37 @@ struct Statistics
     }
 
     void recordTrigger(double amount, double threshold) {
         triggerAmount = amount;
         triggerThreshold = threshold;
         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);
 
     TimeStamp beginSCC();
     void endSCC(unsigned scc, TimeStamp start);
 
     UniqueChars formatCompactSliceMessage() const;
     UniqueChars formatCompactSummaryMessage() const;
@@ -310,16 +331,25 @@ struct Statistics
     PhaseTimeTable parallelTimes;
 
     /* Number of events of this type for this GC. */
     EnumeratedArray<Stat,
                     STAT_LIMIT,
                     mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire,
                                     mozilla::recordreplay::Behavior::DontPreserve>> 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;
 
     /* If the GC was triggered by exceeding some threshold, record the
      * threshold and the value that exceeded it. */
     bool thresholdTriggered;
     double triggerAmount;
     double triggerThreshold;
--- a/js/src/gc/Zone.cpp
+++ b/js/src/gc/Zone.cpp
@@ -29,16 +29,17 @@ JS::Zone::Zone(JSRuntime* rt)
     // Note: don't use |this| before initializing helperThreadUse_!
     // ProtectedData checks in CheckZone::check may read this field.
     helperThreadUse_(HelperThreadUse::None),
     helperThreadOwnerContext_(nullptr),
     debuggers(this, nullptr),
     uniqueIds_(this),
     suppressAllocationMetadataBuilder(this, false),
     arenas(this),
+    tenuredAllocsSinceMinorGC_(0),
     types(this),
     gcWeakMapList_(this),
     compartments_(),
     gcGrayRoots_(this),
     gcWeakRefs_(this),
     weakCaches_(this),
     gcWeakKeys_(this, SystemAllocPolicy(), rt->randomHashCodeScrambler()),
     typeDescrObjects_(this, this),
--- a/js/src/gc/Zone.h
+++ b/js/src/gc/Zone.h
@@ -335,16 +335,30 @@ class Zone : public JS::shadow::Zone,
      * - to avoid attaching allocation stacks to allocation stack nodes, which
      *   is silly
      * And so on.
      */
     js::ZoneData<bool> suppressAllocationMetadataBuilder;
 
     js::gc::ArenaLists arenas;
 
+  private:
+    // Number of allocations since the most recent minor GC for this thread.
+    mozilla::Atomic<uint32_t, mozilla::Relaxed, mozilla::recordreplay::Behavior::DontPreserve>
+        tenuredAllocsSinceMinorGC_;
+
+  public:
+    void addTenuredAllocsSinceMinorGC(uint32_t allocs) {
+        tenuredAllocsSinceMinorGC_ += allocs;
+    }
+
+    uint32_t getAndResetTenuredAllocsSinceMinorGC() {
+        return tenuredAllocsSinceMinorGC_.exchange(0);
+    }
+
     js::TypeZone types;
 
   private:
     /* Live weakmaps in this zone. */
     js::ZoneOrGCTaskData<mozilla::LinkedList<js::WeakMapBase>> gcWeakMapList_;
   public:
     mozilla::LinkedList<js::WeakMapBase>& gcWeakMapList() { return gcWeakMapList_.ref(); }
 
--- a/js/src/jit/CompileWrappers.cpp
+++ b/js/src/jit/CompileWrappers.cpp
@@ -102,16 +102,22 @@ CompileRuntime::wellKnownSymbols()
 }
 
 const void*
 CompileRuntime::mainContextPtr()
 {
     return runtime()->mainContextFromAnyThread();
 }
 
+uint32_t*
+CompileRuntime::addressOfTenuredAllocCount()
+{
+    return runtime()->mainContextFromAnyThread()->addressOfTenuredAllocCount();
+}
+
 const void*
 CompileRuntime::addressOfJitStackLimit()
 {
     return runtime()->mainContextFromAnyThread()->addressOfJitStackLimit();
 }
 
 const void*
 CompileRuntime::addressOfInterruptBits()
@@ -209,16 +215,22 @@ CompileZone::addressOfStringNurseryCurre
     // there is still a separate string end address.  The only time it
     // is different from the regular end address, is when nursery strings are
     // disabled (it will be NULL).
     //
     // This function returns _a pointer to_ that end address.
     return zone()->runtimeFromAnyThread()->gc.addressOfStringNurseryCurrentEnd();
 }
 
+uint32_t*
+CompileZone::addressOfNurseryAllocCount()
+{
+    return zone()->runtimeFromAnyThread()->gc.addressOfNurseryAllocCount();
+}
+
 bool
 CompileZone::canNurseryAllocateStrings()
 {
     return nurseryExists() &&
         zone()->runtimeFromAnyThread()->gc.nursery().canAllocateStrings() &&
         zone()->allocNurseryStrings;
 }
 
--- a/js/src/jit/CompileWrappers.h
+++ b/js/src/jit/CompileWrappers.h
@@ -43,16 +43,17 @@ class CompileRuntime
     const JSAtomState& names();
     const PropertyName* emptyString();
     const StaticStrings& staticStrings();
     const Value& NaNValue();
     const Value& positiveInfinityValue();
     const WellKnownSymbols& wellKnownSymbols();
 
     const void* mainContextPtr();
+    uint32_t* addressOfTenuredAllocCount();
     const void* addressOfJitStackLimit();
     const void* addressOfInterruptBits();
 
 #ifdef DEBUG
     bool isInsideNursery(gc::Cell* cell);
 #endif
 
     // DOM callbacks must be threadsafe (and will hopefully be removed soon).
@@ -77,16 +78,18 @@ class CompileZone
 
     const uint32_t* addressOfNeedsIncrementalBarrier();
     gc::FreeSpan** addressOfFreeList(gc::AllocKind allocKind);
     void* addressOfNurseryPosition();
     void* addressOfStringNurseryPosition();
     const void* addressOfNurseryCurrentEnd();
     const void* addressOfStringNurseryCurrentEnd();
 
+    uint32_t* addressOfNurseryAllocCount();
+
     bool nurseryExists();
     bool canNurseryAllocateStrings();
     void setMinorGCShouldCancelIonCompilations();
 };
 
 class JitRealm;
 
 class CompileRealm
--- a/js/src/jit/MacroAssembler.cpp
+++ b/js/src/jit/MacroAssembler.cpp
@@ -826,16 +826,20 @@ MacroAssembler::freeListAllocate(Registe
     addPtr(temp, result); // Turn the offset into a pointer.
     Push(result);
     // Update the free list to point to the next span (which may be empty).
     load32(Address(result, 0), result);
     store32(result, Address(temp, js::gc::FreeSpan::offsetOfFirst()));
     Pop(result);
 
     bind(&success);
+
+    uint32_t* countAddress = GetJitContext()->runtime->addressOfTenuredAllocCount();
+    movePtr(ImmPtr(countAddress), temp);
+    add32(Imm32(1), Address(temp, 0));
 }
 
 void
 MacroAssembler::callMallocStub(size_t nbytes, Register result, Label* fail)
 {
     // These registers must match the ones in JitRuntime::generateMallocStub.
     const Register regReturn = CallTempReg0;
     const Register regZone = CallTempReg0;
@@ -988,16 +992,27 @@ MacroAssembler::bumpPointerAllocate(Regi
     addPtr(Imm32(totalSize), result);
     CheckedInt<int32_t> endOffset = (CheckedInt<uintptr_t>(uintptr_t(curEndAddr)) -
         CheckedInt<uintptr_t>(uintptr_t(posAddr))).toChecked<int32_t>();
     MOZ_ASSERT(endOffset.isValid(),
         "Position and end pointers must be nearby");
     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<int32_t> counterOffset = (CheckedInt<uintptr_t>(uintptr_t(countAddress)) -
+        CheckedInt<uintptr_t>(uintptr_t(posAddr))).toChecked<int32_t>();
+    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
 // allocation requested but unsuccessful.
 void
 MacroAssembler::allocateString(Register result, Register temp, gc::AllocKind allocKind,
                                gc::InitialHeap initialHeap, Label* fail)
 {
--- a/js/src/vm/JSContext-inl.h
+++ b/js/src/vm/JSContext-inl.h
@@ -417,16 +417,21 @@ JSContext::enterAtomsZone()
 {
     realm_ = nullptr;
     setZone(runtime_->unsafeAtomsZone(), AtomsZone);
 }
 
 inline void
 JSContext::setZone(js::Zone *zone, JSContext::IsAtomsZone isAtomsZone)
 {
+    if (zone_)
+        zone_->addTenuredAllocsSinceMinorGC(allocsThisZoneSinceMinorGC_);
+
+    allocsThisZoneSinceMinorGC_ = 0;
+
     zone_ = zone;
     if (zone == nullptr) {
         freeLists_ = nullptr;
         return;
     }
 
     if (isAtomsZone == AtomsZone && helperThread()) {
         MOZ_ASSERT(!zone_->wasGCStarted());
--- a/js/src/vm/JSContext.h
+++ b/js/src/vm/JSContext.h
@@ -125,16 +125,21 @@ struct JSContext : public JS::RootingCon
     friend class js::gc::AutoSuppressNurseryCellAlloc;
     js::ThreadData<size_t> nurserySuppressions_;
 
     js::ThreadData<JS::ContextOptions> options_;
 
     // Free lists for allocating in the current zone.
     js::ThreadData<js::gc::FreeLists*> 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<js::gc::FreeLists*> atomsZoneFreeLists_;
 
   public:
     // This is used by helper threads to change the runtime their context is
     // currently operating on.
     void setRuntime(JSRuntime* rt);
 
@@ -193,16 +198,30 @@ struct JSContext : public JS::RootingCon
     }
 
     void updateMallocCounter(size_t nbytes);
 
     void reportAllocationOverflow() {
         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; }
     js::SharedImmutableStringsCache& sharedImmutableStrings() {
         return runtime_->sharedImmutableStrings();
     }
     bool permanentAtomsPopulated() { return runtime_->permanentAtomsPopulated(); }
     const js::FrozenAtomSet& permanentAtoms() { return *runtime_->permanentAtoms(); }