Home > Software design >  Clang 14 and 15 apparently optimizing away code that compiles as expected under Clang 13, ICC, GCC,
Clang 14 and 15 apparently optimizing away code that compiles as expected under Clang 13, ICC, GCC,

Time:09-26

I have the following sample code:

inline float successor(float f, bool const check)
{
    const unsigned long int mask = 0x7f800000U;
    unsigned long int i = *(unsigned long int*)&f;

    if (check)
    {
        if ((i & mask) == mask)
            return f;
    }

    i  ;

    return *(float*)&i;
}

float next1(float a)
{
    return successor(a, true);
}

float next2(float a)
{
    return successor(a, false);
}

Under x86-64 clang 13.0.1, the code compiles as expected.

Under x86-64 clang 14.0.0 or 15, the output is merely a ret op for next1(float) and next2(float).

Compiler options: -march=x86-64-v3 -O3

The code and output are here: Godbolt.

The successor(float,bool) function is not a no-op.

As a note, the output is as expected under GCC, ICC, and MSVCC. Am I missing something here?

CodePudding user response:

*(unsigned long int*)&f is an immediate aliasing violation. f is a float. You are not allowed to access it through a pointer to unsigned long int. (And the same applies to *(float*)&i.)

So the code has undefined behavior and Clang likes to assume that code with undefined behavior is unreachable.

Compile with -fno-strict-aliasing to force Clang to not consider aliasing violations as undefined behavior that cannot happen or better do not rely on undefined behavior. Instead use either std::bit_cast (since C 20) or std::memcpy to create a copy of f with the new type but same object representation. That way your program will be valid standard C and not rely on the -fno-strict-aliasing compiler extension.

(And if you use std::memcpy add a static_assert to verify that unsigned long int and float have the same size. That is not true on all platforms and also not on all common platforms. std::bit_cast has the test built-in.)

  • Related