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

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

このコミットは、Go言語のコンパイラ (6g) とリンカ (6l)、およびランタイムにおけるインターフェース関連の重要な変更を導入しています。主な目的は、nil インターフェースと任意の型との間の変換を許可すること、そして複数のコンパイル済みオブジェクトファイル (.6 ファイル) に含まれる型シグネチャの重複を効率的に処理することです。これにより、Goのインターフェースシステムがより柔軟になり、バイナリサイズが最適化されます。

コミット

commit 1983121bbb5d8cb346c1baef18e3dc1a2cbfee10
Author: Russ Cox <rsc@golang.org>
Date:   Wed Nov 5 11:27:50 2008 -0800

    6g interface changes:
            * allow conversion between nil interface and any type.
            * mark signatures as DUPOK so that multiple .6 can
              contain sigt.*[]byte and only one gets used.

    R=ken
    OCL=18538
    CL=18542

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

https://github.com/golang/go/commit/1983121bbb5d8cb346c1baef18e3dc1a2cbfee10

元コミット内容

6g interface changes:
        * allow conversion between nil interface and any type.
        * mark signatures as DUPOK so that multiple .6 can
          contain sigt.*[]byte and only one gets used.

変更の背景

Go言語の初期段階において、インターフェースの扱い、特に nil インターフェースの挙動や、コンパイルされた複数のパッケージ間で共有される型情報の管理には改善の余地がありました。

  1. nil インターフェースの柔軟性向上: 以前のGoでは、nil インターフェースと具体的な型との間の変換が常にスムーズに行われるわけではありませんでした。これは、nil インターフェースが (nil, nil) という内部表現を持つため、特定の型への変換時にランタイムエラーを引き起こす可能性がありました。このコミットは、この変換をより直感的で安全なものにすることを目指しています。

  2. 型シグネチャの重複排除: Goのコンパイルプロセスでは、各型(特にインターフェースやメソッドを持つ型)のランタイム表現に必要な「型シグネチャ」が生成されます。これらのシグネチャは、型のアラインメント、サイズ、メソッドセットなどの情報を含みます。複数のソースファイルやパッケージが同じ型を参照する場合、それぞれのコンパイル済みオブジェクトファイル (.6 ファイル) に同じ型シグネチャの定義が含まれる可能性がありました。これは最終的なバイナリの肥大化を招き、リンカが重複するシンボルをどのように解決するかという問題を引き起こします。このコミットは、リンカレベルでこれらの重複するシグネチャを効率的に処理し、最終バイナリに一意のシグネチャのみを含めるメカニズムを導入することで、この問題を解決しようとしています。

これらの変更は、Go言語のインターフェースのセマンティクスを洗練し、コンパイルおよびリンクプロセスの効率を高めるための基盤を築くものです。

前提知識の解説

このコミットを理解するためには、以下のGo言語およびコンパイラ/リンカに関する基本的な知識が必要です。

  1. Go言語のインターフェース:

    • Goのインターフェースは、メソッドのセットを定義する型です。
    • インターフェース型の変数は、そのインターフェースが定義するすべてのメソッドを実装する任意の具象型の値を保持できます。
    • 内部的には、Goのインターフェース値は2つのポインタで構成されます:
      • 型ポインタ (type pointer): 格納されている具象値の型情報(メソッドセット、サイズ、アラインメントなど)を指します。
      • データポインタ (data pointer): 格納されている具象値自体を指します。
    • nil インターフェースは、両方のポインタが nil である状態 (nil, nil) を指します。
  2. Goコンパイラ (6g):

    • Goのソースコード (.go ファイル) を、特定のアーキテクチャ(この場合は 6、つまりamd64)向けのオブジェクトファイル (.6 ファイル) にコンパイルするツールです。
    • コンパイル時に、型情報やメソッド情報がランタイムで利用できるように、特定のデータ構造(型シグネチャなど)を生成します。
  3. Goリンカ (6l):

    • コンパイラによって生成された複数のオブジェクトファイル (.6 ファイル) を結合し、必要なランタイムライブラリとリンクして、実行可能なバイナリを生成するツールです。
    • リンカは、異なるオブジェクトファイル間で参照されるシンボル(関数、変数、データ構造など)を解決する役割を担います。
  4. 型シグネチャ (Type Signatures):

    • Goのランタイムが型に関する情報を取得するために使用する内部データ構造です。これには、型の名前、サイズ、アラインメント、そしてその型が実装するメソッドのリストなどが含まれます。
    • 特にインターフェースのメソッドディスパッチや型アサーションにおいて、この情報が不可欠です。
    • sigt は具象型(struct, intなど)のシグネチャを指し、sigi はインターフェース型のシグネチャを指すことが多いです。
  5. DUPOK (Duplicate OK) シンボル:

    • リンカの概念の一つで、複数のオブジェクトファイルが同じ名前のシンボルを定義している場合に、リンカがエラーを発生させずに、そのうちの1つ(通常は最初に見つかったもの)を選択して使用することを許可するフラグです。
    • これは、C/C++における「弱いシンボル (weak symbols)」に似ており、ライブラリのデフォルト実装を提供しつつ、ユーザーが独自のバージョンでオーバーライドできるようにする場合などに使用されます。Goの文脈では、重複する型シグネチャの定義を効率的に処理するために利用されます。

