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

[インデックス 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つのバグが特定されています。

  1. g->stacksize の計算が copystack/shrinkstack 時に壊れる: g構造体(goroutineを表す)内のstacksizeフィールドは、goroutineに割り当てられたスタックの総サイズを追跡します。しかし、スタックがコピー(copystack)されたり、縮小(shrinkstack)されたりする際に、このstacksizeの計算が正しく行われず、不正確な値になる問題がありました。これは、スタックの実際の使用状況とランタイムが認識しているサイズとの間に不整合を生じさせます。

  2. stktop->freecopystack/shrinkstack 時に適切に維持されない: Stktop構造体は、スタックのトップに位置するメタデータブロックであり、スタックに関する様々な情報(例えば、パニック情報や引数ポインタなど)を保持します。以前のバージョンでは、このStktop構造体内にfreeというフィールドがあり、これは解放すべきスタックのサイズを示していました。しかし、スタックのコピーや縮小が行われる際に、このstktop->freeフィールドが適切に更新または維持されず、結果としてスタックの不適切な解放やメモリリーク、あるいは二重解放につながる可能性がありました。

  3. stktop->free のロジックが壊れている: 特に問題とされたのは、stktop->freeFixedStack(固定サイズのスタック)と等しい場合、そのスタックがスタックキャッシュに解放されるべきだと判断されるにもかかわらず、実際には「非コピーセグメント縮小」の結果としてヒープから割り当てられたスタックである場合があるという点です。スタックキャッシュは固定サイズのスタックを再利用するためのものであり、ヒープから割り当てられた可変サイズのスタックを扱うべきではありません。この誤った判断により、スタックキャッシュの整合性が損なわれたり、ヒープメモリの不適切な管理が発生したりする可能性がありました。

これらのバグは、特にGoのレース検出ツール(race builders)上で「偽のレースコンディション」(spurious races)として現れていました。これは、実際のデータ競合がないにもかかわらず、ランタイムの内部状態の不整合によって競合が報告されることを意味します。このような偽陽性は、デバッグを困難にし、ランタイムの安定性に対する信頼を損ないます。

このリファクタリングの根本的なアイデアは、スタックサイズと、スタックがどこから割り当てられたか(ヒープかスタックキャッシュか)という情報を、stackalloc(スタック割り当て)とstackfree(スタック解放)という2つの中心的な関数に集約し、一元的に管理することです。これにより、スタック管理ロジックの複雑さを軽減し、上記のバグを根本的に解決することを目指しています。

このコミットは、Go issue #7490 を修正します。

変更の背景

Goランタイムは、goroutineごとに独立したスタックを持ち、必要に応じてそのスタックサイズを動的に伸縮させる機能(copystackshrinkstack)を備えています。これは、メモリ効率を高め、多数のgoroutineを効率的に実行するために不可欠な機能です。しかし、この動的なスタック管理は非常に複雑であり、メモリの割り当て、解放、ポインタの調整、そして各種メタデータの整合性維持が正確に行われる必要があります。

上記の「元コミット内容」で述べられているように、既存のスタック管理コードには複数のバグが存在していました。これらのバグは、特にスタックのコピーや縮小といった、スタックの物理的な位置やサイズが変更される操作において顕在化しました。

  • g->stacksize の不正確な追跡: ランタイムがgoroutineのスタックサイズを正確に把握できないと、メモリの過剰な割り当てや、逆にスタックオーバーフローの検出漏れにつながる可能性があります。
  • Stktop メタデータの不整合: Stktopはスタックの重要なメタデータを含むため、その情報が不整合を起こすと、パニック処理、スタックトレース、あるいはスタックの解放といったランタイムの基本的な機能に悪影響を及ぼします。特に、スタックがヒープから割り当てられたものか、スタックキャッシュから再利用されたものかという「セグメントの起源」に関する情報が不正確であると、誤った解放パスが選択され、メモリ破損やクラッシュにつながる恐れがありました。
  • 偽のレースコンディション: これらの内部的な不整合は、Goのレース検出器が誤ってデータ競合を報告する原因となっていました。これは、ランタイムの内部状態が期待通りに更新されないために、異なるgoroutineが同時にアクセスしているかのように見える状況が生じるためです。偽陽性の報告は、開発者が実際のバグとランタイムの内部的な問題とを区別することを困難にし、デバッグの効率を著しく低下させます。

これらの問題に対処するため、スタック管理ロジックをリファクタリングし、stackallocstackfreeにスタックのサイズと起源に関する責任を集約することで、コードの複雑性を減らし、堅牢性を向上させる必要がありました。これにより、スタック管理の信頼性を高め、ランタイム全体の安定性を確保することが、この変更の重要な背景となっています。

前提知識の解説

このコミットの変更内容を理解するためには、Goランタイムのスタック管理に関するいくつかの基本的な概念を理解しておく必要があります。

  1. Goroutine (G): Goにおける軽量スレッドの単位です。各goroutineは独自の実行スタックを持ちます。ランタイム内部では、G構造体で表現され、スタックの開始アドレス (stack0)、スタックのベースアドレス (stackbase)、スタックガードページのアドレス (stackguard, stackguard0)、そしてスタックの総サイズ (stacksize) などの情報が含まれます。

  2. M (Machine): OSのスレッドを表します。Goランタイムは、複数のMを管理し、それぞれがGoコードを実行します。Mは通常、OSのネイティブスレッドに1対1でマッピングされます。

  3. P (Processor): 論理プロセッサを表し、MとGの間の仲介役となります。Pは実行可能なgoroutineのキューを保持し、MがGを実行するためのコンテキストを提供します。

  4. スタックの動的伸縮 (Stack Growth/Shrink): Goのgoroutineスタックは、必要に応じて自動的にサイズが変更されます。

    • スタックの拡張 (Stack Growth): 関数呼び出しがスタックの現在の容量を超えそうになると、ランタイムはより大きな新しいスタックを割り当て、古いスタックの内容を新しいスタックにコピーします。このプロセスはcopystack関数によって行われます。
    • スタックの縮小 (Stack Shrink): goroutineが多くのスタックメモリを消費した後、その必要がなくなった場合(例えば、深い再帰呼び出しから戻った後など)、ランタイムはスタックをより小さなサイズに縮小し、余分なメモリを解放することがあります。これはshrinkstack関数によって行われます。
  5. スタックキャッシュ: Goランタイムは、頻繁に割り当て・解放される固定サイズのスタック(FixedStack)を効率的に再利用するために、スタックキャッシュを保持しています。これにより、OSへのシステムコールを減らし、メモリ割り当てのオーバーヘッドを削減します。

  6. Stktop 構造体: Goのスタックは下方向に成長します(アドレスが減少する方向)。Stktop構造体は、スタックの「トップ」(最も高いアドレス、つまりスタックの基底)に配置されるメタデータブロックです。この構造体には、パニック情報、引数ポインタ、スタックの起源に関する情報などが含まれていました。このコミット以前は、freeというフィールドがあり、スタック解放時に使用されるサイズを示していました。

  7. stackallocstackfree 関数:

    • runtime·stackalloc(size): 指定されたサイズのスタックメモリを割り当てます。これは、スタックキャッシュから取得するか、必要に応じてヒープから直接割り当てます。
    • runtime·stackfree(ptr, size): 指定されたポインタとサイズのスタックメモリを解放します。これは、スタックキャッシュに戻すか、ヒープに解放します。
  8. FixedStack とヒープからの割り当て: Goのgoroutineは、最初は小さな固定サイズのスタック(FixedStack)で開始します。このスタックは通常、スタックキャッシュから供給されます。しかし、FixedStackよりも大きなスタックが必要な場合や、スタックキャッシュが空の場合、ランタイムはヒープから直接メモリを割り当ててスタックとして使用します。この「起源」の情報は、スタックの解放時に重要になります。なぜなら、スタックキャッシュに戻すべきか、ヒープに解放すべきかを判断する必要があるからです。

  9. レースコンディション (Race Condition): 複数のgoroutineが共有リソース(メモリなど)に同時にアクセスし、少なくとも1つのアクセスが書き込みであり、かつアクセスが同期されていない場合に発生するバグです。結果として、実行のタイミングによってプログラムの挙動が非決定論的になります。Goのレース検出器は、このような問題を特定するのに役立ちますが、ランタイムの内部的な不整合が原因で「偽のレース」が報告されることもあります。

これらの概念を理解することで、このコミットがGoランタイムのスタック管理のどの部分をどのように改善しようとしているのかが明確になります。

技術的詳細

このコミットの技術的詳細は、主にGoランタイムのスタック管理におけるデータ構造と関数のインターフェース、そしてその実装ロジックの変更に集約されます。

  1. Stktop 構造体の変更:

    • free フィールドの削除: 以前のStktop構造体にはuintptr free;フィールドが存在し、これはスタック解放時に使用されるサイズを示していました。このコミットでは、このfreeフィールドが削除されます。これは、スタックの解放サイズをStktopに持たせるのではなく、stackfree関数がスタックのベースアドレスとStktopポインタから計算するように変更されたためです。これにより、copystack/shrinkstack時のfreeフィールドの不適切な維持というバグが根本的に解消されます。
    • malloced フィールドの追加: 新たにbool malloced;フィールドがStktopに追加されます。このフィールドは、そのスタックがヒープから直接割り当てられたもの(true)か、それともスタックキャッシュから取得された固定サイズのスタック(false)かを示すフラグです。この情報は、スタックの解放時に、スタックをヒープに返すか、スタックキャッシュに戻すかを正確に判断するために使用されます。これにより、stktop->freeロジックのバグ(ヒープ割り当てスタックがスタックキャッシュに解放される問題)が修正されます。
  2. 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時に壊れるというバグに対処します。また、割り当てられたスタックのStktopmallocedフラグを設定する責任も持つようになりました。
    • 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は、topvから計算されるようになりました。
  3. g->stacksize の管理の集約: 以前は、g->stacksizeの更新がcopystackshrinkstackなど、複数の場所で散発的に行われていました。この変更により、stackallocがスタックを割り当てる際にgp->stacksizeをインクリメントし、stackfreeがスタックを解放する際にgp->stacksizeをデクリメントするように、その管理責任がstackalloc/stackfreeに集約されました。これにより、stacksizeの計算の正確性が向上し、不整合が減少します。

  4. スタック操作関数の修正: runtime·unwindstackgfputruntime·malgruntime·newstackcopystackruntime·shrinkstackといったスタックを操作する主要な関数は、新しいstackallocstackfreeのシグネチャとロジックに合わせて修正されました。

    • 特にcopystackでは、新しいスタックのStktopにもmallocedフラグが正しくコピーされるように変更されています。
    • runtime·shrinkstackでは、縮小されたスタックのサイズ分だけgp->stacksizeがデクリメントされるようになりました。
  5. StackTop 定数の変更: src/pkg/runtime/stack.hで定義されているStackTop定数が96から88に変更されました。この定数は、スタックのトップに位置するデータブロックの想定サイズを示しています。この変更は、Stktop構造体からfreeフィールドが削除され、mallocedフィールドが追加されたことによるStktop構造体自体のサイズ変更を反映したものです。これにより、ランタイムがスタックトップのメタデータブロックのサイズを正しく認識し、スタックの割り当てやポインタ計算が正確に行われるようになります。

これらの変更は、Goランタイムのスタック管理の堅牢性と正確性を大幅に向上させ、以前のバグによって引き起こされていた偽のレースコンディションやその他の不安定な挙動を解消することを目的としています。

コアとなるコードの変更箇所

このコミットでは、Goランタイムのスタック管理に関連する複数のファイルが変更されています。主要な変更箇所は以下の通りです。

  1. 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のシグネチャに合わせてgptopを渡すようになりました。
  2. 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を介してnewgstacksizeを渡すロジックが導入され、stackallocnewgstacksizeを直接更新するように変更されました。
    • 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)); // 削除
      
      stackallocgpを渡し、gp->stacksizeの直接更新が削除されました。Stktopのクリアもstackalloc内で処理されるようになりました。
  3. 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·stackallocruntime·stackfree関数のプロトタイプが変更されました。
      -void*	runtime·stackalloc(uint32);
      -void	runtime·stackfree(void*, uintptr);
      +void*	runtime·stackalloc(G*, uint32);
      +void	runtime·stackfree(G*, void*, Stktop*);
      
  4. 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);
      
      stackallocgpを渡し、Stktopmallocedフラグを適切に処理するように変更されました。
    • 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;
      
  5. src/pkg/runtime/stack.h:

    • StackTop定数の値が変更されました。
      -	StackTop = 96,
      +	StackTop = 88,
      

