Convention d’appel x64

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

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

Valeurs par défaut de convention d’appel

L’interface binaire d’application x64 (ABI) utilise par défaut une convention d’appel rapide à quatre inscriptions. L’espace est alloué sur la pile des appels en tant que magasin d’ombres pour que les appelants enregistrent 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 d’inscription 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 à 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 le 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 de l’inscription est documentée en détail dans les registres d’inscription x64 et les registres enregistrés par l’appelant/appelé.

Pour les fonctions prototypes, tous les arguments sont convertis en types d’appelé attendus avant de passer. L’appelant est responsable de l’allocation d’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 C-language nonprototyped et des fonctions vararg C/C++. Pour les fonctions vararg ou nonprototyped, 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 magasin d’ombres avant l’appel. Les détails de la fonction Vararg sont disponibles dans Varargs. Les informations de fonction nonprototyped sont détaillées dans les fonctions nonprototyped.

Alignement

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.

Déroulage

Les fonctions feuilles sont des fonctions qui ne modifient pas les registres non volatiles. Une fonction non-feuille peut changer le RSP non volatile, par exemple en appelant une fonction. Ou bien, il peut modifier le reer en allouant de l’espace de pile supplémentaire pour les variables locales. Pour récupérer des registres non volatiles lorsqu’une exception est gérée, les fonctions non feuilles sont annotées avec des données statiques. Les données expliquent comment décompresser 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 données pdata supplémentaires ou une fonction de gestionnaire d’exceptions.

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 n’importe quelle région de code qui ne fait pas partie d’un épilogue ou d’un prologue, sauf dans les fonctions feuille. Les fonctions feuilles peuvent être décodeuses simplement en simulant un retour. Par conséquent, pdata et xdata ne sont pas obligatoires. 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 et la gestion des exceptions et le déroulement de pdata et xdata, consultez 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 envoyés (push) sur la pile dans l’ordre de droite à gauche.

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. Les cinquième et versions ultérieures sont transmis sur la pile, comme décrit précédemment. Tous les arguments entiers dans les registres sont justifiés avec le droit, de sorte que l’appelé peut ignorer les bits supérieurs du registre et accéder uniquement à la partie du registre nécessaire.

Tous 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.

__m128 les types, les tableaux et les chaînes ne sont jamais passés par valeur immédiate. Au lieu de cela, un pointeur est passé à la mémoire allouée par l’appelant. Les structs et les unions de taille 8, 16, 32 ou 64 bits, et __m64 les types sont passés comme s’ils étaient des entiers de la même taille. Les structs ou unions d’autres tailles sont passés en tant que 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’appelé est chargé de vider les paramètres du 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 supérieur quatrième Troisième second Gauche
virgule flottante pile XMM3 XMM2 XMM1 XMM0
entier pile R9 R8 RDX RCX
Agrégats (8, 16, 32 ou 64 bits) et __m64 pile R9 R8 RDX RCX
Autres agrégats, en tant que pointeurs pile R9 R8 RDX RCX
__m128, en tant que pointeur pile R9 R8 RDX RCX

Exemple d’argument passant 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 pushed on stack

Exemple d’argument passant 2 - tous les floats

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 pushed on stack

Exemple de passage d’argument 3 - ints mixtes et floats

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 pushed on stack

Exemple d’argument passant 4 - __m64, __m128et 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 pushed on stack, then ptr to e pushed on stack

Varargs

Si les paramètres sont passés via varargs (par exemple, les arguments de points de suspension), la convention de passage de paramètre d’inscription normale s’applique. Cette convention inclut le déversement des cinquièmes arguments et ultérieurs dans la pile. C’est la responsabilité de l’appelé de vider les arguments qui ont leur adresse 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 nonprotyped

Pour les fonctions qui ne sont pas entièrement prototypes, l’appelant transmet des valeurs entières en tant qu’entiers et des valeurs à virgule flottante en tant que double précision. Pour les valeurs à virgule flottante uniquement, le registre entier et le registre à virgule flottante contiennent la valeur flottante au cas où l’appelé attendait la valeur dans les registres 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 non scalaires, 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 pour ce test.) Sinon, l’appelant doit allouer de la mémoire pour la valeur de retour et lui transmettre un pointeur 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 pushed 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 du 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 pushed 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 enregistrés de l’appelant/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 AVX512VL, les registres ZMM, YMM et XMM sont également volatiles. Lorsque la prise en charge d’AMX est présente, les registres de vignetteS TMM sont volatiles. Considérez les registres volatiles détruits sur les appels de fonction, sauf indication contraire en matière de sécurité par analyse, comme l’optimisation complète du programme.

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

Pointeurs 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 à virgule flottante pour le code plus ancien

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

PCSR

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] Paramètre
PCSR[0 :6] Masque les exceptions toutes les 1 (toutes les exceptions masquées)
PCSR[7] Réservé - 0
PCSR[8 :9] Contrôle de précision - 10B (double précision)
PCSR[10 :11] Contrôle d’arrondi - 0 (arrondi au plus proche)
PCSR[12] Contrôle infini - 0 (non utilisé)

Un appelé qui modifie l’un des champs au sein de l’PCSR doit les restaurer avant de revenir à 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 non-volatilité 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 d’inscription 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] Paramètre
MXCSR[6] Les dénormals sont des zéros - 0
MXCSR[7 :12] Masque les exceptions toutes les 1 (toutes les exceptions masquées)
MXCSR[13 :14] Contrôle d’arrondi - 0 (arrondi au plus proche)
MXCSR[15] Vider sur zéro pour le flux de sous-flux masqué - 0 (désactivé)

Un appelé qui modifie l’un des champs nonvolatiles dans MXCSR doit les restaurer avant de revenir à 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 non-volatilité 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 vers ou longjmp aboutissent à setjmp un déroulement qui appelle des destructeurs et __finally des appels. Ce comportement diffère de x86, où l’inclusion de setjmp.h entraîne __finally l’appel de clauses et de destructeurs.

Un appel pour setjmp conserver le pointeur de pile actuel, les registres non volatiles et les registres MXCSR. Appels pour longjmp revenir au site d’appel le plus récent setjmp et réinitialise le pointeur de pile, les registres non volatiles et les registres MXCSR, à l’état conservé par l’appel le plus récent setjmp .

Voir aussi

Conventions des logiciels x64