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

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

このコミットでは、Goコンパイラ 6g (AMD64アーキテクチャ向け) におけるSSE (Streaming SIMD Extensions) レジスタの内部的な取り扱いに関するバグが修正されています。具体的には、浮動小数点レジスタの番号付けの誤りに起因する内部エラーが解消されました。

変更されたファイルは以下の通りです。

  • src/cmd/6g/reg.c: 浮動小数点レジスタとビットマスク間の変換ロジックが修正されました。
  • test/fixedbugs/bug453.go: このバグを再現し、修正を検証するための新しいテストケースが追加されました。

コミット

commit 36df358a309a7a95438c701ec5687bf4f22d0b28
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date:   Sun Sep 23 18:22:03 2012 +0200

    cmd/6g: fix internal error with SSE registers.
    
    Revision 63f7abcae015 introduced a bug caused by
    code assuming registers started at X5, not X0.
    
    Fixes #4138.
    
    R=rsc
    CC=golang-dev, remy
    https://golang.org/cl/6558043

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

https://github.com/golang/go/commit/36df358a309a7a95438c701ec568bf4f22d0b28

元コミット内容

このコミットは、Goコンパイラ 6g (AMD64アーキテクチャ用) におけるSSEレジスタの内部エラーを修正するものです。以前のコミット 63f7abcae015 によって導入されたバグが原因で、レジスタが X0 ではなく X5 から始まるとコードが誤って仮定していました。この修正は、Issue #4138 を解決します。

変更の背景

この変更は、Goコンパイラ 6g が浮動小数点演算のためにSSEレジスタを割り当てる際に発生していた内部エラーを修正するために行われました。具体的には、コミットメッセージに記載されている Revision 63f7abcae015 (これは go.text: add text/template というコミットで、直接的な関連は薄いように見えますが、おそらくそのコミットに含まれる他の変更が影響したか、あるいはコミットハッシュの記載ミスである可能性があります。Goのコミット履歴を詳細に追う必要がありますが、このコミットの文脈では、特定のレジスタ割り当てロジックの変更が原因とされています。) が、SSEレジスタの開始位置に関する誤った仮定を導入しました。

AMD64アーキテクチャには X0 から X15 までの16個のSSEレジスタ(XMMレジスタとも呼ばれる)が存在します。これらのレジスタは浮動小数点演算やSIMD (Single Instruction, Multiple Data) 演算に使用されます。コンパイラは、プログラムの変数や中間結果をこれらのレジスタに効率的に割り当てることで、高速な実行を実現します。

しかし、以前のコードでは、レジスタの内部的なビットマスク表現と実際のレジスタ番号とのマッピングにおいて、レジスタが X0 からではなく X5 から始まると誤って認識していました。この誤った認識により、コンパイラが利用できるSSEレジスタの範囲が不必要に制限され、特に多くの浮動小数点変数を使用する関数において、コンパイラの内部エラーやコード生成の失敗を引き起こしていました。

test/fixedbugs/bug453.go で追加されたテストケースは、この問題を明確に示しています。このテストは、12個の浮動小数点変数を使用する formula() 関数を定義しており、これによりコンパイラが多くのSSEレジスタを必要とする状況を作り出しています。バグが存在する状態では、コンパイラは必要なレジスタを割り当てることができず、内部エラーが発生していました。

前提知識の解説

SSE (Streaming SIMD Extensions) レジスタ

SSEは、Intelによって導入されたCPU命令セットの拡張機能で、主に浮動小数点演算とSIMD (Single Instruction, Multiple Data) 演算を高速化するために設計されました。AMD64 (x86-64) アーキテクチャでは、XMM0 から XMM15 までの16個の128ビットレジスタが提供されます。これらは通常、X0 から X15 と略記されることがあります。

  • 浮動小数点演算: float32 (単精度浮動小数点数) や float64 (倍精度浮動小数点数) の値を格納し、それらに対する加算、乗算などの演算を行います。
  • SIMD演算: 複数のデータ要素(例えば、4つの単精度浮動小数点数)を1つのレジスタにパックし、単一の命令で同時に処理することができます。これにより、メディア処理、科学技術計算、ゲームなどで高いパフォーマンスを発揮します。

コンパイラのレジスタ割り当て

