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

[インデックス 1387] ファイルの概要

このコミットは、Go言語のコンパイラとランタイムにおけるインターフェースの実装、特に配列(スライス)と構造体の扱いに関する重要な改善とリファクタリングを含んでいます。convert() 関数の廃止と、複合リテラル(composite literal)の内部表現の変更も含まれており、Go言語の型システムとインターフェースの効率性を向上させるための基盤的な変更がなされています。

コミット

commit c3077f7606b8f45d010d5d87c7fa748ef5b88368
Author: Russ Cox <rsc@golang.org>
Date:   Fri Dec 19 17:11:54 2008 -0800

    [] and struct in interfaces.
    other [] cleanup.

    convert() is gone.

    R=r
    DELTA=352  (144 added, 68 deleted, 140 changed)
    OCL=21660
    CL=21662

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/c3077f7606b8f45d010d5d87c7fa748ef5b88368

元コミット内容

このコミットは、以下の主要な変更を含んでいます。

  • インターフェース内での配列(スライス)と構造体のサポートの改善。
  • 配列(スライス)関連のコードのクリーンアップ。
  • convert() 関数の削除。

変更の背景

Go言語の初期開発段階において、インターフェースは言語の重要な特徴の一つでしたが、その内部実装はまだ最適化の余地がありました。特に、値型である配列や構造体がインターフェースに格納される際の効率性や、コンパイラが複合リテラルをどのように扱うかという点に課題がありました。

このコミットの背景には、以下の目的があったと考えられます。

  1. インターフェースの汎用性と効率性の向上: 配列や構造体といった複合型をインターフェースに格納する際のオーバーヘッドを削減し、より自然で効率的な利用を可能にする。
  2. コンパイラの内部表現の明確化: 複合リテラルが「型変換」として扱われるのではなく、それ自体が独立した構文要素としてコンパイラに認識されるようにすることで、コンパイラのロジックを簡素化し、堅牢性を高める。
  3. ランタイムパフォーマンスの最適化: インターフェース値の内部表現を改善し、特に小さな値の場合に余分なヒープアロケーションを避けることで、メモリ使用量と実行速度を向上させる。

Go言語は静的型付け言語でありながら、ダックタイピングを可能にするインターフェースを特徴としています。この柔軟性を維持しつつ、基盤となるランタイムの効率を高めることは、言語全体のパフォーマンスにとって不可欠でした。

前提知識の解説

このコミットを理解するためには、以下のGo言語およびコンパイラの基本的な概念を理解しておく必要があります。

  • Go言語のインターフェース: Goのインターフェースは、メソッドのシグネチャの集合を定義する型です。任意の具象型がインターフェースで定義されたすべてのメソッドを実装していれば、そのインターフェース型として扱われます。インターフェース値は、内部的に「型情報」と「値(データ)」のペアとして表現されます。
  • 複合リテラル (Composite Literals): Go言語で構造体、配列、スライス、マップなどの複合型の値を初期化するための構文です。例えば、[]int{1, 2, 3}Point{X: 1, Y: 2} などがこれにあたります。
  • コンパイラのフェーズ:
    • 字句解析 (Lexing): ソースコードをトークン(単語)のストリームに変換します。
    • 構文解析 (Parsing): トークンのストリームを解析し、抽象構文木 (AST: Abstract Syntax Tree) を構築します。Goコンパイラでは、go.y (Yacc/Bisonの文法定義ファイル) がこの役割を担います。
    • 型チェックとASTウォーク (Type Checking and AST Walking): ASTを走査し、型の整合性をチェックしたり、最適化やコード生成のための変換を行います。walk.c がこのフェーズの一部を担当します。
  • ランタイム (Runtime): Goプログラムの実行を管理する部分で、ガベージコレクション、スケジューリング、インターフェースの動的なディスパッチなどを担当します。runtime/iface.c はインターフェースのランタイムサポートを実装しています。
  • reflect パッケージ: Goのプログラムが実行時に自身の構造を検査(リフレクション)するための機能を提供します。インターフェースの内部構造と密接に関連しています。
  • syscall パッケージ: オペレーティングシステムの低レベルな機能にアクセスするためのパッケージです。このコミットでは、バイトスライスの扱いに関連して言及されています。

