[インデックス 18525] ファイルの概要
このコミットは、Goコンパイラ(gc
)のバックエンド部分であるsrc/cmd/gc/plive.c
とsrc/cmd/gc/popt.c
に対する変更です。
src/cmd/gc/plive.c
: 主にライブネス解析(liveness analysis)に関連するコードが含まれています。ライブネス解析は、プログラムの特定のポイントでどの変数が「生きている」(将来使用される可能性がある)かを判断するコンパイラの最適化フェーズの一部です。これにより、不要な変数のメモリを解放したり、レジスタ割り当てを最適化したりできます。src/cmd/gc/popt.c
: 主にプログラムの最適化(optimization)に関連するコードが含まれています。これには、ジャンプ命令の最適化や不要なコードの削除などが含まれる可能性があります。
これらのファイルは、Goプログラムがコンパイルされて実行可能バイナリになる過程で、中間表現(IR)に対する変換や最適化を行う重要な役割を担っています。
コミット
このコミットは、特定のコンパイラ環境、特にchar
型がデフォルトでunsigned char
として扱われるコンパイラでGoコンパイラgc
をビルドする際の問題を修正します。具体的には、plive.c
とpopt.c
内のmode
フィールドの比較値を-1
から1
に変更することで、ビルドエラーを解消しています。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f0023cf1d57aabd8c0aa30a65eef9ec7de041eb6
元コミット内容
commit f0023cf1d57aabd8c0aa30a65eef9ec7de041eb6
Author: Russ Cox <rsc@golang.org>
Date: Fri Feb 14 00:43:43 2014 -0500
cmd/gc: fix build for 'default unsigned char' compilers
TBR=iant
CC=golang-codereviews
https://golang.org/cl/63680045
---
src/cmd/gc/plive.c | 2 +-\
src/cmd/gc/popt.c | 2 +-\
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/cmd/gc/plive.c b/src/cmd/gc/plive.c
index d353672985..250d9236b3 100644
--- a/src/cmd/gc/plive.c
+++ b/src/cmd/gc/plive.c
@@ -521,7 +521,7 @@ newcfg(Prog *firstp)
// Stop before an unreachable RET, to avoid creating
// unreachable control flow nodes.
- if(p->link != nil && p->link->as == ARET && p->link->mode == -1)
+ if(p->link != nil && p->link->as == ARET && p->link->mode == 1)
break;
// Collect basic blocks with selectgo calls.
diff --git a/src/cmd/gc/popt.c b/src/cmd/gc/popt.c
index 1f0bdb496a..f5067bd0e1 100644
--- a/src/cmd/gc/popt.c
+++ b/src/cmd/gc/popt.c
@@ -155,7 +155,7 @@ fixjmp(Prog *firstp)
// this assumption will not hold in the case of an infinite loop
// at the end of a function.
// Keep the RET but mark it dead for the liveness analysis.
- p->mode = -1;
+ p->mode = 1;
} else {
if(debug['R'] && debug['v'])
print("del %P\n", p);
変更の背景
このコミットの背景には、C言語におけるchar
型の符号付き/符号なしのデフォルトの扱いの違いが関係しています。C標準では、char
型がsigned char
として扱われるかunsigned char
として扱われるかは実装定義(implementation-defined)です。ほとんどのシステムではsigned char
がデフォルトですが、一部のコンパイラ(特にARMアーキテクチャ向けのコンパイラなど)ではunsigned char
がデフォルトとなることがあります。
Goコンパイラgc
はC言語で書かれており、その内部でchar
型や他の整数型を広範囲に使用しています。このコミットで修正された問題は、p->mode
というフィールドが、特定の状態を示すために-1
という値を保持している箇所に関連しています。
char
がデフォルトでunsigned
であるコンパイラ環境では、-1
という値がchar
型に代入されると、それはunsigned char
の最大値(通常は255)として解釈されます。これは、符号付き整数としての-1
とは異なる値です。Goコンパイラのコード内で、このmode
フィールドが-1
であるかどうかをチェックするロジックがあった場合、unsigned char
がデフォルトのコンパイラでは、このチェックが常に失敗し、予期せぬ動作やビルドエラーを引き起こす可能性がありました。
この問題は、Goコンパイラ自体の移植性に関わるものであり、異なるCコンパイラ環境でGoをビルドできるようにするために修正が必要でした。
前提知識の解説
C言語におけるchar
型の符号付き/符号なし
C言語では、char
型は文字を格納するために使用されますが、整数型としても扱えます。char
型にはsigned char
とunsigned char
の2つのバリエーションがあります。
signed char
: 符号付き整数として扱われ、通常は-128から127までの値を表現できます。unsigned char
: 符号なし整数として扱われ、通常は0から255までの値を表現できます。
C標準では、プレーンなchar
型がsigned char
として扱われるかunsigned char
として扱われるかは、コンパイラの実装に依存すると規定されています。多くのx86ベースのシステムではsigned char
がデフォルトですが、ARMなどの組み込みシステム向けのコンパイラではunsigned char
がデフォルトとなることがあります。
この違いは、特に-1
のような負の値をchar
型に代入し、その値を比較する際に問題を引き起こします。signed char
では-1
は-1
ですが、unsigned char
では-1
は2の補数表現によってUCHAR_MAX
(通常は255)として解釈されます。
Goコンパイラ gc
Go言語の公式コンパイラはgc
と呼ばれ、主にC言語で書かれています(一部はGo言語で書かれています)。gc
は、Goのソースコードを機械語に変換する役割を担っています。コンパイルプロセスには、構文解析、型チェック、中間コード生成、最適化、コード生成などの複数のフェーズがあります。
プログラムの制御フローとライブネス解析
コンパイラは、プログラムの実行パスを分析するために「制御フローグラフ(Control Flow Graph, CFG)」を構築します。CFGは、プログラムの基本ブロック(連続した命令のシーケンス)をノードとし、ブロック間のジャンプや条件分岐をエッジとするグラフです。
ライブネス解析は、データフロー解析の一種で、CFG上で変数が「生きている」かどうかを判断します。ある変数がプログラムの特定のポイントで生きているとは、その変数の現在の値が将来のどこかの時点で読み取られる可能性があることを意味します。ライブネス解析の結果は、レジスタ割り当ての最適化や、不要なコードの削除(デッドコードエリミネーション)などに利用されます。
Prog
構造体とmode
フィールド
Goコンパイラの内部では、プログラムの命令や操作はProg
という構造体で表現されることがあります。このProg
構造体には、命令の種類(as
フィールド)、リンク先の命令(link
フィールド)、そして様々なフラグや状態を示すためのmode
フィールドなどが含まれています。
このコミットでは、mode
フィールドが特定の状態を示すために使用されており、その値が-1
であるかどうかをチェックしていました。
技術的詳細
このコミットの技術的な核心は、C言語のchar
型の符号付き/符号なしのデフォルトの扱いの違いによって、Goコンパイラgc
の内部ロジックが誤動作していた点にあります。
Goコンパイラのsrc/cmd/gc/plive.c
とsrc/cmd/gc/popt.c
では、Prog
構造体のmode
フィールドが、特定の命令の状態や特性を示すために使用されていました。元のコードでは、このmode
フィールドに-1
という値を設定したり、-1
と比較したりしていました。
例えば、popt.c
のfixjmp
関数では、到達不能なRET
(return命令)をライブネス解析から除外するために、p->mode = -1;
としていました。これは、このRET
命令が「デッド」(不要)であることを示すフラグとして-1
を使用していたと考えられます。
しかし、char
がデフォルトでunsigned
であるコンパイラでコンパイルされた場合、C言語の型変換規則により、-1
というリテラル値がunsigned char
型の変数に代入されると、それはunsigned char
の最大値(例えば255)として解釈されます。
signed char
の場合:-1
はそのまま-1
。unsigned char
の場合:-1
は0xFF
(255)として解釈される。
したがって、plive.c
のnewcfg
関数内のif(p->link != nil && p->link->as == ARET && p->link->mode == -1)
という条件式は、unsigned char
がデフォルトのコンパイラでは、p->link->mode
が実際に-1
(符号付きとして)であっても、それがunsigned char
として255
と解釈されるため、-1
との比較が常にfalse
となり、条件が満たされなくなっていました。これにより、到達不能なRET
命令が正しく処理されず、コンパイラのロジックに不整合が生じ、ビルドエラーや誤った最適化につながっていた可能性があります。
このコミットでは、mode
フィールドの値を-1
から1
に変更することで、この問題を回避しています。つまり、mode
フィールドが示す状態のセマンティクスを-1
から1
に切り替えたということです。1
という値は、signed char
でもunsigned char
でも同じ1
として解釈されるため、コンパイラのchar
型のデフォルトの扱いに依存しない、より堅牢なコードになります。
この変更は、Goコンパイラがより多くのCコンパイラ環境で正しくビルドできるようにするための、移植性に関する修正です。
コアとなるコードの変更箇所
src/cmd/gc/plive.c
--- a/src/cmd/gc/plive.c
+++ b/src/cmd/gc/plive.c
@@ -521,7 +521,7 @@ newcfg(Prog *firstp)
// Stop before an unreachable RET, to avoid creating
// unreachable control flow nodes.
- if(p->link != nil && p->link->as == ARET && p->link->mode == -1)
+ if(p->link != nil && p->link->as == ARET && p->link->mode == 1)
break;
// Collect basic blocks with selectgo calls.
src/cmd/gc/popt.c
--- a/src/cmd/gc/popt.c
+++ b/src/cmd/gc/popt.c
@@ -155,7 +155,7 @@ fixjmp(Prog *firstp)
// this assumption will not hold in the case of an infinite loop
// at the end of a function.
// Keep the RET but mark it dead for the liveness analysis.
- p->mode = -1;
+ p->mode = 1;
} else {
if(debug['R'] && debug['v'])
print("del %P\n", p);
コアとなるコードの解説
src/cmd/gc/plive.c
の変更
newcfg
関数は、制御フローグラフを構築する際に使用されると考えられます。変更された行は、到達不能なRET
命令(関数からの戻り命令)を検出して、それ以上制御フローノードを作成しないようにするための条件式です。
- 変更前:
if(p->link != nil && p->link->as == ARET && p->link->mode == -1)
- この条件は、次の命令が
ARET
(return命令)であり、かつそのmode
フィールドが-1
である場合に真となります。-1
は、このRET
命令がライブネス解析において「デッド」または「到達不能」としてマークされていることを示していました。
- この条件は、次の命令が
- 変更後:
if(p->link != nil && p->link->as == ARET && p->link->mode == 1)
p->link->mode == -1
がp->link->mode == 1
に変更されました。これにより、mode
フィールドが1
である場合に、この条件が真となるようにロジックが変更されました。これは、popt.c
でmode
を1
に設定する変更と対応しています。
src/cmd/gc/popt.c
の変更
fixjmp
関数は、ジャンプ命令の最適化や不要なコードの処理を行うと考えられます。変更された行は、無限ループの終端にあるRET
命令をライブネス解析のために「デッド」としてマークする部分です。
- 変更前:
p->mode = -1;
Prog
構造体のmode
フィールドに-1
を代入していました。これは、この命令がライブネス解析において無視されるべきであることを示していました。
- 変更後:
p->mode = 1;
p->mode = -1
がp->mode = 1
に変更されました。これにより、mode
フィールドに1
を代入することで、同じくライブネス解析において無視されるべき状態を示すように変更されました。
これらの変更は、mode
フィールドが示す「デッド」または「到達不能」という状態の表現方法を-1
から1
に統一することで、Cコンパイラのchar
型のデフォルトの符号付き/符号なしの扱いの違いによる問題を解消しています。1
という値は、符号付きでも符号なしでも同じ値として解釈されるため、より移植性の高いコードになります。
関連リンク
- Go issue tracker (CL 63680045): https://golang.org/cl/63680045 (コミットメッセージに記載されているChangeListへのリンク)
参考にした情報源リンク
- C言語の
char
型の符号付き/符号なしのデフォルトの扱いに関する情報 (例: C Standard, GCC documentation onchar
signedness) - Goコンパイラ
gc
の内部構造に関する一般的な情報 (例: Goのソースコード、Goのコンパイラに関するブログ記事やドキュメント) - コンパイラの最適化、特にライブネス解析に関する一般的な情報 (例: コンパイラ設計の教科書、オンラインのコンパイラ理論に関する資料)
- Go言語のGitHubリポジトリ (コミット履歴の確認)