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

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

このコミットは、Go言語のcgoツールにおける、生成されるCコードのインクルード順序に関する問題を修正するものです。具体的には、builtinPrologと各ファイルのPreambleの出力順序を変更することで、sys/types.hの二重インクルードによる問題を回避し、既存のcgoコードの互換性を保ちつつ、特定のLinuxシステムでのgo tool cgo -godefsの利用を修正します。

コミット

commit 5feb15508e9cefa06f7d109da8233c91e69937fa
Author: Russ Cox <rsc@golang.org>
Date:   Tue Oct 15 15:00:48 2013 -0400

    cmd/cgo: print the builtin prolog after the per-file preamble
    
    The preamble may want to #define some special symbols
    and then #include <sys/types.h> itself. The builtin prolog
    also #includes <sys/types.h>, which would break such a
    preamble (because the second #include will be a no-op).
    
    The use of sys/types.h in the builtin prolog is new since Go 1.1,
    so this should preserve the semantics of more existing cgo
    code than we would otherwise.
    
    It also fixes src/pkg/syscall/mkall.sh's use of go tool cgo -godefs
    on some Linux systems.
    
    Thanks to fullung@ for identifying the problem.
    
    Fixes #6558.
    
    R=golang-dev, iant
    CC=golang-dev
    https://golang.org/cl/14684044

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

https://github.com/golang/go/commit/5feb15508e9cefa06f7d109da8233c91e69937fa

元コミット内容

cmd/cgo: print the builtin prolog after the per-file preamble

このコミットは、cgoコマンドが生成するCコードにおいて、組み込みのプロローグ(builtinProlog)を、各Goソースファイルに記述されたCコードのプリアンブル(Preamble)の後に配置するように変更します。これにより、プリアンブル内で#defineされたシンボルや、#include <sys/types.h>が適切に機能するようになります。

変更の背景

この変更の背景には、cgoがGoコードとCコードを連携させる際に生成するCソースファイルの構造と、Cプリプロセッサの動作が関係しています。

  1. sys/types.hの二重インクルード問題:

    • Go 1.1以降、cgobuiltinPrologcgoが自動的に挿入するCコードの冒頭部分)が#include <sys/types.h>を含むようになりました。
    • 一方で、ユーザーがGoソースファイル内でimport "C"ブロックに記述するCコード(これがPreambleとして扱われる)も、独自に#include <sys/types.h>を含む場合があります。
    • Cプリプロセッサの一般的な動作として、同じヘッダーファイルが複数回インクルードされた場合、2回目以降のインクルードは通常無視されます(これはヘッダーガードによって実現されます)。しかし、この「無視」が問題を引き起こすことがあります。
    • もしPreamble#defineを使って特定のシンボルを定義し、その後に#include <sys/types.h>を呼び出す場合、そしてbuiltinPrologが先にインクルードされると、Preamble内の#include <sys/types.h>は無効になり、Preambleが意図する#definesys/types.hの定義に影響を与えられなくなります。これは、sys/types.h#defineされたシンボルに依存するような場合に特に問題となります。
  2. 既存のcgoコードのセマンティクス維持:

    • Go 1.1でbuiltinPrologsys/types.hが追加されたことで、それ以前に書かれたcgoコードが、この新しい動作によって予期せぬ影響を受ける可能性がありました。プリアンブルがsys/types.hのインクルード順序に依存している場合、その動作が壊れる恐れがありました。
  3. src/pkg/syscall/mkall.shの問題:

    • 特定のLinuxシステムにおいて、src/pkg/syscall/mkall.shスクリプトがgo tool cgo -godefsを使用する際に問題が発生していました。これは、godefsがCの定義をGoの定義に変換するツールであり、上記のようなインクルード順序の問題が、Cの定義の解釈に影響を与えていたためと考えられます。

これらの問題を解決するため、builtinPrologPreambleの後に配置することで、Preambleが先に処理され、その中で行われる#define#includeが意図通りに機能するように変更されました。

前提知識の解説

