mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-23 13:38:35 +00:00
a3892aa662
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)
367 lines
9.1 KiB
Go
367 lines
9.1 KiB
Go
package vm
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
|
)
|
|
|
|
// 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 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
|
|
}
|
|
|
|
// NewElement returns a new Element object, with its underlying value inferred
|
|
// to the corresponding type.
|
|
func NewElement(v interface{}) Element {
|
|
return Element{stackitem.Make(v)}
|
|
}
|
|
|
|
// Item returns Item contained in the element.
|
|
func (e Element) Item() stackitem.Item {
|
|
return e.value
|
|
}
|
|
|
|
// Value returns value of the Item contained in the element.
|
|
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 {
|
|
val, err := e.value.TryInteger()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return val
|
|
}
|
|
|
|
// 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 {
|
|
b, err := e.value.TryBool()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return b
|
|
}
|
|
|
|
// 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 {
|
|
bs, err := e.value.TryBytes()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return bs
|
|
}
|
|
|
|
// 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 {
|
|
if _, ok := e.value.(stackitem.Null); ok {
|
|
return nil
|
|
}
|
|
bs, err := e.value.TryBytes()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return bs
|
|
}
|
|
|
|
// 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 {
|
|
s, err := stackitem.ToString(e.value)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return s
|
|
}
|
|
|
|
// 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 {
|
|
switch t := e.value.(type) {
|
|
case *stackitem.Array:
|
|
return t.Value().([]stackitem.Item)
|
|
case *stackitem.Struct:
|
|
return t.Value().([]stackitem.Item)
|
|
default:
|
|
panic("element is not an array")
|
|
}
|
|
}
|
|
|
|
// Interop attempts to get the underlying value of the element
|
|
// as an interop item.
|
|
func (e Element) Interop() *stackitem.Interop {
|
|
switch t := e.value.(type) {
|
|
case *stackitem.Interop:
|
|
return t
|
|
default:
|
|
panic("element is not an interop")
|
|
}
|
|
}
|
|
|
|
// Stack represents a Stack backed by a slice of Elements.
|
|
type Stack struct {
|
|
elems []Element
|
|
name string
|
|
refs *refCounter
|
|
}
|
|
|
|
// NewStack returns a new stack name by the given name.
|
|
func NewStack(n string) *Stack {
|
|
return newStack(n, newRefCounter())
|
|
}
|
|
|
|
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.Clear()
|
|
}
|
|
|
|
// Clear clears all elements on the stack and set its length to 0.
|
|
func (s *Stack) Clear() {
|
|
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 len(s.elems)
|
|
}
|
|
|
|
// InsertAt inserts the given item (n) deep on the stack.
|
|
// 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.elems = append(s.elems, e)
|
|
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{}) {
|
|
s.Push(NewElement(v))
|
|
}
|
|
|
|
// 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 len(s.elems) == 0 {
|
|
return Element{}
|
|
}
|
|
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 len(s.elems) == 0 {
|
|
return Element{}
|
|
}
|
|
return s.elems[0]
|
|
}
|
|
|
|
// Peek returns the element (n) far in the stack beginning from
|
|
// 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. 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
|
|
}
|
|
|
|
// Dup duplicates and returns the element at position n.
|
|
// 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 {
|
|
e := s.Peek(n)
|
|
return Element{e.value.Dup()}
|
|
}
|
|
|
|
// Iter iterates over all the elements int the stack, starting from the top
|
|
// of the stack.
|
|
// s.Iter(func(elem *Element) {
|
|
// // do something with the element.
|
|
// })
|
|
func (s *Stack) Iter(f func(Element)) {
|
|
for i := len(s.elems) - 1; i >= 0; i-- {
|
|
f(s.elems[i])
|
|
}
|
|
}
|
|
|
|
// IterBack iterates over all the elements of the stack, starting from the bottom
|
|
// of the stack.
|
|
// s.IterBack(func(elem *Element) {
|
|
// // do something with the element.
|
|
// })
|
|
func (s *Stack) IterBack(f func(Element)) {
|
|
for i := 0; i < len(s.elems); i++ {
|
|
f(s.elems[i])
|
|
}
|
|
}
|
|
|
|
// Swap swaps two elements on the stack without popping and pushing them.
|
|
func (s *Stack) Swap(n1, n2 int) error {
|
|
if n1 < 0 || n2 < 0 {
|
|
return errors.New("negative index")
|
|
}
|
|
l := len(s.elems)
|
|
if n1 >= l || n2 >= l {
|
|
return errors.New("too big index")
|
|
}
|
|
s.elems[l-n1-1], s.elems[l-n2-1] = s.elems[l-n2-1], s.elems[l-n1-1]
|
|
return nil
|
|
}
|
|
|
|
// 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 > l {
|
|
return errors.New("too big index")
|
|
} else if n <= 1 {
|
|
return nil
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// Roll brings an item with the given index to the top of the stack, moving all
|
|
// the other elements down accordingly. It does all of that without popping and
|
|
// pushing elements.
|
|
func (s *Stack) Roll(n int) error {
|
|
if n < 0 {
|
|
return errors.New("negative index")
|
|
}
|
|
l := len(s.elems)
|
|
if n >= l {
|
|
return errors.New("too big index")
|
|
}
|
|
if n == 0 {
|
|
return nil
|
|
}
|
|
e := s.elems[l-1-n]
|
|
copy(s.elems[l-1-n:], s.elems[l-n:])
|
|
s.elems[l-1] = e
|
|
return nil
|
|
}
|
|
|
|
// PopSigElements pops keys or signatures from the stack as needed for
|
|
// CHECKMULTISIG.
|
|
func (s *Stack) PopSigElements() ([][]byte, error) {
|
|
var num int
|
|
var elems [][]byte
|
|
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())
|
|
if num < 1 {
|
|
return nil, fmt.Errorf("less than one element in the array")
|
|
}
|
|
elems = make([][]byte, num)
|
|
for k, v := range item.Array() {
|
|
b, ok := v.Value().([]byte)
|
|
if !ok {
|
|
return nil, fmt.Errorf("bad element %s", v.String())
|
|
}
|
|
elems[k] = b
|
|
}
|
|
default:
|
|
num = int(item.BigInt().Int64())
|
|
if num < 1 || num > s.Len() {
|
|
return nil, fmt.Errorf("wrong number of elements: %d", num)
|
|
}
|
|
elems = make([][]byte, num)
|
|
for i := 0; i < num; i++ {
|
|
elems[i] = s.Pop().Bytes()
|
|
}
|
|
}
|
|
return elems, nil
|
|
}
|
|
|
|
// 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, len(s.elems))
|
|
s.IterBack(func(e Element) {
|
|
items = append(items, e.Item())
|
|
})
|
|
return items
|
|
}
|
|
|
|
// MarshalJSON implements JSON marshalling interface.
|
|
func (s *Stack) MarshalJSON() ([]byte, error) {
|
|
items := s.ToArray()
|
|
arr := make([]json.RawMessage, len(items))
|
|
for i := range items {
|
|
data, err := stackitem.ToJSONWithTypes(items[i])
|
|
if err == nil {
|
|
arr[i] = data
|
|
}
|
|
}
|
|
return json.Marshal(arr)
|
|
}
|