Partager via


Convention d’appel x64

Cet article décrit les processus et conventions standard qu’utilise une fonction (l’appelant) pour effectuer des appels dans une autre fonction (l’appelé) dans le code x64.

Pour plus d’informations sur la __vectorcall convention d’appel, consultez __vectorcall.

Convention d'appel par défaut

L’interface binaire d’application x64 (ABI) utilise par défaut une convention d’appel rapide à quatre registres. Un espace est alloué sur la pile d'appel en tant que magasin fantôme pour que les appelants puissent sauvegarder ces registres.

Il existe une correspondance stricte un-à-un entre les arguments d’un appel de fonction et les registres utilisés pour ces arguments. Tout argument qui ne correspond pas à 8 octets, ou qui n’est pas 1, 2, 4 ou 8 octets, doit être passé par référence. Un seul argument n’est jamais réparti entre plusieurs registres.

La pile de registres x87 n’est pas utilisée. Il peut être utilisé par l’appelé, mais considérez-le volatile entre les appels de fonction. Toutes les opérations en virgule flottante sont effectuées à l'aide des 16 registres XMM.

Les arguments entiers sont passés dans les registres RCX, RDX, R8 et R9. Les arguments à virgule flottante sont passés dans XMM0L, XMM1L, XMM2L et XMM3L. Les arguments de 16 octets sont passés par référence. Le passage de paramètre est décrit en détail dans Passage de paramètre. Ces registres, et RAX, R10, R11, XMM4 et XMM5, sont considérés comme volatiles ou potentiellement modifiés par un appelé lors du retour. L'utilisation des registres est documentée en détail dans l'utilisation des registres x64 et les registres sauvegardés par l'appelant/appelé.

Pour les fonctions prototypées, tous les arguments sont convertis dans les types attendus de l'appelant avant d'être transmis. L'appelant est responsable de l'allocation de l'espace pour les paramètres de l'appelé. L’appelant doit toujours allouer suffisamment d’espace pour stocker quatre paramètres d’inscription, même si l’appelé ne prend pas autant de paramètres. Cette convention simplifie la prise en charge des fonctions en langage C sans prototype et des fonctions variadiques en C/C++. Pour les fonctions vararg ou non prototypées, toutes les valeurs à virgule flottante doivent être dupliquées dans le registre à usage général correspondant. Tous les paramètres au-delà des quatre premiers doivent être stockés sur la pile après le shadow store précédant l'appel. Les détails de la fonction Vararg sont disponibles dans Varargs. Les informations sur les fonctions non prototypées sont détaillées dans fonctions non prototypées.

Alignment

La plupart des structures sont alignées sur leur alignement naturel. Les exceptions principales sont le pointeur de pile et malloc ou alloca la mémoire, qui sont alignés sur 16 octets pour faciliter les performances. L’alignement supérieur à 16 octets doit être effectué manuellement. Étant donné que 16 octets sont une taille d’alignement courante pour les opérations XMM, cette valeur doit fonctionner pour la plupart du code. Pour plus d’informations sur la disposition et l’alignement de la structure, consultez la disposition de type x64 et de stockage. Pour plus d’informations sur la disposition de la pile, consultez l’utilisation de la pile x64.

Indépendance

Les fonctions feuilles sont des fonctions qui ne modifient pas les registres nonvolatiles. Une fonction non-léaf peut modifier le RSP nonvolatile, par exemple en appelant une fonction. Ou, il pourrait changer le RSP en allouant plus d’espace de pile pour les variables locales. Pour récupérer des registres nonvolatiles lorsqu’une exception est gérée, les fonctions non actives sont annotées avec des données statiques. Les données décrivent comment dérouler correctement la fonction à une instruction arbitraire. Ces données sont stockées sous forme de données pdata ou de procédure, qui à leur tour font référence à xdata, aux données de gestion des exceptions. Le xdata contient les informations de déroulement, et peut pointer vers des pdata supplémentaires ou une fonction gestionnaire d'exception.

Les prologs et les épilogues sont très restreints afin qu’ils puissent être correctement décrits dans xdata. Le pointeur de pile doit rester aligné sur 16 octets dans toute région de code qui ne fait pas partie d'un épilogue ou d'un prologue, sauf dans les fonctions feuilles. Les fonctions feuilles peuvent être déroulées simplement en simulant un retour, de sorte que pdata et xdata ne sont pas nécessaires. Pour plus d’informations sur la structure appropriée des prologs de fonction et des épilogues, consultez le prologue x64 et l’épilogue. Pour plus d'informations sur la gestion des exceptions, ainsi que sur la gestion des exceptions et le déroulement de pdata et xdata, reportez-vous à la rubrique Gestion des exceptions x64.

Passage de paramètres

Par défaut, la convention d’appel x64 transmet les quatre premiers arguments à une fonction dans les registres. Les registres utilisés pour ces arguments dépendent de la position et du type de l’argument. Les arguments restants sont transmis sur la pile dans l’ordre de droite à gauche. L’appelant réserve l’espace de pile requis et écrit ces arguments dans la mémoire de pile à l’aide des instructions de stockage ou de déplacement, en conservant l’alignement de 8 octets pour chaque argument.

Les arguments de valeur entière dans les quatre positions les plus à gauche sont passés respectivement dans l’ordre de gauche à droite dans RCX, RDX, R8 et R9. Le cinquième argument et les arguments supérieurs sont passés sur la pile comme décrit précédemment. Tous les arguments entiers dans les registres sont alignés à droite, de manière à ce que la fonction appelée puisse ignorer les bits supérieurs du registre et accéder uniquement à la partie du registre nécessaire.

Les arguments à virgule flottante et à double précision dans les quatre premiers paramètres sont passés dans XMM0 - XMM3, selon la position. Les valeurs à virgule flottante sont placées uniquement dans les registres entiers RCX, RDX, R8 et R9 lorsqu’il existe des arguments varargs. Pour plus d’informations, consultez Varargs. De même, les registres XMM0 - XMM3 sont ignorés lorsque l’argument correspondant est un type entier ou pointeur.

Les types __m128, les tableaux et les chaînes de caractères ne sont jamais transmis par valeur immédiate. Au lieu de cela, un pointeur est transmis à la mémoire allouée par l'appelant. Les structures et les unions de taille 8, 16, 32 ou 64 bits, ainsi que les types __m64, sont transmis comme s'il s'agissait d'entiers de même taille. Les structures ou unions d'une autre taille sont transmises sous la forme d'un pointeur vers la mémoire allouée par l'appelant. Pour ces types d’agrégation passés en tant que pointeur, y compris __m128, la mémoire temporaire allouée par l’appelant doit être alignée sur 16 octets.

Les fonctions intrinsèques qui n’allouent pas d’espace de pile et n’appellent pas d’autres fonctions, utilisent parfois d’autres registres volatiles pour passer des arguments de registre supplémentaires. Cette optimisation est rendue possible par la liaison étroite entre le compilateur et l’implémentation de fonction intrinsèque.

L'appelant est responsable du dumping des paramètres de registre dans leur espace d'ombre si nécessaire.

Le tableau suivant résume la façon dont les paramètres sont passés, par type et position à partir de la gauche :

Type de paramètre cinquième et plus quatrième Troisième second le plus à gauche
virgule flottante stack XMM3 XMM2 XMM1 XMM0
entier stack R9 R8 RDX RCX
Agrégats (8, 16, 32 ou 64 bits) et __m64 stack R9 R8 RDX RCX
Autres agrégats, en tant que pointeurs stack R9 R8 RDX RCX
__m128, en tant que pointeur stack R9 R8 RDX RCX

Exemple de passage d'argument 1 - tous les entiers

func1(int a, int b, int c, int d, int e, int f);
// a in RCX, b in RDX, c in R8, d in R9, f then e passed on stack

Exemple de passage d'argument 2 - tous les flottants

func2(float a, double b, float c, double d, float e, float f);
// a in XMM0, b in XMM1, c in XMM2, d in XMM3, f then e passed on stack

Exemple de passage d'argument 3 - mélange d'entiers et de flottants

func3(int a, double b, int c, float d, int e, float f);
// a in RCX, b in XMM1, c in R8, d in XMM3, f then e passed on stack

Exemple d’argument passant 4 - __m64, __m128 et agrégats

func4(__m64 a, __m128 b, struct c, float d, __m128 e, __m128 f);
// a in RCX, ptr to b in RDX, ptr to c in R8, d in XMM3,
// ptr to f passed on stack, then ptr to e passed on stack

Varargs

