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

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

このコミットは、Go言語のcmd/cgoツールにおいて、gccgoコンパイラを使用する際に特定の#pragma宣言の出力を停止する変更を導入しています。これにより、gccgoが理解できない、または使用できない#pragma cgo_ldflagおよび#pragma cgo_import_static宣言が生成されるのを防ぎます。

コミット

commit c5c52f072b3d33f1a40ffda6d5334d38d0a11fa5
Author: Ian Lance Taylor <iant@golang.org>
Date:   Wed Jul 17 18:02:21 2013 -0700

    cmd/cgo: don't emit #pragma declarations when using gccgo
    
    Update #5905
    
    Don't emit #pragma cgo_ldflag and cgo_import_static
    declarations that gccgo doesn't understand and can't use.
    
    R=golang-dev, rsc, bradfitz
    CC=golang-dev
    https://golang.org/cl/11490043

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

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

元コミット内容

cmd/cgo: gccgo使用時に#pragma宣言を出力しない

#5905 を更新

gccgoが理解できず、使用できない#pragma cgo_ldflagおよびcgo_import_static宣言を出力しない。

変更の背景

Go言語は、C言語のコードをGoプログラムから呼び出すためのメカニズムとしてcgoを提供しています。cgoは、Goのソースコード内にCのコードを埋め込み、GoとCの間でデータを受け渡しするための橋渡しをします。このプロセスの一環として、cgoツールはCのコンパイラ(通常はGCCやClang)が処理するためのCソースファイルを生成します。

標準のGoコンパイラ(gc)と異なり、gccgoはGCCのフロントエンドとしてGo言語をサポートするコンパイラです。gccgoはGoのコードをGCCの内部表現に変換し、GCCのバックエンドを利用してネイティブコードを生成します。このため、gccgogcとは異なるビルドシステムや内部的な処理メカニズムを持っています。

このコミットの背景には、cgoが生成する特定の#pragma宣言がgccgoにとって問題であったという事実があります。具体的には、#pragma cgo_ldflag#pragma cgo_import_staticは、標準のGoツールチェーン(gcコンパイラと連携するcgo)がリンカに情報を渡したり、静的ライブラリのシンボルをインポートしたりするために使用するGo固有の拡張機能です。しかし、gccgoはこれらの#pragmaを認識せず、その結果、コンパイルエラーやリンカエラーを引き起こす可能性がありました。

この変更は、gccgoを使用している場合にこれらの互換性のない#pragma宣言の出力を抑制することで、cgogccgoの間の互換性を向上させることを目的としています。コミットメッセージにあるUpdate #5905は、この問題がGoのイシュートラッカーで報告され、このコミットがその解決策であることを示唆しています。

前提知識の解説

cgoとは

cgoは、GoプログラムからC言語のコードを呼び出すためのGoのツールです。Goのソースファイル内にimport "C"という特別なインポート文を記述し、その直後のコメントブロックにC言語のコードを記述することで、GoとCの相互運用を可能にします。cgoは、Goのビルドプロセス中にこれらのCコードを抽出し、Cコンパイラでコンパイルし、Goのコードとリンクするための接着コード("glue code")を生成します。

#pragmaとは

#pragmaは、C/C++言語のプリプロセッサディレクティブの一つで、コンパイラに対して特定の指示を与えるために使用されます。これは標準化された機能ではなく、コンパイラごとに独自の#pragmaが定義されていることが多く、特定のコンパイラやプラットフォームに依存する機能や最適化を制御するために使われます。cgoの場合、GoのビルドシステムとCコンパイラの間で情報をやり取りするために、Go独自の#pragmaが導入されています。

#pragma cgo_ldflag

#pragma cgo_ldflagは、cgoが生成するCコード内で使用される#pragmaディレクティブの一つです。これは、リンカに渡す追加のフラグ(例: -L/path/to/lib -lmylib)を指定するために使用されます。cgoは、Goのビルド時にこの情報を抽出し、最終的な実行可能ファイルをリンクする際にこれらのリンカフラグを適用します。これにより、GoプログラムがCライブラリにリンクできるようになります。

#pragma cgo_import_static

#pragma cgo_import_staticcgoが生成するCコード内で使用される#pragmaディレクティブです。これは、静的ライブラリから特定のシンボルをインポートする必要があることをリンカに指示するために使用されます。特に、GoのランタイムがCのコードから呼び出される場合や、CのコードがGoのランタイムによって提供される特定のシンボルにアクセスする必要がある場合に利用されます。これにより、静的リンクされたCライブラリのシンボルがGoの実行可能ファイルに正しく含まれるようになります。

gcとgccgoの違い

  • gc (Go Compiler): Goプロジェクトが公式に提供する標準のGoコンパイラです。Goのソースコードを直接機械語にコンパイルします。Goのツールチェーン(go buildなど)のデフォルトコンパイラです。
  • gccgo: GCC (GNU Compiler Collection) のフロントエンドとして実装されたGoコンパイラです。GoのソースコードをGCCの内部表現に変換し、GCCの最適化パスとバックエンドを利用して機械語を生成します。gccgoは、既存のGCCベースのビルドシステムやツールチェーンとの統合が容易であるという利点があります。しかし、gcとは異なるコード生成パスを持つため、Goの特定の機能やcgoの挙動において互換性の問題が生じることがあります。

