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

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

このコミットは、Go言語のビルドツールである go コマンドに gccgo コンパイラをサポートするための変更を導入しています。具体的には、GC=gccgo 環境変数を設定することで、go build, go test, go run, go install などのサブコマンドが gccgo を利用して動作するように拡張されました。これにより、標準のGoディストリビューションと連携しつつ、gccgo の利用が可能になります。

コミット

commit 45a8fae996700a40bc671bc48e78931d277dee0a
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date:   Fri Jan 27 17:05:51 2012 -0500

    go: introduce support for "go build" with gccgo.
    
    The use of gccgo is triggered by GC=gccgo in environment. It
    still needs the standard distribution to behave properly, but
    allows using the test, build, run, install subcommands with
    gccgo.
    
    R=rsc, iant, fullung
    CC=golang-dev, remy
    https://golang.org/cl/5562045

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

https://github.com/golang/go/commit/45a8fae996700a40bc671bc48e78931d277dee0a

元コミット内容

go: introduce support for "go build" with gccgo.

The use of gccgo is triggered by GC=gccgo in environment. It still needs the standard distribution to behave properly, but allows using the test, build, run, install subcommands with gccgo.

変更の背景

Go言語には、公式のコンパイラである gc (Go Compiler) と、GCC (GNU Compiler Collection) のフロントエンドとして実装された gccgo の2つの主要なコンパイラが存在します。gc はGoチームによって開発され、Go言語の最新機能に迅速に対応し、高速なコンパイルが特徴です。一方、gccgo はGCCの豊富な最適化機能を活用できるため、生成されるバイナリの実行速度が向上する可能性があります。また、GCCがサポートする幅広いアーキテクチャに対応できるという利点もあります。

このコミットが作成された2012年当時、go コマンドは主に gc ツールチェイン(6g, 6a, 6l など)を前提として設計されていました。しかし、gccgo の利用を望む開発者も存在し、go コマンドが gccgo を透過的に利用できるようにすることは、Goエコシステムの柔軟性を高め、より多様な開発環境やデプロイメントシナリオに対応するために重要でした。

この変更の背景には、以下の目的があったと考えられます。

  1. gccgo の統合: go コマンドが gccgo をビルドツールチェインとして認識し、利用できるようにする。
  2. 開発体験の向上: gccgo を使用する際にも、go build, go test, go run, go install といった標準的な go コマンドのサブコマンドをそのまま利用できるようにし、開発者の利便性を高める。
  3. 柔軟なビルド環境の提供: ユーザーが環境変数 GC を設定するだけで、使用するコンパイラを簡単に切り替えられるようにする。

前提知識の解説

Go言語のビルドプロセス

Go言語のビルドプロセスは、go build コマンドを中心に非常にシンプルかつ効率的に設計されています。

  1. 依存関係の解析: go build はまず、プロジェクトのソースコードを解析し、必要なパッケージの依存関係を特定します。Go Modules(Go 1.11以降)が導入される前は、GOPATH というワークスペースの概念に基づいて依存関係が解決されていました。
  2. コンパイル: 各Goソースファイルは、選択されたGoコンパイラ(通常は gc)によって機械語にコンパイルされ、オブジェクトファイルが生成されます。
  3. リンク: コンパイルされたオブジェクトファイルと、必要なライブラリ(標準ライブラリやサードパーティライブラリ)がリンカによって結合され、単一の実行可能バイナリが生成されます。Goのバイナリは静的にリンクされるため、実行時に外部の依存関係を必要としない自己完結型であることが特徴です。
  4. クロスコンパイル: Goは、異なるOSやアーキテクチャ向けのバイナリを簡単に生成できるクロスコンパイル機能を標準でサポートしています。

Goツールチェイン (6g, 6a, 6l など)

