Escolhendo quatro registradores de argumento em x64 - comum a UN * X / Win64
Uma das coisas a se ter em mente sobre o x86 é que o nome do registro para a codificação de "número de registro" não é óbvio; em termos de codificação de instrução (o byte MOD R / M , consulte http://www.c-jump.com/CIS77/CPU/x86/X77_0060_mod_reg_r_m_byte.htm ), os números de registro 0 ... 7 são - nessa ordem - ?AX
, ?CX
, ?DX
, ?BX
, ?SP
, ?BP
, ?SI
,?DI
.
Portanto, escolher A / C / D (regs 0..2) para o valor de retorno e os dois primeiros argumentos (que é a __fastcall
convenção "clássica" de 32 bits ) é uma escolha lógica. No que diz respeito a 64 bits, os regs "superiores" são solicitados e tanto a Microsoft quanto o UN * X / Linux optaram por R8
/R9
como os primeiros.
Mantendo isso em mente, a escolha da Microsoft RAX
(valor de retorno) e RCX
, RDX
, R8
, R9
(arg [0..3]) são uma selecção compreensível se você escolher quatro registros achados para argumentos.
Não sei por que o AMD64 UN * X ABI escolheu RDX
antes RCX
.
Escolhendo seis registros de argumento em x64 - específico de UN * X
UN * X, em arquiteturas RISC, tradicionalmente tem feito passagem de argumentos em registros - especificamente, para os primeiros seis argumentos (isso é assim em PPC, SPARC, MIPS pelo menos). Essa pode ser uma das principais razões pelas quais os designers da ABI AMD64 (UN * X) optaram por usar seis registradores também nessa arquitetura.
Então se você quer seis registros para passar argumentos em, e é lógico escolher RCX
, RDX
, R8
eR9
para quatro deles, que outros dois você deve escolher?
Os regs "mais altos" requerem um byte de prefixo de instrução adicional para selecioná-los e, portanto, têm uma pegada de tamanho de instrução maior, então você não gostaria de escolher qualquer um deles se tiver opções. Dos registros clássicos, devido ao significado implícito de RBP
e RSP
estes não estão disponíveis, e RBX
tradicionalmente tem um uso especial em UN * X (tabela de deslocamento global) com o qual aparentemente os designers do AMD64 ABI não queriam se tornar incompatíveis desnecessariamente.
Portanto, a única escolha era RSI
/ RDI
.
Portanto, se você tiver que tomar RSI
/ RDI
como registradores de argumento, quais argumentos eles devem ser?
Fazê-los arg[0]
e arg[1]
tem algumas vantagens. Veja o comentário de cHao.
?SI
e ?DI
são operandos de origem / destino de instrução de string, e como cHao mencionado, seu uso como registradores de argumento significa que, com as convenções de chamada AMD64 UN * X, a strcpy()
função mais simples possível , por exemplo, consiste apenas nas duas instruções de CPU repz movsb; ret
porque a origem / destino endereços foram colocados nos registros corretos pelo chamador. Existe, particularmente no código de "cola" gerado pelo compilador e de baixo nível (pense, por exemplo, alguns alocadores de heap C ++ preenchendo objetos em construção ou as páginas de heap de preenchimento zero do kernel emsbrk()
, ou cópia -write pagefaults) uma enorme quantidade de cópia / preenchimento de bloco, portanto, será útil para o código tão freqüentemente usado para salvar as duas ou três instruções da CPU que, de outra forma, carregariam tais argumentos de endereço de origem / destino nos registros "corretos".
Então, de certa forma, UN * X e Win64 são apenas diferentes em que UN * X "prepends" dois argumentos adicionais, em propositadamente escolhidas RSI
/ RDI
registadoras, para a escolha natural de quatro argumentos em RCX
, RDX
, R8
e R9
.
Além disso ...
Existem mais diferenças entre os ABIs UN * X e Windows x64 do que apenas o mapeamento de argumentos para registros específicos. Para obter uma visão geral do Win64, verifique:
http://msdn.microsoft.com/en-us/library/7kcdt6fy.aspx
Win64 e AMD64 UN * X também diferem notavelmente na forma como o stackspace é usado; no Win64, por exemplo, o chamador deve alocar o espaço de pilha para os argumentos da função, mesmo que os argumentos 0 ... 3 sejam passados nos registradores. No UN * X, por outro lado, uma função folha (ou seja, uma que não chama outras funções) nem mesmo é necessária para alocar espaço de pilha se não precisar de mais de 128 bytes (sim, você possui e pode usar uma certa quantidade de pilha sem alocá-la ... bem, a menos que você seja o código do kernel, uma fonte de bugs bacanas). Todas essas são escolhas de otimização particulares, a maior parte da justificativa para elas é explicada nas referências ABI completas para as quais a referência da Wikipédia do autor original aponta.