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

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

このコミットは、Go言語のcmd/cgoツールにおいて、C言語のvoid型を返す関数からerrno(エラー番号)にアクセスできるようにするための修正です。具体的には、C言語側でvoid関数がerrnoを設定した場合に、Go言語側でそのerrno値を正しく取得できるようにcgoの内部処理を改善しています。これにより、C言語のAPIがvoidを返しつつerrnoでエラーを示すようなケースでも、Go言語から適切にエラーハンドリングが可能になります。

コミット

  • コミットハッシュ: 1b18a6072e3af427b92e26e7f472fd6d0c6efc09
  • 作者: Shenghou Ma minux.ma@gmail.com
  • コミット日時: 2012年12月18日 火曜日 00:26:08 +0800

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

https://github.com/golang/go/commit/1b18a6072e3af427b92e26e7f472fd6d0c6efc09

元コミット内容

cmd/cgo: access errno from void C function
Fixes #3729.

R=rsc
CC=golang-dev
https://golang.org/cl/6938052

変更の背景

この変更は、Go言語のIssue 3729「cmd/cgo: access errno from void C function」を解決するために行われました。

Go言語のcgoツールは、GoプログラムからC言語のコードを呼び出すためのメカニズムを提供します。C言語の関数は、通常、エラーが発生した場合に特定の値を返すか、グローバル変数errnoを設定することでエラーを通知します。しかし、C言語には戻り値がvoid(何も返さない)である関数も存在し、これらの関数がエラーを通知する唯一の手段としてerrnoを使用する場合があります。

従来のcgoの実装では、voidを返すC関数をGoから呼び出した際に、その関数が設定したerrno値をGo側で適切に取得できないという問題がありました。Goの慣習では、C関数呼び出しの結果として(result, error)の2つの値を返すことが期待されます。void関数はGoの世界では[0]byte(空のバイト配列)とerrorを返すものとして扱われますが、errnoが正しくマッピングされないため、C側でエラーが発生してもGo側でそれを検知できないという不具合が生じていました。

このコミットは、このようなvoidを返すC関数からのerrnoアクセスを可能にし、GoプログラムがCライブラリとのより堅牢な連携を行えるようにすることを目的としています。

前提知識の解説

cgo

cgoは、Go言語のプログラムからC言語の関数を呼び出したり、C言語のコード内でGoの関数を使用したりするためのGoツールチェーンの一部です。cgoを使用することで、既存のCライブラリをGoプロジェクトに統合したり、パフォーマンスが重要な部分をCで記述したりすることが可能になります。

cgoは、Goのソースファイル内に特別なコメントブロック(import "C"の直前)でCコードを記述することで機能します。cgoはこれらのCコードをコンパイルし、GoとCの間でデータを変換し、関数呼び出しを仲介する接着コード(glue code)を生成します。

errno

errnoは、C言語の標準ライブラリで定義されているグローバル変数(またはスレッドローカル変数)です。システムコールやライブラリ関数がエラーを報告するために使用されます。関数が失敗した場合、errnoには特定のエラーを示す整数値が設定されます。例えば、EACCESはアクセス拒否、ENOENTはファイルやディレクトリが見つからないことを示します。

Go言語では、C関数がerrnoを設定した場合、cgoはそれをGoのerrorインターフェースに変換して返します。これにより、Goの慣用的なエラーハンドリング(if err != nil)でCのエラーを処理できます。

C言語のvoid関数

C言語において、voidを戻り値の型として持つ関数は、呼び出し元に値を返さないことを意味します。これらの関数は、副作用(例:グローバル変数の変更、ファイルへの書き込み、画面への出力)を実行するために使用されます。

例えば、void print_message(const char *msg)のような関数は、メッセージを画面に出力するだけで、呼び出し元に特定の値を返しません。しかし、このような関数でも内部でエラーが発生した場合、errnoを設定してエラーを通知することがあります。

Go言語におけるCエラーの扱い

cgoは、C関数が返す値とerrnoをGoの戻り値にマッピングします。通常、C関数が単一の値を返す場合、Go側では(C.ReturnType, error)のように2つの値を返します。errorerrnoが設定されている場合に非nilとなります。void関数については、Go側では(struct{}, error)または(byte, error)のように、何らかのダミー値とerrorを返すものとして扱われます。このコミットの修正前は、void関数の場合にerrnoが正しくGoのerrorにマッピングされない問題がありました。

