Windows64がx86-64上の他のすべてのOSとは異なる呼び出し規約を使用するのはなぜですか?


110

AMDには、x86-64で使用する呼び出し規約を記述したABI仕様があります。独自のx86-64呼び出し規約があるWindowsを除いて、すべてのOSがこれに従います。どうして?

誰かがこの違いの技術的、歴史的、または政治的な理由を知っていますか、それとも純粋にNIH症候群の問題ですか?

OSによっては、より高いレベルのものに対するニーズが異なる可能性があることは理解していますが、たとえば、Windowsでのレジスタパラメータの受け渡し順序rcx - rdx - r8 - r9 - rest on stackが他の誰もが使用してrdi - rsi - rdx - rcx - r8 - r9 - rest on stackいる理由を説明していません。

PS私はこれらの呼び出し規約が一般的にどのように異なるを知っており、必要に応じて詳細の場所を知っています。私が知りたいのはその理由です。

編集:方法については、たとえばwikipediaのエントリとそこからのリンクを参照してください。


2
さて、最初のレジスタについてのみ:rcx:ecxは、msvc __thiscall x86規則の「this」パラメーターでした。そのため、おそらくコンパイラをx64に移植しやすくするために、最初はrcxから始めました。それから他のすべても異なるということは、その最初の決定の結果にすぎませんでした。
Chris Becke

@Chris:以下に、AMD64 ABI補足文書(および実際の説明)への参照を追加しました。
FrankH。

1
MSからの理論的根拠は見つかりませんでしたが、ここで
phuclv 14年

回答:


81

x64で4つの引数レジスタを選択する-UN * X / Win64に共通

x86について覚えておくべきことの1つは、「reg番号」エンコーディングへのレジスタ名が明確でないことです。命令のエンコーディング(MOD R / Mバイト、http: //www.c-jump.com/CIS77/CPU/x86/X77_0060_mod_reg_r_m_byte.htmを参照)の観点から、レジスタ番号0 ... 7は、この順序で- ?AX?CX?DX?BX?SP?BP?SI?DI

したがって、戻り値にA / C / D(regs 0..2)を選択し、最初の2つの引数(「クラシック」32ビット__fastcall規約)を選択するのは論理的な選択です。64ビットへの移行に関する限り、「上位」のレジストリが注文され、MicrosoftとUN * X / Linuxの両方が最初のR8/ に移行しましたR9

心の中でそれを維持する、Microsoftの選択RAX(戻り値)とはRCXRDXR8R9(引数[0..3])あなたが選択した場合に理解選択されている4つの引数のレジスタを。

AMD64 UN * X ABIがRDX以前に選択した理由がわかりませんRCX

x64での6つの引数レジスタの選択-UN * X固有

UN * Xは、RISCアーキテクチャ上で、レジスタを渡す引数を伝統的に実行してきました。具体的には、最初の6つの引数に対して(少なくともPPC、SPARC、MIPSはそうです)。これは、AMD64(UN * X)ABI設計者がそのアーキテクチャでも6つのレジスタを使用することを選択した主な理由の1つかもしれません。

あなたが望むのであれば6個のレジスタがで引数を渡すために、それが選択した論理だRCXRDXR8およびR9それらの4のために、他のどの2あなたは選ぶべきですか?

"より高い" regは、それらを選択するために追加の命令プレフィックスバイトを必要とするため、より大きな命令サイズのフットプリントを持っているので、オプションがある場合はそれらを選択したくないでしょう。従来のレジスターの暗黙的な意味のためRBPRSPこれらは使用できません。また、RBX伝統的にUN * X(グローバルオフセットテーブル)で特別な用途があり、AMD64 ABI設計者は不必要に非互換にしたくなかったようです。
エルゴ、唯一の選択肢RSI/ RDIでした。

RSI/ RDIを引数レジスタとして使用する必要がある場合、どの引数を使用する必要がありますか?

それらarg[0]を作るとarg[1]いくつかの利点があります。cHaoのコメントを参照してください。
?SIおよび?DI文字列命令のソース/デスティネーションオペランドであり、cHaoが述べたように、引数レジスタとしての使用は、AMD64 UN * X呼び出し規約ではstrcpy()、たとえば、repz movsb; retソース/ターゲットが2つのCPU命令のみで構成されているため、呼び出し元によってアドレスが正しいレジスターに入れられました。特に、低レベルでコンパイラーによって生成された「接着剤」コード(たとえば、構築時に一部のC ++ヒープアロケーターがゼロで埋められるオブジェクト、またはカーネルでゼロで埋められるヒープページを考える)sbrk()、またはcopy-on-write pagefaults)膨大な量のブロックのコピー/フィルのため、2つまたは3つのCPU命令を保存するために頻繁に使用されるコードに役立ちます。 「正しい」レジスタ。

