Goのmapとheapを自作してみた / How to create your own map and heap in Go

7b606c5039f083d13e2d2320ce6ddcfa?s=47 DQNEO
February 25, 2019

Goのmapとheapを自作してみた / How to create your own map and heap in Go

Goコンパイラを自作するにあたって、mapとheapを自作してみたので、どうやって作ったのかを説明します。

7b606c5039f083d13e2d2320ce6ddcfa?s=128

DQNEO

February 25, 2019
Tweet

Transcript

  1. GoのMapとHeapを自作してみ た @DQNEO (どきゅねお) 2019/2/25 mercari.go

  2. コンパイラ をゼロから自作してます。 https://github.com/DQNEO/minigo

  3. が動くようになった! / https://twitter.com/DQNEO/status/1088422242772381697

  4. どうやって作ったのか

  5. • • GoのMap おさらい • • •

  6. GoのMap Key: 任意の型 Value: 任意の型 要素数:任意

  7. GoのMap 任意の型の と 任意の型の を 任意の数だけ格納できるナニカ

  8. どこから手を付ければ

  9. 自作コンパイラ3原則 小さくはじめる 動けば正義 遅くても気にしない

  10. 小さくはじめる

  11. ↓ とにかくハードルを下げる まずは 型は int のみ

  12. m = map [int]int { 1: 2, 3: 4, }

    とにかくハードルを下げる 要素数も固定
  13. とにかくハードルを下げる 1 2 3 4 メモリ上に数値が並んでるだけ

  14. これなら作れそう!

  15. m = map [int]int { 1: 2, 3: 4, }

    map[int]int 1 2 3 4 メモリ上に8byteずつ値を並べる
  16. x = m[3] map get 1 2 3 4 keyを16byteずつホップしながら探索

    マッチしたらその右隣の値を返す
  17. Keyの探索 (X86-64の例) emit("mov %%r13, %%rax") // i emit("imul $16, %%rax")

    // i * 16 emit("mov %%r10, %%rcx") // head emit("add %%rax, %%rcx") // head + i * 16 emit("mov (%%rcx), %%rdx") // eval key value 16byteずつホップしながらkeyを探索
  18. マッチしたKey位置から Valueを取得 emit("mov 8(%%rcx), %%r15") // get value of value

    emit("jmp %s", labelEnd) key値がマッチしたら、 8bytes隣のvalue値を取り出して 探索ループを脱出する
  19. map get 実装の詳細はこちら https://github.com/DQNEO/minigo/commit/9f0cef573 d772d67a0efc8fa88c78b185743ea40#diff-fa95711d 3528c54a10a4bd5a3303bf8cR1443

  20. map set 前述のようにKeyの探索を行う Keyが存在する? Yes -> その位置+8bytesの場所に値を書き込む。 No -> データ領域の末尾にKey,Valueを追記。(後述)

  21. for range構文 for k,v := range m { ... }

  22. for range構文 for k,v := range m { ... }

    for i:=0, i<len(m), i++ { k := mData[ i * 16] v := m[v] } 構文木を書き換えて、普通のfor文に変換する
  23. を作る

  24. を作る に追記するには動的なメモリ確保 が必要 を実装するのはチョットむずい 疑似 でお茶を濁す

  25. 疑似 静的グローバル 配列をheapに見 立て、そこからメ モリを必要なだけ 切り取る

  26. 疑似 の利点 メモリ開始アドレスが固定なので デバグしやすい

  27. これで への 要素追加が可能に

  28. ↓ 型を汎用化したい

  29. 1 "hello" 3 "world" valueのサイズが可変 → むずい

  30. 1 &s1 3 &s2 1 &i1 3 &i2 値のアドレスを格納する 値自体はheapに置き、そのアドレスをmap領域に書

    き込む。 valueのサイズは8bytesのままなので、今までの mapロジックをほぼそのまま使える
  31. mov (%rax), %rax Valueのデリファレンス 値を取り出した直後に1度デリファレンスするだけ

  32. "hello" "world" "こんにち わ" "世界" keyの型も汎用にしたい

  33. &s1 &s2 &s3 &s4 keyも同様にアドレスを登録する

  34. これで動く が作れた

  35. の実装全体はこんな感じ https://github.com/DQNEO/minigo/compare/3 654b7c283a01efd273a336188fd13ab10dbdf6 e...3f6ce0773dd9bd7a233b2278f8911a5253f 90999

  36. おまけ 簡単そうに言っているが、 動的アドレス計算を駆使するので、 一歩間違えると Segmentation fault 地獄

  37. おかげで gdb が使えるようになりました おまけ

  38. おまけ このmapの仕組みを使えば、"interface"機能も実 現できる。(できた)