開発ツールに頼らず 様々な言語から WebAssembly（第４回）

WebAssembly は多くのプログラム言語からライブラリの様に呼び出す事が出来る。
WebAssembly のコードを書く方法は多くの記事で書かれているが、その使い方はフレームワーク等のツールに頼っている事が多い様だ。
本記事では、そのような便利ツールに出来るだけ頼らず JavaScript, Rust, Python, Ruby から WebAssembly を実行する方法を記載する。

WebAssembly は新しい技術である。
目先の最先端ツールに飛びつくのもよいが、その基礎を学んで長く使える知識を身に着けないか？

本記事はシリーズの第３回である。シリーズ記事の一覧は [第１回] の  **#シリーズ記事一覧** に載せている。シリーズの記事を追加する時は [第１回] の一覧を更新して通知を行うので興味の有る人は [第１回] をストックして欲しい。

# 本記事の概要と過去記事の復習

[第３回] では作成する WebAssembly のコードに整数型の状態（インスタンス変数、プロパティ）を持たせた。
本記事では整数に加えて String 相当のプロパティを持たせる。

イメージとしては下記の JavaScript のクラスの様な物を作る。

```javascript
class Person {
    constructor() {
        this.age = -1;
        this.name = "";
    }

    get_age() {
        return this.age;
    }

    set_age(age) {
        this.age = age;
    }

    get_name() {
        return this.name;
    }

    set_name(name) {
        this.name = name;
    }
}
```

本記事では上記の様な WebAssembly のコードを実行する下記の外部プログラムを作成する。

- Vanilla JS （フレームワークやライブラリを使わない、そのままの JavaScript）から WebAssembly を実行
- Rust, Python, Ruby から低レイヤーのライブラリ [wasmtime] だけを使って WebAssembly を実行

