ctor->defineReadonlyProperty(QStringLiteral("POSITIVE_INFINITY"), Primitive::fromDouble(qInf()));
ctor->defineReadonlyProperty(QStringLiteral("MAX_VALUE"), Primitive::fromDouble(1.7976931348623158e+308));
- #ifdef __INTEL_COMPILER
- # pragma warning( push )
- # pragma warning(disable: 239)
- #endif
+ QT_WARNING_PUSH
+ QT_WARNING_DISABLE_INTEL(239)
ctor->defineReadonlyProperty(QStringLiteral("MIN_VALUE"), Primitive::fromDouble(5e-324));
- #ifdef __INTEL_COMPILER
- # pragma warning( pop )
- #endif
+ QT_WARNING_POP
defineDefaultProperty(QStringLiteral("constructor"), (o = ctor));
- defineDefaultProperty(engine->id_toString, method_toString);
+ defineDefaultProperty(engine->id_toString(), method_toString);
defineDefaultProperty(QStringLiteral("toLocaleString"), method_toLocaleString);
- defineDefaultProperty(engine->id_valueOf, method_valueOf);
+ defineDefaultProperty(engine->id_valueOf(), method_valueOf);
defineDefaultProperty(QStringLiteral("toFixed"), method_toFixed, 1);
defineDefaultProperty(QStringLiteral("toExponential"), method_toExponential);
defineDefaultProperty(QStringLiteral("toPrecision"), method_toPrecision);
#include <QtCore/QString>
#include "qv4global_p.h"
+#include <private/qv4heap_p.h>
+ /* We cannot rely on QT_POINTER_SIZE to be set correctly on host builds. In qmldevtools the Value objects
+ are only used to store primitives, never object pointers. So we can use the 64-bit encoding. */
+ #ifdef V4_BOOTSTRAP
+ #define QV4_USE_64_BIT_VALUE_ENCODING
+ #elif QT_POINTER_SIZE == 8
+ #define QV4_USE_64_BIT_VALUE_ENCODING
+ #endif
+
QT_BEGIN_NAMESPACE
namespace QV4 {
Bit 15-17 is then used to encode other immediates.
*/
+ quint64 _val;
+
+ Q_ALWAYS_INLINE quint64 val() const { return _val; }
+ Q_ALWAYS_INLINE void setVal(quint64 v) { _val = v; }
+ Q_ALWAYS_INLINE void setValue(quint32 v) { setTagValue(tag(), v); }
+ Q_ALWAYS_INLINE void setTag(quint32 t) { setTagValue(t, value()); }
- union {
- quint64 val;
-#ifdef QV4_USE_64_BIT_VALUE_ENCODING
- Heap::Base *m;
-#else
- double dbl;
-#endif
- struct {
-#if Q_BYTE_ORDER != Q_LITTLE_ENDIAN
- uint tag;
-#endif
- union {
- uint uint_32;
- int int_32;
-#ifndef QV4_USE_64_BIT_VALUE_ENCODING
- Heap::Base *m;
-#endif
- };
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
- uint tag;
+ static inline int valueOffset() { return 0; }
+ static inline int tagOffset() { return 4; }
+ Q_ALWAYS_INLINE void setTagValue(quint32 tag, quint32 value) { _val = quint64(tag) << 32 | value; }
+ Q_ALWAYS_INLINE quint32 value() const { return _val & quint64(~quint32(0)); }
+ Q_ALWAYS_INLINE quint32 tag() const { return _val >> 32; }
+#else // !Q_LITTLE_ENDIAN
+ static inline int valueOffset() { return 4; }
+ static inline int tagOffset() { return 0; }
+ Q_ALWAYS_INLINE void setTagValue(quint32 tag, quint32 value) { _val = quint64(value) << 32 | tag; }
+ Q_ALWAYS_INLINE quint32 tag() const { return _val & quint64(~quint32(0)); }
+ Q_ALWAYS_INLINE quint32 value() const { return _val >> 32; }
#endif
- };
- };
+
- #if QT_POINTER_SIZE == 8
++#ifdef QV4_USE_64_BIT_VALUE_ENCODING
+ Q_ALWAYS_INLINE Heap::Base *m() const { Heap::Base *b; memcpy(&b, &_val, 8); return b; }
+ Q_ALWAYS_INLINE void setM(Heap::Base *b) { memcpy(&_val, &b, 8); }
- #else // QT_POINTER_SIZE == 4
++#else // !QV4_USE_64_BIT_VALUE_ENCODING
+ Q_ALWAYS_INLINE Heap::Base *m() const { Heap::Base *b; quint32 v = value(); memcpy(&b, &v, 4); return b; }
+ Q_ALWAYS_INLINE void setM(Heap::Base *b) { quint32 v; memcpy(&v, &b, 4); setValue(v); }
+#endif
+
+ Q_ALWAYS_INLINE int int_32() const { int i; quint32 v = value(); memcpy(&i, &v, 4); return i; }
+ Q_ALWAYS_INLINE void setInt_32(int i) { quint32 u; memcpy(&u, &i, 4); setValue(u); }
+ Q_ALWAYS_INLINE uint uint_32() const { return value(); }
- #if QT_POINTER_SIZE == 4
+ #ifndef QV4_USE_64_BIT_VALUE_ENCODING
enum Masks {
SilentNaNBit = 0x00040000,
NaN_Mask = 0x7ff80000,
}
// used internally in property
- inline bool isEmpty() const { return tag == Empty_Type; }
+ inline bool isEmpty() const { return tag() == Empty_Type; }
- inline bool isUndefined() const { return tag == Undefined_Type; }
- inline bool isNull() const { return tag == _Null_Type; }
- inline bool isBoolean() const { return tag == _Boolean_Type; }
+ inline bool isUndefined() const { return tag() == Undefined_Type; }
+ inline bool isNull() const { return tag() == _Null_Type; }
+ inline bool isBoolean() const { return tag ()== _Boolean_Type; }
- #if QT_POINTER_SIZE == 8
+ #ifdef QV4_USE_64_BIT_VALUE_ENCODING
- inline bool isInteger() const { return (val >> IsNumber_Shift) == 1; }
- inline bool isDouble() const { return (val >> IsDouble_Shift); }
- inline bool isNumber() const { return (val >> IsNumber_Shift); }
- inline bool isManaged() const { return !(val >> IsManaged_Shift); }
- inline bool isNullOrUndefined() const { return ((val >> IsManaged_Shift) & ~2) == 1; }
- inline bool integerCompatible() const { return ((val >> IsConvertibleToInt_Shift) & ~2) == 1; }
+ inline bool isInteger() const { return (_val >> IsNumber_Shift) == 1; }
+ inline bool isDouble() const { return (_val >> IsDouble_Shift); }
+ inline bool isNumber() const { return (_val >> IsNumber_Shift); }
+ inline bool isManaged() const { return !(_val >> IsManaged_Shift); }
+ inline bool isNullOrUndefined() const { return ((_val >> IsManaged_Shift) & ~2) == 1; }
+ inline bool integerCompatible() const { return ((_val >> IsConvertibleToInt_Shift) & ~2) == 1; }
static inline bool integerCompatible(Value a, Value b) {
return a.integerCompatible() && b.integerCompatible();
}
static inline bool bothDouble(Value a, Value b) {
return a.isDouble() && b.isDouble();
}
- double doubleValue() const {
- Q_ASSERT(isDouble());
- union {
- quint64 i;
- double d;
- } v;
- v.i = val ^ NaNEncodeMask;
- return v.d;
- }
- void setDouble(double d) {
- union {
- quint64 i;
- double d;
- } v;
- v.d = d;
- val = v.i ^ NaNEncodeMask;
- Q_ASSERT(isDouble());
- }
- inline bool isNaN() const { return (tag & 0x7fff8000) == 0x00078000; }
+ inline bool isNaN() const { return (tag() & 0x7fff8000) == 0x00078000; }
#else
- inline bool isInteger() const { return tag == _Integer_Type; }
- inline bool isDouble() const { return (tag & NotDouble_Mask) != NotDouble_Mask; }
- inline bool isNumber() const { return tag == _Integer_Type || (tag & NotDouble_Mask) != NotDouble_Mask; }
- inline bool isManaged() const { return tag == Managed_Type; }
- inline bool isNullOrUndefined() const { return (tag & IsNullOrUndefined_Mask) == Undefined_Type; }
- inline bool integerCompatible() const { return (tag & ConvertibleToInt) == ConvertibleToInt; }
+ inline bool isInteger() const { return tag() == _Integer_Type; }
+ inline bool isDouble() const { return (tag() & NotDouble_Mask) != NotDouble_Mask; }
+ inline bool isNumber() const { return tag() == _Integer_Type || (tag() & NotDouble_Mask) != NotDouble_Mask; }
+ inline bool isManaged() const { return tag() == Managed_Type; }
+ inline bool isNullOrUndefined() const { return (tag() & IsNullOrUndefined_Mask) == Undefined_Type; }
+ inline bool integerCompatible() const { return (tag() & ConvertibleToInt) == ConvertibleToInt; }
static inline bool integerCompatible(Value a, Value b) {
- return ((a.tag & b.tag) & ConvertibleToInt) == ConvertibleToInt;
+ return ((a.tag() & b.tag()) & ConvertibleToInt) == ConvertibleToInt;
}
static inline bool bothDouble(Value a, Value b) {
- return ((a.tag | b.tag) & NotDouble_Mask) != NotDouble_Mask;
+ return ((a.tag() | b.tag()) & NotDouble_Mask) != NotDouble_Mask;
}
- double doubleValue() const { Q_ASSERT(isDouble()); return dbl; }
- void setDouble(double d) { dbl = d; Q_ASSERT(isDouble()); }
- inline bool isNaN() const { return (tag & QV4::Value::NotDouble_Mask) == QV4::Value::NaN_Mask; }
+ inline bool isNaN() const { return (tag() & QV4::Value::NotDouble_Mask) == QV4::Value::NaN_Mask; }
#endif
- #if QT_POINTER_SIZE == 8
+ Q_ALWAYS_INLINE double doubleValue() const {
+ Q_ASSERT(isDouble());
+ double d;
+ quint64 v = _val;
- #if QT_POINTER_SIZE == 8
++#ifdef QV4_USE_64_BIT_VALUE_ENCODING
+ v ^= NaNEncodeMask;
+#endif
+ memcpy(&d, &v, 8);
+ return d;
+ }
+ Q_ALWAYS_INLINE void setDouble(double d) {
+ memcpy(&_val, &d, 8);
++#ifdef QV4_USE_64_BIT_VALUE_ENCODING
+ _val ^= NaNEncodeMask;
+#endif
+ Q_ASSERT(isDouble());
+ }
inline bool isString() const;
inline bool isObject() const;
inline bool isInt32() {
static inline Value fromHeapObject(Heap::Base *m)
{
Value v;
- v.m = m;
+ v.setRawValue(0);
+ v.setM(m);
- #if QT_POINTER_SIZE == 4
+ #ifndef QV4_USE_64_BIT_VALUE_ENCODING
- v.tag = Managed_Type;
+ v.setTag(Managed_Type);
#endif
return v;
}
return *this;
}
Value &operator=(Heap::Base *o) {
- m = o;
+ setM(o);
- #if QT_POINTER_SIZE == 4
+ #ifndef QV4_USE_64_BIT_VALUE_ENCODING
- tag = Managed_Type;
+ setTag(Managed_Type);
#endif
return *this;
}
}
};
-inline Managed *Value::asManaged() const
+inline bool Value::isString() const
+{
+ if (!isManaged())
+ return false;
+ return m() && m()->vtable()->isString;
+}
+inline bool Value::isObject() const
+{
+ if (!isManaged())
+ return false;
+ return m() && m()->vtable()->isObject;
+}
+
+inline bool Value::isPrimitive() const
+{
+ return !isObject();
+}
+
+inline double Value::toNumber() const
+{
+ if (isInteger())
+ return int_32();
+ if (isDouble())
+ return doubleValue();
+ return toNumberImpl();
+}
+
+
+#ifndef V4_BOOTSTRAP
+inline uint Value::asArrayIndex() const
{
- #if QT_POINTER_SIZE == 8
- if (isManaged())
- return managed();
- return 0;
++#ifdef QV4_USE_64_BIT_VALUE_ENCODING
+ if (!isNumber())
+ return UINT_MAX;
+ if (isInteger())
+ return int_32() >= 0 ? (uint)int_32() : UINT_MAX;
+#else
+ if (isInteger() && int_32() >= 0)
+ return (uint)int_32();
+ if (!isDouble())
+ return UINT_MAX;
+#endif
+ double d = doubleValue();
+ uint idx = (uint)d;
+ if (idx != d)
+ return UINT_MAX;
+ return idx;
}
+#endif
+
+inline
+ReturnedValue Heap::Base::asReturnedValue() const
+{
+ return Value::fromHeapObject(const_cast<Heap::Base *>(this)).asReturnedValue();
+}
+
+
struct Q_QML_PRIVATE_EXPORT Primitive : public Value
{
inline Primitive Primitive::undefinedValue()
{
Primitive v;
- #if QT_POINTER_SIZE == 8
+ #ifdef QV4_USE_64_BIT_VALUE_ENCODING
- v.val = quint64(Undefined_Type) << Tag_Shift;
+ v.setRawValue(quint64(Undefined_Type) << Tag_Shift);
#else
- v.tag = Undefined_Type;
- v.int_32 = 0;
+ v.setRawValue(0);
+ v.setTag(Undefined_Type);
+ v.setValue(0);
#endif
return v;
}
return v;
}
-template <typename T>
-struct TypedValue : public Value
+inline Primitive Primitive::nullValue()
{
- template<typename X>
- TypedValue &operator =(X *x) {
- m = x;
+ Primitive v;
- #if QT_POINTER_SIZE == 8
+ #ifndef QV4_USE_64_BIT_VALUE_ENCODING
- tag = Managed_Type;
+ v.setRawValue(quint64(_Null_Type) << Tag_Shift);
+#else
+ v.setTagValue(_Null_Type, 0);
#endif
- return *this;
- }
- TypedValue &operator =(T *t);
- TypedValue &operator =(const Scoped<T> &v);
-// TypedValue &operator =(const ManagedRef<T> &v);
-
- TypedValue &operator =(const TypedValue<T> &t);
+ return v;
+}
- bool operator!() const { return !managed(); }
+inline Primitive Primitive::fromBoolean(bool b)
+{
+ Primitive v;
+ v.setTagValue(_Boolean_Type, b);
+ return v;
+}
- operator T *() { return static_cast<T *>(managed()); }
- T *operator->() { return static_cast<T *>(managed()); }
- const T *operator->() const { return static_cast<T *>(managed()); }
- T *getPointer() const { return static_cast<T *>(managed()); }
+inline Primitive Primitive::fromDouble(double d)
+{
+ Primitive v;
+ v.setDouble(d);
+ return v;
+}
- void mark(ExecutionEngine *e) { if (managed()) managed()->mark(e); }
-};
-typedef TypedValue<String> StringValue;
+inline Primitive Primitive::fromInt32(int i)
+{
+ Primitive v;
+ v.setTagValue(_Integer_Type, 0); // For mingw482, because it complains, and for VS9, because of internal compiler errors.
+ v.setInt_32(i);
+ return v;
+}
+inline Primitive Primitive::fromUInt32(uint i)
+{
+ Primitive v;
+ if (i < INT_MAX) {
+ v.setTagValue(_Integer_Type, 0); // For mingw482, because it complains, and for VS9, because of internal compiler errors.
+ v.setInt_32((int)i);
+ } else {
+ v.setDouble(i);
+ }
+ return v;
+}
struct Encode {
static ReturnedValue undefined() {
--- /dev/null
- bool sweepChunk(MemoryManager::Data::ChunkHeader *header, uint *itemsInUse, ExecutionEngine *engine)
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL21$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** As a special exception, The Qt Company gives you certain additional
+** rights. These rights are described in The Qt Company LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qv4engine_p.h"
+#include "qv4object_p.h"
+#include "qv4objectproto_p.h"
+#include "qv4mm_p.h"
+#include "qv4qobjectwrapper_p.h"
+#include <qqmlengine.h>
+#include "PageAllocation.h"
+#include "StdLibExtras.h"
+
+#include <QTime>
+#include <QVector>
+#include <QVector>
+#include <QMap>
+
+#include <iostream>
+#include <cstdlib>
+#include <algorithm>
+#include "qv4alloca_p.h"
+#include "qv4profiling_p.h"
+
+#ifdef V4_USE_VALGRIND
+#include <valgrind/valgrind.h>
+#include <valgrind/memcheck.h>
+#endif
+
+#if OS(QNX)
+#include <sys/storage.h> // __tls()
+#endif
+
+#if USE(PTHREADS) && HAVE(PTHREAD_NP_H)
+#include <pthread_np.h>
+#endif
+
+using namespace WTF;
+
+QT_BEGIN_NAMESPACE
+
+using namespace QV4;
+
+struct MemoryManager::Data
+{
+ struct ChunkHeader {
+ Heap::Base freeItems;
+ ChunkHeader *nextNonFull;
+ char *itemStart;
+ char *itemEnd;
+ int itemSize;
+ };
+
+ bool gcBlocked;
+ bool aggressiveGC;
+ bool gcStats;
+ ExecutionEngine *engine;
+
+ enum { MaxItemSize = 512 };
+ ChunkHeader *nonFullChunks[MaxItemSize/16];
+ uint nChunks[MaxItemSize/16];
+ uint availableItems[MaxItemSize/16];
+ uint allocCount[MaxItemSize/16];
+ int totalItems;
+ int totalAlloc;
+ uint maxShift;
+ std::size_t maxChunkSize;
+ QVector<PageAllocation> heapChunks;
++ std::size_t unmanagedHeapSize; // 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;
+
+ struct LargeItem {
+ LargeItem *next;
+ size_t size;
+ void *data;
+
+ Heap::Base *heapObject() {
+ return reinterpret_cast<Heap::Base *>(&data);
+ }
+ };
+
+ LargeItem *largeItems;
+ std::size_t totalLargeItemsAllocated;
+
+ // statistics:
+#ifdef DETAILED_MM_STATS
+ QVector<unsigned> allocSizeCounters;
+#endif // DETAILED_MM_STATS
+
+ Data()
+ : gcBlocked(false)
+ , engine(0)
+ , totalItems(0)
+ , totalAlloc(0)
+ , maxShift(6)
+ , maxChunkSize(32*1024)
++ , unmanagedHeapSize(0)
++ , unmanagedHeapSizeGCLimit(64 * 1024)
+ , largeItems(0)
+ , totalLargeItemsAllocated(0)
+ {
+ memset(nonFullChunks, 0, sizeof(nonFullChunks));
+ memset(nChunks, 0, sizeof(nChunks));
+ memset(availableItems, 0, sizeof(availableItems));
+ memset(allocCount, 0, sizeof(allocCount));
+ aggressiveGC = !qgetenv("QV4_MM_AGGRESSIVE_GC").isEmpty();
+ gcStats = !qgetenv("QV4_MM_STATS").isEmpty();
+
+ QByteArray overrideMaxShift = qgetenv("QV4_MM_MAXBLOCK_SHIFT");
+ bool ok;
+ uint override = overrideMaxShift.toUInt(&ok);
+ if (ok && override <= 11 && override > 0)
+ maxShift = override;
+
+ QByteArray maxChunkString = qgetenv("QV4_MM_MAX_CHUNK_SIZE");
+ std::size_t tmpMaxChunkSize = maxChunkString.toUInt(&ok);
+ if (ok)
+ maxChunkSize = tmpMaxChunkSize;
+ }
+
+ ~Data()
+ {
+ for (QVector<PageAllocation>::iterator i = heapChunks.begin(), ei = heapChunks.end(); i != ei; ++i) {
+ Q_V4_PROFILE_DEALLOC(engine, 0, i->size(), Profiling::HeapPage);
+ i->deallocate();
+ }
+ }
+};
+
+namespace {
+
- // qDebug("chunk @ %p, size = %lu, in use: %s, mark bit: %s",
- // item, m->size, (m->inUse ? "yes" : "no"), (m->markBit ? "true" : "false"));
++bool sweepChunk(MemoryManager::Data::ChunkHeader *header, uint *itemsInUse, ExecutionEngine *engine, std::size_t *unmanagedHeapSize)
+{
++ Q_ASSERT(unmanagedHeapSize);
++
+ bool isEmpty = true;
+ Heap::Base *tail = &header->freeItems;
+// qDebug("chunkStart @ %p, size=%x, pos=%x", header->itemStart, header->itemSize, header->itemSize>>4);
+#ifdef V4_USE_VALGRIND
+ VALGRIND_DISABLE_ERROR_REPORTING;
+#endif
+ for (char *item = header->itemStart; item <= header->itemEnd; item += header->itemSize) {
+ Heap::Base *m = reinterpret_cast<Heap::Base *>(item);
- Heap::Base *MemoryManager::allocData(std::size_t size)
++// qDebug("chunk @ %p, in use: %s, mark bit: %s",
++// item, (m->inUse() ? "yes" : "no"), (m->isMarked() ? "true" : "false"));
+
+ Q_ASSERT((qintptr) item % 16 == 0);
+
+ if (m->isMarked()) {
+ Q_ASSERT(m->inUse());
+ m->clearMarkBit();
+ isEmpty = false;
+ ++(*itemsInUse);
+ } else {
+ if (m->inUse()) {
+// qDebug() << "-- collecting it." << m << tail << m->nextFree();
+#ifdef V4_USE_VALGRIND
+ VALGRIND_ENABLE_ERROR_REPORTING;
+#endif
++ if (std::size_t(header->itemSize) == MemoryManager::align(sizeof(Heap::String)) && m->vtable()->isString) {
++ std::size_t heapBytes = static_cast<Heap::String *>(m)->retainedTextSize();
++ Q_ASSERT(*unmanagedHeapSize >= heapBytes);
++// qDebug() << "-- it's a string holding on to" << heapBytes << "bytes";
++ *unmanagedHeapSize -= heapBytes;
++ }
++
+ if (m->vtable()->destroy)
+ m->vtable()->destroy(m);
+
+ memset(m, 0, header->itemSize);
+#ifdef V4_USE_VALGRIND
+ VALGRIND_DISABLE_ERROR_REPORTING;
+ VALGRIND_MEMPOOL_FREE(engine->memoryManager, m);
+#endif
+ Q_V4_PROFILE_DEALLOC(engine, m, header->itemSize, Profiling::SmallItem);
+ ++(*itemsInUse);
+ }
+ // Relink all free blocks to rewrite references to any released chunk.
+ tail->setNextFree(m);
+ tail = m;
+ }
+ }
+ tail->setNextFree(0);
+#ifdef V4_USE_VALGRIND
+ VALGRIND_ENABLE_ERROR_REPORTING;
+#endif
+ return isEmpty;
+}
+
+} // namespace
+
+MemoryManager::MemoryManager(ExecutionEngine *engine)
+ : m_d(new Data)
+ , m_persistentValues(new PersistentValueStorage(engine))
+ , m_weakValues(new PersistentValueStorage(engine))
+{
+#ifdef V4_USE_VALGRIND
+ VALGRIND_CREATE_MEMPOOL(this, 0, true);
+#endif
+ m_d->engine = engine;
+}
+
- if (m_d->totalLargeItemsAllocated > 8 * 1024 * 1024)
++Heap::Base *MemoryManager::allocData(std::size_t size, std::size_t unmanagedSize)
+{
+ if (m_d->aggressiveGC)
+ runGC();
+#ifdef DETAILED_MM_STATS
+ willAllocate(size);
+#endif // DETAILED_MM_STATS
+
+ Q_ASSERT(size >= 16);
+ Q_ASSERT(size % 16 == 0);
+
++// qDebug() << "unmanagedHeapSize:" << m_d->unmanagedHeapSize << "limit:" << m_d->unmanagedHeapSizeGCLimit << "unmanagedSize:" << unmanagedSize;
++ m_d->unmanagedHeapSize += unmanagedSize;
++ bool didGCRun = false;
++ if (m_d->unmanagedHeapSize > m_d->unmanagedHeapSizeGCLimit) {
++ runGC();
++
++ if (m_d->unmanagedHeapSizeGCLimit <= m_d->unmanagedHeapSize)
++ m_d->unmanagedHeapSizeGCLimit = std::max(m_d->unmanagedHeapSizeGCLimit, m_d->unmanagedHeapSize) * 2;
++ else if (m_d->unmanagedHeapSize * 4 <= m_d->unmanagedHeapSizeGCLimit)
++ m_d->unmanagedHeapSizeGCLimit /= 2;
++ else if (m_d->unmanagedHeapSizeGCLimit - m_d->unmanagedHeapSize < 5 * unmanagedSize)
++ // try preventing running the GC all the time when we're just below the threshold limit and manage to collect just enough to do this one allocation
++ m_d->unmanagedHeapSizeGCLimit += std::max(std::size_t(8 * 1024), 5 * unmanagedSize);
++ didGCRun = true;
++ }
++
+ size_t pos = size >> 4;
+
+ // doesn't fit into a small bucket
+ if (size >= MemoryManager::Data::MaxItemSize) {
- if (m_d->allocCount[pos] > (m_d->availableItems[pos] >> 1) && m_d->totalAlloc > (m_d->totalItems >> 1) && !m_d->aggressiveGC) {
++ if (!didGCRun && m_d->totalLargeItemsAllocated > 8 * 1024 * 1024)
+ runGC();
+
+ // we use malloc for this
+ MemoryManager::Data::LargeItem *item = static_cast<MemoryManager::Data::LargeItem *>(
+ malloc(Q_V4_PROFILE_ALLOC(m_d->engine, size + sizeof(MemoryManager::Data::LargeItem),
+ Profiling::LargeItem)));
+ memset(item, 0, size + sizeof(MemoryManager::Data::LargeItem));
+ item->next = m_d->largeItems;
+ item->size = size;
+ m_d->largeItems = item;
+ m_d->totalLargeItemsAllocated += size;
+ return item->heapObject();
+ }
+
+ Heap::Base *m = 0;
+ Data::ChunkHeader *header = m_d->nonFullChunks[pos];
+ if (header) {
+ m = header->freeItems.nextFree();
+ goto found;
+ }
+
+ // try to free up space, otherwise allocate
- chunkIsEmpty[i] = sweepChunk(header, &itemsInUse[header->itemSize >> 4], m_d->engine);
++ if (!didGCRun && m_d->allocCount[pos] > (m_d->availableItems[pos] >> 1) && m_d->totalAlloc > (m_d->totalItems >> 1) && !m_d->aggressiveGC) {
+ runGC();
+ header = m_d->nonFullChunks[pos];
+ if (header) {
+ m = header->freeItems.nextFree();
+ goto found;
+ }
+ }
+
+ // no free item available, allocate a new chunk
+ {
+ // allocate larger chunks at a time to avoid excessive GC, but cap at maximum chunk size (2MB by default)
+ uint shift = ++m_d->nChunks[pos];
+ if (shift > m_d->maxShift)
+ shift = m_d->maxShift;
+ std::size_t allocSize = m_d->maxChunkSize*(size_t(1) << shift);
+ allocSize = roundUpToMultipleOf(WTF::pageSize(), allocSize);
+ PageAllocation allocation = PageAllocation::allocate(
+ Q_V4_PROFILE_ALLOC(m_d->engine, allocSize, Profiling::HeapPage),
+ OSAllocator::JSGCHeapPages);
+ m_d->heapChunks.append(allocation);
+
+ header = reinterpret_cast<Data::ChunkHeader *>(allocation.base());
+ header->itemSize = int(size);
+ header->itemStart = reinterpret_cast<char *>(allocation.base()) + roundUpToMultipleOf(16, sizeof(Data::ChunkHeader));
+ header->itemEnd = reinterpret_cast<char *>(allocation.base()) + allocation.size() - header->itemSize;
+
+ header->nextNonFull = m_d->nonFullChunks[pos];
+ m_d->nonFullChunks[pos] = header;
+
+ Heap::Base *last = &header->freeItems;
+ for (char *item = header->itemStart; item <= header->itemEnd; item += header->itemSize) {
+ Heap::Base *o = reinterpret_cast<Heap::Base *>(item);
+ last->setNextFree(o);
+ last = o;
+
+ }
+ last->setNextFree(0);
+ m = header->freeItems.nextFree();
+ const size_t increase = (header->itemEnd - header->itemStart) / header->itemSize;
+ m_d->availableItems[pos] += uint(increase);
+ m_d->totalItems += int(increase);
+#ifdef V4_USE_VALGRIND
+ VALGRIND_MAKE_MEM_NOACCESS(allocation.base(), allocSize);
+ VALGRIND_MEMPOOL_ALLOC(this, header, sizeof(Data::ChunkHeader));
+#endif
+ }
+
+ found:
+#ifdef V4_USE_VALGRIND
+ VALGRIND_MEMPOOL_ALLOC(this, m, size);
+#endif
+ Q_V4_PROFILE_ALLOC(m_d->engine, size, Profiling::SmallItem);
+
+ ++m_d->allocCount[pos];
+ ++m_d->totalAlloc;
+ header->freeItems.setNextFree(m->nextFree());
+ if (!header->freeItems.nextFree())
+ m_d->nonFullChunks[pos] = header->nextNonFull;
+ return m;
+}
+
+static void drainMarkStack(QV4::ExecutionEngine *engine, Value *markBase)
+{
+ while (engine->jsStackTop > markBase) {
+ Heap::Base *h = engine->popForGC();
+ Q_ASSERT (h->vtable()->markObjects);
+ h->vtable()->markObjects(h, engine);
+ }
+}
+
+void MemoryManager::mark()
+{
+ Value *markBase = m_d->engine->jsStackTop;
+
+ m_d->engine->markObjects();
+
+ m_persistentValues->mark(m_d->engine);
+
+ collectFromJSStack();
+
+ // 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) {
+ if (!(*it).isManaged())
+ continue;
+ if ((*it).managed()->d()->vtable() != QObjectWrapper::staticVTable())
+ continue;
+ QObjectWrapper *qobjectWrapper = static_cast<QObjectWrapper*>((*it).managed());
+ 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(m_d->engine);
+
+ if (m_d->engine->jsStackTop >= m_d->engine->jsStackLimit)
+ drainMarkStack(m_d->engine, markBase);
+ }
+
+ drainMarkStack(m_d->engine, markBase);
+}
+
+void MemoryManager::sweep(bool lastSweep)
+{
+ for (PersistentValueStorage::Iterator it = m_weakValues->begin(); it != m_weakValues->end(); ++it) {
+ if (!(*it).isManaged())
+ continue;
+ Managed *m = (*it).as<Managed>();
+ if (m->markBit())
+ continue;
+ // we need to call detroyObject on qobjectwrappers now, so that they can emit the destroyed
+ // signal before we start sweeping the heap
+ if ((*it).managed()->d()->vtable() == QObjectWrapper::staticVTable()) {
+ QObjectWrapper *qobjectWrapper = static_cast<QObjectWrapper*>((*it).managed());
+ qobjectWrapper->destroyObject(lastSweep);
+ }
+
+ (*it) = Primitive::undefinedValue();
+ }
+
+ if (MultiplyWrappedQObjectMap *multiplyWrappedQObjects = m_d->engine->m_multiplyWrappedQObjects) {
+ for (MultiplyWrappedQObjectMap::Iterator it = multiplyWrappedQObjects->begin(); it != multiplyWrappedQObjects->end();) {
+ if (!it.value().isNullOrUndefined())
+ it = multiplyWrappedQObjects->erase(it);
+ else
+ ++it;
+ }
+ }
+
+ bool *chunkIsEmpty = (bool *)alloca(m_d->heapChunks.size() * sizeof(bool));
+ uint itemsInUse[MemoryManager::Data::MaxItemSize/16];
+ memset(itemsInUse, 0, sizeof(itemsInUse));
+ memset(m_d->nonFullChunks, 0, sizeof(m_d->nonFullChunks));
+
+ for (int i = 0; i < m_d->heapChunks.size(); ++i) {
+ Data::ChunkHeader *header = reinterpret_cast<Data::ChunkHeader *>(m_d->heapChunks[i].base());
++ chunkIsEmpty[i] = sweepChunk(header, &itemsInUse[header->itemSize >> 4], m_d->engine, &m_d->unmanagedHeapSize);
+ }
+
+ QVector<PageAllocation>::iterator chunkIter = m_d->heapChunks.begin();
+ for (int i = 0; i < m_d->heapChunks.size(); ++i) {
+ Q_ASSERT(chunkIter != m_d->heapChunks.end());
+ Data::ChunkHeader *header = reinterpret_cast<Data::ChunkHeader *>(chunkIter->base());
+ const size_t pos = header->itemSize >> 4;
+ const size_t decrease = (header->itemEnd - header->itemStart) / header->itemSize;
+
+ // Release that chunk if it could have been spared since the last GC run without any difference.
+ if (chunkIsEmpty[i] && m_d->availableItems[pos] - decrease >= itemsInUse[pos]) {
+ Q_V4_PROFILE_DEALLOC(m_d->engine, 0, chunkIter->size(), Profiling::HeapPage);
+#ifdef V4_USE_VALGRIND
+ VALGRIND_MEMPOOL_FREE(this, header);
+#endif
+ --m_d->nChunks[pos];
+ m_d->availableItems[pos] -= uint(decrease);
+ m_d->totalItems -= int(decrease);
+ chunkIter->deallocate();
+ chunkIter = m_d->heapChunks.erase(chunkIter);
+ continue;
+ } else if (header->freeItems.nextFree()) {
+ header->nextNonFull = m_d->nonFullChunks[pos];
+ m_d->nonFullChunks[pos] = header;
+ }
+ ++chunkIter;
+ }
+
+ Data::LargeItem *i = m_d->largeItems;
+ Data::LargeItem **last = &m_d->largeItems;
+ while (i) {
+ Heap::Base *m = i->heapObject();
+ Q_ASSERT(m->inUse());
+ if (m->isMarked()) {
+ m->clearMarkBit();
+ last = &i->next;
+ i = i->next;
+ continue;
+ }
+ if (m->vtable()->destroy)
+ m->vtable()->destroy(m);
+
+ *last = i->next;
+ free(Q_V4_PROFILE_DEALLOC(m_d->engine, i, i->size + sizeof(Data::LargeItem),
+ Profiling::LargeItem));
+ i = *last;
+ }
+
+ // some execution contexts are allocated on the stack, make sure we clear their markBit as well
+ if (!lastSweep) {
+ Heap::ExecutionContext *ctx = engine()->current;
+ while (ctx) {
+ ctx->clearMarkBit();
+ ctx = ctx->parent;
+ }
+ }
+}
+
+bool MemoryManager::isGCBlocked() const
+{
+ return m_d->gcBlocked;
+}
+
+void MemoryManager::setGCBlocked(bool blockGC)
+{
+ m_d->gcBlocked = blockGC;
+}
+
+void MemoryManager::runGC()
+{
+ if (m_d->gcBlocked) {
+// qDebug() << "Not running GC.";
+ return;
+ }
+
+ if (!m_d->gcStats) {
+ mark();
+ sweep();
+ } else {
+ const size_t totalMem = getAllocatedMem();
+
+ QTime t;
+ t.start();
+ mark();
+ int markTime = t.elapsed();
+ t.restart();
+ const size_t usedBefore = getUsedMem();
+ int chunksBefore = m_d->heapChunks.size();
+ sweep();
+ const size_t usedAfter = getUsedMem();
+ int sweepTime = t.elapsed();
+
+ qDebug() << "========== GC ==========";
+ qDebug() << "Marked object in" << markTime << "ms.";
+ qDebug() << "Sweeped object in" << sweepTime << "ms.";
+ qDebug() << "Allocated" << totalMem << "bytes in" << m_d->heapChunks.size() << "chunks.";
+ qDebug() << "Used memory before GC:" << usedBefore;
+ qDebug() << "Used memory after GC:" << usedAfter;
+ qDebug() << "Freed up bytes:" << (usedBefore - usedAfter);
+ qDebug() << "Released chunks:" << (chunksBefore - m_d->heapChunks.size());
+ qDebug() << "======== End GC ========";
+ }
+
+ memset(m_d->allocCount, 0, sizeof(m_d->allocCount));
+ m_d->totalAlloc = 0;
+ m_d->totalLargeItemsAllocated = 0;
+}
+
+size_t MemoryManager::getUsedMem() const
+{
+ size_t usedMem = 0;
+ for (QVector<PageAllocation>::const_iterator i = m_d->heapChunks.cbegin(), ei = m_d->heapChunks.cend(); i != ei; ++i) {
+ Data::ChunkHeader *header = reinterpret_cast<Data::ChunkHeader *>(i->base());
+ for (char *item = header->itemStart; item <= header->itemEnd; item += header->itemSize) {
+ Heap::Base *m = reinterpret_cast<Heap::Base *>(item);
+ Q_ASSERT((qintptr) item % 16 == 0);
+ if (m->inUse())
+ usedMem += header->itemSize;
+ }
+ }
+ return usedMem;
+}
+
+size_t MemoryManager::getAllocatedMem() const
+{
+ size_t total = 0;
+ for (int i = 0; i < m_d->heapChunks.size(); ++i)
+ total += m_d->heapChunks.at(i).size();
+ return total;
+}
+
+size_t MemoryManager::getLargeItemsMem() const
+{
+ size_t total = 0;
+ for (const Data::LargeItem *i = m_d->largeItems; i != 0; i = i->next)
+ total += i->size;
+ return total;
+}
+
++void MemoryManager::growUnmanagedHeapSizeUsage(size_t delta)
++{
++ m_d->unmanagedHeapSize += delta;
++}
++
+MemoryManager::~MemoryManager()
+{
+ delete m_persistentValues;
+
+ sweep(/*lastSweep*/true);
+
+ delete m_weakValues;
+#ifdef V4_USE_VALGRIND
+ VALGRIND_DESTROY_MEMPOOL(this);
+#endif
+}
+
+ExecutionEngine *MemoryManager::engine() const
+{
+ return m_d->engine;
+}
+
+void MemoryManager::dumpStats() const
+{
+#ifdef DETAILED_MM_STATS
+ std::cerr << "=================" << std::endl;
+ std::cerr << "Allocation stats:" << std::endl;
+ std::cerr << "Requests for each chunk size:" << std::endl;
+ for (int i = 0; i < m_d->allocSizeCounters.size(); ++i) {
+ if (unsigned count = m_d->allocSizeCounters[i]) {
+ std::cerr << "\t" << (i << 4) << " bytes chunks: " << count << std::endl;
+ }
+ }
+#endif // DETAILED_MM_STATS
+}
+
+#ifdef DETAILED_MM_STATS
+void MemoryManager::willAllocate(std::size_t size)
+{
+ unsigned alignedSize = (size + 15) >> 4;
+ QVector<unsigned> &counters = m_d->allocSizeCounters;
+ if ((unsigned) counters.size() < alignedSize + 1)
+ counters.resize(alignedSize + 1);
+ counters[alignedSize]++;
+}
+
+#endif // DETAILED_MM_STATS
+
+void MemoryManager::collectFromJSStack() const
+{
+ Value *v = m_d->engine->jsStackBase;
+ Value *top = m_d->engine->jsStackTop;
+ while (v < top) {
+ Managed *m = v->as<Managed>();
+ if (m && m->inUse())
+ // Skip pointers to already freed objects, they are bogus as well
+ m->mark(m_d->engine);
+ ++v;
+ }
+}
+QT_END_NAMESPACE
--- /dev/null
- inline typename ManagedType::Data *allocManaged(std::size_t size)
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL21$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** As a special exception, The Qt Company gives you certain additional
+** rights. These rights are described in The Qt Company LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QV4GC_H
+#define QV4GC_H
+
+#include <private/qv4global_p.h>
+#include <private/qv4value_p.h>
+#include <private/qv4scopedvalue_p.h>
+
+//#define DETAILED_MM_STATS
+
+QT_BEGIN_NAMESPACE
+
+namespace QV4 {
+
+struct GCDeletable;
+
+class Q_QML_EXPORT MemoryManager
+{
+ Q_DISABLE_COPY(MemoryManager);
+
+public:
+ struct Data;
+
+ class GCBlocker
+ {
+ public:
+ GCBlocker(MemoryManager *mm)
+ : mm(mm)
+ , wasBlocked(mm->isGCBlocked())
+ {
+ mm->setGCBlocked(true);
+ }
+
+ ~GCBlocker()
+ {
+ mm->setGCBlocked(wasBlocked);
+ }
+
+ private:
+ MemoryManager *mm;
+ bool wasBlocked;
+ };
+
+public:
+ MemoryManager(ExecutionEngine *engine);
+ ~MemoryManager();
+
+ // TODO: this is only for 64bit (and x86 with SSE/AVX), so exend it for other architectures to be slightly more efficient (meaning, align on 8-byte boundaries).
+ // Note: all occurrences of "16" in alloc/dealloc are also due to the alignment.
+ static inline std::size_t align(std::size_t size)
+ { return (size + 15) & ~0xf; }
+
+ template<typename ManagedType>
- Heap::Base *o = allocData(size);
++ inline typename ManagedType::Data *allocManaged(std::size_t size, std::size_t unmanagedSize = 0)
+ {
+ size = align(size);
- Heap::Base *allocData(std::size_t size);
++ Heap::Base *o = allocData(size, unmanagedSize);
+ o->setVtable(ManagedType::staticVTable());
+ return static_cast<typename ManagedType::Data *>(o);
+ }
+
+ template <typename ManagedType>
+ typename ManagedType::Data *alloc()
+ {
+ Scope scope(engine());
+ Scoped<ManagedType> t(scope, allocManaged<ManagedType>(sizeof(typename ManagedType::Data)));
+ (void)new (t->d()) typename ManagedType::Data();
+ return t->d();
+ }
+
+ template <typename ManagedType, typename Arg1>
+ typename ManagedType::Data *alloc(Arg1 arg1)
+ {
+ Scope scope(engine());
+ Scoped<ManagedType> t(scope, allocManaged<ManagedType>(sizeof(typename ManagedType::Data)));
+ (void)new (t->d()) typename ManagedType::Data(arg1);
+ return t->d();
+ }
+
++ template <typename ManagedType, typename Arg1>
++ typename ManagedType::Data *allocWithStringData(std::size_t unmanagedSize, Arg1 arg1)
++ {
++ Scope scope(engine());
++ Scoped<ManagedType> t(scope, allocManaged<ManagedType>(sizeof(typename ManagedType::Data), unmanagedSize));
++ (void)new (t->d()) typename ManagedType::Data(this, arg1);
++ return t->d();
++ }
++
+ template <typename ManagedType, typename Arg1, typename Arg2>
+ typename ManagedType::Data *alloc(Arg1 arg1, Arg2 arg2)
+ {
+ Scope scope(engine());
+ Scoped<ManagedType> t(scope, allocManaged<ManagedType>(sizeof(typename ManagedType::Data)));
+ (void)new (t->d()) typename ManagedType::Data(arg1, arg2);
+ return t->d();
+ }
+
+ template <typename ManagedType, typename Arg1, typename Arg2, typename Arg3>
+ typename ManagedType::Data *alloc(Arg1 arg1, Arg2 arg2, Arg3 arg3)
+ {
+ Scope scope(engine());
+ Scoped<ManagedType> t(scope, allocManaged<ManagedType>(sizeof(typename ManagedType::Data)));
+ (void)new (t->d()) typename ManagedType::Data(arg1, arg2, arg3);
+ return t->d();
+ }
+
+ template <typename ManagedType, typename Arg1, typename Arg2, typename Arg3, typename Arg4>
+ typename ManagedType::Data *alloc(Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4)
+ {
+ Scope scope(engine());
+ Scoped<ManagedType> t(scope, allocManaged<ManagedType>(sizeof(typename ManagedType::Data)));
+ (void)new (t->d()) typename ManagedType::Data(arg1, arg2, arg3, arg4);
+ return t->d();
+ }
+
+ template <typename ManagedType, typename Arg1, typename Arg2, typename Arg3, typename Arg4, typename Arg5>
+ typename ManagedType::Data *alloc(Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5)
+ {
+ Scope scope(engine());
+ Scoped<ManagedType> t(scope, allocManaged<ManagedType>(sizeof(typename ManagedType::Data)));
+ (void)new (t->d()) typename ManagedType::Data(arg1, arg2, arg3, arg4, arg5);
+ return t->d();
+ }
+
+ bool isGCBlocked() const;
+ void setGCBlocked(bool blockGC);
+ void runGC();
+
+ ExecutionEngine *engine() const;
+
+ void dumpStats() const;
+
+ size_t getUsedMem() const;
+ size_t getAllocatedMem() const;
+ size_t getLargeItemsMem() const;
+
++ void growUnmanagedHeapSizeUsage(size_t delta); // called when a JS object grows itself. Specifically: Heap::String::append
++
+protected:
+ /// expects size to be aligned
+ // TODO: try to inline
++ Heap::Base *allocData(std::size_t size, std::size_t unmanagedSize);
+
+#ifdef DETAILED_MM_STATS
+ void willAllocate(std::size_t size);
+#endif // DETAILED_MM_STATS
+
+private:
+ void collectFromJSStack() const;
+ void mark();
+ void sweep(bool lastSweep = false);
+
+protected:
+ QScopedPointer<Data> m_d;
+public:
+ PersistentValueStorage *m_persistentValues;
+ PersistentValueStorage *m_weakValues;
+};
+
+}
+
+QT_END_NAMESPACE
+
+#endif // QV4GC_H
#include <private/qqmlscriptstring_p.h>
#include <private/qqmlcontextwrapper_p.h>
#include <private/qqmlbuiltinfunctions_p.h>
+#include <private/qqmlvmemetaobject_p.h>
++#include <private/qqmlvaluetypewrapper_p.h>
#include <QVariant>
#include <QtCore/qdebug.h>
QQmlEnginePrivate *ep = QQmlEnginePrivate::get(context()->engine);
QV4::Scope scope(ep->v4engine());
- QV4::ScopedFunctionObject f(scope, v4function.value());
+ QV4::ScopedFunctionObject f(scope, m_function.value());
Q_ASSERT(f);
- if (!updatingFlag()) {
- QQmlBindingProfiler prof(ep->profiler, f);
- setUpdatingFlag(true);
+ if (updatingFlag()) {
+ QQmlProperty p = QQmlPropertyPrivate::restore(targetObject(), getPropertyData(), 0);
+ QQmlAbstractBinding::printBindingLoopError(p);
+ return;
+ }
+
+ QQmlBindingProfiler prof(ep->profiler, f);
+ setUpdatingFlag(true);
- QQmlAbstractExpression::DeleteWatcher watcher(this);
+ QQmlJavaScriptExpression::DeleteWatcher watcher(this);
- if (m_core.propType == qMetaTypeId<QQmlBinding *>()) {
+ QQmlPropertyData pd = getPropertyData();
- int idx = m_core.coreIndex;
- Q_ASSERT(idx != -1);
+ if (pd.propType == qMetaTypeId<QQmlBinding *>()) {
- QQmlBinding *t = this;
- int status = -1;
- void *a[] = { &t, 0, &status, &flags };
- QMetaObject::metacall(*m_coreObject, QMetaObject::WriteProperty, idx, a);
+ int idx = pd.coreIndex;
+ Q_ASSERT(idx != -1);
- } else {
- ep->referenceScarceResources();
+ QQmlBinding *t = this;
+ int status = -1;
+ void *a[] = { &t, 0, &status, &flags };
+ QMetaObject::metacall(*m_target, QMetaObject::WriteProperty, idx, a);
- bool isUndefined = false;
+ } else {
+ ep->referenceScarceResources();
- QV4::ScopedValue result(scope, QQmlJavaScriptExpression::evaluate(context(), f, &isUndefined));
+ bool isUndefined = false;
- bool needsErrorLocationData = false;
- if (!watcher.wasDeleted() && !hasError())
- needsErrorLocationData = !QQmlPropertyPrivate::writeBinding(*m_coreObject, m_core, context(),
- this, result, isUndefined, flags);
+ QV4::ScopedValue result(scope, QQmlJavaScriptExpression::evaluate(&isUndefined));
- if (!watcher.wasDeleted()) {
+ bool error = false;
+ if (!watcher.wasDeleted() && isAddedToObject() && !hasError())
+ error = !write(pd, result, isUndefined, flags);
- if (needsErrorLocationData)
- delayedError()->setErrorLocation(f->sourceLocation());
+ if (!watcher.wasDeleted()) {
- if (hasError()) {
- if (!delayedError()->addError(ep)) ep->warning(this->error(context()->engine));
- } else {
- clearError();
- }
+ if (error) {
+ delayedError()->setErrorLocation(f->sourceLocation());
+ delayedError()->setErrorObject(m_target.data());
+ }
+ if (hasError()) {
+ if (!delayedError()->addError(ep)) ep->warning(this->error(context()->engine));
+ } else {
+ clearError();
}
- ep->dereferenceScarceResources();
}
- if (!watcher.wasDeleted())
- setUpdatingFlag(false);
- } else {
- QQmlProperty p = property();
- QQmlAbstractBinding::printBindingLoopError(p);
+ ep->dereferenceScarceResources();
}
- if (QObject *o = *(QObject **)value.constData()) {
+
+ if (!watcher.wasDeleted())
+ setUpdatingFlag(false);
+}
+
+// Returns true if successful, false if an error description was set on expression
+bool QQmlBinding::write(const QQmlPropertyData &core,
+ const QV4::Value &result, bool isUndefined,
+ QQmlPropertyPrivate::WriteFlags flags)
+{
+ Q_ASSERT(m_target.data());
+ Q_ASSERT(core.coreIndex != -1);
+
+ QQmlEngine *engine = context()->engine;
+ QV8Engine *v8engine = QQmlEnginePrivate::getV8Engine(engine);
+
+#define QUICK_STORE(cpptype, conversion) \
+ { \
+ cpptype o = (conversion); \
+ int status = -1; \
+ void *argv[] = { &o, 0, &status, &flags }; \
+ QMetaObject::metacall(m_target.data(), QMetaObject::WriteProperty, core.coreIndex, argv); \
+ return true; \
+ } \
+
+
+ if (!isUndefined && !core.isValueTypeVirtual()) {
+ switch (core.propType) {
+ case QMetaType::Int:
+ if (result.isInteger())
+ QUICK_STORE(int, result.integerValue())
+ else if (result.isNumber())
+ QUICK_STORE(int, result.doubleValue())
+ break;
+ case QMetaType::Double:
+ if (result.isNumber())
+ QUICK_STORE(double, result.asDouble())
+ break;
+ case QMetaType::Float:
+ if (result.isNumber())
+ QUICK_STORE(float, result.asDouble())
+ break;
+ case QMetaType::QString:
+ if (result.isString())
+ QUICK_STORE(QString, result.toQStringNoThrow())
+ break;
+ default:
++ if (const QV4::QQmlValueTypeWrapper *vtw = result.as<const QV4::QQmlValueTypeWrapper>()) {
++ if (vtw->d()->valueType->typeId == core.propType) {
++ return vtw->write(m_target.data(), core.coreIndex);
++ }
++ }
+ break;
+ }
+ }
+#undef QUICK_STORE
+
+ int type = core.isValueTypeVirtual() ? core.valueTypePropType : core.propType;
+
+ QQmlJavaScriptExpression::DeleteWatcher watcher(this);
+
+ QVariant value;
+ bool isVarProperty = core.isVarProperty();
+
+ if (isUndefined) {
+ } else if (core.isQList()) {
+ value = QV8Engine::getV4(v8engine)->toVariant(result, qMetaTypeId<QList<QObject *> >());
+ } else if (result.isNull() && core.isQObject()) {
+ value = QVariant::fromValue((QObject *)0);
+ } else if (core.propType == qMetaTypeId<QList<QUrl> >()) {
+ value = QQmlPropertyPrivate::resolvedUrlSequence(QV8Engine::getV4(v8engine)->toVariant(result, qMetaTypeId<QList<QUrl> >()), context());
+ } else if (!isVarProperty && type != qMetaTypeId<QJSValue>()) {
+ value = QV8Engine::getV4(v8engine)->toVariant(result, type);
+ }
+
+ if (hasError()) {
+ return false;
+ } else if (isVarProperty) {
+ const QV4::FunctionObject *f = result.as<QV4::FunctionObject>();
+ if (f && f->isBinding()) {
+ // we explicitly disallow this case to avoid confusion. Users can still store one
+ // in an array in a var property if they need to, but the common case is user error.
+ delayedError()->setErrorDescription(QLatin1String("Invalid use of Qt.binding() in a binding declaration."));
+ return false;
+ }
+
+ QQmlVMEMetaObject *vmemo = QQmlVMEMetaObject::get(m_target.data());
+ Q_ASSERT(vmemo);
+ vmemo->setVMEProperty(core.coreIndex, result);
+ } else if (isUndefined && core.isResettable()) {
+ void *args[] = { 0 };
+ QMetaObject::metacall(m_target.data(), QMetaObject::ResetProperty, core.coreIndex, args);
+ } else if (isUndefined && type == qMetaTypeId<QVariant>()) {
+ QQmlPropertyPrivate::writeValueProperty(m_target.data(), core, QVariant(), context(), flags);
+ } else if (type == qMetaTypeId<QJSValue>()) {
+ const QV4::FunctionObject *f = result.as<QV4::FunctionObject>();
+ if (f && f->isBinding()) {
+ delayedError()->setErrorDescription(QLatin1String("Invalid use of Qt.binding() in a binding declaration."));
+ return false;
+ }
+ QQmlPropertyPrivate::writeValueProperty(m_target.data(), core, QVariant::fromValue(
+ QJSValue(QV8Engine::getV4(v8engine), result.asReturnedValue())),
+ context(), flags);
+ } else if (isUndefined) {
+ QString errorStr = QLatin1String("Unable to assign [undefined] to ");
+ if (!QMetaType::typeName(type))
+ errorStr += QLatin1String("[unknown property type]");
+ else
+ errorStr += QLatin1String(QMetaType::typeName(type));
+ delayedError()->setErrorDescription(errorStr);
+ return false;
+ } else if (const QV4::FunctionObject *f = result.as<QV4::FunctionObject>()) {
+ if (f->isBinding())
+ delayedError()->setErrorDescription(QLatin1String("Invalid use of Qt.binding() in a binding declaration."));
+ else
+ delayedError()->setErrorDescription(QLatin1String("Unable to assign a function to a property of any type other than var."));
+ return false;
+ } else if (!QQmlPropertyPrivate::writeValueProperty(m_target.data(), core, value, context(), flags)) {
+
+ if (watcher.wasDeleted())
+ return true;
+
+ const char *valueType = 0;
+ const char *propertyType = 0;
+
+ if (value.userType() == QMetaType::QObjectStar) {
++ if (QObject *o = *(QObject *const *)value.constData()) {
+ valueType = o->metaObject()->className();
+
+ QQmlMetaObject propertyMetaObject = QQmlPropertyPrivate::rawMetaObjectForType(QQmlEnginePrivate::get(engine), type);
+ if (!propertyMetaObject.isNull())
+ propertyType = propertyMetaObject.className();
+ }
+ } else if (value.userType() != QVariant::Invalid) {
+ if (value.userType() == QMetaType::VoidStar)
+ valueType = "null";
+ else
+ valueType = QMetaType::typeName(value.userType());
+ }
+
+ if (!valueType)
+ valueType = "undefined";
+ if (!propertyType)
+ propertyType = QMetaType::typeName(type);
+ if (!propertyType)
+ propertyType = "[unknown property type]";
+
+ delayedError()->setErrorDescription(QLatin1String("Unable to assign ") +
+ QLatin1String(valueType) +
+ QLatin1String(" to ") +
+ QLatin1String(propertyType));
+ return false;
+ }
+
+ return true;
}
QVariant QQmlBinding::evaluate()
class QQmlEngine;
class QQmlNotifierEndpoint
{
+ QQmlNotifierEndpoint *next;
+ QQmlNotifierEndpoint **prev;
public:
- inline QQmlNotifierEndpoint();
- inline ~QQmlNotifierEndpoint();
-
// QQmlNotifierEndpoint can only invoke one of a set of pre-defined callbacks.
// To add another callback, extend this enum and add the callback to the top
// of qqmlnotifier.cpp. Four bits are reserved for the callback, so there can
if (endpoints) emitNotify(endpoints, args);
}
-QQmlNotifierEndpoint::QQmlNotifierEndpoint()
-: next(0), prev(0), senderPtr(0), callback(None), sourceSignal(-1)
+QQmlNotifierEndpoint::QQmlNotifierEndpoint(Callback callback)
- : senderPtr(0), callback(callback), sourceSignal(-1), next(0), prev(0)
++: next(0), prev(0), senderPtr(0), callback(callback), sourceSignal(-1)
{
}
return true;
}
-// Returns true if successful, false if an error description was set on expression
-bool QQmlPropertyPrivate::writeBinding(QObject *object,
- const QQmlPropertyData &core,
- QQmlContextData *context,
- QQmlJavaScriptExpression *expression,
- const QV4::Value &result, bool isUndefined,
- WriteFlags flags)
-{
- Q_ASSERT(object);
- Q_ASSERT(core.coreIndex != -1);
-
- QQmlEngine *engine = context->engine;
- QV8Engine *v8engine = QQmlEnginePrivate::getV8Engine(engine);
-
-#define QUICK_STORE(cpptype, conversion) \
- { \
- cpptype o = (conversion); \
- int status = -1; \
- void *argv[] = { &o, 0, &status, &flags }; \
- QMetaObject::metacall(object, QMetaObject::WriteProperty, core.coreIndex, argv); \
- return true; \
- } \
-
-
- if (!isUndefined && !core.isValueTypeVirtual()) {
- switch (core.propType) {
- case QMetaType::Int:
- if (result.isInteger())
- QUICK_STORE(int, result.integerValue())
- else if (result.isNumber())
- QUICK_STORE(int, result.doubleValue())
- break;
- case QMetaType::Double:
- if (result.isNumber())
- QUICK_STORE(double, result.asDouble())
- break;
- case QMetaType::Float:
- if (result.isNumber())
- QUICK_STORE(float, result.asDouble())
- break;
- case QMetaType::QString:
- if (result.isString())
- QUICK_STORE(QString, result.toQStringNoThrow())
- break;
- default:
- if (const QV4::QQmlValueTypeWrapper *vtw = result.as<const QV4::QQmlValueTypeWrapper>()) {
- if (vtw->d()->valueType->typeId == core.propType) {
- return vtw->write(object, core.coreIndex);
- }
- }
- break;
- }
- }
-#undef QUICK_STORE
-
- int type = core.isValueTypeVirtual()?core.valueTypePropType:core.propType;
-
- QQmlJavaScriptExpression::DeleteWatcher watcher(expression);
-
- QVariant value;
- bool isVarProperty = core.isVarProperty();
-
- if (isUndefined) {
- } else if (core.isQList()) {
- value = QV8Engine::getV4(v8engine)->toVariant(result, qMetaTypeId<QList<QObject *> >());
- } else if (result.isNull() && core.isQObject()) {
- value = QVariant::fromValue((QObject *)0);
- } else if (core.propType == qMetaTypeId<QList<QUrl> >()) {
- value = resolvedUrlSequence(QV8Engine::getV4(v8engine)->toVariant(result, qMetaTypeId<QList<QUrl> >()), context);
- } else if (!isVarProperty && type != qMetaTypeId<QJSValue>()) {
- value = QV8Engine::getV4(v8engine)->toVariant(result, type);
- }
-
- if (expression->hasError()) {
- return false;
- } else if (isVarProperty) {
- QV4::FunctionObject *f = result.asFunctionObject();
- if (f && f->isBinding()) {
- // we explicitly disallow this case to avoid confusion. Users can still store one
- // in an array in a var property if they need to, but the common case is user error.
- expression->delayedError()->setErrorDescription(QLatin1String("Invalid use of Qt.binding() in a binding declaration."));
- expression->delayedError()->setErrorObject(object);
- return false;
- }
-
- QQmlVMEMetaObject *vmemo = QQmlVMEMetaObject::get(object);
- Q_ASSERT(vmemo);
- vmemo->setVMEProperty(core.coreIndex, result);
- } else if (isUndefined && core.isResettable()) {
- void *args[] = { 0 };
- QMetaObject::metacall(object, QMetaObject::ResetProperty, core.coreIndex, args);
- } else if (isUndefined && type == qMetaTypeId<QVariant>()) {
- writeValueProperty(object, core, QVariant(), context, flags);
- } else if (type == qMetaTypeId<QJSValue>()) {
- QV4::FunctionObject *f = result.asFunctionObject();
- if (f && f->isBinding()) {
- expression->delayedError()->setErrorDescription(QLatin1String("Invalid use of Qt.binding() in a binding declaration."));
- expression->delayedError()->setErrorObject(object);
- return false;
- }
- writeValueProperty(object, core, QVariant::fromValue(
- QJSValue(QV8Engine::getV4(v8engine), result.asReturnedValue())),
- context, flags);
- } else if (isUndefined) {
- QString errorStr = QLatin1String("Unable to assign [undefined] to ");
- if (!QMetaType::typeName(type))
- errorStr += QLatin1String("[unknown property type]");
- else
- errorStr += QLatin1String(QMetaType::typeName(type));
- expression->delayedError()->setErrorDescription(errorStr);
- expression->delayedError()->setErrorObject(object);
- return false;
- } else if (QV4::FunctionObject *f = result.asFunctionObject()) {
- if (f->isBinding())
- expression->delayedError()->setErrorDescription(QLatin1String("Invalid use of Qt.binding() in a binding declaration."));
- else
- expression->delayedError()->setErrorDescription(QLatin1String("Unable to assign a function to a property of any type other than var."));
- expression->delayedError()->setErrorObject(object);
- return false;
- } else if (!writeValueProperty(object, core, value, context, flags)) {
-
- if (watcher.wasDeleted())
- return true;
-
- const char *valueType = 0;
- const char *propertyType = 0;
-
- if (value.userType() == QMetaType::QObjectStar) {
- if (QObject *o = *(QObject *const *)value.constData()) {
- valueType = o->metaObject()->className();
-
- QQmlMetaObject propertyMetaObject = rawMetaObjectForType(QQmlEnginePrivate::get(engine), type);
- if (!propertyMetaObject.isNull())
- propertyType = propertyMetaObject.className();
- }
- } else if (value.userType() != QVariant::Invalid) {
- if (value.userType() == QMetaType::VoidStar)
- valueType = "null";
- else
- valueType = QMetaType::typeName(value.userType());
- }
-
- if (!valueType)
- valueType = "undefined";
- if (!propertyType)
- propertyType = QMetaType::typeName(type);
- if (!propertyType)
- propertyType = "[unknown property type]";
-
- expression->delayedError()->setErrorDescription(QLatin1String("Unable to assign ") +
- QLatin1String(valueType) +
- QLatin1String(" to ") +
- QLatin1String(propertyType));
- expression->delayedError()->setErrorObject(object);
- return false;
- }
-
- return true;
-}
--
QQmlMetaObject QQmlPropertyPrivate::rawMetaObjectForType(QQmlEnginePrivate *engine, int userType)
{
QMetaType metaType(userType);
QVector2D velocity(xDelta / elapsed, yDelta / elapsed);
d->lastPosTime = currentTimestamp;
d->accumulatedWheelPixelDelta += QVector2D(event->pixelDelta());
- d->drag(currentTimestamp, event->type(), event->posF(), d->accumulatedWheelPixelDelta, true, !d->scrollingPhase, velocity);
+ d->drag(currentTimestamp, event->type(), event->posF(), d->accumulatedWheelPixelDelta, true, !d->scrollingPhase, true, velocity);
+ event->accept();
}
if (!event->isAccepted())
}
qreal itemPosition() const {
if (view->orientation() == QQuickListView::Vertical)
- return (view->verticalLayoutDirection() == QQuickItemView::BottomToTop ? -item->height()-itemY() : itemY());
+ return (view->verticalLayoutDirection() == QQuickItemView::BottomToTop ? -itemHeight()-itemY() : itemY());
else
- return (view->effectiveLayoutDirection() == Qt::RightToLeft ? -item->width()-itemX() : itemX());
+ return (view->effectiveLayoutDirection() == Qt::RightToLeft ? -itemWidth()-itemX() : itemX());
}
- qreal size() const {
+ qreal size() const Q_DECL_OVERRIDE {
if (section())
- return (view->orientation() == QQuickListView::Vertical ? item->height()+section()->height() : item->width()+section()->width());
+ return (view->orientation() == QQuickListView::Vertical ? itemHeight()+section()->height() : itemWidth()+section()->width());
else
- return (view->orientation() == QQuickListView::Vertical ? item->height() : item->width());
+ return (view->orientation() == QQuickListView::Vertical ? itemHeight() : itemWidth());
}
qreal itemSize() const {
- return (view->orientation() == QQuickListView::Vertical ? item->height() : item->width());
+ return (view->orientation() == QQuickListView::Vertical ? itemHeight() : itemWidth());
}
- qreal sectionSize() const {
+ qreal sectionSize() const Q_DECL_OVERRIDE {
if (section())
return (view->orientation() == QQuickListView::Vertical ? section()->height() : section()->width());
return 0.0;
ProtectedLayoutAccessor *a = static_cast<ProtectedLayoutAccessor *>(d->document->documentLayout());
QTextCharFormat format = a->formatAccessor(pos);
QTextBlock block = textFrame->firstCursorPosition().block();
- node->m_engine->setCurrentLine(block.layout()->lineForTextPosition(pos - block.position()));
- node->m_engine->addTextObject(QPointF(0, 0), format, QQuickTextNodeEngine::Unselected, d->document,
+ engine.setCurrentLine(block.layout()->lineForTextPosition(pos - block.position()));
+ engine.addTextObject(QPointF(0, 0), format, QQuickTextNodeEngine::Unselected, d->document,
pos, textFrame->frameFormat().position());
nodeStart = pos;
- } else if (qobject_cast<QTextTable*>(textFrame)) { // To keep things simple, map text tables as one text node
- QTextFrame::iterator it = textFrame->begin();
- nodeOffset = d->document->documentLayout()->frameBoundingRect(textFrame).topLeft();
- updateNodeTransform(node, nodeOffset);
- while (!it.atEnd())
- engine.addTextBlock(d->document, (it++).currentBlock(), -nodeOffset, d->color, QColor(), selectionStart(), selectionEnd() - 1);
- nodeStart = textFrame->firstPosition();
} else {
// Having nodes spanning across frame boundaries will break the current bookkeeping mechanism. We need to prevent that.
QList<int> frameBoundaries;
input->select(0,input->text().length());
QVERIFY(input->selectionStart() != input->selectionEnd());
simulateKey(&window, Qt::Key_Right);
- QVERIFY(input->selectionStart() == input->selectionEnd());
- QVERIFY(input->selectionStart() == input->text().length());
- QVERIFY(input->hasActiveFocus() == true);
+ QCOMPARE(input->selectionStart(), input->selectionEnd());
+ QCOMPARE(input->selectionStart(), input->text().length());
+ QVERIFY(input->hasActiveFocus());
simulateKey(&window, Qt::Key_Right);
- QVERIFY(input->hasActiveFocus() == false);
+ QVERIFY(!input->hasActiveFocus());
simulateKey(&window, Qt::Key_Left);
- QVERIFY(input->hasActiveFocus() == true);
+ QVERIFY(input->hasActiveFocus());
- // Up and Down should NOT do Home/End, even on Mac OS X (QTBUG-10438).
+ // Up and Down should NOT do Home/End, even on OS X (QTBUG-10438).
input->setCursorPosition(2);
QCOMPARE(input->cursorPosition(),2);
simulateKey(&window, Qt::Key_Up);
#include <QtCore/QStack>
#include <QtCore/QStringList>
+#include <QtCore/QDataStream>
+ #include <limits>
+
ProfilerClient::ProfilerClient(const QString &clientName,
QQmlDebugConnection *client)
: QQmlDebugClient(clientName, client),