Compartir a través de


Información general sobre las convenciones ABI de ARM64EC

ARM64EC es una interfaz binaria de aplicación (ABI) que permite que los archivos binarios ARM64 se ejecuten de forma nativa e interoperable con código x64. En concreto, la ABI de ARM64EC sigue las convenciones de software x64, incluida la convención de llamada, el uso de la pila y la alineación de datos, lo que hace que el código ARM64EC y x64 sean interoperables. El sistema operativo emula la parte x64 del binario. (La CE de ARM64EC significa emulation compatible [compatible con la emulación]).

Para obtener más información sobre las ABI x64 y ARM64, consulte Información general sobre las convenciones ABI de x64 e Información general sobre las convenciones ABI de ARM64.

ARM64EC no resuelve las diferencias del modelo de memoria entre las arquitecturas basadas en x64 y ARM. Para obtener más información, consulte Problemas comunes de migración de ARM en Visual C++.

Definiciones

  • ARM64: el flujo de código de los procesos ARM64 que contiene código ARM64 tradicional.
  • ARM64EC: flujo de código que utiliza un subconjunto del conjunto de registros ARM64 para proporcionar interoperabilidad con código x64.

Asignación de registros

Los procesos x64 pueden tener subprocesos que ejecutan código ARM64EC. Por lo tanto, siempre es posible recuperar un contexto de registro x64; ARM64EC usa un subconjunto de los registros principales ARM64 que asignan 1:1 a registros x64 emulados. Lo más importante, ARM64EC nunca usa registros ajenos a este subconjunto, excepto para leer la dirección del bloque de entorno de subprocesos (TEB) de x18.

Los procesos ARM64 nativos no deberían retroceder en rendimiento cuando algunas o muchas funciones se vuelven a compilar como ARM64EC. Para mantener el rendimiento, la ABI sigue estos principios:

  • El subconjunto de registros ARM64EC incluye todos los registros que forman parte de la convención de llamada de las funciones ARM64.

  • La convención de llamada ARM64EC se asigna directamente a la convención de llamada ARM64.

Las rutinas auxiliares especiales, como __chkstk_arm64ec, usan convenciones de llamada y registros personalizados. Estos registros también se incluyen en el subconjunto ARM64EC de registros.

Asignación de registros de enteros

Registro ARM64EC Registro x64 Convención de llamadas ARM64EC Convención de llamadas ARM64 Convención de llamadas 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 (registro R1 bajo de 64 bits de x87) volatile volatile volatile
x7 mm2 (registro R2 bajo de 64 bits de x87) volatile volatile volatile
x8 rax volatile volatile volatile
x9 mm3 (registro R3 bajo de 64 bits de x87) volatile volatile volatile
x10 mm4 (registro R4 bajo de 64 bits de x87) volatile volatile volatile
x11 mm5 (registro R5 bajo de 64 bits de x87) volatile volatile volatile
x12 mm6 (registro R6 bajo de 64 bits de x87) volatile volatile volatile
x13 N/D no permitido volatile N/D
x14 N/D no permitido volatile N/D
x15 mm7 (registro R7 bajo de 64 bits de x87) volatile volatile volatile
x16 16 bits altos de cada uno de los registros R0-R3 x87 volátil(xip0) volátil(xip0) volatile
x17 16 bits altos de cada uno de los registros R4-R7 x87 volátil(xip1) volátil(xip1) volatile
x18 GS.base fijo(TEB) fijo(TEB) fijo(TEB)
x19 r12 no volátil no volátil no volátil
x20 r13 no volátil no volátil no volátil
x21 r14 no volátil no volátil no volátil
x22 r15 no volátil no volátil no volátil
x23 N/D no permitido no volátil N/D
x24 N/D no permitido no volátil N/D
x25 rsi no volátil no volátil no volátil
x26 rdi no volátil no volátil no volátil
x27 rbx no volátil no volátil no volátil
x28 N/D no permitido no permitido N/D
fp rbp no volátil no volátil no volátil
lr mm0 (registro R0 bajo de 64 bits de x87) Ambos Ambos Ambos
sp rsp no volátil no volátil no volátil
pc rip puntero de instrucción puntero de instrucción puntero de instrucción
Subconjunto PSTATE: N/Z/C/V/SS1, 2 Subconjunto RFLAGS: SF/ZF/CF/OF/TF volatile volatile volatile
N/D Subconjunto RFLAGS: PF/AF N/D N/D volatile
N/D Subconjunto RFLAGS: DF N/D N/D no volátil

