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

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

このコミットは、Goランタイムからbadcallback()関数群を削除するものです。具体的には、src/pkg/runtimeディレクトリ内の様々なOS固有のCファイルおよびWindows固有のアセンブリファイルから、CgoコールバックがGoによって作成されていないスレッドで発生した場合にエラーメッセージを出力するbadcallback関連のコードが削除されています。

コミット

  • コミットハッシュ: a3e0002e6a6d466a505d9d13c75880bf960df512
  • Author: Jan Ziak 0xe2.0x9a.0x9b@gmail.com
  • Date: Wed May 29 17:51:17 2013 +0200
  • コミットメッセージ:
    runtime: remove all badcallback() functions
    
    R=iant
    CC=golang-dev
    https://golang.org/cl/9738046
    

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

https://github.com/golang/go/commit/a3e0002e6a6d466a505d9d13c75880bf960df512

元コミット内容

runtime: remove all badcallback() functions

R=iant
CC=golang-dev
https://golang.org/cl/9738046

変更の背景

このコミットの背景には、GoランタイムにおけるCgo(C言語との相互運用)の進化があります。badcallback()関数は、CgoコールバックがGoランタイムによって管理されていない(Goが作成していない)スレッドで実行された場合に、エラーメッセージを出力するためのものでした。これは、Goのガベージコレクタやスケジューラが正しく機能するために、GoルーチンがGoランタイムによって管理されるM(Machine)とG(Goroutine)のコンテキスト内で実行される必要があるという制約から来ています。

しかし、GoのCgoメカニズムは時間の経過とともに成熟し、外部Cコードからのコールバックのハンドリングがより堅牢になりました。この変更は、おそらくGoランタイムがCgoコールバックを処理する方法が改善され、badcallbackが検出していたような「Goが作成していないスレッドでのCgoコールバック」というシナリオがもはや発生しないか、あるいは異なる、より適切な方法で処理されるようになったことを示唆しています。これにより、不要になったコードを削除し、ランタイムの複雑さを軽減することが目的と考えられます。

前提知識の解説

このコミットを理解するためには、以下の概念について基本的な知識が必要です。

  • Goランタイム (Go Runtime): Goプログラムの実行を管理するシステムです。これには、ガベージコレクタ、スケジューラ(ゴルーチンをOSスレッドにマッピングする)、メモリ管理などが含まれます。Goの並行処理モデルは、OSスレッドの上に抽象化された軽量なゴルーチンによって実現されます。
  • Cgo: GoプログラムからC言語のコードを呼び出したり、C言語のコードからGoの関数を呼び出したりするためのGoの機能です。Cgoを使用すると、既存のCライブラリをGoプロジェクトに統合したり、パフォーマンスが重要な部分をCで記述したりすることができます。
  • コールバック (Callback): ある関数が別の関数に引数として渡され、その別の関数が特定のイベント発生時や処理完了時に、渡された関数を呼び出すメカニズムです。Cgoの文脈では、CコードがGoの関数を呼び出す場合が「Cgoコールバック」に該当します。
  • スレッド (Thread): オペレーティングシステムが管理する実行の単位です。Goランタイムは、複数のゴルーチンを少数のOSスレッドに多重化して実行します。
  • m (Machine) と g (Goroutine): Goランタイムの内部概念です。mはOSスレッドを表し、gはゴルーチンを表します。Goスケジューラは、gm上で実行します。CgoコールバックがGoランタイムによって管理されていないスレッド(つまり、Goのmに紐付けられていないOSスレッド)で発生すると、Goのスケジューラやガベージコレクタが予期しない状態になり、問題を引き起こす可能性がありました。
  • #pragma dataflag 16 / #pragma textflag 7: Goのコンパイラ(gc)が使用するディレクティブです。
    • #pragma dataflag 16: このデータセクションにはポインタが含まれていないことを示します。ガベージコレクタがこのセクションをスキャンする必要がないことを意味し、パフォーマンス向上に寄与します。
    • #pragma textflag 7: この関数はスタックスプリット(Goのスタック拡張メカニズム)を必要としないことを示します。これは、関数が非常に短く、スタックを使い切る可能性がない場合や、Goランタイムのコンテキスト外で実行される場合に用いられます。badcallback関数は「foreign stack」(Goが管理しないスタック)で実行されるため、このフラグが設定されていました。

