9#include <QtCore/qalgorithms.h>
10#include <QtCore/private/qnumeric_p.h>
11#include <QtCore/qloggingcategory.h>
12#include <private/qv4alloca_p.h>
14#include "PageReservation.h"
15#include "PageAllocation.h"
17#include <QElapsedTimer>
19#include <QScopedValueRollback>
30#if !defined(MM_STATS) && !defined(QT_NO_DEBUG)
35#define DEBUG qDebug() << "MM:"
37#define DEBUG if (1) ; else qDebug() << "MM:"
41#include <valgrind/valgrind.h>
42#include <valgrind/memcheck.h>
45#ifdef V4_USE_HEAPTRACK
46#include <heaptrack_api.h>
50#include <sys/storage.h>
53#if USE(PTHREADS) && HAVE(PTHREAD_NP_H)
54#include <pthread_np.h>
80 SegmentSize = NumChunks*Chunk::ChunkSize,
85 size += Chunk::ChunkSize;
86 if (
size < SegmentSize)
89 pageReservation = PageReservation::reserve(
size, OSAllocator::JSGCHeapPages);
90 base =
reinterpret_cast<Chunk *
>((
reinterpret_cast<quintptr>(pageReservation.base()) + Chunk::ChunkSize - 1) & ~(Chunk::ChunkSize - 1));
93 if (availableBytes < SegmentSize)
106 pageReservation.deallocate();
119 allocatedMap &= ~bit;
124 return (allocatedMap & bit);
129 DEBUG <<
"freeing chunk" << chunk;
130 size_t index =
static_cast<size_t>(chunk -
base);
131 size_t end =
qMin(
static_cast<size_t>(NumChunks),
index + (
size - 1)/Chunk::ChunkSize + 1);
138 size_t pageSize = WTF::pageSize();
139 size = (
size + pageSize - 1) & ~(pageSize - 1);
140#if !defined(Q_OS_LINUX) && !defined(Q_OS_WIN)
145 memset(chunk, 0,
size);
147 pageReservation.decommit(chunk,
size);
157 size_t availableBytes = 0;
163 if (!allocatedMap &&
size >= SegmentSize) {
167 allocatedMap = ~static_cast<quint64>(0);
172 Chunk *candidate =
nullptr;
173 for (
uint i = 0;
i < nChunks; ++
i) {
176 candidate =
base +
i;
182 if (sequence == requiredChunks) {
183 pageReservation.commit(candidate,
size);
184 for (
uint i = 0;
i < requiredChunks; ++
i)
198 size += Chunk::HeaderSize;
199 size_t pageSize = WTF::pageSize();
200 size = (
size + pageSize - 1) & ~(pageSize - 1);
201 if (
size < Chunk::ChunkSize)
202 size = Chunk::ChunkSize;
207 void free(
Chunk *chunk,
size_t size = 0);
215 for (
auto &
m : memorySegments) {
216 if (~
m.allocatedMap) {
225 Chunk *
c = memorySegments.back().allocate(
size);
233 for (
auto &
m : memorySegments) {
234 if (
m.contains(chunk)) {
245 while (
s.length() < 64)
252#define SDUMP if (1) ; else qDebug
261static
void increaseFreedCountForClass(const
char *
className)
263 (*freedObjectStatsGlobal())[
className]++;
269 bool hasUsedSlots =
false;
270 SDUMP() <<
"sweeping chunk" <<
this;
272 bool lastSlotFree =
false;
273 for (
uint i = 0;
i < Chunk::EntriesInBitmap; ++
i) {
274 quintptr toFree = objectBitmap[
i] ^ blackBitmap[
i];
275 Q_ASSERT((toFree & objectBitmap[
i]) == toFree);
277 SDUMP() <<
" index=" <<
i;
302 const VTable *
v =
b->internalClass->vtable;
307 b->_checkIsDestroyed();
309#ifdef V4_USE_HEAPTRACK
310 heaptrack_report_free(itemToFree);
314 - (blackBitmap[
i] |
e)) * Chunk::SlotSize,
315 Profiling::SmallItem);
316 objectBitmap[
i] = blackBitmap[
i];
317 hasUsedSlots |= (blackBitmap[
i] != 0);
318 extendsBitmap[
i] =
e;
319 lastSlotFree = !((objectBitmap[
i]|extendsBitmap[
i]) >> (
sizeof(
quintptr)*8 - 1));
321 SDUMP() <<
" lastSlotFree" << lastSlotFree;
322 Q_ASSERT((objectBitmap[
i] & extendsBitmap[
i]) == 0);
333 for (
uint i = 0;
i < Chunk::EntriesInBitmap; ++
i) {
355 if (
b->internalClass->vtable->destroy) {
356 b->internalClass->vtable->destroy(
b);
357 b->_checkIsDestroyed();
359#ifdef V4_USE_HEAPTRACK
360 heaptrack_report_free(itemToFree);
366 extendsBitmap[
i] =
e;
372void Chunk::resetBlackBits()
374 memset(blackBitmap, 0,
sizeof(blackBitmap));
381#if QT_POINTER_SIZE == 8
388 uint allocatedSlots = 0;
390 for (
int i =
start;
i < EntriesInBitmap; ++
i) {
391 quintptr usedSlots = (objectBitmap[
i]|extendsBitmap[
i]);
392#if QT_POINTER_SIZE == 8
407 if (++
i < EntriesInBitmap) {
408 usedSlots = (objectBitmap[
i]|extendsBitmap[
i]);
413 usedSlots = std::numeric_limits<quintptr>::max();
426 uint nSlots = freeEnd - freeStart;
431 Q_ASSERT(freeEnd > freeStart && freeEnd <= NumSlots);
435 bins[bin] = freeItem;
445 size_t slotsRequired =
size >> Chunk::SlotSizeShift;
448 ++allocationStats[binForSlots(slotsRequired)];
454 if (slotsRequired < NumBins - 1) {
455 m = freeBins[slotsRequired];
457 freeBins[slotsRequired] =
m->freeData.next;
462 if (nFree >= slotsRequired) {
466 nextFree += slotsRequired;
467 nFree -= slotsRequired;
473 last = &freeBins[NumBins - 1];
474 while ((
m = *last)) {
475 if (
m->freeData.availableSlots >= slotsRequired) {
480 if (remainingSlots == 0)
484 if (remainingSlots > nFree) {
486 size_t bin = binForSlots(nFree);
487 nextFree->freeData.next = freeBins[bin];
488 nextFree->freeData.availableSlots = nFree;
489 freeBins[bin] = nextFree;
491 nextFree = remainder;
492 nFree = remainingSlots;
495 size_t binForRemainder = binForSlots(remainingSlots);
497 freeBins[binForRemainder] = remainder;
504 if (slotsRequired < NumBins - 1) {
506 for (
size_t i = slotsRequired + 1;
i < NumBins - 1; ++
i) {
511 size_t remainingSlots =
i - slotsRequired;
512 Q_ASSERT(remainingSlots < NumBins - 1);
516 freeBins[remainingSlots] = remainder;
523 if (!forceAllocation)
528 size_t bin = binForSlots(nFree);
529 nextFree->freeData.next = freeBins[bin];
530 nextFree->freeData.availableSlots = nFree;
531 freeBins[bin] = nextFree;
533 Chunk *newChunk = chunkAllocator->allocate();
535 chunks.push_back(newChunk);
536 nextFree = newChunk->
first();
537 nFree = Chunk::AvailableSlots;
539 nextFree += slotsRequired;
540 nFree -= slotsRequired;
544 m->setAllocatedSlots(slotsRequired);
546#ifdef V4_USE_HEAPTRACK
547 heaptrack_report_alloc(
m, slotsRequired * Chunk::SlotSize);
553void BlockAllocator::sweep()
557 memset(freeBins, 0,
sizeof(freeBins));
560 usedSlotsAfterLastSweep = 0;
562 auto firstEmptyChunk = std::partition(chunks.begin(), chunks.end(), [
this](
Chunk *
c) {
563 return c->sweep(engine);
566 std::for_each(chunks.begin(), firstEmptyChunk, [
this](
Chunk *
c) {
567 c->sortIntoBins(freeBins, NumBins);
568 usedSlotsAfterLastSweep += c->nUsedSlots();
573 std::for_each(firstEmptyChunk, chunks.end(), [
this](
Chunk *
c) {
574 Q_V4_PROFILE_DEALLOC(engine, Chunk::DataSize, Profiling::HeapPage);
575 chunkAllocator->free(c);
578 chunks.erase(firstEmptyChunk, chunks.end());
581void BlockAllocator::freeAll()
583 for (
auto c : chunks)
585 for (
auto c : chunks) {
587 chunkAllocator->free(
c);
591void BlockAllocator::resetBlackBits()
593 for (
auto c : chunks)
600 if (
size >= MemorySegment::SegmentSize/2) {
602 size += Chunk::HeaderSize;
603 size_t pageSize = WTF::pageSize();
604 size = (
size + pageSize - 1) & ~(pageSize - 1);
608 c = chunkAllocator->allocate(
size);
612 Chunk::setBit(
c->objectBitmap,
c->first() -
c->realBase());
614#ifdef V4_USE_HEAPTRACK
615 heaptrack_report_alloc(
c,
size);
624 const VTable *
v =
b->internalClass->vtable;
626 classCountPtr(
v->className);
630 b->_checkIsDestroyed();
634 c.segment->free(
c.chunk,
c.size);
637 chunkAllocator->
free(
c.chunk,
c.size);
639#ifdef V4_USE_HEAPTRACK
640 heaptrack_report_free(
c.chunk);
644void HugeItemAllocator::sweep(ClassDestroyStatsCallback classCountPtr)
646 auto isBlack = [
this, classCountPtr] (
const HugeChunk &
c) {
647 bool b =
c.chunk->first()->isBlack();
648 Chunk::clearBit(
c.chunk->blackBitmap,
c.chunk->first() -
c.chunk->realBase());
656 auto newEnd = std::remove_if(chunks.begin(), chunks.end(), isBlack);
657 chunks.erase(newEnd, chunks.end());
660void HugeItemAllocator::resetBlackBits()
662 for (
auto c : chunks)
663 Chunk::clearBit(
c.chunk->blackBitmap,
c.chunk->first() -
c.chunk->realBase());
666void HugeItemAllocator::freeAll()
668 for (
auto &
c : chunks) {
678 , blockAllocator(chunkAllocator,
engine)
679 , icAllocator(chunkAllocator,
engine)
680 , hugeItemAllocator(chunkAllocator,
engine)
683 , unmanagedHeapSizeGCLimit(MinUnmanagedHeapSizeGCLimit)
685 , gcStats(lcGcStats().isDebugEnabled())
686 , gcCollectorStats(lcGcAllocatorStats().isDebugEnabled())
688#ifdef V4_USE_VALGRIND
689 VALGRIND_CREATE_MEMPOOL(
this, 0,
true);
706 memset(
m, 0, stringSize);
745 if (nMembers <= vtable->nInlineProperties) {
750 std::size_t memberSize =
align(
sizeof(Heap::MemberData) + (nMembers - 1)*
sizeof(
Value));
751 size_t totalSize =
size + memberSize;
759 o =
static_cast<Heap::Object *
>(
b);
761 m = mh->
as<Heap::MemberData>();
763 size_t index = mh -
c->realBase();
770 m->values.alloc =
static_cast<uint>((memberSize -
sizeof(Heap::MemberData) +
sizeof(
Value))/
sizeof(
Value));
771 m->values.size =
o->memberData->values.alloc;
787 m_hardLimit = m_base +
size;
788 m_softLimit = m_base +
size * 3 / 4;
791void MarkStack::drain()
793 while (m_top > m_base) {
797 h->internalClass->vtable->markObjects(
h,
this);
801void MemoryManager::collectRoots(MarkStack *markStack)
807 collectFromJSStack(markStack);
821 QObjectWrapper *qobjectWrapper = (*it).as<QObjectWrapper>();
824 QObject *qobject = qobjectWrapper->object();
839 qobjectWrapper->mark(markStack);
843void MemoryManager::mark()
846 MarkStack markStack(
engine);
847 collectRoots(&markStack);
854 Managed *
m = (*it).managed();
855 if (!
m ||
m->markBit())
859 if (QObjectWrapper *qobjectWrapper = (*it).as<QObjectWrapper>())
860 qobjectWrapper->destroyObject(lastSweep);
865 Heap::MapObject **lastMap = &
weakMaps;
867 if (
map->isMarked()) {
868 map->removeUnmarkedKeys();
870 lastMap = &
map->nextWeakMap;
876 Heap::SetObject **lastSet = &
weakSets;
878 if (
set->isMarked()) {
879 set->removeUnmarkedKeys();
881 lastSet = &
set->nextWeakSet;
889 Managed *
m = (*it).managed();
890 if (!
m ||
m->markBit())
899 remainingWeakQObjectWrappers.
reserve(pendingCount);
900 for (
int i = 0;
i < pendingCount; ++
i) {
902 if (
v->isUndefined() ||
v->isEmpty())
905 remainingWeakQObjectWrappers.
append(
v);
912 if (
it.value().isNullOrUndefined())
928bool MemoryManager::shouldRunGC()
const
939 size_t totalSlotMem = 0;
941 qDebug(stats) <<
"Slot map for" <<
title <<
"allocator:";
948 h =
h->freeData.next;
951 qDebug(stats) <<
" number of entries in slot" <<
i <<
":" << nEntries;
953 SDUMP() <<
" large slot map";
957 h =
h->freeData.next;
992 qDebug(stats) <<
"========== GC ==========";
999 qDebug(stats) <<
"Allocated" << totalMem <<
"bytes in" << oldChunks <<
"chunks";
1000 qDebug(stats) <<
"Fragmented memory before GC" << (totalMem - usedBefore);
1007 qint64 markTime =
t.nsecsElapsed()/1000;
1012 qint64 sweepTime =
t.nsecsElapsed()/1000;
1014 if (triggeredByUnmanagedHeap) {
1015 qDebug(stats) <<
"triggered by unmanaged heap:";
1016 qDebug(stats) <<
" old unmanaged heap size:" << oldUnmanagedSize;
1022 qDebug(stats) <<
"Marked object in" << markTime <<
"us.";
1024 qDebug(stats) <<
"Sweeped object in" << sweepTime <<
"us.";
1028 std::swap(freedObjectStats, *freedObjectStatsGlobal());
1029 typedef std::pair<const char*, int> ObjectStatInfo;
1030 std::vector<ObjectStatInfo> freedObjectsSorted;
1031 freedObjectsSorted.reserve(freedObjectStats.
size());
1033 freedObjectsSorted.push_back(std::make_pair(
it.key(),
it.value()));
1035 std::sort(freedObjectsSorted.begin(), freedObjectsSorted.end(), [](
const ObjectStatInfo &
a,
const ObjectStatInfo &
b) {
1036 return a.second > b.second && strcmp(a.first, b.first) < 0;
1039 qDebug(stats) <<
"Used memory before GC:" << usedBefore;
1040 qDebug(stats) <<
"Used memory after GC:" << usedAfter;
1041 qDebug(stats) <<
"Freed up bytes :" << (usedBefore - usedAfter);
1044 - memInBins - usedAfter;
1046 qDebug(stats) <<
"!!!!!!!!!!!!!!!!!!!!! LOST MEM:" << lost <<
"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!";
1047 if (largeItemsBefore || largeItemsAfter) {
1048 qDebug(stats) <<
"Large item memory before GC:" << largeItemsBefore;
1049 qDebug(stats) <<
"Large item memory after GC:" << largeItemsAfter;
1050 qDebug(stats) <<
"Large item memory freed up:" << (largeItemsBefore - largeItemsAfter);
1053 for (
auto it = freedObjectsSorted.
cbegin();
it != freedObjectsSorted.
cend(); ++
it) {
1057 qDebug(stats) <<
"======== End GC ========";
1118#ifdef V4_USE_VALGRIND
1119 VALGRIND_DESTROY_MEMPOOL(
this);
1131 qDebug(stats) <<
"Qml GC memory allocation statistics:";
1133 qDebug(stats) <<
"Max memory used before a GC run:" <<
statistics.maxAllocatedMem;
1134 qDebug(stats) <<
"Max memory used after a GC run:" <<
statistics.maxUsedMem;
1135 qDebug(stats) <<
"Requests for different item sizes:";
1141void MemoryManager::collectFromJSStack(
MarkStack *markStack)
const
static constexpr QChar fromLatin1(char c) noexcept
Converts the Latin-1 character c to its equivalent QChar.
qsizetype size() const noexcept
Returns the number of items in the hash.
const_iterator constEnd() const noexcept
Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the imaginary item after the ...
const_iterator constBegin() const noexcept
Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the first item in the hash.
void reserve(qsizetype size)
void append(parameter_type t)
QObject * parent() const
Returns a pointer to the parent object.
static bool keepAliveDuringGarbageCollection(const QObject *object)
const_iterator cend() const noexcept
iterator erase(const_iterator i)
const_iterator cbegin() const noexcept
\macro QT_RESTRICTED_CAST_FROM_ASCII
static QString fromLatin1(QByteArrayView ba)
This is an overloaded member function, provided for convenience. It differs from the above function o...
QString arg(qlonglong a, int fieldwidth=0, int base=10, QChar fillChar=u' ') const
static QString number(int, int base=10)
This is an overloaded member function, provided for convenience. It differs from the above function o...
struct QV4::MemoryManager::@638 statistics
Heap::Base * allocString(std::size_t unmanagedSize)
expects size to be aligned
QVector< Value * > m_pendingFreedObjectWrapperValue
Heap::SetObject * weakSets
QV4::ExecutionEngine * engine
Heap::Object * allocObjectWithMemberData(const QV4::VTable *vtable, uint nMembers)
size_t getLargeItemsMem() const
size_t lastAllocRequestedSlots
ObjectType::Data * allocate(Args &&... args)
ChunkAllocator * chunkAllocator
PersistentValueStorage * m_persistentValues
PersistentValueStorage * m_weakValues
std::size_t unmanagedHeapSize
void registerWeakMap(Heap::MapObject *map)
std::size_t usedSlotsAfterLastFullSweep
BlockAllocator blockAllocator
HugeItemAllocator hugeItemAllocator
size_t getUsedMem() const
Heap::Base * allocData(std::size_t size)
size_t getAllocatedMem() const
void registerWeakSet(Heap::SetObject *set)
Heap::MapObject * weakMaps
std::size_t unmanagedHeapSizeGCLimit
static constexpr std::size_t align(std::size_t size)
BlockAllocator icAllocator
QHash< QObjectBiPointer, QV4::WeakValue >::Iterator Iterator
QMap< QString, QString > map
[6]
QSet< QString >::iterator it
Combined button and popup list for selecting options.
\qmltype Particle \inqmlmodule QtQuick.Particles
void(* ClassDestroyStatsCallback)(const char *)
static void increaseFreedCountForClass(const char *className)
static size_t dumpBins(BlockAllocator *b, const char *title)
static uint markStackSize
static void freeHugeChunk(ChunkAllocator *chunkAllocator, const HugeItemAllocator::HugeChunk &c, ClassDestroyStatsCallback classCountPtr)
QTextStream & hex(QTextStream &stream)
Calls QTextStream::setIntegerBase(16) on stream and returns stream.
constexpr uint qCountTrailingZeroBits(quint32 v) noexcept
Q_DECL_CONST_FUNCTION QT_POPCOUNT_CONSTEXPR uint qPopulationCount(quint32 v) noexcept
static bool testBit(long bit, const long *field)
#define Q_GLOBAL_STATIC(TYPE, NAME,...)
#define Q_LOGGING_CATEGORY(name,...)
#define Q_DECLARE_LOGGING_CATEGORY(name)
constexpr const T & qMin(const T &a, const T &b)
constexpr const T & qMax(const T &a, const T &b)
GLsizei GLsizei GLenum void * binary
GLboolean GLboolean GLboolean b
GLsizei const GLfloat * v
[13]
GLboolean GLboolean GLboolean GLboolean a
[7]
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLdouble GLdouble GLdouble GLdouble top
GLint GLint GLint GLint GLint GLint GLint GLbitfield mask
GLfloat GLfloat GLfloat GLfloat h
static constexpr qint64 HeaderSize
Q_CORE_EXPORT bool qEnvironmentVariableIsEmpty(const char *varName) noexcept
unsigned long long quint64
#define Q_V4_PROFILE_DEALLOC(engine, size, type)
#define Q_V4_PROFILE_ALLOC(engine, size, type)
const char className[16]
[1]
QFuture< QSet< QChar > > set
[10]
size_t usedSlotsAfterLastSweep
std::vector< Chunk * > chunks
size_t totalSlots() const
size_t allocatedMem() const
void free(Chunk *chunk, size_t size=0)
size_t requiredChunkSize(size_t size)
std::vector< MemorySegment > memorySegments
static void setBit(quintptr *bitmap, size_t index)
static void clearBit(quintptr *bitmap, size_t index)
IdentifierTable * identifierTable
Heap::InternalClass * internalClasses(InternalClassType icType)
WTF::PageAllocation * gcStack
void markObjects(MarkStack *markStack)
int maxGCStackSize() const
MultiplyWrappedQObjectMap * m_multiplyWrappedQObjects
struct QV4::HeapItem::@641::@645 freeData
HeapItem * allocate(size_t size)
void sweep(ClassDestroyStatsCallback classCountPtr)
MarkStack(ExecutionEngine *engine)
ExecutionEngine * engine() const
void free(Chunk *chunk, 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)
static void free(Value *v)
void mark(MarkStack *markStack)
quint16 inlinePropertyOffset
quint16 nInlineProperties
QML_NEARLY_ALWAYS_INLINE ManagedPtr managed() const
static constexpr Value undefinedValue()
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent