[インデックス 1572] ファイルの概要
このコミットは、Go言語の初期のコンパイラおよびランタイムにおいて、go
ステートメント(ゴルーチン生成)と defer
ステートメント(遅延実行)に関連する潜在的なバグを修正するために、pragma textflag
という新しいプラグマを導入するものです。このプラグマは、特定の関数のコード生成時に特殊なフラグを設定することで、これらの機能が正しく動作するようにします。
コミット
commit e90314d0242fe520f578ea256db1cb3800f1e7b9
Author: Ken Thompson <ken@golang.org>
Date: Tue Jan 27 14:12:35 2009 -0800
pragma textflag
fixes latent bugs in go and defer
R=r
OCL=23613
CL=23613
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e90314d0242fe520f578ea256db1cb3800f1e7b9
元コミット内容
pragma textflag
の導入により、go
および defer
に関連する潜在的なバグを修正します。
変更の背景
Go言語の初期段階では、コンパイラとランタイムの連携がまだ発展途上にありました。特に、ゴルーチンの生成 (go
ステートメント) や関数の終了時に実行される defer
ステートメントは、スタック管理や関数呼び出し規約において特殊な処理を必要とします。
このコミット以前は、これらの特殊な関数(例えば、runtime
パッケージ内の newproc
や deferproc
など、ゴルーチンや遅延実行の内部処理を担う関数)がコンパイルされる際に、通常の関数と同様のコード生成が行われていました。しかし、これらの関数はランタイムの非常に低レベルな部分で動作するため、特定の最適化やコード生成の挙動が問題を引き起こす可能性がありました。具体的には、プロファイリングのためのコード挿入や、特定のスタックフレームの扱いが、go
や defer
の内部ロジックと衝突し、潜在的なバグ(例えば、スタックの破損や不正な関数呼び出し)を引き起こす可能性があったと考えられます。
textflag
は、これらのランタイムのコアとなる関数に対して、コンパイラが特別なコード生成を行うように指示するためのメカニズムとして導入されました。これにより、プロファイリングコードの挿入を抑制したり、特定のリンケージ情報を付与したりすることで、go
および defer
の安定性と正確性を確保することが目的でした。
前提知識の解説
Go言語の初期コンパイラ (6a, 6c, 8c, cc)
Go言語の初期のコンパイラは、現在の go tool compile
とは異なり、ターゲットアーキテクチャごとに独立したツールとして存在していました。
6a
: Plan 9a
(アセンブラ) の Go バージョン。x86-64 (amd64) アーキテクチャ用のアセンブラ。6c
: Plan 9c
(Cコンパイラ) の Go バージョン。x86-64 (amd64) アーキテクチャ用のCコンパイラ。Go言語のソースコードを中間表現に変換し、最終的にアセンブリコードを生成する役割の一部を担っていました。8c
: x86 (386) アーキテクチャ用のCコンパイラ。cc
: 共通のCコンパイラフロントエンド。アーキテクチャ固有のバックエンド(6c
,8c
など)と連携して動作します。
これらのツールは、Go言語のソースコードを機械語に変換するプロセスにおいて重要な役割を担っていました。
go
ステートメントとゴルーチン
go
ステートメントは、Go言語における並行処理の基本単位である「ゴルーチン」を生成するために使用されます。go
キーワードの後に続く関数呼び出しは、新しいゴルーチンとして独立して実行されます。ゴルーチンは軽量なスレッドのようなもので、Goランタイムによって管理されます。
defer
ステートメント
defer
ステートメントは、そのステートメントを含む関数が終了する直前に、指定された関数呼び出しを遅延実行するために使用されます。これは、リソースの解放(ファイルのクローズ、ロックの解除など)を確実に行うためによく利用されます。defer
された関数は、関数の正常終了時だけでなく、パニック発生時にも実行されます。
pragma
(プラグマ)
pragma
は、コンパイラに対する特別な指示を与えるためのディレクティブです。Go言語のコンパイラでは、//go:build
や //go:noinline
のようなコメント形式のプラグマが一般的ですが、初期のコンパイラではC言語の #pragma
に似た形式も使用されていました。これらは、コンパイラの最適化の挙動を変更したり、特定のコード生成ルールを適用したりするために使われます。
textflag
と profileflg
textflag
: このコミットで導入された新しい概念です。関数のコードセクション(テキストセクション)に付与されるフラグで、コンパイラやリンカに対して特定の挙動を指示します。例えば、プロファイリングコードの挿入を抑制したり、特定のリンケージ属性を設定したりするために使用されます。このコミットでは、特にランタイムのコア関数に対して、プロファイリングを無効化し、特別なリンケージ属性(7
という値が設定されていることから、おそらくNOPROF
やRODATA
など、特定のセクションへの配置や最適化を指示するビットフラグの組み合わせ)を付与する目的で使われています。profileflg
: 以前から存在したフラグで、プロファイリングの有効/無効を制御していました。このコミットでは、profileflg
の代わりにtextflag
を使用することで、よりきめ細やかな制御を可能にしています。特に、go
やdefer
の内部関数では、プロファイリングが意図しない副作用を引き起こす可能性があったため、これを無効化する必要がありました。
技術的詳細
このコミットの核心は、Goコンパイラとランタイムが、go
および defer
の内部処理を担う特定の関数(例: runtime.newproc
, runtime.deferproc
, runtime.deferreturn
, runtime.morestack
)をコンパイルする際に、特別な textflag
を付与するように変更した点にあります。
-
#pragma textflag
の導入:src/cmd/cc/cc.h
にtextflag
という新しいグローバル変数が導入され、pragtextflag
という新しいプラグマ処理関数が宣言されました。src/cmd/cc/macbody
では、#pragma textflag
がコンパイラによって認識され、pragtextflag
関数が呼び出されるように変更されました。src/cmd/cc/dpchk.c
にpragtextflag
関数の実装が追加されました。この関数は、#pragma textflag
の後に続く数値(デフォルトは7
)をtextflag
グローバル変数に設定します。これにより、後続の関数定義に対してこのフラグが適用されます。
-
profileflg
からtextflag
への移行:- 以前は
profileflg
というフラグがプロファイリングの有効/無効を制御していましたが、このコミットではprofileflg
の使用が廃止され、textflag
に置き換えられました。 src/cmd/cc/lex.c
では、main
関数からprofileflg = 1;
の初期化が削除されました。src/cmd/cc/dpchk.c
のpragtextflag
関数内で、以前pragprofile
が行っていたprofileflg
の設定ロジックがtextflag
の設定に置き換えられました。
- 以前は
-
コンパイラバックエンドでの
textflag
の利用:src/cmd/6c/txt.c
(amd64用Cコンパイラ) とsrc/cmd/8c/txt.c
(x86用Cコンパイラ) のgpseudo
関数が変更されました。gpseudo
は、擬似命令(例えば、関数のエントリポイントを示す命令)を生成する際に呼び出されます。- 変更前は、
p->from.scale
に(profileflg ? 0 : NOPROF)
が設定されていましたが、変更後はtextflag
の値が直接設定されるようになりました。そして、textflag
は設定後すぐに0
にリセットされます。これは、#pragma textflag
が適用された直後の関数定義にのみそのフラグが有効になるようにするためです。 p->from.scale
は、アセンブラやリンカに対して、シンボル(関数など)の属性や配置に関する情報を提供する役割を担っています。textflag
の値をここに設定することで、ランタイムのコア関数が特別な属性を持つコードとして扱われるようになります。
-
ランタイムコードへの
textflag
の適用:src/runtime/proc.c
にあるsys·newproc
(ゴルーチン生成),sys·deferproc
(defer処理の開始),sys·deferreturn
(defer処理の終了),sys·morestack
(スタック拡張) といった重要なランタイム関数に対して、#pragma textflag 7
が追加されました。- これにより、これらの関数がコンパイルされる際に、コンパイラは
textflag
の値7
をp->from.scale
に設定し、特別なコード生成ルールが適用されるようになります。具体的には、プロファイリングコードの挿入が抑制され、これらの関数がランタイムの低レベルな操作を安全に行えるようになります。
この変更により、go
や defer
の内部処理が、プロファイリングなどの外部要因に影響されずに、常に期待通りに動作することが保証され、潜在的なバグが修正されました。
コアとなるコードの変更箇所
src/cmd/6a/lex.c
:pragtextflag
関数の宣言と、../cc/lexbody
および../cc/macbody
のインクルード。src/cmd/6c/txt.c
:gpseudo
関数内でp->from.scale
にtextflag
を設定し、textflag
をリセットする変更。src/cmd/8c/txt.c
:src/cmd/6c/txt.c
と同様に、gpseudo
関数内でp->from.scale
にtextflag
を設定し、textflag
をリセットする変更。src/cmd/cc/cc.h
:profileflg
をtextflag
に変更し、pragprofile
をpragtextflag
に変更。src/cmd/cc/dpchk.c
:pragprofile
関数をpragtextflag
関数にリネームし、profileflg
の代わりにtextflag
を設定するロジックに変更。デフォルト値を7
に設定。src/cmd/cc/lex.c
:main
関数からprofileflg = 1;
の初期化を削除。src/cmd/cc/macbody
:macprag
関数内で、"profile"
プラグマの代わりに"textflag"
プラグマを認識し、pragtextflag
を呼び出すように変更。src/runtime/proc.c
:sys·newproc
,sys·deferproc
,sys·deferreturn
,sys·morestack
の各関数の定義の前に#pragma textflag 7
を追加。
コアとなるコードの解説
src/cmd/6a/lex.c
(アセンブラの字句解析器)
+void
+pragtextflag(void)
+{
+ while(getnsc() != '\n')
+ ;
+}
このコードは、#pragma textflag
がアセンブラの字句解析器で処理される際に、その行の残りの部分を読み飛ばすための関数です。実際の意味付けはCコンパイラ側で行われます。
src/cmd/6c/txt.c
および src/cmd/8c/txt.c
(Cコンパイラのコード生成部分)
--- a/src/cmd/6c/txt.c
+++ b/src/cmd/6c/txt.c
@@ -1473,7 +1473,9 @@ gpseudo(int a, Sym *s, Node *n)
p->as = a;
p->from.type = D_EXTERN;
p->from.sym = s;
- p->from.scale = (profileflg ? 0 : NOPROF);
+ p->from.scale = textflag;
+ textflag = 0;
+
if(s->class == CSTATIC)
p->from.type = D_STATIC;
naddr(n, &p->to);
gpseudo
関数は、擬似命令(ここでは関数のエントリポイントなど)を生成する際に呼び出されます。
変更前は、profileflg
の値に基づいて NOPROF
(No Profiling) フラグを設定していました。
変更後は、グローバル変数 textflag
の値を直接 p->from.scale
に設定しています。p->from.scale
は、生成されるアセンブリコードのシンボルに付与される属性の一部となります。
textflag = 0;
は、#pragma textflag
が適用された直後の関数にのみその効果が及ぶように、フラグをリセットしています。
src/cmd/cc/cc.h
(Cコンパイラのヘッダ)
--- a/src/cmd/cc/cc.h
+++ b/src/cmd/cc/cc.h
@@ -498,7 +498,7 @@ EXTERN Term term[NTERM];
EXTERN int nterm;
EXTERN int packflg;
EXTERN int fproundflg;
-EXTERN int profileflg;
+EXTERN int textflag;
EXTERN int ncontin;
EXTERN int canreach;
EXTERN int warnreach;
@@ -747,7 +747,7 @@ void arginit(void);
void pragvararg(void);
void pragpack(void);
void pragfpround(void);
-void pragprofile(void);
+void pragtextflag(void);
void pragincomplete(void);
profileflg
グローバル変数が textflag
に変更され、関連するプラグマ処理関数も pragprofile
から pragtextflag
に変更されました。
src/cmd/cc/dpchk.c
(Cコンパイラのプラグマ処理部分)
--- a/src/cmd/cc/dpchk.c
+++ b/src/cmd/cc/dpchk.c
@@ -450,25 +450,19 @@ pragfpround(void)
}
void
-pragprofile(void)
+pragtextflag(void)
{
Sym *s;
- profileflg = 0;
+ textflag = 0;
s = getsym();
- if(s) {
- profileflg = atoi(s->name+1);
- if(strcmp(s->name, "on") == 0 ||
- strcmp(s->name, "yes") == 0)
- profileflg = 1;
- }
+ textflag = 7;
+ if(s)
+ textflag = atoi(s->name+1);
while(getnsc() != '\n')
;
if(debug['f'])
- if(profileflg)
- print("%4ld: profileflg %d\n", lineno, profileflg);
- else
- print("%4ld: profileflg off\n", lineno);
+ print("%4ld: textflag %d\n", lineno, textflag);
}
pragprofile
関数が pragtextflag
に変更され、profileflg
を設定するロジックが textflag
を設定するロジックに置き換えられました。
textflag
のデフォルト値は 7
に設定され、もしプラグマに数値が指定されていればその値が使われます。この 7
という値は、Goランタイムの特定の関数に対して、プロファイリングを無効化し、特定のリンケージ属性を付与するためのビットマスクであると考えられます。
src/cmd/cc/lex.c
(Cコンパイラの字句解析器)
--- a/src/cmd/cc/lex.c
+++ b/src/cmd/cc/lex.c
@@ -75,7 +75,6 @@ main(int argc, char *argv[])
ginit();
arginit();
- profileflg = 1; /* #pragma can turn it off */
tufield = simplet((1L<<tfield->etype) | BUNSIGNED);
ndef = 0;
outfile = 0;
main
関数から profileflg = 1;
の初期化が削除されました。これにより、profileflg
は完全に textflag
に置き換えられることになります。
src/cmd/cc/macbody
(Cコンパイラのマクロ処理部分)
--- a/src/cmd/cc/macbody
+++ b/src/cmd/cc/macbody
@@ -725,8 +725,8 @@ macprag(void)
pragfpround();
return;
}
- if(s && strcmp(s->name, "profile") == 0) {
- pragprofile();
+ if(s && strcmp(s->name, "textflag") == 0) {
+ pragtextflag();
return;
}
if(s && strcmp(s->name, "varargck") == 0) {
#pragma profile
が #pragma textflag
に変更され、対応する処理関数も pragprofile
から pragtextflag
に変更されました。
src/runtime/proc.c
(Goランタイムのプロセス管理部分)
--- a/src/runtime/proc.c
+++ b/src/runtime/proc.c
@@ -155,6 +155,7 @@ malg(int32 stacksize)
return g;
}
+#pragma textflag 7
void
sys·newproc(int32 siz, byte* fn, byte* arg0)
{
@@ -204,6 +205,7 @@ sys·newproc(int32 siz, byte* fn, byte* arg0)
//printf(" goid=%d\n", newg->goid);
}
+#pragma textflag 7
void
sys·deferproc(int32 siz, byte* fn, byte* arg0)
{
@@ -219,6 +221,7 @@ sys·deferproc(int32 siz, byte* fn, byte* arg0)
g->defer = d;
}
+#pragma textflag 7
void
sys·deferreturn(int32 arg0)
{
@@ -760,6 +763,7 @@ newstack(void)
*(int32*)345 = 123; // never return
}
+#pragma textflag 7
void
sys·morestack(uint64 u)
{
sys·newproc
, sys·deferproc
, sys·deferreturn
, sys·morestack
といった、ゴルーチンや defer
の実装に不可欠なランタイム関数の定義の直前に #pragma textflag 7
が追加されました。これにより、これらの関数がコンパイルされる際に、textflag
の値 7
が適用され、プロファイリングの抑制や特別なリンケージ属性の付与が行われることで、これらの低レベルなランタイム操作が安定して実行されるようになります。
関連リンク
- Go言語の初期のコンパイラに関する情報 (Plan 9 C compiler):
- Go言語の
go
ステートメントとゴルーチン: - Go言語の
defer
ステートメント:
参考にした情報源リンク
- Go言語のコミット履歴 (GitHub):
- Go言語の
textflag
に関する議論やドキュメント (当時の情報を見つけるのは困難ですが、関連するキーワードで検索):- "Go textflag meaning"
- "Go compiler pragmas"
- "Go runtime newproc deferproc"
- これらのキーワードで当時のGo言語のメーリングリストやIssueトラッカーを検索すると、より詳細な背景情報が見つかる可能性があります。
- Plan 9 C compiler の
pragma
に関する情報:- https://9p.io/sys/doc/compiler.html (上記と同じですが、
pragma
の一般的な概念を理解するのに役立ちます)
- https://9p.io/sys/doc/compiler.html (上記と同じですが、
- Go言語の
runtime
パッケージのソースコード (現在のバージョンでもtextflag
の概念は存在します):- https://github.com/golang/go/tree/master/src/runtime
- 特に
asm_amd64.s
やproc.go
などでtextflag
の使用例が見られます。 src/cmd/internal/obj/textflag.go
にtextflag
の定義があります。//go:linkname
や//go:noescape
など、Go言語のコンパイラが認識するプラグマに関する情報も参考になります。textflag
の値7
は、NOPROF | NOSPLIT | DUPLOK
の組み合わせに相当する可能性が高いです。 *NOPROF
: プロファイリングを無効化。 *NOSPLIT
: スタック分割を無効化(ランタイムの低レベル関数ではスタックの挙動を厳密に制御する必要があるため)。 *DUPLOK
: 複数の定義があっても問題ないことを示す(リンカ向け)。 * これらのフラグは、ランタイムのコア関数が安定して動作するために重要です。