summaryrefslogtreecommitdiffstats
path: root/js/src/gc/Pretenuring.h
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/gc/Pretenuring.h')
-rw-r--r--js/src/gc/Pretenuring.h348
1 files changed, 348 insertions, 0 deletions
diff --git a/js/src/gc/Pretenuring.h b/js/src/gc/Pretenuring.h
new file mode 100644
index 0000000000..ed156cbbd0
--- /dev/null
+++ b/js/src/gc/Pretenuring.h
@@ -0,0 +1,348 @@
+/* -*- 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/. */
+
+/*
+ * Pretenuring.
+ *
+ * Some kinds of GC cells can be allocated in either the nursery or the tenured
+ * heap. The pretenuring system decides where to allocate such cells based on
+ * their expected lifetime with the aim of minimising total collection time.
+ *
+ * Lifetime is predicted based on data gathered about the cells' allocation
+ * site. This data is gathered in the middle JIT tiers, after code has stopped
+ * executing in the interpreter and before we generate fully optimized code.
+ */
+
+#ifndef gc_Pretenuring_h
+#define gc_Pretenuring_h
+
+#include <algorithm>
+
+#include "gc/AllocKind.h"
+#include "js/TypeDecls.h"
+
+class JS_PUBLIC_API JSTracer;
+
+namespace JS {
+enum class GCReason;
+} // namespace JS
+
+namespace js {
+namespace gc {
+
+class GCRuntime;
+class PretenuringNursery;
+
+// Number of trace kinds supportd by the nursery. These are arranged at the
+// start of JS::TraceKind.
+static constexpr size_t NurseryTraceKinds = 3;
+
+enum class CatchAllAllocSite { Unknown, Optimized };
+
+// Information about an allocation site.
+//
+// Nursery cells contain a pointer to one of these in their cell header (stored
+// before the cell). The site can relate to either a specific JS bytecode
+// instruction, a specific WebAssembly type, or can be a catch-all instance for
+// unknown sites or JS JIT optimized code.
+class AllocSite {
+ public:
+ enum class State : uint32_t { ShortLived = 0, Unknown = 1, LongLived = 2 };
+
+ // The JIT depends on being able to tell the states apart by checking a single
+ // bit.
+ static constexpr int32_t LONG_LIVED_BIT = int32_t(State::LongLived);
+ static_assert((LONG_LIVED_BIT & int32_t(State::Unknown)) == 0);
+ static_assert((AllocSite::LONG_LIVED_BIT & int32_t(State::ShortLived)) == 0);
+
+ private:
+ JS::Zone* zone_ = nullptr;
+
+ // Word storing JSScript pointer and site state.
+ //
+ // The script pointer is the script that owns this allocation site, a special
+ // sentinel script for wasm sites, or null for unknown sites. This is used
+ // when we need to invalidate the script.
+ uintptr_t scriptAndState = uintptr_t(State::Unknown);
+ static constexpr uintptr_t STATE_MASK = BitMask(2);
+
+ // Next pointer forming a linked list of sites at which nursery allocation
+ // happened since the last nursery collection.
+ AllocSite* nextNurseryAllocated = nullptr;
+
+ // Number of nursery allocations at this site since last nursery collection.
+ uint32_t nurseryAllocCount = 0;
+
+ // Number of nursery allocations that survived. Used during collection.
+ uint32_t nurseryTenuredCount : 24;
+
+ // Number of times the script has been invalidated.
+ uint32_t invalidationCount : 4;
+
+ // The trace kind of the allocation. Only kinds up to NurseryTraceKinds are
+ // allowed.
+ uint32_t traceKind_ : 4;
+
+ static AllocSite* const EndSentinel;
+
+ // Sentinel script for wasm sites.
+ static JSScript* const WasmScript;
+
+ friend class PretenuringZone;
+ friend class PretenuringNursery;
+
+ uintptr_t rawScript() const { return scriptAndState & ~STATE_MASK; }
+
+ public:
+ AllocSite() : nurseryTenuredCount(0), invalidationCount(0), traceKind_(0) {}
+
+ // Create a dummy site to use for unknown allocations.
+ explicit AllocSite(JS::Zone* zone, JS::TraceKind kind)
+ : zone_(zone),
+ nurseryTenuredCount(0),
+ invalidationCount(0),
+ traceKind_(uint32_t(kind)) {
+ MOZ_ASSERT(traceKind_ < NurseryTraceKinds);
+ }
+
+ // Create a site for an opcode in the given script.
+ AllocSite(JS::Zone* zone, JSScript* script, JS::TraceKind kind)
+ : AllocSite(zone, kind) {
+ MOZ_ASSERT(script != WasmScript);
+ setScript(script);
+ }
+
+ void initUnknownSite(JS::Zone* zone, JS::TraceKind kind) {
+ MOZ_ASSERT(!zone_ && scriptAndState == uintptr_t(State::Unknown));
+ zone_ = zone;
+ nurseryTenuredCount = 0;
+ invalidationCount = 0;
+ traceKind_ = uint32_t(kind);
+ MOZ_ASSERT(traceKind_ < NurseryTraceKinds);
+ }
+
+ // Initialize a site to be a wasm site.
+ void initWasm(JS::Zone* zone) {
+ MOZ_ASSERT(!zone_ && scriptAndState == uintptr_t(State::Unknown));
+ zone_ = zone;
+ setScript(WasmScript);
+ nurseryTenuredCount = 0;
+ invalidationCount = 0;
+ traceKind_ = uint32_t(JS::TraceKind::Object);
+ }
+
+ JS::Zone* zone() const { return zone_; }
+
+ JS::TraceKind traceKind() const { return JS::TraceKind(traceKind_); }
+
+ State state() const { return State(scriptAndState & STATE_MASK); }
+
+ // Whether this site has a script associated with it. This is not true if
+ // this site is for a wasm site.
+ bool hasScript() const { return rawScript() != uintptr_t(WasmScript); }
+ JSScript* script() const {
+ MOZ_ASSERT(hasScript());
+ return reinterpret_cast<JSScript*>(rawScript());
+ }
+
+ // Whether this site is not an unknown or optimized site.
+ bool isNormal() const { return rawScript() != 0; }
+
+ enum class Kind : uint32_t { Normal, Unknown, Optimized };
+ Kind kind() const;
+
+ bool isInAllocatedList() const { return nextNurseryAllocated; }
+
+ // Whether allocations at this site should be allocated in the nursery or the
+ // tenured heap.
+ Heap initialHeap() const {
+ return state() == State::LongLived ? Heap::Tenured : Heap::Default;
+ }
+
+ bool hasNurseryAllocations() const {
+ return nurseryAllocCount != 0 || nurseryTenuredCount != 0;
+ }
+ void resetNurseryAllocations() {
+ nurseryAllocCount = 0;
+ nurseryTenuredCount = 0;
+ }
+
+ uint32_t incAllocCount() { return ++nurseryAllocCount; }
+ uint32_t* nurseryAllocCountAddress() { return &nurseryAllocCount; }
+
+ void incTenuredCount() {
+ // The nursery is not large enough for this to overflow.
+ nurseryTenuredCount++;
+ MOZ_ASSERT(nurseryTenuredCount != 0);
+ }
+
+ size_t allocCount() const {
+ return std::max(nurseryAllocCount, nurseryTenuredCount);
+ }
+
+ void updateStateOnMinorGC(double promotionRate);
+
+ // Reset the state to 'Unknown' unless we have reached the invalidation limit
+ // for this site. Return whether the state was reset.
+ bool maybeResetState();
+
+ bool invalidationLimitReached() const;
+ bool invalidateScript(GCRuntime* gc);
+
+ void trace(JSTracer* trc);
+
+ static void printInfoHeader(JS::GCReason reason, double promotionRate);
+ static void printInfoFooter(size_t sitesCreated, size_t sitesActive,
+ size_t sitesPretenured, size_t sitesInvalidated);
+ void printInfo(bool hasPromotionRate, double promotionRate,
+ bool wasInvalidated) const;
+
+ static constexpr size_t offsetOfScriptAndState() {
+ return offsetof(AllocSite, scriptAndState);
+ }
+ static constexpr size_t offsetOfNurseryAllocCount() {
+ return offsetof(AllocSite, nurseryAllocCount);
+ }
+ static constexpr size_t offsetOfNextNurseryAllocated() {
+ return offsetof(AllocSite, nextNurseryAllocated);
+ }
+
+ private:
+ void setScript(JSScript* newScript) {
+ MOZ_ASSERT((uintptr_t(newScript) & STATE_MASK) == 0);
+ scriptAndState = uintptr_t(newScript) | uintptr_t(state());
+ }
+
+ void setState(State newState) {
+ MOZ_ASSERT((uintptr_t(newState) & ~STATE_MASK) == 0);
+ scriptAndState = rawScript() | uintptr_t(newState);
+ }
+
+ const char* stateName() const;
+};
+
+// Pretenuring information stored per zone.
+class PretenuringZone {
+ public:
+ // Catch-all allocation site instance used when the actual site is unknown, or
+ // when optimized JIT code allocates a GC thing that's not handled by the
+ // pretenuring system.
+ AllocSite unknownAllocSites[NurseryTraceKinds];
+
+ // Catch-all allocation instance used by optimized JIT code when allocating GC
+ // things that are handled by the pretenuring system. Allocation counts are
+ // not recorded by optimized JIT code.
+ AllocSite optimizedAllocSite;
+
+ // Count of tenured cell allocations made between each major collection and
+ // how many survived.
+ uint32_t allocCountInNewlyCreatedArenas = 0;
+ uint32_t survivorCountInNewlyCreatedArenas = 0;
+
+ // Count of successive collections that had a low young tenured survival
+ // rate. Used to discard optimized code if we get the pretenuring decision
+ // wrong.
+ uint32_t lowYoungTenuredSurvivalCount = 0;
+
+ // Count of successive nursery collections that had a high survival rate for
+ // objects allocated by optimized code. Used to discard optimized code if we
+ // get the pretenuring decision wrong.
+ uint32_t highNurserySurvivalCount = 0;
+
+ // Total allocation count by trace kind (ignoring optimized
+ // allocations). Calculated during nursery collection.
+ uint32_t nurseryAllocCounts[NurseryTraceKinds] = {0};
+
+ explicit PretenuringZone(JS::Zone* zone)
+ : optimizedAllocSite(zone, JS::TraceKind::Object) {
+ for (uint32_t i = 0; i < NurseryTraceKinds; i++) {
+ unknownAllocSites[i].initUnknownSite(zone, JS::TraceKind(i));
+ }
+ }
+
+ AllocSite& unknownAllocSite(JS::TraceKind kind) {
+ size_t i = size_t(kind);
+ MOZ_ASSERT(i < NurseryTraceKinds);
+ return unknownAllocSites[i];
+ }
+
+ void clearCellCountsInNewlyCreatedArenas() {
+ allocCountInNewlyCreatedArenas = 0;
+ survivorCountInNewlyCreatedArenas = 0;
+ }
+ void updateCellCountsInNewlyCreatedArenas(uint32_t allocCount,
+ uint32_t survivorCount) {
+ allocCountInNewlyCreatedArenas += allocCount;
+ survivorCountInNewlyCreatedArenas += survivorCount;
+ }
+
+ bool calculateYoungTenuredSurvivalRate(double* rateOut);
+
+ void noteLowYoungTenuredSurvivalRate(bool lowYoungSurvivalRate);
+ void noteHighNurserySurvivalRate(bool highNurserySurvivalRate);
+
+ // Recovery: if code behaviour change we may need to reset allocation site
+ // state and invalidate JIT code.
+ bool shouldResetNurseryAllocSites();
+ bool shouldResetPretenuredAllocSites();
+
+ uint32_t& nurseryAllocCount(JS::TraceKind kind) {
+ size_t i = size_t(kind);
+ MOZ_ASSERT(i < NurseryTraceKinds);
+ return nurseryAllocCounts[i];
+ }
+ uint32_t nurseryAllocCount(JS::TraceKind kind) const {
+ return const_cast<PretenuringZone*>(this)->nurseryAllocCount(kind);
+ }
+};
+
+// Pretenuring information stored as part of the the GC nursery.
+class PretenuringNursery {
+ gc::AllocSite* allocatedSites;
+
+ size_t allocSitesCreated = 0;
+
+ uint32_t totalAllocCount_ = 0;
+
+ public:
+ PretenuringNursery() : allocatedSites(AllocSite::EndSentinel) {}
+
+ bool hasAllocatedSites() const {
+ return allocatedSites != AllocSite::EndSentinel;
+ }
+
+ bool canCreateAllocSite();
+ void noteAllocSiteCreated() { allocSitesCreated++; }
+
+ void insertIntoAllocatedList(AllocSite* site) {
+ MOZ_ASSERT(!site->isInAllocatedList());
+ site->nextNurseryAllocated = allocatedSites;
+ allocatedSites = site;
+ }
+
+ size_t doPretenuring(GCRuntime* gc, JS::GCReason reason,
+ bool validPromotionRate, double promotionRate,
+ bool reportInfo, size_t reportThreshold);
+
+ void maybeStopPretenuring(GCRuntime* gc);
+
+ uint32_t totalAllocCount() const { return totalAllocCount_; }
+
+ void* addressOfAllocatedSites() { return &allocatedSites; }
+
+ private:
+ void processSite(GCRuntime* gc, AllocSite* site, size_t& sitesActive,
+ size_t& sitesPretenured, size_t& sitesInvalidated,
+ bool reportInfo, size_t reportThreshold);
+ void processCatchAllSite(AllocSite* site, bool reportInfo,
+ size_t reportThreshold);
+ void updateAllocCounts(AllocSite* site);
+};
+
+} // namespace gc
+} // namespace js
+
+#endif /* gc_Pretenuring_h */