diff --git a/src/corelib/global/qnumeric.h b/src/corelib/global/qnumeric.h index bb2859031a6..31e0fa69164 100644 --- a/src/corelib/global/qnumeric.h +++ b/src/corelib/global/qnumeric.h @@ -1,4 +1,5 @@ // Copyright (C) 2021 The Qt Company Ltd. +// Copyright (C) 2025 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Giuseppe D'Angelo // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QNUMERIC_H @@ -15,7 +16,7 @@ #include #include -#include +#include // min() and max() may be #defined by windows.h if that is included before, but we need them // for std::numeric_limits below. You should not use the min() and max() macros, so we just #undef. @@ -37,20 +38,13 @@ # endif # if defined(Q_PROCESSOR_X86_64) || defined(Q_PROCESSOR_ARM_64) # define Q_INTRINSIC_MUL_OVERFLOW64 -# define Q_UMULH(v1, v2) __umulh(v1, v2); -# define Q_SMULH(v1, v2) __mulh(v1, v2); +# define Q_UMULH(v1, v2) __umulh(v1, v2) +# define Q_SMULH(v1, v2) __mulh(v1, v2) # pragma intrinsic(__umulh) # pragma intrinsic(__mulh) # endif #endif -# if defined(Q_OS_INTEGRITY) && defined(Q_PROCESSOR_ARM_64) -# include -# define Q_INTRINSIC_MUL_OVERFLOW64 -# define Q_UMULH(v1, v2) __MULUH64(v1, v2); -# define Q_SMULH(v1, v2) __MULSH64(v1, v2); -#endif - QT_BEGIN_NAMESPACE // To match std::is{inf,nan,finite} functions: @@ -94,41 +88,212 @@ Q_CORE_EXPORT quint64 qFloatDistance(double a, double b); // size_t. Implementations for 8- and 16-bit types will work but may not be as // efficient. Implementations for 64-bit may be missing on 32-bit platforms. -#if (Q_CC_GNU >= 500 || __has_builtin(__builtin_add_overflow)) \ - && !(QT_POINTER_SIZE == 4 && defined(Q_CC_CLANG)) -// GCC 5 and Clang 3.8 have builtins to detect overflows -// 32 bit Clang has the builtins but tries to link a library which hasn't -#define Q_INTRINSIC_MUL_OVERFLOW64 +// All the GCC and Clang versions we support have constexpr +// builtins for overflowing arithmetic. +#if defined(Q_CC_GNU_ONLY) \ + || defined(Q_CC_CLANG_ONLY) \ + || __has_builtin(__builtin_add_overflow) +# define Q_NUMERIC_USE_GCC_OVERFLOW_BUILTINS +// On 32-bit, Clang < 14 will fail to link if multiplying 64-bit +// quantities (emits an unresolved call to __mulodi4), so we can't use +// the builtin in that case. +# if !(QT_POINTER_SIZE == 4 && defined(Q_CC_CLANG_ONLY) && Q_CC_CLANG_ONLY < 1400) +# define Q_INTRINSIC_MUL_OVERFLOW64 +# endif +#endif -template inline -typename std::enable_if_t || std::is_signed_v, bool> -qAddOverflow(T v1, T v2, T *r) -{ return __builtin_add_overflow(v1, v2, r); } - -template inline -typename std::enable_if_t || std::is_signed_v, bool> -qSubOverflow(T v1, T v2, T *r) -{ return __builtin_sub_overflow(v1, v2, r); } - -template inline -typename std::enable_if_t || std::is_signed_v, bool> -qMulOverflow(T v1, T v2, T *r) -{ return __builtin_mul_overflow(v1, v2, r); } - -#else -// Generic implementations - -template inline typename std::enable_if_t, bool> -qAddOverflow(T v1, T v2, T *r) +namespace QtPrivate { +// Generic versions of (some) overflowing math functions, private API. +template +constexpr inline +typename std::enable_if_t, bool> +qAddOverflowGeneric(T v1, T v2, T *r) { // unsigned additions are well-defined *r = v1 + v2; return v1 > T(v1 + v2); } -template inline typename std::enable_if_t, bool> +// Wide multiplication. +// It has been isolated in its own function so that it can be tested. +// Note that this implementation requires a T that doesn't undergo +// promotions. +template +constexpr inline +typename std::enable_if_t, bool> +qMulOverflowWideMultiplication(T v1, T v2, T *r) +{ + // This is a glorified long/school-grade multiplication, + // that considers each input of N bits as two halves of N/2 bits: + // + // v1 = 2^(N/2) * v1_hi + v1_lo + // v2 = 2^(N/2) * v2_hi + v2_lo + // + // Therefore, v1*v2 = 2^N * v1_hi * v2_hi + + // 2^(N/2) * v1_hi * v2_lo + + // 2^(N/2) * v1_lo * v2_hi + + // * v1_lo * v2_lo + // + // Using the N bits of precision we have we can perform the hi*lo + // multiplications safely; that is never going to overflow. + // + // Then we can sum together these partial results: + // + // [ v1_hi | v1_lo ] * + // [ v2_hi | v2_lo ] = + // ------------------- + // [ v1_lo * v2_lo ] + + // [ v1_hi * v2_lo ] + // shifted because it's * 2^(N/2) + // [ v2_hi * v1_lo ] + // shifted because it's * 2^(N/2) + // [ v1_hi * v2_hi ] = // shifted because it's * 2^N + // ------------------------------- + // [ high ][ low ] // exact result (in 2^(2N) bits) + // + // ... except that this way we'll need to bring some carries, so + // we'll do a slightly smarter sum. + // + // We need high for detecting overflows, even if we are not returning it. + + // Get multiplication by zero out of the way + if (v1 == 0 || v2 == 0) { + *r = T(0); + return false; + } + + // Extract the absolute values as unsigned + // (will fix the sign later) + using U = std::make_unsigned_t; + const U v1_abs = (v1 >= 0) ? U(v1) : (U(0) - U(v1)); + const U v2_abs = (v2 >= 0) ? U(v2) : (U(0) - U(v2)); + + // Masks for N/2 bits + constexpr std::size_t half_width = (sizeof(U) * 8) / 2; + const U half_mask = ~U(0) >> half_width; + + // Split in low and half quantities + const U v1_lo = v1_abs & half_mask; + const U v1_hi = v1_abs >> half_width; + const U v2_lo = v2_abs & half_mask; + const U v2_hi = v2_abs >> half_width; + + // Cross-product; this will never overflow + const U lo_lo = v1_lo * v2_lo; + const U lo_hi = v1_lo * v2_hi; + const U hi_lo = v1_hi * v2_lo; + const U hi_hi = v1_hi * v2_hi; + + // We could sum directly the cross-products, but then we'd have to + // keep track of carries. This avoids it. + const U tmp = (lo_lo >> half_width) + (hi_lo & half_mask) + lo_hi; + U result_hi = (hi_lo >> half_width) + (tmp >> half_width) + hi_hi; + U result_lo = (tmp << half_width) | (lo_lo & half_mask); + + if constexpr (std::is_unsigned_v) { + // If the source was unsigned, we're done; a non-zero high + // signals overflow. + *r = result_lo; + return result_hi != U(0); + } else { + // We need to set the correct sign back, and check for overflow. + const bool isNegative = (v1 < T(0)) != (v2 < T(0)); + if (isNegative) { + // Result is negative; calculate two's complement of the + // [high, low] pair, by inverting the bits and adding 1, + // which is equivalent to negating it in unsigned + // arithmetic. + // This operation should be done on the pair as a whole, + // but we have the individual components, so start by + // calculating two's complement of low: + result_lo = U(0) - result_lo; + + // If result_lo is 0, it means that the addition of 1 into + // it has overflown, so now we have a carry to add into the + // inverted high: + result_hi = ~result_hi; + if (result_lo == 0) + result_hi += U(1); + } + + *r = result_lo; + // Overflow has happened if result_hi is not a sign extension + // of the sign bit of result_lo. Note the usage of T, not U. + return result_hi != U(*r >> std::numeric_limits::digits); + } +} + +template +constexpr inline bool HasLargerInt = false; +template +constexpr inline bool HasLargerInt::Unsigned>> = true; + +template +constexpr inline +typename std::enable_if_t<(std::is_unsigned_v || std::is_signed_v), bool> +qMulOverflowGeneric(T v1, T v2, T *r) +{ + // This function is a generic fallback for qMulOverflow, + // called either by constant or non-constant evaluation, + // if the compiler does not have builtins or intrinsics itself. + // + // (For instance, this is never going to be called on GCC or recent + // Clang, as their builtins will be used in all cases.) + // + // If a compiler does have builtins, please amend qMulOverflow + // directly. + + if constexpr (HasLargerInt) { + // Use the next biggest type if available + using LargerInt = QIntegerForSize; + using Larger = typename std::conditional_t, + typename LargerInt::Signed, typename LargerInt::Unsigned>; + Larger lr = Larger(v1) * Larger(v2); + *r = T(lr); + return lr > (std::numeric_limits::max)() || lr < (std::numeric_limits::min)(); + } else { + // Otherwise fall back to a wide multiplication + return qMulOverflowWideMultiplication(v1, v2, r); + } +} +} // namespace QtPrivate + +template +constexpr inline +typename std::enable_if_t, bool> qAddOverflow(T v1, T v2, T *r) { +#if defined(Q_NUMERIC_USE_GCC_OVERFLOW_BUILTINS) + return __builtin_add_overflow(v1, v2, r); +#else + if (q20::is_constant_evaluated()) + return QtPrivate::qAddOverflowGeneric(v1, v2, r); +# if defined(Q_HAVE_ADDCARRY) + // We can use intrinsics for the unsigned operations with MSVC + if constexpr (std::is_same_v) { + return _addcarry_u32(0, v1, v2, r); + } else if constexpr (std::is_same_v) { +# if defined(Q_PROCESSOR_X86_64) + return _addcarry_u64(0, v1, v2, reinterpret_cast(r)); +# else + uint low, high; + uchar carry = _addcarry_u32(0, unsigned(v1), unsigned(v2), &low); + carry = _addcarry_u32(carry, v1 >> 32, v2 >> 32, &high); + *r = (quint64(high) << 32) | low; + return carry; +# endif // defined(Q_PROCESSOR_X86_64) + } +# endif // defined(Q_HAVE_ADDCARRY) + return QtPrivate::qAddOverflowGeneric(v1, v2, r); +#endif // defined(Q_NUMERIC_USE_GCC_OVERFLOW_BUILTINS) +} + +template +constexpr inline +typename std::enable_if_t, bool> +qAddOverflow(T v1, T v2, T *r) +{ +#if defined(Q_NUMERIC_USE_GCC_OVERFLOW_BUILTINS) + return __builtin_add_overflow(v1, v2, r); +#else // Here's how we calculate the overflow: // 1) unsigned addition is well-defined, so we can always execute it // 2) conversion from unsigned back to signed is implementation- @@ -141,34 +306,37 @@ qAddOverflow(T v1, T v2, T *r) using U = typename std::make_unsigned_t; *r = T(U(v1) + U(v2)); - // If int is two's complement, assume all integer types are too. - if (std::is_same_v) { - // Two's complement equivalent (generates slightly shorter code): - // x ^ y is negative if x and y have different signs - // x & y is negative if x and y are negative - // (x ^ z) & (y ^ z) is negative if x and z have different signs - // AND y and z have different signs - return ((v1 ^ *r) & (v2 ^ *r)) < 0; - } - - bool s1 = (v1 < 0); - bool s2 = (v2 < 0); - bool sr = (*r < 0); - return s1 != sr && s2 != sr; - // also: return s1 == s2 && s1 != sr; + // Two's complement equivalent (generates slightly shorter code): + // x ^ y is negative if x and y have different signs + // x & y is negative if x and y are negative + // (x ^ z) & (y ^ z) is negative if x and z have different signs + // AND y and z have different signs + return ((v1 ^ *r) & (v2 ^ *r)) < 0; +#endif // defined(Q_NUMERIC_USE_GCC_OVERFLOW_BUILTINS) } -template inline typename std::enable_if_t, bool> +template +constexpr inline +typename std::enable_if_t, bool> qSubOverflow(T v1, T v2, T *r) { +#if defined(Q_NUMERIC_USE_GCC_OVERFLOW_BUILTINS) + return __builtin_sub_overflow(v1, v2, r); +#else // unsigned subtractions are well-defined *r = v1 - v2; return v1 < v2; +#endif // defined(Q_NUMERIC_USE_GCC_OVERFLOW_BUILTINS) } -template inline typename std::enable_if_t, bool> +template +constexpr inline +typename std::enable_if_t, bool> qSubOverflow(T v1, T v2, T *r) { +#if defined(Q_NUMERIC_USE_GCC_OVERFLOW_BUILTINS) + return __builtin_sub_overflow(v1, v2, r); +#else // See above for explanation. This is the same with some signs reversed. // We can't use qAddOverflow(v1, -v2, r) because it would be UB if // v2 == std::numeric_limits::min(). @@ -176,111 +344,80 @@ qSubOverflow(T v1, T v2, T *r) using U = typename std::make_unsigned_t; *r = T(U(v1) - U(v2)); - if (std::is_same_v) - return ((v1 ^ *r) & (~v2 ^ *r)) < 0; - - bool s1 = (v1 < 0); - bool s2 = !(v2 < 0); - bool sr = (*r < 0); - return s1 != sr && s2 != sr; - // also: return s1 == s2 && s1 != sr; + return ((v1 ^ *r) & (~v2 ^ *r)) < 0; +#endif // defined(Q_NUMERIC_USE_GCC_OVERFLOW_BUILTINS) } -template inline +template +constexpr inline typename std::enable_if_t || std::is_signed_v, bool> qMulOverflow(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. - using LargerInt = QIntegerForSize; - using Larger = typename std::conditional_t, - typename LargerInt::Signed, typename LargerInt::Unsigned>; - Larger lr = Larger(v1) * Larger(v2); - *r = T(lr); - return lr > (std::numeric_limits::max)() || lr < (std::numeric_limits::min)(); -} +#if defined(Q_NUMERIC_USE_GCC_OVERFLOW_BUILTINS) +# if defined(Q_INTRINSIC_MUL_OVERFLOW64) + return __builtin_mul_overflow(v1, v2, r); +# else + if constexpr (sizeof(T) <= 4) + return __builtin_mul_overflow(v1, v2, r); + else + return QtPrivate::qMulOverflowGeneric(v1, v2, r); +# endif +#else + if (q20::is_constant_evaluated()) + return QtPrivate::qMulOverflowGeneric(v1, v2, r); # if defined(Q_INTRINSIC_MUL_OVERFLOW64) -template <> inline bool qMulOverflow(quint64 v1, quint64 v2, quint64 *r) -{ - *r = v1 * v2; - return Q_UMULH(v1, v2); -} -template <> inline bool qMulOverflow(qint64 v1, qint64 v2, qint64 *r) -{ - // This is slightly more complex than the unsigned case above: the sign bit - // of 'low' must be replicated as the entire 'high', so the only valid - // values for 'high' are 0 and -1. Use unsigned multiply since it's the same - // as signed for the low bits and use a signed right shift to verify that - // 'high' is nothing but sign bits that match the sign of 'low'. + if constexpr (std::is_unsigned_v && (sizeof(T) == sizeof(quint64))) { + // T is 64 bit; either unsigned long long, + // or unsigned long on LP64 platforms. + *r = v1 * v2; + return T(Q_UMULH(v1, v2)); + } else if constexpr (std::is_signed_v && (sizeof(T) == sizeof(qint64))) { + // This is slightly more complex than the unsigned case above: the sign bit + // of 'low' must be replicated as the entire 'high', so the only valid + // values for 'high' are 0 and -1. Use unsigned multiply since it's the same + // as signed for the low bits and use a signed right shift to verify that + // 'high' is nothing but sign bits that match the sign of 'low'. - qint64 high = Q_SMULH(v1, v2); - *r = qint64(quint64(v1) * quint64(v2)); - return (*r >> 63) != high; + qint64 high = Q_SMULH(v1, v2); + *r = qint64(quint64(v1) * quint64(v2)); + return (*r >> 63) != high; + } +# endif // defined(Q_INTRINSIC_MUL_OVERFLOW64) + + return QtPrivate::qMulOverflowGeneric(v1, v2, r); +#endif // defined(Q_NUMERIC_USE_GCC_OVERFLOW_BUILTINS) } -# if defined(Q_OS_INTEGRITY) && defined(Q_PROCESSOR_ARM_64) -template <> inline bool qMulOverflow(uint64_t v1, uint64_t v2, uint64_t *r) -{ - return qMulOverflow(v1,v2,reinterpret_cast(r)); -} - -template <> inline bool qMulOverflow(int64_t v1, int64_t v2, int64_t *r) -{ - return qMulOverflow(v1,v2,reinterpret_cast(r)); -} -# endif // OS_INTEGRITY ARM64 -# endif // Q_INTRINSIC_MUL_OVERFLOW64 - -# if defined(Q_HAVE_ADDCARRY) && defined(Q_PROCESSOR_X86) -// We can use intrinsics for the unsigned operations with MSVC -template <> inline bool qAddOverflow(unsigned v1, unsigned v2, unsigned *r) -{ return _addcarry_u32(0, v1, v2, r); } - -// 32-bit qMulOverflow is fine with the generic code above - -template <> inline bool qAddOverflow(quint64 v1, quint64 v2, quint64 *r) -{ -# if defined(Q_PROCESSOR_X86_64) - return _addcarry_u64(0, v1, v2, reinterpret_cast(r)); -# else - uint low, high; - uchar carry = _addcarry_u32(0, unsigned(v1), unsigned(v2), &low); - carry = _addcarry_u32(carry, v1 >> 32, v2 >> 32, &high); - *r = (quint64(high) << 32) | low; - return carry; -# endif // !x86-64 -} -# endif // HAVE ADDCARRY #undef Q_HAVE_ADDCARRY -#endif // !GCC +#undef Q_NUMERIC_USE_GCC_OVERFLOW_BUILTINS // Implementations for addition, subtraction or multiplication by a // compile-time constant. For addition and subtraction, we simply call the code // that detects overflow at runtime. For multiplication, we compare to the // maximum possible values before multiplying to ensure no overflow happens. -template bool qAddOverflow(T v1, std::integral_constant, T *r) +template constexpr bool qAddOverflow(T v1, std::integral_constant, T *r) { return qAddOverflow(v1, V2, r); } -template bool qAddOverflow(T v1, T *r) +template constexpr bool qAddOverflow(T v1, T *r) { return qAddOverflow(v1, std::integral_constant{}, r); } -template bool qSubOverflow(T v1, std::integral_constant, T *r) +template constexpr bool qSubOverflow(T v1, std::integral_constant, T *r) { return qSubOverflow(v1, V2, r); } -template bool qSubOverflow(T v1, T *r) +template constexpr bool qSubOverflow(T v1, T *r) { return qSubOverflow(v1, std::integral_constant{}, r); } -template bool qMulOverflow(T v1, std::integral_constant, T *r) +template constexpr bool qMulOverflow(T v1, std::integral_constant, T *r) { // Runtime detection for anything smaller than or equal to a register // width, as most architectures' multiplication instructions actually @@ -327,7 +464,7 @@ template bool qMulOverflow(T v1, std::integral_constant bool qMulOverflow(T v1, T *r) +template constexpr bool qMulOverflow(T v1, T *r) { if constexpr (V2 == 2) return qAddOverflow(v1, v1, r); diff --git a/tests/auto/corelib/global/qnumeric/tst_qnumeric.cpp b/tests/auto/corelib/global/qnumeric/tst_qnumeric.cpp index ec7896ca4fc..0a49118e637 100644 --- a/tests/auto/corelib/global/qnumeric/tst_qnumeric.cpp +++ b/tests/auto/corelib/global/qnumeric/tst_qnumeric.cpp @@ -13,6 +13,10 @@ #include +#if QT_POINTER_SIZE == 8 || defined(Q_INTRINSIC_MUL_OVERFLOW64) +# define QT_HAS_128_BIT_MULTIPLICATION +#endif + namespace { template struct Fuzzy {}; /* Data taken from qglobal.h's implementation of qFuzzyCompare: @@ -82,6 +86,7 @@ private slots: void mulOverflow_data(); void mulOverflow(); void signedOverflow(); + void genericWideMultiplication(); }; // Floating-point tests: @@ -717,7 +722,7 @@ void tst_QNumeric::mulOverflow() if (size == -32) MulOverflowDispatch()(); if (size == -64) { -#if QT_POINTER_SIZE == 8 || defined(Q_INTRINSIC_MUL_OVERFLOW64) +#ifdef QT_HAS_128_BIT_MULTIPLICATION MulOverflowDispatch()(); #else QFAIL("128-bit multiplication not supported on this platform"); @@ -755,6 +760,292 @@ void tst_QNumeric::signedOverflow() QCOMPARE(qMulOverflow(maxInt, maxInt, &r), true); } +template +static bool genericWideMultiplication_impl_impl(T lhs, T rhs) +{ + T expectedResult; + bool expectedOverflow = qMulOverflow(lhs, + rhs, + &expectedResult); + + T actualResult; + bool actualOverflow = QtPrivate::qMulOverflowWideMultiplication(lhs, rhs, &actualResult); + + if (actualResult != expectedResult || actualOverflow != expectedOverflow) { + qDebug() << "LHS" << lhs << "RHS" << rhs; + qDebug() << "Actual" << actualResult + << "Expected" << expectedResult; + qDebug() << "Actual overflow" << actualOverflow + << "Expected overflow" << expectedOverflow; + return false; + } + + return true; +} + + +template +static bool genericWideMultiplication_impl() +{ + using U = std::make_unsigned_t; + constexpr int SIZE_IN_BITS = sizeof(T) * CHAR_BIT; + for (int i = 0; i < SIZE_IN_BITS; ++i) { + for (int j = 0; j < SIZE_IN_BITS; ++j) { + for (int offset = -5; offset <= 5; ++offset) { + const U lhs_tmp = (U(1) << i) + offset; + const U rhs_tmp = (U(1) << j) + offset; + + const T lhs = T(lhs_tmp); + const T rhs = T(rhs_tmp); + + if (!genericWideMultiplication_impl_impl(lhs, rhs)) + return false; + } + } + } + + for (int i = -256; i <= 256; ++i) { + for (int j = -256; j <= 256; ++j) { + if (!genericWideMultiplication_impl_impl(T(i), T(j))) + return false; + } + } + + constexpr T minimal = std::numeric_limits::min(); + constexpr T maximal = std::numeric_limits::max(); + for (int i = -5; i <= 5; ++i) { + for (T j = minimal; j < minimal + 10; ++j) { + if (!genericWideMultiplication_impl_impl(T(i), T(j))) + return false; + } + for (T j = maximal - 10; j < maximal; ++j) { + if (!genericWideMultiplication_impl_impl(T(i), T(j))) + return false; + } + if (!genericWideMultiplication_impl_impl(T(i), maximal)) + return false; + } + + return true; +} + +void tst_QNumeric::genericWideMultiplication() +{ + QVERIFY(genericWideMultiplication_impl()); + QVERIFY(genericWideMultiplication_impl()); + +#if defined(QT_HAS_128_BIT_MULTIPLICATION) + QVERIFY(genericWideMultiplication_impl()); + QVERIFY(genericWideMultiplication_impl()); + + QVERIFY(genericWideMultiplication_impl()); + QVERIFY(genericWideMultiplication_impl()); +#endif +} + + +#if !defined(Q_CC_GHS) +// Compile-time tests for overflow math +namespace OverflowTest { +template +constexpr bool add(T a, T b, bool expOverflow, T exp = {}) +{ + T result{}; + const bool overflowed = qAddOverflow(a, b, &result); + if (overflowed != expOverflow) + return false; + if (overflowed) + return true; + return result == exp; +} + +template +constexpr bool sub(T a, T b, bool expOverflow, T exp = {}) +{ + T result{}; + const bool overflowed = qSubOverflow(a, b, &result); + if (overflowed != expOverflow) + return false; + if (overflowed) + return true; + return result == exp; +} + +template +constexpr bool mul(T a, T b, bool expOverflow, T exp = {}) +{ + T result{}; + const bool overflowed = qMulOverflow(a, b, &result); + if (overflowed != expOverflow) + return false; + if (overflowed) + return true; + return result == exp; +} + +// Addition +#define ADD_OVERFLOW_COMMON_TEST(type, maximal) \ + static_assert(add(type(0), type(0), false, type(0))); \ + static_assert(add(type(0), type(1), false, type(1))); \ + static_assert(add(type(1), type(1), false, type(2))); \ + static_assert(add(type(maximal), type(0), false, type(maximal))); \ + static_assert(add(type(0), type(maximal), false, type(maximal))); \ + static_assert(add(type(maximal), type(1), true)); \ + static_assert(add(type(maximal), type(2), true)); \ + static_assert(add(type(maximal), type(maximal), true)); \ + static_assert(add(type(type(maximal) / type(2)), \ + type(type(maximal) / type(2)), \ + false, \ + type(type(maximal) - type(1)))); \ + +#define ADD_OVERFLOW_UNSIGNED_TYPE_TEST(type, maximal) \ + ADD_OVERFLOW_COMMON_TEST(type, maximal) \ + +#define ADD_OVERFLOW_SIGNED_TYPE_TEST(type, minimal, maximal) \ + ADD_OVERFLOW_COMMON_TEST(type, maximal) \ + static_assert(add(type(0), type(-1), false, type(-1))); \ + static_assert(add(type(-1), type(-1), false, type(-2))); \ + static_assert(add(type(-1), type(-1), false, type(-2))); \ + static_assert(add(type(minimal), type(0), false, type(minimal))); \ + static_assert(add(type(0), type(minimal), false, type(minimal))); \ + static_assert(add(type(minimal), type(-1), true)); \ + static_assert(add(type(minimal), type(minimal), true)); \ + static_assert(add(type(maximal), type(minimal), false, type(-1))); \ + static_assert(add(type(minimal), type(maximal), false, type(-1))); \ + static_assert(add(type(maximal), type(-1), false, type(type(maximal) - type(1)))); \ + +// Subtraction +#define SUB_OVERFLOW_COMMON_TEST(type, minimal, maximal) \ + static_assert(sub(type(minimal), type(0), false, type(minimal))); \ + static_assert(sub(type(minimal), type(1), true)); \ + static_assert(sub(type(minimal), type(2), true)); \ + static_assert(sub(type(minimal), type(maximal), true)); \ + static_assert(sub(type(0), type(0), false, type(0))); \ + static_assert(sub(type(1), type(0), false, type(1))); \ + static_assert(sub(type(3), type(0), false, type(3))); \ + static_assert(sub(type(9), type(5), false, type(4))); \ + static_assert(sub(type(9), type(9), false, type(0))); \ + static_assert(sub(type(maximal), type(0), false, type(maximal))); \ + static_assert(sub(type(maximal), type(maximal), false, type(0))); \ + +#define SUB_OVERFLOW_UNSIGNED_TYPE_TEST(type, maximal) \ + SUB_OVERFLOW_COMMON_TEST(type, 0, maximal) \ + static_assert(sub(type(1), type(2), true)); \ + static_assert(sub(type(0), type(maximal), true)); \ + static_assert(sub(type(1), type(maximal), true)); \ + +#define SUB_OVERFLOW_SIGNED_TYPE_TEST(type, minimal, maximal) \ + SUB_OVERFLOW_COMMON_TEST(type, minimal, maximal) \ + static_assert(sub(type(1), type(2), false, type(-1))); \ + static_assert(sub(type(0), type(maximal), false, type(type(minimal) + type(1)))); \ + static_assert(sub(type(1), type(maximal), false, type(type(minimal) + type(2)))); \ + static_assert(sub(type(0), type(minimal), true)); \ + static_assert(sub(type(maximal), type(-1), true)); \ + static_assert(sub(type(maximal), type(minimal), true)); \ + +// Multiplication +#define MUL_OVERFLOW_COMMON_TEST(type, minimal, maximal) \ + static_assert(mul(type(0), type(0), false, type(0))); \ + static_assert(mul(type(1), type(0), false, type(0))); \ + static_assert(mul(type(0), type(1), false, type(0))); \ + static_assert(mul(type(1), type(1), false, type(1))); \ + static_assert(mul(type(1), type(10), false, type(10))); \ + static_assert(mul(type(5), type(2), false, type(10))); \ + static_assert(mul(type(minimal), type(0), false, type(0))); \ + static_assert(mul(type(maximal), type(0), false, type(0))); \ + static_assert(mul(type(0), type(minimal), false, type(0))); \ + static_assert(mul(type(0), type(maximal), false, type(0))); \ + static_assert(mul(type(minimal), type(1), false, type(minimal))); \ + static_assert(mul(type(maximal), type(1), false, type(maximal))); \ + static_assert(mul(type(1), type(minimal), false, type(minimal))); \ + static_assert(mul(type(1), type(maximal), false, type(maximal))); \ + static_assert(mul(type(maximal), type(2), true)); \ + static_assert(mul(type(maximal/2), type(2), false, type(type(maximal) - type(1)))); \ + static_assert(mul(type(maximal/4), type(4), false, type(type(maximal) - type(3)))); \ + +#define MUL_OVERFLOW_UNSIGNED_TYPE_TEST(type, maximal) \ + MUL_OVERFLOW_COMMON_TEST(type, 0, maximal) \ + +#define MUL_OVERFLOW_SIGNED_TYPE_TEST(type, minimal, maximal) \ + MUL_OVERFLOW_COMMON_TEST(type, minimal, maximal) \ + static_assert(mul(type(maximal), type(-1), false, type(-maximal))); \ + static_assert(mul(type(minimal), type(-1), true)); \ + static_assert(mul(type(minimal), type(2), true)); \ + static_assert(mul(type(minimal/2), type(3), true)); \ + + +#define UNSIGNED_TYPE_TEST(type, maximal) \ + ADD_OVERFLOW_UNSIGNED_TYPE_TEST(type, maximal) \ + SUB_OVERFLOW_UNSIGNED_TYPE_TEST(type, maximal) \ + MUL_OVERFLOW_UNSIGNED_TYPE_TEST(type, maximal) \ + +#define SIGNED_TYPE_TEST(type, minimal, maximal) \ + ADD_OVERFLOW_SIGNED_TYPE_TEST(type, minimal, maximal) \ + SUB_OVERFLOW_SIGNED_TYPE_TEST(type, minimal, maximal) \ + MUL_OVERFLOW_SIGNED_TYPE_TEST(type, minimal, maximal) \ + +using schar = signed char; +using uchar = unsigned char; +using ushort = unsigned short; +using uint = unsigned int; +using ulong = unsigned long; + +#if CHAR_MAX == 127 // char is signed +SIGNED_TYPE_TEST(char, CHAR_MIN, CHAR_MAX) +#else +UNSIGNED_TYPE_TEST(char, CHAR_MAX) +#endif + +SIGNED_TYPE_TEST(schar, SCHAR_MIN, SCHAR_MAX) +UNSIGNED_TYPE_TEST(uchar, UCHAR_MAX) + +SIGNED_TYPE_TEST(short, SHRT_MIN, SHRT_MAX) +UNSIGNED_TYPE_TEST(ushort, USHRT_MAX) + +SIGNED_TYPE_TEST(int, INT_MIN, INT_MAX) +UNSIGNED_TYPE_TEST(uint, UINT_MAX) + +#if LONG_MAX == 2147483647 || defined(QT_HAS_128_BIT_MULTIPLICATION) +SIGNED_TYPE_TEST(long, LONG_MIN, LONG_MAX) +UNSIGNED_TYPE_TEST(ulong, ULONG_MAX) +#else +ADD_OVERFLOW_SIGNED_TYPE_TEST(long, LONG_MIN, LONG_MAX) +SUB_OVERFLOW_SIGNED_TYPE_TEST(long, LONG_MIN, LONG_MAX) +ADD_OVERFLOW_UNSIGNED_TYPE_TEST(ulong, ULONG_MAX) +SUB_OVERFLOW_UNSIGNED_TYPE_TEST(ulong, ULONG_MAX) +#endif + +#if defined(QT_HAS_128_BIT_MULTIPLICATION) +// Compiling this causes an ICE in MSVC, so skipping it +#if !defined(Q_CC_MSVC) || Q_CC_MSVC > 1942 +SIGNED_TYPE_TEST(qlonglong, LLONG_MIN, LLONG_MAX) +UNSIGNED_TYPE_TEST(qulonglong, ULLONG_MAX) +#endif +#else +ADD_OVERFLOW_SIGNED_TYPE_TEST(qlonglong, LLONG_MIN, LLONG_MAX) +SUB_OVERFLOW_SIGNED_TYPE_TEST(qlonglong, LLONG_MIN, LLONG_MAX) +ADD_OVERFLOW_UNSIGNED_TYPE_TEST(qlonglong, ULLONG_MAX) +SUB_OVERFLOW_UNSIGNED_TYPE_TEST(qlonglong, ULLONG_MAX) +#endif + +#undef ADD_OVERFLOW_COMMON_TEST +#undef ADD_OVERFLOW_UNSIGNED_TYPE_TEST +#undef ADD_OVERFLOW_SIGNED_TYPE_TEST + +#undef SUB_OVERFLOW_COMMON_TEST +#undef SUB_OVERFLOW_UNSIGNED_TYPE_TEST +#undef SUB_OVERFLOW_SIGNED_TYPE_TEST + +#undef MUL_OVERFLOW_COMMON_TEST +#undef MUL_OVERFLOW_UNSIGNED_TYPE_TEST +#undef MUL_OVERFLOW_SIGNED_TYPE_TEST + +#undef UNSIGNED_TYPE_TEST +#undef SIGNED_TYPE_TEST +} // namespace OverflowTest + +#endif // !defined(Q_CC_GHS) + namespace SaturateCastTest { template static constexpr T max = std::numeric_limits::max();