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

[インデックス 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コンパイラ内部では、プログラム中のすべてのエンティティ(変数、関数、型など)が NodeType といった内部表現で扱われます。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.hTDDD という新しい型が追加されています。これは、可変長引数を表すためのコンパイラ内部の型です。
  • src/cmd/gc/subr.cetnames 配列に [TDDD] = "DDD" が追加され、デバッグやエラーメッセージでこの型が「DDD」として表示されるようになります。

2. 型のサイズとアライメントの処理

  • src/cmd/6g/align.cdowidth 関数に TDDD のケースが追加されています。TDDD 型のサイズは 2 * wptr と定義されています。wptr はポインタのサイズ(32ビットシステムでは4バイト、64ビットシステムでは8バイト)を意味します。これは、可変長引数が内部的に「型情報」と「データへのポインタ」の2つのポインタで構成されるインターフェースとして扱われることを示唆しています。

3. 型の特性判定関数の更新

  • src/cmd/6g/gsubr.cisfat 関数に TDDD が追加されています。isfat は、その型が「fat」(つまり、単一のレジスタに収まらないような、複数のワードを占める型)であるかどうかを判定する関数です。可変長引数は複数の値を保持するため、「fat」であるとマークされます。
  • src/cmd/gc/go.hisddd(Type*) 関数が宣言され、src/cmd/gc/subr.c にその実装が追加されています。この関数は、与えられた TypeTDDD 型であるかどうかを判定します。
  • src/cmd/gc/subr.cisinter 関数が更新され、TDDD 型もインターフェースとして扱われるように変更されています。これは、可変長引数がインターフェースとして実装される初期の設計を示しています。

4. シグネチャ生成の変更

  • src/cmd/gc/subr.cglobalsig 関数と 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->typeisddd)が検出された場合、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.cisandss 関数(型比較に関連する関数)で、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);
 	}
 

mkdotargssigtype の追加、ascompatte の変更、および list 関数の呼び出しパターンの簡素化。

コアとなるコードの解説

このコミットの核心は、Go言語の可変長引数 ... のコンパイラ内部での処理方法を定義した点にあります。

  1. TDDD: 可変長引数パラメータは、コンパイラ内部で TDDD という特殊な型として表現されます。これは、通常の型とは異なる特別な処理が必要であることを示します。

  2. 引数のパッケージ化 (mkdotargssigtype):

    • Go言語の可変長引数は、関数内部ではスライスとして扱われます。しかし、コンパイラの初期段階では、渡された個々の引数をどのようにまとめて表現するかが課題となります。
    • mkdotargs 関数は、この問題を解決するために導入されました。この関数は、可変長引数として渡された複数の値を、実行時に動的に生成される匿名構造体(TSTRUCT)のフィールドとして格納します。
    • sigtype 関数は、この匿名構造体に対してユニークな内部名(例: dsigddd_1)を割り当て、その型情報をコンパイラのシンボルテーブルに登録します。これにより、後続のコード生成フェーズでこの構造体のレイアウトやアクセス方法が正しく扱われるようになります。
    • 最終的に、mkdotargs は、この生成された構造体へのポインタを、可変長引数パラメータ(TDDD 型)に割り当てます。これにより、関数内部ではこのポインタを介して、渡されたすべての引数にアクセスできるようになります。
  3. ascompatte における処理フロー:

    • ascompatte 関数は、関数呼び出しの引数と、関数のパラメータの型を比較し、適切な割り当てコードを生成する役割を担います。
    • このコミットでは、ascompatte のループ内で、現在のパラメータ l の型が TDDD であるかどうかがチェックされます (isddd(l->type))。
    • もし TDDD であれば、それは可変長引数パラメータであることを意味します。この場合、残りのすべての引数 rmkdotargs 関数に渡し、それらを単一の構造体にパッケージ化します。
    • "... must be last argument" というエラーチェックは、Go言語の仕様で可変長引数パラメータが常に最後のパラメータでなければならないという制約を強制するものです。
  4. list 関数の簡素化:

    • walk.c の複数の箇所で、list 関数への引数追加ロジックが nn = list(a, nn); のように簡素化されています。これは、list 関数が内部的にリストが空の場合(N)も適切に処理するように改善されたか、あるいはこの特定のコンテキストでは nn が常に有効なリストノードとして扱われるようになったためと考えられます。これにより、コードの冗長性が減り、可読性が向上しています。

これらの変更により、Goコンパイラは ... 記法を認識し、可変長引数を内部的に構造体として扱い、関数呼び出し時にそれらの引数を正しく処理できるようになりました。これは、Go言語の柔軟な関数定義を可能にする上で非常に重要な基盤となりました。

関連リンク

参考にした情報源リンク

  • 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言語の初期のコミットログと関連するコード変更の分析