vm: implement slot-related opcodes

1. Slot is a new mechanism for storing variables during execution
which is more convenient than alt.stack. This commit implements
support for slot opcodes in both vm and compiler.
2. Remove old alt.stack opcodes.
3. Do not process globals at the start of every function, but instead
load them single time at main.
This commit is contained in:
Evgenii Stratonikov 2020-05-07 11:54:35 +03:00
parent a6271f6bf2
commit 0cb6dc47e4
12 changed files with 552 additions and 192 deletions

View file

@ -1,11 +1,14 @@
package compiler package compiler
import ( import (
"errors"
"fmt" "fmt"
"go/ast" "go/ast"
"go/constant" "go/constant"
"go/types" "go/types"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"golang.org/x/tools/go/loader" "golang.org/x/tools/go/loader"
) )
@ -47,6 +50,24 @@ func typeAndValueForField(fld *types.Var) (types.TypeAndValue, error) {
return types.TypeAndValue{}, nil return types.TypeAndValue{}, nil
} }
// newGlobal creates new global variable.
func (c *codegen) newGlobal(name string) {
c.globals[name] = len(c.globals)
}
// traverseGlobals visits and initializes global variables.
func (c *codegen) traverseGlobals(f ast.Node) {
n := countGlobals(f)
if n != 0 {
if n > 255 {
c.prog.BinWriter.Err = errors.New("too many global variables")
return
}
emit.Instruction(c.prog.BinWriter, opcode.INITSSLOT, []byte{byte(n)})
}
c.convertGlobals(f)
}
// countGlobals counts the global variables in the program to add // countGlobals counts the global variables in the program to add
// them with the stack size of the function. // them with the stack size of the function.
func countGlobals(f ast.Node) (i int64) { func countGlobals(f ast.Node) (i int64) {

View file

@ -43,6 +43,8 @@ type codegen struct {
// Current funcScope being converted. // Current funcScope being converted.
scope *funcScope scope *funcScope
globals map[string]int
// A mapping from label's names to their ids. // A mapping from label's names to their ids.
labels map[labelWithType]uint16 labels map[labelWithType]uint16
// A list of nested label names together with evaluation stack depth. // A list of nested label names together with evaluation stack depth.
@ -82,6 +84,14 @@ type labelWithStackSize struct {
sz int sz int
} }
type varType int
const (
varGlobal varType = iota
varLocal
varArgument
)
// newLabel creates a new label to jump to // newLabel creates a new label to jump to
func (c *codegen) newLabel() (l uint16) { func (c *codegen) newLabel() (l uint16) {
li := len(c.l) li := len(c.l)
@ -147,34 +157,6 @@ func (c *codegen) convertBasicType(t types.TypeAndValue, typ *types.Basic) {
} }
} }
func (c *codegen) emitLoadLocal(name string) {
pos := c.scope.loadLocal(name)
if pos < 0 {
c.prog.Err = fmt.Errorf("cannot load local variable with position: %d", pos)
return
}
c.emitLoadLocalPos(pos)
}
func (c *codegen) emitLoadLocalPos(pos int) {
emit.Opcode(c.prog.BinWriter, opcode.DUPFROMALTSTACK)
emit.Int(c.prog.BinWriter, int64(pos))
emit.Opcode(c.prog.BinWriter, opcode.PICKITEM)
}
func (c *codegen) emitStoreLocal(pos int) {
emit.Opcode(c.prog.BinWriter, opcode.DUPFROMALTSTACK)
if pos < 0 {
c.prog.Err = fmt.Errorf("invalid position to store local: %d", pos)
return
}
emit.Int(c.prog.BinWriter, int64(pos))
emit.Opcode(c.prog.BinWriter, opcode.ROT)
emit.Opcode(c.prog.BinWriter, opcode.SETITEM)
}
func (c *codegen) emitLoadField(i int) { func (c *codegen) emitLoadField(i int) {
emit.Int(c.prog.BinWriter, int64(i)) emit.Int(c.prog.BinWriter, int64(i))
emit.Opcode(c.prog.BinWriter, opcode.PICKITEM) emit.Opcode(c.prog.BinWriter, opcode.PICKITEM)
@ -186,6 +168,81 @@ func (c *codegen) emitStoreStructField(i int) {
emit.Opcode(c.prog.BinWriter, opcode.SETITEM) emit.Opcode(c.prog.BinWriter, opcode.SETITEM)
} }
// getVarIndex returns variable type and position in corresponding slot,
// according to current scope.
func (c *codegen) getVarIndex(name string) (varType, int) {
if c.scope != nil {
if i, ok := c.scope.arguments[name]; ok {
return varArgument, i
} else if i, ok := c.scope.locals[name]; ok {
return varLocal, i
}
}
if i, ok := c.globals[name]; ok {
return varGlobal, i
}
return varLocal, c.scope.newVariable(varLocal, name)
}
func getBaseOpcode(t varType) (opcode.Opcode, opcode.Opcode) {
switch t {
case varGlobal:
return opcode.LDSFLD0, opcode.STSFLD0
case varLocal:
return opcode.LDLOC0, opcode.STLOC0
case varArgument:
return opcode.LDARG0, opcode.STARG0
default:
panic("invalid type")
}
}
// emitLoadVar loads specified variable to the evaluation stack.
func (c *codegen) emitLoadVar(name string) {
t, i := c.getVarIndex(name)
base, _ := getBaseOpcode(t)
if i < 7 {
emit.Opcode(c.prog.BinWriter, base+opcode.Opcode(i))
} else {
emit.Instruction(c.prog.BinWriter, base+7, []byte{byte(i)})
}
}
// emitStoreVar stores top value from the evaluation stack in the specified variable.
func (c *codegen) emitStoreVar(name string) {
t, i := c.getVarIndex(name)
_, base := getBaseOpcode(t)
if i < 7 {
emit.Opcode(c.prog.BinWriter, base+opcode.Opcode(i))
} else {
emit.Instruction(c.prog.BinWriter, base+7, []byte{byte(i)})
}
}
func (c *codegen) emitDefault(n ast.Expr) {
tv, ok := c.typeInfo.Types[n]
if !ok {
c.prog.Err = errors.New("invalid type")
return
}
if t, ok := tv.Type.(*types.Basic); ok {
info := t.Info()
switch {
case info&types.IsInteger != 0:
emit.Int(c.prog.BinWriter, 0)
case info&types.IsString != 0:
emit.Bytes(c.prog.BinWriter, []byte{})
case info&types.IsBoolean != 0:
emit.Bool(c.prog.BinWriter, false)
default:
emit.Opcode(c.prog.BinWriter, opcode.PUSHNULL)
}
return
}
emit.Opcode(c.prog.BinWriter, opcode.PUSHNULL)
}
// convertGlobals traverses the AST and only converts global declarations. // convertGlobals traverses the AST and only converts global declarations.
// If we call this in convertFuncDecl then it will load all global variables // If we call this in convertFuncDecl then it will load all global variables
// into the scope of the function. // into the scope of the function.
@ -231,9 +288,17 @@ func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl) {
// All globals copied into the scope of the function need to be added // All globals copied into the scope of the function need to be added
// to the stack size of the function. // to the stack size of the function.
emit.Int(c.prog.BinWriter, f.stackSize()+countGlobals(file)) sizeLoc := f.countLocals()
emit.Opcode(c.prog.BinWriter, opcode.NEWARRAY) if sizeLoc > 255 {
emit.Opcode(c.prog.BinWriter, opcode.TOALTSTACK) c.prog.Err = errors.New("maximum of 255 local variables is allowed")
}
sizeArg := f.countArgs()
if sizeArg > 255 {
c.prog.Err = errors.New("maximum of 255 local variables is allowed")
}
if sizeLoc != 0 || sizeArg != 0 {
emit.Instruction(c.prog.BinWriter, opcode.INITSLOT, []byte{byte(sizeLoc), byte(sizeArg)})
}
// We need to handle methods, which in Go, is just syntactic sugar. // We need to handle methods, which in Go, is just syntactic sugar.
// The method receiver will be passed in as first argument. // The method receiver will be passed in as first argument.
@ -250,23 +315,18 @@ func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl) {
c.prog.Err = fmt.Errorf("method receives for non-struct types is not yet supported") c.prog.Err = fmt.Errorf("method receives for non-struct types is not yet supported")
return return
} }
l := c.scope.newLocal(ident.Name) // only create an argument here, it will be stored via INITSLOT
c.emitStoreLocal(l) c.scope.newVariable(varArgument, ident.Name)
} }
} }
// Load the arguments in scope. // Load the arguments in scope.
for _, arg := range decl.Type.Params.List { for _, arg := range decl.Type.Params.List {
for _, id := range arg.Names { for _, id := range arg.Names {
l := c.scope.newLocal(id.Name) // only create an argument here, it will be stored via INITSLOT
c.emitStoreLocal(l) c.scope.newVariable(varArgument, id.Name)
} }
} }
// Load in all the global variables in to the scope of the function.
// This is not necessary for syscalls.
if !isSyscall(f) {
c.convertGlobals(file)
}
ast.Walk(c, decl.Body) ast.Walk(c, decl.Body)
@ -275,8 +335,6 @@ func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl) {
// This can be the case with void and named-return functions. // This can be the case with void and named-return functions.
if !lastStmtIsReturn(decl) { if !lastStmtIsReturn(decl) {
c.saveSequencePoint(decl.Body) c.saveSequencePoint(decl.Body)
emit.Opcode(c.prog.BinWriter, opcode.FROMALTSTACK)
emit.Opcode(c.prog.BinWriter, opcode.DROP)
emit.Opcode(c.prog.BinWriter, opcode.RET) emit.Opcode(c.prog.BinWriter, opcode.RET)
} }
@ -305,25 +363,32 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
switch t := spec.(type) { switch t := spec.(type) {
case *ast.ValueSpec: case *ast.ValueSpec:
for _, id := range t.Names { for _, id := range t.Names {
c.scope.newLocal(id.Name) if c.scope == nil {
// it is a global declaration
c.newGlobal(id.Name)
} else {
c.scope.newLocal(id.Name)
}
c.registerDebugVariable(id.Name, t.Type) c.registerDebugVariable(id.Name, t.Type)
} }
if len(t.Values) != 0 { if len(t.Values) != 0 {
for i, val := range t.Values { for i, val := range t.Values {
ast.Walk(c, val) ast.Walk(c, val)
l := c.scope.loadLocal(t.Names[i].Name) c.emitStoreVar(t.Names[i].Name)
c.emitStoreLocal(l)
} }
} else if c.isCompoundArrayType(t.Type) { } else if c.isCompoundArrayType(t.Type) {
emit.Opcode(c.prog.BinWriter, opcode.PUSH0) emit.Opcode(c.prog.BinWriter, opcode.PUSH0)
emit.Opcode(c.prog.BinWriter, opcode.NEWARRAY) emit.Opcode(c.prog.BinWriter, opcode.NEWARRAY)
l := c.scope.loadLocal(t.Names[0].Name) c.emitStoreVar(t.Names[0].Name)
c.emitStoreLocal(l)
} else if n, ok := c.isStructType(t.Type); ok { } else if n, ok := c.isStructType(t.Type); ok {
emit.Int(c.prog.BinWriter, int64(n)) emit.Int(c.prog.BinWriter, int64(n))
emit.Opcode(c.prog.BinWriter, opcode.NEWSTRUCT) emit.Opcode(c.prog.BinWriter, opcode.NEWSTRUCT)
l := c.scope.loadLocal(t.Names[0].Name) c.emitStoreVar(t.Names[0].Name)
c.emitStoreLocal(l) } else {
for _, id := range t.Names {
c.emitDefault(t.Type)
c.emitStoreVar(id.Name)
}
} }
} }
} }
@ -337,11 +402,10 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
case *ast.Ident: case *ast.Ident:
switch n.Tok { switch n.Tok {
case token.ADD_ASSIGN, token.SUB_ASSIGN, token.MUL_ASSIGN, token.QUO_ASSIGN, token.REM_ASSIGN: case token.ADD_ASSIGN, token.SUB_ASSIGN, token.MUL_ASSIGN, token.QUO_ASSIGN, token.REM_ASSIGN:
c.emitLoadLocal(t.Name) c.emitLoadVar(t.Name)
ast.Walk(c, n.Rhs[0]) // can only add assign to 1 expr on the RHS ast.Walk(c, n.Rhs[0]) // can only add assign to 1 expr on the RHS
c.convertToken(n.Tok) c.convertToken(n.Tok)
l := c.scope.loadLocal(t.Name) c.emitStoreVar(t.Name)
c.emitStoreLocal(l)
case token.DEFINE: case token.DEFINE:
if !multiRet { if !multiRet {
c.registerDebugVariable(t.Name, n.Rhs[i]) c.registerDebugVariable(t.Name, n.Rhs[i])
@ -355,8 +419,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
if t.Name == "_" { if t.Name == "_" {
emit.Opcode(c.prog.BinWriter, opcode.DROP) emit.Opcode(c.prog.BinWriter, opcode.DROP)
} else { } else {
l := c.scope.loadLocal(t.Name) c.emitStoreVar(t.Name)
c.emitStoreLocal(l)
} }
} }
@ -366,7 +429,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
ast.Walk(c, n.Rhs[i]) ast.Walk(c, n.Rhs[i])
typ := c.typeInfo.ObjectOf(expr).Type().Underlying() typ := c.typeInfo.ObjectOf(expr).Type().Underlying()
if strct, ok := typ.(*types.Struct); ok { if strct, ok := typ.(*types.Struct); ok {
c.emitLoadLocal(expr.Name) // load the struct c.emitLoadVar(expr.Name) // load the struct
i := indexOfStruct(strct, t.Sel.Name) // get the index of the field i := indexOfStruct(strct, t.Sel.Name) // get the index of the field
c.emitStoreStructField(i) // store the field c.emitStoreStructField(i) // store the field
} }
@ -380,7 +443,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
case *ast.IndexExpr: case *ast.IndexExpr:
ast.Walk(c, n.Rhs[i]) ast.Walk(c, n.Rhs[i])
name := t.X.(*ast.Ident).Name name := t.X.(*ast.Ident).Name
c.emitLoadLocal(name) c.emitLoadVar(name)
switch ind := t.Index.(type) { switch ind := t.Index.(type) {
case *ast.BasicLit: case *ast.BasicLit:
indexStr := ind.Value indexStr := ind.Value
@ -391,7 +454,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
} }
c.emitStoreStructField(index) c.emitStoreStructField(index)
case *ast.Ident: case *ast.Ident:
c.emitLoadLocal(ind.Name) c.emitLoadVar(ind.Name)
emit.Opcode(c.prog.BinWriter, opcode.ROT) emit.Opcode(c.prog.BinWriter, opcode.ROT)
emit.Opcode(c.prog.BinWriter, opcode.SETITEM) emit.Opcode(c.prog.BinWriter, opcode.SETITEM)
default: default:
@ -404,7 +467,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
case *ast.SliceExpr: case *ast.SliceExpr:
name := n.X.(*ast.Ident).Name name := n.X.(*ast.Ident).Name
c.emitLoadLocal(name) c.emitLoadVar(name)
if n.Low != nil { if n.Low != nil {
ast.Walk(c, n.Low) ast.Walk(c, n.Low)
@ -442,7 +505,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
for i := len(results.List) - 1; i >= 0; i-- { for i := len(results.List) - 1; i >= 0; i-- {
names := results.List[i].Names names := results.List[i].Names
for j := len(names) - 1; j >= 0; j-- { for j := len(names) - 1; j >= 0; j-- {
c.emitLoadLocal(names[j].Name) c.emitLoadVar(names[j].Name)
} }
} }
} }
@ -454,8 +517,6 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
} }
c.saveSequencePoint(n) c.saveSequencePoint(n)
emit.Opcode(c.prog.BinWriter, opcode.FROMALTSTACK)
emit.Opcode(c.prog.BinWriter, opcode.DROP) // Cleanup the stack.
emit.Opcode(c.prog.BinWriter, opcode.RET) emit.Opcode(c.prog.BinWriter, opcode.RET)
return nil return nil
@ -557,7 +618,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
} else if tv := c.typeInfo.Types[n]; tv.Value != nil { } else if tv := c.typeInfo.Types[n]; tv.Value != nil {
c.emitLoadConst(tv) c.emitLoadConst(tv)
} else { } else {
c.emitLoadLocal(n.Name) c.emitLoadVar(n.Name)
} }
return nil return nil
@ -723,7 +784,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
c.convertBuiltin(n) c.convertBuiltin(n)
case name != "": case name != "":
// Function was not found thus is can be only an invocation of func-typed variable. // Function was not found thus is can be only an invocation of func-typed variable.
c.emitLoadLocal(name) c.emitLoadVar(name)
emit.Opcode(c.prog.BinWriter, opcode.CALLA) emit.Opcode(c.prog.BinWriter, opcode.CALLA)
case isSyscall(f): case isSyscall(f):
c.convertSyscall(n, f.selector.Name, f.name) c.convertSyscall(n, f.selector.Name, f.name)
@ -738,7 +799,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
case *ast.Ident: case *ast.Ident:
typ := c.typeInfo.ObjectOf(t).Type().Underlying() typ := c.typeInfo.ObjectOf(t).Type().Underlying()
if strct, ok := typ.(*types.Struct); ok { if strct, ok := typ.(*types.Struct); ok {
c.emitLoadLocal(t.Name) // load the struct c.emitLoadVar(t.Name) // load the struct
i := indexOfStruct(strct, n.Sel.Name) i := indexOfStruct(strct, n.Sel.Name)
c.emitLoadField(i) // load the field c.emitLoadField(i) // load the field
} }
@ -777,8 +838,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
// for i := 0; i < 10; i++ {} // for i := 0; i < 10; i++ {}
// Where the post stmt is ( i++ ) // Where the post stmt is ( i++ )
if ident, ok := n.X.(*ast.Ident); ok { if ident, ok := n.X.(*ast.Ident); ok {
pos := c.scope.loadLocal(ident.Name) c.emitStoreVar(ident.Name)
c.emitStoreLocal(pos)
} }
return nil return nil
@ -913,9 +973,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
if n.Key != nil { if n.Key != nil {
emit.Opcode(c.prog.BinWriter, opcode.DUP) emit.Opcode(c.prog.BinWriter, opcode.DUP)
c.emitStoreVar(n.Key.(*ast.Ident).Name)
pos := c.scope.loadLocal(n.Key.(*ast.Ident).Name)
c.emitStoreLocal(pos)
} }
ast.Walk(c, n.Body) ast.Walk(c, n.Body)
@ -1315,6 +1373,8 @@ func (c *codegen) compile(info *buildInfo, pkg *loader.PackageInfo) error {
} }
} }
c.traverseGlobals(mainFile)
// convert the entry point first. // convert the entry point first.
c.convertFuncDecl(mainFile, main) c.convertFuncDecl(mainFile, main)
@ -1354,6 +1414,7 @@ func newCodegen(info *buildInfo, pkg *loader.PackageInfo) *codegen {
l: []int{}, l: []int{},
funcs: map[string]*funcScope{}, funcs: map[string]*funcScope{},
lambda: map[string]*funcScope{}, lambda: map[string]*funcScope{},
globals: map[string]int{},
labels: map[labelWithType]uint16{}, labels: map[labelWithType]uint16{},
typeInfo: &pkg.Info, typeInfo: &pkg.Info,

View file

@ -115,6 +115,10 @@ type Event struct {
} }
func (c *codegen) saveSequencePoint(n ast.Node) { func (c *codegen) saveSequencePoint(n ast.Node) {
if c.scope == nil {
// do not save globals for now
return
}
fset := c.buildInfo.program.Fset fset := c.buildInfo.program.Fset
start := fset.Position(n.Pos()) start := fset.Position(n.Pos())
end := fset.Position(n.End()) end := fset.Position(n.End())
@ -143,6 +147,10 @@ func (c *codegen) emitDebugInfo() *DebugInfo {
} }
func (c *codegen) registerDebugVariable(name string, expr ast.Expr) { func (c *codegen) registerDebugVariable(name string, expr ast.Expr) {
if c.scope == nil {
// do not save globals for now
return
}
typ := c.scTypeFromExpr(expr) typ := c.scTypeFromExpr(expr)
c.scope.variables = append(c.scope.variables, name+","+typ) c.scope.variables = append(c.scope.variables, name+","+typ)
} }

View file

@ -27,7 +27,8 @@ type funcScope struct {
variables []string variables []string
// Local variables // Local variables
locals map[string]int locals map[string]int
arguments map[string]int
// voidCalls are basically functions that return their value // voidCalls are basically functions that return their value
// into nothing. The stack has their return value but there // into nothing. The stack has their return value but there
@ -51,6 +52,7 @@ func newFuncScope(decl *ast.FuncDecl, label uint16) *funcScope {
decl: decl, decl: decl,
label: label, label: label,
locals: map[string]int{}, locals: map[string]int{},
arguments: map[string]int{},
voidCalls: map[*ast.CallExpr]bool{}, voidCalls: map[*ast.CallExpr]bool{},
variables: []string{}, variables: []string{},
i: -1, i: -1,
@ -84,7 +86,7 @@ func (c *funcScope) analyzeVoidCalls(node ast.Node) bool {
return true return true
} }
func (c *funcScope) stackSize() int64 { func (c *funcScope) countLocals() int {
size := 0 size := 0
ast.Inspect(c.decl, func(n ast.Node) bool { ast.Inspect(c.decl, func(n ast.Node) bool {
switch n := n.(type) { switch n := n.(type) {
@ -110,28 +112,38 @@ func (c *funcScope) stackSize() int64 {
} }
return true return true
}) })
return size
}
numArgs := len(c.decl.Type.Params.List) func (c *funcScope) countArgs() int {
// Also take care of struct methods recv: e.g. (t Token).Foo(). n := c.decl.Type.Params.NumFields()
if c.decl.Recv != nil { if c.decl.Recv != nil {
numArgs += len(c.decl.Recv.List) n += c.decl.Recv.NumFields()
} }
return n
}
func (c *funcScope) stackSize() int64 {
size := c.countLocals()
numArgs := c.countArgs()
return int64(size + numArgs + len(c.voidCalls)) return int64(size + numArgs + len(c.voidCalls))
} }
// newVariable creates a new local variable or argument in the scope of the function.
func (c *funcScope) newVariable(t varType, name string) int {
c.i++
switch t {
case varLocal:
c.locals[name] = c.i
case varArgument:
c.arguments[name] = c.i
default:
panic("invalid type")
}
return c.i
}
// newLocal creates a new local variable into the scope of the function. // newLocal creates a new local variable into the scope of the function.
func (c *funcScope) newLocal(name string) int { func (c *funcScope) newLocal(name string) int {
c.i++ return c.newVariable(varLocal, name)
c.locals[name] = c.i
return c.i
}
// loadLocal loads the position of a local variable inside the scope of the function.
func (c *funcScope) loadLocal(name string) int {
i, ok := c.locals[name]
if !ok {
// should emit a compiler warning.
return c.newLocal(name)
}
return i
} }

View file

@ -48,18 +48,18 @@ type rpcTestCase struct {
check func(t *testing.T, e *executor, result interface{}) check func(t *testing.T, e *executor, result interface{})
} }
const testContractHash = "a4bea0d56fad00a972135d54b381516205d78484" const testContractHash = "33f3421677fab7f620bd70582f468b4a18df1e5d"
var rpcTestCases = map[string][]rpcTestCase{ var rpcTestCases = map[string][]rpcTestCase{
"getapplicationlog": { "getapplicationlog": {
{ {
name: "positive", name: "positive",
params: `["c296c0929350d051b9b40cace54db5a3eac4b730a8851e958795d44918f23c08"]`, params: `["3f1579e797fedb83b66a85fe21d427a119d0e25ef662582e56393fd0d70e4691"]`,
result: func(e *executor) interface{} { return &result.ApplicationLog{} }, result: func(e *executor) interface{} { return &result.ApplicationLog{} },
check: func(t *testing.T, e *executor, acc interface{}) { check: func(t *testing.T, e *executor, acc interface{}) {
res, ok := acc.(*result.ApplicationLog) res, ok := acc.(*result.ApplicationLog)
require.True(t, ok) require.True(t, ok)
expectedTxHash, err := util.Uint256DecodeStringLE("c296c0929350d051b9b40cace54db5a3eac4b730a8851e958795d44918f23c08") expectedTxHash, err := util.Uint256DecodeStringLE("3f1579e797fedb83b66a85fe21d427a119d0e25ef662582e56393fd0d70e4691")
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, expectedTxHash, res.TxHash) assert.Equal(t, expectedTxHash, res.TxHash)
assert.Equal(t, 1, len(res.Executions)) assert.Equal(t, 1, len(res.Executions))

Binary file not shown.

Binary file not shown.

View file

@ -34,6 +34,9 @@ type Context struct {
// Alt stack pointer. // Alt stack pointer.
astack *Stack astack *Stack
local *Slot
arguments *Slot
// Script hash of the prog. // Script hash of the prog.
scriptHash util.Uint160 scriptHash util.Uint160
@ -107,8 +110,11 @@ func (c *Context) Next() (opcode.Opcode, []byte, error) {
} }
case opcode.JMP, opcode.JMPIF, opcode.JMPIFNOT, opcode.JMPEQ, opcode.JMPNE, case opcode.JMP, opcode.JMPIF, opcode.JMPIFNOT, opcode.JMPEQ, opcode.JMPNE,
opcode.JMPGT, opcode.JMPGE, opcode.JMPLT, opcode.JMPLE, opcode.JMPGT, opcode.JMPGE, opcode.JMPLT, opcode.JMPLE,
opcode.CALL, opcode.ISTYPE, opcode.CONVERT, opcode.NEWARRAYT: opcode.CALL, opcode.ISTYPE, opcode.CONVERT, opcode.NEWARRAYT,
opcode.INITSSLOT, opcode.LDSFLD, opcode.STSFLD, opcode.LDARG, opcode.STARG, opcode.LDLOC, opcode.STLOC:
numtoread = 1 numtoread = 1
case opcode.INITSLOT:
numtoread = 2
case opcode.JMPL, opcode.JMPIFL, opcode.JMPIFNOTL, opcode.JMPEQL, opcode.JMPNEL, case opcode.JMPL, opcode.JMPIFL, opcode.JMPIFNOTL, opcode.JMPEQL, opcode.JMPNEL,
opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLTL, opcode.JMPLEL, opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLTL, opcode.JMPLEL,
opcode.CALLL, opcode.SYSCALL, opcode.PUSHA: opcode.CALLL, opcode.SYSCALL, opcode.PUSHA:

View file

@ -93,10 +93,57 @@ const (
REVERSE4 Opcode = 0x54 REVERSE4 Opcode = 0x54
REVERSEN Opcode = 0x55 REVERSEN Opcode = 0x55
// Old stack opcodes // Slots
DUPFROMALTSTACK Opcode = 0x6A INITSSLOT Opcode = 0x56
TOALTSTACK Opcode = 0x6B INITSLOT Opcode = 0x57
FROMALTSTACK Opcode = 0x6C LDSFLD0 Opcode = 0x58
LDSFLD1 Opcode = 0x59
LDSFLD2 Opcode = 0x5A
LDSFLD3 Opcode = 0x5B
LDSFLD4 Opcode = 0x5C
LDSFLD5 Opcode = 0x5D
LDSFLD6 Opcode = 0x5E
LDSFLD Opcode = 0x5F
STSFLD0 Opcode = 0x60
STSFLD1 Opcode = 0x61
STSFLD2 Opcode = 0x62
STSFLD3 Opcode = 0x63
STSFLD4 Opcode = 0x64
STSFLD5 Opcode = 0x65
STSFLD6 Opcode = 0x66
STSFLD Opcode = 0x67
LDLOC0 Opcode = 0x68
LDLOC1 Opcode = 0x69
LDLOC2 Opcode = 0x6A
LDLOC3 Opcode = 0x6B
LDLOC4 Opcode = 0x6C
LDLOC5 Opcode = 0x6D
LDLOC6 Opcode = 0x6E
LDLOC Opcode = 0x6F
STLOC0 Opcode = 0x70
STLOC1 Opcode = 0x71
STLOC2 Opcode = 0x72
STLOC3 Opcode = 0x73
STLOC4 Opcode = 0x74
STLOC5 Opcode = 0x75
STLOC6 Opcode = 0x76
STLOC Opcode = 0x77
LDARG0 Opcode = 0x78
LDARG1 Opcode = 0x79
LDARG2 Opcode = 0x7A
LDARG3 Opcode = 0x7B
LDARG4 Opcode = 0x7C
LDARG5 Opcode = 0x7D
LDARG6 Opcode = 0x7E
LDARG Opcode = 0x7F
STARG0 Opcode = 0x80
STARG1 Opcode = 0x81
STARG2 Opcode = 0x82
STARG3 Opcode = 0x83
STARG4 Opcode = 0x84
STARG5 Opcode = 0x85
STARG6 Opcode = 0x86
STARG Opcode = 0x87
// Splice // Splice
CAT Opcode = 0x8B CAT Opcode = 0x8B

View file

@ -82,9 +82,56 @@ func _() {
_ = x[REVERSE3-83] _ = x[REVERSE3-83]
_ = x[REVERSE4-84] _ = x[REVERSE4-84]
_ = x[REVERSEN-85] _ = x[REVERSEN-85]
_ = x[DUPFROMALTSTACK-106] _ = x[INITSSLOT-86]
_ = x[TOALTSTACK-107] _ = x[INITSLOT-87]
_ = x[FROMALTSTACK-108] _ = x[LDSFLD0-88]
_ = x[LDSFLD1-89]
_ = x[LDSFLD2-90]
_ = x[LDSFLD3-91]
_ = x[LDSFLD4-92]
_ = x[LDSFLD5-93]
_ = x[LDSFLD6-94]
_ = x[LDSFLD-95]
_ = x[STSFLD0-96]
_ = x[STSFLD1-97]
_ = x[STSFLD2-98]
_ = x[STSFLD3-99]
_ = x[STSFLD4-100]
_ = x[STSFLD5-101]
_ = x[STSFLD6-102]
_ = x[STSFLD-103]
_ = x[LDLOC0-104]
_ = x[LDLOC1-105]
_ = x[LDLOC2-106]
_ = x[LDLOC3-107]
_ = x[LDLOC4-108]
_ = x[LDLOC5-109]
_ = x[LDLOC6-110]
_ = x[LDLOC-111]
_ = x[STLOC0-112]
_ = x[STLOC1-113]
_ = x[STLOC2-114]
_ = x[STLOC3-115]
_ = x[STLOC4-116]
_ = x[STLOC5-117]
_ = x[STLOC6-118]
_ = x[STLOC-119]
_ = x[LDARG0-120]
_ = x[LDARG1-121]
_ = x[LDARG2-122]
_ = x[LDARG3-123]
_ = x[LDARG4-124]
_ = x[LDARG5-125]
_ = x[LDARG6-126]
_ = x[LDARG-127]
_ = x[STARG0-128]
_ = x[STARG1-129]
_ = x[STARG2-130]
_ = x[STARG3-131]
_ = x[STARG4-132]
_ = x[STARG5-133]
_ = x[STARG6-134]
_ = x[STARG-135]
_ = x[CAT-139] _ = x[CAT-139]
_ = x[SUBSTR-140] _ = x[SUBSTR-140]
_ = x[LEFT-141] _ = x[LEFT-141]
@ -143,7 +190,7 @@ func _() {
_ = x[CONVERT-219] _ = x[CONVERT-219]
} }
const _Opcode_name = "PUSHINT8PUSHINT16PUSHINT32PUSHINT64PUSHINT128PUSHINT256PUSHAPUSHNULLPUSHDATA1PUSHDATA2PUSHDATA4PUSHM1PUSH0PUSH1PUSH2PUSH3PUSH4PUSH5PUSH6PUSH7PUSH8PUSH9PUSH10PUSH11PUSH12PUSH13PUSH14PUSH15PUSH16NOPJMPJMPLJMPIFJMPIFLJMPIFNOTJMPIFNOTLJMPEQJMPEQLJMPNEJMPNELJMPGTJMPGTLJMPGEJMPGELJMPLTJMPLTLJMPLEJMPLELCALLCALLLCALLAABORTASSERTTHROWRETSYSCALLDEPTHDROPNIPXDROPCLEARDUPOVERPICKTUCKSWAPOLDPUSH1ROLLREVERSE3REVERSE4REVERSENDUPFROMALTSTACKTOALTSTACKFROMALTSTACKCATSUBSTRLEFTRIGHTINVERTANDORXOREQUALNOTEQUALSIGNABSNEGATEINCDECADDSUBMULDIVMODSHLSHRNOTBOOLANDBOOLORNZNUMEQUALNUMNOTEQUALLTLTEGTGTEMINMAXWITHINPACKUNPACKNEWARRAY0NEWARRAYNEWARRAYTNEWSTRUCT0NEWSTRUCTNEWMAPSIZEHASKEYKEYSVALUESPICKITEMAPPENDSETITEMREVERSEITEMSREMOVECLEARITEMSISNULLISTYPECONVERT" const _Opcode_name = "PUSHINT8PUSHINT16PUSHINT32PUSHINT64PUSHINT128PUSHINT256PUSHAPUSHNULLPUSHDATA1PUSHDATA2PUSHDATA4PUSHM1PUSH0PUSH1PUSH2PUSH3PUSH4PUSH5PUSH6PUSH7PUSH8PUSH9PUSH10PUSH11PUSH12PUSH13PUSH14PUSH15PUSH16NOPJMPJMPLJMPIFJMPIFLJMPIFNOTJMPIFNOTLJMPEQJMPEQLJMPNEJMPNELJMPGTJMPGTLJMPGEJMPGELJMPLTJMPLTLJMPLEJMPLELCALLCALLLCALLAABORTASSERTTHROWRETSYSCALLDEPTHDROPNIPXDROPCLEARDUPOVERPICKTUCKSWAPOLDPUSH1ROLLREVERSE3REVERSE4REVERSENINITSSLOTINITSLOTLDSFLD0LDSFLD1LDSFLD2LDSFLD3LDSFLD4LDSFLD5LDSFLD6LDSFLDSTSFLD0STSFLD1STSFLD2STSFLD3STSFLD4STSFLD5STSFLD6STSFLDLDLOC0LDLOC1LDLOC2LDLOC3LDLOC4LDLOC5LDLOC6LDLOCSTLOC0STLOC1STLOC2STLOC3STLOC4STLOC5STLOC6STLOCLDARG0LDARG1LDARG2LDARG3LDARG4LDARG5LDARG6LDARGSTARG0STARG1STARG2STARG3STARG4STARG5STARG6STARGCATSUBSTRLEFTRIGHTINVERTANDORXOREQUALNOTEQUALSIGNABSNEGATEINCDECADDSUBMULDIVMODSHLSHRNOTBOOLANDBOOLORNZNUMEQUALNUMNOTEQUALLTLTEGTGTEMINMAXWITHINPACKUNPACKNEWARRAY0NEWARRAYNEWARRAYTNEWSTRUCT0NEWSTRUCTNEWMAPSIZEHASKEYKEYSVALUESPICKITEMAPPENDSETITEMREVERSEITEMSREMOVECLEARITEMSISNULLISTYPECONVERT"
var _Opcode_map = map[Opcode]string{ var _Opcode_map = map[Opcode]string{
0: _Opcode_name[0:8], 0: _Opcode_name[0:8],
@ -217,65 +264,112 @@ var _Opcode_map = map[Opcode]string{
83: _Opcode_name[390:398], 83: _Opcode_name[390:398],
84: _Opcode_name[398:406], 84: _Opcode_name[398:406],
85: _Opcode_name[406:414], 85: _Opcode_name[406:414],
106: _Opcode_name[414:429], 86: _Opcode_name[414:423],
107: _Opcode_name[429:439], 87: _Opcode_name[423:431],
108: _Opcode_name[439:451], 88: _Opcode_name[431:438],
139: _Opcode_name[451:454], 89: _Opcode_name[438:445],
140: _Opcode_name[454:460], 90: _Opcode_name[445:452],
141: _Opcode_name[460:464], 91: _Opcode_name[452:459],
142: _Opcode_name[464:469], 92: _Opcode_name[459:466],
144: _Opcode_name[469:475], 93: _Opcode_name[466:473],
145: _Opcode_name[475:478], 94: _Opcode_name[473:480],
146: _Opcode_name[478:480], 95: _Opcode_name[480:486],
147: _Opcode_name[480:483], 96: _Opcode_name[486:493],
151: _Opcode_name[483:488], 97: _Opcode_name[493:500],
152: _Opcode_name[488:496], 98: _Opcode_name[500:507],
153: _Opcode_name[496:500], 99: _Opcode_name[507:514],
154: _Opcode_name[500:503], 100: _Opcode_name[514:521],
155: _Opcode_name[503:509], 101: _Opcode_name[521:528],
156: _Opcode_name[509:512], 102: _Opcode_name[528:535],
157: _Opcode_name[512:515], 103: _Opcode_name[535:541],
158: _Opcode_name[515:518], 104: _Opcode_name[541:547],
159: _Opcode_name[518:521], 105: _Opcode_name[547:553],
160: _Opcode_name[521:524], 106: _Opcode_name[553:559],
161: _Opcode_name[524:527], 107: _Opcode_name[559:565],
162: _Opcode_name[527:530], 108: _Opcode_name[565:571],
168: _Opcode_name[530:533], 109: _Opcode_name[571:577],
169: _Opcode_name[533:536], 110: _Opcode_name[577:583],
170: _Opcode_name[536:539], 111: _Opcode_name[583:588],
171: _Opcode_name[539:546], 112: _Opcode_name[588:594],
172: _Opcode_name[546:552], 113: _Opcode_name[594:600],
177: _Opcode_name[552:554], 114: _Opcode_name[600:606],
179: _Opcode_name[554:562], 115: _Opcode_name[606:612],
180: _Opcode_name[562:573], 116: _Opcode_name[612:618],
181: _Opcode_name[573:575], 117: _Opcode_name[618:624],
182: _Opcode_name[575:578], 118: _Opcode_name[624:630],
183: _Opcode_name[578:580], 119: _Opcode_name[630:635],
184: _Opcode_name[580:583], 120: _Opcode_name[635:641],
185: _Opcode_name[583:586], 121: _Opcode_name[641:647],
186: _Opcode_name[586:589], 122: _Opcode_name[647:653],
187: _Opcode_name[589:595], 123: _Opcode_name[653:659],
192: _Opcode_name[595:599], 124: _Opcode_name[659:665],
193: _Opcode_name[599:605], 125: _Opcode_name[665:671],
194: _Opcode_name[605:614], 126: _Opcode_name[671:677],
195: _Opcode_name[614:622], 127: _Opcode_name[677:682],
196: _Opcode_name[622:631], 128: _Opcode_name[682:688],
197: _Opcode_name[631:641], 129: _Opcode_name[688:694],
198: _Opcode_name[641:650], 130: _Opcode_name[694:700],
200: _Opcode_name[650:656], 131: _Opcode_name[700:706],
202: _Opcode_name[656:660], 132: _Opcode_name[706:712],
203: _Opcode_name[660:666], 133: _Opcode_name[712:718],
204: _Opcode_name[666:670], 134: _Opcode_name[718:724],
205: _Opcode_name[670:676], 135: _Opcode_name[724:729],
206: _Opcode_name[676:684], 139: _Opcode_name[729:732],
207: _Opcode_name[684:690], 140: _Opcode_name[732:738],
208: _Opcode_name[690:697], 141: _Opcode_name[738:742],
209: _Opcode_name[697:709], 142: _Opcode_name[742:747],
210: _Opcode_name[709:715], 144: _Opcode_name[747:753],
211: _Opcode_name[715:725], 145: _Opcode_name[753:756],
216: _Opcode_name[725:731], 146: _Opcode_name[756:758],
217: _Opcode_name[731:737], 147: _Opcode_name[758:761],
219: _Opcode_name[737:744], 151: _Opcode_name[761:766],
152: _Opcode_name[766:774],
153: _Opcode_name[774:778],
154: _Opcode_name[778:781],
155: _Opcode_name[781:787],
156: _Opcode_name[787:790],
157: _Opcode_name[790:793],
158: _Opcode_name[793:796],
159: _Opcode_name[796:799],
160: _Opcode_name[799:802],
161: _Opcode_name[802:805],
162: _Opcode_name[805:808],
168: _Opcode_name[808:811],
169: _Opcode_name[811:814],
170: _Opcode_name[814:817],
171: _Opcode_name[817:824],
172: _Opcode_name[824:830],
177: _Opcode_name[830:832],
179: _Opcode_name[832:840],
180: _Opcode_name[840:851],
181: _Opcode_name[851:853],
182: _Opcode_name[853:856],
183: _Opcode_name[856:858],
184: _Opcode_name[858:861],
185: _Opcode_name[861:864],
186: _Opcode_name[864:867],
187: _Opcode_name[867:873],
192: _Opcode_name[873:877],
193: _Opcode_name[877:883],
194: _Opcode_name[883:892],
195: _Opcode_name[892:900],
196: _Opcode_name[900:909],
197: _Opcode_name[909:919],
198: _Opcode_name[919:928],
200: _Opcode_name[928:934],
202: _Opcode_name[934:938],
203: _Opcode_name[938:944],
204: _Opcode_name[944:948],
205: _Opcode_name[948:954],
206: _Opcode_name[954:962],
207: _Opcode_name[962:968],
208: _Opcode_name[968:975],
209: _Opcode_name[975:987],
210: _Opcode_name[987:993],
211: _Opcode_name[993:1003],
216: _Opcode_name[1003:1009],
217: _Opcode_name[1009:1015],
219: _Opcode_name[1015:1022],
} }
func (i Opcode) String() string { func (i Opcode) String() string {

View file

@ -75,6 +75,8 @@ type VM struct {
estack *Stack // execution stack. estack *Stack // execution stack.
astack *Stack // alt stack. astack *Stack // alt stack.
static *Slot
// Hash to verify in CHECKSIG/CHECKMULTISIG. // Hash to verify in CHECKSIG/CHECKMULTISIG.
checkhash []byte checkhash []byte
@ -557,15 +559,80 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
} }
v.estack.PushVal(result) v.estack.PushVal(result)
// Stack operations. case opcode.INITSSLOT:
case opcode.TOALTSTACK: if v.static != nil {
v.astack.Push(v.estack.Pop()) panic("already initialized")
}
if parameter[0] == 0 {
panic("zero argument")
}
v.static = v.newSlot(int(parameter[0]))
case opcode.FROMALTSTACK: case opcode.INITSLOT:
v.estack.Push(v.astack.Pop()) if ctx.local != nil || ctx.arguments != nil {
panic("already initialized")
}
if parameter[0] == 0 && parameter[1] == 0 {
panic("zero argument")
}
if parameter[0] > 0 {
ctx.local = v.newSlot(int(parameter[0]))
}
if parameter[1] > 0 {
sz := int(parameter[1])
ctx.arguments = v.newSlot(sz)
for i := 0; i < sz; i++ {
ctx.arguments.Set(i, v.estack.Pop().Item())
}
}
case opcode.DUPFROMALTSTACK: case opcode.LDSFLD0, opcode.LDSFLD1, opcode.LDSFLD2, opcode.LDSFLD3, opcode.LDSFLD4, opcode.LDSFLD5, opcode.LDSFLD6:
v.estack.Push(v.astack.Dup(0)) item := v.static.Get(int(op - opcode.LDSFLD0))
v.estack.PushVal(item)
case opcode.LDSFLD:
item := v.static.Get(int(parameter[0]))
v.estack.PushVal(item)
case opcode.STSFLD0, opcode.STSFLD1, opcode.STSFLD2, opcode.STSFLD3, opcode.STSFLD4, opcode.STSFLD5, opcode.STSFLD6:
item := v.estack.Pop().Item()
v.static.Set(int(op-opcode.STSFLD0), item)
case opcode.STSFLD:
item := v.estack.Pop().Item()
v.static.Set(int(parameter[0]), item)
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)
case opcode.LDLOC:
item := ctx.local.Get(int(parameter[0]))
v.estack.PushVal(item)
case opcode.STLOC0, opcode.STLOC1, opcode.STLOC2, opcode.STLOC3, opcode.STLOC4, opcode.STLOC5, opcode.STLOC6:
item := v.estack.Pop().Item()
ctx.local.Set(int(op-opcode.STLOC0), item)
case opcode.STLOC:
item := v.estack.Pop().Item()
ctx.local.Set(int(parameter[0]), item)
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)
case opcode.LDARG:
item := ctx.arguments.Get(int(parameter[0]))
v.estack.PushVal(item)
case opcode.STARG0, opcode.STARG1, opcode.STARG2, opcode.STARG3, opcode.STARG4, opcode.STARG5, opcode.STARG6:
item := v.estack.Pop().Item()
ctx.arguments.Set(int(op-opcode.STARG0), item)
case opcode.STARG:
item := v.estack.Pop().Item()
ctx.arguments.Set(int(parameter[0]), item)
case opcode.CAT: case opcode.CAT:
b := v.estack.Pop().Bytes() b := v.estack.Pop().Bytes()
@ -1134,8 +1201,9 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
case opcode.CALL, opcode.CALLL: case opcode.CALL, opcode.CALLL:
v.checkInvocationStackSize() v.checkInvocationStackSize()
newCtx := ctx.Copy() newCtx := ctx.Copy()
newCtx.local = nil
newCtx.arguments = nil
newCtx.rvcount = -1 newCtx.rvcount = -1
v.istack.PushVal(newCtx) v.istack.PushVal(newCtx)
@ -1149,6 +1217,8 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
} }
newCtx := ctx.Copy() newCtx := ctx.Copy()
newCtx.local = nil
newCtx.arguments = nil
newCtx.rvcount = -1 newCtx.rvcount = -1
v.istack.PushVal(newCtx) v.istack.PushVal(newCtx)
v.jumpIf(newCtx, ptr.pos, true) v.jumpIf(newCtx, ptr.pos, true)

View file

@ -370,10 +370,12 @@ func appendBigStruct(size uint16) []opcode.Opcode {
} }
return append(prog, return append(prog,
opcode.INITSSLOT, 1,
opcode.PUSHINT16, opcode.Opcode(size), opcode.Opcode(size>>8), // LE opcode.PUSHINT16, opcode.Opcode(size), opcode.Opcode(size>>8), // LE
opcode.PACK, opcode.NEWSTRUCT, opcode.PACK, opcode.NEWSTRUCT,
opcode.STSFLD0, opcode.LDSFLD0,
opcode.DUP, opcode.DUP,
opcode.PUSH0, opcode.NEWARRAY, opcode.TOALTSTACK, opcode.DUPFROMALTSTACK, opcode.PUSH0, opcode.NEWARRAY,
opcode.SWAP, opcode.SWAP,
opcode.APPEND, opcode.RET) opcode.APPEND, opcode.RET)
} }
@ -397,17 +399,17 @@ func TestStackLimit(t *testing.T) {
}{ }{
{opcode.PUSH2, 1}, {opcode.PUSH2, 1},
{opcode.NEWARRAY, 3}, // array + 2 items {opcode.NEWARRAY, 3}, // array + 2 items
{opcode.TOALTSTACK, 3}, {opcode.STSFLD0, 3},
{opcode.DUPFROMALTSTACK, 4}, {opcode.LDSFLD0, 4},
{opcode.NEWSTRUCT, 6}, // all items are copied {opcode.NEWSTRUCT, 6}, // all items are copied
{opcode.NEWMAP, 7}, {opcode.NEWMAP, 7},
{opcode.DUP, 8}, {opcode.DUP, 8},
{opcode.PUSH2, 9}, {opcode.PUSH2, 9},
{opcode.DUPFROMALTSTACK, 10}, {opcode.LDSFLD0, 10},
{opcode.SETITEM, 8}, // -3 items and 1 new element in map {opcode.SETITEM, 8}, // -3 items and 1 new element in map
{opcode.DUP, 9}, {opcode.DUP, 9},
{opcode.PUSH2, 10}, {opcode.PUSH2, 10},
{opcode.DUPFROMALTSTACK, 11}, {opcode.LDSFLD0, 11},
{opcode.SETITEM, 8}, // -3 items and no new elements in map {opcode.SETITEM, 8}, // -3 items and no new elements in map
{opcode.DUP, 9}, {opcode.DUP, 9},
{opcode.PUSH2, 10}, {opcode.PUSH2, 10},
@ -415,15 +417,18 @@ func TestStackLimit(t *testing.T) {
{opcode.DROP, 6}, // DROP map with no elements {opcode.DROP, 6}, // DROP map with no elements
} }
prog := make([]opcode.Opcode, len(expected)) prog := make([]opcode.Opcode, len(expected)+2)
prog[0] = opcode.INITSSLOT
prog[1] = 1
for i := range expected { for i := range expected {
prog[i] = expected[i].inst prog[i+2] = expected[i].inst
} }
vm := load(makeProgram(prog...)) vm := load(makeProgram(prog...))
require.NoError(t, vm.Step(), "failed to initialize static slot")
for i := range expected { for i := range expected {
require.NoError(t, vm.Step()) require.NoError(t, vm.Step())
require.Equal(t, expected[i].size, vm.refs.size) require.Equal(t, expected[i].size, vm.refs.size, "i: %d", i)
} }
} }
@ -477,18 +482,18 @@ func TestPushData4BigN(t *testing.T) {
} }
func getEnumeratorProg(n int, isIter bool) (prog []byte) { func getEnumeratorProg(n int, isIter bool) (prog []byte) {
prog = append(prog, byte(opcode.TOALTSTACK)) prog = []byte{byte(opcode.INITSSLOT), 1, byte(opcode.STSFLD0)}
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
prog = append(prog, byte(opcode.DUPFROMALTSTACK)) prog = append(prog, byte(opcode.LDSFLD0))
prog = append(prog, getSyscallProg("Neo.Enumerator.Next")...) prog = append(prog, getSyscallProg("Neo.Enumerator.Next")...)
prog = append(prog, byte(opcode.DUPFROMALTSTACK)) prog = append(prog, byte(opcode.LDSFLD0))
prog = append(prog, getSyscallProg("Neo.Enumerator.Value")...) prog = append(prog, getSyscallProg("Neo.Enumerator.Value")...)
if isIter { if isIter {
prog = append(prog, byte(opcode.DUPFROMALTSTACK)) prog = append(prog, byte(opcode.LDSFLD0))
prog = append(prog, getSyscallProg("Neo.Iterator.Key")...) prog = append(prog, getSyscallProg("Neo.Iterator.Key")...)
} }
} }
prog = append(prog, byte(opcode.DUPFROMALTSTACK)) prog = append(prog, byte(opcode.LDSFLD0))
prog = append(prog, getSyscallProg("Neo.Enumerator.Next")...) prog = append(prog, getSyscallProg("Neo.Enumerator.Next")...)
return return
@ -583,7 +588,6 @@ func TestIteratorConcat(t *testing.T) {
func TestIteratorKeys(t *testing.T) { func TestIteratorKeys(t *testing.T) {
prog := getSyscallProg("Neo.Iterator.Create") prog := getSyscallProg("Neo.Iterator.Create")
prog = append(prog, getSyscallProg("Neo.Iterator.Keys")...) prog = append(prog, getSyscallProg("Neo.Iterator.Keys")...)
prog = append(prog, byte(opcode.TOALTSTACK), byte(opcode.DUPFROMALTSTACK))
prog = append(prog, getEnumeratorProg(2, false)...) prog = append(prog, getEnumeratorProg(2, false)...)
v := load(prog) v := load(prog)
@ -604,7 +608,6 @@ func TestIteratorKeys(t *testing.T) {
func TestIteratorValues(t *testing.T) { func TestIteratorValues(t *testing.T) {
prog := getSyscallProg("Neo.Iterator.Create") prog := getSyscallProg("Neo.Iterator.Create")
prog = append(prog, getSyscallProg("Neo.Iterator.Values")...) prog = append(prog, getSyscallProg("Neo.Iterator.Values")...)
prog = append(prog, byte(opcode.TOALTSTACK), byte(opcode.DUPFROMALTSTACK))
prog = append(prog, getEnumeratorProg(2, false)...) prog = append(prog, getEnumeratorProg(2, false)...)
v := load(prog) v := load(prog)
@ -716,11 +719,11 @@ func TestSerializeArrayBad(t *testing.T) {
} }
func TestSerializeDupInteger(t *testing.T) { func TestSerializeDupInteger(t *testing.T) {
prog := []byte{ prog := makeProgram(
byte(opcode.PUSH0), byte(opcode.NEWARRAY), opcode.PUSH0, opcode.NEWARRAY, opcode.INITSSLOT, 1,
byte(opcode.DUP), byte(opcode.PUSH2), byte(opcode.DUP), byte(opcode.TOALTSTACK), byte(opcode.APPEND), opcode.DUP, opcode.PUSH2, opcode.DUP, opcode.STSFLD0, opcode.APPEND,
byte(opcode.DUP), byte(opcode.FROMALTSTACK), byte(opcode.APPEND), opcode.DUP, opcode.LDSFLD0, opcode.APPEND,
} )
vm := load(append(prog, getSerializeProg()...)) vm := load(append(prog, getSerializeProg()...))
runVM(t, vm) runVM(t, vm)
@ -801,9 +804,10 @@ func TestSerializeInterop(t *testing.T) {
func callNTimes(n uint16) []byte { func callNTimes(n uint16) []byte {
return makeProgram( return makeProgram(
opcode.PUSHINT16, opcode.Opcode(n), opcode.Opcode(n>>8), // little-endian opcode.PUSHINT16, opcode.Opcode(n), opcode.Opcode(n>>8), // little-endian
opcode.TOALTSTACK, opcode.DUPFROMALTSTACK, opcode.INITSSLOT, 1,
opcode.STSFLD0, opcode.LDSFLD0,
opcode.JMPIF, 0x3, opcode.RET, opcode.JMPIF, 0x3, opcode.RET,
opcode.FROMALTSTACK, opcode.DEC, opcode.LDSFLD0, opcode.DEC,
opcode.CALL, 0xF9) // -7 -> JMP to TOALTSTACK) opcode.CALL, 0xF9) // -7 -> JMP to TOALTSTACK)
} }
@ -1171,9 +1175,10 @@ func testNEWARRAYIssue437(t *testing.T, i1, i2 opcode.Opcode, appended bool) {
prog := makeProgram( prog := makeProgram(
opcode.PUSH2, i1, opcode.PUSH2, i1,
opcode.DUP, opcode.PUSH3, opcode.APPEND, opcode.DUP, opcode.PUSH3, opcode.APPEND,
opcode.TOALTSTACK, opcode.DUPFROMALTSTACK, i2, opcode.INITSSLOT, 1,
opcode.STSFLD0, opcode.LDSFLD0, i2,
opcode.DUP, opcode.PUSH4, opcode.APPEND, opcode.DUP, opcode.PUSH4, opcode.APPEND,
opcode.FROMALTSTACK, opcode.PUSH5, opcode.APPEND) opcode.LDSFLD0, opcode.PUSH5, opcode.APPEND)
vm := load(prog) vm := load(prog)
vm.Run() vm.Run()
@ -1233,8 +1238,8 @@ func TestAPPEND(t *testing.T) {
} }
func TestAPPENDCloneStruct(t *testing.T) { func TestAPPENDCloneStruct(t *testing.T) {
prog := makeProgram(opcode.DUP, opcode.PUSH0, opcode.NEWSTRUCT, opcode.TOALTSTACK, prog := makeProgram(opcode.DUP, opcode.PUSH0, opcode.NEWSTRUCT, opcode.INITSSLOT, 1, opcode.STSFLD0,
opcode.DUPFROMALTSTACK, opcode.APPEND, opcode.FROMALTSTACK, opcode.PUSH1, opcode.APPEND) opcode.LDSFLD0, opcode.APPEND, opcode.LDSFLD0, opcode.PUSH1, opcode.APPEND)
arr := []StackItem{&StructItem{[]StackItem{}}} arr := []StackItem{&StructItem{[]StackItem{}}}
runWithArgs(t, prog, NewArrayItem(arr), NewArrayItem(nil)) runWithArgs(t, prog, NewArrayItem(arr), NewArrayItem(nil))
} }
@ -2361,6 +2366,42 @@ func TestBitAndNumericOpcodes(t *testing.T) {
} }
} }
func TestSLOTOpcodes(t *testing.T) {
t.Run("Fail", func(t *testing.T) {
t.Run("EmptyStatic", getTestFuncForVM(makeProgram(opcode.INITSSLOT, 0), nil))
t.Run("EmptyLocal", getTestFuncForVM(makeProgram(opcode.INITSLOT, 0, 0), nil))
t.Run("NotEnoughArguments", getTestFuncForVM(makeProgram(opcode.INITSSLOT, 0, 2), nil, 1))
t.Run("DoubleStatic", getTestFuncForVM(makeProgram(opcode.INITSSLOT, 1, opcode.INITSSLOT, 1), nil))
t.Run("DoubleLocal", getTestFuncForVM(makeProgram(opcode.INITSLOT, 1, 0, opcode.INITSLOT, 1, 0), nil))
t.Run("DoubleArgument", getTestFuncForVM(makeProgram(opcode.INITSLOT, 0, 1, opcode.INITSLOT, 0, 1), nil, 1, 2))
t.Run("LoadBigStatic", getTestFuncForVM(makeProgram(opcode.INITSSLOT, 2, opcode.LDSFLD2), nil))
t.Run("LoadBigLocal", getTestFuncForVM(makeProgram(opcode.INITSLOT, 2, 2, opcode.LDLOC2), nil, 1, 2))
t.Run("LoadBigArgument", getTestFuncForVM(makeProgram(opcode.INITSLOT, 2, 2, opcode.LDARG2), nil, 1, 2))
t.Run("StoreBigStatic", getTestFuncForVM(makeProgram(opcode.INITSSLOT, 2, opcode.STSFLD2), nil, 0))
t.Run("StoreBigLocal", getTestFuncForVM(makeProgram(opcode.INITSLOT, 2, 2, opcode.STLOC2), nil, 0, 1, 2))
t.Run("StoreBigArgument", getTestFuncForVM(makeProgram(opcode.INITSLOT, 2, 2, opcode.STARG2), nil, 0, 1, 2))
})
t.Run("Default", func(t *testing.T) {
t.Run("DefaultStatic", getTestFuncForVM(makeProgram(opcode.INITSSLOT, 2, opcode.LDSFLD1), NullItem{}))
t.Run("DefaultLocal", getTestFuncForVM(makeProgram(opcode.INITSLOT, 2, 0, opcode.LDLOC1), NullItem{}))
t.Run("DefaultArgument", getTestFuncForVM(makeProgram(opcode.INITSLOT, 0, 2, opcode.LDARG1), 2, 2, 1))
})
t.Run("Set/Get", func(t *testing.T) {
t.Run("FailCrossLoads", func(t *testing.T) {
t.Run("Static/Local", getTestFuncForVM(makeProgram(opcode.INITSSLOT, 2, opcode.LDLOC1), nil))
t.Run("Static/Argument", getTestFuncForVM(makeProgram(opcode.INITSSLOT, 2, opcode.LDARG1), nil))
t.Run("Local/Argument", getTestFuncForVM(makeProgram(opcode.INITSLOT, 0, 2, opcode.LDLOC1), nil))
t.Run("Argument/Local", getTestFuncForVM(makeProgram(opcode.INITSLOT, 2, 0, opcode.LDARG1), nil))
})
t.Run("Static", getTestFuncForVM(makeProgram(opcode.INITSSLOT, 8, opcode.STSFLD, 7, opcode.LDSFLD, 7), 42, 42))
t.Run("Local", getTestFuncForVM(makeProgram(opcode.INITSLOT, 8, 0, opcode.STLOC, 7, opcode.LDLOC, 7), 42, 42))
t.Run("Argument", getTestFuncForVM(makeProgram(opcode.INITSLOT, 0, 2, opcode.STARG, 1, opcode.LDARG, 1), 42, 42, 1, 2))
})
}
func makeProgram(opcodes ...opcode.Opcode) []byte { func makeProgram(opcodes ...opcode.Opcode) []byte {
prog := make([]byte, len(opcodes)+1) // RET prog := make([]byte, len(opcodes)+1) // RET
for i := 0; i < len(opcodes); i++ { for i := 0; i < len(opcodes); i++ {