これらの変更は、スタック管理の責任をstackallocstackfreeに集約し、Stktop構造体のmallocedフラグを通じてスタックの起源を正確に追跡することで、以前のバグを修正し、コードの堅牢性を高めています。

コアとなるコードの解説

このコミットのコアとなる変更は、Goランタイムのスタック管理の根幹をなすstackallocstackfree関数の役割とインターフェースの変更、そして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は、割り当てられたスタックのStktopmallocedフラグを適切に設定する責任を負うようになりました。
    • スタックキャッシュから固定サイズのスタックが取得された場合、top->mallocedfalseに設定されます。
    • ヒープからメモリが割り当てられた場合(FixedStackではないサイズや、スタックキャッシュが利用できない場合)、top->mallocedtrueに設定されます。
    • 割り当てられたスタックの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ポインタです。これにより、stackfreetop->mallocedフラグを直接参照して、スタックをヒープに解放すべきか(runtime·free(v))、それともスタックキャッシュに戻すべきか(m->stackcacheに格納)を正確に判断できるようになりました。
  • 解放サイズの計算: 以前は引数としてn(サイズ)を受け取っていましたが、新しいシグネチャではtopv(スタックの開始ポインタ)からn = (uintptr)(top+1) - (uintptr)v;として解放サイズを計算するようになりました。これにより、Stktopfreeフィールドが不要になり、その不整合の問題が解消されます。
  • FixedStack の整合性チェック: スタックキャッシュに戻す場合、解放されるスタックのサイズがFixedStackと一致するかどうかを厳密にチェックするようになりました。一致しない場合はruntime·throw("stackfree: bad fixed size");でパニックを発生させ、ランタイムの整合性を保護します。