そうな方法で、UN * XおよびWin64のはそのUN * X「プリペンド」2つの引数、意図的に選ばれた中で唯一異なっているRSI/ RDIレジスタ、の4つの引数の自然な選択にRCXRDXR8R9

それ以上 ...

UN * XとWindows x64 ABIの違いは、特定のレジスターへの引数のマッピングだけではありません。Win64の概要については、以下を確認してください。

http://msdn.microsoft.com/en-us/library/7kcdt6fy.aspx

Win64とAMD64 UN * Xは、スタックスペースの使用方法も著しく異なります。たとえば、Win64では、args 0 ... 3がレジスターで渡されても、呼び出し元関数の引数にスタックスペースを割り当てる必要があります。一方、UN * Xでは、128バイト以下が必要な場合は、リーフ関数(つまり、他の関数を呼び出さない関数)でスタックスペースを割り当てる必要さえありません(所有し、使用できます)カーネルコードでない限り、割り当てを行わずに一定量のスタックを作成します(気の利いたバグのソース)。これらはすべて特定の最適化の選択です。それらの理論的根拠のほとんどは、元の投稿者のウィキペディアの参照が参照している完全なABIの参照で説明されています。


1
レジスタ名について:そのプレフィックスバイトが要因になる場合があります。ただし、MSが引数レジスタとしてrcx-rdx-rdi-rsiを選択する方がより論理的です。ただし、最初から8つの数値は、ABIを最初から設計する場合に役立ちますが、完全に細かいABIがすでに存在する場合に変更する必要はなく、混乱を招くだけです。
JanKanis

2
RSI / RDIの場合:これらの命令は通常インライン化されます。この場合、呼び出し規約は重要ではありません。それ以外の場合、システム全体でその関数のコピーは1つ(または数個)しかないため、合計でわずか1バイトの節約になるだけです。それだけの価値はありません。その他の違い/コールスタック:特定の選択肢の有用性はABIリファレンスで説明されていますが、比較は行われません。他の最適化が選択されなかった理由はわかりません。たとえば、Windowsに128バイトのレッドゾーンがなく、AMD ABIに引数用の追加のスタックスロットがないのはなぜですか。
JanKanis

1
@cHao:いいえ。しかし、彼らはとにかくそれを変えました。Win64 ABIはWin32とは異なり(互換性もありません)、AMDのABIとも異なります。
JanKanis

7
@Somejan:Win64とWin32 __fastcallは、32ビット以下の引数が2つ以下で、32ビット以下の値を返す場合、100%同一です。それは関数の小さなクラスではありません。UN * X ABI for i386 / amd64間では、このような下位互換性はまったくありません。
FrankH。

2
@szx:2000年11月に関連するメーリングリストのスレッドを見つけ、その理由を要約した回答を投稿しました。それはmemcpy、そうではなく、そのように実装できることに注意してくださいstrcpy
Peter Cordes 2016

42

IDKがWindowsが何をしたのか。推測については、この回答の最後を参照してください。私はSysV呼び出し規約がどのように決定されているのか知りたくて、メーリングリストのアーカイブを調べたところ、きちんとしたものが見つかりました。

AMDアーキテクトが積極的に活動しているため、AMD64メーリングリストにある古いスレッドのいくつかを読むのは興味深いことです。たとえば、レジ​​スタ名の選択は難しい部分の1つでした。AMDは、元の8UAXつのレジスタの名前をr0〜r7に変更するか、新しいレジスタをのように呼び出すことを検討しました。

また、カーネルからのフィードバックは、元の設計作ら識別物事開発者syscallswapgs使用不可能に。これが、AMDが実際のチップをリリースする前にこれを整理するための指示更新した方法です。2000年後半にIntelがおそらくAMD64を採用しないだろうという想定があったことも興味深いです。


