[インデックス 11606] ファイルの概要
このコミットは、Go言語のビルドシステムの中核を担う cmd/dist
ツールに対する大規模な改修を含んでいます。主な目的は、Goのビルドプロセスをより堅牢、効率的、かつ保守しやすくすることです。具体的には、runtime
パッケージのファイル生成メカニズムの改善、並列ビルドのサポート、新しいコマンド(clean
, banner
, version
)の追加、そしてより洗練された引数解析の実装が行われています。また、Go言語のソースコードとC言語のコードを組み合わせた .goc
ファイルをC言語のファイルに変換する goc2c
ツールが cmd/dist
に統合されました。
コミット
- コミットハッシュ:
c6c00ed4827c68e3c041ec501e823a3b0aa07850
- Author: Russ Cox rsc@golang.org
- Date: Fri Feb 3 18:16:42 2012 -0500
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/c6c00ed4827c68e3c041ec501e823a3b0aa07850
元コミット内容
cmd/dist: generate files for package runtime
goc2c moves here.
parallel builds like old makefiles (-j4).
add clean command.
add banner command.
implement Go version check.
real argument parsing (same as 6g etc)
Windows changes will be a separate CL.
R=golang-dev, bradfitz, iant
CC=golang-dev
https://golang.org/cl/5622058
変更の背景
このコミットが行われた2012年2月は、Go言語がオープンソース化されてから約2年が経過し、急速に進化を遂げていた時期にあたります。初期のGoのビルドシステムは、伝統的なUnixの make
ツールに大きく依存していました。しかし、Go言語のクロスプラットフォーム対応や、より複雑なビルド要件に対応するためには、make
に代わる、よりGo言語の哲学に沿ったビルドツールの必要性が高まっていました。
cmd/dist
は、Go言語のコンパイラ、リンカ、標準ライブラリなど、Goツールチェイン自体をビルドするための内部ツールです。このコミット以前は、cmd/dist
の機能は限定的であり、ビルドプロセスの多くの部分が外部の make
コマンドに依存していました。この依存関係は、特にWindowsのような make
が標準ではない環境でのビルドを複雑にし、またビルドプロセスの柔軟性や拡張性を制限していました。
このコミットの背景には、以下の主要な動機があったと考えられます。
- ビルドプロセスのGoネイティブ化:
make
への依存を減らし、Goツールチェイン自身のビルドをGoのコードベース内で完結させることで、クロスプラットフォーム対応を強化し、ビルドシステムの移植性を高める。 - ビルド効率の向上: 並列ビルドのサポートを導入し、マルチコアプロセッサを最大限に活用することで、大規模なGoプロジェクト(特にGo自身のビルド)のビルド時間を短縮する。
- メンテナンス性の向上:
runtime
パッケージのような特殊なビルド要件を持つコンポーネントのファイル生成プロセスを体系化し、コードの重複を排除し、将来的な変更に対応しやすくする。 - ユーザーエクスペリエンスの改善:
clean
やbanner
といった新しいコマンドを追加することで、開発者がGoのビルド環境を管理しやすくする。また、より標準的な引数解析を導入することで、cmd/dist
の使い勝手を向上させる。 goc2c
の統合:goc2c
は、Goのランタイムの一部でC言語とGo言語のハイブリッドコードを扱うために使用される特殊なツールです。これをcmd/dist
に統合することで、ビルドシステム全体の一貫性を高め、依存関係を簡素化します。
これらの変更は、Go言語が成熟し、より大規模なプロジェクトや多様な環境での利用に対応するための基盤を固める上で不可欠なステップでした。
前提知識の解説
このコミットを理解するためには、以下の概念について理解しておく必要があります。
Go言語のビルドシステム (cmd/dist
)
Go言語のソースコードから実行可能なバイナリを生成するプロセスは、一般的なC/C++プロジェクトとは異なる独自のツールチェインを使用します。cmd/dist
は、Go言語のコンパイラ (gc
, 6g
, 8g
など)、アセンブラ (as
, 6a
, 8a
など)、リンカ (ld
, 6l
, 8l
など) といったツールチェイン自体をビルドするために使用される内部ツールです。
Goのビルドプロセスは、大きく分けて以下のステップを含みます。
- ソースコードの準備: Goのソースコード、アセンブリコード、Cコードなどが含まれます。
- コード生成: 特定のプラットフォームやアーキテクチャに依存するファイル(例:
runtime
パッケージのアセンブリコードのヘッダファイル、Goのバージョン情報ファイルなど)が自動生成されます。 - コンパイル: GoのソースコードはGoコンパイラによってオブジェクトファイルにコンパイルされ、CコードはCコンパイラによってコンパイルされます。
- アセンブル: アセンブリコードはアセンブラによってオブジェクトファイルに変換されます。
- リンク: 生成されたオブジェクトファイルとライブラリがリンカによって結合され、最終的な実行可能バイナリが生成されます。
cmd/dist
は、これらのステップをオーケストレーションし、Goツールチェイン全体を自己ホスト型でビルドできるようにします。
GOROOT
, GOOS
, GOARCH
Go言語のビルド環境を構成する重要な環境変数です。
GOROOT
: Goのインストールディレクトリのパスを示します。Goのソースコード、標準ライブラリ、ツールチェインなどがこのディレクトリに配置されます。GOOS
: ターゲットとなるオペレーティングシステム(例:linux
,windows
,darwin
(macOS))を示します。GOARCH
: ターゲットとなるCPUアーキテクチャ(例:amd64
,386
,arm
)を示します。
これらの変数は、クロスコンパイルや特定の環境向けのビルドを行う際に不可欠です。
.goc
ファイルと goc2c
Go言語の初期のランタイム(runtime
パッケージ)の一部は、C言語とGo言語のハイブリッドな形式で記述されていました。これは、Go言語がまだ成熟しておらず、低レベルなシステムプログラミングの要件をC言語の既存の機能で補う必要があったためです。
.goc
ファイルは、このようなハイブリッドコードを記述するための特殊なファイル形式です。Goの構文とCの構文が混在しており、goc2c
ツールによって純粋なC言語のソースファイルに変換されます。この変換プロセスにより、Cコンパイラでコンパイルできるようになります。
このコミットでは、goc2c
が cmd/dist
の一部として組み込まれ、Goのビルドプロセス内でシームレスに実行されるようになりました。
並列ビルド (-j
オプション)
ソフトウェアのビルドは、多くの独立したコンパイルやリンクのタスクから構成されることがよくあります。これらのタスクの中には、互いに依存しないものも多く、それらを同時に実行することでビルド時間を大幅に短縮できます。
伝統的な make
ツールでは、-j
オプション(例: make -j4
)を使用して、指定された数のジョブ(並列タスク)を同時に実行するよう指示できます。このコミットでは、cmd/dist
が同様の並列ビルド機能を内部的にサポートするようになり、Goツールチェイン自身のビルドが高速化されました。
引数解析
コマンドラインツールでは、ユーザーが指定する引数を適切に解析する必要があります。このコミットでは、cmd/dist
の引数解析が改善され、Goの他のツール(例: 6g
)と同様の、より標準的で堅牢な方法で引数を処理できるようになりました。これは、新しい arg.h
ファイルの追加によって実現されています。
技術的詳細
このコミットは、cmd/dist
の内部構造に多岐にわたる変更を加えています。
1. runtime
パッケージのファイル生成の体系化
src/cmd/dist/buildruntime.c
という新しいファイルが追加され、runtime
パッケージが必要とする特定のGoソースファイルやCヘッダファイルを自動生成するロジックがカプセル化されました。これには以下のファイルが含まれます。
zversion.go
: Goのバージョン情報 (defaultGoroot
,theVersion
) を含むGoソースファイル。zgoarch_$GOARCH.go
: ターゲットアーキテクチャ (theGoarch
) を含むGoソースファイル。zgoos_$GOOS.go
: ターゲットOS (theGoos
) を含むGoソースファイル。zasm_$GOOS_$GOARCH.h
: アセンブリコードで使用される構造体オフセットやマクロ定義を含むCヘッダファイル。特にTLS (Thread Local Storage) へのアクセス方法がOS/アーキテクチャごとに定義されています。zruntime_defs_$GOOS_$GOARCH.go
: C言語で定義されたランタイムの構造体をGo言語の構造体として再定義するGoソースファイル。これは、Cgoとの連携や、Goランタイムの内部構造をGoコードから参照するために使用されます。
これらのファイルは、Goのビルド時に動的に生成されるため、手動で管理する必要がなくなり、異なるプラットフォームやアーキテクチャへの対応が容易になります。
2. goc2c
の統合と .goc
ファイルの処理
src/cmd/dist/goc2c.c
が新規追加され、goc2c
ツールの機能が cmd/dist
に直接組み込まれました。これにより、Goのビルドプロセスは .goc
ファイルを自動的にCファイルに変換し、そのCファイルをコンパイルできるようになります。
goc2c
は、Goの package
宣言、プリプロセッサディレクティブ、Goスタイルの関数定義(引数と戻り値の型、関数名)を解析し、対応するC言語の関数シグネチャとボディを生成します。特に、Goの型(bool
, float
, int
, uint
, uintptr
, string
, slice
, eface
など)をCの対応する型にマッピングし、構造体のアライメントを考慮して引数のオフセットを計算するロジックが含まれています。
3. 並列ビルドのサポート
src/cmd/dist/unix.c
に bgrunv
(background run vector) と bgwait
(background wait) 関数が追加され、複数のコマンドをバックグラウンドで並列実行し、その完了を待つ機能が実装されました。これは、従来の make -j
と同様の並列ビルドを実現するための基盤となります。
bgrunv
: 指定されたコマンドを新しいプロセスとしてバックグラウンドで実行します。bgwait
: 現在実行中のすべてのバックグラウンドプロセスが完了するまで待機します。MAXBG
(デフォルトは4) という定数で同時に実行できるジョブの最大数が制御されます。
この変更により、コンパイルなどの時間のかかるタスクを並列で実行できるようになり、ビルド時間が短縮されます。
4. 新しいコマンドと引数解析
src/cmd/dist/main.c
:cmdtab
構造体にbanner
,clean
,version
コマンドが追加されました。- コマンドライン引数の処理が
ARGBEGIN
/ARGEND
マクロを使用するように変更され、より堅牢な引数解析が可能になりました。 usage()
関数が追加され、cmd/dist
の利用方法が表示されるようになりました。
src/cmd/dist/build.c
:findgoversion()
関数が追加され、GOROOT/VERSION
ファイル、GOROOT/VERSION.cache
ファイル、またはMercurialリポジトリのタグ情報からGoのバージョンを動的に決定するようになりました。これにより、ビルドされたGoバイナリに正確なバージョン情報が埋め込まれます。clean()
関数が追加され、ビルドによって生成されたファイルやディレクトリ(オブジェクトファイル、インストールされたパッケージ、ツール、キャッシュファイルなど)を削除する機能が提供されます。rmworkdir()
関数がvflag
(verbose flag) の値に応じて詳細なログを出力するようになりました。setup()
関数で、pkg/obj
ディレクトリが作成されるようになりました。これは、生成されたバイナリを一つのツリーにまとめるためのものです。install()
関数内で、パスの処理にbsubst
(buffer substitute) 関数が導入され、$GOROOT
,$GOOS
,$GOARCH
といった変数をパス内で置換できるようになりました。これにより、パスの構築がより柔軟になります。install()
関数内で、Goのコンパイラやアセンブラの呼び出しがbgrunv
を使用するように変更され、並列コンパイルが可能になりました。install()
関数内で、pkg/runtime
のビルド時にruntime.h
とcgocall.h
がcgo
コンパイルのためにコピーされるようになりました。
src/cmd/dist/arg.h
: Inferno OSのkern.h
から派生したARGBEGIN
,ARGEND
,ARGF
,EARGF
,ARGC
といったマクロが定義され、C言語でのコマンドライン引数解析を簡潔かつ堅牢に行うためのフレームワークが提供されます。
5. パス処理の改善
src/cmd/dist/a.h
とsrc/cmd/dist/unix.c
にbpathf
(buffer path format) とbwritef
(buffer write format) 関数が追加されました。bpathf
:bprintf
と同様にフォーマットされた文字列をバッファに書き込みますが、Windows環境ではパス区切り文字を/
から\
に自動的に変換します。これにより、クロスプラットフォームでのパス処理が簡素化されます。bwritef
:bprintf
と異なり、バッファをリセットせずに文字列を追記します。
- 以前の
fixslash
関数が削除され、bpathf
がその役割を担うようになりました。
これらの変更は、Goのビルドシステムがより複雑な要件に対応し、将来の拡張に備えるための重要なステップでした。
コアとなるコードの変更箇所
1. src/cmd/dist/build.c
- findgoversion
関数の追加
--- a/src/cmd/dist/build.c
+++ b/src/cmd/dist/build.c
@@ -140,10 +142,115 @@ init(void)
static void
rmworkdir(void)
{
-\txprintf("rm -rf %s\n", workdir);
+\tif(vflag > 1)
+\t\txprintf("rm -rf %s\n", workdir);
\txremoveall(workdir);
}
+// Remove trailing spaces.
+static void
+chomp(Buf *b)
+{
+\tint c;
+
+\twhile(b->len > 0 && ((c=b->p[b->len-1]) == ' ' || c == '\t' || c == '\r' || c == '\n'))
+\t\tb->len--;
+}
+
+
+// findgoversion determines the Go version to use in the version string.
+static char*
+findgoversion(void)
+{
+\tchar *tag, *rev, *p;\n+\tint i, nrev;\n+\tBuf b, path, bmore, branch;\n+\tVec tags;\n+\t\n+\tbinit(&b);\n+\tbinit(&path);\n+\tbinit(&bmore);\n+\tbinit(&branch);\n+\tvinit(&tags);\n+\t\n+\t// The $GOROOT/VERSION file takes priority, for distributions\n+\t// without the Mercurial repo.\n+\tbpathf(&path, "%s/VERSION", goroot);\n+\tif(isfile(bstr(&path))) {\n+\t\treadfile(&b, bstr(&path));\n+\t\tchomp(&b);\n+\t\tgoto done;\n+\t}\n+\n+\t// The $GOROOT/VERSION.cache file is a cache to avoid invoking\n+\t// hg every time we run this command. Unlike VERSION, it gets\n+\t// deleted by the clean command.\n+\tbpathf(&path, "%s/VERSION.cache", goroot);\n+\tif(isfile(bstr(&path))) {\n+\t\treadfile(&b, bstr(&path));\n+\t\tchomp(&b);\n+\t\tgoto done;\n+\t}\n+\n+\t// Otherwise, use Mercurial.\n+\t// What is the current branch?\n+\trun(&branch, goroot, CheckExit, "hg", "identify", "-b", nil);\n+\tchomp(&branch);\n+\n+\t// What are the tags along the current branch?\n+\ttag = "";\n+\trev = ".";\n+\trun(&b, goroot, CheckExit, "hg", "log", "-b", bstr(&branch), "--template", "{tags} + ", nil);\n+\tsplitfields(&tags, bstr(&b));\n+\tnrev = 0;\n+\tfor(i=0; i<tags.len; i++) {\n+\t\tp = tags.p[i];\n+\t\tif(streq(p, "+"))\n+\t\t\tnrev++;\n+\t\tif(hasprefix(p, "release.") || hasprefix(p, "weekly.") || hasprefix(p, "go")) {\n+\t\t\ttag = xstrdup(p);\n+\t\t\t// If this tag matches the current checkout\n+\t\t\t// exactly (no "+" yet), don't show extra\n+\t\t\t// revision information.\n+\t\t\tif(nrev == 0)\n+\t\t\t\trev = "";\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\tif(tag[0] == '\\0') {\n+\t\t// Did not find a tag; use branch name.\n+\t\tbprintf(&b, "branch.%s", bstr(&branch));\n+\t\ttag = btake(&b);\n+\t}\n+\t\n+\tif(rev[0]) {\n+\t\t// Tag is before the revision we're building.\n+\t\t// Add extra information.\n+\t\trun(&bmore, goroot, CheckExit, "hg", "log", "--template", " +{node|short}", "-r", rev, nil);\n+\t\tchomp(&bmore);\n+\t}\n+\t\n+\tbprintf(&b, "%s", tag);\n+\tif(bmore.len > 0)\n+\t\tbwriteb(&b, &bmore);\n+\n+\t// Cache version.\n+\twritefile(&b, bstr(&path));\n+\n+done:\n+\tp = btake(&b);\n+\t\n+\t\n+\tbfree(&b);\n+\tbfree(&path);\n+\tbfree(&bmore);\n+\tbfree(&branch);\n+\tvfree(&tags);\n+\t\n+\treturn p;\n+}\n+\n```
### 2. `src/cmd/dist/buildruntime.c` - 新規ファイル
```diff
--- /dev/null
+++ b/src/cmd/dist/buildruntime.c
@@ -0,0 +1,346 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#include "a.h"
+#include <stdio.h>
+
+/*
+ * Helpers for building pkg/runtime.
+ */
+
+// mkzversion writes zversion.go:
+//
+//
+// package runtime
+// const defaultGoroot = <goroot>
+// const theVersion = <version>
+//
+void
+mkzversion(char *dir, char *file)
+{
+ Buf b, out;
+
+ binit(&b);
+ binit(&out);
+
+ bwritestr(&out, bprintf(&b,
+ "// auto generated by go tool dist\n"
+ "\n"
+ "package runtime\n"
+ "\n"
+ "const defaultGoroot = `%s`\n"
+ "const theVersion = `%s`\n", goroot, goversion));
+
+ writefile(&out, file);
+
+ bfree(&b);
+ bfree(&out);
+}
+
+// mkzgoarch writes zgoarch_$GOARCH.go:
+//
+//
+// package runtime
+// const theGoarch = <goarch>
+//
+void
+mkzgoarch(char *dir, char *file)
+{
+ Buf b, out;
+
+ binit(&b);
+ binit(&out);
+
+ bwritestr(&out, bprintf(&b,
+ "// auto generated by go tool dist\n"
+ "\n"
+ "package runtime\n"
+ "\n"
+ "const theGoarch = `%s`\n", goarch));
+
+ writefile(&out, file);
+
+ bfree(&b);
+ bfree(&out);
+}
+
+// mkzgoos writes zgoos_$GOOS.go:
+//
+//
+// package runtime
+// const theGoos = <goos>
+//
+void
+mkzgoos(char *dir, char *file)
+{
+ Buf b, out;
+
+ binit(&b);
+ binit(&out);
+
+ bwritestr(&out, bprintf(&b,
+ "// auto generated by go tool dist\n"
+ "\n"
+ "package runtime\n"
+ "\n"
+ "const theGoos = `%s`\n", goos));
+
+ writefile(&out, file);
+
+ bfree(&b);
+ bfree(&out);
+}
+
+static struct {
+ char *goarch;
+ char *goos;
+ char *hdr;
+} zasmhdr[] = {
+ {"386", "windows",
+ "#define get_tls(r) MOVL 0x14(FS), r\n"
+ "#define g(r) 0(r)\n"
+ "#define m(r) 4(r)\n"
+ },
+ {"386", "plan9",
+ "#define get_tls(r) MOVL _tos(SB), r \n"
+ "#define g(r) -8(r)\n"
+ "#define m(r) -4(r)\n"
+ },
+ {"386", "linux",
+ "// On Linux systems, what we call 0(GS) and 4(GS) for g and m\n"
+ "// turn into %gs:-8 and %gs:-4 (using gcc syntax to denote\n"
+ "// what the machine sees as opposed to 8l input).\n"
+ "// 8l rewrites 0(GS) and 4(GS) into these.\n"
+ "//\n"
+ "// On Linux Xen, it is not allowed to use %gs:-8 and %gs:-4\n"
+ "// directly. Instead, we have to store %gs:0 into a temporary\n"
+ "// register and then use -8(%reg) and -4(%reg). This kind\n"
+ "// of addressing is correct even when not running Xen.\n"
+ "//\n"
+ "// 8l can rewrite MOVL 0(GS), CX into the appropriate pair\n"
+ "// of mov instructions, using CX as the intermediate register\n"
+ "// (safe because CX is about to be written to anyway).\n"
+ "// But 8l cannot handle other instructions, like storing into 0(GS),\n"
+ "// which is where these macros come into play.\n"
+ "// get_tls sets up the temporary and then g and r use it.\n"
+ "//\n"
+ "// The final wrinkle is that get_tls needs to read from %gs:0,\n"
+ "// but in 8l input it's called 8(GS), because 8l is going to\n"
+ "// subtract 8 from all the offsets, as described above.\n"
+ "#define get_tls(r) MOVL 8(GS), r\n"
+ "#define g(r) -8(r)\n"
+ "#define m(r) -4(r)\n"
+ },
+ {"386", "",
+ "#define get_tls(r)\n"
+ "#define g(r) 0(GS)\n"
+ "#define m(r) 4(GS)\n"
+ },
+
+ {"amd64", "windows",
+ "#define get_tls(r) MOVQ 0x28(GS), r\n"
+ "#define g(r) 0(r)\n"
+ "#define m(r) 8(r)\n"
+ },
+ {"amd64", "",
+ "// The offsets 0 and 8 are known to:\n"
+ "// ../../cmd/6l/pass.c:/D_GS\n"
+ "// cgo/gcc_linux_amd64.c:/^threadentry\n"
+ "// cgo/gcc_darwin_amd64.c:/^threadentry\n"
+ "//\n"
+ "#define get_tls(r)\n"
+ "#define g(r) 0(GS)\n"
+ "#define m(r) 8(GS)\n"
+ },
+
+ {"arm", "",
+ "#define g R10\n"
+ "#define m R9\n"
+ "#define LR R14\n"
+ },
+};
+
+// mkzasm writes zasm_$GOOS_$GOARCH.h,
+// which contains struct offsets for use by
+// assembly files. It also writes a copy to the work space
+// under the name zasm_GOOS_GOARCH.h (no expansion).
+//
+void
+mkzasm(char *dir, char *file)
+{
+ int i, n;
+ char *aggr, *p;
+ Buf in, b, out;
+ Vec argv, lines, fields;
+
+ binit(&in);
+ binit(&b);
+ binit(&out);
+ vinit(&argv);
+ vinit(&lines);
+ vinit(&fields);
+
+ bwritestr(&out, "// auto generated by go tool dist\n\n");
+ for(i=0; i<nelem(zasmhdr); i++) {
+ if(hasprefix(goarch, zasmhdr[i].goarch) && hasprefix(goos, zasmhdr[i].goos)) {
+ bwritestr(&out, zasmhdr[i].hdr);
+ goto ok;
+ }
+ }
+ fatal("unknown $GOOS/$GOARCH in mkzasm");
+ok:
+
+ // Run 6c -DGOOS_goos -DGOARCH_goarch -Iworkdir -a proc.c
+ // to get acid [sic] output.
+ vreset(&argv);
+ vadd(&argv, bpathf(&b, "%s/bin/tool/%sc", goroot, gochar));
+ vadd(&argv, bprintf(&b, "-DGOOS_%s", goos));
+ vadd(&argv, bprintf(&b, "-DGOARCH_%s", goarch));
+ vadd(&argv, bprintf(&b, "-I%s", workdir));
+ vadd(&argv, "-a");
+ vadd(&argv, "proc.c");
+ runv(&in, dir, CheckExit, &argv);
+
+ // Convert input like
+ // aggr G
+ // {
+ // Gobuf 24 sched;
+ // 'Y' 48 stack0;
+ // }
+ // into output like
+ // #define g_sched 24
+ // #define g_stack0 48
+ //
+ aggr = nil;
+ splitlines(&lines, bstr(&in));
+ for(i=0; i<lines.len; i++) {
+ splitfields(&fields, lines.p[i]);
+ if(fields.len == 2 && streq(fields.p[0], "aggr")) {
+ if(streq(fields.p[1], "G"))
+ aggr = "g";
+ else if(streq(fields.p[1], "M"))
+ aggr = "m";
+ else if(streq(fields.p[1], "Gobuf"))
+ aggr = "gobuf";
+ else if(streq(fields.p[1], "WinCall"))
+ aggr = "wincall";
+ }
+ if(hasprefix(lines.p[i], "}"))
+ aggr = nil;
+ if(aggr && hasprefix(lines.p[i], "\t") && fields.len >= 2) {
+ n = fields.len;
+ p = fields.p[n-1];
+ if(p[xstrlen(p)-1] == ';')
+ p[xstrlen(p)-1] = '\0';
+ bwritestr(&out, bprintf(&b, "#define %s_%s %s\n", aggr, fields.p[n-1], fields.p[n-2]));
+ }
+ }
+
+ // Write both to file and to workdir/zasm_GOOS_GOARCH.h.
+ writefile(&out, file);
+ writefile(&out, bprintf(&b, "%s/zasm_GOOS_GOARCH.h", workdir));
+
+ bfree(&in);
+ bfree(&b);
+ bfree(&out);
+ vfree(&argv);
+ vfree(&lines);
+ vfree(&fields);
+}
+
+static char *runtimedefs[] = {
+ "proc.c",
+ "iface.c",
+ "hashmap.c",
+ "chan.c",
+};
+
+// mkzruntimedefs writes zruntime_defs_$GOOS_$GOARCH.h,
+// which contains Go struct definitions equivalent to the C ones.
+// Mostly we just write the output of 6c -q to the file.
+// However, we run it on multiple files, so we have to delete
+// the duplicated definitions, and we don't care about the funcs
+// and consts, so we delete those too.
+//
+void
+mkzruntimedefs(char *dir, char *file)
+{
+ int i, skip;
+ char *p;
+ Buf in, b, out;
+ Vec argv, lines, fields, seen;
+
+ binit(&in);
+ binit(&b);
+ binit(&out);
+ vinit(&argv);
+ vinit(&lines);
+ vinit(&fields);
+ vinit(&seen);
+
+ bwritestr(&out, "// auto generated by go tool dist\n"
+ "\n"
+ "package runtime\n"
+ "import \"unsafe\"\n"
+ "var _ unsafe.Pointer\n"
+ "\n"
+ );
+
+
+ // Run 6c -DGOOS_goos -DGOARCH_goarch -Iworkdir -q
+ // on each of the runtimedefs C files.
+ vadd(&argv, bpathf(&b, "%s/bin/tool/%sc", goroot, gochar));
+ vadd(&argv, bprintf(&b, "-DGOOS_%s", goos));
+ vadd(&argv, bprintf(&b, "-DGOARCH_%s", goarch));
+ vadd(&argv, bprintf(&b, "-I%s", workdir));
+ vadd(&argv, "-q");
+ vadd(&argv, "");
+ p = argv.p[argv.len-1];
+ for(i=0; i<nelem(runtimedefs); i++) {
+ argv.p[argv.len-1] = runtimedefs[i];
+ runv(&b, dir, CheckExit, &argv);
+ bwriteb(&in, &b);
+ }
+ argv.p[argv.len-1] = p;
+
+ // Process the aggregate output.
+ skip = 0;
+ splitlines(&lines, bstr(&in));
+ for(i=0; i<lines.len; i++) {
+ p = lines.p[i];
+ // Drop comment, func, and const lines.
+ if(hasprefix(p, "//") || hasprefix(p, "const") || hasprefix(p, "func"))
+ continue;
+
+ // Note beginning of type or var decl, which can be multiline.
+ // Remove duplicates. The linear check of seen here makes the
+ // whole processing quadratic in aggregate, but there are only
+ // about 100 declarations, so this is okay (and simple).
+ if(hasprefix(p, "type ") || hasprefix(p, "var ")) {
+ splitfields(&fields, p);
+ if(fields.len < 2)
+ continue;
+ if(find(fields.p[1], seen.p, seen.len) >= 0) {
+ if(streq(fields.p[fields.len-1], "{"))
+ skip = 1; // skip until }
+ continue;
+ }
+ vadd(&seen, fields.p[1]);
+ }
+ if(skip) {
+ if(hasprefix(p, "}"))
+ skip = 0;
+ continue;
+ }
+
+ bwritestr(&out, p);
+ }
+
+ writefile(&out, file);
+
+ bfree(&in);
+ bfree(&b);
+ bfree(&out);
+ vfree(&argv);
+ vfree(&lines);
+ vfree(&fields);
+ vfree(&seen);
+}
3. src/cmd/dist/goc2c.c
- 新規ファイル
--- /dev/null
+++ b/src/cmd/dist/goc2c.c
@@ -0,0 +1,727 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#include "a.h"
+
+/*
+ * Translate a .goc file into a .c file. A .goc file is a combination
+ * of a limited form of Go with C.
+ */
+
+/*
+ package PACKAGENAME
+ {# line}
+ func NAME([NAME TYPE { , NAME TYPE }]) [(NAME TYPE { , NAME TYPE })] \{
+ C code with proper brace nesting
+ \}
+*/
+
+/*
+ * We generate C code which implements the function such that it can
+ * be called from Go and executes the C code.
+ */
+
+static char *input;
+static Buf *output;
+#define EOF -1
+
+static int
+xgetchar(void)
+{
+ int c;
+
+ c = *input;
+ if(c == 0)
+ return EOF;
+ input++;
+ return c;
+}
+
+static void
+xungetc(void)
+{
+ input--;
+}
+
+static void
+xputchar(char c)
+{
+ bwrite(output, &c, 1);
+}
+
+static int
+xisspace(int c)
+{
+ return c == ' ' || c == '\t' || c == '\r' || c == '\n';
+}
+
+/* Whether we're emitting for gcc */
+static int gcc;
+
+/* File and line number */
+static const char *file;
+static unsigned int lineno = 1;
+
+/* List of names and types. */
+struct params {
+ struct params *next;
+ char *name;
+ char *type;
+};
+
+/* index into type_table */
+enum {
+ Bool,
+ Float,
+ Int,
+ Uint,
+ Uintptr,
+ String,
+ Slice,
+ Eface,
+};
+
+static struct {
+ char *name;
+ int size;
+} type_table[] = {
+ /* variable sized first, for easy replacement */
+ /* order matches enum above */
+ /* default is 32-bit architecture sizes */
+ {"bool", 1},
+ {"float", 4},
+ {"int", 4},
+ {"uint", 4},
+ {"uintptr", 4},
+ {"String", 8},
+ {"Slice", 12},
+ {"Eface", 8},
+
+ /* fixed size */
+ {"float32", 4},
+ {"float64", 8},
+ {"byte", 1},
+ {"int8", 1},
+ {"uint8", 1},
+ {"int16", 2},
+ {"uint16", 2},
+ {"int32", 4},
+ {"uint32", 4},
+ {"int64", 8},
+ {"uint64", 8},
+
+ {nil},
+};
+
+/* Fixed structure alignment (non-gcc only) */
+int structround = 4;
+
+/* Unexpected EOF. */
+static void
+bad_eof(void)
+{
+ fatal("%s:%ud: unexpected EOF\n", file, lineno);
+}
+
+/* Free a list of parameters. */
+static void
+free_params(struct params *p)
+{
+ while (p != nil) {
+ struct params *next;
+
+ next = p->next;
+ xfree(p->name);
+ xfree(p->type);
+ xfree(p);
+ p = next;
+ }
+}
+
+/* Read a character, tracking lineno. */
+static int
+getchar_update_lineno(void)
+{
+ int c;
+
+ c = xgetchar();
+ if (c == '\n')
+ ++lineno;
+ return c;
+}
+
+/* Read a character, giving an error on EOF, tracking lineno. */
+static int
+getchar_no_eof(void)
+{
+ int c;
+
+ c = getchar_update_lineno();
+ if (c == EOF)
+ bad_eof();
+ return c;
+}
+
+/* Read a character, skipping comments. */
+static int
+getchar_skipping_comments(void)
+{
+ int c;
+
+ while (1) {
+ c = getchar_update_lineno();
+ if (c != '/')
+ return c;
+
+ c = xgetchar();
+ if (c == '/') {
+ do {
+ c = getchar_update_lineno();
+ } while (c != EOF && c != '\n');
+ return c;
+ } else if (c == '*') {
+ while (1) {
+ c = getchar_update_lineno();
+ if (c == EOF)
+ return EOF;
+ if (c == '*') {
+ do {
+ c = getchar_update_lineno();
+ } while (c == '*');
+ if (c == '/')
+ break;
+ }
+ }
+ } else {
+ xungetc();
+ return '/';
+ }
+ }
+}
+
+/*
+ * Read and return a token. Tokens are string or character literals
+ * or else delimited by whitespace or by [(),{}].
+ * The latter are all returned as single characters.
+ */
+static char *
+read_token(void)
+{
+ int c, q;
+ char *buf;
+ unsigned int alc, off;
+ char* delims = "(),{}";
+
+ while (1) {
+ c = getchar_skipping_comments();
+ if (c == EOF)
+ return nil;
+ if (!xisspace(c))
+ break;
+ }
+ alc = 16;
+ buf = xmalloc(alc + 1);
+ off = 0;
+ if(c == '"' || c == '\'') {
+ q = c;
+ buf[off] = c;
+ ++off;
+ while (1) {
+ if (off+2 >= alc) { // room for c and maybe next char
+ alc *= 2;
+ buf = xrealloc(buf, alc + 1);
+ }
+ c = getchar_no_eof();
+ buf[off] = c;
+ ++off;
+ if(c == q)
+ break;
+ if(c == '\\') {
+ buf[off] = getchar_no_eof();
+ ++off;
+ }
+ }
+ } else if (xstrrchr(delims, c) != nil) {
+ buf[off] = c;
+ ++off;
+ } else {
+ while (1) {
+ if (off >= alc) {
+ alc *= 2;
+ buf = xrealloc(buf, alc + 1);
+ }
+ buf[off] = c;
+ ++off;
+ c = getchar_skipping_comments();
+ if (c == EOF)
+ break;
+ if (xisspace(c) || xstrrchr(delims, c) != nil) {
+ if (c == '\n')
+ lineno--;
+ xungetc();
+ break;
+ }
+ }
+ }
+ buf[off] = '\0';
+ return buf;
+}
+
+/* Read a token, giving an error on EOF. */
+static char *
+read_token_no_eof(void)
+{
+ char *token = read_token();
+ if (token == nil)
+ bad_eof();
+ return token;
+}
+
+/* Read the package clause, and return the package name. */
+static char *
+read_package(void)
+{
+ char *token;
+
+ token = read_token_no_eof();
+ if (token == nil)
+ fatal("%s:%ud: no token\n", file, lineno);
+ if (!streq(token, "package")) {
+ fatal("%s:%ud: expected \"package\", got \"%s\"\n",
+ file, lineno, token);
+ }
+ return read_token_no_eof();
+}
+
+/* Read and copy preprocessor lines. */
+static void
+read_preprocessor_lines(void)
+{
+ while (1) {
+ int c;
+
+ do {
+ c = getchar_skipping_comments();
+ } while (xisspace(c));
+ if (c != '#') {
+ xungetc();
+ break;
+ }
+ xputchar(c);
+ do {
+ c = getchar_update_lineno();
+ xputchar(c);
+ } while (c != '\n');
+ }
+}
+
+/*
+ * Read a type in Go syntax and return a type in C syntax. We only
+ * permit basic types and pointers.
+ */
+static char *
+read_type(void)
+{
+ char *p, *op, *q;
+ int pointer_count;
+ unsigned int len;
+
+ p = read_token_no_eof();
+ if (*p != '*')
+ return p;
+ op = p;
+ pointer_count = 0;
+ while (*p == '*') {
+ ++pointer_count;
+ ++p;
+ }
+ len = xstrlen(p);
+ q = xmalloc(len + pointer_count + 1);
+ xmemmove(q, p, len);
+ while (pointer_count > 0) {
+ q[len] = '*';
+ ++len;
+ --pointer_count;
+ }
+ q[len] = '\0';
+ xfree(op);
+ return q;
+}
+
+/* Return the size of the given type. */
+static int
+type_size(char *p)
+{
+ int i;
+
+ if(p[xstrlen(p)-1] == '*')
+ return type_table[Uintptr].size;
+
+ for(i=0; type_table[i].name; i++)
+ if(streq(type_table[i].name, p))
+ return type_table[i].size;
+ fatal("%s:%ud: unknown type %s\n", file, lineno, p);
+ return 0;
+}
+
+/*
+ * Read a list of parameters. Each parameter is a name and a type.
+ * The list ends with a ')'. We have already read the '('.
+ */
+static struct params *
+read_params(int *poffset)
+{
+ char *token;
+ struct params *ret, **pp, *p;
+ int offset, size, rnd;
+
+ ret = nil;
+ pp = &ret;
+ token = read_token_no_eof();
+ offset = 0;
+ if (!streq(token, ")")) {
+ while (1) {
+ p = xmalloc(sizeof(struct params));
+ p->name = token;
+ p->type = read_type();
+ p->next = nil;
+ *pp = p;
+ pp = &p->next;
+
+ size = type_size(p->type);
+ rnd = size;
+ if(rnd > structround)
+ rnd = structround;
+ if(offset%rnd)
+ offset += rnd - offset%rnd;
+ offset += size;
+
+ token = read_token_no_eof();
+ if (!streq(token, ","))
+ break;
+ token = read_token_no_eof();
+ }
+ }
+ if (!streq(token, ")")) {
+ fatal("%s:%ud: expected '('",
+ file, lineno);
+ }
+ if (poffset != nil)
+ *poffset = offset;
+ return ret;
+}
+
+/*
+ * Read a function header. This reads up to and including the initial
+ * '{' character. Returns 1 if it read a header, 0 at EOF.
+ */
+static int
+read_func_header(char **name, struct params **params, int *paramwid, struct params **rets)
+{
+ int lastline;
+ char *token;
+
+ lastline = -1;
+ while (1) {
+ token = read_token();
+ if (token == nil)
+ return 0;
+ if (streq(token, "func")) {
+ if(lastline != -1)
+ bwritef(output, "\n");
+ break;
+ }
+ if (lastline != lineno) {
+ if (lastline == lineno-1)
+ bwritef(output, "\n");
+ else
+ bwritef(output, "\n#line %d \"%s\"\n", lineno, file);
+ lastline = lineno;
+ }
+ bwritef(output, "%s ", token);
+ }
+
+ *name = read_token_no_eof();
+
+ token = read_token();
+ if (token == nil || !streq(token, "(")) {
+ fatal("%s:%ud: expected \"(\"\n",
+ file, lineno);
+ }
+ *params = read_params(paramwid);
+
+ token = read_token();
+ if (token == nil || !streq(token, "("))
+ *rets = nil;
+ else {
+ *rets = read_params(nil);
+ token = read_token();
+ }
+ if (token == nil || !streq(token, "{")) {
+ fatal("%s:%ud: expected \"{\"\n",
+ file, lineno);
+ }
+ return 1;
+}
+
+/* Write out parameters. */
+static void
+write_params(struct params *params, int *first)
+{
+ struct params *p;
+
+ for (p = params; p != nil; p = p->next) {
+ if (*first)
+ *first = 0;
+ else
+ bwritef(output, ", ");
+ bwritef(output, "%s %s", p->type, p->name);
+ }
+}
+
+/* Write a 6g function header. */
+static void
+write_6g_func_header(char *package, char *name, struct params *params,
+ int paramwid, struct params *rets)
+{
+ int first, n;
+
+ bwritef(output, "void\n%s·%s(", package, name);
+ first = 1;
+ write_params(params, &first);
+
+ /* insert padding to align output struct */
+ if(rets != nil && paramwid%structround != 0) {
+ n = structround - paramwid%structround;
+ if(n & 1)
+ bwritef(output, ", uint8");
+ if(n & 2)
+ bwritef(output, ", uint16");
+ if(n & 4)
+ bwritef(output, ", uint32");
+ }
+
+ write_params(rets, &first);
+ bwritef(output, ")\n{\n");
+}
+
+/* Write a 6g function trailer. */
+static void
+write_6g_func_trailer(struct params *rets)
+{
+ struct params *p;
+
+ for (p = rets; p != nil; p = p->next)
+ bwritef(output, "\tFLUSH(&%s);\n", p->name);
+ bwritef(output, "}\n");
+}
+
+/* Define the gcc function return type if necessary. */
+static void
+define_gcc_return_type(char *package, char *name, struct params *rets)
+{
+ struct params *p;
+
+ if (rets == nil || rets->next == nil)
+ return;
+ bwritef(output, "struct %s_%s_ret {\n", package, name);
+ for (p = rets; p != nil; p = p->next)
+ bwritef(output, " %s %s;\n", p->type, p->name);
+ bwritef(output, "};\n");
+}
+
+/* Write out the gcc function return type. */
+static void
+write_gcc_return_type(char *package, char *name, struct params *rets)
+{
+ if (rets == nil)
+ bwritef(output, "void");
+ else if (rets->next == nil)
+ bwritef(output, "%s", rets->type);
+ else
+ bwritef(output, "struct %s_%s_ret", package, name);
+}
+
+/* Write out a gcc function header. */
+static void
+write_gcc_func_header(char *package, char *name, struct params *params,
+ struct params *rets)
+{
+ int first;
+ struct params *p;
+
+ define_gcc_return_type(package, name, rets);
+ write_gcc_return_type(package, name, rets);
+ bwritef(output, " %s_%s(", package, name);
+ first = 1;
+ write_params(params, &first);
+ bwritef(output, ") asm (\"%s.%s\");\n", package, name);
+ write_gcc_return_type(package, name, rets);
+ bwritef(output, " %s_%s(", package, name);
+ first = 1;
+ write_params(params, &first);
+ bwritef(output, ")\n{\n");
+ for (p = rets; p != nil; p = p->next)
+ bwritef(output, " %s %s;\n", p->type, p->name);
+}
+
+/* Write out a gcc function trailer. */
+static void
+write_gcc_func_trailer(char *package, char *name, struct params *rets)
+{
+ if (rets == nil)
+ ;
+ else if (rets->next == nil)
+ bwritef(output, "return %s;\n", rets->name);
+ else {
+ struct params *p;
+
+ bwritef(output, " {\n struct %s_%s_ret __ret;\n", package, name);
+ for (p = rets; p != nil; p = p->next)
+ bwritef(output, " __ret.%s = %s;\n", p->name, p->name);
+ bwritef(output, " return __ret;\n }\n");
+ }
+ bwritef(output, "}\n");
+}
+
+/* Write out a function header. */
+static void
+write_func_header(char *package, char *name,
+ struct params *params, int paramwid,
+ struct params *rets)
+{
+ if (gcc)
+ write_gcc_func_header(package, name, params, rets);
+ else
+ write_6g_func_header(package, name, params, paramwid, rets);
+ bwritef(output, "#line %d \"%s\"\n", lineno, file);
+}
+
+/* Write out a function trailer. */
+static void
+write_func_trailer(char *package, char *name,
+ struct params *rets)
+{
+ if (gcc)
+ write_gcc_func_trailer(package, name, rets);
+ else
+ write_6g_func_trailer(rets);
+}
+
+/*
+ * Read and write the body of the function, ending in an unnested }
+ * (which is read but not written).
+ */
+static void
+copy_body(void)
+{
+ int nesting = 0;
+ while (1) {
+ int c;
+
+ c = getchar_no_eof();
+ if (c == '}' && nesting == 0)
+ return;
+ xputchar(c);
+ switch (c) {
+ default:
+ break;
+ case '{':
+ ++nesting;
+ break;
+ case '}':
+ --nesting;
+ break;
+ case '/':
+ c = getchar_update_lineno();
+ xputchar(c);
+ if (c == '/') {
+ do {
+ c = getchar_no_eof();
+ xputchar(c);
+ } while (c != '\n');
+ } else if (c == '*') {
+ while (1) {
+ c = getchar_no_eof();
+ xputchar(c);
+ if (c == '*') {
+ do {
+ c = getchar_no_eof();
+ xputchar(c);
+ } while (c == '*');
+ if (c == '/')
+ break;
+ }
+ }
+ }
+ break;
+ case '"':
+ case '\'':
+ {
+ int delim = c;
+ do {
+ c = getchar_no_eof();
+ xputchar(c);
+ if (c == '\\') {
+ c = getchar_no_eof();
+ xputchar(c);
+ c = '\0';
+ }
+ } while (c != delim);
+ }
+ break;
+ }
+ }
+}
+
+/* Process the entire file. */
+static void
+process_file(void)
+{
+ char *package, *name;
+ struct params *params, *rets;
+ int paramwid;
+
+ package = read_package();
+ read_preprocessor_lines();
+ while (read_func_header(&name, ¶ms, ¶mwid, &rets)) {
+ write_func_header(package, name, params, paramwid, rets);
+ copy_body();
+ write_func_trailer(package, name, rets);
+ xfree(name);
+ free_params(params);
+ free_params(rets);
+ }
+ xfree(package);
+}
+
+void
+goc2c(char *goc, char *c)
+{
+ Buf in, out;
+
+ binit(&in);
+ binit(&out);
+
+ file = goc;
+ readfile(&in, goc);
+
+ // TODO: set gcc=1 when using gcc
+
+ if(!gcc && streq(goarch, "amd64")) {
+ type_table[Uintptr].size = 8;
+ type_table[String].size = 16;
+ type_table[Slice].size = 8+4+4;
+ type_table[Eface].size = 8+8;
+ structround = 8;
+ }
+
+ bprintf(&out, "// auto generated by go tool dist\n\n");
+ input = bstr(&in);
+ output = &out;
+
+ process_file();
+
+ writefile(&out, c);
+}
4. src/cmd/dist/unix.c
- 並列実行関連の追加
--- a/src/cmd/dist/unix.c
+++ b/src/cmd/dist/unix.c
@@ -69,6 +99,8 @@ xgetenv(Buf *b, char *name)\n \t\tbwritestr(b, p);\n }\n \n+static void genrun(Buf *b, char *dir, int mode, Vec *argv, int bg);\n+\n // run runs the command named by cmd.\n // If b is not nil, run replaces b with the output of the command.\n // If dir is not nil, run runs the command in that directory.\n@@ -92,15 +124,43 @@ run(Buf *b, char *dir, int mode, char *cmd, ...)\n \tvfree(&argv);\n }\n \n-\n // runv is like run but takes a vector.\n void\n runv(Buf *b, char *dir, int mode, Vec *argv)\n {\n-\tint i, p[2], pid, status;\n+\tgenrun(b, dir, mode, argv, 1);\n+}\n+\n+// bgrunv is like run but runs the command in the background.\n+// bgwait waits for pending bgrunv to finish.\n+void\n+bgrunv(char *dir, int mode, Vec *argv)\n+{\n+\tgenrun(nil, dir, mode, argv, 0);\n+}\n+\n+#define MAXBG 4 /* maximum number of jobs to run at once */\n+\n+static struct {\n+\tint pid;\n+\tint mode;\n+\tchar *cmd;\n+} bg[MAXBG];\n+static int nbg;\n+\n+static void bgwait1(void);\n+\n+// genrun is the generic run implementation.\n+static void\n+genrun(Buf *b, char *dir, int mode, Vec *argv, int wait)\n+{\n+\tint i, p[2], pid;\n \tBuf cmd;\n \tchar *q;\n \n+\twhile(nbg >= nelem(bg))\n+\t\tbgwait1();\n+\n \t// Generate a copy of the command to show in a log.\n \t// Substitute $WORK for the work directory.\n \tbinit(&cmd);\n@@ -114,8 +174,8 @@ runv(Buf *b, char *dir, int mode, Vec *argv)\n \t\t}\n \t\tbwritestr(&cmd, q);\n \t}\n-\tprintf("%s\n", bstr(&cmd));\n-\tbfree(&cmd);\n+\tif(vflag > 1)\n+\t\txprintf("%s\n", bstr(&cmd));\n \n \tif(b != nil) {\n \t\tbreset(b);\n@@ -143,6 +203,7 @@ runv(Buf *b, char *dir, int mode, Vec *argv)\n \t\t}\n \t\tvadd(argv, nil);\n \t\texecvp(argv->p[0], argv->p);\n+\t\tfprintf(stderr, "%s\n", bstr(&cmd));\n \t\tfprintf(stderr, "exec %s: %s\n", argv->p[0], strerror(errno));\n \t\t_exit(1);\n \t}\n@@ -151,18 +212,55 @@ runv(Buf *b, char *dir, int mode, Vec *argv)\n \t\tbreadfrom(b, p[0]);\n \t\tclose(p[0]);\n \t}\n-wait:\n+\n+\tif(nbg < 0)\n+\t\tfatal("bad bookkeeping");\n+\tbg[nbg].pid = pid;\n+\tbg[nbg].mode = mode;\n+\tbg[nbg].cmd = btake(&cmd);\n+\tnbg++;\n+\t\n+\tif(wait)\n+\t\tbgwait();\n+\n+\tbfree(&cmd);\n+}\n+\n+// bgwait1 waits for a single background job.\n+static void\n+bgwait1(void)\n+{\n+\tint i, pid, status, mode;\n+\tchar *cmd;\n+\n \terrno = 0;\n-\tif(waitpid(pid, &status, 0) != pid) {\n-\t\tif(errno == EINTR)\n-\t\t\tgoto wait;\n-\t\tfatal("waitpid: %s", strerror(errno));\n+\twhile((pid = wait(&status)) < 0) {\n+\t\tif(errno != EINTR)\n+\t\t\tfatal("waitpid: %s", strerror(errno));\n \t}\n-\tif(mode==CheckExit && (!WIFEXITED(status) || WEXITSTATUS(status) != 0)) {\n-\t\tif(b != nil)\n-\t\t\tfwrite(b->p, b->len, 1, stderr);\n-\t\tfatal("%s failed", argv->p[0]);\n+\tfor(i=0; i<nbg; i++)\n+\t\tif(bg[i].pid == pid)\n+\t\t\tgoto ok;\n+\tfatal("waitpid: unexpected pid");\n+\n+ok:\n+\tcmd = bg[i].cmd;\n+\tmode = bg[i].mode;\n+\tbg[i].pid = 0;\n+\tbg[i] = bg[--nbg];\n+\t\n+\tif(mode == CheckExit && (!WIFEXITED(status) || WEXITSTATUS(status) != 0)) {\n+\t\tfatal("FAILED: %s", cmd);\n \t}\n+\txfree(cmd);\n+}\n+\n+// bgwait waits for all the background jobs.\n+void\n+bgwait(void)\n+{\n+\twhile(nbg > 0)\n+\t\tbgwait1();\n }\n \n // xgetwd replaces b with the current directory.\n```
## コアとなるコードの解説
### 1. `findgoversion` 関数の追加 (`src/cmd/dist/build.c`)
この関数は、Goのバージョン文字列を決定するためのロジックをカプセル化します。
* **優先順位**:
1. `$GOROOT/VERSION` ファイル: GoのディストリビューションにMercurialリポジトリが含まれていない場合(例: 公式リリースバイナリ)に優先されます。
2. `$GOROOT/VERSION.cache` ファイル: `hg` コマンドの呼び出しを避けるためのキャッシュとして機能します。`clean` コマンドで削除されます。
3. Mercurialリポジトリ: 上記のファイルが存在しない場合、`hg identify -b` で現在のブランチを特定し、`hg log --template "{tags} + "` で現在のブランチ上のタグ情報を取得します。これにより、`go1`, `release.r60`, `weekly.2012-02-03` のようなタグと、リビジョン情報(例: `+<short_node_id>`)を組み合わせてバージョン文字列を生成します。
* **`chomp` 関数**: 行末の空白文字(スペース、タブ、改行)を削除するヘルパー関数です。`VERSION` ファイルやMercurialの出力から余分な空白を取り除くために使用されます。
* **キャッシュ**: Mercurialからバージョン情報を取得した場合、`$GOROOT/VERSION.cache` にその情報を書き込み、次回の実行時に高速化を図ります。
この機能により、ビルドされたGoバイナリが常に正確なバージョン情報を持つことが保証され、開発者やユーザーが使用しているGoのバージョンを容易に識別できるようになります。
### 2. `buildruntime.c` の新規追加
このファイルは、`pkg/runtime` のビルドに必要な特定のGoソースファイルやCヘッダファイルを生成するための関数群を提供します。
* **`mkzversion`, `mkzgoarch`, `mkzgoos`**:
* それぞれ `zversion.go`, `zgoarch_$GOARCH.go`, `zgoos_$GOOS.go` を生成します。
* これらのファイルは、Goのランタイムが自身のビルド環境(`GOROOT`, `GOOS`, `GOARCH`, `GOVERSION`)に関する情報をGoコードからアクセスできるようにするためのものです。
* 生成されるGoファイルは、`package runtime` と、対応する定数(例: `const defaultGoroot = \`...\``)を含みます。
* **`mkzasm`**:
* `zasm_$GOOS_$GOARCH.h` を生成します。このヘッダファイルは、GoランタイムのアセンブリコードがC言語の構造体やスレッドローカルストレージ (TLS) にアクセスするために必要なオフセットやマクロ定義を含みます。
* 異なるOS/アーキテクチャ(例: `386/windows`, `386/linux`, `amd64/windows`, `amd64` (generic), `arm`)に対応する `get_tls`, `g`, `m` といったマクロが定義されています。
* `6c` (GoのCコンパイラ) を `proc.c` に対して実行し、その出力(`aggr` 構造体の情報)を解析して、`#define g_sched 24` のような形式でオフセットを定義します。
* **`mkzruntimedefs`**:
* `zruntime_defs_$GOOS_$GOARCH.go` を生成します。このファイルは、C言語で定義されたGoランタイムの内部構造体(`G`, `M`, `Gobuf` など)をGo言語の構造体として再定義します。
* 複数のCソースファイル(`proc.c`, `iface.c`, `hashmap.c`, `chan.c`)に対して `6c -q` を実行し、その出力を結合・整形してGoの構造体定義を生成します。重複する定義やコメント、関数、定数などは除外されます。
これらの生成プロセスにより、Goランタイムの低レベルな部分がGo言語のコードベースとC言語のコードベースの間で一貫性を保ち、異なるプラットフォームへの移植が容易になります。
### 3. `goc2c.c` の新規追加
このファイルは、`.goc` ファイルをC言語のソースファイルに変換する `goc2c` ツールの実装です。
* **入力解析**: `package` 宣言、プリプロセッサディレクティブ、Goスタイルの関数定義(`func NAME(...) (...) { ... }`)を解析します。
* **型変換**: Goの基本型(`bool`, `int`, `string` など)やポインタ型をC言語の対応する型に変換します。特に、`amd64` アーキテクチャではポインタや文字列、スライス、インターフェースのサイズが32ビットアーキテクチャとは異なるため、それに応じて型サイズを調整します。
* **関数シグネチャの変換**: Goの関数シグネチャをC言語の関数シグネチャに変換します。Goの複数戻り値は、Cでは構造体として表現されます。
* `6g` (Goコンパイラ) 向けの関数ヘッダ (`void\n%s·%s(...)`) とトレーラ (`FLUSH(&%s);`) を生成します。
* `gcc` 向けの関数ヘッダ (`struct ..._ret { ... };` と `..._...(...)`) とトレーラを生成します。
* **ボディのコピー**: Go関数のC言語のボディ部分を、コメントや文字列リテラル、文字リテラルを適切に処理しながらそのままコピーします。ネストされたブレース `{}` を追跡し、関数の終わりを示す閉じブレース `}` を正しく検出します。
* **行番号の追跡**: `#line` ディレクティブを挿入することで、生成されたCファイルが元の `.goc` ファイルの行番号情報を保持するようにします。これにより、コンパイルエラーが発生した場合に元のソースコードの正確な位置を特定できます。
`goc2c` の統合により、Goのビルドシステムは `.goc` ファイルを透過的に処理できるようになり、Goランタイムのビルドプロセスが簡素化されました。
### 4. 並列実行関連の追加 (`src/cmd/dist/unix.c`)
このファイルは、Unix系システムにおけるファイルシステム操作やプロセス実行のユーティリティ関数を提供します。このコミットでは、特に並列実行をサポートするための重要な変更が加えられました。
* **`genrun` 関数**:
* `runv` (run vector) と `bgrunv` (background run vector) の共通の実装です。
* `MAXBG` (デフォルトは4) で定義された最大並列ジョブ数を超えないように、`bgwait1()` を呼び出して既存のバックグラウンドジョブの完了を待ちます。
* コマンドのログ出力が `vflag` (verbose flag) の値に依存するようになりました。
* 子プロセスを `fork()` し、`execvp()` でコマンドを実行します。
* `b` が指定されている場合、子プロセスの標準出力を読み取ります。
* 子プロセスのPID、モード、コマンド文字列を `bg` 配列に格納し、`nbg` をインクリメントします。
* `wait` フラグが真の場合、`bgwait()` を呼び出してすべてのバックグラウンドジョブの完了を待ちます。
* **`bgwait1` 関数**:
* 単一のバックグラウンドジョブの完了を待ちます。
* `waitpid()` を使用して子プロセスの終了を待ち、そのステータスをチェックします。
* `CheckExit` モードの場合、子プロセスがゼロ以外の終了コードを返した場合にエラーを報告します。
* 完了したジョブを `bg` 配列から削除し、`nbg` をデクリメントします。
* **`bgwait` 関数**:
* すべてのバックグラウンドジョブが完了するまで `bgwait1()` を繰り返し呼び出します。
これらの関数により、`cmd/dist` は複数のコンパイルタスクなどを並列で実行できるようになり、Goツールチェイン自身のビルド時間を大幅に短縮することが可能になりました。これは、大規模なソフトウェアプロジェクトのビルドにおいて非常に重要な最適化です。
## 関連リンク
* Go言語の公式ウェブサイト: [https://golang.org/](https://golang.org/)
* Go言語のソースコード (GitHub): [https://github.com/golang/go](https://github.com/golang/go)
* このコミットのGo Code Review: [https://golang.org/cl/5622058](https://golang.org/cl/5622058)
## 参考にした情報源リンク
* Go言語のビルドシステムに関する議論やドキュメント (当時のもの):
* Goの初期のビルドプロセスは `make.bash` スクリプトに依存していました。
* `cmd/dist` の進化は、Goが自己ホスト型ビルドシステムを構築する上での重要なマイルストーンでした。
* Mercurial (Hg) のドキュメント: `hg identify`, `hg log --template` コマンドの利用方法。
* Unix/Linux の `fork()`, `execvp()`, `waitpid()` システムコールに関するドキュメント。
* C言語のプリプロセッサディレクティブ `#line` に関するドキュメント。
* Inferno OSの `kern.h` (引数解析マクロの元となったコード)。
* [http://code.google.com/p/inferno-os/source/browse/include/kern.h](http://code.google.com/p/inferno-os/source/browse/include/kern.h) (当時のリンク)
* 現在のInferno OSのGitHubリポジトリ: [https://github.com/inferno-os/inferno-os](https://github.com/inferno-os/inferno-os)
(注: 2012年当時の情報源は現在のウェブ上では見つけにくい場合があります。上記のリンクは当時の状況を反映したものです。)