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

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

このコミットは、Goコンパイラのcmd/5g(ARMアーキテクチャ向けのコード生成バックエンド)における最適化を目的としています。具体的には、配列のインデックスが定数である場合に、一時レジスタの使用を避けることで、生成されるアセンブリコードの効率を向上させます。これにより、コードサイズが削減され、実行速度がわずかに向上する可能性があります。

コミット

commit ed0c5dd11f983d0e54806cd087ec852e43fa9f9e
Author: Dave Cheney <dave@cheney.net>
Date:   Sat Oct 6 11:51:06 2012 +1000

    cmd/5g: avoid temporary during constant OINDEX
    
    func addr(s[]int) *int {
            return &s[2]
    }
    
    --- prog list "addr" ---
    0000 (/home/dfc/src/addr.go:5) TEXT     addr+0(SB),$0-16
    0001 (/home/dfc/src/addr.go:6) MOVW     $s+0(FP),R0
    0002 (/home/dfc/src/addr.go:6) MOVW     4(R0),R1
    0003 (/home/dfc/src/addr.go:6) MOVW     $2,R2
    0004 (/home/dfc/src/addr.go:6) CMP      R2,R1,
    0005 (/home/dfc/src/addr.go:6) BHI      ,7(APC)
    0006 (/home/dfc/src/addr.go:6) BL       ,runtime.panicindex+0(SB)
    0007 (/home/dfc/src/addr.go:6) MOVW     0(R0),R0
    0008 (/home/dfc/src/addr.go:6) MOVW     $8,R1
    0009 (/home/dfc/src/addr.go:6) ADD      R1,R0
    0010 (/home/dfc/src/addr.go:6) MOVW     R0,.noname+12(FP)
    0011 (/home/dfc/src/addr.go:6) RET      ,
    
    becomes
    
    --- prog list "addr" ---
    0000 (/home/dfc/src/addr.go:5) TEXT     addr+0(SB),$0-16
    0001 (/home/dfc/src/addr.go:6) MOVW     $s+0(FP),R0
    0002 (/home/dfc/src/addr.go:6) MOVW     4(R0),R1
    0003 (/home/dfc/src/addr.go:6) MOVW     $2,R2
    0004 (/home/dfc/src/addr.go:6) CMP      R2,R1,
    0005 (/home/dfc/src/addr.go:6) BHI      ,7(APC)
    0006 (/home/dfc/src/addr.go:6) BL       ,runtime.panicindex+0(SB)
    0007 (/home/dfc/src/addr.go:6) MOVW     0(R0),R0
    0008 (/home/dfc/src/addr.go:6) ADD      $8,R0
    0009 (/home/dfc/src/addr.go:6) MOVW     R0,.noname+12(FP)
    0010 (/home/dfc/src/addr.go:6) RET      ,
    
    R=rsc, remyoudompheng, minux.ma
    CC=golang-dev
    https://golang.org/cl/6590056

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

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

元コミット内容

cmd/5g: avoid temporary during constant OINDEX

変更の背景

Goコンパイラは、ソースコードを機械語に変換する際に様々な最適化を行います。このコミットの背景にあるのは、配列の要素に定数インデックスでアクセスする際のコード生成の非効率性です。

例えば、&s[2] のような式では、配列 s の先頭アドレスに、インデックス 2 と要素のサイズを掛け合わせたオフセット(2 * sizeof(int))を加算することで、目的の要素のアドレスを計算します。

変更前は、この定数オフセット(例: 8バイト)をレジスタにロードし、そのレジスタの内容をベースアドレスに加算するという2段階の処理が行われていました。これは、定数値を直接加算命令のオペランドとして使用できる場合でも、不必要に一時レジスタを消費し、余分な命令を生成していました。

このコミットは、このような冗長な一時レジスタの使用を排除し、定数オフセットを直接加算命令に埋め込むことで、生成されるアセンブリコードをよりコンパクトかつ効率的にすることを目的としています。

前提知識の解説

