summaryrefslogtreecommitdiff
path: root/Source/JavaScriptCore/heap/Heap.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Source/JavaScriptCore/heap/Heap.cpp')
-rw-r--r--Source/JavaScriptCore/heap/Heap.cpp1533
1 files changed, 495 insertions, 1038 deletions
diff --git a/Source/JavaScriptCore/heap/Heap.cpp b/Source/JavaScriptCore/heap/Heap.cpp
index 10eaa0205..26ec23980 100644
--- a/Source/JavaScriptCore/heap/Heap.cpp
+++ b/Source/JavaScriptCore/heap/Heap.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009, 2011, 2013-2015 Apple Inc. All rights reserved.
+ * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2011, 2013 Apple Inc. All rights reserved.
* Copyright (C) 2007 Eric Seidel <eric@webkit.org>
*
* This library is free software; you can redistribute it and/or
@@ -27,38 +27,33 @@
#include "CopiedSpaceInlines.h"
#include "CopyVisitorInlines.h"
#include "DFGWorklist.h"
-#include "EdenGCActivityCallback.h"
-#include "FullGCActivityCallback.h"
+#include "DelayedReleaseScope.h"
#include "GCActivityCallback.h"
#include "GCIncomingRefCountedSetInlines.h"
-#include "HeapHelperPool.h"
#include "HeapIterationScope.h"
#include "HeapRootVisitor.h"
#include "HeapStatistics.h"
-#include "HeapVerifier.h"
#include "IncrementalSweeper.h"
#include "Interpreter.h"
-#include "JSCInlines.h"
#include "JSGlobalObject.h"
#include "JSLock.h"
-#include "JSVirtualMachineInternal.h"
-#include "SamplingProfiler.h"
+#include "JSONObject.h"
+#include "Operations.h"
+#include "RecursiveAllocationScope.h"
#include "Tracing.h"
-#include "TypeProfilerLog.h"
#include "UnlinkedCodeBlock.h"
#include "VM.h"
#include "WeakSetInlines.h"
#include <algorithm>
-#include <wtf/CurrentTime.h>
-#include <wtf/ParallelVectorIterator.h>
-#include <wtf/ProcessID.h>
#include <wtf/RAMSize.h>
+#include <wtf/CurrentTime.h>
using namespace std;
+using namespace JSC;
namespace JSC {
-namespace {
+namespace {
static const size_t largeHeapSize = 32 * MB; // About 1.5X the average webpage.
static const size_t smallHeapSize = 1 * MB; // Matches the FastMalloc per-thread cache.
@@ -80,129 +75,82 @@ static type name arguments;
struct GCTimer {
GCTimer(const char* name)
- : name(name)
+ : m_time(0)
+ , m_min(100000000)
+ , m_max(0)
+ , m_count(0)
+ , m_name(name)
{
}
~GCTimer()
{
- logData(allCollectionData, "(All)");
- logData(edenCollectionData, "(Eden)");
- logData(fullCollectionData, "(Full)");
- }
-
- struct TimeRecord {
- TimeRecord()
- : time(0)
- , min(std::numeric_limits<double>::infinity())
- , max(0)
- , count(0)
- {
- }
-
- double time;
- double min;
- double max;
- size_t count;
- };
-
- void logData(const TimeRecord& data, const char* extra)
- {
- dataLogF("[%d] %s (Parent: %s) %s: %.2lfms (avg. %.2lf, min. %.2lf, max. %.2lf, count %lu)\n",
- getCurrentProcessID(),
- name,
- parent ? parent->name : "nullptr",
- extra,
- data.time * 1000,
- data.time * 1000 / data.count,
- data.min * 1000,
- data.max * 1000,
- data.count);
- }
-
- void updateData(TimeRecord& data, double duration)
- {
- if (duration < data.min)
- data.min = duration;
- if (duration > data.max)
- data.max = duration;
- data.count++;
- data.time += duration;
- }
-
- void didFinishPhase(HeapOperation collectionType, double duration)
- {
- TimeRecord& data = collectionType == EdenCollection ? edenCollectionData : fullCollectionData;
- updateData(data, duration);
- updateData(allCollectionData, duration);
+ dataLogF("%s: %.2lfms (avg. %.2lf, min. %.2lf, max. %.2lf)\n", m_name, m_time * 1000, m_time * 1000 / m_count, m_min*1000, m_max*1000);
}
-
- static GCTimer* s_currentGlobalTimer;
-
- TimeRecord allCollectionData;
- TimeRecord fullCollectionData;
- TimeRecord edenCollectionData;
- const char* name;
- GCTimer* parent { nullptr };
+ double m_time;
+ double m_min;
+ double m_max;
+ size_t m_count;
+ const char* m_name;
};
-GCTimer* GCTimer::s_currentGlobalTimer = nullptr;
-
struct GCTimerScope {
- GCTimerScope(GCTimer& timer, HeapOperation collectionType)
- : timer(timer)
- , start(WTF::monotonicallyIncreasingTime())
- , collectionType(collectionType)
+ GCTimerScope(GCTimer* timer)
+ : m_timer(timer)
+ , m_start(WTF::monotonicallyIncreasingTime())
{
- timer.parent = GCTimer::s_currentGlobalTimer;
- GCTimer::s_currentGlobalTimer = &timer;
}
~GCTimerScope()
{
- double delta = WTF::monotonicallyIncreasingTime() - start;
- timer.didFinishPhase(collectionType, delta);
- GCTimer::s_currentGlobalTimer = timer.parent;
+ double delta = WTF::monotonicallyIncreasingTime() - m_start;
+ if (delta < m_timer->m_min)
+ m_timer->m_min = delta;
+ if (delta > m_timer->m_max)
+ m_timer->m_max = delta;
+ m_timer->m_count++;
+ m_timer->m_time += delta;
}
- GCTimer& timer;
- double start;
- HeapOperation collectionType;
+ GCTimer* m_timer;
+ double m_start;
};
struct GCCounter {
GCCounter(const char* name)
- : name(name)
- , count(0)
- , total(0)
- , min(10000000)
- , max(0)
+ : m_name(name)
+ , m_count(0)
+ , m_total(0)
+ , m_min(10000000)
+ , m_max(0)
{
}
- void add(size_t amount)
+ void count(size_t amount)
{
- count++;
- total += amount;
- if (amount < min)
- min = amount;
- if (amount > max)
- max = amount;
+ m_count++;
+ m_total += amount;
+ if (amount < m_min)
+ m_min = amount;
+ if (amount > m_max)
+ m_max = amount;
}
~GCCounter()
{
- dataLogF("[%d] %s: %zu values (avg. %zu, min. %zu, max. %zu)\n", getCurrentProcessID(), name, total, total / count, min, max);
+ dataLogF("%s: %zu values (avg. %zu, min. %zu, max. %zu)\n", m_name, m_total, m_total / m_count, m_min, m_max);
}
- const char* name;
- size_t count;
- size_t total;
- size_t min;
- size_t max;
+ const char* m_name;
+ size_t m_count;
+ size_t m_total;
+ size_t m_min;
+ size_t m_max;
};
-#define GCPHASE(name) DEFINE_GC_LOGGING_GLOBAL(GCTimer, name##Timer, (#name)); GCTimerScope name##TimerScope(name##Timer, m_operationInProgress)
-#define GCCOUNTER(name, value) do { DEFINE_GC_LOGGING_GLOBAL(GCCounter, name##Counter, (#name)); name##Counter.add(value); } while (false)
+#define GCPHASE(name) DEFINE_GC_LOGGING_GLOBAL(GCTimer, name##Timer, (#name)); GCTimerScope name##TimerScope(&name##Timer)
+#define COND_GCPHASE(cond, name1, name2) DEFINE_GC_LOGGING_GLOBAL(GCTimer, name1##Timer, (#name1)); DEFINE_GC_LOGGING_GLOBAL(GCTimer, name2##Timer, (#name2)); GCTimerScope name1##CondTimerScope(cond ? &name1##Timer : &name2##Timer)
+#define GCCOUNTER(name, value) do { DEFINE_GC_LOGGING_GLOBAL(GCCounter, name##Counter, (#name)); name##Counter.count(value); } while (false)
#else
#define GCPHASE(name) do { } while (false)
+#define COND_GCPHASE(cond, name1, name2) do { } while (false)
#define GCCOUNTER(name, value) do { } while (false)
#endif
@@ -230,7 +178,7 @@ static inline bool isValidSharedInstanceThreadState(VM* vm)
static inline bool isValidThreadState(VM* vm)
{
- if (vm->atomicStringTable() != wtfThreadData().atomicStringTable())
+ if (vm->identifierTable != wtfThreadData().currentIdentifierTable())
return false;
if (vm->isSharedInstance() && !isValidSharedInstanceThreadState(vm))
@@ -240,17 +188,12 @@ static inline bool isValidThreadState(VM* vm)
}
struct MarkObject : public MarkedBlock::VoidFunctor {
- inline void visit(JSCell* cell)
+ void operator()(JSCell* cell)
{
if (cell->isZapped())
return;
Heap::heap(cell)->setMarked(cell);
}
- IterationStatus operator()(JSCell* cell)
- {
- visit(cell);
- return IterationStatus::Continue;
- }
};
struct Count : public MarkedBlock::CountFunctor {
@@ -258,36 +201,30 @@ struct Count : public MarkedBlock::CountFunctor {
};
struct CountIfGlobalObject : MarkedBlock::CountFunctor {
- inline void visit(JSCell* cell)
- {
+ void operator()(JSCell* cell) {
if (!cell->isObject())
return;
if (!asObject(cell)->isGlobalObject())
return;
count(1);
}
- IterationStatus operator()(JSCell* cell)
- {
- visit(cell);
- return IterationStatus::Continue;
- }
};
class RecordType {
public:
- typedef std::unique_ptr<TypeCountSet> ReturnType;
+ typedef PassOwnPtr<TypeCountSet> ReturnType;
RecordType();
- IterationStatus operator()(JSCell*);
+ void operator()(JSCell*);
ReturnType returnValue();
private:
const char* typeName(JSCell*);
- std::unique_ptr<TypeCountSet> m_typeCountSet;
+ OwnPtr<TypeCountSet> m_typeCountSet;
};
inline RecordType::RecordType()
- : m_typeCountSet(std::make_unique<TypeCountSet>())
+ : m_typeCountSet(adoptPtr(new TypeCountSet))
{
}
@@ -299,72 +236,54 @@ inline const char* RecordType::typeName(JSCell* cell)
return info->className;
}
-inline IterationStatus RecordType::operator()(JSCell* cell)
+inline void RecordType::operator()(JSCell* cell)
{
m_typeCountSet->add(typeName(cell));
- return IterationStatus::Continue;
}
-inline std::unique_ptr<TypeCountSet> RecordType::returnValue()
+inline PassOwnPtr<TypeCountSet> RecordType::returnValue()
{
- return WTFMove(m_typeCountSet);
+ return m_typeCountSet.release();
}
} // anonymous namespace
Heap::Heap(VM* vm, HeapType heapType)
: m_heapType(heapType)
- , m_ramSize(Options::forceRAMSize() ? Options::forceRAMSize() : ramSize())
+ , m_ramSize(ramSize())
, m_minBytesPerCycle(minHeapSize(m_heapType, m_ramSize))
, m_sizeAfterLastCollect(0)
- , m_sizeAfterLastFullCollect(0)
- , m_sizeBeforeLastFullCollect(0)
- , m_sizeAfterLastEdenCollect(0)
- , m_sizeBeforeLastEdenCollect(0)
, m_bytesAllocatedThisCycle(0)
- , m_bytesAbandonedSinceLastFullCollect(0)
+ , m_bytesAbandonedThisCycle(0)
, m_maxEdenSize(m_minBytesPerCycle)
, m_maxHeapSize(m_minBytesPerCycle)
, m_shouldDoFullCollection(false)
, m_totalBytesVisited(0)
, m_totalBytesCopied(0)
, m_operationInProgress(NoOperation)
+ , m_blockAllocator()
, m_objectSpace(this)
, m_storageSpace(this)
- , m_extraMemorySize(0)
- , m_deprecatedExtraMemorySize(0)
+ , m_extraMemoryUsage(0)
, m_machineThreads(this)
- , m_slotVisitor(*this)
+ , m_sharedData(vm)
+ , m_slotVisitor(m_sharedData)
+ , m_copyVisitor(m_sharedData)
, m_handleSet(vm)
, m_isSafeToCollect(false)
, m_writeBarrierBuffer(256)
, m_vm(vm)
- // We seed with 10ms so that GCActivityCallback::didAllocate doesn't continuously
- // schedule the timer if we've never done a collection.
- , m_lastFullGCLength(0.01)
- , m_lastEdenGCLength(0.01)
- , m_fullActivityCallback(GCActivityCallback::createFullTimer(this))
- , m_edenActivityCallback(GCActivityCallback::createEdenTimer(this))
-#if USE(CF)
- , m_sweeper(std::make_unique<IncrementalSweeper>(this, CFRunLoopGetCurrent()))
-#else
- , m_sweeper(std::make_unique<IncrementalSweeper>(this))
-#endif
+ , m_lastGCLength(0)
+ , m_lastCodeDiscardTime(WTF::monotonicallyIncreasingTime())
+ , m_activityCallback(DefaultGCActivityCallback::create(this))
+ , m_sweeper(IncrementalSweeper::create(this))
, m_deferralDepth(0)
-#if USE(CF)
- , m_delayedReleaseRecursionCount(0)
-#endif
- , m_helperClient(&heapHelperPool())
{
m_storageSpace.init();
- if (Options::verifyHeap())
- m_verifier = std::make_unique<HeapVerifier>(this, Options::numberOfGCCyclesToRecordForVerification());
}
Heap::~Heap()
{
- for (WeakBlock* block : m_logicallyEmptyWeakBlocks)
- WeakBlock::destroy(*this, block);
}
bool Heap::isPagedOut(double deadline)
@@ -379,58 +298,31 @@ void Heap::lastChanceToFinalize()
RELEASE_ASSERT(!m_vm->entryScope);
RELEASE_ASSERT(m_operationInProgress == NoOperation);
- m_codeBlocks.lastChanceToFinalize();
m_objectSpace.lastChanceToFinalize();
- releaseDelayedReleasedObjects();
-
- sweepAllLogicallyEmptyWeakBlocks();
}
-void Heap::releaseDelayedReleasedObjects()
+void Heap::reportExtraMemoryCostSlowCase(size_t cost)
{
-#if USE(CF)
- // We need to guard against the case that releasing an object can create more objects due to the
- // release calling into JS. When those JS call(s) exit and all locks are being dropped we end up
- // back here and could try to recursively release objects. We guard that with a recursive entry
- // count. Only the initial call will release objects, recursive calls simple return and let the
- // the initial call to the function take care of any objects created during release time.
- // This also means that we need to loop until there are no objects in m_delayedReleaseObjects
- // and use a temp Vector for the actual releasing.
- if (!m_delayedReleaseRecursionCount++) {
- while (!m_delayedReleaseObjects.isEmpty()) {
- ASSERT(m_vm->currentThreadIsHoldingAPILock());
-
- Vector<RetainPtr<CFTypeRef>> objectsToRelease = WTFMove(m_delayedReleaseObjects);
-
- {
- // We need to drop locks before calling out to arbitrary code.
- JSLock::DropAllLocks dropAllLocks(m_vm);
+ // Our frequency of garbage collection tries to balance memory use against speed
+ // by collecting based on the number of newly created values. However, for values
+ // that hold on to a great deal of memory that's not in the form of other JS values,
+ // that is not good enough - in some cases a lot of those objects can pile up and
+ // use crazy amounts of memory without a GC happening. So we track these extra
+ // memory costs. Only unusually large objects are noted, and we only keep track
+ // of this extra cost until the next GC. In garbage collected languages, most values
+ // are either very short lived temporaries, or have extremely long lifetimes. So
+ // if a large value survives one garbage collection, there is not much point to
+ // collecting more frequently as long as it stays alive.
- objectsToRelease.clear();
- }
- }
- }
- m_delayedReleaseRecursionCount--;
-#endif
-}
-
-void Heap::reportExtraMemoryAllocatedSlowCase(size_t size)
-{
- didAllocate(size);
+ didAllocate(cost);
collectIfNecessaryOrDefer();
}
-void Heap::deprecatedReportExtraMemorySlowCase(size_t size)
-{
- m_deprecatedExtraMemorySize += size;
- reportExtraMemoryAllocatedSlowCase(size);
-}
-
void Heap::reportAbandonedObjectGraph()
{
// Our clients don't know exactly how much memory they
// are abandoning so we just guess for them.
- double abandonedBytes = 0.1 * m_sizeAfterLastCollect;
+ double abandonedBytes = 0.10 * m_sizeAfterLastCollect;
// We want to accelerate the next collection. Because memory has just
// been abandoned, the next collection has the potential to
@@ -441,11 +333,9 @@ void Heap::reportAbandonedObjectGraph()
void Heap::didAbandon(size_t bytes)
{
- if (m_fullActivityCallback) {
- m_fullActivityCallback->didAllocate(
- m_sizeAfterLastCollect - m_sizeAfterLastFullCollect + m_bytesAllocatedThisCycle + m_bytesAbandonedSinceLastFullCollect);
- }
- m_bytesAbandonedSinceLastFullCollect += bytes;
+ if (m_activityCallback)
+ m_activityCallback->didAllocate(m_bytesAllocatedThisCycle + m_bytesAbandonedThisCycle);
+ m_bytesAbandonedThisCycle += bytes;
}
void Heap::protect(JSValue k)
@@ -478,6 +368,40 @@ void Heap::addReference(JSCell* cell, ArrayBuffer* buffer)
}
}
+void Heap::markProtectedObjects(HeapRootVisitor& heapRootVisitor)
+{
+ ProtectCountSet::iterator end = m_protectedValues.end();
+ for (ProtectCountSet::iterator it = m_protectedValues.begin(); it != end; ++it)
+ heapRootVisitor.visit(&it->key);
+}
+
+void Heap::pushTempSortVector(Vector<ValueStringPair, 0, UnsafeVectorOverflow>* tempVector)
+{
+ m_tempSortingVectors.append(tempVector);
+}
+
+void Heap::popTempSortVector(Vector<ValueStringPair, 0, UnsafeVectorOverflow>* tempVector)
+{
+ ASSERT_UNUSED(tempVector, tempVector == m_tempSortingVectors.last());
+ m_tempSortingVectors.removeLast();
+}
+
+void Heap::markTempSortVectors(HeapRootVisitor& heapRootVisitor)
+{
+ typedef Vector<Vector<ValueStringPair, 0, UnsafeVectorOverflow>* > VectorOfValueStringVectors;
+
+ VectorOfValueStringVectors::iterator end = m_tempSortingVectors.end();
+ for (VectorOfValueStringVectors::iterator it = m_tempSortingVectors.begin(); it != end; ++it) {
+ Vector<ValueStringPair, 0, UnsafeVectorOverflow>* tempSortingVector = *it;
+
+ Vector<ValueStringPair>::iterator vectorEnd = tempSortingVector->end();
+ for (Vector<ValueStringPair>::iterator vectorIt = tempSortingVector->begin(); vectorIt != vectorEnd; ++vectorIt) {
+ if (vectorIt->first)
+ heapRootVisitor.visit(&vectorIt->first);
+ }
+ }
+}
+
void Heap::harvestWeakReferences()
{
m_slotVisitor.harvestWeakReferences();
@@ -485,7 +409,6 @@ void Heap::harvestWeakReferences()
void Heap::finalizeUnconditionalFinalizers()
{
- GCPHASE(FinalizeUnconditionalFinalizers);
m_slotVisitor.finalizeUnconditionalFinalizers();
}
@@ -504,413 +427,231 @@ void Heap::didFinishIterating()
m_objectSpace.didFinishIterating();
}
-void Heap::completeAllDFGPlans()
+void Heap::getConservativeRegisterRoots(HashSet<JSCell*>& roots)
{
-#if ENABLE(DFG_JIT)
- DFG::completeAllPlansForVM(*m_vm);
-#endif
+ ASSERT(isValidThreadState(m_vm));
+ ConservativeRoots stackRoots(&m_objectSpace.blocks(), &m_storageSpace);
+ stack().gatherConservativeRoots(stackRoots);
+ size_t stackRootCount = stackRoots.size();
+ JSCell** registerRoots = stackRoots.roots();
+ for (size_t i = 0; i < stackRootCount; i++) {
+ setMarked(registerRoots[i]);
+ roots.add(registerRoots[i]);
+ }
}
-void Heap::markRoots(double gcStartTime, void* stackOrigin, void* stackTop, MachineThreads::RegisterState& calleeSavedRegisters)
+void Heap::markRoots()
{
- SamplingRegion samplingRegion("Garbage Collection: Marking");
+ SamplingRegion samplingRegion("Garbage Collection: Tracing");
GCPHASE(MarkRoots);
ASSERT(isValidThreadState(m_vm));
+#if ENABLE(OBJECT_MARK_LOGGING)
+ double gcStartTime = WTF::monotonicallyIncreasingTime();
+#endif
+
+ void* dummy;
+
// We gather conservative roots before clearing mark bits because conservative
// gathering uses the mark bits to determine whether a reference is valid.
- ConservativeRoots conservativeRoots(&m_objectSpace.blocks(), &m_storageSpace);
- gatherStackRoots(conservativeRoots, stackOrigin, stackTop, calleeSavedRegisters);
- gatherJSStackRoots(conservativeRoots);
- gatherScratchBufferRoots(conservativeRoots);
+ ConservativeRoots machineThreadRoots(&m_objectSpace.blocks(), &m_storageSpace);
+ m_jitStubRoutines.clearMarks();
+ {
+ GCPHASE(GatherConservativeRoots);
+ m_machineThreads.gatherConservativeRoots(machineThreadRoots, &dummy);
+ }
-#if ENABLE(DFG_JIT)
- DFG::rememberCodeBlocks(*m_vm);
-#endif
+ ConservativeRoots stackRoots(&m_objectSpace.blocks(), &m_storageSpace);
+ m_codeBlocks.clearMarks();
+ {
+ GCPHASE(GatherStackRoots);
+ stack().gatherConservativeRoots(stackRoots, m_jitStubRoutines, m_codeBlocks);
+ }
-#if ENABLE(SAMPLING_PROFILER)
- if (SamplingProfiler* samplingProfiler = m_vm->samplingProfiler()) {
- // Note that we need to own the lock from now until we're done
- // marking the SamplingProfiler's data because once we verify the
- // SamplingProfiler's stack traces, we don't want it to accumulate
- // more stack traces before we get the chance to mark it.
- // This lock is released inside visitSamplingProfiler().
- samplingProfiler->getLock().lock();
- samplingProfiler->processUnverifiedStackTraces();
+#if ENABLE(DFG_JIT)
+ ConservativeRoots scratchBufferRoots(&m_objectSpace.blocks(), &m_storageSpace);
+ {
+ GCPHASE(GatherScratchBufferRoots);
+ m_vm->gatherConservativeRoots(scratchBufferRoots);
}
-#endif // ENABLE(SAMPLING_PROFILER)
+#endif
- if (m_operationInProgress == FullCollection) {
- m_opaqueRoots.clear();
- m_slotVisitor.clearMarkStack();
+ {
+ GCPHASE(ClearLivenessData);
+ m_objectSpace.clearNewlyAllocated();
+ m_objectSpace.clearMarks();
}
- clearLivenessData();
+ m_sharedData.didStartMarking();
+ SlotVisitor& visitor = m_slotVisitor;
+ visitor.setup();
+ HeapRootVisitor heapRootVisitor(visitor);
- m_parallelMarkersShouldExit = false;
+#if ENABLE(GGC)
+ Vector<const JSCell*> rememberedSet(m_slotVisitor.markStack().size());
+ m_slotVisitor.markStack().fillVector(rememberedSet);
+#endif
- m_helperClient.setFunction(
- [this] () {
- SlotVisitor* slotVisitor;
- {
- LockHolder locker(m_parallelSlotVisitorLock);
- if (m_availableParallelSlotVisitors.isEmpty()) {
- std::unique_ptr<SlotVisitor> newVisitor =
- std::make_unique<SlotVisitor>(*this);
- slotVisitor = newVisitor.get();
- m_parallelSlotVisitors.append(WTFMove(newVisitor));
- } else
- slotVisitor = m_availableParallelSlotVisitors.takeLast();
- }
+ {
+ ParallelModeEnabler enabler(visitor);
- WTF::registerGCThread();
+ m_vm->smallStrings.visitStrongReferences(visitor);
- {
- ParallelModeEnabler parallelModeEnabler(*slotVisitor);
- slotVisitor->didStartMarking();
- slotVisitor->drainFromShared(SlotVisitor::SlaveDrain);
- }
+ {
+ GCPHASE(VisitMachineRoots);
+ MARK_LOG_ROOT(visitor, "C++ Stack");
+ visitor.append(machineThreadRoots);
+ visitor.donateAndDrain();
+ }
+ {
+ GCPHASE(VisitStackRoots);
+ MARK_LOG_ROOT(visitor, "Stack");
+ visitor.append(stackRoots);
+ visitor.donateAndDrain();
+ }
+#if ENABLE(DFG_JIT)
+ {
+ GCPHASE(VisitScratchBufferRoots);
+ MARK_LOG_ROOT(visitor, "Scratch Buffers");
+ visitor.append(scratchBufferRoots);
+ visitor.donateAndDrain();
+ }
+#endif
+ {
+ GCPHASE(VisitProtectedObjects);
+ MARK_LOG_ROOT(visitor, "Protected Objects");
+ markProtectedObjects(heapRootVisitor);
+ visitor.donateAndDrain();
+ }
+ {
+ GCPHASE(VisitTempSortVectors);
+ MARK_LOG_ROOT(visitor, "Temp Sort Vectors");
+ markTempSortVectors(heapRootVisitor);
+ visitor.donateAndDrain();
+ }
- {
- LockHolder locker(m_parallelSlotVisitorLock);
- m_availableParallelSlotVisitors.append(slotVisitor);
+ {
+ GCPHASE(MarkingArgumentBuffers);
+ if (m_markListSet && m_markListSet->size()) {
+ MARK_LOG_ROOT(visitor, "Argument Buffers");
+ MarkedArgumentBuffer::markLists(heapRootVisitor, *m_markListSet);
+ visitor.donateAndDrain();
}
- });
-
- m_slotVisitor.didStartMarking();
+ }
+ if (m_vm->exception()) {
+ GCPHASE(MarkingException);
+ MARK_LOG_ROOT(visitor, "Exceptions");
+ heapRootVisitor.visit(m_vm->addressOfException());
+ visitor.donateAndDrain();
+ }
- HeapRootVisitor heapRootVisitor(m_slotVisitor);
-
- {
- ParallelModeEnabler enabler(m_slotVisitor);
-
- m_slotVisitor.donateAndDrain();
- visitExternalRememberedSet();
- visitSmallStrings();
- visitConservativeRoots(conservativeRoots);
- visitProtectedObjects(heapRootVisitor);
- visitArgumentBuffers(heapRootVisitor);
- visitException(heapRootVisitor);
- visitStrongHandles(heapRootVisitor);
- visitHandleStack(heapRootVisitor);
- visitSamplingProfiler();
- traceCodeBlocksAndJITStubRoutines();
- converge();
+ {
+ GCPHASE(VisitStrongHandles);
+ MARK_LOG_ROOT(visitor, "Strong Handles");
+ m_handleSet.visitStrongHandles(heapRootVisitor);
+ visitor.donateAndDrain();
+ }
+
+ {
+ GCPHASE(HandleStack);
+ MARK_LOG_ROOT(visitor, "Handle Stack");
+ m_handleStack.visit(heapRootVisitor);
+ visitor.donateAndDrain();
+ }
+
+ {
+ GCPHASE(TraceCodeBlocksAndJITStubRoutines);
+ MARK_LOG_ROOT(visitor, "Trace Code Blocks and JIT Stub Routines");
+ m_codeBlocks.traceMarked(visitor);
+ m_jitStubRoutines.traceMarkedStubRoutines(visitor);
+ visitor.donateAndDrain();
+ }
+
+#if ENABLE(PARALLEL_GC)
+ {
+ GCPHASE(Convergence);
+ visitor.drainFromShared(SlotVisitor::MasterDrain);
+ }
+#endif
}
// Weak references must be marked last because their liveness depends on
// the liveness of the rest of the object graph.
- visitWeakHandles(heapRootVisitor);
-
{
- std::lock_guard<Lock> lock(m_markingMutex);
- m_parallelMarkersShouldExit = true;
- m_markingConditionVariable.notifyAll();
- }
- m_helperClient.finish();
- updateObjectCounts(gcStartTime);
- resetVisitors();
-}
-
-void Heap::copyBackingStores()
-{
- GCPHASE(CopyBackingStores);
- if (m_operationInProgress == EdenCollection)
- m_storageSpace.startedCopying<EdenCollection>();
- else {
- ASSERT(m_operationInProgress == FullCollection);
- m_storageSpace.startedCopying<FullCollection>();
+ GCPHASE(VisitingLiveWeakHandles);
+ MARK_LOG_ROOT(visitor, "Live Weak Handles");
+ while (true) {
+ m_objectSpace.visitWeakSets(heapRootVisitor);
+ harvestWeakReferences();
+ if (visitor.isEmpty())
+ break;
+ {
+ ParallelModeEnabler enabler(visitor);
+ visitor.donateAndDrain();
+#if ENABLE(PARALLEL_GC)
+ visitor.drainFromShared(SlotVisitor::MasterDrain);
+#endif
+ }
+ }
}
- if (m_storageSpace.shouldDoCopyPhase()) {
- if (m_operationInProgress == EdenCollection) {
- // Reset the vector to be empty, but don't throw away the backing store.
- m_blocksToCopy.shrink(0);
- for (CopiedBlock* block = m_storageSpace.m_newGen.fromSpace->head(); block; block = block->next())
- m_blocksToCopy.append(block);
- } else {
- ASSERT(m_operationInProgress == FullCollection);
- WTF::copyToVector(m_storageSpace.m_blockSet, m_blocksToCopy);
+#if ENABLE(GGC)
+ {
+ GCPHASE(ClearRememberedSet);
+ for (unsigned i = 0; i < rememberedSet.size(); ++i) {
+ const JSCell* cell = rememberedSet[i];
+ MarkedBlock::blockFor(cell)->clearRemembered(cell);
}
-
- ParallelVectorIterator<Vector<CopiedBlock*>> iterator(
- m_blocksToCopy, s_blockFragmentLength);
-
- // Note that it's safe to use the [&] capture list here, even though we're creating a task
- // that other threads run. That's because after runFunctionInParallel() returns, the task
- // we have created is not going to be running anymore. Hence, everything on the stack here
- // outlives the task.
- m_helperClient.runFunctionInParallel(
- [&] () {
- CopyVisitor copyVisitor(*this);
-
- iterator.iterate(
- [&] (CopiedBlock* block) {
- if (!block->hasWorkList())
- return;
-
- CopyWorkList& workList = block->workList();
- for (CopyWorklistItem item : workList) {
- if (item.token() == ButterflyCopyToken) {
- JSObject::copyBackingStore(
- item.cell(), copyVisitor, ButterflyCopyToken);
- continue;
- }
-
- item.cell()->methodTable()->copyBackingStore(
- item.cell(), copyVisitor, item.token());
- }
-
- ASSERT(!block->liveBytes());
- m_storageSpace.recycleEvacuatedBlock(block, m_operationInProgress);
- });
- });
}
-
- m_storageSpace.doneCopying();
-}
-
-void Heap::gatherStackRoots(ConservativeRoots& roots, void* stackOrigin, void* stackTop, MachineThreads::RegisterState& calleeSavedRegisters)
-{
- GCPHASE(GatherStackRoots);
- m_jitStubRoutines.clearMarks();
- m_machineThreads.gatherConservativeRoots(roots, m_jitStubRoutines, m_codeBlocks, stackOrigin, stackTop, calleeSavedRegisters);
-}
-
-void Heap::gatherJSStackRoots(ConservativeRoots& roots)
-{
-#if !ENABLE(JIT)
- GCPHASE(GatherJSStackRoots);
- stack().gatherConservativeRoots(roots, m_jitStubRoutines, m_codeBlocks);
-#else
- UNUSED_PARAM(roots);
#endif
-}
-void Heap::gatherScratchBufferRoots(ConservativeRoots& roots)
-{
-#if ENABLE(DFG_JIT)
- GCPHASE(GatherScratchBufferRoots);
- m_vm->gatherConservativeRoots(roots);
-#else
- UNUSED_PARAM(roots);
-#endif
-}
+ GCCOUNTER(VisitedValueCount, visitor.visitCount());
-void Heap::clearLivenessData()
-{
- GCPHASE(ClearLivenessData);
- if (m_operationInProgress == FullCollection)
- m_codeBlocks.clearMarksForFullCollection();
-
- m_objectSpace.clearNewlyAllocated();
- m_objectSpace.clearMarks();
-}
-
-void Heap::visitExternalRememberedSet()
-{
-#if JSC_OBJC_API_ENABLED
- scanExternalRememberedSet(*m_vm, m_slotVisitor);
-#endif
-}
-
-void Heap::visitSmallStrings()
-{
- GCPHASE(VisitSmallStrings);
- if (!m_vm->smallStrings.needsToBeVisited(m_operationInProgress))
- return;
-
- m_vm->smallStrings.visitStrongReferences(m_slotVisitor);
- if (Options::logGC() == GCLogging::Verbose)
- dataLog("Small strings:\n", m_slotVisitor);
- m_slotVisitor.donateAndDrain();
-}
-
-void Heap::visitConservativeRoots(ConservativeRoots& roots)
-{
- GCPHASE(VisitConservativeRoots);
- m_slotVisitor.append(roots);
-
- if (Options::logGC() == GCLogging::Verbose)
- dataLog("Conservative Roots:\n", m_slotVisitor);
-
- m_slotVisitor.donateAndDrain();
-}
-
-void Heap::visitCompilerWorklistWeakReferences()
-{
-#if ENABLE(DFG_JIT)
- for (auto worklist : m_suspendedCompilerWorklists)
- worklist->visitWeakReferences(m_slotVisitor);
-
- if (Options::logGC() == GCLogging::Verbose)
- dataLog("DFG Worklists:\n", m_slotVisitor);
+ m_sharedData.didFinishMarking();
+#if ENABLE(OBJECT_MARK_LOGGING)
+ size_t visitCount = visitor.visitCount();
+#if ENABLE(PARALLEL_GC)
+ visitCount += m_sharedData.childVisitCount();
#endif
-}
-
-void Heap::removeDeadCompilerWorklistEntries()
-{
-#if ENABLE(DFG_JIT)
- GCPHASE(FinalizeDFGWorklists);
- for (auto worklist : m_suspendedCompilerWorklists)
- worklist->removeDeadPlans(*m_vm);
+ MARK_LOG_MESSAGE2("\nNumber of live Objects after full GC %lu, took %.6f secs\n", visitCount, WTF::monotonicallyIncreasingTime() - gcStartTime);
#endif
-}
-
-void Heap::visitProtectedObjects(HeapRootVisitor& heapRootVisitor)
-{
- GCPHASE(VisitProtectedObjects);
-
- for (auto& pair : m_protectedValues)
- heapRootVisitor.visit(&pair.key);
-
- if (Options::logGC() == GCLogging::Verbose)
- dataLog("Protected Objects:\n", m_slotVisitor);
-
- m_slotVisitor.donateAndDrain();
-}
-
-void Heap::visitArgumentBuffers(HeapRootVisitor& visitor)
-{
- GCPHASE(MarkingArgumentBuffers);
- if (!m_markListSet || !m_markListSet->size())
- return;
-
- MarkedArgumentBuffer::markLists(visitor, *m_markListSet);
-
- if (Options::logGC() == GCLogging::Verbose)
- dataLog("Argument Buffers:\n", m_slotVisitor);
-
- m_slotVisitor.donateAndDrain();
-}
-
-void Heap::visitException(HeapRootVisitor& visitor)
-{
- GCPHASE(MarkingException);
- if (!m_vm->exception() && !m_vm->lastException())
- return;
-
- visitor.visit(m_vm->addressOfException());
- visitor.visit(m_vm->addressOfLastException());
-
- if (Options::logGC() == GCLogging::Verbose)
- dataLog("Exceptions:\n", m_slotVisitor);
-
- m_slotVisitor.donateAndDrain();
-}
-
-void Heap::visitStrongHandles(HeapRootVisitor& visitor)
-{
- GCPHASE(VisitStrongHandles);
- m_handleSet.visitStrongHandles(visitor);
-
- if (Options::logGC() == GCLogging::Verbose)
- dataLog("Strong Handles:\n", m_slotVisitor);
-
- m_slotVisitor.donateAndDrain();
-}
-
-void Heap::visitHandleStack(HeapRootVisitor& visitor)
-{
- GCPHASE(VisitHandleStack);
- m_handleStack.visit(visitor);
-
- if (Options::logGC() == GCLogging::Verbose)
- dataLog("Handle Stack:\n", m_slotVisitor);
-
- m_slotVisitor.donateAndDrain();
-}
-void Heap::visitSamplingProfiler()
-{
-#if ENABLE(SAMPLING_PROFILER)
- if (SamplingProfiler* samplingProfiler = m_vm->samplingProfiler()) {
- ASSERT(samplingProfiler->getLock().isLocked());
- GCPHASE(VisitSamplingProfiler);
- samplingProfiler->visit(m_slotVisitor);
- if (Options::logGC() == GCLogging::Verbose)
- dataLog("Sampling Profiler data:\n", m_slotVisitor);
-
- m_slotVisitor.donateAndDrain();
- samplingProfiler->getLock().unlock();
+ if (m_operationInProgress == EdenCollection) {
+ m_totalBytesVisited += visitor.bytesVisited();
+ m_totalBytesCopied += visitor.bytesCopied();
+ } else {
+ ASSERT(m_operationInProgress == FullCollection);
+ m_totalBytesVisited = visitor.bytesVisited();
+ m_totalBytesCopied = visitor.bytesCopied();
}
-#endif // ENABLE(SAMPLING_PROFILER)
-}
-
-void Heap::traceCodeBlocksAndJITStubRoutines()
-{
- GCPHASE(TraceCodeBlocksAndJITStubRoutines);
- m_jitStubRoutines.traceMarkedStubRoutines(m_slotVisitor);
-
- if (Options::logGC() == GCLogging::Verbose)
- dataLog("Code Blocks and JIT Stub Routines:\n", m_slotVisitor);
-
- m_slotVisitor.donateAndDrain();
-}
-
-void Heap::converge()
-{
- GCPHASE(Convergence);
- m_slotVisitor.drainFromShared(SlotVisitor::MasterDrain);
-}
-
-void Heap::visitWeakHandles(HeapRootVisitor& visitor)
-{
- GCPHASE(VisitingLiveWeakHandles);
- while (true) {
- m_objectSpace.visitWeakSets(visitor);
- harvestWeakReferences();
- visitCompilerWorklistWeakReferences();
- if (m_slotVisitor.isEmpty())
- break;
-
- if (Options::logGC() == GCLogging::Verbose)
- dataLog("Live Weak Handles:\n", m_slotVisitor);
+#if ENABLE(PARALLEL_GC)
+ m_totalBytesVisited += m_sharedData.childBytesVisited();
+ m_totalBytesCopied += m_sharedData.childBytesCopied();
+#endif
- {
- ParallelModeEnabler enabler(m_slotVisitor);
- m_slotVisitor.donateAndDrain();
- m_slotVisitor.drainFromShared(SlotVisitor::MasterDrain);
- }
- }
+ visitor.reset();
+#if ENABLE(PARALLEL_GC)
+ m_sharedData.resetChildren();
+#endif
+ m_sharedData.reset();
}
-void Heap::updateObjectCounts(double gcStartTime)
+template <HeapOperation collectionType>
+void Heap::copyBackingStores()
{
- GCCOUNTER(VisitedValueCount, m_slotVisitor.visitCount() + threadVisitCount());
-
- if (Options::logGC() == GCLogging::Verbose) {
- size_t visitCount = m_slotVisitor.visitCount();
- visitCount += threadVisitCount();
- dataLogF("\nNumber of live Objects after GC %lu, took %.6f secs\n", static_cast<unsigned long>(visitCount), WTF::monotonicallyIncreasingTime() - gcStartTime);
- }
-
- size_t bytesRemovedFromOldSpaceDueToReallocation =
- m_storageSpace.takeBytesRemovedFromOldSpaceDueToReallocation();
-
- if (m_operationInProgress == FullCollection) {
- m_totalBytesVisited = 0;
- m_totalBytesCopied = 0;
+ m_storageSpace.startedCopying<collectionType>();
+ if (m_storageSpace.shouldDoCopyPhase()) {
+ m_sharedData.didStartCopying();
+ m_copyVisitor.startCopying();
+ m_copyVisitor.copyFromShared();
+ m_copyVisitor.doneCopying();
+ // We need to wait for everybody to finish and return their CopiedBlocks
+ // before signaling that the phase is complete.
+ m_storageSpace.doneCopying();
+ m_sharedData.didFinishCopying();
} else
- m_totalBytesCopied -= bytesRemovedFromOldSpaceDueToReallocation;
-
- m_totalBytesVisitedThisCycle = m_slotVisitor.bytesVisited() + threadBytesVisited();
- m_totalBytesCopiedThisCycle = m_slotVisitor.bytesCopied() + threadBytesCopied();
-
- m_totalBytesVisited += m_totalBytesVisitedThisCycle;
- m_totalBytesCopied += m_totalBytesCopiedThisCycle;
-}
-
-void Heap::resetVisitors()
-{
- m_slotVisitor.reset();
-
- for (auto& parallelVisitor : m_parallelSlotVisitors)
- parallelVisitor->reset();
-
- ASSERT(m_sharedMarkStack.isEmpty());
- m_weakReferenceHarvesters.removeAll();
+ m_storageSpace.doneCopying();
}
size_t Heap::objectCount()
@@ -918,19 +659,29 @@ size_t Heap::objectCount()
return m_objectSpace.objectCount();
}
-size_t Heap::extraMemorySize()
+size_t Heap::extraSize()
{
- return m_extraMemorySize + m_deprecatedExtraMemorySize + m_arrayBuffers.size();
+ return m_extraMemoryUsage + m_arrayBuffers.size();
}
size_t Heap::size()
{
- return m_objectSpace.size() + m_storageSpace.size() + extraMemorySize();
+ return m_objectSpace.size() + m_storageSpace.size() + extraSize();
}
size_t Heap::capacity()
{
- return m_objectSpace.capacity() + m_storageSpace.capacity() + extraMemorySize();
+ return m_objectSpace.capacity() + m_storageSpace.capacity() + extraSize();
+}
+
+size_t Heap::sizeAfterCollect()
+{
+ // The result here may not agree with the normal Heap::size().
+ // This is due to the fact that we only count live copied bytes
+ // rather than all used (including dead) copied bytes, thus it's
+ // always the case that m_totalBytesCopied <= m_storageSpace.size().
+ ASSERT(m_totalBytesCopied <= m_storageSpace.size());
+ return m_totalBytesVisited + m_totalBytesCopied + extraSize();
}
size_t Heap::protectedGlobalObjectCount()
@@ -949,116 +700,79 @@ size_t Heap::protectedObjectCount()
return forEachProtectedCell<Count>();
}
-std::unique_ptr<TypeCountSet> Heap::protectedObjectTypeCounts()
+PassOwnPtr<TypeCountSet> Heap::protectedObjectTypeCounts()
{
return forEachProtectedCell<RecordType>();
}
-std::unique_ptr<TypeCountSet> Heap::objectTypeCounts()
+PassOwnPtr<TypeCountSet> Heap::objectTypeCounts()
{
HeapIterationScope iterationScope(*this);
return m_objectSpace.forEachLiveCell<RecordType>(iterationScope);
}
-void Heap::deleteAllCodeBlocks()
+void Heap::deleteAllCompiledCode()
{
- // If JavaScript is running, it's not safe to delete all JavaScript code, since
- // we'll end up returning to deleted code.
- RELEASE_ASSERT(!m_vm->entryScope);
- ASSERT(m_operationInProgress == NoOperation);
-
- completeAllDFGPlans();
-
- for (ExecutableBase* executable : m_executables)
- executable->clearCode();
-}
+ // If JavaScript is running, it's not safe to delete code, since we'll end
+ // up deleting code that is live on the stack.
+ if (m_vm->entryScope)
+ return;
-void Heap::deleteAllUnlinkedCodeBlocks()
-{
- for (ExecutableBase* current : m_executables) {
+ for (ExecutableBase* current = m_compiledCode.head(); current; current = current->next()) {
if (!current->isFunctionExecutable())
continue;
- static_cast<FunctionExecutable*>(current)->unlinkedExecutable()->clearCode();
+ static_cast<FunctionExecutable*>(current)->clearCodeIfNotCompiling();
}
+
+ m_codeBlocks.clearMarks();
+ m_codeBlocks.deleteUnmarkedAndUnreferenced();
}
-void Heap::clearUnmarkedExecutables()
+void Heap::deleteUnmarkedCompiledCode()
{
- GCPHASE(ClearUnmarkedExecutables);
- for (unsigned i = m_executables.size(); i--;) {
- ExecutableBase* current = m_executables[i];
+ ExecutableBase* next;
+ for (ExecutableBase* current = m_compiledCode.head(); current; current = next) {
+ next = current->next();
if (isMarked(current))
continue;
- // Eagerly dereference the Executable's JITCode in order to run watchpoint
- // destructors. Otherwise, watchpoints might fire for deleted CodeBlocks.
- current->clearCode();
- std::swap(m_executables[i], m_executables.last());
- m_executables.removeLast();
+ // We do this because executable memory is limited on some platforms and because
+ // CodeBlock requires eager finalization.
+ ExecutableBase::clearCodeVirtual(current);
+ m_compiledCode.remove(current);
}
- m_executables.shrinkToFit();
-}
-
-void Heap::deleteUnmarkedCompiledCode()
-{
- GCPHASE(DeleteCodeBlocks);
- clearUnmarkedExecutables();
- m_codeBlocks.deleteUnmarkedAndUnreferenced(m_operationInProgress);
+ m_codeBlocks.deleteUnmarkedAndUnreferenced();
m_jitStubRoutines.deleteUnmarkedJettisonedStubRoutines();
}
void Heap::addToRememberedSet(const JSCell* cell)
{
ASSERT(cell);
- ASSERT(!Options::useConcurrentJIT() || !isCompilationThread());
- ASSERT(cell->cellState() == CellState::OldBlack);
- // Indicate that this object is grey and that it's one of the following:
- // - A re-greyed object during a concurrent collection.
- // - An old remembered object.
- // "OldGrey" doesn't tell us which of these things is true, but we usually treat the two cases the
- // same.
- cell->setCellState(CellState::OldGrey);
- m_slotVisitor.appendToMarkStack(const_cast<JSCell*>(cell));
-}
-
-void* Heap::copyBarrier(const JSCell*, void*& pointer)
-{
- // Do nothing for now, except making sure that the low bits are masked off. This helps to
- // simulate enough of this barrier that at least we can test the low bits assumptions.
- pointer = bitwise_cast<void*>(
- bitwise_cast<uintptr_t>(pointer) & ~static_cast<uintptr_t>(CopyBarrierBase::spaceBits));
-
- return pointer;
+ ASSERT(!Options::enableConcurrentJIT() || !isCompilationThread());
+ if (isInRememberedSet(cell))
+ return;
+ MarkedBlock::blockFor(cell)->setRemembered(cell);
+ m_slotVisitor.unconditionallyAppend(const_cast<JSCell*>(cell));
}
-void Heap::collectAndSweep(HeapOperation collectionType)
+void Heap::collectAllGarbage()
{
if (!m_isSafeToCollect)
return;
- collect(collectionType);
+ m_shouldDoFullCollection = true;
+ collect();
SamplingRegion samplingRegion("Garbage Collection: Sweeping");
-
- DeferGCForAWhile deferGC(*this);
+ DelayedReleaseScope delayedReleaseScope(m_objectSpace);
m_objectSpace.sweep();
m_objectSpace.shrink();
-
- sweepAllLogicallyEmptyWeakBlocks();
}
-NEVER_INLINE void Heap::collect(HeapOperation collectionType)
-{
- void* stackTop;
- ALLOCATE_AND_GET_REGISTER_STATE(registers);
-
- collectImpl(collectionType, wtfThreadData().stack().origin(), &stackTop, registers);
+static double minute = 60.0;
- sanitizeStackForVM(m_vm);
-}
-
-NEVER_INLINE void Heap::collectImpl(HeapOperation collectionType, void* stackOrigin, void* stackTop, MachineThreads::RegisterState& calleeSavedRegisters)
+void Heap::collect()
{
#if ENABLE(ALLOCATION_LOGGING)
dataLogF("JSC GC starting collection.\n");
@@ -1066,276 +780,127 @@ NEVER_INLINE void Heap::collectImpl(HeapOperation collectionType, void* stackOri
double before = 0;
if (Options::logGC()) {
- dataLog("[GC: ", capacity() / 1024, " kb ");
+ dataLog("[GC: ");
before = currentTimeMS();
}
SamplingRegion samplingRegion("Garbage Collection");
- if (vm()->typeProfiler()) {
- DeferGCForAWhile awhile(*this);
- vm()->typeProfilerLog()->processLogEntries(ASCIILiteral("GC"));
- }
-
RELEASE_ASSERT(!m_deferralDepth);
+ GCPHASE(Collect);
ASSERT(vm()->currentThreadIsHoldingAPILock());
- RELEASE_ASSERT(vm()->atomicStringTable() == wtfThreadData().atomicStringTable());
+ RELEASE_ASSERT(vm()->identifierTable == wtfThreadData().currentIdentifierTable());
ASSERT(m_isSafeToCollect);
JAVASCRIPTCORE_GC_BEGIN();
RELEASE_ASSERT(m_operationInProgress == NoOperation);
-
- suspendCompilerThreads();
- willStartCollection(collectionType);
- GCPHASE(Collect);
-
- double gcStartTime = WTF::monotonicallyIncreasingTime();
- if (m_verifier) {
- // Verify that live objects from the last GC cycle haven't been corrupted by
- // mutators before we begin this new GC cycle.
- m_verifier->verify(HeapVerifier::Phase::BeforeGC);
-
- m_verifier->initializeGCCycle();
- m_verifier->gatherLiveObjects(HeapVerifier::Phase::BeforeMarking);
- }
-
- flushOldStructureIDTables();
- stopAllocation();
- flushWriteBarrierBuffer();
-
- markRoots(gcStartTime, stackOrigin, stackTop, calleeSavedRegisters);
-
- if (m_verifier) {
- m_verifier->gatherLiveObjects(HeapVerifier::Phase::AfterMarking);
- m_verifier->verify(HeapVerifier::Phase::AfterMarking);
- }
- JAVASCRIPTCORE_GC_MARKED();
-
- if (vm()->typeProfiler())
- vm()->typeProfiler()->invalidateTypeSetCache();
-
- reapWeakHandles();
- pruneStaleEntriesFromWeakGCMaps();
- sweepArrayBuffers();
- snapshotMarkedSpace();
-
- copyBackingStores();
-
- finalizeUnconditionalFinalizers();
- removeDeadCompilerWorklistEntries();
- deleteUnmarkedCompiledCode();
- deleteSourceProviderCaches();
- notifyIncrementalSweeper();
- writeBarrierCurrentlyExecutingCodeBlocks();
-
- resetAllocators();
- updateAllocationLimits();
- didFinishCollection(gcStartTime);
- resumeCompilerThreads();
-
- if (m_verifier) {
- m_verifier->trimDeadObjects();
- m_verifier->verify(HeapVerifier::Phase::AfterGC);
- }
-
- if (Options::logGC()) {
- double after = currentTimeMS();
- dataLog(after - before, " ms]\n");
- }
-}
-
-void Heap::suspendCompilerThreads()
-{
-#if ENABLE(DFG_JIT)
- GCPHASE(SuspendCompilerThreads);
- ASSERT(m_suspendedCompilerWorklists.isEmpty());
- for (unsigned i = DFG::numberOfWorklists(); i--;) {
- if (DFG::Worklist* worklist = DFG::worklistForIndexOrNull(i)) {
- m_suspendedCompilerWorklists.append(worklist);
- worklist->suspendAllThreads();
- }
+
+ {
+ RecursiveAllocationScope scope(*this);
+ m_vm->prepareToDiscardCode();
}
-#endif
-}
-void Heap::willStartCollection(HeapOperation collectionType)
-{
- GCPHASE(StartingCollection);
-
- if (Options::logGC())
- dataLog("=> ");
-
- if (shouldDoFullCollection(collectionType)) {
+ bool isFullCollection = m_shouldDoFullCollection;
+ if (isFullCollection) {
m_operationInProgress = FullCollection;
+ m_slotVisitor.clearMarkStack();
m_shouldDoFullCollection = false;
if (Options::logGC())
dataLog("FullCollection, ");
} else {
+#if ENABLE(GGC)
m_operationInProgress = EdenCollection;
if (Options::logGC())
dataLog("EdenCollection, ");
+#else
+ m_operationInProgress = FullCollection;
+ m_slotVisitor.clearMarkStack();
+ if (Options::logGC())
+ dataLog("FullCollection, ");
+#endif
}
- if (m_operationInProgress == FullCollection) {
- m_sizeBeforeLastFullCollect = m_sizeAfterLastCollect + m_bytesAllocatedThisCycle;
- m_extraMemorySize = 0;
- m_deprecatedExtraMemorySize = 0;
-
- if (m_fullActivityCallback)
- m_fullActivityCallback->willCollect();
- } else {
- ASSERT(m_operationInProgress == EdenCollection);
- m_sizeBeforeLastEdenCollect = m_sizeAfterLastCollect + m_bytesAllocatedThisCycle;
- }
-
- if (m_edenActivityCallback)
- m_edenActivityCallback->willCollect();
-
- for (auto* observer : m_observers)
- observer->willGarbageCollect();
-}
-
-void Heap::flushOldStructureIDTables()
-{
- GCPHASE(FlushOldStructureIDTables);
- m_structureIDTable.flushOldTables();
-}
-
-void Heap::flushWriteBarrierBuffer()
-{
- GCPHASE(FlushWriteBarrierBuffer);
- if (m_operationInProgress == EdenCollection) {
- m_writeBarrierBuffer.flush(*this);
- return;
- }
- m_writeBarrierBuffer.reset();
-}
-
-void Heap::stopAllocation()
-{
- GCPHASE(StopAllocation);
- m_objectSpace.stopAllocating();
if (m_operationInProgress == FullCollection)
- m_storageSpace.didStartFullCollection();
-}
-
-void Heap::reapWeakHandles()
-{
- GCPHASE(ReapingWeakHandles);
- m_objectSpace.reapWeakSets();
-}
+ m_extraMemoryUsage = 0;
-void Heap::pruneStaleEntriesFromWeakGCMaps()
-{
- GCPHASE(PruningStaleEntriesFromWeakGCMaps);
- if (m_operationInProgress != FullCollection)
- return;
- for (auto& pruneCallback : m_weakGCMaps.values())
- pruneCallback();
-}
+ if (m_activityCallback)
+ m_activityCallback->willCollect();
-void Heap::sweepArrayBuffers()
-{
- GCPHASE(SweepingArrayBuffers);
- m_arrayBuffers.sweep();
-}
+ double lastGCStartTime = WTF::monotonicallyIncreasingTime();
+ if (lastGCStartTime - m_lastCodeDiscardTime > minute) {
+ deleteAllCompiledCode();
+ m_lastCodeDiscardTime = WTF::monotonicallyIncreasingTime();
+ }
-struct MarkedBlockSnapshotFunctor : public MarkedBlock::VoidFunctor {
- MarkedBlockSnapshotFunctor(Vector<MarkedBlock*>& blocks)
- : m_index(0)
- , m_blocks(blocks)
{
+ GCPHASE(StopAllocation);
+ m_objectSpace.stopAllocating();
+ if (m_operationInProgress == FullCollection)
+ m_storageSpace.didStartFullCollection();
}
- void operator()(MarkedBlock* block) { m_blocks[m_index++] = block; }
+ {
+ GCPHASE(FlushWriteBarrierBuffer);
+ if (m_operationInProgress == EdenCollection)
+ m_writeBarrierBuffer.flush(*this);
+ else
+ m_writeBarrierBuffer.reset();
+ }
- size_t m_index;
- Vector<MarkedBlock*>& m_blocks;
-};
+ markRoots();
+
+ {
+ GCPHASE(ReapingWeakHandles);
+ m_objectSpace.reapWeakSets();
+ }
-void Heap::snapshotMarkedSpace()
-{
- GCPHASE(SnapshotMarkedSpace);
+ JAVASCRIPTCORE_GC_MARKED();
+
+ {
+ GCPHASE(SweepingArrayBuffers);
+ m_arrayBuffers.sweep();
+ }
- if (m_operationInProgress == EdenCollection) {
- m_blockSnapshot.appendVector(m_objectSpace.blocksWithNewObjects());
- // Sort and deduplicate the block snapshot since we might be appending to an unfinished work list.
- std::sort(m_blockSnapshot.begin(), m_blockSnapshot.end());
- m_blockSnapshot.shrink(std::unique(m_blockSnapshot.begin(), m_blockSnapshot.end()) - m_blockSnapshot.begin());
- } else {
- m_blockSnapshot.resizeToFit(m_objectSpace.blocks().set().size());
+ if (m_operationInProgress == FullCollection) {
+ m_blockSnapshot.resize(m_objectSpace.blocks().set().size());
MarkedBlockSnapshotFunctor functor(m_blockSnapshot);
m_objectSpace.forEachBlock(functor);
}
-}
-void Heap::deleteSourceProviderCaches()
-{
- GCPHASE(DeleteSourceProviderCaches);
- m_vm->clearSourceProviderCaches();
-}
+ if (m_operationInProgress == FullCollection)
+ copyBackingStores<FullCollection>();
+ else
+ copyBackingStores<EdenCollection>();
-void Heap::notifyIncrementalSweeper()
-{
- GCPHASE(NotifyIncrementalSweeper);
+ {
+ GCPHASE(FinalizeUnconditionalFinalizers);
+ finalizeUnconditionalFinalizers();
+ }
- if (m_operationInProgress == FullCollection) {
- if (!m_logicallyEmptyWeakBlocks.isEmpty())
- m_indexOfNextLogicallyEmptyWeakBlockToSweep = 0;
+ {
+ GCPHASE(DeleteCodeBlocks);
+ deleteUnmarkedCompiledCode();
}
- m_sweeper->startSweeping();
-}
+ {
+ GCPHASE(DeleteSourceProviderCaches);
+ m_vm->clearSourceProviderCaches();
+ }
-void Heap::writeBarrierCurrentlyExecutingCodeBlocks()
-{
- GCPHASE(WriteBarrierCurrentlyExecutingCodeBlocks);
- m_codeBlocks.writeBarrierCurrentlyExecutingCodeBlocks(this);
-}
+ if (m_operationInProgress == FullCollection)
+ m_sweeper->startSweeping(m_blockSnapshot);
-void Heap::resetAllocators()
-{
- GCPHASE(ResetAllocators);
- m_objectSpace.resetAllocators();
-}
+ {
+ GCPHASE(AddCurrentlyExecutingCodeBlocksToRememberedSet);
+ m_codeBlocks.rememberCurrentlyExecutingCodeBlocks(this);
+ }
-void Heap::updateAllocationLimits()
-{
- GCPHASE(UpdateAllocationLimits);
-
- // Calculate our current heap size threshold for the purpose of figuring out when we should
- // run another collection. This isn't the same as either size() or capacity(), though it should
- // be somewhere between the two. The key is to match the size calculations involved calls to
- // didAllocate(), while never dangerously underestimating capacity(). In extreme cases of
- // fragmentation, we may have size() much smaller than capacity(). Our collector sometimes
- // temporarily allows very high fragmentation because it doesn't defragment old blocks in copied
- // space.
- size_t currentHeapSize = 0;
-
- // For marked space, we use the total number of bytes visited. This matches the logic for
- // MarkedAllocator's calls to didAllocate(), which effectively accounts for the total size of
- // objects allocated rather than blocks used. This will underestimate capacity(), and in case
- // of fragmentation, this may be substantial. Fortunately, marked space rarely fragments because
- // cells usually have a narrow range of sizes. So, the underestimation is probably OK.
- currentHeapSize += m_totalBytesVisited;
-
- // For copied space, we use the capacity of storage space. This is because copied space may get
- // badly fragmented between full collections. This arises when each eden collection evacuates
- // much less than one CopiedBlock's worth of stuff. It can also happen when CopiedBlocks get
- // pinned due to very short-lived objects. In such a case, we want to get to a full collection
- // sooner rather than later. If we used m_totalBytesCopied, then for for each CopiedBlock that an
- // eden allocation promoted, we would only deduct the one object's size from eden size. This
- // would mean that we could "leak" many CopiedBlocks before we did a full collection and
- // defragmented all of them. It would be great to use m_totalBytesCopied, but we'd need to
- // augment it with something that accounts for those fragmented blocks.
- // FIXME: Make it possible to compute heap size using m_totalBytesCopied rather than
- // m_storageSpace.capacity()
- // https://bugs.webkit.org/show_bug.cgi?id=150268
- ASSERT(m_totalBytesCopied <= m_storageSpace.size());
- currentHeapSize += m_storageSpace.capacity();
+ m_bytesAbandonedThisCycle = 0;
- // It's up to the user to ensure that extraMemorySize() ends up corresponding to allocation-time
- // extra memory reporting.
- currentHeapSize += extraMemorySize();
+ {
+ GCPHASE(ResetAllocators);
+ m_objectSpace.resetAllocators();
+ }
+ size_t currentHeapSize = sizeAfterCollect();
if (Options::gcMaxHeapSize() && currentHeapSize > Options::gcMaxHeapSize())
HeapStatistics::exitWithFailure();
@@ -1345,82 +910,55 @@ void Heap::updateAllocationLimits()
// fixed minimum.
m_maxHeapSize = max(minHeapSize(m_heapType, m_ramSize), proportionalHeapSize(currentHeapSize, m_ramSize));
m_maxEdenSize = m_maxHeapSize - currentHeapSize;
- m_sizeAfterLastFullCollect = currentHeapSize;
- m_bytesAbandonedSinceLastFullCollect = 0;
} else {
- static const bool verbose = false;
-
ASSERT(currentHeapSize >= m_sizeAfterLastCollect);
m_maxEdenSize = m_maxHeapSize - currentHeapSize;
- m_sizeAfterLastEdenCollect = currentHeapSize;
- if (verbose) {
- dataLog("Max heap size: ", m_maxHeapSize, "\n");
- dataLog("Current heap size: ", currentHeapSize, "\n");
- dataLog("Size after last eden collection: ", m_sizeAfterLastEdenCollect, "\n");
- }
double edenToOldGenerationRatio = (double)m_maxEdenSize / (double)m_maxHeapSize;
- if (verbose)
- dataLog("Eden to old generation ratio: ", edenToOldGenerationRatio, "\n");
double minEdenToOldGenerationRatio = 1.0 / 3.0;
if (edenToOldGenerationRatio < minEdenToOldGenerationRatio)
m_shouldDoFullCollection = true;
- // This seems suspect at first, but what it does is ensure that the nursery size is fixed.
m_maxHeapSize += currentHeapSize - m_sizeAfterLastCollect;
m_maxEdenSize = m_maxHeapSize - currentHeapSize;
- if (m_fullActivityCallback) {
- ASSERT(currentHeapSize >= m_sizeAfterLastFullCollect);
- m_fullActivityCallback->didAllocate(currentHeapSize - m_sizeAfterLastFullCollect);
- }
}
m_sizeAfterLastCollect = currentHeapSize;
- m_bytesAllocatedThisCycle = 0;
-
- if (Options::logGC())
- dataLog(currentHeapSize / 1024, " kb, ");
-}
-void Heap::didFinishCollection(double gcStartTime)
-{
- GCPHASE(FinishingCollection);
- double gcEndTime = WTF::monotonicallyIncreasingTime();
- HeapOperation operation = m_operationInProgress;
- if (m_operationInProgress == FullCollection)
- m_lastFullGCLength = gcEndTime - gcStartTime;
- else
- m_lastEdenGCLength = gcEndTime - gcStartTime;
+ m_bytesAllocatedThisCycle = 0;
+ double lastGCEndTime = WTF::monotonicallyIncreasingTime();
+ m_lastGCLength = lastGCEndTime - lastGCStartTime;
if (Options::recordGCPauseTimes())
- HeapStatistics::recordGCPauseTime(gcStartTime, gcEndTime);
+ HeapStatistics::recordGCPauseTime(lastGCStartTime, lastGCEndTime);
+ RELEASE_ASSERT(m_operationInProgress == EdenCollection || m_operationInProgress == FullCollection);
+
+ m_operationInProgress = NoOperation;
+ JAVASCRIPTCORE_GC_END();
if (Options::useZombieMode())
zombifyDeadObjects();
- if (Options::useImmortalObjects())
+ if (Options::objectsAreImmortal())
markDeadObjects();
- if (Options::dumpObjectStatistics())
- HeapStatistics::dumpObjectStatistics(this);
-
- if (Options::logGC() == GCLogging::Verbose)
- GCLogging::dumpObjectGraph(this);
-
- RELEASE_ASSERT(m_operationInProgress == EdenCollection || m_operationInProgress == FullCollection);
- m_operationInProgress = NoOperation;
- JAVASCRIPTCORE_GC_END();
-
- for (auto* observer : m_observers)
- observer->didGarbageCollect(operation);
+ if (Options::showObjectStatistics())
+ HeapStatistics::showObjectStatistics(this);
+
+ if (Options::logGC()) {
+ double after = currentTimeMS();
+ dataLog(after - before, " ms, ", currentHeapSize / 1024, " kb]\n");
+ }
}
-void Heap::resumeCompilerThreads()
+bool Heap::collectIfNecessaryOrDefer()
{
-#if ENABLE(DFG_JIT)
- GCPHASE(ResumeCompilerThreads);
- for (auto worklist : m_suspendedCompilerWorklists)
- worklist->resumeAllThreads();
- m_suspendedCompilerWorklists.clear();
-#endif
+ if (m_deferralDepth)
+ return false;
+
+ if (!shouldCollect())
+ return false;
+
+ collect();
+ return true;
}
void Heap::markDeadObjects()
@@ -1429,29 +967,19 @@ void Heap::markDeadObjects()
m_objectSpace.forEachDeadCell<MarkObject>(iterationScope);
}
-void Heap::setFullActivityCallback(PassRefPtr<FullGCActivityCallback> activityCallback)
+void Heap::setActivityCallback(PassOwnPtr<GCActivityCallback> activityCallback)
{
- m_fullActivityCallback = activityCallback;
+ m_activityCallback = activityCallback;
}
-void Heap::setEdenActivityCallback(PassRefPtr<EdenGCActivityCallback> activityCallback)
+GCActivityCallback* Heap::activityCallback()
{
- m_edenActivityCallback = activityCallback;
+ return m_activityCallback.get();
}
-GCActivityCallback* Heap::fullActivityCallback()
+void Heap::setIncrementalSweeper(PassOwnPtr<IncrementalSweeper> sweeper)
{
- return m_fullActivityCallback.get();
-}
-
-GCActivityCallback* Heap::edenActivityCallback()
-{
- return m_edenActivityCallback.get();
-}
-
-void Heap::setIncrementalSweeper(std::unique_ptr<IncrementalSweeper> sweeper)
-{
- m_sweeper = WTFMove(sweeper);
+ m_sweeper = sweeper;
}
IncrementalSweeper* Heap::sweeper()
@@ -1461,16 +989,14 @@ IncrementalSweeper* Heap::sweeper()
void Heap::setGarbageCollectionTimerEnabled(bool enable)
{
- if (m_fullActivityCallback)
- m_fullActivityCallback->setEnabled(enable);
- if (m_edenActivityCallback)
- m_edenActivityCallback->setEnabled(enable);
+ if (m_activityCallback)
+ m_activityCallback->setEnabled(enable);
}
void Heap::didAllocate(size_t bytes)
{
- if (m_edenActivityCallback)
- m_edenActivityCallback->didAllocate(m_bytesAllocatedThisCycle + m_bytesAbandonedSinceLastFullCollect);
+ if (m_activityCallback)
+ m_activityCallback->didAllocate(m_bytesAllocatedThisCycle + m_bytesAbandonedThisCycle);
m_bytesAllocatedThisCycle += bytes;
}
@@ -1498,31 +1024,14 @@ void Heap::FinalizerOwner::finalize(Handle<Unknown> handle, void* context)
WeakSet::deallocate(WeakImpl::asWeakImpl(slot));
}
-void Heap::addExecutable(ExecutableBase* executable)
+void Heap::addCompiledCode(ExecutableBase* executable)
{
- m_executables.append(executable);
-}
-
-void Heap::collectAllGarbageIfNotDoneRecently()
-{
- if (!m_fullActivityCallback) {
- collectAllGarbage();
- return;
- }
-
- if (m_fullActivityCallback->didSyncGCRecently()) {
- // A synchronous GC was already requested recently so we merely accelerate next collection.
- reportAbandonedObjectGraph();
- return;
- }
-
- m_fullActivityCallback->setDidSyncGCRecently();
- collectAllGarbage();
+ m_compiledCode.append(executable);
}
class Zombify : public MarkedBlock::VoidFunctor {
public:
- inline void visit(JSCell* cell)
+ void operator()(JSCell* cell)
{
void** current = reinterpret_cast<void**>(cell);
@@ -1533,111 +1042,59 @@ public:
void* limit = static_cast<void*>(reinterpret_cast<char*>(cell) + MarkedBlock::blockFor(cell)->cellSize());
for (; current < limit; current++)
- *current = zombifiedBits;
- }
- IterationStatus operator()(JSCell* cell)
- {
- visit(cell);
- return IterationStatus::Continue;
+ *current = reinterpret_cast<void*>(0xbbadbeef);
}
};
void Heap::zombifyDeadObjects()
{
// Sweep now because destructors will crash once we're zombified.
- {
- SamplingRegion samplingRegion("Garbage Collection: Sweeping");
- m_objectSpace.zombifySweep();
- }
+ m_objectSpace.sweep();
HeapIterationScope iterationScope(*this);
m_objectSpace.forEachDeadCell<Zombify>(iterationScope);
}
-void Heap::flushWriteBarrierBuffer(JSCell* cell)
+void Heap::incrementDeferralDepth()
{
- m_writeBarrierBuffer.flush(*this);
- m_writeBarrierBuffer.add(cell);
+ RELEASE_ASSERT(m_deferralDepth < 100); // Sanity check to make sure this doesn't get ridiculous.
+
+ m_deferralDepth++;
}
-bool Heap::shouldDoFullCollection(HeapOperation requestedCollectionType) const
+void Heap::decrementDeferralDepth()
{
- if (!Options::useGenerationalGC())
- return true;
-
- switch (requestedCollectionType) {
- case EdenCollection:
- return false;
- case FullCollection:
- return true;
- case AnyCollection:
- return m_shouldDoFullCollection;
- default:
- RELEASE_ASSERT_NOT_REACHED();
- return false;
- }
- RELEASE_ASSERT_NOT_REACHED();
- return false;
+ RELEASE_ASSERT(m_deferralDepth >= 1);
+
+ m_deferralDepth--;
}
-void Heap::addLogicallyEmptyWeakBlock(WeakBlock* block)
+void Heap::decrementDeferralDepthAndGCIfNeeded()
{
- m_logicallyEmptyWeakBlocks.append(block);
+ decrementDeferralDepth();
+ collectIfNecessaryOrDefer();
}
-void Heap::sweepAllLogicallyEmptyWeakBlocks()
+void Heap::writeBarrier(const JSCell* from)
{
- if (m_logicallyEmptyWeakBlocks.isEmpty())
+#if ENABLE(GGC)
+ ASSERT_GC_OBJECT_LOOKS_VALID(const_cast<JSCell*>(from));
+ if (!from || !isMarked(from))
return;
-
- m_indexOfNextLogicallyEmptyWeakBlockToSweep = 0;
- while (sweepNextLogicallyEmptyWeakBlock()) { }
+ Heap* heap = Heap::heap(from);
+ heap->addToRememberedSet(from);
+#else
+ UNUSED_PARAM(from);
+#endif
}
-bool Heap::sweepNextLogicallyEmptyWeakBlock()
+void Heap::flushWriteBarrierBuffer(JSCell* cell)
{
- if (m_indexOfNextLogicallyEmptyWeakBlockToSweep == WTF::notFound)
- return false;
-
- WeakBlock* block = m_logicallyEmptyWeakBlocks[m_indexOfNextLogicallyEmptyWeakBlockToSweep];
-
- block->sweep();
- if (block->isEmpty()) {
- std::swap(m_logicallyEmptyWeakBlocks[m_indexOfNextLogicallyEmptyWeakBlockToSweep], m_logicallyEmptyWeakBlocks.last());
- m_logicallyEmptyWeakBlocks.removeLast();
- WeakBlock::destroy(*this, block);
- } else
- m_indexOfNextLogicallyEmptyWeakBlockToSweep++;
-
- if (m_indexOfNextLogicallyEmptyWeakBlockToSweep >= m_logicallyEmptyWeakBlocks.size()) {
- m_indexOfNextLogicallyEmptyWeakBlockToSweep = WTF::notFound;
- return false;
- }
-
- return true;
-}
-
-size_t Heap::threadVisitCount()
-{
- unsigned long result = 0;
- for (auto& parallelVisitor : m_parallelSlotVisitors)
- result += parallelVisitor->visitCount();
- return result;
-}
-
-size_t Heap::threadBytesVisited()
-{
- size_t result = 0;
- for (auto& parallelVisitor : m_parallelSlotVisitors)
- result += parallelVisitor->bytesVisited();
- return result;
-}
-
-size_t Heap::threadBytesCopied()
-{
- size_t result = 0;
- for (auto& parallelVisitor : m_parallelSlotVisitors)
- result += parallelVisitor->bytesCopied();
- return result;
+#if ENABLE(GGC)
+ m_writeBarrierBuffer.flush(*this);
+ m_writeBarrierBuffer.add(cell);
+#else
+ UNUSED_PARAM(cell);
+#endif
}
} // namespace JSC