[インデックス 14030] ファイルの概要
このコミットは、Go言語のARMアーキテクチャ向けコンパイラである5g
において、ネストされた関数呼び出し時に発生するレジスタ不足のバグを修正し、その修正を検証するためのコンパイラストレステストを追加するものです。具体的には、関数呼び出しの結果を保持するレジスタが、ネストされた呼び出し中に誤って解放されることを防ぐためのレジスタ管理ロジックが改善されました。
コミット
commit 3a4e156ae1789970d37e8e53b053c2e0c8ab2465
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date: Fri Oct 5 23:30:49 2012 +0200
cmd/5g: fix out of registers in nested calls, add compiler torture test.
R=golang-dev, dave, daniel.morsing, rsc
CC=golang-dev, remy
https://golang.org/cl/6586072
---
src/cmd/5g/cgen.c | 24 +++++---
test/torture.go | 171 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 188 insertions(+), 7 deletions(-)
diff --git a/src/cmd/5g/cgen.c b/src/cmd/5g/cgen.c
index 79efab4947..eaa813fcf7 100644
--- a/src/cmd/5g/cgen.c
+++ b/src/cmd/5g/cgen.c
@@ -16,7 +16,7 @@ cgen(Node *n, Node *res)
{
Node *nl, *nr, *r;
Node n1, n2, n3, f0, f1;
-\tint a, w;\n+\tint a, w, rg;\n \tProg *p1, *p2, *p3;\n \tAddr addr;\n \n@@ -406,7 +406,22 @@ cgen(Node *n, Node *res)\n \t\tbreak;\n \n \tcase OCALLMETH:\n-\t\tcgen_callmeth(n, 0);\n+\tcase OCALLFUNC:\n+\t\t// Release res so that it is available for cgen_call.\n+\t\t// Pick it up again after the call.\n+\t\trg = -1;\n+\t\tif(n->ullman >= UINF) {\n+\t\t\tif(res->op == OREGISTER || res->op == OINDREG) {\n+\t\t\t\trg = res->val.u.reg;\n+\t\t\t\treg[rg]--;\n+\t\t\t}\n+\t\t}\n+\t\tif(n->op == OCALLMETH)\n+\t\t\tcgen_callmeth(n, 0);\n+\t\telse\n+\t\t\tcgen_call(n, 0);\n+\t\tif(rg >= 0)\n+\t\t\treg[rg]++;\n \t\tcgen_callret(n, res);\n \t\tbreak;\n \n@@ -415,11 +430,6 @@ cgen(Node *n, Node *res)\n \t\tcgen_callret(n, res);\n \t\tbreak;\n \n-\tcase OCALLFUNC:\n-\t\tcgen_call(n, 0);\n-\t\tcgen_callret(n, res);\n-\t\tbreak;\n-\n \tcase OMOD:\n \tcase ODIV:\n \t\ta = optoas(n->op, nl->type);\ndiff --git a/test/torture.go b/test/torture.go
new file mode 100644
index 0000000000..fdc5ddae0f
--- /dev/null
+++ b/test/torture.go
@@ -0,0 +1,171 @@
+// compile
+
+// Copyright 2012 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n+\n+// Various tests for expressions with high complexity.\n+\n+package main\n+\n+// Concatenate 16 4-bit integers into a 64-bit number.\n+func concat(s *[16]byte) uint64 {\n+\tr := (((((((((((((((uint64(s[0])<<4|\n+\t\tuint64(s[1]))<<4|\n+\t\tuint64(s[2]))<<4|\n+\t\tuint64(s[3]))<<4|\n+\t\tuint64(s[4]))<<4|\n+\t\tuint64(s[5]))<<4|\n+\t\tuint64(s[6]))<<4|\n+\t\tuint64(s[7]))<<4|\n+\t\tuint64(s[8]))<<4|\n+\t\tuint64(s[9]))<<4|\n+\t\tuint64(s[10]))<<4|\n+\t\tuint64(s[11]))<<4|\n+\t\tuint64(s[12]))<<4|\n+\t\tuint64(s[13]))<<4|\n+\t\tuint64(s[14]))<<4 |\n+\t\tuint64(s[15]))\n+\treturn r\n+}\n+\n+// Compute the determinant of a 4x4-matrix by the sum\n+// over all index permutations.\n+func determinant(m [4][4]float64) float64 {\n+\treturn m[0][0]*m[1][1]*m[2][2]*m[3][3] -\n+\t\tm[0][0]*m[1][1]*m[2][3]*m[3][2] -\n+\t\tm[0][0]*m[1][2]*m[2][1]*m[3][3] +\n+\t\tm[0][0]*m[1][2]*m[2][3]*m[3][1] +\n+\t\tm[0][0]*m[1][3]*m[2][1]*m[3][2] -\n+\t\tm[0][0]*m[1][3]*m[2][2]*m[3][1] -\n+\t\tm[0][1]*m[1][0]*m[2][2]*m[3][3] +\n+\t\tm[0][1]*m[1][0]*m[2][3]*m[3][2] +\n+\t\tm[0][1]*m[1][2]*m[2][0]*m[3][3] -\n+\t\tm[0][1]*m[1][2]*m[2][3]*m[3][0] -\n+\t\tm[0][1]*m[1][3]*m[2][0]*m[3][2] +\n+\t\tm[0][1]*m[1][3]*m[2][2]*m[3][0] +\n+\t\tm[0][2]*m[1][0]*m[2][1]*m[3][3] -\n+\t\tm[0][2]*m[1][0]*m[2][3]*m[3][1] -\n+\t\tm[0][2]*m[1][1]*m[2][0]*m[3][3] +\n+\t\tm[0][2]*m[1][1]*m[2][3]*m[3][0] +\n+\t\tm[0][2]*m[1][3]*m[2][0]*m[3][1] -\n+\t\tm[0][2]*m[1][3]*m[2][1]*m[3][0] -\n+\t\tm[0][3]*m[1][0]*m[2][1]*m[3][2] +\n+\t\tm[0][3]*m[1][0]*m[2][2]*m[3][1] +\n+\t\tm[0][3]*m[1][1]*m[2][0]*m[3][2] -\n+\t\tm[0][3]*m[1][1]*m[2][2]*m[3][0] -\n+\t\tm[0][3]*m[1][2]*m[2][0]*m[3][1] +\n+\t\tm[0][3]*m[1][2]*m[2][1]*m[3][0]\n+}\n+\n+// A right-leaning tree of byte multiplications.\n+func righttree(a, b, c, d uint8) uint8 {\n+\treturn a * (b * (c * (d *\n+\t\t(a * (b * (c * (d *\n+\t\t\t(a * (b * (c * (d *\n+\t\t\t\t(a * (b * (c * (d *\n+\t\t\t\t\t(a * (b * (c * (d *\n+\t\t\t\t\t\ta * (b * (c * d)))))))))))))))))))))\n+\n+}\n+\n+// A left-leaning tree of byte multiplications.\n+func lefttree(a, b, c, d uint8) uint8 {\n+\treturn ((((((((((((((((((a * b) * c) * d *\n+\t\ta) * b) * c) * d *\n+\t\ta) * b) * c) * d *\n+\t\ta) * b) * c) * d *\n+\t\ta) * b) * c) * d *\n+\t\ta) * b) * c) * d)\n+}\n+\n+type T struct {\n+\tNext I\n+}\n+\n+type I interface{}\n+\n+// A chains of type assertions.\n+func ChainT(t *T) *T {\n+\treturn t.\n+\t\tNext.(*T).\n+\t\tNext.(*T).\n+\t\tNext.(*T).\n+\t\tNext.(*T).\n+\t\tNext.(*T).\n+\t\tNext.(*T).\n+\t\tNext.(*T).\n+\t\tNext.(*T).\n+\t\tNext.(*T).\n+\t\tNext.(*T).\n+\t\tNext.(*T).\n+\t\tNext.(*T).\n+\t\tNext.(*T).\n+\t\tNext.(*T).\n+\t\tNext.(*T).\n+\t\tNext.(*T).\n+\t\tNext.(*T).\n+\t\tNext.(*T).\n+\t\tNext.(*T).\n+\t\tNext.(*T)\n+}\n+\n+type U struct {\n+\tChildren []J\n+}\n+\n+func (u *U) Child(n int) J { return u.Children[n] }\n+\n+type J interface {\n+\tChild(n int) J\n+}\n+\n+func ChainUAssert(u *U) *U {\n+\treturn u.Child(0).(*U).\n+\t\tChild(0).(*U).\n+\t\tChild(0).(*U).\n+\t\tChild(0).(*U).\n+\t\tChild(0).(*U).\n+\t\tChild(0).(*U).\n+\t\tChild(0).(*U).\n+\t\tChild(0).(*U).\n+\t\tChild(0).(*U).\n+\t\tChild(0).(*U).\n+\t\tChild(0).(*U).\n+\t\tChild(0).(*U).\n+\t\tChild(0).(*U).\n+\t\tChild(0).(*U).\n+\t\tChild(0).(*U).\n+\t\tChild(0).(*U).\n+\t\tChild(0).(*U).\n+\t\tChild(0).(*U).\n+\t\tChild(0).(*U).\n+\t\tChild(0).(*U).\n+\t\tChild(0).(*U).\n+\t\tChild(0).(*U)\n+}\n+\n+func ChainUNoAssert(u *U) *U {\n+\treturn u.Child(0).\n+\t\tChild(0).\n+\t\tChild(0).\n+\t\tChild(0).\n+\t\tChild(0).\n+\t\tChild(0).\n+\t\tChild(0).\n+\t\tChild(0).\n+\t\tChild(0).\n+\t\tChild(0).\n+\t\tChild(0).\n+\t\tChild(0).\n+\t\tChild(0).\n+\t\tChild(0).\n+\t\tChild(0).\n+\t\tChild(0).\n+\t\tChild(0).\n+\t\tChild(0).\n+\t\tChild(0).\n+\t\tChild(0).\n+\t\tChild(0).\n+\t\tChild(0).(*U)\n+}\n```
## GitHub上でのコミットページへのリンク
[https://github.com/golang/go/commit/3a4e156ae1789970d37e8e53b053c2e0c8ab2465](https://github.com/golang/go/commit/3a4e156ae1789970d37e8e53b053c2e0c8ab2465)
## 元コミット内容
`cmd/5g: ネストされた呼び出しにおけるレジスタ不足を修正し、コンパイラのストレステストを追加。`
## 変更の背景
Go言語のARMアーキテクチャ向けコンパイラである`5g`において、関数が深くネストして呼び出される際に、コンパイラがレジスタを適切に管理できず、レジスタが枯渇してしまうというバグが存在しました。この問題は、特に複雑な式や多数の関数呼び出しを含むコードをコンパイルする際に顕在化し、生成されるアセンブリコードが不正になったり、コンパイル自体が失敗したりする可能性がありました。
このバグの根本原因は、コンパイラのコード生成フェーズにおいて、関数呼び出しの結果を一時的に保持するために使用されるレジスタが、ネストされた別の関数呼び出しの途中で誤って解放されてしまい、そのレジスタが別の用途に再割り当てされてしまうことにありました。これにより、本来保持しておくべき値が失われ、プログラムの誤動作を引き起こしていました。
このコミットは、このレジスタ割り当ての不具合を修正し、コンパイラの堅牢性を向上させることを目的としています。
## 前提知識の解説
### Goコンパイラ `5g`
Go言語のコンパイラは、ターゲットアーキテクチャごとに異なる名前が付けられています。`5g`は、ARMアーキテクチャ(具体的にはPlan 5アセンブラ)向けのGoコンパイラを指します。Goのツールチェーンにおいて、ソースコードを機械語に変換する重要な役割を担っています。コンパイラは、構文解析、意味解析、中間コード生成、最適化、そして最終的な機械語コード生成といった複数のフェーズを経て処理を行います。
### レジスタ割り当て (Register Allocation)
レジスタ割り当ては、コンパイラの最適化フェーズにおける重要なプロセスの一つです。CPUのレジスタは、メモリよりもはるかに高速にアクセスできる記憶領域ですが、その数は非常に限られています。コンパイラのレジスタ割り当て器は、プログラムの変数や中間結果をこれらの限られたレジスタに効率的に割り当てることで、生成されるコードの実行速度を最大化しようとします。
関数呼び出しにおいては、引数の受け渡し、戻り値の格納、ローカル変数の管理などにレジスタが使用されます。関数呼び出し規約(Calling Convention)によって、どのレジスタが呼び出し側(caller)によって保存されるべきか、どのレジスタが呼び出された側(callee)によって保存されるべきかなどが定められています。レジスタ割り当てのバグは、これらの規約の誤解釈や、レジスタのライフタイム管理の不備によって発生することがあります。
### ネストされた関数呼び出し (Nested Function Calls)
ネストされた関数呼び出しとは、ある関数(例: `funcA`)が別の関数(例: `funcB`)を呼び出し、その`funcB`がさらに別の関数(例: `funcC`)を呼び出すといった、複数の関数が連続して呼び出される状況を指します。このような呼び出しの連鎖では、各関数の実行コンテキスト(ローカル変数、引数、戻りアドレスなど)がスタックに積まれ、レジスタの状態も適切に保存・復元される必要があります。レジスタ割り当ての観点からは、ある関数の呼び出し中に、その関数が使用しているレジスタが、ネストされた呼び出しによって上書きされたり、誤って解放されたりしないように注意深く管理する必要があります。
### コンパイラのストレステスト (Compiler Torture Test)
コンパイラのストレステスト、または拷問テスト(torture test)とは、コンパイラの限界を試したり、通常では発見されにくいバグをあぶり出すために、意図的に非常に複雑な、あるいはエッジケースを突くようなコードを作成してコンパイルさせるテスト手法です。これには、深くネストされた式、多数の演算子、複雑な型アサーション、再帰的なデータ構造などが含まれることがあります。このようなテストは、コンパイラのレジスタ割り当て、最適化、型チェックなどのロジックの堅牢性を検証するのに役立ちます。
### Ullman Number (ウルマン数)
Ullman Numberは、コンパイラ最適化の文脈で、式の複雑度を測るための指標として用いられることがあります。これは、式の構文木におけるノードの数や深さに基づいて計算され、レジスタ割り当てのヒューリスティックなどで利用されることがあります。`UINF`は"Ullman Infinity"の略で、非常に複雑な式、あるいはレジスタ割り当て器が特別な注意を払うべき式を示す定数として使われることがあります。
## 技術的詳細
このコミットの技術的詳細は、`src/cmd/5g/cgen.c`ファイル内の`cgen`関数における`OCALLMETH`(メソッド呼び出し)と`OCALLFUNC`(関数呼び出し)の処理の変更に集約されます。
変更前は、`OCALLMETH`と`OCALLFUNC`はそれぞれ独立した`case`文で処理されていました。特に`OCALLFUNC`の処理はシンプルで、`cgen_call`を呼び出した後、`cgen_callret`で戻り値を処理するだけでした。
しかし、このアプローチでは、関数呼び出しの結果を格納するレジスタ(`res`ノードが指すレジスタ)が、ネストされた関数呼び出しの途中で誤って解放されてしまう可能性がありました。これは、コンパイラがレジスタを再利用しようとする際に、まだ必要なレジスタを「空き」と判断してしまうためです。
修正後のコードでは、以下の変更が加えられています。
1. **`OCALLFUNC`の`OCALLMETH`への統合**: `OCALLFUNC`の`case`文が削除され、そのロジックが`OCALLMETH`の`case`文内に統合されました。これにより、両方の種類の関数呼び出しに対して共通のレジスタ管理ロジックを適用できるようになりました。
2. **レジスタのライフタイム管理の改善**:
* `rg`という新しい整数変数が導入されました。これは、関数呼び出しの結果を格納するレジスタの番号を一時的に保持するために使用されます。
* `if(n->ullman >= UINF)`という条件が追加されました。これは、非常に複雑な式の結果を格納するレジスタに対してのみ、以下の特別なレジスタ管理を適用することを示唆しています。`UINF`は、コンパイラがレジスタ割り当てにおいて特別な注意を払うべき複雑な式を識別するための閾値と考えられます。
* `if(res->op == OREGISTER || res->op == OINDREG)`の条件で、`res`ノードが実際にレジスタまたはレジスタ間接参照を指しているかを確認します。
* もし条件が満たされれば、`rg = res->val.u.reg;`でレジスタ番号を取得し、`reg[rg]--;`を実行します。`reg`配列は、各レジスタの参照カウント(または使用状況)を追跡していると考えられます。ここで参照カウントをデクリメントすることで、関数呼び出し中にこのレジスタが他の用途に誤って割り当てられないように「予約」する効果があります。
* 実際の関数呼び出し(`cgen_callmeth`または`cgen_call`)が実行されます。
* 関数呼び出しが完了した後、`if(rg >= 0)`の条件で、`rg`が有効なレジスタ番号であれば、`reg[rg]++;`を実行して参照カウントをインクリメントし、レジスタを再び通常の割り当てプールに戻します。
この一連の処理により、ネストされた関数呼び出しの最中に、親の呼び出しの結果を保持するレジスタが、子呼び出しによって誤って上書きされたり、解放されたりするのを防ぎます。これにより、レジスタ不足によるコンパイルエラーや不正なコード生成が回避されます。
また、`test/torture.go`という新しいテストファイルが追加されました。このファイルには、コンパイラのレジスタ割り当てや最適化ロジックの限界を試すための、意図的に複雑なGoコードが含まれています。具体的には、以下のような関数が含まれています。
* `concat`: 多数のビットシフトとOR演算をネストして、複数のバイトから64ビット整数を構築する。
* `determinant`: 4x4行列の行列式を、多数の乗算と加減算の組み合わせで計算する。
* `righttree`, `lefttree`: 深くネストされた乗算のツリー構造。
* `ChainT`, `ChainUAssert`, `ChainUNoAssert`: 多数の型アサーションやメソッド呼び出しを連鎖させた構造。
これらのテストケースは、修正されたレジスタ管理ロジックが、実際に複雑なシナリオで正しく機能するかを検証するために設計されています。
## コアとなるコードの変更箇所
### `src/cmd/5g/cgen.c`
```diff
--- a/src/cmd/5g/cgen.c
+++ b/src/cmd/5g/cgen.c
@@ -16,7 +16,7 @@ cgen(Node *n, Node *res)
{
Node *nl, *nr, *r;
Node n1, n2, n3, f0, f1;
-\tint a, w;\n+\tint a, w, rg;\n \tProg *p1, *p2, *p3;\n \tAddr addr;\n \n@@ -406,7 +406,22 @@ cgen(Node *n, Node *res)\n \t\tbreak;\n \n \tcase OCALLMETH:\n-\t\tcgen_callmeth(n, 0);\n+\tcase OCALLFUNC:\n+\t\t// Release res so that it is available for cgen_call.\n+\t\t// Pick it up again after the call.\n+\t\trg = -1;\n+\t\tif(n->ullman >= UINF) {\n+\t\t\tif(res->op == OREGISTER || res->op == OINDREG) {\n+\t\t\t\trg = res->val.u.reg;\n+\t\t\t\treg[rg]--;\n+\t\t\t}\n+\t\t}\n+\t\tif(n->op == OCALLMETH)\n+\t\t\tcgen_callmeth(n, 0);\n+\t\telse\n+\t\t\tcgen_call(n, 0);\n+\t\tif(rg >= 0)\n+\t\t\treg[rg]++;\n \t\tcgen_callret(n, res);\n \t\tbreak;\n \n@@ -415,11 +430,6 @@ cgen(Node *n, Node *res)\n \t\tcgen_callret(n, res);\n \t\tbreak;\n \n-\tcase OCALLFUNC:\n-\t\tcgen_call(n, 0);\n-\t\tcgen_callret(n, res);\n-\t\tbreak;\n-\n \tcase OMOD:\n \tcase ODIV:\n \t\ta = optoas(n->op, nl->type);\n```
### `test/torture.go` (新規追加ファイル)
```go
// compile
// 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.
// Various tests for expressions with high complexity.
package main
// Concatenate 16 4-bit integers into a 64-bit number.
func concat(s *[16]byte) uint64 {
r := (((((((((((((((uint64(s[0])<<4|\
uint64(s[1]))<<4|\
uint64(s[2]))<<4|\
uint64(s[3]))<<4|\
uint64(s[4]))<<4|\
uint64(s[5]))<<4|\
uint64(s[6]))<<4|\
uint64(s[7]))<<4|\
uint64(s[8]))<<4|\
uint64(s[9]))<<4|\
uint64(s[10]))<<4|\
uint64(s[11]))<<4|\
uint64(s[12]))<<4|\
uint64(s[13]))<<4|\
uint64(s[14]))<<4 |\
uint64(s[15]))
return r
}
// Compute the determinant of a 4x4-matrix by the sum
// over all index permutations.
func determinant(m [4][4]float64) float64 {
return m[0][0]*m[1][1]*m[2][2]*m[3][3] -\
m[0][0]*m[1][1]*m[2][3]*m[3][2] -\
m[0][0]*m[1][2]*m[2][1]*m[3][3] +\
m[0][0]*m[1][2]*m[2][3]*m[3][1] +\
m[0][0]*m[1][3]*m[2][1]*m[3][2] -\
m[0][0]*m[1][3]*m[2][2]*m[3][1] -\
m[0][1]*m[1][0]*m[2][2]*m[3][3] +\
m[0][1]*m[1][0]*m[2][3]*m[3][2] +\
m[0][1]*m[1][2]*m[2][0]*m[3][3] -\
m[0][1]*m[1][2]*m[2][3]*m[3][0] -\
m[0][1]*m[1][3]*m[2][0]*m[3][2] +\
m[0][1]*m[1][3]*m[2][2]*m[3][0] +\
m[0][2]*m[1][0]*m[2][1]*m[3][3] -\
m[0][2]*m[1][0]*m[2][3]*m[3][1] -\
m[0][2]*m[1][1]*m[2][0]*m[3][3] +\
m[0][2]*m[1][1]*m[2][3]*m[3][0] +\
m[0][2]*m[1][3]*m[2][0]*m[3][1] -\
m[0][2]*m[1][3]*m[2][1]*m[3][0] -\
m[0][3]*m[1][0]*m[2][1]*m[3][2] +\
m[0][3]*m[1][0]*m[2][2]*m[3][1] +\
m[0][3]*m[1][1]*m[2][0]*m[3][2] -\
m[0][3]*m[1][1]*m[2][2]*m[3][0] -\
m[0][3]*m[1][2]*m[2][0]*m[3][1] +\
m[0][3]*m[1][2]*m[2][1]*m[3][0]
}
// A right-leaning tree of byte multiplications.
func righttree(a, b, c, d uint8) uint8 {
return a * (b * (c * (d *\
(a * (b * (c * (d *\
(a * (b * (c * (d *\
(a * (b * (c * (d *\
(a * (b * (c * (d *\
a * (b * (c * d))))))))))))))))))))))
}
// A left-leaning tree of byte multiplications.
func lefttree(a, b, c, d uint8) uint8 {
return ((((((((((((((((((a * b) * c) * d *\
a) * b) * c) * d *\
a) * b) * c) * d *\
a) * b) * c) * d *\
a) * b) * c) * d *\
a) * b) * c) * d)
}
type T struct {
Next I
}
type I interface{}
// A chains of type assertions.
func ChainT(t *T) *T {
return t.\
Next.(*T).\
Next.(*T).\
Next.(*T).\
Next.(*T).\
Next.(*T).\
Next.(*T).\
Next.(*T).\
Next.(*T).\
Next.(*T).\
Next.(*T).\
Next.(*T).\
Next.(*T).\
Next.(*T).\
Next.(*T).\
Next.(*T).\
Next.(*T).\
Next.(*T).\
Next.(*T).\
Next.(*T).\
Next.(*T)
}
type U struct {
Children []J
}
func (u *U) Child(n int) J { return u.Children[n] }
type J interface {
Child(n int) J
}
func ChainUAssert(u *U) *U {
return u.Child(0).(*U).\
Child(0).(*U).\
Child(0).(*U).\
Child(0).(*U).\
Child(0).(*U).\
Child(0).(*U).\
Child(0).(*U).\
Child(0).(*U).\
Child(0).(*U).\
Child(0).(*U).\
Child(0).(*U).\
Child(0).(*U).\
Child(0).(*U).\
Child(0).(*U).\
Child(0).(*U).\
Child(0).(*U).\
Child(0).(*U).\
Child(0).(*U).\
Child(0).(*U).\
Child(0).(*U).\
Child(0).(*U).\
Child(0).(*U)
}
func ChainUNoAssert(u *U) *U {
return u.Child(0).\
Child(0).\
Child(0).\
Child(0).\
Child(0).\
Child(0).\
Child(0).\
Child(0).\
Child(0).\
Child(0).\
Child(0).\
Child(0).\
Child(0).\
Child(0).\
Child(0).\
Child(0).\
Child(0).\
Child(0).\
Child(0).\
Child(0).\
Child(0).\
Child(0).(*U)
}
コアとなるコードの解説
src/cmd/5g/cgen.c
の変更は、Goコンパイラ5g
のコード生成ロジックにおけるレジスタ割り当ての精度を向上させるものです。
rg
変数の導入:cgen
関数のローカル変数としてint rg;
が追加されました。この変数は、関数呼び出しの結果を格納するレジスタの番号を一時的に保持するために使用されます。OCALLFUNC
とOCALLMETH
の統合とレジスタ保護ロジック:- 以前は独立していた
OCALLFUNC
のcase
文が削除され、その処理がOCALLMETH
のcase
文に統合されました。これにより、メソッド呼び出しと通常の関数呼び出しの両方で、共通のレジスタ管理ロジックが適用されるようになりました。 - 統合されたブロック内で、まず
rg = -1;
でrg
を初期化します。 if(n->ullman >= UINF)
という条件が追加されました。これは、現在のノードn
が表す式のUllman NumberがUINF
(無限大、つまり非常に複雑)以上である場合に、特別なレジスタ保護処理を適用することを示しています。これは、複雑な式の結果を保持するレジスタが、ネストされた呼び出し中に誤って再利用されるリスクが高いことを示唆しています。- この条件が真であり、かつ
res
(結果を格納するノード)がレジスタ(OREGISTER
)またはレジスタ間接参照(OINDREG
)である場合、rg = res->val.u.reg;
でそのレジスタの番号を取得します。 - そして、
reg[rg]--;
を実行します。reg
配列は、コンパイラが管理するレジスタの使用状況(参照カウントなど)を示すものと考えられます。ここで参照カウントをデクリメントすることで、このレジスタが一時的に「使用中」であることをコンパイラに伝え、ネストされた関数呼び出し中に他の用途に割り当てられないように保護します。 - 次に、実際の関数呼び出しコードを生成するために、
n->op
(ノードの操作タイプ)に応じてcgen_callmeth(n, 0);
またはcgen_call(n, 0);
が呼び出されます。これらの関数は、引数の準備や関数呼び出し命令の生成を行います。 - 関数呼び出しが完了した後、
if(rg >= 0)
の条件で、rg
が有効なレジスタ番号であれば、reg[rg]++;
を実行します。これにより、保護していたレジスタの参照カウントを元に戻し、レジスタを再び通常の割り当てプールに戻します。 - 最後に、
cgen_callret(n, res);
が呼び出され、関数からの戻り値の処理が行われます。
- 以前は独立していた
この修正により、特に複雑な式の結果を保持するレジスタが、ネストされた関数呼び出しのコンテキストで安全に扱われるようになり、レジスタ不足によるコンパイル時の問題が解消されます。
test/torture.go
は、この修正が正しく機能することを検証するための重要なテストケース群です。このファイルに含まれる関数は、以下のような特徴を持ち、コンパイラのレジスタ割り当てや最適化ロジックに負荷をかけます。
concat
、determinant
、righttree
、lefttree
: これらは、非常に深くネストされた演算や、多数のオペランドを含む複雑な式を生成します。これにより、コンパイラが中間結果を保持するために多くのレジスタを必要とし、レジスタ割り当て器の能力が試されます。ChainT
、ChainUAssert
、ChainUNoAssert
: これらは、多数の型アサーションやメソッド呼び出しを連鎖させています。特に型アサーションは、実行時に型チェックを行うためのコードを生成する必要があり、コンパイラにとって複雑な処理となる場合があります。これらのテストは、コンパイラがこのような連鎖的な操作を効率的かつ正しく処理できるかを検証します。
これらのストレステストが追加されたことで、将来的に同様のレジスタ割り当てに関する回帰バグが発生するのを防ぐための安全網が提供されます。
関連リンク
- Go言語公式ドキュメント: https://go.dev/
- Goコンパイラの内部構造に関する一般的な情報: https://go.dev/doc/compiler (これは一般的な情報であり、
5g
に特化したものではありません)
参考にした情報源リンク
- Go言語のソースコード(特に
src/cmd/5g/
ディレクトリ) - コンパイラ設計に関する一般的な知識(レジスタ割り当て、Ullman Numberなど)
- Gitコミットメッセージと差分情報
- Gerrit Code Review: https://golang.org/cl/6586072 (コミットメッセージに記載されているGerritの変更リスト)
- Go言語のテストフレームワークと慣習に関する情報