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

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

このコミットは、GoランタイムにおけるCgoの初期化処理を改善し、スレッドローカル変数であるm (machine) と g (goroutine) の設定ロジックを、Goのコンパイラ(gc toolchain)によってコンパイルおよびリンクされるコード内に集約することを目的としています。これにより、今後のCgoの変更に対する堅牢性が向上し、コードの保守性が高まります。

コミット

commit 6a70f9d07339729639520f4ed8faef8965e6e26e
Author: Russ Cox <rsc@golang.org>
Date:   Mon Mar 25 18:14:02 2013 -0400

    runtime: pass setmg function to cgo_init
    
    This keeps the logic about how to set the thread-local variables
    m and g in code compiled and linked by the gc toolchain,
    an important property for upcoming cgo changes.
    
    It's also just a nice cleanup: one less place to update when
    these details change.
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/7560048

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

https://github.com/golang/go/commit/6a70f9d07339729639520f4ed8faef8965e6e26e

元コミット内容

runtime: pass setmg function to cgo_init

This keeps the logic about how to set the thread-local variables
m and g in code compiled and linked by the gc toolchain,
an important property for upcoming cgo changes.

It's also just a nice cleanup: one less place to update when
these details change.

変更の背景

Go言語のランタイムは、GoのコードとC/C++のコードを連携させるCgo機能において、スレッドローカルなm (machine) と g (goroutine) のポインタを適切に設定する必要があります。これまでの実装では、これらのポインタの設定ロジックが、Cgoが使用するGCCコンパイラによってコンパイルされるCコード内に分散していました。

このアプローチにはいくつかの課題がありました。

  1. 保守性の問題: mgの設定方法が変更された場合、GoのランタイムコードとCgoのCコードの両方を更新する必要があり、変更が漏れるリスクや、一貫性のない状態になる可能性がありました。
  2. Goツールチェインとの分離: mgはGoランタイムの核心的な概念であり、その設定ロジックはGoのコンパイラ(gc toolchain)によって管理されるべきです。Cコード内にロジックが散在していると、Goランタイムの内部実装の詳細が外部に漏れ、将来的なGoランタイムの変更がCgoの実装に影響を与えやすくなります。
  3. 今後のCgoの変更への対応: コミットメッセージにあるように、「upcoming cgo changes(今後のCgoの変更)」に備えるため、この重要なプロパティ(mgの設定ロジックがgc toolchainによってコンパイル・リンクされるコード内にあること)を確保する必要がありました。これは、Cgoのより複雑な統合や最適化を進める上で、ランタイムの内部状態管理をより厳密に制御する必要があったことを示唆しています。

このコミットは、これらの課題を解決し、mgの設定ロジックをGoランタイムの管理下に置くことで、Cgoの堅牢性と保守性を向上させることを目的としています。

前提知識の解説

このコミットを理解するためには、以下のGo言語のランタイムとCgoに関する基本的な概念を理解しておく必要があります。

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

Goプログラムは、Goランタイムと呼ばれる軽量な実行環境上で動作します。Goランタイムは、ガベージコレクション、スケジューラ、メモリ管理、Cgoとの連携など、Goプログラムの実行に必要な多くの低レベルな機能を提供します。

2. Goroutine (g)

GoroutineはGo言語における軽量な並行処理の単位です。OSのスレッドよりもはるかに軽量で、数百万のGoroutineを同時に実行することも可能です。各Goroutineは、独自のスタックを持ち、Goランタイムのスケジューラによって管理されます。Goランタイムは、Goroutineの実行状態を管理するために、各Goroutineに紐付けられたg構造体(runtime.g)を使用します。このg構造体には、スタックポインタ、現在のGoroutineの状態、関連するmへのポインタなどが含まれます。

3. Machine (m)

m (machine) は、GoランタイムがOSのスレッドを抽象化したものです。Goランタイムは、OSのスレッドをプールし、その上でGoroutineを実行します。各OSスレッドは、Goランタイム内でm構造体(runtime.m)として表現されます。m構造体には、OSスレッドのID、現在のg(そのスレッドで実行中のGoroutine)、スケジューラ関連の情報などが含まれます。

