[インデックス 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言語には、文字型としてchar
、signed char
、unsigned 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/6c
とcmd/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アーキテクチャでは、デフォルトでchar
がunsigned char
として扱われるため、これらの変数が符号なしとして解釈されていました。しかし、mul.c
内の乗算ロジックや、それを利用するコンパイラのコード生成部分が、これらの変数を符号付きとして期待していた可能性があります。
例えば、負の値を扱う必要がある場合や、特定のビットパターンが符号付きの数値として意味を持つ場合、符号なしとして解釈されると、値が大きく異なったり、比較や演算の結果が予期せぬものになったりします。これにより、コンパイラが生成する乗算命令が誤ったものとなり、結果として実行時の計算が狂うという問題が発生しました。
この修正では、影響を受けるすべてのchar
型をschar
(signed 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.c
とsrc/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つの箇所で行われています。
-
struct Malg
のvals
メンバー:char vals[10];
からschar vals[10];
へ変更。Malg
構造体は、おそらく乗算アルゴリズムのパラメータや中間値を保持するために使用されます。vals
が符号付きの値を格納する必要がある場合、char
のデフォルトが符号なしであると問題が生じます。
-
struct Mparam
のalg
およびoff
メンバー:char alg;
からschar alg;
へ変更。char off;
からschar off;
へ変更。Mparam
構造体は、乗算のパラメータ(value
、alg
、neg
、shift
、arg
、off
)を定義しています。alg
(アルゴリズムの種類)やoff
(オフセット)が負の値を持ちうる、あるいは符号付きの解釈が必要な場合に、char
のデフォルト挙動が問題となります。
-
mulparam
関数内のローカル変数p
:char *p;
からschar *p;
へ変更。mulparam
関数は、乗算パラメータを計算または処理する関数であり、その中でポインタp
が使用されています。このポインタが指すデータが符号付きとして扱われるべきであるにもかかわらず、char
として宣言されていると、ポインタ演算やデータアクセス時に符号の解釈が誤る可能性があります。
これらの変更により、mul.c
内の関連する変数が、プラットフォームのchar
のデフォルト挙動に依存せず、常に符号付きの8ビット整数として扱われることが保証されます。これにより、乗算のコード生成における符号の不一致が解消され、ARMアーキテクチャ上でも正しい機械語が生成されるようになります。
関連リンク
- Go Issue #4354: https://code.google.com/p/go/issues/detail?id=4354 (古いGoのIssueトラッカーのリンクですが、当時の問題報告です)
- Go CL 6826079: https://golang.org/cl/6826079 (Gerritの変更リストへのリンク)
参考にした情報源リンク
- C言語の
char
型に関する情報 (例: cppreference.com, C Standard documentation) - ARMアーキテクチャにおける
char
のデフォルト符号に関する情報 - コンパイラの最適化とコード生成に関する一般的な知識