Perché MIPS utilizza R0 come "zero" quando è possibile solo XOR due registri per produrre 0?


10

Penso che sto cercando una risposta a una domanda a quiz. Sto cercando di capire perché l'architettura MIPS utilizza un valore "zero" esplicito in un registro quando puoi ottenere la stessa cosa semplicemente XOR''un qualsiasi registro contro se stesso. Si potrebbe dire che l'operazione è già stata eseguita per te; tuttavia, non riesco davvero a immaginare una situazione in cui useresti molti valori "zero". Ho letto i documenti originali di Hennessey e assegna uno zero come dato di fatto senza alcuna vera giustificazione.

Esiste una ragione logica per avere un'assegnazione binaria hardcoded di zero?

aggiornamento: In 8k di un eseguibile da xc32-gcc per il core MIPS in PIC32MZ, ho una singola istanza di "zero".

add     t3,t1,zero

la risposta effettiva: ho assegnato la generosità alla persona che aveva le informazioni su MIPS e codici di condizione. La risposta in realtà sta nell'architettura MIPS per le condizioni. Sebbene inizialmente non volessi assegnare tempo a questo, ho rivisto l'architettura per opensparc , MIPS-V e OpenPOWER (questo documento era interno) e qui ci sono i risultati riassuntivi. Il registro R0 necessario per il confronto sui rami a causa dell'architettura della conduttura.

  • confronto intero con zero e branch (bgez, bgtz, blez, bltz)
  • intero confronta due registri e ramo (beq, bne)
  • intero confronta due registri e trap (teq, tge, tlt, tne)
  • registro di confronto intero e immediato e trap (teqi, tgei, tlti, tnei)

Dipende semplicemente dall'aspetto dell'implementazione dell'hardware. Dal manuale MIPS-V, c'è una citazione senza riferimento a pagina 68:

I rami condizionali sono stati progettati per includere operazioni di confronto aritmetico tra due registri (come anche in PA-RISC e Xtensa ISA), anziché utilizzare codici di condizione (x86, ARM, SPARC, PowerPC) o per confrontare un solo registro con zero ( Alpha, MIPS) o due registri solo per uguaglianza (MIPS). Questo progetto è stato motivato dall'osservazione che un'istruzione combinata di comparazione e diramazione si trasforma in una pipeline regolare, evita lo stato del codice di condizione aggiuntivo o l'uso di un registro temporaneo e riduce le dimensioni del codice statico e il trac di recupero delle istruzioni dinamiche. Un altro punto è che i confronti contro lo zero richiedono un ritardo del circuito non banale (specialmente dopo il passaggio alla logica statica nei processi avanzati) e quindi sono quasi costosi rispetto alla magnitudine aritmetica. Un altro vantaggio di un'istruzione di comparazione e diramazione fusa è che i rami sono osservati prima nel flusso di istruzioni front-end e quindi possono essere previsti in precedenza. Vi è forse un vantaggio in un progetto con codici di condizione nel caso in cui più rami possano essere presi in base agli stessi codici di condizione, ma riteniamo che questo caso sia relativamente raro.

Il documento MIPS-V non colpisce l'autore della sezione citata. Ringrazio tutti per il loro tempo e considerazione.


6
Spesso si desidera utilizzare un registro con valore 0 in alcune operazioni come valore sorgente. Sarebbe un certo sovraccarico azzerare un registro prima di quelle operazioni, quindi le prestazioni traggono vantaggio se si può semplicemente usare uno zero fornito invece di crearlo da soli ogni volta che è necessario. Gli esempi includono l'aggiunta di una bandiera carry.
JimmyB,

3
Sull'architettura AVR, gcc si occupa di inizializzare r1 a zero all'avvio e non tocca mai più quel valore, usando r1 come sorgente ovunque non sia possibile utilizzare uno 0 immediato. Qui, il compilatore di zero dedicato viene "emulato" nel software dal compilatore per motivi di prestazioni. (La maggior parte degli AVR ha 32 registri, quindi mettere da parte uno (due, in realtà) non costa molto in relazione alle possibili prestazioni e ai vantaggi della dimensione del codice.)
JimmyB

1
Non conosco MIPS, ma potrebbe essere più veloce spostare r0 in un altro registro rispetto a XORing quel registro per cancellarlo.
JimmyB,

