本次分享的文章是基于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