[インデックス 18803] ファイルの概要
このコミットは、Goランタイムにおけるスタックサイズの丸め処理に関する変更です。特に、スタックサイズを2のべき乗に丸めることで、Windows/386およびPlan9/386環境でのビルド問題を修正し、Issue #7487を解決することを目的としています。
コミット
commit 84570aa9a18fa46dba1402004a54cedc7cf5e043
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Fri Mar 7 15:11:16 2014 -0500
runtime: round stack size to power of 2.
Fixes build on windows/386 and plan9/386.
Fixes #7487.
LGTM=mattn.jp, dvyukov, rsc
R=golang-codereviews, mattn.jp, dvyukov, 0intro, rsc
CC=golang-codereviews
https://golang.org/cl/72360043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/84570aa9a18fa46dba1402004a54cedc7cf5e043
元コミット内容
Goランタイムにおいて、スタックサイズを2のべき乗に丸めるように変更します。これにより、Windows/386およびPlan9/386環境でのビルドが修正され、Issue #7487が解決されます。
変更の背景
このコミットの主な背景は、特定のアーキテクチャ(特にWindows/386とPlan9/386)において、Goランタイムがスタックを割り当てる際に発生していたビルド問題と、それに起因するIssue #7487の解決です。
Goのランタイムは、ゴルーチンごとにスタックを割り当てます。効率的なメモリ管理とパフォーマンスのために、スタックの割り当てには特定のアライメント要件やサイズ制約が存在することが一般的です。以前のGoランタイムでは、スタックサイズが常に2のべき乗に丸められていなかったため、一部のシステム(特に32ビット環境)でスタックの境界条件に関する問題が発生していました。
具体的には、WindowsやPlan 9のようなOSでは、シグナルハンドリングなどのOS固有の目的のために、通常のガード領域の下にスタックごとに特定のシステム領域(StackSystem
)を必要とします。このStackSystem
のサイズと、Goランタイムが割り当てるスタックの基本サイズ(StackMin
)の合計が、特定の条件下で2のべき乗にならない場合がありました。
Goのスタック管理は、スタックの拡張(stack splitting)や縮小を効率的に行うために、スタックが特定のサイズ(通常は2のべき乗)にアラインされていることを前提としている場合があります。この前提が崩れると、スタックの割り当てや管理ロジックが正しく機能せず、ビルドエラーやランタイムエラーにつながる可能性がありました。
Issue #7487は、まさにこの問題、すなわちスタックサイズが2のべき乗にアラインされていないことによって引き起こされるビルド失敗やクラッシュを報告していました。このコミットは、スタックサイズを明示的に2のべき乗に丸めることで、この根本原因を解決し、これらのプラットフォームでの安定性と互換性を向上させることを目的としています。
前提知識の解説
このコミットを理解するためには、以下のGoランタイムの概念と一般的なコンピュータサイエンスの知識が必要です。
-
ゴルーチン (Goroutine): Go言語における軽量な実行スレッドです。OSのスレッドよりもはるかに少ないメモリで作成でき、数百万のゴルーチンを同時に実行することが可能です。各ゴルーチンは独自のスタックを持ちます。
-
スタック (Stack): プログラムの実行中に、関数呼び出しの引数、ローカル変数、リターンアドレスなどを格納するために使用されるメモリ領域です。Goのゴルーチンスタックは、必要に応じて動的にサイズが変更されます(stack splitting/copying)。
-
スタックの動的拡張 (Stack Splitting/Copying): Goランタイムの重要な機能の一つで、ゴルーチンのスタックが不足しそうになったときに、より大きなスタックを割り当てて内容をコピーし、新しいスタックに切り替えるメカニズムです。これにより、初期スタックサイズを小さく保ちつつ、必要に応じてスタックを拡張できます。
-
2のべき乗 (Power of 2): 2、4、8、16、32、... のように、2を整数回掛け合わせた数です。コンピュータシステムでは、メモリのアライメント、バッファサイズ、ハッシュテーブルのサイズなど、多くの場所で2のべき乗が効率的な処理のために利用されます。2のべき乗にアラインされたメモリ領域は、アドレス計算やビット演算が高速に行えるため、パフォーマンス上の利点があります。
-
メモリのアライメント (Memory Alignment): データがメモリ上で特定の境界(例えば、4バイト境界、8バイト境界など)に配置されることを指します。CPUは、アラインされたデータにアクセスする方が高速であるため、コンパイラやランタイムはデータの配置を調整します。スタックも同様に、特定の境界にアラインされていることが期待される場合があります。
-
StackSystem
: GoランタイムがOS固有の目的(シグナルハンドリングなど)のために、通常のスタックガード領域の下に確保する追加のスタック領域です。特にWindowsやPlan 9のようなOSで必要とされます。 -
StackMin
: ゴルーチンに割り当てられる最小のスタックサイズです。 -
runtime·round2
関数: このコミットで導入または変更される、数値を最も近い(またはそれ以上の)2のべき乗に丸めるユーティリティ関数です。 -
proc.c
,runtime.c
,runtime.h
,stack.c
,stack.h
: これらはGoランタイムのC言語で書かれたソースファイルです。proc.c
: プロセス(GoではP、M、Gなどのスケジューリングエンティティ)管理に関連するコード。ゴルーチンの割り当て(runtime·malg
)もここに含まれます。runtime.c
: ランタイムのコア機能、初期化、エラーチェックなど。runtime.h
: ランタイムの共通ヘッダファイルで、関数プロトタイプや定数定義が含まれます。stack.c
: スタックの割り当て、拡張、コピーなど、スタック管理のロジック。stack.h
: スタック関連の定数や構造体の定義。
技術的詳細
このコミットの核心は、Goランタイムがゴルーチンにスタックを割り当てる際に、そのサイズを常に2のべき乗に丸めるように変更することです。これにより、特に32ビットアーキテクチャ(Windows/386, Plan9/386)におけるスタックアライメントの問題を解決します。
変更の具体的な技術的詳細は以下の通りです。
-
runtime·round2
関数の導入/変更:src/pkg/runtime/stack.c
にあったstatic int32 round2(int32 x)
関数が、int32 runtime·round2(int32 x)
としてグローバルに公開されるように変更されました。これにより、ランタイムの他の部分からこの関数を呼び出してスタックサイズを丸めることが可能になります。- この関数は、与えられた整数
x
を、x
以上で最小の2のべき乗に丸めます。例えば、round2(5)
は8
を返し、round2(10)
は16
を返します。
-
スタック割り当て時の丸め処理の適用:
src/pkg/runtime/proc.c
のruntime·malg
関数(新しいゴルーチンを割り当てる関数)内で、スタックサイズを計算する際にruntime·round2
が適用されるようになりました。
これにより、// 変更前: // stk = runtime·stackalloc(newg, StackSystem + stacksize); // newg->stacksize = StackSystem + stacksize; // newg->stackbase = (uintptr)stk + StackSystem + stacksize - sizeof(Stktop); // 変更後: stacksize = runtime·round2(StackSystem + stacksize); // ここで丸める stk = runtime·stackalloc(newg, stacksize); newg->stacksize = stacksize; newg->stackbase = (uintptr)stk + stacksize - sizeof(Stktop);
runtime·stackalloc
に渡されるスタックサイズ、およびゴルーチン構造体newg
のstacksize
とstackbase
の計算が、常に2のべき乗に丸められた値に基づいて行われるようになります。
-
runtime·newstack
での丸め処理の適用:src/pkg/runtime/stack.c
のruntime·newstack
関数(スタック拡張時に新しいスタックを割り当てる関数)でも、同様にruntime·round2
が適用されるようになりました。
これにより、スタック拡張時にも割り当てられるスタックサイズが2のべき乗に丸められます。// 変更前: // framesize = round2(framesize); // 変更後: framesize = runtime·round2(framesize);
-
FixedStack
の計算ロジックの変更と検証:src/pkg/runtime/stack.h
において、StackSystem
の定義がOSごとに調整され、特にWindowsとPlan 9では具体的なバイト数が指定されました。- 最も重要な変更は、
FixedStack
の計算方法です。
ここで// 変更前: // FixedStack = StackMin + StackSystem, // 変更後: StackSystemRounded = StackSystem + (-StackSystem & (StackMin-1)); FixedStack = StackMin + StackSystemRounded;
StackSystemRounded
は、StackSystem
をStackMin
の倍数に丸めるための計算です。StackMin
は通常4096バイト(4KB)であり、これは2のべき乗です。この計算により、FixedStack
が常に2のべき乗になるように保証されます。 src/pkg/runtime/runtime.c
のruntime·check
関数に、FixedStack
が2のべき乗であることを検証するアサーションが追加されました。
これにより、ビルド時にif(FixedStack != runtime·round2(FixedStack)) runtime·throw("FixedStack is not power-of-2");
FixedStack
が2のべき乗でない場合にエラーがスローされ、問題が早期に検出されるようになります。
これらの変更により、Goランタイムはスタックの割り当てと管理において、より厳密な2のべき乗アライメントを強制するようになります。これは、特に特定のOSやアーキテクチャでスタックの境界条件が厳しく、アライメントが正しくないと問題が発生する環境において、安定性と互換性を大幅に向上させます。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は以下のファイルにまたがっています。
-
src/pkg/runtime/proc.c
:runtime·malg
関数内で、新しいゴルーチンのスタックサイズを計算する際にruntime·round2
を呼び出すように変更。newg->stacksize
とnewg->stackbase
の計算で、丸められたスタックサイズを使用するように変更。
-
src/pkg/runtime/runtime.c
:runtime·check
関数に、FixedStack
が2のべき乗であることを検証するアサーションを追加。stack.h
のインクルードを追加。
-
src/pkg/runtime/runtime.h
:runtime·round2
関数のプロトタイプ宣言を追加し、この関数が外部から利用可能であることを示す。
-
src/pkg/runtime/stack.c
:round2
関数をruntime·round2
にリネームし、static
キーワードを削除してグローバルに公開。copystack
関数内でruntime·round2
を呼び出す箇所を修正。runtime·newstack
関数内で、スタック拡張時のフレームサイズをruntime·round2
で丸めるように変更。
-
src/pkg/runtime/stack.h
:StackSystem
の定義をOS(Windows, Plan 9)ごとに具体的に指定。FixedStack
の計算ロジックを、StackSystemRounded
を導入して変更し、常に2のべき乗になるように保証。
コアとなるコードの解説
src/pkg/runtime/proc.c
の変更
@@ -1751,12 +1751,13 @@ runtime·malg(int32 stacksize)
newg = runtime·malloc(sizeof(G));
if(stacksize >= 0) {
+\t\tstacksize = runtime·round2(StackSystem + stacksize); // ここでスタックサイズを2のべき乗に丸める
\t\tif(g == m->g0) {
\t\t\t// running on scheduler stack already.
-\t\t\tstk = runtime·stackalloc(newg, StackSystem + stacksize);
+\t\t\tstk = runtime·stackalloc(newg, stacksize); // 丸められたサイズを使用
\t\t} else {\
\t\t\t// have to call stackalloc on scheduler stack.
-\t\t\tnewg->stacksize = StackSystem + stacksize;
+\t\t\tnewg->stacksize = stacksize; // 丸められたサイズを使用
\t\t\tg->param = newg;\n \t\t\truntime·mcall(mstackalloc);\n \t\t\tstk = g->param;\n@@ -1765,7 +1766,7 @@ runtime·malg(int32 stacksize)
\t\tnewg->stack0 = (uintptr)stk;\n \t\tnewg->stackguard = (uintptr)stk + StackGuard;\n \t\tnewg->stackguard0 = newg->stackguard;\n-\t\tnewg->stackbase = (uintptr)stk + StackSystem + stacksize - sizeof(Stktop);\n+\t\tnewg->stackbase = (uintptr)stk + stacksize - sizeof(Stktop);\n \t}\n \treturn newg;\n }\n```
`runtime·malg` は新しいゴルーチンを割り当てる際に呼び出されます。ここで、要求された `stacksize` に `StackSystem` を加えた合計が、まず `runtime·round2` 関数によって最も近い2のべき乗に丸められます。その後、この丸められた `stacksize` が `runtime·stackalloc` に渡され、実際にスタックメモリが割り当てられます。また、ゴルーチン構造体 `newg` の `stacksize` と `stackbase` も、この丸められた値に基づいて設定されます。これにより、ゴルーチンのスタックが常に2のべき乗のサイズで確保されることが保証されます。
### `src/pkg/runtime/runtime.c` の変更
```c
@@ -256,6 +257,9 @@ runtime·check(void)
\truntime·throw(\"float32nan3\");
TestAtomic64();
+\n+\tif(FixedStack != runtime·round2(FixedStack))\n+\t\truntime·throw(\"FixedStack is not power-of-2\");\n }
uint32
runtime·check
はランタイムの初期化時に実行される検証関数です。ここに追加された行は、FixedStack
(最小スタックサイズとシステムスタックサイズの合計)が実際に2のべき乗であるかどうかを検証します。もし2のべき乗でなければ、ランタイムはエラーをスローし、プログラムの実行を停止します。これは、スタック関連の重要な定数が正しく設定されていることを保証するための安全チェックです。
src/pkg/runtime/stack.c
の変更
@@ -555,8 +555,8 @@ copystack(G *gp, uintptr nframes, uintptr newsize)
}
// round x up to a power of 2.
-static int32
-round2(int32 x)\n+int32
+runtime·round2(int32 x) // 関数名を変更し、staticを削除
{\n \tint32 s;\n \n@@ -683,7 +683,7 @@ runtime·newstack(void)
\tif(framesize < StackMin)\n \t\tframesize = StackMin;\n \tframesize += StackSystem;\n-\tframesize = round2(framesize);\n+\tframesize = runtime·round2(framesize);\n \tstk = runtime·stackalloc(gp, framesize);\n \tif(gp->stacksize > runtime·maxstacksize) {\n \t\truntime·printf(\"runtime: goroutine stack exceeds %D-byte limit\\n\", (uint64)runtime·maxstacksize);\n```
`round2` 関数が `runtime·round2` にリネームされ、`static` キーワードが削除されました。これにより、この関数はランタイムの他の部分から呼び出し可能になります。また、`runtime·newstack` 関数(スタック拡張時に新しいスタックを割り当てる)でも、割り当てる `framesize` が `runtime·round2` によって2のべき乗に丸められるようになりました。これは、スタックの動的拡張時にもアライメント要件が満たされることを保証します。
### `src/pkg/runtime/stack.h` の変更
```c
@@ -57,15 +57,13 @@ enum {
// to each stack below the usual guard area for OS-specific
// purposes like signal handling. Used on Windows and on
// Plan 9 because they do not use a separate stack.
-\t// The new stack code requires stacks to be a power of two,
-\t// and the default start size is 4k, so make StackSystem also 4k
-\t// to keep the sum a power of two. StackSystem used to be
-\t// 512*sizeof(uintptr) on Windows and 512 bytes on Plan 9.
#ifdef GOOS_windows
-\tStackSystem = 4096,\n+\tStackSystem = 512 * sizeof(uintptr),\n #else
#ifdef GOOS_plan9
-\tStackSystem = 4096,\n+\t// The size of the note handler frame varies among architectures,\n+\t// but 512 bytes should be enough for every implementation.\n+\tStackSystem = 512,\n #else
\tStackSystem = 0,\n #endif\t// Plan 9
@@ -79,7 +77,8 @@ enum {
// If the amount needed for the splitting frame + StackExtra
// is less than this number, the stack will have this size instead.
StackMin = 4096,\n-\tFixedStack = StackMin + StackSystem,\n+\tStackSystemRounded = StackSystem + (-StackSystem & (StackMin-1)),\n+\tFixedStack = StackMin + StackSystemRounded,\n \n \t// Functions that need frames bigger than this use an extra\n \t// instruction to do the stack split check, to avoid overflow\n```
`StackSystem` の定義が、WindowsとPlan 9で具体的なバイト数(それぞれ `512 * sizeof(uintptr)` と `512`)に設定されました。これは、これらのOSでのシステムスタックの要件をより正確に反映するためです。
最も重要な変更は `FixedStack` の計算です。`StackSystemRounded` という新しい中間定数が導入され、`StackSystem` を `StackMin` の倍数に丸めるためのビット演算が使用されています。これにより、`FixedStack` が常に `StackMin` の倍数、かつ2のべき乗になることが保証されます。この変更は、スタックの最小サイズとシステムスタック領域の合計が、特定のアーキテクチャで期待されるアライメント要件を満たすようにするために不可欠です。
## 関連リンク
* Go Issue #7487: [https://github.com/golang/go/issues/7487](https://github.com/golang/go/issues/7487)
* Go CL 72360043: [https://golang.org/cl/72360043](https://golang.org/cl/72360043)
## 参考にした情報源リンク
* Go言語の公式ドキュメント (Go runtime, goroutines, stack management)
* Go言語のソースコード (特に `src/runtime` ディレクトリ)
* Issue #7487 の議論スレッド
* Goのコミット履歴と関連するコードレビュー
* コンピュータアーキテクチャにおけるメモリのアライメントに関する一般的な情報
* 2のべき乗への丸め処理に関するビット演算のテクニック