技術的詳細

このコミットは、Goのコンパイラ、リンカ、ランタイムの複数のコンポーネントにわたる協調的な変更を含んでいます。

  1. nil インターフェース変換の改善:

    • src/cmd/gc/walk.cascompat 関数が変更され、isnilinter(t1) および isnilinter(t2) のチェックが追加されました。これは、nil インターフェースが他のインターフェース型や具象型に変換される際の互換性ルールを拡張するものです。
    • 具体的には、nil インターフェースは、その型がメソッドを持たない場合、任意の具象型やインターフェース型に変換可能であると見なされるようになります。これにより、var i interface{} = nil のようなコードから、i.(MyType) のような型アサーションがより安全に行えるようになります。
  2. 型シグネチャの動的生成と重複排除 (DUPOK フラグの導入):

    • コンパイラ (6g) 側の変更:
      • src/cmd/gc/go.hType および Sym 構造体に siggen (signature generated) フラグが追加されました。これは、特定の型シグネチャが既に生成済みであるかを追跡するために使用されます。
      • src/cmd/gc/subr.csigname 関数が大幅に簡素化されました。この関数は、与えられた型に対応するランタイムシグネチャのシンボル名を生成します。以前は block 引数がありましたが、これが削除され、より汎用的なシグネチャ名生成ロジックになりました。また、signame は、まだ生成されていない型シグネチャを signatlist に追加し、siggen フラグをセットする役割も担います。
      • src/cmd/6g/obj.cdumpsigt (具象型シグネチャのダンプ) および dumpsigi (インターフェース型シグネチャのダンプ) 関数が変更され、生成される AGLOBL 命令に DUPOK フラグが設定されるようになりました。AGLOBL は、グローバルシンボルを定義するためのアセンブラ命令です。
      • dumpsignatures 関数が導入され、signatlist を走査し、siggen フラグに基づいて各型シグネチャが一度だけダンプされるように制御します。これにより、コンパイラは必要なすべての型シグネチャを生成しますが、重複は避けます。
    • リンカ (6l) 側の変更:
      • src/cmd/6l/l.hSym 構造体に dupok (duplicate ok) フラグが追加されました。これは、リンカがシンボルを処理する際に、そのシンボルが重複を許容するかどうかを判断するために使用されます。
      • src/cmd/6l/obj.c では、AGLOBL 命令に DUPOK フラグが設定されている場合、対応するシンボルの dupok フラグがセットされます。
      • さらに重要なのは、ADATA (データセクション) 命令の処理において、もしシンボルが dupok フラグを持っている場合、そのシンボルに対する後続の ADATA 命令(つまり重複する定義)は無視されるようになりました。これにより、複数のオブジェクトファイルに同じ型シグネチャが含まれていても、リンカは最初の定義のみを採用し、最終バイナリには一意のシグネチャのみが残るようになります。
    • ランタイム (src/runtime/iface.c) の変更:
      • 以前は、int8, string, bool などの基本的な組み込み型や空のインターフェース (interface{}) の型シグネチャが src/runtime/iface.c にハードコードされていました。このコミットでは、これらのハードコードされた定義が削除されました。
      • これは、これらの型シグネチャがコンパイラによって動的に生成され、リンカによって適切に処理されるようになったため、ランタイムに静的に含める必要がなくなったことを意味します。これにより、ランタイムのコードベースが簡素化され、より汎用的なものになります。
      • sigi·intersigi·empty に置き換えられ、空のインターフェースのシグネチャがより明確に扱われるようになりました。

これらの変更により、Goのコンパイル・リンクシステムは、型シグネチャの管理においてより洗練されたアプローチを採用し、nil インターフェースのセマンティクスを改善しています。

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

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

  • src/cmd/6g/obj.c:

    • dumpsigt および dumpsigi 関数のシグネチャ変更と内部ロジックの修正。
    • AGLOBL 命令に DUPOK フラグを設定するロジックの追加。
    • dumpsignatures 関数が signatlist を走査し、siggen フラグに基づいて dumpsigi または dumpsigt を呼び出すように変更。
  • src/cmd/6l/l.h:

    • struct Symuchar dupok; フィールドを追加。
  • src/cmd/6l/obj.c:

    • AGLOBL 命令の処理で p->from.scale & DUPOK に基づいて s->dupok = 1; を設定するロジックを追加。
    • ADATA 命令の処理で p->from.sym->dupok が真の場合に重複する定義をスキップするロジックを追加。
  • src/cmd/gc/go.h:

    • struct Typeuchar siggen; フィールドを追加。
    • struct Symuchar siggen; フィールドを追加。
    • signame 関数のプロトタイプを Sym* signame(Type*); に変更(block 引数の削除)。
  • src/cmd/gc/subr.c:

    • globalsig 関数を削除。
    • signame 関数の実装を大幅に簡素化し、block 引数を削除。
    • signame 内で t->siggen = 1; を設定し、signatlist に型を追加するロジックを追加。
    • 空のインターフェース (interface { }) のシグネチャ名を sigi.empty として特別に扱うロジックを追加。
  • src/cmd/gc/walk.c:

    • ascompat 関数で isnilinter(t1) および isnilinter(t2) のチェックを追加。
    • isandss 関数で isnilinter のチェックを含む条件を修正。
    • ifaceop 関数で signame の呼び出しから 0 (block) 引数を削除。
  • src/runtime/iface.c:

    • ハードコードされていた多くの Sigi および Sigt 定義(例: sigt·int8, sigt·string など)を削除。
    • Sigi sigi·inter[2]Sigi sigi·empty[2] に変更し、hashmap の呼び出しもこれに合わせて修正。

