Omówienie konwencji X64 ABI

W tym temacie opisano podstawowy interfejs binarny aplikacji (ABI) dla x64, rozszerzenie 64-bitowe do architektury x86. Obejmuje on tematy, takie jak konwencja wywoływania, układ typu, stos i rejestrowanie użycia itd.

Konwencje wywoływania x64

Istnieją dwie ważne różnice między x86 i x64:

  • Możliwość adresowania 64-bitowego
  • Szesnaście rejestrów 64-bitowych do użytku ogólnego.

Biorąc pod uwagę rozszerzony zestaw rejestrów, x64 używa __fastcall konwencji wywoływania i modelu obsługi wyjątków opartego na protokole RISC.

Konwencja __fastcall używa rejestrów dla pierwszych czterech argumentów, a ramka stosu przekazuje więcej argumentów. Aby uzyskać szczegółowe informacje na temat konwencji wywoływania x64, w tym rejestrowania użycia, parametrów stosu, wartości zwracanych i odwijania stosu, zobacz konwencję wywoływania x64.

Włączanie optymalizacji kompilatora x64

Poniższa opcja kompilatora pomaga zoptymalizować aplikację pod kątem architektury x64:

Typ x64 i układ magazynu

W tej sekcji opisano przechowywanie typów danych dla architektury x64.

Typy skalarne

Chociaż istnieje możliwość uzyskania dostępu do danych przy użyciu dowolnego wyrównania, wyrównania danych na granicy naturalnej lub wielu jej granic naturalnych, aby uniknąć utraty wydajności. Wyliczenia są stałymi liczbami całkowitymi i są traktowane jako 32-bitowe liczby całkowite. W poniższej tabeli opisano definicję typu i zalecany magazyn danych w odniesieniu do wyrównania przy użyciu następujących wartości wyrównania:

  • Bajt — 8 bitów
  • Word — 16 bitów
  • Doubleword — 32 bity
  • Quadword — 64 bity
  • Octaword — 128 bitów
Typ skalarny Typ danych języka C Rozmiar magazynu (w bajtach) Zalecane wyrównanie
INT8 char 1 Byte
UINT8 unsigned char 1 Byte
INT16 short 2 Word
UINT16 unsigned short 2 Word
INT32 int, long 4 Podwójna kolejność
UINT32 unsigned int, unsigned long 4 Podwójna kolejność
INT64 __int64 8 Czworokąt
UINT64 unsigned __int64 8 Czworokąt
FP32 (pojedyncza precyzja) float 4 Podwójna kolejność
FP64 (podwójna precyzja) double 8 Czworokąt
POINTER * 8 Czworokąt
__m64 struct __m64 8 Czworokąt
__m128 struct __m128 16 Octaword

Układ agregacji i unii x64

Inne typy, takie jak tablice, struktury i związki, mają bardziej rygorystyczne wymagania dotyczące wyrównania, które zapewniają spójne agregowanie i przechowywanie w unii oraz pobieranie danych. Poniżej przedstawiono definicje tablicy, struktury i unii:

  • Tablica

    Zawiera uporządkowaną grupę sąsiednich obiektów danych. Każdy obiekt jest nazywany elementem. Wszystkie elementy w tablicy mają ten sam rozmiar i typ danych.

  • Struktura

    Zawiera uporządkowaną grupę obiektów danych. W przeciwieństwie do elementów tablicy elementy członkowskie struktury mogą mieć różne typy danych i rozmiary.

  • Unia

    Obiekt, który zawiera dowolny zestaw nazwanych elementów członkowskich. Składowe nazwanego zestawu mogą być dowolnego typu. Magazyn przydzielony dla unii jest równy magazynowi wymaganemu dla największego członka tej unii, a także wszelkie wypełnienie wymagane do wyrównania.

W poniższej tabeli przedstawiono zdecydowanie zalecane wyrównanie składowych skalarnych związków i struktur.

Typ skalarny Typ danych języka C Wymagane wyrównanie
INT8 char Byte
UINT8 unsigned char Byte
INT16 short Word
UINT16 unsigned short Word
INT32 int, long Podwójna kolejność
UINT32 unsigned int, unsigned long Podwójna kolejność
INT64 __int64 Czworokąt
UINT64 unsigned __int64 Czworokąt
FP32 (pojedyncza precyzja) float Podwójna kolejność
FP64 (podwójna precyzja) double Czworokąt
POINTER * Czworokąt
__m64 struct __m64 Czworokąt
__m128 struct __m128 Octaword

