[インデックス 11849] ファイルの概要
このコミットは、Go言語のx86アセンブラ (8a
) とリンカ (8l
) に、Intel x86アーキテクチャのメモリバリア命令である LFENCE
、MFENCE
、SFENCE
を追加するものです。具体的には、以下の3つのファイルが変更されています。
src/cmd/8a/lex.c
: アセンブラの字句解析器に新しい命令のキーワードを追加。src/cmd/8l/8.out.h
: リンカが使用する命令コードの定義に新しい命令を追加。src/cmd/8l/optab.c
: リンカのオペコードテーブルに新しい命令の情報を追加。
コミット
commit aaac05ae2371940cb868788b8ca365146bb2b84d
Author: Darren Elwood <darren@textnode.com>
Date: Mon Feb 13 13:58:12 2012 -0500
8a, 8l: add LFENCE, MFENCE, SFENCE
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5650076
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/aaac05ae2371940cb868788b8ca365146bb2b84d
元コミット内容
8a, 8l: add LFENCE, MFENCE, SFENCE
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5650076
変更の背景
現代のCPUは、パフォーマンス向上のためにメモリ操作(ロードとストア)をプログラムの順序とは異なる順序で実行する(アウトオブオーダー実行)ことがあります。これはシングルスレッドのプログラムでは問題になりにくいですが、マルチスレッド環境やデバイスドライバ、メモリマップドI/Oなど、厳密なメモリ操作の順序が求められる場面では、予期せぬ動作を引き起こす可能性があります。
LFENCE
、MFENCE
、SFENCE
といったメモリバリア命令は、このようなCPUによるメモリ操作の再順序付けを防ぎ、特定のメモリ操作が完了するまで後続の操作が実行されないことを保証するために使用されます。Go言語のようなシステムプログラミング言語において、低レベルな同期プリミティブの実装や、特定のハードウェアとのインタラクションを行う際に、これらの命令が必要となる場合があります。
このコミットは、Go言語のツールチェインがこれらの重要なx86命令を認識し、アセンブルおよびリンクできるようにすることで、Goプログラムがより低レベルなメモリ制御を必要とするシナリオに対応できるようにすることを目的としています。
前提知識の解説
メモリバリア命令 (Memory Barrier Instructions)
メモリバリア命令は、CPUがメモリ操作を再順序付けするのを防ぐための特殊な命令です。これにより、プログラムの意図したメモリ操作の順序が、実際のハードウェア上でも保証されます。x86アーキテクチャにおける主なメモリバリア命令は以下の3つです。
- LFENCE (Load Fence): ロード(読み込み)操作の順序を保証します。
LFENCE
より前のすべてのロード操作が完了するまで、LFENCE
より後のロード操作は開始されません。 - SFENCE (Store Fence): ストア(書き込み)操作の順序を保証します。
SFENCE
より前のすべてのストア操作がグローバルに可視になるまで、SFENCE
より後のストア操作は開始されません。 - MFENCE (Memory Fence): ロードとストアの両方の操作の順序を保証します。
MFENCE
より前のすべてのロードおよびストア操作が完了し、グローバルに可視になるまで、MFENCE
より後のロードおよびストア操作は開始されません。これは最も強力なメモリバリアです。
x86アセンブラ (8a
) とリンカ (8l
)
Go言語のツールチェインは、独自のクロスコンパイル可能なアセンブラとリンカを持っています。
8a
(Go Assembler for x86): Go言語のソースコードから生成されたアセンブリコード(または手書きのアセンブリコード)を機械語に変換するアセンブラです。このツールは、アセンブリ命令のニーモニック(例:MOV
,ADD
,CALL
)を対応するバイナリコードに変換します。8l
(Go Linker for x86): アセンブルされたオブジェクトファイルやライブラリを結合し、実行可能なバイナリファイルを生成するリンカです。リンカは、命令のオペコード(操作コード)やオペランド(操作対象)の情報を処理し、最終的な実行ファイルを作成します。
これらのツールに新しい命令を追加するということは、アセンブラがその命令のニーモニックを認識し、リンカがその命令のバイナリ表現(オペコード)を正しく処理できるようにする必要があることを意味します。
技術的詳細
メモリバリア命令の機能と用途
-
LFENCE
(Load Fence):- 機能:
LFENCE
命令より前のすべてのロード命令が完了するまで、後続の命令の実行を開始させません。特に、投機的実行(CPUが将来の命令を予測して先行して実行すること)の防止に役立ちます。 - 用途: Spectreなどのサイドチャネル攻撃の緩和策として使用されることがあります。また、特定のハードウェアレジスタからの読み込みが、その後の処理に影響を与える場合に、読み込みの完了を保証するために使用されることがあります。
- オペコード:
0F AE E8
(ModR/MバイトE8
は/8
に対応し、LFENCE
を示す)
- 機能:
-
SFENCE
(Store Fence):- 機能:
SFENCE
命令より前のすべてのストア命令がグローバルに可視になる(つまり、他のプロセッサやキャッシュコヒーレンシ機構から参照可能になる)まで、後続のストア命令の実行を開始させません。 - 用途: 非テンポラルストア命令(キャッシュをバイパスして直接メモリに書き込む命令)を使用する際に、データの順序を保証するために重要です。例えば、DMA転送の前にデータがメモリに完全に書き込まれたことを保証する場合などに使用されます。
- オペコード:
0F AE F8
(ModR/MバイトF8
は/8
に対応し、SFENCE
を示す)
- 機能:
-
MFENCE
(Memory Fence):- 機能:
MFENCE
命令より前のすべてのロードおよびストア命令が完了し、グローバルに可視になるまで、後続のロードおよびストア命令の実行を開始させません。これは、ロードとストアの両方に対して完全なメモリバリアを提供します。 - 用途: マルチプロセッサシステムにおける共有データの同期に不可欠です。ロックの実装、並行データ構造の更新、または複数のスレッド間でメモリの一貫性を維持する必要がある場合に広く使用されます。
- オペコード:
0F AE F0
(ModR/MバイトF0
は/8
に対応し、MFENCE
を示す)
- 機能:
これらの命令は、CPUのパフォーマンス最適化(アウトオブオーダー実行、キャッシュの利用など)によって引き起こされるメモリ操作の再順序付けの問題を解決するために不可欠です。特に、Go言語のような並行処理を重視する言語では、これらの低レベルな制御が、より堅牢で予測可能な並行プログラムを構築するために重要となります。
コアとなるコードの変更箇所
src/cmd/8a/lex.c
--- a/src/cmd/8a/lex.c
+++ b/src/cmd/8a/lex.c
@@ -663,6 +663,9 @@ struct
"FXTRACT", LTYPE0, AFXTRACT,
"FYL2X", LTYPE0, AFYL2X,
"FYL2XP1", LTYPE0, AFYL2XP1,
+ "LFENCE", LTYPE0, ALFENCE,
+ "MFENCE", LTYPE0, AMFENCE,
+ "SFENCE", LTYPE0, ASFENCE,
0
};
この変更は、アセンブラの字句解析器が認識するキーワードのリストに LFENCE
、MFENCE
、SFENCE
を追加しています。LTYPE0
は命令のタイプを示し、ALFENCE
、AMFENCE
、ASFENCE
はリンカが使用する内部的な命令コードの定数に対応します。
src/cmd/8l/8.out.h
--- a/src/cmd/8l/8.out.h
+++ b/src/cmd/8l/8.out.h
@@ -445,6 +445,10 @@ enum
AFCMOVNU,
AFCMOVUN,
+ ALFENCE,
+ AMFENCE,
+ ASFENCE,
+
ALAST
};
このヘッダファイルは、リンカが使用する命令コードの列挙型 (enum
) を定義しています。ALFENCE
、AMFENCE
、ASFENCE
という新しい定数が追加され、それぞれ LFENCE
、MFENCE
、SFENCE
命令に対応する内部的な識別子となります。
src/cmd/8l/optab.c
--- a/src/cmd/8l/optab.c
+++ b/src/cmd/8l/optab.c
@@ -755,5 +755,9 @@ Optab optab[] =
{ AFCMOVNU, yfcmv, Px, 0xdb,(03) },
{ AFCMOVUN, yfcmv, Px, 0xda,(03) },
+ { ALFENCE, ynone, Pm, 0xae,0xe8 },
+ { AMFENCE, ynone, Pm, 0xae,0xf0 },
+ { ASFENCE, ynone, Pm, 0xae,0xf8 },
+
0
};
このファイルは、リンカが命令を処理するためのオペコードテーブルを定義しています。新しいエントリが追加され、各メモリバリア命令 (ALFENCE
, AMFENCE
, ASFENCE
) とそれに対応するオペコードバイトシーケンスがマッピングされています。
ynone
: オペランドがないことを示す。Pm
: プレフィックスバイト0F
が必要であることを示す。0xae
: 命令の主要なオペコードバイト。0xe8
,0xf0
,0xf8
:0F AE
に続くModR/Mバイトで、それぞれLFENCE
、MFENCE
、SFENCE
を区別する。
コアとなるコードの解説
これらの変更は、Go言語のx86アセンブラとリンカが、LFENCE
、MFENCE
、SFENCE
の各命令を完全にサポートするためのものです。
src/cmd/8a/lex.c
: アセンブラがソースコード中のLFENCE
、MFENCE
、SFENCE
という文字列を認識し、それらを内部的なトークン(ALFENCE
など)に変換できるようにします。これにより、Goのアセンブリ言語でこれらの命令を記述できるようになります。src/cmd/8l/8.out.h
: リンカがアセンブラから受け取った内部トークンを、リンカ自身の処理に適した定数として定義します。これは、アセンブラとリンカ間のインターフェースの一部です。src/cmd/8l/optab.c
: リンカが、定義された内部トークン(ALFENCE
など)を実際の機械語のオペコードに変換するためのルールを追加します。具体的には、LFENCE
は0F AE E8
、MFENCE
は0F AE F0
、SFENCE
は0F AE F8
というバイトシーケンスに変換されるように設定されています。これにより、Goのコンパイラやアセンブラがこれらの命令を生成した際に、リンカが正しく実行可能なバイナリに含めることができるようになります。
これらの変更により、Go言語のランタイムやライブラリ、あるいはユーザーが手書きのアセンブリコードを使用する際に、これらの重要なメモリバリア命令を直接利用できるようになり、より高度な並行処理や低レベルなハードウェアインタラクションが可能になります。
関連リンク
- Go CL 5650076: https://golang.org/cl/5650076
参考にした情報源リンク
- x86 Memory Barrier Instructions (LFENCE, MFENCE, SFENCE) Explained: https://asrivas.me/blog/x86-memory-barrier-instructions-lfence-mfence-sfence-explained/
- Intel® 64 and IA-32 Architectures Software Developer’s Manuals: https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html (特にVol. 2A: Instruction Set Reference, A-L および Vol. 2B: Instruction Set Reference, M-Z)
- LFENCE instruction: https://www.felixcloutier.com/x86/lfence
- SFENCE instruction: https://www.felixcloutier.com/x86/sfence
- MFENCE instruction: https://www.felixcloutier.com/x86/mfence
- Memory Barriers: A Hardware View for Software Hackers: https://www.cs.cmu.edu/~410/doc/memory_barrier.pdf
- What is the purpose of LFENCE, SFENCE and MFENCE?: https://stackoverflow.com/questions/10380000/what-is-the-purpose-of-lfence-sfence-and-mfence
- What is the difference between LFENCE, SFENCE and MFENCE?: https://stackoverflow.com/questions/2086500/what-is-the-difference-between-lfence-sfence-and-mfence