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
呼叫所保留的狀態。