[インデックス 16533] ファイルの概要
このコミットは、Go言語のARMアーキテクチャ向けリンカである cmd/5l における BL (Rx) 命令の実装を改善するものです。具体的には、間接分岐命令 BL (Rx) を、従来の MOV LR, PC; MOV PC, Rx という2命令のシーケンスから、単一の BLX Rx 命令に置き換えることで、より効率的かつ正確な分岐処理を実現しています。
コミット
commit 35e1deaebf1b765f20e1692581804a3bf49d95fa
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Tue Jun 11 03:04:24 2013 +0800
cmd/5l: use BLX for BL (Rx).
Fixes #5111.
Update #4718
This CL makes BL (Rx) to use BLX Rx instead of:
MOV LR, PC
MOV PC, Rx
R=cshapiro, rsc
CC=dave, gobot, golang-dev
https://golang.org/cl/9669045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/35e1deaebf1b765f20e1692581804a3bf49d95fa
元コミット内容
cmd/5l: use BLX for BL (Rx).
Fixes #5111.
Update #4718
This CL makes BL (Rx) to use BLX Rx instead of:
MOV LR, PC
MOV PC, Rx
変更の背景
この変更の背景には、ARMアーキテクチャにおける関数呼び出しの効率性と正確性の向上が挙げられます。
従来の BL (Rx) (Branch with Link to Register) の実装では、MOV LR, PC と MOV PC, Rx という2つの命令シーケンスを使用していました。
MOV LR, PC: 現在のプログラムカウンタ (PC) の値をリンクレジスタ (LR) に保存します。これは関数からの戻りアドレスを記録するために行われます。MOV PC, Rx: レジスタRxに格納されているアドレスにプログラムカウンタを移動させ、間接分岐を実行します。
この2命令シーケンスにはいくつかの問題がありました。
- 効率性: 2つの命令を実行するため、単一の命令で同じ機能を実現できる場合に比べてオーバーヘッドがありました。
- ARM/Thumb状態遷移: ARMプロセッサはARM状態(32ビット命令)とThumb状態(16ビット命令)の間で切り替えることができます。
MOV PC, Rxのような直接的なPCへの書き込みは、Rxの最下位ビットが0の場合にARM状態への遷移、1の場合にThumb状態への遷移を引き起こします。しかし、この遷移は明示的に制御されるべきであり、意図しない状態遷移はバグの原因となる可能性がありました。特に、関数ポインタが指すコードがThumb命令セットで書かれている場合、MOV PC, Rxでは正しくThumb状態に遷移できない可能性がありました。 - パイプラインのフラッシュ: PCへの直接書き込みは、プロセッサの命令パイプラインをフラッシュする可能性があり、パフォーマンスに悪影響を与えることがありました。
これらの問題を解決するため、ARMv5T以降で導入された BLX (Branch with Link and Exchange) 命令を使用することが望ましいとされました。BLX 命令は、リンクレジスタに現在のPCを保存し、指定されたレジスタの値に分岐すると同時に、そのレジスタの最下位ビットに基づいてARM/Thumb状態を自動的に切り替える機能を持っています。これにより、単一命令で効率的かつ安全な間接関数呼び出しが可能になります。
コミットメッセージにある Fixes #5111 と Update #4718 は、これらの問題に関連するGo言語のバグトラッカーのイシューを示唆しています。これらのイシューは、ARMアーキテクチャでのGoプログラムの安定性やパフォーマンスに関する問題提起であったと考えられます。
前提知識の解説
このコミットを理解するためには、以下の前提知識が必要です。
-
ARMアーキテクチャ:
- レジスタ: ARMプロセッサには汎用レジスタ (R0-R12), スタックポインタ (SP, R13), リンクレジスタ (LR, R14), プログラムカウンタ (PC, R15) などがあります。
- PC (Program Counter): 次に実行される命令のアドレスを保持します。
- LR (Link Register): 関数呼び出し時に戻りアドレスを保存するために使用されます。
- ARM状態とThumb状態: ARMプロセッサは、32ビットのARM命令セットと、よりコード密度が高い16ビットのThumb命令セット(およびThumb-2)をサポートしています。これらの状態間の切り替えは、特定の命令やPCへの書き込みによって行われます。
- 命令セット:
MOV(Move): レジスタ間でデータを移動させる基本的な命令。MOV PC, RxはRxの値をPCにコピーし、分岐として機能します。BL(Branch with Link): 関数呼び出しに使用される命令。現在のPCの値をLRに保存し、指定されたアドレスに分岐します。通常は直接アドレスを指定します。BLX(Branch with Link and Exchange): ARMv5T以降で導入された命令。BLと同様にLRにPCを保存しますが、分岐先アドレスはレジスタで指定し、そのレジスタの最下位ビットに基づいてARM/Thumb状態を自動的に切り替えます。これにより、異なる命令セットで書かれたサブルーチンへの安全な呼び出しが可能になります。
-
Go言語のツールチェイン:
cmd/5l: Go言語のARMアーキテクチャ向けリンカです。Goのソースコードがコンパイルされた後、最終的な実行可能ファイルを生成する際に、オブジェクトファイルをリンクし、アドレス解決や命令の生成を行います。このリンカは、Goのコンパイラが生成する中間表現(Plan 9アセンブラ形式)を解釈し、ターゲットアーキテクチャのネイティブな機械語に変換する役割も担います。- Plan 9アセンブラ: Go言語のコンパイラは、Goのコードを直接機械語に変換するのではなく、Plan 9アセンブラという独自のアセンブラ形式に変換します。このアセンブラは、一般的なアセンブラとは異なる記法を持ち、Goのリンカがこれを解釈して最終的なバイナリを生成します。
-
リンカの役割:
- リンカは、コンパイラが生成した複数のオブジェクトファイル(機械語コードとデータ)を結合し、単一の実行可能ファイルやライブラリを生成します。
- この過程で、シンボル(関数名や変数名)の参照を解決し、適切なメモリアドレスにマッピングします。
- また、ターゲットアーキテクチャの命令セットに合わせて、アセンブラ命令を実際の機械語コードに変換する役割も持ちます。
技術的詳細
このコミットの技術的な核心は、cmd/5l リンカが ABL (GoのPlan 9アセンブラにおける BL 命令の内部表現) を処理する際に、レジスタ間接分岐の場合に BLX Rx 命令を生成するように変更した点です。
変更前は、ABL 命令がレジスタ Rx をターゲットとする場合(例: BL (R1))、リンカは以下のような2命令シーケンスを生成していました。
MOV LR, PC ; 現在のPCをLRに保存
MOV PC, Rx ; Rxの値をPCにコピーして分岐
このシーケンスは、ARMv4T以前のアーキテクチャでは一般的な間接分岐の方法でしたが、前述の通り、効率性やARM/Thumb状態遷移の扱いに課題がありました。
変更後は、同じ ABL (Rx) に対して、リンカは単一の BLX Rx 命令を生成します。
BLX Rx ; Rxの値をPCにコピーし、LRに現在のPCを保存、Rxの最下位ビットに応じて状態遷移
この変更により、以下の利点が得られます。
- 命令数の削減: 2命令から1命令になり、コードサイズが削減され、命令フェッチの効率が向上します。
- パフォーマンス向上: 単一命令で実行されるため、命令デコードや実行のオーバーヘッドが減少し、パイプラインのフラッシュも最小限に抑えられます。
- 正確な状態遷移:
BLX命令は、分岐先アドレスの最下位ビットを自動的に解釈し、必要に応じてARM状態とThumb状態を切り替えます。これにより、異なる命令セットで書かれたコードへの呼び出しが安全かつ正確に行えるようになります。これは、特にGoがC言語のライブラリ(Thumbコードを含む可能性がある)と連携する場合に重要です。 - ARMv5T以降の機能活用: 現代のARMプロセッサが持つより高度な機能を活用することで、生成されるコードの品質と効率が向上します。
この変更は、src/cmd/5l/asm.c と src/cmd/5l/optab.c の2つのファイルに影響を与えています。
src/cmd/5l/asm.c: このファイルは、Plan 9アセンブラの命令をARMの機械語に変換する主要なロジックを含んでいます。case 7の処理がbl ,O(R)からbl (R)に変更され、instoffsetが0でない場合の診断メッセージが追加されています。これは、BLX Rxがオフセットをサポートしないためです。また、oprrr関数にABLの新しいケースが追加され、BLX REG命令のエンコーディングが定義されています。src/cmd/5l/optab.c: このファイルは、リンカが認識する命令のオペランドタイプと、それに対応する命令の長さや処理方法を定義するテーブルを含んでいます。ABL命令のC_ROREG(レジスタオペランド) のエントリで、命令の長さが8バイトから4バイトに削減されています。これは、2命令シーケンスが1命令に置き換わったことを反映しています。
コアとなるコードの変更箇所
src/cmd/5l/asm.c
--- a/src/cmd/5l/asm.c
+++ b/src/cmd/5l/asm.c
@@ -858,17 +858,12 @@ if(debug['G']) print("%ux: %s: arm %d\n", (uint32)(p->pc), p->from.sym->name, p-
o1 |= REGPC << 12;
break;
- case 7: /* bl ,O(R) -> mov PC,link; add $O,R,PC */
+ case 7: /* bl (R) -> blx R */
aclass(&p->to);
- o1 = oprrr(AADD, p->scond);
- o1 |= immrot(0);
- o1 |= REGPC << 16;
- o1 |= REGLINK << 12;
-
- o2 = oprrr(AADD, p->scond);
- o2 |= immrot(instoffset);
- o2 |= p->to.reg << 16;
- o2 |= REGPC << 12;
+ if(instoffset != 0)
+ diag("%P: doesn't support BL offset(REG) where offset != 0", p);
+ o1 = oprrr(ABL, p->scond);
+ o1 |= p->to.reg;
break;
case 8: /* sll $c,[R],R -> mov (R<<$c),R */
@@ -1709,6 +1704,9 @@ oprrr(int a, int sc)
return (o & (0xf<<28)) | (0x12 << 20) | (0xc<<4);
case AMULAWB:
return (o & (0xf<<28)) | (0x12 << 20) | (0x8<<4);
+
+ case ABL: // BLX REG
+ return (o & (0xf<<28)) | (0x12fff3 << 4);
}
diag("bad rrr %d", a);
prasm(curp);
src/cmd/5l/optab.c
--- a/src/cmd/5l/optab.c
+++ b/src/cmd/5l/optab.c
@@ -62,8 +62,8 @@ Optab optab[] =
{ ABEQ, C_NONE, C_NONE, C_SBRA, 5, 4, 0 },
{ AB, C_NONE, C_NONE, C_ROREG, 6, 4, 0, LPOOL },
- { ABL, C_NONE, C_NONE, C_ROREG, 7, 8, 0 },
- { ABL, C_REG, C_NONE, C_ROREG, 7, 8, 0 },
+ { ABL, C_NONE, C_NONE, C_ROREG, 7, 4, 0 },
+ { ABL, C_REG, C_NONE, C_ROREG, 7, 4, 0 },
{ ABX, C_NONE, C_NONE, C_ROREG, 75, 12, 0 },
{ ABXRET, C_NONE, C_NONE, C_ROREG, 76, 4, 0 },
コアとなるコードの解説
src/cmd/5l/asm.c の変更
-
case 7の変更:- この
caseは、Plan 9アセンブラのABL命令(Goの内部表現ではABL)がレジスタ間接分岐を行う場合の処理を担当しています。 - 変更前は、コメント
/* bl ,O(R) -> mov PC,link; add $O,R,PC */が示すように、MOV LR, PCとADD(またはMOV PC, Rx) の2命令を生成していました。instoffsetはオフセット値を示し、p->to.regはターゲットレジスタを示します。 - 変更後は、コメントが
/* bl (R) -> blx R */となり、BLX Rx命令を生成する意図が明確になっています。 if(instoffset != 0)のチェックが追加され、オフセット付きのレジスタ間接分岐 (BL offset(REG)) はサポートされない旨の診断メッセージ (diag) が出力されるようになりました。これはBLX命令がオフセットを直接サポートしないためです。o1 = oprrr(ABL, p->scond);とo1 |= p->to.reg;により、ABLオペレーションコードとターゲットレジスタp->to.regを組み合わせて、BLX命令のエンコーディングに必要な情報がo1に設定されます。
- この
-
oprrr関数のABLケースの追加:oprrr関数は、特定のARM命令のオペランドと条件コードに基づいて、命令の機械語エンコーディングの一部を生成するヘルパー関数です。- 新しく
case ABL: // BLX REGが追加されています。 return (o & (0xf<<28)) | (0x12fff3 << 4);は、BLX Rx命令の特定のビットパターンを生成しています。(o & (0xf<<28))は、命令の条件コード部分を保持します。(0x12fff3 << 4)は、BLX命令のオペコードと特定のフラグ(レジスタ間接分岐を示すビットなど)を表す固定ビットパターンです。このパターンは、ARMアーキテクチャリファレンスマニュアルに記載されているBLX (register)命令のエンコーディングに対応しています。具体的には、0x12FFF3は000100101111111111110011であり、これはBLX命令の特定の形式(レジスタ間接、リンク付き、状態交換)を示します。
src/cmd/5l/optab.c の変更
ABL命令の長さの変更:optab配列は、リンカが処理する各命令の特性を定義します。{ ABL, C_NONE, C_NONE, C_ROREG, 7, 8, 0 }と{ ABL, C_REG, C_NONE, C_ROREG, 7, 8, 0 }の2つのエントリは、ABL命令がレジスタオペランド (C_ROREG) を持つ場合の定義です。7はasm.cのcase 7に対応する命令タイプです。8は命令のバイト長を示していました。これは、MOV LR, PC(4バイト) とMOV PC, Rx(4バイト) の合計8バイトを反映していました。- この
8が4に変更されています。これは、BLX Rxが単一の4バイト命令であるため、命令の長さが半分になったことを示しています。
これらの変更により、GoのARMリンカは、レジスタを介した関数呼び出しにおいて、より効率的で現代的な BLX 命令を生成するようになり、Goプログラムのパフォーマンスと互換性が向上しました。
関連リンク
- Go Issue #5111: https://github.com/golang/go/issues/5111 (このコミットによって修正されたイシュー)
- Go Issue #4718: https://github.com/golang/go/issues/4718 (このコミットによって更新されたイシュー)
- Go CL 9669045: https://golang.org/cl/9669045 (このコミットに対応するGoのコードレビュー変更リスト)
参考にした情報源リンク
- ARM Architecture Reference Manual (ARMv5T以降のBLX命令の詳細)
- Go言語のソースコード (
src/cmd/5l/) - Go言語のバグトラッカー (GitHub Issues)
- Plan 9 from Bell Labs: https://9p.io/plan9/ (Plan 9アセンブラの背景知識)
- Go Assembly Language (Plan 9アセンブラの記法): https://go.dev/doc/asm
- ARM Instruction Set Overview (BL, BLX命令の比較): https://developer.arm.com/documentation/ddi0406/c/ (具体的なURLはARMのドキュメントサイトで検索して見つける必要があります)
- Stack Overflowなどの技術Q&Aサイト (ARMアセンブリ、BLX命令に関する一般的な情報)
- Go言語のメーリングリストやフォーラム (過去の議論や背景情報)