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

[インデックス 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 パッケージ内の newprocdeferproc など、ゴルーチンや遅延実行の内部処理を担う関数)がコンパイルされる際に、通常の関数と同様のコード生成が行われていました。しかし、これらの関数はランタイムの非常に低レベルな部分で動作するため、特定の最適化やコード生成の挙動が問題を引き起こす可能性がありました。具体的には、プロファイリングのためのコード挿入や、特定のスタックフレームの扱いが、godefer の内部ロジックと衝突し、潜在的なバグ(例えば、スタックの破損や不正な関数呼び出し)を引き起こす可能性があったと考えられます。

textflag は、これらのランタイムのコアとなる関数に対して、コンパイラが特別なコード生成を行うように指示するためのメカニズムとして導入されました。これにより、プロファイリングコードの挿入を抑制したり、特定のリンケージ情報を付与したりすることで、go および defer の安定性と正確性を確保することが目的でした。

前提知識の解説

Go言語の初期コンパイラ (6a, 6c, 8c, cc)

Go言語の初期のコンパイラは、現在の go tool compile とは異なり、ターゲットアーキテクチャごとに独立したツールとして存在していました。

  • 6a: Plan 9 a (アセンブラ) の Go バージョン。x86-64 (amd64) アーキテクチャ用のアセンブラ。
  • 6c: Plan 9 c (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 に似た形式も使用されていました。これらは、コンパイラの最適化の挙動を変更したり、特定のコード生成ルールを適用したりするために使われます。

textflagprofileflg

  • textflag: このコミットで導入された新しい概念です。関数のコードセクション(テキストセクション)に付与されるフラグで、コンパイラやリンカに対して特定の挙動を指示します。例えば、プロファイリングコードの挿入を抑制したり、特定のリンケージ属性を設定したりするために使用されます。このコミットでは、特にランタイムのコア関数に対して、プロファイリングを無効化し、特別なリンケージ属性(7という値が設定されていることから、おそらく NOPROFRODATA など、特定のセクションへの配置や最適化を指示するビットフラグの組み合わせ)を付与する目的で使われています。
  • profileflg: 以前から存在したフラグで、プロファイリングの有効/無効を制御していました。このコミットでは、profileflg の代わりに textflag を使用することで、よりきめ細やかな制御を可能にしています。特に、godefer の内部関数では、プロファイリングが意図しない副作用を引き起こす可能性があったため、これを無効化する必要がありました。

技術的詳細

このコミットの核心は、Goコンパイラとランタイムが、go および defer の内部処理を担う特定の関数(例: runtime.newproc, runtime.deferproc, runtime.deferreturn, runtime.morestack)をコンパイルする際に、特別な textflag を付与するように変更した点にあります。

  1. #pragma textflag の導入:

    • src/cmd/cc/cc.htextflag という新しいグローバル変数が導入され、pragtextflag という新しいプラグマ処理関数が宣言されました。
    • src/cmd/cc/macbody では、#pragma textflag がコンパイラによって認識され、pragtextflag 関数が呼び出されるように変更されました。
    • src/cmd/cc/dpchk.cpragtextflag 関数の実装が追加されました。この関数は、#pragma textflag の後に続く数値(デフォルトは 7)を textflag グローバル変数に設定します。これにより、後続の関数定義に対してこのフラグが適用されます。
  2. profileflg から textflag への移行:

    • 以前は profileflg というフラグがプロファイリングの有効/無効を制御していましたが、このコミットでは profileflg の使用が廃止され、textflag に置き換えられました。
    • src/cmd/cc/lex.c では、main 関数から profileflg = 1; の初期化が削除されました。
    • src/cmd/cc/dpchk.cpragtextflag 関数内で、以前 pragprofile が行っていた profileflg の設定ロジックが textflag の設定に置き換えられました。
  3. コンパイラバックエンドでの 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 の値をここに設定することで、ランタイムのコア関数が特別な属性を持つコードとして扱われるようになります。
  4. ランタイムコードへの textflag の適用:

    • src/runtime/proc.c にある sys·newproc (ゴルーチン生成), sys·deferproc (defer処理の開始), sys·deferreturn (defer処理の終了), sys·morestack (スタック拡張) といった重要なランタイム関数に対して、#pragma textflag 7 が追加されました。
    • これにより、これらの関数がコンパイルされる際に、コンパイラは textflag の値 7p->from.scale に設定し、特別なコード生成ルールが適用されるようになります。具体的には、プロファイリングコードの挿入が抑制され、これらの関数がランタイムの低レベルな操作を安全に行えるようになります。

この変更により、godefer の内部処理が、プロファイリングなどの外部要因に影響されずに、常に期待通りに動作することが保証され、潜在的なバグが修正されました。

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

  • src/cmd/6a/lex.c: pragtextflag 関数の宣言と、../cc/lexbody および ../cc/macbody のインクルード。
  • src/cmd/6c/txt.c: gpseudo 関数内で p->from.scaletextflag を設定し、textflag をリセットする変更。
  • src/cmd/8c/txt.c: src/cmd/6c/txt.c と同様に、gpseudo 関数内で p->from.scaletextflag を設定し、textflag をリセットする変更。
  • src/cmd/cc/cc.h: profileflgtextflag に変更し、pragprofilepragtextflag に変更。
  • 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言語のコミット履歴 (GitHub):
  • Go言語の textflag に関する議論やドキュメント (当時の情報を見つけるのは困難ですが、関連するキーワードで検索):
    • "Go textflag meaning"
    • "Go compiler pragmas"
    • "Go runtime newproc deferproc"
    • これらのキーワードで当時のGo言語のメーリングリストやIssueトラッカーを検索すると、より詳細な背景情報が見つかる可能性があります。
  • Plan 9 C compiler の pragma に関する情報:
  • Go言語の runtime パッケージのソースコード (現在のバージョンでも textflag の概念は存在します):
    • https://github.com/golang/go/tree/master/src/runtime
    • 特に asm_amd64.sproc.go などで textflag の使用例が見られます。
    • src/cmd/internal/obj/textflag.gotextflag の定義があります。
    • //go:linkname//go:noescape など、Go言語のコンパイラが認識するプラグマに関する情報も参考になります。
    • textflag の値 7 は、NOPROF | NOSPLIT | DUPLOK の組み合わせに相当する可能性が高いです。 * NOPROF: プロファイリングを無効化。 * NOSPLIT: スタック分割を無効化(ランタイムの低レベル関数ではスタックの挙動を厳密に制御する必要があるため)。 * DUPLOK: 複数の定義があっても問題ないことを示す(リンカ向け)。 * これらのフラグは、ランタイムのコア関数が安定して動作するために重要です。