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

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

このコミットは、Go言語のx86-32アーキテクチャ向けコンパイラであるcmd/8cにおいて、GO386=387という環境変数が設定されている場合に、CPUのプリフェッチ命令の使用を無効化する変更を導入しています。これは、特定の環境下でのコンパイル済みコードの誤動作を修正するためのものです。

コミット

commit 594360cb1b31a99a349ba03294f5459aff0bc33d
Author: Russ Cox <rsc@golang.org>
Date:   Wed Feb 13 21:13:07 2013 -0500

    cmd/8c: disable use of prefetch with GO386=387
    
    Fixes #4798.
    
    R=ken2
    CC=golang-dev
    https://golang.org/cl/7323061

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

https://github.com/golang/go/commit/594360cb1b31a99a349ba03294f5459aff0bc33d

元コミット内容

cmd/8c: disable use of prefetch with GO386=387

Fixes #4798.

R=ken2
CC=golang-dev
https://golang.org/cl/7323061

変更の背景

この変更は、Go言語のIssue #4798「GO386=387でコンパイルされたプログラムがクラッシュする」を修正するために行われました。

Issue #4798では、GO386環境変数を387に設定してGoプログラムをコンパイルし、実行すると、プログラムがクラッシュするという問題が報告されていました。GO386=387は、Goコンパイラに対して、Intel 80387互換の浮動小数点演算ユニット(FPU)のみを使用し、SSE2(Streaming SIMD Extensions 2)のようなより新しいSIMD命令セットを使用しないように指示するものです。

問題の根本原因は、cmd/8cコンパイラが、GO386=387が設定されている環境(つまり、SSE2命令が利用できない、または利用すべきでないと想定されている環境)でも、誤ってCPUのプリフェッチ命令(PREFETCH)を生成してしまうことにありました。プリフェッチ命令は、メモリからデータを事前にキャッシュに読み込むことで、プログラムのパフォーマンスを向上させるための命令ですが、SSE2命令セットの一部として導入されたものであり、SSE2をサポートしない古いCPUアーキテクチャや、GO386=387のようにSSE2の使用を意図的に避ける設定では、これらの命令が未定義命令例外(Invalid Opcode Exception)を引き起こし、プログラムのクラッシュにつながっていました。

このコミットは、GO386=387が設定されている場合にプリフェッチ命令の生成を無効にすることで、このクラッシュ問題を解決することを目的としています。

前提知識の解説

Goコンパイラとcmd/8c

Go言語のツールチェインは、各アーキテクチャに対応するコンパイラを持っています。cmd/8cは、Go 1.x系の時代に存在した、x86-32(32ビットIntel互換)アーキテクチャ向けのGoコンパイラです。Go 1.5以降、コンパイラはGo自身で書かれるようになり、cmd/compileに統合されましたが、このコミットが作成された2013年当時は、cmd/8cが独立したコンパイラとして機能していました。

GO386環境変数

GO386は、Goコンパイラがx86-32アーキテクチャ向けにコードを生成する際に、どのCPU命令セットを使用するかを制御するための環境変数です。主な設定値は以下の通りです。

  • sse2: デフォルト値。SSE2命令セットを使用するようにコンパイラに指示します。現代のほとんどのx86プロセッサはSSE2をサポートしています。
  • 387: Intel 80387互換の浮動小数点演算ユニット(FPU)のみを使用するようにコンパイラに指示します。これは、SSE2をサポートしない非常に古いプロセッサ(例: Intel Pentium III以前)や、仮想環境などでSSE2が利用できない場合に互換性を確保するために使用されます。387モードでは、浮動小数点演算はx87 FPUスタックレジスタを使用して行われます。

この環境変数は、特定のハードウェア環境や、古いシステムとの互換性を維持する必要がある場合に重要となります。

CPUプリフェッチ命令(PREFETCH

プリフェッチ命令(例: PREFETCHT0, PREFETCHT1, PREFETCHT2, PREFETCHNTAなど)は、プロセッサが将来必要になるであろうデータを、メインメモリからCPUキャッシュに事前に読み込んでおくように指示するための命令です。これにより、データが実際に必要になったときにキャッシュミスによる遅延を減らし、プログラムの実行速度を向上させることができます。

プリフェッチ命令は、IntelのSSE(Streaming SIMD Extensions)命令セットの一部として導入されました。特に、SSE2命令セットは、SIMD(Single Instruction, Multiple Data)演算能力を拡張し、プリフェッチ命令を含む多くの新しい命令を提供しました。したがって、SSE2をサポートしない古いCPUでは、これらのプリフェッチ命令は認識されず、未定義命令例外を引き起こす可能性があります。

技術的詳細

この問題は、cmd/8cコンパイラが、GO386=387が設定されている場合でも、SSE2命令セットの一部であるプリフェッチ命令を誤って生成してしまうという、コンパイラのコード生成ロジックのバグに起因していました。

GO386=387が設定されている場合、コンパイラはSSE2命令を使用しないように設計されているはずです。しかし、gprefetch関数(Goコンパイラ内でプリフェッチ命令を生成する役割を持つ関数)が、GO386の設定を適切にチェックせずにプリフェッチ命令を生成していたため、SSE2をサポートしない環境でクラッシュが発生していました。

このコミットでは、gprefetch関数に明示的なチェックを追加することで、この問題を解決しています。具体的には、getgo386()関数(GO386環境変数の値を取得するGoコンパイラ内部の関数)の戻り値が"sse2"でない場合、つまり"387"などの古いモードが指定されている場合には、プリフェッチ命令の生成をスキップするように変更されました。これにより、GO386=387環境下での不要なプリフェッチ命令の生成が抑制され、プログラムのクラッシュが回避されます。

この修正は、GoコンパイラがターゲットとするCPUアーキテクチャの特性を正確に理解し、それに基づいて適切な命令を生成することの重要性を示しています。特に、古いハードウェアとの互換性を維持しつつ、新しい命令セットの恩恵を受けるためには、このような細かな制御が不可欠です。

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

diff --git a/src/cmd/8c/txt.c b/src/cmd/8c/txt.c
index d7873e3855..1b7617bc52 100644
--- a/src/cmd/8c/txt.c
+++ b/src/cmd/8c/txt.c
@@ -1396,6 +1396,9 @@ gprefetch(Node *n)
 {
 	Node n1;
 	
+	if(strcmp(getgo386(), "sse2") != 0) // assume no prefetch on old machines
+		return;
+
 	regalloc(&n1, n, Z);
 	gmove(n, &n1);
 	n1.op = OINDREG;

コアとなるコードの解説

変更はsrc/cmd/8c/txt.cファイルのgprefetch関数内で行われています。

元のコードでは、gprefetch関数は引数nで指定されたノード(GoのASTにおける式や変数など)に対して、プリフェッチ命令を生成するための処理を直接実行していました。

変更後のコードでは、関数の冒頭に以下の3行が追加されています。

	if(strcmp(getgo386(), "sse2") != 0) // assume no prefetch on old machines
		return;
  • getgo386(): これはGoコンパイラ内部の関数で、GO386環境変数の現在の値(例: "sse2""387")を文字列として返します。
  • strcmp(getgo386(), "sse2") != 0: この条件式は、getgo386()が返す文字列が"sse2"と等しくない場合に真となります。つまり、GO386"sse2"以外の値(例えば"387")に設定されている場合にこの条件が満たされます。
  • return;: 条件が真の場合、gprefetch関数はそこで処理を終了し、プリフェッチ命令の生成を行いません。

この変更により、GO386"sse2"以外の値(特に"387")に設定されている環境では、gprefetch関数が早期にリターンし、プリフェッチ命令が生成されなくなります。コメント// assume no prefetch on old machinesが示すように、これは古いマシン(SSE2をサポートしないマシン)ではプリフェッチ命令が利用できない、または適切に動作しないという前提に基づいています。

この修正は非常に局所的ですが、特定の環境下でのGoプログラムの安定性を大きく向上させるための重要な変更でした。

関連リンク

参考にした情報源リンク

  • Go Issue #4798の議論内容
  • Go言語のドキュメント(GOARCH, GOOS, GO386などの環境変数に関する情報)
  • Intel 64 and IA-32 Architectures Software Developer's Manuals (SSE/SSE2命令セット、PREFETCH命令に関する情報)
  • Go言語のコンパイラ(cmd/8c)のソースコード(当時のもの)
  • strcmp関数のC言語標準ライブラリのドキュメント# [インデックス 15230] ファイルの概要

このコミットは、Go言語のx86-32アーキテクチャ向けコンパイラであるcmd/8cにおいて、GO386=387という環境変数が設定されている場合に、CPUのプリフェッチ命令の使用を無効化する変更を導入しています。これは、特定の環境下でのコンパイル済みコードの誤動作を修正するためのものです。

コミット

commit 594360cb1b31a99a349ba03294f5459aff0bc33d
Author: Russ Cox <rsc@golang.org>
Date:   Wed Feb 13 21:13:07 2013 -0500

    cmd/8c: disable use of prefetch with GO386=387
    
    Fixes #4798.
    
    R=ken2
    CC=golang-dev
    https://golang.org/cl/7323061

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

https://github.com/golang/go/commit/594360cb1b31a99a349ba03294f5459aff0bc33d

元コミット内容

cmd/8c: disable use of prefetch with GO386=387

Fixes #4798.

R=ken2
CC=golang-dev
https://golang.org/cl/7323061

変更の背景

この変更は、Go言語のIssue #4798「GO386=387でコンパイルされたプログラムがクラッシュする」を修正するために行われました。

Issue #4798では、GO386環境変数を387に設定してGoプログラムをコンパイルし、実行すると、プログラムがクラッシュするという問題が報告されていました。GO386=387は、Goコンパイラに対して、Intel 80387互換の浮動小数点演算ユニット(FPU)のみを使用し、SSE2(Streaming SIMD Extensions 2)のようなより新しいSIMD命令セットを使用しないように指示するものです。

問題の根本原因は、cmd/8cコンパイラが、GO386=387が設定されている環境(つまり、SSE2命令が利用できない、または利用すべきでないと想定されている環境)でも、誤ってCPUのプリフェッチ命令(PREFETCH)を生成してしまうことにありました。プリフェッチ命令は、メモリからデータを事前にキャッシュに読み込むことで、プログラムのパフォーマンスを向上させるための命令ですが、SSE2命令セットの一部として導入されたものであり、SSE2をサポートしない古いCPUアーキテクチャや、GO386=387のようにSSE2の使用を意図的に避ける設定では、これらの命令が未定義命令例外(Invalid Opcode Exception)を引き起こし、プログラムのクラッシュにつながっていました。

このコミットは、GO386=387が設定されている場合にプリフェッチ命令の生成を無効にすることで、このクラッシュ問題を解決することを目的としています。

前提知識の解説

Goコンパイラとcmd/8c

Go言語のツールチェインは、各アーキテクチャに対応するコンパイラを持っています。cmd/8cは、Go 1.x系の時代に存在した、x86-32(32ビットIntel互換)アーキテクチャ向けのGoコンパイラです。Go 1.5以降、コンパイラはGo自身で書かれるようになり、cmd/compileに統合されましたが、このコミットが作成された2013年当時は、cmd/8cが独立したコンパイラとして機能していました。

GO386環境変数

GO386は、Goコンパイラがx86-32アーキテクチャ向けにコードを生成する際に、どのCPU命令セットを使用するかを制御するための環境変数です。主な設定値は以下の通りです。

  • sse2: デフォルト値。SSE2命令セットを使用するようにコンパイラに指示します。現代のほとんどのx86プロセッサはSSE2をサポートしています。
  • 387: Intel 80387互換の浮動小数点演算ユニット(FPU)のみを使用するようにコンパイラに指示します。これは、SSE2をサポートしない非常に古いプロセッサ(例: Intel Pentium III以前)や、仮想環境などでSSE2が利用できない場合に互換性を確保するために使用されます。387モードでは、浮動小数点演算はx87 FPUスタックレジスタを使用して行われます。

この環境変数は、特定のハードウェア環境や、古いシステムとの互換性を維持する必要がある場合に重要となります。

CPUプリフェッチ命令(PREFETCH

プリフェッチ命令(例: PREFETCHT0, PREFETCHT1, PREFETCHT2, PREFETCHNTAなど)は、プロセッサが将来必要になるであろうデータを、メインメモリからCPUキャッシュに事前に読み込んでおくように指示するための命令です。これにより、データが実際に必要になったときにキャッシュミスによる遅延を減らし、プログラムの実行速度を向上させることができます。

プリフェッチ命令は、IntelのSSE(Streaming SIMD Extensions)命令セットの一部として導入されました。特に、SSE2命令セットは、SIMD(Single Instruction, Multiple Data)演算能力を拡張し、プリフェッチ命令を含む多くの新しい命令を提供しました。したがって、SSE2をサポートしない古いCPUでは、これらのプリフェッチ命令は認識されず、未定義命令例外を引き起こす可能性があります。

技術的詳細

この問題は、cmd/8cコンパイラが、GO386=387が設定されている場合でも、SSE2命令セットの一部であるプリフェッチ命令を誤って生成してしまうという、コンパイラのコード生成ロジックのバグに起因していました。

GO386=387が設定されている場合、コンパイラはSSE2命令を使用しないように設計されているはずです。しかし、gprefetch関数(Goコンパイラ内でプリフェッチ命令を生成する役割を持つ関数)が、GO386の設定を適切にチェックせずにプリフェッチ命令を生成していたため、SSE2をサポートしない環境でクラッシュが発生していました。

このコミットでは、gprefetch関数に明示的なチェックを追加することで、この問題を解決しています。具体的には、getgo386()関数(GO386環境変数の値を取得するGoコンパイラ内部の関数)の戻り値が"sse2"でない場合、つまり"387"などの古いモードが指定されている場合には、プリフェッチ命令の生成をスキップするように変更されました。これにより、GO386=387環境下での不要なプリフェッチ命令の生成が抑制され、プログラムのクラッシュが回避されます。

この修正は、GoコンパイラがターゲットとするCPUアーキテクチャの特性を正確に理解し、それに基づいて適切な命令を生成することの重要性を示しています。特に、古いハードウェアとの互換性を維持しつつ、新しい命令セットの恩恵を受けるためには、このような細かな制御が不可欠です。

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

diff --git a/src/cmd/8c/txt.c b/src/cmd/8c/txt.c
index d7873e3855..1b7617bc52 100644
--- a/src/cmd/8c/txt.c
+++ b/src/cmd/8c/txt.c
@@ -1396,6 +1396,9 @@ gprefetch(Node *n)
 {
 	Node n1;
 	
+	if(strcmp(getgo386(), "sse2") != 0) // assume no prefetch on old machines
+		return;
+
 	regalloc(&n1, n, Z);
 	gmove(n, &n1);
 	n1.op = OINDREG;

コアとなるコードの解説

変更はsrc/cmd/8c/txt.cファイルのgprefetch関数内で行われています。

元のコードでは、gprefetch関数は引数nで指定されたノード(GoのASTにおける式や変数など)に対して、プリフェッチ命令を生成するための処理を直接実行していました。

変更後のコードでは、関数の冒頭に以下の3行が追加されています。

	if(strcmp(getgo386(), "sse2") != 0) // assume no prefetch on old machines
		return;
  • getgo386(): これはGoコンパイラ内部の関数で、GO386環境変数の現在の値(例: "sse2""387")を文字列として返します。
  • strcmp(getgo386(), "sse2") != 0: この条件式は、getgo386()が返す文字列が"sse2"と等しくない場合に真となります。つまり、GO386"sse2"以外の値(例えば"387")に設定されている場合にこの条件が満たされます。
  • return;: 条件が真の場合、gprefetch関数はそこで処理を終了し、プリフェッチ命令の生成を行いません。

この変更により、GO386"sse2"以外の値(特に"387")に設定されている環境では、gprefetch関数が早期にリターンし、プリフェッチ命令が生成されなくなります。コメント// assume no prefetch on old machinesが示すように、これは古いマシン(SSE2をサポートしないマシン)ではプリフェッチ命令が利用できない、または適切に動作しないという前提に基づいています。

この修正は非常に局所的ですが、特定の環境下でのGoプログラムの安定性を大きく向上させるための重要な変更でした。

関連リンク

参考にした情報源リンク

  • Go Issue #4798の議論内容
  • Go言語のドキュメント(GOARCH, GOOS, GO386などの環境変数に関する情報)
  • Intel 64 and IA-32 Architectures Software Developer's Manuals (SSE/SSE2命令セット、PREFETCH命令に関する情報)
  • Go言語のコンパイラ(cmd/8c)のソースコード(当時のもの)
  • strcmp関数のC言語標準ライブラリのドキュメント