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

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

このコミットは、Go言語のツールチェインの一部である cmd/dist において、アセンブリコードが列挙された定数を使用できるようにするための変更を導入しています。具体的には、Goランタイムのビルドプロセスにおいて、C言語のソースコード内で定義された定数を、アセンブリコードから参照可能な #define ディレクティブとして出力する機能が追加されました。

コミット

commit cfefe6a7633abc8964166720769df60e56a50583
Author: Keith Randall <khr@golang.org>
Date:   Fri Jul 12 12:24:57 2013 -0700

    cmd/dist: allow assembly code to use enumerated constants.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/11056044

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

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

元コミット内容

cmd/dist: allow assembly code to use enumerated constants.

R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/11056044

変更の背景

Go言語のランタイムは、パフォーマンスが重要な部分(例: ゴルーチンのスケジューリング、コンテキストスイッチ)でアセンブリコードを多用しています。これらのアセンブリコードは、Goのデータ構造のオフセットや、ランタイムが使用する特定の定数(例: 最小スタックサイズ StackMin)を参照する必要があります。

このコミット以前は、これらの定数がC言語のソースコード内で定義されている場合、アセンブリコードから直接参照することが困難でした。アセンブリコードでこれらの定数を使用するには、手動でハードコードするか、別のメカニズムで同期させる必要があり、これはエラーの温床となり、コードの保守性を低下させていました。

