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

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

このコミットは、Go言語のcmd/cgoパッケージ内のgcc.goファイルに対する変更です。cgoはGoプログラムからCコードを呼び出すためのツールであり、gcc.goはその中でCコンパイラ(通常はGCCまたはClang)との連携を処理する部分を担っています。具体的には、Cコードのコンパイル、Cの型やシンボルのGoへのマッピング、および関連するエラーハンドリングが含まれます。

コミット

cmd/cgo: don't say "gcc produced no output" if we ran clang

R=golang-dev, iant CC=golang-dev https://golang.org/cl/13420048

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

https://github.com/golang/go/commit/08b26e41043281fd85e4af6ac0c6e790f3336a82

元コミット内容

cmd/cgo: don't say "gcc produced no output" if we ran clang

変更の背景

このコミットの主な背景は、cgoツールがCコードをコンパイルする際に、使用しているCコンパイラがGCCではなくClangである場合に、誤解を招くエラーメッセージを出力していた問題の修正です。

従来のcgoは、Cコンパイラが何も出力しなかった場合に「gcc produced no output」というエラーメッセージを固定で表示していました。しかし、Goの開発環境ではClangが使用されることも多く、その際にこのメッセージが表示されると、ユーザーは実際にClangを使用しているにもかかわらず、GCCに関するエラーであるかのように誤解する可能性がありました。この変更は、エラーメッセージをより正確にし、実際に使用されたコンパイラの名前(GCCまたはClangなど)を動的に表示することで、ユーザーの混乱を避けることを目的としています。

また、このコミットには、Cシンボル名がエラーメッセージに表示される際のフォーマットを改善するための変更や、C.malloc関数の特殊な扱いに関する修正も含まれています。特にC.mallocについては、cgoがGoの多値戻り値(value, error)の形式でC関数をラップする際に、mallocのようなメモリ割り当て関数がGoの慣習的なエラーハンドリングパターンに適合しないため、特定の制約を設ける必要があったと考えられます。

前提知識の解説

cgo

cgoは、GoプログラムからC言語のコードを呼び出すためのGoツールチェーンの一部です。Goのソースファイル内にCコードを直接記述したり、既存のCライブラリをリンクしたりすることを可能にします。cgoは、GoとCの間のデータ型変換、関数呼び出し規約の調整、メモリ管理の橋渡しなど、複雑な処理を自動的に行います。

GCCとClang

  • GCC (GNU Compiler Collection): GNUプロジェクトによって開発された、C、C++、Objective-C、Fortran、Ada、Goなどの多くのプログラミング言語をサポートするコンパイラ群です。長年にわたりUnix系システムで広く使用されてきました。
  • Clang: LLVMプロジェクトの一部として開発されたC、C++、Objective-C、Objective-C++コンパイラです。GCCと比較して、より高速なコンパイル、より良いエラーメッセージ、よりモジュール化された設計が特徴とされています。macOSやiOSの開発では標準のコンパイラとして採用されています。

cgoは、システムにインストールされているCコンパイラ(通常はGCCまたはClang)を内部的に利用してCコードをコンパイルします。

GoとCの連携におけるエラーハンドリング

Goでは、関数がエラーを返す場合、慣習的に最後の戻り値としてerror型を返します(例: (result, error))。一方、C言語では、エラーは通常、戻り値(例: NULLポインタや特定の整数値)やグローバル変数(例: errno)を通じて通知されます。cgoは、これらの異なるエラーハンドリングメカニズムを橋渡ししようとしますが、すべてのC関数がGoの多値戻り値のパターンにきれいにマッピングできるわけではありません。

C.mallocの特殊性

C.mallocはC標準ライブラリの関数で、指定されたサイズのメモリブロックをヒープから割り当て、そのブロックへのポインタを返します。メモリ割り当てに失敗した場合はNULLポインタを返します。Goのcgoでは、Cの関数をGoから呼び出す際に、C.プレフィックスを付けて参照します(例: C.malloc)。mallocはメモリ割り当ての根幹をなす関数であり、そのエラーハンドリング(NULLチェック)はGoのerrorインターフェースとは異なるため、cgoがこれをGoの多値戻り値形式に自動的に変換する際に特別な考慮が必要となることがあります。

