Edit me

[TOC]

wagon对参数的处理流程

当前代码中在执行时需要传入uint64的参数:

func (vm *VM) ExecCode(fnIndex int64, args ...uint64) (rtrn interface{}, err error)

而我们实际调用的函数的参数肯定是多种类型的,可能是指针,可能是字符串,所以这个传参方式局限性比较大,而之所以是这种传参方式,主要是代码在经过wasm的编译后,会把字符串的参数转换为int型的,如下面代码所示:

int create( char * name, int max){
  AbaLogString(name);
  AbaLogInt(max);
  return 0;
}

在编译成wast文件后,指针所示的字符串被转换成了int型:

(module
    (type $FUNCSIG$i (func (result i32)))
    (type $FUNCSIG$ii (func (param i32) (result i32)))
    (import "env" "AbaLogInt" (func $AbaLogInt (param i32) (result i32)))
    (import "env" "AbaLogString" (func $AbaLogString (param i32) (result i32)))
    (table 0 anyfunc)
    (memory $0 1)
    (export "memory" (memory $0))
    (export "create" (func $create))
    (func $create (; 2 ;) (param $0 i32) (param $1 i32) (result i32)
        (drop
        (call $AbaLogString
            (get_local $0)
        )
        )
        (drop
            (call $AbaLogInt
                (get_local $1)
            )
        )
        (i32.const 0)
    )
)

create函数有两个参数,都是i32,在打印字符串时,也是从local0获取获取参数,代码中是将参数存在了ctx中:

for i, arg := range args {
   vm.ctx.locals[i] = arg
}

当执行到get_local指令时,虚拟机会将参数放到堆栈:

func (vm *VM) getLocal() {
   index := vm.fetchUint32()
   vm.pushUint64(vm.ctx.locals[int(index)])
}

再调用call时,从堆栈取出参数,也就是call函数:

func (fn goFunction) call(vm *VM, index int64) {
   numIn := fn.typ.NumIn()
   args := make([]reflect.Value, numIn)

   for i := numIn - 1; i >= 0; i-- {
      val := reflect.New(fn.typ.In(i)).Elem()
      raw := vm.popUint64()
      kind := fn.typ.In(i).Kind()
      switch kind {
      case reflect.Float64, reflect.Float32:
         val.SetFloat(math.Float64frombits(raw))
      case reflect.Uint32, reflect.Uint64:
         val.SetUint(raw)
      case reflect.Int32, reflect.Int64:
         val.SetInt(int64(raw))
      case reflect.String:
         data := vm.memory[raw:]
         index := bytes.IndexByte(data, 0)
         para := data[:index]
         val.SetString(string(para))
      default:
         panic(fmt.Sprintf("exec: args %d invalid kind=%v", i, kind))
      }

      args[i] = val
   }
   rtrns := fn.val.Call(args)

根据参数类型,对参数进行处理,如果是内部的字符串变量,是存放到memory中的,但是外部的字符串变量没有地方存储,这个是需要解决的问题。 本体中的处理方法是直接对虚拟机的memory进行管理和改造,从而让外部参数变成内部参数,这样就可以通过i32类型的参数拿到整个字符串。

通过指针传递

我们可以通过指针来传递外部参数,将外部参数统一为string类型,然后获取它的指针,转换为uint64类型传递到虚拟机,在API中将这个指针指向的字符串再拿出来,这样可以解决外部参数导入问题: string和int的转换:

func StringToPointer(str string) (uint64, error) {
   strPointerInt := fmt.Sprintf("%d", unsafe.Pointer(&str))
   return strconv.ParseUint(strPointerInt, 10, 0)
}

func PointerToString(pointer uint64) string {
   var s *string
   s = *(**string)(unsafe.Pointer(&pointer))
   str := *(*string)(unsafe.Pointer(s))
   b := CopyBytes([]byte(str))
   return string(b)
}

比如,我们要实现一个查询Token是否已经存在的API:

func (ws *WasmService) TokenIsExisted(tokenP uint64) int32 {
   token := common.PointerToString(tokenP)
   ret := ws.ledger.TokenIsExisted(token)
   if ret {
      return 1
   } else {
      return 0
   }
}

传入的是一个指针,然后转换成字符串。

未解决的问题

这种方式和ONT的相比更简单,不需要操作内部memory,但是也有个致命问题,就是外部参数会和内部参数处于不统一的状态,比如下面合约代码:

int test(char *string){
  func(string);
  func("string");
}

在编译完成后对两个参数的处理是不同的:

(module
 (type $FUNCSIG$i (func (result i32)))
 (type $FUNCSIG$ii (func (param i32) (result i32)))
 (import "env" "func" (func $func (param i32) (result i32)))
 (table 0 anyfunc)
 (memory $0 1)
 (data (i32.const 16) "string\00")
 (export "memory" (memory $0))
 (export "test" (func $test))
 (func $test (; 1 ;) (param $0 i32) (result i32)
  (drop
   (call $func
    (get_local $0)
   )
  )
  (drop
   (call $func
    (i32.const 16)
   )
  )
  (get_local $0)
 )
)

一种是通过外部传入指针,然后转换,一种是通过从虚拟机内部memory的偏移获取到参数,两者处理逻辑不同,这就会导致func在执行时出错,因此需要一种统一的方式来处理参数,或许还是需要借鉴ONT的方式了。

Tags: