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

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

このコミットは、Goコンパイラ 6g のバックエンドにおけるレジスタ割り当て(reg)とピーフホール最適化(peep)に関連する広範な変更を含んでいます。具体的には、以下の8つのファイルが修正されています。

  • src/cmd/6g/cgen.c: コード生成に関する変更。
  • src/cmd/6g/gen.c: コード生成の汎用部分に関する変更。
  • src/cmd/6g/gsubr.c: コード生成のサブルーチンに関する変更。
  • src/cmd/6g/opt.h: 最適化パスで使用されるデータ構造の定義に関する変更。
  • src/cmd/6g/peep.c: ピーフホール最適化のロジックに関する変更。
  • src/cmd/6g/reg.c: レジスタ割り当てのロジックに関する変更。
  • src/cmd/gc/go.h: Go言語のASTノードやオペレーションの定義に関する変更。
  • src/cmd/gc/walk.c: ASTのウォーク(走査)に関する変更。

これらの変更は、コンパイラのコード生成と最適化の効率を向上させることを目的としています。

コミット

commit e081f25c3e602804fc3bd0780e09bf35d2a098cb
Author: Ken Thompson <ken@golang.org>
Date:   Sat Nov 22 17:58:53 2008 -0800

    reg and peep
    
    R=r
    OCL=19871
    CL=19871

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

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

元コミット内容

reg and peep

この簡潔なコミットメッセージは、レジスタ割り当て(reg)とピーフホール最適化(peep)に焦点を当てた変更であることを示唆しています。Goコンパイラ 6g は、初期のGo言語のコンパイラであり、Ken Thompson氏によって開発されました。

変更の背景

Go言語の初期のコンパイラである 6g は、コンパイル速度を重視して設計されていました。そのため、GCCやLLVMのような高度な最適化は行われていませんでしたが、基本的な最適化は組み込まれていました。このコミットは、その基本的な最適化、特にレジスタ割り当てとピーフホール最適化の改善を目的としています。

コンパイラにおけるレジスタ割り当ては、プログラムの実行速度に直結する重要な最適化です。頻繁に使用される値をCPUのレジスタに割り当てることで、メモリへのアクセスを最小限に抑え、パフォーマンスを向上させます。ピーフホール最適化は、生成された機械語コードの小さなウィンドウ(ピーフホール)を検査し、より効率的な命令シーケンスに置き換える局所的な最適化手法です。これには、冗長なロード/ストアの削除、複数の操作の結合、デッドコードの排除などが含まれます。

このコミットは、6g コンパイラがより効率的なコードを生成できるように、これらの最適化パスを洗練させるための取り組みの一環と考えられます。特に、インクリメント/デクリメント操作の最適化や、型変換における命令選択の改善などが含まれています。

前提知識の解説

Goコンパイラ 6g

6g は、Go言語の初期のコンパイラツールチェーン gc の一部であり、x86-64アーキテクチャ(AMD64)をターゲットとしていました。6g は、高速なコンパイルを優先する設計思想を持っており、現代の最適化コンパイラに比べると最適化の深度は限定的でした。しかし、それでも基本的なレジスタ割り当てやピーフホール最適化などの手法を取り入れていました。

レジスタ割り当て (Register Allocation)

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

6g のようなコンパイラでは、通常、線形スキャンアルゴリズムやグラフ彩色アルゴリズムなどの手法が用いられます。線形スキャンアルゴリズムは、変数の「生存期間」(定義されてから最後に使用されるまでの期間)を分析し、レジスタが利用可能な期間に変数を割り当てます。レジスタが不足した場合は、「スピル」(レジスタからメモリへの退避)が発生します。

ピーフホール最適化 (Peephole Optimization)

ピーフホール最適化は、コンパイラのバックエンドで行われる局所的な最適化手法です。生成されたアセンブリコードや中間表現の小さな連続した命令列(「ピーフホール」と呼ばれるウィンドウ)を検査し、より効率的な等価な命令列に置き換えます。

