C/C++を使っているRustのコンソールアプリのReact SPA化

Emscriptenを使って、C/C++に依存しているRustのコンソールアプリをReact SPA化した時のメモ。

## 環境構築
### emcc
`emcc`というEmscriptenコンパイラのフロントエンドが必要で、そのためにemsdkのインストールが必要。

### emsdkのインストールと有効化
https://emscripten.org/docs/getting_started/downloads.html
に従って、emsdkのインストールとアクティベイトをした上で、ビルドを実行するshell上で、emsdk_envを実行して、emccを使えるようにする必要がある。

```bash
# shellを開くたびに実行が必要
$ . ./emsdk/emsdk_env.sh
```
## ビルド
まず、emscriptenターゲットを追加する。

```bash
rustup target add wasm32-unknown-emscripten
```

次に、targetに`wasm32-unknown-emscripten`を指定して、cargoでビルド。`CARGO_BUILD_RUSTFLAGS`を使って、emccのオプションを指定。

```bash
$ CARGO_BUILD_RUSTFLAGS="内容は後述" cargo build --target=wasm32-unknown-emscripten --release
```
ビルドが成功すると、Cargo.tomlで指定したパッケージ名で、２つのファイルが作られる

|ファイル名|説明|
|--------|---|
| [パッケージ名].js | JavaScriptからWebAssemblyを使うためのグルーコード |
| [バッケージ名].wasm | WebAssembly本体 |


#### emccオプションの指定方法
`-C link-arg=...`の形で、`CARGO_BUILD_RUSTFLAGS`の中で指定する。**空白がある場合にはバラして別々にする必要がある**ので注意が必要。
例えば、`-s ASYNCIFY`を指定したい場合は、`-C link-arg=-s -C link-arg=ASYNCIFY`となる。

#### C/C++部分のビルド
C/C++部分については事前に、`emconfigure`と`emmake`で、`configure`と`make`をラップして実行して、別途ソースからLLVMのbitcodeにビルドしておく必要がある。`em...`は元のスクリプトの環境変数を書き換え等を行ってbitcodeを出力するスクリプトに変換するためのツール。`.libs`ディレクトリにbitcodeを含んだファイルが出力される。

下の様に実行する。詳細はこちら、https://emscripten.org/docs/compiling/Building-Projects.html#building-projects

```bash
$ ./emconfigure ./configure
$ ./emmake make
```

更に、そのbitcodeをリンクできるように、bitcodeがあるディレクトリへのパスをemccオプションで指定する。

```
-L native=[レポジトリへのパス]/.libs
```

#### うまく行かない場合
結構な割合で依存ライブラリのビルドに失敗したので、エラーメッセージを見でケースバイケースで対処した。大きく分けて下の２つの方法で問題が解決できた。

##### configureスクリプトのパラメーターの調整
`configure`スクリプトに然るべきパラメーターを与えることでうまく行く場合があった。例えば以下のようなもの。

- assemblyはサポートされないので、使わないようにする。
- アーキテクチャーはgenericなものを選択する
- c++のサポートをオンにする

##### ソースコードの修正（C/C++もRustも）
ソースコードの一部がなんらかの理由でWebAssemblyにできない場合があったので、以下のような形で解消した。

- 使われていないコードを消す
- スレッドを使っているコードを使わないように書き直す。C/C++の場合は、`USE_PTHREADS=1`というオプションがあるので、これで動かせる場合もありそうだが、自分のケースではうまく行かなかった。まだ実装が不十分なので。。という内容の開発者の書き込みもあった。詳細はこちら、https://emscripten.org/docs/porting/pthreads.html
- JavaScriptでコードを置き換えた
- 戻り値が省略されている等、一致しないシグネチャーを合わせた

などなど

#### 修正した依存ライブラリの組み込み
直接依存していないものも含めて、修正済みの依存ライブラリをパッケージに組み込むには、Cargo.tomlで以下のような形で、patch指定をする必要が有る。