コアとなるコードの解説

このコミットの核心は、Goの型システムとランタイムの連携を強化し、特にインターフェースの扱いを改善することにあります。

DUPOK フラグによる型シグネチャの重複排除: src/cmd/6g/obj.cdumpsigt および dumpsigi 関数は、Goの型情報をランタイムが利用できる形式(型シグネチャ)に変換し、オブジェクトファイルに出力する役割を担います。このコミットでは、これらの関数が生成する AGLOBL 命令に DUPOK フラグが設定されるようになりました。

// src/cmd/6g/obj.c (dumpsigt, dumpsigi 内)
// set DUPOK to allow other .6s to contain
// the same signature.  only one will be chosen.
p = pc;
gins(AGLOBL, N, N);
p->from = at;
p->from.scale = DUPOK; // ここで DUPOK フラグを設定
p->to = ac;
p->to.offset = ot;

この DUPOK フラグは、リンカ (6l) にとって重要な意味を持ちます。src/cmd/6l/obj.c では、リンカがオブジェクトファイル内の命令を処理する際に、このフラグを認識します。

// src/cmd/6l/obj.c (loop 内)
case AGLOBL:
    // ...
    if(p->from.scale & DUPOK) // DUPOK フラグがセットされているかチェック
        s->dupok = 1; // シンボルに dupok フラグをセット
    goto loop;

case ADATA:
    // Assume that AGLOBL comes after ADATA.
    // If we've seen an AGLOBL that said this sym was DUPOK,
    // ignore any more ADATA we see, which must be
    // redefinitions.
    if(p->from.sym != S && p->from.sym->dupok) // シンボルが dupok なら
        goto loop; // この ADATA 定義をスキップ
    // ...

このロジックにより、複数のオブジェクトファイルが同じ型シグネチャ(例えば sigt.string)を定義していても、リンカは DUPOK フラグがセットされたシンボルに対しては、最初に見つけた定義のみを採用し、後続の重複する定義を無視します。これにより、最終的な実行可能ファイルには各型シグネチャのコピーが1つだけ含まれることが保証され、バイナリサイズが削減されます。

nil インターフェースの変換ロジック: src/cmd/gc/walk.cascompat 関数は、型変換の互換性をチェックするGoコンパイラの重要な部分です。このコミットでは、nil インターフェースの変換に関するルールが緩和されました。

// src/cmd/gc/walk.c (ascompat 関数内)
if(isnilinter(t1)) // t1 が nil インターフェースなら
    return 1; // 互換性ありと見なす

// ...

if(isnilinter(t2)) // t2 が nil インターフェースなら
    return 1; // 互換性ありと見なす

この変更により、nil インターフェースは、それが保持する具体的な型が何であれ(実際には nil)、他のインターフェース型や具象型との間でより柔軟に変換できるようになりました。これは、Goのインターフェースが (type, value) のペアとして表現されることを考えると、nil インターフェースが (nil, nil) であるため、型情報が nil であっても互換性があると見なせるようになったことを意味します。これにより、Goプログラマは nil インターフェースをより自然に扱うことができるようになります。

これらの変更は、Goのコンパイラ、リンカ、ランタイムが密接に連携し、言語のセマンティクスと実装の詳細がどのように進化していくかを示す良い例です。

関連リンク

  • Go言語のインターフェースに関する公式ドキュメントやチュートリアル
  • Goコンパイラとリンカの内部構造に関する資料(Goのソースコードリポジトリ内の doc/ ディレクトリや、Goの設計に関するブログ記事など)
  • リンカにおける「弱いシンボル」や「重複シンボル解決」に関する一般的な情報

参考にした情報源リンク

  • Go言語のソースコード (特に src/cmd/6g, src/cmd/6l, src/cmd/gc, src/runtime ディレクトリ)
  • Goの初期の設計に関するメーリングリストの議論や設計ドキュメント(Goの公式リポジトリやGoのブログで公開されているもの)
  • リンカの動作に関する一般的なコンピュータサイエンスの資料