技術的詳細

このコミットの核心は、cgoコンパイラ(src/cmd/cgo/gcc.go)がC言語のvoid型をどのように扱うかを変更し、void関数からのerrnoアクセスを可能にすることです。

cgoは、Cの型をGoの型にマッピングする際に、dwarf(Debugging With Arbitrary Record Formats)情報を使用して型情報を解析します。これまでの実装では、dwarf.VoidTypeがGoのvoid(Goのvoidは実際には存在しないが、cgo内部で概念的に使用される)にマッピングされていました。しかし、このマッピングでは、void関数がerrnoを設定した場合に、Go側でそのerrnoを適切に取得するためのメカニズムが不足していました。

修正前は、cgovoid関数からの戻り値がないため、call2(2つの戻り値を持つ関数呼び出し、つまり(result, error)形式)のコンテキストでvoid関数が呼び出された場合、戻り値の数が合わないとしてエラーを報告していました。これは、void関数がGoの世界でerrorを返す可能性があることを考慮していなかったためです。

このコミットでは、以下の主要な変更が導入されています。

  1. _Ctype_voidの導入: src/cmd/cgo/gcc.gotypeConv構造体にgoVoid ast.Exprフィールドが追加され、_Ctype_voidという識別子に初期化されます。これは、Cのvoid型をGoの世界で表現するための特別な型として機能します。これにより、cgovoid型を単なる「何もない」ものとしてではなく、errnoを伴う可能性のある戻り値のコンテキストで適切に扱えるようになります。
  2. dwarf.VoidTypeのマッピング変更: typeConv.Typeメソッド内で、dwarf.VoidTypeがGoのc.goVoid(つまり_Ctype_void)にマッピングされるようになりました。また、Align1に設定されています。これは、void型がメモリ上で1バイトのサイズを持つかのように扱われることを示唆しており、cgovoid関数からの戻り値を処理する際に、errnoを格納するためのスペースを確保するなどの内部的な調整を可能にします。
  3. FuncTypeの戻り値処理の改善: typeConv.FuncTypeメソッド内で、C関数の戻り値の型がdwarf.VoidTypeである場合、Goの戻り値としてc.goVoidを含むast.Fieldが生成されるようになりました。これにより、void関数がGoの世界で_Ctype_voiderrorの2つの戻り値を持つかのように扱われ、errnoがGoのerrorに正しくマッピングされる道が開かれました。
  4. rewriteRefの変更: src/cmd/cgo/gcc.gorewriteRef関数から、void関数がcall2コンテキストで呼び出された場合に発生していた「assignment count mismatch」のエラーチェックが削除されました。これは、上記の変更によりvoid関数がGoの世界で2つの戻り値を持つことが許容されるようになったため、このチェックが不要になったためです。

これらの変更により、cgovoidを返すC関数がerrnoを設定した場合でも、Go側でそのerrnoerrorとして取得できるようになります。

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

このコミットで変更された主要なファイルは以下の通りです。

  • misc/cgo/test/cgo_test.go: 新しいテストケースTest3729が追加され、issue3729.goで定義されたテスト関数が実行されるように更新されました。
  • misc/cgo/test/issue3729.go (新規): voidを返すC関数gと、引数を持つvoid関数g2を定義し、それぞれがerrnoを設定するテストケースです。Go側でC.g()C.g2()を呼び出し、返されたerrorが期待されるerrno値(syscall.E2BIGsyscall.EINVAL)と一致するかを検証します。このファイルはWindows以外のシステム向けです。
  • misc/cgo/test/issue3729w.go (新規): issue3729.goと同様のテストですが、Windowsシステム向けです。Windowsではerrnoの動作が異なるため、このテストはスキップされます。
  • misc/cgo/test/issue4417.go: このファイルは、このコミットとは直接関係のない変更で、ファイルの先頭にあった// runコメントが削除されました。これはおそらく、テスト実行方法の変更に伴うクリーンアップです。
  • src/cmd/cgo/gcc.go: cgoコンパイラの核心部分であり、このコミットの主要なロジック変更が含まれています。

コアとなるコードの解説

src/cmd/cgo/gcc.goの変更

