[インデックス 10652] ファイルの概要
このコミットは、Go言語の標準ライブラリ bytes パッケージ内の Equal 関数におけるアセンブリコードの修正に関するものです。具体的には、src/pkg/bytes/asm_amd64.s ファイル内の Equal 関数の実装が変更されています。このファイルは、AMD64アーキテクチャ向けに最適化されたアセンブリコードを含んでおり、bytes.Equal のようなパフォーマンスが重要な関数で利用されます。
コミット
このコミットは、以前の変更セット(CL: Change List)で適用されたはずの編集が失われたため、それを再適用するものです。bytes.Equal 関数のアセンブリ実装において、特定のレジスタへの値のロード順序が修正されています。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1278c6c0556773dab1b126f35e0b5909d8d3929f
元コミット内容
commit 1278c6c0556773dab1b126f35e0b5909d8d3929f
Author: Russ Cox <rsc@golang.org>
Date: Wed Dec 7 15:30:01 2011 -0500
bytes: lost edit from earlier CL
R=iant
CC=golang-dev
https://golang.org/cl/5450125
変更の背景
このコミットの背景は、コミットメッセージにある「lost edit from earlier CL」(以前の変更セットからの失われた編集)という記述に集約されます。これは、Go言語の開発プロセスにおいて、以前に承認・適用されたはずのコード変更が、何らかの理由(例えば、リベースの誤り、マージの競合、あるいは単なる見落としなど)で失われ、それがこのコミットで再適用されたことを意味します。
具体的には、bytes.Equal 関数のAMD64アセンブリ実装において、比較結果を格納するためのレジスタ(DX)への値のロードが、意図したタイミングで行われていなかったか、あるいはその値が途中で上書きされる可能性があったと考えられます。この修正は、DXレジスタに1(真、等しいことを示す値)をロードする命令の配置を、その値が実際に使用される直前に移動させることで、関数の正確性と堅牢性を確保することを目的としています。このような修正は、特にアセンブリレベルの最適化されたコードにおいて、レジスタのライフタイムや命令の順序がパフォーマンスや正確性に直接影響するため、非常に重要です。
前提知識の解説
1. bytes.Equal 関数
Go言語の標準ライブラリ bytes パッケージに含まれる Equal 関数は、2つのバイトスライスが等しいかどうかを比較します。これは、バイトスライスの長さが同じであり、かつすべての要素が同じである場合に真を返します。この関数は、文字列比較やバイナリデータの比較など、様々な場面で頻繁に利用されるため、高いパフォーマンスが求められます。
2. アセンブリ言語 (x86-64)
アセンブリ言語は、CPUが直接実行できる機械語に1対1で対応する低レベルのプログラミング言語です。x86-64は、IntelおよびAMDの64ビットプロセッサアーキテクチャを指します。Go言語では、パフォーマンスが非常に重要な一部の関数(例えば、メモリ操作、並行処理プリミティブ、特定の標準ライブラリ関数など)において、C言語やGo言語自体ではなく、直接アセンブリ言語で実装されることがあります。これにより、CPUの特性を最大限に活用し、最高の実行速度を実現できます。
このコミットで登場する主なx86-64アセンブリ命令は以下の通りです。
TEXT ·Equal(SB),7,$0: Goのアセンブリ構文で、Equalという名前の関数を定義します。SBはStatic Baseレジスタで、グローバルシンボルへの参照に使われます。7はスタックフレームのサイズ、$0は引数のサイズを示します。MOVL(Move Long): 32ビットの値をレジスタまたはメモリ間で移動させます。MOVQ(Move Quad): 64ビットの値をレジスタまたはメモリ間で移動させます。CMPL(Compare Long): 2つの32ビットの値を比較し、CPUのフラグレジスタ(Zero Flag, Carry Flagなど)を設定します。比較結果は、その後の条件分岐命令(JNEなど)で使用されます。JNE(Jump if Not Equal): 直前の比較命令の結果、Zero Flagがクリアされている(値が等しくない)場合に、指定されたラベルにジャンプします。SI(Source Index) /DI(Destination Index): 文字列操作命令(CMPSBなど)で、それぞれソースとデスティネーションのメモリアドレスを指すために使用されるレジスタです。CLD(Clear Direction Flag): 方向フラグをクリアします。これにより、CMPSBのような文字列操作命令が、アドレスをインクリメント(前方へ移動)するようになります。REP; CMPSB(Repeat Compare String Byte):CMPSB命令をCXレジスタの値がゼロになるまで繰り返します。CMPSBは、SIが指すバイトとDIが指すバイトを比較し、両ポインタをインクリメントします。REPプレフィックスは、CXがゼロになるか、比較が不一致になるまでこの操作を繰り返します。すべてのバイトが一致した場合、Zero Flagがセットされます。CMOVLEQ(Conditional Move if Less or Equal): 条件付き移動命令の一つで、Zero Flagがセットされている(等しい)場合に、ソースレジスタの値をデスティネーションレジスタに移動させます。この場合、REP; CMPSBがすべてのバイトが等しいと判断した場合に、DXの値をAXに移動させます。AX(Accumulator Register): 通常、関数の戻り値を格納するために使用されるレジスタです。DX(Data Register): 汎用レジスタの一つで、このコンテキストでは比較結果の真偽値(1または0)を一時的に保持するために使用されています。
3. Go言語におけるアセンブリの利用
Go言語は、多くの部分がGo自身で書かれていますが、一部の低レベルな操作やパフォーマンスクリティカルな部分では、C言語やアセンブリ言語が使用されます。これは、Goのランタイム(ガベージコレクタ、スケジューラなど)や、bytesパッケージのような標準ライブラリの一部で顕著です。アセンブリコードは、特定のCPUアーキテクチャに特化して最適化された命令シーケンスを提供し、Goのポータビリティとパフォーマンスのバランスを取る上で重要な役割を果たします。
技術的詳細
このコミットは、bytes.Equal 関数のAMD64アセンブリ実装における、DXレジスタへの値のロードタイミングの修正です。
bytes.Equal のアセンブリ実装の一般的なロジックは以下のようになります。
- まず、比較する2つのバイトスライスの長さを比較します。
- 長さが異なる場合、スライスは等しくないので、すぐに
false(0)を返します。 - 長さが同じ場合、
REP; CMPSB命令を使用して、バイトスライスの内容をバイトごとに比較します。この命令は、CXレジスタに格納された長さの回数だけ、SIとDIが指すメモリ位置のバイトを比較し、両ポインタをインクリメントします。 REP; CMPSBの実行後、すべてのバイトが一致していればCPUのZero Flag (ZF) がセットされます。- 最後に、
CMOVLEQ DX, AX命令を使って、Zero Flagの状態に基づいて戻り値(AXレジスタ)を設定します。もしZFがセットされていれば(つまり、すべてのバイトが等しければ)、DXレジスタの値(この場合は1、真を表す)がAXに移動されます。そうでなければ、AXは初期値の0(偽を表す)のままです。
このコミットの変更点は、MOVL $1, DX(DXレジスタに1をロードする命令)の配置です。
変更前:
MOVL $1, DX は、長さの比較(CMPL BX, CX)の前に配置されていました。
変更後:
MOVL $1, DX は、REP; CMPSB 命令の直後、かつ CMOVLEQ DX, AX 命令の直前に移動されました。
この変更の技術的な意味合いは、DXレジスタの値がCMOVLEQ命令によって使用される直前に、確実に1に設定されることを保証することにあります。REP; CMPSB命令は、主にSI、DI、CXレジスタとCPUフラグに影響を与えますが、DXレジスタの値を直接変更することはありません。しかし、アセンブリコードの複雑な最適化や、コンパイラ/リンカの挙動によっては、DXレジスタが他の目的で一時的に使用され、その値が意図せず変更される可能性がゼロではありません。
MOVL $1, DXをCMOVLEQの直前に配置することで、DXレジスタがCMOVLEQによって読み取られる瞬間に、その値が確実に1であることを保証できます。これは、コードの堅牢性を高め、潜在的なバグ(特に、以前のCLで失われた編集が示唆するように、特定の条件下でbytes.Equalが誤った結果を返す可能性)を防ぐための修正と考えられます。このような低レベルの修正は、Go言語のランタイムや標準ライブラリの安定性と正確性を維持するために不可欠です。
コアとなるコードの変更箇所
src/pkg/bytes/asm_amd64.s ファイル内の TEXT ·Equal(SB),7,$0 関数において、以下の変更が行われました。
--- a/src/pkg/bytes/asm_amd64.s
+++ b/src/pkg/bytes/asm_amd64.s
@@ -94,13 +94,13 @@ TEXT ·Equal(SB),7,$0
MOVL len+8(FP), BX
MOVL len1+24(FP), CX
MOVL $0, AX
- MOVL $1, DX
CMPL BX, CX
JNE eqret
MOVQ p+0(FP), SI
MOVQ q+16(FP), DI
CLD
REP; CMPSB
+ MOVL $1, DX
CMOVLEQ DX, AX
eqret:
MOVB AX, ret+32(FP)
具体的には、MOVL $1, DX の行が、元の位置(MOVL $0, AX の直後)から削除され、REP; CMPSB の直後、かつ CMOVLEQ DX, AX の直前に挿入されました。
コアとなるコードの解説
変更されたアセンブリコードのセクションは、bytes.Equal 関数の実際のバイト比較ロジックを担っています。
MOVL $0, AX: 戻り値レジスタAXを0(偽)で初期化します。これは、デフォルトでスライスが等しくないと仮定するためです。MOVL len+8(FP), BXとMOVL len1+24(FP), CX: 比較する2つのバイトスライスの長さ(lenとlen1)を、それぞれBXとCXレジスタにロードします。CMPL BX, CX:BXとCX(スライスの長さ)を比較します。JNE eqret: 長さが等しくない場合、eqretラベルにジャンプします。この場合、AXは0のままで、falseが返されます。MOVQ p+0(FP), SIとMOVQ q+16(FP), DI: 比較するバイトスライスのポインタ(pとq)を、それぞれSIとDIレジスタにロードします。これらはREP; CMPSB命令のソースとデスティネーションのアドレスとして使用されます。CLD: 方向フラグをクリアし、CMPSBがメモリポインタをインクリメントするように設定します。REP; CMPSB:CXレジスタに格納された長さの回数だけ、SIとDIが指すバイトを比較します。すべてのバイトが一致した場合、Zero Flagがセットされます。MOVL $1, DX(変更後の位置): ここが変更の核心です。REP; CMPSBによるバイト比較が完了した直後に、DXレジスタに1(真)をロードします。これにより、DXの値がCMOVLEQ命令によって使用される直前に、確実に正しい値に設定されることが保証されます。CMOVLEQ DX, AX: Zero Flagがセットされている(つまり、REP; CMPSBがすべてのバイトが等しいと判断した)場合、DXの値(1)をAXに移動します。これにより、AXが1となり、trueが返される準備ができます。Zero Flagがセットされていない場合(バイトが一致しなかった場合)、AXは初期値の0のままです。eqret:: 戻り値の処理を行うラベルです。MOVB AX, ret+32(FP):AXレジスタの値を、関数の戻り値が格納されるスタック上の位置(ret+32(FP))に移動します。
この修正は、DXレジスタのライフタイム管理をより厳密にし、bytes.Equalが常に正確な結果を返すことを保証するための、低レベルながらも重要な改善です。
関連リンク
- Go言語の
bytesパッケージ: https://pkg.go.dev/bytes - Go言語のアセンブリについて (Go Wiki): https://go.dev/doc/asm
- x86-64 アセンブリ命令セットリファレンス (Intel/AMDの公式ドキュメントを参照するのが最も正確ですが、オンラインのリソースも多数存在します)
参考にした情報源リンク
- Go言語のソースコード (特に
src/pkg/bytes/asm_amd64.s): https://github.com/golang/go/blob/master/src/pkg/bytes/asm_amd64.s - Go言語のコミット履歴: https://github.com/golang/go/commits/master
- Go言語のコードレビューシステム (Gerrit): https://go-review.googlesource.com/ (コミットメッセージに記載されているCLリンク
https://golang.org/cl/5450125は、このGerritシステムへのリンクです) - x86-64 アセンブリに関する一般的な知識と命令セットの解説。
- Stack Overflowなどの技術Q&AサイトでのGoのアセンブリや
bytes.Equalに関する議論。