Go 1.5より前のバージョンでは、Goのビルドプロセスは、特定のアーキテクチャに対応する個別のツール(コンパイラ、アセンブラ、リンカ)を直接呼び出すことで行われていました。これらは通常、GOHOSTARCH 環境変数に基づいて命名されていました。

  • 6g: amd64 (64-bit) アーキテクチャ向けのGoコンパイラ。Goソースコードをオブジェクトファイルにコンパイルします。
  • 6a: amd64 アーキテクチャ向けのアセンブラ。Goのアセンブリ言語(.s ファイル)をオブジェクトファイルに変換します。
  • 6l: amd64 アーキテクチャ向けのリンカ。オブジェクトファイルを結合して実行可能バイナリを生成します。
  • 同様に、5g, 5a, 5larm アーキテクチャ向け、8g, 8a, 8l386 アーキテクチャ向けでした。

Go 1.5以降では、go build コマンドがこれらのツールを内部で自動的に呼び出すようになり、開発者が直接これらのツールを操作する必要はほとんどなくなりました。しかし、このコミットが作成された時点では、これらのツールがビルドプロセスの中心的な役割を担っていました。

gcgccgo

  • gc (Go Compiler):

    • Go言語の公式コンパイラであり、Goチームによって開発されています。
    • Go言語の最新の仕様や機能に最も早く対応します。
    • コンパイル速度が非常に高速です。
    • 生成されるバイナリは、gccgo に比べて最適化の面で劣る場合がありますが、多くの場合十分なパフォーマンスを発揮します。
    • Goのランタイムと密接に統合されています。
  • gccgo:

    • GCC (GNU Compiler Collection) のフロントエンドとして実装されたGoコンパイラです。
    • GCCの強力な最適化パイプラインを利用できるため、生成されるバイナリの実行時パフォーマンスが gc よりも優れる場合があります。
    • GCCがサポートする幅広いCPUアーキテクチャ(SPARC, MIPS, PowerPCなど)に対応できます。
    • GCCのリリースサイクルに依存するため、Go言語の最新機能への対応が gc よりも遅れる傾向があります。
    • バイナリサイズが小さくなる可能性があります(libgo ライブラリを動的にリンクする場合)。
    • gc とは異なり、エスケープ解析を行いません。

GC 環境変数

このコミットでは、GC という新しい環境変数が導入されています。この変数は、go コマンドがどのGoコンパイラツールチェインを使用するかを決定するために利用されます。

  • GC が設定されていない、または GC=go の場合: デフォルトの gc ツールチェインが使用されます。
  • GC=gccgo の場合: gccgo ツールチェインが使用されます。

これは、Goのガベージコレクタ (GC) の動作を制御する GOGC 環境変数とは異なる、ビルドツールチェインを選択するための環境変数です。

技術的詳細

このコミットの主要な技術的変更点は、Goのビルドプロセスにおけるコンパイラ、アセンブラ、リンカ、パッカーといったツールチェインの呼び出しを抽象化し、gcgccgo の両方に対応できるようにした点です。

