[インデックス 18212] ファイルの概要
このコミットは、Goコンパイラ(cmd/gc
)におけるバグ修正を目的としています。具体的には、goto
文がエクスポートデータに書き込まれる際に発生していたノード破損の問題に対処しています。この修正により、goto
文を含むインライン化された関数が正しく処理されるようになります。
コミット
commit f739dae7db61e748c1a23e1fae32274e5431bbd2
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date: Fri Jan 10 01:33:24 2014 +0100
cmd/gc: mark OGOTO as a statement for formatters.
Nodes of goto statements were corrupted when written
to export data.
Fixes #7023.
R=rsc, dave, minux.ma
CC=golang-codereviews
https://golang.org/cl/46190043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f739dae7db61e748c1a23e1fae32274e5431bbd2
元コミット内容
cmd/gc: mark OGOTO as a statement for formatters.
Nodes of goto statements were corrupted when written to export data.
Fixes #7023.
変更の背景
このコミットは、Goコンパイラにおける特定のバグ、Issue 7023を修正するために行われました。このバグは、goto
文を含むコードがコンパイルされ、その情報がエクスポートデータとして書き出される際に発生していました。具体的には、goto
文を表す抽象構文木(AST)のノードが、エクスポートデータに書き込まれる際に破損するという問題です。
この問題は、特にインライン化された関数内でgoto
文が使用された場合に顕在化しました。Goコンパイラはパフォーマンス最適化のために関数をインライン化することがありますが、このプロセスとエクスポートデータの生成が組み合わさることで、goto
文のノードが正しくシリアライズ・デシリアライズされず、結果としてコンパイルエラーや不正なコード生成を引き起こす可能性がありました。
エクスポートデータは、Goのパッケージシステムにおいて非常に重要な役割を果たします。あるパッケージが別のパッケージから参照される際、参照されるパッケージの公開された型、関数、変数などの情報はエクスポートデータとして保存され、参照するパッケージがその情報を利用してコンパイルを進めます。このエクスポートデータが破損していると、依存関係にあるパッケージのコンパイルが失敗したり、誤ったコードが生成されたりする原因となります。
したがって、このコミットはGoコンパイラの堅牢性と正確性を向上させ、goto
文を含むコード、特にインライン化された関数がGo言語の仕様通りに正しくコンパイルされることを保証するために必要でした。
前提知識の解説
このコミットの理解には、以下のGoコンパイラおよび関連技術の概念に関する知識が役立ちます。
-
Goコンパイラ (
cmd/gc
):- Go言語の公式コンパイラであり、Goソースコードを機械語に変換する役割を担います。
- コンパイルプロセスは、字句解析、構文解析、意味解析、中間コード生成、最適化、コード生成などの段階を経て行われます。
cmd/gc
は、Goのツールチェインの一部であり、go build
コマンドなどで内部的に呼び出されます。
-
抽象構文木 (AST - Abstract Syntax Tree):
- ソースコードの構文構造を木構造で表現したものです。コンパイラの構文解析フェーズで生成されます。
- 各ノードは、変数宣言、関数呼び出し、制御構造(
if
,for
,goto
など)といったソースコードの要素に対応します。 - コンパイラはASTを操作して、意味解析、最適化、コード生成を行います。
-
エクスポートデータ (Export Data):
- Goのパッケージシステムにおいて、あるパッケージが他のパッケージから利用される際に、そのパッケージの公開された(エクスポートされた)エンティティ(型、関数、変数など)の情報を記述したメタデータです。
- コンパイル時に生成され、通常は
.a
(アーカイブ)ファイルや.o
(オブジェクト)ファイルに埋め込まれます。 - 他のパッケージがそのパッケージをインポートする際、コンパイラはこのエクスポートデータを読み込み、型チェックやシンボル解決を行います。
- エクスポートデータは、Goの高速なコンパイルとモジュール性を支える重要な要素です。
-
goto
文:- Go言語における制御フロー文の一つで、プログラムの実行を指定されたラベルにジャンプさせます。
- Goでは
goto
の使用は推奨されませんが、特定の状況(例えば、ネストされたループからの脱出など)で合法的に使用できます。 - コンパイラは
goto
文もASTノードとして表現し、適切に処理する必要があります。
-
インライン化 (Inlining):
- コンパイラ最適化の一種で、関数呼び出しをその関数の本体のコードで直接置き換えることです。
- これにより、関数呼び出しのオーバーヘッド(スタックフレームのセットアップ、引数の渡し方など)が削減され、プログラムの実行速度が向上する可能性があります。
- Goコンパイラは、特定の条件(関数のサイズ、複雑さなど)を満たす関数を自動的にインライン化します。
- インライン化されたコードは、呼び出し元のコンテキストに統合されるため、その中の
goto
文も呼び出し元のASTの一部として扱われることになります。
-
fmt.c
(Goコンパイラのフォーマッタ関連コード):- Goコンパイラのソースコードの一部で、ASTノードのフォーマットや、エクスポートデータへの書き出しに関連する処理を担っています。
opprec
のような配列は、ASTノードの種類(OFOR
,OIF
,OGOTO
など)に対応する演算子の優先順位や、それが文であるか式であるかといったメタデータを定義するために使用されます。
これらの概念を理解することで、goto
文のノードがエクスポートデータに書き込まれる際に破損するという問題が、コンパイラのAST処理、エクスポートデータ生成、そしてインライン化の相互作用によってどのように発生したのか、そしてその修正がなぜfmt.c
の変更を伴うのかが明確になります。
技術的詳細
このコミットの技術的詳細は、Goコンパイラの内部、特にASTノードの表現とエクスポートデータの生成メカニズムに深く関わっています。
問題の核心は、goto
文を表すASTノードであるOGOTO
が、コンパイラの内部処理において「文(statement)」として正しく認識されていなかった点にあります。Goコンパイラのsrc/cmd/gc/fmt.c
ファイルには、opprec
という配列が存在します。この配列は、Go言語の各演算子(ASTノードの種類)が持つ優先順位や、それが式(expression)なのか文(statement)なのかといったメタデータを定義するために使用されます。
opprec
配列において、値-1
は通常、そのノードが文であることを示します。文は、それ自体が値を生成せず、プログラムの制御フローや状態を変更するものです(例: if
, for
, return
など)。一方、式は値を生成します(例: a + b
, foo()
)。
元のコードでは、OGOTO
(goto
文のASTノード)がopprec
配列に明示的に含まれていませんでした。これは、コンパイラがOGOTO
ノードをエクスポートデータに書き出す際に、そのノードが文であるという適切なメタデータを持っていなかったことを意味します。エクスポートデータは、Goのパッケージ間で型情報や関数シグネチャなどを共有するために使用されますが、このデータ構造はASTノードの正確な表現に依存しています。
goto
文を含む関数がインライン化されると、そのgoto
ノードは呼び出し元の関数のASTに組み込まれます。このインライン化されたgoto
ノードがエクスポートデータに書き出される際、OGOTO
が文として適切にマークされていないために、エクスポートデータのフォーマット処理が誤動作し、ノードのデータが破損するという現象が発生しました。破損したエクスポートデータは、そのパッケージをインポートする他のパッケージのコンパイルを失敗させる原因となります。
このコミットの修正は非常にシンプルですが、効果的です。src/cmd/gc/fmt.c
のopprec
配列に[OGOTO] = -1,
というエントリを追加することで、OGOTO
ノードが明示的に文としてマークされるようになりました。これにより、コンパイラがエクスポートデータを生成する際に、goto
文のノードを正しくシリアライズできるようになり、データの破損が防止されます。
この修正は、Goコンパイラの内部的なAST処理とエクスポートデータ生成の正確性を保証し、goto
文を含むコード、特にインライン化された関数がGo言語の仕様通りに正しくコンパイルされることを確実にします。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更は、src/cmd/gc/fmt.c
ファイルの一箇所のみです。
--- a/src/cmd/gc/fmt.c
+++ b/src/cmd/gc/fmt.c
@@ -1039,6 +1039,7 @@ static int opprec[] = {
[OEMPTY] = -1,\n [OFALL] = -1,\n [OFOR] = -1,\n+\t[OGOTO] = -1,\n [OIF] = -1,\n [OLABEL] = -1,\n [OPROC] = -1,
また、この修正の検証のために、以下のテストファイルが追加されています。
test/fixedbugs/issue7023.dir/a.go
test/fixedbugs/issue7023.dir/b.go
test/fixedbugs/issue7023.go
コアとなるコードの解説
変更されたコードは、Goコンパイラのsrc/cmd/gc/fmt.c
ファイル内のopprec
という静的配列です。
static int opprec[] = { ... };
このopprec
配列は、Goコンパイラが抽象構文木(AST)のノードを処理する際に使用するメタデータテーブルの一部です。配列のインデックスはASTノードの種類(OFOR
, OIF
, OGOTO
など、Goコンパイラ内部で定義される定数)に対応し、その値はノードの特性(例えば、演算子の優先順位や、それが文であるか式であるか)を示します。
このコミットで追加された行は以下の通りです。
[OGOTO] = -1,
OGOTO
: これはGoコンパイラ内部で定義されている定数で、goto
文を表すASTノードの種類です。-1
:opprec
配列において、値-1
は、対応するASTノードが「文(statement)」であることを示します。文は、それ自体が値を生成せず、プログラムの制御フローや状態を変更するものです。
この変更の目的と効果:
以前のopprec
配列にはOGOTO
のエントリがありませんでした。そのため、コンパイラがgoto
文のASTノードを処理し、特にエクスポートデータに書き出す際に、そのノードが文であるという適切なメタデータが欠けていました。このメタデータの欠如が、エクスポートデータのフォーマット処理におけるノード破損の原因となっていました。
[OGOTO] = -1,
を追加することで、コンパイラはOGOTO
ノードを明示的に文として認識するようになります。これにより、エクスポートデータを生成する際に、goto
文のノードが正しくシリアライズされ、データの破損が防止されます。結果として、goto
文を含むコード、特にインライン化された関数がGo言語の仕様通りに正しくコンパイルされ、パッケージ間の依存関係も健全に保たれるようになります。
追加されたテストファイルは、この修正が正しく機能することを確認するためのものです。issue7023.dir/a.go
とb.go
は、goto
文を含む関数(a.Foo
)を定義し、それを別のパッケージ(b
)から参照するシナリオを模倣しています。issue7023.go
は、compiledir
ディレクティブを使用して、これらのパッケージが正しくコンパイルされることを検証するテストです。これにより、インライン化とエクスポートデータの問題が解決されたことが確認されます。
関連リンク
- Go Issue 7023: https://github.com/golang/go/issues/7023
- Go CL 46190043: https://golang.org/cl/46190043
参考にした情報源リンク
- Go言語の公式ドキュメント
- Goコンパイラのソースコード (
src/cmd/gc
ディレクトリ) - GoのIssueトラッカー (Issue 7023の議論)
- Goのコードレビューシステム (CL 46190043の議論)
- 抽象構文木 (AST) に関する一般的なコンパイラ理論の資料
- Goのエクスポートデータに関する技術記事やドキュメント
- Goのインライン化に関するコンパイラ最適化の資料
goto
文に関するGo言語の仕様