Dlaczego MIPS używa R0 jako „zero”, skoro tylko XOR dwa rejestry dają 0?

10

Myślę, że szukam odpowiedzi na pytanie o ciekawostki. Próbuję zrozumieć, dlaczego architektura MIPS używa jawnej wartości „zero” w rejestrze, skoro można osiągnąć to samo, po prostu XOR'ując dowolny rejestr przeciwko sobie. Można powiedzieć, że operacja jest już dla ciebie wykonana; jednak nie mogę sobie wyobrazić sytuacji, w której użyłbyś wielu wartości „zerowych”. Czytam oryginalne prace Hennesseya, a to po prostu przypisuje zero bez faktycznego uzasadnienia.

Czy istnieje logiczny powód, dla którego binarne przypisanie zerowe jest zakodowane na stałe?

aktualizacja: W 8k pliku wykonywalnego z xc32-gcc dla rdzenia MIPS w PIC32MZ mam jedno wystąpienie „zero”.

add     t3,t1,zero

faktyczna odpowiedź: Nagrodę przyznałem osobie, która posiadała informacje o MIPS i kodach warunków. Odpowiedź leży w architekturze MIPS dla warunków. Chociaż początkowo nie chciałem na to poświęcać czasu, sprawdziłem architekturę dla Opensparc , MIPS-V i OpenPOWER (ten dokument był wewnętrzny) i oto podsumowanie wyników. Rejestr R0 niezbędny do porównania na oddziałach ze względu na architekturę rurociągu.

  • liczba całkowita porównuje z zero i rozgałęzieniem (bgez, bgtz, blez, bltz)
  • liczba całkowita porównaj dwa rejestry i gałąź (beq, bne)
  • liczba całkowita porównaj dwa rejestry i pułapkę (teq, tge, tlt, tne)
  • liczba całkowita porównaj rejestr i natychmiastowa oraz pułapka (teqi, tgei, tlti, tnei)

Po prostu sprowadza się to do wyglądu sprzętu we wdrożeniu. W podręczniku MIPS-V na stronie 68 znajduje się nieograniczony cytat:

Gałęzie warunkowe zostały zaprojektowane w taki sposób, aby zawierały operacje porównania arytmetycznego między dwoma rejestrami (jak również w PA-RISC i Xtensa ISA), zamiast używania kodów warunków (x86, ARM, SPARC, PowerPC), lub aby porównywać tylko jeden rejestr z zerem ( Alpha, MIPS) lub dwa rejestry tylko dla równości (MIPS). Ten projekt był motywowany obserwacją, że połączona instrukcja porównania i rozgałęzienia ts do zwykłego potoku, pozwala uniknąć dodatkowego stanu kodu warunkowego lub użycia rejestru tymczasowego oraz zmniejsza statyczny rozmiar kodu i dynamiczny przebieg pobierania instrukcji. Inną kwestią jest to, że porównania z zerem wymagają nietrywialnego opóźnienia obwodu (szczególnie po przejściu na logikę statyczną w zaawansowanych procesach), a zatem są prawie tak drogie, jak w porównaniu z wielkością arytmetyczną. Kolejną zaletą skondensowanej instrukcji porównania i rozgałęzienia jest to, że gałęzie są obserwowane wcześniej w strumieniu instrukcji frontonu, a zatem można je przewidzieć wcześniej. Być może zaletą jest projekt z kodami warunków w przypadku, gdy wiele gałęzi można pobrać na podstawie tych samych kodów warunków, ale uważamy, że ten przypadek jest stosunkowo rzadki.

Dokument MIPS-V nie trafia w autora cytowanej sekcji. Dziękuję wszystkim za poświęcony czas i uwagę.

