32ビットレジスタのx86-64命令が完全な64ビットレジスタの上位部分をゼロにするのはなぜですか?


118

ではインテルのマニュアルのx86-64のツアー、私が読んで

おそらく最も驚くべき事実はMOV EAX, EBXRAXレジスタなどの上位32ビットを自動的にゼロにするなどの命令です。

同じ出典で引用されているIntelのドキュメント(手動の基本アーキテクチャで64ビットモードの3.4.1.1汎用レジスター)は、次のように述べています。

  • 64ビットのオペランドは、宛先の汎用レジスターで64ビットの結果を生成します。
  • 32ビットのオペランドは32ビットの結果を生成し、デスティネーションの汎用レジスターで64ビットの結果にゼロ拡張します。
  • 8ビットおよび16ビットのオペランドは、8ビットまたは16ビットの結果を生成します。デスティネーション汎用レジスタの上位56ビットまたは48ビットは、それぞれ操作によって変更されません。8ビットまたは16ビット演算の結果が64ビットのアドレス計算を目的としている場合は、明示的にレジスタを完全な64ビットに符号拡張します。

x86-32およびx86-64アセンブリでは、次のような16ビット命令

mov ax, bx

eaxの上位ワードがゼロになるこの種の「奇妙な」動作を表示しないでください。

したがって、この動作が導入された理由は何ですか?一見すると論理的に見えないようです(ただし、x86-32アセンブリの癖に慣れているためかもしれません)。


16
「部分的なレジスターストール」をググったら、彼らが(ほぼ確実に)回避しようとしていた問題に関するかなりの情報を見つけるでしょう。
Jerry Coffin 2012年


4
「ほとんど」だけではありません。AFAIK、宛先オペランドを持つすべての命令r32は、マージするのではなく、上位32をゼロにします。たとえば、一部のアセンブラはで置き換えpmovmskb r64, xmmられpmovmskb r32, xmm、REXを保存します。これは、64ビットの宛先バージョンが同じように動作するためです。マニュアルの操作」セクションでは、32/64ビットdestと64/128 / 256bソースの6つの組み合わせすべてを個別にリストしていますが、r32形式の暗黙のゼロ拡張は、r64形式の明示的なゼロ拡張と重複しています。HWの実装に興味があります...
Peter Cordes、2016年

2
@HansPassant、循環参照が始まります。
kchoi

回答:


97

私はAMDではないし、彼らの代弁者でもないが、私は同じようにそれをしただろう。上位半分をゼロにしても、以前の値に依存しないため、CPUは待機する必要があります。レジスタリネーム、それはそのように行われていなかった場合のメカニズムは基本的に敗北するだろう。

このようにして、常に明示的に依存関係を壊す必要なしに、64ビットモードで32ビット値を使用して高速コードを記述できます。この動作がなければ、64ビットモードのすべての32ビット命令は、その高い部分がほとんど使用されない場合でも、以前に発生した何かを待つ必要があります。(int64ビットを作成すると、キャッシュフットプリントとメモリ帯域幅が無駄になります。x86-64は、32ビットと64ビットのオペランドサイズを最も効率的にサポートします

8ビットと16ビットのオペランドサイズの動作は奇妙です。依存関係の狂気は、16ビット命令が現在回避されている理由の1つです。x86-64は、これを8ビットの場合は8086、16ビットの場合は386から継承し、8ビットと16ビットのレジスタを64ビットモードでも32ビットモードと同じように機能させることにしました。


参照してくださいGCCは一部のレジスタを使用していないのはなぜ?8ビットおよび16ビットのパーシャルレジスタへの書き込み(およびその後のフルレジスタの読み取り)が実際のCPUによってどのように処理されるかについての実用的な詳細については。


8
奇妙なことではないと思います。彼らはあまり壊したくなかったので、古い行動をそこに残しました。
Alexey Frunze 2012年

5
@Alexが32ビットモードを導入したとき、高い部分に古い動作はありませんでした。以前は高い部分はありませんでした。もちろん、その後は変更できませんでした。
ハロルド

1
私は16ビットのオペランドについて話していましたが、その場合、上位ビットがゼロにならないのはなぜですか。非64ビットモードでは使用できません。そして、それも64ビットモードで維持されます。
Alexey Frunze 2012年

3
私はあなたの「16ビット命令の動作は奇妙なものである」と解釈しました。したがって、互換性を高めるために64ビットモードで同じ方法を維持することについての私のコメント。
Alexey Frunze 2012年

8
@アレックスああ、なるほど。OK。そういう意味では不思議だとは思いません。「振り返ってみると、それほど良い考えではなかったかもしれません」という観点から。私はもっ​​と明確だったはずだと思います:)
ハロルド

9

命令と命令セットのスペースを節約するだけです。既存の(32ビット)命令を使用して、小さな即値を64ビットレジスタに移動できます。

またMOV RAX, 42MOV EAX, 42再利用できる場合、の8バイト値をエンコードする必要がなくなります。

この最適化は8ビットと16ビットの演算ではそれほど重要ではなく(それらが小さいため)、そこでルールを変更すると古いコードも壊れます。


7
それが正しければ、0拡張ではなく符号拡張する方が理にかなっているのではないでしょうか。
Damien_The_Unbeliever

16
符号拡張は、ハードウェアでも遅くなります。ゼロ拡張は、下半分を生成する計算と並行して実行できますが、符号拡張は、下半分が(少なくとも符号が)計算されるまで実行できません。
Jerry Coffin 2012年

