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

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

このコミットは、Go言語のビルドツールである cmd/dist における改善です。具体的には、runtime パッケージ内のC言語ソースファイル(proc.c など)をコンパイルする際に、コンパイラ(6c)が出力する「acid output」と呼ばれる詳細な中間出力を一時ファイルにリダイレクトするように変更しています。これにより、コンパイルエラーが発生した場合に、エラーメッセージが大量の「acid output」に埋もれて見つけにくくなる問題を解消し、エラーの視認性を向上させています。

コミット

commit 7fc64a2a1da272361fc78453c7ffea17def3bcb0
Author: Russ Cox <rsc@golang.org>
Date:   Thu Jan 31 22:02:20 2013 -0800

    cmd/dist: redirect acid output to file to separate from errors
    
    If runtime's proc.c does not compile, cmd/dist used to show
    the compile errors in a sea of acid output, making them impossible
    to find. Change the command invocation to write the acid output
    to a file, so that the errors are the only thing shown on failure.
    
    R=golang-dev, iant
    CC=golang-dev
    https://golang.org/cl/7221082

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

https://github.com/golang/go/commit/7fc64a2a1da272361fc78453c7ffea17def3bcb0

元コミット内容

cmd/dist: redirect acid output to file to separate from errors

runtimeproc.c がコンパイルされない場合、cmd/dist はコンパイルエラーを大量の acid output の中に表示し、見つけることを不可能にしていました。このコマンド呼び出しを変更し、acid output をファイルに書き出すようにすることで、エラーが発生した際にエラーのみが表示されるようにしました。

変更の背景

Go言語の初期のビルドシステムでは、cmd/dist というツールがGoのツールチェイン全体をビルドする役割を担っていました。このツールは、Goのランタイム(runtime)の一部であるC言語で書かれたファイル(例: proc.c)をコンパイルするために、6c のような特定のアーキテクチャ向けのCコンパイラを呼び出していました。

問題は、これらのCコンパイラが -a フラグ("acid output" を生成するフラグ)と共に実行された場合、非常に詳細で冗長な中間出力(「acid output」)を標準出力に吐き出すことでした。もしコンパイル中にエラーが発生すると、この大量の「acid output」の中に実際のエラーメッセージが埋もれてしまい、開発者がエラーの原因を特定することが極めて困難になっていました。

このコミットの目的は、この「エラーメッセージの視認性の低下」という問題を解決し、ビルドプロセスにおけるデバッグ体験を改善することにありました。

前提知識の解説

  • cmd/dist: Go言語のソースコードからGoツールチェイン全体(コンパイラ、リンカ、標準ライブラリなど)をビルドするためのコマンドラインツールです。Goの自己ホスト型コンパイラが成熟する以前は、C言語で書かれた部分が多く、それらをコンパイルするためにCコンパイラを呼び出す必要がありました。
  • 6c (および 8c, 5c など): Go言語の初期のツールチェインで使用されていた、Plan 9 CコンパイラをベースにしたCコンパイラ群です。6camd64 (x86-64) アーキテクチャ向け、8carm5c386 (x86) アーキテクチャ向けでした。これらはGoのランタイムや一部の低レベルな部分をコンパイルするために使われました。
  • proc.c: Goランタイムの一部を構成するC言語のソースファイルの一つです。プロセス管理やスケジューリングに関する低レベルな処理が含まれています。
  • "acid output": 6c などのPlan 9ベースのCコンパイラが -a フラグを付けて実行された際に生成する、コンパイラの内部状態や中間コード表現に関する詳細な出力のことです。これは主にコンパイラ開発者がデバッグや解析のために使用するもので、通常のコンパイル時には不要な情報であり、非常に冗長です。この出力は、コンパイラがソースコードをどのように解析し、中間表現に変換しているかを示します。
  • runv 関数: cmd/dist 内部で使用されるヘルパー関数で、外部コマンドを実行するためのものです。引数として、標準入力として使用する Buf (バッファ) へのポインタ、実行ディレクトリ、終了コードチェックのフラグ、およびコマンド引数のベクトルを受け取ります。
  • Buf および Vec: cmd/dist 内部で使われるカスタムのバッファおよびベクトル(動的配列)構造体です。これらは文字列操作やコマンド引数の構築に利用されます。

技術的詳細

このコミットの核心は、6c コンパイラの呼び出し方法を変更し、その「acid output」を標準出力ではなく一時ファイルにリダイレクトすることです。