Obowiązują następujące reguły wyrównania agregacji:

  • Wyrównanie tablicy jest takie samo jak wyrównanie jednego z elementów tablicy.

  • Wyrównanie początku struktury lub unii jest maksymalnym dopasowaniem każdego elementu członkowskiego. Każdy element członkowski w strukturze lub unii musi być umieszczony w odpowiednim wyrównaniu zgodnie z definicją w poprzedniej tabeli, co może wymagać niejawnego wypełnienia wewnętrznego, w zależności od poprzedniego elementu członkowskiego.

  • Rozmiar struktury musi być integralną wielokrotną jego wyrównaniem, co może wymagać wypełnienia po ostatnim elemencie. Ponieważ struktury i związki mogą być zgrupowane w tablicach, każdy element tablicy struktury lub unii musi zaczynać się i kończyć na odpowiednim wyrównaniu wcześniej określonym.

  • Istnieje możliwość dostosowania danych w taki sposób, aby były większe niż wymagania dotyczące wyrównania, o ile poprzednie reguły są utrzymywane.

  • Pojedynczy kompilator może dostosować pakowanie struktury ze względów rozmiaru. Na przykład /Zp (Wyrównanie składowe struktury) umożliwia dostosowanie pakowania struktur.

Przykłady wyrównania struktury x64

Poniższe cztery przykłady deklarują wyrównaną strukturę lub unię, a odpowiadające im liczby ilustrują układ tej struktury lub unii w pamięci. Każda kolumna na rysunku reprezentuje bajt pamięci, a liczba w kolumnie wskazuje przemieszczenie tego bajtu. Nazwa w drugim wierszu każdego rysunku odpowiada nazwie zmiennej w deklaracji . Zacienione kolumny wskazują wypełnienie wymagane do osiągnięcia określonego wyrównania.

Przykład 1

// Total size = 2 bytes, alignment = 2 bytes (word).

_declspec(align(2)) struct {
    short a;      // +0; size = 2 bytes
}

Diagram showing the example 1 structure layout.

Przykład 2

// Total size = 24 bytes, alignment = 8 bytes (quadword).

_declspec(align(8)) struct {
    int a;       // +0; size = 4 bytes
    double b;    // +8; size = 8 bytes
    short c;     // +16; size = 2 bytes
}

Diagram showing the example 2 structure layout.

Przykład 3

// Total size = 12 bytes, alignment = 4 bytes (doubleword).

_declspec(align(4)) struct {
    char a;       // +0; size = 1 byte
    short b;      // +2; size = 2 bytes
    char c;       // +4; size = 1 byte
    int d;        // +8; size = 4 bytes
}

Diagram showing the example 3 structure layout.

Przykład 4

// Total size = 8 bytes, alignment = 8 bytes (quadword).

_declspec(align(8)) union {
    char *p;      // +0; size = 8 bytes
    short s;      // +0; size = 2 bytes
    long l;       // +0; size = 4 bytes
}

Diagram showing the example 4 union layout.

Pola bitów

Pola bitów struktury są ograniczone do 64 bitów i mogą być typu podpisane int, niepodpisane int, int64 lub niepodpisane int64. Pola bitowe, które przekraczają granicę typu, pominie bity w celu wyrównania pola bitowego do następnego wyrównania typu. Na przykład pola bitowe liczb całkowitych mogą nie przekraczać granicy 32-bitowej.

Konflikty z kompilatorem x86

Typy danych, które są większe niż 4 bajty, nie są automatycznie wyrównane do stosu podczas kompilowania aplikacji przy użyciu kompilatora x86. Ponieważ architektura kompilatora x86 jest stosem wyrównanym do 4 bajtów, cokolwiek większego niż 4 bajty, na przykład 64-bitowej liczby całkowitej, nie można automatycznie wyrównać do adresu 8-bajtowego.

Praca z danymi nieprzyznanymi ma dwa implikacje.

  • Uzyskanie dostępu do nieprzygotowanych lokalizacji może potrwać dłużej niż uzyskanie dostępu do wyrównanych lokalizacji.

  • Nie można używać nieprzystawionych lokalizacji w operacjach połączonych.

Jeśli potrzebujesz bardziej ścisłego wyrównania, użyj __declspec(align(N)) deklaracji zmiennych. Dzięki temu kompilator dynamicznie wyrównuje stos zgodnie ze specyfikacjami. Jednak dynamiczne dostosowywanie stosu w czasie wykonywania może spowodować wolniejsze wykonywanie aplikacji.

Rejestrowanie użycia x64

Architektura x64 zapewnia 16 rejestrów ogólnego przeznaczenia (określanych tutaj jako rejestry całkowite), a także 16 rejestrów XMM/YMM dostępnych do użytku zmiennoprzecinkowego. Rejestry lotne to rejestry rysowane zakładane przez obiekt wywołujący, które mają zostać zniszczone przez wywołanie. Rejestry niewolne są wymagane do zachowania ich wartości w wywołaniu funkcji i muszą zostać zapisane przez wywoływanie, jeśli jest używane.

