LoginSignup
4
2

More than 1 year has passed since last update.

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

Last updated at Posted at 2021-07-28

背景

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

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

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

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 がありました.

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

C/C++ コード

emcc でコンパイルします.

#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 のサンプルを参考にして動かします.

# 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 で扱いたいときなど用)

メモリモデル

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: ので, オフセットは必要に応じて何かしら記憶しておく必要があります.

# 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)

#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) だと4バイト進む)

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

C++ 例外

使えません :cry:

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

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

malloc/new とか

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

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

その他

embind は扱えません :cry:

まとめ

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?) でのコンパイルを試す.
4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2