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

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

このコミットは、Goコンパイラのcmd/8g(Plan 9アセンブラ向けのGoコンパイラ、主にx86アーキテクチャをターゲットとする)におけるコード生成の改善に関するものです。具体的には、バイト乗算の際に一時変数を導入し、二項演算におけるsmallintconst(小さな整数定数)のケースの扱いを修正することで、コンパイラの生成するコードの効率性と正確性を向上させています。これにより、特定の演算におけるコンパイラのバグ(Issue 3835)が修正されました。

コミット

commit 4d3cbfdefac06f692c72520a59bede8a1c3f6cc7
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date:   Fri Dec 21 23:46:16 2012 +0100

    cmd/8g: introduce temporaries in byte multiplication.
    
    Also restore the smallintconst case for binary ops.
    
    Fixes #3835.
    
    R=daniel.morsing, rsc
    CC=golang-dev
    https://golang.org/cl/6999043

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

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

元コミット内容

cmd/8g: introduce temporaries in byte multiplication.

Also restore the smallintconst case for binary ops.

Fixes #3835.

R=daniel.morsing, rsc
CC=golang-dev
https://golang.org/cl/6999043

変更の背景

このコミットの主な背景は、Goコンパイラcmd/8gにおけるバイト乗算のコード生成に関するバグ(Issue 3835)の修正です。元の実装では、バイト型の乗算において、コンパイラが生成するアセンブリコードが正しくない結果を導く可能性がありました。特に、複雑な連鎖的な乗算や、定数との乗算において問題が発生していたと考えられます。

コンパイラが最適化を行う際、レジスタの割り当てや一時変数の使用は非常に重要です。不適切なレジスタ割り当てや一時変数の欠如は、誤った計算結果や非効率なコードにつながります。このコミットは、バイト乗算の際に明示的に一時変数を導入することで、この問題を解決し、より堅牢で正確なコード生成を目指しています。

また、二項演算におけるsmallintconst(小さな整数定数)のケースの扱いも修正されています。これは、コンパイラが定数値を扱う際の特定の最適化パスが正しく機能していなかった可能性を示唆しており、その修正も含まれています。

前提知識の解説

このコミットを理解するためには、以下のGoコンパイラの内部構造とコード生成に関する基本的な知識が必要です。

  • Goコンパイラ (cmd/8g): Go言語のソースコードを機械語に変換するプログラムの一部です。8gは、Goの初期のコンパイラ群(6g for amd64, 5g for arm, 8g for x86)の一つで、x86アーキテクチャ向けのコードを生成します。これらのコンパイラは、Goのフロントエンド(構文解析、型チェックなど)とバックエンド(コード生成、最適化)を統合したものです。
  • コード生成 (Code Generation): コンパイラの最終段階で、抽象構文木(AST)や中間表現(IR)からターゲットアーキテクチャの機械語(アセンブリコード)を生成するプロセスです。この段階では、レジスタ割り当て、命令選択、最適化などが行われます。
  • 中間表現 (Intermediate Representation - IR): ソースコードとターゲット機械語の中間に位置する表現形式です。コンパイラはソースコードをIRに変換し、IRに対して様々な最適化を適用した後、最終的な機械語を生成します。Goコンパイラも内部的に独自のIRを使用しています。
  • ullman 数 (Ullman Number): コンパイラのコード生成において、式の評価順序やレジスタ割り当てのヒューリスティックとして使用される概念です。Ullmanアルゴリズムは、式を評価するために必要なレジスタの最小数を決定するために使われます。nl->ullman >= nr->ullmanのような比較は、左右のオペランドのどちらを先に評価すべきか、あるいはどちらを一時的にメモリに退避させるべきかを決定する際に用いられます。一般的に、Ullman数が大きい(より多くのレジスタを必要とする)オペランドを先に評価し、その結果をレジスタに保持することで、レジスタスピル(レジスタの内容をメモリに退避させること)を減らすことを目指します。
  • 一時変数 (Temporaries): コンパイラが式の評価中に中間結果を保持するために内部的に生成する変数です。これらはソースコードには明示的に現れませんが、効率的なコード生成には不可欠です。一時変数は通常、レジスタに割り当てられるか、必要に応じてスタックに格納されます。
  • smallintconst: コンパイラが扱う定数の中でも、特に値が小さい整数定数を指します。これらの定数は、特定の命令(例: 即値オペランド)で直接使用できるため、特別な最適化パスが適用されることがあります。

技術的詳細

このコミットは、主にsrc/cmd/8g/cgen.csrc/cmd/8g/ggen.cの2つのファイルに変更を加えています。これらはGoコンパイラのx86バックエンドにおけるコード生成の中核を担うファイルです。

src/cmd/8g/cgen.cの変更

このファイルは、一般的なコード生成ロジック、特に二項演算(abop - asymmetric binary operation)の処理を担当しています。

変更前は、abopの処理において、左右のオペランドのUllman数を比較し、nl->ullman >= nr->ullmanの場合に一時変数ntを使用して左オペランドnlのコードを生成していました。

