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

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

このコミットは、Go言語のcmd/cgoツールにおけるgccgoコンパイラ使用時の問題を修正するものです。具体的には、gccgo環境下でテストが再びパスするように、シンボルプレフィックスの適用、関数ポインタ変数の参照方法の調整、およびmallocプロローグ関数の修正が行われています。

コミット

commit 935a826a2fd73fd8878d76f052027d100aa46f10
Author: Ian Lance Taylor <iant@golang.org>
Date:   Tue Sep 24 18:11:13 2013 -0700

    cmd/cgo: fix so that tests pass again when using gccgo
    
    Use the symbol prefixes with the prologue functions when using
    gccgo.
    
    Use an & when referring to a function declared as a variable.
    
    Fix the malloc prologue function.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/13878043

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

https://github.com/golang/go/commit/935a826a2fd73fd8878d76f052027d100aa46f10

元コミット内容

cmd/cgo: fix so that tests pass again when using gccgo

gccgoを使用する際に、プロローグ関数でシンボルプレフィックスを使用するように修正。 変数として宣言された関数を参照する際に&を使用するように修正。 mallocプロローグ関数を修正。

変更の背景

Go言語は、GoコードとC/C++コードを連携させるためのcgoツールを提供しています。Goには主に2つのコンパイラ実装があります。一つは公式のgc(Go Compiler)で、もう一つはGCCのフロントエンドとしてGoをコンパイルするgccgoです。これら2つのコンパイラは、コード生成やシンボル解決のメカニズムにおいて異なる場合があります。

このコミットが行われた2013年9月時点では、gccgoを使用した場合にcgo関連のテストが失敗するという問題が発生していました。これは、cgoが生成するCコードがgccgoの期待するシンボル命名規則や関数ポインタの扱い、あるいは特定のプロローグ関数の挙動と合致していなかったためと考えられます。特に、GoランタイムとCライブラリ(mallocなど)の連携を担うプロローグ関数において、gccgo特有の調整が必要とされていました。

このコミットの目的は、gccgo環境下でもcgoが正しく機能し、関連するテストがパスするように、cgoが生成するCコードの互換性を向上させることでした。

前提知識の解説

cgo

cgoは、GoプログラムからC言語のコードを呼び出したり、C言語のコードからGoの関数を呼び出したりするためのGoツールです。Goのビルドプロセスの一部として機能し、GoとCの間の型変換、メモリ管理、スタック切り替えなどを自動的に処理するための接着コード(glue code)を生成します。

gccgo

gccgoは、Go言語のプログラムをコンパイルするための代替コンパイラです。GNU Compiler Collection (GCC) の一部として実装されており、既存のGCCの最適化バックエンドやコード生成インフラストラクチャを利用します。標準のgcコンパイラとは異なるアプローチを取るため、特定の低レベルな挙動(シンボル解決、ABIなど)において差異が生じることがあります。

プロローグ関数 (Prologue Functions)

cgoがGoとCの間の呼び出しを行う際に、GoランタイムとCランタイムの間の橋渡しをするために生成される補助的な関数群です。これらは、引数の渡し方、戻り値の受け取り方、エラーハンドリング、メモリ割り当て(特にmallocfreeのラッパー)など、異なる言語間の呼び出し規約を調整する役割を担います。

シンボルプレフィックス (Symbol Prefixes)

コンパイラやリンカが、関数や変数などのシンボル名を一意に識別するために付与する接頭辞です。例えば、C言語の関数fooがコンパイルされると、リンカが参照する実際のシンボル名は_foo__fooのようになることがあります。cgoは、Goから呼び出されるC関数や、Cから呼び出されるGo関数に対して、特定のプレフィックス(例: _cgo_)を付与して名前の衝突を防ぎ、Goランタイムがそれらを正しく識別できるようにします。gcgccgoでは、このプレフィックスの扱いが異なる場合があり、互換性の問題を引き起こすことがあります。

関数ポインタ変数 (Function Pointer Variables)

C言語では、関数へのポインタを変数として宣言し、そのポインタを通じて関数を呼び出すことができます。GoからCの関数ポインタ変数を扱う場合、その参照方法(値渡し、アドレス渡しなど)がコンパイラによって異なる解釈をされる可能性があり、特にgccgoのような代替コンパイラでは注意が必要です。

//extern ディレクティブ

cgoがGoコードからCの関数を呼び出す際に、Goコンパイラに対してそのC関数が外部で定義されていることを伝えるための特殊なコメントです。cgoは、このディレクティブに基づいて、GoとCの間の呼び出しに必要なスタブコードを生成します。

技術的詳細

