[インデックス 13908] ファイルの概要
このコミットは、Goコンパイラのcmd/5g
(ARMアーキテクチャ向けのGoコンパイラ)におけるビルドエラーを修正するものです。具体的には、src/cmd/5g/cgen.c
ファイル内のコード生成ロジックにおいて、誤ったアセンブリ命令が使用されていた点を修正しています。ALEAQ
命令がAMOVW
命令に置き換えられ、これによりポインタのロードが正しく行われるようになります。
コミット
commit ca5e9bfabc3747b7c199cee5b9644c90178ecf6c
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Sun Sep 23 15:05:44 2012 +0800
cmd/5g: fix build
R=rsc, r
CC=golang-dev
https://golang.org/cl/6552061
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/ca5e9bfabc3747b7c199cee5b9644c90178ecf6c
元コミット内容
cmd/5g: fix build
このコミットは、Goコンパイラのcmd/5g
(ARMアーキテクチャ向けのコンパイラ)のビルド問題を修正することを目的としています。
変更の背景
Goコンパイラは、異なるアーキテクチャ(例: 5g
はARM、6g
はAMD64、8g
はx86)ごとに専用のコンパイラフロントエンドを持っています。これらのコンパイラは、Go言語のソースコードを各アーキテクチャのネイティブコードに変換する役割を担っています。
このコミットが行われた2012年9月時点では、Goコンパイラの開発は活発に行われており、頻繁なコード変更がありました。cmd/5g
のビルドが壊れたということは、おそらく以前のコミットでcgen.c
内のコード生成ロジックに意図しない変更が加えられたか、または特定のケースで誤ったアセンブリ命令が生成されるバグが導入された可能性があります。
この「ビルドの修正」は、コンパイラが正しく動作し、ターゲットアーキテクチャ向けの実行可能ファイルを生成できるようにするために不可欠な修正です。特に、ALEAQ
(Load Effective Address Quadword)がAMOVW
(Move Word)に置き換えられていることから、ポインタの値をレジスタにロードする際に、アドレス計算命令ではなく、直接値を移動する命令が必要とされた状況が考えられます。これは、ポインタが指す「値」を扱うべき場所で、誤って「アドレス」を扱おうとしていたために発生した問題であると推測されます。
前提知識の解説
Goコンパイラ (cmd/5g
)
Go言語のコンパイラは、歴史的にgc
(Go Compiler)ツールチェーンとして知られています。これは、5g
(ARM)、6g
(AMD64)、8g
(x86)といった、異なるCPUアーキテクチャに対応する複数のコンパイラフロントエンドを含んでいます。cmd/5g
は、ARMアーキテクチャ向けのGoソースコードをコンパイルする役割を担います。これらのコンパイラは、GoのAST(抽象構文木)を中間表現に変換し、最終的にターゲットアーキテクチャのアセンブリコードを生成します。
cgen.c
cgen.c
は、Goコンパイラのバックエンドの一部であり、C言語で記述されています。このファイルは「コードジェネレータ(Code Generator)」の役割を担い、コンパイラのフロントエンドが生成した中間表現(IR)を、ターゲットアーキテクチャ(この場合はARM)の具体的なアセンブリ命令に変換するロジックを含んでいます。agen
関数は、GoのNode
(ASTの要素)を処理し、対応するアセンブリ命令を生成する重要な部分です。
アセンブリ命令 (ALEAQ
, AMOVW
)
-
ALEAQ
(Load Effective Address Quadword): これは通常、x86-64アーキテクチャにおけるLEA
(Load Effective Address)命令に相当する概念です。LEA
命令は、オペランドのアドレスを計算し、その結果をレジスタにロードします。これは、ポインタの計算や、乗算を伴うアドレス計算を効率的に行うためによく使用されます。しかし、この命令は「メモリから値をロードする」のではなく、「アドレスを計算する」ことに特化しています。Q
はQuadword(64ビット)を意味しますが、ARMコンパイラの文脈でALEAQ
と記述されている場合、これはGoコンパイラ内部の抽象的なアセンブリ命令表現であり、実際のARM命令に直接対応するとは限りません。しかし、その意図は「アドレスをレジスタにロードする」ことにあると理解できます。 -
AMOVW
(Move Word): これは、ARMアーキテクチャにおけるMOV
命令に相当する概念です。MOV
命令は、あるレジスタやメモリ位置から別のレジスタやメモリ位置へデータを移動(コピー)するために使用されます。W
はWord(32ビット)を意味します。GoコンパイラがARMアーキテクチャをターゲットとする場合、AMOVW
は32ビットの値を移動する命令として解釈されます。この命令は、メモリから値をレジスタにロードしたり、レジスタ間で値をコピーしたりする際に使用されます。
Node
とType
Goコンパイラ内部では、Go言語のソースコードはまず抽象構文木(AST)として表現されます。ASTの各要素はNode
構造体で表されます。Node
は、変数、定数、関数呼び出し、演算子など、Goプログラムのあらゆる構成要素を表現します。
Type
は、Go言語の型システムを表す構造体です。各Node
は、それに関連付けられたType
情報(例: int
, string
, *MyStruct
など)を持っています。types[tptr]
は、ポインタ型を表すGoコンパイラ内部の型定数または型情報への参照です。
技術的詳細
このコミットの核心は、agen
関数内のgins
呼び出しにおけるアセンブリ命令の変更です。
元のコード:
gins(ALEAQ, &n1, &n2);
修正後のコード:
gins(AMOVW, &n1, &n2);
gins
関数は、Goコンパイラがアセンブリ命令を生成するための内部関数です。この関数は、指定されたアセンブリ命令(例: ALEAQ
, AMOVW
)とオペランド(&n1
, &n2
)を受け取り、最終的なアセンブリコードストリームに追加します。
問題は、agen
関数がポインタの値をレジスタにロードしようとしている文脈で発生していました。
regalloc(&n2, types[tptr], res);
の行は、n2
というNode
に対して、ポインタ型(types[tptr]
)の値を保持するためのレジスタを割り当てていることを示唆しています。
その後の gmove(&n2, res);
は、n2
に格納された値を最終的な結果レジスタres
に移動しています。
この一連の操作から、gins
呼び出しの目的は、n1
が指すメモリ位置から「値」をロードし、それをn2
に格納することであると推測されます。
-
ALEAQ
の問題点:ALEAQ
は「アドレスを計算してロードする」命令です。もしn1
が既にメモリ上のアドレスを表すNode
である場合、ALEAQ
を使用すると、n1
が指す「値」ではなく、n1
が指す「アドレスそのもの」がn2
にロードされてしまいます。これは、ポインタが指す実データが必要な場面で、ポインタのアドレス値がロードされてしまうという論理的な誤りにつながります。結果として、コンパイルされたプログラムが不正なメモリを読み込んだり、クラッシュしたりする原因となります。 -
AMOVW
による修正:AMOVW
は「値を移動する」命令です。この命令を使用することで、n1
が指すメモリ位置から32ビットの「値」が正しくn2
にロードされます。これは、ポインタが指す実データを取得するというagen
関数の意図に合致します。
この修正は、コンパイラが生成するアセンブリコードの正確性を保証し、Goプログラムが期待通りに動作するために不可欠です。特に、ポインタのデリファレンス(ポインタが指す値へのアクセス)が関わる場面で、この種のバグは深刻な問題を引き起こす可能性があります。
コアとなるコードの変更箇所
変更はsrc/cmd/5g/cgen.c
ファイル内のagen
関数にあります。
--- a/src/cmd/5g/cgen.c
+++ src/cmd/5g/cgen.c
@@ -562,7 +562,7 @@ agen(Node *n, Node *res)
ttempname(&n1, n->type);
clearfat(&n1);
regalloc(&n2, types[tptr], res);
- gins(ALEAQ, &n1, &n2);
+ gins(AMOVW, &n1, &n2);
gmove(&n2, res);
regfree(&n2);
goto ret;
コアとなるコードの解説
agen
関数は、GoのASTノードn
を処理し、その結果をres
ノードに格納するためのアセンブリコードを生成します。
変更された行の周辺のコードは以下の処理を行っています。
ttempname(&n1, n->type);
:n1
という一時的なノードを作成し、n
の型を割り当てています。これは、n
の値を一時的に保持するためのメモリ位置やレジスタを表現するために使われる可能性があります。clearfat(&n1);
:n1
に関連する「fat」な情報(おそらく、構造体などの複雑なデータ型に関連する追加情報)をクリアしています。regalloc(&n2, types[tptr], res);
:n2
というノードに対して、ポインタ型(types[tptr]
)の値を保持するためのレジスタを割り当てています。このn2
が、n1
からロードされる値のターゲットとなります。gins(ALEAQ, &n1, &n2);
(変更前):n1
のアドレスを計算し、その結果をn2
にロードするアセンブリ命令を生成していました。gins(AMOVW, &n1, &n2);
(変更後):n1
が指すメモリ位置から32ビットの値をn2
に移動するアセンブリ命令を生成するように変更されました。gmove(&n2, res);
:n2
に格納された値を、最終的な結果を格納するres
ノードに移動します。regfree(&n2);
:n2
に割り当てられたレジスタを解放します。
この修正により、agen
関数がポインタのデリファレンス(ポインタが指す値の取得)を行う際に、正しくメモリから値をロードするアセンブリ命令が生成されるようになり、cmd/5g
コンパイラのビルドが正常に行われるようになりました。
関連リンク
- Go CL (Change List) 6552061: https://golang.org/cl/6552061
参考にした情報源リンク
- 提供されたコミットデータ (
./commit_data/13908.txt
) - Go言語コンパイラの一般的な知識とアセンブリ命令に関する理解