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

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

このコミットは、Go言語のcmd/cgoツール内のsrc/cmd/cgo/gcc.goファイルを変更しています。cgoはGoプログラムからC言語のコードを呼び出すためのツールであり、gcc.goファイルはCコンパイラ(GCCやClangなど)との連携、特にDWARFデバッグ情報の解析とGoの型への変換を担当しています。

コミット

cmd/cgo: issue 6506に対するよりシンプルな修正。 CL 14682044を置き換えます。 Fixes #6506。

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

https://github.com/golang/go/commit/4d38d1260e40336008a491033146157bcaa5ef90

元コミット内容

commit 4d38d1260e40336008a491033146157bcaa5ef90
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Tue Oct 15 21:35:52 2013 -0400

    cmd/cgo: simpler fix for issue 6506.
    Replaces CL 14682044.
    Fixes #6506.
    
    R=rsc, iant, dave
    CC=golang-dev
    https://golang.org/cl/14717043
---
 src/cmd/cgo/gcc.go | 55 +++++++++---------------------------------------------\n 1 file changed, 9 insertions(+), 46 deletions(-)\n

変更の背景

このコミットは、GoのcgoツールがClangコンパイラと連携する際に発生していた型変換の問題、具体的にはIssue 6506を修正するためのものです。

問題の核心は、Clangがmalloccallocといった標準ライブラリの組み込み関数に対して、内部的にプロトタイプ(関数の宣言)を持っていることにありました。これらのプロトタイプでは、size_t型の引数が誤ってunsigned long型として定義されていました。

cgoはCのコードをコンパイルする際に、生成されたDWARFデバッグ情報からCの関数の型シグネチャを読み取り、それをGoの型に変換します。Clangのこの誤った内部プロトタイプが原因で、cgosize_tunsigned longとして認識してしまい、Go側でCの関数を呼び出す際に型ミスマッチが発生する可能性がありました。

以前の修正(CL 14682044)では、usesSizeTというマップを用いて、size_tを誤ってunsigned longとして扱う可能性のある特定の関数名をハードコードし、それらの関数に対して手動で型を修正するというアプローチが取られていました。しかし、この方法は新しい関数が追加されたり、既存の関数の振る舞いが変更されたりするたびにマップを更新する必要があり、脆弱で保守が難しいものでした。

このコミットは、より根本的でシンプルな解決策を導入することで、以前の修正を置き換えるものです。

前提知識の解説

  • cgo: Go言語の標準ツールの一つで、GoプログラムからC言語の関数を呼び出したり、C言語のデータ構造をGoで扱ったりするための橋渡しをします。GoとCの型システムは異なるため、cgoは両者間の型変換を適切に行う必要があります。
  • DWARF (Debugging With Attributed Record Formats): プログラムのコンパイル時に生成されるデバッグ情報フォーマットの一つです。変数名、型情報、関数名、ソースコードの行番号など、デバッガがプログラムの実行を追跡するために必要な情報を含んでいます。cgoはCのコードの型情報をDWARFから読み取ってGoの型にマッピングします。
  • Clang: LLVMプロジェクトの一部であるC、C++、Objective-C、Objective-C++コンパイラです。GCCと並んで広く使われているコンパイラであり、特にmacOSやiOSの開発で標準的に利用されています。
  • size_t: C言語で定義されている符号なし整数型です。主にメモリのサイズや配列のインデックスを表すために使用されます。この型の具体的なサイズは、コンパイルされるシステム(アーキテクチャ)によって異なります(例: 32ビットシステムではunsigned int、64ビットシステムではunsigned longまたはunsigned long longに相当することが多い)。
  • unsigned long: C言語の符号なし長整数型です。size_tと同様に、その具体的なサイズはシステムに依存します。問題は、size_tunsigned longと常に同じであるとは限らないにもかかわらず、Clangが内部的にそう扱っていた点にあります。
  • -fno-builtinコンパイラオプション: GCCやClangなどのCコンパイラには、mallocmemcpystrlenといった標準ライブラリの特定の関数について、最適化のために特別な組み込み知識(builtin functions)を持っています。これにより、コンパイラはこれらの関数呼び出しをより効率的な命令に置き換えたり、特定の状況下で最適化を施したりすることができます。-fno-builtinオプションは、このコンパイラが持つ組み込み関数の知識の使用を無効にするものです。これにより、コンパイラはこれらの関数を通常の外部関数呼び出しとして扱い、標準ライブラリのヘッダファイルで定義されているプロトタイプのみを信頼するようになります。

