これは意味があります。しかし、私は最も単純な答えは、他のすべてのレジスタが使用されていると思います。他のレジスタを使用するには、スタックにプッシュする必要があります。
コンパイラは十分にスマートです。コンパイラのレジスタにあるものを追跡することはややこしいことですが、それは問題ではありません。一般的に必ずしもx86とは言えないが、x86よりも多くのレジスタを持っているときは、(呼び出し規約で)入力に使用されるいくつかのレジスタを持つことになり、ごみ箱に入れることができるレジスタがあなたが最初にそれらを保存する必要がありますごみ箱を入力するかどうか、いくつかを入力します。命令セットには特殊レジスタがあり、自動インクリメントにはこのレジスタを、レジスタ間接には命令を使用する必要があります。
たとえば、入力と切り捨て可能なレジスタが同じセットである場合など、コンパイラに腕のコードを生成させるのは簡単ではありませんが、別の関数を呼び出して呼び出し関数を作成するとそれは返品後に使用するものを保存する必要があります:
unsigned int more_fun (unsigned int);
unsigned int fun (unsigned int x)
{
return(more_fun(x)+x);
}
00000000 <fun>:
0: e92d4010 push {r4, lr}
4: e1a04000 mov r4, r0
8: ebfffffe bl 0 <more_fun>
c: e0840000 add r0, r4, r0
10: e8bd4010 pop {r4, lr}
14: e12fff1e bx lr
私はそれが些細なことだと言った。あなたの議論を後方に使うために、なぜ彼らはスタック上のr0をプッシュして後でポップするのですか?なぜr4を押しますか? r0ないしr3は入力に使用され、揮発性であり、r0はそれが適合するときのリターンレジスタであり、r4はほとんどすべての方法で保存しなければならない(1つは例外と考える)。
r4は呼び出し元や呼び出し元によって使用されていると見なされますが、呼び出し規約ではそれをゴミ箱に保存できないように指示していますので、使用すると仮定する必要があります。あなたはr0〜r3をゴミ箱に入れることができますが、被呼者がそれらをゴミ箱に入れることはできませんので、受信した値xを取ってそれを使う(渡す)必要があります。彼らは両方のことをしました、 "移動で別のレジスタを使用しました"が、そうするために、彼らは他のレジスタを保存しました。
この場合のスタックへのr4の保存は非常に明白です。なぜなら、あなたは常にスタックを64ビットのチャンクで使用することを望んでいますので、一度に2つのレジスタを理想的に少なくとも64ビットの境界線に整列させておくと、とにかくlrを保存しなければならないので、そうしないと何か他のものを押し込むことになります。この場合、r4の節約は無料です。 r0を保存すると同時にそれを使用します。 r4またはr5または上記の何かが良い選択です。
BTWは上記のx86コンパイラのようです。
0000000000000000 <fun>:
0: 53 push %rbx
1: 89 fb mov %edi,%ebx
3: e8 00 00 00 00 callq 8 <fun+0x8>
8: 01 d8 add %ebx,%eax
a: 5b pop %rbx
b: c3 retq
彼らは維持する必要がいけない何かを押してそれらのデモンストレーション:
unsigned int more_fun (unsigned int);
unsigned int fun (unsigned int x)
{
return(more_fun(x)+1);
}
00000000 <fun>:
0: e92d4010 push {r4, lr}
4: ebfffffe bl 0 <more_fun>
8: e8bd4010 pop {r4, lr}
c: e2800001 add r0, r0, #1
10: e12fff1e bx lr
R4を保存する理由はない、この場合には、R4が選ばれたので、彼らはただ、整列スタックを作るために、いくつかのレジスタを必要とこのコンパイラのいくつかのバージョンでは、r3やその他のレジスタが使用されています。
コンパイラとオプティマイザを書く人間を覚えておいてください。なぜ、これがなぜ人間や人間にとって本当に質問なのですか?これは単純な作業ではありませんが、合理的なサイズの関数やプロジェクトを作成し、コンパイラの出力を調整して改善することは困難ではありません。もちろん、美しさは見る者の目の前にあり、改善の定義はもう一つの定義が悪化することです。 1つの命令ミックスは、プログラムのサイズ基準では「より良い」、命令やバイトを多く使用することもあれば使用しないこともありますが、実行時間が短く、理想的に実行する命令のコストでメモリアクセスが少なくなるより速いなど
汎用レジスタは数百もありますが、私たちが毎日使っている製品のほとんどにはそのようなものがありません。そのため、一般的には、飛行中に非常に多くの変数を持つ関数やコードを作ることができますあなたはスタックの中間関数をオフに保存しなければならない関数で。だから、関数の最初と最後にいくつかのレジスタを保存するだけでは、中途関数が必要な作業レジスタの数があなたが持っているより多くのレジスタであれば、より多くの作業レジスタを中間関数として与えることはできません。実際には、あまりにも多くのレジスタを必要としないように最適化しないコードを書くことができるようになるには練習が必要ですが、コンパイラがどのように出力を調べるかを見てから、上記のような簡単な関数を書くことができますレジスタ中間機能の最適化または強制保存など
コンパイラがやや正気にならないようにするためには、呼び出し規約が必要です。作成者はコードを作成して管理することから悪夢にならず、コンパイラは邪魔にならないようにします。また、呼び出し規約では、入力レジスタと出力レジスタ、および揮発性レジスタと、保持しなければならないレジスタを明確に定義しようとしています。
unsigned int fun (unsigned int x, unsigned int y, unsigned int z)
{
unsigned int a;
a=x<<y;
a+=(y<<z);
a+=x+y+z;
return(a);
}
00000000 <fun>:
0: e0813002 add r3, r1, r2
4: e0833000 add r3, r3, r0
8: e0832211 add r2, r3, r1, lsl r2
c: e0820110 add r0, r2, r0, lsl r1
10: e12fff1e bx lr
これで数秒しか費やされませんでしたが、それをもっと重視することができました。私は4つの変数を持っていた過去4つのレジスタ合計をプッシュしなかった。そして、コンパイラは、依存関係が解消されたときに、必要に応じてr0-r3をゴミ箱に自由に入れられるように、関数を呼び出さなかった。だから、私は一時的なストレージを作成するためにr4を保存する必要はありませんでした、それはちょうど実行順序を最適化したスタックを使用する必要はありませんでした。たとえば、z2変数を解放して、後でr2を中間変数aのインスタンスの1つは何かに等しい。 5つ目のレジスタを書き込むのではなく、4つのレジスタに保存しておきます。
私が自分のコードで創造力を発揮し、関数呼び出しを追加した場合、もっと多くのレジスタを焼くことができました。この最後のケースでもコンパイラは何の問題もない何がどこにあるのか、そしてコンパイラを使って遊んだときに、あなたがコードを書いたのと同じ順番で、同じレジスタに高レベルの言語変数をそのまま残しておく必要はありませんしかし、レジスタのほんの一部が不安定であるとみなされた場合でも、呼び出し規約の慈悲のもとにいます。コード内の特定の時間に関数から関数を呼び出すと、その関数を保持する必要がありますコンテンツを長期保存として使用することはできません。また、揮発性でないものはすでに消費されているとみなされているので、それらを使用するには保存する必要があります。パフォーマンス上の問題がありますが、その場でスタックに保存するためには(サイズ、スピードなど)コストがかかりますか、命令を減らしたり、見えなくしたり、クロックを少なくしたりする方法で前面を維持できますか?別々の、より効率的でない転送中間関数よりも大きな転送?
私はこれを今7回言いましたが、最終行はそのコンパイラ(バージョン)とターゲット(およびコマンドラインオプション/デフォルト)の呼び出し規約です。揮発性レジスタ(ハードウェア/ ISAのものではなく、汎用レジスタの任意の呼び出し規約)があり、他の関数を呼び出さない場合は、使いやすく、高価なスタック(メモリ)トランザクションを節約できます。あなたが誰かに電話をしている場合、彼らは彼らが自由になることができないように、あなたのコードに依存して彼らがゴミ箱に捨てることができます。不揮発性レジスタは呼び出し元によって消費されると見なされるため、スタック操作を使用するためにスタック操作を書き込む必要があります。スタック操作は自由に使用できません。そして、スタック、プッシュ、ポップ、ムービーをいつ、どこで使うかはパフォーマンスになります。同じコンベンションを使用しても同じコードを生成するコンパイラは2つありませんが、テスト関数を作成し、コンパイルして出力を検査し、そこを微調整してその前後をナビゲートすることはやや簡単です(コンパイラ、バージョンとターゲット、規約とコマンドラインオプション)オプティマイザ
スタック上の値を使用して、それらが占有するレジスタを使用できるようにします。なぜ彼らは他のレジスタにそれらを移動しないのですか?おそらく、他のレジスタもいくつかの値に必要とされるためです。 – fuz
ループ内でESIレジスタが空いている場合は、カウンタをESIに入れてシャッフルしない方がよいでしょう。あなたのコンパイラがスマートなら、それはそれを知っているでしょう。結論:あなたはダンプコンパイラを持っているか、ループ内でESIがフリーではないことを知っているか、他の空きレジスタがないことを知っています。その場合、PUSH/POPの組み合わせはひどいものではありません。 –
* "最近のコンパイラ(少なくとも私が経験したもの)はPUSHとPOP命令を実行する" * ...これはかなり偽の主張であり、 'gcc'や' clang'を試してみると、ループ内に余分なレジスタを置いておけば、 '[ebp-ofs]/[esp + ofs]'というローカル変数を使うことになるでしょう。私は、これらの2つのPUSH/POPを生成するC/C++ソースを見たいと思っています。その後、これらの2つの基本的なコンパイラも基本的に唯一のコンパイラなので、何を確認したのかは分かりません。 – Ped7g