このコミットは、src/cmd/cgo/out.goファイルに対する変更であり、cgoがGoとCの間の接着コードを生成するロジックに影響を与えます。

  1. cPrologGccgoの動的な生成: 変更前は、cPrologGccgoという定数文字列がそのまま使用されていました。変更後は、p.cPrologGccgo()というメソッド呼び出しに変わっています。このメソッドは、cPrologGccgoというテンプレート文字列内の"PREFIX"というプレースホルダーを、実際のcPrefixcgoが使用するシンボルプレフィックス)に置き換えることで、動的にプロローグコードを生成します。これにより、gccgoが期待するシンボル名(_cgoPREFIX_Cfunc_...のような形式)がプロローグ関数に適用されるようになります。

  2. 関数ポインタ変数の参照における&の追加: n.Kind == "fpvar"(関数ポインタ変数)の場合に、gccgoを使用している場合にのみamp = "&"が追加されるようになりました。これは、gccgoが関数ポインタ変数を参照する際に、明示的にアドレス演算子&を必要とする、あるいはその方が正しいシンボル解決につながることを示唆しています。これにより、GoからCの関数ポインタ変数を正しく参照できるようになります。

  3. //extern ディレクティブの統一: 以前は、inPrologというフラグに基づいて//extern %sまたは//extern _cgo%s%sのいずれかを使用していましたが、変更後は常に//extern _cgo%s%sの形式を使用するようになりました。これは、cgoが生成するすべてのC関数に対して、一貫したシンボルプレフィックス(_cgocPrefix)を適用することで、gccgoを含むすべてのコンパイラでシンボル解決が安定するようにするための変更です。

  4. mallocプロローグ関数の修正とシンボルプレフィックスの適用: cPrologGccgoテンプレート内で定義されているCmalloc関数が、_cgoPREFIX_Cfunc__CMallocという名前に変更されました。これは、前述のシンボルプレフィックスの動的適用の一環です。また、stdlib.hがインクルードされるようになりました。これはmalloc関数が定義されているヘッダファイルであり、gccgo環境でCmallocが正しくコンパイルされるために必要です。Cmallocの内部ロジック自体は大きな変更はありませんが、n == 0の場合にmalloc(1)を呼び出すというGoランタイムの特定の挙動をC側でエミュレートするためのものです。

これらの変更は、gccgoがGoとCの間のインターフェースを処理する方法の特定のニュアンスに対応するために行われました。特に、シンボル解決と関数ポインタの扱いはコンパイラによって異なることが多いため、cgoは両方のコンパイラで正しく機能するように調整する必要があります。

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

src/cmd/cgo/out.go

--- a/src/cmd/cgo/out.go
+++ b/src/cmd/cgo/out.go
@@ -87,7 +87,7 @@ func (p *Package) writeDefs() {
 	}
 
 	if *gccgo {
-		fmt.Fprintf(fc, cPrologGccgo)
+		fmt.Fprintf(fc, p.cPrologGccgo())
 	} else {
 		fmt.Fprintf(fc, cProlog)
 	}
