[インデックス 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_X0
とD_X15
に置き換えられています。D_X0
,D_X15
: GoコンパイラがAMD64アーキテクチャのSSEレジスタX0
からX15
を内部的に表現するための定数です。D_X0
はX0
レジスタに対応し、D_X15
はX15
レジスタに対応します。これらの定数は、実際のレジスタ番号とコンパイラ内部の表現をマッピングするために使用されます。
ビットマスク表現
コンパイラは、複数のレジスタの状態を効率的に管理するために、ビットマスクを使用することがよくあります。例えば、32ビットの整数で各ビットが特定のレジスタの使用状況を表すようにします。ビット i
がセットされていれば、レジスタ i
が使用中であることを意味します。
このコミットの文脈では、SSEレジスタ X0
から X15
をビットマスクにマッピングする際に、オフセットの計算が誤っていたことが問題でした。
技術的詳細
このコミットの技術的詳細は、Goコンパイラ 6g
の src/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);
}
変更点:
- コメントの修正:
16 X5 (FREGMIN)
が16 X0
に変更されました。26 X15 (FREGEXT)
が31 X15
に変更されました。 これは、ビットマスクのオフセットがX0
を基準に計算されるべきであり、X5
を基準にしていた以前の仮定が誤っていたことを示しています。また、ビットマスクがカバーするレジスタの範囲がX0
からX15
まで(合計16個のレジスタ)であることを明確にしています。16
から31
までのビットがSSEレジスタに対応することになります。
- レジスタ範囲チェックの修正:
if(f < FREGMIN || f > FREGEXT)
がif(f < D_X0 || f > D_X15)
に変更されました。 これは、有効な浮動小数点レジスタの範囲をFREGMIN
からFREGEXT
ではなく、D_X0
からD_X15
までと正しく定義し直しています。
- ビットマスク計算の修正:
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;
}
変更点:
- レジスタ番号計算の修正:
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)
への変更:- これは、浮動小数点レジスタの有効な範囲を定義する定数を
FREGMIN
とFREGEXT
からD_X0
とD_X15
に変更しています。D_X0
はX0
レジスタの内部表現、D_X15
はX15
レジスタの内部表現に対応します。これにより、コンパイラが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()
のコンパイル自体が失敗するか、誤ったコードが生成されて異なる結果になる可能性があります。このアサートは、コンパイルが成功し、かつ正しい結果が得られることを検証します。
このテストは、コンパイラのレジスタ割り当てロジックが正しく機能していることを確認するための重要な回帰テストとして機能します。
関連リンク
- Go Issue #4138: https://code.google.com/p/go/issues/detail?id=4138 (古いGoogle Codeのリンクですが、GoのIssueトラッカーでこの問題の詳細を確認できます)
- Go Code Review 6558043: https://golang.org/cl/6558043 (このコミットのコードレビューページ)
参考にした情報源リンク
- 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検索を通じて得られた情報)