Información general sobre las convenciones ABI de x64
En este tema se describe la interfaz binaria de aplicación (ABI) básica para x64, la extensión de 64 bits a la arquitectura x86. Trata temas como la convención de llamada, el diseño de tipos, la pila y el uso del registro, etc.
Convenciones de llamada x64
Dos diferencias importantes entre x86 y x64 son:
- Capacidad de direccionamiento de 64 bits
- Dieciséis registros de 64 bits para uso general.
Dado el conjunto de registros expandido, x64 usa la convención de llamada __fastcall y un modelo de control de excepciones basado en RISC.
La convención __fastcall
usa registros para los cuatro primeros argumentos, y el marco de pila para pasar más argumentos. Para obtener información detallada sobre la convención de llamada de x64, incluido el uso de registros, parámetros de la pila, valores devueltos y el desenredado de la pila, vea Convención de llamada de x64.
Para más información sobre la convención de llamada de__vectorcall
, consulte __vectorcall
.
Habilitación de la optimización del compilador x64
La siguiente opción del compilador le ayuda a optimizar la aplicación para x64:
Tipo x64 y diseño de almacenamiento
En esta sección se describe el almacenamiento de tipos de datos para la arquitectura x64.
Tipos escalares
Aunque es posible acceder a datos con cualquier alineación, alinee los datos por su límite natural, o un múltiplo de su límite natural, para evitar un deterioro del rendimiento. Las enumeraciones son enteros constantes y se tratan como enteros de 32 bits. En la tabla siguiente se describe la definición de tipo y el almacenamiento recomendado para los datos con relación a la alineación mediante los valores de alineación siguientes:
- Byte: 8 bits
- Palabra: 16 bits
- Doble palabra: 32 bits
- Palabra cuádruple: 64 bits
- Doble palabra cuádruple: 128 bits
Tipo escalar | tipo de datos C | Tamaño de almacenamiento (en bytes) | Alineación recomendada |
---|---|---|---|
INT8 |
char |
1 | Byte |
UINT8 |
unsigned char |
1 | Byte |
INT16 |
short |
2 | Word |
UINT16 |
unsigned short |
2 | Word |
INT32 |
int , long |
4 | Doble palabra |
UINT32 |
unsigned int , unsigned long |
4 | Doble palabra |
INT64 |
__int64 |
8 | Palabra cuádruple |
UINT64 |
unsigned __int64 |
8 | Palabra cuádruple |
FP32 (precisión sencilla) |
float |
4 | Doble palabra |
FP64 (precisión doble) |
double |
8 | Palabra cuádruple |
POINTER |
* | 8 | Palabra cuádruple |
__m64 |
struct __m64 |
8 | Palabra cuádruple |
__m128 |
struct __m128 |
16 | Doble palabra cuádruple |
Diseño de agregado y unión x64
Otros tipos, como las matrices, estructuras y uniones, tienen requisitos de alineación más estrictos que garantizan el almacenamiento y la recuperación de datos coherentes de los agregados y las uniones. Estas son las definiciones de matriz, estructura y unión:
Matriz
Contiene un grupo ordenado de objetos de datos adyacentes. Cada objeto se denomina elemento. Todos los elementos de una matriz tienen el mismo tamaño y tipo de datos.
Estructura
Contiene un grupo ordenado de objetos de datos. A diferencia de los elementos de una matriz, los miembros de una estructura pueden tener distintos tamaños y tipos de datos.
Unión
Objeto que contiene cualquiera de un conjunto de miembros con nombre. Los miembros del conjunto con nombre pueden ser de cualquier tipo. El almacenamiento asignado para una unión es igual al almacenamiento necesario para el miembro más grande de esa unión, además de cualquier relleno necesario para la alineación.
En la tabla siguiente se muestra la alineación recomendada para los miembros escalares de uniones y estructuras.
Tipo escalar | Tipo de datos de C | Alineación necesaria |
---|---|---|
INT8 |
char |
Byte |
UINT8 |
unsigned char |
Byte |
INT16 |
short |
Word |
UINT16 |
unsigned short |
Word |
INT32 |
int , long |
Doble palabra |
UINT32 |
unsigned int , unsigned long |
Doble palabra |
INT64 |
__int64 |
Palabra cuádruple |
UINT64 |
unsigned __int64 |
Palabra cuádruple |
FP32 (precisión sencilla) |
float |
Doble palabra |
FP64 (precisión doble) |
double |
Palabra cuádruple |
POINTER |
* | Palabra cuádruple |
__m64 |
struct __m64 |
Palabra cuádruple |
__m128 |
struct __m128 |
Doble palabra cuádruple |
Se aplican las siguientes reglas de alineación de agregados:
La alineación de una matriz es la misma que la alineación de uno de los elementos de la matriz.
La alineación del inicio de una estructura o una unión es la alineación máxima de cualquier miembro individual. Cada miembro de la estructura o unión se debe colocar en su alineación adecuada, como se ha definido en la tabla anterior, lo que puede requerir un relleno interno implícito, en función del miembro anterior.
El tamaño de la estructura debe ser un múltiplo entero de su alineación, lo que puede requerir relleno después del último miembro. Como las estructuras y las uniones se pueden agrupar en matrices, cada elemento de matriz de una estructura o unión debe comenzar y finalizar en la alineación adecuada determinada antes.
Es posible alinear datos de tal forma que sean mayores que los requisitos de alineación, siempre que se mantengan las reglas anteriores.
Un compilador individual puede ajustar el empaquetado de una estructura por motivos de tamaño. Por ejemplo, /Zp (Alineación de miembros de estructura) permite ajustar el empaquetado de estructuras.
Ejemplos de alineación de estructuras x64
En los cuatro ejemplos siguientes se declara una estructura alineada o una unión, y en las figuras correspondientes se muestra el diseño de esa estructura o unión en la memoria. Cada columna de una ilustración representa un byte de memoria, y el número de la columna indica el desplazamiento de ese byte. El nombre de la segunda fila de cada ilustración se corresponde con el nombre de una variable en la declaración. Las columnas sombreadas indican el relleno necesario para lograr la alineación especificada.
Ejemplo 1
// Total size = 2 bytes, alignment = 2 bytes (word).
_declspec(align(2)) struct {
short a; // +0; size = 2 bytes
}
Ejemplo 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
}
Ejemplo 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
}
Ejemplo 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
Los campos de bits de estructura se limitan a 64 bits y pueden ser de tipo signed int, unsigned int, int64 o unsigned int64. Los campos de bits que cruzan el límite de tipos omiten bits para alinear el campo de bits con la alineación de tipos siguiente. Por ejemplo, los campos de bits enteros no pueden cruzar un límite de 32 bits.
Conflictos con el compilador de x86
Los tipos de datos de más de 4 bytes no se alinean automáticamente en la pila cuando se usa el compilador x86 para compilar una aplicación. Como la arquitectura del compilador x86 es una pila alineada de 4 bytes, todo lo que sea mayor de 4 bytes, por ejemplo, un entero de 64 bits, no se puede alinear de forma automática con una dirección de 8 bytes.
Trabajar con datos sin alinear tiene dos implicaciones.
Se puede tardar más en acceder a ubicaciones no alineadas de lo que se tarda en acceder a ubicaciones alineadas.
Las ubicaciones no alineadas no se pueden usar en operaciones de bloqueo.
Si necesita una alineación más estricta, use __declspec(align(N))
en las declaraciones de variables. Esto hace que el compilador alinee de forma dinámica la pila para cumplir con las especificaciones. Pero el ajuste dinámico de la pila en tiempo de ejecución puede ralentizar la ejecución de la aplicación.
Uso de registros x64
La arquitectura x64 proporciona 16 registros generales (que se denominarán registros enteros a partir de ahora), así como otros 16 registros de XMM/YMM, disponibles para el uso de puntos flotantes. Los registros volátiles son registros residuales que el llamador da por hecho que van a destruirse a lo largo de la llamada. En cuanto a los registros no volátiles, es necesarios conservar sus valores a lo largo de la llamada de función, y el destinatario de la llamada debe guardarlos si se usan.
Volatilidad y conservación de los registros
En la siguiente tabla se explica el uso de cada registro en las llamadas de función:
Registro | Estado | Usar |
---|---|---|
RAX | Volátil | Registro de valor devuelto |
RCX | Volátil | Primer argumento de entero |
RDX | Volátil | Segundo argumento de entero |
R8 | Volátil | Tercer argumento de entero |
R9 | Volátil | Cuarto argumento de entero |
R10:R11 | Volátil | El llamador debe conservarlo según sea necesario; utilizado en instrucciones syscall/sysret |
R12:R15 | No volátil | El destinatario de la llamada debe conservarlo |
RDI | No volátil | El destinatario de la llamada debe conservarlo |
RSI | No volátil | El destinatario de la llamada debe conservarlo |
RBX | No volátil | El destinatario de la llamada debe conservarlo |
RBP | No volátil | Se puede usar como un puntero de marco; el destinatario de la llamada debe conservarlo según sea necesario |
RSP | No volátil | Puntero de pila |
XMM0, YMM0 | Volátil | Primer argumento de FP; primer argumento de tipo vectorial cuando se usa __vectorcall |
XMM1, YMM1 | Volátil | Segundo argumento de FP; segundo argumento de tipo vectorial cuando se usa __vectorcall |
XMM2, YMM2 | Volátil | Tercer argumento de FP; tercer argumento de tipo vectorial cuando se usa __vectorcall |
XMM3, YMM3 | Volátil | Cuarto argumento de FP; cuarto argumento de tipo vectorial cuando se usa __vectorcall |
XMM4, YMM4 | Volátil | El llamador debe conservarlo según sea necesario; quinto argumento de tipo vectorial cuando se usa __vectorcall |
XMM5, YMM5 | Volátil | El llamador debe conservarlo según sea necesario; sexto argumento de tipo vectorial cuando se usa __vectorcall |
XMM6:XMM15, YMM6:YMM15 | No volátil (XMM), volátil (mitad superior de YMM) | Lo debe conservar el destinatario. El llamador debe conservar los registros de YMM según sea necesario. |
En la salida y entrada de la función para las llamadas a la biblioteca en tiempo de ejecución de C y las llamadas del sistema de Windows, se espera que la marca de dirección del registro de marcas de CPU se borre.
Uso de la pila
Para obtener más información sobre la asignación de pila, la alineación, los tipos de función y los marcos de pila en x64, vea Uso de la pila de x64.
Prólogo y epílogo
Todas las funciones que asignan espacio de pila, llaman a otras funciones, guardan registros no volátiles o usan el control de excepciones deben tener un prólogo cuyos límites de dirección se describen en los datos de desenredado asociados a la entrada de la tabla de funciones correspondiente, así como epílogos en cada salida de una función. Para obtener más información sobre el código de prólogo y epílogo necesario en x64, vea Prólogo y epílogo de x64.
Control de excepciones x64
Para obtener información sobre las convenciones y estructuras de datos que se usan para implementar el control de excepciones estructurado y el comportamiento del control de excepciones de C++ en x64, vea Control de excepciones en x64.
Elementos intrínsecos y ensamblado en línea
Una de las restricciones del compilador x64 es la falta de compatibilidad con el ensamblador en línea. Esto significa que las funciones que no se pueden escribir en C o C++ se tienen que escribir como subrutinas o como funciones intrínsecas admitidas por el compilador. Algunas funciones son sensibles el rendimiento y otras no. Las funciones sensibles al rendimiento se deben implementar como funciones intrínsecas.
Las funciones intrínsecas que admite el compilador se describen en Intrínsecos del controlador.
Formato de imagen x64
El formato de imagen ejecutable de x64 es PE32+. Las imágenes ejecutables (tanto DLL como EXE) están restringidas a un tamaño máximo de 2 gigabytes, por lo que se puede usar el direccionamiento relativo con un desplazamiento de 32 bits para tratar los datos de imagen estáticos. Estos datos incluyen la tabla de direcciones de importación, las constantes de cadena y los datos globales estáticos, entre otros.