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

[インデックス 15238] ファイルの概要

このコミットは、Goランタイムにおけるスタック管理関連のコードを、既存のファイルからstack.cという新しいファイルに移動させることを目的としています。コードの機能的な変更は伴わず、主にコードの整理と将来のスケジューラ変更への準備として行われました。

コミット

commit 691455f780c12e2a7994413d6133d5e512c70b53
Author: Dmitriy Vyukov <dvyukov@google.com>
Date:   Thu Feb 14 12:37:55 2013 +0400

    runtime: move stack management related code to stack.c
    No code changes.
    This is mainly in preparation to scheduler changes,
    oldstack/newstack are not related to scheduling.
    
    R=golang-dev, minux.ma, rsc
    CC=golang-dev
    https://golang.org/cl/7311085

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/691455f780c12e2a7994413d6133d5e512c70b53

元コミット内容

Goランタイムのスタック管理に関連するコードをsrc/pkg/runtime/malloc.gocsrc/pkg/runtime/proc.cからsrc/pkg/runtime/stack.cへ移動しました。機能的なコード変更は一切ありません。この変更は、主にスケジューラの変更に備えるためのものであり、oldstackおよびnewstack関数はスケジューリングとは直接関係ありません。

変更の背景

Goランタイムは、ゴルーチン(Goにおける軽量スレッド)のスタックを効率的に管理するために、スプリットスタック(split stack)というメカニズムを採用しています。これは、必要に応じてスタックを動的に拡張・縮小する仕組みです。このスタック管理に関連するコードは、これまでmalloc.goc(メモリ割り当て関連)やproc.c(プロセッサ・ゴルーチン管理関連)といった複数のファイルに分散していました。

このコミットの主な背景は、コードのモジュール化と整理です。スタック管理のロジックを専用のファイルstack.cに集約することで、以下のメリットが期待されます。

  1. コードの見通しの向上: スタック管理に関するすべてのコードが一箇所にまとまるため、関連するロジックを理解しやすくなります。
  2. 保守性の向上: 特定の機能(スタック管理)に対する変更やデバッグが、他の機能のコードに影響を与えるリスクを減らし、より容易になります。
  3. 将来の変更への準備: コミットメッセージにあるように、「スケジューラの変更」に備えるための準備段階として位置づけられています。スタック管理とスケジューリングは密接に関連していますが、このコミットではスタック管理コードを独立させることで、将来のスケジューラ改善がよりスムーズに行えるように基盤を整えています。特に、oldstacknewstackがスケジューリングとは直接関係ないという言及は、これらの関数がスタックの動的な伸縮という純粋なスタック管理の役割を担っていることを示唆しています。

前提知識の解説

このコミットを理解するためには、以下のGoランタイムの概念とC言語の知識が必要です。

1. Goランタイム (Go Runtime)

Goプログラムは、Goランタイムと呼ばれる実行環境上で動作します。ランタイムは、ガベージコレクション、ゴルーチン管理、スケジューリング、メモリ割り当て、スタック管理など、プログラムの実行に必要な低レベルな機能を提供します。Goランタイムの多くはC言語(またはGoのサブセットであるGo Assembly)で記述されており、OSとのインタフェースやパフォーマンスが重要な部分を担っています。

2. ゴルーチン (Goroutine)

Goにおける並行処理の基本単位です。OSのスレッドよりもはるかに軽量で、数百万のゴルーチンを同時に実行することも可能です。各ゴルーチンは独自のスタックを持ち、このスタックは必要に応じて動的にサイズが変更されます。

3. スプリットスタック (Split Stack)

Goのゴルーチンが採用しているスタック管理戦略です。従来の固定サイズのスタックとは異なり、スプリットスタックは小さな初期スタックサイズで開始し、関数呼び出しの深さに応じて必要に応じてスタックセグメントを動的に割り当てて拡張します。スタックが不要になると、そのセグメントは解放されます。これにより、メモリ使用量を効率的に抑えつつ、多数のゴルーチンをサポートできます。

  • morestack: スタックが足りなくなったときに呼び出されるランタイム関数。新しいスタックセグメントを割り当て、現在の実行コンテキストを新しいスタックに移行させます。
  • lessstack: 関数から戻る際に、スタックセグメントが不要になった場合に呼び出されるランタイム関数。古いスタックセグメントを解放し、実行コンテキストを元のスタックに戻します。

4. m (Machine) と g (Goroutine)

