Slide 1

Slide 1 text

Emscriptenで C/C++アプリを WASM化してブラウザで動 かしてみた Koji AGAWA @atty303

Slide 2

Slide 2 text

Koji AGAWA @atty303 ソフトウェアエンジニア AI事業本部 ミライネージ 普段は Scala書いてます (10年弱 ) 趣味で Rust書いてます (半年 ) #times_atty303

Slide 3

Slide 3 text

今回のターゲットアプリケーション

Slide 4

Slide 4 text

デスクトップ版

Slide 5

Slide 5 text

Web版

Slide 6

Slide 6 text

7日で 7000Visitsぐらい

Slide 7

Slide 7 text

PoBのアーキテクチャ

Slide 8

Slide 8 text

100% Lua ! Luaは、軽量で柔軟なスクリプト言語であり、組み込み用途やゲーム開発に広く使用され ている

Slide 9

Slide 9 text

Host Adapter 例えば描画に関係する関数はこれだけ GetScreenSize SetDrawLayer SetViewport SetDrawColor DrawImage DrawImageQuad DrawString DrawStringWidth DrawStringCursorIndex

Slide 10

Slide 10 text

Luaをなんとかブラウザで動かせれば PoBのブラウザ版が作れそう Luaのオリジナル実装は C言語 他の言語( JSや Rust)での実装もあったが互換性に不安あり オリジナル実装をブラウザで動かしたい!

Slide 11

Slide 11 text

Compile your existing projects written in C or C++ — or any language that uses LLVM — to browsers.

Slide 12

Slide 12 text

Emscriptenとは? LLVMをベースにしたコンパイラツールチェイン C/C++コードを WASMに変換する WASM黎明期からあるツールだがまだ開発が続いている Unityの WebGLエキスポートなどでも使われている

Slide 13

Slide 13 text

WebAssembly(WASM)とは? 主要なブラウザでサポートされる新しめの言語?ランタイム? 高速( GCがない!) JSと相互運用が可能

Slide 14

Slide 14 text

Emscriptenの使い方のイメージ 普通の CMakeでのビルド cd build cmake .. ninja Emscriptenによる CMakeでのビルド cd build emcmake cmake .. ninja

Slide 15

Slide 15 text

Cと JSの相互作用

Slide 16

Slide 16 text

EM_ASM_JS / C → JS static int DrawStringWidth(lua_State *L) { int n = lua_gettop(L); assert(n >= 3); assert(lua_isnumber(L, 1)); assert(lua_isstring(L, 2)); assert(lua_isstring(L, 3)); int height = lua_tointeger(L, 1); int font = luaL_checkoption(L, 2, "FIXED", fontMap); const char *text = lua_tostring(L, 3); int width = EM_ASM_INT({ return Module.getStringWidth($0, $1, UTF8ToString($2)); }, height, font, text); lua_pushinteger(L, width); return 1; } EM_ASM_JSマクロでインラインで JSが書ける

Slide 17

Slide 17 text

Asyncify EM_ASYNC_JS(int, paste, (), { var text = await Module.paste(); var lengthBytes = lengthBytesUTF8(text) + 1; var stringOnWasmHeap = Module._malloc(lengthBytes); stringToUTF8(text, stringOnWasmHeap, lengthBytes); return stringOnWasmHeap; }); static int Paste(lua_State *L) { const char *text = (const char *)paste(); lua_pushlstring(L, text, strlen(text)); free((void *)text); return 1; } Cから同期的に JSの Promiseを解決できる

Slide 18

Slide 18 text

cwrap / JS → C const onFrame = module.cwrap("on_frame", "number", [], { async: true }); onFrame(); EMSCRIPTEN_KEEPALIVE int on_frame() { lua_State *L = GL; draw_begin(); void *buffer; size_t size; draw_get_buffer(&buffer, &size); EM_ASM({ Module.drawCommit($0, $1); }, buffer, size); draw_end(); return 0; }

Slide 19

Slide 19 text

WASMヒープ const wasmMemory = new WebAssembly.Memory(); const HEAPU8 = new Uint8Array(wasmMemory.buffer); WASMのヒープは JSから見ると単なる ArrayBuffer Emscriptenでは HEAPU8などの TypedArrayが公開されている JSで mallocして Cで freeなどもできる

Slide 20

Slide 20 text

C/C++ ←→ JS のやりとりとメモリ表現がわかればあと はなんとかなった

Slide 21

Slide 21 text

高速化のために C/C++( WASM)から細かく JSの関数を呼ぶとそれなりに遅い なるべくそれぞれのランタイムに閉じる時間を長くする 描画関数は Cの世界でバッファに貯めて、フレーム終了時にまとめて JSへ転送

Slide 22

Slide 22 text

苦労するところ メモリ管理を間違えると例外が出る Cのメモリ破壊と同じく例外から原因が分からない デバッグビルド+サニタイザでパフォーマンスを犠牲にある程度デバッグできる

Slide 23

Slide 23 text

雑感 Emscripten+ WASMはかなり実用的 既存の C/C++コードの実行プラットフォームを広げたいときの選択肢としてアリ 新規で作る時は Rustなど WASMをネイティブサポートしている言語を使うほうが良いと 思う どちらにせよ昨今の GCあり言語ほど楽ではないので覚悟は必要 WasmGCが実用レベルになってきているようなので GCあり言語から利用するのが一番 楽そう