[インデックス 1024] ファイルの概要
コミット
commit 74427c63467010cb6aeec6dc315f64319085e545
Author: Ken Thompson <ken@golang.org>
Date: Sat Nov 1 16:36:46 2008 -0700
DOTDOTDOT
R=r
OCL=18317
CL=18317
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/74427c63467010cb6aeec6dc315f64319085e545
元コミット内容
DOTDOTDOT
R=r
OCL=18317
CL=18317
変更の背景
このコミットは、Go言語の初期開発段階において、可変長引数(variadic arguments)のサポートを導入するためのものです。コミットメッセージの「DOTDOTDOT」は、Go言語で可変長引数を表す ...
記法を指しています。
Go言語では、関数が不定数の引数を受け入れることを可能にするために、この可変長引数機能が設計されました。これにより、例えば fmt.Println
のように、引数の数が実行時に決定されるような柔軟な関数を定義できるようになります。
このコミットが行われた2008年11月は、Go言語がまだ一般に公開される前の非常に初期の段階であり、言語の基本的な機能や型システムが構築されている最中でした。可変長引数は、多くのプログラミング言語において共通して存在する強力な機能であり、Go言語においてもその導入は必須と考えられたため、この時期に実装が進められました。
前提知識の解説
1. Go言語のコンパイラ構造 (初期)
このコミットは、Go言語のコンパイラのソースコード (src/cmd/6g
, src/cmd/gc
) に変更を加えています。当時のGoコンパイラは、主に以下のコンポーネントで構成されていました。
6g
: x86-64アーキテクチャ向けのGoコンパイラのフロントエンド。型チェック、AST (Abstract Syntax Tree) の構築、最適化などを行います。gc
: Goコンパイラの共通部分。型システム、シンボルテーブル、コード生成のバックエンドなど、アーキテクチャに依存しない部分を扱います。
2. 型システムと Type
構造体
Goコンパイラ内部では、プログラム中のすべてのエンティティ(変数、関数、型など)が Node
や Type
といった内部表現で扱われます。Type
構造体は、Go言語の型システムを表現するための中心的なデータ構造であり、型の種類(整数、文字列、ポインタ、構造体など)やサイズ、アライメントなどの情報を含んでいます。
3. 可変長引数 (Variadic Arguments)
可変長引数とは、関数が固定されていない数の引数を受け入れることができる機能です。Go言語では、関数の最後のパラメータの型に ...
を付けることで可変長引数を定義します。例えば、func sum(nums ...int)
は、任意の数の int
型の引数を受け取ることができます。関数内部では、可変長引数はスライスとして扱われます。
4. インターフェース (Interface)
Go言語のインターフェースは、メソッドのシグネチャの集合を定義する型です。インターフェース型は、そのインターフェースのすべてのメソッドを実装する任意の具象型の値を保持できます。初期のGo言語では、可変長引数は内部的にインターフェースとして扱われることがありました。これは、異なる型の引数を統一的に扱うための一つの方法でした。
5. align.c
, gsubr.c
, go.h
, subr.c
, walk.c
の役割
src/cmd/6g/align.c
: 型のアライメント(メモリ上での配置)やサイズ計算に関連する処理を扱います。src/cmd/6g/gsubr.c
: Goコンパイラの汎用サブルーチンやヘルパー関数が含まれます。型に関するユーティリティ関数などもここにあります。src/cmd/gc/go.h
: Goコンパイラのグローバルなヘッダーファイルで、型定義、定数、関数プロトタイプなどが宣言されています。src/cmd/gc/subr.c
: Goコンパイラのサブモジュールで、型関連のユーティリティ関数やシンボル解決など、様々な補助的な処理が含まれます。src/cmd/gc/walk.c
: AST (Abstract Syntax Tree) を走査(walk)し、型チェック、コード変換、最適化などを行うコンパイラの中心的な部分です。
技術的詳細
このコミットの主要な目的は、Go言語のコンパイラに可変長引数(...
)のサポートを追加することです。これには、新しい内部型 TDDD
の導入と、その型をコンパイラの各ステージ(型チェック、メモリレイアウト、コード生成)で適切に処理するための変更が含まれます。
1. TDDD
型の導入
src/cmd/gc/go.h
にTDDD
という新しい型が追加されています。これは、可変長引数を表すためのコンパイラ内部の型です。src/cmd/gc/subr.c
のetnames
配列に[TDDD] = "DDD"
が追加され、デバッグやエラーメッセージでこの型が「DDD」として表示されるようになります。
2. 型のサイズとアライメントの処理
src/cmd/6g/align.c
のdowidth
関数にTDDD
のケースが追加されています。TDDD
型のサイズは2 * wptr
と定義されています。wptr
はポインタのサイズ(32ビットシステムでは4バイト、64ビットシステムでは8バイト)を意味します。これは、可変長引数が内部的に「型情報」と「データへのポインタ」の2つのポインタで構成されるインターフェースとして扱われることを示唆しています。
3. 型の特性判定関数の更新
src/cmd/6g/gsubr.c
のisfat
関数にTDDD
が追加されています。isfat
は、その型が「fat」(つまり、単一のレジスタに収まらないような、複数のワードを占める型)であるかどうかを判定する関数です。可変長引数は複数の値を保持するため、「fat」であるとマークされます。src/cmd/gc/go.h
にisddd(Type*)
関数が宣言され、src/cmd/gc/subr.c
にその実装が追加されています。この関数は、与えられたType
がTDDD
型であるかどうかを判定します。src/cmd/gc/subr.c
のisinter
関数が更新され、TDDD
型もインターフェースとして扱われるように変更されています。これは、可変長引数がインターフェースとして実装される初期の設計を示しています。
4. シグネチャ生成の変更
src/cmd/gc/subr.c
のglobalsig
関数とsigname
関数が変更されています。globalsig
は、特定の型に対するグローバルなシンボルシグネチャを生成する関数です。TDDD
型もこのシグネチャ生成の対象となり、インターフェースと同様に扱われます。signame
関数では、globalsig(t)
の呼び出しがt->sym
のチェックよりも前に移動されています。これは、TDDD
のような特殊な型が、通常のシンボル解決よりも先にシグネチャによって識別される必要があるためと考えられます。
5. AST走査とコード変換 (walk.c
の大幅な変更)
src/cmd/gc/walk.c
は、このコミットで最も大きく変更されたファイルです。ここでは、可変長引数の実際の処理ロジックが実装されています。
mkdotargs
関数の追加:- この関数は、可変長引数に渡された複数の引数を、コンパイラが内部的に生成する匿名構造体(
TSTRUCT
)に「パッケージ化」する役割を担います。 - 引数リスト
r
を走査し、それぞれの引数に対応する構造体のフィールドを動的に生成します。 - 生成された構造体には、
dsigddd_N
のようなユニークな名前が付けられます。 - 最終的に、この構造体へのポインタが、可変長引数パラメータ(
TDDD
型)に割り当てられます。
- この関数は、可変長引数に渡された複数の引数を、コンパイラが内部的に生成する匿名構造体(
sigtype
関数の追加:mkdotargs
から呼び出され、可変長引数用の内部構造体st
の型を確定させ、その型にユニークなシンボル名を割り当てます。signatlist
にこの内部型を記録し、後続のコード生成で利用できるようにします。
ascompatte
関数の変更:- この関数は、関数の引数とパラメータの型を比較し、必要に応じて型変換や引数の割り当てを行う部分です。
- 可変長引数(
l->type
がisddd
)が検出された場合、mkdotargs
を呼び出して引数を構造体にパッケージ化するロジックが追加されています。 ...
パラメータが関数の最後の引数でなければならないという制約がyyerror("... must be last argument")
でチェックされています。
list
関数の簡素化:walk.c
内の複数の箇所で、if(nn == N) nn = a; else nn = list(a, nn);
のようなパターンがnn = list(a, nn);
に簡素化されています。これは、list
関数自体がリストの先頭がN
の場合も適切に処理するように変更されたか、あるいはこのコンテキストではnn
が常に初期化されていることを前提としているためと考えられます。
6. インターフェース比較の改善
src/cmd/gc/walk.c
のisandss
関数(型比較に関連する関数)で、isnilinter(lt) && isnilinter(rt)
のチェックが追加されています。これは、両方のインターフェースがnil
インターフェースである場合に、特別な処理を行うためのものです。可変長引数がインターフェースとして扱われるため、この変更も関連しています。
コアとなるコードの変更箇所
src/cmd/gc/go.h
--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -597,6 +597,7 @@ int
isptrarray(Type*);
int
isptrdarray(Type*);
int
isinter(Type*);
int
isnilinter(Type*);
+int
+isddd(Type*);
Sym*
globalsig(Type*);
Type*
ismethod(Type*);
Type*
methtype(Type*);
isddd
関数のプロトタイプ宣言が追加されています。
src/cmd/gc/subr.c
--- a/src/cmd/gc/subr.c
+++ b/src/cmd/gc/subr.c
@@ -818,6 +818,7 @@ etnames[] =
[TBOOL] = "BOOL",
[TPTR32] = "PTR32",
[TPTR64] = "PTR64",
+\t[TDDD] = "DDD",
[TFUNC] = "FUNC",
[TARRAY] = "ARRAY",
// [TDARRAY] = "DARRAY",
@@ -1453,8 +1454,12 @@ isselect(Node *n)
int
isinter(Type *t)
{
-\tif(t != T && t->etype == TINTER)
-\t\treturn 1;
+\tif(t != T) {
+\t\tif(t->etype == TINTER)
+\t\t\treturn 1;
+\t\tif(t->etype == TDDD)
+\t\t\treturn 1;
+\t}
\treturn 0;
}
@@ -1468,6 +1473,14 @@ isnilinter(Type *t)
return 1;
}
+int
+isddd(Type *t)
+{
+\tif(t != T && t->etype == TDDD)
+\t\treturn 1;
+\treturn 0;
+}
+
Type*
ismethod(Type *t)
{
@@ -1604,6 +1617,7 @@ globalsig(Type *t)
return S;
case TINTER:
+\tcase TDDD:
if(isnilinter(t)) {
snprint(buf, sizeof(buf), "%s_%s", "sigi", "inter");
goto out;
@@ -1670,6 +1684,10 @@ signame(Type *t, int block)
if(t == T)
goto bad;
+\tss = globalsig(t);\n+\tif(ss != S)\n+\t\treturn ss;\n+\n s = t->sym;
if(s == S) {
if(isptr[t->etype]) {
snprint(buf, sizeof(buf), "%s_%s", "sigp", signame(t->type, block)->name);
@@ -1682,10 +1700,6 @@ signame(Type *t, int block)
goto bad;
}
-\tss = globalsig(t);\n-\tif(ss != S)\n-\t\treturn ss;\n-\
e = "sigt";
if(t->etype == TINTER)
e = "sigi";
TDDD
型の定義、isddd
関数の実装、isinter
およびシグネチャ関連関数の更新。
src/cmd/gc/walk.c
--- a/src/cmd/gc/walk.c
+++ b/src/cmd/gc/walk.c
@@ -1555,10 +1555,7 @@ loop:
a = nod(OAS, l, r);
a = convas(a);
-\tif(nn == N)
-\t\tnn = a;
-\telse
-\t\tnn = list(a, nn);
+\tnn = list(a, nn);
l = listnext(&savel);
r = listnext(&saver);
@@ -1595,10 +1592,7 @@ loop:
a = nod(OAS, l, nodarg(r, fp));
a = convas(a);
-\tif(nn == N)
-\t\tnn = a;
-\telse
-\t\tnn = list(a, nn);
+\tnn = list(a, nn);
l = listnext(&savel);
r = structnext(&saver);
@@ -1606,6 +1600,109 @@ loop:
goto loop;
}
+/*
+ * make a tsig for the structure
+ * carrying the ... arguments
+ */
+Type*
+sigtype(Type *st)
+{
+\tDcl *x;
+\tSym *s;
+\tType *t;
+\tstatic int sigdddgen;
+\n+\tdowidth(st);\n+\n+\tsigdddgen++;
+\tsnprint(namebuf, sizeof(namebuf), "dsigddd_%d", sigdddgen);
+\ts = lookup(namebuf);
+\tt = newtype(s);
+\tt = dodcltype(t);
+\tupdatetype(t, st);
+\n+\t// record internal type for signature generation
+\tx = mal(sizeof(*x));
+\tx->op = OTYPE;
+\tx->dsym = s;
+\tx->dtype = s->otype;
+\tx->forw = signatlist;
+\tx->block = block;
+\tsignatlist = x;
+\n+\treturn s->otype;
+}
+\n+/*
+ * package all the arguments that
+ * match a ... parameter into an
+ * automatic structure.
+ * then call the ... arg (interface)
+ * with a pointer to the structure
+ */
+Node*
+mkdotargs(Node *r, Iter *saver, Node *nn, Type *l, int fp)
+{
+\tType *t, *st, *ft;\n+\tNode *a, *n, *var;\n+\tIter saven;\n+\n+\tn = N;\t\t\t// list of assignments\n+\n+\tst = typ(TSTRUCT);\t// generated structure
+\tft = T;\t\t\t// last field
+\twhile(r != N) {
+\t\tdefaultlit(r);\n+\n+\t\t// generate the next structure field
+\t\tt = typ(TFIELD);\n+\t\tt->type = r->type;\n+\t\tif(ft == T)\n+\t\t\tst->type = t;\n+\t\telse
+\t\t\tft->down = t;\n+\t\tft = t;\n+\n+\t\ta = nod(OAS, N, r);\n+\t\tn = list(n, a);\n+\t\tr = listnext(saver);\n+\t}\n+\n+\t// make a named type for the struct
+\tst = sigtype(st);\n+\n+\t// now we have the size, make the struct
+\tvar = nod(OXXX, N, N);\n+\ttempname(var, st);\n+\n+\t// assign the fields to the struct
+\tn = rev(n);\n+\tr = listfirst(&saven, &n);\n+\tt = st->type;\n+\twhile(r != N) {
+\t\tr->left = nod(OXXX, N, N);\n+\t\t*r->left = *var;\n+\t\tr->left->type = r->right->type;\n+\t\tr->left->xoffset += t->width;\n+\t\tnn = list(r, nn);\n+\t\tr = listnext(&saven);\n+\t\tt = t->down;\n+\t}\n+\n+\t// last thing is to put assignment
+\t// of a pointer to the structure to
+\t// the DDD parameter
+\n+\ta = nod(OADDR, var, N);\n+\ta->type = ptrto(st);\n+\ta = nod(OAS, nodarg(l, fp), a);\n+\ta = convas(a);\n+\n+\tnn = list(a, nn);\n+\n+\treturn nn;
+}
+\n Node*
ascompatte(int op, Type **nl, Node **nr, int fp)
{
@@ -1622,7 +1719,21 @@ ascompatte(int op, Type **nl, Node **nr, int fp)
l = structfirst(&savel, nl);
r = listfirst(&saver, nr);
nn = N;
+\n loop:
+\tif(l != T && isddd(l->type)) {
+\t\tif(r != T && isddd(r->type)) {
+\t\t\tgoto more;
+\t\t}\n+\n+\t\tnn = mkdotargs(r, &saver, nn, l, fp);\n+\n+\t\tl = structnext(&savel);\n+\t\tif(l != T)\n+\t\t\tyyerror("... must be last argument");
+\t\treturn rev(nn);
+\t}
+\n if(l == T || r == N) {
if(l != T || r != N)
yyerror("error in shape across %O", op);
goto out;
@@ -1634,12 +1745,10 @@ loop:
return N;
}
+more:
a = nod(OAS, nodarg(l, fp), r);
a = convas(a);
-\tif(nn == N)
-\t\tnn = a;
-\telse
-\t\tnn = list(a, nn);
+\tnn = list(a, nn);
l = structnext(&savel);
r = listnext(&saver);
@@ -2518,6 +2627,8 @@ isandss(Type *lt, Node *r)
rt = r->type;
if(isinter(lt)) {
if(isinter(rt)) {
+\t\t\tif(isnilinter(lt) && isnilinter(rt))\n+\t\t\t\treturn Inone;
if(!eqtype(rt, lt, 0))
return I2I;
return Inone;
@@ -2649,6 +2760,9 @@ convas(Node *n)
if(n->op != OAS)
fatal("convas: not OAS %O", n->op);
+\tlt = T;\n+\trt = T;\n+\n l = n->left;
r = n->right;
if(l == N || r == N)
return n;
@@ -2747,10 +2861,7 @@ colas(Node *nl, Node *nr)
walktype(r, Erv);
defaultlit(r);
a = old2new(l, r->type);
-\t\tif(n == N)
-\t\t\tn = a;
-\t\telse
-\t\t\tn = list(n, a);
+\t\tn = list(n, a);
l = listnext(&savel);
r = listnext(&saver);
@@ -2785,10 +2896,7 @@ multi:
t = structfirst(&saver, getoutarg(t));
while(l != N) {
a = old2new(l, t->type);
-\t\t\tif(n == N)
-\t\t\t\tn = a;
-\t\t\telse
-\t\t\t\tn = list(n, a);
+\t\t\tn = list(n, a);
l = listnext(&savel);
t = structnext(&saver);
}
@@ -2877,16 +2985,12 @@ loop2:
if(l == N) {
r = rev(r);
g = rev(g);
-\t\tif(g != N)\n-\t\t\tf = list(g, f);
+\t\tf = list(g, f);
r = list(f, r);
return r;
}
if(l->ullman < UINF) {
-\t\tif(r == N)\n-\t\t\tr = l;
-\t\telse
-\t\t\tr = list(l, r);
+\t\tr = list(l, r);
goto more;
}
if(f == N) {
@@ -2898,19 +3002,12 @@ loop2:
a = nod(OXXX, N, N);
tempname(a, l->right->type);
a = nod(OAS, a, l->right);
-\n-\tif(g == N)\n-\t\tg = a;
-\telse
-\t\tg = list(a, g);
+\tg = list(a, g);
// put normal arg assignment on list
// with fncall replaced by tempname
l->right = a->left;
-\tif(r == N)\n-\t\tr = l;
-\telse
-\t\tr = list(l, r);
+\tr = list(l, r);
more:
l = listnext(&save);
@@ -3040,20 +3137,14 @@ reorder3(Node *n)
q = N;
l1 = listfirst(&save1, &n);
while(l1 != N) {
-\t\tif(q == N)\n-\t\t\tq = l1;
-\t\telse
-\t\t\tq = list(q, l1);
+\t\tq = list(q, l1);
\tl1 = listnext(&save1);
}
r = rev(r);
l1 = listfirst(&save1, &r);
while(l1 != N) {
-\t\tif(q == N)\n-\t\t\tq = l1;
-\t\telse
-\t\t\tq = list(q, l1);
+\t\tq = list(q, l1);
\tl1 = listnext(&save1);
}
mkdotargs
と sigtype
の追加、ascompatte
の変更、および list
関数の呼び出しパターンの簡素化。
コアとなるコードの解説
このコミットの核心は、Go言語の可変長引数 ...
のコンパイラ内部での処理方法を定義した点にあります。
-
TDDD
型: 可変長引数パラメータは、コンパイラ内部でTDDD
という特殊な型として表現されます。これは、通常の型とは異なる特別な処理が必要であることを示します。 -
引数のパッケージ化 (
mkdotargs
とsigtype
):- Go言語の可変長引数は、関数内部ではスライスとして扱われます。しかし、コンパイラの初期段階では、渡された個々の引数をどのようにまとめて表現するかが課題となります。
mkdotargs
関数は、この問題を解決するために導入されました。この関数は、可変長引数として渡された複数の値を、実行時に動的に生成される匿名構造体(TSTRUCT
)のフィールドとして格納します。sigtype
関数は、この匿名構造体に対してユニークな内部名(例:dsigddd_1
)を割り当て、その型情報をコンパイラのシンボルテーブルに登録します。これにより、後続のコード生成フェーズでこの構造体のレイアウトやアクセス方法が正しく扱われるようになります。- 最終的に、
mkdotargs
は、この生成された構造体へのポインタを、可変長引数パラメータ(TDDD
型)に割り当てます。これにより、関数内部ではこのポインタを介して、渡されたすべての引数にアクセスできるようになります。
-
ascompatte
における処理フロー:ascompatte
関数は、関数呼び出しの引数と、関数のパラメータの型を比較し、適切な割り当てコードを生成する役割を担います。- このコミットでは、
ascompatte
のループ内で、現在のパラメータl
の型がTDDD
であるかどうかがチェックされます (isddd(l->type)
)。 - もし
TDDD
であれば、それは可変長引数パラメータであることを意味します。この場合、残りのすべての引数r
をmkdotargs
関数に渡し、それらを単一の構造体にパッケージ化します。 "... must be last argument"
というエラーチェックは、Go言語の仕様で可変長引数パラメータが常に最後のパラメータでなければならないという制約を強制するものです。
-
list
関数の簡素化:walk.c
の複数の箇所で、list
関数への引数追加ロジックがnn = list(a, nn);
のように簡素化されています。これは、list
関数が内部的にリストが空の場合(N
)も適切に処理するように改善されたか、あるいはこの特定のコンテキストではnn
が常に有効なリストノードとして扱われるようになったためと考えられます。これにより、コードの冗長性が減り、可読性が向上しています。
これらの変更により、Goコンパイラは ...
記法を認識し、可変長引数を内部的に構造体として扱い、関数呼び出し時にそれらの引数を正しく処理できるようになりました。これは、Go言語の柔軟な関数定義を可能にする上で非常に重要な基盤となりました。
関連リンク
- Go言語の可変長引数に関する公式ドキュメント (現在のもの): https://go.dev/tour/basics/15
- Go言語のインターフェースに関する公式ドキュメント (現在のもの): https://go.dev/tour/methods/10
参考にした情報源リンク
- Go言語のソースコード (GitHub): https://github.com/golang/go
- Go言語の初期の設計に関する議論 (Go Mailing Listなど、当時の情報源は特定が困難なため一般的なリンク)
- Go言語のコンパイラに関する一般的な情報 (Goコンパイラの内部構造に関する書籍や記事など)
- Web検索: "Go language variadic functions implementation", "Go compiler TDDD", "Go language DOTDOTDOT"
- Go言語ツアー: https://go.dev/tour/
- Go言語の仕様: https://go.dev/ref/spec
- Go言語の歴史に関する記事 (例: The Go Programming Language by Brian Kernighan and Rob Pike)
- Go言語の初期のコミットログと関連するコード変更の分析