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

[インデックス 12872] ファイルの概要

このコミットは、Go言語のランタイムにおけるPlan 9オペレーティングシステム上での浮動小数点例外(Floating Point Exception, FPE)の問題を修正するものです。具体的には、無効なオペランドトラップを無効化するFLDCW命令の実行タイミングが原因で発生していた問題を解決します。

コミット

runtime: fix floating point exception on Plan 9

Change 5660047 moved an FLDCW instruction that disables invalid operand traps into runtime·asminit, which is called from runtime·mstart. Thus, runtime·check is being called prior to setting the appropriate control bits, which on any QNaN comparison will cause Plan 9 to take an invalid operand trap. This change loads the control bits (for Plan 9) prior to runtime·check. Ideally, this should be done before the QNaN checks on any system, but possibly other kernels simply don't ever trap on invalid operands.

R=golang-dev, rminnich CC=golang-dev, john, rsc https://golang.org/cl/5939045

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/7056ec6bfd99e204ebf12dc20fe4c78ad623b581

元コミット内容

commit 7056ec6bfd99e204ebf12dc20fe4c78ad623b581
Author: Akshat Kumar <seed@mail.nanosouffle.net>
Date:   Tue Apr 10 15:14:10 2012 -0400

    runtime: fix floating point exception on Plan 9
    
    Change 5660047 moved an FLDCW instruction
    that disables invalid operand traps into
    runtime·asminit, which is called from
    runtime·mstart. Thus, runtime·check is being
    called prior to setting the appropriate control bits,
    which on any QNaN comparison will cause Plan 9
    to take an invalid operand trap. This change loads
    the control bits (for Plan 9) prior to runtime·check.
    Ideally, this should be done before the QNaN checks
    on any system, but possibly other kernels simply
    don't ever trap on invalid operands.
    
    R=golang-dev, rminnich
    CC=golang-dev, john, rsc
    https://golang.org/cl/5939045

変更の背景

このコミットは、以前の変更(Change 5660047)によってGoランタイムに導入されたバグを修正するために行われました。Change 5660047では、浮動小数点演算における「無効なオペランドトラップ」を無効化するためのFLDCW(Load FPU Control Word)命令が、runtime·asminit関数内に移動されました。このruntime·asminit関数は、runtime·mstartから呼び出されます。

問題は、runtime·checkという関数が、runtime·asminitが呼び出され、適切な浮動小数点制御ビットが設定される前に実行されていた点にありました。Plan 9オペレーティングシステムでは、この制御ビットが適切に設定されていない状態でQNaN(Quiet NaN)との比較が行われると、無効なオペランドトラップが発生し、プログラムが異常終了する原因となっていました。

このコミットの目的は、Plan 9環境において、runtime·checkが実行される前に浮動小数点制御ビットが確実にロードされるようにすることで、この浮動小数点例外を回避することです。

前提知識の解説

Plan 9 オペレーティングシステム

Plan 9 from Bell Labsは、ベル研究所で開発された分散型オペレーティングシステムです。Unixの概念をさらに推し進め、すべてのリソースをファイルとして表現し、ネットワーク透過性を重視しています。Go言語の開発者の一部はPlan 9の開発にも携わっており、Go言語の設計思想にもその影響が見られます。Plan 9は、特定のハードウェアやCPUの挙動に対して、他のOSとは異なる厳密なトラップ処理を行う場合があります。

浮動小数点例外 (Floating Point Exception, FPE) と無効なオペランドトラップ

浮動小数点演算では、ゼロ除算、オーバーフロー、アンダーフロー、無効なオペランドなどの異常な状況が発生することがあります。これらは「浮動小数点例外」と呼ばれます。 「無効なオペランドトラップ」は、特に不正な入力(例: sqrt(-1))や、NaN(Not a Number)との比較など、数学的に意味のない演算が行われた場合に発生する例外の一種です。CPUの浮動小数点ユニット(FPU)は、これらの例外が発生した際に、プログラムの実行を中断し、オペレーティングシステムに制御を渡す「トラップ」を生成することができます。

FLDCW 命令 (Load FPU Control Word)

FLDCWは、x86アーキテクチャの浮動小数点ユニット(FPU)の制御ワードをロードするための命令です。FPU制御ワードは、FPUの動作モード(例: 丸めモード、精度)や、どの浮動小数点例外が発生したときにトラップを生成するか(例外マスク)を設定するために使用されます。 このコミットの文脈では、FLDCW命令は「無効なオペランドトラップ」を無効化するために使用されていました。つまり、この命令が実行されると、無効なオペランド例外が発生しても、FPUはトラップを生成せず、デフォルトのNaN値を返すなどの挙動をします。

QNaN (Quiet NaN)

