compiler: allow to use inlined functions to init globals

This commit is contained in:
Evgeniy Stratonikov 2021-02-25 15:12:16 +03:00
parent ac91de80e7
commit 0a4ff9d3e4
5 changed files with 111 additions and 35 deletions

View file

@ -46,13 +46,24 @@ func (c *codegen) traverseGlobals() (int, int, int) {
var n, nConst int
initLocals := -1
deployLocals := -1
c.ForEachFile(func(f *ast.File, _ *types.Package) {
c.ForEachFile(func(f *ast.File, pkg *types.Package) {
nv, nc := countGlobals(f)
n += nv
nConst += nc
if initLocals == -1 || deployLocals == -1 || !hasDefer {
ast.Inspect(f, func(node ast.Node) bool {
switch n := node.(type) {
case *ast.GenDecl:
if n.Tok == token.VAR {
for i := range n.Specs {
for _, v := range n.Specs[i].(*ast.ValueSpec).Values {
num := c.countLocalsCall(v, pkg)
if num > initLocals {
initLocals = num
}
}
}
}
case *ast.FuncDecl:
if isInitFunc(n) {
num, _ := c.countLocals(n)

View file

@ -102,6 +102,50 @@ func (c *funcScope) analyzeVoidCalls(node ast.Node) bool {
return true
}
func (c *codegen) countLocalsCall(n ast.Expr, pkg *types.Package) int {
ce, ok := n.(*ast.CallExpr)
if !ok {
return -1
}
var size int
var name string
switch fun := ce.Fun.(type) {
case *ast.Ident:
var pkgName string
if pkg != nil {
pkgName = pkg.Path()
}
name = c.getIdentName(pkgName, fun.Name)
case *ast.SelectorExpr:
name, _ = c.getFuncNameFromSelector(fun)
default:
return 0
}
if inner, ok := c.funcs[name]; ok && canInline(name) {
sig, ok := c.typeOf(ce.Fun).(*types.Signature)
if !ok {
info := c.buildInfo.program.Package(pkg.Path())
sig = info.Types[ce.Fun].Type.(*types.Signature)
}
for i := range ce.Args {
switch ce.Args[i].(type) {
case *ast.Ident:
case *ast.BasicLit:
default:
size++
}
}
// Variadic with direct var args.
if sig.Variadic() && !ce.Ellipsis.IsValid() {
size++
}
innerSz, _ := c.countLocalsInline(inner.decl, inner.pkg, inner)
size += innerSz
}
return size
}
func (c *codegen) countLocals(decl *ast.FuncDecl) (int, bool) {
return c.countLocalsInline(decl, nil, nil)
}
@ -117,40 +161,7 @@ func (c *codegen) countLocalsInline(decl *ast.FuncDecl, pkg *types.Package, f *f
ast.Inspect(decl, func(n ast.Node) bool {
switch n := n.(type) {
case *ast.CallExpr:
var name string
switch fun := n.Fun.(type) {
case *ast.Ident:
var pkgName string
if pkg != nil {
pkgName = pkg.Path()
}
name = c.getIdentName(pkgName, fun.Name)
case *ast.SelectorExpr:
name, _ = c.getFuncNameFromSelector(fun)
default:
return false
}
if inner, ok := c.funcs[name]; ok && canInline(name) {
sig, ok := c.typeOf(n.Fun).(*types.Signature)
if !ok {
info := c.buildInfo.program.Package(pkg.Path())
sig = info.Types[n.Fun].Type.(*types.Signature)
}
for i := range n.Args {
switch n.Args[i].(type) {
case *ast.Ident:
case *ast.BasicLit:
default:
size++
}
}
// Variadic with direct var args.
if sig.Variadic() && !n.Ellipsis.IsValid() {
size++
}
innerSz, _ := c.countLocalsInline(inner.decl, inner.pkg, inner)
size += innerSz
}
size += c.countLocalsCall(n, pkg)
return false
case *ast.FuncType:
num := n.Results.NumFields()

View file

@ -19,6 +19,14 @@ func (c *codegen) inlineCall(f *funcScope, n *ast.CallExpr) {
pkg := c.buildInfo.program.Package(f.pkg.Path())
sig := c.typeOf(n.Fun).(*types.Signature)
// When inlined call is used during global initialization
// there is no func scope, thus this if.
if c.scope == nil {
c.scope = &funcScope{}
c.scope.vars.newScope()
defer func() { c.scope = nil }()
}
// Arguments need to be walked with the current scope,
// while stored in the new.
oldScope := c.scope.vars.locals

View file

@ -115,6 +115,32 @@ func TestInline(t *testing.T) {
})
}
func TestInlineGlobalVariable(t *testing.T) {
t.Run("simple", func(t *testing.T) {
src := `package foo
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/inline"
var a = inline.Sum(1, 2)
func Main() int {
return a
}`
eval(t, src, big.NewInt(3))
})
t.Run("complex", func(t *testing.T) {
src := `package foo
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/inline"
var a = inline.Sum(3, 4)
var b = inline.SumSquared(1, 2)
var c = a + b
func init() {
c--
}
func Main() int {
return c
}`
eval(t, src, big.NewInt(15))
})
}
func TestInlineConversion(t *testing.T) {
src1 := `package foo
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/inline"

View file

@ -4,10 +4,12 @@ import (
"math/big"
"testing"
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
istorage "github.com/nspcc-dev/neo-go/pkg/core/interop/storage"
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -72,3 +74,21 @@ func TestNotify(t *testing.T) {
assert.Equal(t, "single", s.events[1].Name)
assert.Equal(t, []stackitem.Item{}, s.events[1].Item.Value())
}
func TestSyscallInGlobalInit(t *testing.T) {
src := `package foo
import "github.com/nspcc-dev/neo-go/pkg/interop/binary"
var a = binary.Base58Decode([]byte("5T"))
func Main() []byte {
return a
}`
v, s := vmAndCompileInterop(t, src)
s.interops[interopnames.ToID([]byte(interopnames.SystemBinaryBase58Decode))] = func(v *vm.VM) error {
s := v.Estack().Pop().Value().([]byte)
require.Equal(t, "5T", string(s))
v.Estack().PushVal([]byte{1, 2})
return nil
}
require.NoError(t, v.Run())
require.Equal(t, []byte{1, 2}, v.Estack().Pop().Value())
}