Quindi non sei d'accordo sul fatto che zero è così frequente che merita una posizione nel file di registro? Quindi probabilmente hai ragione perché è vero che questo è controverso e ci sono molti ISA che scelgono di non riservare un registro zero. Come altre caratteristiche controverse al momento come finestre di registro, slot di succursale, previsioni di istruzioni dai "vecchi tempi" ... se devi progettare un ISA, non devi usarle se decidi di non farlo.
user3528438

2
Potrebbe essere interessante leggere uno dei vecchi documenti RISC di Berkeley, RISC I: A Reduced Instruction Set VLSI Computer . Mostra come l'utilizzo di un registro zero cablato, R0, consente di implementare una serie di istruzioni VAX e modalità di indirizzamento in una singola istruzione RISC.
Mark Plotnick,

Risposte:


14

Il registro zero sulle CPU RISC è utile per due motivi:

È una costante utile

A seconda delle restrizioni dell'ISA, non è possibile utilizzare un valore letterale in alcune istruzioni di codifica, ma si può essere certi di poterlo utilizzare r0per ottenere 0.

Può essere usato per sintetizzare altre istruzioni

Questo è forse il punto più importante. Come progettista ISA, puoi scambiare un registro per scopi generici con un registro zero per poter sintetizzare altre istruzioni utili. La sintesi delle istruzioni è buona perché, avendo meno istruzioni effettive, sono necessari meno bit per codificare un'operazione in un codice operativo, che libera spazio nello spazio di codifica delle istruzioni. Puoi usare quello spazio per avere ad esempio offset e / o letterali di indirizzi più grandi.

La semantica del registro zero è come /dev/zerosui sistemi * nix: tutto ciò che è scritto su di esso viene scartato e si legge sempre 0.

Vediamo alcuni esempi di come possiamo creare pseudo-istruzioni con l'aiuto del r0registro zero:

; ### 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]

Il caso di MIPS

Ho esaminato più da vicino il set di istruzioni MIPS. Ci sono alcune pseudo-istruzioni che usano $zero; sono utilizzati principalmente per i rami. Ecco alcuni esempi di ciò che ho trovato:

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

Per quanto riguarda il motivo per cui hai trovato solo un'istanza del $zeroregistro nel tuo disassemblaggio, forse è il tuo disassemblatore che è abbastanza intelligente da trasformare sequenze note di istruzioni nella loro pseudoistruzione equivalente.

Il registro zero è davvero utile?

Bene, a quanto pare, ARM ritiene che avere un registro zero sia abbastanza utile che nel loro (un po ') nuovo core ARMv8-A, che implementa AArch64, ora c'è un registro zero in modalità 64-bit; prima non c'era un registro zero. (Il registro è un po 'speciale, in alcuni contesti di codifica è un registro zero, in altri indica invece il puntatore dello stack )


Non penso che MIPS usi le bandiere, vero? Il registro zero aggiunge la possibilità di leggere / scrivere incondizionatamente ccess determinati indirizzi senza riguardo per il contenuto di tutti i registri della CPU e aiuta a facilitare un'operazione di tipo "mov immediato", ma altri movimenti potrebbero essere fatti logicamente o mandando l'origine con se stesso .
supercat

1
In effetti, non esiste un registro che tengono le bandiere aritmetici, invece ci sono tre istruzioni che aiutano emulare comuni rami condizionali ( slt, slti, sltu).
Jarhmander

Osservando il set di istruzioni MIPS, e dato che da quello che capisco ogni istruzione verrà recuperata dal momento in cui viene eseguita l'istruzione precedente, mi chiedo se sarebbe stato difficile avere un codice operativo che non riguardasse direttamente nulla, ma invece dire che se viene eseguita un'istruzione in modalità immediata e la successiva istruzione recuperata ha quel modello di bit, i 16 bit superiori dell'operando verranno presi dall'istruzione prefetched? Ciò comporterebbe che le operazioni in modalità immediata a 32 bit vengano gestite con un'istruzione di due cicli di due parole anziché dover spendere due parole e due cicli ...
supercat

... caricamento di un operando e quindi un terzo ciclo per usarlo effettivamente.
supercat

7

La maggior parte delle implementazioni ARM / POWER / SPARC ha un registro RAZ nascosto

