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.

3 Comments

  1. 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.

  2. Netrix:

    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.

  3. Paweł 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.

Leave a comment