変更後は、このロジックの前にif(smallintconst(nr))という条件が追加されました。これは、右オペランドnrが小さな整数定数である場合に特別な処理を行うことを意味します。

  1. mgen(nl, &n1, res);: 左オペランドnlのコードを生成し、結果をn1に格納します。
  2. regalloc(&n2, nl->type, &n1);: n1と同じ型を持つ新しいレジスタn2を割り当てます。
  3. gmove(&n1, &n2);: n1の内容をn2に移動します。
  4. gins(a, nr, &n2);: 演算aを右オペランドnrn2に対して実行します。ここでnrは定数として扱われます。
  5. gmove(&n2, res);: 結果を最終的なレジスタresに移動します。
  6. regfree(&n2); mfree(&n1);: 使用したレジスタとメモリを解放します。

この変更は、右オペランドが小さな整数定数である場合に、より直接的なコード生成パスを提供し、不要な一時変数の使用や複雑なレジスタ割り当てを避けることで、生成されるコードの効率を向上させることを目的としています。元のullman数に基づくロジックは、定数でないオペランドの場合に引き続き適用されます。

src/cmd/8g/ggen.cの変更

このファイルは、特定の演算(特に乗算cgen_bmul)のコード生成を担当しています。

変更前は、cgen_bmul関数内で、左右のオペランドnlnrを直接レジスタn1n2にロードしていました。

変更後は、バイト乗算の際に一時変数ntを導入しています。

  1. tempname(&nt, nl->type);: 左オペランドnlと同じ型を持つ一時変数ntを宣言します。
  2. cgen(nl, &nt);: 左オペランドnlのコードを生成し、その結果を一時変数ntに格納します。
  3. regalloc(&n1, t, res);: 結果を格納するためのレジスタn1を割り当てます。
  4. cgen(nr, &n1);: 右オペランドnrのコードを生成し、その結果をn1に格納します。
  5. regalloc(&n2, t, N);: もう一つのレジスタn2を割り当てます。
  6. gmove(&nt, &n2);: ここで、一時変数ntの内容をレジスタn2に移動します。 変更前はcgen(nr, &n2)で右オペランドを直接n2にロードしていましたが、変更後は左オペランドの値を一時変数経由でn2にロードしています。
  7. a = optoas(op, t);: 演算に対応するアセンブリ命令コードを取得します。
  8. gins(a, &n2, &n1);: 演算an2n1に対して実行します。
  9. regfree(&n2);: n2を解放します。

この変更により、バイト乗算の際に左オペランドの値を一時変数に保持し、その後レジスタにロードするというステップが追加されました。これは、コンパイラが乗算命令を生成する際に、オペランドの順序やレジスタの制約をより適切に扱うためのものです。特に、x86アーキテクチャの乗算命令(MUL, IMUL)は、オペランドの配置に特定の制約がある場合があり、一時変数を介することで、これらの制約をより柔軟に満たし、正しいコードを生成できるようになります。

test/torture.goの変更

このファイルは、コンパイラのテストスイートの一部です。新しいテストケースChainMulBytesが追加されました。

func ChainMulBytes(a, b, c byte) byte {
	return a*(a*(a*(a*(a*(a*(a*(a*(a*b+c)+c)+c)+c)+c)+c)+c)+c) + c
}

このテストケースは、バイト型の変数に対する複雑な連鎖的な乗算と加算の組み合わせを含んでいます。これは、cmd/8gにおけるバイト乗算のコード生成のバグ(Issue 3835)を再現し、修正が正しく機能していることを検証するために設計された「拷問テスト」(torture test)です。このような複雑な式は、コンパイラのレジスタ割り当てや一時変数の管理が不適切だと、容易に誤ったコードを生成する可能性があります。

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

diff --git a/src/cmd/8g/cgen.c b/src/cmd/8g/cgen.c
index 935831d751..d2935d3992 100644
--- a/src/cmd/8g/cgen.c
+++ b/src/cmd/8g/cgen.c
@@ -395,7 +395,15 @@ sbop:	// symmetric binary
 	}
 
 abop:	// asymmetric binary
