Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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制御ワードを継承しないため、デフォルトの拡張倍精度モードのままになることが問題でした。

この修正では、以下の変更が行われました。

  1. _rt0_386からのFP制御ワード設定の削除: src/pkg/runtime/asm_386.sから、初期スレッドでのFP制御ワード設定コードが削除されました。
  2. runtime·asminit関数の導入: 新しいアセンブリ関数runtime·asminitが導入されました。この関数は、386アーキテクチャではFP制御ワードを0x27Fに設定します。
    • 0x27Fという値は、FP制御ワードのビットフィールドにおいて、精度制御ビットを「倍精度(53ビット仮数)」に、丸め制御ビットを「最も近い偶数へ丸め」に設定し、すべての例外マスクを有効にする(例外発生時に割り込みを生成しない)ことを意味します。これは、Goが期待するIEEE 754倍精度浮動小数点演算の標準的な設定です。
    • amd64およびarmアーキテクチャでは、runtime·asminitは何も行いません。これは、これらのアーキテクチャでは同様のFP制御ワードの問題が存在しないか、異なる方法で対処されているためです。
  3. runtime·mstartからのruntime·asminit呼び出し: src/pkg/runtime/proc.c内のruntime·mstart関数からruntime·asminit()が呼び出されるようになりました。runtime·mstartは、Goランタイムが新しいOSスレッド(M)を起動する際に実行される関数です。これにより、Goランタイムが管理するすべてのOSスレッドが起動時に適切なFP制御ワード設定を持つことが保証されます。
  4. strconvテストの有効化: src/pkg/strconv/atof_test.goから、Darwin/386環境でのTestRoundTripテストのスキップロジックが削除されました。これにより、このテストがすべての環境で実行されるようになり、浮動小数点演算の精度が正しく保証されているかを確認できるようになりました。テストのコメントも更新され、FP制御ワードが修正されていない場合に最適化された変換が失敗する可能性が明記されました。

これらの変更により、Goランタイムは、OSやスレッドの作成方法に関わらず、すべてのGoルーチンが実行されるOSスレッド上で一貫した浮動小数点演算環境を確保できるようになりました。

コアとなるコードの変更箇所

このコミットにおける主要なコード変更は以下のファイルに集中しています。

  1. 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
      
  2. src/pkg/runtime/asm_amd64.s:

    • runtime·asminit関数が追加されましたが、amd64では特別な初期化は不要なため、RETのみが含まれています。
      TEXT runtime·asminit(SB),7,$0
          // No per-thread init.
          RET
      
  3. src/pkg/runtime/asm_arm.s:

    • runtime·asminit関数が追加されましたが、armでは特別な初期化は不要なため、RETのみが含まれています。
      TEXT runtime·asminit(SB),7,$0
          // No per-thread init.
          RET
      
  4. src/pkg/runtime/proc.c:

    • runtime·mstart関数内で、runtime·minit()の前にruntime·asminit()の呼び出しが追加されました。
      runtime·asminit();
      runtime·minit();
      
  5. src/pkg/runtime/runtime.h:

    • runtime·asminit関数のプロトタイプ宣言が追加されました。
      void    runtime·asminit(void);
      
  6. 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倍精度標準に準拠することが保証されます。amd64armではこの問題がないため、この関数は空の実装となっています。

  • 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」という哲学を支える重要な基盤の一つであり、異なるプラットフォーム間での数値計算の一貫性を保証するために不可欠なものです。

関連リンク

参考にした情報源リンク