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

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

このコミットは、Go言語のCgoツールにおいて、gccgoコンパイラとの連携を強化するための変更を導入しています。具体的には、gccgo-fgo-pkgpathオプションに対応する-gccgopkgpathオプションをCgoに追加し、gccgoが生成するシンボル名との整合性を向上させています。これにより、Cgoで生成されたコードがgccgoでコンパイルされる際に、より正確なシンボル解決が可能になります。

コミット

commit b575a98121a60f2c74782130d639fa636d41e98a
Author: Ian Lance Taylor <iant@golang.org>
Date:   Fri Jul 20 16:58:08 2012 -0700

    cgo: add -gccgopkgpath option to match gccgo -fgo-pkgpath
    
    R=golang-dev, r, iant
    CC=golang-dev
    https://golang.org/cl/6416056

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

https://github.com/golang/go/commit/b575a98121a60f2c74782130d639fa636d41e98a

元コミット内容

cgo: add -gccgopkgpath option to match gccgo -fgo-pkgpath

変更の背景

Go言語には、公式コンパイラであるgc(Go Compiler)と、GCCのフロントエンドとしてGoをコンパイルするgccgoの二つの主要なコンパイラ実装が存在します。Cgoは、GoプログラムからC言語のコードを呼び出したり、C言語のコードからGoの関数を呼び出したりするためのツールです。

gccgoは、Goのパッケージパスに基づいて、エクスポートされたGoの関数や変数に特定のシンボル名を付与します。これは、CgoがGoとCの間のインターフェースを生成する際に、gccgoが期待するシンボル命名規則と一致させる必要があることを意味します。

以前のCgoは、gccgoのシンボル命名規則を完全にカバーしていませんでした。特に、gccgoが提供する-fgo-pkgpathオプション(Goのパッケージパスをシンボル名に含めるためのオプション)に対応するCgo側のメカニズムが不足していました。この不一致により、Cgoで生成されたコードをgccgoでコンパイルする際に、シンボル解決の問題が発生する可能性がありました。

このコミットは、Cgoがgccgo-fgo-pkgpathオプションによって生成されるシンボル名と正確に一致するように、Cgoに-gccgopkgpathオプションを追加し、内部のシンボル生成ロジックを調整することで、この問題を解決することを目的としています。これにより、gccgoを使用する開発者がCgoをよりスムーズに利用できるようになります。

前提知識の解説

Cgo

Cgoは、GoプログラムとC/C++プログラムの間で相互運用を可能にするGoのツールです。Goのコード内でCの関数を呼び出したり、CのコードからGoの関数を呼び出したりするために使用されます。Cgoは、GoとCの間のブリッジとなるコード(スタブ関数など)を生成します。

Gccgo

gccgoは、GCC(GNU Compiler Collection)の一部として実装されたGo言語のコンパイラです。Goの公式コンパイラであるgcとは異なる実装であり、GCCの最適化やバックエンドを利用できるという特徴があります。gccgoは、Goのソースコードをコンパイルして実行可能なバイナリを生成する際に、独自のシンボル命名規則を使用します。

シンボルとシンボル命名規則

プログラムにおいて、関数、変数、型などの識別子は「シンボル」として扱われます。コンパイラはこれらのシンボルを、リンカが参照できるように、特定の命名規則に従ってバイナリファイル内に記録します。異なるコンパイラや言語は、それぞれ独自のシンボル命名規則を持つことがあり、これらが異なると、異なる言語で書かれたモジュールをリンクする際に「未解決のシンボル」エラーなどの問題が発生することがあります。

gccgoのコンパイルオプション

  • -fgo-pkgpath: gccgoのコンパイルオプションの一つで、Goのパッケージパスをエクスポートされるシンボル名に含めるように指定します。これにより、異なるパッケージに同じ名前の関数や変数があっても、シンボル名が衝突するのを防ぎ、一意性を保証します。例えば、fmtパッケージのPrintln関数は、内部的にはgo.fmt.Printlnのようなシンボル名になることがあります。
  • -fgo-prefix: gccgoのコンパイルオプションの一つで、生成されるGoのシンボルに共通のプレフィックスを追加します。デフォルトではgoが使用されることが多いですが、このオプションで変更できます。

技術的詳細

このコミットの核心は、Cgoがgccgoのシンボル命名規則、特に-fgo-pkgpathオプションによって生成されるシンボル名に正確に合わせるためのロジック変更です。

gccgoは、Goの関数や変数をCから呼び出せるようにエクスポートする際、そのシンボル名にパッケージパスを含めることがあります。例えば、my/package/name.MyFunctionのような形式です。Cgoは、Goの関数をCから呼び出すためのCのスタブ関数を生成しますが、このスタブ関数が参照するGo側のシンボル名は、gccgoが実際に生成するシンボル名と一致している必要があります。

