Convenção de chamada x64

Esta seção descreve os processos e convenções padrão que uma função (o chamador) usa para fazer chamadas em outra função (o receptor da chamada) no código x64.

Para obter mais informações sobre a convenção de __vectorcall chamada, consulte __vectorcall.

Padrões de convenção de chamada

A ABI (Application Binary Interface) x64 usa uma convenção de chamada rápida de quatro registros por padrão. O espaço é alocado na pilha de chamadas como um repositório de sombras para que os receptores da chamada salvem esses registros.

Há uma correspondência um-para-um estrita entre os argumentos de uma chamada de função e os registros usados para esses argumentos. Qualquer argumento que não se encaixe 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 entre vários registros.

A pilha de registro x87 não é utilizada. Ela pode ser usada pelo receptor da chamada, mas considerá-la volátil entre chamadas de função. Todas as operações de ponto flutuante são feitas usando os 16 registros 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. Os 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 receptor da chamada no retorno. O uso do registro é documentado em detalhes em Uso do registro x64 e Registros salvos de chamador/receptor da chamada.

Para funções prototipadas, todos os argumentos são convertidos nos tipos de receptores da chamada esperados antes de passar. O chamador é responsável por alocar espaço para os parâmetros do receptor da chamada. O chamador deve sempre alocar espaço suficiente para armazenar quatro parâmetros de registro, mesmo que o receptor da chamada não use tantos parâmetros. Essa 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, todos os valores de ponto flutuante devem ser duplicados no registro de uso geral correspondente. Todos os parâmetros além dos quatro primeiros devem ser armazenados na pilha após o repositório de sombras antes da chamada. Os detalhes da função vararg podem ser encontrados em Varargs. As informações de função não prototipada são detalhadas em funções não prototipadas.

Alinhamento

A maioria das estruturas está alinhada ao alinhamento natural delas. As exceções primárias são o ponteiro de pilha e a memória malloc ou alloca, que estão alinhados em 16 bytes para auxiliar o 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 empilhado, consulte o Uso da pilha x64.

Capacidade de desenrolamento

Funções de folha são funções que não alteram registros 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 ela pode alterar o RSP alocando espaço de pilha adicional para variáveis locais. Para recuperar registros 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 desenrola 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, os dados de tratamento de exceções. O xdata contém as informações de desenrolamento e pode apontar para pdata adicional ou uma função de manipulador de exceção.

Os prólogos e epílogos são altamente restritos para que possam ser descritos corretamente no xdata. O ponteiro de pilha deve permanecer alinhado em 16 bytes em qualquer região do código que não faça parte de um epílogo ou um prólogo, exceto em funções de folha. As funções de folha podem ser desfeitas simplesmente simulando um retorno, assim, 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 Prólogo e epílogo x64. Para obter mais informações sobre o tratamento de exceções e o desenrolamento de pdata e xdata, consulte Tratamento de exceções x64.

Passagem de parâmetro

Por padrão, a convenção de chamada x64 passa os quatro primeiros argumentos para uma função em registros. Os registros usados para esses argumentos dependem da posição e do tipo do argumento. Os argumentos restantes são enviados por push na pilha na ordem da direita para a esquerda.

Os argumentos de 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 argumento e superiores são passados na pilha, conforme descrito anteriormente. Todos os argumentos inteiros nos registros são justificados com razão, portanto, o receptor da chamada pode ignorar os bits superiores do registro e acessar apenas a parte do registro necessária.

Todos os argumentos de ponto flutuante e de precisão dupla nos quatro primeiros parâmetros são passados em XMM0 – XMM3, dependendo da posição. Os valores de ponto flutuante só são colocados nos registros inteiros RCX, RDX, R8 e R9 quando há argumentos varargs. Para ver detalhes, confira Varargs. Da mesma forma, os registros XMM0 – XMM3 são ignorados quando o argumento correspondente é um tipo de ponteiro ou inteiro.

