Convención de llamada ARM a C, registros para guardar

Ha pasado un tiempo desde la última vez que codifiqué ensamblador de brazo y estoy un poco oxidado sobre los detalles. Si llamo una función C desde el brazo, solo tengo que preocuparme por guardar r0-r3 y lr, ¿verdad? Si la función C usa otros registros, ¿es responsable de guardarlos en la pila y restaurarlos? En otras palabras, el compilador generaría código para hacer esto para las funciones C. Por ejemplo, si uso r10 en una función de ensamblador, no tengo que presionar su valor en la pila, ni en la memoria, y hacer pop/restaurarlo después de una llamada en C, ¿verdad?

Esto es para arm-eabi-gcc 4.3.0.

Me doy cuenta de que podría leer toda la EABI, pero luego atajar el RTFM es para lo que SO es, ¿no? :-)

0
Aquí hay un enlace externo que puede ser útil. introducción de APCS , especialmente algunas diferentes nombres para el uso de register .
agregado el autor artless noise, fuente

5 Respuestas

To add up missing info on NEON registers:

De AAPCS , §5.1.1 Registros básicos:

  • r0-r3 are the argument and scratch registers; r0-r1 are also the result registers
  • r4-r8 are callee-save registers
  • r9 might be a callee-save register or not (on some variants of AAPCS it is a special register)
  • r10-r11 are callee-save registers
  • r12-r15 are special registers

Desde el AAPCS, §5.1.2.1 VFP registra las convenciones de uso:

  • s16–s31 (d8–d15, q4–q7) must be preserved
  • s0–s15 (d0–d7, q0–q3) and d16–d31 (q8–q15) do not need to be preserved

Original post:
arm-to-c-calling-convention-neon-registers-to-save

0
agregado

For 64-bit ARM, A64 (from Procedure Call Standard for the ARM 64-bit Architecture)

Hay treinta y un registros de 64 bits de propósito general (enteros) visibles para el conjunto de instrucciones A64; estos están etiquetados como r0-r30 . En un contexto de 64 bits, estos registros normalmente se denominan usando los nombres x0-x30 ; en un contexto de 32 bits, los registros se especifican utilizando w0-w30 . Además, se puede usar un registro de puntero de pila, SP , con un número restringido de instrucciones.

  • SP The Stack Pointer
  • r30 LR The Link Register
  • r29 FP The Frame Pointer
  • r19…r28 Callee-saved registers
  • r18 The Platform Register, if needed; otherwise a temporary register.
  • r17 IP1 The second intra-procedure-call temporary register (can be used by call veneers and PLT code); at other times may be used as a temporary register.
  • r16 IP0 The first intra-procedure-call scratch register (can be used by call veneers and PLT code); at other times may be used as a temporary register.
  • r9…r15 Temporary registers
  • r8 Indirect result location register
  • r0…r7 Parameter/result registers

Los primeros ocho registros, r0-r7 , se usan para pasar valores de argumento a una subrutina y para devolver valores de resultado de una función. También se pueden usar para mantener valores intermedios dentro de una rutina (pero, en general, solo entre llamadas de subrutina).

Los registros r16 (IP0) y r17 (IP1) pueden ser utilizados por un enlazador como un registro reutilizable entre una rutina y cualquier subrutina que invoque. También se pueden usar dentro de una rutina para mantener valores intermedios entre llamadas de subrutina.

El rol de registrar r18 es específico de la plataforma. Si una plataforma ABI necesita un registro de propósito general dedicado para llevar el estado entre procedimientos (por ejemplo, el contexto de subprocesos), entonces debe usar este registro para ese fin. Si la plataforma ABI no tiene tales requisitos, entonces debe usar r18 como un registro temporal adicional. La especificación de la plataforma ABI debe documentar el uso de este registro.

SIMD

La arquitectura ARM de 64 bits también tiene otros treinta y dos registros, v0-v31 , que pueden ser utilizados por SIMD y operaciones de coma flotante. El nombre exacto del registro cambiará indicando el tamaño del acceso.

Note: Unlike in AArch32, in AArch64 the 128-bit and 64-bit views of a SIMD and Floating-Point register do not overlap multiple registers in a narrower view, so q1, d1 and s1 all refer to the same entry in the register bank.

Los primeros ocho registros, v0-v7 , se utilizan para pasar valores de argumento a una subrutina y para devolver valores de resultado de una función. También se pueden usar para mantener valores intermedios dentro de una rutina (pero, en general, solo entre llamadas de subrutina).

Los registros v8-v15 deben ser preservados por un destinatario en llamadas de subrutina; los registros restantes ( v0-v7, v16-v31 ) no necesitan conservarse (o la persona que llama debe conservarlos). Además, solo se deben conservar los 64 bits inferiores de cada valor almacenado en v8-v15 ; es responsabilidad del que llama para preservar valores más grandes.

0
agregado

