Slide 1

Slide 1 text

© 2024 ANDPAD All Rights Reserved. about #67401 //go:linkname

Slide 2

Slide 2 text

© 2024 ANDPAD All Rights Reserved. Confidential ANDPADとは 社内 現場 営業 / 監督 / 設計 事務 / 管理職 職人 / 業者 メーカー / 流通 現場の効率化から経営改善まで一元管理できる クラウド型建設プロジェクト管理サービス 案件管理 資料 工程表 写真 報告 チャット 黒板 図面 受発注 ● ● ● 
 


Slide 3

Slide 3 text

© 2024 ANDPAD All Rights Reserved. Confidential Go を間接的に使っているプロダクト ANDPAD の Go のプロダクト Go がメインのプロダクト The Go gopher was designed by Renée French. 施工管理 図面 引合粗利管理 検査 黒板 受発注 ボード 資料承認 おうちノート …

Slide 4

Slide 4 text

© 2024 ANDPAD All Rights Reserved. Speaker tomtwinkle tomoki.tsuchiya ANDPAD ボード Product TL

Slide 5

Slide 5 text

Copyright © 2023 ANDPAD Inc. Go 1.23 Release Note(Draft) https://tip.golang.org/doc/go1.23

Slide 6

Slide 6 text

Copyright © 2024 ANDPAD Inc. Go 1.23 highlight ● Compiler: PGOによるオーバーヘッドの削減 ● Timer/Ticker: 明⽰的なStop/Resetが不要になった ● Timer/Ticker: バッファ付きチャネル利⽤取りやめ、それによる遅延解消 ● unique package 爆誕 ● iter package 爆誕 ○ slices package に iter を利⽤する関数が追加 ○ maps package に iter を利⽤する関数が追加 ● slices: Repeat実装 ● sync.Map: Clear実装 ● structs package 爆誕(⾃作ライブラリの名前被り多発事案) ● tcrypto/tls: Encrypted Client Hello(draft)に対応、SNIの暗号化が可能に ● crypto/x509: OID type 実装、ASN.1の⽂字列⇔バイト配列の変換が容易に ● database/sql: driver.ValuerのErrorがWrapされるように変更 ● reflect: 桁溢れ検知関数(OverflowXX)の追加 ● go mod tidy -diff ● and more...

Slide 7

Slide 7 text

Copyright © 2023 ANDPAD Inc. これらの話は今回しません

Slide 8

Slide 8 text

Copyright © 2023 ANDPAD Inc. #67401 cmd/link: lock down future uses of linkname

Slide 9

Slide 9 text

© 2024 ANDPAD All Rights Reserved. https://github.com/goccy/go-json/issues/506 序章

Slide 10

Slide 10 text

© 2024 ANDPAD All Rights Reserved. 序章 goccy/go-json パッケージがGoのランタイムの内部APIの大部分をコピーしていたことが問題とな り、Goの開発者である ianlancetaylor によってIssueが開かれました。このパッケージが非常に危 険でサポートされていない操作を行っていると指摘され、その一つが GoのHEAD(将来の1.23リリー ス)で壊れてしまったと述べられました。 この問題は、Goの開発者である rsc の注意を引きました。彼は、Goの標準ライブラリの内部(特に ランタイムの内部)に //go:linkname を使ってアクセスすることが多すぎると指摘しました。これによ り、内部的には重要でないはずの標準ライブラリの変更が、 Goのエコシステムの大部分に依存して いるパッケージを壊す可能性があると彼は警告しました。 この問題を解決するために、rsc は新たな //go:linkname ベースの依存関係を防ぎ、既存のものを 制約する作業を追跡するIssueを開きました。 (Copilotによる意訳)

Slide 11

Slide 11 text

© 2024 ANDPAD All Rights Reserved. https://github.com/golang/go/issues/67401 //go:linkname (Handshake以外で) 使うの禁止しよう! 意訳 ● Kubernetesなどで利用されている goccy/go-json は //go:linkname を用いてランタ イム内部のprivate struct(reflect.rType)を利用 していたためCL583756により壊れた ● Goは互換性に気を配って開発されており、 Go のバージョンアップにより Goプログラムが破壊 される状況を望んでいない ● Go 1.23で -checklinkname=1 フラグを cmd/link に追加しデフォルト有効化する ハンドシェイク形式以外の //go:linkname を禁 止する

