Konwencja wywoływania x64
W tej sekcji opisano standardowe procesy i konwencje używane przez jedną funkcję (obiekt wywołujący) do tworzenia wywołań do innej funkcji (wywoływanej) w kodzie x64.
Aby uzyskać więcej informacji na __vectorcall
temat konwencji wywoływania, zobacz __vectorcall.
Domyślne konwencje wywoływania
Interfejs binarny aplikacji x64 (ABI) domyślnie używa domyślnie konwencji szybkiego wywoływania z czterema rejestrami. Miejsce jest przydzielane na stos wywołań jako magazyn w tle dla wywołań w celu zapisania tych rejestrów.
Istnieje ścisła korespondencja jeden do jednego między argumentami wywołania funkcji a rejestrami używanymi dla tych argumentów. Każdy argument, który nie mieści się w 8 bajtach lub nie jest 1, 2, 4 lub 8 bajtów, musi zostać przekazany przez odwołanie. Jeden argument nigdy nie jest rozłożony na wiele rejestrów.
Stos rejestru x87 jest nieużywany. Może być używany przez obiekt wywoływany, ale należy wziąć pod uwagę, że nietrwały między wywołaniami funkcji. Wszystkie operacje zmiennoprzecinkowe są wykonywane przy użyciu 16 rejestrów XMM.
Argumenty liczb całkowitych są przekazywane w rejestrach RCX, RDX, R8 i R9. Argumenty zmiennoprzecinkowe są przekazywane w XMM0L, XMM1L, XMM2L i XMM3L. Argumenty 16-bajtowe są przekazywane przez odwołanie. Przekazywanie parametrów zostało szczegółowo opisane w artykule Przekazywanie parametrów. Te rejestry i RAX, R10, R11, XMM4 i XMM5 są uważane za niestabilne lub potencjalnie zmienione przez wywoływanie po powrocie. Rejestrowanie użycia zostało szczegółowo udokumentowane w artykule x64 register usage and Caller/callee saved registers (Rejestrowanie użycia i wywoływanie zapisanych rejestrów).
W przypadku funkcji prototypowych wszystkie argumenty są konwertowane na oczekiwane typy wywoływane przed przekazaniem. Obiekt wywołujący jest odpowiedzialny za przydzielanie miejsca dla parametrów obiektu wywoływanego. Obiekt wywołujący musi zawsze przydzielić wystarczającą ilość miejsca do przechowywania czterech parametrów rejestru, nawet jeśli obiekt wywoływany nie bierze tych wielu parametrów. Ta konwencja upraszcza obsługę nietypowych funkcji języka C i funkcji vararg C/C++. W przypadku funkcji vararg lub nietypowych wszystkie wartości zmiennoprzecinkowe muszą być zduplikowane w odpowiednim rejestrze ogólnego przeznaczenia. Wszystkie parametry wykraczające poza pierwsze cztery muszą być przechowywane na stosie po magazynie w tle przed wywołaniem. Szczegóły funkcji Vararg można znaleźć w temacie Varargs. Nietypowe informacje o funkcji są szczegółowo opisane w temacie Funkcje nietypowe.
Wyrównanie
Większość struktur jest wyrównana do ich naturalnego wyrównania. Główne wyjątki to wskaźnik stosu i malloc
alloca
pamięć, które są 16-bajtowe dopasowane do wydajności. Wyrównanie powyżej 16 bajtów należy wykonać ręcznie. Ponieważ 16 bajtów jest typowym rozmiarem wyrównania dla operacji XMM, ta wartość powinna działać dla większości kodu. Aby uzyskać więcej informacji na temat układu struktury i wyrównania, zobacz x64 type and storage layout (Typ x64 i układ magazynu). Aby uzyskać informacje o układzie stosu, zobacz użycie stosu x64.
Brak możliwości przewijania
Funkcje liścia to funkcje, które nie zmieniają żadnych rejestrów nietrwałych. Funkcja nielistna może na przykład zmienić nietrwały RSP przez wywołanie funkcji. Można też zmienić dostawcę zasobów przez przydzielanie dodatkowego miejsca na stos dla zmiennych lokalnych. Aby odzyskać rejestry nietrwałe w przypadku obsługi wyjątku, funkcje inne niż liścia są oznaczone danymi statycznymi. W danych opisano sposób prawidłowego odwijniania funkcji w dowolnej instrukcji. Te dane są przechowywane jako dane pdata lub dane procedury, które z kolei odnoszą się do xdata, danych obsługi wyjątków. Dane xdata zawierają informacje o odwijeniu i mogą wskazywać dodatkowe dane pdata lub funkcję obsługi wyjątków.
Prologi i epilogi są wysoce ograniczone, aby można je było właściwie opisać w xdata. Wskaźnik stosu musi pozostać wyrównany 16 bajtów w dowolnym regionie kodu, który nie jest częścią epilogu ani prologu, z wyjątkiem funkcji liścia. Funkcje liścia mogą być rozwiane po prostu przez symulowanie powrotu, więc pdata i xdata nie są wymagane. Aby uzyskać szczegółowe informacje na temat właściwej struktury prologów funkcji i epilogów, zobacz x64 prolog i epilog. Aby uzyskać więcej informacji na temat obsługi wyjątków oraz obsługi wyjątków i odwijania danych pdata i xdata, zobacz obsługa wyjątków x64.
Przekazywanie parametrów
Domyślnie konwencja wywoływania x64 przekazuje pierwsze cztery argumenty do funkcji w rejestrach. Rejestry używane dla tych argumentów zależą od pozycji i typu argumentu. Pozostałe argumenty są wypychane na stos w kolejności od prawej do lewej.
Argumenty liczb całkowitych w najbardziej lewej czwórce pozycji są przekazywane w kolejności od lewej do prawej odpowiednio w RCX, RDX, R8 i R9. Piąte i wyższe argumenty są przekazywane na stosie zgodnie z wcześniejszym opisem. Wszystkie argumenty całkowite w rejestrach są uzasadnione prawem, więc obiekt wywoływany może zignorować górne bity rejestru i uzyskać dostęp tylko do części rejestru niezbędnej.
Wszystkie argumenty zmiennoprzecinkowe i podwójnej precyzji w pierwszych czterech parametrach są przekazywane w XMM0 - XMM3, w zależności od położenia. Wartości zmiennoprzecinkowe są umieszczane tylko w rejestrach liczb całkowitych RCX, RDX, R8 i R9, gdy istnieją argumenty varargs. Aby uzyskać szczegółowe informacje, zobacz Varargs. Podobnie rejestry XMM0 — XMM3 są ignorowane, gdy odpowiedni argument jest liczbą całkowitą lub typem wskaźnika.
__m128
typy, tablice i ciągi nigdy nie są przekazywane przez wartość natychmiastową. Zamiast tego wskaźnik jest przekazywany do pamięci przydzielonej przez obiekt wywołujący. Struktury i związki o rozmiarze 8, 16, 32 lub 64 bitach i __m64
typach są przekazywane tak, jakby były liczbą całkowitą o tym samym rozmiarze. Struktury lub związki innych rozmiarów są przekazywane jako wskaźnik do pamięci przydzielonej przez obiekt wywołujący. W przypadku tych typów agregacji przekazywanych jako wskaźnik, w tym __m128
, przydzielona pamięć tymczasowa wywołująca musi być wyrównana do 16 bajtów.
Funkcje wewnętrzne, które nie przydzielają miejsca na stosie i nie nazywają innych funkcji, czasami używają innych rejestrów lotnych do przekazywania dodatkowych argumentów rejestru. Ta optymalizacja jest możliwa przez ścisłe powiązanie między kompilatorem a implementacją funkcji wewnętrznej.
Obiekt wywoływany jest odpowiedzialny za zrzucenie parametrów rejestru do ich przestrzeni w tle w razie potrzeby.
Poniższa tabela zawiera podsumowanie sposobu przekazywania parametrów według typu i pozycji po lewej stronie:
Typ parametru | piąty i wyższy | czwarty | trzeci | drugi | Po lewej stronie |
---|---|---|---|---|---|
zmiennoprzecinkowa | stack | XMM3 | XMM2 | XMM1 | XMM0 |
integer | stack | R9 | R8 | RDX | RCX |
Agregacje (8, 16, 32 lub 64 bity) i __m64 |
stack | R9 | R8 | RDX | RCX |
Inne agregacje jako wskaźniki | stack | R9 | R8 | RDX | RCX |
__m128 , jako wskaźnik |
stack | R9 | R8 | RDX | RCX |
Przykład przekazywania argumentu 1 — wszystkie liczby całkowite
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
Przykład przekazywania argumentu 2 — wszystkie zmiennoprzecinki
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
Przykład przekazywania argumentu 3 — mieszanych kropek i zmiennoprzecinków
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
Przykład przekazywania argumentów 4 — __m64
, __m128
i agregacji
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
Elementy vararg
Jeśli parametry są przekazywane przez varargs (na przykład argumenty wielokropka), ma zastosowanie normalna konwencja przekazywania parametrów rejestru. Konwencja ta obejmuje rozlanie piątych i nowszych argumentów do stosu. Jest to odpowiedzialność wywoływana na argumenty zrzutu, które mają ich adres podjęte. Tylko w przypadku wartości zmiennoprzecinkowych zarówno rejestr liczb całkowitych, jak i rejestr zmiennoprzecinkowa musi zawierać wartość, jeśli obiekt wywoływany oczekuje wartości w rejestrach liczb całkowitych.
Funkcje nietypowe
W przypadku funkcji, które nie są w pełni prototypowane, obiekt wywołujący przekazuje wartości całkowite jako liczby całkowite i wartości zmiennoprzecinkowe jako podwójną precyzję. Tylko w przypadku wartości zmiennoprzecinkowych zarówno rejestr liczb całkowitych, jak i rejestr zmiennoprzecinkowy zawierają wartość zmiennoprzecinkową, jeśli obiekt wywoływany oczekuje wartości w rejestrach liczb całkowitych.
func1();
func2() { // RCX = 2, RDX = XMM1 = 1.0, and R8 = 7
func1(2, 1.0, 7);
}
Wartości zwracane
Wartość zwracana skalarna, która może mieścić się w 64 bitach, w tym __m64
typ, jest zwracana za pośrednictwem funkcji RAX. Typy nieskalarne, w tym liczby zmiennoprzecinkowe, podwójne i typy wektorów, takie jak __m128
, __m128i
__m128d
, są zwracane w programie XMM0. Stan nieużywanych bitów w wartości zwróconej w raX lub XMM0 jest niezdefiniowany.
Typy zdefiniowane przez użytkownika mogą być zwracane przez wartość z funkcji globalnych i statycznych funkcji składowych. Aby zwrócić typ zdefiniowany przez użytkownika według wartości w raX, musi mieć długość 1, 2, 4, 8, 16, 32 lub 64 bitów. Nie musi również mieć zdefiniowanego przez użytkownika konstruktora, destruktora ani operatora przypisania kopiowania. Nie może mieć prywatnych ani chronionych niestacjonanych elementów członkowskich danych ani nie statycznych składowych danych typu odwołania. Nie może mieć klas podstawowych ani funkcji wirtualnych. Ponadto może mieć tylko elementy członkowskie danych, które spełniają te wymagania. (Ta definicja jest zasadniczo taka sama jak typ zasobnika C++03. Ponieważ definicja zmieniła się w standardzie C++11, nie zalecamy używania std::is_pod
tego testu). W przeciwnym razie obiekt wywołujący musi przydzielić pamięć dla wartości zwracanej i przekazać do niego wskaźnik jako pierwszy argument. Pozostałe argumenty są następnie przesunięte jednym argumentem po prawej stronie. Ten sam wskaźnik musi być zwracany przez obiekt wywoływany w usłudze RAX.
W tych przykładach pokazano, jak parametry i zwracane wartości są przekazywane dla funkcji z określonymi deklaracjami:
Przykład zwracanej wartości 1 — wynik 64-bitowy
__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.
Przykład wartości zwracanej 2 — wynik 128-bitowy
__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.
Przykład wartości zwracanej 3 — wynik typu użytkownika według wskaźnika
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.
Przykład wartości zwracanej 4 — wynik typu użytkownika według wartości
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.
Zapisane rejestry wywołujące/wywoływane
X64 ABI uwzględnia rejestry RAX, RCX, RDX, R8, R9, R10, R11 i XMM0-XMM5 volatile. W obecnej górnej części YMM0-YMM15 i ZMM0-ZMM15 są również niestabilne. Na AVX512VL rejestry ZMM, YMM i XMM są również niestabilne. Gdy jest obecna obsługa protokołu AMX, rejestry kafelków TMM są nietrwałe. Rozważ nietrwałe rejestry zniszczone w wywołaniach funkcji, chyba że w inny sposób można je prowokować przez analizę, taką jak optymalizacja całego programu.
X64 ABI uwzględnia rejestry RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15 i XMM6-XMM15 nonvolatile. Należy je zapisać i przywrócić za pomocą funkcji, która ich używa.
Wskaźniki funkcji
Wskaźniki funkcji to po prostu wskaźniki do etykiety odpowiedniej funkcji. Nie ma wymagania spisu treści (TOC) dla wskaźników funkcji.
Obsługa zmiennoprzecinkowa dla starszego kodu
Rejestry stosów MMX i zmiennoprzecinkowych (MM0-MM7/ST0-ST7) są zachowywane w przełącznikach kontekstowych. Nie ma jawnej konwencji wywoływania tych rejestrów. Korzystanie z tych rejestrów jest ściśle zabronione w kodzie trybu jądra.
UKŁAD FPCSR
Stan rejestru zawiera również słowo sterujące x87 FPU. Konwencja wywołująca nakazuje, aby ten rejestr był nonvolatile.
Rejestr wyrazów sterujących x87 FPU jest ustawiany przy użyciu następujących standardowych wartości na początku wykonywania programu:
Rejestrowanie[bity] | Ustawienie |
---|---|
FPCSR[0:6] | Wyjątki maskuje wszystkie 1 (wszystkie wyjątki maskowane) |
FPCSR[7] | Zarezerwowane — 0 |
FPCSR[8:9] | Kontrolka precyzji — 10B (podwójna precyzja) |
FPCSR[10:11] | Kontrolka zaokrąglania — 0 (zaokrąglone do najbliższej) |
FPCSR[12] | Kontrolka Nieskończoność — 0 (nie jest używana) |
Obiekt wywoływany, który modyfikuje dowolne pola w fpCSR, musi je przywrócić przed powrotem do obiektu wywołującego. Ponadto obiekt wywołujący, który zmodyfikował dowolne z tych pól, musi przywrócić je do swoich wartości standardowych przed wywołaniem wywoływania, chyba że zgodnie z umową wywoływany obiekt wywoływany oczekuje zmodyfikowanych wartości.
Istnieją dwa wyjątki od reguł dotyczących braku zmienności flag kontroli:
W funkcjach, w których udokumentowany cel danej funkcji jest modyfikowanie niewolnych flag FPCSR.
Gdy jest to możliwe, że naruszenie tych reguł powoduje, że program zachowuje się tak samo jak program, który nie narusza reguł, na przykład za pośrednictwem analizy całego programu.
MXCSR
Stan rejestracji obejmuje również mxCSR. Konwencja wywołania dzieli ten rejestr na nietrwałą część i część niewolną. Część volatile składa się z sześciu flag stanu w MXCSR[0:5], podczas gdy reszta rejestru MXCSR[6:15] jest uważana za niewolną.
Część nonvolatile jest ustawiona na następujące wartości standardowe na początku wykonywania programu:
Rejestrowanie[bity] | Ustawienie |
---|---|
MXCSR[6] | Denormale to zera - 0 |
MXCSR[7:12] | Wyjątki maskuje wszystkie 1 (wszystkie wyjątki maskowane) |
MXCSR[13:14] | Kontrolka zaokrąglania — 0 (zaokrąglone do najbliższej) |
MXCSR[15] | Opróżnij do zera dla zamaskowanego podpełnienia — 0 (wyłączone) |
Obiekt wywoływany, który modyfikuje dowolne z pól nienalotnych w usłudze MXCSR, musi je przywrócić przed powrotem do obiektu wywołującego. Ponadto obiekt wywołujący, który zmodyfikował dowolne z tych pól, musi przywrócić je do swoich wartości standardowych przed wywołaniem wywoływania, chyba że zgodnie z umową wywoływany obiekt wywoływany oczekuje zmodyfikowanych wartości.
Istnieją dwa wyjątki od reguł dotyczących braku zmienności flag kontroli:
W funkcjach, w których udokumentowany cel danej funkcji jest modyfikowanie flag MXCSR bezvolatile.
Gdy jest to możliwe, że naruszenie tych reguł powoduje, że program zachowuje się tak samo jak program, który nie narusza reguł, na przykład za pośrednictwem analizy całego programu.
Nie należy zakładać stanu nietrwałej części rejestru MXCSR w granicach funkcji, chyba że w dokumentacji funkcji jawnie go opisano.
setjmp/longjmp
Po dołączeniu parametru setjmpex.h lub setjmp.h wszystkie wywołania setjmp
metody lub longjmp
powodują odwijanie, które wywołuje destruktory i __finally
wywołania. To zachowanie różni się od x86, gdzie dołączenie setjmp.h powoduje, że __finally
klauzule i destruktory nie są wywoływane.
Wywołanie w celu setjmp
zachowania bieżącego wskaźnika stosu, rejestrów nietrwałych i rejestrów MXCSR. Wywołuje metodę longjmp
powrotu do najnowszej setjmp
lokacji wywołań i resetuje wskaźnik stosu, rejestry nietrwałe i rejestry MXCSR, z powrotem do stanu zachowanego przez najnowsze setjmp
wywołanie.