[インデックス 11893] ファイルの概要
このコミットは、Goランタイムにおける浮動小数点演算の精度に関する重要な修正を含んでいます。特に、x86アーキテクチャ(386)において、浮動小数点制御ワード(FP control word)が初期スレッドだけでなく、新しく作成されるすべてのスレッドで正しく設定されるように変更されています。これにより、異なるオペレーティングシステム(Linux, Windows, Darwinなど)間での浮動小数点演算の一貫性が向上し、特定の環境で発生していた浮動小数点関連のテスト失敗(Issue 2917)が解決されました。
コミット
commit 1707a9977f2272333b86853c2ac09a3bdba9915e
Author: Russ Cox <rsc@golang.org>
Date: Tue Feb 14 01:23:15 2012 -0500
runtime: on 386, fix FP control word on all threads, not just initial thread
It is possible that Linux and Windows copy the FP control word
from the parent thread when creating a new thread. Empirically,
Darwin does not. Reset the FP control world in all cases.
Enable the floating-point strconv test.
Fixes #2917 (again).
R=golang-dev, r, iant
CC=golang-dev
https://golang.org/cl/5660047
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1707a9977f2272333b8685c2ac09a3bdba9915e
元コミット内容
このコミットの元の内容は、Goランタイムが386アーキテクチャ上で、初期スレッドだけでなく、すべてのスレッドで浮動小数点制御ワードを修正することに焦点を当てています。これは、LinuxやWindowsが新しいスレッドを作成する際に親スレッドからFP制御ワードをコピーする可能性がある一方で、Darwin(macOS)はそうしないという経験的な観察に基づいています。この不整合を解消するため、すべてのケースでFP制御ワードをリセットするよう変更されました。
また、この修正の一環として、浮動小数点文字列変換(strconv)のテストが有効化されました。これは、以前から存在していたIssue 2917(浮動小数点演算の精度に関する問題)を再度修正するものです。
変更の背景
この変更の背景には、Goプログラムが異なるオペレーティングシステムやアーキテクチャ上で実行される際に、浮動小数点演算の結果に一貫性がないという問題がありました。特に、Issue 2917として報告されていた問題は、Darwin/386環境でstrconv
パッケージの浮動小数点変換テストが失敗するというものでした。
この問題の根本原因は、x86アーキテクチャの浮動小数点ユニット(FPU)が持つ「浮動小数点制御ワード(FP control word)」の初期状態が、OSやスレッドの作成方法によって異なることにありました。一部のOS(Linux, Windows)では、新しいスレッドが作成される際に親スレッドのFP制御ワードがコピーされる傾向がありましたが、Darwinではそうではありませんでした。
Goランタイムは、浮動小数点演算の精度をIEEE 754標準の倍精度(64ビット)に統一することを意図しています。しかし、FPUによっては拡張倍精度(80ビット)モードで初期化されることがあり、これがGoランタイムが期待する64ビット精度との不整合を引き起こし、特定の計算で丸め誤差や予期せぬ結果を生じさせていました。
このコミットは、この不整合を解消し、GoプログラムがどのOSやアーキテクチャで実行されても、浮動小数点演算が予測可能で一貫した結果を出すようにするためのものです。
前提知識の解説
浮動小数点数とIEEE 754標準
浮動小数点数は、非常に大きな数や非常に小さな数を表現するためのコンピュータの数値表現形式です。一般的に、符号部、指数部、仮数部から構成されます。IEEE 754は、浮動小数点数の表現と演算に関する国際標準であり、単精度(32ビット)と倍精度(64ビット)の形式を定義しています。Go言語のfloat64
型は、このIEEE 754倍精度浮動小数点数に準拠しています。
x86アーキテクチャの浮動小数点ユニット(FPU)
Intel x86プロセッサには、浮動小数点演算を専門に行うFPUが内蔵されています。FPUは、浮動小数点レジスタ(通常は80ビット幅)と、演算の動作を制御する「浮動小数点制御ワード(FP control word)」を持っています。
浮動小数点制御ワード(FP Control Word)
FP制御ワードは、FPUの動作モードを設定するための16ビットのレジスタです。このワードには、以下のような重要な設定が含まれています。
- 精度制御(Precision Control, PC): 浮動小数点演算の結果をどの精度で丸めるかを指定します。
- 00b: 単精度 (24ビット仮数)
- 01b: 予約済み
- 10b: 倍精度 (53ビット仮数)
- 11b: 拡張倍精度 (64ビット仮数)
- 丸め制御(Rounding Control, RC): 演算結果をどのように丸めるかを指定します。
- 00b: 最も近い偶数へ丸め (Round to Nearest Even)
- 01b: 負の無限大へ丸め (Round Down)
- 10b: 正の無限大へ丸め (Round Up)
- 11b: ゼロへ丸め (Chop/Truncate)
- 例外マスク(Exception Masks): 浮動小数点例外(オーバーフロー、アンダーフロー、ゼロ除算など)が発生したときに、割り込みを生成するかどうかを制御します。
Goランタイムは、IEEE 754倍精度(64ビット)の動作を期待しているため、FP制御ワードの精度制御ビットが「倍精度(53ビット仮数)」に設定されている必要があります。しかし、一部のOSや環境では、FPUがデフォルトで「拡張倍精度(64ビット仮数)」モードで初期化されることがあり、これがGoの期待する動作と異なる結果を生む原因となっていました。
スレッドとOSの挙動
オペレーティングシステムは、新しいスレッドを作成する際に、親スレッドのコンテキスト(レジスタの状態、メモリマップなど)の一部をコピーすることがあります。FP制御ワードもその一つであり、OSによっては新しいスレッドに親スレッドのFP制御ワードを継承させる場合があります。しかし、Darwinのようにこれを継承しないOSも存在するため、Goランタイムは明示的にすべてのスレッドでFP制御ワードを適切な状態に設定する必要がありました。
技術的詳細
このコミットの技術的な核心は、Goランタイムが新しいOSスレッド(GoのM(Machine)に対応)を起動する際に、そのスレッドのFPUのFP制御ワードを明示的に設定することです。
以前は、386アーキテクチャの初期化ルーチンである_rt0_386
内でFP制御ワードを設定していました。しかし、これはプログラムの初期起動時に一度だけ行われる処理であり、その後にOSが新しいスレッドを作成する際に、そのスレッドのFPU状態がGoランタイムの期待する状態(倍精度)になっていない可能性がありました。特に、DarwinのようなOSでは、新しいスレッドが親スレッドのFP制御ワードを継承しないため、デフォルトの拡張倍精度モードのままになることが問題でした。
この修正では、以下の変更が行われました。
_rt0_386
からのFP制御ワード設定の削除:src/pkg/runtime/asm_386.s
から、初期スレッドでのFP制御ワード設定コードが削除されました。runtime·asminit
関数の導入: 新しいアセンブリ関数runtime·asminit
が導入されました。この関数は、386アーキテクチャではFP制御ワードを0x27F
に設定します。0x27F
という値は、FP制御ワードのビットフィールドにおいて、精度制御ビットを「倍精度(53ビット仮数)」に、丸め制御ビットを「最も近い偶数へ丸め」に設定し、すべての例外マスクを有効にする(例外発生時に割り込みを生成しない)ことを意味します。これは、Goが期待するIEEE 754倍精度浮動小数点演算の標準的な設定です。amd64
およびarm
アーキテクチャでは、runtime·asminit
は何も行いません。これは、これらのアーキテクチャでは同様のFP制御ワードの問題が存在しないか、異なる方法で対処されているためです。
runtime·mstart
からのruntime·asminit
呼び出し:src/pkg/runtime/proc.c
内のruntime·mstart
関数からruntime·asminit()
が呼び出されるようになりました。runtime·mstart
は、Goランタイムが新しいOSスレッド(M)を起動する際に実行される関数です。これにより、Goランタイムが管理するすべてのOSスレッドが起動時に適切なFP制御ワード設定を持つことが保証されます。strconv
テストの有効化:src/pkg/strconv/atof_test.go
から、Darwin/386環境でのTestRoundTrip
テストのスキップロジックが削除されました。これにより、このテストがすべての環境で実行されるようになり、浮動小数点演算の精度が正しく保証されているかを確認できるようになりました。テストのコメントも更新され、FP制御ワードが修正されていない場合に最適化された変換が失敗する可能性が明記されました。
これらの変更により、Goランタイムは、OSやスレッドの作成方法に関わらず、すべてのGoルーチンが実行されるOSスレッド上で一貫した浮動小数点演算環境を確保できるようになりました。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は以下のファイルに集中しています。
-
src/pkg/runtime/asm_386.s
:_rt0_386
関数から、FP制御ワードを設定する以下の行が削除されました。// Linux, Windows start the FPU in extended double precision. // Other operating systems use double precision. // Change to double precision to match them, // and to match other hardware that only has double. PUSHL $0x27F FLDCW 0(SP) POPL AX
- 新しい関数
runtime·asminit
が追加され、FP制御ワードを0x27F
に設定するコードが含まれています。TEXT runtime·asminit(SB),7,$0 // Linux, Windows start the FPU in extended double precision. // Other operating systems use double precision. // Change to double precision to match them, // and to match other hardware that only has double. PUSHL $0x27F FLDCW 0(SP) POPL AX RET
-
src/pkg/runtime/asm_amd64.s
:runtime·asminit
関数が追加されましたが、amd64
では特別な初期化は不要なため、RET
のみが含まれています。TEXT runtime·asminit(SB),7,$0 // No per-thread init. RET
-
src/pkg/runtime/asm_arm.s
:runtime·asminit
関数が追加されましたが、arm
では特別な初期化は不要なため、RET
のみが含まれています。TEXT runtime·asminit(SB),7,$0 // No per-thread init. RET
-
src/pkg/runtime/proc.c
:runtime·mstart
関数内で、runtime·minit()
の前にruntime·asminit()
の呼び出しが追加されました。runtime·asminit(); runtime·minit();
-
src/pkg/runtime/runtime.h
:runtime·asminit
関数のプロトタイプ宣言が追加されました。void runtime·asminit(void);
-
src/pkg/strconv/atof_test.go
:TestRoundTrip
関数から、Darwin/386でのテストスキップロジックが削除されました。- if runtime.GOOS == "darwin" && runtime.GOARCH == "386" { - t.Logf("skipping round-trip test on darwin/386 - known failure, issue 2917") - return - }
- 関連するコメントが更新され、FP制御ワードが修正されていない場合にテストが失敗する理由がより明確に説明されました。
// This test will break the optimized conversion if the // FPU is using 80-bit registers instead of 64-bit registers, // usually because the operating system initialized the // thread with 80-bit precision and the Go runtime didn't // fix the FP control word.
コアとなるコードの解説
このコミットの核心は、GoランタイムがOSスレッドの浮動小数点環境を積極的に管理するようになった点にあります。
-
runtime·asminit
の役割: この新しいアセンブリ関数は、特定のアーキテクチャ(この場合は386)において、FPUのFP制御ワードをGoが期待する倍精度モード(0x27F
)に設定する責任を負います。これにより、FPUがデフォルトで拡張倍精度モードで初期化された場合でも、Goの浮動小数点演算がIEEE 754倍精度標準に準拠することが保証されます。amd64
やarm
ではこの問題がないため、この関数は空の実装となっています。 -
runtime·mstart
での呼び出し:runtime·mstart
は、Goランタイムが新しいOSスレッド(M)を起動する際に実行される非常に重要な関数です。Goのスケジューラは、必要に応じて新しいOSスレッドを作成し、その上でGoルーチンを実行します。runtime·mstart
内でruntime·asminit
を呼び出すことで、Goランタイムが作成するすべてのOSスレッドが、起動時に適切なFP制御ワード設定を持つことが保証されます。これにより、初期スレッドだけでなく、Goプログラム内で並行して実行されるすべてのGoルーチンが、一貫した浮動小数点演算環境で動作するようになります。 -
strconv
テストの重要性:strconv
パッケージは、数値と文字列の間の変換を扱います。浮動小数点数の文字列変換は、FPUの精度設定に非常に敏感です。以前はDarwin/386でこのテストがスキップされていましたが、FP制御ワードの修正により、このテストがパスするようになりました。これは、Goランタイムが浮動小数点演算の精度を正しく制御できるようになったことの直接的な検証となります。テストのコメント更新は、この問題の根本原因(FPUのレジスタ幅とFP制御ワード)を明確に示しており、将来のデバッグや理解に役立ちます。
この修正は、Go言語の「Write once, run anywhere」という哲学を支える重要な基盤の一つであり、異なるプラットフォーム間での数値計算の一貫性を保証するために不可欠なものです。
関連リンク
- Go Issue 2917: https://github.com/golang/go/issues/2917
- Go CL 5660047: https://golang.org/cl/5660047
参考にした情報源リンク
- IEEE 754 - Wikipedia: https://ja.wikipedia.org/wiki/IEEE_754
- x86 floating point unit - Wikipedia: https://en.wikipedia.org/wiki/X86_floating-point_unit
- Intel® 64 and IA-32 Architectures Software Developer’s Manuals (特にVol. 1, Chapter 8: Programming with the x87 FPU): https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html
- Go runtime source code (for general understanding of
mstart
,proc.c
,asm_*.s
files): https://github.com/golang/go/tree/master/src/runtime - Go
strconv
package source code: https://github.com/golang/go/tree/master/src/strconv