[インデックス 12008] ファイルの概要
このコミットは、Go言語のツールチェイン、特に8a
(アセンブラ) と 8l
(リンカ) にEMMS
(Empty MMX State) 命令のサポートを追加し、関連するライブラリとアトミックパッケージの386アセンブリコードを更新するものです。
コミット
commit fc444ebac1521b4f36a70c0e1b19c2e78cf5520f
Author: Evan Shaw <chickencha@gmail.com>
Date: Fri Feb 17 11:21:46 2012 -0500
8a, 8l: add EMMS instruction
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5673081
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/fc444ebac1521b4f36a70c0e1b19c2e78cf5520f
元コミット内容
このコミットの目的は、Go言語の386アーキテクチャ向けアセンブラ(8a
)とリンカ(8l
)にEMMS
命令のサポートを追加することです。これにより、MMX命令セットを使用するコードが正しくアセンブルおよびリンクできるようになります。また、src/pkg/sync/atomic/asm_386.s
内の既存のMMX関連コードにおいて、ハードコードされたバイト列で表現されていたEMMS
命令を、新しいアセンブラのニーモニックに置き換える変更も含まれています。
変更の背景
Go言語は、クロスプラットフォーム対応を重視しており、様々なアーキテクチャ向けにコンパイラとアセンブラを提供しています。このコミットが行われた2012年当時、Goのツールチェインはまだ発展途上にあり、特定のCPU命令セットに対するサポートが段階的に追加されていました。
EMMS
命令は、IntelのMMX (MultiMedia eXtensions) 命令セットの一部です。MMX命令は、SIMD (Single Instruction, Multiple Data) 処理を可能にし、特にマルチメディア処理やグラフィックス処理でパフォーマンス向上に寄与しました。しかし、MMXレジスタは浮動小数点演算ユニット (FPU) のレジスタとエイリアス(共有)されており、MMX命令を使用した後はFPU命令を使用する前にEMMS
命令を実行してMMX状態をクリアする必要がありました。これを怠ると、FPU演算が不正な結果を返す可能性がありました。
Go言語のsync/atomic
パッケージは、アトミック操作(不可分操作)を提供し、並行プログラミングにおけるデータ競合を防ぐために重要です。このパッケージの一部は、パフォーマンスのためにアセンブリ言語で実装されています。特に386アーキテクチャ(x86 32-bit)では、MMX命令が使用されるケースがありました。
このコミット以前は、EMMS
命令はGoのアセンブラによって直接認識されるニーモニックではなく、BYTE
ディレクティブを使って命令のバイトコード(0x0F 0x77
)を直接埋め込む形で使用されていました。これは可読性が低く、アセンブラの機能が不完全であることを示していました。このコミットは、EMMS
命令をGoのアセンブラとリンカがネイティブにサポートするようにすることで、コードの可読性と保守性を向上させ、将来的なMMX関連コードの記述を容易にすることを目的としています。
前提知識の解説
1. Go言語のツールチェイン (8a
, 8l
)
8a
(Goアセンブラ): Go言語のソースコード(.go
ファイル)はGoコンパイラによって中間コードに変換されますが、一部の低レベルな処理やパフォーマンスが要求される部分は、Goのアセンブリ言語(.s
ファイル)で記述されます。8a
は、これらのGoアセンブリ言語のソースファイルを、機械語のオブジェクトファイルに変換するアセンブラです。Goのアセンブリ言語は、一般的なx86アセンブリとは異なる独自の構文を持っています。8l
(Goリンカ):8l
は、8a
によって生成されたオブジェクトファイルや、Goコンパイラが生成したオブジェクトファイルを結合し、実行可能なバイナリを生成するリンカです。このプロセス中に、シンボルの解決やアドレスの再配置などが行われます。
2. x86アーキテクチャとMMX命令セット
- x86アーキテクチャ: Intelが開発した命令セットアーキテクチャで、パーソナルコンピュータのCPUで広く採用されています。32ビット版はIA-32またはi386と呼ばれます。
- MMX (MultiMedia eXtensions): IntelがPentium MMXプロセッサで導入したSIMD命令セットです。8つの64ビットMMXレジスタ(
MM0
からMM7
)を提供し、これらはFPUの80ビット浮動小数点レジスタ(ST0
からST7
)と物理的に共有されています。 - FPU (Floating-Point Unit): 浮動小数点演算を行うプロセッサのコンポーネントです。
- SIMD (Single Instruction, Multiple Data): 一つの命令で複数のデータ要素に対して同じ操作を同時に実行する並列処理の形式です。
3. EMMS
命令
- 目的:
EMMS
(Empty MMX State) 命令は、MMX命令を使用した後にFPU命令を使用する前に実行する必要がある命令です。MMXレジスタとFPUレジスタがエイリアスされているため、MMX命令を実行するとFPUのタグワード(レジスタの状態を示す情報)が不正な状態になる可能性があります。EMMS
は、このタグワードを「空」の状態にリセットし、FPUが正しく動作するようにします。 - 重要性:
EMMS
を呼び出さないと、MMX命令の後にFPU命令を実行した際に、FPU例外が発生したり、計算結果が不正になったりする可能性があります。これは、MMXとFPUを混在して使用するコードにおいて、特に重要な命令です。
4. アトミック操作とsync/atomic
パッケージ
- アトミック操作: 複数のスレッドから同時にアクセスされても、その操作全体が不可分(中断されない)であることを保証する操作です。これにより、並行処理におけるデータ競合(race condition)を防ぎ、プログラムの正しさを保証します。
sync/atomic
パッケージ: Go言語の標準ライブラリで、アトミックなプリミティブ操作(例: アトミックな加算、ロード、ストア、比較交換など)を提供します。これらの操作は、通常、CPUの特別な命令(例:LOCK
プレフィックス付きの命令)を利用して実装されており、非常に高速です。
技術的詳細
このコミットは、GoのツールチェインがEMMS
命令をネイティブに認識し、処理できるようにするための変更を複数のファイルにわたって行っています。
-
src/cmd/8a/lex.c
:- Goのアセンブラ(
8a
)の字句解析器(lexer)にEMMS
というニーモニックを追加します。これにより、アセンブリソースコード内でEMMS
と記述された際に、アセンブラがそれを認識し、対応する内部トークンAEMMS
に変換できるようになります。 LTYPE0
は、オペランドを持たない命令(ゼロオペランド命令)であることを示します。
- Goのアセンブラ(
-
src/cmd/8l/8.out.h
:- Goのリンカ(
8l
)が使用する命令コードの列挙型にAEMMS
を追加します。これは、アセンブラが生成したオブジェクトファイル内でEMMS
命令を表す内部的な定数となります。
- Goのリンカ(
-
src/cmd/8l/optab.c
:- リンカが命令を機械語に変換する際に使用するオペレーションテーブル(
Optab
)に、AEMMS
命令のエントリを追加します。 { AEMMS, ynone, Pm, 0x77 }
というエントリは、AEMMS
命令がオペランドを持たず(ynone
)、Pm
(プレフィックスなし)で、その機械語バイトコードが0x77
であることを定義しています。0x77
は、EMMS
命令のオペコードです。
- リンカが命令を機械語に変換する際に使用するオペレーションテーブル(
-
src/libmach/8db.c
:- Goのデバッガや逆アセンブラが使用する命令テーブルに
EMMS
命令を追加します。 [0x77] = { 0,0, "EMMS" }
というエントリは、機械語の0x77
というバイトコードがEMMS
というニーモニックに対応することを示しています。これにより、デバッガがバイナリを逆アセンブルする際に、0x77
をEMMS
として正しく表示できるようになります。
- Goのデバッガや逆アセンブラが使用する命令テーブルに
-
src/pkg/sync/atomic/asm_386.s
:- このファイルは、386アーキテクチャ向けのアトミック操作を実装するアセンブリコードを含んでいます。
- 以前は、
EMMS
命令はBYTE $0x0F; BYTE $0x77
という形で、その機械語バイト列を直接埋め込むことで表現されていました。 - このコミットにより、
EMMS
命令がアセンブラによってネイティブにサポートされるようになったため、これらのハードコードされたバイト列が、より可読性の高いEMMS
ニーモニックに置き換えられました。これは、LoadUint64
とStoreUint64
という関数内で確認できます。
これらの変更により、GoのツールチェインはEMMS
命令を完全にサポートし、アセンブリコードの可読性と保守性が向上しました。
コアとなるコードの変更箇所
src/cmd/8a/lex.c
(アセンブラの字句解析器)
--- a/src/cmd/8a/lex.c
+++ b/src/cmd/8a/lex.c
@@ -666,6 +666,7 @@ struct
"LFENCE", LTYPE0, ALFENCE,
"MFENCE", LTYPE0, AMFENCE,
"SFENCE", LTYPE0, ASFENCE,
+ "EMMS", LTYPE0, AEMMS,
0
};
src/cmd/8l/8.out.h
(リンカの命令定義)
--- a/src/cmd/8l/8.out.h
+++ b/src/cmd/8l/8.out.h
@@ -449,6 +449,8 @@ enum
AMFENCE,
ASFENCE,
+ AEMMS,
+
ALAST
};
src/cmd/8l/optab.c
(リンカのオペレーションテーブル)
--- a/src/cmd/8l/optab.c
+++ b/src/cmd/8l/optab.c
@@ -759,5 +759,7 @@ Optab optab[] =
{ AMFENCE, ynone, Pm, 0xae,0xf0 },
{ ASFENCE, ynone, Pm, 0xae,0xf8 },
+ { AEMMS, ynone, Pm, 0x77 },
+
0
};
src/libmach/8db.c
(デバッガの命令定義)
--- a/src/libmach/8db.c
+++ b/src/libmach/8db.c
@@ -688,6 +688,7 @@ static Optable optab0F[256]=
[0x74] = { RM,0, "PCMPEQB %m,%M" },
[0x75] = { RM,0, "PCMPEQW %m,%M" },
[0x76] = { RM,0, "PCMPEQL %m,%M" },
+[0x77] = { 0,0, "EMMS" },
[0x7E] = { RM,0, "MOV%S %M,%e" },
[0x7F] = { RM,0, "MOVQ %M,%m" },
[0xAE] = { RMOP,0, optab0FAE },
src/pkg/sync/atomic/asm_386.s
(アトミックパッケージの386アセンブリ)
--- a/src/pkg/sync/atomic/asm_386.s
+++ b/src/pkg/sync/atomic/asm_386.s
@@ -108,8 +108,7 @@ TEXT ·LoadUint64(SB),7,$0
BYTE $0x0f; BYTE $0x6f; BYTE $0x00
// MOVQ %MM0, 0x8(%ESP)
BYTE $0x0f; BYTE $0x7f; BYTE $0x44; BYTE $0x24; BYTE $0x08
- // EMMS
- BYTE $0x0F; BYTE $0x77
+ EMMS
RET
TEXT ·LoadUintptr(SB),7,$0
@@ -137,8 +136,7 @@ TEXT ·StoreUint64(SB),7,$0
BYTE $0x0f; BYTE $0x6f; BYTE $0x44; BYTE $0x24; BYTE $0x08
// MOVQ %MM0, (%EAX)
BYTE $0x0f; BYTE $0x7f; BYTE $0x00
- // EMMS
- BYTE $0x0F; BYTE $0x77
+ EMMS
// This is essentially a no-op, but it provides required memory fencing.
// It can be replaced with MFENCE, but MFENCE was introduced only on the Pentium4 (SSE2).
XORL AX, AX
コアとなるコードの解説
このコミットの核となる変更は、GoのツールチェインがEMMS
命令をシンボリックに扱えるようにした点です。
src/cmd/8a/lex.c
: アセンブラがEMMS
というテキストを読み込んだときに、それを内部的な命令コードAEMMS
として認識するようにします。これにより、アセンブリソースコードの可読性が向上し、開発者は機械語のバイト列を直接記述する必要がなくなります。src/cmd/8l/8.out.h
:AEMMS
という新しい命令コードをシステム全体で利用可能にします。src/cmd/8l/optab.c
:AEMMS
命令が実際にどのような機械語(0x77
)に変換されるかをリンカに教えます。これは、アセンブリソースコードから最終的な実行可能バイナリへの変換パスにおいて不可欠なステップです。src/libmach/8db.c
: 逆アセンブラが0x77
というバイトコードを読み込んだときに、それをEMMS
という人間が読めるニーモニックに変換して表示できるようにします。これはデバッグ作業において非常に役立ちます。src/pkg/sync/atomic/asm_386.s
: 既存のMMX関連のアセンブリコード(特にLoadUint64
とStoreUint64
関数)において、以前はBYTE $0x0F; BYTE $0x77
という形で直接埋め込まれていたEMMS
命令のバイト列が、新しいEMMS
ニーモニックに置き換えられています。これは、このコミットの変更が実際にGoの既存コードベースに適用され、その恩恵を受けていることを示しています。この変更により、コードの意図がより明確になり、将来的なメンテナンスが容易になります。
全体として、このコミットはGoのツールチェインの成熟度を高め、特定のCPU命令セットに対するサポートをより堅牢なものにしています。
関連リンク
- Go言語の公式リポジトリ: https://github.com/golang/go
- Go言語のCL (Change List) 5673081: https://golang.org/cl/5673081 (コミットメッセージに記載されているGoのコードレビューシステムへのリンク)
参考にした情報源リンク
- Intel 64 and IA-32 Architectures Software Developer's Manuals (MMX, FPU, EMMS命令の詳細): https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html
- Go Assembly Language (Goのアセンブリ言語の構文について): https://go.dev/doc/asm
- Go
sync/atomic
パッケージのドキュメント: https://pkg.go.dev/sync/atomic - MMX Technology Overview (MMX技術の概要): https://www.intel.com/content/www/us/en/docs/programmable/68346/20-1/mmx-technology-overview.html
EMMS
instruction on Wikipedia: https://en.wikipedia.org/wiki/EMMS_instruction- x86 instruction listings (x86命令一覧): https://www.felixcloutier.com/x86/index.html
- Goのツールチェインに関する情報 (Goのコンパイラ、アセンブラ、リンカの内部構造): https://go.dev/doc/articles/go_toolchain.html