Por que o MIPS usa R0 como "zero" quando você pode apenas XOR dois registros para produzir 0?


10

Eu acho que estou procurando uma resposta para uma pergunta trivial. Estou tentando entender por que a arquitetura MIPS usa um valor "zero" explícito em um registro, quando você pode conseguir a mesma coisa apenas usando XOR em qualquer registro. Pode-se dizer que a operação já foi feita para você; no entanto, não consigo imaginar uma situação em que você usaria muitos valores "zero". Li os artigos originais de Hennessey e, na verdade, atribui um zero sem nenhuma justificativa real.

Existe uma razão lógica para ter uma atribuição binária codificada de zero?

atualização: em 8k de um executável do xc32-gcc para o núcleo MIPS no PIC32MZ, tenho uma única instância de "zero".

add     t3,t1,zero

a resposta real: concedeu a recompensa à pessoa que tinha as informações sobre MIPS e códigos de condição. A resposta realmente está na arquitetura MIPS para condições. Embora eu inicialmente não quisesse atribuir tempo para isso, revisei a arquitetura do opensparc , MIPS-V e OpenPOWER (este documento era interno) e aqui estão os resultados resumidos. O registro R0 é necessário para comparação nas filiais devido à arquitetura do pipeline.

  • número inteiro comparado com zero e ramo (bgez, bgtz, blez, bltz)
  • número inteiro compara dois registros e ramificações (beq, bne)
  • número inteiro compara dois registros e armadilha (teq, tge, tlt, tne)
  • integer compare register e imediato e trap (teqi, tgei, tlti, tnei)

Simplesmente se resume à aparência do hardware na implementação. No manual do MIPS-V, há uma cotação não referenciada na página 68:

As ramificações condicionais foram projetadas para incluir operações de comparação aritmética entre dois registros (como também são feitas no PA-RISC e no Xtensa ISA), em vez de usar códigos de condição (x86, ARM, SPARC, PowerPC) ou para comparar apenas um registro contra zero ( Alpha, MIPS) ou dois registros apenas para igualdade (MIPS). Esse projeto foi motivado pela observação de que uma instrução combinada de comparação e ramificação se transforma em um pipeline regular, evita o estado adicional do código de condição ou o uso de um registro temporário e reduz o tamanho do código estático e o rastreamento dinâmico de busca de instruções. Outro ponto é que as comparações com zero requerem atraso não trivial do circuito (especialmente após a mudança para a lógica estática em processos avançados) e, portanto, são quase tão caras quanto a magnitude aritmética compara. Outra vantagem de uma instrução de comparação e ramificação fundida é que as ramificações são observadas mais cedo no fluxo de instruções front-end e, portanto, podem ser previstas mais cedo. Talvez haja uma vantagem em um projeto com códigos de condição no caso em que várias ramificações podem ser obtidas com base nos mesmos códigos de condição, mas acreditamos que esse caso seja relativamente raro.

O documento MIPS-V não é encontrado no autor da seção citada. Agradeço a todos pelo tempo e consideração.


6
Você geralmente deseja usar um registro com valor 0 em alguma operação como valor de origem. Seria uma sobrecarga zerar um registro antes dessas operações, portanto o desempenho é beneficiado se você puder usar apenas um zero fornecido em vez de criar você mesmo sempre que necessário. Exemplos incluem a adição de um sinalizador de transporte.
JimmyB

3
Na arquitetura do AVR, o gcc cuida de inicializar r1 para zero na inicialização e nunca toca nesse valor novamente, usando r1 como uma fonte sempre que um 0 imediato não puder ser usado. Aqui, o registro zero dedicado é 'emulado' no software pelo compilador por razões de desempenho. (A maioria dos AVRs tem 32 registradores, assim, definir um (dois, na verdade) de lado não custa muito em relação aos possíveis benefícios do tamanho de desempenho e de código.)
JimmyB

1
Não conheço o MIPS, mas pode ser mais rápido mover r0 para outro registrador em comparação com o XORing desse registrador para limpá-lo.
JimmyB

