[インデックス 17821] ファイルの概要
このコミットは、Goコンパイラ(cmd/gc
)におけるマップ(map
)のバケット型(bucket type)の表示名を短縮することを目的としています。これにより、デバッグ時の可読性が向上し、バイナリサイズもわずかに削減されます。
コミット
commit 20f99ffa3e5d58b5ed12289b1ed0e5107376209b
Author: Russ Cox <rsc@golang.org>
Date: Fri Oct 18 15:56:07 2013 -0400
cmd/gc: shorten name used for map bucket type
Before:
type.struct { buckets *struct { overflow *struct { overflow *struct { overflow *struct { overflow *struct { overflow *<...>; keys [8]string; values [8]*\"\".RangeTable }; keys [8]string; values [8]*\"\".RangeTable }; keys [8]string; values [8]*\"\".RangeTable }; keys [8]string; values [8]*\"\".RangeTable }; keys [8]string; values [8]*\"\".RangeTable }; oldbuckets *struct { overflow *struct { overflow *struct { overflow *struct { overflow *struct { overflow *<...>; keys [8]string; values [8]*\"\".RangeTable }; keys [8]string; values [8]*\"\".RangeTable }; keys [8]string; values [8]*\"\".RangeTable }; keys [8]string; values [8]*\"\".RangeTable }; keys [8]string; values [8]*\"\".RangeTable } }
After:
type.map.bucket[string]*\"\".RangeTable
This makes debugging maps a little nicer, and it takes up less space in the binary.
R=golang-dev, r
CC=golang-dev, khr
https://golang.org/cl/15110044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/20f99ffa3e5d58b5ed12289b1ed0e5107376209b
元コミット内容
Goコンパイラ(cmd/gc
)において、マップの内部構造であるバケット型がデバッグ情報などで表示される際に、非常に長く複雑な型名として表現されていました。このコミットは、その冗長な型名を短縮し、より人間が理解しやすい形式(type.map.bucket[KeyType]ValueType
)で表示するように変更します。これにより、デバッグ作業が容易になり、また、コンパイルされたバイナリにおける型情報の記述が効率化され、結果としてバイナリサイズの削減にも寄与します。
変更の背景
Goのマップは、内部的にハッシュテーブルとして実装されており、そのデータは「バケット」と呼ばれる構造体に格納されます。マップの要素数が増えると、バケットがオーバーフローし、新しいバケットがリンクされていくことで、その構造は再帰的になります。
Goコンパイラは、プログラム内のすべての型について内部的な表現を持っています。デバッグ情報やエラーメッセージ、あるいはリフレクションの際にこれらの型情報が利用されます。このコミット以前は、マップのバケット型がコンパイラによって表現される際に、その再帰的な構造がそのまま型名に反映され、非常に長い文字列となっていました。
例えば、map[string]*RangeTable
のようなシンプルなマップであっても、そのバケット型は以下のような非常に冗長な形式で表示されていました。
type.struct { buckets *struct { overflow *struct { overflow *struct { overflow *struct { overflow *struct { overflow *<...>; keys [8]string; values [8]*\"\".RangeTable }; keys [8]string; values [8]*\"\".RangeTable }; keys [8]string; values [8]*\"\".RangeTable }; keys [8]string; values [8]*\"\".RangeTable }; keys [8]string; values [8]*\"\".RangeTable }; oldbuckets *struct { overflow *struct { overflow *struct { overflow *struct { overflow *struct { overflow *<...>; keys [8]string; values [8]*\"\".RangeTable }; keys [8]string; values [8]*\"\".RangeTable }; keys [8]string; values [8]*\"\".RangeTable }; keys [8]string; values [8]*\"\".RangeTable }; keys [8]string; values [8]*\"\".RangeTable } }
このような長い型名は、デバッガでマップの型情報を確認する際に非常に読みにくく、また、バイナリファイル内の型記述領域を不必要に消費していました。この問題を解決し、デバッグ体験を向上させ、バイナリサイズを最適化するために、この変更が導入されました。
前提知識の解説
このコミットを理解するためには、以下のGo言語およびGoコンパイラの内部構造に関する知識が必要です。
-
Goのマップ(
map
)の実装:- Goのマップはハッシュテーブルとして実装されています。
- 内部的には、キーと値のペアを格納するための「バケット」と呼ばれる固定サイズの配列(通常8ペア)を使用します。
- バケットが満杯になると、新しいバケットが作成され、既存のバケットからリンクされます(オーバーフローバケット)。これにより、マップは動的にサイズを拡張できます。
- このバケット構造は、Goのランタイムによって管理される内部的な構造体であり、Goのソースコードからは直接アクセスできません。
-
Goコンパイラ(
cmd/gc
)の型システム:- Goコンパイラは、プログラム内のすべての型を
Type
という内部的なデータ構造で表現します。 Type
構造体には、型の種類(プリミティブ型、構造体、配列、マップなど)、要素の型、サイズなどの情報が含まれます。- コンパイラは、これらの
Type
構造体を使用して、型チェック、コード生成、デバッグ情報の生成などを行います。 src/cmd/gc/fmt.c
は、コンパイラが型情報を文字列としてフォーマットする際のロジックを定義しています。これは、エラーメッセージやデバッグ出力などで型名を表示するために使用されます。src/cmd/gc/reflect.c
は、リフレクションに関連する型情報の生成や管理を行う部分です。特に、マップの内部構造であるHmap
(ハッシュマップ)型を構築するロジックが含まれています。
- Goコンパイラは、プログラム内のすべての型を
-
Type
構造体とhmap
フィールド:- Goコンパイラの内部では、
Type
構造体は様々な型の情報を保持します。マップ型の場合、そのType
構造体は、関連するバケット型やキー型、値型へのポインタを持つことがあります。 - このコミットで特に重要なのは、
Type
構造体内のhmap
フィールドです。これは、マップのバケット型からそのマップ型自体への逆参照を可能にするために使用されます。
- Goコンパイラの内部では、
技術的詳細
このコミットの核心は、Goコンパイラの型フォーマットロジックと型構造の関連付けの改善にあります。
src/cmd/gc/fmt.c
の変更
fmt.c
ファイルは、Goコンパイラが型を文字列として表現する際のフォーマットルールを定義しています。変更はtypefmt
関数内のTSTRUCT
(構造体型)を処理する部分にあります。
- 変更前:
TSTRUCT
の場合、コンパイラは構造体の定義を再帰的に展開して表示していました。マップのバケットは内部的に構造体として定義されており、さらにオーバーフローバケットへのポインタを持つため、再帰的な構造がそのまま型名に反映され、非常に長い文字列が生成されていました。 - 変更後: 新しいコードでは、
TSTRUCT
がマップのバケット型であるかどうかをif(t->hmap != T)
という条件でチェックします。t
は現在フォーマット中のType
構造体です。t->hmap
は、そのType
がマップのバケット型である場合に、対応するマップ型へのポインタを保持します。T
はGoコンパイラ内部で使われるnullまたは無効なType
ポインタを表す定数です。- もし
t->hmap
がT
でなければ(つまり、この構造体がマップのバケット型であれば)、t
をそのマップ型自体に再設定します(t = t->hmap;
)。 - そして、
fmtprint(fp, "map.bucket[%T]%T", t->down, t->type);
という新しいフォーマットルールを適用します。map.bucket
はリテラル文字列です。%T
は、Goコンパイラの型フォーマット指定子で、対応するType
構造体の名前を表示します。t->down
はマップのキー型を指し、t->type
はマップの値型を指します。
- この変更により、マップのバケット型は、その再帰的な内部構造を詳細に表示する代わりに、
map.bucket[キー型]値型
という簡潔で分かりやすい形式で表示されるようになります。
src/cmd/gc/reflect.c
の変更
reflect.c
ファイルは、Goのリフレクションシステムに関連する型情報の生成と管理を担当しています。変更はhmap
関数内にあります。hmap
関数は、Goのマップ型に対応する内部的なHmap
(ハッシュマップ)型構造体を構築する役割を担っています。
- 変更前:
hmap
関数内でt->hmap = h;
という行があり、これは元のマップ型t
から、新しく作成されたHmap
型h
へのポインタを設定していました。 - 変更後:
h->hmap = t;
という行が追加されました。- これは、新しく作成された
Hmap
型h
から、元のマップ型t
への逆ポインタを設定します。 - この逆ポインタ(
h->hmap
)は、fmt.c
での変更が正しく機能するために不可欠です。fmt.c
のtypefmt
関数がマップのバケット型を処理する際に、そのバケット型から関連するマップ型(キー型と値型を取得するため)を効率的に参照できるようになります。この双方向のリンクがなければ、fmt.c
はバケット型からマップのキー型と値型を特定できず、新しい簡潔なフォーマットを適用できません。
- これは、新しく作成された
変更のメリット
- デバッグの容易性: マップのバケット型が簡潔に表示されることで、デバッガでの型情報の確認が格段に容易になります。
- バイナリサイズの削減: 型情報の冗長な記述がなくなることで、コンパイルされたバイナリファイル内の型記述領域が削減され、全体のバイナリサイズがわずかに小さくなります。
コアとなるコードの変更箇所
src/cmd/gc/fmt.c
--- a/src/cmd/gc/fmt.c
+++ b/src/cmd/gc/fmt.c
@@ -700,6 +700,13 @@ typefmt(Fmt *fp, Type *t)
return 0;
case TSTRUCT:
+ // Format the bucket struct for map[x]y as map.bucket[x]y.
+ // This avoids a recursive print that generates very long names.
+ if(t->hmap != T) {
+ t = t->hmap;
+ return fmtprint(fp, "map.bucket[%T]%T", t->down, t->type);
+ }
+
if(t->funarg) {
fmtstrcpy(fp, "(");
if(fmtmode == FTypeId || fmtmode == FErr) { // no argument names on function signature, and no "noescape\" tags
src/cmd/gc/reflect.c
--- a/src/cmd/gc/reflect.c
+++ b/src/cmd/gc/reflect.c
@@ -229,6 +229,7 @@ hmap(Type *t)
h->width = offset;
h->local = t->local;
t->hmap = h;
+ h->hmap = t;
return h;
}
コアとなるコードの解説
src/cmd/gc/fmt.c
の変更点
typefmt
関数は、Goコンパイラが内部的なType
構造体を人間が読める文字列形式に変換する役割を担っています。TSTRUCT
ケースは構造体型を処理します。
追加されたコードブロックは以下の通りです。
if(t->hmap != T) {
t = t->hmap;
return fmtprint(fp, "map.bucket[%T]%T", t->down, t->type);
}
if(t->hmap != T)
: この条件は、現在フォーマットしようとしている構造体t
が、Goのマップの内部的なバケット構造体であるかどうかを判定します。Goコンパイラの内部では、マップのバケット型は、そのバケットが属するマップ型へのポインタをhmap
フィールドに保持しています。T
は、ポインタが設定されていないことを示す特別な値です。したがって、t->hmap != T
は「t
がマップのバケット型であり、そのマップ型への参照を持っている」ことを意味します。t = t->hmap;
: もしt
がマップのバケット型であれば、t
をそのバケットが属する実際のマップ型に再割り当てします。これにより、後続のフォーマットでマップのキー型と値型にアクセスできるようになります。return fmtprint(fp, "map.bucket[%T]%T", t->down, t->type);
: ここで、マップのバケット型を新しい簡潔な形式でフォーマットします。fp
: フォーマットされた文字列を書き込む出力ストリーム。"map.bucket[%T]%T"
: フォーマット文字列。map.bucket
: 固定の文字列リテラル。[%T]
: マップのキー型をフォーマットします。t
はすでにマップ型に設定されているため、t->down
はそのマップのキー型を指します。%T
: マップの値型をフォーマットします。t->type
はそのマップの値型を指します。
- この行が実行されると、
typefmt
関数はすぐに値を返し、通常の構造体フォーマットロジックはスキップされます。
src/cmd/gc/reflect.c
の変更点
hmap
関数は、Goコンパイラがマップ型を処理する際に、そのマップの内部的なハッシュマップ構造(Hmap
)を表現するType
構造体を生成する役割を担っています。
追加された行は以下の通りです。
h->hmap = t;
h
: 新しく生成されたHmap
型(ハッシュマップの内部表現)のType
構造体へのポインタ。t
: このHmap
型が関連付けられている元のマップ型(例:map[string]int
)。h->hmap = t;
: この行は、新しく作成されたHmap
型h
のhmap
フィールドを、元のマップ型t
に設定します。これにより、マップの内部的なハッシュマップ構造から、それが属する元のマップ型への逆参照が可能になります。この逆参照は、fmt.c
の変更でマップのバケット型から元のマップ型(キー型と値型を取得するため)を特定するために不可欠です。
これらの変更により、Goコンパイラはマップのバケット型をより効率的かつ人間が理解しやすい形で表現できるようになり、デバッグ体験とバイナリサイズの両面で改善が図られました。
関連リンク
- Go言語の公式リポジトリ: https://github.com/golang/go
- このコミットのGo Code Reviewサイトでの変更リスト: https://golang.org/cl/15110044
参考にした情報源リンク
- Go言語のマップ実装に関するブログ記事やドキュメント(一般的なGoのマップの内部構造理解のため)
- 例: "Go's map implementation" (Goの公式ブログや関連する技術ブログ)
- Goコンパイラのソースコード(
src/cmd/gc
ディレクトリ内のファイル構造と役割理解のため)- 特に
src/cmd/gc/type.go
(Goの型システムの定義) やsrc/cmd/gc/walk.go
(ASTの走査と型チェック) など。
- 特に
- Goの
reflect
パッケージのドキュメント(リフレクションと型情報の関連性理解のため)
(注: 上記の参考情報源リンクは一般的な知識源であり、この特定のコミットを直接解説しているものではありませんが、コミットの背景にある技術的詳細を理解する上で役立ちます。)