compiler: compile init even if there are no globals

`init` can be useful even if no globals are present,
e.g. we can use some syscall inside.
This commit is contained in:
Evgenii Stratonikov 2020-08-05 12:59:50 +03:00
parent 439d9ff94d
commit 4488f61777
3 changed files with 51 additions and 14 deletions

View file

@ -36,34 +36,54 @@ func (c *codegen) getIdentName(pkg string, name string) string {
} }
// traverseGlobals visits and initializes global variables. // traverseGlobals visits and initializes global variables.
// and returns number of variables initialized. // and returns number of variables initialized and
func (c *codegen) traverseGlobals() int { // true if any init functions were encountered.
func (c *codegen) traverseGlobals() (int, bool) {
var n int var n int
var hasInit bool
c.ForEachFile(func(f *ast.File, _ *types.Package) { c.ForEachFile(func(f *ast.File, _ *types.Package) {
n += countGlobals(f) n += countGlobals(f)
if !hasInit {
ast.Inspect(f, func(node ast.Node) bool {
n, ok := node.(*ast.FuncDecl)
if ok {
if isInitFunc(n) {
hasInit = true
}
return false
}
return true
}) })
if n != 0 { }
})
if n != 0 || hasInit {
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, hasInit
} }
if n != 0 {
emit.Instruction(c.prog.BinWriter, opcode.INITSSLOT, []byte{byte(n)}) emit.Instruction(c.prog.BinWriter, opcode.INITSSLOT, []byte{byte(n)})
}
c.ForEachPackage(func(pkg *loader.PackageInfo) { c.ForEachPackage(func(pkg *loader.PackageInfo) {
if n > 0 {
for _, f := range pkg.Files { for _, f := range pkg.Files {
c.fillImportMap(f, pkg.Pkg) c.fillImportMap(f, pkg.Pkg)
c.convertGlobals(f, pkg.Pkg) c.convertGlobals(f, pkg.Pkg)
} }
}
if hasInit {
for _, f := range pkg.Files { for _, f := range pkg.Files {
c.fillImportMap(f, pkg.Pkg) c.fillImportMap(f, pkg.Pkg)
c.convertInitFuncs(f, pkg.Pkg) c.convertInitFuncs(f, pkg.Pkg)
} }
}
// because we reuse `convertFuncDecl` for init funcs, // because we reuse `convertFuncDecl` for init funcs,
// we need to cleare scope, so that global variables // we need to cleare scope, so that global variables
// encountered after will be recognized as globals. // encountered after will be recognized as globals.
c.scope = nil c.scope = nil
}) })
} }
return n return n, hasInit
} }
// countGlobals counts the global variables in the program to add // countGlobals counts the global variables in the program to add

View file

@ -1495,8 +1495,8 @@ func (c *codegen) compile(info *buildInfo, pkg *loader.PackageInfo) error {
// Bring all imported functions into scope. // Bring all imported functions into scope.
c.ForEachFile(c.resolveFuncDecls) c.ForEachFile(c.resolveFuncDecls)
n := c.traverseGlobals() n, hasInit := c.traverseGlobals()
if n > 0 { if n > 0 || hasInit {
emit.Opcode(c.prog.BinWriter, opcode.RET) emit.Opcode(c.prog.BinWriter, opcode.RET)
c.initEndOffset = c.prog.Len() c.initEndOffset = c.prog.Len()
} }

View file

@ -3,6 +3,8 @@ package compiler_test
import ( import (
"math/big" "math/big"
"testing" "testing"
"github.com/stretchr/testify/require"
) )
func TestInit(t *testing.T) { func TestInit(t *testing.T) {
@ -88,3 +90,18 @@ func TestImportOrder(t *testing.T) {
eval(t, src, big.NewInt(3)) eval(t, src, big.NewInt(3))
}) })
} }
func TestInitWithNoGlobals(t *testing.T) {
src := `package foo
import "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
func init() {
runtime.Notify("called in '_initialize'")
}
func Main() int {
return 42
}`
v, s := vmAndCompileInterop(t, src)
require.NoError(t, v.Run())
assertResult(t, v, big.NewInt(42))
require.True(t, len(s.events) == 1)
}