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

[インデックス 17932] ファイルの概要

このコミットは、Go言語のコンパイラ(cmd/gc)とランタイム(runtime)における、デッドな値(dead value)を追跡するためのマップに関する名称の誤りを修正するものです。具体的には、funcdataシンボルが「dead value map」を誤って「dead pointer map」と呼んでいた点を修正しています。この修正は、スタックフレーム内のデッドな値(ポインタと非ポインタの両方を含む)を識別し、ランタイムがこれらのデッドなデータ領域を「汚染(poisoning)」することで、プログラムの不変条件(invariants)の喪失を捕捉できるようにすることを目的としています。

コミット

commit bc9691c465acb1c7bf9df9848c848408e876bb57
Author: Carl Shapiro <cshapiro@google.com>
Date:   Mon Dec 9 14:45:10 2013 -0800

    cmd/gc, runtime: correct a misnomer regarding dead value maps
    
    The funcdata symbol incorrectly named the dead value map the
    dead pointer map.  The dead value map identifies all dead
    values, including pointers and non-pointers, in a stack frame.
    The purpose of this map is to allow the runtime to poison
    locations of dead data to catch lost invariants.
    
    R=golang-dev, iant
    CC=golang-dev
    https://golang.org/cl/38670043

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/bc9691c465acb1c7bf9df9848c848408e876bb57

元コミット内容

このコミットの元々の内容は、funcdataシンボルが「dead value map」を「dead pointer map」と誤って命名していたという問題提起と、その修正です。デッドバリューマップは、スタックフレーム内のすべてのデッドな値(ポインタと非ポインタの両方)を識別するために使用され、ランタイムがデッドなデータ領域を汚染して不変条件の喪失を捕捉することを可能にします。

変更の背景

Go言語のランタイムとガベージコレクタは、プログラムの安全性と効率性を確保するために、メモリの状態を正確に把握する必要があります。その一環として、スタックフレーム内の変数の「生存期間(liveness)」を追跡します。変数がもはや使用されない状態になったとき、それは「デッド(dead)」であると見なされます。

このコミットの背景には、Goコンパイラが生成する関数データ(funcdata)に付随する情報の一つである「デッドバリューマップ」の命名に関する誤解がありました。当初、このマップは「デッドポインタマップ(DeadPointerMaps)」と呼ばれていましたが、その実態はポインタだけでなく、非ポインタ型のデッドな値も含む広範な「デッドバリューマップ(DeadValueMaps)」でした。

この名称の誤りは、単なる表記上の問題ではなく、そのマップが実際に追跡しているデータの範囲と目的を正確に反映していませんでした。デッドバリューマップの主な目的は、デッドになったメモリ領域をランタイムが「汚染(poisoning)」することにあります。これは、デバッグや開発中に、プログラムが誤ってデッドなメモリ領域にアクセスした場合に、その不正なアクセスを早期に検出するためのメカニズムです。もしマップがポインタのみを追跡していると誤解されると、非ポインタ型のデッドな値に対する不正アクセスを見逃す可能性がありました。

したがって、このコミットは、コードの正確性を高め、将来的な誤解やバグのリスクを減らすために、この誤った命名を修正することを目的としています。

前提知識の解説

