aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFabian Kosmale <fabian.kosmale@qt.io>2023-11-21 19:36:26 +0100
committerFabian Kosmale <fabian.kosmale@qt.io>2023-12-20 08:38:26 +0100
commitb9d37a328ba09bcb2a7a95b5778cb8c63d0ace26 (patch)
tree340739253c3e15f0b6c051434d94061e8e3385c6
parentd08ede57dd530a67c3420b3858fe39bf1e5eb598 (diff)
Long live incremental garbage collection in QML!
The design of the garbage collector is described in src/qml/memory/design.md. The gc and gcdone test helpers are adjusted to drive the gc to completion, even when in incremental mode. Parts of tst_qv4mm and tst_qqmlqt need to run with the incremental gc disabled, as they call gc inside QML and assumes that the GC finishes before returning. Initial-patch-by: Rafal Chomentowski <rafal.chomentowski@ge.com> Task-number: QTBUG-119274 Change-Id: I1d94f41bc7a434fad67de0fd46454b6db285f2eb Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
-rw-r--r--src/qml/CMakeLists.txt2
-rw-r--r--src/qml/jsapi/qjsengine.cpp1
-rw-r--r--src/qml/jsapi/qjsvalue_p.h12
-rw-r--r--src/qml/jsruntime/qv4enginebase_p.h3
-rw-r--r--src/qml/jsruntime/qv4identifiertable.cpp5
-rw-r--r--src/qml/jsruntime/qv4internalclass.cpp14
-rw-r--r--src/qml/jsruntime/qv4persistent.cpp18
-rw-r--r--src/qml/jsruntime/qv4propertykey.cpp1
-rw-r--r--src/qml/jsruntime/qv4propertykey_p.h12
-rw-r--r--src/qml/jsruntime/qv4symbol.cpp2
-rw-r--r--src/qml/memory/design.md131
-rw-r--r--src/qml/memory/qv4mm.cpp346
-rw-r--r--src/qml/memory/qv4mm_p.h85
-rw-r--r--src/qml/memory/qv4mmdefs_p.h17
-rw-r--r--src/qml/memory/qv4writebarrier.cpp36
-rw-r--r--src/qml/memory/qv4writebarrier_p.h77
-rw-r--r--src/qml/qml/qqmlbuiltinfunctions.cpp8
-rw-r--r--src/qml/qml/qqmlengine.cpp1
-rw-r--r--src/quicktestutils/qml/qmlutils.cpp6
-rw-r--r--tests/auto/qml/qqmlqt/tst_qqmlqt.cpp2
-rw-r--r--tests/auto/qml/qv4mm/tst_qv4mm.cpp9
21 files changed, 659 insertions, 129 deletions
diff --git a/src/qml/CMakeLists.txt b/src/qml/CMakeLists.txt
index 254adb9fe8..9e8da459eb 100644
--- a/src/qml/CMakeLists.txt
+++ b/src/qml/CMakeLists.txt
@@ -281,7 +281,7 @@ qt_internal_add_qml_module(Qml
memory/qv4mm.cpp memory/qv4mm_p.h
memory/qv4mmdefs_p.h
memory/qv4stacklimits.cpp memory/qv4stacklimits_p.h
- memory/qv4writebarrier_p.h
+ memory/qv4writebarrier_p.h memory/qv4writebarrier.cpp
parser/qqmljsast.cpp parser/qqmljsast_p.h
parser/qqmljsastfwd_p.h
parser/qqmljsastvisitor.cpp parser/qqmljsastvisitor_p.h
diff --git a/src/qml/jsapi/qjsengine.cpp b/src/qml/jsapi/qjsengine.cpp
index 817fc15943..8f1496a8d4 100644
--- a/src/qml/jsapi/qjsengine.cpp
+++ b/src/qml/jsapi/qjsengine.cpp
@@ -369,6 +369,7 @@ QJSEngine::QJSEngine(QJSEnginePrivate &dd, QObject *parent)
*/
QJSEngine::~QJSEngine()
{
+ m_v4Engine->inShutdown = true;
QJSEnginePrivate::removeFromDebugServer(this);
delete m_v4Engine;
}
diff --git a/src/qml/jsapi/qjsvalue_p.h b/src/qml/jsapi/qjsvalue_p.h
index b95e76e76e..4624652c93 100644
--- a/src/qml/jsapi/qjsvalue_p.h
+++ b/src/qml/jsapi/qjsvalue_p.h
@@ -150,9 +150,17 @@ public:
case QV4::StaticValue::Integer_Type:
return encode(qv4Value.integerValue());
case QV4::StaticValue::Managed_Type: {
- QV4::Value *m = qv4Value.as<QV4::Managed>()->engine()
- ->memoryManager->m_persistentValues->allocate();
+ auto managed = qv4Value.as<QV4::Managed>();
+ auto engine = managed->engine();
+ auto mm = engine->memoryManager;
+ QV4::Value *m = mm->m_persistentValues->allocate();
Q_ASSERT(m);
+ // we create a new strong reference to the heap managed object
+ // to avoid having to rescan the persistent values, we mark it here
+ QV4::WriteBarrier::markCustom(engine, [&](QV4::MarkStack *stack){
+ if constexpr (QV4::WriteBarrier::isInsertionBarrier)
+ managed->heapObject()->mark(stack);
+ });
*m = qv4Value;
return encodePointer(m, Kind::QV4ValuePtr);
}
diff --git a/src/qml/jsruntime/qv4enginebase_p.h b/src/qml/jsruntime/qv4enginebase_p.h
index 721a024efb..68e906baa1 100644
--- a/src/qml/jsruntime/qv4enginebase_p.h
+++ b/src/qml/jsruntime/qv4enginebase_p.h
@@ -45,7 +45,8 @@ struct Q_QML_EXPORT EngineBase {
quint8 isExecutingInRegExpJIT = false;
quint8 isInitialized = false;
- quint8 padding[2];
+ quint8 inShutdown = false;
+ quint8 isGCOngoing = false; // incremental gc is ongoing (but mutator might be running)
MemoryManager *memoryManager = nullptr;
union {
diff --git a/src/qml/jsruntime/qv4identifiertable.cpp b/src/qml/jsruntime/qv4identifiertable.cpp
index d8999951dc..4c915442f4 100644
--- a/src/qml/jsruntime/qv4identifiertable.cpp
+++ b/src/qml/jsruntime/qv4identifiertable.cpp
@@ -36,7 +36,7 @@ void IdentifierTable::addEntry(Heap::StringOrSymbol *str)
if (str->subtype == Heap::String::StringType_ArrayIndex)
return;
- str->identifier = PropertyKey::fromStringOrSymbol(str);
+ str->identifier = PropertyKey::fromStringOrSymbol(engine, str);
bool grow = (alloc <= size*2);
@@ -165,6 +165,9 @@ PropertyKey IdentifierTable::asPropertyKeyImpl(const Heap::String *str)
while (Heap::StringOrSymbol *e = entriesByHash[idx]) {
if (e->stringHash == hash && e->toQString() == str->toQString()) {
str->identifier = e->identifier;
+ QV4::WriteBarrier::markCustom(engine, [&](QV4::MarkStack *stack) {
+ e->identifier.asStringOrSymbol()->mark(stack);
+ });
return e->identifier;
}
++idx;
diff --git a/src/qml/jsruntime/qv4internalclass.cpp b/src/qml/jsruntime/qv4internalclass.cpp
index e4c401f5a7..ddf38c968d 100644
--- a/src/qml/jsruntime/qv4internalclass.cpp
+++ b/src/qml/jsruntime/qv4internalclass.cpp
@@ -122,6 +122,11 @@ PropertyKey SharedInternalClassDataPrivate<PropertyKey>::at(uint i) const
void SharedInternalClassDataPrivate<PropertyKey>::set(uint i, PropertyKey t)
{
Q_ASSERT(data && i < size());
+ QV4::WriteBarrier::markCustom(engine, [&](QV4::MarkStack *stack) {
+ if constexpr (QV4::WriteBarrier::isInsertionBarrier)
+ if (auto string = t.asStringOrSymbol())
+ string->mark(stack);
+ });
data->values.values[i].rawValueRef() = t.id();
}
@@ -256,6 +261,11 @@ void InternalClass::init(Heap::InternalClass *other)
protoId = engine->newProtoId();
internalClass.set(engine, other->internalClass);
+ QV4::WriteBarrier::markCustom(engine, [&](QV4::MarkStack *stack) {
+ if constexpr (QV4::WriteBarrier::isInsertionBarrier) {
+ other->mark(stack);
+ }
+ });
}
void InternalClass::destroy()
@@ -476,6 +486,10 @@ Heap::InternalClass *InternalClass::changePrototypeImpl(Heap::Object *proto)
// create a new class and add it to the tree
Heap::InternalClass *newClass = engine->newClass(this);
+ QV4::WriteBarrier::markCustom(engine, [&](QV4::MarkStack *stack) {
+ if (proto && QV4::WriteBarrier::isInsertionBarrier)
+ proto->mark(stack);
+ });
newClass->prototype = proto;
t.lookup = newClass;
diff --git a/src/qml/jsruntime/qv4persistent.cpp b/src/qml/jsruntime/qv4persistent.cpp
index 75353935a1..3cbaec1dac 100644
--- a/src/qml/jsruntime/qv4persistent.cpp
+++ b/src/qml/jsruntime/qv4persistent.cpp
@@ -171,7 +171,7 @@ Value *PersistentValueStorage::allocate()
Value *v = p->values + p->header.freeList;
p->header.freeList = v->int_32();
- if (p->header.freeList != -1 && p != firstPage) {
+ if (p->header.freeList != -1 && p != firstPage && !engine->isGCOngoing) {
unlink(p);
insertInFront(this, p);
}
@@ -304,6 +304,10 @@ void PersistentValue::set(ExecutionEngine *engine, const Value &value)
{
if (!val)
val = engine->memoryManager->m_persistentValues->allocate();
+ QV4::WriteBarrier::markCustom(engine, [&](QV4::MarkStack *stack){
+ if (QV4::WriteBarrier::isInsertionBarrier && value.isManaged())
+ stack->push(value.heapObject());
+ });
*val = value;
}
@@ -311,6 +315,13 @@ void PersistentValue::set(ExecutionEngine *engine, ReturnedValue value)
{
if (!val)
val = engine->memoryManager->m_persistentValues->allocate();
+ QV4::WriteBarrier::markCustom(engine, [&](QV4::MarkStack *stack){
+ if constexpr (!QV4::WriteBarrier::isInsertionBarrier)
+ return;
+ auto val = Value::fromReturnedValue(value);
+ if (val.isManaged())
+ stack->push(val.heapObject());
+ });
*val = value;
}
@@ -318,6 +329,11 @@ void PersistentValue::set(ExecutionEngine *engine, Heap::Base *obj)
{
if (!val)
val = engine->memoryManager->m_persistentValues->allocate();
+ QV4::WriteBarrier::markCustom(engine, [&](QV4::MarkStack *stack){
+ if constexpr (QV4::WriteBarrier::isInsertionBarrier)
+ stack->push(obj);
+ });
+
*val = obj;
}
diff --git a/src/qml/jsruntime/qv4propertykey.cpp b/src/qml/jsruntime/qv4propertykey.cpp
index d93ff8e794..65dd7e7fc1 100644
--- a/src/qml/jsruntime/qv4propertykey.cpp
+++ b/src/qml/jsruntime/qv4propertykey.cpp
@@ -7,6 +7,7 @@
#include <qv4string_p.h>
#include <qv4engine_p.h>
#include <qv4scopedvalue_p.h>
+#include <private/qv4mm_p.h>
using namespace Qt::Literals::StringLiterals;
diff --git a/src/qml/jsruntime/qv4propertykey_p.h b/src/qml/jsruntime/qv4propertykey_p.h
index 0b8ad084d2..d6d15e402d 100644
--- a/src/qml/jsruntime/qv4propertykey_p.h
+++ b/src/qml/jsruntime/qv4propertykey_p.h
@@ -14,6 +14,7 @@
// We mean it.
//
+#include <private/qv4writebarrier_p.h>
#include <private/qv4global_p.h>
#include <private/qv4staticvalue_p.h>
#include <QtCore/qhashfunctions.h>
@@ -70,11 +71,18 @@ public:
// We cannot #include the declaration of Heap::StringOrSymbol here.
// Therefore we do some gymnastics to enforce the type safety.
- template<typename StringOrSymbol = Heap::StringOrSymbol>
- static PropertyKey fromStringOrSymbol(StringOrSymbol *b)
+ template<typename StringOrSymbol = Heap::StringOrSymbol, typename Engine = QV4::EngineBase>
+ static PropertyKey fromStringOrSymbol(Engine *engine, StringOrSymbol *b)
{
static_assert(std::is_base_of_v<Heap::StringOrSymbol, StringOrSymbol>);
PropertyKey key;
+ QV4::WriteBarrier::markCustom(engine, [&](QV4::MarkStack *stack) {
+ if constexpr (QV4::WriteBarrier::isInsertionBarrier) {
+ // treat this as an insertion - the StringOrSymbol becomes reachable
+ // via the propertykey, so we consequently need to mark it durnig gc
+ b->mark(stack);
+ }
+ });
key.val.setM(b);
Q_ASSERT(key.isManaged());
return key;
diff --git a/src/qml/jsruntime/qv4symbol.cpp b/src/qml/jsruntime/qv4symbol.cpp
index 585038937e..5f7ec89fd2 100644
--- a/src/qml/jsruntime/qv4symbol.cpp
+++ b/src/qml/jsruntime/qv4symbol.cpp
@@ -16,7 +16,7 @@ void Heap::Symbol::init(const QString &s)
Q_ASSERT(s.at(0) == QLatin1Char('@'));
QString desc(s);
StringOrSymbol::init(desc.data_ptr());
- identifier = PropertyKey::fromStringOrSymbol(this);
+ identifier = PropertyKey::fromStringOrSymbol(internalClass->engine, this);
}
void Heap::SymbolCtor::init(QV4::ExecutionContext *scope)
diff --git a/src/qml/memory/design.md b/src/qml/memory/design.md
new file mode 100644
index 0000000000..f75cd7d7f9
--- /dev/null
+++ b/src/qml/memory/design.md
@@ -0,0 +1,131 @@
+The V4 Garbage Collector
+========================
+
+ChangeLog
+---------
+- < 6.8: There was little documentation, and the gc was STW mark&sweep
+- 6.8: The gc became incremental (with a stop-the-world sweep phase)
+
+
+Glossary:
+------------
+- V4: The ECMAScript engine used in Qt (qtdeclarative)
+- gc: abbreviation for garbage collector
+- mutator: the actual user application (in constrast to the collector)
+- roots: A set of pointers from which the gc process starts
+- fast roots: A subset of the roots which are not protected by a barrier
+- write barrier: A set of instructions executed on each write
+- stop the world: not concurrent
+- concurrent gc: gc and mutator can run "at the same time"; this can either mean
+ + incremental: collector and mutator run in the same thread, but in certain time intervals the mutator is paused, a chunk of the collector is executed, and then the mutator resumes. Repeats until the gc cycle is finished
+ + parallel: gc and mutator operations run in different threads
+- precise: a gc is precise if for every value it can know whether it's a pointer to a heap object (a non-precise gc can't in general distinguish pointers from pointer-sized numbers)
+- floating garbage: items that are not live, but nevertheless end up surviving the gc cycle
+- generational: generational refers to dividing objects into different "generations" based on how many collection cycles they survived. This technique is used in garbage collection to improve performance by focusing on collecting the most recently created objects first.
+- moving: A gc is moving if it can relocate objects in memory. Care must be taken to update pointers pointing to them.
+
+
+Overview:
+---------
+
+Since Qt 6.7, V4 uses an incremental, precise mark-and-sweep gc algorithm. It is neither generational nor moving.
+
+In the mark phase, each heap-item can be in one of three states:
+1. unvisited ("white"): The gc has not seen this item at all
+2. seen ("grey"): All grey items have been discovered by the gc, but items directly reachable from them have (potentially) not been visited.
+3. finished ("black"): Not only has the item been seen, but also all items directly reachable from it have been seen.
+
+Items are black if they have their corresponding bit set in the black-object bitmap. They are grey if they are stored at least once in the MarkStack, a stack data structure. Items are white if they are neither grey nor black. Note that black items must never be pushed to the MarkStack (otherwise we could easily end up with endless cycles), but items already _on_ the MarkStack can be black:
+If an item has been pushed multiple times before it has been popped, this can happen. It causes some additional work to revisit its fields, but that is safe, as after popping the item will be black, and thus we won't keep on repeatedly pushing the same item on the mark stack.
+
+The roots consist of
+- the engine-global objects (namely the internal classes for the JS globals)
+- all strings (and symbols) stored in the identifier table and
+- all actively linked compilation units.
+- Moreover, the values on the JS stack are also treated as roots; more precisely as fast roots.
+- Additionally, all persistent values (everything stored in a QJSValue as well as all bound functions of QQmlBindings) are added to the roots.
+- Lastly, all QObjectWrapper of QObjects with C++ ownership, or which are rooted in or parented to a QObject with C++ ownership are added to the root set.
+
+All roots are added to the MarkStack. Then, during mark phase, entries are:
+1. popped from the markstack
+2. All heap-objects reachable from them are added to the MarkStack (unless they are already black)
+
+To avoid that values that were on the heap during the start of the gc cycle, then moved to the stack before they could be visited and consequently freed even though they are still live, the stack is rescanned before the sweep phase.
+
+To avoid that unmarked heap-items are moved from one heap item (or the stack) to an already marked heap-item (and consequently end up hidden from the gc), we employ a Dijkstra style write barrier: Any item that becomes reachable from another heap-item is marked grey (unless already black).
+
+While a gc cycle is ongoing, allocations are black, meaning every allocated object is considered to be live (until the next gc cycle is started).
+This is currently required as compilation units linked to the engine while the gc is running are not protected by the write barrier or another mechanism. It also helps to reduce the amount of work to be done when rescanning the JS stack (as it helps to ensure that most items are already black at that point).
+
+
+The gc state machine
+--------------------
+
+To facilitate incremental garbage collection, the gc algorithm is divided into the following stages:
+
+1. markStart, the atomic initialization phase, in which the MarkStack is initialized, and a flag is set on the engine indicating that incremental gc is active
+2. markGlobalObject, an atomic phase in which the global object, the engine's identifier table and the currently linked compilation units are marked
+3. markJSStack, an atomic phase in which the JS stack is marked
+4. initMarkPersistentValues: Atomic phase. If there are persistent values, some setup is done for the next phase.
+5. markPersistentValues: An interruptible phase in which all persistent values are marked.
+6. initMarkWeakValues: Atomic phase. If there are weak values, some setup is done for the next phase
+7. markWeakValues: An interruptible phase which takes care of marking the QObjectWrappers
+8. markDrain: An interrupible phase. While the MarkStack is not empty, the marking algorithm runs.
+9. markReady: An atomic phase which currently does nothing, but could be used for e.g. logging statistics
+10. sweepPhase: An atomic phase, in which the stack is rescanned, the MarkStack is drained once more, and then the actual sweep algorithm is running, freeing dead objects.
+11. invalid, the "not-running" stage of the state machine.
+
+The transitions between the states look as following (D == done, T == can stay in state if there's a timeout, NW == No work):
+```
+ NW __->-__ _____ NW
+ / \ __ / |
+ D D D | D D v / D v D D D
+1 -> 2 -> 3 -> 4--> 5 -->6---->7----->8---->9--->10---->11
+ ^ T T T |
+ \___ __ | restart gc
+ \ |
+ ----------------------------------------------/
+
+```
+
+
+To avoid constantly having to query the timer, even interruptible phases run for a fixed amount of steps before checking whether there's a timemout.
+
+Most steps are straight-forward, only the persistent and weak value phases require some explanation as to why it's safe to interrupt the process: The important thing to note is that we never remove elements from the structure while we're undergoing gc, and that we only ever append at the end. So we will see any new values that might be added.
+
+Persistent Values
+-----------------
+As shown in the diagram above, the handling of persistent values is interruptible (both for "real" persistent values, and also for weak vaules which also are stored in a `PersistentValueStorage` data structure.
+This is done by storing the `PersistentValueStorage::Iterator` in the gc state machine. That in turn raises two questions: Is the iterator safe against invalidation? And do we actually keep track of newly added persistent values?
+
+The latter part is easy to answer: Any newly added weak value is marked when we are in a gc cycle, so the marking part is handled. Sweeping only cares about unmarked values, so that's safe too.
+To answer the question about iterator validity, we have to look at the `PersistentValueStorage` data structure. Conceptionally, it's a forward-list of `Page`s (arrays of `QV4::Value`). Pages are ref-counted, and only unliked from the list if the ref-count reaches zero. Moreover, iterators also increase the ref-count.
+Therefore, as long as we iterate over the list, we don't risk having the pointer point to a deleted `Page` – even if all values in it have been freed. Freeing values is unproblematic for the gc – it will simply encounter `undefined` values, something it is already prepared to handle.
+Pages are also kept in the list while they are not deleted, so iteration works as expected. The only adjustment we need to do is to disable an optimization: When searching for a Page with an empty slot, we have
+to (potentially) travese the whole `PersistentValueStorage`. To avoid that, the first Page with empty slots is normally moved to the front of the list. However, that would mean that we could potentially skip over it during the marking phase. We sidestep that issue by simply disabling the optimization. This area could
+easily be improved in the future by keeping track of the first page with free slots in a different way.
+
+Custom marking
+---------------
+
+Some parts of the engine have to deviate from the general scheme described in the overview, as they don't integrate with the normal WriteBarrier. They are wrapped in the callback of the `QV4::WriteBarrier::markCustom` function, so that they can easily be found via "Find references".
+
+1. `QJSValue::encode`. QJSValues act as roots, and don't act as normal heap-items. When the current value of a QJSValue is overwritten with another heap-item, we also mark the new object. That aligns nicely with the gc barrier.
+2. The same applies to `PersistentValue::set`.
+3. The identifier table is another root; if a new string is inserted there during gc, it is (conservatively) marked black.
+4. PropertyKeys should for all intents and purposes use a write barrier (and have a deleted operator=). But them being an ad-hoc union type of numbers, Strings and Symbols, which has the additional requirements of having to be trivial, it turned out to be easier to simply mark them in `SharedInternalClassDataPrivate<PropertyKey>::set` (for PropertyKeys that had already been allocated), and on the fact that we allocate black for newly created PropertyKeys.
+5. `QV4::Heap::InternalClass` also requires special handling, as it uses plain members to Heap objects, notably to the prototype and to the parent internal class. As the class is somewhat special in any case (due to the usage of `SharedInternalClassData` and especially due to the usage of `SharedInternalClassData<PropertyKey>`, see notes on PropertyKey above), we use some bespoke sections for now. This could probably be cleaned up.
+
+Motivation for using a Dijkstra style barrier:
+----------------------------------------------
+- Deletion barriers are hard to support with the current PropertyKey design
+- Steele style barriers cause more work (have to revisit more objects), and as long as we have black allocations it doesn't make much sense to optimize for a minimal amount of floating garbage.
+
+Sweep Phase and finalizers:
+---------------------------
+A story for another day
+
+Allocator design:
+-----------------
+Your explanation is in another castle.
+
diff --git a/src/qml/memory/qv4mm.cpp b/src/qml/memory/qv4mm.cpp
index 5c3be27014..c09e20f0a0 100644
--- a/src/qml/memory/qv4mm.cpp
+++ b/src/qml/memory/qv4mm.cpp
@@ -25,6 +25,8 @@
#include "qv4mapobject_p.h"
#include "qv4setobject_p.h"
+#include <chrono>
+
//#define MM_STATS
#if !defined(MM_STATS) && !defined(QT_NO_DEBUG)
@@ -671,6 +673,137 @@ void HugeItemAllocator::freeAll()
}
}
+namespace {
+using ExtraData = GCStateInfo::ExtraData;
+GCState markStart(GCStateMachine *that, ExtraData &)
+{
+ //Initialize the mark stack
+ that->mm->m_markStack = std::make_unique<MarkStack>(that->mm->engine);
+ that->mm->engine->isGCOngoing = true;
+ return MarkGlobalObject;
+}
+
+GCState markGlobalObject(GCStateMachine *that, ExtraData &)
+{
+ that->mm->engine->markObjects(that->mm->m_markStack.get());
+ return MarkJSStack;
+}
+
+GCState markJSStack(GCStateMachine *that, ExtraData &)
+{
+ that->mm->collectFromJSStack(that->mm->markStack());
+ return InitMarkPersistentValues;
+}
+
+GCState initMarkPersistentValues(GCStateMachine *that, ExtraData &stateData)
+{
+ if (!that->mm->m_persistentValues)
+ return InitMarkWeakValues; // no persistent values to mark
+ stateData = GCIteratorStorage { that->mm->m_persistentValues->begin() };
+ return MarkPersistentValues;
+}
+
+static constexpr int markLoopIterationCount = 1024;
+
+bool wasDrainNecessary(MarkStack *ms, QDeadlineTimer deadline)
+{
+ if (ms->remainingBeforeSoftLimit() < markLoopIterationCount)
+ return false;
+ // drain
+ ms->drain(deadline);
+ return true;
+}
+
+GCState markPersistentValues(GCStateMachine *that, ExtraData &stateData) {
+ auto markStack = that->mm->markStack();
+ if (wasDrainNecessary(markStack, that->deadline) && that->deadline.hasExpired())
+ return MarkPersistentValues;
+ PersistentValueStorage::Iterator& it = get<GCIteratorStorage>(stateData).it;
+ // avoid repeatedly hitting the timer constantly by batching iterations
+ for (int i = 0; i < markLoopIterationCount; ++i) {
+ if (!it.p)
+ return InitMarkWeakValues;
+ if (Managed *m = (*it).as<Managed>())
+ m->mark(markStack);
+ ++it;
+ }
+ return MarkPersistentValues;
+}
+
+GCState initMarkWeakValues(GCStateMachine *that, ExtraData &stateData)
+{
+ stateData = GCIteratorStorage { that->mm->m_weakValues->begin() };
+ return MarkWeakValues;
+}
+
+GCState markWeakValues(GCStateMachine *that, ExtraData &stateData)
+{
+ auto markStack = that->mm->markStack();
+ if (wasDrainNecessary(markStack, that->deadline) && that->deadline.hasExpired())
+ return MarkWeakValues;
+ PersistentValueStorage::Iterator& it = get<GCIteratorStorage>(stateData).it;
+ // avoid repeatedly hitting the timer constantly by batching iterations
+ for (int i = 0; i < markLoopIterationCount; ++i) {
+ if (!it.p)
+ return MarkDrain;
+ QObjectWrapper *qobjectWrapper = (*it).as<QObjectWrapper>();
+ ++it;
+ if (!qobjectWrapper)
+ continue;
+ QObject *qobject = qobjectWrapper->object();
+ if (!qobject)
+ continue;
+ bool keepAlive = QQmlData::keepAliveDuringGarbageCollection(qobject);
+
+ if (!keepAlive) {
+ if (QObject *parent = qobject->parent()) {
+ while (parent->parent())
+ parent = parent->parent();
+ keepAlive = QQmlData::keepAliveDuringGarbageCollection(parent);
+ }
+ }
+
+ if (keepAlive)
+ qobjectWrapper->mark(that->mm->markStack());
+ }
+ return MarkWeakValues;
+}
+
+GCState markDrain(GCStateMachine *that, ExtraData &)
+{
+ if (that->deadline.isForever()) {
+ that->mm->markStack()->drain();
+ return MarkReady;
+ }
+ auto drainState = that->mm->m_markStack->drain(that->deadline);
+ return drainState == MarkStack::DrainState::Complete
+ ? MarkReady
+ : MarkDrain;
+}
+
+GCState markReady(GCStateMachine *, ExtraData &)
+{
+ //Possibility to do some clean up, stat printing, etc...
+ return Sweep;
+}
+
+GCState sweepPhase(GCStateMachine *that, ExtraData &)
+{
+ // if we don't have a deletion barrier, then we need to rescan
+ that->mm->collectFromJSStack(that->mm->markStack());
+ that->mm->m_markStack->drain();
+ if (!that->mm->gcCollectorStats) {
+ that->mm->sweep();
+ } else {
+ that->mm->sweep(false, increaseFreedCountForClass);
+ }
+ that->mm->m_markStack.reset();
+ that->mm->engine->isGCOngoing = false;
+ return Invalid;
+}
+
+}
+
MemoryManager::MemoryManager(ExecutionEngine *engine)
: engine(engine)
@@ -691,6 +824,51 @@ MemoryManager::MemoryManager(ExecutionEngine *engine)
memset(statistics.allocations, 0, sizeof(statistics.allocations));
if (gcStats)
blockAllocator.allocationStats = statistics.allocations;
+
+ gcStateMachine = std::make_unique<GCStateMachine>();
+ gcStateMachine->mm = this;
+
+ gcStateMachine->stateInfoMap[GCState::MarkStart] = {
+ markStart,
+ false,
+ };
+ gcStateMachine->stateInfoMap[GCState::MarkGlobalObject] = {
+ markGlobalObject,
+ false,
+ };
+ gcStateMachine->stateInfoMap[GCState::MarkJSStack] = {
+ markJSStack,
+ false,
+ };
+ gcStateMachine->stateInfoMap[GCState::InitMarkPersistentValues] = {
+ initMarkPersistentValues,
+ false,
+ };
+ gcStateMachine->stateInfoMap[GCState::MarkPersistentValues] = {
+ markPersistentValues,
+ false,
+ };
+ gcStateMachine->stateInfoMap[GCState::InitMarkWeakValues] = {
+ initMarkWeakValues,
+ false,
+ };
+ gcStateMachine->stateInfoMap[GCState::MarkWeakValues] = {
+ markWeakValues,
+ false,
+ };
+ gcStateMachine->stateInfoMap[GCState::MarkDrain] = {
+ markDrain,
+ false,
+ };
+ gcStateMachine->stateInfoMap[GCState::MarkReady] = {
+ markReady,
+ true, //Break after this step, so that Sweep is only executed the next time GC is allowed
+ // as sweep is not concurrent, we want to provide as much time as possible to it
+ };
+ gcStateMachine->stateInfoMap[GCState::Sweep] = {
+ sweepPhase,
+ false,
+ };
}
Heap::Base *MemoryManager::allocString(std::size_t unmanagedSize)
@@ -704,7 +882,7 @@ Heap::Base *MemoryManager::allocString(std::size_t unmanagedSize)
HeapItem *m = allocate(&blockAllocator, stringSize);
memset(m, 0, stringSize);
- if (gcBlocked) {
+ if (m_markStack) {
// If the gc is running right now, it will not have a chance to mark the newly created item
// and may therefore sweep it right away.
// Protect the new object from the current GC run to avoid this.
@@ -726,7 +904,7 @@ Heap::Base *MemoryManager::allocData(std::size_t size)
HeapItem *m = allocate(&blockAllocator, size);
memset(m, 0, size);
- if (gcBlocked) {
+ if (m_markStack) {
// If the gc is running right now, it will not have a chance to mark the newly created item
// and may therefore sweep it right away.
// Protect the new object from the current GC run to avoid this.
@@ -764,6 +942,16 @@ Heap::Object *MemoryManager::allocObjectWithMemberData(const QV4::VTable *vtable
Chunk::setBit(c->objectBitmap, index);
Chunk::clearBit(c->extendsBitmap, index);
}
+ /* If the gc is running, then o will be black,as allocData allocates black during gc
+ However, m points to "clear" memory. We must mark it, too, otherwise it might be
+ collected. Note that this must happen after (un)setting the object and extends bit
+ otherwise we hit an assertion.
+ Actually, the write barrier of o->memberData would save us (at leas as long as we
+ keep using a Dijkstra style barrier; however, setting the mark bit directly avoids
+ some unnecessary work.
+ */
+ if (m_markStack) // gc running
+ m->setMarkBit();
o->memberData.set(engine, m);
m->internalClass.set(engine, engine->internalClasses(EngineBase::Class_MemberData));
Q_ASSERT(o->memberData->internalClass);
@@ -790,6 +978,7 @@ MarkStack::MarkStack(ExecutionEngine *engine)
void MarkStack::drain()
{
+ // we're not calling drain(QDeadlineTimer::Forever) as that has higher overhead
while (m_top > m_base) {
Heap::Base *h = pop();
++markStackSize;
@@ -798,71 +987,57 @@ void MarkStack::drain()
}
}
-void MemoryManager::collectRoots(MarkStack *markStack)
+MarkStack::DrainState MarkStack::drain(QDeadlineTimer deadline)
{
- engine->markObjects(markStack);
-
-// qDebug() << " mark stack after engine->mark" << (engine->jsStackTop - markBase);
-
- collectFromJSStack(markStack);
-
-// qDebug() << " mark stack after js stack collect" << (engine->jsStackTop - markBase);
- m_persistentValues->mark(markStack);
-
-// qDebug() << " mark stack after persistants" << (engine->jsStackTop - markBase);
-
- // Preserve QObject ownership rules within JavaScript: A parent with c++ ownership
- // keeps all of its children alive in JavaScript.
-
- // Do this _after_ collectFromStack to ensure that processing the weak
- // managed objects in the loop down there doesn't make then end up as leftovers
- // on the stack and thus always get collected.
- for (PersistentValueStorage::Iterator it = m_weakValues->begin(); it != m_weakValues->end(); ++it) {
- QObjectWrapper *qobjectWrapper = (*it).as<QObjectWrapper>();
- if (!qobjectWrapper)
- continue;
- QObject *qobject = qobjectWrapper->object();
- if (!qobject)
- continue;
- bool keepAlive = QQmlData::keepAliveDuringGarbageCollection(qobject);
-
- if (!keepAlive) {
- if (QObject *parent = qobject->parent()) {
- while (parent->parent())
- parent = parent->parent();
-
- keepAlive = QQmlData::keepAliveDuringGarbageCollection(parent);
- }
+ do {
+ for (int i = 0; i <= markLoopIterationCount * 10; ++i) {
+ if (m_top == m_base)
+ return DrainState::Complete;
+ Heap::Base *h = pop();
+ ++markStackSize;
+ Q_ASSERT(h); // at this point we should only have Heap::Base objects in this area on the stack. If not, weird things might happen.
+ h->internalClass->vtable->markObjects(h, this);
}
+ } while (!deadline.hasExpired());
+ return DrainState::Ongoing;
+}
- if (keepAlive)
- qobjectWrapper->mark(markStack);
+void MemoryManager::onEventLoop()
+{
+ if (engine->inShutdown)
+ return;
+ gcBlocked = false;
+ if (gcStateMachine->inProgress()) {
+ runGC();
}
}
-void MemoryManager::mark()
+
+void MemoryManager::setGCTimeLimit(int timeMs)
{
- markStackSize = 0;
- MarkStack markStack(engine);
- collectRoots(&markStack);
- // dtor of MarkStack drains
+ gcStateMachine->timeLimit = std::chrono::milliseconds(timeMs);
}
void MemoryManager::sweep(bool lastSweep, ClassDestroyStatsCallback classCountPtr)
{
+ Heap::MapObject *map = nullptr;
+ Heap::MapObject **lastMap = nullptr;
+ Heap::SetObject *set = nullptr;
+ Heap::SetObject **lastSet = nullptr;
+
for (PersistentValueStorage::Iterator it = m_weakValues->begin(); it != m_weakValues->end(); ++it) {
Managed *m = (*it).managed();
if (!m || m->markBit())
continue;
// we need to call destroyObject on qobjectwrappers now, so that they can emit the destroyed
// signal before we start sweeping the heap
- if (QObjectWrapper *qobjectWrapper = (*it).as<QObjectWrapper>())
+ if (QObjectWrapper *qobjectWrapper = (*it).as<QObjectWrapper>()) {
qobjectWrapper->destroyObject(lastSweep);
+ }
}
- // remove objects from weak maps and sets
- Heap::MapObject *map = weakMaps;
- Heap::MapObject **lastMap = &weakMaps;
+ map = weakMaps;
+ lastMap = &weakMaps;
while (map) {
if (map->isMarked()) {
map->removeUnmarkedKeys();
@@ -872,8 +1047,8 @@ void MemoryManager::sweep(bool lastSweep, ClassDestroyStatsCallback classCountPt
map = map->nextWeakMap;
}
- Heap::SetObject *set = weakSets;
- Heap::SetObject **lastSet = &weakSets;
+ set = weakSets;
+ lastSet = &weakSets;
while (set) {
if (set->isMarked()) {
set->removeUnmarkedKeys();
@@ -916,13 +1091,20 @@ void MemoryManager::sweep(bool lastSweep, ClassDestroyStatsCallback classCountPt
}
}
-
if (!lastSweep) {
engine->identifierTable->sweep();
blockAllocator.sweep(/*classCountPtr*/);
hugeItemAllocator.sweep(classCountPtr);
icAllocator.sweep(/*classCountPtr*/);
}
+
+ // reset all black bits
+ blockAllocator.resetBlackBits();
+ hugeItemAllocator.resetBlackBits();
+ icAllocator.resetBlackBits();
+
+ usedSlotsAfterLastFullSweep = blockAllocator.usedSlotsAfterLastSweep + icAllocator.usedSlotsAfterLastSweep;
+ gcBlocked = false;
}
bool MemoryManager::shouldRunGC() const
@@ -965,12 +1147,10 @@ static size_t dumpBins(BlockAllocator *b, const char *title)
void MemoryManager::runGC()
{
if (gcBlocked) {
-// qDebug() << "Not running GC.";
return;
}
- QScopedValueRollback<bool> gcBlocker(gcBlocked, true);
-// qDebug() << "runGC";
+ gcBlocked = true;
if (gcStats) {
statistics.maxReservedMem = qMax(statistics.maxReservedMem, getAllocatedMem());
@@ -978,8 +1158,7 @@ void MemoryManager::runGC()
}
if (!gcCollectorStats) {
- mark();
- sweep();
+ gcStateMachine->step();
} else {
bool triggeredByUnmanagedHeap = (unmanagedHeapSize > unmanagedHeapSizeGCLimit);
size_t oldUnmanagedSize = unmanagedHeapSize;
@@ -1003,13 +1182,11 @@ void MemoryManager::runGC()
QElapsedTimer t;
t.start();
- mark();
+ gcStateMachine->step();
qint64 markTime = t.nsecsElapsed()/1000;
t.restart();
- sweep(false, increaseFreedCountForClass);
const size_t usedAfter = getUsedMem();
const size_t largeItemsAfter = getLargeItemsMem();
- qint64 sweepTime = t.nsecsElapsed()/1000;
if (triggeredByUnmanagedHeap) {
qDebug(stats) << "triggered by unmanaged heap:";
@@ -1021,7 +1198,6 @@ void MemoryManager::runGC()
+ dumpBins(&icAllocator, "InternalClasss");
qDebug(stats) << "Marked object in" << markTime << "us.";
qDebug(stats) << " " << markStackSize << "objects marked";
- qDebug(stats) << "Sweeped object in" << sweepTime << "us.";
// sort our object types by number of freed instances
MMStatsHash freedObjectStats;
@@ -1067,13 +1243,6 @@ void MemoryManager::runGC()
Q_ASSERT(icAllocator.allocatedMem()
== icAllocator.usedMem() + dumpBins(&icAllocator, nullptr));
}
-
- usedSlotsAfterLastFullSweep = blockAllocator.usedSlotsAfterLastSweep + icAllocator.usedSlotsAfterLastSweep;
-
- // reset all black bits
- blockAllocator.resetBlackBits();
- hugeItemAllocator.resetBlackBits();
- icAllocator.resetBlackBits();
}
size_t MemoryManager::getUsedMem() const
@@ -1106,10 +1275,22 @@ void MemoryManager::registerWeakSet(Heap::SetObject *set)
MemoryManager::~MemoryManager()
{
delete m_persistentValues;
-
dumpStats();
+ // do one last non-incremental sweep to clean up C++ objects
+ // first, abort any on-going incremental gc operation
+ setGCTimeLimit(-1);
+ if (engine->isGCOngoing) {
+ engine->isGCOngoing = false;
+ m_markStack.reset();
+ gcStateMachine->state = GCState::Invalid;
+ blockAllocator.resetBlackBits();
+ hugeItemAllocator.resetBlackBits();
+ icAllocator.resetBlackBits();
+ }
+ // then sweep
sweep(/*lastSweep*/true);
+
blockAllocator.freeAll();
hugeItemAllocator.freeAll();
icAllocator.freeAll();
@@ -1153,6 +1334,37 @@ void MemoryManager::collectFromJSStack(MarkStack *markStack) const
}
}
+GCStateMachine::GCStateMachine()
+{
+ // base assumption: target 60fps, use at most 1/3 of time for gc
+ timeLimit = std::chrono::milliseconds { (1000 / 60) / 3 };
+}
+
+void GCStateMachine::transition() {
+ if (timeLimit.count() > 0) {
+ deadline = QDeadlineTimer(timeLimit);
+ bool deadlineExpired = false;
+ while (!(deadlineExpired = deadline.hasExpired()) && state != GCState::Invalid) {
+ GCStateInfo& stateInfo = stateInfoMap[int(state)];
+ state = stateInfo.execute(this, stateData);
+ if (stateInfo.breakAfter)
+ break;
+ }
+ if (deadlineExpired)
+ handleTimeout(state);
+ if (state != GCState::Invalid)
+ QMetaObject::invokeMethod(mm->engine->publicEngine, [this]{
+ mm->onEventLoop();
+ }, Qt::QueuedConnection);
+ } else {
+ deadline = QDeadlineTimer::Forever;
+ while (state != GCState::Invalid) {
+ GCStateInfo& stateInfo = stateInfoMap[int(state)];
+ state = stateInfo.execute(this, stateData);
+ }
+ }
+}
+
} // namespace QV4
QT_END_NAMESPACE
diff --git a/src/qml/memory/qv4mm_p.h b/src/qml/memory/qv4mm_p.h
index 3a05bca536..9ed5b946b7 100644
--- a/src/qml/memory/qv4mm_p.h
+++ b/src/qml/memory/qv4mm_p.h
@@ -28,6 +28,68 @@ QT_BEGIN_NAMESPACE
namespace QV4 {
+enum GCState {
+ MarkStart = 0,
+ MarkGlobalObject,
+ MarkJSStack,
+ InitMarkPersistentValues,
+ MarkPersistentValues,
+ InitMarkWeakValues,
+ MarkWeakValues,
+ MarkDrain,
+ MarkReady,
+ Sweep,
+ Invalid,
+ Count,
+};
+
+struct GCData { virtual ~GCData(){};};
+
+struct GCIteratorStorage {
+ PersistentValueStorage::Iterator it{nullptr, 0};
+};
+struct GCStateMachine;
+
+struct GCStateInfo {
+ using ExtraData = std::variant<std::monostate, GCIteratorStorage>;
+ GCState (*execute)(GCStateMachine *, ExtraData &) = nullptr; // Function to execute for this state, returns true if ready to transition
+ bool breakAfter{false};
+};
+
+struct GCStateMachine {
+ using ExtraData = GCStateInfo::ExtraData;
+ GCState state{GCState::Invalid};
+ std::chrono::microseconds timeLimit{};
+ QDeadlineTimer deadline;
+ std::array<GCStateInfo, GCState::Count> stateInfoMap;
+ MemoryManager *mm = nullptr;
+ ExtraData stateData; // extra date for specific states
+
+ GCStateMachine();
+
+ inline void step() {
+ if (!inProgress()) {
+ reset();
+ }
+ transition();
+ }
+
+ inline bool inProgress() {
+ return state != GCState::Invalid;
+ }
+
+ inline void reset() {
+ state = GCState::MarkStart;
+ }
+
+ Q_QML_EXPORT void transition();
+
+ inline void handleTimeout(GCState state) {
+ Q_UNUSED(state);
+ }
+};
+
+
struct ChunkAllocator;
struct MemorySegment;
@@ -258,12 +320,24 @@ public:
typename ManagedType::Data *allocIC()
{
Heap::Base *b = *allocate(&icAllocator, align(sizeof(typename ManagedType::Data)));
+ if (m_markStack) {
+ // If the gc is running right now, it will not have a chance to mark the newly created item
+ // and may therefore sweep it right away.
+ // Protect the new object from the current GC run to avoid this.
+ b->setMarkBit();
+ }
return static_cast<typename ManagedType::Data *>(b);
}
void registerWeakMap(Heap::MapObject *map);
void registerWeakSet(Heap::SetObject *set);
+ void onEventLoop();
+
+ //GC related methods
+ void setGCTimeLimit(int timeMs);
+ MarkStack* markStack() { return m_markStack.get(); }
+
protected:
/// expects size to be aligned
Heap::Base *allocString(std::size_t unmanagedSize);
@@ -275,14 +349,18 @@ private:
MinUnmanagedHeapSizeGCLimit = 128 * 1024
};
+public:
void collectFromJSStack(MarkStack *markStack) const;
- void mark();
void sweep(bool lastSweep = false, ClassDestroyStatsCallback classCountPtr = nullptr);
+private:
bool shouldRunGC() const;
- void collectRoots(MarkStack *markStack);
HeapItem *allocate(BlockAllocator *allocator, std::size_t size)
{
+ // We must not call runGC if incremental gc is running
+ // so temporarily set gcBlocked in that case, too
+ QBoolBlocker block(gcBlocked, m_markStack != nullptr || gcBlocked);
+
bool didGCRun = false;
if (aggressiveGC) {
runGC();
@@ -329,6 +407,9 @@ public:
Heap::MapObject *weakMaps = nullptr;
Heap::SetObject *weakSets = nullptr;
+ std::unique_ptr<GCStateMachine> gcStateMachine{nullptr};
+ std::unique_ptr<MarkStack> m_markStack{nullptr};
+
std::size_t unmanagedHeapSize = 0; // the amount of bytes of heap that is not managed by the memory manager, but which is held onto by managed items.
std::size_t unmanagedHeapSizeGCLimit;
std::size_t usedSlotsAfterLastFullSweep = 0;
diff --git a/src/qml/memory/qv4mmdefs_p.h b/src/qml/memory/qv4mmdefs_p.h
index a7fc7bb7cb..277b1adcc5 100644
--- a/src/qml/memory/qv4mmdefs_p.h
+++ b/src/qml/memory/qv4mmdefs_p.h
@@ -21,6 +21,8 @@
QT_BEGIN_NAMESPACE
+class QDeadlineTimer;
+
namespace QV4 {
struct MarkStack;
@@ -229,7 +231,7 @@ Q_STATIC_ASSERT((1 << Chunk::BitShift) == Chunk::Bits);
struct Q_QML_PRIVATE_EXPORT MarkStack {
MarkStack(ExecutionEngine *engine);
- ~MarkStack() { drain(); }
+ ~MarkStack() { /* we drain manually */ }
void push(Heap::Base *m) {
*(m_top++) = m;
@@ -250,17 +252,28 @@ struct Q_QML_PRIVATE_EXPORT MarkStack {
}
}
+ bool isEmpty() const { return m_top == m_base; }
+
+ qptrdiff remainingBeforeSoftLimit() const
+ {
+ return m_softLimit - m_top;
+ }
+
ExecutionEngine *engine() const { return m_engine; }
+ void drain();
+ enum class DrainState { Ongoing, Complete };
+ DrainState drain(QDeadlineTimer deadline);
private:
Heap::Base *pop() { return *(--m_top); }
- void drain();
Heap::Base **m_top = nullptr;
Heap::Base **m_base = nullptr;
Heap::Base **m_softLimit = nullptr;
Heap::Base **m_hardLimit = nullptr;
+
ExecutionEngine *m_engine = nullptr;
+
quintptr m_drainRecursion = 0;
};
diff --git a/src/qml/memory/qv4writebarrier.cpp b/src/qml/memory/qv4writebarrier.cpp
new file mode 100644
index 0000000000..d7e56212ca
--- /dev/null
+++ b/src/qml/memory/qv4writebarrier.cpp
@@ -0,0 +1,36 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#include <private/qv4value_p.h>
+#include <private/qv4mm_p.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace {
+ void markHeapBase(QV4::MarkStack* markStack, QV4::Heap::Base *base){
+ if (!base)
+ return;
+ base->mark(markStack);
+ }
+}
+namespace QV4 {
+
+void WriteBarrier::write_slowpath(EngineBase *engine, Heap::Base *base, ReturnedValue *slot, ReturnedValue value)
+{
+ Q_UNUSED(base);
+ Q_UNUSED(slot);
+ MarkStack * markStack = engine->memoryManager->markStack();
+ if constexpr (isInsertionBarrier)
+ markHeapBase(markStack, Value::fromReturnedValue(value).heapObject());
+}
+
+void WriteBarrier::write_slowpath(EngineBase *engine, Heap::Base *base, Heap::Base **slot, Heap::Base *value)
+{
+ Q_UNUSED(base);
+ Q_UNUSED(slot);
+ MarkStack * markStack = engine->memoryManager->markStack();
+ if constexpr (isInsertionBarrier)
+ markHeapBase(markStack, value);
+}
+
+}
+QT_END_NAMESPACE
diff --git a/src/qml/memory/qv4writebarrier_p.h b/src/qml/memory/qv4writebarrier_p.h
index 813d360841..9b26766ee5 100644
--- a/src/qml/memory/qv4writebarrier_p.h
+++ b/src/qml/memory/qv4writebarrier_p.h
@@ -15,57 +15,50 @@
//
#include <private/qv4global_p.h>
+#include <private/qv4enginebase_p.h>
QT_BEGIN_NAMESPACE
-#define WRITEBARRIER_none 1
-
-#define WRITEBARRIER(x) (1/WRITEBARRIER_##x == 1)
-
namespace QV4 {
struct EngineBase;
-
-namespace WriteBarrier {
-
-enum Type {
- NoBarrier,
- Barrier
+typedef quint64 ReturnedValue;
+
+struct WriteBarrier {
+
+ static constexpr bool isInsertionBarrier = true;
+
+ Q_ALWAYS_INLINE static void write(EngineBase *engine, Heap::Base *base, ReturnedValue *slot, ReturnedValue value)
+ {
+ if (engine->isGCOngoing)
+ write_slowpath(engine, base, slot, value);
+ *slot = value;
+ }
+ Q_QML_EXPORT Q_NEVER_INLINE static void write_slowpath(
+ EngineBase *engine, Heap::Base *base,
+ ReturnedValue *slot, ReturnedValue value);
+
+ Q_ALWAYS_INLINE static void write(EngineBase *engine, Heap::Base *base, Heap::Base **slot, Heap::Base *value)
+ {
+ if (engine->isGCOngoing)
+ write_slowpath(engine, base, slot, value);
+ *slot = value;
+ }
+ Q_QML_EXPORT Q_NEVER_INLINE static void write_slowpath(
+ EngineBase *engine, Heap::Base *base,
+ Heap::Base **slot, Heap::Base *value);
+
+ // MemoryManager isn't a complete type here, so make Engine a template argument
+ // so that we can still call engine->memoryManager->markStack()
+ template<typename F, typename Engine = EngineBase>
+ static void markCustom(Engine *engine, F &&markFunction) {
+ if (engine->isGCOngoing)
+ (std::forward<F>(markFunction))(engine->memoryManager->markStack());
+ }
};
-enum NewValueType {
- Primitive,
- Object,
- Unknown
-};
-
-// ### this needs to be filled with a real memory fence once marking is concurrent
+ // ### this needs to be filled with a real memory fence once marking is concurrent
Q_ALWAYS_INLINE void fence() {}
-#if WRITEBARRIER(none)
-
-template <NewValueType type>
-static constexpr inline bool isRequired() {
- return false;
-}
-
-inline void write(EngineBase *engine, Heap::Base *base, ReturnedValue *slot, ReturnedValue value)
-{
- Q_UNUSED(engine);
- Q_UNUSED(base);
- *slot = value;
-}
-
-inline void write(EngineBase *engine, Heap::Base *base, Heap::Base **slot, Heap::Base *value)
-{
- Q_UNUSED(engine);
- Q_UNUSED(base);
- *slot = value;
-}
-
-#endif
-
-}
-
}
QT_END_NAMESPACE
diff --git a/src/qml/qml/qqmlbuiltinfunctions.cpp b/src/qml/qml/qqmlbuiltinfunctions.cpp
index 8845d6194d..24de6cc069 100644
--- a/src/qml/qml/qqmlbuiltinfunctions.cpp
+++ b/src/qml/qml/qqmlbuiltinfunctions.cpp
@@ -2360,13 +2360,15 @@ ReturnedValue GlobalExtensions::method_qsTrIdNoOp(const FunctionObject *, const
ReturnedValue GlobalExtensions::method_gc(const FunctionObject *b, const Value *, const Value *, int)
{
- b->engine()->memoryManager->runGC();
+ auto mm = b->engine()->memoryManager;
+ auto oldLimit = mm->gcStateMachine->timeLimit;
+ mm->setGCTimeLimit(-1); // temporarily use non-incremental gc
+ mm->runGC();
+ mm->gcStateMachine->timeLimit = oldLimit;
return QV4::Encode::undefined();
}
-
-
ReturnedValue GlobalExtensions::method_string_arg(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
QV4::Scope scope(b);
diff --git a/src/qml/qml/qqmlengine.cpp b/src/qml/qml/qqmlengine.cpp
index f232c0aaee..046d40a801 100644
--- a/src/qml/qml/qqmlengine.cpp
+++ b/src/qml/qml/qqmlengine.cpp
@@ -554,6 +554,7 @@ QQmlEngine::QQmlEngine(QQmlEnginePrivate &dd, QObject *parent)
QQmlEngine::~QQmlEngine()
{
Q_D(QQmlEngine);
+ handle()->inShutdown = true;
QJSEnginePrivate::removeFromDebugServer(this);
// Emit onDestruction signals for the root context before
diff --git a/src/quicktestutils/qml/qmlutils.cpp b/src/quicktestutils/qml/qmlutils.cpp
index 6cc54af356..64edc91a1c 100644
--- a/src/quicktestutils/qml/qmlutils.cpp
+++ b/src/quicktestutils/qml/qmlutils.cpp
@@ -112,14 +112,14 @@ QQmlTestMessageHandler::~QQmlTestMessageHandler()
bool gcDone(const QV4::ExecutionEngine *engine) {
- // always true as long as the gc is non-incremental
- Q_UNUSED(engine);
- return true;
+ return !engine->memoryManager->gcStateMachine->inProgress();
}
void gc(QV4::ExecutionEngine &engine, GCFlags flags)
{
engine.memoryManager->runGC();
+ while (!gcDone(&engine))
+ engine.memoryManager->gcStateMachine->step();
if (int(GCFlags::DontSendPostedEvents) & int(flags))
return;
QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
diff --git a/tests/auto/qml/qqmlqt/tst_qqmlqt.cpp b/tests/auto/qml/qqmlqt/tst_qqmlqt.cpp
index f66e0c5ffc..397d506dd6 100644
--- a/tests/auto/qml/qqmlqt/tst_qqmlqt.cpp
+++ b/tests/auto/qml/qqmlqt/tst_qqmlqt.cpp
@@ -28,6 +28,7 @@
#include <QtQuickTestUtils/private/qmlutils_p.h>
#include <private/qtenvironmentvariables_p.h> // for qTzSet()
+#include <private/qqmlengine_p.h>
class tst_qqmlqt : public QQmlDataTest
{
@@ -1280,6 +1281,7 @@ void tst_qqmlqt::later_data()
void tst_qqmlqt::later()
{
+ QQmlEngine engine;
QFETCH(QString, function);
QFETCH(QStringList, expectedWarnings);
QFETCH(QStringList, propNames);
diff --git a/tests/auto/qml/qv4mm/tst_qv4mm.cpp b/tests/auto/qml/qv4mm/tst_qv4mm.cpp
index a36c8a8358..205883a176 100644
--- a/tests/auto/qml/qv4mm/tst_qv4mm.cpp
+++ b/tests/auto/qml/qv4mm/tst_qv4mm.cpp
@@ -9,6 +9,7 @@
#include <private/qv4mm_p.h>
#include <private/qv4qobjectwrapper_p.h>
#include <private/qjsvalue_p.h>
+#include <private/qqmlengine_p.h>
#include <QtQuickTestUtils/private/qmlutils_p.h>
@@ -93,6 +94,8 @@ void tst_qv4mm::accessParentOnDestruction()
QCOMPARE(obj->property("iterations").toInt(), 100);
QCOMPARE(obj->property("creations").toInt(), 100);
gc(engine); // ensure incremental gc has finished, and collected all objects
+ // TODO: investigaet whether we really need two gc rounds for incremental gc
+ gc(engine); // ensure incremental gc has finished, and collected all objects
QCOMPARE(obj->property("destructions").toInt(), 100);
}
@@ -184,6 +187,10 @@ void tst_qv4mm::cleanInternalClasses()
// Make sure that all dangling ICs are actually gone.
gc(engine);
+ // NOTE: If we allocate new ICs during gc (potentially triggered on alloc),
+ // then they will survive the previous gc call
+ // run gc again to ensure that a full gc cycle happens
+ gc(engine);
// Now the GC has removed the ICs we originally added by adding properties.
QVERIFY(prevIC->d()->transitions.empty() || prevIC->d()->transitions.front().lookup == nullptr);
@@ -199,8 +206,8 @@ void tst_qv4mm::cleanInternalClasses()
void tst_qv4mm::createObjectsOnDestruction()
{
- QLoggingCategory::setFilterRules("qt.qml.gc.*=false");
QQmlEngine engine;
+
QQmlComponent component(&engine, testFileUrl("createobjects.qml"));
std::unique_ptr<QObject> obj(component.create());
QVERIFY(obj);