No Tour do x86-64 dos manuais da Intel , li
Talvez o fato mais surpreendente seja que uma instrução como essa
MOV EAX, EBX
zera automaticamente os 32 bits superiores doRAX
registro.
A documentação da Intel (3.4.1.1 Registros de uso geral no modo de 64 bits no manual Arquitetura básica) citada na mesma fonte nos diz:
- Operandos de 64 bits geram um resultado de 64 bits no registrador de uso geral de destino.
- Operandos de 32 bits geram um resultado de 32 bits, estendido por zero para um resultado de 64 bits no registrador de uso geral de destino.
- Operandos de 8 e 16 bits geram um resultado de 8 ou 16 bits. Os 56 bits ou 48 bits superiores (respectivamente) do registrador de uso geral de destino não são modificados pela operação. Se o resultado de uma operação de 8 ou 16 bits se destinar ao cálculo do endereço de 64 bits, estenda explicitamente o registro para 64 bits completos.
Em montagem x86-32 e x86-64, instruções de 16 bits, como
mov ax, bx
não mostre esse tipo de comportamento "estranho" de que a palavra superior de eax seja zerada.
Assim: qual é o motivo pelo qual esse comportamento foi introduzido? À primeira vista, parece ilógico (mas o motivo pode ser que estou acostumado com as peculiaridades do assembly x86-32).
r32
operando de destino zeram o 32 alto, em vez de mesclar. Por exemplo, alguns montadores substituirãopmovmskb r64, xmm
porpmovmskb r32, xmm
, salvando um REX, porque a versão de destino de 64 bits se comporta de forma idêntica. Embora a seção Operação do manual liste todas as 6 combinações de 32/64 bits dest e 64/128 / 256b fonte separadamente, a extensão zero implícita da forma r32 duplica a extensão zero explícita da forma r64. Estou curioso sobre a implementação do HW ...xor eax,eax
ouxor r8d,r8d
é a melhor maneira de zerar RAX ou R8 (salvar um prefixo REX para RAX, e XOR de 64 bits não é nem tratado especialmente no Silvermont). Relacionado: Como exatamente os registros parciais no Haswell / Skylake funcionam? A escrita AL parece ter uma falsa dependência de RAX e AH é inconsistenteRespostas:
Não sou AMD ou falando por eles, mas teria feito da mesma forma. Porque zerar a metade superior não cria uma dependência do valor anterior, que a CPU teria que esperar. O mecanismo de renomeação de registro seria essencialmente derrotado se não fosse feito dessa forma.
Dessa forma, você pode escrever código rápido usando valores de 32 bits no modo de 64 bits sem ter que quebrar as dependências explicitamente o tempo todo. Sem esse comportamento, cada instrução de 32 bits no modo de 64 bits teria que esperar por algo que aconteceu antes, mesmo que essa parte alta quase nunca fosse usada. (Fazer
int
64 bits desperdiçaria espaço de cache e largura de banda de memória; x86-64 suporta de forma mais eficiente tamanhos de operando de 32 e 64 bits )O comportamento para tamanhos de operando de 8 e 16 bits é estranho. A loucura da dependência é uma das razões pelas quais as instruções de 16 bits são evitadas agora. O x86-64 herdou isso de 8086 para 8 bits e 386 para 16 bits, e decidiu fazer os registradores de 8 e 16 bits funcionarem da mesma maneira no modo de 64 bits e no modo de 32 bits.
Veja também Por que o GCC não usa registros parciais? para detalhes práticos de como as gravações em registros parciais de 8 e 16 bits (e leituras subsequentes do registro completo) são tratadas por CPUs reais.
fonte
Ele simplesmente economiza espaço nas instruções e no conjunto de instruções. Você pode mover pequenos valores imediatos para um registro de 64 bits usando as instruções existentes (32 bits).
Também evita que você tenha que codificar valores de 8 bytes para
MOV RAX, 42
quandoMOV EAX, 42
puder ser reutilizado.Essa otimização não é tão importante para operações de 8 e 16 bits (porque são menores) e alterar as regras também quebraria o código antigo.
fonte
XOR EAX, EAX
porqueXOR RAX, RAX
precisaria de um prefixo REX.[rsi + edx]
não é permitido). Obviamente, evitar falsas dependências / paralisações de registros parciais (a outra resposta) é outro motivo importante.Sem zero estendendo-se para 64 bits, isso significaria que uma instrução de leitura
rax
teria 2 dependências para seurax
operando (a instrução que escreveeax
e a instrução que escreverax
antes dela), isso significa que 1) o ROB teria que ter entradas para múltiplas dependências para um único operando, o que significa que o ROB exigiria mais lógica e transistores e ocuparia mais espaço, e a execução seria mais lenta aguardando uma segunda dependência desnecessária que poderia levar anos para ser executada; ou alternativamente 2), que suponho que aconteça com as instruções de 16 bits, o estágio de alocação provavelmente para (ou seja, se o RAT tiver uma alocação ativa para umaax
gravação e umaeax
leitura aparecer, ele para até que aax
gravação seja retirada).O único benefício de extensão diferente de zero é garantir que os bits de ordem superior
rax
sejam incluídos, por exemplo, se originalmente contiver 0xffffffffffffffff, o resultado seria 0xffffffff00000007, mas há muito pouca razão para o ISA fazer essa garantia com tal despesa, e é mais provável que o benefício da extensão zero seja realmente mais necessário, portanto, ele economiza a linha de código extramov rax, 0
. Garantindo que ele sempre será estendido de zero a 64 bits, os compiladores podem trabalhar com este axioma em mente enquanto estão ligadosmov rdx, rax
,rax
apenas tem que esperar por sua dependência única, o que significa que pode começar a execução mais rápido e se retirar, liberando unidades de execução. Além disso, também permite expressões idiomáticas zero mais eficientes, comoxor eax, eax
zero,rax
sem exigir um byte REX.fonte
cmovbe
é 2 uops, mascmovb
é 1). Mas nenhuma CPU que renomeie o registro parcial o faz da maneira que você sugeriu. Em vez disso, eles inserem um uop mesclado se um reg parcial for renomeado separadamente do reg completo (ou seja, for "sujo"). Veja Por que o GCC não usa registros parciais? e como exatamente os registros parciais no Haswell / Skylake funcionam? A escrita AL parece ter uma falsa dependência de RAX, e AH é inconsistenteThis 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
Não consigo encontrar um exemplo do 'uop de fusão' que seria usado para resolver isso, o mesmo para uma paralisação parcial de sinalizaçãomov al, [mem]
é uma carga microfundida + ALU- merge, apenas renomeando AH, e um uop de fusão AH ainda apresenta problemas sozinho. Os mecanismos de mesclagem de flag parcial nesses CPUs variam, por exemplo, Core2 / Nehalem ainda está travado para flags parciais, ao contrário de parcial-reg.