このコミットを理解するためには、以下のGo言語の内部動作に関する前提知識が必要です。

  1. Goのガベージコレクション(GC): Goはトレース型ガベージコレクタを使用しています。GCは、プログラムがもはや到達できないメモリ領域を自動的に解放します。GCが正しく動作するためには、どのメモリが「ライブ(live)」であるか(つまり、プログラムから到達可能であるか)を正確に識別する必要があります。

  2. スタックフレームと変数: 関数が呼び出されると、その関数専用のスタックフレームが作成されます。このスタックフレームには、関数のローカル変数、引数、リターンアドレスなどが格納されます。変数は、その変数がスコープ内で使用されなくなった時点で「デッド」になります。

  3. ライブネス解析(Liveness Analysis): コンパイラは、プログラムの各ポイントでどの変数が「ライブ」であるか(将来的に使用される可能性があるか)を決定するために、ライブネス解析を実行します。この解析の結果は、ガベージコレクタが正確に動作するために不可欠です。

  4. funcdataシンボル: Goコンパイラは、各関数について、その関数の実行に必要なメタデータを含むfuncdataシンボルを生成します。このメタデータには、スタックフレームのレイアウト、ライブポインタの情報、そしてこのコミットで扱われているデッドバリューマップの情報などが含まれます。ランタイムは、GCの実行時やデバッグ時にこのfuncdataを利用します。

  5. ポインタマップ(Pointer Maps): GoのGCは、スタックやヒープ上のポインタを正確に識別する必要があります。これは、ポインタが指す先のオブジェクトがライブであるかどうかを判断するために重要だからです。ポインタマップは、メモリ領域内のどのオフセットにポインタが存在するかを示すビットマップのようなデータ構造です。

  6. デッドバリューの汚染(Poisoning Dead Values): デバッグや開発の目的で、Goランタイムはデッドになったメモリ領域を特定のパターン(例えば、0xdeadbeefのような値)で上書きすることがあります。これを「汚染(poisoning)」と呼びます。これにより、プログラムが誤ってデッドなメモリにアクセスした場合、その不正なアクセスがすぐにクラッシュや異常な値として現れるため、バグの発見が容易になります。このメカニズムは、特に不変条件(invariants)が失われた場合に役立ちます。不変条件とは、プログラムの特定の時点で常に真であるべき条件のことです。例えば、あるデータ構造が常に特定の整合性を持つべきである、といった条件です。デッドなメモリへのアクセスが不変条件を破壊する可能性があるため、汚染はそれを検出するのに役立ちます。

技術的詳細

このコミットは、Goコンパイラ(cmd/gc)とランタイム(runtime)の3つのファイルにわたる変更を含んでいます。

  1. src/cmd/gc/pgen.c:

    • このファイルは、Goコンパイラのコード生成フェーズの一部を担っています。
    • 変更点: makefuncdatasym関数の呼び出しにおいて、第2引数として渡される定数がFUNCDATA_DeadPointerMapsからFUNCDATA_DeadValueMapsに変更されています。
    • これは、コンパイラが関数データシンボルを生成する際に、デッドな値のマップを正しくDeadValueMapsとして識別するように指示するものです。元のコードでは、このマップがポインタのみを対象としているかのような誤解を招く名前が使われていました。
  2. src/cmd/gc/plive.c:

    • このファイルは、Goコンパイラのライブネス解析(liveness analysis)と、その結果としてのポインタマップおよびデッドバリューマップの生成を担当しています。
    • 変更点:
      • コメントが// Emit the map data structuresから// Emit the live pointer map data structuresに変更され、ライブポインタマップの生成に関する記述がより明確になりました。
      • 新たに// Optionally emit a dead value map data structure for locals.というコメントが追加され、ローカル変数に対するデッドバリューマップのオプションの生成について言及されています。
      • twobitwritesymbol(lv->deadvalues, deadsym, nil);の呼び出しは変更されていませんが、そのコンテキストがデッドポインタではなくデッドバリュー全体を指すように明確化されています。
  3. src/pkg/runtime/funcdata.h:

    • このヘッダーファイルは、Goランタイムが使用するfuncdataシンボルの種類を定義する定数を含んでいます。
    • 変更点: #define FUNCDATA_DeadPointerMaps 4#define FUNCDATA_DeadValueMaps 4に変更されています。
    • これは、funcdataシンボル内でデッドな値のマップを識別するための定数名を、その実際の意味に合わせて修正するものです。値4自体は変更されていませんが、その意味する内容がより正確になりました。

これらの変更は、コンパイラが生成するメタデータと、ランタイムがそのメタデータを解釈する際の整合性を保つために重要です。これにより、デッドバリューマップがポインタと非ポインタの両方を含むことを明確にし、ランタイムがデッドなデータ領域を汚染するメカニズムが意図通りに機能することを保証します。

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

src/cmd/gc/pgen.c

--- a/src/cmd/gc/pgen.c
+++ b/src/cmd/gc/pgen.c
@@ -159,7 +159,7 @@ compile(Node *fn)
 	// compile time.  At present, the amount of additional RSS is
 	// substantial enough to affect our smallest build machines.
 	if(0)
-		gcdead = makefuncdatasym("gcdead·%d", FUNCDATA_DeadPointerMaps);
+		gcdead = makefuncdatasym("gcdead·%d", FUNCDATA_DeadValueMaps);
 	else
 		gcdead = nil;
 

