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

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

このコミットは、Goコンパイラ(cmd/8g)およびビルドツール(cmd/dist)において、x86アーキテクチャ(386)向けの浮動小数点演算ユニット(FPU)のコード生成フレーバーを選択できるようにするものです。具体的には、従来のx87 FPU命令セット(通称「387」)と、より新しいSSE2(Streaming SIMD Extensions 2)命令セットのどちらを使用するかを、新しい環境変数 GO386 を介して指定できるようになります。これにより、特定のCPU環境やパフォーマンス要件に応じたコード生成が可能になります。

コミット

commit 9afb34b42e5d7568dab3a12f137aa80314b2c6f8
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date:   Wed Jan 2 22:55:23 2013 +0100

    cmd/dist, cmd/8g: implement GO386=387/sse to choose FPU flavour.
    
    A new environment variable GO386 is introduced to choose between
    code generation targeting 387 or SSE2. No auto-detection is
    performed and the setting defaults to 387 to preserve previous
    behaviour.
    
    The patch is a reorganization of CL6549052 by rsc.
    
    Fixes #3912.
    
    R=minux.ma, rsc
    CC=golang-dev
    https://golang.org/cl/6962043

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

https://github.com/golang/go/commit/9afb34b42e5d7568dab3a12f137aa80314b2c6f8

元コミット内容

このコミットは、rsc (おそらくRuss Cox) による CL6549052 の再編成(reorganization)であると明記されています。元のコミット内容自体は直接提供されていませんが、このコミットがその内容を整理し、より洗練された形で導入したものであることが示唆されます。

変更の背景

この変更の背景には、主に以下の点が挙げられます。

  1. FPU命令セットの多様性: x86アーキテクチャには、浮動小数点演算を行うための複数の命令セットが存在します。歴史的にはx87 FPUが広く使われてきましたが、Pentium III以降のCPUではSSE(Streaming SIMD Extensions)命令セットが導入され、特にSSE2は64ビット浮動小数点演算(double-precision)をサポートし、より高速なベクトル演算能力を提供します。
  2. パフォーマンスの最適化: SSE2はx87に比べて、より効率的な浮動小数点演算を可能にします。特に、複数の浮動小数点数を同時に処理できるSIMD(Single Instruction, Multiple Data)機能は、科学技術計算やグラフィックス処理など、浮動小数点演算が多用されるアプリケーションにおいて大きな性能向上をもたらします。
  3. 互換性の維持: 既存のGoプログラムやライブラリがx87 FPUを前提としてコンパイルされている場合、突然SSE2に切り替えると互換性の問題が生じる可能性があります。そのため、ユーザーが明示的にFPUフレーバーを選択できるメカニズムが必要とされました。
  4. Issue #3912の解決: コミットメッセージに「Fixes #3912」とあることから、この変更はGoのIssueトラッカーで報告されていた特定の課題(おそらくFPUの選択肢やパフォーマンスに関するもの)を解決することを目的としています。

前提知識の解説

浮動小数点演算ユニット (FPU)

FPUは、CPU内で浮動小数点数(実数)の計算(加算、減算、乗算、除算など)を専門に行う部分です。整数演算を行うALU(Arithmetic Logic Unit)とは異なり、より複雑な計算を効率的に処理します。

x87 FPU

  • 歴史: Intel 8087コプロセッサに始まり、その後のx86プロセッサに統合されてきた伝統的なFPU命令セットです。
  • スタックベース: x87 FPUは、8つの80ビットレジスタ(ST(0)からST(7))を持つスタックベースのアーキテクチャを採用しています。演算はスタックのトップにある値に対して行われ、結果はスタックにプッシュされます。
  • 精度: 内部的には80ビットの拡張精度で計算を行いますが、メモリとのやり取りでは32ビット(単精度)または64ビット(倍精度)に丸められます。
  • 互換性: 非常に古いx86プロセッサでもサポートされているため、広範な互換性があります。

