[インデックス 18127] ファイルの概要
d2fe44d56834b132f3759cef4bcd5dcc1bfeaa5c
コミット
Author: Dave Cheney dave@cheney.net Date: Sun Dec 29 15:25:34 2013 +1100
runtime: load runtime.goarm as a byte, not a word
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d2fe44d56834b132f3759cef4bcd5dcc1bfeaa5c
元コミット内容
commit d2fe44d56834b132f3759cef4bcd5dcc1bfeaa5c
Author: Dave Cheney <dave@cheney.net>
Date: Sun Dec 29 15:25:34 2013 +1100
runtime: load runtime.goarm as a byte, not a word
Fixes #6952.
runtime.asminit was incorrectly loading runtime.goarm as a word, not a uint8 which made it subject to alignment issues on arm5 platforms.
Alignment aside, this also meant that the top 3 bytes in R11 would have been garbage and could not be assumed to be setting up the FPU reliably.
R=iant, minux.ma
CC=golang-codereviews
https://golang.org/cl/46240043
変更の背景
このコミットは、GoランタイムがARMアーキテクチャ上でruntime.goarm
の値を誤ってロードしていたバグを修正します。具体的には、runtime.asminit
関数がruntime.goarm
をuint8
(1バイト)としてではなく、word
(通常4バイト)としてロードしていました。
この誤ったロードは、特にARMv5プラットフォームにおいて、以下の2つの主要な問題を引き起こしていました。
- アライメント問題:
runtime.goarm
が1バイトの値であるにもかかわらず、4バイトのワードとしてロードされることで、メモリのアライメント(配置)に関する問題が発生する可能性がありました。ARMプロセッサは、特定のデータ型に対して特定のメモリアドレス境界に配置されることを要求する「アライメント」の概念を持っています。特に浮動小数点演算ユニット(FPU)に関連するデータアクセスでは、厳密なアライメントが求められます。誤ったロードは、このアライメント要件を満たさない可能性がありました。 - R11レジスタのゴミデータ:
runtime.goarm
がワードとしてロードされると、その値がR11レジスタに格納されます。しかし、runtime.goarm
は実際には1バイトの値であるため、ロードされたワードの上位3バイトには不定な「ゴミ」データが含まれることになります。このR11レジスタは、FPUのセットアップに関連する処理で使用される可能性があり、ゴミデータが含まれているとFPUが正しく初期化されない、あるいは予期せぬ動作をする原因となっていました。
これらの問題は、GoプログラムがARMv5ベースのデバイスで実行される際に、FPUの動作が不安定になったり、クラッシュしたりする原因となる可能性がありました。このコミットは、これらの問題を解決し、Goランタイムの堅牢性を向上させることを目的としています。
前提知識の解説
GoのGOARM
環境変数とruntime.goarm
GOARM
はGoのビルド時に使用される環境変数で、32ビットARMシステム(GOARCH=arm
)向けのターゲットARMプロセッサのアーキテクチャバージョンと浮動小数点ユニット(FPU)の機能を指定します。runtime.goarm
は、このGOARM
環境変数の値に対応するランタイム内部の変数です。
- 目的: Goコンパイラが、実行される特定のARMハードウェアと互換性のある機械語を生成することを保証します。
- ARMアーキテクチャバージョン: ARMv5、ARMv6、ARMv7などの違いを区別し、それぞれ異なる命令セットと機能に対応します。
- 浮動小数点サポート: 多くの古いARMプロセッサ(ARMv5デバイスなど)はハードウェアFPUを持っていません。
GOARM
は、GoツールチェインがハードウェアFPU命令を使用するか、コンパイルされたバイナリにソフトウェア浮動小数点エミュレータを含めるかを決定するのに役立ちます。例えば、GOARM=5
は通常、ソフトウェア浮動小数点エミュレーションが必要であることを示します。
ARMアーキテクチャにおけるアライメント(Alignment)
アライメントとは、メモリ上のデータが特定のバイト境界に配置されることを指します。ARMプロセッサを含む多くのCPUアーキテクチャでは、効率的なデータアクセスや特定の命令の実行のために、データが特定のメモリアドレスに「アラインされている」必要があります。
- 厳密なFPUアライメント要件: ARMプロセッサでは、特に浮動小数点値のロードおよびストア(VFPハードウェア命令を使用する場合)は、常にアラインされている必要があります。例えば、4バイトの
float
は4の倍数のメモリアドレスに、8バイトのdouble
は8の倍数のメモリアドレスに配置されるべきです。 - アラインされていないFPUアクセスによる影響: 適切にアラインされていない浮動小数点データにアクセスしようとすると、多くの場合、「データアボート」例外(アライメントフォルトステータスコード付き)が発生します。一部の非アライン整数アクセスは、新しいARMハードウェアやLinuxカーネルによって透過的に処理されることがありますが(パフォーマンスペナルティを伴う)、非アライン浮動小数点アクセスは通常カーネルによって処理されず、問題を引き起こします。
- パフォーマンスへの影響: 非アラインアクセスが技術的にサポートされている場合でも、パフォーマンスに大きなコストがかかります。非アライン32ビットアクセスは、アラインアクセスよりも40〜60%遅くなる可能性があり、Cortex-Aシリーズプロセッサでは約2倍の時間がかかることがあります。FPUの場合、このパフォーマンス低下は厳密なアライメント要件によってさらに悪化し、プロセッサがトラップしてソフトウェアでアクセスをエミュレートする必要があるか、単にアボートする可能性があります。
ARMレジスタ (R11)
ARMプロセッサには、汎用レジスタ(R0-R12)、スタックポインタ(SP、R13)、リンクレジスタ(LR、R14)、プログラムカウンタ(PC、R15)などがあります。R11は汎用レジスタの一つであり、特定の用途に予約されているわけではありませんが、コンパイラやアセンブリコードによって様々な目的で使用されます。このコミットの文脈では、runtime.goarm
の値を一時的に保持し、FPUのセットアップロジックで使用されるレジスタとして機能していました。
FPU (Floating-Point Unit)
FPUは、浮動小数点演算(加算、減算、乗算、除算など)を高速に実行するために設計されたプロセッサの特殊なコンポーネントです。FPUがない場合、これらの演算はソフトウェアエミュレーションによって行われますが、これははるかに低速です。FPUの正確な動作と初期化は、浮動小数点演算を伴うプログラムのパフォーマンスと正確性にとって非常に重要です。
技術的詳細
このコミットの核心は、ARMアセンブリコードにおけるメモリロード命令の変更です。
元のコードでは、runtime.goarm
の値をロードするためにMOVW
命令が使用されていました。
MOVW
は「Move Word」の略で、通常32ビット(4バイト)のデータをロードする命令です。
MOVW runtime·goarm(SB), R11
しかし、runtime.goarm
は実際にはuint8
、つまり1バイトのデータとして定義されています。MOVW
命令を使って1バイトのデータをロードしようとすると、プロセッサは指定されたアドレスから4バイトを読み込もうとします。このとき、runtime.goarm
が配置されているアドレスの次の3バイトには、意図しないデータ(ゴミデータ)が含まれている可能性があります。この4バイトがR11レジスタに格納されると、R11レジスタの上位3バイトはゴミデータで埋められてしまいます。
ARMv5のような古いARMプラットフォームでは、アライメントされていないワードアクセスは、ハードウェアによってはサポートされていなかったり、パフォーマンスが著しく低下したり、あるいは「データアボート」例外を引き起こす可能性がありました。たとえ例外が発生しなくても、R11レジスタにゴミデータが含まれることで、その後のFPU設定ロジック(CMP $5, R11
やvmrs r11, fpscr
など)が意図した通りに動作しないリスクがありました。特にvmrs r11, fpscr
命令は、FPUの状態レジスタ(FPSCR)の値をR11にロードするものであり、その後のFPU設定にR11の値が使われることを示唆しています。R11にゴミデータが含まれていると、FPUの初期設定が不安定になる可能性がありました。
このコミットでは、MOVW
命令をMOVB
命令に変更しています。
MOVB
は「Move Byte」の略で、1バイトのデータをロードする命令です。
MOVB runtime·goarm(SB), R11
MOVB
命令を使用することで、プロセッサはruntime.goarm
が配置されているアドレスから正確に1バイトのデータのみを読み込みます。これにより、R11レジスタにはruntime.goarm
の正しい値のみが格納され、上位バイトにゴミデータが含まれることはなくなります。結果として、アライメントの問題が回避され、FPUのセットアップが信頼性の高いものになります。
この修正は、GoランタイムがARMv5を含む様々なARMプラットフォームで、FPUを正しく、かつ安定して初期化するために不可欠でした。
コアとなるコードの変更箇所
--- a/src/pkg/runtime/asm_arm.s
+++ b/src/pkg/runtime/asm_arm.s
@@ -91,7 +91,7 @@ TEXT runtime·breakpoint(SB),NOSPLIT,$0-0
TEXT runtime·asminit(SB),NOSPLIT,$0-0
// disable runfast (flush-to-zero) mode of vfp if runtime.goarm > 5
- MOVW runtime·goarm(SB), R11
+ MOVB runtime·goarm(SB), R11
CMP $5, R11
BLE 4(PC)
WORD $0xeef1ba10 // vmrs r11, fpscr
コアとなるコードの解説
変更はsrc/pkg/runtime/asm_arm.s
ファイル内のruntime·asminit
関数にあります。
元のコード:
MOVW runtime·goarm(SB), R11
この行は、runtime.goarm
というシンボル(メモリ上のアドレス)から「ワード」(4バイト)を読み込み、それをR11レジスタに格納していました。前述の通り、runtime.goarm
が1バイトのデータであるため、これは誤った操作でした。
修正後のコード:
MOVB runtime·goarm(SB), R11
この行は、runtime.goarm
というシンボルから「バイト」(1バイト)を読み込み、それをR11レジスタに格納します。これにより、R11レジスタにはruntime.goarm
の正確な値のみが格納され、上位バイトにゴミデータが含まれることがなくなります。
この変更により、runtime.goarm
の値が正しくR11レジスタにロードされ、その後のCMP $5, R11
(R11の値が5より大きいか比較)やvmrs r11, fpscr
(FPUの状態レジスタをR11にロード)といったFPU関連の命令が、意図した通りに、かつ信頼性高く実行されるようになります。特に、runtime.goarm > 5
の場合にVFP(Vector Floating-Point)のrunfast
(flush-to-zero)モードを無効にするロジックが、正確なruntime.goarm
の値に基づいて機能するようになります。
関連リンク
- Go Issue #6952: コミットメッセージには
Fixes #6952
と記載されていますが、公開されているGoのissueトラッカーではこの番号のissueは見つかりませんでした。これは内部的なissue番号であるか、あるいは既にクローズされた古いissueである可能性があります。 - Go CL 46240043: コミットメッセージには
https://golang.org/cl/46240043
と記載されていますが、このCLはgo mod tidy -compat=1.17
に関する別の修正(go.mod
ファイルにgo
行がない場合のパニック修正)を指しており、このコミットの内容とは直接関連がありません。コミットメッセージ内のCLリンクが誤っている可能性があります。