Si les paramètres sont transmis via des varargs (par exemple, des arguments de type « ellipses »), la convention normale de passage des paramètres de registre s'applique. Cette convention inclut le déversement du cinquième argument et des suivants sur la pile. C'est à l'appelant qu'il incombe de dumper les arguments dont l'adresse a été prise. Pour les valeurs à virgule flottante uniquement, le registre entier et le registre à virgule flottante doivent contenir la valeur, dans le cas où l’appelé attend la valeur dans les registres entiers.

Fonctions non prototypées

Pour les fonctions qui ne sont pas entièrement prototypées, l'appelant transmet les valeurs entières comme des entiers et les valeurs à virgule flottante comme des valeurs à double précision. Pour les valeurs à virgule flottante uniquement, le registre des entiers et le registre des virgules flottantes contiennent tous deux la valeur flottante au cas où l'appelant attendrait la valeur dans les registres des entiers.

func1();
func2() {   // RCX = 2, RDX = XMM1 = 1.0, and R8 = 7
   func1(2, 1.0, 7);
}

Valeurs de retour

Une valeur de retour scalaire qui peut s’adapter à 64 bits, y compris le __m64 type, est retournée via RAX. Les types noncalaires, y compris les floats, les doubles et les types vectoriels tels que __m128, __m128i__m128d sont retournés dans XMM0. L'état des bits non utilisés dans la valeur retournée dans RAX ou XMM0 est non défini.

Les types définis par l'utilisateur peuvent être retournés par valeur depuis des fonctions globales et des fonctions de membres statiques. Pour retourner un type défini par l’utilisateur par valeur dans RAX, il doit avoir une longueur de 1, 2, 4, 8, 16, 32 ou 64 bits. Il ne doit pas avoir de constructeur, de destructeur ou d’opérateur d’affectation de copie défini par l’utilisateur. Il ne peut avoir aucun membre de données privé ou protégé non statique et aucun membre de données non statiques de type référence. Il ne peut pas avoir de classes de base ou de fonctions virtuelles. Et il ne peut avoir que des membres de données qui répondent également à ces exigences. Cette définition est essentiellement identique à un type POD C++03. Étant donné que la définition a changé dans la norme C++11, nous vous déconseillons d’utiliser std::is_pod ce test. Sinon, l’appelant doit allouer de la mémoire pour la valeur de retour et passer un pointeur vers celle-ci comme premier argument. Les arguments restants sont ensuite déplacés d’un argument vers la droite. Le même pointeur doit être retourné par l'appelé dans RAX.

Ces exemples montrent comment les paramètres et les valeurs de retour sont passés pour les fonctions avec les déclarations spécifiées :

Exemple de valeur de retour 1 - résultat 64 bits

__int64 func1(int a, float b, int c, int d, int e);
// Caller passes a in RCX, b in XMM1, c in R8, d in R9, e passed on stack,
// callee returns __int64 result in RAX.

Exemple de valeur de retour 2 - résultat 128 bits

__m128 func2(float a, double b, int c, __m64 d);
// Caller passes a in XMM0, b in XMM1, c in R8, d in R9,
// callee returns __m128 result in XMM0.

Exemple de valeur de retour 3 - résultat de type utilisateur par pointeur

struct Struct1 {
   int j, k, l;    // Struct1 exceeds 64 bits.
};
Struct1 func3(int a, double b, int c, float d);
// Caller allocates memory for Struct1 returned and passes pointer in RCX,
// a in RDX, b in XMM2, c in R9, d passed on the stack;
// callee returns pointer to Struct1 result in RAX.

Exemple de valeur de retour 4 - Résultat de type utilisateur par valeur

struct Struct2 {
   int j, k;    // Struct2 fits in 64 bits, and meets requirements for return by value.
};
Struct2 func4(int a, double b, int c, float d);
// Caller passes a in RCX, b in XMM1, c in R8, and d in XMM3;
// callee returns Struct2 result by value in RAX.

Registres sauvegardés par l'appelant/l'appelé

L’ABI x64 considère les registres RAX, RCX, RDX, R8, R9, R10, R11 et XMM0-XMM5 volatiles. Lorsqu’elles sont présentes, les parties supérieures de YMM0-YMM15 et ZMM0-ZMM15 sont également volatiles. Sur l'AVX512VL, les registres ZMM, YMM et XMM 16-31 sont également volatils. Lorsque le support AMX est présent, les registres de la mosaïque TMM sont volatiles. Considérez les registres volatiles comme détruits lors des appels de fonction, à moins qu'une analyse telle que l'optimisation de programme complet ne prouve le contraire pour des raisons de sécurité.

