使用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):带一个单字节的立即数
下面例子展示了常量指令、加法指令和饱和截断指令
(module
(func
(f32.const 12.3) (f32.const 45.6) (f32.add)
(i32.trunc_sat_f32_s) (drop)
)
)
红色以及蓝色第一个字节的0x43表示f32.const指令操作码,后面4个字节为值
绿色0x92表示f32.add指令操作码
黄色表示饱和截断指令(操作码0xFC)
变量指令
共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)
)
)
红色表示global.get指令操作码(0x23),值为0
黄色表示global.set指令操作码(0x24),值为1
蓝色表示local.get指令操作码(0x20),值为0
绿色表示local.set指令操作码(0x21),值为1
内存指令
内存指令共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)
)
)
绿色表示i32.load指令操作码(0x28),对齐提示为2,全局变量的索引为100(0x64)
蓝色表示i32.store指令操作码(0x36),对齐提示为2,全局变量的索引为100(0x64)
黄色表示memory.size指令(0x3F),内存索引为0
中间隔着drop指令(0x1A)和一条常量指令(0x41)及其全局索引
红色表示memory.grow指令(0x40),内存索引为0
什么是内存对齐?
实际上是一种牺牲空间换时间的方案
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)
)
)
)
黄色代表block指令(操作码:0x02)0x7F表示i32,0x41表示i32.const操作码跟着值1
红色代表loop指令(操作码:0x03)
蓝色表示if指令(操作码:0x04)
绿色表示else指令(操作码:0x05)
黑色和灰色表示end指令(操作码:0x0B)
0x1A代表drop指令
跳转指令
跳转指令共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
return_instr : 0x0f
例子:
(module
(func
(block (block (block
(br 1)
(br_if 2 (i32.const 100))
(br_table 0 1 2 3)
(return)
)))
)
)
红色表示br指令(操作码0x0C),值为1
黄色表示br_if指令(操作码0x0D),值为2
蓝色表示br_table指令(操作码0x0E)最大值3 后面为值
绿色表示return指令(操作码0x0F)之后为end指令…
函数调用指令
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))
)
)
绿色表示call指令(操作码0x10)函数索引为0
红色表示call_indirect指令(操作码0x11)函数签名为1,第0 个表索引