diff --git a/bench/calls/.gitignore b/bench/calls/.gitignore new file mode 100644 index 0000000..66fd13c --- /dev/null +++ b/bench/calls/.gitignore @@ -0,0 +1,15 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ diff --git a/bench/calls/README.md b/bench/calls/README.md new file mode 100644 index 0000000..ecaa611 --- /dev/null +++ b/bench/calls/README.md @@ -0,0 +1,40 @@ +Benchmarking calls to/from VM + +# Run bencmarks + +- go test -run none -benchmem -bench Benchmark_callSumAndAdd1 + +# Profile memory + +- go test -run none -benchmem -memprofile=mem.out -bench Benchmark_callSumAndAdd1 +- go tool pprof -http=:8088 mem.out + +# Profile CPU + +- go test -run none -benchmem -cpuprofile=cpu.out -bench Benchmark_callSumAndAdd1 +- go tool pprof -http=:8088 cpu.out + +# Links + +- https://github.com/perlin-network/life/blob/69f41b0484c346e56a57921aef51f7aa4947f5b2/exec/vm_codegen.go +- GAS https://github.com/perlin-network/life/issues/86 + +# Misc + +goos: linux +goarch: amd64 +pkg: github.com/perlin-network/life/bench/calls +Benchmark_callSumAndAdd1_0_NoAOT-2 2394116 504 ns/op 0 B/op 0 allocs/op +Benchmark_callSumAndAdd1_1_NoAOT-2 974137 1245 ns/op 0 B/op 0 allocs/op +Benchmark_callSumAndAdd1_10_NoAOT-2 142293 8406 ns/op 0 B/op 0 allocs/op +Benchmark_callSumAndAdd1_0_AOT-2 1000000 1192 ns/op 24 B/op 2 allocs/op +Benchmark_callSumAndAdd1_1_AOT-2 606807 2096 ns/op 24 B/op 2 allocs/op +Benchmark_callSumAndAdd1_10_AOT-2 129069 9286 ns/op 24 B/op 2 allocs/op + +Benchmark_life_callSumAndAdd1_0_NoAOT-2 1726413 696 ns/op 56 B/op 2 allocs/op +Benchmark_life_callSumAndAdd1_1_NoAOT-2 669055 1834 ns/op 144 B/op 5 allocs/op +Benchmark_life_callSumAndAdd1_10_NoAOT-2 93620 12866 ns/op 936 B/op 32 allocs/op +Benchmark_life_callSumAndAdd1_0_AOT-2 854827 1390 ns/op 80 B/op 4 allocs/op +Benchmark_life_callSumAndAdd1_1_AOT-2 463479 2645 ns/op 168 B/op 7 allocs/op +Benchmark_life_callSumAndAdd1_10_AOT-2 87625 13668 ns/op 960 B/op 34 allocs/op +Benchmark_life_callSumAndAdd1_100_AOT-2 9847 141453 ns/op 8880 B/op 304 allocs/op \ No newline at end of file diff --git a/bench/calls/callSumAndAdd1_0.md b/bench/calls/callSumAndAdd1_0.md new file mode 100644 index 0000000..ed9c0b6 --- /dev/null +++ b/bench/calls/callSumAndAdd1_0.md @@ -0,0 +1,95 @@ +https://wasdk.github.io/wasmcodeexplorer/ + + case opcodes.GetLocal: + //1, 149 + id := int(LE.Uint32(frame.Code[frame.IP : frame.IP+4])) //2 + val := frame.Locals[id] + frame.IP += 4 + frame.Regs[valueID] = val + + case opcodes.I32Const: + // 2, 3 + val := LE.Uint32(frame.Code[frame.IP : frame.IP+4]) //1 + frame.IP += 4 + frame.Regs[valueID] = int64(val) + + case opcodes.I32GeS: + // 1, 31 + a := int32(frame.Regs[int(LE.Uint32(frame.Code[frame.IP:frame.IP+4]))]) + b := int32(frame.Regs[int(LE.Uint32(frame.Code[frame.IP+4:frame.IP+8]))]) + frame.IP += 8 + if a >= b { + frame.Regs[valueID] = 1 + } else { + frame.Regs[valueID] = 0 + } + + case opcodes.JmpIf: + target := int(LE.Uint32(frame.Code[frame.IP : frame.IP+4])) + cond := int(LE.Uint32(frame.Code[frame.IP+4 : frame.IP+8])) + yieldedReg := int(LE.Uint32(frame.Code[frame.IP+8 : frame.IP+12])) + frame.IP += 12 + if frame.Regs[cond] != 0 { + vm.Yielded = frame.Regs[yieldedReg] + frame.IP = target + } + + case opcodes.Jmp: + target := int(LE.Uint32(frame.Code[frame.IP : frame.IP+4])) + vm.Yielded = frame.Regs[int(LE.Uint32(frame.Code[frame.IP+4:frame.IP+8]))] + frame.IP = target + + case opcodes.GetLocal: + id := int(LE.Uint32(frame.Code[frame.IP : frame.IP+4])) + val := frame.Locals[id] + frame.IP += 4 + frame.Regs[valueID] = val + + case opcodes.ReturnValue: + val := frame.Regs[int(LE.Uint32(frame.Code[frame.IP:frame.IP+4]))] + frame.Destroy(vm) + vm.CurrentFrame-- + if vm.CurrentFrame == -1 { + vm.Exited = true + vm.ReturnValue = val + return + } + + frame = vm.GetCurrentFrame() + frame.Regs[frame.ReturnReg] = val + + + +1.0:{valueID: 1, opcode: opcodes.GetLocal, v1: 2, v2: 2} +1.9:{valueID: 2, opcode: opcodes.I32Const, v1: 1, v2: 1} +1.18:{valueID: 1, opcode: opcodes.I32GeS, v1: 1, v2: 2} +1.31:{valueID: 0, opcode: opcodes.JmpIf, v1: 61, v2: 1} +1.61:{valueID: 1, opcode: opcodes.GetLocal, v1: 0, v2: 2} +1.70:{valueID: 2, opcode: opcodes.GetLocal, v1: 1, v2: 1} +1.79:{valueID: 1, opcode: opcodes.Call, v1: 0, v2: 2} +0.0:{valueID: 1, opcode: opcodes.InvokeImport, v1: 0, v2: 0} +2020/08/04 15:23:01 Resolver called +0.9:{valueID: 0, opcode: opcodes.ReturnValue, v1: 1, v2: 0} +1.100:{valueID: 2, opcode: opcodes.I32Const, v1: 1, v2: 1} +1.109:{valueID: 1, opcode: opcodes.I32Add, v1: 1, v2: 2} +1.122:{valueID: 0, opcode: opcodes.SetLocal, v1: 0, v2: 1} +1.135:{valueID: 1, opcode: opcodes.GetLocal, v1: 2, v2: 2} +1.144:{valueID: 2, opcode: opcodes.I32Const, v1: 4294967295, v2: 1} +1.153:{valueID: 1, opcode: opcodes.I32Add, v1: 1, v2: 2} +1.166:{valueID: 0, opcode: opcodes.SetLocal, v1: 2, v2: 1} +1.179:{valueID: 0, opcode: opcodes.JmpIf, v1: 61, v2: 1} +1.61:{valueID: 1, opcode: opcodes.GetLocal, v1: 0, v2: 2} +1.70:{valueID: 2, opcode: opcodes.GetLocal, v1: 1, v2: 1} +1.79:{valueID: 1, opcode: opcodes.Call, v1: 0, v2: 2} +0.0:{valueID: 1, opcode: opcodes.InvokeImport, v1: 0, v2: 0} +0.9:{valueID: 0, opcode: opcodes.ReturnValue, v1: 1, v2: 0} +1.100:{valueID: 2, opcode: opcodes.I32Const, v1: 1, v2: 1} +1.109:{valueID: 1, opcode: opcodes.I32Add, v1: 1, v2: 2} +1.122:{valueID: 0, opcode: opcodes.SetLocal, v1: 0, v2: 1} +1.135:{valueID: 1, opcode: opcodes.GetLocal, v1: 2, v2: 2} +1.144:{valueID: 2, opcode: opcodes.I32Const, v1: 4294967295, v2: 1} +1.153:{valueID: 1, opcode: opcodes.I32Add, v1: 1, v2: 2} +1.166:{valueID: 0, opcode: opcodes.SetLocal, v1: 2, v2: 1} +1.179:{valueID: 0, opcode: opcodes.JmpIf, v1: 61, v2: 1} +1.196:{valueID: 1, opcode: opcodes.GetLocal, v1: 0, v2: 0} +1.205:{valueID: 0, opcode: opcodes.ReturnValue, v1: 1, v2: 0} \ No newline at end of file diff --git a/bench/calls/calls_test.go b/bench/calls/calls_test.go new file mode 100644 index 0000000..436fed0 --- /dev/null +++ b/bench/calls/calls_test.go @@ -0,0 +1,147 @@ +package calls + +import ( + "fmt" + "io/ioutil" + "log" + "testing" + + "github.com/perlin-network/life/exec" + "github.com/stretchr/testify/require" +) + +func Test_callSumAndAdd1(t *testing.T) { + + input, err := ioutil.ReadFile("sum-add.wasm") + require.Nil(t, err) + + vm := newVM(t, input, &lifeResolver{}, false) + require.Nil(t, err) + + entryID, ok := vm.GetFunctionExport("callSumAndAdd1") + require.True(t, ok) + + ret, err := vm.Run(entryID, 3, 4, 0) + require.Equal(t, int64(3), ret) + + ret, err = vm.Run(entryID, 3, 4, 1) + require.Nil(t, err) + require.Equal(t, int64(8), ret) + + ret, err = vm.Run(entryID, 3, 4, 10) + require.Nil(t, err) + require.Equal(t, int64(53), ret) +} + +func Test_callSumAndAdd1_0(t *testing.T) { + input, err := ioutil.ReadFile("sum-add.wasm") + require.Nil(t, err) + + vm := newVM(t, input, &lifeResolver{}, false) + require.Nil(t, err) + + entryID, ok := vm.GetFunctionExport("callSumAndAdd1") + require.True(t, ok) + + ret, err := vm.Run(entryID, 3, 4, 0) + require.Nil(t, err) + require.Equal(t, int64(8), ret) +} + +func Test_callSumAndAdd1_2(t *testing.T) { + input, err := ioutil.ReadFile("sum-add.wasm") + require.Nil(t, err) + + vm := newVM(t, input, &lifeResolver{}, false) + require.Nil(t, err) + + entryID, ok := vm.GetFunctionExport("callSumAndAdd1") + require.True(t, ok) + + ret, err := vm.Run(entryID, 3, 4, 2) + require.Nil(t, err) + require.Equal(t, int64(8), ret) +} + +func Benchmark_Ignite(t *testing.B) { + + input, err := ioutil.ReadFile("sum-add.wasm") + require.Nil(t, err) + + vm := newVM(t, input, &lifeResolver{}, false) + require.Nil(t, err) + + entryID, ok := vm.GetFunctionExport("callSumAndAdd1") + require.True(t, ok) + + t.ResetTimer() + for i := 0; i < t.N; i++ { + vm.Ignite(entryID, 3, 4, 10) + vm.CurrentFrame-- + vm.Exited = true + } +} + +func Benchmark_callSumAndAdd1_0_NoAOT(b *testing.B) { + callSumAndAdd1(b, 0, false) +} + +func Benchmark_callSumAndAdd1_1_NoAOT(b *testing.B) { + callSumAndAdd1(b, 1, false) +} +func Benchmark_callSumAndAdd1_10_NoAOT(b *testing.B) { + callSumAndAdd1(b, 10, false) +} + +func Benchmark_callSumAndAdd1_0_AOT(b *testing.B) { + callSumAndAdd1(b, 0, true) +} +func Benchmark_callSumAndAdd1_1_AOT(b *testing.B) { + callSumAndAdd1(b, 1, true) +} +func Benchmark_callSumAndAdd1_10_AOT(b *testing.B) { + callSumAndAdd1(b, 10, true) +} + +func callSumAndAdd1(t *testing.B, cnt int, aot bool) { + input, err := ioutil.ReadFile("sum-add.wasm") + require.Nil(t, err) + + vm := newVM(t, input, &lifeResolver{}, aot) + + entryID, ok := vm.GetFunctionExport("callSumAndAdd1") + require.True(t, ok) + + t.ResetTimer() + for i := 0; i < t.N; i++ { + _, err := vm.Run(entryID, 3, 4, int64(cnt)) + if nil != err { + panic(err) + } + } +} + +type lifeResolver struct{} + +func (r *lifeResolver) ResolveFunc(module, field string) exec.FunctionImport { + switch module { + case "env": + switch field { + case "sum": + log.Println("Resolver called") + return func(vm *exec.VirtualMachine) int64 { + v1 := int32(vm.GetCurrentFrame().Locals[0]) + v2 := int32(vm.GetCurrentFrame().Locals[1]) + return int64(v1 + v2) + } + default: + panic(fmt.Errorf("unknown import resolved: %s", field)) + } + default: + panic(fmt.Errorf("unknown module: %s", module)) + } +} + +func (r *lifeResolver) ResolveGlobal(module, field string) int64 { + panic("we're not resolving global variables for now") +} diff --git a/bench/calls/fast_test.go b/bench/calls/fast_test.go new file mode 100644 index 0000000..94d0361 --- /dev/null +++ b/bench/calls/fast_test.go @@ -0,0 +1,58 @@ +package calls + +import ( + "testing" + + "github.com/perlin-network/life/compiler/opcodes" + "github.com/stretchr/testify/require" +) + +func Test_fast_callSumAndAdd1(t *testing.T) { + vm := newFastVM() + fn := newCallSumAndAdd1_0() + res, _ := vm.exec(fn, 3, 4, 0) + require.Equal(t, int64(3), res) + +} + +func Benchmark_fast_callSumAndAdd1(b *testing.B) { + vm := newFastVM() + fn := newCallSumAndAdd1_0() + for i := 0; i < b.N; i++ { + vm.exec(fn, 3, 4, 0) + } +} + +func Benchmark_fast_justCall(b *testing.B) { + vm := newFastVM() + fn := newJustCall() + for i := 0; i < b.N; i++ { + vm.exec(fn) + } +} + +func newCallSumAndAdd1_0() (res *function) { + fn := function{} + fn.NumParams = 3 + fn.NumRegs = 3 + fn.NumLocals = 0 + + fn.inss = append(fn.inss, ins{valueID: 1, opcode: opcodes.GetLocal, v1: 2, v2: 2}) + fn.inss = append(fn.inss, ins{valueID: 2, opcode: opcodes.I32Const, v1: 1, v2: 1}) + fn.inss = append(fn.inss, ins{valueID: 1, opcode: opcodes.I32GeS, v1: 1, v2: 2}) + fn.inss = append(fn.inss, ins{valueID: 0, opcode: opcodes.JmpIf, v1: 61, v2: 1}) + fn.inss = append(fn.inss, ins{valueID: 0, opcode: opcodes.Jmp, v1: 5, v2: 0}) + fn.inss = append(fn.inss, ins{valueID: 1, opcode: opcodes.GetLocal, v1: 0, v2: 0}) + fn.inss = append(fn.inss, ins{valueID: 0, opcode: opcodes.ReturnValue, v1: 1, v2: 0}) + return &fn +} + +func newJustCall() (res *function) { + fn := function{} + fn.NumParams = 0 + fn.NumRegs = 1 + fn.NumLocals = 0 + + fn.inss = append(fn.inss, ins{valueID: 0, opcode: opcodes.ReturnValue, v1: 0, v2: 0}) + return &fn +} diff --git a/bench/calls/fastvm.go b/bench/calls/fastvm.go new file mode 100644 index 0000000..0c05131 --- /dev/null +++ b/bench/calls/fastvm.go @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2020-present Heeus Authors + */ + +package calls + +import ( + "errors" + + "github.com/perlin-network/life/compiler/opcodes" +) + +type function struct { + inss []ins + NumRegs int + NumParams int + NumLocals int + NumReturns int +} + +type ins struct { + opcode opcodes.Opcode + valueID uint32 + v1 uint32 + v2 uint32 +} + +type fastvm struct { + valueSlots []int64 + gas uint + gasLimit uint +} + +func newFastVM() (res *fastvm) { + res = &fastvm{} + res.valueSlots = make([]int64, 1000) + res.gasLimit = ^uint(0) + return res +} + +func (vm *fastvm) exec(fn *function, params ...int64) (res int64, err error) { + return vm.execinternal(0, fn, params...) +} + +func (vm *fastvm) execinternal(slot int, fn *function, params ...int64) (res int64, err error) { + + if fn.NumParams != len(params) { + panic("param count mismatch") + } + + numValueSlots := fn.NumRegs + fn.NumParams + fn.NumLocals + Regs := vm.valueSlots[slot : slot+fn.NumRegs] + Locals := vm.valueSlots[slot+fn.NumRegs : slot+numValueSlots] + copy(Locals, params) + + gas := vm.gas + // gasLimit := vm.gasLimit + + ip := 0 + for { + ins := fn.inss[ip] + valueID := ins.valueID + ip++ + gas++ + switch ins.opcode { + case opcodes.GetLocal: + id := ins.v1 + val := Locals[id] + Regs[valueID] = val + case opcodes.I32Const: + val := ins.v1 + Regs[valueID] = int64(val) + case opcodes.I32GeS: + a := int32(ins.v1) + b := int32(ins.v2) + if a >= b { + Regs[valueID] = 1 + } else { + Regs[valueID] = 0 + } + case opcodes.JmpIf: + if vm.gas > vm.gasLimit { + return 0, errors.New("Gas limit exceeded") + } + + target := int(ins.v1) + cond := int(ins.v2) + // yieldedReg := int(LE.Uint32(frame.Code[frame.IP+8 : frame.IP+12])) + // frame.IP += 12 + if Regs[cond] != 0 { + ip = target + } + case opcodes.Jmp: + if vm.gas > vm.gasLimit { + return 0, errors.New("Gas limit exceeded") + } + + target := int(ins.v1) + // vm.Yielded = frame.Regs[int(LE.Uint32(frame.Code[frame.IP+4:frame.IP+8]))] + ip = target + case opcodes.ReturnValue: + val := Regs[int(ins.v1)] + return val, nil + default: + panic("Unknown op") + } + } +} diff --git a/bench/calls/sum-add.rs b/bench/calls/sum-add.rs new file mode 100644 index 0000000..0db6fad --- /dev/null +++ b/bench/calls/sum-add.rs @@ -0,0 +1,17 @@ +extern { + fn sum(x: i32, y: i32) -> i32; +} + +#[no_mangle] +pub extern fn add1(x: i32, y: i32) -> i32 { + unsafe { sum(x, y) + 1 } +} + +#[no_mangle] +pub extern fn callSumAndAdd1(x: i32, y: i32, cnt: i32) -> i32 { + let mut res = x; + for _i in 0..cnt { + unsafe { res = sum(res, y) + 1 } + } + return res +} diff --git a/bench/calls/sum-add.wasm b/bench/calls/sum-add.wasm new file mode 100644 index 0000000..4b1d785 Binary files /dev/null and b/bench/calls/sum-add.wasm differ diff --git a/bench/calls/utils.go b/bench/calls/utils.go new file mode 100644 index 0000000..6e8cb9d --- /dev/null +++ b/bench/calls/utils.go @@ -0,0 +1,26 @@ +package calls + +import ( + "log" + "runtime" + + "github.com/perlin-network/life/exec" + "github.com/perlin-network/life/platform" + "github.com/stretchr/testify/require" +) + +func newVM(t require.TestingT, input []byte, impResolver exec.ImportResolver, aot bool) *exec.VirtualMachine { + + vm, err := exec.NewVirtualMachine(input, exec.VMConfig{KeepFrameValues: true}, impResolver, nil) + require.Nil(t, err) + + if aot { + aotSvc := platform.FullAOTCompile(vm) + if nil != aotSvc { + vm.AOTService = aotSvc + } else { + log.Println("WARNNING: AOT is not supported on", runtime.GOOS) + } + } + return vm +} diff --git a/exec/helpers.go b/exec/helpers.go index 9998862..512e0a4 100644 --- a/exec/helpers.go +++ b/exec/helpers.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" + "github.com/perlin-network/life/compiler" "github.com/perlin-network/life/utils" ) @@ -30,10 +31,7 @@ func (vm *VirtualMachine) RunWithGasLimit(entryID, limit int, params ...int64) ( vm.Ignite(entryID, params...) for !vm.Exited { vm.Execute() - if vm.Delegate != nil { - vm.Delegate() - vm.Delegate = nil - } + vm.execDelegate() count++ if count == limit { return -1, errors.New("gas limit exceeded") @@ -81,10 +79,7 @@ func (vm *VirtualMachine) Run(entryID int, params ...int64) (retVal int64, retEr for !vm.Exited { vm.Execute() - if vm.Delegate != nil { - vm.Delegate() - vm.Delegate = nil - } + vm.execDelegate() } if vm.ExitError != nil { diff --git a/exec/vm.go b/exec/vm.go index 4d7890b..6b29fff 100644 --- a/exec/vm.go +++ b/exec/vm.go @@ -26,6 +26,12 @@ const ( // JITCodeSizeThreshold is the lower-bound code size threshold for the JIT compiler. JITCodeSizeThreshold = 30 + + // MaxKeptValueSlots defines maximum possible Frame.values capacity to be kept + MaxKeptValueSlots = 30 + // MaxKeptValueCallStackDepth defines maximum stack size to keep Frame.values + // If stak grows above this threshold Frame.values will be always re-allocated + MaxKeptValueCallStackDepth = DefaultCallStackSize ) // LE is a simple alias to `binary.LittleEndian`. @@ -63,7 +69,6 @@ type VirtualMachine struct { NumValueSlots int Yielded int64 InsideExecute bool - Delegate func() Exited bool ExitError interface{} ReturnValue int64 @@ -73,6 +78,11 @@ type VirtualMachine struct { ImportResolver ImportResolver AOTService AOTService StackTrace string + + // if true function defined by importID should be called + delegate bool + delegateImportID int + delegateValueID int } // VMConfig denotes a set of options passed to a single VirtualMachine insta.ce @@ -87,6 +97,9 @@ type VMConfig struct { GasLimit uint64 DisableFloatingPoint bool ReturnOnGasLimitExceeded bool + // If true frame values won't be re-allocated until certain threshold is reached + // See MaxKeptValueSlots and MaxKeptValueCallStackDepth + KeepFrameValues bool } // Frame represents a call frame. @@ -98,6 +111,7 @@ type Frame struct { IP int ReturnReg int Continuation int32 + values []int64 } // ImportResolver is an interface for allowing one to define imports to WebAssembly modules @@ -700,7 +714,20 @@ func (f *Frame) Init(vm *VirtualMachine, functionID int, code compiler.Interpret } vm.NumValueSlots += numValueSlots - values := make([]int64, numValueSlots) + var values []int64 + if vm.Config.KeepFrameValues { + + if cap(f.values) < numValueSlots { + f.values = make([]int64, numValueSlots) + } + f.values = f.values[:numValueSlots] + for idx := range f.values { + f.values[idx] = 0 + } + values = f.values + } else { + values = make([]int64, numValueSlots) + } f.FunctionID = functionID f.Regs = values[:code.NumRegs] @@ -717,6 +744,10 @@ func (f *Frame) Destroy(vm *VirtualMachine) { numValueSlots := len(f.Regs) + len(f.Locals) vm.NumValueSlots -= numValueSlots + if vm.CurrentFrame > MaxKeptValueCallStackDepth || cap(f.values) > MaxKeptValueSlots { + f.values = nil + } + //fmt.Printf("Leave function %d (%s)\n", f.FunctionID, vm.Module.FunctionNames[f.FunctionID]) } @@ -788,7 +819,8 @@ func (vm *VirtualMachine) Ignite(functionID int, params ...int64) { vm.Exited = false vm.CurrentFrame++ - frame := vm.GetCurrentFrame() + var frame *Frame + frame = vm.GetCurrentFrame() frame.Init( vm, functionID, @@ -816,6 +848,28 @@ func (vm *VirtualMachine) AddAndCheckGas(delta uint64) bool { return true } +func (vm *VirtualMachine) execDelegate() { + if !vm.delegate { + return + } + defer func() { vm.delegate = false }() + defer func() { + if err := recover(); err != nil { + vm.Exited = true + vm.ExitError = err + } + }() + imp := vm.FunctionImports[vm.delegateImportID] + if imp.F == nil { + imp.F = vm.ImportResolver.ResolveFunc(imp.ModuleName, imp.FieldName) + // ? May be better `FunctionImports []*FunctionImportInfo` + vm.FunctionImports[vm.delegateImportID] = imp + } + frame := vm.GetCurrentFrame() + frame.Regs[vm.delegateValueID] = imp.F(vm) + +} + // Execute starts the virtual machines main instruction processing loop. // This function may return at any point and is guaranteed to return // at least once every 10000 instructions. Caller is responsible for @@ -825,7 +879,7 @@ func (vm *VirtualMachine) Execute() { panic("attempting to execute an exited vm") } - if vm.Delegate != nil { + if vm.delegate { panic("delegate not cleared") } @@ -844,14 +898,19 @@ func (vm *VirtualMachine) Execute() { } }() - frame := vm.GetCurrentFrame() + var frame *Frame + frame = nil + frame = vm.GetCurrentFrame() for { + valueID := int(LE.Uint32(frame.Code[frame.IP : frame.IP+4])) ins := opcodes.Opcode(frame.Code[frame.IP+4]) frame.IP += 5 //fmt.Printf("INS: [%d] %s\n", valueID, ins.String()) + fmt.Printf("%v.%v:", frame.FunctionID, frame.IP-5) + fmt.Printf("{valueID: %v, opcode: opcodes.%v, v1: %v, v2: %v}\n", valueID, ins, LE.Uint32(frame.Code[frame.IP:frame.IP+4]), LE.Uint32(frame.Code[frame.IP+4:frame.IP+8])) switch ins { case opcodes.Nop: @@ -2040,20 +2099,9 @@ func (vm *VirtualMachine) Execute() { case opcodes.InvokeImport: importID := int(LE.Uint32(frame.Code[frame.IP : frame.IP+4])) frame.IP += 4 - vm.Delegate = func() { - defer func() { - if err := recover(); err != nil { - vm.Exited = true - vm.ExitError = err - } - }() - imp := vm.FunctionImports[importID] - if imp.F == nil { - imp.F = vm.ImportResolver.ResolveFunc(imp.ModuleName, imp.FieldName) - } - frame.Regs[valueID] = imp.F(vm) - } - + vm.delegate = true + vm.delegateValueID = valueID + vm.delegateImportID = importID return case opcodes.CurrentMemory: frame.Regs[valueID] = int64(len(vm.Memory) / DefaultPageSize) @@ -2072,6 +2120,8 @@ func (vm *VirtualMachine) Execute() { case opcodes.Phi: frame.Regs[valueID] = vm.Yielded + // https://bytecodealliance.org/articles/multi-value-all-the-wasm + // A phi function takes a number of mutually exclusive, control flow-dependent parameters and returns the one that was defined where control flow came from. case opcodes.AddGas: delta := LE.Uint64(frame.Code[frame.IP : frame.IP+8]) diff --git a/go.mod b/go.mod index 48e6c11..0ecd347 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,7 @@ module github.com/perlin-network/life require ( github.com/go-interpreter/wagon v0.6.0 + github.com/stretchr/testify v1.6.1 github.com/vmihailenco/msgpack v4.0.4+incompatible golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e // indirect google.golang.org/appengine v1.6.0 // indirect diff --git a/platform/aot_unix.go b/platform/aot_unix.go index 57fd662..041c8a6 100644 --- a/platform/aot_unix.go +++ b/platform/aot_unix.go @@ -1,4 +1,4 @@ -// +build !android +// +build !android,!windows package platform diff --git a/test_runner b/test_runner deleted file mode 100755 index 8998559..0000000 Binary files a/test_runner and /dev/null differ