[インデックス 14794] ファイルの概要
このコミットは、Go言語のビルドツールである cmd/dist
において、x86アーキテクチャ(特に32ビット版のGOARCH=386
)向けにSSE (Streaming SIMD Extensions) 命令セットのサポートを自動検出する機能を追加するものです。これにより、コンパイル時にSSE2命令が利用可能なCPUであれば、Goコンパイラがより最適化されたコードを生成できるようになります。
コミット
commit a4e08183d56aa3e6c524cadb71671c40495f45a1
Author: Russ Cox <rsc@golang.org>
Date: Fri Jan 4 10:59:10 2013 -0500
cmd/dist: sse auto-detect
R=golang-dev, dsymonds, minux.ma, iant, alex.brainman
CC=golang-dev
https://golang.org/cl/7035055
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a4e08183d56aa3e6c524cadb71671c40495f45a1
元コミット内容
このコミットは、Goのビルドシステムの一部である cmd/dist
に、SSE命令セットの自動検出機能を追加します。具体的には、GO386
環境変数のデフォルト値を決定する際に、実行環境のCPUがSSE2をサポートしているかどうかをチェックし、サポートしていれば "sse"
を、そうでなければ従来の "387"
を設定するように変更します。
変更されたファイルは以下の通りです。
src/cmd/dist/a.h
:cansse()
関数のプロトタイプ宣言を追加。src/cmd/dist/build.c
:init()
関数内でGO386
の設定ロジックを変更し、cansse()
の結果に基づいて"sse"
または"387"
を選択するように修正。src/cmd/dist/plan9.c
: Plan 9環境向けのcansse()
実装を追加。ここでは常にSSE非対応として0
を返す。src/cmd/dist/unix.c
: Unix系OS向けのcansse()
実装を追加。cpuid
命令を用いてSSE2のサポートを検出。src/cmd/dist/windows.c
: Windows向けのcansse()
実装を追加。cpuid
命令を用いてSSE2のサポートを検出。
変更の背景
Go言語は、様々なアーキテクチャで動作するように設計されています。x86アーキテクチャにおいては、初期のIntel 80387コプロセッサから始まり、Pentium IIIで導入されたSSE、Pentium 4で導入されたSSE2など、時代とともに新しい命令セットが追加されてきました。これらの新しい命令セットは、浮動小数点演算やSIMD (Single Instruction, Multiple Data) 処理において、より高いパフォーマンスを提供します。
Goのビルドプロセスでは、GO386
環境変数(またはそれに相当する内部設定)が、32ビットx86アーキテクチャ向けのコンパイル時にどの命令セットをターゲットにするかを決定していました。以前は、この設定が明示的に行われない場合、デフォルトで「387」という、SSE命令を考慮しない古い命令セットをターゲットにしていました。
しかし、2013年当時、ほとんどの現代的なx86 CPUはSSE2をサポートしており、Goがこれらの命令を活用できれば、特に数値計算やメディア処理などにおいて、より高速な実行が可能になります。このコミットの背景には、Goのコンパイル済みバイナリが、実行環境のCPU能力を最大限に活用できるように、SSE2のサポートを自動的に検出し、適切なコンパイルオプションを選択するという目的があります。これにより、ユーザーが手動で GO386
を設定する必要がなくなり、利便性とパフォーマンスが向上します。
前提知識の解説
1. cmd/dist
cmd/dist
は、Go言語のソースコードからGoツールチェイン自体をビルドするためのプログラムです。Goのコンパイラ、リンカ、アセンブラなどのツール群を構築する際に使用されます。これはGoのブートストラッププロセスの一部であり、Goのバージョンアップやクロスコンパイル環境の構築において重要な役割を担います。
2. SSE (Streaming SIMD Extensions) と SSE2
SSEは、Intelが開発したSIMD命令セットの拡張機能です。SIMDは、単一の命令で複数のデータ要素に対して同じ操作を同時に実行できるため、特に浮動小数点演算やマルチメディア処理、科学技術計算などで高いパフォーマンスを発揮します。
- SSE (SSE1): Pentium IIIで導入され、主に単精度浮動小数点数(32ビット)のSIMD演算をサポートします。8つの128ビットXMMレジスタを使用します。
- SSE2: Pentium 4で導入され、SSEをさらに拡張したものです。単精度だけでなく、倍精度浮動小数点数(64ビット)のSIMD演算もサポートし、さらに整数演算のSIMD機能も強化されました。現代のほとんどのx86-64 CPUはSSE2をサポートしています。
Go言語のコンパイラがSSE/SSE2命令を利用できるようにコンパイルされると、Goプログラム内でこれらの命令が活用され、特定の処理が高速化される可能性があります。
3. GO386
環境変数
GO386
は、Go言語のビルド時に32ビットx86アーキテクチャ(GOARCH=386
)向けのコード生成オプションを指定するための環境変数です。この変数の値によって、コンパイラが生成するアセンブリコードの種類が変わります。
GO386=387
: これは、Intel 80387コプロセッサ(またはそれ以降のCPUに内蔵されたFPU)が提供する浮動小数点演算命令のみを使用することを意味します。SSE命令は使用されません。これは最も互換性の高いモードですが、パフォーマンスは劣ります。GO386=sse
: これは、SSE2命令セットが利用可能であることを前提とし、コンパイラがSSE2命令を活用してコードを生成することを意味します。これにより、浮動小数点演算やSIMD処理が高速化されます。
このコミット以前は、GO386
が設定されていない場合、デフォルトで 387
が使用されていました。このコミットにより、自動的に sse
が選択される可能性が生まれました。
4. CPUID 命令
CPUID命令は、x86アーキテクチャのプロセッサが提供する特殊な命令で、CPUの機能や情報をプログラムから問い合わせるために使用されます。この命令を実行すると、CPUのモデル、ブランド、サポートする命令セット(SSE, AVXなど)、キャッシュ情報などがレジスタに格納されます。
CPUID命令は、特定の入力値(EAXレジスタに設定)に応じて異なる情報を返します。例えば、EAX=1 を指定してCPUIDを実行すると、プロセッサのバージョン情報と機能情報(EDXレジスタのビットフラグ)が返されます。SSE2のサポートは、この機能情報ビットの特定の場所(EDXレジスタのビット26)で示されます。
技術的詳細
このコミットの核となる技術的変更は、cansse()
という新しい関数の導入と、その関数が cpuid
命令を使用してCPUのSSE2サポートを検出するロジックです。
-
cansse()
関数の導入:src/cmd/dist/a.h
でbool cansse(void);
が宣言され、各OS(Unix系、Windows、Plan 9)固有のファイルで実装されます。この関数は、現在のCPUがSSE2命令セットをサポートしている場合にtrue
を、そうでない場合にfalse
を返します。 -
build.c
でのGO386
の自動設定:src/cmd/dist/build.c
のinit()
関数内で、GO386
環境変数が設定されていない場合に、cansse()
の結果に基づいてデフォルト値を決定するロジックが追加されました。if(b.len == 0) { if(cansse()) bwritestr(&b, "sse"); else bwritestr(&b, "387"); }
これにより、
GO386
が明示的に指定されていない場合でも、システムがSSE2をサポートしていれば自動的に"sse"
が選択され、GoコンパイラがSSE2命令を活用するようになります。 -
OSごとの
cansse()
実装:-
src/cmd/dist/unix.c
およびsrc/cmd/dist/windows.c
: これらのファイルでは、__cpuid
(またはcpuid
) というヘルパー関数が定義されています。これはインラインアセンブリ (asm volatile("cpuid" ...)
) を使用して、CPUのCPUID
命令を実行します。cansse()
関数内では、__cpuid(info, 1)
を呼び出して、EAX=1 のCPUID情報を取得します。この情報のうち、info[3]
(EDXレジスタに対応) のビット26がSSE2のサポートを示すフラグです。return (info[3] & (1<<26)) != 0;
この行は、EDXレジスタのビット26がセットされているかどうかをチェックし、セットされていればSSE2がサポートされていると判断します。 -
src/cmd/dist/plan9.c
: Plan 9はGoの主要なターゲットOSの一つですが、この環境ではCPUID命令への直接的なアクセスが困難であるか、あるいはSSEの利用が一般的ではないため、cansse()
は常に0
(false) を返します。bool cansse(void) { // if we had access to cpuid, could answer this question // less conservatively. return 0; }
これは、Plan 9環境ではSSE自動検出を無効にするという設計判断を示しています。
-
この自動検出機能は、Goのビルドシステムが実行される環境のCPU能力を動的に判断し、それに基づいて最適なコンパイルオプションを選択することを可能にします。これにより、Goバイナリのパフォーマンスと互換性のバランスが向上します。
コアとなるコードの変更箇所
src/cmd/dist/a.h
--- a/src/cmd/dist/a.h
+++ b/src/cmd/dist/a.h
@@ -123,6 +123,7 @@ void runv(Buf *b, char *dir, int mode, Vec *argv);
void bgrunv(char *dir, int mode, Vec *argv);
void bgwait(void);
bool streq(char*, char*);
+bool cansse(void);
void writefile(Buf*, char*, int);
void xatexit(void (*f)(void));
void xexit(int);
src/cmd/dist/build.c
--- a/src/cmd/dist/build.c
+++ b/src/cmd/dist/build.c
@@ -104,8 +104,12 @@ init(void)
goarm = btake(&b);
xgetenv(&b, "GO386");
- if(b.len == 0)
- bwritestr(&b, "387");
+ if(b.len == 0) {
+ if(cansse())
+ bwritestr(&b, "sse");
+ else
+ bwritestr(&b, "387");
+ }
go386 = btake(&b);
p = bpathf(&b, "%s/include/u.h", goroot);
src/cmd/dist/plan9.c
--- a/src/cmd/dist/plan9.c
+++ b/src/cmd/dist/plan9.c
@@ -758,4 +758,12 @@ xtryexecfunc(void (*f)(void))\n return 0; // suffice for now\n }\n \n+bool\n+cansse(void)\n+{\n+\t// if we had access to cpuid, could answer this question\n+\t// less conservatively.\n+\treturn 0;\n+}\n+\n #endif // PLAN9
src/cmd/dist/unix.c
--- a/src/cmd/dist/unix.c
+++ b/src/cmd/dist/unix.c
@@ -741,6 +741,7 @@ xsamefile(char *f1, char *f2)\n \n sigjmp_buf sigill_jmpbuf;\n static void sigillhand(int);\n+\n // xtryexecfunc tries to execute function f, if any illegal instruction\n // signal received in the course of executing that function, it will\n // return 0, otherwise it will return 1.\n@@ -757,6 +758,7 @@ xtryexecfunc(void (*f)(void))\n \tsignal(SIGILL, SIG_DFL);\n \treturn r;\n }\n+\n // SIGILL handler helper\n static void\n sigillhand(int signum)\n@@ -765,5 +767,26 @@ sigillhand(int signum)\n \tsiglongjmp(sigill_jmpbuf, 1);\n }\n \n+static void\n+__cpuid(int dst[4], int ax)\n+{\n+#if defined(__i386__) || defined(__x86_64__)\n+\tasm volatile("cpuid"\n+\t\t: "=a" (dst[0]), "=b" (dst[1]), "=c" (dst[2]), "=d" (dst[3])\n+\t\t: "0" (ax));\n+#else\n+\tdst[0] = dst[1] = dst[2] = dst[3] = 0;\n+#endif\n+}\n+\n+bool\n+cansse(void)\n+{\n+\tint info[4];\n+\t\n+\t__cpuid(info, 1);\n+\treturn (info[3] & (1<<26)) != 0;\t// SSE2\n+}\n+\n #endif // PLAN9
#endif // __WINDOWS__
src/cmd/dist/windows.c
--- a/src/cmd/dist/windows.c
+++ b/src/cmd/dist/windows.c
@@ -971,4 +971,29 @@ xtryexecfunc(void (*f)(void))\n \treturn 0; // suffice for now\n }\n \n+static void\n+cpuid(int dst[4], int ax)\n+{\n+\t// NOTE: This asm statement is for mingw.\n+\t// If we ever support MSVC, use __cpuid(dst, ax)\n+\t// to use the built-in.\n+#if defined(__i386__) || defined(__x86_64__)\n+\tasm volatile("cpuid"\n+\t\t: "=a" (dst[0]), "=b" (dst[1]), "=c" (dst[2]), "=d" (dst[3])\n+\t\t: "0" (ax));\n+#else\n+\tdst[0] = dst[1] = dst[2] = dst[3] = 0;\n+#endif\n+}\n+\n+bool\n+cansse(void)\n+{\n+\tint info[4];\n+\t\n+\tcpuid(info, 1);\n+\treturn (info[3] & (1<<26)) != 0;\t// SSE2\n+}\n+\n+\n #endif // __WINDOWS__
コアとなるコードの解説
このコミットの核心は、src/cmd/dist/build.c
における GO386
環境変数の設定ロジックの変更と、それを支える cansse()
関数の実装です。
-
src/cmd/dist/build.c
の変更:init()
関数は、Goのビルドプロセス初期化時に様々な環境変数を設定します。この中でGO386
の設定部分が変更されました。 変更前:GO386
が設定されていなければ、無条件に"387"
をデフォルトとしていました。 変更後:GO386
が設定されていない場合、新しく追加されたcansse()
関数を呼び出します。cansse()
がtrue
を返した場合(つまり、SSE2がサポートされている場合)、GO386
に"sse"
を設定します。cansse()
がfalse
を返した場合(SSE2がサポートされていないか、検出できない場合)、従来通り"387"
を設定します。 この変更により、Goのビルドシステムは、実行環境のCPU能力を自動的に判断し、最適なコンパイルオプションを選択できるようになりました。
-
cansse()
関数の実装 (Unix系および Windows):src/cmd/dist/unix.c
とsrc/cmd/dist/windows.c
に実装されたcansse()
関数は、CPUの機能を検出するためにCPUID
命令を利用します。__cpuid
(またはcpuid
) ヘルパー関数は、インラインアセンブリを使用してCPUID
命令を実行します。CPUID
命令は、EAXレジスタに特定の値を設定して呼び出すことで、CPUに関する様々な情報を取得できます。ここではax
(EAX) に1
を設定して呼び出しています。EAX=1 のCPUID
呼び出しは、プロセッサのバージョン情報と機能情報を返します。- 返された情報(
info
配列に格納される)のうち、info[3]
はEDXレジスタに対応します。EDXレジスタのビット26は、SSE2命令セットのサポートを示すフラグです。 return (info[3] & (1<<26)) != 0;
という式は、EDXレジスタのビット26がセットされているかどうかをチェックしています。ビット26がセットされていれば、SSE2がサポートされていると判断しtrue
を返します。
このメカニズムにより、Goのビルドシステムは、ユーザーが明示的に GO386
を設定しなくても、現代のCPUの性能を最大限に引き出すためのコンパイルオプションを自動的に選択できるようになりました。
関連リンク
- Go言語の公式ドキュメント: https://golang.org/
- Goのソースコードリポジトリ: https://github.com/golang/go
- Goのコードレビューシステム (Gerrit): https://golang.org/cl/ (コミットメッセージに記載されている
https://golang.org/cl/7035055
はこのコミットのGerritチェンジリストへのリンクです)
参考にした情報源リンク
- Intel 64 and IA-32 Architectures Software Developer's Manuals (CPUID命令の詳細): https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html
- Streaming SIMD Extensions (SSE) - Wikipedia: https://ja.wikipedia.org/wiki/Streaming_SIMD_Extensions
- Goのビルドに関するドキュメント (Goのバージョンによって内容が異なる場合があります): https://go.dev/doc/install/source
- Goの環境変数に関するドキュメント (Goのバージョンによって内容が異なる場合があります): https://go.dev/doc/install/source#environment
- Goの
cmd/dist
に関する議論やコード (Goのソースコードを直接参照): https://github.com/golang/go/tree/master/src/cmd/dist - Goの
GO386
に関する情報 (GoのIssueやメーリングリストの議論): https://groups.google.com/g/golang-nuts (例: "GO386 sse" で検索) - CPUID - Wikipedia: https://ja.wikipedia.org/wiki/CPUID
- Goの
cmd/dist
の役割に関する情報 (Goのブートストラッププロセス): https://go.dev/doc/contribute#bootstrapping