[インデックス 16827] ファイルの概要
このコミットは、Goランタイムにおけるガベージコレクション(GC)情報の伝達メカニズムを根本的に変更するものです。具体的には、これまでコンパイラとリンカがGC関連のメタデータを伝達するために使用していた特定の「擬似命令(pseudo-instructions)」を廃止し、代わりにfuncdata
という汎用的なメカニズムを利用するように移行しています。これにより、GC情報の管理がより柔軟かつ効率的になります。
コミット
commit 48769bf546ba7ad830c2edc05656e217d15a20c8
Author: Russ Cox <rsc@golang.org>
Date: Fri Jul 19 16:04:09 2013 -0400
runtime: use funcdata to supply garbage collection information
This CL introduces a FUNCDATA number for runtime-specific
garbage collection metadata, changes the C and Go compilers
to emit that metadata, and changes the runtime to expect it.
The old pseudo-instructions that carried this information
are gone, as is the linker code to process them.
R=golang-dev, dvyukov, cshapiro
CC=golang-dev
https://golang.org/cl/11406044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/48769bf546ba7ad830c2edc05656e217d15a20c8
元コミット内容
Goランタイムがガベージコレクション情報を供給するためにfuncdata
を使用するように変更。
この変更は、ランタイム固有のガベージコレクションメタデータのためのFUNCDATA
番号を導入し、CおよびGoコンパイラがそのメタデータを出力するように変更し、ランタイムがそれを期待するように変更します。
この情報を伝達していた古い擬似命令は廃止され、それらを処理するリンカのコードも削除されました。
変更の背景
Goのガベージコレクションは、実行時にどのメモリ領域がポインタを含んでいるかを正確に知る必要があります。これにより、GCが到達可能なオブジェクトを正確に特定し、不要なオブジェクトを解放できます。以前のGoのバージョンでは、このGC関連のメタデータ(例えば、スタックフレーム内のローカル変数や引数にポインタが含まれているかどうかを示す情報)は、コンパイラが生成するアセンブリコード内に特定の「擬似命令」として埋め込まれていました。これらの擬似命令は、リンカによって解釈され、最終的な実行可能ファイル内の関数情報テーブルに変換されていました。
しかし、この擬似命令ベースのアプローチにはいくつかの課題がありました。
- 拡張性の問題: 新しい種類のGCメタデータを追加したり、既存のメタデータの形式を変更したりする場合、コンパイラ、リンカ、ランタイムの複数のコンポーネントにわたる大規模な変更が必要となり、複雑性が増していました。
- コードの複雑性: 擬似命令の解釈と処理のためのリンカコードが、GCとは直接関係のない他のリンカのロジックと混在し、コードベースの可読性と保守性を低下させていました。
- 汎用性の欠如: GC情報のためだけに専用の擬似命令を使用することは、他の種類の関数関連メタデータ(例えば、プロファイリング情報やデバッグ情報)を扱うための汎用的なメカニズムがないことを意味していました。
このコミットは、これらの課題を解決するために、より汎用的なfuncdata
メカニズムを導入し、GC情報をその一部として扱うことで、システム全体の設計を簡素化し、将来の拡張性を向上させることを目的としています。
前提知識の解説
このコミットを理解するためには、以下の概念について基本的な知識が必要です。
- Goのガベージコレクション (GC): Goは自動メモリ管理を採用しており、ガベージコレクタが不要になったメモリを自動的に解放します。GCは、プログラムが使用しているメモリ(ヒープ上のオブジェクト)をスキャンし、到達可能なオブジェクトをマークし、到達不能なオブジェクトを解放します。このプロセスにおいて、GCはスタック上のポインタやヒープ上のオブジェクト内のポインタを正確に識別する必要があります。
- コンパイラとリンカ:
- コンパイラ (Goコンパイラ
gc
,5g
,6g
,8g
など): Goのソースコードをアセンブリコード(またはオブジェクトファイル)に変換します。この過程で、関数に関するメタデータ(スタックフレームのサイズ、引数の情報、ポインタの配置など)も生成されます。 - リンカ (Goリンカ
ld
,5l
,6l
,8l
など): コンパイラが生成した複数のオブジェクトファイルやライブラリを結合し、最終的な実行可能ファイルを生成します。リンカは、関数に関するメタデータを集約し、ランタイムが利用できる形式(例えば、pclntab
- PC-line table)に整理する役割も担います。
- コンパイラ (Goコンパイラ
- 擬似命令 (Pseudo-instructions): 実際のマシン命令ではないが、アセンブラやリンカに対する指示として使用される命令です。Goのコンパイラは、GC情報などをリンカに伝えるために、
ALOCALS
,ANPTRS
,APTRS
といった独自の擬似命令を使用していました。これらは、スタックフレームのローカル変数のサイズ、ポインタマップのビット数、ポインタマップのデータなどを表現していました。 funcdata
: Goランタイムが関数に関する追加のメタデータにアクセスするための汎用的なメカニズムです。各関数は、その関数に関連付けられた一連のデータポインタを持つことができます。これらのデータポインタは、コンパイラとリンカによって生成され、ランタイムが特定の目的(GC、プロファイリングなど)のために利用します。funcdata
は、Func
構造体の一部として格納され、runtime.funcdata
関数を通じてアクセスされます。pclntab
(PC-line table): Goの実行可能ファイルに含まれる重要なデータ構造で、プログラムカウンタ(PC)とソースコードの行番号のマッピング、関数情報、スタックフレーム情報など、デバッグやプロファイリング、GCに必要なメタデータが格納されています。
技術的詳細
このコミットの核心は、GoのコンパイラとリンカがGC情報を処理する方法を、特定の擬似命令から汎用的なfuncdata
メカニズムへと移行した点にあります。
変更前:
- コンパイラは、関数のスタックフレーム内のローカル変数や引数に関するポインタ情報を、
ALOCALS
(ローカル変数のサイズ)、ANPTRS
(ポインタマップのビット数)、APTRS
(ポインタマップのデータ)といった擬似命令としてアセンブリコードに出力していました。 - リンカは、これらの擬似命令を解析し、
Sym
構造体(シンボル情報)のlocals
、nptrs
、ptrs
フィールドに情報を格納していました。 - ランタイムのGCは、
Func
構造体(関数情報)のlocals
、ptrsoff
、ptrslen
フィールドを通じてこれらの情報にアクセスし、スタックフレームをスキャンしていました。
変更後:
-
AFUNCDATA
擬似命令の導入:- 新しい擬似命令
AFUNCDATA
が導入されました。これは、特定のFUNCDATA
番号と、そのデータへのシンボル参照を受け取ります。 src/pkg/runtime/funcdata.h
に#define FUNCDATA_GC 0
が追加され、GC関連のメタデータがFUNCDATA_GC
という番号で識別されるようになりました。
- 新しい擬似命令
-
コンパイラの変更 (
src/cmd/gc/pgen.c
,src/cmd/cc/pgen.c
など):- Goコンパイラ(
gc
)およびCコンパイラ(cc
)は、もはやALOCALS
,ANPTRS
,APTRS
といった擬似命令を直接出力しなくなりました。 - 代わりに、関数ごとに一意のGCシンボル(例:
gc·%d
)を生成し、そのシンボルにGC関連のメタデータ(ローカル変数のサイズ、引数のポインタマップ)を格納するように変更されました。 - このGCシンボルへの参照は、
AFUNCDATA, nodconst(FUNCDATA_GC), &nod
という形式のAFUNCDATA
命令として出力されます。これにより、リンカはGCメタデータがどこにあるかを知ることができます。 pointermap
関数は、ポインタマップのビットベクトルを計算し、そのデータを直接GCシンボルに書き込むようになりました。
- Goコンパイラ(
-
リンカの変更 (
src/cmd/5l/obj.c
,src/cmd/6l/obj.c
,src/cmd/8l/obj.c
,src/cmd/ld/lib.c
など):- リンカは、
ALOCALS
,ANPTRS
,APTRS
擬似命令の処理コードを完全に削除しました。 Sym
構造体からlocals
,nptrs
,ptrs
フィールドが削除されました。- リンカは、
AFUNCDATA
命令を処理し、GCシンボルへの参照をFunc
構造体のfuncdata
配列に格納します。 pclntab
の生成ロジックも変更され、古いGC情報フィールド(locals
,ptrsoff
,ptrslen
)は削除され、代わりにfuncdata
メカニズムを通じてGC情報が参照されるようになりました。pclntab
内のこれらのフィールドは、互換性のためにゼロで埋められる「デッドスペース」となりました。
- リンカは、
-
ランタイムの変更 (
src/pkg/runtime/mgc0.c
,src/pkg/runtime/runtime.h
,src/pkg/runtime/symtab.c
,src/pkg/runtime/traceback_arm.c
,src/pkg/runtime/traceback_x86.c
など):Func
構造体からlocals
,ptrsoff
,ptrslen
フィールドが削除され、代わりに汎用的なx1
,x2
,x3
といったプレースホルダーが導入されました(これらは後に削除されるか、別の目的で使用される可能性があります)。runtime.h
にruntime·funcdata(Func*, int32)
関数のプロトタイプが追加されました。src/pkg/runtime/symtab.c
のfuncdata
関数(後にruntime·funcdata
にリネーム)が、Func
構造体内のfuncdata
配列から指定されたFUNCDATA
番号に対応するデータポインタを取得するように変更されました。- GCのスタックフレームスキャンロジック(
addframeroots
)は、runtime·funcdata(f, FUNCDATA_GC)
を呼び出してGC情報を取得するように変更されました。 GCFunc
構造体が導入され、GCメタデータ(ローカル変数のサイズ、ポインタマップ)の形式を定義します。ランタイムは、funcdata
から取得したポインタをこのGCFunc
構造体として解釈します。traceback_arm.c
とtraceback_x86.c
から、古いf->locals
やf->varlen
に基づくローカル変数スキャンロジックが削除されました。
この変更により、GC情報の管理がfuncdata
という単一の、より汎用的なメカニズムに集約され、コンパイラ、リンカ、ランタイム間のGC情報伝達のインターフェースがクリーンになりました。
コアとなるコードの変更箇所
このコミットは広範囲にわたる変更を含みますが、特に重要な変更箇所は以下のファイルに集中しています。
src/cmd/5l/5.out.h
,src/cmd/6l/6.out.h
,src/cmd/8l/8.out.h
: リンカの擬似命令定義からALOCALS
,ANPTRS
,APTRS
が削除され、AFUNCDATA
が追加されています。src/cmd/5l/l.h
,src/cmd/6l/l.h
,src/cmd/8l/l.h
: リンカのシンボル構造体Sym
から、GC情報に関連するlocals
,nptrs
,ptrs
フィールドが削除されています。src/cmd/5l/obj.c
,src/cmd/6l/obj.c
,src/cmd/8l/obj.c
: リンカがオブジェクトファイルを読み込む際の処理で、ALOCALS
,ANPTRS
,APTRS
擬似命令を処理するcase
文が削除されています。src/cmd/cc/pgen.c
,src/cmd/gc/pgen.c
: CおよびGoコンパイラのコード生成部分で、GC情報を出力する方法が変更されています。特にALOCALS
の直接出力が削除され、AFUNCDATA
を通じてGCシンボルへの参照が出力されるようになっています。pointermap
関数のシグネチャも変更され、GCシンボルに直接データを書き込むようになりました。src/cmd/ld/lib.c
: リンカのpclntab
(PC-line table)生成ロジックが変更され、古いGC情報フィールド(locals
,ptrsoff
,ptrslen
)が削除され、funcdata
メカニズムに合わせた変更が加えられています。src/pkg/runtime/funcdata.h
:FUNCDATA_GC
マクロが新しく定義され、GC関連のfuncdata
の識別子となります。src/pkg/runtime/mgc0.c
: ランタイムのGCスキャンロジック(addframeroots
)が、runtime·funcdata
関数を使用してGC情報を取得するように変更されています。また、GCFunc
構造体が定義され、GCメタデータの形式を規定しています。src/pkg/runtime/runtime.h
:Func
構造体から古いGC情報フィールドが削除され、runtime·funcdata
関数のプロトタイプが追加されています。Stkframe
構造体のvarp
とvarlen
の順序が変更されています。src/pkg/runtime/symtab.c
:funcdata
関数(後にruntime·funcdata
にリネーム)の実装が、Func
構造体内のfuncdata
配列からデータを取得するように変更されています。src/pkg/runtime/traceback_arm.c
,src/pkg/runtime/traceback_x86.c
: スタックトレースバックのロジックから、古いf->locals
に基づくローカル変数スキャンロジックが削除されています。
コアとなるコードの解説
src/cmd/gc/pgen.c
(Goコンパイラ)
// 変更前:
// plocals = gins(ALOCALS, N, N);
// ...
// plocals->to.type = D_CONST;
// plocals->to.offset = stksize;
// 変更後:
// 新しいGCシンボルを生成
snprint(namebuf, sizeof namebuf, "gc·%d", ngcsym++);
gcsym = lookup(namebuf);
gcnod = newname(gcsym);
gcnod->class = PEXTERN;
// AFUNCDATA命令を出力し、GCシンボルへの参照を渡す
nodconst(&nod1, types[TINT32], FUNCDATA_GC);
pfuncdata = gins(AFUNCDATA, &nod1, gcnod);
// ...
// GCシンボルにGC情報を書き込む
static void
gcsymbol(Sym *gcsym, Node *fn)
{
int off;
off = 0;
off = duint32(gcsym, off, stksize); // size of local block (ローカル変数のサイズ)
off = pointermap(gcsym, off, fn); // pointer bitmap for args (引数のポインタマップ)
ggloblsym(gcsym, off, 0, 1); // グローバルシンボルとして定義
}
// pointermap関数も変更され、GCシンボルに直接データを書き込む
static int
pointermap(Sym *gcsym, int off, Node *fn)
{
// ... ポインタマップのビットベクトルbvを計算 ...
off = duint32(gcsym, off, bv->n); // ポインタマップのビット数
for(i = 0; i < bv->n; i += 32)
off = duint32(gcsym, off, bv->b[i/32]); // ポインタマップのデータ
// ...
return off;
}
Goコンパイラは、もはやALOCALS
のような特定の擬似命令を使ってスタックフレーム情報を出力するのではなく、AFUNCDATA
という新しい汎用的な擬似命令を使用します。この命令は、FUNCDATA_GC
という識別子と、GC情報が格納された新しいシンボル(例: gc·0
, gc·1
など)への参照をリンカに渡します。gcsymbol
関数とpointermap
関数は、この新しいGCシンボルにローカル変数のサイズと引数のポインタマップのビットデータを直接書き込む責任を負います。
src/pkg/runtime/mgc0.c
(Goランタイム - GCコア)
typedef struct GCFunc GCFunc;
struct GCFunc
{
uint32 locals; // size of local variables in bytes
uint32 nptrs; // number of words that follow
uint32 ptrs[1]; // bitmap of pointers in arguments
};
// Scan a stack frame: local variables and function arguments/results.
static void
addframeroots(Stkframe *frame, void*)
{
Func *f;
byte *ap;
int32 i, j, nuintptr;
uint32 w, b;
GCFunc *gcf;
f = frame->fn;
gcf = runtime·funcdata(f, FUNCDATA_GC); // funcdataからGC情報を取得
// Scan local variables if stack frame has been allocated.
i = frame->varp - (byte*)frame->sp;
if(i > 0) {
if(gcf == nil)
addroot((Obj){frame->varp - i, i, 0});
else if(i >= gcf->locals) // GCFuncからローカル変数のサイズを取得
addroot((Obj){frame->varp - gcf->locals, gcf->locals, 0});
}
// Scan arguments.
// Use pointer information if known.
if(f->args > 0 && gcf != nil && gcf->nptrs > 0) { // GCFuncからポインタマップのビット数を取得
ap = frame->argp;
nuintptr = f->args / sizeof(uintptr);
for(i = 0; i < gcf->nptrs; i++) { // GCFuncからポインタマップのデータを取得
w = gcf->ptrs[i];
// ... ポインタマップに基づいて引数をスキャン ...
}
}
}
ランタイムのGCは、スタックフレームをスキャンする際に、もはやFunc
構造体の直接のフィールド(f->locals
, f->ptrsoff
など)に依存しません。代わりに、runtime·funcdata(f, FUNCDATA_GC)
を呼び出して、GCFunc
構造体として定義されたGCメタデータへのポインタを取得します。このGCFunc
構造体には、ローカル変数のサイズと引数のポインタマップが格納されており、GCはこの情報を使用して正確なスキャンを実行します。
src/pkg/runtime/symtab.c
(Goランタイム - シンボルテーブル)
void*
runtime·funcdata(Func *f, int32 i)
{
byte *p;
if(i < 0 || i >= f->nfuncdata)
return nil;
p = (byte*)&f->nfuncdata + 4 + f->npcdata*4;
if(sizeof(void*) == 8 && ((uintptr)p & 4))
p += 4;
return ((void**)p)[i];
}
runtime·funcdata
関数は、Func
構造体内のfuncdata
配列から、指定されたインデックスi
(この場合はFUNCDATA_GC
)に対応するデータポインタを返します。この関数は、コンパイラとリンカによって配置された汎用的なfuncdata
メカニズムへのランタイムのエントリポイントとなります。
これらの変更により、GC情報の伝達と利用が、より抽象化されたfuncdata
インターフェースを通じて行われるようになり、GoランタイムのGCサブシステムの設計が改善されました。
関連リンク
- Go CL 11406044: このコミットに対応するGoの変更リスト(Change List)。詳細な議論やレビューコメントが含まれている可能性があります。 https://golang.org/cl/11406044
参考にした情報源リンク
- Go言語の公式ドキュメント (特にガベージコレクションやコンパイラ/リンカの内部に関するセクション)
- Goのソースコード (特に
src/cmd
とsrc/pkg/runtime
ディレクトリ) - Goのコミット履歴と関連する変更リストの議論
- Goのガベージコレクションに関する技術ブログや論文 (一般的なGCの概念理解のため)
- Goの
funcdata
に関する情報 (Goの内部構造を解説している記事など)