5#include "PageAllocation.h"
6#include "PageReservation.h"
8#include <private/qnumeric_p.h>
9#include <private/qv4alloca_p.h>
10#include <private/qv4engine_p.h>
11#include <private/qv4identifiertable_p.h>
12#include <private/qv4mapobject_p.h>
13#include <private/qv4mm_p.h>
14#include <private/qv4object_p.h>
15#include <private/qv4profiling_p.h>
16#include <private/qv4qobjectwrapper_p.h>
17#include <private/qv4setobject_p.h>
18#include <private/qv4stackframe_p.h>
20#include <QtQml/qqmlengine.h>
22#include <QtCore/qalgorithms.h>
23#include <QtCore/qelapsedtimer.h>
24#include <QtCore/qloggingcategory.h>
25#include <QtCore/qmap.h>
26#include <QtCore/qscopedvaluerollback.h>
34#if !defined(MM_STATS) && !defined(QT_NO_DEBUG)
39#define DEBUG qDebug() << "MM:"
41#define DEBUG if (1
) ; else qDebug() << "MM:"
45#include <valgrind/valgrind.h>
46#include <valgrind/memcheck.h>
49#ifdef V4_USE_HEAPTRACK
50#include <heaptrack_api.h>
54#include <sys/storage.h>
57#if USE(PTHREADS) && HAVE(PTHREAD_NP_H)
58#include <pthread_np.h>
62Q_STATIC_LOGGING_CATEGORY(lcGcAllocatorStats,
"qt.qml.gc.allocatorStats")
63Q_STATIC_LOGGING_CATEGORY(lcGcStateTransitions,
"qt.qml.gc.stateTransitions")
64Q_STATIC_LOGGING_CATEGORY(lcGcForcedRuns,
"qt.qml.gc.forcedRuns")
65Q_STATIC_LOGGING_CATEGORY(lcGcStepExecution,
"qt.qml.gc.stepExecution")
81 NumChunks =
sizeof(quint64),
90 size += Chunk::ChunkSize;
94 pageReservation = PageReservation::reserve(size, OSAllocator::JSGCHeapPages);
95 base =
reinterpret_cast<Chunk *>((
reinterpret_cast<quintptr>(pageReservation.base()) + Chunk::ChunkSize - 1) & ~(Chunk::ChunkSize - 1));
97 availableBytes = size - (
reinterpret_cast<quintptr>(base) -
reinterpret_cast<quintptr>(pageReservation.base()));
98 if (availableBytes < SegmentSize)
102 qSwap(pageReservation, other.pageReservation);
103 qSwap(base, other.base);
104 qSwap(allocatedMap, other.allocatedMap);
105 qSwap(availableBytes, other.availableBytes);
111 pageReservation.deallocate();
116 quint64 bit =
static_cast<quint64>(1) << index;
121 quint64 bit =
static_cast<quint64>(1) << index;
122 allocatedMap &= ~bit;
126 quint64 bit =
static_cast<quint64>(1) << index;
127 return (allocatedMap & bit);
131 void free(Chunk *chunk, size_t size) {
132 DEBUG <<
"freeing chunk" << chunk;
133 size_t index =
static_cast<size_t>(chunk - base);
134 size_t end = qMin(
static_cast<size_t>(
NumChunks), index + (size - 1)/Chunk::ChunkSize + 1);
135 while (index < end) {
136 Q_ASSERT(testBit(index));
141 size_t pageSize = WTF::pageSize();
142 size = (size + pageSize - 1) & ~(pageSize - 1);
143#if !defined(Q_OS_LINUX) && !defined(Q_OS_WIN)
148 memset(chunk, 0, size);
150 pageReservation.decommit(chunk, size);
154 return c >= base && c < base + nChunks;
166 if (!allocatedMap && size >= SegmentSize) {
168 Q_ASSERT(availableBytes >= size);
169 pageReservation.commit(base, size);
170 allocatedMap = ~
static_cast<quint64>(0);
173 size_t requiredChunks = (size +
sizeof(Chunk) - 1)/
sizeof(Chunk);
175 Chunk *candidate =
nullptr;
176 for (uint i = 0; i <
nChunks; ++i) {
179 candidate = base + i;
185 if (sequence == requiredChunks) {
186 pageReservation.commit(candidate, size);
187 for (uint i = 0; i < requiredChunks; ++i)
188 setBit(candidate - base + i);
189 DEBUG <<
"allocated chunk " << candidate << Qt::hex << size;
201 size += Chunk::HeaderSize;
202 size_t pageSize = WTF::pageSize();
203 size = (size + pageSize - 1) & ~(pageSize - 1);
204 if (size < Chunk::ChunkSize)
205 size = Chunk::ChunkSize;
210 void free(Chunk *chunk, size_t size = 0);
217 size = requiredChunkSize(size);
218 for (
auto &m : memorySegments) {
219 if (~m.allocatedMap) {
220 Chunk *c = m.allocate(size);
227 memorySegments.push_back(MemorySegment(size));
228 Chunk *c = memorySegments.back().allocate(size);
235 size = requiredChunkSize(size);
236 for (
auto &m : memorySegments) {
237 if (m.contains(chunk)) {
255#define SDUMP if (1
) ; else qDebug
261 SDUMP() <<
"sweeping chunk" <<
this;
275 SDUMP() <<
" index=" <<
i;
304#ifdef V4_USE_HEAPTRACK
349#ifdef V4_USE_HEAPTRACK
369#if QT_POINTER_SIZE == 8
379#if QT_POINTER_SIZE == 8
449 while ((
m = *
last)) {
519#ifdef V4_USE_HEAPTRACK
586#ifdef V4_USE_HEAPTRACK
594 HeapItem *itemToFree = c.chunk->first();
595 Heap::Base *b = *itemToFree;
596 const VTable *v = b->internalClass->vtable;
602 c.segment->free(c.chunk, c.size);
605 chunkAllocator->free(c.chunk, c.size);
607#ifdef V4_USE_HEAPTRACK
608 heaptrack_report_free(c.chunk);
643using ExtraData = GCStateInfo::ExtraData;
644GCState markStart(GCStateMachine *that, ExtraData &)
647 that->mm->m_markStack = std::make_unique<MarkStack>(that->mm->engine);
648 that->mm->engine->isGCOngoing =
true;
649 return GCState::MarkGlobalObject;
652GCState markGlobalObject(GCStateMachine *that, ExtraData &)
654 that->mm->engine->markObjects(that->mm->m_markStack.get());
655 return GCState::MarkJSStack;
658GCState markJSStack(GCStateMachine *that, ExtraData &)
660 that->mm->collectFromJSStack(that->mm->markStack());
661 return GCState::InitMarkPersistentValues;
664GCState initMarkPersistentValues(GCStateMachine *that, ExtraData &stateData)
666 if (!that->mm->m_persistentValues)
667 return GCState::InitMarkWeakValues;
668 stateData = GCIteratorStorage { that->mm->m_persistentValues->begin() };
669 return GCState::MarkPersistentValues;
673 MarkLoopIterationCount = 1024,
674 MarkLoopIterationCountForDrain = 10240,
677bool wasDrainNecessary(MarkStack *ms, QDeadlineTimer deadline)
679 if (ms->remainingBeforeSoftLimit() > MarkLoopIterationCount)
686GCState markPersistentValues(GCStateMachine *that, ExtraData &stateData) {
687 auto markStack = that->mm->markStack();
688 if (wasDrainNecessary(markStack, that->deadline) && that->deadline.hasExpired())
689 return GCState::MarkPersistentValues;
690 PersistentValueStorage::Iterator& it = get<GCIteratorStorage>(stateData).it;
692 for (
int i = 0; i < MarkLoopIterationCount; ++i) {
694 return GCState::InitMarkWeakValues;
695 if (Managed *m = (*it).as<Managed>())
699 return GCState::MarkPersistentValues;
702GCState initMarkWeakValues(GCStateMachine *that, ExtraData &stateData)
704 stateData = GCIteratorStorage { that->mm->m_weakValues->begin() };
705 return GCState::MarkWeakValues;
708GCState markWeakValues(GCStateMachine *that, ExtraData &stateData)
710 auto markStack = that->mm->markStack();
711 if (wasDrainNecessary(markStack, that->deadline) && that->deadline.hasExpired())
712 return GCState::MarkWeakValues;
713 PersistentValueStorage::Iterator& it = get<GCIteratorStorage>(stateData).it;
715 for (
int i = 0; i < MarkLoopIterationCount; ++i) {
717 return GCState::MarkDrain;
718 QObjectWrapper *qobjectWrapper = (*it).as<QObjectWrapper>();
722 QObject *qobject = qobjectWrapper->object();
725 bool keepAlive = QQmlData::keepAliveDuringGarbageCollection(qobject);
728 if (QObject *parent = qobject->parent()) {
729 while (parent->parent())
730 parent = parent->parent();
731 keepAlive = QQmlData::keepAliveDuringGarbageCollection(parent);
736 qobjectWrapper->mark(that->mm->markStack());
738 return GCState::MarkWeakValues;
741GCState markDrain(GCStateMachine *that, ExtraData &)
743 if (that->deadline.isForever()) {
744 that->mm->markStack()->drain();
745 return GCState::MarkReady;
747 auto drainState = that->mm->m_markStack->drain(that->deadline);
748 return drainState == MarkStack::DrainState::Complete
750 : GCState::MarkDrain;
753GCState markReady(GCStateMachine *that, ExtraData &)
755 auto isIncrementalRun = [](GCStateMachine* that){
756 return !that->mm->aggressiveGC && that->timeLimit.count() > 0;
759 if (that->mm->crossValidateIncrementalGC && isIncrementalRun(that))
760 return GCState::CrossValidateIncrementalMarkPhase;
761 return GCState::InitCallDestroyObjects;
764GCState crossValidateIncrementalMarkPhase(GCStateMachine *that, ExtraData &)
767 Chunk* operator()(Chunk* chunk) {
return chunk; }
768 Chunk* operator()(
const HugeItemAllocator::HugeChunk& chunk) {
return chunk.chunk; }
771 auto takeBlackBitmap = [&getChunk](
auto& allocator, std::vector<quintptr>& storage){
772 for (
auto chunk : allocator.chunks) {
773 for (
auto& bitmap : getChunk(chunk)->blackBitmap) {
774 storage.push_back(bitmap);
776 getChunk(chunk)->resetBlackBits();
780 auto runMarkPhase = [](GCStateMachine* that) {
782 that->mm->m_markStack.reset();
784 while (that->state != GCStateMachine::MarkReady) {
785 GCStateInfo& stateInfo = that->stateInfoMap[
int(that->state)];
786 that->state = stateInfo.execute(that, that->stateData);
790 auto checkBlackBitmap = [&that, &getChunk](
auto& allocator,
const std::vector<quintptr>& storedBitmap) {
791 auto reportError = [&allocator, &getChunk, &that](
std::size_t chunk_index,
std::size_t bitmap_index, uint bit_index){
792 #ifdef QT_BUILD_INTERNAL
794 if (
auto errors = that->bitmapErrors) {
795 errors->emplace_back(chunk_index, bitmap_index, bit_index);
801 auto object =
reinterpret_cast<Heap::Base*>(getChunk(allocator.chunks[chunk_index])->realBase() + (bit_index + (bitmap_index*Chunk::Bits)));
802 qDebug() <<
"Cross Validation Error on chunk" << chunk_index
803 <<
"on bitmap piece" << bitmap_index <<
"and bit" << bit_index
804 << ((object->internalClass) ?
"With type" :
"")
805 << ((object->internalClass) ?
806 Managed::typeToString(Managed::Type(object->internalClass->vtable->type)) : QString());
809 auto original = storedBitmap.begin();
810 for (
std::size_t chunk_index = 0; original != storedBitmap.end() && chunk_index < allocator.chunks.size(); ++chunk_index) {
811 for (
std::size_t bitmap_index = 0; bitmap_index < Chunk::EntriesInBitmap; ++bitmap_index) {
812 if (
auto differences = (~(*original)) & getChunk(allocator.chunks[chunk_index])->blackBitmap[bitmap_index]) {
813 while (differences != 0) {
814 uint bit_index = qCountTrailingZeroBits(differences);
815 reportError(chunk_index, bitmap_index, bit_index);
816 differences ^= quintptr{1} << bit_index;
824 #ifdef QT_BUILD_INTERNAL
825 if (
auto *errors = that->bitmapErrors)
829 std::vector<quintptr> blockBitmap{};
830 blockBitmap.reserve(Chunk::EntriesInBitmap * that->mm->blockAllocator.chunks.size());
831 takeBlackBitmap(that->mm->blockAllocator, blockBitmap);
833 std::vector<quintptr> hugeItemBitmap{};
834 hugeItemBitmap.reserve(Chunk::EntriesInBitmap * that->mm->hugeItemAllocator.chunks.size());
835 takeBlackBitmap(that->mm->hugeItemAllocator, hugeItemBitmap);
837 std::vector<quintptr> internalClassBitmap{};
838 internalClassBitmap.reserve(Chunk::EntriesInBitmap * that->mm->icAllocator.chunks.size());
839 takeBlackBitmap(that->mm->icAllocator, internalClassBitmap);
843 checkBlackBitmap(that->mm->blockAllocator, blockBitmap);
844 checkBlackBitmap(that->mm->hugeItemAllocator, hugeItemBitmap);
845 checkBlackBitmap(that->mm->icAllocator, internalClassBitmap);
847 return GCState::InitCallDestroyObjects;
851
852
853void redrain(GCStateMachine *that)
855 that->mm->collectFromJSStack(that->mm->markStack());
856 that->mm->m_markStack->drain();
859GCState initCallDestroyObjects(GCStateMachine *that, ExtraData &stateData)
863 if (!that->mm->m_weakValues)
864 return GCState::FreeWeakMaps;
865 stateData = GCIteratorStorage { that->mm->m_weakValues->begin() };
866 return GCState::CallDestroyObjects;
868GCState callDestroyObject(GCStateMachine *that, ExtraData &stateData)
870 PersistentValueStorage::Iterator& it = get<GCIteratorStorage>(stateData).it;
872 auto oldState = std::exchange(that->mm->gcBlocked, QV4::MemoryManager::Blockness::InCriticalSection);
873 auto cleanup = qScopeGuard([&]() {
874 that->mm->gcBlocked = oldState;
877 for (
int i = 0; i < MarkLoopIterationCount; ++i) {
879 return GCState::FreeWeakMaps;
880 Managed *m = (*it).managed();
882 if (!m || m->markBit())
886 if (QObjectWrapper *qobjectWrapper = m->as<QObjectWrapper>())
887 qobjectWrapper->destroyObject(
false);
889 return GCState::CallDestroyObjects;
892void freeWeakMaps(MemoryManager *mm)
894 for (
auto [map, lastMap] =
std::tuple {mm->weakMaps, &mm->weakMaps }; map; map = map->nextWeakMap) {
895 if (!map->isMarked())
897 map->removeUnmarkedKeys();
899 lastMap = &map->nextWeakMap;
903GCState freeWeakMaps(GCStateMachine *that, ExtraData &)
905 freeWeakMaps(that->mm);
906 return GCState::FreeWeakSets;
909void freeWeakSets(MemoryManager *mm)
911 for (
auto [set, lastSet] =
std::tuple {mm->weakSets, &mm->weakSets}; set; set = set->nextWeakSet) {
913 if (!set->isMarked())
915 set->removeUnmarkedKeys();
917 lastSet = &set->nextWeakSet;
921GCState freeWeakSets(GCStateMachine *that, ExtraData &)
923 freeWeakSets(that->mm);
924 return GCState::HandleQObjectWrappers;
927GCState handleQObjectWrappers(GCStateMachine *that, ExtraData &)
929 that->mm->cleanupDeletedQObjectWrappersInSweep();
930 return GCState::DoSweep;
933GCState doSweep(GCStateMachine *that, ExtraData &)
937 mm->engine->identifierTable->sweep();
938 mm->blockAllocator.sweep();
939 mm->hugeItemAllocator.sweep();
940 mm->icAllocator.sweep();
943 mm->blockAllocator.resetBlackBits();
944 mm->hugeItemAllocator.resetBlackBits();
945 mm->icAllocator.resetBlackBits();
947 mm->usedSlotsAfterLastFullSweep = mm->blockAllocator.usedSlotsAfterLastSweep + mm->icAllocator.usedSlotsAfterLastSweep;
948 mm->gcBlocked = MemoryManager::Unblocked;
949 mm->m_markStack.reset();
950 mm->engine->isGCOngoing =
false;
952 mm->updateUnmanagedHeapSizeGCLimit();
954 return GCState::Invalid;
976#ifdef V4_USE_VALGRIND
1228
1229
1230
1231
1278 const QLoggingCategory &stats = lcGcAllocatorStats();
1279 size_t totalSlotMem = 0;
1281 qDebug(stats) <<
"Slot map for" << title <<
"allocator:";
1282 for (uint i = 0; i < BlockAllocator::NumBins; ++i) {
1284 HeapItem *h = b->freeBins[i];
1287 totalSlotMem += h->freeData.availableSlots;
1288 h = h->freeData.next;
1291 qDebug(stats) <<
" number of entries in slot" << i <<
":" << nEntries;
1293 SDUMP() <<
" large slot map";
1294 HeapItem *h = b->freeBins[BlockAllocator::NumBins - 1];
1296 SDUMP() <<
" " << Qt::hex << (quintptr(h)/32) << h->freeData.availableSlots;
1297 h = h->freeData.next;
1301 qDebug(stats) <<
" total mem in bins" << totalSlotMem*Chunk::SlotSize;
1302 return totalSlotMem*Chunk::SlotSize;
1306
1307
1308
1309
1310
1311
1312
1313
1318 <<
"Tried to force the GC to complete a run but failed due to being in a critical section.";
1459#ifdef V4_USE_VALGRIND
1472 qDebug(
stats) <<
"Qml GC memory allocation statistics:";
1476 qDebug(
stats) <<
"Requests for different item sizes:";
1526 auto registerTimingWithResetOnOverflow = [](
1527 GCStateMachine::StepTiming& storage, quint64 timing, GCState state
1529 auto wouldOverflow = [](quint64 lhs, quint64 rhs) {
1530 return rhs > 0 && lhs > std::numeric_limits<quint64>::max() - rhs;
1533 if (wouldOverflow(storage.rolling_sum, timing) || wouldOverflow(storage.count, 1)) {
1534 qDebug(lcGcStepExecution) <<
"Resetting timings storage for"
1535 << QMetaEnum::fromType<GCState>().key(state) <<
"due to overflow.";
1536 storage.rolling_sum = timing;
1539 storage.rolling_sum += timing;
1544 GCStateMachine::StepTiming& storage = that->executionTiming[that->state];
1545 registerTimingWithResetOnOverflow(storage, timing, that->state);
1547 qDebug(lcGcStepExecution) <<
"Performed" << QMetaEnum::fromType<GCState>().key(that->state)
1548 <<
"in" << timing <<
"microseconds";
1549 qDebug(lcGcStepExecution) <<
"This step was performed" << storage.count <<
" time(s), executing in"
1550 << (storage.rolling_sum / storage.count) <<
"microseconds on average.";
1554 if (!that->collectTimings)
1555 return stateInfo.execute(that, that->stateData);
1557 QElapsedTimer timer;
1559 GCState next = stateInfo.execute(that, that->stateData);
1560 logStepTiming(that, timer.nsecsElapsed()/1000);
1566 if (that->state > GCState::InitCallDestroyObjects) {
1568
1569
1570
1571
1572
1573
1574
1575
1683 qDebug(
stats) <<
"!!!!!!!!!!!!!!!!!!!!! LOST MEM:" <<
lost <<
"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!";
1697#include "moc_qv4mm_p.cpp"
static void logStepTiming(GCStateMachine *that, quint64 timing)
static size_t dumpBins(BlockAllocator *b, const char *title)
static void freeHugeChunk(ChunkAllocator *chunkAllocator, const HugeItemAllocator::HugeChunk &c)
static GCState executeWithLoggingIfEnabled(GCStateMachine *that, GCStateInfo &stateInfo)
static void redrainDuringSweep(GCStateMachine *that)
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)
void free(Chunk *chunk, size_t size=0)
size_t requiredChunkSize(size_t size)
Chunk * allocate(size_t size=0)
std::vector< MemorySegment > memorySegments
void free(Chunk *chunk, size_t size)
Chunk * allocate(size_t size)
MemorySegment(size_t size)
PageReservation pageReservation
void setBit(size_t index)
bool testBit(size_t index) const
void clearBit(size_t index)
bool contains(Chunk *c) const
MemorySegment(MemorySegment &&other)