SysV(Linux)呼び出し規約、および呼び出し先保存と呼び出し元保存のどちらのレジスタをいくつにするかの決定は、Jan Hubicka(gcc開発者)によって2000年11月に最初に行われました。彼はSPEC2000コンパイルし、コードサイズと命令数を調べました。そのディスカッションスレッドは、このSOの質問に対する回答やコメントと同じアイデアのいくつかに飛びつきます。2番目のスレッドで、彼は現在のシーケンスを最適で、できれば最終的なものとして提案し、いくつかの代替案よりも小さいコードを生成しました

彼は「グローバル」という用語を使用して、使用される場合はプッシュ/ポップする必要があるコール保存レジスタを意味しています。

選択rdirsirdx最初の3つの引数がが動機れたよう:

  • memsetargs を呼び出す関数やその他のC文字列関数でのマイナーなコードサイズの節約(gccはrep文字列操作をインライン化しますか?)
  • rbxREXプレフィックス(rbxおよびrbp)なしでアクセス可能な2つのコール保存されたregを持つことは有利です。おそらく他の命令で暗黙的に使用されていない他の唯一のregだからです。(rep文字列、シフトカウント、およびmul / div出力/入力は他のすべてに影響します)。
  • 特別な目的を持つレジスターは呼び出し保存されません(前のポイントを参照)。そのため、rep文字列命令または変数カウントシフトを使用する関数は、関数の引数を別の場所に移動する必要があるかもしれませんが、保存する必要はありません/呼び出し元の値を復元します。
  • RCXは、EAXなどの特別な目的で一般的に使用されるレジスターであるため、シーケンスの早い段階でRCXを回避しようとしています。これは、シーケンスで欠落する同じ目的を持っています。また、syscallsには​​使用できません。syscallシーケンスを、関数呼び出しシーケンスと可能な限り一致させる必要があります。

    (背景:syscall/ (with )および(with )をsysret不可避的に破壊するため、カーネルは実行時に元々含まれていたものを認識できません。)rcxripr11RFLAGSrcxsyscall

カーネルのシステムコールABIは、関数呼び出しABIと一致する以外のために選ばれたr10代わりにrcx、libcのラッパー関数のようなので、mmap(2)できるだけmov %rcx, %r10/ mov $0x9, %eax/ syscall


Linuxのi386で使用されているSysV呼び出し規約は、Windowsの32ビット__vectorcallに比べて劣っています。 スタック上のすべてedx:eaxを渡し、小さな構造体ではなく、int64に対してのみ戻ります。それとの互換性を維持するために少しの努力が払われたのは当然のことです。理由がない場合rbxは、元の8に別のもの(REXプレフィックスを必要としない)を使用するのが良いと判断したため、通話を維持するなどの作業を行いました。

ABIの最適を作ることである多くの他の対価より長期より重要。彼らはかなりいい仕事をしたと思う。異なるregの異なるフィールドではなく、レジスタにパックされた構造体を返すことについて完全に確信はありません。実際にフィールドを操作せずに値でそれらを渡すコードはこの方法で勝つと思いますが、アンパックの余分な作業はばかげているようです。それらは単なる以外にも、より多くの整数戻りレジスタを持つことができたrdx:raxので、4つのメンバーを持つ構造体を返すと、rdi、rsi、rdx、raxなどでそれらを返すことができました。

SSE2は整数を操作できるため、ベクトルregに整数を渡すことを検討しました。幸いにも彼らはそれをしませんでした。 整数はポインタオフセットとして頻繁に使用され、スタックメモリへのラウンドトリップはかなり安価です。また、SSE2命令は整数命令よりも多くのコードバイトを必要とします。


Windows ABI設計者は、asmを一方から他方に移植しなければならない、または#ifdefASMでカップルを使用して同じソースをより簡単に構築できるようにするために、32ビットと64ビットの違いを最小限に抑えることを目指していたのではないかと思います。関数の32ビットバージョンまたは64ビットバージョン。

ツールチェーンの変更を最小限に抑えることはほとんどありません。x86-64コンパイラーには、何のためにどのレジスターを使用するか、および呼び出し規則が何であるかについての別個のテーブルが必要です。32ビットとの小さなオーバーラップがあっても、ツールチェーンのコードサイズ/複雑さを大幅に節約することはできません。


1
MS側からベンチマークを行った後、これらのレジスタを選択する根拠について、Raymond Chenのブログのどこかで読んだと思いますが、もう見つかりません。しかしhomezoneに関するいくつかの理由が、ここで説明したblogs.msdn.microsoft.com/oldnewthing/20160623-00/?p=93735 blogs.msdn.microsoft.com/freik/2006/03/06/...を
phuclv


