[インデックス 16807] ファイルの概要
このコミットは、Go言語のリンカ (cmd/ld
) とランタイム (src/pkg/runtime
) における、プログラムカウンタ-行番号テーブル (PC-Line Table, pclntab
) の構造変更に関するものです。具体的には、従来の圧縮された pclntab
と functab
を廃止し、新しい連続した pclntab
を導入することで、ランタイムでのデコード処理を簡素化し、パフォーマンスを向上させることを目的としています。
コミット
commit c3de91bb15cf23bd1757c63c581f9da4b7e14598
Author: Russ Cox <rsc@golang.org>
Date: Thu Jul 18 10:43:22 2013 -0400
cmd/ld, runtime: use new contiguous pcln table
R=golang-dev, r, dave
CC=golang-dev
https://golang.org/cl/11494043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/c3de91bb15cf23bd1757c63c581f9da4b7e14598
元コミット内容
cmd/ld, runtime: use new contiguous pcln table
このコミットは、Goリンカとランタイムが新しい連続したPC-Lineテーブルを使用するように変更します。
変更の背景
Go言語の実行可能ファイルには、プログラムカウンタ (PC) とソースコードの行番号、ファイル名、関数名をマッピングするための重要なランタイムデータ構造である pclntab
(Program Counter Line Table) が含まれています。このテーブルは、クラッシュ時のスタックトレース生成や runtime.GetStack
の呼び出しなど、デバッグやプロファイリングにおいて不可欠な情報を提供します。
Go 1.2より前のバージョンでは、Goリンカは pclntab
の圧縮バージョンを実行可能ファイルに埋め込んでいました。Goランタイムはプログラムの初期化時にこのテーブルを解凍する必要がありました。この圧縮と解凍のプロセスは、特に大規模なアプリケーションにおいて、起動時間のオーバーヘッドやメモリフットプリントの増加につながる可能性がありました。
このコミットの主な背景は、Go 1.2で導入された pclntab
の設計変更にあります。この変更の動機は、アプリケーションの起動時間を改善し、潜在的にメモリフットプリントを削減することでした。pclntab
を実行可能ファイル内で事前に展開された(非圧縮の)形式で格納することで、ランタイムでの解凍処理が不要になります。これにより、オペレーティングシステムは必要に応じてこのテーブルのセクションをデマンドページングできるようになり、特にガベージコレクション時など、ランタイムが頻繁にアクセスする際に効率が向上します。
ただし、この変更にはトレードオフも存在します。pclntab
が圧縮されなくなるため、Go実行可能ファイルのサイズが増加します。このサイズ増加は、プログラム内の関数の数に比例して大きくなる可能性があります。
前提知識の解説
- プログラムカウンタ (PC): CPUが次に実行する命令のアドレスを指すレジスタ。Goのランタイムでは、PC値から対応するソースコードの情報を取得するために
pclntab
が使用されます。 - PC-Line Table (
pclntab
): Goの実行可能ファイルに埋め込まれているデータ構造で、プログラムカウンタ (PC) の値と、それが対応するソースコードのファイル名、行番号、関数名などの情報をマッピングします。スタックトレースの生成やデバッグ情報に利用されます。 - リンカ (
cmd/ld
): Goのコンパイラによって生成されたオブジェクトファイルを結合し、実行可能ファイルを生成するツールです。このプロセス中に、pclntab
などのランタイム情報が実行可能ファイルに埋め込まれます。 - ランタイム (
src/pkg/runtime
): Goプログラムの実行を管理する部分で、ガベージコレクション、スケジューリング、スタック管理、エラー処理など、低レベルの操作を担当します。pclntab
の情報はランタイムによって利用されます。 functab
: 従来のGoランタイムで使用されていた関数情報テーブル。このコミットによってpclntab
に統合され、廃止されます。golang.org/s/go12symtab
: Go 1.2におけるシンボルテーブル形式の変更に関する設計ドキュメントへのショートリンクです。このドキュメントは、pclntab
の新しい構造と、それがどのようにランタイムの効率に貢献するかを詳細に説明しています。
技術的詳細
このコミットの核心は、Goの実行可能ファイル内の関数およびPC-行番号情報の表現方法を根本的に変更することです。
従来のGoのリンカは、functab
と pclntab
という2つの別々のテーブルを生成していました。
functab
は関数のエントリポイントと、その関数に関するメタデータ(引数のサイズ、ローカル変数のサイズ、ポインタ情報など)へのポインタを格納していました。pclntab
はPC値と行番号のマッピングを圧縮形式で格納していました。
このコミットでは、これらのテーブルを統合し、pclntab
という単一の連続したデータ構造として表現します。この新しい pclntab
は、Go 1.2のシンボルテーブル形式 (golang.org/s/go12symtab
で詳細が説明されています) に準拠しており、以下の特徴を持ちます。
- 非圧縮形式:
pclntab
は実行可能ファイル内で非圧縮形式で格納されます。これにより、ランタイムでのデコード処理が不要になり、起動時のパフォーマンスが向上します。 - 単一のデータ構造: 関数情報(エントリポイント、名前オフセット、引数サイズ、ローカル変数サイズなど)とPC-行番号マッピングがすべて
pclntab
内に連続して配置されます。これにより、ランタイムがこれらの情報にアクセスする際のキャッシュ効率が向上します。 - ポインタオフセットの利用:
Func
構造体内のポインタ関連情報 (ptrsoff
,ptrslen
) が、pclntab
のベースアドレスからのオフセットとして表現されるようになります。これにより、Func
構造体自体がpclntab
の一部として扱われ、メモリレイアウトがより効率的になります。 - 可変長整数 (varint) エンコーディング: PC-行番号マッピングのデータは、引き続き可変長整数でエンコードされます。これにより、テーブルのサイズをある程度最適化しつつ、ランタイムでのデコードを効率的に行えます。
- ヘッダの導入:
pclntab
の先頭には、マジックナンバー、PC量子、ポインタサイズなどの情報を含むヘッダが追加されます。これにより、ランタイムはテーブルの形式を検証し、正しく解析できます。
この変更により、リンカ (src/cmd/ld/lib.c
) は functab()
関数を廃止し、pclntab()
関数内で新しい連続したテーブルを構築するようになります。ランタイム (src/pkg/runtime/symtab.c
, src/pkg/runtime/runtime.h
) は、この新しい pclntab
の構造を理解し、関数情報やPC-行番号マッピングにアクセスするためのロジックを更新します。特に、Func
構造体から *name
フィールドが削除され、nameoff
(名前へのオフセット) が導入されています。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は以下のファイルに集中しています。
-
src/cmd/5l/obj.c
,src/cmd/6l/obj.c
,src/cmd/8l/obj.c
:- リンカのメイン関数から
functab()
の呼び出しが削除され、pclntab()
の呼び出しのみが残されています。これは、functab
がpclntab
に統合されたことを示しています。
- リンカのメイン関数から
-
src/cmd/ld/lib.c
:pclntab()
関数の実装が大幅に変更されています。- 従来の
pclntab()
関数とfunctab()
関数が統合され、新しいpclntab()
が単一の連続したテーブルを生成する責任を負います。 ftabaddstring
というヘルパー関数が追加され、文字列をpclntab
に追加し、そのオフセットを返すようになりました。pclntab
のヘッダ (マジックナンバー、PC量子、ポインタサイズなど) が書き込まれます。- 各関数のエントリポイントと、その関数に対応する情報構造体へのオフセットが
pclntab
に書き込まれます。 - 関数情報構造体 (エントリPC、名前オフセット、引数サイズ、ローカル変数サイズ、フレームサイズ、ポインタ情報、PCデータテーブルへのオフセット、関数データテーブルへのオフセットなど) が
pclntab
内に直接構築されます。 - ファイル名テーブルも
pclntab
の一部として、その末尾に配置されるようになりました。
- 従来の
genasmsym
関数から、従来のfunctab
に関連するシンボル出力ロジックが削除されています。
-
src/cmd/ld/lib.h
:SPCLNTAB
の定義が変更され、TODO: move to unmapped section
のコメントが削除されています。これは、pclntab
が実行可能ファイル内の特定のセクションに配置されることを示唆しています。functab()
関数のプロトタイプ宣言が削除されています。
-
src/pkg/runtime/mgc0.c
:addframeroots
関数内で、f->ptrsoff
がpclntab
のベースアドレスからのオフセットとして使用されるように変更されています。これにより、ポインタ情報がpclntab
内に連続して格納されていることを前提としたアクセスが行われます。
-
src/pkg/runtime/runtime.h
:struct Func
の定義が変更されています。String *name;
フィールドが削除され、代わりにint32 nameoff;
フィールドが追加されています。これは、関数名がpclntab
内のオフセットとして参照されるようになったことを示しています。- コメントが
// Layout of in-memory per-function information prepared by linker
と更新され、// See http://golang.org/s/go12symtab.
への参照が追加されています。
runtime·funcname(Func*)
という新しい関数プロトタイプが追加されています。
-
src/pkg/runtime/symtab.c
:Ftab
構造体の定義が変更され、Func *func;
がuintptr funcoff;
に置き換えられています。これは、関数情報へのポインタではなく、pclntab
内のオフセットとして関数情報が参照されるようになったことを示しています。pclntab
がextern byte pclntab[];
として宣言され、そのベースアドレスがランタイムからアクセス可能になっています。runtime·symtabinit()
関数が大幅に更新され、新しいpclntab
のヘッダを検証し、関数テーブルとファイルテーブルをpclntab
から解析するロジックが追加されています。pcvalue()
関数やfuncline()
関数など、PC-行番号マッピングにアクセスする関数が、pclntab
のベースアドレスとオフセットを使用して情報を取得するように変更されています。runtime·funcname()
関数が追加され、Func
構造体のnameoff
を使用して関数名文字列をpclntab
から取得するようになりました。runtime·findfunc()
関数も、pclntab
内のオフセットを使用してFunc
構造体を取得するように変更されています。
-
src/pkg/runtime/traceback_arm.c
,src/pkg/runtime/traceback_x86.c
:- スタックトレース生成ロジック内で、関数名を表示する際に
*f->name
の代わりにruntime·funcname(f)
を使用するように変更されています。
- スタックトレース生成ロジック内で、関数名を表示する際に
コアとなるコードの解説
このコミットの最も重要な変更は、src/cmd/ld/lib.c
の pclntab()
関数と、src/pkg/runtime/runtime.h
および src/pkg/runtime/symtab.c
における Func
構造体と関連するランタイム関数の変更です。
src/cmd/ld/lib.c
の pclntab()
関数:
// 変更前 (抜粋)
void
pclntab(void) { /* ... */ }
void
functab(void) { /* ... */ }
// 変更後 (抜粋)
void
pclntab(void)
{
// ...
// See golang.org/s/go12symtab for the format. Briefly:
// 8-byte header
// nfunc [PtrSize bytes]
// function table, alternating PC and offset to func struct [each entry PtrSize bytes]
// end PC [PtrSize bytes]
// offset to file table [4 bytes]
// ...
setuint32(ftab, 0, 0xfffffffb); // Magic number
setuint8(ftab, 6, MINLC); // PC Quantum
setuint8(ftab, 7, PtrSize); // Pointer size
setuintxx(ftab, 8, nfunc, PtrSize); // Number of functions
// Iterate through functions and build the table
for(cursym = textp; cursym != nil; cursym = cursym->next, nfunc++) {
// ...
setaddr(ftab, 8+PtrSize+nfunc*2*PtrSize, cursym); // Function entry PC
setuintxx(ftab, 8+PtrSize+nfunc*2*PtrSize+PtrSize, funcstart, PtrSize); // Offset to func struct
// Build func struct within pclntab
// ...
setuint32(ftab, nameoff, ftabaddstring(ftab, cursym->name)); // Function name offset
// ...
}
// File table at the end
// ...
}
変更後の pclntab()
は、0xfffffffb
というマジックナンバーで始まるヘッダを書き込み、その後にGoプログラム内の全関数の情報が連続して配置されるようにテーブルを構築します。各関数エントリは、その関数の開始PCと、pclntab
内のその関数に対応する詳細な Func
構造体へのオフセットのペアで構成されます。この Func
構造体には、関数のエントリPC、名前へのオフセット、引数サイズ、ローカル変数サイズ、フレームサイズ、ポインタ情報、PCデータテーブルへのオフセット、関数データテーブルへのオフセットなどが含まれます。関数名自体は、pclntab
の別の領域に文字列として格納され、Func
構造体からはその文字列へのオフセット (nameoff
) で参照されます。これにより、文字列の重複を避け、テーブル全体のサイズを最適化しています。
src/pkg/runtime/runtime.h
の struct Func
:
// 変更前
struct Func
{
String *name; // function name
uintptr entry; // start pc
// ...
};
// 変更後
struct Func
{
uintptr entry; // start pc
int32 nameoff; // function name
// ...
};
Func
構造体から String *name;
が削除され、int32 nameoff;
が追加されたことは、関数名が直接ポインタで参照されるのではなく、pclntab
内のオフセットとして格納されるようになったことを明確に示しています。これにより、Func
構造体自体が pclntab
の一部として、よりコンパクトに配置されることが可能になります。
src/pkg/runtime/symtab.c
の runtime·symtabinit()
と runtime·funcname()
:
// 変更前 (抜粋)
void
runtime·symtabinit(void)
{
ftab = (Ftab*)(functab+1);
nftab = functab[0];
// ...
}
// 変更後 (抜粋)
extern byte pclntab[]; // base for f->ptrsoff
void
runtime·symtabinit(void)
{
// ...
// See golang.org/s/go12symtab for header: 0xfffffffb,
// two zero bytes, a byte giving the PC quantum,
// and a byte giving the pointer width in bytes.
if(*(uint32*)pclntab != 0xfffffffb || pclntab[4] != 0 || pclntab[5] != 0 || pclntab[6] != PCQuantum || pclntab[7] != sizeof(void*)) {
runtime·throw("invalid function symbol table\\n");
}
nftab = *(uintptr*)(pclntab+8); // Number of functions
ftab = (Ftab*)(pclntab+8+sizeof(void*)); // Start of function table
// ...
}
int8*
runtime·funcname(Func *f)
{
if(f == nil || f->nameoff == 0)
return nil;
return (int8*)(pclntab + f->nameoff);
}
runtime·symtabinit()
は、プログラム起動時に pclntab
のヘッダを検証し、その中から関数テーブルとファイルテーブルの開始位置を特定します。これにより、ランタイムは pclntab
全体を単一のメモリブロックとして扱い、必要な情報に効率的にアクセスできるようになります。
runtime·funcname()
は、Func
構造体の nameoff
フィールドを利用して、pclntab
のベースアドレスからオフセットを計算し、関数名文字列へのポインタを返します。これは、関数名が pclntab
内に埋め込まれている新しい設計を反映しています。
これらの変更により、Goの実行可能ファイルは、関数とPC-行番号情報をよりコンパクトで効率的な形式で格納できるようになり、ランタイムがこれらの情報にアクセスする際のパフォーマンスが向上します。
関連リンク
- Go 1.2 Release Notes: https://golang.org/doc/go1.2 (このコミットが関連するGoのバージョン)
- Goのシンボルテーブルに関する議論 (Stack Overflow): https://stackoverflow.com/questions/19094097/what-is-the-purpose-of-the-go-pclntab-section
参考にした情報源リンク
golang.org/s/go12symtab
(Go 1.2 シンボルテーブル形式の設計ドキュメント)- Stack Overflow: "What is the purpose of the Go pclntab section?"
- Cockroach Labs Blog: "Go 1.2: The New Contiguous Pcln Table" (類似の変更に関する解説)
- Y Combinator News: "Go 1.2: The New Contiguous Pcln Table" (関連する議論)