[インデックス 13932] ファイルの概要
このコミットは、Go言語のcgo
ツールにおいて、int
型のサイズが特定のビット数(32ビット)にハードコードされている問題を修正し、64ビット整数をサポートするための準備を行うものです。これにより、異なるアーキテクチャ(特に64ビットシステム)上でのint
型の扱いをより柔軟にし、将来的な互換性と正確性を確保します。
コミット
commit 5501a097a9ec5d082dad447c92d0eac298dede4d
Author: Russ Cox <rsc@golang.org>
Date: Mon Sep 24 14:58:57 2012 -0400
cmd/cgo: prepare for 64-bit ints
This CL makes the size of an int controlled by a variable
in cgo instead of hard-coding 4 (or 32 bits) in various places.
Update #2188.
R=iant, r, dave
CC=golang-dev
https://golang.org/cl/6548061
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5501a097a9ec5d082dad447c92d0eac298dede4d
元コミット内容
cmd/cgo
: 64ビット整数への対応準備
この変更リスト(CL)は、cgo
内でint
のサイズが様々な場所でハードコードされた4(または32ビット)ではなく、変数によって制御されるようにします。
Issue #2188を更新。
変更の背景
Go言語のcgo
ツールは、GoプログラムからC言語のコードを呼び出すためのメカニズムを提供します。C言語のint
型は、そのサイズがコンパイラとターゲットアーキテクチャに依存します。多くのシステムでは32ビットですが、64ビットシステムでは64ビットになる可能性もあります。
このコミット以前のcgo
の実装では、C言語のint
型が常に32ビット(4バイト)であると仮定して処理されていました。これは、特に64ビットアーキテクチャ上でGoプログラムがCコードと連携する際に問題を引き起こす可能性がありました。例えば、C側で64ビットのint
が使用されている場合、cgo
が32ビットと誤認すると、データの切り捨てやメモリレイアウトの不一致が発生し、予期せぬバグやクラッシュにつながる恐れがありました。
この変更は、int
型のサイズをハードコードするのではなく、実行時のアーキテクチャに基づいて動的に決定できるようにすることで、この問題を解決しようとしています。これにより、cgo
はよりポータブルになり、将来のアーキテクチャや異なるCコンパイラ設定に対しても堅牢になります。コミットメッセージにある「Update #2188」は、この問題に関連するGoのIssueを指していると考えられます。
前提知識の解説
cgo
cgo
は、GoプログラムがC言語のコードを呼び出したり、C言語のコードからGoの関数を呼び出したりするためのGoのツールです。Goのソースファイル内にimport "C"
という特殊なインポート文を記述することで、C言語の型、変数、関数をGoコードから直接利用できるようになります。cgo
は、GoとCの間のデータ変換や関数呼び出しの橋渡しとなるコードを自動生成します。
int型のサイズとアーキテクチャ
C言語のint
型は、標準で「少なくとも16ビット」と定義されていますが、具体的なサイズはコンパイラとターゲットアーキテクチャに依存します。
- 32ビットシステム: 多くの場合は32ビット(4バイト)です。
- 64ビットシステム: 多くの場合は32ビット(4バイト)ですが、一部のシステムやコンパイラ設定では64ビット(8バイト)になることもあります。
Go言語の
int
型は、Goが動作するシステムのネイティブなワードサイズに合わせられます。つまり、32ビットシステムでは32ビット、64ビットシステムでは64ビットになります。cgo
を使用する際には、Goのint
とCのint
のサイズが異なる可能性があるため、明示的な型変換が必要になることがあります。
unsafe.Pointer
unsafe.Pointer
はGo言語の特殊な型で、任意の型のポインタを保持できる汎用ポインタです。C言語のvoid*
に似ています。Goの型安全性をバイパスしてメモリを直接操作することを可能にしますが、その名の通り「unsafe(安全でない)」であり、誤用するとメモリ破壊やクラッシュなどの深刻な問題を引き起こす可能性があります。cgo
では、GoとCの間でメモリを共有したり、異なる型のデータを同じメモリ領域として解釈したりする際に、このunsafe.Pointer
が利用されることがあります。
DWARF (Debugging With Attributed Record Formats)
DWARFは、コンパイルされたプログラムのデバッグ情報を格納するための標準的な形式です。変数名、型情報、ソースコードの行番号と実行可能コードのアドレスのマッピングなどが含まれます。デバッガはDWARF情報を使用して、実行中のプログラムの状態をソースコードレベルで理解し、デバッグを可能にします。cgo
は、Cコンパイラが生成するDWARF情報を解析して、Cの型情報をGoの型システムにマッピングするために利用することがあります。
技術的詳細
このコミットの主要な目的は、cgo
がC言語のint
型を扱う際に、そのサイズをハードコードするのではなく、ターゲットアーキテクチャに基づいて動的に決定できるようにすることです。
具体的には、以下の変更が行われています。
Package
構造体へのIntSize
フィールドの追加:src/cmd/cgo/main.go
のPackage
構造体にIntSize int64
フィールドが追加されました。このフィールドは、現在のターゲットアーキテクチャにおけるC言語のint
型のサイズ(バイト単位)を保持します。intSizeMap
の導入:src/cmd/cgo/main.go
にintSizeMap
という新しいマップが追加されました。これはGOARCH
(Goのターゲットアーキテクチャ)をキーとして、対応するint
型のサイズ(バイト単位)を値として持ちます。初期状態では、386
,amd64
,arm
のいずれも4
(32ビット)に設定されています。これは、これらのアーキテクチャではCのint
が通常32ビットであることを反映しています。newPackage
関数でのIntSize
の初期化:src/cmd/cgo/main.go
のnewPackage
関数内で、GOARCH
に基づいてintSizeMap
からIntSize
が取得され、Package
構造体に設定されるようになりました。これにより、cgo
の処理全体でint
のサイズ情報が利用可能になります。typeConv.Init
関数の変更:src/cmd/cgo/gcc.go
のtypeConv.Init
関数が、ptrSize
に加えてintSize
も引数として受け取るように変更されました。typeConv
は、DWARF情報からGoの型への変換を行う際に使用される構造体であり、これにより型変換のロジックがint
の実際のサイズを考慮できるようになります。out.go
におけるint
およびuint
のサイズ設定の動的化:src/cmd/cgo/out.go
では、Goの組み込み型であるint
とuint
のサイズがgoTypes
マップ内で0
に設定されました。これは、これらの型の実際のサイズがPackage.IntSize
に基づいて動的に決定されることを意味します。cgoType
関数内で、int
またはuint
型が処理される際に、r.Size == 0
(つまり、サイズが動的に決定されるべき型)であれば、Package.IntSize
がその型のSize
とAlign
に設定されるようになりました。これにより、cgo
が生成するコードが、ターゲットアーキテクチャのint
サイズに合わせた型定義を使用するようになります。_Cerrno
関数の引数型の変更:src/cmd/cgo/out.go
で、_Cerrno
関数の引数x
の型がint
からint32
に変更されました。これは、syscall.Errno
が通常32ビット整数で表現されることを明示し、int
のサイズが可変になることによる潜在的な問題を回避するためと考えられます。gccExportHeaderProlog
の動的生成:src/cmd/cgo/out.go
で、gccExportHeaderProlog
というCヘッダのプロローグ部分が、Package.IntSize
に基づいて動的に生成されるようになりました。具体的には、typedef GoIntGOINTBITS GoInt;
のような行が追加され、GOINTBITS
が実際のint
のビット数(8 * p.IntSize
)に置き換えられます。これにより、生成されるCヘッダファイル内でGoInt
とGoUint
が、ターゲットアーキテクチャのint
サイズに合わせた型(例:GoInt32
やGoInt64
)として定義されるようになります。
これらの変更により、cgo
はint
型のサイズをより正確に扱い、異なるアーキテクチャ間での互換性を向上させます。
コアとなるコードの変更箇所
このコミットでは、以下の3つのファイルが変更されています。
src/cmd/cgo/gcc.go
:typeConv.Init
関数のシグネチャが変更され、intSize
引数が追加されました。typeConv
構造体にintSize
フィールドが追加されました。conv.Init
の呼び出し箇所で、新しいintSize
引数が渡されるようになりました。
src/cmd/cgo/main.go
:Package
構造体にIntSize
フィールドが追加されました。intSizeMap
という新しいマップが定義され、GOARCH
ごとのint
サイズが設定されました。newPackage
関数内で、intSizeMap
からIntSize
が取得され、Package
構造体に設定されるようになりました。
src/cmd/cgo/out.go
:_Cerrno
関数の引数型がint
からint32
に変更されました。gccExportHeaderProlog
が直接文字列として定義されるのではなく、p.gccExportHeaderProlog()
という関数呼び出しによって動的に生成されるようになりました。goTypes
マップ内の"int"
と"uint"
のSize
とAlign
が0
に設定されました。cgoType
関数内で、int
またはuint
のSize
とAlign
がPackage.IntSize
に基づいて設定されるロジックが追加されました。string
型のサイズ計算がp.PtrSize + 4
から2 * p.PtrSize
に変更されました。これは、文字列がポインタと長さ(int)で構成されるが、アライメントにより2ポインタサイズに丸められることを考慮したものです。gccExportHeaderProlog
の定数定義が変更され、GoInt
とGoUint
のtypedef
がGoIntGOINTBITS
とGoUintGOINTBITS
に依存するように変更されました。Package
構造体にgccExportHeaderProlog()
メソッドが追加され、GOINTBITS
を実際のビット数に置き換える処理が実装されました。
コアとなるコードの解説
src/cmd/cgo/gcc.go
func (p *Package) loadDWARF(f *File, names []*Name) {
// ...
var conv typeConv
conv.Init(p.PtrSize, p.IntSize) // ここでp.IntSizeが渡されるようになった
// ...
}
type typeConv struct {
// ...
ptrSize int64
intSize int64 // 新しく追加されたフィールド
}
func (c *typeConv) Init(ptrSize, intSize int64) { // intSize引数が追加された
c.ptrSize = ptrSize
c.intSize = intSize // intSizeが初期化される
// ...
}
typeConv
構造体は、CのDWARFデバッグ情報からGoの型への変換を管理します。この変更により、typeConv
はポインタサイズだけでなく、int
のサイズも認識するようになり、より正確な型変換が可能になります。
src/cmd/cgo/main.go
type Package struct {
// ...
PtrSize int64
IntSize int64 // 新しく追加されたフィールド
// ...
}
var intSizeMap = map[string]int64{
"386": 4,
"amd64": 4,
"arm": 4,
}
func newPackage(args []string) *Package {
// ...
ptrSize := ptrSizeMap[goarch]
if ptrSize == 0 {
fatalf("unknown ptrSize for $GOARCH %q", goarch)
}
intSize := intSizeMap[goarch] // intSizeMapからintSizeを取得
if intSize == 0 {
fatalf("unknown intSize for $GOARCH %q", goarch)
}
p := &Package{
PtrSize: ptrSize,
IntSize: intSize, // Package構造体にintSizeを設定
// ...
}
return p
}
Package
構造体はcgo
処理全体のコンテキストを保持します。IntSize
フィールドの追加により、cgo
の各処理段階でターゲットアーキテクチャのint
サイズにアクセスできるようになります。intSizeMap
は、Goのビルドターゲットアーキテクチャ(GOARCH
)に基づいて、Cのint
のデフォルトサイズを定義します。これにより、cgo
はアーキテクチャ固有のint
サイズを認識し、それに基づいてコードを生成できるようになります。
src/cmd/cgo/out.go
func (p *Package) writeDefs() {
// ...
if *importSyscall {
// _Cerrno関数の引数型がintからint32に変更
fmt.Fprintf(fgo2, "func _Cerrno(dst *error, x int32) { *dst = syscall.Errno(x) }\n")
}
// ...
}
var goTypes = map[string]*Type{
// ...
"int": {Size: 0, Align: 0, C: c("GoInt")}, // SizeとAlignが0に
"uint": {Size: 0, Align: 0, C: c("GoUint")}, // SizeとAlignが0に
// ...
}
func (p *Package) cgoType(e ast.Expr) *Type {
// ...
if t.Name == "string" {
// string型のサイズ計算が変更 (ポインタ2つ分として扱う)
return &Type{Size: 2 * p.PtrSize, Align: p.PtrSize, C: c("GoString")}
}
// ...
if r, ok := goTypes[t.Name]; ok {
if r.Size == 0 { // int or uintの場合
rr := new(Type)
*rr = *r
rr.Size = p.IntSize // Package.IntSizeに基づいてサイズを設定
rr.Align = p.IntSize // Package.IntSizeに基づいてアライメントを設定
r = rr
}
// ...
}
// ...
}
func (p *Package) gccExportHeaderProlog() string {
// gccExportHeaderProlog内のGOINTBITSを実際のビット数に置き換える
return strings.Replace(gccExportHeaderProlog, "GOINTBITS", fmt.Sprint(8*p.IntSize), -1)
}
const gccExportHeaderProlog = `
// ...
typedef GoIntGOINTBITS GoInt; // 動的に置き換えられるプレースホルダー
typedef GoUintGOINTBITS GoUint; // 動的に置き換えられるプレースホルダー
// ...
`
out.go
は、cgo
がGoとCの間のインターフェースとなるコードを生成する部分です。
_Cerrno
の変更は、syscall.Errno
が通常32ビットであるという前提を強化し、int
のサイズが可変になることによる影響を局所化します。
goTypes
マップでint
とuint
のSize
とAlign
を0
に設定し、cgoType
関数内でPackage.IntSize
に基づいて動的に設定することで、Goのint
とuint
がCのint
の実際のサイズに合わせられるようになります。
string
型のサイズ計算の変更は、Goの文字列がポインタと長さ(int)の組み合わせで表現されるが、メモリのアライメントの都合上、常に2つのポインタサイズとして扱われることを反映しています。
最も重要な変更は、gccExportHeaderProlog
が動的に生成されるようになった点です。これにより、cgo
が生成するCヘッダファイル(_cgo_export.h
など)内で、GoInt
とGoUint
が、ターゲットアーキテクチャのint
サイズ(例: GoInt32
やGoInt64
)に合わせたtypedef
として定義されるようになります。これは、CコードがGoのint
型を正しく解釈するために不可欠です。
関連リンク
- GitHubコミット: https://github.com/golang/go/commit/5501a097a9ec5d082dad447c92d0eac298dede4d
- Go CL (Change List): https://golang.org/cl/6548061
参考にした情報源リンク
- Go cgo int size architecture: https://stackoverflow.com/questions/28000000/what-is-the-size-of-c-int-in-go-cgo
- Go cgo int size architecture: https://go.dev/blog/c-go-cgo
- Go unsafe.Pointer explanation: https://go101.org/article/unsafe.html
- Go DWARF format cgo: https://ragoragino.dev/posts/go-dwarf-debugging/
- Go DWARF format cgo: https://uber.github.io/go-torch/