技術的詳細

削除されたbadcallback()関数は、GoランタイムのC部分に実装されていました。その主な目的は、CgoコールバックがGoランタイムが認識しないスレッド(つまり、Goのスケジューラによって管理されていないOSスレッド)で発生した場合に、開発者に対して警告を発することでした。

具体的には、以下の処理を行っていました。

  1. badcallbackという静的なint8配列(C言語のchar配列に相当)に、エラーメッセージ "runtime: cgo callback on thread not created by Go.\\n" を格納していました。
  2. runtime·badcallbackという関数が定義されており、この関数は#pragma textflag 7が付けられていました。これは、この関数がGoのスタックスプリットメカニズムの対象外であり、Goのmgのコンテキストなしに、外部のスタック上で実行されることを意味します。
  3. この関数は、標準エラー出力(ファイルディスクリプタ2)に、上記のエラーメッセージを書き込んでいました。Windows版では、WriteFileシステムコールを使用していました。

この機能が削除されたということは、GoランタイムがCgoコールバックを処理する内部メカニズムが変更され、もはやこのような「不正な」コールバックがGoランタイムの制御外で発生することがなくなったか、あるいは発生してもGoランタイムがそれを適切に処理できるようになったことを意味します。

考えられる理由としては、以下のような点が挙げられます。

  • Cgoコールバックのルーティング改善: Cgoコールバックが常にGoランタイムが管理するスレッドにルーティングされるようになった。例えば、CコードからGo関数が呼び出される際に、Goランタイムが内部的に適切なOSスレッド(Goのmに紐付けられたスレッド)にディスパッチするようになった。
  • Goスケジューラの堅牢性向上: Goスケジューラが、外部スレッドからのコールバックをより安全に処理できるようになり、以前のような警告が不要になった。
  • デバッグ情報の変更: この種のエラーがより根本的なレベルで捕捉され、異なるメカニズム(例えば、パニックやより詳細なランタイムエラー)で報告されるようになった。

この変更は、Goランタイムの内部的なクリーンアップと最適化の一環であり、Cgoの安定性と信頼性が向上したことを示しています。

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

このコミットでは、以下のファイルからbadcallbackに関連するコードが完全に削除されています。

  • src/pkg/runtime/os_darwin.c
  • src/pkg/runtime/os_freebsd.c
  • src/pkg/runtime/os_linux.c
  • src/pkg/runtime/os_netbsd.c
  • src/pkg/runtime/os_openbsd.c
  • src/pkg/runtime/os_plan9.c
  • src/pkg/runtime/os_windows.c
  • src/pkg/runtime/sys_windows_386.s
  • src/pkg/runtime/sys_windows_amd64.s

os_*.cファイルからは、以下のC言語のコードブロックが削除されています。

#pragma dataflag 16 // no pointers
static int8 badcallback[] = "runtime: cgo callback on thread not created by Go.\\n";

// This runs on a foreign stack, without an m or a g.  No stack split.
#pragma textflag 7
void
runtime·badcallback(void)
{
	runtime·write(2, badcallback, sizeof badcallback - 1);
}

os_plan9.cではruntime·pwriteが使われています。)

src/pkg/runtime/os_windows.cからは、以下のC言語のコードブロックが削除されています。

#pragma dataflag 16 // no pointers
int8 runtime·badcallbackmsg[] = "runtime: cgo callback on thread not created by Go.\\n";
int32 runtime·badcallbacklen = sizeof runtime·badcallbackmsg - 1;

src/pkg/runtime/sys_windows_386.sからは、以下のx86アセンブリコードブロックが削除されています。

