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

[インデックス 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: Nodeopフィールドで表現される操作の種類です。
    • OREGISTER: 値がレジスタに格納されていることを示します。
    • OINDREG: 値がレジスタによって指されるメモリ位置に格納されていることを示します(間接参照)。
  • ODOTPTR: ポインタを通じて構造体のフィールドにアクセスする操作を表すNodeopです(例: ptr.field)。
  • OCALLFUNC, OCALLMETH, OCALLINTER: 関数呼び出しに関連するNodeopです。
    • OCALLFUNC: 通常の関数呼び出し。
    • OCALLMETH: メソッド呼び出し。
    • OCALLINTER: インターフェースメソッド呼び出し。
  • regalloc, regfree: レジスタを割り当てたり解放したりする関数です。
  • gmove: ある場所から別の場所へデータを移動する(コピーする)関数です。
  • isfixedarray: 型が固定サイズの配列であるかどうかを判定する関数です。
  • isptr: 型がポインタであるかどうかを判定する関数です。
  • Array_array, Array_nel: Goのスライスや配列の内部表現に関連するオフセットです。Array_arrayは基底ポインタ、Array_nelは要素数(length)に対応します。
  • mpgetfix: 多倍長整数(mpint)から固定小数点数(fix)を取得する関数です。配列のインデックス計算などで使用されます。

技術的詳細

このコミットの主要な変更点は、コンパイラのコード生成ロジック、特にレジスタ割り当てとポインタ操作のハンドリングに関するものです。

  1. xoffsetの加算 (+=) への変更:
    • src/cmd/5g/cgen.ccgen関数内で、n1.xoffset = 0;となっていた箇所がn1.xoffset += 0;に変更されています。これは、xoffsetが既に何らかの値を持っている場合に、それを上書きするのではなく、既存の値に0を加算するという意味になります。実質的には値は変わりませんが、コンパイラの内部的な処理フローにおいて、xoffsetの扱いがより一貫性を持つように変更された可能性があります。特に、以前の計算結果がxoffsetに格納されている場合に、それを保持しつつオフセットを調整する意図があるかもしれません。
  2. res != Nのチェックの追加:
    • cgen関数とigen関数(5g6g8gの各cgen.c)において、ullman >= UINFの条件分岐内で、res->op == OREGISTER || res->op == OINDREGのチェックの前にres != NresがNULLでないこと)のチェックが追加されました。これは、resがNULLポインタである場合に、そのメンバーにアクセスしようとしてクラッシュするのを防ぐためのNULLポインタチェックです。レジスタ割り当ての際に、結果を格納するresノードが有効であることを保証します。
  3. igen関数におけるODOTPTROCALLFUNCOCALLMETHOCALLINTERのハンドリングの改善:
    • src/cmd/5g/cgen.csrc/cmd/6g/cgen.csrc/cmd/8g/cgen.cigen関数に、ODOTPTR(ポインタ経由のフィールドアクセス)と各種関数呼び出し(OCALLFUNC, OCALLMETH, OCALLINTER)の新しいケースが追加されました。
    • ODOTPTRの改善: 以前は単純にcgen(n->left, a)で左辺のポインタを評価していましたが、新しいコードでは、左辺がaddable(アドレスを直接取得できる)であるか、または関数呼び出しの結果である場合に、igenを再帰的に呼び出してアドレスを生成し、その結果をレジスタに移動するようになりました。これにより、複雑なポインタチェーンや関数呼び出しの結果としてのポインタのデリファレンスがより正確に処理され、レジスタの利用効率が向上します。特に、n->left->type->type->width >= unmappedzeroの条件で、構造体が十分に大きい場合にnilチェックを行うコードが追加されており、不正なポインタアクセスを防ぐための堅牢性が高まっています。
    • OCALL*の改善: 関数呼び出しの結果を処理する際に、resノードがレジスタまたは間接レジスタである場合に、一時的にレジスタを解放し、関数呼び出し後に再度割り当てるロジックが追加されました。これは、関数呼び出しが多くのレジスタを消費する可能性があるため、呼び出し中にresが使用するレジスタを解放することで、レジスタ不足を回避し、呼び出し後のレジスタの状態を適切に管理するためです。
  4. agenr関数の変更 (5g):
    • src/cmd/5g/cgen.cagenr関数が大幅に変更されました。以前はregallocagenを直接呼び出していましたが、新しい実装では、まずigen(n, &n1, res)で間接参照のアドレスを生成し、その結果をn1に格納します。次に、regalloc(a, types[tptr], N)で新しいレジスタを割り当て、agen(&n1, a)n1のアドレスをaに生成し、最後にregfree(&n1)n1が使用していたレジスタを解放します。この変更は、間接参照のアドレス計算とレジスタ割り当てのフローをより明確にし、レジスタのライフサイクル管理を改善することを目的としています。
  5. agen関数における配列インデックスのハンドリングの改善 (6g):
    • src/cmd/6g/cgen.cagen関数において、配列のインデックス操作(OINDEX)に関するコードが変更されました。特に、固定サイズの配列やポインタ経由の配列アクセスにおいて、regalloc(&n3, types[tptr], res)の呼び出しが、条件分岐の内部に移動し、より適切なタイミングでレジスタが割り当てられるようになりました。これにより、レジスタの過剰な割り当てや、不要なレジスタの保持が減少し、レジスタ不足の発生を抑制します。
  6. 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コンパイラのコード生成フェーズにおけるレジスタ割り当てとポインタ操作のロジックを改善することにあります。

  1. xoffsetの変更 (n1.xoffset = 0; から n1.xoffset += 0;):
    • これは一見すると意味のない変更に見えますが、コンパイラの内部的なデータフローにおいて重要な意味を持つ可能性があります。xoffsetは、構造体フィールドのオフセットや配列要素のオフセットなど、メモリ上の位置を示すために使用されます。以前の=による代入は、常にxoffsetを0にリセットしていましたが、+=に変更することで、もしn1が既に有効なオフセット情報を持っている場合、それを保持しつつ、現在の操作によるオフセット調整(この場合は0)を適用するという意図が考えられます。これにより、複雑なアドレス計算の連鎖において、中間結果のオフセット情報が失われることを防ぎ、より正確なアドレス指定を可能にします。
  2. res != NのNULLチェックの追加:
    • cgenおよびigen関数内で、結果を格納するNodeであるresがNULLでないことを明示的にチェックするようになりました。これは、resがNULLであるにもかかわらず、そのメンバー(res->op, res->val.u.regなど)にアクセスしようとすると、NULLポインタデリファレンスによるクラッシュが発生するため、これを防ぐための防御的なプログラミングです。特に、ullman >= UINFのような複雑なケースでは、resが予期せずNULLになる可能性があったのかもしれません。
  3. 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チェックを行うコードが追加されました。これは、大きな構造体へのポインタアクセスにおいて、不正なアドレスへのアクセスを防ぐための重要な安全対策です。
  4. 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]++;: 関数呼び出し後、解放したレジスタを再割り当てします。
    • この戦略により、関数呼び出し中にレジスタが不足するリスクが軽減され、コンパイラの堅牢性が向上します。
  5. agenr関数のリファクタリング:
    • agenrは、間接参照のアドレスをレジスタに生成する関数です。以前はregallocagenを直接呼び出していましたが、新しい実装では、まずigenを使って間接参照のアドレスを計算し、その結果を一時的なNode (n1) に格納します。その後、n1のアドレスを最終的な結果レジスタに移動し、n1が使用していたレジスタを解放します。この変更は、アドレス計算のステップをより明確にし、レジスタのライフサイクル管理を改善することで、レジスタ不足の問題を緩和します。
  6. agen関数における配列インデックスのレジスタ割り当ての最適化 (6g):
    • agen関数内の配列インデックス処理において、regalloc(&n3, types[tptr], res)の呼び出しが、isfixedarray(nl->type)の条件分岐の内部に移動しました。これにより、レジスタの割り当てがより必要なタイミングに限定され、不要なレジスタの保持が減ります。特に、isptr[n->left->type->etype]のチェックが追加され、ポインタ経由の配列アクセスの場合のigengmoveのフローが改善されています。これにより、配列のインデックス計算におけるレジスタの利用がより効率的になり、レジスタ不足の発生を抑制します。
  7. torture.goの新しいテストケース:
    • 追加されたChainAssertIndex, ChainAssertArrayIndex, ChainAssertArrayptrIndexは、コンパイラのレジスタ割り当てとポインタ追跡能力を試すためのものです。これらのテストは、多段階の型アサーションと配列/スライスインデックス操作を連鎖させることで、コンパイラがレジスタを効率的に管理し、複雑なアドレス計算を正確に実行できるかを検証します。これらのテストがパスするようになったことは、このコミットによるコンパイラの改善が成功したことを示しています。

これらの変更は、Goコンパイラがより複雑なコードパターンを効率的かつ正確にコンパイルできるようにするための重要なステップであり、特にレジスタ不足の問題を解決し、コンパイラの安定性と堅牢性を向上させることに貢献しています。

関連リンク

参考にした情報源リンク