[インデックス 13763] ファイルの概要
このコミットは、Go言語のランタイムがARMアーキテクチャ上で動作する際に、ハードウェア浮動小数点ユニットの有無をチェックし、もし不足している場合はプログラムの実行を中止するように変更を加えるものです。具体的には、GOARM=7
でコンパイルされたバイナリが、浮動小数点ハードウェアを持たないARMv5などの古いCPUで実行された場合に、適切なエラーメッセージを出力して終了するようになります。
コミット
commit 212ce41d004cc9e33d35b64cc13c2c9baf843452
Author: Dave Cheney <dave@cheney.net>
Date: Fri Sep 7 14:26:42 2012 +1000
runtime: arm: abort if hardware floating point missing
Fixes #3911.
Requires CL 6449127.
dfc@qnap:~$ ./runtime.test
runtime: this CPU has no floating point hardware, so it cannot run
this GOARM=7 binary. Recompile using GOARM=5.
R=rsc, minux.ma
CC=golang-dev
https://golang.org/cl/6442109
---
src/pkg/runtime/asm_arm.s | 1 +
src/pkg/runtime/signal_linux_arm.c | 16 ++++++++++++++--
2 files changed, 15 insertions(+), 2 deletions(-)
diff --git a/src/pkg/runtime/asm_arm.s b/src/pkg/runtime/asm_arm.s
index 2c89139805..57df8c9c63 100644
--- a/src/pkg/runtime/asm_arm.s
+++ b/src/pkg/runtime/asm_arm.s
@@ -37,6 +37,7 @@ TEXT _rt0_arm(SB),7,$-4
MOVW.NE g, R0 // first argument of initcgo is g
BL.NE (R2) // will clobber R0-R3
+ BL runtime·checkgoarm(SB)
BL runtime·check(SB)
// saved argc, argv
diff --git a/src/pkg/runtime/signal_linux_arm.c b/src/pkg/runtime/signal_linux_arm.c
index c35d139b27..7f93db5fb0 100644
--- a/src/pkg/runtime/signal_linux_arm.c
+++ b/src/pkg/runtime/signal_linux_arm.c
@@ -147,9 +147,21 @@ runtime·setsig(int32 i, void (*fn)(int32, Siginfo*, void*, G*), bool restart)\n #define AT_PLATFORM 15 // introduced in at least 2.6.11
#define AT_HWCAP 16 // introduced in at least 2.6.11
#define AT_RANDOM 25 // introduced in 2.6.29
+#define HWCAP_VFP (1 << 6)\n static uint32 runtime·randomNumber;\n-uint32 runtime·hwcap;\n-uint8 runtime·armArch = 6; // we default to ARMv6\n+uint8 runtime·armArch = 6;\t// we default to ARMv6\n+uint32 runtime·hwcap;\t// set by setup_auxv\n+uint8 runtime·goarm;\t// set by 5l\n+\n+void\n+runtime·checkgoarm(void)\n+{\n+\tif(runtime·goarm > 5 && !(runtime·hwcap & HWCAP_VFP)) {\n+\t\truntime·printf(\"runtime: this CPU has no floating point hardware, so it cannot run\\n\");\n+\t\truntime·printf(\"this GOARM=%d binary. Recompile using GOARM=5.\\n\", runtime·goarm);\n+\t\truntime·exit(1);\n+\t}\n+}\n \n #pragma textflag 7\n void
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/212ce41d004cc9e33d35b64cc13c2c9baf843452
元コミット内容
このコミットは、GoランタイムがARMアーキテクチャ上で動作する際に、ハードウェア浮動小数点ユニットが利用可能かどうかをチェックし、もし利用できない場合はプログラムの実行を中止するというものです。これは、特定のGOARM
設定でコンパイルされたバイナリが、その設定が要求するハードウェア機能をサポートしないCPU上で実行された場合に発生する問題を解決することを目的としています。
コミットメッセージには、以下の情報が含まれています。
- タイトル:
runtime: arm: abort if hardware floating point missing
(ランタイム: arm: ハードウェア浮動小数点がない場合、中止する) - 関連するIssue:
Fixes #3911.
(Issue 3911を修正) - 前提となる変更リスト (CL):
Requires CL 6449127.
(CL 6449127が必要) - 実行例:
dfc@qnap:~$ ./runtime.test
を実行した際に、ハードウェア浮動小数点がないCPUでGOARM=7
のバイナリを実行した場合に表示されるエラーメッセージの例が示されています。
(ランタイム: このCPUには浮動小数点ハードウェアがないため、このGOARM=7バイナリを実行できません。GOARM=5を使用して再コンパイルしてください。)runtime: this CPU has no floating point hardware, so it cannot run this GOARM=7 binary. Recompile using GOARM=5.
- レビュー担当者:
R=rsc, minux.ma
- CC:
golang-dev
- 関連するGo Change List (CL):
https://golang.org/cl/6442109
変更の背景
この変更の背景には、Go言語がARMアーキテクチャをサポートする上で直面した互換性の問題があります。ARMアーキテクチャには、様々なバージョンと機能セットが存在します。特に、浮動小数点演算のサポートは、CPUのバージョンや実装によって異なります。
- ARMv5: 浮動小数点ハードウェアを標準では持たず、ソフトウェアエミュレーションに頼ることが多い。
- ARMv6: VFPv2 (Vector Floating Point Unit version 2) をオプションでサポート。
- ARMv7: VFPv3/v4を標準でサポートし、NEON (Advanced SIMD) 拡張も利用可能。
Go言語のコンパイラは、GOARM
環境変数によって、ターゲットとするARMアーキテクチャのバージョンと、それに伴う浮動小数点サポートの有無を決定します。
GOARM=5
: 浮動小数点ハードウェアを期待せず、ソフトウェア浮動小数点を使用するバイナリを生成。GOARM=6
: ARMv6以降をターゲットとし、VFPv2を期待するバイナリを生成。GOARM=7
: ARMv7以降をターゲットとし、VFPv3/v4を期待するバイナリを生成。
問題は、GOARM=7
(またはGOARM=6
)でコンパイルされたバイナリが、実際には浮動小数点ハードウェアを持たない古いARMv5などのCPUで実行された場合に発生しました。このような場合、バイナリはハードウェア浮動小数点命令を実行しようとしますが、CPUがそれをサポートしていないため、不正な命令例外やクラッシュを引き起こす可能性がありました。
このコミットは、このような互換性の問題を事前に検出し、ユーザーに適切なフィードバック(再コンパイルの推奨)を提供することで、より堅牢なGoアプリケーションのデプロイを可能にすることを目的としています。コミットメッセージに記載されているFixes #3911
は、この問題がGoのIssueトラッカーで報告されていたことを示しています。
前提知識の解説
このコミットを理解するためには、以下の技術的な概念を把握しておく必要があります。
1. ARMアーキテクチャと浮動小数点ユニット (FPU)
ARM (Advanced RISC Machine) は、モバイルデバイスや組み込みシステムで広く使用されているCPUアーキテクチャです。ARMアーキテクチャには多くのバージョンがあり、それぞれ異なる機能セットと性能特性を持っています。
- ARMv5: 古いアーキテクチャで、通常はハードウェア浮動小数点ユニットを持たず、浮動小数点演算はソフトウェアでエミュレートされます。これは性能に大きな影響を与えます。
- ARMv6: ARMv5の後継で、オプションでVFPv2 (Vector Floating Point Unit version 2) をサポートします。VFPは、単精度および倍精度の浮動小数点演算をハードウェアで高速化するためのコプロセッサです。
- ARMv7: 現在でも広く使われているアーキテクチャで、VFPv3またはVFPv4を標準でサポートします。また、NEONと呼ばれるSIMD (Single Instruction, Multiple Data) 拡張も導入され、マルチメディア処理や信号処理の性能が向上しました。
2. GOARM
環境変数
Go言語のコンパイラは、GOARM
環境変数を使用して、ターゲットとするARMプロセッサのバージョンと、それに伴う浮動小数点サポートの有無を決定します。
GOARM=5
: ARMv5互換のバイナリを生成します。これはハードウェア浮動小数点ユニットを期待せず、ソフトウェア浮動小数点を使用します。GOARM=6
: ARMv6互換のバイナリを生成します。これはVFPv2を期待し、ハードウェア浮動小数点を使用します。GOARM=7
: ARMv7互換のバイナリを生成します。これはVFPv3/v4を期待し、ハードウェア浮動小数点を使用します。
GOARM
の値と実際のCPUの機能が一致しない場合、問題が発生します。特に、GOARM=6
やGOARM=7
でコンパイルされたバイナリを、ハードウェア浮動小数点ユニットを持たないCPUで実行しようとすると、実行時エラーやクラッシュにつながります。
3. AT_HWCAP
と HWCAP_VFP
(LinuxのAuxiliary Vector)
Linuxカーネルは、プロセスの起動時に、実行環境に関する様々な情報を「Auxiliary Vector (補助ベクトル)」と呼ばれるデータ構造を通じてユーザー空間に渡します。この情報には、CPUの機能やシステム設定などが含まれます。
AT_HWCAP
: Auxiliary Vectorのエントリの一つで、CPUのハードウェア機能を示すビットマスクが含まれています。HWCAP_VFP
:AT_HWCAP
ビットマスク内で、VFP (Vector Floating Point) ユニットの存在を示す特定のビットです。このビットがセットされていれば、CPUはハードウェア浮動小数点ユニットを搭載していることを意味します。
Goランタイムは、setup_auxv
のような内部関数を通じて、このAuxiliary VectorからAT_HWCAP
の情報を読み取り、CPUのハードウェア機能を検出します。
4. Goランタイムの初期化 (_rt0_arm
)
Goプログラムが起動する際、まずランタイムの初期化コードが実行されます。ARMアーキテクチャの場合、この初期化は通常、アセンブリ言語で書かれた_rt0_arm
関数から始まります。この関数は、スタックの設定、引数の処理、そしてGoランタイムの主要な初期化ルーチンへのジャンプなど、低レベルのセットアップを行います。このコミットでは、この初期化プロセスの非常に早い段階でハードウェア浮動小数点ユニットのチェックが追加されています。
技術的詳細
このコミットの技術的な詳細を掘り下げていきます。
1. runtime·checkgoarm
関数の導入
コミットの主要な変更点は、src/pkg/runtime/signal_linux_arm.c
に runtime·checkgoarm
という新しいC関数が追加されたことです。この関数は、Goバイナリが期待するGOARM
レベルと、実行中のCPUが実際に持つハードウェア浮動小数点機能とを比較します。
void
runtime·checkgoarm(void)
{
if(runtime·goarm > 5 && !(runtime·hwcap & HWCAP_VFP)) {
runtime·printf("runtime: this CPU has no floating point hardware, so it cannot run\\n");
runtime·printf("this GOARM=%d binary. Recompile using GOARM=5.\\n", runtime·goarm);
runtime·exit(1);
}
}
runtime·goarm
: これは、コンパイル時にGoリンカ (5l
) によって設定される変数で、バイナリがターゲットとするGOARM
のバージョンを示します。runtime·hwcap
: これは、LinuxカーネルのAuxiliary Vectorから読み取られたCPUのハードウェア機能を示すビットマスクです。setup_auxv
のようなランタイム内部の関数によって設定されます。HWCAP_VFP
:(1 << 6)
と定義されており、runtime·hwcap
内でVFPユニットの存在を示すビットです。
このif
文の条件 runtime·goarm > 5 && !(runtime·hwcap & HWCAP_VFP)
は、以下の状況を検出します。
runtime·goarm > 5
: バイナリがGOARM=6
またはGOARM=7
でコンパイルされていることを意味します。これらのバージョンはハードウェア浮動小数点ユニットを期待します。!(runtime·hwcap & HWCAP_VFP)
: 実行中のCPUがHWCAP_VFP
ビットを持たない、つまりハードウェア浮動小数点ユニットを搭載していないことを意味します。
この両方の条件が真である場合、Goバイナリはハードウェア浮動小数点命令を実行しようとしますが、CPUがそれをサポートしていないため、実行を継続できません。
2. エラーメッセージと終了
条件が満たされた場合、runtime·checkgoarm
は以下のエラーメッセージを標準エラー出力に表示します。
runtime: this CPU has no floating point hardware, so it cannot run
this GOARM=%d binary. Recompile using GOARM=5.
このメッセージは非常に具体的で、ユーザーに問題の原因(ハードウェア浮動小数点ユニットの欠如)と解決策(GOARM=5
での再コンパイル)を明確に伝えます。メッセージ出力後、runtime·exit(1)
が呼び出され、プログラムはエラーコード1で終了します。これにより、不正な命令によるクラッシュを防ぎ、よりユーザーフレンドリーなエラーハンドリングを提供します。
3. _rt0_arm
からの呼び出し
runtime·checkgoarm
関数は、src/pkg/runtime/asm_arm.s
の _rt0_arm
アセンブリ関数から呼び出されます。
TEXT _rt0_arm(SB),7,$-4
MOVW.NE g, R0 // first argument of initcgo is g
BL.NE (R2) // will clobber R0-R3
BL runtime·checkgoarm(SB)
BL runtime·check(SB)
_rt0_arm
はGoプログラムのエントリポイントの一つであり、ランタイムの初期化の非常に早い段階で実行されます。BL runtime·checkgoarm(SB)
命令は、runtime·checkgoarm
関数を呼び出します。この配置により、Goプログラムが本格的な実行を開始する前に、必要なハードウェア機能のチェックが行われることが保証されます。これにより、浮動小数点命令が実行される前に互換性の問題が検出され、早期に終了することができます。
4. 変数の追加と初期化
src/pkg/runtime/signal_linux_arm.c
には、runtime·hwcap
と runtime·goarm
という2つの新しいグローバル変数が追加されています。
uint32 runtime·hwcap; // set by setup_auxv
uint8 runtime·goarm; // set by 5l
runtime·hwcap
: CPUのハードウェア機能ビットマスクを保持します。コメントにあるように、setup_auxv
関数(Auxiliary Vectorを解析するランタイム内部関数)によって設定されます。runtime·goarm
: コンパイル時にリンカ(5l
)によって設定されるGOARM
の値を保持します。
これらの変数は、runtime·checkgoarm
関数が実行時に必要な情報を取得するために使用されます。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は以下の2つのファイルです。
-
src/pkg/runtime/asm_arm.s
:_rt0_arm
関数の内部に、新しく追加されたruntime·checkgoarm
関数を呼び出すアセンブリ命令BL runtime·checkgoarm(SB)
が追加されました。これは、Goプログラムの初期化シーケンスの非常に早い段階でハードウェアチェックが実行されることを保証します。
-
src/pkg/runtime/signal_linux_arm.c
:HWCAP_VFP
マクロが定義されました。これは、LinuxのAuxiliary Vectorにおけるハードウェア機能ビットマスクで、VFP (Vector Floating Point) ユニットの存在を示すビットです。runtime·hwcap
(uint32) とruntime·goarm
(uint8) という2つのグローバル変数が追加されました。これらはそれぞれ、CPUのハードウェア機能と、コンパイル時のGOARM
設定を保持します。runtime·checkgoarm
という新しいC関数が実装されました。この関数は、runtime·goarm
の値が5より大きい(つまり、ハードウェア浮動小数点が必要なバイナリである)にもかかわらず、runtime·hwcap
にHWCAP_VFP
ビットがセットされていない(つまり、ハードウェア浮動小数点ユニットがない)場合に、エラーメッセージを出力してプログラムを終了させます。
コアとなるコードの解説
src/pkg/runtime/asm_arm.s
の変更
--- a/src/pkg/runtime/asm_arm.s
+++ b/src/pkg/runtime/asm_arm.s
@@ -37,6 +37,7 @@ TEXT _rt0_arm(SB),7,$-4
MOVW.NE g, R0 // first argument of initcgo is g
BL.NE (R2) // will clobber R0-R3
+ BL runtime·checkgoarm(SB)
BL runtime·check(SB)
// saved argc, argv
この変更は、GoランタイムのARMアーキテクチャ向けエントリポイントである_rt0_arm
アセンブリ関数に、runtime·checkgoarm
関数への呼び出しを追加しています。
TEXT _rt0_arm(SB),7,$-4
:_rt0_arm
関数の定義。SB
はStatic Baseレジスタを示し、7
はスタックフレームのサイズ、$-4
は引数のオフセットを示します。BL runtime·checkgoarm(SB)
:BL
(Branch with Link) 命令は、指定されたアドレス(ここではruntime·checkgoarm
関数のアドレス)にジャンプし、現在のプログラムカウンタ(PC)の次の命令のアドレスをリンクレジスタ(LR)に保存します。これにより、runtime·checkgoarm
の実行が完了した後に、元の呼び出し元に戻ることができます。
この変更により、Goプログラムが起動し、基本的なレジスタとスタックが設定された直後に、ハードウェア浮動小数点ユニットのチェックが実行されるようになります。これは、浮動小数点命令が実際に使用される前に互換性の問題を検出するための重要なステップです。
src/pkg/runtime/signal_linux_arm.c
の変更
--- a/src/pkg/runtime/signal_linux_arm.c
+++ b/src/pkg/runtime/signal_linux_arm.c
@@ -147,9 +147,21 @@ runtime·setsig(int32 i, void (*fn)(int32, Siginfo*, void*, G*), bool restart)\n #define AT_PLATFORM 15 // introduced in at least 2.6.11
#define AT_HWCAP 16 // introduced in at least 2.6.11
#define AT_RANDOM 25 // introduced in 2.6.29
+#define HWCAP_VFP (1 << 6)\n static uint32 runtime·randomNumber;\n-uint32 runtime·hwcap;\n-uint8 runtime·armArch = 6; // we default to ARMv6\n+uint8 runtime·armArch = 6;\t// we default to ARMv6\n+uint32 runtime·hwcap;\t// set by setup_auxv\n+uint8 runtime·goarm;\t// set by 5l\n+\n+void\n+runtime·checkgoarm(void)\n+{\n+\tif(runtime·goarm > 5 && !(runtime·hwcap & HWCAP_VFP)) {\n+\t\truntime·printf(\"runtime: this CPU has no floating point hardware, so it cannot run\\n\");\n+\t\truntime·printf(\"this GOARM=%d binary. Recompile using GOARM=5.\\n\", runtime·goarm);\n+\t\truntime·exit(1);\n+\t}\n+}\n \n #pragma textflag 7\n void
このファイルでは、主に以下の3つの変更が行われています。
-
HWCAP_VFP
の定義:#define HWCAP_VFP (1 << 6)
これは、LinuxのAuxiliary VectorでCPUのハードウェア機能を示す
AT_HWCAP
ビットマスク内で、VFP (Vector Floating Point) ユニットの存在を示すビットを定義しています。ビット6がVFPに対応します。 -
新しいグローバル変数の追加:
uint32 runtime·hwcap; // set by setup_auxv uint8 runtime·goarm; // set by 5l
runtime·hwcap
: 実行中のCPUがサポートするハードウェア機能のビットマスクを格納するための変数です。この値は、ランタイムの初期化中にsetup_auxv
関数によって、Linuxカーネルから提供されるAuxiliary VectorのAT_HWCAP
エントリから読み取られます。runtime·goarm
: コンパイル時にGoリンカ(5l
)によって設定されるGOARM
環境変数の値を格納するための変数です。これにより、バイナリがどのARMアーキテクチャバージョンをターゲットとしているかを知ることができます。
-
runtime·checkgoarm
関数の実装:void runtime·checkgoarm(void) { if(runtime·goarm > 5 && !(runtime·hwcap & HWCAP_VFP)) { runtime·printf("runtime: this CPU has no floating point hardware, so it cannot run\\n"); runtime·printf("this GOARM=%d binary. Recompile using GOARM=5.\\n", runtime·goarm); runtime·exit(1); } }
この関数は、以下のロジックを実行します。
runtime·goarm > 5
: これは、コンパイルされたバイナリがGOARM=6
またはGOARM=7
をターゲットとしていることを意味します。これらの設定は、ハードウェア浮動小数点ユニットの存在を前提としています。!(runtime·hwcap & HWCAP_VFP)
: これは、runtime·hwcap
ビットマスクにHWCAP_VFP
ビットがセットされていないことを意味します。つまり、実行中のCPUにはハードウェア浮動小数点ユニットがないということです。- 両方の条件が真の場合、プログラムは互換性のない環境で実行されていると判断されます。
runtime·printf(...)
: ユーザーに対して、CPUに浮動小数点ハードウェアがないため、現在のバイナリは実行できないこと、そしてGOARM=5
で再コンパイルする必要があることを伝えるエラーメッセージを出力します。runtime·exit(1)
: プログラムをエラーコード1で終了させます。これにより、不正な命令の実行によるクラッシュを防ぎ、クリーンな終了を保証します。
これらの変更により、GoランタイムはARM環境での実行時に、必要なハードウェア機能が利用可能かどうかをインテリジェントにチェックし、互換性の問題を早期に検出してユーザーに適切なガイダンスを提供できるようになりました。
関連リンク
- Go Issue #3911: https://github.com/golang/go/issues/3911 (このコミットが修正した問題の元の報告)
- Go Change List 6442109: https://golang.org/cl/6442109 (このコミットに対応するGoの変更リスト)
- Go Change List 6449127: https://golang.org/cl/6449127 (このコミットが前提としている変更リスト)
参考にした情報源リンク
- ARM Architecture Reference Manuals: ARMの公式ドキュメントは、各アーキテクチャバージョン(ARMv5, ARMv6, ARMv7)の機能セット、特に浮動小数点ユニット(VFP, NEON)に関する詳細な情報を提供しています。
- Linux Kernel Documentation on Auxiliary Vector: Linuxカーネルのドキュメントは、
AT_HWCAP
やその他のAuxiliary Vectorエントリの構造と意味について説明しています。 - Go Compiler and Runtime Documentation: Go言語の公式ドキュメントやソースコードは、
GOARM
環境変数の挙動やランタイムの初期化プロセスに関する情報を提供しています。 - Stack Overflow / Go Forum Discussions: 過去のGoコミュニティでの議論やStack Overflowの質問は、
GOARM
とARM浮動小数点に関する一般的な問題と解決策を理解するのに役立ちます。 - Dave Cheney's Blog/Articles: コミットの作者であるDave Cheney氏のブログや記事は、Go言語の内部動作やARMアーキテクチャに関する深い洞察を提供している場合があります。
- Go Source Code: 実際のGoのソースコード(特に
src/pkg/runtime
ディレクトリ)は、ランタイムの動作を理解するための最も正確な情報源です。 - Go Issue Tracker: GoのIssueトラッカーは、過去に報告されたバグや機能要求、それらに対する議論と解決策の履歴を追跡するのに役立ちます。