Então você discorda do ponto em que zero é tão frequente que vale uma posição no arquivo de registro? Então provavelmente você está certo, porque é verdade que isso é controverso e existem muitos ISAs que optam por não reservar um registro zero. Como outro recurso polêmico da época, como janelas de registro, slots de ramificação, previsão de instruções dos "velhos tempos" ... se você deseja projetar um ISA, não precisa usá-los se decidir não fazê-lo.
user3528438

2
Pode ser interessante ler um dos documentos antigos do RISC de Berkeley, o RISC I: um computador VLSI com conjunto de instruções reduzido . Ele mostra como o uso de um registro zero com fio, R0, permite que várias instruções VAX e modos de endereçamento sejam implementados em uma única instrução RISC.
Mark Plotnick

Respostas:


14

O registro zero nas CPUs RISC é útil por dois motivos:

É uma constante útil

Dependendo das restrições do ISA, você não pode usar um literal em algumas instruções de codificação, mas pode ter certeza de que pode usá-lo r0para obter 0.

Pode ser usado para sintetizar outras instruções

Este é talvez o ponto mais importante. Como designer de ISA, você pode trocar um registro de uso geral por um registro zero para poder sintetizar outras instruções úteis. A sintetização das instruções é boa porque, com menos instruções reais, você precisa de menos bits para codificar uma operação em um código de operação, o que libera espaço no espaço de codificação da instrução. Você pode usar esse espaço para, por exemplo, maiores desvios de endereço e / ou literais.

A semântica do registro zero é como /dev/zeronos sistemas * nix: tudo o que é escrito nele é descartado e você sempre lê 0.

Vamos ver alguns exemplos de como podemos fazer pseudo-instruções com a ajuda do 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]

O caso do MIPS

Eu olhei mais de perto o conjunto de instruções do MIPS. Existem algumas pseudo-instruções que são usadas $zero; eles são usados ​​principalmente para galhos. Aqui estão alguns exemplos do que eu encontrei:

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

Quanto ao motivo pelo qual você encontrou apenas uma instância do $zeroregistro na sua desmontagem, talvez seja o seu desmontador que seja inteligente o suficiente para transformar seqüências conhecidas de instruções em suas pseudo-instruções equivalentes.

O registro zero é realmente útil?

Bem, aparentemente, o ARM considera que ter um registro zero é útil o suficiente para que, em seu (um pouco) novo núcleo ARMv8-A, que implementa o AArch64, agora haja um registro zero no modo de 64 bits; não havia um registro zero antes. (O registro é um pouco especial, porém, em alguns contextos de codificação, é um registro zero; em outros, ele designa o ponteiro da pilha )


Não acho que o MIPS use sinalizadores, pois não? O registro zero adiciona a capacidade de acessar / ler incondicionalmente determinados endereços sem levar em consideração o conteúdo de qualquer registro da CPU e ajuda a facilitar uma operação no estilo "mov imediato", mas outros movimentos podem ser feitos através da lógica ou da origem da fonte. .
Supercat 23/03

1
Na verdade, não há registro de que segurar bandeiras aritméticas, em vez há três instruções que desvios condicionais ajuda emular comuns ( slt, slti, sltu).
precisa saber é o seguinte

Observando o conjunto de instruções do MIPS, e considerando que, pelo que entendi, cada instrução será buscada no momento em que a instrução anterior é executada, pergunto-me se teria sido difícil ter um código de operação que não faça nada diretamente, mas diga que se uma instrução de modo imediato for executada e a próxima instrução buscada tiver esse padrão de bits, os 16 bits superiores do operando serão retirados da instrução pré-buscada? Que as operações de modo imediato de 32 bits para ser tratado com um de duas palavras de instrução de dois ciclos em vez de ter que gastar duas palavras e dois ciclos ...
supercat

... carregando um operando e depois um terceiro ciclo para realmente usá-lo.
Supercat 24/03

7

A maioria das implementações ARM / POWER / SPARC possui um registro RAZ oculto

