Defekt optymalizacji kompilatora x64
Podczas dalszego kodowania mojego silnika natrafiłem na bardzo dziwny błąd kompilatora Visual Studio. Zaczynając od początku – mam oto taki kod:
enum { // Rozmiary pól MAX_BITS_INDEX = sizeof(Size_t) * 4, MAX_BITS_MAGIC = sizeof(Size_t) * 4, // Maksymalne wartości pól (Przesunięcie 1 w dwóch operacjach, ponieważ inaczej kompilator nie daje rady) MAX_INDEX = ((1 << MAX_BITS_INDEX / 2) << MAX_BITS_INDEX / 2) - 1, MAX_MAGIC = ((1 << MAX_BITS_MAGIC / 2) << MAX_BITS_MAGIC / 2) - 1 }; /* ... */ // handle = 0x0000000400000006; - przykladowo // Size_t ma rozmiar wskaźnika (jest zależny od platformy) Size_t value = handle & MAX_MAGIC; |
Otóż problem z tym kodem jest taki, że gdy zostanie skompilowany na platformę x64, zostaje wygenerowany następujący kod w assemblerze:
79: { 80: Size_t value = handle & MAX_MAGIC; 000000013FC96F94 mov rax,qword ptr [handle] 000000013FC96F9C mov qword ptr [value],rax 81: if(value == *it) return true; // Dalszy kod |
Jak widać problem polega na braku kluczowej instrukcji and, której zadaniem jest odcięcie najstarszych 32-bitów. Dla porównania kod assemblera wygenerowanego dla platformy x86 jest następujący:
79: { 80: Size_t value = handle & MAX_MAGIC; 00DC9DDC mov eax,dword ptr [handle] 00DC9DDF and eax,0FFFFh 00DC9DE4 mov dword ptr [value],eax 81: if(value == *it) return true; // Dalszy kod |
Tutaj instrukcja ta występuje.
Błąd ten jednak nie występuje gdy samemu sprecyzuję stałą, tj.:
Size_t value = handle & 0xffffffff; |
W tym przypadku zostanie wygenerowany poprawny kod.
Wniosek jest taki, że kompilator widząc operację and zmiennej ze stałą typu enum (który przechowuje wartości 32-bitowe), traktuje tą zmienną jako typ 32-bitowy. W tym przypadku stwierdza, że instrukcja and nie ma sensu, bo wartość MAX_MAGIC zawiera maksymalną wartość (0xffffffff). Według mnie jest to ewidentny błąd i nie powinno takie coś wystąpić.
// Edit :)
Problemem oczywiście jest to, że enum jest tak naprawdę typem int, więc się dokładnie tak zachowuje.
Paweł Dziepak:
Obawiam się jednak, że takie zachowanie z punktu widzenia C++03 jest poprawne. W przypadku operatora & stosuje się “usual arithmetic conversions” które nie przewidują istnienia typu long long (zresztą jak cały C++03), z tego co mi wiadomo sizeof(long) na MS Visual C++ x64 wynosi 4 więc z punktu widzenia standardu jest wszystko ok. Z tym, że mogli to rozwiązać w bardziej normalny i także zgodny z C++03 sposób.
28 June 2009, 12:20 amNetrix:
Dzięki za komentarz :)
Napisałem do Microsoft Connect i tam wytłumaczyli mi to trochę inaczej. Otóż wartość enum jest typu int, czyli liczba ze znakiem. W moim przypadku ma ona wartość -1 (0xffffffff), więc przy konwersji do 64-bitowego Size_t, który jest unsigned zostaje rozszerzona do 64 bitów, a jej wartością dalej pozostaje -1. Dla kompilatora jest to brak operacji, więc ją usuwa.
30 June 2009, 12:55 pmPaweł Dziepak:
No tak tutaj stanard pozostawia UD i implementacja może to zrobić tak jak chce. Pozostaje czekać na C++0x który taz na zawsze rozwiąże problemy z enumami.
1 July 2009, 10:56 am