Übersicht über ARM64EC-ABI-Konventionen

ARM64EC ist eine Binärschnittstelle (Application Binary Interface, ABI), mit der ARM64-Binärdateien nativ und interoperabel mit x64-Code ausgeführt werden können. Insbesondere folgt die ARM64EC ABI x64-Softwarekonventionen, einschließlich Aufrufkonventionen, Stapelnutzung und Datenausrichtung, wodurch ARM64EC- und x64-Code interoperabel werden. Das Betriebssystem emuliert den x64-Teil der Binärdatei. (Die EC in ARM64EC steht für emulationskompatibel.)

Weitere Informationen zu den X64- und ARM64-ABIs finden Sie unter Übersicht über x64 ABI-Konventionen und Übersicht über ARM64 ABI-Konventionen.

ARM64EC löst keine Unterschiede zwischen x64- und ARM-basierten Architekturen im Speichermodell. Weitere Informationen finden Sie unter Allgemeine Probleme bei der Migration von Visual C++ ARM.

Definitionen

  • ARM64 – Der Codestream für ARM64-Prozesse, die herkömmlichen ARM64-Code enthalten.
  • ARM64EC – Der Codedatenstrom, der eine Teilmenge des ARM64-Registers verwendet, um die Interoperabilität mit x64-Code bereitzustellen.

Registrieren der Zuordnung

x64-Prozesse enthalten möglicherweise Threads, die ARM64EC Code ausführen. Daher ist es immer möglich, einen x64-Registerkontext abzurufen, ARM64EC eine Teilmenge der ARM64-Kernregister verwendet, die 1:1 zu emulierten x64-Registern zuordnen. Wichtig ist, dass ARM64EC niemals Register außerhalb dieser Teilmenge verwendet, außer zum Lesen der TEB-Adresse (Thread Environment Block) aus x18.

Systemeigene ARM64-Prozesse sollten nicht in der Leistung zurücktreten, wenn einige oder viele Funktionen wie ARM64EC neu kompiliert werden. Um die Leistung aufrechtzuerhalten, folgt die ABI den folgenden Prinzipien:

  • Die ARM64EC Registeruntermenge enthält alle Register, die Teil der ARM64-Funktionsaufrufkonvention sind.

  • Die ARM64EC Anrufkonvention wird direkt der ARM64-Anrufkonvention zugeordnet.

Spezielle Hilfsroutinen wie __chkstk_arm64ec verwenden benutzerdefinierte Anrufkonventionen und -register. Diese Register sind auch in der ARM64EC Teilmenge der Register enthalten.

Registerzuordnung für ganzzahlige Register

ARM64EC Registrier x64-Register ARM64EC Anrufkonvention Aufrufkonvention bei ARM64-Systemen Aufrufkonvention bei x64-Systemen
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 (niedrige 64 Bit x87-Register R1) volatile volatile volatile
x7 mm2 (niedrige 64 Bit x87-Register R2) volatile volatile volatile
x8 rax volatile volatile volatile
x9 mm3 (niedrige 64 Bit x87-Register R3) volatile volatile volatile
x10 mm4 (niedrige 64 Bit x87-Register R4) volatile volatile volatile
x11 mm5 (niedrige 64 Bit x87-Register R5) volatile volatile volatile
x12 mm6 (niedrige 64 Bit x87-Register R6) volatile volatile volatile
x13 N/V Nicht zulässig volatile
x14 Nicht zulässig volatile N/V
x15 mm7 (niedrige 64 Bit x87-Register R7) volatile volatile volatile
x16 Hohe 16 Bits jeder der x87 R0-R3-Register volatil (xip0) volatil (xip0) volatile
x17 Hohe 16 Bits jeder der x87 R4-R7-Register volatil (xip1) volatil (xip1) volatile
x18 GS.base fixed(TEB) fixed(TEB) fixed(TEB)
x19 r12 Nicht flüchtig Nicht flüchtig Nicht flüchtig
x20 r13 Nicht flüchtig Nicht flüchtig Nicht flüchtig
x21 r14 Nicht flüchtig Nicht flüchtig Nicht flüchtig
x22 r15 Nicht flüchtig Nicht flüchtig Nicht flüchtig
x23 N/V Nicht zulässig Nicht flüchtig
x24 Nicht zulässig Nicht flüchtig N/V
x25 rsi Nicht flüchtig Nicht flüchtig Nicht flüchtig
x26 rdi Nicht flüchtig Nicht flüchtig Nicht flüchtig
x27 rbx Nicht flüchtig Nicht flüchtig Nicht flüchtig
x28 N/V Nicht zulässig Nicht zulässig N/V
fp rbp Nicht flüchtig Nicht flüchtig Nicht flüchtig
lr mm0 (niedrige 64 Bit x87-Register R0) beide beide beide
sp rsp Nicht flüchtig Nicht flüchtig Nicht flüchtig
pc rip Anweisungszeiger Anweisungszeiger Anweisungszeiger
PSTATETeilmengenplattform: N/Z/C/V/SS 1, 2 RFLAGSTeilmengenplattform: SF/ZF/CF/OF/TF volatile volatile volatile
N/V RFLAGSTeilmengenplattform: PF/AF volatile
N/V RFLAGSTeilmengenplattform: DF Nicht flüchtig

