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

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

このコミットは、Goコンパイラのcmd/8g(x86アーキテクチャ向けのコンパイラ)におけるSSE2比較命令のコード生成に関するバグ修正です。具体的には、浮動小数点数の比較において、特定の条件下で誤ったコードが生成される問題(Issue 4785)を解決します。

コミット

commit ac1015e7f3ef68be5d1c0721d76296ad84fe6768
Author: Russ Cox <rsc@golang.org>
Date:   Thu Feb 14 14:49:04 2013 -0500

    cmd/8g: fix sse2 compare code gen
    
    Fixes #4785.
    
    R=ken2
    CC=golang-dev
    https://golang.org/cl/7300109

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

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

元コミット内容

cmd/8g: fix sse2 compare code gen

このコミットは、cmd/8g(Goコンパイラのx86アーキテクチャ向けバックエンド)におけるSSE2比較命令のコード生成のバグを修正します。

Fixes #4785.

これは、GoのIssue 4785を修正するものです。

変更の背景

このコミットの背景には、Goコンパイラがx86アーキテクチャ(特に32ビットおよび64ビット)上で浮動小数点数比較を行う際に、SSE2命令セットを利用して効率的なコードを生成する過程で発生していたバグがあります。

Go言語では、float32float64といった浮動小数点型がサポートされており、これらの型に対する比較演算子(>, <, >=, <=, ==, !=)は、内部的にはCPUの浮動小数点演算ユニット(FPU)またはSIMD(Single Instruction, Multiple Data)命令セット(x86ではSSE/SSE2など)を用いて処理されます。

Issue 4785は、Goコンパイラのcmd/8gが、浮動小数点数の比較(特に><)において、オペランドが一時レジスタにロードされる必要がある場合に、誤ったSSE2比較命令を生成してしまうという問題でした。これにより、期待される比較結果が得られない、あるいはコンパイルエラーが発生するといった不具合が生じていました。

具体的には、比較対象のオペランドがメモリ上の値であったり、複雑な式の結果であったりする場合、コンパイラはそれらを一時レジスタにロードしてから比較命令を実行します。このロードと比較のシーケンスにおいて、SSE2命令の利用方法に誤りがあったため、バグが発生していました。この修正は、この誤ったコード生成ロジックを是正し、正確な比較結果が得られるようにすることを目的としています。

前提知識の解説

Goコンパイラの構造 (cmd/8g)

Goコンパイラは、複数のステージから構成されています。

  • フロントエンド: ソースコードの字句解析、構文解析、型チェック、AST(抽象構文木)の生成を行います。
  • ミドルエンド: ASTを中間表現(IR)に変換し、最適化を行います。
  • バックエンド: 中間表現をターゲットアーキテクチャの機械語に変換します。

cmd/8gは、Goコンパイラのバックエンドの一つで、x86アーキテクチャ(32ビットおよび64ビット)向けのコード生成を担当します。8g8は、歴史的にIntel 80386プロセッサを指す名残です。同様に、6gはamd64、5gはARMなど、各アーキテクチャに対応するバックエンドが存在します。

ggen.cは、cmd/8g内で汎用的なコード生成ロジックを記述しているC言語のファイルです。Goコンパイラの一部はC言語で書かれており、特にバックエンドの低レベルな部分でCが使われることがあります。

SSE2 (Streaming SIMD Extensions 2)

SSE2は、Intelが開発したSIMD命令セットの一つで、Pentium 4以降のx86プロセッサで利用可能です。主に以下の機能を提供します。

  • 浮動小数点演算: 倍精度浮動小数点数(double、Goではfloat64に相当)の演算をサポートします。複数の浮動小数点数を同時に処理できるため、科学技術計算やグラフィックス処理などで高いパフォーマンスを発揮します。
  • 整数演算: 128ビットの整数演算をサポートします。
  • データ転送: メモリとXMMレジスタ(SSE/SSE2で使用される128ビットレジスタ)間のデータ転送命令を提供します。

浮動小数点数の比較においては、SSE2はCMPSS(単精度)やCMPPD(倍精度)のような命令を提供します。これらの命令は、2つの浮動小数点数を比較し、その結果をフラグレジスタに設定したり、マスク値を生成したりします。

