En informatique, une convention d'appel est un ensemble de règles qui décrit comment une fonction reçoit des paramètres et renvoie un résultat. Cela inclut des choix techniques sur où et comment les informations sont envoyées à la fonction appelée, et où et comment le résultat est renvoyé. En général, ces transferts se font soit par des registres, soit en utilisant une "pile" spéciale qui stocke temporairement des informations.
Il y a aussi des règles pour déterminer qui, entre l'appelant (celui qui appelle la fonction) et l'appelé (la fonction elle-même), est responsable de préparer et nettoyer après l'appel. Respecter la convention d'appel est essentiel pour que le programme fonctionne correctement et sans erreurs.
Les conventions d’appel font généralement partie de l’interface binaire d’une application (ABI). Elles servent de contrat entre celui qui appelle la fonction et la fonction elle-même.
Les noms et significations des paramètres et des valeurs de retour sont définis dans l'interface de programmation d'application (API), qui est liée mais distincte de l'ABI et de la convention d'appel. Les noms des éléments à l'intérieur des structures et des objets transmis font aussi partie de l'API, pas de l'ABI. Parfois, les API incluent des mots-clés pour indiquer la convention d'appel des fonctions.
Les conventions d’appel n’incluent généralement pas d’informations sur la gestion de la durée de vie des structures et objets alloués dynamiquement. D'autres documentations complémentaires peuvent indiquer à qui incombe la responsabilité de libérer la mémoire allouée.
Il est peu probable que les conventions d'appel spécifient la disposition des éléments dans les structures et les objets, comme l'ordre des octets ou le regroupement des structures.
Pour certains langages, la convention d'appel inclut des détails sur la gestion des erreurs ou des exceptions (par exemple Go, Java ) et pour d'autres, ce n'est pas le cas (par exemple C++ ).
Pour les appels de procédure à distance, il existe un concept analogue appelé Marshalling.
Les conventions d'appel peuvent être influencées par la stratégie d'évaluation d'un langage de programmation, mais elles ne sont généralement pas considérées comme faisant partie du langage lui-même. La stratégie d'évaluation appartient à un niveau d'abstraction plus élevé, défini comme une caractéristique du langage, tandis que les conventions d'appel relèvent des détails d'implémentation à bas niveau du compilateur.
Les conventions d’appel peuvent différer dans :
Sur une même plateforme, il peut exister plusieurs conventions d'appel, et une combinaison spécifique de plateforme et de langage peut proposer différents choix. Cela permet d’optimiser les performances, de s’adapter aux conventions d'autres langages populaires ou de respecter les exigences imposées par diverses plateformes informatiques.
De nombreuses architectures utilisent une seule convention d'appel, souvent recommandée par l'architecte du système. Pour les architectures RISC comme SPARC, MIPS et RISC-V, les registres sont nommés en fonction de cette convention. Par exemple, sur MIPS, les registres $4 à $7 sont nommés $a0 à $a3 pour indiquer qu'ils servent à passer des paramètres selon la convention d'appel standard. (Les processeurs RISC disposent de nombreux registres à usage général identiques, donc il n'y a généralement pas de raison matérielle de les nommer autrement que par des numéros.)
La convention d'appel d'un langage peut être différente de celle de la plateforme, du système d'exploitation ou des bibliothèques auxquelles il est lié. Par exemple, sous Windows 32 bits, les appels système utilisent la convention d'appel stdcall, tandis que de nombreux programmes C utilisent la convention cdecl. Pour gérer ces différences, les compilateurs permettent souvent d'ajouter des mots-clés aux déclarations de fonction pour spécifier la convention d'appel à utiliser. Cela permet au compilateur de générer le code nécessaire pour appeler les fonctions correctement.
Certains langages permettent de définir explicitement la convention d'appel d'une fonction, tandis que d'autres imposent une convention sans la rendre visible aux utilisateurs, de sorte que le programmeur n’a généralement pas à s'en soucier.
push EAX ; passer le contenu du registre
push dword [EBP+20] ; passer une variable mémoire (syntaxe FASM/TASM)
push 3 ; pass une constante
call calc ; La valeur retournée est dans EAX
L'architecture x86 en 32 bits utilise de nombreuses conventions d'appel. Étant donné le faible nombre de registres disponibles et la priorité historique donnée à la simplicité et à la compacité du code, de nombreuses conventions d'appel x86 passent les arguments via la pile, et renvoient la valeur de retour (ou un pointeur vers celle-ci) dans un registre. Certaines conventions passent les premiers paramètres dans des registres, ce qui peut améliorer les performances, notamment pour les routines simples et courtes appelées fréquemment (celles qui n'appellent pas d'autres fonctions). Exemple d'appel :
calc:
push EBP ; Sauvegarder l'ancien frame pointer
mov EBP,ESP ; Avoir un nouveau pointeur de frame
sub ESP,localsize ; Reserver de l'espace pour les variables locales
.
. ; Faire des calculs, laisser le resultat dans EAX
.
mov ESP,EBP ; Liberer l'espace utilisé par les variables locales
pop EBP ; Restaurer l'ancien frame pointer
ret paramsize ; Liberer l'espace utilisé par les parametres et return
Structure d'appel typique : (certaines ou toutes les instructions ci-dessous, sauf `ret`, peuvent être optimisées dans des procédures simples). Certaines conventions laissent l'espace pour les paramètres alloué, en utilisant `ret` simple au lieu de `ret imm16`. Dans ce cas, l'appelant pourrait utiliser `add esp, 12` dans cet exemple, ou ajuster autrement ESP.
* 1130 subroutine example ENT SUB Declare "SUB" an external entry point SUB DC 0 Reserved word at entry point, conventionally coded "DC *-*" * Subroutine code begins here * If there were arguments the addresses can be loaded indirectly from the return address LDX I 1 SUB Load X1 with the address of the first argument (for example) ... * Return sequence LD RES Load integer result into ACC * If no arguments were provided, indirect branch to the stored return address B I SUB If no arguments were provided END SUB
La version 64 bits de l'architecture x86, appelée x86-64, AMD64 ou Intel 64, utilise deux séquences d'appel courantes. La première, définie par Microsoft, est utilisée sous Windows ; la seconde, spécifiée dans l'ABI AMD64 System V, est utilisée par les systèmes de type Unix et, avec quelques modifications, par OpenVMS. Étant donné que le x86-64 dispose de plus de registres à usage général que le x86 en 32 bits, les deux conventions transmettent certains arguments directement dans des registres.
La convention d'appel ARM 32 bits standard alloue les 16 registres à usage général comme suit :
Si la valeur renvoyée est trop grande pour tenir dans les registres r0 à r3, ou si sa taille ne peut pas être déterminée statiquement lors de la compilation, l'appelant doit allouer de l'espace pour cette valeur pendant l'exécution et passer un pointeur vers cet espace dans r0.
Les sous-routines doivent conserver le contenu des registres r4 à r11 ainsi que le pointeur de pile. Cela peut être fait en les enregistrant dans la pile au début de la fonction (prologue), puis en les utilisant comme espace de travail, et enfin en les restaurant à partir de la pile à la fin de la fonction (épilogue). De plus, les sous-routines qui appellent d'autres sous-routines doivent sauvegarder l'adresse de retour dans le registre de liaison r14, en la plaçant dans la pile avant l'appel. Toutefois, ces sous-routines n'ont pas besoin de renvoyer cette valeur à r14 ; elles doivent simplement la charger dans r15, le compteur de programme, pour revenir.
La convention d'appel ARM exige l'utilisation d'une pile entièrement descendante. De plus, le pointeur de pile doit toujours être aligné sur 4 octets et sur 8 octets lors d'un appel de fonction avec une interface publique.
Cette convention d'appel amène une sous-routine ARM « typique » à :
La convention d'appel ARM 64 bits ( AArch64) alloue les 31 registres à usage général comme suit [2]:
Tous les registres commençant par x ont un registre 32 bits correspondant préfixé par w . Ainsi, un x0 32 bits est appelé w0.
De même, les 32 registres à virgule flottante sont alloués comme suit[3] :
RISC-V a une convention d'appel qui se décline en deux versions, avec ou sans support pour les nombres à virgule flottante. Elle transmet des arguments dans des registres chaque fois que cela est possible.
Les architectures POWER, PowerPC et Power ISA disposent d'un grand nombre de registres, ce qui permet à la plupart des fonctions de transmettre tous les arguments dans les registres lors des appels à un seul niveau. Les arguments supplémentaires sont passés sur la pile, et de l'espace pour les arguments basés sur des registres est toujours alloué sur la pile pour faciliter la fonction appelée, notamment en cas d'appels à plusieurs niveaux (récursifs ou autres), où les registres doivent être sauvegardés. Cela est également utile dans les fonctions variadiques, comme `printf()`, où les arguments doivent être accessibles sous forme de tableau. Une seule convention d'appel est utilisée pour tous les langages procéduraux.
Les instructions de branchement et de liaison stockent l'adresse de retour dans un registre de liaison spécial, distinct des registres à usage général. Une routine revient à son appelant en utilisant une instruction de branchement qui prend le registre de liaison comme adresse de destination. Les routines feuille n'ont pas besoin de sauvegarder ou de restaurer ce registre, tandis que les routines non feuille doivent sauvegarder l'adresse de retour avant d'appeler une autre routine et la restaurer avant de revenir. Cela se fait en utilisant l'instruction Move From Special Purpose Register pour copier le registre de liaison dans un registre à usage général, et si nécessaire, en le sauvegardant dans la pile. Pour restaurer, si l'adresse a été enregistrée dans la pile, on charge la valeur depuis la pile dans un registre à usage général, puis on utilise l'instruction Move To Special Purpose Register pour remettre cette valeur dans le registre de liaison.
L'ABI O32 est l'ABI la plus couramment utilisée, car c'est l'ABI System V d'origine pour MIPS. Elle est strictement basée sur la pile et ne dispose que de quatre registres ($a0 à $a3) pour passer des arguments. Cette limitation, ainsi qu'un ancien modèle à virgule flottante avec seulement 16 registres, ont favorisé le développement de nombreuses autres conventions d'appel. L'ABI a été établie en 1990 et n'a pas été mise à jour depuis 1994. Elle est définie uniquement pour les MIPS 32 bits, mais GCC a créé une version 64 bits appelée O64.
Pour les 64 bits, l'ABI N64 de Silicon Graphics est la plus couramment utilisée (sans lien avec la Nintendo 64). La principale amélioration est qu'elle permet de passer des arguments à l'aide de huit registres, et elle augmente également le nombre de registres à virgule flottante à 32. Il existe aussi une version ILP32 appelée N32, qui utilise des pointeurs de 32 bits pour un code plus compact, similaire à l'ABI x32. Les deux fonctionnent en mode 64 bits sur le processeur.
Quelques tentatives ont été réalisées pour remplacer O32 par un ABI 32 bits plus similaire à N32. Lors d'une conférence en 1995, le MIPS EABI a été présenté, dont la version 32 bits était assez semblable. L'EABI a ensuite inspiré MIPS Technologies à proposer une ABI « NUBI » plus radicale, qui utilise également les registres d'arguments pour renvoyer des valeurs. MIPS EABI est pris en charge par GCC, mais pas par LLVM, et aucun des deux ne prend en charge NUBI.
Pour les ABI O32 et N32/N64, l'adresse de retour est stockée dans le registre $ra. Cela est défini automatiquement lors de l'utilisation des instructions JAL (saut et liaison) ou JALR (registre de saut et de liaison). La pile grandit vers le bas.
L'architecture SPARC, contrairement à la plupart des architectures RISC, utilise des fenêtres de registre. Chaque fenêtre de registre comporte 24 registres : 8 registres « in » (%i0-%i7), 8 registres « local » (%l0-%l7) et 8 registres « out » (%o0-%o7). Les registres « in » sont utilisés pour transmettre des arguments à la fonction appelée, et tous les arguments supplémentaires doivent être placés sur la pile. Cependant, la fonction appelée alloue toujours de l'espace pour gérer un éventuel dépassement de la fenêtre de registre, des variables locales et, sur SPARC 32 bits, le renvoi d'une structure par valeur. Pour appeler une fonction, les arguments sont mis dans les registres « out » ; lorsque la fonction est appelée, ces registres deviennent les registres « in », permettant à la fonction appelée d'accéder aux arguments. À la fin de la fonction appelée, la valeur de retour est placée dans le premier registre « in », qui devient alors le premier registre « out » lors du retour de la fonction.
Le système V ABI[4], que suivent la plupart des systèmes modernes de type Unix, passe les six premiers arguments dans les registres « in » %i0 à %i5, réservant %i6 pour le pointeur de trame et %i7 pour l'adresse de retour.
L'IBM System/360 est une autre architecture sans pile matérielle. Les exemples ci-dessous illustrent la convention d'appel utilisée par OS/360 et ses successeurs avant l'introduction de l'architecture z 64 bits ; d'autres systèmes d'exploitation pour System/360 peuvent avoir des conventions d'appel différentes.
Programme d'appel :
LA 1,ARGS Charger l'adresse de la liste des arguments L 15,=A(SUB) Charger l'adresse du sous-programme BALR 14,15 Branche vers la routine appelée 1 ... ARGS DC A(FIRST) Adresse du 1er argument DC A (SECOND) ... DC A(THIRD)+X'80000000' Dernier argument 2
Programme appelé :
SUB EQU * Ceci est le point d'entrée du sous-programme
Séquence d'entrée standard :
UTILISER *,15 3 STM 14,12,12(13) Enregistrer les registres 4 ST 13, SAVE+4 Enregistrer l'adresse de la zone de sauvegarde de l'appelant LA 12, Zones de sauvegarde de la chaîne SAVE ST 12,8(13) LR 13,12 ...
Séquence de retour standard :
L 13, SAVE+45 LM 14,12,12(13) L 15,RETVAL6 BR 14 Retour à l'appelant ENREGISTRER DS 18F Zone de sauvegarde 7
Remarques :
BALR
stocke l'adresse de l'instruction suivante (adresse de retour) dans le registre spécifié par le premier argument — registre 14 — et se branche vers l'adresse du deuxième argument dans le registre 15.STM
enregistre les registres 14, 15 et 0 à 12 dans une zone de 72 octets fournie par l'appelant, appelée zone de sauvegarde pointée par le registre 13. La routine appelée fournit sa propre zone de sauvegarde à l'usage des sous-routines qu'elle appelle ; l'adresse de cette zone est normalement conservée dans le registre 13 tout au long de la routine. Les instructions qui suivent STM
mettent à jour les chaînes avant et arrière reliant cette zone de sauvegarde à la zone de sauvegarde de l'appelant.savearea
de manière statique dans la routine appelée la rend non réentrante et non récursive ; un programme réentrant utilise une savearea
dynamique, acquise soit à partir du système d'exploitation et libérée au retour, soit dans la mémoire transmise par le programme appelant.Dans l'ABI System/390 [5] et l'ABI z/Architecture [6] utilisés sous Linux :
Des arguments supplémentaires sont passés sur la pile.
Registre | Windows CE 5.0 | gcc | Renesas |
---|---|---|---|
R0 | Valeurs de retour. Temporaire pour étendre les pseudo-instructions d'assemblage. Source/destination implicite pour les opérations 8/16 bits. Non conservé. | Valeur de retour, l'appelant enregistre | Variables/temporaires. Non garanti |
R1.. R3 | Sert de registres temporaires. Non conservé. | L'appelant a sauvegardé le scratch. Adresse de structure (enregistrement de l'appelant, par défaut) | Variables/temporaires. Non garanti |
R4.. R7 | Les quatre premiers mots des arguments entiers. La zone de construction d'arguments fournit un espace dans lequel les arguments R4 à R7 peuvent se déverser. Non conservé. | Passage de paramètres, l'appelant enregistre | Arguments. Non garanti. |
R8.. R13 | Sert de registres permanents. Conservé. | L'appelé enregistre | Variables/temporaires. Garanti. |
R14 | Pointeur de cadre par défaut. (R8-R13 peuvent également servir de pointeur de cadre et les routines feuille peuvent utiliser R1-R3 comme pointeur de cadre.) Préservé. | Pointeur de trame, FP, l'appelé enregistre | Variables/temporaires. Garanti. |
R15 | Sert de pointeur de pile ou de registre permanent. Conservé. | Pointeur de pile, SP, sauvegardes de l'appelé | Pointeur de pile. Garanti. |
Remarque : "préservé" fait référence à la sauvegarde par la fonction appelée ; il en va de même pour "garanti".
La convention d'appel la plus courante pour la série Motorola 68000 est [7],[8],[9],[10]:
L'IBM 1130 était une petite machine qui utilisait un adressage par mots de 16 bits. Elle ne disposait que de six registres, ainsi que d'indicateurs de condition, et n'avait pas de pile. Les registres incluent le registre d'adresses d'instructions (IAR), l'accumulateur (ACC), l'extension d'accumulateur (EXT) et trois registres d'index X1 à X3. Le programme appelant doit sauvegarder ACC, EXT, X1 et X2. Il existe deux pseudo-opérations pour appeler des sous-routines : CALL, pour coder des sous-routines non relocalisables directement liées au programme principal, et LIBF, pour appeler des sous-routines de bibliothèque relocalisables via un vecteur de transfert. Les deux pseudo-opérations se transforment en une instruction machine appelée Branch and Store IAR (BSI), qui stocke l'adresse de l'instruction suivante à son adresse effective (EA) et se branche sur EA+1.
Les arguments suivent le BSI — ce sont des adresses d'arguments d'un seul mot. La routine appelée doit savoir combien d'arguments elle doit attendre afin de pouvoir les ignorer lors du retour. Alternativement, les arguments peuvent être passés dans des registres. Les routines de fonction renvoient le résultat dans l'accumulateur (ACC) pour les arguments réels, ou dans un emplacement mémoire appelé pseudo-accumulateur de nombres réels (FAC). Les arguments et l'adresse de retour sont adressés à l'aide d'un décalage par rapport à la valeur IAR stockée dans le premier emplacement de la sous-routine. Les sous-routines des IBM 1130, CDC 6600 et PDP-8 (les trois ordinateurs ont été introduits en 1965) stockent l'adresse de retour dans le premier emplacement d'une sous-routine[11].
Le code threadé confie toute la responsabilité de la configuration et du nettoyage après un appel de fonction au code appelé. Le code d'appel se limite à indiquer les sous-routines à appeler. Cela centralise tout le code de configuration et de nettoyage de la fonction dans un seul endroit (le prologue et l'épilogue de la fonction), au lieu de le disperser dans les nombreux endroits où la fonction est appelée. Cela rend le code threadé la convention d'appel la plus compacte.
Le code threadé transmet tous les arguments sur la pile et renvoie toutes les valeurs de retour de la même manière. Cela rend les implémentations naïves plus lentes que les conventions d'appel qui conservent davantage de valeurs dans les registres. Cependant, les implémentations de code threadé qui mettent en cache plusieurs des valeurs de la pile supérieure dans les registres (en particulier l'adresse de retour) sont généralement plus rapides que celles des conventions d'appel de sous-routines qui poussent et retirent toujours l'adresse de retour de la pile.
La convention d'appel par défaut pour les programmes écrits en langage PL/I transmet tous les arguments par référence, bien que d'autres conventions puissent être spécifiées. Les arguments sont traités différemment selon les compilateurs et les plates-formes, mais généralement, les adresses des arguments sont transmises via une liste d'arguments en mémoire. Une adresse finale, cachée, peut également être transmise pour pointer vers une zone où la valeur de retour doit être stockée. Étant donné la grande variété de types de données supportés par PL/I, un descripteur de données peut également être transmis pour définir, par exemple, les longueurs des chaînes de caractères ou de bits, la dimension et les limites des tableaux (appelés vecteurs dope), ou la structure et le contenu d'une structure de données. Des arguments factices sont créés pour les arguments qui sont des constantes ou qui ne correspondent pas au type d'argument attendu par la procédure appelée.