x64 呼叫慣例
本節描述一個函式(呼叫端)用來在 x64 程式碼中呼叫另一個函式(被呼叫者)的標準程式和慣例。
如需呼叫慣例的詳細資訊 __vectorcall
,請參閱 __vectorcall 。
呼叫慣例預設值
x64 應用程式二進位介面 (ABI) 預設會使用四暫存器快速呼叫呼叫慣例。 呼叫堆疊上配置空間做為陰影存放區,供被呼叫者儲存這些暫存器。
函式調用的引數與用於這些引數的暫存器之間有嚴格的一對一對應。 任何不符合 8 個位元組或不是 1、2、4 或 8 個位元組的引數,都必須以傳址方式傳遞。 單一引數絕不會分散到多個暫存器。
未使用 x87 暫存器堆疊。 它可由被呼叫者使用,但請考慮在函式呼叫之間變動。 所有浮點運算都是使用 16 個 XMM 暫存器來完成。
整數引數會傳入暫存器 RCX、RDX、R8 和 R9。 浮點引數會傳入 XMM0L、XMM1L、XMM2L 和 XMM3L。 16 位元組的引數會以傳址方式傳遞。 參數傳遞會在參數傳遞 中 詳細說明。 這些暫存器,以及 RAX、R10、R11、XMM4 和 XMM5,會被視為 揮發性 ,或可能由被呼叫者在傳回時變更。 註冊使用量詳述于 x64 註冊使用量 和 呼叫端/被呼叫者已儲存暫存器 中。
針對原型函式,所有引數都會在傳遞之前轉換成預期的被呼叫端類型。 呼叫端負責配置被呼叫者參數的空間。 呼叫端必須一律配置足夠的空間來儲存四個暫存器參數,即使被呼叫者不採用這麼多參數也一樣。 此慣例可簡化對非屬性 C 語言函式和 vararg C/C++ 函式的支援。 對於 vararg 或非屬性型別函式,任何浮點值都必須在對應的一般用途暫存器中重複。 前四個以外的任何參數都必須儲存在陰影存放區之後的堆疊上,再呼叫。 您可以在 Varargs 中找到 Vararg 函式詳細資料。 Unprototyped 函式資訊詳述于 Unprototyped 函式 中。
對齊方式
大部分的結構會對齊其自然對齊方式。 主要例外狀況是堆疊指標和 malloc
或 alloca
記憶體,其為 16 位元組,以協助效能。 必須手動完成 16 個位元組以上的對齊。 由於 16 個位元組是 XMM 作業的常見對齊大小,因此此值應該適用于大部分的程式碼。 如需結構配置和對齊的詳細資訊,請參閱 x64 類型和儲存體配置 。 如需堆疊配置的相關資訊,請參閱 x64 堆疊使用量 。
回溯性
分葉函式是不會變更任何非揮發性暫存器的函式。 例如,透過呼叫函式,非分葉函式可能會變更非揮發性 RSP。 或者,它可以藉由為區域變數配置額外的堆疊空間來變更 RSP。 若要在處理例外狀況時復原非揮發性暫存器,請使用靜態資料標注非分葉函式。 資料描述如何在任意指令中正確回溯函式。 此資料會儲存為 pdata ,或程式資料,接著會 參考 xdata 例外狀況處理資料。 xdata 包含回溯資訊,而且可以指向其他 pdata 或例外狀況處理常式函式。
Prolog 和 epilogs 受到高度限制,因此可以在 xdata 中正確描述它們。 除了分葉函式內,堆疊指標必須在不屬於 epilog 或 prolog 的任何程式碼區域中保持 16 位元組對齊。 分葉函式只要模擬傳回即可解譯,因此不需要 pdata 和 xdata。 如需函式初構和表文的適當結構詳細資訊,請參閱 x64 初構和表結 。 如需例外狀況處理的詳細資訊,以及 pdata 和 xdata 的例外狀況處理和回溯,請參閱 x64 例外狀況處理 。
參數傳遞
根據預設,x64 呼叫慣例會將前四個引數傳遞至暫存器中的函式。 用於這些引數的暫存器取決於引數的位置和類型。 其餘的引數會依由右至左的順序推入堆疊。
最左邊四個位置的整數值引數分別以 RCX、RDX、R8 和 R9 的從左至右順序傳遞。 第五個和更高引數會在堆疊上傳遞,如先前所述。 暫存器中的所有整數引數都是靠右對齊的,因此被呼叫者可以忽略暫存器上層位,並只存取必要的暫存器部分。
前四個參數中的任何浮點和雙精確度引數會根據位置傳入 XMM0 - XMM3。 當有 varargs 引數時,浮點值只會放在整數暫存器 RCX、RDX、R8 和 R9 中。 如需詳細資訊,請參閱 Varargs 。 同樣地,當對應的引數為整數或指標類型時,會忽略 XMM0 - XMM3 暫存器。
__m128
類型、陣列和字串絕不會以即時值傳遞。 相反地,指標會傳遞至呼叫端所配置的記憶體。 大小為 8、16、32 或 64 位的結構和等位和 __m64
類型會傳遞,就像是相同大小的整數一樣。 其他大小的結構或等位會當做呼叫端所配置的記憶體指標傳遞。 對於作為指標傳遞的這些匯總類型,包括 __m128
,呼叫端配置的暫存記憶體必須對齊 16 位元組。
未配置堆疊空間且不會呼叫其他函式的內部函式,有時會使用其他動態暫存器來傳遞其他暫存器引數。 編譯器與內建函式實作之間的緊密系結可達成此優化。
被呼叫者負責視需要將暫存器參數傾印到其陰影空間。
下表摘要說明如何依左側的類型和位置傳遞參數:
參數類型 | 第五個和更高 | 第四 | 第三 | second | 左邊 |
---|---|---|---|---|---|
浮點數 | stack | XMM3 | XMM2 | XMM1 | XMM0 |
整數 | stack | R9 | R8 | RDX | RCX |
匯總 (8、16、32 或 64 位) 和 __m64 |
stack | R9 | R8 | RDX | RCX |
其他匯總,作為指標 | stack | R9 | R8 | RDX | RCX |
__m128 ,做為指標 |
stack | R9 | R8 | RDX | RCX |
傳遞 1 - 所有整數的引數範例
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
傳遞 2 的引數範例 - 所有浮點數
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
傳遞 3 - 混合 ints 和 floats 的引數範例
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
傳遞 4 - __m64
、 __m128
和 匯總的引數範例
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
如果參數是透過 varargs 傳遞(例如省略號引數),則會套用一般暫存器參數傳遞慣例。 該慣例包括將第五個和更新版本的引數溢出至堆疊。 被呼叫者有責任傾印他們位址的論點。 如果只有浮點值,整數暫存器和浮點暫存器都必須包含值,如果被呼叫者預期整數暫存器中的值。
Unprototyped 函式
對於未完全原型的函式,呼叫端會將整數值當做整數傳遞,並將浮點值當做雙精確度傳遞。 只有浮點值,整數暫存器和浮點暫存器都包含浮點數,以防被呼叫者預期整數暫存器中的值。
func1();
func2() { // RCX = 2, RDX = XMM1 = 1.0, and R8 = 7
func1(2, 1.0, 7);
}
傳回值
可放入 64 位的純量傳回值,包括 __m64
型別,會透過 RAX 傳回。 XMM0 中會傳回非純量型別,包括浮點數、雙精度浮點數和向量型別,例如 __m128
__m128i
、 __m128d
。 對於 RAX 或 XMM0 中傳回的值,其中未使用之位元的狀態尚未定義。
使用者定義的類型可透過全域函式和靜態成員函式的值傳回。 若要依 RAX 中的值傳回使用者定義型別,其長度必須為 1、2、4、8、16、32 或 64 位。 它也必須沒有使用者定義的建構函式、解構函式或複製指派運算子。 它不能有私人或受保護的非靜態資料成員,也沒有參考類型的非靜態資料成員。 它不能有基類或虛擬函式。 而且,它只能有也符合這些需求的資料成員。 (此定義基本上與 C++03 POD 類型相同。由於定義已在 C++11 標準中變更,因此不建議使用此 std::is_pod
測試。否則,呼叫端必須配置傳回值的記憶體,並將指標傳遞為第一個引數。 其餘的引數接著會向右移位一個引數。 在 RAX 中的被呼叫端必須傳回相同的指標。
這些範例展示有指定之宣告的函式如何傳遞參數和傳回值:
傳回值 1 - 64 位結果的範例
__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.
傳回值 2 - 128 位結果的範例
__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.
傳回值 3 的範例 - 依指標的使用者類型結果
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.
傳回值 4 的範例 - 依值的使用者類型結果
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.
呼叫端/被呼叫者已儲存暫存器
x64 ABI 會考慮暫存器 RAX、RCX、RDX、R8、R9、R10、R11 和 XMM0-XMM5 揮發性。 當存在時,YMM0-YMM15 和 ZMM0-ZMM15 的上半部也會變動。 在AVX512VL上,ZMM、YMM 和 XMM 暫存器 16-31 也會變動。 當 AMX 支援存在時,TMM 磚暫存器會變動。 請考慮在函式呼叫上終結的揮發性暫存器,除非透過分析來提供安全性,例如整個程式優化。
x64 ABI 會考慮註冊 RBX、RBP、RDI、RSI、RSP、R12、R13、R14、R15 和 XMM6-XMM15 非揮發性。 它們必須由使用它們的函式儲存和還原。
函式指標
函式指標只是個別函式標籤的指標。 函式指標沒有目錄 (TOC) 需求。
舊版程式碼的浮點支援
MMX 和浮點堆疊暫存器 (MM0-MM7/ST0-ST7) 會跨內容交換器保留。 這些暫存器沒有明確的呼叫慣例。 在核心模式程式碼中,絕對禁止使用這些暫存器。
FPCSR
暫存器狀態也包含 x87 FPU 控制字組。 呼叫慣例會指定此暫存器為非揮發性。
x87 FPU 控制項字暫存器會在程式執行開始時使用下列標準值來設定:
Register[bits] | 設定 |
---|---|
FPCSR[0:6] | 例外狀況會遮罩所有 1 的 (所有已遮罩的例外狀況) |
FPCSR[7] | 保留 - 0 |
FPCSR[8:9] | 有效位數控制 - 10B (雙精確度) |
FPCSR[10:11] | 四捨五入控制項 - 0 (四捨五入至最接近) |
FPCSR[12] | 無限控制項 - 0 (未使用) |
修改 FPCSR 內任何欄位的被呼叫者,必須先還原這些欄位,才能返回其呼叫端。 此外,已修改上述任何欄位的呼叫端,必須在叫用被呼叫者之前將其還原為標準值,除非經協定,被呼叫者預期已修改的值。
控制項旗標非波動性的規則有兩個例外:
在指定函式記載目的為修改非揮發性 FPCSR 旗標的函式中。
當違反這些規則的行為與不違反規則的程式相同時,可以證明其正確性,例如,透過整個程式分析。
MXCSR
暫存器狀態也包含 MXCSR。 呼叫慣例會將這個暫存器分成揮發性部分和非揮發性部分。 揮發性部分由 MXCSR[0:5] 中的六個狀態旗標所組成,而其餘的暫存器 MXCSR[6:15]則視為非揮發性。
非volatile 部分會在程式執行開始時設定為下列標準值:
Register[bits] | 設定 |
---|---|
MXCSR[6] | 反正規數為零 - 0 |
MXCSR[7:12] | 例外狀況會遮罩所有 1 的 (所有已遮罩的例外狀況) |
MXCSR[13:14] | 四捨五入控制項 - 0 (四捨五入至最接近) |
MXCSR[15] | 針對遮罩的下溢排排排到零 - 0 (關閉) |
修改 MXCSR 內任何非volatiatile 欄位的被呼叫者,必須先還原這些欄位,才能返回其呼叫端。 此外,已修改上述任何欄位的呼叫端,必須在叫用被呼叫者之前將其還原為標準值,除非經協定,被呼叫者預期已修改的值。
控制項旗標非波動性的規則有兩個例外:
在指定函式記載目的為修改非volatile MXCSR 旗標的函式中。
當違反這些規則的行為與不違反規則的程式相同時,可以證明其正確性,例如,透過整個程式分析。
除非函式檔明確描述它,否則請勿假設 MXCSR 暫存器跨函式界限的揮發性部分狀態。
setjmp/longjmp
當您包含 setjmpex.h 或 setjmp.h 時,所有呼叫 setjmp
或 longjmp
都會導致叫用解構函式和 __finally
呼叫的回溯。 此行為與 x86 不同,其中包括 setjmp.h 會導致 __finally
子句和解構函式未叫用。
的呼叫會 setjmp
保留目前的堆疊指標、非揮發性暫存器,以及 MXCSR 暫存器。 呼叫 以 longjmp
返回最近的 setjmp
呼叫月臺,並重設堆疊指標、非揮發性暫存器和 MXCSR 暫存器,回到最近 setjmp
呼叫所保留的狀態。