Slide 1

Slide 1 text

1 interfaceの内部処理に少しだけ詳しくなる GC24 Recap: Interface Internals mercari.go #27 2024/09/19 @task4233

Slide 2

Slide 2 text

2 所属 ・Mercariのバックエンドエンジニア ・IDPチームで認証認可基盤まわりの開発をしています 興味 ・バックエンドとWebセキュリティ ・GopherCon 2024のCTFで個人賞を5つ貰いました✌ 自己紹介: @task4233

Slide 3

Slide 3 text

3 ・発表の中で登場するアセンブリは linux/amd64 を仮定してください  ・Go の擬似アセンブリは基本的に登場しません  ・興味のある方は The Design of the Go Assembler を参照してください ・CS や Go の基本文法に関する事柄は深く説明しません  ・例) アセンブリ・デバッガの使い方・ interfaceなど  ・†完全に理解† するには余白が足りない  ・簡単な説明は挟む予定です はじめに

Slide 4

Slide 4 text

4 関数呼び出しは、どう実行される ...? func say(mes string) { println(mes) } func main() { say("hello!") }

Slide 5

Slide 5 text

5 関数呼び出しは、どう実行される ...? func say(mes string) { println(mes) } func main() { say("hello!") }

Slide 6

Slide 6 text

6 interfaceを介した呼び出しはどう実行される ...? func f(r io.Reader) { var buf []byte r.Read(buf) } ?

Slide 7

Slide 7 text

7 目的 ・interface の処理がどのように実現されているか理解すること 想定対象者 ・Go の内部処理に興味がある人 ・Go や CS の基本的な部分を理解している人 発表の目的と想定対象者

Slide 8

Slide 8 text

8 おしながき interfaceの内部処理に必要なステップ 高速に実行するための仕組み 02 03 01 まとめ

Slide 9

Slide 9 text

9 ・interfaceに含まれている型の取得 ・メソッドの一覧の取得 ・正しい名前のメソッド情報の取得 ・バイナリにおけるアドレスの取得 ・対象メソッドの呼び出し interfaceの内部処理に必要なステップ

Slide 10

Slide 10 text

10 ・interfaceに含まれている型の取得 ・メソッドの一覧の取得 ・正しい名前のメソッド情報の取得 ・バイナリにおけるアドレスの取得 ・対象メソッドの呼び出し interfaceの内部処理に必要なステップ var buf []byte var r io.Reader r = strings.NewReader("hoge") r.Read(buf)

Slide 11

Slide 11 text

11 ・interfaceに含まれている型の取得 : *strings.Reader ・メソッドの一覧の取得 ・正しい名前のメソッド情報の取得 ・バイナリにおけるアドレスの取得 ・対象メソッドの呼び出し interfaceの内部処理に必要なステップ var buf []byte var r io.Reader r = strings.NewReader("hoge") r.Read(buf)

Slide 12

Slide 12 text

12 ・interfaceに含まれている型の取得 : *strings.Reader ・メソッドの一覧の取得 : Read ・正しい名前のメソッド情報の取得 ・バイナリにおけるアドレスの取得 ・対象メソッドの呼び出し interfaceの内部処理に必要なステップ var buf []byte var r io.Reader r = strings.NewReader("hoge") r.Read(buf)

Slide 13

Slide 13 text

13 ・interfaceに含まれている型の取得 : *strings.Reader ・メソッドの一覧の取得 : Read ・正しい名前のメソッド情報の取得: strings.(*Reader).Read ・バイナリにおけるアドレスの取得 ・対象メソッドの呼び出し interfaceの内部処理に必要なステップ var buf []byte var r io.Reader r = strings.NewReader("hoge") r.Read(buf)

Slide 14

Slide 14 text

14 ・interfaceに含まれている型の取得 : *strings.Reader ・メソッドの一覧の取得 : Read ・正しい名前のメソッド情報の取得: strings.(*Reader).Read ・バイナリにおけるアドレスの取得: strings.(*Reader).Read のアドレス ・対象メソッドの呼び出し interfaceの内部処理に必要なステップ var buf []byte var r io.Reader r = strings.NewReader("hoge") r.Read(buf)

Slide 15

Slide 15 text

15 ・interfaceに含まれている型の取得 : *strings.Reader ・メソッドの一覧の取得 : Read ・正しい名前のメソッド情報の取得: strings.(*Reader).Read ・バイナリにおけるアドレスの取得: strings.(*Reader).Read のアドレス ・対象メソッドの呼び出し: strings.(*Reader).Read の呼び出し interfaceの内部処理に必要なステップ var buf []byte var r io.Reader r = strings.NewReader("hoge") r.Read(buf)

