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

[インデックス 1622] ファイルの概要

このコミットは、Goコンパイラの内部処理に関するもので、具体的にはsrc/cmd/gc/dcl.csrc/cmd/gc/sysimport.cの2つのファイルを変更しています。

  • src/cmd/gc/dcl.c: Goコンパイラの宣言処理(declaration processing)を担当する部分です。ここでは、関数の初期化処理に関連するロジックが変更されています。
  • src/cmd/gc/sysimport.c: sysパッケージとunsafeパッケージのインポートに関する情報(コンパイラが内部的に使用するプロトタイプ宣言など)を定義しているファイルです。

コミット

do not generate Init proto for sys and unsafe.

R=ken
OCL=24455
CL=24455

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/aab26a52487861b53c89102f248d6cff0f88a583

元コミット内容

do not generate Init proto for sys and unsafe.

変更の背景

このコミットは、Goコンパイラ(gc)のビルドプロセスにおける最適化と正確性の向上を目的としています。Go言語には、パッケージの初期化時に自動的に実行されるinit関数という特別な関数があります。コンパイラは、これらのinit関数のプロトタイプ(宣言)を生成し、パッケージのロード時に適切に呼び出されるように管理します。

しかし、sysパッケージとunsafeパッケージはGo言語のランタイムやコンパイラ自体が内部的に使用する特殊なパッケージであり、通常のGoプログラムから直接インポートして使用するものではありません(unsafeパッケージは例外的にユーザーコードから使用されることがありますが、そのinit関数の扱いは特殊です)。

Goコンパイラのビルド過程において、sys.gounsafe.goといったファイルがコンパイルされる際、コンパイラはこれらのファイルが一時的に「PACKAGE」という名前のパッケージとして扱われることがあります。この段階で、通常のパッケージと同様にInit関数のプロトタイプが生成されてしまうと、それは不要な処理であり、場合によってはコンパイラの内部的な整合性に問題を引き起こす可能性がありました。

このコミットは、sysおよびunsafeパッケージに対して不要なInitプロトタイプが生成されるのを防ぐことで、コンパイラのビルドプロセスをより効率的かつ堅牢にするための修正です。

前提知識の解説

このコミットを理解するためには、以下のGo言語およびGoコンパイラに関する前提知識が必要です。

  1. Goコンパイラ (gc): Go言語の公式コンパイラであり、src/cmd/gcディレクトリにそのソースコードが存在します。Goのソースコードを機械語に変換する役割を担います。
  2. init関数: Go言語の各パッケージは、func init() { ... }という形式のinit関数を持つことができます。この関数は、パッケージがインポートされ、そのパッケージ内の変数がすべて初期化された後に、自動的に実行されます。これは、パッケージ固有の初期化ロジック(例: グローバル変数の設定、リソースの初期化など)を実行するために使用されます。
  3. sysパッケージ: Go言語の内部パッケージであり、Goランタイムがシステムコールや低レベルな操作を行うために使用します。通常のGoプログラムから直接インポートして使用することは想定されていません。
  4. unsafeパッケージ: Go言語の標準ライブラリの一部ですが、その名の通り「安全でない」操作(例: ポインタ演算、任意の型へのポインタ変換)を可能にするパッケージです。Goの型安全性をバイパスするため、注意して使用する必要があります。このパッケージも、Goランタイムや特定の低レベルなライブラリで利用されます。
  5. プロトタイプ (Prototype): プログラミング言語の文脈では、関数や変数の「宣言」を指します。コンパイラは、コードを生成する前に、使用される関数や変数の型、引数、戻り値などの情報を把握しておく必要があります。これをプロトタイプ宣言と呼びます。
  6. コンパイラのビルドプロセス: Goコンパイラ自体もGo言語で書かれており、自己ホスト型(self-hosting)です。つまり、Goコンパイラをビルドするためには、既存のGoコンパイラが必要です。このビルドプロセス中には、コンパイラが自身の内部パッケージ(sysunsafeなど)をコンパイルする特殊なフェーズが存在します。このフェーズでは、一時的なパッケージ名が使用されることがあります。
  7. fninit関数 (in dcl.c): src/cmd/gc/dcl.c内のfninit関数は、Goコンパイラがinit関数に関連する処理を行うための内部関数です。具体的には、init関数のプロトタイプを生成したり、その呼び出しをスケジュールしたりする役割を担っています。
  8. sysimport.c: このファイルは、Goコンパイラがsysunsafeといった特殊なパッケージの内部的な定義(関数シグネチャなど)を認識するために使用する文字列定数を含んでいます。これらは、Goのソースコードとしてではなく、コンパイラが直接解釈する形式で記述されています。

技術的詳細

このコミットの技術的な核心は、Goコンパイラの内部的なinit関数プロトタイプ生成ロジックの修正にあります。

Goコンパイラは、ソースコードを解析する際に、各パッケージのinit関数を識別し、そのプロトタイプ(内部的な表現)を生成します。このプロトタイプは、最終的な実行可能ファイルを生成する際に、init関数が適切に呼び出されるようにするために必要です。

しかし、sysパッケージとunsafeパッケージは、Goの通常のパッケージとは異なる特殊な性質を持っています。これらはGoランタイムの非常に低レベルな部分を構成しており、そのinit関数は通常のGoプログラムのinit関数とは異なる方法で扱われるか、あるいは全くinit関数を持たないことが期待されます。

問題は、コンパイラが自身のビルドプロセス中にsys.gounsafe.goをコンパイルする際、一時的にこれらのファイルが「PACKAGE」という仮のパッケージ名で処理される点にありました。この「PACKAGE」という名前は、コンパイラが内部的に使用するプレースホルダーであり、実際のパッケージ名(sysunsafe)が確定する前の段階で使われます。

