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 的函数, 实际这个函数是不存在的, 所以我们要构造这个函数.
构造原则如下:

  1. 由于 types 类型会优先解析. type 类型指的是函数类型. 即函数的 参数个数类型, 返回值类型.
    如 addgas 定义为 void addgas(int), 需要检查 types 中是否有 参数为整型, 无返回值的函数.
    如果没有, 添加该函数类型到 types 末尾
  2. 添加 import 函数, 到 importsSection 末尾
  3. 至于为什么要添加到 types 和 imports 末尾, 是为了不影响原先的流程. function 对应的 typeIndex, call 指令对应的 importedIndex
  4. 需要修改 typesSection, importsSection, codeSection 对应的 payload 长度
最后编辑于 :
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容