Partilhar via


Visão geral das convenções ABI ARM64EC

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

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

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

Definições

  • ARM64 – o fluxo de código para processos ARM64 que contém o 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.

Registrar mapeamento

Os processos x64 podem ter threads executando código ARM64EC. Portanto, é sempre possível recuperar um contexto de registro x64. O ARM64EC usa um subconjunto dos registros de núcleo ARM64 que são mapeados na relação 1:1 para registros x64 emulados. É importante ressaltar que o ARM64EC nunca usa registros fora desse subconjunto, exceto para ler o endereço x18TEB (Thread Environment Block).

Os processos nativos do ARM64 não deverão apresentar perda de desempenho quando algumas ou muitas funções forem 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 é mapeada diretamente para a convenção de chamada ARM64.

Rotinas auxiliares especiais, como __chkstk_arm64ec, usam registros e convenções de chamadas personalizados. Esses registros também estão incluídos no subconjunto de registros do ARM64EC.

Registrar mapeamento para registros inteiros

Registro ARM64EC Registro x64 Convenção de chamada ARM64EC Convenção de chamada ARM64 Convenção de chamada x64
x0 rcx volatile volatile volatile
x1 rdx volatile volatile volatile
x2 r8 volatile volatile volatile
x3 r9 volatile volatile volatile
x4 r10 volatile volatile volatile
x5 r11 volatile volatile volatile
x6 mm1 (os 64 bits inferiores do registro x87 R1) volatile volatile volatile
x7 mm2 (os 64 bits inferiores do registro x87 R2) volatile volatile volatile
x8 rax volatile volatile volatile
x9 mm3 (os 64 bits inferiores do registro x87 R3) volatile volatile volatile
x10 mm4 (os 64 bits inferiores do registro x87 R4) volatile volatile volatile
x11 mm5 (os 64 bits inferiores do registro x87 R5) volatile volatile volatile
x12 mm6 (os 64 bits inferiores do registro x87 R6) volatile volatile volatile
x13 N/D sem permissão volatile N/D
x14 N/D sem permissão volatile N/D
x15 mm7 (os 64 bits inferiores do registro x87 R7) volatile volatile volatile
x16 Os 16 bits superiores de cada um dos registros x87 R0-R3 volátil(xip0) volátil(xip0) volatile
x17 Os 16 bits superiores de cada um dos registros x87 R4-R7 volátil(xip1) volátil(xip1) volatile
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/D sem permissão não volátil N/D
x24 N/D sem permissão não volátil N/D
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/D sem permissão sem permissão N/D
fp rbp não volátil não volátil não volátil
lr mm0 (os 64 bits inferiores do 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
PSTATE subconjunto: N/Z/C/V/SS 1, 2 RFLAGS subconjunto: SF/ZF/CF/OF/TF volatile volatile volatile
N/D RFLAGS subconjunto: PF/AF N/D N/D volatile
N/D RFLAGS subconjunto: DF N/D N/D não volátil

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

2 O sinalizador de transporte C ARM64EC é o inverso do sinalizador de transporte x64 CF para operações de subtração. Não há tratamento especial, pois o sinalizador é volátil e, portanto, é destruído ao fazer a transição entre funções (ARM64EC e x64).

Registrar mapeamento para registros de vetor

Registro ARM64EC Registro x64 Convenção de chamada ARM64EC Convenção de chamada ARM64 Convenção de chamada x64
v0-v5 xmm0-xmm5 volatile volatile volatile
v6-v7 xmm6-xmm7 volatile volatile não volátil
v8-v15 xmm8-xmm15 volátil 1 volátil 1 não volátil
v16-v31 xmm16-xmm31 sem permissão volatile sem permissão (o emulador x64 não dá suporte a AVX-512)
FPCR 2 MXCSR[15:6] não volátil não volátil não volátil
FPSR 2 MXCSR[5:0] volatile volatile volatile

1 Esses registros ARM64 são especiais porque 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 computador chamado destruiria os dados.

2 Evite ler, gravar ou calcular mapeamentos de computação de FPCR e FPSR. Esses bits podem ser usados no futuro e estão sujeitos a alterações.

Empacotamento de struct

ARM64EC segue as mesmas regras de empacotamento de struct usadas para x64 para garantir a interoperabilidade entre o código ARM64EC e o código x64. Para obter mais informações e exemplos de empacotamento de struct x64, confira Visão geral das convenções do ABI x64.

Rotinas auxiliares de emulação da ABI

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

A tabela a seguir descreve cada rotina especial da ABI e os registros que a ABI usa. As rotinas não modificam os registros preservados listados na coluna da ABI. Nenhuma suposição deve ser feita sobre registros não listados. Em disco, os ponteiros de rotina da ABI são nulos. No tempo de 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 chamar um destino x64 (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 registro LR) seguido pelo endereço da instrução que sucede a uma instrução blr x16 que, por sua vez, invoca o emulador x64. Em seguida, ela executa a instrução blr x16 valor retornado em x8 (rax)
__os_arm64x_dispatch_ret Chamado por um thunk de entrada para retornar ao respectivo chamador x64. Ele remove o endereço de retorno x64 mais recente da pilha e invoca o emulador x64 para saltar até ele N/D
__os_arm64x_check_call Chamado pelo código ARM64EC com um ponteiro para um thunk de saída e o endereço de destino ARM64EC indireto a ser executado. O destino ARM64EC é considerado como qualificado para aplicação de patches, e a execução sempre retorna ao chamador com os mesmos dados com os qual ele foi chamado ou dados modificados Argumentos:
x9: o endereço de destino
x10: o endereço do thunk de saída
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 do thunk de saída
x11: se a função foi desviada, ela contém o endereço thunk de saída. Caso contrário, o endereço de destino saltou para

Registros preservados: x0-x8, x15 (chkstk). e q0-q7
__os_arm64x_check_icall Chamado pelo código ARM64EC, com um ponteiro para um thunk de saída, para manipular 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 do endereço de destino. Ele aponta para a versão ARM64EC da função, se houver. 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, ele retorna para o código ARM64EC de chamada que, em seguida, salta para o endereço no registro. Essa rotina é uma versão não otimizada de __os_arm64x_check_call, em que o endereço de destino não é conhecido em tempo de compilação

Usada em um site de chamada de uma chamada indireta
Argumentos:
x9: o endereço de destino
x10: o endereço do thunk de saída
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 do thunk de saída
x11: se a função foi desviada, ela contém o endereço thunk de saída. Caso contrário, o endereço de destino saltou para

Registros preservados: 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 indireto válido do grafo 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 para a função. Caso contrário, é indefinido
x10: o endereço do thunk de saída
x11: se o destino for x64, conterá o endereço do thunk de saída. Caso contrário, o endereço da função

Registros preservados: x0-x8, x15 (chkstk) e q0-q7
__os_arm64x_get_x64_information Obtém a parte solicitada do contexto de registro x64 ativo _Function_class_(ARM64X_GET_X64_INFORMATION) NTSTATUS LdrpGetX64Information(_In_ ULONG Type, _Out_ PVOID Output, _In_ PVOID ExtraInfo)
__os_arm64x_set_x64_information Define a parte solicitada do contexto de registro x64 ativo _Function_class_(ARM64X_SET_X64_INFORMATION) NTSTATUS LdrpSetX64Information(_In_ ULONG Type,_In_ PVOID Input, _In_ PVOID ExtraInfo)
__os_arm64x_x64_jump Usado no ajustador sem assinatura e em outros thunks que fazem encaminhamento direto (jmp) de uma chamada para outra função que pode ter qualquer assinatura, adiando a aplicação potencial do thunk direito para o destino real Argumentos:
x9: destino para o qual saltar

Todos os registros de parâmetro preservados (encaminhados)

Thunks

Thunks são os mecanismos de nível baixo para dar suporte a funções ARM64EC e x64 chamando umas às outras. Há dois tipos: thunks de entrada para inserir funções ARM64EC e thunks de saída para chamar funções x64.

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

Para dar suporte a chamadores x64 quando uma função C/C++ é compilada como ARM64EC, a cadeia de ferramentas gera apenas um thunk de entrada que consiste no código do computador ARM64EC. Funções intrínsecas têm um thunk de entrada próprio. Todas as outras funções compartilham um thunk de entrada com todas as funções que têm uma convenção de chamada, parâmetros e um tipo de retorno correspondentes. 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 concilia as diferenças de volatilidade entre registros de vetores ARM64EC e x64 causados pelo mapeamento de registro de vetor ARM64EC:

Registro ARM64EC Registro x64 Convenção de chamada ARM64EC Convenção de chamada ARM64 Convenção de chamada x64
v6-v15 xmm6-xmm15 volátil, mas salvo/restaurado no thunk de entrada (x64 para ARM64EC) 64 bits superiores voláteis ou parcialmente voláteis não volátil

O thunk de entrada executa as seguintes ações:

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

Como o chamador é ARM64EC, que não inclui um espaço de despejo, os valores armazenados não são sobrescritos.

Aloca mais 128 bytes na pilha e armazena o v8 ARM64EC por meio de v15.
5-8 x4 = 5º parâmetro da pilha
x5 = 6º parâmetro da pilha
x6 = 7º parâmetro da pilha
x7 = 8º parâmetro da pilha

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

Copia o 9º parâmetro e os parâmetros restantes para essa área

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

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

Em seguida, o thunk usa uma instrução bl do ARM64 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 __os_arm64x_dispatch_ret para remover o endereço de retorno x64 mais recente da pilha e retomar a emulação x64.

Thunk de saída: chamada de função ARM64EC para x64

Para cada chamada que uma função ARM64EC C/C++ faz para código x64 em potencial, a cadeia de ferramentas MSVC gera um thunk de saída. O conteúdo do thunk depende dos parâmetros do chamador x64 e se o receptor 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 computador chamado.

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

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

Copia o 5º parâmetro e todos os parâmetros subsequentes do x4-x7 do ARM64EC para esse espaço extra
+9 Copia o 9º parâmetro e os parâmetros restantes para o espaço extra

* Alinhar o valor a um número par garante que a pilha permaneça 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 para executar a função x64. A chamada precisa ser uma instrução blr x16 (convenientemente, x16 é um registro volátil). Uma instrução blr x16 é 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 detecta que ele está em código ARM64EC. Em seguida, ele lê a dica anterior de 4 bytes que, por acaso, é a instrução blr x16 do ARM64. Como essa dica infere que o endereço de retorno está nesse auxiliar, o emulador salta diretamente para esse endereço.

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

Quando o auxiliar retorna ao thunk, o thunk:

  1. Desfazer qualquer alocação de pilha
  2. Remove o registro lr do ARM64EC mais recente da pilha
  3. Executa uma instrução ret lr do ARM64.

Decoração do nome da função ARM64EC

Um nome de função ARM64EC tem uma decoração secundária aplicada após qualquer decoração específica da linguagem de programação. Para funções com vinculação C (sejam elas compiladas como C ou usando extern "C"), um # é pré-acrescentado ao nome. Para funções decoradas C++, uma marca $$h é inserida no nome.

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

__vectorcall

A cadeia de ferramentas ARM64EC atualmente não dá suporte a __vectorcall. O compilador emite um erro ao detectar o uso de __vectorcall com ARM64EC.

Confira também

Noções básicas do código de assembly e da ABI ARM64EC
Problemas comuns de migração ARM do Visual C++
Nomes decorados