Goのスケジューラは、mgを関連付けて、GoroutineをOSスレッド上で実行します。mgは、スレッドローカルストレージ (TLS: Thread-Local Storage) を介して、現在のスレッドで実行中のGoroutineとMachineのポインタを効率的に取得できるように設定されます。

4. Cgo

Cgoは、GoプログラムからC言語のコードを呼び出したり、C言語のコードからGoの関数を呼び出したりするためのGoの機能です。Cgoを使用すると、既存のCライブラリをGoプログラムから利用したり、パフォーマンスが重要な部分をCで記述したりすることができます。

Cgoが関与する際、GoランタイムはCコードが実行されるOSスレッドと、そのスレッド上で動作するGoのmgのコンテキストを適切に管理する必要があります。特に、CgoコールバックなどでCコードからGoランタイムに戻る場合、正しいmgのコンテキストが設定されていることが不可欠です。

5. スレッドローカルストレージ (TLS: Thread-Local Storage)

TLSは、各スレッドが独自のデータを持つことができるメカニズムです。Goランタイムでは、現在のmgのポインタをTLSに格納することで、Goコードが実行されているスレッドから、現在実行中のGoroutineとMachineの情報を高速に取得できるようにしています。これにより、関数呼び出しのたびにこれらの情報を引数として渡す必要がなくなり、効率が向上します。

6. gc toolchain

gc toolchainは、Go言語の公式コンパイラとツールセットを指します。これには、go buildコマンドが使用するコンパイラ(gc)、アセンブラ、リンカなどが含まれます。Goのランタイムコードは、このgc toolchainによってコンパイルされ、Goプログラムの実行ファイルにリンクされます。

技術的詳細

このコミットの技術的な核心は、GoランタイムがCgoスレッドの初期化時に、mgのポインタをスレッドローカルストレージに設定するロジックを、CgoのCコードからGoランタイムのアセンブリコードに移行した点にあります。

以前の実装では、src/pkg/runtime/cgo/gcc_*.cファイル群(FreeBSD, Linux, NetBSD, OpenBSDの386およびamd64アーキテクチャ向け)において、インラインアセンブリ(asm volatile)を使用して直接TLSにmgの値を書き込んでいました。例えば、Linux/386の場合、%gs:-8%gs:-4というオフセットにgmを書き込むようなコードがありました。

このコミットでは、以下の変更が行われました。

  1. setmg_gcc関数の導入: src/pkg/runtime/asm_386.ssrc/pkg/runtime/asm_amd64.sに、setmg_gccという新しいアセンブリ関数が追加されました。この関数は、引数としてmgのポインタを受け取り、現在のスレッドのTLSにそれらを書き込む役割を担います。

    • get_tls(AX): 現在のスレッドのTLSベースアドレスをAXレジスタにロードします。
    • MOVL DX, m(AX) / MOVQ DI, m(AX): mポインタをTLSの適切なオフセットに格納します。
    • MOVL DX, g(AX) / MOVQ SI, g(AX): gポインタをTLSの適切なオフセットに格納します。 これらのアセンブリコードは、Goのgc toolchainによってコンパイル・リンクされます。
  2. x_cgo_init関数のシグネチャ変更: src/pkg/runtime/cgo/gcc_*.cファイル群にあるx_cgo_init関数のシグネチャが変更され、void (*setmg)(void*, void*)という関数ポインタを引数として受け取るようになりました。この関数ポインタは、Goランタイム側で定義されたsetmg_gcc関数を指します。

  3. CコードからのTLS直接操作の削除と関数ポインタの利用: CgoのCコード(src/pkg/runtime/cgo/gcc_*.c)から、TLSを直接操作していたインラインアセンブリコード(asm volatileブロック)が削除されました。代わりに、x_cgo_initで受け取ったsetmg_gcc関数ポインタを介して、setmg_gcc((void*)ts.m, (void*)ts.g);のように呼び出す形に変更されました。

  4. _cgo_init呼び出し箇所の変更: src/pkg/runtime/asm_386.ssrc/pkg/runtime/asm_amd64.sにおいて、_cgo_initを呼び出す際に、新しく追加されたsetmg_gcc関数のアドレスを引数として渡すように変更されました。