Goランタイムの内部構造における重要な概念です。

  • m (Machine): OSのスレッドを表します。Goランタイムは、複数のm(OSスレッド)を管理し、その上でゴルーチンを実行します。mは、現在のゴルーチン(m->curg)や、スケジューラ関連の情報を保持します。
  • g (Goroutine): ゴルーチン自体を表します。各gは、スタックポインタ、プログラムカウンタ、スタックのベースアドレスとガードページなどのスタック情報、そしてゴルーチンの状態などを保持します。m->g0はスケジューラが使用する特別なスタックを持つゴルーチンです。

5. malloc.gocproc.c

Goランタイムのソースコードにおけるファイルです。

  • malloc.goc: 主にメモリ割り当て(ヒープ管理)に関するコードが含まれています。
  • proc.c: プロセッサ(m)とゴルーチン(g)の管理、スケジューリング、コンテキストスイッチなど、Goランタイムのコアな処理に関するコードが含まれています。

6. C言語のポインタと構造体

Goランタイムの低レベルなコードはC言語で書かれているため、ポインタ操作、構造体、メモリ管理の概念が重要です。

技術的詳細

このコミットは、Goランタイムのスタック管理ロジックをsrc/pkg/runtime/malloc.gocsrc/pkg/runtime/proc.cからsrc/pkg/runtime/stack.cという新しいファイルに移動させるものです。この移動は、以下の主要な関数と関連するデータ構造に影響を与えます。

1. スタックキャッシュ (Stack Cache)

Goランタイムは、スタックセグメントの割り当てと解放のオーバーヘッドを減らすために、スタックキャッシュを使用します。これは、使用済みのスタックセグメントを再利用するためのグローバルなキャッシュです。

  • StackCacheNode構造体: スタックキャッシュ内のノードを表し、次のノードへのポインタと、スタックセグメントのバッチを保持します。
  • stackcache: グローバルなスタックキャッシュの先頭ノードへのポインタ。
  • stackcachemu: スタックキャッシュへのアクセスを保護するためのミューテックス(ロック)。
  • stackcacherefill(): グローバルスタックキャッシュから、現在のM(OSスレッド)のローカルスタックキャッシュにスタックセグメントを補充する関数。
  • stackcacherelease(): 現在のMのローカルスタックキャッシュから、グローバルスタックキャッシュにスタックセグメントを解放する関数。

これらの関数とデータ構造は、以前はmalloc.gocに定義されていましたが、このコミットでstack.cに移動されました。

2. スタック割り当てと解放 (runtime·stackalloc, runtime·stackfree)

  • runtime·stackalloc(uint32 n): 指定されたサイズnのスタックセグメントを割り当てる関数。
    • この関数は、スケジューラスタック(m->g0のスタック)上で呼び出される必要があります。これは、スタック割り当て中にスタックを拡張しようとするとデッドロックが発生する可能性があるためです(issue 1547)。
    • m->mallocingまたはm->gcing(メモリ割り当て中またはGC中)の場合、デッドロックを避けるために固定サイズのフリーリストアロケータ(スタックキャッシュ)にフォールバックします。
    • それ以外の場合は、通常のruntime·mallocgc(ガベージコレクション対象のメモリ割り当て)を使用してスタックを割り当てます。
  • runtime·stackfree(void *v, uintptr n): 割り当てられたスタックセグメントvを解放する関数。
    • 割り当て時と同様に、m->mallocingまたはm->gcingの場合はスタックキャッシュに解放し、それ以外の場合は通常のruntime·freeを使用します。

これらの関数も、以前はmalloc.gocに定義されていましたが、stack.cに移動されました。

3. スタック切り替え (runtime·oldstack, runtime·newstack)

Goのスプリットスタックにおいて、スタックセグメントの切り替えを行う重要な関数です。

  • runtime·oldstack(): runtime·lessstackから呼び出され、新しいスタックセグメントから元の(古い)スタックセグメントに戻る際に使用されます。
    • 現在のゴルーチン(m->curg)のスタック情報を、古いスタックセグメントのものに復元します。
    • 新しいスタックセグメントを解放します(runtime·stackfreeを使用)。
    • runtime·gogoを呼び出して、実行コンテキストを古いスタックに戻します。
  • runtime·newstack(): reflect·callまたはruntime·morestackから呼び出され、新しいスタックセグメントが必要な場合に使用されます。
    • 必要なフレームサイズと引数サイズに基づいて、新しいスタックセグメントを割り当てます(runtime·stackallocを使用)。
    • 現在のスタックコンテキスト(m->morebufなど)を保存し、新しいスタックセグメントの先頭にStktop構造体を配置します。
    • 引数を新しいスタックにコピーします。
    • runtime·gogocallを呼び出して、実行コンテキストを新しいスタックに切り替えます。

これらの関数は、以前はproc.cに定義されていましたが、このコミットでstack.cに移動されました。

