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

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

このコミットは、Goコンパイラ(cmd/6g、x86-64アーキテクチャ向け)におけるfloat32およびfloat64からuint64への変換処理に関するバグ修正を扱っています。具体的には、浮動小数点数から符号なし64ビット整数への変換時に、誤った丸めモードが使用されていた問題を修正し、正しい切り捨て(truncate)セマンティクスを保証するように変更されました。

コミット

commit e80f6a4de1a35cab03e7e4d29e26015895ffe04f
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Thu Aug 23 14:35:26 2012 +0800

    cmd/6g: fix float32/64->uint64 conversion
        CVTSS2SQ's rounding mode is controlled by the RC field of MXCSR;
    as we specifically need truncate semantic, we should use CVTTSS2SQ.
    
        Fixes #3804.
    
    R=rsc, r
    CC=golang-dev
    https://golang.org/cl/6352079

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

https://github.com/golang/go/commit/e80f6a4de1a35cab03e7e4d29e26015895ffe04f

元コミット内容

Goコンパイラ(cmd/6g)において、float32およびfloat64からuint64への変換が正しく行われていなかったバグを修正します。この問題は、x86命令セットのCVTSS2SQ命令がMXCSRレジスタのRC(Rounding Control)フィールドによって丸めモードが制御されるのに対し、Goの変換では切り捨て(truncate)セマンティクスが必要であるにもかかわらず、それが保証されていなかったことに起因します。修正として、切り捨てセマンティクスを明示的に提供するCVTTSS2SQ命令を使用するように変更されました。

この修正は、GoのIssue #3804を解決します。

変更の背景

Go言語では、浮動小数点数型(float32, float64)から整数型(int, uintなど)への変換は、一般的にゼロ方向への切り捨て(truncate towards zero)として定義されています。しかし、x86-64アーキテクチャの特定の命令(CVTSS2SQCVTSD2SQ)は、デフォルトの丸めモードが「最も近い偶数への丸め」(round to nearest, ties to even)であるか、またはMXCSRレジスタの設定に依存します。

Goコンパイラがこれらの命令を使用して浮動小数点数をuint64に変換する際、MXCSRレジスタのRCフィールドが適切に設定されていない場合、Go言語の仕様で求められる「ゼロ方向への切り捨て」ではなく、異なる丸めが行われる可能性がありました。これにより、例えば39.7uint64に変換すると39になるべきところが、異なる値になるなどのバグが発生していました。

このコミットは、この不整合を解消し、Go言語の浮動小数点数から整数への変換セマンティクスをx86-64アーキテクチャ上で正確に実装することを目的としています。

前提知識の解説

浮動小数点数と整数変換の丸めモード

浮動小数点数を整数に変換する際には、小数点以下の部分をどのように処理するかという「丸めモード」が重要になります。一般的な丸めモードには以下のようなものがあります。

  1. 最も近い偶数への丸め (Round to Nearest, ties to even): 最も一般的な丸めモードで、IEEE 754標準のデフォルトです。値が二つの整数のちょうど中間にある場合、偶数の方に丸めます(例: 2.5 -> 2, 3.5 -> 4)。
  2. ゼロ方向への切り捨て (Truncate towards Zero): 小数点以下を単純に切り捨てて整数にします。正の数の場合は床関数(floor)、負の数の場合は天井関数(ceil)に相当します(例: 2.7 -> 2, -2.7 -> -2)。Go言語の浮動小数点数から整数への変換はこのセマンティクスに従います。
  3. 正の無限大への丸め (Round towards Positive Infinity): 値を常に大きい方の整数に丸めます(天井関数)。
  4. 負の無限大への丸め (Round towards Negative Infinity): 値を常に小さい方の整数に丸めます(床関数)。

x86-64 SSE/AVX命令セットとMXCSRレジスタ

x86-64アーキテクチャでは、浮動小数点演算は主にSSE(Streaming SIMD Extensions)やAVX(Advanced Vector Extensions)命令セットによって行われます。これらの命令は、MXCSR(Media eXtension Control and Status Register)と呼ばれる32ビットの制御/ステータスレジスタによって動作が制御されます。

MXCSRレジスタには、浮動小数点例外マスク、フラグ、そして丸め制御 (RC: Rounding Control) フィールドが含まれています。RCフィールドは、浮動小数点演算の丸めモードを設定するために使用されます。

CVTSS2SQ, CVTSD2SQ, CVTTSS2SQ, CVTTSD2SQ命令

これらの命令は、x86-64アーキテクチャにおける浮動小数点数から整数への変換命令です。

  • CVTSS2SQ (Convert Single-precision Floating-Point to Signed Quadword Integer): 32ビット単精度浮動小数点数(float32)を64ビット符号付き整数(int64)に変換します。この命令の丸めモードは、MXCSRレジスタのRCフィールドによって制御されます。
  • CVTSD2SQ (Convert Scalar Double-precision Floating-Point to Signed Quadword Integer): 64ビット倍精度浮動小数点数(float64)を64ビット符号付き整数(int64)に変換します。この命令の丸めモードも、MXCSRレジスタのRCフィールドによって制御されます。
  • CVTTSS2SQ (Convert Single-precision Floating-Point to Signed Quadword Integer, Truncate): 32ビット単精度浮動小数点数(float32)を64ビット符号付き整数(int64)に変換します。この命令は、常にゼロ方向への切り捨てを行います。MXCSRレジスタのRCフィールドの設定に関わらず、切り捨てセマンティクスを強制します。
  • CVTTSD2SQ (Convert Scalar Double-precision Floating-Point to Signed Quadword Integer, Truncate): 64ビット倍精度浮動小数点数(float64)を64ビット符号付き整数(int64)に変換します。この命令も、常にゼロ方向への切り捨てを行います。

