[インデックス 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のメモリレイアウトやデータセクションに関する技術ブログなどを参照しました。)