wasm 添加 gas 计算
本篇文章不赘述如何解析 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
长度