このコミット以前は、Cgoはgccgoのシンボル名を生成する際に、主に-gccgoprefixオプションとGoのパッケージ名に基づいていました。しかし、これはgccgo-fgo-pkgpathオプションが提供する、より詳細なパッケージパス情報を含んだシンボル名とは一致しませんでした。

新しい-gccgopkgpathオプションがCgoに追加されたことで、Cgoはgccgo-fgo-pkgpathオプションで指定された正確なパッケージパス情報を取得できるようになります。Cgoは、この情報を使用して、gccgoが期待する形式のシンボル名を生成します。

具体的には、CgoはエクスポートされるGoの関数シンボル名を生成する際に、以下の優先順位でプレフィックスを決定します。

  1. -gccgopkgpathオプションが指定されていれば、その値がシンボルプレフィックスとして使用されます。これは、gccgo-fgo-pkgpathと直接対応します。
  2. -gccgopkgpathが指定されていない場合、従来の-gccgoprefixオプションが考慮されます。
    • もし現在のGoパッケージがmainパッケージであり、かつ-gccgoprefixが指定されていない(または空文字列)場合、シンボルプレフィックスはmainとなります。
    • それ以外の場合、-gccgoprefixの値(デフォルトはgo)とGoのパッケージ名を結合したものがプレフィックスとなります(例: go.mypackage)。

この変更により、Cgoが生成するCのスタブ関数が、gccgoによってコンパイルされたGoの関数を正確なシンボル名で参照できるようになり、リンケージエラーを防ぎます。

また、シンボル名のフォーマットも変更されています。以前は%s.%s.%sgccgoSymbolPrefix.PackageName.FuncName)のような形式でしたが、新しいロジックではgccgoSymbolPrefix自体がパッケージパスを含むため、%s.%sgccgoSymbolPrefix.FuncName)という形式に変更され、冗長なPackageNameが削除されています。

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

src/cmd/cgo/main.go

--- a/src/cmd/cgo/main.go
+++ b/src/cmd/cgo/main.go
@@ -144,7 +144,8 @@ var cdefs = flag.Bool("cdefs", false, "for bootstrap: write C definitions for C
 var objDir = flag.String("objdir", "", "object directory")
 
 var gccgo = flag.Bool("gccgo", false, "generate files for use with gccgo")
-var gccgoprefix = flag.String("gccgoprefix", "go", "prefix of symbols generated by gccgo")
+var gccgoprefix = flag.String("gccgoprefix", "", "-fgo-prefix option used with gccgo")
+var gccgopkgpath = flag.String("gccgopkgpath", "", "-fgo-pkgpath option used with gccgo")
 var importRuntimeCgo = flag.Bool("import_runtime_cgo", true, "import runtime/cgo in generated code")
 var goarch, goos string
  • gccgoprefixフラグのデフォルト値が"go"から""に変更され、説明もgccgo-fgo-prefixオプションに対応することが明記されました。
  • 新しくgccgopkgpathフラグが追加されました。これはgccgo-fgo-pkgpathオプションに対応します。

src/cmd/cgo/out.go

--- a/src/cmd/cgo/out.go
+++ b/src/cmd/cgo/out.go
@@ -669,7 +669,21 @@ func (p *Package) writeGccgoExports(fgo2, fc, fm *os.File) {
 		}
 		return '_'
 	}
