[インデックス 17102] ファイルの概要
このコミットは、Goコンパイラのcmd/gc
パッケージ内のpgen.c
ファイルに対する変更です。具体的には、スタックフレーム内のポインタブロックのサイズ計算(stkptrsize
)に関するバグ修正が目的です。
コミット
commit 390656affd8993ce6332dca3fa86d57659622fdf
Author: Russ Cox <rsc@golang.org>
Date: Thu Aug 8 16:44:16 2013 -0400
cmd/gc: fix stkptrsize calculation
I moved the pointer block from one end of the frame
to the other toward the end of working on the last CL,
and of course that made the optimization no longer work.
Now it works again:
0030 (bug361.go:12) DATA gclocals·0+0(SB)/4,$4
0030 (bug361.go:12) DATA gclocals·0+4(SB)/4,$3
0030 (bug361.go:12) GLOBL gclocals·0+0(SB),8,$8
Fixes arm build (this time for sure!).
TBR=golang-dev
CC=cshapiro, golang-dev, iant
https://golang.org/cl/12627044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/390656affd8993ce6332dca3fa86d57659622fdf
元コミット内容
cmd/gc: fix stkptrsize calculation
このコミットは、Goコンパイラ(cmd/gc
)におけるstkptrsize
の計算を修正するものです。以前の変更でスタックフレーム内のポインタブロックの配置が変更されたため、最適化が正しく機能しなくなっていました。この修正により、その最適化が再び機能するようになり、特にARMビルドの問題が解決されます。
変更の背景
Goのガベージコレクタは、スタック上のポインタを正確に識別する必要があります。そのため、コンパイラは各関数のスタックフレーム内に存在するポインタの情報をgclocals
というシンボルとして出力します。このgclocals
は、スタックフレーム内のどのオフセットにポインタが存在し、そのポインタが指すメモリ領域のサイズがどれくらいかを示すビットマップのような情報を含んでいます。
以前のコミットで、スタックフレーム内のポインタブロックの配置が変更されました。具体的には、スタックフレームの一方の端からもう一方の端へ移動されたようです。この変更により、stkptrsize
(スタックフレーム内のポインタが占める領域のサイズ)の計算ロジックが壊れてしまい、gclocals
の生成が正しく行われなくなりました。結果として、ガベージコレクタがスタック上のポインタを正しく認識できなくなり、特にARMアーキテクチャでのビルドや実行時に問題が発生していました。
このコミットは、この壊れたstkptrsize
の計算を修正し、gclocals
が再び正しく生成されるようにすることで、ARMビルドの問題を解決することを目的としています。
前提知識の解説
- Goコンパイラ (
cmd/gc
): Go言語の公式コンパイラです。Goのソースコードを機械語に変換する役割を担います。スタックフレームのレイアウトやガベージコレクションに必要なメタデータの生成も行います。 - スタックフレーム: 関数が呼び出されるたびに、その関数が使用するローカル変数、引数、リターンアドレスなどを格納するために確保されるメモリ領域です。スタック上に積まれていきます。
- ガベージコレクション (GC): プログラムが動的に確保したメモリのうち、もはや使用されていない領域を自動的に解放する仕組みです。GoのGCは、スタック上のポインタをスキャンして、ヒープ上のオブジェクトへの参照を追跡します。
- ポインタ: メモリ上の特定のアドレスを指し示す変数です。GoのGCは、ポインタを正確に識別し、それらが指すオブジェクトがまだ到達可能かどうかを判断する必要があります。
gclocals
: Goコンパイラが生成するメタデータの一つで、特定の関数のスタックフレーム内に存在するポインタのオフセットとサイズに関する情報を含みます。ガベージコレクタはこの情報を用いて、スタック上のポインタを効率的にスキャンします。stkptrsize
: スタックフレーム内でポインタが占める領域の合計サイズを示す変数です。この値はgclocals
の生成に用いられます。xoffset
: スタックフレーム内の変数やフィールドのオフセット(基準位置からの距離)を示す値です。haspointers(type)
: 指定された型がポインタを含むかどうかを判定する関数です。rnd(size, align)
:size
をalign
の倍数に切り上げる(アラインメントする)関数です。メモリのアラインメントは、CPUが効率的にメモリにアクセスするために重要です。- ARMビルド: ARMアーキテクチャ向けのGoプログラムのビルドを指します。ARMはモバイルデバイスなどで広く使われているCPUアーキテクチャです。特定のアーキテクチャでは、スタックフレームのレイアウトやポインタの扱いに特有の要件がある場合があります。
技術的詳細
このコミットの核心は、src/cmd/gc/pgen.c
ファイル内のallocauto
関数におけるstkptrsize
の計算ロジックの変更です。
以前のコードでは、ptrlimit
という変数を導入し、スタックフレーム内で最初にポインタを持つ変数が現れるオフセットを記録しようとしていました。そして、stkptrsize
をstksize - ptrlimit
として計算していました。これは、ポインタブロックがスタックフレームの「一方の端」に固まっているという前提に基づいた最適化でした。
しかし、コミットメッセージにあるように、ポインタブロックの配置が変更されたため、この前提が崩れました。新しいロジックでは、stkptrsize
の計算方法がより直接的になります。
変更点を見てみましょう。
-
dumpgclocals
関数の変更:xoffset = node->xoffset + stksize;
がxoffset = node->xoffset + stkptrsize;
に変更されています。- これは、
gclocals
のオフセット計算において、スタック全体のサイズstksize
ではなく、ポインタ領域のサイズstkptrsize
を基準にするように修正されたことを意味します。これにより、ポインタブロックの実際の配置に合わせた正確なオフセットが計算されるようになります。
-
allocauto
関数の変更:ptrlimit
変数が削除されました。これは、ptrlimit
に基づくstkptrsize
の計算ロジックが不要になったことを示します。stkptrsize = 0;
が初期化時に追加されました。- ローカル変数をイテレートするループ内で、
if(haspointers(n->type))
の条件が真の場合、stkptrsize = stksize;
と設定されるようになりました。これは、ポインタを持つ変数がスタックに割り当てられるたびに、その時点でのstksize
(スタックの現在の合計サイズ)をstkptrsize
として記録することを意味します。これにより、stkptrsize
はスタックフレーム内のポインタが占める領域の「上限」を正確に追跡するようになります。 - 以前の
if(ptrlimit < 0 && haspointers(n->type)) ptrlimit = stksize - w;
およびstkptrsize = stksize - ptrlimit;
のロジックが削除されました。これは、ポインタブロックがスタックフレームの特定の端に固まっているという仮定を排除し、より汎用的な計算方法に移行したことを示します。
この修正により、stkptrsize
はスタックフレーム内のポインタが実際に占める領域のサイズを正確に反映するようになり、gclocals
の生成が正しく行われるようになりました。これにより、ガベージコレクタがスタック上のポインタを正しく識別できるようになり、ARMビルドの問題が解決されました。
コアとなるコードの変更箇所
src/cmd/gc/pgen.c
--- a/src/cmd/gc/pgen.c
+++ b/src/cmd/gc/pgen.c
@@ -341,7 +341,7 @@ dumpgclocals(Node* fn, Sym *sym)
tnode = ll->n;
if(node->class == PAUTO && node->op == ONAME) {
if(haspointers(node->type)) {
- xoffset = node->xoffset + stksize;
+ xoffset = node->xoffset + stkptrsize;
walktype1(node->type, &xoffset, bv);
}
}
@@ -397,7 +397,6 @@ allocauto(Prog* ptxt)
NodeList *ll;
Node* n;
vlong w;
- vlong ptrlimit;
if(curfn->dcl == nil) {
stksize = 0;
@@ -437,7 +436,7 @@ allocauto(Prog* ptxt)
// Reassign stack offsets of the locals that are still there.
stksize = 0;
- ptrlimit = -1;
+ stkptrsize = 0;
for(ll = curfn->dcl; ll != nil; ll=ll->next) {
n = ll->n;
if (n->class != PAUTO || n->op != ONAME)
@@ -449,8 +448,8 @@ allocauto(Prog* ptxt)
fatal("bad width");
stksize += w;
stksize = rnd(stksize, n->type->align);
- if(ptrlimit < 0 && haspointers(n->type))
- ptrlimit = stksize - w;
+ if(haspointers(n->type))
+ stkptrsize = stksize;
if(thechar == '5')
stksize = rnd(stksize, widthptr);
if(stksize >= (1ULL<<31)) {
@@ -460,11 +459,6 @@ allocauto(Prog* ptxt)
n->stkdelta = -stksize - n->xoffset;
}
stksize = rnd(stksize, widthptr);
-
- if(ptrlimit < 0)
- stkptrsize = 0;
- else
- stkptrsize = stksize - ptrlimit;
stkptrsize = rnd(stkptrsize, widthptr);
fixautoused(ptxt);
コアとなるコードの解説
-
dumpgclocals
関数内の変更:xoffset = node->xoffset + stksize;
をxoffset = node->xoffset + stkptrsize;
に変更。- これは、
gclocals
のオフセット計算において、スタックフレーム全体のサイズstksize
ではなく、ポインタが占める領域のサイズstkptrsize
を基準とすることで、ポインタの正確な位置をgclocals
に反映させるための修正です。
-
allocauto
関数内の変更:vlong ptrlimit;
の削除:ptrlimit
変数が不要になったため削除されました。stkptrsize = 0;
の追加:allocauto
関数が呼び出されるたびにstkptrsize
が0に初期化されるようになりました。ptrlimit = -1;
の削除:ptrlimit
の初期化も不要になりました。if(ptrlimit < 0 && haspointers(n->type)) ptrlimit = stksize - w;
の削除: 以前のptrlimit
に基づく複雑な計算ロジックが削除されました。if(haspointers(n->type)) stkptrsize = stksize;
の追加: これが新しいstkptrsize
の計算ロジックの核心です。ローカル変数を処理するループ内で、もし現在の変数がポインタを含む場合(haspointers(n->type)
が真)、その時点でのスタックの合計サイズstksize
をstkptrsize
に代入します。これにより、stkptrsize
はスタックフレーム内でポインタが占める領域の「最大範囲」を正確に追跡します。if(ptrlimit < 0) stkptrsize = 0; else stkptrsize = stksize - ptrlimit;
の削除:ptrlimit
が削除されたため、この最終的なstkptrsize
の計算ロジックも削除されました。
これらの変更により、stkptrsize
はスタックフレーム内のポインタの実際の配置に関わらず、ポインタが占める領域のサイズを正確に計算できるようになり、gclocals
の生成が安定しました。
関連リンク
- Go言語のガベージコレクションに関するドキュメントやブログ記事: GoのGCの仕組みを理解する上で役立ちます。
- Goコンパイラのソースコード:
src/cmd/gc/
ディレクトリは、Goコンパイラのバックエンド部分のコードを含んでいます。
参考にした情報源リンク
- Go言語の公式ドキュメント
- Goのソースコードリポジトリ (GitHub)
- GoのIssueトラッカー (Go Bug Tracker)
- Goのメーリングリスト (golang-dev)
- Goのガベージコレクションに関する技術ブログや論文
- ARMアーキテクチャに関するドキュメント (スタックフレームのレイアウトなど)
gclocals
に関するGoの内部ドキュメントやコメント (もしあれば)https://golang.org/cl/12627044
(元のCLへのリンク)
(注: 上記の「関連リンク」と「参考にした情報源リンク」は一般的な情報源の例であり、この特定のコミットに関する詳細な情報源を特定するには、さらに調査が必要です。)