[インデックス 14859] ファイルの概要
このコミットは、Goコンパイラのcmd/6g
(386アーキテクチャ向け)とcmd/8g
(amd64アーキテクチャ向け)におけるピーフホール最適化器の挙動を修正し、戻り値レジスタ(AXおよびX0)の最適化を許可するように変更するものです。これにより、Go言語がレジスタを介して値を返さないという特性に合致させ、より効率的なコード生成を可能にします。
コミット
commit b73a1a8e32ed4f394299a9fec5cfb53e963f6c08
Author: Daniel Morsing <daniel.morsing@gmail.com>
Date: Fri Jan 11 15:44:42 2013 +0100
cmd/6g, cmd/8g: Allow optimization of return registers.
The peephole optimizer would keep hands off AX and X0 during returns, even though go doesn't return through registers.
R=dave, rsc
CC=golang-dev
https://golang.org/cl/7030046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b73a1a8e32ed4f394299a9fec5cfb53e963f6c08
元コミット内容
cmd/6g, cmd/8g: Allow optimization of return registers.
The peephole optimizer would keep hands off AX and X0 during returns, even though go doesn't return through registers.
変更の背景
Go言語のコンパイラには、生成されるアセンブリコードを最適化するための「ピーフホール最適化器」が組み込まれています。この最適化器は、コード内の小さなパターン(「ピーフホール」)を認識し、より効率的な命令シーケンスに置き換えることで、プログラムの実行速度を向上させます。
しかし、このコミット以前のcmd/6g
およびcmd/8g
コンパイラのピーフホール最適化器には、特定の不整合がありました。それは、関数からの戻り値の処理に関するものです。Go言語の関数は、通常、戻り値をレジスタを介して返すのではなく、スタックフレームやその他のメモリ領域を介して返します。それにもかかわらず、ピーフホール最適化器は、戻り値に関連するレジスタ(特にx86/x64アーキテクチャにおけるAX
レジスタや浮動小数点レジスタX0
など)を、あたかもそれらが戻り値のために予約されているかのように、最適化の対象から除外していました。
この「手つかず」の状態は、実際にはGoの戻り値メカニズムとは無関係であり、これらのレジスタが他の目的で最適化される機会を不必要に制限していました。結果として、生成されるコードが本来よりも非効率になる可能性がありました。このコミットは、この不必要な制約を取り除き、ピーフホール最適化器が戻り値レジスタも最適化の対象とすることを許可することで、より効率的なコード生成を目指しています。
前提知識の解説
ピーフホール最適化器 (Peephole Optimizer)
ピーフホール最適化は、コンパイラの最適化手法の一つです。コンパイラが生成したアセンブリコードや中間表現を、ごく短い命令シーケンス(「ピーフホール」と呼ばれる小さな窓)に注目してスキャンし、より効率的な同等のシーケンスに置き換えます。例えば、「レジスタに値をロードし、直後にそのレジスタから同じ値をストアする」といった冗長な命令ペアを、「直接メモリからメモリへストアする」といった単一の命令に置き換えることで、命令数を減らしたり、より高速な命令を使用したりします。これは、局所的な最適化であり、コード全体のフローを考慮するより広範な最適化(例:ループ最適化)とは異なりますが、非常に効果的です。
戻り値レジスタ (Return Registers: AX, X0)
CPUには、様々な目的で使用される汎用レジスタや特殊レジスタがあります。関数呼び出し規約(Calling Convention)は、関数が引数をどのように受け取り、戻り値をどのように返すかを定義します。多くのアーキテクチャやコンパイラでは、関数の戻り値を特定のレジスタに格納して返すことが一般的です。
- AXレジスタ: x86およびx64アーキテクチャにおいて、
AX
(またはEAX
/RAX
)レジスタは、関数の戻り値を格納するためによく使用される汎用レジスタです。整数型やポインタ型の戻り値がここに格納されることが多いです。 - X0レジスタ (FREGRET): 浮動小数点演算を行うためのレジスタです。Goコンパイラの文脈では、
FREGRET
は浮動小数点戻り値レジスタを指し、これは通常、浮動小数点演算ユニット(FPU)のレジスタ(例:x86のXMM0
など)に対応します。
Go言語の戻り値メカニズム
Go言語の関数呼び出し規約は、C言語などとは異なり、戻り値をレジスタを介して直接返すことを基本としていません。特に複数の戻り値を持つ場合や、構造体などの複雑な型を返す場合、Goコンパイラは戻り値をスタックフレーム上の特定の領域に配置したり、呼び出し元が用意したメモリ領域に書き込んだりすることが一般的です。これは、レジスタの数が限られていることや、複数の戻り値を効率的に扱うための設計上の選択です。
このため、Goのピーフホール最適化器がAX
やX0
といったレジスタを「戻り値レジスタ」として特別扱いし、最適化の対象から外すことは、Goの実際の戻り値メカニズムとは矛盾していました。
cmd/6g
とcmd/8g
これらは、Go言語の初期のコンパイラツールチェーンの一部です。
cmd/6g
: 386(Intel 80386互換)アーキテクチャ向けのGoコンパイラ。32ビットシステムをターゲットとします。cmd/8g
: amd64(x86-64)アーキテクチャ向けのGoコンパイラ。64ビットシステムをターゲットとします。
これらのコンパイラは、Goソースコードを対応するアーキテクチャのアセンブリコードに変換する役割を担っていました。
技術的詳細
このコミットは、src/cmd/6g/peep.c
とsrc/cmd/8g/peep.c
という2つのファイルに存在するcopyu
関数内のARET
(関数戻り値)ケースのロジックを変更しています。
copyu
関数は、ピーフホール最適化器の一部であり、コード内のアドレス(Adr
)がコピー可能かどうか、または最適化の対象となるかどうかを判断する役割を担っています。この関数は、特定の命令(Prog
)と、その命令が操作するアドレスv
およびs
を受け取ります。戻り値は、最適化の可否や種類を示すコードです。
変更前のコードでは、ARET
(関数戻り値)のケースにおいて、v->type
がREGRET
(汎用戻り値レジスタ)またはFREGRET
(浮動小数点戻り値レジスタ)である場合に、return 2;
という処理を行っていました。このreturn 2;
は、ピーフホール最適化器に対して、これらのレジスタが戻り値のために予約されており、最適化の対象から外すべきであるというシグナルを送っていました。
しかし、前述の通り、Go言語は戻り値をこれらのレジスタを介して直接返すわけではありません。したがって、この条件分岐は不必要であり、これらのレジスタが他の最適化(例:デッドコード削除、レジスタの再利用)の対象となることを妨げていました。
このコミットでは、この不必要な条件分岐を削除することで、ピーフホール最適化器がREGRET
やFREGRET
タイプのレジスタに対しても、他のレジスタと同様に最適化を適用できるようにします。これにより、コンパイラはより積極的にレジスタを再利用したり、冗長な命令を削除したりすることが可能になり、結果として生成されるアセンブリコードの効率が向上します。
コアとなるコードの変更箇所
src/cmd/6g/peep.c
および src/cmd/8g/peep.c
の両ファイルにおいて、copyu
関数内のARET
ケースから以下の2行が削除されました。
--- a/src/cmd/6g/peep.c
+++ b/src/cmd/6g/peep.c
@@ -1157,8 +1157,6 @@ copyu(Prog *p, Adr *v, Adr *s)
return 0;
case ARET: /* funny */
- if(v->type == REGRET || v->type == FREGRET)
- return 2;
if(s != A)
return 1;
return 3;
src/cmd/8g/peep.c
も同様の変更です。
コアとなるコードの解説
削除されたコードは以下の通りです。
if(v->type == REGRET || v->type == FREGRET)
return 2;
v->type
: これは、copyu
関数に渡されるアドレスv
の型を示します。REGRET
: 汎用戻り値レジスタの型を表す定数です。FREGRET
: 浮動小数点戻り値レジスタの型を表す定数です。
このif
文は、「もし現在処理しているアドレスv
が、汎用戻り値レジスタまたは浮動小数点戻り値レジスタであるならば、2
を返せ」というロジックでした。
copyu
関数における戻り値2
は、ピーフホール最適化器に対して、そのアドレス(レジスタ)が「特別な意味を持つため、最適化の対象から除外すべきである」という指示を与えていました。具体的には、戻り値レジスタは関数呼び出し規約によって保護されるべきである、という誤った仮定に基づいていました。
このコミットによってこのif
文が削除されたことで、REGRET
やFREGRET
タイプのアドレスも、他の通常のアドレスと同様にcopyu
関数の残りのロジック(if(s != A) return 1; return 3;
)によって処理されるようになります。これにより、ピーフホール最適化器は、Goの実際の戻り値メカニズムに合致するように、これらのレジスタも自由に最適化の対象とすることができるようになりました。結果として、コンパイラはより積極的なレジスタ割り当てや命令の削減を行うことができ、生成されるバイナリの効率が向上します。
関連リンク
- Go Gerrit Code Review: https://golang.org/cl/7030046
参考にした情報源リンク
- Go Gerrit Code Review (上記と同じリンク)
- Go言語のコンパイラ最適化に関する一般的な情報(ピーフホール最適化、レジスタ割り当てなど)
- x86/x64アーキテクチャにおけるレジスタの役割(AX, XMM0など)
- Go言語の関数呼び出し規約に関する情報
src/cmd/6g/peep.c
およびsrc/cmd/8g/peep.c
のソースコード(コミット前後の比較)- Go言語のコンパイラ設計に関するドキュメントや議論