[インデックス 16420] ファイルの概要
このコミットは、Go言語のランタイムにおけるARMアセンブリコードの変更に関するものです。具体的には、ARMアーキテクチャ向けのFreeBSD、Linux、NetBSDのシステムコールラッパーやシグナルハンドラに関連するアセンブリファイルにおいて、Goランタイムの内部構造であるm(マシン)とg(ゴルーチン)を表すために、汎用レジスタR9とR10を直接使用する代わりに、より意味的なシンボルmとgを用いるように変更しています。また、クラッシュ時のレジスタの扱いについても改善が加えられています。
コミット
commit d8fd8d89ea071c79788b34eddf31858c0e66c19b
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Tue May 28 20:13:02 2013 +0800
runtime: use m and g, instead of R9 and R10, in ARM assembly files
also don't clobber R9 if it is about to crash.
In response to https://golang.org/cl/9251043/#msg2.
R=golang-dev, khr, khr, dave
CC=golang-dev
https://golang.org/cl/9778046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d8fd8d89ea071c79788b34eddf31858c0e66c19b
元コミット内容
runtime: use m and g, instead of R9 and R10, in ARM assembly files
also don't clobber R9 if it is about to crash.
In response to https://golang.org/cl/9251043/#msg2.
変更の背景
この変更の背景には、GoランタイムのARMアセンブリコードの可読性と堅牢性の向上が挙げられます。
-
可読性の向上: Goランタイムは、そのスケジューラやメモリ管理のために、
m(マシン、OSスレッドに対応)とg(ゴルーチン)という重要な内部構造を使用します。アセンブリコード内でこれらの構造体へのポインタを特定の汎用レジスタ(この場合はR9とR10)に割り当てて使用することは一般的ですが、コードを読んだ際にどのレジスタがどの役割を担っているのかを常に記憶しておく必要があります。mやgといった意味的なシンボルを直接使用することで、コードの意図がより明確になり、デバッグやメンテナンスが容易になります。これは、Goのアセンブリ言語(Plan 9アセンブラ)が提供する機能の一つであり、レジスタにシンボリックな名前を割り当てて使用できる特性を活用したものです。 -
クラッシュ時のレジスタの保護: コミットメッセージの「also don't clobber R9 if it is about to crash.」という記述は、システムがクラッシュする可能性がある状況(例えば、システムコールが失敗した場合など)において、
R9レジスタの内容が意図せず上書きされることを防ぐための変更を示唆しています。R9がm(マシン)へのポインタとして使用されていた場合、クラッシュ時にその情報が破壊されると、デバッグ情報の取得やクラッシュダンプの解析が困難になる可能性があります。この変更では、クラッシュ処理に関連する箇所でR9の代わりにR8を使用することで、重要なmポインタの値を保護し、より有用なクラッシュ情報を提供できるようにしています。 -
レビューコメントへの対応: コミットメッセージにある
In response to https://golang.org/cl/9251043/#msg2.は、以前の変更セット(CL: Change List)に対するレビューコメントを受けての修正であることを示しています。これは、Goコミュニティにおけるコードレビュープロセスが活発に行われ、そのフィードバックがコード品質の向上に繋がっていることを示しています。
前提知識の解説
このコミットを理解するためには、以下の概念についての知識が必要です。
1. Goランタイムの基本
- ゴルーチン (Goroutine): Go言語の軽量な並行処理単位です。OSスレッドよりもはるかに軽量で、数百万のゴルーチンを同時に実行できます。Goランタイムがゴルーチンのスケジューリングを担当します。
- M (Machine): OSスレッドを表します。Goランタイムは、OSスレッド上でゴルーチンを実行します。Mは、OSスレッドのスタック、レジスタ、および現在のゴルーチン(G)へのポインタなどの情報を持っています。
- G (Goroutine): ゴルーチンを表す構造体です。ゴルーチンのスタック、現在の実行状態、および関連するMへのポインタなどの情報を持っています。
- P (Processor): 論理プロセッサを表します。MとGの間に位置し、MがGを実行するためのコンテキストを提供します。Pは、実行可能なゴルーチンのキューを保持し、MがPからゴルーチンを取得して実行します。
Goランタイムのスケジューラは、M、G、Pの3つのエンティティを組み合わせて動作します。MはOSスレッドであり、PはMがゴルーチンを実行するためのリソースを提供し、Gは実行されるゴルーチンそのものです。
2. ARMアセンブリ言語の基本
ARMアーキテクチャは、モバイルデバイスや組み込みシステムで広く使用されているRISC(Reduced Instruction Set Computer)アーキテクチャです。
- レジスタ: ARMプロセッサには、汎用レジスタ(R0-R12)、スタックポインタ(SPまたはR13)、リンクレジスタ(LRまたはR14)、プログラムカウンタ(PCまたはR15)などがあります。
- R0-R3: 関数呼び出しの引数や戻り値に使用されることが多いです。
- R4-R11: 汎用レジスタとして使用されます。
- R9: 通常は汎用レジスタですが、一部のABI(Application Binary Interface)では「プラットフォームレジスタ」として特別な用途に割り当てられることがあります。Goランタイムでは、
m(マシン)へのポインタを保持するために慣習的に使用されていました。 - R10: 汎用レジスタ。Goランタイムでは、
g(ゴルーチン)へのポインタを保持するために慣習的に使用されていました。
- 命令:
MOVW Rd, Rn: Rnの値をRdに移動します。BL label:labelに分岐し、現在のPCの次の命令のアドレスをLR(リンクレジスタ)に保存します(関数呼び出しに使用)。RET: LRに保存されたアドレスに分岐します(関数からの戻りに使用)。SWI #imm: ソフトウェア割り込み(System Call)を発生させます。#immはシステムコール番号です。MOVW.CS Rd, Rn: 条件付き実行命令。C(Carry)フラグがセットされている場合(通常、システムコールがエラーを返した場合)にのみ実行されます。MOVW.HI Rd, Rn: 条件付き実行命令。HI(Higher)フラグがセットされている場合(通常、比較結果が「より大きい」場合)にのみ実行されます。CMP Rn, Rm: RnとRmの値を比較し、CPSR(Current Program Status Register)のフラグを更新します。
- Plan 9アセンブラ: Go言語は、独自のツールチェーンとアセンブラ(Plan 9アセンブラ)を使用します。このアセンブラは、一般的なGAS(GNU Assembler)とは異なる構文を持ち、レジスタにシンボリックな名前を割り当てて使用できるなどの特徴があります。例えば、
mやgといったシンボルをレジスタのエイリアスとして定義し、アセンブリコード内で直接使用することができます。
3. システムコールとシグナルハンドラ
- システムコール: ユーザー空間のプログラムがカーネルの機能(ファイルI/O、メモリ管理、プロセス管理など)を利用するためのインターフェースです。ARMでは通常、
SWI命令を使用してシステムコールを呼び出します。 - シグナルハンドラ: プロセスにシグナル(イベント)が送信されたときに実行される特別な関数です。例えば、セグメンテーション違反(SIGSEGV)やCtrl+C(SIGINT)などがシグナルです。シグナルハンドラは、非同期的に実行されるため、レジスタの状態保存や復元、スタックの切り替えなど、慎重なプログラミングが必要です。
技術的詳細
このコミットの技術的な詳細は、GoランタイムがARMアセンブリレベルでどのようにmとgを管理し、システムコールやシグナルハンドリングを行うかに関わっています。
1. mとgのシンボリックな使用
変更前は、GoランタイムのARMアセンブリコードでは、m(マシン)へのポインタをR9レジスタに、g(ゴルーチン)へのポインタをR10レジスタに格納するという慣習がありました。このコミットでは、これらのレジスタを直接参照する代わりに、Plan 9アセンブラの機能を利用して、mとgというシンボリックな名前を導入しています。
例えば、src/pkg/runtime/sys_freebsd_arm.sのruntime·thr_start関数では、以下の変更が行われています。
変更前:
MOVW R0, R9 // m
// ...
MOVW m_g0(R9), R10
変更後:
MOVW R0, m
// ...
MOVW m_g0(m), g
これにより、コードの意図が「R0の値をR9に移動する」から「R0の値をm(マシンポインタ)に移動する」へと明確になります。同様に、m_g0(m)は「mが指す構造体のg0フィールドからg(ゴルーチンポインタ)を取得する」という意味になります。これは、Goランタイムの内部構造とアセンブリコードの間のマッピングをより直接的に表現し、コードの理解を深めます。
2. クラッシュ時のレジスタ保護
コミットメッセージの「also don't clobber R9 if it is about to crash.」は、システムコールが失敗した場合など、エラーパスでR9レジスタが上書きされるのを防ぐための変更です。
多くのシステムコールラッパーでは、システムコールが失敗した場合に、特定のレジスタ(この場合はR9)に無効な値を書き込み、そのアドレスにアクセスすることで意図的にクラッシュさせるコードパターンが見られます。これは、システムコールが予期せぬエラーを返した場合に、プログラムが不正な状態になるのを防ぎ、早期にクラッシュさせることでデバッグを容易にするための手法です。
例えば、src/pkg/runtime/sys_freebsd_arm.sのruntime·exit関数では、以下の変更が行われています。
変更前:
SWI $1
MOVW.CS $0, R9 // crash on syscall failure
MOVW.CS R9, (R9)
変更後:
SWI $1
MOVW.CS $0, R8 // crash on syscall failure
MOVW.CS R8, (R8)
この変更により、システムコールが失敗してC(Carry)フラグがセットされた場合(MOVW.CS)、R9ではなくR8に0を書き込み、そのアドレスにアクセスしてクラッシュさせます。これにより、R9に格納されているm(マシン)へのポインタがクラッシュ処理によって破壊されることを防ぎ、クラッシュダンプやデバッグ時にmの情報を利用できるようになります。これは、Goランタイムの安定性とデバッグ可能性を向上させるための重要な変更です。
3. シグナルハンドラにおけるgの保存と復元
runtime·sigtramp(シグナルハンドラのトランポリン関数)では、シグナル処理中に現在のゴルーチン(g)を一時的に保存し、シグナルハンドラが終了した後に復元する処理が行われます。このコミットでは、ここでもR10の代わりにgシンボルを使用しています。
変更前:
// save g
MOVW R10, R4
MOVW R10, 20(R13)
// g = m->signal
MOVW m_gsignal(R9), R10
// ...
// restore g
MOVW 20(R13), R10
変更後:
// save g
MOVW g, R4
MOVW g, 20(R13)
// g = m->signal
MOVW m_gsignal(m), g
// ...
// restore g
MOVW 20(R13), g
この変更は、シグナルハンドラが実行される際に、現在のゴルーチンコンテキストを正しく保存・復元するために、gシンボルを使用することでコードの意図を明確にしています。m_gsignal(m)は、mが指す構造体のgsignalフィールド(シグナル処理用の特別なゴルーチン)からgを取得することを意味します。
コアとなるコードの変更箇所
このコミットのコアとなる変更は、src/pkg/runtime/sys_freebsd_arm.s、src/pkg/runtime/sys_linux_arm.s、src/pkg/runtime/sys_netbsd_arm.sの3つのファイルにおける以下のパターンです。
-
R9をmに、R10をgに置き換え:MOVW R0, R9->MOVW R0, mMOVW m_g0(R9), R10->MOVW m_g0(m), gMOVW R10, R4->MOVW g, R4MOVW m_gsignal(R9), R10->MOVW m_gsignal(m), gMOVW 20(R13), R10->MOVW 20(R13), g
-
クラッシュ処理における
R9をR8に置き換え:MOVW $2, R9->MOVW $2, R8MOVW R9, (R9)->MOVW R8, (R8)MOVW.CS $0, R9->MOVW.CS $0, R8MOVW.CS R9, (R9)->MOVW.CS R8, (R8)MOVW.HI $0, R9->MOVW.HI $0, R8MOVW.HI R9, (R9)->MOVW.HI R8, (R8)
これらの変更は、GoランタイムのARMアセンブリコード全体にわたって適用されており、一貫性と可読性を向上させています。
コアとなるコードの解説
Goのランタイムは、OSスレッド(M)上でゴルーチン(G)をスケジューリングして実行します。このプロセスでは、MとGのポインタを効率的にアクセスできる必要があります。ARMアセンブリでは、特定のレジスタをこれらのポインタのために予約することが一般的でした。
-
mとgシンボルの導入: GoのPlan 9アセンブラでは、TEXTディレクティブの後に続く関数定義内で、レジスタにシンボリックな名前を割り当てることができます。このコミットでは、mとgというシンボルが、それぞれR9とR10レジスタのエイリアスとして定義されたと推測されます(Goのソースコード全体を見ないと正確な定義箇所は特定できませんが、アセンブリコードの変更内容からそのように解釈できます)。これにより、アセンブリコードの各行が、単なるレジスタ操作ではなく、Goランタイムのmやgといった概念を直接操作していることを明確に示します。これは、コードの自己文書化能力を高め、Goランタイムの内部動作を理解する上で非常に役立ちます。 -
クラッシュ処理の改善: Goランタイムは、システムコールが失敗した場合など、回復不可能なエラーが発生した際に、意図的にパニック(クラッシュ)を引き起こすことがあります。これは、プログラムが不正な状態のまま実行を続けるよりも、早期に終了して問題の根本原因を特定しやすくするためです。 変更前のコードでは、このクラッシュ処理で
R9レジスタ(mポインタが格納されている可能性のあるレジスタ)を上書きしていました。もしR9がmポインタを保持していた場合、クラッシュ時にその情報が失われ、デバッグが困難になる可能性があります。 このコミットでは、クラッシュ処理に使用するレジスタをR9からR8に変更することで、mポインタの値を保護しています。これにより、クラッシュダンプやデバッガでmの情報を参照できるようになり、問題の診断が容易になります。これは、Goランタイムの堅牢性とデバッグサポートの向上に貢献します。
これらの変更は、GoランタイムのARMアセンブリコードの品質を向上させ、開発者がより効率的にデバッグやメンテナンスを行えるようにするためのものです。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/
- Goランタイムのソースコード(GitHub): https://github.com/golang/go/tree/master/src/runtime
- GoのPlan 9アセンブラに関するドキュメント: https://go.dev/doc/asm
- ARMアーキテクチャのリファレンス(ARM社のウェブサイトなど)
参考にした情報源リンク
- https://golang.org/cl/9251043/#msg2 (コミットメッセージに記載されているレビューコメントへのリンク)
- https://golang.org/cl/9778046 (コミットメッセージに記載されているChange-Idへのリンク)
- Go言語のソースコード(特に
src/pkg/runtime/以下のARMアセンブリファイル) - ARMアーキテクチャに関する一般的な情報源(例: Wikipedia, ARM社の公式ドキュメント)
- Goランタイムのスケジューラに関する解説記事やドキュメント
- Plan 9アセンブラの構文と使用法に関する情報