122

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私は、これらの呼び出し規約が一般的にどのように異なるかを認識しており、必要に応じて詳細をどこで見つけることができるかを知っています. 私が知りたいのはその理由です。

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

4

4 に答える 4

94

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

したがって、戻り値と最初の 2 つの引数に A/C/D (regs 0..2) を__fastcall選択することは論理的な選択です (これは「古典的な」32 ビット規則です)。64ビットへの移行に関する限り、「より高い」regが注文され、MicrosoftとUN * X / Linuxの両方が最初のものとしてR8/に行きました。R9

これを念頭に置いて、引数に4 つのレジスタを選択する場合、Microsoft のRAX(戻り値) とRCX, RDX, R8, (arg[0..3]) の選択は理解できる選択です。R9

RDXAMD64 UN*X ABI が以前に選択された理由はわかりませんRCX

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

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

では、6 つのレジスタに引数を渡す必要があり、そのうちの 4 つに対してRCXRDXR8およびを選択するのが論理的R9である場合、他の 2 つを選択する必要があります。

「より高い」レジスタは、それらを選択するために追加の命令プレフィックスバイトを必要とするため、命令サイズのフットプリントが大きくなるため、オプションがある場合はそれらのいずれも選択したくないでしょう。古典的なレジスタのうち、 and の暗黙の意味により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ソース/ターゲットがアドレスは、呼び出し元によって正しいレジスターに入れられました。特に、低レベルおよびコンパイラによって生成された「接着剤」コードに存在します (たとえば、一部の C++ ヒープ アロケータは、構築時にオブジェクトをゼロで埋めたり、カーネルでヒープ ページをゼロで埋めたりします。sbrk()、またはコピー オン ライト ページフォールト) は膨大な量のブロック コピー/フィルを生成するため、そうでなければそのようなソース/ターゲット アドレス引数を「正しい」レジスタ。

したがって、ある意味では、UN*X と Win64 の違いは、UN*X が、、およびの 4 つの引数の自然な選択に、意図的に選択されたRSI/RDIレジスタで 2 つの追加の引数を「先頭に追加」することだけです。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 参照で説明されています。

于 2010-12-14T11:11:09.233 に答える
53

Windows が行ったことを IDK が行った理由。推測については、この回答の最後を参照してください。SysV の呼び出し規約がどのように決定されたのか興味があったので、メーリング リストのアーカイブを調べてみると、すばらしいものを見つけました。

AMD アーキテクトが活発に取り組んでいたので、AMD64 メーリング リストの古いスレッドのいくつかを読むのは興味深いことです。たとえば、レジ​​スタ名の選択は難しい部分の 1 つでした。AMD は、元の 8 つのレジスタ r0 ~ r7 の名前を変更するか、新しいレジスタを呼び出すUAXなどを検討しました。

また、カーネル開発者からのフィードバックにより、syscallとの元の設計がswapgs使用できなくなった原因が特定されました。これが、AMDが実際のチップをリリースする前にこれを整理するために命令を更新した方法です. また興味深いことに、2000 年後半には、Intel はおそらく AMD64 を採用しないだろうという想定がありました。


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

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

最初の 3 つの引数として , をrdi選択rsiした理由は次のとおりです。rdx

  • 引数でまたは他のC文字列関数を呼び出す関数でのマイナーコードサイズの節約memset(gccはrep文字列操作をインライン化しますか?)
  • rbxrbxREX プレフィックス (および)なしでアクセスできる 2 つの呼び出し保存された reg を持つことrbpは勝利であるため、呼び出し保存されます。おそらく、一般的な命令によって暗黙的に使用されない唯一の「レガシー」レジスタであるため、選択されたのでしょう。(rep 文字列、シフト カウント、および mul/div 出力/入力は他のすべてに影響します)。
  • 一般的な命令で強制的に使用されるレジスタはどれも呼び出し保存されないため (前のポイントを参照)、変数カウント シフトまたは除算を使用する関数は、関数の引数を別の場所に移動する必要があるかもしれませんが、移動する必要はありません。呼び出し元の値を保存/復元します。 cmpxchg16bRBXがcpuid必要ですが、めったに使用されないため、大きな要因ではありません. (cmpxchg16b元の AMD64 の一部ではありませんでしたが、RBX は依然として明白な選択でした。cmpxchg8b存在しますが、qword によって廃止されましたcmpxchg)
  • RCX は EAX のような特別な目的で一般的に使用されるレジスタであるため、RCX をシーケンスの早い段階で回避しようとしています。また、syscall には使用できないため、syscall シーケンスをできるだけ関数呼び出しシーケンスに一致させたいと考えています。

(背景: syscall/sysretやむを得ずrcx(with rip) とr11(with ) を破棄するRFLAGSため、カーネルは実行rcx時に元々あったものを認識できませんsyscall。)

カーネル システム コール ABI は、関数呼び出し ABI と一致するように選択されましr10た。rcxmmap(2)mov %rcx, %r10mov $0x9, %eaxsyscall


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

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

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


Windows ABI の設計者は、asm をあるものから別のものに移植する必要がある人や#ifdef、同じソースをより簡単にビルドできるようにいくつかの ASM でいくつかの s を使用できる人のために、32 ビットと 64 ビットの違いを最小限に抑えることを目指していたのではないかと思います。関数の 32 または 64 ビット バージョン。

ツールチェーンの変更を最小限に抑えることはありそうにありません。x86-64 コンパイラでは、どのレジスタが何に使用され、どの呼び出し規則が使用されているかを示す別のテーブルが必要です。32 ビットとのオーバーラップがわずかであっても、ツールチェーンのコード サイズや複雑さを大幅に削減できる可能性は低いです。

于 2016-02-25T06:00:07.993 に答える
14

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

RCX と RDX が入れ替わる理由はわかりません。

于 2010-12-13T14:31:08.200 に答える