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

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

このコミットは、Go言語のARMアーキテクチャ向けコンパイラ (cmd/5c)、アセンブラ (cmd/5a)、およびリンカ (cmd/5l) に、PREFETCH (データプリフェッチ) 命令のサポートを追加するものです。これにより、Goプログラム内でデータプリフェッチを明示的に利用できるようになり、特定のワークロードにおけるパフォーマンス向上が期待されます。

コミット

commit 278d4a583d2e73b04a5460cf269d262adad8f87a
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Fri May 4 03:24:14 2012 +0800

    cmd/5c, cmd/5a, cmd/5l: ARM support for PREFETCH built-in
    
    R=rsc
    CC=golang-dev
    https://golang.org/cl/6174049

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

https://github.com/golang/go/commit/278d4a583d2e73b04a5460cf269d262adad8f87a

元コミット内容

cmd/5c, cmd/5a, cmd/5l: ARMアーキテクチャにおける PREFETCH 組み込み命令のサポート。

変更の背景

この変更の背景には、ARMプロセッサにおけるメモリアクセスの最適化があります。PREFETCH 命令は、CPUが将来必要とするであろうデータを、実際に必要となる前にキャッシュに読み込んでおくようプロセッサにヒントを与えるものです。これにより、データが実際に使用される際にキャッシュミスが発生する可能性を減らし、メモリレイテンシを隠蔽することでプログラムの実行速度を向上させることができます。

Go言語のコンパイラ、アセンブラ、リンカがPREFETCH命令をサポートすることで、開発者はパフォーマンスが重要な部分でこの最適化を明示的に利用できるようになります。特に、大量のデータを扱うアプリケーションや、メモリバウンドな処理において、この機能は大きな効果を発揮する可能性があります。

前提知識の解説

データプリフェッチ (Data Prefetching)

データプリフェッチは、プロセッサがプログラムの実行中に必要となるであろうデータを、予測に基づいてメインメモリからキャッシュメモリに事前に読み込んでおく技術です。これにより、CPUが実際にそのデータにアクセスしようとしたときに、データがすでに高速なキャッシュに存在するため、メモリからの読み込み待ち時間を短縮できます。

プリフェッチは、ハードウェアによる自動プリフェッチと、ソフトウェアによる明示的なプリフェッチの2種類があります。このコミットで追加されるのは、後者のソフトウェアによる明示的なプリフェッチ、具体的にはARMアーキテクチャのPLD (Preload Data) 命令のサポートです。

ARMアーキテクチャ

ARM (Advanced RISC Machine) は、モバイルデバイス、組み込みシステム、サーバーなど、幅広い分野で利用されているRISC (Reduced Instruction Set Computer) ベースのプロセッサアーキテクチャです。低消費電力と高い性能効率が特徴で、特にスマートフォンやタブレットのCPUとして広く採用されています。

ARMプロセッサには、データプリフェッチのためのPLD (Preload Data) 命令が用意されています。この命令は、指定されたアドレスのデータをキャッシュに読み込むようプロセッサに指示します。

Go言語のツールチェイン (cmd/5a, cmd/5c, cmd/5l)

Go言語のコンパイラは、特定のアーキテクチャ向けに設計されており、それぞれ異なるコマンド名を持っています。

  • cmd/5a (ARMアセンブラ): Goのアセンブリ言語をARMアー64ビットアーキテクチャの機械語に変換します。このコミットでは、PLD命令のアセンブリ構文の解析と処理が変更されています。
  • cmd/5c (ARMコンパイラ): GoのソースコードをARMアーキテクチャ向けのオブジェクトコードにコンパイルします。このコミットでは、PREFETCH組み込み関数の呼び出しを適切なPLD命令に変換するロジックが追加されています。
  • cmd/5l (ARMリンカ): オブジェクトファイルを結合し、実行可能なバイナリを生成します。このコミットでは、PLD命令の機械語エンコーディングに関する変更が含まれています。

これらのツールは、GoプログラムがARMプロセッサ上で効率的に動作するために不可欠な要素です。

技術的詳細

