[インデックス 13253] ファイルの概要
このコミットは、Go言語のランタイムにおけるARMアーキテクチャ向けのアセンブリコードファイル src/pkg/runtime/vlop_arm.s に関連する変更です。具体的には、不要なマクロ定義を削除し、対応するARMアセンブリ命令を直接使用するように修正しています。
コミット
commit e8265f18e6b712aefdaf57f872e6d19a4b170fdc
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Sun Jun 3 04:03:09 2012 +0800
runtime: remove unnecessary macros in vlop_arm.s
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/6270045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e8265f18e6b712aefdaf57f872e6d19a4b170fdc
元コミット内容
runtime: remove unnecessary macros in vlop_arm.s
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/6270045
変更の背景
このコミットの背景には、Go言語のARMアーキテクチャ向けランタイムコードのクリーンアップと最適化があります。vlop_arm.s は、Goランタイムが内部的に使用する特定の演算(この場合は乗算)をARMプロセッサ上で効率的に実行するためのアセンブリコードを含んでいます。
以前のコードでは、UMULL, UMLAL, MUL といったARMアセンブリ命令をラップする形で独自のマクロが定義されていました。これらのマクロは、アセンブリコードの記述を簡略化するために導入された可能性があります。しかし、Goのアセンブラ(go tool asm)の進化や、ARMアセンブリ命令の直接的なサポートが改善されたことにより、これらのマクロが冗長または不要になったと考えられます。
不要なマクロを削除し、ネイティブなアセンブリ命令を直接使用することで、以下のようなメリットが期待されます。
- コードの可読性向上: Goのアセンブラに慣れている開発者にとって、独自のマクロよりも標準的なARM命令の方が理解しやすい場合があります。
- メンテナンス性の向上: マクロの定義と使用箇所を別々に管理する必要がなくなり、コードベースがシンプルになります。
- コンパイラ/アセンブラの最適化の恩恵: アセンブラが直接命令を処理することで、より効率的なコード生成や最適化の機会が増える可能性があります。
- 冗長性の排除: 同じ機能を提供するマクロとネイティブ命令が混在する状況を解消し、一貫性のある記述を促進します。
この変更は、GoランタイムのARMサポートが成熟し、より洗練されたアセンブリコードの記述が可能になったことを示唆しています。
前提知識の解説
このコミットを理解するためには、以下の知識が役立ちます。
1. Go言語のランタイム (Runtime)
Go言語のランタイムは、ガベージコレクション、スケジューリング、メモリ管理、システムコールインターフェースなど、Goプログラムの実行をサポートする低レベルのコンポーネント群です。Goプログラムは、OSの機能に直接アクセスするのではなく、ランタイムを介してこれらの機能を利用します。パフォーマンスが重要な部分や、OSとの直接的なインタラクションが必要な部分では、C言語やアセンブリ言語で記述されることがあります。
2. ARMアーキテクチャ
ARM (Advanced RISC Machine) は、モバイルデバイス、組み込みシステム、サーバーなど、幅広い分野で利用されているRISC (Reduced Instruction Set Computer) ベースのプロセッサアーキテクチャです。Go言語は、ARMを含む複数のアーキテクチャをサポートしており、それぞれのアーキテクチャに特化したランタイムコードを持っています。
3. アセンブリ言語とGoのアセンブラ
アセンブリ言語は、CPUが直接実行できる機械語にほぼ1対1で対応する低レベルのプログラミング言語です。Go言語では、go tool asm という独自のアセンブラを使用します。これは、AT&T構文やIntel構文とは異なる、Go独自のアセンブリ構文(Plan 9アセンブラ構文に似ている)を採用しています。Goのアセンブリコードは通常 .s 拡張子を持ちます。
4. アセンブリ言語におけるマクロ
アセンブリ言語におけるマクロは、一連のアセンブリ命令に名前を付け、再利用可能にするための機能です。マクロを使用することで、繰り返し現れるコードパターンを抽象化し、コードの記述量を減らし、可読性を向上させることができます。しかし、マクロが複雑になると、かえってコードの追跡が難しくなることもあります。
5. ARMの乗算命令
このコミットで言及されているARMの乗算命令は以下の通りです。
MUL(Multiply): 32ビットのレジスタ値を乗算し、結果の下位32ビットを別のレジスタに格納します。- 構文例:
MUL Rd, Rm, Rs(Rd = Rm * Rs)
- 構文例:
UMULL(Unsigned Multiply Long): 符号なし32ビットのレジスタ値を乗算し、結果の64ビットを2つのレジスタ(上位32ビットと下位32ビット)に格納します。- 構文例:
UMULL Rlo, Rhi, Rm, Rs(Rhi:Rlo = Rm * Rs)
- 構文例:
UMLAL(Unsigned Multiply Accumulate Long): 符号なし32ビットのレジスタ値を乗算し、その結果を既存の64ビット値(2つのレジスタに格納されている)に加算し、結果の64ビットを2つのレジスタに格納します。- 構文例:
UMLAL Rlo, Rhi, Rm, Rs(Rhi:Rlo = (Rm * Rs) + Rhi:Rlo)
- 構文例:
MULLU(Multiply Unsigned Long): これはGoのアセンブラが提供する擬似命令、または特定のARMアセンブラの拡張構文である可能性が高いです。標準的なARM命令セットにはMULLUという直接の命令は存在しませんが、UMULLと同等の機能を提供するためにGoのアセンブラが内部的に変換するか、より簡潔な記述を可能にするためのものです。Goのアセンブラでは、UMULLのような命令をMULLUのようなより直感的な形で記述できる場合があります。このコミットでは、UMULLマクロがMULLU命令に置き換えられていることから、GoのアセンブラがMULLUをネイティブにサポートしていることが示唆されます。
技術的詳細
このコミットの技術的詳細を掘り下げると、Goのアセンブラにおけるマクロの扱いと、ARMアセンブリ命令の直接利用への移行が見えてきます。
1. 削除されたマクロの定義
コミットの差分を見ると、以下のマクロ定義が削除されています。
-#define UMULL(Rs,Rm,Rhi,Rlo,S) WORD $((14<<28)|(4<<21)|(S<<20)|(Rhi<<16)|(Rlo<<12)|(Rs<<8)|(9<<4)|Rm)
-#define UMLAL(Rs,Rm,Rhi,Rlo,S) WORD $((14<<28)|(5<<21)|(S<<20)|(Rhi<<16)|(Rlo<<12)|(Rs<<8)|(9<<4)|Rm)
-#define MUL(Rs,Rm,Rd,S) WORD $((14<<28)|(0<<21)|(S<<20)|(Rd<<16)|(Rs<<8)|(9<<4)|Rm)
これらの行は、C言語のプリプロセッサのような #define ディレクティブを使って、ARM命令のバイナリエンコーディングを直接生成するマクロを定義していました。
WORDは、Goのアセンブラにおけるデータ定義ディレクティブで、32ビットワードを定義します。$((...))の部分は、ARM命令のオペコードとオペランドをビットシフトとOR演算で組み合わせた32ビットのバイナリ値を計算しています。- 例えば、
UMULLマクロの(14<<28)は条件コード(常に実行)を示し、(4<<21)は特定の命令タイプ(乗算)を示し、残りのビットはレジスタ番号やSビット(状態フラグ更新)などをエンコードしています。
- 例えば、
このようなマクロは、Goのアセンブラが特定のARM命令を直接サポートしていなかった時期や、より低レベルで命令を制御したい場合に用いられた可能性があります。しかし、命令のバイナリエンコーディングを直接扱うため、非常に低レベルで、可読性が低く、エラーを起こしやすいという欠点があります。
2. マクロから直接命令への置き換え
削除されたマクロの代わりに、以下の命令が直接使用されています。
UMULL(4, 2, 7, 6, 0)がMULLU R4, R2, (R7,R6)に置き換えられました。UMULLマクロは、レジスタR4とR2の符号なし乗算結果をR7(上位32ビット) とR6(下位32ビット) に格納する操作を行っていました。MULLU R4, R2, (R7,R6)は、GoのアセンブラにおけるUMULL命令のより現代的で可読性の高い構文です。R4とR2を乗算し、結果をR7とR6のペアに格納することを示しています。これは、GoのアセンブラがUMULL命令を直接、かつより直感的な構文でサポートするようになったことを意味します。
MUL(11, 4, 8, 0)がMUL R11, R4, R8に置き換えられました。MULマクロは、レジスタR11とR4の乗算結果をR8に格納する操作を行っていました。MUL R11, R4, R8は、Goのアセンブラにおける標準的なMUL命令の構文です。R8 = R11 * R4を意味します。
MUL(2, 5, 8, 0)がMUL R2, R5, R8に置き換えられました。- 同様に、
R8 = R2 * R5を意味します。
- 同様に、
この変更は、Goのアセンブラが進化し、ARM命令を直接、かつより抽象度の高い(しかし依然としてアセンブリレベルの)構文で記述できるようになったことを明確に示しています。これにより、開発者は命令のバイナリエンコーディングを意識することなく、命令の機能に集中してコードを記述できるようになります。
3. R10 から R11 へのレジスタ変更
コミットメッセージには「replaced use of R10 by R11 because the former can be the data segment base register」というコメントがあります。これは、R10 レジスタがデータセグメントのベースレジスタとして使用される可能性があるため、衝突を避けるために R11 に変更したことを示しています。これは、アセンブリプログラミングにおける一般的なプラクティスであり、特定のレジスタがシステムやコンパイラによって予約されている場合に、そのレジスタの使用を避けるためのものです。この変更は、マクロの削除とは直接関係ありませんが、同じコミットでコードの健全性を向上させるために行われた追加の修正です。
コアとなるコードの変更箇所
変更は src/pkg/runtime/vlop_arm.s ファイルに集中しています。
--- a/src/pkg/runtime/vlop_arm.s
+++ b/src/pkg/runtime/vlop_arm.s
@@ -23,9 +23,6 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
-#define UMULL(Rs,Rm,Rhi,Rlo,S) WORD $((14<<28)|(4<<21)|(S<<20)|(Rhi<<16)|(Rlo<<12)|(Rs<<8)|(9<<4)|Rm)
-#define UMLAL(Rs,Rm,Rhi,Rlo,S) WORD $((14<<28)|(5<<21)|(S<<20)|(Rhi<<16)|(Rlo<<12)|(Rs<<8)|(9<<4)|Rm)
-#define MUL(Rs,Rm,Rd,S) WORD $((14<<28)|(0<<21)|(S<<20)|(Rd<<16)|(Rs<<8)|(9<<4)|Rm)
arg=0
/* replaced use of R10 by R11 because the former can be the data segment base register */
@@ -36,10 +33,10 @@ TEXT _mulv(SB), $0
MOVW 8(FP), R11 /* h0 */
MOVW 12(FP), R4 /* l1 */
MOVW 16(FP), R5 /* h1 */
-\tUMULL(4, 2, 7, 6, 0)\n+\tMULLU\tR4, R2, (R7,R6)
-\tMUL(11, 4, 8, 0)\n+\tMUL\tR11, R4, R8
ADD\tR8, R7
-\tMUL(2, 5, 8, 0)\n+\tMUL\tR2, R5, R8
ADD\tR8, R7
MOVW\tR6, 0(R(arg))\n MOVW\tR7, 4(R(arg))\n```
## コアとなるコードの解説
このコミットの主要な変更は、`_mulv` という関数(おそらくベクター乗算に関連する関数)内で使用されていた独自のマクロ呼び出しを、Goのアセンブラが直接サポートするARM命令の構文に置き換えた点です。
1. **マクロ定義の削除**:
```diff
-#define UMULL(Rs,Rm,Rhi,Rlo,S) WORD $((14<<28)|(4<<21)|(S<<20)|(Rhi<<16)|(Rlo<<12)|(Rs<<8)|(9<<4)|Rm)
-#define UMLAL(Rs,Rm,Rhi,Rlo,S) WORD $((14<<28)|(5<<21)|(S<<20)|(Rhi<<16)|(Rlo<<12)|(Rs<<8)|(9<<4)|Rm)
-#define MUL(Rs,Rm,Rd,S) WORD $((14<<28)|(0<<21)|(S<<20)|(Rd<<16)|(Rs<<8)|(9<<4)|Rm)
```
これらの行は、ARM命令のバイナリエンコーディングを直接生成するためのマクロ定義でした。これらが削除されたことで、Goのアセンブラがこれらの命令をネイティブに解釈できるようになったことが示唆されます。
2. **`UMULL` マクロから `MULLU` 命令への変更**:
```diff
-\tUMULL(4, 2, 7, 6, 0)\n+\tMULLU\tR4, R2, (R7,R6)
```
* **変更前**: `UMULL(4, 2, 7, 6, 0)` は、マクロ `UMULL` を呼び出し、レジスタ `R4` と `R2` の符号なし乗算を行い、結果の上位32ビットを `R7` に、下位32ビットを `R6` に格納していました。最後の `0` はSビット(状態フラグ更新)がオフであることを示します。
* **変更後**: `MULLU R4, R2, (R7,R6)` は、Goのアセンブラが提供する `UMULL` 命令のより簡潔な構文です。`R4` と `R2` を乗算し、結果の64ビットをレジスタペア `(R7,R6)` に格納します。これは機能的には変更前と同じですが、より標準的で可読性の高い記述になっています。
3. **`MUL` マクロから `MUL` 命令への変更 (1回目)**:
```diff
-\tMUL(11, 4, 8, 0)\n+\tMUL\tR11, R4, R8
```
* **変更前**: `MUL(11, 4, 8, 0)` は、マクロ `MUL` を呼び出し、レジスタ `R11` と `R4` の乗算を行い、結果を `R8` に格納していました。
* **変更後**: `MUL R11, R4, R8` は、Goのアセンブラにおける標準的な `MUL` 命令の構文です。`R8 = R11 * R4` を意味します。
4. **`MUL` マクロから `MUL` 命令への変更 (2回目)**:
```diff
-\tMUL(2, 5, 8, 0)\n+\tMUL\tR2, R5, R8
```
* **変更前**: `MUL(2, 5, 8, 0)` は、マクロ `MUL` を呼び出し、レジスタ `R2` と `R5` の乗算を行い、結果を `R8` に格納していました。
* **変更後**: `MUL R2, R5, R8` は、Goのアセンブラにおける標準的な `MUL` 命令の構文です。`R8 = R2 * R5` を意味します。
これらの変更により、`vlop_arm.s` ファイルは、Goのアセンブラが提供するネイティブなARM命令構文を使用するようになり、コードの可読性とメンテナンス性が向上しました。また、命令のバイナリエンコーディングを直接扱う複雑さが排除されています。
## 関連リンク
* Go言語公式ドキュメント: [https://go.dev/](https://go.dev/)
* Goのアセンブリ言語に関するドキュメント (Go 1.20): [https://go.dev/doc/asm](https://go.dev/doc/asm) (当時のバージョンとは異なる可能性がありますが、基本的な構文は参考になります)
* ARMアーキテクチャリファレンスマニュアル (特定の命令セットの詳細): [https://developer.arm.com/documentation/](https://developer.arm.com/documentation/)
## 参考にした情報源リンク
* Go言語のコミット履歴と関連するコードレビュー (Go CL 6270045): [https://golang.org/cl/6270045](https://golang.org/cl/6270045)
* ARMアーキテクチャの命令セットに関する一般的な情報 (例: Wikipedia, ARM公式ドキュメント)
* Goのアセンブラ構文に関する情報 (Goのソースコードや関連するブログ記事など)