Vue d’ensemble des conventions ABI ARM64EC

ARM64EC est une interface ABI (Application Binary Interface) qui permet aux binaires ARM64 de s’exécuter de façon native et interopérable avec du code x64. Plus précisément, l’interface ABI ARM64EC suit les conventions logicielles x64, notamment la convention d’appel, l’utilisation de piles et l’alignement de données, ce qui rend ARM64EC et le code x64 interopérables. Le système d’exploitation émule la partie x64 du binaire. (EC dans ARM64EC est la contraction d’émulation compatible.)

Pour plus d’informations sur les interfaces ABI x64 et ARM64, consultez Vue d’ensemble des conventions de l’interface ABI x64 et Vue d’ensemble des conventions de l’interface ABI ARM64.

ARM64EC ne résout pas les différences de modèle de mémoire entre les architectures x64 et ARM. Pour plus d’informations, consultez Problèmes courants de migration ARM Visual C++.

Définitions

  • ARM64 : flux de code pour les processus ARM64 qui contiennent du code ARM64 traditionnel.
  • ARM64EC : flux de code qui utilise un sous-ensemble du jeu de registres ARM64 pour assurer l’interopérabilité avec le code x64.

Mappage de registres

Les processus x64 peuvent avoir des threads qui exécutent du code ARM64EC. Donc, il est toujours possible de récupérer un contexte de registre x64. ARM64EC utilise un sous-ensemble des registres de base ARM64 qui mappent aux registres x64 émulés, 1 à 1. Il est important de noter qu’ARM64EC n’utilise jamais de registres en dehors de ce sous-ensemble, sauf pour lire l’adresse TEB (Thread Environment Block) de x18.

Les processus ARM64 natifs ne doivent pas régresser en performances quand des fonctions, aussi nombreuses soient-elles, sont recompilées sous la forme ARM64EC. Pour maintenir les performances, l’interface ABI suit ces principes :

  • Le sous-ensemble de registres ARM64EC englobe tous les registres qui font partie de la convention d’appel de fonction ARM64.

  • La convention d’appel ARM64EC mappe directement à la convention d’appel ARM64.

Des routines d’assistance spéciales telles que __chkstk_arm64ec utilisent des conventions d’appel personnalisées et des registres. Ces registres sont également inclus dans le sous-ensemble ARM64EC des registres.

Mappage de registres pour les registres d’entiers

Registre ARM64EC Registre x64 Convention d’appel ARM64EC Convention d’appel ARM64 Convention d’appel x64
x0 rcx volatile volatile volatile
x1 rdx volatile volatile volatile
x2 r8 volatile volatile volatile
x3 r9 volatile volatile volatile
x4 r10 volatile volatile volatile
x5 r11 volatile volatile volatile
x6 mm1 (64 bits bas du registre R1 x87) volatile volatile volatile
x7 mm2 (64 bits bas du registre R2 x87) volatile volatile volatile
x8 rax volatile volatile volatile
x9 mm3 (64 bits bas du registre R3 x87) volatile volatile volatile
x10 mm4 (64 bits bas du registre R4 x87) volatile volatile volatile
x11 mm5 (64 bits bas du registre R5 x87) volatile volatile volatile
x12 mm6 (64 bits bas du registre R6 x87) volatile volatile volatile
x13 S/O non autorisée volatile N/A
x14 N/A non autorisée volatile S/O
x15 mm7 (64 bits bas du registre R7 x87) volatile volatile volatile
x16 16 bits hauts de chacun des registres R0-R3 x87 volatile(xip0) volatile(xip0) volatile
x17 16 bits hauts de chacun des registres R4-R7 x87 volatile(xip1) volatile(xip1) volatile
x18 GS.base fixed(TEB) fixed(TEB) fixed(TEB)
x19 r12 non volatile non volatile non volatile
x20 r13 non volatile non volatile non volatile
x21 r14 non volatile non volatile non volatile
x22 r15 non volatile non volatile non volatile
x23 S/O non autorisée non volatile N/A
x24 N/A non autorisée non volatile S/O
x25 rsi non volatile non volatile non volatile
x26 rdi non volatile non volatile non volatile
x27 rbx non volatile non volatile non volatile
x28 S/O non autorisée non autorisée S/O
fp rbp non volatile non volatile non volatile
lr mm0 (64 bits bas du registre R0 x87) Les deux Les deux Les deux
sp rsp non volatile non volatile non volatile
pc rip pointeur d’instruction pointeur d’instruction pointeur d’instruction
PSTATE sous-ensemble : N/Z/C/V/SS1, 2 RFLAGS sous-ensemble : SF/ZF/CF/OF/TF volatile volatile volatile
S/O RFLAGS sous-ensemble : PF/AF N/A N/A volatile
S/O RFLAGS sous-ensemble : DF N/A N/A non volatile

