[インデックス 16810] ファイルの概要
このコミットは、Goランタイムにおけるconcatstring
関数の定義に関するバグ修正です。特に、386アーキテクチャでのビルド問題と、ガベージコレクタがスタックフレームを正しく解釈できない問題に対処しています。
コミット
commit 249f807c39e96a30707f5005881b6c1b8e08077e
Author: Russ Cox <rsc@golang.org>
Date: Thu Jul 18 12:19:38 2013 -0400
runtime: mark concatstring as variadic (fixes 386 build)
Windows was the only one seeing this bug reliably in the builder,
but it was easy to reproduce using 'GOGC=1 go test strconv'.
concatstring looked like it took only one string, but in fact it
takes a long list of strings. Add an explicit ... so that the traceback
will not use the "fixed" frame size and instead look at the
frame size metadata recorded by the caller.
R=golang-dev
TBR=golang-dev
CC=golang-dev
https://golang.org/cl/11531043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/249f807c39e96a30707f5005881b6c1b8e08077e
元コミット内容
runtime: mark concatstring as variadic (fixes 386 build)
このコミットは、runtime
パッケージ内のconcatstring
関数を可変引数関数として明示的にマークすることで、386アーキテクチャでのビルド問題を修正します。
Windowsのビルダでこのバグが頻繁に発生していましたが、GOGC=1 go test strconv
を実行することで容易に再現可能でした。concatstring
関数は、一見すると1つの文字列しか受け取らないように見えますが、実際には多数の文字列を受け取ります。明示的に...
を追加することで、トレースバックが「固定された」フレームサイズを使用せず、呼び出し元によって記録されたフレームサイズメタデータを使用するようになります。
変更の背景
Goのランタイム、特にガベージコレクタ(GC)は、スタックをスキャンして到達可能なオブジェクトを特定します。このスキャンプロセスでは、各関数のスタックフレームのサイズと、そのフレーム内にポインタが含まれているかどうかを正確に知る必要があります。
concatstring
関数は、Goの内部で文字列の連結を行うために使用されるランタイム関数です。コミットメッセージによると、この関数は実際には複数の文字列引数を受け取るにもかかわらず、そのシグネチャが可変引数(variadic)として明示されていませんでした。
この不一致が、特に386アーキテクチャのような特定の環境や、GCが積極的に動作する状況(GOGC=1
)で問題を引き起こしました。ガベージコレクタは、関数のスタックフレームサイズが固定であると誤解し、実際には可変長の引数がスタックに積まれているにもかかわらず、その部分を正しくスキャンできませんでした。これにより、ガベージコレクタがポインタを誤って解釈したり、到達可能なオブジェクトを解放してしまったりする可能性があり、結果としてクラッシュやデータ破損につながる可能性がありました。
このコミットは、concatstring
関数が可変引数であることを明示的に示すことで、ガベージコレクタがスタックフレームを正しく解釈し、ポインタを正確に追跡できるようにすることを目的としています。
前提知識の解説
Goランタイムとガベージコレクタ (GC)
Go言語は、独自のランタイムとガベージコレクタを内蔵しています。ガベージコレクタは、プログラムが動的に割り当てたメモリのうち、もはや到達不可能になった(参照されなくなった)メモリ領域を自動的に解放する役割を担います。GoのGCは、並行マーク&スイープ方式を採用しており、プログラムの実行と並行して動作することで、アプリケーションの一時停止(ストップ・ザ・ワールド)時間を最小限に抑えるように設計されています。
GCが正しく動作するためには、スタック上に存在するポインタを正確に識別し、それらが指すヒープ上のオブジェクトをマークする必要があります。このプロセスでは、各関数のスタックフレームの構造、特にポインタがどこに配置されているかを知る必要があります。
スタックフレームとポインタ情報
関数が呼び出されると、その関数専用のスタックフレームがスタック上に作成されます。このフレームには、関数のローカル変数、引数、戻りアドレスなどが格納されます。Goのランタイムは、コンパイル時に各関数のスタックフレームに関するメタデータ(ポインタマップ)を生成します。このメタデータは、GCがスタックをスキャンする際に、どのメモリ位置がポインタであり、どのメモリ位置がポインタではないかを識別するために使用されます。
可変引数関数 (Variadic Functions)
Go言語では、func foo(args ...int)
のように、引数の数が可変である関数を定義できます。このような関数が呼び出されると、可変引数はスライスとして関数内で扱われます。スタック上では、これらの可変引数は通常、固定引数の後に連続して配置されます。
386アーキテクチャ
386は、Intel 80386プロセッサに由来する32ビットアーキテクチャの総称です。古いシステムや特定の組み込み環境でまだ使用されている場合があります。アーキテクチャによっては、スタックフレームのレイアウトや、コンパイラが生成するポインタマップの解釈に微妙な違いが生じることがあります。このコミットで386ビルドが特に言及されているのは、そのアーキテクチャでのスタックフレームの扱いが他のアーキテクチャと異なり、このバグが顕在化しやすかったためと考えられます。
GOGC
環境変数
GOGC
はGoのガベージコレクタの動作を制御する環境変数です。デフォルト値は100で、ヒープサイズが前回のGC後から100%増加するとGCが実行されます。GOGC=1
のように非常に小さい値を設定すると、GCが頻繁に実行されるようになり、GC関連のバグを再現しやすくなります。
技術的詳細
このコミットの核心は、GoランタイムがC言語で記述された部分(src/pkg/runtime/string.goc
)におけるconcatstring
関数のシグネチャの修正です。
元のconcatstring
関数は、C言語の構文で以下のように定義されていました。
func concatstring(n int, s1 String) {
// ...
}
この定義は、n
(文字列の数)とs1
(最初の文字列)の2つの固定引数を持つ関数として解釈されます。しかし、実際にはconcatstring
はs1
の後に続くn-1
個の文字列もスタックから読み取って処理します。これは、Goの内部的な呼び出し規約において、可変引数が固定引数の後に連続してスタックに積まれることを利用したものです。
ガベージコレクタは、このC言語の関数定義を見て、concatstring
のスタックフレームが固定サイズであると判断し、そのポインタマップも固定引数のみを考慮して生成していました。しかし、実際にはs1
の後に可変長の文字列ポインタがスタックに存在するため、GCがスタックをスキャンする際に、これらの追加の文字列ポインタを認識できず、誤って非ポインタデータとして扱ったり、あるいはポインタではないデータをポインタとして解釈して不正なメモリアクセスを引き起こしたりする可能性がありました。
この問題を解決するため、コミットではconcatstring
の定義を以下のように変更しました。
void
runtime·concatstring(int32 n, String s1, ...)
{
// ...
}
ここで重要なのは、C言語の関数シグネチャに...
(エリプシス)が追加されたことです。これは、C言語において可変引数関数を示す標準的な方法です。Goのランタイムコンパイラ(gc
)は、C言語のソースコードをGoのランタイムにコンパイルする際に、この...
を認識し、この関数が可変引数関数であることをガベージコレクタに伝えるための特別なメタデータ(ポインタマップ)を生成します。
具体的には、可変引数関数としてマークされることで、GCは「固定されたフレームサイズ」に依存するのではなく、「呼び出し元によって記録されたフレームサイズメタデータ」を参照するようになります。これにより、concatstring
が実際に受け取る文字列の数に応じて、スタックフレームの可変部分も正しくスキャンできるようになり、ガベージコレクタがすべての文字列ポインタを正確に追跡できるようになります。
また、コミットには以下のコメントが追加されています。
// NOTE: Cannot use func syntax, because we need the ...
// to signal to the garbage collector that this function does
// not have a fixed size argument count.
これは、Goのfunc
キーワードを使った通常の関数定義ではなく、C言語の構文でruntime·concatstring
と定義している理由を説明しています。Goのfunc
構文では、ランタイム内部の特定の低レベル関数に対して、GCに可変引数であることを正確に伝えるための...
を直接指定する方法がなかったため、C言語の構文で定義する必要があったことを示唆しています。
コアとなるコードの変更箇所
変更はsrc/pkg/runtime/string.goc
ファイルに集中しています。
--- a/src/pkg/runtime/string.goc
+++ b/src/pkg/runtime/string.goc
@@ -170,10 +170,13 @@ concatstring(intgo n, String *s)
return out;
}
+// NOTE: Cannot use func syntax, because we need the ...,\
+// to signal to the garbage collector that this function does\
+// not have a fixed size argument count.\
#pragma textflag 7
-// s1 is the first of n strings.\
-// the output string follows.\
-func concatstring(n int, s1 String) {
+void
+runtime·concatstring(int32 n, String s1, ...)
+{
(&s1)[n] = concatstring(n, &s1);\
}
コアとなるコードの解説
この変更の核心は、concatstring
関数のシグネチャの変更と、その変更の意図を説明するコメントの追加です。
-
関数のシグネチャ変更:
- 変更前:
func concatstring(n int, s1 String)
- 変更後:
void runtime·concatstring(int32 n, String s1, ...)
この変更により、
concatstring
関数はC言語の可変引数関数として定義されました。void
は戻り値がないことを示し、runtime·
プレフィックスはGoランタイム内部の関数であることを示します。最も重要なのは、引数リストの最後に...
が追加されたことです。これにより、コンパイラとガベージコレクタは、この関数が固定数の引数だけでなく、可変数の引数も受け取ることを認識します。 - 変更前:
-
コメントの追加:
// NOTE: Cannot use func syntax, because we need the ...,\ // to signal to the garbage collector that this function does\ // not have a fixed size argument count.\
このコメントは、なぜGoの通常の
func
キーワードではなく、C言語の構文で関数を定義する必要があったのかを明確に説明しています。Goのfunc
構文では、ランタイムの低レベルな部分でGCに可変引数であることを正確に伝えるための...
を直接指定するメカニズムが当時なかったため、C言語の可変引数構文を利用する必要があったことを示しています。これは、Goランタイムの非常に低レベルな部分での実装上の制約と、GCの正確な動作を保証するための工夫を示しています。
この修正により、ガベージコレクタはconcatstring
のスタックフレームをスキャンする際に、可変引数部分も正しくポインタとして認識し、メモリの安全性を確保できるようになりました。
関連リンク
- Go言語のガベージコレクタに関する公式ドキュメントやブログ記事:
- Go's Garbage Collector: A Brief History - The Go Programming Language (Go 1.5でのGC改善に関する記事ですが、GCの基本的な考え方を理解するのに役立ちます)
- Go言語の内部実装に関する資料:
- Go runtime source code (Goランタイムのソースコード)
参考にした情報源リンク
- コミットメッセージとdiff: https://github.com/golang/go/commit/249f807c39e96a30707f5005881b6c1b8e08077e
- Go言語のガベージコレクタの仕組みに関する一般的な知識
- C言語の可変引数関数に関する一般的な知識
- Go言語の内部実装に関する一般的な知識 (特にランタイムとコンパイラの連携)
- Go issue tracker: https://golang.org/cl/11531043 (コミットメッセージに記載されているChange Listへのリンク)