13
別の関連するトリックは、REXプレフィックスが必要になるXOR EAX, EAXため使用することXOR RAX, RAXです。
Neil

3
@Nubok:もちろん、即座に引数を取るmovzx / movsxのエンコーディングを追加することもできます。それはですほとんどの時間以上(:すべてのREGSは実効アドレスで同じ大きさでなければならないので、あなたが配列のインデックスとして値を使用できるように、ゼロ上位ビットを持っていると便利[rsi + edx]許可されていません)。もちろん、別の主な理由は、誤った依存関係/部分的なレジスターのストール(他の答え)を回避することです。
Peter Cordes

4
そこにルールを変更すると、古いコードも破られてしまいます。 とにかく、古いコードは64ビットモードで実行できません(たとえば、1バイトのinc / decはREXプレフィックスです)。これは無関係です。x86のいぼをクリーンアップしない理由は、ロングモードと互換/レガシーモードの違いが少ないため、モードに応じて異なる方法でデコードする必要がある命令が少なくなるためです。AMDは、AMD64が普及することを知らず、残念ながら非常に保守的であるため、サポートするトランジスタの数が少なくなります。長期的には、コンパイラと人間が64ビットモードでどのように動作するかを覚えておかなければならないのであれば問題ありません。
Peter Cordes

1

ゼロが64ビットに拡張されていない場合、それは、読み取り命令raxがそのraxオペランドに2つの依存関係(書き込むeax命令とそのrax前に書き込む命令)を持つことを意味します。これは、1)ROBが単一のオペランドに対する複数の依存関係。つまり、ROBはより多くのロジックとトランジスタを必要とし、より多くのスペースを必要とし、実行に時間がかかる可能性のある不要な2番目の依存関係の待機が遅くなります。または2)、16ビット命令で発生すると思いますが、割り当てステージはおそらく停止します(つまり、RATにax書き込み用のアクティブな割り当てがあり、eax読み取りが表示される場合、それはax書き込みが終了するます)。

mov rdx, 1
mov rax, 6
imul rax, rdx
mov rbx, rax
mov eax, 7 //retires before add rax, 6
mov rdx, rax // has to wait for both imul rax, rdx and mov eax, 7 to finish before dispatch to the execution units, even though the higher order bits are identical anyway

ゼロ拡張しない唯一の利点は、の上位ビットが確実にrax含まれることです。たとえば、元々0​​xffffffffffffffffが含まれている場合、結果は0xffffffff00000007になりますが、ISAがそのような費用でこの保証を行う理由はほとんどありません。ゼロ拡張のメリットが実際に必要になる可能性が高くなるため、コードの余分な行を節約できますmov rax, 0。常に64ビットにゼロ拡張されることを保証することにより、コンパイラーは、この公理を念頭に置いて作業できます。一方、はmov rdx, raxrax単一の依存関係を待つだけで済みます。つまり、実行をより早く開始してリタイアし、実行ユニットを解放できます。さらに、REXバイトを必要とせずxor eax, eaxにゼロ化raxするような、より効率的なゼロイディオムも使用できます。


Skylakeの部分フラグは少なくとも、CFとSPAZOのいずれかに対して別々の入力を持つことで機能します。(つまりcmovbe、2 uopsですcmovbが1です)。しかし、部分的なレジスタの名前変更を行うCPUは、あなたが提案する方法を実行しません。代わりに、部分的なregが完全なregとは別に名前が変更された場合(つまり、「ダーティ」)、マージするuopを挿入します。GCCが部分レジスターを使用しない理由を参照してくださいそしてHaswell / Skylakeの部分レジスタはどのように正確に機能しますか?ALの記述はRAXに誤って依存しているようで、AHは一貫していません
Peter Cordes

P6ファミリーのCPUは、マージされたuop(Core2 / Nehalem)を挿入するために〜3サイクル間ストールしたか、以前のP6-ファミリー(PM、PIII、PII、PPro)が(少なくとも?)〜6サイクルだけストールした。おそらくそれは、2で提案したようなものであり、永続的/アーキテクチャレジスタファイルへのライトバックによって完全なreg値が利用可能になるのを待っています。
Peter Cordes

@PeterCordesああ、私は少なくとも部分的なフラグストールのためにuopsをマージすることを知っていました。理にかなっていますが、それが1分間どのように機能するかを忘れていました。1回クリックしましたが、メモを作成するのを忘れました
Lewis Kelsey

@PeterCordes microarchitecture.pdf:This gives a delay of 5 - 6 clocks. The reason is that a temporary register has been assigned to AL to make it independent of AH. The execution unit has to wait until the write to AL has retired before it is possible to combine the value from AL with the value of the rest of EAXこれを解決するために使用される「マージuop」の例は見つかりませんが、部分的なフラグストールの場合と同じです
Lewis Kelsey

そうです、P6の初期は、書き戻しまで停止します。Core2とNehalemがマージするuopを前後に挿入しますか?フロントエンドの停止時間を短くするだけです。Sandybridgeは、マージするuopsを停止することなく挿入します。(ALが合流しながら、しかし、AH-マージは、それ自体でサイクルに問題を有している完全なグループの一部であり得る。)ハズウエル/ SKLが全くRAX別にALの名前を変更しないので、mov al, [mem]マイクロ融合負荷が+ ALU-マージ、AHの名前変更のみ、およびAHマージuopは依然として単独で発行されます。これらのCPUの部分フラグマージメカニズムはさまざまです。たとえば、部分regとは異なり、Core2 / Nehalemはまだ部分フラグが停止しているだけです。
Peter Cordes
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.