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

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

このコミットは、Go言語のリンカ (cmd/ld) とランタイム (runtime) におけるインメモリシンボルテーブルの新しいフォーマット導入に関するものです。これは、Go 1.2リリースに向けて、ガベージコレクタのメタデータ処理を改善し、ランタイムの効率性を高めるための基盤となる変更です。

コミット

  • コミットハッシュ: 5d363c6357ebcacc8ba7c24420f7cfd2e530591a
  • Author: Russ Cox rsc@golang.org
  • Date: Tue Jul 16 09:41:38 2013 -0400
  • Subject: cmd/ld, runtime: new in-memory symbol table format

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

https://github.com/golang/go/commit/5d363c6357ebcacc8ba7c24420f7cfd2e530591a

元コミット内容

cmd/ld, runtime: new in-memory symbol table format

Design at http://golang.org/s/go12symtab.

This enables some cleanup of the garbage collector metadata
that will be done in future CLs.

This CL does not move the old symtab and pclntab back into
an unmapped section of the file. That's a bit tricky and will be
done separately.

Fixes #4020.

R=golang-dev, dave, cshapiro, iant, r
CC=golang-dev, nigeltao
https://golang.org/cl/11085043

変更の背景

Go言語の初期バージョンでは、プログラムの起動時にシンボルテーブルと pc->line (pcln) テーブルのデコードにかなりの時間とメモリを費やしていました。これは、特に大規模なGoプログラムにおいて起動時間のボトルネックとなる可能性がありました。また、コンパイラからランタイムへ新しいメタデータを渡す際に、リンカを介した複数回のエンコード/デコードが必要となり、柔軟性に欠けるという問題も抱えていました。

このコミットは、これらの問題を解決するために、Go 1.2で導入された新しいインメモリシンボルテーブルフォーマットの基盤を構築します。主な目的は以下の通りです。

  1. 起動時間の改善: シンボルテーブルのデコード処理を不要にすることで、プログラムの起動を高速化します。
  2. メモリフットプリントの削減: ランタイムが使用するメモリ量を削減します。
  3. ガベージコレクタの効率化: ガベージコレクタがスタックをスキャンする際に必要となるメタデータ(スタックフレームサイズ、ポインタ情報など)を、より効率的に取得できるようにします。これにより、ガベージコレクションのパフォーマンスが向上します。
  4. メタデータ伝達の簡素化: コンパイラが生成するメタデータをランタイムが直接利用できる形式にすることで、開発の柔軟性を高めます。

前提知識の解説

  • シンボルテーブル (Symbol Table): プログラム内の関数、変数、型などのシンボル(名前)と、それらがメモリ上のどこに配置されているか(アドレス)を対応付けるデータ構造です。デバッガやプロファイラがプログラムの実行状態を解析する際に利用されます。
  • pc->line テーブル (PCLN Table): プログラムカウンタ (PC) の値(実行中の命令のアドレス)から、対応するソースコードのファイル名と行番号を特定するためのテーブルです。スタックトレースの生成やデバッグ情報に不可欠です。
  • ガベージコレクション (Garbage Collection - GC): プログラムが動的に確保したメモリ領域のうち、もはや使用されていない(参照されていない)ものを自動的に解放する仕組みです。GoのGCは、スタック上のポインタを正確に識別し、ヒープ上のオブジェクトをマーク&スイープすることで動作します。この際、関数ごとのスタックフレームのレイアウト(どこにポインタがあるか)に関するメタデータが必要となります。
  • PC-Value テーブル (PC-Value Table): pc->line テーブルの概念を一般化したものです。これは、プログラムカウンタ (PC) の値(コード上の位置)と、それに対応する任意の int32 値をマッピングするテーブルです。例えば、以下のような情報を表現できます。
    • pcsp テーブル: PCからスタックポインタの調整量(スタックフレームサイズ)をマッピングします。スタックの巻き戻し(unwinding)に利用されます。
    • pcfile テーブル: PCからファイル番号をマッピングします。
    • pcln テーブル: PCから行番号をマッピングします。
    • pcdata テーブル: PCからガベージコレクションに必要なポインタ情報などのカスタムデータをマッピングします。
  • 可変長整数 (Varint): データを効率的にエンコードするための手法の一つで、特に小さな数値を少ないバイト数で表現できます。Goのシンボルテーブルでは、値の差分(デルタ)を可変長整数でエンコードすることで、テーブル全体のサイズを削減しています。
  • Zig-zag エンコーディング: 符号付き整数を可変長整数でエンコードする際に、負の数も効率的に表現できるようにする手法です。正の数と負の数を交互に配置することで、絶対値が小さい数値が常に少ないビット数で表現されるようにします。

