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:
parent
a6271f6bf2
commit
0cb6dc47e4
12 changed files with 552 additions and 192 deletions
|
@ -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) {
|
||||
|
|
|
@ -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,11 +402,10 @@ 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])
|
||||
|
@ -355,8 +419,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 +429,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 +443,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 +454,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 +467,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 +505,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 +517,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 +618,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 +784,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 +799,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 +838,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 +973,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 +1373,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 +1414,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,
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
BIN
pkg/rpc/server/testdata/test_contract.avm
vendored
BIN
pkg/rpc/server/testdata/test_contract.avm
vendored
Binary file not shown.
BIN
pkg/rpc/server/testdata/testblocks.acc
vendored
BIN
pkg/rpc/server/testdata/testblocks.acc
vendored
Binary file not shown.
|
@ -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:
|
||||
|
|
|
@ -93,10 +93,57 @@ 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 = 0x8B
|
||||
|
|
|
@ -82,9 +82,56 @@ func _() {
|
|||
_ = x[REVERSE3-83]
|
||||
_ = x[REVERSE4-84]
|
||||
_ = x[REVERSEN-85]
|
||||
_ = x[DUPFROMALTSTACK-106]
|
||||
_ = x[TOALTSTACK-107]
|
||||
_ = x[FROMALTSTACK-108]
|
||||
_ = 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]
|
||||
|
@ -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],
|
||||
139: _Opcode_name[451:454],
|
||||
140: _Opcode_name[454:460],
|
||||
141: _Opcode_name[460:464],
|
||||
142: _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 {
|
||||
|
|
86
pkg/vm/vm.go
86
pkg/vm/vm.go
|
@ -75,6 +75,8 @@ type VM struct {
|
|||
estack *Stack // execution stack.
|
||||
astack *Stack // alt stack.
|
||||
|
||||
static *Slot
|
||||
|
||||
// Hash to verify in CHECKSIG/CHECKMULTISIG.
|
||||
checkhash []byte
|
||||
|
||||
|
@ -557,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()
|
||||
|
@ -1134,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)
|
||||
|
||||
|
@ -1149,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)
|
||||
|
|
|
@ -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.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) {
|
||||
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))
|
||||
}
|
||||
|
@ -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++ {
|
||||
|
|
Loading…
Reference in a new issue