[インデックス 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がmalloc
やcalloc
といった標準ライブラリの組み込み関数に対して、内部的にプロトタイプ(関数の宣言)を持っていることにありました。これらのプロトタイプでは、size_t
型の引数が誤ってunsigned long
型として定義されていました。
cgo
はCのコードをコンパイルする際に、生成されたDWARFデバッグ情報からCの関数の型シグネチャを読み取り、それをGoの型に変換します。Clangのこの誤った内部プロトタイプが原因で、cgo
がsize_t
をunsigned 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_t
がunsigned long
と常に同じであるとは限らないにもかかわらず、Clangが内部的にそう扱っていた点にあります。-fno-builtin
コンパイラオプション: GCCやClangなどのCコンパイラには、malloc
、memcpy
、strlen
といった標準ライブラリの特定の関数について、最適化のために特別な組み込み知識(builtin functions)を持っています。これにより、コンパイラはこれらの関数呼び出しをより効率的な命令に置き換えたり、特定の状況下で最適化を施したりすることができます。-fno-builtin
オプションは、このコンパイラが持つ組み込み関数の知識の使用を無効にするものです。これにより、コンパイラはこれらの関数を通常の外部関数呼び出しとして扱い、標準ライブラリのヘッダファイルで定義されているプロトタイプのみを信頼するようになります。
技術的詳細
このコミットの技術的な核心は、Clangコンパイラの特定の振る舞いとcgo
の型変換ロジックの間の不整合を解消することにあります。
-
Clangの組み込みプロトタイプ問題: Clangは、
malloc
やcalloc
のような一部の標準Cライブラリ関数について、ヘッダファイルがインクルードされていなくてもコンパイル時に利用できる内部的なプロトタイプ定義を持っています。これは通常、コンパイラの最適化やエラーチェックに役立ちます。しかし、Issue 6506で報告されたように、これらの内部プロトタイプにおいて、size_t
型の引数が誤ってunsigned long
型として扱われていました。これはClangのバグまたは設計上の不整合でした。 -
cgo
の型変換への影響:cgo
はCのコードをコンパイルし、その結果生成されるDWARFデバッグ情報からCの関数のシグネチャ(引数の型や戻り値の型など)を読み取ります。この情報に基づいて、cgo
はGo側でCの関数を呼び出すための適切なGoの型定義を生成します。Clangがsize_t
をunsigned long
としてDWARF情報に記録してしまうと、cgo
はそれをそのままGoの_Ctype_ulong
(Cのunsigned long
に対応するGoの型)として解釈してしまいます。しかし、Goの型システムでは_Ctype_ulong
と_Ctype_size_t
は異なる型として扱われるため、Go側からCの関数を呼び出す際に型ミスマッチのエラーや予期せぬ動作が発生する可能性がありました。 -
以前の修正と問題点: 以前の修正(CL 14682044)では、
src/cmd/cgo/gcc.go
内にusesSizeT
というmap[string]bool
が導入されていました。このマップには、malloc
,calloc
,memcpy
など、size_t
を引数に取る可能性があり、かつClangの組み込みプロトタイプで問題を起こす可能性のある関数名がハードコードされていました。cgo
はこれらの関数名を検出すると、DWARFから読み取った型が_Ctype_ulong
であっても、強制的に_Ctype_size_t
に修正していました。このアプローチは、問題のある関数が追加されるたびにマップを更新する必要があり、また、コンパイラの内部実装に依存する脆弱なものでした。 -
このコミットによる解決策: このコミットでは、より堅牢でシンプルな解決策が採用されました。それは、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
ファイルに集中しています。
-
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
が追加されました。 -
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
マップとその長いコメントが完全に削除されました。 -
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
マップを参照して型を修正していたロジックが削除されました。 -
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の型に変換するロジックを根本的に改善した点にあります。
-
-fno-builtin
オプションの導入 (gccCmd
関数):gccCmd
関数は、cgo
がCのソースコードをコンパイルするために外部のCコンパイラ(gcc
やclang
)を呼び出す際に使用するコマンドライン引数を構築します。この関数に-fno-builtin
オプションが追加されたことが最も重要な変更点です。 このオプションは、コンパイラに対して、malloc
やcalloc
などの標準ライブラリ関数について、コンパイラが内部的に持っている「組み込みの知識」(builtin functions)を使用しないように指示します。Clangの場合、この「組み込みの知識」には、size_t
を誤ってunsigned long
として扱うプロトタイプが含まれていました。-fno-builtin
を指定することで、コンパイラはこれらの関数を通常の外部関数として扱い、標準ライブラリのヘッダファイル(例:stdlib.h
)で定義されている正しいプロトタイプのみを参照するようになります。これにより、生成されるDWARFデバッグ情報内の型情報が正確になり、size_t
は正しくsize_t
として記録されるようになります。 -
usesSizeT
マップと関連ロジックの削除 (FuncType
関数): 以前の修正では、usesSizeT
というマップを使って、Clangのバグによってsize_t
がunsigned long
として誤って認識される可能性のある特定のC関数名をリストアップしていました。FuncType
関数は、DWARFから読み取った関数の引数や戻り値の型が_Ctype_ulong
であり、かつその関数名がusesSizeT
マップに含まれている場合に、強制的に型を_Ctype_size_t
に修正していました。-fno-builtin
オプションの導入により、Clangが生成するDWARF情報自体が正しくなるため、このような手動での型修正ロジックは完全に不要になりました。したがって、usesSizeT
マップとそのマップを参照するすべてのコード(FuncType
内の条件分岐)が削除されました。これにより、コードが大幅に簡素化され、特定のコンパイラのバグに依存しない、よりクリーンで堅牢な型変換ロジックが実現されました。
この変更は、cgo
がCコンパイラとの連携をより安定させ、将来的なコンパイラの変更や新しい標準ライブラリ関数の追加に対しても、より柔軟に対応できるようになることを意味します。
関連リンク
- Go Issue 6506: https://github.com/golang/go/issues/6506
参考にした情報源リンク
- Go Issue Tracker: 上記のIssue 6506の議論。
- GCC/Clang
-fno-builtin
オプションに関するドキュメント:- GCC: https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html (
-fno-builtin
のセクションを参照) - Clang: ClangのドキュメントはGCCと互換性のあるオプションが多く、
-fno-builtin
も同様に機能します。
- GCC: https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html (
- DWARF Debugging Information Format: DWARFの一般的な情報源。
- cgo documentation: Go言語の
cgo
に関する公式ドキュメント。 - C Standard Library:
size_t
やmalloc
などの標準Cライブラリ関数の定義に関する情報。 - CL 14682044: このコミットが置き換えた以前の変更リスト。GoのコードレビューシステムGerritで確認可能。
- https://go-review.googlesource.com/c/go/+/14682044 (このCLは最終的に取り消され、本コミットに置き換えられました)
- CL 14717043: このコミット自体の変更リスト。