この変更により、mgのTLSへの設定ロジックは、CgoのCコードからGoランタイムのアセンブリコード(gc toolchainによってコンパイルされる部分)に完全に移行しました。これにより、Goランタイムの内部実装の詳細がCgoのCコードから隠蔽され、Goランタイムの変更がCgoに与える影響が最小限に抑えられます。また、TLSのオフセットや設定方法が変更された場合でも、Goランタイムのアセンブリコードのみを更新すればよくなり、保守性が大幅に向上します。

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

このコミットにおける主要なコード変更は、以下のファイル群にわたります。

  1. src/pkg/runtime/asm_386.s および src/pkg/runtime/asm_amd64.s:

    • 新しいアセンブリ関数 setmg_gcc が追加されました。この関数は、mgのポインタを引数として受け取り、スレッドローカルストレージに設定します。
    • _cgo_init を呼び出す箇所で、setmg_gcc のアドレスを引数として渡すように変更されました。

    例 (src/pkg/runtime/asm_386.s):

    --- a/src/pkg/runtime/asm_386.s
    +++ b/src/pkg/runtime/asm_386.s
    @@ -37,6 +37,8 @@ nocpuinfo:
     	MOVL	_cgo_init(SB), AX
     	TESTL	AX, AX
     	JZ	needtls
    +	MOVL	$setmg_gcc<>(SB), BX
    +	MOVL	BX, 4(SP)
     	MOVL	BP, 0(SP)
     	CALL	AX
     	// skip runtime·ldt0setup(SB) and tls test after _cgo_init for non-windows
    @@ -643,6 +645,15 @@ settls:
     	MOVL	BX, g(CX)
     	RET
     
    +// void setmg_gcc(M*, G*); set m and g. for use by gcc
    +TEXT setmg_gcc<>(SB), 7, $0	
    +	get_tls(AX)
    +	MOVL	mm+0(FP), DX
    +	MOVL	DX, m(AX)
    +	MOVL	gg+4(FP), DX
    +	MOVL	DX,g (AX)
    +	RET
    +
     // check that SP is in range [g->stackbase, g->stackguard)
     TEXT runtime·stackcheck(SB), 7, $0
     	get_tls(CX)
    
  2. src/pkg/runtime/cgo/gcc_freebsd_386.csrc/pkg/runtime/cgo/gcc_freebsd_amd64.csrc/pkg/runtime/cgo/gcc_linux_386.csrc/pkg/runtime/cgo/gcc_linux_amd64.csrc/pkg/runtime/cgo/gcc_netbsd_386.csrc/pkg/runtime/cgo/gcc_netbsd_amd64.csrc/pkg/runtime/cgo/gcc_openbsd_386.csrc/pkg/runtime/cgo/gcc_openbsd_amd64.c:

    • x_cgo_init 関数のシグネチャが変更され、void (*setmg)(void*, void*) 型の関数ポインタを引数として受け取るようになりました。
    • threadentry 関数内で、TLSを直接操作していたインラインアセンブリコードが削除され、代わりに受け取った関数ポインタ setmg_gcc を呼び出すように変更されました。

    例 (src/pkg/runtime/cgo/gcc_linux_386.c):

    --- a/src/pkg/runtime/cgo/gcc_linux_386.c
    +++ b/src/pkg/runtime/cgo/gcc_linux_386.c
    @@ -8,13 +8,15 @@
     #include "libcgo.h"
     
     static void *threadentry(void*);
    +static void (*setmg_gcc)(void*, void*);
     
     void
    -x_cgo_init(G *g)
    +x_cgo_init(G *g, void (*setmg)(void*, void*))
     {
     	pthread_attr_t attr;
     	size_t size;
     
    +	setmg_gcc = setmg;
     	pthread_attr_init(&attr);
     	pthread_attr_getstacksize(&attr, &size);
     	g->stackguard = (uintptr)&attr - size + 4096;
    @@ -69,18 +71,9 @@ threadentry(void *v)
     	ts.g->stackguard = (uintptr)&ts - ts.g->stackguard + 4096;
     
     	/*
    -	 * Set specific keys.  On Linux/ELF, the thread local storage
    -	 * is just before %gs:0.  Our dynamic 8.out's reserve 8 bytes
    -	 * for the two words g and m at %gs:-8 and %gs:-4.
    -	 * Xen requires us to access those words indirect from %gs:0
    -	 * which points at itself.
    +	 * Set specific keys.
     	 */
    -	asm volatile (
    -		"movl %%gs:0, %%eax\\n"		// MOVL 0(GS), tmp
    -		"movl %0, -8(%%eax)\\n"	// MOVL g, -8(GS)
    -		"movl %1, -4(%%eax)\\n"	// MOVL m, -4(GS)
    -		:: "r"(ts.g), "r"(ts.m) : "%eax"
    -	);
    +	setmg_gcc((void*)ts.m, (void*)ts.g);
     
     	crosscall_386(ts.fn);
     	return nil;
    

コアとなるコードの解説

このコミットのコアとなる変更は、Goランタイムのmgポインタをスレッドローカルストレージに設定する責任を、CgoのCコードからGoランタイムのアセンブリコードに移管した点に集約されます。

  1. setmg_gcc アセンブリ関数の役割: setmg_gcc は、Goランタイムが提供する低レベルなアセンブリ関数です。この関数は、現在のスレッドのTLSベースアドレスを取得し、そのオフセットにmgのポインタを書き込みます。このアセンブリコードは、Goのgc toolchainによってコンパイルされるため、TLSのレイアウトやアクセス方法に関するGoランタイムの内部的な知識を安全にカプセル化できます。

  2. x_cgo_initsetmg 関数ポインタ: x_cgo_init は、Cgoが新しいOSスレッドを初期化する際に呼び出される関数です。この関数がsetmgという関数ポインタを受け取るようになったことで、CgoのCコードはTLSへの直接的な書き込みロジックを持つ必要がなくなりました。代わりに、Goランタイムが提供するsetmg_gcc関数を、この関数ポインタを介して呼び出すだけでよくなりました。これにより、CgoのCコードはGoランタイムの内部実装に依存することなく、抽象化されたインターフェースを通じてmgを設定できるようになりました。

  3. TLS直接操作の削除: 以前のCgoのCコードでは、各OS/アーキテクチャ固有のインラインアセンブリを使用して、TLSにmgを直接書き込んでいました。これは、TLSのオフセットやアクセス方法がOSやアーキテクチャによって異なるため、多くの重複したコードと複雑さをもたらしていました。このコミットにより、これらの重複したインラインアセンブリコードが削除され、コードベースが大幅に簡素化されました。

この変更は、Goランタイムの設計原則である「分離とカプセル化」を強化するものです。Goランタイムの内部状態(mg)の管理は、Goランタイム自身に完全に委ねられるべきであり、Cgoのような外部インターフェースはその詳細に立ち入るべきではありません。このコミットは、この原則をCgoの初期化プロセスに適用し、Goランタイムの将来的な進化に対する柔軟性を高めています。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード:
    • src/pkg/runtime/asm_386.s
    • src/pkg/runtime/asm_amd64.s
    • src/pkg/runtime/cgo/ 以下の各OS/アーキテクチャ固有のCファイル
  • GoのIssueトラッカー/CL (Change List):
  • Goランタイムに関する技術記事やブログ:
    • Goのスケジューラ、Goroutine、M/P/Gモデルに関する解説記事は多数存在します。例えば、「Go scheduler M P G」などで検索すると多くの情報が見つかります。
    • スレッドローカルストレージ (TLS) に関する一般的な情報源。
  • GCCインラインアセンブリに関するドキュメント:
    • GCCの公式ドキュメントや、インラインアセンブリの使用方法に関するチュートリアル。
  • オペレーティングシステムのTLS実装に関するドキュメント:
    • FreeBSD, Linux, NetBSD, OpenBSDなどのOSにおけるスレッドローカルストレージのメカニズムに関する情報。
  • Goのコミット履歴:
    • このコミットの前後の変更履歴を追うことで、より広範な文脈を理解できます。
  • Russ Coxのブログや発表:
    • Russ CoxはGo言語の主要な開発者の一人であり、彼のブログや発表にはGoランタイムの設計思想や将来の方向性に関する貴重な情報が含まれていることがあります。 I have generated the detailed explanation of the commit in Markdown format, following all the specified instructions and including all the required sections. The output is printed to standard output only, as requested.