Slide 12

Slide 12 text

© 2024 ANDPAD All Rights Reserved. //go:linknameとは package main import "fmt" "time" _ "unsafe" ) //go:linkname MockNow time.Now func MockNow() time.Time { return time.Date(2024, 6, 1, 10, 0, 0, 0, time.Local) } func main() { fmt.Printf("mockTime:%s", time.Now()) } package main import ( "fmt" "time" _ "unsafe" ) //go:linkname MockNow time.Now func MockNow() time.Time { return time.Date(2024, 6, 1, 10, 0, 0, 0, time.Local) } func main() { fmt.Printf("mockTime:%s", time.Now()) // mockTime:2024-06-01 10:00:00 +0900 JST } unsafeが必須 Goのcompiler directiveであるgo:linknameは private(internal)な変数または関数のオブジェク トシンボルを定義した別名のものと同じもののよ うにエイリアス出来るようコンパイラに指示でき ます。 型システムやモジュール性を破壊するため unsafe packageを明示的にimportする必要が あります。 この例ではパッケージ内でグローバルに標準ラ イブラリのtime.Now関数を独自のMockNow関 数を上書きします。

Slide 13

Slide 13 text

© 2024 ANDPAD All Rights Reserved. https://github.com/golang/go/issues/67401 //go:linkname Push/Pull/Handshake…?

Slide 14

Slide 14 text

© 2024 ANDPAD All Rights Reserved. https://github.com/golang/go/issues/67401 //go:linkname Push https://go.dev/play/p/jTjcvGARWNM – hoge/hoge.go – package hoge import _ "unsafe" //go:linkname getHoge /fuga.getHoge func getHoge() string { return "hoge" } – fuga/fuga.go – package fuga import _ "/hoge" func getHoge() string – fuga/dummy.s – var/funcオブジェクトシンボ ルを別packageのシンボ ルで利用できるようPush する time.NowのmockはPush を利用している そのままではコンパイラがpackage fugaの buildを行えないため、go tool compileに渡 さないように *.s ファイル(拡張子がsであれ ばファイル名は何でも良い)をpackage内に 配置する

Slide 15

Slide 15 text

© 2024 ANDPAD All Rights Reserved. https://github.com/golang/go/issues/67401 //go:linkname Pull https://go.dev/play/p/LRaTfIRnC8m – hoge/hoge.go – package hoge func getHoge() string { return "hoge" } – fuga/fuga.go – package fuga import ( _ "unsafe" _ "/hoge" ) //go:linkname getHoge /hoge.getHoge func getHoge() string 別packageのvar/funcオブ ジェクトシンボルを package内のシンボルに Pullする 後述する runtime.fastrand64などを 利用する際に記載されて いる事が多い

Slide 16

Slide 16 text

© 2024 ANDPAD All Rights Reserved. https://github.com/golang/go/issues/67401 //go:linkname Handshake https://go.dev/play/p/Yr3bkzDKqI8 – hoge/hoge.go – package hoge import _ "unsafe" //go:linkname getHoge /fuga.getHoge func getHoge() string { return "hoge" } – fuga/fuga.go – package fuga import ( _ "unsafe" _ "/hoge" ) //go:linkname getHoge func getHoge() Pushしたいオブジェクトシ ンボルとPullするオブジェ クトシンボル双方に go:linknameを指定する お互い関数の利用を意識 された設計

Slide 17

Slide 17 text

© 2024 ANDPAD All Rights Reserved. //go:linknameの問題点 ● internal packageやprivateな関数や変数を利用 できる ⇨可視性/スコープの破壊 – internal/hoge.go – // projectA package internal func GetHoge() string { return "hoge" } – go.mod – module projectA – fuga.go – // projectB package fuga import projectA/internal/hoge //go:linkname GetHoge projectA/hoge.GetHoge func GetHoge() string – go.mod – module projectB internal packageの場合 本来import出来ないが利 用できてしまう

Slide 18

Slide 18 text

