[インデックス 10901] ファイルの概要
コミット
このコミットは、GoのコンパイラツールチェーンにおけるCGO(C言語との統合)機能の改善を行った重要な変更です。具体的には、これまで複雑すぎてビルドできなかったruntime/cgo
パッケージのビルドを可能にし、同時にgoコマンドの-v
フラグを-x
フラグに変更しました。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/54fb9940cf315546f43c724e670518acedfa185e
元コミット内容
commit 54fb9940cf315546f43c724e670518acedfa185e
Author: Russ Cox <rsc@golang.org>
Date: Tue Dec 20 14:25:23 2011 -0500
go: build runtime/cgo
Also rename -v to -x in the build and install commands,
to match the flag in go test (which we can't change
because -v is taken). Matches sh -x anyway.
R=r, iant, ality
CC=golang-dev
https://golang.org/cl/5504045
変更の背景
この変更は、Go 1.0リリースに向けた重要な準備作業の一環として行われました。2011年12月の時点で、Go言語の開発チームは以下の課題を抱えていました:
-
CGOパッケージの複雑性:
runtime/cgo
パッケージは、GoとC言語の世界を橋渡しする重要なパッケージでしたが、その複雑性からビルドシステムでは「複雑すぎてビルドできない」として扱われていました。 -
フラグの一貫性:
go test
コマンドでは-v
フラグが既に「テスト結果の詳細表示」として使用されていたため、新しいgoコマンドでは別の文字を使用する必要がありました。 -
ビルドシステムの統一: MakeベースのビルドシステムからGoツールチェーンへの移行過程で、一貫性のあるビルドシステムが必要でした。
前提知識の解説
CGO(C言語との統合)について
CGOは、GoプログラムからC言語のコードを呼び出すためのツールです。Go言語の重要な機能の一つで、以下のような特徴があります:
- 特別なimport "C": Go言語では
import "C"
という特別な疑似パッケージを使用してC言語のコードにアクセスします。 - コンパイル時の統合: CGOは、Go言語のコンパイル時にC言語のコードをコンパイルし、リンクする仕組みを提供します。
- 型の相互変換: Go言語のデータ型とC言語のデータ型の間での変換機能を提供します。
runtime/cgoパッケージの役割
runtime/cgo
パッケージは、CGOの機能を実現するためのランタイムサポートを提供します:
- ランタイムサポート: CGOツールによって生成されたコードのランタイムサポートを提供
- 世界の橋渡し: GoとC言語の世界の間でのデータのやり取りを管理
- ポインタ管理: Go言語のガベージコレクションとC言語のメモリ管理の統合
Goコマンドフラグの歴史
2011年当時、Go言語の開発チームはコマンドラインツールの統一化を進めていました:
- -v フラグ:
go test
では「詳細なテスト結果の表示」として使用 - -x フラグ: 新しく導入された「実行されるコマンドの表示」フラグ
- シェルとの一貫性:
sh -x
コマンドと同じ意味を持つフラグとして設計
技術的詳細
1. CGOファイルの分類システム
このコミットの最も重要な技術的変更は、CGOファイルの分類システムの導入です:
// runtime/cgoパッケージ専用の処理
if a.p.Standard && a.p.ImportPath == "runtime/cgo" {
filter := func(files, nongcc, gcc []string) ([]string, []string) {
for _, f := range files {
if strings.HasPrefix(f, "gcc_") {
gcc = append(gcc, f)
} else {
nongcc = append(nongcc, f)
}
}
return nongcc, gcc
}
cfiles, gccfiles = filter(cfiles, cfiles[:0], gccfiles)
sfiles, gccfiles = filter(sfiles, sfiles[:0], gccfiles)
}
この機能により、runtime/cgo
パッケージ内のファイルが以下のように分類されます:
- gcc_プレフィックス: GCCでコンパイルされるファイル
- その他のファイル: Goツールチェーンでコンパイルされるファイル
2. import_runtime_cgoフラグの追加
CGOツールに新しい-import_runtime_cgo
フラグが追加されました:
var importRuntimeCgo = flag.Bool("import_runtime_cgo", true, "import runtime/cgo in generated code")
この フラグにより、生成されるコードでruntime/cgo
パッケージをインポートするかどうかを制御できます。
3. No-opアクションの改善
ビルドシステムの効率化のため、No-op(何もしない)アクションの処理が改善されました:
// 以前: 専用のnop関数を使用
a.f = (*builder).nop
// 変更後: nilによるNo-opアクション
a := &action{} // f フィールドがnilの場合は何もしない
4. ファイル名の一貫性
runtime/cgo
パッケージ内のファイル名が統一されました:
amd64.S
→gcc_amd64.S
darwin_386.c
→gcc_darwin_386.c
util.c
→gcc_util.c
この変更により、どのファイルがGCCでコンパイルされるべきかが明確になります。
5. trigger.goファイルの追加
新しく追加されたtrigger.go
ファイルは、CGOパッケージであることを示すマーカーとして機能します:
package cgo
import "C"
この単純なファイルにより、Goビルドシステムがruntime/cgo
パッケージをCGOパッケージとして認識できるようになります。
コアとなるコードの変更箇所
1. src/cmd/go/build.go:212-220
// 以前のコード
case "runtime/cgo":
// Too complex - can't build.
a.f = (*builder).nop
return a
// 変更後
case "builtin", "unsafe":
// Fake packages - nothing to build.
return a
この変更により、runtime/cgo
パッケージの特別扱いが削除され、通常のビルドプロセスが適用されるようになりました。
2. src/cmd/go/build.go:274-300
// 新しいファイル分類ロジック
if a.p.Standard && a.p.ImportPath == "runtime/cgo" {
filter := func(files, nongcc, gcc []string) ([]string, []string) {
for _, f := range files {
if strings.HasPrefix(f, "gcc_") {
gcc = append(gcc, f)
} else {
nongcc = append(nongcc, f)
}
}
return nongcc, gcc
}
cfiles, gccfiles = filter(cfiles, cfiles[:0], gccfiles)
sfiles, gccfiles = filter(sfiles, sfiles[:0], gccfiles)
}
3. src/cmd/cgo/main.go:46
var importRuntimeCgo = flag.Bool("import_runtime_cgo", true, "import runtime/cgo in generated code")
4. src/cmd/cgo/out.go:59-65
if *importRuntimeCgo {
fmt.Fprintf(fm, "void crosscall2(void(*fn)(void*, int), void *a, int c) { }\n")
} else {
// If we're not importing runtime/cgo, we *are* runtime/cgo,
// which provides crosscall2. We just need a prototype.
fmt.Fprintf(fm, "void crosscall2(void(*fn)(void*, int), void *a, int c);")
}
コアとなるコードの解説
1. 循環インポート問題の解決
runtime/cgo
パッケージは、他のパッケージからインポートされると同時に、自身もCGOを使用するパッケージです。これにより循環インポートの問題が発生していました。
-import_runtime_cgo=false
フラグを使用することで、runtime/cgo
パッケージ自体のビルド時には自分自身をインポートしないようにできます。
2. コンパイラの使い分け
runtime/cgo
パッケージでは、ファイルによって異なるコンパイラを使用する必要があります:
- GCC: C言語の標準的な機能を使用するファイル(
gcc_
プレフィックス) - Goツールチェーン: Go言語のランタイムと密接に統合されたファイル
3. アクション最適化
ビルドシステムのアクションチェーンにおいて、実際に何もする必要がないアクションはf
フィールドをnil
にすることで効率化されました。
// 実行時のチェック
if a.f != nil {
if err := a.f(b, a); err != nil {
errorf("%s", err)
a.failed = true
}
}
4. 依存関係の正確な判定
allNop
関数により、依存関係のアクションがすべて不要な場合のみ、親のアクションも不要と判定されます:
func allNop(actions []*action) bool {
for _, a := range actions {
if a.f != nil {
return false
}
}
return true
}