Potresti pensare che ARM32, SPARC ecc. Non abbiano un registro 0 ma in realtà lo fanno! A livello di microarchitettura, la maggior parte dei progettisti di CPU aggiunge un registro 0 che può essere invisibile al software (il registro zero di ARM è invisibile) e usa quel registro zero per semplificare la decodifica delle istruzioni.

Considera un tipico design ARM32 moderno che ha un registro invisibile del software, ad esempio R16 cablato a 0. Considera il carico ARM32, molti casi di istruzioni di caricamento ARM32 rientrano in una di queste forme (Ignora l'indicizzazione pre-post per un po 'per mantenere la discussione semplice ) ...

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

All'interno del processore, questo viene decodificato in generale

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

prima di entrare nella fase di emissione in cui vengono letti i registri. Si noti che rx rappresenta il registro per riscrivere l'indirizzo aggiornato. Ecco alcuni esempi di decodifica:

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.

A livello di circuito, tutti e tre i carichi sono in realtà le stesse istruzioni interne e un modo semplice per ottenere questo tipo di ortogonalità è creare un registro di terra R16. Poiché l'R16 è sempre collegato a terra, queste istruzioni naturalmente decodificano correttamente senza alcuna logica aggiuntiva. La mappatura di una classe di istruzioni su un singolo formato interno aiuta notevolmente nelle implementazioni superscalari in quanto riduce la complessità della logica.

Un altro motivo è un modo semplificato per buttare via le scritture. Le istruzioni possono essere disabilitate semplicemente impostando il registro di destinazione e i flag su R16. Non è necessario creare alcun altro segnale di controllo per disabilitare la riscrittura, ecc.

La maggior parte delle implementazioni del processore, indipendentemente dall'architettura, finiscono presto con un modello di registro RAZ. La pipeline MIPS inizia essenzialmente in un punto in cui in altre architetture ci sarebbero alcune fasi.

MIPS ha fatto la scelta giusta

Pertanto, un registro read-as-zero è quasi obbligatorio in qualsiasi implementazione di processore moderno e MIPS che lo rende visibile al software è sicuramente un punto in più dato come semplifica la logica di decodifica interna. I progettisti di processori MIPS non devono aggiungere un registro RAZ aggiuntivo poiché $ 0 è già a terra. Poiché RAZ è disponibile per l'assemblatore, molte istruzioni psuedo sono disponibili per MIPS e si può pensare a questo come a spingere parte della logica di decodifica nell'assemblatore stesso invece di creare formati dedicati per ciascun tipo di istruzione per nascondere il registro RAZ dal software come con altre architetture. Il registro RAZ è una buona idea ed è per questo che ARMv8 lo ha copiato.

Se ARM32 avesse un registro di $ 0, la logica di decodifica sarebbe diventata più semplice e l'architettura sarebbe stata molto migliore in termini di velocità, area e potenza. Ad esempio, delle tre versioni di LDR presentate sopra, sarebbero necessari solo 2 formati. Allo stesso modo, non è necessario riservare la logica di decodifica per le istruzioni MOV e MVN. Inoltre, CMP / CMN / TST / TEQ diventerebbe ridondante. Non sarebbe inoltre necessario distinguere tra moltiplicazione corta (MUL) e lunga (UMULL / SMULL) poiché la moltiplicazione breve potrebbe essere considerata come una moltiplicazione lunga con il registro alto impostato su $ 0 ecc.

Poiché MIPS è stato inizialmente progettato da un piccolo team, la semplicità del design era importante e quindi $ 0 è stato scelto esplicitamente nello spirito di RISC. ARM32 mantiene molte funzionalità CISC tradizionali a livello di architettura.


1
Non tutte le CPU ARM32 funzionano come descritto. Alcuni hanno prestazioni inferiori per istruzioni di caricamento più complesse e / o per la riscrittura nel registro. Quindi non possono decodificare tutti esattamente allo stesso modo.
Peter Cordes,

6

Disclamer: non conosco davvero l'assemblatore MIPS, ma il registro a valore 0 non è univoco per questa architettura e immagino che sia usato allo stesso modo di altre architetture RISC che conosco.

XORing un registro per ottenere 0 ti costerà un'istruzione, mentre l'uso di un registro con valore 0 predefinito no.