Slide 16

Slide 16 text

16 empty interface ( interface{} ) 型情報を保持 データへのポインタを保持 空のメソッドセットを持つ interface ・Assignabilityに基づき、任意の値を代入できる   ref: https://go.dev/ref/spec#Assignability 2つのフィールドで実現されている ref: https://github.com/golang/go/blob/go1.23.1/src/runtime/runtime2.go#L210-L213

Slide 17

Slide 17 text

17 empty interface ( interface{} ) 型情報を保持 データへのポインタを保持 空のメソッドセットを持つ interface ・Assignabilityに基づき、任意の値を代入できる   ref: https://go.dev/ref/spec#Assignability 2つのフィールドで実現されている ref: https://github.com/golang/go/blob/go1.23.1/src/runtime/runtime2.go#L210-L213

Slide 18

Slide 18 text

18 empty interfaceのデータ参照 var i interface{} i = [3]int{1, 2, 3} type data i: 1 2 3 = registers / stack = heap ref: https://github.com/gophercon/2024-talks/blob/main/KeithRandall-InterfaceInternal s/KeithRandall-InterfaceInternals.pdf

Slide 19

Slide 19 text

19 empty interfaceのデータ参照 var i interface{} i = [3]int{1, 2, 3} i = [4]float64{4.0, 5.0} type data i: = registers / stack = heap ref: https://github.com/gophercon/2024-talks/blob/main/KeithRandall-InterfaceInternal s/KeithRandall-InterfaceInternals.pdf 1 2 3 4.0 5.0 GCのsweep対象に

Slide 20

Slide 20 text

20 empty interfaceのデータ参照の最適化 var i interface{} i = strings.NewReader("hoge") type data i: = registers / stack = heap ref: https://github.com/gophercon/2024-talks/blob/main/KeithRandall-InterfaceInternal s/KeithRandall-InterfaceInternals.pdf *strings.Reader s string i int64 prevRune int strings.Reader type data 🤔

Slide 21

Slide 21 text

21 empty interfaceのデータ参照の最適化 var i interface{} i = strings.NewReader("hoge") type *strings.Reader i: = registers / stack = heap ref: https://github.com/gophercon/2024-talks/blob/main/KeithRandall-InterfaceInternal s/KeithRandall-InterfaceInternals.pdf s string i int64 prevRune int strings.Reader 👍

Slide 22

Slide 22 text

22 empty interfaceのデータ参照の最適化 var i interface{} i = 'A' type data i: = registers / stack = heap ref: https://github.com/gophercon/2024-talks/blob/main/KeithRandall-InterfaceInternal s/KeithRandall-InterfaceInternals.pdf 'A' type data

Slide 23

Slide 23 text

23 empty interfaceのデータ参照の最適化 var i interface{} i = 'A' type data i: = registers / stack = heap ref: https://github.com/gophercon/2024-talks/blob/main/KeithRandall-InterfaceInternal s/KeithRandall-InterfaceInternals.pdf type data: 'A'

Slide 24

Slide 24 text

24 empty interface ( interface{} ) 型情報を保持 データへのポインタを保持 空のメソッドセットを持つ interface ・Assignabilityに基づき、任意の値を代入できる   ref: https://go.dev/ref/spec#Assignability 2つのフィールドで実現されている ref: https://github.com/golang/go/blob/go1.23.1/src/runtime/runtime2.go#L210-L213

Slide 25

Slide 25 text

25 empty interfaceの型情報 var i interface{} i = strings.NewReader("hoge") type data i: = registers / stack = heap ref: https://github.com/gophercon/2024-talks/blob/main/KeithRandall-InterfaceInternal s/KeithRandall-InterfaceInternals.pdf *strings.Readerの type descriptor 実データ = 静的データ

Slide 26

Slide 26 text

26 次のような型情報を与える ・型のサイズ ・ポインタフィールド ・文字列表現 ・型に対して == を行う方法 ・メソッドのリスト など interfaceのtype descriptor ref: https://github.com/golang/go/blob/go1.23.1/src/internal/abi/type.go#L20-L37 ref: https://github.com/golang/go/blob/go1.23.1/src/internal/abi/type.go#L442-L446

Slide 27

Slide 27 text

