wasm
WASM: webassembly
wasm 由两个部分组成: host + module
- module: 编写的 APP
- host: 解释运行 wasm 模块的平台
WASM 的表现形式有两种:
- 文本格式: 文本呈现的 webassembly 格式
- 二进制格式: 转换后的二进制格式
Wasm 是一种面向基于栈的虚拟机二进制指令格式. 它的本质是可移植的, 即在任何机器/架构上都能运行. 可以用来编写传统意义上的前端或后端.
那什么是基于栈的虚拟机(stack-based virtual machine)呢? 后面会讲到.
需要纠正一个认识: wasm 并非针对 web.
Wasm 在沙盒运行, 因此无法在 host 上实现 I/O 及其他需要访问平台功能的能力.
WebAssembly 的结构
Stack Machine
当代计算机可以认为是"Register Machine", 即 CPU 是基于寄存器进行取指令, 翻译指令, 执行指令的. 而 WASM 是基于一种全新的虚拟机, 即基于栈的虚拟机.
栈虚拟机上, 指令被存放在栈上而非寄存器. 举个形象化的例子, 比如两个数相加的操作执行时(1+2
), 此时栈上存放的指令从栈顶到底分别是 2
, +
, 1
.
栈虚拟机并非新东西, 比如 JVM 或 .NET 的 CLR 都是栈虚拟机.
基本数据类型
WASM 的设计非常简单, 底层只有四种基本数据类型(在 WASM 1.0 中), 分别是:
- i32
- i64
- f32
- f64
线性内存
WASM 中没有类似"堆"的东西, 在 WASM 指令中也没有 new 这种东西, 它的内存模型非常简单, 就是一个线性增长的连续内存.
WASM 内存以 64 KB 的 "Page" 作为基本单位, 当需要更多内存时, 会以这个单位分配更多的 Page.
理解线性内存模型是有必要的, 理解它有助于开发者针对 WASM 环境设计更高效的程序, 并理解代码生成和工具的工作原理.
相关工具
- 基础工具 wabt: WebAssembly 组织提供的最基础的工具集, 开发过程中至少会用到如下两个:
wat2wasm
: 将 WebAssemble 文本格式转换为 WebAssembly 二进制格式.wasm-objdump
: 类似 objdump, 打印 wasm 二进制文件的信息.
- 语言或发布平台对应的相关工具: 这些根据开发情况自行选择.
一个手写的 wasm text 格式并转换为 wasm 二进制格式的例子
比如下面两个数相加的函数例子:
-
第一种形式的写法:
(module
(func $add (param $lhs i32) (param $rhs i32) (result i32)
local.get $lhs
local.get $rhs
i32.add)
(export "add" (func $add))
) -
第二种形式的写法(更多括号但更易读): 操作符和操作数顺序和上述不同
(module
(func $add (param $lhs i32) (param $rhs i32) (result i32)
(i32.add
(local.get $lhs)
(local.get $rhs)
)
)
(export "add" (func $add))
)
并非所有 func 都需要 export, 默认情况下只有在对外沟通时才需要暴露, 默认情况下 func 都是 private 的, 即没有被 export.
下面就可以使用 wat2wasm
进行转换了:
wat2wasm add.wat -o add.wasm
wat2wasm add-p.wat -o add-p.wasm
这样, 便可以用 wasm-objdump 查看其中的内容:
wasm-objdump -x add.wasm
wasm-objdump -x add-p.wasm
二者的输出一致:
add-p.wasm: file format wasm 0x1
Section Details:
Type[1]:
- type[0] (i32, i32) -> i32
Function[1]:
- func[0] sig=0 <add>
Export[1]:
- func[0] <add> -> "add"
Code[1]:
- func[0] size=7 <add>
并且, 还可以使用 `` 将 wasm 反编译为 wat:
wasm2wat add-p.wasm -o dis.wat
输出结果如下:
(module
(type (;0;) (func (param i32 i32) (result i32)))
(func (;0;) (type 0) (param i32 i32) (result i32)
local.get 0
local.get 1
i32.add)
(export "add" (func 0)))
可以看到 type 被创建了, 也有一个 func.
汇总上面的信息, 实际在浏览器中只要有原始的代码(比如 rust 代码), 和 binary(.wasm), 就可以导出 WebAssembly 的 Source Map, 特别是主流浏览器都提供这样的支持. 因此实际可以在浏览器中对 rust 编写的代码设置断点调试.