wasm も返す
メモリ回収の Wasm backend への移植は、珍しい出発点を持っていた: git の歴史が「何 Phase も前に一度試みられ、unsound として撤去された」ことを示していたのだ —— escape する値が壊れるため、backend はリークする方を選んだ。復活は古いアイデアを保ち、欠けていた 3 部品を足した: 結果を copy-out する、escape する store をコンパイル時に拒否する、escape する closure を結果型で拒否する。受け入れテストは書き上がっていた —— あの 2048 probe が、今度はフラットな線を期待して。線は出た: bump pointer は 3 万手のあいだ 4,544 bytes で凍結した。
前回の forcing number —— 64 メガバイトの寿命、1 手 ~8.4 キロバイト —— は、それが買うはず だった実装セッションを買った。だがセッションは考古学の講義から始まった。Wasm backend の region ブロック emission には、かつて自明なことそのもの —— ブロック入口で bump pointer を保存し、 出口で復元する —— をやっていて、それが unsound として撤去されたことを説明するコメントが 残っていた: ブロック内で割り当てられた値が escape すると(記録に残る例では、ブロックから 返された container)、次の割当てが黙って上書きするメモリを指す。壊すくらいなら、当時の正直な 選択はリークだった。だからこの移植は「Wasm に回収を足す」ではなく、もっと面白いものだった: 既知の unsound 設計を、それを unsound にしていた欠落部品を供給して復活させる。
三つの部品と、より単純な機械
部品は C backend の回収が要したのと同じ三つの翻訳だった。第一に、結果は copy-out する: ブロックの値は型別 copy 機構で deep copy される —— 実は二度: まずブロックのゴミの上の staging copy へ、次に bump pointer 復元後、外側の割当てレンジへ。二つのコピーは重なり得ない。staging copy は body が割り当てた全てより上に座るからだ —— 微妙なバグを一つの論証に置き換える、小さな 証明。第二に、escape する store はコンパイルエラー: Wasm には per-container の置き場が 無いので、C が store された値を container 自身の region へ copy する場面で、Wasm は端的に拒否 する —— ブロック外で作られた container への heap 値の push、channel への送信、spawn、extern への callback 手渡しは、docs への案内付きでコンパイルに失敗する。ブロック内で作られた container は 自由に触れる。ブロックと共に死ぬからだ。第三に、escape する closure は結果型で拒否。この 移植が元の C の仕事より軽かった理由は、Wasm の単一スレッド世界には C の装置が何も要らない ことだ —— thread-local も heap block chain も無し: mark は Wasm の値スタックに乗り、scratch global 一個がコピーを繰る。同じ設計、可動部品は四つでなく一つ。
フラットな線
2048 のキーハンドラは body の周りに region R { ... } を一つ得た —— スナップショット、描画
文字列、あらゆる中間値が手ごとに死ぬ。一方、盤面そのもの(起動時に作られた素の整数の vector)は
この全てに触れられず、その場で mutate され続ける。probe を再走: 3 万手、trap ゼロ、そして
bump pointer は 1 手目で 4,544 bytes、1 万 5 千手目で 4,544 bytes、3 万手目で 4,544 bytes を
読んだ。ほぼフラット、ではない —— 凍結だ。7,700 手で死んでいたゲームで、1 手あたりの純割当て
ゼロ。suite は歴史の筋を一本正した: リークを主張していた旧テスト(リークが健全な選択だった
時代に意図して固定された Phase 16.4 の挙動)は、新しい形を主張するテスト群に置き換えられた ——
「ブロックから container を返すのは、壊れるのではなくコンパイルエラーになる」を含めて。
アークが閉じる場所
これで三つの Part を跨いで開いていたループが閉じた。長命プログラム編は C backend の回収と 「Wasm にはまだ無い」という正直な注記で終わった。probe の回は「無い」を数字に変えた。この回は 数字をフラットな線に変えた。各 backend はいまや自分の正直な物語を持つ: interpreter は GC。 C は copy-on-store 付きの回収で、store は無制限。Wasm は copy-on-store の代わりに escape を エラーにする回収 —— より厳格で、より単純で、それが仕えるブラウザプログラムには十分。C と Wasm の物語の残る差分は、他の差分と同じ場所に書き留められ、それを要するプログラムを待っている。 次の回は別種の発見について —— 探しに行った機能が、既に建っていた probe の話だ。