¿Qué registra para guardar en la convención de llamadas ARM C?

Ha pasado un tiempo desde la última vez que codifiqué ensamblador de arm y estoy un poco oxidado sobre los detalles. Si llamo una función C desde el arm, 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 stack y restaurarlos? En otras palabras, el comstackdor 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 stack, 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.

Depende de la ABI de la plataforma para la que está comstackndo. 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 el infocentro de ARM .

Del AAPCS, §5.1.1 :

  • r0-r3 son los registros de argumento y cero; r0-r1 son también los registros de resultados
  • r4-r8 son registros de guardado de llamadas
  • r9 podría ser un registro callee-save o no (en algunas variantes de AAPCS es un registro especial)
  • r10-r11 son registros de guardado de llamadas
  • r12-r15 son registros especiales

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 guardarlo).

Editar: El comstackdor que está utilizando no hace diferencia; gcc, en particular, se puede configurar para varios ABI diferentes, e incluso se puede cambiar en la línea de comando. Ver el código de prólogo / epílogo que genera no es tan útil, ya que está diseñado para cada función y el comstackdor puede usar otras formas de guardar un registro (por ejemplo, guardarlo en el medio de una función).

Para agregar información faltante en los registros NEON:

De AAPCS , §5.1.1 Registros básicos:

  • r0-r3 son los registros de argumento y cero; r0-r1 son también los registros de resultados
  • r4-r8 son registros de guardado de llamadas
  • r9 podría ser un registro callee-save o no (en algunas variantes de AAPCS es un registro especial)
  • r10-r11 son registros de guardado de llamadas
  • r12-r15 son registros especiales

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

  • s16-s31 (d8-d15, q4-q7) debe conservarse
  • s0-s15 (d0-d7, q0-q3) y d16-d31 (q8-q15) no necesitan ser preservados

Publicación original:
arm-to-c-calling-convention-neon-registers-to-save

Para ARM de 64 bits, A64 (del estándar de llamada a procedimiento para la architecture ARM de 64 bits)

Hay treinta y un registros de 64 bits de propósito general (enteros) visibles para el conjunto de instrucciones A64; estos están etiquetados r0-r30 . En un contexto de 64 bits, estos registros normalmente se denominan utilizando 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 stack, SP , con un número restringido de instrucciones.

  • SP El puntero de stack
  • r30 LR El registro de enlaces
  • r29 FP El puntero de marco
  • r19 … r28 Registros guardados de Callee
  • r18 The Platform Register, si es necesario; de lo contrario, un registro temporal.
  • r17 IP1 El segundo registro temporal de llamada dentro del procedimiento (puede ser utilizado por chapas de llamada y código PLT); en otros momentos se puede usar como un registro temporal.
  • r16 IP0 El primer registro de scratch de llamada dentro del procedimiento (puede ser utilizado por chapas de llamada y código PLT); en otros momentos se puede usar como un registro temporal.
  • r9 … r15 Registros temporales
  • r8 Registro de ubicación de resultado indirecto
  • r0 … r7 Registros de parámetros / resultados

Los primeros ocho registros, r0-r7 , se utilizan para pasar valores de argumento en 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 de cero 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 del registro 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 architecture 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.

Nota: A diferencia de AArch32, en AArch64 las vistas de 128 bits y 64 bits de un SIMD y un registro de coma flotante no se superponen a múltiples registros en una vista más estrecha, por lo que q1, d1 y s1 se refieren a la misma entrada en el registro banco.

Los primeros ocho registros, v0-v7 , 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 v8-v15 deben ser preservados por un destinatario en llamadas de subrutina; los registros restantes ( v0-v7, v16-v31 ) no necesitan ser preservados (o deben ser preservados por la persona que llama). 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.

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 comstackdor 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 comstackdor guarda y restaura r4-r11. El comstackdor 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 -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 son guardados en línea
  • r4-r11 son guardados en línea
  • r12 (alias ip) no está guardado en línea
  • r13 (alias sp) está guardado en línea
  • r14 (alias lr) no está guardado en línea
  • r15 (alias pc) es el contador del progtwig 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.

También hay diferencia, al menos, en la architecture 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 stack 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.