1 Vermeiden Sie direktes Lesen, Schreiben oder Berechnen von Zuordnungen zwischen PSTATE und RFLAGS. Diese Bits können in Zukunft verwendet werden und können sich ändern.

2 Das ARM64EC-Carry-Flag C ist das Gegenteil des x64-Carry-Flags CF für Subtraktionsoperationen. Es gibt keine spezielle Behandlung, da das Flag volatil ist und daher beim Übergang zwischen Funktionen (ARM64EC und x64) gelöscht wird.

Registrieren der Zuordnung für Vektorregister

ARM64EC Register x64-Register ARM64EC Anrufkonvention Aufrufkonvention bei ARM64-Systemen Aufrufkonvention bei x64-Systemen
v0-v5 xmm0-xmm5 volatile volatile volatile
v6-v7 xmm6-xmm7 volatile volatile Nicht flüchtig
v8-v15 xmm8-xmm15 volatil 1 volatil 1 Nicht flüchtig
v16-v31 xmm16-xmm31 Nicht zulässig volatile nicht zulässig (der x64-Emulator unterstützt AVX-512 nicht)
FPCR 2 MXCSR[15:6] Nicht flüchtig Nicht flüchtig Nicht flüchtig
FPSR 2 MXCSR[5:0] volatile volatile volatile

1 Diese ARM64-Register sind speziell dafür, dass die unteren 64 Bits nicht veränderlich sind, aber die oberen 64 Bits veränderlich sind. Aus der Sicht eines x64-Anrufers sind sie praktisch volatil, da der Angerufene Daten zerstören würde.

2 Vermeiden Sie direktes Lesen, Schreiben oder Berechnen von Zuordnungen von FPCR und FPSR. Diese Bits können in Zukunft verwendet werden und können sich ändern.

Strukturverpackung

ARM64EC folgt den gleichen Strukturverpackungsregeln, die für x64 verwendet werden, um die Interoperabilität zwischen ARM64EC Code und x64-Code sicherzustellen. Weitere Informationen und Beispiele für die x64-Strukturverpackung finden Sie unter Übersicht über x64 ABI-Konventionen.

Emulationshilfs-ABI-Routinen

ARM64EC Code und Thunks verwenden Emulationshilfsroutinen für den Übergang zwischen x64 und ARM64EC Funktionen.

In der folgenden Tabelle werden die einzelnen speziellen ABI-Routinen und die ABI-Registrierungen beschrieben. Die Routinen ändern die aufgelisteten beibehaltenen Register nicht unter der Spalte ABI. Es sollten keine Annahmen über nicht aufgelistete Register getroffen werden. Auf dem Datenträger sind die ABI-Routinezeiger null. Zur Ladezeit aktualisiert das Ladeprogramm die Zeiger so, dass er auf die x64-Emulatorroutinen verweist.