GoのASTとエラー報告

  • token.NoPos: Goのgo/tokenパッケージで定義されている定数で、ソースコード上の位置情報がないことを示します。エラーメッセージを生成する際に、特定のコード位置に関連付けられない場合に利用されます。
  • ast.Expr: Goのgo/astパッケージで定義されているインターフェースで、抽象構文木(AST)における式を表します。
  • ast.NewIdent: go/astパッケージの関数で、新しい識別子(変数名や関数名など)を表すASTノードを作成します。
  • fatalf: Goの標準的なエラー報告パターンで、フォーマットされた文字列をエラーとして出力し、プログラムを終了させます。
  • error_: cgo内部で定義されているエラー報告関数で、特定のコード位置(token.Pos)に関連付けられたエラーメッセージを出力します。

これらの要素は、cgoがCコードを解析し、Goコードを生成する過程で発生する様々な問題(型不一致、未定義のシンボルなど)をユーザーに報告するために使用されます。

技術的詳細

このコミットは、src/cmd/cgo/gcc.goファイルに対して複数の変更を加えています。

  1. 動的なコンパイラ名のエラーメッセージへの組み込み:

    • 変更前: fatalf("gcc produced no output\\non input:\\n%s", b.Bytes())
    • 変更後: fatalf("%s produced no output\\non input:\\n%s", p.gccBaseCmd()[0], b.Bytes())
    • この変更により、Cコンパイラが何も出力しなかった場合のエラーメッセージが改善されました。p.gccBaseCmd()[0]は、実際にcgoが使用したCコンパイラの実行ファイル名(例: gccまたはclang)を返します。これにより、エラーメッセージが「clang produced no output」のように動的に変化し、ユーザーがどのコンパイラで問題が発生したのかを正確に把握できるようになります。
  2. fixGo関数の導入と適用:

    • 複数のエラーメッセージ出力箇所で、CシンボルのGo名(n.Gor.Name.Go)がfixGo()関数でラップされるようになりました。
    • 例: error_(token.NoPos, "could not determine kind of name for C.%s", fixGo(n.Go))
    • fixGo関数は、おそらくCのシンボル名をGoの文脈で表示する際に、より適切で読みやすい形式に整形するためのユーティリティ関数です。例えば、Cの識別子にはGoでは使用できない文字が含まれる場合や、Goの予約語と衝突する場合などに、適切なエスケープや変換を行うことで、エラーメッセージの可読性を向上させます。
  3. C.mallocの二値戻り値形式の明示的な禁止:

    • r.Context == "call2"(Goの二値戻り値形式でC関数を呼び出そうとしているコンテキスト)の場合に、C.mallocに対して特別なチェックが追加されました。
    • if r.Name.Go == "_CMalloc" { error_(r.Pos(), "no two-result form for C.malloc"); break }
    • _CMallocは、cgoが内部的にC.mallocをGoから呼び出すために生成するシンボル名であると推測されます。このコードは、GoのcgoC.mallocvalue, errorのような二値戻り値形式でラップすることを明示的に禁止しています。これは、mallocがエラー時にNULLを返すというCの慣習が、Goのerrorインターフェースとは直接的に一致しないため、cgomallocの呼び出しをGoの多値戻り値として安全に変換できない、あるいは変換すべきではないという設計判断に基づいていると考えられます。ユーザーはC.mallocを呼び出す際に、Goのerrorではなく、Cの戻り値(ポインタ)を直接チェックしてNULLかどうかを判断する必要があります。

これらの変更は、cgoのエラー報告の精度とユーザーフレンドリーさを向上させるとともに、C.mallocのような特定のC標準ライブラリ関数のGoからの利用方法に関するcgoの内部的な設計原則を明確にするものです。

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