-	if(nl->ullman >= nr->ullman) {
+	if(smallintconst(nr)) {
+		mgen(nl, &n1, res);
+		regalloc(&n2, nl->type, &n1);
+		gmove(&n1, &n2);
+		gins(a, nr, &n2);
+		gmove(&n2, res);
+		regfree(&n2);
+		mfree(&n1);
+	} else if(nl->ullman >= nr->ullman) {
 		tempname(&nt, nl->type);
 		cgen(nl, &nt);
 		mgen(nr, &n2, N);
diff --git a/src/cmd/8g/ggen.c b/src/cmd/8g/ggen.c
index d72c2259bd..641b4389e9 100644
--- a/src/cmd/8g/ggen.c
+++ b/src/cmd/8g/ggen.c
@@ -748,7 +748,7 @@ cgen_shift(int op, int bounded, Node *nl, Node *nr, Node *res)
 void
 cgen_bmul(int op, Node *nl, Node *nr, Node *res)
 {
-	Node n1, n2, *tmp;
+	Node n1, n2, nt, *tmp;
 	Type *t;
 	int a;
 
@@ -764,10 +764,12 @@ cgen_bmul(int op, Node *nl, Node *nr, Node *res)
 		nr = tmp;
 	}
 
+	tempname(&nt, nl->type);
+	cgen(nl, &nt);
 	regalloc(&n1, t, res);
-	cgen(nl, &n1);
+	cgen(nr, &n1);
 	regalloc(&n2, t, N);
-	cgen(nr, &n2);
+	gmove(&nt, &n2);
 	a = optoas(op, t);
 	gins(a, &n2, &n1);
 	regfree(&n2);
diff --git a/test/torture.go b/test/torture.go
index d14d78fd14..bbf6d347d9 100644
--- a/test/torture.go
+++ b/test/torture.go
@@ -333,3 +333,7 @@ func ChainDivConst(a int) int {\n 		17 / 17 / 17 / 17 /\n 		17 / 17 / 17 / 17\n }\n+\n+func ChainMulBytes(a, b, c byte) byte {\n+\treturn a*(a*(a*(a*(a*(a*(a*(a*(a*b+c)+c)+c)+c)+c)+c)+c)+c) + c\n+}\n```

## コアとなるコードの解説

### `src/cmd/8g/cgen.c`の変更点

*   **`abop` (asymmetric binary operation) の処理に`smallintconst`のケースを追加**:
    *   変更前は、左右のオペランドのUllman数を比較してコード生成の順序を決定していました。
    *   変更後は、まず右オペランド`nr`が`smallintconst`(小さな整数定数)であるかをチェックします。
    *   もし`smallintconst`であれば、特別な最適化パスに入ります。このパスでは、左オペランド`nl`をレジスタ`n1`にロードし、その内容を別のレジスタ`n2`に移動した後、定数`nr`と`n2`に対して直接演算を実行します。これにより、定数オペランドに対するより効率的なコードが生成されます。
    *   `smallintconst`でない場合は、従来のUllman数に基づくロジックが適用されます。

### `src/cmd/8g/ggen.c`の変更点

*   **`cgen_bmul` (byte multiplication) における一時変数の導入**:
    *   変更前は、`cgen_bmul`関数内で、乗算の左右のオペランドを直接レジスタ`n1`と`n2`にロードしていました。
    *   変更後は、`Node n1, n2, nt, *tmp;`のように、一時変数`nt`が追加されました。
    *   左オペランド`nl`のコードを生成し、その結果を一時変数`nt`に格納する`tempname(&nt, nl->type); cgen(nl, &nt);`という行が追加されました。
    *   その後、右オペランド`nr`をレジスタ`n1`にロードし、**一時変数`nt`の内容をレジスタ`n2`に移動する**`gmove(&nt, &n2);`という行が追加されました。
    *   これにより、乗算のオペランドがレジスタにロードされる前に一時変数に格納されることで、コンパイラがより柔軟にレジスタを管理し、x86の乗算命令の制約(例えば、特定のオペランドが特定のレジスタにある必要があるなど)を適切に満たすことができるようになります。これは、特にバイト乗算のような特定のデータ型と演算の組み合わせで発生する可能性のあるコード生成のバグを修正するために重要です。

### `test/torture.go`の変更点

*   **`ChainMulBytes`関数の追加**:
    *   この関数は、バイト型の変数に対する複雑な連鎖的な乗算と加算を含む式を定義しています。
    *   これは、`cmd/8g`におけるバイト乗算のコード生成のバグ(Issue 3835)を意図的にトリガーし、修正が正しく適用されたことを検証するための回帰テストとして機能します。このような複雑な式は、コンパイラの最適化やレジスタ割り当てのロジックに潜在する問題を露呈させやすいです。

これらの変更は全体として、Goコンパイラ`cmd/8g`が生成するコードの正確性と堅牢性を向上させることを目的としています。特に、バイト乗算のような特定のケースで発生していたバグを修正し、小さな整数定数に対するコード生成を最適化しています。

## 関連リンク

*   GitHubコミットページ: [https://github.com/golang/go/commit/4d3cbfdefac06f692c72520a59bede8a1c3f6cc7](https://github.com/golang/go/commit/4d3cbfdefac06f692c72520a59bede8a1c3f6cc7)
*   Go Change List (CL): [https://golang.org/cl/6999043](https://golang.org/cl/6999043)

## 参考にした情報源リンク

*   Web search results for "golang.org/cl/6999043":
    *   appspot.com (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQH68v_3UiwKHx0nNXa3jpbLOeCjqPCmMBJYAO3jK-WTtI7iEMOnD58d3apBmdw92AMErRGGopnhfjeW2-gLnHDZK9o9Pf9OJj9bPpEbHpuB7GHXk3x32D3ai2W4t3o907tN)
    *   golang.org (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQE_thX9Kg-BwiOb_8DLdc9U-YucHOLp-MBA_D97W-9ADzZ4BUA_AK5YE9xDQ5559haT7HFL8PCvEl7XTJhPbs-aspDRVVZQdu9uhiy3jIQcFzvlPEDJTgTh)
*   Web search results for "golang/go issue 3835": (具体的なIssueページは見つかりませんでしたが、CLの記述からこのコミットがIssue 3835を修正したことが確認できました。)