[インデックス 12152] ファイルの概要
このコミットは、Go言語のARMアーキテクチャ向けアセンブラである5a
における特定のバグ("same arm bug")を修正するものです。具体的には、src/cmd/5a/a.y
ファイル内のコード生成ロジックにおいて、outcode
関数に渡されるレジスタ指定の引数をNREG
から0
に変更することで、誤ったアセンブリコードが生成される問題を解決しています。
コミット
commit 123130f7894993587d3049b90d93c2319e099a4b
Author: Russ Cox <rsc@golang.org>
Date: Wed Feb 22 17:36:25 2012 -0500
5a: fix same arm bug
R=golang-dev, minux.ma
CC=golang-dev
https://golang.org/cl/5689073
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/123130f7894993587d3049b90d93c2319e099a4b
元コミット内容
5a: fix same arm bug
変更の背景
このコミットは、Go言語のコンパイラツールチェーンの一部であるARMアーキテクチャ向けアセンブラ5a
に存在していた、特定のコード生成バグを修正するために行われました。コミットメッセージにある「same arm bug」という表現は、ARMアーキテクチャ特有の命令セットやレジスタの扱いに起因する問題を示唆しています。
Go言語のコンパイラは、Goのソースコードを直接機械語に変換するのではなく、まずGoアセンブリ言語(Plan 9アセンブリに似た構文)に変換し、その後各アーキテクチャ(x86, ARM, ARM64など)に対応するアセンブラ(例: 5a
はARM、6a
はx86-64)が機械語に変換します。このプロセスにおいて、アセンブラが特定の命令やオペランドの組み合わせを誤って解釈・生成してしまうと、実行時に予期せぬ動作やクラッシュを引き起こす可能性があります。
この「same arm bug」は、おそらくARM命令におけるレジスタのエイリアシング(同じレジスタがソースとデスティネーションの両方に現れる場合など)や、特定の命令形式におけるオペランドの解釈に関する問題であったと推測されます。5a
アセンブラが、特定の状況下でレジスタの指定を誤り、結果として不正な機械語を生成していたと考えられます。
前提知識の解説
Go言語のツールチェーンとアセンブラ
Go言語のビルドプロセスでは、Goソースコードはまず中間表現(Goアセンブリ)に変換され、その後、ターゲットアーキテクチャ固有のアセンブラによって最終的な機械語に変換されます。
5a
: Go言語のツールチェーンにおけるARMアーキテクチャ向けのアセンブラです。5
はARMアーキテクチャを指すPlan 9の慣例に由来します。a.y
: このファイルは、Yacc (Yet Another Compiler Compiler) または Bison の入力ファイルであり、5a
アセンブラの構文解析器(パーサー)の定義を含んでいます。Yacc/Bisonは、文法規則に基づいてソースコードを解析し、抽象構文木(AST)を構築するためのツールです。このファイルは、アセンブリ命令の構文を定義し、それに対応するC言語(またはGo言語)のアクションを記述します。これらのアクションが、最終的な機械語を生成するoutcode
のような関数を呼び出します。
ARMアーキテクチャとレジスタ
ARM (Advanced RISC Machine) は、モバイルデバイスなどで広く使われているRISCベースのCPUアーキテクチャです。ARMプロセッサは、汎用レジスタ(R0-R15など)を使用してデータを操作します。アセンブリ言語では、これらのレジスタを明示的に指定して命令を記述します。
outcode
関数
outcode
関数は、アセンブラのバックエンドで実際に機械語(バイナリコード)を生成する役割を担う関数です。この関数は、アセンブリ命令の種類、オペランド(レジスタ、即値、メモリ番地など)、およびその他のフラグを受け取り、対応する機械語バイト列を出力します。
NREG
NREG
は、"No Register"(レジスタなし)または"Null Register"のような意味合いを持つ定数であると推測されます。アセンブラの内部処理において、特定のオペランド位置にレジスタが指定されない場合や、レジスタが不要な場合にプレースホルダーとして使用される値です。しかし、誤った文脈でNREG
が使用されると、アセンブラが期待するレジスタ情報が欠落したり、不正なレジスタが指定されたと解釈されたりして、バグにつながる可能性があります。
技術的詳細
このコミットの技術的詳細を理解するためには、src/cmd/5a/a.y
ファイルがどのようにアセンブリ命令を解析し、機械語に変換しているかを把握する必要があります。
a.y
ファイル内のinst:
(instruction)セクションは、アセンブリ命令の構文規則を定義しています。変更された行は以下の部分です。
inst:
/* ... 既存の命令定義 ... */
|\tLTYPEB name ',' imm
{
outcode($1, Always, &$2, NREG, &$4); // 変更前
}
/* ... */
このYaccルールは、LTYPEB
(おそらく命令の種類を示すトークン)、name
(レジスタ名やシンボル)、,
(カンマ)、imm
(即値)という形式のアセンブリ命令を処理しています。例えば、MOV R0, #10
のような命令がこれに該当する可能性があります。
outcode($1, Always, &$2, NREG, &$4);
という行は、この命令形式が解析されたときに実行されるC言語(またはGo言語)のアクションです。
$1
:LTYPEB
トークンの値(命令の種類)Always
: 条件コード(常に実行)&$2
:name
トークンのアドレス(レジスタまたはシンボル)NREG
: 4番目の引数。これが今回の変更の焦点です。&$4
:imm
トークンのアドレス(即値)
変更前は、4番目の引数にNREG
が渡されていました。これは、この特定の命令形式において、outcode
関数が期待するレジスタ情報がNREG
では不適切であったことを示唆しています。NREG
が「レジスタなし」を意味するとしても、この命令形式の文脈では、特定のレジスタが明示的に指定されるべきか、あるいはその位置に「レジスタではない」ことを示す別の値(例えば0
)が渡されるべきだった可能性があります。
「same arm bug」という表現から、このバグは、ソースオペランドとデスティネーションオペランドが同じレジスタである場合や、特定の命令が複数のレジスタを暗黙的に使用する際に、NREG
が誤って解釈され、結果として不正な命令エンコーディング(機械語への変換)が行われていた可能性が高いです。
修正は、NREG
を0
に置き換えることで行われました。
inst:
/* ... 既存の命令定義 ... */
|\tLTYPEB name ',' imm
{
outcode($1, Always, &$2, 0, &$4); // 変更後
}
/* ... */
NREG
を0
に置き換えることで、outcode
関数は、この4番目の引数に対して「レジスタではない」ことを正しく認識するか、あるいはこの位置にレジスタが不要であることを示す適切な値として0
を解釈するようになったと考えられます。これにより、アセンブラが正しい機械語を生成し、バグが解消されました。
この変更は、アセンブラの内部的なレジスタ管理や命令エンコーディングのロジックに深く関わるものであり、NREG
が特定の文脈で誤った意味を持っていたか、あるいはoutcode
関数が0
を特定の意味(例: 「レジスタなし」または「このオペランドは使用しない」)で解釈するように設計されていたことを示しています。
コアとなるコードの変更箇所
diff --git a/src/cmd/5a/a.y b/src/cmd/5a/a.y
index 9a0efd5e06..512fb5a952 100644
--- a/src/cmd/5a/a.y
+++ b/src/cmd/5a/a.y
@@ -217,7 +217,7 @@ inst:\
*/
|\tLTYPEB name ',' imm
\t{\
-\t\toutcode($1, Always, &$2, NREG, &$4);\
+\t\toutcode($1, Always, &$2, 0, &$4);\
\t}\
|\tLTYPEB name ',' con ',' imm
\t{\
コアとなるコードの解説
変更はsrc/cmd/5a/a.y
ファイルの217行目付近にあります。
元のコード:
outcode($1, Always, &$2, NREG, &$4);
修正後のコード:
outcode($1, Always, &$2, 0, &$4);
この変更は、outcode
関数への4番目の引数をNREG
から0
に変更しています。
outcode
関数: この関数は、アセンブリ命令を機械語に変換するGoアセンブラの内部関数です。引数として、命令の種類、条件コード、オペランド情報などを受け取ります。$1
: Yacc/Bisonのセマンティック値で、この場合はLTYPEB
トークン(命令の種類)に対応します。Always
: 命令が常に実行されることを示す条件コードです。&$2
:name
トークン(レジスタ名やシンボル)のアドレスです。NREG
vs0
: ここが変更の核心です。NREG
は通常、「レジスタなし」を意味する定数ですが、この特定の命令形式の文脈では、outcode
関数が期待するレジスタ情報として不適切でした。0
に置き換えることで、outcode
関数は、このオペランド位置にレジスタが指定されていないことを正しく解釈するか、あるいはこの位置にレジスタが不要であることを示す適切な値として0
を認識するようになりました。&$4
:imm
トークン(即値)のアドレスです。
この修正により、5a
アセンブラは、特定のARM命令形式において、レジスタの扱いに関するバグ("same arm bug")を解消し、正しい機械語を生成できるようになりました。これは、アセンブラの内部的なレジスタ割り当てや命令エンコーディングのロジックにおける微細な誤りを修正したものです。
関連リンク
- Go CL 5689073: https://golang.org/cl/5689073
参考にした情報源リンク
- Go言語のソースコード (特に
src/cmd/5a/
ディレクトリ内のファイル) - Yacc/Bisonのドキュメンテーション (構文解析器の動作理解のため)
- ARMアーキテクチャのリファレンスマニュアル (命令セットとレジスタの理解のため)
- Go言語のツールチェーンに関する一般的な情報