© 2024 ANDPAD All Rights Reserved. //go:linknameの問題点 ● Pull対象のオブジェクトが存在しない場合以外 はコンパイルエラーにならない ● プログラム自体は動作してしまうため、深刻な 型関連のメモリエラーが発生する ● 有害・危険だからこその “unsafe” https://go.dev/play/p/GGK8eC3J396 – hoge/hoge.go – package hoge func getHoge() int { return 100 } – fuga/fuga.go – package fuga import ( _ "unsafe" _ "/hoge" ) //go:linkname getHoge /hoge.getHoge func getHoge() string

Slide 19

Slide 19 text

© 2024 ANDPAD All Rights Reserved. //go:linknameが必要な処理 Handshakeを利用するパターン ● runtimeを利用する標準package内では今後も 必須 ● 互換性担保が難しいため公開したくないが内部 では利用したい便利関数が含まれる共通ライブ ラリなどのinternal package (でも、内部ライブラリなら publicでよくない…?) ● 同じ処理をコピペで増やす必要もなく、テストも 1 箇所で済む ● Hanshakeで公開先が明示的に示されている場 合は今後も利用可能であるため、このケースの 利用は許容される – internal/hoge.go – package internal //go:linkname getHoge /hoge.getHoge func getHoge() string { // とてつもなく複雑なHogeの処理 return hoge } – hoge.go – package hoge import ( _ "unsafe" _ "/runtime/hoge" ) //go:linkname getHoge func getHoge() string func SomethingHoge() { var hoge = getHoge() // 省略 }

Slide 20

Slide 20 text

© 2024 ANDPAD All Rights Reserved. //go:linknameが必要な処理 Pullが必要である恐らく唯一のパターン ● runtime内でユーザー定義のエントリポイント関 数を利用したい場合 プログラムのエントリポイントは runtime内にあ る必要があるが、runtime内ではmain package はインポートできないため、 //go:linkname で処 理をバイパスする ● こんなパターンはそもそも使うべきではない – runtime/hoge.go – package runtime //go:linkname main_main main.main func main_main() func main() { main_main() } – main.go – package main func main() { // runtimeから呼びたいユーザーの処理 } runtimeからユーザーの main packageはイン ポートできない https://www.cnblogs.com/apocelipes/p/18195214

Slide 21

Slide 21 text

© 2024 ANDPAD All Rights Reserved. //go:linkname と math/rand Go 1.20 まで ● math/randは別goroutineで走ることも考慮して 毎回lockかけているのでlockのオーバヘッドが 発生し遅い ● globalRandにlockedSource利用しているので 全体的にオーバヘッドのかかる処理になってい た https://cs.opensource.google/go/go/+/refs/tags/go1.20.1 4:src/math/rand/rand.go;l=403 – src/math/rand/rand.go – L304: var globalRand = New(new(lockedSource)) L334: // Uint64 returns a pseudo-random 64-bit value as a uint64 L335: // from the default Source. L336: func Uint64() uint64 { return globalRand.Uint64() } L403: type lockedSource struct { L404: lk sync.Mutex L405: s *rngSource // nil if not yet allocated L406: } L435: func (r *lockedSource) Uint64() (n uint64) { L436: r.lk.Lock() L437: n = r.source().Uint64() L438: r.lk.Unlock() L439: return L440: }

Slide 22

Slide 22 text

© 2024 ANDPAD All Rights Reserved. //go:linkname と math/rand Go 1.20 まで ● Seed作成するための乱数生成器に高速スレッ ドローカル擬似乱数生成器である runtime.fastrand64 を利用している ● runtime.fastrand64 は呼び出し元のgoroutine のcontextで動作するため、lockの必要がなく math/rand.Uint64() と fastrand64.Uint64() で は環境によりますが大体 5倍程度は早くなる https://cs.opensource.google/go/go/+/refs/tags/go1.20.1 4:src/math/rand/rand.go;l=403 – src/math/rand/rand.go – L408: //go:linkname fastrand64 L409: func fastrand64() uint64 L410: L411: var randautoseed = godebug.New("randautoseed") L412: L413: // source returns r.s, allocating and seeding it if needed. L414: // The caller must have locked r. L415: func (r *lockedSource) source() *rngSource { L416: if r.s == nil { L417: var seed int64 L418: if randautoseed.Value() == "0" { L419: seed = 1 L420: } else { L421: seed = int64(fastrand64()) L422: } L423: r.s = newSource(seed) L424: } L425: return r.s L426: }