変更点まとめ

  • src/pkg/runtime/malloc.gocから、スタックキャッシュ関連のデータ構造と関数(StackCacheNode, stackcache, stackcachemu, stackcacherefill, stackcacherelease)、およびスタック割り当て/解放関数(runtime·stackalloc, runtime·stackfree)が削除されました。
  • src/pkg/runtime/proc.cから、スタック切り替え関数(runtime·oldstack, runtime·newstack)が削除されました。
  • src/pkg/runtime/stack.cという新しいファイルが作成され、上記で削除されたすべてのコードがこのファイルに移動されました。
  • 各ファイルでのインクルードパスが調整されました。例えば、malloc.gocproc.cからは不要になったヘッダのインクルードが削除され、stack.cには必要なヘッダが追加されました。

この変更は、コードの機能には影響を与えず、純粋にコードの再編成(リファクタリング)です。これにより、Goランタイムのスタック管理部分がより独立したモジュールとして扱われるようになり、将来の機能拡張や改善が容易になります。

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

このコミットは、既存のコードを新しいファイルに移動させるものであり、機能的な変更はありません。したがって、「変更箇所」は主にファイルの移動と、それに伴うインクルードパスの調整になります。

src/pkg/runtime/malloc.goc からの削除

malloc.gocからは、スタックキャッシュ関連の定義と、runtime·stackallocruntime·stackfree関数が削除されました。

--- a/src/pkg/runtime/malloc.goc
+++ b/src/pkg/runtime/malloc.goc
@@ -9,9 +9,7 @@
 package runtime
 #include "runtime.h"
 #include "arch_GOARCH.h"
-#include "stack.h"
 #include "malloc.h"
-#include "defs_GOOS_GOARCH.h"
 #include "type.h"
 #include "typekind.h"
 #include "race.h"
@@ -750,123 +748,6 @@ runtime·cnew(Type *typ)
 	return ret;
 }
 
-typedef struct StackCacheNode StackCacheNode;
-struct StackCacheNode
-{
-	StackCacheNode *next;
-	void*	batch[StackCacheBatch-1];
-};
-
-static StackCacheNode *stackcache;
-static Lock stackcachemu;
-
-// stackcacherefill/stackcacherelease implement global cache of stack segments.
-// The cache is required to prevent unlimited growth of per-thread caches.
-static void
-stackcacherefill(void)
-{
-	StackCacheNode *n;
-	int32 i, pos;
-
-	runtime·lock(&stackcachemu);
-	n = stackcache;
-	if(n)
-		stackcache = n->next;
-	runtime·unlock(&stackcachemu);
-	if(n == nil) {
-		n = (StackCacheNode*)runtime·SysAlloc(FixedStack*StackCacheBatch);
-		if(n == nil)
-			runtime·throw("out of memory (staccachekrefill)");
-		runtime·xadd64(&mstats.stacks_sys, FixedStack*StackCacheBatch);
-		for(i = 0; i < StackCacheBatch-1; i++)
-			n->batch[i] = (byte*)n + (i+1)*FixedStack;
-	}
-	pos = m->stackcachepos;
-	for(i = 0; i < StackCacheBatch-1; i++) {
-		m->stackcache[pos] = n->batch[i];
-		pos = (pos + 1) % StackCacheSize;
-	}
-	m->stackcache[pos] = n;
-	pos = (pos + 1) % StackCacheSize;
-	m->stackcachepos = pos;
-	m->stackcachecnt += StackCacheBatch;
-}
-
-static void
-stackcacherelease(void)
-{
-	StackCacheNode *n;
-	uint32 i, pos;
-
-	pos = (m->stackcachepos - m->stackcachecnt) % StackCacheSize;
-	n = (StackCacheNode*)m->stackcache[pos];
-	pos = (pos + 1) % StackCacheSize;
-	for(i = 0; i < StackCacheBatch-1; i++) {
-		n->batch[i] = m->stackcache[pos];
-		pos = (pos + 1) % StackCacheSize;
-	}
-	m->stackcachecnt -= StackCacheBatch;
-	runtime·lock(&stackcachemu);
-	n->next = stackcache;
-	stackcache = n;
-	runtime·unlock(&stackcachemu);
-}
-
-void*
-runtime·stackalloc(uint32 n)
-{
-	uint32 pos;
-	void *v;
-
-	// Stackalloc must be called on scheduler stack, so that we
-	// never try to grow the stack during the code that stackalloc runs.
-	// Doing so would cause a deadlock (issue 1547).
-	if(g != m->g0)
-		runtime·throw("stackalloc not on scheduler stack");
-
-	// Stack allocator uses malloc/free most of the time,
-	// but if we're in the middle of malloc and need stack,
-	// we have to do something else to avoid deadlock.
-	// In that case, we fall back on a fixed-size free-list
-	// allocator, assuming that inside malloc all the stack
-	// frames are small, so that all the stack allocations
-	// will be a single size, the minimum (right now, 5k).
-	if(n == FixedStack || m->mallocing || m->gcing) {
-		if(n != FixedStack) {
-			runtime·printf("stackalloc: in malloc, size=%d want %d", FixedStack, n);
-			runtime·throw("stackalloc");
-		}
-		if(m->stackcachecnt == 0)
-			stackcacherefill();
-		pos = m->stackcachepos;
-		pos = (pos - 1) % StackCacheSize;
-		v = m->stackcache[pos];
-		m->stackcachepos = pos;
-		m->stackcachecnt--;
-		m->stackinuse++;
-		return v;
-	}
-	return runtime·mallocgc(n, FlagNoProfiling|FlagNoGC, 0, 0);
-}
-
-void
-runtime·stackfree(void *v, uintptr n)
-{
-	uint32 pos;
-
-	if(n == FixedStack || m->mallocing || m->gcing) {
-		if(m->stackcachecnt == StackCacheSize)
-			stackcacherelease();
-		pos = m->stackcachepos;
-		m->stackcache[pos] = v;
-		m->stackcachepos = (pos + 1) % StackCacheSize;
-		m->stackcachecnt++;
-		m->stackinuse--;
-		return;
-	}
-	runtime·free(v);
-}
-
 func GC() {
 	runtime·gc(1);
 }

