[インデックス 16487] ファイルの概要
このコミットは、Goコンパイラのcmd/5c (ARM), cmd/6c (x86-64), cmd/8c (x86) におけるtextflagとdataflagの扱いを分離し、#5419で報告されたバグを修正するものです。具体的には、AGLOBL(グローバルデータ)とATEXT(テキスト/コード)の擬似命令が、それぞれ適切なフラグ(dataflagとtextflag)を使用するように変更されています。
コミット
commit 2449909d816de46bc43065e6e93fe801c7770055
Author: Anthony Martin <ality@pbrane.org>
Date: Tue Jun 4 15:18:02 2013 -0700
cmd/5c, cmd/6c, cmd/8c: isolate textflag and dataflag
Fixes #5419.
R=golang-dev, dave, minux.ma, rsc
CC=golang-dev
https://golang.org/cl/9241044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/2449909d816de46bc43065e6e93fe801c7770055
元コミット内容
cmd/5c, cmd/6c, cmd/8c: isolate textflag and dataflag
このコミットは、textflagとdataflagを分離します。
#5419を修正します。
変更の背景
このコミットは、GoのIssue #5419「cmd/gc: dataflag is not reset after AGLOBL」を修正するために行われました。このIssueは、コンパイラがグローバルデータを処理する際に使用するdataflagが、その後のテキストセクション(コード)の処理に誤って影響を与えてしまうというバグを報告しています。
具体的には、Goコンパイラのバックエンド(cmd/5c, cmd/6c, cmd/8cなど)では、シンボル(変数や関数など)に関するメタデータをフラグとして管理していました。textflagは関数のプロパティ(例: NOSPLITなど)を、dataflagはグローバル変数のプロパティ(例: RODATAなど)を示すために使われます。
元のコードでは、AGLOBL擬似命令(グローバルデータの定義)を処理する際に、s->dataflagの値を一時的にtextflagに設定し、その後gpseudo関数を呼び出していました。しかし、gpseudoの呼び出し後、textflagが適切にリセットされていなかったため、次にATEXT擬似命令(関数の定義)が処理される際に、誤ったtextflagの値が使用されてしまい、結果として生成されるアセンブリコードに問題が生じていました。例えば、グローバル変数がRODATA(読み取り専用データ)としてマークされている場合、そのdataflagがtextflagにコピーされ、その後の関数が誤ってRODATAとして扱われる可能性がありました。
このバグは、特にGoのコンパイラが生成するアセンブリコードの正確性に影響を与え、プログラムの実行時エラーや予期せぬ動作を引き起こす可能性がありました。
前提知識の解説
このコミットを理解するためには、以下のGoコンパイラの内部構造と概念に関する知識が必要です。
-
Goコンパイラのアーキテクチャ: Goコンパイラは、フロントエンド(
cmd/gc)とバックエンド(cmd/5c,cmd/6c,cmd/8cなど)に分かれています。cmd/gc: Goソースコードを解析し、中間表現(AST)を生成し、型チェックや最適化を行います。最終的に、各アーキテクチャ固有のバックエンドが処理できる形式に変換します。cmd/5c,cmd/6c,cmd/8c: それぞれARM (5c), x86-64 (6c), x86 (8c) アーキテクチャ向けのアセンブラコードを生成するバックエンドコンパイラです。これらは、cmd/gcから渡された中間表現を元に、最終的な機械語に近いアセンブリ命令を生成します。
-
擬似命令 (Pseudo-instructions): コンパイラがアセンブリコードを生成する際に使用する、実際のマシン命令ではないが、アセンブラによって特別な意味を持つ命令です。
ATEXT: 関数の開始を示す擬似命令です。これに続く命令は、その関数のコードとして扱われます。AGLOBL: グローバル変数の定義を示す擬似命令です。これに続くデータは、プログラムのグローバルデータセクションに配置されます。
-
Sym構造体: コンパイラ内部でシンボル(変数、関数、型など)を表す構造体です。この構造体には、シンボルの名前、型、クラス(グローバル、ローカルなど)、そしてdataflagなどのプロパティが含まれます。s->dataflag: シンボルがグローバルデータである場合の、そのデータに関するフラグ(例: 読み取り専用、初期化済みなど)を格納します。
-
textflag: コンパイラが関数を処理する際に使用する内部的なフラグです。関数のプロパティ(例:NOSPLIT- スタックを拡張しない、WRAPPER- ラッパー関数など)を制御するために使われます。これは、アセンブリコード生成時にATEXT命令の引数として渡されることがあります。 -
gpseudo関数: Goコンパイラのバックエンドで、擬似命令(ATEXT,AGLOBLなど)を処理し、アセンブリ命令を生成するためのヘルパー関数です。 -
gclean関数: コンパイラのコード生成フェーズの一部で、シンボルテーブルを走査し、グローバル変数などの処理を行う関数です。この関数内でAGLOBL擬似命令が生成されることがあります。
これらの概念を理解することで、textflagとdataflagがどのように使用され、なぜそれらの分離が必要だったのかが明確になります。
技術的詳細
このコミットの技術的な核心は、textflagとdataflagという2つの異なる目的を持つフラグが、gclean関数とgpseudo関数内で誤って共有されていた問題を解決することにあります。
元のコードでは、gclean関数内でグローバルシンボル(AGLOBL)を処理する際に、以下のようなロジックがありました。
// src/cmd/5c/txt.c (変更前)
// ...
textflag = s->dataflag; // グローバルシンボルのdataflagをtextflagにコピー
gpseudo(AGLOBL, s, nodconst(s->type->width));
textflag = 0; // textflagをリセット
// ...
このコードの問題点は、gpseudo関数がAGLOBLを処理する際にtextflagを使用し、その後textflagを0にリセットしている点です。しかし、gpseudo関数内部でATEXTとAGLOBLの処理が分岐しておらず、textflagがグローバルな状態として扱われていました。
さらに重要なのは、gpseudo関数自体がATEXTとAGLOBLの処理でtextflag(またはそれに相当するレジスタ/スケール値)をどのように設定するかという点です。
変更前のgpseudo関数(cmd/5cの例):
// src/cmd/5c/txt.c (変更前)
// ...
if(a == ATEXT || a == AGLOBL) {
p->reg = textflag;
textflag = 0;
}
// ...
このロジックでは、ATEXTとAGLOBLの両方でp->reg = textflag;が実行され、その後textflag = 0;でリセットされていました。しかし、AGLOBLの場合にtextflagにs->dataflagが設定されていると、その値がp->regにコピーされ、その後textflagが0にリセットされます。このリセットは一見正しいように見えますが、gclean関数内でAGLOBLが処理された直後に、別の関数(ATEXT)が処理される場合、textflagが意図しない値(s->dataflag)を保持している可能性がありました。
このコミットでは、この問題を解決するために、gpseudo関数内でATEXTとAGLOBLの処理をswitch文で明確に分離しました。
変更後のgpseudo関数(cmd/5cの例):
// src/cmd/5c/txt.c (変更後)
// ...
switch(a) {
case ATEXT:
p->reg = textflag;
textflag = 0;
break;
case AGLOBL:
p->reg = s->dataflag; // AGLOBLの場合は直接s->dataflagを使用
break;
}
// ...
この変更により、AGLOBLの場合はs->dataflagが直接p->regに設定され、textflagは変更されなくなりました。これにより、gclean関数内でtextflag = s->dataflag;とtextflag = 0;という一時的な設定とリセットの行が不要になり、削除されました。
同様の変更がsrc/cmd/6c/txt.cとsrc/cmd/8c/txt.cにも適用されています。6cと8cではp->from.scaleがtextflagやdataflagの値を保持する役割を担っていますが、基本的なロジックは同じです。
この修正により、textflagは関数のプロパティのみを、dataflagはグローバルデータのプロパティのみを適切に反映するようになり、コンパイラが生成するアセンブリコードの正確性が向上しました。
コアとなるコードの変更箇所
このコミットによる主要な変更は、以下の3つのファイルにわたっています。
src/cmd/5c/txt.c(ARMアーキテクチャ向けコンパイラ)src/cmd/6c/txt.c(x86-64アーキテクチャ向けコンパイラ)src/cmd/8c/txt.c(x86アーキテクチャ向けコンパイラ)
それぞれのファイルで、gclean関数とgpseudo関数が変更されています。
src/cmd/5c/txt.c
gclean関数内の変更
--- a/src/cmd/5c/txt.c
+++ b/src/cmd/5c/txt.c
@@ -139,9 +139,7 @@ gclean(void)
continue;
if(s->type == types[TENUM])
continue;
- textflag = s->dataflag;
gpseudo(AGLOBL, s, nodconst(s->type->width));
- textflag = 0;
}
nextpc();
p->as = AEND;
textflag = s->dataflag;の行が削除されました。textflag = 0;の行が削除されました。
gpseudo関数内の変更
--- a/src/cmd/5c/txt.c
+++ b/src/cmd/5c/txt.c
@@ -1181,10 +1179,17 @@ gpseudo(int a, Sym *s, Node *n)
p->from.type = D_OREG;
p->from.sym = s;
p->from.name = D_EXTERN;
- if(a == ATEXT || a == AGLOBL) {
- p->reg = textflag;
- textflag = 0;
- }
+
+ switch(a) {
+ case ATEXT:
+ p->reg = textflag;
+ textflag = 0;
+ break;
+ case AGLOBL:
+ p->reg = s->dataflag;
+ break;
+ }
+
if(s->class == CSTATIC)
p->from.name = D_STATIC;
naddr(n, &p->to);
if(a == ATEXT || a == AGLOBL)ブロックがswitch(a)ブロックに置き換えられました。ATEXTのケースでは、p->reg = textflag;とtextflag = 0;が維持されます。AGLOBLのケースが追加され、p->reg = s->dataflag;となり、textflagは変更されません。
src/cmd/6c/txt.c および src/cmd/8c/txt.c
これらのファイルでも、src/cmd/5c/txt.c と同様の変更が行われています。
gclean関数からtextflag = s->dataflag;とtextflag = 0;の行が削除されました。gpseudo関数内のif(a == ATEXT || a == AGLOBL)ブロックがswitch(a)ブロックに置き換えられ、ATEXTとAGLOBLの処理が分離されました。6cと8cでは、p->from.scaleがフラグの値を保持する役割を担っていますが、ロジックは5cのp->regと同様です。
コアとなるコードの解説
このコミットの核心は、Goコンパイラのバックエンドにおけるtextflagとdataflagの役割を明確に分離し、それぞれのフラグが意図された目的のためにのみ使用されるようにした点にあります。
gclean関数からの変更
gclean関数は、コンパイルの最終段階でシンボルテーブルを走査し、グローバル変数などの処理を行う役割を担っています。変更前は、この関数内でグローバルシンボル(AGLOBL)を処理する際に、一時的にs->dataflagをtextflagにコピーし、その後gpseudoを呼び出し、最後にtextflagをリセットしていました。
// 変更前 (例: src/cmd/5c/txt.c)
textflag = s->dataflag; // グローバルデータのフラグをtextflagに一時的に設定
gpseudo(AGLOBL, s, nodconst(s->type->width));
textflag = 0; // textflagをリセット
このコードは、AGLOBLの処理中にtextflagを汚染し、その後のATEXT(関数定義)の処理に影響を与える可能性がありました。今回の修正では、gpseudo関数内でAGLOBLの処理がtextflagに依存しないように変更されたため、gclean関数内でtextflagを一時的に設定・リセットする必要がなくなり、これらの行が削除されました。これにより、gclean関数はよりクリーンになり、textflagのグローバルな状態への影響がなくなりました。
gpseudo関数内の変更
gpseudo関数は、ATEXTやAGLOBLといった擬似命令を処理し、最終的なアセンブリ命令を生成する役割を担っています。変更前は、ATEXTとAGLOBLの両方で共通のロジックを使用してtextflagの値をアセンブリ命令のプロパティ(p->regまたはp->from.scale)に設定し、その後textflagをリセットしていました。
// 変更前 (例: src/cmd/5c/txt.c)
if(a == ATEXT || a == AGLOBL) {
p->reg = textflag; // textflagの値をアセンブリ命令に設定
textflag = 0; // textflagをリセット
}
この共通ロジックが問題でした。AGLOBLの場合、本来はs->dataflagの値がアセンブリ命令に反映されるべきであり、textflagの値は関係ありません。しかし、この共通ロジックではtextflagが使用されていました。
今回の修正では、この共通ロジックをswitch文に置き換え、ATEXTとAGLOBLの処理を明確に分離しました。
// 変更後 (例: src/cmd/5c/txt.c)
switch(a) {
case ATEXT:
p->reg = textflag; // ATEXTの場合はtextflagを使用
textflag = 0;
break;
case AGLOBL:
p->reg = s->dataflag; // AGLOBLの場合は直接s->dataflagを使用
break;
}
この変更により、以下の点が改善されました。
- 役割の明確化:
ATEXTはtextflagを、AGLOBLはs->dataflagをそれぞれ適切に使用するようになりました。これにより、フラグの役割が明確になり、混同がなくなりました。 - 状態の分離:
AGLOBLの処理がtextflagのグローバルな状態に影響を与えなくなりました。これにより、AGLOBLの後に続くATEXTの処理が、以前のAGLOBLのdataflagの値に誤って影響されることがなくなりました。 - コードの簡素化:
gclean関数から不要なtextflagの設定・リセットの行が削除され、コードがより簡潔になりました。
この修正は、Goコンパイラの内部的な正確性を高め、特に異なる種類のシンボル(関数とグローバル変数)のプロパティが正しくアセンブリコードに反映されるようにするために重要でした。
関連リンク
- Go Issue #5419: cmd/gc: dataflag is not reset after AGLOBL
- Go CL 9241044: cmd/5c, cmd/6c, cmd/8c: isolate textflag and dataflag
参考にした情報源リンク
- Go Issue #5419
- Go CL 9241044
- Goコンパイラのソースコード (特に
src/cmd/5c/txt.c,src/cmd/6c/txt.c,src/cmd/8c/txt.cの変更履歴) - Go言語のコンパイラに関する一般的なドキュメントや解説記事 (Goコンパイラの内部構造、アセンブリ生成など)
- (具体的なURLは省略しますが、Goの公式ドキュメントやGoコンパイラのソースコードリーディングに関するブログ記事などを参照しました。)
- Go言語の
textflagに関する情報 (例:go tool compile -Sの出力や、Goのランタイムに関するドキュメント)- (具体的なURLは省略しますが、Goのランタイムやコンパイラの詳細に関する技術ブログなどを参照しました。)
- Go言語の
dataflagに関する情報 (例: グローバル変数の初期化やデータセクションに関する情報)- (具体的なURLは省略しますが、Goのメモリレイアウトやデータセクションに関する技術ブログなどを参照しました。)