web-dev-qa-db-fra.com

Quels registres enregistrer dans la convention d'appel ARM C?

Cela fait un moment que je n'ai pas assemblé de bras codé pour la dernière fois et je suis un peu rouillé sur les détails. Si j'appelle une fonction C à partir du bras, je n'ai qu'à me soucier de sauvegarder r0-r3 et lr, non?

Si la fonction C utilise d'autres registres, est-elle responsable de les enregistrer sur la pile et de les restaurer? En d'autres termes, le compilateur générerait du code pour ce faire pour les fonctions C.

Par exemple, si j'utilise r10 dans une fonction d'assembleur, je n'ai pas besoin de pousser sa valeur sur la pile ou dans la mémoire et de la faire apparaître/restaurer après un appel C, n'est-ce pas?

C'est pour arm-eabi-gcc 4.3.0.

50
richq

Cela dépend de ABI pour la plateforme pour laquelle vous compilez. Sous Linux, il y a deux ARM ABI; l'ancien et le nouveau. AFAIK, le nouveau (EABI) est en fait AAPCS d'ARM. Les définitions complètes d'EABI vivent actuellement ici sur l'infocentre d'ARM .

De l'AAPCS, §5.1.1 :

  • r0-r3 sont les registres d'argument et de scratch; r0-r1 sont également les registres de résultats
  • r4-r8 sont des registres de sauvegarde des appels
  • r9 peut être un registre sauvegardé ou non (sur certaines variantes d'AAPCS, c'est un registre spécial)
  • r10-r11 sont des registres de sauvegarde des appels
  • r12-r15 sont des registres spéciaux

Un registre de sauvegarde de l'appelé doit être sauvegardé par l'appelé (contrairement à un registre de sauvegarde de l'appelant, où l'appelant sauvegarde le registre); donc, si c'est l'ABI que vous utilisez, vous n'avez pas besoin de sauvegarder r10 avant d'appeler une autre fonction (l'autre fonction est responsable de la sauvegarder).

Edit: Le compilateur que vous utilisez ne fait aucune différence; gcc en particulier peut être configuré pour plusieurs ABI différents, et il peut même être modifié sur la ligne de commande. Regarder le code prologue/épilogue qu'il génère n'est pas très utile, car il est adapté à chaque fonction et le compilateur peut utiliser d'autres façons de sauvegarder un registre (par exemple, en le sauvegardant au milieu d'une fonction).


Terminologie: "callee-save" est un synonyme de "non volatile" ou "call-protected": Que sont les registres sauvegardés de l'appelé et de l'appelant?
Lors d'un appel de fonction, vous pouvez supposer que les valeurs de r4-r11 (sauf peut-être r9) sont toujours là après (appel conservé), mais pas pour r0-r3 (appel clobbered/volatile).

69
CesarB

Pour ajouter des informations manquantes sur les registres NEON:

De l'AAPCS , §5.1.1 Registres de base:

  • r0-r3 sont les registres d'argument et de scratch; r0-r1 sont également les registres de résultats
  • r4-r8 sont des registres de sauvegarde des appels
  • r9 peut être un registre sauvegardé ou non (sur certaines variantes d'AAPCS, c'est un registre spécial)
  • r10-r11 sont des registres de sauvegarde des appels
  • r12-r15 sont des registres spéciaux

À partir de l'AAPCS, §5.1.2.1 Conventions d'utilisation des registres VFP:

  • s16 – s31 (d8 – d15, q4 – q7) doit être conservé
  • s0 – s15 (d0 – d7, q0 – q3) et d16 – d31 (q8 – q15) n'a pas besoin d'être conservé

Message d'origine:
arm-to-c-calling-convention-neon-registers-to-save

24
Pavel

Pour ARM 64 bits, A64 (à partir de la norme d'appel de procédure pour ARM architecture 64 bits)

Il y a 31 registres 64 bits à usage général (entier) visibles par le jeu d'instructions A64; ceux-ci sont étiquetés r0-r. Dans un contexte 64 bits, ces registres sont normalement appelés en utilisant les noms x0-x; dans un contexte 32 bits, les registres sont spécifiés en utilisant w0-w. De plus, un registre de pointeur de pile, SP , peut être utilisé avec un nombre restreint d'instructions.

  • SP Le pointeur de pile
  • r LR Le registre des liens
  • r29 FP Le pointeur de cadre
  • r19… r28 Registres sauvegardés par l'appelé
  • r18 Le registre de la plate-forme, si nécessaire; sinon un registre temporaire.
  • r17 IP1 Le deuxième registre temporaire d'appel intra-procédure (peut être utilisé par les facettes d'appel et le code PLT); à d'autres moments, peut être utilisé comme registre temporaire.
  • r16 IP0 Le premier registre à zéro d'appel intra-procédure (peut être utilisé par les facettes d'appel et le code PLT); à d'autres moments, peut être utilisé comme registre temporaire.
  • r9… r15 Registres temporaires
  • r8 Registre de localisation des résultats indirects
  • r0… r7 Registres de paramètres/résultats

Les huit premiers registres, r0-r7, sont utilisés pour passer des valeurs d'argument dans un sous-programme et pour retourner les valeurs de résultat d'une fonction. Ils peuvent également être utilisés pour conserver des valeurs intermédiaires dans une routine (mais, en général, uniquement entre les appels de sous-programme).