Depende de la ABI de la plataforma para la que está compilando. En Linux, hay dos ABI ARM; el viejo y el nuevo. AFAIK, el nuevo (EABI) es, de hecho, el AAPCS de ARM. Las definiciones completas de EABI actualmente viven aquí en ARM's infocenter .

De AAPCS, §5.1.1 :

  • r0-r3 are the argument and scratch registers; r0-r1 are also the result registers
  • r4-r8 are callee-save registers
  • r9 might be a callee-save register or not (on some variants of AAPCS it is a special register)
  • r10-r11 are callee-save registers
  • r12-r15 are special registers

El destinatario de la llamada debe guardar un registro de guardado de llamada (en oposición a un registro de salvar llamada, donde la persona que llama guarda el registro); entonces, si este es el ABI que está usando, no tiene que guardar r10 antes de llamar a otra función (la otra función es responsable de guardarla).

Edit: Which compiler you are using makes no difference; gcc in particular can be configured for several different ABIs, and it can even be changed on the command line. Looking at the prologue/epilogue code it generates is not that useful, since it is tailored for each function and the compiler can use other ways of saving a register (for instance, saving it in the middle of a function).

0
agregado
"Puede descargar toda la especificación ABI y sus documentos de soporte y código de ejemplo como un archivo ZIP desde esta página". Archivo Zip: infocenter.arm.com/help/topic /com.arm.doc.ihi0036b/bsabi.zip
agregado el autor jww, fuente
Gracias, esto parece sonar algunas campanas. Creo que el primer "r0-r4" en su lista es un error tipográfico, ¿verdad? +1 (y probablemente la mejor respuesta a menos que haya un cambio radical)
agregado el autor richq, fuente
Estoy repasando este documento de PCS y tengo esta duda, los registros de variables v1-v8 se usan para guardar variables locales, de ser así, ¿qué sucede cuando asigno más variables locales? No puedo conectar stack y estos registros ...
agregado el autor Xavier Geoffrey, fuente
Sí, fue un error tipográfico (y no el único, pero arreglé los otros antes de presionar enviar la primera vez, o eso espero).
agregado el autor CesarB, fuente
En resumen: cuando se llama a una función C, los registros r0-r3, r12 (y tal vez r9) deben guardarse. Desde mi experiencia, gcc usa r12 como un registro de raspado dentro de una función y, por lo tanto, no se guarda automáticamente, incluso si no se usa el interfuncionamiento de brazo/pulgar. En caso de interfuncionamiento, el enlazador generará un código de pegamento que usa r12 si una función de brazo llama a una función de pulgar.
agregado el autor Sven, fuente
El comentario de Alex es confuso ya que es desde el punto de vista del llamado. La pregunta discutida aquí es desde el punto de vista de la persona que llama. Una persona que llama NO necesita guardar r4-r11 cuando llama a una función C. La función C (el destinatario) guardará estos registros. Además, ¿por qué nadie aclara si R9 necesita ser salvado por la persona que llama o no? Creo que para una cadena de herramientas de brazo-eabi-gcc, r9 también está guardado. ¿Quién puede señalar una fuente de información que resuelva el problema r9?
agregado el autor Sven, fuente
Creo que es mucho más fácil recordar que tienes que guardar y restaurar r4-r11 en caso de que los vayas a usar; es por eso que están callee-salvados.
agregado el autor amc, fuente
Para extender el comentario de amorenoc: r4-r11 (quizás con la excepción de r9 ) se puede considerar "seguro" cuando se llama a una función. r0-r3 probablemente no se conserve después de la llamada a la función, y dependiendo de cómo se realice el enlace, tampoco lo hará r12 (que se puede usar como un registro reutilizable).
agregado el autor Leo, fuente

Las respuestas de CesarB y Pavel proporcionaron citas de AAPCS, pero quedan cuestiones pendientes. ¿El destinatario guarda r9? ¿Qué hay de r12? ¿Qué hay de r14? Además, las respuestas fueron muy generales, y no específicas para la cadena de herramientas de arm-eabi según lo solicitado. Aquí hay un enfoque práctico para averiguar qué registro son guardados en línea y cuáles no.

El siguiente código C contiene un bloque de ensamblaje en línea, que pretende modificar los registros r0-r12 y r14. El compilador generará el código para guardar los registros requeridos por el ABI.

