[インデックス 11795] ファイルの概要
このコミットは、Goコンパイラの8g(おそらくx86アーキテクチャ向けのGoコンパイラバックエンド)における最適化バグを修正するものです。具体的には、float64の重複する移動を最適化しようとした際に、int64をレジスタ化しようとしたことで発生した問題に対処しています。
コミット
commit 5340510203c321545f20d5b456e3a8254ac8f077
Author: Russ Cox <rsc@golang.org>
Date: Fri Feb 10 22:32:02 2012 -0500
8g: fix opt bug
Was trying to optimize a duplicate float64 move
by registerizing an int64.
Fixes #2588.
R=ken2
CC=golang-dev
https://golang.org/cl/5645086
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5340510203c321545f20d5b456e3a8254ac8f077
元コミット内容
このコミットは、Goコンパイラの8g(x86アーキテクチャ向けコンパイラ)における最適化のバグを修正します。このバグは、float64型の値の重複した移動を最適化しようとした際に、誤ってint64型の値をレジスタに割り当てようとしたために発生しました。この問題はGoのIssue 2588として報告されていました。
変更の背景
Goコンパイラは、生成されるコードの効率を高めるために様々な最適化を行います。その一つに、頻繁にアクセスされる値をレジスタに割り当てる「レジスタ割り当て(register allocation)」があります。レジスタはCPU内部の高速な記憶領域であり、メモリへのアクセスよりもはるかに高速です。
このコミットの背景にある問題は、コンパイラがfloat64(64ビット浮動小数点数)の移動を最適化しようとした際に、関連するint64(64ビット整数)の値を誤ってレジスタに割り当てようとしたことにあります。浮動小数点数と整数は、CPUの異なるレジスタセット(例えば、x86のSSEレジスタと汎用レジスタ)を使用することが一般的です。コンパイラが型を誤認し、不適切なレジスタ割り当てを試みた結果、内部コンパイラエラー(ICE)や不正なコード生成を引き起こす可能性がありました。
具体的には、int64の値をfloat64に変換し、そのfloat64値を複数回使用するようなコードパターンで問題が発生していました。コンパイラはfloat64の重複移動を最適化しようとしましたが、その過程で元のint64がメモリオペランドとして使われているにも関わらず、それをレジスタに割り当てようとしていました。これは、int64が浮動小数点演算の文脈で使われる際に、コンパイラの最適化ロジックがその型の特性を正しく扱えていなかったことを示唆しています。
前提知識の解説
- Goコンパイラ (8g): Go言語のコンパイラは、ターゲットアーキテクチャごとに異なるバックエンドを持っています。
8gは、x86(32ビットおよび64ビット)アーキテクチャ向けのGoコンパイラのバックエンドを指します。コンパイラのフロントエンドがGoのソースコードを中間表現に変換し、バックエンドがその中間表現を特定のアーキテクチャの機械語に変換します。 - レジスタ割り当て (Register Allocation): コンパイラの最適化フェーズの一つで、プログラム中で使用される変数をCPUのレジスタに割り当てるプロセスです。レジスタはメモリよりもアクセスが高速なため、適切にレジスタを使用することでプログラムの実行速度を向上させることができます。
- 浮動小数点数 (float64): 倍精度浮動小数点数。IEEE 754規格に準拠した64ビットの浮動小数点数で、Go言語では
float64型として扱われます。通常、CPUの専用の浮動小数点レジスタ(x86ではSSEレジスタなど)で処理されます。 - 整数 (int64): 64ビット整数。Go言語では
int64型として扱われます。通常、CPUの汎用レジスタで処理されます。 reg.c: Goコンパイラのバックエンドにおけるレジスタ割り当てや最適化に関連するコードが含まれているファイルです。Prog構造体: コンパイラの中間表現における命令(instruction)を表す構造体です。ALEMOVL,AFMOVL,AFMOVW,AFMOVV: これらはGoコンパイラ内部で使用されるアセンブリ命令のニーモニック(mnemonic)です。AMOVL: 汎用レジスタ間の32ビット移動命令(x86のMOV命令に相当)。AFMOVL: 浮動小数点レジスタ間の32ビット移動命令(x86のMOVSSまたはMOVSD命令に相当)。AFMOVW: 浮動小数点レジスタ間の16ビット移動命令。AFMOVV: 浮動小数点レジスタ間の64ビット移動命令(x86のMOVSD命令に相当)。ALEAL: x86のLEA(Load Effective Address) 命令に相当。メモリのアドレスを計算してレジスタに格納する命令で、ポインタ演算やオフセット計算によく使われます。
setaddrs(bit): レジスタ割り当てのコンテキストで、特定のアドレスモード(オペランドがレジスタ、メモリ、即値のいずれであるか)を設定する関数です。
技術的詳細
このバグは、src/cmd/8g/reg.cファイル内のregopt関数、特にレジスタ最適化のロジックに起因していました。regopt関数は、コンパイラが生成した命令列を走査し、レジスタ割り当てやその他の最適化を適用します。
問題は、ALEAL命令(アドレス計算)のケースでsetaddrs関数が呼び出されていたにも関わらず、浮動小数点移動命令(AFMOVL, AFMOVW, AFMOVV)のケースではsetaddrsが呼び出されていなかった点にありました。
setaddrs関数は、命令のオペランドがレジスタに割り当てられるべきか、あるいはメモリに留まるべきかを判断する上で重要な役割を果たします。浮動小数点移動命令の場合、ソースオペランドがメモリ上のint64であったとしても、その値が最終的にfloat64として扱われるため、コンパイラはfloat64の文脈で最適化を試みます。しかし、setaddrsが呼び出されないことで、コンパイラはint64のメモリオペランドを不適切にレジスタ化しようとし、結果として型ミスマッチや不正なレジスタ割り当てが発生していました。
この修正は、浮動小数点移動命令に対してもsetaddrsを呼び出すことで、コンパイラがこれらの命令のオペランドのアドレスモードを正しく認識し、適切なレジスタ割り当てを行うようにします。これにより、int64がfloat64に変換される際の最適化パスで、コンパイラが誤ってint64をレジスタ化しようとするバグが解消されます。
また、addmove関数内のfatal("unknown type\\n")という汎用的なエラーメッセージが、fatal("unknown type %E", v->etype)と変更されています。これは、未知の型に遭遇した場合に、その具体的な型情報(v->etype)をエラーメッセージに含めることで、デバッグを容易にするための改善です。これは直接的なバグ修正ではありませんが、コンパイラの堅牢性とデバッグ可能性を高めるための良いプラクティスです。
test/fixedbugs/bug411.goは、このバグを再現するためのテストケースです。int64をfloat64にキャストし、そのfloat64値を複数回関数に渡すというシンプルなコードですが、これが以前のコンパイラでは内部エラーを引き起こしていました。このテストケースが追加されたことで、将来的に同様の回帰バグが発生することを防ぎます。
コアとなるコードの変更箇所
src/cmd/8g/reg.c
--- a/src/cmd/8g/reg.c
+++ b/src/cmd/8g/reg.c
@@ -220,6 +220,9 @@ regopt(Prog *firstp)
* funny
*/
case ALEAL:
+ case AFMOVL:
+ case AFMOVW:
+ case AFMOVV:
setaddrs(bit);
break;
@@ -741,7 +744,7 @@ addmove(Reg *r, int bn, int int rn, int f)
p1->as = AMOVL;
switch(v->etype) {
default:
- fatal("unknown type\\n");
+ fatal("unknown type %E", v->etype);
case TINT8:
case TUINT8:
case TBOOL:
test/fixedbugs/bug411.go
--- /dev/null
+++ b/test/fixedbugs/bug411.go
@@ -0,0 +1,19 @@
+// $G $D/$F.go
+
+// 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 2588. Used to trigger internal compiler error on 8g,
+// because the compiler tried to registerize the int64 being
+// used as a memory operand of a int64->float64 move.
+
+package p
+
+func f1(a int64) {
+ f2(float64(a), float64(a))
+}
+
+func f2(a,b float64) {
+}
+
コアとなるコードの解説
src/cmd/8g/reg.c の変更
-
regopt関数内のswitch文の変更:case ALEAL:の下に、新たにcase AFMOVL:,case AFMOVW:,case AFMOVV:が追加されました。- これらのケースでも
setaddrs(bit);が呼び出されるようになりました。 - 目的: これにより、浮動小数点移動命令(
AFMOVL,AFMOVW,AFMOVV)が処理される際に、そのオペランドのアドレスモードが正しく設定されるようになります。以前はALEAL(アドレス計算)命令のみがこの処理を受けており、浮動小数点移動命令が適切に扱われていませんでした。この修正により、コンパイラがint64からfloat64への変換を伴う浮動小数点移動を最適化する際に、int64のメモリオペランドを誤ってレジスタ化しようとする問題が解消されます。
-
addmove関数内のfatalエラーメッセージの変更:fatal("unknown type\\n");がfatal("unknown type %E", v->etype);に変更されました。- 目的:
addmove関数が未知の型に遭遇した場合に、より詳細なエラーメッセージを出力するように改善されました。%EはGoコンパイラ内部で型情報を文字列に変換するためのフォーマット指定子です。これにより、デバッグ時にどの型が問題を引き起こしたのかを特定しやすくなります。
test/fixedbugs/bug411.go の追加
- このファイルは、GoのIssue 2588で報告されたバグを再現するための新しいテストケースです。
f1関数はint64型の引数aを受け取り、それを2回float64にキャストしてf2関数に渡しています。f2関数は2つのfloat64型の引数を受け取るだけのシンプルな関数です。- 目的: このテストケースは、コンパイラが
int64からfloat64への変換を伴う重複したfloat64の移動を最適化しようとした際に、以前のコンパイラが内部エラー("internal compiler error")を引き起こしていた状況を再現します。このテストが追加されたことで、将来的に同様のバグが再発することを防ぐことができます。
これらの変更により、Goコンパイラは浮動小数点数と整数の間の型変換を伴う最適化をより堅牢に処理できるようになり、特定のコードパターンで発生していた内部コンパイラエラーが解消されました。
関連リンク
- Go Issue 2588: https://github.com/golang/go/issues/2588 (このコミットが修正したIssueへのリンク。ただし、GitHubのIssue番号はGoの公式Issueトラッカーの番号と異なる場合があるため、
golang.org/cl/5645086のリンクがより正確な情報源となる可能性が高いです。) - Go Code Review 5645086: https://golang.org/cl/5645086 (このコミットのコードレビューページ。詳細な議論や変更履歴が確認できます。)
参考にした情報源リンク
- Go言語の公式ドキュメント
- Goコンパイラのソースコード (
src/cmd/8g/reg.c) - GoのIssueトラッカー (Issue 2588)
- Goのコードレビューシステム (CL 5645086)
- x86アセンブリ言語およびコンパイラ最適化に関する一般的な知識
- IEEE 754 浮動小数点数標準