wasmを使ってRust製JVMをブラウザで動かしてみた

# 誰？
- 名前: r-chaser53
- GitHub: https://github.com/rchaser53
- Twitter: https://twitter.com/rChaser53
- 所属: LINE株式会社
- 言語開発とか好きな人(歴は1.5年くらい)
- rustfmtのお手伝いを今年はしてます

---
# 概要

- Rustで作成したJVMをwasm-packを使ってブラウザで動かした
- 何も使わないで動かすよりはるかに楽だった
- ついでに見つけた便利なmoduleの紹介
- JVM作成については[別スライド](https://slides.com/rchaser53/copy-of)をどうぞ

---
# 今回の成果物
- Rust製JVM。wasmとしてブラウザで動かすこともできる
- https://github.com/rchaser53/rust-jvm

---

# wasm-packとは
- [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen)を使って生成したwasmファイルにRustとJavaScriptのFFIのランタイムを追加するビルドツール
- 引数や戻り値として普通に文字列や配列が使える
- [ドキュメント](https://rustwasm.github.io/docs/wasm-pack)はそれなりに充実している
- 細かい用語などに関しては[legokichiさんの資料](https://qiita.com/legokichi/items/5d6344314ab6d6633554#wasm-bindgen-%E7%B3%BB%E7%B5%B1)がとても良くまとまっているのでオススメ

---

# 実際にどう使うの?
- Rust側
- JavaScript側

---

# Rust側

```rust
use wasm_bindgen::prelude::*;

// JavaScriptから関数をimport
#[wasm_bindgen(module = "/import.js")]
extern "C" {
    // 意味はほぼ無いが32bitのintを取ってStringを返す関数
    fn import_from_js_fn(input: i32) -> String;
}

// JavaScriptへ関数をexport
#[wasm_bindgen]
pub fn export_to_js_fn(input: i32) -> String {
    // 意味はほぼ無いが32bitのintを取ってStringを返す関数
    input.to_string()
}

```

---
# JavaScript側

```js
// index.js
const wasm = await import('./pkg');
const result = wasm.export_to_js_fn(111);
console.log(result);     // "111"

// import.js
export function import_from_js_fn(val) {
   return val.toString();
}
```

---

# JavaScript側

実際に使ったのは[wasm-pack-plugin](https://github.com/wasm-tool/wasm-pack-plugin)。設定は少なめ

```js
/* pluginsだけ表示 */
plugins: [
  new HtmlWebpackPlugin({
    template: 'index.html'
  }),
  new WasmPackPlugin({
    // wasm-packが作成してくれるwasmなどのファイルが入っているディレクトリ
    // pkgのpathを指定する
    crateDirectory: path.resolve(__dirname, "crate")
  }),
  // JavaScriptはutf16, Rustはutf8のStringで非常に具合が悪い
  // この子たちがなんとかしてくれる
  new webpack.ProvidePlugin({
    TextDecoder: ['text-encoding', 'TextDecoder'],
    TextEncoder: ['text-encoding', 'TextEncoder']
  })
],
```

--- 
# JavaScript側

- 後はwebpack-dev-serverを立ち上げればrustのビルドも一緒にやってくれる
- Rust側のhot reloadもしてくれるので開発は結構サクサク

---

# どれくらい楽になったの？
何も使わないで簡単な操作を行うことで試してみる
レポジトリ: https://github.com/rchaser53/vanilla-rust-wasm

---

# Rustから文字列を返してみよう
Rust側

```rust
#[macro_use]
extern crate lazy_static;
use std::sync::Mutex;

// staticな領域にメモリを確保。これを用いてJavaScriptとデータのやりとりをする
lazy_static! {
    // RustのStringはutf8。全ての要素を0にすることで初期化する
    pub static ref STRING_MEMORY: Mutex<[u8; 1_000]> = Mutex::new([0; 1_000]);
}

// マングリングを行わない
#[no_mangle]
pub fn get_string() -> *const u8 {
    // 文字列を生成してメモリにコピーする
    let data = String::from("hello world");
    let length = data.len();
    let s = data.as_bytes() as &[u8];
    let mut memory = STRING_MEMORY.lock().unwrap();
    memory[..length].clone_from_slice(&s[..length]);

    // 先端のポインタを返す
    memory.as_ptr()
}
```

---

# 文字列を返してみよう
JavaScript側

```js
WebAssembly.instantiateStreaming(fetch(path), {})
  .then(({ instance }) => {          
    const getString = (instance) => {
      const pointer = instance.exports.get_string();
      const buffer = new Uint8Array(instance.exports.memory.buffer, pointer);
      const input = new Uint8Array(extractInput(buffer));
      // RustのStringはutf8
      const decoder = new TextDecoder('utf-8');
      let result = decoder.decode(input);
      console.log({ result })
    }

    const extractInput = (heap) => {
      let index = 0;
      let inputArray = [];
      // 何も値が入っていない(0)までwhileで回す
      while (heap[index] !== 0) {
        inputArray.push(heap[index]);
        index += 1;
      }
      return inputArray;
    }

    getString(instance);
  });
```

とても辛い(実際に使うならもっと色々と考慮する必要があると思う)

---

# まだまだ制限はある
```rust
// referencesは返せない
/* error: cannot return references in #[wasm_bindgen] imports yet */
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen(module = "/web/map.js")]
extern "C" {
    pub fn get_file_content_from_js(key: &str) -> &[u8];
}

// lifetimeや型パラメータは使えない
/* error: can't #[wasm_bindgen] functions with lifetime or type parameters */
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen(module = "/web/map.js")]
extern "C" {
    pub fn get_file_content_from_js<'a>'(key: &'a str) -> Vec<u8>;
}
```

--- 
# 便利な子たち
wasm-bindgenのドキュメントの方には何故書かれていない便利な子たち2つ
- [console_error_panic_hook](https://github.com/rustwasm/console_error_panic_hook)
- [wee_alloc](https://github.com/rustwasm/wee_alloc)

---

# console_error_panic_hook
- これがないとwasm内でこけても「RuntimeError: Unreachable executed」しか出ない
- これを使うとrustのエラーメッセージがconsole.errorとして出力される
- 開発中だけでも使うべき

---

# console_error_panic_hook
使わない場合

![without_panic_hook.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99025/8f7ab693-d3cd-fa0f-ce5a-5997d05aa65f.png)

---

# console_error_panic_hook
使った場合

![with_panic_hook.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99025/e1a8a604-9e15-bba4-614f-4ebed5b7f416.png)


---

# wee_alloc
- デフォルトで提供されるメモリアロケータより性能は少し低いがサイズがとても小さい
- 軽く使うくらいならデフォルトのメモリアロケータは性能が過剰
- これを使うだけでwasmのサイズが削減できるらしい(要確認)

---
# demo

![fizzbuzz.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99025/abb74461-2ac1-5f2d-4c44-c8ad963a392a.gif)


---

# 感想
- 開発自体はかなりしやすくなった
- ドキュメントを読めば簡単なことなら特に問題なく実装できそう
- 引数、戻り値の制限はあるが、致命的なレベルではないと思う
- 用途さえ見つかれば十二分に使えそうに感じる

---

# ご視聴ありがとうございました