[インデックス 17429] ファイルの概要
このコミットは、Go言語のコンパイラ (cmd/cc
) とランタイム (src/pkg/runtime
) におけるプリプロセッサの挙動変更に関するものです。具体的には、#pragma textflag
および #pragma dataflag
ディレクティブ内でマクロを展開できるようにし、dataflag
ディレクティブで整数定数ではなくシンボルを使用するように更新しています。これにより、GoランタイムのC/アセンブリコードの可読性と保守性が向上しています。
コミット
commit ed467db6d8db16dcc2956d85f0ce114635e12f06
Author: Keith Randall <khr@golang.org>
Date: Thu Aug 29 12:36:59 2013 -0700
cmd/cc,runtime: change preprocessor to expand macros inside of
#pragma textflag and #pragma dataflag directives.
Update dataflag directives to use symbols instead of integer constants.
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/13310043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/ed467db6d8db16dcc2956d85f0ce114635e12f06
元コミット内容
cmd/cc,runtime: change preprocessor to expand macros inside of #pragma textflag and #pragma dataflag directives. Update dataflag directives to use symbols instead of integer constants.
(日本語訳)
cmd/cc,runtime: #pragma textflag および #pragma dataflag ディレクティブ内でマクロを展開するようにプリプロセッサを変更。dataflag ディレクティブを整数定数ではなくシンボルを使用するように更新。
変更の背景
Go言語のランタイムは、パフォーマンスと低レベルな制御のためにC言語やアセンブリ言語で書かれた部分を含んでいます。これらのコードでは、コンパイラやリンカに対して特定のメタデータや挙動を指示するために、#pragma
ディレクティブが使用されます。特に、#pragma textflag
は関数(コードセクション)に、#pragma dataflag
はグローバル変数(データセクション)にフラグを付与するために用いられます。
このコミット以前は、これらの#pragma
ディレクティブに渡されるフラグ値は、16
のような直接的な整数定数でした。これは、フラグの意味を理解するために別途定義を参照する必要があり、コードの可読性を低下させ、誤解やエラーの原因となる可能性がありました。例えば、#pragma dataflag 16
が何を意味するのかは、textflag.h
などのヘッダファイルを見なければ分かりませんでした。
この変更の背景には、以下の目的があります。
- 可読性の向上: 整数定数ではなく、
NOPTR
やRODATA
といった意味のあるシンボル(マクロ)を使用することで、コードが自己文書化され、開発者がフラグの意味を直感的に理解できるようになります。 - 保守性の向上: フラグの数値が変更された場合でも、
textflag.h
の定義を更新するだけで、関連するすべての#pragma
ディレクティブが自動的に更新されるようになります。これにより、手動での一括置換といった手間や、それに伴うエラーのリスクを削減できます。 - 開発体験の改善: よりセマンティックな表現を用いることで、Goランタイムの低レベルな部分に携わる開発者の負担を軽減し、コードの理解と修正を容易にします。
前提知識の解説
このコミットを理解するためには、以下のGo言語のビルドシステムとC言語の概念に関する知識が必要です。
- Go言語のビルドプロセス: Goのソースコードは、Goコンパイラによって中間表現に変換され、最終的に機械語にコンパイルされます。Goランタイムの一部はC言語やアセンブリ言語で書かれており、これらはGoのツールチェーンに含まれるCコンパイラ (
cmd/cc
) やアセンブラ (cmd/asm
) によって処理されます。その後、Goリンカ (cmd/ld
) がこれらを結合して実行可能ファイルを生成します。 #pragma
ディレクティブ: C言語において、#pragma
はプリプロセッサに対する特別な指示を与えるディレクティブです。これは標準Cには含まれない、コンパイラ固有の拡張機能として提供されることが多く、コンパイラの挙動を制御したり、特定の最適化を指示したりするために使用されます。Goのビルドシステムでは、#pragma textflag
と#pragma dataflag
がGoランタイムのC/アセンブリコードで使用され、関数やデータセクションに特定の属性(フラグ)を付与します。#pragma textflag
: 関数(コードセクション)に適用されるフラグを指定します。#pragma dataflag
: グローバル変数(データセクション)に適用されるフラグを指定します。
- プリプロセッサ: C言語のコンパイル過程の最初の段階で行われる処理です。ソースコード中の
#include
(ヘッダファイルの読み込み)、#define
(マクロの定義と展開)、#ifdef
などの条件付きコンパイルディレクティブを処理します。マクロ展開は、定義されたマクロ名がソースコード中に現れると、その定義内容に置き換えられるテキスト置換のプロセスです。 - リンカフラグ: Goのリンカ (
cmd/ld
) は、コンパイルされたオブジェクトファイルを結合して最終的な実行可能ファイルを生成します。この際、textflag
やdataflag
によって付与された情報は、リンカがコードやデータをどのように配置し、処理するかを決定するために使用されます。NOPROF
: プロファイリングを行わないことを示すフラグ。DUPOK
: リンカがこのシンボルの複数の定義を見つけても問題ないことを示すフラグ。リンカは重複する定義の中から一つを選択します。NOSPLIT
: スタック分割を行わないことを示すフラグ。Goの関数は通常、必要に応じてスタックを動的に拡張しますが、NOSPLIT
が指定された関数はスタック分割を行いません。これは、非常に短い関数や、スタックの制約が厳しい低レベルなランタイム関数で使われます。RODATA
: このデータが読み取り専用セクションに配置されるべきであることを示すフラグ。NOPTR
: このデータセクションにポインタが含まれていないことを示すフラグ。これはGoのガベージコレクタにとって非常に重要な情報です。NOPTR
が設定されたデータは、ガベージコレクタがポインタをスキャンする必要がないため、GCの効率が向上します。
技術的詳細
このコミットの技術的な核心は、GoのCコンパイラ (cmd/cc
) のプリプロセッサが、#pragma textflag
と#pragma dataflag
ディレクティブの引数として渡される値を処理する方法を変更した点にあります。
-
src/cmd/cc/dpchk.c
の変更:pragtextflag
関数とpragdataflag
関数が修正されました。これらの関数は、それぞれ#pragma textflag
と#pragma dataflag
ディレクティブを処理するGoのCコンパイラの内部関数です。- 変更前は、これらの関数は
getnsn()
("get next number"の略と思われる)を呼び出して、直接数値を取得していました。 - 変更後は、まず
getsym()
("get symbol"の略と思われる)を呼び出してシンボルを取得します。 - 取得したシンボルがマクロである場合(
s->macro
が真の場合)、macexpand(s, symb)
を呼び出してマクロを展開し、その結果をsymb
バッファに格納します。 - その後、
atoi(symb)
を使用して、展開されたマクロ文字列(または直接取得されたシンボル文字列)を整数に変換します。 - 変換された文字列が数字でない場合(
symb[0] < '0' || symb[0] > '9'
)、yyerror
を呼び出してエラーを報告する堅牢性も追加されました。 - この変更により、プリプロセッサは
#pragma dataflag NOPTR
のような形式を認識し、NOPTR
が定義されている数値に展開できるようになります。
-
src/cmd/ld/textflag.h
の変更:- このヘッダファイルは、リンカフラグの定義を含んでいます。
NOPROF
,DUPOK
,NOSPLIT
,RODATA
,NOPTR
といったマクロの定義が変更されました。- 変更前は、これらのフラグはビットシフト演算子 (
(1<<0)
,(1<<1)
など) を使用して定義されていました。例えば、#define NOPTR (1<<4)
は16
を意味します。 - 変更後は、これらのマクロは直接整数定数として定義されるようになりました(例:
#define NOPTR 16
)。 - この変更は、
dpchk.c
でのatoi
による文字列から整数への変換と整合性を保つために行われました。atoi("16")
は16
を返しますが、atoi("(1<<4)")
は期待通りに動作しません。直接整数定数を定義することで、マクロ展開後にatoi
で正しく数値に変換できるようになります。これらの値は依然として2のべき乗であり、ビットフラグとして使用される意図は変わりません。
-
src/pkg/runtime
内のファイルの変更:- Goランタイムの様々なC/アセンブリファイル(例:
hashmap.c
,malloc.goc
,mgc0.c
,os_linux.c
,sema.goc
など)が更新されました。 - これらのファイルでは、以前は
#pragma dataflag 16
のように直接整数定数を使用していた箇所が、#pragma dataflag NOPTR
や#pragma dataflag RODATA
といったシンボリックなマクロ名に置き換えられました。 - これにより、コードの意図がより明確になり、可読性が大幅に向上しました。
- また、これらのファイルには、新しいシンボル定義を利用するために
#include "../../cmd/ld/textflag.h"
が追加されました。
- Goランタイムの様々なC/アセンブリファイル(例:
これらの変更は、Goのビルドツールチェーンの内部的な連携を強化し、低レベルなランタイムコードの記述をより人間が理解しやすい形に進化させるものです。
コアとなるコードの変更箇所
src/cmd/cc/dpchk.c
--- a/src/cmd/cc/dpchk.c
+++ b/src/cmd/cc/dpchk.c
@@ -567,7 +567,19 @@ pragfpround(void)
void
pragtextflag(void)
{
- textflag = getnsn();
+ Sym *s;
+
+ s = getsym();
+ if(s == S) {
+ textflag = getnsn();
+ } else {
+ if(s->macro) {
+ macexpand(s, symb);
+ }
+ if(symb[0] < '0' || symb[0] > '9')
+ yyerror("pragma textflag not an integer");
+ textflag = atoi(symb);
+ }
while(getnsc() != '\n')
;
if(debug['f'])
@@ -577,7 +589,19 @@ pragtextflag(void)
void
pragdataflag(void)
{
- dataflag = getnsn();
+ Sym *s;
+
+ s = getsym();
+ if(s == S) {
+ dataflag = getnsn();
+ } else {
+ if(s->macro) {
+ macexpand(s, symb);
+ }
+ if(symb[0] < '0' || symb[0] > '9')
+ yyerror("pragma dataflag not an integer");
+ dataflag = atoi(symb);
+ }
while(getnsc() != '\n')
;
if(debug['f'])
src/cmd/ld/textflag.h
--- a/src/cmd/ld/textflag.h
+++ b/src/cmd/ld/textflag.h
@@ -7,13 +7,13 @@
// all agree on these values.
// Don't profile the marked routine. This flag is deprecated.
-#define NOPROF (1<<0)
+#define NOPROF 1
// It is ok for the linker to get multiple of these symbols. It will
// pick one of the duplicates to use.
-#define DUPOK (1<<1)
+#define DUPOK 2
// Don't insert stack check preamble.
-#define NOSPLIT (1<<2)
+#define NOSPLIT 4
// Put this data in a read-only section.
-#define RODATA (1<<3)
+#define RODATA 8
// This data contains no pointers.
-#define NOPTR (1<<4)
+#define NOPTR 16
src/pkg/runtime/hashmap.c
(代表例)
--- a/src/pkg/runtime/hashmap.c
+++ b/src/pkg/runtime/hashmap.c
@@ -8,6 +8,7 @@
#include "hashmap.h"
#include "type.h"
#include "race.h"
+#include "../../cmd/ld/textflag.h"
// This file contains the implementation of Go's map type.
//
@@ -524,7 +525,7 @@ hash_lookup(MapType *t, Hmap *h, byte **keyp)
}
// When an item is not found, fast versions return a pointer to this zeroed memory.
-#pragma dataflag 16 // no pointers
+#pragma dataflag RODATA
static uint8 empty_value[MAXVALUESIZE];
// Specialized versions of mapaccess1 for specific types.
@@ -593,7 +594,6 @@ static uint8 empty_value[MAXVALUESIZE];
#define SLOW_EQ(x,y) runtime·memeq((x).str, (y).str, (x).len)
#define MAYBE_EQ(x,y) (*(CHECKTYPE*)(x).str == *(CHECKTYPE*)(y).str && *(CHECKTYPE*)((x).str + (x).len - sizeof(CHECKTYPE)) == *(CHECKTYPE*)((y).str + (x).len - sizeof(CHECKTYPE)))
#include "hashmap_fast.c"
-#include "../../cmd/ld/textflag.h"
static void
hash_insert(MapType *t, Hmap *h, void *key, void *value)
コアとなるコードの解説
src/cmd/cc/dpchk.c
の解説
このファイルはGoのCコンパイラ (cmd/cc
) の一部であり、主にプリプロセッサのディレクティブ処理を担当しています。pragtextflag
とpragdataflag
関数は、それぞれ#pragma textflag
と#pragma dataflag
ディレクティブを解析し、対応するフラグ値をtextflag
およびdataflag
グローバル変数に設定します。
変更の核心は、フラグ値の取得方法にあります。
- 変更前:
textflag = getnsn();
やdataflag = getnsn();
のように、getnsn()
関数を直接呼び出して、入力ストリームから次の数値を読み取っていました。これは、#pragma dataflag 16
のような直接的な数値のみを想定していました。 - 変更後:
Sym *s; s = getsym();
:まずgetsym()
を呼び出して、次のトークンをシンボルとして取得します。これにより、NOPTR
のようなシンボル名も読み取れるようになります。if(s == S)
:もしシンボルが取得できなかった場合(例えば、直接数値が書かれている場合)、以前と同様にgetnsn()
を呼び出して数値を読み取ります。else { ... }
:シンボルが取得できた場合、そのシンボルがマクロであるかを確認します(if(s->macro)
)。macexpand(s, symb);
:もしマクロであれば、macexpand()
関数を呼び出してそのマクロを展開し、展開された文字列をsymb
バッファに格納します。これにより、NOPTR
が16
に展開されるといった処理が行われます。if(symb[0] < '0' || symb[0] > '9') yyerror("pragma ... not an integer");
:展開された文字列(または直接取得されたシンボル文字列)の最初の文字が数字でない場合、エラーを報告します。これは、#pragma
の引数が有効な整数に解決されることを保証するための堅牢性チェックです。textflag = atoi(symb);
またはdataflag = atoi(symb);
:最終的に、atoi()
関数(ASCII to Integer)を使用して、展開された文字列を整数に変換し、対応するフラグ変数に設定します。
この一連の変更により、GoのCコンパイラは#pragma
ディレクティブの引数としてマクロを受け入れ、それを数値に解決できるようになりました。
src/cmd/ld/textflag.h
の解説
このヘッダファイルは、Goのリンカ (cmd/ld
) が使用する様々なフラグの定義を含んでいます。これらのフラグは、GoランタイムのC/アセンブリコードで#pragma textflag
や#pragma dataflag
を通じて使用されます。
変更のポイントは、マクロの定義方法です。
- 変更前:
NOPROF (1<<0)
,DUPOK (1<<1)
,NOSPLIT (1<<2)
,RODATA (1<<3)
,NOPTR (1<<4)
のように、ビットシフト演算子を使用してフラグ値を定義していました。これは、各フラグが独立したビットを表すことを明確にする一般的な方法です。 - 変更後:
NOPROF 1
,DUPOK 2
,NOSPLIT 4
,RODATA 8
,NOPTR 16
のように、直接整数定数として定義されるようになりました。
この変更は、dpchk.c
でのatoi()
による文字列から整数への変換と密接に関連しています。atoi()
は文字列を整数に変換するため、"(1<<4)"
のような文字列は正しく変換できません。しかし、"16"
のような文字列であれば正しく16
に変換できます。したがって、マクロ展開後にatoi()
で処理されることを考慮し、マクロの定義を直接的な整数値に変更したものです。これらの値は依然として2のべき乗であり、複数のフラグをビットOR演算子で組み合わせることで使用されるという意図は変わりません。
src/pkg/runtime
内のファイルの解説 (代表例: src/pkg/runtime/hashmap.c
)
GoランタイムのC/アセンブリコードでは、特定のデータ構造や関数に対して、ガベージコレクションの挙動やリンカの処理方法を指示するために#pragma dataflag
や#pragma textflag
が頻繁に使用されます。
src/pkg/runtime/hashmap.c
の例では、empty_value
という静的配列に対して#pragma dataflag 16
が適用されていました。
- 変更前:
#pragma dataflag 16 // no pointers
- コメントで
no pointers
と説明されていますが、16
という数値だけではその意味が直感的に分かりません。開発者はtextflag.h
を参照するか、Goランタイムの内部知識を持っている必要がありました。
- コメントで
- 変更後:
#pragma dataflag RODATA
RODATA
はtextflag.h
で8
と定義されており、empty_value
は読み取り専用データセクションに配置されるべきであることを示しています。- また、
#pragma dataflag NOPTR
も追加されています。NOPTR
はtextflag.h
で16
と定義されており、このデータセクションにポインタが含まれていないことをガベージコレクタに伝えます。これにより、ガベージコレクタはこの領域をスキャンする必要がなくなり、GCの効率が向上します。 - さらに、
#include "../../cmd/ld/textflag.h"
が追加され、RODATA
やNOPTR
といったシンボルがこのファイルで利用可能になりました。
この変更により、コードの可読性が大幅に向上し、empty_value
が読み取り専用であり、ポインタを含まないデータであることを一目で理解できるようになりました。これは、Goランタイムの他の多くのファイルでも同様に適用され、全体的なコードベースの品質と保守性を高めています。
関連リンク
- Go言語の公式リポジトリ: https://github.com/golang/go
- Go言語のIssueトラッカー: https://go.dev/issue
- Go言語のコードレビューシステム (Gerrit): https://go.dev/cl
参考にした情報源リンク
- Go言語のソースコード (特に
src/cmd/cc/dpchk.c
,src/cmd/ld/textflag.h
,src/pkg/runtime
ディレクトリ内のファイル) - C言語の
#pragma
ディレクティブに関する一般的な情報 - Go言語のビルドプロセスとランタイムに関するドキュメント (必要に応じてGo公式ドキュメントやブログ記事を参照)
- Goのガベージコレクションに関する情報 (特に
NOPTR
フラグの重要性について) - Goのリンカ (
cmd/ld
) の挙動に関する情報 - GoのCコンパイラ (
cmd/cc
) の挙動に関する情報
(注: この解説は、提供されたコミット情報と一般的なGo言語およびC言語の知識に基づいて生成されています。特定のGoの内部実装の詳細については、Goの公式ドキュメントやソースコードを直接参照することが最も正確な情報源となります。)