1 Evite leer, escribir o calcular asignaciones directamente entre PSTATE y RFLAGS. Estos bits se pueden usar en el futuro y están sujetos a cambios.

2 La marca de transporte ARM64EC C es la inversa de la marca de transporte x64 CF para las operaciones de resta. No hay control especial, ya que la marca es volátil y, por tanto, se borra al realizar la transición entre las funciones (ARM64EC y x64).

Asignación de registros vectoriales

Registro ARM64EC Registro x64 Convención de llamadas ARM64EC Convención de llamadas ARM64 Convención de llamadas x64
v0-v5 xmm0-xmm5 volatile volatile volatile
v6-v7 xmm6-xmm7 volatile volatile no volátil
v8-v15 xmm8-xmm15 volátil 1 volátil 1 no volátil
v16-v31 xmm16-xmm31 no permitido volatile no permitido (el emulador x64 no admite AVX-512)
FPCR 2 MXCSR[15:6] no volátil no volátil no volátil
FPSR 2 MXCSR[5:0] volatile volatile volatile

1 Estos registros ARM64 son especiales en cuanto a que los 64 bits inferiores son no volátiles, pero los 64 bits superiores son volátiles. Desde el punto de vista de un autor de llamada x64, son realmente volátiles porque el destinatario de la llamada borraría los datos.

2 Evite leer, escribir o calcular asignaciones directamente de FPCR y FPSR. Estos bits se pueden usar en el futuro y están sujetos a cambios.

Empaquetado de struct

ARM64EC sigue las mismas reglas de empaquetado de estructura que se usan para x64 a fin de garantizar la interoperabilidad entre los códigos ARM64EC y x64. Para obtener más información y ejemplos de empaquetado de estructuras x64, vea Información general de las convenciones ABI de x64.

Rutinas ABI del asistente de emulación

El código ARM64EC y los códigos thunk usan rutinas auxiliares de emulación para realizar la transición entre las funciones x64 y ARM64EC.

En la tabla siguiente se describe cada rutina ABI especial y se registran los usos de ABI. Las rutinas no modifican los registros conservados enumerados en la columna ABI. No se debe realizar ninguna suposición sobre los registros no registrados en la lista. En disco, los punteros de rutina ABI son null. En el tiempo de carga, el cargador actualiza los punteros para que apunten a las rutinas del emulador x64.

Nombre Descripción ABI
__os_arm64x_dispatch_call_no_redirect Llamado por un código thunk de salida para llamar a un destino x64 (ya sea una función x64 o una secuencia de avance rápido x64). La rutina inserta la dirección de retorno ARM64EC (en el registro LR), seguida de la dirección de la instrucción que sucede a una instrucción blr x16 que invoca el emulador x64. A continuación, ejecuta la instrucción blr x16 valor devuelto en x8 (rax)
__os_arm64x_dispatch_ret Llamado por una entrada thunk para volver a su autor de llamada x64. Extrae la dirección de retorno x64 de la pila e invoca al emulador x64 para saltar a ella N/D
__os_arm64x_check_call Llamado por el código ARM64EC con un puntero a un código thunk de salida y la dirección de destino de ARM64EC indirecta que se va a ejecutar. El destino ARM64EC se considera revisable y la ejecución siempre vuelve al autor de la llamada con los mismos datos con los que fue llamado o con datos modificados Argumentos:
x9: dirección de destino
x10: dirección de código thunk de salida
x11: la dirección de secuencia de avance rápido

Out:
x9: Si la función de destino se ha desviado, contiene la dirección de la secuencia de avance rápido
x10: dirección de código thunk de salida
x11: si la función se ha desviado, contiene la dirección de código thunk de salida. De lo contrario, la dirección de destino saltó a