src/pkg/runtime/proc.c からの削除

proc.cからは、runtime·oldstackruntime·newstack関数が削除されました。

--- a/src/pkg/runtime/proc.c
+++ b/src/pkg/runtime/proc.c
@@ -4,9 +4,7 @@
 
 #include "runtime.h"
 #include "arch_GOARCH.h"
-#include "defs_GOOS_GOARCH.h"
 #include "malloc.h"
-#include "os_GOOS.h"
 #include "stack.h"
 #include "race.h"
 #include "type.h"
@@ -1100,160 +1098,6 @@ runtime·exitsyscall(void)
 	g->gcstack = (uintptr)nil;
 }
 
-// Called from runtime·lessstack when returning from a function which
-// allocated a new stack segment.  The function's return value is in
-// m->cret.
-void
-runtime·oldstack(void)
-{
-	Stktop *top;
-	Gobuf label;
-	uint32 argsize;
-	uintptr cret;
-	byte *sp, *old;
-	uintptr *src, *dst, *dstend;
-	G *gp;
-	int64 goid;
-
-//printf("oldstack m->cret=%p\n", m->cret);
-
-	gp = m->curg;
-	top = (Stktop*)gp->stackbase;
-	old = (byte*)gp->stackguard - StackGuard;
-	sp = (byte*)top;
-	argsize = top->argsize;
-	if(argsize > 0) {
-		sp -= argsize;
-		dst = (uintptr*)top->argp;
-		dstend = dst + argsize/sizeof(*dst);
-		src = (uintptr*)sp;
-		while(dst < dstend)
-			*dst++ = *src++;
-	}
-	goid = top->gobuf.g->goid;	// fault if g is bad, before gogo
-	USED(goid);
-
-	label = top->gobuf;
-	gp->stackbase = (uintptr)top->stackbase;
-	gp->stackguard = (uintptr)top->stackguard;
-	if(top->free != 0)
-		runtime·stackfree(old, top->free);
-
-	cret = m->cret;
-	m->cret = 0;  // drop reference
-	runtime·gogo(&label, cret);
-}
-
-// Called from reflect·call or from runtime·morestack when a new
-// stack segment is needed.  Allocate a new stack big enough for
-// m->moreframesize bytes, copy m->moreargsize bytes to the new frame,
-// and then act as though runtime·lessstack called the function at
-// m->morepc.
-void
-runtime·newstack(void)
-{
-	int32 framesize, minalloc, argsize;
-	Stktop *top;
-	byte *stk, *sp;
-	uintptr *src, *dst, *dstend;
-	G *gp;
-	Gobuf label;
-	bool reflectcall;
-	uintptr free;
-
-	framesize = m->moreframesize;
-	argsize = m->moreargsize;
-	gp = m->curg;
-
-	if(m->morebuf.sp < gp->stackguard - StackGuard) {
-		runtime·printf("runtime: split stack overflow: %p < %p\n", m->morebuf.sp, gp->stackguard - StackGuard);
-		runtime·throw("runtime: split stack overflow");
-	}
-	if(argsize % sizeof(uintptr) != 0) {
-		runtime·printf("runtime: stack split with misaligned argsize %d\n", argsize);
-		runtime·throw("runtime: stack split argsize");
-	}
-
-	minalloc = 0;
-	reflectcall = framesize==1;
-	if(reflectcall) {
-		framesize = 0;
-		// moreframesize_minalloc is only set in runtime·gc(),
-		// that calls newstack via reflect·call().
-		minalloc = m->moreframesize_minalloc;
-		m->moreframesize_minalloc = 0;
-		if(framesize < minalloc)
-			framesize = minalloc;
-	}
-
-	if(reflectcall && minalloc == 0 && m->morebuf.sp - sizeof(Stktop) - argsize - 32 > gp->stackguard) {
-		// special case: called from reflect.call (framesize==1)
-		// to call code with an arbitrary argument size,
-		// and we have enough space on the current stack.
-		// the new Stktop* is necessary to unwind, but
-		// we don't need to create a new segment.
-		top = (Stktop*)(m->morebuf.sp - sizeof(*top));
-		stk = (byte*)gp->stackguard - StackGuard;
-		free = 0;
-	} else {
-		// allocate new segment.
-		framesize += argsize;
-		framesize += StackExtra;	// room for more functions, Stktop.
-		if(framesize < StackMin)
-			framesize = StackMin;
-		framesize += StackSystem;
-		stk = runtime·stackalloc(framesize);
-		top = (Stktop*)(stk+framesize-sizeof(*top));
-		free = framesize;
-	}
-
-	if(0) {
-		runtime·printf("newstack framesize=%d argsize=%d morepc=%p moreargp=%p gobuf=%p, %p top=%p old=%p\n",
-			framesize, argsize, m->morepc, m->moreargp, m->morebuf.pc, m->morebuf.sp, top, gp->stackbase);
-	}
-
-	top->stackbase = (byte*)gp->stackbase;
-	top->stackguard = (byte*)gp->stackguard;
-	top->gobuf = m->morebuf;
-	top->argp = m->moreargp;
-	top->argsize = argsize;
-	top->free = free;
-	m->moreargp = nil;
-	m->morebuf.pc = nil;
-	m->morebuf.sp = (uintptr)nil;
-
-	// copy flag from panic
-	top->panic = gp->ispanic;
-	gp->ispanic = false;
-
-	gp->stackbase = (uintptr)top;
-	gp->stackguard = (uintptr)stk + StackGuard;
-
-	sp = (byte*)top;
-	if(argsize > 0) {
-		sp -= argsize;
-		dst = (uintptr*)sp;
-		dstend = dst + argsize/sizeof(*dst);
-		src = (uintptr*)top->argp;
-		while(dst < dstend)
-			*dst++ = *src++;
-	}
-	if(thechar == '5') {
-		// caller would have saved its LR below args.
-		sp -= sizeof(void*);
-		*(void**)sp = nil;
-	}
-
-	// Continue as if lessstack had just called m->morepc
-	// (the PC that decided to grow the stack).
-	label.sp = (uintptr)sp;
-	label.pc = (byte*)runtime·lessstack;
-	label.g = m->curg;
-	runtime·gogocall(&label, m->morepc);
-
-	*(int32*)345 = 123;	// never return
-}