コンパイラは、ソースコードを機械語に変換する際に、プログラムの変数や中間結果をCPUのレジスタに割り当てます。レジスタはCPU内部の高速な記憶領域であり、メモリへのアクセスよりもはるかに高速です。効率的なレジスタ割り当ては、生成されるコードのパフォーマンスに大きく影響します。

Goコンパイラのようなコンパイラは、レジスタを管理するために内部的なデータ構造を使用します。これには、各レジスタの状態(使用中か、空いているか)、レジスタに格納されている値、そしてレジスタを識別するための内部的な番号付けやビットマスク表現などが含まれます。

FREGMIN, FREGEXT, D_X0, D_X15

これらはGoコンパイラの内部で使われる定数やマクロで、SSEレジスタの範囲や種類を定義するために使用されます。

  • FREGMIN, FREGEXT: 以前のコードでSSEレジスタの最小値と最大値を表すために使われていた可能性のある定数です。このコミットでは、これらが D_X0D_X15 に置き換えられています。
  • D_X0, D_X15: GoコンパイラがAMD64アーキテクチャのSSEレジスタ X0 から X15 を内部的に表現するための定数です。D_X0X0 レジスタに対応し、D_X15X15 レジスタに対応します。これらの定数は、実際のレジスタ番号とコンパイラ内部の表現をマッピングするために使用されます。

ビットマスク表現

コンパイラは、複数のレジスタの状態を効率的に管理するために、ビットマスクを使用することがよくあります。例えば、32ビットの整数で各ビットが特定のレジスタの使用状況を表すようにします。ビット i がセットされていれば、レジスタ i が使用中であることを意味します。

このコミットの文脈では、SSEレジスタ X0 から X15 をビットマスクにマッピングする際に、オフセットの計算が誤っていたことが問題でした。

技術的詳細

このコミットの技術的詳細は、Goコンパイラ 6gsrc/cmd/6g/reg.c ファイル内の FtoB (Float to Bitmask) および BtoF (Bitmask to Float) 関数におけるSSEレジスタの番号付けの修正に集約されます。

FtoB 関数 (Float to Bitmask)

この関数は、Goコンパイラが内部的に使用する浮動小数点レジスタの番号 f を、レジスタのビットマスク表現に変換します。

変更前:

/*
 *	bit	reg
 *	16	X5 (FREGMIN)
 *	...
 *	26	X15 (FREGEXT)
 */
int32
FtoB(int f)
{
	if(f < FREGMIN || f > FREGEXT)
		return 0;
	return 1L << (f - FREGMIN + 16);
}

変更点:

  1. コメントの修正:
    • 16 X5 (FREGMIN)16 X0 に変更されました。
    • 26 X15 (FREGEXT)31 X15 に変更されました。 これは、ビットマスクのオフセットが X0 を基準に計算されるべきであり、X5 を基準にしていた以前の仮定が誤っていたことを示しています。また、ビットマスクがカバーするレジスタの範囲が X0 から X15 まで(合計16個のレジスタ)であることを明確にしています。16 から 31 までのビットがSSEレジスタに対応することになります。
  2. レジスタ範囲チェックの修正:
    • if(f < FREGMIN || f > FREGEXT)if(f < D_X0 || f > D_X15) に変更されました。 これは、有効な浮動小数点レジスタの範囲を FREGMIN から FREGEXT ではなく、D_X0 から D_X15 までと正しく定義し直しています。
  3. ビットマスク計算の修正:
    • return 1L << (f - FREGMIN + 16);return 1L << (f - D_X0 + 16); に変更されました。 この行がバグの核心でした。ビットマスクの計算において、レジスタ番号 f から FREGMIN (つまり X5) を引いていたため、X0 から X4 までのレジスタが正しくビットマスクにマッピングされず、結果としてコンパイラがこれらのレジスタを適切に利用できない状態になっていました。D_X0 を引くことで、X0 を基準とした正しいオフセットでビットマスクが生成されるようになります。+ 16 は、ビットマスクの16ビット目からSSEレジスタのビットが始まることを示しています。

BtoF 関数 (Bitmask to Float)

この関数は、レジスタのビットマスク表現 b を、Goコンパイラが内部的に使用する浮動小数点レジスタの番号に変換します。

変更前:

int
BtoF(int32 b)
{
	b &= 0xFFFF0000L;
	if(b == 0)
		return 0;
	return bitno(b) - 16 + FREGMIN;
}

