wasmer-python と emcc で C/C++ コードを wasm にして python でポータブルに動かすメモ(制約多い)

## 背景

Python のモジュールや Blender の addon(プラグイン)など, クロスプラットホームで C++ で書きたい(すごい性能が必要というわけではないが, C/C++ が開発がラクなときとか). 

ただ, 作った module や plugin を配布して使ってもらうにしても, 各プラットフォームにプレビルド用意したりと C/C++ のビルドがめんどい.
(Windows で pip でソースからビルドさせる場合, ユーザーの環境に Visual Studio とか入れてもらう必要がありめんどい)

python だと cibuildwheel で pypi 向けにビルドするのもあるけど, 設定がめんどい

https://qiita.com/syoyo/items/97f35b4d5c40761cc314 

plugin を WASM にし, クロスプラットホームな plugin として扱えないか?

メモリが確保できないなど(C++ STL で new とかしている(e.g. `vector.resize()` とかは全滅), 結構制約あることがわかりましたが, 一応動かせはします.

より汎用に C/C++ コードを動かしたいときは, wasi-sdk 利用を検討ください.

wasi-sdk + wasmer-python で C/C++ アプリを WASM でポータブルに動かすメモ
https://qiita.com/syoyo/items/300d4ef7fd105e69ad10

## 方法

wasmer-python がありました.

https://github.com/wasmerio/wasmer-python

ありがとうございます :pray:

### C/C++ コード

emcc でコンパイルします.

```c
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#else
#define EMSCRIPTEN_KEEPALIVE
#endif

#ifdef __cplusplus
extern "C" 
#endif

float EMSCRIPTEN_KEEPALIVE func(float a, float b) {
  return a + 2.0f * b;
}
#ifdef __cplusplus
} 
#endif
```

```
$ emcc test.c
```

a.out.wasm が出来上がります.


### 実行

wasmer-python のサンプルを参考にして動かします.

```py
# main.py
from wasmer import engine, Store, Module, Instance
from wasmer_compiler_cranelift import Compiler

# Let's define the store, that holds the engine, that holds the compiler.
store = Store(engine.JIT(Compiler))

# Let's compile the module to be able to execute it!
module = Module(store, open('a.out.wasm', 'rb').read())

# Now the module is compiled, we can instantiate it.
instance = Instance(module)

result = instance.exports.func(5.0, 3.0)

print(result) 
```

```
$ python main.py
11.0
```

Voila!

## 画像データなどポインタを渡す場合

wasmer-python にまともなドキュメントやサンプルコードがなくてつらい :cry:

python 側に Float32Array など用意されていますが, これをそのまま使うのはできないです... :cry:
(これらは主にPython 側で, メモリ上のデータを Float32Array で扱いたいときなど用)

### メモリモデル

https://wasmerio.github.io/wasmer-python/api/wasmer/wasmer.html#Memory

64KB 単位のページでメモリ確保されます.

`instance.exports.memory` では, WASM 側のデフォルト?メモリ領域にアクセスできます. デフォルトでは 256 pages です.

wasmer-python では, 基本メモリアドレス(ページのオフセット)を渡すことでやりとります.

組み込み C/C++ みたいな感じですね.

#### Wasmer 
 
wasm 側(C/C++側)で malloc/new で動的メモリ確保というのもできません(シンボルエラーになる. すごい頑張ればできるっぽいようだが... これは JS で動かすときもカナ :thinking: )



## メモリの受け渡し

`instance.exports.memory` でないとダメっぽいようです.

なぜか `Memory` とか `Float32Array`(の view) からオフセット(`byteOffset`, `offset`)は取得できません :cry: ので, オフセットは必要に応じて何かしら記憶しておく必要があります.

```py
# main.py
from wasmer import engine, Store, Memory, MemoryType, Module, Instance, Float32Array
from wasmer_compiler_cranelift import Compiler

# Let's define the store, that holds the engine, that holds the compiler.
store = Store(engine.JIT(Compiler))

# Let's compile the module to be able to execute it!
module = Module(store, open('a.out.wasm', 'rb').read())
print(module)

# Now the module is compiled, we can instantiate it.
instance = Instance(module)

m = instance.exports.memory
print(m.size)
m.grow(2)
print(m.size)

f32_view = m.float32_view(0)
f32_view[0] = 1.0
f32_view[1] = 2.0
f32_view[2] = 3.3
f32_view[3] = 4.4
print(f32_view)

#instance.exports.func
result = instance.exports.func(0, 4)
print(result)

```

```C++
#include <vector>
#include <string>

#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#else
#define EMSCRIPTEN_KEEPALIVE
#endif

#ifdef __cplusplus
extern "C" {
#endif

float EMSCRIPTEN_KEEPALIVE func(const float *a, size_t n) { //, float *b) {
  float sum = 0.0f;
  for (size_t i = 0; i < n; i++) {
    sum += a[i];
  }

  return sum;
}

#ifdef __cplusplus
}

```

10.7 と出るはずです!(正確には丸め誤差とか出て 10.700000762939453)


#### offset

`m.float32_view` とかでは, 実際のメモリアドレスは要素のバイト数倍になります(e.g. `float32_view(1)` だと４バイト進む)


`export.func` などの WASM とのインターフェースでは, ポインタのオフセット値は常に `byte` になります.

## C++ 例外

使えません :cry:

`-fno-exceptions` `-fno-rtti` でコンパイルしましょう.

https://emscripten.org/docs/porting/exceptions.html

WASM での例外の仕組みがあるようですが, wasmer(WASI) ではまだ対応していないかも?

## malloc/new とか

↑で例外 off にしても, `abort` シンボルが見つからないエラーが出ます.
host(実行側)で用意しないとダメです.

https://github.com/flow-heater/hello-wasm-pyts/commit/09deec2b0c44baf84d71250935e649f406f4f989

しかし, それでも `emscripten_memcpy_big` などが missing になるので, 自前で python 側で実装するしかないかもですが... めんどうなので実質使えないと思いましょう.

## その他

embind は扱えません :cry:

https://github.com/wasmerio/wasmer/issues/1378

## まとめ

wasmer などは WASI(サンドボックス環境で動かすのを想定しており, システム系関数などはあまり提供されていない)な WASM を対象としていました.

emscripten は JavaScript をターゲットとしており, JavaScript 側でのバインディング(エミュレーション)は提供されますが, WASI 系のサポートは行っていませんでした.

emscripten 利用はかなり制約があるのがわかりましたので, 任意な C/C++ が動くというのは考えないほうがいいです. 

ちょっとした画像フィルタ(メモリ確保などしない)を, C/C++ ではぱぱっとかけるが Python で書くのが面倒なときに使うくらいでしょうか...
 
より一般的な C/C++ コードを動かすばあい, wasi-sdk で C/C++ コンパイルするのがいいかもしれません.

## TODO

* [ ] wasi-sdk(or clang + wasi-libc?) でのコンパイルを試す.