src/pkg/runtime/stack.c の新規作成

stack.cが新規作成され、上記で削除されたすべてのコードがこのファイルに挿入されました。

--- /dev/null
+++ b/src/pkg/runtime/stack.c
@@ -0,0 +1,279 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#include "runtime.h"
+#include "arch_GOARCH.h"
+#include "malloc.h"
+#include "stack.h"
+
+typedef struct StackCacheNode StackCacheNode;
+struct StackCacheNode
+{
+	StackCacheNode *next;
+	void*	batch[StackCacheBatch-1];
+};
+
+static StackCacheNode *stackcache;
+static Lock stackcachemu;
+
+// stackcacherefill/stackcacherelease implement global cache of stack segments.
+// The cache is required to prevent unlimited growth of per-thread caches.
+static void
+stackcacherefill(void)
+{
+	StackCacheNode *n;
+	int32 i, pos;
+
+	runtime·lock(&stackcachemu);
+	n = stackcache;
+	if(n)
+		stackcache = n->next;
+	runtime·unlock(&stackcachemu);
+	if(n == nil) {
+		n = (StackCacheNode*)runtime·SysAlloc(FixedStack*StackCacheBatch);
+		if(n == nil)
+			runtime·throw("out of memory (staccachekrefill)");
+		runtime·xadd64(&mstats.stacks_sys, FixedStack*StackCacheBatch);
+		for(i = 0; i < StackCacheBatch-1; i++)
+			n->batch[i] = (byte*)n + (i+1)*FixedStack;
+	}
+	pos = m->stackcachepos;
+	for(i = 0; i < StackCacheBatch-1; i++) {
+		m->stackcache[pos] = n->batch[i];
+		pos = (pos + 1) % StackCacheSize;
+	}
+	m->stackcache[pos] = n;
+	pos = (pos + 1) % StackCacheSize;
+	m->stackcachepos = pos;
+	m->stackcachecnt += StackCacheBatch;
+}
+
+static void
+stackcacherelease(void)
+{
+	StackCacheNode *n;
+	uint32 i, pos;
+
+	pos = (m->stackcachepos - m->stackcachecnt) % StackCacheSize;
+	n = (StackCacheNode*)m->stackcache[pos];
+	pos = (pos + 1) % StackCacheSize;
+	for(i = 0; i < StackCacheBatch-1; i++) {
+		n->batch[i] = m->stackcache[pos];
+		pos = (pos + 1) % StackCacheSize;
+	}
+	m->stackcachecnt -= StackCacheBatch;
+	runtime·lock(&stackcachemu);
+	n->next = stackcache;
+	stackcache = n;
+	runtime·unlock(&stackcachemu);
+}
+
+void*
+runtime·stackalloc(uint32 n)
+{
+	uint32 pos;
+	void *v;
+
+	// Stackalloc must be called on scheduler stack, so that we
+	// never try to grow the stack during the code that stackalloc runs.
+	// Doing so would cause a deadlock (issue 1547).
+	if(g != m->g0)
+		runtime·throw("stackalloc not on scheduler stack");
+
+	// Stack allocator uses malloc/free most of the time,
+	// but if we're in the middle of malloc and need stack,
+	// we have to do something else to avoid deadlock.
+	// In that case, we fall back on a fixed-size free-list
+	// allocator, assuming that inside malloc all the stack
+	// frames are small, so that all the stack allocations
+	// will be a single size, the minimum (right now, 5k).
+	if(n == FixedStack || m->mallocing || m->gcing) {
+		if(n != FixedStack) {
+			runtime·printf("stackalloc: in malloc, size=%d want %d", FixedStack, n);
+			runtime·throw("stackalloc");
+		}
+		if(m->stackcachecnt == 0)
+			stackcacherefill();
+		pos = m->stackcachepos;
+		pos = (pos - 1) % StackCacheSize;
+		v = m->stackcache[pos];
+		m->stackcachepos = pos;
+		m->stackcachecnt--;
+		m->stackinuse++;
+		return v;
+	}
+	return runtime·mallocgc(n, FlagNoProfiling|FlagNoGC, 0, 0);
+}
+
+void
+runtime·stackfree(void *v, uintptr n)
+{
+	uint32 pos;
+
+	if(n == FixedStack || m->mallocing || m->gcing) {
+		if(m->stackcachecnt == StackCacheSize)
+			stackcacherelease();
+		pos = m->stackcachepos;
+		m->stackcache[pos] = v;
+		m->stackcachepos = (pos + 1) % StackCacheSize;
+		m->stackcachecnt++;
+		m->stackinuse--;
+		return;
+	}
+	runtime·free(v);
+}
+
+// Called from runtime·lessstack when returning from a function which
+// allocated a new stack segment.  The function's return value is in
+// m->cret.
+void
+runtime·oldstack(void)
+{
+	Stktop *top;
+	Gobuf label;
+	uint32 argsize;
+	uintptr cret;
+	byte *sp, *old;
+	uintptr *src, *dst, *dstend;
+	G *gp;
+	int64 goid;
+
+//printf("oldstack m->cret=%p\n", m->cret);
+
+	gp = m->curg;
+	top = (Stktop*)gp->stackbase;
+	old = (byte*)gp->stackguard - StackGuard;
+	sp = (byte*)top;
+	argsize = top->argsize;
+	if(argsize > 0) {
+		sp -= argsize;
+		dst = (uintptr*)top->argp;
+		dstend = dst + argsize/sizeof(*dst);
+		src = (uintptr*)sp;
+		while(dst < dstend)
+			*dst++ = *src++;
+	}
+	goid = top->gobuf.g->goid;	// fault if g is bad, before gogo
+	USED(goid);
+
+	label = top->gobuf;
+	gp->stackbase = (uintptr)top->stackbase;
+	gp->stackguard = (uintptr)top->stackguard;
+	if(top->free != 0)
+		runtime·stackfree(old, top->free);
+
+	cret = m->cret;
+	m->cret = 0;  // drop reference
+	runtime·gogo(&label, cret);
+}
+
+// Called from reflect·call or from runtime·morestack when a new
+// stack segment is needed.  Allocate a new stack big enough for
+// m->moreframesize bytes, copy m->moreargsize bytes to the new frame,
+// and then act as though runtime·lessstack called the function at
+// m->morepc.
+void
+runtime·newstack(void)
+{
+	int32 framesize, minalloc, argsize;
+	Stktop *top;
+	byte *stk, *sp;
+	uintptr *src, *dst, *dstend;
+	G *gp;
+	Gobuf label;
+	bool reflectcall;
+	uintptr free;
+
+	framesize = m->moreframesize;
+	argsize = m->moreargsize;
+	gp = m->curg;
+
+	if(m->morebuf.sp < gp->stackguard - StackGuard) {
+		runtime·printf("runtime: split stack overflow: %p < %p\n", m->morebuf.sp, gp->stackguard - StackGuard);
+		runtime·throw("runtime: split stack overflow");
+	}
+	if(argsize % sizeof(uintptr) != 0) {
+		runtime·printf("runtime: stack split with misaligned argsize %d\n", argsize);
+		runtime·throw("runtime: stack split argsize");
+	}
+
+	minalloc = 0;
+	reflectcall = framesize==1;
+	if(reflectcall) {
+		framesize = 0;
+		// moreframesize_minalloc is only set in runtime·gc(),
+		// that calls newstack via reflect·call().
+		minalloc = m->moreframesize_minalloc;
+		m->moreframesize_minalloc = 0;
+		if(framesize < minalloc)
+			framesize = minalloc;
+	}
+
+	if(reflectcall && minalloc == 0 && m->morebuf.sp - sizeof(Stktop) - argsize - 32 > gp->stackguard) {
+		// special case: called from reflect.call (framesize==1)
+		// to call code with an arbitrary argument size,
+		// and we have enough space on the current stack.
+		// the new Stktop* is necessary to unwind, but
+		// we don't need to create a new segment.
+		top = (Stktop*)(m->morebuf.sp - sizeof(*top));
+		stk = (byte*)gp->stackguard - StackGuard;
+		free = 0;
+	} else {
+		// allocate new segment.
+		framesize += argsize;
+		framesize += StackExtra;	// room for more functions, Stktop.
+		if(framesize < StackMin)
+			framesize = StackMin;
+		framesize += StackSystem;
+		stk = runtime·stackalloc(framesize);
+		top = (Stktop*)(stk+framesize-sizeof(*top));
+		free = framesize;
+	}
+
+	if(0) {
+		runtime·printf("newstack framesize=%d argsize=%d morepc=%p moreargp=%p gobuf=%p, %p top=%p old=%p\n",
+			framesize, argsize, m->morepc, m->moreargp, m->morebuf.pc, m->morebuf.sp, top, gp->stackbase);
+	}
+
+	top->stackbase = (byte*)gp->stackbase;
+	top->stackguard = (byte*)gp->stackguard;
+	top->gobuf = m->morebuf;
+	top->argp = m->moreargp;
+	top->argsize = argsize;
+	top->free = free;
+	m->moreargp = nil;
+	m->morebuf.pc = nil;
+	m->morebuf.sp = (uintptr)nil;
+
+	// copy flag from panic
+	top->panic = gp->ispanic;
+	gp->ispanic = false;
+
+	gp->stackbase = (uintptr)top;
+	gp->stackguard = (uintptr)stk + StackGuard;
+
+	sp = (byte*)top;
+	if(argsize > 0) {
+		sp -= argsize;
+		dst = (uintptr*)sp;
+		dstend = dst + argsize/sizeof(*dst);
+		src = (uintptr*)top->argp;
+		while(dst < dstend)
+			*dst++ = *src++;
+	}
+	if(thechar == '5') {
+		// caller would have saved its LR below args.
+		sp -= sizeof(void*);
+		*(void**)sp = nil;
+	}
+
+	// Continue as if lessstack had just called m->morepc
+	// (the PC that decided to grow the stack).
+	label.sp = (uintptr)sp;
+	label.pc = (byte*)runtime·lessstack;
+	label.g = m->curg;
+	runtime·gogocall(&label, m->morepc);
+
+	*(int32*)345 = 123;	// never return
+}