Os tipos __m128, 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. Structs e uniões de tamanho 8, 16, 32 ou 64 bits, e tipos __m64, são passados como se fossem inteiros do mesmo tamanho. Structs ou uniões de outros tamanhos são passados como um ponteiro para a memória alocada pelo chamador. Para esses tipos de agregação passados como um ponteiro, incluindo __m128, a memória temporária alocada pelo chamador deve estar alinhada em 16 bytes.

As 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. Essa otimização é possibilitada pela associação justa entre o compilador e a implementação da função intrínseca.

O receptor da chamada é responsável por despejar os parâmetros de 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 à esquerda:

Tipo de parâmetro quinto e superior quarto terceiro second Esquerda
ponto flutuante stack XMM3 XMM2 XMM1 XMM0
Número inteiro stack R9 R8 RDX RCX
Agregações (8, 16, 32 ou 64 bits) e __m64 stack R9 R8 RDX RCX
Outras agregações, como ponteiros stack R9 R8 RDX RCX
__m128, como um ponteiro stack R9 R8 RDX RCX

Exemplo de argumento passando 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 pushed on stack

Exemplo de argumento passando 2 – todos os floats

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 pushed on stack

Exemplo de argumento passando 3 – ints e floats mistos

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 pushed on stack

Exemplo de argumento passando 4 – __m64, __m128 e agregações

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 pushed on stack, then ptr to e pushed on stack

Varargs

Se os parâmetros forem passados por meio de varargs (por exemplo, argumentos de reticências), a convenção de passagem de parâmetro de registro normal se aplicará. Essa convenção inclui despejar o quinto argumento e os argumentos posteriores para a pilha. É responsabilidade do receptor da chamada despejar argumentos que tenham seu endereço tomado. Somente para valores de ponto flutuante, o registro de inteiro e o registro de ponto flutuante devem conter o valor, caso o receptor da chamada espere o valor nos registros de inteiros.

Funções não prototipadas

Para funções não totalmente prototipadas, o chamador passa valores inteiros como inteiros e valores de ponto flutuante como precisão dupla. Somente para valores de ponto flutuante, o registro de inteiro e o registro de ponto flutuante devem conter o valor flutuante caso o receptor da chamada espere o valor nos registros de 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 ajustar-se em 64 bits, incluindo o tipo __m64, é retornado por meio de RAX. Os tipos não escalares, incluindo floats, duplos e tipos de vetor, como __m128, __m128i, __m128d são retornados em XMM0. O estado de bits não utilizados no valor retornado em RAX ou XMM0 é indefinido.

Os tipos definidos pelo usuário podem ser retornados por valor de funções globais e funções de membro estático. Para retornar um tipo definido pelo usuário por valor em RAX, ele deve ter um tamanho 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. Ele não pode ter membros de dados não estáticos privados ou protegidos e nenhum membro de dados não estáticos do tipo de referência. Ele não pode ter classes base ou funções virtuais. Além disso, ele só pode ter membros de dados que também atendam a esses requisitos. (Essa definição é essencialmente a mesma que um tipo POD C++03. Como a definição foi alterada no padrão C++11, não recomendamos usar std::is_pod para esse teste.) Caso contrário, o chamador deve alocar memória para o valor retornado e passar um ponteiro para ele como o primeiro argumento. Os argumentos restantes são então deslocados um argumento para a direita. O mesmo ponteiro deve ser retornado pelo receptor da chamada em RAX.

Estes exemplos mostram como parâmetros e valores retornados são passados para funções com as declarações especificadas:

Exemplo de valor retornado 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 pushed on stack,
// callee returns __int64 result in RAX.

Exemplo de valor retornado 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 retornado 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 pushed on the stack;
// callee returns pointer to Struct1 result in RAX.

Exemplo de valor retornado 4 – resultado do tipo de usuário por ponteiro

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.

Registros salvos de chamador/receptor da chamada

