mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-23 03:38:35 +00:00
Refactor of imports + lots of sweet stuff (#30)
* implemented global variables. * refactored imports + lots and lots of other sweet stuff + fix #28. * Implemented the VM interop runtime API (GetTrigger, CheckWitness, ...)
This commit is contained in:
parent
63bc244163
commit
de3395fb51
14 changed files with 344 additions and 158 deletions
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
0.15.0
|
0.16.0
|
||||||
|
|
36
pkg/smartcontract/runtime/runtime.go
Normal file
36
pkg/smartcontract/runtime/runtime.go
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
import "github.com/CityOfZion/neo-go/pkg/core"
|
||||||
|
|
||||||
|
// T is shorthand for interface{} which allows us to use the function signatures
|
||||||
|
// in a more elegant way.
|
||||||
|
type T interface{}
|
||||||
|
|
||||||
|
// GetTrigger return the current trigger type. The return in this function
|
||||||
|
// doesn't really mather, this is just an interop placeholder.
|
||||||
|
func GetTrigger() T { return 0 }
|
||||||
|
|
||||||
|
// CheckWitness verifies if the invoker is the owner of the contract.
|
||||||
|
func CheckWitness(hash T) T { return 0 }
|
||||||
|
|
||||||
|
// GetCurrentBlock returns the current block.
|
||||||
|
func GetCurrentBlock() core.Block { return core.Block{} }
|
||||||
|
|
||||||
|
// GetTime returns the timestamp of the most recent block.
|
||||||
|
func GetTime() int { return 0 }
|
||||||
|
|
||||||
|
// Notify an event to the VM.
|
||||||
|
func Notify(arg T) T { return 0 }
|
||||||
|
|
||||||
|
// Log intructs the VM to log the given message.
|
||||||
|
func Log(message string) T { return 0 }
|
||||||
|
|
||||||
|
// Verification returns the verification trigger type.
|
||||||
|
func Verification() byte {
|
||||||
|
return 0x00
|
||||||
|
}
|
||||||
|
|
||||||
|
// Application returns the application trigger type.
|
||||||
|
func Application() byte {
|
||||||
|
return 0x10
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
package storage
|
package storage
|
||||||
|
|
||||||
// GetContext ..
|
// GetContext ..
|
||||||
func GetContext() int { return 0 }
|
func GetContext() interface{} { return 0 }
|
||||||
|
|
||||||
// Put stores a value in to the storage.
|
// Put stores a value in to the storage.
|
||||||
func Put(ctx interface{}, key interface{}, value interface{}) int { return 0 }
|
func Put(ctx interface{}, key interface{}, value interface{}) int { return 0 }
|
|
@ -15,7 +15,8 @@ The neo-go compiler compiles Go programs to bytecode that the NEO virtual machin
|
||||||
### Go internals
|
### Go internals
|
||||||
- type checker
|
- type checker
|
||||||
- multiple assigns
|
- multiple assigns
|
||||||
- types int, string and booleans
|
- global variables
|
||||||
|
- types int, string, byte and booleans
|
||||||
- struct types + method receives
|
- struct types + method receives
|
||||||
- functions
|
- functions
|
||||||
- composite literals `[]int, []string`
|
- composite literals `[]int, []string`
|
||||||
|
@ -26,12 +27,13 @@ The neo-go compiler compiles Go programs to bytecode that the NEO virtual machin
|
||||||
|
|
||||||
### VM API (interop layer)
|
### VM API (interop layer)
|
||||||
- storage
|
- storage
|
||||||
|
- runtime
|
||||||
|
|
||||||
## Not yet implemented
|
## Not yet implemented
|
||||||
- for loops
|
- for loops
|
||||||
- ranges
|
- ranges
|
||||||
- builtins (append, len, ..)
|
- builtins (append, len, ..)
|
||||||
- large part of the interop layer (VM API)
|
- some parts of the interop layer (VM API)
|
||||||
|
|
||||||
## Not supported
|
## Not supported
|
||||||
Due to the limitations of the NEO virtual machine, features listed below will not be supported.
|
Due to the limitations of the NEO virtual machine, features listed below will not be supported.
|
||||||
|
|
71
pkg/vm/compiler/analysis.go
Normal file
71
pkg/vm/compiler/analysis.go
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
package compiler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/ast"
|
||||||
|
"go/constant"
|
||||||
|
"go/types"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"golang.org/x/tools/go/loader"
|
||||||
|
)
|
||||||
|
|
||||||
|
// countGlobals counts the global variables in the program to add
|
||||||
|
// them with the stacksize of the function.
|
||||||
|
func countGlobals(f *ast.File) (i int64) {
|
||||||
|
ast.Inspect(f, func(node ast.Node) bool {
|
||||||
|
switch node.(type) {
|
||||||
|
// Skip all functio declarations.
|
||||||
|
case *ast.FuncDecl:
|
||||||
|
return false
|
||||||
|
// After skipping all funcDecls we are sure that each value spec
|
||||||
|
// is a global declared variable or constant.
|
||||||
|
case *ast.ValueSpec:
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// isIdentBool looks if the given ident is a boolean.
|
||||||
|
func isIdentBool(ident *ast.Ident) bool {
|
||||||
|
return ident.Name == "true" || ident.Name == "false"
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeBoolFromIdent creates a bool type from an *ast.Ident.
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveEntryPoint returns the function declaration of the entrypoint and the corresponding file.
|
||||||
|
func resolveEntryPoint(entry string, pkg *loader.PackageInfo) (*ast.FuncDecl, *ast.File) {
|
||||||
|
var (
|
||||||
|
main *ast.FuncDecl
|
||||||
|
file *ast.File
|
||||||
|
)
|
||||||
|
for _, f := range pkg.Files {
|
||||||
|
ast.Inspect(f, func(n ast.Node) bool {
|
||||||
|
switch t := n.(type) {
|
||||||
|
case *ast.FuncDecl:
|
||||||
|
if t.Name.Name == entry {
|
||||||
|
main = t
|
||||||
|
file = f
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return main, file
|
||||||
|
}
|
|
@ -15,16 +15,20 @@ import (
|
||||||
const mainIdent = "Main"
|
const mainIdent = "Main"
|
||||||
|
|
||||||
type codegen struct {
|
type codegen struct {
|
||||||
|
// Information about the program with all its dependencies.
|
||||||
|
buildInfo *buildInfo
|
||||||
|
|
||||||
|
// prog holds the output buffer
|
||||||
prog *bytes.Buffer
|
prog *bytes.Buffer
|
||||||
|
|
||||||
// Type information
|
// Type information
|
||||||
typeInfo *types.Info
|
typeInfo *types.Info
|
||||||
|
|
||||||
// a mapping of func identifiers with its scope.
|
// A mapping of func identifiers with their scope.
|
||||||
funcs map[string]*funcScope
|
funcs map[string]*funcScope
|
||||||
|
|
||||||
// current function being generated
|
// Current funcScope being converted.
|
||||||
fctx *funcScope
|
scope *funcScope
|
||||||
|
|
||||||
// Label table for recording jump destinations.
|
// Label table for recording jump destinations.
|
||||||
l []int
|
l []int
|
||||||
|
@ -50,15 +54,21 @@ func (c *codegen) emitLoadConst(t types.TypeAndValue) {
|
||||||
switch typ := t.Type.Underlying().(type) {
|
switch typ := t.Type.Underlying().(type) {
|
||||||
case *types.Basic:
|
case *types.Basic:
|
||||||
switch typ.Kind() {
|
switch typ.Kind() {
|
||||||
case types.Int:
|
case types.Int, types.UntypedInt:
|
||||||
val, _ := constant.Int64Val(t.Value)
|
val, _ := constant.Int64Val(t.Value)
|
||||||
emitInt(c.prog, val)
|
emitInt(c.prog, val)
|
||||||
case types.String:
|
case types.String, types.UntypedString:
|
||||||
val := constant.StringVal(t.Value)
|
val := constant.StringVal(t.Value)
|
||||||
emitString(c.prog, val)
|
emitString(c.prog, val)
|
||||||
case types.Bool, types.UntypedBool:
|
case types.Bool, types.UntypedBool:
|
||||||
val := constant.BoolVal(t.Value)
|
val := constant.BoolVal(t.Value)
|
||||||
emitBool(c.prog, val)
|
emitBool(c.prog, val)
|
||||||
|
case types.Byte:
|
||||||
|
val, _ := constant.Int64Val(t.Value)
|
||||||
|
b := byte(val)
|
||||||
|
emitBytes(c.prog, []byte{b})
|
||||||
|
default:
|
||||||
|
log.Fatalf("compiler don't know how to convert this basic type: %v", t)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
log.Fatalf("compiler don't know how to convert this constant: %v", t)
|
log.Fatalf("compiler don't know how to convert this constant: %v", t)
|
||||||
|
@ -66,7 +76,7 @@ func (c *codegen) emitLoadConst(t types.TypeAndValue) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *codegen) emitLoadLocal(name string) {
|
func (c *codegen) emitLoadLocal(name string) {
|
||||||
pos := c.fctx.loadLocal(name)
|
pos := c.scope.loadLocal(name)
|
||||||
if pos < 0 {
|
if pos < 0 {
|
||||||
log.Fatalf("cannot load local variable with position: %d", pos)
|
log.Fatalf("cannot load local variable with position: %d", pos)
|
||||||
}
|
}
|
||||||
|
@ -80,7 +90,7 @@ func (c *codegen) emitLoadLocal(name string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *codegen) emitLoadStructField(sName, fName string) {
|
func (c *codegen) emitLoadStructField(sName, fName string) {
|
||||||
strct := c.fctx.loadStruct(sName)
|
strct := c.scope.loadStruct(sName)
|
||||||
pos := strct.loadField(fName)
|
pos := strct.loadField(fName)
|
||||||
emitInt(c.prog, int64(pos))
|
emitInt(c.prog, int64(pos))
|
||||||
emitOpcode(c.prog, vm.Opickitem)
|
emitOpcode(c.prog, vm.Opickitem)
|
||||||
|
@ -102,14 +112,42 @@ func (c *codegen) emitStoreLocal(pos int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *codegen) emitStoreStructField(sName, fName string) {
|
func (c *codegen) emitStoreStructField(sName, fName string) {
|
||||||
strct := c.fctx.loadStruct(sName)
|
strct := c.scope.loadStruct(sName)
|
||||||
pos := strct.loadField(fName)
|
pos := strct.loadField(fName)
|
||||||
emitInt(c.prog, int64(pos))
|
emitInt(c.prog, int64(pos))
|
||||||
emitOpcode(c.prog, vm.Orot)
|
emitOpcode(c.prog, vm.Orot)
|
||||||
emitOpcode(c.prog, vm.Osetitem)
|
emitOpcode(c.prog, vm.Osetitem)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *codegen) convertFuncDecl(decl *ast.FuncDecl) {
|
func (c *codegen) emitSyscallReturn() {
|
||||||
|
emitOpcode(c.prog, vm.Ojmp)
|
||||||
|
emitOpcode(c.prog, vm.Opcode(0x03))
|
||||||
|
emitOpcode(c.prog, vm.Opush0)
|
||||||
|
|
||||||
|
emitInt(c.prog, int64(0))
|
||||||
|
|
||||||
|
emitOpcode(c.prog, vm.Onop)
|
||||||
|
emitOpcode(c.prog, vm.Ofromaltstack)
|
||||||
|
emitOpcode(c.prog, vm.Odrop)
|
||||||
|
emitOpcode(c.prog, vm.Oret)
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertGlobals will traverse the AST and only convert global declarations.
|
||||||
|
// If we call this in convertFuncDecl then it will load all global variables
|
||||||
|
// into the scope of the function.
|
||||||
|
func (c *codegen) convertGlobals(f *ast.File) {
|
||||||
|
ast.Inspect(f, func(node ast.Node) bool {
|
||||||
|
switch n := node.(type) {
|
||||||
|
case *ast.FuncDecl:
|
||||||
|
return false
|
||||||
|
case *ast.GenDecl:
|
||||||
|
ast.Walk(c, n)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *codegen) convertFuncDecl(file *ast.File, decl *ast.FuncDecl) {
|
||||||
var (
|
var (
|
||||||
f *funcScope
|
f *funcScope
|
||||||
ok bool
|
ok bool
|
||||||
|
@ -122,10 +160,12 @@ func (c *codegen) convertFuncDecl(decl *ast.FuncDecl) {
|
||||||
f = c.newFunc(decl)
|
f = c.newFunc(decl)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.fctx = f
|
c.scope = f
|
||||||
ast.Inspect(decl, c.fctx.analyzeVoidCalls)
|
ast.Inspect(decl, c.scope.analyzeVoidCalls)
|
||||||
|
|
||||||
emitInt(c.prog, f.stackSize())
|
// All globals copied into the scope of the function need to be added
|
||||||
|
// to the stack size of the function.
|
||||||
|
emitInt(c.prog, f.stackSize()+countGlobals(file))
|
||||||
emitOpcode(c.prog, vm.Onewarray)
|
emitOpcode(c.prog, vm.Onewarray)
|
||||||
emitOpcode(c.prog, vm.Otoaltstack)
|
emitOpcode(c.prog, vm.Otoaltstack)
|
||||||
|
|
||||||
|
@ -142,8 +182,8 @@ func (c *codegen) convertFuncDecl(decl *ast.FuncDecl) {
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Fatal("method receiver is not a struct type")
|
log.Fatal("method receiver is not a struct type")
|
||||||
}
|
}
|
||||||
c.fctx.newStruct(t)
|
c.scope.newStruct(t)
|
||||||
l := c.fctx.newLocal(ident.Name)
|
l := c.scope.newLocal(ident.Name)
|
||||||
c.emitStoreLocal(l)
|
c.emitStoreLocal(l)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -151,28 +191,41 @@ func (c *codegen) convertFuncDecl(decl *ast.FuncDecl) {
|
||||||
// Load the arguments in scope.
|
// Load the arguments in scope.
|
||||||
for _, arg := range decl.Type.Params.List {
|
for _, arg := range decl.Type.Params.List {
|
||||||
name := arg.Names[0].Name // for now.
|
name := arg.Names[0].Name // for now.
|
||||||
l := c.fctx.newLocal(name)
|
l := c.scope.newLocal(name)
|
||||||
c.emitStoreLocal(l)
|
c.emitStoreLocal(l)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If this function is a syscall we will manipulate the return value to 0.
|
||||||
|
// All the syscalls are just signatures functions and bring no real return value.
|
||||||
|
// The return values you will find in the smartcontract package is just for
|
||||||
|
// satisfying the typechecker and the user experience.
|
||||||
|
if isSyscall(f.name) {
|
||||||
|
c.emitSyscallReturn()
|
||||||
|
} else {
|
||||||
|
// After loading the arguments we can convert the globals into the scope of the function.
|
||||||
|
c.convertGlobals(file)
|
||||||
ast.Walk(c, decl.Body)
|
ast.Walk(c, decl.Body)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
switch n := node.(type) {
|
switch n := node.(type) {
|
||||||
|
|
||||||
// General declarations.
|
// General declarations.
|
||||||
// With value: var x int = 2
|
// var (
|
||||||
// Without value: var x int
|
// x = 2
|
||||||
|
// )
|
||||||
case *ast.GenDecl:
|
case *ast.GenDecl:
|
||||||
switch t := n.Specs[0].(type) {
|
for _, spec := range n.Specs {
|
||||||
|
switch t := spec.(type) {
|
||||||
case *ast.ValueSpec:
|
case *ast.ValueSpec:
|
||||||
if len(t.Values) > 0 {
|
for i, val := range t.Values {
|
||||||
ast.Walk(c, t.Values[0])
|
ast.Walk(c, val)
|
||||||
l := c.fctx.newLocal(t.Names[0].Name)
|
l := c.scope.newLocal(t.Names[i].Name)
|
||||||
c.emitStoreLocal(l)
|
c.emitStoreLocal(l)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
case *ast.AssignStmt:
|
case *ast.AssignStmt:
|
||||||
|
@ -184,11 +237,11 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
c.emitLoadLocal(t.Name)
|
c.emitLoadLocal(t.Name)
|
||||||
ast.Walk(c, n.Rhs[0])
|
ast.Walk(c, n.Rhs[0])
|
||||||
c.convertToken(n.Tok)
|
c.convertToken(n.Tok)
|
||||||
l := c.fctx.loadLocal(t.Name)
|
l := c.scope.loadLocal(t.Name)
|
||||||
c.emitStoreLocal(l)
|
c.emitStoreLocal(l)
|
||||||
default:
|
default:
|
||||||
ast.Walk(c, n.Rhs[0])
|
ast.Walk(c, n.Rhs[0])
|
||||||
l := c.fctx.loadLocal(t.Name)
|
l := c.scope.loadLocal(t.Name)
|
||||||
c.emitStoreLocal(l)
|
c.emitStoreLocal(l)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,7 +299,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
case *ast.BasicLit:
|
case *ast.BasicLit:
|
||||||
c.emitLoadConst(c.getTypeInfo(n))
|
c.emitLoadConst(c.typeInfo.Types[n])
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
case *ast.Ident:
|
case *ast.Ident:
|
||||||
|
@ -268,7 +321,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
default:
|
default:
|
||||||
ln := len(n.Elts)
|
ln := len(n.Elts)
|
||||||
for i := ln - 1; i >= 0; i-- {
|
for i := ln - 1; i >= 0; i-- {
|
||||||
c.emitLoadConst(c.getTypeInfo(n.Elts[i]))
|
c.emitLoadConst(c.typeInfo.Types[n.Elts[i]])
|
||||||
}
|
}
|
||||||
emitInt(c.prog, int64(ln))
|
emitInt(c.prog, int64(ln))
|
||||||
emitOpcode(c.prog, vm.Opack)
|
emitOpcode(c.prog, vm.Opack)
|
||||||
|
@ -300,9 +353,13 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
// The AST package will try to resolve all basic literals for us.
|
// 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
|
// 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.
|
// and needs no further action. e.g. x := 2 + 2 + 2 will be resolved to 6.
|
||||||
if tinfo := c.getTypeInfo(n); tinfo.Value != nil {
|
// NOTE: Constants will also be automagically resolved be the AST parser.
|
||||||
|
// example:
|
||||||
|
// const x = 10
|
||||||
|
// x + 2 will results into 12
|
||||||
|
if tinfo := c.typeInfo.Types[n]; tinfo.Value != nil {
|
||||||
c.emitLoadConst(tinfo)
|
c.emitLoadConst(tinfo)
|
||||||
return c
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ast.Walk(c, n.X)
|
ast.Walk(c, n.X)
|
||||||
|
@ -364,7 +421,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
|
|
||||||
// If we are not assigning this function to a variable we need to drop
|
// If we are not assigning this function to a variable we need to drop
|
||||||
// the top stack item. It's not a void but you get the point \o/.
|
// the top stack item. It's not a void but you get the point \o/.
|
||||||
if _, ok := c.fctx.voidCalls[n]; ok && !isNoRetSyscall(f.name) {
|
if _, ok := c.scope.voidCalls[n]; ok && !isNoRetSyscall(f.name) {
|
||||||
emitOpcode(c.prog, vm.Odrop)
|
emitOpcode(c.prog, vm.Odrop)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -372,8 +429,14 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
case *ast.SelectorExpr:
|
case *ast.SelectorExpr:
|
||||||
switch t := n.X.(type) {
|
switch t := n.X.(type) {
|
||||||
case *ast.Ident:
|
case *ast.Ident:
|
||||||
|
typ := c.typeInfo.ObjectOf(t).Type().Underlying()
|
||||||
|
switch typ.(type) {
|
||||||
|
case *types.Struct:
|
||||||
c.emitLoadLocal(t.Name) // load the struct
|
c.emitLoadLocal(t.Name) // load the struct
|
||||||
c.emitLoadStructField(t.Name, n.Sel.Name) // load the field
|
c.emitLoadStructField(t.Name, n.Sel.Name) // load the field
|
||||||
|
default:
|
||||||
|
log.Fatal("non struct import selections not yet implemented, please use functions instead")
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
log.Fatal("nested selectors not supported yet")
|
log.Fatal("nested selectors not supported yet")
|
||||||
}
|
}
|
||||||
|
@ -398,7 +461,7 @@ func (c *codegen) convertStruct(lit *ast.CompositeLit) {
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Fatalf("the given literal is not of type struct: %v", lit)
|
log.Fatalf("the given literal is not of type struct: %v", lit)
|
||||||
}
|
}
|
||||||
strct := c.fctx.newStruct(t)
|
strct := c.scope.newStruct(t)
|
||||||
|
|
||||||
emitOpcode(c.prog, vm.Onop)
|
emitOpcode(c.prog, vm.Onop)
|
||||||
emitInt(c.prog, int64(strct.t.NumFields()))
|
emitInt(c.prog, int64(strct.t.NumFields()))
|
||||||
|
@ -472,84 +535,45 @@ func (c *codegen) newFunc(decl *ast.FuncDecl) *funcScope {
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Don't know if we really use this. Check if it can be deleted.
|
|
||||||
// If it's used once or twice, also remove this functions and
|
|
||||||
// call .Types direcly. e.g. c.typeInfo.Types[expr]
|
|
||||||
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.
|
// CodeGen is the function that compiles the program to bytecode.
|
||||||
func CodeGen(f *ast.File, tInfo *types.Info, imports map[string]*archive) (*bytes.Buffer, error) {
|
func CodeGen(info *buildInfo) (*bytes.Buffer, error) {
|
||||||
|
pkg := info.program.Package(info.initialPackage)
|
||||||
c := &codegen{
|
c := &codegen{
|
||||||
|
buildInfo: info,
|
||||||
prog: new(bytes.Buffer),
|
prog: new(bytes.Buffer),
|
||||||
l: []int{},
|
l: []int{},
|
||||||
funcs: map[string]*funcScope{},
|
funcs: map[string]*funcScope{},
|
||||||
typeInfo: tInfo,
|
typeInfo: &pkg.Info,
|
||||||
}
|
}
|
||||||
|
|
||||||
var main *ast.FuncDecl
|
// Resolve the entrypoint of the program
|
||||||
ast.Inspect(f, func(n ast.Node) bool {
|
main, mainFile := resolveEntryPoint(mainIdent, pkg)
|
||||||
switch t := n.(type) {
|
|
||||||
case *ast.FuncDecl:
|
|
||||||
if t.Name.Name == mainIdent {
|
|
||||||
main = t
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
if main == nil {
|
if main == nil {
|
||||||
log.Fatal("could not find func main. did you forgot to declare it?")
|
log.Fatal("could not find func main. did you forgot to declare it?")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bring all imported functions into scope
|
// Bring all imported functions into scope
|
||||||
for _, arch := range imports {
|
for _, pkg := range info.program.AllPackages {
|
||||||
c.resolveFuncDecls(arch.f)
|
for _, f := range pkg.Files {
|
||||||
|
c.resolveFuncDecls(f)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.resolveFuncDecls(f)
|
// convert the entry point first
|
||||||
c.convertFuncDecl(main)
|
c.convertFuncDecl(mainFile, main)
|
||||||
|
|
||||||
|
// Generate the code for the program
|
||||||
|
for _, pkg := range info.program.AllPackages {
|
||||||
|
c.typeInfo = &pkg.Info
|
||||||
|
for _, f := range pkg.Files {
|
||||||
for _, decl := range f.Decls {
|
for _, decl := range f.Decls {
|
||||||
switch n := decl.(type) {
|
switch n := decl.(type) {
|
||||||
case *ast.FuncDecl:
|
case *ast.FuncDecl:
|
||||||
if n.Name.Name != mainIdent {
|
if n.Name.Name != mainIdent {
|
||||||
c.convertFuncDecl(n)
|
c.convertFuncDecl(f, n)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate code for the imported packages.
|
|
||||||
for _, arch := range imports {
|
|
||||||
c.typeInfo = arch.typeInfo
|
|
||||||
for _, decl := range arch.f.Decls {
|
|
||||||
switch n := decl.(type) {
|
|
||||||
case *ast.FuncDecl:
|
|
||||||
if n.Name.Name != mainIdent {
|
|
||||||
c.convertFuncDecl(n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ import (
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/vm"
|
"github.com/CityOfZion/neo-go/pkg/vm"
|
||||||
|
"golang.org/x/tools/go/loader"
|
||||||
)
|
)
|
||||||
|
|
||||||
const fileExt = "avm"
|
const fileExt = "avm"
|
||||||
|
@ -35,36 +36,31 @@ type Options struct {
|
||||||
Debug bool
|
Debug bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type buildInfo struct {
|
||||||
|
initialPackage string
|
||||||
|
program *loader.Program
|
||||||
|
}
|
||||||
|
|
||||||
// Compile compiles a Go program into bytecode that can run on the NEO virtual machine.
|
// Compile compiles a Go program into bytecode that can run on the NEO virtual machine.
|
||||||
func Compile(input io.Reader, o *Options) ([]byte, error) {
|
func Compile(r io.Reader, o *Options) ([]byte, error) {
|
||||||
fset := token.NewFileSet()
|
conf := loader.Config{ParserMode: parser.ParseComments}
|
||||||
f, err := parser.ParseFile(fset, "", input, 0)
|
f, err := conf.ParseFile("", r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
conf.CreateFromFiles("", f)
|
||||||
|
|
||||||
|
prog, err := conf.Load()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
conf := types.Config{Importer: importer.Default()}
|
ctx := &buildInfo{
|
||||||
typeInfo := &types.Info{
|
initialPackage: f.Name.Name,
|
||||||
Types: make(map[ast.Expr]types.TypeAndValue),
|
program: prog,
|
||||||
Defs: make(map[*ast.Ident]types.Object),
|
|
||||||
Uses: make(map[*ast.Ident]types.Object),
|
|
||||||
Implicits: make(map[ast.Node]types.Object),
|
|
||||||
Selections: make(map[*ast.SelectorExpr]*types.Selection),
|
|
||||||
Scopes: make(map[ast.Node]*types.Scope),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Typechecker
|
buf, err := CodeGen(ctx)
|
||||||
_, err = conf.Check("", fset, []*ast.File{f}, typeInfo)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
imports, err := resolveImports(f)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
buf, err := CodeGen(f, typeInfo, imports)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,20 +6,20 @@ import (
|
||||||
"log"
|
"log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A funcScope represents a scope within the function context.
|
// A funcScope represents the scope within the function context.
|
||||||
// It holds al the local variables along with the initialized struct positions.
|
// It holds al the local variables along with the initialized struct positions.
|
||||||
type funcScope struct {
|
type funcScope struct {
|
||||||
// function identifier
|
// identifier of the function.
|
||||||
name string
|
name string
|
||||||
|
|
||||||
// The declaration of the function in the AST
|
// The declaration of the function in the AST. Nil if this scope is not a function.
|
||||||
decl *ast.FuncDecl
|
decl *ast.FuncDecl
|
||||||
|
|
||||||
// Program label of the function
|
// Program label of the scope
|
||||||
label int
|
label int
|
||||||
|
|
||||||
// Local scope of the function
|
// Local variables
|
||||||
scope map[string]int
|
locals map[string]int
|
||||||
|
|
||||||
// A mapping of structs positions with their scope
|
// A mapping of structs positions with their scope
|
||||||
structs map[int]*structScope
|
structs map[int]*structScope
|
||||||
|
@ -41,7 +41,7 @@ func newFuncScope(decl *ast.FuncDecl, label int) *funcScope {
|
||||||
name: decl.Name.Name,
|
name: decl.Name.Name,
|
||||||
decl: decl,
|
decl: decl,
|
||||||
label: label,
|
label: label,
|
||||||
scope: map[string]int{},
|
locals: map[string]int{},
|
||||||
structs: map[int]*structScope{},
|
structs: map[int]*structScope{},
|
||||||
voidCalls: map[*ast.CallExpr]bool{},
|
voidCalls: map[*ast.CallExpr]bool{},
|
||||||
i: -1,
|
i: -1,
|
||||||
|
@ -64,8 +64,11 @@ func (c *funcScope) analyzeVoidCalls(node ast.Node) bool {
|
||||||
case *ast.CallExpr:
|
case *ast.CallExpr:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
case *ast.BinaryExpr:
|
||||||
|
return false
|
||||||
case *ast.CallExpr:
|
case *ast.CallExpr:
|
||||||
c.voidCalls[n] = true
|
c.voidCalls[n] = true
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -98,7 +101,7 @@ func (c *funcScope) stackSize() int64 {
|
||||||
|
|
||||||
func (c *funcScope) newStruct(t *types.Struct) *structScope {
|
func (c *funcScope) newStruct(t *types.Struct) *structScope {
|
||||||
strct := newStructScope(t)
|
strct := newStructScope(t)
|
||||||
c.structs[len(c.scope)] = strct
|
c.structs[len(c.locals)] = strct
|
||||||
return strct
|
return strct
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,13 +117,13 @@ func (c *funcScope) loadStruct(name string) *structScope {
|
||||||
// newLocal creates a new local variable into the scope of the function.
|
// newLocal creates a new local variable into the scope of the function.
|
||||||
func (c *funcScope) newLocal(name string) int {
|
func (c *funcScope) newLocal(name string) int {
|
||||||
c.i++
|
c.i++
|
||||||
c.scope[name] = c.i
|
c.locals[name] = c.i
|
||||||
return c.i
|
return c.i
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadLocal loads the position of a local variable inside the scope of the function.
|
// loadLocal loads the position of a local variable inside the scope of the function.
|
||||||
func (c *funcScope) loadLocal(name string) int {
|
func (c *funcScope) loadLocal(name string) int {
|
||||||
i, ok := c.scope[name]
|
i, ok := c.locals[name]
|
||||||
if !ok {
|
if !ok {
|
||||||
// should emit a compiler warning.
|
// should emit a compiler warning.
|
||||||
return c.newLocal(name)
|
return c.newLocal(name)
|
||||||
|
|
|
@ -32,14 +32,11 @@ func TestAllCases(t *testing.T) {
|
||||||
testCases = append(testCases, structTestCases...)
|
testCases = append(testCases, structTestCases...)
|
||||||
testCases = append(testCases, ifStatementTestCases...)
|
testCases = append(testCases, ifStatementTestCases...)
|
||||||
testCases = append(testCases, customTypeTestCases...)
|
testCases = append(testCases, customTypeTestCases...)
|
||||||
|
testCases = append(testCases, constantTestCases...)
|
||||||
// TODO: issue #28
|
testCases = append(testCases, importTestCases...)
|
||||||
// These tests are passing locally, but circleci is failing to resolve the dependency.
|
|
||||||
// https://github.com/CityOfZion/neo-go/issues/28
|
|
||||||
// testCases = append(testCases, importTestCases...)
|
|
||||||
|
|
||||||
// Blockchain specific
|
// Blockchain specific
|
||||||
// testCases = append(testCases, storageTestCases...)
|
testCases = append(testCases, storageTestCases...)
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
b, err := compiler.Compile(strings.NewReader(tc.src), &compiler.Options{})
|
b, err := compiler.Compile(strings.NewReader(tc.src), &compiler.Options{})
|
||||||
|
@ -54,8 +51,7 @@ func TestAllCases(t *testing.T) {
|
||||||
|
|
||||||
if bytes.Compare(b, expectedResult) != 0 {
|
if bytes.Compare(b, expectedResult) != 0 {
|
||||||
t.Log(hex.EncodeToString(b))
|
t.Log(hex.EncodeToString(b))
|
||||||
want, _ := hex.DecodeString(tc.result)
|
dumpOpCodeSideBySide(b, expectedResult)
|
||||||
dumpOpCodeSideBySide(b, want)
|
|
||||||
t.Fatalf("compiling %s failed", tc.name)
|
t.Fatalf("compiling %s failed", tc.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
64
pkg/vm/compiler/tests/constant_test.go
Normal file
64
pkg/vm/compiler/tests/constant_test.go
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
package compiler
|
||||||
|
|
||||||
|
var constantTestCases = []testCase{
|
||||||
|
{
|
||||||
|
"basic constant",
|
||||||
|
`
|
||||||
|
package foo
|
||||||
|
|
||||||
|
const x = 10
|
||||||
|
|
||||||
|
func Main() int {
|
||||||
|
return x + 2
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
// This ouput wil not be the same als the boa compiler.
|
||||||
|
// The go compiler will automatically resolve binary expressions
|
||||||
|
// involving constants.
|
||||||
|
// x + 2 in this case will be resolved to 12.
|
||||||
|
"52c56b5a6c766b00527ac46203005c616c7566",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"shorthand multi const",
|
||||||
|
`
|
||||||
|
package foo
|
||||||
|
|
||||||
|
const (
|
||||||
|
z = 3
|
||||||
|
y = 2
|
||||||
|
x = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
// should load al 3 constants in the Main.
|
||||||
|
func Main() int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
"54c56b536c766b00527ac4526c766b51527ac4516c766b52527ac462030000616c7566",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"globals with function arguments",
|
||||||
|
`
|
||||||
|
package foobar
|
||||||
|
|
||||||
|
const (
|
||||||
|
bar = "FOO"
|
||||||
|
foo = "BAR"
|
||||||
|
)
|
||||||
|
|
||||||
|
func something(x int) string {
|
||||||
|
if x > 10 {
|
||||||
|
return bar
|
||||||
|
}
|
||||||
|
return foo
|
||||||
|
}
|
||||||
|
|
||||||
|
func Main() string {
|
||||||
|
trigger := 100
|
||||||
|
x := something(trigger)
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
"55c56b03464f4f6c766b00527ac4034241526c766b51527ac401646c766b52527ac46c766b52c3616516006c766b53527ac46203006c766b53c3616c756656c56b6c766b00527ac403464f4f6c766b51527ac4034241526c766b52527ac46c766b00c35aa0640f006203006c766b51c3616c75666203006c766b52c3616c7566",
|
||||||
|
},
|
||||||
|
}
|
|
@ -6,7 +6,7 @@ var storageTestCases = []testCase{
|
||||||
`
|
`
|
||||||
package foo
|
package foo
|
||||||
|
|
||||||
import "github.com/CityOfZion/neo-go/pkg/vm/smartcontract/storage"
|
import "github.com/CityOfZion/neo-go/pkg/smartcontract/storage"
|
||||||
|
|
||||||
func Main() int {
|
func Main() int {
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
package runtime
|
|
||||||
|
|
||||||
// TriggerType represents a byte.
|
|
||||||
type TriggerType byte
|
|
||||||
|
|
||||||
// List of valid trigger types.
|
|
||||||
const (
|
|
||||||
Verification TriggerType = 0x00
|
|
||||||
Application TriggerType = 0x10
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetTrigger return the current trigger type. The return in this function
|
|
||||||
// doesn't really mather, this is just an interop placeholder.
|
|
||||||
func GetTrigger() TriggerType { return 0x00 }
|
|
|
@ -9,4 +9,12 @@ var Syscalls = map[string]string{
|
||||||
"GetInt": "Neo.Storage.Get",
|
"GetInt": "Neo.Storage.Get",
|
||||||
"GetString": "Neo.Storage.Get",
|
"GetString": "Neo.Storage.Get",
|
||||||
"Delete": "Neo.Storage.Delete",
|
"Delete": "Neo.Storage.Delete",
|
||||||
|
|
||||||
|
// Runtime
|
||||||
|
"GetTrigger": "Neo.Runtime.GetTrigger",
|
||||||
|
"CheckWitness": "Neo.Runtime.CheckWitness",
|
||||||
|
"GetCurrentBlock": "Neo.Runtime.GetCurrentBlock",
|
||||||
|
"GetTime": "Neo.Runtime.GetTime",
|
||||||
|
"Notify": "Neo.runtime.Notify",
|
||||||
|
"Log": "Neo.Runtime.Log",
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue