[インデックス 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 のドキュメント (構文解析器の理解のため)
- コンパイラ設計に関する一般的な書籍やオンラインリソース