[インデックス 15524] ファイルの概要
このコミットは、Go言語のCgoツールチェーンにおけるオブジェクトファイル内のCgo関連セクションの構造と、Cgoディレクティブ(#pragma
)の命名規則を刷新するものです。具体的には、既存のdynimport
、dynexport
、dynlinker
といったCgo関連のセクションを単一のcgo
セクションに統合し、対応する#pragma
ディレクティブの名称をcgo_import_dynamic
、cgo_export
、cgo_dynamic_linker
、cgo_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/cgo
、cmd/ld
:新しいCgoオブジェクトファイルセクション
新しいプラグマ名に切り替えるが、古いものは当面の間利用可能にしておく。
.6
ファイル内の3つのCgo関連セクションを単一のcgo
セクションにマージする。
変更の背景
この変更の背景には、Go言語のCgo(C Foreign Function Interface)メカニズムの内部構造を合理化し、保守性と拡張性を向上させる目的があります。以前は、Cgoが生成するオブジェクトファイル(.6
ファイル)内に、動的インポート(dynimport
)、動的エクスポート(dynexport
)、動的リンカ(dynlinker
)に関する情報がそれぞれ独立したセクションとして格納されていました。
このような複数のセクションに分散した構造は、Cgoツールチェーン(特にコンパイラとリンカ)での処理を複雑にし、新しい機能の追加や既存機能の変更を困難にする可能性がありました。また、#pragma
ディレクティブの命名も、その機能がCgoに特化していることを明確に示すものではありませんでした。
このコミットは、これらの課題に対処するために、以下の目的で実施されました。
- セクションの統合による簡素化: 複数のCgo関連セクションを単一の
cgo
セクションにまとめることで、オブジェクトファイルの構造を簡素化し、リンカがCgo情報を処理する際のロジックを統一します。これにより、コードの重複が減り、保守が容易になります。 - 命名規則の明確化:
dynimport
のような汎用的な名称から、cgo_import_dynamic
のようにCgoの文脈を明確に示す名称に変更することで、ディレクティブの意図をより分かりやすくします。これは、将来的なCgo機能の追加や、Go言語の進化に伴う変更への対応を容易にします。 - 将来的な拡張性: 統一されたセクションと明確な命名規則は、Cgoの機能が今後拡張される際に、より柔軟な設計を可能にします。例えば、新しいCgoディレクティブや情報タイプが導入された場合でも、既存のフレームワークに容易に統合できるようになります。
この変更は、Go言語のCgo機能が成熟していく過程で、その基盤をより堅牢で効率的なものにするための重要なステップと言えます。
前提知識の解説
このコミットを理解するためには、以下の概念について基本的な知識が必要です。
-
Go言語のCgo:
- Cgoは、GoプログラムからC言語のコードを呼び出したり、C言語のコードからGoの関数を呼び出したりするためのGo言語の機能です。
- GoとCの間の相互運用を可能にし、既存のCライブラリを利用したり、パフォーマンスが重要な部分をCで記述したりする際に使用されます。
- Cgoは、Goのビルドプロセスの一部として動作し、Cgoディレクティブ(
#cgo
)を含むGoソースファイルをCコンパイラで処理可能なCソースファイルに変換し、その後、GoリンカがCのオブジェクトファイルとGoのオブジェクトファイルを結合します。
-
#pragma
ディレクティブ:- C言語のプリプロセッサディレクティブの一つで、コンパイラに対して特定の指示を与えるために使用されます。
- Cgoの文脈では、Goのコンパイラやリンカに対して、Cgo関連の特別な情報(例:動的リンクするシンボル、エクスポートするGo関数など)を伝えるために利用されます。
- このコミットでは、Cgoが生成するCソースファイルに埋め込まれる
#pragma
ディレクティブの命名規則が変更されています。
-
動的リンクと静的リンク:
- 静的リンク: プログラムのビルド時に、必要なライブラリのコードが実行可能ファイルに直接組み込まれる方式です。実行可能ファイルは自己完結型となり、ライブラリがシステムに存在しなくても実行できますが、ファイルサイズは大きくなります。
- 動的リンク: プログラムの実行時に、必要なライブラリがメモリにロードされ、プログラムとリンクされる方式です。実行可能ファイルのサイズは小さくなり、複数のプログラムで同じライブラリを共有できますが、実行時にライブラリがシステムに存在する必要があります。
- Cgoでは、Cライブラリへのリンクに動的リンクと静的リンクの両方を使用する場合があります。
-
オブジェクトファイルとリンカ:
- オブジェクトファイル: ソースコードがコンパイラによって機械語に変換された中間ファイルです。通常、
.o
(C/C++)やGoの場合は.6
(amd64の場合)のような拡張子を持ちます。これには、コンパイルされたコード、データ、およびシンボル情報(関数名、変数名など)が含まれます。 - リンカ(
ld
): 複数のオブジェクトファイルやライブラリを結合して、最終的な実行可能ファイルや共有ライブラリを生成するツールです。リンカは、オブジェクトファイル内の未解決のシンボル参照を解決し、各セクションを適切に配置します。 - Goのツールチェーンでは、
cmd/ld
がGoのリンカを担当します。
- オブジェクトファイル: ソースコードがコンパイラによって機械語に変換された中間ファイルです。通常、
-
ELF (Executable and Linkable Format):
- Unix系システムで広く使われている実行可能ファイル、オブジェクトファイル、共有ライブラリの標準フォーマットです。
- ELFファイルは、ヘッダ、プログラムヘッダテーブル、セクションヘッダテーブル、および様々なセクション(
.text
、.data
、.bss
など)で構成されます。 - Cgo関連の情報も、Goのオブジェクトファイル内で特定のセクションに格納されます。
-
dynimport
、dynexport
、dynlinker
(旧Cgoプラグマ):#pragma dynimport
: 動的にインポートされるシンボル(外部ライブラリからGoプログラムが利用する関数など)をリンカに伝えるために使用されていました。#pragma dynexport
: Goの関数をCコードから呼び出せるようにエクスポートするために使用されていました。#pragma dynlinker
: 動的リンカ(例:/lib64/ld-linux-x86-64.so.2
)のパスを指定するために使用されていました。
これらの前提知識を理解することで、コミットがGoのビルドシステムとCgoの内部動作にどのように影響を与えるかを深く把握できます。
技術的詳細
このコミットの技術的詳細は、主にGoのCgoツールチェーンにおけるオブジェクトファイル(.6
ファイル)の構造と、Cgoが生成する#pragma
ディレクティブの処理方法の変更に集約されます。
-
Cgo関連セクションの統合:
- 以前のGoオブジェクトファイルでは、Cgoに関連する情報が
dynimport
、dynexport
、dynlinker
という3つの異なるセクションに分散して格納されていました。 - このコミットにより、これらの情報がすべて単一の
cgo
セクションに統合されます。これは、src/cmd/5c/swt.c
、src/cmd/6c/swt.c
、src/cmd/8c/swt.c
(各アーキテクチャのGoコンパイラ)のoutcode
関数におけるオブジェクトファイルへの出力ロジックの変更によって実現されています。具体的には、ndynimp > 0 || ndynexp > 0
という条件からpragcgobuf.to > pragcgobuf.start
という条件に変わり、dynimport
、dynexport
、dynlinker
の個別の出力ループが削除され、pragcgobuf
というバッファに蓄積されたCgo情報がまとめて$$ // cgo
セクションとして出力されるようになります。 - リンカ側(
src/cmd/ld/go.c
)では、以前は$$ // dynimport
、$$ // dynexport
、$$ // dynlinker
という文字列を検索して各セクションを読み込んでいましたが、この変更により$$ // cgo
という単一のセクションを検索し、その内容をloadcgo
関数で一括して処理するようになります。これにより、リンカのコードも簡素化されます。
- 以前のGoオブジェクトファイルでは、Cgoに関連する情報が
-
#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.c
のpragcgo
関数が、これらの新しい#pragma
ディレクティブを解析する中心的なロジックとなります。この関数は、引数として渡されたプラグマ名(verb
)に基づいて、cgo_dynamic_linker
、cgo_export
、cgo_import_dynamic
、cgo_import_static
のいずれかを識別し、それぞれの引数を解析してpragcgobuf
にフォーマットされた文字列として書き込みます。src/cmd/cc/cc.h
からは、Dynimp
、Dynexp
構造体およびdynimp
、ndynimp
、dynexp
、ndynexp
、dynlinker
といったグローバル変数が削除され、Cgo関連の情報を一元的に管理するpragcgobuf
(Fmt
型)が導入されました。これにより、Cgo情報の管理がより効率的になります。src/cmd/cc/lex.c
では、main
関数内でfmtstrinit(&pragcgobuf)
とquotefmtinstall()
が呼び出され、pragcgobuf
の初期化と引用符付き文字列のフォーマット機能が設定されます。src/cmd/cc/macbody
では、macprag
関数がs->name
がcgo_
またはdyn
で始まる場合にpragcgo
関数を呼び出すように変更され、新しいプラグマ名と古いプラグマ名の両方に対応しています(ただし、古いプラグマは最終的に削除される可能性があります)。
- 旧来の
-
SHOSTOBJ
シンボルタイプの導入:src/cmd/ld/lib.h
にSHOSTOBJ
という新しいシンボルタイプが追加されました。これは、ホストリンカによって解決されるべきシンボル(Goのリンカが直接解決しないシンボル)をマークするために使用されます。src/cmd/ld/symtab.c
のasmelfsym
関数では、SHOSTOBJ
タイプのシンボルがELFシンボルテーブルに追加されるようになりました。これにより、Goのリンカはこれらのシンボルを適切に扱い、最終的な実行可能ファイルの生成時にホストリンカにその解決を委ねることができます。
-
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
に関する説明が追加されました。
- Cgoのドキュメントが更新され、
-
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
の出力ロジックが追加されました。
- Cgoツールが生成する
-
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
という文字列を個別に検索し、それぞれのセクションの内容をloaddynimport
、loaddynexport
、loaddynlinker
という関数で処理していました。
変更後:
// 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_dynamic
、cgo_import_static
、cgo_export
、cgo_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言語のビルドプロセスに関する一般的な情報