[インデックス 1648] ファイルの概要
このコミットは、Goコンパイラの初期バージョンである6g
におけるアライメント(メモリ配置)のバグに関連する修正を導入しています。具体的には、src/cmd/gc/walk.c
ファイルに、異なるアライメントを持つ構造体が多値返却される際に発生する可能性のある問題を検出するためのチェックが追加されています。この修正は、バグの根本的な解決ではなく、一時的な回避策として、問題の発生を早期に検知し、コンパイラが不正なコードを生成するのを防ぐことを目的としています。
コミット
commit a81870badf2b625dd82c705796d2e897b53749bc
Author: Russ Cox <rsc@golang.org>
Date: Sun Feb 8 11:19:45 2009 -0800
add error to catch 6g alignment bug.
the fix appears to be to align the
out struct on an 8 boundary, but that
is a bit involved.
R=ken
OCL=24657
CL=24657
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a81870badf2b625dd82c705796d2e897b53749bc
元コミット内容
add error to catch 6g alignment bug.
the fix appears to be to align the
out struct on an 8 boundary, but that
is a bit involved.
変更の背景
このコミットは、Go言語の初期のコンパイラである6g
(x86-64アーキテクチャ向けのGoコンパイラ)に存在した、メモリのアライメントに関するバグに対処するために行われました。特に、関数が複数の値を返す際に、それらの戻り値が格納される構造体(out struct
)のメモリ配置が適切でない場合に問題が発生していました。
コンパイラは、プログラムの実行効率を最大化するために、データを特定のメモリ境界に配置(アライメント)します。例えば、64ビットの整数は8バイト境界に配置されるのが一般的です。しかし、6g
コンパイラが多値返却の際に生成する内部的な構造体のアライメントが正しくない場合、データへのアクセスが遅くなったり、最悪の場合、クラッシュや不正な動作を引き起こす可能性がありました。
コミットメッセージによると、根本的な解決策は「out struct
を8バイト境界にアライメントすること」と認識されていましたが、その実装は複雑であったため、このコミットではまず、問題が発生した場合にコンパイラがエラーを出すようにすることで、バグの発生を早期に検出し、開発者に警告する暫定的な措置が取られました。これは、不正なコードが生成されてしまうよりも、コンパイル時にエラーとして顕在化させる方が望ましいという判断に基づいています。
前提知識の解説
1. Go言語の初期コンパイラ 6g
Go言語の初期には、各アーキテクチャ(例: x86-64, ARM)ごとに異なるコンパイラが存在しました。6g
は、x86-64アーキテクチャ(64ビットIntel/AMDプロセッサ)向けのGoコンパイラを指します。これらのコンパイラはC言語で書かれており、現在のGoコンパイラ(Go言語自体で書かれている)とは異なります。src/cmd/gc/
ディレクトリは、これらのC言語ベースのコンパイラの共通部分やバックエンドに関連するコードが含まれていました。
2. メモリのアライメント (Memory Alignment)
メモリのアライメントとは、コンピュータのメモリ上でデータが配置される際の、特定のメモリアドレスへの制約のことです。CPUは、特定のバイト境界(例: 4バイト境界、8バイト境界)に配置されたデータを効率的に読み書きできます。例えば、64ビット(8バイト)の整数は、8の倍数のアドレスに配置されていると、CPUは一度のメモリアクセスでその値を読み取ることができます。もしデータがアライメントされていない場合、CPUは複数回のメモリアクセスを行う必要があったり、アライメント違反エラーが発生したりすることがあります。
- パディング (Padding): アライメント要件を満たすために、構造体やデータ型の間に挿入される未使用のバイトのことです。これにより、次のデータが適切な境界に配置されるようになります。
- 構造体のアライメント: 構造体内の各メンバーはそれぞれのアライメント要件を持ち、構造体全体のサイズも特定のアライメント境界に合うようにパディングされることがあります。
3. Go言語の多値返却 (Multiple Return Values)
Go言語の大きな特徴の一つに、関数が複数の値を返すことができる点があります。例えば、func foo() (int, string)
のように、複数の型を返すことができます。コンパイラは内部的に、これらの複数の戻り値を格納するための一時的な構造体(out struct
)を生成し、その構造体を介して値を返します。このコミットで言及されているバグは、この内部的なout struct
のアライメントが正しくない場合に発生していました。
4. src/cmd/gc/walk.c
とは
src/cmd/gc/walk.c
は、Goコンパイラの「walk」フェーズの一部を実装していたC言語のファイルです。Goコンパイラのコンパイルプロセスは複数のフェーズに分かれており、walk
フェーズはその中でも重要な中間段階です。
- 抽象構文木 (AST) の変換:
walk
フェーズは、パーサーによって生成された抽象構文木(AST)を受け取り、それをより低レベルの、コード生成に適した中間表現(IR)に変換します。 - 高レベルなGo構文の「脱糖衣化 (Desugaring)」:
switch
文、map
操作、channel
操作など、高レベルなGoの構文を、よりプリミティブな操作やランタイム関数呼び出しに分解します。 - 評価順序の決定: 式の評価順序を決定し、必要に応じて一時変数を導入します。
このコミットでは、多値返却に関連する処理がwalk
フェーズで行われる際に、アライメントの不一致を検出するチェックが追加されました。
技術的詳細
このコミットは、src/cmd/gc/walk.c
ファイル内のascompatte
関数に修正を加えています。ascompatte
関数は、Goコンパイラのwalk
フェーズにおいて、型変換や代入の互換性を処理する役割を担っています。特に、多値返却の処理において、戻り値の型と代入先の型との互換性をチェックする部分で、アライメントのバグを検出するためのコードが追加されました。
追加されたコードは、以下の条件が満たされた場合に実行されます。
op
がOAS
(代入)またはOAS2
(多値代入)である。r
(右辺)が多値返却の呼び出し結果である。nl
(左辺の型リスト)とnr
(右辺のノードリスト)が特定のパターンに一致する。
この条件が満たされたとき、コンパイラは右辺の型(r->type
)と左辺の型(*nl
)のwidth
(メモリ上のサイズ)を比較します。
if(r->type->width != (*nl)->width)
yyerror("misaligned multiple return (6g's fault)");
ここで、r->type->width
は、関数が実際に返す値の集合(内部的には構造体として扱われる)のメモリ上のサイズを示し、(*nl)->width
は、それを受け取る側の変数の集合(これも内部的には構造体として扱われる)のメモリ上のサイズを示します。
通常、これらのwidth
は一致するはずです。しかし、6g
コンパイラが多値返却のために生成する内部的なout struct
のアライメントが正しくない場合、このwidth
が期待される値と異なる可能性がありました。例えば、パディングの計算が誤っているために、構造体のサイズが本来よりも小さく(または大きく)計算されてしまうようなケースです。
このif
文は、このwidth
の不一致を検出した場合に、yyerror
関数を呼び出してコンパイルエラーを発生させます。エラーメッセージは「misaligned multiple return (6g's fault)
」(アライメントが不正な多値返却(6gのバグ))と明確に示されており、これが6g
コンパイラ自身の問題であることを示唆しています。
この修正は、バグを修正するものではなく、バグによって引き起こされる可能性のある不正なコード生成を防ぐための「ガード」として機能します。これにより、開発者はコンパイル時に問題を認識し、回避策を講じたり、コンパイラの修正を待ったりすることができました。
コアとなるコードの変更箇所
src/cmd/gc/walk.c
ファイルのascompatte
関数内。
--- a/src/cmd/gc/walk.c
+++ b/src/cmd/gc/walk.c
@@ -1906,6 +1906,11 @@ ascompatte(int op, Type **nl, Node **nr, int fp)\n \t&& structnext(&peekl) != T\n \t&& listnext(&peekr) == N\n \t&& eqtypenoname(r->type, *nl)) {\n+\t\t// clumsy check for differently aligned structs.\n+\t\t// need to handle eventually, but this keeps us\n+\t\t// from inserting bugs\n+\t\tif(r->type->width != (*nl)->width)\n+\t\t\tyyerror(\"misaligned multiple return (6g's fault)\");\n \t\ta = nodarg(*nl, fp);\n \t\ta->type = r->type;\n \t\treturn convas(nod(OAS, a, r));\n```
## コアとなるコードの解説
追加されたコードは以下の5行です。
```c
// clumsy check for differently aligned structs.
// need to handle eventually, but this keeps us
// from inserting bugs
if(r->type->width != (*nl)->width)
yyerror("misaligned multiple return (6g's fault)");
// clumsy check for differently aligned structs.
- 「異なるアライメントを持つ構造体に対する不器用なチェック」というコメントです。これは、このチェックが一時的で、より洗練された解決策が必要であることを示唆しています。
// need to handle eventually, but this keeps us
// from inserting bugs
- 「最終的には対処する必要があるが、これによりバグの混入を防ぐ」というコメントです。これは、このチェックが根本的な修正ではなく、将来的なバグの発生を防ぐための予防策であることを明確にしています。
if(r->type->width != (*nl)->width)
- この条件文が、アライメントバグの検出の中核です。
r->type->width
: 関数が返す複数の値(内部的には構造体)のメモリ上のサイズ。(*nl)->width
: それらの値を受け取る側の変数の集合(これも内部的には構造体)のメモリ上のサイズ。- これらのサイズが一致しない場合、アライメントの計算に問題がある可能性が高いと判断されます。
yyerror("misaligned multiple return (6g's fault)");
- 上記の
if
文の条件が真(width
が一致しない)の場合に実行されます。 yyerror
は、コンパイラがエラーメッセージを出力するために使用する関数です。- 出力されるメッセージは「
misaligned multiple return (6g's fault)
」であり、多値返却におけるアライメントの不一致が6g
コンパイラのバグに起因することを示しています。
- 上記の
このコードは、コンパイル時にアライメントの不一致を検出し、エラーとして報告することで、不正なメモリレイアウトを持つコードが生成されるのを防ぎます。これにより、ランタイムでのクラッシュや予期せぬ動作を未然に防ぐことができます。
関連リンク
- Go言語の多値返却に関する公式ドキュメントやチュートリアル(Go言語の基本的な機能として広く説明されています)
- Goコンパイラの内部構造に関する資料(特に
walk
フェーズやAST変換について) - メモリのアライメントに関する一般的なコンピュータサイエンスの資料
参考にした情報源リンク
- Go言語の初期コンパイラ(
6g
など)に関する歴史的な情報や、Goコンパイラの進化に関する記事。 - Goコンパイラの
walk
フェーズに関する技術ブログやドキュメント。 - メモリのアライメントに関する一般的なコンピュータアーキテクチャの教科書やオンラインリソース。
- Go言語の多値返却に関する公式ドキュメントやチュートリアル。
- Go言語のソースコードリポジトリ(特に
src/cmd/gc/
ディレクトリの歴史的なコミットログ)。