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

[インデックス 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に準拠していることが不可欠です。

  • 8ggccgo:

    • 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パッケージにおける型情報の表現と、それを用いた構造体のメモリレイアウト計算に根本的な変更を加えています。

  1. reflect.Typeインターフェースの拡張: Goの型システムにおける抽象的な型を表すreflect.Typeインターフェースに、新たにFieldAlign() intメソッドが追加されました。このメソッドは、その型が構造体のフィールドとして使用される場合に、メモリ上で要求されるアラインメント(バイト単位)を返します。これまでのSize()メソッドが返すのは型全体のサイズであり、型のアラインメントと構造体フィールドとしてのアラインメントが異なる場合があるという、このコミットの核心的な問題意識を反映しています。

  2. basicType構造体の変更と初期化の改善: intfloat64stringなどのGoの基本型を表す内部構造体であるbasicTypeに、fieldAlign intという新しいフィールドが追加されました。これにより、各基本型が構造体のメンバーとして持つべきアラインメント情報を明示的に保持できるようになりました。 このfieldAlign値を正確に初期化するために、allTypesという新しい構造体が導入されました。この構造体は、Goの様々な組み込み型をフィールドとして含んでいます。そして、グローバル変数xとしてallTypesのインスタンスが宣言されています。 各基本型のreflect.Typeオブジェクトを生成するnewBasicType関数は、fieldAlign引数を受け取るように変更され、unsafe.Sizeof(x.x...)unsafe.Alignof(x.x...)を用いて、xの各フィールドの実際のサイズとアラインメント情報を取得し、それをnewBasicTypeに渡すようになりました。これにより、reflectパッケージが提供する型情報が、コンパイラが決定する実際のメモリレイアウトと一致するようになります。

  3. 複合型のアラインメント情報の追加: ポインタ型 (ptrTypeStruct)、配列型 (arrayTypeStruct)、マップ型 (mapTypeStruct)、チャネル型 (chanTypeStruct)、インターフェース型 (interfaceTypeStruct) など、他の複合型を表す構造体にも、同様にFieldAlign()メソッドが実装されました。これらのメソッドは、それぞれの型が構造体フィールドとして使用される場合に要求されるアラインメントを、対応するallTypesのフィールドに対するunsafe.Alignof()の結果に基づいて返します。例えば、ptrTypeStruct.FieldAlign()unsafe.Alignof(x.xptr)を返します。

  4. 構造体サイズ計算ロジックの変更 (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; の行は、現在の構造体サイズに適切なパディングを追加し、次のフィールドが正しいアラインメント境界から始まるように調整するロジックです。&^はビットクリア演算子で、sizealign + 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 }
    }
    

    basicTypefieldAlignが追加され、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.Sizeofunsafe.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(...): intfloat64stringといった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パッケージの型情報が実際のメモリレイアウトと一致するように、基本型のsizefieldAlignが初期化されます。これは、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; の行は、現在の構造体サイズに適切なパディングを追加し、次のフィールドが正しいアラインメント境界から始まるように調整するロジックです。&^はビットクリア演算子で、sizealign + 1の倍数に切り上げる効果があります。これにより、各フィールドがその型が要求するアラインメントで配置されることが保証されます。
    • 構造体全体のアラインメント: structalign変数は、構造体内の全フィールドの中で最も厳しいアラインメント要求を追跡します。最終的に、構造体全体のサイズがこのstructalignの倍数になるように調整され、structTypeStruct.fieldAlignにもその値が設定されます。これは、構造体全体がメモリ上で正しくアラインされることを保証するために重要です。

これらの変更により、Goのreflectパッケージは、コンパイラが実際に生成するメモリレイアウト、特にC/C++ ABIのような外部規約の要件をより正確に反映できるようになり、gccgoのようなコンパイラがC/C++のコードとシームレスに連携するための基盤が強化されました。

関連リンク

参考にした情報源リンク

  • 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)