1. 程式人生 > Android開發 >探索前沿:移動端WebAssembly虛擬機器器

探索前沿:移動端WebAssembly虛擬機器器

所謂“移動端動態執行WebAssembly”就是要找個移動端可用的WebAssembly虛擬機器器。所謂虛擬機器器簡單說就是能執行Wasm二進位制格式檔案的程式。

初步搜尋

上面兩個 Awesome 連結裡基本上包含比較全的開源的虛擬機器器(VM)了,例如:

經過對比和試用,最後發現wasm3是目前最好的選擇,理由如下:

wasm3連結:github.com/wasm3/wasm3

  1. 使用C語言開發。其他vm有Rust和Golang等語言開發,用於移動端的話,相對沒有C語言簡單。
  2. 純解釋執行,沒有JIT。其他vm有的有JIT功能,有點有AOT功能,存在執行時編譯為Native程式碼的行為,不是為移動端(尤其是iOS)設計。
  3. 開發活躍。最近一次更新是18天之前,作者也經常在Twitter活躍。
  4. 天生支援移動端,且不只是移動端。見下圖。

當看到上圖時,就知道選wasm3沒錯了。

Wasm3 上手

wasm3提供了比較全的 demo,對新手來說可謂是十分周到。

github.com/wasm3/wasm3…

目前我們只關心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

www.assemblyscript.org/

這個語言我也玩耍了好久,曾經想用來實現使用TypeScript來熱修復。但想來大家還得學習這個“非官方”的TypeScript,就不如直接用C語言好了。

參考

總結

好了,我們能用C語言生存Wasm了,也能用移動端執行Wasm了。