@@ -120,6 +120,9 @@ func (p *Package) writeDefs() {
 		if n.Type.Go != nil && n.Type.Go.Obj != nil && n.Type.Go.Obj.Kind == ast.Typ {
 			node = &ast.StarExpr{X: n.Type.Go}
 		} else if n.Kind == "fpvar" {
 			node = n.Type.Go
+			if *gccgo {
+				amp = "&"
+			}
 		} else {
 			panic(fmt.Errorf("invalid var kind %q", n.Kind))
 		}
@@ -380,11 +383,7 @@ func (p *Package) writeDefsFunc(fc, fgo2 *os.File, n *Name) {
 		fmt.Fprint(fgo2, "}\n")
 
 		// declare the C function.
-		if inProlog {
-			fmt.Fprintf(fgo2, "//extern %s\n", n.C)
-		} else {
-			fmt.Fprintf(fgo2, "//extern _cgo%s%s\n", cPrefix, n.Mangle)
-		}
+		fmt.Fprintf(fgo2, "//extern _cgo%s%s\n", cPrefix, n.Mangle)
 		d.Name = ast.NewIdent(cname)
 		if n.AddError {
 			l := d.Type.Results.List
@@ -1193,8 +1192,13 @@ void
 }
 `
 
+func (p *Package) cPrologGccgo() string {
+	return strings.Replace(cPrologGccgo, "PREFIX", cPrefix, -1)
+}
+
 const cPrologGccgo = `
 #include <stdint.h>
+#include <stdlib.h>
 #include <string.h>
 
 typedef unsigned char byte;
@@ -1214,26 +1218,26 @@ typedef struct __go_open_array {
 struct __go_string __go_byte_array_to_string(const void* p, intgo len);\nstruct __go_open_array __go_string_to_byte_array (struct __go_string str);\n \n-const char *CString(struct __go_string s) {\n+const char *_cgoPREFIX_Cfunc_CString(struct __go_string s) {\n \treturn strndup((const char*)s.__data, s.__length);\n }\n \n-struct __go_string GoString(char *p) {\n+struct __go_string _cgoPREFIX_Cfunc_GoString(char *p) {\n \tintgo len = (p != NULL) ? strlen(p) : 0;\n \treturn __go_byte_array_to_string(p, len);\n }\n \n-struct __go_string GoStringN(char *p, int32_t n) {\n+struct __go_string _cgoPREFIX_Cfunc_GoStringN(char *p, int32_t n) {\n \treturn __go_byte_array_to_string(p, n);\n }\n \n-Slice GoBytes(char *p, int32_t n) {\n+Slice _cgoPREFIX_Cfunc_GoBytes(char *p, int32_t n) {\n \tstruct __go_string s = { (const unsigned char *)p, n };\n \treturn __go_string_to_byte_array(s);\n }\n \n-extern void runtime_throw(const char *):\n-void *Cmalloc(size_t n) {\n+extern void runtime_throw(const char *);\n+void *_cgoPREFIX_Cfunc__CMalloc(size_t n) {\n         void *p = malloc(n);\n         if(p == NULL && n == 0)\n                 p = malloc(1);\n```

## コアとなるコードの解説

### `func (p *Package) writeDefs()` 内の変更

-   **`fmt.Fprintf(fc, p.cPrologGccgo())`**:
    `gccgo`を使用する場合、以前はハードコードされた`cPrologGccgo`定数を出力していましたが、この変更により`p.cPrologGccgo()`メソッドを呼び出すようになりました。このメソッドは、`cgo`が使用する現在のシンボルプレフィックス(`cPrefix`)を埋め込んだ動的なプロローグ文字列を生成します。これにより、`gccgo`が期待する正確なシンボル名がプロローグ関数に適用されるようになります。

-   **関数ポインタ変数 (`n.Kind == "fpvar"`) の扱い**:
    `n.Kind == "fpvar"`(関数ポインタ変数)の場合、`gccgo`が有効な場合にのみ`amp = "&"`が設定されるようになりました。これは、GoからCの関数ポインタ変数を参照する際に、`gccgo`がその変数のアドレスを明示的に必要とするためと考えられます。これにより、GoとCの間で関数ポインタが正しく受け渡されるようになります。

### `func (p *Package) writeDefsFunc(fc, fgo2 *os.File, n *Name)` 内の変更

-   **`//extern` ディレクティブの統一**:
    C関数を宣言する`//extern`ディレクティブの生成ロジックが簡素化されました。以前は`inProlog`というフラグに基づいて異なる形式を使用していましたが、変更後は常に`fmt.Fprintf(fgo2, "//extern _cgo%s%s\n", cPrefix, n.Mangle)`という形式を使用するようになりました。これにより、`cgo`が生成するすべてのC関数に対して、`_cgo`と`cPrefix`を組み合わせた一貫したシンボルプレフィックスが適用され、`gccgo`を含むすべてのコンパイラでシンボル解決がより堅牢になります。

### 新しいメソッド `func (p *Package) cPrologGccgo() string` の追加

-   この新しいメソッドは、`cPrologGccgo`という定数文字列(テンプレート)内の`"PREFIX"`というプレースホルダーを、現在の`cPrefix`の値で置き換えた文字列を返します。これにより、`cPrologGccgo`内で定義されているC関数(`CString`, `GoString`, `GoStringN`, `GoBytes`, `Cmalloc`)の名前が、`_cgo`と`cPrefix`を含む形式(例: `_cgo_myprefix_Cfunc_CString`)に動的に変更されます。これは、`gccgo`がこれらのプロローグ関数を正しくリンクするために必要なシンボル命名規則に合わせるための重要な変更です。

### `const cPrologGccgo` の変更

-   **`#include <stdlib.h>` の追加**:
    `cPrologGccgo`の定義に`#include <stdlib.h>`が追加されました。これは、`Cmalloc`関数内で使用される標準Cライブラリの`malloc`関数がこのヘッダファイルで宣言されているため、`gccgo`でコンパイルする際に必要なインクルードです。

-   **プロローグ関数の名前変更**:
    `cPrologGccgo`内で定義されているGoとCの間の型変換やメモリ管理を担うプロローグ関数(`CString`, `GoString`, `GoStringN`, `GoBytes`, `Cmalloc`)の名前が、`_cgoPREFIX_Cfunc_`というプレフィックスを含む形式に変更されました。例えば、`CString`は`_cgoPREFIX_Cfunc_CString`に、`Cmalloc`は`_cgoPREFIX_Cfunc__CMalloc`に変更されています。この`PREFIX`部分は、前述の`p.cPrologGccgo()`メソッドによって実際の`cPrefix`に置き換えられます。これにより、`gccgo`がこれらのシンボルを正しく解決できるようになります。

## 関連リンク

-   Go言語の`cgo`に関する公式ドキュメント: [https://pkg.go.dev/cmd/cgo](https://pkg.go.dev/cmd/cgo)
-   `gccgo`に関する情報: [https://gcc.gnu.org/onlinedocs/gccgo/](https://gcc.gnu.org/onlinedocs/gccgo/)

## 参考にした情報源リンク

-   Go言語のソースコード(特に`cmd/cgo`ディレクトリ)
-   GCCのドキュメント
-   Go言語のIssueトラッカーやメーリングリスト(当時の議論を追うことで、より詳細な背景がわかる可能性がありますが、このコミット単体では直接的なリンクは提供されていません。)
-   Go言語のコミット履歴: [https://github.com/golang/go/commits/master](https://github.com/golang/go/commits/master)
-   Go CL 13878043: [https://golang.org/cl/13878043](https://golang.org/cl/13878043) (コミットメッセージに記載されているChange-ID)