什么是 wasm wasi wapm 技术
什么是 wasm
wasm官网: webassembly.org/
wasm 全称 WebAssembly,是一种通用字节码的技术,通过该技术将其他语言(比如 go, rust, c/c++, 等)的程序代码编译成可以直接在浏览器环境执行的字节码程序。
因为该 WebAssembly 是一种字通用的节码技术规范,所以 WebAssembly 不仅仅可以在浏览器执行,还可以在其他变成语言环境中执行,比如 ,python, c,go, rust,等.
为什么需要搞一个 wasm 字节码,不能所以平台都用 js 做为 通用编码技术吗?
理论上 在其他语言环境中实现 js 执行引擎, 是可以把 js 做为一个 通用编码技术的。
那为什么还需做一个 wasm 出来呢。 我认为有以下几点原因:
- js 代码是文本文件格式。在 web 环境下,网络传输效率远远不如 wasm 二进制数据传输。
- js 相对于 c 和 rust ,而已,是一种弱类型语言,写代码没有严格类型校验,容易写成不稳定的代码。
- js 是有 GC(自动垃圾清理功能),用户不用关注内存的处理,导致的结果就是 GC 处理过程严重拖慢性能, 而且由于是系统 GC, 用户很难精确控制内存。容易导致内存占用过高。
- js 是文本解析型编程语言,文本解析效率低,wasm 二进制代码解析执行效率会高于 js。
- js 由于历史原因,js 本身就存在一些缺陷(js 之父说的),而且 js 引擎需要实现的成本过高, wasm 的执行引擎可以说是 比 js 新的产物, 在设计 wasm 字节码的时候,考虑各个方便比较完善,漏洞也比较少。
什么是 wasi
wasi官网: wasi.dev/
wasi 全称 The WebAssembly System Interface 通俗来讲就是一个对接规范。
就像前后端分离的 RestFul 类似,只要前后端约定一套 RESTful 接口对接规范 ,无论前端使用的框架是 jQuery 或 vue 或 react , 无论后端使用 java node.js 或者 java 还是 go。 都可以进行前后端通信。
wasi 也是同样的道理, wasi 约定了也行函数接口,不同的语言代码的生成的字节码规则是一致的,这样就可以在其他平台执行这个字节码。
举个栗子:
比如我用 c语言 写了一个程序,读取某个文件, 把文件内容输出到屏幕。
然后我把这个程序编译成 wasm 字节码。 然后让 web 浏览器去执行。
我们都知道 浏览器是不可以直接读取系统文件的。那么 web 浏览器应该如何去执行这个字节码程序呢?
通过 wasi (后文会介绍) 协议,我们可以在 浏览器环境中用 JavaScript 写 读取文件的 API 回调函数,然后在执行字节码程序之前,把这个 options 传给 wasm 执行引擎。 js 中可以实现 文件读取逻辑(可能是从网络中读取,也可以返回一段字符串)。
然后 web 浏览器 就可以无障碍的执行这段 字节码程序了。
wasm的好处:
- 可以移植其他平台的代码给不同平台执行(跨平台跨语言执行能力)
- 可以用到其他语言的特性,给浏览器执行环境赋能,比如通过 rust 生成的 wasm,有更高的执行效率和内存安全特性。
- 通过 wasi 可以在浏览器端实现模拟 文件系统, sock 网络服务等, 实现类似 webide
如何生成 wasm 字节码程序
因为 wasm 是一种通用的字节码交互技术,大部分流行的语言的环境的 sdk 工具包中就内置了 wasm 相关的工具。 比如 go 语言中可以通 GOOS=js GOARCH=wasm go build -o static/main.wasm
生成 wasm 字节码程序。
另外还有其他第三方或者 wasm官方的工具。目前比较通用的一个工具是:wasmtime
wasmtime官网文档: docs.wasmtime.dev/
这里简单举个栗子 用 Rust 生成字节码。
第一步我们需要安装 Rust 语言环境:
第二步 安装 rust 的 wasm 工具
$ cargo install cargo-wasi
第三步 创建 rust hello 项目
$ cargo new hello-world
$ cd hello-world
修改 src/main.rs 添加导出函数
pub fn main() {
println!("Hello, world! wasm!");
}
调试运行 rust wasi 字节码程序
$ cargo wasi run
info: downloading component 'rust-std' for 'wasm32-wasi'
info: installing component 'rust-std' for 'wasm32-wasi'
Compiling hello-world v0.1.0 (/hello-world)
Finished dev [unoptimized + debuginfo] target(s) in 0.16s
Running `/.cargo/bin/cargo-wasi target/wasm32-wasi/debug/hello-world.wasm`
Running `target/wasm32-wasi/debug/hello-world.wasm`
Hello, world!
通过 wasmtime 工具 运行 wasi 字节码程序。
需要安装 wasmtime 工具, 安装文档:docs.wasmtime.dev/cli-install…
wasmtime target/wasm32-wasi/debug/hello-world.wasm
Hello, world!
到此为止我们就实现生成了 wasm 字节码程序了
为什么我更推荐使用 rust 字节码程序。
因为 rust 是一种强类型,性能比较优秀的的 编程语言。 和 c 语言一样没有 gc,但是 rust 在内存操作方面,rust 编译器有优秀的 语法检查机制,所以可以让用户写出优秀的内存安全代码。
而且 rust 和 c 语言相比, 由于 rust 是后辈, 所以 rust 有很多新的现代语言特性, 可以通过简单的代码语法糖,实现 c语言的需要很多代码实现的功能。
如何在 nodejs 环境执行 wasm 代码
nodejs 16 内置了 WebAssembly API
在 刚刚的 rust 项目下, 创建 一个 test.js 脚本, 写入如下内容:
(async () => {
try{
const fs = require('fs');
const path = require('path');
// 定义 wasm 环境变量
const env = {
PWD: '/'
};
// wasm 内存操作对象
let thisView = null;
// wasm 程序接口对象
let wasmRes = null;
// wasm 内存对象
let thisMemory = null;
// 控制台输出信息缓存变量
let consoleLogData = '';
const refreshMemory = () => {
if (!thisView || thisView.buffer.byteLength === 0) {
thisView = new DataView(thisMemory.buffer);
}
}
const getiovs = (iovs, iovsLen) => {
refreshMemory();
const buffers = Array.from({ length: iovsLen }, (_, i) => {
const ptr = iovs + i * 8;
const buf = thisView.getUint32(ptr, true);
const bufLen = thisView.getUint32(ptr + 4, true);
return new Uint8Array(thisMemory.buffer, buf, bufLen);
});
return buffers;
};
function Uint8ArrayToString(fileData){
var dataString = "";
for (var i = 0; i < fileData.length; i++) {
dataString += String.fromCharCode(fileData[i]);
}
return dataString
}
const importObject = {
wasi_snapshot_preview1: {
fd_write: (fd, iovs, iovsLen, nwritten) => {
let written = 0;
const buffers = getiovs(iovs, iovsLen);
buffers.forEach(buffer => {
consoleLogData+= Uint8ArrayToString(buffer);
written+=buffer.length;
});
thisView.setUint32(nwritten, written, true);
return 0;
},
environ_get: (environ, environBuf) => {
refreshMemory();
let coffset = environ;
let offset = environBuf;
Object.entries(env).forEach(([key, value]) => {
thisView.setUint32(coffset, offset, true);
coffset += 4;
offset += Buffer.from(thisMemory.buffer).write(
`${key}=${value}\0`,
offset
);
});
return 0;
},
environ_sizes_get: (environCount, environBufSize) => {
refreshMemory();
const envProcessed = Object.entries(env).map(
([key, value]) => `${key}=${value}\0`
);
const size = envProcessed.reduce(
(acc, e) => acc + Buffer.byteLength(e),
0
);
thisView.setUint32(environCount, envProcessed.length, true);
thisView.setUint32(environBufSize, size, true);
return 0;
},
proc_exit: (rval) => {
return 0;
},
}
};
const wasmFileBuffer = fs.readFileSync(path.join(__dirname, 'target/wasm32-wasi/debug/code-stock.wasm'));
wasmRes = await WebAssembly.instantiate(wasmFileBuffer, importObject);
thisMemory = wasmRes.instance.exports.memory;
// wasmRes.instance.exports._start && wasmRes.instance.exports._start();
wasmRes.instance.exports.main();
console.log({
consoleLogData,
});
} catch(e) {
console.error(e);
}
})();
然后直接通过 node test.js
既可执行 wasm 代码
如何在浏览器环境执行 wasm 程序
目前新版本的现代浏览器基本上都有 WebAssembly API
(async () => {
try{
const env = {
PWD: '/'
};
let thisView = null;
let wasmRes = null;
let thisMemory = null;
let consoleLogData = '';
const refreshMemory = () => {
if (!thisView || thisView.buffer.byteLength === 0) {
thisView = new DataView(thisMemory.buffer);
}
}
const getiovs = (iovs, iovsLen) => {
refreshMemory();
const buffers = Array.from({ length: iovsLen }, (_, i) => {
const ptr = iovs + i * 8;
const buf = thisView.getUint32(ptr, true);
const bufLen = thisView.getUint32(ptr + 4, true);
return new Uint8Array(thisMemory.buffer, buf, bufLen);
});
return buffers;
};
function Uint8ArrayToString(fileData){
var dataString = "";
for (var i = 0; i < fileData.length; i++) {
dataString += String.fromCharCode(fileData[i]);
}
return dataString
}
const importObject = {
wasi_snapshot_preview1: {
fd_write: (fd, iovs, iovsLen, nwritten) => {
let written = 0;
const buffers = getiovs(iovs, iovsLen);
buffers.forEach(buffer => {
consoleLogData+= Uint8ArrayToString(buffer);
written+=buffer.length;
});
thisView.setUint32(nwritten, written, true);
return 0;
},
environ_get: (environ, environBuf) => {
refreshMemory();
let coffset = environ;
let offset = environBuf;
Object.entries(env).forEach(([key, value]) => {
thisView.setUint32(coffset, offset, true);
coffset += 4;
offset += Buffer.from(thisMemory.buffer).write(
`${key}=${value}\0`,
offset
);
});
return 0;
},
environ_sizes_get: (environCount, environBufSize) => {
refreshMemory();
const envProcessed = Object.entries(env).map(
([key, value]) => `${key}=${value}\0`
);
const size = envProcessed.reduce(
(acc, e) => acc + Buffer.byteLength(e),
0
);
thisView.setUint32(environCount, envProcessed.length, true);
thisView.setUint32(environBufSize, size, true);
return 0;
},
proc_exit: (rval) => {
return 0;
},
}
};
const wasmFileBuffer = fetch('target/wasm32-wasi/debug/code-stock.wasm');
wasmRes = await WebAssembly.instantiate(wasmFileBuffer, importObject);
thisMemory = wasmRes.instance.exports.memory;
// wasmRes.instance.exports._start && wasmRes.instance.exports._start();
wasmRes.instance.exports.main();
console.log({
consoleLogData,
});
} catch(e) {
console.error(e);
}
})();
什么是 wapm
wamp官网: wapm.io/
wapm 全称是 webassembly Package management , 是一个线上的 wasm 二进制包管理仓库。作用类似于 node.js 的 npm 或 java 的 maven
我们可以在这个仓库上 发布自己的 wasm 程序,或者下载运行别人发布的 wasm 程序。
git repo
- rust 生成 wams: github.com/yunqiangwu/…
- node.js 调用 wams: github.com/yunqiangwu/…
- 浏览器环境执行 wams: github.com/yunqiangwu/…