このコミットは、gcgccgoの間のcgo関連の互換性の問題を解決するためのものです。

技術的詳細

このコミットの技術的詳細は、cgoツールがCソースファイルを生成する際の条件分岐にあります。cgoは、Goのソースコードを解析し、Cの関数呼び出しやCの型定義をGoの型にマッピングするためのCコードを生成します。この生成されたCコードには、Goのビルドシステムが利用する特定の#pragmaディレクティブが含まれることがあります。

具体的には、src/cmd/cgo/out.goファイル内のPackage構造体のwriteDefsメソッドが、Cの定義ファイル(_cgo_defun.cなど)を生成する際にこれらの#pragmaを出力します。

変更前は、LDFLAGS(リンカフラグ)が指定されている場合、常に#pragma cgo_ldflagが出力されていました。また、静的にインポートされるシンボル(cgo_import_static)についても、常に#pragma cgo_import_staticが出力されていました。

このコミットでは、これらの#pragmaの出力に!*gccgoという条件が追加されました。これは、cgoツールがgccgoコンパイラを使用しているかどうかを判断するためのフラグです。gccgoが使用されていない場合(つまり、標準のgcコンパイラが使用されている場合)にのみ、これらの#pragma宣言が出力されるようになります。

これにより、gccgoがこれらのGo固有の#pragmaを処理しようとして発生する可能性のあるコンパイルエラーや警告が回避されます。gccgoは独自のリンカフラグ処理やシンボル解決メカニズムを持っているため、これらの#pragmaは不要であり、むしろ問題を引き起こす可能性がありました。

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

変更はsrc/cmd/cgo/out.goファイルに集中しています。

--- a/src/cmd/cgo/out.go
+++ b/src/cmd/cgo/out.go
@@ -32,7 +32,7 @@ func (p *Package) writeDefs() {
 	fflg := creat(*objDir + "_cgo_flags")
 	for k, v := range p.CgoFlags {
 		fmt.Fprintf(fflg, "_CGO_%s=%s\n", k, strings.Join(v, " "))
-		if k == "LDFLAGS" {
+		if k == "LDFLAGS" && !*gccgo {
 			for _, arg := range v {
 				fmt.Fprintf(fc, "#pragma cgo_ldflag %q\n", arg)
 			}
@@ -105,7 +105,10 @@ func (p *Package) writeDefs() {
 			fmt.Fprintf(fm, "extern char %s[];\n", n.C)
 			fmt.Fprintf(fm, "void *_cgohack_%s = %s;\n\n", n.C, n.C)
 
-			fmt.Fprintf(fc, "#pragma cgo_import_static %s\n", n.C)
+			if !*gccgo {
+				fmt.Fprintf(fc, "#pragma cgo_import_static %s\n", n.C)
+			}
+
 			fmt.Fprintf(fc, "extern byte *%s;\n", n.C)
 
 			cVars[n.C] = true

コアとなるコードの解説

  1. LDFLAGSの処理変更: 変更前:

    if k == "LDFLAGS" {
        for _, arg := range v {
            fmt.Fprintf(fc, "#pragma cgo_ldflag %q\n", arg)
        }
    }
    

    変更後:

    if k == "LDFLAGS" && !*gccgo {
        for _, arg := range v {
            fmt.Fprintf(fc, "#pragma cgo_ldflag %q\n", arg)
        }
    }
    

    この変更により、CgoFlagsマップからLDFLAGS(リンカフラグ)が検出された場合でも、グローバル変数gccgotrue(つまりgccgoコンパイラが使用されている)でなければ、#pragma cgo_ldflagが出力されるようになりました。*gccgoは、cgoツールがgccgoモードで実行されているかどうかを示すブール型のフラグです。

  2. cgo_import_staticの処理変更: 変更前:

    fmt.Fprintf(fc, "#pragma cgo_import_static %s\n", n.C)
    

    変更後:

    if !*gccgo {
        fmt.Fprintf(fc, "#pragma cgo_import_static %s\n", n.C)
    }
    

    同様に、静的にインポートされるCシンボル(n.C)についても、gccgoが使用されていない場合にのみ#pragma cgo_import_staticが出力されるようになりました。

これらの変更は、cgoが生成するCコードがgccgoによってコンパイルされる際に、gccgoが認識しない#pragmaディレクティブを含まないようにするためのものです。これにより、gccgoを使用するGoプロジェクトのビルドがよりスムーズに行われるようになります。

関連リンク

参考にした情報源リンク

  • GitHub上のコミットページ: https://github.com/golang/go/commit/c5c52f072b3d33f1a40ffda6d5334d38d0a11fa5
  • Go言語のイシュートラッカー(#5905に関する情報が見つからなかったため、一般的なcgogccgoの互換性に関する議論を参照)
  • Stack OverflowやGoコミュニティのフォーラムでのcgo_ldflagcgo_import_staticに関する議論。
  • Go言語のソースコード(src/cmd/cgo/out.go)の分析。