一般的なピーフホール最適化の例としては、以下のようなものがあります。

  • 冗長なロード/ストアの削除: 同じ値をレジスタにロードし、すぐにストアするような冗長な操作を削除します。
  • 強度の削減: 複雑な命令をより単純で高速な命令に置き換えます(例: x * 2x << 1 に)。
  • 定数伝播: 定数値を計算時に使用し、実行時の計算を減らします。
  • デッドコードの削除: 決して実行されないコードや、その結果が使用されないコードを削除します。
  • インクリメント/デクリメントの最適化: ADD reg, 1INC reg に置き換えるなど。

6g は、コンパイル速度を重視していたため、ピーフホール最適化も限定的な範囲で行われていました。

技術的詳細

このコミットは、6g コンパイラのコード生成と最適化の複数の側面に影響を与えています。

src/cmd/6g/cgen.c の変更

  • cgen 関数内で、OINDREG オペレーションの処理が変更されています。以前は gmove(&n1, res) で直接結果に移動していましたが、n2 = n1; n2.op = OINDREG; n2.type = types[TINT32]; gmove(&n2, &n1); という中間ステップが追加され、その後 gmove(&n1, res); で最終的な結果に移動しています。これは、ポインタのデリファレンスと値の移動のセマンティクスをより正確に扱うための変更である可能性があります。

src/cmd/6g/gen.c の変更

  • if(!debug['N'] || debug['R'] || debug['P']) regopt(ptxt); の行が追加され、デバッグフラグ N(最適化なし)が設定されていない場合、またはレジスタ割り当て (R) やピーフホール最適化 (P) のデバッグフラグが設定されている場合に regopt 関数(レジスタ最適化)が呼び出されるようになりました。これにより、最適化の適用条件がより柔軟になりました。
  • cgen_asop 関数内で、OADD, OSUB, OXOR, OAND, OOR などの二項演算子とリテラル値 1 の組み合わせに対する特殊な最適化が追加されました。
    • OADDOSUB の場合、右オペランドがリテラル 1 であれば、それぞれ OINC (インクリメント) および ODEC (デクリメント) 命令に変換されるようになりました。これは、より効率的な単一命令に置き換えるピーフホール最適化の一種です。
    • OXOR, OAND, OOR の場合も、右オペランドがリテラルであれば、直接対応する命令を生成するようになりました。
  • cgen_as 関数に iszer フラグが追加され、右辺がゼロである場合の処理が改善されました。特に、nl->addable かつ iszer の場合に ANOP (No Operation) 命令を挿入することで、後続の最適化パスで利用される可能性のある「使用済み」マークを付ける意図があるようです。

src/cmd/6g/gsubr.c の変更

  • gmove 関数内で、TPTR32 から TPTR64 への型変換において、AMOVLQZX 命令が使用されるようになりました。これは、32ビットポインタを64ビットポインタにゼロ拡張して移動するための命令です。
  • コメントアウトされていた整数型間の変換に関する CASE 文が、コメントアウトされたままですが、フォーマットが変更されています。
  • TUINT64 から TINT8, TINT16, TINT32 への変換に AMOVLQSX (符号拡張移動) が追加されました。
  • TUINT32 から TINT64, TUINT64, TPTR64 への変換、および TPTR32 から TINT64, TUINT64, TPTR64 への変換に AMOVLQZX (ゼロ拡張移動) が追加されました。これにより、異なるサイズの整数型やポインタ型間の変換がより正確かつ効率的に行われるようになりました。
  • optoas 関数に、OINC (インクリメント) と ODEC (デクリメント) オペレーションに対する新しい命令マッピングが追加されました。
    • TINT8/TUINT8 には AINCB/ADECB (バイト単位)
    • TINT16/TUINT16 には AINCW/ADECW (ワード単位)
    • TINT32/TUINT32/TPTR32 には AINCL/ADECL (ロングワード単位)
    • TINT64/TUINT64/TPTR64 には AINCQ/ADECQ (クワッドワード単位) これらの命令は、CPUが提供する単一のインクリメント/デクリメント命令を利用することで、ADD reg, 1SUB reg, 1 といった2命令を1命令に削減し、コードサイズと実行速度を改善します。

