[インデックス 1923] ファイルの概要
このコミットは、Go言語のreflect
パッケージにおけるメモリレイアウトの取り扱い、特に構造体内のフィールドのアラインメント計算を洗練させることに焦点を当てています。型の一般的なアラインメントと、その型が構造体のフィールドとして使用される際に要求されるアラインメントを明確に区別することで、構造体の正確なサイズ計算と、特にgccgo
コンパイラにおける外部ABI(Application Binary Interface)との互換性を確保することを目的としています。
コミット
- Author: Ian Lance Taylor iant@golang.org
- Date: Mon Mar 30 23:19:31 2009 -0700
- Commit Hash: 4e8417481689214e388c3e978bf6af156e117de9
- Files Changed:
src/lib/reflect/type.go
(1 file changed, 111 insertions(+), 38 deletions(-))
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4e8417481689214e388c3e978bf6af156e117de9
元コミット内容
Separate the alignment of a field from the alignment of the
type of the field. Use the field alignment to compute the
size of a structure.
This may help 8g but is mainly for gccgo. gccgo maintains the
standard C/C++ ABI for structure field alignment. For the
i386, this requires that a float64 field in a struct be
aligned on a 32-bit boundary, although for efficiency a
variable of type float64 or []float64 should be aligned on a
64-bit boundary.
I also removed the unused size field from structField.
変更の背景
このコミットの主な背景には、Go言語のreflect
パッケージが提供する型情報の正確性を向上させ、特にgccgo
コンパイラがC/C++の標準的なABI(Application Binary Interface)に準拠できるようにするという目的があります。
Go言語には、主に2つの主要なコンパイラが存在します。一つはGo言語チームが開発・保守する公式コンパイラであるgc
(当時のi386向けは8g
と呼ばれていました)、もう一つはGNU Compiler Collection (GCC) のGo言語フロントエンドであるgccgo
です。gccgo
はGCCの強力な最適化バックエンドを利用できる一方で、CやC++など他の言語で書かれたコードやライブラリと連携する際には、それらの言語が従うABIに準拠する必要があります。
特に問題となったのは、i386アーキテクチャにおけるfloat64
型(C/C++におけるdouble
に相当)のメモリ配置です。C/C++のSystem V ABIでは、float64
が構造体のフィールドとして使用される場合、そのアラインメントは32ビット(4バイト)境界に制限されます。これは、float64
変数を単独で宣言したり、float64
の配列を宣言したりする場合に、パフォーマンスのために64ビット(8バイト)境界にアラインされることが望ましいという一般的な慣習とは異なります。
このアラインメントの不一致は、gccgo
がGoの構造体をC/C++の構造体と互換性のある形でメモリに配置しようとする際に問題を引き起こします。Goのreflect
パッケージが提供する型情報が、このABIの要件を正確に反映していなければ、gccgo
は誤ったメモリレイアウトのコードを生成し、C/C++コードとの連携が破綻する可能性があります。
このコミットは、この問題を解決するために、Goの型システムにおいて「型自体の一般的なアラインメント」と「その型が構造体のフィールドとして使用される際に要求されるアラインメント」を明確に区別するメカニズムを導入しました。これにより、reflect
パッケージが構造体のサイズを計算する際に、各フィールドのABIに準拠した正確なアラインメント情報を利用できるようになり、gccgo
がC/C++のABIに適合したコードを生成できるようになります。コミットメッセージでは8g
にも役立つ可能性が示唆されていますが、主な動機はgccgo
のABI互換性向上にありました。
前提知識の解説
このコミットの変更内容を深く理解するためには、以下の概念を把握しておく必要があります。
-
メモリのアラインメント (Memory Alignment): CPUがメモリからデータを読み書きする際、データが特定のメモリアドレス境界に配置されている必要があるという要件です。例えば、4バイトの整数は4の倍数のアドレス(0x00, 0x04, 0x08など)に配置されると、CPUは効率的にアクセスできます。アラインメントが正しくないと、CPUが複数回のメモリアクセスを必要としたり、場合によってはハードウェア例外(アラインメント違反)が発生したりする可能性があります。Go言語では、
unsafe.Alignof()
関数を使って、特定の型の変数がメモリ上で要求するアラインメントをバイト単位で取得できます。 -
構造体のアラインメントとパディング (Struct Alignment and Padding): 構造体は複数のフィールド(メンバー変数)を持つ複合型です。構造体内の各フィールドは、その型が要求するアラインメントに従ってメモリに配置されます。この際、フィールド間に「パディング」と呼ばれる未使用のバイトが挿入されることがあります。これは、次のフィールドが正しいアラインメントで始まるようにするため、あるいは構造体全体のサイズが特定の境界に揃うようにするために行われます。構造体全体のサイズも、その構造体内で最も大きなアラインメント要求を持つフィールドのアラインメントの倍数になるように調整されます。Go言語では、
unsafe.Sizeof()
関数を使って、特定の型の変数がメモリ上で占めるバイト数を取得できます。構造体の場合は、パディングバイトも含まれたサイズが返されます。 -
ABI (Application Binary Interface): ABIは、異なるコンパイラやプログラミング言語でコンパイルされたコードが互いに連携して動作するために必要な、低レベルの規約のセットです。これには、関数呼び出し規約(引数の渡し方、戻り値の受け取り方)、データ型のメモリレイアウト(構造体のフィールド配置、アラインメント)、レジスタの使用方法などが含まれます。特に、CやC++で書かれたライブラリやシステムコールとGoのコードが連携する際には、両者が同じABIに準拠していることが不可欠です。
-
8g
とgccgo
:8g
: これは、2009年当時のGo言語の公式コンパイラであるgc
の、i386(32ビットx86)アーキテクチャ向けバージョンを指します。gc
コンパイラはGo言語チームによって開発・保守されており、Go言語の標準的な実装を提供します。gccgo
: これは、GNU Compiler Collection (GCC) のGo言語フロントエンドです。gccgo
はGoのソースコードをGCCの中間表現に変換し、GCCの強力な最適化バックエンドを利用して機械語を生成します。このため、gccgo
はGCCがサポートする幅広いアーキテクチャやオペレーティングシステムに対応できる利点があります。また、C/C++など他のGCCがサポートする言語で書かれたコードとの連携が容易ですが、その反面、Go言語の最新機能への対応がgc
に比べて遅れることがあります。このコミットの文脈では、gccgo
がC/C++のABIに厳密に準拠する必要がある点が重要です。
-
reflect
パッケージ: Go言語のreflect
パッケージは、プログラムの実行時に型情報を検査・操作するための機能を提供します。これにより、変数の型、その型の種類(構造体、配列、関数など)、サイズ、アラインメント、構造体のフィールド情報などを動的に取得できます。このコミットでは、reflect
パッケージが提供する型情報に「フィールドアラインメント」という新しい概念を導入し、構造体のサイズ計算ロジックを改善しています。
技術的詳細
このコミットは、Goのreflect
パッケージにおける型情報の表現と、それを用いた構造体のメモリレイアウト計算に根本的な変更を加えています。
-
reflect.Type
インターフェースの拡張: Goの型システムにおける抽象的な型を表すreflect.Type
インターフェースに、新たにFieldAlign() int
メソッドが追加されました。このメソッドは、その型が構造体のフィールドとして使用される場合に、メモリ上で要求されるアラインメント(バイト単位)を返します。これまでのSize()
メソッドが返すのは型全体のサイズであり、型のアラインメントと構造体フィールドとしてのアラインメントが異なる場合があるという、このコミットの核心的な問題意識を反映しています。 -
basicType
構造体の変更と初期化の改善:int
、float64
、string
などのGoの基本型を表す内部構造体であるbasicType
に、fieldAlign int
という新しいフィールドが追加されました。これにより、各基本型が構造体のメンバーとして持つべきアラインメント情報を明示的に保持できるようになりました。 このfieldAlign
値を正確に初期化するために、allTypes
という新しい構造体が導入されました。この構造体は、Goの様々な組み込み型をフィールドとして含んでいます。そして、グローバル変数x
としてallTypes
のインスタンスが宣言されています。 各基本型のreflect.Type
オブジェクトを生成するnewBasicType
関数は、fieldAlign
引数を受け取るように変更され、unsafe.Sizeof(x.x...)
とunsafe.Alignof(x.x...)
を用いて、x
の各フィールドの実際のサイズとアラインメント情報を取得し、それをnewBasicType
に渡すようになりました。これにより、reflect
パッケージが提供する型情報が、コンパイラが決定する実際のメモリレイアウトと一致するようになります。 -
複合型のアラインメント情報の追加: ポインタ型 (
ptrTypeStruct
)、配列型 (arrayTypeStruct
)、マップ型 (mapTypeStruct
)、チャネル型 (chanTypeStruct
)、インターフェース型 (interfaceTypeStruct
) など、他の複合型を表す構造体にも、同様にFieldAlign()
メソッドが実装されました。これらのメソッドは、それぞれの型が構造体フィールドとして使用される場合に要求されるアラインメントを、対応するallTypes
のフィールドに対するunsafe.Alignof()
の結果に基づいて返します。例えば、ptrTypeStruct.FieldAlign()
はunsafe.Alignof(x.xptr)
を返します。 -
構造体サイズ計算ロジックの変更 (
structTypeStruct.Size()
): このコミットの最も重要な変更は、構造体全体のサイズを計算するstructTypeStruct.Size()
メソッドのロジックの大幅な修正です。structField
からのsize
フィールド削除: 以前はstructField
構造体にsize
フィールドがありましたが、新しい計算ロジックにより不要になったため削除されました。- 正確なフィールドアラインメントの利用: 変更前は、各フィールドのサイズに基づいてアラインメントを単純に推測していましたが、変更後は
typ := t.field[i].typ.Get(); align := typ.FieldAlign() - 1;
のように、各フィールドのFieldAlign()
メソッドを呼び出して、その型が構造体フィールドとして要求する正確なアラインメントを取得するようになりました。これにより、i386におけるfloat64
のように、型自体のサイズとフィールドとしてのアラインメントが異なるケースにも対応できます。 - パディング計算の改善:
size = (size + align) &^ align;
の行は、現在の構造体サイズに適切なパディングを追加し、次のフィールドが正しいアラインメント境界から始まるように調整するロジックです。&^
はビットクリア演算子で、size
をalign + 1
の倍数に切り上げる効果があります。 - 構造体全体のアラインメントの決定:
structalign
変数は、構造体内の全フィールドの中で最も厳しいアラインメント要求を追跡します。最終的に、構造体全体のサイズがこのstructalign
の倍数になるように調整され、structTypeStruct.fieldAlign
にもその値が設定されます。
これらの変更により、Goのreflect
パッケージは、コンパイラが実際に生成するメモリレイアウト、特にABIの要件をより正確に反映できるようになり、gccgo
のようなコンパイラがC/C++のコードとシームレスに連携するための基盤が強化されました。
コアとなるコードの変更箇所
src/lib/reflect/type.go
ファイルがこのコミットの主要な変更対象です。
-
Type
インターフェースの定義変更:type Type interface { // ... 既存のメソッド ... // The alignment of a value of this type when used as a field in a struct. FieldAlign() int; }
Type
インターフェースにFieldAlign()
メソッドが追加され、すべてのreflect.Type
がフィールドとしてのアラインメント情報を提供する責任を負うようになりました。 -
basicType
構造体の変更と初期化:type basicType struct { commonType; fieldAlign int; // 新しく追加されたフィールド } func newBasicType(name string, kind int, size int, fieldAlign int) Type { return &basicType{ commonType{kind, name, name, size}, fieldAlign } }
basicType
にfieldAlign
が追加され、newBasicType
のシグネチャも変更されました。// For sizes and alignments. type allTypes struct { xarray []byte; xbool bool; xchan chan byte; xfloat float; xfloat32 float32; xfloat64 float64; xint int; xint16 int16; xint32 int32; xint64 int64; xint8 int8; xinterface interface {}; xmap map[byte]byte; xptr *byte; xslice []byte; xstring string; xuint uint; xuint16 uint16; xuint32 uint32; xuint64 uint64; xuint8 uint8; xuintptr uintptr; } var x allTypes // グローバル変数としてインスタンス化 // 各基本型の初期化部分 (抜粋) var ( // ... Bool = newBasicType("bool", BoolKind, unsafe.Sizeof(x.xbool), unsafe.Alignof(x.xbool)); Int = newBasicType("int", IntKind, unsafe.Sizeof(x.xint), unsafe.Alignof(x.xint)); // ... Float64 = newBasicType("float64", Float64Kind, unsafe.Sizeof(x.xfloat64), unsafe.Alignof(x.xfloat64)); // ... )
allTypes
構造体とx
変数が導入され、unsafe.Sizeof
とunsafe.Alignof
を用いて各基本型のサイズとフィールドアラインメントが初期化されるようになりました。 -
structField
構造体からのsize
フィールド削除:--- a/src/lib/reflect/type.go +++ b/src/lib/reflect/type.go @@ -302,7 +302,6 @@ type structField struct { name string; typ *stubType; tag string; - size int; // この行が削除された offset int; }
structField
からsize
フィールドが削除されました。 -
structTypeStruct.Size()
メソッドの変更:--- a/src/lib/reflect/type.go +++ b/src/lib/reflect/type.go @@ -302,40 +355,51 @@ type structField struct {\n \tname\tstring;\n \ttyp\t*stubType;\n \ttag\tstring;\n-\tsize\tint;\n \toffset\tint;\n }\n \n type structTypeStruct struct {\n \tcommonType;\n \tfield\t[]structField;\n+\tfieldAlign\tint;\n }\n \n func newStructTypeStruct(name, typestring string, field []structField) *structTypeStruct {\n-\treturn &structTypeStruct{ commonType{StructKind, typestring, name, 0}, field}\n+\treturn &structTypeStruct{ commonType{StructKind, typestring, name, 0}, field, 0}\n+}\n+\n+func (t *structTypeStruct) FieldAlign() int {\n+\tt.Size();\t// Compute size and alignment.\n+\treturn t.fieldAlign\n }\n \n-// TODO: not portable; depends on 6g\n func (t *structTypeStruct) Size() int {\n \tif t.size > 0 {\n \t\treturn t.size\n \t}\n \tsize := 0;\n-\tstructalignmask := 7;\t// BUG: we know structs are 8-aligned\n+\tstructalign := 0;\n \tfor i := 0; i < len(t.field); i++ {\n-\t\telemsize := t.field[i].typ.Get().Size();\n-\t\t// pad until at (elemsize mod 8) boundary\n-\t\talign := elemsize - 1;\n-\t\tif align > structalignmask {\n-\t\t\talign = structalignmask\n+\t\ttyp := t.field[i].typ.Get();\n+\t\telemsize := typ.Size();\n+\t\talign := typ.FieldAlign() - 1;\n+\t\tif align > structalign {\n+\t\t\tstructalign = align\n \t\t}\n \t\tif align > 0 {\n-\t\t\tsize = (size + align) & ^align;\n+\t\t\tsize = (size + align) &^ align;\n \t\t}\n \t\tt.field[i].offset = size;\n \t\tsize += elelmsize;\n \t}\n-\tsize = (size + structalignmask) & ^(structalignmask);\n+\tif (structalign > 0) {\n+\t\t// TODO: In the PPC64 ELF ABI, floating point fields\n+\t\t// in a struct are aligned to a 4-byte boundary, but\n+\t\t// if the first field in the struct is a 64-bit float,\n+\t\t// the whole struct is aligned to an 8-byte boundary.\n+\t\tsize = (size + structalign) &^ structalign;\n+\t\tt.fieldAlign = structalign + 1;\n+\t}\n \tt.size = size;\n \treturn size;\n }\n ``` `structTypeStruct.Size()`のロジックが大幅に変更され、各フィールドの`FieldAlign()`を使用して構造体フィールドのアラインメントとパディングを計算するように修正されました。また、構造体全体の`fieldAlign`も計算されるようになりました。
コアとなるコードの解説
このコミットにおけるコアとなるコードの変更は、src/lib/reflect/type.go
ファイルに集約されており、Goの型システムがメモリレイアウト、特にアラインメント情報をどのように扱うかを根本的に変えています。
-
type Type interface { ... FieldAlign() int; }
: Goのreflect
パッケージにおいて、すべての型を表す最も基本的なインターフェースであるType
に、FieldAlign() int
という新しいメソッドが追加されました。このメソッドは、その型が構造体のフィールドとして配置される場合に、メモリ上で要求されるアラインメントをバイト単位で返します。これは、型自体の一般的なアラインメント(Size()
メソッドが間接的に示すもの)と、構造体フィールドとしてのアラインメントを明確に区別するという、このコミットの最も重要な設計変更を象徴しています。これにより、Goの型システムは、より粒度の高いメモリレイアウト情報を提供できるようになりました。 -
type basicType struct { commonType; fieldAlign int; }
とnewBasicType(...)
:int
、float64
、string
といったGoの組み込み基本型は、内部的にはbasicType
構造体によって表現されます。このコミットでは、basicType
構造体にfieldAlign int
という新しいフィールドが追加されました。このフィールドは、その基本型が構造体のメンバーとして使用される際に要求されるアラインメント値を保持します。newBasicType
関数は、このfieldAlign
値を初期化時に受け取るように変更されました。これにより、各基本型は自身のフィールドアラインメント情報を内部に持つことができ、reflect
パッケージがその情報を利用できるようになります。 -
type allTypes struct { ... }
とvar x allTypes
: このコミットで導入されたallTypes
構造体は、Goの様々な組み込み型(例:byte
,bool
,chan
,float
,int
など)をフィールドとして含んでいます。そして、var x allTypes
という形で、このallTypes
構造体のグローバルインスタンスx
が宣言されています。 このx
のフィールドに対して、Goのunsafe
パッケージが提供するunsafe.Sizeof()
とunsafe.Alignof()
関数が使用されます。例えば、unsafe.Sizeof(x.xfloat64)
はfloat64
型のサイズを、unsafe.Alignof(x.xfloat64)
はfloat64
型がメモリ上で要求するアラインメントを返します。これらの関数はコンパイル時に評価されるため、コンパイラが実際に割り当てる各型のサイズとアラインメント情報を正確に取得できます。 これらの取得された値は、newBasicType
の呼び出し時に引数として渡され、reflect
パッケージの型情報が実際のメモリレイアウトと一致するように、基本型のsize
とfieldAlign
が初期化されます。これは、Goの型システムが低レベルのメモリ特性を正確に反映するための重要なメカニズムです。 -
func (t *structTypeStruct) Size() int { ... }
: この関数は、Goのreflect
パッケージにおいて、構造体全体のサイズを計算する非常に重要なロジックを含んでいます。このコミットでは、この関数の内部実装が大きく変更されました。- フィールドアラインメントの利用: 以前は、各フィールドのサイズに基づいてアラインメントを単純に推測していましたが、変更後は
typ := t.field[i].typ.Get(); align := typ.FieldAlign() - 1;
のように、各フィールドのFieldAlign()
メソッドを呼び出して、その型が構造体フィールドとして要求する正確なアラインメントを取得するようになりました。これにより、i386におけるfloat64
のように、型自体のサイズとフィールドとしてのアラインメントが異なるケースにも正確に対応できるようになりました。 - パディングの挿入:
size = (size + align) &^ align;
の行は、現在の構造体サイズに適切なパディングを追加し、次のフィールドが正しいアラインメント境界から始まるように調整するロジックです。&^
はビットクリア演算子で、size
をalign + 1
の倍数に切り上げる効果があります。これにより、各フィールドがその型が要求するアラインメントで配置されることが保証されます。 - 構造体全体のアラインメント:
structalign
変数は、構造体内の全フィールドの中で最も厳しいアラインメント要求を追跡します。最終的に、構造体全体のサイズがこのstructalign
の倍数になるように調整され、structTypeStruct.fieldAlign
にもその値が設定されます。これは、構造体全体がメモリ上で正しくアラインされることを保証するために重要です。
- フィールドアラインメントの利用: 以前は、各フィールドのサイズに基づいてアラインメントを単純に推測していましたが、変更後は
これらの変更により、Goのreflect
パッケージは、コンパイラが実際に生成するメモリレイアウト、特にC/C++ ABIのような外部規約の要件をより正確に反映できるようになり、gccgo
のようなコンパイラがC/C++のコードとシームレスに連携するための基盤が強化されました。
関連リンク
- Go言語の
unsafe
パッケージに関する公式ドキュメント: https://pkg.go.dev/unsafe - Go言語の
reflect
パッケージに関する公式ドキュメント: https://pkg.go.dev/reflect - GCCGoプロジェクトページ (当時の情報): https://gcc.gnu.org/onlinedocs/gccgo/ (現在のドキュメントは異なる可能性があります)
参考にした情報源リンク
- Go reflect package unsafe.Sizeof unsafe.Alignof (Web Search Result)
- C++ ABI struct alignment i386 float64 (Web Search Result)
- Go 8g vs gccgo compiler differences (Web Search Result)