具体的には、以下の変更が行われました。

  1. toolchain インターフェースの導入: src/cmd/go/build.gotoolchain という新しいインターフェースが定義されました。このインターフェースは、Goのビルドプロセスで必要となる主要な操作(コンパイル、アセンブル、リンク、パッケージング、パッケージパスの決定)を抽象化します。

    type toolchain interface {
        // gc runs the compiler in a specific directory on a set of files
        // and returns the name of the generated output file. 
        gc(b *builder, p *Package, obj string, importArgs []string, gofiles []string) (ofile string, err error)
        // cc runs the toolchain's C compiler in a directory on a C file
        // to produce an output file.
        cc(b *builder, p *Package, objdir, ofile, cfile string) error
        // asm runs the assembler in a specific directory on a specific file
        // to generate the named output file. 
        asm(b *builder, p *Package, obj, ofile, sfile string) error
        // pkgpath creates the appropriate destination path for a package file.
        pkgpath(basedir string, p *Package) string
        // pack runs the archive packer in a specific directory to create
        // an archive from a set of object files.
        // typically it is run in the object directory.
        pack(b *builder, p *Package, objDir, afile string, ofiles []string) error
        // ld runs the linker to create a package starting at mainpkg.
        ld(b *builder, p *Package, out string, allactions []*action, mainpkg string, ofiles []string) error
    }
    
  2. goToolchaingccgoToolchain の実装: toolchain インターフェースを実装する2つの構造体 goToolchaingccgoToolchain が導入されました。

    • goToolchain: 従来の 6g, 6a, 6l などのGoツールチェインのコマンドを呼び出す実装を提供します。
    • gccgoToolchain: gccgo, gcc, ar などのGCC関連のコマンドを呼び出す実装を提供します。特に、pkgpath メソッドでは gccgo のライブラリ命名規則に合わせて lib プレフィックスを追加するロジックが含まれています。また、ld メソッドでは gccgo が明示的なパッケージ依存関係のリンクを必要とするため、allactions からすべての依存パッケージのアーカイブファイルとCgoのリンカフラグを収集して渡すように変更されています。
  3. buildToolchain 変数による動的なツールチェイン選択: src/cmd/go/build.goinit 関数内で、GC 環境変数の値に基づいて buildToolchain グローバル変数が goToolchain または gccgoToolchain のいずれかに初期化されるようになりました。

    var buildToolchain toolchain
    
    func init() {
        if os.Getenv("GC") == "gccgo" {
            buildToolchain = gccgoToolchain{}
        } else {
            buildToolchain = goToolchain{}
        }
    }
    
  4. 既存のビルドロジックの変更: src/cmd/go/build.go 内の builder 構造体のメソッド(gc, cc, asm, gopack, ld など)が、直接ツールチェインコマンドを呼び出す代わりに、buildToolchain インターフェースの対応するメソッドを呼び出すように変更されました。これにより、ビルドロジック自体はツールチェインの実装に依存せず、抽象化されたインターフェースを通じて操作されるようになりました。

  5. パッケージパスとターゲットパスの調整: src/cmd/go/pkg.gosrc/cmd/go/test.go において、パッケージのターゲットパス (p.target) やテスト関連のオブジェクトファイルのパスが、buildToolchain.pkgpath メソッドを通じて決定されるように変更されました。これにより、gccgo が期待するライブラリの命名規則(例: lib<package>.a)に適切に対応できるようになりました。

  6. Cgoのサポート: src/cmd/go/build.gocgo 関数において、gccgo ツールチェインが選択されている場合に cgo コマンドに -gccgo フラグを渡すように変更されました。これにより、Cgoの処理も gccgo に対応できるようになります。

これらの変更により、go コマンドは、ユーザーが GC=gccgo を設定するだけで、内部的に gccgo ツールチェインを透過的に利用してGoプログラムをビルド、テスト、実行、インストールできるようになりました。

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

このコミットによる主要なコード変更は以下の3つのファイルに集中しています。

  1. src/cmd/go/build.go:

    • toolchain インターフェースの定義と、goToolchain および gccgoToolchain 構造体の実装が追加されました。
    • init 関数内で GC 環境変数に基づいて buildToolchain が初期化されるロジックが追加されました。
    • builder 構造体の gc, cc, asm, gopack (→ pack), ld メソッドが、直接ツールコマンドを呼び出す代わりに、buildToolchain インターフェースの対応するメソッドを呼び出すように変更されました。
    • includeArgs 関数で、gccgo の場合にパッケージディレクトリのパスに gccgo サブディレクトリを追加するロジックが追加されました。
    • cgo 関数で、gccgo ツールチェインの場合に cgo コマンドに -gccgo フラグを渡すように変更されました。
  2. src/cmd/go/pkg.go:

    • scanPackage 関数内で、非標準パッケージのターゲットパス (p.target) が buildToolchain.pkgpath を使用して決定されるように変更されました。これにより、gccgo の命名規則に対応できるようになります。
  3. src/cmd/go/test.go:

    • テスト関連のオブジェクトファイルパス (ptestObj) が buildToolchain.pkgpath を使用して決定されるように変更されました。

コアとなるコードの解説

toolchain インターフェース

このインターフェースは、Goのビルドプロセスにおける各ステップ(コンパイル、アセンブル、リンク、パッケージング、パス解決)を抽象化します。これにより、異なるコンパイラツールチェイン(gcgccgo)がこれらの操作をそれぞれ独自の方法で実装できるようになります。