コアとなるコードの解説

このコミットは、Goランタイムのスタック管理に関するコードを、既存のmalloc.gocproc.cからstack.cという新しいファイルに移動させるものです。機能的な変更は一切含まれていません。

stack.c に移動された主要な機能

  1. スタックキャッシュ管理:

    • StackCacheNode構造体、stackcache(グローバルキャッシュのヘッド)、stackcachemu(ロック)といったデータ構造。
    • stackcacherefill(): グローバルキャッシュからローカルキャッシュへスタックセグメントを補充する。
    • stackcacherelease(): ローカルキャッシュからグローバルキャッシュへスタックセグメントを解放する。 これらの機能は、スタックセグメントの頻繁な割り当てと解放によるオーバーヘッドを削減し、メモリの再利用を効率化するために存在します。
  2. スタックの割り当てと解放:

    • runtime·stackalloc(uint32 n): 指定されたサイズのスタックセグメントを割り当てる。
      • この関数は、スケジューラスタック(m->g0)上で呼び出されることが必須です。これは、スタック割り当て中にスタックを拡張しようとするとデッドロックが発生する可能性があるためです。
      • Goランタイムがメモリ割り当て中(m->mallocing)またはGC中(m->gcing)の場合、デッドロックを避けるために、固定サイズのスタックセグメントを管理する内部のフリーリスト(スタックキャッシュ)から割り当てを行います。それ以外の場合は、通常のガベージコレクション対象のメモリ割り当て関数runtime·mallocgcを使用します。
    • runtime·stackfree(void *v, uintptr n): 割り当てられたスタックセグメントを解放する。
      • 割り当て時と同様に、メモリ割り当て中またはGC中の場合はスタックキャッシュに返却し、それ以外の場合は通常のメモリ解放関数runtime·freeを使用します。
  3. スタックの切り替え:

    • runtime·oldstack(): runtime·lessstackから呼び出され、ゴルーチンが新しいスタックセグメントから古いスタックセグメントに戻る際に使用されます。
      • Stktop構造体(スタック切り替え時のコンテキスト情報)から、ゴルーチンの古いスタックベースとスタックガードの値を復元します。
      • 新しいスタックセグメントを解放します。
      • runtime·gogoを呼び出し、保存されたコンテキスト(Gobuf)にジャンプして実行を再開します。
    • runtime·newstack(): reflect·callまたはruntime·morestackから呼び出され、ゴルーチンのスタックが不足し、新しいスタックセグメントが必要になった際に使用されます。
      • 必要なフレームサイズと引数サイズを計算し、runtime·stackallocを使用して新しいスタックセグメントを割り当てます。
      • 現在のゴルーチンのコンテキスト(プログラムカウンタ、スタックポインタ、引数ポインタなど)をStktop構造体に保存し、新しいスタックセグメントの先頭に配置します。
      • 引数を古いスタックから新しいスタックにコピーします。
      • runtime·gogocallを呼び出し、runtime·lessstackm->morepc(スタック拡張を決定した元のPC)を呼び出したかのように実行を継続します。

