[インデックス 17132] ファイルの概要
このコミットは、Go言語のCコンパイラ (cmd/cc
) におけるポインタマップの構築方法を変更するものです。具体的には、ガベージコレクタ (gc
) がポインタマップを構築する方法に近づけるために、一時的なビットマップ (Bvec
) を使用するように修正されています。これは、将来的にポインタマップの内部構造を変更し、インターフェースポインタと通常のポインタを区別できるようにするための準備作業です。
コミット
- コミットハッシュ:
2db5e4bb0b8acda435da4cc6d8a06bd082187214
- Author: Carl Shapiro cshapiro@google.com
- Date: Fri Aug 9 13:02:33 2013 -0700
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/2db5e4bb0b8acda435da4cc6d8a06bd082187214
元コミット内容
cmd/cc: use a temporary bitmap when constructing pointer maps
This change makes the way cc constructs pointer maps closer to
what gc does and is being done in preparation for changes to
the internal content of the pointer map such as a change to
distinguish interface pointers from ordinary pointers.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/12692043
変更の背景
このコミットの主な背景は、Goランタイムのガベージコレクタ (GC) とCコンパイラ (cmd/cc
) の間で、ポインタマップの構築方法を統一することにあります。
Goのガベージコレクタは、ヒープ上のオブジェクトがポインタを含んでいるかどうかを正確に識別する必要があります。これにより、GCは到達可能なオブジェクトを正確に追跡し、不要なメモリを解放できます。この識別には「ポインタマップ」が使用されます。ポインタマップは、特定のメモリ領域内のどのオフセットにポインタが存在するかを示すビットマップのようなデータ構造です。
これまでのcmd/cc
では、ポインタマップの構築方法がGCのそれとは異なっていました。この差異は、将来的なGCの機能拡張、特にインターフェースポインタと通常のポインタを区別するような変更を行う際に、問題となる可能性がありました。インターフェースはGoの強力な機能であり、その内部表現には型情報と値(ポインタまたは直接値)が含まれます。GCがインターフェースの値を正しく処理するためには、それがポインタであるか否かを正確に知る必要があります。
このコミットは、cmd/cc
がポインタマップを構築する際に、GCが採用している一時的なビットマップ (Bvec
) を利用するアプローチに移行することで、この統一を図っています。これにより、将来のGCの進化に対応できる柔軟性と整合性が向上します。
前提知識の解説
Goのガベージコレクタ (GC)
Goのガベージコレクタは、並行マーク&スイープ方式を採用しています。プログラムの実行中にバックグラウンドで動作し、到達可能なオブジェクトをマークし、到達不能なオブジェクトをスイープ(解放)します。GCが正しく動作するためには、メモリ上のどの部分がポインタであり、どの部分がポインタでないかを正確に知る必要があります。これを実現するのが「ポインタマップ」です。
ポインタマップ (Pointer Maps)
ポインタマップは、特定のメモリ領域(例えば、スタックフレーム、ヒープオブジェクト)内に存在するポインタの位置を示すメタデータです。GoのGCは、このマップを参照して、どのメモリワードがポインタを含んでいるかを判断し、それらのポインタをたどって到達可能なオブジェクトを特定します。ポインタマップは通常、ビットマップ形式で表現され、各ビットが特定のオフセットにポインタが存在するかどうかを示します。
cmd/cc
cmd/cc
は、Goのツールチェインの一部であり、Goのソースコードから生成されたCコードをコンパイルするために使用されるCコンパイラです。Goのコンパイラは、Goのコードを中間表現に変換し、最終的にアセンブリコードを生成しますが、一部の低レベルなランタイムコードや、Goのコンパイラ自体がCで書かれている部分(過去の経緯による)をコンパイルするためにcmd/cc
が利用されます。このコミットは、cmd/cc
が生成するコードが、GCが利用するポインタマップのメタデータをどのように生成するかに影響を与えます。
ビットマップ (Bitmap)
ビットマップは、データの存在や状態をビットの集合で表現するデータ構造です。この文脈では、メモリ領域内の各ワードがポインタであるか否かを1ビットで表現するのに使われます。例えば、32ワードの領域であれば、32ビットの整数1つでそのポインタマップを表現できます。
インターフェースポインタと通常のポインタ
- 通常のポインタ:
*T
のように、特定の型の値へのメモリアドレスを直接指すポインタです。 - インターフェースポインタ: Goのインターフェースは、
itab
(interface table) とdata
の2つのワードで構成されます。itab
はインターフェースが保持する具体的な型とメソッドセットに関する情報を含み、data
は具体的な値へのポインタ(または直接値)です。GCは、このdata
部分がポインタである場合に、それを追跡する必要があります。インターフェースの動的な性質上、data
部分がポインタであるか否かは実行時に決定されるため、GCはこれを正確に識別するメカニズムを必要とします。
bv.c
と Bvec
このコミットで新しく導入された bv.c
は、ビットベクトル(Bit Vector)を操作するためのユーティリティ関数群を提供します。Bvec
は、このビットベクトルを表現するための構造体です。これは、ポインタマップを効率的に構築・操作するための汎用的なメカニズムとして導入されました。
技術的詳細
このコミットの核心は、cmd/cc
が関数の引数やローカル変数のポインタマップを生成する際に、gc
が使用するアプローチに合わせた新しいビットベクトル (Bvec
) ベースのメカニズムを導入した点にあります。
以前のpgen.c
のpointermap
関数(コミット後dumpgcargs
に改名)では、ポインタマップを直接uint32
のビットマスクとして構築し、それをgcsym
というシンボルに書き込んでいました。このアプローチは、特定のオフセットにあるポインタを直接ビットとして設定するものでした。
新しいアプローチでは、以下の変更が行われました。
-
bv.c
の導入:Bvec
構造体が定義されました。これは、ビットベクトルのサイズ (n
) と、実際のビットを格納するuint32
の配列 (b
) を持ちます。bvalloc(int32 n)
: 指定されたビット数n
を持つBvec
を割り当て、初期化します。bvset(Bvec *bv, int32 i)
:Bvec
内の指定されたインデックスi
のビットをセットします。
-
cc.h
の更新:Bvec
構造体の定義が追加されました。bvalloc
とbvset
関数のプロトタイプが追加されました。
-
pgen.c
の変更:pointermap
関数がdumpgcargs
に改名され、そのシグネチャも変更されました。pointermap_type
関数がwalktype1
に改名され、Bvec
ポインタを引数として受け取るようになりました。- ポインタマップ構築ロジックの変更:
dumpgcargs
関数内で、まずbvalloc
を使って関数の引数リスト全体をカバーするのに十分なサイズのBvec
が割り当てられます。- 関数の引数を走査する際に、
walktype1
関数が呼び出されます。walktype1
は、型定義を再帰的に辿り、ポインタを含むオフセットが見つかるたびに、新しく導入されたbvset
関数を使ってBvec
内の対応するビットをセットします。 TIND
(ポインタ型) やTARRAY
(Cでは配列はポインタとして渡される) の場合、bvset
が呼び出されます。TSTRUCT
(構造体) の場合、そのメンバーを再帰的にwalktype1
で走査します。TUNION
(共用体) の場合、最初のメンバーに対してwalktype1
を呼び出します(共用体のポインタマップは全てのメンバーで同じであるという前提)。- 最終的に、構築された
Bvec
の内容(ビット数とビットデータ)がgcsym
シンボルに書き込まれます。これにより、GCが後でこの情報を読み取ってポインタを識別できるようになります。 - 処理の最後に
bv
はfree
されます。
この変更により、ポインタマップの構築がより抽象化され、ビット操作がbv.c
にカプセル化されました。これにより、将来的にポインタマップの表現形式が変更された場合でも、bv.c
の内部実装を変更するだけで済み、pgen.c
のような高レベルなロジックへの影響を最小限に抑えることができます。特に、インターフェースポインタと通常のポインタの区別を導入する際に、この新しい抽象化が役立つと期待されます。
コアとなるコードの変更箇所
src/cmd/cc/bv.c
(新規ファイル)
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#include <u.h>
#include <libc.h>
#include "cc.h"
enum {
WORDSIZE = sizeof(uint32),
WORDBITS = 32,
};
uintptr
bvsize(uintptr n)
{
return ((n + WORDBITS - 1) / WORDBITS) * WORDSIZE;
}
Bvec*
bvalloc(int32 n)
{
Bvec *bv;
uintptr nbytes;
if(n < 0)
fatal(Z, "bvalloc: initial size is negative\\n");
nbytes = sizeof(Bvec) + bvsize(n);
bv = malloc(nbytes);
if(bv == nil)
fatal(Z, "bvalloc: malloc failed\\n");
memset(bv, 0, nbytes);
bv->n = n;
return bv;
}
void
bvset(Bvec *bv, int32 i)
{
uint32 mask;
if(i < 0 || i >= bv->n)
fatal(Z, "bvset: index %d is out of bounds with length %d\\n", i, bv->n);
mask = 1 << (i % WORDBITS);
bv->b[i / WORDBITS] |= mask;
}
src/cmd/cc/cc.h
--- a/src/cmd/cc/cc.h
+++ b/src/cmd/cc/cc.h
@@ -52,6 +52,7 @@ typedef struct Hist Hist;
typedef struct Term Term;
typedef struct Init Init;
typedef struct Bits Bits;
+typedef struct Bvec Bvec; // Bvec構造体の追加
typedef struct Dynimp Dynimp;
typedef struct Dynexp Dynexp;
@@ -76,6 +77,12 @@ struct Bits
uint32 b[BITS];
};
+struct Bvec // Bvec構造体の定義
+{
+ int32 n; // number of bits
+ uint32 b[];
+};
+
struct Node
{
Node* left;
@@ -750,6 +757,12 @@ Bits
blsh(uint);
int beq(Bits, Bits);
int bset(Bits, uint);
+/*
+ * bv.c
+ */
+Bvec* bvalloc(int32 n); // bvalloc関数のプロトタイプ
+void bvset(Bvec *bv, int32 i); // bvset関数のプロトタイプ
+
/*
* dpchk.c
*/
src/cmd/cc/pgen.c
--- a/src/cmd/cc/pgen.c
+++ b/src/cmd/cc/pgen.c
@@ -31,7 +31,7 @@
#include "gc.h"
#include "../../pkg/runtime/funcdata.h"
-static int32 pointermap(Sym *gcsym, int32 offset);
+static void dumpgcargs(Type *fn, Sym *sym); // 関数名変更とシグネチャ変更
int
hasdotdotdot(void)
@@ -82,7 +82,6 @@ codgen(Node *n, Node *nn)
Sym *gcsym, *gclocalssym;
static int ngcsym, ngclocalssym;
static char namebuf[40];
-\tint32 off; // 削除
cursafe = 0;
curarg = 0;
@@ -164,16 +163,9 @@ codgen(Node *n, Node *nn)
if(thechar=='6' || thechar=='7') /* [sic] */
maxargsafe = xround(maxargsafe, 8);
sp->to.offset += maxargsafe;
-\t
+\n+\tdumpgcargs(thisfn, gcsym); // dumpgcargsの呼び出し
+\n // TODO(rsc): "stkoff" is not right. It does not account for
// the possibility of data stored in .safe variables.
// Unfortunately those move up and down just like
// the arguments. The right thing to do is to give the .safe
// area its own section.
// That said, we've been using stkoff for months
// and nothing too terrible has happened.
-\toff = 0;
-\toff = pointermap(gcsym, off); // nptrs and ptrs[...] // 削除
-\tgcsym->type = typ(0, T);
-\tgcsym->type->width = off;
-\n-\toff = 0;
-\tgextern(gclocalssym, nodconst(-stkoff), off, 4); // locals // 削除
-\toff += 4; // 削除
+\tgextern(gclocalssym, nodconst(-stkoff), 0, 4); // locals // 変更
\tgclocalssym->type = typ(0, T);
-\tgclocalssym->type->width = off; // 削除
+\tgclocalssym->type->width = 4; // 変更
}\n \n void
@@ -652,80 +644,74 @@ bcomplex(Node *n, Node *c)
return 0;
}\n \n-// Makes a bitmap marking the the pointers in t. t starts at the given byte\n-// offset in the argument list. The returned bitmap should be for pointer\n-// indexes (relative to offset 0) between baseidx and baseidx+32.\n-static int32\n-pointermap_type(Type *t, int32 offset, int32 baseidx) // 関数名変更とシグネチャ変更
+// Updates the bitvector with a set bit for each pointer containing\n+// value in the type description starting at offset.\n+static void\n+walktype1(Type *t, int32 offset, Bvec *bv) // 関数名変更とシグネチャ変更
{\n Type *t1;\n-\tint32 idx;\n-\tint32 m; // 削除
\n \tswitch(t->etype) {\n \tcase TCHAR:\n@@ -676,80 +667,74 @@ pointermap_type(Type *t, int32 offset, int32 baseidx)\n \tcase TFLOAT:\n \tcase TDOUBLE:\n \t\t// non-pointer types\n-\t\treturn 0; // 削除
+\t\tbreak; // 変更
+\n \tcase TIND:\n \tcase TARRAY: // unlike Go, C passes arrays by reference\n \t\t// pointer types\n \t\tif((offset + t->offset) % ewidth[TIND] != 0)\n \t\t\tyyerror(\"unaligned pointer\");\n-\t\tidx = (offset + t->offset) / ewidth[TIND];\n-\t\tif(idx >= baseidx && idx < baseidx + 32)\n-\t\t\treturn 1 << (idx - baseidx); // 削除
-\t\treturn 0; // 削除
+\t\tbvset(bv, (offset + t->offset) / ewidth[TIND]); // bvsetの呼び出し
+\t\tbreak; // 変更
+\n \tcase TSTRUCT:\n \t\t// build map recursively\n-\t\tm = 0; // 削除
-\t\tfor(t1=t->link; t1; t1=t1->down)\n-\t\t\tm |= pointermap_type(t1, offset, baseidx); // 削除
-\t\treturn m; // 削除
+\t\tfor(t1 = t->link; t1 != T; t1 = t1->down) // 変更
+\t\t\twalktype1(t1, offset, bv); // walktype1の再帰呼び出し
+\t\tbreak; // 変更
+\n \tcase TUNION:\n-\t\t// We require that all elements of the union have the same pointer map.\n-\t\tm = pointermap_type(t->link, offset, baseidx); // 削除
-\t\tfor(t1=t->link->down; t1; t1=t1->down) {\n-\t\t\tif(pointermap_type(t1, offset, baseidx) != m)\n-\t\t\t\tyyerror(\"invalid union in argument list - pointer maps differ\");\n-\t\t} // 削除
-\t\treturn m; // 削除
+\t\twalktype1(t->link, offset, bv); // walktype1の呼び出し
+\t\tbreak; // 変更
+\n \tdefault:\n \t\tyyerror(\"can\'t handle arg type %s\\n\", tnames[t->etype]);\n-\t\treturn 0; // 削除
\t}\n }\n \n // Compute a bit vector to describe the pointer containing locations\n // in the argument list. Adds the data to gcsym and returns the offset\n // of end of the bit vector.\n-static int32\n-pointermap(Sym *gcsym, int32 off) // 関数名変更とシグネチャ変更
+static void\n+dumpgcargs(Type *fn, Sym *sym) // 関数名変更とシグネチャ変更
{\n-\tint32 nptrs;\n-\tint32 i;\n-\tint32 s; // offset in argument list (in bytes)\n-\tint32 m; // current ptrs[i/32] // 削除
+\tBvec *bv; // Bvecポインタの追加
\tType *t;\n+\tint32 i;\n+\tint32 symoffset, argoffset; // 変数追加
\n \tif(hasdotdotdot()) {\n \t\t// give up for C vararg functions.\n \t\t// TODO: maybe make a map just for the args we do know?\n-\t\tgextern(gcsym, nodconst(0), off, 4); // nptrs=0 // 削除
-\t\treturn off + 4; // 削除
+\t\tgextern(sym, nodconst(0), 0, 4); // nptrs=0 // 変更
+\t\tsymoffset = 4; // 追加
+\t} else {\n+\t\tbv = bvalloc((argsize() + ewidth[TIND] - 1) / ewidth[TIND]); // bvallocでBvecを割り当て
+\t\targoffset = align(0, fn->link, Aarg0, nil); // 変数名変更
+\t\tif(argoffset > 0) {\n+\t\t\t// The C calling convention returns structs by\n+\t\t\t// copying them to a location pointed to by a\n+\t\t\t// hidden first argument. This first argument\n+\t\t\t// is a pointer.\n+\t\t\tif(argoffset != ewidth[TIND]) // 変数名変更
\t\t\t\tyyerror(\"passbyptr arg not the right size\");\n-\t\t\tm = 1; // 削除
+\t\t\tbvset(bv, 0); // bvsetの呼び出し
\t\t}\n-\t\tfor(t=thisfn->down; t!=T; t=t->down) {\n+\t\tfor(t = fn->down; t != T; t = t->down) { // 変更
\t\t\tif(t->etype == TVOID)\n \t\t\t\tcontinue;\n-\t\t\ts = align(s, t, Aarg1, nil); // 削除
-\t\t\tm |= pointermap_type(t, s, i); // 削除
-\t\t\ts = align(s, t, Aarg2, nil); // 削除
+\t\t\targoffset = align(argoffset, t, Aarg1, nil); // 変更
+\t\t\twalktype1(t, argoffset, bv); // walktype1の呼び出し
+\t\t\targoffset = align(argoffset, t, Aarg2, nil); // 変更
+\t\t}\n+\t\tgextern(sym, nodconst(bv->n), 0, 4); // bv->nを書き込み
+\t\tsymoffset = 4; // 初期化
+\t\tfor(i = 0; i < bv->n; i += 32) { // bvのビットデータを書き込み
+\t\t\tgextern(sym, nodconst(bv->b[i/32]), symoffset, 4);
+\t\t\tsymoffset += 4;
\t\t}\n-\t\tgextern(gcsym, nodconst(m), off, 4); // 削除
-\t\toff += 4; // 削除
+\t\tfree(bv); // bvの解放
\t}\n-\treturn off; // 削除
-\t// TODO: needs a test for nptrs>32 // 削除
+\tsym->type = typ(0, T); // 変更
+\tsym->type->width = symoffset; // 変更
}\n```
## コアとなるコードの解説
### `src/cmd/cc/bv.c`
このファイルは、ビットベクトル (`Bvec`) を扱うための基本的なユーティリティ関数を提供します。
* `WORDSIZE` と `WORDBITS`: `uint32`のサイズとビット数を定義しています。これは、ビットベクトルを`uint32`の配列として扱うための定数です。
* `bvsize(uintptr n)`: `n`ビットを格納するために必要なバイト数を計算します。`uint32`のワード境界に合わせて切り上げられます。
* `bvalloc(int32 n)`: `n`ビットを格納できる新しい`Bvec`構造体をヒープに割り当て、ゼロで初期化します。`Bvec`構造体自体と、その後に続くビットデータのためのメモリを確保します。
* `bvset(Bvec *bv, int32 i)`: `Bvec` `bv`の`i`番目のビットを1にセットします。これは、`i`を`WORDBITS`で割った商を配列のインデックスとし、剰余をビットシフト量として、対応する`uint32`ワードのビットをOR演算でセットすることで実現されます。
### `src/cmd/cc/cc.h`
`Bvec`構造体の定義と、`bvalloc`、`bvset`関数のプロトタイプが追加されました。これにより、他のCファイルからこれらの関数と構造体を利用できるようになります。
### `src/cmd/cc/pgen.c`
このファイルは、GoのCコンパイラのコード生成部分を担当しています。主要な変更点は以下の通りです。
* **`pointermap`から`dumpgcargs`への変更**:
* 以前の`pointermap`関数は、関数の引数リストのポインタマップを直接計算し、その結果を`gcsym`シンボルに書き込んでいました。
* 新しい`dumpgcargs`関数は、`Bvec`を一時的なストレージとして使用してポインタマップを構築します。
* 可変引数関数 (`hasdotdotdot()`) の場合は、ポインタがないことを示す`nptrs=0`を書き込みます。
* それ以外の場合、`bvalloc`を使って引数リストのサイズに応じた`Bvec`を割り当てます。
* `walktype1`関数を呼び出して、引数リスト内のポインタの位置を`Bvec`に記録します。
* `Bvec`が完成したら、そのビット数 (`bv->n`) と実際のビットデータ (`bv->b`) を`gcsym`シンボルに書き込みます。
* 最後に、割り当てた`Bvec`を`free`してメモリを解放します。
* **`pointermap_type`から`walktype1`への変更**:
* 以前の`pointermap_type`関数は、特定の型`t`とオフセット`offset`に基づいて、ポインタのビットマスクを直接計算して返していました。
* 新しい`walktype1`関数は、`Bvec`ポインタ`bv`を引数として受け取ります。
* ポインタ型 (`TIND`, `TARRAY`) が見つかると、`bvset(bv, ...)`を呼び出して、`bv`内の対応するビットをセットします。
* 構造体 (`TSTRUCT`) や共用体 (`TUNION`) の場合、再帰的に`walktype1`を呼び出して、そのメンバーのポインタを`bv`に記録します。
* この変更により、ポインタマップの構築ロジックが、直接ビットマスクを返すのではなく、共有の`Bvec`オブジェクトを更新する形に変わりました。これにより、より複雑なポインタマップの構築や、将来的な拡張が容易になります。
これらの変更により、`cmd/cc`はGCがポインタマップを扱う方法とより密接に連携するようになり、Goランタイム全体の整合性と将来のGC機能拡張への対応が強化されました。
## 関連リンク
* Go CL (Change List): [https://golang.org/cl/12692043](https://golang.org/cl/12692043)
## 参考にした情報源リンク
* [Go's Garbage Collector: A Comprehensive Guide](https://go.dev/blog/go15gc) (Go 1.5のGCに関する公式ブログ記事。ポインタマップの概念理解に役立つ)
* [Go runtime source code](https://github.com/golang/go/tree/master/src/runtime) (Goランタイムのソースコード。GCの実装やポインタマップの利用方法を深く理解するために参照)
* [Go compiler source code](https://github.com/golang/go/tree/master/src/cmd/compile) (Goコンパイラのソースコード。`cmd/cc`がどのようにGoコンパイラと連携するかを理解するために参照)
* [Go Interfaces in Depth](https://www.ardanlabs.com/blog/2019/07/go-interfaces-in-depth.html) (Goのインターフェースの内部構造に関する記事。インターフェースポインタの理解に役立つ)