##### creates-ioから取得しているライブラリ
```
[patch.crates-io]
some-library = { path = "./dependency/some-library" }
```
##### github等から取得しているライブラリ

```
[patch."https://github.com/foo/some-library"]
some-library = { version = "0.5.0", path = "./dependency/some-library" }
```

#### その他
- Cargo.tomlに`edition = "2018"`がないとビルドできなかった。 Rust 2018というC++11みたいなものを使えという意味らしい。

## JavaScriptとRustの相互呼び出し
### RustからJavaScriptの関数を呼び出す
グローバルな名前空間で定義したJavaScriptの関数を、Rust側から呼び出せる。
Rustからは、`emscripten_run_script*`にJavaScriptを書いた文字列を渡すと、JavaScript上でevalしてくれる。結果を返すタイプの関数であれば、その結果も返してくれる。

|関数|戻り値|
|----------|-----|
|emscripten_run_script_string|*const std::os::raw::c_char|
|emscripten_run_script_int|std::os::raw::c_int|
|emscripten_run_script|なし|

他に試していない関数がいろいろとここに、https://emscripten.org/docs/api_reference/emscripten.h.html

以下は例。

#### JavaScript
```javascript
function foo(s: string) {
  console.log(s)
}

window.foo = foo // global関数に
```

#### Rust
```rust
use std::ffi::CString;

extern "C" {
  pub fn emscripten_run_script(s: *const std::os::raw::c_char);
}

pub fn set_status(s: &str) {
  unsafe {
    let script = CString::new(format!("foo({:?})", s)).unwrap();
    emscripten_run_script(script.as_ptr());
  }
}
```

### JavaScriptからRustの関数を呼び出す
emccのオプションに、`-s EXTRA_EXPORTED_RUNTIME_METHODS=['cwrap']`の追加して、
グルーコードが提供する`cwrap`という関数を使って、関数をJavaScript側で定義する。
cwrapの最後の引数に`{ async: true }`を指定すると`Promise<戻り値の型>`を返すようになる。省略すると戻り値の型を返す。Promise化のためには、emccオプションに`-s ASYNCIFY`の追加が必要。戻り値の型を`void`にしたい場合は`null`を指定する。

更に、emccのオプションの`EXPORTED_FUNCTIONS`のリストに、`-s EXPORTED_FUNCTIONS=['_foo']`のように、関数名の先頭にアンダースコアーをつけたものを指定する必要が有る。これがないと最適化で呼び出す関数が消されるらしい。
#### JavaScript側

```javascript
const foo = Module.cwrap("foo" /* 関数名*/, "string" /* 戻り値の型 */, ["string", "number"] /* 引数の型 */, { async: true })`
```

#### Rust側
```rust
use std::os::raw::{c_char, c_int};