Ullman Number (ウルマン数)

コンパイラのコード生成において、Ullman Number(ウルマン数)は、式の評価に必要なレジスタの最小数を示すヒューリスティックな値です。ASTの各ノードに割り当てられ、レジスタ割り当ての最適化やコード生成の順序決定に利用されます。Ullman Numberが高いノードは、より多くのレジスタを必要とするか、複雑な計算を伴うことを示唆します。

UINFは、Ullman Numberが無限大、つまりレジスタに収まらないか、非常に複雑な式であることを示す定数として使われることがあります。

addable プロパティ

コンパイラのコード生成において、addableというプロパティは、あるオペランドが直接命令のオペランドとして使用できるかどうかを示します。例えば、メモリ上の値や定数は直接addableである場合がありますが、複雑な式の結果は一時レジスタにロードしないとaddableではない場合があります。

技術的詳細

このコミットは、src/cmd/8g/ggen.c内のsseラベルが付いたセクション、すなわちSSE2命令を用いたコード生成ロジックに焦点を当てています。このセクションは、浮動小数点数の比較演算子(例: ><>=<=)を処理する際に呼び出されます。

変更前のコードでは、sseセクションの冒頭に、nr->ullman >= UINFという条件分岐がありました。これは、右オペランド(nr)のUllman Numberが非常に大きい(つまり、複雑な式である)場合に、特別な処理を行うためのものでした。この処理の中では、左右のオペランド(nlnr)がaddableでない場合に一時レジスタにロードし、その後ssecmpラベルにジャンプして比較を行っていました。

しかし、このif(nr->ullman >= UINF)ブロック内のロジックが、特定のケースで誤ったコードを生成する原因となっていました。特に、右オペランドが複雑な式である場合に、不必要なレジスタのロードや、比較命令への誤ったオペランドの渡し方が発生していたと考えられます。

修正の核心は、このif(nr->ullman >= UINF)ブロック全体を削除した点にあります。これにより、sseセクションのコード生成パスが簡素化され、すべての浮動小数点数比較において、より一貫性のある、そして正しいSSE2命令の生成ロジックが適用されるようになりました。

削除されたブロックの代わりに、以下の既存のロジックが適用されます。

  1. 左オペランドnladdableでない場合、一時レジスタn1にロードします。
  2. 右オペランドnraddableでない場合、一時レジスタtmpにロードします。
  3. 右オペランドnrをレジスタn2に移動します(gmove(nr, &n2);)。これは、比較命令のオペランドとしてレジスタが必要なためです。
  4. 左オペランドnlがレジスタにない場合、レジスタn3に移動します。

この変更により、Ullman Numberの複雑さに関わらず、すべての浮動小数点数比較が統一された方法で処理されるようになり、以前のバグが解消されました。特に、ssecmpラベルへの不必要なジャンプが削除され、コードのフローが直線的になったことも、バグ修正に寄与しています。

test/fixedbugs/issue4785.goは、このバグを再現し、修正を検証するための新しいテストケースです。interface{}型の引数を受け取り、内部でfloat64に型アサートして比較を行うシンプルな関数tを定義しています。このテストは、修正前にはコンパイルエラーまたは誤った結果を招いていたケースを捕捉します。

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

src/cmd/8g/ggen.c

--- a/src/cmd/8g/ggen.c
+++ b/src/cmd/8g/ggen.c
@@ -1053,35 +1053,16 @@ x87:
  	goto ret;
 
 sse:
-	if(nr->ullman >= UINF) {
-		if(!nl->addable) {
-			tempname(&n1, nl->type);
-			cgen(nl, &n1);
-			nl = &n1;
-		}
-		if(!nr->addable) {
-			tempname(&tmp, nr->type);
-			cgen(nr, &tmp);
-			nr = &tmp;
-		}
-		regalloc(&n2, nr->type, N);
-		cgen(nr, &n2);
-		nr = &n2;
-		goto ssecmp;
-	}
-
 	if(!nl->addable) {
 		tempname(&n1, nl->type);
 		cgen(nl, &n1);
 		nl = &n1;
 	}
-
 	if(!nr->addable) {
 		tempname(&tmp, nr->type);
 		cgen(nr, &tmp);
 		nr = &tmp;
 	}
