forked from TrueCloudLab/neoneo-go
Merge pull request #1240 from nspcc-dev/fix/compiler_globals
Allow to use exported variables and constants
This commit is contained in:
commit
87119da2f7
13 changed files with 314 additions and 123 deletions
|
@ -38,14 +38,13 @@ func (t Token) GetSupply(ctx storage.Context) interface{} {
|
||||||
return getIntFromDB(ctx, []byte(t.CirculationKey))
|
return getIntFromDB(ctx, []byte(t.CirculationKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TBalanceOf gets the token balance of a specific address
|
// BalanceOf gets the token balance of a specific address
|
||||||
// TODO: https://github.com/nspcc-dev/neo-go/issues/1150
|
func (t Token) BalanceOf(ctx storage.Context, holder []byte) interface{} {
|
||||||
func (t Token) TBalanceOf(ctx storage.Context, holder []byte) interface{} {
|
|
||||||
return getIntFromDB(ctx, holder)
|
return getIntFromDB(ctx, holder)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TTransfer token from one user to another
|
// Transfer token from one user to another
|
||||||
func (t Token) TTransfer(ctx storage.Context, from []byte, to []byte, amount int) bool {
|
func (t Token) Transfer(ctx storage.Context, from []byte, to []byte, amount int) bool {
|
||||||
amountFrom := t.CanTransfer(ctx, from, to, amount)
|
amountFrom := t.CanTransfer(ctx, from, to, amount)
|
||||||
if amountFrom == -1 {
|
if amountFrom == -1 {
|
||||||
return false
|
return false
|
||||||
|
@ -105,8 +104,8 @@ func IsUsableAddress(addr []byte) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// TMint initial supply of tokens.
|
// Mint initial supply of tokens.
|
||||||
func (t Token) TMint(ctx storage.Context, to []byte) bool {
|
func (t Token) Mint(ctx storage.Context, to []byte) bool {
|
||||||
if !IsUsableAddress(t.Owner) {
|
if !IsUsableAddress(t.Owner) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,19 +99,19 @@ func TotalSupply() interface{} {
|
||||||
func BalanceOf(holder []byte) interface{} {
|
func BalanceOf(holder []byte) interface{} {
|
||||||
t := createToken()
|
t := createToken()
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
return t.TBalanceOf(ctx, holder)
|
return t.BalanceOf(ctx, holder)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transfer token from one user to another
|
// Transfer token from one user to another
|
||||||
func Transfer(from []byte, to []byte, amount int) bool {
|
func Transfer(from []byte, to []byte, amount int) bool {
|
||||||
t := createToken()
|
t := createToken()
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
return t.TTransfer(ctx, from, to, amount)
|
return t.Transfer(ctx, from, to, amount)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mint initial supply of tokens
|
// Mint initial supply of tokens
|
||||||
func Mint(to []byte) bool {
|
func Mint(to []byte) bool {
|
||||||
t := createToken()
|
t := createToken()
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
return t.TMint(ctx, to)
|
return t.Mint(ctx, to)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
"golang.org/x/tools/go/loader"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -22,26 +21,33 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// newGlobal creates new global variable.
|
// newGlobal creates new global variable.
|
||||||
func (c *codegen) newGlobal(name string) {
|
func (c *codegen) newGlobal(pkg string, name string) {
|
||||||
|
name = c.getIdentName(pkg, name)
|
||||||
c.globals[name] = len(c.globals)
|
c.globals[name] = len(c.globals)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getIdentName returns fully-qualified name for a variable.
|
||||||
|
func (c *codegen) getIdentName(pkg string, name string) string {
|
||||||
|
if fullName, ok := c.importMap[pkg]; ok {
|
||||||
|
pkg = fullName
|
||||||
|
}
|
||||||
|
return pkg + "." + name
|
||||||
|
}
|
||||||
|
|
||||||
// traverseGlobals visits and initializes global variables.
|
// traverseGlobals visits and initializes global variables.
|
||||||
// and returns number of variables initialized.
|
// and returns number of variables initialized.
|
||||||
func (c *codegen) traverseGlobals(fs ...*ast.File) int {
|
func (c *codegen) traverseGlobals() int {
|
||||||
var n int
|
var n int
|
||||||
for _, f := range fs {
|
c.ForEachFile(func(f *ast.File, _ *types.Package) {
|
||||||
n += countGlobals(f)
|
n += countGlobals(f)
|
||||||
}
|
})
|
||||||
if n != 0 {
|
if n != 0 {
|
||||||
if n > 255 {
|
if n > 255 {
|
||||||
c.prog.BinWriter.Err = errors.New("too many global variables")
|
c.prog.BinWriter.Err = errors.New("too many global variables")
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
emit.Instruction(c.prog.BinWriter, opcode.INITSSLOT, []byte{byte(n)})
|
emit.Instruction(c.prog.BinWriter, opcode.INITSSLOT, []byte{byte(n)})
|
||||||
for _, f := range fs {
|
c.ForEachFile(c.convertGlobals)
|
||||||
c.convertGlobals(f)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
@ -70,28 +76,6 @@ func isExprNil(e ast.Expr) bool {
|
||||||
return ok && v.Name == "nil"
|
return ok && v.Name == "nil"
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// indexOfStruct returns the index of the given field inside that struct.
|
// indexOfStruct returns the index of the given field inside that struct.
|
||||||
// If the struct does not contain that field it will return -1.
|
// If the struct does not contain that field it will return -1.
|
||||||
func indexOfStruct(strct *types.Struct, fldName string) int {
|
func indexOfStruct(strct *types.Struct, fldName string) int {
|
||||||
|
@ -119,31 +103,30 @@ func lastStmtIsReturn(decl *ast.FuncDecl) (b bool) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func analyzeFuncUsage(mainPkg *loader.PackageInfo, pkgs map[*types.Package]*loader.PackageInfo) funcUsage {
|
func (c *codegen) analyzeFuncUsage() funcUsage {
|
||||||
usage := funcUsage{}
|
usage := funcUsage{}
|
||||||
|
|
||||||
for _, pkg := range pkgs {
|
c.ForEachFile(func(f *ast.File, pkg *types.Package) {
|
||||||
isMain := pkg == mainPkg
|
isMain := pkg == c.mainPkg.Pkg
|
||||||
for _, f := range pkg.Files {
|
|
||||||
ast.Inspect(f, func(node ast.Node) bool {
|
ast.Inspect(f, func(node ast.Node) bool {
|
||||||
switch n := node.(type) {
|
switch n := node.(type) {
|
||||||
case *ast.CallExpr:
|
case *ast.CallExpr:
|
||||||
switch t := n.Fun.(type) {
|
switch t := n.Fun.(type) {
|
||||||
case *ast.Ident:
|
case *ast.Ident:
|
||||||
usage[t.Name] = true
|
usage[c.getIdentName("", t.Name)] = true
|
||||||
case *ast.SelectorExpr:
|
case *ast.SelectorExpr:
|
||||||
usage[t.Sel.Name] = true
|
name, _ := c.getFuncNameFromSelector(t)
|
||||||
|
usage[name] = true
|
||||||
}
|
}
|
||||||
case *ast.FuncDecl:
|
case *ast.FuncDecl:
|
||||||
// exported functions are always assumed to be used
|
// exported functions are always assumed to be used
|
||||||
if isMain && n.Name.IsExported() {
|
if isMain && n.Name.IsExported() {
|
||||||
usage[n.Name.Name] = true
|
usage[c.getFuncNameFromDecl(pkg.Path(), n)] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
}
|
})
|
||||||
}
|
|
||||||
return usage
|
return usage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -62,6 +62,15 @@ type codegen struct {
|
||||||
// initEndOffset specifies the end of the initialization method.
|
// initEndOffset specifies the end of the initialization method.
|
||||||
initEndOffset int
|
initEndOffset int
|
||||||
|
|
||||||
|
// importMap contains mapping from package aliases to full package names for the current file.
|
||||||
|
importMap map[string]string
|
||||||
|
|
||||||
|
// constMap contains constants from foreign packages.
|
||||||
|
constMap map[string]types.TypeAndValue
|
||||||
|
|
||||||
|
// currPkg is current package being processed.
|
||||||
|
currPkg *types.Package
|
||||||
|
|
||||||
// mainPkg is a main package metadata.
|
// mainPkg is a main package metadata.
|
||||||
mainPkg *loader.PackageInfo
|
mainPkg *loader.PackageInfo
|
||||||
|
|
||||||
|
@ -167,14 +176,16 @@ func (c *codegen) emitStoreStructField(i int) {
|
||||||
|
|
||||||
// getVarIndex returns variable type and position in corresponding slot,
|
// getVarIndex returns variable type and position in corresponding slot,
|
||||||
// according to current scope.
|
// according to current scope.
|
||||||
func (c *codegen) getVarIndex(name string) (varType, int) {
|
func (c *codegen) getVarIndex(pkg string, name string) (varType, int) {
|
||||||
|
if pkg == "" {
|
||||||
if c.scope != nil {
|
if c.scope != nil {
|
||||||
vt, val := c.scope.vars.getVarIndex(name)
|
vt, val := c.scope.vars.getVarIndex(name)
|
||||||
if val >= 0 {
|
if val >= 0 {
|
||||||
return vt, val
|
return vt, val
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if i, ok := c.globals[name]; ok {
|
}
|
||||||
|
if i, ok := c.globals[c.getIdentName(pkg, name)]; ok {
|
||||||
return varGlobal, i
|
return varGlobal, i
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,8 +206,8 @@ func getBaseOpcode(t varType) (opcode.Opcode, opcode.Opcode) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// emitLoadVar loads specified variable to the evaluation stack.
|
// emitLoadVar loads specified variable to the evaluation stack.
|
||||||
func (c *codegen) emitLoadVar(name string) {
|
func (c *codegen) emitLoadVar(pkg string, name string) {
|
||||||
t, i := c.getVarIndex(name)
|
t, i := c.getVarIndex(pkg, name)
|
||||||
base, _ := getBaseOpcode(t)
|
base, _ := getBaseOpcode(t)
|
||||||
if i < 7 {
|
if i < 7 {
|
||||||
emit.Opcode(c.prog.BinWriter, base+opcode.Opcode(i))
|
emit.Opcode(c.prog.BinWriter, base+opcode.Opcode(i))
|
||||||
|
@ -206,12 +217,12 @@ func (c *codegen) emitLoadVar(name string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// emitStoreVar stores top value from the evaluation stack in the specified variable.
|
// emitStoreVar stores top value from the evaluation stack in the specified variable.
|
||||||
func (c *codegen) emitStoreVar(name string) {
|
func (c *codegen) emitStoreVar(pkg string, name string) {
|
||||||
if name == "_" {
|
if name == "_" {
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.DROP)
|
emit.Opcode(c.prog.BinWriter, opcode.DROP)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
t, i := c.getVarIndex(name)
|
t, i := c.getVarIndex(pkg, name)
|
||||||
_, base := getBaseOpcode(t)
|
_, base := getBaseOpcode(t)
|
||||||
if i < 7 {
|
if i < 7 {
|
||||||
emit.Opcode(c.prog.BinWriter, base+opcode.Opcode(i))
|
emit.Opcode(c.prog.BinWriter, base+opcode.Opcode(i))
|
||||||
|
@ -252,18 +263,14 @@ func (c *codegen) emitDefault(t types.Type) {
|
||||||
// convertGlobals traverses the AST and only converts global declarations.
|
// convertGlobals traverses the AST and only converts global declarations.
|
||||||
// If we call this in convertFuncDecl then it will load all global variables
|
// If we call this in convertFuncDecl then it will load all global variables
|
||||||
// into the scope of the function.
|
// into the scope of the function.
|
||||||
func (c *codegen) convertGlobals(f ast.Node) {
|
func (c *codegen) convertGlobals(f *ast.File, _ *types.Package) {
|
||||||
ast.Inspect(f, func(node ast.Node) bool {
|
ast.Inspect(f, func(node ast.Node) bool {
|
||||||
switch n := node.(type) {
|
switch n := node.(type) {
|
||||||
case *ast.FuncDecl:
|
case *ast.FuncDecl:
|
||||||
return false
|
return false
|
||||||
case *ast.GenDecl:
|
case *ast.GenDecl:
|
||||||
// constants are loaded directly so there is no need
|
|
||||||
// to store them as a local variables
|
|
||||||
if n.Tok != token.CONST {
|
|
||||||
ast.Walk(c, n)
|
ast.Walk(c, n)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -274,19 +281,18 @@ func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl, pkg *types.
|
||||||
ok, isLambda bool
|
ok, isLambda bool
|
||||||
)
|
)
|
||||||
|
|
||||||
f, ok = c.funcs[decl.Name.Name]
|
f, ok = c.funcs[c.getFuncNameFromDecl("", decl)]
|
||||||
if ok {
|
if ok {
|
||||||
// If this function is a syscall or builtin we will not convert it to bytecode.
|
// If this function is a syscall or builtin we will not convert it to bytecode.
|
||||||
if isSyscall(f) || isCustomBuiltin(f) {
|
if isSyscall(f) || isCustomBuiltin(f) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.setLabel(f.label)
|
c.setLabel(f.label)
|
||||||
} else if f, ok = c.lambda[decl.Name.Name]; ok {
|
} else if f, ok = c.lambda[c.getIdentName("", decl.Name.Name)]; ok {
|
||||||
isLambda = ok
|
isLambda = ok
|
||||||
c.setLabel(f.label)
|
c.setLabel(f.label)
|
||||||
} else {
|
} else {
|
||||||
f = c.newFunc(decl)
|
f = c.newFunc(decl)
|
||||||
f.pkg = pkg
|
|
||||||
}
|
}
|
||||||
|
|
||||||
f.rng.Start = uint16(c.prog.Len())
|
f.rng.Start = uint16(c.prog.Len())
|
||||||
|
@ -362,13 +368,22 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
// x = 2
|
// x = 2
|
||||||
// )
|
// )
|
||||||
case *ast.GenDecl:
|
case *ast.GenDecl:
|
||||||
|
if n.Tok == token.CONST {
|
||||||
|
for _, spec := range n.Specs {
|
||||||
|
vs := spec.(*ast.ValueSpec)
|
||||||
|
for i := range vs.Names {
|
||||||
|
c.constMap[c.getIdentName("", vs.Names[i].Name)] = c.typeAndValueOf(vs.Values[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
for _, spec := range n.Specs {
|
for _, spec := range n.Specs {
|
||||||
switch t := spec.(type) {
|
switch t := spec.(type) {
|
||||||
case *ast.ValueSpec:
|
case *ast.ValueSpec:
|
||||||
for _, id := range t.Names {
|
for _, id := range t.Names {
|
||||||
if c.scope == nil {
|
if c.scope == nil {
|
||||||
// it is a global declaration
|
// it is a global declaration
|
||||||
c.newGlobal(id.Name)
|
c.newGlobal("", id.Name)
|
||||||
} else {
|
} else {
|
||||||
c.scope.newLocal(id.Name)
|
c.scope.newLocal(id.Name)
|
||||||
}
|
}
|
||||||
|
@ -380,7 +395,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
} else {
|
} else {
|
||||||
c.emitDefault(c.typeOf(t.Type))
|
c.emitDefault(c.typeOf(t.Type))
|
||||||
}
|
}
|
||||||
c.emitStoreVar(t.Names[i].Name)
|
c.emitStoreVar("", t.Names[i].Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -411,13 +426,19 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
if !isAssignOp && (i == 0 || !multiRet) {
|
if !isAssignOp && (i == 0 || !multiRet) {
|
||||||
ast.Walk(c, n.Rhs[i])
|
ast.Walk(c, n.Rhs[i])
|
||||||
}
|
}
|
||||||
c.emitStoreVar(t.Name)
|
c.emitStoreVar("", t.Name)
|
||||||
|
|
||||||
case *ast.SelectorExpr:
|
case *ast.SelectorExpr:
|
||||||
if !isAssignOp {
|
if !isAssignOp {
|
||||||
ast.Walk(c, n.Rhs[i])
|
ast.Walk(c, n.Rhs[i])
|
||||||
}
|
}
|
||||||
strct, ok := c.typeOf(t.X).Underlying().(*types.Struct)
|
typ := c.typeOf(t.X)
|
||||||
|
if typ == nil {
|
||||||
|
// Store to other package global variable.
|
||||||
|
c.emitStoreVar(t.X.(*ast.Ident).Name, t.Sel.Name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
strct, ok := typ.Underlying().(*types.Struct)
|
||||||
if !ok {
|
if !ok {
|
||||||
c.prog.Err = fmt.Errorf("nested selector assigns not supported yet")
|
c.prog.Err = fmt.Errorf("nested selector assigns not supported yet")
|
||||||
return nil
|
return nil
|
||||||
|
@ -442,7 +463,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
|
|
||||||
case *ast.SliceExpr:
|
case *ast.SliceExpr:
|
||||||
name := n.X.(*ast.Ident).Name
|
name := n.X.(*ast.Ident).Name
|
||||||
c.emitLoadVar(name)
|
c.emitLoadVar("", name)
|
||||||
|
|
||||||
if n.Low != nil {
|
if n.Low != nil {
|
||||||
ast.Walk(c, n.Low)
|
ast.Walk(c, n.Low)
|
||||||
|
@ -480,7 +501,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
for i := len(results.List) - 1; i >= 0; i-- {
|
for i := len(results.List) - 1; i >= 0; i-- {
|
||||||
names := results.List[i].Names
|
names := results.List[i].Names
|
||||||
for j := len(names) - 1; j >= 0; j-- {
|
for j := len(names) - 1; j >= 0; j-- {
|
||||||
c.emitLoadVar(names[j].Name)
|
c.emitLoadVar("", names[j].Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -595,7 +616,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
} else if n.Name == "nil" {
|
} else if n.Name == "nil" {
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.PUSHNULL)
|
emit.Opcode(c.prog.BinWriter, opcode.PUSHNULL)
|
||||||
} else {
|
} else {
|
||||||
c.emitLoadVar(n.Name)
|
c.emitLoadVar("", n.Name)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
|
@ -719,7 +740,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
|
|
||||||
switch fun := n.Fun.(type) {
|
switch fun := n.Fun.(type) {
|
||||||
case *ast.Ident:
|
case *ast.Ident:
|
||||||
f, ok = c.funcs[fun.Name]
|
f, ok = c.funcs[c.getIdentName("", fun.Name)]
|
||||||
isBuiltin = isGoBuiltin(fun.Name)
|
isBuiltin = isGoBuiltin(fun.Name)
|
||||||
if !ok && !isBuiltin {
|
if !ok && !isBuiltin {
|
||||||
name = fun.Name
|
name = fun.Name
|
||||||
|
@ -732,13 +753,14 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
// If this is a method call we need to walk the AST to load the struct locally.
|
// If this is a method call we need to walk the AST to load the struct locally.
|
||||||
// Otherwise this is a function call from a imported package and we can call it
|
// Otherwise this is a function call from a imported package and we can call it
|
||||||
// directly.
|
// directly.
|
||||||
if c.typeInfo.Selections[fun] != nil {
|
name, isMethod := c.getFuncNameFromSelector(fun)
|
||||||
|
if isMethod {
|
||||||
ast.Walk(c, fun.X)
|
ast.Walk(c, fun.X)
|
||||||
// Dont forget to add 1 extra argument when its a method.
|
// Dont forget to add 1 extra argument when its a method.
|
||||||
numArgs++
|
numArgs++
|
||||||
}
|
}
|
||||||
|
|
||||||
f, ok = c.funcs[fun.Sel.Name]
|
f, ok = c.funcs[name]
|
||||||
// @FIXME this could cause runtime errors.
|
// @FIXME this could cause runtime errors.
|
||||||
f.selector = fun.X.(*ast.Ident)
|
f.selector = fun.X.(*ast.Ident)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -789,7 +811,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
if isString(c.typeOf(n.Fun)) {
|
if isString(c.typeOf(n.Fun)) {
|
||||||
c.emitConvert(stackitem.ByteArrayT)
|
c.emitConvert(stackitem.ByteArrayT)
|
||||||
} else if isFunc {
|
} else if isFunc {
|
||||||
c.emitLoadVar(name)
|
c.emitLoadVar("", name)
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.CALLA)
|
emit.Opcode(c.prog.BinWriter, opcode.CALLA)
|
||||||
}
|
}
|
||||||
case isSyscall(f):
|
case isSyscall(f):
|
||||||
|
@ -801,7 +823,19 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
case *ast.SelectorExpr:
|
case *ast.SelectorExpr:
|
||||||
strct, ok := c.typeOf(n.X).Underlying().(*types.Struct)
|
typ := c.typeOf(n.X)
|
||||||
|
if typ == nil {
|
||||||
|
// This is a global variable from a package.
|
||||||
|
pkgAlias := n.X.(*ast.Ident).Name
|
||||||
|
name := c.getIdentName(pkgAlias, n.Sel.Name)
|
||||||
|
if tv, ok := c.constMap[name]; ok {
|
||||||
|
c.emitLoadConst(tv)
|
||||||
|
} else {
|
||||||
|
c.emitLoadVar(pkgAlias, n.Sel.Name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
strct, ok := typ.Underlying().(*types.Struct)
|
||||||
if !ok {
|
if !ok {
|
||||||
c.prog.Err = fmt.Errorf("selectors are supported only on structs")
|
c.prog.Err = fmt.Errorf("selectors are supported only on structs")
|
||||||
return nil
|
return nil
|
||||||
|
@ -840,7 +874,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
// for i := 0; i < 10; i++ {}
|
// for i := 0; i < 10; i++ {}
|
||||||
// Where the post stmt is ( i++ )
|
// Where the post stmt is ( i++ )
|
||||||
if ident, ok := n.X.(*ast.Ident); ok {
|
if ident, ok := n.X.(*ast.Ident); ok {
|
||||||
c.emitStoreVar(ident.Name)
|
c.emitStoreVar("", ident.Name)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
|
@ -992,7 +1026,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
} else {
|
} else {
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.DUP)
|
emit.Opcode(c.prog.BinWriter, opcode.DUP)
|
||||||
}
|
}
|
||||||
c.emitStoreVar(n.Key.(*ast.Ident).Name)
|
c.emitStoreVar("", n.Key.(*ast.Ident).Name)
|
||||||
}
|
}
|
||||||
if needValue {
|
if needValue {
|
||||||
if !isMap || !keyLoaded {
|
if !isMap || !keyLoaded {
|
||||||
|
@ -1005,7 +1039,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.SWAP) // key should be on top
|
emit.Opcode(c.prog.BinWriter, opcode.SWAP) // key should be on top
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.PICKITEM)
|
emit.Opcode(c.prog.BinWriter, opcode.PICKITEM)
|
||||||
}
|
}
|
||||||
c.emitStoreVar(n.Value.(*ast.Ident).Name)
|
c.emitStoreVar("", n.Value.(*ast.Ident).Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
ast.Walk(c, n.Body)
|
ast.Walk(c, n.Body)
|
||||||
|
@ -1400,32 +1434,40 @@ func (c *codegen) convertToken(tok token.Token) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *codegen) newFunc(decl *ast.FuncDecl) *funcScope {
|
func (c *codegen) newFunc(decl *ast.FuncDecl) *funcScope {
|
||||||
f := newFuncScope(decl, c.newLabel())
|
f := c.newFuncScope(decl, c.newLabel())
|
||||||
c.funcs[f.name] = f
|
c.funcs[c.getFuncNameFromDecl("", decl)] = f
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getFuncNameFromSelector returns fully-qualified function name from the selector expression.
|
||||||
|
// Second return value is true iff this was a method call, not foreign package call.
|
||||||
|
func (c *codegen) getFuncNameFromSelector(e *ast.SelectorExpr) (string, bool) {
|
||||||
|
ident := e.X.(*ast.Ident)
|
||||||
|
if c.typeInfo.Selections[e] != nil {
|
||||||
|
typ := c.typeInfo.Types[ident].Type.String()
|
||||||
|
return c.getIdentName(typ, e.Sel.Name), true
|
||||||
|
}
|
||||||
|
return c.getIdentName(ident.Name, e.Sel.Name), false
|
||||||
|
}
|
||||||
|
|
||||||
func (c *codegen) newLambda(u uint16, lit *ast.FuncLit) {
|
func (c *codegen) newLambda(u uint16, lit *ast.FuncLit) {
|
||||||
name := fmt.Sprintf("lambda@%d", u)
|
name := fmt.Sprintf("lambda@%d", u)
|
||||||
c.lambda[name] = newFuncScope(&ast.FuncDecl{
|
f := c.newFuncScope(&ast.FuncDecl{
|
||||||
Name: ast.NewIdent(name),
|
Name: ast.NewIdent(name),
|
||||||
Type: lit.Type,
|
Type: lit.Type,
|
||||||
Body: lit.Body,
|
Body: lit.Body,
|
||||||
}, u)
|
}, u)
|
||||||
|
c.lambda[c.getFuncNameFromDecl("", f.decl)] = f
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *codegen) compile(info *buildInfo, pkg *loader.PackageInfo) error {
|
func (c *codegen) compile(info *buildInfo, pkg *loader.PackageInfo) error {
|
||||||
funUsage := analyzeFuncUsage(pkg, info.program.AllPackages)
|
c.mainPkg = pkg
|
||||||
|
funUsage := c.analyzeFuncUsage()
|
||||||
|
|
||||||
// Bring all imported functions into scope.
|
// Bring all imported functions into scope.
|
||||||
for _, pkg := range info.program.AllPackages {
|
c.ForEachFile(c.resolveFuncDecls)
|
||||||
for _, f := range pkg.Files {
|
|
||||||
c.resolveFuncDecls(f, pkg.Pkg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.mainPkg = pkg
|
n := c.traverseGlobals()
|
||||||
n := c.traverseGlobals(pkg.Files...)
|
|
||||||
if n > 0 {
|
if n > 0 {
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.RET)
|
emit.Opcode(c.prog.BinWriter, opcode.RET)
|
||||||
c.initEndOffset = c.prog.Len()
|
c.initEndOffset = c.prog.Len()
|
||||||
|
@ -1439,23 +1481,19 @@ func (c *codegen) compile(info *buildInfo, pkg *loader.PackageInfo) error {
|
||||||
sort.Slice(keys, func(i, j int) bool { return keys[i].Path() < keys[j].Path() })
|
sort.Slice(keys, func(i, j int) bool { return keys[i].Path() < keys[j].Path() })
|
||||||
|
|
||||||
// Generate the code for the program.
|
// Generate the code for the program.
|
||||||
for _, k := range keys {
|
c.ForEachFile(func(f *ast.File, pkg *types.Package) {
|
||||||
pkg := info.program.AllPackages[k]
|
|
||||||
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:
|
||||||
// Don't convert the function if it's not used. This will save a lot
|
// Don't convert the function if it's not used. This will save a lot
|
||||||
// of bytecode space.
|
// of bytecode space.
|
||||||
if funUsage.funcUsed(n.Name.Name) {
|
name := c.getFuncNameFromDecl(pkg.Path(), n)
|
||||||
c.convertFuncDecl(f, n, k)
|
if funUsage.funcUsed(name) && !isInteropPath(pkg.Path()) {
|
||||||
}
|
c.convertFuncDecl(f, n, pkg)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return c.prog.Err
|
return c.prog.Err
|
||||||
}
|
}
|
||||||
|
@ -1470,6 +1508,7 @@ func newCodegen(info *buildInfo, pkg *loader.PackageInfo) *codegen {
|
||||||
globals: map[string]int{},
|
globals: map[string]int{},
|
||||||
labels: map[labelWithType]uint16{},
|
labels: map[labelWithType]uint16{},
|
||||||
typeInfo: &pkg.Info,
|
typeInfo: &pkg.Info,
|
||||||
|
constMap: map[string]types.TypeAndValue{},
|
||||||
|
|
||||||
sequencePoints: make(map[string][]DebugSeqPoint),
|
sequencePoints: make(map[string][]DebugSeqPoint),
|
||||||
}
|
}
|
||||||
|
@ -1496,7 +1535,6 @@ func (c *codegen) resolveFuncDecls(f *ast.File, pkg *types.Package) {
|
||||||
switch n := decl.(type) {
|
switch n := decl.(type) {
|
||||||
case *ast.FuncDecl:
|
case *ast.FuncDecl:
|
||||||
c.newFunc(n)
|
c.newFunc(n)
|
||||||
c.funcs[n.Name.Name].pkg = pkg
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,9 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
"go/parser"
|
"go/parser"
|
||||||
|
"go/types"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
@ -42,6 +44,35 @@ type buildInfo struct {
|
||||||
program *loader.Program
|
program *loader.Program
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ForEachFile executes fn on each file used in current program.
|
||||||
|
func (c *codegen) ForEachFile(fn func(*ast.File, *types.Package)) {
|
||||||
|
for _, pkg := range c.buildInfo.program.AllPackages {
|
||||||
|
c.typeInfo = &pkg.Info
|
||||||
|
c.currPkg = pkg.Pkg
|
||||||
|
for _, f := range pkg.Files {
|
||||||
|
c.fillImportMap(f, pkg.Pkg)
|
||||||
|
fn(f, pkg.Pkg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fillImportMap fills import map for f.
|
||||||
|
func (c *codegen) fillImportMap(f *ast.File, pkg *types.Package) {
|
||||||
|
c.importMap = map[string]string{"": pkg.Path()}
|
||||||
|
for _, imp := range f.Imports {
|
||||||
|
// We need to load find package metadata because
|
||||||
|
// name specified in `package ...` decl, can be in
|
||||||
|
// conflict with package path.
|
||||||
|
pkgPath := strings.Trim(imp.Path.Value, `"`)
|
||||||
|
realPkg := c.buildInfo.program.Package(pkgPath)
|
||||||
|
name := realPkg.Pkg.Name()
|
||||||
|
if imp.Name != nil {
|
||||||
|
name = imp.Name.Name
|
||||||
|
}
|
||||||
|
c.importMap[name] = realPkg.Pkg.Path()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func getBuildInfo(src interface{}) (*buildInfo, error) {
|
func getBuildInfo(src interface{}) (*buildInfo, error) {
|
||||||
conf := loader.Config{ParserMode: parser.ParseComments}
|
conf := loader.Config{ParserMode: parser.ParseComments}
|
||||||
f, err := conf.ParseFile("", src)
|
f, err := conf.ParseFile("", src)
|
||||||
|
|
|
@ -152,6 +152,8 @@ func (c *codegen) methodInfoFromScope(name string, scope *funcScope) *MethodDebu
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ss := strings.Split(name, ".")
|
||||||
|
name = ss[len(ss)-1]
|
||||||
return &MethodDebugInfo{
|
return &MethodDebugInfo{
|
||||||
ID: name,
|
ID: name,
|
||||||
Name: DebugMethodName{
|
Name: DebugMethodName{
|
||||||
|
|
|
@ -45,7 +45,7 @@ type funcScope struct {
|
||||||
i int
|
i int
|
||||||
}
|
}
|
||||||
|
|
||||||
func newFuncScope(decl *ast.FuncDecl, label uint16) *funcScope {
|
func (c *codegen) newFuncScope(decl *ast.FuncDecl, label uint16) *funcScope {
|
||||||
var name string
|
var name string
|
||||||
if decl.Name != nil {
|
if decl.Name != nil {
|
||||||
name = decl.Name.Name
|
name = decl.Name.Name
|
||||||
|
@ -54,6 +54,7 @@ func newFuncScope(decl *ast.FuncDecl, label uint16) *funcScope {
|
||||||
name: name,
|
name: name,
|
||||||
decl: decl,
|
decl: decl,
|
||||||
label: label,
|
label: label,
|
||||||
|
pkg: c.currPkg,
|
||||||
vars: newVarScope(),
|
vars: newVarScope(),
|
||||||
voidCalls: map[*ast.CallExpr]bool{},
|
voidCalls: map[*ast.CallExpr]bool{},
|
||||||
variables: []string{},
|
variables: []string{},
|
||||||
|
@ -61,6 +62,14 @@ func newFuncScope(decl *ast.FuncDecl, label uint16) *funcScope {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *codegen) getFuncNameFromDecl(pkgPath string, decl *ast.FuncDecl) string {
|
||||||
|
name := decl.Name.Name
|
||||||
|
if decl.Recv != nil {
|
||||||
|
name = decl.Recv.List[0].Type.(*ast.Ident).Name + "." + name
|
||||||
|
}
|
||||||
|
return c.getIdentName(pkgPath, name)
|
||||||
|
}
|
||||||
|
|
||||||
// analyzeVoidCalls checks for functions that are not assigned
|
// analyzeVoidCalls checks for functions that are not assigned
|
||||||
// and therefore we need to cleanup the return value from the stack.
|
// and therefore we need to cleanup the return value from the stack.
|
||||||
func (c *funcScope) analyzeVoidCalls(node ast.Node) bool {
|
func (c *funcScope) analyzeVoidCalls(node ast.Node) bool {
|
||||||
|
|
|
@ -127,3 +127,102 @@ func TestContractWithNoMain(t *testing.T) {
|
||||||
require.Equal(t, 1, v.Estack().Len())
|
require.Equal(t, 1, v.Estack().Len())
|
||||||
require.Equal(t, big.NewInt(42), v.PopResult())
|
require.Equal(t, big.NewInt(42), v.PopResult())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMultipleFiles(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/multi"
|
||||||
|
func Main() int {
|
||||||
|
return multi.Sum()
|
||||||
|
}`
|
||||||
|
eval(t, src, big.NewInt(42))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExportedVariable(t *testing.T) {
|
||||||
|
t.Run("Use", func(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/multi"
|
||||||
|
func Main() int {
|
||||||
|
return multi.SomeVar12
|
||||||
|
}`
|
||||||
|
eval(t, src, big.NewInt(12))
|
||||||
|
})
|
||||||
|
t.Run("ChangeAndUse", func(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/multi"
|
||||||
|
func Main() int {
|
||||||
|
multi.SomeVar12 = 10
|
||||||
|
return multi.Sum()
|
||||||
|
}`
|
||||||
|
eval(t, src, big.NewInt(40))
|
||||||
|
})
|
||||||
|
t.Run("PackageAlias", func(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
import kek "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/multi"
|
||||||
|
func Main() int {
|
||||||
|
kek.SomeVar12 = 10
|
||||||
|
return kek.Sum()
|
||||||
|
}`
|
||||||
|
eval(t, src, big.NewInt(40))
|
||||||
|
})
|
||||||
|
t.Run("DifferentName", func(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/strange"
|
||||||
|
func Main() int {
|
||||||
|
normal.NormalVar = 42
|
||||||
|
return normal.NormalVar
|
||||||
|
}`
|
||||||
|
eval(t, src, big.NewInt(42))
|
||||||
|
})
|
||||||
|
t.Run("MultipleEqualNames", func(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/multi"
|
||||||
|
var SomeVar12 = 1
|
||||||
|
func Main() int {
|
||||||
|
SomeVar30 := 3
|
||||||
|
sum := SomeVar12 + multi.SomeVar30
|
||||||
|
sum += SomeVar30
|
||||||
|
sum += multi.SomeVar12
|
||||||
|
return sum
|
||||||
|
}`
|
||||||
|
eval(t, src, big.NewInt(46))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExportedConst(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/multi"
|
||||||
|
func Main() int {
|
||||||
|
return multi.SomeConst
|
||||||
|
}`
|
||||||
|
eval(t, src, big.NewInt(42))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultipleFuncSameName(t *testing.T) {
|
||||||
|
t.Run("Simple", func(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/multi"
|
||||||
|
func Main() int {
|
||||||
|
return multi.Sum() + Sum()
|
||||||
|
}
|
||||||
|
func Sum() int {
|
||||||
|
return 11
|
||||||
|
}`
|
||||||
|
eval(t, src, big.NewInt(53))
|
||||||
|
})
|
||||||
|
t.Run("WithMethod", func(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/foo"
|
||||||
|
type Foo struct{}
|
||||||
|
func (f Foo) Bar() int { return 11 }
|
||||||
|
func Bar() int { return 22 }
|
||||||
|
func Main() int {
|
||||||
|
var a Foo
|
||||||
|
var b foo.Foo
|
||||||
|
return a.Bar() + // 11
|
||||||
|
foo.Bar() + // 1
|
||||||
|
b.Bar() + // 8
|
||||||
|
Bar() // 22
|
||||||
|
}`
|
||||||
|
eval(t, src, big.NewInt(42))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
13
pkg/compiler/testdata/foo/foo.go
vendored
13
pkg/compiler/testdata/foo/foo.go
vendored
|
@ -4,3 +4,16 @@ package foo
|
||||||
func NewBar() int {
|
func NewBar() int {
|
||||||
return 10
|
return 10
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Foo is a type.
|
||||||
|
type Foo struct{}
|
||||||
|
|
||||||
|
// Bar is a function.
|
||||||
|
func Bar() int {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bar is a method.
|
||||||
|
func (f Foo) Bar() int {
|
||||||
|
return 8
|
||||||
|
}
|
||||||
|
|
5
pkg/compiler/testdata/multi/file1.go
vendored
Normal file
5
pkg/compiler/testdata/multi/file1.go
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
package multi
|
||||||
|
|
||||||
|
var SomeVar12 = 12
|
||||||
|
|
||||||
|
const SomeConst = 42
|
7
pkg/compiler/testdata/multi/file2.go
vendored
Normal file
7
pkg/compiler/testdata/multi/file2.go
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
package multi
|
||||||
|
|
||||||
|
var SomeVar30 = 30
|
||||||
|
|
||||||
|
func Sum() int {
|
||||||
|
return SomeVar12 + SomeVar30
|
||||||
|
}
|
4
pkg/compiler/testdata/strange/normal.go
vendored
Normal file
4
pkg/compiler/testdata/strange/normal.go
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
package normal
|
||||||
|
|
||||||
|
// NormalVar is pretty normal, nothing special.
|
||||||
|
var NormalVar = 7
|
|
@ -74,6 +74,7 @@ func vmAndCompileInterop(t *testing.T, src string) (*vm.VM, *storagePlugin) {
|
||||||
|
|
||||||
b, di, err := compiler.CompileWithDebugInfo(strings.NewReader(src))
|
b, di, err := compiler.CompileWithDebugInfo(strings.NewReader(src))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
invokeMethod(t, testMainIdent, b, vm, di)
|
invokeMethod(t, testMainIdent, b, vm, di)
|
||||||
return vm, storePlugin
|
return vm, storePlugin
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue