En informática , una convención de llamada es un esquema de nivel de implementación (nivel bajo) para cómo las subrutinas o funciones reciben parámetros de su llamador y cómo devuelven un resultado. [1] Cuando algún código llama a una función, se han tomado decisiones de diseño para dónde y cómo se pasan los parámetros a esa función, y dónde y cómo se devuelven los resultados de esa función, y estas transferencias generalmente se realizan a través de ciertos registros o dentro de un marco de pila en la pila de llamadas . Hay opciones de diseño para cómo se dividen las tareas de preparación para una llamada de función y restauración del entorno después de que la función se haya completado entre el llamador y el llamado. Algunas convenciones de llamada especifican la forma en que se debe llamar a cada función. Se debe utilizar la convención de llamada correcta para cada llamada de función, para permitir la ejecución correcta y confiable de todo el programa utilizando estas funciones.
Las convenciones de llamada suelen considerarse parte de la interfaz binaria de la aplicación (ABI). Pueden considerarse un contrato entre el que llama y la función llamada. [1]
Los nombres o significados de los parámetros y valores de retorno se definen en la interfaz de programación de aplicaciones (API, a diferencia de ABI), que es un concepto independiente, aunque relacionado con ABI y la convención de llamada. Los nombres de los miembros dentro de las estructuras y objetos pasados también se considerarían parte de la API, y no de la ABI. A veces, las API incluyen palabras clave para especificar la convención de llamada para las funciones.
Las convenciones de llamadas no suelen incluir información sobre el manejo de la vida útil de las estructuras y los objetos asignados dinámicamente. Otra documentación complementaria puede indicar quién es el responsable de liberar la memoria asignada.
Es poco probable que las convenciones de llamada especifiquen el diseño de los elementos dentro de las estructuras y los objetos, como el orden de bytes o el empaquetamiento de estructuras.
Para algunos lenguajes, la convención de llamada incluye detalles de manejo de errores o excepciones (por ejemplo, Go , Java ) y para otros, no (por ejemplo, C++ ).
Para las llamadas a procedimientos remotos , existe un concepto análogo llamado Marshalling .
Las convenciones de llamada pueden estar relacionadas con la estrategia de evaluación de un lenguaje de programación en particular , pero la mayoría de las veces no se las considera parte de ella (o viceversa), ya que la estrategia de evaluación generalmente se define en un nivel de abstracción más alto y se la considera parte del lenguaje en lugar de un detalle de implementación de bajo nivel del compilador de un lenguaje en particular .
Las convenciones de llamada pueden diferir en:
A veces aparecen múltiples convenciones de llamada en una única plataforma; una determinada plataforma e implementación de lenguaje puede ofrecer una selección de convenciones de llamada. Las razones para esto incluyen el rendimiento, la adaptación de convenciones de otros lenguajes populares y las restricciones o convenciones impuestas por varias " plataformas informáticas ".
Muchas arquitecturas solo tienen una convención de llamada ampliamente utilizada, a menudo sugerida por el arquitecto. Para RISC, como SPARC, MIPS y RISC-V , se suelen utilizar nombres de registros basados en esta convención de llamada. Por ejemplo, los registros MIPS $4
a través de $7
tienen "nombres ABI" $a0
a través de $a3
, lo que refleja su uso para el paso de parámetros en la convención de llamada estándar. (Las CPU RISC tienen muchos registros de propósito general equivalentes, por lo que normalmente no hay ninguna razón de hardware para darles nombres distintos a los números).
La convención de llamada del lenguaje de un programa determinado puede diferir de la convención de llamada de la plataforma subyacente, el sistema operativo o alguna biblioteca a la que se vincula. Por ejemplo, en Windows de 32 bits , las llamadas del sistema operativo tienen la convención de llamada stdcall , mientras que muchos programas C que se ejecutan allí utilizan la convención de llamada cdecl . Para dar cabida a estas diferencias en la convención de llamada, los compiladores suelen permitir palabras clave que especifican la convención de llamada para una función determinada. Las declaraciones de función incluirán palabras clave específicas de la plataforma adicionales que indican la convención de llamada que se utilizará. Cuando se maneja correctamente, el compilador generará código para llamar a las funciones de la manera adecuada.
Algunos lenguajes permiten que la convención de llamada para una función se especifique explícitamente con esa función; otros lenguajes tendrán alguna convención de llamada pero estará oculta para los usuarios de ese lenguaje y, por lo tanto, normalmente no será una consideración para el programador.
La versión de 32 bits de la arquitectura x86 se utiliza con muchas convenciones de llamada diferentes. Debido a la pequeña cantidad de registros arquitectónicos y al enfoque histórico en la simplicidad y el tamaño pequeño del código, muchas convenciones de llamada x86 pasan argumentos en la pila. El valor de retorno (o un puntero a él) se devuelve en un registro. Algunas convenciones utilizan registros para los primeros parámetros, lo que puede mejorar el rendimiento, especialmente para rutinas de hoja cortas y simples que se invocan con mucha frecuencia (es decir, rutinas que no llaman a otras rutinas).
Ejemplo de llamada:
push EAX ; pasa algún resultado de registro push dword [ EBP + 20 ] ; pasa alguna variable de memoria (sintaxis FASM/TASM) push 3 ; pasa alguna constante call calc ; el resultado devuelto ahora está en EAX
Estructura típica del llamado: (algunas o todas (excepto ret) de las instrucciones a continuación pueden optimizarse en procedimientos simples). Algunas convenciones dejan el espacio de parámetros asignado, utilizando plain ret
en lugar de ret imm16
. En ese caso, el llamador podría add esp,12
en este ejemplo, o de otra manera, lidiar con el cambio a ESP.
calc: push EBP ; guardar el puntero de marco antiguo mov EBP , ESP ; obtener el nuevo puntero de marco sub ESP , localsize ; reservar espacio de pila para variables locales ; realizar cálculos, dejar el resultado en EAX . mov ESP , EBP ; liberar espacio para variables locales pop EBP ; restaurar el puntero de marco antiguo ret paramsize ; liberar espacio de parámetros y regresar.
La versión de 64 bits de la arquitectura x86, conocida como x86-64 , AMD64 e Intel 64, tiene dos secuencias de llamada de uso común. Una secuencia de llamada, definida por Microsoft, se utiliza en Windows; la otra secuencia de llamada, especificada en la ABI AMD64 System V, se utiliza en sistemas tipo Unix y, con algunos cambios, en OpenVMS . Como x86-64 tiene más registros de propósito general que x86 de 16 bits, ambas convenciones pasan algunos argumentos en los registros.
La convención de llamada ARM estándar de 32 bits asigna los 16 registros de propósito general de la siguiente manera:
Si el tipo de valor devuelto es demasiado grande para caber en r0 a r3, o cuyo tamaño no se puede determinar estáticamente en tiempo de compilación, entonces el llamador debe asignar espacio para ese valor en tiempo de ejecución y pasar un puntero a ese espacio en r0.
Las subrutinas deben conservar el contenido de r4 a r11 y el puntero de pila (quizás guardándolos en la pila en el prólogo de la función , luego usándolos como espacio de trabajo y luego restaurándolos desde la pila en el epílogo de la función ). En particular, las subrutinas que llaman a otras subrutinas deben guardar la dirección de retorno en el registro de enlace r14 en la pila antes de llamar a esas otras subrutinas. Sin embargo, dichas subrutinas no necesitan devolver ese valor a r14; solo necesitan cargar ese valor en r15, el contador del programa, para regresar.
La convención de llamadas ARM exige el uso de una pila completamente descendente. Además, el puntero de la pila siempre debe estar alineado con 4 bytes y siempre debe estar alineado con 8 bytes en una llamada de función con una interfaz pública. [3]
Esta convención de llamada hace que una subrutina ARM "típica":
La convención de llamada ARM de 64 bits ( AArch64 ) asigna los 31 registros de propósito general como: [4]
Todos los registros que comienzan con x tienen un registro correspondiente de 32 bits con el prefijo w . Por lo tanto, un x0 de 32 bits se denomina w0.
De manera similar, los 32 registros de punto flotante se asignan como: [5]
RISC-V tiene una convención de llamada definida con dos variantes, con o sin punto flotante. [6] Pasa argumentos en registros siempre que sea posible.
Las arquitecturas POWER , PowerPC y Power ISA tienen una gran cantidad de registros, por lo que la mayoría de las funciones pueden pasar todos los argumentos en registros para llamadas de un solo nivel . Los argumentos adicionales se pasan en la pila y siempre se asigna espacio para argumentos basados en registros en la pila como una conveniencia para la función llamada en caso de que se utilicen llamadas de varios niveles (recursivas o de otro tipo) y se deban guardar los registros. Esto también es útil en funciones variádicas , como printf()
, donde se debe acceder a los argumentos de la función como una matriz. Se utiliza una única convención de llamada para todos los lenguajes procedimentales.
Las instrucciones de ramificación y enlace almacenan la dirección de retorno en un registro de enlace especial separado de los registros de propósito general; una rutina regresa a su llamador con una instrucción de ramificación que usa el registro de enlace como la dirección de destino. Las rutinas de hoja no necesitan guardar o restaurar el registro de enlace; las rutinas que no son de hoja deben guardar la dirección de retorno antes de realizar una llamada a otra rutina y restaurarla antes de que regrese, guardándola mediante la instrucción Mover desde registro de propósito especial para mover el registro de enlace a un registro de propósito general y, si es necesario, guardándola luego en la pila y restaurándola, si se guardó en la pila, cargando el valor del registro de enlace guardado en un registro de propósito general y luego usando la instrucción Mover a registro de propósito especial para mover el registro que contiene el valor del registro de enlace guardado al registro de enlace.
La ABI O32 [7] es la más utilizada, debido a su condición de ABI original de System V para MIPS. [8] Está basada estrictamente en pila, con solo cuatro registros disponibles para pasar argumentos. Esta lentitud percibida, junto con un modelo de punto flotante antiguo con solo 16 registros, ha fomentado la proliferación de muchas otras convenciones de llamada. La ABI tomó forma en 1990 y nunca se actualizó desde 1994. Solo está definida para MIPS de 32 bits, pero GCC ha creado una variación de 64 bits llamada O64. [9]$a0-$a3
Para 64 bits, la ABI de N64 (no relacionada con Nintendo 64 ) de Silicon Graphics es la más utilizada. La mejora más importante es que ahora hay ocho registros disponibles para el paso de argumentos; también aumenta el número de registros de punto flotante a 32. También existe una versión ILP32 llamada N32, que utiliza punteros de 32 bits para código más pequeño, análogo a la ABI x32 . Ambas se ejecutan bajo el modo de 64 bits de la CPU. [9]
Se han hecho algunos intentos para reemplazar O32 con una ABI de 32 bits que se parezca más a N32. En una conferencia de 1995 se presentó MIPS EABI, para la cual la versión de 32 bits era bastante similar. [10] EABI inspiró a MIPS Technologies a proponer una ABI "NUBI" más radical que además reutiliza los registros de argumentos para el valor de retorno. [11] GCC admite MIPS EABI, pero no LLVM; ninguno admite NUBI.
Para todos los sistemas O32 y N32/N64, la dirección de retorno se almacena en un $ra
registro. Esto se establece automáticamente con el uso de las instrucciones JAL
(saltar y enlazar) o JALR
(saltar y enlazar registro). La pila crece hacia abajo.
La arquitectura SPARC , a diferencia de la mayoría de las arquitecturas RISC , está construida sobre ventanas de registro . Hay 24 registros accesibles en cada ventana de registro: 8 son los registros "in" (%i0-%i7), 8 son los registros "locales" (%l0-%l7) y 8 son los registros "out" (%o0-%o7). Los registros "in" se utilizan para pasar argumentos a la función que se está llamando, y cualquier argumento adicional debe colocarse en la pila . Sin embargo, la función llamada siempre asigna espacio para manejar un posible desbordamiento de la ventana de registro, las variables locales y (en SPARC de 32 bits) devolver una estructura por valor. Para llamar a una función, se colocan los argumentos para la función que se va a llamar en los registros "out"; cuando se llama a la función, los registros "out" se convierten en los registros "in" y la función llamada accede a los argumentos en sus registros "in". Cuando se completa la función llamada, coloca el valor de retorno en el primer registro "de entrada", que se convierte en el primer registro "de salida" cuando la función llamada regresa.
La ABI del System V , [12] que siguen la mayoría de los sistemas modernos tipo Unix , pasa los primeros seis argumentos en los registros "de entrada" %i0 a %i5, reservando %i6 para el puntero del marco y %i7 para la dirección de retorno.
El IBM System/360 es otra arquitectura sin una pila de hardware. Los ejemplos siguientes ilustran la convención de llamada utilizada por OS/360 y sus sucesores antes de la introducción de z/Architecture de 64 bits ; otros sistemas operativos para System/360 pueden tener convenciones de llamada diferentes.
Programa de llamada:
LA 1,ARGS Cargar la dirección de la lista de argumentos L 15,=A(SUB) Cargar dirección de subrutina BALR 14,15 Ramal a llamada rutina 1 ...ARGS DC A(PRIMER) Dirección del primer argumento DC A(SEGUNDO) ... DC A(TERCERO)+X'80000000' Último argumento 2
Programa llamado:
SUB EQU * Este es el punto de entrada del subprograma
Secuencia de entrada estándar:
USANDO *,15 3 STM 14,12,12(13) Guardar registros 4 ST 13, GUARDAR+4 Guardar la dirección del área de guardado del llamante LA 12, GUARDAR Cadena de zonas de guardado S. 12,8(13) LR 13,12 ...
Secuencia de retorno estándar:
L 13, GUARDAR+4 5 LM 14,12,12(13) L 15, RETIRO 6 BR 14 Regresar a la llamadaGUARDAR DS 18F Área de guardado 7
Notas:
BALR
instrucción almacena la dirección de la siguiente instrucción (dirección de retorno) en el registro especificado por el primer argumento (registro 14) y se ramifica a la dirección del segundo argumento en el registro 15.STM
instrucción guarda los registros 14, 15 y 0 a 12 en un área de 72 bytes proporcionada por el invocador, denominada área de guardado a la que apunta el registro 13. La rutina llamada proporciona su propia área de guardado para que la utilicen las subrutinas que llama; la dirección de esta área normalmente se guarda en el registro 13 durante toda la rutina. Las instrucciones siguientes STM
actualizan las cadenas hacia adelante y hacia atrás que vinculan esta área de guardado con el área de guardado del invocador.savearea
estáticamente en la rutina llamada lo hace no reentrante y no recursivo ; un programa reentrante utiliza un dinámico savearea
, adquirido ya sea del sistema operativo y liberado al regresar, o en el almacenamiento pasado por el programa que llama.En la ABI System/390 [13] y la ABI z/Architecture [14] , utilizadas en Linux:
Se pasan argumentos adicionales a la pila.
Nota: las reservas "preservadas" corresponden al ahorro del destinatario; lo mismo aplica para las "garantizadas".
La convención de llamada más común para la serie Motorola 68000 es: [15] [16] [17] [18]
El IBM 1130 era una pequeña máquina direccionable por palabra de 16 bits. Tenía solo seis registros más indicadores de condición y no tenía pila. Los registros son Registro de dirección de instrucción (IAR) , Acumulador (ACC) , Extensión de acumulador (EXT) y tres registros de índice X1–X3. El programa que realiza la llamada es responsable de guardar ACC, EXT, X1 y X2. [19] Hay dos pseudooperaciones para llamar a subrutinas, CALL
para codificar subrutinas no reubicables vinculadas directamente con el programa principal y LIBF
para llamar a subrutinas de biblioteca reubicables a través de un vector de transferencia . [20] Ambas pseudooperaciones se resuelven en una instrucción de máquina Branch and Store IAR ( BSI
) que almacena la dirección de la siguiente instrucción en su dirección efectiva (EA) y se ramifica a EA+1.
Los argumentos siguen a BSI
—normalmente son direcciones de argumentos de una palabra—la rutina llamada debe saber cuántos argumentos esperar para poder omitirlos al regresar. Alternativamente, los argumentos se pueden pasar en registros. Las rutinas de función devolvieron el resultado en ACC para argumentos reales, o en una ubicación de memoria denominada Pseudoacumulador de números reales (FAC). Los argumentos y la dirección de retorno se direccionaron utilizando un desplazamiento al valor IAR almacenado en la primera ubicación de la subrutina.
* Ejemplo de subrutina 1130 ENT SUB Declara "SUB" un punto de entrada externo SUB DC 0 Palabra reservada en el punto de entrada, codificada convencionalmente "DC *-*" * El código de subrutina comienza aquí * Si hubiera argumentos las direcciones se pueden cargar indirectamente desde la dirección de retorno LDX I 1 SUB Cargar X1 con la dirección del primer argumento (por ejemplo) ... * Secuencia de retorno LD RES Cargar resultado entero en ACC * Si no se proporcionaron argumentos, rama indirecta a la dirección de retorno almacenada BI SUB Si no se proporcionaron argumentos FIN DEL SUB
Las subrutinas en IBM 1130, CDC 6600 y PDP-8 (las tres computadoras se introdujeron en 1965) almacenan la dirección de retorno en la primera ubicación de una subrutina. [21]
El código enhebrado asigna toda la responsabilidad de la configuración y la limpieza después de una llamada de función al código llamado. El código de llamada no hace nada más que enumerar las subrutinas que se llamarán. Esto coloca todo el código de configuración y limpieza de la función en un solo lugar (el prólogo y el epílogo de la función) en lugar de en los muchos lugares en los que se llama a la función. Esto hace que el código enhebrado sea la convención de llamada más compacta.
El código enhebrado pasa todos los argumentos a la pila. Todos los valores de retorno se devuelven a la pila. Esto hace que las implementaciones ingenuas sean más lentas que las convenciones de llamada que mantienen más valores en los registros. Sin embargo, las implementaciones de código enhebrado que almacenan en caché varios de los valores superiores de la pila en los registros (en particular, la dirección de retorno) suelen ser más rápidas que las convenciones de llamada de subrutinas que siempre insertan y extraen la dirección de retorno en la pila. [22] [23] [24]
La convención de llamada predeterminada para los programas escritos en el lenguaje PL/I pasa todos los argumentos por referencia , aunque se pueden especificar opcionalmente otras convenciones. Los argumentos se manejan de forma diferente para distintos compiladores y plataformas, pero normalmente las direcciones de los argumentos se pasan a través de una lista de argumentos en memoria. Se puede pasar una dirección final, oculta, que apunte a un área para contener el valor de retorno. Debido a la amplia variedad de tipos de datos admitidos por PL/I, también se puede pasar un descriptor de datos para definir, por ejemplo, las longitudes de cadenas de caracteres o bits, la dimensión y los límites de matrices ( vectores dope ) o el diseño y el contenido de una estructura de datos . Se crean argumentos ficticios para argumentos que son constantes o que no concuerdan con el tipo de argumento que espera el procedimiento llamado.
Todos los registros excepto d0, d1, a0, a1 y a7 deben conservarse en una llamada.
En el 6809 o el Zilog Super8, el DTC es más rápido que el STC.
Aunque se sabe que los intérpretes de subprocesos directos tienen propiedades de predicción de bifurcaciones deficientes... la latencia de una llamada y un retorno puede ser mayor que la de un salto indirecto.