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

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

このコミットは、GoコンパイラのARMアーキテクチャ向けバックエンド(cmd/5ccmd/5gcmd/5l)において、利用可能なレジスタの範囲を拡張するものです。具体的には、汎用レジスタR12と、浮動小数点レジスタF8からF15の使用を有効にしています。これにより、コンパイラがより多くのレジスタを効率的に利用できるようになり、生成されるコードのパフォーマンス向上に寄与する可能性があります。

コミット

commit d87bc2f0c0e12672592e7dbf30d2c439376891d9
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Sat Jun 2 12:54:28 2012 -0400

    cmd/5c, cmd/5g, cmd/5l: enable use of R12, F8-F15
    
    R=dave, rsc
    CC=golang-dev
    https://golang.org/cl/6248070

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

https://github.com/golang/go/commit/d87bc2f0c0e12672592e7dbf30d2c439376891d9

元コミット内容

cmd/5c, cmd/5g, cmd/5l: enable use of R12, F8-F15

変更の背景

Go言語は、その設計当初からクロスプラットフォーム対応を重視しており、様々なアーキテクチャで動作するようにコンパイラとランタイムが開発されてきました。ARMアーキテクチャは、モバイルデバイスや組み込みシステムで広く利用されており、Go言語がこれらの分野で普及するためには、ARM向けコード生成の効率性が重要でした。

このコミットが行われた2012年頃は、Go言語がまだ比較的新しい言語であり、各アーキテクチャ向けのコンパイラバックエンドが活発に開発・最適化されていた時期にあたります。特に、レジスタ割り当てはコンパイラの性能を左右する重要な要素であり、利用可能なレジスタを最大限に活用することで、メモリへのアクセスを減らし、命令の実行速度を向上させることができます。

以前のGoコンパイラのARMバックエンドでは、特定のレジスタ(R12F8-F15)が利用対象から除外されていた可能性があります。これは、初期の実装の簡素化、特定のABI(Application Binary Interface)の制約、あるいは単にまだ最適化が及んでいなかったためかもしれません。しかし、より高性能なコードを生成するためには、これらの利用可能なレジスタをコンパイラが認識し、適切に割り当てられるようにする必要がありました。

この変更は、GoコンパイラのARMアーキテクチャ向けコード生成において、より多くのレジスタをレジスタ割り当て器が利用できるようにすることで、生成されるバイナリのパフォーマンスを向上させることを目的としています。特に、浮動小数点演算を多用するアプリケーションや、多くのローカル変数を持つ関数において、この変更は顕著な効果をもたらす可能性があります。

前提知識の解説

ARMアーキテクチャのレジスタ

ARMアーキテクチャは、RISC(Reduced Instruction Set Computer)ベースのプロセッサであり、その命令セットとレジスタセットは効率的な実行を重視して設計されています。

  • 汎用レジスタ (General-Purpose Registers - GPRs): ARMプロセッサには、通常R0からR15までの汎用レジスタがあります。

    • R0-R12: 通常のデータ操作やアドレス計算に使用されます。
    • R13 (SP): スタックポインタ。
    • R14 (LR): リンクレジスタ。関数呼び出しからの戻りアドレスを保持します。
    • R15 (PC): プログラムカウンタ。次に実行される命令のアドレスを保持します。
    • R9R10R11R12は、特定のABIやコンパイラの慣習によって、特別な用途(例: スクラッチレジスタ、フレームポインタ、スレッドローカルストレージポインタなど)に割り当てられることがあります。このコミットではR12の利用を有効にしています。
  • 浮動小数点レジスタ (Floating-Point Registers - FPRs): ARMプロセッサには、VFP(Vector Floating Point)拡張などの浮動小数点ユニット(FPU)が搭載されており、S0からS31(単精度)またはD0からD31(倍精度)といった浮動小数点レジスタが提供されます。

    • D0-D7: 関数呼び出しの引数や戻り値に使用されることが多いです。
    • D8-D15: 呼び出し元保存(caller-saved)レジスタとして使用されることが多く、関数内で自由に使用できますが、呼び出し元がその値を保持したい場合は、呼び出し元が保存・復元する必要があります。
    • D16-D31: 呼び出し先保存(callee-saved)レジスタとして使用されることが多く、関数内で使用する場合は、関数がその値を保存・復元する責任があります。 このコミットではF8からF15(おそらく倍精度レジスタD8からD15に対応)の利用を有効にしています。