A ABI x64 considera os registros RAX, RCX, RDX, R8, R9, R10, R11 e XMM0-XMM5 voláteis. Quando presentes, as partes superiores de YMM0-YMM15 e ZMM0-ZMM15 também são voláteis. No AVX512VL, os registros ZMM, YMM e XMM 16-31 também são voláteis. Quando há suporte presente para AMX, os registros de bloco do TMM são voláteis. Considere os registros voláteis destruídos em chamadas de função, a menos que, de outra forma, a segurança seja comprovada por análise, como a otimização do programa inteiro.

A ABI x64 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 usa.

Ponteiros de função

Ponteiros de função são simplesmente ponteiros para o rótulo da respectiva função. Não há requisitos de TOC (sumário) para ponteiros de função.

Suporte de ponto flutuante para código mais antigo

Os registros MMX e de pilha de ponto flutuante (MM0-MM7/ST0-ST7) são preservados entre as alternâncias de contexto. Não há qualquer convenção de chamada explícita para esses registros. O uso desses registros é rigorosamente proibido no código do modo kernel.

FPCSR

O estado de registro também inclui a palavra de controle de FPU x87. A convenção de chamada determina que esse registro seja não volátil.

O registro de palavras de controle de FPU x87 é definido usando os seguintes valores padrão no início da execução do programa:

Register[bits] Configuração
FPCSR[0:6] Exceção mascara todos os 1 (todas as exceções mascaradas)
FPCSR[7] Reservado − 0
FPCSR[8:9] Controle de precisão – 10B (precisão dupla)
FPCSR[10:11] Controle de arredondamento − 0 (arredondar para o mais próximo)
FPCSR[12] Controle de infinito − 0 (não usado)

Um receptor da chamada que modifica qualquer um dos campos dentro do FPCSR deve restaurá-los antes de retornar ao chamador. Além disso, um chamador que modificou qualquer um desses campos deve restaurá-los para seus valores padrão antes de invocar um receptor da chamada, a menos que, por contrato, o receptor espere os valores modificados.

Há duas exceções às regras sobre a não volatilidade dos sinalizadores de controle:

  • Nas funções em que a finalidade documentada 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, por meio de análise de programas inteiros.

MXCSR

O estado de registro também inclui MXCSR. A convenção de chamada divide esse registro em uma parte volátil e uma parte não volátil. A parte volátil consiste nos seis sinalizadores de status, em MXCSR[0:5], enquanto o restante do registro, MXCSR[6:15], é considerado não volátil.

A parte não volátil é definida como os seguintes valores padrão no início da execução do programa:

Register[bits] Configuração
MXCSR[6] Desnormalizados são zeros − 0
MXCSR[7:12] Exceção mascara todos os 1 (todas as exceções mascaradas)
MXCSR[13:14] Controle de arredondamento − 0 (arredondar para o mais próximo)
MXCSR[15] Liberar para zero para fluxo de estouro negativo − 0 (desativado)

Um receptor da chamada que modifica qualquer um dos campos não voláteis dentro do MXCSR deve restaurá-los antes de retornar ao chamador. Além disso, um chamador que modificou qualquer um desses campos deve restaurá-los para seus valores padrão antes de invocar um receptor da chamada, a menos que, por contrato, o receptor espere os valores modificados.

Há duas exceções às regras sobre a não volatilidade dos sinalizadores de controle:

  • Nas funções em que a finalidade documentada 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, por meio de análise de programas inteiros.

Não faça nenhuma suposição sobre o estado da parte volátil do registro MXCSR em um limite de função, a menos que a documentação da função a descreva explicitamente.

setjmp/longjmp

Quando você inclui setjmpex.h ou setjmp.h, todas as chamadas para setjmp ou longjmp resultam em um desenrolamento que invoca destruidores e chamadas __finally. Esse comportamento se difere de x86, onde incluir setjmp.h resulta em cláusulas __finally e destruidores que não são invocados.

Uma chamada para setjmp preserva o ponteiro de pilha atual, registros não voláteis e registros MXCSR. Chama para longjmp retornam ao local de chamada setjmp mais recente e redefine o ponteiro de pilha, os registros não voláteis e os registros MXCSR, de volta ao estado, como preservados pela chamada mais recente setjmp.

Confira também

Convenções de software x64