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

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

このコミットは、Go言語のコンパイラにおける既知のバグ(Issue 3907とIssue 4156)に対するテストケースを追加するものです。具体的には、test/fixedbugs/bug455.gotest/fixedbugs/bug456.goの2つの新しいテストファイルが追加されました。これらのテストは、コンパイラが特定のコードパターン(メソッドチェーンとネストされた乗算)を処理する際に発生していた「固定レジスタ不足 (out of fixed registers)」の問題を再現し、修正が正しく適用されたことを検証するために作成されました。

コミット

commit 4bb75cd9ada7058cb90ff43dca13bd246f59f46d
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date:   Sun Sep 30 10:35:09 2012 +0200

    test/fixedbugs: forgotten test cases for issues 3907 and 4156.
    
    Update #3907.
    Update #4156.
    
    R=golang-dev, dave
    CC=golang-dev
    https://golang.org/cl/6595044

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

https://github.com/golang/go/commit/4bb75cd9ada7058cb90ff43dca13bd246f59f46d

元コミット内容

test/fixedbugs: forgotten test cases for issues 3907 and 4156.

Update #3907.
Update #4156.

R=golang-dev, dave
CC=golang-dev
https://golang.org/cl/6595044

変更の背景

このコミットは、Go言語のコンパイラ(特に当時の6gおよび8gコンパイラ)が抱えていた特定のバグ、すなわちIssue 3907とIssue 4156に対する「忘れられていたテストケース」を追加するものです。これらのバグは、コンパイラのレジスタ割り当てに関する問題に起因し、特定の複雑なコードパターン(長いメソッドチェーンや深くネストされた算術演算)をコンパイルする際に「out of fixed registers」(固定レジスタ不足)というエラーを引き起こしていました。

コンパイラのバグが修正された後も、その修正が将来のリグレッション(回帰)を防ぐために、関連するテストケースが追加されることが一般的です。このコミットは、これらの重要なバグに対するテストがまだ存在していなかったため、それらを追加することで、Goコンパイラの堅牢性と信頼性を向上させることを目的としています。これにより、将来のコンパイラの変更がこれらの特定のバグを再導入するのを防ぐことができます。

前提知識の解説

Goコンパイラ (6g, 8g)

Go言語の初期のコンパイラは、ターゲットアーキテクチャに基づいて命名されていました。

  • 6g: AMD64 (x86-64) アーキテクチャ向けのGoコンパイラを指します。
  • 8g: ARMアーキテクチャ向けのGoコンパイラを指します。
  • 5g: x86 (32-bit) アーキテクチャ向けのGoコンパイラを指します。

これらのコンパイラは、Goのソースコードを機械語に変換する役割を担っていました。現在のGoコンパイラは、go buildコマンドを通じて抽象化されており、ユーザーが直接6g8gといったコマンドを意識することは少なくなっていますが、当時の開発においては重要な区別でした。

レジスタ割り当て (Register Allocation)

レジスタ割り当ては、コンパイラの最適化フェーズの一部であり、プログラムの実行中に使用される変数の値をCPUのレジスタに割り当てるプロセスです。CPUのレジスタは非常に高速なメモリであり、プログラムのパフォーマンスに直接影響します。

  • 固定レジスタ (Fixed Registers): 特定の目的(例: スタックポインタ、フレームポインタ、特定の演算結果を保持するレジスタなど)のために予約されているレジスタのことです。
  • レジスタ不足 (Out of Registers): コンパイラが、特定の演算やデータ保持のために利用可能なレジスタを使い果たしてしまい、それ以上レジスタを割り当てることができなくなる状況を指します。これは、複雑な式や多数の変数が同時に「生存」している場合に発生しやすく、コンパイラがコードを正しく生成できなくなる原因となります。レジスタ不足が発生すると、コンパイラは一時的にメモリ(スタックなど)にデータを退避させる「スピル (spill)」処理を行うことがありますが、これが過度になるとパフォーマンスが低下したり、最悪の場合コンパイルエラーになったりします。

メソッドチェーン (Method Chaining)

メソッドチェーンは、オブジェクト指向プログラミングにおいて、あるメソッドの戻り値がそのオブジェクト自身(または同じ型の別のオブジェクト)である場合に、その戻り値に対してさらに別のメソッドを連続して呼び出すプログラミングスタイルです。これにより、コードの可読性が向上し、簡潔に記述できます。

例: object.method1().method2().method3()

ネストされた演算 (Nested Operations)

ネストされた演算とは、ある演算の結果が別の演算のオペランドとして使用され、それが複数回繰り返される構造を指します。特に算術演算において、括弧が深くネストされた式などがこれに該当します。

例: a * (b * (c * (d * ...)))

技術的詳細

このコミットで追加されたテストケースは、Goコンパイラのレジスタ割り当てアルゴリズムが特定の極端なケースで失敗するバグを浮き彫りにします。

Issue 4156: メソッドチェーンにおける固定レジスタ不足

bug455.goは、非常に長いメソッドチェーンがコンパイラのレジスタ割り当てに与える影響をテストします。Test()メソッドがtest_iインターフェースを返し、その戻り値に対してさらにTest()メソッドを呼び出すというパターンを繰り返しています。

test.
    Test().
    Test().
    Test().
    Test().
    Test().
    Test().
    Test().
    Test().
    Test().
    Test().
    Result():