src/cmd/6g/opt.h の変更

  • Reg 構造体から magic, pc, log5 フィールドが削除され、loop フィールドが int32 から uint16 に変更され、refset フィールドが uchar 型で追加されました。これは、レジスタ割り当ての内部データ構造の整理と効率化を示しています。
  • EXTERN Bits ovar; が追加され、出力引数(戻り値)の変数を追跡するためのビットセットが導入されました。
  • ostats 構造体が追加され、コンパイラの最適化に関する統計情報(ncvtreg, nspill, nreload, ndelmov, nvar, naddr)を収集できるようになりました。これは、最適化の有効性を評価し、さらなる改善のためのデータを提供するために重要です。
  • dumpitnoreturn 関数のプロトタイプが、static から通常の関数に変更されました。

src/cmd/6g/peep.c の変更

  • peep 関数内で、Prog 構造体の reg フィールドが r2->prog = p; p->reg = r2; のように設定されるようになりました。これにより、プログラム命令とレジスタ情報が相互に参照できるようになり、最適化パスでのデータアクセスが容易になります。
  • デバッグ出力 dumpit("loop1", firstr); が追加され、ピーフホール最適化のループ開始時の状態を可視化できるようになりました。
  • AADDQ, AADDL, ASUBQ, ASUBL 命令に対するピーフホール最適化が改善されました。オフセットが -1 または 1 の場合、それぞれ ADECQ/ADECL/AINCQ/AINCL などの単一命令に変換されるロジックがより堅牢になりました。これは、gen.c での OINC/ODEC の生成と連携して、より効率的なコードを生成します。
  • excise 関数内で、削除される命令のデバッグ出力が追加され、ostats.ndelmov++ で削除された移動命令の統計が記録されるようになりました。

src/cmd/6g/reg.c の変更

  • MAGIC マクロが削除されました。
  • setoutvar 関数が追加されました。この関数は、関数の出力引数(戻り値)を走査し、それらの変数を ovar ビットセットに記録します。これは、レジスタ割り当てにおいて、戻り値がレジスタに割り当てられるべきか、あるいはメモリにスピルされるべきかを判断する際に重要な情報となります。
  • regopt 関数内で、ovar ビットセットの初期化が追加されました。
  • regopt のパス1において、AINCB, AINCL, AINCQ, AINCW, ADECB, ADECL, ADECQ, ADECW といったインクリメント/デクリメント命令が、レジスタの読み書き操作として適切に処理されるようになりました。
  • regopt の各パス(パス1、パス2、パス2.5、パス3、パス4、パス6)の開始時に、デバッグフラグ Rv が設定されている場合に dumpit 関数が呼び出され、レジスタ割り当ての各段階での状態を詳細にトレースできるようになりました。
  • 「used and not set」および「set and not used」の警告ロジックが変更され、r->refset フラグが導入されました。これにより、警告が一度だけ表示されるようになり、冗長な出力が抑制されます。また、デバッグフラグ w が追加され、警告の表示を制御できるようになりました。
  • nregionNRGN を超えた場合の fatal エラーが、デバッグモードでは警告に変わるようになりました。
  • peep() 関数の呼び出し条件が if(!debug['R'] || debug['P']) に変更されました。これは、レジスタ割り当てのデバッグ (-R) が有効な場合はピーフホール最適化 (-P) をスキップしないことを意味します。
  • 最適化後の不要な ANOP 命令の削除ロジックが改善され、分岐命令のターゲットも適切に更新されるようになりました。
  • regopt 関数の最後に、ostats 構造体に記録された最適化統計情報が出力されるようになりました。これにより、レジスタ割り当てやピーフホール最適化の効果を数値的に評価できるようになります。
  • addmove 関数内で、AMOVSD (double-precision float move) が TFLOAT64 型の移動に使用されるようになりました。また、ostats.nspill++ でスピル操作の統計が記録されるようになりました。
  • mkvar 関数内で、レジスタ使用ビットマップ r->regu の更新が r != R の条件付きになりました。また、ostats.naddr++ostats.nvar++ でアドレスと変数の統計が記録されるようになりました。
  • prop 関数内で、ARET (return) 命令の処理が変更され、cal.b[z]ovar.b[z] (出力変数) が含まれるようになりました。これにより、戻り値がレジスタ割り当ての考慮対象となることが明確になります。
  • paint1 関数内で、デバッグ出力が削除され、より簡潔になりました。
  • paint3 関数内で、デバッグ出力が debug['R'] && debug['v'] の条件付きになり、より詳細なデバッグ情報が必要な場合にのみ表示されるようになりました。また、addreg 関数呼び出し後に ostats.ncvtreg++ でレジスタ変換の統計が記録されるようになりました。
  • BtoR 関数で、ビットマスクが 0xffffL から 0x3fffL に変更されました。これは、レジスタのビット表現に関する変更であり、おそらく特定のレジスタ(R14, R15)を除外するためです。
  • dumpitnoreturn 関数の定義が static から通常の関数に変更されました。

src/cmd/gc/go.h の変更

  • enumOINCODEC という新しいオペレーションが追加されました。これらは、インクリメントとデクリメントを表すプレースホルダーであり、コンパイラのフロントエンドで認識され、バックエンドで対応する機械語命令に変換されます。

src/cmd/gc/walk.c の変更

  • nottop ラベルの処理が変更され、yyerror のメッセージがより詳細になりました。top の値に応じて、操作がステートメントコンテキスト、代入コンテキスト、または式コンテキストで許可されていないことを示すエラーメッセージが表示されるようになりました。これにより、コンパイラのエラー報告が改善されます。
  • symlist[1] = pkglookup("panicl", "sys"); が追加され、noreturn 関数が panicl も非リターン関数として認識するようになりました。

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

このコミットのコアとなる変更は、以下のファイルと機能に集約されます。

  1. src/cmd/6g/gen.c および src/cmd/6g/gsubr.c における OINC/ODEC の導入と最適化:
    • gen.c で、+1-1 のリテラルを含む加減算を OINC/ODEC オペレーションに変換するロジックが追加されました。
    • gsubr.coptoas 関数で、これらの OINC/ODEC オペレーションが、ターゲットアーキテクチャの効率的な単一命令(AINCB, AINCW, AINCL, AINCQ, ADECB, ADECW, ADECL, ADECQ)にマッピングされるようになりました。
  2. src/cmd/6g/reg.c におけるレジスタ割り当ての改善と統計情報の導入:
    • setoutvar 関数の追加により、関数の戻り値がレジスタ割り当ての考慮対象となるようになりました。
    • ostats 構造体の導入により、レジスタ割り当てや最適化の各段階での統計情報が収集されるようになり、コンパイラのパフォーマンス分析と改善に役立つようになりました。
    • レジスタ割り当ての各パスにおける詳細なデバッグ出力が追加され、トレースが容易になりました。
  3. src/cmd/6g/peep.c におけるピーフホール最適化の強化:
    • AADDQ/AADDL/ASUBQ/ASUBL 命令に対する INC/DEC 命令への変換ロジックが改善され、より多くのケースで効率的な命令が生成されるようになりました。
    • 最適化によって削除された命令の統計が ostats.ndelmov で追跡されるようになりました。

コアとなるコードの解説

インクリメント/デクリメントの最適化 (gen.c, gsubr.c)

Goコンパイラ 6g は、x = x + 1x = x - 1 のような一般的な操作を、より効率的なCPU命令に変換するようになりました。

src/cmd/6g/gen.ccgen_asop 関数では、以下のようなロジックが追加されています。

