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

[インデックス 17162] ファイルの概要

このコミットは、Go言語のARMアーキテクチャ向けリンカであるcmd/5lにおける、MOVB(バイト移動)およびMOVH(ハーフワード移動)命令のエンコーディングに関するバグ修正です。これらの命令がMOVW(ワード移動)命令と同様に扱われるべきであり、命令エンコーディングにおいて2つのレジスタフィールドのみを設定するよう修正されました。

コミット

commit 5636b60b705f462de0ee794fac6a0d071ed3a952
Author: Russ Cox <rsc@golang.org>
Date:   Mon Aug 12 13:42:04 2013 -0400

    cmd/5l: fix encoding of new MOVB, MOVH instructions
    
    They are just like MOVW and should be setting only
    two register fields, not three.
    
    R=ken2
    CC=golang-dev, remyoudompheng
    https://golang.org/cl/12781043

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/5636b60b705f462de0ee794fac6a0d071ed3a952

元コミット内容

このコミットは、MOVBおよびMOVH命令が、MOVW命令と同様に、命令エンコーディングにおいて2つのレジスタフィールドのみを使用すべきであるにもかかわらず、誤って3つのレジスタフィールドを設定しようとしていた問題を修正しています。これは、新しい命令が追加された際に、既存のMOVW命令の処理ロジックを適切に適用しなかったために発生したエンコーディングのバグです。

変更の背景

Go言語のコンパイラおよびリンカは、様々なアーキテクチャをサポートしています。ARMアーキテクチャはその一つであり、cmd/5lはARM向けのリンカです。アセンブリ命令は、CPUが理解できるバイナリ形式(機械語)にエンコードされる必要があります。このエンコーディングプロセスにおいて、命令の種類に応じて特定のフィールド(例えば、オペランドとなるレジスタを指定するフィールド)が正しく設定される必要があります。

MOVB(Move Byte)とMOVH(Move Halfword)は、それぞれ1バイトと2バイトのデータをレジスタ間で移動させるための命令です。これらは、MOVW(Move Word、4バイトデータ移動)と同様に、ソースレジスタとデスティネーションレジスタの2つのレジスタをオペランドとして取ります。しかし、リンカのコードでは、これらの新しい命令が追加された際に、誤って3つのレジスタフィールドをエンコードしようとしていました。これは、おそらく命令の構造に関する誤解、または既存のMOVWの処理ロジックを新しい命令に適用する際のミスが原因と考えられます。

このエンコーディングの誤りは、生成される機械語が不正になることを意味します。結果として、コンパイルされたGoプログラムがARMプロセッサ上で正しく実行されない、またはリンカがエラーを報告するといった問題が発生する可能性があります。このバグは、GoプログラムのARMアーキテクチャへの移植性や安定性に直接影響を与えるため、修正が必要でした。

前提知識の解説

1. Go言語のツールチェインとcmd/5l

Go言語は、独自のコンパイラとリンカを含むツールチェインを持っています。

  • コンパイラ: Goのソースコードをアセンブリコードに変換します。
  • リンカ: コンパイラが生成したオブジェクトファイル(アセンブリコードを機械語に変換したもの)と、必要なライブラリを結合して実行可能なバイナリを生成します。 cmd/5lは、Go言語のツールチェインの一部であり、ARMアーキテクチャ(Goでは通常GOARCH=armで指定)向けのリンカです。5はARMアーキテクチャを指すGoの慣例的なプレフィックスです。

2. ARMアセンブリ命令とデータ転送命令

ARMアーキテクチャは、RISC(Reduced Instruction Set Computer)ベースの命令セットアーキテクチャです。

  • MOV (Move): レジスタ間でデータを移動させる基本的な命令です。
  • MOVB (Move Byte): 1バイトのデータを移動させます。
  • MOVH (Move Halfword): 2バイト(ハーフワード)のデータを移動させます。
  • MOVW (Move Word): 4バイト(ワード)のデータを移動させます。
  • AMVN (Move Not): オペランドのビットを反転(NOT演算)させてから移動させます。これは論理演算とデータ転送を組み合わせた命令です。

これらの命令は、通常、ソースレジスタとデスティネーションレジスタの2つのレジスタをオペランドとして取ります。

3. 命令エンコーディングとレジスタフィールド

CPUが実行する機械語命令は、バイナリ形式の特定のビットパターンで構成されています。このビットパターンを「命令エンコーディング」と呼びます。命令エンコーディングには、命令の種類(オペコード)、オペランド(データやレジスタ)、およびその他の制御情報を示すための特定のフィールドが含まれています。

  • レジスタフィールド: 命令エンコーディングの一部で、命令が操作するレジスタを指定するために使用されるビットの集まりです。例えば、ARMの命令では、ソースレジスタ(Rn)とデスティネーションレジスタ(Rd)を指定するための専用のフィールドが存在します。
  • 2つのレジスタフィールド vs. 3つのレジスタフィールド:
    • 2つのレジスタフィールド: 多くのデータ転送命令(例: MOV)は、ソースとデスティネーションの2つのレジスタのみを必要とします。
    • 3つのレジスタフィールド: 一部の算術論理演算命令(例: ADD R1, R2, R3 のように R2 + R3 の結果を R1 に格納する命令)は、2つのソースレジスタと1つのデスティネーションレジスタの合計3つのレジスタを必要とすることがあります。