技術的詳細

このコミットの技術的な核心は、Clangコンパイラの特定の振る舞いとcgoの型変換ロジックの間の不整合を解消することにあります。

  1. Clangの組み込みプロトタイプ問題: Clangは、malloccallocのような一部の標準Cライブラリ関数について、ヘッダファイルがインクルードされていなくてもコンパイル時に利用できる内部的なプロトタイプ定義を持っています。これは通常、コンパイラの最適化やエラーチェックに役立ちます。しかし、Issue 6506で報告されたように、これらの内部プロトタイプにおいて、size_t型の引数が誤ってunsigned long型として扱われていました。これはClangのバグまたは設計上の不整合でした。

  2. cgoの型変換への影響: cgoはCのコードをコンパイルし、その結果生成されるDWARFデバッグ情報からCの関数のシグネチャ(引数の型や戻り値の型など)を読み取ります。この情報に基づいて、cgoはGo側でCの関数を呼び出すための適切なGoの型定義を生成します。Clangがsize_tunsigned longとしてDWARF情報に記録してしまうと、cgoはそれをそのままGoの_Ctype_ulong(Cのunsigned longに対応するGoの型)として解釈してしまいます。しかし、Goの型システムでは_Ctype_ulong_Ctype_size_tは異なる型として扱われるため、Go側からCの関数を呼び出す際に型ミスマッチのエラーや予期せぬ動作が発生する可能性がありました。

  3. 以前の修正と問題点: 以前の修正(CL 14682044)では、src/cmd/cgo/gcc.go内にusesSizeTというmap[string]boolが導入されていました。このマップには、malloc, calloc, memcpyなど、size_tを引数に取る可能性があり、かつClangの組み込みプロトタイプで問題を起こす可能性のある関数名がハードコードされていました。cgoはこれらの関数名を検出すると、DWARFから読み取った型が_Ctype_ulongであっても、強制的に_Ctype_size_tに修正していました。このアプローチは、問題のある関数が追加されるたびにマップを更新する必要があり、また、コンパイラの内部実装に依存する脆弱なものでした。

  4. このコミットによる解決策: このコミットでは、より堅牢でシンプルな解決策が採用されました。それは、Clangに-fno-builtinコンパイラオプションを渡すことです。

    • gccCmd関数(Cコンパイラに渡すコマンドライン引数を構築する部分)に-fno-builtinが追加されました。
    • このオプションを渡すことで、Clangは標準ライブラリの組み込み関数に関する内部的な知識(誤ったsize_tの型定義を含む)を使用しなくなります。
    • 代わりに、Clangは標準ライブラリのヘッダファイル(例: stdlib.h)で定義されている正しいプロトタイプのみを信頼するようになります。
    • これにより、cgoがDWARF情報から読み取るsize_tの型情報が常に正しくなり、手動での型修正ロジック(usesSizeTマップとその関連コード)が不要になりました。

