入門WebAssemblyの読書メモ

読んだ。

本書では、WATという人が読み書きしやすいテキストフォーマットを通して、WebAssemblyを解説している。 実務でWATを直接書くのは想像できないので、教養的な位置付けになるかと思っていたが、結構よかったと思う。

ちなみに、8章で、衝突判定をwasmとjsで比較していて、wasmの方が速いという結果になっていたのだけれど、本書のjsではclassを使ったOOP的な書き方になっていていかにも遅そうだったので、jsで線形メモリを使って書きなおしてみたところと、O3で最適化したwasmより圧倒的に速い結果となってしまった。(書き直したコードはこの記事の末尾に貼っておく。)

こうなると、大きめの数値演算処理をwasmに切り出せれば早くなるとも言えなさそう。 いずれにしても、WASMを実投入するには、十分に検証が必要必要になりそうだなーという印象。 (js版とwasm版を並行開発するして速い方を選ぶくらいやらないとつらそう。。。)

以下はメモ。順不同です。

WebAssembly

  • ダウンロード、コンパイル、実行がJavascriptよりも高速(ということになっているが、個別に検証はいると思う)
  • ブラウザだけでなく、Node.jsなどのJavascriptエンジンからも実行できる。
  • まだJavascriptほど汎用的ではない。

エンディアンはリトル。

スタックマシン

レジスタマシン上(x86)出来実行しなければならない、仮想スタックマシン。

スタックマシンの利点はバイトコードのサイズが小さいことと、利用できる汎用レジスタの数についての前提を設けなくてよいこと。(実行するハードウェア側で選択できる)

ページ

ページのサイズはすべて64KB。アプリケーションは合計で最大2GBのページを確保できる。 一度確保したページは解放できない。 サーバーサイドでは不十分。組み込みでは64KBでも大きすぎることがある。

記述スタイル

WAT: WebAssembly Text

疑似アセンブリ言語アセンブリ言語ではない)

WASI: WebAssembly System Interface

ランタイム仕様でOSとやりとりするための規格。

i32やi64などの整数型でsignedとunsignedは、型ではなく関数で区別する。 区別が必要な関数にのみsとuの両方が用意されている。

なお、Javascriptの数値は64bitの浮動小数点型。 (64bit浮動小数点では32bit整数をすべて表現できる。)

JavascriptではES2021まで64bit整数を扱えなかったが、ES2021ではBigInt型が追加されている。

文字列

  • null 終端
  • 文字列長を表すprefix

とのことだけど、これは文字列表現の一般論であって、自力で実装する必要があると理解した。

メモは以上。

jsのコード

jsとwatの速度に疑問があったので、 衝突判定のjsを線形メモリを使うように修正したものは以下。watとだいたい同じアルゴリズムになるように気を付けている。

function set_pixel(x, y, c) {
    if (x >= cnvs_size) return;
    if (y >= cnvs_size) return;

    const i = y * cnvs_size + x;
    mem_i32[i] = c;
}

function draw_obj(x, y, c) {
    for (let xi = 0; xi < obj_size; ++xi) {
        for (let yi = 0; yi < obj_size; ++yi) {
            set_pixel(x + xi, y + yi, c);
        }
    }
}

function clear_canvas() {
    for (let i = 0; i < pixel_count; ++i) {
        mem_i32[i] = 0xff_00_00_00;
    }
}

function animate_by_js() {
    clear_canvas();

    const stride_i32 = stride_bytes / 4;
    const end = obj_cnt * stride_i32;
    for (let i = 0; i < end; i += stride_i32) {
        let x = mem_i32[obj_start_32 + i];
        let y = mem_i32[obj_start_32 + i + 1];
        let vx = mem_i32[obj_start_32 + i + 2];
        let vy = mem_i32[obj_start_32 + i + 3];

        mem_i32[obj_start_32 + i] = (x + vx) % cnvs_size;
        mem_i32[obj_start_32 + i + 1] = (y + vy) % cnvs_size;
    }

    for (let i = 0; i < end; i += stride_i32) {
        let x1 = mem_i32[obj_start_32 + i];
        let y1 = mem_i32[obj_start_32 + i + 1];

        let hit = false;
        for (let j = 0; j < end; j += stride_i32) {
            if (i == j) {
                continue;
            }

            const x2 = mem_i32[obj_start_32 + j];
            const xdist = Math.abs(x1 - x2);
            if (xdist >= obj_size) {
                continue;
            }

            const y2 = mem_i32[obj_start_32 + j + 1];
            const ydist = Math.abs(y1 - y2);
            if (ydist >= obj_size) {
                continue;
            }
            hit = true;
            break;
        }

        if (hit) {
            draw_obj(x1, y1, hit_color);
        } else {
            draw_obj(x1, y1, no_hit_color);
        }
    }
}