このコミットを理解するためには、以下の概念を理解しておく必要があります。

  • cgo: Go言語とC言語のコードを相互に呼び出すためのGoツールチェーンの一部です。GoプログラムからC関数を呼び出したり、CプログラムからGo関数を呼び出したりすることを可能にします。cgoは、Goソースファイル内の特別なimport "C"ブロックに記述されたCコードを抽出し、GoコードとCコードをリンクするための橋渡しとなるCソースファイルを生成します。
  • import "C"ブロック(Cプリアンブル): Goソースファイル内でCコードを記述するための特別なブロックです。このブロックに記述されたCコードは、cgoによって生成されるCソースファイルの冒頭部分(プリアンブル)に挿入されます。ユーザーはここに#includeディレクティブや#defineマクロ、C関数の定義などを記述できます。コミットメッセージではこれをper-file preambleまたは単にPreambleと呼んでいます。
  • builtinProlog: cgoツールが自動的に生成するCコードの冒頭部分です。これは、ユーザーが記述したCプリアンブルとは別に、cgoがGoとCの連携に必要な基本的な定義やインクルード(例: sys/types.h)を挿入するために使用されます。
  • #include <sys/types.h>: C言語の標準ヘッダーファイルの一つで、様々なデータ型(例: size_t, ssize_t, off_tなど)の定義が含まれています。システムコールやファイル操作など、多くの低レベルなCプログラミングで必要とされます。
  • Cプリプロセッサ: Cコンパイルプロセスの最初の段階で実行されるプログラムです。#include#define#ifdefなどのプリプロセッサディレクティブを処理し、最終的なCソースコードを生成します。ヘッダーガード(#ifndef/#define/#endif)は、ヘッダーファイルが複数回インクルードされるのを防ぐための一般的な手法です。
  • go tool cgo -godefs: cgoツールの一機能で、Cの構造体や定数をGoの構造体や定数に変換するために使用されます。特に、Goのsyscallパッケージのように、OSのシステムコールをGoから呼び出す際に、Cの定義をGoの型にマッピングするために利用されます。
  • bytes.Buffer: Go言語の標準ライブラリbytesパッケージにある型で、可変長のバイトシーケンスを効率的に構築するためのバッファです。文字列の連結や、データを一時的に保持する際によく使用されます。
  • gccDefines: cgo内部の関数で、CコードをGCCに渡し、その出力から#defineされたシンボルなどの情報を抽出するために使用されます。

技術的詳細

このコミットの技術的な核心は、src/cmd/cgo/gcc.goファイル内のbytes.Bufferへの書き込み順序の変更にあります。cgoツールは、Goのソースコードを解析し、Cコードを生成する際に、いくつかの段階でCのコンパイラ(GCCなど)を利用してCの定義を解析したり、最終的なCソースファイルを生成したりします。

変更前は、builtinPrologPreambleよりも先にbytes.Bufferに書き込まれていました。これは、生成されるCコードにおいてbuiltinPrologPreambleよりも前に配置されることを意味します。

// 変更前
b.WriteString(builtinProlog)
b.WriteString(f.Preamble)

この順序だと、もしPreamble#defineで特定のシンボルを定義し、その後に#include <sys/types.h>を呼び出す場合、builtinPrologが先に#include <sys/types.h>を処理してしまうため、Preamble内の#include <sys/types.h>はヘッダーガードによって無視されます。結果として、Preambleで定義された#definesys/types.hの定義に影響を与えることができず、意図しない動作を引き起こす可能性がありました。

変更後は、PreamblebuiltinPrologよりも先にbytes.Bufferに書き込まれるようになりました。

// 変更後
b.WriteString(f.Preamble)
b.WriteString(builtinProlog)

この変更により、生成されるCコードではPreamblebuiltinPrologよりも前に配置されます。これにより、Preamble内で#defineされたシンボルが、その後に続くbuiltinPrologや、Preamble自身がインクルードするsys/types.hの定義に影響を与えることが可能になります。もしPreamble#include <sys/types.h>を含んでいても、それが先に処理されるため、builtinProlog内の#include <sys/types.h>はヘッダーガードによって無視されますが、これは問題ありません。なぜなら、Preambleが意図するsys/types.hのインクルードは既に完了しているからです。

この修正は、cgoがCコンパイラに渡すCコードの構造を根本的に変更するものであり、特にloadDefinesguessKindsloadDWARFといった、Cの定義を解析するためにCコンパイラを利用する関数に影響を与えます。これらの関数は、Cコンパイラに渡すCコードを構築する際に、この新しい順序を適用します。

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