Name Beschreibung ABI
__os_arm64x_dispatch_call_no_redirect Wird von einem Exit-Thunk aufgerufen, um ein x64-Ziel aufzurufen (entweder eine x64-Funktion oder eine x64-Schnellweiterleitungssequenz). Die Routine verschiebt die ARM64EC Absenderadresse (im LR Register) gefolgt von der Adresse der Anweisung, die eine blr x16 Anweisung erfolgreich ist, die den x64-Emulator aufruft. Anschließend wird die blr x16 Anweisung ausgeführt. Rückgabewert in x8 (rax)
__os_arm64x_dispatch_ret Aufgerufen von einem Eintrag Thunk, um zu seinem x64-Anrufer zurückzukehren. Sie füllt die x64-Absenderadresse aus dem Stapel und ruft den x64-Emulator auf, um dorthin zu springen N/V
__os_arm64x_check_call Wird von ARM64EC Code mit einem Zeiger auf ein Exit-Thunk und die indirekte ARM64EC auszuführende Zieladresse aufgerufen. Das ARM64EC Ziel gilt als patchbar, und die Ausführung wird immer mit denselben Daten, mit der sie aufgerufen wurde, oder mit geänderten Daten an den Aufrufer zurückgegeben Argumente:
x9: Die Zieladresse
x10: Die Exit-Thunk-Adresse
x11: Die Adresse der schnellen Weiterleitungssequenz

Out:
x9: Wenn die Zielfunktion umgeleitet wurde, enthält sie die Adresse der schnellen Weiterleitungssequenz
x10: Die Exit-Thunk-Adresse
x11: Wenn die Funktion umgeleitet wurde, enthält sie die Ausgangs-Thunk-Adresse. Andernfalls springt die Zieladresse zu

Beibehaltenen Register: x0-x8, x15 (chkstk). und q0-q7 geändert.
__os_arm64x_check_icall Wird von ARM64EC Code mit einem Zeiger auf ein Exit-Thunk aufgerufen, um einen Sprung zu einer Zieladresse zu behandeln, die entweder x64 oder ARM64EC ist. Wenn das Ziel x64 ist und der x64-Code nicht gepatcht wurde, legt die Routine das Zieladressregister fest. Er verweist auf die ARM64EC Version der Funktion, sofern vorhanden. Andernfalls wird das Register so festgelegt, dass es auf den Ausgangs-Thunk zeigt, der zum x64-Ziel wechselt. Anschließend kehrt er zum aufrufenden ARM64EC Code zurück, der dann zur Adresse im Register springt. Diese Routine ist eine nicht optimierte Version von __os_arm64x_check_call, bei der die Zieladresse zur Kompilierungszeit nicht bekannt ist

Wird an einem Anrufstandort eines indirekten Anrufs verwendet
Argumente:
x9: Die Zieladresse
x10: Die Exit-Thunk-Adresse
x11: Die Adresse der schnellen Weiterleitungssequenz

Out:
x9: Wenn die Zielfunktion umgeleitet wurde, enthält sie die Adresse der schnellen Weiterleitungssequenz
x10: Die Exit-Thunk-Adresse
x11: Wenn die Funktion umgeleitet wurde, enthält sie die Ausgangs-Thunk-Adresse. Andernfalls springt die Zieladresse zu

Beibehaltene Register: x0-x8, x15 (chkstk) und q0-q7
__os_arm64x_check_icall_cfg Identisch mit __os_arm64x_check_icall, aber überprüft auch, dass die angegebene Adresse ein gültiges Steuerelementflussdiagramm-indirektes Anrufziel ist Argumente:
x10: Die Adresse des Exit-Thunk
x11: Die Adresse der Zielfunktion

Out:
x9: Wenn das Ziel x64 ist, wird die Adresse an die Funktion adressiert. Andernfalls wird der Wert nicht definiert
x10: Die Adresse des Exit-Thunk
x11: Wenn das Ziel x64 ist, enthält es die Adresse des Exit-Thunk. Andernfalls die Adresse der Funktion

