diff options
Diffstat (limited to 'src/qml/jsruntime/qv4value_p.h')
-rw-r--r-- | src/qml/jsruntime/qv4value_p.h | 441 |
1 files changed, 302 insertions, 139 deletions
diff --git a/src/qml/jsruntime/qv4value_p.h b/src/qml/jsruntime/qv4value_p.h index 1158b82318..b3f04d69be 100644 --- a/src/qml/jsruntime/qv4value_p.h +++ b/src/qml/jsruntime/qv4value_p.h @@ -51,6 +51,7 @@ // #include <limits.h> +#include <cmath> #include <QtCore/QString> #include "qv4global_p.h" @@ -58,10 +59,6 @@ #include <private/qnumeric_p.h> -#if QT_POINTER_SIZE == 8 -#define QV4_USE_64_BIT_VALUE_ENCODING -#endif - QT_BEGIN_NAMESPACE namespace QV4 { @@ -74,9 +71,7 @@ struct Q_QML_PRIVATE_EXPORT Value { private: /* - We use two different ways of encoding JS values. One for 32bit and one for 64bit systems. - - In both cases, we use 8 bytes for a value and a different variant of NaN boxing. A Double + We use 8 bytes for a value and a different variant of NaN boxing. A Double NaN (actually -qNaN) is indicated by a number that has the top 13 bits set, and for a signalling NaN it is the top 14 bits. The other values are usually set to 0 by the processor, and are thus free for us to store other data. We keep pointers in there for @@ -85,18 +80,14 @@ private: only have 48 bits of addressable memory. (Note: we do leave the lower 49 bits available for pointers.) - On 32bit, we store doubles as doubles. All other values, have the high 32bits set to a value - that will make the number a NaN. The Masks below are used for encoding the other types. - - On 64 bit, we xor Doubles with (0xffff8000 << 32). That has the effect that no doubles will + We xor Doubles with (0xffff8000 << 32). That has the effect that no doubles will get encoded with bits 63-49 all set to 0. We then use bit 48 to distinguish between managed/undefined (0), or Null/Int/Bool/Empty (1). So, storing a 49 bit pointer will leave the top 15 bits 0, which is exactly the 'natural' representation of pointers. If bit 49 is set, bit 48 indicates Empty (0) or integer-convertible (1). Then the 3 bit below that are used to encode Null/Int/Bool. - On both 32bit and 64bit, Undefined is encoded as a managed pointer with value 0. This is - the same as a nullptr. + Undefined is encoded as a managed pointer with value 0. This is the same as a nullptr. Specific bit-sequences: 0 = always 0 @@ -104,8 +95,6 @@ private: x = stored value a,b,c,d = specific bit values, see notes - 64bit: - 32109876 54321098 76543210 98765432 10987654 32109876 54321098 76543210 | 66665555 55555544 44444444 33333333 33222222 22221111 11111100 00000000 | JS Value ------------------------------------------------------------------------+-------------- @@ -114,9 +103,9 @@ private: a0000000 0000bc00 00000000 00000000 00000000 00000000 00000000 00000000 | NaN/Inf dddddddd ddddddxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx | double 00000000 00000010 00000000 00000000 00000000 00000000 00000000 00000000 | empty (non-sparse array hole) - 00000000 00000011 10000000 00000000 00000000 00000000 00000000 00000000 | Null - 00000000 00000011 01000000 00000000 00000000 00000000 00000000 0000000x | Bool - 00000000 00000011 00100000 00000000 xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx | Int + 00000000 00000010 10000000 00000000 00000000 00000000 00000000 00000000 | Null + 00000000 00000011 00000000 00000000 00000000 00000000 00000000 0000000x | Bool + 00000000 00000011 10000000 00000000 xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx | Int Notes: - a: xor-ed signbit, always 1 for NaN @@ -130,31 +119,8 @@ private: - Null, Bool, and Int have bit 48 set, indicating integer-convertible - xoring _val with NaNEncodeMask will convert to a double in "natural" representation, where any non double results in a NaN - - 32bit: - - 32109876 54321098 76543210 98765432 10987654 32109876 54321098 76543210 | - 66665555 55555544 44444444 33333333 33222222 22221111 11111100 00000000 | JS Value - ------------------------------------------------------------------------+-------------- - 01111111 11111100 00000000 00000000 00000000 00000000 00000000 00000000 | Undefined - 01111111 11111100 00000000 00000000 xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx | Managed (heap pointer) - a1111111 1111bc00 00000000 00000000 00000000 00000000 00000000 00000000 | NaN/Inf - xddddddd ddddddxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx | double - 01111111 11111110 00000000 00000000 00000000 00000000 00000000 00000000 | empty (non-sparse array hole) - 01111111 11111111 10000000 00000000 00000000 00000000 00000000 00000000 | Null - 01111111 11111111 01000000 00000000 00000000 00000000 00000000 0000000x | Bool - 01111111 11111111 00100000 00000000 xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx | Int - - Notes: - - the upper 32 bits are the tag, the lower 32 bits the value - - Undefined has a nullptr in the value, Managed has a non-nullptr stored in the value - - a: sign bit, always 0 for NaN - - b,c: 00=inf, 01 = sNaN, 10 = qNaN, 11 = boxed value - - d: stored double value, as long as not *all* of them are 1, because that's a boxed value - (see above) - - empty, Null, Bool, and Int have bit 63 set to 0, bits 62-50 set to 1 (same as undefined - and managed), and bit 49 set to 1 (where undefined and managed have it set to 0) - - Null, Bool, and Int have bit 48 set, indicating integer-convertible + - on 32bit we can use the fact that addresses are 32bits wide, so the tag part (bits 32 to + 63) are zero. No need to shift. */ quint64 _val; @@ -174,8 +140,9 @@ public: QML_NEARLY_ALWAYS_INLINE void setTagValue(quint32 tag, quint32 value) { _val = quint64(tag) << 32 | value; } QML_NEARLY_ALWAYS_INLINE quint32 value() const { return _val & quint64(~quint32(0)); } QML_NEARLY_ALWAYS_INLINE quint32 tag() const { return _val >> 32; } + QML_NEARLY_ALWAYS_INLINE void setTag(quint32 tag) { setTagValue(tag, value()); } -#if defined(QV4_USE_64_BIT_VALUE_ENCODING) +#if QT_POINTER_SIZE == 8 QML_NEARLY_ALWAYS_INLINE Heap::Base *m() const { Heap::Base *b; @@ -186,7 +153,7 @@ public: { memcpy(&_val, &b, 8); } -#else // !QV4_USE_64_BIT_VALUE_ENCODING +#elif QT_POINTER_SIZE == 4 QML_NEARLY_ALWAYS_INLINE Heap::Base *m() const { Q_STATIC_ASSERT(sizeof(Heap::Base*) == sizeof(quint32)); @@ -201,6 +168,8 @@ public: memcpy(&v, &b, 4); setTagValue(Managed_Type_Internal, v); } +#else +# error "unsupported pointer size" #endif QML_NEARLY_ALWAYS_INLINE int int_32() const @@ -234,24 +203,37 @@ public: return quint32(value()); } + // ### Fix for 32 bit (easiest solution is to set highest bit to 1 for mananged/undefined/integercompatible + // and use negative numbers here + enum QuickType { + QT_ManagedOrUndefined = 0, + QT_ManagedOrUndefined1 = 1, + QT_ManagedOrUndefined2 = 2, + QT_ManagedOrUndefined3 = 3, + QT_Empty = 4, + QT_Null = 5, + QT_Bool = 6, + QT_Int = 7 + // all other values are doubles + }; + enum Type { - Undefined_Type, - Managed_Type, - Empty_Type, - Integer_Type, - Boolean_Type, - Null_Type, - Double_Type + Undefined_Type = 0, + Managed_Type = 1, + Empty_Type = 4, + Null_Type = 5, + Boolean_Type = 6, + Integer_Type = 7, + Double_Type = 8 }; inline Type type() const { - if (isUndefined()) return Undefined_Type; - if (isManaged()) return Managed_Type; - if (isEmpty()) return Empty_Type; - if (isInteger()) return Integer_Type; - if (isBoolean()) return Boolean_Type; - if (isNull()) return Null_Type; - Q_ASSERT(isDouble()); return Double_Type; + int t = quickType(); + if (t < QT_Empty) + return _val ? Managed_Type : Undefined_Type; + if (t > QT_Int) + return Double_Type; + return static_cast<Type>(t); } // Shared between 32-bit and 64-bit encoding @@ -264,19 +246,18 @@ public: enum { IsDouble_Shift = 64-14, IsManagedOrUndefined_Shift = 64-15, - IsIntegerConvertible_Shift = 64-16, - IsDoubleTag_Shift = IsDouble_Shift - Tag_Shift, - Managed_Type_Internal_64 = 0 + IsIntegerConvertible_Shift = 64-15, + IsIntegerOrBool_Shift = 64-16, + QuickType_Shift = 64 - 17 }; static const quint64 Immediate_Mask_64 = 0x00020000u; // bit 49 enum class ValueTypeInternal_64 { - Empty = Immediate_Mask_64| 0, - ConvertibleToInt = Immediate_Mask_64| 0x10000u, // bit 48 - Null = ConvertibleToInt | 0x08000u, - Boolean = ConvertibleToInt | 0x04000u, - Integer = ConvertibleToInt | 0x02000u + Empty = Immediate_Mask_64 | 0, + Null = Immediate_Mask_64 | 0x08000u, + Boolean = Immediate_Mask_64 | 0x10000u, + Integer = Immediate_Mask_64 | 0x18000u }; // Used only by 32-bit encoding @@ -287,50 +268,44 @@ public: static const quint64 Immediate_Mask_32 = NotDouble_Mask | 0x00020000u | SilentNaNBit; enum class ValueTypeInternal_32 { - Empty = Immediate_Mask_32| 0, - ConvertibleToInt = Immediate_Mask_32| 0x10000u, // bit 48 - Null = ConvertibleToInt | 0x08000u, - Boolean = ConvertibleToInt | 0x04000u, - Integer = ConvertibleToInt | 0x02000u + Empty = Immediate_Mask_32 | 0, + Null = Immediate_Mask_32 | 0x08000u, + Boolean = Immediate_Mask_32 | 0x10000u, + Integer = Immediate_Mask_32 | 0x18000u }; enum { - Managed_Type_Internal_32 = NotDouble_Mask + Managed_Type_Internal = 0 }; -#ifdef QV4_USE_64_BIT_VALUE_ENCODING - enum { - Managed_Type_Internal = Managed_Type_Internal_64 - }; - static const quint64 Immediate_Mask = Immediate_Mask_64; using ValueTypeInternal = ValueTypeInternal_64; -#else - enum { - Managed_Type_Internal = Managed_Type_Internal_32 - }; - static const quint64 Immediate_Mask = Immediate_Mask_32; - using ValueTypeInternal = ValueTypeInternal_32; -#endif + enum { NaN_Mask = 0x7ff80000, }; + inline quint64 quickType() const { return (_val >> QuickType_Shift); } + // used internally in property inline bool isEmpty() const { return tag() == quint32(ValueTypeInternal::Empty); } inline bool isNull() const { return tag() == quint32(ValueTypeInternal::Null); } inline bool isBoolean() const { return tag() == quint32(ValueTypeInternal::Boolean); } inline bool isInteger() const { return tag() == quint32(ValueTypeInternal::Integer); } inline bool isNullOrUndefined() const { return isNull() || isUndefined(); } - inline bool isNumber() const { return isDouble() || isInteger(); } + inline bool isNumber() const { return quickType() >= QT_Int; } -#ifdef QV4_USE_64_BIT_VALUE_ENCODING inline bool isUndefined() const { return _val == 0; } inline bool isDouble() const { return (_val >> IsDouble_Shift); } - inline bool isManaged() const { return !isUndefined() && ((_val >> IsManagedOrUndefined_Shift) == 0); } + inline bool isManaged() const { return _val && ((_val >> IsManagedOrUndefined_Shift) == 0); } inline bool isManagedOrUndefined() const { return ((_val >> IsManagedOrUndefined_Shift) == 0); } + inline bool isIntOrBool() const { + return (_val >> IsIntegerOrBool_Shift) == 3; + } + inline bool integerCompatible() const { - return (_val >> IsIntegerConvertible_Shift) == 3; + Q_ASSERT(!isEmpty()); + return (_val >> IsIntegerConvertible_Shift) == 1; } static inline bool integerCompatible(Value a, Value b) { return a.integerCompatible() && b.integerCompatible(); @@ -339,41 +314,25 @@ public: return a.isDouble() && b.isDouble(); } inline bool isNaN() const { return (tag() & 0x7ffc0000 ) == 0x00040000; } -#else - inline bool isUndefined() const { return tag() == Managed_Type_Internal && value() == 0; } - inline bool isDouble() const { return (tag() & NotDouble_Mask) != NotDouble_Mask; } - inline bool isManaged() const { return tag() == Managed_Type_Internal && !isUndefined(); } - inline bool isManagedOrUndefined() const { return tag() == Managed_Type_Internal; } - inline bool integerCompatible() const { return (tag() & quint32(ValueTypeInternal::ConvertibleToInt)) == quint32(ValueTypeInternal::ConvertibleToInt); } - static inline bool integerCompatible(Value a, Value b) { - return ((a.tag() & b.tag()) & quint32(ValueTypeInternal::ConvertibleToInt)) == quint32(ValueTypeInternal::ConvertibleToInt); - } - static inline bool bothDouble(Value a, Value b) { - return ((a.tag() | b.tag()) & NotDouble_Mask) != NotDouble_Mask; - } - inline bool isNaN() const { return (tag() & QV4::Value::NotDouble_Mask) == QV4::Value::NaN_Mask; } -#endif + QML_NEARLY_ALWAYS_INLINE double doubleValue() const { Q_ASSERT(isDouble()); double d; - quint64 v = _val; -#ifdef QV4_USE_64_BIT_VALUE_ENCODING - v ^= NaNEncodeMask; -#endif - memcpy(&d, &v, 8); + Value v = *this; + v._val ^= NaNEncodeMask; + memcpy(&d, &v._val, 8); return d; } QML_NEARLY_ALWAYS_INLINE void setDouble(double d) { if (qt_is_nan(d)) d = qt_qnan(); 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 isFunctionObject() const; inline bool isInt32() { if (tag() == quint32(ValueTypeInternal::Integer)) return true; @@ -430,14 +389,31 @@ public: inline int toInt32() const; inline unsigned int toUInt32() const; - bool toBoolean() const; + bool toBoolean() const { + if (integerCompatible()) + return static_cast<bool>(int_32()); + + return toBooleanImpl(*this); + } + static bool toBooleanImpl(Value val); double toInteger() const; inline double toNumber() const; - double toNumberImpl() const; + static double toNumberImpl(Value v); + double toNumberImpl() const { return toNumberImpl(*this); } QString toQStringNoThrow() const; QString toQString() const; - Heap::String *toString(ExecutionEngine *e) const; - Heap::Object *toObject(ExecutionEngine *e) const; + Heap::String *toString(ExecutionEngine *e) const { + if (isString()) + return reinterpret_cast<Heap::String *>(m()); + return toString(e, *this); + } + static Heap::String *toString(ExecutionEngine *e, Value val); + Heap::Object *toObject(ExecutionEngine *e) const { + if (isObject()) + return reinterpret_cast<Heap::Object *>(m()); + return toObject(e, *this); + } + static Heap::Object *toObject(ExecutionEngine *e, Value val); inline bool isPrimitive() const; inline bool tryIntegerConversion() { @@ -485,6 +461,7 @@ public: uint asArrayLength(bool *ok) const; #endif + ReturnedValue *data_ptr() { return &_val; } ReturnedValue asReturnedValue() const { return _val; } static Value fromReturnedValue(ReturnedValue val) { Value v; v._val = val; return v; } @@ -511,7 +488,14 @@ public: template<typename T> Value &operator=(const Scoped<T> &t); }; -V4_ASSERT_IS_TRIVIAL(Value) +Q_STATIC_ASSERT(std::is_trivial< Value >::value); + +inline void Value::mark(MarkStack *markStack) +{ + Heap::Base *o = heapObject(); + if (o) + o->mark(markStack); +} inline bool Value::isString() const { @@ -524,6 +508,12 @@ inline bool Value::isObject() const return b && b->vtable()->isObject; } +inline bool Value::isFunctionObject() const +{ + Heap::Base *b = heapObject(); + return b && b->vtable()->isFunctionObject; +} + inline bool Value::isPrimitive() const { return !isObject(); @@ -542,7 +532,7 @@ inline double Value::toNumber() const #ifndef V4_BOOTSTRAP inline uint Value::asArrayIndex() const { -#ifdef QV4_USE_64_BIT_VALUE_ENCODING +#if QT_POINTER_SIZE == 8 if (!isNumber()) return UINT_MAX; if (isInteger()) @@ -562,8 +552,8 @@ inline uint Value::asArrayIndex() const inline bool Value::asArrayIndex(uint &idx) const { - if (!isDouble()) { - if (isInteger() && int_32() >= 0) { + if (Q_LIKELY(!isDouble())) { + if (Q_LIKELY(isInteger() && int_32() >= 0)) { idx = (uint)int_32(); return true; } @@ -597,15 +587,15 @@ struct Q_QML_PRIVATE_EXPORT Primitive : public Value using Value::toInt32; using Value::toUInt32; - static double toInteger(double fromNumber); - static int toInt32(double value); - static unsigned int toUInt32(double value); + static double toInteger(double d); + static int toInt32(double d); + static unsigned int toUInt32(double d); }; inline Primitive Primitive::undefinedValue() { Primitive v; - v.setM(Q_NULLPTR); + v.setM(nullptr); return v; } @@ -662,6 +652,72 @@ inline Primitive Primitive::fromUInt32(uint i) return v; } +struct Double { + quint64 d; + + Double(double dbl) { + memcpy(&d, &dbl, sizeof(double)); + } + + int sign() const { + return (d >> 63) ? -1 : 1; + } + + bool isDenormal() const { + return static_cast<int>((d << 1) >> 53) == 0; + } + + int exponent() const { + return static_cast<int>((d << 1) >> 53) - 1023; + } + + quint64 significant() const { + quint64 m = (d << 12) >> 12; + if (!isDenormal()) + m |= (static_cast<quint64>(1) << 52); + return m; + } + + static int toInt32(double d) { + int i = static_cast<int>(d); + if (i == d) + return i; + return Double(d).toInt32(); + } + + int toInt32() { + int e = exponent() - 52; + if (e < 0) { + if (e <= -53) + return 0; + return sign() * static_cast<int>(significant() >> -e); + } else { + if (e > 31) + return 0; + return sign() * (static_cast<int>(significant()) << e); + } + } +}; + +inline double Primitive::toInteger(double d) +{ + if (std::isnan(d)) + return +0; + else if (!d || std::isinf(d)) + return d; + return d >= 0 ? std::floor(d) : std::ceil(d); +} + +inline int Primitive::toInt32(double value) +{ + return Double::toInt32(value); +} + +inline unsigned int Primitive::toUInt32(double d) +{ + return static_cast<uint>(toInt32(d)); +} + struct Encode { static ReturnedValue undefined() { return Primitive::undefinedValue().rawValue(); @@ -670,33 +726,47 @@ struct Encode { return Primitive::nullValue().rawValue(); } - Encode(bool b) { + explicit Encode(bool b) { val = Primitive::fromBoolean(b).rawValue(); } - Encode(double d) { + explicit Encode(double d) { val = Primitive::fromDouble(d).rawValue(); } - Encode(int i) { + explicit Encode(int i) { val = Primitive::fromInt32(i).rawValue(); } - Encode(uint i) { + explicit Encode(uint i) { val = Primitive::fromUInt32(i).rawValue(); } - Encode(ReturnedValue v) { + explicit Encode(ReturnedValue v) { val = v; } + Encode(Value v) { + val = v.rawValue(); + } - Encode(Heap::Base *o) { - Q_ASSERT(o); + explicit Encode(Heap::Base *o) { val = Value::fromHeapObject(o).asReturnedValue(); } + explicit Encode(Value *o) { + Q_ASSERT(o); + val = o->asReturnedValue(); + } + + static ReturnedValue smallestNumber(double d) { + if (static_cast<int>(d) == d && !(d == 0. && std::signbit(d))) + return Encode(static_cast<int>(d)); + else + return Encode(d); + } + operator ReturnedValue() const { return val; } quint64 val; private: - Encode(void *); + explicit Encode(void *); }; template<typename T> @@ -704,24 +774,117 @@ ReturnedValue value_convert(ExecutionEngine *e, const Value &v); inline int Value::toInt32() const { - if (isInteger()) + if (Q_LIKELY(integerCompatible())) return int_32(); - double d = isNumber() ? doubleValue() : toNumberImpl(); - const double D32 = 4294967296.0; - const double D31 = D32 / 2.0; + if (Q_LIKELY(isDouble())) + return Double::toInt32(doubleValue()); - if ((d >= -D31 && d < D31)) - return static_cast<int>(d); - - return Primitive::toInt32(d); + return Double::toInt32(toNumberImpl()); } inline unsigned int Value::toUInt32() const { - return (unsigned int)toInt32(); + return static_cast<unsigned int>(toInt32()); +} + +inline double Value::toInteger() const +{ + if (integerCompatible()) + return int_32(); + + return Primitive::toInteger(isDouble() ? doubleValue() : toNumberImpl()); } + +template <size_t o> +struct HeapValue : Value { + static Q_CONSTEXPR size_t offset = o; + Heap::Base *base() { + Heap::Base *base = reinterpret_cast<Heap::Base *>(this) - (offset/sizeof(Heap::Base)); + Q_ASSERT(base->inUse()); + return base; + } + + void set(EngineBase *e, const Value &newVal) { + WriteBarrier::write(e, base(), data_ptr(), newVal.asReturnedValue()); + } + void set(EngineBase *e, Heap::Base *b) { + WriteBarrier::write(e, base(), data_ptr(), b->asReturnedValue()); + } +}; + +template <size_t o> +struct ValueArray { + static Q_CONSTEXPR size_t offset = o; + uint size; + uint alloc; + Value values[1]; + + Heap::Base *base() { + Heap::Base *base = reinterpret_cast<Heap::Base *>(this) - (offset/sizeof(Heap::Base)); + Q_ASSERT(base->inUse()); + return base; + } + + void set(EngineBase *e, uint index, Value v) { + WriteBarrier::write(e, base(), values[index].data_ptr(), v.asReturnedValue()); + } + void set(EngineBase *e, uint index, Heap::Base *b) { + WriteBarrier::write(e, base(), values[index].data_ptr(), b->asReturnedValue()); + } + inline const Value &operator[] (uint index) const { + Q_ASSERT(index < alloc); + return values[index]; + } + inline const Value *data() const { + return values; + } + + void insertData(EngineBase *e, uint index, Value v) { + for (uint i = size - 1; i > index; --i) { + values[i] = values[i - 1]; + } + set(e, index, v); + } + void removeData(EngineBase *e, uint index, int n = 1) { + Q_UNUSED(e); + for (uint i = index; i < size - n; ++i) { + values[i] = values[i + n]; + } + } + + void mark(MarkStack *markStack) { + Value *v = values; + const Value *end = v + alloc; + if (alloc > 32*1024) { + // drain from time to time to avoid overflows in the js stack + Heap::Base **currentBase = markStack->top; + while (v < end) { + v->mark(markStack); + ++v; + if (markStack->top >= currentBase + 32*1024) { + Heap::Base **oldBase = markStack->base; + markStack->base = currentBase; + markStack->drain(); + markStack->base = oldBase; + } + } + } else { + while (v < end) { + v->mark(markStack); + ++v; + } + } + } +}; + +// It's really important that the offset of values in this structure is +// constant across all architecture, otherwise JIT cross-compiled code will +// have wrong offsets between host and target. +Q_STATIC_ASSERT(offsetof(ValueArray<0>, values) == 8); + + } QT_END_NAMESPACE |