From e09a0f396925198156c304055504b5126a0852d7 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Sat, 28 Aug 2021 22:27:38 +0300 Subject: [PATCH 1/4] vm: don't allocate for break points in NewContext They're rarely used and when they're used they're appended to. --- pkg/vm/context.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pkg/vm/context.go b/pkg/vm/context.go index 25d29f2fb..a4a288477 100644 --- a/pkg/vm/context.go +++ b/pkg/vm/context.go @@ -79,11 +79,10 @@ func NewContext(b []byte) *Context { // return value count and initial position in script. func NewContextWithParams(b []byte, pcount int, rvcount int, pos int) *Context { return &Context{ - prog: b, - breakPoints: []int{}, - ParamCount: pcount, - RetCount: rvcount, - nextip: pos, + prog: b, + ParamCount: pcount, + RetCount: rvcount, + nextip: pos, } } From bc31c97c320a1ab2d494d554b85203c6cc49b780 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Sat, 28 Aug 2021 22:31:08 +0300 Subject: [PATCH 2/4] vm: simplify access to context, don't call Context() twice MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Avoid going through Value(), avoid doing type casts twice for every instruction. name old time/op new time/op delta ScriptFibonacci-8 793µs ± 3% 736µs ± 1% -7.18% (p=0.000 n=10+9) ScriptNestedRefCount-8 1.09ms ± 1% 1.08ms ± 2% -0.96% (p=0.035 n=10+10) ScriptPushPop/4-8 1.51µs ± 3% 1.48µs ± 3% ~ (p=0.072 n=10+10) ScriptPushPop/16-8 3.76µs ± 1% 3.59µs ± 1% -4.56% (p=0.000 n=10+10) ScriptPushPop/128-8 25.0µs ± 1% 23.7µs ± 1% -5.28% (p=0.000 n=10+10) ScriptPushPop/1024-8 184µs ± 1% 176µs ± 2% -4.22% (p=0.000 n=9+9) --- pkg/vm/context.go | 3 +-- pkg/vm/vm.go | 20 ++++++++++++++------ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/pkg/vm/context.go b/pkg/vm/context.go index a4a288477..d00e091bc 100644 --- a/pkg/vm/context.go +++ b/pkg/vm/context.go @@ -286,8 +286,7 @@ func (v *VM) getContextScriptHash(n int) util.Uint160 { return util.Uint160{} } element := istack.Peek(n) - ctxIface := element.Value() - ctx := ctxIface.(*Context) + ctx := element.value.(*Context) return ctx.ScriptHash() } diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 35b89dfa3..eaa8b93eb 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -322,7 +322,7 @@ func (v *VM) Context() *Context { if v.istack.Len() == 0 { return nil } - return v.istack.Peek(0).Value().(*Context) + return v.istack.Peek(0).value.(*Context) } // PopResult is used to pop the first item of the evaluation stack. This allows @@ -360,6 +360,8 @@ func (v *VM) Ready() bool { // Run starts the execution of the loaded program. func (v *VM) Run() error { + var ctx *Context + if !v.Ready() { v.state = FaultState return errors.New("no program loaded") @@ -372,6 +374,7 @@ func (v *VM) Run() error { } // HaltState (the default) or BreakState are safe to continue. v.state = NoneState + ctx = v.Context() for { switch { case v.state.HasFlag(FaultState): @@ -382,7 +385,7 @@ func (v *VM) Run() error { // Normal exit from this loop. return nil case v.state == NoneState: - if err := v.Step(); err != nil { + if err := v.step(ctx); err != nil { return err } default: @@ -390,7 +393,7 @@ func (v *VM) Run() error { return errors.New("unknown state") } // check for breakpoint before executing the next instruction - ctx := v.Context() + ctx = v.Context() if ctx != nil && ctx.atBreakPoint() { v.state = BreakState } @@ -400,6 +403,11 @@ func (v *VM) Run() error { // Step 1 instruction in the program. func (v *VM) Step() error { ctx := v.Context() + return v.step(ctx) +} + +// step executes one instruction in given context. +func (v *VM) step(ctx *Context) error { op, param, err := ctx.Next() if err != nil { v.state = FaultState @@ -1285,7 +1293,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro } case opcode.RET: - oldCtx := v.istack.Pop().Value().(*Context) + oldCtx := v.istack.Pop().value.(*Context) oldEstack := v.estack v.unloadContext(oldCtx) @@ -1552,7 +1560,7 @@ func calcJumpOffset(ctx *Context, parameter []byte) (int, int, error) { func (v *VM) handleException() { for pop := 0; pop < v.istack.Len(); pop++ { ictxv := v.istack.Peek(pop) - ictx := ictxv.Value().(*Context) + ictx := ictxv.value.(*Context) for j := 0; j < ictx.tryStack.Len(); j++ { e := ictx.tryStack.Peek(j) ectx := e.Value().(*exceptionHandlingContext) @@ -1562,7 +1570,7 @@ func (v *VM) handleException() { continue } for i := 0; i < pop; i++ { - ctx := v.istack.Pop().Value().(*Context) + ctx := v.istack.Pop().value.(*Context) v.unloadContext(ctx) } if ectx.State == eTry && ectx.HasCatch() { From a3892aa662e2887d30077314933a14cf28932883 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Sat, 28 Aug 2021 22:35:43 +0300 Subject: [PATCH 3/4] vm: don't use PushVal when item type is known MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PushVal is very convenient, but type switch is somewhat expensive. name old time/op new time/op delta ScriptFibonacci-8 736µs ± 1% 602µs ± 1% -18.21% (p=0.000 n=9+10) ScriptNestedRefCount-8 1.08ms ± 2% 0.96ms ± 1% -11.13% (p=0.000 n=10+10) ScriptPushPop/4-8 1.48µs ± 3% 1.35µs ± 2% -9.14% (p=0.000 n=10+9) ScriptPushPop/16-8 3.59µs ± 1% 3.38µs ± 1% -6.01% (p=0.000 n=10+10) ScriptPushPop/128-8 23.7µs ± 1% 22.6µs ± 1% -4.39% (p=0.000 n=10+8) ScriptPushPop/1024-8 176µs ± 2% 167µs ± 3% -5.24% (p=0.000 n=9+10) name old alloc/op new alloc/op delta ScriptFibonacci-8 123kB ± 0% 114kB ± 0% -6.88% (p=0.000 n=10+9) ScriptNestedRefCount-8 266kB ± 0% 241kB ± 0% -9.23% (p=0.000 n=8+10) ScriptPushPop/4-8 160B ± 0% 160B ± 0% ~ (all equal) ScriptPushPop/16-8 640B ± 0% 640B ± 0% ~ (all equal) ScriptPushPop/128-8 8.70kB ± 0% 8.70kB ± 0% ~ (all equal) ScriptPushPop/1024-8 73.2kB ± 0% 73.2kB ± 0% ~ (all equal) name old allocs/op new allocs/op delta ScriptFibonacci-8 3.53k ± 0% 3.17k ± 0% -9.98% (p=0.000 n=10+10) ScriptNestedRefCount-8 11.8k ± 0% 10.7k ± 0% -8.70% (p=0.000 n=10+10) ScriptPushPop/4-8 8.00 ± 0% 8.00 ± 0% ~ (all equal) ScriptPushPop/16-8 32.0 ± 0% 32.0 ± 0% ~ (all equal) ScriptPushPop/128-8 259 ± 0% 259 ± 0% ~ (all equal) ScriptPushPop/1024-8 2.05k ± 0% 2.05k ± 0% ~ (all equal) --- pkg/vm/context.go | 2 +- pkg/vm/stack.go | 5 ++ pkg/vm/vm.go | 147 ++++++++++++++++++++++++---------------------- 3 files changed, 82 insertions(+), 72 deletions(-) diff --git a/pkg/vm/context.go b/pkg/vm/context.go index d00e091bc..1b0f1630b 100644 --- a/pkg/vm/context.go +++ b/pkg/vm/context.go @@ -294,6 +294,6 @@ func (v *VM) getContextScriptHash(n int) util.Uint160 { // invocation stack element number n. func (v *VM) PushContextScriptHash(n int) error { h := v.getContextScriptHash(n) - v.Estack().PushVal(h.BytesBE()) + v.Estack().PushItem(stackitem.NewByteArray(h.BytesBE())) return nil } diff --git a/pkg/vm/stack.go b/pkg/vm/stack.go index f94023f34..a435ccbf4 100644 --- a/pkg/vm/stack.go +++ b/pkg/vm/stack.go @@ -170,6 +170,11 @@ func (s *Stack) Push(e Element) { s.refs.Add(e.value) } +// PushItem pushed an Item to the stack. +func (s *Stack) PushItem(i stackitem.Item) { + s.Push(Element{i}) +} + // PushVal pushes the given value on the stack. It will infer the // underlying Item to its corresponding type. func (s *Stack) PushVal(v interface{}) { diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index eaa8b93eb..5b2d83254 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -286,7 +286,7 @@ func (v *VM) LoadScriptWithFlags(b []byte, f callflag.CallFlag) { ctx.callFlag = f ctx.static = newSlot(&v.refs) ctx.callingScriptHash = v.GetCurrentScriptHash() - v.istack.PushVal(ctx) + v.istack.PushItem(ctx) } // LoadScriptWithHash if similar to the LoadScriptWithFlags method, but it also loads @@ -540,7 +540,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro } if op <= opcode.PUSHINT256 { - v.estack.PushVal(bigint.FromBytes(parameter)) + v.estack.PushItem(stackitem.NewBigInteger(bigint.FromBytes(parameter))) return } @@ -551,26 +551,26 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro opcode.PUSH12, opcode.PUSH13, opcode.PUSH14, opcode.PUSH15, opcode.PUSH16: val := int(op) - int(opcode.PUSH0) - v.estack.PushVal(val) + v.estack.PushItem(stackitem.NewBigInteger(big.NewInt(int64(val)))) case opcode.PUSHDATA1, opcode.PUSHDATA2, opcode.PUSHDATA4: - v.estack.PushVal(parameter) + v.estack.PushItem(stackitem.NewByteArray(parameter)) case opcode.PUSHA: n := getJumpOffset(ctx, parameter) ptr := stackitem.NewPointerWithHash(n, ctx.prog, ctx.ScriptHash()) - v.estack.PushVal(ptr) + v.estack.PushItem(ptr) case opcode.PUSHNULL: - v.estack.PushVal(stackitem.Null{}) + v.estack.PushItem(stackitem.Null{}) case opcode.ISNULL: _, ok := v.estack.Pop().value.(stackitem.Null) - v.estack.PushVal(ok) + v.estack.PushItem(stackitem.Bool(ok)) case opcode.ISTYPE: res := v.estack.Pop().Item() - v.estack.PushVal(res.Type() == stackitem.Type(parameter[0])) + v.estack.PushItem(stackitem.Bool(res.Type() == stackitem.Type(parameter[0]))) case opcode.CONVERT: typ := stackitem.Type(parameter[0]) @@ -579,7 +579,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro if err != nil { panic(err) } - v.estack.PushVal(result) + v.estack.PushItem(result) case opcode.INITSSLOT: if parameter[0] == 0 { @@ -607,11 +607,11 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro case opcode.LDSFLD0, opcode.LDSFLD1, opcode.LDSFLD2, opcode.LDSFLD3, opcode.LDSFLD4, opcode.LDSFLD5, opcode.LDSFLD6: item := ctx.static.Get(int(op - opcode.LDSFLD0)) - v.estack.PushVal(item) + v.estack.PushItem(item) case opcode.LDSFLD: item := ctx.static.Get(int(parameter[0])) - v.estack.PushVal(item) + v.estack.PushItem(item) case opcode.STSFLD0, opcode.STSFLD1, opcode.STSFLD2, opcode.STSFLD3, opcode.STSFLD4, opcode.STSFLD5, opcode.STSFLD6: item := v.estack.Pop().Item() @@ -623,11 +623,11 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro case opcode.LDLOC0, opcode.LDLOC1, opcode.LDLOC2, opcode.LDLOC3, opcode.LDLOC4, opcode.LDLOC5, opcode.LDLOC6: item := ctx.local.Get(int(op - opcode.LDLOC0)) - v.estack.PushVal(item) + v.estack.PushItem(item) case opcode.LDLOC: item := ctx.local.Get(int(parameter[0])) - v.estack.PushVal(item) + v.estack.PushItem(item) case opcode.STLOC0, opcode.STLOC1, opcode.STLOC2, opcode.STLOC3, opcode.STLOC4, opcode.STLOC5, opcode.STLOC6: item := v.estack.Pop().Item() @@ -639,11 +639,11 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro case opcode.LDARG0, opcode.LDARG1, opcode.LDARG2, opcode.LDARG3, opcode.LDARG4, opcode.LDARG5, opcode.LDARG6: item := ctx.arguments.Get(int(op - opcode.LDARG0)) - v.estack.PushVal(item) + v.estack.PushItem(item) case opcode.LDARG: item := ctx.arguments.Get(int(parameter[0])) - v.estack.PushVal(item) + v.estack.PushItem(item) case opcode.STARG0, opcode.STARG1, opcode.STARG2, opcode.STARG3, opcode.STARG4, opcode.STARG5, opcode.STARG6: item := v.estack.Pop().Item() @@ -658,7 +658,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro if n < 0 || n > stackitem.MaxSize { panic("invalid size") } - v.estack.PushVal(stackitem.NewBuffer(make([]byte, n))) + v.estack.PushItem(stackitem.NewBuffer(make([]byte, n))) case opcode.MEMCPY: n := toInt(v.estack.Pop().BigInt()) @@ -693,7 +693,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro ab := make([]byte, l) copy(ab, a) copy(ab[len(a):], b) - v.estack.PushVal(stackitem.NewBuffer(ab)) + v.estack.PushItem(stackitem.NewBuffer(ab)) case opcode.SUBSTR: l := int(v.estack.Pop().BigInt().Int64()) @@ -711,7 +711,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro } res := make([]byte, l) copy(res, s[o:last]) - v.estack.PushVal(stackitem.NewBuffer(res)) + v.estack.PushItem(stackitem.NewBuffer(res)) case opcode.LEFT: l := int(v.estack.Pop().BigInt().Int64()) @@ -724,7 +724,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro } res := make([]byte, l) copy(res, s[:l]) - v.estack.PushVal(stackitem.NewBuffer(res)) + v.estack.PushItem(stackitem.NewBuffer(res)) case opcode.RIGHT: l := int(v.estack.Pop().BigInt().Int64()) @@ -734,10 +734,10 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro s := v.estack.Pop().Bytes() res := make([]byte, l) copy(res, s[len(s)-l:]) - v.estack.PushVal(stackitem.NewBuffer(res)) + v.estack.PushItem(stackitem.NewBuffer(res)) case opcode.DEPTH: - v.estack.PushVal(v.estack.Len()) + v.estack.PushItem(stackitem.NewBigInteger(big.NewInt(int64(v.estack.Len())))) case opcode.DROP: if v.estack.Len() < 1 { @@ -826,22 +826,22 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro // Bit operations. case opcode.INVERT: i := v.estack.Pop().BigInt() - v.estack.PushVal(new(big.Int).Not(i)) + v.estack.PushItem(stackitem.NewBigInteger(new(big.Int).Not(i))) case opcode.AND: b := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt() - v.estack.PushVal(new(big.Int).And(b, a)) + v.estack.PushItem(stackitem.NewBigInteger(new(big.Int).And(b, a))) case opcode.OR: b := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt() - v.estack.PushVal(new(big.Int).Or(b, a)) + v.estack.PushItem(stackitem.NewBigInteger(new(big.Int).Or(b, a))) case opcode.XOR: b := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt() - v.estack.PushVal(new(big.Int).Xor(b, a)) + v.estack.PushItem(stackitem.NewBigInteger(new(big.Int).Xor(b, a))) case opcode.EQUAL, opcode.NOTEQUAL: if v.estack.Len() < 2 { @@ -849,63 +849,64 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro } b := v.estack.Pop() a := v.estack.Pop() - v.estack.PushVal(a.value.Equals(b.value) == (op == opcode.EQUAL)) + res := stackitem.Bool(a.value.Equals(b.value) == (op == opcode.EQUAL)) + v.estack.PushItem(res) // Numeric operations. case opcode.SIGN: x := v.estack.Pop().BigInt() - v.estack.PushVal(x.Sign()) + v.estack.PushItem(stackitem.NewBigInteger(big.NewInt(int64(x.Sign())))) case opcode.ABS: x := v.estack.Pop().BigInt() - v.estack.PushVal(new(big.Int).Abs(x)) + v.estack.PushItem(stackitem.NewBigInteger(new(big.Int).Abs(x))) case opcode.NEGATE: x := v.estack.Pop().BigInt() - v.estack.PushVal(new(big.Int).Neg(x)) + v.estack.PushItem(stackitem.NewBigInteger(new(big.Int).Neg(x))) case opcode.INC: x := v.estack.Pop().BigInt() a := new(big.Int).Add(x, big.NewInt(1)) - v.estack.PushVal(a) + v.estack.PushItem(stackitem.NewBigInteger(a)) case opcode.DEC: x := v.estack.Pop().BigInt() a := new(big.Int).Sub(x, big.NewInt(1)) - v.estack.PushVal(a) + v.estack.PushItem(stackitem.NewBigInteger(a)) case opcode.ADD: a := v.estack.Pop().BigInt() b := v.estack.Pop().BigInt() c := new(big.Int).Add(a, b) - v.estack.PushVal(c) + v.estack.PushItem(stackitem.NewBigInteger(c)) case opcode.SUB: b := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt() c := new(big.Int).Sub(a, b) - v.estack.PushVal(c) + v.estack.PushItem(stackitem.NewBigInteger(c)) case opcode.MUL: a := v.estack.Pop().BigInt() b := v.estack.Pop().BigInt() c := new(big.Int).Mul(a, b) - v.estack.PushVal(c) + v.estack.PushItem(stackitem.NewBigInteger(c)) case opcode.DIV: b := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt() - v.estack.PushVal(new(big.Int).Quo(a, b)) + v.estack.PushItem(stackitem.NewBigInteger(new(big.Int).Quo(a, b))) case opcode.MOD: b := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt() - v.estack.PushVal(new(big.Int).Rem(a, b)) + v.estack.PushItem(stackitem.NewBigInteger(new(big.Int).Rem(a, b))) case opcode.POW: exp := v.estack.Pop().BigInt() @@ -913,7 +914,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro if ei := exp.Uint64(); !exp.IsUint64() || ei > maxSHLArg { panic("invalid exponent") } - v.estack.PushVal(new(big.Int).Exp(a, exp, nil)) + v.estack.PushItem(stackitem.NewBigInteger(new(big.Int).Exp(a, exp, nil))) case opcode.SQRT: a := v.estack.Pop().BigInt() @@ -921,7 +922,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro panic("negative value") } - v.estack.PushVal(new(big.Int).Sqrt(a)) + v.estack.PushItem(stackitem.NewBigInteger(new(big.Int).Sqrt(a))) case opcode.SHL, opcode.SHR: b := v.estack.Pop().BigInt().Int64() @@ -939,35 +940,35 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro item.Rsh(a, uint(b)) } - v.estack.PushVal(&item) + v.estack.PushItem(stackitem.NewBigInteger(&item)) case opcode.NOT: x := v.estack.Pop().Bool() - v.estack.PushVal(!x) + v.estack.PushItem(stackitem.Bool(!x)) case opcode.BOOLAND: b := v.estack.Pop().Bool() a := v.estack.Pop().Bool() - v.estack.PushVal(a && b) + v.estack.PushItem(stackitem.Bool(a && b)) case opcode.BOOLOR: b := v.estack.Pop().Bool() a := v.estack.Pop().Bool() - v.estack.PushVal(a || b) + v.estack.PushItem(stackitem.Bool(a || b)) case opcode.NZ: x := v.estack.Pop().BigInt() - v.estack.PushVal(x.Sign() != 0) + v.estack.PushItem(stackitem.Bool(x.Sign() != 0)) case opcode.NUMEQUAL: b := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt() - v.estack.PushVal(a.Cmp(b) == 0) + v.estack.PushItem(stackitem.Bool(a.Cmp(b) == 0)) case opcode.NUMNOTEQUAL: b := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt() - v.estack.PushVal(a.Cmp(b) != 0) + v.estack.PushItem(stackitem.Bool(a.Cmp(b) != 0)) case opcode.LT, opcode.LE, opcode.GT, opcode.GE: eb := v.estack.Pop() @@ -989,7 +990,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro res = cmp >= 0 } } - v.estack.PushVal(res) + v.estack.PushItem(stackitem.Bool(res)) case opcode.MIN: b := v.estack.Pop().BigInt() @@ -998,7 +999,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro if a.Cmp(b) == 1 { val = b } - v.estack.PushVal(val) + v.estack.PushItem(stackitem.NewBigInteger(val)) case opcode.MAX: b := v.estack.Pop().BigInt() @@ -1007,17 +1008,17 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro if a.Cmp(b) == -1 { val = b } - v.estack.PushVal(val) + v.estack.PushItem(stackitem.NewBigInteger(val)) case opcode.WITHIN: b := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt() x := v.estack.Pop().BigInt() - v.estack.PushVal(a.Cmp(x) <= 0 && x.Cmp(b) == -1) + v.estack.PushItem(stackitem.Bool(a.Cmp(x) <= 0 && x.Cmp(b) == -1)) // Object operations case opcode.NEWARRAY0: - v.estack.PushVal(stackitem.NewArray([]stackitem.Item{})) + v.estack.PushItem(stackitem.NewArray([]stackitem.Item{})) case opcode.NEWARRAY, opcode.NEWARRAYT, opcode.NEWSTRUCT: n := toInt(v.estack.Pop().BigInt()) @@ -1035,10 +1036,10 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro } else { res = stackitem.NewArray(items) } - v.estack.PushVal(res) + v.estack.PushItem(res) case opcode.NEWSTRUCT0: - v.estack.PushVal(stackitem.NewStruct([]stackitem.Item{})) + v.estack.PushItem(stackitem.NewStruct([]stackitem.Item{})) case opcode.APPEND: itemElem := v.estack.Pop() @@ -1068,15 +1069,15 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro items[i] = v.estack.Pop().value } - v.estack.PushVal(items) + v.estack.PushItem(stackitem.NewArray(items)) case opcode.UNPACK: a := v.estack.Pop().Array() l := len(a) for i := l - 1; i >= 0; i-- { - v.estack.PushVal(a[i]) + v.estack.PushItem(a[i]) } - v.estack.PushVal(l) + v.estack.PushItem(stackitem.NewBigInteger(big.NewInt(int64(l)))) case opcode.PICKITEM: key := v.estack.Pop() @@ -1093,20 +1094,20 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro panic("PICKITEM: invalid index") } item := arr[index].Dup() - v.estack.PushVal(item) + v.estack.PushItem(item) case *stackitem.Map: index := t.Index(key.Item()) if index < 0 { panic("invalid key") } - v.estack.Push(Element{value: t.Value().([]stackitem.MapElement)[index].Value.Dup()}) + v.estack.PushItem(t.Value().([]stackitem.MapElement)[index].Value.Dup()) default: arr := obj.Bytes() if index < 0 || index >= len(arr) { panic("PICKITEM: invalid index") } item := arr[index] - v.estack.PushVal(int(item)) + v.estack.PushItem(stackitem.NewBigInteger(big.NewInt(int64(item)))) } case opcode.SETITEM: @@ -1231,20 +1232,22 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro item.Remove(index) } v.refs.Remove(elem) - v.estack.PushVal(elem) + v.estack.PushItem(elem) case opcode.SIZE: elem := v.estack.Pop() + var res int // Cause there is no native (byte) item type here, hence we need to check // the type of the item for array size operations. switch t := elem.Value().(type) { case []stackitem.Item: - v.estack.PushVal(len(t)) + res = len(t) case []stackitem.MapElement: - v.estack.PushVal(len(t)) + res = len(t) default: - v.estack.PushVal(len(elem.Bytes())) + res = len(elem.Bytes()) } + v.estack.PushItem(stackitem.NewBigInteger(big.NewInt(int64(res)))) case opcode.JMP, opcode.JMPL, opcode.JMPIF, opcode.JMPIFL, opcode.JMPIFNOT, opcode.JMPIFNOTL, opcode.JMPEQ, opcode.JMPEQL, opcode.JMPNE, opcode.JMPNEL, @@ -1317,7 +1320,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro } case opcode.NEWMAP: - v.estack.Push(Element{value: stackitem.NewMap()}) + v.estack.PushItem(stackitem.NewMap()) case opcode.KEYS: if v.estack.Len() == 0 { @@ -1334,7 +1337,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro for k := range m.Value().([]stackitem.MapElement) { arr = append(arr, m.Value().([]stackitem.MapElement)[k].Key.Dup()) } - v.estack.PushVal(arr) + v.estack.PushItem(stackitem.NewArray(arr)) case opcode.VALUES: if v.estack.Len() == 0 { @@ -1359,7 +1362,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro panic("not a Map, Array or Struct") } - v.estack.PushVal(arr) + v.estack.PushItem(stackitem.NewArray(arr)) case opcode.HASKEY: if v.estack.Len() < 2 { @@ -1369,24 +1372,26 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro validateMapKey(key) c := v.estack.Pop() + var res bool switch t := c.value.(type) { case *stackitem.Array, *stackitem.Struct: index := key.BigInt().Int64() if index < 0 { panic("negative index") } - v.estack.PushVal(index < int64(len(c.Array()))) + res = index < int64(len(c.Array())) case *stackitem.Map: - v.estack.PushVal(t.Has(key.Item())) + res = t.Has(key.Item()) case *stackitem.Buffer: index := key.BigInt().Int64() if index < 0 { panic("negative index") } - v.estack.PushVal(index < int64(t.Len())) + res = index < int64(t.Len()) default: panic("wrong collection type") } + v.estack.PushItem(stackitem.Bool(res)) case opcode.NOP: // unlucky ^^ @@ -1417,7 +1422,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro fOffset = -1 } eCtx := newExceptionHandlingContext(cOffset, fOffset) - ctx.tryStack.PushVal(eCtx) + ctx.tryStack.PushItem(eCtx) case opcode.ENDTRY, opcode.ENDTRYL: eCtx := ctx.tryStack.Peek(0).Value().(*exceptionHandlingContext) @@ -1520,7 +1525,7 @@ func (v *VM) call(ctx *Context, offset int) { newCtx.arguments = nil initStack(&newCtx.tryStack, "exception", nil) newCtx.NEF = ctx.NEF - v.istack.PushVal(newCtx) + v.istack.PushItem(newCtx) v.Jump(newCtx, offset) } @@ -1575,7 +1580,7 @@ func (v *VM) handleException() { } if ectx.State == eTry && ectx.HasCatch() { ectx.State = eCatch - v.estack.PushVal(v.uncaughtException) + v.estack.PushItem(v.uncaughtException) v.uncaughtException = nil v.Jump(ictx, ectx.CatchOffset) } else { From b07347e602775aed96cec392cc736ab8438f52f0 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 30 Aug 2021 23:43:17 +0300 Subject: [PATCH 4/4] core: reuse PushItem for interops Probably less critical here, but still let's push things faster. --- pkg/core/interop/contract/call.go | 4 ++-- pkg/core/interop/crypto/ecdsa.go | 5 +++-- pkg/core/interop/iterator/interop.go | 4 ++-- pkg/core/interop/runtime/engine.go | 9 +++++---- pkg/core/interop/runtime/util.go | 13 +++++++------ pkg/core/interop/runtime/witness.go | 3 ++- pkg/core/interop_system.go | 19 ++++++++++--------- pkg/core/native/interop.go | 2 +- 8 files changed, 32 insertions(+), 27 deletions(-) diff --git a/pkg/core/interop/contract/call.go b/pkg/core/interop/contract/call.go index 3829ea290..c6f7c59c2 100644 --- a/pkg/core/interop/contract/call.go +++ b/pkg/core/interop/contract/call.go @@ -70,7 +70,7 @@ func Call(ic *interop.Context) error { } hasReturn := md.ReturnType != smartcontract.VoidType if !hasReturn { - ic.VM.Estack().PushVal(stackitem.Null{}) + ic.VM.Estack().PushItem(stackitem.Null{}) } return callInternal(ic, cs, method, fs, hasReturn, args) } @@ -116,7 +116,7 @@ func callExFromNative(ic *interop.Context, caller util.Uint160, cs *state.Contra ic.VM.LoadScriptWithCallingHash(caller, cs.NEF.Script, cs.Hash, ic.VM.Context().GetCallFlags()&f, hasReturn, uint16(len(args))) ic.VM.Context().NEF = &cs.NEF for i := len(args) - 1; i >= 0; i-- { - ic.VM.Estack().PushVal(args[i]) + ic.VM.Estack().PushItem(args[i]) } // use Jump not Call here because context was loaded in LoadScript above. ic.VM.Jump(ic.VM.Context(), md.Offset) diff --git a/pkg/core/interop/crypto/ecdsa.go b/pkg/core/interop/crypto/ecdsa.go index 14c93aed4..668bda363 100644 --- a/pkg/core/interop/crypto/ecdsa.go +++ b/pkg/core/interop/crypto/ecdsa.go @@ -10,6 +10,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/vm" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) // ECDSASecp256r1CheckMultisig checks multiple ECDSA signatures at once using @@ -32,7 +33,7 @@ func ECDSASecp256r1CheckMultisig(ic *interop.Context) error { return errors.New("more signatures than there are keys") } sigok := vm.CheckMultisigPar(ic.VM, elliptic.P256(), hash.NetSha256(ic.Network, ic.Container).BytesBE(), pkeys, sigs) - ic.VM.Estack().PushVal(sigok) + ic.VM.Estack().PushItem(stackitem.Bool(sigok)) return nil } @@ -45,6 +46,6 @@ func ECDSASecp256r1CheckSig(ic *interop.Context) error { return err } res := pkey.VerifyHashable(signature, ic.Network, ic.Container) - ic.VM.Estack().PushVal(res) + ic.VM.Estack().PushItem(stackitem.Bool(res)) return nil } diff --git a/pkg/core/interop/iterator/interop.go b/pkg/core/interop/iterator/interop.go index 6a72e8cf9..681d147a7 100644 --- a/pkg/core/interop/iterator/interop.go +++ b/pkg/core/interop/iterator/interop.go @@ -14,7 +14,7 @@ type iterator interface { func Next(ic *interop.Context) error { iop := ic.VM.Estack().Pop().Interop() arr := iop.Value().(iterator) - ic.VM.Estack().PushVal(arr.Next()) + ic.VM.Estack().PushItem(stackitem.Bool(arr.Next())) return nil } @@ -25,7 +25,7 @@ func Next(ic *interop.Context) error { func Value(ic *interop.Context) error { iop := ic.VM.Estack().Pop().Interop() arr := iop.Value().(iterator) - ic.VM.Estack().PushVal(arr.Value()) + ic.VM.Estack().PushItem(arr.Value()) return nil } diff --git a/pkg/core/interop/runtime/engine.go b/pkg/core/interop/runtime/engine.go index fe25c8b77..b80bb8a98 100644 --- a/pkg/core/interop/runtime/engine.go +++ b/pkg/core/interop/runtime/engine.go @@ -3,6 +3,7 @@ package runtime import ( "errors" "fmt" + "math/big" "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/state" @@ -28,7 +29,7 @@ func GetExecutingScriptHash(ic *interop.Context) error { // one native to another, no operations are performed on invocation stack. func GetCallingScriptHash(ic *interop.Context) error { h := ic.VM.GetCallingScriptHash() - ic.VM.Estack().PushVal(h.BytesBE()) + ic.VM.Estack().PushItem(stackitem.NewByteArray(h.BytesBE())) return nil } @@ -39,13 +40,13 @@ func GetEntryScriptHash(ic *interop.Context) error { // Platform returns the name of the platform. func Platform(ic *interop.Context) error { - ic.VM.Estack().PushVal([]byte("NEO")) + ic.VM.Estack().PushItem(stackitem.NewByteArray([]byte("NEO"))) return nil } // GetTrigger returns the script trigger. func GetTrigger(ic *interop.Context) error { - ic.VM.Estack().PushVal(byte(ic.Trigger)) + ic.VM.Estack().PushItem(stackitem.NewBigInteger(big.NewInt(int64(ic.Trigger)))) return nil } @@ -97,7 +98,7 @@ func Log(ic *interop.Context) error { // GetTime returns timestamp of the block being verified, or the latest // one in the blockchain if no block is given to Context. func GetTime(ic *interop.Context) error { - ic.VM.Estack().PushVal(ic.Block.Timestamp) + ic.VM.Estack().PushItem(stackitem.NewBigInteger(new(big.Int).SetUint64(ic.Block.Timestamp))) return nil } diff --git a/pkg/core/interop/runtime/util.go b/pkg/core/interop/runtime/util.go index 77f311061..d14684e66 100644 --- a/pkg/core/interop/runtime/util.go +++ b/pkg/core/interop/runtime/util.go @@ -3,6 +3,7 @@ package runtime import ( "encoding/binary" "errors" + "math/big" "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/state" @@ -16,9 +17,9 @@ import ( // GasLeft returns remaining amount of GAS. func GasLeft(ic *interop.Context) error { if ic.VM.GasLimit == -1 { - ic.VM.Estack().PushVal(ic.VM.GasLimit) + ic.VM.Estack().PushItem(stackitem.NewBigInteger(big.NewInt(ic.VM.GasLimit))) } else { - ic.VM.Estack().PushVal(ic.VM.GasLimit - ic.VM.GasConsumed()) + ic.VM.Estack().PushItem(stackitem.NewBigInteger(big.NewInt(ic.VM.GasLimit - ic.VM.GasConsumed()))) } return nil } @@ -55,7 +56,7 @@ func GetNotifications(ic *interop.Context) error { }) arr.Append(ev) } - ic.VM.Estack().PushVal(arr) + ic.VM.Estack().PushItem(arr) return nil } @@ -67,21 +68,21 @@ func GetInvocationCounter(ic *interop.Context) error { count = 1 ic.VM.Invocations[currentScriptHash] = count } - ic.VM.Estack().PushVal(count) + ic.VM.Estack().PushItem(stackitem.NewBigInteger(big.NewInt(int64(count)))) return nil } // GetNetwork returns chain network number. func GetNetwork(ic *interop.Context) error { m := ic.Chain.GetConfig().Magic - ic.VM.Estack().PushVal(uint32(m)) + ic.VM.Estack().PushItem(stackitem.NewBigInteger(big.NewInt(int64(m)))) return nil } // GetRandom returns pseudo-random number which depends on block nonce and transaction hash. func GetRandom(ic *interop.Context) error { res := murmur128(ic.NonceData[:], ic.Network) - ic.VM.Estack().PushVal(bigint.FromBytesUnsigned(res)) + ic.VM.Estack().PushItem(stackitem.NewBigInteger(bigint.FromBytesUnsigned(res))) copy(ic.NonceData[:], res) return nil } diff --git a/pkg/core/interop/runtime/witness.go b/pkg/core/interop/runtime/witness.go index 2eb5766af..e9e456d0f 100644 --- a/pkg/core/interop/runtime/witness.go +++ b/pkg/core/interop/runtime/witness.go @@ -11,6 +11,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) // CheckHashedWitness checks given hash against current list of script hashes @@ -97,6 +98,6 @@ func CheckWitness(ic *interop.Context) error { if err != nil { return fmt.Errorf("failed to check witness: %w", err) } - ic.VM.Estack().PushVal(res) + ic.VM.Estack().PushItem(stackitem.Bool(res)) return nil } diff --git a/pkg/core/interop_system.go b/pkg/core/interop_system.go index e83e3880f..a56ef18cd 100644 --- a/pkg/core/interop_system.go +++ b/pkg/core/interop_system.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "math" + "math/big" "sort" "github.com/nspcc-dev/neo-go/pkg/core/block" @@ -44,7 +45,7 @@ func engineGetScriptContainer(ic *interop.Context) error { default: return errors.New("unknown script container") } - ic.VM.Estack().PushVal(item) + ic.VM.Estack().PushItem(item) return nil } @@ -72,9 +73,9 @@ func storageGet(ic *interop.Context) error { key := ic.VM.Estack().Pop().Bytes() si := ic.DAO.GetStorageItem(stc.ID, key) if si != nil { - ic.VM.Estack().PushVal([]byte(si)) + ic.VM.Estack().PushItem(stackitem.NewByteArray([]byte(si))) } else { - ic.VM.Estack().PushVal(stackitem.Null{}) + ic.VM.Estack().PushItem(stackitem.Null{}) } return nil } @@ -100,7 +101,7 @@ func storageGetContextInternal(ic *interop.Context, isReadOnly bool) error { ID: contract.ID, ReadOnly: isReadOnly, } - ic.VM.Estack().PushVal(stackitem.NewInterop(sc)) + ic.VM.Estack().PushItem(stackitem.NewInterop(sc)) return nil } @@ -157,7 +158,7 @@ func storageContextAsReadOnly(ic *interop.Context) error { } stc = stx } - ic.VM.Estack().PushVal(stackitem.NewInterop(stc)) + ic.VM.Estack().PushItem(stackitem.NewInterop(stc)) return nil } @@ -210,7 +211,7 @@ func storageFind(ic *interop.Context) error { filteredMap := stackitem.NewMapWithValue(arr) item := istorage.NewIterator(filteredMap, len(prefix), opts) - ic.VM.Estack().PushVal(stackitem.NewInterop(item)) + ic.VM.Estack().PushItem(stackitem.NewInterop(item)) return nil } @@ -236,7 +237,7 @@ func contractCreateMultisigAccount(ic *interop.Context) error { if err != nil { return err } - ic.VM.Estack().PushVal(hash.Hash160(script).BytesBE()) + ic.VM.Estack().PushItem(stackitem.NewByteArray(hash.Hash160(script).BytesBE())) return nil } @@ -247,12 +248,12 @@ func contractCreateStandardAccount(ic *interop.Context) error { if err != nil { return err } - ic.VM.Estack().PushVal(p.GetScriptHash().BytesBE()) + ic.VM.Estack().PushItem(stackitem.NewByteArray(p.GetScriptHash().BytesBE())) return nil } // contractGetCallFlags returns current context calling flags. func contractGetCallFlags(ic *interop.Context) error { - ic.VM.Estack().PushVal(ic.VM.Context().GetCallFlags()) + ic.VM.Estack().PushItem(stackitem.NewBigInteger(big.NewInt(int64(ic.VM.Context().GetCallFlags())))) return nil } diff --git a/pkg/core/native/interop.go b/pkg/core/native/interop.go index c112ba539..cf12a419b 100644 --- a/pkg/core/native/interop.go +++ b/pkg/core/native/interop.go @@ -53,7 +53,7 @@ func Call(ic *interop.Context) error { } result := m.Func(ic, args) if m.MD.ReturnType != smartcontract.VoidType { - ctx.Estack().PushVal(result) + ctx.Estack().PushItem(result) } return nil }