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

[インデックス 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型を扱う際に、そのサイズをハードコードするのではなく、ターゲットアーキテクチャに基づいて動的に決定できるようにすることです。

具体的には、以下の変更が行われています。

  1. Package構造体へのIntSizeフィールドの追加: src/cmd/cgo/main.goPackage構造体にIntSize int64フィールドが追加されました。このフィールドは、現在のターゲットアーキテクチャにおけるC言語のint型のサイズ(バイト単位)を保持します。
  2. intSizeMapの導入: src/cmd/cgo/main.gointSizeMapという新しいマップが追加されました。これはGOARCH(Goのターゲットアーキテクチャ)をキーとして、対応するint型のサイズ(バイト単位)を値として持ちます。初期状態では、386, amd64, armのいずれも4(32ビット)に設定されています。これは、これらのアーキテクチャではCのintが通常32ビットであることを反映しています。
  3. newPackage関数でのIntSizeの初期化: src/cmd/cgo/main.gonewPackage関数内で、GOARCHに基づいてintSizeMapからIntSizeが取得され、Package構造体に設定されるようになりました。これにより、cgoの処理全体でintのサイズ情報が利用可能になります。
  4. typeConv.Init関数の変更: src/cmd/cgo/gcc.gotypeConv.Init関数が、ptrSizeに加えてintSizeも引数として受け取るように変更されました。typeConvは、DWARF情報からGoの型への変換を行う際に使用される構造体であり、これにより型変換のロジックがintの実際のサイズを考慮できるようになります。
  5. out.goにおけるintおよびuintのサイズ設定の動的化: src/cmd/cgo/out.goでは、Goの組み込み型であるintuintのサイズがgoTypesマップ内で0に設定されました。これは、これらの型の実際のサイズがPackage.IntSizeに基づいて動的に決定されることを意味します。 cgoType関数内で、intまたはuint型が処理される際に、r.Size == 0(つまり、サイズが動的に決定されるべき型)であれば、Package.IntSizeがその型のSizeAlignに設定されるようになりました。これにより、cgoが生成するコードが、ターゲットアーキテクチャのintサイズに合わせた型定義を使用するようになります。
  6. _Cerrno関数の引数型の変更: src/cmd/cgo/out.goで、_Cerrno関数の引数xの型がintからint32に変更されました。これは、syscall.Errnoが通常32ビット整数で表現されることを明示し、intのサイズが可変になることによる潜在的な問題を回避するためと考えられます。
  7. gccExportHeaderPrologの動的生成: src/cmd/cgo/out.goで、gccExportHeaderPrologというCヘッダのプロローグ部分が、Package.IntSizeに基づいて動的に生成されるようになりました。具体的には、typedef GoIntGOINTBITS GoInt;のような行が追加され、GOINTBITSが実際のintのビット数(8 * p.IntSize)に置き換えられます。これにより、生成されるCヘッダファイル内でGoIntGoUintが、ターゲットアーキテクチャのintサイズに合わせた型(例: GoInt32GoInt64)として定義されるようになります。

これらの変更により、cgoint型のサイズをより正確に扱い、異なるアーキテクチャ間での互換性を向上させます。

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

このコミットでは、以下の3つのファイルが変更されています。

  1. src/cmd/cgo/gcc.go:
    • typeConv.Init関数のシグネチャが変更され、intSize引数が追加されました。
    • typeConv構造体にintSizeフィールドが追加されました。
    • conv.Initの呼び出し箇所で、新しいintSize引数が渡されるようになりました。
  2. src/cmd/cgo/main.go:
    • Package構造体にIntSizeフィールドが追加されました。
    • intSizeMapという新しいマップが定義され、GOARCHごとのintサイズが設定されました。
    • newPackage関数内で、intSizeMapからIntSizeが取得され、Package構造体に設定されるようになりました。
  3. src/cmd/cgo/out.go:
    • _Cerrno関数の引数型がintからint32に変更されました。
    • gccExportHeaderPrologが直接文字列として定義されるのではなく、p.gccExportHeaderProlog()という関数呼び出しによって動的に生成されるようになりました。
    • goTypesマップ内の"int""uint"SizeAlign0に設定されました。
    • cgoType関数内で、intまたはuintSizeAlignPackage.IntSizeに基づいて設定されるロジックが追加されました。
    • string型のサイズ計算がp.PtrSize + 4から2 * p.PtrSizeに変更されました。これは、文字列がポインタと長さ(int)で構成されるが、アライメントにより2ポインタサイズに丸められることを考慮したものです。
    • gccExportHeaderPrologの定数定義が変更され、GoIntGoUinttypedefGoIntGOINTBITSGoUintGOINTBITSに依存するように変更されました。
    • 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マップでintuintSizeAlign0に設定し、cgoType関数内でPackage.IntSizeに基づいて動的に設定することで、GoのintuintがCのintの実際のサイズに合わせられるようになります。 string型のサイズ計算の変更は、Goの文字列がポインタと長さ(int)の組み合わせで表現されるが、メモリのアライメントの都合上、常に2つのポインタサイズとして扱われることを反映しています。 最も重要な変更は、gccExportHeaderPrologが動的に生成されるようになった点です。これにより、cgoが生成するCヘッダファイル(_cgo_export.hなど)内で、GoIntGoUintが、ターゲットアーキテクチャのintサイズ(例: GoInt32GoInt64)に合わせたtypedefとして定義されるようになります。これは、CコードがGoのint型を正しく解釈するために不可欠です。

関連リンク

参考にした情報源リンク