このコミットを理解するためには、以下の概念について基本的な知識が必要です。

  1. Goコンパイラ (cmd/5g):

    • Go言語のコンパイラは、cmd/gc がフロントエンドとして構文解析、型チェック、中間表現の生成などを行い、その後、各アーキテクチャ固有のバックエンド(例: cmd/5g はARM、cmd/6g はx86-64、cmd/8g はx86-32)がアセンブリコードを生成します。
    • cmd/5g は、ARMアーキテクチャ向けのGoプログラムをコンパイルする際に使用されるコンパイラバックエンドです。
  2. アセンブリ言語とレジスタ:

    • アセンブリ言語は、CPUが直接実行できる機械語に非常に近い低レベルのプログラミング言語です。
    • レジスタ: CPU内部にある高速な記憶領域で、計算の途中の値やアドレスを一時的に保持するために使われます。ARMアーキテクチャでは、R0, R1, R2 などの汎用レジスタがあります。
    • 命令: MOVW (Move Word), ADD (Add), CMP (Compare), BHI (Branch if Higher), BL (Branch and Link), RET (Return) などは、ARMアセンブリの基本的な命令です。
      • MOVW $value, Reg: 即値 valueReg に移動します。
      • ADD Reg1, Reg2: Reg2 の値に Reg1 の値を加算し、結果を Reg2 に格納します。
      • ADD $value, Reg: 即値 valueReg に加算し、結果を Reg に格納します(このコミットで最適化される形式)。
      • TEXT func+offset(SB),$framesize-argsize: 関数の開始を宣言します。SB は静的ベースレジスタ、$framesize はスタックフレームサイズ、$argsize は引数のサイズです。
      • FP (Frame Pointer): 現在のスタックフレームのベースアドレスを指すポインタ。関数引数やローカル変数へのアクセスに使われます。s+0(FP) は、フレームポインタからのオフセットで引数 s にアクセスすることを示します。
  3. Goコンパイラの内部構造(cgen.c:

    • cgen.c は、Goコンパイラのコード生成フェーズの一部であり、中間表現(ASTノードなど)をターゲットアーキテクチャのアセンブリ命令に変換する役割を担っています。
    • Node: コンパイラ内部でコードの各要素(変数、定数、演算など)を表すデータ構造です。
    • nodconst(&n2, types[tptr], v*w): 定数ノード n2 を作成する関数です。v*w は定数オフセットの計算結果(例: 8)です。
    • regalloc(&n4, n2.type, N): レジスタ n4 を割り当てる関数です。N はノードを表します。
    • gmove(&n2, &n4): ノード n2 の値をノード n4 に移動するアセンブリ命令を生成する関数です。これは通常、レジスタ間のデータ転送や、メモリからレジスタへのロード、レジスタからメモリへのストアなどに使われます。
    • gins(optoas(OADD, types[tptr]), &n4, &n3): アセンブリ命令を生成する関数です。
      • optoas(OADD, types[tptr]): 抽象的な演算子 OADD(加算)と型 types[tptr](ポインタ型)を、ターゲットアーキテクチャの具体的なアセンブリ命令(例: ADD)に変換します。
      • &n4, &n3: 命令のオペランドとなるノードです。この場合、n4 の値を n3 に加算します。
    • regfree(&n4): レジスタ n4 を解放する関数です。
  4. OINDEX:

    • Goコンパイラ内部で、配列やスライスのインデックスアクセスを表す抽象的な操作です。OINDEX は、s[2] のような式を処理する際に使用されます。

技術的詳細

このコミットの技術的な核心は、Goコンパイラのコード生成器が、定数オフセットを持つアドレス計算をより効率的に行うように変更された点にあります。

変更前は、&s[2] のような式を処理する際、コンパイラは以下のような手順でアセンブリコードを生成していました(簡略化された概念図):

  1. 配列のベースアドレスをレジスタ R0 にロードします。
  2. 定数オフセット(例: 8バイト)を別のレジスタ R1 にロードします(MOVW $8, R1)。
  3. R0R1 の内容を加算し、結果を R0 に格納します(ADD R1, R0)。

このプロセスでは、定数 8 を一時的に R1 レジスタに格納するという余分なステップがありました。多くのCPUアーキテクチャ(ARMを含む)では、加算命令のオペランドとして即値(定数)を直接指定できる「即値オペランド」という機能があります。これにより、レジスタを介さずに直接定数を加算できます。

変更後のコンパイラは、この即値オペランドの機能を活用するように修正されました。具体的には、src/cmd/5g/cgen.c 内の agen 関数(アドレス生成を担当する部分)で、定数インデックスによるオフセット計算が行われる際に、一時レジスタの割り当て (regalloc)、値の移動 (gmove)、レジスタの解放 (regfree) のステップが削除されました。

代わりに、gins 関数が直接、定数ノード (n2) を加算命令のオペランドとして受け取れるようになりました。これにより、コンパイラは以下のようなより効率的なアセンブリコードを生成します:

  1. 配列のベースアドレスをレジスタ R0 にロードします。
  2. R0 に定数 8 を直接加算します(ADD $8, R0)。

この変更により、MOVW $8, R1ADD R1, R0 の2つの命令が ADD $8, R0 の1つの命令に削減されます。これにより、生成されるバイナリのサイズがわずかに小さくなり、CPUが実行する命令数が減るため、実行速度もわずかに向上します。これは、特にループ内で頻繁にアクセスされる配列に対して効果を発揮します。

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

--- a/src/cmd/5g/cgen.c
+++ b/src/cmd/5g/cgen.c
@@ -733,11 +733,7 @@ agen(Node *n, Node *res)\n 			}\n \n 			nodconst(&n2, types[tptr], v*w);\n-\t\t\tregalloc(&n4, n2.type, N);\n-\t\t\tgmove(&n2, &n4);\n-\t\t\tgins(optoas(OADD, types[tptr]), &n4, &n3);\n-\t\t\tregfree(&n4);\n-\n+\t\t\tgins(optoas(OADD, types[tptr]), &n2, &n3);\n \t\t\tgmove(&n3, res);\
 \t\t\tregfree(&n3);\
 \t\t\tbreak;

