J'ai du mal à comprendre la différence entre les registres enregistrés de l'appelant et de l'appelé et quand utiliser quoi.
J'utilise le MSP430:
procédure:
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
le code ci-dessus est un appelé et a été utilisé dans un exemple de manuel, il suit donc la convention. R6 et R7 sont enregistrés et l'appelé R12 est enregistré. Ma compréhension est que les regs enregistrés de l'appelé ne sont pas "globaux" dans le sens où le changement de sa valeur dans une procédure n'affectera pas sa valeur en dehors de la procédure. C'est pourquoi vous devez enregistrer une nouvelle valeur dans le registre de l'appelé au début.
R12, l'appelant enregistré est "global", faute de meilleurs mots. Ce que fait la procédure a un effet durable sur R12 après l'appel.
Ma compréhension est-elle correcte? Suis-je en train de manquer d'autres choses?
registres sauvegardés par l'appelant (AKA volatile registres, ou call-clobbered) sont utilisés pour conserver des quantités temporaires qui n'ont pas besoin d'être préservées entre les appels.
Pour cette raison, il est de la responsabilité de l'appelant de pousser ces registres sur la pile ou de les copier ailleurs si il veut restaurer cette valeur après un appel de procédure .
Il est cependant normal de laisser un call
détruire les valeurs temporaires dans ces registres.
registres sauvegardés par l'appelé (AKA non volatils les registres, ou préservés par l'appel) sont utilisés pour contenir des valeurs à longue durée de vie qui doivent être préservées sur appels.
Lorsque l'appelant effectue un appel de procédure, il peut s'attendre à ce que ces registres conservent la même valeur après le retour de l'appelé, ce qui fait qu'il incombe à l'appelé de les enregistrer et de les restaurer avant de revenir à l'appelant. Ou de ne pas les toucher.
Appelé vs appelant enregistré est une convention qui détermine qui est responsable de l'enregistrement et de la restauration de la valeur dans un registre sur un appel. TOUS les registres sont "globaux" en ce que n'importe quel code n'importe où peut voir (ou modifier) un registre et ces modifications seront vues par n'importe quel code ultérieur n'importe où. Le point des conventions d'enregistrement de registre est que le code n'est pas censé modifier certains registres, car un autre code suppose que la valeur n'est pas modifiée.
Dans votre exemple de code, AUCUN des registres n'est appelé sauvegarde, car il n'essaie pas de sauvegarder ou de restaurer les valeurs de registre. Cependant, il semblerait que ce ne soit pas une procédure complète, car elle contient une branche vers une étiquette non définie (l$loop
). Il peut donc s'agir d'un fragment de code provenant du milieu d'une procédure qui traite certains registres comme des sauvegardes appelées; il vous manque juste les instructions de sauvegarde/restauration.
La terminologie sauvegardée par l'appelant/sauvegardée par l'appelant est basée sur un modèle de programmation inefficace assez braindead où les appelants sauvegardent/restaurent tous les registres clobés (au lieu de conserver des valeurs utiles à long terme ailleurs), et les callees enregistrent réellement/restaurer tous les registres préservés par les appels (au lieu de ne pas en utiliser certains ou certains d'entre eux).
Ou vous devez comprendre que "sauvé par l'appelant" signifie "sauvé d'une manière ou d'une autre si vous voulez la valeur plus tard".
En réalité, un code efficace permet de détruire des valeurs lorsqu'elles ne sont plus nécessaires. Les compilateurs créent généralement des fonctions qui enregistrent quelques registres préservés des appels au début d'une fonction (et les restaurent à la fin). À l'intérieur de la fonction, ils utilisent ces regs pour les valeurs qui doivent survivre à travers les appels de fonction.
Je préfère "préservé par appel" par rapport à "clobé par appel" , qui sont sans ambiguïté et auto-décrivant une fois que vous avez entendu parler du concept de base, et n'exigent aucune gymnastique mentale sérieuse à penser du point de vue de l'appelant ou du point de vue de l'appelé. (Les deux termes sont du point de vue même).
De plus, ces termes diffèrent par plus d'une lettre.
Les termes volatile/non volatile sont assez bons, par analogie avec le stockage qui perd sa valeur en cas de perte de puissance ou non, (comme DRAM vs Flash ). Mais le mot clé C volatile
a une signification technique totalement différente, c'est donc un inconvénient de "(non) -volatile" lors de la description des conventions d'appel C.
Du point de vue de l'appelé, votre fonction peut librement écraser (alias clobber) ces registres sans enregistrer/restaurer.
Du point de vue de l'appelant, call foo
détruit (alias clobbers) tous les registres clobber, ou du moins vous devez supposer que c'est le cas.
Vous pouvez écrire des fonctions d'assistance privées qui ont une convention d'appel personnalisée, par exemple vous savez qu'ils ne modifient pas un certain registre. Mais si tout ce que vous savez (ou si vous voulez supposer ou dépendre) est que la fonction cible suit la convention d'appel normale, alors vous devez traiter un appel de fonction comme s'il détruisait tous les registres encombrés d'appels. C'est littéralement de cela que vient le nom: un appel tapote ces registres.
Certains compilateurs qui effectuent une optimisation inter-procédurale peuvent également créer des définitions à usage interne uniquement de fonctions qui ne suivent pas l'ABI, à l'aide d'une convention d'appel personnalisée.
Du point de vue de l'appelé, ces registres ne peuvent être modifiés que si vous enregistrez la valeur d'origine quelque part afin de pouvoir la restaurer avant de revenir. Ou pour les registres comme le pointeur de pile (qui est presque toujours préservé des appels), vous pouvez soustraire un décalage connu et l'ajouter à nouveau avant de revenir, au lieu de réellement enregistrer l'ancienne valeur n'importe où. c'est-à-dire que vous pouvez le restaurer à l'estime, sauf si vous allouez une quantité d'espace de pile variable à l'exécution. Ensuite, vous restaurez généralement le pointeur de pile à partir d'un autre registre.
Une fonction qui peut bénéficier de l'utilisation de nombreux registres peut enregistrer/restaurer certains registres préservés par les appels afin de pouvoir les utiliser comme plus temporaires, même si elle ne fait aucun appel de fonction. Normalement, vous ne le feriez qu'après avoir épuisé les registres clappés à utiliser, car l'enregistrement/la restauration coûte généralement un Push/pop au début/à la fin de la fonction. (Ou si votre fonction a plusieurs chemins de sortie, un pop
dans chacun d'eux.)
Le nom "enregistré par l'appelant" est trompeur: vous n'avez pas avez pour les sauvegarder/restaurer spécialement. Normalement, vous organisez votre code pour qu'il ait des valeurs qui doivent survivre à un appel de fonction dans des registres préservés par appel, ou quelque part sur la pile, ou ailleurs que vous pouvez recharger. Il est normal de laisser un call
détruire les valeurs temporaires.
Voir par exemple Quels registres sont conservés via un appel de fonction linux x86-64 pour le x86-64 System V ABI.
De plus, les registres qui passent l'argument sont toujours clobés dans toutes les conventions d'appel de fonction que je connais. Voir Les appelants rdi et rsi sont-ils enregistrés ou enregistrés dans l'appelé?
Mais les conventions d'appel système appellent généralement tous les registres à l'exception de la valeur de retour préservée par l'appel. (Incluant généralement même les codes de condition/drapeaux.) Voir Quelles sont les conventions d'appel pour les appels système UNIX et Linux sur i386 et x86-64
En plus des réponses ci-dessus, il est bon d'utiliser des registres enregistrés pour les variables dont les valeurs doivent être conservées après l'appel de fonction car il peut ne pas être nécessaire de sauvegarder les valeurs de registre dans la fonction appelée. Sinon, utilisez des registres sauvegardés par l'appelant si le contenu des variables n'a pas besoin d'être enregistré après l'appel de la fonction car aucune sauvegarde n'est nécessaire dans la fonction appelant ou la fonction appelée.