WebAssembly原理与核心技术
上QQ阅读APP看书,第一时间看更新

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例子将主要采用折叠形式的指令。