本次分享的文章是基於WebAssembly的探索與研究。最近須要作一個與加密相關的項目,想將後端的加密方案直接放到前端使用,好處是加密方案代碼只用維護一套,且後端方案更貼近系統底層,應該能夠獲得更好的性能。剛好發現 WebAssembly ,它是爲了可移植的目標而設計的,能夠知足需求。javascript
此次研究 WebAssembly的過程當中遇到了各類問題,我均記錄下來,並在後期能夠和你們一塊兒分享,文末放置了參考的文章,你們能夠延伸閱讀。這篇文章是本系列的第一部分,主要是瞭解WebAssembly和WebAssembly的基本使用方法。html
當人們說 WebAssembly 更快的時候,通常來說是與 JavaScript 相比而言的。前端
JavaScript 於 1995 年問世,它的設計初衷並非爲了執行起來快,在前 10 個年頭,它的執行速度也確實不快。緊接着,瀏覽器市場競爭開始激烈起來。被人們廣爲傳播的「性能大戰」在 2008 年打響。許多瀏覽器引入了 Just-in-time 編譯器,也叫 JIT。基於 JIT 的模式,JavaScript 代碼的運行漸漸變快。正是因爲這些 JIT 的引入,使得 JavaScript 的性能達到了一個轉折點,JS 代碼執行速度快了 10 倍。java
隨着性能的提高,JavaScript 能夠應用到之前根本沒有想到過的領域,好比用於後端開發的 Node.js。性能的提高使得 JavaScript 的應用範圍獲得很大的擴展。node
但這也漸漸暴露出了 JavaScript 的問題:python
針對以上兩點缺陷,近年來出現了一些 JS 的代替語言,例如:git
以上嘗試各有優缺點,其中:github
三大瀏覽器巨頭分別提出了本身的解決方案,互不兼容,這違背了 Web 的宗旨; 是技術的規範統一讓 Web 走到了今天,所以造成一套新的規範去解決 JS 所面臨的問題迫在眉睫。web
因而 WebAssembly 誕生了,WebAssembly 是一種新的字節碼格式,主流瀏覽器都已經支持 WebAssembly。 和 JS 須要解釋執行不一樣的是,WebAssembly 字節碼和底層機器碼很類似可快速裝載運行,所以性能相對於 JS 解釋執行大大提高。 也就是說 WebAssembly 並非一門編程語言,而是一份字節碼標準,須要用高級編程語言編譯出字節碼放到 WebAssembly 虛擬機中才能運行, 瀏覽器廠商須要作的就是根據 WebAssembly 規範實現虛擬機。shell
WebAssembly(縮寫 Wasm)是基於堆棧虛擬機的二進制指令格式。Wasm爲了一個可移植的目標而設計的,可用於編譯C/C+/RUST等高級語言,使客戶端和服務器應用程序可以在Web上部署。
上面這段話是來自官方的定義。
咱們能夠從字面上理解,WebAssembly的名字帶個彙編Assembly,因此咱們從其名字上就能知道其意思是給Web使用的彙編語言,是經過Web執行低級二進制語法。可是WebAssembly並非直接用匯編語言,而是提供了抓換機制(LLVM IR),把高級別的語言(C,C++和Rust)編譯爲WebAssembly,以便有機會在瀏覽器中運行。能夠看出來它實際上是一種運行機制,一種新的字節碼格式(.wasm),而不是新的語言。
若是要把一個C/C++程序編譯成一個.wasm文件,是須要編譯工具來完成的。WebAssembly 社區推薦經常使用工具:
Emscripten:能把 C、C++代碼轉換成 wasm、asm.js;
接下來,您須要經過源碼本身編譯一個Emscripten。運行下列命令來自動化地使用Emscripten SDK。
git clone https://github.com/juj/emsdk.git cd emsdk # 編譯源碼 ./emsdk install latest # 激活sdk ./emsdk activate latest #設置環境變量 source ./emsdk_env.sh
在運行上述命令的時候,可能會遇到以下問題:
./emsdk install latest 報錯:
likai@likaideMacBook-Pro:~/resource/emsdk$ ./emsdk install latest Installing SDK 'sdk-releases-upstream-7a7f38ca19da152d4cd6da4776921a0f1e3f3e3f-64bit'.. Installing tool 'node-12.18.1-64bit'.. Error: Downloading URL 'https://storage.googleapis.com/webassembly/emscripten-releases-builds/deps/node-v12.18.1-darwin-x64.tar.gz': <urlopen error unknown url type: https> Warning: Possibly SSL/TLS issue. Update or install Python SSL root certificates (2048-bit or greater) supplied in Python folder or https://pypi.org/project/certifi/ and try again. Installation failed!
解決辦法:
簡單看了emsdk的內容,發現這個命令調用的是emsdk.py文件,因此使用 ./emsdk.py install latest便可解決。
likai@likaideMacBook-Pro:~/resource/emsdk$ ./emsdk.py install latest Installing SDK 'sdk-releases-upstream-7a7f38ca19da152d4cd6da4776921a0f1e3f3e3f-64bit'.. Installing tool 'node-12.18.1-64bit'.. Downloading: /Users/likai/hisun/resource/emsdk/zips/node-v12.18.1-darwin-x64.tar.gz from https://storage.googleapis.com/webassembly/emscripten-releases-builds/deps/node-v12.18.1-darwin-x64.tar.gz, 20873670 Bytes Unpacking '/Users/likai/hisun/resource/emsdk/zips/node-v12.18.1-darwin-x64.tar.gz' to '/Users/likai/hisun/resource/emsdk/node/12.18.1_64bit' Done installing tool 'node-12.18.1-64bit'. Installing tool 'python-3.7.4-2-64bit'.. Downloading: /Users/likai/hisun/resource/emsdk/zips/python-3.7.4-2-macos.tar.gz from https://storage.googleapis.com/webassembly/emscripten-releases-builds/deps/python-3.7.4-2-macos.tar.gz, 25365593 Bytes Unpacking '/Users/likai/hisun/resource/emsdk/zips/python-3.7.4-2-macos.tar.gz' to '/Users/likai/hisun/resource/emsdk/python/3.7.4-2_64bit' Done installing tool 'python-3.7.4-2-64bit'. Installing tool 'releases-upstream-7a7f38ca19da152d4cd6da4776921a0f1e3f3e3f-64bit'.. Downloading: /Users/likai/hisun/resource/emsdk/zips/7a7f38ca19da152d4cd6da4776921a0f1e3f3e3f-wasm-binaries.tbz2 from https://storage.googleapis.com/webassembly/emscripten-releases-builds/mac/7a7f38ca19da152d4cd6da4776921a0f1e3f3e3f/wasm-binaries.tbz2, 69799761 Bytes Unpacking '/Users/likai/hisun/resource/emsdk/zips/7a7f38ca19da152d4cd6da4776921a0f1e3f3e3f-wasm-binaries.tbz2' to '/Users/likai/hisun/resource/emsdk/upstream' Done installing tool 'releases-upstream-7a7f38ca19da152d4cd6da4776921a0f1e3f3e3f-64bit'. Running post-install step: npm ci ... Done running: npm ci Done installing SDK 'sdk-releases-upstream-7a7f38ca19da152d4cd6da4776921a0f1e3f3e3f-64bit'.
一樣激活 Emscripten也是使用 ./emsdk.py activate latest
likai@likaideMacBook-Pro:~/resource/emsdk$ ./emsdk.py activate latest Setting the following tools as active: node-12.18.1-64bit python-3.7.4-2-64bit releases-upstream-7a7f38ca19da152d4cd6da4776921a0f1e3f3e3f-64bit Next steps: - To conveniently access emsdk tools from the command line, consider adding the following directories to your PATH: /Users/likai/hisun/resource/emsdk /Users/likai/hisun/resource/emsdk/node/12.18.1_64bit/bin /Users/likai/hisun/resource/emsdk/python/3.7.4-2_64bit/bin /Users/likai/hisun/resource/emsdk/upstream/emscripten - This can be done for the current shell by running: source "/Users/likai/hisun/resource/emsdk/emsdk_env.sh" - Configure emsdk in your bash profile by running: echo 'source "/Users/likai/hisun/resource/emsdk/emsdk_env.sh"' >> $HOME/.bash_profile
source ./emsdk_env.sh
likai@likaideMacBook-Pro:~/resource/emsdk$ source ./emsdk_env.sh Adding directories to PATH: PATH += /Users/likai/hisun/resource/emsdk PATH += /Users/likai/hisun/resource/emsdk/upstream/emscripten PATH += /Users/likai/hisun/resource/emsdk/node/12.18.1_64bit/bin PATH += /Users/likai/hisun/resource/emsdk/python/3.7.4-2_64bit/bin Setting environment variables: EMSDK = /Users/likai/hisun/resource/emsdk EM_CONFIG = /Users/likai/hisun/resource/emsdk/.emscripten EM_CACHE = /Users/likai/hisun/resource/emsdk/upstream/emscripten/cache EMSDK_NODE = /Users/likai/hisun/resource/emsdk/node/12.18.1_64bit/bin/node EMSDK_PYTHON = /Users/likai/hisun/resource/emsdk/python/3.7.4-2_64bit/bin/python3
emcc -v 不報錯就成功了
likai@likaideMacBook-Pro:~/resource/emsdk$ emcc -v emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 2.0.3 clang version 12.0.0 (/b/s/w/ir/cache/git/chromium.googlesource.com-external-github.com-llvm-llvm--project a39423084cbbeb59e81002e741190dccf08b5c82) Target: x86_64-apple-darwin19.4.0 Thread model: posix InstalledDir: /Users/likai/hisun/resource/emsdk/upstream/bin shared:INFO: (Emscripten: Running sanity checks)
獲取幫助 emcc --help,內容過多就不展現了。
看下emcc 的版本是2.0.3
likai@likaideMacBook-Pro:~/resource/emsdk$ emcc --version emcc (Emscripten gcc/clang-like replacement) 2.0.3 (43fcfd2938b72c57373a910ece897b27aa298852) Copyright (C) 2014 the Emscripten authors (see AUTHORS.txt) This is free and open source software under the MIT license. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
到這裏WebAssembly的編譯工具已經安裝好了,咱們使用兩個官方樣例,看一下WebAssembly是如何使用的,方便後面的學習。
當使用Emscripten來編譯的時候有不少種不一樣的選擇,咱們介紹其中主要的2種:
編譯到 wasm 而且生成一個用來運行咱們代碼的HTML,將全部 wasm 在web環境下運行所須要的 「膠水」 JavaScript代碼都添加進去。
找個目錄建立hello_world.c文件
#include <stdio.h> int main(int argc, char ** argv) { printf("Hello World\n"); }
使用剛纔已經配置過的終端,找到hello_world.c文件,執行以下命令
emcc ./hello_world.c -s WASM=1 -o ./hello_world.html
emcc 是Emscripten編譯器行命令
hello_world.c 是咱們的輸入文件
-s WASM=1 指定咱們想要的wasm輸出形式。若是咱們不指定這個選項,Emscripten默認將只會生成asm.js。(可參考 emcc --help 參數說明)
likai@likaideMacBook-Pro:~/resource/emsdk/demo$ emcc ./hello_world.c -s WASM=1 -o ./hello_world.html shared:INFO: (Emscripten: Running sanity checks) likai@likaideMacBook-Pro:~/resource/emsdk/demo$ ls hello_world.c hello_world.html hello_world.js hello_world.wasm
執行後會產生三個新文件:
啓動http服務命令,查看運行結果
emrun --no_browser --port 8080 ./hello_world.html
likai@likaideMacBook-Pro:~/resource/emsdk/demo$ emrun --no_browser --port 8080 ./hello_world.html Web server root directory: /Users/likai/hisun/resource/emsdk/demo Now listening at http://0.0.0.0:8080/
能夠看到原來helloworld.c文件中打印的內容如今了瀏覽器中。我很好奇C代碼中的打印結果是怎麼跑到瀏覽器的控制檯上的。看似很簡單的操做實際上Emscripten作了不少事,點開生成膠水代碼hello_world.js看了下,裏面寫了不少代碼2000多行嘞,加載wasm,處理內存分配、內存釋放、垃圾回收、函數調用,封裝了各類方法。編譯後的js文件我放在了gihub中,點擊查看 hello_world.js
簡單分析一下膠水代碼的內容,有助於咱們對WebAssembly的理解,對於後面的使用會頗有幫助。
先一塊兒看下.wasm的真容,上面提到了.wasm是個二進制文件,打不開,想要看裏面內容的話推薦反編譯工具wasm2wast,固然瀏覽器也能夠解析,咱們經過瀏覽器簡單看下。 右鍵打開控制檯-->Sources-->hello_world.wasm
果真這個文件看得不太懂,看到了module,我猜這大概是個模塊,我找到了main函數,不知道是否是hello_world.c的main,咱們仍是看膠水代碼吧。
從膠水代碼hello_world.js中能夠看到,載入了WebAssembly彙編模塊(.wasm),原來這個.wasm被膠水代碼加載了一下,核心部分以下:
function instantiateArrayBuffer(receiver) { return getBinaryPromise().then(function(binary) { return WebAssembly.instantiate(binary, info); }).then(receiver, function(reason) { err('failed to asynchronously prepare wasm: ' + reason); abort(reason); }); } // Prefer streaming instantiation if available. function instantiateAsync() { if (!wasmBinary && typeof WebAssembly.instantiateStreaming === 'function' && !isDataURI(wasmBinaryFile) && // Don't use streaming for file:// delivered objects in a webview, fetch them synchronously. !isFileURI(wasmBinaryFile) && typeof fetch === 'function') { fetch(wasmBinaryFile, { credentials: 'same-origin' }).then(function (response) { var result = WebAssembly.instantiateStreaming(response, info); return result.then(receiveInstantiatedSource, function(reason) { // We expect the most common failure cause to be a bad MIME type for the binary, // in which case falling back to ArrayBuffer instantiation should work. err('wasm streaming compile failed: ' + reason); err('falling back to ArrayBuffer instantiation'); return instantiateArrayBuffer(receiveInstantiatedSource); }); }); } else { return instantiateArrayBuffer(receiveInstantiatedSource); } }
主要作了以下幾件事情:
成功實例化後的返回值交由receiveInstantiatedSource()方法處理。
receiveInstantiatedSource()代碼
function receiveInstance(instance, module) { var exports = instance.exports; Module['asm'] = exports; removeRunDependency('wasm-instantiate'); } ...... function receiveInstantiatedSource(output) { // 'output' is a WebAssemblyInstantiatedSource object which has both the module and instance. // receiveInstance() will swap in the exports (to Module.asm) so they can be called assert(Module === trueModule, 'the Module object should not be replaced during async compilation - perhaps the order of HTML elements is wrong?'); trueModule = null; // TODO: Due to Closure regression https://github.com/google/closure-compiler/issues/3193, the above line no longer optimizes out down to the following line. // When the regression is fixed, can restore the above USE_PTHREADS-enabled path. receiveInstance(output['instance']); }
receiveInstantiatedSource()方法調用了receiveInstance()方法,後者的這條指令:
Module['asm'] = exports;
將wasm模塊實例的導出對象傳給了Module的子對象asm。假若咱們在上述函數中手動添加打印實例導出對象的代碼。
function receiveInstance(instance, module) { ... ... Module['asm'] = exports; console.log(Module['asm']); //print instance.exports ... ...
因而可知,上述一系列代碼運行後,Module['asm']中保存了WebAssembly實例的導出對象——而導出函數恰是WebAssembly實例供外部調用最主要的入口。
看看我理解的對不,wasm的編譯器把C代碼編譯了.wasm文件,這個文件是個彙編代碼,裏面有C代碼的內容,膠水代碼去加載.wasm文件,經過WebAssembly實例對外提供了C代碼裏面的方法,而後使用javascript調用C代碼。最後給人的感受就是瀏覽器上能運行C語言的程序。
咱們再一塊兒細品下官方原話(翻譯過的):
WebAssembly(縮寫 Wasm)是基於堆棧虛擬機的二進制指令格式。Wasm爲了一個可移植的目標而設計的,可用於編譯C/C+/RUST等高級語言,使客戶端和服務器應用程序可以在Web上部署。
這個很好理解,就是在編譯的時候,不生成默認推薦的html,只生成wasm,而後直接調用wasm便可。這就要咱們本身寫膠水代碼,下面看個簡單的例子。步驟以下:
char* toChar (char* str) { return str; } int add (int x, int y) { return x + y; } int square (int x) { return x * x; }
編譯成.wasm文件
emcc ./test.c -Os -s WASM=1 -s SIDE_MODULE=1 -o ./test.wasm
這個命令好像和上面不同,解釋下:
function loadWebAssembly (path, imports = {}) { return fetch(path) // 加載文件 .then(response => response.arrayBuffer()) // 轉成 ArrayBuffer .then(buffer => WebAssembly.compile(buffer)) .then(module => { imports.env = imports.env || {} // 開闢內存空間 imports.env.memoryBase = imports.env.memoryBase || 0 if (!imports.env.memory) { imports.env.memory = new WebAssembly.Memory({ initial: 256 }) } // 建立變量映射表 imports.env.tableBase = imports.env.tableBase || 0 if (!imports.env.table) { // 在 MVP 版本中 element 只能是 "anyfunc" imports.env.table = new WebAssembly.Table({ initial: 0, element: 'anyfunc' }) } // 建立 WebAssembly 實例 return new WebAssembly.Instance(module, imports) }) } // 加載wasm文件 loadWebAssembly('test.wasm') .then(instance => { //調用c裏面的方法 const toChar = instance.exports.toChar const add = instance.exports.add const square = instance.exports.square console.log('return: ', toChar("12352324")) console.log('10 + 20 =', add(10, 20)) console.log('3*3 =', square(3)) console.log('(2 + 5)*2 =', square(add(2 + 5))) })
有了第一個案例的理解,就大概知道這個意思了,建立了一個WebAssembly的實例,返回WebAssembly導出對象,調用了test.c裏面的函數。這裏面有一些膠水代碼語法相關的知識。[MDN Web docs-WebAssembly](./https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly)
能夠看到優化後的wasm文件,只有這幾個函數了,而且能夠看出包含導出test.c中的函數。
咱們今天經過兩個簡單的例子講述了WebAssembly的使用,也進一步理解了WebAssembly是什麼,總體的流程是這樣的:
使用Emscripten編譯C語言源代碼,生成.wasm文件和膠水代碼,經過javascript調用膠水代碼或者.wasm,使C語言的程序在瀏覽器中運行。
以上就是這篇文章要分享的所有內容了,下一篇,基於wasm的加密工具。
Netwarps 由國內資深的雲計算和分佈式技術開發團隊組成,該團隊在金融、電力、通訊及互聯網行業有很是豐富的落地經驗。Netwarps 目前在深圳、北京均設立了研發中心,團隊規模30+,其中大部分爲具有十年以上開發經驗的技術人員,分別來自互聯網、金融、雲計算、區塊鏈以及科研機構等專業領域。Netwarps 專一於安全存儲技術產品的研發與應用,主要產品有去中心化文件系統(DFS)、去中心化計算平臺(DCP),致力於提供基於去中心化網絡技術實現的分佈式存儲和分佈式計算平臺,具備高可用、低功耗和低網絡的技術特色,適用於物聯網、工業互聯網等場景。公衆號:Netwarps