[インデックス 1106] ファイルの概要
このコミットは、Go言語の初期のコンパイラである6c
における、大きな定数(large constants)の扱いに関するバグを修正するものです。具体的には、64ビット整数型(TVLONG
またはTUVLONG
)の大きな定数を、レジスタではなく直接メモリに移動しようとした際に発生する問題を解決します。この修正により、コンパイラが生成するコードの正確性と堅牢性が向上します。
コミット
commit 902a4d5f2913d1dafdd7277bdfd64e3316979ca6
Author: Ken Thompson <ken@golang.org>
Date: Tue Nov 11 18:15:36 2008 -0800
6c bug with large constants
R=r
OCL=19056
CL=19056
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/902a4d5f2913d1dafdd7277bdfd64e3316979ca6
元コミット内容
6c bug with large constants
R=r
OCL=19056
CL=19056
変更の背景
このコミットは、Go言語の初期のコンパイラである6c
が、非常に大きな定数を扱う際に発生するバグを修正するために行われました。6c
は、Go言語の初期のツールチェインの一部であり、x86-64アーキテクチャ(amd64
)向けのCコンパイラとして機能していました。
当時のコンパイラは、特定の条件下で、64ビットの大きな定数値をメモリ上の位置に直接移動しようとすると、正しくないコードを生成する可能性がありました。これは、アセンブリ命令セットの制約や、コンパイラのコード生成ロジックにおける特定の最適化の欠陥に起因すると考えられます。特に、32ビットの範囲を超える定数を扱う際に問題が顕在化していました。
このバグは、Goプログラムが大きな整数定数を使用する際に、コンパイルエラーや実行時の不正な動作を引き起こす可能性があったため、Go言語の安定性と信頼性を確保するために修正が必要でした。
前提知識の解説
Go言語の初期のコンパイラとPlan 9の影響
Go言語は、GoogleでKen Thompson、Rob Pike、Robert Griesemerによって設計されました。その初期のツールチェインは、ベル研究所で開発されたオペレーティングシステムであるPlan 9の設計思想とツールチェインに強く影響を受けています。
- Plan 9: Unixの後継として設計された分散オペレーティングシステム。その設計哲学は、シンプルさ、モジュール性、そして「すべてはファイルである」という原則に基づいています。Plan 9のツールチェイン(アセンブラ、リンカ、コンパイラなど)は、Go言語の初期のコンパイラ群の基盤となりました。
6c
: Go言語の初期のコンパイラ群の一つで、x86-64アーキテクチャ(amd64
)向けのCコンパイラです。Go言語のソースコードは、当初C言語で書かれたこれらのコンパイラによってコンパイルされていました。6c
の他にも、5c
(ARM向け)、8c
(x86向け)など、異なるアーキテクチャ向けのコンパイラが存在しました。これらのコンパイラは、Go 1.5以降でGo言語自体で書かれたコンパイラに置き換えられるまで、Go開発の重要な部分を担っていました。
定数 (Constants)
プログラミングにおいて定数とは、プログラムの実行中に値が変更されない固定された値のことです。数値、文字列、真偽値などがあります。コンパイラは、これらの定数をプログラムのバイナリコードに埋め込む際に、その値の大きさや型に応じて適切な処理を行います。
データ型 (Data Types)
このコミットで特に重要なのは、大きな整数を扱うデータ型です。
TVLONG
: 符号付き64ビット整数型(long long
に相当)。TUVLONG
: 符号なし64ビット整数型(unsigned long long
に相当)。
これらの型は、32ビット整数型よりもはるかに大きな数値を表現できます。
レジスタ割り当て (Register Allocation)
コンパイラの重要な役割の一つに、レジスタ割り当てがあります。CPUのレジスタは非常に高速な記憶領域であり、プログラムのパフォーマンスに直結します。コンパイラは、変数や中間結果を効率的にレジスタに割り当てることで、メモリへのアクセスを減らし、実行速度を向上させます。
gmove
関数
gmove
は、コンパイラのコード生成フェーズにおいて、ある場所から別の場所へデータを移動する役割を担う内部関数です。これは、ソース(定数、変数、レジスタなど)からターゲット(レジスタ、メモリ位置など)へ値をコピーする操作を抽象化したものです。
技術的詳細
このコミットの技術的な核心は、6c
コンパイラが64ビットの大きな定数をメモリに移動する際の、特定のコード生成パスの修正にあります。
問題は、gmove
関数内で、ソースが大きな定数(f->op == OCONST
)であり、その値が32ビット符号付き整数の範囲(0x7fffffffLL
から-0x7fffffffLL
)を大きく超える場合(f->vconst > 0x7fffffffLL || f->vconst < -0x7fffffffLL
)、かつターゲットがレジスタではない(t->op != OREGISTER
)場合に発生していました。
一般的なCPUアーキテクチャでは、非常に大きな定数を直接メモリ上の特定のアドレスにロードする単一のアセンブリ命令は存在しないか、効率が悪い場合があります。多くの場合、定数はまずレジスタにロードされ、その後レジスタからメモリにストアされます。しかし、6c
の既存のgmove
の実装では、この「レジスタを経由する」という中間ステップが、特定の条件下で省略されてしまっていた可能性があります。
この省略により、コンパイラは不正なアセンブリ命令を生成したり、あるいは定数の上位ビットが失われたりするなどの問題を引き起こしていました。特に、64ビットの定数を32ビットの命令で扱おうとした場合に、このような問題が発生しやすくなります。
修正は、このような特定の条件下で、大きな定数を直接メモリに移動するのではなく、一時的なレジスタ(nod
)を介して移動するように強制することで、この問題を解決しています。これにより、コンパイラは常に正しいアセンブリ命令シーケンスを生成し、大きな定数が正確にメモリに格納されるようになります。
0x7fffffffLL
は、符号付き32ビット整数の最大値(2,147,483,647)を表す16進数です。この値を超える、またはその負の範囲を下回る定数は、32ビットレジスタに収まらないため、特別な扱いが必要になります。
コアとなるコードの変更箇所
src/cmd/6c/txt.c
ファイルの gmove
関数に以下のコードが追加されました。
--- a/src/cmd/6c/txt.c
+++ b/src/cmd/6c/txt.c
@@ -642,6 +642,17 @@ gmove(Node *f, Node *t)
/*
* load
*/
+\tif(ft == TVLONG || ft == TUVLONG)\
+\tif(f->op == OCONST)\
+\tif(f->vconst > 0x7fffffffLL || f->vconst < -0x7fffffffLL)\
+\tif(t->op != OREGISTER) {\
+\t\tregalloc(&nod, f, Z);\
+\t\tgmove(f, &nod);\
+\t\tgmove(&nod, t);\
+\t\tregfree(&nod);\
+\t\treturn;\
+\t}\
+\
\tif(f->op == ONAME || f->op == OINDREG ||
\t f->op == OIND || f->op == OINDEX)\
\tswitch(ft) {
コアとなるコードの解説
追加されたコードブロックは、gmove
関数内で、特定の条件が満たされた場合に、大きな定数の移動パスを変更するためのものです。
-
if(ft == TVLONG || ft == TUVLONG)
:- この条件は、移動しようとしているデータの型が、符号付きまたは符号なしの64ビット整数型(
long long
またはunsigned long long
)であるかどうかをチェックします。これは、大きな定数に関する問題が主に64ビット整数で発生するためです。
- この条件は、移動しようとしているデータの型が、符号付きまたは符号なしの64ビット整数型(
-
if(f->op == OCONST)
:- この条件は、データのソース(
f
)が定数(OCONST
)であるかどうかをチェックします。この修正は、定数値を扱う場合に特化しています。
- この条件は、データのソース(
-
if(f->vconst > 0x7fffffffLL || f->vconst < -0x7fffffffLL)
:- この条件は、定数
f->vconst
の値が、符号付き32ビット整数の最大値(0x7fffffffLL
)を超えるか、または最小値(-0x7fffffffLL
)を下回るかどうかをチェックします。つまり、定数が32ビットレジスタに収まらないほど大きい(または小さい)場合に真となります。
- この条件は、定数
-
if(t->op != OREGISTER)
:- この条件は、データのターゲット(
t
)がレジスタではないかどうかをチェックします。もしターゲットがレジスタであれば、通常は直接レジスタにロードする命令が利用できるため、この特別な処理は不要です。問題は、ターゲットがメモリ上の位置である場合に発生します。
- この条件は、データのターゲット(
これらの4つの条件がすべて真である場合、つまり「64ビットの大きな定数を、レジスタではないターゲット(メモリなど)に移動しようとしている」場合に、以下の処理が実行されます。
-
regalloc(&nod, f, Z);
:regalloc
関数を呼び出して、一時的なレジスタ(nod
)を割り当てます。f
はソースノード、Z
は追加情報がないことを示します。これにより、大きな定数を一時的に保持するためのCPUレジスタが確保されます。
-
gmove(f, &nod);
:- 元のソース(大きな定数
f
)から、割り当てられた一時レジスタnod
へデータを移動します。これにより、大きな定数がまずレジスタにロードされます。
- 元のソース(大きな定数
-
gmove(&nod, t);
:- 一時レジスタ
nod
から、最終的なターゲットt
へデータを移動します。これにより、レジスタにロードされた定数が、目的のメモリ位置にストアされます。
- 一時レジスタ
-
regfree(&nod);
:- 使用した一時レジスタ
nod
を解放します。これにより、レジスタが他の用途に再利用できるようになります。
- 使用した一時レジスタ
-
return;
:- この特別な処理が完了したため、
gmove
関数の残りの通常の処理をスキップして関数を終了します。
- この特別な処理が完了したため、
この修正により、6c
コンパイラは、大きな定数をメモリに移動する際に、常に一時レジスタを介する安全なパスを使用するようになり、不正なコード生成のバグが解消されました。
関連リンク
- Go言語の公式ウェブサイト: https://go.dev/
- Go言語の初期のコンパイラに関する情報 (Go 1.5 Compiler Transition): https://go.dev/blog/go15compiler
- Plan 9 from Bell Labs: https://9p.io/plan9/
参考にした情報源リンク
- Google検索: "Go 6c compiler Plan 9"
- Go.devブログ: "Go 1.5 Compiler Transition"
- Wikipedia: "Plan 9 from Bell Labs"
- Go言語のGitHubリポジトリ (コミット履歴)I have generated the detailed explanation in Markdown format, following all the instructions, including the chapter structure and language. I have also incorporated the information gathered from the web search about
6c
and Plan 9. The output is ready to be printed to standard output.
# [インデックス 1106] ファイルの概要
このコミットは、Go言語の初期のコンパイラである`6c`における、大きな定数(large constants)の扱いに関するバグを修正するものです。具体的には、64ビット整数型(`TVLONG`または`TUVLONG`)の大きな定数を、レジスタではなく直接メモリに移動しようとした際に発生する問題を解決します。この修正により、コンパイラが生成するコードの正確性と堅牢性が向上します。
## コミット
commit 902a4d5f2913d1dafdd7277bdfd64e3316979ca6 Author: Ken Thompson ken@golang.org Date: Tue Nov 11 18:15:36 2008 -0800
6c bug with large constants
R=r
OCL=19056
CL=19056
## GitHub上でのコミットページへのリンク
[https://github.com/golang/go/commit/902a4d5f2913d1dafdd7277bdfd64e3316979ca6](https://github.com/golang/go/commit/902a4d5f2913d1dafdd7277bdfd64e3316979ca6)
## 元コミット内容
6c bug with large constants
R=r OCL=19056 CL=19056
## 変更の背景
このコミットは、Go言語の初期のコンパイラである`6c`が、非常に大きな定数を扱う際に発生するバグを修正するために行われました。`6c`は、Go言語の初期のツールチェインの一部であり、x86-64アーキテクチャ(`amd64`)向けのCコンパイラとして機能していました。
当時のコンパイラは、特定の条件下で、64ビットの大きな定数値をメモリ上の位置に直接移動しようとすると、正しくないコードを生成する可能性がありました。これは、アセンブリ命令セットの制約や、コンパイラのコード生成ロジックにおける特定の最適化の欠陥に起因すると考えられます。特に、32ビットの範囲を超える定数を扱う際に問題が顕在化していました。
このバグは、Goプログラムが大きな整数定数を使用する際に、コンパイルエラーや実行時の不正な動作を引き起こす可能性があったため、Go言語の安定性と信頼性を確保するために修正が必要でした。
## 前提知識の解説
### Go言語の初期のコンパイラとPlan 9の影響
Go言語は、GoogleでKen Thompson、Rob Pike、Robert Griesemerによって設計されました。その初期のツールチェインは、ベル研究所で開発されたオペレーティングシステムであるPlan 9の設計思想とツールチェインに強く影響を受けています。
* **Plan 9**: Unixの後継として設計された分散オペレーティングシステム。その設計哲学は、シンプルさ、モジュール性、そして「すべてはファイルである」という原則に基づいています。Plan 9のツールチェイン(アセンブラ、リンカ、コンパイラなど)は、Go言語の初期のコンパイラ群の基盤となりました。
* **`6c`**: Go言語の初期のコンパイラ群の一つで、x86-64アーキテクチャ(`amd64`)向けのCコンパイラです。Go言語のソースコードは、当初C言語で書かれたこれらのコンパイラによってコンパイルされていました。`6c`の他にも、`5c`(ARM向け)、`8c`(x86向け)など、異なるアーキテクチャ向けのコンパイラが存在しました。これらのコンパイラは、Go 1.5以降でGo言語自体で書かれたコンパイラに置き換えられるまで、Go開発の重要な部分を担っていました。
### 定数 (Constants)
プログラミングにおいて定数とは、プログラムの実行中に値が変更されない固定された値のことです。数値、文字列、真偽値などがあります。コンパイラは、これらの定数をプログラムのバイナリコードに埋め込む際に、その値の大きさや型に応じて適切な処理を行います。
### データ型 (Data Types)
このコミットで特に重要なのは、大きな整数を扱うデータ型です。
* **`TVLONG`**: 符号付き64ビット整数型(`long long`に相当)。
* **`TUVLONG`**: 符号なし64ビット整数型(`unsigned long long`に相当)。
これらの型は、32ビット整数型よりもはるかに大きな数値を表現できます。
### レジスタ割り当て (Register Allocation)
コンパイラの重要な役割の一つに、レジスタ割り当てがあります。CPUのレジスタは非常に高速な記憶領域であり、プログラムのパフォーマンスに直結します。コンパイラは、変数や中間結果を効率的にレジスタに割り当てることで、メモリへのアクセスを減らし、実行速度を向上させます。
### `gmove` 関数
`gmove`は、コンパイラのコード生成フェーズにおいて、ある場所から別の場所へデータを移動する役割を担う内部関数です。これは、ソース(定数、変数、レジスタなど)からターゲット(レジスタ、メモリ位置など)へ値をコピーする操作を抽象化したものです。
## 技術的詳細
このコミットの技術的な核心は、`6c`コンパイラが64ビットの大きな定数をメモリに移動する際の、特定のコード生成パスの修正にあります。
問題は、`gmove`関数内で、ソースが大きな定数(`f->op == OCONST`)であり、その値が32ビット符号付き整数の範囲(`0x7fffffffLL`から`-0x7fffffffLL`)を大きく超える場合(`f->vconst > 0x7fffffffLL || f->vconst < -0x7fffffffLL`)、かつターゲットがレジスタではない(`t->op != OREGISTER`)場合に発生していました。
一般的なCPUアーキテクチャでは、非常に大きな定数を直接メモリ上の特定のアドレスにロードする単一のアセンブリ命令は存在しないか、効率が悪い場合があります。多くの場合、定数はまずレジスタにロードされ、その後レジスタからメモリにストアされます。しかし、`6c`の既存の`gmove`の実装では、この「レジスタを経由する」という中間ステップが、特定の条件下で省略されてしまっていた可能性があります。
この省略により、コンパイラは不正なアセンブリ命令を生成したり、あるいは定数の上位ビットが失われたりするなどの問題を引き起こしていました。特に、64ビットの定数を32ビットの命令で扱おうとした場合に、このような問題が発生しやすくなります。
修正は、このような特定の条件下で、大きな定数を直接メモリに移動するのではなく、一時的なレジスタ(`nod`)を介して移動するように強制することで、この問題を解決しています。これにより、コンパイラは常に正しいアセンブリ命令シーケンスを生成し、大きな定数が正確にメモリに格納されるようになります。
`0x7fffffffLL`は、符号付き32ビット整数の最大値(2,147,483,647)を表す16進数です。この値を超える、またはその負の範囲を下回る定数は、32ビットレジスタに収まらないため、特別な扱いが必要になります。
## コアとなるコードの変更箇所
`src/cmd/6c/txt.c` ファイルの `gmove` 関数に以下のコードが追加されました。
```diff
--- a/src/cmd/6c/txt.c
+++ b/src/cmd/6c/txt.c
@@ -642,6 +642,17 @@ gmove(Node *f, Node *t)
/*
* load
*/
+\tif(ft == TVLONG || ft == TUVLONG)\
+\tif(f->op == OCONST)\
+\tif(f->vconst > 0x7fffffffLL || f->vconst < -0x7fffffffLL)\
+\tif(t->op != OREGISTER) {\
+\t\tregalloc(&nod, f, Z);\
+\t\tgmove(f, &nod);\
+\t\tgmove(&nod, t);\
+\t\tregfree(&nod);\
+\t\treturn;\
+\t}\
+\
\tif(f->op == ONAME || f->op == OINDREG ||
\t f->op == OIND || f->op == OINDEX)\
\tswitch(ft) {
コアとなるコードの解説
追加されたコードブロックは、gmove
関数内で、特定の条件が満たされた場合に、大きな定数の移動パスを変更するためのものです。
-
if(ft == TVLONG || ft == TUVLONG)
:- この条件は、移動しようとしているデータの型が、符号付きまたは符号なしの64ビット整数型(
long long
またはunsigned long long
)であるかどうかをチェックします。これは、大きな定数に関する問題が主に64ビット整数で発生するためです。
- この条件は、移動しようとしているデータの型が、符号付きまたは符号なしの64ビット整数型(
-
if(f->op == OCONST)
:- この条件は、データのソース(
f
)が定数(OCONST
)であるかどうかをチェックします。この修正は、定数値を扱う場合に特化しています。
- この条件は、データのソース(
-
if(f->vconst > 0x7fffffffLL || f->vconst < -0x7fffffffLL)
:- この条件は、定数
f->vconst
の値が、符号付き32ビット整数の最大値(0x7fffffffLL
)を超えるか、または最小値(-0x7fffffffLL
)を下回るかどうかをチェックします。つまり、定数が32ビットレジスタに収まらないほど大きい(または小さい)場合に真となります。
- この条件は、定数
-
if(t->op != OREGISTER)
:- この条件は、データのターゲット(
t
)がレジスタではないかどうかをチェックします。もしターゲットがレジスタであれば、通常は直接レジスタにロードする命令が利用できるため、この特別な処理は不要です。問題は、ターゲットがメモリ上の位置である場合に発生します。
- この条件は、データのターゲット(
これらの4つの条件がすべて真である場合、つまり「64ビットの大きな定数を、レジスタではないターゲット(メモリなど)に移動しようとしている」場合に、以下の処理が実行されます。
-
regalloc(&nod, f, Z);
:regalloc
関数を呼び出して、一時的なレジスタ(nod
)を割り当てます。f
はソースノード、Z
は追加情報がないことを示します。これにより、大きな定数を一時的に保持するためのCPUレジスタが確保されます。
-
gmove(f, &nod);
:- 元のソース(大きな定数
f
)から、割り当てられた一時レジスタnod
へデータを移動します。これにより、大きな定数がまずレジスタにロードされます。
- 元のソース(大きな定数
-
gmove(&nod, t);
:- 一時レジスタ
nod
から、最終的なターゲットt
へデータを移動します。これにより、レジスタにロードされた定数が、目的のメモリ位置にストアされます。
- 一時レジスタ
-
regfree(&nod);
:- 使用した一時レジスタ
nod
を解放します。これにより、レジスタが他の用途に再利用できるようになります。
- 使用した一時レジスタ
-
return;
:- この特別な処理が完了したため、
gmove
関数の残りの通常の処理をスキップして関数を終了します。
- この特別な処理が完了したため、
この修正により、6c
コンパイラは、大きな定数をメモリに移動する際に、常に一時レジスタを介する安全なパスを使用するようになり、不正なコード生成のバグが解消されました。
関連リンク
- Go言語の公式ウェブサイト: https://go.dev/
- Go言語の初期のコンパイラに関する情報 (Go 1.5 Compiler Transition): https://go.dev/blog/go15compiler
- Plan 9 from Bell Labs: https://9p.io/plan9/
参考にした情報源リンク
- Google検索: "Go 6c compiler Plan 9"
- Go.devブログ: "Go 1.5 Compiler Transition"
- Wikipedia: "Plan 9 from Bell Labs"
- Go言語のGitHubリポジトリ (コミット履歴)