[インデックス 10798] ファイルの概要
このコミットは、Go言語の初期のコンパイラである5c
(ARM), 6c
(x86-64), 8c
(x86) において、switch
文が64ビットの値を適切に扱えるようにするための変更を導入しています。具体的には、switch
文の評価対象となる値が64ビット整数型(long long
やint64
など)である場合に、コンパイラが正しくコードを生成できるように、スイッチ処理のロジックが修正されています。
コミット
commit d89b7173c2e2c919677753f38ed67022ebea175d
Author: Anthony Martin <ality@pbrane.org>
Date: Wed Dec 14 17:30:40 2011 -0500
5c, 6c, 8c: support 64-bit switch value
For real this time. :-)
R=rsc, ken
CC=golang-dev
https://golang.org/cl/5486061
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d89b7173c2e2c919677753f38ed67022ebea175d
元コミット内容
このコミットの元の内容は、Go言語の初期のコンパイラ(5c, 6c, 8c)が、switch
文で64ビットの値をサポートするように修正することです。コミットメッセージには「For real this time. :-)」とあり、以前にも同様の試みがあったものの、今回で完全に実装されたことを示唆しています。
変更の背景
Go言語は、その設計当初から様々なアーキテクチャをサポートすることを目指していました。switch
文はプログラミングにおいて非常に一般的な制御構造であり、その評価対象となる値の型は、言語の表現力と実用性において重要です。特に、64ビットシステムが普及する中で、64ビット整数値をswitch
文で直接扱うことができないのは大きな制約となります。
このコミットが行われた2011年12月は、Go言語がまだ比較的新しい時期であり、コンパイラやランタイムの基盤が活発に開発されていました。初期のGoコンパイラは、Plan 9のCコンパイラツールチェーン(5c, 6c, 8c)をベースにしており、これらのコンパイラはGo言語のコードを機械語に変換する役割を担っていました。
switch
文が32ビット値のみをサポートしていた場合、開発者は64ビット値を扱う際に、明示的な型変換を行ったり、if-else if
の連鎖を使用したりするなど、不便な回避策を講じる必要がありました。これはコードの可読性やパフォーマンスに悪影響を与える可能性がありました。したがって、64ビット値のswitch
サポートは、Go言語の実用性と表現力を向上させる上で不可欠な機能でした。
前提知識の解説
-
5c, 6c, 8c コンパイラ: これらは、Go言語の初期のコンパイラツールチェーンの一部であり、Plan 9オペレーティングシステムのCコンパイラに由来します。
5c
: ARMアーキテクチャ(特にARM v5以降の32ビット)向けのCコンパイラ。6c
: AMD64 (x86-64) アーキテクチャ向けのCコンパイラ。8c
: Intel 386 (x86) アーキテクチャ向けのCコンパイラ。 Go 1.5以降、Goコンパイラ自体がGo言語で書かれるようになり(セルフホスティング)、これらのCコンパイラはGoのメインツールチェーンからは直接使用されなくなりましたが、Goの初期の発展において重要な役割を果たしました。
-
switch
文: プログラミング言語における制御構造の一つで、式の値に基づいて複数のコードブロックの中から一つを実行します。多くの言語では、switch
文の評価対象は整数型や列挙型に限定されることがありますが、Go言語のswitch
文はより柔軟で、任意の型の式を評価できます。 -
64ビット値: 64ビットの幅を持つ整数値です。32ビット値と比較して、より大きな数値を表現できます(約9×10^18まで)。現代の多くのシステムは64ビットアーキテクチャであり、メモリのアドレス指定や大規模な数値計算において64ビット値が広く利用されています。
-
gc.h
: Goコンパイラの共通ヘッダーファイルの一つで、型定義、関数プロトタイプ、マクロなどが含まれています。コンパイラの各部分で共有される重要な宣言がここに集約されています。 -
swt.c
: Goコンパイラのソースファイルの一つで、switch
文のコード生成ロジックを実装しています。switch
文の各case
を効率的に処理するためのジャンプテーブルや比較命令の生成を担当します。 -
pgen.c
: Goコンパイラのソースファイルの一つで、パーサーが生成した抽象構文木(AST)から、より低レベルの中間表現(PCODE)を生成する役割を担っています。型チェックや式の評価順序の決定など、コード生成の前段階の処理が含まれます。
技術的詳細
このコミットの主要な変更点は、switch
文の処理を担う関数swit1
のロジックを修正し、新たにswit2
関数を導入したことです。これにより、switch
文の評価対象が64ビット整数型である場合に、適切な型でレジスタを割り当て、コードを生成できるようになりました。
具体的には、以下の変更が行われています。
-
swit2
関数の導入:src/cmd/5c/gc.h
,src/cmd/6c/gc.h
,src/cmd/8c/gc.h
の各ヘッダーファイルに、void swit2(C1*, int, int32, Node*);
というプロトタイプが追加されました。これは、swit2
関数がswitch
文の実際のコード生成ロジックを担うことを示しています。 -
swit1
関数の役割変更:src/cmd/5c/swt.c
,src/cmd/6c/swt.c
,src/cmd/8c/swt.c
の各swt.c
ファイルにおいて、既存のswit1
関数の実装が変更されました。- 変更前の
swit1
は、switch
文のコード生成の主要なロジックを含んでいました。 - 変更後の
swit1
は、まずswitch
式の型をチェックし、それが64ビット型(typev[n->type->etype]
が真の場合、TVLONG
型)であれば、64ビットレジスタを割り当てます。そうでなければ、通常の32ビットレジスタ(TLONG
型)を割り当てます。 - その後、
cgen
関数を呼び出して式を評価し、結果を割り当てられたレジスタに格納します。 - 最終的に、実際のスイッチ処理は新しく導入された
swit2
関数に委譲されます。これにより、swit1
は型の前処理とレジスタ割り当てに特化し、swit2
が共通のスイッチ処理ロジックを扱うという役割分担がなされました。
- 変更前の
-
swit2
関数でのスイッチ処理:swit2
関数は、swit1
から渡されたレジスタに格納された値と、case
ラベルの値を比較し、適切なジャンプ命令を生成します。この関数内で、再帰的に自身を呼び出すことで、二分探索木のような効率的なスイッチ処理を実現しています。64ビット値の比較もこの関数内で適切に処理されます。 -
src/cmd/cc/pgen.c
の変更:pgen.c
ファイルでは、switch
文の式に対する型チェックロジックが変更されました。- 変更前は、
!typeword[l->type->etype] || l->type->etype == TIND
という条件で、switch
式が整数型であることを確認していました。typeword
は32ビット整数型をチェックするものでした。 - 変更後は、
!typechlvp[l->type->etype] || l->type->etype == TIND
という条件に変わっています。typechlvp
は、文字型、短整数型、長整数型、および64ビット長整数型(long long
)を含む、より広範な整数型をチェックするものです。これにより、switch
式が64ビット整数型であってもエラーにならず、正しく処理されるようになります。 - また、
pgen.c
内のdoswit
関数の呼び出し部分も簡略化され、switch
式のノードを直接doswit
に渡すようになりました。レジスタ割り当てのロジックはswit1
(そしてswit2
)に移動したため、pgen.c
からはその詳細が隠蔽されました。
- 変更前は、
これらの変更により、Goコンパイラはswitch
文において64ビット整数値をネイティブにサポートできるようになり、より堅牢で表現力豊かなコードの生成が可能になりました。
コアとなるコードの変更箇所
このコミットで変更された主要なファイルとコードスニペットは以下の通りです。
1. src/cmd/5c/gc.h
, src/cmd/6c/gc.h
, src/cmd/8c/gc.h
(ヘッダーファイル)
--- a/src/cmd/5c/gc.h
+++ b/src/cmd/5c/gc.h
@@ -304,6 +304,7 @@ void gpseudo(int, Sym*, Node*);
int swcmp(const void*, const void*);
void doswit(Node*);
void swit1(C1*, int, int32, Node*);
+void swit2(C1*, int, int32, Node*);
void newcase(void);
void bitload(Node*, Node*, Node*, Node*, Node*);
void bitstore(Node*, Node*, Node*, Node*, Node*);
swit2
関数のプロトタイプが追加されています。
2. src/cmd/5c/swt.c
, src/cmd/6c/swt.c
, src/cmd/8c/swt.c
(スイッチ処理の実装)
swit1
関数の実装が変更され、swit2
関数が新しく追加されています。
例: src/cmd/5c/swt.c
--- a/src/cmd/5c/swt.c
+++ b/src/cmd/5c/swt.c
@@ -28,11 +28,30 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
-
#include "gc.h"
void
swit1(C1 *q, int nc, int32 def, Node *n)
+{
+ Node nreg;
+
+ if(typev[n->type->etype]) {
+ regsalloc(&nreg, n);
+ nreg.type = types[TVLONG];
+ cgen(n, &nreg);
+ swit2(q, nc, def, &nreg);
+ return;
+ }
+
+ regalloc(&nreg, n, Z);
+ nreg.type = types[TLONG];
+ cgen(n, &nreg);
+ swit2(q, nc, def, &nreg);
+ regfree(&nreg);
+}
+
+void
+swit2(C1 *q, int nc, int32 def, Node *n)
{
C1 *r;
int i;
@@ -65,12 +84,12 @@ swit1(C1 *q, int nc, int32 def, Node *n)
sp = p;
gopcode(OEQ, nodconst(r->val), n, Z); /* just gen the B.EQ */
patch(p, r->label);
- swit1(q, i, def, n);
+ swit2(q, i, def, n);
if(debug['W'])
print("case < %.8ux\\n", r->val);
patch(sp, pc);
- swit1(r+1, nc-i-1, def, n);
+ swit2(r+1, nc-i-1, def, n);
return;
direct:
swit1
がswit2
を呼び出すように変更され、swit2
が実際のスイッチ処理ロジックを保持しています。typev[n->type->etype]
のチェックにより、64ビット型の場合はTVLONG
(64ビット長整数)を、それ以外はTLONG
(32ビット長整数)を使用するように分岐しています。
3. 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(!typeword[l->type->etype] || l->type->etype == TIND) {
+ if(!typechlvp[l->type->etype] || l->type->etype == TIND) {
diag(n, "switch expression must be integer");
break;
}
@@ -320,15 +320,7 @@ loop:
}
patch(sp, pc);
- 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);
+ doswit(l);
patch(spb, pc);
cases = cn;
typeword
がtypechlvp
に変更され、switch
式の型チェックがより広範な整数型に対応しました。また、doswit
の呼び出しが簡略化され、レジスタ割り当てのロジックがswit1
/swit2
に移動したことがわかります。
コアとなるコードの解説
このコミットの核心は、swit1
とswit2
という2つの関数によるswitch
文の処理の分担と、64ビット値の適切な型処理です。
-
swit1
関数: この関数は、switch
文の式(Node *n
)を受け取り、その式の型を検査します。if(typev[n->type->etype])
の条件は、n
の型が64ビット整数型(long long
やint64
など)であるかどうかを判定します。- もし64ビット型であれば、
regsalloc(&nreg, n);
を使って64ビット値を格納できるレジスタ(nreg
)を割り当て、nreg.type = types[TVLONG];
でそのレジスタの型をTVLONG
(Type Value Long、64ビット長整数)に設定します。 - そうでなければ、
regalloc(&nreg, n, Z);
を使って通常のレジスタを割り当て、nreg.type = types[TLONG];
でそのレジスタの型をTLONG
(Type Long、32ビット長整数)に設定します。 その後、cgen(n, &nreg);
を呼び出して、switch
式の値を実際にnreg
に格納する機械語コードを生成します。 最後に、swit2(q, nc, def, &nreg);
を呼び出し、実際のスイッチ処理をswit2
に委譲します。swit1
は、switch
式の評価と、その結果を格納するレジスタの準備に特化しています。
- もし64ビット型であれば、
-
swit2
関数: この関数は、swit1
から渡された、switch
式の値が格納されたレジスタ(Node *n
)と、case
ラベルの情報(C1 *q
,int nc
,int32 def
)を受け取ります。swit2
の内部では、switch
文の各case
を効率的に処理するために、二分探索のようなロジックが実装されています。これは、case
ラベルの値をソートし、中央のcase
値と比較することで、ジャンプ先を絞り込む手法です。gopcode(OEQ, nodconst(r->val), n, Z);
のような行は、switch
式の値(n
)と現在のcase
値(r->val
)を比較し、等しい場合に指定されたラベル(r->label
)にジャンプする機械語命令を生成します。swit2
は再帰的に自身を呼び出すことで、残りのcase
を処理します。この再帰的な構造により、多数のcase
を持つswitch
文でも効率的なコードが生成されます。
pgen.c
の変更は、switch
文の式に対するコンパイラの型チェックを緩和し、64ビット整数型も有効なswitch
式の型として認識するようにしました。これにより、フロントエンド(パーサー)からバックエンド(コード生成)への連携がスムーズになり、64ビットswitch
値のサポートがエンドツーエンドで実現されました。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/
- Go言語の
switch
文に関するドキュメント: https://go.dev/ref/spec#Switch_statements - Go言語の初期のコンパイラに関する情報(Plan 9との関連など)は、Goの歴史に関する記事やGoのソースコードリポジトリの初期のコミット履歴で確認できます。
参考にした情報源リンク
- Go言語の
switch
文の64ビットサポートに関する情報:- https://go.dev/blog/go1.5bootstrapping (Go 1.5でのセルフホスティングに関する記事)
- https://go.dev/doc/go1.5 (Go 1.5リリースノート)
- Plan 9 Cコンパイラ(5c, 6c, 8c)に関する情報:
- https://9p.io/plan9/doc/compiler.html (Plan 9のコンパイラに関するドキュメント)
- https://www.youtube.com/watch?v=rFE_D4t7s0w (Goの歴史に関する講演など)
- Go言語のソースコードリポジトリ: https://github.com/golang/go
- Go言語のコードレビューシステム (Gerrit): https://go.googlesource.com/go/+/refs/heads/master (コミットメッセージに記載されている
https://golang.org/cl/5486061
は、このGerritの変更リストへのリンクです。)