Go言語の浮動小数点数から整数への変換は「ゼロ方向への切り捨て」であるため、CVTTSS2SQCVTTSD2SQのような明示的に切り捨てを行う命令を使用することが正しい実装となります。

技術的詳細

このコミットの核心は、Goコンパイラが浮動小数点数から整数への変換を行う際に生成するx86アセンブリ命令の変更です。

Goコンパイラのバックエンド(cmd/6g)は、Goのソースコードを機械語に変換する過程で、浮動小数点数から整数への型変換(キャスト)を検出すると、対応するx86命令を生成します。

修正前は、float32からuint64への変換にはACVTSS2SQ(Goコンパイラ内部のオペコードで、最終的にCVTSS2SQにマップされる)が、float64からuint64への変換にはACVTSD2SQ(最終的にCVTSD2SQにマップされる)が使用されていました。これらの命令はMXCSRレジスタのRCフィールドに依存するため、Goの期待する「ゼロ方向への切り捨て」が保証されませんでした。特に、GoのランタイムがMXCSRレジスタのRCフィールドをデフォルトの「最も近い偶数への丸め」に設定している場合、変換結果がGoの仕様と異なる可能性がありました。

修正では、これらの命令をそれぞれACVTTSS2SQACVTTSD2SQに変更しました。これらの命令は、命令自体が「Truncate」(切り捨て)のセマンティクスを持つため、MXCSRレジスタのRCフィールドの設定に関わらず、常にゼロ方向への切り捨てを行います。これにより、Go言語の浮動小数点数から整数への変換の動作が、x86-64アーキテクチャ上で正確に保証されるようになりました。

また、この修正にはtest/fixedbugs/bug447.goという新しいテストファイルが追加されています。このテストは、様々な浮動小数点数と整数型の組み合わせに対して、浮動小数点数から整数への変換が正しく行われることを検証するためのものです。特に、境界値や負の値、大きな値など、丸めモードの影響を受けやすいケースを網羅的にテストすることで、このバグが再発しないことを保証しています。

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

変更はsrc/cmd/6g/gsubr.cファイル内のgmove関数に集中しています。この関数は、Goコンパイラが型変換(ムーブ)を処理する際に呼び出される部分です。

--- a/src/cmd/6g/gsubr.c
+++ b/src/cmd/6g/gsubr.c
@@ -796,9 +796,9 @@ gmove(Node *f, Node *t)
 		// algorithm is:
 		//	if small enough, use native float64 -> int64 conversion.
 		//	otherwise, subtract 2^63, convert, and add it back.
-		a = ACVTSS2SQ;
+		a = ACVTTSS2SQ;
 		if(ft == TFLOAT64)
-			a = ACVTSD2SQ;
+			a = ACVTTSD2SQ;
 		bignodes();
 		regalloc(&r1, types[ft], N);
 		regalloc(&r2, types[tt], t);

また、バグの修正を検証するために、test/fixedbugs/bug447.goという新しいテストファイルが追加されました。

コアとなるコードの解説

src/cmd/6g/gsubr.cgmove関数は、Goコンパイラが異なる型の間で値を移動(変換)する際のコード生成ロジックを含んでいます。

変更された部分のコンテキストは、浮動小数点数型(ftTFLOAT32またはTFLOAT64)から符号なし64ビット整数型(ttTUINT64)への変換を処理するブロックです。

  • a = ACVTSS2SQ;a = ACVTTSS2SQ; に変更されました。
    • これは、float32からuint64への変換に使用される内部オペコードを、MXCSRレジスタの丸めモードに依存するCVTSS2SQから、常に切り捨てを行うCVTTSS2SQに変更したことを意味します。
  • if(ft == TFLOAT64) ブロック内の a = ACVTSD2SQ;a = ACVTTSD2SQ; に変更されました。
    • これは、float64からuint64への変換に使用される内部オペコードを、MXCSRレジスタの丸めモードに依存するCVTSD2SQから、常に切り捨てを行うCVTTSD2SQに変更したことを意味します。

これらの変更により、Go言語の浮動小数点数から整数への変換セマンティクス(ゼロ方向への切り捨て)が、x86-64アーキテクチャ上で正確に反映されるようになりました。

test/fixedbugs/bug447.goは、この修正が正しく機能することを検証するための包括的なテストケースです。このテストは、Goのfmtパッケージとbytesパッケージを使用して、様々な浮動小数点数と整数型の組み合わせに対する変換結果を動的に生成し、期待される値と比較します。これにより、float32およびfloat64からint8, int16, int32, int64, uint8, uint16, uint32, uint64への変換がすべてGoの仕様通りに切り捨てられることを確認します。特に、負の数や大きな数、小数点以下が.5であるような境界ケースも含まれており、丸めモードの正確性を厳密に検証しています。

関連リンク

参考にした情報源リンク