このファイルはcgoコンパイラのバックエンドの一部であり、Cの型情報をGoの型に変換し、GoとCの間の呼び出し規約を処理します。

  1. typeConv構造体へのgoVoidフィールドの追加:

    type typeConv struct {
        // ... 既存のフィールド ...
        goVoid                                 ast.Expr // _Ctype_void, denotes C's void
    }
    

    goVoidは、Cのvoid型をGoの抽象構文木(AST)内で表現するためのast.Exprです。これは_Ctype_voidという識別子に初期化され、cgovoid型を特別な方法で扱うための内部的なマーカーとして機能します。

  2. typeConv.InitでのgoVoidの初期化:

    func (c *typeConv) Init(ptrSize, intSize int64) {
        // ... 既存の初期化 ...
        c.goVoid = c.Ident("_Ctype_void")
    }
    

    _Ctype_voidは、cgoが生成するGoコード内でCのvoid型を表すために使用される識別子です。

  3. typeConv.Typeでのdwarf.VoidTypeの処理:

    case *dwarf.VoidType:
        t.Go = c.goVoid
        t.C.Set("void")
        t.Align = 1
    

    dwarf.VoidType(DWARFデバッグ情報におけるvoid型)が検出された場合、対応するGoの型表現としてc.goVoid_Ctype_void)が設定されます。t.Align = 1は、void型がメモリ上で1バイトの境界にアラインされることを示唆しており、cgovoid関数からの戻り値を処理する際に、errnoを格納するための内部的な構造を適切に配置できるようにします。

  4. typeConv.FuncTypeでのdwarf.FuncTypeの戻り値処理の改善:

    var r *Type
    var gr []*ast.Field
    if _, ok := dtype.ReturnType.(*dwarf.VoidType); ok {
        gr = []*ast.Field{{Type: c.goVoid}}
    } else if dtype.ReturnType != nil {
        r = c.Type(dtype.ReturnType, pos)
        gr = []*ast.Field{{Type: r.Go}}
    }
    

    この変更は、C関数の戻り値の型がdwarf.VoidTypeである場合に、Goの戻り値のフィールドリストgr_Ctype_void型を持つフィールドを追加するようにします。これにより、void関数がGoの世界で_Ctype_voiderrorの2つの戻り値を持つかのように扱われ、errnoがGoのerrorに正しくマッピングされるようになります。

  5. rewriteRef関数からのエラーチェックの削除:

    // 変更前:
    // if r.Context == "call2" {
    //     if r.Name.FuncType.Result == nil {
    //         error_(r.Pos(), "assignment count mismatch: 2 = 0")
    //     }
    //     // ...
    // }
    
    // 変更後:
    // if r.Context == "call2" {
    //     // Invent new Name for the two-result function.
    //     n := f.Name["2"+r.Name.Go]
    //     // ...
    // }
    

    rewriteRef関数は、Goのコード内でC関数呼び出しをGoのセマンティクスに合うように書き換える役割を担っています。以前は、call2(2つの戻り値を持つGo関数呼び出し)のコンテキストでCのvoid関数が呼び出された場合、戻り値がないため「assignment count mismatch」エラーが発生していました。しかし、上記の型マッピングの変更により、void関数もGoの世界で_Ctype_voiderrorの2つの戻り値を持つように扱われるようになったため、このエラーチェックは不要となり削除されました。

テストファイルの追加

  • misc/cgo/test/issue3729.go: このファイルは、voidを返すC関数g()と、複数の引数を持つvoid関数g2()を定義しています。これらのC関数は、それぞれerrnoを設定します。Goのテスト関数test3729は、これらのC関数を呼び出し、返されたerrorが期待されるerrno値(syscall.E2BIGsyscall.EINVAL)と一致するかを検証します。これにより、cgovoid関数からのerrnoを正しくGoのerrorに変換していることを確認します。

  • misc/cgo/test/issue3729w.go: このファイルはWindows環境向けのテストです。Windowsではerrnoの動作がUnix系システムと異なるため、このテストは単にt.Log("skip errno test on Windows")と出力してテストをスキップします。これは、プラットフォーム間の差異を考慮したテスト戦略の一例です。

これらの変更とテストの追加により、cgovoidを返すC関数からのerrnoアクセスを正確に処理できるようになり、GoとCの相互運用性が向上しました。

関連リンク

参考にした情報源リンク