Přehled konvencí X64 ABI
Toto téma popisuje základní binární rozhraní aplikace (ABI) pro x64, 64bitové rozšíření architektury x86. Zabývá se tématy, jako jsou konvence volání, rozložení typů, zásobník a využití registru a další.
Konvence volání x64
Mezi x86 a x64 jsou dva důležité rozdíly:
- Možnosti 64bitového adresování
- Šestnáct 64bitových registrů pro obecné použití.
Vzhledem k rozšířené sadě registrů používá x64 konvenci volání __fastcall a model zpracování výjimek založený na RISC.
Konvence __fastcall
používá registry pro první čtyři argumenty a rámec zásobníku k předání více argumentů. Podrobnosti o konvenci volání x64, včetně registrace využití, parametrů zásobníku, návratových hodnot a odvíjení zásobníku, najdete v tématu konvence volání x64.
Další informace o __vectorcall
konvenci volání naleznete v tématu __vectorcall
.
Povolení optimalizace kompilátoru x64
Následující možnost kompilátoru vám pomůže optimalizovat aplikaci pro platformu x64:
Rozložení typu x64 a úložiště
Tato část popisuje úložiště datových typů pro architekturu x64.
Skalární typy
I když je možné přistupovat k datům s jakýmkoli zarovnáním, zarovnat data na jejich přirozené hranici nebo násobek jeho přirozené hranice, aby nedošlo ke ztrátě výkonu. Výčty jsou konstantní celá čísla a považují se za 32bitová celá čísla. Následující tabulka popisuje definici typu a doporučené úložiště pro data, která se týkají zarovnání pomocí následujících hodnot zarovnání:
- Bajt – 8 bitů
- Word – 16 bitů
- Doubleword - 32 bitů
- Čtyřúhelník – 64 bitů
- Octaword – 128 bitů
Skalární typ | Datový typ jazyka C | Velikost úložiště (v bajtech) | Doporučené zarovnání |
---|---|---|---|
INT8 |
char |
0 | Byte |
UINT8 |
unsigned char |
0 | Byte |
INT16 |
short |
2 | Word |
UINT16 |
unsigned short |
2 | Word |
INT32 |
int , long |
4 | Dvojitý meč |
UINT32 |
unsigned int , unsigned long |
4 | Dvojitý meč |
INT64 |
__int64 |
8 | Čtyřúhelník |
UINT64 |
unsigned __int64 |
8 | Čtyřúhelník |
FP32 (jednoduchá přesnost) |
float |
4 | Dvojitý meč |
FP64 (dvojitá přesnost) |
double |
8 | Čtyřúhelník |
POINTER |
* | 8 | Čtyřúhelník |
__m64 |
struct __m64 |
8 | Čtyřúhelník |
__m128 |
struct __m128 |
16 | Octaword |
Agregace a sjednocení rozložení x64
Jiné typy, jako jsou pole, struktury a sjednocení, mají přísnější požadavky na zarovnání, které zajišťují konzistentní agregaci a sjednocovací úložiště a načítání dat. Tady jsou definice polí, struktur a sjednocení:
Pole
Obsahuje uspořádanou skupinu sousedních datových objektů. Každý objekt se nazývá prvek. Všechny prvky v poli mají stejnou velikost a datový typ.
Struktura
Obsahuje uspořádanou skupinu datových objektů. Na rozdíl od prvků pole můžou mít členy struktury různé datové typy a velikosti.
Sjednocení
Objekt, který obsahuje libovolnou sadu pojmenovaných členů. Členy pojmenované sady můžou být libovolného typu. Úložiště přidělené sjednocení se rovná úložišti požadovanému pro největšího člena této sjednocení a veškeré odsazení potřebné pro zarovnání.
Následující tabulka uvádí důrazně doporučené zarovnání skalárních členů sjednocení a struktur.
Skalární typ | Datový typ jazyka C | Požadované zarovnání |
---|---|---|
INT8 |
char |
Byte |
UINT8 |
unsigned char |
Byte |
INT16 |
short |
Word |
UINT16 |
unsigned short |
Word |
INT32 |
int , long |
Dvojitý meč |
UINT32 |
unsigned int , unsigned long |
Dvojitý meč |
INT64 |
__int64 |
Čtyřúhelník |
UINT64 |
unsigned __int64 |
Čtyřúhelník |
FP32 (jednoduchá přesnost) |
float |
Dvojitý meč |
FP64 (dvojitá přesnost) |
double |
Čtyřúhelník |
POINTER |
* | Čtyřúhelník |
__m64 |
struct __m64 |
Čtyřúhelník |
__m128 |
struct __m128 |
Octaword |
Platí následující pravidla agregace zarovnání:
Zarovnání pole je stejné jako zarovnání jednoho z prvků pole.
Zarovnání začátku struktury nebo sjednocení je maximální zarovnání každého jednotlivého člena. Každý člen v rámci struktury nebo sjednocení musí být umístěn ve správném zarovnání, jak je definováno v předchozí tabulce, což může vyžadovat implicitní vnitřní odsazení v závislosti na předchozím členu.
Velikost struktury musí být násobek jeho zarovnání, který může vyžadovat odsazení za posledním členem. Vzhledem k tomu, že struktury a sjednocení lze seskupit do polí, musí každý prvek pole struktury nebo sjednocení začínat a končit na správném zarovnání dříve určeném.
Data je možné zarovnat tak, aby byla větší než požadavky na zarovnání, pokud jsou zachována předchozí pravidla.
Jednotlivý kompilátor může upravit balení struktury z důvodů velikosti. Například /Zp (zarovnání člena struktury) umožňuje úpravu balení struktur.
Příklady zarovnání struktury x64
Následující čtyři příklady každý deklaruje zarovnanou strukturu nebo sjednocení a odpovídající obrázky znázorňují rozložení této struktury nebo sjednocení v paměti. Každý sloupec na obrázku představuje bajt paměti a číslo ve sloupci označuje přesunutí tohoto bajtu. Název na druhém řádku každého obrázku odpovídá názvu proměnné v deklaraci. Stínované sloupce označují odsazení, které je nutné k dosažení zadaného zarovnání.
Příklad 1
// Total size = 2 bytes, alignment = 2 bytes (word).
_declspec(align(2)) struct {
short a; // +0; size = 2 bytes
}
Příklad 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
}
Příklad 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
}
Příklad 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
}
Bitová pole
Bitová pole struktury jsou omezena na 64 bitů a můžou mít typ znaménků, bez znaménka, int64 nebo bez znaménka int64. Bitová pole, která překračují hranici typu, přeskočí bity, aby zarovná bitové pole na další zarovnání typu. Například celočíselná bitová pole nemusí překročit 32bitovou hranici.
Konflikty s kompilátorem x86
Datové typy větší než 4 bajty nejsou automaticky zarovnány do zásobníku, když ke kompilaci aplikace použijete kompilátor x86. Vzhledem k tomu, že architektura kompilátoru x86 je 4 bajtová sada, nesmí být automaticky zarovnaná 4 bajty, například 64bitové celé číslo, automaticky zarovnané na 8 bajtovou adresu.
Práce s nerovnanými daty má dva důsledky.
Přístup k nerovnaným umístěním může trvat déle, než trvá přístup k zarovnaným umístěním.
Nerovná umístění se nedají použít v vzájemně uzamčených operacích.
Pokud požadujete přísnější zarovnání, použijte __declspec(align(N))
u deklarací proměnných. To způsobí, že kompilátor dynamicky zarovná zásobník tak, aby splňoval vaše specifikace. Dynamické úpravy zásobníku za běhu však můžou způsobit pomalejší spouštění aplikace.
Využití registrace x64
Architektura x64 poskytuje 16 registrů pro obecné účely (dále označovaných jako celočíselné registry) a také 16 registrů XMM/YMM, které jsou k dispozici pro použití s plovoucí desetinou čárkou. Volatilní registry jsou pomocné registry, které volající předpokládá, že bude zničen v rámci volání. Nevolatilní registry jsou vyžadovány k zachování jejich hodnot v rámci volání funkce a musí být uloženy volaným, pokud je použit.
Registrace nestálosti a zachování
Následující tabulka popisuje, jak se jednotlivé registry používají napříč voláními funkcí:
Registrovat | Stav | Používání |
---|---|---|
RAX | Nestálý | Návratový registr hodnot |
RCX | Nestálý | První celočíselná hodnota |
RDX | Nestálý | Druhý celočíselná argument |
R8 | Nestálý | Třetí celočíselná hodnota |
R9 | Nestálý | Čtvrtý celočíselné argumenty |
R10:R11 | Nestálý | Volající musí být zachován podle potřeby; použité v pokynech syscall/sysret |
R12:R15 | Netěkavý | Musí být zachováno volanou |
RDI | Netěkavý | Musí být zachováno volanou |
RSI | Netěkavý | Musí být zachováno volanou |
RBX | Netěkavý | Musí být zachováno volanou |
RBP | Netěkavý | Lze použít jako ukazatel rámce; musí být zachována volanou |
RSP | Netěkavý | Ukazatel zásobníku |
XMM0, YMM0 | Nestálý | První argument FP; první argument typu vektoru při __vectorcall použití |
XMM1, YMM1 | Nestálý | Druhý argument FP; druhý argument typu vektoru při __vectorcall použití |
XMM2, YMM2 | Nestálý | Třetí argument FP; třetí argument typu vektoru při __vectorcall použití |
XMM3, YMM3 | Nestálý | Čtvrtý argument FP; čtvrtý argument typu vektoru při __vectorcall použití |
XMM4, YMM4 | Nestálý | Volající musí být zachován podle potřeby; argument pátého typu vektoru při __vectorcall použití |
XMM5, YMM5 | Nestálý | Volající musí být zachován podle potřeby; šestý argument typu vektoru při __vectorcall použití |
XMM6:XMM15, YMM6:YMM15 | Nevolatil (XMM), Volatile (horní polovina YMM) | Musí být zachována volanou. Registrace YMM musí být zachovány podle potřeby volajícím. |
Při ukončení funkce a vstupu funkce do volání knihovny modulu runtime jazyka C a systémových volání systému Windows se očekává, že se vymaže směr příznaku v registru příznaků procesoru.
Využití zásobníku
Podrobnosti o přidělování, zarovnání, typech funkcí a rámech zásobníku na platformě x64 najdete v tématu o využití zásobníku x64.
Prolog a epilog
Každá funkce, která přiděluje prostor zásobníku, volá jiné funkce, ukládá nevolatilní registry nebo používá zpracování výjimek, musí mít prolog, jehož limity adres jsou popsány v datech odvíjení přidružených k příslušné položce tabulky funkcí a epilogy na každém výstupu funkce. Podrobnosti o požadovaném kódu prologu a epilogu na platformě x64 najdete v tématu x64 prolog a epilog.
x64 – ošetření výjimek
Informace o konvencích a datových strukturách používaných k implementaci strukturovaného zpracování výjimek a chování zpracování výjimek jazyka C++ v x64 najdete v tématu zpracování výjimek x64.
Vnitřní objekty a vložené sestavení
Jedním z omezení kompilátoru x64 není podpora vloženého assembleru. To znamená, že funkce, které nelze zapsat v jazyce C nebo C++, musí být buď zapsány jako podprogramy, nebo jako vnitřní funkce podporované kompilátorem. Některé funkce jsou citlivé na výkon, zatímco jiné ne. Funkce citlivé na výkon by měly být implementovány jako vnitřní funkce.
Vnitřní objekty podporované kompilátorem jsou popsány ve vnitřních objektech kompilátoru.
Formát obrázku x64
Formát spustitelné image x64 je PE32+. Spustitelné obrázky (knihovny DLL i EXE) jsou omezeny na maximální velikost 2 gigabajty, takže relativní adresování s 32bitovým posunem je možné použít k adresování statických dat obrázků. Tato data zahrnují tabulku adres importu, řetězcové konstanty, statická globální data atd.