[インデックス 130] ファイルの概要
このコミットは、Goコンパイラのコード生成部分(src/cmd/6g/cgen.c
)と、構文解析部分(src/cmd/gc/go.y
)にわたる変更を含んでいます。主な目的は、コードベースのクリーンアップ、診断情報の改善、そして特定の複雑な比較演算のコード生成ロジックの最適化または修正であると考えられます。また、Go言語の文法定義におけるステートメントの扱いが微調整されています。
コミット
commit 610644a1cac0bf881cc40d375691edd890e58e61
Author: Ken Thompson <ken@golang.org>
Date: Sun Jun 8 17:21:46 2008 -0700
asdf
SVN=121615
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/610644a1cac0bf881cc40d375691edd890e58e61
元コミット内容
コミットメッセージは「asdf」と非常に簡潔で、具体的な変更内容を説明していません。これは初期のGo開発における内部的なコミットによく見られる特徴です。SVNリビジョン番号 121615
が付記されています。
変更の背景
このコミットの背景には、Goコンパイラの初期開発段階における継続的な改善と最適化の取り組みがあると考えられます。
- コードベースの整理:
cgen.c
から大量のコメントアウトされたコードが削除されています。これらは過去に試行された、あるいは現在は使用されていないコードパスであり、コードベースの可読性と保守性を向上させるためのクリーンアップの一環と見られます。 - 診断情報の改善:
agen
関数におけるdynlineno
の取り扱い変更は、コンパイル時のエラーメッセージやデバッグ情報の精度を高めることを目的としている可能性があります。正確な行番号情報は、開発者がコンパイルエラーを特定し、修正する上で不可欠です。 - コード生成の最適化/修正:
bgen
関数におけるullman >= UINF
の条件分岐の追加は、特定の複雑な比較演算のコード生成における潜在的な問題の修正、またはより効率的なコード生成パスの導入を示唆しています。これはコンパイラの安定性と性能に直接影響します。 - 文法解析の洗練:
go.y
の変更は、Go言語の文法定義をより堅牢にし、特に空のステートメント(;
のみの行)の扱いを明確にすることを目指していると考えられます。これにより、パーサーの挙動がより予測可能になり、将来的な言語機能の追加や文法拡張の基盤が強化されます。
前提知識の解説
このコミットを理解するためには、以下の概念に関する知識が役立ちます。
- Goコンパイラ (gc): Go言語の公式コンパイラであり、初期はC言語で書かれていました(現在はGo言語で書かれています)。
6g
は64-bitアーキテクチャ向けのコンパイラを指します。 - コード生成 (Code Generation): コンパイラのフェーズの一つで、抽象構文木 (AST) や中間表現 (IR) をターゲットマシンが実行できる機械語やアセンブリコードに変換するプロセスです。
cgen.c
はこのコード生成ロジックの一部を担っています。 - Yacc/Bison:
go.y
はYacc (Yet Another Compiler Compiler) またはそのGNU版であるBisonで記述された文法定義ファイルです。これらのツールは、指定された文法規則に基づいてソースコードを解析するパーサーを自動生成します。.y
ファイルは、言語の構文規則と、それらの規則が認識されたときに実行されるアクション(通常はASTの構築)を定義します。 - 抽象構文木 (AST): ソースコードの構造を木構造で表現したものです。コンパイラの多くのフェーズ(意味解析、最適化、コード生成)で利用されます。
- Ullman Number (ウルマン数): コンパイラ最適化の分野で用いられる概念で、式を評価するために必要な最小限のレジスタ数を表します。Ullman数は、式の各ノードに対して再帰的に計算され、レジスタ割り当て戦略の決定に役立ちます。
UINF
(Ullman Infinity) は、非常に複雑な式や、特定のレジスタ割り当て戦略では扱いきれない式を示すために使われることがあります。 - レジスタ割り当て (Register Allocation): コンパイラ最適化の一つで、プログラムの変数をCPUの高速なレジスタに割り当てるプロセスです。効率的なレジスタ割り当ては、生成されるコードの実行速度に大きく影響します。
技術的詳細
src/cmd/6g/cgen.c
の変更
このファイルは、Goコンパイラ(6g
)のコードジェネレータの一部です。
-
コメントアウトされたコードの削除:
OINDEXPTRSTR
,OINDEXSTR
,OSLICESTR
,OSLICEPTRSTR
(文字列/スライスインデックス操作)ODOTMETH
,ODOTINTER
(メソッド呼び出し/インターフェース関連) これらのコードブロックは、以前のGo言語の設計や実装の名残である可能性があり、現在は使用されていないか、より洗練された方法で処理されているため削除されました。これはコードベースのデッドコードを削減し、保守性を高めるための一般的なプラクティスです。
-
agen
関数の改善:agen
関数は、アドレスを生成するためのコードを生成します。lno = dynlineno;
とdynlineno = n->lineno;
の追加は、現在の行番号を保存し、ノードn
の行番号をdynlineno
に設定することで、コード生成中に発生する可能性のある診断メッセージ(エラーや警告)が、より正確なソースコードの行を参照するようにします。goto ret;
の導入は、if(n->addable)
ブロック内で早期リターンする際に、dynlineno = lno;
を実行するためのものです。これにより、関数の終了時に元のdynlineno
が確実に復元され、後続のコード生成に影響を与えないようになります。nl = n->left;
とnr = n->right;
の初期化が、switch
文の外部に移動されました。これにより、これらのポインタが常に利用可能になり、コードの重複が減り、可読性が向上します。
-
bgen
関数の改善:bgen
関数は、ブール式(条件分岐)のコードを生成します。nl = n->left;
とnr = n->right;
の初期化が、switch
文の外部に移動されました。これはagen
と同様の理由です。ullman >= UINF
の特殊なハンドリング: この変更は最も技術的に重要です。ullman
は式の複雑度を示す値であり、UINF
(Ullman Infinity) は非常に複雑な式、またはレジスタ割り当てが困難な式を示すために使われることがあります。 追加されたコードブロックは、比較演算(OCMP
)において、右オペランド (nr
) のUllman数がUINF
以上である場合に特殊な処理を行います。nr
の値を一時的なノードtmp
に格納します。これは、nr
が複雑な式であり、その評価結果を一時的に保持する必要があるためです。nl
(左オペランド) のコードを生成し、結果をレジスタn1
に格納します。tmp
(右オペランドの評価結果) のコードを生成し、結果をレジスタn2
に格納します。gins(optoas(OCMP, nr->type), &n1, &n2);
で、n1
とn2
の内容を比較するアセンブリ命令を生成します。patch(gbranch(a, nr->type), to);
で、比較結果に基づいて条件分岐命令を生成し、ジャンプ先をパッチします。 このロジックは、コンパイラが複雑な式を評価する際に、レジスタの枯渇を防ぎ、正しい評価順序を保証するためのものです。特に、右オペランドが非常に複雑で、その評価が左オペランドの評価に影響を与えたり、レジスタを過剰に消費したりする場合に、一時的な格納を介することで問題を回避します。
src/cmd/gc/go.y
の変更
このファイルはGo言語の文法定義です。
-
空のステートメントの許可:
Astmt:
ルールに| ';'
が追加されました。これにより、セミコロン単独で構成される空のステートメントが有効な構文として認識されるようになりました。これは、C言語やJavaなどの多くの言語で許容されている一般的な構文です。{ $$ = N; }
は、この空のステートメントがAST上で特別なノードを生成せず、単に無視されることを意味します(N
はnilノードを意味することが多い)。
-
Astmt_list_r
ルールの整理:- 既存の
| Astmt_list_r ';'
と| ';'
ルールが削除されました。これは、新しいAstmt: | ';'
ルールによって空のステートメントが直接Astmt
として扱われるようになったため、リスト内で個別に処理する必要がなくなったためです。 | Astmt_list_r Cstmt
と| Astmt_list_r Bstmt
の順序が入れ替えられました。これは、文法規則の曖昧さを解消したり、パーサーの効率を改善したりするための微調整である可能性があります。Astmt
,Bstmt
,Cstmt
はGo言語の異なる種類のステートメント(例:単純なステートメント、ブロックステートメントなど)を表す内部的な分類であると考えられます。
- 既存の
コアとなるコードの変更箇所
src/cmd/6g/cgen.c
--- a/src/cmd/6g/cgen.c
+++ b/src/cmd/6g/cgen.c
@@ -127,62 +127,6 @@ cgen(Node *n, Node *res)
regfree(&n1);
break;
-// case OINDEXPTRSTR:
-// case OINDEXSTR:
-// case OSLICESTR:
-// case OSLICEPTRSTR:
-// case ODOTMETH:
-// case ODOTINTER:
-// (上記に該当する大量のコメントアウトされたコードが削除)
-
case OS2I:
case OI2I:
case OI2S:
@@ -210,11 +154,6 @@ cgen(Node *n, Node *res)
fatal("cgen: OLEN: unknown type %lT", nl->type);
break;
-// case ODOTMETH:
-// case ODOTINTER:
-// (上記に該当するコメントアウトされたコードが削除)
-
case OADDR:
agen(nl, res);
break;
@@ -238,6 +177,7 @@ cgen(Node *n, Node *res)
case ODIV:
cgen_div(n->op, nl, nr, res);
break;
+\
case OLSH:
case ORSH:
cgen_shift(n->op, nl, nr, res);
@@ -287,7 +227,7 @@ agen(Node *n, Node *res)
{
Node *nl, *nr;
Node n1, n2, n3, tmp;
-\tulong w;\n+\tulong w, lno;\n \n if(n == N || n->type == T)
return;
if(!isptr[res->type->etype])
fatal("agen: not tptr: %T", res->type);
@@ -295,14 +235,21 @@ agen(Node *n, Node *res)
+\tlno = dynlineno;\n+\tif(n->op != ONAME)\n+\t\tdynlineno = n->lineno;\t// for diagnostics\n+\n if(n->addable) {
regalloc(&n1, types[tptr], res);
gins(ALEAQ, n, &n1);
gmove(&n1, res);
regfree(&n1);
-\t\treturn;\n+\t\tgoto ret;\n }
+\tnl = n->left;\n+\tnr = n->right;\n+\n switch(n->op) {
default:
fatal("agen: unknown op %N", n);
@@ -317,8 +264,6 @@ agen(Node *n, Node *res)
// \t\tbreak;\n
case OINDEXPTR:
-\t\tnl = n->left;\n-\t\tnr = n->right;\n \t\tw = n->type->width;
\tif(nr->addable)
\t\tgoto iprad;
@@ -347,8 +292,6 @@ agen(Node *n, Node *res)
// case OINDREG:\n
case OINDEX:
-\t\tnl = n->left;\n-\t\tnr = n->right;\n \t\tw = n->type->width;
\tif(nr->addable)
\t\tgoto irad;
@@ -395,7 +338,6 @@ agen(Node *n, Node *res)
// \t\tbreak;\n
case ODOT:
-\t\tnl = n->left;\n \t\tt = nl->type;
\tagen(nl, res);
\tif(n->xoffset != 0) {
gins(AADDL, nodintconst(n->xoffset), res);
@@ -405,7 +347,6 @@ agen(Node *n, Node *res)
break;
case ODOTPTR:
-\t\tnl = n->left;\n \t\tt = nl->type;
\tif(!isptr[t->etype])
\t\tfatal("agen: not ptr %N", n);
\tagen(nl, res);
@@ -416,6 +357,9 @@ agen(Node *n, Node *res)
}
break;
}\n+\n+ret:\n+\tdynlineno = lno;\n }\n
vlong
@@ -443,7 +387,7 @@ bgen(Node *n, int true, Prog *to)
long lno;\n int et, a;
Node *nl, *nr, *r;
-\tNode n1, n2;\n+\tNode n1, n2, tmp;\n \n if(n == N)
return;
if(n->op != ONAME)
dynlineno = n->lineno; // for diagnostics
@@ -451,6 +395,9 @@ bgen(Node *n, int true, Prog *to)
+\tnl = n->left;\n+\tnr = n->right;\n+\n if(n->type == T) {
convlit(n, types[TBOOL]);
if(n->type == T)
return;
@@ -558,8 +505,32 @@ bgen(Node *n, int true, Prog *to)
\tnl = nr;
\tnr = r;
}\n+\n \t\ta = optoas(a, nr->type);\n \n+\t\tif(nr->ullman >= UINF) {\n+\t\t\tregalloc(&n1, nr->type, N);\n+\t\t\tcgen(nr, &n1);\n+\n+\t\t\ttempname(&tmp, nr->type);\n+\t\t\tgmove(&n1, &tmp);\n+\t\t\tregfree(&n1);\n+\t\t\t\n+\t\t\tregalloc(&n1, nl->type, N);\n+\t\t\tcgen(nl, &n1);\n+\n+\t\t\tregalloc(&n2, nr->type, &n2);\n+\t\t\tcgen(&tmp, &n2);\n+\n+\t\t\tgins(optoas(OCMP, nr->type), &n1, &n2);\n+\t\t\tpatch(gbranch(a, nr->type), to);\n+\n+\t\t\tregfree(&n1);\n+\t\t\tregfree(&n2);\n+\t\t\tbreak;\n+\t\t}\n+\n+\n \t\tregalloc(&n1, nl->type, N);\n \t\tcgen(nl, &n1);\n```
### `src/cmd/gc/go.y`
```diff
--- a/src/cmd/gc/go.y
+++ b/src/cmd/gc/go.y
@@ -1065,6 +1065,10 @@ arg_type_list_r:
Astmt:
complex_stmt
| compound_stmt
+|\t\';\'
+\t{\n+\t\t$$ = N;\n+\t}\n
/*
* need semi in front NO
@@ -1091,11 +1095,6 @@ Astmt_list_r:
\t$$ = nod(OLIST, $1, $2);\n }\n |\tBstmt_list_r \';\'
-|\tAstmt_list_r \';\'
-|\t\';\'
-\t{\n-\t\t$$ = N;\n-\t}\n
/*
* statement list that need semi in back YES
@@ -1107,11 +1106,11 @@ Bstmt_list_r:
\t$$ = nod(OLIST, $1, $2);\n }\n-|\tAstmt_list_r Cstmt\n+|\tAstmt_list_r Bstmt\n {\n \t$$ = nod(OLIST, $1, $2);\n }\n-|\tAstmt_list_r Bstmt\n+|\tAstmt_list_r Cstmt\n {\n \t$$ = nod(OLIST, $1, $2);\n }\n```
## コアとなるコードの解説
### `src/cmd/6g/cgen.c`
* **コメントアウトされたコードの削除**: 以前のGoコンパイラのコードベースには、開発中に試行されたり、設計変更によって不要になったりしたコードがコメントアウトされた状態で残されていました。これらを削除することで、コードの肥大化を防ぎ、現在の実装に集中しやすくなります。
* **`agen` 関数の `dynlineno` 処理**:
* `dynlineno` は、コンパイラが現在処理しているソースコードの行番号を追跡するためのグローバル変数(またはそれに準ずるもの)です。
* `lno = dynlineno;` で現在の行番号を一時変数 `lno` に保存します。
* `dynlineno = n->lineno;` で、現在コード生成を行っているASTノード `n` に関連付けられた行番号を `dynlineno` に設定します。これにより、このノードの処理中に発生するエラーや警告が、ソースコードの正しい位置を指すようになります。
* `goto ret;` と `ret: dynlineno = lno;` の組み合わせは、`agen` 関数が途中で終了する場合でも、関数のエントリで保存した元の `dynlineno` を確実に復元するためのイディオムです。これにより、`agen` の呼び出し元が正しい行番号コンテキストで処理を続行できます。
* **`bgen` 関数の `ullman >= UINF` 処理**:
* このブロックは、比較演算の右オペランド (`nr`) が非常に複雑な場合に発動します。
* `regalloc(&n1, nr->type, N); cgen(nr, &n1);` は、まず右オペランド `nr` のコードを生成し、その結果を一時レジスタ `n1` に格納します。
* `tempname(&tmp, nr->type); gmove(&n1, &tmp); regfree(&n1);` は、`n1` の内容をさらに一時的なメモリ位置 (`tmp`) に移動し、`n1` レジスタを解放します。これは、`nr` の評価が多くのレジスタを必要とし、かつその結果がすぐに必要でない場合に、レジスタの再利用を可能にするための戦略です。
* その後、左オペランド `nl` のコードを生成し、その結果をレジスタ `n1` に格納します。
* そして、一時メモリに格納しておいた右オペランドの評価結果をレジスタ `n2` にロードし直します (`cgen(&tmp, &n2);`)。
* 最後に、`n1` と `n2` の内容を比較し、条件分岐命令を生成します。
この一連の処理は、複雑な式(特にUllman数が高いもの)の評価において、レジスタの競合を避け、正しい評価順序を保証するためのレジスタスピル(レジスタの内容をメモリに一時的に退避させること)の一種と解釈できます。
### `src/cmd/gc/go.y`
* **空のステートメントの導入**: Go言語の文法において、セミコロン単独のステートメント(例: `func main() { ; ; fmt.Println("Hello") }`)が正式に許可されました。これは、コードの整形や、特定のコード生成ツールからの出力との互換性を高めるために有用な場合があります。`$$ = N;` は、このステートメントがASTに影響を与えないことを示します。
* **文法規則の整理**: `Astmt_list_r` から冗長なルールが削除され、`Astmt`, `Bstmt`, `Cstmt` 間の関係がより明確に定義されました。これは、パーサーの効率性や、将来的な文法拡張の容易さを考慮した文法設計の改善です。
## 関連リンク
* Go言語の初期開発に関する情報: [https://go.dev/doc/history](https://go.dev/doc/history)
* コンパイラのコード生成に関する一般的な情報: [https://en.wikipedia.org/wiki/Code_generation_(compiler)](https://en.wikipedia.org/wiki/Code_generation_(compiler))
* Yacc/Bisonに関する情報: [https://www.gnu.org/software/bison/manual/](https://www.gnu.org/software/bison/manual/)
* Ullman Numberに関する情報 (コンパイラ最適化の文脈で): [https://en.wikipedia.org/wiki/Ullman%27s_algorithm](https://en.wikipedia.org/wiki/Ullman%27s_algorithm)
## 参考にした情報源リンク
* Go言語の公式ドキュメント
* コンパイラ設計に関する一般的な教科書(例: Dragon Book)
* Goコンパイラのソースコード(`src/cmd/6g/cgen.c`, `src/cmd/gc/go.y`)
* Ullman numbers in compiler optimization - Stack Overflow (一般的な概念理解のため)
* Go言語の初期のコミット履歴 (GitHub)