[インデックス 11902] ファイルの概要
このコミットは、Go言語のCgoツールにおいて、gccgo
コンパイラを使用する際にC関数呼び出しからのerrno
(エラー番号)の返却をサポートするための変更です。また、ビルド時のいくつかの警告を解消することも目的としています。具体的には、src/cmd/cgo/out.go
でgccgo
向けのerrno
取得ラッパー関数を導入し、src/cmd/go/build.go
でgccgo
使用時のビルドプロセスにおけるdynimport
の扱いとオブジェクトファイルの追加順序を調整しています。
コミット
commit f8f0a2bc7bbb587836312747600d0e084b30baef
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date: Tue Feb 14 20:23:45 2012 +0100
cgo: add support for returning errno with gccgo.
Also eliminate some warnings in builds.
R=golang-dev, fullung, iant, rsc
CC=golang-dev, remy
https://golang.org/cl/5650066
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f8f0a2bc7bbb587836312747600d0e084b30baef
元コミット内容
Cgoツールにおいて、gccgo
コンパイラでC関数を呼び出した際にerrno
を返す機能を追加します。また、ビルド時に発生していたいくつかの警告を解消します。
変更の背景
Go言語のCgoは、GoプログラムからC言語のコードを呼び出すためのメカニズムを提供します。C言語のシステムコールやライブラリ関数は、エラーが発生した場合にグローバル変数errno
にエラーコードを設定することが一般的です。GoプログラムがC関数を呼び出す際、このerrno
の値を適切に取得し、Goのエラーとして扱うことが重要になります。
このコミット以前は、標準のGoコンパイラ(gc
)ではerrno
の取得がサポートされていましたが、gccgo
(GCCをバックエンドとするGoコンパイラ)ではそのサポートが不十分であったと考えられます。特に、gccgo
はcgocall
というGoランタイムのCgo呼び出しメカニズムを直接サポートしていなかったため、errno
の取得に特別な対応が必要でした。
また、既存のコードベースには、strndup
や構造体フィールドの型キャストに関する警告が存在しており、これらを解消することも変更の背景にあります。issue/2601
は、go/build
における.syso
ファイルの扱いに関連するもので、このコミットではdynimport
の処理順序の変更によって、その問題への対応も行われています。
前提知識の解説
- Cgo: Go言語の標準パッケージの一つで、GoプログラムからC言語の関数を呼び出したり、C言語のデータ構造を扱ったりするためのツールです。Cgoは、GoとCの間のインターフェースコードを生成し、Goの関数呼び出しをCの関数呼び出しに変換します。
errno
: C言語の標準ライブラリで定義されているグローバル変数です。システムコールや一部のライブラリ関数がエラーを返した場合に、そのエラーの種類を示す整数値が格納されます。errno
の値は、perror()
関数やstrerror()
関数などを用いて人間が読めるエラーメッセージに変換できます。Go言語では、syscall
パッケージを通じてerrno
の値にアクセスできます。gccgo
: Go言語のもう一つのコンパイラ実装です。標準のGoコンパイラ(gc
)がGo言語で書かれているのに対し、gccgo
はGCC(GNU Compiler Collection)のフロントエンドとして実装されており、GCCの最適化やターゲットプラットフォームのサポートを利用できます。syscall.GetErrno()
: Go言語のsyscall
パッケージに含まれる関数で、現在のスレッドのerrno
の値をGoのエラー型として取得します。CgoでC関数を呼び出した後、この関数を使ってC側で設定されたerrno
を取得することが一般的です。dynimport
: Cgoが動的リンクライブラリからシンボルをインポートする際に使用するメカニズムです。特に、GoプログラムがCライブラリの関数を呼び出す場合、その関数が動的にリンクされることを示す情報が必要になります。
技術的詳細
このコミットの主要な技術的変更点は以下の通りです。
-
gccgo
向けerrno
取得ラッパーの導入 (src/cmd/cgo/out.go
):gccgo
は標準のGoコンパイラ(gc
)が使用するcgocall
メカニズムを直接サポートしていません。そのため、Cgoで生成されるGoコードがC関数を呼び出し、その結果としてerrno
を取得する必要がある場合、特別なラッパー関数が必要になります。- このコミットでは、
n.AddError
がtrue
(つまり、errno
を返す必要があるC関数)の場合に、gccgo
向けに小さなGoラッパー関数を生成するように変更されています。 - このラッパー関数は、元のC関数を呼び出し、その戻り値と
syscall.GetErrno()
の戻り値をGoのタプルとして返します。これにより、Go側でC関数の結果とerrno
の両方を一度に受け取れるようになります。 - 生成されるC関数は、
_cgo
プレフィックスを持つ新しい名前(例:_cgo_Cfunc_my_c_function
)で宣言され、元のC関数名(例:my_c_function
)は//extern
ディレクティブで外部シンボルとして宣言されます。 __asm__
ディレクティブは、Goの関数がCの関数に直接フックされることを示しており、gccgo
がcgocall
を介さずにC関数を呼び出すためのメカニズムです。
-
gccgo
使用時のdynimport
処理の変更 (src/cmd/go/build.go
):src/cmd/go/build.go
はGoのビルドプロセスを制御するファイルです。Cgoを使用する場合、Cgoが生成するオブジェクトファイルや動的インポートに関する情報がビルドプロセスに渡されます。- このコミットでは、
gccgo
を使用している場合(buildToolchain.(gccgoToolchain)
がtrue
の場合)、dynimport
の処理をスキップするように変更されています。これは、gccgo
がdynimport
のメカニズムを必要としないためと考えられます。 - また、
outObj
(出力オブジェクトファイルのリスト)にimportObj
(動的インポートに関するオブジェクトファイル)を追加する際に、outObj = append([]string{importObj}, outObj...)
という形で、importObj
をリストの先頭に追加するように変更されています。これは、Windows環境など特定のプラットフォームで、gcc
が生成したオブジェクトファイルよりも前にcgo
が生成したオブジェクトファイルを処理する必要があるという、issue/2601
で言及された問題への対応です。
-
型キャストの追加と警告の解消 (
src/cmd/cgo/out.go
):CString
関数とGoBytes
関数において、strndup
の引数や構造体フィールドの初期化時に明示的な型キャスト((const char*)
や(const unsigned char *)
)が追加されています。これにより、ポインタ型の不一致によるコンパイラの警告が解消されます。
これらの変更により、gccgo
を使用するGoプログラムでもCgoを介してC関数のerrno
を正確に取得できるようになり、ビルド時の警告が減少し、特定のプラットフォームでのビルドの安定性が向上しています。
コアとなるコードの変更箇所
src/cmd/cgo/out.go
:writeDefsFunc
関数内で、*gccgo
がtrue
かつn.AddError
がtrue
の場合に、errno
を取得するためのGoラッパー関数を生成するロジックが追加されました。writeOutputFunc
関数内で、*gccgo
がtrue
の場合にラッパーを使用しないように早期リターンするロジックが追加されました。CString
関数とGoBytes
関数で、引数と構造体フィールドの初期化に明示的な型キャストが追加されました。
src/cmd/go/build.go
:cgo
関数内で、gccgoToolchain
を使用している場合にdynimport
の処理をスキップする条件が追加されました。importObj
をoutObj
リストの先頭に追加するロジックが変更されました。
コアとなるコードの解説
src/cmd/cgo/out.go
の変更
func (p *Package) writeDefsFunc(fc, fgo2 *os.File, n *Name) {
// ... (既存のコード) ...
if *gccgo {
// Gccgo style hooks.
// we hook directly into C. gccgo goes not support cgocall yet.
if !n.AddError {
fmt.Fprintf(fgo2, "//extern %s\n", n.C)
conf.Fprint(fgo2, fset, d)
fmt.Fprint(fgo2, "\n")
} else {
// write a small wrapper to retrieve errno.
cname := fmt.Sprintf("_cgo%s%s", cPrefix, n.Mangle)
paramnames := []string(nil)
for i, param := range d.Type.Params.List {
paramName := fmt.Sprintf("p%d", i)
param.Names = []*ast.Ident{ast.NewIdent(paramName)}
paramnames = append(paramnames, paramName)
}
conf.Fprint(fgo2, fset, d) // Original function declaration
fmt.Fprintf(fgo2, "{\n")
fmt.Fprintf(fgo2, "\tr := %s(%s)\n", cname, strings.Join(paramnames, ", "))
fmt.Fprintf(fgo2, "\treturn r, syscall.GetErrno()\n")
fmt.Fprintf(fgo2, "}\n")
// declare the C function.
fmt.Fprintf(fgo2, "//extern %s\n", n.C)
d.Name = ast.NewIdent(cname) // Rename the C function for the wrapper
l := d.Type.Results.List
d.Type.Results.List = l[:len(l)-1] // Remove the last return type (which was errno)
conf.Fprint(fgo2, fset, d)
fmt.Fprint(fgo2, "\n")
}
return
}
conf.Fprint(fgo2, fset, d)
fmt.Fprint(fgo2, "\n")
// ... (既存のコード) ...
}
func (p *Package) writeOutputFunc(fgcc *os.File, n *Name) {
// ... (既存のコード) ...
if *gccgo {
// we don't use wrappers with gccgo.
return
}
// ... (既存のコード) ...
}
// CString, GoBytes関数の型キャストの追加
const char *CString(struct __go_string s) {
return strndup((const char*)s.__data, s.__length);
}
Slice GoBytes(char *p, int n) {
struct __go_string s = { (const unsigned char *)p, n };
return __go_string_to_byte_array(s);
}
writeDefsFunc
内の変更は、gccgo
がcgocall
をサポートしないため、errno
を返すC関数に対してGoのラッパー関数を生成する部分です。n.AddError
がtrue
の場合、元のC関数名に_cgo
プレフィックスを付けた新しいC関数名(cname
)を定義し、Go側でそのcname
を呼び出し、その結果とsyscall.GetErrno()
の戻り値をGoのタプルとして返すラッパー関数を生成します。これにより、Go側でC関数の戻り値とerrno
の両方を透過的に扱えるようになります。
writeOutputFunc
の変更は、gccgo
の場合にはCgoが生成するラッパー関数を使用しないように早期リターンするものです。これは、gccgo
が直接C関数にフックするため、追加のラッパーが不要になるためです。
CString
とGoBytes
関数における型キャストの追加は、コンパイラの警告を解消するためのものです。strndup
の第一引数はconst char *
を期待し、__go_string
の__data
フィールドはvoid *
であるため、明示的なキャストが必要です。同様に、GoBytes
では__go_string
の__data
フィールドがconst unsigned char *
を期待するため、キャストが追加されています。
src/cmd/go/build.go
の変更
func (b *builder) cgo(p *Package, cgoExe, obj string, gccfiles []string) (outGo, outObj []string, err error) {
// ... (既存のコード) ...
if _, ok := buildToolchain.(gccgoToolchain); ok {
// we don't use dynimport when using gccgo.
return outGo, outObj, nil
}
// cgo -dynimport
importC := obj + "_cgo_import.c"
if err := b.run(p.Dir, p.ImportPath, cgoExe, "-objdir", obj, "-dynimport", dynobj, "-dynout", importC); err != nil {
return nil, nil, err
}
// NOTE(rsc): The importObj is a 5c/6c/8c object and on Windows
// must be processed before the gcc-generated objects.
// Put it first. http://golang.org/issue/2601
outObj = append([]string{importObj}, outObj...)
return outGo, outObj, nil
}
この変更は、gccgo
を使用している場合にdynimport
の処理をスキップする条件を追加しています。gccgo
はdynimport
のメカニズムを必要としないため、この処理を省略することでビルドプロセスを最適化します。
また、outObj = append([]string{importObj}, outObj...)
という行は、importObj
をoutObj
リストの先頭に挿入しています。これは、Windowsなどの特定の環境で、cgo
が生成した動的インポート関連のオブジェクトファイルが、gcc
が生成した他のオブジェクトファイルよりも先にリンカに渡される必要があるという問題(issue/2601
で言及)に対応するためのものです。これにより、リンカが正しい順序でオブジェクトファイルを処理し、リンクエラーを防ぎます。
関連リンク
- https://golang.org/cl/5650066 (このコミットのGo Code Review)