リンカが命令をエンコードする際、命令の種類に応じて正しい数のレジスタフィールドを識別し、対応するレジスタ番号をそのフィールドに埋め込む必要があります。誤った数のフィールドを扱おうとすると、命令のバイナリ表現が不正になり、CPUが正しく解釈できなくなります。

技術的詳細

このコミットの技術的な核心は、ARM命令のエンコーディングにおけるレジスタフィールドの扱いです。src/cmd/5l/asm.cは、GoのリンカがARMアセンブリ命令を機械語に変換する際のロジックを含んでいます。

問題は、MOVBMOVH命令が、MOVWAMVN命令と同様に、命令エンコーディングにおいて2つのレジスタフィールド(ソースとデスティネーション)のみを必要とするにもかかわらず、リンカがこれらを3つのレジスタフィールドを持つ命令として扱おうとしていた点にあります。

ARM命令のエンコーディングでは、特定のビットパターンがレジスタの指定に使用されます。例えば、データ処理命令のフォーマットでは、ソースレジスタ(Rn)とデスティネーションレジスタ(Rd)のフィールドが明確に定義されています。もしリンカが不要な3つ目のレジスタフィールドに値を書き込もうとしたり、あるいは2つのレジスタしか持たない命令に対して3つのレジスタフィールドを期待するロジックを適用したりすると、生成される機械語はARMプロセッサの命令セット仕様に違反します。

具体的には、asm.c内のコードは、特定の命令(AMOVWAMVN)に対してレジスタフィールドrを0に設定する条件を持っていました。これは、これらの命令が特定のエンコーディングパターンを持つため、あるいは特定のレジスタフィールドが使用されないことを示すためと考えられます。この条件にAMOVBAMOVHが追加されていなかったため、これらの命令が誤ったレジスタフィールドの処理を受けていたと考えられます。

この修正により、MOVBMOVHMOVWと同様に、エンコーディング時に適切なレジスタフィールドの処理(この場合はrフィールドを0に設定する)を受けるようになり、正しい機械語が生成されるようになります。これにより、ARMアーキテクチャ上でGoプログラムが正しく動作することが保証されます。

コアとなるコードの変更箇所

--- a/src/cmd/5l/asm.c
+++ b/src/cmd/5l/asm.c
@@ -798,7 +798,7 @@ if(debug['G']) print("%ux: %s: arm %d\n", (uint32)(p->pc), p->from.sym->name, p-
 		r = p->reg;
 		if(p->to.type == D_NONE)
 			rt = 0;
-		if(p->as == AMOVW || p->as == AMVN)
+		if(p->as == AMOVB || p->as == AMOVH || p->as == AMOVW || p->as == AMVN)
 			r = 0;
 		else
 		if(r == NREG)

コアとなるコードの解説

変更はsrc/cmd/5l/asm.cファイルの798行目付近にあります。

元のコード:

		if(p->as == AMOVW || p->as == AMVN)
			r = 0;

修正後のコード:

		if(p->as == AMOVB || p->as == AMOVH || p->as == AMOVW || p->as == AMVN)
			r = 0;

このコードスニペットは、ARM命令のエンコーディング処理の一部です。

  • p->asは現在処理しているアセンブリ命令の種類(オペコード)を示します。AMOVW, AMVN, AMOVB, AMOVHはそれぞれMOVW, MVN, MOVB, MOVH命令に対応する内部定数です。
  • rはおそらく、命令エンコーディングにおける特定のレジスタフィールド(例えば、第3のレジスタオペランドや、特定のフラグを示すフィールド)を表す変数です。

元のコードでは、MOVWまたはAMVN命令の場合にのみ、変数r0に設定されていました。これは、これらの命令が特定のエンコーディングパターンを持つため、あるいは特定のレジスタフィールドが使用されないことを示すためと考えられます。

このコミットの修正は、AMOVBAMOVH命令もAMOVWAMVNと同様に、このr = 0の条件に含めるように変更しています。コミットメッセージにある「They are just like MOVW and should be setting only two register fields, not three.」という説明から、MOVBMOVHMOVWと同様に、エンコーディング時に3つ目のレジスタフィールドを考慮する必要がなく、そのフィールドを0に設定することで正しい機械語が生成されることを示唆しています。

この変更により、MOVBMOVH命令がリンカによって正しくエンコードされるようになり、ARMアーキテクチャ上でGoプログラムが期待通りに動作するようになります。

関連リンク

参考にした情報源リンク