void foo() {
  asm volatile ( "nop" : : : "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "r14");
}

Use la línea de comando arm-eabi-gcc-4.7 -O2 -S -o - foo.c y agregue los switches para su plataforma (como -mcpu = arm7tdmi por ejemplo). El comando imprimirá el código ensamblador generado en STDOUT. Puede parecerse a esto:

foo:
    stmfd   sp!, {r4, r5, r6, r7, r8, r9, sl, fp, lr}
    nop
    ldmfd   sp!, {r4, r5, r6, r7, r8, r9, sl, fp, lr}
    bx  lr

Tenga en cuenta que el código generado por el compilador guarda y restaura r4-r11. El compilador no guarda r0-r3, r12. Que restaura r14 (alias lr) es puramente accidental ya que sé por experiencia que el código de salida también puede cargar la lr guardada en r0 y luego hacer un "bx r0" en lugar de "bx lr". Al agregar el -mcpu = arm7tdmi -mno-thumb-interwork o al usar -mcpu = cortex-m4 -mthumb obtenemos un código de ensamblaje ligeramente diferente que se ve así:

foo:
    stmfd   sp!, {r4, r5, r6, r7, r8, r9, sl, fp, lr}
    nop
    ldmfd   sp!, {r4, r5, r6, r7, r8, r9, sl, fp, pc}

De nuevo, r4-r11 se guardan y restauran. Pero r14 (alias lr) no se restaura.

Para resumir:

  • r0-r3 no se guardan llamadas
  • r4-r11 son guardados en línea
  • r12 (alias ip) está no guardado en línea
  • r13 (alias sp) está guardado en línea
  • r14 (alias lr) no callee-saved
  • r15 (alias pc) es el contador del programa y se establece en el valor de lr antes de la llamada a la función

Esto se cumple al menos para los valores predeterminados de arm-eabi-gcc. Hay conmutadores de línea de comando (en particular, el conmutador -mabi) que pueden influir en los resultados.

0
agregado
Ah, los registros están almacenados para interrupciones solo por esta razón. De todos modos estamos de acuerdo en estar en desacuerdo.
agregado el autor artless noise, fuente
Veo que tienes un punto en un sentido hipotético; Puede escribir algún ensamblador que devuelva un puntero de función en lr . Sin embargo, no veo qué tendría el valor original de lr . Está ejecutando el código que estaba en el lr a su regreso, por lo que su valor original es explícito mediante la ejecución del código. Bien resumido por Pavel ya que r12-r15 son registros especiales . El valor de lr en la llamada será el valor de la pc en la salida. La pregunta de si el lr está restaurado o no me pare
agregado el autor artless noise, fuente
Consulte enlace ARM y puntero de marco para obtener detalles sobre pc y lr . r12 también se conoce como ip y se puede usar durante un prólogo y epílogo . Es un registro volátil . Esto es importante para las rutinas que analizan la pila/cuadros de llamadas.
agregado el autor artless noise, fuente
Su análisis es incorrecto ; el lr está reventado como el pc para una forma más rápida de regresar. La respuesta a su pregunta r9 está en APCS . Se llama base estática en este documento y la sección Reentrante frente a código no reentrante es relativa. El APCS admite varias configuraciones, pero gcc en general es reentrante sin límites de pila . Especial
agregado el autor artless noise, fuente
Esto es exactamente lo que estaba buscando: una forma de averiguar qué registros se conservan mediante la configuración específica del compilador que estoy usando para mi proyecto. ¡Gracias!
agregado el autor TonyK, fuente
¿En qué sentido es incorrecto mi análisis con respecto a lr ? Creo que me malinterpretaste De todos modos, estaba presentando el segundo fragmento de código de ensamblaje, ya que el primero parecía que lr se había guardado. Sin embargo, creo que no es así. Sí, en el segundo fragmento, lr aparece como pc como una forma más rápida de regresar y no lo explique, pero el objetivo de presentar el segundo fragmento fue que muestra que lr no se ha guardado.
agregado el autor Sven, fuente
Es cierto que lr se restaura en pc . Pero no es cierto, se puede esperar que se restaure el valor de lr . No veo cómo esto puede estar mal. Que el valor termine en un registro que no es lr es completamente irrelevante para la pregunta de si lr se restablece o no. Tiene razón en que el conjunto de registros que se restaura y no se restaura puede cambiar a medida que cambia la opción -mabi .
agregado el autor Sven, fuente
En este momento, estoy escribiendo un contenedor ensamblador para interrupciones anidadas en ARM7TDMI. Si el valor de lr se conserva mediante una llamada al código C es importante, ya que el valor de lr debe ser restaurado a su valor anterior por el warpper de ensamblador. Por lo tanto, saber si lr está guardado en línea no es tan hipotético.
agregado el autor Sven, fuente

También hay diferencia, al menos, en la arquitectura Cortex M3 para llamar e interrumpir funciones.

Si se produce una interrupción, se aplicarán automáticamente R0-R3, R12, LR, PC en la pila y cuando se devuelva la forma de IRQ POP automática. Si usa otros registros en la rutina IRQ, tiene que empujarlos/pop en Stack manualmente.

No creo que este PUSH y POP automáticos esté hecho para una llamada de función (instrucción de salto). Si la convención dice que R0-R3 se puede usar solo como un argumento, resultados o registros reutilizables, entonces no hay necesidad de almacenarlos antes de la llamada a la función porque no debería haber ningún valor usado más tarde después del retorno a la función. Pero al igual que en una interrupción, debe almacenar todos los otros registros de la CPU si los usa en su función.

0
agregado