変更点:

  1. レジスタ番号計算の修正:
    • return bitno(b) - 16 + FREGMIN;return bitno(b) - 16 + D_X0; に変更されました。 bitno(b) は、ビットマスク b の中で最も低い位置にあるセットされたビットのインデックスを返します。このインデックスから 16 を引くことで、SSEレジスタに対応するビットの相対的な位置が得られます。以前はこれに FREGMIN を加えていたため、X5 を基準としたレジスタ番号が返されていました。D_X0 を加えることで、X0 を基準とした正しいレジスタ番号が返されるようになります。

test/fixedbugs/bug453.go

この新しいテストファイルは、このバグが修正されたことを検証するために追加されました。

  • formula() 関数内で12個の float32 型の変数を宣言し、それらに対して複数の浮動小数点演算を実行しています。
  • AMD64アーキテクチャには X0 から X15 までの16個のSSEレジスタがありますが、このバグが存在すると、コンパイラは X5 からしかレジスタを認識しないため、利用可能なレジスタ数が制限されます。
  • 12個の変数を同時に使用することで、コンパイラが利用可能なレジスタ数を使い果たし、内部エラーを引き起こす状況を意図的に作り出しています。
  • 修正が適用されると、コンパイラは X0 から X15 までのすべてのSSEレジスタを正しく認識し、割り当てることができるため、このテストは正常に実行され、x != 7.0 のパニックが発生しなくなります。

このテストは、コンパイラのレジスタ割り当てロジックが正しく機能していることを確認するための重要な回帰テストとして機能します。

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

diff --git a/src/cmd/6g/reg.c b/src/cmd/6g/reg.c
index 398e6a70d9..a139b1caa3 100644
--- a/src/cmd/6g/reg.c
+++ b/src/cmd/6g/reg.c
@@ -1600,16 +1600,16 @@ BtoR(int32 b)
 
 /*
  *\tbit\treg
- *\t16\tX5 (FREGMIN)
+ *\t16\tX0
  *\t...\
- *\t26\tX15 (FREGEXT)
+ *\t31\tX15
  */
 int32
 FtoB(int f)
 {
-\tif(f < FREGMIN || f > FREGEXT)\
+\tif(f < D_X0 || f > D_X15)\
 \t\treturn 0;\
-\treturn 1L << (f - FREGMIN + 16);\
+\treturn 1L << (f - D_X0 + 16);\
 }
 
 int
@@ -1619,7 +1619,7 @@ BtoF(int32 b)
 \tb &= 0xFFFF0000L;\
 \tif(b == 0)\
 \t\treturn 0;\
-\treturn bitno(b) - 16 + FREGMIN;\
+\treturn bitno(b) - 16 + D_X0;\
 }
 
 void
diff --git a/test/fixedbugs/bug453.go b/test/fixedbugs/bug453.go
new file mode 100644
index 0000000000..136abefb7d
--- /dev/null
+++ b/test/fixedbugs/bug453.go
@@ -0,0 +1,39 @@
+// run
+
+// Copyright 2012 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 4138: bug in floating-point registers numbering.
+// Makes 6g unable to use more than 11 registers.
+
+package main
+
+func formula() float32 {
+	mA := [1]float32{1.0}
+	det1 := mA[0]
+	det2 := mA[0]
+	det3 := mA[0]
+	det4 := mA[0]
+	det5 := mA[0]
+	det6 := mA[0]
+	det7 := mA[0]
+	det8 := mA[0]
+	det9 := mA[0]
+	det10 := mA[0]
+	det11 := mA[0]
+	det12 := mA[0]
+
+	return det1 + det2*det3 +
+		det4*det5 + det6*det7 +
+		det8*det9 + det10*det11 +
+		det12
+}
+
+func main() {
+	x := formula()
+	if x != 7.0 {
+		println(x, 7.0)
+		panic("x != 7.0")
+	}
+}

コアとなるコードの解説

src/cmd/6g/reg.c の変更

