Merge pull request #2140 from nspcc-dev/vm-optimize-stack
Optimize VM stack
This commit is contained in:
commit
40c6c065d2
10 changed files with 252 additions and 281 deletions
|
@ -1917,8 +1917,9 @@ func (bc *Blockchain) verifyHashAgainstScript(hash util.Uint160, witness *transa
|
|||
if vm.HasFailed() {
|
||||
return 0, fmt.Errorf("%w: vm execution has failed: %v", ErrVerificationFailed, err)
|
||||
}
|
||||
resEl := vm.Estack().Pop()
|
||||
if resEl != nil {
|
||||
estack := vm.Estack()
|
||||
if estack.Len() > 0 {
|
||||
resEl := estack.Pop()
|
||||
res, err := resEl.Item().TryBool()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("%w: invalid return value", ErrVerificationFailed)
|
||||
|
|
50
pkg/vm/bench_test.go
Normal file
50
pkg/vm/bench_test.go
Normal file
|
@ -0,0 +1,50 @@
|
|||
package vm
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func benchScript(t *testing.B, script []byte) {
|
||||
for n := 0; n < t.N; n++ {
|
||||
t.StopTimer()
|
||||
vm := load(script)
|
||||
t.StartTimer()
|
||||
err := vm.Run()
|
||||
t.StopTimer()
|
||||
require.NoError(t, err)
|
||||
t.StartTimer()
|
||||
}
|
||||
}
|
||||
|
||||
// Shared as is by @ixje once upon a time (compiled from Python).
|
||||
func BenchmarkScriptFibonacci(t *testing.B) {
|
||||
var script = []byte{87, 5, 0, 16, 112, 17, 113, 105, 104, 18, 192, 114, 16, 115, 34, 28, 104, 105, 158, 116, 106, 108, 75,
|
||||
217, 48, 38, 5, 139, 34, 5, 207, 34, 3, 114, 105, 112, 108, 113, 107, 17, 158, 115, 107, 12, 2, 94, 1,
|
||||
219, 33, 181, 36, 222, 106, 64}
|
||||
benchScript(t, script)
|
||||
}
|
||||
|
||||
func BenchmarkScriptNestedRefCount(t *testing.B) {
|
||||
b64script := "whBNEcARTRHAVgEB/gGdYBFNEU0SwFMSwFhKJPNFUUVFRQ=="
|
||||
script, err := base64.StdEncoding.DecodeString(b64script)
|
||||
require.NoError(t, err)
|
||||
benchScript(t, script)
|
||||
}
|
||||
|
||||
func BenchmarkScriptPushPop(t *testing.B) {
|
||||
for _, i := range []int{4, 16, 128, 1024} {
|
||||
t.Run(strconv.Itoa(i), func(t *testing.B) {
|
||||
var script = make([]byte, i*2)
|
||||
for p := 0; p < i; p++ {
|
||||
script[p] = byte(opcode.PUSH1)
|
||||
script[i+p] = byte(opcode.DROP)
|
||||
}
|
||||
benchScript(t, script)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -35,8 +35,8 @@ type Context struct {
|
|||
local *Slot
|
||||
arguments *Slot
|
||||
|
||||
// Exception context stack pointer.
|
||||
tryStack *Stack
|
||||
// Exception context stack.
|
||||
tryStack Stack
|
||||
|
||||
// Script hash of the prog.
|
||||
scriptHash util.Uint160
|
||||
|
@ -282,10 +282,11 @@ func (c *Context) IsDeployed() bool {
|
|||
// getContextScriptHash returns script hash of the invocation stack element
|
||||
// number n.
|
||||
func (v *VM) getContextScriptHash(n int) util.Uint160 {
|
||||
element := v.Istack().Peek(n)
|
||||
if element == nil {
|
||||
istack := v.Istack()
|
||||
if istack.Len() <= n {
|
||||
return util.Uint160{}
|
||||
}
|
||||
element := istack.Peek(n)
|
||||
ctxIface := element.Value()
|
||||
ctx := ctxIface.(*Context)
|
||||
return ctx.ScriptHash()
|
||||
|
|
|
@ -20,7 +20,7 @@ func TestVM_Debug(t *testing.T) {
|
|||
require.NoError(t, v.Run())
|
||||
require.Equal(t, 5, v.Context().NextIP())
|
||||
require.NoError(t, v.Run())
|
||||
require.Equal(t, 1, v.estack.len)
|
||||
require.Equal(t, 1, v.estack.Len())
|
||||
require.Equal(t, big.NewInt(5), v.estack.Top().Value())
|
||||
})
|
||||
t.Run("StepInto", func(t *testing.T) {
|
||||
|
@ -29,14 +29,14 @@ func TestVM_Debug(t *testing.T) {
|
|||
require.Equal(t, 3, v.Context().NextIP())
|
||||
require.NoError(t, v.StepOut())
|
||||
require.Equal(t, 2, v.Context().NextIP())
|
||||
require.Equal(t, 1, v.estack.len)
|
||||
require.Equal(t, 1, v.estack.Len())
|
||||
require.Equal(t, big.NewInt(5), v.estack.Top().Value())
|
||||
})
|
||||
t.Run("StepOver", func(t *testing.T) {
|
||||
v := load(prog)
|
||||
require.NoError(t, v.StepOver())
|
||||
require.Equal(t, 2, v.Context().NextIP())
|
||||
require.Equal(t, 1, v.estack.len)
|
||||
require.Equal(t, 1, v.estack.Len())
|
||||
require.Equal(t, big.NewInt(5), v.estack.Top().Value())
|
||||
})
|
||||
}
|
||||
|
|
268
pkg/vm/stack.go
268
pkg/vm/stack.go
|
@ -9,70 +9,36 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
)
|
||||
|
||||
// Stack implementation for the neo-go virtual machine. The stack implements
|
||||
// a double linked list where its semantics are first in first out.
|
||||
// To simplify the implementation, internally a Stack s is implemented as a
|
||||
// ring, such that &s.top is both the next element of the last element s.Back()
|
||||
// and the previous element of the first element s.Top().
|
||||
//
|
||||
// s.Push(0)
|
||||
// s.Push(1)
|
||||
// s.Push(2)
|
||||
//
|
||||
// [ 2 ] > top
|
||||
// [ 1 ]
|
||||
// [ 0 ] > back
|
||||
//
|
||||
// s.Pop() > 2
|
||||
//
|
||||
// [ 1 ]
|
||||
// [ 0 ]
|
||||
// Stack implementation for the neo-go virtual machine. The stack with its LIFO
|
||||
// semantics is emulated from simple slice where the top of the stack corresponds
|
||||
// to the latest element of this slice. Pushes are appends to this slice, pops are
|
||||
// slice resizes.
|
||||
|
||||
// Element represents an element in the double linked list (the stack),
|
||||
// which will hold the underlying stackitem.Item.
|
||||
// Element represents an element on the stack, technically it's a wrapper around
|
||||
// stackitem.Item interface to provide some API simplification for VM.
|
||||
type Element struct {
|
||||
value stackitem.Item
|
||||
next, prev *Element
|
||||
stack *Stack
|
||||
}
|
||||
|
||||
// NewElement returns a new Element object, with its underlying value inferred
|
||||
// to the corresponding type.
|
||||
func NewElement(v interface{}) *Element {
|
||||
return &Element{
|
||||
value: stackitem.Make(v),
|
||||
}
|
||||
}
|
||||
|
||||
// Next returns the next element in the stack.
|
||||
func (e *Element) Next() *Element {
|
||||
if elem := e.next; e.stack != nil && elem != &e.stack.top {
|
||||
return elem
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Prev returns the previous element in the stack.
|
||||
func (e *Element) Prev() *Element {
|
||||
if elem := e.prev; e.stack != nil && elem != &e.stack.top {
|
||||
return elem
|
||||
}
|
||||
return nil
|
||||
func NewElement(v interface{}) Element {
|
||||
return Element{stackitem.Make(v)}
|
||||
}
|
||||
|
||||
// Item returns Item contained in the element.
|
||||
func (e *Element) Item() stackitem.Item {
|
||||
func (e Element) Item() stackitem.Item {
|
||||
return e.value
|
||||
}
|
||||
|
||||
// Value returns value of the Item contained in the element.
|
||||
func (e *Element) Value() interface{} {
|
||||
func (e Element) Value() interface{} {
|
||||
return e.value.Value()
|
||||
}
|
||||
|
||||
// BigInt attempts to get the underlying value of the element as a big integer.
|
||||
// Will panic if the assertion failed which will be caught by the VM.
|
||||
func (e *Element) BigInt() *big.Int {
|
||||
func (e Element) BigInt() *big.Int {
|
||||
val, err := e.value.TryInteger()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -82,7 +48,7 @@ func (e *Element) BigInt() *big.Int {
|
|||
|
||||
// Bool converts an underlying value of the element to a boolean if it's
|
||||
// possible to do so, it will panic otherwise.
|
||||
func (e *Element) Bool() bool {
|
||||
func (e Element) Bool() bool {
|
||||
b, err := e.value.TryBool()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -92,7 +58,7 @@ func (e *Element) Bool() bool {
|
|||
|
||||
// Bytes attempts to get the underlying value of the element as a byte array.
|
||||
// Will panic if the assertion failed which will be caught by the VM.
|
||||
func (e *Element) Bytes() []byte {
|
||||
func (e Element) Bytes() []byte {
|
||||
bs, err := e.value.TryBytes()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -102,7 +68,7 @@ func (e *Element) Bytes() []byte {
|
|||
|
||||
// BytesOrNil attempts to get the underlying value of the element as a byte array or nil.
|
||||
// Will panic if the assertion failed which will be caught by the VM.
|
||||
func (e *Element) BytesOrNil() []byte {
|
||||
func (e Element) BytesOrNil() []byte {
|
||||
if _, ok := e.value.(stackitem.Null); ok {
|
||||
return nil
|
||||
}
|
||||
|
@ -115,7 +81,7 @@ func (e *Element) BytesOrNil() []byte {
|
|||
|
||||
// String attempts to get string from the element value.
|
||||
// It is assumed to be use in interops and panics if string is not a valid UTF-8 byte sequence.
|
||||
func (e *Element) String() string {
|
||||
func (e Element) String() string {
|
||||
s, err := stackitem.ToString(e.value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -126,7 +92,7 @@ func (e *Element) String() string {
|
|||
// Array attempts to get the underlying value of the element as an array of
|
||||
// other items. Will panic if the item type is different which will be caught
|
||||
// by the VM.
|
||||
func (e *Element) Array() []stackitem.Item {
|
||||
func (e Element) Array() []stackitem.Item {
|
||||
switch t := e.value.(type) {
|
||||
case *stackitem.Array:
|
||||
return t.Value().([]stackitem.Item)
|
||||
|
@ -139,7 +105,7 @@ func (e *Element) Array() []stackitem.Item {
|
|||
|
||||
// Interop attempts to get the underlying value of the element
|
||||
// as an interop item.
|
||||
func (e *Element) Interop() *stackitem.Interop {
|
||||
func (e Element) Interop() *stackitem.Interop {
|
||||
switch t := e.value.(type) {
|
||||
case *stackitem.Interop:
|
||||
return t
|
||||
|
@ -148,11 +114,10 @@ func (e *Element) Interop() *stackitem.Interop {
|
|||
}
|
||||
}
|
||||
|
||||
// Stack represents a Stack backed by a double linked list.
|
||||
// Stack represents a Stack backed by a slice of Elements.
|
||||
type Stack struct {
|
||||
top Element
|
||||
elems []Element
|
||||
name string
|
||||
len int
|
||||
refs *refCounter
|
||||
}
|
||||
|
||||
|
@ -163,63 +128,46 @@ func NewStack(n string) *Stack {
|
|||
|
||||
func newStack(n string, refc *refCounter) *Stack {
|
||||
s := new(Stack)
|
||||
s.elems = make([]Element, 0, 16) // Most of uses are expected to fit into 16 elements.
|
||||
initStack(s, n, refc)
|
||||
return s
|
||||
}
|
||||
func initStack(s *Stack, n string, refc *refCounter) {
|
||||
s.name = n
|
||||
s.refs = refc
|
||||
s.top.next = &s.top
|
||||
s.top.prev = &s.top
|
||||
s.Clear()
|
||||
}
|
||||
|
||||
// Clear clears all elements on the stack and set its length to 0.
|
||||
func (s *Stack) Clear() {
|
||||
s.top.next = &s.top
|
||||
s.top.prev = &s.top
|
||||
s.len = 0
|
||||
if s.elems != nil {
|
||||
for _, el := range s.elems {
|
||||
s.refs.Remove(el.value)
|
||||
}
|
||||
s.elems = s.elems[:0]
|
||||
}
|
||||
}
|
||||
|
||||
// Len returns the number of elements that are on the stack.
|
||||
func (s *Stack) Len() int {
|
||||
return s.len
|
||||
}
|
||||
|
||||
// insert inserts the element after element (at) on the stack.
|
||||
func (s *Stack) insert(e, at *Element) *Element {
|
||||
// If we insert an element that is already popped from this stack,
|
||||
// we need to clean it up, there are still pointers referencing to it.
|
||||
if e.stack == s {
|
||||
e = NewElement(e.value)
|
||||
}
|
||||
|
||||
n := at.next
|
||||
at.next = e
|
||||
e.prev = at
|
||||
e.next = n
|
||||
n.prev = e
|
||||
e.stack = s
|
||||
s.len++
|
||||
|
||||
s.refs.Add(e.value)
|
||||
|
||||
return e
|
||||
return len(s.elems)
|
||||
}
|
||||
|
||||
// InsertAt inserts the given item (n) deep on the stack.
|
||||
// Be very careful using it and _always_ check both e and n before invocation
|
||||
// as it will silently do wrong things otherwise.
|
||||
func (s *Stack) InsertAt(e *Element, n int) *Element {
|
||||
before := s.Peek(n - 1)
|
||||
if before == nil {
|
||||
return nil
|
||||
}
|
||||
return s.insert(e, before)
|
||||
// Be very careful using it and _always_ check n before invocation
|
||||
// as it will panic otherwise.
|
||||
func (s *Stack) InsertAt(e Element, n int) {
|
||||
l := len(s.elems)
|
||||
s.elems = append(s.elems, e)
|
||||
copy(s.elems[l-n+1:], s.elems[l-n:l])
|
||||
s.elems[l-n] = e
|
||||
s.refs.Add(e.value)
|
||||
}
|
||||
|
||||
// Push pushes the given element on the stack.
|
||||
func (s *Stack) Push(e *Element) {
|
||||
s.insert(e, &s.top)
|
||||
func (s *Stack) Push(e Element) {
|
||||
s.elems = append(s.elems, e)
|
||||
s.refs.Add(e.value)
|
||||
}
|
||||
|
||||
// PushVal pushes the given value on the stack. It will infer the
|
||||
|
@ -228,63 +176,49 @@ func (s *Stack) PushVal(v interface{}) {
|
|||
s.Push(NewElement(v))
|
||||
}
|
||||
|
||||
// Pop removes and returns the element on top of the stack.
|
||||
func (s *Stack) Pop() *Element {
|
||||
return s.Remove(s.Top())
|
||||
// Pop removes and returns the element on top of the stack. Panics if stack is
|
||||
// empty.
|
||||
func (s *Stack) Pop() Element {
|
||||
l := len(s.elems)
|
||||
e := s.elems[l-1]
|
||||
s.elems = s.elems[:l-1]
|
||||
s.refs.Remove(e.value)
|
||||
return e
|
||||
}
|
||||
|
||||
// Top returns the element on top of the stack. Nil if the stack
|
||||
// is empty.
|
||||
func (s *Stack) Top() *Element {
|
||||
if s.len == 0 {
|
||||
return nil
|
||||
func (s *Stack) Top() Element {
|
||||
if len(s.elems) == 0 {
|
||||
return Element{}
|
||||
}
|
||||
return s.top.next
|
||||
return s.elems[len(s.elems)-1]
|
||||
}
|
||||
|
||||
// Back returns the element at the end of the stack. Nil if the stack
|
||||
// is empty.
|
||||
func (s *Stack) Back() *Element {
|
||||
if s.len == 0 {
|
||||
return nil
|
||||
func (s *Stack) Back() Element {
|
||||
if len(s.elems) == 0 {
|
||||
return Element{}
|
||||
}
|
||||
return s.top.prev
|
||||
return s.elems[0]
|
||||
}
|
||||
|
||||
// Peek returns the element (n) far in the stack beginning from
|
||||
// the top of the stack.
|
||||
// n = 0 => will return the element on top of the stack.
|
||||
func (s *Stack) Peek(n int) *Element {
|
||||
i := 0
|
||||
for e := s.Top(); e != nil; e = e.Next() {
|
||||
if n == i {
|
||||
return e
|
||||
}
|
||||
i++
|
||||
}
|
||||
return nil
|
||||
// the top of the stack. For n == 0 it's effectively the same as Top,
|
||||
// but it'll panic if the stack is empty.
|
||||
func (s *Stack) Peek(n int) Element {
|
||||
n = len(s.elems) - n - 1
|
||||
return s.elems[n]
|
||||
}
|
||||
|
||||
// RemoveAt removes the element (n) deep on the stack beginning
|
||||
// from the top of the stack.
|
||||
func (s *Stack) RemoveAt(n int) *Element {
|
||||
return s.Remove(s.Peek(n))
|
||||
}
|
||||
|
||||
// Remove removes and returns the given element from the stack.
|
||||
func (s *Stack) Remove(e *Element) *Element {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
e.prev.next = e.next
|
||||
e.next.prev = e.prev
|
||||
e.next = nil // avoid memory leaks.
|
||||
e.prev = nil // avoid memory leaks.
|
||||
e.stack = nil
|
||||
s.len--
|
||||
|
||||
// from the top of the stack. Panics if called with out of bounds n.
|
||||
func (s *Stack) RemoveAt(n int) Element {
|
||||
l := len(s.elems)
|
||||
e := s.elems[l-1-n]
|
||||
s.elems = append(s.elems[:l-1-n], s.elems[l-n:]...)
|
||||
s.refs.Remove(e.value)
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
|
@ -292,15 +226,9 @@ func (s *Stack) Remove(e *Element) *Element {
|
|||
// Dup is used for copying elements on to the top of its own stack.
|
||||
// s.Push(s.Peek(0)) // will result in unexpected behaviour.
|
||||
// s.Push(s.Dup(0)) // is the correct approach.
|
||||
func (s *Stack) Dup(n int) *Element {
|
||||
func (s *Stack) Dup(n int) Element {
|
||||
e := s.Peek(n)
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &Element{
|
||||
value: e.value.Dup(),
|
||||
}
|
||||
return Element{e.value.Dup()}
|
||||
}
|
||||
|
||||
// Iter iterates over all the elements int the stack, starting from the top
|
||||
|
@ -308,9 +236,9 @@ func (s *Stack) Dup(n int) *Element {
|
|||
// s.Iter(func(elem *Element) {
|
||||
// // do something with the element.
|
||||
// })
|
||||
func (s *Stack) Iter(f func(*Element)) {
|
||||
for e := s.Top(); e != nil; e = e.Next() {
|
||||
f(e)
|
||||
func (s *Stack) Iter(f func(Element)) {
|
||||
for i := len(s.elems) - 1; i >= 0; i-- {
|
||||
f(s.elems[i])
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -319,9 +247,9 @@ func (s *Stack) Iter(f func(*Element)) {
|
|||
// s.IterBack(func(elem *Element) {
|
||||
// // do something with the element.
|
||||
// })
|
||||
func (s *Stack) IterBack(f func(*Element)) {
|
||||
for e := s.Back(); e != nil; e = e.Prev() {
|
||||
f(e)
|
||||
func (s *Stack) IterBack(f func(Element)) {
|
||||
for i := 0; i < len(s.elems); i++ {
|
||||
f(s.elems[i])
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -330,37 +258,27 @@ func (s *Stack) Swap(n1, n2 int) error {
|
|||
if n1 < 0 || n2 < 0 {
|
||||
return errors.New("negative index")
|
||||
}
|
||||
if n1 >= s.len || n2 >= s.len {
|
||||
l := len(s.elems)
|
||||
if n1 >= l || n2 >= l {
|
||||
return errors.New("too big index")
|
||||
}
|
||||
if n1 == n2 {
|
||||
s.elems[l-n1-1], s.elems[l-n2-1] = s.elems[l-n2-1], s.elems[l-n1-1]
|
||||
return nil
|
||||
}
|
||||
s.swap(n1, n2)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Stack) swap(n1, n2 int) {
|
||||
a := s.Peek(n1)
|
||||
b := s.Peek(n2)
|
||||
a.value, b.value = b.value, a.value
|
||||
}
|
||||
|
||||
// ReverseTop reverses top n items of the stack.
|
||||
func (s *Stack) ReverseTop(n int) error {
|
||||
l := len(s.elems)
|
||||
if n < 0 {
|
||||
return errors.New("negative index")
|
||||
} else if n > s.len {
|
||||
} else if n > l {
|
||||
return errors.New("too big index")
|
||||
} else if n <= 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
a, b := s.Peek(0), s.Peek(n-1)
|
||||
for i := 0; i < n/2; i++ {
|
||||
a.value, b.value = b.value, a.value
|
||||
a = a.Next()
|
||||
b = b.Prev()
|
||||
for i, j := l-n, l-1; i <= j; i, j = i+1, j-1 {
|
||||
s.elems[i], s.elems[j] = s.elems[j], s.elems[i]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -372,24 +290,16 @@ func (s *Stack) Roll(n int) error {
|
|||
if n < 0 {
|
||||
return errors.New("negative index")
|
||||
}
|
||||
if n >= s.len {
|
||||
l := len(s.elems)
|
||||
if n >= l {
|
||||
return errors.New("too big index")
|
||||
}
|
||||
if n == 0 {
|
||||
return nil
|
||||
}
|
||||
top := s.Peek(0)
|
||||
e := s.Peek(n)
|
||||
|
||||
e.prev.next = e.next
|
||||
e.next.prev = e.prev
|
||||
|
||||
top.prev = e
|
||||
e.next = top
|
||||
|
||||
e.prev = &s.top
|
||||
s.top.next = e
|
||||
|
||||
e := s.elems[l-1-n]
|
||||
copy(s.elems[l-1-n:], s.elems[l-n:])
|
||||
s.elems[l-1] = e
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -398,10 +308,10 @@ func (s *Stack) Roll(n int) error {
|
|||
func (s *Stack) PopSigElements() ([][]byte, error) {
|
||||
var num int
|
||||
var elems [][]byte
|
||||
item := s.Pop()
|
||||
if item == nil {
|
||||
if s.Len() == 0 {
|
||||
return nil, fmt.Errorf("nothing on the stack")
|
||||
}
|
||||
item := s.Pop()
|
||||
switch item.value.(type) {
|
||||
case *stackitem.Array:
|
||||
num = len(item.Array())
|
||||
|
@ -431,8 +341,8 @@ func (s *Stack) PopSigElements() ([][]byte, error) {
|
|||
|
||||
// ToArray converts stack to an array of stackitems with top item being the last.
|
||||
func (s *Stack) ToArray() []stackitem.Item {
|
||||
items := make([]stackitem.Item, 0, s.len)
|
||||
s.IterBack(func(e *Element) {
|
||||
items := make([]stackitem.Item, 0, len(s.elems))
|
||||
s.IterBack(func(e Element) {
|
||||
items = append(items, e.Item())
|
||||
})
|
||||
return items
|
||||
|
|
|
@ -76,9 +76,6 @@ func TestRemoveAt(t *testing.T) {
|
|||
|
||||
elem := s.RemoveAt(8)
|
||||
assert.Equal(t, elems[1], elem)
|
||||
assert.Nil(t, elem.prev)
|
||||
assert.Nil(t, elem.next)
|
||||
assert.Nil(t, elem.stack)
|
||||
|
||||
// Test if the pointers are moved.
|
||||
assert.Equal(t, elems[0], s.Peek(8))
|
||||
|
@ -147,8 +144,6 @@ func TestRemoveLastElement(t *testing.T) {
|
|||
}
|
||||
elem := s.RemoveAt(1)
|
||||
assert.Equal(t, elems[0], elem)
|
||||
assert.Nil(t, elem.prev)
|
||||
assert.Nil(t, elem.next)
|
||||
assert.Equal(t, 1, s.Len())
|
||||
}
|
||||
|
||||
|
@ -163,7 +158,7 @@ func TestIterAfterRemove(t *testing.T) {
|
|||
s.RemoveAt(0)
|
||||
|
||||
i := 0
|
||||
s.Iter(func(elem *Element) {
|
||||
s.Iter(func(_ Element) {
|
||||
i++
|
||||
})
|
||||
assert.Equal(t, len(elems)-1, i)
|
||||
|
@ -180,15 +175,16 @@ func TestIteration(t *testing.T) {
|
|||
}
|
||||
assert.Equal(t, len(elems), s.Len())
|
||||
|
||||
iteratedElems := make([]*Element, 0)
|
||||
iteratedElems := make([]Element, 0)
|
||||
|
||||
s.Iter(func(elem *Element) {
|
||||
s.Iter(func(elem Element) {
|
||||
iteratedElems = append(iteratedElems, elem)
|
||||
})
|
||||
|
||||
// Top to bottom order of iteration.
|
||||
poppedElems := make([]*Element, 0)
|
||||
for elem := s.Pop(); elem != nil; elem = s.Pop() {
|
||||
poppedElems = append(poppedElems, elem)
|
||||
poppedElems := make([]Element, 0)
|
||||
for s.Len() != 0 {
|
||||
poppedElems = append(poppedElems, s.Pop())
|
||||
}
|
||||
assert.Equal(t, poppedElems, iteratedElems)
|
||||
}
|
||||
|
@ -204,9 +200,9 @@ func TestBackIteration(t *testing.T) {
|
|||
}
|
||||
assert.Equal(t, len(elems), s.Len())
|
||||
|
||||
iteratedElems := make([]*Element, 0)
|
||||
iteratedElems := make([]Element, 0)
|
||||
|
||||
s.IterBack(func(elem *Element) {
|
||||
s.IterBack(func(elem Element) {
|
||||
iteratedElems = append(iteratedElems, elem)
|
||||
})
|
||||
// Bottom to the top order of iteration.
|
||||
|
@ -331,6 +327,25 @@ func TestRoll(t *testing.T) {
|
|||
assert.Equal(t, int64(1), s.Pop().BigInt().Int64())
|
||||
}
|
||||
|
||||
func TestInsertAt(t *testing.T) {
|
||||
s := NewStack("stack")
|
||||
s.PushVal(1)
|
||||
s.PushVal(2)
|
||||
s.PushVal(3)
|
||||
s.PushVal(4)
|
||||
s.PushVal(5)
|
||||
|
||||
e := s.Dup(1) // it's `4`
|
||||
s.InsertAt(e, 3)
|
||||
|
||||
assert.Equal(t, int64(5), s.Peek(0).BigInt().Int64())
|
||||
assert.Equal(t, int64(4), s.Peek(1).BigInt().Int64())
|
||||
assert.Equal(t, int64(3), s.Peek(2).BigInt().Int64())
|
||||
assert.Equal(t, int64(4), s.Peek(3).BigInt().Int64())
|
||||
assert.Equal(t, int64(2), s.Peek(4).BigInt().Int64())
|
||||
assert.Equal(t, int64(1), s.Peek(5).BigInt().Int64())
|
||||
}
|
||||
|
||||
func TestPopSigElements(t *testing.T) {
|
||||
s := NewStack("test")
|
||||
|
||||
|
@ -369,8 +384,8 @@ func TestPopSigElements(t *testing.T) {
|
|||
assert.Equal(t, z, [][]byte{b1, b2})
|
||||
}
|
||||
|
||||
func makeElements(n int) []*Element {
|
||||
elems := make([]*Element, n)
|
||||
func makeElements(n int) []Element {
|
||||
elems := make([]Element, n)
|
||||
for i := 0; i < n; i++ {
|
||||
elems[i] = NewElement(i)
|
||||
}
|
||||
|
|
|
@ -95,14 +95,12 @@ func (w *serContext) serialize(item Item) error {
|
|||
switch t := item.(type) {
|
||||
case *ByteArray:
|
||||
w.data = append(w.data, byte(ByteArrayT))
|
||||
data := t.Value().([]byte)
|
||||
w.appendVarUint(uint64(len(data)))
|
||||
w.data = append(w.data, data...)
|
||||
w.appendVarUint(uint64(len(*t)))
|
||||
w.data = append(w.data, *t...)
|
||||
case *Buffer:
|
||||
w.data = append(w.data, byte(BufferT))
|
||||
data := t.Value().([]byte)
|
||||
w.appendVarUint(uint64(len(data)))
|
||||
w.data = append(w.data, data...)
|
||||
w.appendVarUint(uint64(len(*t)))
|
||||
w.data = append(w.data, *t...)
|
||||
case Bool:
|
||||
w.data = append(w.data, byte(BooleanT))
|
||||
if t {
|
||||
|
@ -112,10 +110,9 @@ func (w *serContext) serialize(item Item) error {
|
|||
}
|
||||
case *BigInteger:
|
||||
w.data = append(w.data, byte(IntegerT))
|
||||
v := t.Value().(*big.Int)
|
||||
ln := len(w.data)
|
||||
w.data = append(w.data, 0)
|
||||
data := bigint.ToPreallocatedBytes(v, w.data[len(w.data):])
|
||||
data := bigint.ToPreallocatedBytes((*big.Int)(t), w.data[len(w.data):])
|
||||
w.data[ln] = byte(len(data))
|
||||
w.data = append(w.data, data...)
|
||||
case *Interop:
|
||||
|
|
|
@ -207,3 +207,15 @@ func BenchmarkEncodeBinary(b *testing.B) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSerializeSimple(b *testing.B) {
|
||||
s := NewStruct(nil)
|
||||
s.Append(Make(100500))
|
||||
s.Append(Make("1aada0032aba1ef6d1f0")) // Mimicking uint160.
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := Serialize(s)
|
||||
if err != nil {
|
||||
b.FailNow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
103
pkg/vm/vm.go
103
pkg/vm/vm.go
|
@ -282,7 +282,7 @@ func (v *VM) LoadScriptWithFlags(b []byte, f callflag.CallFlag) {
|
|||
ctx := NewContextWithParams(b, 0, -1, 0)
|
||||
v.estack = newStack("evaluation", &v.refs)
|
||||
ctx.estack = v.estack
|
||||
ctx.tryStack = newStack("exception", nil)
|
||||
initStack(&ctx.tryStack, "exception", nil)
|
||||
ctx.callFlag = f
|
||||
ctx.static = newSlot(&v.refs)
|
||||
ctx.callingScriptHash = v.GetCurrentScriptHash()
|
||||
|
@ -328,12 +328,11 @@ func (v *VM) Context() *Context {
|
|||
// PopResult is used to pop the first item of the evaluation stack. This allows
|
||||
// us to test compiler and vm in a bi-directional way.
|
||||
func (v *VM) PopResult() interface{} {
|
||||
e := v.estack.Pop()
|
||||
if e != nil {
|
||||
return e.Value()
|
||||
}
|
||||
if v.estack.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
return v.estack.Pop().Value()
|
||||
}
|
||||
|
||||
// Stack returns json formatted representation of the given stack.
|
||||
func (v *VM) Stack(n string) string {
|
||||
|
@ -448,8 +447,8 @@ func (v *VM) StepOut() error {
|
|||
v.state = NoneState
|
||||
}
|
||||
|
||||
expSize := v.istack.len
|
||||
for v.state == NoneState && v.istack.len >= expSize {
|
||||
expSize := v.istack.Len()
|
||||
for v.state == NoneState && v.istack.Len() >= expSize {
|
||||
err = v.StepInto()
|
||||
}
|
||||
if v.state == NoneState {
|
||||
|
@ -470,10 +469,10 @@ func (v *VM) StepOver() error {
|
|||
v.state = NoneState
|
||||
}
|
||||
|
||||
expSize := v.istack.len
|
||||
expSize := v.istack.Len()
|
||||
for {
|
||||
err = v.StepInto()
|
||||
if !(v.state == NoneState && v.istack.len > expSize) {
|
||||
if !(v.state == NoneState && v.istack.Len() > expSize) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -739,20 +738,20 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
|||
v.estack.Pop()
|
||||
|
||||
case opcode.NIP:
|
||||
elem := v.estack.RemoveAt(1)
|
||||
if elem == nil {
|
||||
if v.estack.Len() < 2 {
|
||||
panic("no second element found")
|
||||
}
|
||||
_ = v.estack.RemoveAt(1)
|
||||
|
||||
case opcode.XDROP:
|
||||
n := int(v.estack.Pop().BigInt().Int64())
|
||||
if n < 0 {
|
||||
panic("invalid length")
|
||||
}
|
||||
e := v.estack.RemoveAt(n)
|
||||
if e == nil {
|
||||
if v.estack.Len() < n+1 {
|
||||
panic("bad index")
|
||||
}
|
||||
_ = v.estack.RemoveAt(n)
|
||||
|
||||
case opcode.CLEAR:
|
||||
v.estack.Clear()
|
||||
|
@ -761,10 +760,10 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
|||
v.estack.Push(v.estack.Dup(0))
|
||||
|
||||
case opcode.OVER:
|
||||
a := v.estack.Dup(1)
|
||||
if a == nil {
|
||||
if v.estack.Len() < 2 {
|
||||
panic("no second element found")
|
||||
}
|
||||
a := v.estack.Dup(1)
|
||||
v.estack.Push(a)
|
||||
|
||||
case opcode.PICK:
|
||||
|
@ -772,20 +771,17 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
|||
if n < 0 {
|
||||
panic("negative stack item returned")
|
||||
}
|
||||
a := v.estack.Dup(n)
|
||||
if a == nil {
|
||||
if v.estack.Len() < n+1 {
|
||||
panic("no nth element found")
|
||||
}
|
||||
a := v.estack.Dup(n)
|
||||
v.estack.Push(a)
|
||||
|
||||
case opcode.TUCK:
|
||||
a := v.estack.Dup(0)
|
||||
if a == nil {
|
||||
panic("no top-level element found")
|
||||
}
|
||||
if v.estack.Len() < 2 {
|
||||
panic("can't TUCK with a one-element stack")
|
||||
panic("too short stack to TUCK")
|
||||
}
|
||||
a := v.estack.Dup(0)
|
||||
v.estack.InsertAt(a, 2)
|
||||
|
||||
case opcode.SWAP:
|
||||
|
@ -821,10 +817,8 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
|||
|
||||
// Bit operations.
|
||||
case opcode.INVERT:
|
||||
// inplace
|
||||
e := v.estack.Peek(0)
|
||||
i := e.BigInt()
|
||||
e.value = stackitem.Make(new(big.Int).Not(i))
|
||||
i := v.estack.Pop().BigInt()
|
||||
v.estack.PushVal(new(big.Int).Not(i))
|
||||
|
||||
case opcode.AND:
|
||||
b := v.estack.Pop().BigInt()
|
||||
|
@ -842,14 +836,11 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
|||
v.estack.PushVal(new(big.Int).Xor(b, a))
|
||||
|
||||
case opcode.EQUAL, opcode.NOTEQUAL:
|
||||
if v.estack.Len() < 2 {
|
||||
panic("need a pair of elements on the stack")
|
||||
}
|
||||
b := v.estack.Pop()
|
||||
if b == nil {
|
||||
panic("no top-level element found")
|
||||
}
|
||||
a := v.estack.Pop()
|
||||
if a == nil {
|
||||
panic("no second-to-the-top element found")
|
||||
}
|
||||
v.estack.PushVal(a.value.Equals(b.value) == (op == opcode.EQUAL))
|
||||
|
||||
// Numeric operations.
|
||||
|
@ -1100,7 +1091,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
|||
if index < 0 {
|
||||
panic("invalid key")
|
||||
}
|
||||
v.estack.Push(&Element{value: t.Value().([]stackitem.MapElement)[index].Value.Dup()})
|
||||
v.estack.Push(Element{value: t.Value().([]stackitem.MapElement)[index].Value.Dup()})
|
||||
default:
|
||||
arr := obj.Bytes()
|
||||
if index < 0 || index >= len(arr) {
|
||||
|
@ -1318,13 +1309,13 @@ 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.Push(Element{value: stackitem.NewMap()})
|
||||
|
||||
case opcode.KEYS:
|
||||
item := v.estack.Pop()
|
||||
if item == nil {
|
||||
if v.estack.Len() == 0 {
|
||||
panic("no argument")
|
||||
}
|
||||
item := v.estack.Pop()
|
||||
|
||||
m, ok := item.value.(*stackitem.Map)
|
||||
if !ok {
|
||||
|
@ -1338,10 +1329,10 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
|||
v.estack.PushVal(arr)
|
||||
|
||||
case opcode.VALUES:
|
||||
item := v.estack.Pop()
|
||||
if item == nil {
|
||||
if v.estack.Len() == 0 {
|
||||
panic("no argument")
|
||||
}
|
||||
item := v.estack.Pop()
|
||||
|
||||
var arr []stackitem.Item
|
||||
switch t := item.value.(type) {
|
||||
|
@ -1363,13 +1354,13 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
|||
v.estack.PushVal(arr)
|
||||
|
||||
case opcode.HASKEY:
|
||||
if v.estack.Len() < 2 {
|
||||
panic("not enough arguments")
|
||||
}
|
||||
key := v.estack.Pop()
|
||||
validateMapKey(key)
|
||||
|
||||
c := v.estack.Pop()
|
||||
if c == nil {
|
||||
panic("no value found")
|
||||
}
|
||||
switch t := c.value.(type) {
|
||||
case *stackitem.Array, *stackitem.Struct:
|
||||
index := key.BigInt().Int64()
|
||||
|
@ -1519,7 +1510,7 @@ func (v *VM) call(ctx *Context, offset int) {
|
|||
newCtx.RetCount = -1
|
||||
newCtx.local = nil
|
||||
newCtx.arguments = nil
|
||||
newCtx.tryStack = newStack("exception", nil)
|
||||
initStack(&newCtx.tryStack, "exception", nil)
|
||||
newCtx.NEF = ctx.NEF
|
||||
v.istack.PushVal(newCtx)
|
||||
v.Jump(newCtx, offset)
|
||||
|
@ -1559,16 +1550,15 @@ func calcJumpOffset(ctx *Context, parameter []byte) (int, int, error) {
|
|||
}
|
||||
|
||||
func (v *VM) handleException() {
|
||||
pop := 0
|
||||
ictxv := v.istack.Peek(0)
|
||||
for pop := 0; pop < v.istack.Len(); pop++ {
|
||||
ictxv := v.istack.Peek(pop)
|
||||
ictx := ictxv.Value().(*Context)
|
||||
for ictx != nil {
|
||||
e := ictx.tryStack.Peek(0)
|
||||
for e != nil {
|
||||
for j := 0; j < ictx.tryStack.Len(); j++ {
|
||||
e := ictx.tryStack.Peek(j)
|
||||
ectx := e.Value().(*exceptionHandlingContext)
|
||||
if ectx.State == eFinally || (ectx.State == eCatch && !ectx.HasFinally()) {
|
||||
ictx.tryStack.Pop()
|
||||
e = ictx.tryStack.Peek(0)
|
||||
j = -1
|
||||
continue
|
||||
}
|
||||
for i := 0; i < pop; i++ {
|
||||
|
@ -1586,12 +1576,6 @@ func (v *VM) handleException() {
|
|||
}
|
||||
return
|
||||
}
|
||||
pop++
|
||||
ictxv = ictxv.Next()
|
||||
if ictxv == nil {
|
||||
break
|
||||
}
|
||||
ictx = ictxv.Value().(*Context)
|
||||
}
|
||||
throwUnhandledException(v.uncaughtException)
|
||||
}
|
||||
|
@ -1753,17 +1737,18 @@ func makeArrayOfType(n int, typ stackitem.Type) []stackitem.Item {
|
|||
return items
|
||||
}
|
||||
|
||||
func validateMapKey(key *Element) {
|
||||
if key == nil {
|
||||
func validateMapKey(key Element) {
|
||||
item := key.Item()
|
||||
if item == nil {
|
||||
panic("no key found")
|
||||
}
|
||||
if err := stackitem.IsValidMapKey(key.Item()); err != nil {
|
||||
if err := stackitem.IsValidMapKey(item); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (v *VM) checkInvocationStackSize() {
|
||||
if v.istack.len >= MaxInvocationStackSize {
|
||||
if v.istack.Len() >= MaxInvocationStackSize {
|
||||
panic("invocation stack is too big")
|
||||
}
|
||||
}
|
||||
|
@ -1785,7 +1770,7 @@ func (v *VM) GetCallingScriptHash() util.Uint160 {
|
|||
|
||||
// GetEntryScriptHash implements ScriptHashGetter interface.
|
||||
func (v *VM) GetEntryScriptHash() util.Uint160 {
|
||||
return v.getContextScriptHash(v.istack.len - 1)
|
||||
return v.getContextScriptHash(v.istack.Len() - 1)
|
||||
}
|
||||
|
||||
// GetCurrentScriptHash implements ScriptHashGetter interface.
|
||||
|
|
|
@ -1176,7 +1176,7 @@ func TestPICKITEMDupMap(t *testing.T) {
|
|||
vm := load(prog)
|
||||
m := stackitem.NewMap()
|
||||
m.Add(stackitem.Make(42), stackitem.Make(-1))
|
||||
vm.estack.Push(&Element{value: m})
|
||||
vm.estack.Push(Element{value: m})
|
||||
runVM(t, vm)
|
||||
assert.Equal(t, 2, vm.estack.Len())
|
||||
assert.Equal(t, int64(1), vm.estack.Pop().BigInt().Int64())
|
||||
|
@ -1245,7 +1245,7 @@ func TestSETITEMBigMapGood(t *testing.T) {
|
|||
for i := 0; i < MaxStackSize-3; i++ {
|
||||
m.Add(stackitem.Make(i), stackitem.Make(i))
|
||||
}
|
||||
vm.estack.Push(&Element{value: m})
|
||||
vm.estack.Push(Element{value: m})
|
||||
vm.estack.PushVal(0)
|
||||
vm.estack.PushVal(0)
|
||||
|
||||
|
@ -1274,7 +1274,7 @@ func TestKEYSMap(t *testing.T) {
|
|||
m := stackitem.NewMap()
|
||||
m.Add(stackitem.Make(5), stackitem.Make(6))
|
||||
m.Add(stackitem.Make([]byte{0, 1}), stackitem.Make(6))
|
||||
vm.estack.Push(&Element{value: m})
|
||||
vm.estack.Push(Element{value: m})
|
||||
|
||||
runVM(t, vm)
|
||||
assert.Equal(t, 1, vm.estack.Len())
|
||||
|
@ -1298,7 +1298,7 @@ func TestVALUESMap(t *testing.T) {
|
|||
m := stackitem.NewMap()
|
||||
m.Add(stackitem.Make(5), stackitem.Make([]byte{2, 3}))
|
||||
m.Add(stackitem.Make([]byte{0, 1}), stackitem.Make([]stackitem.Item{}))
|
||||
vm.estack.Push(&Element{value: m})
|
||||
vm.estack.Push(Element{value: m})
|
||||
|
||||
runVM(t, vm)
|
||||
assert.Equal(t, 1, vm.estack.Len())
|
||||
|
@ -1880,7 +1880,7 @@ func TestREVERSEITEMSGoodStruct(t *testing.T) {
|
|||
for i := range elements {
|
||||
arr[i] = stackitem.Make(elements[i])
|
||||
}
|
||||
vm.estack.Push(&Element{value: stackitem.NewStruct(arr)})
|
||||
vm.estack.Push(Element{value: stackitem.NewStruct(arr)})
|
||||
|
||||
runVM(t, vm)
|
||||
assert.Equal(t, 2, vm.estack.Len())
|
||||
|
@ -1944,8 +1944,8 @@ func TestREMOVEMap(t *testing.T) {
|
|||
m := stackitem.NewMap()
|
||||
m.Add(stackitem.Make(5), stackitem.Make(3))
|
||||
m.Add(stackitem.Make([]byte{0, 1}), stackitem.Make([]byte{2, 3}))
|
||||
vm.estack.Push(&Element{value: m})
|
||||
vm.estack.Push(&Element{value: m})
|
||||
vm.estack.Push(Element{value: m})
|
||||
vm.estack.Push(Element{value: m})
|
||||
vm.estack.PushVal(stackitem.Make(5))
|
||||
|
||||
runVM(t, vm)
|
||||
|
|
Loading…
Reference in a new issue