Convenzione di chiamata x64
Questa sezione descrive i processi e le convenzioni standard usati da una funzione (il chiamante) per effettuare chiamate in un'altra funzione (chiamata) nel codice x64.
Per altre informazioni sulla convenzione di __vectorcall
chiamata, vedere __vectorcall.
Impostazioni predefinite delle convenzioni di chiamata
L'interfaccia ABI (Application Binary Interface) x64 usa una convenzione di chiamata rapida a quattro registri per impostazione predefinita. Lo spazio viene allocato nello stack di chiamate come archivio shadow per i chiamato per salvare tali registri.
Esiste una stretta corrispondenza uno-a-uno tra gli argomenti di una chiamata di funzione e i registri usati per tali argomenti. Qualsiasi argomento che non rientra in 8 byte o non è 1, 2, 4 o 8 byte, deve essere passato per riferimento. Un singolo argomento non viene mai distribuito tra più registri.
Lo stack di registri x87 non è usato. Può essere usato dal chiamato, ma considerarlo volatile tra le chiamate di funzione. Tutte le operazioni a virgola mobile vengono eseguite usando i registri XMM 16.
Gli argomenti integer vengono passati nei registri RCX, RDX, R8 e R9. Gli argomenti a virgola mobile vengono passati in XMM0L, XMM1L, XMM2L e XMM3L. Gli argomenti a 16 byte vengono passati per riferimento. Il passaggio dei parametri è descritto in dettaglio in Passaggio di parametri. Questi registri, e RAX, R10, R11, XMM4 e XMM5, sono considerati volatili o potenzialmente modificati da un chiamato al ritorno. L'utilizzo della registrazione è documentato in dettaglio nei registri registrati x64 e nei registri salvati del chiamante/chiamato.
Per le funzioni prototipo, tutti gli argomenti vengono convertiti nei tipi chiamato previsti prima di passare. Il chiamante è responsabile dell'allocazione dello spazio per i parametri del chiamato. Il chiamante deve sempre allocare spazio sufficiente per archiviare quattro parametri di registro, anche se il chiamato non accetta molti parametri. Questa convenzione semplifica il supporto per funzioni del linguaggio C non tipizzato e funzioni vararg C/C++. Per le funzioni vararg o non tipate, tutti i valori a virgola mobile devono essere duplicati nel registro generico corrispondente. Tutti i parametri oltre i primi quattro devono essere archiviati nello stack dopo l'archivio shadow prima della chiamata. I dettagli della funzione Vararg sono disponibili in Varargs. Le informazioni sulle funzioni non tipate sono descritte in dettaglio nelle funzioni non tipate.
Allineamento
La maggior parte delle strutture è allineata al loro allineamento naturale. Le eccezioni principali sono il puntatore dello stack e malloc
o alloca
la memoria, allineati a 16 byte per facilitare le prestazioni. L'allineamento superiore a 16 byte deve essere eseguito manualmente. Poiché 16 byte è una dimensione di allineamento comune per le operazioni XMM, questo valore dovrebbe funzionare per la maggior parte del codice. Per altre informazioni sul layout e l'allineamento della struttura, vedere Layout di archiviazione e tipo x64. Per informazioni sul layout dello stack, vedere Utilizzo dello stack x64.
Rimozione
Le funzioni foglia sono funzioni che non modificano registri non volatili. Una funzione non foglia può cambiare RSP non volatile, ad esempio chiamando una funzione. In alternativa, potrebbe modificare RSP allocando spazio dello stack aggiuntivo per le variabili locali. Per recuperare registri non volatili quando viene gestita un'eccezione, le funzioni non foglia vengono annotate con dati statici. I dati descrivono come rimuovere correttamente la funzione in un'istruzione arbitraria. Questi dati vengono archiviati come dati pdata o procedure, che a loro volta fanno riferimento a xdata, i dati di gestione delle eccezioni. Xdata contiene le informazioni di rimozione e può puntare a pdata aggiuntive o a una funzione del gestore eccezioni.
I prologi e gli epilogi sono altamente limitati in modo che possano essere descritti correttamente in xdata. Il puntatore dello stack deve rimanere allineato a 16 byte in qualsiasi area di codice che non fa parte di un epilogo o di un prologo, ad eccezione delle funzioni foglia. Le funzioni foglia possono essere rimosse semplicemente simulando una restituzione, quindi pdata e xdata non sono necessarie. Per informazioni dettagliate sulla struttura corretta dei prologo e degli epilogi delle funzioni, vedere prologo ed epilogo x64. Per altre informazioni sulla gestione delle eccezioni e sulla gestione delle eccezioni e sulla rimozione di pdata e xdata, vedere Gestione delle eccezioni x64.
Passaggio dei parametri
Per impostazione predefinita, la convenzione di chiamata x64 passa i primi quattro argomenti a una funzione nei registri. I registri utilizzati per questi argomenti dipendono dalla posizione e dal tipo dell'argomento. Gli argomenti rimanenti vengono inseriti nello stack in ordine da destra a sinistra.
Gli argomenti con valori integer nelle quattro posizioni più a sinistra vengono passati rispettivamente in ordine da sinistra a destra in RCX, RDX, R8 e R9. Il quinto e superiore argomenti vengono passati nello stack come descritto in precedenza. Tutti gli argomenti integer nei registri sono giustificati a destra, pertanto il chiamato può ignorare i bit superiori del registro e accedere solo alla parte del registro necessaria.
Tutti gli argomenti a virgola mobile e precisione doppia nei primi quattro parametri vengono passati in XMM0 - XMM3, a seconda della posizione. I valori a virgola mobile vengono inseriti solo nei registri integer RCX, RDX, R8 e R9 quando sono presenti argomenti varargs. Per informazioni dettagliate, vedere Varargs. Analogamente, i registri XMM0 - XMM3 vengono ignorati quando l'argomento corrispondente è un tipo integer o puntatore.
__m128
i tipi, le matrici e le stringhe non vengono mai passati per valore immediato. Al contrario, un puntatore viene passato alla memoria allocata dal chiamante. Gli struct e le unioni di dimensioni 8, 16, 32 o 64 bit e __m64
tipi vengono passati come se fossero numeri interi della stessa dimensione. Gli struct o le unioni di altre dimensioni vengono passati come puntatore alla memoria allocata dal chiamante. Per questi tipi di aggregazione passati come puntatore, inclusa __m128
, la memoria temporanea allocata dal chiamante deve essere allineata a 16 byte.
Funzioni intrinseche che non allocano lo spazio dello stack e che non chiamano altre funzioni, talvolta usano altri registri volatili per passare argomenti di registro aggiuntivi. Questa ottimizzazione è resa possibile dall'associazione stretta tra il compilatore e l'implementazione della funzione intrinseca.
Il chiamato è responsabile del dump dei parametri del registro nello spazio shadow, se necessario.
Nella tabella seguente viene riepilogato il modo in cui i parametri vengono passati, in base al tipo e alla posizione a sinistra:
Tipo parametro | quinto e superiore | quarto | terzo | second | All' estrema sinistra |
---|---|---|---|---|---|
a virgola mobile | stack | XMM3 | XMM2 | XMM1 | XMM0 |
integer | stack | R9 | R8 | RDX | RCX |
Aggregazioni (8, 16, 32 o 64 bit) e __m64 |
stack | R9 | R8 | RDX | RCX |
Altre aggregazioni, come puntatori | stack | R9 | R8 | RDX | RCX |
__m128 , come puntatore |
stack | R9 | R8 | RDX | RCX |
Esempio di argomento che passa 1 - tutti i numeri interi
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
Esempio di argomento che passa 2 - tutti i valori float
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
Esempio di argomento che passa 3 : int misti e float
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
Esempio di argomento che passa 4 - __m64
, __m128
e aggregazioni
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
Varargs
Se i parametri vengono passati tramite varargs (ad esempio, argomenti con puntini di sospensione), si applica la normale convenzione di passaggio del parametro del registro. Tale convenzione include lo spilling del quinto e dei successivi argomenti nello stack. È responsabilità del chiamato scaricare gli argomenti che hanno preso il loro indirizzo. Solo per i valori a virgola mobile, sia il registro integer che il registro a virgola mobile devono contenere il valore, nel caso in cui il chiamato prevede il valore nei registri integer.
Funzioni non tipate
Per le funzioni non completamente prototipo, il chiamante passa valori interi come interi e valori a virgola mobile come precisione doppia. Solo per i valori a virgola mobile, sia il registro integer che il registro a virgola mobile contengono il valore float nel caso in cui il chiamato prevede il valore nei registri integer.
func1();
func2() { // RCX = 2, RDX = XMM1 = 1.0, and R8 = 7
func1(2, 1.0, 7);
}
Valori restituiti
Un valore restituito scalare che può rientrare in 64 bit, incluso il __m64
tipo, viene restituito tramite RAX. I tipi non scalari, inclusi floats, doubles e vector, ad __m128
esempio , __m128i
__m128d
, vengono restituiti in XMM0. Lo stato dei bit non usati nel valore restituito in RAX o XMM0 non è definito.
I tipi definiti dall'utente possono essere restituiti per valore dalle funzioni globali e dalle funzioni membro statiche. Per restituire un tipo definito dall'utente per valore in RAX, deve avere una lunghezza di 1, 2, 4, 8, 16, 32 o 64 bit. Non deve inoltre avere un costruttore, un distruttore o un operatore di assegnazione di copia definito dall'utente. Non può avere membri dati non statici privati o protetti e nessun membro dati non statico di tipo riferimento. Non può avere classi di base o funzioni virtuali. Inoltre, può avere solo membri dati che soddisfano questi requisiti. Questa definizione è essenzialmente uguale a un tipo POD C++03. Poiché la definizione è stata modificata nello standard C++11, non è consigliabile usare std::is_pod
per questo test. In caso contrario, il chiamante deve allocare memoria per il valore restituito e passarvi un puntatore come primo argomento. Gli argomenti rimanenti vengono quindi spostati a destra di un argomento. È necessario che lo stesso puntatore sia restituito dal computer chiamato in RAX.
Questi esempi mostrano in che modo vengono passati i parametri e i valori restituiti per le funzioni con le dichiarazioni specificate:
Esempio di valore restituito 1 - risultato a 64 bit
__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.
Esempio di valore restituito 2 - risultato a 128 bit
__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.
Esempio di valore restituito 3 : risultato del tipo utente in base al puntatore
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.
Esempio di valore restituito 4 - risultato del tipo utente per valore
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.
Registri salvati dal chiamante/chiamato
L'ABI x64 considera i registri RAX, RCX, RDX, R8, R9, R10, R11 e XMM0-XMM5 volatile. Quando presente, anche le parti superiori di YMM0-YMM15 e ZMM0-ZMM15 sono volatili. In AVX512VL, anche i registri ZMM, YMM e XMM 16-31 sono volatili. Quando è presente il supporto AMX, i registri dei riquadri TMM sono volatili. Prendere in considerazione i registri volatili eliminati dalle chiamate di funzione, a meno che non sia altrimenti verificabile da un'analisi, ad esempio l'ottimizzazione dell'intero programma.
L'ABI x64 considera i registri RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15 e XMM6-XMM15 non volatile. Devono essere salvati e ripristinati da una funzione che li usa.
Puntatori a funzioni
I puntatori a funzione sono semplicemente puntatori all'etichetta della rispettiva funzione. Non esiste alcun requisito sommario (TOC) per i puntatori a funzione.
Supporto a virgola mobile per il codice precedente
I registri dello stack MMX e a virgola mobile (MM0-MM7/ST0-ST7) vengono mantenuti tra i commutatori di contesto. Non esiste alcuna convenzione di chiamata esplicita per questi registri. L'uso di questi registri è strettamente vietato nel codice in modalità kernel.
FPCSR
Lo stato del registro include anche la parola di controllo FPU x87. La convenzione di chiamata impone che questo registro sia non volatile.
Il registro delle parole di controllo FPU x87 viene impostato usando i valori standard seguenti all'inizio dell'esecuzione del programma:
Register[bits] | Impostazione |
---|---|
FPCSR[0:6] | Exception masks all 1's (tutte le eccezioni mascherate) |
FPCSR[7] | Riservato - 0 |
FPCSR[8:9] | Controllo precisione - 10B (precisione doppia) |
FPCSR[10:11] | Controllo arrotondamento - 0 (arrotondamento al più vicino) |
FPCSR[12] | Controllo Infinity - 0 (non usato) |
Un chiamato che modifica uno dei campi all'interno di FPCSR deve ripristinarli prima di tornare al chiamante. Inoltre, un chiamante che ha modificato uno di questi campi deve ripristinarli ai valori standard prima di richiamare un chiamato, a meno che il chiamato non preveda i valori modificati.
Esistono due eccezioni alle regole relative alla non volatilità dei flag di controllo:
Nelle funzioni in cui lo scopo documentato della funzione specificata consiste nel modificare i flag FPCSR non volatile.
Quando è provabilmente corretto che la violazione di queste regole comporta un programma che si comporta come un programma che non viola le regole, ad esempio tramite l'analisi dell'intero programma.
MXCSR
Lo stato del registro include anche MXCSR. La convenzione di chiamata divide questo registro in una parte volatile e una parte non volatile. La parte volatile è costituita dai sei flag di stato, in MXCSR[0:5], mentre il resto del registro, MXCSR[6:15], è considerato non volatile.
La parte non volatile è impostata sui valori standard seguenti all'inizio dell'esecuzione del programma:
Register[bits] | Impostazione |
---|---|
MXCSR[6] | I denormali sono zeri - 0 |
MXCSR[7:12] | Exception masks all 1's (tutte le eccezioni mascherate) |
MXCSR[13:14] | Controllo arrotondamento - 0 (arrotondamento al più vicino) |
MXCSR[15] | Scarica su zero per l'underflow mascherato - 0 (disattivato) |
Un chiamato che modifica uno dei campi non volatile all'interno di MXCSR deve ripristinarli prima di tornare al chiamante. Inoltre, un chiamante che ha modificato uno di questi campi deve ripristinarli ai valori standard prima di richiamare un chiamato, a meno che il chiamato non preveda i valori modificati.
Esistono due eccezioni alle regole relative alla non volatilità dei flag di controllo:
Nelle funzioni in cui lo scopo documentato della funzione specificata consiste nel modificare i flag MXCSR non volatile.
Quando è provabilmente corretto che la violazione di queste regole comporta un programma che si comporta come un programma che non viola le regole, ad esempio tramite l'analisi dell'intero programma.
Non fare ipotesi sullo stato della parte volatile del registro MXCSR attraverso un limite di funzione, a meno che la documentazione della funzione non la descriva in modo esplicito.
setjmp/longjmp
Quando si include setjmpex.h o setjmp.h, tutte le chiamate a setjmp
o longjmp
comportano una rimozione che richiama distruttori e __finally
chiamate. Questo comportamento è diverso da x86, in cui l'inclusione di setjmp.h comporta __finally
clausole e distruttori che non vengono richiamati.
Una chiamata a setjmp
mantiene il puntatore allo stack corrente, i registri non volatili e i registri MXCSR. Chiama per longjmp
tornare al sito di chiamata più recente setjmp
e reimposta il puntatore dello stack, i registri non volatili e i registri MXCSR, allo stato mantenuto dalla chiamata più recente setjmp
.