技術的詳細

このコミットの技術的な詳細は、主にGoコンパイラのフロントエンド(字句解析、構文解析、AST変換)とランタイムのインターフェース実装に集中しています。

  1. Iface 構造体の導入とインターフェース表現の統一:

    • 最も重要な変更は、src/runtime/runtime.hIface 構造体が導入され、src/runtime/iface.c でその実装が変更されたことです。
    • 以前は、インターフェース値は Map *im(型情報)と void *it(データ)という2つのポインタのペアとして扱われていたようです。
    • 新しい Iface 構造体は、Itype *type(型情報)と void *data[1](データ)を直接含みます。
    • void *data[1] は、Goランタイムにおける「小さな値の最適化 (small value optimization)」を示唆しています。これは、インターフェースに格納される値がポインタサイズ(またはそれ以下)の小さな値である場合、その値をヒープに別途アロケートするのではなく、Iface 構造体自体に直接格納することで、メモリ使用量とアロケーションコストを削減する最適化です。大きな値の場合は、data[0] がヒープにアロケートされた値へのポインタとなります。
    • これにより、インターフェース値の内部表現がより統一され、効率的になりました。
  2. convert() の廃止と OCOMP の導入:

    • src/cmd/gc/go.hsrc/cmd/gc/go.ysrc/cmd/gc/lex.csrc/cmd/gc/walk.c の変更から、Goコンパイラ内部で LCONVERT トークンと OCONV オペレーションが複合リテラルを扱うために使われていたことがわかります。
    • このコミットでは、LCONVERT が字句解析器から削除され、OCONVOCOMP(Composite Literal)に置き換えられました。
    • これは、複合リテラルが「型変換」の一種として扱われるのではなく、それ自体が独立したASTノード(OCOMP)として認識されるようになったことを意味します。これにより、コンパイラは複合リテラルをより直接的かつ正確に処理できるようになり、コンパイラのロジックが簡素化され、堅牢性が向上します。
  3. インターフェースにおける配列と構造体のサポート強化:

    • src/cmd/gc/subr.csigname 関数から if(t->etype == TARRAY) という配列をインターフェースに許可しないチェックが削除されました。
    • test/bigalg.go に追加された interfacetest は、配列、配列へのポインタ、構造体をインターフェースに代入し、型アサーションで取り出すテストを含んでいます。これは、これらの型がインターフェースで正しく扱えるようになったことを検証しています。
    • src/lib/fmt/print.gogetString 関数が reflect.ArrayKind を直接処理するように変更されたことも、バイトスライス([]byte)がインターフェース内でより効率的に扱われるようになったことを示唆しています。
  4. ランタイムインターフェース関数の更新:

    • src/runtime/iface.c 内の sys·ifaceT2I (具象型からインターフェースへ)、sys·ifaceI2T (インターフェースから具象型へ)、sys·ifaceI2I (インターフェースからインターフェースへ) などの関数が、新しい Iface 構造体と Itype 構造体を使用するように全面的に更新されました。
    • これらの関数は、インターフェース値のコピー、型チェック、データアクセスロジックを、新しい Iface 構造体の data フィールドと algarray[alg].copy を用いて実装しています。特に、小さな値の最適化がこれらの関数内で考慮されています。

これらの変更は、Go言語のインターフェースがより効率的で、より多くの型をシームレスに扱えるようにするための重要なステップでした。

コアとなるコードの変更箇所