Les registres r16 (IP0) et r17 (IP1) peuvent être utilisés par un éditeur de liens comme registre de travail entre une routine et tout sous-programme qu'il appelle. Ils peuvent également être utilisés dans une routine pour conserver des valeurs intermédiaires entre les appels de sous-programme.

Le rôle de register r18 est spécifique à la plateforme. Si une plateforme ABI a besoin d'un registre à usage général dédié pour transporter l'état inter-procédural (par exemple, le contexte du thread), elle doit utiliser ce registre à cette fin. Si la plate-forme ABI n'a pas de telles exigences, elle doit utiliser r18 comme registre temporaire supplémentaire. La spécification ABI de la plate-forme doit documenter l'utilisation de ce registre.

SIMD

L'architecture ARM 64 bits possède également trente-deux registres supplémentaires, v0-v31, qui peuvent être utilisés par les opérations SIMD et à virgule flottante. Le nom précis du registre changera en indiquant la taille de l'accès.

Remarque: Contrairement à AArch32, dans AArch64, les vues 128 bits et 64 bits d'un registre SIMD et à virgule flottante ne chevauchent pas plusieurs registres dans une vue plus étroite, donc q1, d1 et s1 font tous référence à la même entrée dans la banque de registres.

Les huit premiers registres, v0-v7, sont utilisés pour passer des valeurs d'argument dans un sous-programme et pour retourner les valeurs de résultat d'une fonction. Ils peuvent également être utilisés pour conserver des valeurs intermédiaires dans une routine (mais, en général, uniquement entre les appels de sous-programme).

Les registres v8-v15 doivent être conservés par un appelé à travers les appels de sous-programme; les registres restants (v0-v7, v16-v31) n'ont pas besoin d'être conservés (ou doivent être conservés par l'appelant). De plus, seuls les 64 derniers bits de chaque valeur stockée dans v8-v15 doivent être conservés; il est de la responsabilité de l'appelant de conserver des valeurs plus importantes.

17
auselen

Les réponses de CesarB et Pavel ont fourni des citations de l'AAPCS, mais des questions en suspens demeurent. L'appelé enregistre-t-il r9? Et la r12? Et la r14? De plus, les réponses étaient très générales et n'étaient pas spécifiques à la chaîne d'outils arm-eabi comme demandé. Voici une approche pratique pour savoir quels registres sont enregistrés et ceux qui ne le sont pas.

Le code C suivant contient un bloc d'assemblage en ligne, qui prétend modifier les registres r0-r12 et r14. Le compilateur générera le code pour enregistrer les registres requis par l'ABI.

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

Utilisez la ligne de commande arm-eabi-gcc-4.7 -O2 -S -o - foo.c et ajoutez les commutateurs de votre plate-forme (tels que -mcpu=arm7tdmi par exemple). La commande imprimera le code d'assemblage généré sur STDOUT. Cela peut ressembler à ceci:

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

Notez que le code généré par le compilateur enregistre et restaure r4-r11. Le compilateur n'enregistre pas r0-r3, r12. Le fait qu'il restaure r14 (alias lr) est purement accidentel car je sais par expérience que le code de sortie peut également charger le lr enregistré dans r0 puis faire un "bx r0" au lieu de "bx lr". Soit en ajoutant le -mcpu=arm7tdmi -mno-thumb-interwork ou en utilisant -mcpu=cortex-m4 -mthumb nous obtenons un code d'assemblage légèrement différent qui ressemble à ceci:

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

Encore une fois, r4-r11 sont enregistrés et restaurés. Mais r14 (alias lr) n'est pas restauré.

Résumer:

  • r0-r3 sont pas sauvegardés par l'appelé
  • r4-r11 sont enregistrés
  • r12 (alias ip) est pas sauvegardé par l'appelé
  • r13 (alias sp) est enregistré
  • r14 (alias lr) est pas sauvegardé par l'appelé
  • r15 (alias pc) est le compteur de programme et est défini sur la valeur de lr avant l'appel de fonction

Cela vaut au moins pour les valeurs par défaut de arm-eabi-gcc. Il existe des commutateurs de ligne de commande (en particulier le commutateur -mabi) qui peuvent influencer les résultats.

7
Sven

Il existe également une différence au moins au niveau de l'architecture Cortex M3 pour l'appel et l'interruption de fonction.

Si une interruption se produit, elle fera automatiquement Pousser R0-R3, R12, LR, PC sur la pile et lors du retour du formulaire IRQ POP automatique. Si vous utilisez d'autres registres dans la routine IRQ, vous devez les pousser/sauter manuellement sur la pile.

Je ne pense pas que ce Push and POP automatique soit fait pour un appel de fonction (instruction de saut). Si la convention indique que R0-R3 ne peut être utilisé que comme registre d'argument, de résultat ou de travail, il n'est donc pas nécessaire de les stocker avant l'appel de fonction car il ne devrait pas y avoir de valeur utilisée plus tard après le retour de la fonction. Mais comme pour une interruption, vous devez stocker tous les autres registres CPU si vous les utilisez dans votre fonction.

0
Frik