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

[インデックス 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): sizealignの倍数に切り上げる(アラインメントする)関数です。メモリのアラインメントは、CPUが効率的にメモリにアクセスするために重要です。
  • ARMビルド: ARMアーキテクチャ向けのGoプログラムのビルドを指します。ARMはモバイルデバイスなどで広く使われているCPUアーキテクチャです。特定のアーキテクチャでは、スタックフレームのレイアウトやポインタの扱いに特有の要件がある場合があります。

技術的詳細

このコミットの核心は、src/cmd/gc/pgen.cファイル内のallocauto関数におけるstkptrsizeの計算ロジックの変更です。

以前のコードでは、ptrlimitという変数を導入し、スタックフレーム内で最初にポインタを持つ変数が現れるオフセットを記録しようとしていました。そして、stkptrsizestksize - ptrlimitとして計算していました。これは、ポインタブロックがスタックフレームの「一方の端」に固まっているという前提に基づいた最適化でした。

しかし、コミットメッセージにあるように、ポインタブロックの配置が変更されたため、この前提が崩れました。新しいロジックでは、stkptrsizeの計算方法がより直接的になります。

変更点を見てみましょう。

  1. dumpgclocals関数の変更:

    • xoffset = node->xoffset + stksize;xoffset = node->xoffset + stkptrsize; に変更されています。
    • これは、gclocalsのオフセット計算において、スタック全体のサイズstksizeではなく、ポインタ領域のサイズstkptrsizeを基準にするように修正されたことを意味します。これにより、ポインタブロックの実際の配置に合わせた正確なオフセットが計算されるようになります。
  2. 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)が真)、その時点でのスタックの合計サイズstksizestkptrsizeに代入します。これにより、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へのリンク)

(注: 上記の「関連リンク」と「参考にした情報源リンク」は一般的な情報源の例であり、この特定のコミットに関する詳細な情報源を特定するには、さらに調査が必要です。)