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

[インデックス 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コンパイラの内部構造に関する知識が必要です。

  1. Goのマップ(map)の実装:

    • Goのマップはハッシュテーブルとして実装されています。
    • 内部的には、キーと値のペアを格納するための「バケット」と呼ばれる固定サイズの配列(通常8ペア)を使用します。
    • バケットが満杯になると、新しいバケットが作成され、既存のバケットからリンクされます(オーバーフローバケット)。これにより、マップは動的にサイズを拡張できます。
    • このバケット構造は、Goのランタイムによって管理される内部的な構造体であり、Goのソースコードからは直接アクセスできません。
  2. Goコンパイラ(cmd/gc)の型システム:

    • Goコンパイラは、プログラム内のすべての型をTypeという内部的なデータ構造で表現します。
    • Type構造体には、型の種類(プリミティブ型、構造体、配列、マップなど)、要素の型、サイズなどの情報が含まれます。
    • コンパイラは、これらのType構造体を使用して、型チェック、コード生成、デバッグ情報の生成などを行います。
    • src/cmd/gc/fmt.cは、コンパイラが型情報を文字列としてフォーマットする際のロジックを定義しています。これは、エラーメッセージやデバッグ出力などで型名を表示するために使用されます。
    • src/cmd/gc/reflect.cは、リフレクションに関連する型情報の生成や管理を行う部分です。特に、マップの内部構造であるHmap(ハッシュマップ)型を構築するロジックが含まれています。
  3. Type構造体とhmapフィールド:

    • Goコンパイラの内部では、Type構造体は様々な型の情報を保持します。マップ型の場合、そのType構造体は、関連するバケット型やキー型、値型へのポインタを持つことがあります。
    • このコミットで特に重要なのは、Type構造体内のhmapフィールドです。これは、マップのバケット型からそのマップ型自体への逆参照を可能にするために使用されます。

技術的詳細

このコミットの核心は、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->hmapTでなければ(つまり、この構造体がマップのバケット型であれば)、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から、新しく作成されたHmaphへのポインタを設定していました。
  • 変更後: h->hmap = t;という行が追加されました。
    • これは、新しく作成されたHmaphから、元のマップ型tへの逆ポインタを設定します。
    • この逆ポインタ(h->hmap)は、fmt.cでの変更が正しく機能するために不可欠です。fmt.ctypefmt関数がマップのバケット型を処理する際に、そのバケット型から関連するマップ型(キー型と値型を取得するため)を効率的に参照できるようになります。この双方向のリンクがなければ、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;: この行は、新しく作成されたHmaphhmapフィールドを、元のマップ型tに設定します。これにより、マップの内部的なハッシュマップ構造から、それが属する元のマップ型への逆参照が可能になります。この逆参照は、fmt.cの変更でマップのバケット型から元のマップ型(キー型と値型を取得するため)を特定するために不可欠です。

これらの変更により、Goコンパイラはマップのバケット型をより効率的かつ人間が理解しやすい形で表現できるようになり、デバッグ体験とバイナリサイズの両面で改善が図られました。

関連リンク

参考にした情報源リンク

  • 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パッケージのドキュメント(リフレクションと型情報の関連性理解のため)

(注: 上記の参考情報源リンクは一般的な知識源であり、この特定のコミットを直接解説しているものではありませんが、コミットの背景にある技術的詳細を理解する上で役立ちます。)