什么是 wasm(webassembly) 技术

什么是 wasm wasi wapm 技术

什么是 wasm

wasm官网: https://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官网: https://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官网文档: https://docs.wasmtime.dev/

这里简单举个栗子 用 Rust 生成字节码。

第一步我们需要安装 Rust 语言环境:

安装 rust 文档

第二步 安装 rust 的 wasm 工具

1
$ cargo install cargo-wasi

第三步 创建 rust hello 项目

1
2
$ cargo new hello-world
$ cd hello-world

修改 src/main.rs 添加导出函数

1
2
3
pub fn main() {
println!("Hello, world! wasm!");
}

调试运行 rust wasi 字节码程序

1
2
3
4
5
6
7
8
9
$ 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 工具, 安装文档:https://docs.wasmtime.dev/cli-install.html

1
2
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 脚本, 写入如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116

(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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108

(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官网: https://wapm.io/

wapm 全称是 webassembly Package management , 是一个线上的 wasm 二进制包管理仓库。作用类似于 node.js 的 npm 或 java 的 maven

我们可以在这个仓库上 发布自己的 wasm 程序,或者下载运行别人发布的 wasm 程序。

git repo