Beibehaltene Register: x0-x8, x15 (chkstk) und q0-q7
__os_arm64x_get_x64_information Ruft den angeforderten Teil des Live x64-Registrierungskontexts ab _Function_class_(ARM64X_GET_X64_INFORMATION) NTSTATUS LdrpGetX64Information(_In_ ULONG Type, _Out_ PVOID Output, _In_ PVOID ExtraInfo)
__os_arm64x_set_x64_information Legt den angeforderten Teil des Live x64-Registrierungskontexts fest _Function_class_(ARM64X_SET_X64_INFORMATION) NTSTATUS LdrpSetX64Information(_In_ ULONG Type,_In_ PVOID Input, _In_ PVOID ExtraInfo)
__os_arm64x_x64_jump Wird in signaturlosen Adjustern und anderen Thunks verwendet, die einen Aufruf direkt an eine andere Funktion weiterleiten (jmp), die eine beliebige Signatur haben kann, und so die mögliche Anwendung des richtigen Thunks auf das eigentliche Ziel verschieben Argumente:
x9: Ziel für den Sprung zu

Alle Parameterregister bleiben erhalten (weitergeleitet)

Thunks

Thunks sind die Low-Level-Mechanismen zur Unterstützung von ARM64EC- und x64-Funktionen, die einander aufrufen. Es gibt zwei Arten: Eingabe-Thunks für die Eingabe ARM64EC Funktionen und Ausgabe-Thunks zum Aufrufen von x64-Funktionen.

Eingabe-Thunk und systeminterne Eingabe-Thunks: x64 zum ARM64EC Funktionsaufruf

Um x64-Aufrufer zu unterstützen, wenn eine C/C++-Funktion als ARM64EC kompiliert wird, generiert die Toolkette einen einzelnen Eingabe-Thunk, der aus ARM64EC Computercode besteht. Systeminterne haben einen eigenen Eingabe-Thunk. Alle anderen Funktionen teilen einen Eingabe-Thunk mit allen Funktionen, die über eine übereinstimmende Aufrufkonvention, Parameter und Rückgabetyp verfügen. Der Inhalt des Thunk hängt von der Aufrufkonvention der C/C++-Funktion ab.

Neben der Behandlung von Parametern und der Absenderadresse überbrückt der Thunk die Unterschiede bei der Volatilität zwischen ARM64EC und x64-Vektorregistern, die durch ARM64EC Vektorregisterzuordnung verursacht werden:

ARM64EC Register x64-Register ARM64EC Anrufkonvention Aufrufkonvention bei ARM64-Systemen Aufrufkonvention bei x64-Systemen
v6-v15 xmm6-xmm15 veränderlich, aber im Eingabe-Thunk gespeichert/wiederhergestellt (x64 bis ARM64EC) veränderliche oder teilweise veränderliche obere 64 Bits Nicht flüchtig

Der Eingabe-Thunk führt die folgenden Aktionen aus:

Parameteranzahl Stapelverwendung
0–4 Speichert ARM64EC v6 und v7 im vom Anrufer zugewiesenen Heimbereich

Da der Angerufene ARM64EC ist, der nicht über den Begriff eines Heimbereichs verfügt, werden die gespeicherten Werte nicht überschrieben.

Weist dem Stapel zusätzliche 128 Bytes zu und speichert ARM64EC v8 bis v15.
5-8 x4 = 5. Parameter aus dem Stapel
x5 = 6. Parameter aus dem Stapel
x6 = 7. Parameter aus dem Stapel
x7 = 8. Parameter aus dem Stapel

Wenn der Parameter SIMD ist, werden stattdessen die v4-v7 Register verwendet
+9 Ordnet AlignUp(NumParams - 8 , 2) * 8 Bytes im Stapel zu. *

Kopiert die 9. und verbleibenden Parameter in diesen Bereich

* Das Ausrichten des Werts an einer geraden Zahl garantiert, dass der Stapel auf 16 Byte ausgerichtet bleibt

Wenn die Funktion einen ganzzahligen 32-Bit-Parameter akzeptiert, darf der Thunk nur 32 Bit anstelle der vollständigen 64 Bits des übergeordneten Registers pushen.

