aboutsummaryrefslogtreecommitdiffstats
path: root/src/qml/common/qv4staticvalue_p.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/qml/common/qv4staticvalue_p.h')
-rw-r--r--src/qml/common/qv4staticvalue_p.h547
1 files changed, 357 insertions, 190 deletions
diff --git a/src/qml/common/qv4staticvalue_p.h b/src/qml/common/qv4staticvalue_p.h
index 5c2cdcf56e..e887cdc674 100644
--- a/src/qml/common/qv4staticvalue_p.h
+++ b/src/qml/common/qv4staticvalue_p.h
@@ -1,41 +1,5 @@
-/****************************************************************************
-**
-** Copyright (C) 2019 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the QtQml module of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** 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 https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://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 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef QV4STATICVALUE_P_H
#define QV4STATICVALUE_P_H
@@ -53,6 +17,8 @@
#include <qjsnumbercoercion.h>
#include <QtCore/private/qnumeric_p.h>
+#include <private/qtqmlglobal_p.h>
+
#include <cstring>
#ifdef QT_NO_DEBUG
@@ -71,8 +37,14 @@ namespace QV4 {
// It will be returned in rax on x64, [eax,edx] on x86 and [r0,r1] on arm
typedef quint64 ReturnedValue;
+namespace Heap {
+struct Base;
+}
+
struct StaticValue
{
+ using HeapBasePtr = Heap::Base *;
+
StaticValue() = default;
constexpr StaticValue(quint64 val) : _val(val) {}
@@ -92,21 +64,31 @@ struct StaticValue
Value &asValue();
/*
- 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
- managed objects, and encode the other types using the free space given to use by the unused
- bits for NaN values. This also works for pointers on 64 bit systems, as they all currently
- only have 48 bits of addressable memory. (Note: we do leave the lower 49 bits available for
- pointers.)
-
- 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.
+ We use 8 bytes for a value. In order to store all possible values we employ a variant of NaN
+ boxing. A "special" Double is indicated by a number that has the 11 exponent bits set to 1.
+ Those can be NaN, positive or negative infinity. We only store one variant of NaN: The sign
+ bit has to be off and the bit after the exponent ("quiet bit") has to be on. However, since
+ the exponent bits are enough to identify special doubles, we can use a different bit as
+ discriminator to tell us how the rest of the bits (including quiet and sign) are to be
+ interpreted. This bit is bit 48. If set, we have an unmanaged value, which includes the
+ special doubles and various other values. If unset, we have a managed value, and all of the
+ other bits can be used to assemble a pointer.
+
+ On 32bit systems the pointer can just live in the lower 4 bytes. On 64 bit systems the lower
+ 48 bits can be used for verbatim pointer bits. However, since all our heap objects are
+ aligned to 32 bytes, we can use the 5 least significant bits of the pointer to store, e.g.
+ pointer tags on android. The same holds for the 3 bits between the double exponent and
+ bit 48.
+
+ With that out of the way, we can use the other bits to store different values.
+
+ We xor Doubles with (0x7ff48000 << 32). That has the effect that any double with all the
+ exponent bits set to 0 is one of our special doubles. Those special doubles then get the
+ other two bits in the mask (Special and Number) set to 1, as they cannot have 1s in those
+ positions to begin with.
+
+ We dedicate further bits to integer-convertible and bool-or-int. With those bits we can
+ describe all values we need to store.
Undefined is encoded as a managed pointer with value 0. This is the same as a nullptr.
@@ -114,34 +96,31 @@ struct StaticValue
0 = always 0
1 = always 1
x = stored value
- a,b,c,d = specific bit values, see notes
+ y = stored value, shifted to different position
+ a = xor-ed bits, where at least one bit is set
+ b = xor-ed bits
32109876 54321098 76543210 98765432 10987654 32109876 54321098 76543210 |
66665555 55555544 44444444 33333333 33222222 22221111 11111100 00000000 | JS Value
------------------------------------------------------------------------+--------------
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 | Undefined
- 00000000 0000000x xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx | Managed (heap pointer)
- 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 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
- - bc, xor-ed values: 11 = inf, 10 = sNaN, 01 = qNaN, 00 = boxed value
- - d: xor-ed bits, where at least one bit is set, so: (val >> (64-14)) > 0
- - Undefined maps to C++ nullptr, so the "default" initialization is the same for both C++
- and JS
- - Managed has the left 15 bits set to 0, so: (val >> (64-15)) == 0
- - empty, Null, Bool, and Int have the left 14 bits set to 0, and bit 49 set to 1,
- so: (val >> (64-15)) == 1
- - 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
- - 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.
+ y0000000 0000yyy0 xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxyyyyy | Managed (heap pointer)
+ 00000000 00001101 10000000 00000000 00000000 00000000 00000000 00000000 | NaN
+ 00000000 00000101 10000000 00000000 00000000 00000000 00000000 00000000 | +Inf
+ 10000000 00000101 10000000 00000000 00000000 00000000 00000000 00000000 | -Inf
+ xaaaaaaa aaaaxbxb bxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx | double
+ 00000000 00000001 00000000 00000000 00000000 00000000 00000000 00000000 | empty (non-sparse array hole)
+ 00000000 00000011 00000000 00000000 00000000 00000000 00000000 00000000 | Null
+ 00000000 00000011 10000000 00000000 00000000 00000000 00000000 0000000x | Bool
+ 00000000 00000011 11000000 00000000 xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx | Int
+ ^ ^^^ ^^
+ | ||| ||
+ | ||| |+-> Number
+ | ||| +--> Int or Bool
+ | ||+----> Unmanaged
+ | |+-----> Integer compatible
+ | +------> Special double
+ +--------------------> Double sign, also used for special doubles
*/
quint64 _val;
@@ -157,10 +136,10 @@ struct StaticValue
static inline int valueOffset() { return 4; }
static inline int tagOffset() { return 0; }
#endif
- static inline constexpr quint64 tagValue(quint32 tag, quint32 value) { return quint64(tag) << 32 | value; }
- QV4_NEARLY_ALWAYS_INLINE constexpr void setTagValue(quint32 tag, quint32 value) { _val = quint64(tag) << 32 | value; }
+ static inline constexpr quint64 tagValue(quint32 tag, quint32 value) { return quint64(tag) << Tag_Shift | value; }
+ QV4_NEARLY_ALWAYS_INLINE constexpr void setTagValue(quint32 tag, quint32 value) { _val = quint64(tag) << Tag_Shift | value; }
QV4_NEARLY_ALWAYS_INLINE constexpr quint32 value() const { return _val & quint64(~quint32(0)); }
- QV4_NEARLY_ALWAYS_INLINE constexpr quint32 tag() const { return _val >> 32; }
+ QV4_NEARLY_ALWAYS_INLINE constexpr quint32 tag() const { return _val >> Tag_Shift; }
QV4_NEARLY_ALWAYS_INLINE constexpr void setTag(quint32 tag) { setTagValue(tag, value()); }
QV4_NEARLY_ALWAYS_INLINE constexpr int int_32() const
@@ -169,96 +148,133 @@ struct StaticValue
}
QV4_NEARLY_ALWAYS_INLINE constexpr void setInt_32(int i)
{
- setTagValue(quint32(ValueTypeInternal::Integer), quint32(i));
+ setTagValue(quint32(QuickType::Integer), quint32(i));
}
QV4_NEARLY_ALWAYS_INLINE uint uint_32() const { return value(); }
QV4_NEARLY_ALWAYS_INLINE constexpr void setEmpty()
{
- setTagValue(quint32(ValueTypeInternal::Empty), 0);
- }
-
- // ### 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 = 0,
- Managed_Type = 1,
- Empty_Type = 4,
- Null_Type = 5,
- Boolean_Type = 6,
- Integer_Type = 7,
- Double_Type = 8
+ setTagValue(quint32(QuickType::Empty), 0);
+ }
+
+ enum class TagBit {
+ // s: sign bit
+ // e: double exponent bit
+ // u: upper 3 bits if managed
+ // m: bit 48, denotes "unmanaged" if 1
+ // p: significant pointer bits (some re-used for non-managed)
+ // seeeeeeeeeeeuuumpppp
+ SpecialNegative = 0b10000000000000000000 << 12,
+ SpecialQNaN = 0b00000000000010000000 << 12,
+ Special = 0b00000000000001000000 << 12,
+ IntCompat = 0b00000000000000100000 << 12,
+ Unmanaged = 0b00000000000000010000 << 12,
+ IntOrBool = 0b00000000000000001000 << 12,
+ Number = 0b00000000000000000100 << 12,
};
- inline Type type() const {
- 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);
- }
+ static inline constexpr quint64 tagBitMask(TagBit bit) { return quint64(bit) << Tag_Shift; }
- // Shared between 32-bit and 64-bit encoding
- enum {
- Tag_Shift = 32
+ enum Type {
+ // Managed, Double and undefined are not directly encoded
+ Managed_Type = 0,
+ Double_Type = 1,
+ Undefined_Type = 2,
+
+ Empty_Type = quint32(TagBit::Unmanaged),
+ Null_Type = Empty_Type | quint32(TagBit::IntCompat),
+ Boolean_Type = Null_Type | quint32(TagBit::IntOrBool),
+ Integer_Type = Boolean_Type | quint32(TagBit::Number)
};
- // Used only by 64-bit encoding
- static const quint64 NaNEncodeMask = 0xfffc000000000000ull;
enum {
- IsDouble_Shift = 64-14,
- IsManagedOrUndefined_Shift = 64-15,
- IsIntegerConvertible_Shift = 64-15,
- IsIntegerOrBool_Shift = 64-16,
- QuickType_Shift = 64 - 17,
- IsPositiveIntShift = 31
- };
+ Tag_Shift = 32,
- static const quint64 Immediate_Mask_64 = 0x00020000u; // bit 49
+ IsIntegerConvertible_Shift = 48,
+ IsIntegerConvertible_Value = 3, // Unmanaged | IntCompat after shifting
- enum class ValueTypeInternal_64 {
- Empty = Immediate_Mask_64 | 0,
- Null = Immediate_Mask_64 | 0x08000u,
- Boolean = Immediate_Mask_64 | 0x10000u,
- Integer = Immediate_Mask_64 | 0x18000u
+ IsIntegerOrBool_Shift = 47,
+ IsIntegerOrBool_Value = 7, // Unmanaged | IntCompat | IntOrBool after shifting
};
- // Used only by 32-bit encoding
- enum Masks {
- SilentNaNBit = 0x00040000,
- NotDouble_Mask = 0x7ffa0000,
- };
- static const quint64 Immediate_Mask_32 = NotDouble_Mask | 0x00020000u | SilentNaNBit;
-
- enum class ValueTypeInternal_32 {
- Empty = Immediate_Mask_32 | 0,
- Null = Immediate_Mask_32 | 0x08000u,
- Boolean = Immediate_Mask_32 | 0x10000u,
- Integer = Immediate_Mask_32 | 0x18000u
+ static_assert(IsIntegerConvertible_Value ==
+ (quint32(TagBit::IntCompat) | quint32(TagBit::Unmanaged))
+ >> (IsIntegerConvertible_Shift - Tag_Shift));
+
+ static_assert(IsIntegerOrBool_Value ==
+ (quint32(TagBit::IntOrBool) | quint32(TagBit::IntCompat) | quint32(TagBit::Unmanaged))
+ >> (IsIntegerOrBool_Shift - Tag_Shift));
+
+ static constexpr quint64 ExponentMask = 0b0111111111110000ull << 48;
+
+ static constexpr quint64 Top1Mask = 0b1000000000000000ull << 48;
+ static constexpr quint64 Upper3Mask = 0b0000000000001110ull << 48;
+ static constexpr quint64 Lower5Mask = 0b0000000000011111ull;
+
+ static constexpr quint64 ManagedMask = ExponentMask | quint64(TagBit::Unmanaged) << Tag_Shift;
+ static constexpr quint64 DoubleMask = ManagedMask | quint64(TagBit::Special) << Tag_Shift;
+ static constexpr quint64 NumberMask = ManagedMask | quint64(TagBit::Number) << Tag_Shift;
+ static constexpr quint64 IntOrBoolMask = ManagedMask | quint64(TagBit::IntOrBool) << Tag_Shift;
+ static constexpr quint64 IntCompatMask = ManagedMask | quint64(TagBit::IntCompat) << Tag_Shift;
+
+ static constexpr quint64 EncodeMask = DoubleMask | NumberMask;
+
+ static constexpr quint64 DoubleDiscriminator
+ = ((quint64(TagBit::Unmanaged) | quint64(TagBit::Special)) << Tag_Shift);
+ static constexpr quint64 NumberDiscriminator
+ = ((quint64(TagBit::Unmanaged) | quint64(TagBit::Number)) << Tag_Shift);
+
+ // Things we can immediately determine by just looking at the upper 4 bytes.
+ enum class QuickType : quint32 {
+ // Managed takes precedence over all others. That is, other bits may be set if it's managed.
+ // However, since all others include the Unmanaged bit, we can still check them with simple
+ // equality operations.
+ Managed = Managed_Type,
+
+ Empty = Empty_Type,
+ Null = Null_Type,
+ Boolean = Boolean_Type,
+ Integer = Integer_Type,
+
+ PlusInf = quint32(TagBit::Number) | quint32(TagBit::Special) | quint32(TagBit::Unmanaged),
+ MinusInf = PlusInf | quint32(TagBit::SpecialNegative),
+ NaN = PlusInf | quint32(TagBit::SpecialQNaN),
+ MinusNaN = NaN | quint32(TagBit::SpecialNegative), // Can happen with UMinus on NaN
+ // All other values are doubles
};
+ // Aliases for easier porting. Remove those when possible
+ using ValueTypeInternal = QuickType;
enum {
- Managed_Type_Internal = 0
+ QT_Empty = Empty_Type,
+ QT_Null = Null_Type,
+ QT_Bool = Boolean_Type,
+ QT_Int = Integer_Type,
+ QuickType_Shift = Tag_Shift,
};
- using ValueTypeInternal = ValueTypeInternal_64;
+ inline Type type() const
+ {
+ const quint64 masked = _val & DoubleMask;
+ if (masked >= DoubleDiscriminator)
+ return Double_Type;
- enum {
- NaN_Mask = 0x7ff80000,
- };
+ // Any bit set in the exponent would have been caught above, as well as both bits being set.
+ // None of them being set as well as only Special being set means "managed".
+ // Only Unmanaged being set means "unmanaged". That's all remaining options.
+ if (masked != tagBitMask(TagBit::Unmanaged)) {
+ Q_ASSERT((_val & tagBitMask(TagBit::Unmanaged)) == 0);
+ return isUndefined() ? Undefined_Type : Managed_Type;
+ }
+
+ const Type ret = Type(tag());
+ Q_ASSERT(
+ ret == Empty_Type ||
+ ret == Null_Type ||
+ ret == Boolean_Type ||
+ ret == Integer_Type);
+ return ret;
+ }
inline quint64 quickType() const { return (_val >> QuickType_Shift); }
@@ -268,34 +284,52 @@ struct StaticValue
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 quickType() >= QT_Int; }
-
inline bool isUndefined() const { return _val == 0; }
- inline bool isDouble() const { return (_val >> IsDouble_Shift); }
- inline bool isManaged() const
+
+ inline bool isDouble() const
{
-#if QT_POINTER_SIZE == 4
- return value() && tag() == Managed_Type_Internal;
-#else
- return _val && ((_val >> IsManagedOrUndefined_Shift) == 0);
-#endif
+ // If any of the flipped exponent bits are 1, it's a regular double, and the masked tag is
+ // larger than Unmanaged | Special.
+ //
+ // If all (flipped) exponent bits are 0:
+ // 1. If Unmanaged bit is 0, it's managed
+ // 2. If the Unmanaged bit it is 1, and the Special bit is 0, it's not a special double
+ // 3. If both are 1, it is a special double and the masked tag equals Unmanaged | Special.
+
+ return (_val & DoubleMask) >= DoubleDiscriminator;
}
- inline bool isManagedOrUndefined() const
+
+ inline bool isNumber() const
{
-#if QT_POINTER_SIZE == 4
- return tag() == Managed_Type_Internal;
-#else
- return ((_val >> IsManagedOrUndefined_Shift) == 0);
-#endif
+ // If any of the flipped exponent bits are 1, it's a regular double, and the masked tag is
+ // larger than Unmanaged | Number.
+ //
+ // If all (flipped) exponent bits are 0:
+ // 1. If Unmanaged bit is 0, it's managed
+ // 2. If the Unmanaged bit it is 1, and the Number bit is 0, it's not number
+ // 3. If both are 1, it is a number and masked tag equals Unmanaged | Number.
+
+ return (_val & NumberMask) >= NumberDiscriminator;
}
- inline bool isIntOrBool() const {
- return (_val >> IsIntegerOrBool_Shift) == 3;
+ inline bool isManagedOrUndefined() const { return (_val & ManagedMask) == 0; }
+
+ // If any other bit is set in addition to the managed mask, it's not undefined.
+ inline bool isManaged() const
+ {
+ return isManagedOrUndefined() && !isUndefined();
+ }
+
+ inline bool isIntOrBool() const
+ {
+ // It's an int or bool if all the exponent bits are 0,
+ // and the "int or bool" bit as well as the "umanaged" bit are set,
+ return (_val >> IsIntegerOrBool_Shift) == IsIntegerOrBool_Value;
}
inline bool integerCompatible() const {
Q_ASSERT(!isEmpty());
- return (_val >> IsIntegerConvertible_Shift) == 1;
+ return (_val >> IsIntegerConvertible_Shift) == IsIntegerConvertible_Value;
}
static inline bool integerCompatible(StaticValue a, StaticValue b) {
@@ -308,36 +342,45 @@ struct StaticValue
inline bool isNaN() const
{
- return (tag() & 0x7ffc0000 ) == 0x00040000;
+ switch (QuickType(tag())) {
+ case QuickType::NaN:
+ case QuickType::MinusNaN:
+ return true;
+ default:
+ return false;
+ }
}
inline bool isPositiveInt() const {
-#if QT_POINTER_SIZE == 4
return isInteger() && int_32() >= 0;
-#else
- return (_val >> IsPositiveIntShift) == (quint64(ValueTypeInternal::Integer) << 1);
-#endif
}
QV4_NEARLY_ALWAYS_INLINE double doubleValue() const {
Q_ASSERT(isDouble());
double d;
- StaticValue v = *this;
- v._val ^= NaNEncodeMask;
- memcpy(&d, &v._val, 8);
+ const quint64 unmasked = _val ^ EncodeMask;
+ memcpy(&d, &unmasked, 8);
return d;
}
QV4_NEARLY_ALWAYS_INLINE void setDouble(double d) {
- if (qt_is_nan(d))
- d = qt_qnan();
- memcpy(&_val, &d, 8);
- _val ^= NaNEncodeMask;
+ if (qt_is_nan(d)) {
+ // We cannot store just any NaN. It has to be a NaN with only the quiet bit
+ // set in the upper bits of the mantissa and the sign bit off.
+ // qt_qnan() happens to produce such a thing via std::numeric_limits,
+ // but this is actually not guaranteed. Therefore, we make our own.
+ _val = (quint64(QuickType::NaN) << Tag_Shift);
+ Q_ASSERT(isNaN());
+ } else {
+ memcpy(&_val, &d, 8);
+ _val ^= EncodeMask;
+ }
+
Q_ASSERT(isDouble());
}
inline bool isInt32() {
- if (tag() == quint32(ValueTypeInternal::Integer))
+ if (tag() == quint32(QuickType::Integer))
return true;
if (isDouble()) {
double d = doubleValue();
@@ -350,12 +393,12 @@ struct StaticValue
}
QV4_NEARLY_ALWAYS_INLINE static bool isInt32(double d) {
- int i = int(d);
+ int i = QJSNumberCoercion::toInteger(d);
return (i == d && !(d == 0 && std::signbit(d)));
}
double asDouble() const {
- if (tag() == quint32(ValueTypeInternal::Integer))
+ if (tag() == quint32(QuickType::Integer))
return int_32();
return doubleValue();
}
@@ -371,7 +414,7 @@ struct StaticValue
inline bool tryIntegerConversion() {
bool b = integerCompatible();
if (b)
- setTagValue(quint32(ValueTypeInternal::Integer), value());
+ setTagValue(quint32(QuickType::Integer), value());
return b;
}
@@ -402,19 +445,18 @@ struct StaticValue
return 0; // Coercion of NaN to int, results in 0;
}
- Q_UNREACHABLE();
- return 0;
+ Q_UNREACHABLE_RETURN(0);
}
ReturnedValue *data_ptr() { return &_val; }
constexpr ReturnedValue asReturnedValue() const { return _val; }
constexpr static StaticValue fromReturnedValue(ReturnedValue val) { return {val}; }
- inline static constexpr StaticValue emptyValue() { return { tagValue(quint32(ValueTypeInternal::Empty), 0) }; }
- static inline constexpr StaticValue fromBoolean(bool b) { return { tagValue(quint32(ValueTypeInternal::Boolean), b) }; }
- static inline constexpr StaticValue fromInt32(int i) { return { tagValue(quint32(ValueTypeInternal::Integer), quint32(i)) }; }
+ inline static constexpr StaticValue emptyValue() { return { tagValue(quint32(QuickType::Empty), 0) }; }
+ static inline constexpr StaticValue fromBoolean(bool b) { return { tagValue(quint32(QuickType::Boolean), b) }; }
+ static inline constexpr StaticValue fromInt32(int i) { return { tagValue(quint32(QuickType::Integer), quint32(i)) }; }
inline static constexpr StaticValue undefinedValue() { return { 0 }; }
- static inline constexpr StaticValue nullValue() { return { tagValue(quint32(ValueTypeInternal::Null), 0) }; }
+ static inline constexpr StaticValue nullValue() { return { tagValue(quint32(QuickType::Null), 0) }; }
static inline StaticValue fromDouble(double d)
{
@@ -427,7 +469,7 @@ struct StaticValue
{
StaticValue v;
if (i < uint(std::numeric_limits<int>::max())) {
- v.setTagValue(quint32(ValueTypeInternal::Integer), i);
+ v.setTagValue(quint32(QuickType::Integer), i);
} else {
v.setDouble(i);
}
@@ -452,8 +494,133 @@ struct StaticValue
{
return static_cast<uint>(toInt32(d));
}
+
+ // While a value containing a Heap::Base* is not actually static, we still implement
+ // the setting and retrieving of heap pointers here in order to have the encoding
+ // scheme completely in one place.
+
+#if QT_POINTER_SIZE == 8
+
+ // All pointer shifts are from more significant to less significant bits.
+ // When encoding, we shift right by that amount. When decoding, we shift left.
+ // Negative numbers mean shifting the other direction. 0 means no shifting.
+ //
+ // The IA64 and Sparc64 cases are mostly there to demonstrate the idea. Sparc64
+ // and IA64 are not officially supported, but we can expect more platforms with
+ // similar "problems" in the future.
+ enum PointerShift {
+#if 0 && defined(Q_OS_ANDROID) && defined(Q_PROCESSOR_ARM_64)
+ // We used to assume that Android on arm64 uses the top byte to store pointer tags.
+ // However, at least currently, the pointer tags are only applied on new/malloc and
+ // delete/free, not on mmap() and munmap(). We manage the JS heap directly using
+ // mmap, so we don't have to preserve any tags.
+ //
+ // If this ever changes, here is how to preserve the top byte:
+ // Move it to Upper3 and Lower5.
+ Top1Shift = 0,
+ Upper3Shift = 12,
+ Lower5Shift = 56,
+#elif defined(Q_PROCESSOR_IA64)
+ // On ia64, bits 63-61 in a 64-bit pointer are used to store the virtual region
+ // number. We can move those to Upper3.
+ Top1Shift = 0,
+ Upper3Shift = 12,
+ Lower5Shift = 0,
+#elif defined(Q_PROCESSOR_SPARC_64)
+ // Sparc64 wants to use 52 bits for pointers.
+ // Upper3 can stay where it is, bit48 moves to the top bit.
+ Top1Shift = -15,
+ Upper3Shift = 0,
+ Lower5Shift = 0,
+#elif 0 // TODO: Once we need 5-level page tables, add the appropriate check here.
+ // With 5-level page tables (as possible on linux) we need 57 address bits.
+ // Upper3 can stay where it is, bit48 moves to the top bit, the rest moves to Lower5.
+ Top1Shift = -15,
+ Upper3Shift = 0,
+ Lower5Shift = 52,
+#else
+ Top1Shift = 0,
+ Upper3Shift = 0,
+ Lower5Shift = 0
+#endif
+ };
+
+ template<int Offset, quint64 Mask>
+ static constexpr quint64 movePointerBits(quint64 val)
+ {
+ if constexpr (Offset > 0)
+ return (val & ~Mask) | ((val & Mask) >> Offset);
+ if constexpr (Offset < 0)
+ return (val & ~Mask) | ((val & Mask) << -Offset);
+ return val;
+ }
+
+ template<int Offset, quint64 Mask>
+ static constexpr quint64 storePointerBits(quint64 val)
+ {
+ constexpr quint64 OriginMask = movePointerBits<-Offset, Mask>(Mask);
+ return movePointerBits<Offset, OriginMask>(val);
+ }
+
+ template<int Offset, quint64 Mask>
+ static constexpr quint64 retrievePointerBits(quint64 val)
+ {
+ return movePointerBits<-Offset, Mask>(val);
+ }
+
+ QML_NEARLY_ALWAYS_INLINE HeapBasePtr m() const
+ {
+ Q_ASSERT(!(_val & ManagedMask));
+
+ // Re-assemble the pointer from its fragments.
+ const quint64 tmp = retrievePointerBits<Top1Shift, Top1Mask>(
+ retrievePointerBits<Upper3Shift, Upper3Mask>(
+ retrievePointerBits<Lower5Shift, Lower5Mask>(_val)));
+
+ HeapBasePtr b;
+ memcpy(&b, &tmp, 8);
+ return b;
+ }
+ QML_NEARLY_ALWAYS_INLINE void setM(HeapBasePtr b)
+ {
+ quint64 tmp;
+ memcpy(&tmp, &b, 8);
+
+ // Has to be aligned to 32 bytes
+ Q_ASSERT(!(tmp & Lower5Mask));
+
+ // MinGW produces a bogus warning about array bounds.
+ // There is no array access here.
+ QT_WARNING_PUSH
+ QT_WARNING_DISABLE_GCC("-Warray-bounds")
+
+ // Encode the pointer.
+ _val = storePointerBits<Top1Shift, Top1Mask>(
+ storePointerBits<Upper3Shift, Upper3Mask>(
+ storePointerBits<Lower5Shift, Lower5Mask>(tmp)));
+
+ QT_WARNING_POP
+ }
+#elif QT_POINTER_SIZE == 4
+ QML_NEARLY_ALWAYS_INLINE HeapBasePtr m() const
+ {
+ Q_STATIC_ASSERT(sizeof(HeapBasePtr) == sizeof(quint32));
+ HeapBasePtr b;
+ quint32 v = value();
+ memcpy(&b, &v, 4);
+ return b;
+ }
+ QML_NEARLY_ALWAYS_INLINE void setM(HeapBasePtr b)
+ {
+ quint32 v;
+ memcpy(&v, &b, 4);
+ setTagValue(quint32(QuickType::Managed), v);
+ }
+#else
+# error "unsupported pointer size"
+#endif
};
-Q_STATIC_ASSERT(std::is_trivial<StaticValue>::value);
+Q_STATIC_ASSERT(std::is_trivial_v<StaticValue>);
struct Encode {
static constexpr ReturnedValue undefined() {