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

[インデックス 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言語の初期段階において、chanmap は参照型として機能していましたが、その宣言には明示的にポインタを示す * が必要でした(例: *chan int, *map[string]int)。これは、これらの型が内部的にポインタとして実装されていることを開発者に示唆する意図があったと考えられます。しかし、string 型が内部的にポインタとして実装されているにもかかわらず、その宣言に * を必要としないのと比較すると、一貫性に欠けるという問題がありました。

このコミットの背景には、Go言語の設計哲学である「シンプルさ」と「一貫性」を追求する意図があります。chanmapstring と同様に、ユーザーがポインタであることを意識せずに扱えるようにすることで、言語の学習コストを下げ、より自然なコーディング体験を提供することを目指しました。また、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コンパイラの複数のコンポーネントにわたる変更を伴います。

  1. 構文解析器 (src/cmd/gc/go.y) の変更:
    • mapchan の型定義において、typ(TMAP)typ(TCHAN) で型オブジェクトを生成した後、$$ = ptrto($$); という行が追加されました。これは、構文解析の段階で mapchan 型が自動的にポインタ型として扱われるようにするための変更です。これにより、ソースコード上では map[K]Vchan T と記述されても、コンパイラ内部では *map[K]V*chan T と同等の表現になります。
  2. 型表示ルーチン (src/cmd/gc/subr.c) の変更:
    • Tpretty 関数は、型を人間が読める形式の文字列に変換するために使用されます。この関数が TPTR32 または TPTR64 (32ビットまたは64ビットポインタ) 型を処理する際に、その指す型が TMAPTCHAN であれば、* を付けずに map[K]Vchan T の形式で表示するように変更されました。これにより、ユーザーには * なしの簡潔な型名が表示されます。
    • iscomposite 関数も更新され、map 型がポインタ型として扱われることを反映しています。
  3. システム関数定義 (src/cmd/gc/sys.go, src/cmd/gc/sysimport.c) の変更:
    • Goランタイムが提供する内部的なシステム関数のシグネチャから、mapchan 型の引数や戻り値の * が削除されました。例えば、newmap 関数の戻り値の型が *map[any]any から map[any]any に変更されています。これは、Go言語のソースコードからこれらの関数を呼び出す際の型の一貫性を保つためです。
    • sysimport.c では、symtab *[]uint8pclntab *[]uint8symtab []uint8pclntab []uint8 に変更されており、スライスも同様に暗黙的なポインタとして扱われるようになったことが示唆されます。
  4. 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言語の型システムにおける「参照型」の概念をより直感的で一貫性のあるものにするためのものです。開発者は mapchan を宣言する際に * を意識する必要がなくなり、より自然にこれらの型を扱えるようになります。

コアとなるコードの変更箇所

このコミットにおける主要な変更は、以下のファイルに集中しています。

  • 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 と同様に、システム関数のインポート定義文字列から mapchan 型の * が削除されました。
    • symtabpclntab の型が *[]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 に行われました。mapchan 型が定義される各プロダクションルール(convtype, Aothertype, Bothertype, Achantype, Bchantype, hidden_type1, hidden_type2)において、型オブジェクトが作成された直後に $$ = ptrto($$); という行が追加されています。 ptrto 関数は、与えられた型を指すポインタ型を生成するコンパイラ内部の関数です。この変更により、ソースコード上で map[K]Vchan T と記述された場合でも、コンパイラの内部表現では自動的に *map[K]V*chan T として扱われるようになります。これは、mapchan が参照型であり、内部的にはポインタとして実装されているという事実を、構文レベルで隠蔽するための重要なステップです。

src/cmd/gc/walk.cnewcompat 関数の変更

--- 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 の型に応じて異なる処理を行っていました。TMAPTCHAN の場合は 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) ブロック内で、TMAPTCHAN の場合はそれぞれ mapopchanop を呼び出します。これらの関数は、マップとチャネルの初期化と関連するランタイム関数の呼び出しを処理します。
    • default ケースでは、syslook("mal", 1) を使用してメモリを割り当てます。これは、Goランタイムのメモリ割り当て関数 mal (allocate memory) を呼び出すことを意味します。割り当てられたメモリのサイズは t->width (型のサイズ) に基づきます。
    • 最終的に、この関数は割り当てられたメモリへのポインタを返します。エラーメッセージも cannot new(*%T) の形式に更新され、新しいセマンティクスを反映しています。

この変更により、new(T) は常に T 型のゼロ値へのポインタを返すようになり、Go言語におけるメモリ割り当てのセマンティクスがより明確で予測可能なものになりました。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード (特に src/cmd/gc ディレクトリ)
  • Go言語の初期の設計に関する議論やメーリングリストのアーカイブ (Go言語の進化を追う上で有用)
  • Yacc/Bison のドキュメント (構文解析器の理解のため)
  • コンパイラ設計に関する一般的な書籍やオンラインリソース