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?
>Solution :
*(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.)