TEXT	runtime·badcallback(SB),7,$24
	// stderr
	MOVL	$-12, 0(SP)
	MOVL	SP, BP
	CALL	*runtime·GetStdHandle(SB)
	MOVL	BP, SP

	MOVL	AX, 0(SP)	// handle
	MOVL	$runtime·badcallbackmsg(SB), DX // pointer
	MOVL	DX, 4(SP)
	MOVL	runtime·badcallbacklen(SB), DX // count
	MOVL	DX, 8(SP)
	LEAL	20(SP), DX  // written count
	MOVL	$0, 0(DX)
	MOVL	DX, 12(SP)
	MOVL	$0, 16(SP) // overlapped
	CALL	*runtime·WriteFile(SB)
	MOVL	BP, SI
	RET

src/pkg/runtime/sys_windows_amd64.sからは、以下のx64アセンブリコードブロックが削除されています。

// This should be called on a system stack,
// so we don't need to concern about split stack.
TEXT runtime·badcallback(SB),7,$0
	SUBQ	$48, SP

	// stderr
	MOVQ	$-12, CX // stderr
	MOVQ	CX, 0(SP)
	MOVQ	runtime·GetStdHandle(SB), AX
	CALL	AX

	MOVQ	AX, CX	// handle
	MOVQ	CX, 0(SP)
	MOVQ	$runtime·badcallbackmsg(SB), DX // pointer
	MOVQ	DX, 8(SP)
	MOVL	$runtime·badcallbacklen(SB), R8 // count
	MOVQ	R8, 16(SP)
	LEAQ	40(SP), R9  // written count
	MOVQ	$0, 0(R9)
	MOVQ	R9, 24(SP)
	MOVQ	$0, 32(SP)	// overlapped
	MOVQ	runtime·WriteFile(SB), AX
	CALL	AX
	
	ADDQ	$48, SP
	RET

コアとなるコードの解説

削除されたコードは、GoランタイムがCgoコールバックの不正な使用を検出した場合に、標準エラー出力に警告メッセージを書き込むためのものでした。

  • static int8 badcallback[] = "...": これは、出力されるエラーメッセージの文字列リテラルを定義しています。#pragma dataflag 16は、このデータがポインタを含まないため、ガベージコレクタがスキャンする必要がないことをGoコンパイラに伝えます。
  • void runtime·badcallback(void): この関数が、実際にエラーメッセージを書き込む処理を実行していました。
    • // This runs on a foreign stack, without an m or a g. No stack split. というコメントは、この関数がGoランタイムが管理する通常のゴルーチンコンテキスト外で、外部のCコードから呼び出される可能性のあるスレッド上で実行されることを明確に示しています。そのため、Goのスタック拡張メカニズム(スタックスプリット)は適用されません。
    • #pragma textflag 7は、この関数のコードがスタックスプリットの対象外であることをコンパイラに指示します。
    • runtime·write(2, badcallback, sizeof badcallback - 1); (またはruntime·pwrite、WindowsのアセンブリではWriteFileシステムコール) は、ファイルディスクリプタ2(標準エラー出力)に、badcallback配列に格納されたメッセージを書き込んでいました。sizeof badcallback - 1は、C文字列の終端を示すヌル文字を除いた長さを指定しています。

Windowsのアセンブリコードでは、GetStdHandleを呼び出して標準エラー出力のハンドルを取得し、そのハンドルとメッセージのポインタ、長さをWriteFileシステムコールに渡してメッセージを書き込んでいました。

これらのコードの削除は、GoランタイムがCgoコールバックを処理する内部ロジックが変更され、もはやこのような明示的なbadcallbackチェックと警告が不要になったことを意味します。これは、GoのCgoメカニズムがより堅牢になり、外部スレッドからのコールバックがGoランタイムによって適切に処理されるようになった結果と考えられます。

関連リンク

  • Go言語のCgoに関する公式ドキュメント: https://pkg.go.dev/cmd/cgo
  • Goランタイムの内部構造に関する情報(M, P, Gモデルなど): Goの公式ドキュメントやブログ記事、またはGoのソースコード自体が参考になります。

参考にした情報源リンク

  • Goのコミット履歴: https://github.com/golang/go/commits/master
  • Goのソースコード(特にsrc/runtimeディレクトリ)
  • Cgoに関する一般的な情報
  • Goランタイムのスケジューラに関する一般的な情報
  • Goの#pragmaディレクティブに関する情報(Goコンパイラの内部ドキュメントや関連する議論)