x64 – konvence volání
Tato část popisuje standardní procesy a konvence, které jedna funkce (volající) používá k volání do jiné funkce (volaný) v kódu x64.
Další informace o __vectorcall
konvenci volání najdete v tématu __vectorcall.
Výchozí hodnoty konvence volání
Binární rozhraní x64 (ABI) ve výchozím nastavení používá čtyřregistrovou konvenci rychlého volání volání. V zásobníku volání je přidělen prostor jako stínové úložiště pro volané, aby se tyto registry uložily.
Mezi argumenty volání funkce a registry použitými pro tyto argumenty existuje striktní korespondence 1:1. Všechny argumenty, které se nevejdou do 8 bajtů nebo nejsou 1, 2, 4 nebo 8 bajtů, musí být předány odkazem. Jeden argument se nikdy nerozprostírá mezi více registrů.
Zásobník registru x87 se nepoužívá. Může ho používat volaný, ale zvažte to nestálé mezi voláními funkce. Všechny operace s plovoucí desetinou čárkou se provádějí pomocí 16 registrů XMM.
Celočíselné argumenty se předávají v registrech RCX, RDX, R8 a R9. Argumenty s plovoucí desetinou čárkou se předávají v XMM0L, XMM1L, XMM2L a XMM3L. 16 bajtové argumenty jsou předány odkazem. Předávání parametrů je podrobně popsáno při předávání parametrů. Tyto registry a RAX, R10, R11, XMM4 a XMM5 se považují za nestálé nebo potenciálně změněné volaným při vrácení. Použití registru je podrobně popsané v registru x64 a uložených registrech volajícího nebo volaného.
U prototypovaných funkcí se všechny argumenty před předáním převedou na očekávané volané typy. Volající zodpovídá za přidělování prostoru pro parametry volané osoby. Volající musí vždy přidělit dostatek místa pro uložení čtyř parametrů registru, i když volaný nepřebere tento počet parametrů. Tato konvence zjednodušuje podporu neprotypovaných funkcí jazyka C a funkcí jazyka Vararg C/C++. Pro funkce vararg nebo neprotypované funkce musí být všechny hodnoty s plovoucí desetinou čárkou duplikovány v odpovídajícím registru pro obecné účely. Všechny parametry nad rámec prvních čtyř musí být uloženy v zásobníku za stínovým úložištěm před voláním. Podrobnosti funkce Vararg najdete v nástroji Varargs. Informace o neprotypované funkci jsou podrobně popsány v neprotypovaných funkcích.
Zarovnání
Většina struktur je zarovnaná k jejich přirozenému zarovnání. Primárními výjimkami jsou ukazatel zásobníku a malloc
paměť alloca
, které jsou 16bajtů zarovnané tak, aby pomohly výkonu. Zarovnání nad 16 bajty musí být provedeno ručně. Vzhledem k tomu, že 16 bajtů je běžnou velikost zarovnání pro operace XMM, měla by tato hodnota fungovat pro většinu kódu. Další informace o rozložení struktury a zarovnání najdete v tématu x64 – typ a rozložení úložiště. Informace o rozložení zásobníku najdete v tématu o využití zásobníku x64.
Unwindability
Funkce typu List jsou funkce, které nemění žádné nevolatilní registry. Nechtěná funkce MŮŽE například změnit nestálou funkci RSP. Nebo by se mohlo změnit RSP přidělením dalšího prostoru zásobníku pro místní proměnné. Pokud chcete obnovit nevolatilní registry, když je zpracována výjimka, jsou funkce mimo list opatřeny poznámkami se statickými daty. Data popisují, jak správně uvolnit funkci pomocí libovolné instrukce. Tato data jsou uložena jako pdata nebo procedurní data, která následně odkazují na data xdata, zpracování výjimek. Xdata obsahuje informace o odvíjení a může odkazovat na další pdata nebo funkci obslužné rutiny výjimky.
Prology a epilogy jsou vysoce omezené, aby je bylo možné správně popsat v xdata. Ukazatel zásobníku musí zůstat 16 bajtů zarovnaný v jakékoli oblasti kódu, která není součástí epilogu nebo prologu, s výjimkou funkcí typu list. Funkce typu List se dají jednoduše zrušit simulací návratu, takže nejsou vyžadovány hodnoty pdata a xdata. Podrobnosti o správné struktuře prologů a epilogů funkcí najdete v tématu x64 prolog a epilog. Další informace o zpracování výjimek a zpracování výjimek a odvíjení pdata a xdata najdete v tématu zpracování výjimek x64.
Předávání parametrů
Ve výchozím nastavení konvence volání x64 předává první čtyři argumenty funkci v registrech. Registry použité pro tyto argumenty závisí na pozici a typu argumentu. Zbývající argumenty se nasdílí do zásobníku v pořadí zprava doleva.
Celočíselné hodnotné argumenty ve čtyřech pozicích úplně vlevo jsou předány v pořadí zleva doprava ve formátu RCX, RDX, R8 a R9. Páté a vyšší argumenty se předávají v zásobníku, jak jsme popsali dříve. Všechny celočíselné argumenty v registrech jsou správné, takže volaný může ignorovat horní části registru a přistupovat pouze k části registru potřebné.
Všechny argumenty s plovoucí desetinnou čárkou a dvojitou přesností v prvních čtyřech parametrech se předávají v XMM0 – XMM3 v závislosti na pozici. Hodnoty s plovoucí desetinou čárkou jsou umístěny pouze v celočíselném registru RCX, RDX, R8 a R9, pokud existují argumenty varargs. Podrobnosti najdete v tématu Varargs. Podobně jsou registru XMM0 – XMM3 ignorovány, pokud je odpovídající argument celé číslo nebo typ ukazatele.
__m128
typy, pole a řetězce se nikdy nepředávají okamžitou hodnotou. Místo toho se ukazatel předá do paměti přidělené volajícím. Struktury a sjednocení velikosti 8, 16, 32 nebo 64 bitů a __m64
typů se předávají jako celá čísla stejné velikosti. Struktury nebo sjednocení jiných velikostí se předávají jako ukazatel na paměť přidělenou volajícím. U těchto agregačních typů předaných jako ukazatel, včetně __m128
, musí být dočasná paměť přidělená volajícím zarovnaná na 16 bajtů.
Vnitřní funkce, které nepřidělují prostor zásobníku a nevolají jiné funkce, někdy používají jiné nestálé registry k předání dalších argumentů registru. Tato optimalizace je možná úzkou vazbou mezi kompilátorem a implementací vnitřní funkce.
Volaný je zodpovědný za výpis parametrů registru do jejich stínového prostoru v případě potřeby.
Následující tabulka shrnuje předávání parametrů podle typu a pozice zleva:
Typ parametru | pátý a vyšší | čtvrtý | třetí | vteřina | nejvíce vlevo |
---|---|---|---|---|---|
plovoucí desetiná čárka | stack | XMM3 | XMM2 | XMM1 | XMM0 |
integer | stack | R9 | R8 | RDX | RCX |
Agregace (8, 16, 32 nebo 64 bitů) a __m64 |
stack | R9 | R8 | RDX | RCX |
Další agregace jako ukazatele | stack | R9 | R8 | RDX | RCX |
__m128 , jako ukazatel |
stack | R9 | R8 | RDX | RCX |
Příklad předání argumentu 1 – všechna celá čísla
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
Příklad předání argumentu 2 – všechny plovoucí hodnoty
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
Příklad předávání argumentů 3 – smíšené inty a plovoucí hodnoty
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
Příklad argumentu předaného 4 - __m64
, __m128
a agregací
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
Vararg
Pokud jsou parametry předány prostřednictvím varargs (například argumenty se třemi tečkami), použije se normální konvence předávání parametrů registru. Tato konvence zahrnuje přelití pátého a pozdějšího argumentu do zásobníku. Volaný je zodpovědný za výpis argumentů, které mají jejich adresu. Pouze pro hodnoty s plovoucí desetinou čárkou musí celý registr i registr s plovoucí desetinou čárkou obsahovat hodnotu, pokud volaný očekává hodnotu v celočíselném registru.
Neprotypované funkce
U funkcí, které nejsou plně prototypované, volající předá celočíselné hodnoty jako celá čísla a hodnoty s plovoucí desetinnou čárkou jako dvojitou přesnost. Pouze pro hodnoty s plovoucí desetinou čárkou obsahují celočíselná registrace i registr s plovoucí desetinou čárkou hodnotu v případě, že volaný očekává hodnotu v celočíselném registru.
func1();
func2() { // RCX = 2, RDX = XMM1 = 1.0, and R8 = 7
func1(2, 1.0, 7);
}
Vrácené hodnoty
Skalární návratová hodnota, která se může vejít do 64 bitů včetně __m64
typu, se vrátí prostřednictvím RAX. V XMM0 se vrátí jiné než skalární typy, včetně plovoucích, dvojitých a vektorových typů, jako __m128
je například , __m128i
__m128d
. Stav nepoužívaných bitů v hodnotě vrácené v RAX nebo XMM0 není definován.
Uživatelem definované typy mohou být vráceny hodnotou z globálních funkcí a statických členských funkcí. Pokud chcete vrátit uživatelem definovaný typ podle hodnoty v RAX, musí mít délku 1, 2, 4, 8, 16, 32 nebo 64 bitů. Nesmí obsahovat ani uživatelem definovaný konstruktor, destruktor nebo operátor přiřazení kopírování. Nemůže mít žádné soukromé nebo chráněné nestatické datové členy a žádné nestatické datové členy referenčního typu. Nemůže mít základní třídy ani virtuální funkce. A může mít pouze datové členy, které tyto požadavky splňují. (Tato definice je v podstatě stejná jako typ POD C++03. Vzhledem k tomu, že se definice změnila ve standardu C++11, nedoporučujeme pro tento test používat std::is_pod
.) Jinak volající musí přidělit paměť pro návratovou hodnotu a předat jí ukazatel jako první argument. Zbývající argumenty se pak posunou o jeden argument doprava. Stejný ukazatel musí vrátit volaný v RAX.
Tyto příklady ukazují, jak se parametry a návratové hodnoty předávají pro funkce se zadanými deklaracemi:
Příklad návratové hodnoty 1 – 64bitový výsledek
__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.
Příklad návratové hodnoty 2 – 128bitový výsledek
__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.
Příklad návratové hodnoty 3 – výsledek typu uživatele podle ukazatele
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.
Příklad návratové hodnoty 4 – výsledek typu uživatele podle hodnoty
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.
Uložené registry volajícího nebo volaný
X64 ABI považuje registry RAX, RCX, RDX, R8, R9, R10, R11 a XMM0-XMM5. Při přítomnosti jsou horní části YMM0-YMM15 a ZMM0-ZMM15 také nestálé. Na AVX512VL registruje ZMM, YMM a XMM také 16-31. Pokud je k dispozici podpora AMX, registru dlaždic TMM jsou nestálé. Zvažte volatelní registry zničené u volání funkcí, pokud není možné provádět analýzu, jako je optimalizace celého programu.
X64 ABI považuje registry RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15 a XMM6-XMM15 nonvolatile. Musí být uloženy a obnoveny funkcí, která je používá.
Ukazatelé funkcí
Ukazatele funkce jsou jednoduše ukazatele na popisek příslušné funkce. Pro ukazatele funkcí neexistuje žádný požadavek na obsah (TOC).
Podpora s plovoucí desetinou čárkou pro starší kód
Rejstříky zásobníku MMX a plovoucí desetiny (MM0-MM7/ST0-ST7) se zachovají napříč kontextovými přepínači. Pro tyto registry neexistuje žádná explicitní konvence volání. Použití těchto registrů je přísně zakázáno v kódu režimu jádra.
FPCSR
Stav registrace obsahuje také ovládací slovo FPU x87. Konvence volání určuje, aby tento registr byl nevolatilní.
Ovládací prvek X87 FPU word register se nastaví pomocí následujících standardních hodnot na začátku provádění programu:
Zaregistrovat[bity] | Nastavení |
---|---|
FPCSR[0:6] | Masky výjimek všech 1 (všechny výjimky maskované) |
FPCSR[7] | Rezervováno – 0 |
FPCSR[8:9] | Přesné ovládání – 10B (dvojitá přesnost) |
FPCSR[10:11] | Ovládací prvek zaokrouhlování – 0 (zaokrouhlení na nejbližší) |
FPCSR[12] | Ovládací prvek nekonečna – 0 (nepoužívá se) |
Volaný, který upravuje některá pole v rámci FPCSR, musí je obnovit před návratem do volajícího. Volající, který změnil některá z těchto polí, navíc musí před vyvoláním volaného obnovit jejich standardní hodnoty, pokud volaný neočekává změněné hodnoty.
Existují dvě výjimky pravidel o nevolatilitě kontrolních příznaků:
Ve funkcích, kde zdokumentovaný účel dané funkce je upravit nevolatilní příznaky FPCSR.
Pokud je prokazatelně správné, že porušení těchto pravidel vede k tomu, že program, který se chová stejně jako program, který nesplňuje pravidla, například prostřednictvím analýzy celého programu.
MXCSR
Stav registrace zahrnuje také MXCSR. Konvence volání rozdělí tento registr na nestálou část a nevolatilní část. Těkavá část se skládá ze šesti příznaků stavu v MXCSR[0:5], zatímco zbytek registru MXCSR[6:15] se považuje za nevolatilní.
Nevolatilní část je nastavena na následující standardní hodnoty na začátku provádění programu:
Zaregistrovat[bity] | Nastavení |
---|---|
MXCSR[6] | Denormální nuly - 0 |
MXCSR[7:12] | Masky výjimek všech 1 (všechny výjimky maskované) |
MXCSR[13:14] | Ovládací prvek zaokrouhlování – 0 (zaokrouhlení na nejbližší) |
MXCSR[15] | Vyprázdnění na nulu pro maskovaný podtečení – 0 (vypnuto) |
Volaný, který změní některá z nevolatilních polí v rámci MXCSR, musí je před návratem do volajícího obnovit. Volající, který změnil některá z těchto polí, navíc musí před vyvoláním volaného obnovit jejich standardní hodnoty, pokud volaný neočekává změněné hodnoty.
Existují dvě výjimky pravidel o nevolatilitě kontrolních příznaků:
Ve funkcích, kde zdokumentovaný účel dané funkce je upravit nevolatilní příznaky MXCSR.
Pokud je prokazatelně správné, že porušení těchto pravidel vede k tomu, že program, který se chová stejně jako program, který nesplňuje pravidla, například prostřednictvím analýzy celého programu.
Nepředpokládáme žádné předpoklady o stavu nestálé části registru MXCSR v rámci hranice funkce, pokud ji dokumentace k funkci explicitně nepopisuje.
setjmp/longjmp
Pokud zahrnete setjmpex.h nebo setjmp.h, všechna volání setjmp
nebo longjmp
způsobí unwind, který vyvolá destruktory a __finally
volání. Toto chování se liší od x86, kde zahrnutí setjmp.h vede k __finally
tomu, že klauzule a destruktory se nevyvolávají.
Volání pro setjmp
zachování aktuálního ukazatele zásobníku, nestálých registrů a registrů MXCSR. Volání, která se longjmp
mají vrátit na nejnovější setjmp
web volání a resetuje ukazatel zásobníku, nevolatelní registry a registry MXCSR, zpět do stavu, který je zachován posledním setjmp
voláním.