このコミットは、Go言語のARMツールチェインにPREFETCH命令のサポートを統合するために、複数のファイルにわたる変更を加えています。

  1. src/cmd/5a/a.y:

    • このファイルは、Goのアセンブラ (5a) の文法定義 (Yacc/Bison形式) です。
    • PLD命令の構文が LTYPEPLD reg から LTYPEPLD oreg に変更されています。これは、PLD命令が単なるレジスタだけでなく、オフセット付きレジスタ (oreg) をオペランドとして受け入れるようになったことを意味します。これにより、より柔軟なメモリアドレス指定が可能になります。
  2. src/cmd/5a/y.tab.c:

    • a.yから生成されるパーサーのC言語ソースコードです。
    • a.yの変更に伴い、生成されるコードも更新されています。具体的には、YYLAST (Yaccの内部テーブルの最終インデックス) の値が609から603に減少しており、これは文法規則の変更がパーサーの内部構造に影響を与えたことを示しています。また、yyrhsyydefactyypactyytableなどの内部テーブルの値も変更されており、これはPLD命令の新しい構文を認識するためのパーサーのロジックが更新されたことを意味します。
  3. src/cmd/5c/peep.c:

    • GoのARMコンパイラ (5c) のピーフホール最適化 (peephole optimization) を行うファイルです。
    • copyu関数内で、APLD命令がコピー操作の対象として追加されています。これは、PLD命令が他の命令と同様に、レジスタやアドレスのコピー操作において適切に扱われるようにするためです。
  4. src/cmd/5c/reg.c:

    • GoのARMコンパイラ (5c) のレジスタ割り当てに関するファイルです。
    • regopt関数内で、APLD命令が「右辺読み込み (right side read)」として扱われるように変更されています。これは、PLD命令がオペランドとして指定されたアドレスからデータを読み込む(ただし、レジスタには格納しない)という性質をレジスタ割り当て器に認識させるためのものです。これにより、レジスタのライブネス解析が正確に行われ、不要なレジスタの再利用を防ぎます。
  5. src/cmd/5c/txt.c:

    • GoのARMコンパイラ (5c) のテキスト生成に関するファイルです。
    • gprefetch関数が追加されています。この関数は、Goの組み込みPREFETCH関数が呼び出された際に、対応するARMのAPLD命令を生成する役割を担います。具体的には、プリフェッチ対象のアドレスをレジスタに割り当て (regalloc)、その値を移動 (gmove) した後、OINDREG (間接レジスタ) オペランドを持つAPLD命令を挿入 (gins) します。最後に、割り当てたレジスタを解放 (regfree) します。以前は// nothingとコメントされており、何も処理していなかった部分が、実際にPLD命令を生成するようになりました。
  6. src/cmd/5l/asm.c:

    • GoのARMリンカ (5l) のアセンブリコード生成に関するファイルです。
    • PLD命令の機械語エンコーディングロジックが変更されています。特に、p->from.offset (オペランドのオフセット値) が負の場合に、命令のビットパターンを適切に調整するロジックが追加されています。これにより、PLD命令が負のオフセットを持つアドレスに対しても正しくエンコードされるようになります。
  7. src/cmd/5l/optab.c:

    • GoのARMリンカ (5l) の命令オペランドテーブルです。
    • APLD命令のエントリが更新され、オペランドの型がC_REG (レジスタ) からC_SOREG (オフセット付きレジスタ) に変更されています。これは、a.yの変更と整合しており、リンカがPLD命令のオペランドとしてオフセット付きレジスタを正しく認識し、処理できるようにするためです。

これらの変更は、GoのARMツールチェイン全体でPREFETCH命令のサポートを一貫して実装するためのものです。

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

このコミットのコアとなるコードの変更箇所は以下のファイルと関数です。

  • src/cmd/5c/txt.cgprefetch 関数:

    • Goの組み込みPREFETCH関数が呼び出された際に、実際にARMのAPLD命令を生成するロジックが実装された部分です。以前は何もしていなかったこの関数が、プリフェッチの機能を実現する上で最も重要な役割を担うようになりました。
  • src/cmd/5a/a.yPLD 命令の文法定義:

    • アセンブラがPLD命令をどのように解釈するかを定義する部分です。regからoregへの変更は、PLD命令の柔軟性を高め、より実用的なプリフェッチを可能にするための基盤となります。
  • src/cmd/5l/asm.cPLD 命令のエンコーディングロジック:

    • PLD命令が最終的にどのような機械語に変換されるかを決定する部分です。特にオフセット値の処理は、正確なプリフェッチアドレス指定に不可欠です。

コアとなるコードの解説

src/cmd/5c/txt.cgprefetch 関数

void
gprefetch(Node *n)
{
	Node n1;

	regalloc(&n1, n, Z); // プリフェッチ対象のアドレスを保持するためのレジスタを割り当て
	gmove(n, &n1);       // プリフェッチ対象のアドレスを割り当てたレジスタに移動
	n1.op = OINDREG;     // オペランドのタイプを間接レジスタに設定
	gins(APLD, &n1, Z);  // APLD (プリフェッチ) 命令を生成
	regfree(&n1);        // 割り当てたレジスタを解放
}