src/cmd/gc/plive.c

--- a/src/cmd/gc/plive.c
+++ b/src/cmd/gc/plive.c
@@ -1477,9 +1477,11 @@ liveness(Node *fn, Prog *firstp, Sym *argssym, Sym *livesym, Sym *deadsym)
 	if(0) livenessprintcfg(lv);\n \tlivenessepilogue(lv);\n \n-\t// Emit the map data structures
+\t// Emit the live pointer map data structures
 \ttwobitwritesymbol(lv->livepointers, livesym, nil);\n \ttwobitwritesymbol(lv->argslivepointers, argssym, nil);\n+\n+\t// Optionally emit a dead value map data structure for locals.
 \tif(deadsym != nil)
 \t\ttwobitwritesymbol(lv->deadvalues, deadsym, nil);

src/pkg/runtime/funcdata.h

--- a/src/pkg/runtime/funcdata.h
+++ b/src/pkg/runtime/funcdata.h
@@ -12,7 +12,7 @@
 
 #define FUNCDATA_ArgsPointerMaps 2 /* garbage collector blocks */
 #define FUNCDATA_LocalsPointerMaps 3
-#define FUNCDATA_DeadPointerMaps 4
+#define FUNCDATA_DeadValueMaps 4
 
 // To be used in assembly.
 #define ARGSIZE(n) PCDATA $PCDATA_ArgSize, $n

コアとなるコードの解説

このコミットの核心は、Goコンパイラとランタイムが内部的に使用する定数名と、それに関連するコメントの変更にあります。

  • src/pkg/runtime/funcdata.h の変更:

    • #define FUNCDATA_DeadPointerMaps 4 から #define FUNCDATA_DeadValueMaps 4 への変更は、このコミットの最も直接的な目的を示しています。これは、funcdataシンボルの一部として格納される「デッドな値のマップ」を識別するための定数名を、その実際の意味(ポインタだけでなく、非ポインタ型のデッドな値も含む)に合わせて修正したものです。値自体は4のままであり、これは既存のバイナリ互換性を維持しつつ、セマンティクスを明確にするための変更です。
  • src/cmd/gc/pgen.c の変更:

    • makefuncdatasym("gcdead·%d", FUNCDATA_DeadPointerMaps); から makefuncdatasym("gcdead·%d", FUNCDATA_DeadValueMaps); への変更は、コンパイラがこのマップを生成する際に、新しい(正しい)定数名を使用するように指示しています。これにより、コンパイラが生成するバイナリ内のfuncdataが、ランタイムの期待する正しいセマンティクスを持つようになります。
  • src/cmd/gc/plive.c の変更:

    • コメントの変更は、コードの意図をより明確にするためのものです。
      • // Emit the map data structures// Emit the live pointer map data structures に変更されたことで、その直後のtwobitwritesymbolがライブポインタマップに関するものであることが明確になりました。
      • 新しく追加された // Optionally emit a dead value map data structure for locals. というコメントは、その後のtwobitwritesymbol(lv->deadvalues, deadsym, nil);が、ローカル変数に対するデッドバリューマップの生成に関連していることを示しています。これにより、コードの可読性が向上し、この部分がデッドポインタだけでなく、デッドな値全般を扱っていることが強調されます。

これらの変更は、Goのコンパイラとランタイムの内部的な整合性を高め、特にデバッグや診断の目的で使用されるデッドバリューマップの正確な意味を明確にすることを目的としています。これにより、ランタイムがデッドなメモリ領域を汚染する機能が、ポインタと非ポインタの両方に対して正しく適用されることが保証され、プログラムの不変条件の喪失をより効果的に検出できるようになります。

関連リンク

  • Go言語のガベージコレクションに関する公式ドキュメントやブログ記事
  • Goコンパイラのソースコード(特にcmd/gcディレクトリ)
  • Goランタイムのソースコード(特にsrc/pkg/runtimeディレクトリ)

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコードリポジトリ (GitHub: golang/go)
  • Go言語のガベージコレクションに関する技術記事や論文
  • Go言語のコンパイラ設計に関する資料
  • Go言語のfuncdataに関する議論や仕様
  • Go言語のliveness analysisに関する情報