@phuclv:参照ESPの下に書き込むことは有効ですか?。そこでのレイモンドの私のコメントに対するコメントは、私が知らなかったいくつかのSEHの詳細を指摘しました。これは、x86 32/64 Windowsに現在、事実上のレッドゾーンがない理由を説明しています。彼のブログ投稿には、その回答で述べたのと同じコードページインハンドラの可能性について、いくつかのもっともらしいケースがあります:)そうです、そうです、レイモンドは私よりもそれを説明するのにより優れた仕事をしました(当然のことながら、私はWindowsについてほとんど知らなかったためです)。また、x86以外のレッドゾーンサイズの表は非常にきれいです。
Peter Cordes

13

Microsoftは、IA64アーキテクチャー上のIntelとの強力なパートナーであったため、当初は「初期のAMD64への取り組みに非公式に取り組んでいた」(Matthew KernerおよびNeil Padgettによる「A History of Modern 64-bit Computing」から)ことを覚えておいてください。これは、彼らがそうでなければUnixとWindowsの両方で使用するためにABIでGCCエンジニアと協力することにオープンであったとしても、彼らが持っていなかったときにAMD64の取り組みを公にサポートすることを意味するので、そうしなかったであろうことを意味すると思いますまだ公式にそうしている(そしておそらくIntelを混乱させるだろう)。

その上、当時、マイクロソフトはオープンソースプロジェクトに友好的であることにまったく傾倒していませんでした。確かにLinuxやGCCではありません。

では、なぜ彼らはABIに協力したのでしょうか。ABIは、ほぼ同時に分離して設計されたというだけの理由で異なると思います。

「現代の64ビットコンピューティングの歴史」からの別の引用:

AMDは、マイクロソフトとのコラボレーションと並行して、チップの準備をするためにオープンソースコミュニティにも関与しました。AMDは、ツールチェーンの作業について、Code SorceryとSuSEの両方と契約しました(Red Hatは、すでにIA64ツールチェーンポートでIntelと契約していた)。ラッセルは、SuSEがCおよびFORTRANコンパイラを作成し、Code SorceryがPascalコンパイラを作成したと説明しました。ウェーバー氏はまた、同社はLinuxコミュニティと協力してLinuxへの移植を準備していると説明した。この取り組みは非常に重要でした。これは、AMD64 Windowsへの取り組みに引き続き投資するインセンティブとして機能し、チップがリリースされると、当時重要なOSであったLinuxが確実に利用可能になることも保証しました。

ウェーバー氏は、AMD64が成功するためには、Linuxの取り組みが絶対に不可欠であると述べています。これにより、AMDは、必要に応じて他の企業の助けを借りずにエンドツーエンドシステムを製造できるようになりました。この可能性により、他のパートナーが撤退したとしても、AMDが最悪の場合の生存戦略を立てることが保証されました。

これは、AMDでさえ、MSとUnixの間の協力が必ずしも最も重要であるとは感じていなかったが、Unix / Linuxをサポートすることが非常に重要であると感じたことを示しています。どちらか一方または両方に妥協または協力するよう説得しようとしても、どちらかを刺激する努力やリスク(?)に値するものではありませんでしたか?おそらくAMDは、一般的なABIを提案することでさえ、チップの準備ができたときにソフトウェアサポートを準備するというより重要な目的を遅らせたり、脱線させたりする可能性があると考えていました。

私の推測はしますが、ABIが異なる主な理由は、MSとUnix / Linuxの側が一緒に機能しなかったという政治的な理由であり、AMDはそれを問題と見なしていませんでした。


政治についての素晴らしい見方。AMDの責任でも責任でもないことに同意します。マイクロソフトが悪い呼び出し規約を選択したことを非難します。彼らの呼び出し規約の方が優れていることが判明した場合、私は同情しましたが、スタック__vectorcallを渡すのが面倒だったため、彼らは最初のABIからに変更する必要がありました__m128。一部のベクトルregの128bの低さで呼び出しが維持されるセマンティクスを持つことも奇妙です(元々はSSEで拡張可能な保存/復元メカニズムを設計しておらず、AVXではまだ設計していないというIntelの責任)
Peter Cordes

1
ABIがどれほど優れているかについての専門知識や知識は本当にありません。アセンブリレベルで理解/デバッグできるように、たまにそれらが何であるかを知る必要があります。
Michael Burr

