本篇文章不赘述如何解析 wasm 指令, 如何执行 webassembly
在解析 wasm 的过程中, 添加 addgas 指令, 具体由 imported 函数 addgas() 来实现
addgas 指令需要添加在 函数段(以下用block代替)的最一开始.
block , 指的是函数分支, 如下:
//block1
int a = 3;
if (a > 5) {
//block2
a++;
}else{
//block3
a--;
return;
}
以上我们给这个函数分了 3 个block, 运行时 可能会覆盖 block1, block2; 也可能覆盖 block1, block3;
所以 以这样的分支结构, 来划分block, 可以精确的计算代码执行消耗.
难点在于如何对 block 分区. 在尝试了几种代码流程控制的命令后(如 if/else, switch, loop, continue等), 在编译成 wasm 后, 大多会转变成 br_if loop end 等, 后面有发现新的再添加. 对这些指令进行处理.
函数开始时, 向 stack 中放入元素 0. 该元素即代表最外层代码块的 gas 消耗. stack 中第 n 个元素,代表该函数第 n 层代码块. 由于 br_if 没有匹配的 end 指令, 即遇到 下一个 br_if 或者 return 时, 代表该 block 结束, 并即使添加(计算) gas 消耗数量.
- 碰到
(br_if return), pop_stack => top, addgas(top) , push_stack 0 - 碰到
(block, loop), push_stack 0 - 碰到
end , pop_stack - code 读取结束
pop_stack => top, addgas(top) - 其余指令
stack[top] = stack[top] + 1
操作实例如下
(module
(type $type0 (func (param i32)))
(type $type1 (func (param i32) (result i32)))
(import "env" "_Z6addgasi" (func $import0 (param i32)))
(table $table0 0 anyfunc)
(memory $memory0 1)
(export "memory" (memory $memory0))
(export "computer" (func $func1))
(func $func1 (param $var0 i32) (result i32) // 初始化 stack: [0]
i32.const 3
call $import0 [2]
block $label2 [2, 0]
block $label1 [2, 0, 0]
block $label0
>> 添加gas 指令, 改指令为后续添加, 且不计入指令数量
i32.const 3 [2, 0, 0, 0]
call $import0
>>
get_local $var0
i32.const 3
i32.eq [2, 0, 0, 3]
br_if $label0 [2, 0, 0]=> addgas 3 [2, 0, 0, 0]
get_local $var0
i32.const 2
i32.eq [2, 0, 0, 3]
br_if $label1 => addgas [2, 0, 0, 0]
get_local $var0
i32.const 1
i32.ne
br_if $label2
i32.const 1
call $import0
get_local $var0 [2, 0, 0, 3]
return addgas 3 => [2, 0, 0, 0]
end $label0 [2, 0, 0]
i32.const 3
call $import0
get_local $var0 [2, 0, 3]
return addgas 3 => [2, 0, 0]
end $label1 [2, 0]
i32.const 2
call $import0
get_local $var0 [2, 3]
return addgas 3 => [2, 0]
end $label2 [2]
i32.const 0
call $import0
get_local $var0 [5]
addgas 5 => []
)
)
这样 添加的 addgas 指令就是 wasm 内置的操作了, 然后你的指令怎么解析来的,就怎么解析回去. 可以 encode 回 wasm 文件. 给其他 wasm 虚拟机调用. 不用自己重新定义运行时.
至于每个 op 对应的 gas 权重. 可以定义一个 map 表. 在解析 functionCode 的时候根据 op 计算 gas 值.
添加导入函数结构
addgas 这里把它定义为一个 imported 的函数, 实际这个函数是不存在的, 所以我们要构造这个函数.
构造原则如下:
- 由于
types类型会优先解析.type类型指的是函数类型. 即函数的 参数个数类型, 返回值类型.
如addgas定义为void addgas(int), 需要检查types中是否有 参数为整型, 无返回值的函数.
如果没有, 添加该函数类型到 types 末尾 - 添加
import函数, 到importsSection末尾 - 至于为什么要添加到
types和imports末尾, 是为了不影响原先的流程.function对应的typeIndex, call指令对应的importedIndex - 需要修改
typesSection, importsSection, codeSection对应的payload长度