このファイルは、Goコンパイラ 6g におけるレジスタ管理と割り当てロジックの一部を扱っています。

  • コメントの修正:

    • FtoB 関数のコメントブロックが更新され、ビットマスクの16ビット目が X0 レジスタに対応し、31ビット目が X15 レジスタに対応することが明記されました。これは、SSEレジスタのビットマスク表現が X0 を基準としていることを示しています。以前のコメントは X5 を基準としており、これが誤解の原因でした。
  • FtoB 関数の変更:

    • if(f < FREGMIN || f > FREGEXT) から if(f < D_X0 || f > D_X15) への変更:
      • これは、浮動小数点レジスタの有効な範囲を定義する定数を FREGMINFREGEXT から D_X0D_X15 に変更しています。D_X0X0 レジスタの内部表現、D_X15X15 レジスタの内部表現に対応します。これにより、コンパイラが X0 から X15 までのすべてのSSEレジスタを正しく認識し、範囲外のレジスタ番号が渡された場合に適切に処理できるようになります。
    • return 1L << (f - FREGMIN + 16); から return 1L << (f - D_X0 + 16); への変更:
      • この行は、浮動小数点レジスタの内部番号 f を、対応するビットマスクのビット位置に変換します。以前のコードでは f から FREGMIN (おそらく X5 に対応) を引いていたため、X0 から X4 までのレジスタが正しくビットマスクにマッピングされませんでした。D_X0 を引くことで、X0 を基準とした正しいオフセットが計算され、すべてのSSEレジスタがビットマスク上で正しい位置にマッピングされるようになります。+ 16 は、ビットマスクの16ビット目からSSEレジスタのビットが始まることを示しています。
  • BtoF 関数の変更:

    • return bitno(b) - 16 + FREGMIN; から return bitno(b) - 16 + D_X0; への変更:
      • この行は FtoB の逆の操作を行い、ビットマスク b から浮動小数点レジスタの内部番号を導出します。bitno(b) はビットマスク内でセットされている最も低いビットのインデックスを返します。以前のコードでは、このインデックスから 16 を引いた後、FREGMIN を加えていたため、X5 を基準としたレジスタ番号が返されていました。D_X0 を加えることで、X0 を基準とした正しいレジスタ番号が返されるようになります。

これらの変更により、Goコンパイラ 6g はAMD64アーキテクチャのSSEレジスタを X0 から X15 まで正しく認識し、割り当てることができるようになり、浮動小数点演算を多用するコードのコンパイル時の内部エラーが解消されました。

test/fixedbugs/bug453.go の追加

このファイルは、このコミットで修正されたバグを具体的に再現し、修正が正しく機能することを確認するためのテストケースです。

  • // run: このコメントは、Goのテストフレームワークに対して、このファイルが実行可能なテストであることを示します。
  • // Issue 4138: bug in floating-point registers numbering.: このテストが解決する特定のIssue番号と、問題の概要(浮動小数点レジスタの番号付けのバグ)を示しています。
  • // Makes 6g unable to use more than 11 registers.: このバグの具体的な影響を説明しています。つまり、6g コンパイラが11個以上のSSEレジスタを使用できない状態になっていたことを示唆しています。AMD64には16個のSSEレジスタがあるため、これは重大な制限です。
  • formula() 関数:
    • mA := [1]float32{1.0}: 単一要素の float32 配列を初期化しています。
    • det1 から det12 までの12個の float32 変数を宣言し、mA[0] で初期化しています。これにより、コンパイラが多くの浮動小数点レジスタを必要とする状況を作り出します。
    • これらの変数を使った複雑な浮動小数点演算を実行しています。この演算自体は特定の数学的意味を持つものではなく、コンパイラが多くのレジスタを同時に使用する必要がある状況を作り出すことが目的です。
  • main() 関数:
    • x := formula(): formula 関数を呼び出し、結果を x に格納します。
    • if x != 7.0 { ... panic("x != 7.0") }: formula() の計算結果が 7.0 であることをアサートしています。もしバグが修正されていなければ、formula() のコンパイル自体が失敗するか、誤ったコードが生成されて異なる結果になる可能性があります。このアサートは、コンパイルが成功し、かつ正しい結果が得られることを検証します。

このテストは、コンパイラのレジスタ割り当てロジックが正しく機能していることを確認するための重要な回帰テストとして機能します。

関連リンク

参考にした情報源リンク

  • AMD64 Architecture Programmer's Manual Volume 1: Application Programming (SSE registers)
  • Go言語のコンパイラに関する一般的な知識
  • GoのIssueトラッカー (Issue #4138)
  • Goのコードレビューシステム (CL 6558043)
  • src/cmd/6g/reg.c および test/fixedbugs/bug453.go のソースコード
  • bitno 関数の一般的な意味 (ビットマスク操作)
  • Goコンパイラの内部構造に関する一般的な情報 (Web検索を通じて得られた情報)