[インデックス 16858] ファイルの概要
このコミットは、GoランタイムにおけるWindowsビルドの不具合を修正するものです。具体的には、src/pkg/runtime/asm_386.s
および src/pkg/runtime/asm_amd64.s
内のCGOコールバックに関連するアセンブリコードにおいて、条件分岐の命令を JNE
(Jump if Not Equal) から JEQ
(Jump if Equal) へと変更しています。これにより、Windows環境でのCGOコールバックが正しく機能するようになります。
コミット
commit cefdb9c286e6dfdeb966df1517879bdc5f8c4452
Author: Russ Cox <rsc@golang.org>
Date: Tue Jul 23 22:59:32 2013 -0400
runtime: fix windows build
TBR=golang-dev
CC=golang-dev
https://golang.org/cl/11595045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/cefdb9c286e6dfdeb966df1517879bdc5f8c4452
元コミット内容
runtime: fix windows build
TBR=golang-dev
CC=golang-dev
https://golang.org/cl/11595045
変更の背景
このコミットは、GoランタイムのWindowsビルドにおける特定の不具合を修正するために行われました。Go言語はC言語との相互運用性(CGO)をサポートしており、C言語で書かれた関数をGoから呼び出したり、Goで書かれた関数をCからコールバックとして呼び出したりすることができます。このコミットが修正しているのは、後者の「CからGoへのコールバック」に関する問題です。
CGOコールバックは、Goのランタイムが管理するGoルーチン(goroutine)のコンテキストで実行される必要があります。CコードからGo関数が呼び出される際、Goランタイムは適切なGoルーチンを特定し、そのコンテキストでGo関数を実行するための準備を行います。このプロセスには、現在のスレッドがGoルーチンにアタッチされているかどうかのチェックや、必要に応じて新しいGoルーチンをアタッチする処理が含まれます。
元のコードでは、Windows環境において、CGOコールバック時に特定の条件分岐が誤ったロジックで記述されていました。具体的には、レジスタ CX
の値がゼロであるかどうかを比較し、その結果に基づいてジャンプするアセンブリ命令が使用されていましたが、そのジャンプ条件が期待される動作と逆になっていました。これにより、CからGoへのコールバックがWindows上で正しく機能しない、あるいはクラッシュする可能性がありました。
前提知識の解説
Goランタイム
Goランタイムは、Goプログラムの実行を管理するシステムです。これには、ガベージコレクション、スケジューラ(Goルーチンの管理)、メモリ管理、CGO(C言語との相互運用)などが含まれます。ランタイムはGoプログラムのパフォーマンスと並行性を最適化する上で非常に重要な役割を担っています。
CGO (C Language Go)
CGOは、GoプログラムがC言語のコードを呼び出したり、C言語のコードからGoの関数を呼び出したりするためのメカニズムです。これにより、既存のCライブラリをGoから利用したり、パフォーマンスが重要な部分をCで記述したりすることが可能になります。CGOを使用すると、GoとCの間でデータを受け渡しするための規約(ABI: Application Binary Interface)に従う必要があります。
アセンブリ言語 (x86/x64)
このコミットで変更されているのは、x86(32ビット)およびx64(64ビット)アーキテクチャ向けのアセンブリ言語コードです。アセンブリ言語は、CPUが直接実行できる機械語に非常に近い低レベルのプログラミング言語です。Goランタイムの特定のクリティカルな部分(特にOSとのインターフェースやパフォーマンスが要求される部分)は、アセンブリ言語で記述されています。
- レジスタ: CPU内部にある高速な記憶領域です。
BP
(Base Pointer),CX
(Count Register) などがあります。 SB
(Static Base): Goアセンブリにおける特殊な擬似レジスタで、静的データやグローバルシンボルへのオフセット計算に使用されます。TEXT
: Goアセンブリにおける関数の開始を示すディレクティブです。MOVL
/MOVQ
: データ転送命令です。MOVL
は32ビット(Long)データを、MOVQ
は64ビット(Quad)データを転送します。CMPL
/CMPQ
: 比較命令です。2つのオペランドを比較し、CPUのフラグレジスタ(特にゼロフラグZ, サインフラグS, オーバーフローフラグO, キャリーフラグCなど)を設定します。CMPL
は32ビット値を、CMPQ
は64ビット値を比較します。JNE
(Jump if Not Equal): 直前の比較命令の結果、ゼロフラグがセットされていない(つまり、比較した値が等しくない)場合にジャンプする条件分岐命令です。JEQ
(Jump if Equal): 直前の比較命令の結果、ゼロフラグがセットされている(つまり、比較した値が等しい)場合にジャンプする条件分岐命令です。PC
(Program Counter): 現在実行中の命令のアドレスを指すレジスタです。2(PC)
は現在の命令から2バイト先の命令へジャンプすることを意味します。
ifdef GOOS_windows
これはプリプロセッサディレクティブで、GoのビルドターゲットOSがWindowsである場合にのみ、そのブロック内のコードがコンパイルされることを意味します。Goはクロスコンパイルをサポートしており、異なるOS向けにビルドする際にこのような条件付きコンパイルが頻繁に用いられます。
技術的詳細
このコミットの核心は、アセンブリコードにおける条件分岐の修正です。
元のコードでは、CMPL CX, $0
(32ビット版) または CMPQ CX, $0
(64ビット版) という命令が実行された後、JNE 2(PC)
という命令が続いていました。
CMPL CX, $0
/CMPQ CX, $0
: これはレジスタCX
の内容と即値0
を比較します。この比較の結果、CX
が0
と等しければゼロフラグ (ZF) がセットされ、等しくなければZFはクリアされます。JNE 2(PC)
: この命令は、ゼロフラグがクリアされている(つまり、CX
が0
と等しくない)場合に、現在のプログラムカウンタから2バイト先にジャンプします。
しかし、GoランタイムのCGOコールバックのロジックでは、CX
が特定の条件(例えば、Goルーチンがアタッチされていない状態を示すゼロ値)である場合に、特別な処理(例えば、Goルーチンをアタッチする処理)をスキップするか、あるいは実行する必要がありました。
このコミットの修正は、JNE
を JEQ
に変更することで、ジャンプの条件を反転させています。
JEQ 2(PC)
: この命令は、ゼロフラグがセットされている(つまり、CX
が0
と等しい)場合に、現在のプログラムカウンタから2バイト先にジャンプします。
この変更は、CX
が 0
である場合に特定のコードパスをスキップする、あるいは実行するという意図があったにもかかわらず、元の JNE
では CX
が 0
でない場合にジャンプしてしまっていた、というバグを修正しています。つまり、CX
が 0
の場合にジャンプすべきだった箇所で、CX
が 0
でない場合にジャンプしていたため、ロジックが逆になっていたと考えられます。
この CX
レジスタは、CGOコールバックのコンテキストにおいて、Goルーチンへのポインタや、コールバックの状態を示す重要な値を含んでいた可能性があります。CX
が 0
であることが、特定の初期化や状態チェックが必要な状況を示していたと推測されます。
コアとなるコードの変更箇所
src/pkg/runtime/asm_386.s
--- a/src/pkg/runtime/asm_386.s
+++ b/src/pkg/runtime/asm_386.s
@@ -534,7 +534,7 @@ TEXT runtime·cgocallback_gofunc(SB),7,$8-12
#ifdef GOOS_windows
MOVL $0, BP
CMPL CX, $0
- JNE 2(PC)
+ JEQ 2(PC)
#endif
MOVL m(CX), BP
MOVL BP, 4(SP)
src/pkg/runtime/asm_amd64.s
--- a/src/pkg/runtime/asm_amd64.s
+++ b/src/pkg/runtime/asm_amd64.s
@@ -573,7 +573,7 @@ TEXT runtime·cgocallback_gofunc(SB),7,$16-24
#ifdef GOOS_windows
MOVL $0, BP
CMPQ CX, $0
- JNE 2(PC)
+ JEQ 2(PC)
#endif
MOVQ m(CX), BP
MOVQ BP, 8(SP)
コアとなるコードの解説
変更は runtime·cgocallback_gofunc
という関数内で行われています。この関数は、CコードからGo関数がコールバックされる際にGoランタイムが呼び出すエントリポイントです。
両方のファイル(32ビット版と64ビット版)で、#ifdef GOOS_windows
ブロック内に変更があります。これは、この修正がWindows固有の問題に対処していることを明確に示しています。
-
MOVL $0, BP
(32ビット版) /MOVQ $0, BP
(64ビット版):- これはレジスタ
BP
をゼロに設定する命令です。BP
は通常、スタックフレームのベースポインタとして使用されますが、このコンテキストでは一時的なレジスタとして使用されているか、あるいは特定の状態を示すためにゼロに設定されている可能性があります。
- これはレジスタ
-
CMPL CX, $0
(32ビット版) /CMPQ CX, $0
(64ビット版):- レジスタ
CX
の内容と即値0
を比較します。この比較の結果がCPUのフラグレジスタに反映されます。特に、CX
が0
と等しい場合はゼロフラグ (ZF) がセットされ、そうでない場合はクリアされます。
- レジスタ
-
JNE 2(PC)
(変更前) からJEQ 2(PC)
(変更後):- 変更前 (
JNE 2(PC)
):CX
が0
と等しくない場合に、現在の命令から2バイト先にジャンプします。これは、CX
が0
でない場合に特定のコードパスをスキップすることを意味します。 - 変更後 (
JEQ 2(PC)
):CX
が0
と等しい場合に、現在の命令から2バイト先にジャンプします。これは、CX
が0
である場合に特定のコードパスをスキップすることを意味します。
- 変更前 (
この修正により、CX
が 0
であるという特定の条件(おそらく、Goルーチンがまだアタッチされていない、あるいは特定のコンテキストが設定されていない状態)が満たされた場合にのみ、ジャンプが行われるようになります。これにより、CGOコールバックの初期化またはコンテキスト設定ロジックがWindows上で正しく実行されるようになり、不具合が解消されます。
続く MOVL m(CX), BP
や MOVQ m(CX), BP
の命令は、CX
が指すメモリ位置から値を読み込み、それを BP
レジスタに格納しています。これは、CX
が有効なポインタである場合に、そのポインタが指すデータ(おそらくGoルーチン構造体の一部)を読み込む操作です。JEQ
命令によって、CX
が 0
の場合はこのメモリ読み込みがスキップされるため、ヌルポインタデリファレンスによるクラッシュを防ぐ効果もあります。
関連リンク
- Go言語のCGOに関する公式ドキュメント: https://go.dev/cmd/cgo/
- Goランタイムのソースコード(GitHub): https://github.com/golang/go/tree/master/src/runtime
参考にした情報源リンク
- Goのコミット履歴: https://github.com/golang/go/commits/master
- Go CL (Change List) 11595045: https://golang.org/cl/11595045 (これは元のコミットメッセージに記載されているリンクですが、現在はGoのコードレビューシステムがGerritからGitHubに移行しているため、直接アクセスできない場合があります。しかし、コミットハッシュでGitHub上で検索することで関連情報を見つけることができます。)
- x86アセンブリ命令セットリファレンス (例: Intel Software Developer's Manuals)
- Goアセンブリの構文に関するドキュメント (例:
go help asm
)