Goコンパイラのバックエンド

Goコンパイラは、フロントエンド(Goソースコードの解析、AST生成、型チェックなど)とバックエンド(中間表現からターゲットアーキテクチャの機械語への変換、レジスタ割り当て、最適化など)に分かれています。

  • cmd/5c: ARMアーキテクチャ向けのGoコンパイラのC言語部分。レジスタの定義や、レジスタ割り当てに関する低レベルなロジックが含まれることがあります。
  • cmd/5g: ARMアーキテクチャ向けのGoコンパイラのGo言語部分。Go言語で書かれたコンパイラの主要なロジック、中間表現の処理、レジスタ割り当てアルゴリズムなどが含まれます。
  • cmd/5l: ARMアーキテクチャ向けのGoリンカ。コンパイルされたオブジェクトファイルを結合し、実行可能なバイナリを生成します。この過程で、レジスタの使用に関する情報(例: レジスタのビットマスク定義など)が必要となる場合があります。

レジスタ割り当て

レジスタ割り当ては、コンパイラの最適化フェーズの一つで、プログラムの変数や中間結果をCPUのレジスタに割り当てるプロセスです。レジスタはメモリよりもはるかに高速にアクセスできるため、レジスタを効率的に使用することはプログラムの実行速度に直結します。

  • レジスタの可用性: コンパイラが利用できるレジスタの数が多いほど、より多くの値をレジスタに保持でき、メモリへのアクセスを減らすことができます。
  • レジスタのビットマスク: コンパイラ内部では、どのレジスタが利用可能であるか、あるいはどのレジスタが現在使用中であるかを管理するために、ビットマスクがよく使用されます。各ビットが特定のレジスタに対応し、ビットがセットされていればそのレジスタが利用可能/使用中であることを示します。

技術的詳細

このコミットは、GoコンパイラのARMバックエンドにおけるレジスタ管理ロジックを修正し、R12レジスタとF8-F15浮動小数点レジスタをレジスタ割り当ての対象に含めるように変更しています。

具体的には、以下のファイルが変更されています。

  1. src/cmd/5c/reg.c:

    • RtoB関数(Register to Bitmask): 汎用レジスタ番号をビットマスクに変換する関数。R12が除外されないように条件が変更されています。以前はREGTMP-2(おそらくR9R10)より大きいレジスタは除外されていましたが、R12は除外対象から外されました。
    • BtoR関数(Bitmask to Register): ビットマスクからレジスタ番号を抽出する関数。R12に対応するビット(0x1000、つまり1L << 12)がマスクに追加され、R12が正しく認識されるようになりました。
    • FtoB関数(Floating-point Register to Bitmask): 浮動小数点レジスタ番号をビットマスクに変換する関数。F15まで(つまり31まで)を有効にするようにコメントとロジックが更新されています。
    • BtoF関数(Bitmask to Floating-point Register): ビットマスクから浮動小数点レジスタ番号を抽出する関数。浮動小数点レジスタのビットマスク範囲が0xfffc0000Lに拡張され、F8-F15に対応するビットが認識されるようになりました。
  2. src/cmd/5g/reg.c:

    • NREGVARマクロ: レジスタ変数の数を24から32に増やしています。これは、利用可能なレジスタの総数が増えたことを反映しています。
    • REGBITSマクロ: レジスタビットマスクの最大値を0xffffffから0xffffffff(32ビット全て)に拡張しています。これにより、より多くのレジスタをビットマスクで表現できるようになります。
    • regname配列: 浮動小数点レジスタの名称リストに.F8から.F15が追加されています。
    • RtoBBtoRFtoBBtoF関数: src/cmd/5c/reg.cと同様のロジック変更が適用されています。
  3. src/cmd/5l/5.out.h:

    • REGTMPマクロ: 11のままですが、REGSB(Static Base Register)の定義が削除されています。これは、R12REGSBとして予約されていた可能性があり、その予約を解除して汎用レジスタとして利用可能にしたことを示唆しています。
    • NFREGマクロ: 浮動小数点レジスタの数を8から16に増やしています。これはF0-F7に加えてF8-F15が利用可能になったことを直接的に示しています。
    • FREGTMPマクロ: 浮動小数点一時レジスタの最大番号が15に設定されています。