src/cmd/cgo/gcc.goファイルにおける主要な変更箇所は以下の通りです。

  1. 行 311-312:
    -	fatalf("gcc produced no output\\non input:\\n%s", b.Bytes())
    +	fatalf("%s produced no output\\non input:\\n%s", p.gccBaseCmd()[0], b.Bytes())
    
  2. 行 383-384:
    -	error_(token.NoPos, "could not determine kind of name for C.%s", n.Go)
    +	error_(token.NoPos, "could not determine kind of name for C.%s", fixGo(n.Go))
    
  3. 行 593-594:
    -	error_(r.Pos(), "unable to find value of constant C.%s", r.Name.Go)
    +	error_(r.Pos(), "unable to find value of constant C.%s", fixGo(r.Name.Go))
    
  4. 行 604-605:
    -	error_(r.Pos(), "call of non-function C.%s", r.Name.Go)
    +	error_(r.Pos(), "call of non-function C.%s", fixGo(r.Name.Go))
    
  5. 行 608-612:
    +			if r.Name.Go == "_CMalloc" {
    +				error_(r.Pos(), "no two-result form for C.malloc")
    +				break
    +			}
    
  6. 行 649-650:
    -	error_(r.Pos(), "expression C.%s used as type", r.Name.Go)
    +	error_(r.Pos(), "expression C.%s used as type", fixGo(r.Name.Go))
    
  7. 行 653-654:
    -	error_(r.Pos(), "type C.%s: undefined C type '%s'", r.Name.Go, r.Name.C)
    +	error_(r.Pos(), "type C.%s: undefined C type '%s'", fixGo(r.Name.Go), r.Name.C)
    
  8. 行 657-658:
    -	error_(r.Pos(), "must call C.%s", r.Name.Go)
    +	error_(r.Pos(), "must call C.%s", fixGo(r.Name.Go))
    

コアとなるコードの解説

1. 動的なコンパイラ名のエラーメッセージへの組み込み

// Old: fatalf("gcc produced no output\\non input:\\n%s", b.Bytes())
// New: fatalf("%s produced no output\\non input:\\n%s", p.gccBaseCmd()[0], b.Bytes())

この変更は、cgoがCコンパイラを実行した際に、そのコンパイラが予期せぬ出力をしなかった場合に発生するエラーメッセージを修正します。以前は「gcc produced no output」と固定で表示されていましたが、p.gccBaseCmd()[0]を使用することで、実際に実行されたコンパイラのコマンド名(例: clang)がメッセージに埋め込まれるようになります。これにより、ユーザーはどのコンパイラが問題を引き起こしたのかを正確に知ることができ、デバッグが容易になります。

2. fixGo関数の導入と適用

// Example: error_(token.NoPos, "could not determine kind of name for C.%s", fixGo(n.Go))

fixGo関数は、cgoがCのシンボル名をGoのコンテキストでエラーメッセージに表示する際に、その名前を適切に整形するために導入されました。Cの識別子には、Goの識別子としては無効な文字が含まれる場合や、Goの予約語と衝突する場合があります。fixGoはこれらのケースを処理し、エラーメッセージがより正確で理解しやすくなるように、Cシンボル名をGoの慣習に沿った形式に変換します。これにより、ユーザーはエラーメッセージに表示されたCシンボル名と、Goコード内で参照しているCシンボル名との対応関係をより明確に把握できます。

3. C.mallocの二値戻り値形式の明示的な禁止

if r.Name.Go == "_CMalloc" {
	error_(r.Pos(), "no two-result form for C.malloc")
	break
}

このコードブロックは、cgoC.malloc関数をGoの二値戻り値形式(例: (ptr, err))でラップしようとした場合に、明示的にエラーを発生させます。C.mallocはメモリ割り当てに失敗した場合にNULLポインタを返しますが、これはGoのerrorインターフェースとは異なるエラー通知メカニズムです。cgoの設計では、mallocのような特定のC関数については、Goの慣習的なエラーハンドリングパターンに無理に合わせるのではなく、Cの元のセマンティクスを維持することが適切であると判断されたため、このような制約が設けられました。したがって、GoからC.mallocを呼び出す際には、戻り値のポインタがnil(GoにおけるNULLに相当)であるかどうかを直接チェックしてエラーを処理する必要があります。

これらの変更は、cgoの堅牢性とユーザーエクスペリエンスを向上させるための重要な改善点であり、特にCとGoの間のインターフェースにおける微妙なセマンティクスの違いを適切に処理するためのものです。

関連リンク

参考にした情報源リンク