Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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コンパイラおよび関連技術の概念に関する知識が役立ちます。

  1. Goコンパイラ (cmd/gc):

    • Go言語の公式コンパイラであり、Goソースコードを機械語に変換する役割を担います。
    • コンパイルプロセスは、字句解析、構文解析、意味解析、中間コード生成、最適化、コード生成などの段階を経て行われます。
    • cmd/gcは、Goのツールチェインの一部であり、go buildコマンドなどで内部的に呼び出されます。
  2. 抽象構文木 (AST - Abstract Syntax Tree):

    • ソースコードの構文構造を木構造で表現したものです。コンパイラの構文解析フェーズで生成されます。
    • 各ノードは、変数宣言、関数呼び出し、制御構造(if, for, gotoなど)といったソースコードの要素に対応します。
    • コンパイラはASTを操作して、意味解析、最適化、コード生成を行います。
  3. エクスポートデータ (Export Data):

    • Goのパッケージシステムにおいて、あるパッケージが他のパッケージから利用される際に、そのパッケージの公開された(エクスポートされた)エンティティ(型、関数、変数など)の情報を記述したメタデータです。
    • コンパイル時に生成され、通常は.a(アーカイブ)ファイルや.o(オブジェクト)ファイルに埋め込まれます。
    • 他のパッケージがそのパッケージをインポートする際、コンパイラはこのエクスポートデータを読み込み、型チェックやシンボル解決を行います。
    • エクスポートデータは、Goの高速なコンパイルとモジュール性を支える重要な要素です。
  4. goto:

    • Go言語における制御フロー文の一つで、プログラムの実行を指定されたラベルにジャンプさせます。
    • Goではgotoの使用は推奨されませんが、特定の状況(例えば、ネストされたループからの脱出など)で合法的に使用できます。
    • コンパイラはgoto文もASTノードとして表現し、適切に処理する必要があります。
  5. インライン化 (Inlining):

    • コンパイラ最適化の一種で、関数呼び出しをその関数の本体のコードで直接置き換えることです。
    • これにより、関数呼び出しのオーバーヘッド(スタックフレームのセットアップ、引数の渡し方など)が削減され、プログラムの実行速度が向上する可能性があります。
    • Goコンパイラは、特定の条件(関数のサイズ、複雑さなど)を満たす関数を自動的にインライン化します。
    • インライン化されたコードは、呼び出し元のコンテキストに統合されるため、その中のgoto文も呼び出し元のASTの一部として扱われることになります。
  6. 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())。

元のコードでは、OGOTOgoto文のASTノード)がopprec配列に明示的に含まれていませんでした。これは、コンパイラがOGOTOノードをエクスポートデータに書き出す際に、そのノードが文であるという適切なメタデータを持っていなかったことを意味します。エクスポートデータは、Goのパッケージ間で型情報や関数シグネチャなどを共有するために使用されますが、このデータ構造はASTノードの正確な表現に依存しています。

goto文を含む関数がインライン化されると、そのgotoノードは呼び出し元の関数のASTに組み込まれます。このインライン化されたgotoノードがエクスポートデータに書き出される際、OGOTOが文として適切にマークされていないために、エクスポートデータのフォーマット処理が誤動作し、ノードのデータが破損するという現象が発生しました。破損したエクスポートデータは、そのパッケージをインポートする他のパッケージのコンパイルを失敗させる原因となります。

このコミットの修正は非常にシンプルですが、効果的です。src/cmd/gc/fmt.copprec配列に[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.gob.goは、goto文を含む関数(a.Foo)を定義し、それを別のパッケージ(b)から参照するシナリオを模倣しています。issue7023.goは、compiledirディレクティブを使用して、これらのパッケージが正しくコンパイルされることを検証するテストです。これにより、インライン化とエクスポートデータの問題が解決されたことが確認されます。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Goコンパイラのソースコード (src/cmd/gc ディレクトリ)
  • GoのIssueトラッカー (Issue 7023の議論)
  • Goのコードレビューシステム (CL 46190043の議論)
  • 抽象構文木 (AST) に関する一般的なコンパイラ理論の資料
  • Goのエクスポートデータに関する技術記事やドキュメント
  • Goのインライン化に関するコンパイラ最適化の資料
  • goto文に関するGo言語の仕様