Registros conservados: x0-x8, x15 (chkstk). y q0-q7
__os_arm64x_check_icall Llamado por el código ARM64EC, con un puntero a un código thunk de salida, para controlar un salto a una dirección de destino que sea x64 o ARM64EC. Si el destino es x64 y el código x64 no se ha revisado, la rutina establece el registro de direcciones de destino. Apunta a la versión ARM64EC de la función si existe. De lo contrario, establece el registro para que apunte al código thunk de salida que hace la transición al destino x64. A continuación, vuelve al código ARM64EC que llama, que luego salta a la dirección del registro. Esta rutina es una versión no optimizada de __os_arm64x_check_call, donde la dirección de destino no se conoce en tiempo de compilación

Se usa en un sitio de llamada de una llamada indirecta
Argumentos:
x9: dirección de destino
x10: dirección de código thunk de salida
x11: la dirección de secuencia de avance rápido

Out:
x9: Si la función de destino se ha desviado, contiene la dirección de la secuencia de avance rápido
x10: dirección de código thunk de salida
x11: si la función se ha desviado, contiene la dirección de código thunk de salida. De lo contrario, la dirección de destino saltó a

Registros conservados: x0-x8, x15 (chkstk), y q0-q7
__os_arm64x_check_icall_cfg Igual que __os_arm64x_check_icall, pero también comprueba que la dirección especificada es un destino de llamada indirecta de Graph de flujo de control válido Argumentos:
x10: dirección de código thunk de salida
x11: dirección de la función de destino

Out:
x9: si el destino es x64, la dirección a la función. De lo contrario,indefinido
x10: dirección de código thunk de salida
x11: si el destino es x64, contiene la dirección del código thunk de salida. De lo contrario, la dirección de la función

Registros conservados: x0-x8, x15 (chkstk), y q0-q7
__os_arm64x_get_x64_information Obtiene la parte solicitada del contexto de registro x64 en directo _Function_class_(ARM64X_GET_X64_INFORMATION) NTSTATUS LdrpGetX64Information(_In_ ULONG Type, _Out_ PVOID Output, _In_ PVOID ExtraInfo)
__os_arm64x_set_x64_information Establece la parte solicitada del contexto de registro x64 en directo _Function_class_(ARM64X_SET_X64_INFORMATION) NTSTATUS LdrpSetX64Information(_In_ ULONG Type,_In_ PVOID Input, _In_ PVOID ExtraInfo)
__os_arm64x_x64_jump Se utiliza en el ajustador sin firma y en otros códigos thunk que reenvían directamente (jmp) una llamada a otra función que puede tener cualquier firma, aplazando la posible aplicación del código thunk correcto al destino real Argumentos:
x9: destino al que saltar

Todos los registros de parámetros conservados (reenviados)

Códigos thunk

Los códigos thunk son los mecanismos de nivel inferior para admitir funciones ARM64EC y x64 que se llaman entre sí. Hay dos tipos: códigos thunk de entrada para escribir funciones ARM64EC y códigos thunk de salida para llamar a funciones x64.

Código thunk de entrada y códigos thunk de entrada intrínseca: llamada de función x64 a ARM64EC

Para admitir autores de llamada x64 cuando se compila una función C/C++ como ARM64EC, la cadena de herramientas genera un único código thunk de entrada que consta de código de máquina ARM64EC. Los intrínsecos tienen un código thunk de entrada propio. Todas las demás funciones comparten un código thunk de entrada con todas las funciones que tienen una convención de llamada, unos parámetros y un tipo de valor devuelto coincidentes. El contenido del código thunk depende de la convención de llamada de la función C/C++.

Además de controlar los parámetros y la dirección de retorno, el código thunk puentea las diferencias de volatilidad entre los registros vectoriales ARM64EC y x64 causadas por la asignación de registros vectoriales ARM64EC:

Registro ARM64EC Registro x64 Convención de llamadas ARM64EC Convención de llamadas ARM64 Convención de llamadas x64
v6-v15 xmm6-xmm15 volátil, pero guardado o restaurado en el código thunk de entrada (x64 a ARM64EC) 64 bits superiores volátiles o parcialmente volátiles no volátil

El código thunk de entrada realiza las siguientes acciones:

Número de parámetros Uso de la pila
0-4 Almacena ARM64EC v6 y v7 en el espacio principal asignado por el autor de la llamada