NaN(Not a Number)は、浮動小数点演算の結果が数値として表現できない場合に用いられる特殊な値です。NaNには、シグナリングNaN(SNaN)とクワイエットNaN(QNaN)の2種類があります。

  • SNaN: 未定義の演算や例外的な状況を示すために使用され、通常、演算で使用されると浮動小数点例外を発生させます。
  • QNaN: 例外を発生させずに伝播するNaNです。通常、無効な演算の結果として生成され、その後の演算でもNaNとして伝播します。

この問題では、QNaNとの比較がトラップの原因となっていました。これは、FPU制御ワードが適切に設定されていない場合、QNaNとの比較であっても無効なオペランドトラップが発生する可能性があることを示唆しています。

Goランタイムの初期化プロセス (runtime·asminit, runtime·mstart, runtime·check)

Go言語のプログラムが起動する際、ランタイムは様々な初期化処理を行います。

  • runtime·mstart: Goランタイムの主要な開始点の一つで、新しいM(マシン、OSスレッド)が起動する際に呼び出されます。
  • runtime·asminit: アセンブリレベルでの初期化を行う関数で、FPU制御ワードの設定など、低レベルのハードウェア関連の初期化が含まれることがあります。
  • runtime·check: ランタイムの整合性チェックや、特定の環境設定の検証を行う関数です。この関数内で浮動小数点演算やQNaNとの比較が行われる可能性があります。

技術的詳細

問題の核心は、Goランタイムの初期化シーケンスにおけるFLDCW命令の実行タイミングにありました。以前の変更(Change 5660047)により、無効なオペランドトラップを無効化するFLDCW命令の実行がruntime·asminit関数内に移動されました。runtime·asminitruntime·mstartから呼び出されるため、この命令の実行はGoランタイムの起動プロセスの比較的早い段階で行われるはずでした。

しかし、Plan 9環境では、runtime·check関数がruntime·asminitが呼び出され、FPU制御ワードが適切に設定される前に実行されていました。runtime·check内でQNaNとの比較が行われると、FPU制御ワードが「無効なオペランドトラップを無効化する」設定になっていないため、Plan 9はこれをトラップとして扱い、プログラムがクラッシュしていました。

このコミットの修正は、Plan 9に特化して、runtime·checkが呼び出される前にruntime·asminitを明示的に呼び出すことで、FPU制御ワードが確実に設定されるようにします。これにより、QNaNとの比較が行われる時点で、無効なオペランドトラップが無効化されている状態となり、Plan 9での浮動小数点例外が回避されます。

コミットメッセージには「理想的には、これはどのシステムでもQNaNチェックの前に実行されるべきだが、おそらく他のカーネルは無効なオペランドでトラップしないだけだろう」と述べられており、この問題がPlan 9のFPUトラップ処理の厳密さに起因するものであることが示唆されています。他のOSでは、同様の状況でもデフォルトでトラップが発生しないか、異なる方法で処理されるため、この問題が顕在化しなかったと考えられます。

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

変更は、src/pkg/runtime/rt0_plan9_386.sというファイルに1行追加されています。

--- a/src/pkg/runtime/rt0_plan9_386.s
+++ b/src/pkg/runtime/rt0_plan9_386.s
@@ -25,6 +25,7 @@ argv_fix:
  	ADDL	$4, BP
  	LOOP	argv_fix
  	
+	CALL	runtime·asminit(SB)
  	JMP	_rt0_386(SB)
 
  DATA  runtime·isplan9(SB)/4, $1

具体的には、JMP _rt0_386(SB)の直前にCALL runtime·asminit(SB)が追加されています。

コアとなるコードの解説

追加されたCALL runtime·asminit(SB)命令は、Goランタイムの初期化ルーチンであるruntime·asminit関数を明示的に呼び出しています。

  • CALL: x86アセンブリにおける関数呼び出し命令です。
  • runtime·asminit(SB): 呼び出す関数のシンボル名です。SBはStatic Baseレジスタを意味し、グローバルシンボルを参照する際に使用されます。

この変更により、Plan 9環境におけるGoプログラムの起動シーケンスにおいて、_rt0_386(Goプログラムのエントリポイントの一つ)にジャンプする前に、runtime·asminitが確実に実行されるようになります。これにより、runtime·asminit内で設定されるFPU制御ワード(特に無効なオペランドトラップを無効化する設定)が、runtime·checkが実行される前に適用されることが保証されます。結果として、Plan 9特有の厳密なFPUトラップ処理による浮動小数点例外が回避され、プログラムが正常に動作するようになります。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード(特にsrc/pkg/runtime/ディレクトリ内のアセンブリファイル)
  • x86アセンブリ言語のドキュメント(FLDCW命令に関する情報)
  • 浮動小数点演算、NaN、FPU制御ワードに関する一般的な情報
  • Plan 9オペレーティングシステムに関する情報
  • Go言語のランタイム初期化に関する一般的な知識