type toolchain interface {
    gc(b *builder, p *Package, obj string, importArgs []string, gofiles []string) (ofile string, err error)
    cc(b *builder, p *Package, objdir, ofile, cfile string) error
    asm(b *builder, p *Package, obj, ofile, sfile string) error
    pkgpath(basedir string, p *Package) string
    pack(b *builder, p *Package, objDir, afile string, ofiles []string) error
    ld(b *builder, p *Package, out string, allactions []*action, mainpkg string, ofiles []string) error
}
  • gc: Goソースファイルをコンパイルします。
  • cc: Cソースファイルをコンパイルします(Cgo用)。
  • asm: アセンブリファイルをアセンブルします。
  • pkgpath: パッケージのアーカイブファイルのパスを生成します。gccgo の場合、lib プレフィックスが付くなどの違いを吸収します。
  • pack: オブジェクトファイルをアーカイブ(ライブラリ)にまとめます。
  • ld: リンクを行い、実行可能バイナリまたは共有ライブラリを生成します。

goToolchain の実装

goToolchain は、従来のGoツールチェイン(6g, 6a, 6l など)を呼び出す実装を提供します。

  • gc: b.arch + "g" (例: 6g) コマンドを呼び出します。runtime パッケージのコンパイル時には -+ フラグを追加します。
  • asm: b.arch + "a" (例: 6a) コマンドを呼び出します。
  • pkgpath: filepath.Join(basedir, filepath.FromSlash(p.ImportPath+".a")) の形式でパッケージアーカイブパスを生成します。
  • pack: gopack コマンドを呼び出します。
  • ld: b.arch + "l" (例: 6l) コマンドを呼び出します。
  • cc: b.arch + "c" (例: 6c) コマンドを呼び出します。

gccgoToolchain の実装

gccgoToolchain は、gccgo, gcc, ar などのGCC関連のコマンドを呼び出す実装を提供します。

  • gc: gccgo コマンドを呼び出します。パッケージ名に応じて -fgo-prefix フラグを追加します。
  • asm: gccgo コマンドを呼び出します。
  • pkgpath: filepath.Join(filepath.Dir(afile), "lib"+filepath.Base(afile)) の形式でパッケージアーカイブパスを生成します。これは gccgo が生成するライブラリファイル名が lib で始まる慣例に対応するためです。
  • pack: ar コマンドを呼び出します。
  • ld: gccgo コマンドを呼び出します。gccgo は明示的な依存関係のリンクを必要とするため、allactions からすべてのパッケージのアーカイブファイルとCgoのリンカフラグを収集し、-Wl,-(-Wl,-) で囲んで渡します。
  • cc: gcc コマンドを呼び出します。

buildToolchain の動的な選択

src/cmd/go/build.goinit 関数で、os.Getenv("GC") の値に基づいて buildToolchaingoToolchain または gccgoToolchain のいずれかに設定されます。これにより、go コマンドの実行時に使用するツールチェインが動的に切り替わります。

この設計により、go コマンドのビルドロジックは、特定のツールチェインの実装に依存することなく、抽象化されたインターフェースを通じて動作するようになり、将来的に他のツールチェインをサポートする際にも拡張が容易になります。

関連リンク

参考にした情報源リンク

  • gccgo の概要:
    • https://go.dev/doc/install/gccgo
    • https://stackoverflow.com/questions/10079493/what-is-the-difference-between-gc-and-gccgo
  • Goビルドプロセス:
    • https://codedamn.com/news/go/go-build-process
    • https://www.digitalocean.com/community/tutorials/how-to-build-go-applications-for-multiple-platforms-on-ubuntu-20-04
  • Goツールチェイン (6g, 6a, 6l):
    • https://www.cheney.net/articles/go-toolchain-6g-6a-6l
    • https://www.cs.cmu.edu/~410/doc/go-toolchain.pdf
  • Go環境変数 (GOGC など):
    • https://englyk.com/go-garbage-collector-gogc-gomemlimit/
    • https://dev.to/joshuabaker/understanding-go-garbage-collection-and-gogc-300k
    • https://go.dev/doc/diagnostics#godebug