1
優れたABIは、コードサイズと命令数を最小限に抑え、メモリを介した余分なラウンドトリップを回避することにより、依存関係チェーンを低遅延に保ちます。(引数、またはスピル/リロードが必要なローカルの場合)。トレードオフがあります。SysVのレッドゾーンは、いくつかの追加の命令を1か所(カーネルのシグナルハンドラーディスパッチャー)で取得するため、スタックポインターを調整してスクラッチスペースを取得する必要がないというリーフ関数の比較的大きなメリットがあります。つまり、マイナス面がほぼゼロの明らかな勝利です。それはSysVに提案された後ほとんど議論なしで採用されました。
Peter Cordes、2016

1
@dgnuff:そうです、それがカーネルコードがRed Zoneを使用できない理由の答えです。割り込みは、CPUがユーザー空間コードを実行しているときに到着した場合でも、ユーザー空間スタックではなくカーネルスタックを使用します。カーネルはユーザー空間スタックを信頼しません。同じユーザー空間プロセス内の別のスレッドがそれを変更して、カーネルの制御を引き継ぐ可能性があるためです。
Peter Cordes

1
@ DavidA.Gray:ええ、ABIはRBPをフレームポインターとして使用する必要があると言っていないので、最適化されたコードは通常は使用しません(使用する関数allocaまたは他のいくつかのケースを除く)。gcc -fomit-frame-pointerLinuxのデフォルトに慣れている場合、これは正常です。ABIは、例外処理を引き続き機能させるスタック巻き戻しメタデータを定義します。(私はそれがGNU / Linux x86-64 System VのCFIのものと同様に機能すると思います.eh_frame)。 gcc -fomit-frame-pointerx86-64ではずっと(最適化が有効になっている)デフォルトであり、他のコンパイラ(MSVCなど)も同じことを行います。
Peter Cordes

12

Win32にはESIとEDIの独自の用途があり、変更しない(または少なくともAPIを呼び出す前に復元する)必要があります。64ビットのコードがRSIとRDIで同じことをするのではないかと思います。RSIとRDIは、関数の引数を渡すために使用されない理由を説明しています。

しかし、なぜRCXとRDXが入れ替わったのかはわかりません。


1
すべての呼び出し規約には、スクラッチとして指定されたレジスタと、Win64上のESI / EDIおよびRSI / RDIのように保存されたレジスタがあります。しかし、これらは汎用レジスターであり、Microsoftはそれらを別の方法で使用することを問題なく選択できたはずです。
JanKanis

1
@Somejan:もちろん、API全体を書き換えて2つの異なるOSを使用したい場合は、私はそれを「問題なし」とは呼びません。今から数十年の間、MSはx86レジスターで何ができるか、何ができないかについて一定の約束をしてきました。そして、それらは多かれ少なかれ一貫性があり、ずっと互換性があります。AMDからの指示、特に非常に恣意的で「プロセッサの構築」の領域外にあるものがあるからといって、それらをすべて放り出すつもりはありません。
cHao

5
@Somejan:AMD64 UN * X ABIは常にまさにその通りでした-UNIX 固有の部分です。ドキュメントx86-64.org/documentation/abi.pdfのタイトルは、理由により、「System V Application Binary Interface、AMD64 Architecture Processor Supplement」です。(共通)UNIXのABI(マルチボリュームコレクション、sco.com/developers/devspecsは、 - )プロセッサ固有の第3章セクション残す補足 -特定のプロセッサのための規則とデータ・レイアウト・ルールを呼び出す関数です。
FrankH。

7
@Somejan:Microsoft Windowsは、特にUN * Xに近づこうと試みたことはなく、Windowsをx64 / AMD64に移植することになると、彼らは単に独自の __fastcall呼び出し規約を拡張することを選択しました。Win32 / Win64は互換性がないと主張しているが、注意深く見てください。2つの 32ビット引数を取り、32ビットを返す関数の場合、Win64とWin32は__fastcall実際 100%互換です(2つの32ビット引数を渡すための同じreg、同じ戻り値)。一部のバイナリ(!)コードでも、両方の動作モードで動作する場合があります。UNIX側は「古い方法」で完全に破られました。正当な理由がありますが、休憩は休憩です。
FrankH。

2
@Olof:それは単なるコンパイラーのものではありません。NASMでスタンドアロンのものを実行すると、ESIとEDIで問題が発生しました。Windowsはこれらのレジスターを確実に考慮します。しかし、そうです。実行前に保存して、Windowsで必要になる前に復元すれば使用できます。
cHao '25年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.