この変更により、cgoのコードは大幅に簡素化され、Clangの特定の内部実装に依存しない、より汎用的な型変換ロジックが実現されました。

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

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

  1. gccCmd関数への-fno-builtinの追加:

    --- a/src/cmd/cgo/gcc.go
    +++ b/src/cmd/cgo/gcc.go
    @@ -757,6 +757,13 @@ func (p *Package) gccCmd() []string {
     			"-Wno-unneeded-internal-declaration",
     			"-Wno-unused-function",
     			"-Qunused-arguments",
    +			// Clang embeds prototypes for some builtin functions,
    +			// like malloc and calloc, but all size_t parameters are
    +			// incorrectly typed unsigned long. We work around that
    +			// by disabling the builtin functions (this is safe as
    +			// it won't affect the actual compilation of the C code).
    +			// See: http://golang.org/issue/6506.
    +			"-fno-builtin",
     		)
     	}
    

    Cコンパイラに渡す引数リストに-fno-builtinが追加されました。

  2. usesSizeTマップの削除:

    --- a/src/cmd/cgo/gcc.go
    +++ b/src/cmd/cgo/gcc.go
    @@ -1327,41 +1334,6 @@ func (c *typeConv) Type(dtype dwarf.Type, pos token.Pos) *Type {
     	return t
     }
     
    -// Clang contains built-in prototypes for many functions in the standard library.
    -// If you use the function without a header, clang uses these definitions to print
    -// an error telling which header to #include and then to continue on with the correct
    -// prototype. Unfortunately, the DWARF debug information generated for one
    -// of these functions, even after the header has been #included, records each of
    -// the size_t arguments as an unsigned long instead. Go treats C.ulong and C.size_t
    -// as different types, so we must correct the prototype for code that works on other
    -// systems to work with clang and vice versa. See golang.org/issue/6506#c21.
    -var usesSizeT = map[string]bool{
    -	"alloca":      true,
    -	"bzero":       true,
    -	"calloc":      true,
    -	"malloc":      true,
    -	"memchr":      true,
    -	"memcmp":      true,
    -	"memcpy":      true,
    -	"memmove":     true,
    -	"memset":      true,
    -	"realloc":     true,
    -	"snprintf":    true,
    -	"stpncpy":     true,
    -	"strcspn":     true,
    -	"strlcat":     true,
    -	"strlcpy":     true,
    -	"strlen":      true,
    -	"strncasecmp": true,
    -	"strncat":     true,
    -	"strncmp":     true,
    -	"strncpy":     true,
    -	"strndup":     true,
    -	"strspn":      true,
    -	"strxfrm":     true,
    -	"vsnprintf":   true,\n}
    -
     // FuncArg returns a Go type with the same memory layout as
     // dtype when used as the type of a C function argument.
     func (c *typeConv) FuncArg(dtype dwarf.Type, pos token.Pos) *Type {
    

    usesSizeTマップとその長いコメントが完全に削除されました。

  3. FuncType関数のシグネチャ変更と関連ロジックの削除:

    --- a/src/cmd/cgo/gcc.go
    +++ b/src/cmd/cgo/gcc.go
    @@ -1406,7 +1377,7 @@ func (c *typeConv) FuncArg(dtype dwarf.Type, pos token.Pos) *Type {
     
     // FuncType returns the Go type analogous to dtype.
     // There is no guarantee about matching memory layout.
    -func (c *typeConv) FuncType(name *Name, dtype *dwarf.FuncType, pos token.Pos) *FuncType {
    +func (c *typeConv) FuncType(dtype *dwarf.FuncType, pos token.Pos) *FuncType {
      	p := make([]*Type, len(dtype.ParamType))\n      	gp := make([]*ast.Field, len(dtype.ParamType))\n      	for i, f := range dtype.ParamType {
    @@ -1420,10 +1391,6 @@ func (c *typeConv) FuncType(name *Name, dtype *dwarf.FuncType, pos token.Pos) *F
      			break
      		}
      		p[i] = c.FuncArg(f, pos)
    -		// See comment on usesSizeT.
    -		if id, ok := p[i].Go.(*ast.Ident); ok && id.Name == "_Ctype_ulong" && usesSizeT[name.C] {
    -			p[i].Go = c.Ident("_Ctype_size_t")
    -		}
      		gp[i] = &ast.Field{Type: p[i].Go}
      	}
      	var r *Type
    @@ -1432,10 +1399,6 @@ func (c *typeConv) FuncType(name *Name, dtype *dwarf.FuncType, pos token.Pos) *F
      	\tgr = []*ast.Field{{Type: c.goVoid}}\n      	} else if dtype.ReturnType != nil {
      	\tr = c.Type(dtype.ReturnType, pos)
    -		// See comment on usesSizeT.
    -		if id, ok := r.Go.(*ast.Ident); ok && id.Name == "_Ctype_ulong" && usesSizeT[name.C] {
    -			r.Go = c.Ident("_Ctype_size_t")
    -		}
      	\tgr = []*ast.Field{{Type: r.Go}}\n      	}
      	return &FuncType{
    

    FuncType関数のシグネチャからname *Name引数が削除され、それに伴いusesSizeTマップを参照して型を修正していたロジックが削除されました。

  4. loadDWARF関数内のFuncType呼び出しの引数変更:

    --- a/src/cmd/cgo/gcc.go
    +++ b/src/cmd/cgo/gcc.go
    @@ -543,7 +543,7 @@ func (p *Package) loadDWARF(f *File, names []*Name) {
     		tf, fok := types[i].(*dwarf.FuncType)
     		if n.Kind != "type" && fok {
     			n.Kind = "func"
    -			n.FuncType = conv.FuncType(n, f, pos)
    +			n.FuncType = conv.FuncType(f, pos)
     		} else {
     			n.Type = conv.Type(types[i], pos)
     			if enums[i] != 0 && n.Type.EnumValues != nil {
    

    loadDWARF内でFuncTypeを呼び出す際に、name引数が渡されなくなりました。

コアとなるコードの解説

このコミットの主要な変更は、cgoがCコンパイラ(特にClang)とどのように対話するか、そしてCの型情報をGoの型に変換するロジックを根本的に改善した点にあります。

  1. -fno-builtinオプションの導入 (gccCmd関数): gccCmd関数は、cgoがCのソースコードをコンパイルするために外部のCコンパイラ(gccclang)を呼び出す際に使用するコマンドライン引数を構築します。この関数に-fno-builtinオプションが追加されたことが最も重要な変更点です。 このオプションは、コンパイラに対して、malloccallocなどの標準ライブラリ関数について、コンパイラが内部的に持っている「組み込みの知識」(builtin functions)を使用しないように指示します。Clangの場合、この「組み込みの知識」には、size_tを誤ってunsigned longとして扱うプロトタイプが含まれていました。 -fno-builtinを指定することで、コンパイラはこれらの関数を通常の外部関数として扱い、標準ライブラリのヘッダファイル(例: stdlib.h)で定義されている正しいプロトタイプのみを参照するようになります。これにより、生成されるDWARFデバッグ情報内の型情報が正確になり、size_tは正しくsize_tとして記録されるようになります。

  2. usesSizeTマップと関連ロジックの削除 (FuncType関数): 以前の修正では、usesSizeTというマップを使って、Clangのバグによってsize_tunsigned longとして誤って認識される可能性のある特定のC関数名をリストアップしていました。FuncType関数は、DWARFから読み取った関数の引数や戻り値の型が_Ctype_ulongであり、かつその関数名がusesSizeTマップに含まれている場合に、強制的に型を_Ctype_size_tに修正していました。 -fno-builtinオプションの導入により、Clangが生成するDWARF情報自体が正しくなるため、このような手動での型修正ロジックは完全に不要になりました。したがって、usesSizeTマップとそのマップを参照するすべてのコード(FuncType内の条件分岐)が削除されました。これにより、コードが大幅に簡素化され、特定のコンパイラのバグに依存しない、よりクリーンで堅牢な型変換ロジックが実現されました。

この変更は、cgoがCコンパイラとの連携をより安定させ、将来的なコンパイラの変更や新しい標準ライブラリ関数の追加に対しても、より柔軟に対応できるようになることを意味します。

関連リンク

参考にした情報源リンク

  • Go Issue Tracker: 上記のIssue 6506の議論。
  • GCC/Clang -fno-builtinオプションに関するドキュメント:
  • DWARF Debugging Information Format: DWARFの一般的な情報源。
  • cgo documentation: Go言語のcgoに関する公式ドキュメント。
  • C Standard Library: size_tmallocなどの標準Cライブラリ関数の定義に関する情報。
  • CL 14682044: このコミットが置き換えた以前の変更リスト。GoのコードレビューシステムGerritで確認可能。
  • CL 14717043: このコミット自体の変更リスト。