Open7

WebAssembly でのメモリの読み方

mizchimizchi

Moonbit 言語の生成する wat ファイルを理解するために、シンプルな wat を手書きすることで理解したい

mizchimizchi

最初に簡単なユーティリティを用意した。deno で wat をコンパイルして実効する。
wat2wasm が CLI しか見つからなかったので、 dax でラップしている。

import $ from "jsr:@david/dax";

type Exports = Record<string, (...args: Array<number>) => number>;

const wat = /*s*/ `
(module
  (func $i (import "imports" "imported_func") (param i32))
  (func (export "exported_func")
    i32.const 42
    call $i))
`;

async function compile(wat: string) {
  const temp = await Deno.makeTempFile({ suffix: ".wat" });
  const tempOut = await Deno.makeTempFile({ suffix: ".wasm" });
  await Deno.writeTextFile(temp, wat);
  await $`wat2wasm ${temp} -o ${tempOut}`;
  return Deno.readFile(tempOut);
}

こんな風に使う。

const wat = /*s*/ `
(module
  (func $i (import "imports" "imported_func") (param i32))
  (func (export "exported_func")
    i32.const 42
    call $i))
`;

{
  const { instance: { exports } } = await WebAssembly.instantiate(
    await compile(wat),
    {
      imports: {
        imported_func: (x: number) => console.log(x),
      },
    }
  );
  const api = exports as Exports;
  const out = api.exported_func(2, 3);
  console.log(out);
}

/s/ はvscode 上で S 式としてコンパイルする前提の記法

https://marketplace.visualstudio.com/items?itemName=bierner.comment-tagged-templates

以降はこのユーティリティがある前提

mizchimizchi

以下のMDNのドキュメントを読みながらステップバイステップで理解していく。

https://developer.mozilla.org/ja/docs/WebAssembly/Using_the_JavaScript_API

WebAssembly.Memory の 0 番目を読み出すだけの wat と、そこに 5 を書き込んで渡すだけの ts

// simple read_memory
const wat_read_mem = /*s*/ `
(module
  (memory (import "js" "mem") 1)
  (func (export "read") (param $ptr i32) (result i32)
    (i32.load
      (local.get $ptr)
    )
  )
)
`;

{
  const binary = await compile(wat_read_mem);
  const mem = new WebAssembly.Memory({ initial: 1 });
  const { instance: { exports } } = await WebAssembly.instantiate(
    binary,
    { js: { mem } }
  );
  const api = exports as Exports;

  const i32 = new Uint32Array(mem.buffer);
  i32[0] = 5;
  const out = api.read(0);
  console.log(out);
}
mizchimizchi

(memory (import "js" "mem") 1) を省略するとエラー。i32.load はメモリがある前提の処理

mizchimizchi

0 番目に書き込んで、それを JS から読み出す例

// simple read_memory
const wat_read_store = /*s*/ `
(module
  (memory (import "js" "mem") 1)
  (func (export "write") (param $ptr i32) (param $val i32)
    (i32.store
      (local.get $ptr)  ;; アドレス
      (local.get $val)  ;; 書き込む値
    )
  )
)
`;

{
  const mem = new WebAssembly.Memory({ initial: 1 });
  const { instance: { exports } } = await WebAssembly.instantiate(
    await compile(wat_read_store),
    { js: { mem } }
  );
  const api = exports as Exports;
  api.write(0, 3);

  const i32 = new Uint32Array(mem.buffer);
  console.log(i32[0]);
}
mizchimizchi

WebAssembly.Table を使う例。値を返す関数をアサインする。

MDNのドキュメントが間違っていてだいぶ迷った。

const wat_tbl = /*s*/ `
(module
  (import "js" "tbl" (table 2 funcref))
  (func $f42 (result i32) i32.const 42)
  (func $f83 (result i32) i32.const 83)
  (elem (i32.const 0) $f42 $f83)
)
`;

{
  console.log("-------------");
  const tbl = new WebAssembly.Table({
    initial: 2,
    element: "anyfunc"
  });
  const { instance: { exports: _ } } = await WebAssembly.instantiate(
    await compile(wat_tbl),
    { js: { tbl } }
  );

  console.log(tbl.get(0)?.());
  console.log(tbl.get(1)?.());
}

mizchimizchi

memory.init の使い方の確認

const wat_init_memory = /*s*/ `
(module
  (memory (export "mem") 1)
  (data $init_data "Hello World")
  (func $initialize_memory
    (memory.init $init_data (i32.const 0) (i32.const 0) (i32.const 11))  ;; コピーするデータの範囲を明示
  )
  (func $drop_data
    (data.drop $init_data)
  )
  (export "initialize_memory" (func $initialize_memory))
  (export "drop_data" (func $drop_data))
)
`;

{
  const { instance: { exports } } = await WebAssembly.instantiate(await compile(wat_init_memory));
  const api = exports as Exports & { mem: WebAssembly.Memory; };
  api.initialize_memory();
  const dataView = new DataView(api.mem.buffer);
  const length = 11; // "Hello World" の長さ
  let text = '';
  for (let i = 0; i < length; i++) {
    text += String.fromCharCode(dataView.getUint8(i));
  }
  console.log(text);
  api.drop_data();
  // api.initialize_memory(); // 呼ぶとエラー
}