Pony 試乗記(6) シリアライズ

# はじめに

Pony の[チュートリアル](https://tutorial.ponylang.io/appendices/serialisation.html)を眺めていると、シリアライズ機構が組み込まれているという記述がありました。

オブジェクトのシリアライズができると面白いことができそうな気がしたのですが、よくよく読むと

- 現状では全く同じバイナリで動作しているプログラム同士でしかシリアライズされたオブジェクトを受け渡しできない
- `embed` なフィールドはシリアライズ不可
- `Pointer` フィールドはデシリアライズすると null になる。それが嫌ならシリアライズ機構を自分でカスタムせよ

などと書かれています。

異なるバイナリ間で受け渡ししたり永続化したりできるポータブルな形式にできるのなら使いみちはかなりありそうですが、同一バイナリでしかオブジェクトの受け渡しができないということだと利用可能な状況はかなり限られます。

とはいえ、どのような形式でシリアライズされるのか興味があったのでシリアライズ結果を覗いてみます。


# 16 進ダンプ

シリアライズされた内容を確認するためにまず、Array[U8] を 16 進ダンプするクラスを作りました。

(オフセット) : (16 進数での羅列) | (キャラクタでの羅列)

の形式で表示します。

~~~pony
use "format"

class HexDump
  new create(env: Env, a: Array[U8] box) =>
    """
    与えられた Array[U8] の内容を 16 進ダンプする。
    """
    var i: USize = 0
    var j: USize = 0
    var shex: String ref = String(60)  // 16 進数として表示する部分
    var stxt: String ref = String(16)  // キャラクタとして表示する部分
    for v in a.values() do
      if shex == "" then
        shex.append(
          Format.int[USize](i where width = 8, fill='0', fmt=FormatHexBare))
        shex.append(" :")
      end
      shex.append(
        " " + Format.int[U8](v where width = 2, fill='0', fmt=FormatHexBare))
      stxt.push(if (v < ' ') or (v > 'z') then '.' else v end)
      i = i + 1
      j = j + 1
      if j >= 16 then
        _print_line(env, shex, stxt)
        j = 0
        shex.clear()
        stxt.clear()
      end
    end
    if shex != "" then
      _print_line(env, shex, stxt)
    end
  fun _print_line(env: Env, shex: String ref, stxt: String ref) =>
    while shex.size() < 58 do
      shex.push(' ')
    end
    env.out.print(shex + " | " + stxt)
~~~

# シリアライズ

シリアライズ対象のクラスをこんなふうに作ってみました。

~~~pony
class Test
  let i: U32
  let str: String
  new create(i': U32, str': String) =>
    i = i'
    str = str'
~~~

まずは、これをシリアライズするとどうなるのかを見てみます。

~~~pony
use "serialise"

actor Main
  new create(env: Env) =>
    let serialise = SerialiseAuth(env.root)
    let output = OutputSerialisedAuth(env.root)
    let t: Test = Test(0xdeadbeaf, "test")
    try
      let s = Serialised(serialise, t)?
      let a = s.output(output)
      env.out.print("Test(0xdeadbeaf, \"test\")")
      HexDump(env, a)
    else
      env.out.print("failed to serialise")
    end
~~~

実行結果は以下のようになりました。引数に与えた数値 0xdeadbeaf（リトルエンディアンなので逆順）および文字列 "test" がそのまま見えています。

~~~console
$ ./test_serialise 
Test(0xdeadbeaf, "test")
00000000 : 0F 00 00 00 00 00 00 00 AF BE AD DE 00 00 00 00 | ................
00000010 : 18 00 00 00 00 00 00 00 05 00 00 00 00 00 00 00 | ................
00000020 : 04 00 00 00 00 00 00 00 05 00 00 00 00 00 00 00 | ................
00000030 : 38 00 00 00 00 00 00 00 74 65 73 74 00          | 8.......test.
~~~

クラスが入れ子になっている場合はどうなるかも試してみます。

~~~pony
class Test2
  let test: Test
  new create(test': Test) =>
    test = test'
~~~

というのを作って、`Test2(Test(0xdeadbeaf, "test"))` をシリアライズしてみました。

~~~console
$ ./test_serialise 
Test2(Test(0xdeadbeaf, "test"))
00000000 : 11 00 00 00 00 00 00 00 10 00 00 00 00 00 00 00 | ................
00000010 : 0F 00 00 00 00 00 00 00 AF BE AD DE 00 00 00 00 | ................
00000020 : 28 00 00 00 00 00 00 00 05 00 00 00 00 00 00 00 | (...............
00000030 : 04 00 00 00 00 00 00 00 05 00 00 00 00 00 00 00 | ................
00000040 : 48 00 00 00 00 00 00 00 74 65 73 74 00          | H.......test.
~~~

微妙に大きくなりました。

## シリアライズ手続きのランタイム処理

(ponyc/packages/serialise/serialise.pony)[https://github.com/ponylang/ponyc/blob/main/packages/serialise/serialise.pony] にある Serialised コンストラクタで

```pony
@pony_serialise(@pony_ctx(), data, Pointer[None], r, alloc_fn, throw_fn) ?
_data = consume r
```

のようにフィールド _data が設定されます、output メソッドは _data を返すだけです。

`@pony_serialise` は (ponyc/src/libponyrt/gc/serialise.c)[https://github.com/ponylang/ponyc/blob/main/src/libponyrt/gc/serialise.c] にあって、

1. コンテキスト(`pony_ctx_t`) にあるハッシュマップ `serialise` にシリアライズ対象オブジェクトを追加する。
2. ハッシュマップ `serialise` にあるオブジェクト全てをシリアライズする。
3. ハッシュマップ `serialise` をクリアする。

という感じで動作しているようです。コンテキストに一旦積むのは、対象オブジェクトがシリアライズ中に GC で消えてしまうのを防ぐためかなと思います。

ponyint_serialise_actor の中の処理を見ると abort となっていて、アクターのシリアライズは禁止されているようです。なのでアクターを他プロセスにマイグレーションさせる、といった使い方は少なくとも現状ではできなさそうです。

# おまけ

[yuml](https://yuml.me/) で図を書きながら Pony ランタイムのソースを見ました。自分用メモとして残して置きます。

![pony.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412308/eda37161-d7c4-06c1-6ace-8abd808aefb2.png)


```yuml
[actor]<>-q>[messageq]
[actor]-type>[type]
[actor]<>-heap>[heap]
[actor]<>-gc>[gc]
[messageq]-tail>[message]
[messageq]-head>[message]
[messageq]-tail>[message]
[message]0..1-next>[message]
[gc]<>-local>[objectmap]
[gc]<>-foreign>[actormap]-*>[actor]
[gc]-delta>[deltamap]-*>[delta]
[heap]-small_free**>[chunk]
[heap]-small_full**>[chunk]
[heap]-large*>[chunk]
[chunk]-actor>[actor]
[chunk]-next>[chunk]
[objectmap]-*>[object]
[object]-type>[type]
[delta]-actor>[actor]
[ctx]-current>[actor]
[ctx]-stack>[gcstack]
[ctx]<>-acquire>[actormap]
[ctx]<>-serialise>[ponyint_serialise_t(hashmap)]
[ponyint_serialise_t(hashmap)]->[serialise]-t>[type]
[ctx]-scheduler>[scheduler]
[scheduler]<>-sleep_object>[pony_signal_event]
[scheduler]-last_victim>[scheduler]
[scheduler]<>-ctx>[ctx]
[scheduler]<>-mute_mapping>[mutemap]-*>[actor]
[scheduler]<>-q>[mpmcq]
[scheduler]<>-mq>[messageq]

[<<messageq_t>>;messageq]
[<<pony_msg_t>>;message|+index: u32_t;+id: u32_t]
[<<pony_actor_t>>;actor|+muted: atomic(size_t);+flags: atomic(uint8_t);+is_muted: atomic(uint8_t)]
[<<pony_type_t>>;type|+id: uint32_t;+size: uint32_t;+field_count: uint32_t;+field_offset: uint32_t;+instance: void*;+trace: pony_trace_fn;+serialise_trace: pony_trace_fn;+custom_serialise_space: pony_custom_serialise_space_fn;+custom_deserialise: pony_custom_deserialise_fn;+dispatch: pony_dispatch_fn;+final: pony_final_fn;+event_notify: u32_t;+traits: uintptr_t**;+fields: void*;+vtable: void*]
[<<gc_t>>;gc|+mark: uint32_t;+rc_mark: uint32_t;+rc: size_t]
[<<heap_t>>;heap|+used: size_t;+next_gc: size_t]
[<<chunk_t>>;chunk|+m: char*;+size: size_t;+slots: uint32_t;+shallow: uint32_t;+finalisers: uint32_t]
[<<object_t>>;object|+address: void*;+rc: size_t;+mark: uint32_t;+immutable: bool]
[<<delta_t>>;delta|+rc: size_t]
[<<pony_ctx_t>>;ctx|+trace_object: trace_object_fn;+trace_actor: trace_actor_fn;+serialise_buffer: void*;+serialise_size:size_t;+serialise_alloc: serialise_alloc_fn;+serialise_alloc_final: serialise_alloc_fn;+serialise_throw: selialise_throw_fn]
[<<scheduler_t>>;scheduler;+tid: pony_thread_id_t;+index: int32_t;+cpu: u32_t;+node: uint32_t;+terminate: bool;+asio_stoppable: bool;+asio_noisy: bool;+block_count: uint32_t;+ack_token: int32_t;+ack_count: uint32_t]
[<<serialise_t>>;serialise|+key: uintptr_t;+value: uintptr_t;+mutability: int;+block: bool]
```