diff --git a/pkg/compiler/analysis.go b/pkg/compiler/analysis.go index fc2c36067..c2382cfd9 100644 --- a/pkg/compiler/analysis.go +++ b/pkg/compiler/analysis.go @@ -1,11 +1,14 @@ package compiler import ( + "errors" "fmt" "go/ast" "go/constant" "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" ) @@ -47,6 +50,24 @@ func typeAndValueForField(fld *types.Var) (types.TypeAndValue, error) { 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 // them with the stack size of the function. func countGlobals(f ast.Node) (i int64) { diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index 709747e57..ca1766d97 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -43,6 +43,8 @@ type codegen struct { // Current funcScope being converted. scope *funcScope + globals map[string]int + // A mapping from label's names to their ids. labels map[labelWithType]uint16 // A list of nested label names together with evaluation stack depth. @@ -82,6 +84,14 @@ type labelWithStackSize struct { sz int } +type varType int + +const ( + varGlobal varType = iota + varLocal + varArgument +) + // newLabel creates a new label to jump to func (c *codegen) newLabel() (l uint16) { 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) { emit.Int(c.prog.BinWriter, int64(i)) emit.Opcode(c.prog.BinWriter, opcode.PICKITEM) @@ -186,6 +168,81 @@ func (c *codegen) emitStoreStructField(i int) { 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. // If we call this in convertFuncDecl then it will load all global variables // 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 // to the stack size of the function. - emit.Int(c.prog.BinWriter, f.stackSize()+countGlobals(file)) - emit.Opcode(c.prog.BinWriter, opcode.NEWARRAY) - emit.Opcode(c.prog.BinWriter, opcode.TOALTSTACK) + sizeLoc := f.countLocals() + if sizeLoc > 255 { + 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. // 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") return } - l := c.scope.newLocal(ident.Name) - c.emitStoreLocal(l) + // only create an argument here, it will be stored via INITSLOT + c.scope.newVariable(varArgument, ident.Name) } } // Load the arguments in scope. for _, arg := range decl.Type.Params.List { for _, id := range arg.Names { - l := c.scope.newLocal(id.Name) - c.emitStoreLocal(l) + // only create an argument here, it will be stored via INITSLOT + 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) @@ -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. if !lastStmtIsReturn(decl) { 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) } @@ -305,25 +363,32 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { switch t := spec.(type) { case *ast.ValueSpec: 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) } if len(t.Values) != 0 { for i, val := range t.Values { ast.Walk(c, val) - l := c.scope.loadLocal(t.Names[i].Name) - c.emitStoreLocal(l) + c.emitStoreVar(t.Names[i].Name) } } else if c.isCompoundArrayType(t.Type) { emit.Opcode(c.prog.BinWriter, opcode.PUSH0) emit.Opcode(c.prog.BinWriter, opcode.NEWARRAY) - l := c.scope.loadLocal(t.Names[0].Name) - c.emitStoreLocal(l) + c.emitStoreVar(t.Names[0].Name) } else if n, ok := c.isStructType(t.Type); ok { emit.Int(c.prog.BinWriter, int64(n)) emit.Opcode(c.prog.BinWriter, opcode.NEWSTRUCT) - l := c.scope.loadLocal(t.Names[0].Name) - c.emitStoreLocal(l) + c.emitStoreVar(t.Names[0].Name) + } else { + for _, id := range t.Names { + c.emitDefault(t.Type) + c.emitStoreVar(id.Name) + } } } } @@ -337,15 +402,17 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { case *ast.Ident: switch n.Tok { 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 c.convertToken(n.Tok) - l := c.scope.loadLocal(t.Name) - c.emitStoreLocal(l) + c.emitStoreVar(t.Name) case token.DEFINE: if !multiRet { c.registerDebugVariable(t.Name, n.Rhs[i]) } + if t.Name != "_" { + c.scope.newLocal(t.Name) + } fallthrough default: if i == 0 || !multiRet { @@ -355,8 +422,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { if t.Name == "_" { emit.Opcode(c.prog.BinWriter, opcode.DROP) } else { - l := c.scope.loadLocal(t.Name) - c.emitStoreLocal(l) + c.emitStoreVar(t.Name) } } @@ -366,7 +432,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { ast.Walk(c, n.Rhs[i]) typ := c.typeInfo.ObjectOf(expr).Type().Underlying() 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 c.emitStoreStructField(i) // store the field } @@ -380,7 +446,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { case *ast.IndexExpr: ast.Walk(c, n.Rhs[i]) name := t.X.(*ast.Ident).Name - c.emitLoadLocal(name) + c.emitLoadVar(name) switch ind := t.Index.(type) { case *ast.BasicLit: indexStr := ind.Value @@ -391,7 +457,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { } c.emitStoreStructField(index) case *ast.Ident: - c.emitLoadLocal(ind.Name) + c.emitLoadVar(ind.Name) emit.Opcode(c.prog.BinWriter, opcode.ROT) emit.Opcode(c.prog.BinWriter, opcode.SETITEM) default: @@ -404,7 +470,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { case *ast.SliceExpr: name := n.X.(*ast.Ident).Name - c.emitLoadLocal(name) + c.emitLoadVar(name) if n.Low != nil { ast.Walk(c, n.Low) @@ -442,7 +508,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { for i := len(results.List) - 1; i >= 0; i-- { names := results.List[i].Names for j := len(names) - 1; j >= 0; j-- { - c.emitLoadLocal(names[j].Name) + c.emitLoadVar(names[j].Name) } } } @@ -454,8 +520,6 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { } 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) return nil @@ -557,7 +621,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { } else if tv := c.typeInfo.Types[n]; tv.Value != nil { c.emitLoadConst(tv) } else { - c.emitLoadLocal(n.Name) + c.emitLoadVar(n.Name) } return nil @@ -723,7 +787,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { c.convertBuiltin(n) case name != "": // 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) case isSyscall(f): c.convertSyscall(n, f.selector.Name, f.name) @@ -738,7 +802,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { case *ast.Ident: typ := c.typeInfo.ObjectOf(t).Type().Underlying() 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) c.emitLoadField(i) // load the field } @@ -777,8 +841,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { // for i := 0; i < 10; i++ {} // Where the post stmt is ( i++ ) if ident, ok := n.X.(*ast.Ident); ok { - pos := c.scope.loadLocal(ident.Name) - c.emitStoreLocal(pos) + c.emitStoreVar(ident.Name) } return nil @@ -913,9 +976,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { if n.Key != nil { emit.Opcode(c.prog.BinWriter, opcode.DUP) - - pos := c.scope.loadLocal(n.Key.(*ast.Ident).Name) - c.emitStoreLocal(pos) + c.emitStoreVar(n.Key.(*ast.Ident).Name) } ast.Walk(c, n.Body) @@ -1315,6 +1376,8 @@ func (c *codegen) compile(info *buildInfo, pkg *loader.PackageInfo) error { } } + c.traverseGlobals(mainFile) + // convert the entry point first. c.convertFuncDecl(mainFile, main) @@ -1354,6 +1417,7 @@ func newCodegen(info *buildInfo, pkg *loader.PackageInfo) *codegen { l: []int{}, funcs: map[string]*funcScope{}, lambda: map[string]*funcScope{}, + globals: map[string]int{}, labels: map[labelWithType]uint16{}, typeInfo: &pkg.Info, diff --git a/pkg/compiler/debug.go b/pkg/compiler/debug.go index 0d607eaae..29af3f425 100644 --- a/pkg/compiler/debug.go +++ b/pkg/compiler/debug.go @@ -115,6 +115,10 @@ type Event struct { } func (c *codegen) saveSequencePoint(n ast.Node) { + if c.scope == nil { + // do not save globals for now + return + } fset := c.buildInfo.program.Fset start := fset.Position(n.Pos()) end := fset.Position(n.End()) @@ -143,6 +147,10 @@ func (c *codegen) emitDebugInfo() *DebugInfo { } func (c *codegen) registerDebugVariable(name string, expr ast.Expr) { + if c.scope == nil { + // do not save globals for now + return + } typ := c.scTypeFromExpr(expr) c.scope.variables = append(c.scope.variables, name+","+typ) } diff --git a/pkg/compiler/func_scope.go b/pkg/compiler/func_scope.go index d3f3810fc..d9e81916d 100644 --- a/pkg/compiler/func_scope.go +++ b/pkg/compiler/func_scope.go @@ -27,7 +27,8 @@ type funcScope struct { variables []string // Local variables - locals map[string]int + locals map[string]int + arguments map[string]int // voidCalls are basically functions that return their value // into nothing. The stack has their return value but there @@ -51,6 +52,7 @@ func newFuncScope(decl *ast.FuncDecl, label uint16) *funcScope { decl: decl, label: label, locals: map[string]int{}, + arguments: map[string]int{}, voidCalls: map[*ast.CallExpr]bool{}, variables: []string{}, i: -1, @@ -84,7 +86,7 @@ func (c *funcScope) analyzeVoidCalls(node ast.Node) bool { return true } -func (c *funcScope) stackSize() int64 { +func (c *funcScope) countLocals() int { size := 0 ast.Inspect(c.decl, func(n ast.Node) bool { switch n := n.(type) { @@ -110,28 +112,38 @@ func (c *funcScope) stackSize() int64 { } return true }) + return size +} - numArgs := len(c.decl.Type.Params.List) - // Also take care of struct methods recv: e.g. (t Token).Foo(). +func (c *funcScope) countArgs() int { + n := c.decl.Type.Params.NumFields() 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)) } +// 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. func (c *funcScope) newLocal(name string) int { - c.i++ - 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 + return c.newVariable(varLocal, name) } diff --git a/pkg/compiler/global_test.go b/pkg/compiler/global_test.go new file mode 100644 index 000000000..ede29b849 --- /dev/null +++ b/pkg/compiler/global_test.go @@ -0,0 +1,21 @@ +package compiler_test + +import ( + "math/big" + "testing" +) + +func TestChangeGlobal(t *testing.T) { + src := `package foo + var a int + func Main() int { + setLocal() + set42() + setLocal() + return a + } + func set42() { a = 42 } + func setLocal() { a := 10; _ = a }` + + eval(t, src, big.NewInt(42)) +} diff --git a/pkg/compiler/lambda_test.go b/pkg/compiler/lambda_test.go new file mode 100644 index 000000000..d9ab32613 --- /dev/null +++ b/pkg/compiler/lambda_test.go @@ -0,0 +1,15 @@ +package compiler_test + +import ( + "math/big" + "testing" +) + +func TestFuncLiteral(t *testing.T) { + src := `package foo + func Main() int { + inc := func(x int) int { return x + 1 } + return inc(1) + inc(2) + }` + eval(t, src, big.NewInt(5)) +} diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index e038a584c..4bac91ab6 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -48,18 +48,18 @@ type rpcTestCase struct { check func(t *testing.T, e *executor, result interface{}) } -const testContractHash = "a4bea0d56fad00a972135d54b381516205d78484" +const testContractHash = "33f3421677fab7f620bd70582f468b4a18df1e5d" var rpcTestCases = map[string][]rpcTestCase{ "getapplicationlog": { { name: "positive", - params: `["c296c0929350d051b9b40cace54db5a3eac4b730a8851e958795d44918f23c08"]`, + params: `["3f1579e797fedb83b66a85fe21d427a119d0e25ef662582e56393fd0d70e4691"]`, result: func(e *executor) interface{} { return &result.ApplicationLog{} }, check: func(t *testing.T, e *executor, acc interface{}) { res, ok := acc.(*result.ApplicationLog) require.True(t, ok) - expectedTxHash, err := util.Uint256DecodeStringLE("c296c0929350d051b9b40cace54db5a3eac4b730a8851e958795d44918f23c08") + expectedTxHash, err := util.Uint256DecodeStringLE("3f1579e797fedb83b66a85fe21d427a119d0e25ef662582e56393fd0d70e4691") require.NoError(t, err) assert.Equal(t, expectedTxHash, res.TxHash) assert.Equal(t, 1, len(res.Executions)) diff --git a/pkg/rpc/server/testdata/test_contract.avm b/pkg/rpc/server/testdata/test_contract.avm index 4afd3f630..ed72f09df 100755 Binary files a/pkg/rpc/server/testdata/test_contract.avm and b/pkg/rpc/server/testdata/test_contract.avm differ diff --git a/pkg/rpc/server/testdata/testblocks.acc b/pkg/rpc/server/testdata/testblocks.acc index ea5e93376..16577dde5 100644 Binary files a/pkg/rpc/server/testdata/testblocks.acc and b/pkg/rpc/server/testdata/testblocks.acc differ diff --git a/pkg/vm/context.go b/pkg/vm/context.go index 4c610ee9e..08c439ac4 100644 --- a/pkg/vm/context.go +++ b/pkg/vm/context.go @@ -34,6 +34,9 @@ type Context struct { // Alt stack pointer. astack *Stack + local *Slot + arguments *Slot + // Script hash of the prog. 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, 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 + case opcode.INITSLOT: + numtoread = 2 case opcode.JMPL, opcode.JMPIFL, opcode.JMPIFNOTL, opcode.JMPEQL, opcode.JMPNEL, opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLTL, opcode.JMPLEL, opcode.CALLL, opcode.SYSCALL, opcode.PUSHA: diff --git a/pkg/vm/opcode/opcode.go b/pkg/vm/opcode/opcode.go index b6641747f..026e1b01e 100644 --- a/pkg/vm/opcode/opcode.go +++ b/pkg/vm/opcode/opcode.go @@ -93,16 +93,63 @@ const ( REVERSE4 Opcode = 0x54 REVERSEN Opcode = 0x55 - // Old stack opcodes - DUPFROMALTSTACK Opcode = 0x6A - TOALTSTACK Opcode = 0x6B - FROMALTSTACK Opcode = 0x6C + // Slots + INITSSLOT Opcode = 0x56 + INITSLOT Opcode = 0x57 + 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 - CAT Opcode = 0x7E - SUBSTR Opcode = 0x7F - LEFT Opcode = 0x80 - RIGHT Opcode = 0x81 + CAT Opcode = 0x8B + SUBSTR Opcode = 0x8C + LEFT Opcode = 0x8D + RIGHT Opcode = 0x8E // Bitwise logic INVERT Opcode = 0x90 diff --git a/pkg/vm/opcode/opcode_string.go b/pkg/vm/opcode/opcode_string.go index ad726d7e0..74852c03e 100644 --- a/pkg/vm/opcode/opcode_string.go +++ b/pkg/vm/opcode/opcode_string.go @@ -82,13 +82,60 @@ func _() { _ = x[REVERSE3-83] _ = x[REVERSE4-84] _ = x[REVERSEN-85] - _ = x[DUPFROMALTSTACK-106] - _ = x[TOALTSTACK-107] - _ = x[FROMALTSTACK-108] - _ = x[CAT-126] - _ = x[SUBSTR-127] - _ = x[LEFT-128] - _ = x[RIGHT-129] + _ = x[INITSSLOT-86] + _ = x[INITSLOT-87] + _ = 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[SUBSTR-140] + _ = x[LEFT-141] + _ = x[RIGHT-142] _ = x[INVERT-144] _ = x[AND-145] _ = x[OR-146] @@ -143,7 +190,7 @@ func _() { _ = x[CONVERT-219] } -const _Opcode_name = "PUSHINT8PUSHINT16PUSHINT32PUSHINT64PUSHINT128PUSHINT256PUSHAPUSHNULLPUSHDATA1PUSHDATA2PUSHDATA4PUSHM1PUSH0PUSH1PUSH2PUSH3PUSH4PUSH5PUSH6PUSH7PUSH8PUSH9PUSH10PUSH11PUSH12PUSH13PUSH14PUSH15PUSH16NOPJMPJMPLJMPIFJMPIFLJMPIFNOTJMPIFNOTLJMPEQJMPEQLJMPNEJMPNELJMPGTJMPGTLJMPGEJMPGELJMPLTJMPLTLJMPLEJMPLELCALLCALLLCALLAABORTASSERTTHROWRETSYSCALLDEPTHDROPNIPXDROPCLEARDUPOVERPICKTUCKSWAPOLDPUSH1ROLLREVERSE3REVERSE4REVERSENDUPFROMALTSTACKTOALTSTACKFROMALTSTACKCATSUBSTRLEFTRIGHTINVERTANDORXOREQUALNOTEQUALSIGNABSNEGATEINCDECADDSUBMULDIVMODSHLSHRNOTBOOLANDBOOLORNZNUMEQUALNUMNOTEQUALLTLTEGTGTEMINMAXWITHINPACKUNPACKNEWARRAY0NEWARRAYNEWARRAYTNEWSTRUCT0NEWSTRUCTNEWMAPSIZEHASKEYKEYSVALUESPICKITEMAPPENDSETITEMREVERSEITEMSREMOVECLEARITEMSISNULLISTYPECONVERT" +const _Opcode_name = "PUSHINT8PUSHINT16PUSHINT32PUSHINT64PUSHINT128PUSHINT256PUSHAPUSHNULLPUSHDATA1PUSHDATA2PUSHDATA4PUSHM1PUSH0PUSH1PUSH2PUSH3PUSH4PUSH5PUSH6PUSH7PUSH8PUSH9PUSH10PUSH11PUSH12PUSH13PUSH14PUSH15PUSH16NOPJMPJMPLJMPIFJMPIFLJMPIFNOTJMPIFNOTLJMPEQJMPEQLJMPNEJMPNELJMPGTJMPGTLJMPGEJMPGELJMPLTJMPLTLJMPLEJMPLELCALLCALLLCALLAABORTASSERTTHROWRETSYSCALLDEPTHDROPNIPXDROPCLEARDUPOVERPICKTUCKSWAPOLDPUSH1ROLLREVERSE3REVERSE4REVERSENINITSSLOTINITSLOTLDSFLD0LDSFLD1LDSFLD2LDSFLD3LDSFLD4LDSFLD5LDSFLD6LDSFLDSTSFLD0STSFLD1STSFLD2STSFLD3STSFLD4STSFLD5STSFLD6STSFLDLDLOC0LDLOC1LDLOC2LDLOC3LDLOC4LDLOC5LDLOC6LDLOCSTLOC0STLOC1STLOC2STLOC3STLOC4STLOC5STLOC6STLOCLDARG0LDARG1LDARG2LDARG3LDARG4LDARG5LDARG6LDARGSTARG0STARG1STARG2STARG3STARG4STARG5STARG6STARGCATSUBSTRLEFTRIGHTINVERTANDORXOREQUALNOTEQUALSIGNABSNEGATEINCDECADDSUBMULDIVMODSHLSHRNOTBOOLANDBOOLORNZNUMEQUALNUMNOTEQUALLTLTEGTGTEMINMAXWITHINPACKUNPACKNEWARRAY0NEWARRAYNEWARRAYTNEWSTRUCT0NEWSTRUCTNEWMAPSIZEHASKEYKEYSVALUESPICKITEMAPPENDSETITEMREVERSEITEMSREMOVECLEARITEMSISNULLISTYPECONVERT" var _Opcode_map = map[Opcode]string{ 0: _Opcode_name[0:8], @@ -217,65 +264,112 @@ var _Opcode_map = map[Opcode]string{ 83: _Opcode_name[390:398], 84: _Opcode_name[398:406], 85: _Opcode_name[406:414], - 106: _Opcode_name[414:429], - 107: _Opcode_name[429:439], - 108: _Opcode_name[439:451], - 126: _Opcode_name[451:454], - 127: _Opcode_name[454:460], - 128: _Opcode_name[460:464], - 129: _Opcode_name[464:469], - 144: _Opcode_name[469:475], - 145: _Opcode_name[475:478], - 146: _Opcode_name[478:480], - 147: _Opcode_name[480:483], - 151: _Opcode_name[483:488], - 152: _Opcode_name[488:496], - 153: _Opcode_name[496:500], - 154: _Opcode_name[500:503], - 155: _Opcode_name[503:509], - 156: _Opcode_name[509:512], - 157: _Opcode_name[512:515], - 158: _Opcode_name[515:518], - 159: _Opcode_name[518:521], - 160: _Opcode_name[521:524], - 161: _Opcode_name[524:527], - 162: _Opcode_name[527:530], - 168: _Opcode_name[530:533], - 169: _Opcode_name[533:536], - 170: _Opcode_name[536:539], - 171: _Opcode_name[539:546], - 172: _Opcode_name[546:552], - 177: _Opcode_name[552:554], - 179: _Opcode_name[554:562], - 180: _Opcode_name[562:573], - 181: _Opcode_name[573:575], - 182: _Opcode_name[575:578], - 183: _Opcode_name[578:580], - 184: _Opcode_name[580:583], - 185: _Opcode_name[583:586], - 186: _Opcode_name[586:589], - 187: _Opcode_name[589:595], - 192: _Opcode_name[595:599], - 193: _Opcode_name[599:605], - 194: _Opcode_name[605:614], - 195: _Opcode_name[614:622], - 196: _Opcode_name[622:631], - 197: _Opcode_name[631:641], - 198: _Opcode_name[641:650], - 200: _Opcode_name[650:656], - 202: _Opcode_name[656:660], - 203: _Opcode_name[660:666], - 204: _Opcode_name[666:670], - 205: _Opcode_name[670:676], - 206: _Opcode_name[676:684], - 207: _Opcode_name[684:690], - 208: _Opcode_name[690:697], - 209: _Opcode_name[697:709], - 210: _Opcode_name[709:715], - 211: _Opcode_name[715:725], - 216: _Opcode_name[725:731], - 217: _Opcode_name[731:737], - 219: _Opcode_name[737:744], + 86: _Opcode_name[414:423], + 87: _Opcode_name[423:431], + 88: _Opcode_name[431:438], + 89: _Opcode_name[438:445], + 90: _Opcode_name[445:452], + 91: _Opcode_name[452:459], + 92: _Opcode_name[459:466], + 93: _Opcode_name[466:473], + 94: _Opcode_name[473:480], + 95: _Opcode_name[480:486], + 96: _Opcode_name[486:493], + 97: _Opcode_name[493:500], + 98: _Opcode_name[500:507], + 99: _Opcode_name[507:514], + 100: _Opcode_name[514:521], + 101: _Opcode_name[521:528], + 102: _Opcode_name[528:535], + 103: _Opcode_name[535:541], + 104: _Opcode_name[541:547], + 105: _Opcode_name[547:553], + 106: _Opcode_name[553:559], + 107: _Opcode_name[559:565], + 108: _Opcode_name[565:571], + 109: _Opcode_name[571:577], + 110: _Opcode_name[577:583], + 111: _Opcode_name[583:588], + 112: _Opcode_name[588:594], + 113: _Opcode_name[594:600], + 114: _Opcode_name[600:606], + 115: _Opcode_name[606:612], + 116: _Opcode_name[612:618], + 117: _Opcode_name[618:624], + 118: _Opcode_name[624:630], + 119: _Opcode_name[630:635], + 120: _Opcode_name[635:641], + 121: _Opcode_name[641:647], + 122: _Opcode_name[647:653], + 123: _Opcode_name[653:659], + 124: _Opcode_name[659:665], + 125: _Opcode_name[665:671], + 126: _Opcode_name[671:677], + 127: _Opcode_name[677:682], + 128: _Opcode_name[682:688], + 129: _Opcode_name[688:694], + 130: _Opcode_name[694:700], + 131: _Opcode_name[700:706], + 132: _Opcode_name[706:712], + 133: _Opcode_name[712:718], + 134: _Opcode_name[718:724], + 135: _Opcode_name[724:729], + 139: _Opcode_name[729:732], + 140: _Opcode_name[732:738], + 141: _Opcode_name[738:742], + 142: _Opcode_name[742:747], + 144: _Opcode_name[747:753], + 145: _Opcode_name[753:756], + 146: _Opcode_name[756:758], + 147: _Opcode_name[758:761], + 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 { diff --git a/pkg/vm/ref_counter.go b/pkg/vm/ref_counter.go new file mode 100644 index 000000000..c89f07983 --- /dev/null +++ b/pkg/vm/ref_counter.go @@ -0,0 +1,62 @@ +package vm + +// refCounter represents reference counter for the VM. +type refCounter struct { + items map[StackItem]int + size int +} + +func newRefCounter() *refCounter { + return &refCounter{ + items: make(map[StackItem]int), + } +} + +// Add adds an item to the reference counter. +func (r *refCounter) Add(item StackItem) { + r.size++ + + switch item.(type) { + case *ArrayItem, *StructItem, *MapItem: + if r.items[item]++; r.items[item] > 1 { + return + } + + switch t := item.(type) { + case *ArrayItem, *StructItem: + for _, it := range item.Value().([]StackItem) { + r.Add(it) + } + case *MapItem: + for i := range t.value { + r.Add(t.value[i].Value) + } + } + } +} + +// Remove removes item from the reference counter. +func (r *refCounter) Remove(item StackItem) { + r.size-- + + switch item.(type) { + case *ArrayItem, *StructItem, *MapItem: + if r.items[item] > 1 { + r.items[item]-- + return + } + + delete(r.items, item) + + switch t := item.(type) { + case *ArrayItem, *StructItem: + for _, it := range item.Value().([]StackItem) { + r.Remove(it) + } + case *MapItem: + for i := range t.value { + r.Remove(t.value[i].Value) + } + } + } +} diff --git a/pkg/vm/ref_counter_test.go b/pkg/vm/ref_counter_test.go new file mode 100644 index 000000000..731a5c46a --- /dev/null +++ b/pkg/vm/ref_counter_test.go @@ -0,0 +1,32 @@ +package vm + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestRefCounter_Add(t *testing.T) { + r := newRefCounter() + + require.Equal(t, 0, r.size) + + r.Add(NullItem{}) + require.Equal(t, 1, r.size) + + r.Add(NullItem{}) + require.Equal(t, 2, r.size) // count scalar items twice + + arr := NewArrayItem([]StackItem{NewByteArrayItem([]byte{1}), NewBoolItem(false)}) + r.Add(arr) + require.Equal(t, 5, r.size) // array + 2 elements + + r.Add(arr) + require.Equal(t, 6, r.size) // count only array + + r.Remove(arr) + require.Equal(t, 5, r.size) + + r.Remove(arr) + require.Equal(t, 2, r.size) +} diff --git a/pkg/vm/slot.go b/pkg/vm/slot.go new file mode 100644 index 000000000..279cb3f39 --- /dev/null +++ b/pkg/vm/slot.go @@ -0,0 +1,43 @@ +package vm + +// Slot is a fixed-size slice of stack items. +type Slot struct { + storage []StackItem + refs *refCounter +} + +// newSlot returns new slot of n items. +func newSlot(n int, refs *refCounter) *Slot { + return &Slot{ + storage: make([]StackItem, n), + refs: refs, + } +} + +func (v *VM) newSlot(n int) *Slot { + return newSlot(n, v.refs) +} + +// Set sets i-th storage slot. +func (s *Slot) Set(i int, item StackItem) { + if s.storage[i] == item { + return + } + old := s.storage[i] + s.storage[i] = item + if old != nil { + s.refs.Remove(old) + } + s.refs.Add(item) +} + +// Get returns item contained in i-th slot. +func (s *Slot) Get(i int) StackItem { + if item := s.storage[i]; item != nil { + return item + } + return NullItem{} +} + +// Size returns slot size. +func (s *Slot) Size() int { return len(s.storage) } diff --git a/pkg/vm/slot_test.go b/pkg/vm/slot_test.go new file mode 100644 index 000000000..65781b7af --- /dev/null +++ b/pkg/vm/slot_test.go @@ -0,0 +1,21 @@ +package vm + +import ( + "math/big" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestSlot_Get(t *testing.T) { + s := newSlot(3, newRefCounter()) + require.NotNil(t, s) + require.Equal(t, 3, s.Size()) + + // NullItem is the default + item := s.Get(2) + require.Equal(t, NullItem{}, item) + + s.Set(1, NewBigIntegerItem(big.NewInt(42))) + require.Equal(t, NewBigIntegerItem(big.NewInt(42)), s.Get(1)) +} diff --git a/pkg/vm/stack.go b/pkg/vm/stack.go index e65bcada2..19e9e9e8f 100644 --- a/pkg/vm/stack.go +++ b/pkg/vm/stack.go @@ -125,9 +125,7 @@ type Stack struct { top Element name string len int - - itemCount map[StackItem]int - size *int + refs *refCounter } // NewStack returns a new stack name by the given name. @@ -138,8 +136,7 @@ func NewStack(n string) *Stack { s.top.next = &s.top s.top.prev = &s.top s.len = 0 - s.itemCount = make(map[StackItem]int) - s.size = new(int) + s.refs = newRefCounter() return s } @@ -171,58 +168,11 @@ func (s *Stack) insert(e, at *Element) *Element { e.stack = s s.len++ - s.updateSizeAdd(e.value) + s.refs.Add(e.value) return e } -func (s *Stack) updateSizeAdd(item StackItem) { - *s.size++ - - switch item.(type) { - case *ArrayItem, *StructItem, *MapItem: - if s.itemCount[item]++; s.itemCount[item] > 1 { - return - } - - switch t := item.(type) { - case *ArrayItem, *StructItem: - for _, it := range item.Value().([]StackItem) { - s.updateSizeAdd(it) - } - case *MapItem: - for i := range t.value { - s.updateSizeAdd(t.value[i].Value) - } - } - } -} - -func (s *Stack) updateSizeRemove(item StackItem) { - *s.size-- - - switch item.(type) { - case *ArrayItem, *StructItem, *MapItem: - if s.itemCount[item] > 1 { - s.itemCount[item]-- - return - } - - delete(s.itemCount, item) - - switch t := item.(type) { - case *ArrayItem, *StructItem: - for _, it := range item.Value().([]StackItem) { - s.updateSizeRemove(it) - } - case *MapItem: - for i := range t.value { - s.updateSizeRemove(t.value[i].Value) - } - } - } -} - // 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. @@ -300,7 +250,7 @@ func (s *Stack) Remove(e *Element) *Element { e.stack = nil s.len-- - s.updateSizeRemove(e.value) + s.refs.Remove(e.value) return e } diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 694ae147c..07ca81046 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -75,11 +75,12 @@ type VM struct { estack *Stack // execution stack. astack *Stack // alt stack. + static *Slot + // Hash to verify in CHECKSIG/CHECKMULTISIG. checkhash []byte - itemCount map[StackItem]int - size int + refs *refCounter gasConsumed util.Fixed8 gasLimit util.Fixed8 @@ -94,9 +95,8 @@ func New() *VM { getInterop: make([]InteropGetterFunc, 0, 3), // 3 functions is typical for our default usage. state: haltState, istack: NewStack("invocation"), - - itemCount: make(map[StackItem]int), - keys: make(map[string]*keys.PublicKey), + refs: newRefCounter(), + keys: make(map[string]*keys.PublicKey), } vm.estack = vm.newItemStack("evaluation") @@ -108,8 +108,7 @@ func New() *VM { func (v *VM) newItemStack(n string) *Stack { s := NewStack(n) - s.size = &v.size - s.itemCount = v.itemCount + s.refs = v.refs return s } @@ -499,7 +498,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro if errRecover := recover(); errRecover != nil { v.state = faultState err = newError(ctx.ip, op, errRecover) - } else if v.size > MaxStackSize { + } else if v.refs.size > MaxStackSize { v.state = faultState err = newError(ctx.ip, op, "stack is too big") } @@ -560,15 +559,80 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro } v.estack.PushVal(result) - // Stack operations. - case opcode.TOALTSTACK: - v.astack.Push(v.estack.Pop()) + case opcode.INITSSLOT: + if v.static != nil { + panic("already initialized") + } + if parameter[0] == 0 { + panic("zero argument") + } + v.static = v.newSlot(int(parameter[0])) - case opcode.FROMALTSTACK: - v.estack.Push(v.astack.Pop()) + case opcode.INITSLOT: + 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: - v.estack.Push(v.astack.Dup(0)) + case opcode.LDSFLD0, opcode.LDSFLD1, opcode.LDSFLD2, opcode.LDSFLD3, opcode.LDSFLD4, opcode.LDSFLD5, opcode.LDSFLD6: + 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: b := v.estack.Pop().Bytes() @@ -955,7 +1019,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro panic("APPEND: not of underlying type Array") } - v.estack.updateSizeAdd(val) + v.refs.Add(val) case opcode.PACK: n := int(v.estack.Pop().BigInt().Int64()) @@ -1024,17 +1088,17 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro if index < 0 || index >= len(arr) { panic("SETITEM: invalid index") } - v.estack.updateSizeRemove(arr[index]) + v.refs.Remove(arr[index]) arr[index] = item - v.estack.updateSizeAdd(arr[index]) + v.refs.Add(arr[index]) case *MapItem: if t.Has(key.value) { - v.estack.updateSizeRemove(item) + v.refs.Remove(item) } else if len(t.value) >= MaxArraySize { panic("too big map") } t.Add(key.value, item) - v.estack.updateSizeAdd(item) + v.refs.Add(item) default: panic(fmt.Sprintf("SETITEM: invalid item type %s", t)) @@ -1059,7 +1123,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro if k < 0 || k >= len(a) { panic("REMOVE: invalid index") } - v.estack.updateSizeRemove(a[k]) + v.refs.Remove(a[k]) a = append(a[:k], a[k+1:]...) t.value = a case *StructItem: @@ -1068,14 +1132,14 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro if k < 0 || k >= len(a) { panic("REMOVE: invalid index") } - v.estack.updateSizeRemove(a[k]) + v.refs.Remove(a[k]) a = append(a[:k], a[k+1:]...) t.value = a case *MapItem: index := t.Index(key.Item()) // NEO 2.0 doesn't error on missing key. if index >= 0 { - v.estack.updateSizeRemove(t.value[index].Value) + v.refs.Remove(t.value[index].Value) t.Drop(index) } default: @@ -1087,17 +1151,17 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro switch t := elem.value.(type) { case *ArrayItem: for _, item := range t.value { - v.estack.updateSizeRemove(item) + v.refs.Remove(item) } t.value = t.value[:0] case *StructItem: for _, item := range t.value { - v.estack.updateSizeRemove(item) + v.refs.Remove(item) } t.value = t.value[:0] case *MapItem: for i := range t.value { - v.estack.updateSizeRemove(t.value[i].Value) + v.refs.Remove(t.value[i].Value) } t.value = t.value[:0] default: @@ -1137,8 +1201,9 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro case opcode.CALL, opcode.CALLL: v.checkInvocationStackSize() - newCtx := ctx.Copy() + newCtx.local = nil + newCtx.arguments = nil newCtx.rvcount = -1 v.istack.PushVal(newCtx) @@ -1152,6 +1217,8 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro } newCtx := ctx.Copy() + newCtx.local = nil + newCtx.arguments = nil newCtx.rvcount = -1 v.istack.PushVal(newCtx) v.jumpIf(newCtx, ptr.pos, true) diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index 5a313bfa5..a8d08c23d 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -370,10 +370,12 @@ func appendBigStruct(size uint16) []opcode.Opcode { } return append(prog, + opcode.INITSSLOT, 1, opcode.PUSHINT16, opcode.Opcode(size), opcode.Opcode(size>>8), // LE opcode.PACK, opcode.NEWSTRUCT, + opcode.STSFLD0, opcode.LDSFLD0, opcode.DUP, - opcode.PUSH0, opcode.NEWARRAY, opcode.TOALTSTACK, opcode.DUPFROMALTSTACK, + opcode.PUSH0, opcode.NEWARRAY, opcode.SWAP, opcode.APPEND, opcode.RET) } @@ -397,17 +399,17 @@ func TestStackLimit(t *testing.T) { }{ {opcode.PUSH2, 1}, {opcode.NEWARRAY, 3}, // array + 2 items - {opcode.TOALTSTACK, 3}, - {opcode.DUPFROMALTSTACK, 4}, + {opcode.STSFLD0, 3}, + {opcode.LDSFLD0, 4}, {opcode.NEWSTRUCT, 6}, // all items are copied {opcode.NEWMAP, 7}, {opcode.DUP, 8}, {opcode.PUSH2, 9}, - {opcode.DUPFROMALTSTACK, 10}, + {opcode.LDSFLD0, 10}, {opcode.SETITEM, 8}, // -3 items and 1 new element in map {opcode.DUP, 9}, {opcode.PUSH2, 10}, - {opcode.DUPFROMALTSTACK, 11}, + {opcode.LDSFLD0, 11}, {opcode.SETITEM, 8}, // -3 items and no new elements in map {opcode.DUP, 9}, {opcode.PUSH2, 10}, @@ -415,15 +417,18 @@ func TestStackLimit(t *testing.T) { {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 { - prog[i] = expected[i].inst + prog[i+2] = expected[i].inst } vm := load(makeProgram(prog...)) + require.NoError(t, vm.Step(), "failed to initialize static slot") for i := range expected { require.NoError(t, vm.Step()) - require.Equal(t, expected[i].size, vm.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) { - prog = append(prog, byte(opcode.TOALTSTACK)) + prog = []byte{byte(opcode.INITSSLOT), 1, byte(opcode.STSFLD0)} 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, byte(opcode.DUPFROMALTSTACK)) + prog = append(prog, byte(opcode.LDSFLD0)) prog = append(prog, getSyscallProg("Neo.Enumerator.Value")...) if isIter { - prog = append(prog, byte(opcode.DUPFROMALTSTACK)) + prog = append(prog, byte(opcode.LDSFLD0)) 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")...) return @@ -583,7 +588,6 @@ func TestIteratorConcat(t *testing.T) { func TestIteratorKeys(t *testing.T) { prog := getSyscallProg("Neo.Iterator.Create") prog = append(prog, getSyscallProg("Neo.Iterator.Keys")...) - prog = append(prog, byte(opcode.TOALTSTACK), byte(opcode.DUPFROMALTSTACK)) prog = append(prog, getEnumeratorProg(2, false)...) v := load(prog) @@ -604,7 +608,6 @@ func TestIteratorKeys(t *testing.T) { func TestIteratorValues(t *testing.T) { prog := getSyscallProg("Neo.Iterator.Create") prog = append(prog, getSyscallProg("Neo.Iterator.Values")...) - prog = append(prog, byte(opcode.TOALTSTACK), byte(opcode.DUPFROMALTSTACK)) prog = append(prog, getEnumeratorProg(2, false)...) v := load(prog) @@ -716,11 +719,11 @@ func TestSerializeArrayBad(t *testing.T) { } func TestSerializeDupInteger(t *testing.T) { - prog := []byte{ - byte(opcode.PUSH0), byte(opcode.NEWARRAY), - byte(opcode.DUP), byte(opcode.PUSH2), byte(opcode.DUP), byte(opcode.TOALTSTACK), byte(opcode.APPEND), - byte(opcode.DUP), byte(opcode.FROMALTSTACK), byte(opcode.APPEND), - } + prog := makeProgram( + opcode.PUSH0, opcode.NEWARRAY, opcode.INITSSLOT, 1, + opcode.DUP, opcode.PUSH2, opcode.DUP, opcode.STSFLD0, opcode.APPEND, + opcode.DUP, opcode.LDSFLD0, opcode.APPEND, + ) vm := load(append(prog, getSerializeProg()...)) runVM(t, vm) @@ -801,9 +804,10 @@ func TestSerializeInterop(t *testing.T) { func callNTimes(n uint16) []byte { return makeProgram( 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.FROMALTSTACK, opcode.DEC, + opcode.LDSFLD0, opcode.DEC, opcode.CALL, 0xF9) // -7 -> JMP to TOALTSTACK) } @@ -1171,9 +1175,10 @@ func testNEWARRAYIssue437(t *testing.T, i1, i2 opcode.Opcode, appended bool) { prog := makeProgram( opcode.PUSH2, i1, 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.FROMALTSTACK, opcode.PUSH5, opcode.APPEND) + opcode.LDSFLD0, opcode.PUSH5, opcode.APPEND) vm := load(prog) vm.Run() @@ -1233,8 +1238,8 @@ func TestAPPEND(t *testing.T) { } func TestAPPENDCloneStruct(t *testing.T) { - prog := makeProgram(opcode.DUP, opcode.PUSH0, opcode.NEWSTRUCT, opcode.TOALTSTACK, - opcode.DUPFROMALTSTACK, opcode.APPEND, opcode.FROMALTSTACK, opcode.PUSH1, opcode.APPEND) + prog := makeProgram(opcode.DUP, opcode.PUSH0, opcode.NEWSTRUCT, opcode.INITSSLOT, 1, opcode.STSFLD0, + opcode.LDSFLD0, opcode.APPEND, opcode.LDSFLD0, opcode.PUSH1, opcode.APPEND) arr := []StackItem{&StructItem{[]StackItem{}}} runWithArgs(t, prog, NewArrayItem(arr), NewArrayItem(nil)) } @@ -1980,7 +1985,7 @@ func testCLEARITEMS(t *testing.T, item StackItem) { v.estack.PushVal(item) runVM(t, v) require.Equal(t, 2, v.estack.Len()) - require.EqualValues(t, 2, v.size) // empty collection + it's size + require.EqualValues(t, 2, v.refs.size) // empty collection + it's size require.EqualValues(t, 0, v.estack.Pop().BigInt().Int64()) } @@ -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 { prog := make([]byte, len(opcodes)+1) // RET for i := 0; i < len(opcodes); i++ {