本節描述一個函式(呼叫端)用來在 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 呼叫慣例會將前四個自變數傳遞至緩存器中的函式。 用於這些自變數的緩存器取決於自變數的位置和類型。 其餘的參數會依由右至左的順序推入堆疊。 在堆疊上傳遞的所有參數都是8位元對齊。
最左邊四個位置的整數值自變數分別以 RCX、RDX、R8 和 R9 的從左至右順序傳遞。 第五個及更高的參數會如先前所述,在堆疊上傳遞。 緩存器中的所有整數自變數都是靠右對齊的,因此被呼叫者可以忽略緩存器上層位,並只存取必要的緩存器部分。
前四個參數中的任何浮點和雙精確度自變數會根據位置傳入 XMM0 - XMM3。 當有 varargs 自變數時,浮點值只會放在整數緩存器 RCX、RDX、R8 和 R9 中。 如需詳細資訊,請參閱 Varargs。 同樣地,當對應的引數為整數或指標類型時,會忽略 XMM0 - XMM3 暫存器。
__m128
類型、陣列和字串絕不會以即時值傳遞。 而是將指標傳遞至呼叫端所分配的記憶體。 大小為 8、16、32 或 64 位的結構和聯合以及 __m64
類型的傳遞方式,就像是相同位元大小的整數。 其他大小的結構或聯合體會以指向由呼叫端分配的記憶體的指標傳遞。 對於作為指標傳遞的這些匯總類型,包括 __m128
,呼叫端配置的暫存記憶體必須對齊 16 字節。
不配置堆疊空間並且不呼叫其他函式的內建函式,有時會使用其他易失性暫存器來傳遞額外的暫存器參數。 編譯程式與內部函數實作之間的緊密系結可達成此優化。
被呼叫者負責視需要將寄存器參數轉儲到其備份空間。
下表摘要說明如何依左側的類型和位置傳遞參數:
參數類型 | 第五及更高 | 第四 | 第三 | 秒 | 最左邊 |
---|---|---|---|---|---|
浮點數 | 堆疊 | XMM3 | XMM2 | XMM1 | XMM0 |
整數 | 堆疊 | R9 | R8 | RDX | RCX |
匯總 (8、16、32 或 64 位) 和 __m64 |
堆疊 | R9 | R8 | RDX | RCX |
其他匯總,作為指標 | 堆疊 | 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
傳遞引數範例二 - 全部為浮點數
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 - 混合整數和浮點數
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 控制字寄存器會在程式執行開始時使用下列標準值來設定:
註冊[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 部分會在程式執行開始時設定為下列標準值:
暫存器[位元] | 設定 |
---|---|
MXCSR[6] | 反正規數為零 - 0 |
MXCSR[7:12] | 例外狀況會遮罩所有 1 的 (所有已遮罩的例外狀況) |
MXCSR[13:14] | 四捨五入控件 - 0 (四捨五入至最接近) |
MXCSR[15] | 將遮罩的下溢情況歸零 - 0 (關閉) |
被呼叫者若修改 MXCSR 內任何非揮發性欄位,必須在返回呼叫端前先還原這些欄位。 此外,已修改上述任何欄位的呼叫端,必須在叫用被呼叫者之前將其還原為標準值,除非經協定,被呼叫者預期已修改的值。
控件旗標非波動性的規則有兩個例外:
在指定函式記載目的為修改非volatile MXCSR 旗標的函式中。
當違反這些規則的行為與不違反規則的程式相同時,可以證明其正確性,例如,透過整個程式分析。
除非函式文件明確描述它,否則請勿假設 MXCSR 緩存器跨函式界限的揮發性部分狀態。
setjmp/longjmp
當您包含 setjmpex.h 或 setjmp.h 時,所有呼叫 setjmp
或 longjmp
都會觸發回溯過程,這個過程會叫用解構函式並產生 __finally
呼叫。 此行為與 x86 不同,其中引用 setjmp.h 時會導致 __finally
子句和解構函式無法被叫用。
setjmp
會保留目前的堆疊指標、非揮發性暫存器,以及 MXCSR 暫存器。 呼叫 longjmp
返回到最近的 setjmp
呼叫點,並重設堆疊指標、非揮發性暫存器和 MXCSR 暫存器,回復到最近 setjmp
呼叫所保存的狀態。