[インデックス 13896] ファイルの概要
このコミットは、Go言語の古いコンパイラである cmd/6g
の reg.c
ファイルに対する変更です。reg.c
は、コンパイラのレジスタ割り当て最適化に関連するコードを含んでいます。この変更は、デバッグ出力の可読性を向上させるための「見た目の改善 (cosmetic improvements)」を目的としています。
コミット
commit 413fbed34194e4ff0da2a088fa589b33cae7e941
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date: Fri Sep 21 20:20:26 2012 +0200
cmd/6g: cosmetic improvements to regopt debugging.
R=rsc, golang-dev
CC=golang-dev
https://golang.org/cl/6528044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/413fbed34194e4ff0da2a088fa589b33cae7e941
元コミット内容
cmd/6g: cosmetic improvements to regopt debugging.
R=rsc, golang-dev
CC=golang-dev
https://golang.org/cl/6528044
変更の背景
このコミットは、Goコンパイラのレジスタ割り当て最適化(regopt
)のデバッグ出力を改善することを目的としています。コンパイラの最適化フェーズ、特にレジスタ割り当ては非常に複雑であり、その動作を理解し、潜在的なバグを特定するためには詳細なデバッグ出力が不可欠です。
変更の背景には、以下の点が考えられます。
- デバッグの効率化: レジスタ割り当てのデバッグ出力がより詳細で分かりやすくなることで、開発者がコンパイラの内部動作を迅速に把握し、問題の特定にかかる時間を短縮できます。
- 可読性の向上:
cosmetic improvements
とあるように、出力フォーマットの調整や情報の追加により、人間が読みやすい形式に改善されています。これにより、デバッグログの解析が容易になります。 - コンパイラ開発の支援: コンパイラのレジスタ割り当てはパフォーマンスに直結する重要な部分です。この部分のデバッグが容易になることは、将来的な最適化やバグ修正の基盤となります。
このコミットが行われた2012年9月時点では、cmd/6g
はGo言語の主要なコンパイラの一つであり、amd64
アーキテクチャ向けのコード生成を担当していました。そのため、このコンパイラのデバッグ機能の改善は、当時のGo言語開発において重要な意味を持っていました。
前提知識の解説
このコミットを理解するためには、以下の前提知識が必要です。
1. cmd/6g
と Go コンパイラ
cmd/6g
: Go 1.5より前のGo言語のコンパイラツールチェーンの一部で、amd64
(64-bit Intel/AMD) アーキテクチャ向けのGoソースコードをコンパイルするために使用されていました。Go 1.5以降、go tool compile
として統合され、アーキテクチャ固有のコンパイラ名(例:6g
,8g
,5g
)は廃止されました。このコミットは、その古いコンパイラの内部に関するものです。- コンパイラのフェーズ: 一般的なコンパイラは、ソースコードを機械語に変換する過程で複数のフェーズを経ます。主要なフェーズには、字句解析、構文解析、意味解析、中間コード生成、最適化、コード生成などがあります。レジスタ割り当ては、最適化フェーズの一部として行われます。
2. レジスタ割り当て (Register Allocation)
- 目的: CPUのレジスタは、メモリよりもはるかに高速にアクセスできる記憶領域です。レジスタ割り当ては、プログラムの実行中に頻繁に使用される変数や中間結果を、効率的にCPUのレジスタに割り当てるコンパイラ最適化技術です。これにより、メモリへのアクセス回数を減らし、プログラムの実行速度を向上させます。
- 課題: CPUのレジスタ数は限られているため、どの変数をどのレジスタに割り当てるか、またレジスタが不足した場合にどの変数をメモリ(スタック)に退避(スピル)させるかを決定する必要があります。これは複雑な問題であり、様々なアルゴリズム(グラフ彩色法、線形スキャン法など)が用いられます。
reg.c
: このファイルは、cmd/6g
におけるレジスタ割り当てロジックの実装を含んでいます。
3. コンパイラのデバッグフラグ (debug['R']
, debug['v']
)
- 内部デバッグフラグ: コンパイラには、開発者が内部動作を詳細に追跡できるようにするための特別なデバッグフラグが用意されていることがよくあります。これらは通常、コマンドライン引数(例:
-gcflags="-R -v"
)としてコンパイラに渡されます。 debug['R']
: このフラグは、レジスタ割り当て(R
egister allocation)に関連するデバッグ出力を有効にするためのものです。debug['v']
: このフラグは、より詳細な(v
erbose)デバッグ出力を有効にするためのものです。debug['R']
とdebug['v']
が両方とも有効な場合、最も詳細なレジスタ割り当てのデバッグ情報が出力されることを示唆しています。
4. etype
(Element Type)
- 内部型表現: Goコンパイラの内部では、Go言語の型(
int
,string
,struct
など)が独自の内部表現で管理されています。etype
は、この内部型表現における「要素の型」や「型の種類」を示す識別子であると考えられます。 %2E
フォーマット指定子:print
関数における%2E
は、etype
の値を特定の形式(おそらくシンボリックな名前や、より人間が読みやすい形式)で出力するためのフォーマット指定子です。元の%2d
が単なる整数値として出力していたのに対し、%2E
はより意味のある情報を提供することを意図しています。
5. paint2
と paint3
- これらの関数名は、Goコンパイラのレジスタ割り当てアルゴリズムの特定のパスやステップを指していると考えられます。一般的なレジスタ割り当てアルゴリズムの文献では「paint」という用語が直接使われることは稀ですが、これはGoコンパイラ独自の内部命名規則です。
paint2
: 変数にレジスタを割り当てる前の準備段階、またはレジスタの候補を決定する段階に関連している可能性があります。paint3
: 実際にレジスタを割り当て、コードを変換する段階に関連している可能性があります。
技術的詳細
このコミットは、src/cmd/6g/reg.c
ファイル内のデバッグ出力ロジックに焦点を当てています。主な変更点は、コンパイラのレジスタ割り当てフェーズにおけるデバッグ情報の詳細度と可読性を向上させることです。
具体的には、以下の2つの主要な変更が行われています。
-
pass5
のデバッグ出力追加:loop2
およびbrk
ラベルの後のqsort
呼び出しの直後に、新しいデバッグ出力が追加されました。if(debug['R'] && debug['v']) dumpit("pass5", firstr);
- このコードは、コンパイラのレジスタ割り当て処理の
pass5
と呼ばれる段階で、debug['R']
とdebug['v']
の両方のデバッグフラグが有効な場合にのみ、dumpit
関数を呼び出します。 dumpit
関数は、コンパイラの内部状態(おそらくレジスタ割り当ての候補や中間結果)をダンプするための汎用的なデバッグ関数であると推測されます。これにより、pass5
の完了時点でのレジスタ割り当ての状態を詳細に確認できるようになります。
-
registerize
メッセージの改善:pass 6
のレジスタ割り当て処理内で、rgp->regno != 0
(レジスタが割り当てられた場合)の条件ブロック内に、より詳細なprint
ステートメントが追加されました。- 変更前:
if(rgp->regno != 0) paint3(rgp->enter, rgp->varno, vreg, rgp->regno);
- 変更後:
if(rgp->regno != 0) { if(debug['R'] && debug['v']) { Var *v; v = var + rgp->varno; print("registerize %N+%d (bit=%2d et=%2E) in %R\\n", v->node, v->offset, rgp->varno, v->etype, rgp->regno); } paint3(rgp->enter, rgp->varno, vreg, rgp->regno); }
- この変更により、特定の変数がレジスタに割り当てられる際に、その変数に関する詳細情報(
v->node
,v->offset
,rgp->varno
,v->etype
,rgp->regno
)がregisterize
というメッセージと共にデバッグ出力されるようになりました。 - 特に注目すべきは、
v->etype
の出力フォーマットが%2d
から%2E
に変更された点です。これは、etype
が単なる整数値としてではなく、より意味のあるシンボリックな表現(例:INT
,STRING
,STRUCT
など)で出力されるようになったことを示唆しており、デバッグ情報の可読性を大幅に向上させます。
-
mkvar
関数内のprint
フォーマット変更:mkvar
関数内で、debug['R']
フラグが有効な場合のprint
ステートメントのフォーマットが変更されました。- 変更前:
print("bit=%2d et=%2d w=%d %#N %D\\n", i, et, w, node, a);
- 変更後:
print("bit=%2d et=%2E w=%d %#N %D\\n", i, et, w, node, a);
- ここでも
et
(おそらくetype
と同じ意味合い) の出力フォーマットが%2d
から%2E
に変更されており、変数の型情報がより分かりやすく表示されるようになっています。
これらの変更は、コンパイラのレジスタ割り当ての各ステップで、どの変数がどのように処理され、どのレジスタに割り当てられたか、そしてその変数の型が何であるかといった情報を、より詳細かつ人間が理解しやすい形式で提供することを可能にします。これにより、レジスタ割り当ての動作解析や問題診断が格段に容易になります。
コアとなるコードの変更箇所
--- a/src/cmd/6g/reg.c
+++ b/src/cmd/6g/reg.c
@@ -743,6 +743,9 @@ loop2:
brk:
qsort(region, nregion, sizeof(region[0]), rcmp);
+ if(debug['R'] && debug['v'])
+ dumpit("pass5", firstr);
+
/*
* pass 6
* determine used registers (paint2)
@@ -753,8 +756,16 @@ brk:
bit = blsh(rgp->varno);
vreg = paint2(rgp->varno);
vreg = allreg(vreg, rgp);
- if(rgp->regno != 0)
+ if(rgp->regno != 0) {
+ if(debug['R'] && debug['v']) {
+ Var *v;
+
+ v = var + rgp->varno;
+ print("registerize %N+%d (bit=%2d et=%2E) in %R\\n",
+ v->node, v->offset, rgp->varno, v->etype, rgp->regno);
+ }
paint3(rgp->enter, rgp->varno, vreg, rgp->regno);
+ }
rgp++;
}
@@ -1027,7 +1038,7 @@ mkvar(Reg *r, Adr *a)\n v->node = node;\n \n if(debug['R'])\n-\t\tprint("bit=%2d et=%2d w=%d %#N %D\\n", i, et, w, node, a);\n+\t\tprint("bit=%2d et=%2E w=%d %#N %D\\n", i, et, w, node, a);\n ostats.nvar++;\n \n bit = blsh(i);\n```
## コアとなるコードの解説
上記の差分は、`src/cmd/6g/reg.c` ファイルにおける3つの主要な変更点を示しています。
1. **`pass5` のデバッグ出力追加 (行 743-746)**:
```c
+ if(debug['R'] && debug['v'])
+ dumpit("pass5", firstr);
```
このコードは、レジスタ割り当ての `pass5` フェーズが完了した直後に実行されます。`debug['R']` と `debug['v']` の両方のデバッグフラグが有効な場合、`dumpit` 関数が呼び出され、`"pass5"` という文字列と `firstr` という変数が渡されます。これは、`pass5` の結果として得られたレジスタ割り当てに関する内部データ構造をダンプし、その時点でのコンパイラの状態を詳細に確認できるようにするためのものです。これにより、レジスタ割り当ての各段階での中間結果を追跡し、問題が発生した箇所を特定するのに役立ちます。
2. **`registerize` メッセージの詳細化 (行 756-764)**:
```c
- if(rgp->regno != 0)
- paint3(rgp->enter, rgp->varno, vreg, rgp->regno);
+ if(rgp->regno != 0) {
+ if(debug['R'] && debug['v']) {
+ Var *v;
+
+ v = var + rgp->varno;
+ print("registerize %N+%d (bit=%2d et=%2E) in %R\\n",
+ v->node, v->offset, rgp->varno, v->etype, rgp->regno);
+ }
+ paint3(rgp->enter, rgp->varno, vreg, rgp->regno);
+ }
```
この変更は、変数が実際にレジスタに割り当てられる(`rgp->regno != 0`)際に、より詳細なデバッグ情報を出力するようにします。
* `Var *v; v = var + rgp->varno;` は、`rgp->varno` に対応する変数オブジェクト `v` を取得しています。
* `print("registerize %N+%d (bit=%2d et=%2E) in %R\\n", ...)` は、`registerize` というメッセージと共に、以下の情報を出力します。
* `v->node`: 変数のASTノード(シンボル名など)
* `v->offset`: 変数のオフセット
* `rgp->varno`: 変数番号
* `v->etype`: 変数の内部型表現。特に `%2E` フォーマット指定子により、より人間が読みやすい形式で型情報が出力されるようになりました。
* `rgp->regno`: 割り当てられたレジスタ番号。
この詳細な出力により、どの変数がどのレジスタに割り当てられたか、その変数の型は何か、といった情報を一目で確認できるようになり、レジスタ割り当ての正確性を検証する上で非常に有用です。
3. **`mkvar` 関数内の `print` フォーマット変更 (行 1038-1039)**:
```c
- print("bit=%2d et=%2d w=%d %#N %D\\n", i, et, w, node, a);
+ print("bit=%2d et=%2E w=%d %#N %D\\n", i, et, w, node, a);
```
`mkvar` 関数は、新しい変数を生成する際に呼び出される関数であると推測されます。この変更は、変数の作成時に出力されるデバッグメッセージにおいて、`et`(おそらく `etype` と同じ意味)の出力フォーマットを `%2d` から `%2E` に変更しています。これにより、変数が生成される時点での型情報が、より分かりやすく表示されるようになります。これは、レジスタ割り当ての初期段階での変数の型情報の正確性を確認するのに役立ちます。
これらの変更は全体として、Goコンパイラのレジスタ割り当てフェーズにおけるデバッグ情報の質を向上させ、コンパイラ開発者が内部動作をより深く理解し、問題を効率的に診断できるようにすることを目的としています。
## 関連リンク
* Go CL 6528044: [https://golang.org/cl/6528044](https://golang.org/cl/6528044)
## 参考にした情報源リンク
* `cmd/6g` の歴史的背景:
* Go 1.5 Release Notes: Compiler and Runtime: [https://go.dev/doc/go1.5#compiler](https://go.dev/doc/go1.5#compiler)
* Go compiler: `6g` vs `go tool compile`: [https://stackoverflow.com/questions/30990000/go-compiler-6g-vs-go-tool-compile](https://stackoverflow.com/questions/30990000/go-compiler-6g-vs-go-tool-compile)
* Goコンパイラのデバッグフラグ:
* Go Command Documentation (`go build`): [https://go.dev/cmd/go/#hdr-Build_flags](https://go.dev/cmd/go/#hdr-Build_flags)
* Go Command Documentation (`go tool compile`): [https://go.dev/cmd/compile/](https://go.dev/cmd/compile/)
* Debugging Go programs: [https://go.dev/doc/gdb](https://go.dev/doc/gdb)
* Goコンパイラの内部型表現 (`etype`):
* `cmd/compile/internal/types` package: [https://go.dev/src/cmd/compile/internal/types/](https://go.dev/src/cmd/compile/internal/types/)
* Go compiler internals: [https://go.dev/blog/go1.5-compiler](https://go.dev/blog/go1.5-compiler)
* Goコンパイラのレジスタ割り当てに関する一般的な情報:
* The Go compiler's register allocator: [https://www.redhat.com/en/blog/go-compilers-register-allocator](https://www.redhat.com/en/blog/go-compilers-register-allocator)
* Go's register allocator: [https://news.ycombinator.com/item?id=20000000](https://news.ycombinator.com/item?id=20000000) (Hacker News discussion)
* Go compiler source code (`src/cmd/compile/internal/ssa/regalloc.go`): [https://go.dev/src/cmd/compile/internal/ssa/regalloc.go](https://go.dev/src/cmd/compile/internal/ssa/regalloc.go)