[インデックス 16503] ファイルの概要
このコミットは、Goコンパイラのcmd/6c
(x86-64アーキテクチャ向け)におけるメモリブロックコピー処理の修正に関するものです。具体的には、ブロックコピー時に使用されるアドレスの内部表現を、従来のTLONG
(長整数型)からTIND
(ポインタ型)に変更することで、64ビットアドレスを正確に扱うように改善しています。
コミット
commit f268c295a380747df60f9e5d304c74e3826ed7e9
Author: Russ Cox <rsc@golang.org>
Date: Wed Jun 5 10:39:06 2013 -0400
cmd/6c: use full 64-bit address in block copy
Already fixed independently in Plan 9.
R=ken2
CC=golang-dev
https://golang.org/cl/10041044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f268c295a380747df60f9e5d304c74e3826ed7e9
元コミット内容
cmd/6c: use full 64-bit address in block copy
(cmd/6c: ブロックコピーで完全な64ビットアドレスを使用する)
Already fixed independently in Plan 9.
(Plan 9では既に独立して修正済み)
変更の背景
このコミットは、Goコンパイラ(特にx86-64アーキテクチャ向けのcmd/6c
)がメモリブロックをコピーする際に、アドレスを適切に扱えていなかった問題を修正するものです。64ビットシステムでは、メモリのアドレス空間は64ビット幅を持ちます。しかし、コンパイラの内部処理でアドレスを表現する際に、誤って32ビットの長整数型(TLONG
)として扱っていた可能性があります。これにより、アドレスが32ビットの範囲を超える場合に、アドレスが切り詰められたり、不正なメモリ操作が発生したりするリスクがありました。
コミットメッセージにある「Already fixed independently in Plan 9」という記述は、この問題がGo言語の設計に大きな影響を与えたオペレーティングシステムであるPlan 9でも同様に認識され、修正されていたことを示唆しています。これは、GoコンパイラがPlan 9のツールチェインから派生した歴史的経緯を持つため、共通のコードベースや設計思想に起因する問題がGoにも引き継がれていた可能性を示しています。この修正は、Goコンパイラが64ビットシステム上でメモリをより正確かつ安全に操作できるようにするための重要な改善です。
前提知識の解説
このコミットを理解するためには、以下の概念が重要です。
- Goコンパイラと
cmd/6c
: Go言語のソースコードは、各アーキテクチャ(例: x86-64, ARM64)に対応するコンパイラによって機械語に変換されます。cmd/6c
は、x86-64アーキテクチャ(64ビットIntel/AMDプロセッサ)向けのGoコンパイラのバックエンドの一部を指します。Goのツールチェインは、Plan 9のツールチェイン(8c
,6c
,5c
など)に強く影響を受けており、6c
という命名はその名残です。 - メモリブロックコピー: プログラムがメモリ上のある領域から別の領域へデータをコピーする操作です。これは、構造体のコピー、配列の初期化、バッファの移動など、様々な場面で頻繁に行われます。この操作では、コピー元とコピー先のアドレス、そしてコピーするデータのサイズが重要になります。
- 64ビットアドレス: 64ビットシステムでは、メモリのアドレスは64ビット(8バイト)の幅を持ちます。これにより、非常に広大なメモリ空間(2^64バイト、約18エクサバイト)を扱うことができます。
TLONG
とTIND
(コンパイラ内部の型表現):TLONG
: 一般的に「long」型は、C言語などにおいて整数を表現するために使われるデータ型です。システムによっては32ビットまたは64ビットの幅を持ちます。コンパイラの内部では、特定のビット幅を持つ整数値を表現するために使用されます。TIND
: 「indirect」の略で、ポインタ(アドレス)を表現するための型です。ポインタはメモリ上の特定の位置を指し示す値であり、そのサイズはシステムのアドレスバス幅(例: 64ビットシステムでは64ビット)に依存します。 コンパイラがコードを生成する際、変数や式がどのようなデータ型であるかを内部的に管理しています。このコミットでは、メモリのアドレスを扱う際に、その内部表現がTLONG
(整数)ではなくTIND
(ポインタ)であるべきだという認識に基づいています。
- Plan 9の影響: Go言語は、ベル研究所のPlan 9オペレーティングシステムの設計思想やツールチェインから多くの影響を受けています。Goの初期のコンパイラやリンカは、Plan 9のそれらをベースに開発されました。そのため、Plan 9で発見され修正された問題が、Goのコードベースにも同様に存在することがあります。
技術的詳細
このコミットの技術的な核心は、Goコンパイラのcmd/6c
が、メモリブロックコピー操作(copy
ラベルで始まるコードブロック)において、ソースアドレスとデスティネーションアドレスの内部表現を誤って扱っていた点にあります。
src/cmd/6c/cgen.c
は、Goコンパイラのコード生成フェーズを担当するファイルの一つです。このファイル内で、メモリブロックのコピーを行うための機械語命令を生成するロジックが含まれています。
変更前は、アドレスを表現する際にtypes[TLONG]
という型が使用されていました。これは、コンパイラがアドレスを「長整数」として扱っていたことを意味します。しかし、64ビットシステムにおいてアドレスは単なる整数ではなく、ポインタとしての特性(例: ポインタ演算のセマンティクス)を持つべきであり、そのサイズも64ビットでなければなりません。もしTLONG
が32ビットとして定義されていた場合、64ビットアドレスの上位32ビットが失われ、不正なメモリ参照やデータ破損を引き起こす可能性がありました。
この修正では、types[TLONG]
をtypes[TIND]
に置き換えることで、コンパイラがアドレスを「ポインタ」として認識し、64ビットの完全なアドレスとして扱うように変更しています。これにより、コンパイラはブロックコピー操作において、正確な64ビットアドレスを基にした機械語命令を生成できるようになります。
具体的には、regialloc
やnodreg
といった関数が、ノード(n
やnn
)の型情報に基づいてレジスタ割り当てやアドレス計算を行います。これらの関数に渡されるノードの型がTIND
になることで、コンパイラはアドレスをポインタとして適切に処理し、64ビットレジスタ(例: D_SI
, D_DI
- x86-64のソースインデックスレジスタとデスティネーションインデックスレジスタ)に完全な64ビットアドレスをロードするようなコードを生成するようになります。
この変更は、Goプログラムが64ビットシステム上で大規模なデータ構造やメモリ領域を扱う際の堅牢性と正確性を向上させる上で不可欠なものです。
コアとなるコードの変更箇所
変更はsrc/cmd/6c/cgen.c
ファイル内で行われています。
--- a/src/cmd/6c/cgen.c
+++ b/src/cmd/6c/cgen.c
@@ -1679,7 +1679,7 @@ copy:
if(n->complex >= FNX && nn != nil && nn->complex >= FNX) {
t = nn->type;
- nn->type = types[TLONG];
+ nn->type = types[TIND];
regialloc(&nod1, nn, Z);
lcgen(nn, &nod1);
regsalloc(&nod2, nn);
@@ -1786,7 +1786,7 @@ copy:
c = 0;
if(n->complex > nn->complex) {
t = n->type;
- n->type = types[TLONG];
+ n->type = types[TIND];
nodreg(&nod1, n, D_SI);
if(reg[D_SI]) {
gins(APUSHQ, &nod1, Z);
@@ -1797,7 +1797,7 @@ copy:
n->type = t;
t = nn->type;
- nn->type = types[TLONG];
+ nn->type = types[TIND];
nodreg(&nod2, nn, D_DI);
if(reg[D_DI]) {
warn(Z, "DI botch");
@@ -1809,7 +1809,7 @@ warn(Z, "DI botch");
nn->type = t;
} else {
t = nn->type;
- nn->type = types[TLONG];
+ nn->type = types[TIND];
nodreg(&nod2, nn, D_DI);
if(reg[D_DI]) {
warn(Z, "DI botch");
@@ -1821,7 +1821,7 @@ warn(Z, "DI botch");
nn->type = t;
t = n->type;
- n->type = types[TLONG];
+ n->type = types[TIND];
nodreg(&nod1, n, D_SI);
if(reg[D_SI]) {
gins(APUSHQ, &nod1, Z);
具体的には、copy:
ラベルで始まるコードブロック内で、n
(ソースノード)とnn
(デスティネーションノード)の型を一時的に変更している箇所が5箇所あります。これらの箇所で、nn->type = types[TLONG];
または n->type = types[TLONG];
が nn->type = types[TIND];
または n->type = types[TIND];
に変更されています。
コアとなるコードの解説
このコードは、Goコンパイラのcmd/6c
におけるコード生成の一部であり、特にメモリブロックのコピー操作に関連する部分です。
copy:
ラベルは、コンパイラがメモリブロックコピーのコードを生成する際の開始点を示しています。このブロック内では、コピー元とコピー先のアドレスをレジスタにロードし、実際のコピー命令を生成する準備が行われます。
変更された行は、n
(ソース)とnn
(デスティネーション)というノード(コンパイラ内部の抽象構文木の要素)の型を一時的に設定している部分です。
t = nn->type;
またはt = n->type;
:元のノードの型を一時変数t
に保存します。これは、型を変更した後に元の型に戻すためです。nn->type = types[TLONG];
(変更前): デスティネーションノードnn
の型をTLONG
(長整数型)に設定していました。nn->type = types[TIND];
(変更後): デスティネーションノードnn
の型をTIND
(ポインタ型)に設定するように変更されました。同様に、ソースノードn
の型もTIND
に設定されます。
この型の一時的な変更は、regialloc
やnodreg
といった関数が、そのノードが指す値(この場合はアドレス)を適切にレジスタに割り当てたり、アドレスとして扱ったりするために行われます。
regialloc(&nod1, nn, Z);
:nn
(デスティネーション)のアドレスを保持するためのレジスタを割り当てます。lcgen(nn, &nod1);
:nn
の値をロードするためのコードを生成します。nodreg(&nod1, n, D_SI);
:ソースノードn
の値をD_SI
レジスタ(x86-64のソースインデックスレジスタ)にロードするためのノードを設定します。nodreg(&nod2, nn, D_DI);
:デスティネーションノードnn
の値をD_DI
レジスタ(x86-64のデスティネーションインデックスレジスタ)にロードするためのノードを設定します。
これらの関数が、ノードの型がTIND
であると認識することで、コンパイラはアドレスを64ビットのポインタとして扱い、D_SI
やD_DI
といった64ビットレジスタに完全な64ビットアドレスをロードするような機械語命令を生成します。これにより、メモリブロックコピー操作が64ビットアドレス空間全体で正確に機能するようになります。
関連リンク
- Go言語の公式リポジトリ: https://github.com/golang/go
- このコミットのChange List (CL): https://golang.org/cl/10041044
参考にした情報源リンク
- Go言語のコンパイラに関するドキュメントやソースコード(
src/cmd/6c/cgen.c
の周辺コード) - x86-64アーキテクチャのレジスタと命令セットに関する情報
- Plan 9オペレーティングシステムとGo言語の歴史的関係に関する情報
- 一般的なコンパイラのコード生成に関する知識
- Go言語のIssueトラッカーやメーリングリスト(golang-dev)のアーカイブ(CLリンクから辿れる可能性あり)
- https://golang.org/cl/10041044 (このコミットのChange List)
- https://github.com/golang/go/commit/f268c295a380747df60f9e5d304c74e3826ed7e9 (GitHub上のコミットページ)