SSE2 (Streaming SIMD Extensions 2)

  • 導入: Intel Pentium 4プロセッサで導入されたSIMD(Single Instruction, Multiple Data)命令セットの一部です。AMD Athlon 64以降のプロセッサでもサポートされています。
  • レジスタベース: 8つの128ビットXMMレジスタ(XMM0からXMM7)を使用します。これらのレジスタは、複数の単精度または倍精度浮動小数点数を同時に格納し、単一の命令で並列処理できます。
  • SIMD: 複数のデータを同時に処理できるため、メディア処理、科学技術計算、3Dグラフィックスなど、データ並列性が高いタスクで高いパフォーマンスを発揮します。
  • 精度: 主に32ビット単精度(float)と64ビット倍精度(double)の浮動小数点演算をサポートします。
  • 現代の標準: 現在のほとんどのx86-64プロセッサはSSE2以降のSIMD命令セットをサポートしており、現代のコンパイラやライブラリでは浮動小数点演算のデフォルトとしてSSE2が使用されることが多いです。

cmd/distcmd/8g

  • cmd/dist: Goのビルドシステムの一部で、Goツールチェインの構築や環境変数の設定などを行います。このコミットでは、GO386 環境変数の読み込みと設定が追加されています。
  • cmd/8g: Goコンパイラのx86 (386) アーキテクチャ向けバックエンドです。Goのソースコードをx86アセンブリコードに変換する役割を担います。このコミットの主要な変更は、このコンパイラが浮動小数点演算のコード生成において、x87とSSE2のどちらを使用するかを切り替えるロジックが追加された点です。

技術的詳細

このコミットの技術的な核心は、Goコンパイラが浮動小数点演算のコードを生成する際に、ターゲットとなるFPU命令セットを動的に選択できるようにした点にあります。

  1. GO386 環境変数の導入:

    • src/cmd/dist/build.cGO386 環境変数を読み込むロジックが追加されました。
    • デフォルト値は "387" に設定されており、これは既存のGoの挙動を維持するためです。
    • ユーザーは GO386=sse のように設定することで、SSE2命令セットをターゲットに指定できます。
    • この環境変数は、コンパイル時にGoコンパイラ(cmd/8g)に渡されます。
  2. コンパイラでのFPUフレーバー選択:

    • src/cmd/gc/lex.c で、getgo386() 関数を呼び出し、GO386 の値が "sse" であるかどうかに基づいて use_sse というグローバル変数を設定します。
    • use_sse 変数は、cmd/8g 内のコード生成ロジック全体で参照され、x87とSSE2のどちらの命令を使用するかを決定します。
  3. 浮動小数点演算のコード生成ロジックの分離:

    • src/cmd/8g/cgen.csrc/cmd/8g/ggen.c において、浮動小数点演算のコード生成ロジックが大幅にリファクタリングされました。
    • cgen_float 関数が導入され、この関数内で use_sse の値に応じて cgen_float387 または cgen_floatsse が呼び出されるようになりました。
    • 同様に、浮動小数点数の比較演算を行う bgen_float 関数も導入され、x87sse のラベルに分岐するロジックが追加されました。
    • これにより、x87とSSE2それぞれの命令セットに特化したコード生成パスが明確に分離されました。
  4. レジスタ管理の拡張:

    • SSE2はXMMレジスタ(XMM0-XMM7)を使用するため、src/cmd/8g/gsubr.csrc/cmd/8g/reg.c でレジスタ割り当てロジックが更新され、これらの新しいレジスタが管理対象に追加されました。
    • D_X0 から D_X7 までの新しいレジスタ定義が src/cmd/8g/list.c に追加されています。
  5. 命令セットの対応:

    • src/cmd/8g/gsubr.cfoptoas 関数が拡張され、use_sse が真の場合にSSE2の浮動小数点命令(AADDSS, AMULSD, AUCOMISS など)に対応するアセンブリ命令コードを返すようになりました。
    • src/cmd/8g/gsubr.cgmove 関数も、浮動小数点数の移動(floatmove)を処理するためにリファクタリングされ、floatmove_387floatmove_sse に処理が委譲されるようになりました。これにより、x87のスタックベースの移動とSSE2のレジスタベースの移動が適切に処理されます。
  6. 最適化の追加:

    • src/cmd/8g/peep.c では、MOVSD 命令の最適化が追加されました。SSE2では、レジスタ間のMOVSD(単一の倍精度浮動小数点数を移動)をMOVAPD(2つの倍精度浮動小数点数を並列移動)に置き換えることで、プロセッサがより効率的に処理できる場合があります。これは、Goコンパイラが通常パックされたレジスタを使用しない場合でも、プロセッサの内部的な最適化を活用するためのものです。

