Partager via


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 Microsoft 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 correspondent aux registres x64 émulés, un pour un. 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 correspond 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 volatil volatil volatil
x1 rdx volatil volatil volatil
x2 r8 volatil volatil volatil
x3 r9 volatil volatil volatil
x4 r10 volatil volatil volatil
x5 r11 volatil volatil volatil
x6 mm1 (64 bits bas du registre R1 x87) volatil volatil volatil
x7 mm2 (64 bits bas du registre R2 x87) volatil volatil volatil
x8 rax volatil volatil volatil
x9 mm3 (64 bits inférieurs du registre R3 x87) volatil volatil volatil
x10 mm4 (partie basse de 64 bits du registre R4 x87) volatil volatil volatil
x11 mm5 (64 bits inférieurs du registre x87 R5) volatil volatil volatil
x12 mm6 (les 64 bits inférieurs du registre R6 x87) volatil volatil volatil
x13 Non applicable non autorisée volatil N/A
x14 Non applicable non autorisé volatil S/O
x15 mm7 (64 bits bas du registre R7 x87) volatil volatil volatil
x16 16 bits supérieurs de chaque registre x87 R0-R3 volatile(xip0) volatile(xip0) volatil
x17 16 bits hauts de chacun des registres R4-R7 x87 volatile(xip1) volatile(xip1) volatil
x18 GS.base fixé(TEB) fixé(TEB) fixé(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é non volatile S/O
x24 S/O non autorisé 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é non autorisé 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 volatil volatil volatil
S/O RFLAGS sous-ensemble : PF/AF S/O S/O volatil
S/O RFLAGS sous-ensemble : DF S/O Sans Objet non volatile

1 Évitez de lire, écrire ou calculer directement des mappages entre PSTATE et RFLAGS. Ces bits pourraient être utilisés à l'avenir 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 volatil volatil volatil
v6-v7 xmm6-xmm7 volatil volatil non volatile
v8-v15 xmm8-xmm15 volatile 1 volatile 1 non volatile
v16-v31 xmm16-xmm31 non autorisé volatil 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] volatil volatil volatil

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 pourraient être utilisés à l'avenir et sont susceptibles de changer.

Emballage de structures

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.

Exceptions à virgule flottante

Pour déterminer si un processeur ARM prend en charge les exceptions, écrivez une valeur qui active les exceptions dans le registre FPCR, puis lisez-la. Si l’UC prend en charge les exceptions à virgule flottante, les bits correspondant aux exceptions prises en charge restent définis, tandis que l’UC réinitialise les bits pour les exceptions non prises en charge.

Sur ARM64EC, Windows intercepte les exceptions à virgule flottante du processeur et les désactive dans le registre FPCR. Cela garantit un comportement cohérent entre différentes variantes de processeur.

Routines ABI auxiliaires 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 Descriptif ABI
__os_arm64x_dispatch_call_no_redirect Appelée par un thunk de sortie pour invoquer une cible x64 (soit une fonction x64, soit une séquence accélérée x64). La routine pousse l'adresse de retour ARM64EC (dans le registre LR) suivie de l'adresse de l'instruction qui succède à une instruction blr x16 qui invoque 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 d'avance rapide

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 le thunk de sortie, pour gérer un saut vers une adresse cible qui est soit x64 soit 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, il définit le registre pour qu'il pointe 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 à un point d'appel pour un appel indirect
Arguments :
x9 : Adresse cible
x10 : L'adresse de sortie du thunk
x11 : Adresse de la séquence d'avance rapide

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 section demandée du contexte du registre x64 en cours d'exécution _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 actuel de registre x64 _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 ajustement sans signature et d'autres thunks qui transfèrent directement (jmp) un appel à une autre fonction pouvant avoir n'importe quelle signature, tout en reportant l'application potentielle du thunk approprié à 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 intrinsèques d’entrée : appel de fonction x64 vers 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 le destinataire est ARM64EC, qui n’a pas la notion de "home space", 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 les paramètres restants 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 de 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’appelant x64 et de l’utilisation par celui-ci de la convention d’appel standard ou de __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 octets supplémentaires 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 à l'assistant de l'émulateur en utilisant n'importe quelle instruction de branchement, y compris x64 jmp et call. L’émulateur gère également ces scénarios.

Lorsque l'assistant 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
Microsoft C++ ARM problèmes courants de migration
Noms décorés