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

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

このコミットは、Goコンパイラの内部コードにおけるchar型の符号付き/符号なしの挙動に起因するバグを修正するものです。特にARMアーキテクチャにおいて、charがデフォルトで符号なしとして扱われることが、乗算のコード生成に問題を引き起こしていました。この修正は、char型を明示的にsigned char(Goコンパイラの内部コードではscharと表記)とすることで、このプラットフォーム依存の挙動を解消し、正しい乗算コードが生成されるようにします。

コミット

commit cc224c004d0f389efcbb54251840bbb264ea4826
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date:   Fri Nov 9 21:06:45 2012 +0100

    cmd/6c, cmd/8c: use signed char explicitly in mul.c
    
    On ARM, char is unsigned, and the code generation for
    multiplication gets totally broken.
    
    Fixes #4354.
    
    R=golang-dev, dave, minux.ma, rsc
    CC=golang-dev
    https://golang.org/cl/6826079

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

https://github.com/golang/go/commit/cc224c004d0f389efcbb54251840bbb264ea4826

元コミット内容

cmd/6c, cmd/8c: use signed char explicitly in mul.c

On ARM, char is unsigned, and the code generation for
multiplication gets totally broken.

Fixes #4354.

変更の背景

この変更の背景には、Goコンパイラが生成するコードが特定のアーキテクチャ(この場合はARM)で正しく動作しないという問題がありました。具体的には、Goコンパイラのバックエンドの一部であるmul.cファイル(乗算処理に関連するコード)において、C言語のchar型が使用されていました。

C言語の標準では、char型が符号付き(signed char)として扱われるか、符号なし(unsigned char)として扱われるかは、実装定義(implementation-defined)とされています。これは、コンパイラやターゲットとするCPUアーキテクチャによってそのデフォルトの挙動が異なる可能性があることを意味します。

ARMアーキテクチャの多くの実装では、char型がデフォルトでunsigned charとして扱われます。このことが、mul.c内の乗算コード生成ロジックに予期せぬ影響を与え、結果として「乗算のコード生成が完全に壊れる」という深刻なバグを引き起こしていました。このバグはGoのIssue #4354として報告されていました。

コンパイラが特定の最適化やコード生成を行う際に、変数の符号付き/符号なしの特性に依存している場合、このようなプラットフォーム間のcharのデフォルト挙動の違いは、予期せぬバグの温床となります。このコミットは、この根本原因に対処し、コードの移植性と堅牢性を向上させることを目的としています。

前提知識の解説

C言語におけるchar型の符号付き/符号なし

C言語には、文字型としてcharsigned charunsigned charの3種類があります。

  • char: この型の符号付き/符号なしは、コンパイラの実装に依存します。多くのシステムではsigned charとして扱われますが、ARMなどの一部のシステムではunsigned charとして扱われることがあります。
  • signed char: 常に符号付きの8ビット整数型です。通常、-128から127までの値を表現できます。
  • unsigned char: 常に符号なしの8ビット整数型です。通常、0から255までの値を表現できます。

この実装定義の特性が、異なるアーキテクチャ間でC言語のコードを移植する際に問題を引き起こすことがあります。特に、数値計算やビット操作を行う際に、符号の解釈の違いが結果に大きな影響を与える可能性があります。

コンパイラのコード生成と最適化

コンパイラは、ソースコードを機械語に変換する際に、様々な最適化を行います。乗算のような基本的な演算に対しても、コンパイラはターゲットアーキテクチャの特性を考慮して、効率的な機械語命令を生成しようとします。例えば、定数による乗算は、シフト演算や加算の組み合わせに変換されることがあります。

この最適化の過程で、変数の型(特に符号付きか符号なしか)は非常に重要です。符号付き整数と符号なし整数では、オーバーフローの挙動や、負の数を扱う際のビット表現が異なります。コンパイラがこれらの違いを正しく考慮しないと、誤った機械語が生成され、プログラムが期待通りに動作しなくなります。

Goコンパイラの構造(cmd/6c, cmd/8c

Goコンパイラは、複数のコンポーネントから構成されています。このコミットで言及されているcmd/6ccmd/8cは、Goコンパイラのバックエンドの一部です。

  • cmd/6c: 32ビットARMアーキテクチャ向けのGoコンパイラのC言語部分を指します。GoのソースコードをARMの機械語に変換する過程で、C言語で書かれた低レベルのルーチンが使用されます。
  • cmd/8c: x86-64アーキテクチャ向けのGoコンパイラのC言語部分を指します。同様に、Goのソースコードをx86-64の機械語に変換する過程で使用されます。

これらのディレクトリ内のmul.cファイルは、おそらくコンパイラが生成する乗算ルーチンや、乗算に関連する最適化アルゴリズムの実装を含んでいます。

技術的詳細

このコミットの技術的な核心は、C言語のchar型の符号付き/符号なしの挙動がプラットフォームによって異なるという問題に、明示的な型指定で対処した点です。

mul.cファイルは、Goコンパイラが乗算処理を最適化したり、特定の乗算アルゴリズムを実装したりするために使用される内部コードです。このコード内で、char型が構造体のメンバーやローカル変数として使用されていました。

// 変更前 (例: src/cmd/6c/mul.c)
struct Malg
{
	char	vals[10];
};

struct Mparam
{
	uint32	value;
	char	alg;
	char	neg;
	char	shift;
	char	arg;
	char	off; // ここもchar
};

// mulparam関数内のローカル変数
char *p;

ARMアーキテクチャでは、デフォルトでcharunsigned charとして扱われるため、これらの変数が符号なしとして解釈されていました。しかし、mul.c内の乗算ロジックや、それを利用するコンパイラのコード生成部分が、これらの変数を符号付きとして期待していた可能性があります。

例えば、負の値を扱う必要がある場合や、特定のビットパターンが符号付きの数値として意味を持つ場合、符号なしとして解釈されると、値が大きく異なったり、比較や演算の結果が予期せぬものになったりします。これにより、コンパイラが生成する乗算命令が誤ったものとなり、結果として実行時の計算が狂うという問題が発生しました。

この修正では、影響を受けるすべてのchar型をscharsigned charのGoコンパイラ内部でのエイリアス、または単にsigned char)に明示的に変更しました。

