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

[インデックス 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の間の相互運用性を提供します。

しかし、gcgccgoはそれぞれ異なるABI(Application Binary Interface)やランタイムの特性を持つことがあります。特に、Goの文字列(string)やスライス([]byteなど)といった複合型がC言語側でどのように表現されるか、またGoランタイムとの連携方法が異なります。

このコミットが行われた2011年当時、cgoは主にgcコンパイラ向けに設計されていました。gccgocgoを利用するためには、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)

アプリケーションバイナリインターフェースは、コンパイルされたプログラム(バイナリ)が、オペレーティングシステムや他のライブラリとどのようにやり取りするかを定義する一連の規約です。これには、関数呼び出しの際に引数がどのようにレジスタやスタックに配置されるか、戻り値がどのように返されるか、データ構造がメモリ上でどのように配置されるかなどが含まれます。異なるコンパイラ(例: gcgccgo)が同じソースコードをコンパイルしても、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とは異なるか、あるいは全く不要となる場合があります。

技術的詳細

このコミットの主要な目的は、cgogccgoコンパイラと連携できるようにすることです。そのために、以下の技術的な変更が導入されています。

  1. gccgoフラグの導入: cgoコマンドラインツールに-gccgoという新しいフラグが追加されました。このフラグが指定されると、cgogccgoに特化したコード生成モードで動作します。

  2. runtime/cgoインポートの条件化: 標準のGoコンパイラ(gc)では、cgoが生成するGoファイルは通常、import _ "runtime/cgo"という行を含みます。これは、runtime/cgoパッケージがGoとCの間の低レベルな連携(例: スケジューラの管理、スタックの切り替え)を処理するために必要だからです。 しかし、gccgoはGoランタイムの異なる実装を持つため、runtime/cgoのインポートが不要であるか、あるいは異なる方法で連携を処理する可能性があります。このコミットでは、-gccgoフラグが指定された場合、このruntime/cgoのインポート行が生成されないように変更されました。これは、gccgoが独自のCGOランタイムメカニズムを持っていることを示唆しています。

  3. 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ファイルに挿入するようになりました。

  4. __asm__ディレクティブの追加: cgoがGoの関数をCから呼び出せるようにするためのラッパー関数を生成する際、gccgoモードでは、生成されるGoコードに__asm__("symbol_name")というディレクティブが追加されるようになりました。これは、Goの関数シンボルをC側から参照できるように、gccgoが期待する特定のリンケージ名(アセンブリ名)を明示的に指定するためのものです。これにより、gccgoコンパイラがGoの関数を正しくリンクできるようになります。

  5. ラッパー関数生成の最適化: 標準のcgoでは、GoからC関数を呼び出す際に、エラーハンドリングやGoランタイムとの連携のために、Go側でC関数を呼び出すためのラッパー関数が生成されます。 このコミットでは、gccgoモードの場合、C関数がエラーを返さない(n.AddErrorfalse)限り、Go側のラッパー関数を生成しないように変更されました。これは、gccgoがGoとCの間の関数呼び出しにおいて、gcよりも直接的なメカニズムを提供しており、不要なラッパーを省略することでオーバーヘッドを削減できる可能性を示唆しています。

これらの変更により、cgogccgoがGoの型をCでどのように表現し、C関数をどのように呼び出すかというABIの差異に対応できるようになり、gccgo環境下でのCGOの利用が可能になりました。

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

このコミットでは、主に以下の2つのファイルが変更されています。

  1. 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という新しいブール型フラグが追加されました。このフラグは、cgogccgoコンパイラで使用するためのファイルを生成するかどうかを制御します。
  2. 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の間の相互運用コードを実際に生成するロジックを含むファイルです。

  1. 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ランタイムメカニズムを持っていることを前提としています。

  2. 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互換実装を含んでいます。
  3. 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.AddErrorfalse)場合、Go側のラッパー関数生成をスキップします。これは、gccgoがGoとCの間の関数呼び出しにおいて、gcよりも直接的なメカニズムを提供しており、不要なラッパーを省略することでオーバーヘッドを削減できるためと考えられます。
  4. 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の間で安全かつ効率的にデータをやり取りできるようにします。

これらの変更全体として、cgogccgoコンパイラの特定の要件(ABI、ランタイム連携、シンボルリンケージ)に対応できるようになり、gccgo環境下でのCGOの利用を可能にしています。

関連リンク

参考にした情報源リンク