| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtQml module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qv4engine_p.h" |
| #include "qv4object_p.h" |
| #include "qv4objectproto_p.h" |
| #include "qv4mm_p.h" |
| #include "qv4qobjectwrapper_p.h" |
| #include "qv4identifiertable_p.h" |
| #include <QtCore/qalgorithms.h> |
| #include <QtCore/private/qnumeric_p.h> |
| #include <QtCore/qloggingcategory.h> |
| #include <private/qv4alloca_p.h> |
| #include <qqmlengine.h> |
| #include "PageReservation.h" |
| #include "PageAllocation.h" |
| #include "PageAllocationAligned.h" |
| #include "StdLibExtras.h" |
| |
| #include <QElapsedTimer> |
| #include <QMap> |
| #include <QScopedValueRollback> |
| |
| #include <iostream> |
| #include <cstdlib> |
| #include <algorithm> |
| #include "qv4profiling_p.h" |
| #include "qv4mapobject_p.h" |
| #include "qv4setobject_p.h" |
| #include "qv4writebarrier_p.h" |
| |
| //#define MM_STATS |
| |
| #if !defined(MM_STATS) && !defined(QT_NO_DEBUG) |
| #define MM_STATS |
| #endif |
| |
| #if MM_DEBUG |
| #define DEBUG qDebug() << "MM:" |
| #else |
| #define DEBUG if (1) ; else qDebug() << "MM:" |
| #endif |
| |
| #ifdef V4_USE_VALGRIND |
| #include <valgrind/valgrind.h> |
| #include <valgrind/memcheck.h> |
| #endif |
| |
| #ifdef V4_USE_HEAPTRACK |
| #include <heaptrack_api.h> |
| #endif |
| |
| #if OS(QNX) |
| #include <sys/storage.h> // __tls() |
| #endif |
| |
| #if USE(PTHREADS) && HAVE(PTHREAD_NP_H) |
| #include <pthread_np.h> |
| #endif |
| |
| Q_LOGGING_CATEGORY(lcGcStats, "qt.qml.gc.statistics") |
| Q_DECLARE_LOGGING_CATEGORY(lcGcStats) |
| Q_LOGGING_CATEGORY(lcGcAllocatorStats, "qt.qml.gc.allocatorStats") |
| Q_DECLARE_LOGGING_CATEGORY(lcGcAllocatorStats) |
| |
| using namespace WTF; |
| |
| QT_BEGIN_NAMESPACE |
| |
| namespace QV4 { |
| |
| enum { |
| MinSlotsGCLimit = QV4::Chunk::AvailableSlots*16, |
| GCOverallocation = 200 /* Max overallocation by the GC in % */ |
| }; |
| |
| struct MemorySegment { |
| enum { |
| #ifdef Q_OS_RTEMS |
| NumChunks = sizeof(quint64), |
| #else |
| NumChunks = 8*sizeof(quint64), |
| #endif |
| SegmentSize = NumChunks*Chunk::ChunkSize, |
| }; |
| |
| MemorySegment(size_t size) |
| { |
| size += Chunk::ChunkSize; // make sure we can get enough 64k aligment memory |
| if (size < SegmentSize) |
| size = SegmentSize; |
| |
| pageReservation = PageReservation::reserve(size, OSAllocator::JSGCHeapPages); |
| base = reinterpret_cast<Chunk *>((reinterpret_cast<quintptr>(pageReservation.base()) + Chunk::ChunkSize - 1) & ~(Chunk::ChunkSize - 1)); |
| nChunks = NumChunks; |
| availableBytes = size - (reinterpret_cast<quintptr>(base) - reinterpret_cast<quintptr>(pageReservation.base())); |
| if (availableBytes < SegmentSize) |
| --nChunks; |
| } |
| MemorySegment(MemorySegment &&other) { |
| qSwap(pageReservation, other.pageReservation); |
| qSwap(base, other.base); |
| qSwap(allocatedMap, other.allocatedMap); |
| qSwap(availableBytes, other.availableBytes); |
| qSwap(nChunks, other.nChunks); |
| } |
| |
| ~MemorySegment() { |
| if (base) |
| pageReservation.deallocate(); |
| } |
| |
| void setBit(size_t index) { |
| Q_ASSERT(index < nChunks); |
| quint64 bit = static_cast<quint64>(1) << index; |
| // qDebug() << " setBit" << hex << index << (index & (Bits - 1)) << bit; |
| allocatedMap |= bit; |
| } |
| void clearBit(size_t index) { |
| Q_ASSERT(index < nChunks); |
| quint64 bit = static_cast<quint64>(1) << index; |
| // qDebug() << " setBit" << hex << index << (index & (Bits - 1)) << bit; |
| allocatedMap &= ~bit; |
| } |
| bool testBit(size_t index) const { |
| Q_ASSERT(index < nChunks); |
| quint64 bit = static_cast<quint64>(1) << index; |
| return (allocatedMap & bit); |
| } |
| |
| Chunk *allocate(size_t size); |
| void free(Chunk *chunk, size_t size) { |
| DEBUG << "freeing chunk" << chunk; |
| size_t index = static_cast<size_t>(chunk - base); |
| size_t end = qMin(static_cast<size_t>(NumChunks), index + (size - 1)/Chunk::ChunkSize + 1); |
| while (index < end) { |
| Q_ASSERT(testBit(index)); |
| clearBit(index); |
| ++index; |
| } |
| |
| size_t pageSize = WTF::pageSize(); |
| size = (size + pageSize - 1) & ~(pageSize - 1); |
| #if !defined(Q_OS_LINUX) && !defined(Q_OS_WIN) |
| // Linux and Windows zero out pages that have been decommitted and get committed again. |
| // unfortunately that's not true on other OSes (e.g. BSD based ones), so zero out the |
| // memory before decommit, so that we can be sure that all chunks we allocate will be |
| // zero initialized. |
| memset(chunk, 0, size); |
| #endif |
| pageReservation.decommit(chunk, size); |
| } |
| |
| bool contains(Chunk *c) const { |
| return c >= base && c < base + nChunks; |
| } |
| |
| PageReservation pageReservation; |
| Chunk *base = nullptr; |
| quint64 allocatedMap = 0; |
| size_t availableBytes = 0; |
| uint nChunks = 0; |
| }; |
| |
| Chunk *MemorySegment::allocate(size_t size) |
| { |
| if (!allocatedMap && size >= SegmentSize) { |
| // chunk allocated for one huge allocation |
| Q_ASSERT(availableBytes >= size); |
| pageReservation.commit(base, size); |
| allocatedMap = ~static_cast<quint64>(0); |
| return base; |
| } |
| size_t requiredChunks = (size + sizeof(Chunk) - 1)/sizeof(Chunk); |
| uint sequence = 0; |
| Chunk *candidate = nullptr; |
| for (uint i = 0; i < nChunks; ++i) { |
| if (!testBit(i)) { |
| if (!candidate) |
| candidate = base + i; |
| ++sequence; |
| } else { |
| candidate = nullptr; |
| sequence = 0; |
| } |
| if (sequence == requiredChunks) { |
| pageReservation.commit(candidate, size); |
| for (uint i = 0; i < requiredChunks; ++i) |
| setBit(candidate - base + i); |
| DEBUG << "allocated chunk " << candidate << Qt::hex << size; |
| |
| return candidate; |
| } |
| } |
| return nullptr; |
| } |
| |
| struct ChunkAllocator { |
| ChunkAllocator() {} |
| |
| size_t requiredChunkSize(size_t size) { |
| size += Chunk::HeaderSize; // space required for the Chunk header |
| size_t pageSize = WTF::pageSize(); |
| size = (size + pageSize - 1) & ~(pageSize - 1); // align to page sizes |
| if (size < Chunk::ChunkSize) |
| size = Chunk::ChunkSize; |
| return size; |
| } |
| |
| Chunk *allocate(size_t size = 0); |
| void free(Chunk *chunk, size_t size = 0); |
| |
| std::vector<MemorySegment> memorySegments; |
| }; |
| |
| Chunk *ChunkAllocator::allocate(size_t size) |
| { |
| size = requiredChunkSize(size); |
| for (auto &m : memorySegments) { |
| if (~m.allocatedMap) { |
| Chunk *c = m.allocate(size); |
| if (c) |
| return c; |
| } |
| } |
| |
| // allocate a new segment |
| memorySegments.push_back(MemorySegment(size)); |
| Chunk *c = memorySegments.back().allocate(size); |
| Q_ASSERT(c); |
| return c; |
| } |
| |
| void ChunkAllocator::free(Chunk *chunk, size_t size) |
| { |
| size = requiredChunkSize(size); |
| for (auto &m : memorySegments) { |
| if (m.contains(chunk)) { |
| m.free(chunk, size); |
| return; |
| } |
| } |
| Q_ASSERT(false); |
| } |
| |
| #ifdef DUMP_SWEEP |
| QString binary(quintptr n) { |
| QString s = QString::number(n, 2); |
| while (s.length() < 64) |
| s.prepend(QChar::fromLatin1('0')); |
| return s; |
| } |
| #define SDUMP qDebug |
| #else |
| QString binary(quintptr) { return QString(); } |
| #define SDUMP if (1) ; else qDebug |
| #endif |
| |
| // Stores a classname -> freed count mapping. |
| typedef QHash<const char*, int> MMStatsHash; |
| Q_GLOBAL_STATIC(MMStatsHash, freedObjectStatsGlobal) |
| |
| // This indirection avoids sticking QHash code in each of the call sites, which |
| // shaves off some instructions in the case that it's unused. |
| static void increaseFreedCountForClass(const char *className) |
| { |
| (*freedObjectStatsGlobal())[className]++; |
| } |
| |
| //bool Chunk::sweep(ClassDestroyStatsCallback classCountPtr) |
| bool Chunk::sweep(ExecutionEngine *engine) |
| { |
| bool hasUsedSlots = false; |
| SDUMP() << "sweeping chunk" << this; |
| HeapItem *o = realBase(); |
| bool lastSlotFree = false; |
| for (uint i = 0; i < Chunk::EntriesInBitmap; ++i) { |
| #if WRITEBARRIER(none) |
| Q_ASSERT((grayBitmap[i] | blackBitmap[i]) == blackBitmap[i]); // check that we don't have gray only objects |
| #endif |
| quintptr toFree = objectBitmap[i] ^ blackBitmap[i]; |
| Q_ASSERT((toFree & objectBitmap[i]) == toFree); // check all black objects are marked as being used |
| quintptr e = extendsBitmap[i]; |
| SDUMP() << " index=" << i; |
| SDUMP() << " toFree =" << binary(toFree); |
| SDUMP() << " black =" << binary(blackBitmap[i]); |
| SDUMP() << " object =" << binary(objectBitmap[i]); |
| SDUMP() << " extends =" << binary(e); |
| if (lastSlotFree) |
| e &= (e + 1); // clear all lowest extent bits |
| while (toFree) { |
| uint index = qCountTrailingZeroBits(toFree); |
| quintptr bit = (static_cast<quintptr>(1) << index); |
| |
| toFree ^= bit; // mask out freed slot |
| // DEBUG << " index" << hex << index << toFree; |
| |
| // remove all extends slots that have been freed |
| // this is a bit of bit trickery. |
| quintptr mask = (bit << 1) - 1; // create a mask of 1's to the right of and up to the current bit |
| quintptr objmask = e | mask; // or'ing mask with e gives all ones until the end of the current object |
| quintptr result = objmask + 1; |
| Q_ASSERT(qCountTrailingZeroBits(result) - index != 0); // ensure we freed something |
| result |= mask; // ensure we don't clear stuff to the right of the current object |
| e &= result; |
| |
| HeapItem *itemToFree = o + index; |
| Heap::Base *b = *itemToFree; |
| const VTable *v = b->internalClass->vtable; |
| // if (Q_UNLIKELY(classCountPtr)) |
| // classCountPtr(v->className); |
| if (v->destroy) { |
| v->destroy(b); |
| b->_checkIsDestroyed(); |
| } |
| #ifdef V4_USE_HEAPTRACK |
| heaptrack_report_free(itemToFree); |
| #endif |
| } |
| Q_V4_PROFILE_DEALLOC(engine, qPopulationCount((objectBitmap[i] | extendsBitmap[i]) |
| - (blackBitmap[i] | e)) * Chunk::SlotSize, |
| Profiling::SmallItem); |
| objectBitmap[i] = blackBitmap[i]; |
| grayBitmap[i] = 0; |
| hasUsedSlots |= (blackBitmap[i] != 0); |
| extendsBitmap[i] = e; |
| lastSlotFree = !((objectBitmap[i]|extendsBitmap[i]) >> (sizeof(quintptr)*8 - 1)); |
| SDUMP() << " new extends =" << binary(e); |
| SDUMP() << " lastSlotFree" << lastSlotFree; |
| Q_ASSERT((objectBitmap[i] & extendsBitmap[i]) == 0); |
| o += Chunk::Bits; |
| } |
| // DEBUG << "swept chunk" << this << "freed" << slotsFreed << "slots."; |
| return hasUsedSlots; |
| } |
| |
| void Chunk::freeAll(ExecutionEngine *engine) |
| { |
| // DEBUG << "sweeping chunk" << this << (*freeList); |
| HeapItem *o = realBase(); |
| for (uint i = 0; i < Chunk::EntriesInBitmap; ++i) { |
| quintptr toFree = objectBitmap[i]; |
| quintptr e = extendsBitmap[i]; |
| // DEBUG << hex << " index=" << i << toFree; |
| while (toFree) { |
| uint index = qCountTrailingZeroBits(toFree); |
| quintptr bit = (static_cast<quintptr>(1) << index); |
| |
| toFree ^= bit; // mask out freed slot |
| // DEBUG << " index" << hex << index << toFree; |
| |
| // remove all extends slots that have been freed |
| // this is a bit of bit trickery. |
| quintptr mask = (bit << 1) - 1; // create a mask of 1's to the right of and up to the current bit |
| quintptr objmask = e | mask; // or'ing mask with e gives all ones until the end of the current object |
| quintptr result = objmask + 1; |
| Q_ASSERT(qCountTrailingZeroBits(result) - index != 0); // ensure we freed something |
| result |= mask; // ensure we don't clear stuff to the right of the current object |
| e &= result; |
| |
| HeapItem *itemToFree = o + index; |
| Heap::Base *b = *itemToFree; |
| if (b->internalClass->vtable->destroy) { |
| b->internalClass->vtable->destroy(b); |
| b->_checkIsDestroyed(); |
| } |
| #ifdef V4_USE_HEAPTRACK |
| heaptrack_report_free(itemToFree); |
| #endif |
| } |
| Q_V4_PROFILE_DEALLOC(engine, (qPopulationCount(objectBitmap[i]|extendsBitmap[i]) |
| - qPopulationCount(e)) * Chunk::SlotSize, Profiling::SmallItem); |
| objectBitmap[i] = 0; |
| grayBitmap[i] = 0; |
| extendsBitmap[i] = e; |
| o += Chunk::Bits; |
| } |
| // DEBUG << "swept chunk" << this << "freed" << slotsFreed << "slots."; |
| } |
| |
| void Chunk::resetBlackBits() |
| { |
| memset(blackBitmap, 0, sizeof(blackBitmap)); |
| } |
| |
| void Chunk::collectGrayItems(MarkStack *markStack) |
| { |
| // DEBUG << "sweeping chunk" << this << (*freeList); |
| HeapItem *o = realBase(); |
| for (uint i = 0; i < Chunk::EntriesInBitmap; ++i) { |
| #if WRITEBARRIER(none) |
| Q_ASSERT((grayBitmap[i] | blackBitmap[i]) == blackBitmap[i]); // check that we don't have gray only objects |
| #endif |
| quintptr toMark = blackBitmap[i] & grayBitmap[i]; // correct for a Steele type barrier |
| Q_ASSERT((toMark & objectBitmap[i]) == toMark); // check all black objects are marked as being used |
| // DEBUG << hex << " index=" << i << toFree; |
| while (toMark) { |
| uint index = qCountTrailingZeroBits(toMark); |
| quintptr bit = (static_cast<quintptr>(1) << index); |
| |
| toMark ^= bit; // mask out marked slot |
| // DEBUG << " index" << hex << index << toFree; |
| |
| HeapItem *itemToFree = o + index; |
| Heap::Base *b = *itemToFree; |
| Q_ASSERT(b->inUse()); |
| markStack->push(b); |
| } |
| grayBitmap[i] = 0; |
| o += Chunk::Bits; |
| } |
| // DEBUG << "swept chunk" << this << "freed" << slotsFreed << "slots."; |
| |
| } |
| |
| void Chunk::sortIntoBins(HeapItem **bins, uint nBins) |
| { |
| // qDebug() << "sortIntoBins:"; |
| HeapItem *base = realBase(); |
| #if QT_POINTER_SIZE == 8 |
| const int start = 0; |
| #else |
| const int start = 1; |
| #endif |
| #ifndef QT_NO_DEBUG |
| uint freeSlots = 0; |
| uint allocatedSlots = 0; |
| #endif |
| for (int i = start; i < EntriesInBitmap; ++i) { |
| quintptr usedSlots = (objectBitmap[i]|extendsBitmap[i]); |
| #if QT_POINTER_SIZE == 8 |
| if (!i) |
| usedSlots |= (static_cast<quintptr>(1) << (HeaderSize/SlotSize)) - 1; |
| #endif |
| #ifndef QT_NO_DEBUG |
| allocatedSlots += qPopulationCount(usedSlots); |
| // qDebug() << hex << " i=" << i << "used=" << usedSlots; |
| #endif |
| while (1) { |
| uint index = qCountTrailingZeroBits(usedSlots + 1); |
| if (index == Bits) |
| break; |
| uint freeStart = i*Bits + index; |
| usedSlots &= ~((static_cast<quintptr>(1) << index) - 1); |
| while (!usedSlots) { |
| if (++i < EntriesInBitmap) { |
| usedSlots = (objectBitmap[i]|extendsBitmap[i]); |
| } else { |
| Q_ASSERT(i == EntriesInBitmap); |
| // Overflows to 0 when counting trailing zeroes above in next iteration. |
| // Then, all the bits are zeroes and we break. |
| usedSlots = std::numeric_limits<quintptr>::max(); |
| break; |
| } |
| #ifndef QT_NO_DEBUG |
| allocatedSlots += qPopulationCount(usedSlots); |
| // qDebug() << hex << " i=" << i << "used=" << usedSlots; |
| #endif |
| } |
| HeapItem *freeItem = base + freeStart; |
| |
| index = qCountTrailingZeroBits(usedSlots); |
| usedSlots |= (quintptr(1) << index) - 1; |
| uint freeEnd = i*Bits + index; |
| uint nSlots = freeEnd - freeStart; |
| #ifndef QT_NO_DEBUG |
| // qDebug() << hex << " got free slots from" << freeStart << "to" << freeEnd << "n=" << nSlots << "usedSlots=" << usedSlots; |
| freeSlots += nSlots; |
| #endif |
| Q_ASSERT(freeEnd > freeStart && freeEnd <= NumSlots); |
| freeItem->freeData.availableSlots = nSlots; |
| uint bin = qMin(nBins - 1, nSlots); |
| freeItem->freeData.next = bins[bin]; |
| bins[bin] = freeItem; |
| } |
| } |
| #ifndef QT_NO_DEBUG |
| Q_ASSERT(freeSlots + allocatedSlots == (EntriesInBitmap - start) * 8 * sizeof(quintptr)); |
| #endif |
| } |
| |
| HeapItem *BlockAllocator::allocate(size_t size, bool forceAllocation) { |
| Q_ASSERT((size % Chunk::SlotSize) == 0); |
| size_t slotsRequired = size >> Chunk::SlotSizeShift; |
| |
| if (allocationStats) |
| ++allocationStats[binForSlots(slotsRequired)]; |
| |
| HeapItem **last; |
| |
| HeapItem *m; |
| |
| if (slotsRequired < NumBins - 1) { |
| m = freeBins[slotsRequired]; |
| if (m) { |
| freeBins[slotsRequired] = m->freeData.next; |
| goto done; |
| } |
| } |
| |
| if (nFree >= slotsRequired) { |
| // use bump allocation |
| Q_ASSERT(nextFree); |
| m = nextFree; |
| nextFree += slotsRequired; |
| nFree -= slotsRequired; |
| goto done; |
| } |
| |
| // DEBUG << "No matching bin found for item" << size << bin; |
| // search last bin for a large enough item |
| last = &freeBins[NumBins - 1]; |
| while ((m = *last)) { |
| if (m->freeData.availableSlots >= slotsRequired) { |
| *last = m->freeData.next; // take it out of the list |
| |
| size_t remainingSlots = m->freeData.availableSlots - slotsRequired; |
| // DEBUG << "found large free slots of size" << m->freeData.availableSlots << m << "remaining" << remainingSlots; |
| if (remainingSlots == 0) |
| goto done; |
| |
| HeapItem *remainder = m + slotsRequired; |
| if (remainingSlots > nFree) { |
| if (nFree) { |
| size_t bin = binForSlots(nFree); |
| nextFree->freeData.next = freeBins[bin]; |
| nextFree->freeData.availableSlots = nFree; |
| freeBins[bin] = nextFree; |
| } |
| nextFree = remainder; |
| nFree = remainingSlots; |
| } else { |
| remainder->freeData.availableSlots = remainingSlots; |
| size_t binForRemainder = binForSlots(remainingSlots); |
| remainder->freeData.next = freeBins[binForRemainder]; |
| freeBins[binForRemainder] = remainder; |
| } |
| goto done; |
| } |
| last = &m->freeData.next; |
| } |
| |
| if (slotsRequired < NumBins - 1) { |
| // check if we can split up another slot |
| for (size_t i = slotsRequired + 1; i < NumBins - 1; ++i) { |
| m = freeBins[i]; |
| if (m) { |
| freeBins[i] = m->freeData.next; // take it out of the list |
| // qDebug() << "got item" << slotsRequired << "from slot" << i; |
| size_t remainingSlots = i - slotsRequired; |
| Q_ASSERT(remainingSlots < NumBins - 1); |
| HeapItem *remainder = m + slotsRequired; |
| remainder->freeData.availableSlots = remainingSlots; |
| remainder->freeData.next = freeBins[remainingSlots]; |
| freeBins[remainingSlots] = remainder; |
| goto done; |
| } |
| } |
| } |
| |
| if (!m) { |
| if (!forceAllocation) |
| return nullptr; |
| Chunk *newChunk = chunkAllocator->allocate(); |
| Q_V4_PROFILE_ALLOC(engine, Chunk::DataSize, Profiling::HeapPage); |
| chunks.push_back(newChunk); |
| nextFree = newChunk->first(); |
| nFree = Chunk::AvailableSlots; |
| m = nextFree; |
| nextFree += slotsRequired; |
| nFree -= slotsRequired; |
| } |
| |
| done: |
| m->setAllocatedSlots(slotsRequired); |
| Q_V4_PROFILE_ALLOC(engine, slotsRequired * Chunk::SlotSize, Profiling::SmallItem); |
| #ifdef V4_USE_HEAPTRACK |
| heaptrack_report_alloc(m, slotsRequired * Chunk::SlotSize); |
| #endif |
| // DEBUG << " " << hex << m->chunk() << m->chunk()->objectBitmap[0] << m->chunk()->extendsBitmap[0] << (m - m->chunk()->realBase()); |
| return m; |
| } |
| |
| void BlockAllocator::sweep() |
| { |
| nextFree = nullptr; |
| nFree = 0; |
| memset(freeBins, 0, sizeof(freeBins)); |
| |
| // qDebug() << "BlockAlloc: sweep"; |
| usedSlotsAfterLastSweep = 0; |
| |
| auto firstEmptyChunk = std::partition(chunks.begin(), chunks.end(), [this](Chunk *c) { |
| return c->sweep(engine); |
| }); |
| |
| std::for_each(chunks.begin(), firstEmptyChunk, [this](Chunk *c) { |
| c->sortIntoBins(freeBins, NumBins); |
| usedSlotsAfterLastSweep += c->nUsedSlots(); |
| }); |
| |
| // only free the chunks at the end to avoid that the sweep() calls indirectly |
| // access freed memory |
| std::for_each(firstEmptyChunk, chunks.end(), [this](Chunk *c) { |
| Q_V4_PROFILE_DEALLOC(engine, Chunk::DataSize, Profiling::HeapPage); |
| chunkAllocator->free(c); |
| }); |
| |
| chunks.erase(firstEmptyChunk, chunks.end()); |
| } |
| |
| void BlockAllocator::freeAll() |
| { |
| for (auto c : chunks) |
| c->freeAll(engine); |
| for (auto c : chunks) { |
| Q_V4_PROFILE_DEALLOC(engine, Chunk::DataSize, Profiling::HeapPage); |
| chunkAllocator->free(c); |
| } |
| } |
| |
| void BlockAllocator::resetBlackBits() |
| { |
| for (auto c : chunks) |
| c->resetBlackBits(); |
| } |
| |
| void BlockAllocator::collectGrayItems(MarkStack *markStack) |
| { |
| for (auto c : chunks) |
| c->collectGrayItems(markStack); |
| |
| } |
| |
| HeapItem *HugeItemAllocator::allocate(size_t size) { |
| MemorySegment *m = nullptr; |
| Chunk *c = nullptr; |
| if (size >= MemorySegment::SegmentSize/2) { |
| // too large to handle through the ChunkAllocator, let's get our own memory segement |
| size += Chunk::HeaderSize; // space required for the Chunk header |
| size_t pageSize = WTF::pageSize(); |
| size = (size + pageSize - 1) & ~(pageSize - 1); // align to page sizes |
| m = new MemorySegment(size); |
| c = m->allocate(size); |
| } else { |
| c = chunkAllocator->allocate(size); |
| } |
| Q_ASSERT(c); |
| chunks.push_back(HugeChunk{m, c, size}); |
| Chunk::setBit(c->objectBitmap, c->first() - c->realBase()); |
| Q_V4_PROFILE_ALLOC(engine, size, Profiling::LargeItem); |
| #ifdef V4_USE_HEAPTRACK |
| heaptrack_report_alloc(c, size); |
| #endif |
| return c->first(); |
| } |
| |
| static void freeHugeChunk(ChunkAllocator *chunkAllocator, const HugeItemAllocator::HugeChunk &c, ClassDestroyStatsCallback classCountPtr) |
| { |
| HeapItem *itemToFree = c.chunk->first(); |
| Heap::Base *b = *itemToFree; |
| const VTable *v = b->internalClass->vtable; |
| if (Q_UNLIKELY(classCountPtr)) |
| classCountPtr(v->className); |
| |
| if (v->destroy) { |
| v->destroy(b); |
| b->_checkIsDestroyed(); |
| } |
| if (c.segment) { |
| // own memory segment |
| c.segment->free(c.chunk, c.size); |
| delete c.segment; |
| } else { |
| chunkAllocator->free(c.chunk, c.size); |
| } |
| #ifdef V4_USE_HEAPTRACK |
| heaptrack_report_free(c.chunk); |
| #endif |
| } |
| |
| void HugeItemAllocator::sweep(ClassDestroyStatsCallback classCountPtr) |
| { |
| auto isBlack = [this, classCountPtr] (const HugeChunk &c) { |
| bool b = c.chunk->first()->isBlack(); |
| Chunk::clearBit(c.chunk->blackBitmap, c.chunk->first() - c.chunk->realBase()); |
| if (!b) { |
| Q_V4_PROFILE_DEALLOC(engine, c.size, Profiling::LargeItem); |
| freeHugeChunk(chunkAllocator, c, classCountPtr); |
| } |
| return !b; |
| }; |
| |
| auto newEnd = std::remove_if(chunks.begin(), chunks.end(), isBlack); |
| chunks.erase(newEnd, chunks.end()); |
| } |
| |
| void HugeItemAllocator::resetBlackBits() |
| { |
| for (auto c : chunks) |
| Chunk::clearBit(c.chunk->blackBitmap, c.chunk->first() - c.chunk->realBase()); |
| } |
| |
| void HugeItemAllocator::collectGrayItems(MarkStack *markStack) |
| { |
| for (auto c : chunks) |
| // Correct for a Steele type barrier |
| if (Chunk::testBit(c.chunk->blackBitmap, c.chunk->first() - c.chunk->realBase()) && |
| Chunk::testBit(c.chunk->grayBitmap, c.chunk->first() - c.chunk->realBase())) { |
| HeapItem *i = c.chunk->first(); |
| Heap::Base *b = *i; |
| b->mark(markStack); |
| } |
| } |
| |
| void HugeItemAllocator::freeAll() |
| { |
| for (auto &c : chunks) { |
| Q_V4_PROFILE_DEALLOC(engine, c.size, Profiling::LargeItem); |
| freeHugeChunk(chunkAllocator, c, nullptr); |
| } |
| } |
| |
| |
| MemoryManager::MemoryManager(ExecutionEngine *engine) |
| : engine(engine) |
| , chunkAllocator(new ChunkAllocator) |
| , blockAllocator(chunkAllocator, engine) |
| , icAllocator(chunkAllocator, engine) |
| , hugeItemAllocator(chunkAllocator, engine) |
| , m_persistentValues(new PersistentValueStorage(engine)) |
| , m_weakValues(new PersistentValueStorage(engine)) |
| , unmanagedHeapSizeGCLimit(MinUnmanagedHeapSizeGCLimit) |
| , aggressiveGC(!qEnvironmentVariableIsEmpty("QV4_MM_AGGRESSIVE_GC")) |
| , gcStats(lcGcStats().isDebugEnabled()) |
| , gcCollectorStats(lcGcAllocatorStats().isDebugEnabled()) |
| { |
| #ifdef V4_USE_VALGRIND |
| VALGRIND_CREATE_MEMPOOL(this, 0, true); |
| #endif |
| memset(statistics.allocations, 0, sizeof(statistics.allocations)); |
| if (gcStats) |
| blockAllocator.allocationStats = statistics.allocations; |
| } |
| |
| Heap::Base *MemoryManager::allocString(std::size_t unmanagedSize) |
| { |
| const size_t stringSize = align(sizeof(Heap::String)); |
| #ifdef MM_STATS |
| lastAllocRequestedSlots = stringSize >> Chunk::SlotSizeShift; |
| ++allocationCount; |
| #endif |
| unmanagedHeapSize += unmanagedSize; |
| |
| HeapItem *m = allocate(&blockAllocator, stringSize); |
| memset(m, 0, stringSize); |
| return *m; |
| } |
| |
| Heap::Base *MemoryManager::allocData(std::size_t size) |
| { |
| #ifdef MM_STATS |
| lastAllocRequestedSlots = size >> Chunk::SlotSizeShift; |
| ++allocationCount; |
| #endif |
| |
| Q_ASSERT(size >= Chunk::SlotSize); |
| Q_ASSERT(size % Chunk::SlotSize == 0); |
| |
| HeapItem *m = allocate(&blockAllocator, size); |
| memset(m, 0, size); |
| return *m; |
| } |
| |
| Heap::Object *MemoryManager::allocObjectWithMemberData(const QV4::VTable *vtable, uint nMembers) |
| { |
| uint size = (vtable->nInlineProperties + vtable->inlinePropertyOffset)*sizeof(Value); |
| Q_ASSERT(!(size % sizeof(HeapItem))); |
| |
| Heap::Object *o; |
| if (nMembers <= vtable->nInlineProperties) { |
| o = static_cast<Heap::Object *>(allocData(size)); |
| } else { |
| // Allocate both in one go through the block allocator |
| nMembers -= vtable->nInlineProperties; |
| std::size_t memberSize = align(sizeof(Heap::MemberData) + (nMembers - 1)*sizeof(Value)); |
| size_t totalSize = size + memberSize; |
| Heap::MemberData *m; |
| if (totalSize > Chunk::DataSize) { |
| o = static_cast<Heap::Object *>(allocData(size)); |
| m = hugeItemAllocator.allocate(memberSize)->as<Heap::MemberData>(); |
| } else { |
| HeapItem *mh = reinterpret_cast<HeapItem *>(allocData(totalSize)); |
| Heap::Base *b = *mh; |
| o = static_cast<Heap::Object *>(b); |
| mh += (size >> Chunk::SlotSizeShift); |
| m = mh->as<Heap::MemberData>(); |
| Chunk *c = mh->chunk(); |
| size_t index = mh - c->realBase(); |
| Chunk::setBit(c->objectBitmap, index); |
| Chunk::clearBit(c->extendsBitmap, index); |
| } |
| o->memberData.set(engine, m); |
| m->internalClass.set(engine, engine->internalClasses(EngineBase::Class_MemberData)); |
| Q_ASSERT(o->memberData->internalClass); |
| m->values.alloc = static_cast<uint>((memberSize - sizeof(Heap::MemberData) + sizeof(Value))/sizeof(Value)); |
| m->values.size = o->memberData->values.alloc; |
| m->init(); |
| // qDebug() << " got" << o->memberData << o->memberData->size; |
| } |
| // qDebug() << "allocating object with memberData" << o << o->memberData.operator->(); |
| return o; |
| } |
| |
| static uint markStackSize = 0; |
| |
| MarkStack::MarkStack(ExecutionEngine *engine) |
| : m_engine(engine) |
| { |
| m_base = (Heap::Base **)engine->gcStack->base(); |
| m_top = m_base; |
| const size_t size = engine->maxGCStackSize() / sizeof(Heap::Base); |
| m_hardLimit = m_base + size; |
| m_softLimit = m_base + size * 3 / 4; |
| } |
| |
| void MarkStack::drain() |
| { |
| while (m_top > m_base) { |
| Heap::Base *h = pop(); |
| ++markStackSize; |
| Q_ASSERT(h); // at this point we should only have Heap::Base objects in this area on the stack. If not, weird things might happen. |
| h->internalClass->vtable->markObjects(h, this); |
| } |
| } |
| |
| void MemoryManager::collectRoots(MarkStack *markStack) |
| { |
| engine->markObjects(markStack); |
| |
| // qDebug() << " mark stack after engine->mark" << (engine->jsStackTop - markBase); |
| |
| collectFromJSStack(markStack); |
| |
| // qDebug() << " mark stack after js stack collect" << (engine->jsStackTop - markBase); |
| m_persistentValues->mark(markStack); |
| |
| // qDebug() << " mark stack after persistants" << (engine->jsStackTop - markBase); |
| |
| // Preserve QObject ownership rules within JavaScript: A parent with c++ ownership |
| // keeps all of its children alive in JavaScript. |
| |
| // Do this _after_ collectFromStack to ensure that processing the weak |
| // managed objects in the loop down there doesn't make then end up as leftovers |
| // on the stack and thus always get collected. |
| for (PersistentValueStorage::Iterator it = m_weakValues->begin(); it != m_weakValues->end(); ++it) { |
| QObjectWrapper *qobjectWrapper = (*it).as<QObjectWrapper>(); |
| if (!qobjectWrapper) |
| continue; |
| QObject *qobject = qobjectWrapper->object(); |
| if (!qobject) |
| continue; |
| bool keepAlive = QQmlData::keepAliveDuringGarbageCollection(qobject); |
| |
| if (!keepAlive) { |
| if (QObject *parent = qobject->parent()) { |
| while (parent->parent()) |
| parent = parent->parent(); |
| |
| keepAlive = QQmlData::keepAliveDuringGarbageCollection(parent); |
| } |
| } |
| |
| if (keepAlive) |
| qobjectWrapper->mark(markStack); |
| } |
| } |
| |
| void MemoryManager::mark() |
| { |
| markStackSize = 0; |
| MarkStack markStack(engine); |
| collectRoots(&markStack); |
| // dtor of MarkStack drains |
| } |
| |
| void MemoryManager::sweep(bool lastSweep, ClassDestroyStatsCallback classCountPtr) |
| { |
| for (PersistentValueStorage::Iterator it = m_weakValues->begin(); it != m_weakValues->end(); ++it) { |
| Managed *m = (*it).managed(); |
| if (!m || m->markBit()) |
| continue; |
| // we need to call destroyObject on qobjectwrappers now, so that they can emit the destroyed |
| // signal before we start sweeping the heap |
| if (QObjectWrapper *qobjectWrapper = (*it).as<QObjectWrapper>()) |
| qobjectWrapper->destroyObject(lastSweep); |
| } |
| |
| // remove objects from weak maps and sets |
| Heap::MapObject *map = weakMaps; |
| Heap::MapObject **lastMap = &weakMaps; |
| while (map) { |
| if (map->isMarked()) { |
| map->removeUnmarkedKeys(); |
| *lastMap = map; |
| lastMap = &map->nextWeakMap; |
| } |
| map = map->nextWeakMap; |
| } |
| |
| Heap::SetObject *set = weakSets; |
| Heap::SetObject **lastSet = &weakSets; |
| while (set) { |
| if (set->isMarked()) { |
| set->removeUnmarkedKeys(); |
| *lastSet = set; |
| lastSet = &set->nextWeakSet; |
| } |
| set = set->nextWeakSet; |
| } |
| |
| // onDestruction handlers may have accessed other QObject wrappers and reset their value, so ensure |
| // that they are all set to undefined. |
| for (PersistentValueStorage::Iterator it = m_weakValues->begin(); it != m_weakValues->end(); ++it) { |
| Managed *m = (*it).managed(); |
| if (!m || m->markBit()) |
| continue; |
| (*it) = Value::undefinedValue(); |
| } |
| |
| // Now it is time to free QV4::QObjectWrapper Value, we must check the Value's tag to make sure its object has been destroyed |
| const int pendingCount = m_pendingFreedObjectWrapperValue.count(); |
| if (pendingCount) { |
| QVector<Value *> remainingWeakQObjectWrappers; |
| remainingWeakQObjectWrappers.reserve(pendingCount); |
| for (int i = 0; i < pendingCount; ++i) { |
| Value *v = m_pendingFreedObjectWrapperValue.at(i); |
| if (v->isUndefined() || v->isEmpty()) |
| PersistentValueStorage::free(v); |
| else |
| remainingWeakQObjectWrappers.append(v); |
| } |
| m_pendingFreedObjectWrapperValue = remainingWeakQObjectWrappers; |
| } |
| |
| if (MultiplyWrappedQObjectMap *multiplyWrappedQObjects = engine->m_multiplyWrappedQObjects) { |
| for (MultiplyWrappedQObjectMap::Iterator it = multiplyWrappedQObjects->begin(); it != multiplyWrappedQObjects->end();) { |
| if (!it.value().isNullOrUndefined()) |
| it = multiplyWrappedQObjects->erase(it); |
| else |
| ++it; |
| } |
| } |
| |
| |
| if (!lastSweep) { |
| engine->identifierTable->sweep(); |
| blockAllocator.sweep(/*classCountPtr*/); |
| hugeItemAllocator.sweep(classCountPtr); |
| icAllocator.sweep(/*classCountPtr*/); |
| } |
| } |
| |
| bool MemoryManager::shouldRunGC() const |
| { |
| size_t total = blockAllocator.totalSlots() + icAllocator.totalSlots(); |
| if (total > MinSlotsGCLimit && usedSlotsAfterLastFullSweep * GCOverallocation < total * 100) |
| return true; |
| return false; |
| } |
| |
| static size_t dumpBins(BlockAllocator *b, const char *title) |
| { |
| const QLoggingCategory &stats = lcGcAllocatorStats(); |
| size_t totalSlotMem = 0; |
| if (title) |
| qDebug(stats) << "Slot map for" << title << "allocator:"; |
| for (uint i = 0; i < BlockAllocator::NumBins; ++i) { |
| uint nEntries = 0; |
| HeapItem *h = b->freeBins[i]; |
| while (h) { |
| ++nEntries; |
| totalSlotMem += h->freeData.availableSlots; |
| h = h->freeData.next; |
| } |
| if (title) |
| qDebug(stats) << " number of entries in slot" << i << ":" << nEntries; |
| } |
| SDUMP() << " large slot map"; |
| HeapItem *h = b->freeBins[BlockAllocator::NumBins - 1]; |
| while (h) { |
| SDUMP() << " " << Qt::hex << (quintptr(h)/32) << h->freeData.availableSlots; |
| h = h->freeData.next; |
| } |
| |
| if (title) |
| qDebug(stats) << " total mem in bins" << totalSlotMem*Chunk::SlotSize; |
| return totalSlotMem*Chunk::SlotSize; |
| } |
| |
| void MemoryManager::runGC() |
| { |
| if (gcBlocked) { |
| // qDebug() << "Not running GC."; |
| return; |
| } |
| |
| QScopedValueRollback<bool> gcBlocker(gcBlocked, true); |
| // qDebug() << "runGC"; |
| |
| if (gcStats) { |
| statistics.maxReservedMem = qMax(statistics.maxReservedMem, getAllocatedMem()); |
| statistics.maxAllocatedMem = qMax(statistics.maxAllocatedMem, getUsedMem() + getLargeItemsMem()); |
| } |
| |
| if (!gcCollectorStats) { |
| mark(); |
| sweep(); |
| } else { |
| bool triggeredByUnmanagedHeap = (unmanagedHeapSize > unmanagedHeapSizeGCLimit); |
| size_t oldUnmanagedSize = unmanagedHeapSize; |
| |
| const size_t totalMem = getAllocatedMem(); |
| const size_t usedBefore = getUsedMem(); |
| const size_t largeItemsBefore = getLargeItemsMem(); |
| |
| const QLoggingCategory &stats = lcGcAllocatorStats(); |
| qDebug(stats) << "========== GC =========="; |
| #ifdef MM_STATS |
| qDebug(stats) << " Triggered by alloc request of" << lastAllocRequestedSlots << "slots."; |
| qDebug(stats) << " Allocations since last GC" << allocationCount; |
| allocationCount = 0; |
| #endif |
| size_t oldChunks = blockAllocator.chunks.size(); |
| qDebug(stats) << "Allocated" << totalMem << "bytes in" << oldChunks << "chunks"; |
| qDebug(stats) << "Fragmented memory before GC" << (totalMem - usedBefore); |
| dumpBins(&blockAllocator, "Block"); |
| dumpBins(&icAllocator, "InternalClass"); |
| |
| QElapsedTimer t; |
| t.start(); |
| mark(); |
| qint64 markTime = t.nsecsElapsed()/1000; |
| t.restart(); |
| sweep(false, increaseFreedCountForClass); |
| const size_t usedAfter = getUsedMem(); |
| const size_t largeItemsAfter = getLargeItemsMem(); |
| qint64 sweepTime = t.nsecsElapsed()/1000; |
| |
| if (triggeredByUnmanagedHeap) { |
| qDebug(stats) << "triggered by unmanaged heap:"; |
| qDebug(stats) << " old unmanaged heap size:" << oldUnmanagedSize; |
| qDebug(stats) << " new unmanaged heap:" << unmanagedHeapSize; |
| qDebug(stats) << " unmanaged heap limit:" << unmanagedHeapSizeGCLimit; |
| } |
| size_t memInBins = dumpBins(&blockAllocator, "Block") |
| + dumpBins(&icAllocator, "InternalClasss"); |
| qDebug(stats) << "Marked object in" << markTime << "us."; |
| qDebug(stats) << " " << markStackSize << "objects marked"; |
| qDebug(stats) << "Sweeped object in" << sweepTime << "us."; |
| |
| // sort our object types by number of freed instances |
| MMStatsHash freedObjectStats; |
| std::swap(freedObjectStats, *freedObjectStatsGlobal()); |
| typedef std::pair<const char*, int> ObjectStatInfo; |
| std::vector<ObjectStatInfo> freedObjectsSorted; |
| freedObjectsSorted.reserve(freedObjectStats.count()); |
| for (auto it = freedObjectStats.constBegin(); it != freedObjectStats.constEnd(); ++it) { |
| freedObjectsSorted.push_back(std::make_pair(it.key(), it.value())); |
| } |
| std::sort(freedObjectsSorted.begin(), freedObjectsSorted.end(), [](const ObjectStatInfo &a, const ObjectStatInfo &b) { |
| return a.second > b.second && strcmp(a.first, b.first) < 0; |
| }); |
| |
| qDebug(stats) << "Used memory before GC:" << usedBefore; |
| qDebug(stats) << "Used memory after GC:" << usedAfter; |
| qDebug(stats) << "Freed up bytes :" << (usedBefore - usedAfter); |
| qDebug(stats) << "Freed up chunks :" << (oldChunks - blockAllocator.chunks.size()); |
| size_t lost = blockAllocator.allocatedMem() + icAllocator.allocatedMem() |
| - memInBins - usedAfter; |
| if (lost) |
| qDebug(stats) << "!!!!!!!!!!!!!!!!!!!!! LOST MEM:" << lost << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"; |
| if (largeItemsBefore || largeItemsAfter) { |
| qDebug(stats) << "Large item memory before GC:" << largeItemsBefore; |
| qDebug(stats) << "Large item memory after GC:" << largeItemsAfter; |
| qDebug(stats) << "Large item memory freed up:" << (largeItemsBefore - largeItemsAfter); |
| } |
| |
| for (auto it = freedObjectsSorted.cbegin(); it != freedObjectsSorted.cend(); ++it) { |
| qDebug(stats).noquote() << QString::fromLatin1("Freed JS type: %1 (%2 instances)").arg(QString::fromLatin1(it->first), QString::number(it->second)); |
| } |
| |
| qDebug(stats) << "======== End GC ========"; |
| } |
| |
| if (gcStats) |
| statistics.maxUsedMem = qMax(statistics.maxUsedMem, getUsedMem() + getLargeItemsMem()); |
| |
| if (aggressiveGC) { |
| // ensure we don't 'loose' any memory |
| Q_ASSERT(blockAllocator.allocatedMem() |
| == blockAllocator.usedMem() + dumpBins(&blockAllocator, nullptr)); |
| Q_ASSERT(icAllocator.allocatedMem() |
| == icAllocator.usedMem() + dumpBins(&icAllocator, nullptr)); |
| } |
| |
| usedSlotsAfterLastFullSweep = blockAllocator.usedSlotsAfterLastSweep + icAllocator.usedSlotsAfterLastSweep; |
| |
| // reset all black bits |
| blockAllocator.resetBlackBits(); |
| hugeItemAllocator.resetBlackBits(); |
| icAllocator.resetBlackBits(); |
| } |
| |
| size_t MemoryManager::getUsedMem() const |
| { |
| return blockAllocator.usedMem() + icAllocator.usedMem(); |
| } |
| |
| size_t MemoryManager::getAllocatedMem() const |
| { |
| return blockAllocator.allocatedMem() + icAllocator.allocatedMem() + hugeItemAllocator.usedMem(); |
| } |
| |
| size_t MemoryManager::getLargeItemsMem() const |
| { |
| return hugeItemAllocator.usedMem(); |
| } |
| |
| void MemoryManager::registerWeakMap(Heap::MapObject *map) |
| { |
| map->nextWeakMap = weakMaps; |
| weakMaps = map; |
| } |
| |
| void MemoryManager::registerWeakSet(Heap::SetObject *set) |
| { |
| set->nextWeakSet = weakSets; |
| weakSets = set; |
| } |
| |
| MemoryManager::~MemoryManager() |
| { |
| delete m_persistentValues; |
| |
| dumpStats(); |
| |
| sweep(/*lastSweep*/true); |
| blockAllocator.freeAll(); |
| hugeItemAllocator.freeAll(); |
| icAllocator.freeAll(); |
| |
| delete m_weakValues; |
| #ifdef V4_USE_VALGRIND |
| VALGRIND_DESTROY_MEMPOOL(this); |
| #endif |
| delete chunkAllocator; |
| } |
| |
| |
| void MemoryManager::dumpStats() const |
| { |
| if (!gcStats) |
| return; |
| |
| const QLoggingCategory &stats = lcGcStats(); |
| qDebug(stats) << "Qml GC memory allocation statistics:"; |
| qDebug(stats) << "Total memory allocated:" << statistics.maxReservedMem; |
| qDebug(stats) << "Max memory used before a GC run:" << statistics.maxAllocatedMem; |
| qDebug(stats) << "Max memory used after a GC run:" << statistics.maxUsedMem; |
| qDebug(stats) << "Requests for different item sizes:"; |
| for (int i = 1; i < BlockAllocator::NumBins - 1; ++i) |
| qDebug(stats) << " <" << (i << Chunk::SlotSizeShift) << " bytes: " << statistics.allocations[i]; |
| qDebug(stats) << " >=" << ((BlockAllocator::NumBins - 1) << Chunk::SlotSizeShift) << " bytes: " << statistics.allocations[BlockAllocator::NumBins - 1]; |
| } |
| |
| void MemoryManager::collectFromJSStack(MarkStack *markStack) const |
| { |
| Value *v = engine->jsStackBase; |
| Value *top = engine->jsStackTop; |
| while (v < top) { |
| Managed *m = v->managed(); |
| if (m) { |
| Q_ASSERT(m->inUse()); |
| // Skip pointers to already freed objects, they are bogus as well |
| m->mark(markStack); |
| } |
| ++v; |
| } |
| } |
| |
| } // namespace QV4 |
| |
| QT_END_NAMESPACE |