[インデックス 1376] ファイルの概要
このコミットは、Go言語のコンパイラにおける型システムと構文の重要な変更を導入しています。具体的には、chan (チャネル) と map (マップ) 型の宣言から明示的なポインタ記号 * を削除し、これらを string 型と同様に「隠されたポインタ」として扱うように変更しました。また、組み込み関数 new(T) の挙動を変更し、T 型のゼロ値へのポインタ (*T) を返すように統一しました。これらの変更は主に構文的なものであり、コンパイラ内部でのこれらの型の表現は大きく変わっていません。
コミット
commit dc7b2e98d2dabd8b862476f0ca25c7c4e1423f38
Author: Russ Cox <rsc@golang.org>
Date: Fri Dec 19 03:05:54 2008 -0800
compiler changes for *chan -> chan; *map -> map; new(T) -> new(*T)
mainly a syntactic change: the compiler representations don't change
(chan and map are now hidden pointers like string).
R=ken
OCL=21578
CL=21582
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/dc7b2e98d2dabd8b862476f0ca25c7c4e1423f38
元コミット内容
compiler changes for *chan -> chan; *map -> map; new(T) -> new(*T)
mainly a syntactic change: the compiler representations don't change
(chan and map are now hidden pointers like string).
変更の背景
Go言語の初期段階において、chan と map は参照型として機能していましたが、その宣言には明示的にポインタを示す * が必要でした(例: *chan int, *map[string]int)。これは、これらの型が内部的にポインタとして実装されていることを開発者に示唆する意図があったと考えられます。しかし、string 型が内部的にポインタとして実装されているにもかかわらず、その宣言に * を必要としないのと比較すると、一貫性に欠けるという問題がありました。
このコミットの背景には、Go言語の設計哲学である「シンプルさ」と「一貫性」を追求する意図があります。chan と map を string と同様に、ユーザーがポインタであることを意識せずに扱えるようにすることで、言語の学習コストを下げ、より自然なコーディング体験を提供することを目指しました。また、new(T) が T のゼロ値ではなく *T を返すように変更することで、メモリ割り当てとポインタの概念をより明確に結びつけ、一貫した挙動を提供することも目的とされています。
前提知識の解説
このコミットの変更を理解するためには、以下のGo言語の基本的な概念とコンパイラの仕組みに関する知識が必要です。
- 型システム: Go言語は静的型付け言語であり、変数は特定の型を持ちます。型は値の性質と操作を定義します。
- 参照型と値型:
- 値型: 変数に直接値が格納される型(例:
int,bool,struct)。代入や関数への引数渡しで値がコピーされます。 - 参照型: 変数に値へのポインタ(メモリアドレス)が格納される型(例:
slice,map,chan,interface)。代入や関数への引数渡しでポインタがコピーされるため、同じ基盤のデータが共有されます。
- 値型: 変数に直接値が格納される型(例:
- ポインタ: メモリアドレスを保持する変数です。Goでは
*を使ってポインタ型を宣言し、&で変数のアドレスを取得します。 new組み込み関数:new(T)は、T型のゼロ値を格納するのに十分なメモリを割り当て、そのメモリへのポインタを返します。- コンパイラのフェーズ:
- 字句解析 (Lexical Analysis): ソースコードをトークンに分割します。
- 構文解析 (Syntax Analysis): トークン列が言語の文法規則に合致するかをチェックし、抽象構文木 (AST: Abstract Syntax Tree) を構築します。このフェーズでYacc/Bisonのようなパーサジェネレータが使用されることがあります(
go.yファイルがこれに該当)。 - 意味解析 (Semantic Analysis): ASTを走査し、型チェック、名前解決、エラーチェックなどを行います。このフェフェーズでASTの変換(
walk.cファイルがこれに該当)が行われることがあります。 - コード生成 (Code Generation): 最終的な実行可能コードを生成します。
- Yacc/Bison: LALRパーサジェネレータの一種で、文法定義ファイル(
.y)からC言語のソースコードを生成し、構文解析器を作成します。go.yはGoコンパイラの構文解析器の定義ファイルです。
技術的詳細
このコミットは、Goコンパイラの複数のコンポーネントにわたる変更を伴います。
- 構文解析器 (
src/cmd/gc/go.y) の変更:mapとchanの型定義において、typ(TMAP)やtyp(TCHAN)で型オブジェクトを生成した後、$$ = ptrto($$);という行が追加されました。これは、構文解析の段階でmapとchan型が自動的にポインタ型として扱われるようにするための変更です。これにより、ソースコード上ではmap[K]Vやchan Tと記述されても、コンパイラ内部では*map[K]Vや*chan Tと同等の表現になります。
- 型表示ルーチン (
src/cmd/gc/subr.c) の変更:Tpretty関数は、型を人間が読める形式の文字列に変換するために使用されます。この関数がTPTR32またはTPTR64(32ビットまたは64ビットポインタ) 型を処理する際に、その指す型がTMAPやTCHANであれば、*を付けずにmap[K]Vやchan Tの形式で表示するように変更されました。これにより、ユーザーには*なしの簡潔な型名が表示されます。iscomposite関数も更新され、map型がポインタ型として扱われることを反映しています。
- システム関数定義 (
src/cmd/gc/sys.go,src/cmd/gc/sysimport.c) の変更:- Goランタイムが提供する内部的なシステム関数のシグネチャから、
mapとchan型の引数や戻り値の*が削除されました。例えば、newmap関数の戻り値の型が*map[any]anyからmap[any]anyに変更されています。これは、Go言語のソースコードからこれらの関数を呼び出す際の型の一貫性を保つためです。 sysimport.cでは、symtab *[]uint8とpclntab *[]uint8がsymtab []uint8とpclntab []uint8に変更されており、スライスも同様に暗黙的なポインタとして扱われるようになったことが示唆されます。
- Goランタイムが提供する内部的なシステム関数のシグネチャから、
- ASTウォーク (
src/cmd/gc/walk.c) の変更:walk.cは、コンパイラの意味解析フェーズでASTを走査し、変換を行う部分です。newcompat関数はnew組み込み関数の呼び出しを処理します。この関数は、new(T)が常に*Tを返すように変更されました。以前はnew(T)がTを返す場合と*Tを返す場合がありましたが、この変更により一貫性が保たれます。具体的には、newに渡された型Tがポインタ型でない場合でも、内部的にptrto(T)を適用してポインタ型として扱い、そのポインタ型に対するメモリ割り当てと初期化を行います。OADDR(アドレス取得演算子&) の処理も調整され、&StructLiteral{...}のような構文がnew(*StructType)と*var = StructLiteral{...}の形式に変換されるようになりました。maplit(マップリテラル) の処理も、map型が暗黙的にポインタであることを反映するように変更されました。
これらの変更は、Go言語の型システムにおける「参照型」の概念をより直感的で一貫性のあるものにするためのものです。開発者は map や chan を宣言する際に * を意識する必要がなくなり、より自然にこれらの型を扱えるようになります。
コアとなるコードの変更箇所
このコミットにおける主要な変更は、以下のファイルに集中しています。
src/cmd/gc/go.y:TMAPおよびTCHAN型を生成する各ルールに$$ = ptrto($$);が追加され、これらの型が構文解析時に自動的にポインタとして扱われるようになりました。
src/cmd/gc/subr.c:Tpretty関数内で、ポインタ型 (TPTR32,TPTR64) が指す型がTMAPまたはTCHANの場合に、*を付けずに型名を表示するロジックが追加されました。iscomposite関数で、map型がポインタ型として扱われることを反映する条件が追加されました。
src/cmd/gc/sys.go:newmap,mapaccess1,chanrecv1などのシステム関数のシグネチャから、mapおよびchan型の引数と戻り値の*が削除されました。
src/cmd/gc/sysimport.c:sys.goと同様に、システム関数のインポート定義文字列からmapとchan型の*が削除されました。symtabとpclntabの型が*[]uint8から[]uint8に変更され、スライスも暗黙的なポインタとして扱われるようになりました。
src/cmd/gc/walk.c:newcompat関数が大幅に修正され、new(T)が常に*Tを返すように、内部的な型変換とメモリ割り当てロジックが変更されました。maplit関数内のマップリテラル処理ロジックが、map型が暗黙的なポインタであることを前提とするように調整されました。
コアとなるコードの解説
src/cmd/gc/go.y の変更
--- a/src/cmd/gc/go.y
+++ b/src/cmd/gc/go.y
@@ -1022,6 +1022,7 @@ convtype:
$$ = typ(TMAP);
$$->down = $3;
$$->type = $5;
+ $$ = ptrto($$);
}
|\tstructtype
@@ -1106,18 +1107,21 @@ Aothertype:
$$ = typ(TCHAN);
$$->type = $3;
$$->chan = Crecv;
+ $$ = ptrto($$);
}
|\tLCHAN LCOMM Anon_chan_type
{
$$ = typ(TCHAN);
$$->type = $3;
$$->chan = Csend;
+ $$ = ptrto($$);
}
|\tLMAP '[' type ']' Atype
{
$$ = typ(TMAP);
$$->down = $3;
$$->type = $5;
+ $$ = ptrto($$);
}
|\t'*' Atype
{
@@ -1140,18 +1144,21 @@ Bothertype:
$$ = typ(TCHAN);
$$->type = $3;
$$->chan = Crecv;
+ $$ = ptrto($$);
}
|\tLCHAN LCOMM Bnon_chan_type
{
$$ = typ(TCHAN);
$$->type = $3;
$$->chan = Csend;
+ $$ = ptrto($$);
}
|\tLMAP '[' type ']' Btype
{
$$ = typ(TMAP);
$$->down = $3;
$$->type = $5;
+ $$ = ptrto($$);
}
|\t'*' Btype
{
@@ -1168,6 +1175,7 @@ Achantype:
$$ = typ(TCHAN);
$$->type = $2;
$$->chan = Cboth;
+ $$ = ptrto($$);
}
Bchantype:
@@ -1176,6 +1184,7 @@ Bchantype:
$$ = typ(TCHAN);
$$->type = $2;
$$->chan = Cboth;
+ $$ = ptrto($$);
}
structtype:
@@ -1858,6 +1867,7 @@ hidden_type1:
$$ = typ(TMAP);
$$->down = $3;
$$->type = $5;
+ $$ = ptrto($$);
}
|\tLSTRUCT '{' ohidden_structdcl_list '}'
{
@@ -1878,12 +1888,14 @@ hidden_type1:
$$ = typ(TCHAN);
$$->type = $3;
$$->chan = Crecv;
+ $$ = ptrto($$);
}
|\tLCHAN LCOMM hidden_type1
{
$$ = typ(TCHAN);
$$->type = $3;
$$->chan = Csend;
+ $$ = ptrto($$);
}
|\tLDDD
{
@@ -1896,6 +1908,7 @@ hidden_type2:
$$ = typ(TCHAN);
$$->type = $2;
$$->chan = Cboth;
+ $$ = ptrto($$);
}
|\t'(' ohidden_funarg_list ')' ohidden_funres
{
この変更は、Go言語の構文解析器の定義ファイルである go.y に行われました。map と chan 型が定義される各プロダクションルール(convtype, Aothertype, Bothertype, Achantype, Bchantype, hidden_type1, hidden_type2)において、型オブジェクトが作成された直後に $$ = ptrto($$); という行が追加されています。
ptrto 関数は、与えられた型を指すポインタ型を生成するコンパイラ内部の関数です。この変更により、ソースコード上で map[K]V や chan T と記述された場合でも、コンパイラの内部表現では自動的に *map[K]V や *chan T として扱われるようになります。これは、map と chan が参照型であり、内部的にはポインタとして実装されているという事実を、構文レベルで隠蔽するための重要なステップです。
src/cmd/gc/walk.c の newcompat 関数の変更
--- a/src/cmd/gc/walk.c
+++ b/src/cmd/gc/walk.c
@@ -2004,67 +2006,45 @@ newcompat(Node *n)
if(t == T)
goto bad;
-/*
- if(isptr[t->etype]) {
- if(t->type == T)
- goto bad;
- t = t->type;
-
- dowidth(t);
-
- on = syslook("mal", 1);
- argtype(on, t);
-
- r = nodintconst(t->width);
- r = nod(OCALL, on, r);
- walktype(r, Erv);
-
- r->type = n->type;
- goto ret;
- }
-*/
-
- switch(t->etype) {
- default:
-// \tgoto bad;
-//
-// case TSTRUCT:
- if(n->left != N)
- yyerror("dont know what new(,e) means");
-
- dowidth(t);
-
- on = syslook("mal", 1);
-
- argtype(on, t);
-
- r = nodintconst(t->width);
- r = nod(OCALL, on, r);
- walktype(r, Erv);
-
- r->type = ptrto(n->type);
-
- return r;
- case TMAP:
- \tn->type = ptrto(n->type);
- \tr = mapop(n, Erv);
- \tbreak;
-
- case TCHAN:
- \tn->type = ptrto(n->type);
- \tr = chanop(n, Erv);
- \tbreak;
-
- case TARRAY:
- \tr = arrayop(n, Erv);
- break;
- }
-
-ret:
- return r;
-
-bad:
-\tfatal("cannot make new %T", t);
+\tif(t->etype == TARRAY)
+\t\treturn arrayop(n, Erv);
+
+\tif(!isptr[t->etype] || t->type == T)
+\t\tgoto bad;
+
+\tt = t->type;
+\tswitch(t->etype) {
+\tcase TSTRING:
+\t\tgoto bad;
+
+\t// the call looks like new(map[int]int)
+\t// but internally we see new(*MAP[int]int)
+\tcase TMAP:
+\t\tr = mapop(n, Erv);
+\t\tbreak;
+
+\t// the call looks like new(chan int)
+\t// but internally we see new(*CHAN int)
+\tcase TCHAN:
+\t\tr = chanop(n, Erv);
+\t\tbreak;
+
+\tdefault:
+\t\tif(n->left != N)
+\t\t\tyyerror("cannot new(*%T, expr)", t);
+\t\tdowidth(t);
+\t\ton = syslook("mal", 1);
+\t\targtype(on, t);
+\t\tr = nodintconst(t->width);
+\t\tr = nod(OCALL, on, r);
+\t\twalktype(r, Erv);
+\t\tbreak;
+\t}
+
+\treturn r;
+
+bad:
+\tyyerror("cannot new(*%T)", t);
return n;
}
newcompat 関数は、new 組み込み関数の呼び出しを処理するコンパイラの重要な部分です。この変更により、new(T) の挙動が一貫して *T を返すように統一されました。
- 変更前:
newcompatは、引数tの型に応じて異なる処理を行っていました。TMAPやTCHANの場合はptrto(n->type)を適用してポインタ型を生成し、それ以外の場合は直接tを使用してメモリを割り当てていました。 - 変更後:
- まず、
t->etype == TARRAYの場合はarrayopを呼び出すように変更されました。 - 次に、
!isptr[t->etype] || t->type == Tのチェックが追加され、newに渡された型tがポインタ型でない、または無効なポインタ型である場合にエラーを発生させます。これは、new(T)が常に*Tを返すという新しいセマンティクスを強制するためです。 t = t->type;という行が追加され、newに渡された型がポインタ型であると仮定し、そのポインタが指す基底型 (t->type) を取得します。switch(t->etype)ブロック内で、TMAPとTCHANの場合はそれぞれmapopとchanopを呼び出します。これらの関数は、マップとチャネルの初期化と関連するランタイム関数の呼び出しを処理します。defaultケースでは、syslook("mal", 1)を使用してメモリを割り当てます。これは、Goランタイムのメモリ割り当て関数mal(allocate memory) を呼び出すことを意味します。割り当てられたメモリのサイズはt->width(型のサイズ) に基づきます。- 最終的に、この関数は割り当てられたメモリへのポインタを返します。エラーメッセージも
cannot new(*%T)の形式に更新され、新しいセマンティクスを反映しています。
- まず、
この変更により、new(T) は常に T 型のゼロ値へのポインタを返すようになり、Go言語におけるメモリ割り当てのセマンティクスがより明確で予測可能なものになりました。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/doc/
- Go言語の仕様: https://go.dev/ref/spec
- Go言語のコンパイラソースコード (GitHub): https://github.com/golang/go
参考にした情報源リンク
- Go言語のソースコード (特に
src/cmd/gcディレクトリ) - Go言語の初期の設計に関する議論やメーリングリストのアーカイブ (Go言語の進化を追う上で有用)
- Yacc/Bison のドキュメント (構文解析器の理解のため)
- コンパイラ設計に関する一般的な書籍やオンラインリソース