27 具象型から interfaceへの変換 var s *strings.Reader = ... var i interface{} = s type data i: 実データ = registers / stack = heap = 静的データ $type:*strings.Reader(SB) *strings.Readerの type descriptor

Slide 28

Slide 28 text

28 interfaceから具象型への型アサーション var i interface{} = ... if s, ok := i.(*strings.Reader); ok {...} type data i: 実データ = registers / stack = heap = 静的データ 比較 *strings.Readerの type descriptor *strings.Readerの type descriptor

Slide 29

Slide 29 text

29 interfaceから具象型への型アサーション err := f() if err != nil {...} type data err: 実データ = registers / stack = heap = 静的データ 比較 nil errの type descriptor

Slide 30

Slide 30 text

30 type descriptorは一意でなくてはならない ・複数存在すると参照先が一意に定まらないため ・重複がある場合はリンカによって排除される ・ reflectやplugin パッケージを利用する際、この性質を壊さないように  注意しなければならない interfaceのtype descriptor

Slide 31

Slide 31 text

31 ・クラスごとにvirtual method table(vtable)を作成して解決 余談: C++における型情報の持ち方 ref: http://www.kniraj.com/vtable-and-vptr-how-it-works-in-c/ Base *__vptr; virtual f(); virtual g(); B1: public Base *__vptr; (継承) virtual f(); Base vtable f() g() B1 vtable f() g()

Slide 32

Slide 32 text

32 ・interfaceに含まれている型の取得 ・メソッドの一覧の取得 ・正しい名前のメソッド情報の取得 ・バイナリにおけるアドレスの取得 ・対象メソッドの呼び出し interfaceの内部処理に必要なステップ

Slide 33

Slide 33 text

33 ・interfaceに含まれている型の取得 <- ✅ ・メソッドの一覧の取得 ・正しい名前のメソッド情報の取得 ・バイナリにおけるアドレスの取得 ・対象メソッドの呼び出し interfaceの内部処理に必要なステップ

Slide 34

Slide 34 text

34 ・interfaceに含まれている型の取得 <- ✅ ・メソッドの一覧の取得 残りはコストの高い処理になるかも......🤔 ・正しい名前のメソッド情報の取得 ・バイナリにおけるアドレスの取得 ・対象メソッドの呼び出し interfaceの内部処理に必要なステップ

Slide 35

Slide 35 text

35 ・interfaceに含まれている型の取得 <- ✅ ・メソッドの一覧の取得 残りはコストの高い処理になるかも......🤔 ・正しい名前のメソッド情報の取得 ・バイナリにおけるアドレスの取得 ・対象メソッドの呼び出し interfaceの内部処理に必要なステップ 事前に計算できないか......?

Slide 36

Slide 36 text

36 ・interfaceに含まれている型の取得 <- ✅ ・メソッドの一覧の取得 残りはコストの高い処理になるかも......🤔 ・正しい名前のメソッド情報の取得 ・バイナリにおけるアドレスの取得 ・対象メソッドの呼び出し interfaceの内部処理に必要なステップ 出来ても保持するスペースがない😭 事前に計算できないか......?

Slide 37

Slide 37 text

37 ・interfaceに含まれている型の取得 <- ✅ ・メソッドの一覧の取得 残りはコストの高い処理になるかも......🤔 ・正しい名前のメソッド情報の取得 ・バイナリにおけるアドレスの取得 ・対象メソッドの呼び出し interfaceの内部処理に必要なステップ 出来ても保持するスペースがない😭 事前に計算できないか......?

Slide 38

Slide 38 text

38 interface tableの導入 type data i: = registers / stack = heap ref: https://github.com/gophercon/2024-talks/blob/main/KeithRandall-InterfaceInternal s/KeithRandall-InterfaceInternals.pdf type descriptor 実データ = 静的データ 🤔

Slide 39

Slide 39 text

39 interface tableの導入 type data i: = registers / stack = heap ref: https://github.com/gophercon/2024-talks/blob/main/KeithRandall-InterfaceInternal s/KeithRandall-InterfaceInternals.pdf type descriptor 実データ = 静的データ type ... interface table (itab) で追加データを保持

Slide 40

Slide 40 text

40 empty interface / non-empty interface ref: https://github.com/golang/go/blob/go1.23.1/src/runtime/runtime2.go#L210-L213 ref: https://github.com/golang/go/blob/go1.23.1/src/internal/abi/iface.go#L14-L19 ref: https://github.com/golang/go/blob/go1.23.1/src/runtime/runtime2.go#L205-L208