この変更により、Goコンパイラは、より新しいCPUの機能を活用して浮動小数点演算のパフォーマンスを向上させる柔軟性を獲得しました。

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

このコミットにおけるコアとなるコードの変更は、主に以下のファイルに集中しています。

  • src/cmd/dist/build.c: GO386 環境変数の読み込みと設定。
  • src/cmd/gc/go.h: use_sse グローバル変数の宣言。
  • src/cmd/gc/lex.c: GO386 環境変数の値に基づいて use_sse を設定。
  • src/cmd/8g/gg.h: 浮動小数点コード生成関数のプロトタイプ宣言 (cgen_float, bgen_float)。
  • src/cmd/8g/cgen.c: 浮動小数点演算のコード生成を cgen_float に委譲するロジックの追加。
  • src/cmd/8g/ggen.c: cgen_float, cgen_float387, cgen_floatsse, bgen_float の実装。ここでx87とSSE2の具体的なコード生成ロジックが記述されています。
  • src/cmd/8g/gsubr.c: 浮動小数点命令のアセンブリコード変換 (foptoas) のSSE2対応、レジスタ管理の拡張、gmove 関数における浮動小数点数移動の分離 (floatmove, floatmove_387, floatmove_sse)。
  • src/cmd/8g/reg.c: レジスタ割り当てロジックの更新とSSE2レジスタの追加。
  • src/cmd/8g/peep.c: MOVSD 命令の最適化 (MOVAPD への変換)。

コアとなるコードの解説

src/cmd/dist/build.c

// ...
char *go386; // 新しい環境変数

// ...

void
init(void)
{
    // ...
    xgetenv(&b, "GO386");
    if(b.len == 0)
        writestr(&b, "387"); // デフォルトは387
    go386 = btake(&b);

    // ...
    xsetenv("GO386", go386); // 環境変数を設定
    // ...
}

// ...