変更はsrc/cmd/cgo/gcc.goファイルに集中しています。具体的には、以下の3つの箇所でbytes.Bufferへの書き込み順序が入れ替えられています。

  1. func (p *Package) loadDefines(f *File): この関数は、Cの定義をロードするために使用されます。

    --- a/src/cmd/cgo/gcc.go
    +++ b/src/cmd/cgo/gcc.go
    @@ -188,8 +188,8 @@ func (p *Package) Translate(f *File) {
     // in the file f and saves relevant renamings in f.Name[name].Define.
     func (p *Package) loadDefines(f *File) {
     	var b bytes.Buffer
    -	b.WriteString(builtinProlog)
     	b.WriteString(f.Preamble)
    +	b.WriteString(builtinProlog)
     	stdout := p.gccDefines(b.Bytes())
     
     	for _, line := range strings.Split(stdout, "\\n") {
    
  2. func (p *Package) guessKinds(f *File) []*Name: この関数は、Cの型の種類を推測するために使用されます。

    --- a/src/cmd/cgo/gcc.go
    +++ b/src/cmd/cgo/gcc.go
    @@ -301,8 +301,8 @@ func (p *Package) guessKinds(f *File) []*Name {
     	}
     
     	var b bytes.Buffer
    -	b.WriteString(builtinProlog)
     	b.WriteString(f.Preamble)
    +	b.WriteString(builtinProlog)
     	b.WriteString("void __cgo__f__(void) {\\n")
     
     	// For a #defined expression, clang silences the warning about "unused expression".
    
  3. func (p *Package) loadDWARF(f *File, names []*Name): この関数は、DWARFデバッグ情報からCの型情報をロードするために使用されます。

    --- a/src/cmd/cgo/gcc.go
    +++ b/src/cmd/cgo/gcc.go
    @@ -417,8 +417,8 @@ func (p *Package) loadDWARF(f *File, names []*Name) {
     	// for each entry in names and then dereference the type we
     	// learn for __cgo__i.
     	var b bytes.Buffer
    -	b.WriteString(builtinProlog)
     	b.WriteString(f.Preamble)
    +	b.WriteString(builtinProlog)
     	for i, n := range names {
     		fmt.Fprintf(&b, "typeof(%s) *__cgo__%d;\\n", n.C, i)
     		if n.Kind == "const" {
    

コアとなるコードの解説

上記の3つの変更箇所は、いずれもcgoがCコンパイラに渡すCコードスニペットを構築する部分です。これらのスニペットは、Cの定義を解析したり、Cの型情報を取得したりするために一時的に生成されます。

変更前は、これらのスニペットが常にbuiltinPrologで始まり、その後にユーザーが記述したPreambleが続く形でした。

// 変更前の一般的な構造
// b.WriteString(builtinProlog)
// b.WriteString(f.Preamble)
// ... その他のCコード ...

この順序では、もしf.Preamble#defineマクロと#include <sys/types.h>を含んでいた場合、builtinPrologが先にsys/types.hをインクルードしてしまうため、f.Preamble内の#includeは効果がなくなります。その結果、f.Preambleで定義された#definesys/types.h内の型定義に影響を与えることができず、コンパイルエラーや予期せぬ動作につながる可能性がありました。

変更後は、f.PreamblebuiltinPrologよりも先に書き込まれるようになりました。

// 変更後の一般的な構造
// b.WriteString(f.Preamble)
// b.WriteString(builtinProlog)
// ... その他のCコード ...

この新しい順序により、f.Preamble内の#define#includebuiltinPrologよりも先に処理されます。これにより、f.Preamblesys/types.hをインクルードする場合でも、それが先に完了し、f.Preambleで定義された#definesys/types.hの定義に適切に影響を与えることができるようになります。builtinPrologがその後でsys/types.hをインクルードしようとしても、ヘッダーガードによって無視されるため、問題は発生しません。

この修正は、cgoが生成するCコードのセマンティクスをより堅牢にし、特にsys/types.hのような共通のヘッダーファイルとユーザー定義のプリアンブルとの間の相互作用における潜在的な問題を解決します。これにより、既存のcgoコードの互換性が向上し、特定の環境でのビルド問題が解消されました。

関連リンク

  • Go Issue #6558: cmd/cgo: sys/types.h included too early - このコミットが修正した具体的な問題のトラッキングイシューです。問題の詳細な議論や再現手順が記載されています。
  • Go CL 14684044: https://golang.org/cl/14684044 - このコミットに対応するGerritの変更リストです。レビューコメントやパッチセットの履歴を確認できます。

参考にした情報源リンク