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 呼叫慣例會將前四個引數傳遞至暫存器中的函式。 用於這些引數的暫存器取決於引數的位置和類型。 其餘的引數會依由右至左的順序推入堆疊。

最左邊四個位置的整數值引數分別以 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 時,所有呼叫 setjmplongjmp 都會導致叫用解構函式和 __finally 呼叫的回溯。 此行為與 x86 不同,其中包括 setjmp.h 會導致 __finally 子句和解構函式未叫用。

的呼叫會 setjmp 保留目前的堆疊指標、非揮發性暫存器,以及 MXCSR 暫存器。 呼叫 以 longjmp 返回最近的 setjmp 呼叫月臺,並重設堆疊指標、非揮發性暫存器和 MXCSR 暫存器,回到最近 setjmp 呼叫所保留的狀態。

另請參閱

x64 軟體慣例