変更の意図

このコミットの主な意図は、Goランタイムのコードベースの構造を改善することです。スタック管理に関連するすべてのロジックをstack.cに集約することで、以下の利点が得られます。

  • 関心の分離 (Separation of Concerns): メモリ割り当て(malloc.goc)やプロセッサ・ゴルーチン管理(proc.c)といった他の機能から、スタック管理のロジックを明確に分離します。これにより、各ファイルの役割がより明確になり、コードの理解が容易になります。
  • 保守性の向上: スタック管理に関するバグ修正や機能追加が必要になった場合、関連するコードがstack.cに集中しているため、変更箇所を特定しやすくなります。これにより、他の機能への意図しない影響を最小限に抑えつつ、効率的に作業を進めることができます。
  • 将来の拡張性: コミットメッセージにあるように、「スケジューラの変更」への準備として行われました。スタック管理コードが独立したモジュールとなることで、将来的にスケジューラがスタックの振る舞いに依存するような変更が加えられる際に、より柔軟に対応できるようになります。スタック管理の内部実装が変更されても、他のランタイムコンポーネントへの影響を局所化できます。

このコミットは、Goランタイムの堅牢性と進化を支えるための、重要な基盤整備の一環と言えます。

関連リンク

参考にした情報源リンク

  • Goランタイムのソースコード(上記GitHubリンク)
  • Goのコミットメッセージ
  • GoのIssueトラッカー
  • Goのスタック管理に関する一般的な技術記事(Goのsplit stackやgoroutine stackに関する解説)
    • 例: "Go's Execution Tracer" (Goのスケジューラとスタックに関する詳細な情報が含まれることがあります)
    • 例: "A tour of Go's runtime" (Goランタイムの全体像を解説する記事)
    • 例: "The Go scheduler" (Goのスケジューラとゴルーチンの関係を解説する記事)
    • これらの記事は、Goのランタイム内部の動作、特にスタック管理やスケジューリングの概念を理解する上で役立ちます。具体的なURLはコミット内容から直接参照できませんが、関連する概念を深く掘り下げるために参照されるべき情報源です。