[インデックス 10799] ファイルの概要
このコミットは、Go言語のcgo
ツールにgccgo
コンパイラ向けの基本的なサポートを追加するものです。具体的には、cgo
が生成するCコードおよびGoコードがgccgo
のABI(Application Binary Interface)と互換性を持つように調整されます。これにより、gccgo
を使用してコンパイルされたGoプログラムがC言語のライブラリと連携できるようになります。
コミット
commit 076ebed0d87e1c9678ece352986d8b1ad877b440
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date: Wed Dec 14 15:40:35 2011 -0800
cgo: add basic gccgo support.
R=rsc, iant
CC=golang-dev, remy
https://golang.org/cl/5485070
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/076ebed0d87e1c9678ece352986d8b1ad877b440
元コミット内容
cgo: add basic gccgo support.
R=rsc, iant
CC=golang-dev, remy
https://golang.org/cl/5485070
変更の背景
Go言語には、公式のコンパイラであるgc
(Go Compiler)の他に、GCCのフロントエンドとして実装されたgccgo
という代替コンパイラが存在します。cgo
はGoプログラムからC言語の関数を呼び出したり、C言語のコードからGoの関数を呼び出したりするためのツールであり、GoとCの間の相互運用性を提供します。
しかし、gc
とgccgo
はそれぞれ異なるABI(Application Binary Interface)やランタイムの特性を持つことがあります。特に、Goの文字列(string
)やスライス([]byte
など)といった複合型がC言語側でどのように表現されるか、またGoランタイムとの連携方法が異なります。
このコミットが行われた2011年当時、cgo
は主にgc
コンパイラ向けに設計されていました。gccgo
でcgo
を利用するためには、cgo
が生成するCコードやGoコードがgccgo
の期待するABIやランタイムの挙動に適合するように調整する必要がありました。このコミットは、そのための基本的なサポートをcgo
に追加することを目的としています。これにより、gccgo
ユーザーもcgo
を介してCライブラリを利用できるようになる道が開かれました。
前提知識の解説
Go言語
Googleによって開発されたオープンソースのプログラミング言語です。シンプルさ、効率性、並行処理のサポートを重視しており、システムプログラミングからWebアプリケーション開発まで幅広く利用されています。
cgo
Go言語に標準で付属するツールの一つで、GoプログラムとC/C++コードを連携させるためのものです。cgo
は、Goのソースコード内にCのコードを直接記述できるようにし、GoとCの間の関数呼び出しやデータ変換のための「接着剤」となるコード(GoとCの両方のソースファイル)を自動生成します。これにより、既存のCライブラリをGoから利用したり、パフォーマンスが重要な部分をCで記述したりすることが可能になります。
gccgo
Go言語の代替コンパイラの一つです。GNU Compiler Collection (GCC) のフロントエンドとして実装されており、GoのソースコードをGCCの強力な最適化バックエンドでコンパイルします。標準のGoコンパイラであるgc
とは独立して開発されており、生成されるバイナリの特性や、Goランタイムとの連携方法、特にC言語とのABI(Application Binary Interface)がgc
とは異なる場合があります。
ABI (Application Binary Interface)
アプリケーションバイナリインターフェースは、コンパイルされたプログラム(バイナリ)が、オペレーティングシステムや他のライブラリとどのようにやり取りするかを定義する一連の規約です。これには、関数呼び出しの際に引数がどのようにレジスタやスタックに配置されるか、戻り値がどのように返されるか、データ構造がメモリ上でどのように配置されるかなどが含まれます。異なるコンパイラ(例: gc
とgccgo
)が同じソースコードをコンパイルしても、ABIが異なると互換性のないバイナリが生成される可能性があります。cgo
がGoとCの間の橋渡しをするためには、両者のABIを正しく理解し、それに合わせてコードを生成する必要があります。
Prolog (CGO context)
cgo
がGoとCの間の相互運用コードを生成する際、生成されるCファイルには、Goの型とCの型を変換するためのヘルパー関数や、Goランタイムとの連携に必要な初期化コードなどが含まれます。これらの共通して必要となるCコードのブロックを「Prolog」と呼びます。このPrologは、cgo
が生成するCソースファイルの冒頭に挿入されます。
runtime/cgo
標準のGoコンパイラ(gc
)でcgo
を使用する際に、GoランタイムとCコードの間の連携を担うGoの標準ライブラリパッケージです。CGO呼び出しの際のスタック切り替え、Goルーチンのブロック、CからのGoコールバックの処理など、低レベルなランタイム連携機能を提供します。gccgo
は独自のランタイム実装を持つため、このパッケージの利用方法がgc
とは異なるか、あるいは全く不要となる場合があります。
技術的詳細
このコミットの主要な目的は、cgo
がgccgo
コンパイラと連携できるようにすることです。そのために、以下の技術的な変更が導入されています。
-
gccgo
フラグの導入:cgo
コマンドラインツールに-gccgo
という新しいフラグが追加されました。このフラグが指定されると、cgo
はgccgo
に特化したコード生成モードで動作します。 -
runtime/cgo
インポートの条件化: 標準のGoコンパイラ(gc
)では、cgo
が生成するGoファイルは通常、import _ "runtime/cgo"
という行を含みます。これは、runtime/cgo
パッケージがGoとCの間の低レベルな連携(例: スケジューラの管理、スタックの切り替え)を処理するために必要だからです。 しかし、gccgo
はGoランタイムの異なる実装を持つため、runtime/cgo
のインポートが不要であるか、あるいは異なる方法で連携を処理する可能性があります。このコミットでは、-gccgo
フラグが指定された場合、このruntime/cgo
のインポート行が生成されないように変更されました。これは、gccgo
が独自のCGOランタイムメカニズムを持っていることを示唆しています。 -
cPrologGccgo
の導入と条件付きProlog生成:cgo
は、GoとCの間のデータ変換(特に文字列やスライス)を容易にするために、生成されるCファイルにいくつかのヘルパー関数(例:CString
,GoString
,GoBytes
)を含むPrologコードを挿入します。 このコミットでは、cProlog
という既存のProlog定数に加えて、gccgo
専用の新しいProlog定数cPrologGccgo
が導入されました。cPrologGccgo
は、gccgo
のABIに合わせたGoの文字列(__go_string
)やスライス(__go_open_array
)のC言語での構造体定義を含み、これらの型をCのポインタや配列に変換するためのCString
,GoString
,GoStringN
,GoBytes
といったヘルパー関数のgccgo
互換実装を提供します。cgo
は、-gccgo
フラグの有無に応じて、適切なProlog(cProlog
またはcPrologGccgo
)を生成されるCファイルに挿入するようになりました。 -
__asm__
ディレクティブの追加:cgo
がGoの関数をCから呼び出せるようにするためのラッパー関数を生成する際、gccgo
モードでは、生成されるGoコードに__asm__("symbol_name")
というディレクティブが追加されるようになりました。これは、Goの関数シンボルをC側から参照できるように、gccgo
が期待する特定のリンケージ名(アセンブリ名)を明示的に指定するためのものです。これにより、gccgo
コンパイラがGoの関数を正しくリンクできるようになります。 -
ラッパー関数生成の最適化: 標準の
cgo
では、GoからC関数を呼び出す際に、エラーハンドリングやGoランタイムとの連携のために、Go側でC関数を呼び出すためのラッパー関数が生成されます。 このコミットでは、gccgo
モードの場合、C関数がエラーを返さない(n.AddError
がfalse
)限り、Go側のラッパー関数を生成しないように変更されました。これは、gccgo
がGoとCの間の関数呼び出しにおいて、gc
よりも直接的なメカニズムを提供しており、不要なラッパーを省略することでオーバーヘッドを削減できる可能性を示唆しています。
これらの変更により、cgo
はgccgo
がGoの型をCでどのように表現し、C関数をどのように呼び出すかというABIの差異に対応できるようになり、gccgo
環境下でのCGOの利用が可能になりました。
コアとなるコードの変更箇所
このコミットでは、主に以下の2つのファイルが変更されています。
-
src/cmd/cgo/main.go
:--- a/src/cmd/cgo/main.go +++ b/src/cmd/cgo/main.go @@ -130,6 +130,8 @@ var dynobj = flag.String("dynimport", "", "if non-empty, print dynamic import da var godefs = flag.Bool("godefs", false, "for bootstrap: write Go definitions for C file to standard output") var cdefs = flag.Bool("cdefs", false, "for bootstrap: write C definitions for C file to standard output") +var gccgo = flag.Bool("gccgo", false, "generate files for use with gccgo") + var goarch, goos string func main() {
gccgo
という新しいブール型フラグが追加されました。このフラグは、cgo
がgccgo
コンパイラで使用するためのファイルを生成するかどうかを制御します。
-
src/cmd/cgo/out.go
:--- a/src/cmd/cgo/out.go +++ b/src/cmd/cgo/out.go @@ -46,7 +46,9 @@ func (p *Package) writeDefs() { fmt.Fprintf(fgo2, "package %s\n\n", p.PackageName) fmt.Fprintf(fgo2, "import \"unsafe\"\n\n") fmt.Fprintf(fgo2, "import \"syscall\"\n\n") - fmt.Fprintf(fgo2, "import _ \"runtime/cgo\"\n\n") + if !*gccgo { + fmt.Fprintf(fgo2, "import _ \"runtime/cgo\"\n\n") + } fmt.Fprintf(fgo2, "type _ unsafe.Pointer\n\n") fmt.Fprintf(fgo2, "func _Cerrno(dst *error, x int) { *dst = syscall.Errno(x) }\n") @@ -57,7 +59,11 @@ func (p *Package) writeDefs() { } fmt.Fprintf(fgo2, "type _Ctype_void [0]byte\n") - fmt.Fprintf(fc, cProlog) + if *gccgo { + fmt.Fprintf(fc, cPrologGccgo) + } else { + fmt.Fprintf(fc, cProlog) + } cVars := make(map[string]bool) for _, n := range p.Name { @@ -238,13 +244,22 @@ func (p *Package) writeDefsFunc(fc, fgo2 *os.File, n *Name) { Type: gtype, }\n printer.Fprint(fgo2, fset, d) - fmt.Fprintf(fgo2, "\n") + if *gccgo { + fmt.Fprintf(fgo2, " __asm__(\"%s\")\n", n.C) + } else { + fmt.Fprintf(fgo2, "\n") + } if name == "CString" || name == "GoString" || name == "GoStringN" || name == "GoBytes" { // The builtins are already defined in the C prolog. return } + // gccgo does not require a wrapper unless an error must be returned. + if *gccgo && !n.AddError { + return + } + var argSize int64 _, argSize = p.structType(n) @@ -730,6 +745,42 @@ void } ` +const cPrologGccgo = ` +#include <stdint.h> +#include <string.h> + +struct __go_string { + const unsigned char *__data; + int __length; +}; + +typedef struct __go_open_array { + void* __values; + int __count; + int __capacity; +} Slice; + +struct __go_string __go_byte_array_to_string(const void* p, int len); +struct __go_open_array __go_string_to_byte_array (struct __go_string str); + +const char *CString(struct __go_string s) { + return strndup(s.__data, s.__length); +} + +struct __go_string GoString(char *p) { + return __go_byte_array_to_string(p, strlen(p)); +} + +struct __go_string GoStringN(char *p, int n) { + return __go_byte_array_to_string(p, n); +} + +Slice GoBytes(char *p, int n) { + struct __go_string s = { p, n }; + return __go_string_to_byte_array(s); +} +` + const gccExportHeaderProlog = ` typedef unsigned int uint; typedef signed char schar;
runtime/cgo
のインポートが、gccgo
フラグがfalse
の場合にのみ行われるように条件化されました。- Cプロローグの生成が条件化され、
gccgo
フラグがtrue
の場合には新しいcPrologGccgo
が使用されるようになりました。 writeDefsFunc
関数内で、gccgo
フラグがtrue
の場合にGoの関数定義に__asm__
ディレクティブが追加されるようになりました。これは、gccgo
がGoの関数をCから呼び出す際に使用するシンボル名を指定するためです。gccgo
フラグがtrue
で、かつエラーを返す必要がない(!n.AddError
)Go関数については、Go側のラッパー関数が生成されないように変更されました。これは、gccgo
がより直接的なC呼び出しをサポートするため、不要なラッパーを省略できるためです。- 新しい定数
cPrologGccgo
が追加されました。この定数には、gccgo
のABIに合わせたGoの文字列(__go_string
)やスライス(__go_open_array
)のC言語での構造体定義、およびそれらをCの型に変換するためのヘルパー関数(CString
,GoString
,GoStringN
,GoBytes
)のgccgo
互換実装が含まれています。
コアとなるコードの解説
src/cmd/cgo/main.go
におけるgccgo
フラグの追加
main.go
では、cgo
コマンドラインツールが受け付ける新しいオプション-gccgo
が定義されています。
var gccgo = flag.Bool("gccgo", false, "generate files for use with gccgo")
この行は、cgo
コマンドを実行する際に-gccgo
オプションを渡すと、*gccgo
という変数がtrue
になるように設定します。このフラグの値は、cgo
が後続のコード生成処理でgccgo
向けの特殊な挙動を行うかどうかの判断基準となります。これにより、ユーザーは明示的にgccgo
との互換性モードを有効にできるようになります。
src/cmd/cgo/out.go
における変更
out.go
は、cgo
がGoとCの間の相互運用コードを実際に生成するロジックを含むファイルです。
-
runtime/cgo
インポートの条件化:if !*gccgo { fmt.Fprintf(fgo2, "import _ \"runtime/cgo\"\n\n") }
この変更は、生成されるGoファイル(
fgo2
に書き込まれる)にimport _ "runtime/cgo"
という行を含めるかどうかを制御します。標準のGoコンパイラ(gc
)では、runtime/cgo
パッケージはCGO呼び出しの際にGoランタイムとの連携(例: スケジューラの管理、スタックの切り替え)に不可欠です。しかし、gccgo
はGoランタイムの異なる実装を持つため、このパッケージのインポートが不要であるか、あるいは異なる方法で連携を処理する可能性があります。-gccgo
フラグがtrue
の場合、このインポート行は生成されず、gccgo
が独自のCGOランタイムメカニズムを持っていることを前提としています。 -
Cプロローグの条件付き生成:
if *gccgo { fmt.Fprintf(fc, cPrologGccgo) } else { fmt.Fprintf(fc, cProlog) }
fc
は生成されるCファイルへのファイルポインタです。この部分では、cgo
がCファイルに挿入する初期化コードやヘルパー関数群(Prolog)を、gccgo
フラグの有無によって切り替えています。cProlog
: 標準のGoコンパイラ(gc
)向けのPrologです。cPrologGccgo
:gccgo
向けの新しいPrologです。このPrologは、gccgo
のABIに合わせたGoの文字列やスライスのC言語での構造体定義と、それらをCのポインタや配列に変換するためのヘルパー関数のgccgo
互換実装を含んでいます。
-
writeDefsFunc
における__asm__
ディレクティブの追加とラッパー生成の最適化:writeDefsFunc
は、Goの関数をCから呼び出すための定義を生成する部分です。if *gccgo { fmt.Fprintf(fgo2, " __asm__(\"%s\")\n", n.C) } else { fmt.Fprintf(fgo2, "\n") } // ... if *gccgo && !n.AddError { return }
__asm__
ディレクティブの追加:gccgo
モードの場合、Goの関数定義の後に__asm__("symbol_name")
という行が追加されます。これは、gccgo
がGoの関数シンボルをC側から参照できるように、特定のリンケージ名(アセンブリ名)を明示的に指定するためのものです。これにより、gccgo
コンパイラがGoの関数を正しくリンクできるようになります。- ラッパー生成の最適化:
gccgo
モードで、かつGo関数がエラーを返さない(n.AddError
がfalse
)場合、Go側のラッパー関数生成をスキップします。これは、gccgo
がGoとCの間の関数呼び出しにおいて、gc
よりも直接的なメカニズムを提供しており、不要なラッパーを省略することでオーバーヘッドを削減できるためと考えられます。
-
cPrologGccgo
定数の定義:const cPrologGccgo = ` #include <stdint.h> #include <string.h> struct __go_string { const unsigned char *__data; int __length; }; typedef struct __go_open_array { void* __values; int __count; int __capacity; } Slice; struct __go_string __go_byte_array_to_string(const void* p, int len); struct __go_open_array __go_string_to_byte_array (struct __go_string str); const char *CString(struct __go_string s) { return strndup(s.__data, s.__length); } struct __go_string GoString(char *p) { return __go_byte_array_to_string(p, strlen(p)); } struct __go_string GoStringN(char *p, int n) { return __go_byte_array_to_string(p, n); } Slice GoBytes(char *p, int n) { struct __go_string s = { p, n }; return __go_string_to_byte_array(s); } `
この新しいCコードブロックは、
gccgo
がGoの文字列やスライスをC言語側でどのように表現するかを定義しています。struct __go_string
: Goの文字列型string
がC言語側でどのように表現されるかを定義します。データポインタと長さを持つ構造体です。struct __go_open_array
(別名Slice
): Goのスライス型がC言語側でどのように表現されるかを定義します。値へのポインタ、要素数、容量を持つ構造体です。__go_byte_array_to_string
と__go_string_to_byte_array
: これらはgccgo
ランタイムが提供する、バイト配列とGo文字列の間で変換を行う内部関数への前方宣言です。CString
,GoString
,GoStringN
,GoBytes
: これらはcgo
が提供するGoとCの間の文字列・スライス変換ヘルパー関数のgccgo
互換実装です。例えば、CString
はGoの__go_string
構造体を受け取り、Cスタイルのヌル終端文字列を返します。これらの関数は、gccgo
のABIに厳密に従って実装されており、GoとCの間で安全かつ効率的にデータをやり取りできるようにします。
これらの変更全体として、cgo
はgccgo
コンパイラの特定の要件(ABI、ランタイム連携、シンボルリンケージ)に対応できるようになり、gccgo
環境下でのCGOの利用を可能にしています。
関連リンク
- Go Gerrit Change 5485070: https://golang.org/cl/5485070
参考にした情報源リンク
- Go言語公式ドキュメント: https://go.dev/
- cgoのドキュメント: https://go.dev/cmd/cgo/
- gccgoの概要 (GCCドキュメント): https://gcc.gnu.org/onlinedocs/gccgo/
- Go言語のABIに関する議論 (GoのIssueやメーリングリストなど、具体的なリンクはコミット当時の情報に依存するため一般的なものに留める)
- GoのIssueトラッカー: https://github.com/golang/go/issues
- golang-devメーリングリスト: https://groups.google.com/g/golang-dev
- Application Binary Interface (Wikipedia): https://ja.wikipedia.org/wiki/%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%83%90%E3%82%A4%E3%83%8A%E3%83%AA%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%BC%E3%83%95%E3%82%A7%E3%83%BC%E3%82%B9