Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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=26911CL=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 のように型情報のみを必要とする場合、引数の式が副作用を持つと、コンパイラが予期せぬ動作をしたり、クラッシュしたりする可能性があります。

OCLCL

OCL (Old Change List) と CL (Change List) は、Goプロジェクトが初期に採用していたGerritベースのコードレビューシステムで使われていた変更セットの識別子です。これは、現在のGitHubのプルリクエストに相当する概念です。

技術的詳細

このコミットが修正した問題は、Goコンパイラが unsafe.Alignof の引数を処理する際の、式の評価と副作用の管理に関するものです。

unsafe.Alignof(struct{x float}{0}.x) という式を考えます。

  1. unsafe.Alignof はコンパイル時関数であり、引数の「型」のみを必要とします。struct{x float}{0}.x の型は float です。
  2. しかし、コンパイラは struct{x float}{0}.x という式を解析する際に、その型を決定するだけでなく、その式を「評価」しようとすることがありました。この評価プロセスには、一時的な構造体リテラル struct{x float}{0} をメモリ上に構築するような内部的な副作用が含まれる可能性があります。
  3. dcl.c 内の関連コードパスでは、Alignof の引数として渡された式が、本来は実行時に評価されるべきではないにもかかわらず、コンパイラの内部表現(ASTノード)上で副作用を持つ可能性のある操作として扱われていました。
  4. この副作用が、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: このコンテキストでは、Nunsafe.Alignof の引数として渡された式の抽象構文木(AST)ノードを指します。つまり、struct{x float}{0}.x という式全体を表すノードです。
  • addtop: addtop はGoコンパイラ gc の内部変数であり、現在のコンパイルユニット(関数やトップレベルの宣言など)に追加されるべきASTノードのリストの「トップ」または「現在の位置」を管理するために使用されます。コンパイラがコードを処理し、ASTを構築していく過程で、新しいノードが addtop が指す位置に追加されていきます。

通常、式が評価されると、その評価の結果や副作用に関連するノードが addtop が指すリストに追加されます。しかし、unsafe.Alignof の引数の場合、その式は型情報のみを提供すればよく、実行時の評価やそれに伴う副作用は不要です。

addtop = N; という行は、NAlignof の引数ノード)を現在の addtop に設定することで、それ以前に Alignof の引数を評価しようとした際に発生した可能性のある、不要な副作用を持つASTノードを「破棄」または「無視」する効果があります。つまり、N より前の、Alignof の引数評価に関連する一時的なノードや副作用を持つノードは、コンパイルユニットの最終的なASTには含まれなくなります。コメント // any side effects disappear がその意図を明確に示しています。

これにより、コンパイラは Alignof の引数から型情報のみを安全に抽出し、本来不要な副作用の処理をスキップすることで、クラッシュを回避できるようになります。これは、コンパイル時評価されるべき式と、実行時に評価されるべき式との間のコンパイラの内部的な区別を強化する修正と言えます。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード (特に src/cmd/gc/dcl.c の周辺コード)
  • Go言語の unsafe パッケージに関する公式ドキュメント
  • Goコンパイラの内部構造に関する一般的な知識 (AST、型チェック、コード生成など)
  • GerritのChange-Idに関する情報 (OCL/CLの理解のため)
  • Go言語のコンパイルプロセスに関する一般的な解説記事