[インデックス 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
ファイル)に変換される必要がありました。この二段階のプロセスにはいくつかの非効率性がありました。
- 余分なコピー処理: オブジェクトファイルが生成された後、それをアーカイブにまとめるために再度ファイルシステム上でのコピー処理が発生していました。これはI/Oオーバーヘッドを伴い、ビルド時間を増加させる要因となっていました。
- エクスポートデータの重複: 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アーカイブファイルを生成するように動作を変更することです。
具体的には、以下の点が変更されています。
gc
における-pack
フラグの追加:src/cmd/gc/go.h
にwritearchive
という新しいグローバル変数が追加され、この変数が-pack
フラグの有無を制御します。src/cmd/gc/lex.c
のmain
関数内で、コマンドライン引数のパース処理に-pack
フラグが追加され、writearchive
変数が設定されるようになります。
gc
による直接アーカイブ生成:src/cmd/gc/obj.c
のdumpobj
関数が大幅に修正されています。この関数はコンパイル結果をオブジェクトファイルに出力する役割を担っています。writearchive
が真の場合、dumpobj
はまず!<arch>\n
というマジックバイトで始まるar
アーカイブヘッダを書き込みます。- 次に、パッケージのエクスポートデータ(
dumpexport()
によって生成される)を__.PKGDEF
という名前のar
メンバーとしてアーカイブに書き込みます。この際、formathdr
関数が新しいar
メンバーのヘッダを適切にフォーマットします。 - その後、実際のコンパイル済みGoコード(オブジェクトデータ)を
_go_.{arch}
(例:_go_.6
)という名前のar
メンバーとしてアーカイブに書き込みます。 - これにより、従来の
.5/.6/.8
ファイルとgo tool pack
によるアーカイブ化という二段階のプロセスが、コンパイラによる単一のステップに統合されます。
- エクスポートデータの重複排除:
- 従来のプロセスでは、エクスポートデータは中間オブジェクトファイル内にも存在し、さらに
go tool pack
によって生成されるアーカイブの__.PKGDEF
セクションにも格納されていました。 -pack
フラグが有効な場合、src/cmd/gc/export.c
のdumpexport
関数におけるエクスポートデータの出力フォーマットがわずかに変更されています。具体的には、$$ // exports
や$$ // local types
といったコメント行が削除され、より簡潔な形式になっています。これは、エクスポートデータが.5/.6/.8
ファイル内に直接埋め込まれるのではなく、__.PKGDEF
としてアーカイブ内に一度だけ格納されることを前提とした変更です。
- 従来のプロセスでは、エクスポートデータは中間オブジェクトファイル内にも存在し、さらに
ld
におけるアーカイブフォーマットの簡素化:src/cmd/ld/go.c
とsrc/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
関数が追加されました。formathdr
はar
アーカイブのメンバーヘッダをフォーマットするためのユーティリティ関数です。dumpobj
関数が大幅に修正されました。writearchive
が真の場合、!<arch>\n
というマジックバイトと、初期の空のar
ヘッダが書き込まれます。dumpexport()
の呼び出し後、Bflush
とBseek
を使って、書き込まれたエクスポートデータ(__.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 Gerrit Change-ID: https://golang.org/cl/42880043
参考にした情報源リンク
- Go言語のビルドプロセスに関する一般的な情報(Goの公式ドキュメントやブログ記事など)
ar
アーカイブフォーマットに関する情報(Unix/Linuxのマニュアルページなど)- Goの内部構造に関するコミュニティの議論や資料