AMD memiliki spesifikasi ABI yang menjelaskan konvensi pemanggilan untuk digunakan pada x86-64. Semua OS mengikutinya, kecuali untuk Windows yang memiliki konvensi pemanggilan x86-64 sendiri. Mengapa?
Adakah yang tahu alasan teknis, sejarah, atau politik untuk perbedaan ini, atau apakah ini murni masalah NIHsyndrome?
Saya memahami bahwa OS yang berbeda mungkin memiliki kebutuhan yang berbeda untuk hal-hal tingkat yang lebih tinggi, tetapi itu tidak menjelaskan mengapa misalnya parameter register yang meneruskan urutan pada Windows adalah rcx - rdx - r8 - r9 - rest on stack
sementara orang lain menggunakan rdi - rsi - rdx - rcx - r8 - r9 - rest on stack
.
NB Saya mengetahui bagaimana perbedaan konvensi panggilan ini secara umum dan saya tahu di mana menemukan detailnya jika perlu. Yang ingin saya ketahui adalah mengapa .
Edit: untuk caranya, lihat misalnya entri wikipedia dan link dari sana.
sumber
Jawaban:
Memilih empat register argumen pada x64 - umum untuk UN * X / Win64
Salah satu hal yang perlu diingat tentang x86 adalah bahwa nama register untuk pengkodean "reg number" tidak jelas; dalam hal pengkodean instruksi ( MOD R / M byte, lihat http://www.c-jump.com/CIS77/CPU/x86/X77_0060_mod_reg_r_m_byte.htm ), nomor register 0 ... 7 adalah - dalam urutan itu -
?AX
,?CX
,?DX
,?BX
,?SP
,?BP
,?SI
,?DI
.Oleh karena itu memilih A / C / D (regs 0..2) untuk nilai kembalian dan dua argumen pertama (yang merupakan
__fastcall
konvensi 32bit "klasik" ) adalah pilihan yang logis. Sejauh menyangkut 64bit, regs "lebih tinggi" dipesan, dan baik Microsoft dan UN * X / Linux menggunakanR8
/R9
sebagai yang pertama.Menjaga bahwa dalam pikiran, pilihan Microsoft
RAX
(nilai kembali) danRCX
,RDX
,R8
,R9
(arg [0..3]) adalah pilihan yang dimengerti jika Anda memilih empat register untuk argumen.Saya tidak tahu mengapa memilih AMD64 UN * X ABI
RDX
sebelumnyaRCX
.Memilih enam register argumen pada x64 - spesifik UN * X
UN * X, pada arsitektur RISC, secara tradisional melakukan penyampaian argumen dalam register - khususnya, untuk enam argumen pertama (demikian pula pada PPC, SPARC, MIPS setidaknya). Yang mungkin menjadi salah satu alasan utama mengapa desainer ABI AMD64 (UN * X) memilih untuk menggunakan enam register pada arsitektur itu juga.
Jadi jika Anda ingin enam register untuk melewati argumen di, dan itu logis untuk memilih
RCX
,RDX
,R8
danR9
empat dari mereka, yang dua lainnya harus Anda pilih?Regs "lebih tinggi" memerlukan byte awalan instruksi tambahan untuk memilihnya dan karena itu memiliki footprint ukuran instruksi yang lebih besar, jadi Anda tidak ingin memilih salah satu dari itu jika Anda memiliki opsi. Dari register klasik, karena arti implisit
RBP
danRSP
ini tidak tersedia, dan secaraRBX
tradisional memiliki penggunaan khusus pada UN * X (tabel offset global) yang tampaknya tidak diinginkan oleh desainer AMD64 ABI untuk menjadi tidak kompatibel.Ergo, satu - satunya pilihan adalah
RSI
/RDI
.Jadi jika Anda harus mengambil
RSI
/RDI
sebagai register argumen, argumen mana yang seharusnya?Membuatnya
arg[0]
danarg[1]
memiliki beberapa keuntungan. Lihat komentar cHao.?SI
dan?DI
merupakan operan sumber / tujuan instruksi string, dan seperti yang disebutkan cHao, penggunaannya sebagai register argumen berarti bahwa dengan konvensi pemanggilan AMD64 UN * X, fungsi yang paling sederhanastrcpy()
, misalnya, hanya terdiri dari dua instruksi CPUrepz movsb; ret
karena sumber / target alamat telah dimasukkan ke register yang benar oleh pemanggil. Ada, khususnya dalam kode "perekat" tingkat rendah dan yang dihasilkan compiler (pikirkan, misalnya, beberapa pengalokasi heap C ++ objek pengisian nol pada konstruksi, atau halaman heap pengisian-nol kernel padasbrk()
, atau copy-on-write pagefaults) sejumlah besar salinan / isi blok, oleh karena itu akan berguna untuk kode yang sering digunakan untuk menyimpan dua atau tiga instruksi CPU yang sebaliknya memuat argumen alamat sumber / target ke dalam register yang "benar".Jadi dengan cara, PBB * X dan Win64 hanya berbeda dalam bahwa PBB * X "prepends" dua argumen tambahan, di sengaja dipilih
RSI
/RDI
register, dengan pilihan alami dari empat argumen diRCX
,RDX
,R8
danR9
.Lebih dari itu ...
Ada lebih banyak perbedaan antara UN * X dan Windows x64 ABI daripada hanya pemetaan argumen ke register tertentu. Untuk gambaran umum tentang Win64, periksa:
http://msdn.microsoft.com/en-us/library/7kcdt6fy.aspx
Win64 dan AMD64 UN * X juga sangat berbeda dalam cara penggunaan stackspace; di Win64, misalnya, pemanggil harus mengalokasikan stackspace untuk argumen fungsi meskipun argumen 0 ... 3 diteruskan dalam register. Di UN * X di sisi lain, fungsi daun (yaitu yang tidak memanggil fungsi lain) bahkan tidak diperlukan untuk mengalokasikan stackspace sama sekali jika dibutuhkan tidak lebih dari 128 Byte (ya, Anda memiliki dan dapat menggunakan sejumlah tumpukan tertentu tanpa mengalokasikannya ... yah, kecuali Anda adalah kode kernel, sumber bug bagus). Semua ini adalah pilihan pengoptimalan khusus, sebagian besar alasan untuk itu dijelaskan dalam referensi ABI lengkap yang dirujuk oleh referensi wikipedia poster asli.
sumber
__fastcall
100% identik jika tidak lebih dari dua argumen yang tidak lebih dari 32bit dan mengembalikan nilai tidak lebih dari 32bit. Itu bukan kelas fungsi yang kecil. Tidak ada kompatibilitas mundur seperti itu sama sekali yang mungkin antara UN * X ABI untuk i386 / amd64.memcpy
bisa diterapkan seperti itu, bukanstrcpy
.IDK mengapa Windows melakukan apa yang mereka lakukan. Lihat akhir dari jawaban ini untuk menebak. Saya ingin tahu tentang bagaimana konvensi panggilan SysV diputuskan, jadi saya menggali arsip milis dan menemukan beberapa hal yang rapi.
Menarik sekali membaca beberapa utas lama di milis AMD64, karena arsitek AMD aktif di dalamnya. misalnya Memilih nama register adalah salah satu bagian yang sulit: AMD mempertimbangkan untuk mengganti nama asli 8 register r0-r7, atau memanggil register baru seperti
UAX
.Juga, umpan balik dari pengembang kernel mengidentifikasi hal-hal yang membuat desain asli
syscall
danswapgs
tidak dapat digunakan . Begitulah cara AMD memperbarui instruksi untuk menyelesaikan ini sebelum merilis chip yang sebenarnya. Menarik juga bahwa di akhir tahun 2000, ada anggapan bahwa Intel mungkin tidak akan mengadopsi AMD64.Konvensi pemanggilan SysV (Linux), dan keputusan tentang berapa banyak register yang harus dipertahankan-callee vs. caller-save, awalnya dibuat pada November 2000, oleh Jan Hubicka (pengembang gcc). Dia mengumpulkan SPEC2000 dan melihat ukuran kode dan jumlah instruksi. Utas diskusi itu memantulkan beberapa ide yang sama dengan jawaban dan komentar atas pertanyaan SO ini. Di thread ke-2, dia mengusulkan urutan saat ini sebagai yang optimal dan semoga final, menghasilkan kode yang lebih kecil daripada beberapa alternatif .
Dia menggunakan istilah "global" yang berarti register dengan panggilan yang dipertahankan, yang harus di-push / pop jika digunakan.
Pilihan
rdi
,rsi
,rdx
sebagai tiga args pertama didorong oleh:memset
atau fungsi string C lainnya pada args mereka (di mana gcc mengaitkan operasi string rep?)rbx
dipertahankan panggilan karena memiliki dua reg yang dipertahankan panggilan dapat diakses tanpa awalan REX (rbx dan rbp) adalah kemenangan. Mungkin dipilih karena itu satu-satunya reg lain yang tidak secara implisit digunakan oleh instruksi apa pun. (string rep, hitungan shift, dan output / input mul / div menyentuh yang lainnya).(latar belakang:
syscall
/sysret
menghancurkanrcx
(denganrip
) danr11
(denganRFLAGS
), sehingga kernel tidak dapat melihat apa yang semula beradarcx
saatsyscall
dijalankan.)ABI panggilan sistem kernel dipilih untuk mencocokkan panggilan fungsi ABI, kecuali sebagai
r10
gantinyarcx
, jadi pembungkus libc berfungsi sepertimmap(2)
can justmov %rcx, %r10
/mov $0x9, %eax
/syscall
.Perhatikan bahwa konvensi pemanggilan SysV yang digunakan oleh i386 Linux payah dibandingkan dengan 32bit __vectorcall dari Window. Ini meneruskan semua yang ada di tumpukan, dan hanya mengembalikan
edx:eax
untuk int64, bukan untuk struct kecil . Tidak mengherankan jika sedikit usaha dilakukan untuk menjaga kompatibilitas dengannya. Ketika tidak ada alasan untuk tidak melakukannya, mereka melakukan hal-hal seperti menjagarbx
panggilan dipertahankan, karena mereka memutuskan bahwa memiliki yang lain di 8 asli (yang tidak memerlukan awalan REX) itu bagus.Menjadikan ABI optimal untuk jangka panjang jauh lebih penting daripada pertimbangan lainnya. Saya pikir mereka melakukan pekerjaan yang cukup baik. Saya tidak sepenuhnya yakin tentang mengembalikan struct yang dikemas ke dalam register, alih-alih bidang yang berbeda di reg yang berbeda. Saya kira kode yang menyebarkan mereka berdasarkan nilai tanpa benar-benar beroperasi di ladang menang dengan cara ini, tetapi pekerjaan ekstra untuk membongkar tampak konyol. Mereka dapat memiliki lebih banyak register pengembalian integer, lebih dari sekedar
rdx:rax
, jadi mengembalikan struct dengan 4 anggota dapat mengembalikannya dalam rdi, rsi, rdx, rax atau sesuatu.Mereka mempertimbangkan untuk melewatkan bilangan bulat dalam reg vektor, karena SSE2 dapat beroperasi pada bilangan bulat. Untungnya mereka tidak melakukan itu. Bilangan bulat sangat sering digunakan sebagai offset penunjuk, dan perjalanan pulang-pergi ke memori stack cukup murah . Juga instruksi SSE2 mengambil lebih banyak byte kode daripada instruksi integer.
Saya menduga perancang Windows ABI mungkin bertujuan untuk meminimalkan perbedaan antara 32 dan 64bit untuk kepentingan orang-orang yang harus mem-port asm dari satu ke yang lain, atau yang dapat menggunakan pasangan
#ifdef
dalam beberapa ASM sehingga sumber yang sama dapat lebih mudah dibangun versi 32 atau 64bit dari suatu fungsi.Meminimalkan perubahan dalam toolchain sepertinya tidak mungkin. Kompiler x86-64 memerlukan tabel terpisah yang registernya digunakan untuk apa, dan apa konvensi pemanggilannya. Memiliki sedikit tumpang tindih dengan 32bit tidak mungkin menghasilkan penghematan yang signifikan dalam ukuran / kompleksitas kode toolchain.
sumber
Ingatlah bahwa Microsoft pada awalnya "secara resmi tidak berkomitmen terhadap upaya awal AMD64" (dari "A History of Modern 64-bit Computing" oleh Matthew Kerner dan Neil Padgett) karena mereka adalah mitra yang kuat dengan Intel dalam arsitektur IA64. Saya pikir ini berarti bahwa meskipun mereka akan terbuka untuk bekerja dengan insinyur GCC pada ABI untuk digunakan pada Unix dan Windows, mereka tidak akan melakukannya karena itu berarti secara terbuka mendukung upaya AMD64 ketika mereka tidak melakukannya. t belum resmi melakukannya (dan mungkin akan mengecewakan Intel).
Selain itu, pada masa itu Microsoft sama sekali tidak bersahabat dengan proyek open source. Tentu bukan Linux atau GCC.
Jadi mengapa mereka bekerja sama dalam ABI? Saya rasa ABI berbeda hanya karena dirancang pada waktu yang kurang lebih sama dan dalam isolasi.
Kutipan lain dari "A History of Modern 64-bit Computing":
Ini menunjukkan bahwa bahkan AMD tidak merasa bahwa kerjasama merupakan hal yang paling penting antara MS dan Unix, tetapi memiliki dukungan Unix / Linux sangatlah penting. Mungkin bahkan mencoba meyakinkan salah satu atau kedua belah pihak untuk berkompromi atau bekerja sama tidak sebanding dengan usaha atau risiko (?) Untuk menjengkelkan salah satu dari mereka? Mungkin AMD berpikir bahwa bahkan menyarankan ABI yang umum dapat menunda atau menggagalkan tujuan yang lebih penting dari sekadar menyiapkan dukungan perangkat lunak ketika chip sudah siap.
Spekulasi di pihak saya, tetapi saya pikir alasan utama ABI berbeda adalah alasan politik bahwa MS dan pihak Unix / Linux tidak bekerja sama di dalamnya, dan AMD tidak melihatnya sebagai masalah.
sumber
__vectorcall
karena meneruskan__m128
tumpukan itu payah. Memiliki semantik yang dipertahankan panggilan untuk 128b rendah dari beberapa reg vektor juga aneh (sebagian karena kesalahan Intel karena tidak merancang mekanisme simpan / pulihkan yang dapat diperluas dengan SSE aslinya, dan masih tidak dengan AVX.)alloca
atau beberapa kasus lain). Ini normal jika Anda terbiasa menggunakangcc -fomit-frame-pointer
default di Linux. ABI mendefinisikan metadata stack-unwind yang memungkinkan penanganan pengecualian tetap berfungsi. (Saya berasumsi ini berfungsi seperti GNU / Linux x86-64 System V's CFI stuff in.eh_frame
).gcc -fomit-frame-pointer
telah menjadi default (dengan pengoptimalan diaktifkan) sejak selamanya di x86-64, dan kompiler lain (seperti MSVC) melakukan hal yang sama.Win32 memiliki kegunaannya sendiri untuk ESI dan EDI, dan mengharuskan mereka tidak dimodifikasi (atau setidaknya dipulihkan sebelum memanggil ke API). Saya membayangkan kode 64-bit melakukan hal yang sama dengan RSI dan RDI, yang akan menjelaskan mengapa mereka tidak digunakan untuk meneruskan argumen fungsi.
Saya tidak bisa memberi tahu Anda mengapa RCX dan RDX diaktifkan.
sumber
__fastcall
. Anda mengklaim Win32 / Win64 tidak kompatibel, tapi kemudian, melihat dari dekat: Untuk fungsi yang mengambil dua 32bit args dan kembali 32bit, Win64 dan Win32__fastcall
sebenarnya adalah 100% kompatibel (regs sama untuk melewati dua 32bit args, yang sama nilai kembali). Bahkan beberapa kode biner (!) Dapat bekerja di kedua mode operasi. Sisi UNIX benar-benar putus dengan "cara lama". Untuk alasan yang bagus, tapi istirahat adalah istirahat.