forked from TrueCloudLab/neoneo-go
Merge pull request #942 from nspcc-dev/feature/slots
Implement NEO3 VM slots mechanism
This commit is contained in:
commit
b9b1066435
19 changed files with 778 additions and 274 deletions
|
@ -1,11 +1,14 @@
|
||||||
package compiler
|
package compiler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/constant"
|
"go/constant"
|
||||||
"go/types"
|
"go/types"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
"golang.org/x/tools/go/loader"
|
"golang.org/x/tools/go/loader"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -47,6 +50,24 @@ func typeAndValueForField(fld *types.Var) (types.TypeAndValue, error) {
|
||||||
return types.TypeAndValue{}, nil
|
return types.TypeAndValue{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newGlobal creates new global variable.
|
||||||
|
func (c *codegen) newGlobal(name string) {
|
||||||
|
c.globals[name] = len(c.globals)
|
||||||
|
}
|
||||||
|
|
||||||
|
// traverseGlobals visits and initializes global variables.
|
||||||
|
func (c *codegen) traverseGlobals(f ast.Node) {
|
||||||
|
n := countGlobals(f)
|
||||||
|
if n != 0 {
|
||||||
|
if n > 255 {
|
||||||
|
c.prog.BinWriter.Err = errors.New("too many global variables")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
emit.Instruction(c.prog.BinWriter, opcode.INITSSLOT, []byte{byte(n)})
|
||||||
|
}
|
||||||
|
c.convertGlobals(f)
|
||||||
|
}
|
||||||
|
|
||||||
// countGlobals counts the global variables in the program to add
|
// countGlobals counts the global variables in the program to add
|
||||||
// them with the stack size of the function.
|
// them with the stack size of the function.
|
||||||
func countGlobals(f ast.Node) (i int64) {
|
func countGlobals(f ast.Node) (i int64) {
|
||||||
|
|
|
@ -43,6 +43,8 @@ type codegen struct {
|
||||||
// Current funcScope being converted.
|
// Current funcScope being converted.
|
||||||
scope *funcScope
|
scope *funcScope
|
||||||
|
|
||||||
|
globals map[string]int
|
||||||
|
|
||||||
// A mapping from label's names to their ids.
|
// A mapping from label's names to their ids.
|
||||||
labels map[labelWithType]uint16
|
labels map[labelWithType]uint16
|
||||||
// A list of nested label names together with evaluation stack depth.
|
// A list of nested label names together with evaluation stack depth.
|
||||||
|
@ -82,6 +84,14 @@ type labelWithStackSize struct {
|
||||||
sz int
|
sz int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type varType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
varGlobal varType = iota
|
||||||
|
varLocal
|
||||||
|
varArgument
|
||||||
|
)
|
||||||
|
|
||||||
// newLabel creates a new label to jump to
|
// newLabel creates a new label to jump to
|
||||||
func (c *codegen) newLabel() (l uint16) {
|
func (c *codegen) newLabel() (l uint16) {
|
||||||
li := len(c.l)
|
li := len(c.l)
|
||||||
|
@ -147,34 +157,6 @@ func (c *codegen) convertBasicType(t types.TypeAndValue, typ *types.Basic) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *codegen) emitLoadLocal(name string) {
|
|
||||||
pos := c.scope.loadLocal(name)
|
|
||||||
if pos < 0 {
|
|
||||||
c.prog.Err = fmt.Errorf("cannot load local variable with position: %d", pos)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.emitLoadLocalPos(pos)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *codegen) emitLoadLocalPos(pos int) {
|
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.DUPFROMALTSTACK)
|
|
||||||
emit.Int(c.prog.BinWriter, int64(pos))
|
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.PICKITEM)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *codegen) emitStoreLocal(pos int) {
|
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.DUPFROMALTSTACK)
|
|
||||||
|
|
||||||
if pos < 0 {
|
|
||||||
c.prog.Err = fmt.Errorf("invalid position to store local: %d", pos)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
emit.Int(c.prog.BinWriter, int64(pos))
|
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.ROT)
|
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.SETITEM)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *codegen) emitLoadField(i int) {
|
func (c *codegen) emitLoadField(i int) {
|
||||||
emit.Int(c.prog.BinWriter, int64(i))
|
emit.Int(c.prog.BinWriter, int64(i))
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.PICKITEM)
|
emit.Opcode(c.prog.BinWriter, opcode.PICKITEM)
|
||||||
|
@ -186,6 +168,81 @@ func (c *codegen) emitStoreStructField(i int) {
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.SETITEM)
|
emit.Opcode(c.prog.BinWriter, opcode.SETITEM)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getVarIndex returns variable type and position in corresponding slot,
|
||||||
|
// according to current scope.
|
||||||
|
func (c *codegen) getVarIndex(name string) (varType, int) {
|
||||||
|
if c.scope != nil {
|
||||||
|
if i, ok := c.scope.arguments[name]; ok {
|
||||||
|
return varArgument, i
|
||||||
|
} else if i, ok := c.scope.locals[name]; ok {
|
||||||
|
return varLocal, i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if i, ok := c.globals[name]; ok {
|
||||||
|
return varGlobal, i
|
||||||
|
}
|
||||||
|
|
||||||
|
return varLocal, c.scope.newVariable(varLocal, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBaseOpcode(t varType) (opcode.Opcode, opcode.Opcode) {
|
||||||
|
switch t {
|
||||||
|
case varGlobal:
|
||||||
|
return opcode.LDSFLD0, opcode.STSFLD0
|
||||||
|
case varLocal:
|
||||||
|
return opcode.LDLOC0, opcode.STLOC0
|
||||||
|
case varArgument:
|
||||||
|
return opcode.LDARG0, opcode.STARG0
|
||||||
|
default:
|
||||||
|
panic("invalid type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// emitLoadVar loads specified variable to the evaluation stack.
|
||||||
|
func (c *codegen) emitLoadVar(name string) {
|
||||||
|
t, i := c.getVarIndex(name)
|
||||||
|
base, _ := getBaseOpcode(t)
|
||||||
|
if i < 7 {
|
||||||
|
emit.Opcode(c.prog.BinWriter, base+opcode.Opcode(i))
|
||||||
|
} else {
|
||||||
|
emit.Instruction(c.prog.BinWriter, base+7, []byte{byte(i)})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// emitStoreVar stores top value from the evaluation stack in the specified variable.
|
||||||
|
func (c *codegen) emitStoreVar(name string) {
|
||||||
|
t, i := c.getVarIndex(name)
|
||||||
|
_, base := getBaseOpcode(t)
|
||||||
|
if i < 7 {
|
||||||
|
emit.Opcode(c.prog.BinWriter, base+opcode.Opcode(i))
|
||||||
|
} else {
|
||||||
|
emit.Instruction(c.prog.BinWriter, base+7, []byte{byte(i)})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *codegen) emitDefault(n ast.Expr) {
|
||||||
|
tv, ok := c.typeInfo.Types[n]
|
||||||
|
if !ok {
|
||||||
|
c.prog.Err = errors.New("invalid type")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if t, ok := tv.Type.(*types.Basic); ok {
|
||||||
|
info := t.Info()
|
||||||
|
switch {
|
||||||
|
case info&types.IsInteger != 0:
|
||||||
|
emit.Int(c.prog.BinWriter, 0)
|
||||||
|
case info&types.IsString != 0:
|
||||||
|
emit.Bytes(c.prog.BinWriter, []byte{})
|
||||||
|
case info&types.IsBoolean != 0:
|
||||||
|
emit.Bool(c.prog.BinWriter, false)
|
||||||
|
default:
|
||||||
|
emit.Opcode(c.prog.BinWriter, opcode.PUSHNULL)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
emit.Opcode(c.prog.BinWriter, opcode.PUSHNULL)
|
||||||
|
}
|
||||||
|
|
||||||
// convertGlobals traverses the AST and only converts global declarations.
|
// convertGlobals traverses the AST and only converts global declarations.
|
||||||
// If we call this in convertFuncDecl then it will load all global variables
|
// If we call this in convertFuncDecl then it will load all global variables
|
||||||
// into the scope of the function.
|
// into the scope of the function.
|
||||||
|
@ -231,9 +288,17 @@ func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl) {
|
||||||
|
|
||||||
// All globals copied into the scope of the function need to be added
|
// All globals copied into the scope of the function need to be added
|
||||||
// to the stack size of the function.
|
// to the stack size of the function.
|
||||||
emit.Int(c.prog.BinWriter, f.stackSize()+countGlobals(file))
|
sizeLoc := f.countLocals()
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.NEWARRAY)
|
if sizeLoc > 255 {
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.TOALTSTACK)
|
c.prog.Err = errors.New("maximum of 255 local variables is allowed")
|
||||||
|
}
|
||||||
|
sizeArg := f.countArgs()
|
||||||
|
if sizeArg > 255 {
|
||||||
|
c.prog.Err = errors.New("maximum of 255 local variables is allowed")
|
||||||
|
}
|
||||||
|
if sizeLoc != 0 || sizeArg != 0 {
|
||||||
|
emit.Instruction(c.prog.BinWriter, opcode.INITSLOT, []byte{byte(sizeLoc), byte(sizeArg)})
|
||||||
|
}
|
||||||
|
|
||||||
// We need to handle methods, which in Go, is just syntactic sugar.
|
// We need to handle methods, which in Go, is just syntactic sugar.
|
||||||
// The method receiver will be passed in as first argument.
|
// The method receiver will be passed in as first argument.
|
||||||
|
@ -250,23 +315,18 @@ func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl) {
|
||||||
c.prog.Err = fmt.Errorf("method receives for non-struct types is not yet supported")
|
c.prog.Err = fmt.Errorf("method receives for non-struct types is not yet supported")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
l := c.scope.newLocal(ident.Name)
|
// only create an argument here, it will be stored via INITSLOT
|
||||||
c.emitStoreLocal(l)
|
c.scope.newVariable(varArgument, ident.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the arguments in scope.
|
// Load the arguments in scope.
|
||||||
for _, arg := range decl.Type.Params.List {
|
for _, arg := range decl.Type.Params.List {
|
||||||
for _, id := range arg.Names {
|
for _, id := range arg.Names {
|
||||||
l := c.scope.newLocal(id.Name)
|
// only create an argument here, it will be stored via INITSLOT
|
||||||
c.emitStoreLocal(l)
|
c.scope.newVariable(varArgument, id.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Load in all the global variables in to the scope of the function.
|
|
||||||
// This is not necessary for syscalls.
|
|
||||||
if !isSyscall(f) {
|
|
||||||
c.convertGlobals(file)
|
|
||||||
}
|
|
||||||
|
|
||||||
ast.Walk(c, decl.Body)
|
ast.Walk(c, decl.Body)
|
||||||
|
|
||||||
|
@ -275,8 +335,6 @@ func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl) {
|
||||||
// This can be the case with void and named-return functions.
|
// This can be the case with void and named-return functions.
|
||||||
if !lastStmtIsReturn(decl) {
|
if !lastStmtIsReturn(decl) {
|
||||||
c.saveSequencePoint(decl.Body)
|
c.saveSequencePoint(decl.Body)
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.FROMALTSTACK)
|
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.DROP)
|
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.RET)
|
emit.Opcode(c.prog.BinWriter, opcode.RET)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -305,25 +363,32 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
switch t := spec.(type) {
|
switch t := spec.(type) {
|
||||||
case *ast.ValueSpec:
|
case *ast.ValueSpec:
|
||||||
for _, id := range t.Names {
|
for _, id := range t.Names {
|
||||||
c.scope.newLocal(id.Name)
|
if c.scope == nil {
|
||||||
|
// it is a global declaration
|
||||||
|
c.newGlobal(id.Name)
|
||||||
|
} else {
|
||||||
|
c.scope.newLocal(id.Name)
|
||||||
|
}
|
||||||
c.registerDebugVariable(id.Name, t.Type)
|
c.registerDebugVariable(id.Name, t.Type)
|
||||||
}
|
}
|
||||||
if len(t.Values) != 0 {
|
if len(t.Values) != 0 {
|
||||||
for i, val := range t.Values {
|
for i, val := range t.Values {
|
||||||
ast.Walk(c, val)
|
ast.Walk(c, val)
|
||||||
l := c.scope.loadLocal(t.Names[i].Name)
|
c.emitStoreVar(t.Names[i].Name)
|
||||||
c.emitStoreLocal(l)
|
|
||||||
}
|
}
|
||||||
} else if c.isCompoundArrayType(t.Type) {
|
} else if c.isCompoundArrayType(t.Type) {
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.PUSH0)
|
emit.Opcode(c.prog.BinWriter, opcode.PUSH0)
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.NEWARRAY)
|
emit.Opcode(c.prog.BinWriter, opcode.NEWARRAY)
|
||||||
l := c.scope.loadLocal(t.Names[0].Name)
|
c.emitStoreVar(t.Names[0].Name)
|
||||||
c.emitStoreLocal(l)
|
|
||||||
} else if n, ok := c.isStructType(t.Type); ok {
|
} else if n, ok := c.isStructType(t.Type); ok {
|
||||||
emit.Int(c.prog.BinWriter, int64(n))
|
emit.Int(c.prog.BinWriter, int64(n))
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.NEWSTRUCT)
|
emit.Opcode(c.prog.BinWriter, opcode.NEWSTRUCT)
|
||||||
l := c.scope.loadLocal(t.Names[0].Name)
|
c.emitStoreVar(t.Names[0].Name)
|
||||||
c.emitStoreLocal(l)
|
} else {
|
||||||
|
for _, id := range t.Names {
|
||||||
|
c.emitDefault(t.Type)
|
||||||
|
c.emitStoreVar(id.Name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -337,15 +402,17 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
case *ast.Ident:
|
case *ast.Ident:
|
||||||
switch n.Tok {
|
switch n.Tok {
|
||||||
case token.ADD_ASSIGN, token.SUB_ASSIGN, token.MUL_ASSIGN, token.QUO_ASSIGN, token.REM_ASSIGN:
|
case token.ADD_ASSIGN, token.SUB_ASSIGN, token.MUL_ASSIGN, token.QUO_ASSIGN, token.REM_ASSIGN:
|
||||||
c.emitLoadLocal(t.Name)
|
c.emitLoadVar(t.Name)
|
||||||
ast.Walk(c, n.Rhs[0]) // can only add assign to 1 expr on the RHS
|
ast.Walk(c, n.Rhs[0]) // can only add assign to 1 expr on the RHS
|
||||||
c.convertToken(n.Tok)
|
c.convertToken(n.Tok)
|
||||||
l := c.scope.loadLocal(t.Name)
|
c.emitStoreVar(t.Name)
|
||||||
c.emitStoreLocal(l)
|
|
||||||
case token.DEFINE:
|
case token.DEFINE:
|
||||||
if !multiRet {
|
if !multiRet {
|
||||||
c.registerDebugVariable(t.Name, n.Rhs[i])
|
c.registerDebugVariable(t.Name, n.Rhs[i])
|
||||||
}
|
}
|
||||||
|
if t.Name != "_" {
|
||||||
|
c.scope.newLocal(t.Name)
|
||||||
|
}
|
||||||
fallthrough
|
fallthrough
|
||||||
default:
|
default:
|
||||||
if i == 0 || !multiRet {
|
if i == 0 || !multiRet {
|
||||||
|
@ -355,8 +422,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
if t.Name == "_" {
|
if t.Name == "_" {
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.DROP)
|
emit.Opcode(c.prog.BinWriter, opcode.DROP)
|
||||||
} else {
|
} else {
|
||||||
l := c.scope.loadLocal(t.Name)
|
c.emitStoreVar(t.Name)
|
||||||
c.emitStoreLocal(l)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -366,7 +432,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
ast.Walk(c, n.Rhs[i])
|
ast.Walk(c, n.Rhs[i])
|
||||||
typ := c.typeInfo.ObjectOf(expr).Type().Underlying()
|
typ := c.typeInfo.ObjectOf(expr).Type().Underlying()
|
||||||
if strct, ok := typ.(*types.Struct); ok {
|
if strct, ok := typ.(*types.Struct); ok {
|
||||||
c.emitLoadLocal(expr.Name) // load the struct
|
c.emitLoadVar(expr.Name) // load the struct
|
||||||
i := indexOfStruct(strct, t.Sel.Name) // get the index of the field
|
i := indexOfStruct(strct, t.Sel.Name) // get the index of the field
|
||||||
c.emitStoreStructField(i) // store the field
|
c.emitStoreStructField(i) // store the field
|
||||||
}
|
}
|
||||||
|
@ -380,7 +446,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
case *ast.IndexExpr:
|
case *ast.IndexExpr:
|
||||||
ast.Walk(c, n.Rhs[i])
|
ast.Walk(c, n.Rhs[i])
|
||||||
name := t.X.(*ast.Ident).Name
|
name := t.X.(*ast.Ident).Name
|
||||||
c.emitLoadLocal(name)
|
c.emitLoadVar(name)
|
||||||
switch ind := t.Index.(type) {
|
switch ind := t.Index.(type) {
|
||||||
case *ast.BasicLit:
|
case *ast.BasicLit:
|
||||||
indexStr := ind.Value
|
indexStr := ind.Value
|
||||||
|
@ -391,7 +457,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
}
|
}
|
||||||
c.emitStoreStructField(index)
|
c.emitStoreStructField(index)
|
||||||
case *ast.Ident:
|
case *ast.Ident:
|
||||||
c.emitLoadLocal(ind.Name)
|
c.emitLoadVar(ind.Name)
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.ROT)
|
emit.Opcode(c.prog.BinWriter, opcode.ROT)
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.SETITEM)
|
emit.Opcode(c.prog.BinWriter, opcode.SETITEM)
|
||||||
default:
|
default:
|
||||||
|
@ -404,7 +470,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
|
|
||||||
case *ast.SliceExpr:
|
case *ast.SliceExpr:
|
||||||
name := n.X.(*ast.Ident).Name
|
name := n.X.(*ast.Ident).Name
|
||||||
c.emitLoadLocal(name)
|
c.emitLoadVar(name)
|
||||||
|
|
||||||
if n.Low != nil {
|
if n.Low != nil {
|
||||||
ast.Walk(c, n.Low)
|
ast.Walk(c, n.Low)
|
||||||
|
@ -442,7 +508,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
for i := len(results.List) - 1; i >= 0; i-- {
|
for i := len(results.List) - 1; i >= 0; i-- {
|
||||||
names := results.List[i].Names
|
names := results.List[i].Names
|
||||||
for j := len(names) - 1; j >= 0; j-- {
|
for j := len(names) - 1; j >= 0; j-- {
|
||||||
c.emitLoadLocal(names[j].Name)
|
c.emitLoadVar(names[j].Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -454,8 +520,6 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
}
|
}
|
||||||
|
|
||||||
c.saveSequencePoint(n)
|
c.saveSequencePoint(n)
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.FROMALTSTACK)
|
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.DROP) // Cleanup the stack.
|
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.RET)
|
emit.Opcode(c.prog.BinWriter, opcode.RET)
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
|
@ -557,7 +621,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
} else if tv := c.typeInfo.Types[n]; tv.Value != nil {
|
} else if tv := c.typeInfo.Types[n]; tv.Value != nil {
|
||||||
c.emitLoadConst(tv)
|
c.emitLoadConst(tv)
|
||||||
} else {
|
} else {
|
||||||
c.emitLoadLocal(n.Name)
|
c.emitLoadVar(n.Name)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
|
@ -723,7 +787,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
c.convertBuiltin(n)
|
c.convertBuiltin(n)
|
||||||
case name != "":
|
case name != "":
|
||||||
// Function was not found thus is can be only an invocation of func-typed variable.
|
// Function was not found thus is can be only an invocation of func-typed variable.
|
||||||
c.emitLoadLocal(name)
|
c.emitLoadVar(name)
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.CALLA)
|
emit.Opcode(c.prog.BinWriter, opcode.CALLA)
|
||||||
case isSyscall(f):
|
case isSyscall(f):
|
||||||
c.convertSyscall(n, f.selector.Name, f.name)
|
c.convertSyscall(n, f.selector.Name, f.name)
|
||||||
|
@ -738,7 +802,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
case *ast.Ident:
|
case *ast.Ident:
|
||||||
typ := c.typeInfo.ObjectOf(t).Type().Underlying()
|
typ := c.typeInfo.ObjectOf(t).Type().Underlying()
|
||||||
if strct, ok := typ.(*types.Struct); ok {
|
if strct, ok := typ.(*types.Struct); ok {
|
||||||
c.emitLoadLocal(t.Name) // load the struct
|
c.emitLoadVar(t.Name) // load the struct
|
||||||
i := indexOfStruct(strct, n.Sel.Name)
|
i := indexOfStruct(strct, n.Sel.Name)
|
||||||
c.emitLoadField(i) // load the field
|
c.emitLoadField(i) // load the field
|
||||||
}
|
}
|
||||||
|
@ -777,8 +841,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
// for i := 0; i < 10; i++ {}
|
// for i := 0; i < 10; i++ {}
|
||||||
// Where the post stmt is ( i++ )
|
// Where the post stmt is ( i++ )
|
||||||
if ident, ok := n.X.(*ast.Ident); ok {
|
if ident, ok := n.X.(*ast.Ident); ok {
|
||||||
pos := c.scope.loadLocal(ident.Name)
|
c.emitStoreVar(ident.Name)
|
||||||
c.emitStoreLocal(pos)
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
|
@ -913,9 +976,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
|
|
||||||
if n.Key != nil {
|
if n.Key != nil {
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.DUP)
|
emit.Opcode(c.prog.BinWriter, opcode.DUP)
|
||||||
|
c.emitStoreVar(n.Key.(*ast.Ident).Name)
|
||||||
pos := c.scope.loadLocal(n.Key.(*ast.Ident).Name)
|
|
||||||
c.emitStoreLocal(pos)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ast.Walk(c, n.Body)
|
ast.Walk(c, n.Body)
|
||||||
|
@ -1315,6 +1376,8 @@ func (c *codegen) compile(info *buildInfo, pkg *loader.PackageInfo) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.traverseGlobals(mainFile)
|
||||||
|
|
||||||
// convert the entry point first.
|
// convert the entry point first.
|
||||||
c.convertFuncDecl(mainFile, main)
|
c.convertFuncDecl(mainFile, main)
|
||||||
|
|
||||||
|
@ -1354,6 +1417,7 @@ func newCodegen(info *buildInfo, pkg *loader.PackageInfo) *codegen {
|
||||||
l: []int{},
|
l: []int{},
|
||||||
funcs: map[string]*funcScope{},
|
funcs: map[string]*funcScope{},
|
||||||
lambda: map[string]*funcScope{},
|
lambda: map[string]*funcScope{},
|
||||||
|
globals: map[string]int{},
|
||||||
labels: map[labelWithType]uint16{},
|
labels: map[labelWithType]uint16{},
|
||||||
typeInfo: &pkg.Info,
|
typeInfo: &pkg.Info,
|
||||||
|
|
||||||
|
|
|
@ -115,6 +115,10 @@ type Event struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *codegen) saveSequencePoint(n ast.Node) {
|
func (c *codegen) saveSequencePoint(n ast.Node) {
|
||||||
|
if c.scope == nil {
|
||||||
|
// do not save globals for now
|
||||||
|
return
|
||||||
|
}
|
||||||
fset := c.buildInfo.program.Fset
|
fset := c.buildInfo.program.Fset
|
||||||
start := fset.Position(n.Pos())
|
start := fset.Position(n.Pos())
|
||||||
end := fset.Position(n.End())
|
end := fset.Position(n.End())
|
||||||
|
@ -143,6 +147,10 @@ func (c *codegen) emitDebugInfo() *DebugInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *codegen) registerDebugVariable(name string, expr ast.Expr) {
|
func (c *codegen) registerDebugVariable(name string, expr ast.Expr) {
|
||||||
|
if c.scope == nil {
|
||||||
|
// do not save globals for now
|
||||||
|
return
|
||||||
|
}
|
||||||
typ := c.scTypeFromExpr(expr)
|
typ := c.scTypeFromExpr(expr)
|
||||||
c.scope.variables = append(c.scope.variables, name+","+typ)
|
c.scope.variables = append(c.scope.variables, name+","+typ)
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,8 @@ type funcScope struct {
|
||||||
variables []string
|
variables []string
|
||||||
|
|
||||||
// Local variables
|
// Local variables
|
||||||
locals map[string]int
|
locals map[string]int
|
||||||
|
arguments map[string]int
|
||||||
|
|
||||||
// voidCalls are basically functions that return their value
|
// voidCalls are basically functions that return their value
|
||||||
// into nothing. The stack has their return value but there
|
// into nothing. The stack has their return value but there
|
||||||
|
@ -51,6 +52,7 @@ func newFuncScope(decl *ast.FuncDecl, label uint16) *funcScope {
|
||||||
decl: decl,
|
decl: decl,
|
||||||
label: label,
|
label: label,
|
||||||
locals: map[string]int{},
|
locals: map[string]int{},
|
||||||
|
arguments: map[string]int{},
|
||||||
voidCalls: map[*ast.CallExpr]bool{},
|
voidCalls: map[*ast.CallExpr]bool{},
|
||||||
variables: []string{},
|
variables: []string{},
|
||||||
i: -1,
|
i: -1,
|
||||||
|
@ -84,7 +86,7 @@ func (c *funcScope) analyzeVoidCalls(node ast.Node) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *funcScope) stackSize() int64 {
|
func (c *funcScope) countLocals() int {
|
||||||
size := 0
|
size := 0
|
||||||
ast.Inspect(c.decl, func(n ast.Node) bool {
|
ast.Inspect(c.decl, func(n ast.Node) bool {
|
||||||
switch n := n.(type) {
|
switch n := n.(type) {
|
||||||
|
@ -110,28 +112,38 @@ func (c *funcScope) stackSize() int64 {
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
|
||||||
numArgs := len(c.decl.Type.Params.List)
|
func (c *funcScope) countArgs() int {
|
||||||
// Also take care of struct methods recv: e.g. (t Token).Foo().
|
n := c.decl.Type.Params.NumFields()
|
||||||
if c.decl.Recv != nil {
|
if c.decl.Recv != nil {
|
||||||
numArgs += len(c.decl.Recv.List)
|
n += c.decl.Recv.NumFields()
|
||||||
}
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *funcScope) stackSize() int64 {
|
||||||
|
size := c.countLocals()
|
||||||
|
numArgs := c.countArgs()
|
||||||
return int64(size + numArgs + len(c.voidCalls))
|
return int64(size + numArgs + len(c.voidCalls))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newVariable creates a new local variable or argument in the scope of the function.
|
||||||
|
func (c *funcScope) newVariable(t varType, name string) int {
|
||||||
|
c.i++
|
||||||
|
switch t {
|
||||||
|
case varLocal:
|
||||||
|
c.locals[name] = c.i
|
||||||
|
case varArgument:
|
||||||
|
c.arguments[name] = c.i
|
||||||
|
default:
|
||||||
|
panic("invalid type")
|
||||||
|
}
|
||||||
|
return c.i
|
||||||
|
}
|
||||||
|
|
||||||
// newLocal creates a new local variable into the scope of the function.
|
// newLocal creates a new local variable into the scope of the function.
|
||||||
func (c *funcScope) newLocal(name string) int {
|
func (c *funcScope) newLocal(name string) int {
|
||||||
c.i++
|
return c.newVariable(varLocal, name)
|
||||||
c.locals[name] = c.i
|
|
||||||
return c.i
|
|
||||||
}
|
|
||||||
|
|
||||||
// loadLocal loads the position of a local variable inside the scope of the function.
|
|
||||||
func (c *funcScope) loadLocal(name string) int {
|
|
||||||
i, ok := c.locals[name]
|
|
||||||
if !ok {
|
|
||||||
// should emit a compiler warning.
|
|
||||||
return c.newLocal(name)
|
|
||||||
}
|
|
||||||
return i
|
|
||||||
}
|
}
|
||||||
|
|
21
pkg/compiler/global_test.go
Normal file
21
pkg/compiler/global_test.go
Normal file
|
@ -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))
|
||||||
|
}
|
15
pkg/compiler/lambda_test.go
Normal file
15
pkg/compiler/lambda_test.go
Normal file
|
@ -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))
|
||||||
|
}
|
|
@ -48,18 +48,18 @@ type rpcTestCase struct {
|
||||||
check func(t *testing.T, e *executor, result interface{})
|
check func(t *testing.T, e *executor, result interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
const testContractHash = "a4bea0d56fad00a972135d54b381516205d78484"
|
const testContractHash = "33f3421677fab7f620bd70582f468b4a18df1e5d"
|
||||||
|
|
||||||
var rpcTestCases = map[string][]rpcTestCase{
|
var rpcTestCases = map[string][]rpcTestCase{
|
||||||
"getapplicationlog": {
|
"getapplicationlog": {
|
||||||
{
|
{
|
||||||
name: "positive",
|
name: "positive",
|
||||||
params: `["c296c0929350d051b9b40cace54db5a3eac4b730a8851e958795d44918f23c08"]`,
|
params: `["3f1579e797fedb83b66a85fe21d427a119d0e25ef662582e56393fd0d70e4691"]`,
|
||||||
result: func(e *executor) interface{} { return &result.ApplicationLog{} },
|
result: func(e *executor) interface{} { return &result.ApplicationLog{} },
|
||||||
check: func(t *testing.T, e *executor, acc interface{}) {
|
check: func(t *testing.T, e *executor, acc interface{}) {
|
||||||
res, ok := acc.(*result.ApplicationLog)
|
res, ok := acc.(*result.ApplicationLog)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
expectedTxHash, err := util.Uint256DecodeStringLE("c296c0929350d051b9b40cace54db5a3eac4b730a8851e958795d44918f23c08")
|
expectedTxHash, err := util.Uint256DecodeStringLE("3f1579e797fedb83b66a85fe21d427a119d0e25ef662582e56393fd0d70e4691")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, expectedTxHash, res.TxHash)
|
assert.Equal(t, expectedTxHash, res.TxHash)
|
||||||
assert.Equal(t, 1, len(res.Executions))
|
assert.Equal(t, 1, len(res.Executions))
|
||||||
|
|
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.
|
// Alt stack pointer.
|
||||||
astack *Stack
|
astack *Stack
|
||||||
|
|
||||||
|
local *Slot
|
||||||
|
arguments *Slot
|
||||||
|
|
||||||
// Script hash of the prog.
|
// Script hash of the prog.
|
||||||
scriptHash util.Uint160
|
scriptHash util.Uint160
|
||||||
|
|
||||||
|
@ -107,8 +110,11 @@ func (c *Context) Next() (opcode.Opcode, []byte, error) {
|
||||||
}
|
}
|
||||||
case opcode.JMP, opcode.JMPIF, opcode.JMPIFNOT, opcode.JMPEQ, opcode.JMPNE,
|
case opcode.JMP, opcode.JMPIF, opcode.JMPIFNOT, opcode.JMPEQ, opcode.JMPNE,
|
||||||
opcode.JMPGT, opcode.JMPGE, opcode.JMPLT, opcode.JMPLE,
|
opcode.JMPGT, opcode.JMPGE, opcode.JMPLT, opcode.JMPLE,
|
||||||
opcode.CALL, opcode.ISTYPE, opcode.CONVERT, opcode.NEWARRAYT:
|
opcode.CALL, opcode.ISTYPE, opcode.CONVERT, opcode.NEWARRAYT,
|
||||||
|
opcode.INITSSLOT, opcode.LDSFLD, opcode.STSFLD, opcode.LDARG, opcode.STARG, opcode.LDLOC, opcode.STLOC:
|
||||||
numtoread = 1
|
numtoread = 1
|
||||||
|
case opcode.INITSLOT:
|
||||||
|
numtoread = 2
|
||||||
case opcode.JMPL, opcode.JMPIFL, opcode.JMPIFNOTL, opcode.JMPEQL, opcode.JMPNEL,
|
case opcode.JMPL, opcode.JMPIFL, opcode.JMPIFNOTL, opcode.JMPEQL, opcode.JMPNEL,
|
||||||
opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLTL, opcode.JMPLEL,
|
opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLTL, opcode.JMPLEL,
|
||||||
opcode.CALLL, opcode.SYSCALL, opcode.PUSHA:
|
opcode.CALLL, opcode.SYSCALL, opcode.PUSHA:
|
||||||
|
|
|
@ -93,16 +93,63 @@ const (
|
||||||
REVERSE4 Opcode = 0x54
|
REVERSE4 Opcode = 0x54
|
||||||
REVERSEN Opcode = 0x55
|
REVERSEN Opcode = 0x55
|
||||||
|
|
||||||
// Old stack opcodes
|
// Slots
|
||||||
DUPFROMALTSTACK Opcode = 0x6A
|
INITSSLOT Opcode = 0x56
|
||||||
TOALTSTACK Opcode = 0x6B
|
INITSLOT Opcode = 0x57
|
||||||
FROMALTSTACK Opcode = 0x6C
|
LDSFLD0 Opcode = 0x58
|
||||||
|
LDSFLD1 Opcode = 0x59
|
||||||
|
LDSFLD2 Opcode = 0x5A
|
||||||
|
LDSFLD3 Opcode = 0x5B
|
||||||
|
LDSFLD4 Opcode = 0x5C
|
||||||
|
LDSFLD5 Opcode = 0x5D
|
||||||
|
LDSFLD6 Opcode = 0x5E
|
||||||
|
LDSFLD Opcode = 0x5F
|
||||||
|
STSFLD0 Opcode = 0x60
|
||||||
|
STSFLD1 Opcode = 0x61
|
||||||
|
STSFLD2 Opcode = 0x62
|
||||||
|
STSFLD3 Opcode = 0x63
|
||||||
|
STSFLD4 Opcode = 0x64
|
||||||
|
STSFLD5 Opcode = 0x65
|
||||||
|
STSFLD6 Opcode = 0x66
|
||||||
|
STSFLD Opcode = 0x67
|
||||||
|
LDLOC0 Opcode = 0x68
|
||||||
|
LDLOC1 Opcode = 0x69
|
||||||
|
LDLOC2 Opcode = 0x6A
|
||||||
|
LDLOC3 Opcode = 0x6B
|
||||||
|
LDLOC4 Opcode = 0x6C
|
||||||
|
LDLOC5 Opcode = 0x6D
|
||||||
|
LDLOC6 Opcode = 0x6E
|
||||||
|
LDLOC Opcode = 0x6F
|
||||||
|
STLOC0 Opcode = 0x70
|
||||||
|
STLOC1 Opcode = 0x71
|
||||||
|
STLOC2 Opcode = 0x72
|
||||||
|
STLOC3 Opcode = 0x73
|
||||||
|
STLOC4 Opcode = 0x74
|
||||||
|
STLOC5 Opcode = 0x75
|
||||||
|
STLOC6 Opcode = 0x76
|
||||||
|
STLOC Opcode = 0x77
|
||||||
|
LDARG0 Opcode = 0x78
|
||||||
|
LDARG1 Opcode = 0x79
|
||||||
|
LDARG2 Opcode = 0x7A
|
||||||
|
LDARG3 Opcode = 0x7B
|
||||||
|
LDARG4 Opcode = 0x7C
|
||||||
|
LDARG5 Opcode = 0x7D
|
||||||
|
LDARG6 Opcode = 0x7E
|
||||||
|
LDARG Opcode = 0x7F
|
||||||
|
STARG0 Opcode = 0x80
|
||||||
|
STARG1 Opcode = 0x81
|
||||||
|
STARG2 Opcode = 0x82
|
||||||
|
STARG3 Opcode = 0x83
|
||||||
|
STARG4 Opcode = 0x84
|
||||||
|
STARG5 Opcode = 0x85
|
||||||
|
STARG6 Opcode = 0x86
|
||||||
|
STARG Opcode = 0x87
|
||||||
|
|
||||||
// Splice
|
// Splice
|
||||||
CAT Opcode = 0x7E
|
CAT Opcode = 0x8B
|
||||||
SUBSTR Opcode = 0x7F
|
SUBSTR Opcode = 0x8C
|
||||||
LEFT Opcode = 0x80
|
LEFT Opcode = 0x8D
|
||||||
RIGHT Opcode = 0x81
|
RIGHT Opcode = 0x8E
|
||||||
|
|
||||||
// Bitwise logic
|
// Bitwise logic
|
||||||
INVERT Opcode = 0x90
|
INVERT Opcode = 0x90
|
||||||
|
|
|
@ -82,13 +82,60 @@ func _() {
|
||||||
_ = x[REVERSE3-83]
|
_ = x[REVERSE3-83]
|
||||||
_ = x[REVERSE4-84]
|
_ = x[REVERSE4-84]
|
||||||
_ = x[REVERSEN-85]
|
_ = x[REVERSEN-85]
|
||||||
_ = x[DUPFROMALTSTACK-106]
|
_ = x[INITSSLOT-86]
|
||||||
_ = x[TOALTSTACK-107]
|
_ = x[INITSLOT-87]
|
||||||
_ = x[FROMALTSTACK-108]
|
_ = x[LDSFLD0-88]
|
||||||
_ = x[CAT-126]
|
_ = x[LDSFLD1-89]
|
||||||
_ = x[SUBSTR-127]
|
_ = x[LDSFLD2-90]
|
||||||
_ = x[LEFT-128]
|
_ = x[LDSFLD3-91]
|
||||||
_ = x[RIGHT-129]
|
_ = 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[INVERT-144]
|
||||||
_ = x[AND-145]
|
_ = x[AND-145]
|
||||||
_ = x[OR-146]
|
_ = x[OR-146]
|
||||||
|
@ -143,7 +190,7 @@ func _() {
|
||||||
_ = x[CONVERT-219]
|
_ = x[CONVERT-219]
|
||||||
}
|
}
|
||||||
|
|
||||||
const _Opcode_name = "PUSHINT8PUSHINT16PUSHINT32PUSHINT64PUSHINT128PUSHINT256PUSHAPUSHNULLPUSHDATA1PUSHDATA2PUSHDATA4PUSHM1PUSH0PUSH1PUSH2PUSH3PUSH4PUSH5PUSH6PUSH7PUSH8PUSH9PUSH10PUSH11PUSH12PUSH13PUSH14PUSH15PUSH16NOPJMPJMPLJMPIFJMPIFLJMPIFNOTJMPIFNOTLJMPEQJMPEQLJMPNEJMPNELJMPGTJMPGTLJMPGEJMPGELJMPLTJMPLTLJMPLEJMPLELCALLCALLLCALLAABORTASSERTTHROWRETSYSCALLDEPTHDROPNIPXDROPCLEARDUPOVERPICKTUCKSWAPOLDPUSH1ROLLREVERSE3REVERSE4REVERSENDUPFROMALTSTACKTOALTSTACKFROMALTSTACKCATSUBSTRLEFTRIGHTINVERTANDORXOREQUALNOTEQUALSIGNABSNEGATEINCDECADDSUBMULDIVMODSHLSHRNOTBOOLANDBOOLORNZNUMEQUALNUMNOTEQUALLTLTEGTGTEMINMAXWITHINPACKUNPACKNEWARRAY0NEWARRAYNEWARRAYTNEWSTRUCT0NEWSTRUCTNEWMAPSIZEHASKEYKEYSVALUESPICKITEMAPPENDSETITEMREVERSEITEMSREMOVECLEARITEMSISNULLISTYPECONVERT"
|
const _Opcode_name = "PUSHINT8PUSHINT16PUSHINT32PUSHINT64PUSHINT128PUSHINT256PUSHAPUSHNULLPUSHDATA1PUSHDATA2PUSHDATA4PUSHM1PUSH0PUSH1PUSH2PUSH3PUSH4PUSH5PUSH6PUSH7PUSH8PUSH9PUSH10PUSH11PUSH12PUSH13PUSH14PUSH15PUSH16NOPJMPJMPLJMPIFJMPIFLJMPIFNOTJMPIFNOTLJMPEQJMPEQLJMPNEJMPNELJMPGTJMPGTLJMPGEJMPGELJMPLTJMPLTLJMPLEJMPLELCALLCALLLCALLAABORTASSERTTHROWRETSYSCALLDEPTHDROPNIPXDROPCLEARDUPOVERPICKTUCKSWAPOLDPUSH1ROLLREVERSE3REVERSE4REVERSENINITSSLOTINITSLOTLDSFLD0LDSFLD1LDSFLD2LDSFLD3LDSFLD4LDSFLD5LDSFLD6LDSFLDSTSFLD0STSFLD1STSFLD2STSFLD3STSFLD4STSFLD5STSFLD6STSFLDLDLOC0LDLOC1LDLOC2LDLOC3LDLOC4LDLOC5LDLOC6LDLOCSTLOC0STLOC1STLOC2STLOC3STLOC4STLOC5STLOC6STLOCLDARG0LDARG1LDARG2LDARG3LDARG4LDARG5LDARG6LDARGSTARG0STARG1STARG2STARG3STARG4STARG5STARG6STARGCATSUBSTRLEFTRIGHTINVERTANDORXOREQUALNOTEQUALSIGNABSNEGATEINCDECADDSUBMULDIVMODSHLSHRNOTBOOLANDBOOLORNZNUMEQUALNUMNOTEQUALLTLTEGTGTEMINMAXWITHINPACKUNPACKNEWARRAY0NEWARRAYNEWARRAYTNEWSTRUCT0NEWSTRUCTNEWMAPSIZEHASKEYKEYSVALUESPICKITEMAPPENDSETITEMREVERSEITEMSREMOVECLEARITEMSISNULLISTYPECONVERT"
|
||||||
|
|
||||||
var _Opcode_map = map[Opcode]string{
|
var _Opcode_map = map[Opcode]string{
|
||||||
0: _Opcode_name[0:8],
|
0: _Opcode_name[0:8],
|
||||||
|
@ -217,65 +264,112 @@ var _Opcode_map = map[Opcode]string{
|
||||||
83: _Opcode_name[390:398],
|
83: _Opcode_name[390:398],
|
||||||
84: _Opcode_name[398:406],
|
84: _Opcode_name[398:406],
|
||||||
85: _Opcode_name[406:414],
|
85: _Opcode_name[406:414],
|
||||||
106: _Opcode_name[414:429],
|
86: _Opcode_name[414:423],
|
||||||
107: _Opcode_name[429:439],
|
87: _Opcode_name[423:431],
|
||||||
108: _Opcode_name[439:451],
|
88: _Opcode_name[431:438],
|
||||||
126: _Opcode_name[451:454],
|
89: _Opcode_name[438:445],
|
||||||
127: _Opcode_name[454:460],
|
90: _Opcode_name[445:452],
|
||||||
128: _Opcode_name[460:464],
|
91: _Opcode_name[452:459],
|
||||||
129: _Opcode_name[464:469],
|
92: _Opcode_name[459:466],
|
||||||
144: _Opcode_name[469:475],
|
93: _Opcode_name[466:473],
|
||||||
145: _Opcode_name[475:478],
|
94: _Opcode_name[473:480],
|
||||||
146: _Opcode_name[478:480],
|
95: _Opcode_name[480:486],
|
||||||
147: _Opcode_name[480:483],
|
96: _Opcode_name[486:493],
|
||||||
151: _Opcode_name[483:488],
|
97: _Opcode_name[493:500],
|
||||||
152: _Opcode_name[488:496],
|
98: _Opcode_name[500:507],
|
||||||
153: _Opcode_name[496:500],
|
99: _Opcode_name[507:514],
|
||||||
154: _Opcode_name[500:503],
|
100: _Opcode_name[514:521],
|
||||||
155: _Opcode_name[503:509],
|
101: _Opcode_name[521:528],
|
||||||
156: _Opcode_name[509:512],
|
102: _Opcode_name[528:535],
|
||||||
157: _Opcode_name[512:515],
|
103: _Opcode_name[535:541],
|
||||||
158: _Opcode_name[515:518],
|
104: _Opcode_name[541:547],
|
||||||
159: _Opcode_name[518:521],
|
105: _Opcode_name[547:553],
|
||||||
160: _Opcode_name[521:524],
|
106: _Opcode_name[553:559],
|
||||||
161: _Opcode_name[524:527],
|
107: _Opcode_name[559:565],
|
||||||
162: _Opcode_name[527:530],
|
108: _Opcode_name[565:571],
|
||||||
168: _Opcode_name[530:533],
|
109: _Opcode_name[571:577],
|
||||||
169: _Opcode_name[533:536],
|
110: _Opcode_name[577:583],
|
||||||
170: _Opcode_name[536:539],
|
111: _Opcode_name[583:588],
|
||||||
171: _Opcode_name[539:546],
|
112: _Opcode_name[588:594],
|
||||||
172: _Opcode_name[546:552],
|
113: _Opcode_name[594:600],
|
||||||
177: _Opcode_name[552:554],
|
114: _Opcode_name[600:606],
|
||||||
179: _Opcode_name[554:562],
|
115: _Opcode_name[606:612],
|
||||||
180: _Opcode_name[562:573],
|
116: _Opcode_name[612:618],
|
||||||
181: _Opcode_name[573:575],
|
117: _Opcode_name[618:624],
|
||||||
182: _Opcode_name[575:578],
|
118: _Opcode_name[624:630],
|
||||||
183: _Opcode_name[578:580],
|
119: _Opcode_name[630:635],
|
||||||
184: _Opcode_name[580:583],
|
120: _Opcode_name[635:641],
|
||||||
185: _Opcode_name[583:586],
|
121: _Opcode_name[641:647],
|
||||||
186: _Opcode_name[586:589],
|
122: _Opcode_name[647:653],
|
||||||
187: _Opcode_name[589:595],
|
123: _Opcode_name[653:659],
|
||||||
192: _Opcode_name[595:599],
|
124: _Opcode_name[659:665],
|
||||||
193: _Opcode_name[599:605],
|
125: _Opcode_name[665:671],
|
||||||
194: _Opcode_name[605:614],
|
126: _Opcode_name[671:677],
|
||||||
195: _Opcode_name[614:622],
|
127: _Opcode_name[677:682],
|
||||||
196: _Opcode_name[622:631],
|
128: _Opcode_name[682:688],
|
||||||
197: _Opcode_name[631:641],
|
129: _Opcode_name[688:694],
|
||||||
198: _Opcode_name[641:650],
|
130: _Opcode_name[694:700],
|
||||||
200: _Opcode_name[650:656],
|
131: _Opcode_name[700:706],
|
||||||
202: _Opcode_name[656:660],
|
132: _Opcode_name[706:712],
|
||||||
203: _Opcode_name[660:666],
|
133: _Opcode_name[712:718],
|
||||||
204: _Opcode_name[666:670],
|
134: _Opcode_name[718:724],
|
||||||
205: _Opcode_name[670:676],
|
135: _Opcode_name[724:729],
|
||||||
206: _Opcode_name[676:684],
|
139: _Opcode_name[729:732],
|
||||||
207: _Opcode_name[684:690],
|
140: _Opcode_name[732:738],
|
||||||
208: _Opcode_name[690:697],
|
141: _Opcode_name[738:742],
|
||||||
209: _Opcode_name[697:709],
|
142: _Opcode_name[742:747],
|
||||||
210: _Opcode_name[709:715],
|
144: _Opcode_name[747:753],
|
||||||
211: _Opcode_name[715:725],
|
145: _Opcode_name[753:756],
|
||||||
216: _Opcode_name[725:731],
|
146: _Opcode_name[756:758],
|
||||||
217: _Opcode_name[731:737],
|
147: _Opcode_name[758:761],
|
||||||
219: _Opcode_name[737:744],
|
151: _Opcode_name[761:766],
|
||||||
|
152: _Opcode_name[766:774],
|
||||||
|
153: _Opcode_name[774:778],
|
||||||
|
154: _Opcode_name[778:781],
|
||||||
|
155: _Opcode_name[781:787],
|
||||||
|
156: _Opcode_name[787:790],
|
||||||
|
157: _Opcode_name[790:793],
|
||||||
|
158: _Opcode_name[793:796],
|
||||||
|
159: _Opcode_name[796:799],
|
||||||
|
160: _Opcode_name[799:802],
|
||||||
|
161: _Opcode_name[802:805],
|
||||||
|
162: _Opcode_name[805:808],
|
||||||
|
168: _Opcode_name[808:811],
|
||||||
|
169: _Opcode_name[811:814],
|
||||||
|
170: _Opcode_name[814:817],
|
||||||
|
171: _Opcode_name[817:824],
|
||||||
|
172: _Opcode_name[824:830],
|
||||||
|
177: _Opcode_name[830:832],
|
||||||
|
179: _Opcode_name[832:840],
|
||||||
|
180: _Opcode_name[840:851],
|
||||||
|
181: _Opcode_name[851:853],
|
||||||
|
182: _Opcode_name[853:856],
|
||||||
|
183: _Opcode_name[856:858],
|
||||||
|
184: _Opcode_name[858:861],
|
||||||
|
185: _Opcode_name[861:864],
|
||||||
|
186: _Opcode_name[864:867],
|
||||||
|
187: _Opcode_name[867:873],
|
||||||
|
192: _Opcode_name[873:877],
|
||||||
|
193: _Opcode_name[877:883],
|
||||||
|
194: _Opcode_name[883:892],
|
||||||
|
195: _Opcode_name[892:900],
|
||||||
|
196: _Opcode_name[900:909],
|
||||||
|
197: _Opcode_name[909:919],
|
||||||
|
198: _Opcode_name[919:928],
|
||||||
|
200: _Opcode_name[928:934],
|
||||||
|
202: _Opcode_name[934:938],
|
||||||
|
203: _Opcode_name[938:944],
|
||||||
|
204: _Opcode_name[944:948],
|
||||||
|
205: _Opcode_name[948:954],
|
||||||
|
206: _Opcode_name[954:962],
|
||||||
|
207: _Opcode_name[962:968],
|
||||||
|
208: _Opcode_name[968:975],
|
||||||
|
209: _Opcode_name[975:987],
|
||||||
|
210: _Opcode_name[987:993],
|
||||||
|
211: _Opcode_name[993:1003],
|
||||||
|
216: _Opcode_name[1003:1009],
|
||||||
|
217: _Opcode_name[1009:1015],
|
||||||
|
219: _Opcode_name[1015:1022],
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i Opcode) String() string {
|
func (i Opcode) String() string {
|
||||||
|
|
62
pkg/vm/ref_counter.go
Normal file
62
pkg/vm/ref_counter.go
Normal file
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
32
pkg/vm/ref_counter_test.go
Normal file
32
pkg/vm/ref_counter_test.go
Normal file
|
@ -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)
|
||||||
|
}
|
43
pkg/vm/slot.go
Normal file
43
pkg/vm/slot.go
Normal file
|
@ -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) }
|
21
pkg/vm/slot_test.go
Normal file
21
pkg/vm/slot_test.go
Normal file
|
@ -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))
|
||||||
|
}
|
|
@ -125,9 +125,7 @@ type Stack struct {
|
||||||
top Element
|
top Element
|
||||||
name string
|
name string
|
||||||
len int
|
len int
|
||||||
|
refs *refCounter
|
||||||
itemCount map[StackItem]int
|
|
||||||
size *int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewStack returns a new stack name by the given name.
|
// 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.next = &s.top
|
||||||
s.top.prev = &s.top
|
s.top.prev = &s.top
|
||||||
s.len = 0
|
s.len = 0
|
||||||
s.itemCount = make(map[StackItem]int)
|
s.refs = newRefCounter()
|
||||||
s.size = new(int)
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,58 +168,11 @@ func (s *Stack) insert(e, at *Element) *Element {
|
||||||
e.stack = s
|
e.stack = s
|
||||||
s.len++
|
s.len++
|
||||||
|
|
||||||
s.updateSizeAdd(e.value)
|
s.refs.Add(e.value)
|
||||||
|
|
||||||
return e
|
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.
|
// InsertAt inserts the given item (n) deep on the stack.
|
||||||
// Be very careful using it and _always_ check both e and n before invocation
|
// Be very careful using it and _always_ check both e and n before invocation
|
||||||
// as it will silently do wrong things otherwise.
|
// as it will silently do wrong things otherwise.
|
||||||
|
@ -300,7 +250,7 @@ func (s *Stack) Remove(e *Element) *Element {
|
||||||
e.stack = nil
|
e.stack = nil
|
||||||
s.len--
|
s.len--
|
||||||
|
|
||||||
s.updateSizeRemove(e.value)
|
s.refs.Remove(e.value)
|
||||||
|
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
121
pkg/vm/vm.go
121
pkg/vm/vm.go
|
@ -75,11 +75,12 @@ type VM struct {
|
||||||
estack *Stack // execution stack.
|
estack *Stack // execution stack.
|
||||||
astack *Stack // alt stack.
|
astack *Stack // alt stack.
|
||||||
|
|
||||||
|
static *Slot
|
||||||
|
|
||||||
// Hash to verify in CHECKSIG/CHECKMULTISIG.
|
// Hash to verify in CHECKSIG/CHECKMULTISIG.
|
||||||
checkhash []byte
|
checkhash []byte
|
||||||
|
|
||||||
itemCount map[StackItem]int
|
refs *refCounter
|
||||||
size int
|
|
||||||
|
|
||||||
gasConsumed util.Fixed8
|
gasConsumed util.Fixed8
|
||||||
gasLimit 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.
|
getInterop: make([]InteropGetterFunc, 0, 3), // 3 functions is typical for our default usage.
|
||||||
state: haltState,
|
state: haltState,
|
||||||
istack: NewStack("invocation"),
|
istack: NewStack("invocation"),
|
||||||
|
refs: newRefCounter(),
|
||||||
itemCount: make(map[StackItem]int),
|
keys: make(map[string]*keys.PublicKey),
|
||||||
keys: make(map[string]*keys.PublicKey),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vm.estack = vm.newItemStack("evaluation")
|
vm.estack = vm.newItemStack("evaluation")
|
||||||
|
@ -108,8 +108,7 @@ func New() *VM {
|
||||||
|
|
||||||
func (v *VM) newItemStack(n string) *Stack {
|
func (v *VM) newItemStack(n string) *Stack {
|
||||||
s := NewStack(n)
|
s := NewStack(n)
|
||||||
s.size = &v.size
|
s.refs = v.refs
|
||||||
s.itemCount = v.itemCount
|
|
||||||
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
@ -499,7 +498,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
||||||
if errRecover := recover(); errRecover != nil {
|
if errRecover := recover(); errRecover != nil {
|
||||||
v.state = faultState
|
v.state = faultState
|
||||||
err = newError(ctx.ip, op, errRecover)
|
err = newError(ctx.ip, op, errRecover)
|
||||||
} else if v.size > MaxStackSize {
|
} else if v.refs.size > MaxStackSize {
|
||||||
v.state = faultState
|
v.state = faultState
|
||||||
err = newError(ctx.ip, op, "stack is too big")
|
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)
|
v.estack.PushVal(result)
|
||||||
|
|
||||||
// Stack operations.
|
case opcode.INITSSLOT:
|
||||||
case opcode.TOALTSTACK:
|
if v.static != nil {
|
||||||
v.astack.Push(v.estack.Pop())
|
panic("already initialized")
|
||||||
|
}
|
||||||
|
if parameter[0] == 0 {
|
||||||
|
panic("zero argument")
|
||||||
|
}
|
||||||
|
v.static = v.newSlot(int(parameter[0]))
|
||||||
|
|
||||||
case opcode.FROMALTSTACK:
|
case opcode.INITSLOT:
|
||||||
v.estack.Push(v.astack.Pop())
|
if ctx.local != nil || ctx.arguments != nil {
|
||||||
|
panic("already initialized")
|
||||||
|
}
|
||||||
|
if parameter[0] == 0 && parameter[1] == 0 {
|
||||||
|
panic("zero argument")
|
||||||
|
}
|
||||||
|
if parameter[0] > 0 {
|
||||||
|
ctx.local = v.newSlot(int(parameter[0]))
|
||||||
|
}
|
||||||
|
if parameter[1] > 0 {
|
||||||
|
sz := int(parameter[1])
|
||||||
|
ctx.arguments = v.newSlot(sz)
|
||||||
|
for i := 0; i < sz; i++ {
|
||||||
|
ctx.arguments.Set(i, v.estack.Pop().Item())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case opcode.DUPFROMALTSTACK:
|
case opcode.LDSFLD0, opcode.LDSFLD1, opcode.LDSFLD2, opcode.LDSFLD3, opcode.LDSFLD4, opcode.LDSFLD5, opcode.LDSFLD6:
|
||||||
v.estack.Push(v.astack.Dup(0))
|
item := v.static.Get(int(op - opcode.LDSFLD0))
|
||||||
|
v.estack.PushVal(item)
|
||||||
|
|
||||||
|
case opcode.LDSFLD:
|
||||||
|
item := v.static.Get(int(parameter[0]))
|
||||||
|
v.estack.PushVal(item)
|
||||||
|
|
||||||
|
case opcode.STSFLD0, opcode.STSFLD1, opcode.STSFLD2, opcode.STSFLD3, opcode.STSFLD4, opcode.STSFLD5, opcode.STSFLD6:
|
||||||
|
item := v.estack.Pop().Item()
|
||||||
|
v.static.Set(int(op-opcode.STSFLD0), item)
|
||||||
|
|
||||||
|
case opcode.STSFLD:
|
||||||
|
item := v.estack.Pop().Item()
|
||||||
|
v.static.Set(int(parameter[0]), item)
|
||||||
|
|
||||||
|
case opcode.LDLOC0, opcode.LDLOC1, opcode.LDLOC2, opcode.LDLOC3, opcode.LDLOC4, opcode.LDLOC5, opcode.LDLOC6:
|
||||||
|
item := ctx.local.Get(int(op - opcode.LDLOC0))
|
||||||
|
v.estack.PushVal(item)
|
||||||
|
|
||||||
|
case opcode.LDLOC:
|
||||||
|
item := ctx.local.Get(int(parameter[0]))
|
||||||
|
v.estack.PushVal(item)
|
||||||
|
|
||||||
|
case opcode.STLOC0, opcode.STLOC1, opcode.STLOC2, opcode.STLOC3, opcode.STLOC4, opcode.STLOC5, opcode.STLOC6:
|
||||||
|
item := v.estack.Pop().Item()
|
||||||
|
ctx.local.Set(int(op-opcode.STLOC0), item)
|
||||||
|
|
||||||
|
case opcode.STLOC:
|
||||||
|
item := v.estack.Pop().Item()
|
||||||
|
ctx.local.Set(int(parameter[0]), item)
|
||||||
|
|
||||||
|
case opcode.LDARG0, opcode.LDARG1, opcode.LDARG2, opcode.LDARG3, opcode.LDARG4, opcode.LDARG5, opcode.LDARG6:
|
||||||
|
item := ctx.arguments.Get(int(op - opcode.LDARG0))
|
||||||
|
v.estack.PushVal(item)
|
||||||
|
|
||||||
|
case opcode.LDARG:
|
||||||
|
item := ctx.arguments.Get(int(parameter[0]))
|
||||||
|
v.estack.PushVal(item)
|
||||||
|
|
||||||
|
case opcode.STARG0, opcode.STARG1, opcode.STARG2, opcode.STARG3, opcode.STARG4, opcode.STARG5, opcode.STARG6:
|
||||||
|
item := v.estack.Pop().Item()
|
||||||
|
ctx.arguments.Set(int(op-opcode.STARG0), item)
|
||||||
|
|
||||||
|
case opcode.STARG:
|
||||||
|
item := v.estack.Pop().Item()
|
||||||
|
ctx.arguments.Set(int(parameter[0]), item)
|
||||||
|
|
||||||
case opcode.CAT:
|
case opcode.CAT:
|
||||||
b := v.estack.Pop().Bytes()
|
b := v.estack.Pop().Bytes()
|
||||||
|
@ -955,7 +1019,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
||||||
panic("APPEND: not of underlying type Array")
|
panic("APPEND: not of underlying type Array")
|
||||||
}
|
}
|
||||||
|
|
||||||
v.estack.updateSizeAdd(val)
|
v.refs.Add(val)
|
||||||
|
|
||||||
case opcode.PACK:
|
case opcode.PACK:
|
||||||
n := int(v.estack.Pop().BigInt().Int64())
|
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) {
|
if index < 0 || index >= len(arr) {
|
||||||
panic("SETITEM: invalid index")
|
panic("SETITEM: invalid index")
|
||||||
}
|
}
|
||||||
v.estack.updateSizeRemove(arr[index])
|
v.refs.Remove(arr[index])
|
||||||
arr[index] = item
|
arr[index] = item
|
||||||
v.estack.updateSizeAdd(arr[index])
|
v.refs.Add(arr[index])
|
||||||
case *MapItem:
|
case *MapItem:
|
||||||
if t.Has(key.value) {
|
if t.Has(key.value) {
|
||||||
v.estack.updateSizeRemove(item)
|
v.refs.Remove(item)
|
||||||
} else if len(t.value) >= MaxArraySize {
|
} else if len(t.value) >= MaxArraySize {
|
||||||
panic("too big map")
|
panic("too big map")
|
||||||
}
|
}
|
||||||
t.Add(key.value, item)
|
t.Add(key.value, item)
|
||||||
v.estack.updateSizeAdd(item)
|
v.refs.Add(item)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("SETITEM: invalid item type %s", t))
|
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) {
|
if k < 0 || k >= len(a) {
|
||||||
panic("REMOVE: invalid index")
|
panic("REMOVE: invalid index")
|
||||||
}
|
}
|
||||||
v.estack.updateSizeRemove(a[k])
|
v.refs.Remove(a[k])
|
||||||
a = append(a[:k], a[k+1:]...)
|
a = append(a[:k], a[k+1:]...)
|
||||||
t.value = a
|
t.value = a
|
||||||
case *StructItem:
|
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) {
|
if k < 0 || k >= len(a) {
|
||||||
panic("REMOVE: invalid index")
|
panic("REMOVE: invalid index")
|
||||||
}
|
}
|
||||||
v.estack.updateSizeRemove(a[k])
|
v.refs.Remove(a[k])
|
||||||
a = append(a[:k], a[k+1:]...)
|
a = append(a[:k], a[k+1:]...)
|
||||||
t.value = a
|
t.value = a
|
||||||
case *MapItem:
|
case *MapItem:
|
||||||
index := t.Index(key.Item())
|
index := t.Index(key.Item())
|
||||||
// NEO 2.0 doesn't error on missing key.
|
// NEO 2.0 doesn't error on missing key.
|
||||||
if index >= 0 {
|
if index >= 0 {
|
||||||
v.estack.updateSizeRemove(t.value[index].Value)
|
v.refs.Remove(t.value[index].Value)
|
||||||
t.Drop(index)
|
t.Drop(index)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
@ -1087,17 +1151,17 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
||||||
switch t := elem.value.(type) {
|
switch t := elem.value.(type) {
|
||||||
case *ArrayItem:
|
case *ArrayItem:
|
||||||
for _, item := range t.value {
|
for _, item := range t.value {
|
||||||
v.estack.updateSizeRemove(item)
|
v.refs.Remove(item)
|
||||||
}
|
}
|
||||||
t.value = t.value[:0]
|
t.value = t.value[:0]
|
||||||
case *StructItem:
|
case *StructItem:
|
||||||
for _, item := range t.value {
|
for _, item := range t.value {
|
||||||
v.estack.updateSizeRemove(item)
|
v.refs.Remove(item)
|
||||||
}
|
}
|
||||||
t.value = t.value[:0]
|
t.value = t.value[:0]
|
||||||
case *MapItem:
|
case *MapItem:
|
||||||
for i := range t.value {
|
for i := range t.value {
|
||||||
v.estack.updateSizeRemove(t.value[i].Value)
|
v.refs.Remove(t.value[i].Value)
|
||||||
}
|
}
|
||||||
t.value = t.value[:0]
|
t.value = t.value[:0]
|
||||||
default:
|
default:
|
||||||
|
@ -1137,8 +1201,9 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
||||||
|
|
||||||
case opcode.CALL, opcode.CALLL:
|
case opcode.CALL, opcode.CALLL:
|
||||||
v.checkInvocationStackSize()
|
v.checkInvocationStackSize()
|
||||||
|
|
||||||
newCtx := ctx.Copy()
|
newCtx := ctx.Copy()
|
||||||
|
newCtx.local = nil
|
||||||
|
newCtx.arguments = nil
|
||||||
newCtx.rvcount = -1
|
newCtx.rvcount = -1
|
||||||
v.istack.PushVal(newCtx)
|
v.istack.PushVal(newCtx)
|
||||||
|
|
||||||
|
@ -1152,6 +1217,8 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
||||||
}
|
}
|
||||||
|
|
||||||
newCtx := ctx.Copy()
|
newCtx := ctx.Copy()
|
||||||
|
newCtx.local = nil
|
||||||
|
newCtx.arguments = nil
|
||||||
newCtx.rvcount = -1
|
newCtx.rvcount = -1
|
||||||
v.istack.PushVal(newCtx)
|
v.istack.PushVal(newCtx)
|
||||||
v.jumpIf(newCtx, ptr.pos, true)
|
v.jumpIf(newCtx, ptr.pos, true)
|
||||||
|
|
|
@ -370,10 +370,12 @@ func appendBigStruct(size uint16) []opcode.Opcode {
|
||||||
}
|
}
|
||||||
|
|
||||||
return append(prog,
|
return append(prog,
|
||||||
|
opcode.INITSSLOT, 1,
|
||||||
opcode.PUSHINT16, opcode.Opcode(size), opcode.Opcode(size>>8), // LE
|
opcode.PUSHINT16, opcode.Opcode(size), opcode.Opcode(size>>8), // LE
|
||||||
opcode.PACK, opcode.NEWSTRUCT,
|
opcode.PACK, opcode.NEWSTRUCT,
|
||||||
|
opcode.STSFLD0, opcode.LDSFLD0,
|
||||||
opcode.DUP,
|
opcode.DUP,
|
||||||
opcode.PUSH0, opcode.NEWARRAY, opcode.TOALTSTACK, opcode.DUPFROMALTSTACK,
|
opcode.PUSH0, opcode.NEWARRAY,
|
||||||
opcode.SWAP,
|
opcode.SWAP,
|
||||||
opcode.APPEND, opcode.RET)
|
opcode.APPEND, opcode.RET)
|
||||||
}
|
}
|
||||||
|
@ -397,17 +399,17 @@ func TestStackLimit(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{opcode.PUSH2, 1},
|
{opcode.PUSH2, 1},
|
||||||
{opcode.NEWARRAY, 3}, // array + 2 items
|
{opcode.NEWARRAY, 3}, // array + 2 items
|
||||||
{opcode.TOALTSTACK, 3},
|
{opcode.STSFLD0, 3},
|
||||||
{opcode.DUPFROMALTSTACK, 4},
|
{opcode.LDSFLD0, 4},
|
||||||
{opcode.NEWSTRUCT, 6}, // all items are copied
|
{opcode.NEWSTRUCT, 6}, // all items are copied
|
||||||
{opcode.NEWMAP, 7},
|
{opcode.NEWMAP, 7},
|
||||||
{opcode.DUP, 8},
|
{opcode.DUP, 8},
|
||||||
{opcode.PUSH2, 9},
|
{opcode.PUSH2, 9},
|
||||||
{opcode.DUPFROMALTSTACK, 10},
|
{opcode.LDSFLD0, 10},
|
||||||
{opcode.SETITEM, 8}, // -3 items and 1 new element in map
|
{opcode.SETITEM, 8}, // -3 items and 1 new element in map
|
||||||
{opcode.DUP, 9},
|
{opcode.DUP, 9},
|
||||||
{opcode.PUSH2, 10},
|
{opcode.PUSH2, 10},
|
||||||
{opcode.DUPFROMALTSTACK, 11},
|
{opcode.LDSFLD0, 11},
|
||||||
{opcode.SETITEM, 8}, // -3 items and no new elements in map
|
{opcode.SETITEM, 8}, // -3 items and no new elements in map
|
||||||
{opcode.DUP, 9},
|
{opcode.DUP, 9},
|
||||||
{opcode.PUSH2, 10},
|
{opcode.PUSH2, 10},
|
||||||
|
@ -415,15 +417,18 @@ func TestStackLimit(t *testing.T) {
|
||||||
{opcode.DROP, 6}, // DROP map with no elements
|
{opcode.DROP, 6}, // DROP map with no elements
|
||||||
}
|
}
|
||||||
|
|
||||||
prog := make([]opcode.Opcode, len(expected))
|
prog := make([]opcode.Opcode, len(expected)+2)
|
||||||
|
prog[0] = opcode.INITSSLOT
|
||||||
|
prog[1] = 1
|
||||||
for i := range expected {
|
for i := range expected {
|
||||||
prog[i] = expected[i].inst
|
prog[i+2] = expected[i].inst
|
||||||
}
|
}
|
||||||
|
|
||||||
vm := load(makeProgram(prog...))
|
vm := load(makeProgram(prog...))
|
||||||
|
require.NoError(t, vm.Step(), "failed to initialize static slot")
|
||||||
for i := range expected {
|
for i := range expected {
|
||||||
require.NoError(t, vm.Step())
|
require.NoError(t, vm.Step())
|
||||||
require.Equal(t, expected[i].size, vm.size)
|
require.Equal(t, expected[i].size, vm.refs.size, "i: %d", i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -477,18 +482,18 @@ func TestPushData4BigN(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getEnumeratorProg(n int, isIter bool) (prog []byte) {
|
func getEnumeratorProg(n int, isIter bool) (prog []byte) {
|
||||||
prog = append(prog, byte(opcode.TOALTSTACK))
|
prog = []byte{byte(opcode.INITSSLOT), 1, byte(opcode.STSFLD0)}
|
||||||
for i := 0; i < n; i++ {
|
for i := 0; i < n; i++ {
|
||||||
prog = append(prog, byte(opcode.DUPFROMALTSTACK))
|
prog = append(prog, byte(opcode.LDSFLD0))
|
||||||
prog = append(prog, getSyscallProg("Neo.Enumerator.Next")...)
|
prog = append(prog, getSyscallProg("Neo.Enumerator.Next")...)
|
||||||
prog = append(prog, byte(opcode.DUPFROMALTSTACK))
|
prog = append(prog, byte(opcode.LDSFLD0))
|
||||||
prog = append(prog, getSyscallProg("Neo.Enumerator.Value")...)
|
prog = append(prog, getSyscallProg("Neo.Enumerator.Value")...)
|
||||||
if isIter {
|
if isIter {
|
||||||
prog = append(prog, byte(opcode.DUPFROMALTSTACK))
|
prog = append(prog, byte(opcode.LDSFLD0))
|
||||||
prog = append(prog, getSyscallProg("Neo.Iterator.Key")...)
|
prog = append(prog, getSyscallProg("Neo.Iterator.Key")...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
prog = append(prog, byte(opcode.DUPFROMALTSTACK))
|
prog = append(prog, byte(opcode.LDSFLD0))
|
||||||
prog = append(prog, getSyscallProg("Neo.Enumerator.Next")...)
|
prog = append(prog, getSyscallProg("Neo.Enumerator.Next")...)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -583,7 +588,6 @@ func TestIteratorConcat(t *testing.T) {
|
||||||
func TestIteratorKeys(t *testing.T) {
|
func TestIteratorKeys(t *testing.T) {
|
||||||
prog := getSyscallProg("Neo.Iterator.Create")
|
prog := getSyscallProg("Neo.Iterator.Create")
|
||||||
prog = append(prog, getSyscallProg("Neo.Iterator.Keys")...)
|
prog = append(prog, getSyscallProg("Neo.Iterator.Keys")...)
|
||||||
prog = append(prog, byte(opcode.TOALTSTACK), byte(opcode.DUPFROMALTSTACK))
|
|
||||||
prog = append(prog, getEnumeratorProg(2, false)...)
|
prog = append(prog, getEnumeratorProg(2, false)...)
|
||||||
|
|
||||||
v := load(prog)
|
v := load(prog)
|
||||||
|
@ -604,7 +608,6 @@ func TestIteratorKeys(t *testing.T) {
|
||||||
func TestIteratorValues(t *testing.T) {
|
func TestIteratorValues(t *testing.T) {
|
||||||
prog := getSyscallProg("Neo.Iterator.Create")
|
prog := getSyscallProg("Neo.Iterator.Create")
|
||||||
prog = append(prog, getSyscallProg("Neo.Iterator.Values")...)
|
prog = append(prog, getSyscallProg("Neo.Iterator.Values")...)
|
||||||
prog = append(prog, byte(opcode.TOALTSTACK), byte(opcode.DUPFROMALTSTACK))
|
|
||||||
prog = append(prog, getEnumeratorProg(2, false)...)
|
prog = append(prog, getEnumeratorProg(2, false)...)
|
||||||
|
|
||||||
v := load(prog)
|
v := load(prog)
|
||||||
|
@ -716,11 +719,11 @@ func TestSerializeArrayBad(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSerializeDupInteger(t *testing.T) {
|
func TestSerializeDupInteger(t *testing.T) {
|
||||||
prog := []byte{
|
prog := makeProgram(
|
||||||
byte(opcode.PUSH0), byte(opcode.NEWARRAY),
|
opcode.PUSH0, opcode.NEWARRAY, opcode.INITSSLOT, 1,
|
||||||
byte(opcode.DUP), byte(opcode.PUSH2), byte(opcode.DUP), byte(opcode.TOALTSTACK), byte(opcode.APPEND),
|
opcode.DUP, opcode.PUSH2, opcode.DUP, opcode.STSFLD0, opcode.APPEND,
|
||||||
byte(opcode.DUP), byte(opcode.FROMALTSTACK), byte(opcode.APPEND),
|
opcode.DUP, opcode.LDSFLD0, opcode.APPEND,
|
||||||
}
|
)
|
||||||
vm := load(append(prog, getSerializeProg()...))
|
vm := load(append(prog, getSerializeProg()...))
|
||||||
|
|
||||||
runVM(t, vm)
|
runVM(t, vm)
|
||||||
|
@ -801,9 +804,10 @@ func TestSerializeInterop(t *testing.T) {
|
||||||
func callNTimes(n uint16) []byte {
|
func callNTimes(n uint16) []byte {
|
||||||
return makeProgram(
|
return makeProgram(
|
||||||
opcode.PUSHINT16, opcode.Opcode(n), opcode.Opcode(n>>8), // little-endian
|
opcode.PUSHINT16, opcode.Opcode(n), opcode.Opcode(n>>8), // little-endian
|
||||||
opcode.TOALTSTACK, opcode.DUPFROMALTSTACK,
|
opcode.INITSSLOT, 1,
|
||||||
|
opcode.STSFLD0, opcode.LDSFLD0,
|
||||||
opcode.JMPIF, 0x3, opcode.RET,
|
opcode.JMPIF, 0x3, opcode.RET,
|
||||||
opcode.FROMALTSTACK, opcode.DEC,
|
opcode.LDSFLD0, opcode.DEC,
|
||||||
opcode.CALL, 0xF9) // -7 -> JMP to TOALTSTACK)
|
opcode.CALL, 0xF9) // -7 -> JMP to TOALTSTACK)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1171,9 +1175,10 @@ func testNEWARRAYIssue437(t *testing.T, i1, i2 opcode.Opcode, appended bool) {
|
||||||
prog := makeProgram(
|
prog := makeProgram(
|
||||||
opcode.PUSH2, i1,
|
opcode.PUSH2, i1,
|
||||||
opcode.DUP, opcode.PUSH3, opcode.APPEND,
|
opcode.DUP, opcode.PUSH3, opcode.APPEND,
|
||||||
opcode.TOALTSTACK, opcode.DUPFROMALTSTACK, i2,
|
opcode.INITSSLOT, 1,
|
||||||
|
opcode.STSFLD0, opcode.LDSFLD0, i2,
|
||||||
opcode.DUP, opcode.PUSH4, opcode.APPEND,
|
opcode.DUP, opcode.PUSH4, opcode.APPEND,
|
||||||
opcode.FROMALTSTACK, opcode.PUSH5, opcode.APPEND)
|
opcode.LDSFLD0, opcode.PUSH5, opcode.APPEND)
|
||||||
vm := load(prog)
|
vm := load(prog)
|
||||||
vm.Run()
|
vm.Run()
|
||||||
|
|
||||||
|
@ -1233,8 +1238,8 @@ func TestAPPEND(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAPPENDCloneStruct(t *testing.T) {
|
func TestAPPENDCloneStruct(t *testing.T) {
|
||||||
prog := makeProgram(opcode.DUP, opcode.PUSH0, opcode.NEWSTRUCT, opcode.TOALTSTACK,
|
prog := makeProgram(opcode.DUP, opcode.PUSH0, opcode.NEWSTRUCT, opcode.INITSSLOT, 1, opcode.STSFLD0,
|
||||||
opcode.DUPFROMALTSTACK, opcode.APPEND, opcode.FROMALTSTACK, opcode.PUSH1, opcode.APPEND)
|
opcode.LDSFLD0, opcode.APPEND, opcode.LDSFLD0, opcode.PUSH1, opcode.APPEND)
|
||||||
arr := []StackItem{&StructItem{[]StackItem{}}}
|
arr := []StackItem{&StructItem{[]StackItem{}}}
|
||||||
runWithArgs(t, prog, NewArrayItem(arr), NewArrayItem(nil))
|
runWithArgs(t, prog, NewArrayItem(arr), NewArrayItem(nil))
|
||||||
}
|
}
|
||||||
|
@ -1980,7 +1985,7 @@ func testCLEARITEMS(t *testing.T, item StackItem) {
|
||||||
v.estack.PushVal(item)
|
v.estack.PushVal(item)
|
||||||
runVM(t, v)
|
runVM(t, v)
|
||||||
require.Equal(t, 2, v.estack.Len())
|
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())
|
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 {
|
func makeProgram(opcodes ...opcode.Opcode) []byte {
|
||||||
prog := make([]byte, len(opcodes)+1) // RET
|
prog := make([]byte, len(opcodes)+1) // RET
|
||||||
for i := 0; i < len(opcodes); i++ {
|
for i := 0; i < len(opcodes); i++ {
|
||||||
|
|
Loading…
Reference in a new issue