なお、説明の都合上 [Vanilla JS から実行](#vanilla-js-から実行) の章は JavaScript に興味が無くとも読んでほしい。
（そんなに難しい事はやっていないので JavaScript を知らなくとも本質的な部分は理解できるはずだ。）
それ以外の興味の無い言語の章については読み飛ばして問題無い。

# WebAssembly の線形メモリ

WebAssembly で String 相当の物を扱うにはハードルが 2 個存在する。

1. WebAssembly がどうやってメモリを確保するか
1. WebAssembly と外部プログラムで、どうやって String の受け渡しを行うか

String は一般的にインスタンスの外側にメモリを確保し、そこに保存する byte 列を書き込む。
String のインスタンス自信には byte 列を保存したメモリのアドレスや、その長さ等を保存する。

しかし [第１回] で説明したように「メモリ確保」には System Call を実行する必要が有り、WebAssembly から実行する事は出来ない。

これは外部プログラムが WebAssembly のインスタンスを立ち上げる時にメモリの塊を渡す事で解決する。WebAssembly のコードは OS からメモリをもらうのではなく、あらかじめ渡されたメモリの中でやり繰りするのだ。この「あらかじめ渡されたメモリ」の事を「線形メモリ」と言う。
[第２回] で外部プログラムから関数を import したが、同様に線形メモリを import するのだ。

また、線形メモリを export すれば外部プログラムから読み書きできる。
WebAssembly のコードと外部プログラムは String を直接やり取り出来ない。しかし外部プログラムと WebAssembly の両方が線形メモリにアクセス出来れば、あとは String を保管した場所（ポインター）やその長さだけを受け渡しする事で実質的に String の受け渡しの様な事が出来る。

詳細は過去に [WebAssembly と外部プログラムのインターフェース](https://qiita.com/wbcchsyn/items/2866f78dbe5d36da6247#string-を返す) という記事で書いたので、気になる人は読んで欲しい。

# WebAssembly コードの準備

過去記事同様、WebAssembly Text Format で動作確認をする WebAssembly のコードを作成し、wat2wasm でコンパイルする。

## wabt のインストール

[github](https://github.com/WebAssembly/wabt) の README.md を読んでコンパイルする。
C++ のコンパイル環境が必要なので注意。

wat2wasm というコマンドがコンパイルされるので、必要に応じて Path を通しておくと良いだろう。

## wasm ファイルの準備

wasm ファイルとは、WebAssembly の規格に沿ったバイナリファイルの事である。
最初に各種言語から動かして動作確認するための wasm ファイルを用意しよう。

テキストエディタで以下の様な内容のファイルを作成し `/tmp/person2.wat` という名前で保存する。
(`/tmp/person.wat` という名前は [第３回] で使ってしまった。)

```wat
(module
    (import "host" "mem" (memory 0))
    (export "memory" (memory 0))

    (global $age (mut i64) (i64.const -1))
    (global $name_ptr (mut i32) (i32.const 0))
    (global $name_len (mut i32) (i32.const 0))

    (func (export "get_age") (result i64)
        global.get $age)
    (func (export "set_age") (param $age i64)
        local.get $age
        global.set $age)

    (func (export "get_name") (result i32 i32)
        global.get $name_ptr
        global.get $name_len)

    (func (export "set_name") (param $ptr i32) (param $len i32)
        local.get $ptr
        global.set $name_ptr
        
        local.get $len
        global.set $name_len))
```

上記は WebAssembly Text Format という wasm ファイルのソースコードだ。
多くの読者に内容を何となく理解してもらうには、これが良いと思って選択した。

上記コードは大まかに以下の事を行っている。
（以下で `i64` とは「符号付 64 bit 整数」、 `i32` とは「符号付 32 bit 整数」の事である。）

- 外部プログラムの `host.mem` という名前で指定されている線形メモリを import。この線形メモリの初期サイズは 0 page (= 0 byte) である。最大サイズは指定していないので、 WebAssembly の仕様上の上限である 32 page (= 2 GB) となる。（WebAssembly の 1 page は 64 KB。）
- 線形メモリを外部から `memory` という名前でアクセス可能にする
- グローバル変数（インスタンス変数、プロパティ）を宣言。この WebAssembly 内部からは `$age` という名前でアクセス可能にしている。この変数は変更可能な `i64` 型である。インスタンス作成時にこの変数は -1 で初期化される。
- グローバル変数（インスタンス変数、プロパティ）を宣言。この WebAssembly 内部からは `$name_ptr` という名前でアクセス可能にしている。この変数は変更可能な `i32` 型である。インスタンス作成時にこの変数は 0 で初期化される。
- グローバル変数（インスタンス変数、プロパティ）を宣言。この WebAssembly 内部からは `$name_len` という名前でアクセス可能にしている。この変数は変更可能な `i32` 型である。インスタンス作成時にこの変数は 0 で初期化される。
- 関数を定義。この関数は外部から `get_age` という名前でアクセス可能である。この関数は引数を取らず `i64` 型の値を 1 個返す。
- 関数を定義。この関数は外部から `set_age` という名前でアクセス可能である。この関数は `i64` 型の引数を 1 個取り、値を返さない。
- 関数を定義。この関数は外部から `get_name` という名前でアクセス可能である。この関数は引数を取らず `i32` 型の値を 2 個返す。
- 関数を定義。この関数は外部から `set_name` という名前でアクセス可能である。この関数は `i32` 型の引数を 2 個取り、値を返さない。

線形メモリの初期サイズを 0 byte としたのは「線形メモリのサイズを拡張する」というコードも含めて解説したかったからだ。（通常ならば 1 page (= 64 KB) とするだろう。）

本記事の執筆時点（2023 年 7 月）では、WebAssembly は 32 bit OS を想定している。
そのため線形メモリの最大サイズは 2 GB であり、ポインターやその長さは 32 bit 符号付整数の範囲内に収まる。
（多くのプログラム言語同様、32 bit の最上位 bit はエラーフラグとなっており実質的に使用可能なのは 31 bit である。符号付 32 bit の範囲内に収まる。）

各関数はその名前から分かる様に、それぞれ `age` と `name` のゲッター、セッターと呼ばれる物だ。
`name` は 2 個の `i32` からなる値で表し、1 個目はポインター、2 個目は長さに相当する。

[第３回] はグローバル変数に直接アクセスする方法も解説したが、 `name` は 2 個の値を同時に扱う必要が有るので今回はこの方法は控える。

この wat ファイルを以下の様に wat2wasm コマンドでコンパイルすると `/tmp/person2.wasm` というバイナリファイルが出来る。

```sh
$ wat2wasm -o /tmp/person2.wasm /tmp/person2.wat
$ ls /tmp/person2.wasm
/tmp/person2.wasm
```

# Vanilla JS から実行

説明の都合上、本章は JavaScript に興味の無い人でも斜めに読んで欲しい。

## 筆者の JavaScript 環境

- nodejs v18.16.1

## Vanilla JS のコード

nodejs から WebAssembly を実行するコードは、例えば下記の様になる。

```javascript
// Build Module
const fs = require('fs');
const wasm = fs.readFileSync('/tmp/person2.wasm');
const mod = new WebAssembly.Module(wasm);

// Build Instance
const importObject = {
    host: {
        mem: new WebAssembly.Memory({ initial: 0 })
    }
};
const person = new WebAssembly.Instance(mod, importObject);

// Set the age
{
    const age = BigInt(41);
    person.exports.set_age(age);
}

// Initialize the memory
const linearMemory = importObject.host.mem;
const page = 1;
linearMemory.grow(page);
const buffer = new Uint8Array(linearMemory.buffer);

// Set the name
{
    const name = new TextEncoder().encode("Shin Yoshida");
    const ptr = 8;
    const subarray = buffer.subarray(ptr, ptr + name.length);
    subarray.set(name);
    person.exports.set_name(ptr, name.length);
}

// Display the name and the age
{
    const age = person.exports.get_age();
    const [ptr, length] = person.exports.get_name();
    const name = new TextDecoder().decode(buffer.subarray(ptr, ptr + length));
    console.log(`The age of ${name} is ${age}.`);
}
```

上記のファイルを `call_wasm.js` という名前で保存し実行すると、下記の様な結果を得る。

```sh
$ node call_wasm.js
The age of Shin Yoshida is 41.
```

JavaScript のコードを少しずつ読んでみる。
（以下の **太字リンク** は JavaScript のクラス名（コンストラクター名）である事を示す。リンク先は [MDN Web Docs](https://developer.mozilla.org/en-US/) 。）

最初に以下を読んでみる。

```javascript
// Build Module
const fs = require('fs');
const wasm = fs.readFileSync('/tmp/person2.wasm');
const mod = new WebAssembly.Module(wasm);

// Build Instance
const importObject = {
    host: {
        mem: new WebAssembly.Memory({ initial: 0 })
    }
};
const person = new WebAssembly.Instance(mod, importObject);
```

ここまでは [第２回] とほとんど同じだ。
違いは wasm ファイルのパスと、 `importObject.host.mem` が **[Memory][Memory_js]** のインスタンスである事だけだ。

複数の **[Instance][Instance_js]** を作る場合は、同一の `importObject` を使うと同じ線形メモリを共有してしまうので注意しよう。

次に以下の行を読んでみる。

```javascript
// Set the age
{
    const age = BigInt(41);
    person.exports.set_age(age);
}
```

ここも [第３回] と同様の方法で `age` を取得している。

続いて以下の行を読んでみる。

```javascript
// Initialize the memory
const linearMemory = importObject.host.mem;
const page = 1;
linearMemory.grow(page);
const buffer = new Uint8Array(linearMemory.buffer);
```

ここでは線形メモリのサイズを 1 page (= 64 KB) に拡張している。
（この wasm ファイルが線形メモリを使うのは `name` の保存だけなので 1 page で十分だと判断した。）

**[Memory][Memory_js]** は **[ArrayBuffer][ArrayBuffer_js]** （もしくは **[SharedArrayBuffer][SharedArrayBuffer_js]** ）のラッパーである。
後で使いやすい様に、この **[ArrayBuffer][ArrayBuffer_js]** に `buffer` という名前でアクセス可能にしている。
（この **[ArrayBuffer][ArrayBuffer_js]** については JavaScript を良く知らない人は読み飛ばして良い。）

私達は wat ファイルを作成する時に `memory` という名前で外部から線形メモリにアクセス可能にした。
`const linearMemory = importObject.host.mem;` というステップは
`const linearMemory = person.exports.memory;` に置き換える事も可能だ。

「wat ファイルで線形メモリを export （公開）する必要は無かったのではないか？」と疑問に思う人も居るだろう。

その疑問は、ある意味では正しい。実際に線形メモリを export しなくとも、このプログラムは正常動作する。
しかし、この方法は「抜け道」の様な物だと筆者は考えている。

例えば外部プログラムを Rust で実装する場合はこの抜け道は使用できないないので export が必要だ。
JavaScript も今後の仕様変更で、この抜け道が禁止されるかもしれない。

筆者の個人的意見としては、外部プログラムはどうでも良い。（動けば良い。）
しかし WebAssembly は（少なくとも建前上は）「どの環境でも動く」事を目指しているので export する方が良いと思う。

続いて以下の行を読んでみる。

```javascript
// Set the name
{
    const name = new TextEncoder().encode("Shin Yoshida");
    const ptr = 8;
    const subarray = buffer.subarray(ptr, ptr + name.length);
    subarray.set(name);
    person.exports.set_name(ptr, name.length);
}
```

ここでは `"Shin Yoshida"` という `string` を `person` に保存を以下の手順で実行している。

1. `"Shin Yoshida"` という `string` をバイト列に変更し、 `name` という変数に保存
1. 線形メモリの `ptr` バイト目から `ptr + name.length` バイト目の部分に `subarray` という変数名をつける
1. `subarray` に `name` のバイト列をコピー
1. `person` のグローバル変数（インスタンス変数、プロパティ）に `ptr` と `name.length` の情報を保存

`ptr` の値を `8` にした事に大きな意味はない。（線形メモリは 64 KB 確保してあるので、コピー先はその中のどの部分でも良い。）
ただ、先頭（0 byte 目）から使用すると wasm ファイルの中ではアドレス 0 番地にデータが書き込まれている様に見えてしまう。0 番地は多くのプログラム言語で Null ポインターと呼ばれる物だ。あまり気持ちが良くないので 8 byte 目から使用を開始した。

最後に以下の行を読んでみる。

```javascript
// Display the name and the age
{
    const age = person.exports.get_age();
    const [ptr, length] = person.exports.get_name();
    const name = new TextDecoder().decode(buffer.subarray(ptr, ptr + length));
    console.log(`The age of ${name} is ${age}.`);
}
```

ここでは `person` の `age` と `name` を取得、表示している。

`age` は [第３回] と同じ方法で関数 `get_age` を実行して取得している。

`name` も同様に関数 `get_name` を実行して「線形メモリの、どの部分に `name` が保存されているか」を取得した後、 `TextDecoder` を介して `string` に変換している。

# wasmtime から実行

以降の章では wasmtime を使って [Rust から実行](#rust-から実行), [Python から実行](#python-から実行), [Ruby から実行](#ruby-から実行) 方法について、それぞれ説明している。
不要な章は読み飛ばしても大丈夫だ。

## Rust から実行

### 筆者の Rust 環境

- cargo 1.70.0
- rustup 1.26.0
- rustc 1.70.0 stable-x86_64-unknown-linux-gnu

### Rust のコード

下記の様に call_person というプロジェクトを作成する。

```sh
$ cargo new call_person
$ cd call_person
```

Cargo.toml の `[dependencies]` セクションに wasmtime をの設定を加える。
筆者の環境では Cargo.toml は下記の様になった。

```toml
[package]
name = "call_person"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
wasmtime = "10.0.1"
```

Rust から WebAssembly を実行するコードは、例えば下記の様になる。

```rust
use std::fs;
use wasmtime::*;

fn main() -> wasmtime::Result<()> {
    // Build `Engine`
    let engine = Engine::default();

    // Build `Module`
    let wasm = fs::read("/tmp/person2.wasm").unwrap();
    let module = Module::new(&engine, wasm)?;

    // Build `Store`
    let mut store = Store::new(&engine, ());

    // Build `Memory`
    let ty = MemoryType::new(0, None);
    let memory = Memory::new(&mut store, ty)?;

    // Build `Instance`. (`Linker` is builder class.)
    let mut linker = Linker::new(&engine);
    linker.define(&store, "host", "mem", memory)?;
    let person = linker.instantiate(&mut store, &module)?;

    // Set the age
    let set_age = person.get_typed_func::<i64, ()>(&mut store, "set_age")?;
    set_age.call(&mut store, 41)?;

    // Initialize the memory
    const PAGE: u64 = 1;
    let linear_memory = person.get_memory(&mut store, "memory").unwrap();
    linear_memory.grow(&mut store, PAGE)?;

    // Set the name
    {
        const NAME: &'static [u8] = "Shin Yoshida".as_bytes();
        const PTR: usize = 8;
        linear_memory.write(&mut store, PTR, NAME)?;
        let set_name = person.get_typed_func::<(i32, i32), ()>(&mut store, "set_name")?;
        set_name.call(&mut store, (PTR as i32, NAME.len() as i32))?;
    }

    // Display the name and the age.
    {
        let get_age = person.get_typed_func::<(), i64>(&mut store, "get_age")?;
        let age = get_age.call(&mut store, ())?;

        let get_name = person.get_typed_func::<(), (i32, i32)>(&mut store, "get_name")?;
        let (ptr, length) = get_name.call(&mut store, ())?;

        let mut buffer = Vec::with_capacity(length as usize);
        unsafe { buffer.set_len(length as usize) };
        linear_memory.read(&store, ptr as usize, buffer.as_mut_slice())?;
        let name = String::from_utf8(buffer).unwrap();

        println!("The age of {} is {}.", &name, &age);
    }

    Ok(())
}
```

上記の内容で `src/main.rs` を上書きして実行すると、下記の様な結果を得る。

```sh
$ cargo run
   Compiling call_person2 v0.1.0 (/home/wbcchsyn/tmp/person2/call_person2)
    Finished dev [unoptimized + debuginfo] target(s) in 3.55s
     Running `target/debug/call_person2`
The age of Shin Yoshida is 41.
```

上記の Rust コードを少しずつ解説していく。
以下で **太字のリンク** は wasmtime で定義されたクラスである事を示す。リンク先は [Rust 版 wasmtime の公式ドキュメント](https://docs.rs/wasmtime/10.0.1/wasmtime/index.html) 。

まずはコードの全体像を把握しよう。

```rust
use std::fs;
use wasmtime::*;

fn main() -> wasmtime::Result<()> {
...
}
```

見ての通り、本コードは main 関数が定義してあるだけである。

以下、main 関数の中身を上から少しずつ読んでみる
まずは以下の行からだ。

```rust
    // Build `Engine`.
    let engine = Engine::default();

    // Build `Module`.
    let wasm = fs::read("/tmp/person2.wasm").unwrap();
    let module = Module::new(&engine, wasm)?;

    // Build `Store`.
    let mut store = Store::new(&engine, ());

    // Build `Memory`
    let ty = MemoryType::new(0, None);
    let memory = Memory::new(&mut store, ty)?;

    // Build `Instance`. (`Linker` is builder class.)
    let mut linker = Linker::new(&engine);
    linker.define(&store, "host", "mem", memory)?;
    let person = linker.instantiate(&mut store, &module)?;
```

ここは [第２回] とほとんど同じだ。
違いは wasm ファイルのパスと `linker.define` の引数に **[Memory][Memory_rust]** を渡している事だけだ。

変数 `person` の宣言に `mut` を付ける必要が無いのは [第３回] と同じ理由だ。
`mut` を付けなくとも `age` や `name` を変更出来る。

続いて以下の行を読んでみる。

```rust
    // Set the age
    let set_age = person.get_typed_func::<i64, ()>(&mut store, "set_age")?;
    set_age.call(&mut store, 41)?;
```

ここでは [第３回] と同じような方法で `person` の `age` を保存している。

続いて以下の行を読んでみる。

```rust
    // Initialize the memory
    const PAGE: u64 = 1;
    let linear_memory = person.get_memory(&mut store, "memory").unwrap();
    linear_memory.grow(&mut store, PAGE)?;
```

ここでは線形メモリのサイズを 1 page (= 64 KB) に拡張している。
（この wasm ファイルが線形メモリを使うのは `name` の保存だけなので 1 page で十分だと判断した。）
私達は wat ファイルを作成する時に `memory` という名前で外部から線形メモリにアクセス可能にしたので関数 `person.get_memory` で取得できる。

続いて以下の行を読んでみる。

```rust
    // Set the name
    {
        const NAME: &'static [u8] = "Shin Yoshida".as_bytes();
        const PTR: usize = 8;
        linear_memory.write(&mut store, PTR, NAME)?;
        let set_name = person.get_typed_func::<(i32, i32), ()>(&mut store, "set_name")?;
        set_name.call(&mut store, (PTR as i32, NAME.len() as i32))?;
    }
```

ここでは `"Shin Yoshida"` という `String` を `person` に保存を以下の手順で実行している。

1. `"Shin Yoshida"` という `&str` を `&[u8]` に変更し、 `NAME` という変数に保存
1. 線形メモリの `PTR` バイト目以降に `NAME` のバイト列をコピー（コピーする長さは `NAME.len()` ）
1. `person` のグローバル変数（インスタンス変数、プロパティ）に `PTR` と `NAME.len()` の情報を保存

`PTR` の値を `8` にした事に大きな意味は無い。（線形メモリは 64 KB 確保してあるので、コピー先はその中のどの部分でも良い。）
ただ、先頭（0 byte 目）から使用すると wasm ファイルの中ではアドレス 0 番地にデータが書き込まれている様に見えてしまう。0 番地は多くのプログラム言語で Null ポインターと呼ばれる物だ。あまり気持ちが良くないので 8 byte 目から使用を開始した。

Rust コードで 1 点注意が必要なのは、 `PTR` や `NAME.len()` といった値の型だ。
外部プログラムでは、これらの値は `usize` である。この外部プログラムを 64 bit OS 上で動かすならば `usize` は `符号無し 64 bit 整数` になるだろう。
一方、本記事の執筆時点（2023 年 7 月）では WebAssembly は基本的に 32 bit OS を想定しており、私達の作った wat ファイルでは関数 `set_name` の引数は `符号付き 32 bit 整数` である。

外部プログラムと WebAssembly 関数の引数の間で明確な型変換が必要である。

最後に以下の行を読んでみる。

```rust
    // Display the name and the age.
    {
        let get_age = person.get_typed_func::<(), i64>(&mut store, "get_age")?;
        let age = get_age.call(&mut store, ())?;

        let get_name = person.get_typed_func::<(), (i32, i32)>(&mut store, "get_name")?;
        let (ptr, length) = get_name.call(&mut store, ())?;

        let mut buffer = Vec::with_capacity(length as usize);
        unsafe { buffer.set_len(length as usize) };
        linear_memory.read(&store, ptr as usize, buffer.as_mut_slice())?;
        let name = String::from_utf8(buffer).unwrap();

        println!("The age of {} is {}.", &name, &age);
    }
```

ここでは `person` から `age` と `name` を取得して標準出力に表示している。

`age` の取得方法は [第３回] と同じだ。

`name` の取得は下記の方法で行っている。（ざっくり言うと、先ほど `name` を保存した時の逆の手順だ。）

1. 関数 `get_name` を実行し、 `name` が線形メモリ内のどの場所に保存されているか確認する。
1. 線形メモリ上の `name` の内容をコピーする `buffer` という変数を作成
1. 線形メモリ上の `name` を `buffer` にコピー
1. `Vec<u8>` 型の `buffer` を `String` 型に変換

先ほどと同様に、外部プログラムの `usize` のサイズと WebAssembly のアドレスの bit 長が一致するとは限らないので注意しよう。

## Python から実行

### 筆者の Python 環境

- Python 3.11.2
- pip 23.0.1

### Python のコード

最初に Python の version と環境に応じた方法で wasmtime をインストールする。

```sh
$ pip install wasmtime
Collecting wasmtime
  Downloading wasmtime-9.0.0-py3-none-manylinux1_x86_64.whl (6.6 MB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 6.6/6.6 MB 2.1 MB/s eta 0:00:00
Installing collected packages: wasmtime
Successfully installed wasmtime-9.0.0
```

Python から WebAssembly を実行するコードは、例えば下記の様になる。

```python
from wasmtime import (Engine, Instance, Limits, Linker, Memory, MemoryType,
                      Module, Store)

# Build Engine.
engine = Engine()

# Build Module.
wasm = open('/tmp/person2.wasm', 'rb').read()
module = Module(engine, wasm)

# Build Store.
store = Store(engine)

# Build Memory.
limits = Limits(0, None)
ty = MemoryType(limits)
linear_memory = Memory(store, ty)

# Build Instance.
linker = Linker(engine)
linker.define(store, "host", "mem", linear_memory)
person = linker.instantiate(store, module)

# Set the age.
person.exports(store)['set_age'](store, 41)

# Initialize the memory.
PAGE = 1
linear_memory.grow(store, PAGE)

# Set the name.
NAME = b"Shin Yoshida"
PTR = 8
linear_memory.write(store, NAME, PTR)
person.exports(store)["set_name"](store, PTR, len(NAME))

# Display the name and the age.
age = person.exports(store)["get_age"](store)

(name_ptr, name_length) = person.exports(store)["get_name"](store)
name = linear_memory.read(store, name_ptr, name_ptr + name_length).decode()

print("The age of %s = %d" % (name, age))
```

上記の内容を call_wasm.py というファイル名で保存すると、実行結果は以下の様になる。

```sh
$ python call_wasm.py
The age of Shin = 41
```

上記の Python コードを少しずつ解説していく。
以下では **太字のリンク** は wasmtime で定義されたクラスである事を示す。リンク先は [Python 版 wasmtime の公式ドキュメント](https://bytecodealliance.github.io/wasmtime-py/) である。

最初に少しまとめて読んでみよう。

```python
from wasmtime import (Engine, Instance, Limits, Linker, Memory, MemoryType,
                      Module, Store)

# Build Engine.
engine = Engine()

# Build Module.
wasm = open('/tmp/person2.wasm', 'rb').read()
module = Module(engine, wasm)

# Build Store.
store = Store(engine)

# Build Memory.
limits = Limits(0, None)
ty = MemoryType(limits)
linear_memory = Memory(store, ty)

# Build Instance.
linker = Linker(engine)
linker.define(store, "host", "mem", linear_memory)
person = linker.instantiate(store, module)
```

ここでは [第２回] とほとんど同じだ。
違いは wasm ファイルのパスと、`linker.define()` の引数に **[Memory][Memory_py]** を渡している事だけだ。

続いて以下の行を読んでみる。

```python
# Set the age.
person.exports(store)['set_age'](store, 41)
```

ここでは [第３回] と同じ方法で `age` を設定している。

続いて以下の行を読んでみる。

```python
# Initialize the memory.
PAGE = 1
linear_memory.grow(store, PAGE)
```

ここでは線形メモリのサイズを 1 page (= 64 KB) に拡張している。
（この wasm ファイルが線形メモリを使うのは `name` の保存だけなので 1 page で十分だと判断した。）

私達は wat ファイルを作成する時に `memory` という名前で外部から線形メモリにアクセス可能にした。
`linear_memory.grow(store, PAGE);` というステップは
`person.exports(store)["memory"].grow(store, PAGE)` に置き換えても良い。

続いて以下の行を読んでみる。

```python
# Set the name.
NAME = b"Shin Yoshida"
PTR = 8
linear_memory.write(store, NAME, PTR)
person.exports(store)["set_name"](store, PTR, len(NAME))
```
ここでは `"Shin Yoshida"` という文字列を `person` に保存を以下の手順で実行している。

1. `"Shin Yoshida"` という文字列の `bytes` 型に変更した値を NAME という変数に保存
1. 線形メモリの `PTR` バイト目以降に `NAME` のバイト列をコピー（コピーする長さは `len(NAME)` ）
1. `person` のグローバル変数（インスタンス変数、プロパティ）に `PTR` と `len(NAME)` の情報を保存

`PTR` の値を `8` にした事に大きな意味は無い。（線形メモリは 64 KB 確保してあるので、コピー先はその中のどの部分でも良い。）
ただ、先頭（0 byte 目）から使用すると wasm ファイルの中ではアドレス 0 番地にデータが書き込まれている様に見えてしまう。0 番地は多くのプログラム言語で Null ポインターと呼ばれる物だ。あまり気持ちが良くないので 8 byte 目から使用を開始した。

続いて以下の行を読んでみる。

```python
# Display the name and the age.
age = person.exports(store)["get_age"](store)

(name_ptr, name_length) = person.exports(store)["get_name"](store)
name = linear_memory.read(store, name_ptr, name_ptr + name_length).decode()

print("The age of %s = %d" % (name, age))
```

ここでは `person` から `age` と `name` を取得して標準出力に表示している。

`age` の取得方法は [第３回] と同じだ。

`name` の取得は下記の方法で行っている。（ざっくり言うと、先ほど `name` を保存した時の逆の手順だ。）

1. 関数 `get_name` を実行し、 `name` が線形メモリ内のどの場所に保存されているか確認
1. 線形メモリ上の `name` の内容をコピーした `bytearray` 型を取得し、 `str` 型に変換

## Ruby から実行

### 筆者の Ruby 環境

- ruby 3.1.2p20

### Ruby のコード

最初に gem で wasmtime をインストールする。
（必要に応じて bundler 等を使うと良いだろう。）

```sh
$ gem install wasmtime
Fetching wasmtime-9.0.1-x86_64-linux.gem
Successfully installed wasmtime-9.0.1-x86_64-linux
Parsing documentation for wasmtime-9.0.1-x86_64-linux
Installing ri documentation for wasmtime-9.0.1-x86_64-linux
Done installing documentation for wasmtime after 0 seconds
1 gem installed
```

Ruby から WebAssembly を実行するコードは、例えば下記の様になる。

```ruby
require 'wasmtime'

# Build Engine
engine = Wasmtime::Engine.new

# Build Module
wasm = open('/tmp/person2.wasm', 'rb').read
mod = Wasmtime::Module.new(engine, wasm)

# Build Store
store = Wasmtime::Store.new(engine)

# Build Memory
linear_memory = Wasmtime::Memory.new(store, min_size: 0)

# Build instance (Linker is builder Class)
linker = Wasmtime::Linker.new(engine)
linker.define(store, "host", "mem", linear_memory)
person = linker.instantiate(store, mod)

# Set the age
person.export('set_age').to_func.call(41)

# Initialize the memory
PAGE = 1
linear_memory.grow(PAGE)

# Set the name
NAME = "Shin Yoshida"
PTR = 8
linear_memory.write(PTR, NAME)
person.export('set_name').to_func.call(PTR, NAME.length)

# Display the name and age
age = person.export('get_age').to_func.call

name_ptr, name_length = person.export('get_name').to_func.call
name = linear_memory.read(name_ptr, name_length)

puts("The age of %s is %s." % [name, age])
```

上記の内容を call_wasm.rb というファイル名で保存すると、実行結果は以下の様になる。

```sh
$ ruby call_wasm.rb
The age of Shin Yoshida is 41.
```

上記の Ruby コードを少しずつ解説していく。
以下では **太字のリンク** は wasmtime で定義されたクラスである事を示す。リンク先は [Ruby 版 wasmtime の公式ドキュメント](https://bytecodealliance.github.io/wasmtime-rb/latest/top-level-namespace.html) である。


最初に少しまとめて読んでみよう。

```ruby
require 'wasmtime'

# Build Engine
engine = Wasmtime::Engine.new

# Build Module
wasm = open('/tmp/person2.wasm', 'rb').read
mod = Wasmtime::Module.new(engine, wasm)

# Build Store
store = Wasmtime::Store.new(engine)

# Build Memory
linear_memory = Wasmtime::Memory.new(store, min_size: 0)

# Build instance (Linker is builder Class)
linker = Wasmtime::Linker.new(engine)
linker.define(store, "host", "mem", linear_memory)
person = linker.instantiate(store, mod)
```

ここまでは [第２回] とほとんど同じだ。
違いは wasm ファイルのパスと、`linker.define` の引数に **[Memory][Memory_rb]** を渡している事だけだ。

続いて以下の行を読んでみる。

```ruby
# Set the age
person.export('set_age').to_func.call(41)
```

ここでは [第３回] と同じ方法で `age` を設定している。

続いて以下の行を読んでみる。

```ruby
# Initialize the memory
PAGE = 1
linear_memory.grow(PAGE)
```

ここでは線形メモリのサイズを 1 page (= 64 KB) に拡張している。
（この wasm ファイルが線形メモリを使うのは `name` の保存だけなので 1 page で十分だと判断した。）

私達は wat ファイルを作成する時に `memory` という名前で外部から線形メモリにアクセス可能にした。
`linear_memory.grow(PAGE);` というステップは
`person.export("memory").to_memory.grow(PAGE)` に置き換えても良い。

続いて以下の行を読んでみる。

```ruby
# Set the name
NAME = "Shin Yoshida"
PTR = 8
linear_memory.write(PTR, NAME)
person.export('set_name').to_func.call(PTR, NAME.length)
```
ここでは `"Shin Yoshida"` という文字列を `person` に保存を以下の手順で実行している。

1. `"Shin Yoshida"` という　`String` を NAME という変数に保存
1. 線形メモリの `PTR` バイト目以降に `NAME` のバイト列をコピー（コピーする長さは `NAME.length` ）
1. `person` のグローバル変数（インスタンス変数、プロパティ）に `PTR` と `NAME.length` の情報を保存

`PTR` の値を `8` にした事に大きな意味は無い。（線形メモリは 64 KB 確保してあるので、コピー先はその中のどの部分でも良い。）
ただ、先頭（0 byte 目）から使用すると wasm ファイルの中ではアドレス 0 番地にデータが書き込まれている様に見えてしまう。0 番地は多くのプログラム言語で Null ポインターと呼ばれる物だ。あまり気持ちが良くないので 8 byte 目から使用を開始した。

続いて以下の行を読んでみる。

```ruby
# Display the name and age
age = person.export('get_age').to_func.call

name_ptr, name_length = person.export('get_name').to_func.call
name = linear_memory.read(name_ptr, name_length)

puts("The age of %s is %s." % [name, age])
```

ここでは `person` から `age` と `name` を取得して標準出力に表示している。

`age` の取得方法は [第３回] と同じだ。

`name` の取得は下記の方法で行っている。（ざっくり言うと、先ほど `name` を保存した時の逆の手順だ。）

1. 関数 `get_name` を実行し、 `name` が線形メモリ内のどの場所に保存されているか確認
1. 線形メモリ上の `name` の内容をコピーした `String` を取得

# まとめ

今回は外部プログラムと WebAssembly 間で String の受け渡し方法をご紹介した。

ただ、今回紹介した方法は「WebAssembly 側で線形メモリを使うのがプロパティ `name` の保存だけ」という制限付きでのみ上手くいく。例えば WebAssembly に「文字列を保持する可変長配列（Array や Vector と呼ばれる物）」を持たせたいとしたら、話は変わってくる。

もう少し複雑な例については次回以降にご紹介したいと思う。

[wasmtime]: https://github.com/bytecodealliance/wasmtime
[第１回]: https://qiita.com/wbcchsyn/items/e1f699bb86c466411e36
[第２回]: https://qiita.com/wbcchsyn/items/925d11c6fd9111b29851
[第３回]: https://qiita.com/wbcchsyn/items/47fa89c5b3c4bec24c15

[Instance_js]: https://developer.mozilla.org/en-US/docs/WebAssembly/JavaScript_interface/Instance
[Memory_js]: https://developer.mozilla.org/en-US/docs/WebAssembly/JavaScript_interface/Memory
[ArrayBuffer_js]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer
[SharedArrayBuffer_js]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer

[Memory_rust]: https://docs.rs/wasmtime/10.0.1/wasmtime/struct.Memory.html

[Memory_py]: https://bytecodealliance.github.io/wasmtime-py/#wasmtime.Memory

[Memory_rb]: https://bytecodealliance.github.io/wasmtime-rb/latest/Wasmtime/Memory.html