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

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

このコミットは、Goコンパイラ(cmd/gc、具体的には5g, 6g, 8gといったアーキテクチャ固有のコンパイラ)に-packフラグを実装するものです。このフラグを導入することで、コンパイラが直接Goアーカイブ(.aファイル)を生成できるようになり、従来必要だったgo tool packコマンドによる中間オブジェクトファイル(.5, .6, .8ファイル)からアーカイブへの変換ステップが不要になります。これにより、ビルドプロセスの効率化と、エクスポートデータの重複保存の解消が図られています。また、リンカ(cmd/ld)におけるアーカイブフォーマットのわずかな簡素化も含まれています。

コミット

commit b02233402961621e1cd705d79dcf63387daadea2
Author: Russ Cox <rsc@golang.org>
Date:   Tue Dec 17 21:43:33 2013 -0500

    cmd/gc: implement -pack flag
    
    The -pack flag causes 5g, 6g, 8g to write a Go archive directly,
    instead of requiring the use of 'go tool pack' to convert the .5/.6/.8
    to .a format.
    
    Writing directly avoids the copy and also avoids having the
    export data stored twice in the archive (once in __.PKGDEF,
    once in .5/.6/.8).
    
    A separate CL will enable the use of this flag by cmd/go.
    
    Other build systems that do not know about -pack will be unaffected.
    
    The changes to cmd/ld handle a minor simplification to the format:
    an unused section is removed.
    
    R=iant, r
    CC=golang-dev
    https://golang.org/cl/42880043

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

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

元コミット内容

cmd/gc: implement -pack flag

The -pack flag causes 5g, 6g, 8g to write a Go archive directly,
instead of requiring the use of 'go tool pack' to convert the .5/.6/.8
to .a format.

Writing directly avoids the copy and also avoids having the
export data stored twice in the archive (once in __.PKGDEF,
once in .5/.6/.8).

A separate CL will enable the use of this flag by cmd/go.

Other build systems that do not know about -pack will be unaffected.

The changes to cmd/ld handle a minor simplification to the format:
an unused section is removed.

変更の背景

Goの初期のビルドプロセスでは、コンパイラ(5g, 6g, 8gなど)はまず中間オブジェクトファイル(.5, .6, .8といった拡張子を持つファイル)を生成していました。これらのオブジェクトファイルは、その後go tool packというユーティリティによってGoアーカイブ(.aファイル)に変換される必要がありました。この二段階のプロセスにはいくつかの非効率性がありました。

  1. 余分なコピー処理: オブジェクトファイルが生成された後、それをアーカイブにまとめるために再度ファイルシステム上でのコピー処理が発生していました。これはI/Oオーバーヘッドを伴い、ビルド時間を増加させる要因となっていました。
  2. エクスポートデータの重複: Goのパッケージは、他のパッケージから参照されるためのエクスポート情報(型、関数、変数などの定義)を持っています。このエクスポートデータは、中間オブジェクトファイル内にも存在し、さらにgo tool packによって生成されるGoアーカイブ内の__.PKGDEFセクションにも格納されていました。これにより、同じデータが二重に保存されることになり、ディスクスペースの無駄や、潜在的なデータ不整合のリスクがありました。

このコミットは、これらの問題を解決するために、コンパイラ自身が直接Goアーカイブを生成する機能(-packフラグ)を導入することで、ビルドプロセスの合理化と効率化を目指しています。

前提知識の解説

このコミットを理解するためには、Goのビルドシステムとファイルフォーマットに関するいくつかの前提知識が必要です。

  • Goコンパイラ (gc): Go言語のソースコードを機械語に変換するプログラムです。かつてはターゲットアーキテクチャごとに5g (ARM), 6g (x86-64), 8g (x86) のように命名されていましたが、現在ではgo tool compileとして統合されています。これらのコンパイラは、Goのソースコードを解析し、中間オブジェクトファイルを生成します。
  • Goリンカ (ld): コンパイラによって生成されたオブジェクトファイルやライブラリを結合し、実行可能なバイナリや共有ライブラリを作成するプログラムです。現在ではgo tool linkとして統合されています。
  • Goオブジェクトファイル (.5, .6, .8 / .o): Goコンパイラが生成する中間ファイルで、コンパイルされたGoコードの機械語命令やメタデータが含まれます。現在のGoでは、これらのファイルは通常.o拡張子を持ちます。
  • Goアーカイブ (.aファイル): 複数のGoオブジェクトファイルを一つにまとめたファイルです。Unix系のシステムにおける静的ライブラリ(.a)と同様のフォーマットを使用します。Goのパッケージは通常、このアーカイブ形式で配布され、他のGoプログラムからインポートされて利用されます。
  • go tool pack: Go 1.0の時代に存在したユーティリティで、Goオブジェクトファイル(.5, .6, .8)をGoアーカイブ(.a)に変換するために使用されました。このコミットによって、その役割がコンパイラに統合され、最終的には非推奨となりました。
  • __.PKGDEF: Goアーカイブファイル内に存在する特殊なセクションの一つです。このセクションには、そのアーカイブが含むGoパッケージのエクスポートデータ(パッケージが外部に公開する型、関数、変数などの情報)が格納されています。他のGoパッケージがこのパッケージをインポートする際に、このエクスポートデータを読み込んで型チェックやシンボル解決を行います。
  • __.GOSYMDEF: Goアーカイブファイル内に存在するもう一つの特殊なセクションで、Goのシンボル情報(関数名、変数名などとそれらのアドレスのマッピング)が含まれると考えられます。リンカがシンボル解決を行う際に利用されます。
  • ar (archive) フォーマット: Unix系のシステムで静的ライブラリ(.aファイル)を作成するために使われるファイルフォーマットです。複数のファイルを一つのアーカイブにまとめることができます。Goのアーカイブファイルもこのarフォーマットをベースにしています。

技術的詳細

このコミットの核となる変更は、Goコンパイラ(cmd/gc)に-packという新しいコマンドラインフラグを追加し、このフラグが指定された場合に、コンパイラが直接Goアーカイブファイルを生成するように動作を変更することです。

具体的には、以下の点が変更されています。

  1. gcにおける-packフラグの追加:
    • src/cmd/gc/go.hwritearchiveという新しいグローバル変数が追加され、この変数が-packフラグの有無を制御します。
    • src/cmd/gc/lex.cmain関数内で、コマンドライン引数のパース処理に-packフラグが追加され、writearchive変数が設定されるようになります。
  2. gcによる直接アーカイブ生成:
    • src/cmd/gc/obj.cdumpobj関数が大幅に修正されています。この関数はコンパイル結果をオブジェクトファイルに出力する役割を担っています。
    • writearchiveが真の場合、dumpobjはまず!<arch>\nというマジックバイトで始まるarアーカイブヘッダを書き込みます。
    • 次に、パッケージのエクスポートデータ(dumpexport()によって生成される)を__.PKGDEFという名前のarメンバーとしてアーカイブに書き込みます。この際、formathdr関数が新しいarメンバーのヘッダを適切にフォーマットします。
    • その後、実際のコンパイル済みGoコード(オブジェクトデータ)を_go_.{arch}(例: _go_.6)という名前のarメンバーとしてアーカイブに書き込みます。
    • これにより、従来の.5/.6/.8ファイルとgo tool packによるアーカイブ化という二段階のプロセスが、コンパイラによる単一のステップに統合されます。
  3. エクスポートデータの重複排除:
    • 従来のプロセスでは、エクスポートデータは中間オブジェクトファイル内にも存在し、さらにgo tool packによって生成されるアーカイブの__.PKGDEFセクションにも格納されていました。
    • -packフラグが有効な場合、src/cmd/gc/export.cdumpexport関数におけるエクスポートデータの出力フォーマットがわずかに変更されています。具体的には、$$ // exports$$ // local typesといったコメント行が削除され、より簡潔な形式になっています。これは、エクスポートデータが.5/.6/.8ファイル内に直接埋め込まれるのではなく、__.PKGDEFとしてアーカイブ内に一度だけ格納されることを前提とした変更です。
  4. ldにおけるアーカイブフォーマットの簡素化:
    • src/cmd/ld/go.csrc/cmd/ld/lib.cにおいて、リンカがGoアーカイブを読み込む際の処理が変更されています。
    • 特にsrc/cmd/ld/go.cでは、__.PKGDEFセクションの後に存在すると仮定されていた「ローカル型」に関するセクションの処理が削除されています。コミットメッセージにある「unused section is removed」とはこの部分を指していると考えられます。これは、gc-packフラグでアーカイブを生成する際に、このセクションが不要になったためです。
    • src/cmd/ld/lib.cでは、アーカイブ内の__.GOSYMDEF__.PKGDEFの読み込みロジックが調整され、__.GOSYMDEFがオプションであること、そして__.PKGDEFが常に存在することに対応しています。

