借り初めのひみつきち

仮ブログです。

AssemblyScript の配列

さいきん AssemblyScript を触ってました。*1

AssemblyScript とは、 WebAssembly にコンパイルできる TypeScript のサブセット言語です。
WebAssembly の世界と JavaScript の世界は別の世界なので結構勝手が違いますが、 AssemblyScript を使うと同じ TypeScript ファイルを参照したりできるようになり、両者の溝が若干低くなります。

WebAssembly を使う時は大量のデータを高速に処理する必要がある場面が多いと思うので、配列の受け渡しは重要な要件です。しかし、実は容易ではありません。

WebAssembly.Memory

現在の WebAssembly にはオブジェクトの概念がありません。実はポインタすらありません。

あるのは int や double のような少数のスカラ値と、 WebAssembly.Memory という謎のオブジェクトだけです。

WebAssembly.Memory は内部に ArrayBuffer を持っていて、 WebAssembly 内部でメモリアクセスする命令はこの ArrayBuffer を対象とします。
現在の WebAssembly では1モジュールあたりひとつの Memory を扱うことができ、この中身をどう利用するかは WebAssembly 内部のコードの責任となっています。
WebAssembly に対応するコンパイラはこの ArrayBuffer を適当に配分してオブジェクトを割り当て、 ArrayBuffer の中の何バイト目かを示すインデックス値 (wasm32 の場合は int32) をポインタのように扱うことでオブジェクトを表現しています。意外と原始的ですね。

このインデックス値を WebAssembly の外の世界から見るとただの整数にしか見えないので、オブジェクトに変換するには WebAssembly.Memory の内部の ArrayBuffer から探さなくてはなりません。

AssemblyScript の配列

AssemblyScript の場合、exportされたクラスの情報はライブラリで吸収して JS のクラスとほとんど同じように扱うことができます。
しかし、それ以外のオブジェクトはそう単純ではありません。

オブジェクトを入出力する関数は JS から見ると number の入出力に見えます。
これは先述のように WebAssembly.Memory の内部の ArrayBuffer のインデックス値となっています。

文字列は getString みたいなユーティリティー関数でアクセスできますが、それ以外のオブジェクトはメモリから直接読み書きしないといけないようです。*2

メモリ上のオブジェクト構造は以下のページに情報があります。

Memory - The AssemblyScript Book

f:id:neriring16:20200401205336p:plain

AssemblyScript には JavaScript の Array は存在せず、実質全部 TypedArray であることがわかります。
また、通常の配列は ArrayBuffer と ArrayBufferView の二段構成になってることがわかります。

これを参考に以下のような関数を作ると AssemblyScript の配列に JavaScript からアクセスできるようになります。
this.wmem は先述の WebAssembly.Memory のことで、 instantiate 時に前もって作成するかまたは instantiate の戻り値のオブジェクトの memory というキーにあります。

getArray(ptr: number): Uint8Array {
    const buffer = this.wmem.buffer
    const view = new DataView(buffer)
    const base = view.getUint32(ptr + 4, true)
    const size = view.getUint32(ptr + 8, true)
    return new Uint8Array(buffer, base, size)
}

これで、大量のデータの入出力も ArrayBuffer や TypedArray 経由で高速にやりとりできるようになりました💡

なお、 JavaScript のメモリ管理は通常 GC を使いますが AssemblyScript は独立したリファレンスカウント方式のため、グローバル変数で固定的に確保されてないオブジェクトの扱いはサポート関数の __retain や __release で管理しないといけなくなってちょっと面倒になります💦

*1:といってもこの原稿を書き始めて既に一月近く経って成果物は公開前に開発中止しそうな勢いですが...

*2:バージョンによって違うかもしれません