このような長いチェーンは、コンパイラが各メソッド呼び出しの中間結果を保持するために一時的なレジスタを必要とします。当時の6gコンパイラは、この中間結果の管理において効率的でなかったか、あるいは特定のレジスタが不足するような状況に陥りやすかったため、レジスタを使い果たしてコンパイルエラーを引き起こしていました。これは、コンパイラがコードを機械語に変換する際に、利用可能なCPUレジスタを効率的に管理できない場合に発生します。特に、関数呼び出しの引数渡しや戻り値の受け渡しにおいて、レジスタが一時的に占有されることが多く、それが連鎖するとレジスタが枯渇する可能性がありました。

Issue 3907: ネストされたバイト乗算における固定レジスタ不足

bug456.goは、深くネストされたuint8(バイト)の乗算がコンパイラのレジスタ割り当てに与える影響をテストします。

return a * (b * (c * (d *
    (a * (b * (c * (d *
        (a * (b * (c * (d *
            a * (b * (c * d))))))))))))))

この式は、非常に多くの乗算が連続して行われ、かつそれらが深くネストされています。各乗算の中間結果は、次の乗算のオペランドとして使用されるため、コンパイラはこれらの値を一時的に保持する必要があります。当時の6gおよび8gコンパイラは、このような複雑な算術式を処理する際に、中間結果を保持するためのレジスタが不足し、「out of fixed registers」エラーを発生させていました。特に、バイト演算は通常、より大きなデータ型(例: 32ビット整数)に昇格されてから演算が行われることがあり、その過程でレジスタの使用量が増加する可能性も考えられます。

これらの問題は、コンパイラのバックエンド、特にコード生成とレジスタ割り当てのロジックにおける欠陥を示しています。テストケースの追加は、これらの特定のシナリオが将来のコンパイラの変更によって再び壊れないようにするための重要なステップです。

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

このコミットでは、以下の2つの新しいファイルが追加されています。

  1. test/fixedbugs/bug455.go
  2. test/fixedbugs/bug456.go

これらのファイルは、Go言語のテストスイートの一部として、特定のバグが修正されたことを検証するために使用されます。

コアとなるコードの解説

test/fixedbugs/bug455.go

このファイルは、Issue 4156(メソッドチェーンにおける固定レジスタ不足)をテストします。

// 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 4156: out of fixed registers when chaining method calls.
// Used to happen with 6g.

package main

type test_i interface {
	Test() test_i
	Result() bool
}

type test_t struct {
}

func newTest() *test_t {
	return &test_t{}
}

type testFn func(string) testFn // この型は未使用だが、当時のテストコードの一部として含まれている可能性

func main() {
	test := newTest()

	switch {
	case test.
		Test().
		Test().
		Test().
		Test().
		Test().
		Test().
		Test().
		Test().
		Test().
		Test().
		Result():
		// case worked
	default:
		panic("Result returned false unexpectedly")
	}
}

func (t *test_t) Test() test_i {
	return t
}

func (t *test_t) Result() bool {
	return true
}
  • test_i インターフェース: Test()Result()という2つのメソッドを持つインターフェースを定義しています。Test()test_i自身を返すため、メソッドチェーンを可能にします。
  • test_t 構造体: test_iインターフェースを実装する具体的な型です。Test()メソッドはレシーバであるt自身を返し、Result()メソッドは常にtrueを返します。
  • main 関数:
    • newTest()test_tのインスタンスを作成します。
    • switch文の中で、testオブジェクトに対してTest()メソッドを10回連続で呼び出し、最後にResult()を呼び出しています。
    • この長いメソッドチェーンが、当時の6gコンパイラでレジスタ不足を引き起こしていました。
    • Result()trueを返せばテストは成功し、false(このコードでは発生しないはず)であればpanicします。これは、コンパイルが成功し、かつ実行時にも期待通りの動作をすることを確認するためのものです。

test/fixedbugs/bug456.go

このファイルは、Issue 3907(ネストされたバイト乗算における固定レジスタ不足)をテストします。

// 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 3907: out of fixed registers in nested byte multiply.
// Used to happen with both 6g and 8g.

package main

func F(a, b, c, d uint8) uint8 {
	return a * (b * (c * (d *
		(a * (b * (c * (d *
			(a * (b * (c * (d *
				a * (b * (c * d))))))))))))))
}

func main() {
	var a, b, c, d uint8 = 1, 1, 1, 1
	x := F(a, b, c, d)
	if x != 1 {
		println(x)
		panic("x != 1")
	}
}
  • F 関数:
    • uint8型の4つの引数a, b, c, dを受け取り、uint8を返します。
    • 関数本体は、a * (b * (c * (d * ...)))という形式で、非常に深くネストされた乗算式を含んでいます。この式は、a, b, c, dの組み合わせが繰り返し使用されています。
    • この複雑なネスト構造が、当時の6gおよび8gコンパイラでレジスタ不足を引き起こしていました。
  • main 関数:
    • a, b, c, dの各変数を1で初期化します。
    • F関数を呼び出し、結果をxに格納します。
    • すべての入力が1であるため、乗算の結果も常に1になるはずです。
    • x1でない場合はpanicを発生させます。これにより、コンパイルが成功し、かつ実行時にも正しい結果が得られることを検証します。

これらのテストケースは、Goコンパイラのレジスタ割り当てロジックが、特定の複雑なコードパターンを適切に処理できるようになったことを確認するための重要な回帰テストとして機能します。

関連リンク

参考にした情報源リンク