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

[インデックス 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が誤って解釈され、結果として不正な命令エンコーディング(機械語への変換)が行われていた可能性が高いです。

修正は、NREG0に置き換えることで行われました。

inst:
    /* ... 既存の命令定義 ... */
|\tLTYPEB name ',' imm
    {
        outcode($1, Always, &$2, 0, &$4); // 変更後
    }
/* ... */

NREG0に置き換えることで、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 vs 0: ここが変更の核心です。NREGは通常、「レジスタなし」を意味する定数ですが、この特定の命令形式の文脈では、outcode関数が期待するレジスタ情報として不適切でした。0に置き換えることで、outcode関数は、このオペランド位置にレジスタが指定されていないことを正しく解釈するか、あるいはこの位置にレジスタが不要であることを示す適切な値として0を認識するようになりました。
  • &$4: immトークン(即値)のアドレスです。

この修正により、5aアセンブラは、特定のARM命令形式において、レジスタの扱いに関するバグ("same arm bug")を解消し、正しい機械語を生成できるようになりました。これは、アセンブラの内部的なレジスタ割り当てや命令エンコーディングのロジックにおける微細な誤りを修正したものです。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード (特にsrc/cmd/5a/ディレクトリ内のファイル)
  • Yacc/Bisonのドキュメンテーション (構文解析器の動作理解のため)
  • ARMアーキテクチャのリファレンスマニュアル (命令セットとレジスタの理解のため)
  • Go言語のツールチェーンに関する一般的な情報