// 変更後 (例: src/cmd/6c/mul.c)
struct Malg
{
	schar	vals[10]; // char -> schar
};

struct Mparam
{
	uint32	value;
	schar	alg; // char -> schar
	char	neg;
	char	shift;
	char	arg;
	schar	off; // char -> schar
};

// mulparam関数内のローカル変数
schar *p; // char -> schar

これにより、コンパイラはこれらの変数を常に符号付きの8ビット整数として扱うことが保証され、ARMを含むすべてのプラットフォームで乗算コード生成が正しく行われるようになりました。この変更は、Goコンパイラのクロスプラットフォーム対応における重要なバグ修正と言えます。

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

変更は、src/cmd/6c/mul.csrc/cmd/8c/mul.cの2つのファイルにわたっています。

src/cmd/6c/mul.c

--- a/src/cmd/6c/mul.c
+++ b/src/cmd/6c/mul.c
@@ -35,17 +35,17 @@ typedef struct	Mparam	Mparam;
 
 struct	Malg
 {
-	char	vals[10];
+	schar	vals[10];
 };
 
 struct	Mparam
 {
 	uint32	value;
-	char	alg;
+	schar	alg;
 	char	neg;
 	char	shift;
 	char	arg;
-	char	off;
+	schar	off;
 };
 
 static	Mparam	multab[32];
@@ -101,7 +101,7 @@ mulparam(uint32 m, Mparam *mp)
 {
 	int c, i, j, n, o, q, s;
 	int bc, bi, bn, bo, bq, bs, bt;
-	char *p;
+	schar *p;
 	int32 u;
 	uint32 t;

src/cmd/8c/mul.c

--- a/src/cmd/8c/mul.c
+++ b/src/cmd/8c/mul.c
@@ -35,17 +35,17 @@ typedef struct	Mparam	Mparam;
 
 struct	Malg
 {
-	char	vals[10];
+	schar	vals[10];
 };
 
 struct	Mparam
 {
 	uint32	value;
-	char	alg;
+	schar	alg;
 	char	neg;
 	char	shift;
 	char	arg;
-	char	off;
+	schar	off;
 };
 
 static	Mparam	multab[32];
@@ -101,7 +101,7 @@ mulparam(uint32 m, Mparam *mp)
 {
 	int c, i, j, n, o, q, s;
 	int bc, bi, bn, bo, bq, bs, bt;
-	char *p;
+	schar *p;
 	int32 u;
 	uint32 t;

コアとなるコードの解説

上記の差分が示すように、変更は主に以下の3つの箇所で行われています。

  1. struct Malgvalsメンバー:

    • char vals[10]; から schar vals[10]; へ変更。
    • Malg構造体は、おそらく乗算アルゴリズムのパラメータや中間値を保持するために使用されます。valsが符号付きの値を格納する必要がある場合、charのデフォルトが符号なしであると問題が生じます。
  2. struct Mparamalgおよびoffメンバー:

    • char alg; から schar alg; へ変更。
    • char off; から schar off; へ変更。
    • Mparam構造体は、乗算のパラメータ(valuealgnegshiftargoff)を定義しています。alg(アルゴリズムの種類)やoff(オフセット)が負の値を持ちうる、あるいは符号付きの解釈が必要な場合に、charのデフォルト挙動が問題となります。
  3. mulparam関数内のローカル変数p:

    • char *p; から schar *p; へ変更。
    • mulparam関数は、乗算パラメータを計算または処理する関数であり、その中でポインタpが使用されています。このポインタが指すデータが符号付きとして扱われるべきであるにもかかわらず、charとして宣言されていると、ポインタ演算やデータアクセス時に符号の解釈が誤る可能性があります。

これらの変更により、mul.c内の関連する変数が、プラットフォームのcharのデフォルト挙動に依存せず、常に符号付きの8ビット整数として扱われることが保証されます。これにより、乗算のコード生成における符号の不一致が解消され、ARMアーキテクチャ上でも正しい機械語が生成されるようになります。

関連リンク

参考にした情報源リンク

  • C言語のchar型に関する情報 (例: cppreference.com, C Standard documentation)
  • ARMアーキテクチャにおけるcharのデフォルト符号に関する情報
  • コンパイラの最適化とコード生成に関する一般的な知識