Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qv4mm.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:critical reason:low-level-memory-management
4
5#include "PageAllocation.h"
6#include "PageReservation.h"
7
8#include <private/qnumeric_p.h>
9#include <private/qv4alloca_p.h>
10#include <private/qv4engine_p.h>
11#include <private/qv4identifiertable_p.h>
12#include <private/qv4mapobject_p.h>
13#include <private/qv4mm_p.h>
14#include <private/qv4object_p.h>
15#include <private/qv4profiling_p.h>
16#include <private/qv4qobjectwrapper_p.h>
17#include <private/qv4setobject_p.h>
18#include <private/qv4stackframe_p.h>
19
20#include <QtQml/qqmlengine.h>
21
22#include <QtCore/qalgorithms.h>
23#include <QtCore/qelapsedtimer.h>
24#include <QtCore/qloggingcategory.h>
25#include <QtCore/qmap.h>
26#include <QtCore/qscopedvaluerollback.h>
27
28#include <algorithm>
29#include <chrono>
30#include <cstdlib>
31
32//#define MM_STATS
33
34#if !defined(MM_STATS) && !defined(QT_NO_DEBUG)
35#define MM_STATS
36#endif
37
38#if MM_DEBUG
39#define DEBUG qDebug() << "MM:"
40#else
41#define DEBUG if (1) ; else qDebug() << "MM:"
42#endif
43
44#ifdef V4_USE_VALGRIND
45#include <valgrind/valgrind.h>
46#include <valgrind/memcheck.h>
47#endif
48
49#ifdef V4_USE_HEAPTRACK
50#include <heaptrack_api.h>
51#endif
52
53#if OS(QNX)
54#include <sys/storage.h> // __tls()
55#endif
56
57#if USE(PTHREADS) && HAVE(PTHREAD_NP_H)
58#include <pthread_np.h>
59#endif
60
61Q_STATIC_LOGGING_CATEGORY(lcGcStats, "qt.qml.gc.statistics")
62Q_STATIC_LOGGING_CATEGORY(lcGcAllocatorStats, "qt.qml.gc.allocatorStats")
63Q_STATIC_LOGGING_CATEGORY(lcGcStateTransitions, "qt.qml.gc.stateTransitions")
64Q_STATIC_LOGGING_CATEGORY(lcGcForcedRuns, "qt.qml.gc.forcedRuns")
65Q_STATIC_LOGGING_CATEGORY(lcGcStepExecution, "qt.qml.gc.stepExecution")
66
67using namespace WTF;
68
69QT_BEGIN_NAMESPACE
70
71namespace QV4 {
72
73enum {
74 MinSlotsGCLimit = QV4::Chunk::AvailableSlots*16,
75 GCOverallocation = 200 /* Max overallocation by the GC in % */
76};
77
79 enum {
80#ifdef Q_OS_RTEMS
81 NumChunks = sizeof(quint64),
82#else
83 NumChunks = 8*sizeof(quint64),
84#endif
85 SegmentSize = NumChunks*Chunk::ChunkSize,
86 };
87
88 MemorySegment(size_t size)
89 {
90 size += Chunk::ChunkSize; // make sure we can get enough 64k alignment memory
91 if (size < SegmentSize)
92 size = SegmentSize;
93
94 pageReservation = PageReservation::reserve(size, OSAllocator::JSGCHeapPages);
95 base = reinterpret_cast<Chunk *>((reinterpret_cast<quintptr>(pageReservation.base()) + Chunk::ChunkSize - 1) & ~(Chunk::ChunkSize - 1));
97 availableBytes = size - (reinterpret_cast<quintptr>(base) - reinterpret_cast<quintptr>(pageReservation.base()));
98 if (availableBytes < SegmentSize)
99 --nChunks;
100 }
102 qSwap(pageReservation, other.pageReservation);
103 qSwap(base, other.base);
104 qSwap(allocatedMap, other.allocatedMap);
105 qSwap(availableBytes, other.availableBytes);
106 qSwap(nChunks, other.nChunks);
107 }
108
110 if (base)
111 pageReservation.deallocate();
112 }
113
114 void setBit(size_t index) {
115 Q_ASSERT(index < nChunks);
116 quint64 bit = static_cast<quint64>(1) << index;
117 allocatedMap |= bit;
118 }
119 void clearBit(size_t index) {
120 Q_ASSERT(index < nChunks);
121 quint64 bit = static_cast<quint64>(1) << index;
122 allocatedMap &= ~bit;
123 }
124 bool testBit(size_t index) const {
125 Q_ASSERT(index < nChunks);
126 quint64 bit = static_cast<quint64>(1) << index;
127 return (allocatedMap & bit);
128 }
129
130 Chunk *allocate(size_t size);
131 void free(Chunk *chunk, size_t size) {
132 DEBUG << "freeing chunk" << chunk;
133 size_t index = static_cast<size_t>(chunk - base);
134 size_t end = qMin(static_cast<size_t>(NumChunks), index + (size - 1)/Chunk::ChunkSize + 1);
135 while (index < end) {
136 Q_ASSERT(testBit(index));
137 clearBit(index);
138 ++index;
139 }
140
141 size_t pageSize = WTF::pageSize();
142 size = (size + pageSize - 1) & ~(pageSize - 1);
143#if !defined(Q_OS_LINUX) && !defined(Q_OS_WIN)
144 // Linux and Windows zero out pages that have been decommitted and get committed again.
145 // unfortunately that's not true on other OSes (e.g. BSD based ones), so zero out the
146 // memory before decommit, so that we can be sure that all chunks we allocate will be
147 // zero initialized.
148 memset(chunk, 0, size);
149#endif
150 pageReservation.decommit(chunk, size);
151 }
152
153 bool contains(Chunk *c) const {
154 return c >= base && c < base + nChunks;
155 }
156
158 Chunk *base = nullptr;
161 uint nChunks = 0;
162};
163
165{
166 if (!allocatedMap && size >= SegmentSize) {
167 // chunk allocated for one huge allocation
168 Q_ASSERT(availableBytes >= size);
169 pageReservation.commit(base, size);
170 allocatedMap = ~static_cast<quint64>(0);
171 return base;
172 }
173 size_t requiredChunks = (size + sizeof(Chunk) - 1)/sizeof(Chunk);
174 uint sequence = 0;
175 Chunk *candidate = nullptr;
176 for (uint i = 0; i < nChunks; ++i) {
177 if (!testBit(i)) {
178 if (!candidate)
179 candidate = base + i;
180 ++sequence;
181 } else {
182 candidate = nullptr;
183 sequence = 0;
184 }
185 if (sequence == requiredChunks) {
186 pageReservation.commit(candidate, size);
187 for (uint i = 0; i < requiredChunks; ++i)
188 setBit(candidate - base + i);
189 DEBUG << "allocated chunk " << candidate << Qt::hex << size;
190
191 return candidate;
192 }
193 }
194 return nullptr;
195}
196
199
201 size += Chunk::HeaderSize; // space required for the Chunk header
202 size_t pageSize = WTF::pageSize();
203 size = (size + pageSize - 1) & ~(pageSize - 1); // align to page sizes
204 if (size < Chunk::ChunkSize)
205 size = Chunk::ChunkSize;
206 return size;
207 }
208
209 Chunk *allocate(size_t size = 0);
210 void free(Chunk *chunk, size_t size = 0);
211
213};
214
216{
217 size = requiredChunkSize(size);
218 for (auto &m : memorySegments) {
219 if (~m.allocatedMap) {
220 Chunk *c = m.allocate(size);
221 if (c)
222 return c;
223 }
224 }
225
226 // allocate a new segment
227 memorySegments.push_back(MemorySegment(size));
228 Chunk *c = memorySegments.back().allocate(size);
229 Q_ASSERT(c);
230 return c;
231}
232
233void ChunkAllocator::free(Chunk *chunk, size_t size)
234{
235 size = requiredChunkSize(size);
236 for (auto &m : memorySegments) {
237 if (m.contains(chunk)) {
238 m.free(chunk, size);
239 return;
240 }
241 }
242 Q_ASSERT(false);
243}
244
245#ifdef DUMP_SWEEP
247 QString s = QString::number(n, 2);
248 while (s.length() < 64)
249 s.prepend(QChar::fromLatin1('0'));
250 return s;
251}
252#define SDUMP qDebug
253#else
254QString binary(quintptr) { return QString(); }
255#define SDUMP if (1) ; else qDebug
256#endif
257
259{
260 bool hasUsedSlots = false;
261 SDUMP() << "sweeping chunk" << this;
262 HeapItem *o = realBase();
263 bool lastSlotFree = false;
264 for (uint i = 0; i < Chunk::EntriesInBitmap; ++i) {
265 // Snapshots. The destructors below may allocate, and setAllocatedSlots() then
266 // sets object and extends bits in this very word. We have to remember what we
267 // decided to free, so that we can clear exactly that further down rather than
268 // assigning the words wholesale and dropping those bits again.
271
273 Q_ASSERT((toFree & objectBitmap[i]) == toFree); // check all black objects are marked as being used
275 SDUMP() << " index=" << i;
276 SDUMP() << " toFree =" << binary(toFree);
277 SDUMP() << " black =" << binary(blackBitmap[i]);
278 SDUMP() << " object =" << binary(objectBitmap[i]);
279 SDUMP() << " extends =" << binary(e);
280 if (lastSlotFree)
281 e &= (e + 1); // clear all lowest extent bits
282 while (toFree) {
284 quintptr bit = (static_cast<quintptr>(1) << index);
285
286 toFree ^= bit; // mask out freed slot
287
288 // remove all extends slots that have been freed
289 // this is a bit of bit trickery.
290 quintptr mask = (bit << 1) - 1; // create a mask of 1's to the right of and up to the current bit
291 quintptr objmask = e | mask; // or'ing mask with e gives all ones until the end of the current object
292 quintptr result = objmask + 1;
293 Q_ASSERT(qCountTrailingZeroBits(result) - index != 0); // ensure we freed something
294 result |= mask; // ensure we don't clear stuff to the right of the current object
295 e &= result;
296
298 Heap::Base *b = *itemToFree;
299 const VTable *v = b->internalClass->vtable;
300// if (Q_UNLIKELY(classCountPtr))
301// classCountPtr(v->className);
302 if (v->destroy)
303 v->destroy(b);
304#ifdef V4_USE_HEAPTRACK
306#endif
307 }
314 hasUsedSlots |= (objectBitmap[i] != 0);
315 lastSlotFree = !((objectBitmap[i]|extendsBitmap[i]) >> (sizeof(quintptr)*8 - 1));
316 SDUMP() << " new extends =" << binary(e);
317 SDUMP() << " lastSlotFree" << lastSlotFree;
319 o += Chunk::Bits;
320 }
321 return hasUsedSlots;
322}
323
325{
326 HeapItem *o = realBase();
327 for (uint i = 0; i < Chunk::EntriesInBitmap; ++i) {
330 while (toFree) {
332 quintptr bit = (static_cast<quintptr>(1) << index);
333
334 toFree ^= bit; // mask out freed slot
335
336 // remove all extends slots that have been freed
337 // this is a bit of bit trickery.
338 quintptr mask = (bit << 1) - 1; // create a mask of 1's to the right of and up to the current bit
339 quintptr objmask = e | mask; // or'ing mask with e gives all ones until the end of the current object
340 quintptr result = objmask + 1;
341 Q_ASSERT(qCountTrailingZeroBits(result) - index != 0); // ensure we freed something
342 result |= mask; // ensure we don't clear stuff to the right of the current object
343 e &= result;
344
346 Heap::Base *b = *itemToFree;
349#ifdef V4_USE_HEAPTRACK
351#endif
352 }
355 objectBitmap[i] = 0;
356 extendsBitmap[i] = e;
357 o += Chunk::Bits;
358 }
359}
360
362{
363 memset(blackBitmap, 0, sizeof(blackBitmap));
364}
365
367{
369#if QT_POINTER_SIZE == 8
370 const int start = 0;
371#else
372 const int start = 1;
373#endif
374 uint freeSlots = 0;
376
377 for (int i = start; i < EntriesInBitmap; ++i) {
379#if QT_POINTER_SIZE == 8
380 if (!i)
381 usedSlots |= (static_cast<quintptr>(1) << (HeaderSize/SlotSize)) - 1;
382#endif
384 while (1) {
386 if (index == Bits)
387 break;
389 usedSlots &= ~((static_cast<quintptr>(1) << index) - 1);
390 while (!usedSlots) {
391 if (++i < EntriesInBitmap) {
393 } else {
395 // Overflows to 0 when counting trailing zeroes above in next iteration.
396 // Then, all the bits are zeroes and we break.
398 break;
399 }
401 }
403
405 usedSlots |= (quintptr(1) << index) - 1;
406 uint freeEnd = i*Bits + index;
408 freeSlots += nSlots;
411 uint bin = qMin(nBins - 1, nSlots);
413 bins[bin] = freeItem;
414 }
415 }
417}
418
420 Q_ASSERT((size % Chunk::SlotSize) == 0);
422
423 if (allocationStats)
425
426 HeapItem **last;
427
428 HeapItem *m;
429
430 if (slotsRequired < NumBins - 1) {
432 if (m) {
434 goto done;
435 }
436 }
437
438 if (nFree >= slotsRequired) {
439 // use bump allocation
441 m = nextFree;
444 goto done;
445 }
446
447 // search last bin for a large enough item
448 last = &freeBins[NumBins - 1];
449 while ((m = *last)) {
451 *last = m->freeData.next; // take it out of the list
452
454 if (remainingSlots == 0)
455 goto done;
456
458 if (remainingSlots > nFree) {
459 if (nFree) {
464 }
467 } else {
472 }
473 goto done;
474 }
475 last = &m->freeData.next;
476 }
477
478 if (slotsRequired < NumBins - 1) {
479 // check if we can split up another slot
480 for (size_t i = slotsRequired + 1; i < NumBins - 1; ++i) {
481 m = freeBins[i];
482 if (m) {
483 freeBins[i] = m->freeData.next; // take it out of the list
490 goto done;
491 }
492 }
493 }
494
495 if (!m) {
496 if (!forceAllocation)
497 return nullptr;
498 if (nFree) {
499 // Save any remaining slots of the current chunk
500 // for later, smaller allocations.
505 }
511 m = nextFree;
514 }
515
516done:
519#ifdef V4_USE_HEAPTRACK
521#endif
522 return m;
523}
524
526{
527 const auto firstEmptyChunkPos = partition(chunks, [this](const std::size_t i) {
528 return chunks.at(i)->sweep(engine);
529 });
531
532 nextFree = nullptr;
533 nFree = 0;
534 memset(freeBins, 0, sizeof(freeBins));
535
537
541 });
542
543 // only free the chunks at the end to avoid that the sweep() calls indirectly
544 // access freed memory
548 });
549
551}
552
554{
555 for (auto c : chunks)
556 c->freeAll(engine);
557 for (auto c : chunks) {
560 }
561}
562
564{
565 for (auto c : chunks)
566 c->resetBlackBits();
567}
568
570 MemorySegment *m = nullptr;
571 Chunk *c = nullptr;
572 if (size >= MemorySegment::SegmentSize/2) {
573 // too large to handle through the ChunkAllocator, let's get our own memory segement
574 size += Chunk::HeaderSize; // space required for the Chunk header
576 size = (size + pageSize - 1) & ~(pageSize - 1); // align to page sizes
577 m = new MemorySegment(size);
578 c = m->allocate(size);
579 } else {
581 }
582 Q_ASSERT(c);
586#ifdef V4_USE_HEAPTRACK
588#endif
589 return c->first();
590}
591
592static void freeHugeChunk(ChunkAllocator *chunkAllocator, const HugeItemAllocator::HugeChunk &c)
593{
594 HeapItem *itemToFree = c.chunk->first();
595 Heap::Base *b = *itemToFree;
596 const VTable *v = b->internalClass->vtable;
597
598 if (v->destroy)
599 v->destroy(b);
600 if (c.segment) {
601 // own memory segment
602 c.segment->free(c.chunk, c.size);
603 delete c.segment;
604 } else {
605 chunkAllocator->free(c.chunk, c.size);
606 }
607#ifdef V4_USE_HEAPTRACK
608 heaptrack_report_free(c.chunk);
609#endif
610}
611
613{
614 auto isBlack = [this] (const HugeChunk &c) {
615 bool b = c.chunk->first()->isBlack();
617 if (!b) {
620 }
621 return !b;
622 };
623
626}
627
629{
630 for (auto c : chunks)
632}
633
641
642namespace {
643using ExtraData = GCStateInfo::ExtraData;
644GCState markStart(GCStateMachine *that, ExtraData &)
645{
646 //Initialize the mark stack
647 that->mm->m_markStack = std::make_unique<MarkStack>(that->mm->engine);
648 that->mm->engine->isGCOngoing = true;
649 return GCState::MarkGlobalObject;
650}
651
652GCState markGlobalObject(GCStateMachine *that, ExtraData &)
653{
654 that->mm->engine->markObjects(that->mm->m_markStack.get());
655 return GCState::MarkJSStack;
656}
657
658GCState markJSStack(GCStateMachine *that, ExtraData &)
659{
660 that->mm->collectFromJSStack(that->mm->markStack());
661 return GCState::InitMarkPersistentValues;
662}
663
664GCState initMarkPersistentValues(GCStateMachine *that, ExtraData &stateData)
665{
666 if (!that->mm->m_persistentValues)
667 return GCState::InitMarkWeakValues; // no persistent values to mark
668 stateData = GCIteratorStorage { that->mm->m_persistentValues->begin() };
669 return GCState::MarkPersistentValues;
670}
671
672enum: int {
673 MarkLoopIterationCount = 1024,
674 MarkLoopIterationCountForDrain = 10240,
675};
676
677bool wasDrainNecessary(MarkStack *ms, QDeadlineTimer deadline)
678{
679 if (ms->remainingBeforeSoftLimit() > MarkLoopIterationCount)
680 return false;
681 // drain
682 ms->drain(deadline);
683 return true;
684}
685
686GCState markPersistentValues(GCStateMachine *that, ExtraData &stateData) {
687 auto markStack = that->mm->markStack();
688 if (wasDrainNecessary(markStack, that->deadline) && that->deadline.hasExpired())
689 return GCState::MarkPersistentValues;
690 PersistentValueStorage::Iterator& it = get<GCIteratorStorage>(stateData).it;
691 // avoid repeatedly hitting the timer constantly by batching iterations
692 for (int i = 0; i < MarkLoopIterationCount; ++i) {
693 if (!it.p)
694 return GCState::InitMarkWeakValues;
695 if (Managed *m = (*it).as<Managed>())
696 m->mark(markStack);
697 ++it;
698 }
699 return GCState::MarkPersistentValues;
700}
701
702GCState initMarkWeakValues(GCStateMachine *that, ExtraData &stateData)
703{
704 stateData = GCIteratorStorage { that->mm->m_weakValues->begin() };
705 return GCState::MarkWeakValues;
706}
707
708GCState markWeakValues(GCStateMachine *that, ExtraData &stateData)
709{
710 auto markStack = that->mm->markStack();
711 if (wasDrainNecessary(markStack, that->deadline) && that->deadline.hasExpired())
712 return GCState::MarkWeakValues;
713 PersistentValueStorage::Iterator& it = get<GCIteratorStorage>(stateData).it;
714 // avoid repeatedly hitting the timer constantly by batching iterations
715 for (int i = 0; i < MarkLoopIterationCount; ++i) {
716 if (!it.p)
717 return GCState::MarkDrain;
718 QObjectWrapper *qobjectWrapper = (*it).as<QObjectWrapper>();
719 ++it;
720 if (!qobjectWrapper)
721 continue;
722 QObject *qobject = qobjectWrapper->object();
723 if (!qobject)
724 continue;
725 bool keepAlive = QQmlData::keepAliveDuringGarbageCollection(qobject);
726
727 if (!keepAlive) {
728 if (QObject *parent = qobject->parent()) {
729 while (parent->parent())
730 parent = parent->parent();
731 keepAlive = QQmlData::keepAliveDuringGarbageCollection(parent);
732 }
733 }
734
735 if (keepAlive)
736 qobjectWrapper->mark(that->mm->markStack());
737 }
738 return GCState::MarkWeakValues;
739}
740
741GCState markDrain(GCStateMachine *that, ExtraData &)
742{
743 if (that->deadline.isForever()) {
744 that->mm->markStack()->drain();
745 return GCState::MarkReady;
746 }
747 auto drainState = that->mm->m_markStack->drain(that->deadline);
748 return drainState == MarkStack::DrainState::Complete
749 ? GCState::MarkReady
750 : GCState::MarkDrain;
751}
752
753GCState markReady(GCStateMachine *that, ExtraData &)
754{
755 auto isIncrementalRun = [](GCStateMachine* that){
756 return !that->mm->aggressiveGC && that->timeLimit.count() > 0;
757 };
758
759 if (that->mm->crossValidateIncrementalGC && isIncrementalRun(that))
760 return GCState::CrossValidateIncrementalMarkPhase;
761 return GCState::InitCallDestroyObjects;
762}
763
764GCState crossValidateIncrementalMarkPhase(GCStateMachine *that, ExtraData &)
765{
766 struct {
767 Chunk* operator()(Chunk* chunk) { return chunk; }
768 Chunk* operator()(const HugeItemAllocator::HugeChunk& chunk) { return chunk.chunk; }
769 } getChunk{};
770
771 auto takeBlackBitmap = [&getChunk](auto& allocator, std::vector<quintptr>& storage){
772 for (auto chunk : allocator.chunks) {
773 for (auto& bitmap : getChunk(chunk)->blackBitmap) {
774 storage.push_back(bitmap);
775 }
776 getChunk(chunk)->resetBlackBits();
777 }
778 };
779
780 auto runMarkPhase = [](GCStateMachine* that) {
781 that->reset();
782 that->mm->m_markStack.reset();
783
784 while (that->state != GCStateMachine::MarkReady) {
785 GCStateInfo& stateInfo = that->stateInfoMap[int(that->state)];
786 that->state = stateInfo.execute(that, that->stateData);
787 }
788 };
789
790 auto checkBlackBitmap = [&that, &getChunk](auto& allocator, const std::vector<quintptr>& storedBitmap) {
791 auto reportError = [&allocator, &getChunk, &that](std::size_t chunk_index, std::size_t bitmap_index, uint bit_index){
792 #ifdef QT_BUILD_INTERNAL
793 // If we're collecting errors, don't output the debug message.
794 if (auto errors = that->bitmapErrors) {
795 errors->emplace_back(chunk_index, bitmap_index, bit_index);
796 return;
797 }
798 #endif
799
800 Q_UNUSED(that);
801 auto object = reinterpret_cast<Heap::Base*>(getChunk(allocator.chunks[chunk_index])->realBase() + (bit_index + (bitmap_index*Chunk::Bits)));
802 qDebug() << "Cross Validation Error on chunk" << chunk_index
803 << "on bitmap piece" << bitmap_index << "and bit" << bit_index
804 << ((object->internalClass) ? "With type" : "")
805 << ((object->internalClass) ?
806 Managed::typeToString(Managed::Type(object->internalClass->vtable->type)) : QString());
807 };
808
809 auto original = storedBitmap.begin();
810 for (std::size_t chunk_index = 0; original != storedBitmap.end() && chunk_index < allocator.chunks.size(); ++chunk_index) {
811 for (std::size_t bitmap_index = 0; bitmap_index < Chunk::EntriesInBitmap; ++bitmap_index) {
812 if (auto differences = (~(*original)) & getChunk(allocator.chunks[chunk_index])->blackBitmap[bitmap_index]) {
813 while (differences != 0) {
814 uint bit_index = qCountTrailingZeroBits(differences);
815 reportError(chunk_index, bitmap_index, bit_index);
816 differences ^= quintptr{1} << bit_index;
817 }
818 }
819 ++original;
820 }
821 }
822 };
823
824 #ifdef QT_BUILD_INTERNAL
825 if (auto *errors = that->bitmapErrors)
826 errors->clear();
827 #endif
828
829 std::vector<quintptr> blockBitmap{};
830 blockBitmap.reserve(Chunk::EntriesInBitmap * that->mm->blockAllocator.chunks.size());
831 takeBlackBitmap(that->mm->blockAllocator, blockBitmap);
832
833 std::vector<quintptr> hugeItemBitmap{};
834 hugeItemBitmap.reserve(Chunk::EntriesInBitmap * that->mm->hugeItemAllocator.chunks.size());
835 takeBlackBitmap(that->mm->hugeItemAllocator, hugeItemBitmap);
836
837 std::vector<quintptr> internalClassBitmap{};
838 internalClassBitmap.reserve(Chunk::EntriesInBitmap * that->mm->icAllocator.chunks.size());
839 takeBlackBitmap(that->mm->icAllocator, internalClassBitmap);
840
841 runMarkPhase(that);
842
843 checkBlackBitmap(that->mm->blockAllocator, blockBitmap);
844 checkBlackBitmap(that->mm->hugeItemAllocator, hugeItemBitmap);
845 checkBlackBitmap(that->mm->icAllocator, internalClassBitmap);
846
847 return GCState::InitCallDestroyObjects;
848}
849
850/** \!internal
851collects new references from the stack, then drains the mark stack again
852*/
853void redrain(GCStateMachine *that)
854{
855 that->mm->collectFromJSStack(that->mm->markStack());
856 that->mm->m_markStack->drain();
857}
858
859GCState initCallDestroyObjects(GCStateMachine *that, ExtraData &stateData)
860{
861 // as we don't have a deletion barrier, we need to rescan the stack
862 redrain(that);
863 if (!that->mm->m_weakValues)
864 return GCState::FreeWeakMaps; // no need to call destroy objects
865 stateData = GCIteratorStorage { that->mm->m_weakValues->begin() };
866 return GCState::CallDestroyObjects;
867}
868GCState callDestroyObject(GCStateMachine *that, ExtraData &stateData)
869{
870 PersistentValueStorage::Iterator& it = get<GCIteratorStorage>(stateData).it;
871 // destroyObject might call user code, which really shouldn't call back into the gc
872 auto oldState = std::exchange(that->mm->gcBlocked, QV4::MemoryManager::Blockness::InCriticalSection);
873 auto cleanup = qScopeGuard([&]() {
874 that->mm->gcBlocked = oldState;
875 });
876 // avoid repeatedly hitting the timer constantly by batching iterations
877 for (int i = 0; i < MarkLoopIterationCount; ++i) {
878 if (!it.p)
879 return GCState::FreeWeakMaps;
880 Managed *m = (*it).managed();
881 ++it;
882 if (!m || m->markBit())
883 continue;
884 // we need to call destroyObject on qobjectwrappers now, so that they can emit the destroyed
885 // signal before we start sweeping the heap
886 if (QObjectWrapper *qobjectWrapper = m->as<QObjectWrapper>())
887 qobjectWrapper->destroyObject(/*lastSweep =*/false);
888 }
889 return GCState::CallDestroyObjects;
890}
891
892void freeWeakMaps(MemoryManager *mm)
893{
894 for (auto [map, lastMap] = std::tuple {mm->weakMaps, &mm->weakMaps }; map; map = map->nextWeakMap) {
895 if (!map->isMarked())
896 continue;
897 map->removeUnmarkedKeys();
898 *lastMap = map;
899 lastMap = &map->nextWeakMap;
900 }
901}
902
903GCState freeWeakMaps(GCStateMachine *that, ExtraData &)
904{
905 freeWeakMaps(that->mm);
906 return GCState::FreeWeakSets;
907}
908
909void freeWeakSets(MemoryManager *mm)
910{
911 for (auto [set, lastSet] = std::tuple {mm->weakSets, &mm->weakSets}; set; set = set->nextWeakSet) {
912
913 if (!set->isMarked())
914 continue;
915 set->removeUnmarkedKeys();
916 *lastSet = set;
917 lastSet = &set->nextWeakSet;
918 }
919}
920
921GCState freeWeakSets(GCStateMachine *that, ExtraData &)
922{
923 freeWeakSets(that->mm);
924 return GCState::HandleQObjectWrappers;
925}
926
927GCState handleQObjectWrappers(GCStateMachine *that, ExtraData &)
928{
929 that->mm->cleanupDeletedQObjectWrappersInSweep();
930 return GCState::DoSweep;
931}
932
933GCState doSweep(GCStateMachine *that, ExtraData &)
934{
935 auto mm = that->mm;
936
937 mm->engine->identifierTable->sweep();
938 mm->blockAllocator.sweep();
939 mm->hugeItemAllocator.sweep();
940 mm->icAllocator.sweep();
941
942 // reset all black bits
943 mm->blockAllocator.resetBlackBits();
944 mm->hugeItemAllocator.resetBlackBits();
945 mm->icAllocator.resetBlackBits();
946
947 mm->usedSlotsAfterLastFullSweep = mm->blockAllocator.usedSlotsAfterLastSweep + mm->icAllocator.usedSlotsAfterLastSweep;
948 mm->gcBlocked = MemoryManager::Unblocked;
949 mm->m_markStack.reset();
950 mm->engine->isGCOngoing = false;
951
952 mm->updateUnmanagedHeapSizeGCLimit();
953
954 return GCState::Invalid;
955}
956
957}
958
959
961 : engine(engine)
969 , aggressiveGC(!qEnvironmentVariableIsEmpty("QV4_MM_AGGRESSIVE_GC"))
970 , crossValidateIncrementalGC(qEnvironmentVariableIsSet("QV4_MM_CROSS_VALIDATE_INCREMENTAL_GC"))
975{
976#ifdef V4_USE_VALGRIND
977 VALGRIND_CREATE_MEMPOOL(this, 0, true);
978#endif
979 if (statistics) {
982 }
983
985 gcStateMachine->mm = this;
986
988 markStart,
989 false,
990 };
993 false,
994 };
997 false,
998 };
1001 false,
1002 };
1005 false,
1006 };
1009 false,
1010 };
1013 false,
1014 };
1016 markDrain,
1017 false,
1018 };
1020 markReady,
1021 false,
1022 };
1025 false,
1026 };
1029 false,
1030 };
1033 false,
1034 };
1037 false,
1038 };
1041 true, // ensure that handleQObjectWrappers runs in isolation
1042 };
1045 false,
1046 };
1048 doSweep,
1049 false,
1050 };
1051}
1052
1066
1068{
1069#ifdef MM_STATS
1072#endif
1073
1075 Q_ASSERT(size % Chunk::SlotSize == 0);
1076
1078 memset(m, 0, size);
1079 return *m;
1080}
1081
1083{
1085 Q_ASSERT(!(size % sizeof(HeapItem)));
1086
1087 Heap::Object *o;
1089 o = static_cast<Heap::Object *>(allocData(size));
1090 } else {
1091 // Allocate both in one go through the block allocator
1093 std::size_t memberSize = align(sizeof(Heap::MemberData) + (nMembers - 1)*sizeof(Value));
1095 Heap::MemberData *m;
1096 if (totalSize > Chunk::DataSize) {
1097 o = static_cast<Heap::Object *>(allocData(size));
1099 } else {
1100 HeapItem *mh = reinterpret_cast<HeapItem *>(allocData(totalSize));
1101 Heap::Base *b = *mh;
1102 o = static_cast<Heap::Object *>(b);
1103 mh += (size >> Chunk::SlotSizeShift);
1104 m = mh->as<Heap::MemberData>();
1105 Chunk *c = mh->chunk();
1106 size_t index = mh - c->realBase();
1109 }
1111 o->memberData.set(engine, m);
1113 m->values.alloc = static_cast<uint>((memberSize - sizeof(Heap::MemberData) + sizeof(Value))/sizeof(Value));
1115 m->init();
1116 }
1117
1118 return o;
1119}
1120
1122 : m_engine(engine)
1123{
1124 m_base = (Heap::Base **)engine->gcStack->base();
1125 m_top = m_base;
1126 const size_t size = engine->maxGCStackSize() / sizeof(Heap::Base);
1128 m_softLimit = m_base + size * 3 / 4;
1129}
1130
1132{
1133 // we're not calling drain(QDeadlineTimer::Forever) as that has higher overhead
1134 while (m_top > m_base) {
1135 Heap::Base *h = pop();
1136 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.
1139 }
1140}
1141
1143{
1144 do {
1145 for (int i = 0; i <= MarkLoopIterationCountForDrain; ++i) {
1146 if (m_top == m_base)
1147 return DrainState::Complete;
1148 Heap::Base *h = pop();
1149 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.
1152 }
1153 } while (!deadline.hasExpired());
1154 return DrainState::Ongoing;
1155}
1156
1162
1164{
1165 if (engine->inShutdown)
1166 return;
1169 onEventLoop();
1170 }, Qt::QueuedConnection);
1171 return;
1172 }
1173 if (!gcStateMachine->inProgress())
1174 return;
1175
1176 if (collectorStatistics) {
1178 if (!gcStateMachine->inProgress())
1179 collectorStatistics->end(this);
1180 } else {
1182 }
1183}
1184
1185
1190
1192{
1193
1195 Managed *m = (*it).managed();
1196 if (!m || m->markBit())
1197 continue;
1198 // we need to call destroyObject on qobjectwrappers now, so that they can emit the destroyed
1199 // signal before we start sweeping the heap
1202 }
1203 }
1204
1205 freeWeakMaps(this);
1206 freeWeakSets(this);
1207
1209
1210 if (!lastSweep) {
1212 blockAllocator.sweep(/*classCountPtr*/);
1214 icAllocator.sweep(/*classCountPtr*/);
1215 }
1216
1217 // reset all black bits
1221
1225}
1226
1227/*
1228 \internal
1229 Helper function used in sweep to clean up the (to-be-freed) QObjectWrapper
1230 Used both in MemoryManager::sweep, and the corresponding gc statemachine phase
1231*/
1233{
1234 // onDestruction handlers may have accessed other QObject wrappers and reset their value, so ensure
1235 // that they are all set to undefined.
1237 Managed *m = (*it).managed();
1238 if (!m || m->markBit())
1239 continue;
1240 (*it) = Value::undefinedValue();
1241 }
1242
1243 // Now it is time to free QV4::QObjectWrapper Value, we must check the Value's tag to make sure its object has been destroyed
1245 if (pendingCount) {
1248 for (int i = 0; i < pendingCount; ++i) {
1250 if (v->isUndefined() || v->isEmpty())
1252 else
1254 }
1256 }
1257
1260 if (it.value().isNullOrUndefined())
1262 else
1263 ++it;
1264 }
1265 }
1266}
1267
1268bool MemoryManager::shouldRunGC() const
1269{
1272 return true;
1273 return false;
1274}
1275
1276static size_t dumpBins(BlockAllocator *b, const char *title)
1277{
1278 const QLoggingCategory &stats = lcGcAllocatorStats();
1279 size_t totalSlotMem = 0;
1280 if (title)
1281 qDebug(stats) << "Slot map for" << title << "allocator:";
1282 for (uint i = 0; i < BlockAllocator::NumBins; ++i) {
1283 uint nEntries = 0;
1284 HeapItem *h = b->freeBins[i];
1285 while (h) {
1286 ++nEntries;
1287 totalSlotMem += h->freeData.availableSlots;
1288 h = h->freeData.next;
1289 }
1290 if (title)
1291 qDebug(stats) << " number of entries in slot" << i << ":" << nEntries;
1292 }
1293 SDUMP() << " large slot map";
1294 HeapItem *h = b->freeBins[BlockAllocator::NumBins - 1];
1295 while (h) {
1296 SDUMP() << " " << Qt::hex << (quintptr(h)/32) << h->freeData.availableSlots;
1297 h = h->freeData.next;
1298 }
1299
1300 if (title)
1301 qDebug(stats) << " total mem in bins" << totalSlotMem*Chunk::SlotSize;
1302 return totalSlotMem*Chunk::SlotSize;
1303}
1304
1305/*!
1306 \internal
1307 Precondition: Incremental garbage collection must be currently active
1308 Finishes incremental garbage collection, unless in a critical section
1309 Code entering a critical section is expected to check if we need to
1310 force a gc completion, and to trigger the gc again if necessary
1311 when exiting the critcial section.
1312 Returns \c true if the gc cycle completed, false otherwise.
1313 */
1315{
1318 << "Tried to force the GC to complete a run but failed due to being in a critical section.";
1319 return false;
1320 }
1321
1322 const bool incrementalGCIsAlreadyRunning = m_markStack != nullptr;
1324
1325 qCDebug(lcGcForcedRuns) << "Forcing the GC to complete a run.";
1326
1328 if (collectorStatistics) {
1329 while (gcStateMachine->inProgress())
1331 collectorStatistics->end(this);
1332 } else {
1333 while (gcStateMachine->inProgress())
1335 }
1336
1338 return true;
1339}
1340
1342{
1343 runGC();
1344 if (m_markStack != nullptr)
1346}
1347
1349{
1350 if (gcBlocked != Unblocked) {
1351 return;
1352 }
1353
1355
1356 if (statistics) {
1361 }
1362
1363 if (collectorStatistics) {
1364 if (!gcStateMachine->inProgress())
1367 if (!gcStateMachine->inProgress())
1368 collectorStatistics->end(this);
1369 } else {
1371 }
1372
1373
1374 if (statistics) {
1377 }
1378}
1379
1384
1389
1394
1396{
1398 // more than 75% full, raise limit
1400 unmanagedHeapSize) * 2;
1401 } else if (unmanagedHeapSize * 4 <= unmanagedHeapSizeGCLimit) {
1402 // less than 25% full, lower limit
1405 }
1406
1407 if (aggressiveGC && !engine->inShutdown) {
1408 // ensure we don't 'loose' any memory
1409 // but not during shutdown, because than we skip parts of sweep
1410 // and use freeAll instead
1412 == blockAllocator.usedMem() + dumpBins(&blockAllocator, nullptr));
1414 == icAllocator.usedMem() + dumpBins(&icAllocator, nullptr));
1415 }
1416}
1417
1423
1429
1431{
1432 delete m_persistentValues;
1433 dumpStats();
1434
1435 // do one last non-incremental sweep to clean up C++ objects
1436 // first, abort any on-going incremental gc operation
1437 setGCTimeLimit(-1);
1438 if (engine->isGCOngoing) {
1439 engine->isGCOngoing = false;
1440 // Aborting the incremental run drops the mark stack, so we must also clear the
1441 // blocked state. Otherwise the final sweep below runs destruction handlers with
1442 // gcBlocked still set, and any GCCriticalSection they open would try to mark
1443 // through the now-absent mark stack.
1450 }
1451 // then sweep
1452 sweep(/*lastSweep*/true);
1453
1457
1458 delete m_weakValues;
1459#ifdef V4_USE_VALGRIND
1461#endif
1462 delete chunkAllocator;
1463}
1464
1465
1467{
1468 if (!statistics)
1469 return;
1470
1471 const QLoggingCategory &stats = lcGcStats();
1472 qDebug(stats) << "Qml GC memory allocation statistics:";
1473 qDebug(stats) << "Total memory allocated:" << statistics->maxAllocatedMem;
1474 qDebug(stats) << "Max memory used before a GC run:" << statistics->maxUsedBeforeGC;
1475 qDebug(stats) << "Max memory used after a GC run:" << statistics->maxUsedAfterGC;
1476 qDebug(stats) << "Requests for different item sizes:";
1477 for (int i = 1; i < BlockAllocator::NumBins - 1; ++i)
1478 qDebug(stats) << " <" << (i << Chunk::SlotSizeShift) << " bytes: " << statistics->allocations[i];
1479 qDebug(stats) << " >=" << ((BlockAllocator::NumBins - 1) << Chunk::SlotSizeShift) << " bytes: " << statistics->allocations[BlockAllocator::NumBins - 1];
1480}
1481
1483{
1486 while (v < top) {
1487 Managed *m = v->managed();
1488 if (m) {
1489 Q_ASSERT(m->inUse());
1490 // Skip pointers to already freed objects, they are bogus as well
1491 m->mark(markStack);
1492 }
1493 ++v;
1494 }
1495
1496 for (auto *frame = engine->currentStackFrame; frame; frame = frame->parentFrame()) {
1497 if (!frame->isMetaTypesFrame())
1498 continue;
1499
1501 = static_cast<const MetaTypesStackFrame *>(frame)->locals()) {
1502 // Actual AOT-compiled functions initialize the locals firsth thing when they
1503 // are called. However, the ScopedStackFrame has no locals, but still uses a
1504 // MetaTypesStackFrame.
1506 }
1507 }
1508}
1509
1512{
1513 // base assumption: target 60fps, use at most 1/3 of time for gc
1514 // unless overridden by env variable
1515 bool ok = false;
1516 auto envTimeLimit = qEnvironmentVariableIntValue("QV4_GC_TIMELIMIT", &ok );
1517 if (!ok)
1518 envTimeLimit = (1000 / 60) / 3;
1519 if (envTimeLimit > 0)
1521 else
1522 timeLimit = std::chrono::milliseconds { 0 };
1523}
1524
1525static void logStepTiming(GCStateMachine* that, quint64 timing) {
1526 auto registerTimingWithResetOnOverflow = [](
1527 GCStateMachine::StepTiming& storage, quint64 timing, GCState state
1528 ) {
1529 auto wouldOverflow = [](quint64 lhs, quint64 rhs) {
1530 return rhs > 0 && lhs > std::numeric_limits<quint64>::max() - rhs;
1531 };
1532
1533 if (wouldOverflow(storage.rolling_sum, timing) || wouldOverflow(storage.count, 1)) {
1534 qDebug(lcGcStepExecution) << "Resetting timings storage for"
1535 << QMetaEnum::fromType<GCState>().key(state) << "due to overflow.";
1536 storage.rolling_sum = timing;
1537 storage.count = 1;
1538 } else {
1539 storage.rolling_sum += timing;
1540 storage.count += 1;
1541 }
1542 };
1543
1544 GCStateMachine::StepTiming& storage = that->executionTiming[that->state];
1545 registerTimingWithResetOnOverflow(storage, timing, that->state);
1546
1547 qDebug(lcGcStepExecution) << "Performed" << QMetaEnum::fromType<GCState>().key(that->state)
1548 << "in" << timing << "microseconds";
1549 qDebug(lcGcStepExecution) << "This step was performed" << storage.count << " time(s), executing in"
1550 << (storage.rolling_sum / storage.count) << "microseconds on average.";
1551}
1552
1553static GCState executeWithLoggingIfEnabled(GCStateMachine* that, GCStateInfo& stateInfo) {
1554 if (!that->collectTimings)
1555 return stateInfo.execute(that, that->stateData);
1556
1557 QElapsedTimer timer;
1558 timer.start();
1559 GCState next = stateInfo.execute(that, that->stateData);
1560 logStepTiming(that, timer.nsecsElapsed()/1000);
1561 return next;
1562}
1563
1564static void redrainDuringSweep(GCStateMachine *that)
1565{
1566 if (that->state > GCState::InitCallDestroyObjects) {
1567 /* initCallDestroyObjects is the last action which drains the mark
1568 stack by default. But as our write-barrier might end up putting
1569 objects on the markStack which still reference other objects.
1570 Especially when we call user code triggered by Component.onDestruction,
1571 but also when we run into a timeout.
1572 We don't redrain before InitCallDestroyObjects, as that would
1573 potentially lead to useless busy-work (e.g., if the last referencs
1574 to objects are removed while the mark phase is running)
1575 */
1576 redrain(that);
1577 }
1578}
1579
1581 if (timeLimit.count() > 0) {
1583 bool deadlineExpired = false;
1584 do {
1585 redrainDuringSweep(this);
1586 qCDebug(lcGcStateTransitions) << "Preparing to execute the"
1587 << QMetaEnum::fromType<GCState>().key(state) << "state";
1590 qCDebug(lcGcStateTransitions) << "Transitioning to the"
1591 << QMetaEnum::fromType<GCState>().key(state) << "state";
1593 break;
1594 } while (!(deadlineExpired = deadline.hasExpired()) && state != GCState::Invalid);
1595 if (deadlineExpired)
1597 if (state != GCState::Invalid)
1599 mm->onEventLoop();
1600 }, Qt::QueuedConnection);
1601 } else {
1603 while (state != GCState::Invalid) {
1604 redrainDuringSweep(this);
1605 qCDebug(lcGcStateTransitions) << "Preparing to execute the"
1606 << QMetaEnum::fromType<GCState>().key(state) << "state";
1609 qCDebug(lcGcStateTransitions) << "Transitioning to the"
1610 << QMetaEnum::fromType<GCState>().key(state) << "state";
1611 }
1612 }
1613}
1614
1627
1629{
1631
1637
1638 qDebug(stats) << "========== GC ==========";
1639#ifdef MM_STATS
1640 qDebug(stats) << " Triggered by alloc request of" << mm->lastAllocRequestedSlots << "slots.";
1641 qDebug(stats) << " Allocations since last GC" << mm->allocationCount;
1642 mm->allocationCount = 0;
1643#endif
1645 qDebug(stats) << "Allocated" << allocatedMem << "bytes in" << oldChunks << "chunks";
1646 qDebug(stats) << "Fragmented memory before GC" << (allocatedMem - regularItemsBefore);
1647 dumpBins(&mm->blockAllocator, "Block");
1648 dumpBins(&mm->icAllocator, "InternalClass");
1649}
1650
1658
1660{
1662
1665
1667 qDebug(stats) << "triggered by unmanaged heap:";
1668 qDebug(stats) << " old unmanaged heap size:" << oldUnmanagedSize;
1669 qDebug(stats) << " new unmanaged heap:" << mm->unmanagedHeapSize;
1670 qDebug(stats) << " unmanaged heap limit:" << mm->unmanagedHeapSizeGCLimit;
1671 }
1672 const size_t memInBins = dumpBins(&mm->blockAllocator, "Block")
1673 + dumpBins(&mm->icAllocator, "InternalClasss");
1674 qDebug(stats) << "Garbage collection took" << (gcTime / 1000) << "us.";
1675
1676 qDebug(stats) << "Regular item memory before GC:" << regularItemsBefore;
1677 qDebug(stats) << "Regular item memory after GC:" << regularItemsAfter;
1678 qDebug(stats) << "Freed up bytes :" << (regularItemsBefore - regularItemsAfter);
1679 qDebug(stats) << "Freed up chunks :" << (oldChunks - mm->blockAllocator.chunks.size());
1682 if (lost)
1683 qDebug(stats) << "!!!!!!!!!!!!!!!!!!!!! LOST MEM:" << lost << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!";
1685 qDebug(stats) << "Large item memory before GC:" << largeItemsBefore;
1686 qDebug(stats) << "Large item memory after GC:" << largeItemsAfter;
1687 qDebug(stats) << "Large item memory freed up:" << (largeItemsBefore - largeItemsAfter);
1688 }
1689
1690 qDebug(stats) << "======== End GC ========";
1691}
1692
1693} // namespace QV4
1694
1695QT_END_NAMESPACE
1696
1697#include "moc_qv4mm_p.cpp"
Definition qjsvalue.h:24
static void logStepTiming(GCStateMachine *that, quint64 timing)
Definition qv4mm.cpp:1525
static size_t dumpBins(BlockAllocator *b, const char *title)
Definition qv4mm.cpp:1276
QString binary(quintptr)
Definition qv4mm.cpp:254
@ MinSlotsGCLimit
Definition qv4mm.cpp:74
@ GCOverallocation
Definition qv4mm.cpp:75
static void freeHugeChunk(ChunkAllocator *chunkAllocator, const HugeItemAllocator::HugeChunk &c)
Definition qv4mm.cpp:592
static GCState executeWithLoggingIfEnabled(GCStateMachine *that, GCStateInfo &stateInfo)
Definition qv4mm.cpp:1553
static void redrainDuringSweep(GCStateMachine *that)
Definition qv4mm.cpp:1564
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)
#define SDUMP
Definition qv4mm.cpp:255
#define MM_STATS
Definition qv4mm.cpp:35
void free(Chunk *chunk, size_t size=0)
Definition qv4mm.cpp:233
size_t requiredChunkSize(size_t size)
Definition qv4mm.cpp:200
Chunk * allocate(size_t size=0)
Definition qv4mm.cpp:215
std::vector< MemorySegment > memorySegments
Definition qv4mm.cpp:212
void free(Chunk *chunk, size_t size)
Definition qv4mm.cpp:131
Chunk * allocate(size_t size)
Definition qv4mm.cpp:164
MemorySegment(size_t size)
Definition qv4mm.cpp:88
PageReservation pageReservation
Definition qv4mm.cpp:157
void setBit(size_t index)
Definition qv4mm.cpp:114
size_t availableBytes
Definition qv4mm.cpp:160
bool testBit(size_t index) const
Definition qv4mm.cpp:124
quint64 allocatedMap
Definition qv4mm.cpp:159
void clearBit(size_t index)
Definition qv4mm.cpp:119
bool contains(Chunk *c) const
Definition qv4mm.cpp:153
MemorySegment(MemorySegment &&other)
Definition qv4mm.cpp:101