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

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

このコミットは、Go言語のx86アセンブラ (8a) とリンカ (8l) に、Intel x86アーキテクチャのメモリバリア命令である LFENCEMFENCESFENCE を追加するものです。具体的には、以下の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など、厳密なメモリ操作の順序が求められる場面では、予期せぬ動作を引き起こす可能性があります。

LFENCEMFENCESFENCE といったメモリバリア命令は、このような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
 };

この変更は、アセンブラの字句解析器が認識するキーワードのリストに LFENCEMFENCESFENCE を追加しています。LTYPE0 は命令のタイプを示し、ALFENCEAMFENCEASFENCE はリンカが使用する内部的な命令コードの定数に対応します。

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) を定義しています。ALFENCEAMFENCEASFENCE という新しい定数が追加され、それぞれ LFENCEMFENCESFENCE 命令に対応する内部的な識別子となります。

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バイトで、それぞれ LFENCEMFENCESFENCE を区別する。

コアとなるコードの解説

これらの変更は、Go言語のx86アセンブラとリンカが、LFENCEMFENCESFENCE の各命令を完全にサポートするためのものです。

  1. src/cmd/8a/lex.c: アセンブラがソースコード中の LFENCEMFENCESFENCE という文字列を認識し、それらを内部的なトークン(ALFENCE など)に変換できるようにします。これにより、Goのアセンブリ言語でこれらの命令を記述できるようになります。
  2. src/cmd/8l/8.out.h: リンカがアセンブラから受け取った内部トークンを、リンカ自身の処理に適した定数として定義します。これは、アセンブラとリンカ間のインターフェースの一部です。
  3. src/cmd/8l/optab.c: リンカが、定義された内部トークン(ALFENCE など)を実際の機械語のオペコードに変換するためのルールを追加します。具体的には、LFENCE0F AE E8MFENCE0F AE F0SFENCE0F AE F8 というバイトシーケンスに変換されるように設定されています。これにより、Goのコンパイラやアセンブラがこれらの命令を生成した際に、リンカが正しく実行可能なバイナリに含めることができるようになります。

これらの変更により、Go言語のランタイムやライブラリ、あるいはユーザーが手書きのアセンブリコードを使用する際に、これらの重要なメモリバリア命令を直接利用できるようになり、より高度な並行処理や低レベルなハードウェアインタラクションが可能になります。

関連リンク

参考にした情報源リンク