assembly - ¿Por qué el IA-32 tiene un llamador no intuitivo y una convención de guardado de registro de llamada?
x86 calling-convention (4)
Las convenciones de llamadas comunes para IA-32 dicen:
• Callee-save registers
%ebx, %esi, %edi, %ebp, %esp
Callee must not change these. (Or restore the caller''s values before returning.)
• Caller-save registers
%eax, %edx, %ecx, condition flags
Caller saves these if it wants to preserve them. Callee can freely clobber.
¿Por qué existe esta extraña convención? ¿Por qué no guardar todos los registros antes de llamar a otra función? ¿O hacer que el destinatario guarde y restaure todo con pusha
/ popa
?
¿Por qué querrías escribir código para guardar registros en cada función que quizás no necesites? Eso agregaría código adicional y escritura de memoria extra a cada llamada de función. Puede que ahora no parezca importante, pero en los años 80, cuando se creó esta convención, probablemente sí importó.
Y tenga en cuenta que ia-32 no tiene una convención de llamadas fijas; lo que usted menciona es solo una convención externa, ia-32 no lo exige. Si está escribiendo su propio código, use los registros como desee.
También vea la discusión Historia de las convenciones de llamadas en el blog Old New Thing.
Al decidir qué registros debe conservar una convención de llamadas, debe equilibrar las necesidades de la persona que llama con las necesidades del destinatario. La persona que llama prefiere que se conserven todos los registros, ya que elimina la necesidad de que la persona que llama se preocupe por guardar / restaurar el valor en una llamada. El destinatario prefiere que no se conserven registros, ya que elimina la necesidad de guardar el valor en la entrada y restaurarlo al salir.
Si necesita muy pocos registros para preservar, las personas que llaman se llenan con el código de guardar / restaurar. Pero si requiere que se guarden demasiados registros, las llamadas se verán obligadas a guardar y restaurar registros que la persona que llama podría no haberse preocupado realmente. Esto es particularmente importante para las funciones de hoja (funciones que no llaman a ninguna otra función).
En resumen, el salvador de llamadas se debe a la aprobación de argumentos. Todo lo demás está guardado.
Si miras un poco más profundamente en los registros utilizados, puedes ver por qué no serán preservados por el destinatario:
-
EAX
: utilizado para los retornos de funciones, por lo que obviamente no se puede preservar. -
EDX:EAX
: utilizado para retornos de funciones de 64 bits, igual queEAX
. -
ECX
: este es el registro de recuento, y en los días anteriores de x86 cuandoLOOPcc
era ''cool'', este registro seLOOPcc
loco, incluso hoy en día todavía hay bastantes instrucciones que usanECX
como contador (como las instrucciones prefijadas deREP
) Sin embargo, gracias a la llegada de__thiscall
y__fastcall
, se usa para pasar args, lo que significa que es muy probable que cambie, por lo que casi no tiene sentido preservarlo. -
ESP
: esta es una pequeña excepción lateral, ya que no se conserva realmente, se altera de acuerdo con los cambios de pila. Aunque se puede conservar para evitar la corrupción / seguridad del puntero de la pila o el desequilibrio gracias al ensamblaje en línea (a través de los marcos de pila).
Ahora realmente se vuelve intuitivo :)
Una adivinanza:
Si la persona que llama guarda todos los registros que aún necesitará después de una llamada de función, pierde tiempo cuando la función llamada no modifica todos los registros.
Si el destinatario guarda todos los registros que cambia, pierde tiempo cuando la persona que llama no necesita los valores en esos registros nuevamente.
Cuando algunos registros son guardados por la persona que llama y otros por el destinatario, el compilador (o el programador ensamblador) puede elegir qué tipo usar dependiendo de si el valor es necesario después de la siguiente llamada a la función.