From 8fe079ec8ebb83ce53d6f1535bfe513271231958 Mon Sep 17 00:00:00 2001 From: Anthony De Meulemeester Date: Mon, 19 Feb 2018 10:24:28 +0100 Subject: [PATCH] Update compiler (#22) * refactor to use ast.Walk for recursive converting * added lots of test cases * added a new way to handle jump labels * function calls with multiple arguments * binary expression (LOR LAND) * struct types + method receives * cleaner opcode dumps, side by side diff for debugging test cases --- VERSION | 2 +- cli/smartcontract/smart_contract.go | 45 +- pkg/vm/compiler/README.md | 35 ++ pkg/vm/compiler/codegen.go | 472 ++++++++++++++++++ pkg/vm/compiler/compiler.go | 525 +++----------------- pkg/vm/compiler/emit.go | 101 ++++ pkg/vm/compiler/func_context.go | 96 ---- pkg/vm/compiler/func_scope.go | 89 ++++ pkg/vm/compiler/script_builder.go | 152 ------ pkg/vm/compiler/script_builder_test.go | 104 ---- pkg/vm/compiler/struct_scope.go | 51 ++ pkg/vm/compiler/tests/array_test.go | 26 + pkg/vm/compiler/tests/assign_test.go | 31 ++ pkg/vm/compiler/tests/bool_test.go | 15 + pkg/vm/compiler/tests/compiler_test.go | 18 +- pkg/vm/compiler/tests/function_call_test.go | 15 + pkg/vm/compiler/tests/struct_test.go | 157 ++++++ pkg/vm/opcode.go | 214 ++++---- pkg/vm/opcode_string.go | 214 ++++---- 19 files changed, 1298 insertions(+), 1064 deletions(-) create mode 100644 pkg/vm/compiler/README.md create mode 100644 pkg/vm/compiler/codegen.go create mode 100644 pkg/vm/compiler/emit.go delete mode 100644 pkg/vm/compiler/func_context.go create mode 100644 pkg/vm/compiler/func_scope.go delete mode 100644 pkg/vm/compiler/script_builder.go delete mode 100644 pkg/vm/compiler/script_builder_test.go create mode 100644 pkg/vm/compiler/struct_scope.go create mode 100644 pkg/vm/compiler/tests/array_test.go create mode 100644 pkg/vm/compiler/tests/assign_test.go create mode 100644 pkg/vm/compiler/tests/bool_test.go create mode 100644 pkg/vm/compiler/tests/struct_test.go diff --git a/VERSION b/VERSION index 78bc1abd1..d9df1bbc0 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.10.0 +0.11.0 diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index e2cc2084d..4425d8cb5 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -1,12 +1,7 @@ package smartcontract import ( - "encoding/hex" "errors" - "fmt" - "io" - "os" - "strings" "github.com/CityOfZion/neo-go/pkg/vm/compiler" "github.com/urfave/cli" @@ -43,39 +38,19 @@ func contractCompile(ctx *cli.Context) error { return errors.New("not enough arguments") } + o := &compiler.Options{ + Outfile: ctx.String("out"), + Debug: true, + } + src := ctx.Args()[0] - c := compiler.New() - if err := c.CompileSource(src); err != nil { - return err - } - - filename := strings.Split(src, ".")[0] - filename = filename + ".avm" - - out := ctx.String("out") - if len(out) > 0 { - filename = out - } - - f, err := os.Create(out) - if err != nil { - return err - } - - hx := hex.EncodeToString(c.Buffer().Bytes()) - fmt.Println(hx) - - _, err = io.Copy(f, c.Buffer()) - return err + return compiler.CompileAndSave(src, o) } func contractDumpOpcode(ctx *cli.Context) error { - src := ctx.Args()[0] - - c := compiler.New() - if err := c.CompileSource(src); err != nil { - return err + if len(ctx.Args()) == 0 { + return errors.New("not enough arguments") } - c.DumpOpcode() - return nil + src := ctx.Args()[0] + return compiler.DumpOpcode(src) } diff --git a/pkg/vm/compiler/README.md b/pkg/vm/compiler/README.md new file mode 100644 index 000000000..254623b18 --- /dev/null +++ b/pkg/vm/compiler/README.md @@ -0,0 +1,35 @@ +# NEO-GO Compiler + +The neo-go compiler compiles Go programs to bytecode that the NEO virtual machine can understand. + +> The neo-go compiler is under very active development and will be updated on a weekly basis. + +## Currently supported +- type checker +- multiple assigns +- types int, string and bool +- struct types + method receives +- functions +- composite literals `[]int, []string` +- basic if statements +- binary expressions. +- return statements + +## Not yet implemented +- for loops +- ranges +- builtins (append, len, ..) +- blockchain helpers (sha256, storage, ..) +- import packages + +## Not supported +Due to the limitations of the NEO virtual machine, features listed below will not be supported. +- channels +- goroutines +- multiple returns + +## How to report bugs +1. Make a proper testcase (example testcases can be found in the tests folder) +2. Create an issue on Github +3. Make a PR with a reference to the created issue, containing the testcase that proves the bug +4. Either you fix the bug yourself or wait for patch that solves the problem diff --git a/pkg/vm/compiler/codegen.go b/pkg/vm/compiler/codegen.go new file mode 100644 index 000000000..d480e14b2 --- /dev/null +++ b/pkg/vm/compiler/codegen.go @@ -0,0 +1,472 @@ +package compiler + +import ( + "bytes" + "encoding/binary" + "go/ast" + "go/constant" + "go/token" + "go/types" + "log" + + "github.com/CityOfZion/neo-go/pkg/vm" +) + +const mainIdent = "Main" + +type codegen struct { + prog *bytes.Buffer + + // Type information + typeInfo *types.Info + + // a mapping of func identifiers with its scope. + funcs map[string]*funcScope + + // current function being generated + fctx *funcScope + + // Label table for recording jump destinations. + l []int +} + +// newLabel creates a new label to jump to +func (c *codegen) newLabel() (l int) { + l = len(c.l) + c.l = append(c.l, -1) + return +} + +func (c *codegen) setLabel(l int) { + c.l[l] = c.pc() + 1 +} + +// pc return the program offset off the last instruction. +func (c *codegen) pc() int { + return c.prog.Len() - 1 +} + +func (c *codegen) emitLoadConst(t types.TypeAndValue) { + switch typ := t.Type.(type) { + case *types.Basic: + switch typ.Kind() { + case types.Int: + val, _ := constant.Int64Val(t.Value) + emitInt(c.prog, val) + case types.String: + val := constant.StringVal(t.Value) + emitString(c.prog, val) + case types.Bool, types.UntypedBool: + val := constant.BoolVal(t.Value) + emitBool(c.prog, val) + } + default: + log.Fatalf("compiler don't know how to convert this constant: %v", t) + } +} + +func (c *codegen) emitLoadLocal(name string) { + pos := c.fctx.loadLocal(name) + if pos < 0 { + log.Fatalf("cannot load local variable with position: %d", pos) + } + + emitOpcode(c.prog, vm.Ofromaltstack) + emitOpcode(c.prog, vm.Odup) + emitOpcode(c.prog, vm.Otoaltstack) + + emitInt(c.prog, int64(pos)) + emitOpcode(c.prog, vm.Opickitem) +} + +func (c *codegen) emitLoadStructField(sName, fName string) { + strct := c.fctx.loadStruct(sName) + pos := strct.loadField(fName) + emitInt(c.prog, int64(pos)) + emitOpcode(c.prog, vm.Opickitem) +} + +func (c *codegen) emitStoreLocal(pos int) { + emitOpcode(c.prog, vm.Ofromaltstack) + emitOpcode(c.prog, vm.Odup) + emitOpcode(c.prog, vm.Otoaltstack) + + if pos < 0 { + log.Fatalf("invalid position to store local: %d", pos) + } + + emitInt(c.prog, int64(pos)) + emitInt(c.prog, 2) + emitOpcode(c.prog, vm.Oroll) + emitOpcode(c.prog, vm.Osetitem) +} + +func (c *codegen) emitStoreStructField(sName, fName string) { + strct := c.fctx.loadStruct(sName) + pos := strct.loadField(fName) + emitInt(c.prog, int64(pos)) + emitOpcode(c.prog, vm.Orot) + emitOpcode(c.prog, vm.Osetitem) +} + +func (c *codegen) convertFuncDecl(decl *ast.FuncDecl) { + var ( + f *funcScope + ok bool + ) + + f, ok = c.funcs[decl.Name.Name] + if ok { + c.setLabel(f.label) + } else { + f = c.newFunc(decl) + } + c.fctx = f + + emitInt(c.prog, f.stackSize()) + emitOpcode(c.prog, vm.Onewarray) + emitOpcode(c.prog, vm.Otoaltstack) + + // We need to handle methods, which in Go, is just syntactic sugar. + // The method receiver will be passed in as first argument. + // We check if this declaration has a receiver and load it into scope. + // + // FIXME: For now we will hard cast this to a struct. We can later finetune this + // to support other types. + if decl.Recv != nil { + for _, arg := range decl.Recv.List { + strct := c.fctx.newStruct() + + ident := arg.Names[0] + strct.initializeFields(ident, c.typeInfo) + l := c.fctx.newLocal(ident.Name) + c.emitStoreLocal(l) + } + } + + // Load the arguments in scope. + for _, arg := range decl.Type.Params.List { + name := arg.Names[0].Name // for now. + l := c.fctx.newLocal(name) + c.emitStoreLocal(l) + } + + ast.Walk(c, decl.Body) +} + +func (c *codegen) Visit(node ast.Node) ast.Visitor { + switch n := node.(type) { + + case *ast.AssignStmt: + for i := 0; i < len(n.Lhs); i++ { + // resolve the whole right hand side. + ast.Walk(c, n.Rhs[i]) + // check if we are assigning to a struct or an identifier + switch t := n.Lhs[i].(type) { + case *ast.Ident: + l := c.fctx.loadLocal(t.Name) + c.emitStoreLocal(l) + + case *ast.SelectorExpr: + switch n := t.X.(type) { + case *ast.Ident: + c.emitLoadLocal(n.Name) // load the struct + c.emitStoreStructField(n.Name, t.Sel.Name) // store the field + default: + log.Fatal("nested selector assigns not supported yet") + } + } + } + return nil + + case *ast.ReturnStmt: + if len(n.Results) > 1 { + log.Fatal("multiple returns not supported.") + } + + emitOpcode(c.prog, vm.Ojmp) + emitOpcode(c.prog, vm.Opcode(0x03)) + emitOpcode(c.prog, vm.Opush0) + + if len(n.Results) > 0 { + ast.Walk(c, n.Results[0]) + } + + emitOpcode(c.prog, vm.Onop) + emitOpcode(c.prog, vm.Ofromaltstack) + emitOpcode(c.prog, vm.Odrop) + emitOpcode(c.prog, vm.Oret) + return nil + + case *ast.IfStmt: + lIf := c.newLabel() + lElse := c.newLabel() + if n.Cond != nil { + ast.Walk(c, n.Cond) + emitJmp(c.prog, vm.Ojmpifnot, int16(lElse)) + } + + c.setLabel(lIf) + ast.Walk(c, n.Body) + + if n.Else != nil { + // TODO: handle else statements. + // emitJmp(c.prog, vm.Ojmp, int16(lEnd)) + } + c.setLabel(lElse) + if n.Else != nil { + ast.Walk(c, n.Else) + } + return nil + + case *ast.BasicLit: + c.emitLoadConst(c.getTypeInfo(n)) + return nil + + case *ast.Ident: + if isIdentBool(n) { + c.emitLoadConst(makeBoolFromIdent(n, c.typeInfo)) + } else { + c.emitLoadLocal(n.Name) + } + return nil + + case *ast.CompositeLit: + switch t := n.Type.(type) { + case *ast.Ident: + typ := c.typeInfo.ObjectOf(t).Type().Underlying() + switch typ.(type) { + case *types.Struct: + c.convertStruct(n) + } + + default: + ln := len(n.Elts) + for i := ln - 1; i >= 0; i-- { + c.emitLoadConst(c.getTypeInfo(n.Elts[i])) + } + emitInt(c.prog, int64(ln)) + emitOpcode(c.prog, vm.Opack) + } + return nil + + case *ast.BinaryExpr: + switch n.Op { + case token.LAND: + ast.Walk(c, n.X) + emitJmp(c.prog, vm.Ojmpifnot, int16(len(c.l)-1)) + ast.Walk(c, n.Y) + return nil + + case token.LOR: + ast.Walk(c, n.X) + emitJmp(c.prog, vm.Ojmpif, int16(len(c.l)-2)) + ast.Walk(c, n.Y) + return nil + + default: + // The AST package will try to resolve all basic literals for us. + // If the typeinfo.Value is not nil we know that the expr is resolved + // and needs no further action. e.g. x := 2 + 2 + 2 will be resolved to 6. + if tinfo := c.getTypeInfo(n); tinfo.Value != nil { + c.emitLoadConst(tinfo) + return c + } + + ast.Walk(c, n.X) + ast.Walk(c, n.Y) + + switch n.Op { + case token.ADD: + emitOpcode(c.prog, vm.Oadd) + case token.SUB: + emitOpcode(c.prog, vm.Osub) + case token.MUL: + emitOpcode(c.prog, vm.Omul) + case token.QUO: + emitOpcode(c.prog, vm.Odiv) + case token.LSS: + emitOpcode(c.prog, vm.Olt) + case token.LEQ: + emitOpcode(c.prog, vm.Olte) + case token.GTR: + emitOpcode(c.prog, vm.Ogt) + case token.GEQ: + emitOpcode(c.prog, vm.Ogte) + } + return nil + } + + case *ast.CallExpr: + var ( + f *funcScope + ok bool + numArgs = len(n.Args) + ) + + switch fun := n.Fun.(type) { + case *ast.Ident: + f, ok = c.funcs[fun.Name] + if !ok { + log.Fatalf("could not resolve function %s", fun.Name) + } + case *ast.SelectorExpr: + ast.Walk(c, fun.X) + f, ok = c.funcs[fun.Sel.Name] + if !ok { + log.Fatalf("could not resolve function %s", fun.Sel.Name) + } + // Dont forget to add 1 extra argument when its a method. + numArgs++ + } + + for _, arg := range n.Args { + ast.Walk(c, arg) + } + if numArgs == 2 { + emitOpcode(c.prog, vm.Oswap) + } + if numArgs == 3 { + emitInt(c.prog, 2) + emitOpcode(c.prog, vm.Oxswap) + } + + // c# compiler adds a NOP (0x61) before every function call. Dont think its relevant + // and we could easily removed it, but to be consistent with the original compiler I + // will put them in. ^^ + emitOpcode(c.prog, vm.Onop) + emitCall(c.prog, vm.Ocall, int16(f.label)) + return nil + + case *ast.SelectorExpr: + switch t := n.X.(type) { + case *ast.Ident: + c.emitLoadLocal(t.Name) // load the struct + c.emitLoadStructField(t.Name, n.Sel.Name) // load the field + default: + log.Fatal("nested selectors not supported yet") + } + return nil + } + return c +} + +func (c *codegen) convertStruct(lit *ast.CompositeLit) { + emitOpcode(c.prog, vm.Onop) + emitInt(c.prog, int64(len(lit.Elts))) + emitOpcode(c.prog, vm.Onewstruct) + emitOpcode(c.prog, vm.Otoaltstack) + + // Create a new struct scope to store the positions of its variables. + strct := c.fctx.newStruct() + + for _, field := range lit.Elts { + f := field.(*ast.KeyValueExpr) + // Walk to resolve the expression of the value. + ast.Walk(c, f.Value) + l := strct.newField(f.Key.(*ast.Ident).Name) + c.emitStoreLocal(l) + } + emitOpcode(c.prog, vm.Ofromaltstack) +} + +func (c *codegen) newFunc(decl *ast.FuncDecl) *funcScope { + f := newFuncScope(decl, c.newLabel()) + c.funcs[f.name] = f + return f +} + +func (c *codegen) getTypeInfo(expr ast.Expr) types.TypeAndValue { + return c.typeInfo.Types[expr] +} + +func makeBoolFromIdent(ident *ast.Ident, tinfo *types.Info) types.TypeAndValue { + var b bool + if ident.Name == "true" { + b = true + } else if ident.Name == "false" { + b = false + } else { + log.Fatalf("givent identifier cannot be converted to a boolean => %s", ident.Name) + } + + return types.TypeAndValue{ + Type: tinfo.ObjectOf(ident).Type(), + Value: constant.MakeBool(b), + } +} + +func isIdentBool(ident *ast.Ident) bool { + return ident.Name == "true" || ident.Name == "false" +} + +// CodeGen is the function that compiles the program to bytecode. +func CodeGen(f *ast.File, tInfo *types.Info) (*bytes.Buffer, error) { + c := &codegen{ + prog: new(bytes.Buffer), + l: []int{}, + funcs: map[string]*funcScope{}, + typeInfo: tInfo, + } + + var main *ast.FuncDecl + ast.Inspect(f, func(n ast.Node) bool { + switch t := n.(type) { + case *ast.FuncDecl: + if t.Name.Name == mainIdent { + main = t + return false + } + } + return true + }) + if main == nil { + log.Fatal("could not find func main. did you forgot to declare it?") + } + + c.resolveFuncDecls(f) + c.convertFuncDecl(main) + + for _, decl := range f.Decls { + switch n := decl.(type) { + case *ast.FuncDecl: + if n.Name.Name != mainIdent { + c.convertFuncDecl(n) + } + } + } + + c.writeJumps() + + return c.prog, nil +} + +func (c *codegen) resolveFuncDecls(f *ast.File) { + for _, decl := range f.Decls { + switch n := decl.(type) { + case *ast.FuncDecl: + if n.Name.Name != mainIdent { + c.newFunc(n) + } + } + } +} + +func (c *codegen) writeJumps() { + b := c.prog.Bytes() + for i, op := range b { + j := i + 1 + switch vm.Opcode(op) { + case vm.Ojmpifnot, vm.Ojmpif, vm.Ocall: + index := binary.LittleEndian.Uint16(b[j : j+2]) + if int(index) > len(c.l) { + continue + } + offset := uint16(c.l[index] - i) + if offset < 0 { + log.Fatalf("new offset is negative, table list %v", c.l) + } + binary.LittleEndian.PutUint16(b[j:j+2], offset) + } + } +} diff --git a/pkg/vm/compiler/compiler.go b/pkg/vm/compiler/compiler.go index eecd355a2..6aa7e8c9a 100644 --- a/pkg/vm/compiler/compiler.go +++ b/pkg/vm/compiler/compiler.go @@ -2,153 +2,44 @@ package compiler import ( "bytes" + "encoding/hex" + "errors" + "fmt" "go/ast" - "go/constant" "go/importer" "go/parser" "go/token" "go/types" "io" + "io/ioutil" "log" "os" - "reflect" + "strings" + "text/tabwriter" "github.com/CityOfZion/neo-go/pkg/vm" ) -const ( - outputExt = ".avm" - // Identifier off the entry point function. - mainIdent = "Main" -) +const fileExt = ".avm" -// CallContext represents more details on function calls in the program. -// It stores the position off where the call happend along with the -// function it called. The compiler will store all function calls, so -// it can update them later. -type CallContext struct { - pos int - funcName string +// Options contains all the parameters that affect the behaviour of the compiler. +type Options struct { + // The extension of the output file default set to .avm + Ext string + + // The name of the output file. + Outfile string + + // Debug will output an hex encoded string of the generated bytecode. + Debug bool } -// A VarContext holds the info about the context of a variable in the program. -type VarContext struct { - name string - tinfo types.TypeAndValue - pos int -} - -func newVarContext(name string, tinfo types.TypeAndValue) *VarContext { - return &VarContext{ - name: name, - pos: -1, - tinfo: tinfo, - } -} - -// Compiler holds the output buffer of the compiled source. -type Compiler struct { - // Output extension of the file. Default .avm. - OutputExt string - - // scriptBuilder is responsible for all opcode writes. - sb *ScriptBuilder - - // map with all function contexts across the program. - funcs map[string]*FuncContext - - // list of function calls - funcCalls []CallContext - - // struct with info about decls, types, uses, .. - typeInfo *types.Info -} - -// New returns a new compiler ready to compile smartcontracts. -func New() *Compiler { - return &Compiler{ - OutputExt: outputExt, - sb: &ScriptBuilder{buf: new(bytes.Buffer)}, - funcs: map[string]*FuncContext{}, - funcCalls: []CallContext{}, - } -} - -// CompileSource will compile the source file into an avm format. -func (c *Compiler) CompileSource(src string) error { - file, err := os.Open(src) - if err != nil { - return err - } - return c.Compile(file) -} - -// LoadConst load a constant, if storeLocal is true it will store it on the position -// of the VarContext. -func (c *Compiler) loadConst(ctx *VarContext, storeLocal bool) { - switch t := ctx.tinfo.Type.(type) { - case *types.Basic: - switch t.Kind() { - case types.Int: - val, _ := constant.Int64Val(ctx.tinfo.Value) - c.sb.emitPushInt(val) - case types.String: - val := constant.StringVal(ctx.tinfo.Value) - c.sb.emitPushString(val) - case types.Bool, types.UntypedBool: - val := constant.BoolVal(ctx.tinfo.Value) - c.sb.emitPushBool(val) - } - default: - log.Fatalf("compiler don't know how to handle this => %v", ctx) - } - - if storeLocal { - c.storeLocal(ctx) - } -} - -// Load a local variable. The position of the VarContext is used to retrieve from -// that position. -func (c *Compiler) loadLocal(ctx *VarContext) { - pos := int64(ctx.pos) - if pos < 0 { - log.Fatalf("want to load local %v but got invalid position => %d <=", ctx, pos) - } - - c.sb.emitPush(vm.OpFromAltStack) - c.sb.emitPush(vm.OpDup) - c.sb.emitPush(vm.OpToAltStack) - - // push it's index on the stack - c.sb.emitPushInt(pos) - c.sb.emitPush(vm.OpPickItem) -} - -// Store a local variable on the stack. The position of the VarContext is used -// to store at that position. -func (c *Compiler) storeLocal(vctx *VarContext) { - c.sb.emitPush(vm.OpFromAltStack) - c.sb.emitPush(vm.OpDup) - c.sb.emitPush(vm.OpToAltStack) - - pos := int64(vctx.pos) - if pos < 0 { - log.Fatalf("want to store local %v but got invalid positionl => %d", vctx, pos) - } - - c.sb.emitPushInt(pos) - c.sb.emitPushInt(2) - c.sb.emitPush(vm.OpRoll) - c.sb.emitPush(vm.OpSetItem) -} - -// Compile will compile from r into an avm format. -func (c *Compiler) Compile(r io.Reader) error { +// Compile compiles a Go program into bytecode that can run on the NEO virtual machine. +func Compile(input io.Reader, o *Options) ([]byte, error) { fset := token.NewFileSet() - f, err := parser.ParseFile(fset, "", r, 0) + f, err := parser.ParseFile(fset, "", input, 0) if err != nil { - return err + return nil, err } conf := types.Config{Importer: importer.Default()} @@ -161,343 +52,67 @@ func (c *Compiler) Compile(r io.Reader) error { Scopes: make(map[ast.Node]*types.Scope), } - c.typeInfo = typeInfo - // Typechecker _, err = conf.Check("", fset, []*ast.File{f}, typeInfo) if err != nil { - log.Fatal(err) + return nil, err } - var main *ast.FuncDecl - ast.Inspect(f, func(n ast.Node) bool { - switch t := n.(type) { - case *ast.FuncDecl: - if t.Name.Name == mainIdent { - main = t - return false - } + buf, err := CodeGen(f, typeInfo) + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +// CompileAndSave will compile and save the file to disk. +func CompileAndSave(src string, o *Options) error { + if len(o.Outfile) == 0 { + if !strings.HasSuffix(src, ".go") { + return errors.New("not a Go file") } - return true - }) - if main == nil { - log.Fatal("could not find func main. did you forgot to declare it?") + o.Outfile = strings.TrimSuffix(src, ".go") + } + if len(o.Ext) == 0 { + o.Ext = fileExt + } + b, err := ioutil.ReadFile(src) + if err != nil { + return err + } + b, err = Compile(bytes.NewReader(b), o) + if err != nil { + return err + } + if o.Debug { + log.Println(hex.EncodeToString(b)) } - c.resolveFuncDecls(f) - c.convertFuncDecl(main) + out := fmt.Sprintf("%s.%s", o.Outfile, o.Ext) + return ioutil.WriteFile(out, b, os.ModePerm) +} - // Start building all declarations - for _, decl := range f.Decls { - switch t := decl.(type) { - case *ast.GenDecl: - case *ast.FuncDecl: - if t.Name.Name != mainIdent { - c.convertFuncDecl(t) - } - } +// DumpOpcode compiles the program and dumps the opcode in a user friendly format. +func DumpOpcode(src string) error { + b, err := ioutil.ReadFile(src) + if err != nil { + return err + } + b, err = Compile(bytes.NewReader(b), &Options{}) + if err != nil { + return err } - // update all local function calls. - c.updateFuncCalls() - + w := tabwriter.NewWriter(os.Stdout, 0, 0, 4, ' ', 0) + fmt.Fprintln(w, "INDEX\tOPCODE\tDESC\t") + for i := 0; i < len(b); i++ { + fmt.Fprintf(w, "%d\t0x%2x\t%s\t\n", i, b[i], vm.Opcode(b[i])) + } + w.Flush() return nil } -// updateFuncCalls will update all local function calls that occured the program. -func (c *Compiler) updateFuncCalls() { - for _, ctx := range c.funcCalls { - fun, ok := c.funcs[ctx.funcName] - if !ok { - log.Fatalf("could not resolve function %s", ctx.funcName) - } - // pos is the position of the call op, we need to add 1 to get the - // start of the label. - // for calculating the correct offset we need to subtract the target label - // with the position the call occured. - offset := fun.label - int16(ctx.pos) - c.sb.updatePushCall(ctx.pos+1, offset) - } -} - -func (c *Compiler) resolveFuncDecls(f *ast.File) { - for _, decl := range f.Decls { - switch t := decl.(type) { - case *ast.GenDecl: - case *ast.FuncDecl: - if t.Name.Name != mainIdent { - c.funcs[t.Name.Name] = newFuncContext(t, 0) - } - } - } -} - -func (c *Compiler) convertFuncDecl(decl *ast.FuncDecl) { - ctx := newFuncContext(decl, c.currentPos()) - c.funcs[ctx.name] = ctx - - // We need to write the the total stack size of the function first. - // That size is the number of arguments + body operations that will be - // pushed on the stack - c.sb.emitPushInt(ctx.numStackOps()) - c.sb.emitPush(vm.OpNewArray) - c.sb.emitPush(vm.OpToAltStack) - - // Load the arguments into scope. - for _, arg := range decl.Type.Params.List { - name := arg.Names[0].Name - ctx.args[name] = true - vctx := ctx.newConst(name, c.getTypeInfo(arg.Type), true) - c.storeLocal(vctx) - } - - for _, stmt := range decl.Body.List { - c.convertStmt(ctx, stmt) - } -} - -func (c *Compiler) convertStmt(fctx *FuncContext, stmt ast.Stmt) { - switch t := stmt.(type) { - case *ast.AssignStmt: - for i := 0; i < len(t.Lhs); i++ { - lhs := t.Lhs[i].(*ast.Ident) - - switch rhs := t.Rhs[i].(type) { - case *ast.BasicLit: - vctx := fctx.newConst(lhs.Name, c.getTypeInfo(t.Rhs[i]), true) - c.loadConst(vctx, true) - - case *ast.CompositeLit: - // Write constants in reverse order on the stack. - n := len(rhs.Elts) - for i := n - 1; i >= 0; i-- { - vctx := fctx.newConst("", c.getTypeInfo(rhs.Elts[i]), false) - c.loadConst(vctx, false) - } - - c.sb.emitPushInt(int64(n)) - c.sb.emitPush(vm.OpPack) - - vctx := fctx.newConst(lhs.Name, c.getTypeInfo(rhs), true) - c.storeLocal(vctx) - - case *ast.Ident: - if isIdentBool(rhs) { - vctx := fctx.newConst(lhs.Name, makeBoolFromIdent(rhs, c.typeInfo), true) - c.loadConst(vctx, true) - continue - } - - knownCtx := fctx.getContext(rhs.Name) - c.loadLocal(knownCtx) - newCtx := fctx.newConst(lhs.Name, c.getTypeInfo(rhs), true) - c.storeLocal(newCtx) - - default: - c.convertExpr(fctx, t.Rhs[i]) - vctx := fctx.newConst(lhs.Name, c.getTypeInfo(t.Rhs[i]), true) - c.storeLocal(vctx) - } - } - - //Due to the design of the orginal VM, multiple return are not supported. - case *ast.ReturnStmt: - if len(t.Results) > 1 { - log.Fatal("multiple returns not supported.") - } - - c.sb.emitPush(vm.OpJMP) - c.sb.emitPush(vm.OpCode(0x03)) - c.sb.emitPush(vm.OpPush0) - - c.convertExpr(fctx, t.Results[0]) - - c.sb.emitPush(vm.OpNOP) - c.sb.emitPush(vm.OpFromAltStack) - c.sb.emitPush(vm.OpDrop) - c.sb.emitPush(vm.OpRET) - - // TODO: this needs a rewrite ASAP. - case *ast.IfStmt: - c.convertExpr(fctx, t.Cond) - - binExpr, ok := t.Cond.(*ast.BinaryExpr) - if ok && binExpr.Op != token.LAND && binExpr.Op != token.LOR { - // use a placeholder for the label. - c.sb.emitJump(vm.OpJMPIFNOT, int16(0)) - // track our offset to update later subtract sizeOf int16. - offset := int(c.currentPos()) - 2 - - defer func(offset int) { - jumpTo := c.currentPos() + 1 - int16(offset) - c.sb.updateJmpLabel(jumpTo, offset) - }(offset) - } - - labelBeforeBlock := c.currentPos() - // Process the block. - for _, stmt := range t.Body.List { - c.convertStmt(fctx, stmt) - } - - // if there are any labels we need to update. - if len(fctx.jumpLabels) > 0 { - for _, label := range fctx.jumpLabels { - var pos int16 - if label.op == vm.OpJMPIF { - pos = labelBeforeBlock + 1 - } else { - pos = c.currentPos() + 1 - } - jumpTo := pos - int16(label.offset) - c.sb.updateJmpLabel(jumpTo, label.offset) - } - fctx.jumpLabels = []jumpLabel{} - } - - default: - log.Fatalf("compiler has not implemented this statement => %v", reflect.TypeOf(t)) - } -} - -func (c *Compiler) convertExpr(fctx *FuncContext, expr ast.Expr) { - switch t := expr.(type) { - case *ast.BasicLit: - vctx := fctx.newConst("", c.getTypeInfo(t), false) - c.loadConst(vctx, false) - - case *ast.Ident: - if isIdentBool(t) { - vctx := fctx.newConst(t.Name, makeBoolFromIdent(t, c.typeInfo), false) - c.loadConst(vctx, false) - return - } - if fctx.isArgument(t.Name) { - vctx := fctx.getContext(t.Name) - c.loadLocal(vctx) - return - } - vctx := fctx.getContext(t.Name) - c.loadLocal(vctx) - - case *ast.CallExpr: - fun := t.Fun.(*ast.Ident) - fctx, ok := c.funcs[fun.Name] - if !ok { - log.Fatalf("could not resolve func %s", fun.Name) - } - - // handle the passed arguments. - for _, arg := range t.Args { - vctx := fctx.newConst("", c.getTypeInfo(arg), false) - c.loadLocal(vctx) - } - - // c# compiler adds a NOP (0x61) before every function call. Dont think its relevant - // and we could easily removed it, but to be consistent with the original compiler I - // will put them in. ^^ - c.sb.emitPush(vm.OpNOP) - - c.funcCalls = append(c.funcCalls, CallContext{int(c.currentPos()), fun.Name}) - c.sb.emitPushCall(0) // placeholder, update later. - - case *ast.BinaryExpr: - if t.Op == token.LAND || t.Op == token.LOR { - c.convertExpr(fctx, t.X) - - opJMP := vm.OpJMPIFNOT - if t.Op == token.LOR { - opJMP = vm.OpJMPIF - } - - if e, ok := t.X.(*ast.BinaryExpr); ok && e.Op != token.LAND && e.Op != token.LOR { - c.sb.emitJump(opJMP, int16(0)) - fctx.addJump(opJMP, int(c.currentPos())-2) - } - - c.convertExpr(fctx, t.Y) - c.sb.emitJump(vm.OpJMPIFNOT, int16(0)) - fctx.addJump(vm.OpJMPIFNOT, int(c.currentPos())-2) - c.convertToken(t.Op) - return - } - - // The AST package resolves all basic literals for us. If the typeinfo.Value is not nil - // we know that the bin expr is resolved and needs no further action. - // e.g. x := 2 + 2 + 2 will be resolved to 6. - if tinfo := c.getTypeInfo(t); tinfo.Value != nil { - vctx := fctx.newConst("", tinfo, false) - c.loadConst(vctx, false) - return - } - - c.convertExpr(fctx, t.X) - c.convertExpr(fctx, t.Y) - c.convertToken(t.Op) - - default: - log.Fatalf("compiler has not implemented this expr => %v", reflect.TypeOf(t)) - } -} - -func (c *Compiler) convertToken(tok token.Token) { - switch tok { - case token.ADD: - c.sb.emitPush(vm.OpAdd) - case token.SUB: - c.sb.emitPush(vm.OpSub) - case token.MUL: - c.sb.emitPush(vm.OpMul) - case token.QUO: - c.sb.emitPush(vm.OpDiv) - case token.LSS: - c.sb.emitPush(vm.OpLT) - case token.LEQ: - c.sb.emitPush(vm.OpLTE) - case token.GTR: - c.sb.emitPush(vm.OpGT) - case token.GEQ: - c.sb.emitPush(vm.OpGTE) - } -} - -// getTypeInfo return TypeAndValue for the given expression. If it could not resolve -// the type value and type will be NIL. -func (c *Compiler) getTypeInfo(expr ast.Expr) types.TypeAndValue { - return c.typeInfo.Types[expr] -} - -// currentPos return the current position (address) of the latest opcode. -func (c *Compiler) currentPos() int16 { - return int16(c.sb.buf.Len()) -} - -// Buffer returns the buffer of the builder as a io.Reader. -func (c *Compiler) Buffer() *bytes.Buffer { - return c.sb.buf -} - -// DumpOpcode dumps the current buffer, formatted with index, hex and opcode. -func (c *Compiler) DumpOpcode() { - c.sb.dumpOpcode() -} - -func makeBoolFromIdent(ident *ast.Ident, tinfo *types.Info) types.TypeAndValue { - var b bool - if ident.Name == "true" { - b = true - } else if ident.Name == "false" { - b = false - } else { - log.Fatalf("givent identifier cannot be converted to a boolean => %s", ident.Name) - } - - return types.TypeAndValue{ - Type: tinfo.ObjectOf(ident).Type(), - Value: constant.MakeBool(b), - } -} - -func isIdentBool(ident *ast.Ident) bool { - return ident.Name == "true" || ident.Name == "false" +func init() { + log.SetFlags(0) } diff --git a/pkg/vm/compiler/emit.go b/pkg/vm/compiler/emit.go new file mode 100644 index 000000000..c0cfb738e --- /dev/null +++ b/pkg/vm/compiler/emit.go @@ -0,0 +1,101 @@ +package compiler + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "math/big" + + "github.com/CityOfZion/neo-go/pkg/util" + "github.com/CityOfZion/neo-go/pkg/vm" +) + +func emit(w *bytes.Buffer, op vm.Opcode, b []byte) error { + if err := w.WriteByte(byte(op)); err != nil { + return err + } + _, err := w.Write(b) + return err +} + +func emitOpcode(w *bytes.Buffer, op vm.Opcode) error { + return w.WriteByte(byte(op)) +} + +func emitBool(w *bytes.Buffer, ok bool) error { + if ok { + return emitOpcode(w, vm.Opusht) + } + return emitOpcode(w, vm.Opushf) +} + +func emitInt(w *bytes.Buffer, i int64) error { + if i == -1 { + return emitOpcode(w, vm.Opushm1) + } + if i == 0 { + return emitOpcode(w, vm.Opushf) + } + if i > 0 && i < 16 { + val := vm.Opcode((int(vm.Opush1) - 1 + int(i))) + return emitOpcode(w, val) + } + + bInt := big.NewInt(i) + val := util.ToArrayReverse(bInt.Bytes()) + return emitBytes(w, val) +} + +func emitString(w *bytes.Buffer, s string) error { + return emitBytes(w, []byte(s)) +} + +func emitBytes(w *bytes.Buffer, b []byte) error { + var ( + err error + n = len(b) + ) + + if n == 0 { + return errors.New("cannot emit 0 bytes") + } + if n <= int(vm.Opushbytes75) { + return emit(w, vm.Opcode(n), b) + } else if n < 0x100 { + err = emit(w, vm.Opushdata1, []byte{byte(n)}) + } else if n < 0x10000 { + buf := make([]byte, 2) + binary.LittleEndian.PutUint16(buf, uint16(n)) + err = emit(w, vm.Opushdata2, buf) + } else { + buf := make([]byte, 4) + binary.LittleEndian.PutUint32(buf, uint32(n)) + err = emit(w, vm.Opushdata4, buf) + } + if err != nil { + return err + } + _, err = w.Write(b) + return err +} + +func emitCall(w *bytes.Buffer, op vm.Opcode, label int16) error { + return emitJmp(w, op, label) +} + +func emitJmp(w *bytes.Buffer, op vm.Opcode, label int16) error { + if !isOpcodeJmp(op) { + return fmt.Errorf("opcode %s is not a jump or call type", op) + } + buf := make([]byte, 2) + binary.LittleEndian.PutUint16(buf, uint16(label)) + return emit(w, op, buf) +} + +func isOpcodeJmp(op vm.Opcode) bool { + if op == vm.Ojmp || op == vm.Ojmpifnot || op == vm.Ojmpif || op == vm.Ocall { + return true + } + return false +} diff --git a/pkg/vm/compiler/func_context.go b/pkg/vm/compiler/func_context.go deleted file mode 100644 index e00e6dcc4..000000000 --- a/pkg/vm/compiler/func_context.go +++ /dev/null @@ -1,96 +0,0 @@ -package compiler - -import ( - "go/ast" - "go/types" - "log" - - "github.com/CityOfZion/neo-go/pkg/vm" -) - -type jumpLabel struct { - offset int - op vm.OpCode -} - -// A FuncContext represents details about a function in the program along withs its variables. -type FuncContext struct { - // The declaration tree of this function. - decl *ast.FuncDecl - // Identifier (name of the function in the program). - name string - // The scope of the function. - scope map[string]*VarContext - // Arguments of the function. - args map[string]bool - // Address (label) where the compiler can find this function when someone calls it. - label int16 - // Counter for stored local variables. - i int - // This needs refactor along with the (if stmt) - jumpLabels []jumpLabel -} - -func (f *FuncContext) addJump(op vm.OpCode, offset int) { - f.jumpLabels = append(f.jumpLabels, jumpLabel{offset, op}) -} - -func newFuncContext(decl *ast.FuncDecl, label int16) *FuncContext { - return &FuncContext{ - decl: decl, - label: int16(label), - name: decl.Name.Name, - scope: map[string]*VarContext{}, - args: map[string]bool{}, - jumpLabels: []jumpLabel{}, - } -} - -func (f *FuncContext) newConst(name string, t types.TypeAndValue, needStore bool) *VarContext { - ctx := &VarContext{ - name: name, - tinfo: t, - } - if needStore { - f.storeContext(ctx) - } - return ctx -} - -func (f *FuncContext) numStackOps() int64 { - ops := 0 - ast.Inspect(f.decl, func(n ast.Node) bool { - switch n.(type) { - case *ast.AssignStmt, *ast.ReturnStmt, *ast.IfStmt: - ops++ - } - return true - }) - - numArgs := len(f.decl.Type.Params.List) - return int64(ops + numArgs) -} - -func (f *FuncContext) storeContext(ctx *VarContext) { - ctx.pos = f.i - f.scope[ctx.name] = ctx - f.i++ -} - -func (f *FuncContext) getContext(name string) *VarContext { - ctx, ok := f.scope[name] - if !ok { - log.Fatalf("could not resolve variable %s", name) - } - return ctx -} - -func (f *FuncContext) isRegistered(ctx *VarContext) bool { - _, ok := f.scope[ctx.name] - return ok -} - -func (f *FuncContext) isArgument(name string) bool { - _, ok := f.args[name] - return ok -} diff --git a/pkg/vm/compiler/func_scope.go b/pkg/vm/compiler/func_scope.go new file mode 100644 index 000000000..c545e3273 --- /dev/null +++ b/pkg/vm/compiler/func_scope.go @@ -0,0 +1,89 @@ +package compiler + +import ( + "go/ast" + "log" +) + +// A funcScope represents a scope within the function context. +// It holds al the local variables along with the initialized struct positions. +type funcScope struct { + // function identifier + name string + + // The declaration of the function in the AST + decl *ast.FuncDecl + + // program label of the function + label int + + // local scope of the function + scope map[string]int + + // mapping of structs positions with their scope + structs map[int]*structScope + + // local variable counter + i int +} + +func newFuncScope(decl *ast.FuncDecl, label int) *funcScope { + return &funcScope{ + name: decl.Name.Name, + decl: decl, + label: label, + scope: map[string]int{}, + structs: map[int]*structScope{}, + i: -1, + } +} + +func (c *funcScope) stackSize() int64 { + size := 0 + ast.Inspect(c.decl, func(n ast.Node) bool { + switch n.(type) { + case *ast.AssignStmt, *ast.ReturnStmt, *ast.IfStmt: + size++ + } + return true + }) + + numArgs := len(c.decl.Type.Params.List) + // Also take care of struct methods recv: e.g. (t Token).Foo(). + if c.decl.Recv != nil { + numArgs += len(c.decl.Recv.List) + } + return int64(size + numArgs) +} + +func (c *funcScope) newStruct() *structScope { + strct := newStructScope() + c.structs[len(c.scope)] = strct + return strct +} + +func (c *funcScope) loadStruct(name string) *structScope { + l := c.loadLocal(name) + strct, ok := c.structs[l] + if !ok { + log.Fatalf("could not resolve struct %s", name) + } + return strct +} + +// newLocal creates a new local variable into the scope of the function. +func (c *funcScope) newLocal(name string) int { + c.i++ + c.scope[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.scope[name] + if !ok { + // should emit a compiler warning. + return c.newLocal(name) + } + return i +} diff --git a/pkg/vm/compiler/script_builder.go b/pkg/vm/compiler/script_builder.go deleted file mode 100644 index 317e2582a..000000000 --- a/pkg/vm/compiler/script_builder.go +++ /dev/null @@ -1,152 +0,0 @@ -package compiler - -import ( - "bytes" - "encoding/binary" - "errors" - "fmt" - "math/big" - "os" - "text/tabwriter" - - "github.com/CityOfZion/neo-go/pkg/util" - "github.com/CityOfZion/neo-go/pkg/vm" -) - -// ScriptBuilder generates bytecode and will write all -// generated bytecode into its internal buffer. -type ScriptBuilder struct { - buf *bytes.Buffer -} - -func (sb *ScriptBuilder) emit(op vm.OpCode, b []byte) error { - if err := sb.buf.WriteByte(byte(op)); err != nil { - return err - } - _, err := sb.buf.Write(b) - return err -} - -func (sb *ScriptBuilder) emitPush(op vm.OpCode) error { - return sb.buf.WriteByte(byte(op)) -} - -func (sb *ScriptBuilder) emitPushBool(b bool) error { - if b { - return sb.emitPush(vm.OpPushT) - } - return sb.emitPush(vm.OpPushF) -} - -func (sb *ScriptBuilder) emitPushInt(i int64) error { - if i == -1 { - return sb.emitPush(vm.OpPushM1) - } - if i == 0 { - return sb.emitPush(vm.OpPushF) - } - if i > 0 && i < 16 { - val := vm.OpCode((int(vm.OpPush1) - 1 + int(i))) - return sb.emitPush(val) - } - - bInt := big.NewInt(i) - val := util.ToArrayReverse(bInt.Bytes()) - return sb.emitPushArray(val) -} - -func (sb *ScriptBuilder) emitPushArray(b []byte) error { - var ( - err error - n = len(b) - ) - - if n == 0 { - return errors.New("0 bytes given in pushArray") - } - if n <= int(vm.OpPushBytes75) { - return sb.emit(vm.OpCode(n), b) - } else if n < 0x100 { - err = sb.emit(vm.OpPushData1, []byte{byte(n)}) - } else if n < 0x10000 { - buf := make([]byte, 2) - binary.LittleEndian.PutUint16(buf, uint16(n)) - err = sb.emit(vm.OpPushData2, buf) - } else { - buf := make([]byte, 4) - binary.LittleEndian.PutUint32(buf, uint32(n)) - err = sb.emit(vm.OpPushData4, buf) - } - if err != nil { - return err - } - _, err = sb.buf.Write(b) - return err -} - -func (sb *ScriptBuilder) emitPushString(str string) error { - return sb.emitPushArray([]byte(str)) -} - -func (sb *ScriptBuilder) emitSysCall(api string) error { - lenAPI := len(api) - if lenAPI == 0 { - return errors.New("syscall argument cant be 0") - } - if lenAPI > 252 { - return fmt.Errorf("invalid syscall argument: %s", api) - } - - bapi := []byte(api) - args := make([]byte, lenAPI+1) - args[0] = byte(lenAPI) - copy(args, bapi[1:]) - return sb.emit(vm.OpSysCall, args) -} - -func (sb *ScriptBuilder) emitPushCall(offset int16) error { - buf := new(bytes.Buffer) - binary.Write(buf, binary.LittleEndian, offset) - return sb.emit(vm.OpCall, buf.Bytes()) -} - -func (sb *ScriptBuilder) emitJump(op vm.OpCode, offset int16) error { - if op != vm.OpJMP && op != vm.OpJMPIF && op != vm.OpJMPIFNOT && op != vm.OpCall { - return fmt.Errorf("invalid jump opcode: %v", op) - } - buf := make([]byte, 2) - binary.LittleEndian.PutUint16(buf, uint16(offset)) - return sb.emit(op, buf) -} - -func (sb *ScriptBuilder) updateJmpLabel(label int16, offset int) error { - sizeOfInt16 := 2 - if sizeOfInt16+offset >= sb.buf.Len() { - return fmt.Errorf("cannot update label at offset %d", offset) - } - - b := make([]byte, sizeOfInt16) - binary.LittleEndian.PutUint16(b, uint16(label)) - buf := sb.buf.Bytes() - copy(buf[offset:offset+sizeOfInt16], b) - return nil -} - -func (sb *ScriptBuilder) updatePushCall(offset int, label int16) { - b := new(bytes.Buffer) - binary.Write(b, binary.LittleEndian, label) - - buf := sb.buf.Bytes() - copy(buf[offset:offset+2], b.Bytes()) -} - -func (sb *ScriptBuilder) dumpOpcode() { - w := tabwriter.NewWriter(os.Stdout, 0, 0, 4, ' ', 0) - buf := sb.buf.Bytes() - - fmt.Fprintln(w, "INDEX\tOPCODE\tDESC") - for i := 0; i < len(buf); i++ { - fmt.Fprintf(w, "%d\t0x%2x\t%s\n", i, buf[i], vm.OpCode(buf[i])) - } - w.Flush() -} diff --git a/pkg/vm/compiler/script_builder_test.go b/pkg/vm/compiler/script_builder_test.go deleted file mode 100644 index 9e22cbd04..000000000 --- a/pkg/vm/compiler/script_builder_test.go +++ /dev/null @@ -1,104 +0,0 @@ -package compiler - -import ( - "bytes" - "math/big" - "testing" - - "github.com/CityOfZion/neo-go/pkg/util" - "github.com/CityOfZion/neo-go/pkg/vm" -) - -func TestEmitPush(t *testing.T) { - sb := &ScriptBuilder{buf: new(bytes.Buffer)} - - if err := sb.emitPush(vm.OpPush1); err != nil { - t.Fatal(err) - } - if sb.buf.Len() != 1 { - t.Fatalf("expect buffer len of 1 got %d", sb.buf.Len()) - } -} -func TestEmitPushIntNeg(t *testing.T) { - sb := &ScriptBuilder{buf: new(bytes.Buffer)} - val := -1 - if err := sb.emitPushInt(int64(val)); err != nil { - t.Fatal(err) - } - if want, have := vm.OpPushM1, vm.OpCode(sb.buf.Bytes()[0]); want != have { - t.Fatalf("expected %v got %v", want, have) - } -} - -func TestEmitPushInt0(t *testing.T) { - sb := &ScriptBuilder{buf: new(bytes.Buffer)} - val := 0 - if err := sb.emitPushInt(int64(val)); err != nil { - t.Fatal(err) - } - if want, have := vm.OpPushF, vm.OpCode(sb.buf.Bytes()[0]); want != have { - t.Fatalf("expected %v got %v", want, have) - } -} - -func TestEmitPushInt1(t *testing.T) { - sb := &ScriptBuilder{buf: new(bytes.Buffer)} - val := 1 - if err := sb.emitPushInt(int64(val)); err != nil { - t.Fatal(err) - } - if want, have := vm.OpPush1, vm.OpCode(sb.buf.Bytes()[0]); want != have { - t.Fatalf("expected %v got %v", want, have) - } -} - -func TestEmitPushInt100(t *testing.T) { - x := 100 - bigx := big.NewInt(int64(x)) - t.Log(bigx.Bytes()) - - sb := &ScriptBuilder{buf: new(bytes.Buffer)} - val := 100 - if err := sb.emitPushInt(int64(val)); err != nil { - t.Fatal(err) - } - // len = 1 - if want, have := byte(0x01), byte(sb.buf.Bytes()[0]); want != have { - t.Fatalf("expected %v got %v", want, have) - } - if want, have := byte(0x64), byte(sb.buf.Bytes()[1]); want != have { - t.Fatalf("expected %v got %v", want, have) - } -} - -func TestEmitPush1000(t *testing.T) { - sb := &ScriptBuilder{buf: new(bytes.Buffer)} - val := 1000 - if err := sb.emitPushInt(int64(val)); err != nil { - t.Fatal(err) - } - bInt := big.NewInt(int64(val)) - if want, have := byte(len(bInt.Bytes())), byte(sb.buf.Bytes()[0]); want != have { - t.Fatalf("expected %v got %v", want, have) - } - want := util.ToArrayReverse(bInt.Bytes()) // reverse - have := sb.buf.Bytes()[1:] - if bytes.Compare(want, have) != 0 { - t.Fatalf("expected %v got %v", want, have) - } -} - -func TestEmitPushString(t *testing.T) { - sb := &ScriptBuilder{buf: new(bytes.Buffer)} - str := "anthdm" - if err := sb.emitPushString(str); err != nil { - t.Fatal(err) - } - if want, have := byte(len(str)), sb.buf.Bytes()[0]; want != have { - t.Fatalf("expected %v got %v", want, have) - } - want, have := []byte(str), sb.buf.Bytes()[1:] - if bytes.Compare(want, have) != 0 { - t.Fatalf("expected %v got %v", want, have) - } -} diff --git a/pkg/vm/compiler/struct_scope.go b/pkg/vm/compiler/struct_scope.go new file mode 100644 index 000000000..8bf71fea7 --- /dev/null +++ b/pkg/vm/compiler/struct_scope.go @@ -0,0 +1,51 @@ +package compiler + +import ( + "go/ast" + "go/types" + "log" +) + +// A structScope holds the positions for it's fields. Struct fields have different +// positions then local variables in any scope. +type structScope struct { + // identifier of the initialized struct in the program. + name string + + // a mapping of field identifier and its position. + fields map[string]int +} + +func newStructScope() *structScope { + return &structScope{ + fields: map[string]int{}, + } +} + +func (s *structScope) newField(name string) int { + i := len(s.fields) + s.fields[name] = i + return i +} + +func (s *structScope) loadField(name string) int { + i, ok := s.fields[name] + if !ok { + log.Fatalf("could not resolve field name %s for struct %s", name, s.name) + } + return i +} + +func (s *structScope) initializeFields(ident *ast.Ident, tInfo *types.Info) { + def, ok := tInfo.Defs[ident] + if !ok { + log.Fatalf("could not initialize fields of %s: definitions not found in typeinfo", ident.Name) + } + t, ok := def.Type().Underlying().(*types.Struct) + if !ok { + log.Fatalf("%s is not of type struct", ident.Name) + } + for i := 0; i < t.NumFields(); i++ { + s.newField(t.Field(i).Name()) + } +} diff --git a/pkg/vm/compiler/tests/array_test.go b/pkg/vm/compiler/tests/array_test.go new file mode 100644 index 000000000..2b6c19883 --- /dev/null +++ b/pkg/vm/compiler/tests/array_test.go @@ -0,0 +1,26 @@ +package compiler_test + +var arrayTestCases = []testCase{ + { + "assign int array", + ` + package foo + func Main() []int { + x := []int{1, 2, 3} + return x + } + `, + "52c56b53525153c16c766b00527ac46203006c766b00c3616c7566", + }, + { + "assign string array", + ` + package foo + func Main() []string { + x := []string{"foo", "bar", "foobar"} + return x + } + `, + "52c56b06666f6f6261720362617203666f6f53c16c766b00527ac46203006c766b00c3616c7566", + }, +} diff --git a/pkg/vm/compiler/tests/assign_test.go b/pkg/vm/compiler/tests/assign_test.go new file mode 100644 index 000000000..7fa71d387 --- /dev/null +++ b/pkg/vm/compiler/tests/assign_test.go @@ -0,0 +1,31 @@ +package compiler_test + +var assignTestCases = []testCase{ + { + "chain define", + ` + package foo + func Main() int { + x := 4 + y := x + z := y + foo := z + bar := foo + return bar + } + `, + "56c56b546c766b00527ac46c766b00c36c766b51527ac46c766b51c36c766b52527ac46c766b52c36c766b53527ac46c766b53c36c766b54527ac46203006c766b54c3616c7566", + }, + { + "simple assign", + ` + package foo + func Main() int { + x := 4 + x = 8 + return x + } + `, + "53c56b546c766b00527ac4586c766b00527ac46203006c766b00c3616c7566", + }, +} diff --git a/pkg/vm/compiler/tests/bool_test.go b/pkg/vm/compiler/tests/bool_test.go new file mode 100644 index 000000000..90d09f9d9 --- /dev/null +++ b/pkg/vm/compiler/tests/bool_test.go @@ -0,0 +1,15 @@ +package compiler_test + +var boolTestCases = []testCase{ + { + "bool assign", + ` + package foo + func Main() bool { + x := true + return x + } + `, + "52c56b516c766b00527ac46203006c766b00c3616c7566", + }, +} diff --git a/pkg/vm/compiler/tests/compiler_test.go b/pkg/vm/compiler/tests/compiler_test.go index 4f600b5ad..d3b55d800 100644 --- a/pkg/vm/compiler/tests/compiler_test.go +++ b/pkg/vm/compiler/tests/compiler_test.go @@ -21,14 +21,18 @@ type testCase struct { func TestAllCases(t *testing.T) { testCases := []testCase{} + testCases = append(testCases, assignTestCases...) + testCases = append(testCases, arrayTestCases...) + testCases = append(testCases, functionCallTestCases...) + testCases = append(testCases, boolTestCases...) testCases = append(testCases, stringTestCases...) testCases = append(testCases, binaryExprTestCases...) + testCases = append(testCases, structTestCases...) testCases = append(testCases, ifStatementTestCases...) - testCases = append(testCases, functionCallTestCases...) for _, tc := range testCases { - c := compiler.New() - if err := c.Compile(strings.NewReader(tc.src)); err != nil { + b, err := compiler.Compile(strings.NewReader(tc.src), &compiler.Options{}) + if err != nil { t.Fatal(err) } @@ -37,10 +41,10 @@ func TestAllCases(t *testing.T) { t.Fatal(err) } - if bytes.Compare(c.Buffer().Bytes(), expectedResult) != 0 { - t.Log(hex.EncodeToString(c.Buffer().Bytes())) + if bytes.Compare(b, expectedResult) != 0 { + t.Log(hex.EncodeToString(b)) want, _ := hex.DecodeString(tc.result) - dumpOpCodeSideBySide(c.Buffer().Bytes(), want) + dumpOpCodeSideBySide(b, want) t.Fatalf("compiling %s failed", tc.name) } } @@ -59,7 +63,7 @@ func dumpOpCodeSideBySide(have, want []byte) { diff = "<<" } fmt.Fprintf(w, "%d\t0x%2x\t%s\t0x%2x\t%s\t%s\n", - i, have[i], vm.OpCode(have[i]), want[i], vm.OpCode(want[i]), diff) + i, have[i], vm.Opcode(have[i]), want[i], vm.Opcode(want[i]), diff) } w.Flush() } diff --git a/pkg/vm/compiler/tests/function_call_test.go b/pkg/vm/compiler/tests/function_call_test.go index 4b41621be..947e86872 100644 --- a/pkg/vm/compiler/tests/function_call_test.go +++ b/pkg/vm/compiler/tests/function_call_test.go @@ -58,4 +58,19 @@ var functionCallTestCases = []testCase{ `, "53c56b5a6c766b00527ac46c766b00c3616516006c766b51527ac46203006c766b51c3616c756653c56b6c766b00527ac4586c766b51527ac46203006c766b00c36c766b51c393616c7566", }, + { + "function call with multiple arguments", + ` + package testcase + func Main() int { + x := addIntegers(2, 4) + return x + } + + func addIntegers(x int, y int) int { + return x + y + } + `, + "52c56b52547c616516006c766b00527ac46203006c766b00c3616c756653c56b6c766b00527ac46c766b51527ac46203006c766b00c36c766b51c393616c7566", + }, } diff --git a/pkg/vm/compiler/tests/struct_test.go b/pkg/vm/compiler/tests/struct_test.go new file mode 100644 index 000000000..758fcbe17 --- /dev/null +++ b/pkg/vm/compiler/tests/struct_test.go @@ -0,0 +1,157 @@ +package compiler_test + +var structTestCases = []testCase{ + { + "struct field assign", + ` + package foo + type token struct { + x int + y int + } + + func Main() int { + t := token { + x: 2, + y: 4, + } + + age := t.x + return age + } + `, + "53c56b6152c66b526c766b00527ac4546c766b51527ac46c6c766b00527ac46c766b00c300c36c766b51527ac46203006c766b51c3616c7566", + }, + { + "struct field return", + ` + package foo + type token struct { + x int + y int + } + + func Main() int { + t := token { + x: 2, + y: 4, + } + + return t.x + } + `, + "52c56b6152c66b526c766b00527ac4546c766b51527ac46c6c766b00527ac46203006c766b00c300c3616c7566", + }, + { + "struct field assign", + ` + package foo + type token struct { + x int + y int + } + + func Main() int { + t := token { + x: 2, + y: 4, + } + t.x = 10 + return t.x + } + `, + "53c56b6152c66b526c766b00527ac4546c766b51527ac46c6c766b00527ac45a6c766b00c3007bc46203006c766b00c300c3616c7566", + }, + { + "complex struct", + ` + package foo + type token struct { + x int + y int + } + + func Main() int { + x := 10 + + t := token { + x: 2, + y: 4, + } + + y := x + t.x + + return y + } + `, + "54c56b5a6c766b00527ac46152c66b526c766b00527ac4546c766b51527ac46c6c766b51527ac46c766b00c36c766b51c300c3936c766b52527ac46203006c766b52c3616c7566", + }, + { + "initialize same struct twice", + ` + package foo + type token struct { + x int + y int + } + + func Main() int { + t1 := token { + x: 2, + y: 4, + } + t2 := token { + x: 2, + y: 4, + } + return t1.x + t2.y + } + `, + "53c56b6152c66b526c766b00527ac4546c766b51527ac46c6c766b00527ac46152c66b526c766b00527ac4546c766b51527ac46c6c766b51527ac46203006c766b00c300c36c766b51c351c393616c7566", + }, + { + "struct methods", + ` + package foo + type token struct { + x int + } + + func(t token) getInteger() int { + return t.x + } + + func Main() int { + t := token { + x: 4, + } + someInt := t.getInteger() + return someInt + } + `, + "53c56b6151c66b546c766b00527ac46c6c766b00527ac46c766b00c3616516006c766b51527ac46203006c766b51c3616c756652c56b6c766b00527ac46203006c766b00c300c3616c7566", + }, + { + "struct methods with arguments", + ` + package foo + type token struct { + x int + } + + // Also tests if x conflicts with t.x + func(t token) addIntegers(x int, y int) int { + return t.x + x + y + } + + func Main() int { + t := token { + x: 4, + } + someInt := t.addIntegers(2, 4) + return someInt + } + `, + "53c56b6151c66b546c766b00527ac46c6c766b00527ac46c766b00c352545272616516006c766b51527ac46203006c766b51c3616c756654c56b6c766b00527ac46c766b51527ac46c766b52527ac46203006c766b00c300c36c766b51c3936c766b52c393616c7566", + }, +} diff --git a/pkg/vm/opcode.go b/pkg/vm/opcode.go index cf36bc059..724df2616 100644 --- a/pkg/vm/opcode.go +++ b/pkg/vm/opcode.go @@ -1,129 +1,129 @@ package vm -// OpCode is an single operational instruction for the GO NEO virtual machine. -type OpCode byte +// Opcode is an single operational instruction for the GO NEO virtual machine. +type Opcode byte // List of supported opcodes. const ( // Constants - OpPush0 OpCode = 0x00 // An empty array of bytes is pushed onto the stack. - OpPushF OpCode = OpPush0 - OpPushBytes1 OpCode = 0x01 // 0x01-0x4B The next opcode bytes is data to be pushed onto the stack - OpPushBytes75 OpCode = 0x4B - OpPushData1 OpCode = 0x4C // The next byte contains the number of bytes to be pushed onto the stack. - OpPushData2 OpCode = 0x4D // The next two bytes contain the number of bytes to be pushed onto the stack. - OpPushData4 OpCode = 0x4E // The next four bytes contain the number of bytes to be pushed onto the stack. - OpPushM1 OpCode = 0x4F // The number -1 is pushed onto the stack. - OpPush1 OpCode = 0x51 - OpPushT OpCode = OpPush1 - OpPush2 OpCode = 0x52 // The number 2 is pushed onto the stack. - OpPush3 OpCode = 0x53 // The number 3 is pushed onto the stack. - OpPush4 OpCode = 0x54 // The number 4 is pushed onto the stack. - OpPush5 OpCode = 0x55 // The number 5 is pushed onto the stack. - OpPush6 OpCode = 0x56 // The number 6 is pushed onto the stack. - OpPush7 OpCode = 0x57 // The number 7 is pushed onto the stack. - OpPush8 OpCode = 0x58 // The number 8 is pushed onto the stack. - OpPush9 OpCode = 0x59 // The number 9 is pushed onto the stack. - OpPush10 OpCode = 0x5A // The number 10 is pushed onto the stack. - OpPush11 OpCode = 0x5B // The number 11 is pushed onto the stack. - OpPush12 OpCode = 0x5C // The number 12 is pushed onto the stack. - OpPush13 OpCode = 0x5D // The number 13 is pushed onto the stack. - OpPush14 OpCode = 0x5E // The number 14 is pushed onto the stack. - OpPush15 OpCode = 0x5F // The number 15 is pushed onto the stack. - OpPush16 OpCode = 0x60 // The number 16 is pushed onto the stack. + Opush0 Opcode = 0x00 // An empty array of bytes is pushed onto the stack. + Opushf Opcode = Opush0 + Opushbytes1 Opcode = 0x01 // 0x01-0x4B The next opcode bytes is data to be pushed onto the stack + Opushbytes75 Opcode = 0x4B + Opushdata1 Opcode = 0x4C // The next byte contains the number of bytes to be pushed onto the stack. + Opushdata2 Opcode = 0x4D // The next two bytes contain the number of bytes to be pushed onto the stack. + Opushdata4 Opcode = 0x4E // The next four bytes contain the number of bytes to be pushed onto the stack. + Opushm1 Opcode = 0x4F // The number -1 is pushed onto the stack. + Opush1 Opcode = 0x51 + Opusht Opcode = Opush1 + Opush2 Opcode = 0x52 // The number 2 is pushed onto the stack. + Opush3 Opcode = 0x53 // The number 3 is pushed onto the stack. + Opush4 Opcode = 0x54 // The number 4 is pushed onto the stack. + Opush5 Opcode = 0x55 // The number 5 is pushed onto the stack. + Opush6 Opcode = 0x56 // The number 6 is pushed onto the stack. + Opush7 Opcode = 0x57 // The number 7 is pushed onto the stack. + Opush8 Opcode = 0x58 // The number 8 is pushed onto the stack. + Opush9 Opcode = 0x59 // The number 9 is pushed onto the stack. + Opush10 Opcode = 0x5A // The number 10 is pushed onto the stack. + Opush11 Opcode = 0x5B // The number 11 is pushed onto the stack. + Opush12 Opcode = 0x5C // The number 12 is pushed onto the stack. + Opush13 Opcode = 0x5D // The number 13 is pushed onto the stack. + Opush14 Opcode = 0x5E // The number 14 is pushed onto the stack. + Opush15 Opcode = 0x5F // The number 15 is pushed onto the stack. + Opush16 Opcode = 0x60 // The number 16 is pushed onto the stack. // Flow control - OpNOP OpCode = 0x61 // No operation. - OpJMP OpCode = 0x62 - OpJMPIF OpCode = 0x63 - OpJMPIFNOT OpCode = 0x64 - OpCall OpCode = 0x65 - OpRET OpCode = 0x66 - OpAppCall OpCode = 0x67 - OpSysCall OpCode = 0x68 - OpTailCall OpCode = 0x69 + Onop Opcode = 0x61 // No operation. + Ojmp Opcode = 0x62 + Ojmpif Opcode = 0x63 + Ojmpifnot Opcode = 0x64 + Ocall Opcode = 0x65 + Oret Opcode = 0x66 + Opcall Opcode = 0x67 + Osyscall Opcode = 0x68 + Otailcall Opcode = 0x69 // The stack - OpDupFromAltStack OpCode = 0x6A - OpToAltStack OpCode = 0x6B // Puts the input onto the top of the alt stack. Removes it from the main stack. - OpFromAltStack OpCode = 0x6C // Puts the input onto the top of the main stack. Removes it from the alt stack. - OpXDrop OpCode = 0x6D - OpXSwap OpCode = 0x72 - OpXTuck OpCode = 0x73 - OpDepth OpCode = 0x74 // Puts the number of stack items onto the stack. - OpDrop OpCode = 0x75 // Removes the top stack item. - OpDup OpCode = 0x76 // Duplicates the top stack item. - OpNip OpCode = 0x77 // Removes the second-to-top stack item. - OpOver OpCode = 0x78 // Copies the second-to-top stack item to the top. - OpPick OpCode = 0x79 // The item n back in the stack is copied to the top. - OpRoll OpCode = 0x7A // The item n back in the stack is moved to the top. - OpRot OpCode = 0x7B // The top three items on the stack are rotated to the left. - OpSwap OpCode = 0x7C // The top two items on the stack are swapped. - OpTuck OpCode = 0x7D // The item at the top of the stack is copied and inserted before the second-to-top item. + Odupfromaltstack Opcode = 0x6A + Otoaltstack Opcode = 0x6B // Puts the input onto the top of the alt stack. Removes it from the main stack. + Ofromaltstack Opcode = 0x6C // Puts the input onto the top of the main stack. Removes it from the alt stack. + Oxdrop Opcode = 0x6D + Oxswap Opcode = 0x72 + Oxtuck Opcode = 0x73 + Odepth Opcode = 0x74 // Puts the number of stack items onto the stack. + Odrop Opcode = 0x75 // Removes the top stack item. + Odup Opcode = 0x76 // Duplicates the top stack item. + Onip Opcode = 0x77 // Removes the second-to-top stack item. + Oover Opcode = 0x78 // Copies the second-to-top stack item to the top. + Opick Opcode = 0x79 // The item n back in the stack is copied to the top. + Oroll Opcode = 0x7A // The item n back in the stack is moved to the top. + Orot Opcode = 0x7B // The top three items on the stack are rotated to the left. + Oswap Opcode = 0x7C // The top two items on the stack are swapped. + Otuck Opcode = 0x7D // The item at the top of the stack is copied and inserted before the second-to-top item. // Splice - OpCat OpCode = 0x7E // Concatenates two strings. - OpSubStr OpCode = 0x7F // Returns a section of a string. - OpLeft OpCode = 0x80 // Keeps only characters left of the specified point in a string. - OpRight OpCode = 0x81 // Keeps only characters right of the specified point in a string. - OpSize OpCode = 0x82 // Returns the length of the input string. + Ocat Opcode = 0x7E // Concatenates two strings. + Osubstr Opcode = 0x7F // Returns a section of a string. + Oleft Opcode = 0x80 // Keeps only characters left of the specified point in a string. + Oright Opcode = 0x81 // Keeps only characters right of the specified point in a string. + Osize Opcode = 0x82 // Returns the length of the input string. // Bitwise logic - OpInvert OpCode = 0x83 // Flips all of the bits in the input. - OpAnd OpCode = 0x84 // Boolean and between each bit in the inputs. - OpOr OpCode = 0x85 // Boolean or between each bit in the inputs. - OpXor OpCode = 0x86 // Boolean exclusive or between each bit in the inputs. - OpEqual OpCode = 0x87 // Returns 1 if the inputs are exactly equal, 0 otherwise. + Oinvert Opcode = 0x83 // Flips all of the bits in the input. + Oand Opcode = 0x84 // Boolean and between each bit in the inputs. + Oor Opcode = 0x85 // Boolean or between each bit in the inputs. + Oxor Opcode = 0x86 // Boolean exclusive or between each bit in the inputs. + Oequal Opcode = 0x87 // Returns 1 if the inputs are exactly equal, 0 otherwise. // Arithmetic // Note: Arithmetic inputs are limited to signed 32-bit integers, but may overflow their output. - OpInc OpCode = 0x8B // 1 is added to the input. - OpDec OpCode = 0x8C // 1 is subtracted from the input. - OpSign OpCode = 0x8D - OpNegate OpCode = 0x8F // The sign of the input is flipped. - OpAbs OpCode = 0x90 // The input is made positive. - OpNot OpCode = 0x91 // If the input is 0 or 1, it is flipped. Otherwise the output will be 0. - OpNZ OpCode = 0x92 // Returns 0 if the input is 0. 1 otherwise. - OpAdd OpCode = 0x93 // a is added to b. - OpSub OpCode = 0x94 // b is subtracted from a. - OpMul OpCode = 0x95 // a is multiplied by b. - OpDiv OpCode = 0x96 // a is divided by b. - OpMod OpCode = 0x97 // Returns the remainder after dividing a by b. - OpShl OpCode = 0x98 // Shifts a left b bits, preserving sign. - OpShr OpCode = 0x99 // Shifts a right b bits, preserving sign. - OpBoolAnd OpCode = 0x9A // If both a and b are not 0, the output is 1. Otherwise 0. - OpBoolOr OpCode = 0x9B // If a or b is not 0, the output is 1. Otherwise 0. - OpNumEqual OpCode = 0x9C // Returns 1 if the numbers are equal, 0 otherwise. - OpNumNotEqual OpCode = 0x9E // Returns 1 if the numbers are not equal, 0 otherwise. - OpLT OpCode = 0x9F // Returns 1 if a is less than b, 0 otherwise. - OpGT OpCode = 0xA0 // Returns 1 if a is greater than b, 0 otherwise. - OpLTE OpCode = 0xA1 // Returns 1 if a is less than or equal to b, 0 otherwise. - OpGTE OpCode = 0xA2 // Returns 1 if a is greater than or equal to b, 0 otherwise. - OpMin OpCode = 0xA3 // Returns the smaller of a and b. - OpMax OpCode = 0xA4 // Returns the larger of a and b. - OpWithin OpCode = 0xA5 // Returns 1 if x is within the specified range (left-inclusive), 0 otherwise. + Oinc Opcode = 0x8B // 1 is added to the input. + Odec Opcode = 0x8C // 1 is subtracted from the input. + Osign Opcode = 0x8D + Onegate Opcode = 0x8F // The sign of the input is flipped. + Oabs Opcode = 0x90 // The input is made positive. + Onot Opcode = 0x91 // If the input is 0 or 1, it is flipped. Otherwise the output will be 0. + Onz Opcode = 0x92 // Returns 0 if the input is 0. 1 otherwise. + Oadd Opcode = 0x93 // a is added to b. + Osub Opcode = 0x94 // b is subtracted from a. + Omul Opcode = 0x95 // a is multiplied by b. + Odiv Opcode = 0x96 // a is divided by b. + Omod Opcode = 0x97 // Returns the remainder after dividing a by b. + Oshl Opcode = 0x98 // Shifts a left b bits, preserving sign. + Oshr Opcode = 0x99 // Shifts a right b bits, preserving sign. + Obooland Opcode = 0x9A // If both a and b are not 0, the output is 1. Otherwise 0. + Oboolor Opcode = 0x9B // If a or b is not 0, the output is 1. Otherwise 0. + Onumequal Opcode = 0x9C // Returns 1 if the numbers are equal, 0 otherwise. + Onumnotequal Opcode = 0x9E // Returns 1 if the numbers are not equal, 0 otherwise. + Olt Opcode = 0x9F // Returns 1 if a is less than b, 0 otherwise. + Ogt Opcode = 0xA0 // Returns 1 if a is greater than b, 0 otherwise. + Olte Opcode = 0xA1 // Returns 1 if a is less than or equal to b, 0 otherwise. + Ogte Opcode = 0xA2 // Returns 1 if a is greater than or equal to b, 0 otherwise. + Omin Opcode = 0xA3 // Returns the smaller of a and b. + Omax Opcode = 0xA4 // Returns the larger of a and b. + Owithin Opcode = 0xA5 // Returns 1 if x is within the specified range (left-inclusive), 0 otherwise. // Crypto - OpSHA1 OpCode = 0xA7 // The input is hashed using SHA-1. - OpSHA256 OpCode = 0xA8 // The input is hashed using SHA-256. - OpHASH160 OpCode = 0xA9 - OpHASH256 OpCode = 0xAA - OpCheckSig OpCode = 0xAC - OpCheckMultiSig OpCode = 0xAE + Osha1 Opcode = 0xA7 // The input is hashed using SHA-1. + Osha256 Opcode = 0xA8 // The input is hashed using SHA-256. + Ohash160 Opcode = 0xA9 + Ohash256 Opcode = 0xAA + Ochecksig Opcode = 0xAC + Ocheckmultisig Opcode = 0xAE - // Array - OpArraySize OpCode = 0xC0 - OpPack OpCode = 0xC1 - OpUnpack OpCode = 0xC2 - OpPickItem OpCode = 0xC3 - OpSetItem OpCode = 0xC4 - OpNewArray OpCode = 0xC5 // Pops size from stack and creates a new array with that size, and pushes the array into the stack - OpNewStruct OpCode = 0xC6 - OpAppend OpCode = 0xC8 - OpReverse OpCode = 0xC9 - OpRemove OpCode = 0xCA + // array + Oarraysize Opcode = 0xC0 + Opack Opcode = 0xC1 + Ounpack Opcode = 0xC2 + Opickitem Opcode = 0xC3 + Osetitem Opcode = 0xC4 + Onewarray Opcode = 0xC5 // Pops size from stack and creates a new array with that size, and pushes the array into the stack + Onewstruct Opcode = 0xC6 + Oappend Opcode = 0xC8 + Oreverse Opcode = 0xC9 + Oremove Opcode = 0xCA - // Exceptions - OpThrow OpCode = 0xF0 - OpThrowIfNot OpCode = 0xF1 + // exceptions + Othrow Opcode = 0xF0 + Othrowifnot Opcode = 0xF1 ) diff --git a/pkg/vm/opcode_string.go b/pkg/vm/opcode_string.go index 67e922e35..5d118ec0b 100644 --- a/pkg/vm/opcode_string.go +++ b/pkg/vm/opcode_string.go @@ -1,118 +1,118 @@ -// Code generated by "stringer -type=OpCode ./pkg/vm"; DO NOT EDIT. +// Code generated by "stringer -type=Opcode ./pkg/vm/compiler"; DO NOT EDIT. package vm import "strconv" -const _OpCode_name = "OpPush0OpPushBytes1OpPushBytes75OpPushData1OpPushData2OpPushData4OpPushM1OpPush1OpPush2OpPush3OpPush4OpPush5OpPush6OpPush7OpPush8OpPush9OpPush10OpPush11OpPush12OpPush13OpPush14OpPush15OpPush16OpNOPOpJMPOpJMPIFOpJMPIFNOTOpCallOpRETOpAppCallOpSysCallOpTailCallOpDupFromAltStackOpToAltStackOpFromAltStackOpXDropOpXSwapOpXTuckOpDepthOpDropOpDupOpNipOpOverOpPickOpRollOpRotOpSwapOpTuckOpCatOpSubStrOpLeftOpRightOpSizeOpInvertOpAndOpOrOpXorOpEqualOpIncOpDecOpSignOpNegateOpAbsOpNotOpNZOpAddOpSubOpMulOpDivOpModOpShlOpShrOpBoolAndOpBoolOrOpNumEqualOpNumNotEqualOpLTOpGTOpLTEOpGTEOpMinOpMaxOpWithinOpSHA1OpSHA256OpHASH160OpHASH256OpCheckSigOpCheckMultiSigOpArraySizeOpPackOpUnpackOpPickItemOpSetItemOpNewArrayOpNewStructOpAppendOpReverseOpRemoveOpThrowOpThrowIfNot" +const _Opcode_name = "Opush0Opushbytes1Opushbytes75Opushdata1Opushdata2Opushdata4Opushm1Opush1Opush2Opush3Opush4Opush5Opush6Opush7Opush8Opush9Opush10Opush11Opush12Opush13Opush14Opush15Opush16OnopOjmpOjmpifOjmpifnotOcallOretOpcallOsyscallOtailcallOdupfromaltstackOtoaltstackOfromaltstackOxdropOxswapOxtuckOdepthOdropOdupOnipOoverOpickOrollOrotOswapOtuckOcatOsubstrOleftOrightOsizeOinvertOandOorOxorOequalOincOdecOsignOnegateOabsOnotOnzOaddOsubOmulOdivOmodOshlOshrOboolandOboolorOnumequalOnumnotequalOltOgtOlteOgteOminOmaxOwithinOsha1Osha256Ohash160Ohash256OchecksigOcheckmultisigOarraysizeOpackOunpackOpickitemOsetitemOnewarrayOnewstructOappendOreverseOremoveOthrowOthrowifnot" -var _OpCode_map = map[OpCode]string{ - 0: _OpCode_name[0:7], - 1: _OpCode_name[7:19], - 75: _OpCode_name[19:32], - 76: _OpCode_name[32:43], - 77: _OpCode_name[43:54], - 78: _OpCode_name[54:65], - 79: _OpCode_name[65:73], - 81: _OpCode_name[73:80], - 82: _OpCode_name[80:87], - 83: _OpCode_name[87:94], - 84: _OpCode_name[94:101], - 85: _OpCode_name[101:108], - 86: _OpCode_name[108:115], - 87: _OpCode_name[115:122], - 88: _OpCode_name[122:129], - 89: _OpCode_name[129:136], - 90: _OpCode_name[136:144], - 91: _OpCode_name[144:152], - 92: _OpCode_name[152:160], - 93: _OpCode_name[160:168], - 94: _OpCode_name[168:176], - 95: _OpCode_name[176:184], - 96: _OpCode_name[184:192], - 97: _OpCode_name[192:197], - 98: _OpCode_name[197:202], - 99: _OpCode_name[202:209], - 100: _OpCode_name[209:219], - 101: _OpCode_name[219:225], - 102: _OpCode_name[225:230], - 103: _OpCode_name[230:239], - 104: _OpCode_name[239:248], - 105: _OpCode_name[248:258], - 106: _OpCode_name[258:275], - 107: _OpCode_name[275:287], - 108: _OpCode_name[287:301], - 109: _OpCode_name[301:308], - 114: _OpCode_name[308:315], - 115: _OpCode_name[315:322], - 116: _OpCode_name[322:329], - 117: _OpCode_name[329:335], - 118: _OpCode_name[335:340], - 119: _OpCode_name[340:345], - 120: _OpCode_name[345:351], - 121: _OpCode_name[351:357], - 122: _OpCode_name[357:363], - 123: _OpCode_name[363:368], - 124: _OpCode_name[368:374], - 125: _OpCode_name[374:380], - 126: _OpCode_name[380:385], - 127: _OpCode_name[385:393], - 128: _OpCode_name[393:399], - 129: _OpCode_name[399:406], - 130: _OpCode_name[406:412], - 131: _OpCode_name[412:420], - 132: _OpCode_name[420:425], - 133: _OpCode_name[425:429], - 134: _OpCode_name[429:434], - 135: _OpCode_name[434:441], - 139: _OpCode_name[441:446], - 140: _OpCode_name[446:451], - 141: _OpCode_name[451:457], - 143: _OpCode_name[457:465], - 144: _OpCode_name[465:470], - 145: _OpCode_name[470:475], - 146: _OpCode_name[475:479], - 147: _OpCode_name[479:484], - 148: _OpCode_name[484:489], - 149: _OpCode_name[489:494], - 150: _OpCode_name[494:499], - 151: _OpCode_name[499:504], - 152: _OpCode_name[504:509], - 153: _OpCode_name[509:514], - 154: _OpCode_name[514:523], - 155: _OpCode_name[523:531], - 156: _OpCode_name[531:541], - 158: _OpCode_name[541:554], - 159: _OpCode_name[554:558], - 160: _OpCode_name[558:562], - 161: _OpCode_name[562:567], - 162: _OpCode_name[567:572], - 163: _OpCode_name[572:577], - 164: _OpCode_name[577:582], - 165: _OpCode_name[582:590], - 167: _OpCode_name[590:596], - 168: _OpCode_name[596:604], - 169: _OpCode_name[604:613], - 170: _OpCode_name[613:622], - 172: _OpCode_name[622:632], - 174: _OpCode_name[632:647], - 192: _OpCode_name[647:658], - 193: _OpCode_name[658:664], - 194: _OpCode_name[664:672], - 195: _OpCode_name[672:682], - 196: _OpCode_name[682:691], - 197: _OpCode_name[691:701], - 198: _OpCode_name[701:712], - 200: _OpCode_name[712:720], - 201: _OpCode_name[720:729], - 202: _OpCode_name[729:737], - 240: _OpCode_name[737:744], - 241: _OpCode_name[744:756], +var _Opcode_map = map[Opcode]string{ + 0: _Opcode_name[0:6], + 1: _Opcode_name[6:17], + 75: _Opcode_name[17:29], + 76: _Opcode_name[29:39], + 77: _Opcode_name[39:49], + 78: _Opcode_name[49:59], + 79: _Opcode_name[59:66], + 81: _Opcode_name[66:72], + 82: _Opcode_name[72:78], + 83: _Opcode_name[78:84], + 84: _Opcode_name[84:90], + 85: _Opcode_name[90:96], + 86: _Opcode_name[96:102], + 87: _Opcode_name[102:108], + 88: _Opcode_name[108:114], + 89: _Opcode_name[114:120], + 90: _Opcode_name[120:127], + 91: _Opcode_name[127:134], + 92: _Opcode_name[134:141], + 93: _Opcode_name[141:148], + 94: _Opcode_name[148:155], + 95: _Opcode_name[155:162], + 96: _Opcode_name[162:169], + 97: _Opcode_name[169:173], + 98: _Opcode_name[173:177], + 99: _Opcode_name[177:183], + 100: _Opcode_name[183:192], + 101: _Opcode_name[192:197], + 102: _Opcode_name[197:201], + 103: _Opcode_name[201:207], + 104: _Opcode_name[207:215], + 105: _Opcode_name[215:224], + 106: _Opcode_name[224:240], + 107: _Opcode_name[240:251], + 108: _Opcode_name[251:264], + 109: _Opcode_name[264:270], + 114: _Opcode_name[270:276], + 115: _Opcode_name[276:282], + 116: _Opcode_name[282:288], + 117: _Opcode_name[288:293], + 118: _Opcode_name[293:297], + 119: _Opcode_name[297:301], + 120: _Opcode_name[301:306], + 121: _Opcode_name[306:311], + 122: _Opcode_name[311:316], + 123: _Opcode_name[316:320], + 124: _Opcode_name[320:325], + 125: _Opcode_name[325:330], + 126: _Opcode_name[330:334], + 127: _Opcode_name[334:341], + 128: _Opcode_name[341:346], + 129: _Opcode_name[346:352], + 130: _Opcode_name[352:357], + 131: _Opcode_name[357:364], + 132: _Opcode_name[364:368], + 133: _Opcode_name[368:371], + 134: _Opcode_name[371:375], + 135: _Opcode_name[375:381], + 139: _Opcode_name[381:385], + 140: _Opcode_name[385:389], + 141: _Opcode_name[389:394], + 143: _Opcode_name[394:401], + 144: _Opcode_name[401:405], + 145: _Opcode_name[405:409], + 146: _Opcode_name[409:412], + 147: _Opcode_name[412:416], + 148: _Opcode_name[416:420], + 149: _Opcode_name[420:424], + 150: _Opcode_name[424:428], + 151: _Opcode_name[428:432], + 152: _Opcode_name[432:436], + 153: _Opcode_name[436:440], + 154: _Opcode_name[440:448], + 155: _Opcode_name[448:455], + 156: _Opcode_name[455:464], + 158: _Opcode_name[464:476], + 159: _Opcode_name[476:479], + 160: _Opcode_name[479:482], + 161: _Opcode_name[482:486], + 162: _Opcode_name[486:490], + 163: _Opcode_name[490:494], + 164: _Opcode_name[494:498], + 165: _Opcode_name[498:505], + 167: _Opcode_name[505:510], + 168: _Opcode_name[510:517], + 169: _Opcode_name[517:525], + 170: _Opcode_name[525:533], + 172: _Opcode_name[533:542], + 174: _Opcode_name[542:556], + 192: _Opcode_name[556:566], + 193: _Opcode_name[566:571], + 194: _Opcode_name[571:578], + 195: _Opcode_name[578:587], + 196: _Opcode_name[587:595], + 197: _Opcode_name[595:604], + 198: _Opcode_name[604:614], + 200: _Opcode_name[614:621], + 201: _Opcode_name[621:629], + 202: _Opcode_name[629:636], + 240: _Opcode_name[636:642], + 241: _Opcode_name[642:653], } -func (i OpCode) String() string { - if str, ok := _OpCode_map[i]; ok { +func (i Opcode) String() string { + if str, ok := _Opcode_map[i]; ok { return str } - return "OpCode(" + strconv.FormatInt(int64(i), 10) + ")" + return "Opcode(" + strconv.FormatInt(int64(i), 10) + ")" }