[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的方式了。