[インデックス 10776] ファイルの概要
このコミットは、Go言語のコンパイラ(8c
およびcc
)における変更を元に戻す(undo)ものです。具体的には、以前のコミットである CL 5485063
(ハッシュ 21595dc0395a
)によって導入された、64ビットのswitch
文の値を扱うための変更が、64ビットビルドを壊すという問題を引き起こしたため、その変更を元に戻しています。
コミット
commit c8a5f8841c816b105251919e244ddbd4ca5c38f1
Author: Russ Cox <rsc@golang.org>
Date: Wed Dec 14 00:46:07 2011 -0500
undo CL 5485063 / 21595dc0395a
breaks 64-bit build
««« original CL description
8c: handle 64-bit switch value
Cases must still be 32-bit values, but one thing at a time.
R=ality, ken2, ken
CC=golang-dev
https://golang.org/cl/5485063
»»»
R=ken2
CC=golang-dev
https://golang.org/cl/5488075
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/c8a5f8841c816b105251919e244ddbd4ca5c38f1
元コミット内容
このコミットは、以下の内容を持つ元のコミット(CL 5485063)を元に戻すものです。
元のコミットメッセージ:
8c: handle 64-bit switch value
Cases must still be 32-bit values, but one thing at a time.
この元のコミットは、Goコンパイラの8c
(x86アーキテクチャ向けのコンパイラ)において、switch
文の評価値が64ビットである場合に対応するための変更を導入しようとしました。ただし、case
の値自体はまだ32ビットに限定されるという、段階的な対応であったことが示唆されています。
変更の背景
Go言語の初期のコンパイラは、Plan 9のツールチェインをベースにしていました。8c
はx86アーキテクチャ向けのGoコンパイラ、cc
はCコンパイラを指します。
元のコミット CL 5485063
は、Goコンパイラがswitch
文の式として64ビットの値を扱えるようにするための機能拡張を試みました。これは、Go言語がより広範なデータ型とアーキテクチャをサポートする上で自然な進化の一部です。しかし、この変更が意図せず64ビット環境でのGoのビルドプロセスを破壊するという重大な副作用を引き起こしました。
コンパイラの変更は非常に複雑であり、特に異なるアーキテクチャ(32ビットと64ビット)間でのデータ型の扱い、レジスタ割り当て、コード生成のロジックは慎重に扱う必要があります。このコミットは、導入された変更が64ビットビルドの安定性を損なったため、その問題を解決するために元の変更を迅速に元に戻すことを目的としています。これは、Goプロジェクトにおける安定性と後方互換性を重視する開発プラクティスを反映しています。
前提知識の解説
- Goコンパイラ (
8c
,cc
):- Go言語の初期のコンパイラは、Plan 9のツールチェイン(
8c
,6c
,5c
など)に由来しています。これらはそれぞれx86、amd64、ARMなどのアーキテクチャに対応するCコンパイラでしたが、Goのコンパイラも同様の命名規則で呼ばれていました。 8c
はx86(32ビット)およびx86-64(64ビット)アーキテクチャ向けのGoコンパイラを指すことが多かったです。cc
は、Goのランタイムや一部の標準ライブラリがC言語で書かれていたため、それらをコンパイルするためのCコンパイラです。
- Go言語の初期のコンパイラは、Plan 9のツールチェイン(
switch
文: プログラミング言語における制御構造の一つで、式の値に基づいて複数のコードブロックの中から一つを実行します。コンパイラはswitch
文を効率的な分岐命令(ジャンプテーブルなど)に変換します。- 64ビットビルド: 64ビットアーキテクチャ(例: x86-64)上で動作する実行ファイルを生成するプロセスです。これには、64ビットのレジスタ、ポインタ、データ型を適切に扱うためのコンパイラとランタイムのサポートが必要です。
CL
(Change List): Google内部で使われるコードレビューシステム(Gerritなど)における変更の単位です。Goプロジェクトも初期にはGoogleのインフラを利用していたため、CL
という用語が使われています。src/cmd/8c/swt.c
:8c
コンパイラのソースコードの一部で、switch
文の処理(swit1
関数など)に関連するロジックが含まれています。swt
はおそらく "switch table" や "switch statement" を意味します。src/cmd/cc/pgen.c
:cc
コンパイラのソースコードの一部で、"program generation" または "parser generation" に関連するロジックが含まれている可能性があります。コンパイラのフロントエンド(構文解析)や中間コード生成に関わる部分です。Node
: コンパイラの抽象構文木(AST)におけるノードを表すデータ構造です。プログラムの各要素(変数、定数、演算子、文など)がノードとして表現されます。Type
: コンパイラが扱うデータ型(整数、浮動小数点数、ポインタなど)を表す構造体です。Prog
: コンパイラが生成するアセンブリ命令(または中間表現)を表す構造体です。typev
: 型が値型(value type)であるかどうかを示す配列またはマップ。TLONG
,TVLONG
: Goコンパイラ内部で使われる型定数で、それぞれ通常のlong
型(32ビットまたは64ビット整数)と、long
型の値型(value type long
)を指す可能性があります。nodconst
: 定数ノードを生成する関数。gopcode
: オペコード(命令コード)を生成する関数。regalloc
,regfree
: レジスタを割り当てたり解放したりする関数。コンパイラのレジスタ割り当てフェーズで使用されます。cgen
: コード生成(code generation)を行う関数。ASTノードからアセンブリ命令を生成します。boolgen
: 真偽値の条件分岐コードを生成する関数。patch
: 生成されたコード内のジャンプ命令のターゲットアドレスを修正する関数。gbranch
: 分岐命令を生成する関数。AJEQ
: アセンブリ命令のニーモニックで、"Jump if Equal"(等しければジャンプ)を意味します。
技術的詳細
このコミットは、src/cmd/8c/swt.c
とsrc/cmd/cc/pgen.c
の2つのファイルに対する変更を元に戻しています。
src/cmd/8c/swt.c
の変更点(元に戻された部分):
このファイルは、switch
文のコンパイルロジックを扱います。元のコミットでは、swit1
関数内で64ビットのswitch
値を処理するためのコードが追加されていました。
元に戻されたコードは、主に以下の部分です。
-
64ビット値のレジスタ割り当てとコード生成:
if(typev[n->type->etype]) { if(n->op != ONAME || n->sym != nodsafe->sym) { regsalloc(&nreg, n); nreg.type = types[TVLONG]; cgen(n, &nreg); swit1(q, nc, def, &nreg); return; } } else { if(n->op != OREGISTER) { regalloc(&nreg, n, Z); nreg.type = types[TLONG]; cgen(n, &nreg); swit1(q, nc, def, &nreg); regfree(&nreg); return; } }
このコードブロックは、
switch
式の型が64ビット値(typev[n->type->etype]
が真の場合)であるか、またはレジスタにない場合(n->op != OREGISTER
)に、一時的なレジスタを割り当てて64ビット型(TVLONG
またはTLONG
)としてコードを生成し、再帰的にswit1
を呼び出すロジックでした。これが元に戻されたことで、switch
式の値は以前のように32ビットとして扱われるようになります。 -
64ビット値の比較ロジック:
if(nc < 5)
のブロック内と、それ以降のif(debug['W'])
のブロック内で、switch
のcase
値との比較(OEQ
やOGT
)において、64ビット値の型を考慮したmemset
,n1.op
,n1.left
,n1.right
,boolgen
を使った複雑な比較ロジックが追加されていました。これが元に戻され、シンプルなgopcode
による比較に戻されました。元のコード(元に戻された部分)は、
switch
式の値が64ビット型である場合に、nodconst(q->val)
で生成される定数ノードとの比較を、より汎用的なboolgen
関数を使って行おうとしていました。これは、switch
式の値がレジスタにない場合や、64ビット値である場合に、適切な比較命令を生成するための試みでした。しかし、この変更が64ビットビルドで問題を引き起こしたため、より単純な32ビット値の比較ロジックに戻されました。
src/cmd/cc/pgen.c
の変更点(元に戻された部分):
このファイルは、Cコンパイラのコード生成に関連する部分です。元のコミットでは、switch
文の式を処理する際に、64ビット値を考慮したレジスタ割り当てとコード生成のロジックが追加されていました。
元に戻されたコードは、主に以下の部分です。
-
switch
式の型チェックの変更:- if(!typechlvp[l->type->etype] || l->type->etype == TIND) { + if(!typeword[l->type->etype] || l->type->etype == TIND) {
typechlvp
からtypeword
への変更は、switch
式の型が「文字またはロング値のポインタ」であるかどうかのチェックから、「ワードサイズ(32ビットまたは64ビット)の型」であるかどうかのチェックに変更されました。これは、switch
式が整数型であることを保証するためのものです。元のコミットでは、より厳密な型チェックを導入しようとした可能性がありますが、これも元に戻されました。 -
switch
式のレジスタ割り当てとコード生成の変更:- doswit(l); + regalloc(&nod, l, Z); + /* always signed */ + if(typev[l->type->etype]) + nod.type = types[TVLONG]; + else + nod.type = types[TLONG]; + cgen(l, &nod); + doswit(&nod); + regfree(&nod);
元のコミットでは、
switch
式の値l
を直接doswit
関数に渡すのではなく、一時的なノードnod
を割り当て、その型をTVLONG
またはTLONG
(64ビットまたは32ビットのロング型)に設定し、cgen
でコードを生成してからdoswit
に渡すように変更されていました。これは、switch
式の値が64ビットである可能性を考慮し、適切な型でレジスタにロードしてからswitch
処理を行うための変更でした。この変更が元に戻されたことで、switch
式の値は以前のようにdoswit
に直接渡され、その型はdoswit
内部で処理されることになります。
これらの変更は、Goコンパイラがswitch
文の値を64ビットとして適切に処理しようとする試みでしたが、その実装が64ビットビルドの安定性を損ねたため、一時的に元に戻されました。これは、コンパイラの低レベルなコード生成におけるデータ型の扱い、レジスタ割り当て、そしてアーキテクチャ固有の最適化の複雑さを示しています。
コアとなるコードの変更箇所
src/cmd/8c/swt.c
--- a/src/cmd/8c/swt.c
+++ b/src/cmd/8c/swt.c
@@ -36,40 +36,12 @@ swit1(C1 *q, int nc, int32 def, Node *n)
C1 *r;
int i;
Prog *sp;
- Node n1, nreg, ncon;
-
- if(typev[n->type->etype]) {
- if(n->op != ONAME || n->sym != nodsafe->sym) {
- regsalloc(&nreg, n);
- nreg.type = types[TVLONG];
- cgen(n, &nreg);
- swit1(q, nc, def, &nreg);
- return;
- }
- } else {
- if(n->op != OREGISTER) {
- regalloc(&nreg, n, Z);
- nreg.type = types[TLONG];
- cgen(n, &nreg);
- swit1(q, nc, def, &nreg);
- regfree(&nreg);
- return;
- }
- }
if(nc < 5) {
for(i=0; i<nc; i++) {
if(debug['W'])
print("case = %.8ux\\n", q->val);
- if(n->type && typev[n->type->etype]) {
- memset(&n1, 0, sizeof n1);
- n1.op = OEQ;
- n1.left = n;
- ncon = *nodconst(q->val);
- n1.right = &ncon;
- boolgen(&n1, 1, Z);
- } else
- gopcode(OEQ, n->type, n, nodconst(q->val));
+ gopcode(OEQ, n->type, n, nodconst(q->val));
patch(p, q->label);
q++;
}
@@ -81,22 +53,10 @@ swit1(C1 *q, int nc, int32 def, Node *n)
r = q+i;
if(debug['W'])
print("case > %.8ux\\n", r->val);
- if(n->type && typev[n->type->etype]) {
- memset(&n1, 0, sizeof n1);
- n1.op = OGT;
- n1.left = n;
- ncon = *nodconst(r->val);
- n1.right = &ncon;
- boolgen(&n1, 1, Z);
- sp = p;
- n1.op = OEQ;
- boolgen(&n1, 1, Z);
- } else {
- gopcode(OGT, n->type, n, nodconst(r->val));
- sp = p;
- gbranch(OGOTO);
- p->as = AJEQ;
- }
+ gopcode(OGT, n->type, n, nodconst(r->val));
+ sp = p;
+ gbranch(OGOTO);
+ p->as = AJEQ;
patch(p, r->label);
swit1(q, i, def, n);
src/cmd/cc/pgen.c
--- a/src/cmd/cc/pgen.c
+++ b/src/cmd/cc/pgen.c
@@ -293,7 +293,7 @@ loop:
complex(l);
if(l->type == T)
break;
- if(!typechlvp[l->type->etype] || l->type->etype == TIND) {
+ if(!typeword[l->type->etype] || l->type->etype == TIND) {
diag(n, "switch expression must be integer");
break;
}
@@ -320,7 +320,15 @@ loop:
}
patch(sp, pc);
- doswit(l);
+ regalloc(&nod, l, Z);
+ /* always signed */
+ if(typev[l->type->etype])
+ nod.type = types[TVLONG];
+ else
+ nod.type = types[TLONG];
+ cgen(l, &nod);
+ doswit(&nod);
+ regfree(&nod);
patch(spb, pc);
cases = cn;
コアとなるコードの解説
このコミットは、以前のコミットで導入された64ビットswitch
値のサポートに関するコードを削除することで、その変更を元に戻しています。
src/cmd/8c/swt.c
の変更点:
swit1
関数の冒頭部分の削除:- 元のコミットでは、
switch
式の型が64ビット値であるか、またはレジスタにない場合に、一時的なレジスタを割り当てて64ビット型としてコードを生成し、再帰的にswit1
を呼び出すロジックが追加されていました。このロジックは、switch
式の値を64ビットとして適切に処理するためのものでしたが、削除されました。これにより、switch
式の値は以前と同様に32ビットとして扱われるようになります。
- 元のコミットでは、
case
値との比較ロジックの簡素化:if(nc < 5)
のブロック内と、それ以降のif(debug['W'])
のブロック内で、switch
のcase
値との比較(OEQ
やOGT
)において、64ビット値の型を考慮した複雑なboolgen
を使った比較ロジックが削除され、シンプルなgopcode
による比較に戻されました。これは、64ビット値の比較が64ビットビルドを壊したため、より安定した32ビット値の比較に戻したことを意味します。
src/cmd/cc/pgen.c
の変更点:
switch
式の型チェックの変更の巻き戻し:typechlvp
からtypeword
への変更が元に戻されました。これにより、switch
式の型が「ワードサイズ(32ビットまたは64ビット)の型」であるかどうかのチェックから、以前の「文字またはロング値のポインタ」であるかどうかのチェックに戻されました。これは、switch
式が整数型であることを保証するための型チェックの厳密化が、何らかの理由で問題を引き起こしたため、元に戻されたと考えられます。
switch
式のレジスタ割り当てとコード生成の変更の巻き戻し:switch
式の値l
を直接doswit
関数に渡すのではなく、一時的なノードnod
を割り当て、その型をTVLONG
またはTLONG
に設定し、cgen
でコードを生成してからdoswit
に渡すように変更されていた部分が削除されました。これにより、switch
式の値は以前のようにdoswit
に直接渡され、その型はdoswit
内部で処理されることになります。これは、64ビット値のレジスタ割り当てとコード生成が64ビットビルドを壊したため、元に戻されたことを意味します。
これらの変更は、Goコンパイラがswitch
文の値を64ビットとして適切に処理しようとする試みでしたが、その実装が64ビットビルドの安定性を損ねたため、一時的に元に戻されました。これは、コンパイラの低レベルなコード生成におけるデータ型の扱い、レジスタ割り当て、そしてアーキテクチャ固有の最適化の複雑さを示しています。
関連リンク
- 元の変更リスト (CL 5485063): https://golang.org/cl/5485063
- このコミットの変更リスト (CL 5488075): https://golang.org/cl/5488075
参考にした情報源リンク
- Go言語の初期のコンパイラに関する情報:
- https://go.dev/doc/install/source (Goのソースからのビルドに関するドキュメント)
- https://go.dev/blog/go-compiler-internals (Goコンパイラの内部に関するブログ記事)
- Plan 9のツールチェインに関する情報:
- https://9p.io/sys/doc/compiler.html (Plan 9のコンパイラに関するドキュメント)
- Go言語の
switch
文に関する情報:- https://go.dev/ref/spec#Switch_statements (Go言語仕様の
switch
文のセクション)
- https://go.dev/ref/spec#Switch_statements (Go言語仕様の
- GoのChange List (CL) について:
- https://go.dev/doc/contribute#code_review (Goへの貢献に関するドキュメント、コードレビュープロセスについて)
- コンパイラのレジスタ割り当てとコード生成に関する一般的な情報:
- コンパイラ設計に関する教科書(例: Dragon Book - "Compilers: Principles, Techniques, and Tools")
- オンラインのコンパイラ開発に関するチュートリアルや記事
memset
,gopcode
,regalloc
,cgen
,boolgen
,patch
,gbranch
,AJEQ
などのGoコンパイラ内部の関数や定数については、Goコンパイラのソースコード自体が最も正確な情報源となります。- https://github.com/golang/go (Go言語のGitHubリポジトリ)
- 特に
src/cmd/8c/
およびsrc/cmd/cc/
ディレクトリ内のファイル。 src/cmd/internal/obj/
ディレクトリ内のオブジェクトファイル形式や命令セットに関する定義。src/cmd/compile/internal/
ディレクトリ内のコンパイラの内部実装。src/cmd/asm/
ディレクトリ内のアセンブラに関する情報。