Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 14 additions & 8 deletions JSTests/stress/codeblock-aging-ftl-idle.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
//@ skip if !$isFTLPlatform
//@ runDefault("--useEagerCodeBlockJettisonTiming=1", "--useConcurrentJIT=0", "--optimizedCodeAgingQuietSeconds=0.02")

// FTL code has no execution counter of its own, so it ages against the mutator as a whole
// (Options::optimizedCodeAgingQuietAllocationMB): it is jettisoned by a full collection that the
// embedder tagged as idle (idleFullGC() here, GCRequest::isIdle) once (almost) nothing was allocated
// since the previous full collection looked at it for optimizedCodeAgingQuietSeconds, and kept -
// together with the baseline code it pins - whenever the mutator has been allocating, and by every
// collection that is not tagged idle.
// FTL code has no execution counter of its own, so it ages against the mutator as a whole: it is
// jettisoned only by a full collection the embedder tagged idle (idleFullGC() here, GCRequest::isIdle), and
// only once no collection has seen the mutator allocating for optimizedCodeAgingQuietSeconds. Ordinary
// collections never drop it; allocation keeps it (and the baseline code it pins) alive.

function shouldBe(actual, expected, msg) {
if (actual !== expected)
Expand Down Expand Up @@ -47,13 +45,21 @@ var o = { a: 1, b: 2 };
var f = makeHot();
if (!warmToFTL(f))
throw new Error("test needs f to reach FTL");
fullGC(); // records the allocation total for f's FTL block
fullGC();
idleFor(quiet);
fullGC(); // quiet and past the window, but not an idle collection: kept
idleFor(quiet);
fullGC();
shouldBe(f(o)[1], true, "an FTL block should survive full collections that are not tagged idle;");
idleFullGC(); // the embedder says the application is idle: aged out
allocateMB(4);
var activeAt = preciseTime();
fullGC(); // this collection saw the mutator allocating: the clock restarts here
idleFullGC(); // idle, but the mutator was active a moment ago: kept
// (unless the two collections themselves took longer than the quiet window, e.g. on a slow debug build)
if (preciseTime() - activeAt < 0.02)
shouldBe(f(o)[1], true, "an FTL block should survive an idle collection right after an active one;");
idleFor(quiet);
idleFullGC(); // idle and nothing has happened for the quiet window: aged out
Comment thread
coderabbitai[bot] marked this conversation as resolved.
shouldBe(f(o)[1], false, "an FTL block should age out in an idle collection after a quiet stretch;");
shouldBe(f(o)[0], 64, "and the function should still work;");

Expand Down
27 changes: 8 additions & 19 deletions Source/JavaScriptCore/bytecode/CodeBlock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1375,31 +1375,20 @@ ALWAYS_INLINE bool CodeBlock::shouldJettisonDueToOldAge(const ConcurrentJSLocker
// returns and loop back-edges as the sign of life. FTL code, and DFG code compiled without tier-up checks, has no
// counter of its own. It is expensive to rebuild, but it also pins its baseline alternative and every baseline
// CodeBlock it inlined along with their metadata, so rather than never ageing it ages against the mutator as a
// whole: a full collection lets it go once it is past its TTL and (almost) nothing has been allocated since the
// previous full collection looked at it and the embedder says the application is idle, and renews it whenever the
// mutator has been allocating, keeping the code exactly as before.
// whole: only a collection the embedder tagged idle (GCRequest::isIdle) may let it go, and only once neither its
// own compilation nor a collection that saw the mutator allocating (Heap::lastActiveCollectionTime) has happened
// for optimizedCodeAgingQuietSeconds. Collections paced by allocation, forced by the program or triggered by
// memory pressure never drop it, and a busy mutator keeps the active-collection time fresh.
if (agesByMutatorQuietness()) {
unsigned quietMB = Options::optimizedCodeAgingQuietAllocationMB();
if (!quietMB)
if (!Options::optimizedCodeAgingQuietAllocationMB())
return false;
// Whole megabytes, wrapping: only the distance from the lease's start matters. The first check of a block (field
// still 0) and a block that just moved into this regime both see a large distance and renew.
uint32_t allocatedMB = static_cast<uint32_t>(vm().heap.totalBytesAllocated() >> 20);
if (static_cast<uint32_t>(allocatedMB - m_leaseStartAllocatedMB) > quietMB) {
// The mutator has been allocating since this block's lease started: renew it from here.
m_leaseStartAllocatedMB = allocatedMB;
m_creationTime = ApproximateTime::now();
return false;
}
// Any full collection may renew the lease, but only one the embedder requested because the application went idle
// (GCRequest::isIdle) lets the code go: never one paced by allocation, forced by the program (which may do so on
// a timer while hot code runs between the calls) or triggered by memory pressure.
if (!vm().heap.isIdleCollection())
Heap& heap = vm().heap;
if (!heap.isIdleCollection())
return false;
Seconds quietFor = Seconds(Options::optimizedCodeAgingQuietSeconds());
if (Options::useEagerCodeBlockJettisonTiming()) [[unlikely]]
quietFor = std::min(quietFor, ttl);
return timeSinceCreation() >= quietFor;
return ApproximateTime::now() - std::max(m_creationTime, heap.lastActiveCollectionTime()) >= quietFor;
}

if (timeSinceCreation() < ttl)
Expand Down
4 changes: 0 additions & 4 deletions Source/JavaScriptCore/bytecode/CodeBlock.h
Original file line number Diff line number Diff line change
Expand Up @@ -1002,10 +1002,6 @@ class CodeBlock : public JSCell {
uint16_t m_optimizationDelayCounter { 0 };
uint16_t m_reoptimizationRetryCounter { 0 };
float m_previousCounter { 0 };
#if USE(BUN_JSC_ADDITIONS)
// For agesByMutatorQuietness() code: the heap's allocation total, in whole MB modulo 2^32, when the current lease started.
uint32_t m_leaseStartAllocatedMB { 0 };
#endif
StructureWatchpointMap m_llintGetByIdWatchpointMap;
RefPtr<JSC::JITCode> m_jitCode;
#if ENABLE(JIT)
Expand Down
12 changes: 9 additions & 3 deletions Source/JavaScriptCore/heap/Heap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1574,6 +1574,15 @@ NEVER_INLINE bool Heap::runBeginPhase(GCConductor conn)
RELEASE_ASSERT(!m_requests.isEmpty());
m_currentRequest = m_requests.first();
}
#if USE(BUN_JSC_ADDITIONS)
// Accumulated across collections, so a mutator that works steadily but is collected often (each cycle small) still
// reads as active; only a genuinely quiet stretch leaves the stamp to age.
m_bytesAllocatedSinceLastActiveCollection += totalBytesAllocatedThisCycle();
if (m_bytesAllocatedSinceLastActiveCollection > Options::optimizedCodeAgingQuietAllocationMB() * MB) {
m_bytesAllocatedSinceLastActiveCollection = 0;
m_lastActiveCollectionTime = ApproximateTime::now();
}
#endif

dataLogIf(Options::logGC(), "[GC<", RawPointer(this), ">: START ", gcConductorShortName(conn), " ", capacity() / 1024, "kb ");

Expand Down Expand Up @@ -2754,9 +2763,6 @@ void Heap::setGarbageCollectionTimerEnabled(bool enable)
constexpr size_t oversizedAllocationThreshold = 64 * KB;
void Heap::didAllocate(size_t bytes)
{
#if USE(BUN_JSC_ADDITIONS)
m_totalBytesAllocated += bytes;
#endif
if (bytes >= oversizedAllocationThreshold) {
m_oversizedBytesAllocatedThisCycle += bytes;
m_lastOversidedAllocationThisCycle = bytes;
Expand Down
9 changes: 6 additions & 3 deletions Source/JavaScriptCore/heap/Heap.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
#include "Synchronousness.h"
#include "WeakHandleOwner.h"
#include <JavaScriptCore/SubspaceAccess.h>
#include <wtf/ApproximateTime.h>
#include <wtf/AutomaticThread.h>
#include <wtf/Box.h>
#include <wtf/ConcurrentPtrHashSet.h>
Expand Down Expand Up @@ -726,8 +727,9 @@ class Heap {

size_t totalBytesAllocatedThisCycle() { return m_nonOversizedBytesAllocatedThisCycle + m_oversizedBytesAllocatedThisCycle; }
#if USE(BUN_JSC_ADDITIONS)
// Monotonic; lets a full collection tell whether the mutator did anything since an earlier one (CodeBlock::shouldJettisonDueToOldAge).
uint64_t totalBytesAllocated() const { return m_totalBytesAllocated; }
// When a collection last began that found the mutator had allocated more than a trickle since the one before: the
// mutator was at work then. Idle optimized code ages against this (CodeBlock::shouldJettisonDueToOldAge).
ApproximateTime lastActiveCollectionTime() const { return m_lastActiveCollectionTime; }
// The collection in progress was requested by the embedder because the application went idle (GCRequest::isIdle).
bool isIdleCollection() const { return m_currentRequest.isIdle; }
#endif
Expand Down Expand Up @@ -884,7 +886,8 @@ class Heap {
const size_t m_minBytesPerCycle;
size_t m_bytesAllocatedBeforeLastEdenCollect { 0 };
#if USE(BUN_JSC_ADDITIONS)
uint64_t m_totalBytesAllocated { 0 };
ApproximateTime m_lastActiveCollectionTime;
size_t m_bytesAllocatedSinceLastActiveCollection { 0 };
#endif
size_t m_sizeAfterLastCollect { 0 };
size_t m_sizeAfterLastFullCollect { 0 };
Expand Down
4 changes: 2 additions & 2 deletions Source/JavaScriptCore/runtime/OptionsList.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ bool hasCapacityToUseLargeGigacage();
v(Bool, verboseFFI, false, Normal, "dataLog on FFI thunk/stub/signature creation"_s)
#define FOR_EACH_JSC_CODEBLOCK_AGING_OPTION(v) \
v(Bool, useExecutionCountForCodeBlockAging, true, Normal, "If true, an LLInt/Baseline CodeBlock whose execution counter has advanced since the last old-age check is treated as still in use and its TTL is renewed instead of being jettisoned."_s) \
v(Unsigned, optimizedCodeAgingQuietAllocationMB, 1, Normal, "FTL code (and DFG code without a tier-up counter) past its TTL is jettisoned for old age by a full collection that finds at most this much was allocated since the previous one looked at it, i.e. the mutator has been idle. 0 = such code never ages out."_s) \
v(Double, optimizedCodeAgingQuietSeconds, 30, Normal, "How long the mutator must have been that quiet, measured from the last collection that saw it allocating, before such code is let go (capped at the tier's TTL under useEagerCodeBlockJettisonTiming)."_s) \
v(Unsigned, optimizedCodeAgingQuietAllocationMB, 1, Normal, "A collection that finds more than this much allocated since the last one that did marks the mutator as active for the aging of FTL code (and DFG code without a tier-up counter); an embedder-tagged idle collection lets such code go once nothing has been active for optimizedCodeAgingQuietSeconds. 0 = such code never ages out."_s) \
v(Double, optimizedCodeAgingQuietSeconds, 30, Normal, "How long since the last active collection (and since the code was installed) before an idle collection lets such code go (capped at the tier's TTL under useEagerCodeBlockJettisonTiming)."_s) \
v(Double, codeBlockAgingLeaseMultiplier, 3.0, Normal, "When useExecutionCountForCodeBlockAging proves a CodeBlock is still active, renew its old-age TTL to this many multiples of timeToLive for its tier."_s)
#define FOR_EACH_JSC_BYTECODE_CACHE_DECODER_OPTION(v) \
v(Bool, useLeanBytecodeCacheDecoder, true, Normal, "If true, the bytecode cache Decoder skips bookkeeping that is only needed for decoded objects shared by multiple references."_s) \
Expand Down
Loading