W x86-64 Tour Intel Podręczniki , czytam
Być może najbardziej zaskakującym faktem jest to, że instrukcja taka jak
MOV EAX, EBX
automatycznie zeruje górne 32 bityRAX
rejestru.
Dokumentacja firmy Intel (3.4.1.1 Rejestry ogólnego przeznaczenia w trybie 64-bitowym w ręcznej architekturze podstawowej) cytowana w tym samym źródle mówi nam:
- 64-bitowe operandy generują wynik 64-bitowy w docelowym rejestrze ogólnego przeznaczenia.
- 32-bitowe operandy generują wynik 32-bitowy, rozszerzony przez zero do wyniku 64-bitowego w docelowym rejestrze ogólnego przeznaczenia.
- 8-bitowe i 16-bitowe operandy generują wynik 8-bitowy lub 16-bitowy. Górne 56 bitów lub 48 bitów (odpowiednio) docelowego rejestru ogólnego przeznaczenia nie jest modyfikowanych przez operację. Jeśli wynik operacji 8-bitowej lub 16-bitowej ma na celu obliczenie adresu 64-bitowego, jawnie wpisz znak-rozszerz rejestr do pełnych 64-bitów.
W zestawie x86-32 i x86-64 instrukcje 16-bitowe, takie jak
mov ax, bx
nie pokazuj tego rodzaju „dziwnego” zachowania, gdy górne słowo eax jest zerowane.
A zatem: jaki jest powód, dla którego wprowadzono to zachowanie? Na pierwszy rzut oka wydaje się to nielogiczne (ale powodem może być to, że jestem przyzwyczajony do dziwactw asemblera x86-32).
r32
operandem docelowym zerują wysoki 32, zamiast scalać. Na przykład, niektórzy monterzy zastąpipmovmskb r64, xmm
zpmovmskb r32, xmm
, oszczędzając REX, ponieważ wersja 64bit przeznaczenia zachowuje się identycznie. Mimo że sekcja Operacja podręcznika zawiera osobno wszystkie 6 kombinacji źródła 32/64-bitowego dest i 64/128 / 256b, niejawne rozszerzenie zerowe postaci r32 powiela jawne rozszerzenie zerowe postaci r64. Jestem ciekawy implementacji HW ...xor eax,eax
lubxor r8d,r8d
jest najlepszym sposobem na wyzerowanie RAX lub R8 (zapisanie prefiksu REX dla RAX, a 64-bitowy XOR nie jest nawet obsługiwany specjalnie w Silvermont). Powiązane: Jak dokładnie działają częściowe rejestry w Haswell / Skylake? Pisanie AL wydaje się mieć fałszywą zależność od RAX, a AH jest niespójneOdpowiedzi:
Nie jestem AMD ani nie mówię w ich imieniu, ale zrobiłbym to w ten sam sposób. Ponieważ wyzerowanie górnej połowy nie tworzy zależności od poprzedniej wartości, procesor musiałby czekać. Przemianowanie rejestrów mechanizm będzie zasadniczo zostać pokonany, jeśli nie została wykonana w ten sposób.
W ten sposób możesz pisać szybki kod przy użyciu wartości 32-bitowych w trybie 64-bitowym bez konieczności jawnego zrywania przez cały czas zależności. Bez tego zachowania każda pojedyncza 32-bitowa instrukcja w trybie 64-bitowym musiałaby czekać na coś, co wydarzyło się wcześniej, mimo że ta wysoka część prawie nigdy nie byłaby używana. (Tworzenie wersji
int
64-bitowej zmarnuje miejsce w pamięci podręcznej i przepustowość pamięci; x86-64 najefektywniej obsługuje 32- i 64-bitowe rozmiary operandów )Zachowanie dla rozmiarów operandów 8 i 16-bitowych jest dziwne. Szaleństwo zależności jest jednym z powodów, dla których obecnie unika się 16-bitowych instrukcji. x86-64 odziedziczył to po 8086 dla 8-bitów i 386 dla 16-bitów i zdecydował, że 8 i 16-bitowe rejestry działają tak samo w trybie 64-bitowym, jak w trybie 32-bitowym.
Zobacz także Dlaczego GCC nie używa rejestrów częściowych? praktyczne szczegóły, jak zapisy do 8 i 16-bitowych rejestrów częściowych (i późniejsze odczyty pełnego rejestru) są obsługiwane przez rzeczywiste procesory.
źródło
Po prostu oszczędza miejsce w instrukcjach i zestawie instrukcji. Możesz przenieść małe wartości natychmiastowe do rejestru 64-bitowego, korzystając z istniejących (32-bitowych) instrukcji.
Oszczędza to również konieczności kodowania 8-bajtowych wartości
MOV RAX, 42
, kiedyMOV EAX, 42
można je ponownie wykorzystać.Ta optymalizacja nie jest tak ważna dla operacji 8- i 16-bitowych (ponieważ są one mniejsze), a zmiana tamtejszych reguł również zepsułaby stary kod.
źródło
XOR EAX, EAX
ponieważXOR RAX, RAX
potrzebowałby prefiksu REX.[rsi + edx]
nie jest to dozwolone). Oczywiście unikanie fałszywych zależności / opóźnień w rejestrach częściowych (druga odpowiedź) jest kolejnym ważnym powodem.Bez zera rozszerzającego się do 64 bitów oznaczałoby to, że instrukcja odczytująca z
rax
miałaby 2 zależności dla swojegorax
operandu (instrukcja, która zapisuje doeax
i instrukcja, która zapisujerax
przed nią), to znaczy, że 1) ROB musiałby mieć wpisy dla wiele zależności dla pojedynczego operandu, co oznacza, że ROB wymagałby więcej logiki i tranzystorów oraz zajmowałby więcej miejsca, a wykonanie byłoby wolniejsze, czekając na niepotrzebną drugą zależność, której wykonanie może zająć wieki; lub alternatywnie 2), co, jak przypuszczam, dzieje się z instrukcjami 16-bitowymi, etap alokacji prawdopodobnie zatrzymuje się (tj. jeśli RAT ma aktywną alokację doax
zapisu ieax
pojawia się odczyt, zatrzymuje się do momentuax
wycofania zapisu).Jedyną korzyścią wynikającą z niezerowania rozszerzania jest zapewnienie uwzględnienia bitów wyższego rzędu
rax
, na przykład, jeśli pierwotnie zawiera on 0xffffffffffffffff, wynikiem będzie 0xffffffff00000007, ale nie ma bardzo niewielkiego powodu, aby ISA zapewniał tę gwarancję takim kosztem, i bardziej prawdopodobne jest, że korzyść z zerowego rozszerzenia byłaby faktycznie wymagana więcej, więc oszczędza dodatkową linię kodumov rax, 0
. Gwarantując, że zawsze będzie on rozszerzony do 64 bitów, kompilatory mogą pracować z tym aksjomatem, gdy są wmov rdx, rax
,rax
muszą tylko czekać na jego pojedynczą zależność, co oznacza, że może rozpocząć wykonywanie szybciej i wycofać się, zwalniając jednostki wykonawcze. Co więcej, pozwala również na bardziej wydajne idiomy zerowe, takie jakxor eax, eax
zero,rax
bez konieczności stosowania bajtu REX.źródło
cmovbe
jest 2 uops, alecmovb
jest 1). Ale żaden procesor, który dokonuje jakiejkolwiek częściowej zmiany nazwy rejestru, nie robi tego tak, jak sugerujesz. Zamiast tego wstawiają scalające uop, jeśli nazwa częściowego rejestru jest zmieniana niezależnie od pełnego rejestru (tj. Jest „brudny”). Zobacz Dlaczego GCC nie używa rejestrów częściowych? i jak dokładnie działają częściowe rejestry w Haswell / Skylake? Pisanie AL wydaje się mieć fałszywą zależność od RAX, a AH jest niespójneThis gives a delay of 5 - 6 clocks. The reason is that a temporary register has been assigned to AL to make it independent of AH. The execution unit has to wait until the write to AL has retired before it is possible to combine the value from AL with the value of the rest of EAX
Nie mogę znaleźć przykładu „scalania uop”, który zostałby użyty do rozwiązania tego problemu, tak samo jak w przypadku częściowego stoiska z flagamimov al, [mem]
jest to ładunek mikro-fused + ALU- merge, tylko zmiana nazwy AH, a uop łączący AH nadal występuje samodzielnie. Mechanizmy scalania częściowych flag w tych procesorach są różne, np. Core2 / Nehalem nadal po prostu blokują dla częściowych flag, w przeciwieństwie do częściowej reg.