wasm二进制格式简析

二进制格式

参考:《WebAssembly原理与核心技术》

总体示意图

wasm的二进制格式也是以魔数和版本号开头,
之后是模块的主体内容,主要分为12种段,
并给每种段分配了ID(0-11),除自定义段外,
其他段只能出现一次,且必须按照段ID递增顺序出现;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main
import "fmt"

const PI float32 = 3.14

type type0 = func(a, b int32) int32
type type1 = func()
type type2 = func(ptr, len int32)

func Add(a, b int32) int32 { return a + b }
func Sub(a, b int32) int32 { return a - b }
func Mul(a, b int32) int32 { return a * b }
func Div(a, b int32) int32 { return a / b }

func main() {
fmt.Println("Hello, World!")
}
  1. 类型段(ID=1)

    该段列出wasm模块用到的所有函数类型(函数签名)
    上面代码共有三种函数类型
    前两个是(int32,int32)->(int32)和()->()

  2. 导入段和导出段(ID=2|ID=7)

    该段分别列出模块所有的导入项和导出项,
    上面代码使用了fmt.Println(),所以导入段只有一个项目
    有一个全局变量和四个函数被导出,所有导出段有五个项目

  3. 函数段和代码段(ID=3|ID=10)

    函数信息被分别存储在两个段中,函数段实际是一个索引表,
    列出内部函数所对应的函数签名索引;
    代码段存储内部函数的局部变量和字节码;
    函数段和代码段中的项目数量必须一致且一一对应;

  4. 表段和元素段(ID=4|ID=9)

    表段列出模块内定义的所有表,元素段列出表初始化数据.
    wasm规范规定模块最多只能导入或定义一张表,
    所以即使模块有表段,里面也只能有一个项目

  5. 内存段和数据段(ID=5|ID=11)

    内存段列出模块内定义的所有内存,数据段列出内存初始化数据;
    wasm规范规定模块最多只能导入或定义一块内存,
    所以即使模块内有内存段,里面也只能有一个项目

  6. 全局段(ID=6)

    该段列出模块内定义的所有全局变量信息,包括值类型、可变性和初始值

  7. 起始段(ID=8)

    该段给出模块的起始函数索引,起始段只能有一个项目;
    起始函数主要有两个作用,一个是模块加载后进行一些初始化操作,
    另一个是吧模块变成可执行程序

  8. 自定义段(ID=0)

    该段是给编译器等工具使用的,里面可以存放函数名等调试信息,
    或者其他任何附加信息.自定义段不参与wasm语义,所以完全忽略也不影响模块的执行

所有段必须按照ID递增顺序出现,因为很多段之间存在依赖关系,
比如导入段、函数段、代码段等都需要知道函数类型,
所以类型段要在这三个段之前出现.

wasm二进制格式的设计原则之一是可以一遍完成模块的解析、验证和编译(JIT\AOT),
换句话说,wasm实现可以在下载模块的同时进行解码、验证和编译,可以流式处理

索引空间

函数签名、函数、表、内存、全局变量在模块内有各自的索引空间,
局部变量和跳转标签在函数内有各自的索引空间.

  1. 类型索引

    不管是外部导入的函数还是内部定义的函数,
    其签名全都存储在类型段中,因此类型段的有效索引范围就是类型索引空间.

  2. 函数索引

    函数索引空间由外部函数和内部函数共同构成

  3. 全局变量索引

    由外部全局变量和内部全局变量共同构成.

  4. 表和内存索引

    跟函数索引空间类似,不过只能有一个表和内存,所以有效索引只能为0.

  5. 局部变量索引

    函数的局部变量索引空间由函数的参数和局部变量构成.

  6. 跳转标签索引

    每个函数有自己的跳转索引空间.

实体类型

wasm规范共定义了8种实体类型

  1. 值类型

    4种基本类型: 32位整数(简称i32)、64位整数(简称i64)、32位浮点数(简称f32)和64位浮点数(简称f64)

  2. 函数类型

    就是函数的签名或原型,描述函数的参数数量和类型,以及返回值数量和类型

  3. 限制类型

    描述表的元素数量或者内存页数的上下限.

  4. 内存类型

    描述内存的页数限制,可以定义成限制类型的别名

  5. 表类型

    描述表的元素类型以及元素数量的限制.

  6. 全局变量类型

    描述全局变量的类型和可变性.

  7. 结果类型

    表示函数或表达式的执行结果,在函数类型里已经表示了

  8. 外部类型

    是函数类型、表类型、内存类型和全局变量类型的集合

参考