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

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

このコミットは、Go言語のコンパイラ 8g における整数定数を含む算術最適化に関するバグ(Issue 3835)を再現するためのテストケース test/fixedbugs/bug452.go を追加するものです。以前 bug451 として存在していたテストが、何らかの理由で削除された後に bug452 として復元された経緯があります。

コミット

commit 6d0e3242eb296922995c6335de4d5c8bcd88728a
Author: Russ Cox <rsc@golang.org>
Date:   Fri Aug 31 15:43:27 2012 -0400

    test: restore nigel's bug451 as bug452.
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/6497066
---\n test/fixedbugs/bug452.go | 38 ++++++++++++++++++++++++++++++++++++++\n 1 file changed, 38 insertions(+)

diff --git a/test/fixedbugs/bug452.go b/test/fixedbugs/bug452.go
new file mode 100644
index 0000000000..d2e4a0b44a
--- /dev/null
+++ b/test/fixedbugs/bug452.go
@@ -0,0 +1,38 @@
+// 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 3835: 8g tries to optimize arithmetic involving integer
+// constants, but can run out of registers in the process.
+
+package main
+
+var a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G int
+
+func foo() int {
+	return a + 1 + b + 2 + c + 3 + d + 4 + e + 5 + f + 6 + g + 7 + h + 8 + i + 9 + j + 10 +
+	k + 1 + l + 2 + m + 3 + n + 4 + o + 5 + p + 6 + q + 7 + r + 8 + s + 9 + t + 10 +
+	u + 1 + v + 2 + w + 3 + x + 4 + y + 5 + z + 6 + A + 7 + B + 8 + C + 9 + D + 10 +
+	E + 1 + F + 2 + G + 3
+}
+
+func bar() int8 {
+	var (
+		W int16
+		X int32
+		Y int32
+		Z int32
+	)
+	return int8(W+int16(X+3)+3) * int8(Y+3+Z*3)
+}
+
+func main() {
+	if foo() == 0 {
+		panic("foo")
+	}
+	if bar() == 0 {
+		panic("bar")
+	}
+}

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

https://github.com/golang/go/commit/6d0e3242eb296922995c6335de4d5c8bcd88728a

元コミット内容

このコミットは、test: restore nigel's bug451 as bug452. という簡潔なメッセージが示す通り、以前 bug451 として存在していたテストケースを bug452 として復元するものです。具体的な変更内容は、test/fixedbugs/bug452.go という新しいファイルを追加し、その中に特定のGoコードを記述することです。このコードは、Goコンパイラ 8g が整数定数を含む算術演算を最適化する際に発生する可能性のあるレジスタ不足のバグ(Issue 3835)を再現することを目的としています。

変更の背景

この変更の背景には、Go言語のコンパイラ 8g が抱えていた特定の最適化バグがあります。コミットメッセージと追加されたファイルのコメントから、「Issue 3835: 8g tries to optimize arithmetic involving integer constants, but can run out of registers in the process.」という問題が明らかになります。

Goコンパイラは、生成されるバイナリのパフォーマンスを向上させるために、コードの最適化を行います。特に、定数を含む算術演算は、コンパイル時に計算結果を確定させることで、実行時のオーバーヘッドを削減できる可能性があります。しかし、この最適化プロセスにおいて、コンパイラが利用可能なCPUレジスタを使い果たしてしまうという問題が発生していました。レジスタはCPUが高速にアクセスできる一時的な記憶領域であり、その数が限られているため、効率的なレジスタ割り当てはコンパイラの重要な課題の一つです。

このバグは、特に多数の変数と定数を含む複雑な算術式において顕著に現れたと考えられます。コンパイラがこれらの式を最適化しようとする際に、中間結果を保持するためのレジスタが不足し、正しくコードを生成できない、あるいはクラッシュするといった問題を引き起こしていた可能性があります。

bug451 というテストケースが以前存在し、それが削除された後、このコミットで bug452 として復元されたのは、このバグが再発したか、あるいはその重要性が再認識されたためと考えられます。テストケースを復元することで、将来的に同様のバグが導入されることを防ぎ、コンパイラの安定性と正確性を保証する目的があります。

