Visão geral das convenções ARM64EC ABI

ARM64EC é uma interface binária de aplicativo (ABI) que permite que binários ARM64 sejam executados nativamente e de forma interoperável com código x64. Especificamente, o ABI ARM64EC segue as convenções de software x64, tais como a convenção de chamada, uso de pilha e alinhamento de dados, tornando o código ARM64EC interoperável com o código x64. O sistema operacional emula a parte x64 do binário. (O EC no ARM64EC significa emulação compatível.)

Para obter mais informações sobre as ABIs x64 e ARM64, consulte Visão geral das convenções ABI x64 e Visão geral das convenções ABI ARM64.

ARM64EC não resolve as diferenças do modelo de memória entre arquiteturas baseadas em x64 e ARM. Para obter mais informações, consulte Problemas comuns de migração do Microsoft C++ ARM.

Definições

  • ARM64 - O fluxo de código para processos ARM64 que contém código ARM64 tradicional.
  • ARM64EC - O fluxo de código que utiliza um subconjunto do conjunto de registros ARM64 para fornecer interoperabilidade com código x64.

Mapeamento de registos

Os processos x64 podem ter threads executando ARM64EC código. Portanto, é sempre possível recuperar um contexto de registro x64, a ARM64EC usa um subconjunto dos registos principais ARM64 que correspondem 1:1 aos registros x64 emulados. É importante ressaltar que ARM64EC nunca usa registradores fora desse subconjunto, exceto para ler o endereço TEB (Thread Environment Block) de x18.

Os processos ARM64 nativos não devem regredir no desempenho quando algumas ou muitas funções são recompiladas como ARM64EC. Para manter o desempenho, a ABI segue estes princípios:

  • O subconjunto de registro ARM64EC inclui todos os registros que fazem parte da convenção de chamada de função ARM64.

  • A convenção de chamada ARM64EC mapeia diretamente para a convenção de chamada ARM64.

Rotinas auxiliares especiais, como __chkstk_arm64ec, usam convenções de chamadas personalizadas e registadores. Estes registos estão também incluídos no subconjunto ARM64EC de registos.

Mapeamento de registro para registros inteiros

ARM64EC registo Registo x64 ARM64EC convenção de chamada Convenção de chamada ARM64 Convenção de chamada x64
x0 rcx volátil volátil volátil
x1 rdx volátil volátil volátil
x2 r8 volátil volátil volátil
x3 r9 volátil volátil volátil
x4 r10 volátil volátil volátil
x5 r11 volátil volátil volátil
x6 mm1 (baixo 64 bits de registro x87 R1 ) volátil volátil volátil
x7 mm2 (baixo 64 bits de registro x87 R2 ) volátil volátil volátil
x8 rax volátil volátil volátil
x9 mm3 (baixo 64 bits de registro x87 R3 ) volátil volátil volátil
x10 mm4 (baixo 64 bits de registro x87 R4 ) volátil volátil volátil
x11 mm5 (baixo 64 bits de registro x87 R5 ) volátil volátil volátil
x12 mm6 (baixo 64 bits de registro x87 R6 ) volátil volátil volátil
x13 N/A Não permitido volátil N/A
x14 N/A Não permitido volátil N/A
x15 mm7 (baixo 64 bits de registro x87 R7 ) volátil volátil volátil
x16 Parte alta dos 16 bits de cada um dos registadores x87 R0-R3 volátil(xip0) volátil(xip0) volátil
x17 Parte alta dos 16 bits de cada um dos registadores x87 R4-R7 volátil(xip1) volátil(xip1) volátil
x18 GS.base fixo (TEB) fixo (TEB) fixo (TEB)
x19 r12 não-volátil não-volátil não-volátil
x20 r13 não-volátil não-volátil não-volátil
x21 r14 não-volátil não-volátil não-volátil
x22 r15 não-volátil não-volátil não-volátil
x23 N/A Não permitido não-volátil N/A
x24 N/A Não permitido não-volátil N/A
x25 rsi não-volátil não-volátil não-volátil
x26 rdi não-volátil não-volátil não-volátil
x27 rbx não-volátil não-volátil não-volátil
x28 N/A Não permitido Não permitido N/A
fp rbp não-volátil não-volátil não-volátil
lr mm0 (baixo 64 bits de registro x87 R0 ) ambos ambos ambos
sp rsp não-volátil não-volátil não-volátil
pc rip ponteiro de instrução ponteiro de instrução ponteiro de instrução
PSTATEsubconjunto: N/Z/C/V/SS1, 2 RFLAGS subconjunto: SF/ZF/CF/OF/TF volátil volátil volátil
N/A RFLAGS subconjunto: PF/AF N/A N/A volátil
N/A RFLAGS subconjunto: DF N/A N/A não-volátil

