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

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

このコミットは、Go言語のCgoツールにおいて、gccgoコンパイラを使用する際にC関数呼び出しからのerrno(エラー番号)の返却をサポートするための変更です。また、ビルド時のいくつかの警告を解消することも目的としています。具体的には、src/cmd/cgo/out.gogccgo向けのerrno取得ラッパー関数を導入し、src/cmd/go/build.gogccgo使用時のビルドプロセスにおける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コンパイラ)ではそのサポートが不十分であったと考えられます。特に、gccgocgocallという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ライブラリの関数を呼び出す場合、その関数が動的にリンクされることを示す情報が必要になります。

技術的詳細

このコミットの主要な技術的変更点は以下の通りです。

  1. gccgo向けerrno取得ラッパーの導入 (src/cmd/cgo/out.go):

    • gccgoは標準のGoコンパイラ(gc)が使用するcgocallメカニズムを直接サポートしていません。そのため、Cgoで生成されるGoコードがC関数を呼び出し、その結果としてerrnoを取得する必要がある場合、特別なラッパー関数が必要になります。
    • このコミットでは、n.AddErrortrue(つまり、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の関数に直接フックされることを示しており、gccgocgocallを介さずにC関数を呼び出すためのメカニズムです。
  2. gccgo使用時のdynimport処理の変更 (src/cmd/go/build.go):

    • src/cmd/go/build.goはGoのビルドプロセスを制御するファイルです。Cgoを使用する場合、Cgoが生成するオブジェクトファイルや動的インポートに関する情報がビルドプロセスに渡されます。
    • このコミットでは、gccgoを使用している場合(buildToolchain.(gccgoToolchain)trueの場合)、dynimportの処理をスキップするように変更されています。これは、gccgodynimportのメカニズムを必要としないためと考えられます。
    • また、outObj(出力オブジェクトファイルのリスト)にimportObj(動的インポートに関するオブジェクトファイル)を追加する際に、outObj = append([]string{importObj}, outObj...)という形で、importObjをリストの先頭に追加するように変更されています。これは、Windows環境など特定のプラットフォームで、gccが生成したオブジェクトファイルよりも前にcgoが生成したオブジェクトファイルを処理する必要があるという、issue/2601で言及された問題への対応です。
  3. 型キャストの追加と警告の解消 (src/cmd/cgo/out.go):

    • CString関数とGoBytes関数において、strndupの引数や構造体フィールドの初期化時に明示的な型キャスト((const char*)(const unsigned char *))が追加されています。これにより、ポインタ型の不一致によるコンパイラの警告が解消されます。

これらの変更により、gccgoを使用するGoプログラムでもCgoを介してC関数のerrnoを正確に取得できるようになり、ビルド時の警告が減少し、特定のプラットフォームでのビルドの安定性が向上しています。

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

  • src/cmd/cgo/out.go:
    • writeDefsFunc関数内で、*gccgotrueかつn.AddErrortrueの場合に、errnoを取得するためのGoラッパー関数を生成するロジックが追加されました。
    • writeOutputFunc関数内で、*gccgotrueの場合にラッパーを使用しないように早期リターンするロジックが追加されました。
    • CString関数とGoBytes関数で、引数と構造体フィールドの初期化に明示的な型キャストが追加されました。
  • src/cmd/go/build.go:
    • cgo関数内で、gccgoToolchainを使用している場合にdynimportの処理をスキップする条件が追加されました。
    • importObjoutObjリストの先頭に追加するロジックが変更されました。

コアとなるコードの解説

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内の変更は、gccgocgocallをサポートしないため、errnoを返すC関数に対してGoのラッパー関数を生成する部分です。n.AddErrortrueの場合、元のC関数名に_cgoプレフィックスを付けた新しいC関数名(cname)を定義し、Go側でそのcnameを呼び出し、その結果とsyscall.GetErrno()の戻り値をGoのタプルとして返すラッパー関数を生成します。これにより、Go側でC関数の戻り値とerrnoの両方を透過的に扱えるようになります。

writeOutputFuncの変更は、gccgoの場合にはCgoが生成するラッパー関数を使用しないように早期リターンするものです。これは、gccgoが直接C関数にフックするため、追加のラッパーが不要になるためです。

CStringGoBytes関数における型キャストの追加は、コンパイラの警告を解消するためのものです。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の処理をスキップする条件を追加しています。gccgodynimportのメカニズムを必要としないため、この処理を省略することでビルドプロセスを最適化します。

また、outObj = append([]string{importObj}, outObj...)という行は、importObjoutObjリストの先頭に挿入しています。これは、Windowsなどの特定の環境で、cgoが生成した動的インポート関連のオブジェクトファイルが、gccが生成した他のオブジェクトファイルよりも先にリンカに渡される必要があるという問題(issue/2601で言及)に対応するためのものです。これにより、リンカが正しい順序でオブジェクトファイルを処理し、リンクエラーを防ぎます。

関連リンク

参考にした情報源リンク