Строгий псевдоним, математика и SSE

Рассмотрим следующую программу:

#include <iostream>
#include <cmath>
#include <cstring>
#include <xmmintrin.h>

using namespace std;

int main()
{
    // 4 float32s.
    __m128 nans;
    // Set them all to 0xffffffff which should be NaN.
    memset(&nans, 0xff, 4*4);

    // cmpord should return a mask of 0xffffffff for any non-NaNs, and 0x00000000 for NaNs.
    __m128 mask = _mm_cmpord_ps(nans, nans);
    // AND the mask with nans to zero any of the nans. The result should be 0x00000000 for every component.
    __m128 z = _mm_and_ps(mask, nans);

    cout << z[0] << " " << z[1] << " " << z[2] << " " << z[3] << endl;

    return 0;
}

Если я скомпилирую с Apple Clang 7.0.2 с и без -ffast-mathЯ получаю ожидаемый результат 0 0 0 0:

$ clang --version
Apple LLVM version 7.0.2 (clang-700.1.81)
Target: x86_64-apple-darwin14.5.0
Thread model: posix

$ clang test.cpp -o test
$ ./test
0 0 0 0 

$ clang test.cpp -ffast-math -o test
$ ./test 
0 0 0 0

Однако после обновления до 8.1.0 (извините, я понятия не имею, какой фактической версии Clang это соответствует - Apple больше не публикует эту информацию), -ffast-math кажется, сломать это:

$ clang --version
Apple LLVM version 8.1.0 (clang-802.0.42)
Target: x86_64-apple-darwin16.6.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin

$ clang test.cpp -o test
$ ./test
0 0 0 0 

$ clang test.cpp -ffast-math -o test
$ ./test 
nan nan nan nan

Я подозреваю, что это из-за строгих правил наложения имен или что-то в этом роде. Кто-нибудь может объяснить это поведение?

Редактировать: я забыл упомянуть, что если вы делаете nans = { std::nanf(nullptr), ... это работает отлично.

Также, глядя на Godbolt, кажется, что поведение изменилось между Clang 3.8.1 и Clang 3.9 - последний удаляет cmpordps инструкция. GCC 7.1, кажется, оставляет его в.

1 ответ

Решение

Это не строгая проблема с алиасами. Если вы читаете документацию -ffast-math, вы увидите свою проблему:

Включить режим быстрой математики. Это определяет __FAST_MATH__ макрос препроцессора, и позволяет компилятору делать агрессивные предположения с потенциальными потерями относительно математики с плавающей точкой. Они включают:

  • [...]
  • операнды операций с плавающей запятой не равны NaN а также Inf, а также
  • [...]

-ffast-math позволяет компилятору предполагать, что число с плавающей точкой никогда не бывает NaN (потому что это устанавливает -ffinite-math-only опция). Поскольку clang пытается сопоставить параметры gcc, мы можем немного прочесть документацию по параметрам GCC, чтобы лучше понять, что -ffinite-math-only делает:

Разрешить оптимизацию для арифметики с плавающей точкой, которая предполагает, что аргументы и результаты не являются NaN или +-Infs.

Эта опция никогда не должна включаться никаким параметром -O, так как это может привести к неправильному выводу для программ, которые зависят от точной реализации правил / спецификаций IEEE или ISO.

Так что если ваш код должен работать с NaN, вы не можете использовать -ffast-math или же -ffinite-math-only, В противном случае вы рискуете, что оптимизатор разрушит ваш код, как вы видите здесь.

Другие вопросы по тегам