WebAssembly 与 Rust 编程系列03 手撕 WebAssembly 文本格式
WebAssembly 文本格式存在的意义
WebAssembly 文本格式是wasm二进制格式与开发人员调试,测试的中间形式;
WebAssembly 的目标中,可读、可调试是非常重要的一点,而WebAssembly 文本格式是实现这一目标的重要方式;
虽然我们在开发过程中,并不是需要直接面对这些中间形式的文本格式,但是如果你拒绝黑盒,或者想优化wasm模块以及构建自己的WebAssembly
编译器,那么花点时间掌握这些文本格式是非常值得的
安装调式工具
把.wat文本文件转换为.wasm二进制文件 需要专门的工具进行编译转换
我们可以使用wabt
工具,该工具包括了在WebAssembly文本表示和wasm之间进行相互转化的编译器以及其他一些功能
wabt-github
在这个项目中可以选择release
中预编译好的,也可以进行本地源码编译
本地源码编译
源码编译需要你本地已经安装好了 Cmake
以及 ``
S-表达式
不论是二进制还是文本格式,WebAssembly代码中的基本单元是一个模块。在文本格式中,一个模块被表示为一个大的S-表达式。
S-表达式是一个非常古老和非常简单的用来表示树的文本格式。因此,我们可以把一个模块想象为一棵由描述了模块结构和代码的节点组成的树。不过,与一门编程语言的抽象语法树不同的是,WebAssembly的树是相当平的,也就是大部分包含了指令列表。
首先,让我们看下S-表达式长什么样。树上的每个一个节点都有一对括号——( … )——包围。括号内的第一个标签告诉你该节点的类型,其后跟随的是由空格分隔的属性或孩子节点列表。
S-表达式如下:
(module (memory 1) (func))
这条表达式,表示一棵根节点为“模块(module)”的树,该树有两个孩子节点,分别是 属性为1的“内存(memory)”节点 和 一个“函数(func)”节点。我们一会儿就会看到这些节点的含义。
最简单的模块
(module)
添加函数功能
(func <signature> <locals> <body>)
签名和参数
签名是由一系列参数类型声明,及其后面的返回值类型声明列表组成。值得注意的是:
- 没有(result)意味着函数不返回任何东西。
- 在当前版本中,最多拥有一个返回类型
因此,接受两个32位整数,返回一个64位浮点数的函数应该这样写:
(func (param i32) (param i32) (result f64) ... )
添加参数名
(func (param $p1 i32) (param $p2 f32) (local $loc i32) …)
获取和设置局部变量和参数
局部变量和参数能够被函数体使用get_local和set_local指令进行读写。
调用和导出函数
wasm函数必须通过模块里面的export语句显式地导出。
像局部变量一样,函数默认也是通过索引来区分的,但是为了方便,可以给它们起个名字。
(func $add … )
我们需要增加一个导出声明:
(export “add” (func
a
d
d
)
)
这
里
的
a
d
d
是
J
a
v
a
S
c
r
i
p
t
中
用
来
区
别
这
个
函
数
的
名
字
,
而
add)) 这里的add是JavaScript中用来区别这个函数的名字,而
add))这里的add是JavaScript中用来区别这个函数的名字,而add则是指出模块中的哪个WebAssembly函数将会被导出:
所以,我们最终的模块(当前)看起来像下面这样:
(module
(func $add (param $lhs i32) (param $rhs i32) (result i32)
get_local $lhs
get_local $rhs
i32.add)
(export "add" (func $add))
)
调用同一模块里的函数
为函数给定一个索引或名字,call指令可以调用它。
(module
(func $getAnswer (result i32)
i32.const 42)
(func (export "getAnswerPlus1") (result i32)
call $getAnswer
i32.const 1
i32.add))