Dado que el destinatario es ARM64EC, que no tiene la noción de un espacio principal, los valores almacenados no se destruyen.

Asigna un extra de 128 bytes en la pila y almacena ARM64EC v8 a través de v15.
5-8 x4 = 5.º parámetro de la pila
x5 = 6.º parámetro de la pila
x6 = 7.º parámetro de la pila
x7 = 8.º parámetro de la pila

Si el parámetro es SIMD, se usan los registros v4-v7 en su lugar
+9 Asigna AlignUp(NumParams - 8 , 2) * 8 bytes en la pila. *

Copia el 9.º parámetro y los restantes en esta área

* Alinear el valor con un número par garantiza que la pila permanece alineada a 16 bytes

Si la función acepta un parámetro entero de 32 bits, el código thunk solo puede insertar 32 bits en lugar de los 64 bits completos del registro primario.

A continuación, el código thunk usa una instrucción ARM64 bl para llamar a la función ARM64EC. Después de que la función vuelva, el código thunk:

  1. Deshace las asignaciones de pila
  2. Llama al asistente del emulador __os_arm64x_dispatch_ret para mostrar la dirección de devolución x64 y reanudar la emulación x64.

Código thunk de salida: llamada de función ARM64EC a x64

Para cada llamada que realiza una función ARM64EC C/C++ a un código x64 potencial, la cadena de herramientas de MSVC genera un código thunk de salida. El contenido del código thunk depende de los parámetros del destinatario x64 y de si el destinatario usa la convención de llamada estándar o __vectorcall. El compilador obtiene esta información de una declaración de función para el destinatario.

En primer lugar, el código thunk inserta la dirección de retorno que se encuentra en el registro ARM64EC lr y un valor ficticio de 8 bytes para garantizar que la pila está alineada a 16 bytes. En segundo lugar, el código thunk controla los parámetros:

Número de parámetros Uso de la pila
0-4 Asigna 32 bytes de espacio principal en la pila
5-8 Asigna AlignUp(NumParams - 4, 2) * 8 más bytes más arriba en la pila. *

Copia el 5.º parámetro y los parámetros posteriores de ARM64EC x4-x7 en este espacio adicional
+9 Copia el 9.º parámetro y los parámetros restantes en este espacio adicional

* Alinear el valor con un número par garantiza que la pila permanece alineada a 16 bytes.

En tercer lugar, el código thunk llama al asistente del emulador __os_arm64x_dispatch_call_no_redirect para invocar el emulador x64 y ejecutar la función x64. La llamada debe ser una instrucción blr x16 (convenientemente, x16 es un registro volátil). Se requiere una instrucción blr x16 porque el emulador x64 analiza esta instrucción como una sugerencia.

La función x64 normalmente intenta volver al asistente del emulador mediante una instrucción x64 ret. En este momento, el emulador x64 detecta que está en código ARM64EC. A continuación, lee la sugerencia de 4 bytes anterior que resulta ser la instrucción blr x16 de ARM64. Dado que esta sugerencia indica que la dirección de devolución está en este asistente, el emulador salta directamente a esta dirección.

La función x64 puede volver al asistente del emulador mediante cualquier instrucción de rama, incluidas jmp y call de x64. El emulador también controla estos escenarios.

Cuando el asistente vuelve al código thunk, el código thunk:

  1. Deshace las asignaciones de pila
  2. Muestra el registro lr de ARM64EC
  3. Ejecuta una instrucción ret lr de ARM64.

Decoración de nombres de función ARM64EC

Un nombre de función ARM64EC tiene una decoración secundaria aplicada después de cualquier decoración específica del lenguaje. Para las funciones con vinculación de C (si se compilan como C o mediante extern "C"), # se antepone al nombre. Para las funciones decoradas de C++, se inserta una etiqueta $$h en el nombre.

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

__vectorcall

La cadena de herramientas ARM64EC no admite __vectorcall actualmente. El compilador emite un error cuando detecta el uso de __vectorcall con ARM64EC.

Consulte también

Descripción de la ABI y el código de ensamblado de ARM64EC
Problemas comunes de migración de ARM en Visual C++
Nombres representativos