このgprefetch関数は、Goのソースコード内でPREFETCH組み込み関数が使用されたときにコンパイラによって呼び出されます。

  1. regalloc(&n1, n, Z);: nで指定されたプリフェッチ対象のアドレスを一時的に保持するためのレジスタn1を割り当てます。Zはゼロレジスタを意味し、ここでは使用されません。
  2. gmove(n, &n1);: n (プリフェッチ対象のアドレス) の値を、割り当てられたレジスタn1に移動します。
  3. n1.op = OINDREG;: n1のオペランドタイプをOINDREG (間接レジスタ) に設定します。これは、APLD命令がレジスタに格納されたアドレスを間接的に参照してプリフェッチを行うことを示します。
  4. gins(APLD, &n1, Z);: 実際にARMのAPLD (Preload Data) 命令を生成し、n1をオペランドとして渡します。これにより、n1に格納されたアドレスのデータがキャッシュにプリフェッチされるようプロセッサに指示されます。
  5. regfree(&n1);: 一時的に使用したレジスタn1を解放します。

この関数により、GoのPREFETCH呼び出しが、ARMプロセッサのデータプリフェッチ機能に直接マッピングされるようになります。

src/cmd/5a/a.yPLD 命令の文法定義

--- a/src/cmd/5a/a.y
+++ b/src/cmd/5a/a.y
@@ -304,7 +304,7 @@ inst:
 /*
  * PLD
  */
-|	LTYPEPLD reg
+|	LTYPEPLD oreg
 	{
 	\toutcode($1, Always, &$2, NREG, &nullgen);\
 	}

この変更は、アセンブラがPLD命令を解析する際の文法規則を更新しています。

  • 変更前: LTYPEPLD reg
    • PLD命令が単一のレジスタをオペランドとして受け入れることを意味します。これは、レジスタに格納されたアドレスを直接プリフェッチする場合に限定されます。
  • 変更後: LTYPEPLD oreg
    • PLD命令が「オフセット付きレジスタ (offset register)」をオペランドとして受け入れることを意味します。oregは、[base_register, offset]のような形式で、ベースレジスタとオフセット値の組み合わせでアドレスを指定できるため、より柔軟なメモリアクセスパターンに対応できます。例えば、構造体のメンバーや配列の要素など、ベースアドレスからの相対的な位置にあるデータをプリフェッチする際に便利です。

この文法変更により、Goのアセンブラはより多様なPLD命令の記述をサポートできるようになります。

src/cmd/5l/asm.cPLD 命令のエンコーディングロジック

--- a/src/cmd/5l/asm.c
+++ b/src/cmd/5l/asm.c
@@ -1429,9 +1429,14 @@ if(debug['G']) print("%ux: %s: arm %d\n", (uint32)(p->pc), p->from.sym->name, p-
 			break;
 		o2 = oshr(p->from.reg, 0, REGTMP, p->scond);
 		break;
-	case 95:	/* PLD reg */
+	case 95:	/* PLD off(reg) */
 		o1 = 0xf5d0f000;
 		o1 |= p->from.reg << 16;
+		if(p->from.offset < 0) {
+			o1 &= ~(1 << 23);
+			o1 |= (-p->from.offset) & 0xfff;
+		} else
+			o1 |= p->from.offset & 0xfff;
 	}
 	
 	out[0] = o1;

このリンカのコードは、PLD命令を実際のARM機械語に変換する部分です。

  • o1 = 0xf5d0f000;: これはPLD命令の基本的なビットパターンです。
  • o1 |= p->from.reg << 16;: p->from.reg (ベースレジスタ) の値を、命令の適切な位置 (16ビットシフト) に挿入します。
  • if(p->from.offset < 0) { ... } else { ... }: この部分がオフセット値の処理です。
    • p->from.offset < 0: オフセットが負の場合、命令の23ビット目をクリア (o1 &= ~(1 << 23);) します。これは、ARMのPLD命令のエンコーディングにおいて、オフセットが負であることを示すビットです。そして、オフセットの絶対値 (-p->from.offset) を下位12ビット (0xfff) にマスクして命令に組み込みます。
    • else: オフセットが正の場合、オフセット値をそのまま下位12ビットにマスクして命令に組み込みます。

このロジックにより、PLD命令が正負両方のオフセットを持つアドレスに対して正しく機械語にエンコードされ、正確なデータプリフェッチが可能になります。

関連リンク

参考にした情報源リンク