¿Qué son los registros guardados del destinatario y del llamante?
Tengo problemas para comprender la diferencia entre los registros guardados de la persona que llama y de la persona que llama y cuándo usar qué.
Estoy usando el MSP430:
procedimiento:
mov.w #0,R7
mov.w #0,R6
add.w R6,R7
inc.w R6
cmp.w R12,R6
jl l$loop
mov.w R7,R12
ret
El código anterior es un destinatario y se usó en un ejemplo de libro de texto, por lo que sigue la convención. R6 y R7 se guardan como destinatario de la llamada y R12 se guarda como persona que llama. Tengo entendido que los registros guardados del destinatario no son "globales" en el sentido de que cambiar su valor en un procedimiento no afectará su valor fuera del procedimiento. Es por eso que debe guardar un nuevo valor en el registro del destinatario al principio.
R12, la persona que llama guardada es "global", a falta de mejores palabras. Lo que hace el procedimiento tiene un efecto duradero en R12 después de la llamada.
¿Es correcto mi entendimiento? ¿Me estoy perdiendo otras cosas?
Los registros guardados por la persona que llama (también conocidos como registros volátiles o call-clobbered ) se utilizan para almacenar cantidades temporales que no necesitan conservarse entre las llamadas.
Por esa razón, es responsabilidad del autor de la llamada insertar estos registros en la pila o copiarlos en otro lugar si desea restaurar este valor después de una llamada a un procedimiento.
Sin embargo, es normal permitir que a call
destruya valores temporales en estos registros.
Los registros guardados por el destinatario (también conocidos como registros no volátiles o conservados de llamada ) se utilizan para contener valores de larga duración que deben conservarse en todas las llamadas.
Cuando la persona que llama realiza una llamada a un procedimiento, puede esperar que esos registros conserven el mismo valor después de que la persona que llama regrese, por lo que es responsabilidad de la persona que llama guardarlos y restaurarlos antes de regresar a la persona que llama. O no tocarlos.
La terminología de guardado de la persona que llama/guardado de la persona que llama se basa en un modelo simplista de variables de registro donde las personas que llaman realmente guardan/restauran (de nuevo en el mismo registro) alrededor de cada llamada para cualquier registro bloqueado en el que mantenían un valor que deseaban más adelante. . (En lugar de mantener valores útiles a largo plazo en otro lugar, o comenzar de nuevo con la asignación de registros para recargar según sea necesario).
Debe comprender que "guardado por la persona que llama" significa "guardado de alguna manera si desea el valor más adelante". No es que todos los registros siempre se guarden/restauren en cada llamada a función, sino en la persona que llama o en la persona que llama.
En realidad, el código eficiente permite que los valores se destruyan cuando ya no son necesarios. Los compiladores suelen crear funciones que guardan algunos registros conservados de llamadas al inicio (prólogo) de una función (y los restauran al final, en el epílogo). Dentro de la función, usan esas regulaciones para los valores que necesitan sobrevivir en las llamadas a funciones. (Si se quedan sin registros de llamadas preservadas, es posible que necesiten derramar algo antes de una llamada, pero a menudo no es necesario volver a cargarlo de inmediato, tal vez no hasta unas cuantas llamadas más tarde, o después de un bucle, y tal vez no necesariamente en el mismo registro en el que estaba antes.)
Prefiero "llamada preservada" frente a "llamada golpeada" , que son inequívocos y se autodescriben una vez que has oído hablar del concepto básico, y no requieren ninguna gimnasia mental seria para pensar desde la perspectiva de la persona que llama o del la perspectiva de la persona que llama. (Ambos términos son desde la misma perspectiva).
Además, estos términos difieren en más de una letra.
Los términos volátil/no volátil son bastante buenos, por analogía con el almacenamiento que pierde su valor si se corta o no la energía (como DRAM versus Flash). Pero la volatile
palabra clave C tiene un significado técnico totalmente diferente, por lo que es una desventaja de "(no) volátil" cuando se describen las convenciones de llamadas de C.
- Los registros volátiles o guardados por la persona que llama son buenos para valores temporales o temporales que no son necesarios después de la siguiente llamada a función.
Desde la perspectiva de la persona que llama, su función puede sobrescribir libremente (también conocido como clobber) estos registros sin guardarlos/restaurarlos.
Desde la perspectiva de la persona que llama, call foo
destruye (también conocido como clobbers) todos los registros de llamadas, o al menos hay que asumir que así es.
Puedes escribir funciones auxiliares privadas que tengan una convención de llamada personalizada, por ejemplo, sabes que no modifican un determinado registro. Pero si todo lo que sabe (o quiere asumir o depende de) es que la función de destino sigue la convención de llamada normal, entonces debe tratar una llamada de función como si destruyera todos los registros bloqueados por llamadas. De ahí proviene literalmente el nombre: una llamada golpea esos registros.
Algunos compiladores que realizan optimización entre procedimientos también pueden crear definiciones de funciones de uso interno exclusivo que no siguen la ABI, utilizando una convención de llamada personalizada.
- Los registros preservados de llamadas , también conocidos como guardados por el destinatario de la llamada o no volátiles, mantienen sus valores en las llamadas a funciones . Esto es útil para variables de bucle en un bucle que realiza llamadas a funciones, o básicamente cualquier cosa en una función que no sea de hoja en general.
Desde la perspectiva del destinatario, estos registros no se pueden modificar a menos que guarde el valor original en algún lugar para poder restaurarlo antes de regresar. O para registros como el puntero de pila (que casi siempre se conserva en la llamada), puede restar un desplazamiento conocido y volver a agregarlo antes de regresar, en lugar de guardar el valor anterior en cualquier lugar. es decir, puede restaurarlo a estima, a menos que asigne una cantidad de espacio de pila variable en tiempo de ejecución. Luego, normalmente restaura el puntero de la pila desde otro registro.
Una función que puede beneficiarse del uso de muchos registros puede guardar/restaurar algunos registros conservados de llamadas solo para poder usarlos como más temporales, incluso si no realiza ninguna llamada a función. Normalmente, solo haría esto después de quedarse sin registros de llamadas para usar, porque guardar/restaurar generalmente cuesta un push/pop al inicio/final de la función. (O si su función tiene varias rutas de salida, una pop
en cada una de ellas).
El nombre "guardado por la persona que llama" es engañoso: no es necesario guardarlos/restaurarlos especialmente. Normalmente, organiza su código para que tenga valores que deben sobrevivir a una llamada de función en registros conservados de llamadas, o en algún lugar de la pila, o en algún otro lugar desde donde pueda recargar. Es normal dejar que a call
destruya valores temporales.
Una ABI o convención de llamada define cuáles son cuáles
Consulte, por ejemplo, Qué registros se conservan a través de una llamada de función de Linux x86-64 para la ABI del Sistema V x86-64.
Además, los registros de paso de argumentos siempre están bloqueados en todas las convenciones de llamada de funciones que conozco. Consulte ¿Se guardan los registros de la persona que llama rdi y rsi o de la persona que llama?
Pero las convenciones de llamadas al sistema generalmente hacen que todos los registros, excepto el valor de retorno, se mantengan en la llamada. (Por lo general, incluye incluso códigos de condición/indicadores). Consulte ¿Cuáles son las convenciones de llamada para las llamadas al sistema UNIX y Linux (y funciones de espacio de usuario) en i386 y x86-64?
Callee vs persona que llama guardada es una convención sobre quién es responsable de guardar y restaurar el valor en un registro durante una llamada. TODOS los registros son "globales" en el sentido de que cualquier código en cualquier lugar puede ver (o modificar) un registro y esas modificaciones serán vistas por cualquier código posterior en cualquier lugar. El objetivo de las convenciones para guardar registros es que se supone que el código no modifica ciertos registros, ya que otro código supone que el valor no se modifica.
En su código de ejemplo, NINGUNO de los registros se guarda como destinatario de la llamada, ya que no intenta guardar ni restaurar los valores de los registros. Sin embargo, parecería que no es un procedimiento completo, ya que contiene una rama a una etiqueta indefinida ( l$loop
). Por lo tanto, podría ser un fragmento de código de la mitad de un procedimiento que trata algunos registros como guardados del destinatario; simplemente te faltan las instrucciones para guardar/restaurar.