From 648563c3e2cac8b489379f8c2a3a8e80babd8a24 Mon Sep 17 00:00:00 2001 From: Anthony De Meulemeester Date: Sun, 22 Apr 2018 20:11:37 +0200 Subject: [PATCH] Compiler (#73) Compiler and VM update --- VERSION | 2 +- pkg/vm/api/crypto/util.go | 21 +++++ pkg/vm/api/util/util.go | 8 -- pkg/vm/compiler/README.md | 6 ++ pkg/vm/compiler/analysis.go | 28 +++++-- pkg/vm/compiler/codegen.go | 20 ++++- pkg/vm/compiler/tests/builtin_test.go | 17 ---- pkg/vm/compiler/tests/compiler_test.go | 76 ------------------ pkg/vm/compiler/tests/custom_type_test.go | 25 ------ pkg/vm/compiler/tests/if_statement_test.go | 91 --------------------- pkg/vm/compiler/tests/import_test.go | 34 -------- pkg/vm/{compiler => }/tests/bar/bar.go | 0 pkg/vm/{compiler => }/tests/foo/foo.go | 0 pkg/vm/tests/for_test.go | 12 +++ pkg/vm/tests/if_test.go | 93 ++++++++++++++++++++++ pkg/vm/tests/import_test.go | 36 +++++++++ pkg/vm/tests/type_test.go | 24 ++++++ pkg/vm/tests/util_test.go | 65 +++++++++++++++ pkg/vm/vm.go | 11 ++- 19 files changed, 304 insertions(+), 265 deletions(-) create mode 100644 pkg/vm/api/crypto/util.go delete mode 100644 pkg/vm/api/util/util.go delete mode 100644 pkg/vm/compiler/tests/builtin_test.go delete mode 100644 pkg/vm/compiler/tests/compiler_test.go delete mode 100644 pkg/vm/compiler/tests/custom_type_test.go delete mode 100644 pkg/vm/compiler/tests/if_statement_test.go delete mode 100644 pkg/vm/compiler/tests/import_test.go rename pkg/vm/{compiler => }/tests/bar/bar.go (100%) rename pkg/vm/{compiler => }/tests/foo/foo.go (100%) create mode 100644 pkg/vm/tests/if_test.go create mode 100644 pkg/vm/tests/import_test.go create mode 100644 pkg/vm/tests/type_test.go create mode 100644 pkg/vm/tests/util_test.go diff --git a/VERSION b/VERSION index 6599454d4..787ffc30a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.41.2 +0.42.0 diff --git a/pkg/vm/api/crypto/util.go b/pkg/vm/api/crypto/util.go new file mode 100644 index 000000000..d2f734564 --- /dev/null +++ b/pkg/vm/api/crypto/util.go @@ -0,0 +1,21 @@ +package crypto + +// SHA1 computes the sha1 hash of b. +func SHA1(b []byte) []byte { + return nil +} + +// SHA256 computes the sha256 hash of b. +func SHA256(b []byte) []byte { + return nil +} + +// Hash160 .. +func Hash160(b []byte) []byte { + return nil +} + +// Hash256 .. +func Hash256(b []byte) []byte { + return nil +} diff --git a/pkg/vm/api/util/util.go b/pkg/vm/api/util/util.go deleted file mode 100644 index 207dcc1d6..000000000 --- a/pkg/vm/api/util/util.go +++ /dev/null @@ -1,8 +0,0 @@ -package util - -// Package util provides utility functions that can be used in smart contracts. -// These functions are just signatures and provide not internal logic. -// Only the compiler knows how to convert them to bytecode. - -// Print is a VM helper function to print/log data. -func Print(v interface{}) {} diff --git a/pkg/vm/compiler/README.md b/pkg/vm/compiler/README.md index 1f4959887..b97069731 100644 --- a/pkg/vm/compiler/README.md +++ b/pkg/vm/compiler/README.md @@ -26,6 +26,12 @@ The neo-go compiler compiles Go programs to bytecode that the NEO virtual machin - storage - runtime +### VM utility helper functions +- SHA1 +- SHA256 +- Hash256 +- Hash160 + ## Not yet implemented - range - some parts of the interop layer (VM API) diff --git a/pkg/vm/compiler/analysis.go b/pkg/vm/compiler/analysis.go index bf0150aa5..05cd1e5fc 100644 --- a/pkg/vm/compiler/analysis.go +++ b/pkg/vm/compiler/analysis.go @@ -11,8 +11,12 @@ import ( ) var ( - // Go language builtin functions. - builtinFuncs = []string{"len", "append"} + // Go language builtin functions and custom builtin utility functions. + builtinFuncs = []string{ + "len", "append", "SHA256", + "SHA1", "Hash256", "Hash160", + } + // VM system calls that have no return value. noRetSyscalls = []string{ "Notify", "Log", "Put", "Register", "Delete", @@ -162,14 +166,22 @@ func analyzeFuncUsage(pkgs map[*types.Package]*loader.PackageInfo) funcUsage { } func isBuiltin(expr ast.Expr) bool { - if ident, ok := expr.(*ast.Ident); ok { - for _, n := range builtinFuncs { - if ident.Name == n { - return true - } - } + var name string + + switch t := expr.(type) { + case *ast.Ident: + name = t.Name + case *ast.SelectorExpr: + name = t.Sel.Name + default: return false } + + for _, n := range builtinFuncs { + if name == n { + return true + } + } return false } diff --git a/pkg/vm/compiler/codegen.go b/pkg/vm/compiler/codegen.go index c7e2dee80..b8ac0ae3b 100644 --- a/pkg/vm/compiler/codegen.go +++ b/pkg/vm/compiler/codegen.go @@ -421,7 +421,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { if isBuiltin { // Use the ident to check, builtins are not in func scopes. // We can be sure builtins are of type *ast.Ident. - c.convertBuiltin(n.Fun.(*ast.Ident).Name, n) + c.convertBuiltin(n) } else if isSyscall(f.name) { c.convertSyscall(f.name) } else { @@ -529,7 +529,15 @@ func (c *codegen) convertSyscall(name string) { emitOpcode(c.prog, vm.Onop) // @OPTIMIZE } -func (c *codegen) convertBuiltin(name string, expr *ast.CallExpr) { +func (c *codegen) convertBuiltin(expr *ast.CallExpr) { + var name string + switch t := expr.Fun.(type) { + case *ast.Ident: + name = t.Name + case *ast.SelectorExpr: + name = t.Sel.Name + } + switch name { case "len": arg := expr.Args[0] @@ -541,6 +549,14 @@ func (c *codegen) convertBuiltin(name string, expr *ast.CallExpr) { } case "append": emitOpcode(c.prog, vm.Oappend) + case "SHA256": + emitOpcode(c.prog, vm.Osha256) + case "SHA1": + emitOpcode(c.prog, vm.Osha1) + case "Hash256": + emitOpcode(c.prog, vm.Ohash256) + case "Hash160": + emitOpcode(c.prog, vm.Ohash160) } } diff --git a/pkg/vm/compiler/tests/builtin_test.go b/pkg/vm/compiler/tests/builtin_test.go deleted file mode 100644 index 4b1f0515b..000000000 --- a/pkg/vm/compiler/tests/builtin_test.go +++ /dev/null @@ -1,17 +0,0 @@ -package compiler - -var builtinTestCases = []testCase{ - { - "array len", - ` - package foo - - func Main() int { - x := []int{0, 1, 2} - y := len(x) - return y - } - `, - "53c56b52510053c16c766b00527ac46c766b00c361c06c766b51527ac46203006c766b51c3616c7566", - }, -} diff --git a/pkg/vm/compiler/tests/compiler_test.go b/pkg/vm/compiler/tests/compiler_test.go deleted file mode 100644 index 4e87dbd1a..000000000 --- a/pkg/vm/compiler/tests/compiler_test.go +++ /dev/null @@ -1,76 +0,0 @@ -package compiler - -import ( - "fmt" - "os" - "testing" - "text/tabwriter" - - "github.com/CityOfZion/neo-go/pkg/vm" -) - -type testCase struct { - name string - src string - result interface{} -} - -func TestAllCases(t *testing.T) { - // The Go language - //testCases = append(testCases, builtinTestCases...) - //testCases = append(testCases, arrayTestCases...) - //testCases = append(testCases, binaryExprTestCases...) - //testCases = append(testCases, functionCallTestCases...) - //testCases = append(testCases, boolTestCases...) - //testCases = append(testCases, stringTestCases...) - //testCases = append(testCases, structTestCases...) - //testCases = append(testCases, ifStatementTestCases...) - //testCases = append(testCases, customTypeTestCases...) - //testCases = append(testCases, constantTestCases...) - //testCases = append(testCases, importTestCases...) - //testCases = append(testCases, forTestCases...) - - //// Blockchain specific - //testCases = append(testCases, storageTestCases...) - //testCases = append(testCases, runtimeTestCases...) - - //for _, tc := range testCases { - // b, err := compiler.Compile(strings.NewReader(tc.src), &compiler.Options{}) - // if err != nil { - // t.Fatal(err) - // } - - // expectedResult, err := hex.DecodeString(tc.result) - // if err != nil { - // t.Fatal(err) - // } - - // if bytes.Compare(b, expectedResult) != 0 { - // fmt.Println(tc.src) - // t.Log(hex.EncodeToString(b)) - // dumpOpCodeSideBySide(b, expectedResult) - // t.Fatalf("compiling %s failed", tc.name) - // } - //} -} - -func dumpOpCodeSideBySide(have, want []byte) { - w := tabwriter.NewWriter(os.Stdout, 0, 0, 4, ' ', 0) - fmt.Fprintln(w, "INDEX\tHAVE OPCODE\tDESC\tWANT OPCODE\tDESC\tDIFF") - - var b byte - for i := 0; i < len(have); i++ { - if len(want) <= i { - b = 0x00 - } else { - b = want[i] - } - diff := "" - if have[i] != b { - diff = "<<" - } - fmt.Fprintf(w, "%d\t0x%2x\t%s\t0x%2x\t%s\t%s\n", - i, have[i], vm.Opcode(have[i]), b, vm.Opcode(b), diff) - } - w.Flush() -} diff --git a/pkg/vm/compiler/tests/custom_type_test.go b/pkg/vm/compiler/tests/custom_type_test.go deleted file mode 100644 index dfaf3bd16..000000000 --- a/pkg/vm/compiler/tests/custom_type_test.go +++ /dev/null @@ -1,25 +0,0 @@ -package compiler - -var customTypeTestCases = []testCase{ - { - "test custom type", - ` - package foo - - type bar int - type specialString string - - func Main() specialString { - var x bar - var str specialString - x = 10 - str = "some short string" - if x == 10 { - return str - } - return "none" - } - `, - "55c56b5a6c766b00527ac411736f6d652073686f727420737472696e676c766b51527ac46c766b00c35a9c640f006203006c766b51c3616c7566620300046e6f6e65616c7566", - }, -} diff --git a/pkg/vm/compiler/tests/if_statement_test.go b/pkg/vm/compiler/tests/if_statement_test.go deleted file mode 100644 index 0352eda86..000000000 --- a/pkg/vm/compiler/tests/if_statement_test.go +++ /dev/null @@ -1,91 +0,0 @@ -package compiler - -var ifStatementTestCases = []testCase{ - { - "if statement LT", - ` - package testcase - func Main() int { - x := 10 - if x < 100 { - return 1 - } - return 0 - } - `, - "54c56b5a6c766b00527ac46c766b00c301649f640b0062030051616c756662030000616c7566", - }, - { - "if statement GT", - ` - package testcase - func Main() int { - x := 10 - if x > 100 { - return 1 - } - return 0 - } - `, - "54c56b5a6c766b00527ac46c766b00c30164a0640b0062030051616c756662030000616c7566", - }, - { - "if statement GTE", - ` - package testcase - func Main() int { - x := 10 - if x >= 100 { - return 1 - } - return 0 - } - `, - "54c56b5a6c766b00527ac46c766b00c30164a2640b0062030051616c756662030000616c7566", - }, - { - "complex if statement with LAND", - ` - package testcase - func Main() int { - x := 10 - if x >= 10 && x <= 20 { - return 1 - } - return 0 - } - `, - "54c56b5a6c766b00527ac46c766b00c35aa26416006c766b00c30114a1640b0062030051616c756662030000616c7566", - }, - { - "complex if statement with LOR", - ` - package testcase - func Main() int { - x := 10 - if x >= 10 || x <= 20 { - return 1 - } - return 0 - } - `, - "54c56b5a6c766b00527ac46c766b00c35aa2630e006c766b00c30114a1640b0062030051616c756662030000616c7566", - }, - { - "nested if statements", - ` - package testcase - func Main() int { - x := 10 - if x > 10 { - if x < 20 { - return 1 - } - return 2 - } - return 0 - } - `, - "56c56b5a6c766b00527ac46c766b00c35aa0641e006c766b00c301149f640b0062030051616c756662030052616c756662030000616c7566", - }, -} diff --git a/pkg/vm/compiler/tests/import_test.go b/pkg/vm/compiler/tests/import_test.go deleted file mode 100644 index 3fcd695ea..000000000 --- a/pkg/vm/compiler/tests/import_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package compiler - -var importTestCases = []testCase{ - { - "import function", - ` - package somethingelse - - import "github.com/CityOfZion/neo-go/pkg/vm/compiler/tests/foo" - - func Main() int { - i := foo.NewBar() - return i - } - `, - "52c56b616516006c766b00527ac46203006c766b00c3616c756651c56b6203005a616c7566", - }, - { - "import test", - ` - package somethingwedontcareabout - - import "github.com/CityOfZion/neo-go/pkg/vm/compiler/tests/bar" - - func Main() int { - b := bar.Bar{ - X: 4, - } - return b.Y - } - `, - "52c56b6154c66b546c766b00527ac4006c766b51527ac4006c766b52527ac4006c766b53527ac46c6c766b00527ac46203006c766b00c351c3616c7566", - }, -} diff --git a/pkg/vm/compiler/tests/bar/bar.go b/pkg/vm/tests/bar/bar.go similarity index 100% rename from pkg/vm/compiler/tests/bar/bar.go rename to pkg/vm/tests/bar/bar.go diff --git a/pkg/vm/compiler/tests/foo/foo.go b/pkg/vm/tests/foo/foo.go similarity index 100% rename from pkg/vm/compiler/tests/foo/foo.go rename to pkg/vm/tests/foo/foo.go diff --git a/pkg/vm/tests/for_test.go b/pkg/vm/tests/for_test.go index ed6d26266..0cc443451 100644 --- a/pkg/vm/tests/for_test.go +++ b/pkg/vm/tests/for_test.go @@ -177,6 +177,18 @@ func TestStringLen(t *testing.T) { eval(t, src, big.NewInt(27)) } +func TestByteArrayLen(t *testing.T) { + src := ` + package foo + + func Main() int { + b := []byte{0x00, 0x01, 0x2} + return len(b) + } + ` + eval(t, src, big.NewInt(3)) +} + func TestSimpleString(t *testing.T) { src := ` package foo diff --git a/pkg/vm/tests/if_test.go b/pkg/vm/tests/if_test.go new file mode 100644 index 000000000..6e39d0ab6 --- /dev/null +++ b/pkg/vm/tests/if_test.go @@ -0,0 +1,93 @@ +package vm_test + +import ( + "math/big" + "testing" +) + +func TestLT(t *testing.T) { + src := ` + package foo + func Main() int { + x := 10 + if x < 100 { + return 1 + } + return 0 + } + ` + eval(t, src, big.NewInt(1)) +} + +func TestGT(t *testing.T) { + src := ` + package testcase + func Main() int { + x := 10 + if x > 100 { + return 1 + } + return 0 + } + ` + eval(t, src, big.NewInt(0)) +} + +func TestGTE(t *testing.T) { + src := ` + package testcase + func Main() int { + x := 10 + if x >= 100 { + return 1 + } + return 0 + } + ` + eval(t, src, big.NewInt(0)) +} + +func TestLAND(t *testing.T) { + src := ` + package testcase + func Main() int { + x := 10 + if x >= 10 && x <= 20 { + return 1 + } + return 0 + } + ` + eval(t, src, big.NewInt(1)) +} + +func TestLOR(t *testing.T) { + src := ` + package testcase + func Main() int { + x := 10 + if x >= 10 || x <= 20 { + return 1 + } + return 0 + } + ` + eval(t, src, big.NewInt(1)) +} + +func TestNestedIF(t *testing.T) { + src := ` + package testcase + func Main() int { + x := 10 + if x > 10 { + if x < 20 { + return 1 + } + return 2 + } + return 0 + } + ` + eval(t, src, big.NewInt(0)) +} diff --git a/pkg/vm/tests/import_test.go b/pkg/vm/tests/import_test.go new file mode 100644 index 000000000..eb5fe3e8e --- /dev/null +++ b/pkg/vm/tests/import_test.go @@ -0,0 +1,36 @@ +package vm_test + +import ( + "math/big" + "testing" +) + +func TestImportFunction(t *testing.T) { + src := ` + package somethingelse + + import "github.com/CityOfZion/neo-go/pkg/vm/tests/foo" + + func Main() int { + i := foo.NewBar() + return i + } + ` + eval(t, src, big.NewInt(10)) +} + +func TestImportStruct(t *testing.T) { + src := ` + package somethingwedontcareabout + + import "github.com/CityOfZion/neo-go/pkg/vm/tests/bar" + + func Main() int { + b := bar.Bar{ + X: 4, + } + return b.Y + } + ` + eval(t, src, big.NewInt(0)) +} diff --git a/pkg/vm/tests/type_test.go b/pkg/vm/tests/type_test.go new file mode 100644 index 000000000..af3ed280c --- /dev/null +++ b/pkg/vm/tests/type_test.go @@ -0,0 +1,24 @@ +package vm_test + +import "testing" + +func TestCustomType(t *testing.T) { + src := ` + package foo + + type bar int + type specialString string + + func Main() specialString { + var x bar + var str specialString + x = 10 + str = "some short string" + if x == 10 { + return str + } + return "none" + } + ` + eval(t, src, []byte("some short string")) +} diff --git a/pkg/vm/tests/util_test.go b/pkg/vm/tests/util_test.go new file mode 100644 index 000000000..db56be0a3 --- /dev/null +++ b/pkg/vm/tests/util_test.go @@ -0,0 +1,65 @@ +package vm_test + +import ( + "testing" +) + +func TestSHA256(t *testing.T) { + src := ` + package foo + import ( + "github.com/CityOfZion/neo-go/pkg/vm/api/crypto" + ) + func Main() []byte { + src := []byte{0x97} + hash := crypto.SHA256(src) + return hash + } + ` + eval(t, src, []byte{0x2a, 0xa, 0xb7, 0x32, 0xb4, 0xe9, 0xd8, 0x5e, 0xf7, 0xdc, 0x25, 0x30, 0x3b, 0x64, 0xab, 0x52, 0x7c, 0x25, 0xa4, 0xd7, 0x78, 0x15, 0xeb, 0xb5, 0x79, 0xf3, 0x96, 0xec, 0x6c, 0xac, 0xca, 0xd3}) +} + +func TestSHA1(t *testing.T) { + src := ` + package foo + import ( + "github.com/CityOfZion/neo-go/pkg/vm/api/crypto" + ) + func Main() []byte { + src := []byte{0x97} + hash := crypto.SHA1(src) + return hash + } + ` + eval(t, src, []byte{0xfa, 0x13, 0x8a, 0xe3, 0x56, 0xd3, 0x5c, 0x8d, 0x77, 0x8, 0x3c, 0x40, 0x6a, 0x5b, 0xe7, 0x37, 0x45, 0x64, 0x3a, 0xae}) +} + +func TestHash160(t *testing.T) { + src := ` + package foo + import ( + "github.com/CityOfZion/neo-go/pkg/vm/api/crypto" + ) + func Main() []byte { + src := []byte{0x97} + hash := crypto.Hash160(src) + return hash + } + ` + eval(t, src, []byte{0x5f, 0xa4, 0x1c, 0x76, 0xf7, 0xe8, 0xca, 0x72, 0xb7, 0x18, 0xff, 0x59, 0x22, 0x91, 0xc2, 0x3a, 0x3a, 0xf5, 0x58, 0x6c}) +} + +func TestHash256(t *testing.T) { + src := ` + package foo + import ( + "github.com/CityOfZion/neo-go/pkg/vm/api/crypto" + ) + func Main() []byte { + src := []byte{0x97} + hash := crypto.Hash256(src) + return hash + } + ` + eval(t, src, []byte{0xc0, 0x85, 0x26, 0xad, 0x17, 0x36, 0x53, 0xee, 0xb8, 0xc7, 0xf4, 0xae, 0x82, 0x8b, 0x6e, 0xa1, 0x84, 0xac, 0x5a, 0x3, 0x8a, 0xf6, 0xc3, 0x68, 0x23, 0xfa, 0x5f, 0x5d, 0xd9, 0x1b, 0x91, 0xa2}) +} diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 4bb132132..370854a18 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -584,11 +584,16 @@ func (v *VM) execute(ctx *Context, op Opcode) { case Oarraysize: elem := v.estack.Pop() - arr, ok := elem.value.Value().([]StackItem) - if !ok { + // Cause there is no native (byte) item type here, hence we need to check + // the type of the item for array size operations. + switch t := elem.value.Value().(type) { + case []StackItem: + v.estack.PushVal(len(t)) + case []uint8: + v.estack.PushVal(len(t)) + default: panic("ARRAYSIZE: item not of type []StackItem") } - v.estack.PushVal(len(arr)) case Osize: elem := v.estack.Pop()