From 4488f61777cd8c22e88c53432010609e44b7c500 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Wed, 5 Aug 2020 12:59:50 +0300 Subject: [PATCH] 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. --- pkg/compiler/analysis.go | 44 ++++++++++++++++++++++++++++----------- pkg/compiler/codegen.go | 4 ++-- pkg/compiler/init_test.go | 17 +++++++++++++++ 3 files changed, 51 insertions(+), 14 deletions(-) diff --git a/pkg/compiler/analysis.go b/pkg/compiler/analysis.go index b15e81513..899d9e35f 100644 --- a/pkg/compiler/analysis.go +++ b/pkg/compiler/analysis.go @@ -36,26 +36,46 @@ func (c *codegen) getIdentName(pkg string, name string) string { } // traverseGlobals visits and initializes global variables. -// and returns number of variables initialized. -func (c *codegen) traverseGlobals() int { +// and returns number of variables initialized and +// true if any init functions were encountered. +func (c *codegen) traverseGlobals() (int, bool) { var n int + var hasInit bool c.ForEachFile(func(f *ast.File, _ *types.Package) { 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 { 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) { - for _, f := range pkg.Files { - c.fillImportMap(f, pkg.Pkg) - c.convertGlobals(f, pkg.Pkg) + if n > 0 { + for _, f := range pkg.Files { + c.fillImportMap(f, pkg.Pkg) + c.convertGlobals(f, pkg.Pkg) + } } - for _, f := range pkg.Files { - c.fillImportMap(f, pkg.Pkg) - c.convertInitFuncs(f, pkg.Pkg) + if hasInit { + for _, f := range pkg.Files { + c.fillImportMap(f, pkg.Pkg) + c.convertInitFuncs(f, pkg.Pkg) + } } // because we reuse `convertFuncDecl` for init funcs, // we need to cleare scope, so that global variables @@ -63,7 +83,7 @@ func (c *codegen) traverseGlobals() int { c.scope = nil }) } - return n + return n, hasInit } // countGlobals counts the global variables in the program to add diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index b1ae269e4..384c19197 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -1495,8 +1495,8 @@ func (c *codegen) compile(info *buildInfo, pkg *loader.PackageInfo) error { // Bring all imported functions into scope. c.ForEachFile(c.resolveFuncDecls) - n := c.traverseGlobals() - if n > 0 { + n, hasInit := c.traverseGlobals() + if n > 0 || hasInit { emit.Opcode(c.prog.BinWriter, opcode.RET) c.initEndOffset = c.prog.Len() } diff --git a/pkg/compiler/init_test.go b/pkg/compiler/init_test.go index 4327855e2..dd74639ad 100644 --- a/pkg/compiler/init_test.go +++ b/pkg/compiler/init_test.go @@ -3,6 +3,8 @@ package compiler_test import ( "math/big" "testing" + + "github.com/stretchr/testify/require" ) func TestInit(t *testing.T) { @@ -88,3 +90,18 @@ func TestImportOrder(t *testing.T) { 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) +}