変更前は、6c コマンドは以下のように実行されていました(簡略化)。 6c -D GOOS_goos -D GOARCH_goarch -I workdir -a proc.c このコマンドは、proc.c をコンパイルし、その「acid output」を runv 関数の第一引数で指定された Buf (バッファ) に直接書き込んでいました。このバッファは通常、後続の処理で利用されるか、あるいは単に標準出力に流れていました。エラーが発生した場合、エラーメッセージもこの同じストリームに出力されるため、両者が混在していました。

変更後、6c コマンドの引数に -n-o <output_file> が追加されました。 6c -D GOOS_goos -D GOARCH_goarch -I workdir -a -n -o workdir/proc.acid proc.c

  • -n フラグ: このフラグは、コンパイラが通常生成するオブジェクトファイル(.o ファイル)の生成を抑制します。この場合、コンパイラの目的はオブジェクトファイルの生成ではなく、「acid output」の生成であるため、オブジェクトファイルは不要です。
  • -o workdir/proc.acid: このフラグは、コンパイラの出力を指定されたファイル(例: workdir/proc.acid)にリダイレクトします。これにより、「acid output」は標準出力ではなく、このファイルに書き込まれるようになります。

また、runv 関数の呼び出しも変更されました。 変更前: runv(&in, dir, CheckExit, &argv); 変更後: runv(nil, dir, CheckExit, &argv);

runv の第一引数が &in から nil に変更されたことで、6c の標準出力は runv 内部のバッファには取り込まれなくなりました。代わりに、-o フラグによって指定されたファイルに直接書き込まれます。

runv の実行後、新しく追加された readfile(&in, bpathf(&b, "%s/proc.acid", workdir)); という行によって、proc.acid ファイルの内容が in バッファに読み込まれます。これは、後続の処理でこの「acid output」の内容が必要になるためです。

同様の変更が mkzruntimedefs 関数にも適用されています。この関数はランタイム定義ファイルを生成するために複数のCファイルを処理しますが、ここでも 6c の呼び出しに対して -n-o フラグが追加され、出力が一時ファイルにリダイレクトされ、その後読み戻されるようになっています。

この変更により、コンパイルエラーが発生した場合、6c コンパイラはエラーメッセージのみを標準エラー出力(または標準出力)に直接出力し、冗長な「acid output」は別のファイルに隔離されるため、エラーメッセージが明確に表示されるようになります。

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

src/cmd/dist/buildruntime.c ファイルの mkzasm 関数と mkzruntimedefs 関数が変更されています。

mkzasm 関数における変更:

--- a/src/cmd/dist/buildruntime.c
+++ b/src/cmd/dist/buildruntime.c
@@ -205,7 +205,7 @@ mkzasm(char *dir, char *file)\
  	fatal("unknown $GOOS/$GOARCH in mkzasm");
 ok:\
 
-	// Run 6c -D GOOS_goos -D GOARCH_goarch -I workdir -a proc.c
+	// Run 6c -D GOOS_goos -D GOARCH_goarch -I workdir -a -n -o workdir/proc.acid proc.c
  	// to get acid [sic] output.
  	vreset(&argv);\
  	vadd(&argv, bpathf(&b, "%s/%sc", tooldir, gochar));\
@@ -216,8 +216,12 @@ ok:\
  	vadd(&argv, "-I");\
  	vadd(&argv, bprintf(&b, "%s", workdir));\
  	vadd(&argv, "-a");\
+\tvadd(&argv, "-n");\
+\tvadd(&argv, "-o");\
+\tvadd(&argv, bpathf(&b, "%s/proc.acid", workdir));\
  	vadd(&argv, "proc.c");\
-\trunv(&in, dir, CheckExit, &argv);\
+\trunv(nil, dir, CheckExit, &argv);\
+\treadfile(&in, bpathf(&b, "%s/proc.acid", workdir));\
  	\
  	// Convert input like\
  	//\taggr G

mkzruntimedefs 関数における変更:

--- a/src/cmd/dist/buildruntime.c
+++ b/src/cmd/dist/buildruntime.c
@@ -288,11 +292,12 @@ mkzruntimedefs(char *dir, char *file)\
  {\n \tint i, skip;\n \tchar *p;\n-\tBuf in, b, out;\n+\tBuf in, b, b1, out;\n \tVec argv, lines, fields, seen;\n \t\n \tbinit(&in);\n \tbinit(&b);\n+\tbinit(&b1);\
 \tbinit(&out);\
 \tvinit(&argv);\
 \tvinit(&lines);\
@@ -308,7 +313,7 @@ mkzruntimedefs(char *dir, char *file)\
  \t);\
  \n \t\
