【Go】 machoは「マッチョ」ではない

公開:2020/12/25
更新:2020/12/28
4 min読了の目安(約4000字TECH技術記事

本記事は、Go 4 Advent Calendar 2020の25日目の記事です。

macho package を眺めたことはあるでしょうか。こちらはmachoなので呼び方を表題でピンとこない方は要注意かもしれません。

背景: Go 1.15 on macOSでperformance regressionというIssue

同僚の@budougumi0617さんが『私がGoのソースコードを読むときのTips』にて、Goコードリーディング会を継続して開催していることを紹介しておりました。

https://devblog.thebase.in/entry/go-code-reading

それに対して、弊社ではBASEメンバーと合同で定期的にGoコードリーディングパーティを実施しています。
ひとりでは詰まってしまうようなこともみんなで見ていると解決の糸口がみつかったり、他のメンバーのエディタ捌きを盗むことができます。

12月25日本日、@dice_zuさんをゲストに迎えて testing パッケージ内のキャッシュの挙動について読んでいたりしたのですが、雑談の中で 「vimのslackで昔@itchynyさんがGo 1.15になった際にmacOSでちょっと遅くなった気がするという話をされてすごいと思った」っていう話題が上がり、「このIssueですか〜」というissueをとりあげた。

https://github.com/golang/go/issues/40727

はて、このIssueはどうやって解決しているのかな?とよんでいったのが始まりです。

表題のIssueです。

https://go-review.googlesource.com/c/go/+/248719/

このIssueの差分は次のようなif文です。

src/cmd/oldlink/internal/ld/go.go
if lib != "" && ctxt.HeadType == objabi.Hdarwin {
	machoadddynlib(lib, ctxt.LinkMode)
}

https://go-review.googlesource.com/c/go/+/248719/2/src/cmd/link/internal/ld/go.go

現在のバージョンのコードではちょっと変わっていて if-else での処理となっています。

src/cmd/oldlink/internal/ld/go.go
if local == "_" && remote == "_" {
	// allow #pragma dynimport _ _ "foo.so"
	// to force a link of foo.so.
	havedynamic = 1

	if ctxt.HeadType == objabi.Hdarwin {
		machoadddynlib(lib, ctxt.LinkMode)
	} else {
		dynlib = append(dynlib, lib)
	}
	continue
}

内容としては、objabi.Hdarwinの場合、つまりMacOSに該当するようなオペレーションシステムの場合はmachoadddynlib()関数を呼び、そうではない場合はdynamic importを行なうというものです。

当該Issueでの指摘は、

Remove the unconditional imports in the runtime. Now,
Security.framework and CoreFoundation.framework are only linked
when the x509 package is imported (or otherwise specified).

とある通り、不要なimportを取り除くという趣旨ですので、Security.frameworkやCoreFoundation.frameworkは取り除くという振る舞いを実装したと推測されます。

これは、同僚の@glassemonkeyさんよりSwiftではよくCoreFoundation使うよねという情報提供があり、それに該当する奴らなのねっていうことが会の中では話に上がりました。

このPRのsrc/runtime/sys_darwin.goでもその旨が理解できるコメントの削除が確認できます。

https://go-review.googlesource.com/c/go/+/248719/2/src/runtime/sys_darwin.go

machoadddynlib

ここで出てくるmachoとはなんぞやという話題です。結論から言うと「マッチョ」ではないです(そらそうよ)。

https://twitter.com/hgsgtk/status/1342368642797764608

これは、「Mach-O(マーク・オー)」です。macOS標準のバイナルファイルフォーマットとして採用されているファイルフォーマットです。

https://ja.wikipedia.org/wiki/Mach-O

今回のIssueはMacOSに関するruntimeについてですので、「Mach-O」の関連性はしっかり点と点で繋がります。

パッケージとしては、debug/machoという強そうなパッケージがあります。

https://twitter.com/dice_zu/status/1342375743876001792

machoは「マーク・オー」

マーク・オーだよ

余談: testingパッケージとgocachetest

冒頭でTestingパッケージについても読んだ話を書きました。@budougumi0617さんがこんな記事ありますよね〜と『Go ランタイムのデバッグをサポートする環境変数 by @mattnさん』をシェアしてくださりました。

gocachetestという設定はキャッシュされたテスト結果を再利用するかどうかの詳細を表示するオプションです。これが実際内部ではどういう扱いについてちょっとだけ読みました(これ自体はサイドメニュー的な内容ですが)。

まず、gochachetestを有効にすると、以下のコードでDebugTestがtrueになります。

src/cmd/go/internal/cache/cache.go
func initEnv() {
	verify = false
	debugHash = false
	debug := strings.Split(os.Getenv("GODEBUG"), ",")
	for _, f := range debug {
		if f == "gocacheverify=1" {
			verify = true
		}
		if f == "gocachehash=1" {
			debugHash = true
		}
		if f == "gocachetest=1" {
			DebugTest = true
		}
	}
}

https://golang.org/src/cmd/go/internal/cache/cache.go#L130

以降、テストを実行する際に、DebugTestをみてlogを出す分岐のコードが散りばめられています。

cmd/go/internal/test/test.go
func (c *runCache) tryCacheWithID(b *work.Builder, a *work.Action, id string) bool {
	if len(pkgArgs) == 0 {
		// Caching does not apply to "go test",
		// only to "go test foo" (including "go test .").
		if cache.DebugTest {
			fmt.Fprintf(os.Stderr, "testcache: caching disabled in local directory mode\n")
		}
		c.disableCache = true
		return false
	}

https://golang.org/src/cmd/go/internal/test/test.go#L1304

それではGopherのみなさんメリークリスマス!