Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
Este artigo descreve os processos e convenções padrão que uma função (o chamador) usa para fazer chamadas para outra função (o chamado) em código x64.
Para mais informações sobre a __vectorcall convenção de chamadas, consulte __vectorcall.
Padrões da convenção de chamada
A Interface Binária de Aplicações (ABI) x64 utiliza por defeito uma convenção de chamadas rápidas de quatro registos. O espaço é alocado na pilha de chamadas como um armazenamento de sombra para os destinatários salvarem esses registros.
Há uma correspondência estrita um-para-um entre os argumentos de uma chamada de função e os registros usados para esses argumentos. Qualquer argumento que não caiba em 8 bytes, ou não seja 1, 2, 4 ou 8 bytes, deve ser passado por referência. Um único argumento nunca é distribuído por vários registos.
A pilha de registro x87 não é usada. Pode ser usado pela função chamada, mas considere-o volátil entre chamadas de função. Todas as operações de ponto flutuante são feitas usando os 16 registradores XMM.
Os argumentos inteiros são passados nos registros RCX, RDX, R8 e R9. Os argumentos de ponto flutuante são passados em XMM0L, XMM1L, XMM2L e XMM3L. Argumentos de 16 bytes são passados por referência. A passagem de parâmetros é descrita em detalhes em Passagem de parâmetros. Esses registros, e RAX, R10, R11, XMM4 e XMM5, são considerados voláteis ou potencialmente alterados por um destinatário no retorno. O uso do registo é documentado em detalhes no uso do registo x64 e nos registos preservados pelo chamador/destinatário.
Para funções protótipo, todos os argumentos são convertidos para os tipos de destinatários esperados antes de passar. O chamador é responsável por alocar espaço para os parâmetros do destinatário. O chamador deve sempre alocar espaço suficiente para armazenar quatro parâmetros de registro, mesmo que o chamador não aceite tantos parâmetros. Esta convenção simplifica o suporte para funções de linguagem C não prototipadas e funções vararg C/C++. Para funções vararg ou não prototipadas, quaisquer valores de ponto flutuante devem ser duplicados no registo de uso geral correspondente. Quaisquer parâmetros além dos quatro primeiros devem ser armazenados na pilha após o armazenamento de sombra antes da chamada. Os detalhes da função Vararg podem ser encontrados em Varargs. As informações de funções não prototipadas são detalhadas em Funções não prototipadas.
Alinhamento
A maioria das estruturas está alinhada ao seu alinhamento natural. As principais exceções são o ponteiro de pilha e malloc ou alloca memória, que são alinhados para 16 bytes para ajudar no desempenho. O alinhamento acima de 16 bytes deve ser feito manualmente. Como 16 bytes é um tamanho de alinhamento comum para operações XMM, esse valor deve funcionar para a maioria dos códigos. Para obter mais informações sobre layout e alinhamento da estrutura, consulte Tipo x64 e layout de armazenamento. Para obter informações sobre o layout da pilha, consulte Uso da pilha x64.
Capacidade de Desenrolamento
As funções folha são funções que não alteram registos não voláteis. Uma função não-folha pode alterar a RSP não volátil, por exemplo, chamando uma função. Ou pode alterar a RSP alocando mais espaço de pilha para variáveis locais. Para recuperar registos não voláteis quando uma exceção é tratada, as funções não-folha são anotadas com dados estáticos. Os dados descrevem como desenrolar corretamente a função em uma instrução arbitrária. Esses dados são armazenados como pdata, ou dados de procedimento, que por sua vez se referem a xdata, a exceção que manipula dados. O xdata contém as informações de desenrolamento e pode apontar para pdata adicional ou uma função manipuladora de exceção.
Prologs e epilogs são altamente restritos para que possam ser descritos corretamente no xdata. O ponteiro da pilha deve permanecer alinhado a 16 bytes em qualquer região do código que não faça parte de um epílogo ou prólogo, exceto nas funções folha. As funções do Leaf podem ser desenroladas simplesmente simulando um retorno, portanto, pdata e xdata não são necessários. Para obter detalhes sobre a estrutura adequada de prólogos e epílogos de função, consulte x64 prolog e epilog. Para obter mais informações sobre o tratamento de exceções e o tratamento e desenrolamento de exceções de pdata e xdata, consulte Tratamento de exceções x64.
Passagem de parâmetros
Por padrão, a convenção de chamada x64 passa os quatro primeiros argumentos para uma função em registros. Os registos utilizados para estes argumentos dependem da posição e do tipo do argumento. Os argumentos restantes são passados na pilha em ordem da direita para a esquerda. O chamador reserva o espaço de pilha necessário e escreve esses argumentos na memória da pilha usando instruções de armazenamento ou de movimento, mantendo o alinhamento de 8 bytes para cada argumento.
Os argumentos com valor inteiro nas quatro posições mais à esquerda são passados na ordem da esquerda para a direita em RCX, RDX, R8 e R9, respectivamente. O quinto e mais alto argumento é passado na pilha como descrito anteriormente. Todos os argumentos inteiros em registros são justificados corretamente, de modo que o destinatário pode ignorar os bits superiores do registro e acessar apenas a parte do registro necessária.
Todos os argumentos de ponto flutuante e precisão dupla nos primeiros quatro parâmetros são passados em XMM0 - XMM3, dependendo da posição. Os valores de ponto flutuante só são colocados nos registos inteiros RCX, RDX, R8 e R9 quando existem argumentos varargs. Para obter detalhes, consulte Varargs. Da mesma forma, os registros XMM0 - XMM3 são ignorados quando o argumento correspondente é um número inteiro ou tipo de ponteiro.
__m128 tipos, matrizes e cadeias de caracteres nunca são passados por valor imediato. Em vez disso, um ponteiro é passado para a memória alocada pelo chamador. Estruturas e uniões de tamanho 8, 16, 32 ou 64 bits, e tipos __m64, são passados como se fossem inteiros do mesmo tamanho. Estruturas ou uniões de outros tamanhos são passadas por um ponteiro para a memória alocada pelo chamador. Para esses tipos agregados passados como um ponteiro, incluindo __m128, a memória temporária alocada pelo chamador deve estar alinhada a 16 bytes.
Funções intrínsecas que não alocam espaço de pilha e não chamam outras funções, às vezes usam outros registros voláteis para passar argumentos de registro adicionais. Esta otimização é possível graças à ligação estreita entre o compilador e a implementação da função intrínseca.
O destinatário é responsável por despejar os parâmetros do registro em seu espaço de sombra, se necessário.
A tabela a seguir resume como os parâmetros são passados, por tipo e posição a partir da esquerda.
| Tipo de parâmetro | quinta e superior | quarto | terceiro | segundo | mais à esquerda |
|---|---|---|---|---|---|
| ponto flutuante | pilha | XMM3 | XMM2 | XMM1 | XMM0 |
| número inteiro | pilha | R9 | R8 | RDX | RCX |
Agregados (8, 16, 32 ou 64 bits) e __m64 |
pilha | R9 | R8 | RDX | RCX |
| Outros agregados, como ponteiros | pilha | R9 | R8 | RDX | RCX |
__m128, como um ponteiro |
pilha | R9 | R8 | RDX | RCX |
Exemplo de passagem de argumento 1 - todos os inteiros
func1(int a, int b, int c, int d, int e, int f);
// a in RCX, b in RDX, c in R8, d in R9, f then e passed on stack
Exemplo de passagem de argumento 2 - todos os flutuadores
func2(float a, double b, float c, double d, float e, float f);
// a in XMM0, b in XMM1, c in XMM2, d in XMM3, f then e passed on stack
Exemplo de passagem de argumento 3 - mistos ints e floats
func3(int a, double b, int c, float d, int e, float f);
// a in RCX, b in XMM1, c in R8, d in XMM3, f then e passed on stack
Exemplo de passagem de argumento 4 - __m64, __m128, e agregados
func4(__m64 a, __m128 b, struct c, float d, __m128 e, __m128 f);
// a in RCX, ptr to b in RDX, ptr to c in R8, d in XMM3,
// ptr to f passed on stack, then ptr to e passed on stack
Varargs
Se os parâmetros forem passados através de varargs (por exemplo, argumentos de elipse), então a convenção normal de passagem de parâmetros por registo aplica-se. Essa convenção inclui derramar o quinto e posteriores argumentos para a pilha. É responsabilidade do destinatário descartar argumentos que tenham seu endereço tomado. Apenas para valores de vírgula flutuante, tanto o registro inteiro quanto o registro de ponto flutuante devem conter o valor, caso o destinatário espere o valor nos registros inteiros.
Funções não prototipadas
Para funções não totalmente prototipadas, o chamador passa valores inteiros como inteiros e valores de vírgula flutuante como precisão dupla. Apenas para valores em ponto flutuante, tanto o registo inteiro como o registo de ponto flutuante contêm o valor de ponto flutuante caso a função chamada espere o valor nos registos inteiros.
func1();
func2() { // RCX = 2, RDX = XMM1 = 1.0, and R8 = 7
func1(2, 1.0, 7);
}
Valores de retorno
Um valor de retorno escalar que pode caber em 64 bits, incluindo o tipo __m64, é retornado através de RAX. Tipos não escalares, incluindo floats, doubles e tipos vetoriais como __m128, __m128i, __m128d são devolvidos em XMM0. O estado dos bits não utilizados no valor retornado em RAX ou XMM0 é indefinido.
Tipos definidos pelo usuário podem ser retornados por valor de funções globais e funções membro estáticas. Para retornar um tipo definido pelo usuário por valor no RAX, ele deve ter um comprimento de 1, 2, 4, 8, 16, 32 ou 64 bits. Ele também não deve ter nenhum construtor, destruidor ou operador de atribuição de cópia definido pelo usuário. Não pode ter quaisquer membros de dados não estáticos privados ou protegidos, nem membros de dados não estáticos do tipo de referência. Não pode ter classes base ou funções virtuais. E só pode ter membros de dados que também atendam a esses requisitos. Esta definição é essencialmente a mesma que um tipo POD C++03. Como a definição mudou no padrão C++11, não recomendamos usar std::is_pod para este teste. Caso contrário, o chamador deve alocar memória para o valor de retorno e passar um ponteiro para esse valor como o primeiro argumento. Os argumentos restantes são então deslocados um lugar para a direita. O mesmo ponteiro deve ser retornado pelo destinatário no RAX.
Estes exemplos mostram como parâmetros e valores de retorno são passados para funções com as declarações especificadas:
Exemplo de valor de retorno 1 - resultado de 64 bits
__int64 func1(int a, float b, int c, int d, int e);
// Caller passes a in RCX, b in XMM1, c in R8, d in R9, e passed on stack,
// callee returns __int64 result in RAX.
Exemplo de valor de retorno 2 - resultado de 128 bits
__m128 func2(float a, double b, int c, __m64 d);
// Caller passes a in XMM0, b in XMM1, c in R8, d in R9,
// callee returns __m128 result in XMM0.
Exemplo de valor de retorno 3 - resultado do tipo de usuário por ponteiro
struct Struct1 {
int j, k, l; // Struct1 exceeds 64 bits.
};
Struct1 func3(int a, double b, int c, float d);
// Caller allocates memory for Struct1 returned and passes pointer in RCX,
// a in RDX, b in XMM2, c in R9, d passed on the stack;
// callee returns pointer to Struct1 result in RAX.
Exemplo de valor de retorno 4 - resultado do tipo de utilizador por valor
struct Struct2 {
int j, k; // Struct2 fits in 64 bits, and meets requirements for return by value.
};
Struct2 func4(int a, double b, int c, float d);
// Caller passes a in RCX, b in XMM1, c in R8, and d in XMM3;
// callee returns Struct2 result by value in RAX.
Registos guardados do chamador/destinatário
O x64 ABI considera os registros RAX, RCX, RDX, R8, R9, R10, R11 e XMM0-XMM5 voláteis. Quando presentes, as porções superiores de YMM0-YMM15 e ZMM0-ZMM15 também são voláteis. Em AVX512VL, os registros ZMM, YMM e XMM 16-31 também são voláteis. Quando o suporte AMX está presente, os registradores de bloco TMM são voláteis. Considere registros voláteis destruídos em chamadas de função, a menos que seja possível comprovar a segurança por meio de análises, como otimização de todo o programa.
O x64 ABI considera os registros RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15 e XMM6-XMM15 não voláteis. Eles devem ser salvos e restaurados por uma função que os use.
Ponteiros de função
Ponteiros de função são simplesmente ponteiros para o rótulo da respetiva função. Não há nenhum requisito de sumário (TOC) para ponteiros de função.
Suporte de ponto flutuante para código mais antigo
Os registradores MMX e de pilha de ponto flutuante (MM0-MM7/ST0-ST7) são preservados nas mudanças de contexto. Não há uma convenção de chamada explícita para esses registros. O uso desses registros é estritamente proibido no código do modo kernel.
FPCSR
O estado de registo também inclui a palavra de controlo da FPU x87. A convenção de convocação determina que este registo não seja volátil.
O registro de palavras de controle FPU x87 é definido usando os seguintes valores padrão no início da execução do programa:
| Registo[bits] | Configurações |
|---|---|
| FPCSR[0:6] | Exceção mascara todos os 1 (todas as exceções mascaradas) |
| FPCSR[7] | Reservado - 0 |
| FPCSR[8:9] | Controlo de Precisão - 10B (precisão dupla) |
| FPCSR[10:11] | Controlo de arredondamento - 0 (arredondar ao mais próximo) |
| FPCSR[12] | Controlo infinito - 0 (não utilizado) |
Um callee que modifica qualquer um dos campos dentro do FPCSR deve restaurá-los antes de retornar ao seu chamador. Além disso, um chamador que modificou qualquer um desses campos deve restaurá-los aos seus valores padrão antes de invocar um chamado, a menos que, por acordo, o destinatário espere os valores modificados.
Existem duas exceções às regras sobre a não volatilidade das bandeiras de controlo:
Em funções onde o objetivo documentado da função dada é modificar os sinalizadores FPCSR não voláteis.
Quando é comprovadamente correto que a violação dessas regras resulta em um programa que se comporta da mesma forma que um programa que não viola as regras, por exemplo, através da análise de todo o programa.
MXCSR
O estado de registro também inclui MXCSR. A convenção de chamada divide esse registro em uma porção volátil e uma porção não volátil. A parte volátil consiste nas seis bandeiras de status, em MXCSR[0:5], enquanto o resto do registro, MXCSR[6:15], é considerado não volátil.
A parte não volátil é definida para os seguintes valores padrão no início da execução do programa:
| Registo[bits] | Configurações |
|---|---|
| MXCSR[6] | Desnormalizados são zeros - 0 |
| MXCSR[07:12] | Exceção mascara todos os 1s (todas as exceções mascaradas) |
| MXCSR[13:14] | Controlo de arredondamento - 0 (arredondar ao mais próximo) |
| MXCSR[15] | Nivelar a zero para subfluxo mascarado - 0 (desligado) |
Um callee que modifica qualquer um dos campos não voláteis no MXCSR deve restaurá-los antes de retornar ao seu chamador. Além disso, um chamador que modificou qualquer um desses campos deve restaurá-los aos seus valores padrão antes de invocar um chamado, a menos que, por acordo, o destinatário espere os valores modificados.
Existem duas exceções às regras sobre a não volatilidade das bandeiras de controlo:
Em funções onde o objetivo documentado da função dada é modificar os sinalizadores MXCSR não voláteis.
Quando é comprovadamente correto que a violação dessas regras resulta em um programa que se comporta da mesma forma que um programa que não viola as regras, por exemplo, através da análise de todo o programa.
Não faça suposições sobre o estado da porção volátil do registro MXCSR em um limite de função, a menos que a documentação da função o descreva explicitamente.
setjmp/longjmp
Quando inclui setjmpex.h ou setjmp.h, todas as chamadas para setjmp ou longjmp resultam numa operação de unwind que aciona destrutores e chamadas __finally. Este comportamento difere do x86, onde a inclusão de setjmp.h resulta na não invocação das cláusulas __finally e dos destrutores.
Uma chamada para setjmp preserva o ponteiro da pilha atual, os registos não voláteis e os registos MXCSR. Chamadas para longjmp regressam ao local de chamada mais recente setjmp e restauram o ponteiro de pilha, os registos não voláteis e os registos MXCSR, voltando ao estado preservado pela chamada mais recente setjmp.