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

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

このコミットは、Go言語のCgoツールチェーンにおけるオブジェクトファイル内のCgo関連セクションの構造と、Cgoディレクティブ(#pragma)の命名規則を刷新するものです。具体的には、既存のdynimportdynexportdynlinkerといったCgo関連のセクションを単一のcgoセクションに統合し、対応する#pragmaディレクティブの名称をcgo_import_dynamiccgo_exportcgo_dynamic_linkercgo_import_staticといった新しい形式に変更しています。これにより、Cgoの内部処理が簡素化され、将来的な拡張性が向上しています。

コミット

commit 7556ccc7b1763d94b64b04fd7d1ada368397e647
Author: Russ Cox <rsc@golang.org>
Date:   Fri Mar 1 00:27:57 2013 -0500

    cmd/cgo, cmd/ld: new cgo object file section

    Switch to new pragma names, but leave old ones available for now.
    Merge the three cgo-related sections in the .6 files into a single
    cgo section.

    R=golang-dev, iant, ality
    CC=golang-dev
    https://golang.org/cl/7424048

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

https://github.com/golang/go/commit/7556ccc7b1763d94b64b04fd7d1ada368397e647

元コミット内容

cmd/cgocmd/ld:新しいCgoオブジェクトファイルセクション

新しいプラグマ名に切り替えるが、古いものは当面の間利用可能にしておく。 .6ファイル内の3つのCgo関連セクションを単一のcgoセクションにマージする。

変更の背景

この変更の背景には、Go言語のCgo(C Foreign Function Interface)メカニズムの内部構造を合理化し、保守性と拡張性を向上させる目的があります。以前は、Cgoが生成するオブジェクトファイル(.6ファイル)内に、動的インポート(dynimport)、動的エクスポート(dynexport)、動的リンカ(dynlinker)に関する情報がそれぞれ独立したセクションとして格納されていました。

このような複数のセクションに分散した構造は、Cgoツールチェーン(特にコンパイラとリンカ)での処理を複雑にし、新しい機能の追加や既存機能の変更を困難にする可能性がありました。また、#pragmaディレクティブの命名も、その機能がCgoに特化していることを明確に示すものではありませんでした。

このコミットは、これらの課題に対処するために、以下の目的で実施されました。

  1. セクションの統合による簡素化: 複数のCgo関連セクションを単一のcgoセクションにまとめることで、オブジェクトファイルの構造を簡素化し、リンカがCgo情報を処理する際のロジックを統一します。これにより、コードの重複が減り、保守が容易になります。
  2. 命名規則の明確化: dynimportのような汎用的な名称から、cgo_import_dynamicのようにCgoの文脈を明確に示す名称に変更することで、ディレクティブの意図をより分かりやすくします。これは、将来的なCgo機能の追加や、Go言語の進化に伴う変更への対応を容易にします。
  3. 将来的な拡張性: 統一されたセクションと明確な命名規則は、Cgoの機能が今後拡張される際に、より柔軟な設計を可能にします。例えば、新しいCgoディレクティブや情報タイプが導入された場合でも、既存のフレームワークに容易に統合できるようになります。

この変更は、Go言語のCgo機能が成熟していく過程で、その基盤をより堅牢で効率的なものにするための重要なステップと言えます。

前提知識の解説

このコミットを理解するためには、以下の概念について基本的な知識が必要です。

  1. Go言語のCgo:

    • Cgoは、GoプログラムからC言語のコードを呼び出したり、C言語のコードからGoの関数を呼び出したりするためのGo言語の機能です。
    • GoとCの間の相互運用を可能にし、既存のCライブラリを利用したり、パフォーマンスが重要な部分をCで記述したりする際に使用されます。
    • Cgoは、Goのビルドプロセスの一部として動作し、Cgoディレクティブ(#cgo)を含むGoソースファイルをCコンパイラで処理可能なCソースファイルに変換し、その後、GoリンカがCのオブジェクトファイルとGoのオブジェクトファイルを結合します。
  2. #pragma ディレクティブ:

    • C言語のプリプロセッサディレクティブの一つで、コンパイラに対して特定の指示を与えるために使用されます。
    • Cgoの文脈では、Goのコンパイラやリンカに対して、Cgo関連の特別な情報(例:動的リンクするシンボル、エクスポートするGo関数など)を伝えるために利用されます。
    • このコミットでは、Cgoが生成するCソースファイルに埋め込まれる#pragmaディレクティブの命名規則が変更されています。
  3. 動的リンクと静的リンク:

    • 静的リンク: プログラムのビルド時に、必要なライブラリのコードが実行可能ファイルに直接組み込まれる方式です。実行可能ファイルは自己完結型となり、ライブラリがシステムに存在しなくても実行できますが、ファイルサイズは大きくなります。
    • 動的リンク: プログラムの実行時に、必要なライブラリがメモリにロードされ、プログラムとリンクされる方式です。実行可能ファイルのサイズは小さくなり、複数のプログラムで同じライブラリを共有できますが、実行時にライブラリがシステムに存在する必要があります。
    • Cgoでは、Cライブラリへのリンクに動的リンクと静的リンクの両方を使用する場合があります。
  4. オブジェクトファイルとリンカ:

    • オブジェクトファイル: ソースコードがコンパイラによって機械語に変換された中間ファイルです。通常、.o(C/C++)やGoの場合は.6(amd64の場合)のような拡張子を持ちます。これには、コンパイルされたコード、データ、およびシンボル情報(関数名、変数名など)が含まれます。
    • リンカ(ld: 複数のオブジェクトファイルやライブラリを結合して、最終的な実行可能ファイルや共有ライブラリを生成するツールです。リンカは、オブジェクトファイル内の未解決のシンボル参照を解決し、各セクションを適切に配置します。
    • Goのツールチェーンでは、cmd/ldがGoのリンカを担当します。
  5. ELF (Executable and Linkable Format):

    • Unix系システムで広く使われている実行可能ファイル、オブジェクトファイル、共有ライブラリの標準フォーマットです。
    • ELFファイルは、ヘッダ、プログラムヘッダテーブル、セクションヘッダテーブル、および様々なセクション(.text.data.bssなど)で構成されます。
    • Cgo関連の情報も、Goのオブジェクトファイル内で特定のセクションに格納されます。
  6. dynimportdynexportdynlinker (旧Cgoプラグマ):

    • #pragma dynimport: 動的にインポートされるシンボル(外部ライブラリからGoプログラムが利用する関数など)をリンカに伝えるために使用されていました。
    • #pragma dynexport: Goの関数をCコードから呼び出せるようにエクスポートするために使用されていました。
    • #pragma dynlinker: 動的リンカ(例: /lib64/ld-linux-x86-64.so.2)のパスを指定するために使用されていました。

これらの前提知識を理解することで、コミットがGoのビルドシステムとCgoの内部動作にどのように影響を与えるかを深く把握できます。

技術的詳細

このコミットの技術的詳細は、主にGoのCgoツールチェーンにおけるオブジェクトファイル(.6ファイル)の構造と、Cgoが生成する#pragmaディレクティブの処理方法の変更に集約されます。

  1. Cgo関連セクションの統合:

    • 以前のGoオブジェクトファイルでは、Cgoに関連する情報がdynimportdynexportdynlinkerという3つの異なるセクションに分散して格納されていました。
    • このコミットにより、これらの情報がすべて単一のcgoセクションに統合されます。これは、src/cmd/5c/swt.csrc/cmd/6c/swt.csrc/cmd/8c/swt.c(各アーキテクチャのGoコンパイラ)のoutcode関数におけるオブジェクトファイルへの出力ロジックの変更によって実現されています。具体的には、ndynimp > 0 || ndynexp > 0という条件からpragcgobuf.to > pragcgobuf.startという条件に変わり、dynimportdynexportdynlinkerの個別の出力ループが削除され、pragcgobufというバッファに蓄積されたCgo情報がまとめて$$ // cgoセクションとして出力されるようになります。
    • リンカ側(src/cmd/ld/go.c)では、以前は$$ // dynimport$$ // dynexport$$ // dynlinkerという文字列を検索して各セクションを読み込んでいましたが、この変更により$$ // cgoという単一のセクションを検索し、その内容をloadcgo関数で一括して処理するようになります。これにより、リンカのコードも簡素化されます。
  2. #pragmaディレクティブの命名変更と処理の統一:

    • 旧来の#pragma dynimport#pragma dynexport#pragma dynlinkerは、それぞれ#pragma cgo_import_dynamic#pragma cgo_export#pragma cgo_dynamic_linkerに名称が変更されました。
    • さらに、#pragma cgo_import_staticという新しいディレクティブが導入されました。これは、ホストリンカに提供されるgo.oオブジェクトファイル内の未解決参照を許可し、他のオブジェクトファイルによって提供されることを期待するシンボルをマークするために使用されます。
    • src/cmd/cc/dpchk.cpragcgo関数が、これらの新しい#pragmaディレクティブを解析する中心的なロジックとなります。この関数は、引数として渡されたプラグマ名(verb)に基づいて、cgo_dynamic_linkercgo_exportcgo_import_dynamiccgo_import_staticのいずれかを識別し、それぞれの引数を解析してpragcgobufにフォーマットされた文字列として書き込みます。
    • src/cmd/cc/cc.hからは、DynimpDynexp構造体およびdynimpndynimpdynexpndynexpdynlinkerといったグローバル変数が削除され、Cgo関連の情報を一元的に管理するpragcgobufFmt型)が導入されました。これにより、Cgo情報の管理がより効率的になります。
    • src/cmd/cc/lex.cでは、main関数内でfmtstrinit(&pragcgobuf)quotefmtinstall()が呼び出され、pragcgobufの初期化と引用符付き文字列のフォーマット機能が設定されます。
    • src/cmd/cc/macbodyでは、macprag関数がs->namecgo_またはdynで始まる場合にpragcgo関数を呼び出すように変更され、新しいプラグマ名と古いプラグマ名の両方に対応しています(ただし、古いプラグマは最終的に削除される可能性があります)。
  3. SHOSTOBJシンボルタイプの導入:

    • src/cmd/ld/lib.hSHOSTOBJという新しいシンボルタイプが追加されました。これは、ホストリンカによって解決されるべきシンボル(Goのリンカが直接解決しないシンボル)をマークするために使用されます。
    • src/cmd/ld/symtab.casmelfsym関数では、SHOSTOBJタイプのシンボルがELFシンボルテーブルに追加されるようになりました。これにより、Goのリンカはこれらのシンボルを適切に扱い、最終的な実行可能ファイルの生成時にホストリンカにその解決を委ねることができます。
  4. Cgoツールの出力変更:

    • src/cmd/cgo/out.goでは、Cgoツールが生成する#pragmaディレクティブの出力が、新しい命名規則に従うように変更されました。例えば、#pragma dynlinker#pragma cgo_dynamic_linkerに、#pragma dynimport#pragma cgo_import_dynamicに、#pragma dynexport#pragma cgo_exportにそれぞれ変更されています。
    • src/cmd/cgo/doc.goのCgoドキュメントも、新しい#pragmaディレクティブの名称と使用法を反映するように更新されています。

これらの変更は、Cgoの内部実装をよりクリーンで、理解しやすく、そして将来の機能拡張に対応しやすいものにすることを目的としています。特に、Cgo関連情報のセクション統合と、#pragma処理の一元化は、Goのビルドシステム全体の効率と堅牢性を向上させる上で重要な役割を果たします。

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

このコミットにおけるコアとなるコードの変更箇所は以下の通りです。

  • src/cmd/5c/swt.c, src/cmd/6c/swt.c, src/cmd/8c/swt.c:

    • 各アーキテクチャのGoコンパイラにおけるオブジェクトファイル出力ロジックが変更されています。
    • outcode関数内で、dynimport, dynexport, dynlinkerの個別のセクション出力が削除され、pragcgobufに蓄積されたCgo情報が単一の$$ // cgoセクションとして出力されるようになりました。
  • src/cmd/cc/cc.h:

    • 旧来のDynimp, Dynexp構造体および関連するグローバル変数(dynimp, ndynimp, dynexp, ndynexp, dynlinker)が削除されました。
    • Cgo関連の情報を一元的に管理するためのEXTERN Fmt pragcgobuf;が追加されました。
    • 旧来のpragdynimport, pragdynexport, pragdynlinker関数の宣言が削除され、新しいvoid pragcgo(char*);が追加されました。
  • src/cmd/cc/dpchk.c:

    • 旧来のpragdynimport, pragdynexport, pragdynlinker関数が削除されました。
    • 新しいpragcgo(char *verb)関数が追加され、cgo_dynamic_linker, cgo_export, cgo_import_dynamic, cgo_import_staticといった新しい#pragmaディレクティブの解析とpragcgobufへの書き込みを一元的に行います。
  • src/cmd/cc/lex.c:

    • main関数内でfmtstrinit(&pragcgobuf);quotefmtinstall();が呼び出され、pragcgobufの初期化と引用符付き文字列のフォーマット機能が設定されます。
  • src/cmd/cc/lexbody:

    • pragdynimport, pragdynexport, pragdynlinkerのダミー関数が削除され、pragcgo(char *name)のダミー関数が追加されました。
  • src/cmd/cc/macbody:

    • macprag関数内で、s->name"dynimport", "dynexport", "dynlinker"である場合の処理が削除され、s->name"cgo_"または"dyn"で始まる場合にpragcgo(s->name)を呼び出すように変更されました。これにより、新しいプラグマ名と旧来のプラグマ名の両方に対応します。
  • src/cmd/cgo/doc.go:

    • Cgoのドキュメントが更新され、#pragma dynlinker, #pragma dynimportなどの旧来のプラグマ名が、#pragma cgo_dynamic_linker, #pragma cgo_import_dynamicなどの新しいプラグマ名に置き換えられました。
    • #pragma cgo_import_staticに関する説明が追加されました。
  • src/cmd/cgo/out.go:

    • Cgoツールが生成する#pragmaディレクティブの出力が、新しい命名規則に従うように変更されました。例えば、fmt.Fprintf(stdout, "#pragma dynlinker ...")fmt.Fprintf(stdout, "#pragma cgo_dynamic_linker ...")に、fmt.Fprintf(stdout, "#pragma dynimport ...")fmt.Fprintf(stdout, "#pragma cgo_import_dynamic ...")に、fmt.Fprintf(stdout, "#pragma dynexport ...")fmt.Fprintf(stdout, "#pragma cgo_export ...")に変更されています。
    • #pragma cgo_import_staticの出力ロジックが追加されました。
  • src/cmd/ld/go.c:

    • リンカがオブジェクトファイルを読み込むldpkg関数内で、$$ // dynimport, $$ // dynexport, $$ // dynlinkerセクションの検索と読み込みロジックが削除され、単一の$$ // cgoセクションを検索し、loadcgo関数で処理するように変更されました。
    • loaddynimport, loaddynexport, loaddynlinker関数が削除され、新しいloadcgo関数が追加されました。このloadcgo関数は、cgo_import_dynamic, cgo_import_static, cgo_export, cgo_dynamic_linkerといった新しいCgoディレクティブを解析し、リンカの内部データ構造を更新します。
  • src/cmd/ld/lib.h:

    • 新しいシンボルタイプSHOSTOBJが追加されました。
  • src/cmd/ld/symtab.c:

    • asmelfsym関数内で、SHOSTOBJタイプのシンボルがELFシンボルテーブルに追加されるロジックが追加されました。

これらの変更は、Cgoの内部処理の大部分に影響を与え、その構造をより統一的で効率的なものに再構築しています。

コアとなるコードの解説

このコミットのコアとなる変更は、Cgo関連情報のオブジェクトファイルへの書き込み、リンカによる読み込み、そして#pragmaディレクティブの解析方法の根本的な変更にあります。

src/cmd/5c/swt.c, src/cmd/6c/swt.c, src/cmd/8c/swt.c (Goコンパイラ)

これらのファイルは、Goの各アーキテクチャ(5c: ARM, 6c: amd64, 8c: 386)に対応するコンパイラのバックエンドの一部です。変更の核心はoutcode関数にあります。

変更前:

    if(ndynimp > 0 || ndynexp > 0) {
        // ...
        Bprint(&outbuf, "$$  // dynimport\\n");
        for(i=0; i<ndynimp; i++)
            Bprint(&outbuf, "dynimport %s %s %s\\n", dynimp[i].local, dynimp[i].remote, dynimp[i].path);
        // ...
        Bprint(&outbuf, "\\n$$  // dynexport\\n");
        for(i=0; i<ndynexp; i++)
            Bprint(&outbuf, "dynexport %s %s\\n", dynexp[i].local, dynexp[i].remote);
        // ...
        Bprint(&outbuf, "\\n$$  // dynlinker\\n");
        if(dynlinker != nil) {
            Bprint(&outbuf, "dynlinker %s\\n", dynlinker);
        }
        // ...
    }

以前は、ndynimp(動的インポートシンボル数)やndynexp(動的エクスポートシンボル数)が存在する場合に、$$ // dynimport$$ // dynexport$$ // dynlinkerというコメントで区切られた個別のセクションとして、それぞれのCgo情報がオブジェクトファイルに書き出されていました。

変更後:

    if(pragcgobuf.to > pragcgobuf.start) {
        // ...
        Bprint(&outbuf, "$$  // cgo\\n");
        Bprint(&outbuf, "%s", fmtstrflush(&pragcgobuf));
        // ...
    }

変更後は、pragcgobufというバッファにCgo関連のすべての情報が蓄積され、そのバッファにデータが存在する場合(pragcgobuf.to > pragcgobuf.start)、単一の$$ // cgoセクションとしてまとめて書き出されます。これにより、オブジェクトファイルのCgo関連情報の構造が簡素化されました。

src/cmd/cc/dpchk.c (Cgoプラグマの解析)

このファイルは、Cgoの#pragmaディレクティブを解析する主要なロジックを含んでいます。

変更前: pragdynimport, pragdynexport, pragdynlinkerという個別の関数が存在し、それぞれが特定の#pragmaディレクティブ(例: #pragma dynimport local remote "path")を解析していました。

変更後:

void
pragcgo(char *verb)
{
    Sym *local, *remote;
    char *p;

    if(strcmp(verb, "cgo_dynamic_linker") == 0 || strcmp(verb, "dynlinker") == 0) {
        // ... parse cgo_dynamic_linker ...
        fmtprint(&pragcgobuf, "cgo_dynamic_linker %q\\n", p);
        goto out;
    }

    if(strcmp(verb, "cgo_export") == 0 || strcmp(verb, "dynexport") == 0) {
        // ... parse cgo_export ...
        fmtprint(&pragcgobuf, "cgo_export %q\\n", local->name); // or with remote
        goto out;
    }

    if(strcmp(verb, "cgo_import_dynamic") == 0 || strcmp(verb, "dynimport") == 0) {
        // ... parse cgo_import_dynamic ...
        fmtprint(&pragcgobuf, "cgo_import_dynamic %q %q %q\\n", local->name, remote->name, p);
        goto out;
    }

    if(strcmp(verb, "cgo_import_static") == 0) {
        // ... parse cgo_import_static ...
        fmtprint(&pragcgobuf, "cgo_import_static %q\\n", local->name);
        goto out;
    }
out:
    while(getnsc() != '\\n')
        ;
}

pragcgoという単一の関数が導入され、verb引数(プラグマ名)に基づいて、どのCgoディレクティブが指定されたかを判断します。そして、それぞれのディレクティブに応じた引数を解析し、その情報をpragcgobufにフォーマットして書き込みます。これにより、Cgoプラグマの解析ロジックが一元化され、新しいプラグマの追加が容易になりました。また、古いプラグマ名(dynlinker, dynexport, dynimport)も当面の間サポートされています。

src/cmd/ld/go.c (Goリンカ)

このファイルは、Goのリンカがオブジェクトファイルを読み込み、Cgo関連の情報を処理する部分です。

変更前:

    // look for dynimport section
    p0 = strstr(p1, "\\n$$  // dynimport");
    if(p0 != nil) {
        // ... loaddynimport(filename, pkg, p0 + 1, p1 - (p0+1));
    }

    // look for dynexp section
    p0 = strstr(p1, "\\n$$  // dynexport");
    if(p0 != nil) {
        // ... loaddynexport(filename, pkg, p0 + 1, p1 - (p0+1));
    }

    p0 = strstr(p1, "\\n$$  // dynlinker");
    if(p0 != nil) {
        // ... loaddynlinker(filename, pkg, p0 + 1, p1 - (p0+1));
    }

リンカは、オブジェクトファイル内で$$ // dynimport$$ // dynexport$$ // dynlinkerという文字列を個別に検索し、それぞれのセクションの内容をloaddynimportloaddynexportloaddynlinkerという関数で処理していました。

変更後:

    // look for cgo section
    p0 = strstr(p1, "\\n$$  // cgo");
    if(p0 != nil) {
        // ...
        loadcgo(filename, pkg, p0 + 1, p1 - (p0+1));
    }

リンカは、単一の$$ // cgoセクションを検索し、その内容全体を新しいloadcgo関数に渡して処理します。

static void
loadcgo(char *file, char *pkg, char *p, int n)
{
    // ...
    if(strcmp(f[0], "cgo_import_dynamic") == 0) {
        // ... process cgo_import_dynamic ...
    } else if(strcmp(f[0], "cgo_import_static") == 0) {
        // ... process cgo_import_static ...
    } else if(strcmp(f[0], "cgo_export") == 0) {
        // ... process cgo_export ...
    } else if(strcmp(f[0], "cgo_dynamic_linker") == 0) {
        // ... process cgo_dynamic_linker ...
    }
    // ...
}

loadcgo関数は、$$ // cgoセクション内の各行をトークン化し、最初のトークン(ディレクティブ名)に基づいて、cgo_import_dynamiccgo_import_staticcgo_exportcgo_dynamic_linkerのいずれであるかを判断します。そして、それぞれのディレクティブに応じた処理(シンボル情報の更新、動的ライブラリの追加など)を行います。これにより、リンカのCgo情報処理も一元化され、より柔軟になりました。

これらの変更は、GoのCgoツールチェーンの内部アーキテクチャを大幅に改善し、将来の機能拡張のための強固な基盤を築いています。

関連リンク

  • Go言語のCgoに関する公式ドキュメント: https://pkg.go.dev/cmd/cgo
  • Go言語のリンカに関する情報(Goのソースコード内): src/cmd/ld/ ディレクトリ

参考にした情報源リンク

  • Go言語のソースコード (特に src/cmd/cgo, src/cmd/cc, src/cmd/ld ディレクトリ)
  • ELF (Executable and Linkable Format) の仕様に関する一般的な情報
  • 動的リンクと静的リンクに関する一般的な情報
  • Go言語のビルドプロセスに関する一般的な情報