所谓“移动端动态执行WebAssembly”就是要找个移动端可用的WebAssembly虚拟机。所谓虚拟机简单说就是能执行Wasm二进制格式文件的程序。
初步搜索
- https://github.com/appcypher/awesome-wasm-runtimes
- https://github.com/appcypher/awesome-wasm-langs
上面两个 Awesome 链接里基本上包含比较全的开源的虚拟机(VM)了,例如:
- https://github.com/WAVM/WAVM
- https://github.com/wasmerio/wasmer
- https://github.com/perlin-network/life
- https://github.com/paritytech/wasmi
- https://github.com/fluencelabs/wagon
- https://github.com/EOSIO/eos-vm
- https://github.com/wasm3/wasm3
经过对比和试用,最后发现wasm3是目前最好的选择,理由如下:
wasm3链接:https://github.com/wasm3/wasm3
- 使用C语言开发。其他vm有Rust和Golang等语言开发,用于移动端的话,相对没有C语言简单。
- 纯解释执行,没有JIT。其他vm有的有JIT功能,有点有AOT功能,存在运行时编译为Native代码的行为,不是为移动端(尤其是iOS)设计。
- 开发活跃。最近一次更新是18天之前,作者也经常在Twitter活跃。
- 天生支持移动端,且不只是移动端。见下图。
当看到上图时,就知道选wasm3没错了。
Wasm3 上手
wasm3提供了比较全的 demo,对新手来说可谓是十分周到。
https://github.com/wasm3/wasm3/tree/master/platforms
目前我们只关心iOS,可以直接打开iOS的例子直接build即可。
iOS例子:https://github.com/wasm3/wasm3/tree/master/platforms/ios
最关键的入门代码见这里:
https://github.com/wasm3/wasm3/blob/master/platforms/ios/wasm3/main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | void run_wasm() { M3Result result = m3Err_none; uint8_t* wasm = (uint8_t*)fib32_wasm; uint32_t fsize = fib32_wasm_len - 1; printf("Loading WebAssembly...\ "); IM3Environment env = m3_NewEnvironment (); if (!env) FATAL("m3_NewEnvironment failed"); IM3Runtime runtime = m3_NewRuntime (env, 8192, NULL); if (!runtime) FATAL("m3_NewRuntime failed"); IM3Module module; result = m3_ParseModule (env, &module, wasm, fsize); if (result) FATAL("m3_ParseModule: %s", result); result = m3_LoadModule (runtime, module); if (result) FATAL("m3_LoadModule: %s", result); IM3Function f; result = m3_FindFunction (&f, runtime, "fib"); if (result) FATAL("m3_FindFunction: %s", result); printf("Running fib(" FIB_ARG_VALUE ") on WebAssembly...\ "); const char* i_argv[2] = { FIB_ARG_VALUE, NULL }; clock_t start = clock(); result = m3_CallWithArgs (f, 1, i_argv); clock_t end = clock(); if (result) FATAL("m3_CallWithArgs: %s", result); printf("Elapsed: %ld ms\ \ ", (end - start) * 1000 / CLOCKS_PER_SEC); // uint64_t value = *(uint64_t*)(runtime->stack); // printf("Result: %llu\ ", value); } |
代码十分简单直接,本文就不详细解释啦。
进一步使用
有了可用的虚拟机,大家可以自己操作一下。
首先用WasmFiddle 写个简单的代码:
https://wasdk.github.io/WasmFiddle/
点击下载Wasm文件,然后就可以用如下代码运行了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | // Load wasm data std::string path = "path to add.wasm"; std::string buffer; if (!wasmfix::read_buffer_from_file(path, buffer)) { return; } uint8_t* wasm = (uint8_t*)buffer.data(); uint32_t fsize = (uint32_t)buffer.size(); // Load wasm M3Result result = m3Err_none; IM3Environment env = m3_NewEnvironment (); if (!env) FATAL("m3_NewEnvironment failed"); IM3Runtime runtime = m3_NewRuntime (env, 8192, NULL); if (!runtime) FATAL("m3_NewRuntime failed"); IM3Module module; result = m3_ParseModule (env, &module, wasm, fsize); if (result) FATAL("m3_ParseModule: %s", result); result = m3_LoadModule (runtime, module); if (result) FATAL("m3_LoadModule: %s", result); const char* func_name = "add"; const char* i_argv[3] = {"11", "5", NULL }; printf("\ calling : %s\ ", func_name); IM3Function f; result = m3_FindFunction (&f, runtime, func_name); if (result) FATAL("m3_FindFunction: %s", result); result = m3_CallWithArgs (f, sizeof(i_argv)/sizeof(const char*) - 1, i_argv); if (result) FATAL("m3_CallWithArgs: %s", result); u32 value = *(u32*)(runtime->stack); printf("Result: %u\ ", value); |
再进一步使用
Native提供方法给Wasm用
1 2 3 4 5 6 7 | const char* module_name = "env"; const char* func_name = "bind_sum"; const char* func_signature = "i(ii)"; M3RawCall func_raw = bind_sum_raw; result = m3_LinkRawFunction(module, module_name, func_name, func_signature, func_raw); if (result) FATAL("m3_LinkRawFunction(%s): %s", func_name, result); |
Native与Wasm操作同一片内存
1 2 3 4 5 6 7 | char* vram = (char*)(m3_GetMemory(runtime, 0, 0)); int * data = (int*)(char*)(vram + dataOffset); for (int i=0; i<10; i++) { printf("%d\ ",data[i]); } |
m3_GetMemory 的使用确实麻烦,具体使用方法就后续文章介绍,这里大家先自行研究下哈。
用什么语言来生存Wasm呢
我们的目的是要实现iOS热修复,如果像上面的例子那样,还用Javascript生成Wasm,那还不如直接用JavascriptCore或者QuickJS,毕竟Javascript的虚拟机提供的接口更友好、好用。
但由于Wasm这个中间层,理论上我们的热修复可以用任何语言,只要这个代码可以转换成Wasm。这也是我想探索一下的原因吧,如果我们可以实现用C语言去修复,或者ObjC语言,甚至可以用Swift修复。再甚至可以用Rust(那就是太闲了哈)。
C语言生存Wasm,大家肯定会想到 Emscripten ,但调研后 Emscripten 生成的Wasm文件太大了,Emscripten 太过于强大。我们想要个更“单纯”的Wasm。就像WasmFiddle那样生成的Wasm文件很小。
找了一番资料,研究了一番之后,找到了这两篇文章:
- https://surma.dev/things/c-to-webassembly/
- https://depth-first.com/articles/2019/10/16/compiling-c-to-webassembly-and-running-it-without-emscripten/
其中第二篇是第一篇的续集,两篇文章都很简单易懂,大家跟着上手操作即可。
这里比较明确了,我们用C语言来生成Wasm。后期如果有需要,可以把ObjC代码转换成C语言,间接实现了ObjC语言热修复的功能。当然甚至可以用Swift。
C代码生成Wasm
首先安装llvm
1 2 | brew install llvm brew link --force llvm |
然后确认支持wasm(操作没问题的话,肯定是支持了)
1 | llc --version |
然后写个C代码
1 2 3 4 | // Filename: add.c int add(int a, int b) { return a*a + b; } |
运行
1 | clang --target=wasm32 -O3 -flto --no-standard-libraries -Wl,--export-all -Wl,--no-entry -Wl,--allow-undefined -Wl,--lto-O3 -o add.wasm add.c |
即可产出 wasm文件。
参数解释如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 | # sample: # # clang \\ # --target=wasm32 \\ # + -O3 \\ # Agressive optimizations # + -flto \\ # Add metadata for link-time optimizations # -nostdlib \\ # -Wl,--no-entry \\ # -Wl,--export-all \\ # -Wl,--allow-undefined \\ # + -Wl,--lto-O3 \\ # Aggressive link-time optimizations # -o add.wasm \\ # add.c |
更详细的解释就见那两篇文章吧。
AssemblyScript
https://www.assemblyscript.org/
这个语言我也玩耍了好久,曾经想用来实现使用TypeScript来热修复。但想来大家还得学习这个“非官方”的TypeScript,就不如直接用C语言好了。
参考
- https://github.com/wasm3/wasm3
- https://www.assemblyscript.org/
- https://github.com/appcypher/awesome-wasm-langs
- https://depth-first.com/articles/2019/10/16/compiling-c-to-webassembly-and-running-it-without-emscripten/
- https://depth-first.com/articles/2019/05/15/compiling-inchi-to-webassembly-part-1/
- https://surma.dev/things/c-to-webassembly/
- https://00f.net/2019/04/07/compiling-to-webassembly-with-llvm-and-clang/
- https://medium.com/perlin-network/life-a-secure-blazing-fast-cross-platform-webassembly-vm-in-go-ea3b31fa6e09
- https://eos.io/news/eos-virtual-machine-a-high-performance-blockchain-webassembly-interpreter/
总结
好了,我们能用C语言生存Wasm了,也能用移动端执行Wasm了。