[インデックス 18740] ファイルの概要
このコミットは、Go言語のランタイムにおけるARMアーキテクチャ向けのアセンブリコードの修正に関するものです。具体的には、src/pkg/runtime/asm_arm.s
ファイル内のruntime·morestack_noctxt
関数におけるジャンプ命令がJMP
からB
に変更されています。
コミット
commit f884e15aabcdf547eb8c8a10e02e6bddc801e7e8
Author: Russ Cox <rsc@golang.org>
Date: Tue Mar 4 14:03:39 2014 -0500
runtime: fix arm build (B not JMP)
TBR=dvyukov
CC=golang-codereviews
https://golang.org/cl/71060046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f884e15aabcdf547eb8c8a10e02e6bddc801e7e8
元コミット内容
diff --git a/src/pkg/runtime/asm_arm.s b/src/pkg/runtime/asm_arm.s
index aa171d7be9..3aed51f490 100644
--- a/src/pkg/runtime/asm_arm.s
+++ b/src/pkg/runtime/asm_arm.s
@@ -215,7 +215,7 @@ TEXT runtime·morestack(SB),NOSPLIT,$-4-0
TEXT runtime·morestack_noctxt(SB),NOSPLIT,$-4-0
MOVW $0, R7
- JMP runtime·morestack(SB)
+ B runtime·morestack(SB)
// Called from panic. Mimics morestack,
// reuses stack growth code to create a frame
変更の背景
このコミットの背景には、GoランタイムのARMアーキテクチャ向けビルドにおける問題がありました。Goランタイムは、スタックの拡張(morestack
)やゴルーチンのスケジューリングなど、低レベルの処理にアセンブリコードを使用しています。特に、スタックが不足した場合に呼び出されるmorestack
関数は、Goプログラムの安定性と効率性に不可欠です。
runtime·morestack_noctxt
関数は、コンテキスト情報なしでruntime·morestack
を呼び出すためのラッパーのような役割を果たしています。元のコードでは、この関数内でJMP
命令を使用してruntime·morestack
へジャンプしていました。しかし、ARMアーキテクチャにおけるJMP
命令の挙動、またはGoのアセンブラがJMP
をどのように解釈・変換するかに問題があった可能性があります。
具体的には、Goのアセンブラは、異なるアーキテクチャ間で共通のニーモニック(例: JMP
)を提供しつつ、それを各アーキテクチャのネイティブな命令に変換します。ARMの場合、JMP
は通常、PC(プログラムカウンタ)相対アドレス指定を用いた分岐命令に変換されます。しかし、特定の状況下でこの変換が正しく行われず、ビルドエラーや実行時エラーを引き起こしていたと考えられます。
このコミットは、JMP
命令がARMビルドで問題を引き起こしていたため、より直接的でARMアーキテクチャに適した分岐命令であるB
(Branch)に置き換えることで、この問題を解決することを目的としています。
前提知識の解説
Goランタイムとスタック管理
Go言語は、独自のランタイムを持っており、ガベージコレクション、スケジューリング、スタック管理などを担当しています。Goのゴルーチンは、必要に応じてスタックサイズを動的に拡張する「可変スタック」を採用しています。
runtime·morestack
: Goランタイムにおいて、現在のゴルーチンのスタックが不足した場合に呼び出される重要な関数です。この関数は、新しい(より大きな)スタックフレームを割り当て、既存のスタックの内容を新しいスタックにコピーし、実行を再開する役割を担います。runtime·morestack_noctxt
:runtime·morestack
を呼び出すための補助的な関数です。noctxt
という名前が示す通り、コンテキスト情報(例: レジスタの状態など)を保存せずにmorestack
を呼び出す必要がある場合に利用されます。これは、特定の低レベルな状況や、コンテキストの保存が不要または不適切である場合に用いられます。
ARMアセンブリ命令
ARMアーキテクチャは、RISC(Reduced Instruction Set Computer)ベースのプロセッサであり、そのアセンブリ言語は特定の命令セットを持っています。
JMP
(Jump): 一般的なアセンブリ言語のニーモニックで、無条件ジャンプを意味します。Goのアセンブラでは、このJMP
ニーモニックは、ターゲットアーキテクチャ(この場合はARM)の適切な分岐命令に変換されます。通常、これはPC相対アドレス指定を用いた分岐命令(例:B
やBL
)にマッピングされますが、その具体的な実装はアセンブラのバージョンやターゲットアーキテクチャのサブセットによって異なる場合があります。B
(Branch): ARMアセンブリにおける無条件分岐命令です。指定されたアドレスにプログラムカウンタ(PC)を直接設定し、実行フローをそのアドレスに移動させます。B
命令は、関数呼び出しではなく、単なるジャンプ(gotoのようなもの)に使用されます。これは、PC相対オフセットを使用してターゲットアドレスを指定します。
Goのアセンブラ (Plan 9 Assembler)
Go言語は、独自のツールチェインの一部として、Plan 9アセンブラをベースにしたアセンブラを使用しています。このアセンブラは、一般的なx86やARMのアセンブラとは異なる独自の構文とセマンティクスを持っています。
- 擬似命令: Goのアセンブラは、
JMP
のような擬似命令を提供することがあります。これは、複数のネイティブ命令に展開されたり、特定のアーキテクチャの慣習に合わせて最適化されたりする場合があります。 - レジスタの命名規則: Goのアセンブラでは、レジスタの命名規則も標準的なARMアセンブリとは異なる場合があります(例:
R0
,R1
など)。 - シンボル解決:
runtime·morestack(SB)
のように、GoのランタイムシンボルはSB
(Static Base)レジスタからのオフセットとして参照されます。これは、Goのリンカが最終的なアドレスを解決する方法に関連しています。
技術的詳細
このコミットの技術的な核心は、ARMアーキテクチャにおける分岐命令の選択とそのGoアセンブラでの解釈にあります。
元のコードでは、runtime·morestack_noctxt
関数内でJMP runtime·morestack(SB)
という命令が使用されていました。Goのアセンブラは、このJMP
をARMのネイティブ命令に変換します。しかし、この変換がARMビルドにおいて問題を引き起こしていました。
考えられる問題点としては、以下の点が挙げられます。
JMP
の変換の複雑性: GoのアセンブラがJMP
をARM命令に変換する際に、特定のコンテキスト(例:runtime·morestack_noctxt
からruntime·morestack
へのジャンプ)で、生成される命令が正しくない、または効率的でない可能性がありました。例えば、PC相対オフセットの計算が誤っていたり、命令のエンコーディングが特定のARMプロセッサのサブセットで問題を引き起こしたりする可能性です。- PC相対分岐の範囲: ARMの
B
命令は、通常、比較的広い範囲のPC相対分岐をサポートしますが、JMP
がより汎用的なニーモニックであるため、アセンブラがより複雑な(そして場合によっては問題のある)分岐シーケンスを生成していた可能性も考えられます。 - アセンブラのバグまたは制限: 最も直接的な原因として、Goのアセンブラ自体に、ARMアーキテクチャ向けに
JMP
を処理する際のバグや、特定のケースでの制限があった可能性が高いです。JMP
は、Goのアセンブラが提供する高レベルな抽象化であり、その背後で複数のネイティブ命令に展開されることがあります。この展開プロセスがARMの特定のバージョンやコンパイラの最適化と競合していたのかもしれません。
JMP runtime·morestack(SB)
をB runtime·morestack(SB)
に置き換えることで、この問題は解決されました。B
命令はARMアーキテクチャのネイティブな無条件分岐命令であり、より直接的で予測可能な挙動をします。これにより、アセンブラがよりシンプルかつ正確な命令を生成できるようになり、ARMビルドの問題が解消されたと考えられます。
この変更は、Goランタイムの低レベルなアセンブリコードの正確性が、特定のアーキテクチャ(ARM)でのビルドの成功にどれほど重要であるかを示しています。また、Goのアセンブラが提供する抽象化(JMP
)が、常にすべてのターゲットアーキテクチャで完璧に機能するわけではないという教訓も示唆しています。
コアとなるコードの変更箇所
変更はsrc/pkg/runtime/asm_arm.s
ファイルの一箇所のみです。
--- a/src/pkg/runtime/asm_arm.s
+++ b/src/pkg/runtime/asm_arm.s
@@ -215,7 +215,7 @@ TEXT runtime·morestack(SB),NOSPLIT,$-4-0
TEXT runtime·morestack_noctxt(SB),NOSPLIT,$-4-0
MOVW $0, R7
- JMP runtime·morestack(SB)
+ B runtime·morestack(SB)
具体的には、runtime·morestack_noctxt
関数の定義内で、MOVW $0, R7
命令の直後に続くジャンプ命令が、JMP runtime·morestack(SB)
からB runtime·morestack(SB)
に変更されました。
コアとなるコードの解説
変更された行は、runtime·morestack_noctxt
関数の一部です。
TEXT runtime·morestack_noctxt(SB),NOSPLIT,$-4-0
MOVW $0, R7
B runtime·morestack(SB) // 変更された行
TEXT runtime·morestack_noctxt(SB),NOSPLIT,$-4-0
:runtime·morestack_noctxt
という名前の関数を定義しています。SB
: Static Base。Goのアセンブラにおけるグローバルシンボルを参照するためのベースレジスタのようなものです。NOSPLIT
: この関数がスタックの分割(stack split)を行わないことを示します。つまり、この関数自体がスタックの拡張をトリガーしないことを意味します。$-4-0
: スタックフレームのサイズと引数のサイズを示します。
MOVW $0, R7
: レジスタR7
に値0
を移動(書き込み)します。ARMアーキテクチャでは、R7
はシステムコール番号を保持するために使用されることがありますが、このコンテキストでの正確な目的は、Goランタイムの内部実装に依存します。しかし、この命令自体は今回のコミットの主要な変更点ではありません。B runtime·morestack(SB)
: これが変更された命令です。B
: ARMの無条件分岐命令です。プログラムの実行フローを、指定されたラベル(この場合はruntime·morestack(SB)
)に直接ジャンプさせます。runtime·morestack(SB)
: ジャンプ先のラベルです。これにより、runtime·morestack_noctxt
の実行はruntime·morestack
関数に引き継がれます。
この変更により、runtime·morestack_noctxt
は、より直接的でARMアーキテクチャにネイティブなB
命令を使用してruntime·morestack
にジャンプするようになりました。これにより、GoのアセンブラがJMP
をARM命令に変換する際に発生していた潜在的な問題が回避され、ARMビルドが正しく行われるようになりました。
関連リンク
- Go言語の公式リポジトリ: https://github.com/golang/go
- Go言語のスタック管理に関するドキュメント(Goのバージョンによって内容は異なる可能性があります):
- "Go's Execution Tracer": https://go.dev/blog/go-execution-tracer (スタックトレースやランタイムの挙動に関する一般的な情報)
- "A Tour of Go's Runtime": https://go.dev/blog/go-runtime-tour (ランタイムの概要)
- ARMアーキテクチャのリファレンスマニュアル(ARMの公式ウェブサイトなどで入手可能)
参考にした情報源リンク
- Go言語のソースコード(特に
src/pkg/runtime/asm_arm.s
の履歴) - ARMアーキテクチャのリファレンス(
B
命令と分岐命令に関する情報) - Goのアセンブラ(Plan 9 Assembler)に関するドキュメントや解説記事
- Go言語のコミット履歴と関連するコードレビュー(
https://golang.org/cl/71060046
) - Go言語のメーリングリストやIssueトラッカー(関連する議論やバグレポートがある場合)
- 一般的なアセンブリ言語とコンパイラの原理に関する知識
- Go言語のランタイムに関する技術ブログや論文