if(nl->addable && nr->op == OLITERAL)
switch(n->etype) {
case OADD:
    if(!isint[nl->type->etype])
        goto com;
    if(mpgetfix(nr->val.u.xval) != 1)
        goto com;
    gins(optoas(OINC, nl->type), N, nl); // OINC に変換
    goto ret;
case OSUB:
    if(!isint[nl->type->etype])
        goto com;
    if(mpgetfix(nr->val.u.xval) != 1)
        goto com;
    gins(optoas(ODEC, nl->type), N, nl); // ODEC に変換
    goto ret;
// ...
}

このコードは、左辺がアドレス可能 (nl->addable) で、右辺がリテラル (nr->op == OLITERAL) の場合に、加算 (OADD) や減算 (OSUB) のオペレーションをチェックします。もし右辺のリテラル値が 1 であれば、それぞれ OINC または ODEC という新しい内部オペレーションに変換します。

次に、src/cmd/6g/gsubr.coptoas 関数では、これらの OINC/ODEC オペレーションが実際の機械語命令にマッピングされます。

case CASE(OINC, TINT8):
case CASE(OINC, TUINT8):
    a = AINCB; // バイト単位のインクリメント
    break;
// ... 他の型に対する AINCW, AINCL, AINCQ
case CASE(ODEC, TINT8):
case CASE(ODEC, TUINT8):
    a = ADECB; // バイト単位のデクリメント
    break;
// ... 他の型に対する ADECW, ADECL, ADECQ

これにより、例えば x = x + 1 というGoのコードは、コンパイル後に INCQ x (64ビット整数) のような単一のCPU命令に変換される可能性が高まります。これは、ADDQ $1, x のような2命令よりも効率的です。

レジスタ割り当ての統計情報 (reg.c, opt.h)

src/cmd/6g/opt.h で定義された ostats 構造体は、コンパイラの最適化パスの効率を測定するための重要なツールです。

struct
{
    int32 ncvtreg;  // レジスタ変換の回数
    int32 nspill;   // レジスタスピルの回数
    int32 nreload;  // レジスタリロードの回数
    int32 ndelmov;  // 削除された移動命令の回数
    int32 nvar;     // 変数の処理回数
    int32 naddr;    // アドレスの処理回数
} ostats;

これらのカウンタは、src/cmd/6g/reg.csrc/cmd/6g/peep.c の様々な場所でインクリメントされます。例えば、addmove 関数(レジスタからメモリへのスピルを生成する可能性のある関数)では ostats.nspill++ が、excise 関数(不要な命令を削除する関数)では ostats.ndelmov++ が呼び出されます。

reg.cregopt 関数の最後で、これらの統計情報がデバッグ出力として表示されます。

if(debug['R']) {
    if(ostats.ncvtreg || ostats.nspill || ostats.nreload || ostats.ndelmov || ostats.nvar || ostats.naddr || 0)
        print("\nstats\n");
    // ... 各統計情報の出力
    memset(&ostats, 0, sizeof(ostats));
}

この統計情報は、コンパイラの開発者がレジスタ割り当てやピーフホール最適化のボトルネックを特定し、さらなる改善のための指針を得るのに役立ちます。例えば、nspill の値が高い場合、レジスタが不足している可能性があり、より洗練されたレジスタ割り当てアルゴリズムが必要であることを示唆します。

戻り値のレジスタ割り当てへの考慮 (reg.c)

src/cmd/6g/reg.c に追加された setoutvar 関数と、prop 関数における ARET (return) 命令の処理の変更は、関数の戻り値がレジスタ割り当ての対象となることを保証します。

setoutvar 関数は、関数の出力引数(戻り値)を走査し、それらの変数をグローバルな ovar ビットセットに記録します。

void
setoutvar(void)
{
    // ...
    t = structfirst(&save, getoutarg(curfn->type));
    while(t != T) {
        // ...
        for(z=0; z<BITS; z++)
            ovar.b[z] |= bit.b[z]; // ovar に出力変数を追加
        t = structnext(&save);
    }
}

