Goコンパイラを自作するにあたって、mapとheapを自作してみたので、どうやって作ったのかを説明します。
GoのMapとHeapを自作してみた@DQNEO (どきゅねお)2019/2/25 mercari.go
View Slide
コンパイラをゼロから自作してます。https://github.com/DQNEO/minigo
が動くようになった!/https://twitter.com/DQNEO/status/1088422242772381697
どうやって作ったのか
●●GoのMap おさらい●●●
GoのMapKey: 任意の型 Value: 任意の型要素数:任意
GoのMap任意の型の と任意の型の を任意の数だけ格納できるナニカ
どこから手を付ければ
自作コンパイラ3原則小さくはじめる動けば正義遅くても気にしない
小さくはじめる
↓とにかくハードルを下げるまずは 型は int のみ
m = map [int]int {1: 2,3: 4,}とにかくハードルを下げる要素数も固定
とにかくハードルを下げる1 23 4メモリ上に数値が並んでるだけ
これなら作れそう!
m = map [int]int {1: 2,3: 4,}map[int]int1 2 3 4メモリ上に8byteずつ値を並べる
x = m[3]map get1 2 3 4keyを16byteずつホップしながら探索マッチしたらその右隣の値を返す
Keyの探索 (X86-64の例)emit("mov %%r13, %%rax") // iemit("imul $16, %%rax") // i * 16emit("mov %%r10, %%rcx") // heademit("add %%rax, %%rcx") // head + i * 16emit("mov (%%rcx), %%rdx") // eval key value16byteずつホップしながらkeyを探索
マッチしたKey位置からValueを取得emit("mov 8(%%rcx), %%r15") // get value of valueemit("jmp %s", labelEnd)key値がマッチしたら、8bytes隣のvalue値を取り出して探索ループを脱出する
map get実装の詳細はこちらhttps://github.com/DQNEO/minigo/commit/9f0cef573d772d67a0efc8fa88c78b185743ea40#diff-fa95711d3528c54a10a4bd5a3303bf8cR1443
map set前述のようにKeyの探索を行うKeyが存在する?Yes -> その位置+8bytesの場所に値を書き込む。No -> データ領域の末尾にKey,Valueを追記。(後述)
for range構文for k,v := range m {...}
for range構文for k,v := range m {...}for i:=0, ik := mData[ i * 16]v := m[v]}構文木を書き換えて、普通のfor文に変換する
を作る
を作るに追記するには動的なメモリ確保が必要を実装するのはチョットむずい疑似 でお茶を濁す
疑似静的グローバル配列をheapに見立て、そこからメモリを必要なだけ切り取る
疑似 の利点メモリ開始アドレスが固定なのでデバグしやすい
これで への要素追加が可能に
↓型を汎用化したい
1 "hello"3 "world"valueのサイズが可変 → むずい
1 &s13 &s21 &i13 &i2値のアドレスを格納する値自体はheapに置き、そのアドレスをmap領域に書き込む。valueのサイズは8bytesのままなので、今までのmapロジックをほぼそのまま使える
mov (%rax), %raxValueのデリファレンス値を取り出した直後に1度デリファレンスするだけ
"hello" "world""こんにちわ""世界"keyの型も汎用にしたい
&s1 &s2&s3 &s4keyも同様にアドレスを登録する
これで動く が作れた
の実装全体はこんな感じhttps://github.com/DQNEO/minigo/compare/3654b7c283a01efd273a336188fd13ab10dbdf6e...3f6ce0773dd9bd7a233b2278f8911a5253f90999
おまけ簡単そうに言っているが、動的アドレス計算を駆使するので、一歩間違えると Segmentation fault 地獄
おかげで gdb が使えるようになりましたおまけ
おまけこのmapの仕組みを使えば、"interface"機能も実現できる。(できた)