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

[インデックス 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ビットで行われるようにするために、MOVLMOVQ に置き換える変更が必要となりました。

前提知識の解説

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ビットのデータ(バイト)を扱うことができます。

アセンブリ命令 MOVLMOVQ

  • MOVL (Move Longword/Doubleword):

    • この命令は、32ビット(4バイト)の値を移動するために使用されます。
    • x86-64システムで32ビット値を64ビットレジスタに移動する場合、MOVL を使用すると、通常、宛先レジスタの上位32ビットはゼロクリアされます。この動作は、Goコンパイラによって正しく利用される重要な特性です。
    • 例えば、MOVL $0, CXCX レジスタの下位32ビットをクリアし、上位32ビットもゼロクリアします。
  • MOVQ (Move Quadword):

    • この命令は、64ビット(8バイト)の値を移動するために使用されます。
    • Goアセンブリでは、int64uint64、またはポインタ型など、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); というコードが使用されていました。ここで AMOVLMOVL 命令に対応します。しかし、ポインタは64ビットのデータであるため、32ビットの MOVL では不十分でした。

MOVL が64ビットレジスタに対して実行されると、ソースオペランドの32ビット値が宛先レジスタの下位32ビットにコピーされ、宛先レジスタの上位32ビットはゼロで埋められます。これは、32ビット整数を64ビットレジスタに格納する場合には正しい動作ですが、64ビットのポインタをコピーする場合には、ポインタの上位32ビットが常にゼロになってしまい、元のポインタ値が破壊されることになります。

この問題を解決するために、MOVLMOVQ に変更しました。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プログラムがポインタを扱う際の正確性と信頼性を向上させる上で非常に重要な変更です。

関連リンク

参考にした情報源リンク