このコミットにおけるコアとなるコードの変更箇所は以下のファイルに集中しています。

  1. src/runtime/iface.c および src/runtime/runtime.h:

    • Map 構造体が Itype にリネームされ、インターフェースの型情報を表すようになりました。
    • Iface 構造体が新しく定義され、インターフェース値の統一された表現を提供します。この構造体は Itype *typevoid *data[1] を持ち、小さな値の最適化を可能にします。
    • インターフェース関連のランタイム関数(sys·ifaceT2I, sys·ifaceI2T, sys·ifaceI2I など)が、新しい Iface 構造体を使用するように全面的に書き換えられました。
  2. src/cmd/gc/go.y および src/cmd/gc/walk.c:

    • go.y (Yacc文法ファイル) から LCONVERT トークンと関連する文法ルールが削除されました。
    • 複合リテラルを生成する際に、OCONV オペレーションが OCOMP オペレーションに置き換えられました。
    • walk.c では、OCONV を処理していた複合リテラル関連のロジックが削除され、新しく追加された OCOMP ケースで処理されるようになりました。これにより、複合リテラルがコンパイラ内部でより明確に区別されるようになりました。
  3. src/cmd/gc/subr.c:

    • signame 関数から、配列型がインターフェースに格納されることを禁止していたチェック (if(t->etype == TARRAY)) が削除されました。これにより、配列がインターフェースでサポートされるようになりました。
  4. test/bigalg.go:

    • interfacetest 関数が追加され、配列、配列へのポインタ、構造体がインターフェースに代入され、正しく型アサーションできることを検証するテストが記述されました。

コアとなるコードの解説

src/runtime/runtime.h (新しい Iface 構造体)

struct Iface
{
    Itype *type;
    void *data[1];  // could make bigger later
};

この定義は、Goのインターフェース値がランタイムでどのように表現されるかの核心です。

  • Itype *type: インターフェースに格納されている具象型の型情報へのポインタです。この型情報には、具象型が実装しているメソッドのテーブルなどが含まれます。
  • void *data[1]: インターフェースに格納されている実際のデータです。
    • もし具象型の値がポインタサイズ(例えば64ビットシステムで8バイト)以下であれば、その値は直接この data 配列の最初の要素に格納されます。これにより、ヒープアロケーションが不要になり、パフォーマンスが向上します。
    • もし具象型の値がポインタサイズよりも大きい場合、その値はヒープにアロケートされ、data[0] はそのヒープ上のデータへのポインタとなります。

src/runtime/iface.c (インターフェース変換関数の変更例)

sys·ifaceT2I (具象型からインターフェースへの変換) の変更は、新しい Iface 構造体の利用を示しています。

// 変更前 (簡略化)
// void sys·ifaceT2I(Sigi *si, Sigt *st, void *elem, Map *retim, void *retit)
// {
//     retim = hashmap(si, st, 0); // 型情報を取得
//     retit = elem;               // データを直接コピー
//     // ...
// }

// 変更後 (簡略化)
// sys·ifaceT2I(Sigi *si, Sigt *st, ...) の引数リストが変更され、
// 戻り値として Iface 構造体へのポインタが暗黙的に渡されるようになる
void
sys·ifaceT2I(Sigi *si, Sigt *st, ...)
{
    byte *elem;
    Iface *ret; // 新しい Iface 構造体へのポインタ
    int32 alg, wid;

    elem = (byte*)(&st+1); // 可変引数リストから elem を取得
    wid = st->offset;      // 型の幅(サイズ)を取得
    ret = (Iface*)(elem + rnd(wid, 8)); // 戻り値の Iface 構造体の位置を計算
    ret->type = itype(si, st, 0); // 型情報を設定

    if(wid <= sizeof ret->data) { // 小さな値の場合
        algarray[alg].copy(wid, ret->data, elem); // データを直接コピー
    } else { // 大きな値の場合
        ret->data[0] = mal(wid); // ヒープをアロケート
        algarray[alg].copy(wid, ret->data[0], elem); // ヒープにデータをコピー
    }
    // ...
}

この変更は、インターフェースへの値の格納方法が、値のサイズに応じて最適化されるようになったことを明確に示しています。algarray[alg].copy は、特定の型(alg で識別される)のデータをコピーするためのランタイムヘルパー関数です。

src/cmd/gc/go.y (複合リテラルの文法変更)