1 Évitez de lire, écrire ou calculer directement des mappages entre PSTATE et RFLAGS. Ces bits peuvent être utilisés et sont susceptibles de changer.

2 L’indicateur de retenue ARM64EC C est l’inverse de l’indicateur de retenue x64 CF pour les opérations de soustraction. Aucun traitement particulier n’est nécessaire, car l’indicateur est volatil et donc jeté lors de la transition d’une fonction à l’autre (ARM64EC et x64).

Mappage de registres pour les registres vectoriels

Registre ARM64EC Registre x64 Convention d’appel ARM64EC Convention d’appel ARM64 Convention d’appel x64
v0-v5 xmm0-xmm5 volatile volatile volatile
v6-v7 xmm6-xmm7 volatile volatile non volatile
v8-v15 xmm8-xmm15 volatile 1 volatile 1 non volatile
v16-v31 xmm16-xmm31 non autorisée volatile non autorisée (l’émulateur x64 ne prend pas en charge AVX-512)
FPCR 2 MXCSR[15:6] non volatile non volatile non volatile
FPSR 2 MXCSR[5:0] volatile volatile volatile

1 Ces registres ARM64 sont particuliers dans le sens où les 64 bits inférieurs ne sont pas volatils alors que les 64 bits supérieurs le sont. Du point de vue d’un appelant x64, ils sont effectivement volatils parce que l’appelé jette les données.

2 Évitez de lire, écrire ou calculer directement des mappages de FPCR et de FPSR. Ces bits peuvent être utilisés et sont susceptibles de changer.

Compression de structs

ARM64EC suit les mêmes règles de compression de structs que celles utilisées pour x64 afin de garantir l’interopérabilité entre le code ARM64EC et le code x64. Pour plus d’informations et des exemples de compression de structs x64, consultez Vue d’ensemble des conventions de l’interface ABI x64.

Routines ABI d’assistance d’émulation

Le code ARM64EC et les thunks utilisent des routines d’assistance d’émulation pour passer d’une fonction x64 à une fonction ARM64EC.

Le tableau suivant décrit chaque routine ABI spéciale et les registres que l’interface ABI utilise. Les routines ne modifient pas les registres conservés listés sous la colonne ABI. Aucune hypothèse ne doit être établie sur les registres non listés. Sur disque, les pointeurs de routine ABI sont nuls. Au moment du chargement, le chargeur met à jour les pointeurs pour les faire pointer vers les routines de l’émulateur x64.

Nom Description ABI
__os_arm64x_dispatch_call_no_redirect Appelée par un thunk de sortie pour appeler une cible x64 (une fonction x64 ou une séquence fast-forward x64). La routine envoie (push) l’adresse de retour ARM64EC (dans le registre LR) suivie de l’adresse de l’instruction qui réussit une instruction blr x16 qui appelle l’émulateur x64. Elle exécute ensuite l’instruction blr x16 valeur de retour en x8 (rax)
__os_arm64x_dispatch_ret Appelée par un thunk d’entrée pour revenir à son appelant x64. Elle affiche l’adresse de retour x64 de la pile et appelle l’émulateur x64 pour y accéder S/O
__os_arm64x_check_call Appelée par le code ARM64EC avec un pointeur vers un thunk de sortie et l’adresse cible ARM64EC indirecte à exécuter. La cible ARM64EC est considérée comme corrigeable et l’exécution revient toujours à l’appelant avec les mêmes données avec lesquelles elle a été appelée, ou avec des données modifiées Arguments :
x9 : Adresse cible
x10 : Adresse du thunk de sortie
x11 : Adresse de la séquence fast-forward

Sortie :
x9 : Si la fonction cible a été déviée, elle contient l’adresse de la séquence fast-forward
x10 : Adresse du thunk de sortie
x11 : Si la fonction a été déviée, elle contient l’adresse du thunk de sortie. Sinon, l’adresse cible est redirigée vers