Slide 23

Slide 23 text

© 2024 ANDPAD All Rights Reserved. //go:linkname と math/rand #54880 Go 1.21 ● GODEBUG=randautoseed=0 を指定してSeed を明示的に指定しない限り globalRand が runtime.fastrand64 を利用する ようになった ● runtime.fastrand64 を利用したいサードパー ティ製ライブラリは math/rand.Uint64() を使え ばよいので //go:linkname を利用する必要はな くなった https://cs.opensource.google/go/go/+/refs/tags/go1.21.0 :src/math/rand/rand.go;l=354;bpv=0 – 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() }

Slide 24

Slide 24 text

© 2024 ANDPAD All Rights Reserved. swiss table と runtime.fastrand64 ちなみに、cockroachdbによるswiss tableのサンプル 実装にも runtime.fastrand64 が利用されている go 1.21以降を利用するなら //go:linkname は消せる はず https://github.com/cockroachdb/swiss/blob/232b93a2b8 29cb6cbf31dba46703479d7d598bef/runtime_go1.20.go #L25-L30 – runtime_go1.20.go – //go:build go1.20 && !go1.24 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())

Slide 25

Slide 25 text

© 2024 ANDPAD All Rights Reserved. //go:linkname と math/rand/v2 https://cs.opensource.google/go/go/+/refs/tags/go1.22.0:src/math/rand/rand.go;l= 354;bpv=1;bpt=0 https://cs.opensource.google/go/go/+/refs/tags/go1.22.0:src/runtime/rand.go;l=12 6-127;bpv=1 Go 1.22 から ● math/rand/v2が登場し (fastSourceと似たよう な実装の) runtimeSource を利用するように なった ● math/randも併せて fastSource から runtimeSource へリネーム ● math/rand, math/rand/v2 双方が内部的に runtime.fastrand64(runtime.rand) を利用する ようになったため – src/math/rand/rand.go – L318: func globalRand() *Rand { L330: r = &Rand{ L331: src: &runtimeSource{}, L332: s64: &runtimeSource{}, L333: } L349: //go:linkname runtime_rand runtime.rand L350: func runtime_rand() uint64 L351: L352: // runtimeSource is an implementation of Source64 that uses the runtime L353: // fastrand functions. L354: type runtimeSource struct { L355: // The mutex is used to avoid race conditions in Read. L356: mu sync.Mutex L357: } L367: func (*runtimeSource) Uint64() uint64 { L368: return runtime_rand() L369: } L431: func Uint64() uint64 { return globalRand().Uint64() }

Slide 26

Slide 26 text

© 2024 ANDPAD All Rights Reserved. //go:linkname と runtime.fastrand64 math/rand, math/rand/v2 双方が内部的に runtime.fastrand64(runtime.rand) を利用 するようになった つまり、//go:linkname を利用するための理由の1つであるruntime.fastrand64 の利用 は Go1.21以降は既にmath/rand 及び math/rand/v2 を利用すればよいため消えてい る

Slide 27

Slide 27 text

© 2024 ANDPAD All Rights Reserved. https://github.com/golang/go/issues/67401 Go1.23 で開発者は何をすれば良いか 意訳 ● Kubernetesなどで利用されている goccy/go-json は //go:linkname を用いてランタ イム内部のprivate struct(reflect.rType)を利用 していたためCL583756により壊れた ● Goは互換性に気を配って開発されており、 Go のバージョンアップにより Goプログラムが破壊 される状況を望んでいない ● Go 1.23で -checklinkname=1 フラグを cmd/link に追加しデフォルト有効化する ハンドシェイク形式以外の //go:linkname を禁 止する

Slide 28

Slide 28 text

© 2024 ANDPAD All Rights Reserved. Go1.23 で開発者は何をすれば良いか ● コードサーチで//go:linknameを利用している箇所がないか探す、//go:linknameの 処理を削除する ● -checklinkname=1フラグ追加後 (Go1.23より前になりそう)、-checklinkname=1を つけてbuildしエラーにならないか確認 ● どうしてもruntime内で利用したいprivate関数があり、合理的な理由がある場合は 公式にissueを立ててpublicにして欲しいとお願いしてみる(望みは薄いが) ● -ldflags=-checklinkname=0をつけてしばらく耐える