Você pode pensar que o ARM32, SPARC etc não tem um registro 0, mas na verdade eles têm! No nível da microarquitetura, a maioria dos engenheiros de design da CPU adiciona um registro 0 que pode ser invisível ao software (o registro zero do ARM é invisível) e usa esse registro zero para otimizar a decodificação de instruções.

Considere um projeto típico típico do ARM32 que possui um registro invisível do software, digamos R16 conectado a 0. Considere a carga do ARM32, muitos casos de instrução de carregamento do ARM32 se enquadram em uma dessas formas (Ignore a indexação pré-pós por um tempo para manter a discussão simples) ) ...

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

Dentro do processador, isso decodifica em geral

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

antes de entrar no estágio de emissão em que os registros são lidos. Observe que rx representa o registro para gravar novamente o endereço atualizado. Aqui estão alguns exemplos de decodificação:

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.

No nível do circuito, as três cargas são na verdade a mesma instrução interna e uma maneira fácil de obter esse tipo de ortogonalidade é criar um registrador de solo R16. Como o R16 está sempre aterrado, essas instruções naturalmente decodificam corretamente sem nenhuma lógica extra. O mapeamento de uma classe de instruções para um único formato interno ajuda muito nas implementações superescalares, pois reduz a complexidade lógica.

Outro motivo é uma maneira simplificada de jogar fora as gravações. As instruções podem ser desabilitadas simplesmente configurando o registro de destino e os sinalizadores para R16. Não há necessidade de criar nenhum outro sinal de controle para desativar a gravação, etc.

A maioria das implementações de processador, independentemente da arquitetura, acaba com um modelo de registro RAZ no início do pipeline. O pipeline do MIPS começa essencialmente em um ponto que, em outras arquiteturas, teria alguns estágios.

O MIPS fez a escolha certa

Assim, um registro de leitura como zero é quase obrigatório em qualquer implementação de processador moderna e o MIPS tornando-o visível para o software é definitivamente um ponto positivo, dado que ele simplifica a lógica de decodificação interna. Os projetistas de processadores MIPS não precisam adicionar um registro RAZ extra, já que $ 0 já está no chão. Como o RAZ está disponível para o montador, muitas instruções psuedo estão disponíveis para o MIPS e pode-se pensar nisso como empurrar parte da lógica de decodificação para o próprio montador em vez de criar formatos dedicados para cada tipo de instrução para ocultar o registro RAZ do software como em outras arquiteturas. O registro do RAZ é uma boa ideia e é por isso que o ARMv8 o copiou.

Se o ARM32 tivesse um registro de US $ 0, a lógica de decodificação se tornaria mais simples e a arquitetura teria sido muito melhor em termos de velocidade, área e potência. Por exemplo, das três versões do LDR apresentadas acima, apenas 2 formatos seriam necessários. Da mesma forma, não há necessidade de reservar a lógica de decodificação para as instruções MOV e MVN. Além disso, o CMP / CMN / TST / TEQ se tornaria redundante. Também não seria necessário diferenciar entre multiplicação curta (MUL) e longa (UMULL / SMULL), pois a multiplicação curta poderia ser considerada como multiplicação longa com o registro alto definido como $ 0 etc.

Como o MIPS foi inicialmente projetado por uma equipe pequena, a simplicidade do design foi importante e, portanto, US $ 0 foram escolhidos explicitamente no espírito do RISC. O ARM32 mantém muitos recursos tradicionais do CISC no nível arquitetural.


1
Nem todas as CPUs ARM32 funcionam da maneira que você descreve. Alguns têm desempenho inferior para instruções de carregamento mais complexas e / ou para write-back no registrador. Portanto, eles não podem todos decodificar exatamente da mesma maneira.
Peter Cordes

6

Disclamer: Eu realmente não conheço o MIPS assembler, mas o registro de valor 0 não é exclusivo dessa arquitetura e acho que é usado da mesma maneira que em outras arquiteturas RISC que conheço.