その他の関連関数の変更

  • runtime·unwindstackgfputruntime·oldstack: これらの関数は、スタックを解放する際に、古いtop->freeフィールドに依存するロジックを削除し、新しいruntime·stackfree(gp, stk, top)の呼び出しに置き換えられました。これにより、スタック解放のロジックが一元化され、Stktopmallocedフラグに基づいて正しく処理されるようになります。
  • copystack: スタックをコピーする際、新しいスタックのStktopにも古いスタックのmallocedフラグが正しく引き継がれるように変更されました。また、古いスタックの解放も新しいruntime·stackfreeを使って行われます。gp->stackbaseの更新も、新しいStktopのポインタを直接指すように変更されました。
  • runtime·shrinkstack: スタックを縮小した後、gp->stacksize -= oldsize - newsize;という行が追加され、縮小されたサイズ分だけgp->stacksizeが正確にデクリメントされるようになりました。これにより、g->stacksizeの会計処理の正確性が向上します。

これらの変更により、Goランタイムのスタック管理はより堅牢で、バグが少なく、理解しやすいものになりました。スタックのサイズと起源に関する情報がstackallocstackfreeに集約されたことで、ランタイムの内部状態の整合性が大幅に向上し、偽のレースコンディションの報告も減少することが期待されます。

関連リンク

参考にした情報源リンク

  • 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検索を行い、関連情報を参照します。)