本篇文章不贅述如何解析 wasm 指令, 如何執(zhí)行 webassembly
在解析 wasm 的過程中, 添加 addgas 指令, 具體由 imported 函數(shù) addgas() 來實現(xiàn)
addgas 指令需要添加在 函數(shù)段(以下用block代替)的最一開始.
block , 指的是函數(shù)分支, 如下:
//block1
int a = 3;
if (a > 5) {
//block2
a++;
}else{
//block3
a--;
return;
}
以上我們給這個函數(shù)分了 3 個block, 運行時 可能會覆蓋 block1, block2; 也可能覆蓋 block1, block3;
所以 以這樣的分支結(jié)構(gòu), 來劃分block, 可以精確的計算代碼執(zhí)行消耗.
難點在于如何對 block 分區(qū). 在嘗試了幾種代碼流程控制的命令后(如 if/else, switch, loop, continue等), 在編譯成 wasm 后, 大多會轉(zhuǎn)變成 br_if loop end 等, 后面有發(fā)現(xiàn)新的再添加. 對這些指令進行處理.
函數(shù)開始時, 向 stack 中放入元素 0. 該元素即代表最外層代碼塊的 gas 消耗. stack 中第 n 個元素,代表該函數(shù)第 n 層代碼塊. 由于 br_if 沒有匹配的 end 指令, 即遇到 下一個 br_if 或者 return 時, 代表該 block 結(jié)束, 并即使添加(計算) gas 消耗數(shù)量.
- 碰到
(br_if return), pop_stack => top, addgas(top) , push_stack 0 - 碰到
(block, loop), push_stack 0 - 碰到
end , pop_stack - code 讀取結(jié)束
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 指令, 改指令為后續(xù)添加, 且不計入指令數(shù)量
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 內(nèi)置的操作了, 然后你的指令怎么解析來的,就怎么解析回去. 可以 encode 回 wasm 文件. 給其他 wasm 虛擬機調(diào)用. 不用自己重新定義運行時.
至于每個 op 對應(yīng)的 gas 權(quán)重. 可以定義一個 map 表. 在解析 functionCode 的時候根據(jù) op 計算 gas 值.
添加導(dǎo)入函數(shù)結(jié)構(gòu)
addgas 這里把它定義為一個 imported 的函數(shù), 實際這個函數(shù)是不存在的, 所以我們要構(gòu)造這個函數(shù).
構(gòu)造原則如下:
- 由于
types類型會優(yōu)先解析.type類型指的是函數(shù)類型. 即函數(shù)的 參數(shù)個數(shù)類型, 返回值類型.
如addgas定義為void addgas(int), 需要檢查types中是否有 參數(shù)為整型, 無返回值的函數(shù).
如果沒有, 添加該函數(shù)類型到 types 末尾 - 添加
import函數(shù), 到importsSection末尾 - 至于為什么要添加到
types和imports末尾, 是為了不影響原先的流程.function對應(yīng)的typeIndex, call指令對應(yīng)的importedIndex - 需要修改
typesSection, importsSection, codeSection對應(yīng)的payload長度