[インデックス 14151] ファイルの概要
このコミットは、Go言語のコンパイラ(cmd/5g
, cmd/6g
, cmd/8g
)におけるレジスタ割り当ての不具合を修正し、特にポインタ操作や関数呼び出しに関連するコード生成の堅牢性を向上させるものです。これにより、特定の複雑なコードパターン(特にexp/types
テストや、連鎖的な型アサーションとスライスインデックス操作を含むコード)のコンパイルが正常に行われるようになります。
コミット
commit 7e144bcab0df7efa7255ecfa1ac425931902437f
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date: Tue Oct 16 07:22:33 2012 +0200
cmd/5g, cmd/6g, cmd/8g: fix out of registers.
This patch is enough to fix compilation of
exp/types tests but only passes a stripped down
version of the appripriate torture test.
Update #4207.
R=dave, nigeltao, rsc, golang-dev
CC=golang-dev
https://golang.org/cl/6621061
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/7e144bcab0df7efa7255ecfa1ac425931902437f
元コミット内容
cmd/5g, cmd/6g, cmd/8g: fix out of registers.
This patch is enough to fix compilation of
exp/types tests but only passes a stripped down
version of the appripriate torture test.
Update #4207.
変更の背景
このコミットは、Go言語のコンパイラが特定の複雑なコードパターンをコンパイルする際に発生していた「out of registers」(レジスタ不足)の問題を解決するために導入されました。これは、GoのIssue 4207として報告されていた問題に対応するものです。
当時のGoコンパイラは、5g
(ARM)、6g
(x86-64)、8g
(x86)といったアーキテクチャ固有のバックエンドを持っていました。これらのコンパイラは、コード生成の過程でレジスタを効率的に利用する必要がありましたが、特定の状況下でレジスタが枯渇し、コンパイルエラーや不正なコード生成を引き起こすことがありました。
特に、exp/types
パッケージのテストや、連鎖的な型アサーションとスライスインデックス操作を含む「torture test」のような複雑なコードは、このレジスタ不足の問題を顕在化させました。このコミットは、これらの問題に対処し、コンパイラの堅牢性を高めることを目的としています。コミットメッセージにあるように、このパッチはexp/types
テストのコンパイルを修正するのに十分でしたが、完全な「torture test」をパスするには、まださらなる改善が必要であることが示唆されています。
前提知識の解説
このコミットを理解するためには、Goコンパイラのバックエンドにおけるコード生成の基本的な概念と、レジスタ割り当ての仕組みについて知る必要があります。
- Goコンパイラバックエンド (
5g
,6g
,8g
): Go言語の初期のコンパイラは、ターゲットアーキテクチャごとに異なるバックエンドを持っていました。5g
はARM、6g
はx86-64、8g
はx86アーキテクチャに対応していました。これらのバックエンドは、Goの抽象構文木(AST)を各アーキテクチャの機械語に変換する役割を担っていました。 - レジスタ割り当て (Register Allocation): CPUのレジスタは、非常に高速なデータ記憶領域であり、プログラムの実行速度に大きく影響します。コンパイラは、変数や中間結果を効率的にレジスタに割り当てることで、メモリへのアクセスを減らし、パフォーマンスを向上させます。しかし、利用可能なレジスタの数には限りがあるため、複雑な計算や多数の変数を扱う場合、レジスタが不足する「レジスタ不足」の状態に陥ることがあります。
cgen.c
,igen.c
,agen.c
: これらはGoコンパイラのコード生成フェーズにおける主要なファイルです。cgen.c
(Code Generation): 式の値をレジスタに生成する関数が含まれます。igen.c
(Indirect Generation): 式のアドレスをレジスタに生成する関数が含まれます。ポインタのデリファレンスや構造体メンバーへのアクセスなど、間接参照を伴う操作で利用されます。agen.c
(Address Generation): 式のアドレスをレジスタに生成する関数が含まれますが、igen
とは異なる文脈(例えば、配列のインデックス計算など)で使われることがあります。
Node
: Goコンパイラ内部で、プログラムの抽象構文木(AST)の各要素を表すデータ構造です。変数、定数、演算子、関数呼び出しなどがNode
として表現されます。Type
: Go言語の型システムにおける型情報を表すデータ構造です。ullman
number: Ullman数は、コンパイラの最適化において、式の評価順序を決定するために使用されるヒューリスティックな値です。レジスタ割り当ての効率に影響を与えます。UINF
(無限大)は、非常に複雑な式や、レジスタを多く消費する可能性のある式を示すことがあります。OREGISTER
,OINDREG
:Node
のop
フィールドで表現される操作の種類です。OREGISTER
: 値がレジスタに格納されていることを示します。OINDREG
: 値がレジスタによって指されるメモリ位置に格納されていることを示します(間接参照)。
ODOTPTR
: ポインタを通じて構造体のフィールドにアクセスする操作を表すNode
のop
です(例:ptr.field
)。OCALLFUNC
,OCALLMETH
,OCALLINTER
: 関数呼び出しに関連するNode
のop
です。OCALLFUNC
: 通常の関数呼び出し。OCALLMETH
: メソッド呼び出し。OCALLINTER
: インターフェースメソッド呼び出し。
regalloc
,regfree
: レジスタを割り当てたり解放したりする関数です。gmove
: ある場所から別の場所へデータを移動する(コピーする)関数です。isfixedarray
: 型が固定サイズの配列であるかどうかを判定する関数です。isptr
: 型がポインタであるかどうかを判定する関数です。Array_array
,Array_nel
: Goのスライスや配列の内部表現に関連するオフセットです。Array_array
は基底ポインタ、Array_nel
は要素数(length)に対応します。mpgetfix
: 多倍長整数(mpint
)から固定小数点数(fix
)を取得する関数です。配列のインデックス計算などで使用されます。
技術的詳細
このコミットの主要な変更点は、コンパイラのコード生成ロジック、特にレジスタ割り当てとポインタ操作のハンドリングに関するものです。
xoffset
の加算 (+=
) への変更:src/cmd/5g/cgen.c
のcgen
関数内で、n1.xoffset = 0;
となっていた箇所がn1.xoffset += 0;
に変更されています。これは、xoffset
が既に何らかの値を持っている場合に、それを上書きするのではなく、既存の値に0を加算するという意味になります。実質的には値は変わりませんが、コンパイラの内部的な処理フローにおいて、xoffset
の扱いがより一貫性を持つように変更された可能性があります。特に、以前の計算結果がxoffset
に格納されている場合に、それを保持しつつオフセットを調整する意図があるかもしれません。
res != N
のチェックの追加:cgen
関数とigen
関数(5g
、6g
、8g
の各cgen.c
)において、ullman >= UINF
の条件分岐内で、res->op == OREGISTER || res->op == OINDREG
のチェックの前にres != N
(res
がNULLでないこと)のチェックが追加されました。これは、res
がNULLポインタである場合に、そのメンバーにアクセスしようとしてクラッシュするのを防ぐためのNULLポインタチェックです。レジスタ割り当ての際に、結果を格納するres
ノードが有効であることを保証します。
igen
関数におけるODOTPTR
、OCALLFUNC
、OCALLMETH
、OCALLINTER
のハンドリングの改善:src/cmd/5g/cgen.c
、src/cmd/6g/cgen.c
、src/cmd/8g/cgen.c
のigen
関数に、ODOTPTR
(ポインタ経由のフィールドアクセス)と各種関数呼び出し(OCALLFUNC
,OCALLMETH
,OCALLINTER
)の新しいケースが追加されました。ODOTPTR
の改善: 以前は単純にcgen(n->left, a)
で左辺のポインタを評価していましたが、新しいコードでは、左辺がaddable
(アドレスを直接取得できる)であるか、または関数呼び出しの結果である場合に、igen
を再帰的に呼び出してアドレスを生成し、その結果をレジスタに移動するようになりました。これにより、複雑なポインタチェーンや関数呼び出しの結果としてのポインタのデリファレンスがより正確に処理され、レジスタの利用効率が向上します。特に、n->left->type->type->width >= unmappedzero
の条件で、構造体が十分に大きい場合にnilチェックを行うコードが追加されており、不正なポインタアクセスを防ぐための堅牢性が高まっています。OCALL*
の改善: 関数呼び出しの結果を処理する際に、res
ノードがレジスタまたは間接レジスタである場合に、一時的にレジスタを解放し、関数呼び出し後に再度割り当てるロジックが追加されました。これは、関数呼び出しが多くのレジスタを消費する可能性があるため、呼び出し中にres
が使用するレジスタを解放することで、レジスタ不足を回避し、呼び出し後のレジスタの状態を適切に管理するためです。
agenr
関数の変更 (5g
):src/cmd/5g/cgen.c
のagenr
関数が大幅に変更されました。以前はregalloc
とagen
を直接呼び出していましたが、新しい実装では、まずigen(n, &n1, res)
で間接参照のアドレスを生成し、その結果をn1
に格納します。次に、regalloc(a, types[tptr], N)
で新しいレジスタを割り当て、agen(&n1, a)
でn1
のアドレスをa
に生成し、最後にregfree(&n1)
でn1
が使用していたレジスタを解放します。この変更は、間接参照のアドレス計算とレジスタ割り当てのフローをより明確にし、レジスタのライフサイクル管理を改善することを目的としています。
agen
関数における配列インデックスのハンドリングの改善 (6g
):src/cmd/6g/cgen.c
のagen
関数において、配列のインデックス操作(OINDEX
)に関するコードが変更されました。特に、固定サイズの配列やポインタ経由の配列アクセスにおいて、regalloc(&n3, types[tptr], res)
の呼び出しが、条件分岐の内部に移動し、より適切なタイミングでレジスタが割り当てられるようになりました。これにより、レジスタの過剰な割り当てや、不要なレジスタの保持が減少し、レジスタ不足の発生を抑制します。
torture.go
の更新:test/torture.go
に、ChainAssertIndex
,UArr
,ChainAssertArrayIndex
,UArrPtr
,ChainAssertArrayptrIndex
といった新しいテストケースが追加されました。これらのテストは、連鎖的な型アサーションと配列/スライスインデックス操作を組み合わせた複雑なパターンを検証するためのものです。特に、ChainAssertIndex
は10回以上の連鎖的な型アサーションとインデックス操作を含み、コンパイラのレジスタ割り当てとポインタ追跡能力を極限まで試すことを意図しています。これらのテストの追加は、このコミットが解決しようとしている問題の性質を具体的に示しています。
これらの変更は全体として、Goコンパイラが複雑なポインタ操作、関数呼び出し、および配列インデックス操作をより堅牢かつ効率的に処理できるようにすることを目的としています。特に、レジスタのライフサイクル管理を改善し、レジスタ不足によるコンパイルエラーを減らすことに貢献しています。
コアとなるコードの変更箇所
src/cmd/5g/cgen.c
: 91行追加, 3行削除src/cmd/6g/cgen.c
: 56行追加, 13行削除src/cmd/8g/cgen.c
: 18行追加, 3行削除test/torture.go
: 76行追加, 3行削除
src/cmd/5g/cgen.c
の変更点
--- a/src/cmd/5g/cgen.c
+++ b/src/cmd/5g/cgen.c
@@ -313,7 +313,7 @@ cgen(Node *n, Node *res)
regalloc(&n2, n->type, &n1);
n1.op = OINDREG;
n1.type = n->type;
- n1.xoffset = 0;
+ n1.xoffset += 0;
gmove(&n1, &n2);
gmove(&n2, res);
regfree(&n1);
@@ -349,7 +349,7 @@ cgen(Node *n, Node *res)
regalloc(&n2, types[TUINT32], &n1);
n1.op = OINDREG;
n1.type = types[TUINT32];
- n1.xoffset = Array_nel;
+ n1.xoffset += Array_nel;
gmove(&n1, &n2);
gmove(&n2, res);
regfree(&n1);
@@ -405,7 +405,7 @@ cgen(Node *n, Node *res)
// Pick it up again after the call.
rg = -1;
if(n->ullman >= UINF) {
- if(res->op == OREGISTER || res->op == OINDREG) {
+ if(res != N && (res->op == OREGISTER || res->op == OINDREG)) {
r = res->val.u.reg;
reg[rg]--;
}
@@ -890,6 +890,83 @@ ret:
void
igen(Node *n, Node *a, Node *res)
{
+ Node n1;
+ Prog *p1;
+ int r;
+
+ if(debug['g']) {
+ dump("\nigen-n", n);
+ }
+ switch(n->op) {
+ case ODOT:
+ igen(n->left, a, res);
+ a->xoffset += n->xoffset;
+ a->type = n->type;
+ return;
+
+ case ODOTPTR:
+ if(n->left->addable
+ || n->left->op == OCALLFUNC
+ || n->left->op == OCALLMETH
+ || n->left->op == OCALLINTER) {
+ // igen-able nodes.
+ igen(n->left, &n1, res);
+ regalloc(a, types[tptr], &n1);
+ gmove(&n1, a);
+ regfree(&n1);
+ } else {
+ regalloc(a, types[tptr], res);
+ cgen(n->left, a);
+ }
+ if(n->xoffset != 0) {
+ // explicit check for nil if struct is large enough
+ // that we might derive too big a pointer.
+ if(n->left->type->type->width >= unmappedzero) {
+ regalloc(&n1, types[tptr], N);
+ gmove(a, &n1);
+ p1 = gins(AMOVW, &n1, &n1);
+ p1->from.type = D_OREG;
+ p1->from.offset = 0;
+ regfree(&n1);
+ }
+ }
+ a->op = OINDREG;
+ a->xoffset = n->xoffset;
+ a->type = n->type;
+ return;
+
+ case OCALLMETH:
+ case OCALLFUNC:
+ case OCALLINTER:
+ // Release res so that it is available for cgen_call.
+ // Pick it up again after the call.
+ r = -1;
+ if(n->ullman >= UINF) {
+ if(res != N && (res->op == OREGISTER || res->op == OINDREG)) {
+ r = res->val.u.reg;
+ reg[r]--;
+ }
+ }
+ switch(n->op) {
+ case OCALLMETH:
+ cgen_callmeth(n, 0);
+ break;
+ case OCALLFUNC:
+ cgen_call(n, 0);
+ break;
+ case OCALLINTER:
+ cgen_callinter(n, N, 0);
+ break;
+ }
+ if(r >= 0)
+ reg[r]++;
+ regalloc(a, types[tptr], res);
+ cgen_aret(n, a);
+ a->op = OINDREG;
+ a->type = n->type;
+ return;
+ }
+
regalloc(a, types[tptr], res);
agen(n, a);
a->op = OINDREG;
@@ -905,8 +982,12 @@ igen(Node *n, Node *a, Node *res)
void
agenr(Node *n, Node *a, Node *res)
{
- regalloc(a, types[tptr], res);
- agen(n, a);
+ Node n1;
+
+ igen(n, &n1, res);
+ regalloc(a, types[tptr], N);
+ agen(&n1, a);
+ regfree(&n1);
}
void
src/cmd/6g/cgen.c
の変更点
--- a/src/cmd/6g/cgen.c
+++ b/src/cmd/6g/cgen.c
@@ -605,13 +605,14 @@ agen(Node *n, Node *res)
tcgen(nr, &n1);
}
if(!isconst(nl, CTSTR)) {
- regalloc(&n3, types[tptr], res);
- if(isfixedarray(nl->type))
+ if(isfixedarray(nl->type)) {
+ regalloc(&n3, types[tptr], res);
agen(nl, &n3);
- else {
+ } else {
igen(nl, &nlen, res);
nlen.type = types[tptr];
nlen.xoffset += Array_array;
+ regalloc(&n3, types[tptr], res);
gmove(&nlen, &n3);
nlen.type = types[simtype[TUINT]];
nlen.xoffset += Array_nel-Array_array;
@@ -624,10 +625,10 @@ agen(Node *n, Node *res)
nr = &tmp;
irad:
if(!isconst(nl, CTSTR)) {
- regalloc(&n3, types[tptr], res);
- if(isfixedarray(nl->type))
+ if(isfixedarray(nl->type)) {
+ regalloc(&n3, types[tptr], res);
agen(nl, &n3);
- else {
+ } else {
if(!nl->addable) {
// igen will need an addressable node.
tempname(&tmp2, nl->type);
@@ -637,6 +638,7 @@ agen(Node *n, Node *res)
igen(nl, &nlen, res);
nlen.type = types[tptr];
nlen.xoffset += Array_array;
+ regalloc(&n3, types[tptr], res);
gmove(&nlen, &n3);
nlen.type = types[simtype[TUINT]];
nlen.xoffset += Array_nel-Array_array;
@@ -814,8 +816,11 @@ igen(Node *n, Node *a, Node *res)
{
Type *fp;
Iter flist;
- Node n1, n2;
+ Node n1;
+ if(debug['g']) {
+ dump("\nigen-n", n);
+ }
switch(n->op) {
case ONAME:
if((n->class&PHEAP) || n->class == PPARAMREF)
@@ -838,8 +843,19 @@ igen(Node *n, Node *a, Node *res)
return;
case ODOTPTR:
- regalloc(a, types[tptr], res);
- cgen(n->left, a);
+ if(n->left->addable
+ || n->left->op == OCALLFUNC
+ || n->left->op == OCALLMETH
+ || n->left->op == OCALLINTER) {
+ // igen-able nodes.
+ igen(n->left, &n1, res);
+ regalloc(a, types[tptr], &n1);
+ gmove(&n1, a);
+ regfree(&n1);
+ } else {
+ regalloc(a, types[tptr], res);
+ cgen(n->left, a);
+ }
if(n->xoffset != 0) {
// explicit check for nil if struct is large enough
// that we might derive too big a pointer.
@@ -878,7 +894,7 @@ igen(Node *n, Node *a, Node *res)
a->xoffset = fp->width;
a->type = n->type;
return;
-
+
case OINDEX:
// Index of fixed-size array by constant can
// put the offset in the addressing.
@@ -887,18 +903,22 @@ igen(Node *n, Node *a, Node *res)
if(isfixedarray(n->left->type) ||
(isptr[n->left->type->etype] && isfixedarray(n->left->left->type)))
if(isconst(n->right, CTINT)) {
- nodconst(&n1, types[TINT64], 0);
- n2 = *n;
- n2.right = &n1;
+ // Compute &a.
+ if(!isptr[n->left->type->etype])
+ igen(n->left, a, res);
+ else {
+ igen(n->left, &n1, res);
+ regalloc(a, types[tptr], res);
+ gmove(&n1, a);
+ regfree(&n1);
+ a->op = OINDREG;
+ }
- regalloc(a, types[tptr], res);
- agen(&n2, a);
- a->op = OINDREG;
- a->xoffset = mpgetfix(n->right->val.u.xval)*n->type->width;
+ // Compute &a[i] as &a + i*width.
a->type = n->type;
+ a->xoffset += mpgetfix(n->right->val.u.xval)*n->type->width;
return;
}
-
}
regalloc(a, types[tptr], res);
src/cmd/8g/cgen.c
の変更点
src/cmd/5g/cgen.c
と類似の変更がigen
関数とODOTPTR
のハンドリングに適用されています。
test/torture.go
の変更点
--- a/test/torture.go
+++ b/test/torture.go
@@ -170,6 +170,74 @@ func ChainUNoAssert(u *U) *U {
Child(0).(*U)
}
+// Type assertions and slice indexing. See issue 4207.
+func ChainAssertIndex(u *U) J {
+ return u.
+ Children[0].(*U).
+ Children[0].(*U).
+ Children[0].(*U).
+ Children[0].(*U).
+ Children[0].(*U).
+ Children[0].(*U).
+ Children[0].(*U).
+ Children[0].(*U).
+ Children[0].(*U).
+ Children[0].(*U).
+ Children[0].(*U).
+ Children[0].(*U).
+ Children[0].(*U).
+ Children[0]
+}
+
+type UArr struct {
+ Children [2]J
+}
+
+func (u *UArr) Child(n int) J { return u.Children[n] }
+
+func ChainAssertArrayIndex(u *UArr) J {
+ return u.
+ Children[0].(*UArr).
+ Children[0].(*UArr).
+ Children[0].(*UArr).
+ Children[0].(*UArr).
+ Children[0].(*UArr).
+ Children[0].(*UArr).
+ Children[0].(*UArr).
+ // Children[0].(*UArr).
+ // Children[0].(*UArr).
+ // Children[0].(*UArr).
+ // Children[0].(*UArr).
+ // Children[0].(*UArr).
+ // Children[0].(*UArr).
+ Children[0]
+}
+
+type UArrPtr struct {
+ Children *[2]J
+}
+
+func (u *UArrPtr) Child(n int) J { return u.Children[n] }
+
+func ChainAssertArrayptrIndex(u *UArrPtr) J {
+ // TODO: don't crash on longer chains.
+ return u.
+ Children[0].(*UArrPtr).
+ Children[0].(*UArrPtr).
+ Children[0].(*UArrPtr).
+ Children[0].(*UArrPtr).
+ // Children[0].(*UArrPtr).
+ // Children[0].(*UArrPtr).
+ // Children[0].(*UArrPtr).
+ // Children[0].(*UArrPtr).
+ // Children[0].(*UArrPtr).
+ // Children[0].(*UArrPtr).
+ // Children[0].(*UArrPtr).
+ // Children[0].(*UArrPtr).
+ // Children[0].(*UArrPtr).
+ Children[0]
+}
+
// Chains of divisions. See issue 4201.
func ChainDiv(a, b int) int {
@@ -180,10 +248,10 @@ func ChainDiv(a, b int) int {
func ChainDivRight(a, b int) int {
return a / (b / (a / (b /
- (a / (b / (a / (b /
- (a / (b / (a / (b /
- (a / (b / (a / (b /
- (a / (b / (a / b))))))))))))))))))
+ (a / (b / (a / (b /
+ (a / (b / (a / (b /
+ (a / (b / (a / (b /
+ (a / (b / (a / b))))))))))))))))))
}
func ChainDivConst(a int) int {
コアとなるコードの解説
このコミットの核心は、Goコンパイラのコード生成フェーズにおけるレジスタ割り当てとポインタ操作のロジックを改善することにあります。
xoffset
の変更 (n1.xoffset = 0;
からn1.xoffset += 0;
):- これは一見すると意味のない変更に見えますが、コンパイラの内部的なデータフローにおいて重要な意味を持つ可能性があります。
xoffset
は、構造体フィールドのオフセットや配列要素のオフセットなど、メモリ上の位置を示すために使用されます。以前の=
による代入は、常にxoffset
を0にリセットしていましたが、+=
に変更することで、もしn1
が既に有効なオフセット情報を持っている場合、それを保持しつつ、現在の操作によるオフセット調整(この場合は0)を適用するという意図が考えられます。これにより、複雑なアドレス計算の連鎖において、中間結果のオフセット情報が失われることを防ぎ、より正確なアドレス指定を可能にします。
- これは一見すると意味のない変更に見えますが、コンパイラの内部的なデータフローにおいて重要な意味を持つ可能性があります。
res != N
のNULLチェックの追加:cgen
およびigen
関数内で、結果を格納するNode
であるres
がNULLでないことを明示的にチェックするようになりました。これは、res
がNULLであるにもかかわらず、そのメンバー(res->op
,res->val.u.reg
など)にアクセスしようとすると、NULLポインタデリファレンスによるクラッシュが発生するため、これを防ぐための防御的なプログラミングです。特に、ullman >= UINF
のような複雑なケースでは、res
が予期せずNULLになる可能性があったのかもしれません。
igen
関数におけるODOTPTR
の強化:ODOTPTR
は、ポインタを通じて構造体のフィールドにアクセスする操作(例:p.field
)を扱います。以前は、左辺(ポインタ自体)をcgen
で評価していましたが、新しいコードでは、左辺がaddable
(アドレスを直接取得できる)であるか、または関数呼び出しの結果である場合に、igen
を再帰的に呼び出すようになりました。igen(n->left, &n1, res);
: 左辺のアドレスをn1
に生成します。igen
はアドレスを生成するのに特化しているため、より効率的で正確なアドレス計算が期待できます。regalloc(a, types[tptr], &n1); gmove(&n1, a); regfree(&n1);
:n1
に生成されたアドレスを、最終的な結果を格納するa
に移動し、n1
が使用していたレジスタを解放します。
- この変更により、
(*ptr).field
のような複雑なポインタデリファレンスや、func().field
のように関数がポインタを返す場合のフィールドアクセスが、より適切に処理されるようになります。 - さらに、
if(n->xoffset != 0)
のブロック内で、構造体のサイズがunmappedzero
(通常はページサイズなど、nilチェックが必要な閾値)以上の場合に、明示的なnilチェックを行うコードが追加されました。これは、大きな構造体へのポインタアクセスにおいて、不正なアドレスへのアクセスを防ぐための重要な安全対策です。
igen
関数におけるOCALL*
(関数呼び出し)のレジスタ管理の改善:- 関数呼び出しは、多くのレジスタを消費する可能性があり、レジスタ不足の一般的な原因となります。このコミットでは、
OCALLMETH
,OCALLFUNC
,OCALLINTER
のケースで、関数呼び出しの前にres
が使用するレジスタを一時的に解放し、呼び出し後に再割り当てするロジックが追加されました。if(res != N && (res->op == OREGISTER || res->op == OINDREG)) { r = res->val.u.reg; reg[r]--; }
:res
がレジスタを使用している場合、そのレジスタを一時的に解放します。- 関数呼び出し(
cgen_callmeth
,cgen_call
,cgen_callinter
)を実行。 if(r >= 0) reg[r]++;
: 関数呼び出し後、解放したレジスタを再割り当てします。
- この戦略により、関数呼び出し中にレジスタが不足するリスクが軽減され、コンパイラの堅牢性が向上します。
- 関数呼び出しは、多くのレジスタを消費する可能性があり、レジスタ不足の一般的な原因となります。このコミットでは、
agenr
関数のリファクタリング:agenr
は、間接参照のアドレスをレジスタに生成する関数です。以前はregalloc
とagen
を直接呼び出していましたが、新しい実装では、まずigen
を使って間接参照のアドレスを計算し、その結果を一時的なNode
(n1
) に格納します。その後、n1
のアドレスを最終的な結果レジスタに移動し、n1
が使用していたレジスタを解放します。この変更は、アドレス計算のステップをより明確にし、レジスタのライフサイクル管理を改善することで、レジスタ不足の問題を緩和します。
agen
関数における配列インデックスのレジスタ割り当ての最適化 (6g
):agen
関数内の配列インデックス処理において、regalloc(&n3, types[tptr], res)
の呼び出しが、isfixedarray(nl->type)
の条件分岐の内部に移動しました。これにより、レジスタの割り当てがより必要なタイミングに限定され、不要なレジスタの保持が減ります。特に、isptr[n->left->type->etype]
のチェックが追加され、ポインタ経由の配列アクセスの場合のigen
とgmove
のフローが改善されています。これにより、配列のインデックス計算におけるレジスタの利用がより効率的になり、レジスタ不足の発生を抑制します。
torture.go
の新しいテストケース:- 追加された
ChainAssertIndex
,ChainAssertArrayIndex
,ChainAssertArrayptrIndex
は、コンパイラのレジスタ割り当てとポインタ追跡能力を試すためのものです。これらのテストは、多段階の型アサーションと配列/スライスインデックス操作を連鎖させることで、コンパイラがレジスタを効率的に管理し、複雑なアドレス計算を正確に実行できるかを検証します。これらのテストがパスするようになったことは、このコミットによるコンパイラの改善が成功したことを示しています。
- 追加された
これらの変更は、Goコンパイラがより複雑なコードパターンを効率的かつ正確にコンパイルできるようにするための重要なステップであり、特にレジスタ不足の問題を解決し、コンパイラの安定性と堅牢性を向上させることに貢献しています。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/7e144bcab0df7efa7255ecfa1ac425931902437f
- Go Issue 4207 (関連する可能性のある情報): https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHTZz7cs90yIobcUyrZ1hCWep7__Uz-JIv0SyYvPcIP9NIx8S-0zBcJnRAq9Us0AUmIWiQ4dfnNrBfU_j2BwB2mc68DiDil4J8R9iwWjDPPIKYHHYqWFUMXhDEk6fy7zy0=