Registres conservés : x0-x8, x15 (chkstk). et q0-q7
__os_arm64x_check_icall Appelée par le code ARM64EC, avec un pointeur vers un thunk de sortie, pour gérer un accès à une adresse cible qui est x64 ou ARM64EC. Si la cible est x64 et que le code x64 n’a pas été corrigé, la routine définit le registre d’adresses cibles. Elle pointe vers la version ARM64EC de la fonction s’il en existe une. Sinon, elle définit le registre pour le faire pointer vers le thunk de sortie qui passe à la cible x64. Ensuite, elle retourne au code ARM64EC de l’appelant, qui accède ensuite à l’adresse dans le registre. Cette routine est une version non optimisée de __os_arm64x_check_call, où l’adresse cible n’est pas connue au moment de la compilation

Utilisée sur un site d’appel d’un appel indirect
Arguments :
x9 : Adresse cible
x10 : Adresse du thunk de sortie
x11 : Adresse de la séquence fast-forward

Sortie :
x9 : Si la fonction cible a été déviée, elle contient l’adresse de la séquence fast-forward
x10 : Adresse du thunk de sortie
x11 : Si la fonction a été déviée, elle contient l’adresse du thunk de sortie. Sinon, l’adresse cible est redirigée vers

Registres conservés : x0-x8, x15 (chkstk) et q0-q7
__os_arm64x_check_icall_cfg Identique à __os_arm64x_check_icall, mais vérifie également que l’adresse spécifiée est une cible d’appel indirect de graphe de flux de contrôle valide Arguments :
x10 : Adresse du thunk de sortie
x11 : Adresse de la fonction cible

Sortie :
x9 : Si la cible est x64, adresse de la fonction. Sinon, non définie
x10 : Adresse du thunk de sortie
x11 : Si la cible est x64, elle contient l’adresse du thunk de sortie. Sinon, adresse de la fonction

Registres conservés : x0-x8, x15 (chkstk) et q0-q7
__os_arm64x_get_x64_information Obtient la partie demandée du contexte du registre x64 en direct _Function_class_(ARM64X_GET_X64_INFORMATION) NTSTATUS LdrpGetX64Information(_In_ ULONG Type, _Out_ PVOID Output, _In_ PVOID ExtraInfo)
__os_arm64x_set_x64_information Définit la partie demandée du contexte du registre x64 en direct _Function_class_(ARM64X_SET_X64_INFORMATION) NTSTATUS LdrpSetX64Information(_In_ ULONG Type,_In_ PVOID Input, _In_ PVOID ExtraInfo)
__os_arm64x_x64_jump Utilisée dans un ajusteur sans signature et d’autres thunks qui transfèrent directement (jmp) un appel à une autre fonction qui peut avoir une signature, en reportant l’application potentielle du thunk droit à la cible réelle Arguments :
x9 : cible à atteindre

Tous les registres de paramètres conservés (transférés)

Thunks

Les thunks sont les mécanismes de bas niveau permettant de prendre en charge les fonctions ARM64EC et x64 qui s’appellent mutuellement. Il en existe deux types : les thunks d’entrée pour entrer des fonctions ARM64EC et les thunks de sortie pour appeler des fonctions x64.

Thunk d’entrée et thunks d’entrée intrinsèques : appel de fonction x64 à ARM64EC

Pour prendre en charge les appelants x64 lorsqu’une fonction C/C++ est compilée en tant qu’ARM64EC, la chaîne d’outils génère un seul thunk d’entrée constitué de code machine ARM64EC. Les intrinsèques ont un thunk d’entrée à eux. Toutes les autres fonctions partagent un thunk d’entrée avec toutes les fonctions qui ont une convention d’appel, des paramètres et un type de retour correspondants. Le contenu du thunk dépend de la convention d’appel de la fonction C/C++.

En plus de la gestion des paramètres et de l’adresse de retour, le thunk réduit les différences de volatilité entre les registres vectoriels ARM64EC et x64 qu’entraîne le mappage de registres vectoriels ARM64EC :

Registre ARM64EC Registre x64 Convention d’appel ARM64EC Convention d’appel ARM64 Convention d’appel x64
v6-v15 xmm6-xmm15 volatile, mais enregistrée/restaurée dans le thunk d’entrée (x64 à ARM64EC) volatile ou partiellement volatile pour les 64 bits supérieurs non volatile

Le thunk d’entrée effectue les actions suivantes :

Nombre de paramètres Utilisation de la pile
0-4 Stocke ARM64EC v6 et v7 dans l’espace d’accueil alloué par l’appelant

Étant donné que l’appelé est ARM64EC, qui n’a pas la notion d’espace d’accueil, les valeurs stockées ne sont pas écrasées.