b degnan
źródło
6
Często chcesz użyć rejestru o wartości 0 w niektórych operacjach jako wartości źródłowej. Byłoby trochę narzutu, aby wyzerować rejestr przed tymi operacjami, więc poprawa wydajności, jeśli możesz po prostu użyć dostarczonego zera zamiast tworzyć go samodzielnie, gdy jest to potrzebne. Przykłady obejmują dodanie flagi carry.
JimmyB
3
W architekturze AVR gcc dba o inicjalizację r1 do zera podczas uruchamiania i nigdy nie dotyka tej wartości ponownie, używając r1 jako źródła wszędzie tam, gdzie nie można użyć natychmiastowego 0. Dedykowany rejestr zerowy jest „emulowany” w oprogramowaniu przez kompilator ze względu na wydajność. (Większość AVR-ów ma 32 rejestry, więc odłożenie jednego (dwóch) faktycznie nie kosztuje dużo w stosunku do możliwych korzyści w zakresie wydajności i rozmiaru kodu.)
JimmyB
1
Nie wiem o MIPS, ale może być szybsze przeniesienie r0 do innego rejestru niż XORing tego rejestru, aby go wyczyścić.
JimmyB
Więc nie zgadzasz się z tym, że zero jest tak często, że warte jest miejsca w pliku rejestru? Prawdopodobnie masz rację, ponieważ to prawda, że ​​jest to kontrowersyjne i wiele ISA decyduje się nie rezerwować rejestru zerowego. Podobnie jak inne kontrowersyjne funkcje w tym czasie, takie jak okna rejestrów, sloty oddziałów, przewidywanie instrukcji z „dawnych czasów” ... jeśli chcesz zaprojektować ISA, nie musisz ich używać, jeśli zdecydujesz się tego nie robić.
user3528438,
2
Interesujące może być przeczytanie jednego ze starych artykułów RISC Berkeley, RISC I: Zestaw instrukcji zredukowanych VLSI Computer . Pokazuje, w jaki sposób użycie przewodowego rejestru zerowego, R0, pozwala na wdrożenie szeregu instrukcji VAX i trybów adresowania w pojedynczej instrukcji RISC.
Mark Plotnick

Odpowiedzi:

14

Rejestr zerowy na procesorach RISC jest użyteczny z dwóch powodów:

Jest to użyteczna stała

W zależności od ograniczeń ISA, nie możesz używać literału w niektórych instrukcjach kodowania, ale możesz być pewien, że możesz go użyć, r0aby uzyskać 0.

Można go użyć do syntezy innych instrukcji

To chyba najważniejszy punkt. Jako projektant ISA możesz zamienić rejestr ogólnego przeznaczenia na rejestr zerowy, aby móc zsyntetyzować inne przydatne instrukcje. Syntezowanie instrukcji jest dobre, ponieważ mając mniej rzeczywistych instrukcji, potrzebujesz mniej bitów, aby zakodować operację w kodzie op, co zwalnia miejsce w przestrzeni kodowania instrukcji. Możesz użyć tej przestrzeni do np. Większych przesunięć adresu i / lub literałów.

Semantyka rejestru zerowego jest jak /dev/zerow systemach * nix: wszystko do niego zapisywane jest odrzucane, a ty zawsze odczytujesz 0.

Zobaczmy kilka przykładów, w jaki sposób możemy tworzyć pseudo-instrukcje za pomocą r0rejestru zerowego:

; ### Hypothetical CPU ###

; Assembler with syntax:
; op rd, rm, rn 
; => rd: destination, rm: 1st operand, rn: 2nd operand
; literal as #lit

; On an CPU architecture with a status register (which contains arithmetic status
; flags), `sub` can be used, with r0 as destination to discard result.
cmp rn, rm     ; => sub r0, rn, rm

; `add` instruction can be used as a `mov` instruction:
mov rd, rm     ; => add rd, rm, r0
mov rd, #lit   ; => add rd, r0, #lit

; Negate:
neg rd, rm     ; => sub rd, r0, rm

; On CPU without status flags,
nop            ; => add r0, r0, r0

; RISC-V's `jal` instruction -- Jump and Link: Jump to PC-relative instruction,
; save return address into rd; we can synthesize a `jmp` instruction out of it.
jmp dest       ; => jal r0, dest

