[インデックス 1912] ファイルの概要
このコミットは、unsafe.Alignof(struct{x float}{0}.x)
のような特定の形式の unsafe.Alignof
の呼び出しがGoコンパイラでクラッシュするのを防ぐための修正です。具体的には、構造体リテラルのゼロ値のフィールドに対する Alignof
の呼び出しが、コンパイラの内部処理で予期せぬ副作用を引き起こし、クラッシュに至る問題を解決します。
コミット
- コミットハッシュ:
e224b1ebdb8db39b1d0c682898e9a223e1fd77ba
- 作者: Russ Cox rsc@golang.org
- コミット日時: Mon Mar 30 17:07:30 2009 -0700
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e224b1ebdb8db39b1d0c682898e9a223e1fd77ba
元コミット内容
don't crash on
unsafe.Alignof(struct{x float}{0}.x)
R=ken
OCL=26911
CL=26913
変更の背景
このコミットは、Goコンパイラ(gc
)が unsafe.Alignof
関数を特定の引数で処理する際に発生するクラッシュを修正するために行われました。問題となる引数は struct{x float}{0}.x
のような形式です。
unsafe.Alignof
は、Goの unsafe
パッケージが提供する関数で、引数として与えられた型のメモリ配置におけるアライメント要件をバイト単位で返します。この関数はコンパイル時に評価される組み込み関数であり、実行時にはコードを生成しません。そのため、Alignof
の引数として渡される式は、その値自体ではなく、その「型」情報のみがコンパイラによって必要とされます。
しかし、struct{x float}{0}.x
のような構造体リテラル(struct{x float}{0}
は x
フィールドが 0.0
で初期化された匿名構造体のインスタンス)のフィールド (.x
) を Alignof
の引数として渡した場合、コンパイラは型情報を取得するだけでなく、その式の「評価」を試みてしまうことがありました。この評価プロセス中に、本来不要な副作用(例えば、一時的な構造体インスタンスの生成やそのフィールドへのアクセスに関連する内部処理)が発生し、それがコンパイラの内部状態を不正にし、最終的にクラッシュを引き起こしていたと考えられます。
特に、dcl.c
ファイルはGoコンパイラの宣言処理と型チェックに関連する部分であり、式の評価や型情報の抽出を行う際に、このような副作用の管理が重要になります。このクラッシュは、コンパイラが Alignof
の引数を処理する際の最適化や副作用の抑制が不十分であったことを示唆しています。
R=ken
はコードレビュー担当者を示し、OCL=26911
と CL=26913
は、当時のGoプロジェクトが使用していたGerritベースのコードレビューシステムにおける変更リスト(Change List)の番号です。
前提知識の解説
unsafe.Alignof
unsafe.Alignof
はGo言語の unsafe
パッケージに含まれる組み込み関数です。この関数は、任意の型の変数がメモリ上で占めるアライメント(整列)の要件をバイト単位で返します。アライメントとは、データがメモリ上で特定のバイト境界に配置されることを保証するもので、CPUが効率的にデータにアクセスするために重要です。
例:
package main
import (
"fmt"
"unsafe"
)
type MyStruct struct {
a int8
b int64
c int32
}
func main() {
var s MyStruct
fmt.Printf("Alignof(s.a): %d\n", unsafe.Alignof(s.a)) // int8のアライメント
fmt.Printf("Alignof(s.b): %d\n", unsafe.Alignof(s.b)) // int64のアライメント
fmt.Printf("Alignof(s.c): %d\n", unsafe.Alignof(s.c)) // int32のアライメント
fmt.Printf("Alignof(s): %d\n", unsafe.Alignof(s)) // MyStruct全体のアライメント
}
unsafe.Alignof
はコンパイル時に評価されるため、引数として渡される式は実行時に値を持つ必要はありません。コンパイラは、その式の型情報のみを利用してアライメント値を決定します。
Goコンパイラ (gc
)
Go言語の公式コンパイラは gc
と呼ばれます。gc
はGoのソースコードを機械語に変換する役割を担っています。コンパイルプロセスは複数のフェーズに分かれており、字句解析、構文解析、型チェック、中間コード生成、最適化、コード生成などがあります。
src/cmd/gc/dcl.c
src/cmd/gc/dcl.c
は、Goコンパイラのソースコードの一部であり、主に宣言(declaration)と型チェック(type checking)に関連する処理を担当しています。このファイルには、変数、関数、型の宣言を処理し、それらの型がGoの型システム規則に準拠していることを確認するためのロジックが含まれています。また、式の型を決定したり、コンパイル時に評価されるべき式(例えば unsafe.Alignof
の引数など)を処理したりする部分も含まれる可能性があります。
構造体リテラルとフィールドアクセス
Goでは、構造体リテラルを使用して構造体の新しいインスタンスを作成し、初期化することができます。
例: Point{X: 10, Y: 20}
匿名構造体も使用できます。
例: struct { x float }{0}
は、x
という float
型のフィールドを持つ匿名構造体のインスタンスを作成し、その x
フィールドを 0.0
で初期化します。
この構造体リテラルの後に .x
のようにフィールドアクセスを行うと、その構造体の特定のフィールドにアクセスできます。
コンパイラにおける「副作用」
プログラミングにおいて「副作用」とは、関数や式がその戻り値を返す以外の方法で、プログラムの状態を変更する動作を指します。例えば、変数の値を変更する、I/O操作を行う、メモリを割り当てるなどが副作用にあたります。
コンパイラがコードを解析する際、特定のコンテキストでは副作用のない式のみを期待することがあります。unsafe.Alignof
のように型情報のみを必要とする場合、引数の式が副作用を持つと、コンパイラが予期せぬ動作をしたり、クラッシュしたりする可能性があります。
OCL
と CL
OCL
(Old Change List) と CL
(Change List) は、Goプロジェクトが初期に採用していたGerritベースのコードレビューシステムで使われていた変更セットの識別子です。これは、現在のGitHubのプルリクエストに相当する概念です。
技術的詳細
このコミットが修正した問題は、Goコンパイラが unsafe.Alignof
の引数を処理する際の、式の評価と副作用の管理に関するものです。
unsafe.Alignof(struct{x float}{0}.x)
という式を考えます。
unsafe.Alignof
はコンパイル時関数であり、引数の「型」のみを必要とします。struct{x float}{0}.x
の型はfloat
です。- しかし、コンパイラは
struct{x float}{0}.x
という式を解析する際に、その型を決定するだけでなく、その式を「評価」しようとすることがありました。この評価プロセスには、一時的な構造体リテラルstruct{x float}{0}
をメモリ上に構築するような内部的な副作用が含まれる可能性があります。 dcl.c
内の関連コードパスでは、Alignof
の引数として渡された式が、本来は実行時に評価されるべきではないにもかかわらず、コンパイラの内部表現(ASTノード)上で副作用を持つ可能性のある操作として扱われていました。- この副作用が、
Alignof
のコンパイル時評価という文脈において、コンパイラの内部状態を矛盾させ、最終的にセグメンテーション違反などのクラッシュを引き起こしていました。具体的には、Alignof
の引数として渡された式が、コンパイラが期待する「副作用のない」形式ではない場合に、内部的なエラーハンドリングが不十分であったり、不適切なコードパスが実行されたりしたことが原因と考えられます。
修正は、dcl.c
内の Alignof
の引数を処理する部分に addtop = N;
という行を追加することで行われました。これは、Alignof
の引数 N
を処理する際に、それ以前に発生した可能性のある不要な副作用を「破棄」または「無視」するようにコンパイラに指示するものです。これにより、コンパイラは Alignof
の引数から型情報のみを安全に抽出し、クラッシュを回避できるようになります。
コアとなるコードの変更箇所
変更は src/cmd/gc/dcl.c
ファイルの1箇所です。
--- a/src/cmd/gc/dcl.c
+++ b/src/cmd/gc/dcl.c
@@ -1591,6 +1591,7 @@ no:
return N;
yes:
+\taddtop = N;\t// any side effects disappear
val.ctype = CTINT;
val.u.xval = mal(sizeof(*n->val.u.xval));
mpmovecfix(val.u.xval, v);
この変更は、no:
と yes:
というラベルを持つコードブロックの一部にあります。このコンテキストは、おそらくコンパイル時定数やコンパイル時に評価可能な式を処理する部分であると推測されます。yes:
ブロックは、式がコンパイル時に評価可能であると判断された場合に実行されるパスです。
コアとなるコードの解説
追加された行 addtop = N; // any side effects disappear
がこのコミットの核心です。
N
: このコンテキストでは、N
はunsafe.Alignof
の引数として渡された式の抽象構文木(AST)ノードを指します。つまり、struct{x float}{0}.x
という式全体を表すノードです。addtop
:addtop
はGoコンパイラgc
の内部変数であり、現在のコンパイルユニット(関数やトップレベルの宣言など)に追加されるべきASTノードのリストの「トップ」または「現在の位置」を管理するために使用されます。コンパイラがコードを処理し、ASTを構築していく過程で、新しいノードがaddtop
が指す位置に追加されていきます。
通常、式が評価されると、その評価の結果や副作用に関連するノードが addtop
が指すリストに追加されます。しかし、unsafe.Alignof
の引数の場合、その式は型情報のみを提供すればよく、実行時の評価やそれに伴う副作用は不要です。
addtop = N;
という行は、N
(Alignof
の引数ノード)を現在の addtop
に設定することで、それ以前に Alignof
の引数を評価しようとした際に発生した可能性のある、不要な副作用を持つASTノードを「破棄」または「無視」する効果があります。つまり、N
より前の、Alignof
の引数評価に関連する一時的なノードや副作用を持つノードは、コンパイルユニットの最終的なASTには含まれなくなります。コメント // any side effects disappear
がその意図を明確に示しています。
これにより、コンパイラは Alignof
の引数から型情報のみを安全に抽出し、本来不要な副作用の処理をスキップすることで、クラッシュを回避できるようになります。これは、コンパイル時評価されるべき式と、実行時に評価されるべき式との間のコンパイラの内部的な区別を強化する修正と言えます。
関連リンク
- Go言語公式ドキュメント: https://go.dev/doc/
unsafe
パッケージのドキュメント: https://pkg.go.dev/unsafe- Goコンパイラ
gc
のソースコード (GitHub): https://github.com/golang/go/tree/master/src/cmd/compile - Go言語のGerritコードレビューシステム (現在はGitHubに移行): https://go-review.googlesource.com/
参考にした情報源リンク
- Go言語のソースコード (特に
src/cmd/gc/dcl.c
の周辺コード) - Go言語の
unsafe
パッケージに関する公式ドキュメント - Goコンパイラの内部構造に関する一般的な知識 (AST、型チェック、コード生成など)
- GerritのChange-Idに関する情報 (OCL/CLの理解のため)
- Go言語のコンパイルプロセスに関する一般的な解説記事