技術的詳細

このコミットの核となるのは、Goランタイムが直接利用できる新しいインメモリシンボルテーブルフォーマットの導入です。これは、http://golang.org/s/go12symtab で設計が詳細に説明されています。

  1. pclntab シンボル: 以前は複数のセクションに分散していたシンボル情報とpclnテーブルが、pclntab という単一の連続したメモリ領域にまとめられます。これにより、デバッガがGoのシンボル情報をロードしやすくなります。
  2. struct Func の再定義: ランタイムが各関数に関するメタデータ(エントリPC、関数名、引数サイズ、フレームサイズ、各種PC-Valueテーブルへのオフセットなど)を格納するための struct Func が再定義されました。
    • src/pkg/runtime/runtime.hstruct Func の定義が変更され、name, entry, args, locals, frame といったフィールドに加え、pcsp, pcfile, pcln, npcdata, nfuncdata, ptrsoff, ptrslen といった新しいフィールドが追加されています。これらは、PC-Valueテーブルへのオフセットや、関数に関連するデータの数を示します。
  3. PC-Value テーブルの導入: pcsp (PC to stack pointer adjustment), pcfile (PC to file number), pcln (PC to line number) といったPC-Valueテーブルが導入され、これらは関数ごとに格納されます。これらのテーブルは、PCのデルタと値のデルタを可変長整数とZig-zagエンコーディングで効率的に表現します。
  4. 新しい擬似命令 (FUNCDATA, PCDATA): コンパイラは、FUNCDATAPCDATA という新しい擬似命令を使用して、カスタムの関数ごとのメタデータやPC-Valueテーブルを直接生成できるようになります。これにより、リンカを介さずにコンパイラからランタイムへ直接情報を渡すことが可能になり、柔軟性が向上します。
    • AFUNCDATA: 関数に関連するデータ(例: GCポインタマップ)を定義します。
    • APCDATA: PC-Valueテーブルの値を定義します。
  5. ストレージオーバーヘッドの削減: 新しいテーブル形式は、以前のPlan 9ベースのテーブルと比較して、実行イメージのサイズを約2MB削減するとされています(読み取り専用データが1MB、デコード済みデータが1MB削減)。これは、デバッガ固有の情報がランタイムから分離されたためです。ただし、実行可能ファイル自体のサイズは、デバッグ情報が別途格納されるため、1MB程度増加する可能性があります。
  6. functabfiletab の初期化: リンカは、functabfiletab というシンボルを初期化します。
    • functab: すべてのGo関数のエントリポイントと、それに対応する Func 構造体へのポインタのリストを格納します。
    • filetab: ソースファイル名とそれに対応するファイル番号のリストを格納します。
  7. savehistgetline: 履歴スタック(インクルードファイルのネストなど)を処理し、仮想的な行番号から実際のファイル名と行番号を解決するための savehistgetline 関数が導入されています。これにより、ランタイムがソースコードの行情報を正確に取得できるようになります。

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

