[インデックス 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言語のスライスに関する公式ドキュメント