[インデックス 19255] ファイルの概要
このコミットは、Go言語のCgoツールチェインの一部であるsrc/cmd/cgo/out.go
ファイルに対する変更です。out.go
は、CgoがGoコードとCコード間の相互運用に必要なCヘッダーファイルやCソースファイルを生成する際に使用されるロジックを含んでいます。具体的には、Go関数をCから呼び出せるようにエクスポートする際のシンボル名の扱いに焦点を当てた修正が行われています。
コミット
commit fee51f45ab37c77ad8b7967b091ddf19d4e259a3
Author: Ian Lance Taylor <iant@golang.org>
Date: Tue Apr 29 08:53:38 2014 -0400
cmd/cgo: for gccgo add #define to cgo_export.h for expected name
For gccgo we rename exported functions so that the compiler
will make them visible. This CL adds a #define so that C
functions that #include "cgo_export.h" can use the expected
names of the function.
The test for this is the existing issue6833 test in
misc/cgo/test. Without this CL it fails when using
-compiler=gccgo.
LGTM=minux.ma, rsc
R=golang-codereviews, gobot, rsc, minux.ma
CC=golang-codereviews
https://golang.org/cl/91830046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/fee51f45ab37c77ad8b7967b091ddf19d4e259a3
元コミット内容
cmd/cgo: for gccgo add #define to cgo_export.h for expected name
gccgoの場合、エクスポートされた関数をコンパイラが可視化するように名前を変更します。この変更は、cgo_export.h
をインクルードするC関数が、期待される関数名を使用できるように、#define
を追加するものです。
この変更のテストは、misc/cgo/test
にある既存のissue6833
テストです。この変更がない場合、-compiler=gccgo
を使用するとテストが失敗します。
変更の背景
このコミットは、Go言語のCgoツールとgccgo
コンパイラを使用する際の特定の互換性問題に対処するために導入されました。
Go言語には、C言語のコードとGo言語のコードを相互に呼び出すためのcgo
というツールがあります。cgo
は、GoのパッケージからCの関数を呼び出したり、CのコードからGoの関数を呼び出したりすることを可能にします。この相互運用性を実現するために、cgo
はGoとCの間のブリッジとなるコードを生成します。
Goコンパイラには、公式のgc
コンパイラと、GCCフロントエンドをベースにしたgccgo
コンパイラの2種類があります。gccgo
は、GoコードをGCCのバックエンドを通じてコンパイルするため、生成されるバイナリやシンボルの扱いにgc
コンパイラとは異なる特性を持つことがあります。
問題は、Goの関数をCから呼び出せるようにエクスポートする際に発生しました。cgo
は通常、エクスポートされるGo関数に対して、Cコードから参照するための特定のシンボル名を生成します。しかし、gccgo
コンパイラは、これらのエクスポートされたGo関数のシンボル名を内部的に変更(マングル)することがありました。これにより、Cコードがcgo_export.h
ヘッダーファイルを通じて期待する関数名でGo関数を呼び出そうとしても、gccgo
が生成した実際のシンボル名と一致せず、リンケージエラーが発生していました。
具体的には、gccgo
はエクスポートされたGo関数をデフォルトでstatic
として扱い、外部から参照できないようにしてしまう傾向がありました。これを回避するために、cgo
はgccgo
向けに__asm__
ディレクティブを使用して、Go関数のシンボル名をCgoexp_
プレフィックスを付けた形式に強制的に変更していました。しかし、この変更された名前はCコードが直接使用するものではなく、Cコードは元の(Cgoexp_
プレフィックスのない)期待される名前でGo関数を参照しようとします。
この不一致が、misc/cgo/test
にあるissue6833
テストの失敗として現れていました。このテストは、Cgoを介してGo関数をCから呼び出すシナリオを検証するものであり、gccgo
コンパイラを使用した場合にリンケージエラーにより失敗していました。
このコミットは、Cコードが期待する関数名とgccgo
が生成する実際のシンボル名の間のギャップを埋めることで、この問題を解決することを目的としています。
前提知識の解説
このコミットを理解するためには、以下の概念について理解しておく必要があります。
-
Cgo:
- Go言語とC言語のコードを相互運用するためのGoのツールです。
- Goプログラム内でCの関数を呼び出したり、Cプログラム内でGoの関数を呼び出したりすることを可能にします。
import "C"
という特殊な擬似パッケージを使用することで、GoコードからCの関数や変数にアクセスできます。- Cgoは、GoとCの間の呼び出し規約の変換、データ型のマッピング、および必要なブリッジコードの生成を行います。
- Go関数をCから呼び出せるようにするには、Go関数を
//export FunctionName
というコメントでマークし、cgo_export.h
というヘッダーファイルを生成します。
-
gccgo:
- Go言語の代替コンパイラの一つで、GCC (GNU Compiler Collection) のフロントエンドとして実装されています。
- GoのソースコードをGCCの中間表現に変換し、GCCの最適化パスとバックエンドを利用して実行可能なバイナリを生成します。
- 公式の
gc
コンパイラとは異なるコード生成戦略やシンボル管理の特性を持つことがあります。特に、シンボルの可視性(エクスポートされるか否か)の扱いに違いが生じることがあります。
-
シンボルとリンケージ:
- コンパイルされたプログラムにおいて、関数やグローバル変数などは「シンボル」として表現されます。
- シンボルは、その名前と、それがメモリ上のどこにあるかを示すアドレスを持ちます。
- 「リンケージ」とは、異なるコンパイル単位(例えば、GoのオブジェクトファイルとCのオブジェクトファイル)で定義されたシンボルを解決し、それらを結合して最終的な実行可能ファイルを生成するプロセスです。
- シンボルには「外部リンケージ」(他のファイルから参照可能)と「内部リンケージ」(そのファイル内でのみ参照可能、
static
キーワードで指定されることが多い)があります。 gccgo
は、Goからエクスポートされた関数をデフォルトで内部リンケージとして扱う傾向があり、これがCコードからの呼び出しを妨げる原因となっていました。
-
cgo_export.h
:- CgoがGo関数をCから呼び出せるようにエクスポートする際に生成するヘッダーファイルです。
- このファイルには、エクスポートされたGo関数のC言語でのプロトタイプ宣言が含まれており、CコードはこのヘッダーをインクルードすることでGo関数を型安全に呼び出すことができます。
-
#define
プリプロセッサディレクティブ:- C/C++言語のプリプロセッサ機能の一つで、マクロを定義するために使用されます。
#define MACRO_NAME replacement_text
の形式で記述され、コンパイル前にMACRO_NAME
が出現する箇所がreplacement_text
に置き換えられます。- このコミットでは、Cコードが期待する関数名と
gccgo
が生成する実際のシンボル名の間のマッピングを提供するために使用されます。
-
__asm__
ディレクティブ:- GCC拡張機能の一つで、C/C++コード内でアセンブリ言語を直接記述したり、シンボル名を操作したりするために使用されます。
- このコミットでは、
__asm__("symbol.name")
の形式で、Go関数がCから参照される際のシンボル名を強制的に指定するために使われています。gccgo
は、Goの関数名をそのままエクスポートするのではなく、gccgo
の内部的な命名規則に従ってシンボル名を生成します。__asm__
ディレクティブは、このgccgo
が生成するシンボル名(例:_cgo_export.Cgoexp_MyGoFunction
)を明示的に指定するために使用されます。
技術的詳細
このコミットの核心は、gccgo
コンパイラがGoからエクスポートされた関数のシンボル名をどのように扱うか、という問題にあります。
通常のGoコンパイラ(gc
)では、//export MyGoFunction
とマークされたGo関数は、Cコードから直接MyGoFunction
という名前で呼び出せるように、適切なシンボル名でエクスポートされます。
しかし、gccgo
の場合、Goの関数がCから呼び出されるようにエクスポートされる際、gccgo
はデフォルトでそのシンボルをstatic
(内部リンケージ)として扱い、外部から参照できないようにしてしまう傾向があります。これを回避し、シンボルを外部に公開するために、cgo
はgccgo
向けに特別な処理を行います。
具体的には、cgo
はエクスポートされるGo関数に対して、Cコードが期待する元の名前(例: MyGoFunction
)とは異なる、Cgoexp_
プレフィックスが付加された名前(例: Cgoexp_MyGoFunction
)を内部的に生成します。そして、このCgoexp_
プレフィックス付きの名前を、__asm__
ディレクティブを使って、gccgo
が生成する実際のシンボル名(例: _cgo_export.Cgoexp_MyGoFunction
)にマッピングします。これにより、gccgo
はシンボルを外部にエクスポートし、リンカが参照できるようになります。
問題は、Cコードがcgo_export.h
をインクルードしてGo関数を呼び出す際に、Cgoexp_MyGoFunction
のような内部的な名前ではなく、元の期待される名前MyGoFunction
を使用しようとすることです。この名前の不一致がリンケージエラーを引き起こしていました。
このコミットは、この問題を解決するために、cgo_export.h
に#define
ディレクティブを追加します。この#define
は、Cコードが期待する元の関数名(例: MyGoFunction
)を、gccgo
が実際にエクスポートするCgoexp_
プレフィックス付きの名前(例: Cgoexp_MyGoFunction
)にマッピングします。
例:
#define MyGoFunction Cgoexp_MyGoFunction
これにより、CコードがMyGoFunction()
と記述しても、プリプロセッサがそれをCgoexp_MyGoFunction()
に展開するため、gccgo
がエクスポートした正しいシンボル名でGo関数を呼び出すことができるようになります。
さらに、_cgo_export.c
というファイル(Cgoが生成するCソースファイルで、Go関数をCから呼び出すためのラッパー関数が定義されている)では、この#define
が問題を引き起こす可能性があります。なぜなら、_cgo_export.c
は実際にCgoexp_MyGoFunction
という名前の関数を定義する必要があるため、#define
によってその名前がMyGoFunction
に置き換えられてしまうと、定義が正しく行われなくなるからです。このため、_cgo_export.c
の先頭で、該当する#define
を#undef
することで無効化しています。これにより、_cgo_export.c
内では元のCgoexp_MyGoFunction
という名前で関数を定義し、cgo_export.h
をインクルードする他のCファイルでは#define
による名前解決が行われる、という両立が実現されます。
この修正により、gccgo
を使用した場合でも、Cgoを介したGo関数のエクスポートとCからの呼び出しが正しく機能するようになります。
コアとなるコードの変更箇所
変更はsrc/cmd/cgo/out.go
ファイルに集中しています。
--- a/src/cmd/cgo/out.go
+++ b/src/cmd/cgo/out.go
@@ -880,10 +880,24 @@ func (p *Package) writeGccgoExports(fgo2, fc, fm *os.File) {
fmt.Fprintf(cdeclBuf, ")")
cParams := cdeclBuf.String()
+ // We need to use a name that will be exported by the
+ // Go code; otherwise gccgo will make it static and we
+ // will not be able to link against it from the C
+ // code.
goName := "Cgoexp_" + exp.ExpName
fmt.Fprintf(fgcch, `extern %s %s %s __asm__(\"%s.%s\");`, cRet, goName, cParams, gccgoSymbolPrefix, goName)
fmt.Fprint(fgcch, "\n")
+ // Use a #define so that the C code that includes
+ // cgo_export.h will be able to refer to the Go
+ // function using the expected name.
+ fmt.Fprintf(fgcch, "#define %s %s\\n", exp.ExpName, goName)
+
+ // Use a #undef in _cgo_export.c so that we ignore the
+ // #define from cgo_export.h, since here we are
+ // defining the real function.
+ fmt.Fprintf(fgcc, "#undef %s\\n", exp.ExpName)
+
fmt.Fprint(fgcc, "\n")
fmt.Fprintf(fgcc, "%s %s %s {\\n", cRet, exp.ExpName, cParams)
fmt.Fprint(fgcc, "\t")
追加された行は以下の通りです。
fmt.Fprintf(fgcch, "#define %s %s\\n", exp.ExpName, goName)
fmt.Fprintf(fgcc, "#undef %s\\n", exp.ExpName)
コアとなるコードの解説
src/cmd/cgo/out.go
のwriteGccgoExports
関数は、gccgo
コンパイラ向けにGo関数をエクスポートするためのCヘッダーファイル(cgo_export.h
)とCソースファイル(_cgo_export.c
)を生成する役割を担っています。
変更前のコードでは、gccgo
がGo関数を外部に公開できるように、Cgoexp_
プレフィックスを付けた名前(goName
)を生成し、__asm__
ディレクティブを使ってそのシンボル名を明示的に指定していました。これにより、gccgo
はシンボルをstatic
にせず、外部リンケージとして扱えるようになります。
しかし、このgoName
はCコードが直接参照する名前ではありませんでした。Cコードはcgo_export.h
をインクルードし、Go関数を元の名前(exp.ExpName
)で呼び出そうとします。このギャップを埋めるのが今回の変更です。
-
fmt.Fprintf(fgcch, "#define %s %s\\n", exp.ExpName, goName)
- この行は、
cgo_export.h
ファイル(fgcch
はcgo_export.h
へのファイルポインタ)に#define
ディレクティブを書き込みます。 exp.ExpName
は、Cコードが期待するGo関数の元の名前(例:MyGoFunction
)です。goName
は、gccgo
が実際にエクスポートする、Cgoexp_
プレフィックスが付加された名前(例:Cgoexp_MyGoFunction
)です。- 結果として、
cgo_export.h
には例えば#define MyGoFunction Cgoexp_MyGoFunction
のような行が追加されます。 - これにより、
cgo_export.h
をインクルードするCソースファイルでは、MyGoFunction
という名前がプリプロセッサによってCgoexp_MyGoFunction
に置き換えられ、gccgo
がエクスポートした正しいシンボル名でGo関数を呼び出すことができるようになります。
- この行は、
-
fmt.Fprintf(fgcc, "#undef %s\\n", exp.ExpName)
- この行は、
_cgo_export.c
ファイル(fgcc
は_cgo_export.c
へのファイルポインタ)に#undef
ディレクティブを書き込みます。 _cgo_export.c
は、Go関数をCから呼び出すための実際のラッパー関数を定義するファイルです。このファイル内では、Cgoexp_MyGoFunction
という名前で関数を定義する必要があります。- もし
_cgo_export.c
がcgo_export.h
をインクルードし、かつこの#undef
がない場合、#define MyGoFunction Cgoexp_MyGoFunction
によってCgoexp_MyGoFunction
という名前がMyGoFunction
に置き換えられてしまい、関数定義が正しく行えなくなります。 #undef
を使用することで、_cgo_export.c
内では、cgo_export.h
で定義された#define
の影響を受けずに、元のCgoexp_
プレフィックス付きの名前で関数を定義できるようになります。これは、_cgo_export.c
が「実際の関数を定義する場所」であり、他のCファイルが「その関数を参照する場所」であるという役割の違いを考慮したものです。
- この行は、
これらの変更により、gccgo
コンパイラを使用した場合でも、Cgoを介してGo関数がCコードから正しく参照され、リンケージエラーが解消されるようになりました。
関連リンク
- https://github.com/golang/go/commit/fee51f45ab37c77ad8b7967b091ddf19d4e259a3
- Go issue 6833: https://go.dev/issue/6833 (コミットメッセージで言及されているテストの元となったissue)
参考にした情報源リンク
- Go言語公式ドキュメント: Cgo (https://go.dev/blog/c-go-cgo)
- GCC
__asm__
keyword: (https://gcc.gnu.org/onlinedocs/gcc/Asm-Labels.html) - Cプリプロセッサ
#define
と#undef
: (https://ja.cppreference.com/w/c/preprocessor/define) - Go言語の
gccgo
コンパイラに関する情報 (https://go.dev/doc/install/gccgo) - シンボルとリンケージに関する一般的な情報 (コンパイラ、リンカの概念)```markdown
[インデックス 19255] ファイルの概要
このコミットは、Go言語のCgoツールチェインの一部であるsrc/cmd/cgo/out.go
ファイルに対する変更です。out.go
は、CgoがGoコードとCコード間の相互運用に必要なCヘッダーファイルやCソースファイルを生成する際に使用されるロジックを含んでいます。具体的には、Go関数をCから呼び出せるようにエクスポートする際のシンボル名の扱いに焦点を当てた修正が行われています。
コミット
commit fee51f45ab37c77ad8b7967b091ddf19d4e259a3
Author: Ian Lance Taylor <iant@golang.org>
Date: Tue Apr 29 08:53:38 2014 -0400
cmd/cgo: for gccgo add #define to cgo_export.h for expected name
For gccgo we rename exported functions so that the compiler
will make them visible. This CL adds a #define so that C
functions that #include "cgo_export.h" can use the expected
names of the function.
The test for this is the existing issue6833 test in
misc/cgo/test. Without this CL it fails when using
-compiler=gccgo.
LGTM=minux.ma, rsc
R=golang-codereviews, gobot, rsc, minux.ma
CC=golang-codereviews
https://golang.org/cl/91830046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/fee51f45ab37c77ad8b7967b091ddf19d4e259a3
元コミット内容
cmd/cgo: for gccgo add #define to cgo_export.h for expected name
gccgoの場合、エクスポートされた関数をコンパイラが可視化するように名前を変更します。この変更は、cgo_export.h
をインクルードするC関数が、期待される関数名を使用できるように、#define
を追加するものです。
この変更のテストは、misc/cgo/test
にある既存のissue6833
テストです。この変更がない場合、-compiler=gccgo
を使用するとテストが失敗します。
変更の背景
このコミットは、Go言語のCgoツールとgccgo
コンパイラを使用する際の特定の互換性問題に対処するために導入されました。
Go言語には、C言語のコードとGo言語のコードを相互に呼び出すためのcgo
というツールがあります。cgo
は、GoのパッケージからCの関数を呼び出したり、CのコードからGoの関数を呼び出したりすることを可能にします。この相互運用性を実現するために、cgo
はGoとCの間のブリッジとなるコードを生成します。
Goコンパイラには、公式のgc
コンパイラと、GCCフロントエンドをベースにしたgccgo
コンパイラの2種類があります。gccgo
は、GoコードをGCCのバックエンドを通じてコンパイルするため、生成されるバイナリやシンボルの扱いにgc
コンパイラとは異なる特性を持つことがあります。
問題は、Goの関数をCから呼び出せるようにエクスポートする際に発生しました。cgo
は通常、エクスポートされるGo関数に対して、Cコードから参照するための特定のシンボル名を生成します。しかし、gccgo
コンパイラは、これらのエクスポートされたGo関数のシンボル名を内部的に変更(マングル)することがありました。これにより、Cコードがcgo_export.h
ヘッダーファイルを通じて期待する関数名でGo関数を呼び出そうとしても、gccgo
が生成した実際のシンボル名と一致せず、リンケージエラーが発生していました。
具体的には、gccgo
はエクスポートされたGo関数をデフォルトでstatic
として扱い、外部から参照できないようにしてしまう傾向がありました。これを回避するために、cgo
はgccgo
向けに__asm__
ディレクティブを使用して、Go関数のシンボル名をCgoexp_
プレフィックスを付けた形式に強制的に変更していました。しかし、この変更された名前はCコードが直接使用するものではなく、Cコードは元の(Cgoexp_
プレフィックスのない)期待される名前でGo関数を参照しようとします。
この不一致が、misc/cgo/test
にあるissue6833
テストの失敗として現れていました。このテストは、Cgoを介してGo関数をCから呼び出すシナリオを検証するものであり、gccgo
コンパイラを使用した場合にリンケージエラーにより失敗していました。
このコミットは、Cコードが期待する関数名とgccgo
が生成する実際のシンボル名の間のギャップを埋めることで、この問題を解決することを目的としています。
前提知識の解説
このコミットを理解するためには、以下の概念について理解しておく必要があります。
-
Cgo:
- Go言語とC言語のコードを相互運用するためのGoのツールです。
- Goプログラム内でCの関数を呼び出したり、Cプログラム内でGoの関数を呼び出したりすることを可能にします。
import "C"
という特殊な擬似パッケージを使用することで、GoコードからCの関数や変数にアクセスできます。- Cgoは、GoとCの間の呼び出し規約の変換、データ型のマッピング、および必要なブリッジコードの生成を行います。
- Go関数をCから呼び出せるようにするには、Go関数を
//export FunctionName
というコメントでマークし、cgo_export.h
というヘッダーファイルを生成します。
-
gccgo:
- Go言語の代替コンパイラの一つで、GCC (GNU Compiler Collection) のフロントエンドとして実装されています。
- GoのソースコードをGCCの中間表現に変換し、GCCの最適化パスとバックエンドを利用して実行可能なバイナリを生成します。
- 公式の
gc
コンパイラとは異なるコード生成戦略やシンボル管理の特性を持つことがあります。特に、シンボルの可視性(エクスポートされるか否か)の扱いに違いが生じることがあります。
-
シンボルとリンケージ:
- コンパイルされたプログラムにおいて、関数やグローバル変数などは「シンボル」として表現されます。
- シンボルは、その名前と、それがメモリ上のどこにあるかを示すアドレスを持ちます。
- 「リンケージ」とは、異なるコンパイル単位(例えば、GoのオブジェクトファイルとCのオブジェクトファイル)で定義されたシンボルを解決し、それらを結合して最終的な実行可能ファイルを生成するプロセスです。
- シンボルには「外部リンケージ」(他のファイルから参照可能)と「内部リンケージ」(そのファイル内でのみ参照可能、
static
キーワードで指定されることが多い)があります。 gccgo
は、Goからエクスポートされた関数をデフォルトで内部リンケージとして扱う傾向があり、これがCコードからの呼び出しを妨げる原因となっていました。
-
cgo_export.h
:- CgoがGo関数をCから呼び出せるようにエクスポートする際に生成するヘッダーファイルです。
- このファイルには、エクスポートされたGo関数のC言語でのプロトタイプ宣言が含まれており、CコードはこのヘッダーをインクルードすることでGo関数を型安全に呼び出すことができます。
-
#define
プリプロセッサディレクティブ:- C/C++言語のプリプロセッサ機能の一つで、マクロを定義するために使用されます。
#define MACRO_NAME replacement_text
の形式で記述され、コンパイル前にMACRO_NAME
が出現する箇所がreplacement_text
に置き換えられます。- このコミットでは、Cコードが期待する関数名と
gccgo
が生成する実際のシンボル名の間のマッピングを提供するために使用されます。
-
__asm__
ディレクティブ:- GCC拡張機能の一つで、C/C++コード内でアセンブリ言語を直接記述したり、シンボル名を操作したりするために使用されます。
- このコミットでは、
__asm__("symbol.name")
の形式で、Go関数がCから参照される際のシンボル名を強制的に指定するために使われています。gccgo
は、Goの関数名をそのままエクスポートするのではなく、gccgo
の内部的な命名規則に従ってシンボル名を生成します。__asm__
ディレクティブは、このgccgo
が生成するシンボル名(例:_cgo_export.Cgoexp_MyGoFunction
)を明示的に指定するために使用されます。
技術的詳細
このコミットの核心は、gccgo
コンパイラがGoからエクスポートされた関数のシンボル名をどのように扱うか、という問題にあります。
通常のGoコンパイラ(gc
)では、//export MyGoFunction
とマークされたGo関数は、Cコードから直接MyGoFunction
という名前で呼び出せるように、適切なシンボル名でエクスポートされます。
しかし、gccgo
の場合、Goの関数がCから呼び出されるようにエクスポートされる際、gccgo
はデフォルトでそのシンボルをstatic
(内部リンケージ)として扱い、外部から参照できないようにしてしまう傾向があります。これを回避し、シンボルを外部に公開するために、cgo
はgccgo
向けに特別な処理を行います。
具体的には、cgo
はエクスポートされるGo関数に対して、Cコードが期待する元の名前(例: MyGoFunction
)とは異なる、Cgoexp_
プレフィックスが付加された名前(例: Cgoexp_MyGoFunction
)を内部的に生成します。そして、このCgoexp_
プレフィックス付きの名前を、__asm__
ディレクティブを使って、gccgo
が生成する実際のシンボル名(例: _cgo_export.Cgoexp_MyGoFunction
)にマッピングします。これにより、gccgo
はシンボルを外部にエクスポートし、リンカが参照できるようになります。
問題は、Cコードがcgo_export.h
をインクルードしてGo関数を呼び出す際に、Cgoexp_MyGoFunction
のような内部的な名前ではなく、元の期待される名前MyGoFunction
を使用しようとすることです。この名前の不一致がリンケージエラーを引き起こしていました。
このコミットは、この問題を解決するために、cgo_export.h
に#define
ディレクティブを追加します。この#define
は、Cコードが期待する元の関数名(例: MyGoFunction
)を、gccgo
が実際にエクスポートするCgoexp_
プレフィックス付きの名前(例: Cgoexp_MyGoFunction
)にマッピングします。
例:
#define MyGoFunction Cgoexp_MyGoFunction
これにより、CコードがMyGoFunction()
と記述しても、プリプロセッサがそれをCgoexp_MyGoFunction()
に展開するため、gccgo
がエクスポートした正しいシンボル名でGo関数を呼び出すことができるようになります。
さらに、_cgo_export.c
というファイル(Cgoが生成するCソースファイルで、Go関数をCから呼び出すためのラッパー関数が定義されている)では、この#define
が問題を引き起こす可能性があります。なぜなら、_cgo_export.c
は実際にCgoexp_MyGoFunction
という名前の関数を定義する必要があるため、#define
によってその名前がMyGoFunction
に置き換えられてしまうと、定義が正しく行われなくなるからです。このため、_cgo_export.c
の先頭で、該当する#define
を#undef
することで無効化しています。これにより、_cgo_export.c
内では元のCgoexp_MyGoFunction
という名前で関数を定義し、cgo_export.h
をインクルードする他のCファイルでは#define
による名前解決が行われる、という両立が実現されます。
この修正により、gccgo
を使用した場合でも、Cgoを介したGo関数のエクスポートとCからの呼び出しが正しく機能するようになります。
コアとなるコードの変更箇所
変更はsrc/cmd/cgo/out.go
ファイルに集中しています。
--- a/src/cmd/cgo/out.go
+++ b/src/cmd/cgo/out.go
@@ -880,10 +880,24 @@ func (p *Package) writeGccgoExports(fgo2, fc, fm *os.File) {
fmt.Fprintf(cdeclBuf, ")")
cParams := cdeclBuf.String()
+ // We need to use a name that will be exported by the
+ // Go code; otherwise gccgo will make it static and we
+ // will not be able to link against it from the C
+ // code.
goName := "Cgoexp_" + exp.ExpName
fmt.Fprintf(fgcch, `extern %s %s %s __asm__(\"%s.%s\");`, cRet, goName, cParams, gccgoSymbolPrefix, goName)
fmt.Fprint(fgcch, "\n")
+ // Use a #define so that the C code that includes
+ // cgo_export.h will be able to refer to the Go
+ // function using the expected name.
+ fmt.Fprintf(fgcch, "#define %s %s\\n", exp.ExpName, goName)
+
+ // Use a #undef in _cgo_export.c so that we ignore the
+ // #define from cgo_export.h, since here we are
+ // defining the real function.
+ fmt.Fprintf(fgcc, "#undef %s\\n", exp.ExpName)
+
fmt.Fprint(fgcc, "\n")
fmt.Fprintf(fgcc, "%s %s %s {\\n", cRet, exp.ExpName, cParams)
fmt.Fprint(fgcc, "\t")
追加された行は以下の通りです。
fmt.Fprintf(fgcch, "#define %s %s\\n", exp.ExpName, goName)
fmt.Fprintf(fgcc, "#undef %s\\n", exp.ExpName)
コアとなるコードの解説
src/cmd/cgo/out.go
のwriteGccgoExports
関数は、gccgo
コンパイラ向けにGo関数をエクスポートするためのCヘッダーファイル(cgo_export.h
)とCソースファイル(_cgo_export.c
)を生成する役割を担っています。
変更前のコードでは、gccgo
がGo関数を外部に公開できるように、Cgoexp_
プレフィックスを付けた名前(goName
)を生成し、__asm__
ディレクティブを使ってそのシンボル名を明示的に指定していました。これにより、gccgo
はシンボルをstatic
にせず、外部リンケージとして扱えるようになります。
しかし、このgoName
はCコードが直接参照する名前ではありませんでした。Cコードはcgo_export.h
をインクルードし、Go関数を元の名前(exp.ExpName
)で呼び出そうとします。このギャップを埋めるのが今回の変更です。
-
fmt.Fprintf(fgcch, "#define %s %s\\n", exp.ExpName, goName)
- この行は、
cgo_export.h
ファイル(fgcch
はcgo_export.h
へのファイルポインタ)に#define
ディレクティブを書き込みます。 exp.ExpName
は、Cコードが期待するGo関数の元の名前(例:MyGoFunction
)です。goName
は、gccgo
が実際にエクスポートする、Cgoexp_
プレフィックスが付加された名前(例:Cgoexp_MyGoFunction
)です。- 結果として、
cgo_export.h
には例えば#define MyGoFunction Cgoexp_MyGoFunction
のような行が追加されます。 - これにより、
cgo_export.h
をインクルードするCソースファイルでは、MyGoFunction
という名前がプリプロセッサによってCgoexp_MyGoFunction
に置き換えられ、gccgo
がエクスポートした正しいシンボル名でGo関数を呼び出すことができるようになります。
- この行は、
-
fmt.Fprintf(fgcc, "#undef %s\\n", exp.ExpName)
- この行は、
_cgo_export.c
ファイル(fgcc
は_cgo_export.c
へのファイルポインタ)に#undef
ディレクティブを書き込みます。 _cgo_export.c
は、Go関数をCから呼び出すための実際のラッパー関数を定義するファイルです。このファイル内では、Cgoexp_MyGoFunction
という名前で関数を定義する必要があります。- もし
_cgo_export.c
がcgo_export.h
をインクルードし、かつこの#undef
がない場合、#define MyGoFunction Cgoexp_MyGoFunction
によってCgoexp_MyGoFunction
という名前がMyGoFunction
に置き換えられてしまい、関数定義が正しく行えなくなります。 #undef
を使用することで、_cgo_export.c
内では、cgo_export.h
で定義された#define
の影響を受けずに、元のCgoexp_
プレフィックス付きの名前で関数を定義できるようになります。これは、_cgo_export.c
が「実際の関数を定義する場所」であり、他のCファイルが「その関数を参照する場所」であるという役割の違いを考慮したものです。
- この行は、
これらの変更により、gccgo
コンパイラを使用した場合でも、Cgoを介してGo関数がCコードから正しく参照され、リンケージエラーが解消されるようになりました。
関連リンク
- https://github.com/golang/go/commit/fee51f45ab37c77ad8b7967b091ddf19d4e259a3
- Go issue 6833: https://go.dev/issue/6833 (コミットメッセージで言及されているテストの元となったissue)
参考にした情報源リンク
- Go言語公式ドキュメント: Cgo (https://go.dev/blog/c-go-cgo)
- GCC
__asm__
keyword: (https://gcc.gnu.org/onlinedocs/gcc/Asm-Labels.html) - Cプリプロセッサ
#define
と#undef
: (https://ja.cppreference.com/w/c/preprocessor/define) - Go言語の
gccgo
コンパイラに関する情報 (https://go.dev/doc/install/gccgo) - シンボルとリンケージに関する一般的な情報 (コンパイラ、リンカの概念)