; You can even load from an absolute (direct) address, for a usually small range
; of addresses by using a literal offset as an address.
ld rd, addr    ; => ld rd, [r0, #addr]

Przypadek MIPS

Przyjrzałem się bliżej zestawowi instrukcji MIPS. Istnieje garść pseudo-instrukcji, które wykorzystują $zero; wykorzystywane są głównie do oddziałów. Oto kilka przykładów tego, co znalazłem:

move $rt, $rs          => add $rt, $rs, $zero

not $rt, $rs           => nor $rt, $rs, $zero

b Label                => beq $zero, $zero, Label ; a small relative branch

bgt $rs, $rt, Label    => slt $at, $rt, $rs
                          bne $at, $zero, Label

blt $rs, $rt, Label    => slt $at, $rs, $rt
                          bne $at, $zero, Label

bge $rs, $rt, Label    => slt $at, $rs, $rt
                          beq $at, $zero, Label

ble $rs, $rt, Label    => slt $at, $rt, $rs
                          beq $at, $zero, Label

Jeśli chodzi o to, dlaczego znalazłeś tylko jedną instancję $zerorejestru w swoim demontażu, być może to twój deasembler jest wystarczająco inteligentny, aby przekształcić znane sekwencje instrukcji w ich równoważne pseudo-instrukcje.

Czy rejestr zerowy jest naprawdę przydatny?

Najwyraźniej ARM uważa, że ​​posiadanie rejestru zerowego jest na tyle przydatne, że w jego (nieco) nowym rdzeniu ARMv8-A, który implementuje AArch64, jest teraz rejestr zerowy w trybie 64-bitowym; wcześniej nie było rejestru zerowego. (Rejestr jest jednak trochę wyjątkowy, w niektórych kontekstach kodowania jest rejestrem zerowym, w innych natomiast oznacza wskaźnik stosu )

Jarhmander
źródło
Nie sądzę, że MIPS używa flag, prawda? Rejestr zerowy dodaje możliwość bezwarunkowego odczytu / zapisu niektórych adresów bez względu na zawartość rejestrów procesora i pomaga ułatwić operację typu „mov natychmiast”, ale inne ruchy można wykonać logicznie lub samodzielnie zródłem .
supercat
1
Rzeczywiście, nie ma rejestru, które posiadają arytmetycznych flagi, zamiast istnieją trzy instrukcje, które pomagają Emulate wspólnych skoków warunkowych ( slt, slti, sltu).
Jarhmander
Patrząc na zestaw instrukcji MIPS i biorąc pod uwagę, że z tego, co rozumiem, każda instrukcja zostanie pobrana do czasu wykonania poprzedniej instrukcji, zastanawiam się, czy trudno byłoby mieć kod operacyjny, który nie odnosi się bezpośrednio do niczego, ale zamiast tego mówi, że jeśli zostanie wykonana instrukcja trybu natychmiastowego, a następna pobrana instrukcja ma ten wzorzec bitów, górne 16 bitów argumentu zostanie pobranych z instrukcji wstępnie pobranej? To byłoby 32-bitowe operacje w trybie natychmiastowym, które byłyby obsługiwane
dwuskładową
... ładowanie operandu, a następnie trzeci cykl, aby go faktycznie użyć.
supercat
7

Większość implementacji ARM / POWER / SPARC ma ukryty rejestr RAZ

Można by pomyśleć, że ARM32, SPARC itp. Nie mają rejestru 0, ale w rzeczywistości mają! Na poziomie mikro-architektury większość inżynierów projektujących procesory dodaje rejestr 0, który może być niewidoczny dla oprogramowania (rejestr zerowy ARM jest niewidoczny) i wykorzystuje ten rejestr zerowy do usprawnienia dekodowania instrukcji.

Rozważ typowy współczesny projekt ARM32, który ma niewidoczny programowo rejestr, powiedzmy R16 podłączony do 0. Rozważ obciążenie ARM32, wiele przypadków instrukcji ładowania ARM32 mieści się w jednej z tych form (zignoruj ​​indeksowanie przed postem, aby uprościć dyskusję ) ...

LDR ra, [rb] // NOTE:The ! is optional and represents address writeback.
LDR ra, [rb, rc](!)
LDR ra, [rb, #k](!)

Wewnątrz procesora dekoduje się to do generała

ldr.uop ra, rb, rx, rc, #c // Internal decoded instruction format.

przed wejściem w fazę wydania, w której odczytywane są rejestry. Zauważ, że rx reprezentuje rejestr, aby zapisać zaktualizowany adres. Oto kilka przykładów dekodowania:

LDR R0, [R1]      ==> ldr.uop R0, R1, R16, R16, #0 // Writeback to NULL. 
LDR R0, [R1, R2]! ==> ldr.uop R0, R1, R1, R2,   #0 // Writeback to R1.
LDR R0, [R1, #2]  ==> ldr.uop R0, R1, R16, R16, #2 // Writeback to NULL.

Na poziomie obwodu wszystkie trzy obciążenia są w rzeczywistości tą samą instrukcją wewnętrzną, a łatwym sposobem na uzyskanie tego rodzaju ortogonalności jest utworzenie rejestru masy R16. Ponieważ R16 jest zawsze uziemiony, instrukcje te naturalnie dekodują poprawnie bez dodatkowej logiki. Odwzorowanie klasy instrukcji na pojedynczy format wewnętrzny bardzo pomaga w implementacjach superskalarnych, ponieważ zmniejsza złożoność logiki.

Innym powodem jest usprawniony sposób wyrzucania zapisów. Instrukcje można wyłączyć, po prostu ustawiając rejestr docelowy i flagi na R16. Nie ma potrzeby tworzenia żadnego innego sygnału sterującego, aby wyłączyć zapisywanie zwrotne itp.

Większość implementacji procesorów, niezależnie od architektury, kończy się na wczesnym etapie tworzenia modelu rejestru RAZ. Potok MIPS zasadniczo rozpoczyna się w punkcie, który w innych architekturach miałby kilka etapów.

MIPS dokonał właściwego wyboru

Dlatego też rejestr „odczyt zerowy” jest prawie obowiązkowy w każdej nowoczesnej implementacji procesora, a MIPS, dzięki czemu jest widoczny dla oprogramowania, jest zdecydowanie zaletą, biorąc pod uwagę, w jaki sposób usprawnia on wewnętrzną logikę dekodowania. Projektanci procesorów MIPS nie muszą dodawać dodatkowego rejestru RAZ, ponieważ 0 USD już jest na ziemi. Ponieważ RAZ jest dostępny dla asemblera, wiele instrukcji psuedo jest dostępnych dla MIPS i można to traktować jako wypychanie części logiki dekodowania do samego asemblera zamiast tworzenia dedykowanych formatów dla każdego typu instrukcji w celu ukrycia rejestru RAZ przed oprogramowaniem jak w przypadku innych architektur. Rejestr RAZ jest dobrym pomysłem i dlatego ARMv8 go skopiował.

Gdyby ARM32 miał rejestr 0 USD, logika dekodowania stałaby się prostsza, a architektura byłaby znacznie lepsza pod względem prędkości, powierzchni i mocy. Na przykład z trzech przedstawionych powyżej wersji LDR potrzebne byłyby tylko 2 formaty. Podobnie nie ma potrzeby rezerwowania logiki dekodowania dla instrukcji MOV i MVN. Również CMP / CMN / TST / TEQ stałyby się zbędne. Nie byłoby też potrzeby rozróżniania krótkiego (MUL) i długiego mnożenia (UMULL / SMULL), ponieważ krótkie mnożenie można uznać za długie mnożenie z wysokim rejestrem ustawionym na 0 USD itp.

Ponieważ MIPS został początkowo zaprojektowany przez mały zespół, prostota projektowania była ważna, dlatego też 0 USD zostało wyraźnie wybrane w duchu RISC. ARM32 zachowuje wiele tradycyjnych funkcji CISC na poziomie architektonicznym.

Revanth Kamaraj
źródło
1
Nie wszystkie procesory ARM32 działają w opisany sposób. Niektóre mają niższą wydajność w przypadku bardziej złożonych instrukcji ładowania i / lub zapisu do rejestru. Dlatego nie wszyscy mogą dekodować dokładnie w ten sam sposób.
Peter Cordes
6

Disclamer: Naprawdę nie znam asemblera MIPS, ale rejestr o wartości 0 nie jest unikalny dla tej architektury i myślę, że jest on używany w taki sam sposób, jak w innych architekturach RISC, które znam.

XOR rejestrowanie rejestru w celu uzyskania 0 kosztuje jedną instrukcję, podczas gdy korzystanie ze wstępnie zdefiniowanego rejestru o wartości 0 nie będzie.

Na przykład mov RX, RYinstrukcja jest często implementowana jako add RX, RY, R0. Bez rejestru o wartości 0 będziesz musiał za xor RZ, RZkażdym razem, gdy chcesz użyć mov.

Innym przykładem jest cmpinstrukcja i jej warianty (takie jak „porównaj i skacz”, „porównaj i przenieś” itp.), W których cmp RX, R0stosuje się testowanie liczb ujemnych.

Dmitrij Grigoriew
źródło
1
Czy byłyby jakieś problemy z wdrożeniem MOV Rx,Ryjako AND Rx,Ry,Ry?
supercat
3
@ superuper Nie będziesz mógł kodować mov RX, Immlub mov RX, mem[RY]jeśli twój zestaw instrukcji obsługuje tylko jedną bezpośrednią wartość i pojedynczy dostęp do pamięci dla instrukcji.
Dmitrij Grigoryev
Nie wiem, jakie tryby adresowania ma MIPS. Wiem, że ARM ma tryby [Rx + Ry << skala] i [Rx + disp] i chociaż możliwość korzystania z tego drugiego dla niektórych adresów bezwzględnych może być przydatna w niektórych przypadkach, ogólnie nie jest niezbędna. Prosty tryb [Rx] można emulować za pomocą [Rx + disp] przy zerowym przesunięciu. Z czego korzysta MIPS?
supercat
movjest złym przykładem; możesz zaimplementować go z natychmiastowym 0 zamiast rejestru zerowego. np ori dst, src, 0. Ale tak, potrzebujesz kodu operacji dla mov-instant, aby się zarejestrować, jeśli nie masz addiu $dst, $zero, 1234, na przykład luidla niższych 16 bitów zamiast dla górnych 16. I nie możesz użyć norani subzbudować jednego operandu not / neg .
Peter Cordes
@ supercat: jeśli nadal się zastanawiasz: klasyczny system MIPS ma tylko jeden tryb adresowania: register + disp16. Nowoczesne MIPS dodało inne kody dla trybów adresowania z dwoma rejestrami dla obciążeń / sklepów FP, przyspieszając indeksowanie macierzy. (Ale wciąż nie w przypadku ładowania / przechowywania liczb całkowitych, być może dlatego, że może to wymagać więcej portów odczytu w pliku rejestru liczb całkowitych dla 2 rejestrów adresów + rejestru danych dla sklepu. Zobacz Używanie rejestru jako przesunięcia )
Peter Cordes
3

Wiązanie kilku prowadzi do ziemi na końcu banku rejestru jest tanie (tańsze niż uczynienie z niego pełnego rejestru).

Wykonanie rzeczywistego Xora wymaga trochę mocy i czasu, aby przełączyć bramę, a następnie zapisać ją w rejestrze, po co płacić ten koszt, gdy istniejąca wartość 0 może być łatwo dostępna.

Nowoczesne procesory mają również (ukryty) rejestr wartości 0, którego mogą używać w wyniku xor eax eaxinstrukcji poprzez zmianę nazwy rejestru.

maniak zapadkowy
źródło
6
Rzeczywistym kosztem R0nie jest uziemienie kilku przewodów, ale fakt, że musisz zarezerwować dla niego kod w każdej instrukcji dotyczącej rejestrów.
Dmitrij Grigoryev
Xor to czerwony śledź. zerowanie xor jest dobre tylko na x86, gdzie procesory rozpoznają idiom i unikają zależności od danych wejściowych. Jak zauważyłeś, rodzina Sandybridge nawet nie uruchamia się dla niego, po prostu obsługuje go na etapie zmiany nazwy rejestru. ( Jaki jest najlepszy sposób ustawienia rejestru na zero w zespole x86: xor, mov lub and? ). Ale w MIPS XORing rejestru miałby fałszywą zależność; Reguły porządkowania zależności pamięci (HW odpowiednik C ++ std::memory_order_consume) wymagają propagacji zależności przez XOR.
Peter Cordes
Jeśli nie masz rejestru zerowego, możesz dołączyć kod operacji, aby przenieść natychmiast do rejestru. Jak, luiale nie przesunięty w lewo o 16. Więc nadal możesz umieścić małą liczbę w rejestrze za pomocą jednej instrukcji. Dopuszczenie tylko zera z fałszywą zależnością byłoby szalone. (Normalne MIPS tworzy niezerowe wartości za pomocą addiu $dst, $zero, 1234lub ori, więc argument „koszt energii” załamuje się. Jeśli chcesz uniknąć uruchamiania ALU, możesz dołączyć kod operacji dla mov-natychmiastowej rejestracji zamiast oprogramowania ADD lub OR natychmiastowe zero.)
Peter Cordes