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

[インデックス 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言語の開発チームは以下の課題を抱えていました:

  1. CGOパッケージの複雑性: runtime/cgoパッケージは、GoとC言語の世界を橋渡しする重要なパッケージでしたが、その複雑性からビルドシステムでは「複雑すぎてビルドできない」として扱われていました。

  2. フラグの一貫性: go testコマンドでは-vフラグが既に「テスト結果の詳細表示」として使用されていたため、新しいgoコマンドでは別の文字を使用する必要がありました。

  3. ビルドシステムの統一: MakeベースのビルドシステムからGoツールチェーンへの移行過程で、一貫性のあるビルドシステムが必要でした。

前提知識の解説

CGO(C言語との統合)について

CGOは、GoプログラムからC言語のコードを呼び出すためのツールです。Go言語の重要な機能の一つで、以下のような特徴があります:

  1. 特別なimport "C": Go言語ではimport "C"という特別な疑似パッケージを使用してC言語のコードにアクセスします。
  2. コンパイル時の統合: CGOは、Go言語のコンパイル時にC言語のコードをコンパイルし、リンクする仕組みを提供します。
  3. 型の相互変換: Go言語のデータ型とC言語のデータ型の間での変換機能を提供します。

runtime/cgoパッケージの役割

runtime/cgoパッケージは、CGOの機能を実現するためのランタイムサポートを提供します:

  1. ランタイムサポート: CGOツールによって生成されたコードのランタイムサポートを提供
  2. 世界の橋渡し: GoとC言語の世界の間でのデータのやり取りを管理
  3. ポインタ管理: Go言語のガベージコレクションとC言語のメモリ管理の統合

Goコマンドフラグの歴史

2011年当時、Go言語の開発チームはコマンドラインツールの統一化を進めていました:

  1. -v フラグ: go testでは「詳細なテスト結果の表示」として使用
  2. -x フラグ: 新しく導入された「実行されるコマンドの表示」フラグ
  3. シェルとの一貫性: 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.Sgcc_amd64.S
  • darwin_386.cgcc_darwin_386.c
  • util.cgcc_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
}

関連リンク

参考にした情報源リンク