コアとなるコードの解説

変更は src/cmd/5g/cgen.c ファイルの agen 関数内で行われています。この関数は、アドレス計算(&s[2] のような式)をアセンブリ命令に変換する役割を担っています。

  • 削除された行:

    			regalloc(&n4, n2.type, N);
    			gmove(&n2, &n4);
    			gins(optoas(OADD, types[tptr]), &n4, &n3);
    			regfree(&n4);
    

    これらの行は、定数オフセット n2 を一時レジスタ n4 にロードし、その n4 を使って加算命令を生成する処理を表しています。

    • regalloc(&n4, n2.type, N);: 一時レジスタ n4 を割り当てます。
    • gmove(&n2, &n4);: 定数 n2 の値を n4 に移動するアセンブリ命令(例: MOVW $8, R1)を生成します。
    • gins(optoas(OADD, types[tptr]), &n4, &n3);: n4 の内容を n3 に加算するアセンブリ命令(例: ADD R1, R0)を生成します。
    • regfree(&n4);: 一時レジスタ n4 を解放します。
  • 追加された行:

    			gins(optoas(OADD, types[tptr]), &n2, &n3);
    

    この行は、削除された4行を置き換えるものです。ここでは、gins 関数が直接定数ノード n2 を加算命令のオペランドとして受け取っています。これにより、コンパイラは定数オフセットを直接加算命令に埋め込むアセンブリ命令(例: ADD $8, R0)を生成できるようになります。一時レジスタの割り当て、移動、解放のオーバーヘッドがなくなります。

この変更により、コンパイラは定数インデックスアクセスにおけるアドレス計算の際に、より最適化されたアセンブリコードを生成するようになります。

関連リンク

参考にした情報源リンク

  • Goのコミット履歴(GitHub):https://github.com/golang/go/commits/master
  • Goのコードレビューシステム(Gerrit):https://go.dev/cl/ (コミットメッセージに記載されている https://golang.org/cl/6590056 はGerritのリンクです)
  • ARMアセンブリ言語の基本(一般的な情報源)
  • コンパイラ最適化の基本(一般的な情報源)
  • Goコンパイラの内部構造に関するブログ記事や解説(一般的な情報源)
  • Goのソースコード内のコメントや関連ファイル