1 Evite ler, escrever ou calcular mapeamentos diretamente entre PSTATE e RFLAGS. Estes bits podem ser utilizados no futuro e estão sujeitos a alterações.

2 A bandeira C de transporte ARM64EC é o inverso da bandeira CF de transporte x64 para operações de subtração. Não há manipulação especial, porque o sinalizador é volátil e, portanto, é descartado durante a transição entre as funções (ARM64EC e x64).

Mapeamento de registro para registros vetoriais

ARM64EC registo Registo x64 ARM64EC convenção de chamada Convenção de chamada ARM64 Convenção de chamada x64
v0-v5 xmm0-xmm5 volátil volátil volátil
v6-v7 xmm6-xmm7 volátil volátil não-volátil
v8-v15 xmm8-xmm15 volátil 1 volátil 1 não-volátil
v16-v31 xmm16-xmm31 Não permitido volátil não permitido (emulador x64 não suporta AVX-512)
FPCR 2 MXCSR[15:6] não-volátil não-volátil não-volátil
FPSR 2 MXCSR[5:0] volátil volátil volátil

1 Estes registos ARM64 são especiais na medida em que os 64 bits inferiores não são voláteis, mas os 64 bits superiores são voláteis. Do ponto de vista de um chamador x64, eles são efetivamente voláteis porque o destinatário descartaria dados.

2 Evite ler, escrever ou calcular mapeamentos diretamente de FPCR e FPSR. Estes bits podem ser utilizados no futuro e estão sujeitos a alterações.

Empacotamento de Estruturas

ARM64EC segue as mesmas regras de empacotamento de estrutura usadas pelo x64 para garantir a interoperabilidade entre o código ARM64EC e o código x64. Para mais informações e exemplos sobre o empacotamento de estruturas x64, consulte Visão geral das convenções ABI x64.

Exceções de ponto flutuante

Para determinar se uma CPU ARM suporta exceções, escreva um valor que permita exceções ao registro FPCR e leia-o novamente. Se a CPU suportar exceções de ponto flutuante, os bits correspondentes às exceções suportadas permanecerão definidos, enquanto a CPU redefinirá os bits para exceções sem suporte.

No ARM64EC, o Windows captura exceções de ponto flutuante do processador e as desabilita no registro FPCR. Isso garante um comportamento consistente em diferentes variantes de processador.

Rotinas ABI auxiliares de emulação

O código ARM64EC e os thunks utilizam funções auxiliares de emulação para fazer a transição entre as funções x64 e ARM64EC.

A tabela a seguir descreve cada rotina especial de ABI e os registros que a ABI usa. As rotinas não modificam os registros preservados listados na coluna ABI. Não devem ser feitas suposições sobre registos não enumerados. No disco, os ponteiros das rotinas de ABI são nulos. No momento do carregamento, o carregador atualiza os ponteiros para apontar para as rotinas do emulador x64.

Nome Descrição ABI
__os_arm64x_dispatch_call_no_redirect Chamado por um thunk de saída para invocar um alvo x64, que pode ser uma função x64 ou uma sequência de avanço rápido x64. A rotina envia por push o endereço de retorno ARM64EC (no LR registro) seguido pelo endereço da instrução que sucede uma blr x16 instrução que invoca o emulador x64. Em seguida, executa a blr x16 instrução valor de retorno em x8 (rax)
__os_arm64x_dispatch_ret Chamado por um thunk de entrada para retornar ao seu chamador x64. Ele remove o endereço de retorno x64 da pilha e invoca o emulador x64 para executar um salto para o endereço N/A
__os_arm64x_check_call Chamado por código ARM64EC com um ponteiro para um thunk de saída e para executar o endereço de destino ARM64EC indireto. O destino ARM64EC é considerado passível de correção e a execução sempre retorna ao chamador com os mesmos dados com os quais foi chamado ou com dados modificados Argumentos:
x9: O endereço de destino
x10: O endereço de saída thunk
x11: O endereço da sequência de avanço rápido