Slide 29

Slide 29 text

© 2024 ANDPAD All Rights Reserved. 不名誉殿堂入りリスト ● 悲しいことに不名誉殿堂入りしてしまったpackageは"Notable members of the hall of shame include"で検索すると色々出てきます https://cs.opensource.google/search?q=%22Notable%20members%20of%20the %20hall%20of%20shame%20include%22&ss=go%2Fgo ● 理由は分かりませんがとても動きが早かったので、rscは相当怒ってそう ● 「不名誉殿堂入り」ホワイトリストに入れられたlibraryはHandshake出来るので buildは出来る(許されたとは言っていない)

Slide 30

Slide 30 text

© 2024 ANDPAD All Rights Reserved. https://tip.golang.org/doc/go1.23 go:linkname 騒動のその後 意訳 ● //go:linkname で runtimeなど内部シンボルにア クセスできなくするよ ● 「不名誉殿堂入りリスト」に入れた OSSは取り敢 えず今まで通り使えるように Handshakeで許可 するよ ● ただし、新しく作るのは許さん

Slide 31

Slide 31 text

© 2024 ANDPAD All Rights Reserved. https://tip.golang.org/doc/go1.23 go:linkname 騒動のその後 意訳 ● //go:linkname で runtimeなど内部シンボルにア クセスできなくするよ ● 「不名誉殿堂入りリスト」に入れた OSSは取り敢 えず今まで通り使えるように Handshakeで許可 するよ ● ただし、新しく作るのは許さん

Slide 32

Slide 32 text

© 2024 ANDPAD All Rights Reserved. go build -overlay スレッドセーフなテスト用の時間を固定するライブラリを作った | tenntenn.dev https://tenntenn.dev/ja/posts/2021-07-06-testtime/ Goでモンキーパッチするライブラリを作った | Plan 9とGo言語のブログ https://blog.lufia.org/entry/2024/05/28/214447 ● tenntenn/testtime, lufia/plug が利用しているのは //go:linkname コンパイラディレ クティブ ではなく go build の -overlay オプション ● ユーザーの明示的な指定で動作するため //go:linknameのように即座に消される 事はないだろう(たぶん) ● モンキーパッチはできるだけ避けたいがMock用途で //go:linknameを使っている 場合はこっちの逃げ道はある

Slide 33

Slide 33 text

© 2024 ANDPAD All Rights Reserved. go build -overlay go build -overlay ● jsonでbuild時に置き換えたいファイルを指定 する ● 同一package名である必要あり ● json内で ${GOROOT} 書いても動かないの でtime packageなどをmockしたい場合はフ ルパスで書く必要あり ● 同一package内のファイルは基本的には全コ ピーしないと動かない (事が多い) – main.go – package main import "overlaytest/hoge" func main() { hoge.Print() } – hoge/hoge.go – package hoge func Print() { println("hoge!") } – mock/hoge/hoge.go – package hoge func Print() { println("Mock hoge!") } – overlay.json – { "Replace": { "./hoge/hoge.go": "./mock/hoge/hoge.go" } } > go run . hoge! > go run -overlay=overlay.json . Mock hoge!

Slide 34

Slide 34 text

© 2024 ANDPAD All Rights Reserved. Confidential Copyright © 2023 ANDPAD Inc. いま建築‧建設業界で “ものづくり” に携わる⽅の⼈⼿不⾜や ⻑時間労働が社会問題となっています。 今後これらの課題に対して、デジタルシフトによる⽣産性向上や、 就労者数の底上げを急ぐ必要があります。 本来、ものづくりに携わる⼈々は、誰かに幸せを届ける⼈たちです。 そんな⽅々がもっとクリエイティブに、もっと豊かに働けるよう、 私たちは熱い想いで⽇々現場に向き合っています。

Slide 35

Slide 35 text

Copyright © 2023 ANDPAD Inc. ANDPADについて 35 Copyright © 2023 ANDPAD Inc.