Convención de llamadas x64
En esta sección se describen los procesos y convenciones estándar que usa una función (autor de la llamada) para realizar llamadas en otra función (destinatario) en código x64.
Para obtener más información sobre la convención de __vectorcall
llamada, consulte __vectorcall.
Valor predeterminado de la convención de llamada
De forma predeterminada, la interfaz binaria de aplicación (ABI) x64 utiliza una Convención de llamada rápida de cuatro registros. Se asigna espacio en la pila de llamadas como un almacén de propiedades reemplazadas para que los destinatarios puedan guardar esos registros.
Hay una correspondencia uno a uno estricta entre los argumentos de una llamada de función y los registros utilizados para esos argumentos. Cualquier argumento que no se ajuste a 8 bytes, o que no sea 1, 2, 4 u 8 bytes, debe pasarse por referencia. Un único argumento nunca se distribuye entre varios registros.
La pila de registro x87 no se utiliza. La puede usar el destinatario, pero considérela volátil en las funciones de llamada. Todas las operaciones de punto flotante se realizan mediante los 16 registros de XMM.
Los argumentos de entero se pasan en los registros RCX, RDX, R8 y R9. Los argumentos de punto flotante se pasan en XMM0L, XMM1L, XMM2L y XMM3L. Los argumentos de 16 bytes se pasan por referencia. El paso de parámetros se describe en detalle en la sección Paso de parámetros. Estos registros, y RAX, R10, R11, XMM4 y XMM5, se consideran volátiles o, potencialmente, los cambia un destinatario en la devolución. El uso del registro se documenta en detalle en registro de uso de x64 y registros guardados de llamador o destinatario.
En el caso de las funciones con prototipo, todos los argumentos se convierten en los tipos de destinatarios esperados antes de pasar. El autor de la llamada es el responsable de asignar espacio para los parámetros del destinatario. Siempre debe asignar espacio suficiente para almacenar cuatro parámetros de registro, aunque el destinatario no use tantos. Esta convención simplifica la compatibilidad con las funciones de lenguaje de C sin prototipo y las funciones vararg C/C++. En el caso de las funciones vararg o sin prototipo, los valores de punto flotante se deben duplicar en el registro de uso general correspondiente. Cualquier parámetro adicional a los cuatro primeros se deberá almacenar en la pila, después del almacén de propiedades reemplazadas antes de la llamada. Los detalles de la función vararg se pueden encontrar en Varargs. La información de funciones sin prototipo se detalla en Funciones sin prototipo.
Alineación
La mayoría de las estructuras se alinean con su alineación natural. Las excepciones principales son el puntero de pila y la memoria malloc
o alloca
, que se alinean a 16 bytes para un mejor rendimiento. Las alineaciones superiores a 16 bytes se deberán realizar manualmente. Como 16 bytes es un tamaño de alineación común para las operaciones XMM, este valor debería funcionar para la mayoría del código. Para obtener más información sobre el diseño y la alineación de la estructura, el tipo x64 y el diseño de almacenamiento. Para obtener información sobre el diseño de pila, vea Uso de las pilas x64.
Capacidad de desenredado
Las funciones de hoja son funciones que no cambian ningún registro no volátil. Las funciones que no son de hoja pueden cambiar el RSP no volátil, por ejemplo, mediante una llamada a una función. O bien, podrían cambiar el RSP asignando espacio de pila adicional para las variables locales. Para recuperar los registros no volátiles al manipular una excepción, las funciones que no son de hoja se anotan con los datos estáticos. Los datos describen cómo desenredar correctamente la función en una instrucción arbitraria. Estos datos se almacenan como pdata o datos de procedimiento, que a su vez hacen referencia a xdata, los datos de control de excepciones. Los xdata contienen la información de desenredado y pueden apuntar a un pdata adicional o a una función de controlador de excepciones.
Los prólogos y epílogos están muy restringidos, de tal forma que se puedan describir correctamente en xdata. El puntero de pila debe permanecer alineado a 16 bytes en cualquier región de código que no forme parte de un epílogo o prólogo, excepto en las funciones de hoja. Las funciones de hoja se pueden desenredar simplemente simulando una devolución, por lo que no se requieren pdata ni xdata. Para obtener más información sobre la estructura adecuada de los prólogos y epílogos de función, vea Prólogo y epílogo x64. Para obtener más información sobre el control de excepciones, así como del control de excepciones y el desenredado de pdata y xdata, vea Control de excepciones x64.
Paso de parámetros
De forma predeterminada, la convención de llamada x64 pasa los cuatro primeros argumentos a una función en los registros. Los registros que se utilizan para estos argumentos dependen de la posición y el tipo del argumento. Los argumentos restantes se insertan en la pila de derecha a izquierda.
Los valores enteros de las cuatro posiciones más a la izquierda se pasan de izquierda a derecha en RCX, RDX, R8 y R9, respectivamente. Los siguientes argumentos (del quinto en adelante) se pasan en la pila como se ha descrito anteriormente. Todos los argumentos de enteros de los registros están justificados a la derecha, por lo que el destinatario puede ignorar los bits superiores del registro y acceder solo a la parte del registro necesaria.
Los argumentos de punto flotante y de precisión doble de los cuatro primeros parámetros se pasan en XMM0-XMM3, en función de la posición. Los valores de punto flotante solo se colocan en los registros de enteros RCX, RDX, R8 y R9 cuando hay argumentos varargs. Para obtener información detallada, vea Varargs. Del mismo modo, los registros de XMM0-XMM3 se ignoran cuando el argumento correspondiente es un tipo entero o de puntero.
Las cadenas, las matrices y los tipos __m128
nunca se pasan por valor inmediato. En su lugar, se pasa un puntero a la memoria que asigna el autor de la llamada. Las estructuras y uniones de 8, 16, 32 o 64 bits, así como los tipos __m64
, se pasan como si fueran enteros del mismo tamaño. Las estructuras o uniones de otros tamaños se pasan como un puntero a la memoria que asigna el autor de la llamada. Para estos tipos de agregado que se pasan como puntero, incluido __m128
, la memoria temporal asignada por el autor de la llamada debe tener una alineación de 16 bytes.
Las funciones intrínsecas que no asignan espacio de pila y no llaman a otras funciones, a veces usan otros registros volátiles para pasar argumentos de registro adicionales. Esta optimización se realiza mediante el enlace estricto entre el compilador y la implementación de la función intrínseca.
El destinatario es el responsable de volcar los parámetros de registro en su espacio de propiedades reemplazadas, en caso necesario.
En la tabla siguiente se resume cómo se pasan los parámetros, por tipo y posición desde la izquierda:
Parameter type (Tipo de parámetro) | quinto y posteriores | cuarto | Tercero | second | extremo izquierdo |
---|---|---|---|---|---|
punto flotante | stack | XMM3 | XMM2 | XMM1 | XMM0 |
integer | stack | R9 | R8 | RDX | RCX |
Agregados (de 8, 16, 32 o 64 bits) y __m64 |
stack | R9 | R8 | RDX | RCX |
otros agregados como punteros | stack | R9 | R8 | RDX | RCX |
__m128 , como puntero |
stack | R9 | R8 | RDX | RCX |
Ejemplo 1 de paso de argumentos: todos los enteros
func1(int a, int b, int c, int d, int e, int f);
// a in RCX, b in RDX, c in R8, d in R9, f then e pushed on stack
Ejemplo 2 de paso de argumentos: todos los flotantes
func2(float a, double b, float c, double d, float e, float f);
// a in XMM0, b in XMM1, c in XMM2, d in XMM3, f then e pushed on stack
Ejemplo 3 de paso de argumentos: los enteros y los flotantes combinados
func3(int a, double b, int c, float d, int e, float f);
// a in RCX, b in XMM1, c in R8, d in XMM3, f then e pushed on stack
Ejemplo 4 de paso de argumentos: __m64
, __m128
y los agregados
func4(__m64 a, __m128 b, struct c, float d, __m128 e, __m128 f);
// a in RCX, ptr to b in RDX, ptr to c in R8, d in XMM3,
// ptr to f pushed on stack, then ptr to e pushed on stack
Varargs
Si los parámetros se pasan a través de varargs (por ejemplo, argumentos de puntos suspensivos), se aplica la convención normal de paso de los parámetros de registro. Esta convención incluye el volcado del quinto argumento y los siguientes en la pila. Es responsabilidad del destinatario volcar los argumentos que tienen su dirección tomada. Solo para los valores de punto flotante, tanto el registro de entero como el de punto flotante deben contener el valor, en caso de que el destinatario espere el valor de los registros enteros.
Funciones sin prototipo
En el caso de las funciones sin prototipo completo, el autor de la llamada pasa valores enteros como enteros y valores de punto flotante como precisión doble. Solo para los valores de punto flotante, tanto el registro de entero como el de punto flotante contienen el valor flotante, en caso de que el destinatario espere el valor de los registros enteros.
func1();
func2() { // RCX = 2, RDX = XMM1 = 1.0, and R8 = 7
func1(2, 1.0, 7);
}
Valores devueltos
Valor escalar devuelto que puede caber en 64 bits, incluido el tipo __m64
, se devuelve mediante RAX. Los tipos no escalares, incluidos los tipos float, doubles y vectores, como __m128
, __m128i
, __m128d
se devuelven en XMM0. El estado de bits no usados en el valor devuelto en RAX o XMM0 es indefinido.
Se pueden devolver tipos definidos por el usuario por valor de funciones globales y funciones miembro estáticas. Para devolver un tipo definido por el usuario por valor en RAX, debe tener una longitud de 1, 2, 4, 8, 16, 32 o 64 bits. Además, no debe tener ningún operador de asignación de copia, destructor ni constructor definidos por el usuario. No puede tener miembros de datos no estáticos privados o protegidos ni miembros de datos no estáticos de tipo de referencia. No puede tener funciones virtuales ni clases base. Solo puede tener miembros de datos que también cumplan estos requisitos. (Esta definición es básicamente la misma que un tipo POD de C++03. Dado que la definición ha cambiado en el estándar C++11, no se recomienda usar std::is_pod
para esta prueba). De lo contrario, el autor de la llamada debe asignar memoria para el valor devuelto y pasarle un puntero como primer argumento. Los argumentos restantes se desplazan entonces un argumento a la derecha. El destinatario debe devolver el mismo puntero en RAX.
Estos ejemplos muestran cómo se pasan los parámetros y valores devueltos para las funciones con las declaraciones especificadas:
Ejemplo 1 de valor devuelto: resultado de 64 bits
__int64 func1(int a, float b, int c, int d, int e);
// Caller passes a in RCX, b in XMM1, c in R8, d in R9, e pushed on stack,
// callee returns __int64 result in RAX.
Ejemplo 2 de valor devuelto: resultado de 128 bits
__m128 func2(float a, double b, int c, __m64 d);
// Caller passes a in XMM0, b in XMM1, c in R8, d in R9,
// callee returns __m128 result in XMM0.
Ejemplo 3 de valor devuelto: resultado de tipo de usuario por puntero
struct Struct1 {
int j, k, l; // Struct1 exceeds 64 bits.
};
Struct1 func3(int a, double b, int c, float d);
// Caller allocates memory for Struct1 returned and passes pointer in RCX,
// a in RDX, b in XMM2, c in R9, d pushed on the stack;
// callee returns pointer to Struct1 result in RAX.
Ejemplo 4 de valor devuelto: resultado de tipo de usuario por valor
struct Struct2 {
int j, k; // Struct2 fits in 64 bits, and meets requirements for return by value.
};
Struct2 func4(int a, double b, int c, float d);
// Caller passes a in RCX, b in XMM1, c in R8, and d in XMM3;
// callee returns Struct2 result by value in RAX.
Registros guardados del autor y el destinatario de la llamada
ABI x64 considera volátiles los registros RAX, RCX, RDX, R8, R9, R10 y R11, así como los comprendidos entre el XMM0 y el XMM5. Cuando están presentes, las partes superiores de los registros comprendidos entre el YMM0 y el YMM15 y entre el ZMM0 y el ZMM15 son volátiles. En AVX512VL, también lo son los registros ZMM, YMM y XMM (del 16 al 31). Cuando hay compatibilidad con AMX, los registros de icono de TMM son volátiles. Considere destruidos los registros volátiles en las llamadas a funciones, a menos que lo contrario sea demostrable de forma segura mediante un análisis, como la optimización de todo el programa.
ABI x64 considera no volátiles los registros RBX, RBP, RDI, RSI, RSP, R12, R13, R14 y R15, así como los comprendidos entre el XMM6 y el XMM15. Deben guardarse y restaurarse mediante una función que los use.
Punteros de función
Los punteros de función son simplemente punteros a la etiqueta de la función respectiva. No hay ningún requisito de tabla de contenido (TOC) para los punteros de función.
Compatibilidad del código antiguo con puntos flotantes
Los registros de la pila de punto flotante y MMX (MM0-MM7/ST0-ST7) se conservan en los cambios de contexto. No hay ninguna convención de llamada explícita para estos registros. El uso de estos registros está estrictamente prohibido en el código del modo kernel.
FPCSR
El estado del registro también incluye la palabra de control de FPU x87. La convención de llamada dicta este registro para que sea no volátil.
El registro de palabra de control de FPU x87 se establece mediante los siguientes valores estándar al inicio de la ejecución del programa:
Register[bits] | Configuración |
---|---|
FPCSR[0:6] | Máscaras de excepción, todos los 1 (todas las excepciones enmascaradas) |
FPCSR[7] | Reservado: 0 |
FPCSR[8:9] | Control de precisión: 10B (precisión doble) |
FPCSR[10:11] | Control de redondeo: 0 (redondeo al valor más próximo) |
FPCSR[12] | Control de infinito: 0 (no utilizado) |
Un destinatario que modifique cualquiera de los campos en FPCSR debe restaurarlos antes de devolverlos al autor de la llamada. Además, un autor de llamada que haya modificado cualquiera de estos campos debe restaurarlos a sus valores estándar antes de invocar a un destinatario, a menos que el destinatario espere los valores modificados por contrato.
Existen dos excepciones a las reglas sobre la no volatilidad de las marcas de control:
En funciones en las que el propósito documentado de la función determinada sea modificar las marcas FPCSR no volátiles.
Cuando sea correcto de un modo demostrable que la infracción de estas reglas dé como resultado un programa que se comporte igual que un programa que no infringe las reglas, por ejemplo, a través del análisis de todo el programa.
MXCSR
El estado del registro también incluye MXCSR. La convención de llamada divide este registro en una parte volátil y una parte no volátil. La parte volátil consta de las seis marcas de estado, en MXCSR[0:5], mientras que el resto del registro, MXCSR[6:15], se considera no volátil.
La parte no volátil se establece en los siguientes valores estándar al inicio de la ejecución del programa:
Register[bits] | Configuración |
---|---|
MXCSR[6] | Los desnormalizados son ceros: 0 |
MXCSR[7:12] | Máscaras de excepción, todos los 1 (todas las excepciones enmascaradas) |
MXCSR[13:14] | Control de redondeo: 0 (redondeo al valor más próximo) |
MXCSR[15] | Vaciado en cero para desbordamiento enmascarado: 0 (desactivado) |
Un destinatario que modifique cualquiera de los campos no volátiles en MXCSR debe restaurarlos antes de devolverlos al autor de la llamada. Además, un autor de llamada que haya modificado cualquiera de estos campos debe restaurarlos a sus valores estándar antes de invocar a un destinatario, a menos que el destinatario espere los valores modificados por contrato.
Existen dos excepciones a las reglas sobre la no volatilidad de las marcas de control:
En funciones en las que el propósito documentado de la función determinada sea modificar las marcas MXCSR no volátiles.
Cuando sea correcto de un modo demostrable que la infracción de estas reglas dé como resultado un programa que se comporte igual que un programa que no infringe las reglas, por ejemplo, a través del análisis de todo el programa.
No realice suposiciones sobre el estado de la parte volátil del registro MXCSR a través de un límite de función, a menos que la documentación de la función lo describa explícitamente.
setjmp/longjmp
Al incluir setjmpex.h o setjmp.h, todas las llamadas a setjmp
o longjmp
dan como resultado un desenredo que invoca destructores y llamadas a __finally
. Este comportamiento difiere de x86, donde incluir setjmp.h no provoca que se invoquen las cláusulas y destructores __finally
.
Una llamada a setjmp
conserva el puntero de pila actual, los registros no volátiles y los registros MXCSR. Las llamadas a longjmp
devuelven al sitio de la llamada a setjmp
más reciente y restablecen el puntero de pila, los registros no volátiles y los registros MXCSR de nuevo al estado que conserva la llamada a setjmp
más reciente.