これらの変更により、Goコンパイラのレジスタ割り当て器は、R12F8-F15を通常の汎用レジスタおよび浮動小数点レジスタとして認識し、コード生成時にこれらを活用できるようになります。これにより、レジスタの競合が減少し、メモリへのスピル(レジスタからメモリへの退避)が削減され、結果として生成されるコードの実行効率が向上します。

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

src/cmd/5c/reg.c

--- a/src/cmd/5c/reg.c
+++ b/src/cmd/5c/reg.c
@@ -1150,12 +1150,13 @@ addreg(Adr *a, int rn)
  *	1	R1
  *	...	...
  *	10	R10
+ *	12  R12
  */
 int32
 RtoB(int r)
 {
 
-	if(r < 2 || r >= REGTMP-2)	// excluded R9 and R10 for m and g
+	if(r < 2 || (r >= REGTMP-2 && r != 12))	// excluded R9 and R10 for m and g, but not R12
 		return 0;
 	return 1L << r;
 }
@@ -1163,7 +1164,7 @@ RtoB(int r)
 int
 BtoR(int32 b)
 {
-	b &= 0x01fcL;	// excluded R9 and R10 for m and g
+	b &= 0x11fcL;	// excluded R9 and R10 for m and g, but not R12
 	if(b == 0)
 		return 0;
 	return bitno(b);
@@ -1174,7 +1175,7 @@ BtoR(int32 b)
  *	18	F2
  *	19	F3
  *	...	...
- *	23	F7
+ *	31	F15
  */
 int32
 FtoB(int f)
@@ -1189,7 +1190,7 @@ int
 BtoF(int32 b)
 {
 
-	b &= 0xfc0000L;
+	b &= 0xfffc0000L;
 	if(b == 0)
 		return 0;
 	return bitno(b) - 16;

src/cmd/5g/reg.c

--- a/src/cmd/5g/reg.c
+++ b/src/cmd/5g/reg.c
@@ -34,8 +34,8 @@
 #include "gg.h"
 #include "opt.h"
 
-#define	NREGVAR	24
-#define	REGBITS	((uint32)0xffffff)
+#define	NREGVAR	32
+#define	REGBITS	((uint32)0xffffffff)
 #define	P2R(p)\t(Reg*)(p->reg)
 
 	void	addsplits(void);
@@ -160,6 +160,14 @@ static char* regname[] = {
 	".F5",
 	".F6",
 	".F7",
+	".F8",
+	".F9",
+	".F10",
+	".F11",
+	".F12",
+	".F13",
+	".F14",
+	".F15",
 };
 
 void
@@ -1486,11 +1494,12 @@ addreg(Adr *a, int rn)
  *	1	R1
  *	...	...
  *	10	R10
+ *	12  R12
  */
 int32
 RtoB(int r)
 {
-\tif(r >= REGTMP-2)\t// excluded R9 and R10 for m and g
+\tif(r >= REGTMP-2 && r != 12)\t// excluded R9 and R10 for m and g, but not R12
 		return 0;
 	return 1L << r;
 }
@@ -1498,7 +1507,7 @@ RtoB(int r)
 int
 BtoR(int32 b)
 {
-\tb &= 0x01fcL;\t// excluded R9 and R10 for m and g
+\tb &= 0x11fcL;\t// excluded R9 and R10 for m and g, but not R12
 	if(b == 0)
 		return 0;
 	return bitno(b);
@@ -1509,7 +1518,7 @@ BtoR(int32 b)
  *	18	F2
  *	19	F3
  *	...	...
- *	23	F7
+ *	31	F15
  */
 int32
 FtoB(int f)
@@ -1524,7 +1533,7 @@ int
 BtoF(int32 b)
 {
 
-\tb &= 0xfc0000L;\n+\tb &= 0xfffc0000L;
+\tb &= 0xfffc0000L;
 	if(b == 0)
 		return 0;
 	return bitno(b) - 16;

src/cmd/5l/5.out.h

--- a/src/cmd/5l/5.out.h
+++ b/src/cmd/5l/5.out.h
@@ -49,12 +49,11 @@
 #define REGM        (REGEXT-1)
 /* compiler allocates external registers R10 down */
 #define	REGTMP		11
-#define	REGSB		12
 #define	REGSP		13
 #define	REGLINK		14
 #define	REGPC		15
 
-#define	NFREG		8
+#define	NFREG		16
 #define	FREGRET		0
 #define	FREGEXT		7
 #define	FREGTMP		15

コアとなるコードの解説

src/cmd/5c/reg.c および src/cmd/5g/reg.c の変更

これらのファイルは、GoコンパイラのARMバックエンドにおけるレジスタのビットマスク表現と、レジスタ番号とビットマスク間の変換ロジックを定義しています。

  • RtoB 関数 (R12 の有効化):
    • 変更前: if(r < 2 || r >= REGTMP-2)
      • これは、レジスタ番号r2未満(R0, R1)であるか、またはREGTMP-2(おそらくR9)以上である場合に0を返していました。つまり、R9R10が除外され、さらにR12もこの条件に引っかかって除外されていた可能性があります。
    • 変更後: if(r < 2 || (r >= REGTMP-2 && r != 12))
      • 新しい条件では、r >= REGTMP-2であってもr12R12)でない限りは除外するというロジックに変更されています。これにより、R12がレジスタ割り当ての対象として認識されるようになります。
  • BtoR 関数 (R12 のビットマスク認識):
    • 変更前: b &= 0x01fcL;
      • このビットマスクは、R2からR8、そしてR10に対応するビット(0x0004から0x01000x0200)のみを抽出していました。R12に対応するビット(0x1000)は含まれていませんでした。
    • 変更後: b &= 0x11fcL;
      • 新しいビットマスク0x11fcLは、0x01fcL0x1000L1L << 12、つまりR12に対応するビット)が追加されています。これにより、ビットマスクからR12が正しく抽出できるようになります。
  • FtoB 関数 (F8-F15 の有効化):
    • コメントの変更: * 23 F7 から * 31 F15 へ。これは、浮動小数点レジスタの範囲がF7までではなくF15まで拡張されたことを示しています。
  • BtoF 関数 (F8-F15 のビットマスク認識):
    • 変更前: b &= 0xfc0000L;
      • このビットマスクは、F2からF7に対応するビット(0x040000から0x800000)のみを抽出していました。
    • 変更後: b &= 0xfffc0000L;
      • 新しいビットマスク0xfffc0000Lは、F8からF15に対応するビット(0x1000000から0x80000000)を含むように拡張されています。これにより、ビットマスクからF8-F15が正しく抽出できるようになります。
  • src/cmd/5g/reg.c のマクロ変更:
    • NREGVAR24から32へ: これは、コンパイラが管理するレジスタ変数の総数が増加したことを示します。
    • REGBITS0xffffffから0xffffffffへ: これは、レジスタのビットマスクが24ビットから32ビットに拡張され、より多くのレジスタを表現できるようになったことを示します。
    • regname配列に".F8"から".F15"を追加: これは、これらの浮動小数点レジスタがコンパイラによって認識され、デバッグ情報やエラーメッセージなどで正しく表示されるようにするための変更です。

src/cmd/5l/5.out.h の変更

このファイルは、Goリンカが使用するARMアーキテクチャ固有の定数を定義しています。

  • REGSB の削除:
    • #define REGSB 12 の行が削除されています。これは、以前はR12REGSB(Static Base Register)として予約されており、汎用レジスタとしては利用できなかったことを示唆しています。この定義を削除することで、R12が汎用レジスタとして解放され、コンパイラが利用できるようになります。
  • NFREG の変更:
    • #define NFREG 8 から #define NFREG 16 へ。これは、利用可能な浮動小数点レジスタの総数が8個(F0-F7)から16個(F0-F15)に倍増したことを明確に示しています。

これらの変更は、GoコンパイラがARMアーキテクチャ上でより多くのレジスタを効果的に利用できるようにするための基盤を構築しています。これにより、レジスタ割り当ての効率が向上し、結果として生成されるGoプログラムの実行速度が改善されることが期待されます。

関連リンク

参考にした情報源リンク

  • ARM Architecture Reference Manual (ARM社の公式ドキュメント)
  • Go Compiler Internals (Goコンパイラの内部構造に関するドキュメントやブログ記事)
  • Register Allocation in Compilers (コンパイラのレジスタ割り当てに関する一般的な情報)
  • Go言語のARMポートに関する議論やメーリングリストのアーカイブ (golang-devなど)