共用方式為


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 函式中。

對齊方式

大部分的結構會對齊其自然對齊方式。 主要例外狀況是堆棧指標和 mallocalloca 記憶體,其為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,做為指標 堆疊 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 passed 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 passed 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 passed 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 passed on stack, then ptr to e passed on stack

Varargs

如果參數是透過 varargs 傳遞(例如省略號自變數),則會套用一般緩存器參數傳遞慣例。 該慣例包括將第五個及之後的參數溢出至堆疊。 被呼叫者有責任輸出其地址已被使用的參數。 如果只有浮點值,整數緩存器和浮點緩存器都必須包含值,如果被呼叫者預期整數緩存器中的值。

Unprototyped 函式

對於未完全原型的函式,呼叫端會將整數值當做整數傳遞,並將浮點值當做雙精確度傳遞。 僅針對浮點值,整數暫存器和浮點暫存器都包含浮點值,以防被呼叫者預期值在整數暫存器中。

func1();
func2() {   // RCX = 2, RDX = XMM1 = 1.0, and R8 = 7
   func1(2, 1.0, 7);
}

傳回值

可放入 64 位的純量傳回值,包括 __m64 型別,會透過 RAX 傳回。 非純量型態,包括浮點數、雙數,以及向量型態如 __m128__m128i__m128d 會在 XMM0 中回傳。 對於 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 passed 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 passed 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 部分會在程式執行開始時設定為下列標準值:

註冊[bits] 設定
MXCSR[6] 反正規數為零 - 0
MXCSR[7:12] 例外會遮蔽所有 1(所有例外都被遮蔽)
MXCSR[13:14] 四捨五入控制 - 0 (四捨五入到最接近)
MXCSR[15] 將遮罩的下溢情況歸零 - 0 (關閉)

被呼叫者若修改 MXCSR 內任何非揮發性欄位,必須在返回呼叫端前先還原這些欄位。 此外,已修改上述任何欄位的呼叫端,必須在叫用被呼叫者之前將其還原為標準值,除非經協定,被呼叫者預期已修改的值。

關於控制旗標非揮發性的規則有兩個例外:

  • 在指定函式記載目的為修改非volatile MXCSR 旗標的函式中。

  • 當可以證明違反這些規則後程式的行為與未違反規則的程式相同時,例如,通過整體程式分析來證明其正確性。

除非函式文件明確描述它,否則請勿假設 MXCSR 緩存器跨函式界限的揮發性部分狀態。

setjmp/longjmp

當你包含setjmpex.hsetjmp.h時,所有對setjmplongjmp的呼叫會導致解構,並引發解構函式和__finally的呼叫。 此行為與 x86 不同,後者 包含 setjmp.h 會導致 __finally 子句與解構子未被呼叫。

呼叫到setjmp會保留目前的堆疊指標、非揮發性暫存器及 MXCSR 暫存器。 longjmp 將返回到最近的 setjmp 呼叫點,並將堆棧指標、非揮發性寄存器和 MXCSR 寄存器重置為最近一次由 setjmp 呼叫所保存的狀態。

另請參閱