この変更により、Goのビルドシステムはより効率的になり、中間ファイルの生成と変換のステップが削減され、ディスクI/Oとビルド時間の削減に貢献します。

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

このコミットで変更された主要なファイルと、その中でのコアとなる変更箇所は以下の通りです。

  • src/cmd/gc/export.c:

    • dumpexport関数内のBprint呼び出しで、エクスポートデータのヘッダ部分のフォーマットが変更されました。具体的には、"\\n$$ // exports\\n""\\n$$\\n"に、"\\n$$ // local types\\n\\n$$\\n""\\n$$\\n"にそれぞれ変更されています。これは、エクスポートデータが.5/.6/.8ファイル内に直接埋め込まれる際の冗長なコメントが削除されたことを示唆しています。
  • src/cmd/gc/go.h:

    • EXTERN int writearchive;という新しいグローバル変数の宣言が追加されました。この変数は、コンパイラが直接アーカイブを書き込むべきかどうかを制御します。
  • src/cmd/gc/lex.c:

    • main関数内で、コマンドライン引数のパース処理に-packフラグが追加されました。flagcount("pack", "write package file instead of object file", &writearchive);の行がそれにあたります。これにより、-packフラグが指定されるとwritearchive変数がインクリメントされます。
    • skiptopkgdef関数内のコメントが/* symbol table is first; skip it */から/* symbol table may be first; skip it */に変更され、__.GOSYMDEFが常に存在するとは限らないことを示唆しています。また、Bseekのロジックが変更され、__.GOSYMDEFが存在しない場合でも適切に__.PKGDEFをスキップできるように修正されています。
  • src/cmd/gc/obj.c:

    • ArhdrSize定数とformathdr関数が追加されました。formathdrarアーカイブのメンバーヘッダをフォーマットするためのユーティリティ関数です。
    • dumpobj関数が大幅に修正されました。
      • writearchiveが真の場合、!<arch>\nというマジックバイトと、初期の空のarヘッダが書き込まれます。
      • dumpexport()の呼び出し後、BflushBseekを使って、書き込まれたエクスポートデータ(__.PKGDEF)のサイズを計算し、そのarヘッダを更新するロジックが追加されました。
      • 同様に、コンパイルされたオブジェクトデータ(_go_.{arch})の書き込み後にも、そのarヘッダを更新するロジックが追加されました。これにより、コンパイラが直接arアーカイブを構築するようになりました。
  • src/cmd/ld/go.c:

    • ldpkg関数内で、__.PKGDEFセクションの後に存在すると仮定されていた「ローカル型」に関するセクションの読み込みロジックが完全に削除されました。これは、// local types begin where exports end.から始まる複数行のコードブロックが削除されたことによって確認できます。
  • src/cmd/ld/lib.c:

    • objfile関数内で、アーカイブ内の__.GOSYMDEF__.PKGDEFの処理ロジックが変更されました。
    • /* skip over __.GOSYMDEF */のコメントが/* skip over optional __.GOSYMDEF and process __.PKGDEF */に変更され、__.GOSYMDEFがオプションであることを明示しています。
    • if(strncmp(arhdr.name, symname, strlen(symname)))の条件がif(strncmp(arhdr.name, symname, strlen(symname)) == 0)に変更され、__.GOSYMDEFが存在する場合の処理がより明確になりました。
    • __.PKGDEFが見つからない場合の診断メッセージが"second entry not package header"から"cannot find package header"に変更されました。

