[インデックス 12868] ファイルの概要
このコミットは、Goコンパイラの一部である src/cmd/6c/cgen.c
ファイルに対する変更です。cgen.c
は、Go言語のソースコードをx86-64アーキテクチャ(AMD64)向けの機械語に変換するコード生成器の役割を担っています。具体的には、Goの型システムとアセンブリ命令のマッピング、レジスタ割り当て、メモリ操作などの低レベルな処理を扱います。
コミット
このコミットは、Goコンパイラ(cmd/6c
)における潜在的なコード生成バグを修正するものです。ポインタのコピー処理において、誤って32ビットの移動命令 MOVL
が使用されていた箇所を、正しい64ビットの移動命令 MOVQ
に変更しています。これにより、ポインタの完全な値が正しくコピーされるようになります。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b72c7e943cfbb7296bb182717642072c0e8efd5c
元コミット内容
cmd/6c: fix probable code gen bug
This is a pointer being copied; MOVL can't possibly be right.
R=ken2
CC=golang-dev
https://golang.org/cl/5999043
変更の背景
この変更の背景には、Goコンパイラが生成するアセンブリコードにおけるポインタの扱いに関する潜在的なバグがありました。x86-64アーキテクチャでは、ポインタは通常64ビット(8バイト)の値を持ちます。しかし、src/cmd/6c/cgen.c
内のコード生成ロジックにおいて、ポインタをコピーする際に誤って32ビットのデータを扱う MOVL
命令が使用されていました。
MOVL
命令は32ビットのデータを移動させるためのものであり、64ビットのポインタ全体を正確にコピーするには不適切です。これにより、ポインタの上位32ビットが失われたり、予期せぬ値になったりする可能性があり、プログラムの誤動作やクラッシュにつながる「潜在的なコード生成バグ」と認識されました。この問題を修正し、ポインタのコピーが常に64ビットで行われるようにするために、MOVL
を MOVQ
に置き換える変更が必要となりました。
前提知識の解説
cmd/6c
cmd/6c
は、Go言語の初期のコンパイラツールチェーンの一部であり、x86-64アーキテクチャ(AMD64)向けのGoプログラムをコンパイルするために使用されていました。Go 1.5以降、コンパイラは cmd/compile
という単一のツールに統合され、GOARCH
環境変数に基づいて異なるアーキテクチャをターゲットにするようになりましたが、アセンブリの構文はPlan 9の伝統を多く引き継いでいます。cgen.c
はこのコンパイラのコード生成部分を担当していました。
x86-64アーキテクチャにおけるレジスタとデータサイズ
x86-64アーキテクチャでは、汎用レジスタは64ビット幅です(例: RAX
, RBX
, RCX
, RDX
など)。これらのレジスタは、64ビットのデータ(クワッドワード)、32ビットのデータ(ロングワードまたはダブルワード)、16ビットのデータ(ワード)、8ビットのデータ(バイト)を扱うことができます。
アセンブリ命令 MOVL
と MOVQ
-
MOVL
(Move Longword/Doubleword):- この命令は、32ビット(4バイト)の値を移動するために使用されます。
- x86-64システムで32ビット値を64ビットレジスタに移動する場合、
MOVL
を使用すると、通常、宛先レジスタの上位32ビットはゼロクリアされます。この動作は、Goコンパイラによって正しく利用される重要な特性です。 - 例えば、
MOVL $0, CX
はCX
レジスタの下位32ビットをクリアし、上位32ビットもゼロクリアします。
-
MOVQ
(Move Quadword):- この命令は、64ビット(8バイト)の値を移動するために使用されます。
- Goアセンブリでは、
int64
、uint64
、またはポインタ型など、x86-64システム上で64ビットである型を扱う際にMOVQ
が生成されます。 - ポインタはメモリ上のアドレスを指す64ビットの値であるため、その完全な値をコピーするには
MOVQ
が適切です。
ポインタのコピー
Go言語において、ポインタはメモリ上の特定のアドレスを指す変数です。x86-64アーキテクチャでは、このアドレスは64ビットの数値として表現されます。ポインタを別の変数にコピーするということは、この64ビットのアドレス値を完全に複製することを意味します。もし32ビットの命令でコピーしようとすると、アドレスの上位部分が失われ、不正なメモリ参照を引き起こす可能性があります。
技術的詳細
このコミットの技術的な核心は、ポインタのコピー操作におけるデータサイズの不一致を修正することにあります。
Goコンパイラは、Go言語のソースコードを中間表現に変換し、最終的にターゲットアーキテクチャ(この場合はx86-64)のアセンブリコードを生成します。src/cmd/6c/cgen.c
はこのコード生成フェーズの一部であり、Goの型情報に基づいて適切なアセンブリ命令を選択します。
問題の箇所では、ポインタ型 (t
) の値をコピーする際に、gins(AMOVL, &nod1, &nod2);
というコードが使用されていました。ここで AMOVL
は MOVL
命令に対応します。しかし、ポインタは64ビットのデータであるため、32ビットの MOVL
では不十分でした。
MOVL
が64ビットレジスタに対して実行されると、ソースオペランドの32ビット値が宛先レジスタの下位32ビットにコピーされ、宛先レジスタの上位32ビットはゼロで埋められます。これは、32ビット整数を64ビットレジスタに格納する場合には正しい動作ですが、64ビットのポインタをコピーする場合には、ポインタの上位32ビットが常にゼロになってしまい、元のポインタ値が破壊されることになります。
この問題を解決するために、MOVL
を MOVQ
に変更しました。MOVQ
は64ビットのデータを移動する命令であり、ポインタの完全な64ビット値をソースから宛先へ正確にコピーします。これにより、ポインタのコピー操作が意図通りに機能し、潜在的なバグが解消されます。
この修正は、コンパイラが生成するアセンブリコードの正確性を保証し、Goプログラムがポインタを安全かつ正しく利用できるようにするために不可欠です。
コアとなるコードの変更箇所
--- a/src/cmd/6c/cgen.c
+++ b/src/cmd/6c/cgen.c
@@ -1672,7 +1672,7 @@ copy:
regsalloc(&nod2, nn);\
nn->type = t;\
-\t\tgins(AMOVL, &nod1, &nod2);\
+\t\tgins(AMOVQ, &nod1, &nod2);\
regfree(&nod1);\
\t\tnod2.type = typ(TIND, t);\
コアとなるコードの解説
変更は src/cmd/6c/cgen.c
ファイルの copy:
ラベルが付いたセクション内で行われています。このセクションは、Goコンパイラが値をコピーする際のコード生成ロジックの一部です。
元のコードでは、以下の行がありました。
gins(AMOVL, &nod1, &nod2);
ここで、gins
はアセンブリ命令を生成するための関数です。
AMOVL
は、32ビットの移動命令であるMOVL
を表す定数です。&nod1
はソースオペランド(コピー元の値)を表すノードへのポインタです。&nod2
はデスティネーションオペランド(コピー先の値)を表すノードへのポインタです。
この行は、「nod1
の内容を nod2
に32ビットとして移動する」というアセンブリ命令を生成していました。しかし、コメントにもあるように、nod1
がポインタである場合、ポインタは64ビットの値を持ちます。したがって、32ビットの MOVL
命令ではポインタの完全な値をコピーできませんでした。
修正後のコードは以下のようになっています。
gins(AMOVQ, &nod1, &nod2);
ここで、AMOVQ
は64ビットの移動命令である MOVQ
を表す定数です。この変更により、gins
関数は「nod1
の内容を nod2
に64ビットとして移動する」というアセンブリ命令を生成するようになります。
この修正は、ポインタのコピーが常に64ビットで行われることを保証し、ポインタの上位ビットが失われることによる潜在的なバグを防ぎます。これは、Goプログラムがポインタを扱う際の正確性と信頼性を向上させる上で非常に重要な変更です。
関連リンク
参考にした情報源リンク
- Go Assembly Syntax: https://go.dev/doc/asm
- Go Assembly:
MOVQ
vsMOVL
: https://github.com/golang/go/wiki/GoAssembly (関連情報を含む) - x86-64 Instruction Set Reference (MOVL, MOVQ): (一般的なアセンブリ命令に関する情報源、例: Intel/AMDの公式ドキュメント)
- Go compiler source code (cmd/compile): https://github.com/golang/go/tree/master/src/cmd/compile (現在のコンパイラの構造理解のため)