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

[インデックス 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関連のメタデータ(例えば、スタックフレーム内のローカル変数や引数にポインタが含まれているかどうかを示す情報)は、コンパイラが生成するアセンブリコード内に特定の「擬似命令」として埋め込まれていました。これらの擬似命令は、リンカによって解釈され、最終的な実行可能ファイル内の関数情報テーブルに変換されていました。

しかし、この擬似命令ベースのアプローチにはいくつかの課題がありました。

  1. 拡張性の問題: 新しい種類のGCメタデータを追加したり、既存のメタデータの形式を変更したりする場合、コンパイラ、リンカ、ランタイムの複数のコンポーネントにわたる大規模な変更が必要となり、複雑性が増していました。
  2. コードの複雑性: 擬似命令の解釈と処理のためのリンカコードが、GCとは直接関係のない他のリンカのロジックと混在し、コードベースの可読性と保守性を低下させていました。
  3. 汎用性の欠如: GC情報のためだけに専用の擬似命令を使用することは、他の種類の関数関連メタデータ(例えば、プロファイリング情報やデバッグ情報)を扱うための汎用的なメカニズムがないことを意味していました。

このコミットは、これらの課題を解決するために、より汎用的なfuncdataメカニズムを導入し、GC情報をその一部として扱うことで、システム全体の設計を簡素化し、将来の拡張性を向上させることを目的としています。

前提知識の解説

このコミットを理解するためには、以下の概念について基本的な知識が必要です。

  • Goのガベージコレクション (GC): Goは自動メモリ管理を採用しており、ガベージコレクタが不要になったメモリを自動的に解放します。GCは、プログラムが使用しているメモリ(ヒープ上のオブジェクト)をスキャンし、到達可能なオブジェクトをマークし、到達不能なオブジェクトを解放します。このプロセスにおいて、GCはスタック上のポインタやヒープ上のオブジェクト内のポインタを正確に識別する必要があります。
  • コンパイラとリンカ:
    • コンパイラ (Goコンパイラ gc, 5g, 6g, 8g など): Goのソースコードをアセンブリコード(またはオブジェクトファイル)に変換します。この過程で、関数に関するメタデータ(スタックフレームのサイズ、引数の情報、ポインタの配置など)も生成されます。
    • リンカ (Goリンカ ld, 5l, 6l, 8l など): コンパイラが生成した複数のオブジェクトファイルやライブラリを結合し、最終的な実行可能ファイルを生成します。リンカは、関数に関するメタデータを集約し、ランタイムが利用できる形式(例えば、pclntab - PC-line table)に整理する役割も担います。
  • 擬似命令 (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構造体(シンボル情報)のlocalsnptrsptrsフィールドに情報を格納していました。
  • ランタイムのGCは、Func構造体(関数情報)のlocalsptrsoffptrslenフィールドを通じてこれらの情報にアクセスし、スタックフレームをスキャンしていました。

変更後:

  1. AFUNCDATA擬似命令の導入:

    • 新しい擬似命令AFUNCDATAが導入されました。これは、特定のFUNCDATA番号と、そのデータへのシンボル参照を受け取ります。
    • src/pkg/runtime/funcdata.h#define FUNCDATA_GC 0が追加され、GC関連のメタデータがFUNCDATA_GCという番号で識別されるようになりました。
  2. コンパイラの変更 (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シンボルに書き込むようになりました。
  3. リンカの変更 (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内のこれらのフィールドは、互換性のためにゼロで埋められる「デッドスペース」となりました。
  4. ランタイムの変更 (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.hruntime·funcdata(Func*, int32)関数のプロトタイプが追加されました。
    • src/pkg/runtime/symtab.cfuncdata関数(後にruntime·funcdataにリネーム)が、Func構造体内のfuncdata配列から指定されたFUNCDATA番号に対応するデータポインタを取得するように変更されました。
    • GCのスタックフレームスキャンロジック(addframeroots)は、runtime·funcdata(f, FUNCDATA_GC)を呼び出してGC情報を取得するように変更されました。
    • GCFunc構造体が導入され、GCメタデータ(ローカル変数のサイズ、ポインタマップ)の形式を定義します。ランタイムは、funcdataから取得したポインタをこのGCFunc構造体として解釈します。
    • traceback_arm.ctraceback_x86.cから、古いf->localsf->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構造体のvarpvarlenの順序が変更されています。
  • 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/cmdsrc/pkg/runtimeディレクトリ)
  • Goのコミット履歴と関連する変更リストの議論
  • Goのガベージコレクションに関する技術ブログや論文 (一般的なGCの概念理解のため)
  • Goのfuncdataに関する情報 (Goの内部構造を解説している記事など)