#[no_mangle]
pub extern fn foo(name: *const c_char, age: c_int) -> *const c_char {
  ...
}
```

##### c_charポインタからStringへの変換
例

```rust
let s = CStr::from_ptr(some_c_char_ptr).to_str().unwrap();
```

##### ポインター型を返す場合
文字列型であれば、`into_raw()`とすると、ライフサイクルの監視から外れて、関数の終わりでライフサイクルが終わったとしも、値がJavaScript側に渡る。例えば、

```rust
CString::new("some string").unwrap().into_raw()
```
のようにする。

`into_raw()`で取得したポインタは、どこかで`from_raw()`を使ってRustの世界に戻して、割当てられたメモリを開放する必要がある。

このような形でJavaScriptに戻すと、モジュールのリニアメモリに文字列が書き出されて、その先頭へのオフセットが戻ってくるらしい。後は文字列の長さがわかれば、リニアメモリから直接文字を読み出せる。説明はここ。https://stackoverflow.com/questions/47529643/how-to-return-a-string-or-similar-from-rust-in-webassembly
cwrapを使う場合、おそらくその辺りをcwrapがやってくれている。

JavaScript側でEmscriptenのheapにメモリを割り当てて、それを使う方法もあるらしい。ここにその話が書いてある。https://stackoverflow.com/questions/17883799/how-to-handle-passing-returning-array-pointers-to-emscripten-compiled-code

#### 相互に渡せる型
| JavaScript | Rust |
|------------|------|
| number | c_int |
| string | *const c_char |
| byte array |  *const c_int (メモにないので未確認。 バイト列へのポインタだったと記憶）|

## React SPAへのwasmの組み込み
以下、webpackを使っている想定。必要なファイルは、wasm本体とグルーJSのみ。

### 必要なemccオプション
以下のビルドオプションを追加。

|オプション|説明|
|--------|----|
|`-s MODULARIZE=1`|モジュールの形でグルーJSを出力|
|`-s ENVIRONMENT=web`|ブラウザがサポートしないコードを出力しない|

### wasmのロード
#### タイミング
ルートコンポーネントの`componetDidMount`あたり。

#### 方法
グルーJSでdefault exportされている関数を呼び出す。その戻り値を使うと、wasmとやり取りすることが出来るようになる。

#### 仕組み
グルーJSは自身でwasmをロードする。デフォルトでは、グルーJSはローカルファイルシステムにwasmがある前提で動くので、`locateFile`フックを使って、wasmファイルへのパスをURLに変換する。その上で、webpackのfile-loaderでwasmファイルを**内容変えずにファイルとして出力**して、そのURLがwasmファイルを返すようにすると、グルーJSはwasmをネットワーク経由でロードする。

#### Webpackの設定
```javascript
module: {
    rules: [
      ...
      {
        test: /\.wasm$/,
        type: "javascript/auto",  // これがないと必要なヘッダがないとエラーになる
        loader: "file-loader",
        options: {
          name: '[name]-[hash].[ext]'  // ブラウザにキャッシュされないようにhashを含める
        }
      }
    ]
```

#### wasmをロードするコード
```typescript
const glue = require("[グルーJSへの相対パス]/foo.js")
const wasm = require("[wasm本体への相対パス]/foo.wasm")
...
componentDidMount() {
  const module = glue({
    locateFile(path: string) {  // convert from source wasm file name to asset url
      if (path === "foo.wasm") {
        return `${ファイル名部分を除いたwasmファイルのURL}/${wasm.default}` // wasm.defaultにはhashが含まれた実際にwebpackが出力したファイル名が入る 
      }
      return path
    }
  })
  module.onRuntimeInitialized = () => {
    // wasmが使用可能になると、この関数が呼び出される。
  }
}
```

### アプリのレスポンスの改善
wasm側のコードが動いている間は、ブラウザ側のイベントループが止まってしまうので、実行に時間がかかるコードを動かす場合は、画面が固まってしまうが、

```rust
extern "C" {
  pub fn emscripten_sleep(i: std::os::raw::c_int);
}
```

で、emscripten_sleepへのbindingを作って、

```rust
emscripten_sleep(1);
```

をRust側で呼び出すと、その時点で、一時的にブラウザ側に処理を戻すことができる。
なお、emccオプションに`-s ASYNCIFY`の追加が必要。

#### 非同期イベントループ
wasm側で非同期のイベントループを作って、１ループごとに処理をブラウザに戻すこともできる。
Rustのコードを１から作るのなら、この形で作るべき。以下の様に１ループ分の処理を実行する関数を定義して、`emscripten_set_main_loop`に渡せば良い。

```rust
fn f() {
  // 1ループ分の処理
}

fn main() {
  ...
  emscripten_set_main_loop(f, 60 /* fps */, 1 /* 1 for infinite loop */);
  ...
}
```

細かい説明はここに、
https://emscripten.org/docs/porting/emscripten-runtime-environment.html