Compiler (#73)

Compiler and VM update
This commit is contained in:
Anthony De Meulemeester 2018-04-22 20:11:37 +02:00 committed by GitHub
parent a73757df66
commit 648563c3e2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 304 additions and 265 deletions

View file

@ -1 +1 @@
0.41.2
0.42.0

21
pkg/vm/api/crypto/util.go Normal file
View file

@ -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
}

View file

@ -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{}) {}

View file

@ -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)

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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",
},
}

View file

@ -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()
}

View file

@ -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",
},
}

View file

@ -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",
},
}

View file

@ -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",
},
}

View file

@ -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

93
pkg/vm/tests/if_test.go Normal file
View file

@ -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))
}

View file

@ -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))
}

24
pkg/vm/tests/type_test.go Normal file
View file

@ -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"))
}

65
pkg/vm/tests/util_test.go Normal file
View file

@ -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})
}

View file

@ -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()