前提知識の解説

このコミットを理解するためには、以下の前提知識が役立ちます。

Go言語のコンパイラ (8g)

Go言語の初期のコンパイラは、ターゲットアーキテクチャごとに異なる名前を持っていました。8g は、x86-64アーキテクチャ(64ビットIntel/AMDプロセッサ)向けのGoコンパイラを指します。Goのツールチェーンは、ソースコードを機械語に変換する際に、このコンパイラを使用します。コンパイラの主な役割は、構文解析、意味解析、中間コード生成、最適化、そして最終的な機械語コード生成です。

コンパイラの最適化

コンパイラの最適化とは、生成される機械語コードの実行速度やサイズを改善するためのプロセスです。様々な最適化手法がありますが、このコミットに関連するのは「定数畳み込み (Constant Folding)」や「共通部分式除去 (Common Subexpression Elimination)」といった算術最適化です。

  • 定数畳み込み: コンパイル時に計算結果が確定する式(例: 1 + 2)を、その結果(3)に置き換える最適化です。これにより、実行時の計算が不要になり、パフォーマンスが向上します。
  • レジスタ割り当て (Register Allocation): コンパイラが、プログラムの実行中に頻繁にアクセスされる変数の値をCPUのレジスタに割り当てるプロセスです。レジスタは非常に高速な記憶領域であるため、レジスタに割り当てられたデータへのアクセスは、メモリへのアクセスよりもはるかに高速です。しかし、レジスタの数は限られているため、どの変数をレジスタに割り当てるか、いつレジスタからメモリに退避させるか(スピルアウト)は、コンパイラの重要な最適化課題です。

CPUレジスタ

CPUレジスタは、CPU内部にある非常に高速なデータ記憶領域です。プログラムの実行中に、CPUはレジスタに格納されたデータを直接操作します。レジスタの数はアーキテクチャによって異なり、例えばx86-64アーキテクチャでは汎用レジスタが限られた数しかありません。コンパイラは、これらの限られたレジスタを効率的に利用して、生成されるコードのパフォーマンスを最大化しようとします。レジスタが不足すると、コンパイラは変数の値をメモリに退避させる必要があり、これはパフォーマンスの低下につながります。