-\t// Run 6c -D GOOS_goos -D GOARCH_goarch -I workdir -q\
+\t// Run 6c -D GOOS_goos -D GOARCH_goarch -I workdir -q -n -o workdir/runtimedefs\
  \t// on each of the runtimedefs C files.\
  \tvadd(&argv, bpathf(&b, "%s/%sc", tooldir, gochar));\
  \tvadd(&argv, "-D");\
@@ -318,11 +323,15 @@ mkzruntimedefs(char *dir, char *file)\
  \tvadd(&argv, "-I");\
  \tvadd(&argv, bprintf(&b, "%s", workdir));\
  \tvadd(&argv, "-q");\
+\tvadd(&argv, "-n");\
+\tvadd(&argv, "-o");\
+\tvadd(&argv, bpathf(&b, "%s/runtimedefs", workdir));\
  \tvadd(&argv, "");\
  \tp = argv.p[argv.len-1];\
  \tfor(i=0; i<nelem(runtimedefs); i++) {\
  \t\targv.p[argv.len-1] = runtimedefs[i];\
-\t\trunv(&b, dir, CheckExit, &argv);\
+\t\trunv(nil, dir, CheckExit, &argv);\
+\t\treadfile(&b, bpathf(&b1, "%s/runtimedefs", workdir));\
  \t\tbwriteb(&in, &b);\
  \t}\
  \targv.p[argv.len-1] = p;\
@@ -364,6 +373,7 @@ mkzruntimedefs(char *dir, char *file)\
  \n \tbfree(&in);\
  \tbfree(&b);\
+\tbfree(&b1);\
  \tbfree(&out);\
  \tvfree(&argv);\
  \tvfree(&lines);\

コアとなるコードの解説

  • mkzasm 関数:

    • vadd(&argv, "-n");
    • vadd(&argv, "-o");
    • vadd(&argv, bpathf(&b, "%s/proc.acid", workdir));
      • これらの行は、6c コンパイラを呼び出す際のコマンドライン引数リスト argv に、-n (オブジェクトファイル生成抑制) と -o <output_file> (出力をファイルにリダイレクト) のオプションを追加しています。出力ファイル名は workdir/proc.acid となります。
    • runv(nil, dir, CheckExit, &argv);
      • runv 関数の第一引数が &in から nil に変更されました。これにより、6c の標準出力は runv が管理するバッファには取り込まれず、-o で指定されたファイルに直接書き込まれます。
    • readfile(&in, bpathf(&b, "%s/proc.acid", workdir));
      • runv の実行後、新しく生成された proc.acid ファイルの内容を in バッファに読み込み直しています。これは、後続の処理でこの「acid output」の内容が必要となるためです。
  • mkzruntimedefs 関数:

    • Buf in, b, b1, out;
      • 新しい Buf 型の変数 b1 が追加されました。これは readfile 関数で一時的なバッファとして使用されます。
    • binit(&b1);
      • 追加された b1 の初期化です。
    • vadd(&argv, "-n");
    • vadd(&argv, "-o");
    • vadd(&argv, bpathf(&b, "%s/runtimedefs", workdir));
      • mkzasm と同様に、6c コンパイラの引数に -n-o <output_file> を追加し、出力を workdir/runtimedefs にリダイレクトします。
    • runv(nil, dir, CheckExit, &argv);
      • ここでも runv の第一引数が nil に変更され、標準出力への直接書き込みを抑制します。
    • readfile(&b, bpathf(&b1, "%s/runtimedefs", workdir));
      • runv 実行後、リダイレクトされた runtimedefs ファイルの内容を b バッファに読み込み直しています。
    • bfree(&b1);
      • 追加された b1 バッファの解放処理です。

これらの変更により、6c コンパイラからの冗長な「acid output」は一時ファイルに隔離され、コンパイルエラーが発生した際には、エラーメッセージが他の出力に埋もれることなく、開発者にとって明確に表示されるようになりました。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード (特に src/cmd/dist ディレクトリ)
  • Plan 9 C Compiler (6c, 8c, 5c) のドキュメント (Goの初期コンパイラのベース)
  • Go言語のコミット履歴と関連する議論
  • cmd/distrunv 関数や Buf, Vec などのユーティリティ関数の実装
  • Go言語のビルドプロセスに関する一般的な知識
  • https://golang.org/cl/7221082 (コミットメッセージに記載されているCLへのリンク)
  • https://github.com/golang/go/commit/7fc64a2a1da272361fc78453c7ffea17def3bcb0 (GitHub上のコミットページ)