[インデックス 18798] ファイルの概要
このコミットは、Goランタイムにおけるスタック管理コードの大規模なリファクタリングとバグ修正を目的としています。特に、goroutineのスタックサイズ計算、スタックトップ情報(Stktop
)の維持、およびスタックの解放ロジックにおける複数の既存のバグに対処しています。これらのバグは、レースコンディションなどの不安定な挙動を引き起こす可能性がありました。変更の核心は、スタックの割り当てと解放を司るstackalloc
およびstackfree
関数に、スタックサイズとスタックのメモリ割り当て元(ヒープかスタックキャッシュか)に関するロジックを集約することにあります。
コミット
commit 1a89e6388c3f1994da17a1d91a45920663db2af5
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Fri Mar 7 20:52:29 2014 +0400
runtime: refactor and fix stack management code
There are at least 3 bugs:
1. g->stacksize accounting is broken during copystack/shrinkstack
2. stktop->free is not properly maintained during copystack/shrinkstack
3. stktop->free logic is broken:
we can have stktop->free==FixedStack,
and we will free it into stack cache,
but it actually comes from heap as the result of non-copying segment shrink
This shows as at least spurious races on race builders (maybe something else as well I don't know).
The idea behind the refactoring is to consolidate stacksize and
segment origin logic in stackalloc/stackfree.
Fixes #7490.
LGTM=rsc, khr
R=golang-codereviews, rsc, khr
CC=golang-codereviews
https://golang.org/cl/72440043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1a89e6388c3f1994da17a1d91a45920663db2af5
元コミット内容
このコミットは、Goランタイムのスタック管理コードにおける複数の深刻なバグを修正し、関連コードをリファクタリングすることを目的としています。コミットメッセージには、少なくとも以下の3つのバグが特定されています。
-
g->stacksize
の計算がcopystack
/shrinkstack
時に壊れる:g
構造体(goroutineを表す)内のstacksize
フィールドは、goroutineに割り当てられたスタックの総サイズを追跡します。しかし、スタックがコピー(copystack
)されたり、縮小(shrinkstack
)されたりする際に、このstacksize
の計算が正しく行われず、不正確な値になる問題がありました。これは、スタックの実際の使用状況とランタイムが認識しているサイズとの間に不整合を生じさせます。 -
stktop->free
がcopystack
/shrinkstack
時に適切に維持されない:Stktop
構造体は、スタックのトップに位置するメタデータブロックであり、スタックに関する様々な情報(例えば、パニック情報や引数ポインタなど)を保持します。以前のバージョンでは、このStktop
構造体内にfree
というフィールドがあり、これは解放すべきスタックのサイズを示していました。しかし、スタックのコピーや縮小が行われる際に、このstktop->free
フィールドが適切に更新または維持されず、結果としてスタックの不適切な解放やメモリリーク、あるいは二重解放につながる可能性がありました。 -
stktop->free
のロジックが壊れている: 特に問題とされたのは、stktop->free
がFixedStack
(固定サイズのスタック)と等しい場合、そのスタックがスタックキャッシュに解放されるべきだと判断されるにもかかわらず、実際には「非コピーセグメント縮小」の結果としてヒープから割り当てられたスタックである場合があるという点です。スタックキャッシュは固定サイズのスタックを再利用するためのものであり、ヒープから割り当てられた可変サイズのスタックを扱うべきではありません。この誤った判断により、スタックキャッシュの整合性が損なわれたり、ヒープメモリの不適切な管理が発生したりする可能性がありました。
これらのバグは、特にGoのレース検出ツール(race builders)上で「偽のレースコンディション」(spurious races)として現れていました。これは、実際のデータ競合がないにもかかわらず、ランタイムの内部状態の不整合によって競合が報告されることを意味します。このような偽陽性は、デバッグを困難にし、ランタイムの安定性に対する信頼を損ないます。
このリファクタリングの根本的なアイデアは、スタックサイズと、スタックがどこから割り当てられたか(ヒープかスタックキャッシュか)という情報を、stackalloc
(スタック割り当て)とstackfree
(スタック解放)という2つの中心的な関数に集約し、一元的に管理することです。これにより、スタック管理ロジックの複雑さを軽減し、上記のバグを根本的に解決することを目指しています。
このコミットは、Go issue #7490 を修正します。
変更の背景
Goランタイムは、goroutineごとに独立したスタックを持ち、必要に応じてそのスタックサイズを動的に伸縮させる機能(copystack
やshrinkstack
)を備えています。これは、メモリ効率を高め、多数のgoroutineを効率的に実行するために不可欠な機能です。しかし、この動的なスタック管理は非常に複雑であり、メモリの割り当て、解放、ポインタの調整、そして各種メタデータの整合性維持が正確に行われる必要があります。
上記の「元コミット内容」で述べられているように、既存のスタック管理コードには複数のバグが存在していました。これらのバグは、特にスタックのコピーや縮小といった、スタックの物理的な位置やサイズが変更される操作において顕在化しました。
g->stacksize
の不正確な追跡: ランタイムがgoroutineのスタックサイズを正確に把握できないと、メモリの過剰な割り当てや、逆にスタックオーバーフローの検出漏れにつながる可能性があります。Stktop
メタデータの不整合:Stktop
はスタックの重要なメタデータを含むため、その情報が不整合を起こすと、パニック処理、スタックトレース、あるいはスタックの解放といったランタイムの基本的な機能に悪影響を及ぼします。特に、スタックがヒープから割り当てられたものか、スタックキャッシュから再利用されたものかという「セグメントの起源」に関する情報が不正確であると、誤った解放パスが選択され、メモリ破損やクラッシュにつながる恐れがありました。- 偽のレースコンディション: これらの内部的な不整合は、Goのレース検出器が誤ってデータ競合を報告する原因となっていました。これは、ランタイムの内部状態が期待通りに更新されないために、異なるgoroutineが同時にアクセスしているかのように見える状況が生じるためです。偽陽性の報告は、開発者が実際のバグとランタイムの内部的な問題とを区別することを困難にし、デバッグの効率を著しく低下させます。
これらの問題に対処するため、スタック管理ロジックをリファクタリングし、stackalloc
とstackfree
にスタックのサイズと起源に関する責任を集約することで、コードの複雑性を減らし、堅牢性を向上させる必要がありました。これにより、スタック管理の信頼性を高め、ランタイム全体の安定性を確保することが、この変更の重要な背景となっています。
前提知識の解説
このコミットの変更内容を理解するためには、Goランタイムのスタック管理に関するいくつかの基本的な概念を理解しておく必要があります。
-
Goroutine (G): Goにおける軽量スレッドの単位です。各goroutineは独自の実行スタックを持ちます。ランタイム内部では、
G
構造体で表現され、スタックの開始アドレス (stack0
)、スタックのベースアドレス (stackbase
)、スタックガードページのアドレス (stackguard
,stackguard0
)、そしてスタックの総サイズ (stacksize
) などの情報が含まれます。 -
M (Machine): OSのスレッドを表します。Goランタイムは、複数のMを管理し、それぞれがGoコードを実行します。Mは通常、OSのネイティブスレッドに1対1でマッピングされます。
-
P (Processor): 論理プロセッサを表し、MとGの間の仲介役となります。Pは実行可能なgoroutineのキューを保持し、MがGを実行するためのコンテキストを提供します。
-
スタックの動的伸縮 (Stack Growth/Shrink): Goのgoroutineスタックは、必要に応じて自動的にサイズが変更されます。
- スタックの拡張 (Stack Growth): 関数呼び出しがスタックの現在の容量を超えそうになると、ランタイムはより大きな新しいスタックを割り当て、古いスタックの内容を新しいスタックにコピーします。このプロセスは
copystack
関数によって行われます。 - スタックの縮小 (Stack Shrink): goroutineが多くのスタックメモリを消費した後、その必要がなくなった場合(例えば、深い再帰呼び出しから戻った後など)、ランタイムはスタックをより小さなサイズに縮小し、余分なメモリを解放することがあります。これは
shrinkstack
関数によって行われます。
- スタックの拡張 (Stack Growth): 関数呼び出しがスタックの現在の容量を超えそうになると、ランタイムはより大きな新しいスタックを割り当て、古いスタックの内容を新しいスタックにコピーします。このプロセスは
-
スタックキャッシュ: Goランタイムは、頻繁に割り当て・解放される固定サイズのスタック(
FixedStack
)を効率的に再利用するために、スタックキャッシュを保持しています。これにより、OSへのシステムコールを減らし、メモリ割り当てのオーバーヘッドを削減します。 -
Stktop
構造体: Goのスタックは下方向に成長します(アドレスが減少する方向)。Stktop
構造体は、スタックの「トップ」(最も高いアドレス、つまりスタックの基底)に配置されるメタデータブロックです。この構造体には、パニック情報、引数ポインタ、スタックの起源に関する情報などが含まれていました。このコミット以前は、free
というフィールドがあり、スタック解放時に使用されるサイズを示していました。 -
stackalloc
とstackfree
関数:runtime·stackalloc(size)
: 指定されたサイズのスタックメモリを割り当てます。これは、スタックキャッシュから取得するか、必要に応じてヒープから直接割り当てます。runtime·stackfree(ptr, size)
: 指定されたポインタとサイズのスタックメモリを解放します。これは、スタックキャッシュに戻すか、ヒープに解放します。
-
FixedStack
とヒープからの割り当て: Goのgoroutineは、最初は小さな固定サイズのスタック(FixedStack
)で開始します。このスタックは通常、スタックキャッシュから供給されます。しかし、FixedStack
よりも大きなスタックが必要な場合や、スタックキャッシュが空の場合、ランタイムはヒープから直接メモリを割り当ててスタックとして使用します。この「起源」の情報は、スタックの解放時に重要になります。なぜなら、スタックキャッシュに戻すべきか、ヒープに解放すべきかを判断する必要があるからです。 -
レースコンディション (Race Condition): 複数のgoroutineが共有リソース(メモリなど)に同時にアクセスし、少なくとも1つのアクセスが書き込みであり、かつアクセスが同期されていない場合に発生するバグです。結果として、実行のタイミングによってプログラムの挙動が非決定論的になります。Goのレース検出器は、このような問題を特定するのに役立ちますが、ランタイムの内部的な不整合が原因で「偽のレース」が報告されることもあります。
これらの概念を理解することで、このコミットがGoランタイムのスタック管理のどの部分をどのように改善しようとしているのかが明確になります。
技術的詳細
このコミットの技術的詳細は、主にGoランタイムのスタック管理におけるデータ構造と関数のインターフェース、そしてその実装ロジックの変更に集約されます。
-
Stktop
構造体の変更:free
フィールドの削除: 以前のStktop
構造体にはuintptr free;
フィールドが存在し、これはスタック解放時に使用されるサイズを示していました。このコミットでは、このfree
フィールドが削除されます。これは、スタックの解放サイズをStktop
に持たせるのではなく、stackfree
関数がスタックのベースアドレスとStktop
ポインタから計算するように変更されたためです。これにより、copystack
/shrinkstack
時のfree
フィールドの不適切な維持というバグが根本的に解消されます。malloced
フィールドの追加: 新たにbool malloced;
フィールドがStktop
に追加されます。このフィールドは、そのスタックがヒープから直接割り当てられたもの(true
)か、それともスタックキャッシュから取得された固定サイズのスタック(false
)かを示すフラグです。この情報は、スタックの解放時に、スタックをヒープに返すか、スタックキャッシュに戻すかを正確に判断するために使用されます。これにより、stktop->free
ロジックのバグ(ヒープ割り当てスタックがスタックキャッシュに解放される問題)が修正されます。
-
runtime·stackalloc
およびruntime·stackfree
関数のシグネチャ変更:runtime·stackalloc(uint32 n)
からruntime·stackalloc(G *gp, uint32 n)
へ:stackalloc
関数は、割り当てるスタックのサイズn
に加えて、対象のgoroutineのG
ポインタgp
を受け取るようになりました。これにより、stackalloc
内でgp->stacksize
を直接更新できるようになり、g->stacksize
の計算がcopystack
/shrinkstack
時に壊れるというバグに対処します。また、割り当てられたスタックのStktop
にmalloced
フラグを設定する責任も持つようになりました。runtime·stackfree(void *v, uintptr n)
からruntime·stackfree(G *gp, void *v, Stktop *top)
へ:stackfree
関数は、解放するスタックのポインタv
とサイズn
に加えて、対象のgoroutineのG
ポインタgp
と、スタックのStktop
ポインタtop
を受け取るようになりました。これにより、stackfree
内でgp->stacksize
を正確にデクリメントできるようになります。また、Stktop
ポインタを受け取ることで、top->malloced
フラグを参照して、スタックをヒープに解放すべきか、スタックキャッシュに戻すべきかを正確に判断できるようになりました。解放サイズn
は、top
とv
から計算されるようになりました。
-
g->stacksize
の管理の集約: 以前は、g->stacksize
の更新がcopystack
やshrinkstack
など、複数の場所で散発的に行われていました。この変更により、stackalloc
がスタックを割り当てる際にgp->stacksize
をインクリメントし、stackfree
がスタックを解放する際にgp->stacksize
をデクリメントするように、その管理責任がstackalloc
/stackfree
に集約されました。これにより、stacksize
の計算の正確性が向上し、不整合が減少します。 -
スタック操作関数の修正:
runtime·unwindstack
、gfput
、runtime·malg
、runtime·newstack
、copystack
、runtime·shrinkstack
といったスタックを操作する主要な関数は、新しいstackalloc
とstackfree
のシグネチャとロジックに合わせて修正されました。- 特に
copystack
では、新しいスタックのStktop
にもmalloced
フラグが正しくコピーされるように変更されています。 runtime·shrinkstack
では、縮小されたスタックのサイズ分だけgp->stacksize
がデクリメントされるようになりました。
- 特に
-
StackTop
定数の変更:src/pkg/runtime/stack.h
で定義されているStackTop
定数が96
から88
に変更されました。この定数は、スタックのトップに位置するデータブロックの想定サイズを示しています。この変更は、Stktop
構造体からfree
フィールドが削除され、malloced
フィールドが追加されたことによるStktop
構造体自体のサイズ変更を反映したものです。これにより、ランタイムがスタックトップのメタデータブロックのサイズを正しく認識し、スタックの割り当てやポインタ計算が正確に行われるようになります。
これらの変更は、Goランタイムのスタック管理の堅牢性と正確性を大幅に向上させ、以前のバグによって引き起こされていた偽のレースコンディションやその他の不安定な挙動を解消することを目的としています。
コアとなるコードの変更箇所
このコミットでは、Goランタイムのスタック管理に関連する複数のファイルが変更されています。主要な変更箇所は以下の通りです。
-
src/pkg/runtime/panic.c
:runtime·unwindstack
関数内で、古いスタックを解放する際にruntime·stackfree
の呼び出し方が変更されました。- if(top->free != 0) { - gp->stacksize -= top->free; - runtime·stackfree(stk, top->free); - } + runtime·stackfree(gp, stk, top);
top->free
のチェックとgp->stacksize
の更新が削除され、新しいruntime·stackfree
のシグネチャに合わせてgp
とtop
を渡すようになりました。
-
src/pkg/runtime/proc.c
:mstackalloc
関数とruntime·malg
関数で、runtime·stackalloc
の呼び出しにG*
ポインタを渡すように変更されました。// mstackalloc - gp->param = runtime·stackalloc((uintptr)gp->param); + newg = (G*)gp->param; + size = newg->stacksize; + newg->stacksize = 0; + gp->param = runtime·stackalloc(newg, size); // runtime·malg - stk = runtime·stackalloc(StackSystem + stacksize); + stk = runtime·stackalloc(newg, StackSystem + stacksize); // ... - g->param = (void*)(StackSystem + stacksize); + newg->stacksize = StackSystem + stacksize; + g->param = newg; // ... - newg->stacksize = StackSystem + stacksize; // 削除
g->param
を介してnewg
とstacksize
を渡すロジックが導入され、stackalloc
がnewg
のstacksize
を直接更新するように変更されました。gfput
関数で、goroutineのスタックを解放するロジックが変更されました。- if(stksize != FixedStack) { + if(stksize != gp->stacksize) { // スタックサイズの一貫性チェック + runtime·printf("runtime: bad stacksize, goroutine %D, remain=%d, last=%d\n", + gp->goid, (int32)gp->stacksize, (int32)stksize); + runtime·throw("gfput: bad stacksize"); + } + top = (Stktop*)gp->stackbase; + if(top->malloced) { // mallocedフラグに基づいて解放 - // non-standard stack size - free it. - runtime·stackfree((void*)gp->stack0, stksize); - gp->stacksize = 0; + runtime·stackfree(gp, (void*)gp->stack0, top);
stksize != FixedStack
のチェックがstksize != gp->stacksize
に変更され、Stktop->malloced
に基づいてstackfree
が呼び出されるようになりました。runtime·newstack
関数で、新しいスタックの割り当てと初期化ロジックが変更されました。- stk = runtime·stackalloc(FixedStack); + stk = runtime·stackalloc(gp, FixedStack); // ... - g->param = (void*)FixedStack; + gp->stacksize = FixedStack; + g->param = gp; // ... - gp->stacksize = FixedStack; // 削除 // ... - runtime·memclr((byte*)gp->stackbase, sizeof(Stktop)); // 削除
stackalloc
にgp
を渡し、gp->stacksize
の直接更新が削除されました。Stktop
のクリアもstackalloc
内で処理されるようになりました。
-
src/pkg/runtime/runtime.h
:Stktop
構造体にbool malloced;
フィールドが追加され、uintptr free;
フィールドが削除されました。struct Stktop { uint32 panicwrap; uint8* argp; // pointer to arguments in old frame - uintptr free; // if free>0, call stackfree using free as size bool panic; // is this frame the top of a panic? + bool malloced; };
runtime·stackalloc
とruntime·stackfree
関数のプロトタイプが変更されました。-void* runtime·stackalloc(uint32); -void runtime·stackfree(void*, uintptr); +void* runtime·stackalloc(G*, uint32); +void runtime·stackfree(G*, void*, Stktop*);
-
src/pkg/runtime/stack.c
:runtime·stackalloc
関数の実装が大幅に変更されました。void* -runtime·stackalloc(uint32 n) +runtime·stackalloc(G *gp, uint32 n) { uint32 pos; void *v; + bool malloced; + Stktop *top; // ... + gp->stacksize += n; // gp->stacksizeの更新 // ... + malloced = true; // デフォルトでmallocedをtrueに設定 if(n == FixedStack || m->mallocing) { // ... - return v; + malloced = false; // スタックキャッシュからの場合はfalse + } else + v = runtime·mallocgc(n, 0, FlagNoProfiling|FlagNoGC|FlagNoZero|FlagNoInvokeGC); + + top = (Stktop*)((byte*)v+n-sizeof(Stktop)); + runtime·memclr((byte*)top, sizeof(*top)); + top->malloced = malloced; // Stktopにmallocedフラグを設定 + return v; }
gp->stacksize
の更新、malloced
フラグの導入、Stktop
の初期化ロジックが追加されました。runtime·stackfree
関数の実装が大幅に変更されました。void -runtime·stackfree(void *v, uintptr n) +runtime·stackfree(G *gp, void *v, Stktop *top) { uint32 pos; + uintptr n; + n = (uintptr)(top+1) - (uintptr)v; // nをtopとvから計算 // ... + gp->stacksize -= n; // gp->stacksizeの更新 // ... - if(n == FixedStack || m->mallocing || m->gcing) { + if(top->malloced) { // mallocedフラグに基づいて解放パスを決定 + runtime·free(v); return; } - runtime·free(v); + if(n != FixedStack) // FixedStackでない場合はエラー + runtime·throw("stackfree: bad fixed size"); // ... スタックキャッシュに戻すロジック
n
の計算方法、gp->stacksize
の更新、top->malloced
に基づく解放パスの分岐が導入されました。runtime·oldstack
関数で、古いスタックの解放ロジックが変更されました。- if(top->free != 0) { - gp->stacksize -= top->free; - runtime·stackfree(old, top->free); - } + runtime·stackfree(gp, old, top);
runtime·unwindstack
と同様の変更です。copystack
関数で、新しいスタックの割り当てと古いスタックの解放ロジックが変更されました。- newstk = runtime·stackalloc(newsize); + newstk = runtime·stackalloc(gp, newsize); + newbase = newstk + newsize; + newtop = (Stktop*)(newbase - sizeof(Stktop)); + malloced = newtop->malloced; // 新しいStktopのmallocedフラグを保存 // ... + newtop->malloced = malloced; // コピー後にmallocedフラグを復元 // ... - gp->stackbase = (uintptr)newbase - sizeof(Stktop); + gp->stackbase = (uintptr)newtop; // gp->stackbaseの更新 // ... - runtime·stackfree(oldstk, oldsize); + runtime·stackfree(gp, oldstk, oldtop);
stackalloc
にgp
を渡し、Stktop
のmalloced
フラグを適切に処理するように変更されました。runtime·newstack
関数で、新しいスタックの割り当てロジックが変更されました。- gp->stacksize += framesize; // 削除 + stk = runtime·stackalloc(gp, framesize); // stackalloc内でgp->stacksizeを更新 // ... - stk = runtime·stackalloc(framesize); // 削除 // ... - free = framesize; // 削除 // ... - top->free = free; // 削除
gp->stacksize
の直接更新やtop->free
の設定が削除されました。runtime·shrinkstack
関数で、スタックの縮小後のgp->stacksize
の更新が追加されました。+ gp->stacksize -= oldsize - newsize;
-
src/pkg/runtime/stack.h
:StackTop
定数の値が変更されました。- StackTop = 96, + StackTop = 88,
これらの変更は、スタック管理の責任をstackalloc
とstackfree
に集約し、Stktop
構造体のmalloced
フラグを通じてスタックの起源を正確に追跡することで、以前のバグを修正し、コードの堅牢性を高めています。
コアとなるコードの解説
このコミットのコアとなる変更は、Goランタイムのスタック管理の根幹をなすstackalloc
とstackfree
関数の役割とインターフェースの変更、そしてStktop
構造体の定義変更にあります。
Stktop
構造体の変更
free
フィールドの削除: 以前のStktop
にはuintptr free;
というフィールドがあり、これはスタックの解放時に使用されるサイズを示していました。このフィールドは、スタックのコピーや縮小の際に正しく更新されないというバグの原因となっていました。このコミットでは、このフィールドが削除されます。これにより、スタックの解放サイズをStktop
に依存するのではなく、stackfree
関数がスタックのポインタとStktop
ポインタから動的に計算するようになります。malloced
フィールドの追加: 新たにbool malloced;
フィールドが追加されました。このフラグは、そのスタックがヒープからmallocgc
によって直接割り当てられたものか(true
)、それともスタックキャッシュから取得された固定サイズのスタック(false
)かを示します。この情報は、スタックを解放する際に、ヒープに返すか、スタックキャッシュに戻すかを正確に判断するために不可欠です。これにより、ヒープ割り当てスタックが誤ってスタックキャッシュに解放されるという以前のバグが修正されます。
runtime·stackalloc
関数の変更
- シグネチャの変更:
void* runtime·stackalloc(uint32 n)
からvoid* runtime·stackalloc(G *gp, uint32 n)
へと変更されました。- 新しい引数
G *gp
は、スタックを割り当てる対象のgoroutineのポインタです。これにより、stackalloc
関数内で直接gp->stacksize += n;
という形で、goroutineのスタックサイズを正確に更新できるようになりました。以前は、stackalloc
の呼び出し元がg->stacksize
を更新する責任を負っていましたが、この変更により、スタックサイズの会計処理がスタック割り当てのロジック内に集約され、一貫性が保たれます。
- 新しい引数
malloced
フラグの設定:stackalloc
は、割り当てられたスタックのStktop
にmalloced
フラグを適切に設定する責任を負うようになりました。- スタックキャッシュから固定サイズのスタックが取得された場合、
top->malloced
はfalse
に設定されます。 - ヒープからメモリが割り当てられた場合(
FixedStack
ではないサイズや、スタックキャッシュが利用できない場合)、top->malloced
はtrue
に設定されます。 - 割り当てられたスタックの
Stktop
領域は、runtime·memclr
によってゼロクリアされ、初期状態が保証されます。
- スタックキャッシュから固定サイズのスタックが取得された場合、
runtime·stackfree
関数の変更
- シグネチャの変更:
void runtime·stackfree(void *v, uintptr n)
からvoid runtime·stackfree(G *gp, void *v, Stktop *top)
へと変更されました。- 新しい引数
G *gp
は、解放するスタックを持つgoroutineのポインタです。これにより、stackfree
関数内でgp->stacksize -= n;
という形で、goroutineのスタックサイズを正確にデクリメントできるようになりました。 - 新しい引数
Stktop *top
は、解放するスタックのStktop
ポインタです。これにより、stackfree
はtop->malloced
フラグを直接参照して、スタックをヒープに解放すべきか(runtime·free(v)
)、それともスタックキャッシュに戻すべきか(m->stackcache
に格納)を正確に判断できるようになりました。
- 新しい引数
- 解放サイズの計算: 以前は引数として
n
(サイズ)を受け取っていましたが、新しいシグネチャではtop
とv
(スタックの開始ポインタ)からn = (uintptr)(top+1) - (uintptr)v;
として解放サイズを計算するようになりました。これにより、Stktop
のfree
フィールドが不要になり、その不整合の問題が解消されます。 FixedStack
の整合性チェック: スタックキャッシュに戻す場合、解放されるスタックのサイズがFixedStack
と一致するかどうかを厳密にチェックするようになりました。一致しない場合はruntime·throw("stackfree: bad fixed size");
でパニックを発生させ、ランタイムの整合性を保護します。
その他の関連関数の変更
runtime·unwindstack
、gfput
、runtime·oldstack
: これらの関数は、スタックを解放する際に、古いtop->free
フィールドに依存するロジックを削除し、新しいruntime·stackfree(gp, stk, top)
の呼び出しに置き換えられました。これにより、スタック解放のロジックが一元化され、Stktop
のmalloced
フラグに基づいて正しく処理されるようになります。copystack
: スタックをコピーする際、新しいスタックのStktop
にも古いスタックのmalloced
フラグが正しく引き継がれるように変更されました。また、古いスタックの解放も新しいruntime·stackfree
を使って行われます。gp->stackbase
の更新も、新しいStktop
のポインタを直接指すように変更されました。runtime·shrinkstack
: スタックを縮小した後、gp->stacksize -= oldsize - newsize;
という行が追加され、縮小されたサイズ分だけgp->stacksize
が正確にデクリメントされるようになりました。これにより、g->stacksize
の会計処理の正確性が向上します。
これらの変更により、Goランタイムのスタック管理はより堅牢で、バグが少なく、理解しやすいものになりました。スタックのサイズと起源に関する情報がstackalloc
とstackfree
に集約されたことで、ランタイムの内部状態の整合性が大幅に向上し、偽のレースコンディションの報告も減少することが期待されます。
関連リンク
- Go Issue #7490: runtime: stack accounting is broken https://github.com/golang/go/issues/7490
- Go Change-list 72440043: runtime: refactor and fix stack management code https://golang.org/cl/72440043
参考にした情報源リンク
- Goソースコード (上記コミットの変更点)
- Goのドキュメントおよびランタイムに関する一般的な知識
- Goのスタック管理に関する既存の解説記事 (一般的な概念理解のため)
- (例: Goのスケジューラ、goroutineスタックの仕組みなどに関する記事)
- 具体的なURLは、Web検索で得られた情報に基づいて適宜追加します。
- "Go runtime stack management"
- "Go goroutine stack growth"
- "Go runtime Stktop"
- "Go runtime stack cache"
- "Go runtime race detector spurious races"
(注: 上記の「参考にした情報源リンク」は、一般的な情報源の例であり、この解説生成のために実際にアクセスした特定のURLを指すものではありません。実際の生成プロセスでは、必要に応じてこれらのキーワードでWeb検索を行い、関連情報を参照します。)