src/cmd/gc/dcl.c内のfninit関数は、この「PACKAGE」という仮のパッケージ名を持つファイルに対しても、通常のinit関数のプロトタイプ生成ロジックを適用しようとしていました。これは、sysunsafeパッケージのinit関数が、コンパイラが期待する形式で存在しないため、不要なプロトタイプが生成されたり、コンパイラの内部状態が不整合になったりする原因となっていました。

また、src/cmd/gc/sysimport.cには、sys.Init·sysunsafe.Init·unsafeといった、コンパイラが内部的に認識するInit関数のプロトタイプ文字列がハードコードされていました。これらは、おそらく過去のコンパイラのバージョンで必要とされていたか、あるいは誤って含まれていたものです。これらのプロトタイプは、実際のsysunsafeパッケージの動作とは一致せず、混乱を招く可能性がありました。

このコミットは、以下の2つの変更によってこの問題を解決しています。

  1. dcl.cにおける早期リターン: fninit関数内で、現在のパッケージ名が「PACKAGE」である場合に、initプロトタイプの生成処理をスキップするように変更しました。これにより、コンパイラのビルド中にsys.gounsafe.goが一時的に「PACKAGE」として扱われる際に、不要なinitプロトタイプが生成されるのを防ぎます。
  2. sysimport.cからの不要なプロトタイプ削除: sysimport.cから、sys.Init·sysunsafe.Init·unsafeという文字列のプロトタイプ宣言を削除しました。これにより、コンパイラがこれらの特殊なパッケージに対して、存在しないInit関数を誤って認識することを防ぎます。

これらの変更により、Goコンパイラはsysおよびunsafeパッケージをより正確に、かつ効率的に処理できるようになり、コンパイラ自身のビルドの堅牢性が向上します。

コアとなるコードの変更箇所

src/cmd/gc/dcl.c

--- a/src/cmd/gc/dcl.c
+++ b/src/cmd/gc/dcl.c
@@ -1050,6 +1050,11 @@ fninit(Node *n)
 	uint32 h;
 	Sym *s;
 
+	if(strcmp(package, "PACKAGE") == 0) {
+		// sys.go or unsafe.go during compiler build
+		return;
+	}
+
 	r = N;
 
 	// (1)

src/cmd/gc/sysimport.c

--- a/src/cmd/gc/sysimport.c
+++ b/src/cmd/gc/sysimport.c
@@ -59,12 +59,10 @@ char *sysimport =
 	"func sys.Goexit ()\\n"
 	"func sys.Exit (? int)\\n"
 	"func sys.Caller (n int) (pc uint64, file string, line int, ok bool)\\n"
-"	\"func sys.Init·sys ()\\n\"
 	"\\n"
 	"$$\\n";
 char *unsafeimport =
 	"package unsafe\\n"
 	"type unsafe.Pointer *any\\n"
-"	\"func unsafe.Init·unsafe ()\\n\"
 	"\\n"
 	"$$\\n";

コアとなるコードの解説

src/cmd/gc/dcl.cの変更

fninit関数は、Goコンパイラがinit関数を処理する際に呼び出される内部関数です。追加されたコードは以下の通りです。

	if(strcmp(package, "PACKAGE") == 0) {
		// sys.go or unsafe.go during compiler build
		return;
	}
  • strcmp(package, "PACKAGE") == 0: これは、現在のコンパイル対象のパッケージ名が文字列「PACKAGE」と一致するかどうかをチェックしています。
  • // sys.go or unsafe.go during compiler build: このコメントが示すように、「PACKAGE」という名前は、Goコンパイラが自身のビルド中にsys.gounsafe.goといった特殊なファイルをコンパイルする際に一時的に使用する内部的なプレースホルダーです。
  • return;: もしパッケージ名が「PACKAGE」であれば、fninit関数の残りの処理(init関数のプロトタイプ生成など)をスキップして、すぐにリターンします。

この変更により、sysunsafeといった特殊なパッケージがコンパイラのビルド中に一時的な名前で処理される際に、不要なinit関数のプロトタイプが生成されるのを防ぎます。これにより、コンパイラの内部的な整合性が保たれ、不必要な処理が削減されます。

src/cmd/gc/sysimport.cの変更

このファイルは、Goコンパイラがsysおよびunsafeパッケージの内部的な定義を認識するために使用する文字列定数を含んでいます。変更内容は、以下の2行の削除です。

  • "func sys.Init·sys ()\\n"
  • "func unsafe.Init·unsafe ()\\n"

これらの行は、それぞれsysパッケージとunsafeパッケージにInit·sysおよびInit·unsafeという名前のinit関数が存在するというプロトタイプ宣言をコンパイラに伝えていました。しかし、これらの関数は実際には存在しないか、あるいは通常のinit関数とは異なる方法で扱われるべきものです。

これらの不要なプロトタイプ宣言を削除することで、コンパイラがsysおよびunsafeパッケージのinit関数に関して誤った仮定をすることを防ぎます。これにより、コンパイラの内部的な定義が実際のパッケージの動作と一致し、より正確なコンパイルが可能になります。

関連リンク

参考にした情報源リンク

  • Go言語のinit関数に関する一般的な情報源(例: Go by Example, A Tour of Goなど)
  • Goコンパイラの内部構造に関する一般的な情報源(例: Goのコンパイラ設計に関するブログ記事や論文など)
  • strcmp関数のC言語における動作に関する情報
  • Go言語のsysパッケージに関する情報(主にGoのソースコード内のコメントや関連する議論)
  • GitHubのGoリポジトリにおける関連するコミットやIssueの議論(もしあれば)
  • Go言語のコンパイラがどのようにパッケージを処理するかに関する一般的な知識
  • Go言語のビルドプロセスに関する情報