探索前沿:移動端WebAssembly虛擬機器器
所謂“移動端動態執行WebAssembly”就是要找個移動端可用的WebAssembly虛擬機器器。所謂虛擬機器器簡單說就是能執行Wasm二進位制格式檔案的程式。
初步搜尋
上面兩個 Awesome 連結裡基本上包含比較全的開源的虛擬機器器(VM)了,例如:
- github.com/WAVM/WAVM
- github.com/wasmerio/wa…
- github.com/perlin-netw…
- github.com/paritytech/…
- github.com/fluencelabs…
- github.com/EOSIO/eos-v…
- github.com/wasm3/wasm3
經過對比和試用,最後發現wasm3是目前最好的選擇,理由如下:
wasm3連結:github.com/wasm3/wasm3
- 使用C語言開發。其他vm有Rust和Golang等語言開發,用於移動端的話,相對沒有C語言簡單。
- 純解釋執行,沒有JIT。其他vm有的有JIT功能,有點有AOT功能,存在執行時編譯為Native程式碼的行為,不是為移動端(尤其是iOS)設計。
- 開發活躍。最近一次更新是18天之前,作者也經常在Twitter活躍。
- 天生支援移動端,且不只是移動端。見下圖。
Wasm3 上手
wasm3提供了比較全的 demo,對新手來說可謂是十分周到。
目前我們只關心iOS,可以直接開啟iOS的例子直接build即可。 iOS例子:github.com/wasm3/wasm3…
最關鍵的入門程式碼見這裡: github.com/wasm3/wasm3…
void run_wasm()
{
M3Result result = m3Err_none;
uint8_t* wasm = (uint8_t*)fib32_wasm;
uint32_t fsize = fib32_wasm_len - 1;
printf("Loading WebAssembly...\n");
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...\n");
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\n\n",(end - start) * 1000 / CLOCKS_PER_SEC);
// uint64_t value = *(uint64_t*)(runtime->stack);
// printf("Result: %llu\n",value);
}
複製程式碼
程式碼十分簡單直接,本文就不詳細解釋啦。
進一步使用
有了可用的虛擬機器器,大家可以自己操作一下。
首先用WasmFiddle 寫個簡單的程式碼: wasdk.github.io/WasmFiddle/
點選下載Wasm檔案,然後就可以用如下程式碼運行了:
// 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,NULL);
if (!runtime) FATAL("m3_NewRuntime failed");
IM3Module module;
result = m3_ParseModule (env,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("\ncalling : %s\n",func_name);
IM3Function f;
result = m3_FindFunction (&f,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\n",value);
複製程式碼
再進一步使用
Native提供方法給Wasm用
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",result);
複製程式碼
Native與Wasm操作同一片記憶體
char* vram = (char*)(m3_GetMemory(runtime,0));
int * data = (int*)(char*)(vram + dataOffset);
for (int i=0; i<10; i++) {
printf("%d\n",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檔案很小。
找了一番資料,研究了一番之後,找到了這兩篇文章:
其中第二篇是第一篇的續集,兩篇文章都很簡單易懂,大家跟著上手操作即可。
這裡比較明確了,我們用C語言來生成Wasm。後期如果有需要,可以把ObjC程式碼轉換成C語言,間接實現了ObjC語言熱修復的功能。當然甚至可以用Swift。
C程式碼生成Wasm
首先安裝llvm
brew install llvm
brew link --force llvm
複製程式碼
然後確認支援wasm(操作沒問題的話,肯定是支援了)
llc --version
複製程式碼
然後寫個C程式碼
// Filename: add.c
int add(int a,int b) {
return a*a + b;
}
複製程式碼
執行
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檔案。
引數解釋如下:
# 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
這個語言我也玩耍了好久,曾經想用來實現使用TypeScript來熱修復。但想來大家還得學習這個“非官方”的TypeScript,就不如直接用C語言好了。
參考
- github.com/wasm3/wasm3
- www.assemblyscript.org/
- github.com/appcypher/a…
- depth-first.com/articles/20…
- depth-first.com/articles/20…
- surma.dev/things/c-to…
- 00f.net/2019/04/07/…
- medium.com/perlin-netw…
- eos.io/news/eos-vi…
總結
好了,我們能用C語言生存Wasm了,也能用移動端執行Wasm了。