コアとなるコードの解説

  • src/cmd/gc/obj.cにおけるアーカイブ生成ロジック:

    • formathdr関数は、arアーカイブの各メンバー(ファイル)のヘッダを生成します。このヘッダには、ファイル名、所有者ID、グループID、パーミッション、サイズなどが含まれます。Goのアーカイブでは、ファイル名として__.PKGDEF_go_.{arch}のような特殊な名前が使われます。
    • dumpobj関数内のif(writearchive)ブロックがこのコミットの心臓部です。
      • Bwrite(bout, "!<arch>\\n", 8);は、arアーカイブの開始を示すマジックバイトを書き込みます。
      • memset(arhdr, 0, sizeof arhdr); Bwrite(bout, arhdr, sizeof arhdr);は、アーカイブ全体のヘッダ(通常はシンボルテーブルのオフセットなどを含むが、ここでは初期化のために空で書き込まれる)のためのスペースを確保します。
      • startobj = Boffset(bout);は、最初のオブジェクト(__.PKGDEF)が書き込まれる開始オフセットを記録します。
      • dumpexport();は、パッケージのエクスポートデータを書き込みます。このデータは、__.PKGDEFメンバーの内容となります。
      • size = Boffset(bout) - startobj;__.PKGDEFの実際のサイズを計算し、Bseekでヘッダ位置に戻り、formathdrで適切なヘッダを作成して書き込みます。これにより、__.PKGDEFメンバーがアーカイブに正しく登録されます。
      • 同様のプロセスが、コンパイルされたGoコード(_go_.{arch}メンバー)に対しても繰り返されます。これにより、コンパイラが単一のステップで完全なGoアーカイブを生成できるようになります。
  • src/cmd/ld/go.cにおけるローカル型セクションの削除:

    • 削除されたコードは、__.PKGDEFの後に続く「ローカル型」のセクションを読み込むためのものでした。このセクションは、Goの内部的な型情報を含んでいた可能性があります。
    • このコードが削除されたということは、-packフラグによって生成される新しいアーカイブフォーマットでは、この「ローカル型」セクションがもはや存在しないか、あるいはその情報が別の方法で処理されるようになったことを意味します。これにより、リンカはより簡素化されたアーカイブフォーマットに対応できるようになります。
  • src/cmd/ld/lib.cにおけるアーカイブメンバーの読み込み順序の柔軟化:

    • objfile関数は、Goアーカイブファイルを開いてその内容を処理する役割を担っています。
    • 変更前は、アーカイブの最初のメンバーが__.GOSYMDEF、二番目のメンバーが__.PKGDEFであると厳密に仮定していました。
    • 変更後は、__.GOSYMDEFがオプションである可能性を考慮し、if(strncmp(arhdr.name, symname, strlen(symname)) == 0)という条件で__.GOSYMDEFが存在するかどうかを確認し、存在すればスキップし、存在しなければ次のメンバーに進むようにロジックが修正されました。これにより、リンカはより多様な(あるいは新しい)アーカイブ構造に対応できるようになります。

これらの変更は、Goのビルドシステムがより効率的で堅牢になるための重要なステップでした。

関連リンク

参考にした情報源リンク

  • Go言語のビルドプロセスに関する一般的な情報(Goの公式ドキュメントやブログ記事など)
  • arアーカイブフォーマットに関する情報(Unix/Linuxのマニュアルページなど)
  • Goの内部構造に関するコミュニティの議論や資料