[インデックス 1371] ファイルの概要
このコミットは、Goコンパイラのバックエンドの一部である src/cmd/gc/walk.c
ファイルに対する変更です。walk.c
は、Goコンパイラの「gc」における重要なフェーズである「walk」を担当するファイルです。walkフェーズでは、構文解析によって生成された抽象構文木(AST)を走査し、型チェック後のASTをさらに最適化し、最終的なコード生成に適した中間表現に変換する役割を担います。具体的には、高レベルなGoの構文要素(例えば、配列リテラルやマップリテラルなど)を、より低レベルなコンパイラ内部の操作(メモリ割り当て、変数への代入、インデックスアクセスなど)に展開する処理が行われます。
コミット
commit f38d2b80a4f693aab1c7d3d3750a54aad8c83e7b
Author: Russ Cox <rsc@golang.org>
Date: Thu Dec 18 21:59:12 2008 -0800
new []int literal
R=ken
OCL=21568
CL=21568
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f38d2b80a4f693aab1c7d3d3750a54aad8c83e7b
元コミット内容
new []int literal
R=ken
OCL=21568
CL=21568
変更の背景
このコミットは、Go言語の初期開発段階(2008年12月)において、スライス(動的配列)リテラルのサポートを導入するために行われました。Go言語では、[]int{1, 2, 3}
のような構文でスライスを直接初期化できます。この機能は、コードの記述を簡潔にし、可読性を向上させる上で非常に重要です。
コミットメッセージの「new []int literal」が示す通り、この変更以前は、このようなスライスリテラルを直接記述する構文がコンパイラによって適切に処理されていなかったか、あるいは存在していなかった可能性があります。このコミットは、コンパイラがこの新しい構文を認識し、それを実行可能な低レベルの操作(メモリ割り当て、要素の代入など)に変換できるようにするための基盤を構築するものです。特に、t->bound >= 0
のチェックで「literal fixed arrays not implemented」とあることから、この時点では固定長配列のリテラル(例: [3]int{1, 2, 3}
)はまだサポートされておらず、スライスリテラル([]int{1, 2, 3}
)のみが対象であったことが伺えます。
前提知識の解説
このコミットの理解には、以下のGoコンパイラ(gc)の内部構造と概念に関する知識が役立ちます。
- Goコンパイラ (gc): Go言語の公式コンパイラであり、Goプログラムを機械語に変換します。
gc
は、フロントエンド(構文解析、型チェック)とバックエンド(最適化、コード生成)から構成されます。 - 抽象構文木 (AST): ソースコードの構文構造を木構造で表現したものです。コンパイラの各フェーズでASTが変換され、最終的に実行可能なコードが生成されます。
Node
:gc
コンパイラ内部でASTの各ノードを表すデータ構造です。各Node
は、演算の種類(op
)、関連する型(type
)、子ノード(left
,right
など)などの情報を持っています。Type
: Go言語の型システムにおける型情報を表すデータ構造です。配列の型には、要素の型や配列の長さ(bound
)などの情報が含まれます。walk
フェーズ:gc
コンパイラの主要なフェーズの一つで、型チェック後のASTを走査し、高レベルなGoの構文を低レベルなコンパイラ内部の操作に変換します。例えば、make
関数呼び出しやスライスリテラルなどは、このフェーズで具体的なメモリ割り当てや初期化の操作に展開されます。OAS
(Op Assign): 代入操作を表すNode
の種類です。例:a = b
。ONEW
(Op New): メモリ割り当て操作を表すNode
の種類です。Goのnew
関数に対応し、指定された型のゼロ値で初期化されたメモリを割り当てます。OINDEX
(Op Index): 配列やスライス、マップのインデックスアクセス操作を表すNode
の種類です。例:a[i]
。nod()
: 新しいNode
を作成するためのコンパイラ内部関数です。list()
:Node
のリストを操作するための関数です。コンパイラが生成する一連のステートメントを管理するために使用されます。tempname()
: 一時変数を生成し、それにユニークな名前を割り当てるための関数です。コンパイラが内部的に使用する変数を生成する際に利用されます。nodintconst()
: 整数定数を表すNode
を作成するための関数です。fatal()
: コンパイル時に致命的なエラーが発生した場合に呼び出される関数です。
技術的詳細
このコミットの核心は、arraylit
という新しい関数が導入され、スライスリテラルを処理するロジックが実装された点にあります。
変更前は oldarraylit
という関数が存在していましたが、このコミットでその実装が削除され、代わりに arraylit
という新しい関数が追加されました。この新しい arraylit
関数は、以下のような手順でスライスリテラルをコンパイラ内部の操作に変換します。
-
型情報の取得と検証:
- 入力
Node *n
から、リテラルが表す配列またはスライスの型t
を取得します。 if(t->etype != TARRAY)
で、型が配列型であることを確認します。そうでなければfatal
エラーを発生させます。if(t->bound >= 0)
で、配列の長さが固定されているかどうかをチェックします。この時点では固定長配列リテラルは未実装であるため、固定長配列の場合はfatal
エラーを発生させます。これは、このコミットがスライスリテラル(動的配列)のみを対象としていることを明確に示しています。
- 入力
-
一時変数の生成:
var = nod(OXXX, N, N); tempname(var, t);
によって、新しく作成されるスライスを保持するための一時変数var
を生成します。tempname
は、この一時変数にコンパイラ内部で一意な名前を割り当てます。
-
スライスのメモリ割り当て:
nnew = nod(ONEW, N, N); nnew->type = t;
によって、新しいスライスを割り当てるためのONEW
(new演算子) ノードを作成します。このノードは、スライスの型t
を持ちます。nas = nod(OAS, var, nnew); addtop = list(addtop, nas);
によって、ONEW
で割り当てられた新しいスライスを一時変数var
に代入するOAS
(代入) ノードを作成し、それを現在の関数のトップレベルに追加されるステートメントのリストaddtop
に追加します。これにより、コンパイル時にスライスのメモリが確保され、一時変数にその参照が格納されます。
-
要素の初期化:
while(r != N)
ループを使って、スライスリテラルの各要素を走査します。n->left
は、リテラルの要素を表すNode
のリストです。- ループ内で、
a = nodintconst(idx); a = nod(OINDEX, var, a);
によって、一時変数var
のidx
番目の要素へのインデックスアクセスを表すOINDEX
ノードを作成します。 a = nod(OAS, a, r); addtop = list(addtop, a);
によって、リテラルの現在の要素r
を、先ほど作成したインデックス位置var[idx]
に代入するOAS
ノードを作成し、addtop
リストに追加します。これにより、スライスリテラルの各要素が、割り当てられたメモリ上の適切な位置に初期化されます。idx++;
でインデックスをインクリメントします。
-
スライスの長さの設定:
nnew->left = nodintconst(idx);
は、ONEW
ノードのleft
フィールドに、スライスの最終的な長さ(要素数)を設定しています。これは、Goのスライスが内部的にポインタ、長さ、容量の3つの要素で構成されるため、その長さをコンパイラが認識するために必要です。
-
一時変数の返却:
return var;
によって、初期化されたスライスを保持する一時変数var
を返します。この一時変数は、後続のコンパイルフェーズで利用されます。
この一連の処理により、[]int{1, 2, 3}
のようなGoのスライスリテラルは、コンパイラ内部で以下のような低レベルな操作に展開されます(擬似コード):
// 内部的な一時変数
var tempSlice []int
// スライスのメモリを割り当て
tempSlice = new []int // 実際には、make([]int, 0, 0) のような形でメモリが確保される
// 各要素を代入
tempSlice[0] = 1
tempSlice[1] = 2
tempSlice[2] = 3
// スライスの長さを設定
tempSlice.len = 3 // 内部的な表現
// tempSlice を返す
return tempSlice
コアとなるコードの変更箇所
src/cmd/gc/walk.c
ファイルにおいて、以下の変更が行われました。
--- a/src/cmd/gc/walk.c
+++ b/src/cmd/gc/walk.c
@@ -3450,7 +3450,7 @@ loop:
}
Node*
-arraylit(Node *n)
+oldarraylit(Node *n)
{
Iter saver;
Type *t;
@@ -3500,6 +3500,47 @@ loop:
goto loop;
}
+Node*
+arraylit(Node *n)
+{
+ Iter saver;
+ Type *t;
+ Node *var, *r, *a, *nas, *nnew, *ncon;
+ int idx;
+
+ t = n->type;
+ if(t->etype != TARRAY)
+ fatal("arraylit: not array");
+
+ if(t->bound >= 0)
+ fatal("arraylit: literal fixed arrays not implemented");
+
+ var = nod(OXXX, N, N);
+ tempname(var, t);
+
+ nnew = nod(ONEW, N, N);
+ nnew->type = t;
+
+ nas = nod(OAS, var, nnew);
+ addtop = list(addtop, nas);
+
+ idx = 0;
+ r = listfirst(&saver, &n->left);
+ if(r != N && r->op == OEMPTY)
+ r = N;
+ while(r != N) {
+ // build list of var[c] = expr
+ a = nodintconst(idx);
+ a = nod(OINDEX, var, a);
+ a = nod(OAS, a, r);
+ addtop = list(addtop, a);
+ idx++;
+ r = listnext(&saver);
+ }
+ nnew->left = nodintconst(idx);
+ return var;
+}
+
Node*
maplit(Node *n)
{
コアとなるコードの解説
このコミットの主要な変更は、arraylit
関数の実装です。
oldarraylit
のリネーム/削除: 既存のarraylit
関数がoldarraylit
にリネームされ、その実装は実質的に削除されました。これは、新しいスライスリテラルの処理ロジックを導入するための準備です。- 新しい
arraylit
関数の追加:Node* arraylit(Node *n)
: この関数が、Goのスライスリテラル(例:[]int{1, 2, 3}
)を処理するエントリポイントとなります。引数n
は、ASTにおけるスライスリテラルを表すノードです。Iter saver; Type *t; Node *var, *r, *a, *nas, *nnew, *ncon; int idx;
: 処理に必要な内部変数(イテレータ、型、各種ASTノード、インデックス)を宣言しています。t = n->type;
: 入力ノードn
から、リテラルの型(例:[]int
)を取得します。if(t->etype != TARRAY) fatal("arraylit: not array");
: 取得した型が配列型(スライスも内部的には配列型として扱われる)であることを確認します。if(t->bound >= 0) fatal("arraylit: literal fixed arrays not implemented");
: ここが重要で、この時点では固定長配列のリテラル(例:[3]int{1, 2, 3}
)はサポートされておらず、スライスリテラルのみが対象であることを示しています。t->bound
は配列の固定長を表します。var = nod(OXXX, N, N); tempname(var, t);
: スライスリテラルの結果を保持するための一時変数var
を作成します。tempname
は、この一時変数にコンパイラ内部で一意な名前を割り当てます。nnew = nod(ONEW, N, N); nnew->type = t;
: 新しいスライスをメモリ上に割り当てるためのONEW
(new演算子) ノードを作成します。このノードは、スライスの型t
を持ちます。nas = nod(OAS, var, nnew); addtop = list(addtop, nas);
:ONEW
で割り当てられた新しいスライスを一時変数var
に代入するOAS
(代入) ノードを作成し、現在の関数のトップレベルに追加されるステートメントのリストaddtop
に追加します。これにより、コンパイル時にスライスのメモリが確保され、一時変数にその参照が格納されます。idx = 0; r = listfirst(&saver, &n->left); ... while(r != N) { ... }
: このループは、スライスリテラルの各要素を順に処理します。n->left
は、リテラルの要素を表すASTノードのリストです。a = nodintconst(idx); a = nod(OINDEX, var, a);
: 現在のインデックスidx
を使って、一時変数var
のidx
番目の要素へのインデックスアクセスを表すOINDEX
ノードを作成します。a = nod(OAS, a, r); addtop = list(addtop, a);
: リテラルの現在の要素r
を、先ほど作成したインデックス位置var[idx]
に代入するOAS
ノードを作成し、addtop
リストに追加します。これにより、スライスリテラルの各要素が、割り当てられたメモリ上の適切な位置に初期化されます。idx++;
: 次の要素のためにインデックスをインクリメントします。nnew->left = nodintconst(idx);
:ONEW
ノードのleft
フィールドに、スライスの最終的な長さ(要素数)を設定しています。これは、Goのスライスが内部的にポインタ、長さ、容量の3つの要素で構成されるため、その長さをコンパイラが認識するために必要です。return var;
: 初期化されたスライスを保持する一時変数var
を返します。この一時変数は、後続のコンパイルフェーズで利用されます。
この新しい arraylit
関数は、Goのスライスリテラルを、コンパイラが理解できる低レベルなメモリ割り当てと要素代入のシーケンスに変換する役割を担っています。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/
- Go言語のソースコード(GitHub): https://github.com/golang/go
- Go言語のコンパイラに関する一般的な情報(Go Wiki): https://go.dev/wiki/Compiler
参考にした情報源リンク
- Go言語のソースコード(特に
src/cmd/gc/walk.c
の周辺コード) - Go言語のコンパイラ設計に関する一般的な知識
- Go言語のスライスに関する公式ドキュメント