-
 	regalloc(&n2, nr->type, N);
 	gmove(nr, &n2);
 	nr = &n2;
@@ -1092,7 +1073,6 @@ sse:
  		nl = &n3;
  	}
 
-ssecmp:
  	if(a == OGE || a == OGT) {
  		// only < and <= work right with NaN; reverse if needed
  		r = nr;

test/fixedbugs/issue4785.go

--- /dev/null
+++ b/test/fixedbugs/issue4785.go
@@ -0,0 +1,20 @@
+// run
+
+// Copyright 2013 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// issue 4785: used to fail to compile
+
+package main
+
+func t(x, y interface{}) interface{} {
+	return x.(float64) > y.(float64)
+}
+
+func main() {
+	v := t(1.0, 2.0)
+	if v != false {
+		panic("bad comparison")
+	}
+}

コアとなるコードの解説

src/cmd/8g/ggen.c の変更

  • 削除されたブロック: if(nr->ullman >= UINF) { ... goto ssecmp; } このブロックは、右オペランドnrのUllman NumberがUINF(無限大、つまり非常に複雑な式)以上の場合に実行される特殊なコードパスでした。このパスは、オペランドを一時レジスタにロードし、その後ssecmpラベルにジャンプして比較を行うというものでした。この特殊な処理が、特定の条件下で誤ったSSE2比較命令を生成する原因となっていました。このブロックを削除することで、コード生成のロジックが簡素化され、より一般的なパスで処理されるようになりました。

  • ssecmpラベルの削除: ssecmp:ラベル自体は削除されていませんが、その直前のgoto ssecmp;が削除されたため、このラベルへのジャンプがなくなりました。これにより、sseセクションのコードフローがより直線的になり、ssecmpラベルは単にその後の比較ロジックの開始点として機能するようになりました。

この変更により、nr->ullman >= UINFという条件に依存しない、より統一された浮動小数点数比較のコード生成ロジックが適用されるようになります。これは、コンパイラがオペランドの複雑さに関わらず、常に正しいSSE2比較命令を生成することを保証します。

test/fixedbugs/issue4785.go の追加

  • // run: このコメントは、Goのテストフレームワークに対して、このファイルが実行可能なテストであることを示します。
  • package main: 実行可能なプログラムであることを示します。
  • func t(x, y interface{}) interface{}: この関数は、interface{}型の2つの引数xyを受け取ります。これは、Goの型システムにおいて、任意の型の値を扱うことができることを示します。 return x.(float64) > y.(float64): ここがテストの核心です。xyをそれぞれfloat64型に型アサートし、その結果を比較しています。この型アサートと浮動小数点数比較の組み合わせが、以前のcmd/8gのバグをトリガーしていました。
  • func main(): v := t(1.0, 2.0): 関数tを呼び出し、1.02.0というfloat64リテラルを渡しています。期待される結果はfalse(1.0は2.0より大きくない)です。 if v != false { panic("bad comparison") }: 比較結果が期待通りでない場合(つまりtrueが返された場合)、panicを発生させてテストを失敗させます。

このテストケースは、Goコンパイラがinterface{}からfloat64への型アサートを伴う浮動小数点数比較を正しく処理できることを保証します。

関連リンク

  • Go Issue 4785: https://github.com/golang/go/issues/4785 このコミットが修正した具体的なバグに関するGoのIssueトラッカーのエントリです。詳細な議論や再現手順が記載されている場合があります。
  • Go CL 7300109: https://golang.org/cl/7300109 このコミットに対応するGerrit Code Reviewのチェンジリストです。コミットがGitHubにマージされる前のレビュープロセスやコメントを確認できます。

参考にした情報源リンク

  • Go言語の公式ドキュメント: https://golang.org/doc/
  • Intel 64 and IA-32 Architectures Software Developer's Manuals: SSE2命令の詳細については、Intelの公式ドキュメントを参照。
  • コンパイラ設計に関する一般的な書籍やオンラインリソース(Ullman Numberやレジスタ割り当てについて)。
  • Goコンパイラのソースコード(特にsrc/cmd/8g/ディレクトリ)。
  • GoのIssueトラッカー: https://github.com/golang/go/issues
  • GoのGerrit Code Review: https://go-review.googlesource.com/