diff --git a/src/corelib/global/qglobal.h b/src/corelib/global/qglobal.h index f8f306f6147414c87ec9cf805af32c903dc6d3f6..6ee3e37ce632e353b55e7e45fd6dbd61d8e3c0d8 100644 --- a/src/corelib/global/qglobal.h +++ b/src/corelib/global/qglobal.h @@ -448,6 +448,9 @@ template <> struct QIntegerForSize<1> { typedef quint8 Unsigned; typedef qin template <> struct QIntegerForSize<2> { typedef quint16 Unsigned; typedef qint16 Signed; }; template <> struct QIntegerForSize<4> { typedef quint32 Unsigned; typedef qint32 Signed; }; template <> struct QIntegerForSize<8> { typedef quint64 Unsigned; typedef qint64 Signed; }; +#if defined(Q_CC_GNU) && defined(__SIZEOF_INT128__) +template <> struct QIntegerForSize<16> { __extension__ typedef unsigned __int128 Unsigned; __extension__ typedef __int128 Signed; }; +#endif template <class T> struct QIntegerForSizeof: QIntegerForSize<sizeof(T)> { }; typedef QIntegerForSize<Q_PROCESSOR_WORDSIZE>::Signed qregisterint; typedef QIntegerForSize<Q_PROCESSOR_WORDSIZE>::Unsigned qregisteruint; diff --git a/src/corelib/global/qnumeric_p.h b/src/corelib/global/qnumeric_p.h index 5fa0a881a37836fdb9dfbc89ef4d9cf3fd6e14ae..e5f9d8e13e116da0ae3560c1ed9884e15354fc93 100644 --- a/src/corelib/global/qnumeric_p.h +++ b/src/corelib/global/qnumeric_p.h @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2015 The Qt Company Ltd. +** Copyright (C) 2015 Intel Corporation. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the QtCore module of the Qt Toolkit. @@ -47,6 +48,18 @@ #include "QtCore/qglobal.h" +#include <limits> + +#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) +# include <intrin.h> +#elif defined(Q_CC_INTEL) +# include <immintrin.h> // for _addcarry_u<nn> +#endif + +#ifndef __has_builtin +# define __has_builtin(x) 0 +#endif + QT_BEGIN_NAMESPACE #if !defined(Q_CC_MIPS) @@ -188,6 +201,110 @@ static inline bool qt_is_finite(float d) } } +// +// Overflow math +// +namespace { +template <typename T> inline typename QtPrivate::QEnableIf<QtPrivate::is_unsigned<T>::value, bool>::Type +add_overflow(T v1, T v2, T *r) +{ + // unsigned additions are well-defined + *r = v1 + v2; + return v1 > T(v1 + v2); +} + +template <typename T> inline typename QtPrivate::QEnableIf<QtPrivate::is_unsigned<T>::value, bool>::Type +mul_overflow(T v1, T v2, T *r) +{ + // use the next biggest type + // Note: for 64-bit systems where __int128 isn't supported, this will cause an error. + // A fallback is present below. + typedef typename QIntegerForSize<sizeof(T) * 2>::Unsigned Larger; + Larger lr = Larger(v1) * Larger(v2); + *r = T(lr); + return lr > std::numeric_limits<T>::max(); +} + +#if defined(__SIZEOF_INT128__) +# define HAVE_MUL64_OVERFLOW +#endif + +// GCC 5 and Clang have builtins to detect overflows +#if (defined(Q_CC_GNU) && !defined(Q_CC_INTEL) && Q_CC_GNU >= 500) || __has_builtin(__builtin_uadd_overflow) +template <> inline bool add_overflow(unsigned v1, unsigned v2, unsigned *r) +{ return __builtin_uadd_overflow(v1, v2, r); } +#endif +#if (defined(Q_CC_GNU) && !defined(Q_CC_INTEL) && Q_CC_GNU >= 500) || __has_builtin(__builtin_uaddl_overflow) +template <> inline bool add_overflow(unsigned long v1, unsigned long v2, unsigned long *r) +{ return __builtin_uaddl_overflow(v1, v2, r); } +#endif +#if (defined(Q_CC_GNU) && !defined(Q_CC_INTEL) && Q_CC_GNU >= 500) || __has_builtin(__builtin_uaddll_overflow) +template <> inline bool add_overflow(unsigned long long v1, unsigned long long v2, unsigned long long *r) +{ return __builtin_uaddll_overflow(v1, v2, r); } +#endif + +#if (defined(Q_CC_GNU) && !defined(Q_CC_INTEL) && Q_CC_GNU >= 500) || __has_builtin(__builtin_umul_overflow) +template <> inline bool mul_overflow(unsigned v1, unsigned v2, unsigned *r) +{ return __builtin_umul_overflow(v1, v2, r); } +#endif +#if (defined(Q_CC_GNU) && !defined(Q_CC_INTEL) && Q_CC_GNU >= 500) || __has_builtin(__builtin_umull_overflow) +template <> inline bool mul_overflow(unsigned long v1, unsigned long v2, unsigned long *r) +{ return __builtin_umull_overflow(v1, v2, r); } +#endif +#if (defined(Q_CC_GNU) && !defined(Q_CC_INTEL) && Q_CC_GNU >= 500) || __has_builtin(__builtin_umulll_overflow) +template <> inline bool mul_overflow(unsigned long long v1, unsigned long long v2, unsigned long long *r) +{ return __builtin_umulll_overflow(v1, v2, r); } +# define HAVE_MUL64_OVERFLOW +#endif + +#if ((defined(Q_CC_MSVC) && _MSC_VER >= 1800) || defined(Q_CC_INTEL)) && defined(Q_PROCESSOR_X86) +template <> inline bool add_overflow(unsigned v1, unsigned v2, unsigned *r) +{ return _addcarry_u32(0, v1, v2, r); } +# ifdef Q_CC_MSVC // longs are 32-bit +template <> inline bool add_overflow(unsigned long v1, unsigned long v2, unsigned long *r) +{ return _addcarry_u32(0, v1, v2, reinterpret_cast<unsigned *>(r)); } +# endif +#endif +#if ((defined(Q_CC_MSVC) && _MSC_VER >= 1800) || defined(Q_CC_INTEL)) && defined(Q_PROCESSOR_X86_64) +template <> inline bool add_overflow(quint64 v1, quint64 v2, quint64 *r) +{ return _addcarry_u64(0, v1, v2, reinterpret_cast<unsigned __int64 *>(r)); } +# ifndef Q_CC_MSVC // longs are 64-bit +template <> inline bool add_overflow(unsigned long v1, unsigned long v2, unsigned long *r) +{ return _addcarry_u64(0, v1, v2, reinterpret_cast<unsigned __int64 *>(r)); } +# endif +#endif + +#if defined(Q_CC_MSVC) && (defined(Q_PROCESSOR_X86_64) || defined(Q_PROCESSOR_IA64)) +#pragma intrinsic(_umul128) +template <> inline bool mul_overflow(quint64 v1, quint64 v2, quint64 *r) +{ + // use 128-bit multiplication with the _umul128 intrinsic + // https://msdn.microsoft.com/en-us/library/3dayytw9.aspx + quint64 high; + *r = _umul128(v1, v2, &high); + return high; +} +# define HAVE_MUL64_OVERFLOW +#endif + +#if !defined(HAVE_MUL64_OVERFLOW) && defined(__LP64__) +// no 128-bit multiplication, we need to figure out with a slow division +template <> inline bool mul_overflow(quint64 v1, quint64 v2, quint64 *r) +{ + if (v2 && v1 > std::numeric_limits<quint64>::max() / v2) + return true; + *r = v1 * v2; + return false; +} +template <> inline bool mul_overflow(unsigned long v1, unsigned long v2, unsigned long *r) +{ + return mul_overflow<quint64>(v1, v2, reinterpret_cast<quint64 *>(r)); +} +#else +# undef HAVE_MUL64_OVERFLOW +#endif +} + QT_END_NAMESPACE #endif // QNUMERIC_P_H diff --git a/tests/auto/corelib/global/qnumeric/qnumeric.pro b/tests/auto/corelib/global/qnumeric/qnumeric.pro index 00f3635be956d9770cfefac978d53d99248e27fe..0772ce6aab27a80457984ce97f6a4445c6c7b476 100644 --- a/tests/auto/corelib/global/qnumeric/qnumeric.pro +++ b/tests/auto/corelib/global/qnumeric/qnumeric.pro @@ -1,6 +1,6 @@ CONFIG += testcase parallel_test TARGET = tst_qnumeric -QT = core testlib +QT = core-private testlib SOURCES = tst_qnumeric.cpp DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0 intel_icc: QMAKE_CXXFLAGS += -fp-model strict diff --git a/tests/auto/corelib/global/qnumeric/tst_qnumeric.cpp b/tests/auto/corelib/global/qnumeric/tst_qnumeric.cpp index fdc8bc6aabc7748ff14a0e09ec8d086ab86df9bc..59a536ed25b16cdf90f7ed699e5b59a026eb39b8 100644 --- a/tests/auto/corelib/global/qnumeric/tst_qnumeric.cpp +++ b/tests/auto/corelib/global/qnumeric/tst_qnumeric.cpp @@ -34,6 +34,7 @@ #include <QtTest/QtTest> #include <QtGlobal> +#include "private/qnumeric_p.h" #include <math.h> #include <float.h> @@ -50,6 +51,10 @@ private slots: void floatDistance(); void floatDistance_double_data(); void floatDistance_double(); + void addOverflow_data(); + void addOverflow(); + void mulOverflow_data(); + void mulOverflow(); }; void tst_QNumeric::fuzzyCompare_data() @@ -206,5 +211,160 @@ void tst_QNumeric::floatDistance_double() QCOMPARE(qFloatDistance(val1, val2), expectedDistance); } +void tst_QNumeric::addOverflow_data() +{ + QTest::addColumn<int>("size"); + QTest::newRow("quint8") << 8; + QTest::newRow("quint16") << 16; + QTest::newRow("quint32") << 32; + QTest::newRow("quint64") << 64; + QTest::newRow("ulong") << 48; // it's either 32- or 64-bit, so on average it's 48 :-) +} + +// Note: in release mode, all the tests may be statically determined and only the calls +// to QTest::toString and QTest::qCompare will remain. +template <typename Int> static void addOverflow_template() +{ +#if defined(Q_CC_MSVC) && Q_CC_MSVC < 1900 + QSKIP("Test disabled, this test generates an Internal Compiler Error compiling"); +#else + const Int max = std::numeric_limits<Int>::max(); + Int r; + + // basic values + QCOMPARE(add_overflow(Int(0), Int(0), &r), false); + QCOMPARE(r, Int(0)); + QCOMPARE(add_overflow(Int(1), Int(0), &r), false); + QCOMPARE(r, Int(1)); + QCOMPARE(add_overflow(Int(0), Int(1), &r), false); + QCOMPARE(r, Int(1)); + + // half-way through max + QCOMPARE(add_overflow(Int(max/2), Int(max/2), &r), false); + QCOMPARE(r, Int(max / 2 * 2)); + QCOMPARE(add_overflow(Int(max/2 - 1), Int(max/2 + 1), &r), false); + QCOMPARE(r, Int(max / 2 * 2)); + QCOMPARE(add_overflow(Int(max/2 + 1), Int(max/2), &r), false); + QCOMPARE(r, max); + QCOMPARE(add_overflow(Int(max/2), Int(max/2 + 1), &r), false); + QCOMPARE(r, max); + + // more than half + QCOMPARE(add_overflow(Int(max/4 * 3), Int(max/4), &r), false); + QCOMPARE(r, Int(max / 4 * 4)); + + // max + QCOMPARE(add_overflow(max, Int(0), &r), false); + QCOMPARE(r, max); + QCOMPARE(add_overflow(Int(0), max, &r), false); + QCOMPARE(r, max); + + // 64-bit issues + if (max > std::numeric_limits<uint>::max()) { + QCOMPARE(add_overflow(Int(std::numeric_limits<uint>::max()), Int(std::numeric_limits<uint>::max()), &r), false); + QCOMPARE(r, Int(2 * Int(std::numeric_limits<uint>::max()))); + } + + // overflows + QCOMPARE(add_overflow(max, Int(1), &r), true); + QCOMPARE(add_overflow(Int(1), max, &r), true); + QCOMPARE(add_overflow(Int(max/2 + 1), Int(max/2 + 1), &r), true); +#endif +} + +void tst_QNumeric::addOverflow() +{ + QFETCH(int, size); + if (size == 8) + addOverflow_template<quint8>(); + if (size == 16) + addOverflow_template<quint16>(); + if (size == 32) + addOverflow_template<quint32>(); + if (size == 48) + addOverflow_template<ulong>(); // not really 48-bit + if (size == 64) + addOverflow_template<quint64>(); +} + +void tst_QNumeric::mulOverflow_data() +{ + addOverflow_data(); +} + +// Note: in release mode, all the tests may be statically determined and only the calls +// to QTest::toString and QTest::qCompare will remain. +template <typename Int> static void mulOverflow_template() +{ +#if defined(Q_CC_MSVC) && Q_CC_MSVC < 1900 + QSKIP("Test disabled, this test generates an Internal Compiler Error compiling"); +#else + const Int max = std::numeric_limits<Int>::max(); + const Int middle = Int(max >> (sizeof(Int) * CHAR_BIT / 2)); + Int r; + + // basic multiplications + QCOMPARE(mul_overflow(Int(0), Int(0), &r), false); + QCOMPARE(r, Int(0)); + QCOMPARE(mul_overflow(Int(1), Int(0), &r), false); + QCOMPARE(r, Int(0)); + QCOMPARE(mul_overflow(Int(0), Int(1), &r), false); + QCOMPARE(r, Int(0)); + QCOMPARE(mul_overflow(max, Int(0), &r), false); + QCOMPARE(r, Int(0)); + QCOMPARE(mul_overflow(Int(0), max, &r), false); + QCOMPARE(r, Int(0)); + + QCOMPARE(mul_overflow(Int(1), Int(1), &r), false); + QCOMPARE(r, Int(1)); + QCOMPARE(mul_overflow(Int(1), max, &r), false); + QCOMPARE(r, max); + QCOMPARE(mul_overflow(max, Int(1), &r), false); + QCOMPARE(r, max); + + // almost max + QCOMPARE(mul_overflow(middle, middle, &r), false); + QCOMPARE(r, Int(max - 2 * middle)); + QCOMPARE(mul_overflow(Int(middle + 1), middle, &r), false); + QCOMPARE(r, Int(middle << (sizeof(Int) * CHAR_BIT / 2))); + QCOMPARE(mul_overflow(middle, Int(middle + 1), &r), false); + QCOMPARE(r, Int(middle << (sizeof(Int) * CHAR_BIT / 2))); + QCOMPARE(mul_overflow(Int(max / 2), Int(2), &r), false); + QCOMPARE(r, Int(max & ~Int(1))); + QCOMPARE(mul_overflow(Int(max / 4), Int(4), &r), false); + QCOMPARE(r, Int(max & ~Int(3))); + + // overflows + QCOMPARE(mul_overflow(max, Int(2), &r), true); + QCOMPARE(mul_overflow(Int(max / 2), Int(3), &r), true); + QCOMPARE(mul_overflow(Int(middle + 1), Int(middle + 1), &r), true); +#endif +} + +template <typename Int, bool enabled = sizeof(Int) <= sizeof(void*)> struct MulOverflowDispatch; +template <typename Int> struct MulOverflowDispatch<Int, true> +{ + void operator()() { mulOverflow_template<Int>(); } +}; +template <typename Int> struct MulOverflowDispatch<Int, false> +{ + void operator()() { QSKIP("This type is too big for this architecture"); } +}; + +void tst_QNumeric::mulOverflow() +{ + QFETCH(int, size); + if (size == 8) + MulOverflowDispatch<quint8>()(); + if (size == 16) + MulOverflowDispatch<quint16>()(); + if (size == 32) + MulOverflowDispatch<quint32>()(); + if (size == 48) + MulOverflowDispatch<ulong>()(); // not really 48-bit + if (size == 64) + MulOverflowDispatch<quint64>()(); +} + QTEST_APPLESS_MAIN(tst_QNumeric) #include "tst_qnumeric.moc"