[インデックス 1219] ファイルの概要
このコミットは、Goコンパイラ 6g
のバックエンドにおけるレジスタ割り当て(reg
)とピーフホール最適化(peep
)に関連する広範な変更を含んでいます。具体的には、以下の8つのファイルが修正されています。
src/cmd/6g/cgen.c
: コード生成に関する変更。src/cmd/6g/gen.c
: コード生成の汎用部分に関する変更。src/cmd/6g/gsubr.c
: コード生成のサブルーチンに関する変更。src/cmd/6g/opt.h
: 最適化パスで使用されるデータ構造の定義に関する変更。src/cmd/6g/peep.c
: ピーフホール最適化のロジックに関する変更。src/cmd/6g/reg.c
: レジスタ割り当てのロジックに関する変更。src/cmd/gc/go.h
: Go言語のASTノードやオペレーションの定義に関する変更。src/cmd/gc/walk.c
: ASTのウォーク(走査)に関する変更。
これらの変更は、コンパイラのコード生成と最適化の効率を向上させることを目的としています。
コミット
commit e081f25c3e602804fc3bd0780e09bf35d2a098cb
Author: Ken Thompson <ken@golang.org>
Date: Sat Nov 22 17:58:53 2008 -0800
reg and peep
R=r
OCL=19871
CL=19871
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e081f25c3e602804fc3bd0780e09bf35d2a098cb
元コミット内容
reg and peep
この簡潔なコミットメッセージは、レジスタ割り当て(reg
)とピーフホール最適化(peep
)に焦点を当てた変更であることを示唆しています。Goコンパイラ 6g
は、初期のGo言語のコンパイラであり、Ken Thompson氏によって開発されました。
変更の背景
Go言語の初期のコンパイラである 6g
は、コンパイル速度を重視して設計されていました。そのため、GCCやLLVMのような高度な最適化は行われていませんでしたが、基本的な最適化は組み込まれていました。このコミットは、その基本的な最適化、特にレジスタ割り当てとピーフホール最適化の改善を目的としています。
コンパイラにおけるレジスタ割り当ては、プログラムの実行速度に直結する重要な最適化です。頻繁に使用される値をCPUのレジスタに割り当てることで、メモリへのアクセスを最小限に抑え、パフォーマンスを向上させます。ピーフホール最適化は、生成された機械語コードの小さなウィンドウ(ピーフホール)を検査し、より効率的な命令シーケンスに置き換える局所的な最適化手法です。これには、冗長なロード/ストアの削除、複数の操作の結合、デッドコードの排除などが含まれます。
このコミットは、6g
コンパイラがより効率的なコードを生成できるように、これらの最適化パスを洗練させるための取り組みの一環と考えられます。特に、インクリメント/デクリメント操作の最適化や、型変換における命令選択の改善などが含まれています。
前提知識の解説
Goコンパイラ 6g
6g
は、Go言語の初期のコンパイラツールチェーン gc
の一部であり、x86-64アーキテクチャ(AMD64)をターゲットとしていました。6g
は、高速なコンパイルを優先する設計思想を持っており、現代の最適化コンパイラに比べると最適化の深度は限定的でした。しかし、それでも基本的なレジスタ割り当てやピーフホール最適化などの手法を取り入れていました。
レジスタ割り当て (Register Allocation)
レジスタ割り当ては、コンパイラの最適化フェーズの一つで、プログラムの変数や中間結果をCPUのレジスタに割り当てるプロセスです。レジスタはメモリよりもはるかに高速にアクセスできるため、適切にレジスタを割り当てることでプログラムの実行速度を大幅に向上させることができます。
6g
のようなコンパイラでは、通常、線形スキャンアルゴリズムやグラフ彩色アルゴリズムなどの手法が用いられます。線形スキャンアルゴリズムは、変数の「生存期間」(定義されてから最後に使用されるまでの期間)を分析し、レジスタが利用可能な期間に変数を割り当てます。レジスタが不足した場合は、「スピル」(レジスタからメモリへの退避)が発生します。
ピーフホール最適化 (Peephole Optimization)
ピーフホール最適化は、コンパイラのバックエンドで行われる局所的な最適化手法です。生成されたアセンブリコードや中間表現の小さな連続した命令列(「ピーフホール」と呼ばれるウィンドウ)を検査し、より効率的な等価な命令列に置き換えます。
一般的なピーフホール最適化の例としては、以下のようなものがあります。
- 冗長なロード/ストアの削除: 同じ値をレジスタにロードし、すぐにストアするような冗長な操作を削除します。
- 強度の削減: 複雑な命令をより単純で高速な命令に置き換えます(例:
x * 2
をx << 1
に)。 - 定数伝播: 定数値を計算時に使用し、実行時の計算を減らします。
- デッドコードの削除: 決して実行されないコードや、その結果が使用されないコードを削除します。
- インクリメント/デクリメントの最適化:
ADD reg, 1
をINC reg
に置き換えるなど。
6g
は、コンパイル速度を重視していたため、ピーフホール最適化も限定的な範囲で行われていました。
技術的詳細
このコミットは、6g
コンパイラのコード生成と最適化の複数の側面に影響を与えています。
src/cmd/6g/cgen.c
の変更
cgen
関数内で、OINDREG
オペレーションの処理が変更されています。以前はgmove(&n1, res)
で直接結果に移動していましたが、n2 = n1; n2.op = OINDREG; n2.type = types[TINT32]; gmove(&n2, &n1);
という中間ステップが追加され、その後gmove(&n1, res);
で最終的な結果に移動しています。これは、ポインタのデリファレンスと値の移動のセマンティクスをより正確に扱うための変更である可能性があります。
src/cmd/6g/gen.c
の変更
if(!debug['N'] || debug['R'] || debug['P']) regopt(ptxt);
の行が追加され、デバッグフラグN
(最適化なし)が設定されていない場合、またはレジスタ割り当て (R
) やピーフホール最適化 (P
) のデバッグフラグが設定されている場合にregopt
関数(レジスタ最適化)が呼び出されるようになりました。これにより、最適化の適用条件がより柔軟になりました。cgen_asop
関数内で、OADD
,OSUB
,OXOR
,OAND
,OOR
などの二項演算子とリテラル値1
の組み合わせに対する特殊な最適化が追加されました。OADD
とOSUB
の場合、右オペランドがリテラル1
であれば、それぞれOINC
(インクリメント) およびODEC
(デクリメント) 命令に変換されるようになりました。これは、より効率的な単一命令に置き換えるピーフホール最適化の一種です。OXOR
,OAND
,OOR
の場合も、右オペランドがリテラルであれば、直接対応する命令を生成するようになりました。
cgen_as
関数にiszer
フラグが追加され、右辺がゼロである場合の処理が改善されました。特に、nl->addable
かつiszer
の場合にANOP
(No Operation) 命令を挿入することで、後続の最適化パスで利用される可能性のある「使用済み」マークを付ける意図があるようです。
src/cmd/6g/gsubr.c
の変更
gmove
関数内で、TPTR32
からTPTR64
への型変換において、AMOVLQZX
命令が使用されるようになりました。これは、32ビットポインタを64ビットポインタにゼロ拡張して移動するための命令です。- コメントアウトされていた整数型間の変換に関する
CASE
文が、コメントアウトされたままですが、フォーマットが変更されています。 TUINT64
からTINT8
,TINT16
,TINT32
への変換にAMOVLQSX
(符号拡張移動) が追加されました。TUINT32
からTINT64
,TUINT64
,TPTR64
への変換、およびTPTR32
からTINT64
,TUINT64
,TPTR64
への変換にAMOVLQZX
(ゼロ拡張移動) が追加されました。これにより、異なるサイズの整数型やポインタ型間の変換がより正確かつ効率的に行われるようになりました。optoas
関数に、OINC
(インクリメント) とODEC
(デクリメント) オペレーションに対する新しい命令マッピングが追加されました。TINT8
/TUINT8
にはAINCB
/ADECB
(バイト単位)TINT16
/TUINT16
にはAINCW
/ADECW
(ワード単位)TINT32
/TUINT32
/TPTR32
にはAINCL
/ADECL
(ロングワード単位)TINT64
/TUINT64
/TPTR64
にはAINCQ
/ADECQ
(クワッドワード単位) これらの命令は、CPUが提供する単一のインクリメント/デクリメント命令を利用することで、ADD reg, 1
やSUB reg, 1
といった2命令を1命令に削減し、コードサイズと実行速度を改善します。
src/cmd/6g/opt.h
の変更
Reg
構造体からmagic
,pc
,log5
フィールドが削除され、loop
フィールドがint32
からuint16
に変更され、refset
フィールドがuchar
型で追加されました。これは、レジスタ割り当ての内部データ構造の整理と効率化を示しています。EXTERN Bits ovar;
が追加され、出力引数(戻り値)の変数を追跡するためのビットセットが導入されました。ostats
構造体が追加され、コンパイラの最適化に関する統計情報(ncvtreg
,nspill
,nreload
,ndelmov
,nvar
,naddr
)を収集できるようになりました。これは、最適化の有効性を評価し、さらなる改善のためのデータを提供するために重要です。dumpit
とnoreturn
関数のプロトタイプが、static
から通常の関数に変更されました。
src/cmd/6g/peep.c
の変更
peep
関数内で、Prog
構造体のreg
フィールドがr2->prog = p; p->reg = r2;
のように設定されるようになりました。これにより、プログラム命令とレジスタ情報が相互に参照できるようになり、最適化パスでのデータアクセスが容易になります。- デバッグ出力
dumpit("loop1", firstr);
が追加され、ピーフホール最適化のループ開始時の状態を可視化できるようになりました。 AADDQ
,AADDL
,ASUBQ
,ASUBL
命令に対するピーフホール最適化が改善されました。オフセットが-1
または1
の場合、それぞれADECQ
/ADECL
/AINCQ
/AINCL
などの単一命令に変換されるロジックがより堅牢になりました。これは、gen.c
でのOINC
/ODEC
の生成と連携して、より効率的なコードを生成します。excise
関数内で、削除される命令のデバッグ出力が追加され、ostats.ndelmov++
で削除された移動命令の統計が記録されるようになりました。
src/cmd/6g/reg.c
の変更
MAGIC
マクロが削除されました。setoutvar
関数が追加されました。この関数は、関数の出力引数(戻り値)を走査し、それらの変数をovar
ビットセットに記録します。これは、レジスタ割り当てにおいて、戻り値がレジスタに割り当てられるべきか、あるいはメモリにスピルされるべきかを判断する際に重要な情報となります。regopt
関数内で、ovar
ビットセットの初期化が追加されました。regopt
のパス1において、AINCB
,AINCL
,AINCQ
,AINCW
,ADECB
,ADECL
,ADECQ
,ADECW
といったインクリメント/デクリメント命令が、レジスタの読み書き操作として適切に処理されるようになりました。regopt
の各パス(パス1、パス2、パス2.5、パス3、パス4、パス6)の開始時に、デバッグフラグR
とv
が設定されている場合にdumpit
関数が呼び出され、レジスタ割り当ての各段階での状態を詳細にトレースできるようになりました。- 「used and not set」および「set and not used」の警告ロジックが変更され、
r->refset
フラグが導入されました。これにより、警告が一度だけ表示されるようになり、冗長な出力が抑制されます。また、デバッグフラグw
が追加され、警告の表示を制御できるようになりました。 nregion
がNRGN
を超えた場合のfatal
エラーが、デバッグモードでは警告に変わるようになりました。peep()
関数の呼び出し条件がif(!debug['R'] || debug['P'])
に変更されました。これは、レジスタ割り当てのデバッグ (-R
) が有効な場合はピーフホール最適化 (-P
) をスキップしないことを意味します。- 最適化後の不要な
ANOP
命令の削除ロジックが改善され、分岐命令のターゲットも適切に更新されるようになりました。 regopt
関数の最後に、ostats
構造体に記録された最適化統計情報が出力されるようになりました。これにより、レジスタ割り当てやピーフホール最適化の効果を数値的に評価できるようになります。addmove
関数内で、AMOVSD
(double-precision float move) がTFLOAT64
型の移動に使用されるようになりました。また、ostats.nspill++
でスピル操作の統計が記録されるようになりました。mkvar
関数内で、レジスタ使用ビットマップr->regu
の更新がr != R
の条件付きになりました。また、ostats.naddr++
とostats.nvar++
でアドレスと変数の統計が記録されるようになりました。prop
関数内で、ARET
(return) 命令の処理が変更され、cal.b[z]
にovar.b[z]
(出力変数) が含まれるようになりました。これにより、戻り値がレジスタ割り当ての考慮対象となることが明確になります。paint1
関数内で、デバッグ出力が削除され、より簡潔になりました。paint3
関数内で、デバッグ出力がdebug['R'] && debug['v']
の条件付きになり、より詳細なデバッグ情報が必要な場合にのみ表示されるようになりました。また、addreg
関数呼び出し後にostats.ncvtreg++
でレジスタ変換の統計が記録されるようになりました。BtoR
関数で、ビットマスクが0xffffL
から0x3fffL
に変更されました。これは、レジスタのビット表現に関する変更であり、おそらく特定のレジスタ(R14, R15)を除外するためです。dumpit
とnoreturn
関数の定義がstatic
から通常の関数に変更されました。
src/cmd/gc/go.h
の変更
enum
にOINC
とODEC
という新しいオペレーションが追加されました。これらは、インクリメントとデクリメントを表すプレースホルダーであり、コンパイラのフロントエンドで認識され、バックエンドで対応する機械語命令に変換されます。
src/cmd/gc/walk.c
の変更
nottop
ラベルの処理が変更され、yyerror
のメッセージがより詳細になりました。top
の値に応じて、操作がステートメントコンテキスト、代入コンテキスト、または式コンテキストで許可されていないことを示すエラーメッセージが表示されるようになりました。これにより、コンパイラのエラー報告が改善されます。symlist[1] = pkglookup("panicl", "sys");
が追加され、noreturn
関数がpanicl
も非リターン関数として認識するようになりました。
コアとなるコードの変更箇所
このコミットのコアとなる変更は、以下のファイルと機能に集約されます。
src/cmd/6g/gen.c
およびsrc/cmd/6g/gsubr.c
におけるOINC
/ODEC
の導入と最適化:gen.c
で、+1
や-1
のリテラルを含む加減算をOINC
/ODEC
オペレーションに変換するロジックが追加されました。gsubr.c
のoptoas
関数で、これらのOINC
/ODEC
オペレーションが、ターゲットアーキテクチャの効率的な単一命令(AINCB
,AINCW
,AINCL
,AINCQ
,ADECB
,ADECW
,ADECL
,ADECQ
)にマッピングされるようになりました。
src/cmd/6g/reg.c
におけるレジスタ割り当ての改善と統計情報の導入:setoutvar
関数の追加により、関数の戻り値がレジスタ割り当ての考慮対象となるようになりました。ostats
構造体の導入により、レジスタ割り当てや最適化の各段階での統計情報が収集されるようになり、コンパイラのパフォーマンス分析と改善に役立つようになりました。- レジスタ割り当ての各パスにおける詳細なデバッグ出力が追加され、トレースが容易になりました。
src/cmd/6g/peep.c
におけるピーフホール最適化の強化:AADDQ
/AADDL
/ASUBQ
/ASUBL
命令に対するINC
/DEC
命令への変換ロジックが改善され、より多くのケースで効率的な命令が生成されるようになりました。- 最適化によって削除された命令の統計が
ostats.ndelmov
で追跡されるようになりました。
コアとなるコードの解説
インクリメント/デクリメントの最適化 (gen.c
, gsubr.c
)
Goコンパイラ 6g
は、x = x + 1
や x = x - 1
のような一般的な操作を、より効率的なCPU命令に変換するようになりました。
src/cmd/6g/gen.c
の cgen_asop
関数では、以下のようなロジックが追加されています。
if(nl->addable && nr->op == OLITERAL)
switch(n->etype) {
case OADD:
if(!isint[nl->type->etype])
goto com;
if(mpgetfix(nr->val.u.xval) != 1)
goto com;
gins(optoas(OINC, nl->type), N, nl); // OINC に変換
goto ret;
case OSUB:
if(!isint[nl->type->etype])
goto com;
if(mpgetfix(nr->val.u.xval) != 1)
goto com;
gins(optoas(ODEC, nl->type), N, nl); // ODEC に変換
goto ret;
// ...
}
このコードは、左辺がアドレス可能 (nl->addable
) で、右辺がリテラル (nr->op == OLITERAL
) の場合に、加算 (OADD
) や減算 (OSUB
) のオペレーションをチェックします。もし右辺のリテラル値が 1
であれば、それぞれ OINC
または ODEC
という新しい内部オペレーションに変換します。
次に、src/cmd/6g/gsubr.c
の optoas
関数では、これらの OINC
/ODEC
オペレーションが実際の機械語命令にマッピングされます。
case CASE(OINC, TINT8):
case CASE(OINC, TUINT8):
a = AINCB; // バイト単位のインクリメント
break;
// ... 他の型に対する AINCW, AINCL, AINCQ
case CASE(ODEC, TINT8):
case CASE(ODEC, TUINT8):
a = ADECB; // バイト単位のデクリメント
break;
// ... 他の型に対する ADECW, ADECL, ADECQ
これにより、例えば x = x + 1
というGoのコードは、コンパイル後に INCQ x
(64ビット整数) のような単一のCPU命令に変換される可能性が高まります。これは、ADDQ $1, x
のような2命令よりも効率的です。
レジスタ割り当ての統計情報 (reg.c
, opt.h
)
src/cmd/6g/opt.h
で定義された ostats
構造体は、コンパイラの最適化パスの効率を測定するための重要なツールです。
struct
{
int32 ncvtreg; // レジスタ変換の回数
int32 nspill; // レジスタスピルの回数
int32 nreload; // レジスタリロードの回数
int32 ndelmov; // 削除された移動命令の回数
int32 nvar; // 変数の処理回数
int32 naddr; // アドレスの処理回数
} ostats;
これらのカウンタは、src/cmd/6g/reg.c
や src/cmd/6g/peep.c
の様々な場所でインクリメントされます。例えば、addmove
関数(レジスタからメモリへのスピルを生成する可能性のある関数)では ostats.nspill++
が、excise
関数(不要な命令を削除する関数)では ostats.ndelmov++
が呼び出されます。
reg.c
の regopt
関数の最後で、これらの統計情報がデバッグ出力として表示されます。
if(debug['R']) {
if(ostats.ncvtreg || ostats.nspill || ostats.nreload || ostats.ndelmov || ostats.nvar || ostats.naddr || 0)
print("\nstats\n");
// ... 各統計情報の出力
memset(&ostats, 0, sizeof(ostats));
}
この統計情報は、コンパイラの開発者がレジスタ割り当てやピーフホール最適化のボトルネックを特定し、さらなる改善のための指針を得るのに役立ちます。例えば、nspill
の値が高い場合、レジスタが不足している可能性があり、より洗練されたレジスタ割り当てアルゴリズムが必要であることを示唆します。
戻り値のレジスタ割り当てへの考慮 (reg.c
)
src/cmd/6g/reg.c
に追加された setoutvar
関数と、prop
関数における ARET
(return) 命令の処理の変更は、関数の戻り値がレジスタ割り当ての対象となることを保証します。
setoutvar
関数は、関数の出力引数(戻り値)を走査し、それらの変数をグローバルな ovar
ビットセットに記録します。
void
setoutvar(void)
{
// ...
t = structfirst(&save, getoutarg(curfn->type));
while(t != T) {
// ...
for(z=0; z<BITS; z++)
ovar.b[z] |= bit.b[z]; // ovar に出力変数を追加
t = structnext(&save);
}
}
そして、prop
関数(レジスタ割り当てのデータフロー解析の一部)では、ARET
命令が処理される際に、cal
(call-clobbered registers) ビットセットに ovar
が含まれるようになりました。
case ARET:
for(z=0; z<BITS; z++) {
cal.b[z] = externs.b[z] | ovar.b[z]; // 戻り値も cal に含める
ref.b[z] = 0;
}
break;
これにより、レジスタ割り当てアルゴリズムは、関数の戻り値が呼び出し元に渡される際にレジスタに保持されるべきであることを認識し、不必要なメモリへのスピルを避けることができます。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/
- Goコンパイラのソースコード: https://github.com/golang/go
参考にした情報源リンク
- GitHubコミットページ: https://github.com/golang/go/commit/e081f25c3e602804fc3bd0780e09bf35d2a098cb
- Web検索結果: "Go compiler 6g register allocation peephole optimization" (Google Search)
- redhat.com (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEtbzE9h94vXgRZlCJSWD9XNy11H_lqimXVhYgtN27TNzBgBYXHrcI2-zWFzQRCGwogdvxO486BM1Q8efAsziUvaUqeyQ506LGKthSRsZVpmRT-fo_xFYBGfLNwdFkt4iK4hKT5Lo7K7e5WFm-f55xEYlsJg8An0f4sb4Yty7fSYuP6NzeHMUD12wMD)
- github.com (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQE0BEKtKUWKsI8Sug5kr1-eNeAjc2PrSf5T4euOqzmKlE49JQa_Es57_UrZS6x7po6gzAwmScLEMjBXVn_c8s8gwRYyNtTLYnNKERFfSdpPJCKi_kofXbgNFl2-6ZkEXneYHo1Ylqjdo9bBM8f3A26CaI8ohFwh0A==)
- wikipedia.org (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFtkK2KCP3B6eU_flYVh9W9O36GjH81YNiTyQRptLSM7GyFGcAho24NBGAVlTMtr4OWUYGh0p-_CSGjYH5wmtk7gOdiZfZVkmNb4wvZeW98Zsn1whG9A1DvBxTJo_wljKJdIUM3IpjOuTb0gwot)
- medium.com (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHEM2KZ_Z9YZlkTRQK4o6u5QEZxHdXnHX9_S-yk5isaMjcqfLJcud_CxdYOVMebZrweeF0Mnwg9Sj-45fdfFTpnwl1l3_JQ8ndiXc7B6Uukkmkwyzdx5E38TO4w1SmkVQr0a_KQowiwHxNK-Z22s0B5n29vCEyBOfBWnbLmISsDwbyaGr2kyLEujQjo)
- collegenote.net (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFIvjcnhtMTltr-sHaRQ-ovrDOlZP0ugFCimm93sCYqpE6t0ATQ2PJvFN1d_YKDtvR0SbKiCffKmHebCrjLb7KDJF7zZOjbr3u-ruvsAw9bFvFWhAa2a7jTl7FswSZvx27-SxMDL_T9yl-3fbRLA3Gw7k4JBTw28ZA-yOBcYViLmbuf9U3SeC48e-bcCX87)
- h-da.de (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEGiJjhtfhFp2RCahMSNTTkTIX6TkMZ6xVyfpw9CQxn2qaSKhngVHcWYBses3KaV4QY8sQ3xQ_RxvJdhpiPbfsHXA8xACcw_2C8q3FuXtuXV8svVOzuTdNxc4HQf6uCg_cVjSbsf8I2Vatf78ee09sgbzOdb4YDKvPQiIg0M3TqK9T8-kSy2dXQjy6iUkPoEGUsadUprGICaxpaIg==)
- ycombinator.com (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQERlDlM2zVNxAuYIopLX12fSrldfCLysawKawG-oH95qUZqdwdFje9VZL5qotz7ENyJUtCDpc4scG0AbrqIQ6IKkus85Afx4DzCNa_0g3yXfYWmX18eTKiLvJEt1iQFgoIztS1RPElk)