Ad esempio, l' mov RX, RYistruzione viene spesso implementata come add RX, RY, R0. Senza un registro a valore 0, dovresti farlo xor RZ, RZogni volta che vuoi usare mov.

Un altro esempio è l' cmpistruzione e le sue varianti (come "confronta e salta", "confronta e sposta", ecc.), Dove cmp RX, R0viene utilizzato per verificare la presenza di numeri negativi.


1
Ci sarebbero problemi di implementazione MOV Rx,Rycome AND Rx,Ry,Ry?
supercat

3
@supercat Non sarai in grado di codificare mov RX, Immo mov RX, mem[RY]se il tuo set di istruzioni supporta solo un singolo valore immediato e un singolo accesso alla memoria per istruzione.
Dmitry Grigoryev il

Non ho familiarità con quali modalità di indirizzamento ha il MIPS. So che ARM ha le modalità [Rx + Ry << scale] e [Rx + disp], e mentre essere in grado di utilizzare quest'ultimo per alcuni indirizzi assoluti potrebbe essere utile in alcuni casi, in genere non è essenziale. Una modalità [Rx] diretta può essere emulata tramite [Rx + disp] usando lo spostamento zero. Cosa usa il MIPS?
supercat

movè un cattivo esempio; potresti implementarlo con uno 0 immediato invece di un registro zero. es ori dst, src, 0. Ma sì, avresti bisogno di un codice operativo per la registrazione di mov-immediate se non lo hai fatto addiu $dst, $zero, 1234, come luima per i 16 bit inferiori invece dei 16 superiori. E non potresti usare noro subcostruire un operando no / neg .
Peter Cordes,

@supercat: nel caso ti stia ancora chiedendo: il MIPS classico ha una sola modalità di indirizzamento: register + disp16. Il moderno MIPS ha aggiunto altri codici operativi per le modalità di indirizzamento a 2 registri per carichi / negozi FP, accelerando l'indicizzazione dell'array. (Ma non ancora per il carico / archivio di numeri interi, forse perché ciò potrebbe richiedere più porte di lettura nel file di registro di numeri interi per 2 registri di indirizzi + un registro di dati per un archivio. Vedere Utilizzo di un registro come offset )
Peter Cordes,

3

Legare alcuni cavi a terra alla fine della banca dei registri è economico (più economico che trasformarlo in un registro completo).

Fare l'effettivo xor richiede un po 'di energia e tempo per cambiare le porte e per poi memorizzarlo nel registro, perché pagare quel costo quando un valore 0 esistente può essere facilmente disponibile.

I cpus moderni hanno anche un registro (nascosto) con valore 0 che possono usare come risultato di xor eax eaxun'istruzione attraverso la ridenominazione del registro.


6
Il vero costo R0non sta nel mettere a terra alcuni fili ma nel fatto che è necessario riservare un codice per esso in ogni istruzione relativa ai registri.
Dmitry Grigoryev,

Lo xor è un'aringa rossa. xor-zeroing è valido solo su x86, in cui le CPU riconoscono il linguaggio ed evitano una dipendenza dagli input. Come hai sottolineato, la famiglia Sandybridge non ha nemmeno fatto nulla per farlo, ma solo gestendolo nella fase di rinomina del registro. ( Qual è il modo migliore per impostare un registro su zero nell'assembly x86: xor, mov o e? ). Ma su MIPS, XORing un registro avrebbe una falsa dipendenza; le regole di ordinamento delle dipendenze di memoria (equivalente HW di C ++ std::memory_order_consume) richiedono che XOR propaghi la dipendenza.
Peter Cordes,

Se non avessi un registro zero, includeresti un codice operativo per spostare un immediato in un registro. Come luima non spostato a sinistra di 16. Quindi puoi ancora mettere un piccolo numero in un registro con un'istruzione. Consentire solo zero con una falsa dipendenza sarebbe folle. (Il MIPS normale crea valori diversi da zero con addiu $dst, $zero, 1234o ori, quindi l'argomento "power cost" si interrompe. Se si desidera evitare di avviare un ALU, si dovrebbe includere un codice operativo per la registrazione di mov-immediate invece di avere il software ADD o OR un immediato con zero.)
Peter Cordes,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.