Slide 41

Slide 41 text

41 interface tableの導入 = registers / stack = heap ref: https://github.com/gophercon/2024-talks/blob/main/KeithRandall-InterfaceInternals/KeithRandall-InterfaceInternals.pdf ... = 静的データ Inter Type Hash Fun var r io.Reader r = strings.NewReader("hoge") type: io.Reader Hash ... type: *strings.Reader ... strings.(*Reader).Read itab: *strings.Reader, io.Reader

Slide 42

Slide 42 text

42 interface tableの導入 = registers / stack = heap ref: https://github.com/gophercon/2024-talks/blob/main/KeithRandall-InterfaceInternals/KeithRandall-InterfaceInternals.pdf ... = 静的データ Inter Type Hash Fun var r io.Reader r = strings.NewReader("hoge") type: io.Reader Hash ... type: *strings.Reader ... strings.(*Reader).Read itab: *strings.Reader, io.Reader tab data r: 実データ

Slide 43

Slide 43 text

43 interface tableの導入 = registers / stack = heap ref: https://github.com/gophercon/2024-talks/blob/main/KeithRandall-InterfaceInternals/KeithRandall-InterfaceInternals.pdf ... = 静的データ Inter Type Hash Fun var r io.Reader r = ? type: io.Reader Hash ... type: ? ... ?.Read itab: ?, io.Reader tab data r: ?

Slide 44

Slide 44 text

44 interfaceを介した呼び出しはどう実行される ...? func f(r io.Reader) { var buf []byte r.Read(buf) } mov rdx,QWORD PTR [rdx+0x18] ; rdxはr.tabを指し、+0x18でfun[0]へアクセス mov rax,QWORD PTR [rsp+0x50] ; raxはreceiverを設定 call rdx ; 対象のメソッドを呼び出す

Slide 45

Slide 45 text

45 interface tableのキャッシュ戦略 (Go1.22〜) ・interface tableが過去に作成されたか確認 ・具象型の全てのメソッドリストを取得 ・リストの中からinterfaceの各メソッドを見つける ・interface tableを構築して、キャッシュに保存して返却 ref: https://go-review.googlesource.com/c/go/+/526658 , https://go-review.googlesource.com/c/go/+/529316 etc… type assertion, type switch時の改善 高速に実行するための仕組み ref: https://github.com/gophercon/2024-talks/blob/main/KeithRandall-InterfaceInternals/KeithRandall-InterfaceInternals.pdf

Slide 46

Slide 46 text

46 interfaceは以下のステップで実行時に処理される  ・含まれている型の発見  ・メソッドの一覧取得  ・正しい名前のメソッド取得  ・バイナリでの位置取得  ・その位置へのジャンプ 高速化のための工夫 ・data ・interface tableのキャッシュ(Go 1.22~)  ・type assertionで73.53%、type switchで90.43%の改善 まとめ

Slide 47

Slide 47 text

47 ・Interface Internals: https://github.com/gophercon/2024-talks/blob/main/KeithRandall-InterfaceInternals/KeithRandall-InterfaceInternals.pdf ・teh-cmc / go-internals: https://github.com/teh-cmc/go-internals/tree/master ・Interfaces in Golang: A Deeper Lock: https://blog.stackademic.com/interfaces-in-golang-a-deeper-look-4a0931481c00 ・golang / go: https://github.com/golang/go/tree/go1.23.1/src ・Debugging Go Code with GDB: https://go.dev/doc/gdb ・cmd/compile: add a cache to interface type switches: https://go-review.googlesource.com/c/go/+/526658 ・cmd/compile: use cache in front of type assert runtime call: https://go-review.googlesource.com/c/go/+/529316 ・Vtable and Vptr How it works in C++: https://web.archive.org/web/20111215130820/http://www.kniraj.com/vtable-and-vptr-how-it-works-in-c/ ・The Design of the Go Assembler: https://go.dev/talks/2016/asm.slide ・A Quick Guide to Go’s Assembler: https://go.dev/doc/asm ・The Go Programming Language Specification: https://go.dev/ref/spec ・Goクイズで学ぶメソッドセット: https://speakerdeck.com/task4233/gokuizudexue-bumesotudosetuto ・Go internal ABI specification: https://go.googlesource.com/go/+/refs/heads/dev.regabi/src/cmd/compile/internal-abi.md References