[インデックス 10322] ファイルの概要
本コミットは、Go言語のx86-64アーキテクチャ向けリンカである6l
において、MOVQ
命令がXMMレジスタ間、またはXMMレジスタとメモリ間で正しく扱われるようにするための変更を導入しています。特に、MOVQ xmm_reg, xmm_reg
のような命令形式のサポートを追加し、既存のREX.W MOVD
命令よりもネイティブなMOVQ
命令が優先されるように改善しています。
コミット
commit 17105870ffbcdf8c68c1ee9cb399f71b3fbc8f81
Author: Michał Derkacz <ziutek@lnet.pl>
Date: Wed Nov 9 16:01:17 2011 -0500
6l: add MOVQ xmm_reg, xmm_reg
Added handler for:
MOVQ xmm_reg, xmm_reg/mem64
MOVQ xmm_reg/mem64, xmm_reg
using native MOVQ (it take precedence above REX.W MOVD)
I don't understood 6l code enough to be sure that my small changes
didn't broke it. But now 6l works with MOVQ xmm_reg, xmm_reg and
all.bash reports "0 unexpected bugs".
There is test assembly source:
MOVQ X0, X1
MOVQ AX, X1
MOVQ X1, AX
MOVQ xxx+8(FP), X2
MOVQ X2, xxx+8(FP)
and generated code (gdb disassemble /r):
0x000000000040f112 <+0>: f3 0f 7e c8 movq %xmm0,%xmm1
0x000000000040f116 <+4>: 66 48 0f 6e c8 movq %rax,%xmm1
0x000000000040f11b <+9>: 66 48 0f 7e c8 movq %xmm1,%rax
0x000000000040f120 <+14>: f3 0f 7e 54 24 10 movq 0x10(%rsp),%xmm2
0x000000000040f126 <+20>: 66 0f d6 54 24 10 movq %xmm2,0x10(%rsp)
Fixes #2418.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5316076
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/17105870ffbcdf8c68c1ee9cb399f71b3fbc8f81
元コミット内容
このコミットは、Go言語のx86-64リンカである6l
に、MOVQ
命令の新しいハンドラを追加するものです。具体的には、以下の形式のMOVQ
命令をサポートします。
MOVQ xmm_reg, xmm_reg/mem64
(XMMレジスタからXMMレジスタまたは64ビットメモリへの移動)MOVQ xmm_reg/mem64, xmm_reg
(XMMレジスタまたは64ビットメモリからXMMレジスタへの移動)
この変更により、REX.W MOVD
命令よりもネイティブなMOVQ
命令が優先的に使用されるようになります。コミットメッセージには、これらの変更が6l
の既存の機能を破壊していないことを確認するために、テストアセンブリコードとそれによって生成された機械語(GDBによる逆アセンブル結果)が示されています。また、all.bash
スクリプトが「0 unexpected bugs」と報告したことから、広範なテストスイートが正常に実行されたことが示唆されています。
このコミットは、Go言語のIssue #2418を修正するものです。
変更の背景
Go言語のコンパイラとリンカは、Goプログラムを様々なアーキテクチャの機械語に変換する役割を担っています。x86-64アーキテクチャでは、浮動小数点演算やSIMD(Single Instruction, Multiple Data)操作のためにXMMレジスタが使用されます。これらのレジスタ間でデータを移動させる際には、MOVQ
命令が用いられます。
しかし、Goのリンカ6l
の以前のバージョンでは、MOVQ
命令の特定の形式、特にXMMレジスタ間での移動(例: MOVQ X0, X1
)や、XMMレジスタと64ビットメモリ間の移動に対する適切なハンドリングが不足していた可能性があります。これにより、コンパイラが生成したアセンブリコードが正しくリンクされなかったり、非効率な命令が選択されたりする問題が発生していました。
コミットメッセージに記載されているFixes #2418
は、この問題がGoのIssueトラッカーで報告されていたことを示しています。このIssueは、MOVQ
命令の特定の形式が6l
によって正しく処理されないというバグを指摘していたと考えられます。このコミットは、そのバグを修正し、6l
がより広範なMOVQ
命令の形式をネイティブかつ効率的にサポートできるようにすることを目的としています。
また、REX.W MOVD
よりもネイティブなMOVQ
を優先するという記述は、命令エンコーディングの最適化を示唆しています。MOVD
命令は通常32ビットデータを扱うために使用されますが、REX.W
プレフィックスを付加することで64ビットデータを扱うことができます。しかし、MOVQ
命令は元々64ビットデータを扱うために設計されており、XMMレジスタと関連する操作においては、より直接的で効率的な選択肢となる場合があります。リンカが適切な命令を選択できるようにすることで、生成されるバイナリのパフォーマンスが向上する可能性があります。
前提知識の解説
このコミットを理解するためには、以下の概念についての知識が必要です。
-
Go言語のツールチェイン:
6l
: Go言語のx86-64アーキテクチャ向けリンカです。Goのコンパイラ(6g
など)によって生成されたオブジェクトファイルを結合し、実行可能なバイナリを生成します。Goのツールチェインは、各アーキテクチャ(例:6
はx86-64、8
はx86、5
はARM)ごとに異なるコンパイラ、アセンブラ、リンカを持っています。- Goアセンブリ: Go言語は、特定のパフォーマンスが要求される部分や、OSとのインタフェース部分などで、Goアセンブリと呼ばれる独自のアセンブリ言語を使用します。これはAT&T構文に似ていますが、Go独自の擬似命令やレジスタ命名規則を持っています。
all.bash
: Goプロジェクトのルートディレクトリにあるシェルスクリプトで、Goのツールチェイン全体をビルドし、テストを実行するための主要なスクリプトです。これが「0 unexpected bugs」と報告することは、変更が既存の機能に悪影響を与えなかったことを示す重要な指標となります。
-
x86-64アーキテクチャ:
- XMMレジスタ: SSE(Streaming SIMD Extensions)以降の命令セットで導入された128ビットレジスタです。主に浮動小数点数やSIMD演算に使用されます。XMM0からXMM15まで存在します。Goアセンブリでは
X0
,X1
などのように表記されます。 MOVQ
命令: x86-64アーキテクチャにおけるデータ転送命令の一つです。- 汎用レジスタとメモリ間で64ビットデータを移動させる場合(例:
MOVQ RAX, [RSP+8]
)。 - XMMレジスタと汎用レジスタ間で64ビットデータを移動させる場合(例:
MOVQ XMM0, RAX
)。 - XMMレジスタとメモリ間で64ビットデータを移動させる場合(例:
MOVQ XMM0, [RSP+8]
)。 - XMMレジスタ間で64ビットデータを移動させる場合(例:
MOVQ XMM0, XMM1
)。この形式は、SSE2以降でサポートされます。
- 汎用レジスタとメモリ間で64ビットデータを移動させる場合(例:
MOVD
命令: 通常は32ビットデータを汎用レジスタとXMMレジスタ間で移動させる命令です。REX.W
プレフィックス: x86-64アーキテクチャで導入された命令プレフィックスの一つです。命令のオペランドサイズを64ビットに拡張したり、追加のレジスタ(R8-R15, XMM8-XMM15など)にアクセスするために使用されます。MOVD
にREX.W
を付加することで、64ビットデータを扱うMOVD
として機能させることができます。- 命令エンコーディング: 各アセンブリ命令は、CPUが解釈できる特定のバイト列(オペコードとオペランド)に変換されます。リンカは、この命令エンコーディングを正しく生成する責任があります。
- XMMレジスタ: SSE(Streaming SIMD Extensions)以降の命令セットで導入された128ビットレジスタです。主に浮動小数点数やSIMD演算に使用されます。XMM0からXMM15まで存在します。Goアセンブリでは
-
リンカの役割:
- リンカは、コンパイラが生成した個々のオブジェクトファイル(機械語コードとシンボル情報を含む)を結合し、最終的な実行可能ファイルを生成します。
- この過程で、リンカはシンボル解決(関数呼び出しや変数参照を正しいメモリアドレスに解決する)や、命令の再配置(アドレスが確定した後に、相対アドレス指定の命令のオフセットを修正する)などを行います。
- また、リンカは命令の選択とエンコーディングにも関与します。特に、Goのリンカは、アセンブリコードの擬似命令を実際の機械語命令に変換する役割も持っています。
技術的詳細
このコミットの技術的な核心は、6l
リンカがMOVQ
命令の特定の形式、特にXMMレジスタ間およびXMMレジスタとメモリ間の64ビットデータ転送を、より適切に処理できるようにすることにあります。
x86-64アーキテクチャにおいて、MOVQ
命令は複数の形式を持ちます。
MOVQ r/m64, r64
(汎用レジスタ/メモリから汎用レジスタ)MOVQ r64, r/m64
(汎用レジスタから汎用レジスタ/メモリ)MOVQ xmm, r/m64
(XMMレジスタから汎用レジスタ/メモリ) - オペコードF3 0F 7E /r
MOVQ r/m64, xmm
(汎用レジスタ/メモリからXMMレジスタ) - オペコード66 0F 6E /r
MOVQ xmm1, xmm2
(XMMレジスタからXMMレジスタ) - オペコードF3 0F 7E /r
(ただし、ソースとデスティネーションが両方XMMレジスタの場合)
コミットメッセージで言及されている「REX.W MOVD
よりもネイティブなMOVQ
が優先される」という点は重要です。MOVD
命令は通常32ビットデータを扱いますが、REX.W
プレフィックスを付加することで64ビットデータを扱うことができます。しかし、XMMレジスタ間の64ビット移動には、F3 0F 7E
オペコードを持つMOVQ
命令がより直接的で適切な命令です。リンカがこのネイティブなMOVQ
命令を正しく認識し、生成できるようにすることで、より効率的で意図通りの機械語が生成されます。
変更点を見ると、src/cmd/6l/l.h
でOptab
構造体のop
配列のサイズが20から22に拡張されています。これは、MOVQ
命令の新しい形式をエンコードするために、より多くのオペコードバイトが必要になったことを示唆しています。
src/cmd/6l/optab.c
では、ymovq
というオペランドタイプ配列に新しいエントリが追加されています。
Yxm, Yxr, Zm_r_xm_nr, 2, // MOVQ xmm1/m64 -> xmm2
Yxr, Yxm, Zr_m_xm_nr, 2, // MOVQ xmm1 -> xmm2/m64
ここで、Yxm
はXMMレジスタまたは64ビットメモリオペランドを、Yxr
はXMMレジスタを指すGoリンカ内部の型定義です。Zm_r_xm_nr
とZr_m_xm_nr
は、それぞれメモリ/レジスタからXMMレジスタへ、XMMレジスタからメモリ/レジスタへの移動に対応するオペコードパターンを示しています。これらの追加により、リンカはMOVQ
命令のこれらの新しいオペランド組み合わせを認識し、適切な機械語に変換できるようになります。
また、optab
配列内のAMOVQ
エントリのオペコードシーケンスが変更され、Pf3,0x7e
が追加されています。Pf3
はF3
プレフィックス(MOVQ
命令でよく使われる)を、0x7e
はMOVQ
命令の主要なオペコードの一部を示します。これにより、MOVQ xmm_reg, xmm_reg
のような命令が正しくエンコードされるようになります。
src/cmd/6l/span.c
では、ycover
配列から汎用レジスタ(Yax
, Ycx
, Yrx
, Yrl
)とXMMレジスタ(Yxm
)の組み合わせに関するエントリが削除されています。ycover
は、リンカが命令のオペランドタイプをカバーしているかどうかを追跡するための内部データ構造です。これらのエントリの削除は、新しいMOVQ
ハンドラが導入されたことで、これらの特定の汎用レジスタからXMMレジスタへの移動が、より一般的なMOVQ
のルールによって処理されるようになったか、あるいは以前の定義が新しい命令エンコーディングと競合していたため、冗長または不正確になったことを示唆しています。
提供されたテストアセンブリと生成された機械語は、これらの変更が意図通りに機能することを示しています。
MOVQ X0, X1
->f3 0f 7e c8 movq %xmm0,%xmm1
(ネイティブなXMM間MOVQ)MOVQ AX, X1
->66 48 0f 6e c8 movq %rax,%xmm1
(汎用レジスタからXMMレジスタへのMOVQ)MOVQ X1, AX
->66 48 0f 7e c8 movq %xmm1,%rax
(XMMレジスタから汎用レジスタへのMOVQ)MOVQ xxx+8(FP), X2
->f3 0f 7e 54 24 10 movq 0x10(%rsp),%xmm2
(メモリからXMMレジスタへのMOVQ)MOVQ X2, xxx+8(FP)
->66 0f d6 54 24 10 movq %xmm2,0x10(%rsp)
(XMMレジスタからメモリへのMOVQ)
これらの逆アセンブル結果は、6l
がMOVQ
命令の様々な形式を正しくエンコードできるようになったことを明確に示しています。特に、f3 0f 7e
と66 0f 6e/7e
といったオペコードが適切に選択されていることが確認できます。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更は、主に以下の3つのファイルに分散しています。
-
src/cmd/6l/l.h
:struct Optab
定義内のop
配列のサイズが20
から22
に拡張されました。これは、新しいMOVQ
命令のエンコーディングに必要なバイト数が増加したためです。
--- a/src/cmd/6l/l.h +++ b/src/cmd/6l/l.h @@ -163,7 +163,7 @@ struct Optab short as; uchar* ytab; uchar prefix; - uchar op[20]; + uchar op[22]; }; struct Movtab {
-
src/cmd/6l/optab.c
:ymovq
オペランドタイプ配列に、XMMレジスタ間およびXMMレジスタとメモリ間のMOVQ
命令を処理するための新しいエントリが追加されました。AMOVQ
命令のオペコード定義が更新され、Pf3,0x7e
が追加されました。
--- a/src/cmd/6l/optab.c +++ b/src/cmd/6l/optab.c @@ -200,7 +200,8 @@ uchar ymovq[] = Ymm, Ymr, Zm_r_xm, 1, // MMX MOVD Ymr, Ymm, Zr_m_xm, 1, // MMX MOVD Yxr, Ymr, Zm_r_xm_nr, 2, // MOVDQ2Q - Yxr, Ym, Zr_m_xm_nr, 2, // MOVQ xmm store + Yxm, Yxr, Zm_r_xm_nr, 2, // MOVQ xmm1/m64 -> xmm2 + Yxr, Yxm, Zr_m_xm_nr, 2, // MOVQ xmm1 -> xmm2/m64 Yml, Yxr, Zm_r_xm, 2, // MOVD xmm load Yxr, Yml, Zr_m_xm, 2, // MOVD xmm store Yiauto, Yrl, Zaut_r, 2, // built-in LEAQ @@ -862,7 +863,7 @@ Optab optab[] = { AMOVNTPD, yxr_ml, Pe, 0x2b }, { AMOVNTPS, yxr_ml, Pm, 0x2b }, { AMOVNTQ, ymr_ml, Pm, 0xe7 }, - { AMOVQ, ymovq, Pw, 0x89,0x8b,0x31,0xc7,(00),0xb8,0xc7,(00),0x6f,0x7f,0x6e,0x7e,Pf2,0xd6,Pe,0xd6,Pe,0x6e,Pe,0x7e }, + { AMOVQ, ymovq, Pw, 0x89, 0x8b, 0x31, 0xc7,(00), 0xb8, 0xc7,(00), 0x6f, 0x7f, 0x6e, 0x7e, Pf2,0xd6, Pf3,0x7e, Pe,0xd6, Pe,0x6e, Pe,0x7e }, { AMOVQOZX, ymrxr, Pf3, 0xd6,0x7e }, { AMOVSB, ynone, Pb, 0xa4 }, { AMOVSD, yxmov, Pf2, 0x10,0x11 },
-
src/cmd/6l/span.c
:instinit
関数内で、汎用レジスタからXMMレジスタへの特定のycover
エントリが削除されました。
--- a/src/cmd/6l/span.c +++ b/src/cmd/6l/span.c @@ -266,10 +266,6 @@ instinit(void) ycover[Ym*Ymax + Ymm] = 1; ycover[Ymr*Ymax + Ymm] = 1; - ycover[Yax*Ymax + Yxm] = 1; - ycover[Ycx*Ymax + Yxm] = 1; - ycover[Yrx*Ymax + Yxm] = 1; - ycover[Yrl*Ymax + Yxm] = 1; ycover[Ym*Ymax + Yxm] = 1; ycover[Yxr*Ymax + Yxm] = 1;
コアとなるコードの解説
src/cmd/6l/l.h
の変更
Optab
構造体は、Goリンカが各アセンブリ命令のオペコード、オペランドタイプ、プレフィックスなどを定義するために使用するテーブルのエントリを表現します。op
フィールドは、命令の機械語エンコーディング(オペコードバイト列)を格納するための配列です。この配列のサイズが20
から22
に拡張されたのは、MOVQ
命令の新しい形式、特にXMMレジスタ間の移動や、XMMレジスタとメモリ間の移動をサポートするために、より複雑なオペコードシーケンスが必要になったためです。これは、リンカがより多くの命令バリエーションを正確にエンコードできるようにするための基盤となる変更です。
src/cmd/6l/optab.c
の変更
このファイルは、Goリンカがサポートするすべてのアセンブリ命令とそのオペランドの組み合わせ、およびそれらに対応する機械語オペコードを定義する中心的な場所です。
-
ymovq
配列への追加:ymovq
は、MOVQ
命令の様々なオペランドの組み合わせを定義する配列です。Yxm, Yxr, Zm_r_xm_nr, 2, // MOVQ xmm1/m64 -> xmm2
この行は、ソースオペランドがXMMレジスタまたは64ビットメモリ(Yxm
)で、デスティネーションオペランドがXMMレジスタ(Yxr
)であるMOVQ
命令のハンドラを追加します。Zm_r_xm_nr
は、このオペランドの組み合わせに対応するオペコードパターンを示し、2
はオペランドのサイズ(64ビット)を示します。これは、MOVQ xxx+8(FP), X2
のような命令を処理するために必要です。Yxr, Yxm, Zr_m_xm_nr, 2, // MOVQ xmm1 -> xmm2/m64
この行は、ソースオペランドがXMMレジスタ(Yxr
)で、デスティネーションオペランドがXMMレジスタまたは64ビットメモリ(Yxm
)であるMOVQ
命令のハンドラを追加します。Zr_m_xm_nr
は、このオペランドの組み合わせに対応するオペコードパターンを示します。これは、MOVQ X2, xxx+8(FP)
のような命令を処理するために必要です。 これらの追加により、6l
はXMMレジスタとメモリ間の64ビットデータ転送を正しく処理できるようになります。
-
AMOVQ
エントリの更新:optab
配列は、すべてのアセンブリ命令(AMOVQ
はMOVQ
命令に対応)の主要な定義を含みます。 変更前:Pf2,0xd6,Pe,0xd6,Pe,0x6e,Pe,0x7e
変更後:Pf2,0xd6, Pf3,0x7e, Pe,0xd6, Pe,0x6e, Pe,0x7e
ここで追加されたPf3,0x7e
は、MOVQ xmm_reg, xmm_reg
命令のエンコーディングに直接対応します。Pf3
は、命令の前にF3
プレフィックスを付加することを示します。F3
プレフィックスは、MOVQ
命令がXMMレジスタ間で64ビットデータを移動させる際に使用される重要なプレフィックスです。0x7e
は、MOVQ
命令の主要なオペコードの一部です。 この変更により、リンカはMOVQ X0, X1
のようなXMMレジスタ間の移動を、f3 0f 7e c8
という機械語に正しく変換できるようになります。これは、REX.W MOVD
のような代替手段ではなく、ネイティブなMOVQ
命令を優先するというコミットの意図を反映しています。
src/cmd/6l/span.c
の変更
span.c
は、リンカが命令のサイズを計算し、コードを配置する際に使用する情報(例えば、オペランドの型が互換性があるかなど)を初期化する部分を含みます。
ycover
エントリの削除:ycover
配列は、特定のオペランドタイプの組み合わせが、リンカによって「カバーされている」(つまり、処理可能である)ことを示すために使用されます。削除された行は以下の通りです。ycover[Yax*Ymax + Yxm] = 1;
ycover[Ycx*Ymax + Yxm] = 1;
ycover[Yrx*Ymax + Yxm] = 1;
ycover[Yrl*Ymax + Yxm] = 1;
これらの行は、汎用レジスタ(Yax
,Ycx
,Yrx
,Yrl
)からXMMレジスタ(Yxm
)への移動がカバーされていることを示していました。これらのエントリが削除された理由は、新しいMOVQ
ハンドラがより包括的にXMMレジスタ関連の移動を処理するようになったため、これらの特定の汎用レジスタからXMMレジスタへの移動が、より一般的なルールによって処理されるようになったか、あるいは以前の定義が新しい命令エンコーディングと競合していたため、冗長または不正確になったためと考えられます。これにより、リンカの内部ロジックが簡素化され、新しいMOVQ
ハンドラに一元化された可能性があります。
これらの変更全体として、6l
リンカがx86-64アーキテクチャにおけるMOVQ
命令のXMMレジスタ関連の形式を、より正確かつ効率的に処理できるようになり、Goアセンブリコードのコンパイルとリンクの信頼性が向上しました。
関連リンク
- Go Issue #2418: https://github.com/golang/go/issues/2418 このコミットが修正したGo言語のIssueです。タイトルは "cmd/6l: MOVQ xmm_reg, xmm_reg not handled" であり、コミットメッセージの内容と完全に一致しています。
- Go Change List 5316076: https://golang.org/cl/5316076 このコミットに対応するGoのChange List(コードレビューシステムのエントリ)です。
参考にした情報源リンク
- x86 Instruction Set Reference:
MOVQ
命令に関する詳細な情報(オペコード、オペランド形式など)は、IntelまたはAMDのx86-64アーキテクチャの命令セットリファレンスマニュアルで確認できます。- Intel® 64 and IA-32 Architectures Software Developer’s Manuals: https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html
- Go Assembly Language:
- Go言語のアセンブリに関する公式ドキュメントやチュートリアルは、Goの公式サイトで参照できます。
- Go Assembly Language (by Rob Pike): https://go.dev/doc/asm
- Go言語のアセンブリに関する公式ドキュメントやチュートリアルは、Goの公式サイトで参照できます。
- Go Source Code:
- Go言語のリンカ
6l
のソースコードは、Goのリポジトリで公開されています。- Go GitHub Repository: https://github.com/golang/go
- Go言語のリンカ
- Go Toolchain Documentation:
- Goのビルドプロセスやツールチェインに関する情報は、Goの公式ドキュメントで提供されています。
- Go Command Documentation: https://go.dev/cmd/
- Goのビルドプロセスやツールチェインに関する情報は、Goの公式ドキュメントで提供されています。
- Stack Overflow / Technical Blogs:
- 特定の命令やリンカの挙動に関する技術的な議論は、Stack Overflowや専門の技術ブログで参照されることがあります。
- 例: "MOVQ xmm_reg, xmm_reg instruction"
- 例: "REX.W prefix x86"
- 例: "Go 6l linker internals" (具体的なURLは検索結果によるため、ここでは一般的な情報源として記載)
- 特定の命令やリンカの挙動に関する技術的な議論は、Stack Overflowや専門の技術ブログで参照されることがあります。
- Wikipedia:
- x86-64アーキテクチャ、SSE命令セット、アセンブリ言語の基本概念に関する一般的な情報はWikipediaで確認できます。
- x86-64: https://ja.wikipedia.org/wiki/X86-64
- Streaming SIMD Extensions: https://ja.wikipedia.org/wiki/Streaming_SIMD_Extensions
- アセンブリ言語: https://ja.wikipedia.org/wiki/%Eアセンブリ言語 (具体的なURLは検索結果によるため、ここでは一般的な情報源として記載)
- x86-64アーキテクチャ、SSE命令セット、アセンブリ言語の基本概念に関する一般的な情報はWikipediaで確認できます。
これらの情報源は、コミットの背景にある技術的な詳細を深く理解するために役立ちます。 I have completed the detailed explanation of the commit following all the instructions. I have included all the required sections in the specified order, provided a comprehensive explanation in Japanese, and utilized web search to gather additional context and links. The explanation covers the background, prerequisite knowledge, technical details, core code changes, and related/referenced links.
I believe the task is complete.I have generated the commit explanation as requested, following all instructions and including all specified sections in Japanese.