Goのテストフレームワーク (// run)

Go言語には、標準でテストをサポートする機能が組み込まれています。test/fixedbugs ディレクトリは、特定のバグを再現し、その修正を検証するためのテストケースを格納する場所です。ファイルの先頭にある // run コメントは、このファイルが実行可能なテストであることを示し、go test コマンドによって実行される際に、特別な処理が行われることを示唆しています。これらのテストは、通常、プログラムがパニックを起こさないことや、特定の結果を返すことを検証します。

技術的詳細

このコミットが対処しようとしている技術的な問題は、Goコンパイラ 8g のレジスタ割り当てアルゴリズムの限界に起因しています。

8g コンパイラは、Goのソースコードを機械語に変換する過程で、中間表現(IR)を生成し、そのIRに対して様々な最適化を適用します。整数定数を含む算術演算の最適化は、コンパイル時に可能な限り多くの計算を完了させ、実行時の負荷を軽減することを目的としています。例えば、a + 1 + b + 2 のような式があった場合、コンパイラは (a + b) + (1 + 2) のように定数をまとめて計算し、a + b + 3 のような形に変換しようとします。

しかし、この最適化の過程で、コンパイラは中間結果を一時的に保持するためにレジスタを必要とします。特に、foo() 関数のように多数の変数と定数が連続して加算されるような長い式の場合、コンパイラは多くのレジスタを同時に使用しようとします。

func foo() int {
	return a + 1 + b + 2 + c + 3 + d + 4 + e + 5 + f + 6 + g + 7 + h + 8 + i + 9 + j + 10 +
	k + 1 + l + 2 + m + 3 + n + 4 + o + 5 + p + 6 + q + 7 + r + 8 + s + 9 + t + 10 +
	u + 1 + v + 2 + w + 3 + x + 4 + y + 5 + z + 6 + A + 7 + B + 8 + C + 9 + D + 10 +
	E + 1 + F + 2 + G + 3
}

このような式では、コンパイラは各加算の結果をレジスタに保持し、次の加算に利用しようとします。もし、利用可能なレジスタの数が、中間結果を保持するために必要なレジスタの数よりも少なかった場合、コンパイラは一部のレジスタの内容をメモリに「スピルアウト」(退避)させる必要があります。スピルアウトは、メモリへのアクセスを伴うため、パフォーマンスが低下します。さらに悪い場合、コンパイラのレジスタ割り当てアルゴリズムがこの状況を適切に処理できず、コンパイルエラーや不正なコード生成を引き起こす可能性がありました。

bar() 関数は、異なる整数型(int16, int32, int8)の混合演算と、乗算を含むより複雑な式をテストしています。

func bar() int8 {
	var (
		W int16
		X int32
		Y int32
		Z int32
	)
	return int8(W+int16(X+3)+3) * int8(Y+3+Z*3)
}

このような型変換と複雑な演算が絡む場合も、コンパイラは中間結果の型とサイズを考慮しながらレジスタを割り当てる必要があり、同様のレジスタ不足の問題が発生する可能性がありました。

このテストケースは、これらの特定のシナリオでコンパイラがレジスタ不足に陥り、正しくコードを生成できないというバグを再現するために設計されています。テストが成功するということは、コンパイラがこれらの複雑な算術式を、レジスタを使い果たすことなく、正しくコンパイルできるようになったことを意味します。

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

このコミットにおけるコアとなるコードの変更箇所は、以下の新しいファイルの追加です。

test/fixedbugs/bug452.go

このファイルは、38行からなるGoのソースコードで、特定のコンパイラバグを再現するためのテストケースを含んでいます。

コアとなるコードの解説

追加された test/fixedbugs/bug452.go ファイルは、Goコンパイラ 8g のレジスタ割り当てに関するバグを検証するためのものです。

// 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 3835: 8g tries to optimize arithmetic involving integer
// constants, but can run out of registers in the process.

package main

var a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G int

func foo() int {
	return a + 1 + b + 2 + c + 3 + d + 4 + e + 5 + f + 6 + g + 7 + h + 8 + i + 9 + j + 10 +
	k + 1 + l + 2 + m + 3 + n + 4 + o + 5 + p + 6 + q + 7 + r + 8 + s + 9 + t + 10 +
	u + 1 + v + 2 + w + 3 + x + 4 + y + 5 + z + 6 + A + 7 + B + 8 + C + 9 + D + 10 +
	E + 1 + F + 2 + G + 3
}

func bar() int8 {
	var (
		W int16
		X int32
		Y int32
		Z int32
	)
	return int8(W+int16(X+3)+3) * int8(Y+3+Z*3)
}

func main() {
	if foo() == 0 {
		panic("foo")
	}
	if bar() == 0 {
		panic("bar")
	}
}
  1. // run: このコメントは、Goのテストシステムに対して、このファイルが実行可能なテストであることを示します。go test コマンドがこのファイルを見つけると、コンパイルして実行し、パニックが発生しないか、あるいは特定の条件が満たされるかを検証します。

  2. コメント // Issue 3835: ...: このコメントは、このテストケースがGoのIssueトラッカーにおけるIssue 3835に関連していることを明確に示しています。問題は「8g コンパイラが整数定数を含む算術演算を最適化しようとする際に、レジスタを使い果たす可能性がある」というものです。これは、コンパイラのレジスタ割り当てアルゴリズムの限界を突くテストであることを示唆しています。

  3. グローバル変数宣言:

    var a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G int
    

    大量のグローバル変数(int型)が宣言されています。これらの変数は foo() 関数で使用され、コンパイラが多くの異なる値を同時に扱う必要がある状況を作り出します。グローバル変数はゼロ値で初期化されるため、ここではすべて 0 です。

  4. foo() 関数:

    func foo() int {
    	return a + 1 + b + 2 + c + 3 + d + 4 + e + 5 + f + 6 + g + 7 + h + 8 + i + 9 + j + 10 +
    	k + 1 + l + 2 + m + 3 + n + 4 + o + 5 + p + 6 + q + 7 + r + 8 + s + 9 + t + 10 +
    	u + 1 + v + 2 + w + 3 + x + 4 + y + 5 + z + 6 + A + 7 + B + 8 + C + 9 + D + 10 +
    	E + 1 + F + 2 + G + 3
    }
    

    この関数は、大量のグローバル変数と整数定数を連続して加算する非常に長い式を返します。

    • 目的: この長い式は、コンパイラが算術最適化を行う際に、多数の中間結果を保持するために多くのレジスタを必要とする状況を意図的に作り出しています。もしコンパイラのレジスタ割り当てが不十分であれば、この式をコンパイルする際にレジスタ不足に陥り、エラーを発生させるか、不正なコードを生成する可能性があります。
    • 期待される動作: 変数はすべて 0 で初期化されているため、この関数の戻り値はすべての定数の合計になります。1+2+...+10 が3回繰り返され、最後に 1+2+3 が加算されます。1 から 10 までの合計は 55 なので、55 * 3 + (1+2+3) = 165 + 6 = 171 となります。したがって、foo()171 を返すはずです。テストの main 関数では foo() == 0 でパニックするかをチェックしており、1710 ではないため、パニックしないことが期待されます。
  5. bar() 関数:

    func bar() int8 {
    	var (
    		W int16
    		X int32
    		Y int32
    		Z int32
    	)
    	return int8(W+int16(X+3)+3) * int8(Y+3+Z*3)
    }
    

    この関数は、異なる整数型(int16, int32, int8)の混合演算と、乗算を含むより複雑な式を返します。

    • 目的: 型変換と複雑な算術演算が絡む場合も、コンパイラがレジスタ割り当てで問題を抱える可能性があるため、このシナリオをテストします。
    • 期待される動作: W, X, Y, Z はすべてゼロ値で初期化されるため、0 です。
      • W+int16(X+3)+30 + int16(0+3) + 3 = 0 + 3 + 3 = 6 となります。
      • Y+3+Z*30 + 3 + 0*3 = 3 + 0 = 3 となります。
      • 最終的に int8(6) * int8(3) = 18 となります。
      • したがって、bar()18 を返すはずです。テストの main 関数では bar() == 0 でパニックするかをチェックしており、180 ではないため、パニックしないことが期待されます。
  6. main() 関数:

    func main() {
    	if foo() == 0 {
    		panic("foo")
    	}
    	if bar() == 0 {
    		panic("bar")
    	}
    }
    

    main 関数は、foo()bar() を呼び出し、それぞれの戻り値が 0 でないことを確認します。もし 0 であれば panic を発生させます。このテストの目的は、コンパイラがこれらの関数を正しくコンパイルし、期待される非ゼロの値を返すことを確認することです。もしコンパイラがバグによってクラッシュしたり、不正なコードを生成して 0 を返したりした場合、このテストは失敗(パニック)します。

このテストケースは、Goコンパイラの堅牢性を保証するために非常に重要であり、特定の最適化パスにおける潜在的なレジスタ割り当ての問題を早期に検出する役割を果たします。

関連リンク

  • Go Issue 3835: https://github.com/golang/go/issues/3835 (このコミットが参照しているIssueの可能性が高いですが、正確なリンクはGoのIssueトラッカーで確認が必要です。古いIssueはアーカイブされている場合があります。)
  • Go Code Review: https://golang.org/cl/6497066 (コミットメッセージに記載されているChange Listのリンク)

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Goコンパイラの内部構造に関する資料 (一般的なコンパイラのレジスタ割り当てに関する知識)
  • GoのIssueトラッカー (Issue 3835に関する詳細情報)
  • Goのコードレビューシステム (CL 6497066に関する議論)

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

このコミットは、Go言語のコンパイラ 8g における整数定数を含む算術最適化に関するバグ(Issue 3835)を再現するためのテストケース test/fixedbugs/bug452.go を追加するものです。以前 bug451 として存在していたテストが、何らかの理由で削除された後に bug452 として復元された経緯があります。

コミット

commit 6d0e3242eb296922995c6335de4d5c8bcd88728a
Author: Russ Cox <rsc@golang.org>
Date:   Fri Aug 31 15:43:27 2012 -0400

    test: restore nigel's bug451 as bug452.
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/6497066
---\n test/fixedbugs/bug452.go | 38 ++++++++++++++++++++++++++++++++++++++\n 1 file changed, 38 insertions(+)

diff --git a/test/fixedbugs/bug452.go b/test/fixedbugs/bug452.go
new file mode 100644
index 0000000000..d2e4a0b44a
--- /dev/null
+++ b/test/fixedbugs/bug452.go
@@ -0,0 +1,38 @@
+// 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 3835: 8g tries to optimize arithmetic involving integer
+// constants, but can run out of registers in the process.
+
+package main
+
+var a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G int
+
+func foo() int {
+	return a + 1 + b + 2 + c + 3 + d + 4 + e + 5 + f + 6 + g + 7 + h + 8 + i + 9 + j + 10 +
+	k + 1 + l + 2 + m + 3 + n + 4 + o + 5 + p + 6 + q + 7 + r + 8 + s + 9 + t + 10 +
+	u + 1 + v + 2 + w + 3 + x + 4 + y + 5 + z + 6 + A + 7 + B + 8 + C + 9 + D + 10 +
+	E + 1 + F + 2 + G + 3
+}
+
+func bar() int8 {
+	var (
+		W int16
+		X int32
+		Y int32
+		Z int32
+	)
+	return int8(W+int16(X+3)+3) * int8(Y+3+Z*3)
+}
+
+func main() {
+	if foo() == 0 {
+		panic("foo")
+	}
+	if bar() == 0 {
+		panic("bar")
+	}
+}

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

https://github.com/golang/go/commit/6d0e3242eb296922995c6335de4d5c8bcd88728a

元コミット内容

このコミットは、test: restore nigel's bug451 as bug452. という簡潔なメッセージが示す通り、以前 bug451 として存在していたテストケースを bug452 として復元するものです。具体的な変更内容は、test/fixedbugs/bug452.go という新しいファイルを追加し、その中に特定のGoコードを記述することです。このコードは、Goコンパイラ 8g が整数定数を含む算術演算を最適化する際に発生する可能性のあるレジスタ不足のバグ(Issue 3835)を再現することを目的としています。

変更の背景

この変更の背景には、Go言語のコンパイラ 8g が抱えていた特定の最適化バグがあります。コミットメッセージと追加されたファイルのコメントから、「Issue 3835: 8g tries to optimize arithmetic involving integer constants, but can run out of registers in the process.」という問題が明らかになります。

Goコンパイラは、生成されるバイナリのパフォーマンスを向上させるために、コードの最適化を行います。特に、定数を含む算術演算は、コンパイル時に計算結果を確定させることで、実行時のオーバーヘッドを削減できる可能性があります。しかし、この最適化プロセスにおいて、コンパイラが利用可能なCPUレジスタを使い果たしてしまうという問題が発生していました。レジスタはCPUが高速にアクセスできる一時的な記憶領域であり、その数が限られているため、効率的なレジスタ割り当てはコンパイラの重要な課題の一つです。

このバグは、特に多数の変数と定数を含む複雑な算術式において顕著に現れたと考えられます。コンパイラがこれらの式を最適化しようとする際に、中間結果を保持するためのレジスタが不足し、正しくコードを生成できない、あるいはクラッシュするといった問題を引き起こしていた可能性があります。

bug451 というテストケースが以前存在し、それが削除された後、このコミットで bug452 として復元されたのは、このバグが再発したか、あるいはその重要性が再認識されたためと考えられます。テストケースを復元することで、将来的に同様のバグが導入されることを防ぎ、コンパイラの安定性と正確性を保証する目的があります。

なお、「8g compiler」という用語は、Go 1.5で統一された go tool compile に置き換えられる前の、32ビットx86コンパイラを指す古いものです。そのため、現在のGoのIssueトラッカーで「Go issue 3835」という正確なラベルでこの問題が見つからない可能性があります。

前提知識の解説

このコミットを理解するためには、以下の前提知識が役立ちます。

Go言語のコンパイラ (8g)

Go言語の初期のコンパイラは、ターゲットアーキテクチャごとに異なる名前を持っていました。8g は、x86-64アーキテクチャ(64ビットIntel/AMDプロセッサ)向けのGoコンパイラを指します。Goのツールチェーンは、ソースコードを機械語に変換する際に、このコンパイラを使用します。コンパイラの主な役割は、構文解析、意味解析、中間コード生成、最適化、そして最終的な機械語コード生成です。

コンパイラの最適化

コンパイラの最適化とは、生成される機械語コードの実行速度やサイズを改善するためのプロセスです。様々な最適化手法がありますが、このコミットに関連するのは「定数畳み込み (Constant Folding)」や「共通部分式除去 (Common Subexpression Elimination)」といった算術最適化です。

  • 定数畳み込み: コンパイル時に計算結果が確定する式(例: 1 + 2)を、その結果(3)に置き換える最適化です。これにより、実行時の計算が不要になり、パフォーマンスが向上します。
  • レジスタ割り当て (Register Allocation): コンパイラが、プログラムの実行中に頻繁にアクセスされる変数の値をCPUのレジスタに割り当てるプロセスです。レジスタは非常に高速な記憶領域であるため、レジスタに割り当てられたデータへのアクセスは、メモリへのアクセスよりもはるかに高速です。しかし、レジスタの数は限られているため、どの変数をレジスタに割り当てるか、いつレジスタからメモリに退避させるか(スピルアウト)は、コンパイラの重要な最適化課題です。

CPUレジスタ

CPUレジスタは、CPU内部にある非常に高速なデータ記憶領域です。プログラムの実行中に、CPUはレジスタに格納されたデータを直接操作します。レジスタの数はアーキテクチャによって異なり、例えばx86-64アーキテクチャでは汎用レジスタが限られた数しかありません。コンパイラは、これらの限られたレジスタを効率的に利用して、生成されるコードのパフォーマンスを最大化しようとします。レジスタが不足すると、コンパイラは変数の値をメモリに退避させる必要があり、これはパフォーマンスの低下につながります。

Goのテストフレームワーク (// run)

Go言語には、標準でテストをサポートする機能が組み込まれています。test/fixedbugs ディレクトリは、特定のバグを再現し、その修正を検証するためのテストケースを格納する場所です。ファイルの先頭にある // run コメントは、このファイルが実行可能なテストであることを示し、go test コマンドによって実行される際に、特別な処理が行われることを示唆しています。これらのテストは、通常、プログラムがパニックを起こさないことや、特定の結果を返すことを検証します。

技術的詳細

このコミットが対処しようとしている技術的な問題は、Goコンパイラ 8g のレジスタ割り当てアルゴリズムの限界に起因しています。

8g コンパイラは、Goのソースコードを機械語に変換する過程で、中間表現(IR)を生成し、そのIRに対して様々な最適化を適用します。整数定数を含む算術演算の最適化は、コンパイル時に可能な限り多くの計算を完了させ、実行時の負荷を軽減することを目的としています。例えば、a + 1 + b + 2 のような式があった場合、コンパイラは (a + b) + (1 + 2) のように定数をまとめて計算し、a + b + 3 のような形に変換しようとします。

しかし、この最適化の過程で、コンパイラは中間結果を一時的に保持するためにレジスタを必要とします。特に、foo() 関数のように多数の変数と定数が連続して加算されるような長い式の場合、コンパイラは多くのレジスタを同時に使用しようとします。

func foo() int {
	return a + 1 + b + 2 + c + 3 + d + 4 + e + 5 + f + 6 + g + 7 + h + 8 + i + 9 + j + 10 +
	k + 1 + l + 2 + m + 3 + n + 4 + o + 5 + p + 6 + q + 7 + r + 8 + s + 9 + t + 10 +
	u + 1 + v + 2 + w + 3 + x + 4 + y + 5 + z + 6 + A + 7 + B + 8 + C + 9 + D + 10 +
	E + 1 + F + 2 + G + 3
}

このような式では、コンパイラは各加算の結果をレジスタに保持し、次の加算に利用しようとします。もし、利用可能なレジスタの数が、中間結果を保持するために必要なレジスタの数よりも少なかった場合、コンパイラは一部のレジスタの内容をメモリに「スピルアウト」(退避)させる必要があります。スピルアウトは、メモリへのアクセスを伴うため、パフォーマンスが低下します。さらに悪い場合、コンパイラのレジスタ割り当てアルゴリズムがこの状況を適切に処理できず、コンパイルエラーや不正なコード生成を引き起こす可能性がありました。

bar() 関数は、異なる整数型(int16, int32, int8)の混合演算と、乗算を含むより複雑な式をテストしています。

func bar() int8 {
	var (
		W int16
		X int32
		Y int32
		Z int32
	)
	return int8(W+int16(X+3)+3) * int8(Y+3+Z*3)
}

このような型変換と複雑な演算が絡む場合も、コンパイラは中間結果の型とサイズを考慮しながらレジスタを割り当てる必要があり、同様のレジスタ不足の問題が発生する可能性がありました。

このテストケースは、これらの特定のシナリオでコンパイラがレジスタ不足に陥り、正しくコードを生成できないというバグを再現するために設計されています。テストが成功するということは、コンパイラがこれらの複雑な算術式を、レジスタを使い果たすことなく、正しくコンパイルできるようになったことを意味します。

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

このコミットにおけるコアとなるコードの変更箇所は、以下の新しいファイルの追加です。

test/fixedbugs/bug452.go

このファイルは、38行からなるGoのソースコードで、特定のコンパイラバグを再現するためのテストケースを含んでいます。

コアとなるコードの解説

追加された test/fixedbugs/bug452.go ファイルは、Goコンパイラ 8g のレジスタ割り当てに関するバグを検証するためのものです。

// 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 3835: 8g tries to optimize arithmetic involving integer
// constants, but can run out of registers in the process.

package main

var a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G int

func foo() int {
	return a + 1 + b + 2 + c + 3 + d + 4 + e + 5 + f + 6 + g + 7 + h + 8 + i + 9 + j + 10 +
	k + 1 + l + 2 + m + 3 + n + 4 + o + 5 + p + 6 + q + 7 + r + 8 + s + 9 + t + 10 +
	u + 1 + v + 2 + w + 3 + x + 4 + y + 5 + z + 6 + A + 7 + B + 8 + C + 9 + D + 10 +
	E + 1 + F + 2 + G + 3
}

func bar() int8 {
	var (
		W int16
		X int32
		Y int32
		Z int32
	)
	return int8(W+int16(X+3)+3) * int8(Y+3+Z*3)
}

func main() {
	if foo() == 0 {
		panic("foo")
	}
	if bar() == 0 {
		panic("bar")
	}
}
  1. // run: このコメントは、Goのテストシステムに対して、このファイルが実行可能なテストであることを示します。go test コマンドがこのファイルを見つけると、コンパイルして実行し、パニックが発生しないか、あるいは特定の条件が満たされるかを検証します。

  2. コメント // Issue 3835: ...: このコメントは、このテストケースがGoのIssueトラッカーにおけるIssue 3835に関連していることを明確に示しています。問題は「8g コンパイラが整数定数を含む算術演算を最適化しようとする際に、レジスタを使い果たす可能性がある」というものです。これは、コンパイラのレジスタ割り当てアルゴリズムの限界を突くテストであることを示唆しています。

  3. グローバル変数宣言:

    var a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G int
    

    大量のグローバル変数(int型)が宣言されています。これらの変数は foo() 関数で使用され、コンパイラが多くの異なる値を同時に扱う必要がある状況を作り出します。グローバル変数はゼロ値で初期化されるため、ここではすべて 0 です。

  4. foo() 関数:

    func foo() int {
    	return a + 1 + b + 2 + c + 3 + d + 4 + e + 5 + f + 6 + g + 7 + h + 8 + i + 9 + j + 10 +
    	k + 1 + l + 2 + m + 3 + n + 4 + o + 5 + p + 6 + q + 7 + r + 8 + s + 9 + t + 10 +
    	u + 1 + v + 2 + w + 3 + x + 4 + y + 5 + z + 6 + A + 7 + B + 8 + C + 9 + D + 10 +
    	E + 1 + F + 2 + G + 3
    }
    

    この関数は、大量のグローバル変数と整数定数を連続して加算する非常に長い式を返します。

    • 目的: この長い式は、コンパイラが算術最適化を行う際に、多数の中間結果を保持するために多くのレジスタを必要とする状況を意図的に作り出しています。もしコンパイラのレジスタ割り当てが不十分であれば、この式をコンパイルする際にレジスタ不足に陥り、エラーを発生させるか、不正なコードを生成する可能性があります。
    • 期待される動作: 変数はすべて 0 で初期化されているため、この関数の戻り値はすべての定数の合計になります。1 から 10 までの合計は 55 なので、55 * 3 + (1+2+3) = 165 + 6 = 171 となります。したがって、foo()171 を返すはずです。テストの main 関数では foo() == 0 でパニックするかをチェックしており、1710 ではないため、パニックしないことが期待されます。
  5. bar() 関数:

    func bar() int8 {
    	var (
    		W int16
    		X int32
    		Y int32
    		Z int32
    	)
    	return int8(W+int16(X+3)+3) * int8(Y+3+Z*3)
    }
    

    この関数は、異なる整数型(int16, int32, int8)の混合演算と、乗算を含むより複雑な式を返します。

    • 目的: 型変換と複雑な算術演算が絡む場合も、コンパイラがレジスタ割り当てで問題を抱える可能性があるため、このシナリオをテストします。
    • 期待される動作: W, X, Y, Z はすべてゼロ値で初期化されるため、0 です。
      • W+int16(X+3)+30 + int16(0+3) + 3 = 0 + 3 + 3 = 6 となります。
      • Y+3+Z*30 + 3 + 0*3 = 3 + 0 = 3 となります。
      • 最終的に int8(6) * int8(3) = 18 となります。
      • したがって、bar()18 を返すはずです。テストの main 関数では bar() == 0 でパニックするかをチェックしており、180 ではないため、パニックしないことが期待されます。
  6. main() 関数:

    func main() {
    	if foo() == 0 {
    		panic("foo")
    	}
    	if bar() == 0 {
    		panic("bar")
    	}
    }
    

    main 関数は、foo()bar() を呼び出し、それぞれの戻り値が 0 でないことを確認します。もし 0 であれば panic を発生させます。このテストの目的は、コンパイラがこれらの関数を正しくコンパイルし、期待される非ゼロの値を返すことを確認することです。もしコンパイラがバグによってクラッシュしたり、不正なコードを生成して 0 を返したりした場合、このテストは失敗(パニック)します。

このテストケースは、Goコンパイラの堅牢性を保証するために非常に重要であり、特定の最適化パスにおける潜在的なレジスタ割り当ての問題を早期に検出する役割を果たします。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Goコンパイラの内部構造に関する資料 (一般的なコンパイラのレジスタ割り当てに関する知識)
  • GoのIssueトラッカー (Issue 3835に関する詳細情報)
  • Goのコードレビューシステム (CL 6497066に関する議論)
  • Go compiler register allocation (Red Hat Developer): https://developers.redhat.com/articles/2022/09/20/go-compiler-register-allocation
  • The Go Programming Language (go.dev): https://go.dev/