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

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

このコミットは、Go言語のcmd/cgoツールにおける_cgo_gotypes.goファイルの生成に関する変更です。具体的には、生成されるファイルの安定性を向上させるために、型定義(typedef)マップの走査順序を一定に保つように修正されています。

コミット

commit a49172663cb4082dcbafa8200b70c93b1c19314a
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Thu Jul 5 15:24:33 2012 -0400

    cmd/cgo: make typedef map traversal order consistent
            So that _cgo_gotypes.go will be the same for the same source
            code.
    
    R=nigeltao
    CC=golang-dev
    https://golang.org/cl/6357067

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

https://github.com/golang/go/commit/a49172663cb4082dcbafa8200b70c93b1c19314a

元コミット内容

cmd/cgo: make typedef map traversal order consistent
        So that _cgo_gotypes.go will be the same for the same source
        code.

変更の背景

Go言語において、map(ハッシュマップ)の要素の走査順序は保証されていません。これは、マップが内部的にハッシュテーブルとして実装されており、要素の挿入順序や削除、リサイズなどによって、イテレーションの順序が変化する可能性があるためです。

cmd/cgoは、C言語のコードをGo言語から呼び出すためのツールであり、Cの型定義(typedef)をGoの型にマッピングする処理を行います。このマッピングされた型定義は、_cgo_gotypes.goというファイルに書き出されます。

問題は、typedefマップの走査順序が不定であるため、同じCのソースコードからcgoを実行しても、生成される_cgo_gotypes.goファイルの内容が毎回異なる可能性があったことです。これは、バージョン管理システム(Gitなど)において、ソースコードが変更されていないにもかかわらず、生成されたファイルが変更されたと認識され、不必要な差分(diff)が発生するという問題を引き起こします。このような不必要な差分は、コードレビューを困難にし、ビルドシステムのキャッシュを無効にするなど、開発プロセスに悪影響を及ぼします。

このコミットは、この問題を解決し、_cgo_gotypes.goファイルが同じソースコードに対して常に同じ内容になるようにするために行われました。

前提知識の解説

  • cmd/cgo: Go言語の標準ツールの一つで、C言語のコードをGoプログラムから呼び出すためのバインディングを生成します。Cの関数や型をGoのコードで利用できるようにするための橋渡しをします。
  • _cgo_gotypes.go: cgoツールが生成するGoのソースファイルの一つです。Cの型定義(typedef)や構造体、共用体などがGoの型としてどのように表現されるか、そのマッピング情報が含まれています。このファイルは、CとGoの間でデータをやり取りする際の型変換の基盤となります。
  • Go言語のmapの走査順序: Go言語のmapは、意図的に走査順序が保証されていません。これは、開発者が特定の順序に依存しないようにするため、また、実装の自由度を高めるためです。順序が必要な場合は、キーをスライスに抽出し、ソートしてから走査するなどの明示的な処理が必要です。
  • 決定論的ビルド (Deterministic Builds): 同じソースコードとビルド環境であれば、常に同じバイナリや生成物(この場合は_cgo_gotypes.go)が生成されることを指します。決定論的ビルドは、再現性、キャッシュの効率化、セキュリティ監査の容易さなどの点で重要です。

技術的詳細

この変更は、src/cmd/cgo/out.goファイル内のwriteDefs関数に焦点を当てています。この関数は、Cの型定義をGoの型に変換し、_cgo_gotypes.goファイルに書き出す役割を担っています。

変更前は、typedefというマップを直接for name, def := range typedefという構文で走査していました。前述の通り、Goのマップの走査順序は不定であるため、この方法では_cgo_gotypes.goに書き出される型定義の順序が実行ごとに変わる可能性がありました。

このコミットでは、この問題を解決するために以下の手順が導入されました。

  1. キーの抽出: typedefマップのすべてのキー(型定義の名前)をtypedefNamesという新しい文字列スライスに抽出します。
  2. キーのソート: 抽出したtypedefNamesスライスをsort.Strings関数を使用して辞書順にソートします。
  3. ソートされたキーでの走査: ソートされたtypedefNamesスライスを順に走査し、それぞれの名前を使ってtypedefマップから対応する型定義(def)を取得します。

この変更により、_cgo_gotypes.goファイルに書き出される型定義の順序が常に辞書順となり、同じソースコードからは常に同じ内容のファイルが生成されることが保証されます。

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

src/cmd/cgo/out.goファイルのwriteDefs関数内。

--- a/src/cmd/cgo/out.go
+++ b/src/cmd/cgo/out.go
@@ -14,6 +14,7 @@ import (
  	"go/printer"
  	"go/token"
  	"os"
+	"sort"
  	"strings"
  )
 
@@ -57,7 +58,13 @@ func (p *Package) writeDefs() {
  	fmt.Fprintf(fgo2, "type _ unsafe.Pointer\\n\\n")
  	fmt.Fprintf(fgo2, "func _Cerrno(dst *error, x int) { *dst = syscall.Errno(x) }\\n")
 
-	for name, def := range typedef {
+	typedefNames := make([]string, 0, len(typedef))
+	for name := range typedef {
+		typedefNames = append(typedefNames, name)
+	}
+	sort.Strings(typedefNames)
+	for _, name := range typedefNames {
+		def := typedef[name]
  		fmt.Fprintf(fgo2, "type %s ", name)
  		conf.Fprint(fgo2, fset, def.Go)
  		fmt.Fprintf(fgo2, "\\n\\n")

コアとなるコードの解説

  • import ("sort"): sortパッケージが新しくインポートされています。これは、文字列スライスをソートするために必要です。
  • typedefNames := make([]string, 0, len(typedef)): typedefマップのキーを格納するための文字列スライスtypedefNamesを初期化しています。len(typedef)を容量として指定することで、アロケーションを最適化しています。
  • for name := range typedef { typedefNames = append(typedefNames, name) }: typedefマップを走査し、各キー(型定義の名前)をtypedefNamesスライスに追加しています。この時点での走査順序は不定ですが、キーはすべてスライスに集められます。
  • sort.Strings(typedefNames): typedefNamesスライスに含まれる文字列(型定義の名前)を辞書順にソートします。これにより、以降の処理で型定義が常に一定の順序で処理されるようになります。
  • for _, name := range typedefNames { def := typedef[name] ... }: ソートされたtypedefNamesスライスを順に走査し、各nameに対応するdef(型定義の構造体)をtypedefマップから取得しています。このループ内で、_cgo_gotypes.goファイルへの書き出しが行われるため、出力される型定義の順序が保証されます。

この変更は、Go言語のマップの特性を理解し、その非決定論的な挙動がビルドプロセスに与える影響を回避するための典型的なパターンを示しています。

関連リンク

  • Go言語のmapの仕様: https://go.dev/blog/maps (Go言語のマップに関する公式ブログ記事)
  • cmd/cgoのドキュメント: https://pkg.go.dev/cmd/cgo (Go言語のcgoコマンドに関する公式ドキュメント)
  • Go言語のsortパッケージ: https://pkg.go.dev/sort (Go言語のソート機能を提供するパッケージの公式ドキュメント)

参考にした情報源リンク

  • 上記のGitHubコミットページ
  • Go言語の公式ドキュメント
  • Go言語のマップの非決定論的順序に関する一般的な知識
  • 決定論的ビルドに関する一般的なソフトウェアエンジニアリングの概念