お久しぶりです、ANDPADボードの tomtwinkle です。
この記事はGoの go:linkname
騒動は 6/18に行われた Go Bash で話した内容を要約したものです。
そもそも go:linkname
とは何かといえば
internal packageやprivate var/funcなど普通はアクセスできないオブジェクトシンボルをエイリアス出来るようCompilerに指示して、アクセス可能にするcompiler directiveです。
go:linkname
はprivateな変数へアクセス可能な便利なものでしたが
unsafe packageのimportを必須とする通り、せっかく互換性や安全を考慮して作られているGoプログラムを簡単に破壊できる諸刃の剣でした。
詳細は発表スライドを見てください。
go:linkname 禁止騒動
Go 1.23 のリリースまで2ヶ月を切ったタイミングで突如吹き上がった #67401 というissueが一部のOSS開発を行っているGopher達をザワつかさせていました。
事の発端は今から1ヶ月程前、 goccy/go-json
というGoでは有名なOSSのjson libraryに、GoのコントリビューターであるIan Lance Taylor氏がとあるissueが立ち上げました。
goccy/go-json
は 標準packageで提供されている encoding/json
コンパチのまま高速化することを売りにしているlibraryです。
README の Unique speed-up technique の項に記述されている通りreflect周りのエスケープ処理をあえて無視する結構攻めたhackを一部処理で差し込んでおり、Goの標準packageであるreflectのprivate structにアクセスするため go:linkname
を利用しています。
//go:linkname rtype_Method reflect.(*rtype).Method //go:noescape func rtype_Method(*Type, int) reflect.Method func (t *Type) Method(a0 int) reflect.Method { return rtype_Method(t, a0) }
go:linknameと runtime.fastrand64() の課題
先述のissueは Go 1.23 のリリースでreflectの内部実装が変わるため go:linkname
でprivate structを呼んでいる goccy/go-json
が動作しなくなる旨を警告するものでした。
GitHubを探してみると //go:linkname
を利用しているlibraryは他にもたくさん見つかりますがでは何故ここで goccy/go-json
が警告の対象になったのかというと、 goccy/go-json
がKubernetesなど非常に多くのOSSで利用されており影響範囲が大きかったためです。
というわけで、一番最初の Russ Cox氏 による golang/go の issue #67401 に立ち戻ります。
Goは互換性を非常に大事にしている言語であるため、Goのメジャーバージョンが上がったことにより今まで動いていたGoプログラムが動かなくようなことがないよう非常に気を使って開発されています。
go:linkname
のような下手すればビルドエラーにもならずに実行時に初めて深刻なメモリエラーが発生するような危険な実装は、プロダクションコード内で動作するようなlibraryで実装されることを想定していなかったのかもしれません。
go:linkname
も ユーザーのlibrary内で閉じた範囲でunsafe なことを承知で使うという場合は問題なかったのですが
Go 1.20 までのmath/rand.Uint64()
はlock処理がボトルネックになっており、seedの初期値などを生成するために頻繁に乱数を生成する場合、同一のgoroutine内で処理可能でより高速に動作する内部実装 runtime.fastrand()
や runtime.fastrand64()
を go:linkname
で内部利用する記述が割と一般的に行われていたりしました。
弊社の小島さんが Go Conference 2024 で発表したSwissTableでも SwissTableのサンプル実装にはseed値の作成のために go:linkname
で runtime.fastrand64()
が利用されています。
speakerdeck.com
package swiss import "unsafe" import _ "unsafe" //go:linkname fastrand64 runtime.fastrand64 func fastrand64() uint64 – map.go – func (m *Map[K, V]) Init(initialCapacity int, options ...Option[K, V]) { seed: uintptr(fastrand64()), func (m *Map[K, V]) All(yield func(key K, value V) bool) { // Randomize iteration order by starting iteration at a random bucket and // within each bucket at a random offset. offset := uintptr(fastrand64())
Go 1.21 での math/rand ステルス修正
先ほど Go 1.20 までのmath/rand.Uint64()は
と書きましたが
Go 1.21 より math/rand.Uint64()
は内部的に runtime.fastrand64()
を呼ぶように修正されています。
– src/math/rand/rand.go – L318: func globalRand() *Rand { L330: r = &Rand{ L331: src: &fastSource{}, L332: s64: &fastSource{}, L333: } L347: } L349: //go:linkname fastrand64 L350: func fastrand64() uint64 L351: L352: // fastSource is an implementation of Source64 that uses the runtime L353: // fastrand functions. L354: type fastSource struct { L355: // The mutex is used to avoid race conditions in Read. L356: mu sync.Mutex L357: } L367: func (*fastSource) Uint64() uint64 { L368: return fastrand64() L369:} L431: func Uint64() uint64 { return globalRand().Uint64() }
この事にGo 1.21時点で触れている記事は自分が観測する限りでは存在せず、自分自身も今回調べて初めて知ったのですが それもそのはず、Go 1.21のリリースノートにはそももそ書かれていませんでした。
この事は golang/go の issue を細かくwatchしている Gopherのみ知っている情報だったかもしれません。
これによりGo 1.21 よりわざわざ math/rand.Uint64()
の代わりに runtime.fastrand64()
を利用するために go:linkname
を利用する必要がなくなっていました。
Go 1.22 で実装された math/rand/v2
も同様の実装を行っているため、go:linkname
で無理やりruntimeにアクセスせず、素直に標準packageを使っとけという状態に立ち返ったのでした。
もしかすると Russ Cox氏らは go:linkname
と runtime.fastrand64()
の課題を事前に把握した上で修正版を出し
課題が解決したことで今回Go 1.23 で満を期して一気に禁止という流れに行ったのかもしれません。
Genericsやiterの件でもそうですがGoチームは結構こういった根気の入った根回しをするんですよね。
go:linkname
禁止騒動のその後
#67401 でも go:linkname
は Handshake方式の場合のみは許可すると前置きされていました。
Handshakeとはgo:linkname
を利用するPush先のオブジェクトシンボルとPull元のオブジェクトシンボルがお互いに go:linkname
で利用するpackageをホワイトリスト形式で記載している場合です。
結局 go:linkname
を現状使っている著名なOSSがどうなったかというと、「不名誉殿堂入りリスト」という名のもと、標準package内にHandshake方式でpackage名が記載される流れとなりました。
こうしてみると色々なlibraryがruntimeに依存していたことが分かりますね。 今回は「不名誉殿堂入りリスト」入りだけで許してやるが新規libraryは許さんという感じの決着になっています。
Go 1.23のDraft版リリースノートにも割と強めに Any new references to standard library internal symbols will be disallowed.
と書かれているので今後新しく「不名誉殿堂入りリスト」するlibraryが現れることは恐らくないんじゃないかなと思います。
Goを使ってカリカリにパフォーマンスチューニングしたい場合は、今「不名誉殿堂入りリスト」に入ってるlibraryを利用するか大人しくissueを立てて議論するしかなさそうです。
アンドパッドでは Go の issue や開発模様をウォッチするほど、 Go を愛してやまない Gopher を募集しています!