L’ABI x64 considère les registres RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15 et XMM6-XMM15 comme non volatils. Ils doivent être enregistrés et restaurés par une fonction qui les utilise.

Pointeurs de fonction

Les pointeurs de fonction sont simplement des pointeurs vers l’étiquette de la fonction respective. Il n’existe aucune exigence de table des matières (TOC) pour les pointeurs de fonction.

Prise en charge de la virgule flottante pour les anciens codes

Les registres MMX et les registres de pile en virgule flottante (MM0-MM7/ST0-ST7) sont préservés lors des changements de contexte. Il n’existe aucune convention d’appel explicite pour ces registres. L’utilisation de ces registres est strictement interdite en mode noyau.

FPCSR

L’état de registre inclut également le mot de contrôle FPU x87. La convention d’appel détermine ce registre comme nonvolatile.

Le registre de mots de contrôle du processeur complet x87 est défini à l’aide des valeurs standard suivantes au début de l’exécution du programme :

Register[bits] Setting
FPCSR[0:6] Masque des exceptions tous les bits à 1 (toutes les exceptions masquées)
FPCSR[7] Réservé - 0
FPCSR[8:9] Contrôle de précision - 10B (double précision)
FPCSR[10:11] Contrôle d'arrondi - 0 (arrondi au plus proche)
FPCSR[12] Contrôle infini - 0 (non utilisé)

Un appelant qui modifie l'un des champs de FPCSR doit le restaurer avant de retourner à son appelant. En outre, un appelant qui a modifié l’un de ces champs doit les restaurer à leurs valeurs standard avant d’appeler un appelé, sauf si, par accord, l’appelé attend les valeurs modifiées.

Il existe deux exceptions aux règles relatives à la nonvolatilité des indicateurs de contrôle :

  • Dans les fonctions où l’objectif documenté de la fonction donnée est de modifier les indicateurs PCSR nonvolatiles.

  • Lorsqu’il est provablement correct que la violation de ces règles entraîne un programme qui se comporte de la même façon qu’un programme qui ne viole pas les règles, par exemple par le biais d’une analyse complète du programme.

MXCSR

L'état du registre inclut également MXCSR. La convention d’appel divise ce registre en une partie volatile et une partie nonvolatile. La partie volatile se compose des six indicateurs d’état, dans MXCSR[0:5], tandis que le reste du registre, MXCSR[6:15], est considéré comme nonvolatile.

La partie nonvolatile est définie sur les valeurs standard suivantes au début de l’exécution du programme :

Register[bits] Setting
MXCSR[6] Les dénormaux sont des zéros - 0
MXCSR[7:12] Masque d'exceptions tous les bits à 1 (toutes les exceptions masquées)
MXCSR[13:14] Contrôle d'arrondi - 0 (arrondi au plus proche)
MXCSR[15] Rinçage à zéro en cas de sous-écoulement masqué - 0 (désactivé)

Un appelant qui modifie l'un des champs non volatils du MXCSR doit le restaurer avant de retourner à son appelant. En outre, un appelant qui a modifié l’un de ces champs doit les restaurer à leurs valeurs standard avant d’appeler un appelé, sauf si, par accord, l’appelé attend les valeurs modifiées.

Il existe deux exceptions aux règles relatives à la nonvolatilité des indicateurs de contrôle :

  • Dans les fonctions où l’objectif documenté de la fonction donnée est de modifier les indicateurs MXCSR nonvolatiles.

  • Lorsqu’il est provablement correct que la violation de ces règles entraîne un programme qui se comporte de la même façon qu’un programme qui ne viole pas les règles, par exemple par le biais d’une analyse complète du programme.

Ne faites aucune hypothèse sur l’état de portion volatile du registre MXCSR sur une limite de fonction, sauf si la documentation de la fonction la décrit explicitement.

setjmp/longjmp

Lorsque vous incluez setjmpex.h ou setjmp.h, tous les appels à setjmp ou longjmp entraînent un déroulement qui invoque des destructeurs et des appels à __finally. Ce comportement diffère de x86, où l'inclusion de setjmp.h fait que les clauses __finally et les destructeurs ne sont pas appelés.

Un appel à setjmp préserve le pointeur de pile actuel, les registres non volatiles et les registres MXCSR. Les appels à longjmp reviennent au site d’appel le plus récent setjmp et réinitialisent le pointeur de pile, les registres non volatils et les registres MXCSR, à l'état tel que préservé par l'appel le plus récent setjmp.

Voir aussi