Saída:
x9: Se a função de destino foi desviada, ela contém o endereço da sequência de avanço rápido
x10: O endereço de saída thunk
x11: Se a função foi redirecionada, ela contém o endereço de saída do thunk. Caso contrário, o endereço de destino saltou para

Registos conservados: x0-x8, x15 ().chkstk e ainda q0-q7
__os_arm64x_check_icall Chamado pelo código ARM64EC, com um ponteiro para uma função de saída, para gerir um salto para um endereço de destino que seja x64 ou ARM64EC. Se o destino for x64 e o código x64 não tiver sido corrigido, a rotina definirá o registro de endereço de destino. Ele aponta para a versão ARM64EC da função, se existir. Caso contrário, ele define o registro para apontar para o thunk de saída que faz a transição para o destino x64. Em seguida, retorna ao código de chamada ARM64EC, que então salta para o endereço no registo. Essa rotina é uma versão não otimizada do __os_arm64x_check_call, onde o endereço de destino não é conhecido no momento da compilação

Usado num ponto de chamada de uma chamada indireta
Argumentos:
x9: O endereço de destino
x10: O endereço de saída thunk
x11: O endereço da sequência de avanço rápido

Saída:
x9: Se a função de destino foi desviada, ela contém o endereço da sequência de avanço rápido
x10: O endereço de saída thunk
x11: Se a função foi redirecionada, ela contém o endereço de saída do thunk. Caso contrário, o endereço de destino saltou para

Registos conservados: x0-x8, x15 (chkstk), e q0-q7
__os_arm64x_check_icall_cfg O mesmo que __os_arm64x_check_icall , mas também verifica se o endereço especificado é um destino de chamada indireta válido do Gráfico de Fluxo de Controle Argumentos:
x10: O endereço do thunk de saída
x11: O endereço da função de destino

Saída:
x9: Se o destino for x64, o endereço da função. Caso contrário, indefinido
x10: O endereço do thunk de saída
x11: Se o destino for x64, ele contém o endereço do thunk de saída. Caso contrário, o endereço da função

Registos conservados: x0-x8, x15 (chkstk), e q0-q7
__os_arm64x_get_x64_information Obtém a parte solicitada do contexto de registo x64 em tempo real _Function_class_(ARM64X_GET_X64_INFORMATION) NTSTATUS LdrpGetX64Information(_In_ ULONG Type, _Out_ PVOID Output, _In_ PVOID ExtraInfo)
__os_arm64x_set_x64_information Estabelece a parte solicitada do contexto de registo x64 ativo _Function_class_(ARM64X_SET_X64_INFORMATION) NTSTATUS LdrpSetX64Information(_In_ ULONG Type,_In_ PVOID Input, _In_ PVOID ExtraInfo)
__os_arm64x_x64_jump Usado em mecanismos de ajuste sem assinatura e outros thunks que encaminham diretamente (jmp) uma chamada para outra função que pode ter qualquer assinatura, adiando a potencial aplicação da lógica correta para o alvo real. Argumentos:
x9: alvo para saltar

Todos os registos de parâmetros preservados (encaminhados)

Tunks

Thunks são os mecanismos de baixo nível para suportar funções ARM64EC e x64 chamando umas às outras. Existem dois tipos: thunks de entrada para entrar em funções ARM64EC e thunks de saída para chamar funções x64.

Thunk de entrada e thunks de entrada intrínsecos: chamada de função de x64 para ARM64EC

Para suportar chamadores x64 quando uma função C/C++ é compilada como ARM64EC, o conjunto de ferramentas gera um único thunk de entrada que consiste em código de máquina ARM64EC. Os intrínsecos têm um "thunk" de entrada próprio. Todas as outras funções partilham uma entrada thunk com todas as funções que têm convenção de chamada, parâmetros e tipo de retorno compatíveis. O conteúdo do thunk depende da convenção de chamada da função C/C++.

Além de manipular parâmetros e o endereço de retorno, o thunk faz a ponte entre as diferenças de volatilidade entre os registos de vetores ARM64EC e x64 resultantes do mapeamento de registo vetorial ARM64EC:

ARM64EC registo Registo x64 ARM64EC convenção de chamada Convenção de chamada ARM64 Convenção de chamada x64
v6-v15 xmm6-xmm15 volátil, mas salvo/restaurado na entrada thunk (x64 a ARM64EC) volátil ou parcialmente volátil nos 64 bits superiores não-volátil

A entrada thunk executa as seguintes ações:

