Skip to main content

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 编写的代码设置断点调试.