Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Artikel ini menjelaskan proses dan konvensi standar yang digunakan satu fungsi (pemanggil) untuk melakukan panggilan ke fungsi lain (penerima panggilan) dalam kode x64.
Untuk informasi selengkapnya tentang __vectorcall konvensi panggilan, lihat __vectorcall.
Untuk informasi selengkapnya tentang __preserve_none konvensi panggilan ini, lihat __preserve_none.
Default konvensi panggilan
Antarmuka Biner Aplikasi (ABI) x64 menggunakan konvensi panggilan cepat dengan empat register secara default. Ruang dialokasikan pada tumpukan panggilan sebagai penyimpanan sementara bagi yang dipanggil untuk menyimpan register tersebut.
Ada kesesuaian satu-ke-satu yang ketat antara argumen pada pemanggilan fungsi dan register yang digunakan untuk argumen tersebut. Argumen apa pun yang tidak muat dalam 8 byte, atau bukan 1, 2, 4, atau 8 byte, harus diteruskan melalui referensi. Satu argumen tidak pernah tersebar di beberapa register.
Tumpukan register x87 tidak digunakan. Ini mungkin digunakan oleh penerima panggilan, tetapi anggap volatil di seluruh panggilan fungsi. Semua operasi floating point dilakukan menggunakan 16 register XMM.
Argumen bilangan bulat diteruskan dalam register RCX, , RDXR8, dan R9. Argumen titik mengambang diteruskan dalam XMM0L, , XMM1LXMM2L, dan XMM3L. Argumen 16-byte diteruskan melalui referensi. Penerusan parameter dijelaskan secara rinci dalam Penerusan parameter. Register ini, dan RAX, R10, R11, XMM4, dan XMM5, dianggap volatil, atau berpotensi diubah oleh penerima panggilan saat pengembalian. Penggunaan register didokumenkan secara rinci dalam x64 register usage dan Caller/callee saved registers.
Untuk fungsi yang diprototipekan, semua argumen dikonversi ke tipe penerima yang diharapkan sebelum diteruskan. Pemanggil bertanggung jawab untuk mengalokasikan ruang untuk parameter penerima panggilan. Pemanggil harus selalu mengalokasikan ruang yang cukup untuk menyimpan empat parameter register, bahkan jika penerima panggilan tidak mengambil parameter sebanyak itu. Konvensi ini menyederhanakan dukungan untuk fungsi bahasa C yang tidak diprototi dan fungsi vararg C/C++. Untuk fungsi vararg atau tidak memiliki prototipe, nilai floating point apa pun harus diduplikasi dalam register serbaguna yang sesuai. Parameter apa pun di luar empat pertama harus disimpan di tumpukan setelah penyimpanan bayangan sebelum panggilan. Detail fungsi Vararg dapat ditemukan di Varargs. Informasi tentang fungsi yang tidak diprototipe dirinci dalam Fungsi Yang Tidak Diprototipe.
Penjajaran
Sebagian besar struktur selaras dengan keselarasan alaminya. Pengecualian utama adalah penunjuk tumpukan dan malloc atau alloca memori, yang selaras dengan 16 byte untuk membantu performa. Perataan di atas 16 byte harus dilakukan secara manual. Karena 16 byte adalah ukuran perataan umum untuk operasi XMM, nilai ini harus berfungsi untuk sebagian besar kode. Untuk informasi selengkapnya tentang tata letak dan perataan struktur, lihat jenis x64 dan tata letak penyimpanan. Untuk informasi tentang tata letak tumpukan, lihat penggunaan tumpukan x64.
Kemampuan Dibuka
Fungsi daun adalah fungsi yang tidak mengubah register nonvolatile apa pun. Fungsi non-leaf mungkin mengubah nonvolatile RSP, misalnya dengan memanggil suatu fungsi. Atau, itu bisa berubah RSP dengan mengalokasikan lebih banyak ruang tumpukan untuk variabel lokal. Untuk memulihkan register nonvolatile ketika terjadi pengecualian, fungsi nonleaf diberi anotasi dengan data statis. Data menjelaskan cara melepas fungsi dengan benar pada instruksi arbitrer. Data ini disimpan sebagai pdata, atau data prosedur, yang pada gilirannya mengacu pada xdata, data penanganan pengecualian. xdata berisi informasi pemulihan serta dapat mengarahkan ke pdata tambahan atau fungsi penangan pengecualian.
Prolog dan epilog sangat dibatasi sehingga dapat dijelaskan dengan benar dalam xdata. Penunjuk tumpukan harus tetap selaras 16 byte di wilayah kode apa pun yang bukan bagian dari epilog atau prolog, kecuali dalam fungsi daun. Fungsi ujung dapat diurai dengan mudah dengan mensimulasikan pengembalian, sehingga pdata dan xdata tidak diperlukan. Untuk detail tentang struktur prolog dan epilog fungsi yang tepat, lihat prolog x64 dan epilog. Untuk informasi selengkapnya tentang penanganan pengecualian serta penanganan dan pemulihan pdata dan xdata, lihat penanganan pengecualian x64.
Pengiriman parameter
Secara default, konvensi panggilan x64 meneruskan empat argumen pertama ke fungsi dalam register. Register yang digunakan untuk argumen ini bergantung pada posisi dan jenis argumen. Argumen yang tersisa diteruskan pada tumpukan dalam susunan dari kanan ke kiri. Pemanggil mengalokasikan ruang tumpukan yang diperlukan dan menuliskan argumen-argumen ini ke memori tumpukan menggunakan instruksi simpan atau pindah, sambil mempertahankan penjajaran 8-byte untuk setiap argumen.
Argumen bernilai bilangan bulat di empat posisi paling kiri diteruskan dalam urutan kiri-ke-kanan dalam RCX, RDX, R8, dan R9, masing-masing. Argumen kelima dan yang lebih tinggi diteruskan pada tumpukan seperti yang dijelaskan sebelumnya. Semua argumen bilangan bulat dalam register dibenarkan kanan, sehingga penerima panggilan dapat mengabaikan bit atas register dan hanya mengakses bagian dari register yang diperlukan.
Setiap argumen floating point dan presisi ganda pada empat parameter pertama dilewatkan melalui XMM0 - XMM3, bergantung pada posisinya. Nilai floating-point hanya ditempatkan di register integer RCX, RDX, R8, dan R9 jika terdapat argumen varargs. Untuk detailnya, lihat Varargs. Demikian pula, XMM0 - XMM3 register diabaikan ketika argumen yang sesuai adalah bilangan bulat atau jenis pointer.
__m128 jenis, array, dan string tidak pernah diteruskan sebagai nilai langsung. Sebagai gantinya, pointer diteruskan ke memori yang dialokasikan oleh pemanggil. Struktur dan penyatuan ukuran 8, 16, 32, atau 64 bit, dan __m64 jenis, diteruskan seolah-olah bilangan bulat dengan ukuran yang sama. Struktur atau union dengan ukuran lain diteruskan sebagai pointer ke memori yang dialokasikan oleh pemanggil. Untuk jenis agregat ini yang diteruskan sebagai pointer, termasuk __m128, memori sementara yang dialokasikan pemanggil harus sejajar dengan 16 byte.
Fungsi intrinsik yang tidak mengalokasikan ruang tumpukan, dan tidak memanggil fungsi lain, terkadang menggunakan register volatil lainnya untuk meneruskan argumen register tambahan. Pengoptimalan ini dimungkinkan dengan pengikatan ketat antara pengkompilasi dan implementasi fungsi intrinsik.
Penerima panggilan bertanggung jawab untuk menyimpan parameter register ke dalam ruang bayangan mereka jika diperlukan.
Tabel berikut ini meringkas bagaimana parameter diteruskan, berdasarkan jenis dan posisi dari kiri:
| Jenis parameter | kelima dan lebih tinggi | keempat | ketiga | detik | paling kiri |
|---|---|---|---|---|---|
| titik mengambang | stack | XMM3 |
XMM2 |
XMM1 |
XMM0 |
| Bilangan bulat | stack | R9 |
R8 |
RDX |
RCX |
Agregat (8, 16, 32, atau 64 bit) dan __m64 |
stack | R9 |
R8 |
RDX |
RCX |
| Agregat lain, sebagai penunjuk | stack | R9 |
R8 |
RDX |
RCX |
__m128, sebagai penunjuk |
stack | R9 |
R8 |
RDX |
RCX |
Contoh argumen melewati 1 - semua bilangan bulat
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
Contoh pengoperan argumen 2 - semua floats
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
Contoh penerusan argumen 3 - bilangan bulat dan bilangan desimal campuran
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
Contoh pengiriman argumen 4 - __m64, __m128, dan agregat
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
Jika parameter diteruskan melalui vararg (misalnya, argumen elipsis), maka konvensi lolos parameter register normal berlaku. Konvensi itu termasuk menumpahkan argumen kelima dan argumen berikutnya ke tumpukan. Ini adalah tanggung jawab penerima panggilan untuk mencadangkan argumen yang memiliki alamat mereka diambil. Untuk nilai floating-point saja, baik register bilangan bulat maupun register floating-point harus mengandung nilai tersebut, jika pihak pemanggil mengharapkan nilai dalam register bilangan bulat.
Fungsi yang tidak prototipe
Untuk fungsi yang tidak sepenuhnya diprototipekan, pemanggil meneruskan nilai bilangan bulat sebagai bilangan bulat dan nilai floating-point sebagai presisi ganda. Hanya untuk nilai floating-point, baik register bilangan bulat maupun register floating-point berisi nilai float jika penerima panggilan mengharapkan nilai dalam register bilangan bulat.
func1();
func2() { // RCX = 2, RDX = XMM1 = 1.0, and R8 = 7
func1(2, 1.0, 7);
}
Mengembalikan nilai
Nilai hasil pengembalian skalar yang dapat ditampung dalam 64 bit, termasuk tipe __m64, dikembalikan melalui RAX. Jenis nonskalar termasuk float, ganda, dan jenis vektor seperti __m128, __m128i, __m128d dikembalikan dalam XMM0. Keadaan bit yang tidak digunakan dalam nilai yang dikembalikan di RAX atau XMM0 tidak ditentukan.
Jenis yang ditentukan pengguna dapat dikembalikan berdasarkan nilai dari fungsi global dan fungsi anggota statis. Untuk mengembalikan jenis berdasarkan nilai yang ditentukan pengguna di RAX, harus memiliki panjang 1, 2, 4, 8, 16, 32, atau 64 bit. Ini juga tidak boleh memiliki konstruktor, destruktor, atau operator penugasan salinan yang ditentukan pengguna. Ini tidak dapat memiliki anggota data nonstatis privat atau dilindungi, dan tidak ada anggota data nonstatis dari jenis referensi. Ini tidak dapat memiliki kelas dasar atau fungsi virtual. Dan, itu hanya dapat memiliki anggota data yang juga memenuhi persyaratan ini. Definisi ini pada dasarnya sama dengan jenis POD C++03. Karena definisi telah berubah dalam standar C++11, kami tidak menyarankan penggunaan std::is_pod untuk pengujian ini. Jika tidak, pemanggil harus mengalokasikan memori untuk nilai yang dikembalikan dan meneruskan penunjuk ke sana sebagai argumen pertama. Argumen yang tersisa kemudian digeser satu argumen ke kanan. Penunjuk yang sama harus dikembalikan oleh penerima panggilan di RAX.
Contoh-contoh ini menunjukkan bagaimana parameter dan nilai pengembalian diteruskan untuk fungsi dengan deklarasi yang ditentukan:
Contoh nilai pengembalian 1 - hasil 64-bit
__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.
Contoh nilai pengembalian 2 - hasil 128-bit
__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.
Contoh nilai pengembalian 3 - hasil tipe pengguna dengan pointer
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.
Contoh nilai pengembalian 4 - tipe pengguna hasil berdasarkan nilai
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.
Register yang disimpan oleh pemanggil/penerima
x64 ABI menganggap register RAX, RCX, RDX, R8, R9, R10, R11, dan XMM0-XMM5 volatile. Jika ada, bagian atas YMM0-YMM15 dan ZMM0-ZMM15 juga bersifat volatil. Pada AVX512VL, register ZMM, YMM, dan XMM 16-31 juga bersifat volatil. Saat dukungan AMX tersedia, register tile TMM bersifat volatil. Pertimbangkan register mudah diubah yang dihancurkan pada panggilan fungsi kecuali jika dapat terbukti aman oleh analisis seperti optimasi seluruh program.
ABI x64 menganggap register RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15, dan XMM6-XMM15 sebagai nonvolatil. Mereka harus disimpan dan dipulihkan oleh fungsi yang menggunakannya.
Ketika dukungan APX ada, register R16-R29 bersifat volatil.
R30 dan R31 bersifat nonvolatile.
Pointer fungsi
Penunjuk fungsi hanyalah penunjuk ke label fungsi masing-masing. Tidak ada persyaratan daftar isi (TOC) untuk penunjuk fungsi.
Dukungan floating-point untuk kode yang lebih lama
Register MMX dan register tumpukan floating-point (MM0-MM7/ST0-ST7) dipertahankan selama perpindahan konteks. Tidak ada konvensi panggilan eksplisit untuk register ini. Penggunaan register ini sangat dilarang dalam kode mode kernel.
FPCSR
Status register juga menyertakan kata kontrol FPU x87. Konvensi pemanggilan menentukan register ini menjadi nonvolatile.
Register kata kontrol FPU x87 diatur menggunakan nilai standar berikut pada awal eksekusi program:
| Daftar[bit] | Pengaturan |
|---|---|
FPCSR\[0:6] |
Pengecualian memasker semua nilai 1 (semua pengecualian dimasker) |
FPCSR\[7] |
Dicadangkan - 0 |
FPCSR\[8:9] |
Kontrol Presisi - 10B (presisi ganda) |
FPCSR\[10:11] |
Kontrol pembulatan - 0 (pembulatan ke angka terdekat) |
FPCSR\[12] |
Kontrol tak terbatas - 0 (tidak digunakan) |
Penerima panggilan yang memodifikasi salah satu bidang di dalamnya FPCSR harus memulihkannya sebelum kembali ke pemanggilnya. Selain itu, penelepon yang telah memodifikasi salah satu bidang ini harus memulihkannya ke nilai standar mereka sebelum memanggil penerima panggilan, kecuali dengan perjanjian penerima panggilan mengharapkan nilai yang dimodifikasi.
Ada dua pengecualian untuk aturan tentang nonvolatilitas bendera kontrol:
Pada fungsi yang tujuan terdokumentasinya adalah memodifikasi flag nonvolatile
FPCSR.Ketika terbukti benar bahwa pelanggaran aturan ini menghasilkan program yang berperilaku sama dengan program yang sesuai aturan, misalnya, melalui analisis seluruh program.
Meskipun dianggap nonvolatile, tidak ada deskriptor unwind statis yang menjelaskan di mana nilai tersebut disimpan dan dari mana nilai tersebut harus dipulihkan. Kode yang tahan terhadap pengecualian dan memodifikasi FPCSR harus menggunakan finalizer untuk pengecualian (misalnya destruktor C++ atau klausa __finally) untuk memulihkannya secara eksplisit saat stack di-unwind.
MXCSR
Status register juga mencakup MXCSR. Konvensi pemanggilan membagi register ini menjadi bagian volatil dan bagian nonvolatile. Bagian volatil terdiri dari enam bendera status, di MXCSR\[0:5], sementara sisa register, MXCSR\[6:15], dianggap nonvolatile.
Bagian nonvolatile diatur ke nilai standar berikut pada awal eksekusi program:
| Daftar[bit] | Pengaturan |
|---|---|
MXCSR\[6] |
Denormal adalah nol - 0 |
MXCSR\[7:12] |
Pengecualian memasker semua nilai 1 (semua pengecualian dimasker) |
MXCSR\[13:14] |
Kontrol pembulatan - 0 (pembulatan ke angka terdekat) |
MXCSR\[15] |
Mengatur ke nol untuk pelimpahan arus tersamar - 0 (nonaktif) |
Penerima panggilan yang memodifikasi salah satu bidang nonvolatile di dalamnya MXCSR harus memulihkannya sebelum kembali ke pemanggilnya. Selain itu, penelepon yang telah memodifikasi salah satu bidang ini harus memulihkannya ke nilai standar mereka sebelum memanggil penerima panggilan, kecuali dengan perjanjian penerima panggilan mengharapkan nilai yang dimodifikasi.
Ada dua pengecualian untuk aturan tentang nonvolatilitas bendera kontrol:
Dalam fungsi yang tujuan terdokumentasi dari fungsi tersebut adalah untuk memodifikasi flag nonvolatile
MXCSR.Ketika terbukti benar bahwa pelanggaran aturan ini menghasilkan program yang berperilaku sama dengan program yang sesuai aturan, misalnya, melalui analisis seluruh program.
Jangan berasumsi tentang status bagian volatil dari register MXCSR saat melintasi batas fungsi, kecuali jika dokumentasi fungsi menjelaskannya secara eksplisit.
Meskipun sebagian dari MXCSR dianggap nonvolatile, tidak ada deskriptor unwind statis yang menjelaskan di mana bagian tersebut disimpan dan dari mana bagian tersebut harus dipulihkan. Kode yang aman terhadap pengecualian yang memodifikasi bagian nonvolatile dari MXCSR harus menggunakan finalizer untuk pengecualian (misalnya destruktor C++ atau klausa __finally) untuk memulihkannya secara eksplisit saat stack di-unwind.
setjmp/longjmp
Saat Anda menyertakan setjmpex.h atau setjmp.h, semua panggilan ke setjmp atau longjmp akan menyebabkan proses unwind yang memanggil destruktor dan panggilan __finally. Perilaku ini berbeda dari x86, di mana termasuk setjmp.h mengakibatkan klausa __finally dan destruktor tidak dipanggil.
Pemanggilan ke setjmp mempertahankan penunjuk tumpukan saat ini, register nonvolatile, dan register MXCSR. Panggilan ke longjmp kembali ke lokasi pemanggilan setjmp terbaru dan mengembalikan penunjuk tumpukan, register nonvolatile, dan register MXCSR ke keadaan yang disimpan oleh panggilan setjmp terbaru.
Jika APX didukung, R30 dan R31 tidak boleh dimodifikasi dalam fungsi dari titik setjmp dipanggil ke titik di mana panggilan yang pada akhirnya menghasilkan longjmp dilakukan. Batasan ini adalah hasil dari R30 dan R31 tidak disimpan sebagai bagian dari jmp_buf - definisi struktur ini tidak dapat berubah. Sebaliknya, mereka dipulihkan melalui unwinder. Contoh berikut menunjukkan bagaimana perbedaan tentang bagaimana data dipulihkan memengaruhi pembatasan ini:
jmp_buf jmpbuffer;
void function_a() {
...
int val = setjmp(jmpbuffer); // At this time R30 is 10
...
if (val == 0) {
function_b(); // At this time R30 is 20
}
...
}
void function_b() {
...
longjmp(jmpbuffer, 1);
...
}
Dalam contoh ini, nilai R30 berubah dari titik saat setjmp dipanggil hingga titik saat function_b dipanggil. Dalam function_b, longjmp lepaskan tumpukan hingga mencapai fungsi yang disebut setjmp (function_a dalam hal ini). Nilai yang dipulihkan untuk R30 adalah 20 (nilai pada saat function_b dipanggil), bukan 10 (nilai pada saat setjmp dipanggil). Ini berarti bahwa ketika setjmp kembali untuk kedua kalinya (sebagai hasil dari longjmp) nilai R30 akan diatur ke 20 bukan 10, yang salah. Inilah sebabnya mengapa kompilator harus memastikan R30 dan R31 tetap konstan dari titik setjmp dipanggil ke tempat terakhir dalam fungsi yang pada akhirnya dapat mengakibatkan dipanggil longjmp .
Karena longjmp dapat dipanggil dari filter pengecualian (bukan hanya sub-rutinitas), ini secara efektif menentukan bahwa R30 dan R31 harus tetap konstan dari titik setjmp dipanggil melalui sisa fungsi.