Número do parâmetro Uso da pilha
0-4 Armazena ARM64EC v6 e v7 no espaço de armazenamento reservado pelo chamador

Como o destinatário é ARM64EC, que não possui a noção de um espaço de armazenamento designado, os valores armazenados não são sobrescritos.

Aloca 128 bytes extra na pilha e armazena ARM64EC de v8 a v15.
5-8 x4 = 5º parâmetro da pilha
x5 = 6º parâmetro da pilha
x6 = 7º parâmetro da pilha de memória
x7 = 8º parâmetro da pilha

Se o parâmetro for SIMD, os registos v4-v7 serão usados.
9+ Aloca AlignUp(NumParams - 8 , 2) * 8 bytes na pilha. *

Copia o 9.º parâmetro e os restantes para esta área

* Alinhar o valor a um número par garante que a pilha permanece alinhada a 16 bytes

Se a função aceitar um parâmetro inteiro de 32 bits, o thunk terá permissão para enviar apenas 32 bits em vez dos 64 bits completos do registro pai.

Em seguida, o thunk usa uma instrução ARM64 bl para chamar a função ARM64EC. Depois que a função retorna, o thunk:

  1. Desfaz todas as alocações de pilha
  2. Chama o auxiliar do emulador para exibir o endereço de retorno x64 e retomar a __os_arm64x_dispatch_ret emulação x64.

Exit thunk: função de transição de ARM64EC para chamada de função x64

Para cada chamada que uma função ARM64EC C/C++ faz para o código x64 potencial, a cadeia de ferramentas MSVC gera um thunk de saída. O conteúdo do thunk depende dos parâmetros do destinatário x64 e se o destinatário está usando a convenção de chamada padrão ou __vectorcall. O compilador obtém essas informações de uma declaração de função para o destinatário.

Primeiro, o thunk envia por push o endereço de retorno que está no registro ARM64EC lr e um valor fictício de 8 bytes para garantir que a pilha esteja alinhada a 16 bytes. Em segundo lugar, o thunk lida com os parâmetros:

Número do parâmetro Uso da pilha
0-4 Aloca 32 bytes de espaço doméstico na pilha
5-8 Aloca AlignUp(NumParams - 4, 2) * 8 mais bytes mais acima na pilha. *

Copia o 5º parâmetro e todos os subsequentes de ARM64EC x4-x7 para este espaço extra.
9+ Copia o nono parâmetro e os restantes para o espaço extra

* Alinhar o valor a um número par garante que a pilha permanece alinhada a 16 bytes.

Em terceiro lugar, o thunk chama o auxiliar do emulador __os_arm64x_dispatch_call_no_redirect para invocar o emulador x64 e executar a função x64. A chamada deve ser uma blr x16 instrução (convenientemente, x16 é um registro volátil). Uma blr x16 instrução é necessária porque o emulador x64 analisa essa instrução como uma dica.

A função x64 geralmente tenta retornar ao auxiliar do emulador usando uma instrução x64 ret . Neste ponto, o emulador x64 deteta que está no código ARM64EC. Em seguida, ele lê a dica anterior de 4 bytes que coincide com a instrução ARM64 blr x16 . Como esta dica indica que o endereço de retorno está neste auxiliar, o emulador direciona-se diretamente para este endereço.

A função x64 tem permissão para retornar ao auxiliar do emulador usando qualquer instrução de ramificação, incluindo x64 jmp e call. O emulador também lida com esses cenários.

Quando o auxiliar retorna ao thunk, este:

  1. Desfaz qualquer alocação de memória
  2. Liberta o registo ARM64EC lr
  3. Executa uma instrução ARM64 ret lr .

ARM64EC decorar nomes de funções

Um nome de função ARM64EC tem uma decoração secundária aplicada após qualquer decoração específica do idioma. Para funções com ligação C (quer sejam compiladas como C ou usando extern "C"), um # é adicionado ao início do nome. Para funções decoradas em C++, uma $$h tag é inserida no nome.

foo         => #foo
?foo@@YAHXZ => ?foo@@$$hYAHXZ

__vectorcall

A cadeia de ferramentas ARM64EC atualmente não suporta __vectorcall. O compilador emite um erro quando deteta o uso de __vectorcall com ARM64EC.

Ver também

Entendendo ARM64EC ABI e código de montagem
Problemas comuns de migração do Microsoft C++ ARM
Nomes decorados