Als Nächstes verwendet der Thunk eine ARM64-Anweisung bl, um die ARM64EC-Funktion aufzurufen. Nachdem die Funktion zurückgegeben wurde, gibt der Thunk folgendes zurück:

  1. Rückgängigmachen aller Stapelzuweisungen
  2. Ruft die __os_arm64x_dispatch_ret Emulatorhilfsprogramm auf, um die x64-Absenderadresse einzugeben und die x64-Emulation fortzusetzen.

Ausgabe-Thunk: ARM64EC zu x64-Funktionsaufruf

Für jeden Aufruf, den eine ARM64EC C/C++-Funktion an potenziellen x64-Code macht, generiert die MSVC-Toolkette einen usgabe-Thunk. Der Inhalt des Thunk hängt von den Parametern des x64 Angerufenen ab und ob der Angerufene die Standardanrufkonvention oder __vectorcall verwendet. Der Compiler ruft diese Informationen aus einer Funktionsdeklaration für den Angerufenen ab.

Zuerst verschiebt der Thunk die Absenderadresse, die sich im ARM64EC lr Register befindet, und einen Dummy 8-Byte-Wert, um sicherzustellen, dass der Stapel auf 16 Byte ausgerichtet ist. Zweitens behandelt der Thunk die Parameter:

Parameteranzahl Stapelverwendung
0–4 Ordnet 32 Byte Home Space auf dem Stapel zu
5-8 Weist AlignUp(NumParams - 4, 2) * 8 mehr Bytes höher auf dem Stapel zu. *

Kopiert den 5. und alle nachfolgenden Parameter aus ARM64EC x4-x7 in diesen zusätzlichen Bereich
+9 Kopiert die 9. und verbleibenden Parameter in den zusätzlichen Leerraum

* Das Ausrichten des Werts an einer geraden Zahl garantiert, dass der Stapel auf 16 Bytes ausgerichtet bleibt.

Drittens ruft der Thunk die __os_arm64x_dispatch_call_no_redirect Emulatorhilfsfunktion auf, um den x64-Emulator aufzurufen, um die x64-Funktion auszuführen. Der Anruf muss eine blr x16 Anweisung sein (glücklicherweise ist x16 ein veränderliches Register). Eine blr x16 Anweisung ist erforderlich, da der x64-Emulator diese Anweisung als Hinweis analysiert.

Die x64-Funktion versucht in der Regel, mithilfe einer x64-ret-Anweisung zum Emulatorhilfsprogramm zurückzukehren. An diesem Punkt erkennt der x64-Emulator, dass er sich in ARM64EC Code befindet. Anschließend wird der vorherige 4-Byte-Hinweis gelesen, der als ARM64-blr x16-Anweisung auftritt. Da dieser Hinweis darauf hinweist, dass sich die Absenderadresse in diesem Hilfsprogramm befindet, springt der Emulator direkt zu dieser Adresse.

Die x64-Funktion kann mithilfe einer verzweigten Anweisung, einschließlich x64 jmp und call. Der Emulator behandelt diese Szenarien ebenfalls.

Wenn der Helfer dann wieder zum Thunk zurückkehrt, geht der Thunk wie folgt vor:

  1. Rückgängigmachen aller Stapelzuweisungen
  2. Öffnen des ARM64EC lr Registers
  3. Ausführung einer ARM64-Anweisung ret lr.

ARM64EC Funktionsname Dekoration

Ein ARM64EC Funktionsname weist nach jeder sprachspezifischen Dekoration eine sekundäre Dekoration auf. Für Funktionen mit C-Verknüpfung (ob als C oder mithilfe von extern "C" kompiliert) wird dem Namen eine # vorangestellt. Für C++-dekorierte Funktionen wird ein $$h Tag in den Namen eingefügt.

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

__vectorcall

Die ARM64EC Toolkette unterstützt __vectorcall derzeit nicht. Der Compiler gibt einen Fehler aus, wenn er die __vectorcall Verwendung mit ARM64EC erkennt.

Weitere Informationen

Grundlegendes zu ARM64EC ABI- und Assemblycode
Häufig auftretende ARM-Migrationsprobleme bei Visual C++
Dekorierte Namen