-	gccgoSymbolPrefix := strings.Map(clean, *gccgoprefix)
+
+	var gccgoSymbolPrefix string
+	if *gccgopkgpath != "" {
+		gccgoSymbolPrefix = strings.Map(clean, *gccgopkgpath)
+	} else {
+		if *gccgoprefix == "" && p.PackageName == "main" {
+			gccgoSymbolPrefix = "main"
+		} else {
+			prefix := strings.Map(clean, *gccgoprefix)
+			if prefix == "" {
+				prefix = "go"
+			}
+			gccgoSymbolPrefix = prefix + "." + p.PackageName
+		}
+	}
 
 	for _, exp := range p.ExpFunc {
 		// TODO: support functions with receivers.
@@ -707,7 +721,7 @@ func (p *Package) writeGccgoExports(fgo2, fc, fm *os.File) {
 
 		// The function name.
 		fmt.Fprintf(cdeclBuf, " "+exp.ExpName)
-		gccgoSymbol := fmt.Sprintf("%s.%s.%s", gccgoSymbolPrefix, p.PackageName, exp.Func.Name)
+		gccgoSymbol := fmt.Sprintf("%s.%s", gccgoSymbolPrefix, exp.Func.Name)
 		fmt.Fprintf(cdeclBuf, " (")
 		// Function parameters.
 		forFieldList(fntype.Params,
  • writeGccgoExports関数内で、gccgoSymbolPrefixの計算ロジックが大幅に変更されました。
    • *gccgopkgpathが空でなければ、その値が直接gccgoSymbolPrefixとして使用されます。
    • *gccgopkgpathが空の場合、従来の*gccgoprefixp.PackageNameに基づいてプレフィックスが生成されます。特に、mainパッケージの場合はmainが、それ以外の場合はprefix.PackageNameprefixのデフォルトはgo)が使用されます。
  • gccgoSymbolのフォーマット文字列が"%s.%s.%s"から"%s.%s"に変更されました。これにより、p.PackageNameがシンボル名に二重に含まれることがなくなりました。

コアとなるコードの解説

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

main.goでのフラグの追加と変更は、Cgoツールがコマンドライン引数として新しい情報を受け取れるようにするためのものです。

  • var gccgopkgpath = flag.String("gccgopkgpath", "", "-fgo-pkgpath option used with gccgo") この行は、Cgoの実行時に-gccgopkgpathという新しいコマンドラインオプションを定義しています。このオプションは文字列を受け取り、そのデフォルト値は空文字列です。この値は、gccgoコンパイラの-fgo-pkgpathオプションで指定されるパッケージパスと一致させることを意図しています。Cgoは、この情報を使って、gccgoが期待する正確なシンボル名を生成します。
  • var gccgoprefix = flag.String("gccgoprefix", "", "-fgo-prefix option used with gccgo") gccgoprefixフラグのデフォルト値が"go"から""に変更されました。これは、-gccgopkgpathが優先されるようになったため、gccgoprefixが指定されない場合の挙動をより柔軟にするためと考えられます。説明もgccgo-fgo-prefixオプションに対応することが明確にされました。

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

out.gowriteGccgoExports関数は、Goの関数をCから呼び出せるようにするためのエクスポートコード(Cの宣言やGoのスタブ関数など)を生成する役割を担っています。この関数内のgccgoSymbolPrefixの計算ロジックが、今回の変更の最も重要な部分です。

	var gccgoSymbolPrefix string
	if *gccgopkgpath != "" {
		gccgoSymbolPrefix = strings.Map(clean, *gccgopkgpath)
	} else {
		if *gccgoprefix == "" && p.PackageName == "main" {
			gccgoSymbolPrefix = "main"
		} else {
			prefix := strings.Map(clean, *gccgoprefix)
			if prefix == "" {
				prefix = "go"
			}
			gccgoSymbolPrefix = prefix + "." + p.PackageName
		}
	}

このコードブロックは、gccgoが生成するGoのシンボル名のプレフィックスを決定します。

  1. *gccgopkgpathの優先: 最も優先されるのは、新しく追加された-gccgopkgpathオプションの値です。もしこのオプションが指定されていれば(空文字列でなければ)、その値がそのままシンボルプレフィックスとして使用されます。これは、gccgo-fgo-pkgpathオプションが提供する、より正確なパッケージパス情報にCgoが直接対応できるようにするためです。strings.Map(clean, ...)は、シンボル名として有効な文字に変換するための処理です。
  2. *gccgoprefixmainパッケージの考慮: -gccgopkgpathが指定されていない場合、Cgoは従来の-gccgoprefixオプションと現在のGoパッケージ名(p.PackageName)に基づいてプレフィックスを生成します。
    • もし-gccgoprefixが空文字列であり、かつ現在のパッケージがmainパッケージである場合、シンボルプレフィックスは単純に"main"となります。これは、mainパッケージの実行可能ファイルにおけるシンボル命名の慣習に合わせたものです。
    • それ以外の場合、*gccgoprefixの値(もし空であればデフォルトの"go"が使用される)と現在のパッケージ名を.で結合したものがプレフィックスとなります(例: go.mypackage)。
		fmt.Fprintf(cdeclBuf, " "+exp.ExpName)
		gccgoSymbol := fmt.Sprintf("%s.%s", gccgoSymbolPrefix, exp.Func.Name)
		fmt.Fprintf(cdeclBuf, " (")

この部分では、実際にCの宣言内で使用されるGoのシンボル名gccgoSymbolが生成されます。以前は%s.%s.%sというフォーマットでgccgoSymbolPrefix, p.PackageName, exp.Func.Nameの3つの要素を結合していましたが、新しいロジックではgccgoSymbolPrefix自体が既にパッケージパス情報を含んでいる可能性があるため、p.PackageNameが冗長になります。そのため、フォーマットが"%s.%s"に変更され、gccgoSymbolPrefixexp.Func.Nameの2つの要素のみを結合するようになりました。これにより、生成されるシンボル名がgccgoの期待する形式と正確に一致するようになります。

関連リンク

参考にした情報源リンク