1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtQml module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39
40 #include "qv4engine_p.h"
41 #include "qv4object_p.h"
42 #include "qv4objectproto_p.h"
43 #include "qv4mm_p.h"
44 #include "qv4qobjectwrapper_p.h"
45 #include "qv4identifiertable_p.h"
46 #include <QtCore/qalgorithms.h>
47 #include <QtCore/private/qnumeric_p.h>
48 #include <QtCore/qloggingcategory.h>
49 #include <private/qv4alloca_p.h>
50 #include <qqmlengine.h>
51 #include "PageReservation.h"
52 #include "PageAllocation.h"
53 #include "PageAllocationAligned.h"
54 #include "StdLibExtras.h"
55
56 #include <QElapsedTimer>
57 #include <QMap>
58 #include <QScopedValueRollback>
59
60 #include <iostream>
61 #include <cstdlib>
62 #include <algorithm>
63 #include "qv4profiling_p.h"
64 #include "qv4mapobject_p.h"
65 #include "qv4setobject_p.h"
66 #include "qv4writebarrier_p.h"
67
68 //#define MM_STATS
69
70 #if !defined(MM_STATS) && !defined(QT_NO_DEBUG)
71 #define MM_STATS
72 #endif
73
74 #if MM_DEBUG
75 #define DEBUG qDebug() << "MM:"
76 #else
77 #define DEBUG if (1) ; else qDebug() << "MM:"
78 #endif
79
80 #ifdef V4_USE_VALGRIND
81 #include <valgrind/valgrind.h>
82 #include <valgrind/memcheck.h>
83 #endif
84
85 #ifdef V4_USE_HEAPTRACK
86 #include <heaptrack_api.h>
87 #endif
88
89 #if OS(QNX)
90 #include <sys/storage.h> // __tls()
91 #endif
92
93 #if USE(PTHREADS) && HAVE(PTHREAD_NP_H)
94 #include <pthread_np.h>
95 #endif
96
97 Q_LOGGING_CATEGORY(lcGcStats, "qt.qml.gc.statistics")
98 Q_DECLARE_LOGGING_CATEGORY(lcGcStats)
99 Q_LOGGING_CATEGORY(lcGcAllocatorStats, "qt.qml.gc.allocatorStats")
100 Q_DECLARE_LOGGING_CATEGORY(lcGcAllocatorStats)
101
102 using namespace WTF;
103
104 QT_BEGIN_NAMESPACE
105
106 namespace QV4 {
107
108 enum {
109 MinSlotsGCLimit = QV4::Chunk::AvailableSlots*16,
110 GCOverallocation = 200 /* Max overallocation by the GC in % */
111 };
112
113 struct MemorySegment {
114 enum {
115 #ifdef Q_OS_RTEMS
116 NumChunks = sizeof(quint64),
117 #else
118 NumChunks = 8*sizeof(quint64),
119 #endif
120 SegmentSize = NumChunks*Chunk::ChunkSize,
121 };
122
MemorySegmentQV4::MemorySegment123 MemorySegment(size_t size)
124 {
125 size += Chunk::ChunkSize; // make sure we can get enough 64k aligment memory
126 if (size < SegmentSize)
127 size = SegmentSize;
128
129 pageReservation = PageReservation::reserve(size, OSAllocator::JSGCHeapPages);
130 base = reinterpret_cast<Chunk *>((reinterpret_cast<quintptr>(pageReservation.base()) + Chunk::ChunkSize - 1) & ~(Chunk::ChunkSize - 1));
131 nChunks = NumChunks;
132 availableBytes = size - (reinterpret_cast<quintptr>(base) - reinterpret_cast<quintptr>(pageReservation.base()));
133 if (availableBytes < SegmentSize)
134 --nChunks;
135 }
MemorySegmentQV4::MemorySegment136 MemorySegment(MemorySegment &&other) {
137 qSwap(pageReservation, other.pageReservation);
138 qSwap(base, other.base);
139 qSwap(allocatedMap, other.allocatedMap);
140 qSwap(availableBytes, other.availableBytes);
141 qSwap(nChunks, other.nChunks);
142 }
143
~MemorySegmentQV4::MemorySegment144 ~MemorySegment() {
145 if (base)
146 pageReservation.deallocate();
147 }
148
setBitQV4::MemorySegment149 void setBit(size_t index) {
150 Q_ASSERT(index < nChunks);
151 quint64 bit = static_cast<quint64>(1) << index;
152 // qDebug() << " setBit" << hex << index << (index & (Bits - 1)) << bit;
153 allocatedMap |= bit;
154 }
clearBitQV4::MemorySegment155 void clearBit(size_t index) {
156 Q_ASSERT(index < nChunks);
157 quint64 bit = static_cast<quint64>(1) << index;
158 // qDebug() << " setBit" << hex << index << (index & (Bits - 1)) << bit;
159 allocatedMap &= ~bit;
160 }
testBitQV4::MemorySegment161 bool testBit(size_t index) const {
162 Q_ASSERT(index < nChunks);
163 quint64 bit = static_cast<quint64>(1) << index;
164 return (allocatedMap & bit);
165 }
166
167 Chunk *allocate(size_t size);
freeQV4::MemorySegment168 void free(Chunk *chunk, size_t size) {
169 DEBUG << "freeing chunk" << chunk;
170 size_t index = static_cast<size_t>(chunk - base);
171 size_t end = qMin(static_cast<size_t>(NumChunks), index + (size - 1)/Chunk::ChunkSize + 1);
172 while (index < end) {
173 Q_ASSERT(testBit(index));
174 clearBit(index);
175 ++index;
176 }
177
178 size_t pageSize = WTF::pageSize();
179 size = (size + pageSize - 1) & ~(pageSize - 1);
180 #if !defined(Q_OS_LINUX) && !defined(Q_OS_WIN)
181 // Linux and Windows zero out pages that have been decommitted and get committed again.
182 // unfortunately that's not true on other OSes (e.g. BSD based ones), so zero out the
183 // memory before decommit, so that we can be sure that all chunks we allocate will be
184 // zero initialized.
185 memset(chunk, 0, size);
186 #endif
187 pageReservation.decommit(chunk, size);
188 }
189
containsQV4::MemorySegment190 bool contains(Chunk *c) const {
191 return c >= base && c < base + nChunks;
192 }
193
194 PageReservation pageReservation;
195 Chunk *base = nullptr;
196 quint64 allocatedMap = 0;
197 size_t availableBytes = 0;
198 uint nChunks = 0;
199 };
200
allocate(size_t size)201 Chunk *MemorySegment::allocate(size_t size)
202 {
203 if (!allocatedMap && size >= SegmentSize) {
204 // chunk allocated for one huge allocation
205 Q_ASSERT(availableBytes >= size);
206 pageReservation.commit(base, size);
207 allocatedMap = ~static_cast<quint64>(0);
208 return base;
209 }
210 size_t requiredChunks = (size + sizeof(Chunk) - 1)/sizeof(Chunk);
211 uint sequence = 0;
212 Chunk *candidate = nullptr;
213 for (uint i = 0; i < nChunks; ++i) {
214 if (!testBit(i)) {
215 if (!candidate)
216 candidate = base + i;
217 ++sequence;
218 } else {
219 candidate = nullptr;
220 sequence = 0;
221 }
222 if (sequence == requiredChunks) {
223 pageReservation.commit(candidate, size);
224 for (uint i = 0; i < requiredChunks; ++i)
225 setBit(candidate - base + i);
226 DEBUG << "allocated chunk " << candidate << Qt::hex << size;
227
228 return candidate;
229 }
230 }
231 return nullptr;
232 }
233
234 struct ChunkAllocator {
ChunkAllocatorQV4::ChunkAllocator235 ChunkAllocator() {}
236
requiredChunkSizeQV4::ChunkAllocator237 size_t requiredChunkSize(size_t size) {
238 size += Chunk::HeaderSize; // space required for the Chunk header
239 size_t pageSize = WTF::pageSize();
240 size = (size + pageSize - 1) & ~(pageSize - 1); // align to page sizes
241 if (size < Chunk::ChunkSize)
242 size = Chunk::ChunkSize;
243 return size;
244 }
245
246 Chunk *allocate(size_t size = 0);
247 void free(Chunk *chunk, size_t size = 0);
248
249 std::vector<MemorySegment> memorySegments;
250 };
251
allocate(size_t size)252 Chunk *ChunkAllocator::allocate(size_t size)
253 {
254 size = requiredChunkSize(size);
255 for (auto &m : memorySegments) {
256 if (~m.allocatedMap) {
257 Chunk *c = m.allocate(size);
258 if (c)
259 return c;
260 }
261 }
262
263 // allocate a new segment
264 memorySegments.push_back(MemorySegment(size));
265 Chunk *c = memorySegments.back().allocate(size);
266 Q_ASSERT(c);
267 return c;
268 }
269
free(Chunk * chunk,size_t size)270 void ChunkAllocator::free(Chunk *chunk, size_t size)
271 {
272 size = requiredChunkSize(size);
273 for (auto &m : memorySegments) {
274 if (m.contains(chunk)) {
275 m.free(chunk, size);
276 return;
277 }
278 }
279 Q_ASSERT(false);
280 }
281
282 #ifdef DUMP_SWEEP
binary(quintptr n)283 QString binary(quintptr n) {
284 QString s = QString::number(n, 2);
285 while (s.length() < 64)
286 s.prepend(QChar::fromLatin1('0'));
287 return s;
288 }
289 #define SDUMP qDebug
290 #else
binary(quintptr)291 QString binary(quintptr) { return QString(); }
292 #define SDUMP if (1) ; else qDebug
293 #endif
294
295 // Stores a classname -> freed count mapping.
296 typedef QHash<const char*, int> MMStatsHash;
Q_GLOBAL_STATIC(MMStatsHash,freedObjectStatsGlobal)297 Q_GLOBAL_STATIC(MMStatsHash, freedObjectStatsGlobal)
298
299 // This indirection avoids sticking QHash code in each of the call sites, which
300 // shaves off some instructions in the case that it's unused.
301 static void increaseFreedCountForClass(const char *className)
302 {
303 (*freedObjectStatsGlobal())[className]++;
304 }
305
306 //bool Chunk::sweep(ClassDestroyStatsCallback classCountPtr)
sweep(ExecutionEngine * engine)307 bool Chunk::sweep(ExecutionEngine *engine)
308 {
309 bool hasUsedSlots = false;
310 SDUMP() << "sweeping chunk" << this;
311 HeapItem *o = realBase();
312 bool lastSlotFree = false;
313 for (uint i = 0; i < Chunk::EntriesInBitmap; ++i) {
314 #if WRITEBARRIER(none)
315 Q_ASSERT((grayBitmap[i] | blackBitmap[i]) == blackBitmap[i]); // check that we don't have gray only objects
316 #endif
317 quintptr toFree = objectBitmap[i] ^ blackBitmap[i];
318 Q_ASSERT((toFree & objectBitmap[i]) == toFree); // check all black objects are marked as being used
319 quintptr e = extendsBitmap[i];
320 SDUMP() << " index=" << i;
321 SDUMP() << " toFree =" << binary(toFree);
322 SDUMP() << " black =" << binary(blackBitmap[i]);
323 SDUMP() << " object =" << binary(objectBitmap[i]);
324 SDUMP() << " extends =" << binary(e);
325 if (lastSlotFree)
326 e &= (e + 1); // clear all lowest extent bits
327 while (toFree) {
328 uint index = qCountTrailingZeroBits(toFree);
329 quintptr bit = (static_cast<quintptr>(1) << index);
330
331 toFree ^= bit; // mask out freed slot
332 // DEBUG << " index" << hex << index << toFree;
333
334 // remove all extends slots that have been freed
335 // this is a bit of bit trickery.
336 quintptr mask = (bit << 1) - 1; // create a mask of 1's to the right of and up to the current bit
337 quintptr objmask = e | mask; // or'ing mask with e gives all ones until the end of the current object
338 quintptr result = objmask + 1;
339 Q_ASSERT(qCountTrailingZeroBits(result) - index != 0); // ensure we freed something
340 result |= mask; // ensure we don't clear stuff to the right of the current object
341 e &= result;
342
343 HeapItem *itemToFree = o + index;
344 Heap::Base *b = *itemToFree;
345 const VTable *v = b->internalClass->vtable;
346 // if (Q_UNLIKELY(classCountPtr))
347 // classCountPtr(v->className);
348 if (v->destroy) {
349 v->destroy(b);
350 b->_checkIsDestroyed();
351 }
352 #ifdef V4_USE_HEAPTRACK
353 heaptrack_report_free(itemToFree);
354 #endif
355 }
356 Q_V4_PROFILE_DEALLOC(engine, qPopulationCount((objectBitmap[i] | extendsBitmap[i])
357 - (blackBitmap[i] | e)) * Chunk::SlotSize,
358 Profiling::SmallItem);
359 objectBitmap[i] = blackBitmap[i];
360 grayBitmap[i] = 0;
361 hasUsedSlots |= (blackBitmap[i] != 0);
362 extendsBitmap[i] = e;
363 lastSlotFree = !((objectBitmap[i]|extendsBitmap[i]) >> (sizeof(quintptr)*8 - 1));
364 SDUMP() << " new extends =" << binary(e);
365 SDUMP() << " lastSlotFree" << lastSlotFree;
366 Q_ASSERT((objectBitmap[i] & extendsBitmap[i]) == 0);
367 o += Chunk::Bits;
368 }
369 // DEBUG << "swept chunk" << this << "freed" << slotsFreed << "slots.";
370 return hasUsedSlots;
371 }
372
freeAll(ExecutionEngine * engine)373 void Chunk::freeAll(ExecutionEngine *engine)
374 {
375 // DEBUG << "sweeping chunk" << this << (*freeList);
376 HeapItem *o = realBase();
377 for (uint i = 0; i < Chunk::EntriesInBitmap; ++i) {
378 quintptr toFree = objectBitmap[i];
379 quintptr e = extendsBitmap[i];
380 // DEBUG << hex << " index=" << i << toFree;
381 while (toFree) {
382 uint index = qCountTrailingZeroBits(toFree);
383 quintptr bit = (static_cast<quintptr>(1) << index);
384
385 toFree ^= bit; // mask out freed slot
386 // DEBUG << " index" << hex << index << toFree;
387
388 // remove all extends slots that have been freed
389 // this is a bit of bit trickery.
390 quintptr mask = (bit << 1) - 1; // create a mask of 1's to the right of and up to the current bit
391 quintptr objmask = e | mask; // or'ing mask with e gives all ones until the end of the current object
392 quintptr result = objmask + 1;
393 Q_ASSERT(qCountTrailingZeroBits(result) - index != 0); // ensure we freed something
394 result |= mask; // ensure we don't clear stuff to the right of the current object
395 e &= result;
396
397 HeapItem *itemToFree = o + index;
398 Heap::Base *b = *itemToFree;
399 if (b->internalClass->vtable->destroy) {
400 b->internalClass->vtable->destroy(b);
401 b->_checkIsDestroyed();
402 }
403 #ifdef V4_USE_HEAPTRACK
404 heaptrack_report_free(itemToFree);
405 #endif
406 }
407 Q_V4_PROFILE_DEALLOC(engine, (qPopulationCount(objectBitmap[i]|extendsBitmap[i])
408 - qPopulationCount(e)) * Chunk::SlotSize, Profiling::SmallItem);
409 objectBitmap[i] = 0;
410 grayBitmap[i] = 0;
411 extendsBitmap[i] = e;
412 o += Chunk::Bits;
413 }
414 // DEBUG << "swept chunk" << this << "freed" << slotsFreed << "slots.";
415 }
416
resetBlackBits()417 void Chunk::resetBlackBits()
418 {
419 memset(blackBitmap, 0, sizeof(blackBitmap));
420 }
421
collectGrayItems(MarkStack * markStack)422 void Chunk::collectGrayItems(MarkStack *markStack)
423 {
424 // DEBUG << "sweeping chunk" << this << (*freeList);
425 HeapItem *o = realBase();
426 for (uint i = 0; i < Chunk::EntriesInBitmap; ++i) {
427 #if WRITEBARRIER(none)
428 Q_ASSERT((grayBitmap[i] | blackBitmap[i]) == blackBitmap[i]); // check that we don't have gray only objects
429 #endif
430 quintptr toMark = blackBitmap[i] & grayBitmap[i]; // correct for a Steele type barrier
431 Q_ASSERT((toMark & objectBitmap[i]) == toMark); // check all black objects are marked as being used
432 // DEBUG << hex << " index=" << i << toFree;
433 while (toMark) {
434 uint index = qCountTrailingZeroBits(toMark);
435 quintptr bit = (static_cast<quintptr>(1) << index);
436
437 toMark ^= bit; // mask out marked slot
438 // DEBUG << " index" << hex << index << toFree;
439
440 HeapItem *itemToFree = o + index;
441 Heap::Base *b = *itemToFree;
442 Q_ASSERT(b->inUse());
443 markStack->push(b);
444 }
445 grayBitmap[i] = 0;
446 o += Chunk::Bits;
447 }
448 // DEBUG << "swept chunk" << this << "freed" << slotsFreed << "slots.";
449
450 }
451
sortIntoBins(HeapItem ** bins,uint nBins)452 void Chunk::sortIntoBins(HeapItem **bins, uint nBins)
453 {
454 // qDebug() << "sortIntoBins:";
455 HeapItem *base = realBase();
456 #if QT_POINTER_SIZE == 8
457 const int start = 0;
458 #else
459 const int start = 1;
460 #endif
461 #ifndef QT_NO_DEBUG
462 uint freeSlots = 0;
463 uint allocatedSlots = 0;
464 #endif
465 for (int i = start; i < EntriesInBitmap; ++i) {
466 quintptr usedSlots = (objectBitmap[i]|extendsBitmap[i]);
467 #if QT_POINTER_SIZE == 8
468 if (!i)
469 usedSlots |= (static_cast<quintptr>(1) << (HeaderSize/SlotSize)) - 1;
470 #endif
471 #ifndef QT_NO_DEBUG
472 allocatedSlots += qPopulationCount(usedSlots);
473 // qDebug() << hex << " i=" << i << "used=" << usedSlots;
474 #endif
475 while (1) {
476 uint index = qCountTrailingZeroBits(usedSlots + 1);
477 if (index == Bits)
478 break;
479 uint freeStart = i*Bits + index;
480 usedSlots &= ~((static_cast<quintptr>(1) << index) - 1);
481 while (!usedSlots) {
482 if (++i < EntriesInBitmap) {
483 usedSlots = (objectBitmap[i]|extendsBitmap[i]);
484 } else {
485 Q_ASSERT(i == EntriesInBitmap);
486 // Overflows to 0 when counting trailing zeroes above in next iteration.
487 // Then, all the bits are zeroes and we break.
488 usedSlots = std::numeric_limits<quintptr>::max();
489 break;
490 }
491 #ifndef QT_NO_DEBUG
492 allocatedSlots += qPopulationCount(usedSlots);
493 // qDebug() << hex << " i=" << i << "used=" << usedSlots;
494 #endif
495 }
496 HeapItem *freeItem = base + freeStart;
497
498 index = qCountTrailingZeroBits(usedSlots);
499 usedSlots |= (quintptr(1) << index) - 1;
500 uint freeEnd = i*Bits + index;
501 uint nSlots = freeEnd - freeStart;
502 #ifndef QT_NO_DEBUG
503 // qDebug() << hex << " got free slots from" << freeStart << "to" << freeEnd << "n=" << nSlots << "usedSlots=" << usedSlots;
504 freeSlots += nSlots;
505 #endif
506 Q_ASSERT(freeEnd > freeStart && freeEnd <= NumSlots);
507 freeItem->freeData.availableSlots = nSlots;
508 uint bin = qMin(nBins - 1, nSlots);
509 freeItem->freeData.next = bins[bin];
510 bins[bin] = freeItem;
511 }
512 }
513 #ifndef QT_NO_DEBUG
514 Q_ASSERT(freeSlots + allocatedSlots == (EntriesInBitmap - start) * 8 * sizeof(quintptr));
515 #endif
516 }
517
allocate(size_t size,bool forceAllocation)518 HeapItem *BlockAllocator::allocate(size_t size, bool forceAllocation) {
519 Q_ASSERT((size % Chunk::SlotSize) == 0);
520 size_t slotsRequired = size >> Chunk::SlotSizeShift;
521
522 if (allocationStats)
523 ++allocationStats[binForSlots(slotsRequired)];
524
525 HeapItem **last;
526
527 HeapItem *m;
528
529 if (slotsRequired < NumBins - 1) {
530 m = freeBins[slotsRequired];
531 if (m) {
532 freeBins[slotsRequired] = m->freeData.next;
533 goto done;
534 }
535 }
536
537 if (nFree >= slotsRequired) {
538 // use bump allocation
539 Q_ASSERT(nextFree);
540 m = nextFree;
541 nextFree += slotsRequired;
542 nFree -= slotsRequired;
543 goto done;
544 }
545
546 // DEBUG << "No matching bin found for item" << size << bin;
547 // search last bin for a large enough item
548 last = &freeBins[NumBins - 1];
549 while ((m = *last)) {
550 if (m->freeData.availableSlots >= slotsRequired) {
551 *last = m->freeData.next; // take it out of the list
552
553 size_t remainingSlots = m->freeData.availableSlots - slotsRequired;
554 // DEBUG << "found large free slots of size" << m->freeData.availableSlots << m << "remaining" << remainingSlots;
555 if (remainingSlots == 0)
556 goto done;
557
558 HeapItem *remainder = m + slotsRequired;
559 if (remainingSlots > nFree) {
560 if (nFree) {
561 size_t bin = binForSlots(nFree);
562 nextFree->freeData.next = freeBins[bin];
563 nextFree->freeData.availableSlots = nFree;
564 freeBins[bin] = nextFree;
565 }
566 nextFree = remainder;
567 nFree = remainingSlots;
568 } else {
569 remainder->freeData.availableSlots = remainingSlots;
570 size_t binForRemainder = binForSlots(remainingSlots);
571 remainder->freeData.next = freeBins[binForRemainder];
572 freeBins[binForRemainder] = remainder;
573 }
574 goto done;
575 }
576 last = &m->freeData.next;
577 }
578
579 if (slotsRequired < NumBins - 1) {
580 // check if we can split up another slot
581 for (size_t i = slotsRequired + 1; i < NumBins - 1; ++i) {
582 m = freeBins[i];
583 if (m) {
584 freeBins[i] = m->freeData.next; // take it out of the list
585 // qDebug() << "got item" << slotsRequired << "from slot" << i;
586 size_t remainingSlots = i - slotsRequired;
587 Q_ASSERT(remainingSlots < NumBins - 1);
588 HeapItem *remainder = m + slotsRequired;
589 remainder->freeData.availableSlots = remainingSlots;
590 remainder->freeData.next = freeBins[remainingSlots];
591 freeBins[remainingSlots] = remainder;
592 goto done;
593 }
594 }
595 }
596
597 if (!m) {
598 if (!forceAllocation)
599 return nullptr;
600 Chunk *newChunk = chunkAllocator->allocate();
601 Q_V4_PROFILE_ALLOC(engine, Chunk::DataSize, Profiling::HeapPage);
602 chunks.push_back(newChunk);
603 nextFree = newChunk->first();
604 nFree = Chunk::AvailableSlots;
605 m = nextFree;
606 nextFree += slotsRequired;
607 nFree -= slotsRequired;
608 }
609
610 done:
611 m->setAllocatedSlots(slotsRequired);
612 Q_V4_PROFILE_ALLOC(engine, slotsRequired * Chunk::SlotSize, Profiling::SmallItem);
613 #ifdef V4_USE_HEAPTRACK
614 heaptrack_report_alloc(m, slotsRequired * Chunk::SlotSize);
615 #endif
616 // DEBUG << " " << hex << m->chunk() << m->chunk()->objectBitmap[0] << m->chunk()->extendsBitmap[0] << (m - m->chunk()->realBase());
617 return m;
618 }
619
sweep()620 void BlockAllocator::sweep()
621 {
622 nextFree = nullptr;
623 nFree = 0;
624 memset(freeBins, 0, sizeof(freeBins));
625
626 // qDebug() << "BlockAlloc: sweep";
627 usedSlotsAfterLastSweep = 0;
628
629 auto firstEmptyChunk = std::partition(chunks.begin(), chunks.end(), [this](Chunk *c) {
630 return c->sweep(engine);
631 });
632
633 std::for_each(chunks.begin(), firstEmptyChunk, [this](Chunk *c) {
634 c->sortIntoBins(freeBins, NumBins);
635 usedSlotsAfterLastSweep += c->nUsedSlots();
636 });
637
638 // only free the chunks at the end to avoid that the sweep() calls indirectly
639 // access freed memory
640 std::for_each(firstEmptyChunk, chunks.end(), [this](Chunk *c) {
641 Q_V4_PROFILE_DEALLOC(engine, Chunk::DataSize, Profiling::HeapPage);
642 chunkAllocator->free(c);
643 });
644
645 chunks.erase(firstEmptyChunk, chunks.end());
646 }
647
freeAll()648 void BlockAllocator::freeAll()
649 {
650 for (auto c : chunks)
651 c->freeAll(engine);
652 for (auto c : chunks) {
653 Q_V4_PROFILE_DEALLOC(engine, Chunk::DataSize, Profiling::HeapPage);
654 chunkAllocator->free(c);
655 }
656 }
657
resetBlackBits()658 void BlockAllocator::resetBlackBits()
659 {
660 for (auto c : chunks)
661 c->resetBlackBits();
662 }
663
collectGrayItems(MarkStack * markStack)664 void BlockAllocator::collectGrayItems(MarkStack *markStack)
665 {
666 for (auto c : chunks)
667 c->collectGrayItems(markStack);
668
669 }
670
allocate(size_t size)671 HeapItem *HugeItemAllocator::allocate(size_t size) {
672 MemorySegment *m = nullptr;
673 Chunk *c = nullptr;
674 if (size >= MemorySegment::SegmentSize/2) {
675 // too large to handle through the ChunkAllocator, let's get our own memory segement
676 size += Chunk::HeaderSize; // space required for the Chunk header
677 size_t pageSize = WTF::pageSize();
678 size = (size + pageSize - 1) & ~(pageSize - 1); // align to page sizes
679 m = new MemorySegment(size);
680 c = m->allocate(size);
681 } else {
682 c = chunkAllocator->allocate(size);
683 }
684 Q_ASSERT(c);
685 chunks.push_back(HugeChunk{m, c, size});
686 Chunk::setBit(c->objectBitmap, c->first() - c->realBase());
687 Q_V4_PROFILE_ALLOC(engine, size, Profiling::LargeItem);
688 #ifdef V4_USE_HEAPTRACK
689 heaptrack_report_alloc(c, size);
690 #endif
691 return c->first();
692 }
693
freeHugeChunk(ChunkAllocator * chunkAllocator,const HugeItemAllocator::HugeChunk & c,ClassDestroyStatsCallback classCountPtr)694 static void freeHugeChunk(ChunkAllocator *chunkAllocator, const HugeItemAllocator::HugeChunk &c, ClassDestroyStatsCallback classCountPtr)
695 {
696 HeapItem *itemToFree = c.chunk->first();
697 Heap::Base *b = *itemToFree;
698 const VTable *v = b->internalClass->vtable;
699 if (Q_UNLIKELY(classCountPtr))
700 classCountPtr(v->className);
701
702 if (v->destroy) {
703 v->destroy(b);
704 b->_checkIsDestroyed();
705 }
706 if (c.segment) {
707 // own memory segment
708 c.segment->free(c.chunk, c.size);
709 delete c.segment;
710 } else {
711 chunkAllocator->free(c.chunk, c.size);
712 }
713 #ifdef V4_USE_HEAPTRACK
714 heaptrack_report_free(c.chunk);
715 #endif
716 }
717
sweep(ClassDestroyStatsCallback classCountPtr)718 void HugeItemAllocator::sweep(ClassDestroyStatsCallback classCountPtr)
719 {
720 auto isBlack = [this, classCountPtr] (const HugeChunk &c) {
721 bool b = c.chunk->first()->isBlack();
722 Chunk::clearBit(c.chunk->blackBitmap, c.chunk->first() - c.chunk->realBase());
723 if (!b) {
724 Q_V4_PROFILE_DEALLOC(engine, c.size, Profiling::LargeItem);
725 freeHugeChunk(chunkAllocator, c, classCountPtr);
726 }
727 return !b;
728 };
729
730 auto newEnd = std::remove_if(chunks.begin(), chunks.end(), isBlack);
731 chunks.erase(newEnd, chunks.end());
732 }
733
resetBlackBits()734 void HugeItemAllocator::resetBlackBits()
735 {
736 for (auto c : chunks)
737 Chunk::clearBit(c.chunk->blackBitmap, c.chunk->first() - c.chunk->realBase());
738 }
739
collectGrayItems(MarkStack * markStack)740 void HugeItemAllocator::collectGrayItems(MarkStack *markStack)
741 {
742 for (auto c : chunks)
743 // Correct for a Steele type barrier
744 if (Chunk::testBit(c.chunk->blackBitmap, c.chunk->first() - c.chunk->realBase()) &&
745 Chunk::testBit(c.chunk->grayBitmap, c.chunk->first() - c.chunk->realBase())) {
746 HeapItem *i = c.chunk->first();
747 Heap::Base *b = *i;
748 b->mark(markStack);
749 }
750 }
751
freeAll()752 void HugeItemAllocator::freeAll()
753 {
754 for (auto &c : chunks) {
755 Q_V4_PROFILE_DEALLOC(engine, c.size, Profiling::LargeItem);
756 freeHugeChunk(chunkAllocator, c, nullptr);
757 }
758 }
759
760
MemoryManager(ExecutionEngine * engine)761 MemoryManager::MemoryManager(ExecutionEngine *engine)
762 : engine(engine)
763 , chunkAllocator(new ChunkAllocator)
764 , blockAllocator(chunkAllocator, engine)
765 , icAllocator(chunkAllocator, engine)
766 , hugeItemAllocator(chunkAllocator, engine)
767 , m_persistentValues(new PersistentValueStorage(engine))
768 , m_weakValues(new PersistentValueStorage(engine))
769 , unmanagedHeapSizeGCLimit(MinUnmanagedHeapSizeGCLimit)
770 , aggressiveGC(!qEnvironmentVariableIsEmpty("QV4_MM_AGGRESSIVE_GC"))
771 , gcStats(lcGcStats().isDebugEnabled())
772 , gcCollectorStats(lcGcAllocatorStats().isDebugEnabled())
773 {
774 #ifdef V4_USE_VALGRIND
775 VALGRIND_CREATE_MEMPOOL(this, 0, true);
776 #endif
777 memset(statistics.allocations, 0, sizeof(statistics.allocations));
778 if (gcStats)
779 blockAllocator.allocationStats = statistics.allocations;
780 }
781
allocString(std::size_t unmanagedSize)782 Heap::Base *MemoryManager::allocString(std::size_t unmanagedSize)
783 {
784 const size_t stringSize = align(sizeof(Heap::String));
785 #ifdef MM_STATS
786 lastAllocRequestedSlots = stringSize >> Chunk::SlotSizeShift;
787 ++allocationCount;
788 #endif
789 unmanagedHeapSize += unmanagedSize;
790
791 HeapItem *m = allocate(&blockAllocator, stringSize);
792 memset(m, 0, stringSize);
793 return *m;
794 }
795
allocData(std::size_t size)796 Heap::Base *MemoryManager::allocData(std::size_t size)
797 {
798 #ifdef MM_STATS
799 lastAllocRequestedSlots = size >> Chunk::SlotSizeShift;
800 ++allocationCount;
801 #endif
802
803 Q_ASSERT(size >= Chunk::SlotSize);
804 Q_ASSERT(size % Chunk::SlotSize == 0);
805
806 HeapItem *m = allocate(&blockAllocator, size);
807 memset(m, 0, size);
808 return *m;
809 }
810
allocObjectWithMemberData(const QV4::VTable * vtable,uint nMembers)811 Heap::Object *MemoryManager::allocObjectWithMemberData(const QV4::VTable *vtable, uint nMembers)
812 {
813 uint size = (vtable->nInlineProperties + vtable->inlinePropertyOffset)*sizeof(Value);
814 Q_ASSERT(!(size % sizeof(HeapItem)));
815
816 Heap::Object *o;
817 if (nMembers <= vtable->nInlineProperties) {
818 o = static_cast<Heap::Object *>(allocData(size));
819 } else {
820 // Allocate both in one go through the block allocator
821 nMembers -= vtable->nInlineProperties;
822 std::size_t memberSize = align(sizeof(Heap::MemberData) + (nMembers - 1)*sizeof(Value));
823 size_t totalSize = size + memberSize;
824 Heap::MemberData *m;
825 if (totalSize > Chunk::DataSize) {
826 o = static_cast<Heap::Object *>(allocData(size));
827 m = hugeItemAllocator.allocate(memberSize)->as<Heap::MemberData>();
828 } else {
829 HeapItem *mh = reinterpret_cast<HeapItem *>(allocData(totalSize));
830 Heap::Base *b = *mh;
831 o = static_cast<Heap::Object *>(b);
832 mh += (size >> Chunk::SlotSizeShift);
833 m = mh->as<Heap::MemberData>();
834 Chunk *c = mh->chunk();
835 size_t index = mh - c->realBase();
836 Chunk::setBit(c->objectBitmap, index);
837 Chunk::clearBit(c->extendsBitmap, index);
838 }
839 o->memberData.set(engine, m);
840 m->internalClass.set(engine, engine->internalClasses(EngineBase::Class_MemberData));
841 Q_ASSERT(o->memberData->internalClass);
842 m->values.alloc = static_cast<uint>((memberSize - sizeof(Heap::MemberData) + sizeof(Value))/sizeof(Value));
843 m->values.size = o->memberData->values.alloc;
844 m->init();
845 // qDebug() << " got" << o->memberData << o->memberData->size;
846 }
847 // qDebug() << "allocating object with memberData" << o << o->memberData.operator->();
848 return o;
849 }
850
851 static uint markStackSize = 0;
852
MarkStack(ExecutionEngine * engine)853 MarkStack::MarkStack(ExecutionEngine *engine)
854 : m_engine(engine)
855 {
856 m_base = (Heap::Base **)engine->gcStack->base();
857 m_top = m_base;
858 const size_t size = engine->maxGCStackSize() / sizeof(Heap::Base);
859 m_hardLimit = m_base + size;
860 m_softLimit = m_base + size * 3 / 4;
861 }
862
drain()863 void MarkStack::drain()
864 {
865 while (m_top > m_base) {
866 Heap::Base *h = pop();
867 ++markStackSize;
868 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.
869 h->internalClass->vtable->markObjects(h, this);
870 }
871 }
872
collectRoots(MarkStack * markStack)873 void MemoryManager::collectRoots(MarkStack *markStack)
874 {
875 engine->markObjects(markStack);
876
877 // qDebug() << " mark stack after engine->mark" << (engine->jsStackTop - markBase);
878
879 collectFromJSStack(markStack);
880
881 // qDebug() << " mark stack after js stack collect" << (engine->jsStackTop - markBase);
882 m_persistentValues->mark(markStack);
883
884 // qDebug() << " mark stack after persistants" << (engine->jsStackTop - markBase);
885
886 // Preserve QObject ownership rules within JavaScript: A parent with c++ ownership
887 // keeps all of its children alive in JavaScript.
888
889 // Do this _after_ collectFromStack to ensure that processing the weak
890 // managed objects in the loop down there doesn't make then end up as leftovers
891 // on the stack and thus always get collected.
892 for (PersistentValueStorage::Iterator it = m_weakValues->begin(); it != m_weakValues->end(); ++it) {
893 QObjectWrapper *qobjectWrapper = (*it).as<QObjectWrapper>();
894 if (!qobjectWrapper)
895 continue;
896 QObject *qobject = qobjectWrapper->object();
897 if (!qobject)
898 continue;
899 bool keepAlive = QQmlData::keepAliveDuringGarbageCollection(qobject);
900
901 if (!keepAlive) {
902 if (QObject *parent = qobject->parent()) {
903 while (parent->parent())
904 parent = parent->parent();
905
906 keepAlive = QQmlData::keepAliveDuringGarbageCollection(parent);
907 }
908 }
909
910 if (keepAlive)
911 qobjectWrapper->mark(markStack);
912 }
913 }
914
mark()915 void MemoryManager::mark()
916 {
917 markStackSize = 0;
918 MarkStack markStack(engine);
919 collectRoots(&markStack);
920 // dtor of MarkStack drains
921 }
922
sweep(bool lastSweep,ClassDestroyStatsCallback classCountPtr)923 void MemoryManager::sweep(bool lastSweep, ClassDestroyStatsCallback classCountPtr)
924 {
925 for (PersistentValueStorage::Iterator it = m_weakValues->begin(); it != m_weakValues->end(); ++it) {
926 Managed *m = (*it).managed();
927 if (!m || m->markBit())
928 continue;
929 // we need to call destroyObject on qobjectwrappers now, so that they can emit the destroyed
930 // signal before we start sweeping the heap
931 if (QObjectWrapper *qobjectWrapper = (*it).as<QObjectWrapper>())
932 qobjectWrapper->destroyObject(lastSweep);
933 }
934
935 // remove objects from weak maps and sets
936 Heap::MapObject *map = weakMaps;
937 Heap::MapObject **lastMap = &weakMaps;
938 while (map) {
939 if (map->isMarked()) {
940 map->removeUnmarkedKeys();
941 *lastMap = map;
942 lastMap = &map->nextWeakMap;
943 }
944 map = map->nextWeakMap;
945 }
946
947 Heap::SetObject *set = weakSets;
948 Heap::SetObject **lastSet = &weakSets;
949 while (set) {
950 if (set->isMarked()) {
951 set->removeUnmarkedKeys();
952 *lastSet = set;
953 lastSet = &set->nextWeakSet;
954 }
955 set = set->nextWeakSet;
956 }
957
958 // onDestruction handlers may have accessed other QObject wrappers and reset their value, so ensure
959 // that they are all set to undefined.
960 for (PersistentValueStorage::Iterator it = m_weakValues->begin(); it != m_weakValues->end(); ++it) {
961 Managed *m = (*it).managed();
962 if (!m || m->markBit())
963 continue;
964 (*it) = Value::undefinedValue();
965 }
966
967 // Now it is time to free QV4::QObjectWrapper Value, we must check the Value's tag to make sure its object has been destroyed
968 const int pendingCount = m_pendingFreedObjectWrapperValue.count();
969 if (pendingCount) {
970 QVector<Value *> remainingWeakQObjectWrappers;
971 remainingWeakQObjectWrappers.reserve(pendingCount);
972 for (int i = 0; i < pendingCount; ++i) {
973 Value *v = m_pendingFreedObjectWrapperValue.at(i);
974 if (v->isUndefined() || v->isEmpty())
975 PersistentValueStorage::free(v);
976 else
977 remainingWeakQObjectWrappers.append(v);
978 }
979 m_pendingFreedObjectWrapperValue = remainingWeakQObjectWrappers;
980 }
981
982 if (MultiplyWrappedQObjectMap *multiplyWrappedQObjects = engine->m_multiplyWrappedQObjects) {
983 for (MultiplyWrappedQObjectMap::Iterator it = multiplyWrappedQObjects->begin(); it != multiplyWrappedQObjects->end();) {
984 if (it.value().isNullOrUndefined())
985 it = multiplyWrappedQObjects->erase(it);
986 else
987 ++it;
988 }
989 }
990
991
992 if (!lastSweep) {
993 engine->identifierTable->sweep();
994 blockAllocator.sweep(/*classCountPtr*/);
995 hugeItemAllocator.sweep(classCountPtr);
996 icAllocator.sweep(/*classCountPtr*/);
997 }
998 }
999
shouldRunGC() const1000 bool MemoryManager::shouldRunGC() const
1001 {
1002 size_t total = blockAllocator.totalSlots() + icAllocator.totalSlots();
1003 if (total > MinSlotsGCLimit && usedSlotsAfterLastFullSweep * GCOverallocation < total * 100)
1004 return true;
1005 return false;
1006 }
1007
dumpBins(BlockAllocator * b,const char * title)1008 static size_t dumpBins(BlockAllocator *b, const char *title)
1009 {
1010 const QLoggingCategory &stats = lcGcAllocatorStats();
1011 size_t totalSlotMem = 0;
1012 if (title)
1013 qDebug(stats) << "Slot map for" << title << "allocator:";
1014 for (uint i = 0; i < BlockAllocator::NumBins; ++i) {
1015 uint nEntries = 0;
1016 HeapItem *h = b->freeBins[i];
1017 while (h) {
1018 ++nEntries;
1019 totalSlotMem += h->freeData.availableSlots;
1020 h = h->freeData.next;
1021 }
1022 if (title)
1023 qDebug(stats) << " number of entries in slot" << i << ":" << nEntries;
1024 }
1025 SDUMP() << " large slot map";
1026 HeapItem *h = b->freeBins[BlockAllocator::NumBins - 1];
1027 while (h) {
1028 SDUMP() << " " << Qt::hex << (quintptr(h)/32) << h->freeData.availableSlots;
1029 h = h->freeData.next;
1030 }
1031
1032 if (title)
1033 qDebug(stats) << " total mem in bins" << totalSlotMem*Chunk::SlotSize;
1034 return totalSlotMem*Chunk::SlotSize;
1035 }
1036
runGC()1037 void MemoryManager::runGC()
1038 {
1039 if (gcBlocked) {
1040 // qDebug() << "Not running GC.";
1041 return;
1042 }
1043
1044 QScopedValueRollback<bool> gcBlocker(gcBlocked, true);
1045 // qDebug() << "runGC";
1046
1047 if (gcStats) {
1048 statistics.maxReservedMem = qMax(statistics.maxReservedMem, getAllocatedMem());
1049 statistics.maxAllocatedMem = qMax(statistics.maxAllocatedMem, getUsedMem() + getLargeItemsMem());
1050 }
1051
1052 if (!gcCollectorStats) {
1053 mark();
1054 sweep();
1055 } else {
1056 bool triggeredByUnmanagedHeap = (unmanagedHeapSize > unmanagedHeapSizeGCLimit);
1057 size_t oldUnmanagedSize = unmanagedHeapSize;
1058
1059 const size_t totalMem = getAllocatedMem();
1060 const size_t usedBefore = getUsedMem();
1061 const size_t largeItemsBefore = getLargeItemsMem();
1062
1063 const QLoggingCategory &stats = lcGcAllocatorStats();
1064 qDebug(stats) << "========== GC ==========";
1065 #ifdef MM_STATS
1066 qDebug(stats) << " Triggered by alloc request of" << lastAllocRequestedSlots << "slots.";
1067 qDebug(stats) << " Allocations since last GC" << allocationCount;
1068 allocationCount = 0;
1069 #endif
1070 size_t oldChunks = blockAllocator.chunks.size();
1071 qDebug(stats) << "Allocated" << totalMem << "bytes in" << oldChunks << "chunks";
1072 qDebug(stats) << "Fragmented memory before GC" << (totalMem - usedBefore);
1073 dumpBins(&blockAllocator, "Block");
1074 dumpBins(&icAllocator, "InternalClass");
1075
1076 QElapsedTimer t;
1077 t.start();
1078 mark();
1079 qint64 markTime = t.nsecsElapsed()/1000;
1080 t.restart();
1081 sweep(false, increaseFreedCountForClass);
1082 const size_t usedAfter = getUsedMem();
1083 const size_t largeItemsAfter = getLargeItemsMem();
1084 qint64 sweepTime = t.nsecsElapsed()/1000;
1085
1086 if (triggeredByUnmanagedHeap) {
1087 qDebug(stats) << "triggered by unmanaged heap:";
1088 qDebug(stats) << " old unmanaged heap size:" << oldUnmanagedSize;
1089 qDebug(stats) << " new unmanaged heap:" << unmanagedHeapSize;
1090 qDebug(stats) << " unmanaged heap limit:" << unmanagedHeapSizeGCLimit;
1091 }
1092 size_t memInBins = dumpBins(&blockAllocator, "Block")
1093 + dumpBins(&icAllocator, "InternalClasss");
1094 qDebug(stats) << "Marked object in" << markTime << "us.";
1095 qDebug(stats) << " " << markStackSize << "objects marked";
1096 qDebug(stats) << "Sweeped object in" << sweepTime << "us.";
1097
1098 // sort our object types by number of freed instances
1099 MMStatsHash freedObjectStats;
1100 std::swap(freedObjectStats, *freedObjectStatsGlobal());
1101 typedef std::pair<const char*, int> ObjectStatInfo;
1102 std::vector<ObjectStatInfo> freedObjectsSorted;
1103 freedObjectsSorted.reserve(freedObjectStats.count());
1104 for (auto it = freedObjectStats.constBegin(); it != freedObjectStats.constEnd(); ++it) {
1105 freedObjectsSorted.push_back(std::make_pair(it.key(), it.value()));
1106 }
1107 std::sort(freedObjectsSorted.begin(), freedObjectsSorted.end(), [](const ObjectStatInfo &a, const ObjectStatInfo &b) {
1108 return a.second > b.second && strcmp(a.first, b.first) < 0;
1109 });
1110
1111 qDebug(stats) << "Used memory before GC:" << usedBefore;
1112 qDebug(stats) << "Used memory after GC:" << usedAfter;
1113 qDebug(stats) << "Freed up bytes :" << (usedBefore - usedAfter);
1114 qDebug(stats) << "Freed up chunks :" << (oldChunks - blockAllocator.chunks.size());
1115 size_t lost = blockAllocator.allocatedMem() + icAllocator.allocatedMem()
1116 - memInBins - usedAfter;
1117 if (lost)
1118 qDebug(stats) << "!!!!!!!!!!!!!!!!!!!!! LOST MEM:" << lost << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!";
1119 if (largeItemsBefore || largeItemsAfter) {
1120 qDebug(stats) << "Large item memory before GC:" << largeItemsBefore;
1121 qDebug(stats) << "Large item memory after GC:" << largeItemsAfter;
1122 qDebug(stats) << "Large item memory freed up:" << (largeItemsBefore - largeItemsAfter);
1123 }
1124
1125 for (auto it = freedObjectsSorted.cbegin(); it != freedObjectsSorted.cend(); ++it) {
1126 qDebug(stats).noquote() << QString::fromLatin1("Freed JS type: %1 (%2 instances)").arg(QString::fromLatin1(it->first), QString::number(it->second));
1127 }
1128
1129 qDebug(stats) << "======== End GC ========";
1130 }
1131
1132 if (gcStats)
1133 statistics.maxUsedMem = qMax(statistics.maxUsedMem, getUsedMem() + getLargeItemsMem());
1134
1135 if (aggressiveGC) {
1136 // ensure we don't 'loose' any memory
1137 Q_ASSERT(blockAllocator.allocatedMem()
1138 == blockAllocator.usedMem() + dumpBins(&blockAllocator, nullptr));
1139 Q_ASSERT(icAllocator.allocatedMem()
1140 == icAllocator.usedMem() + dumpBins(&icAllocator, nullptr));
1141 }
1142
1143 usedSlotsAfterLastFullSweep = blockAllocator.usedSlotsAfterLastSweep + icAllocator.usedSlotsAfterLastSweep;
1144
1145 // reset all black bits
1146 blockAllocator.resetBlackBits();
1147 hugeItemAllocator.resetBlackBits();
1148 icAllocator.resetBlackBits();
1149 }
1150
getUsedMem() const1151 size_t MemoryManager::getUsedMem() const
1152 {
1153 return blockAllocator.usedMem() + icAllocator.usedMem();
1154 }
1155
getAllocatedMem() const1156 size_t MemoryManager::getAllocatedMem() const
1157 {
1158 return blockAllocator.allocatedMem() + icAllocator.allocatedMem() + hugeItemAllocator.usedMem();
1159 }
1160
getLargeItemsMem() const1161 size_t MemoryManager::getLargeItemsMem() const
1162 {
1163 return hugeItemAllocator.usedMem();
1164 }
1165
registerWeakMap(Heap::MapObject * map)1166 void MemoryManager::registerWeakMap(Heap::MapObject *map)
1167 {
1168 map->nextWeakMap = weakMaps;
1169 weakMaps = map;
1170 }
1171
registerWeakSet(Heap::SetObject * set)1172 void MemoryManager::registerWeakSet(Heap::SetObject *set)
1173 {
1174 set->nextWeakSet = weakSets;
1175 weakSets = set;
1176 }
1177
~MemoryManager()1178 MemoryManager::~MemoryManager()
1179 {
1180 delete m_persistentValues;
1181
1182 dumpStats();
1183
1184 sweep(/*lastSweep*/true);
1185 blockAllocator.freeAll();
1186 hugeItemAllocator.freeAll();
1187 icAllocator.freeAll();
1188
1189 delete m_weakValues;
1190 #ifdef V4_USE_VALGRIND
1191 VALGRIND_DESTROY_MEMPOOL(this);
1192 #endif
1193 delete chunkAllocator;
1194 }
1195
1196
dumpStats() const1197 void MemoryManager::dumpStats() const
1198 {
1199 if (!gcStats)
1200 return;
1201
1202 const QLoggingCategory &stats = lcGcStats();
1203 qDebug(stats) << "Qml GC memory allocation statistics:";
1204 qDebug(stats) << "Total memory allocated:" << statistics.maxReservedMem;
1205 qDebug(stats) << "Max memory used before a GC run:" << statistics.maxAllocatedMem;
1206 qDebug(stats) << "Max memory used after a GC run:" << statistics.maxUsedMem;
1207 qDebug(stats) << "Requests for different item sizes:";
1208 for (int i = 1; i < BlockAllocator::NumBins - 1; ++i)
1209 qDebug(stats) << " <" << (i << Chunk::SlotSizeShift) << " bytes: " << statistics.allocations[i];
1210 qDebug(stats) << " >=" << ((BlockAllocator::NumBins - 1) << Chunk::SlotSizeShift) << " bytes: " << statistics.allocations[BlockAllocator::NumBins - 1];
1211 }
1212
collectFromJSStack(MarkStack * markStack) const1213 void MemoryManager::collectFromJSStack(MarkStack *markStack) const
1214 {
1215 Value *v = engine->jsStackBase;
1216 Value *top = engine->jsStackTop;
1217 while (v < top) {
1218 Managed *m = v->managed();
1219 if (m) {
1220 Q_ASSERT(m->inUse());
1221 // Skip pointers to already freed objects, they are bogus as well
1222 m->mark(markStack);
1223 }
1224 ++v;
1225 }
1226 }
1227
1228 } // namespace QV4
1229
1230 QT_END_NAMESPACE
1231