Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Optimizations are killing my integer overflow checks in clang 6

I have a fixed-point implementation for some financial application. It's basically an integer wrapped in a class that is based on the number of decimals given Ntreated as a decimal number. The class is paranoid and checks for overflows, but when I ran my tests in release mode, and they failed, and finally I created this minimal example that demonstrates the problem:

#include <iostream>
#include <sstream>

template <typename T, typename U>
typename std::enable_if<std::is_convertible<U, std::string>::value, T>::type 
FromString(U&& str)
{
    std::stringstream ss;
    ss << str;
    T ret;
    ss >> ret;
    return ret;
}

int main()
{
    int NewAccu=32;
    int N=10;

    using T = int64_t;

    T l = 10;
    T r = FromString<T>("1" + std::string(NewAccu - N, '0'));
    if (l == 0 || r == 0) {
        return 0;
    }
    T res = l * r;
    std::cout << l << std::endl;
    std::cout << r << std::endl;
    std::cout << res << std::endl;
    std::cout << (res / l) << std::endl;
    std::cout << std::endl;
    if ((res / l) != r) {
        throw std::runtime_error(
                   "FixedPoint Multiplication Overflow while upscaling [:" + std::to_string(l) + ", " + std::to_string(r) + "]");
    }

    return 0;
}

This happens with Clang 6, my version is:

$ clang++ --version
clang version 6.0.0-1ubuntu2 (tags/RELEASE_600/final)
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin

It's funny because it's an impressive optimization, but this ruins my application and prevents me from detecting overflows. I was able to reproduce this problem in g++ here. It doesn't throw an exception there.

Notice that the exception is thrown in debug mode, but it's not in release mode.

like image 639
The Quantum Physicist Avatar asked Dec 11 '22 05:12

The Quantum Physicist


1 Answers

As @Basile already stated, signed integer overflow is an undefined behavior, so the compiler can handle it in any way - even optimizing it away to gain a performance advantage. So detecting integer overflow after its occurence is way too late. Instead, you should predict integer overflow just before it occurs.

Here is my implementation of overflow prediction of integer multiplication:

#include <limits>

template <typename T>
bool predict_mul_overflow(T x, T y)
{
    static_assert(std::numeric_limits<T>::is_integer, "predict_mul_overflow expects integral types");

    if constexpr (std::numeric_limits<T>::is_bounded)
    {
        return ((x != T{0}) && ((std::numeric_limits<T>::max() / x) < y));
    }
    else
    {
        return false;
    }
}

The function returns true if the integer multiplication x * y is predicted to overflow.

Note that while unsigned overflow is well-defined in terms of modular arithmetic, signed overflow is an undefined behavior. Nevertheless, the presented function works for signed and unsigned T types as well.

like image 99
plasmacel Avatar answered Feb 04 '23 03:02

plasmacel