Rejestrowanie zmienności i zachowania

W poniższej tabeli opisano sposób użycia każdego rejestru między wywołaniami funkcji:

Zarejestruj Status Używanie
RAX Lotnych Rejestr wartości zwracanych
RCX Lotnych Pierwszy argument liczby całkowitej
RDX Lotnych Drugi argument liczby całkowitej
R8 Lotnych Trzeci argument liczby całkowitej
R9 Lotnych Czwarty argument liczby całkowitej
R10:R11 Lotnych Należy zachować zgodnie z potrzebami przez obiekt wywołujący; używane w instrukcjach syscall/sysret
R12:R15 Nieulotna Należy zachować przez wywoływanie
RDI Nieulotna Należy zachować przez wywoływanie
RSI Nieulotna Należy zachować przez wywoływanie
RBX Nieulotna Należy zachować przez wywoływanie
RBP Nieulotna Może być używany jako wskaźnik ramki; muszą być zachowywane przez obiekt wywoływany
RSP Nieulotna Wskaźnik stosu
XMM0, YMM0 Lotnych Pierwszy argument FP; pierwszy argument typu wektorowego, gdy __vectorcall jest używany
XMM1, YMM1 Lotnych Drugi argument FP; drugi argument typu wektora, gdy __vectorcall jest używany
XMM2, YMM2 Lotnych Trzeci argument FP; trzeci argument typu wektorowego, gdy __vectorcall jest używany
XMM3, YMM3 Lotnych Czwarty argument FP; czwarty argument typu wektora, gdy __vectorcall jest używany
XMM4, YMM4 Lotnych Należy zachować zgodnie z potrzebami przez obiekt wywołujący; piąty argument typu wektora, gdy __vectorcall jest używany
XMM5, YMM5 Lotnych Należy zachować zgodnie z potrzebami przez obiekt wywołujący; szósty argument typu wektora, gdy __vectorcall jest używany
XMM6:XMM15, YMM6:YMM15 Nonvolatile (XMM), Volatile (górna połowa YMM) Należy zachować przez wywoływanie. Rejestry YMM muszą być zachowywane zgodnie z potrzebami przez obiekt wywołujący.

Po zakończeniu działania funkcji i wpisie funkcji do wywołań biblioteki środowiska uruchomieniowego języka C i wywołaniach systemu Windows należy wyczyścić flagę kierunku w rejestrze flag procesora CPU.

Użycie stosu

Aby uzyskać szczegółowe informacje na temat alokacji stosu, wyrównania, typów funkcji i ramek stosu w architekturze x64, zobacz użycie stosu x64.

Prolog i epilog

Każda funkcja, która przydziela przestrzeń stosu, wywołuje inne funkcje, zapisuje rejestry niezawolone lub używa obsługi wyjątków, musi mieć prolog, którego limity adresów są opisane w danych odwijanych skojarzonych z odpowiednim wpisem tabeli funkcji i epilogami w każdym wyjściu do funkcji. Aby uzyskać szczegółowe informacje na temat wymaganego kodu prologu i epilogu na x64, zobacz x64 prolog i epilog.

Obsługa wyjątku x64

Aby uzyskać informacje na temat konwencji i struktur danych używanych do implementowania obsługi wyjątków strukturalnych i obsługi wyjątków języka C++ w architekturze x64, zobacz obsługa wyjątków x64.

Funkcje wewnętrzne i wbudowany zestaw

Jednym z ograniczeń kompilatora x64 jest brak wbudowanej obsługi asemblera. Oznacza to, że funkcje, których nie można zapisać w języku C lub C++, muszą być zapisywane jako podroutines lub jako funkcje wewnętrzne obsługiwane przez kompilator. Niektóre funkcje są wrażliwe na wydajność, a inne nie. Funkcje wrażliwe na wydajność powinny być implementowane jako funkcje wewnętrzne.

Funkcje wewnętrzne obsługiwane przez kompilator są opisane w temacie Funkcje wewnętrzne kompilatora.

Format obrazu x64

Format obrazu wykonywalnego x64 to PE32+. Obrazy wykonywalne (zarówno biblioteki DLL, jak i EXEs) są ograniczone do maksymalnego rozmiaru 2 gigabajtów, więc względne adresowanie z przemieszczeniem 32-bitowym może służyć do adresowania danych obrazów statycznych. Te dane obejmują tabelę adresów importu, stałe ciągów, statyczne dane globalne itd.

Zobacz też

Konwencje wywoływania