diff options
author | Ulf Hermann <ulf.hermann@qt.io> | 2022-05-03 12:06:25 +0200 |
---|---|---|
committer | Ulf Hermann <ulf.hermann@qt.io> | 2022-05-07 08:37:42 +0200 |
commit | 38002df2065d3730defe3513f73088b444e68139 (patch) | |
tree | 62963389291a81acf1ec4544696c014fb90d8e0e | |
parent | b94ec982c1bbc9cf4e0efdbd0c4b8f14ec4ebdcc (diff) |
Endian: Provide special integer bitfield unions
Our previous approach of creating a union from individual special
integer bitfields leads to undefined values because only one member of a
union can be active at any given time. Compilers have finally caught up
with us on that and have started removing "no-op" writes to members.
The primary user of the special integer bitfield unions is
qv4compileddata_p.h in qtdeclarative. We want our on-disk format of
QML compilation units to be platform agnostic and space efficient.
Pick-to: 5.15 6.2 6.3
Task-number: QTBUG-99545
Change-Id: I24847bda2c364eb8ba75f074cde2a9bec25ced06
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
-rw-r--r-- | src/corelib/global/qendian_p.h | 181 | ||||
-rw-r--r-- | tests/auto/corelib/global/qtendian/tst_qtendian.cpp | 106 |
2 files changed, 286 insertions, 1 deletions
diff --git a/src/corelib/global/qendian_p.h b/src/corelib/global/qendian_p.h index 609b1a97be..cf4e4af5c4 100644 --- a/src/corelib/global/qendian_p.h +++ b/src/corelib/global/qendian_p.h @@ -131,6 +131,187 @@ using qint32_be_bitfield = QBEIntegerBitfield<int, pos, width>; template<int pos, int width> using quint32_be_bitfield = QBEIntegerBitfield<uint, pos, width>; +enum class QSpecialIntegerBitfieldInitializer {}; +constexpr QSpecialIntegerBitfieldInitializer QSpecialIntegerBitfieldZero{}; + +template<class S> +class QSpecialIntegerStorage +{ +public: + using UnsignedStorageType = std::make_unsigned_t<typename S::StorageType>; + + constexpr QSpecialIntegerStorage() = default; + constexpr QSpecialIntegerStorage(QSpecialIntegerBitfieldInitializer) : val(0) {} + constexpr QSpecialIntegerStorage(UnsignedStorageType initial) : val(initial) {} + + UnsignedStorageType val; +}; + +template<class S, int pos, int width, class T = typename S::StorageType> +class QSpecialIntegerAccessor; + +template<class S, int pos, int width, class T = typename S::StorageType> +class QSpecialIntegerConstAccessor +{ + Q_DISABLE_COPY_MOVE(QSpecialIntegerConstAccessor) +public: + using Storage = const QSpecialIntegerStorage<S>; + using Type = T; + using UnsignedType = std::make_unsigned_t<T>; + + operator Type() const noexcept + { + if constexpr (std::is_signed_v<Type>) { + UnsignedType i = S::fromSpecial(storage->val); + i <<= (sizeof(Type) * 8) - width - pos; + Type t = Type(i); + t >>= (sizeof(Type) * 8) - width; + return t; + } + return (S::fromSpecial(storage->val) & mask()) >> pos; + } + + bool operator!() const noexcept { return !(storage->val & S::toSpecial(mask())); } + + static constexpr UnsignedType mask() noexcept + { + return ((UnsignedType(1) << width) - 1) << pos; + } + +private: + template<class Storage, typename... Accessors> + friend class QSpecialIntegerBitfieldUnion; + friend class QSpecialIntegerAccessor<S, pos, width, T>; + + explicit QSpecialIntegerConstAccessor(Storage *storage) : storage(storage) {} + + friend bool operator==(const QSpecialIntegerConstAccessor<S, pos, width, T> &i, + const QSpecialIntegerConstAccessor<S, pos, width, T> &j) noexcept + { + return ((i.storage->val ^ j.storage->val) & S::toSpecial(mask())) == 0; + } + + friend bool operator!=(const QSpecialIntegerConstAccessor<S, pos, width, T> &i, + const QSpecialIntegerConstAccessor<S, pos, width, T> &j) noexcept + { + return ((i.storage->val ^ j.storage->val) & S::toSpecial(mask())) != 0; + } + + Storage *storage; +}; + +template<class S, int pos, int width, class T> +class QSpecialIntegerAccessor +{ + Q_DISABLE_COPY_MOVE(QSpecialIntegerAccessor) +public: + using Const = QSpecialIntegerConstAccessor<S, pos, width, T>; + using Storage = QSpecialIntegerStorage<S>; + using Type = T; + using UnsignedType = std::make_unsigned_t<T>; + + QSpecialIntegerAccessor &operator=(Type t) + { + UnsignedType i = S::fromSpecial(storage->val); + i &= ~Const::mask(); + i |= (UnsignedType(t) << pos) & Const::mask(); + storage->val = S::toSpecial(i); + return *this; + } + + operator Const() { return Const(storage); } + +private: + template<class Storage, typename... Accessors> + friend class QSpecialIntegerBitfieldUnion; + + explicit QSpecialIntegerAccessor(Storage *storage) : storage(storage) {} + + Storage *storage; +}; + +template<class S, typename... Accessors> +class QSpecialIntegerBitfieldUnion +{ +public: + constexpr QSpecialIntegerBitfieldUnion() = default; + constexpr QSpecialIntegerBitfieldUnion(QSpecialIntegerBitfieldInitializer initial) + : storage(initial) + {} + + constexpr QSpecialIntegerBitfieldUnion( + typename QSpecialIntegerStorage<S>::UnsignedStorageType initial) + : storage(initial) + {} + + template<typename A> + void set(typename A::Type value) + { + member<A>() = value; + } + + template<typename A> + typename A::Type get() const + { + return member<A>(); + } + + typename QSpecialIntegerStorage<S>::UnsignedStorageType data() const + { + return storage.val; + } + +private: + template<typename A> + static constexpr bool isAccessor = std::disjunction_v<std::is_same<A, Accessors>...>; + + template<typename A> + A member() + { + static_assert(isAccessor<A>); + return A(&storage); + } + + template<typename A> + typename A::Const member() const + { + static_assert(isAccessor<A>); + return typename A::Const(&storage); + } + + QSpecialIntegerStorage<S> storage; +}; + +template<typename T, typename... Accessors> +using QLEIntegerBitfieldUnion + = QSpecialIntegerBitfieldUnion<QLittleEndianStorageType<T>, Accessors...>; + +template<typename T, typename... Accessors> +using QBEIntegerBitfieldUnion + = QSpecialIntegerBitfieldUnion<QBigEndianStorageType<T>, Accessors...>; + +template<typename... Accessors> +using qint32_le_bitfield_union = QLEIntegerBitfieldUnion<int, Accessors...>; +template<typename... Accessors> +using quint32_le_bitfield_union = QLEIntegerBitfieldUnion<uint, Accessors...>; +template<typename... Accessors> +using qint32_be_bitfield_union = QBEIntegerBitfieldUnion<int, Accessors...>; +template<typename... Accessors> +using quint32_be_bitfield_union = QBEIntegerBitfieldUnion<uint, Accessors...>; + +template<int pos, int width, typename T = int> +using qint32_le_bitfield_member + = QSpecialIntegerAccessor<QLittleEndianStorageType<int>, pos, width, T>; +template<int pos, int width, typename T = uint> +using quint32_le_bitfield_member + = QSpecialIntegerAccessor<QLittleEndianStorageType<uint>, pos, width, T>; +template<int pos, int width, typename T = int> +using qint32_be_bitfield_member + = QSpecialIntegerAccessor<QBigEndianStorageType<int>, pos, width, T>; +template<int pos, int width, typename T = uint> +using quint32_be_bitfield_member + = QSpecialIntegerAccessor<QBigEndianStorageType<uint>, pos, width, T>; + QT_END_NAMESPACE #endif // QENDIAN_P_H diff --git a/tests/auto/corelib/global/qtendian/tst_qtendian.cpp b/tests/auto/corelib/global/qtendian/tst_qtendian.cpp index 74c2deff52..015c896f8f 100644 --- a/tests/auto/corelib/global/qtendian/tst_qtendian.cpp +++ b/tests/auto/corelib/global/qtendian/tst_qtendian.cpp @@ -30,11 +30,17 @@ #include <QTest> #include <QtCore/qendian.h> #include <QtCore/private/qendian_p.h> - +#include <QtCore/qsysinfo.h> class tst_QtEndian: public QObject { Q_OBJECT +public: + enum Signedness { + Unsigned, + Signed + }; + Q_ENUM(Signedness); private slots: void fromBigEndian(); @@ -55,6 +61,9 @@ private slots: void endianIntegers(); void endianBitfields(); + + void endianBitfieldUnions_data(); + void endianBitfieldUnions(); }; struct TestData @@ -387,5 +396,100 @@ void tst_QtEndian::endianBitfields() QCOMPARE(u.bottom, -8); } +template<template<typename... Accessors> typename Union, template<int, int, typename> typename Member> +void testBitfieldUnion() +{ + using upper = Member<21, 11, uint>; + using lower = Member<10, 11, uint>; + using bottom = Member<0, 10, int>; + + using UnionType = Union<upper, lower, bottom>; + UnionType u; + + u.template set<upper>(200); + QCOMPARE(u.template get<upper>(), 200U); + u.template set<lower>(1000); + u.template set<bottom>(-8); + QCOMPARE(u.template get<lower>(), 1000U); + QCOMPARE(u.template get<upper>(), 200U); + + u.template set<lower>(u.template get<lower>() + u.template get<upper>()); + QCOMPARE(u.template get<upper>(), 200U); + QCOMPARE(u.template get<lower>(), 1200U); + + u.template set<upper>(65536 + 7); + u.template set<lower>(65535); + QCOMPARE(u.template get<lower>(), 65535U & ((1<<11) - 1)); + QCOMPARE(u.template get<upper>(), 7U); + + QCOMPARE(u.template get<bottom>(), -8); + + UnionType u2(QSpecialIntegerBitfieldZero); + QCOMPARE(u2.data(), 0U); + + UnionType u3(42U); + QCOMPARE(u3.data(), 42U); + + using BEUintAccessor = QSpecialIntegerAccessor<QBigEndianStorageType<uint>, 21, 11>; + using LEUintAccessor = QSpecialIntegerAccessor<QLittleEndianStorageType<uint>, 21, 11>; + using BEIntAccessor = QSpecialIntegerAccessor<QBigEndianStorageType<int>, 0, 10>; + using LEIntAccessor = QSpecialIntegerAccessor<QLittleEndianStorageType<int>, 0, 10>; + + if constexpr (std::is_same_v<BEUintAccessor, upper>) { + QCOMPARE(u.template get<BEUintAccessor>(), 7U); + } else if constexpr (std::is_same_v<LEUintAccessor, upper>) { + QCOMPARE(u.template get<LEUintAccessor>(), 7U); + } else if constexpr (std::is_same_v<BEIntAccessor, bottom>) { + QCOMPARE(u.template get<BEIntAccessor>(), -8); + } else if constexpr (std::is_same_v<LEIntAccessor, bottom>) { + QCOMPARE(u.template get<LEIntAccessor>(), -8); + } else { + QFAIL("none of the manually defined accessors match"); + } +} + +void tst_QtEndian::endianBitfieldUnions_data() +{ + QTest::addColumn<QSysInfo::Endian>("byteOrder"); + QTest::addColumn<Signedness>("signedness"); + + QTest::addRow("little endian unsigned") << QSysInfo::LittleEndian << Unsigned; + QTest::addRow("little endian signed") << QSysInfo::LittleEndian << Signed; + QTest::addRow("big endian unsigned") << QSysInfo::BigEndian << Unsigned; + QTest::addRow("big endian signed") << QSysInfo::BigEndian << Signed; +} + +void tst_QtEndian::endianBitfieldUnions() +{ + QFETCH(QSysInfo::Endian, byteOrder); + QFETCH(Signedness, signedness); + + switch (byteOrder) { + case QSysInfo::LittleEndian: + switch (signedness) { + case Unsigned: + testBitfieldUnion<quint32_le_bitfield_union, quint32_le_bitfield_member>(); + return; + case Signed: + testBitfieldUnion<qint32_le_bitfield_union, qint32_le_bitfield_member>(); + return; + } + Q_UNREACHABLE(); + return; + case QSysInfo::BigEndian: + switch (signedness) { + case Unsigned: + testBitfieldUnion<quint32_be_bitfield_union, quint32_be_bitfield_member>(); + return; + case Signed: + testBitfieldUnion<qint32_be_bitfield_union, qint32_be_bitfield_member>(); + return; + } + Q_UNREACHABLE(); + return; + } +} + + QTEST_MAIN(tst_QtEndian) #include "tst_qtendian.moc" |