2023 03 30 - qnighy/umo GitHub Wiki

近況

昨日は特に進捗はない。

VMについて

最終的にはWASMにコンパイルされる言語にするつもりだが、当面の間はVM方式で実行することになりそう。ここでややこしいのが借用の扱い。

実は、対象言語の参照(※Rustにおける参照と同等のものを指す)をRustの参照で表すのはかなり難しい。Rustのライフタイムは型消去されてしまうのでランタイムチェックする余地が少ないからだ。一般的に、対象言語の型検査で保証された性質をメタ言語でも同様に保証された扱いにするのは難しく、メタ言語では対象言語で保証された性質をランタイムチェックで追認するしかない場合が多いが、ライフタイムの場合はそもそもそのランタイムチェックでお茶を濁すこと自体が難しい。

特に面倒くさいのがスタックの扱いで、スタックは Vec<T> で表したくなるが、Vec は各要素に対して均質なライフタイムしか持つことができない。しかし、VMのスタックにとってはスタックの左側ほど大きなライフタイム、右側ほど小さなライフタイムという構造があり、この構造のミスマッチが問題になる。

そもそもRustにおいてコールスタックは非常に特殊なデータ構造である。元々Rustではコールスタックを具現化する必要がなかったので問題にならなかったが、非同期処理を可能にするにあたって状態を陽にRustの型として表す必要が発生し、この特殊性が問題になった。これがまさに、自己参照構造体 (スタックの後ろ側がスタックの手前側を参照する) の問題である。このようなデータ構造をうまく扱うにはデータ構造のメモリ位置を固定 (pin) する必要がある。そして固定すると今度は別の問題が出てくる。スタックを自由に延長できなくなるのである。Rustではそもそも原則としてasync再帰を許さず、スタック長の予測ができないときはスタックの一部をboxして分離することを強制することでこの問題を解決している。

というわけで、ここでは2つの方法が考えられる。

  1. Vec<T> を慎重にラップし、自己参照が可能なスタックを作る。
  2. 対象言語の参照をRustの参照で表すのをあきらめる。

今回はプログラムが期待したオーダーで動けば十分なので、安全性を考えると2の方針で実装したい。