この変更の目的は、C言語のソースコードで定義された定数を、Goのビルドツールチェーンの一部である cmd/dist を介して、アセンブリコードが利用できる形式(Cプリプロセッサの #define ディレクティブ)で自動的に生成することです。これにより、定数の一元管理が可能になり、Cコードとアセンブリコード間での定数の同期が容易になり、ランタイムコード全体の堅牢性と保守性が向上します。

前提知識の解説

Go Toolchain (cmd/dist)

cmd/dist は、Go言語のビルドツールチェーンの一部であり、Goランタイム、標準ライブラリ、およびその他のツールをビルドするために使用されます。これは、Goのソースコードから実行可能なバイナリを生成するプロセスにおいて、様々な補助的なタスク(例: ヘッダーファイルの生成、アセンブリコードの準備)を実行します。src/cmd/dist/buildruntime.c は、このツールチェーンの一部であり、Goランタイムのビルドに必要なC言語のヘッダーファイルなどを生成する役割を担っています。

Goにおけるアセンブリコード

Go言語は、その高性能なランタイムを実現するために、一部のクリティカルなパスでアセンブリコードを使用しています。これには、ゴルーチンのスケジューリング、スタック管理、コンテキストスイッチなどが含まれます。Goのアセンブリコードは、Plan 9アセンブラの構文に基づいており、C言語のプリプロセッサ(#define など)を介してC言語のヘッダーファイルを取り込むことができます。

列挙された定数 (Enumerated Constants)

列挙された定数とは、特定の意味を持つ固定値に名前を付けたものです。プログラミングにおいて、マジックナンバー(意味不明な数値リテラル)を避けるために広く使用されます。Goランタイムの文脈では、StackMin のような値がこれに該当します。

Cプリプロセッサの #define

C言語のプリプロセッサディレクティブである #define は、マクロや定数を定義するために使用されます。コンパイルの前に、プリプロセッサは #define されたシンボルをその値に置き換えます。アセンブリコードがCプリプロセッサを介してCヘッダーファイルを取り込む場合、これらの #define された定数をシンボル名で参照できるようになります。

GobufStackMin

  • Gobuf: Goランタイムにおける Gobuf は、ゴルーチンの実行コンテキスト(レジスタの値、スタックポインタなど)を保存および復元するために使用される構造体です。アセンブリコードは、ゴルーチンのコンテキストスイッチ時にこの構造体の特定のフィールドにアクセスする必要があります。
  • StackMin: Goのゴルーチンが持つ最小のスタックサイズを定義する定数です。アセンブリコードは、スタックの割り当てやチェックを行う際にこの値を使用することがあります。

技術的詳細

このコミットは、src/cmd/dist/buildruntime.c ファイルを変更することで、Goランタイムのビルドプロセスに新しいロジックを追加します。buildruntime.c は、GoランタイムのCソースコードを解析し、アセンブリコードや他のCソースコードが利用できる #define ディレクティブを含むヘッダーファイルを生成します。

変更の核心は、入力されたCソースコードの行を解析する際に、特定のパターンを持つ行を識別し、それらを定数定義として扱う点にあります。具体的には、Name = Value; の形式で記述された行(例: StackMin = 128;)を検出します。

このパターンが検出されると、buildruntime.c は、その NameValue を使用して、#define const_Name Value という形式のCプリプロセッサディレクティブを生成します。ここで、const_ というプレフィックスが付けられるのは、通常の構造体フィールドのオフセットなどと区別するためと考えられます。

この生成された #define ディレクティブは、Goランタイムのアセンブリソースファイルがインクルードするヘッダーファイルに書き込まれます。これにより、アセンブリコードは const_StackMin のようなシンボル名を使用して、対応する定数値を参照できるようになります。これは、アセンブリコードの可読性と保守性を大幅に向上させ、定数値の変更があった場合でも、Cソースコードの定義を更新するだけで済むようになります。

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

変更は src/cmd/dist/buildruntime.c ファイルの ok: ラベルの後のセクションにあります。

--- a/src/cmd/dist/buildruntime.c
+++ b/src/cmd/dist/buildruntime.c
@@ -234,10 +234,11 @@ ok:
  	//		Gobuf 24 sched;
  	//		'Y' 48 stack0;
  	//	}\n
+\t//	StackMin = 128;\n
  	// into output like
  	//	#define g_sched 24
  	//	#define g_stack0 48
-\t//\n
+\t//	#define const_StackMin 128\n
  aggr = nil;
  splitlines(&lines, bstr(&in));
  for(i=0; i<lines.len; i++) {
@@ -265,6 +266,12 @@ ok:
  	\t\t\tp[xstrlen(p)-1] = '\0';
  	\t\tbwritestr(&out, bprintf(&b, "#define %s_%s %s\\n", aggr, fields.p[n-1], fields.p[n-2]));
  	\t}\n
+\t\tif(fields.len == 3 && streq(fields.p[1], "=")) { // generated from enumerated constants
+\t\t\tp = fields.p[2];
+\t\t\tif(p[xstrlen(p)-1] == ';')
+\t\t\t\tp[xstrlen(p)-1] = '\0';
+\t\t\tbwritestr(&out, bprintf(&b, "#define const_%s %s\\n", fields.p[0], p));
+\t\t}\n
  	}\n
  
  	// Some #defines that are used for .c files.\n

コアとなるコードの解説

追加された主要なコードブロックは以下の部分です。

if(fields.len == 3 && streq(fields.p[1], "=")) { // generated from enumerated constants
    p = fields.p[2];
    if(p[xstrlen(p)-1] == ';')
        p[xstrlen(p)-1] = '\0';
    writestr(&out, bprintf(&b, "#define const_%s %s\\n", fields.p[0], p));
}

このコードブロックは、入力された行を解析し、それが列挙された定数の定義であるかどうかを判断します。

  1. if(fields.len == 3 && streq(fields.p[1], "=")):

    • fields.len == 3: 解析された行が3つのフィールド(単語やトークン)に分割されることをチェックします。例えば、StackMin = 128;StackMin, =, 128; の3つのフィールドに分割されます。
    • streq(fields.p[1], "="): 2番目のフィールドが文字列 = であることをチェックします。これにより、Name = Value; のパターンに一致するかを確認します。
    • この条件が真である場合、現在の行が列挙された定数の定義であると判断されます。
  2. p = fields.p[2];:

    • 3番目のフィールド(Value の部分、例: 128;)をポインタ p に代入します。
  3. if(p[xstrlen(p)-1] == ';') p[xstrlen(p)-1] = '\0';:

    • Value の文字列の末尾がセミコロン ; で終わっている場合、そのセミコロンをヌル終端文字 \0 に置き換えます。これにより、定数値からセミコロンが取り除かれます(例: 128;128 になります)。
  4. writestr(&out, bprintf(&b, "#define const_%s %s\\n", fields.p[0], p));:

    • 最終的に、#define ディレクティブを生成し、出力ストリーム out に書き込みます。
    • bprintf(&b, ...) は、フォーマットされた文字列をバッファ b に書き込む関数です。
    • 生成される文字列の形式は #define const_NAME VALUE となります。
      • const_: 定数であることを示すプレフィックス。
      • %s (1つ目): 1番目のフィールド(Name の部分、例: StackMin)がここに入ります。
      • %s (2つ目): 処理された Value の部分(例: 128)がここに入ります。
      • \\n: 改行文字。

この変更により、buildruntime.c は、GoランタイムのCソースコードから定数定義を抽出し、アセンブリコードが利用できる形式で自動的にヘッダーファイルを生成する能力を獲得しました。

関連リンク

参考にした情報源リンク

  • コミットメッセージと差分 (git show cfefe6a7633abc8964166720769df60e56a50583)
  • Go言語のドキュメント(cmd/dist、アセンブリ、ランタイムの概念に関する一般的な知識)
  • C言語のプリプロセッサに関する一般的な知識
  • Goのソースコードリポジトリ(src/cmd/dist/buildruntime.c のコンテキスト理解のため)