4.2 指令
在Wasm文本格式里,指令有两种写法:普通形式和折叠形式。指令的普通形式和其二进制编码格式基本一致,非常容易理解;折叠形式则完全是语法糖,更方便人类编写,但是WAT编译器会把它们全部展开。
4.2.1 普通形式
指令的普通形式写法非常直接,对于大部分指令来说,就是操作码后跟立即数。下面的例子展示了除控制指令外其他指令的一般写法。
(module (memory 1 2) (global $g1 (mut i32) (i32.const 0)) (func $f1) (func $f2 (param $a i32) i32.const 123 i32.load offset=100 align=4 i32.const 456 i32.store offset=200 global.get $g1 local.get $a i32.add call $f1 drop ) )
可以看到,大部分指令的立即数(如果有的话)都是不能省略的,必须以数值或者名字的形式跟在操作码后面。内存读写系列指令是个例外,offset和align这两个立即数都是可选的,而且需要显式指定(名称和数值用等号分开)。
结构化控制指令(block、loop和if)可以指定可选的参数和结果类型,必须以end结尾。if指令还可以用else分割成两条分支。下面的例子展示了block、loop、if、br和br_if等控制指令的一般写法。
(module (func $foo block $l1 (result i32) i32.const 123 br $l1 loop $l2 i32.const 123 br_if $l2 end end drop ) (func $max (param $a i32) (param $b i32) (result i32) local.get $a local.get $b i32.gt_s if (result i32) local.get $a else local.get $b end ) )
和第3章一样,这一章的WAT例子也只是为了展示Wasm文本格式,并没有实际含义。本章暂不讨论br_table指令,其写法和br指令差不多,第8章详细介绍这条指令时会给出具体的例子。
4.2.2 折叠形式
指令的普通形式较难理解写起来略为烦琐,使用折叠形式可以缓解这个情况。我们可以对普通指令做三步调整,把它变为折叠形式:第一步,用圆括号把指令包起来;第二步,如果是结构化控制指令,把end去掉。if指令要稍微麻烦一些,具体请看下面的例子;第三步(这一步是可选的),如果某条指令(无论是普通还是折叠形式)和它前面的几条指令从逻辑上可以看成一组操作,则把前几条指令折叠进该指令。比如local.get $a、local.get $b、i32.add这3条指令,逻辑上是一组操作,也就是进行加法计算。那么可以把这3条指令折叠起来,写成(i32.add (local.get $a) (local.get $b))。
折叠指令实际上是把指令从扁平结构变成了树形结构,WAT编译器会按照后续遍历(从左到右访问子树,最后访问树根)的方式展开折叠指令。我们按照上面的3个步骤改写前面的例子,改写后的代码应该是下面这样(注意if指令)。
(module (func $foo (block $l1 (result i32) (i32.const 123) (br $l1) (loop $l2 (br_if $l2 (i32.const 123)) ) ) (drop) ) (func $max (param $a i32) (param $b i32) (result i32) (if (result i32) (i32.gt_s (local.get $a) (local.get $b)) (then (local.get $a)) (else (local.get $b)) ) ) )
可以看到,经过改写后,代码的可读性的确提高了不少。为了能更好地理解指令的折叠形式,我们展开一层max()函数的if指令,把i32.gt_s指令提出来,改写成下面的等价形式。
(module (func $max (param $a i32) (param $b i32) (result i32) (i32.gt_s (local.get $a) (local.get $b)) (if $l (result i32) (then (local.get $a)) (else (local.get $b)) ) ) )
我们再继续展开i32.gt_s指令,把local.get指令提出来,改写成下面的等价形式。
(module (func $max (param $a i32) (param $b i32) (result i32) (local.get $a) (local.get $b) (i32.gt_s) (if $l (result i32) (then (local.get $a)) (else (local.get $b)) ) ) )
到这里,指令的两种写法都介绍完毕了。对于本书的大部分WAT例子来说,使用折叠形式的指令更好看也更节约空间。另外,指令的折叠形式和WAT其他结构的写法也更加一致。基于这两点原因,本书中出现的WAT例子将主要采用折叠形式的指令。