このコミットでは、主に以下のファイルが変更されています。

  • src/cmd/ld/lib.c: リンカの主要なロジックが含まれており、新しいシンボルテーブルの構築と functabfiletab の生成ロジックが追加されています。特に functab() 関数が新設されています。
  • src/cmd/ld/lib.h: リンカのヘッダファイルで、新しいシンボルタイプ (SGOFUNC, SFUNCTAB, SFILEPATH) や、functabsavehistgetline などの新しい関数のプロトタイプが追加されています。
  • src/cmd/ld/symtab.c: リンカのシンボルテーブル関連の処理が含まれており、新しいシンボルタイプ (SGOFUNC) の定義と処理が追加されています。
  • src/pkg/runtime/runtime.h: Goランタイムのヘッダファイルで、struct Func の定義が新しいインメモリシンボルテーブルフォーマットに合わせて変更されています。
  • src/pkg/runtime/symtab.c: Goランタイムのシンボルテーブル解析ロジックが含まれており、新しい pclntab フォーマットを読み取るための runtime·symtabinitpcvalue などの関数が実装されています。以前のPlan 9形式のシンボルテーブル解析ロジックは削除されています。
  • src/pkg/runtime/extern.go: Goの runtime パッケージの外部インターフェースで、Func 構造体の定義が変更され、FuncForPC, Name, Entry, FileLine などのメソッドが新しい Func 構造体に対応するように修正されています。
  • src/cmd/5l/, src/cmd/6l/, src/cmd/8l/ (各アーキテクチャのリンカ): ALOCALS, ATYPE などの古い擬似命令が削除され、AFUNCDATA, APCDATA などの新しい擬似命令が追加されています。また、functab() の呼び出しが追加されています。

src/cmd/ld/lib.c の変更点 (抜粋)

// 新しい関数: addvarint
void
addvarint(Sym *s, uint32 val)
{
    // 可変長整数をシンボルに書き込むロジック
}

// 新しい関数: funcpctab
void
funcpctab(Sym *dst, Sym *func, char *desc, int32 (*valfunc)(Sym*, int32, Prog*, int32, int32), int32 arg)
{
    // PC-Valueテーブルを生成するロジック
    // valfunc を使用して、PCごとの値を計算し、デルタエンコーディングで書き込む
}

// 新しい関数: pctofileline
static int32
pctofileline(Sym *sym, int32 oldval, Prog *p, int32 phase, int32 arg)
{
    // PCからファイル番号または行番号を計算するロジック
}

// 新しい関数: pctospadj
static int32
pctospadj(Sym *sym, int32 oldval, Prog *p, int32 phase, int32 arg)
{
    // PCからスタックポインタの調整量を計算するロジック
}

// 新しい関数: pctopcdata
static int32
pctopcdata(Sym *sym, int32 oldval, Prog *p, int32 phase, int32 arg)
{
    // PCからPCDATAの値を計算するロジック
}

// 新しい関数: savehist
void
savehist(int32 line, int32 off)
{
    // 履歴スタック(インクルードファイルのネストなど)を保存するロジック
}

// 新しい関数: gethist
Hist*
gethist(void)
{
    // 現在の履歴スタックを取得するロジック
}

// 新しい関数: getline
void
getline(Hist *h, int32 line, int32 *f, int32 *l)
{
    // 履歴スタックと仮想的な行番号から実際のファイル番号と行番号を解決するロジック
}

// 新しい関数: defgostring
Sym*
defgostring(char *text)
{
    // Go文字列を定義するシンボルを返すロジック
}

// 新しい関数: addpctab
static int32
addpctab(Sym *f, int32 off, Sym *func, char *desc, int32 (*valfunc)(Sym*, int32, Prog*, int32, int32), int32 arg)
{
    // PC-Valueテーブルをシンボルに追加し、そのオフセットを返すロジック
}

// 新しい関数: functab
void
functab(void)
{
    // functab と filetab シンボルを初期化し、ランタイム関数とファイル名情報を格納するロジック
    // 各関数について、そのメタデータ(名前、エントリポイント、引数/ローカルサイズ、各種PC-Valueテーブルへのオフセット)を
    // go.func.<funcname> シンボルとして構築する
}

src/pkg/runtime/runtime.h の変更点 (抜粋)

// layout of in-memory per-function information prepared by linker
// See http://golang.org/s/go12symtab.
struct	Func
{
    String	*name;	// function name
    uintptr	entry;	// start pc

    // TODO: Remove these fields.
    int32	args;	// in/out args size
    int32	locals;	// locals size
    int32	frame;	// legacy frame size; use pcsp if possible
    int32	ptrsoff; // offset to pointer map
    int32	ptrslen; // length of pointer map

    int32	pcsp;	// offset to pc-value table for stack pointer adjustment
    int32	pcfile;	// offset to pc-value table for file number
    int32	pcln;	// offset to pc-value table for line number
    int32	npcdata; // number of pcdata tables
    int32	nfuncdata; // number of funcdata tables
};

src/pkg/runtime/symtab.c の変更点 (抜粋)