そして、prop 関数(レジスタ割り当てのデータフロー解析の一部)では、ARET 命令が処理される際に、cal (call-clobbered registers) ビットセットに ovar が含まれるようになりました。

case ARET:
    for(z=0; z<BITS; z++) {
        cal.b[z] = externs.b[z] | ovar.b[z]; // 戻り値も cal に含める
        ref.b[z] = 0;
    }
    break;

これにより、レジスタ割り当てアルゴリズムは、関数の戻り値が呼び出し元に渡される際にレジスタに保持されるべきであることを認識し、不必要なメモリへのスピルを避けることができます。

関連リンク

参考にした情報源リンク

  • GitHubコミットページ: https://github.com/golang/go/commit/e081f25c3e602804fc3bd0780e09bf35d2a098cb
  • Web検索結果: "Go compiler 6g register allocation peephole optimization" (Google Search)
    • redhat.com (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEtbzE9h94vXgRZlCJSWD9XNy11H_lqimXVhYgtN27TNzBgBYXHrcI2-zWFzQRCGwogdvxO486BM1Q8efAsziUvaUqeyQ506LGKthSRsZVpmRT-fo_xFYBGfLNwdFkt4iK4hKT5Lo7K7e5WFm-f55xEYlsJg8An0f4sb4Yty7fSYuP6NzeHMUD12wMD)
    • github.com (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQE0BEKtKUWKsI8Sug5kr1-eNeAjc2PrSf5T4euOqzmKlE49JQa_Es57_UrZS6x7po6gzAwmScLEMjBXVn_c8s8gwRYyNtTLYnNKERFfSdpPJCKi_kofXbgNFl2-6ZkEXneYHo1Ylqjdo9bBM8f3A26CaI8ohFwh0A==)
    • wikipedia.org (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFtkK2KCP3B6eU_flYVh9W9O36GjH81YNiTyQRptLSM7GyFGcAho24NBGAVlTMtr4OWUYGh0p-_CSGjYH5wmtk7gOdiZfZVkmNb4wvZeW98Zsn1whG9A1DvBxTJo_wljKJdIUM3IpjOuTb0gwot)
    • medium.com (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHEM2KZ_Z9YZlkTRQK4o6u5QEZxHdXnHX9_S-yk5isaMjcqfLJcud_CxdYOVMebZrweeF0Mnwg9Sj-45fdfFTpnwl1l3_JQ8ndiXc7B6Uukkmkwyzdx5E38TO4w1SmkVQr0a_KQowiwHxNK-Z22s0B5n29vCEyBOfBWnbLmISsDwbyaGr2kyLEujQjo)
    • collegenote.net (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFIvjcnhtMTltr-sHaRQ-ovrDOlZP0ugFCimm93sCYqpE6t0ATQ2PJvFN1d_YKDtvR0SbKiCffKmHebCrjLb7KDJF7zZOjbr3u-ruvsAw9bFvFWhAa2a7jTl7FswSZvx27-SxMDL_T9yl-3fbRLA3Gw7k4JBTw28ZA-yOBcYViLmbuf9U3SeC48e-bcCX87)
    • h-da.de (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEGiJjhtfhFp2RCahMSNTTkTIX6TkMZ6xVyfpw9CQxn2qaSKhngVHcWYBses3KaV4QY8sQ3xQ_RxvJdhpiPbfsHXA8xACcw_2C8q3FuXtuXV8svVOzuTdNxc4HQf6uCg_cVjSbsf8I2Vatf78ee09sgbzOdb4YDKvPQiIg0M3TqK9T8-kSy2dXQjy6iUkPoEGUsadUprGICaxpaIg==)
    • ycombinator.com (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQERlDlM2zVNxAuYIopLX12fSrldfCLysawKawG-oH95qUZqdwdFje9VZL5qotz7ENyJUtCDpc4scG0AbrqIQ6IKkus85Afx4DzCNa_0g3yXfYWmX18eTKiLvJEt1iQFgoIztS1RPElk)