From 464b878b973710077b2b92b1682d6f38c83554dd Mon Sep 17 00:00:00 2001 From: Lars Knoll Date: Sun, 26 Aug 2018 15:07:50 +0200 Subject: Implement support for WeakMap Change-Id: Id23e80fe5918ba7dc897568123bf3db4d35e9092 Reviewed-by: Simon Hausmann --- src/qml/jsruntime/qv4engine.cpp | 5 ++ src/qml/jsruntime/qv4engine_p.h | 4 + src/qml/jsruntime/qv4estable.cpp | 22 ++++- src/qml/jsruntime/qv4estable_p.h | 4 +- src/qml/jsruntime/qv4global_p.h | 4 + src/qml/jsruntime/qv4mapobject.cpp | 160 +++++++++++++++++++++++++++++-------- src/qml/jsruntime/qv4mapobject_p.h | 35 +++++++- src/qml/jsruntime/qv4setobject.cpp | 2 +- 8 files changed, 195 insertions(+), 41 deletions(-) (limited to 'src/qml/jsruntime') diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp index d6cfb1dcc1..7087e49b30 100644 --- a/src/qml/jsruntime/qv4engine.cpp +++ b/src/qml/jsruntime/qv4engine.cpp @@ -485,6 +485,10 @@ ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine) sequencePrototype()->cast()->init(); #endif + jsObjects[WeakMap_Ctor] = memoryManager->allocate(global); + jsObjects[WeakMapProto] = memoryManager->allocate(); + static_cast(weakMapPrototype())->init(this, weakMapCtor()); + jsObjects[Map_Ctor] = memoryManager->allocate(global); jsObjects[MapProto] = memoryManager->allocate(); static_cast(mapPrototype())->init(this, mapCtor()); @@ -548,6 +552,7 @@ ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine) globalObject->defineDefaultProperty(QStringLiteral("ArrayBuffer"), *arrayBufferCtor()); globalObject->defineDefaultProperty(QStringLiteral("DataView"), *dataViewCtor()); globalObject->defineDefaultProperty(QStringLiteral("Set"), *setCtor()); + globalObject->defineDefaultProperty(QStringLiteral("WeakMap"), *weakMapCtor()); globalObject->defineDefaultProperty(QStringLiteral("Map"), *mapCtor()); for (int i = 0; i < NTypedArrayTypes; ++i) diff --git a/src/qml/jsruntime/qv4engine_p.h b/src/qml/jsruntime/qv4engine_p.h index 0f4a4a75a2..028615abfb 100644 --- a/src/qml/jsruntime/qv4engine_p.h +++ b/src/qml/jsruntime/qv4engine_p.h @@ -166,6 +166,7 @@ public: ArrayBufferProto, DataViewProto, SetProto, + WeakMapProto, MapProto, IntrinsicTypedArrayProto, ValueTypeProto, @@ -198,6 +199,7 @@ public: ArrayBuffer_Ctor, DataView_Ctor, Set_Ctor, + WeakMap_Ctor, Map_Ctor, IntrinsicTypedArray_Ctor, @@ -235,6 +237,7 @@ public: FunctionObject *arrayBufferCtor() const { return reinterpret_cast(jsObjects + ArrayBuffer_Ctor); } FunctionObject *dataViewCtor() const { return reinterpret_cast(jsObjects + DataView_Ctor); } FunctionObject *setCtor() const { return reinterpret_cast(jsObjects + Set_Ctor); } + FunctionObject *weakMapCtor() const { return reinterpret_cast(jsObjects + WeakMap_Ctor); } FunctionObject *mapCtor() const { return reinterpret_cast(jsObjects + Map_Ctor); } FunctionObject *intrinsicTypedArrayCtor() const { return reinterpret_cast(jsObjects + IntrinsicTypedArray_Ctor); } FunctionObject *typedArrayCtors; @@ -269,6 +272,7 @@ public: Object *arrayBufferPrototype() const { return reinterpret_cast(jsObjects + ArrayBufferProto); } Object *dataViewPrototype() const { return reinterpret_cast(jsObjects + DataViewProto); } Object *setPrototype() const { return reinterpret_cast(jsObjects + SetProto); } + Object *weakMapPrototype() const { return reinterpret_cast(jsObjects + WeakMapProto); } Object *mapPrototype() const { return reinterpret_cast(jsObjects + MapProto); } Object *intrinsicTypedArrayPrototype() const { return reinterpret_cast(jsObjects + IntrinsicTypedArrayProto); } Object *typedArrayPrototype; diff --git a/src/qml/jsruntime/qv4estable.cpp b/src/qml/jsruntime/qv4estable.cpp index 55b7407000..4b0eddb989 100644 --- a/src/qml/jsruntime/qv4estable.cpp +++ b/src/qml/jsruntime/qv4estable.cpp @@ -38,6 +38,7 @@ ****************************************************************************/ #include "qv4estable_p.h" +#include "qv4object_p.h" using namespace QV4; @@ -67,10 +68,11 @@ ESTable::~ESTable() m_values = nullptr; } -void ESTable::markObjects(MarkStack *s) +void ESTable::markObjects(MarkStack *s, bool isWeakMap) { for (uint i = 0; i < m_size; ++i) { - m_keys[i].mark(s); + if (!isWeakMap) + m_keys[i].mark(s); m_values[i].mark(s); } } @@ -179,3 +181,19 @@ void ESTable::iterate(uint idx, Value *key, Value *value) *value = m_values[idx]; } +void ESTable::removeUnmarkedKeys() +{ + uint idx = 0; + uint toIdx = 0; + for (; idx < m_size; ++idx) { + Q_ASSERT(m_keys[idx].isObject()); + Object &o = static_cast(m_keys[idx]); + if (o.d()->isMarked()) { + m_keys[toIdx] = m_keys[idx]; + m_values[toIdx] = m_values[idx]; + ++toIdx; + } + } + m_size = toIdx; +} + diff --git a/src/qml/jsruntime/qv4estable_p.h b/src/qml/jsruntime/qv4estable_p.h index c665467760..f54fc37a7b 100644 --- a/src/qml/jsruntime/qv4estable_p.h +++ b/src/qml/jsruntime/qv4estable_p.h @@ -64,7 +64,7 @@ public: ESTable(); ~ESTable(); - void markObjects(MarkStack *s); + void markObjects(MarkStack *s, bool isWeakMap); void clear(); void set(const Value &k, const Value &v); bool has(const Value &k) const; @@ -73,6 +73,8 @@ public: uint size() const; void iterate(uint idx, Value *k, Value *v); + void removeUnmarkedKeys(); + private: Value *m_keys = nullptr; Value *m_values = nullptr; diff --git a/src/qml/jsruntime/qv4global_p.h b/src/qml/jsruntime/qv4global_p.h index e812894a97..5019d4af3a 100644 --- a/src/qml/jsruntime/qv4global_p.h +++ b/src/qml/jsruntime/qv4global_p.h @@ -195,6 +195,8 @@ namespace Heap { struct DataView; struct TypedArray; + struct MapObject; + template struct Pointer; } @@ -242,6 +244,8 @@ struct ArrayBuffer; struct DataView; struct TypedArray; +struct MapObject; + // ReturnedValue is used to return values from runtime methods // the type has to be a primitive type (no struct or union), so that the compiler // will return it in a register on all platforms. diff --git a/src/qml/jsruntime/qv4mapobject.cpp b/src/qml/jsruntime/qv4mapobject.cpp index ca9e1723f9..e8467a17b0 100644 --- a/src/qml/jsruntime/qv4mapobject.cpp +++ b/src/qml/jsruntime/qv4mapobject.cpp @@ -45,18 +45,29 @@ using namespace QV4; +DEFINE_OBJECT_VTABLE(WeakMapCtor); DEFINE_OBJECT_VTABLE(MapCtor); DEFINE_OBJECT_VTABLE(MapObject); +void Heap::WeakMapCtor::init(QV4::ExecutionContext *scope) +{ + Heap::FunctionObject::init(scope, QStringLiteral("WeakMap")); +} + void Heap::MapCtor::init(QV4::ExecutionContext *scope) { Heap::FunctionObject::init(scope, QStringLiteral("Map")); } -ReturnedValue MapCtor::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *) +ReturnedValue WeakMapCtor::construct(const FunctionObject *f, const Value *argv, int argc, const Value *, bool weakMap) { Scope scope(f); Scoped a(scope, scope.engine->memoryManager->allocate()); + if (weakMap) { + a->setPrototypeOf(scope.engine->weakMapPrototype()); + scope.engine->memoryManager->registerWeakMap(a->d()); + } + a->d()->isWeakMap = weakMap; if (argc > 0) { ScopedValue iterable(scope, argv[0]); @@ -73,19 +84,20 @@ ReturnedValue MapCtor::virtualCallAsConstructor(const FunctionObject *f, const V ScopedFunctionObject adder(scope, a->get(ScopedString(scope, scope.engine->newString(QString::fromLatin1("set"))))); if (!adder) return scope.engine->throwTypeError(); - ScopedObject iter(scope, Runtime::method_getIterator(scope.engine, iterable, true)); - CHECK_EXCEPTION(); - if (!iter) - return a.asReturnedValue(); + ScopedObject iter(scope, Runtime::method_getIterator(scope.engine, iterable, true)); + if (scope.hasException()) + return Encode::undefined(); + Q_ASSERT(iter); Value *nextValue = scope.alloc(1); ScopedValue done(scope); forever { done = Runtime::method_iteratorNext(scope.engine, iter, nextValue); - CHECK_EXCEPTION(); + if (scope.hasException()) + return Encode::undefined(); if (done->toBoolean()) - return a.asReturnedValue(); + return a->asReturnedValue(); adder->call(a, nextValue, 1); if (scope.engine->hasException) { @@ -95,15 +107,44 @@ ReturnedValue MapCtor::virtualCallAsConstructor(const FunctionObject *f, const V } } } - return a.asReturnedValue(); + return a->asReturnedValue(); +} + +ReturnedValue WeakMapCtor::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget) +{ + return construct(f, argv, argc, newTarget, true); } -ReturnedValue MapCtor::virtualCall(const FunctionObject *f, const Value *, const Value *, int) +ReturnedValue WeakMapCtor::virtualCall(const FunctionObject *f, const Value *, const Value *, int) { Scope scope(f); - return scope.engine->throwTypeError(QString::fromLatin1("Map requires new")); + return scope.engine->throwTypeError(QString::fromLatin1("(Weak)Map requires new")); } + +ReturnedValue MapCtor::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget) +{ + return construct(f, argv, argc, newTarget, false); +} + +void WeakMapPrototype::init(ExecutionEngine *engine, Object *ctor) +{ + Scope scope(engine); + ScopedObject o(scope); + ctor->defineReadonlyConfigurableProperty(engine->id_length(), Primitive::fromInt32(0)); + ctor->defineReadonlyProperty(engine->id_prototype(), (o = this)); + defineDefaultProperty(engine->id_constructor(), (o = ctor)); + + defineDefaultProperty(QStringLiteral("delete"), method_delete, 1); + defineDefaultProperty(QStringLiteral("get"), method_get, 1); + defineDefaultProperty(QStringLiteral("has"), method_has, 1); + defineDefaultProperty(QStringLiteral("set"), method_set, 2); + + ScopedString val(scope, engine->newString(QLatin1String("WeakMap"))); + defineReadonlyConfigurableProperty(engine->symbol_toStringTag(), val); +} + + void MapPrototype::init(ExecutionEngine *engine, Object *ctor) { Scope scope(engine); @@ -119,7 +160,7 @@ void MapPrototype::init(ExecutionEngine *engine, Object *ctor) defineDefaultProperty(QStringLiteral("get"), method_get, 1); defineDefaultProperty(QStringLiteral("has"), method_has, 1); defineDefaultProperty(QStringLiteral("keys"), method_keys, 0); - defineDefaultProperty(QStringLiteral("set"), method_set, 0); + defineDefaultProperty(QStringLiteral("set"), method_set, 2); defineAccessorProperty(QStringLiteral("size"), method_get_size, nullptr); defineDefaultProperty(QStringLiteral("values"), method_values, 0); @@ -142,42 +183,97 @@ void Heap::MapObject::init() void Heap::MapObject::destroy() { delete esTable; - esTable = 0; + esTable = nullptr; +} + +void Heap::MapObject::removeUnmarkedKeys() +{ + esTable->removeUnmarkedKeys(); } void Heap::MapObject::markObjects(Heap::Base *that, MarkStack *markStack) { MapObject *m = static_cast(that); - m->esTable->markObjects(markStack); + m->esTable->markObjects(markStack, m->isWeakMap); Object::markObjects(that, markStack); } +ReturnedValue WeakMapPrototype::method_delete(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped that(scope, thisObject); + if (!that || !that->d()->isWeakMap) + return scope.engine->throwTypeError(); + if (!argc || !argv[0].isObject()) + return Encode(false); + + return Encode(that->d()->esTable->remove(argv[0])); + +} + +ReturnedValue WeakMapPrototype::method_get(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped that(scope, thisObject); + if (!that || !that->d()->isWeakMap) + return scope.engine->throwTypeError(); + if (!argc || !argv[0].isObject()) + return Encode::undefined(); + + return that->d()->esTable->get(argv[0]); +} + +ReturnedValue WeakMapPrototype::method_has(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped that(scope, thisObject); + if (!that || !that->d()->isWeakMap) + return scope.engine->throwTypeError(); + if (!argc || !argv[0].isObject()) + return Encode(false); + + return Encode(that->d()->esTable->has(argv[0])); +} + +ReturnedValue WeakMapPrototype::method_set(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped that(scope, thisObject); + if ((!that || !that->d()->isWeakMap) || + (!argc || !argv[0].isObject())) + return scope.engine->throwTypeError(); + + that->d()->esTable->set(argv[0], argc > 1 ? argv[1] : Primitive::undefinedValue()); + return that.asReturnedValue(); +} + + ReturnedValue MapPrototype::method_clear(const FunctionObject *b, const Value *thisObject, const Value *, int) { Scope scope(b); Scoped that(scope, thisObject); - if (!that) + if (!that || that->d()->isWeakMap) return scope.engine->throwTypeError(); that->d()->esTable->clear(); return Encode::undefined(); } -ReturnedValue MapPrototype::method_delete(const FunctionObject *b, const Value *thisObject, const Value *argv, int) +ReturnedValue MapPrototype::method_delete(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { Scope scope(b); Scoped that(scope, thisObject); - if (!that) + if (!that || that->d()->isWeakMap) return scope.engine->throwTypeError(); - return Encode(that->d()->esTable->remove(argv[0])); + return Encode(that->d()->esTable->remove(argc ? argv[0] : Primitive::undefinedValue())); } ReturnedValue MapPrototype::method_entries(const FunctionObject *b, const Value *thisObject, const Value *, int) { Scope scope(b); Scoped that(scope, thisObject); - if (!that) + if (!that || that->d()->isWeakMap) return scope.engine->throwTypeError(); Scoped ao(scope, scope.engine->newMapIteratorObject(that)); @@ -189,7 +285,7 @@ ReturnedValue MapPrototype::method_forEach(const FunctionObject *b, const Value { Scope scope(b); Scoped that(scope, thisObject); - if (!that) + if (!that || that->d()->isWeakMap) return scope.engine->throwTypeError(); ScopedFunctionObject callbackfn(scope, argv[0]); @@ -211,31 +307,31 @@ ReturnedValue MapPrototype::method_forEach(const FunctionObject *b, const Value return Encode::undefined(); } -ReturnedValue MapPrototype::method_get(const FunctionObject *b, const Value *thisObject, const Value *argv, int) +ReturnedValue MapPrototype::method_get(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { Scope scope(b); Scoped that(scope, thisObject); - if (!that) + if (!that || that->d()->isWeakMap) return scope.engine->throwTypeError(); - return that->d()->esTable->get(argv[0]); + return that->d()->esTable->get(argc ? argv[0] : Primitive::undefinedValue()); } -ReturnedValue MapPrototype::method_has(const FunctionObject *b, const Value *thisObject, const Value *argv, int) +ReturnedValue MapPrototype::method_has(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { Scope scope(b); Scoped that(scope, thisObject); - if (!that) + if (!that || that->d()->isWeakMap) return scope.engine->throwTypeError(); - return Encode(that->d()->esTable->has(argv[0])); + return Encode(that->d()->esTable->has(argc ? argv[0] : Primitive::undefinedValue())); } ReturnedValue MapPrototype::method_keys(const FunctionObject *b, const Value *thisObject, const Value *, int) { Scope scope(b); Scoped that(scope, thisObject); - if (!that) + if (!that || that->d()->isWeakMap) return scope.engine->throwTypeError(); Scoped ao(scope, scope.engine->newMapIteratorObject(that)); @@ -243,14 +339,14 @@ ReturnedValue MapPrototype::method_keys(const FunctionObject *b, const Value *th return ao->asReturnedValue(); } -ReturnedValue MapPrototype::method_set(const FunctionObject *b, const Value *thisObject, const Value *argv, int) +ReturnedValue MapPrototype::method_set(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { Scope scope(b); Scoped that(scope, thisObject); - if (!that) + if (!that || that->d()->isWeakMap) return scope.engine->throwTypeError(); - that->d()->esTable->set(argv[0], argv[1]); + that->d()->esTable->set(argc ? argv[0] : Primitive::undefinedValue(), argc > 1 ? argv[1] : Primitive::undefinedValue()); return that.asReturnedValue(); } @@ -258,7 +354,7 @@ ReturnedValue MapPrototype::method_get_size(const FunctionObject *b, const Value { Scope scope(b); Scoped that(scope, thisObject); - if (!that) + if (!that || that->d()->isWeakMap) return scope.engine->throwTypeError(); return Encode(that->d()->esTable->size()); @@ -268,12 +364,10 @@ ReturnedValue MapPrototype::method_values(const FunctionObject *b, const Value * { Scope scope(b); Scoped that(scope, thisObject); - if (!that) + if (!that || that->d()->isWeakMap) return scope.engine->throwTypeError(); Scoped ao(scope, scope.engine->newMapIteratorObject(that)); ao->d()->iterationKind = IteratorKind::ValueIteratorKind; return ao->asReturnedValue(); } - - diff --git a/src/qml/jsruntime/qv4mapobject_p.h b/src/qml/jsruntime/qv4mapobject_p.h index 6793612bcb..a0fae2a14a 100644 --- a/src/qml/jsruntime/qv4mapobject_p.h +++ b/src/qml/jsruntime/qv4mapobject_p.h @@ -64,7 +64,11 @@ class ESTable; namespace Heap { -struct MapCtor : FunctionObject { +struct WeakMapCtor : FunctionObject { + void init(QV4::ExecutionContext *scope); +}; + +struct MapCtor : WeakMapCtor { void init(QV4::ExecutionContext *scope); }; @@ -72,19 +76,32 @@ struct MapObject : Object { static void markObjects(Heap::Base *that, MarkStack *markStack); void init(); void destroy(); + void removeUnmarkedKeys(); + + MapObject *nextWeakMap; ESTable *esTable; + bool isWeakMap; }; } -struct MapCtor: FunctionObject +struct WeakMapCtor: FunctionObject { - V4_OBJECT2(MapCtor, FunctionObject) + V4_OBJECT2(WeakMapCtor, FunctionObject) + + static ReturnedValue construct(const FunctionObject *f, const Value *argv, int argc, const Value *, bool weakMap); static ReturnedValue virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *); static ReturnedValue virtualCall(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc); }; +struct MapCtor : WeakMapCtor +{ + V4_OBJECT2(MapCtor, WeakMapCtor) + + static ReturnedValue virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *); +}; + struct MapObject : Object { V4_OBJECT2(MapObject, Object) @@ -92,7 +109,17 @@ struct MapObject : Object V4_NEEDS_DESTROY }; -struct MapPrototype : Object +struct WeakMapPrototype : Object +{ + void init(ExecutionEngine *engine, Object *ctor); + + static ReturnedValue method_delete(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_get(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_has(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_set(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); +}; + +struct MapPrototype : WeakMapPrototype { void init(ExecutionEngine *engine, Object *ctor); diff --git a/src/qml/jsruntime/qv4setobject.cpp b/src/qml/jsruntime/qv4setobject.cpp index 30e849bfed..a03cc3ddf8 100644 --- a/src/qml/jsruntime/qv4setobject.cpp +++ b/src/qml/jsruntime/qv4setobject.cpp @@ -139,7 +139,7 @@ void Heap::SetObject::destroy() void Heap::SetObject::markObjects(Heap::Base *that, MarkStack *markStack) { SetObject *s = static_cast(that); - s->esTable->markObjects(markStack); + s->esTable->markObjects(markStack, false); Object::markObjects(that, markStack); } -- cgit v1.2.3