From 38c3629ebef7509da95c74fe73bdef5be9072877 Mon Sep 17 00:00:00 2001 From: Michal Idzikowski Date: Mon, 21 Feb 2022 16:58:22 +0100 Subject: [PATCH 1/2] RunCompiled in VM --- script.go | 10 +++++++++ vm.go | 60 +++++++++++++++++++++++++++++++++++++++++++++++++----- vm_test.go | 40 ++++++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+), 5 deletions(-) diff --git a/script.go b/script.go index 82b02f52..c7373dd4 100644 --- a/script.go +++ b/script.go @@ -336,3 +336,13 @@ func (c *Compiled) Set(name string, value interface{}) error { c.globals[idx] = obj return nil } + +// Bytecode returns bytecode of this Compiled +func (c *Compiled) Bytecode() *Bytecode { + return c.bytecode +} + +// Globals return globals in this Compiled +func (c *Compiled) Globals() []Object { + return c.globals +} \ No newline at end of file diff --git a/vm.go b/vm.go index c8365252..3e8c8f09 100644 --- a/vm.go +++ b/vm.go @@ -64,18 +64,69 @@ func (v *VM) Abort() { atomic.StoreInt64(&v.aborting, 1) } +// constant wrapper function func(fn, ...args){ return fn(args...) } +var funcWrapper = &CompiledFunction{ + Instructions: append( + MakeInstruction(parser.OpGetLocal, 0), + append(MakeInstruction(parser.OpGetLocal, 1), + append(MakeInstruction(parser.OpCall, 1, 1), + MakeInstruction(parser.OpReturn, 1)...)...)..., + ), + NumLocals: 2, + NumParameters: 2, + VarArgs: true, +} + +// RunCompiled run specified function in VM context +func (v *VM) RunCompiled(fn *CompiledFunction, args ...Object) (val Object, err error) { + v.stack = [StackSize]Object{} + sp := 0 + + if fn != nil { // run user supplied function + entry := &CompiledFunction{ + Instructions: append( + MakeInstruction(parser.OpCall, 1+len(args), 0), + MakeInstruction(parser.OpSuspend)..., + ), + } + v.stack[0] = funcWrapper + v.stack[1] = fn + for i, arg := range args { + v.stack[i+2] = arg + } + sp = 2 + len(args) + v.frames[0].fn = entry + } + + v.resetState() + v.sp = sp + + val = UndefinedValue + v.run() + if fn != nil && atomic.LoadInt64(&v.aborting) == 0 { + val = v.stack[v.sp-1] + } + atomic.StoreInt64(&v.aborting, 0) + err = v.handleError() + return +} + // Run starts the execution. func (v *VM) Run() (err error) { - // reset VM states + _, err = v.RunCompiled(nil) + return +} + +func (v *VM) resetState() { v.sp = 0 v.curFrame = &(v.frames[0]) v.curInsts = v.curFrame.fn.Instructions v.framesIndex = 1 v.ip = -1 v.allocs = v.maxAllocs + 1 +} - v.run() - atomic.StoreInt64(&v.aborting, 0) +func (v *VM) handleError() (err error) { err = v.err if err != nil { filePos := v.fileSet.Position( @@ -89,9 +140,8 @@ func (v *VM) Run() (err error) { v.curFrame.fn.SourcePos(v.curFrame.ip - 1)) err = fmt.Errorf("%w\n\tat %s", err, filePos) } - return err } - return nil + return } func (v *VM) run() { diff --git a/vm_test.go b/vm_test.go index feb91f4f..37f77bd9 100644 --- a/vm_test.go +++ b/vm_test.go @@ -3625,6 +3625,46 @@ func TestSpread(t *testing.T) { "Runtime Error: wrong number of arguments: want=3, got=2") } +func TestRunCompiled(t *testing.T) { + s := tengo.NewScript([]byte(`fnMap := {"fun1": func(a) { return a * 2 }}`)) + c, err := s.Run() + require.NoError(t, err) + + cFn := c.Get("fnMap").Map()["fun1"].(*tengo.CompiledFunction) + + globals := make([]tengo.Object, tengo.GlobalsSize) + vm := tengo.NewVM(c.Bytecode(), globals, -1) + require.NotNil(t, vm) + + res, err := vm.RunCompiled(cFn, &tengo.Int{Value: 12}) + require.NoError(t, err) + require.NotNil(t, res) + require.Equal(t, res, &tengo.Int{Value: 24}) +} + +func TestRunCompiledWithModules(t *testing.T) { + mods := tengo.NewModuleMap() + mods.AddSourceModule("mod2", []byte(`export func() { return 5 }`)) + mods.AddSourceModule("mod1", []byte(`mod2 := import("mod2"); export func() { return mod2() * 3 }`)) + + s := tengo.NewScript([]byte(`mod1 := import("mod1"); fnMap := {"fun1": func(a) { return a * 2 + mod1() }}`)) + s.SetImports(mods) + + c, err := s.Run() + require.NoError(t, err) + + cFn := c.Get("fnMap").Map()["fun1"].(*tengo.CompiledFunction) + + //globals := make([]tengo.Object, tengo.GlobalsSize) + vm := tengo.NewVM(c.Bytecode(), c.Globals(), -1) + require.NotNil(t, vm) + + res, err := vm.RunCompiled(cFn, &tengo.Int{Value: 12}) + require.NoError(t, err) + require.NotNil(t, res) + require.Equal(t, &tengo.Int{Value: 39}, res) +} + func expectRun( t *testing.T, input string, From e8d6f04270290eaebcf280c3ffeaf91f3e80828e Mon Sep 17 00:00:00 2001 From: Michal Idzikowski Date: Tue, 6 Sep 2022 15:54:33 +0200 Subject: [PATCH 2/2] add concurrency test for CompiledFunction --- vm_test.go | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/vm_test.go b/vm_test.go index 37f77bd9..64633a92 100644 --- a/vm_test.go +++ b/vm_test.go @@ -3655,14 +3655,28 @@ func TestRunCompiledWithModules(t *testing.T) { cFn := c.Get("fnMap").Map()["fun1"].(*tengo.CompiledFunction) - //globals := make([]tengo.Object, tengo.GlobalsSize) - vm := tengo.NewVM(c.Bytecode(), c.Globals(), -1) - require.NotNil(t, vm) + var testCases [][]int64 + for i := 0; i < 1000; i++ { + testCases = append(testCases, []int64{int64(i), int64(2*i + 15)}) + } - res, err := vm.RunCompiled(cFn, &tengo.Int{Value: 12}) - require.NoError(t, err) - require.NotNil(t, res) - require.Equal(t, &tengo.Int{Value: 39}, res) + results := make(chan bool, 1000) + + for _, testCase := range testCases { + go func(pair []int64) { + vm := tengo.NewVM(c.Bytecode(), c.Globals(), -1) + require.NotNil(t, vm) + res, err := vm.RunCompiled(cFn, &tengo.Int{Value: pair[0]}) + require.NoError(t, err) + require.NotNil(t, res) + require.Equal(t, &tengo.Int{Value: pair[1]}, res) + results <- true + }(testCase) + } + + for i := 0; i < 1000; i++ { + require.True(t, <-results) + } } func expectRun(