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
, __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 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] | Setting |
---|---|
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] | Setting |
---|---|
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
.