2017-05-23 18 views
10

Rozważmy następujący program:Strict aliasing, -ffast-matematyka i 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; 
} 

Jeśli mogę skompilować z Apple Clang 7.0.2 i bez -ffast-math, uzyskać oczekiwane wyjście 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 

Jednak po aktualizacji do 8.1.0 (przepraszam, nie mam pojęcia, która aktualna wersja Clang to odpowiada - Apple już nie publikuje tej informacji), -ffast-math wydaje się łamać to:

$ 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 

Podejrzewam, że dzieje się tak z powodu surowych reguł aliasingu lub czegoś w tym stylu. Czy ktoś może wyjaśnić to zachowanie?

Edycja: Zapomniałem wspomnieć, że jeśli robisz nans = { std::nanf(nullptr), ..., to działa dobrze.

Wygląda również na godbolt wydaje się, że zachowanie zmieniło się pomiędzy Clang 3.8.1 a Clang 3.9 - ten ostatni usuwa instrukcję cmpordps. Wydaje się, że GCC 7.1 go zostawia.

Odpowiedz

12

To nie jest ściśle określony problem z aliasingiem. Jeśli przeczytasz the documentation of -ffast-math, zobaczysz swój problem:

Włącz tryb szybkiego matematyki. Definiuje to makro preprocesora __FAST_MATH__ i pozwala kompilatorowi na agresywne, potencjalnie stratne założenia dotyczące matematyki zmiennoprzecinkowej. Należą do nich:

  • [...]
  • argumenty do zmiennoprzecinkowych operacji nie są równe NaN i Inf i
  • [...]

-ffast-math pozwala kompilator zakłada, że ​​liczba zmiennopozycyjna nigdy nie jest NaN (ponieważ ustawia opcję -ffinite-math-only). Od dzyń próbuje dopasować opcje GCC, możemy czytać trochę od GCC's option documentation aby lepiej zrozumieć to, co robi -ffinite-math-only:

Pozwól optymalizacje dla arytmetyki zmiennoprzecinkowej, które zakładają, że argumenty i wyniki nie są Koncepcja nieliczby lub + -Infs.

Opcja ta nigdy nie powinna być włączana przez żadną opcję -O, ponieważ może to skutkować niepoprawnym wyjściem dla programów zależnych od dokładnej implementacji zasad IEEE lub norm/specyfikacji ISO.

Więc jeśli Twój kod musi współpracować z NaN, nie można używać -ffast-math lub -ffinite-math-only. W przeciwnym razie ryzykujesz, że optymalizator zniszczy twój kod, tak jak tu widzisz.

+0

Huh Nie wiedziałem tego ... Dzięki! – Timmmm