From 700941732517f9c5b28c55c8151f3c20531359d3 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Tue, 28 Jul 2020 10:59:21 +0300 Subject: [PATCH 1/7] compiler: allow to declare global variables in multiple files Traverse and count globals across all used files. Signed-off-by: Evgenii Stratonikov --- pkg/compiler/analysis.go | 10 ++++------ pkg/compiler/codegen.go | 4 ++-- pkg/compiler/compiler.go | 11 +++++++++++ pkg/compiler/global_test.go | 9 +++++++++ pkg/compiler/testdata/multi/file1.go | 3 +++ pkg/compiler/testdata/multi/file2.go | 7 +++++++ 6 files changed, 36 insertions(+), 8 deletions(-) create mode 100644 pkg/compiler/testdata/multi/file1.go create mode 100644 pkg/compiler/testdata/multi/file2.go diff --git a/pkg/compiler/analysis.go b/pkg/compiler/analysis.go index 0033e427b..3dc20a382 100644 --- a/pkg/compiler/analysis.go +++ b/pkg/compiler/analysis.go @@ -28,20 +28,18 @@ func (c *codegen) newGlobal(name string) { // traverseGlobals visits and initializes global variables. // and returns number of variables initialized. -func (c *codegen) traverseGlobals(fs ...*ast.File) int { +func (c *codegen) traverseGlobals() int { var n int - for _, f := range fs { + c.ForEachFile(func(f *ast.File) { n += countGlobals(f) - } + }) if n != 0 { if n > 255 { c.prog.BinWriter.Err = errors.New("too many global variables") return 0 } emit.Instruction(c.prog.BinWriter, opcode.INITSSLOT, []byte{byte(n)}) - for _, f := range fs { - c.convertGlobals(f) - } + c.ForEachFile(c.convertGlobals) } return n } diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index 5b96f5f94..f52ec6f15 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -252,7 +252,7 @@ func (c *codegen) emitDefault(t types.Type) { // convertGlobals traverses the AST and only converts 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.Node) { +func (c *codegen) convertGlobals(f *ast.File) { ast.Inspect(f, func(node ast.Node) bool { switch n := node.(type) { case *ast.FuncDecl: @@ -1425,7 +1425,7 @@ func (c *codegen) compile(info *buildInfo, pkg *loader.PackageInfo) error { } c.mainPkg = pkg - n := c.traverseGlobals(pkg.Files...) + n := c.traverseGlobals() if n > 0 { emit.Opcode(c.prog.BinWriter, opcode.RET) c.initEndOffset = c.prog.Len() diff --git a/pkg/compiler/compiler.go b/pkg/compiler/compiler.go index 4c16af02e..35d96f3a7 100644 --- a/pkg/compiler/compiler.go +++ b/pkg/compiler/compiler.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/json" "fmt" + "go/ast" "go/parser" "io" "io/ioutil" @@ -42,6 +43,16 @@ type buildInfo struct { program *loader.Program } +// ForEachFile executes fn on each file used in current program. +func (c *codegen) ForEachFile(fn func(*ast.File)) { + for _, pkg := range c.buildInfo.program.AllPackages { + c.typeInfo = &pkg.Info + for _, f := range pkg.Files { + fn(f) + } + } +} + func getBuildInfo(src interface{}) (*buildInfo, error) { conf := loader.Config{ParserMode: parser.ParseComments} f, err := conf.ParseFile("", src) diff --git a/pkg/compiler/global_test.go b/pkg/compiler/global_test.go index 359e8ef69..e49d516d2 100644 --- a/pkg/compiler/global_test.go +++ b/pkg/compiler/global_test.go @@ -127,3 +127,12 @@ func TestContractWithNoMain(t *testing.T) { require.Equal(t, 1, v.Estack().Len()) 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)) +} diff --git a/pkg/compiler/testdata/multi/file1.go b/pkg/compiler/testdata/multi/file1.go new file mode 100644 index 000000000..7aa330c8e --- /dev/null +++ b/pkg/compiler/testdata/multi/file1.go @@ -0,0 +1,3 @@ +package multi + +var SomeVar12 = 12 diff --git a/pkg/compiler/testdata/multi/file2.go b/pkg/compiler/testdata/multi/file2.go new file mode 100644 index 000000000..2ee034599 --- /dev/null +++ b/pkg/compiler/testdata/multi/file2.go @@ -0,0 +1,7 @@ +package multi + +var SomeVar30 = 30 + +func Sum() int { + return SomeVar12 + SomeVar30 +} From ac040a6f22548c820f0c9796dc72b58d51335cf7 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Tue, 28 Jul 2020 18:20:18 +0300 Subject: [PATCH 2/7] compiler: remove unused `resolveEntryPoint` Signed-off-by: Evgenii Stratonikov --- pkg/compiler/analysis.go | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/pkg/compiler/analysis.go b/pkg/compiler/analysis.go index 3dc20a382..ab75b0c35 100644 --- a/pkg/compiler/analysis.go +++ b/pkg/compiler/analysis.go @@ -68,28 +68,6 @@ func isExprNil(e ast.Expr) bool { 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. // If the struct does not contain that field it will return -1. func indexOfStruct(strct *types.Struct, fldName string) int { From b8d7e93459137568cb412b230934f2abee46dffb Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Tue, 28 Jul 2020 18:40:41 +0300 Subject: [PATCH 3/7] compiler: make `ForEachFile` accept a package Signed-off-by: Evgenii Stratonikov --- pkg/compiler/analysis.go | 45 +++++++++++++++++++--------------------- pkg/compiler/codegen.go | 35 ++++++++++++------------------- pkg/compiler/compiler.go | 5 +++-- 3 files changed, 37 insertions(+), 48 deletions(-) diff --git a/pkg/compiler/analysis.go b/pkg/compiler/analysis.go index ab75b0c35..f8fabb3a9 100644 --- a/pkg/compiler/analysis.go +++ b/pkg/compiler/analysis.go @@ -8,7 +8,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" - "golang.org/x/tools/go/loader" ) var ( @@ -30,7 +29,7 @@ func (c *codegen) newGlobal(name string) { // and returns number of variables initialized. func (c *codegen) traverseGlobals() int { var n int - c.ForEachFile(func(f *ast.File) { + c.ForEachFile(func(f *ast.File, _ *types.Package) { n += countGlobals(f) }) if n != 0 { @@ -95,31 +94,29 @@ func lastStmtIsReturn(decl *ast.FuncDecl) (b bool) { return false } -func analyzeFuncUsage(mainPkg *loader.PackageInfo, pkgs map[*types.Package]*loader.PackageInfo) funcUsage { +func (c *codegen) analyzeFuncUsage() funcUsage { usage := funcUsage{} - for _, pkg := range pkgs { - isMain := pkg == mainPkg - for _, f := range pkg.Files { - ast.Inspect(f, func(node ast.Node) bool { - switch n := node.(type) { - case *ast.CallExpr: - switch t := n.Fun.(type) { - case *ast.Ident: - usage[t.Name] = true - case *ast.SelectorExpr: - usage[t.Sel.Name] = true - } - case *ast.FuncDecl: - // exported functions are always assumed to be used - if isMain && n.Name.IsExported() { - usage[n.Name.Name] = true - } + c.ForEachFile(func(f *ast.File, pkg *types.Package) { + isMain := pkg == c.mainPkg.Pkg + ast.Inspect(f, func(node ast.Node) bool { + switch n := node.(type) { + case *ast.CallExpr: + switch t := n.Fun.(type) { + case *ast.Ident: + usage[t.Name] = true + case *ast.SelectorExpr: + usage[t.Sel.Name] = true } - return true - }) - } - } + case *ast.FuncDecl: + // exported functions are always assumed to be used + if isMain && n.Name.IsExported() { + usage[n.Name.Name] = true + } + } + return true + }) + }) return usage } diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index f52ec6f15..2486a67c9 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -252,7 +252,7 @@ func (c *codegen) emitDefault(t types.Type) { // convertGlobals traverses the AST and only converts 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) { +func (c *codegen) convertGlobals(f *ast.File, _ *types.Package) { ast.Inspect(f, func(node ast.Node) bool { switch n := node.(type) { case *ast.FuncDecl: @@ -1415,16 +1415,12 @@ func (c *codegen) newLambda(u uint16, lit *ast.FuncLit) { } 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. - for _, pkg := range info.program.AllPackages { - for _, f := range pkg.Files { - c.resolveFuncDecls(f, pkg.Pkg) - } - } + c.ForEachFile(c.resolveFuncDecls) - c.mainPkg = pkg n := c.traverseGlobals() if n > 0 { emit.Opcode(c.prog.BinWriter, opcode.RET) @@ -1439,23 +1435,18 @@ 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() }) // Generate the code for the program. - for _, k := range keys { - pkg := info.program.AllPackages[k] - c.typeInfo = &pkg.Info - - for _, f := range pkg.Files { - for _, decl := range f.Decls { - switch n := decl.(type) { - case *ast.FuncDecl: - // Don't convert the function if it's not used. This will save a lot - // of bytecode space. - if funUsage.funcUsed(n.Name.Name) { - c.convertFuncDecl(f, n, k) - } + c.ForEachFile(func(f *ast.File, pkg *types.Package) { + for _, decl := range f.Decls { + switch n := decl.(type) { + case *ast.FuncDecl: + // Don't convert the function if it's not used. This will save a lot + // of bytecode space. + if funUsage.funcUsed(n.Name.Name) { + c.convertFuncDecl(f, n, pkg) } } } - } + }) return c.prog.Err } diff --git a/pkg/compiler/compiler.go b/pkg/compiler/compiler.go index 35d96f3a7..5703f8901 100644 --- a/pkg/compiler/compiler.go +++ b/pkg/compiler/compiler.go @@ -6,6 +6,7 @@ import ( "fmt" "go/ast" "go/parser" + "go/types" "io" "io/ioutil" "os" @@ -44,11 +45,11 @@ type buildInfo struct { } // ForEachFile executes fn on each file used in current program. -func (c *codegen) ForEachFile(fn func(*ast.File)) { +func (c *codegen) ForEachFile(fn func(*ast.File, *types.Package)) { for _, pkg := range c.buildInfo.program.AllPackages { c.typeInfo = &pkg.Info for _, f := range pkg.Files { - fn(f) + fn(f, pkg.Pkg) } } } From 6df019913dfdb5d24debdb339c77f1159392e86d Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Tue, 28 Jul 2020 19:35:41 +0300 Subject: [PATCH 4/7] compiler: allow to use exported variables Signed-off-by: Evgenii Stratonikov --- pkg/compiler/analysis.go | 11 ++++- pkg/compiler/codegen.go | 63 ++++++++++++++++--------- pkg/compiler/compiler.go | 18 +++++++ pkg/compiler/global_test.go | 51 ++++++++++++++++++++ pkg/compiler/testdata/strange/normal.go | 4 ++ 5 files changed, 124 insertions(+), 23 deletions(-) create mode 100644 pkg/compiler/testdata/strange/normal.go diff --git a/pkg/compiler/analysis.go b/pkg/compiler/analysis.go index f8fabb3a9..4a0821267 100644 --- a/pkg/compiler/analysis.go +++ b/pkg/compiler/analysis.go @@ -21,10 +21,19 @@ var ( ) // 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) } +// 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. // and returns number of variables initialized. func (c *codegen) traverseGlobals() int { diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index 2486a67c9..15163c05f 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -62,6 +62,9 @@ type codegen struct { // initEndOffset specifies the end of the initialization method. initEndOffset int + // importMap contains mapping from package aliases to full package names for the current file. + importMap map[string]string + // mainPkg is a main package metadata. mainPkg *loader.PackageInfo @@ -167,14 +170,16 @@ func (c *codegen) emitStoreStructField(i int) { // getVarIndex returns variable type and position in corresponding slot, // according to current scope. -func (c *codegen) getVarIndex(name string) (varType, int) { - if c.scope != nil { - vt, val := c.scope.vars.getVarIndex(name) - if val >= 0 { - return vt, val +func (c *codegen) getVarIndex(pkg string, name string) (varType, int) { + if pkg == "" { + if c.scope != nil { + vt, val := c.scope.vars.getVarIndex(name) + if val >= 0 { + return vt, val + } } } - if i, ok := c.globals[name]; ok { + if i, ok := c.globals[c.getIdentName(pkg, name)]; ok { return varGlobal, i } @@ -195,8 +200,8 @@ func getBaseOpcode(t varType) (opcode.Opcode, opcode.Opcode) { } // emitLoadVar loads specified variable to the evaluation stack. -func (c *codegen) emitLoadVar(name string) { - t, i := c.getVarIndex(name) +func (c *codegen) emitLoadVar(pkg string, name string) { + t, i := c.getVarIndex(pkg, name) base, _ := getBaseOpcode(t) if i < 7 { emit.Opcode(c.prog.BinWriter, base+opcode.Opcode(i)) @@ -206,12 +211,12 @@ func (c *codegen) emitLoadVar(name string) { } // 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 == "_" { emit.Opcode(c.prog.BinWriter, opcode.DROP) return } - t, i := c.getVarIndex(name) + t, i := c.getVarIndex(pkg, name) _, base := getBaseOpcode(t) if i < 7 { emit.Opcode(c.prog.BinWriter, base+opcode.Opcode(i)) @@ -368,7 +373,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { for _, id := range t.Names { if c.scope == nil { // it is a global declaration - c.newGlobal(id.Name) + c.newGlobal("", id.Name) } else { c.scope.newLocal(id.Name) } @@ -380,7 +385,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { } else { c.emitDefault(c.typeOf(t.Type)) } - c.emitStoreVar(t.Names[i].Name) + c.emitStoreVar("", t.Names[i].Name) } } } @@ -411,13 +416,19 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { if !isAssignOp && (i == 0 || !multiRet) { ast.Walk(c, n.Rhs[i]) } - c.emitStoreVar(t.Name) + c.emitStoreVar("", t.Name) case *ast.SelectorExpr: if !isAssignOp { 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 { c.prog.Err = fmt.Errorf("nested selector assigns not supported yet") return nil @@ -442,7 +453,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { case *ast.SliceExpr: name := n.X.(*ast.Ident).Name - c.emitLoadVar(name) + c.emitLoadVar("", name) if n.Low != nil { ast.Walk(c, n.Low) @@ -480,7 +491,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { for i := len(results.List) - 1; i >= 0; i-- { names := results.List[i].Names for j := len(names) - 1; j >= 0; j-- { - c.emitLoadVar(names[j].Name) + c.emitLoadVar("", names[j].Name) } } } @@ -595,7 +606,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { } else if n.Name == "nil" { emit.Opcode(c.prog.BinWriter, opcode.PUSHNULL) } else { - c.emitLoadVar(n.Name) + c.emitLoadVar("", n.Name) } return nil @@ -789,7 +800,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { if isString(c.typeOf(n.Fun)) { c.emitConvert(stackitem.ByteArrayT) } else if isFunc { - c.emitLoadVar(name) + c.emitLoadVar("", name) emit.Opcode(c.prog.BinWriter, opcode.CALLA) } case isSyscall(f): @@ -801,7 +812,15 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { return nil 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 + pkgPath := c.importMap[pkgAlias] + c.emitLoadVar(pkgPath, n.Sel.Name) + return nil + } + strct, ok := typ.Underlying().(*types.Struct) if !ok { c.prog.Err = fmt.Errorf("selectors are supported only on structs") return nil @@ -840,7 +859,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { // for i := 0; i < 10; i++ {} // Where the post stmt is ( i++ ) if ident, ok := n.X.(*ast.Ident); ok { - c.emitStoreVar(ident.Name) + c.emitStoreVar("", ident.Name) } return nil @@ -992,7 +1011,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { } else { emit.Opcode(c.prog.BinWriter, opcode.DUP) } - c.emitStoreVar(n.Key.(*ast.Ident).Name) + c.emitStoreVar("", n.Key.(*ast.Ident).Name) } if needValue { if !isMap || !keyLoaded { @@ -1005,7 +1024,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.PICKITEM) } - c.emitStoreVar(n.Value.(*ast.Ident).Name) + c.emitStoreVar("", n.Value.(*ast.Ident).Name) } ast.Walk(c, n.Body) diff --git a/pkg/compiler/compiler.go b/pkg/compiler/compiler.go index 5703f8901..0a19a0f5a 100644 --- a/pkg/compiler/compiler.go +++ b/pkg/compiler/compiler.go @@ -49,11 +49,29 @@ func (c *codegen) ForEachFile(fn func(*ast.File, *types.Package)) { for _, pkg := range c.buildInfo.program.AllPackages { c.typeInfo = &pkg.Info 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) { conf := loader.Config{ParserMode: parser.ParseComments} f, err := conf.ParseFile("", src) diff --git a/pkg/compiler/global_test.go b/pkg/compiler/global_test.go index e49d516d2..076555188 100644 --- a/pkg/compiler/global_test.go +++ b/pkg/compiler/global_test.go @@ -136,3 +136,54 @@ func TestMultipleFiles(t *testing.T) { }` 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)) + }) +} diff --git a/pkg/compiler/testdata/strange/normal.go b/pkg/compiler/testdata/strange/normal.go new file mode 100644 index 000000000..98a4593dc --- /dev/null +++ b/pkg/compiler/testdata/strange/normal.go @@ -0,0 +1,4 @@ +package normal + +// NormalVar is pretty normal, nothing special. +var NormalVar = 7 From 528c184f006046dbd6748258eebfc0d9aa28c10d Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Tue, 28 Jul 2020 19:50:44 +0300 Subject: [PATCH 5/7] compiler: allow to use exported constants Signed-off-by: Evgenii Stratonikov --- pkg/compiler/codegen.go | 27 ++++++++++++++++++++------- pkg/compiler/global_test.go | 9 +++++++++ pkg/compiler/testdata/multi/file1.go | 2 ++ 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index 15163c05f..49d9250e1 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -65,6 +65,9 @@ type codegen struct { // 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 + // mainPkg is a main package metadata. mainPkg *loader.PackageInfo @@ -263,11 +266,7 @@ func (c *codegen) convertGlobals(f *ast.File, _ *types.Package) { case *ast.FuncDecl: return false 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 }) @@ -367,6 +366,15 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { // x = 2 // ) 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 { switch t := spec.(type) { case *ast.ValueSpec: @@ -816,8 +824,12 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { if typ == nil { // This is a global variable from a package. pkgAlias := n.X.(*ast.Ident).Name - pkgPath := c.importMap[pkgAlias] - c.emitLoadVar(pkgPath, n.Sel.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) @@ -1480,6 +1492,7 @@ func newCodegen(info *buildInfo, pkg *loader.PackageInfo) *codegen { globals: map[string]int{}, labels: map[labelWithType]uint16{}, typeInfo: &pkg.Info, + constMap: map[string]types.TypeAndValue{}, sequencePoints: make(map[string][]DebugSeqPoint), } diff --git a/pkg/compiler/global_test.go b/pkg/compiler/global_test.go index 076555188..1b46ff986 100644 --- a/pkg/compiler/global_test.go +++ b/pkg/compiler/global_test.go @@ -187,3 +187,12 @@ func TestExportedVariable(t *testing.T) { 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)) +} diff --git a/pkg/compiler/testdata/multi/file1.go b/pkg/compiler/testdata/multi/file1.go index 7aa330c8e..c51714e74 100644 --- a/pkg/compiler/testdata/multi/file1.go +++ b/pkg/compiler/testdata/multi/file1.go @@ -1,3 +1,5 @@ package multi var SomeVar12 = 12 + +const SomeConst = 42 From a781d299e097b6efe36576de41d2b8498a049e5d Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Wed, 29 Jul 2020 17:20:00 +0300 Subject: [PATCH 6/7] compiler: use fully-qualified names for tracking functions Function name now consists of 3 parts: 1) full package path 2) method receiver type (if any) 3) function name itself . Fix #1150. Signed-off-by: Evgenii Stratonikov --- pkg/compiler/analysis.go | 7 +++--- pkg/compiler/codegen.go | 37 ++++++++++++++++++++++---------- pkg/compiler/compiler.go | 1 + pkg/compiler/debug.go | 2 ++ pkg/compiler/func_scope.go | 11 +++++++++- pkg/compiler/global_test.go | 30 ++++++++++++++++++++++++++ pkg/compiler/testdata/foo/foo.go | 13 +++++++++++ pkg/compiler/vm_test.go | 1 + 8 files changed, 87 insertions(+), 15 deletions(-) diff --git a/pkg/compiler/analysis.go b/pkg/compiler/analysis.go index 4a0821267..1c1dab8bd 100644 --- a/pkg/compiler/analysis.go +++ b/pkg/compiler/analysis.go @@ -113,14 +113,15 @@ func (c *codegen) analyzeFuncUsage() funcUsage { case *ast.CallExpr: switch t := n.Fun.(type) { case *ast.Ident: - usage[t.Name] = true + usage[c.getIdentName("", t.Name)] = true case *ast.SelectorExpr: - usage[t.Sel.Name] = true + name, _ := c.getFuncNameFromSelector(t) + usage[name] = true } case *ast.FuncDecl: // exported functions are always assumed to be used if isMain && n.Name.IsExported() { - usage[n.Name.Name] = true + usage[c.getFuncNameFromDecl(pkg.Path(), n)] = true } } return true diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index 49d9250e1..fc34b7b04 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -68,6 +68,9 @@ type codegen struct { // 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 *loader.PackageInfo @@ -278,19 +281,18 @@ func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl, pkg *types. ok, isLambda bool ) - f, ok = c.funcs[decl.Name.Name] + f, ok = c.funcs[c.getFuncNameFromDecl("", decl)] if ok { // If this function is a syscall or builtin we will not convert it to bytecode. if isSyscall(f) || isCustomBuiltin(f) { return } 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 c.setLabel(f.label) } else { f = c.newFunc(decl) - f.pkg = pkg } f.rng.Start = uint16(c.prog.Len()) @@ -738,7 +740,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { switch fun := n.Fun.(type) { case *ast.Ident: - f, ok = c.funcs[fun.Name] + f, ok = c.funcs[c.getIdentName("", fun.Name)] isBuiltin = isGoBuiltin(fun.Name) if !ok && !isBuiltin { name = fun.Name @@ -751,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. // Otherwise this is a function call from a imported package and we can call it // directly. - if c.typeInfo.Selections[fun] != nil { + name, isMethod := c.getFuncNameFromSelector(fun) + if isMethod { ast.Walk(c, fun.X) // Dont forget to add 1 extra argument when its a method. numArgs++ } - f, ok = c.funcs[fun.Sel.Name] + f, ok = c.funcs[name] // @FIXME this could cause runtime errors. f.selector = fun.X.(*ast.Ident) if !ok { @@ -1431,18 +1434,30 @@ func (c *codegen) convertToken(tok token.Token) { } func (c *codegen) newFunc(decl *ast.FuncDecl) *funcScope { - f := newFuncScope(decl, c.newLabel()) - c.funcs[f.name] = f + f := c.newFuncScope(decl, c.newLabel()) + c.funcs[c.getFuncNameFromDecl("", decl)] = 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) { name := fmt.Sprintf("lambda@%d", u) - c.lambda[name] = newFuncScope(&ast.FuncDecl{ + f := c.newFuncScope(&ast.FuncDecl{ Name: ast.NewIdent(name), Type: lit.Type, Body: lit.Body, }, u) + c.lambda[c.getFuncNameFromDecl("", f.decl)] = f } func (c *codegen) compile(info *buildInfo, pkg *loader.PackageInfo) error { @@ -1472,7 +1487,8 @@ func (c *codegen) compile(info *buildInfo, pkg *loader.PackageInfo) error { case *ast.FuncDecl: // Don't convert the function if it's not used. This will save a lot // of bytecode space. - if funUsage.funcUsed(n.Name.Name) { + name := c.getFuncNameFromDecl(pkg.Path(), n) + if funUsage.funcUsed(name) && !isInteropPath(pkg.Path()) { c.convertFuncDecl(f, n, pkg) } } @@ -1519,7 +1535,6 @@ func (c *codegen) resolveFuncDecls(f *ast.File, pkg *types.Package) { switch n := decl.(type) { case *ast.FuncDecl: c.newFunc(n) - c.funcs[n.Name.Name].pkg = pkg } } } diff --git a/pkg/compiler/compiler.go b/pkg/compiler/compiler.go index 0a19a0f5a..af37f0250 100644 --- a/pkg/compiler/compiler.go +++ b/pkg/compiler/compiler.go @@ -48,6 +48,7 @@ type buildInfo struct { 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) diff --git a/pkg/compiler/debug.go b/pkg/compiler/debug.go index 01b1aed64..95bb7677b 100644 --- a/pkg/compiler/debug.go +++ b/pkg/compiler/debug.go @@ -152,6 +152,8 @@ func (c *codegen) methodInfoFromScope(name string, scope *funcScope) *MethodDebu }) } } + ss := strings.Split(name, ".") + name = ss[len(ss)-1] return &MethodDebugInfo{ ID: name, Name: DebugMethodName{ diff --git a/pkg/compiler/func_scope.go b/pkg/compiler/func_scope.go index 2202c120d..01ed1d175 100644 --- a/pkg/compiler/func_scope.go +++ b/pkg/compiler/func_scope.go @@ -45,7 +45,7 @@ type funcScope struct { i int } -func newFuncScope(decl *ast.FuncDecl, label uint16) *funcScope { +func (c *codegen) newFuncScope(decl *ast.FuncDecl, label uint16) *funcScope { var name string if decl.Name != nil { name = decl.Name.Name @@ -54,6 +54,7 @@ func newFuncScope(decl *ast.FuncDecl, label uint16) *funcScope { name: name, decl: decl, label: label, + pkg: c.currPkg, vars: newVarScope(), voidCalls: map[*ast.CallExpr]bool{}, 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 // and therefore we need to cleanup the return value from the stack. func (c *funcScope) analyzeVoidCalls(node ast.Node) bool { diff --git a/pkg/compiler/global_test.go b/pkg/compiler/global_test.go index 1b46ff986..934972fec 100644 --- a/pkg/compiler/global_test.go +++ b/pkg/compiler/global_test.go @@ -196,3 +196,33 @@ func TestExportedConst(t *testing.T) { }` 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)) + }) +} diff --git a/pkg/compiler/testdata/foo/foo.go b/pkg/compiler/testdata/foo/foo.go index 731c66bb9..ca98105ac 100644 --- a/pkg/compiler/testdata/foo/foo.go +++ b/pkg/compiler/testdata/foo/foo.go @@ -4,3 +4,16 @@ package foo func NewBar() int { 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 +} diff --git a/pkg/compiler/vm_test.go b/pkg/compiler/vm_test.go index 237ee8c12..7456eedc4 100644 --- a/pkg/compiler/vm_test.go +++ b/pkg/compiler/vm_test.go @@ -74,6 +74,7 @@ func vmAndCompileInterop(t *testing.T, src string) (*vm.VM, *storagePlugin) { b, di, err := compiler.CompileWithDebugInfo(strings.NewReader(src)) require.NoError(t, err) + invokeMethod(t, testMainIdent, b, vm, di) return vm, storePlugin } From d1ef9e67be287123a508c14e2bdd327d8b5f2b5f Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Wed, 29 Jul 2020 17:23:31 +0300 Subject: [PATCH 7/7] examples: fix method names Signed-off-by: Evgenii Stratonikov --- examples/token/nep5/nep5.go | 13 ++++++------- examples/token/token.go | 6 +++--- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/examples/token/nep5/nep5.go b/examples/token/nep5/nep5.go index e463ab581..79f7b7692 100644 --- a/examples/token/nep5/nep5.go +++ b/examples/token/nep5/nep5.go @@ -38,14 +38,13 @@ func (t Token) GetSupply(ctx storage.Context) interface{} { return getIntFromDB(ctx, []byte(t.CirculationKey)) } -// TBalanceOf gets the token balance of a specific address -// TODO: https://github.com/nspcc-dev/neo-go/issues/1150 -func (t Token) TBalanceOf(ctx storage.Context, holder []byte) interface{} { +// BalanceOf gets the token balance of a specific address +func (t Token) BalanceOf(ctx storage.Context, holder []byte) interface{} { return getIntFromDB(ctx, holder) } -// TTransfer token from one user to another -func (t Token) TTransfer(ctx storage.Context, from []byte, to []byte, amount int) bool { +// Transfer token from one user to another +func (t Token) Transfer(ctx storage.Context, from []byte, to []byte, amount int) bool { amountFrom := t.CanTransfer(ctx, from, to, amount) if amountFrom == -1 { return false @@ -105,8 +104,8 @@ func IsUsableAddress(addr []byte) bool { return false } -// TMint initial supply of tokens. -func (t Token) TMint(ctx storage.Context, to []byte) bool { +// Mint initial supply of tokens. +func (t Token) Mint(ctx storage.Context, to []byte) bool { if !IsUsableAddress(t.Owner) { return false } diff --git a/examples/token/token.go b/examples/token/token.go index c7b090383..1bacf8e72 100644 --- a/examples/token/token.go +++ b/examples/token/token.go @@ -99,19 +99,19 @@ func TotalSupply() interface{} { func BalanceOf(holder []byte) interface{} { t := createToken() ctx := storage.GetContext() - return t.TBalanceOf(ctx, holder) + return t.BalanceOf(ctx, holder) } // Transfer token from one user to another func Transfer(from []byte, to []byte, amount int) bool { t := createToken() ctx := storage.GetContext() - return t.TTransfer(ctx, from, to, amount) + return t.Transfer(ctx, from, to, amount) } // Mint initial supply of tokens func Mint(to []byte) bool { t := createToken() ctx := storage.GetContext() - return t.TMint(ctx, to) + return t.Mint(ctx, to) }