// 以前のシンボルテーブル解析ロジックは大幅に削除され、新しい形式に対応
// See http://golang.org/s/go12symtab for an overview.

// 新しい関数: runtime·symtabinit
void
runtime·symtabinit(void)
{
    // 新しいfunctabとfiletabを初期化し、ランタイムが利用できるようにする
    // functabがPC順にソートされているか検証する
}

// 新しい関数: readvarint
static uint32
readvarint(byte **pp)
{
    // 可変長整数を読み取るロジック
}

// 新しい関数: funcdata
static uintptr
funcdata(Func *f, int32 i)
{
    // Func構造体から指定されたfuncdataの値を返すロジック
}

// 新しい関数: pcvalue
static int32
pcvalue(Func *f, int32 off, uintptr targetpc)
{
    // PC-Valueテーブルを解析し、指定されたPCに対応する値を返すロジック
    // デルタエンコーディングとZig-zagエンコーディングをデコードする
}

コアとなるコードの解説

このコミットの核心は、Goの実行可能ファイル内に埋め込まれるシンボル情報とデバッグ情報の構造を根本的に変更した点にあります。

  1. リンカ (cmd/ld) 側の変更:

    • functab() 関数の導入: この関数は、すべてのGo関数に関する詳細なメタデータを含む go.func.<funcname> シンボルを構築します。このメタデータには、関数名、エントリポイント、引数とローカル変数のサイズ、そして最も重要な pcsppcfilepcln といったPC-Valueテーブルへのオフセットが含まれます。これらのPC-Valueテーブルは、funcpctab 関数によって生成され、PCのデルタと値のデルタを可変長整数とZig-zagエンコーディングで効率的に格納します。
    • savehist()getline(): ソースファイルのインクルード階層を追跡し、仮想的な行番号から実際のファイル名と行番号を解決するためのメカニズムが導入されました。これは、コンパイラが生成する履歴情報(z シンボル)をリンカが解釈し、ランタイムが利用できるようにするためのものです。
    • 新しいシンボルタイプ: SGOFUNC (Go関数情報)、SFUNCTAB (関数テーブル)、SFILEPATH (ファイルパス) といった新しいシンボルタイプが導入され、リンカがこれらの情報を適切に管理・出力できるようになりました。
    • アーキテクチャ固有のリンカの更新: 5l, 6l, 8l (386, AMD64, ARM) の各リンカで、古い擬似命令が新しい AFUNCDATAAPCDATA に置き換えられ、functab() の呼び出しが追加されました。これにより、コンパイラが生成する新しい形式のメタデータがリンカによって正しく処理されるようになります。
  2. ランタイム (runtime) 側の変更:

    • struct Func の再設計: ランタイムが利用する struct Func は、リンカが生成する新しいメタデータフォーマットに直接対応するように変更されました。これにより、ランタイムはシンボルテーブルをデコードすることなく、直接メモリ上の Func 構造体から必要な情報を取得できるようになります。特に、pcsp, pcfile, pcln といったPC-Valueテーブルへのオフセットが直接格納されることで、スタックトレースやGCの効率が向上します。
    • runtime·symtabinit(): この関数は、起動時に新しい functabfiletab を初期化し、ランタイムが関数情報やファイル情報を参照できるようにします。以前の複雑なシンボルテーブル解析ロジックは削除され、よりシンプルで高速な初期化が可能になりました。
    • pcvalue() 関数: この関数は、指定されたPCとPC-Valueテーブルのオフセットから、対応する値を効率的に取得します。これは、デルタエンコーディングとZig-zagエンコーディングをデコードするロジックを含んでいます。これにより、ランタイムはPCからスタックフレームサイズ、ファイル番号、行番号などの情報を高速に取得できます。
    • mgc0.c の変更: ガベージコレクタのスタックフレームスキャンロジックが、新しい Func 構造体とポインタマップの形式に対応するように更新されました。これにより、GCがスタック上のポインタをより正確かつ効率的に識別できるようになります。

これらの変更により、Goの実行可能ファイルは、ランタイムが直接利用できる最適化されたシンボル情報を持つようになり、起動時間の短縮、メモリ使用量の削減、そしてガベージコレクションのパフォーマンス向上に貢献しています。

関連リンク

参考にした情報源リンク