void
install(char *dir)
{
    // ...
    // コンパイル時にGO386の値を定義として渡す
    vadd(&compile, bprintf(&b, "-DGO386=\\\"%s\\\""", go386));
    // ...
}

cmd/dist はビルド時に GO386 環境変数を読み込み、設定します。もし設定されていなければデフォルトで "387" を使用します。この値はコンパイラに -DGO386="..." として渡され、コンパイル時のマクロ定義として利用されます。

src/cmd/gc/lex.c

// ...
#include "go.h" // use_sse の宣言を含む

// ...

void
main(int argc, char *argv[])
{
    // ...
    goarch = thestring; // "386" など
    use_sse = strcmp(getgo386(), "sse") == 0; // GO386が"sse"ならtrue
    // ...
}

Goコンパイラのフロントエンドである gc/lex.c は、GO386 環境変数の値を取得し、それが "sse" であるかどうかに基づいて use_sse グローバル変数を設定します。この use_sse 変数が、バックエンドである cmd/8g で浮動小数点コード生成の分岐を制御します。

src/cmd/8g/ggen.c

// ...
static void cgen_float387(Node *n, Node *res);
static void cgen_floatsse(Node *n, Node *res);

/*
 * generate floating-point operation.
 */
void
cgen_float(Node *n, Node *res)
{
    // ...
    // 浮動小数点比較演算の特殊処理
    switch(n->op) {
    case OEQ: case ONE: case OLT: case OLE: case OGE:
        // ... 比較結果をbool値として生成するロジック ...
        return;
    case OPLUS: // 単項プラスはそのまま
        cgen(nl, res);
        return;
    case OCONV: // 型変換
        // ... 型変換ロジック ...
        return;
    }

    // use_sse の値に応じて適切なFPUコード生成関数を呼び出す
    if(use_sse)
        cgen_floatsse(n, res);
    else
        cgen_float387(n, res);
}

// floating-point.  387 (not SSE2)
static void
cgen_float387(Node *n, Node *res)
{
    // ... x87 FPU命令を使ったコード生成ロジック ...
    // 例: FADD, FMUL, FDIV などのスタックベース命令
}

static void
cgen_floatsse(Node *n, Node *res)
{
    // ... SSE2命令を使ったコード生成ロジック ...
    // 例: ADDSS, MULSD, DIVSS などのレジスタベース命令
}

void
bgen_float(Node *n, int true, int likely, Prog *to)
{
    // ...
    if(!true) { // 条件が偽の場合の処理
        // ...
        bgen_float(n, 1, -likely, p2); // 再帰呼び出し
        // ...
        return;
    }

    if(use_sse)
        goto sse; // SSE2パスへ
    else
        goto x87; // x87パスへ

x87:
    // ... x87 FPU命令を使った比較コード生成ロジック ...
    // 例: FUCOMIP, FSTSW, SAHF など
    goto ret;

sse:
    // ... SSE2命令を使った比較コード生成ロジック ...
    // 例: UCOMISS, UCOMISD など
    // ...
ret:
    // ... 比較結果に基づく分岐ロジック ...
}

ggen.c は、浮動小数点演算のコード生成の核心部分です。cgen_float 関数は、use_sse の値に基づいて、x87 FPU (cgen_float387) またはSSE2 (cgen_floatsse) のどちらの命令セットを使用するかを決定します。これにより、GoコンパイラはターゲットCPUのFPU機能に合わせた最適なコードを生成できます。bgen_float も同様に、浮動小数点数の比較と条件分岐のコードをFPUフレーバーに応じて生成します。

src/cmd/8g/gsubr.c

// ...
int
foptoas(int op, Type *t, int flg)
{
    // ...
    if(use_sse)
        goto sse; // SSE2パスへ

    // ... x87 FPU命令のアセンブリコード変換ロジック ...

sse:
    switch(CASE(op, et)) {
    // ... SSE2命令のアセンブリコード変換ロジック ...
    case CASE(OCMP, TFLOAT32): return AUCOMISS;
    case CASE(OADD, TFLOAT64): return AADDSD;
    // ...
    }
}

// ... レジスタ管理の拡張 ...
// D_X0 から D_X7 までのSSEレジスタが追加され、regalloc などで利用可能になる

static void floatmove(Node *f, Node *t);
static void floatmove_387(Node *f, Node *t);
static void floatmove_sse(Node *f, Node *t);

void
gmove(Node *f, Node *t)
{
    // ...
    if(isfloat[ft] || isfloat[tt]) { // 浮動小数点数の移動の場合
        floatmove(f, t); // floatmove に処理を委譲
        return;
    }
    // ...
}

static void
floatmove(Node *f, Node *t)
{
    // ...
    if(use_sse)
        floatmove_sse(f, t);
    else
        floatmove_387(f, t);
    return;
    // ...
}

static void
floatmove_387(Node *f, Node *t)
{
    // ... x87 FPU命令を使った浮動小数点数移動ロジック ...
}

static void
floatmove_sse(Node *f, Node *t)
{
    // ... SSE2命令を使った浮動小数点数移動ロジック ...
    // 例: AMOVSS, AMOVSD, ACVTSS2SD など
}

gsubr.c は、Goの抽象的な演算子を具体的なアセンブリ命令に変換する役割を担っています。foptoas 関数は、use_sse に応じてx87またはSSE2の浮動小数点命令のアセンブリコードを返します。また、gmove 関数は、浮動小数点数のデータ移動を floatmove に委譲し、さらに floatmove_387floatmove_sse に分岐させることで、それぞれのFPUフレーバーに合わせた効率的なデータ転送を実現しています。

関連リンク

参考にした情報源リンク

  • x87 FPU:
  • SSE2:
  • SIMD:
  • Go Compiler Internals (General):
    • Goのコンパイラに関する公式ドキュメントやブログ記事は、Goのバージョンアップに伴いURLが変更される可能性があるため、具体的なリンクは避け、一般的な情報源として記載します。Goの公式ブログやドキュメントサイトで「compiler」「internals」「x86」などのキーワードで検索すると、関連情報が見つかる可能性があります。
  • Go Source Code:
    • Goのソースコード自体が最も正確な情報源です。GitHubリポジトリ (https://github.com/golang/go) を参照しました。

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

このコミットは、Goコンパイラ(cmd/8g)およびビルドツール(cmd/dist)において、x86アーキテクチャ(386)向けの浮動小数点演算ユニット(FPU)のコード生成フレーバーを選択できるようにするものです。具体的には、従来のx87 FPU命令セット(通称「387」)と、より新しいSSE2(Streaming SIMD Extensions 2)命令セットのどちらを使用するかを、新しい環境変数 GO386 を介して指定できるようになります。これにより、特定のCPU環境やパフォーマンス要件に応じたコード生成が可能になります。

コミット

commit 9afb34b42e5d7568dab3a12f137aa80314b2c6f8
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date:   Wed Jan 2 22:55:23 2013 +0100

    cmd/dist, cmd/8g: implement GO386=387/sse to choose FPU flavour.
    
    A new environment variable GO386 is introduced to choose between
    code generation targeting 387 or SSE2. No auto-detection is
    performed and the setting defaults to 387 to preserve previous
    behaviour.
    
    The patch is a reorganization of CL6549052 by rsc.
    
    Fixes #3912.
    
    R=minux.ma, rsc
    CC=golang-dev
    https://golang.org/cl/6962043

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

https://github.com/golang/go/commit/9afb34b42e5d7568dab3a12f137aa80314b2c6f8

元コミット内容

このコミットは、rsc (おそらくRuss Cox) による CL6549052 の再編成(reorganization)であると明記されています。元のコミット内容自体は直接提供されていませんが、このコミットがその内容を整理し、より洗練された形で導入したものであることが示唆されます。

変更の背景

この変更の背景には、主に以下の点が挙げられます。

  1. FPU命令セットの多様性: x86アーキテクチャには、浮動小数点演算を行うための複数の命令セットが存在します。歴史的にはx87 FPUが広く使われてきましたが、Pentium III以降のCPUではSSE(Streaming SIMD Extensions)命令セットが導入され、特にSSE2は64ビット浮動小数点演算(double-precision)をサポートし、より高速なベクトル演算能力を提供します。
  2. パフォーマンスの最適化: SSE2はx87に比べて、より効率的な浮動小数点演算を可能にします。特に、複数の浮動小数点数を同時に処理できるSIMD(Single Instruction, Multiple Data)機能は、科学技術計算やグラフィックス処理など、浮動小数点演算が多用されるアプリケーションにおいて大きな性能向上をもたらします。
  3. 互換性の維持: 既存のGoプログラムやライブラリがx87 FPUを前提としてコンパイルされている場合、突然SSE2に切り替えると互換性の問題が生じる可能性があります。そのため、ユーザーが明示的にFPUフレーバーを選択できるメカニズムが必要とされました。
  4. Issue #3912の解決: コミットメッセージに「Fixes #3912」とありますが、このIssueはGoの内部トラッカーのものであり、その具体的な内容は公開されていません。しかし、コミット内容から、FPUの選択肢やパフォーマンスに関する課題を解決することを目的としていると推測されます。

前提知識の解説

浮動小数点演算ユニット (FPU)

FPUは、CPU内で浮動小数点数(実数)の計算(加算、減算、乗算、除算など)を専門に行う部分です。整数演算を行うALU(Arithmetic Logic Unit)とは異なり、より複雑な計算を効率的に処理します。

x87 FPU

  • 歴史: Intel 8087コプロセッサに始まり、その後のx86プロセッサに統合されてきた伝統的なFPU命令セットです。
  • スタックベース: x87 FPUは、8つの80ビットレジスタ(ST(0)からST(7))を持つスタックベースのアーキテクチャを採用しています。演算はスタックのトップにある値に対して行われ、結果はスタックにプッシュされます。
  • 精度: 内部的には80ビットの拡張精度で計算を行いますが、メモリとのやり取りでは32ビット(単精度)または64ビット(倍精度)に丸められます。
  • 互換性: 非常に古いx86プロセッサでもサポートされているため、広範な互換性があります。

SSE2 (Streaming SIMD Extensions 2)

  • 導入: Intel Pentium 4プロセッサで導入されたSIMD(Single Instruction, Multiple Data)命令セットの一部です。AMD Athlon 64以降のプロセッサでもサポートされています。
  • レジスタベース: 8つの128ビットXMMレジスタ(XMM0からXMM7)を使用します。これらのレジスタは、複数の単精度または倍精度浮動小数点数を同時に格納し、単一の命令で並列処理できます。
  • SIMD: 複数のデータを同時に処理できるため、メディア処理、科学技術計算、3Dグラフィックスなど、データ並列性が高いタスクで高いパフォーマンスを発揮します。
  • 精度: 主に32ビット単精度(float)と64ビット倍精度(double)の浮動小数点演算をサポートします。
  • 現代の標準: 現在のほとんどのx86-64プロセッサはSSE2以降のSIMD命令セットをサポートしており、現代のコンパイラやライブラリでは浮動小数点演算のデフォルトとしてSSE2が使用されることが多いです。

cmd/distcmd/8g

  • cmd/dist: Goのビルドシステムの一部で、Goツールチェインの構築や環境変数の設定などを行います。このコミットでは、GO386 環境変数の読み込みと設定が追加されています。
  • cmd/8g: Goコンパイラのx86 (386) アーキテクチャ向けバックエンドです。Goのソースコードをx86アセンブリコードに変換する役割を担います。このコミットの主要な変更は、このコンパイラが浮動小数点演算のコード生成において、x87とSSE2のどちらを使用するかを切り替えるロジックが追加された点です。

技術的詳細

このコミットの技術的な核心は、Goコンパイラが浮動小数点演算のコードを生成する際に、ターゲットとなるFPU命令セットを動的に選択できるようにした点にあります。

  1. GO386 環境変数の導入:

    • src/cmd/dist/build.cGO386 環境変数を読み込むロジックが追加されました。
    • デフォルト値は "387" に設定されており、これは既存のGoの挙動を維持するためです。
    • ユーザーは GO386=sse のように設定することで、SSE2命令セットをターゲットに指定できます。
    • この環境変数は、コンパイル時にGoコンパイラ(cmd/8g)に渡されます。
  2. コンパイラでのFPUフレーバー選択:

    • src/cmd/gc/lex.c で、getgo386() 関数を呼び出し、GO386 の値が "sse" であるかどうかに基づいて use_sse というグローバル変数を設定します。
    • use_sse 変数は、cmd/8g 内のコード生成ロジック全体で参照され、x87とSSE2のどちらの命令を使用するかを決定します。
  3. 浮動小数点演算のコード生成ロジックの分離:

    • src/cmd/8g/cgen.csrc/cmd/8g/ggen.c において、浮動小数点演算のコード生成ロジックが大幅にリファクタリングされました。
    • cgen_float 関数が導入され、この関数内で use_sse の値に応じて cgen_float387 または cgen_floatsse が呼び出されるようになりました。
    • 同様に、浮動小数点数の比較演算を行う bgen_float 関数も導入され、x87sse のラベルに分岐するロジックが追加されました。
    • これにより、x87とSSE2それぞれの命令セットに特化したコード生成パスが明確に分離されました。
  4. レジスタ管理の拡張:

    • SSE2はXMMレジスタ(XMM0-XMM7)を使用するため、src/cmd/8g/gsubr.csrc/cmd/8g/reg.c でレジスタ割り当てロジックが更新され、これらの新しいレジスタが管理対象に追加されました。
    • D_X0 から D_X7 までの新しいレジスタ定義が src/cmd/8g/list.c に追加されています。
  5. 命令セットの対応:

    • src/cmd/8g/gsubr.cfoptoas 関数が拡張され、use_sse が真の場合にSSE2の浮動小数点命令(AADDSS, AMULSD, AUCOMISS など)に対応するアセンブリ命令コードを返すようになりました。
    • src/cmd/8g/gsubr.cgmove 関数も、浮動小数点数の移動(floatmove)を処理するためにリファクタリングされ、floatmove_387floatmove_sse に処理が委譲されるようになりました。これにより、x87のスタックベースの移動とSSE2のレジスタベースの移動が適切に処理されます。
  6. 最適化の追加:

    • src/cmd/8g/peep.c では、MOVSD 命令の最適化が追加されました。SSE2では、レジスタ間のMOVSD(単一の倍精度浮動小数点数を移動)をMOVAPD(2つの倍精度浮動小数点数を並列移動)に置き換えることで、プロセッサがより効率的に処理できる場合があります。これは、Goコンパイラが通常パックされたレジスタを使用しない場合でも、プロセッサの内部的な最適化を活用するためのものです。

この変更により、Goコンパイラは、より新しいCPUの機能を活用して浮動小数点演算のパフォーマンスを向上させる柔軟性を獲得しました。

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

このコミットにおけるコアとなるコードの変更は、主に以下のファイルに集中しています。

  • src/cmd/dist/build.c: GO386 環境変数の読み込みと設定。
  • src/cmd/gc/go.h: use_sse グローバル変数の宣言。
  • src/cmd/gc/lex.c: GO386 環境変数の値に基づいて use_sse を設定。
  • src/cmd/8g/gg.h: 浮動小数点コード生成関数のプロトタイプ宣言 (cgen_float, bgen_float)。
  • src/cmd/8g/cgen.c: 浮動小数点演算のコード生成を cgen_float に委譲するロジックの追加。
  • src/cmd/8g/ggen.c: cgen_float, cgen_float387, cgen_floatsse, bgen_float の実装。ここでx87とSSE2の具体的なコード生成ロジックが記述されています。
  • src/cmd/8g/gsubr.c: 浮動小数点命令のアセンブリコード変換 (foptoas) のSSE2対応、レジスタ管理の拡張、gmove 関数における浮動小数点数移動の分離 (floatmove, floatmove_387, floatmove_sse)。
  • src/cmd/8g/reg.c: レジスタ割り当てロジックの更新とSSE2レジスタの追加。
  • src/cmd/8g/peep.c: MOVSD 命令の最適化 (MOVAPD への変換)。

コアとなるコードの解説

src/cmd/dist/build.c

// ...
char *go386; // 新しい環境変数

// ...

void
init(void)
{
    // ...
    xgetenv(&b, "GO386");
    if(b.len == 0)
        writestr(&b, "387"); // デフォルトは387
    go386 = btake(&b);

    // ...
    xsetenv("GO386", go386); // 環境変数を設定
    // ...
}

// ...

void
install(char *dir)
{
    // ...
    // コンパイル時にGO386の値を定義として渡す
    vadd(&compile, bprintf(&b, "-DGO386=\\\"%s\\\""", go386));
    // ...
}

cmd/dist はビルド時に GO386 環境変数を読み込み、設定します。もし設定されていなければデフォルトで "387" を使用します。この値はコンパイラに -DGO386="..." として渡され、コンパイル時のマクロ定義として利用されます。

src/cmd/gc/lex.c

// ...
#include "go.h" // use_sse の宣言を含む

// ...

void
main(int argc, char *argv[])
{
    // ...
    goarch = thestring; // "386" など
    use_sse = strcmp(getgo386(), "sse") == 0; // GO386が"sse"ならtrue
    // ...
}

Goコンパイラのフロントエンドである gc/lex.c は、GO386 環境変数の値を取得し、それが "sse" であるかどうかに基づいて use_sse グローバル変数を設定します。この use_sse 変数が、バックエンドである cmd/8g で浮動小数点コード生成の分岐を制御します。

src/cmd/8g/ggen.c

// ...
static void cgen_float387(Node *n, Node *res);
static void cgen_floatsse(Node *n, Node *res);

/*
 * generate floating-point operation.
 */
void
cgen_float(Node *n, Node *res)
{
    // ...
    // 浮動小数点比較演算の特殊処理
    switch(n->op) {
    case OEQ: case ONE: case OLT: case OLE: case OGE:
        // ... 比較結果をbool値として生成するロジック ...
        return;
    case OPLUS: // 単項プラスはそのまま
        cgen(nl, res);
        return;
    case OCONV: // 型変換
        // ... 型変換ロジック ...
        return;
    }

    // use_sse の値に応じて適切なFPUコード生成関数を呼び出す
    if(use_sse)
        cgen_floatsse(n, res);
    else
        cgen_float387(n, res);
}

// floating-point.  387 (not SSE2)
static void
cgen_float387(Node *n, Node *res)
{
    // ... x87 FPU命令を使ったコード生成ロジック ...
    // 例: FADD, FMUL, FDIV などのスタックベース命令
}

static void
cgen_floatsse(Node *n, Node *res)
{
    // ... SSE2命令を使ったコード生成ロジック ...
    // 例: ADDSS, MULSD, DIVSS などのレジスタベース命令
}

void
bgen_float(Node *n, int true, int likely, Prog *to)
{
    // ...
    if(!true) { // 条件が偽の場合の処理
        // ...
        bgen_float(n, 1, -likely, p2); // 再帰呼び出し
        // ...
        return;
    }

    if(use_sse)
        goto sse; // SSE2パスへ
    else
        goto x87; // x87パスへ

x87:
    // ... x87 FPU命令を使った比較コード生成ロジック ...
    // 例: FUCOMIP, FSTSW, SAHF など
    goto ret;

sse:
    // ... SSE2命令を使った比較コード生成ロジック ...
    // 例: UCOMISS, UCOMISD など
    // ...
ret:
    // ... 比較結果に基づく分岐ロジック ...
}

ggen.c は、浮動小数点演算のコード生成の核心部分です。cgen_float 関数は、use_sse の値に基づいて、x87 FPU (cgen_float387) またはSSE2 (cgen_floatsse) のどちらの命令セットを使用するかを決定します。これにより、GoコンパイラはターゲットCPUのFPU機能に合わせた最適なコードを生成できます。bgen_float も同様に、浮動小数点数の比較と条件分岐のコードをFPUフレーバーに応じて生成します。

src/cmd/8g/gsubr.c

// ...
int
foptoas(int op, Type *t, int flg)
{
    // ...
    if(use_sse)
        goto sse; // SSE2パスへ

    // ... x87 FPU命令のアセンブリコード変換ロジック ...

sse:
    switch(CASE(op, et)) {
    // ... SSE2命令のアセンブリコード変換ロジック ...
    case CASE(OCMP, TFLOAT32): return AUCOMISS;
    case CASE(OADD, TFLOAT64): return AADDSD;
    // ...
    }
}

// ... レジスタ管理の拡張 ...
// D_X0 から D_X7 までのSSEレジスタが追加され、regalloc などで利用可能になる

static void floatmove(Node *f, Node *t);
static void floatmove_387(Node *f, Node *t);
static void floatmove_sse(Node *f, Node *t);

void
gmove(Node *f, Node *t)
{
    // ...
    if(isfloat[ft] || isfloat[tt]) { // 浮動小数点数の移動の場合
        floatmove(f, t); // floatmove に処理を委譲
        return;
    }
    // ...
}

static void
floatmove(Node *f, Node *t)
{
    // ...
    if(use_sse)
        floatmove_sse(f, t);
    else
        floatmove_387(f, t);
    return;
    // ...
}

static void
floatmove_387(Node *f, Node *t)
{
    // ... x87 FPU命令を使った浮動小数点数移動ロジック ...
}

static void
floatmove_sse(Node *f, Node *t)
{
    // ... SSE2命令を使った浮動小数点数移動ロジック ...
    // 例: AMOVSS, AMOVSD, ACVTSS2SD など
}

gsubr.c は、Goの抽象的な演算子を具体的なアセンブリ命令に変換する役割を担っています。foptoas 関数は、use_sse に応じてx87またはSSE2の浮動小数点命令のアセンブリコードを返します。また、gmove 関数は、浮動小数点数のデータ移動を floatmove に委譲し、さらに floatmove_387floatmove_sse に分岐させることで、それぞれのFPUフレーバーに合わせた効率的なデータ転送を実現しています。

関連リンク

  • Go Issue #3912: このコミットが解決した課題ですが、Goの内部トラッカーのIssueであり、その具体的な内容は公開されていません。
  • Go Change List 6962043: https://golang.org/cl/6962043 (このコミットのコードレビューページ)
  • Go Change List 6549052: (直接のリンクは見つかりませんでしたが、このコミットが再編成した元の変更)

参考にした情報源リンク

  • x87 FPU:
  • SSE2:
  • SIMD:
  • Go Compiler Internals (General):
    • Goのコンパイラに関する公式ドキュメントやブログ記事は、Goのバージョンアップに伴いURLが変更される可能性があるため、具体的なリンクは避け、一般的な情報源として記載します。Goの公式ブログやドキュメントサイトで「compiler」「internals」「x86」などのキーワードで検索すると、関連情報が見つかる可能性があります。
  • Go Source Code:
    • Goのソースコード自体が最も正確な情報源です。GitHubリポジトリ (https://github.com/golang/go) を参照しました。