XORing um registro para obter 0 custará uma instrução, enquanto usar um registro de valor 0 predefinido não.

Por exemplo, as mov RX, RYinstruções são frequentemente implementadas como add RX, RY, R0. Sem um registro de valor 0, você precisaria xor RZ, RZsempre que quiser usar mov.

Outro exemplo é a cmpinstrução e suas variantes (como "comparar e pular", "comparar e mover" etc.), onde cmp RX, R0é usado para testar números negativos.


1
Haveria algum problema implementando MOV Rx,Rycomo AND Rx,Ry,Ry?
Supercat 21/03

3
@supercat Você não poderá codificar mov RX, Immou mov RX, mem[RY]se o seu conjunto de instruções suportar apenas um único valor imediato e um único acesso à memória por instrução.
Dmitry Grigoryev

Não estou familiarizado com os modos de endereçamento do MIPS. Eu sei que o ARM tem modos [Rx + Ry << escala] e [Rx + disp] e, embora seja capaz de usá-lo para alguns endereços absolutos, pode ser útil em alguns casos, geralmente não é essencial. Um modo [Rx] reto pode ser emulado via [Rx + disp] usando deslocamento zero. O que o MIPS usa?
Supercat 21/03

mové um péssimo exemplo; você pode implementá-lo com um 0 imediato em vez de um registro zero. por exemplo ori dst, src, 0. Mas sim, você precisaria de um código de operação para o mov-imediato se registrar, se não o tivesse addiu $dst, $zero, 1234, como luios 16 bits inferiores em vez dos 16 superiores. E você não poderia usar norou subcriar um operando not / neg .
Peter Cordes

@ supercat: caso você ainda esteja se perguntando: o MIPS clássico possui apenas um único modo de endereçamento: register + disp16. O MIPS moderno adicionou outros opcodes para modos de endereçamento de 2 registros para cargas / lojas FP, acelerando a indexação da matriz. (Mas ainda não para carregamento / armazenamento inteiro, talvez porque isso possa exigir mais portas de leitura no arquivo de registro inteiro para 2 registros de endereço + um registro de dados para uma loja. Consulte Usando um registro como deslocamento )
Peter Cordes

3

Amarrar algumas pistas no final do seu banco de registro é barato (mais barato do que torná-lo um registro completo).

Fazer o xor real leva um pouco de energia e tempo para trocar os portões e depois armazená-lo no registro, por que pagar esse custo quando um valor 0 existente pode facilmente estar disponível.

Os cpus modernos também têm um registro de valor 0 (oculto) que podem ser usados ​​como resultado de uma xor eax eaxinstrução através da renomeação do registro.


6
O custo real R0não está no aterramento de alguns fios, mas no fato de que você precisa reservar um código para ele em todas as instruções que tratam dos registros.
Dmitry Grigoryev 21/03

O xor é um arenque vermelho. O xor-zero é bom apenas no x86, onde as CPUs reconhecem o idioma e evitam uma dependência das entradas. Como você ressalta, a família Sandybridge nem sequer se preocupa com isso, apenas lidando com isso no estágio de renomeação do registro. ( Qual é a melhor maneira de definir um registro como zero na montagem x86: xor, mov ou e? ). Mas no MIPS, XORing um registro teria uma falsa dependência; regras de ordenação de dependências de memória (HW equivalente a C ++ std::memory_order_consume) exigem que o XOR propague a dependência.
Peter Cordes

Se você não tiver um registro zero, inclua um código de operação para mover um imediato para um registro. Gosto, luimas não o deslocamento da esquerda para 16. Portanto, você ainda pode colocar um número pequeno em um registro com uma instrução. Permitir apenas zero com uma falsa dependência seria insano. (O MIPS normal cria valores diferentes de zero com addiu $dst, $zero, 1234ou ori, portanto, seu argumento de "custo de energia" é interrompido. Se você quiser evitar o acionamento de uma ALU, inclua um código de operação para registro imediato em movimento em vez de ter o software ADD ou OR um imediato com zero.)
Peter Cordes
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.