--- a/src/cmd/gc/go.y
+++ b/src/cmd/gc/go.y
@@ -884,7 +879,7 @@ pexpr:
  	\t\t\t$$ = nod(OEMPTY, N, N);\
  	\t\tif(!iscomposite($1))\
  	\t\t\tyyerror(\"illegal composite literal type %T\", $1);\
-\t\t$$ = nod(OCONV, $$, N);\
+\t\t$$ = nod(OCOMP, $$, N);\
  	\t\t$$->type = $1;\
  	\t}\
  |\tfnliteral

この差分は、構文解析器が複合リテラルを処理する際に、以前は OCONV (変換) ノードとして扱っていたものを、新しい OCOMP (複合リテラル) ノードとして生成するように変更されたことを示しています。これにより、コンパイラは複合リテラルをよりセマンティックに正確に表現できるようになります。

src/cmd/gc/walk.c (複合リテラルのASTウォーク処理の変更)

--- a/src/cmd/gc/walk.c
+++ b/src/cmd/gc/walk.c
@@ -595,26 +594,6 @@ loop:
  	\tif(issarray(t) && isdarray(l->type))\
  	\t\tgoto ret;\
  \
-\t\t// structure literal\
-\t\tif(t->etype == TSTRUCT) {\
-\t\t\tindir(n, structlit(n));\
-\t\t\tgoto ret;\
-\t\t}\
-\
-\t\t// array literal\
-\t\tif(t->etype == TARRAY) {\
-\t\t\tr = arraylit(n);\
-\t\t\tindir(n, r);\
-\t\t\tgoto ret;\
-\t\t}\
-\
-\t\t// map literal\
-\t\tif(isptr[t->etype] && t->type != t && t->type->etype == TMAP) {\
-\t\t\tr = maplit(n);\
-\t\t\tindir(n, r);\
-\t\t\tgoto ret;\
-\t\t}\
-\
  \t\t// interface and structure\
  \t\tet = isandss(n->type, l);\
  \t\tif(et != Inone) {\
@@ -642,6 +621,43 @@ loop:
  \t\t\tyyerror(\"cannot convert %T to %T\", l->type, t);\
  \t\tgoto ret;\
  \
+\tcase OCOMP:\
+\t\tif(top == Etop)\
+\t\t\tgoto nottop;\
+\
+\t\tl = n->left;\
+\t\tif(l == N)\
+\t\t\tgoto ret;\
+\
+\t\twalktype(l, Erv);\
+\
+\t\tt = n->type;\
+\t\tif(t == T)\
+\t\t\tgoto ret;\
+\
+\t\t// structure literal\
+\t\tif(t->etype == TSTRUCT) {\
+\t\t\tindir(n, structlit(n));\
+\t\t\tgoto ret;\
+\t\t}\
+\
+\t\t// array literal\
+\t\tif(t->etype == TARRAY) {\
+\t\t\tr = arraylit(n);\
+\t\t\tindir(n, r);\
+\t\t\tgoto ret;\
+\t\t}\
+\
+\t\t// map literal\
+\t\tif(isptr[t->etype] && t->type != t && t->type->etype == TMAP) {\
+\t\t\tr = maplit(n);\
+\t\t\tindir(n, r);\
+\t\t\tgoto ret;\
+\t\t}\
+\
+\t\tyyerror(\"bad composite literal %T\", t);\
+\t\tgoto ret;\
+\
  \tcase ORETURN:\
  \t\tif(top != Etop)\
  \t\t\tgoto nottop;\

この差分は、walk.cloop 関数内で、以前 OCONV として処理されていた構造体、配列、マップの複合リテラルに関するロジックが削除され、新しく追加された OCOMP ケースに移動したことを示しています。これにより、コンパイラのASTウォークフェーズで複合リテラルがより適切に処理されるようになります。

関連リンク

  • Go言語のインターフェースに関する公式ドキュメントやチュートリアル(現在のGoのバージョンに基づく)
  • Goコンパイラのソースコードリポジトリ (GitHub)
  • Go言語の初期の設計ドキュメントやメーリングリストのアーカイブ(当時の議論を追うため)

参考にした情報源リンク

  • Go言語の公式ドキュメント: https://go.dev/doc/
  • Go言語のソースコード (GitHub): https://github.com/golang/go
  • Go言語の初期のコミット履歴と関連する設計ドキュメント(Goの進化を理解するため)
  • Go言語のインターフェース実装に関する技術記事やブログポスト(「小さな値の最適化」など)