Alloue 128 octets supplémentaires sur la pile et stocke ARM64EC v8 via v15.
5-8 x4 = 5ème paramètre de la pile
x5 = 6ème paramètre de la pile
x6 = 7ème paramètre de la pile
x7 = 8ème paramètre de la pile

Si le paramètre est SIMD, les registres v4-v7 sont utilisés à la place
+9 Alloue les octets AlignUp(NumParams - 8 , 2) * 8 sur la pile. *

Copie le 9ème paramètre et tous ceux qui restent dans cette zone

* L’alignement de la valeur sur un nombre pair garantit que la pile reste alignée sur 16 octets

Si la fonction accepte un paramètre entier de 32 bits, le thunk est autorisé à envoyer (push) uniquement 32 bits au lieu des 64 bits du registre parent.

Ensuite, le thunk utilise une instruction ARM64 bl pour appeler la fonction ARM64EC. Une fois la fonction retournée, le thunk :

  1. Annule toutes les allocations de la pile
  2. Appelle la fonction d’assistance de l’émulateur __os_arm64x_dispatch_ret pour afficher l’adresse de retour x64 et reprendre l’émulation x64.

Thunk de sortie : appel de fonction ARM64EC à x64

Pour chaque appel qu’une fonction ARM64EC C/C++ effectue au code x64 potentiel, la chaîne d’outils MSVC génère un thunk de sortie. Le contenu du thunk dépend des paramètres de l’appelé x64 et si celui-ci utilise la convention d’appel standard ou __vectorcall. Le compilateur obtient ces informations d’une déclaration de fonction pour l’appelé.

Premièrement, le thunk envoie (push) l’adresse de retour qui se trouve dans le registre ARM64EC lr et une valeur factice de 8 octets pour garantir que la pile est alignée sur 16 octets. Deuxièmement, le thunk gère les paramètres :

Nombre de paramètres Utilisation de la pile
0-4 Alloue 32 octets d’espace d’accueil sur la pile
5-8 Alloue AlignUp(NumParams - 4, 2) * 8 autres octets plus haut dans la pile. *

Copie le 5ème paramètre et les suivants du x4-x7 d’ARM64EC vers cet espace supplémentaire
+9 Copie le 9ème paramètre et tous ceux qui restent dans l’espace supplémentaire

* L’alignement de la valeur sur un nombre pair garantit que la pile reste alignée sur 16 octets.

Troisièmement, le thunk appelle la fonction d’assistance de l’émulateur __os_arm64x_dispatch_call_no_redirect pour appeler l’émulateur x64 afin d’exécuter la fonction x64. L’appel doit être une instruction blr x16 (x16 est un registre volatil, ce qui est pratique). Une instruction blr x16 est requise, car l’émulateur x64 analyse cette instruction en tant qu’indicateur.

La fonction x64 tente généralement de revenir à la fonction d’assistance de l’émulateur à l’aide d’une instruction ret x64. À ce stade, l’émulateur x64 détecte qu’il se trouve dans du code ARM64EC. Il lit ensuite l’indicateur de 4 octets précédent qui se trouve être l’instruction ARM64 blr x16. Étant donné que cet indicateur indique que l’adresse de retour se trouve dans cette fonction d’assistance, l’émulateur accède directement à cette adresse.

La fonction x64 est autorisée à revenir à la fonction d’assistance de l’émulateur avec une instruction de branche, notamment jmp et call x64. L’émulateur gère également ces scénarios.

Lorsque la fonction d’assistance revient ensuite au thunk, celui-ci :

  1. Annule toute allocation de la pile
  2. Fait apparaître le registre ARM64EC lr
  3. Exécute une instruction ARM64 ret lr.

Décoration de noms de fonction ARM64EC

Un nom de fonction ARM64EC a une décoration secondaire appliquée après toute décoration spécifique au langage. Pour les fonctions avec liaison C (compilées en tant que C ou à l’aide de extern "C"), un #précède le nom. Pour les fonctions C++ décorées, une balise $$h est insérée dans le nom.

foo         => #foo
?foo@@YAHXZ => ?foo@@$$hYAHXZ

__vectorcall

La chaîne d’outils ARM64EC ne prend pas en charge __vectorcall actuellement. Le compilateur émet une erreur lorsqu’il détecte l’utilisation de __vectorcall avec ARM64EC.

Voir aussi

Présentation de l’interface ABI ARM64EC et du code d’assembly
Problèmes courants de migration ARM Visual C++
Noms décorés