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

[インデックス 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命令をネイティブに認識し、処理できるようにするための変更を複数のファイルにわたって行っています。

  1. src/cmd/8a/lex.c:

    • Goのアセンブラ(8a)の字句解析器(lexer)にEMMSというニーモニックを追加します。これにより、アセンブリソースコード内でEMMSと記述された際に、アセンブラがそれを認識し、対応する内部トークンAEMMSに変換できるようになります。
    • LTYPE0は、オペランドを持たない命令(ゼロオペランド命令)であることを示します。
  2. src/cmd/8l/8.out.h:

    • Goのリンカ(8l)が使用する命令コードの列挙型にAEMMSを追加します。これは、アセンブラが生成したオブジェクトファイル内でEMMS命令を表す内部的な定数となります。
  3. src/cmd/8l/optab.c:

    • リンカが命令を機械語に変換する際に使用するオペレーションテーブル(Optab)に、AEMMS命令のエントリを追加します。
    • { AEMMS, ynone, Pm, 0x77 }というエントリは、AEMMS命令がオペランドを持たず(ynone)、Pm(プレフィックスなし)で、その機械語バイトコードが0x77であることを定義しています。0x77は、EMMS命令のオペコードです。
  4. src/libmach/8db.c:

    • Goのデバッガや逆アセンブラが使用する命令テーブルにEMMS命令を追加します。
    • [0x77] = { 0,0, "EMMS" }というエントリは、機械語の0x77というバイトコードがEMMSというニーモニックに対応することを示しています。これにより、デバッガがバイナリを逆アセンブルする際に、0x77EMMSとして正しく表示できるようになります。
  5. src/pkg/sync/atomic/asm_386.s:

    • このファイルは、386アーキテクチャ向けのアトミック操作を実装するアセンブリコードを含んでいます。
    • 以前は、EMMS命令はBYTE $0x0F; BYTE $0x77という形で、その機械語バイト列を直接埋め込むことで表現されていました。
    • このコミットにより、EMMS命令がアセンブラによってネイティブにサポートされるようになったため、これらのハードコードされたバイト列が、より可読性の高いEMMSニーモニックに置き換えられました。これは、LoadUint64StoreUint64という関数内で確認できます。

これらの変更により、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関連のアセンブリコード(特にLoadUint64StoreUint64関数)において、以前はBYTE $0x0F; BYTE $0x77という形で直接埋め込まれていたEMMS命令のバイト列が、新しいEMMSニーモニックに置き換えられています。これは、このコミットの変更が実際にGoの既存コードベースに適用され、その恩恵を受けていることを示しています。この変更により、コードの意図がより明確になり、将来的なメンテナンスが容易になります。

全体として、このコミットはGoのツールチェインの成熟度を高め、特定のCPU命令セットに対するサポートをより堅牢なものにしています。

関連リンク

参考にした情報源リンク