Visão geral das convenções ABI x64
Este tópico descreve a ABI (interface binária de aplicativo) básica para x64, a extensão de 64 bits para a arquitetura x86. Ele aborda tópicos como a convenção de chamada, layout de tipo, uso de pilha e registro e muito mais.
convenções de chamada x64
Duas diferenças importantes entre x86 e x64 são:
- Funcionalidade de endereçamento de 64 bits
- Dezesseis registros de 64 bits para uso geral.
Considerando o conjunto de registros expandido, o x64 usa a convenção de chamada __fastcall e um modelo de tratamento de exceções baseado em RISC.
A convenção __fastcall
usa registros para os quatro primeiros argumentos e o registro de ativação para passar mais argumentos. Para obter detalhes sobre a convenção de chamada x64, incluindo uso de registro, parâmetros de pilha, valores retornados e desenrolamento de pilha, confira a convenção de chamada x64.
Para obter mais informações sobre a convenção de chamada __vectorcall
, veja __vectorcall
.
Habilitar a otimização do compilador x64
A seguinte opção do compilador ajuda você a otimizar seu aplicativo para x64:
Tipo x64 e layout de armazenamento
Esta seção descreve o armazenamento de tipos de dados para a arquitetura x64.
Tipos escalares
Embora seja possível acessar dados com qualquer alinhamento, alinhe os dados no limite natural deles ou um múltiplo do limite natural deles, para evitar perda de desempenho. Enumerações são inteiros constantes e são tratados como inteiros de 32 bits. A tabela a seguir descreve a definição de tipo e o armazenamento recomendado para dados, pois se refere ao alinhamento usando os seguintes valores de alinhamento:
- Byte – 8 bits
- Palavra – 16 bits
- Doubleword – 32 bits
- Quadword – 64 bits
- Octaword – 128 bits
Tipo escalar | Tipos de dados do C | Tamanho de armazenamento (em bytes) | Alinhamento recomendado |
---|---|---|---|
INT8 |
char |
1 | Byte |
UINT8 |
unsigned char |
1 | Byte |
INT16 |
short |
2 | Word |
UINT16 |
unsigned short |
2 | Word |
INT32 |
int , long |
4 | Doubleword |
UINT32 |
unsigned int , unsigned long |
4 | Doubleword |
INT64 |
__int64 |
8 | Quadword |
UINT64 |
unsigned __int64 |
8 | Quadword |
FP32 (precisão simples) |
float |
4 | Doubleword |
FP64 (precisão dupla) |
double |
8 | Quadword |
POINTER |
* | 8 | Quadword |
__m64 |
struct __m64 |
8 | Quadword |
__m128 |
struct __m128 |
16 | Octaword |
Layout de união e agregação x64
Outros tipos, como matrizes, structs e uniões, têm requisitos de alinhamento mais rigorosos que garantem a agregação consistente e a recuperação de dados e armazenamento de união. Aqui estão as definições para matriz, estrutura e união:
Array
Contém um grupo ordenado de objetos de dados adjacentes. Cada objeto é chamado de elemento. Todos os elementos dentro de uma matriz têm o mesmo tamanho e tipo de dados.
Estrutura
Contém um grupo ordenado de objetos de dados. Ao contrário dos elementos de uma matriz, os membros de uma estrutura podem ter tipos e tamanhos de dados diferentes.
Union
Um objeto que contém qualquer um dos membros nomeados de um conjunto. Os membros do conjunto nomeado podem ser de qualquer tipo. O armazenamento alocado para uma união é igual ao armazenamento necessário para o maior membro dessa união, além de qualquer preenchimento necessário para alinhamento.
A tabela a seguir mostra o alinhamento fortemente recomendado para os membros escalares de uniões e estruturas.
Tipo escalar | Tipo de dados em C++ | Alinhamento Necessário |
---|---|---|
INT8 |
char |
Byte |
UINT8 |
unsigned char |
Byte |
INT16 |
short |
Word |
UINT16 |
unsigned short |
Word |
INT32 |
int , long |
Doubleword |
UINT32 |
unsigned int , unsigned long |
Doubleword |
INT64 |
__int64 |
Quadword |
UINT64 |
unsigned __int64 |
Quadword |
FP32 (precisão simples) |
float |
Doubleword |
FP64 (precisão dupla) |
double |
Quadword |
POINTER |
* | Quadword |
__m64 |
struct __m64 |
Quadword |
__m128 |
struct __m128 |
Octaword |
As seguintes regras de alinhamento agregado se aplicam:
O alinhamento de uma matriz é o mesmo que o alinhamento de um dos elementos da matriz.
O alinhamento do início de uma estrutura ou de uma união é o alinhamento máximo de qualquer membro individual. Cada membro dentro da estrutura ou união precisa ser colocado no alinhamento adequado, conforme definido na tabela anterior, o que pode exigir preenchimento interno implícito, dependendo do membro anterior.
O tamanho da estrutura precisa ser um múltiplo integral do alinhamento dele, o que pode exigir preenchimento após o último membro. Como estruturas e uniões podem ser agrupadas em matrizes, cada elemento de matriz de uma estrutura ou união precisa começar e terminar no alinhamento adequado determinado anteriormente.
É possível alinhar dados de maneira a exceder os requisitos de alinhamento, desde que as regras anteriores sejam mantidas.
Um compilador individual pode ajustar o empacotamento de uma estrutura por motivos de tamanho. Por exemplo, /Zp (Alinhamento de membro struct) permite ajustar o empacotamento de estruturas.
Exemplos de alinhamento de estrutura x64
Os quatro exemplos a seguir declaram uma estrutura ou união alinhada, e as figuras correspondentes ilustram o layout dessa estrutura ou união na memória. Cada coluna em uma figura representa um byte de memória, e o número na coluna indica o deslocamento desse byte. O nome na segunda linha de cada figura corresponde ao nome de uma variável na declaração. As colunas sombreadas indicam o preenchimento necessário para alcançar o alinhamento especificado.
Exemplo 1
// Total size = 2 bytes, alignment = 2 bytes (word).
_declspec(align(2)) struct {
short a; // +0; size = 2 bytes
}
Exemplo 2
// Total size = 24 bytes, alignment = 8 bytes (quadword).
_declspec(align(8)) struct {
int a; // +0; size = 4 bytes
double b; // +8; size = 8 bytes
short c; // +16; size = 2 bytes
}
Exemplo 3
// Total size = 12 bytes, alignment = 4 bytes (doubleword).
_declspec(align(4)) struct {
char a; // +0; size = 1 byte
short b; // +2; size = 2 bytes
char c; // +4; size = 1 byte
int d; // +8; size = 4 bytes
}
Exemplo 4
// Total size = 8 bytes, alignment = 8 bytes (quadword).
_declspec(align(8)) union {
char *p; // +0; size = 8 bytes
short s; // +0; size = 2 bytes
long l; // +0; size = 4 bytes
}
Campos de bits
Os campos de bits de estrutura são limitados a 64 bits e podem ser do tipo int, int sem sinal, int64 ou int64 sem sinal. Campos de bits que cruzam o limite de tipo ignorarão bits para alinhar o campo de bits ao próximo alinhamento de tipo. Por exemplo, os campos de bits de inteiro podem não cruzar um limite de 32 bits.
Conflitos com o compilador x86
Os tipos de dados maiores que 4 bytes não são alinhados automaticamente na pilha quando você usa o compilador x86 para compilar um aplicativo. Como a arquitetura do compilador x86 é uma pilha alinhada de 4 bytes, qualquer coisa maior que 4 bytes, por exemplo, um inteiro de 64 bits, não pode ser alinhada automaticamente a um endereço de 8 bytes.
Trabalhar usando dados não atribuídos tem duas implicações.
Pode levar mais tempo para acessar locais não atribuídos do que é necessário para acessar locais alinhados.
Locais não atribuídos não podem ser usados em operações interligadas.
Se você precisar de um alinhamento mais estrito, use __declspec(align(N))
em suas declarações de variável. Isso faz com que o compilador alinhe dinamicamente a pilha para atender às suas especificações. No entanto, ajustar dinamicamente a pilha em tempo de execução pode causar uma execução mais lenta do aplicativo.
Uso do registro x64
A arquitetura x64 sustenta 16 registros de uso geral (de agora em diante, chamados de registros de inteiros), bem como para 16 registros XMM/YMM disponíveis para uso de ponto flutuante. Os registros voláteis são registros a partir do zero presumidos pelo chamador para serem destruídos em uma chamada. Os registros não voláteis são obrigados a manter seus valores em uma chamada de função e devem ser salvos pelo receptor da chamada se usados.
Registrar volatilidade e preservação
A tabela a seguir descreve como cada registro é usado nas chamadas de função:
Registrar-se | Status | Usar |
---|---|---|
RAX | Volátil | Registro de valores retornados |
RCX | Volátil | Primeiro argumento inteiro |
RDX | Volátil | Segundo argumento inteiro |
R8 | Volátil | Terceiro argumento inteiro |
R9 | Volátil | Quarto argumento inteiro |
R10, R11 | Volátil | Deve ser preservado, conforme a necessidade do chamador; usado em instruções syscall/sysret |
R12, R15 | Não volátil | Deve ser preservado pelo receptor da chamada |
RDI | Não volátil | Deve ser preservado pelo receptor da chamada |
RSI | Não volátil | Deve ser preservado pelo receptor da chamada |
RBX | Não volátil | Deve ser preservado pelo receptor da chamada |
RBP | Não volátil | Pode ser usado como um ponteiro de quadro; deve ser preservado pelo receptor da chamada |
RSP | Não volátil | Ponteiro de pilha |
XMM0, YMM0 | Volátil | Primeiro argumento FP; primeiro argumento de tipo vetorial quando __vectorcall for usado |
XMM1, YMM1 | Volátil | Segundo argumento FP; segundo argumento de tipo vetorial quando __vectorcall for usado |
XMM2, YMM2 | Volátil | Terceiro argumento FP; terceiro argumento de tipo vetorial quando __vectorcall for usado |
XMM3, YMM3 | Volátil | Quarto argumento FP; quarto argumento de tipo vetorial quando __vectorcall for usado |
XMM4, YMM4 | Volátil | Deve ser preservado conforme necessário pelo chamador; quinto argumento de tipo vetorial quando __vectorcall for usado |
XMM5, YMM5 | Volátil | Deve ser preservado conforme necessário pelo chamador; sexto argumento de tipo vetorial quando __vectorcall for usado |
XMM6:XMM15, YMM6:YMM15 | Não volátil (XMM), Volátil (metade superior de YMM) | Precisa ser preservado pelo computador chamado. Os registros YMM devem ser preservados conforme necessário pelo chamador. |
Na saída da função e na entrada da função para chamadas da Biblioteca de Runtime C e chamadas do sistema Windows, espera-se que o sinalizador de direção no registro de sinalizadores de CPU seja limpo.
Uso da pilha
Para obter detalhes sobre alocação de pilha, alinhamento, tipos de função e registros de ativação no x64, confira Uso da pilha x64.
Prólogo e epílogo
Cada função que aloca espaço de pilha, chama outras funções, salva registros não voláteis ou usa tratamento de exceção precisa ter uma caixa de diálogo cujos limites de endereço são descritos nos dados de desenrolamento associados à respectiva entrada de tabela de funções, bem como epílogos em cada saída para uma função. Para obter detalhes sobre o código de prólogo e de epílogo necessários em x64, confira Prólogo e epílogo x64.
Tratamento de exceções x64
Para obter informações sobre as convenções e estruturas de dados usadas para implementar o tratamento de exceções estruturados e o comportamento de tratamento de exceção C++ no x64, consulte o tratamento de exceção x64.
Intrínsecos e assembly embutido
Uma das restrições para o compilador x64 não é um suporte de assembler embutido. Isso significa que as funções que não podem ser gravadas em C ou C++ precisarão ser gravadas como sub-rotinas ou como funções intrínsecas compatíveis com o compilador. Determinadas funções são sensíveis ao desempenho, enquanto outras não. As funções sensíveis ao desempenho devem ser implementadas como funções intrínsecas.
Os intrínsecos compatíveis com o compilador são descritos Intrínsecos do compilador.
Formato de imagem x64
O formato de imagem executável x64 é PE32+. As imagens executáveis (DLLs e EXEs) são restritas a um tamanho máximo de 2 gigabytes, portanto, o endereçamento relativo com um deslocamento de 32 bits pode ser usado para lidar com os dados de imagem estáticos. Esses dados incluem a tabela de endereços de importação, constantes de cadeia de caracteres, dados globais estáticos e assim por diante.