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

[インデックス 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コンパイラです。
  • 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.csrc/cmd/cc/pgen.cの2つのファイルに対する変更を元に戻しています。

src/cmd/8c/swt.c の変更点(元に戻された部分):

このファイルは、switch文のコンパイルロジックを扱います。元のコミットでは、swit1関数内で64ビットのswitch値を処理するためのコードが追加されていました。

元に戻されたコードは、主に以下の部分です。

  1. 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ビットとして扱われるようになります。

  2. 64ビット値の比較ロジック: if(nc < 5) のブロック内と、それ以降の if(debug['W']) のブロック内で、switchcase値との比較(OEQOGT)において、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ビット値を考慮したレジスタ割り当てとコード生成のロジックが追加されていました。

元に戻されたコードは、主に以下の部分です。

  1. 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式が整数型であることを保証するためのものです。元のコミットでは、より厳密な型チェックを導入しようとした可能性がありますが、これも元に戻されました。

  2. 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'])のブロック内で、switchcase値との比較(OEQOGT)において、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ビットビルドの安定性を損ねたため、一時的に元に戻されました。これは、コンパイラの低レベルなコード生成におけるデータ型の扱い、レジスタ割り当て、そしてアーキテクチャ固有の最適化の複雑さを示しています。

関連リンク

参考にした情報源リンク

  • Go言語の初期のコンパイラに関する情報:
  • Plan 9のツールチェインに関する情報:
  • Go言語のswitch文に関する情報:
  • GoのChange List (CL) について:
  • コンパイラのレジスタ割り当てとコード生成に関する一般的な情報:
    • コンパイラ設計に関する教科書(例: 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/ ディレクトリ内のアセンブラに関する情報。