使用Wasm文本格式也就是WAT语言编写。

    数值指令
    数值指令中的饱和截断指令比较特殊,饱和指令其实是一组指令,共8条,和普通的浮点数截断指令一一对应。这组指令的格式为前缀操作码(0x0F)+子操作码,每条指令的操作码占两个字节,也可以当作一条指令,因此在全部133条数值指令中,只有4条常量指令和饱和截断指令有立即数。
    1、i32.const(操作码0x41):带一个s32类型的立即数,使用LEB128有符号编码
    2、i64.const(操作码0x42):带一个s64类型的立即数,使用LEB128有符号编码
    3、f32.const(操作码0x43):带一个f32类型的立即数,固定占4个字节
    4、f64.const(操作码0x43):带一个f64类型的立即数,固定占8个字节
    5、trunc_sat(操作码0xFC):带一个单字节的立即数
    下面例子展示了常量指令、加法指令和饱和截断指令

    1. (module
    2. (func
    3. (f32.const 12.3) (f32.const 45.6) (f32.add)
    4. (i32.trunc_sat_f32_s) (drop)
    5. )
    6. )

    image.png
    红色以及蓝色第一个字节的0x43表示f32.const指令操作码,后面4个字节为值
    绿色0x92表示f32.add指令操作码
    黄色表示饱和截断指令(操作码0xFC)
    image.png

    变量指令
    共5条,3条用于读写局部变量,立即数是局部变量索引,2条用户读写全局变量,立即数是全局变量索引。
    local.get : 0x20|local_idx
    local.set : 0x21|local_idx
    local.tee : 0x22|local_idx
    global.get : 0x23|global_idx
    global.set : 0x24|global_idx
    例子:

    (module
      (global $g1 (mut i32) (i32.const 1))
      (global $g2 (mut i32) (i32.const 1))
    
      (func (param $a i32) (param $b i32)
        (global.get $g1) (global.set $g2)
        (local.get $a) (local.set $b)
      )
    )
    

    image.png
    红色表示global.get指令操作码(0x23),值为0
    黄色表示global.set指令操作码(0x24),值为1
    蓝色表示local.get指令操作码(0x20),值为0
    绿色表示local.set指令操作码(0x21),值为1
    image.png

    内存指令
    内存指令共25条,14条是加载指令,用于将内存数据载入操作数栈,9条是存储指令,用于将操作数栈顶数据写回内存,有23条指令统一带有两个立即数,对齐提示和内存偏移量。剩余2条指令用于获取和扩展内存页数,立即数是内存索引。
    load_instr : opcode|align|offset # align:u32,offset:u32
    store_instr : opcode|align|offse
    memory.size : 0xf3 | 0x00
    memory.grow : 0x40 | 0x00
    例子:

    (module
      (memory 1 8)
      (data (offset (i32.const 100)) "hello")
    
      (func
        (i32.const 1) (i32.const 2)
        (i32.load offset=100)
        (i32.store offset=100)
        (memory.size) (drop)
        (i32.const 4) (memory.grow) (drop)
      )
    )
    

    image.png
    绿色表示i32.load指令操作码(0x28),对齐提示为2,全局变量的索引为100(0x64)
    蓝色表示i32.store指令操作码(0x36),对齐提示为2,全局变量的索引为100(0x64)
    黄色表示memory.size指令(0x3F),内存索引为0
    中间隔着drop指令(0x1A)和一条常量指令(0x41)及其全局索引
    红色表示memory.grow指令(0x40),内存索引为0
    image.png

    什么是内存对齐?
    实际上是一种牺牲空间换时间的方案

    type struct A{
      char  b;
      int32 c;
    }
    
    内存地址 0 1 2 3 4 5 6 7
    无对齐 b c c c c - - -
    4字节对齐 b - - - c c c c

    在无对齐下32位 CPU想要取c变量 第一次取4个字节 0 1 2 3 ,只有1 2 3才是c的部分 所以CPU还要取第二次 取到4 5 6 7 只有4才是c变量剩下的部分再把1 2 3 4合并位c变量的值,非常耗费性能

    在对齐下32位 CPU可直接获取到c的值无需其他处理,大大加快性能。

    结构化指令
    结构化指令共13条,包括结构化控制指令、跳转指令、函数调用指令,结构化指令有3条,分别是block(操作码:0x02)、loop(操作码:0x03)、if(操作码:0x04),这3条指令必须和end指令(操作码:0x0B)搭配,并成对出现。如果if指令有两条分支,则中间由else指令(操作码:0x05)分隔,由于end和else指令比较特殊,只起分隔作用,也可以称之为伪指令。
    block_instr : 0x02|block_type|instr|0x0b
    loop_instr : 0x03|block_type|instr
    |0x0b
    if_instr : 0x04|block_type|instr|(0x05|instr)|0x0b
    block_type : s32
    例子:

    (module
      (func (result i32)
        (block (result i32)
          (i32.const 1)
          (loop (result i32)
            (if (result i32) (i32.const 2)
              (then (i32.const 3))
              (else (i32.const 4))
            )
          )
          (drop)
        )
      )
    )
    

    image.png
    黄色代表block指令(操作码:0x02)0x7F表示i32,0x41表示i32.const操作码跟着值1
    红色代表loop指令(操作码:0x03)
    蓝色表示if指令(操作码:0x04)
    绿色表示else指令(操作码:0x05)
    黑色和灰色表示end指令(操作码:0x0B)
    0x1A代表drop指令
    image.png

    跳转指令
    跳转指令共4条,其中br指令(操作码0x0C)进行无条件跳转,立即数是目标标签索引
    br_if指令(操作码0x0D)进行有条件跳转,立即数也是目标标签索引
    br_table指令(操作码0x0E)进行查表跳转,立即数是目标标签索引表和默认标签索引。
    return指令(操作码0x0F)只是br指令的一种特殊形式,执行结果是跳出最外层循环并导致整个函数返回,没有立即数。
    br_instr : 0x0C|label_idx
    br_if_instr : 0x0D|label_idx
    br_table : 0x0E|vec|label_idx
    return_instr : 0x0f
    例子:

    (module
      (func
        (block (block (block
          (br 1)
          (br_if 2 (i32.const 100))
          (br_table 0 1 2 3)
          (return)
        )))
      )
    )
    

    image.png
    红色表示br指令(操作码0x0C),值为1
    黄色表示br_if指令(操作码0x0D),值为2
    蓝色表示br_table指令(操作码0x0E)最大值3 后面为值
    绿色表示return指令(操作码0x0F)之后为end指令…
    image.png

    函数调用指令
    Wasm有直接和间接两种函数调用方法。
    call指令(操作码0x10)进行直接函数调用,函数索引由立即数指定。
    call_indirect指令(操作码0x11)进行间接函数调用,函数签名索引由立即数指定,到运行时才能知道具体调用哪个函数。
    call_instr : 0x10|func_idx
    call_indirect : 0x11|type_idx|0x00
    间接函数调用指令需要查表才能完成,由第2个立即数指定差哪张表,现在立即数只起占位作用必须是0
    例子:

    (module
      (type $ft1 (func))
      (type $ft2 (func))
      (table funcref (elem $f1 $f1 $f1))
      (func $f1
        (call $f1)
        (call_indirect (type $ft2) (i32.const 2))
      )
    )
    

    image.png
    绿色表示call指令(操作码0x10)函数索引为0
    红色表示call_indirect指令(操作码0x11)函数签名为1,第0 个表索引
    image.png