forked from TrueCloudLab/neoneo-go
compiler: emit code for unnamed global var decls more careful
In case if global var is unnamed (and, as a consequence, unused) and contains a function call inside its value specification, we need to emit code for this var to be able to call the function as it can have side-effects. See the example: ``` package foo import "github.com/nspcc-dev/neo-go/pkg/interop/runtime" var A = f() func Main() int { return 3 } func f() int { runtime.Notify("Valuable notification", 1) return 2 } ```
This commit is contained in:
parent
4531f79a4b
commit
1dcbdb011a
2 changed files with 80 additions and 15 deletions
|
@ -49,11 +49,15 @@ func (c *codegen) getIdentName(pkg string, name string) string {
|
||||||
func (c *codegen) traverseGlobals() bool {
|
func (c *codegen) traverseGlobals() bool {
|
||||||
var hasDefer bool
|
var hasDefer bool
|
||||||
var n, nConst int
|
var n, nConst int
|
||||||
|
var hasUnusedCall bool
|
||||||
var hasDeploy bool
|
var hasDeploy bool
|
||||||
c.ForEachFile(func(f *ast.File, pkg *types.Package) {
|
c.ForEachFile(func(f *ast.File, pkg *types.Package) {
|
||||||
nv, nc := countGlobals(f)
|
nv, nc, huc := countGlobals(f, !hasUnusedCall)
|
||||||
n += nv
|
n += nv
|
||||||
nConst += nc
|
nConst += nc
|
||||||
|
if huc {
|
||||||
|
hasUnusedCall = true
|
||||||
|
}
|
||||||
if !hasDeploy || !hasDefer {
|
if !hasDeploy || !hasDefer {
|
||||||
ast.Inspect(f, func(node ast.Node) bool {
|
ast.Inspect(f, func(node ast.Node) bool {
|
||||||
switch n := node.(type) {
|
switch n := node.(type) {
|
||||||
|
@ -85,7 +89,10 @@ func (c *codegen) traverseGlobals() bool {
|
||||||
|
|
||||||
lastCnt, maxCnt := -1, -1
|
lastCnt, maxCnt := -1, -1
|
||||||
c.ForEachPackage(func(pkg *packages.Package) {
|
c.ForEachPackage(func(pkg *packages.Package) {
|
||||||
if n+nConst > 0 {
|
// TODO: @optimizeme: it could happen that we don't need the whole set of globals to be emitted.
|
||||||
|
// We don't need the code for unused var at all, but at the same time we need to emit code for those
|
||||||
|
// vars that have function call inside. Thus convertGlobals should be able to distinguish these cases.
|
||||||
|
if n+nConst > 0 || hasUnusedCall {
|
||||||
for _, f := range pkg.Syntax {
|
for _, f := range pkg.Syntax {
|
||||||
c.fillImportMap(f, pkg)
|
c.fillImportMap(f, pkg)
|
||||||
c.convertGlobals(f, pkg.Types)
|
c.convertGlobals(f, pkg.Types)
|
||||||
|
@ -143,26 +150,37 @@ func (c *codegen) traverseGlobals() bool {
|
||||||
// countGlobals counts the global variables in the program to add
|
// countGlobals counts the global variables in the program to add
|
||||||
// them with the stack size of the function.
|
// them with the stack size of the function.
|
||||||
// Second returned argument contains the amount of global constants.
|
// Second returned argument contains the amount of global constants.
|
||||||
func countGlobals(f ast.Node) (int, int) {
|
// If checkUnusedCalls set to true then unnamed global variables containing call
|
||||||
|
// will be searched for and their presence is returned as the last argument.
|
||||||
|
func countGlobals(f ast.Node, checkUnusedCalls bool) (int, int, bool) {
|
||||||
var numVar, numConst int
|
var numVar, numConst int
|
||||||
|
var hasUnusedCall bool
|
||||||
ast.Inspect(f, func(node ast.Node) bool {
|
ast.Inspect(f, func(node ast.Node) bool {
|
||||||
switch n := node.(type) {
|
switch n := node.(type) {
|
||||||
// Skip all function declarations if we have already encountered `defer`.
|
// Skip all function declarations if we have already encountered `defer`.
|
||||||
case *ast.FuncDecl:
|
case *ast.FuncDecl:
|
||||||
return false
|
return false
|
||||||
// After skipping all funcDecls, we are sure that each value spec
|
// After skipping all funcDecls, we are sure that each value spec
|
||||||
// is a global declared variable or constant.
|
// is a globally declared variable or constant.
|
||||||
case *ast.GenDecl:
|
case *ast.GenDecl:
|
||||||
isVar := n.Tok == token.VAR
|
isVar := n.Tok == token.VAR
|
||||||
if isVar || n.Tok == token.CONST {
|
if isVar || n.Tok == token.CONST {
|
||||||
for _, s := range n.Specs {
|
for _, s := range n.Specs {
|
||||||
for _, id := range s.(*ast.ValueSpec).Names {
|
valueSpec := s.(*ast.ValueSpec)
|
||||||
|
multiRet := len(valueSpec.Values) != 0 && len(valueSpec.Names) != len(valueSpec.Values) // e.g. var A, B = f() where func f() (int, int)
|
||||||
|
for j, id := range valueSpec.Names {
|
||||||
if id.Name != "_" {
|
if id.Name != "_" {
|
||||||
if isVar {
|
if isVar {
|
||||||
numVar++
|
numVar++
|
||||||
} else {
|
} else {
|
||||||
numConst++
|
numConst++
|
||||||
}
|
}
|
||||||
|
} else if isVar && len(valueSpec.Values) != 0 && checkUnusedCalls && !hasUnusedCall {
|
||||||
|
indexToCheck := j
|
||||||
|
if multiRet {
|
||||||
|
indexToCheck = 0
|
||||||
|
}
|
||||||
|
hasUnusedCall = containsCall(valueSpec.Values[indexToCheck])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -171,7 +189,23 @@ func countGlobals(f ast.Node) (int, int) {
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
return numVar, numConst
|
return numVar, numConst, hasUnusedCall
|
||||||
|
}
|
||||||
|
|
||||||
|
// containsCall traverses node and looks if it contains a function or method call.
|
||||||
|
func containsCall(n ast.Node) bool {
|
||||||
|
var hasCall bool
|
||||||
|
ast.Inspect(n, func(node ast.Node) bool {
|
||||||
|
switch node.(type) {
|
||||||
|
case *ast.CallExpr:
|
||||||
|
hasCall = true
|
||||||
|
case *ast.Ident:
|
||||||
|
// Can safely skip idents immediately, we're interested at function calls only.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return !hasCall
|
||||||
|
})
|
||||||
|
return hasCall
|
||||||
}
|
}
|
||||||
|
|
||||||
// isExprNil looks if the given expression is a `nil`.
|
// isExprNil looks if the given expression is a `nil`.
|
||||||
|
|
|
@ -13,15 +13,46 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestUnusedGlobal(t *testing.T) {
|
func TestUnusedGlobal(t *testing.T) {
|
||||||
src := `package foo
|
t.Run("simple unused", func(t *testing.T) {
|
||||||
const (
|
src := `package foo
|
||||||
_ int = iota
|
const (
|
||||||
a
|
_ int = iota
|
||||||
)
|
a
|
||||||
func Main() int {
|
)
|
||||||
return 1
|
func Main() int {
|
||||||
}`
|
return 1
|
||||||
eval(t, src, big.NewInt(1))
|
}`
|
||||||
|
prog := eval(t, src, big.NewInt(1))
|
||||||
|
require.Equal(t, 2, len(prog)) // PUSH1 + RET
|
||||||
|
})
|
||||||
|
t.Run("unused with function call inside", func(t *testing.T) {
|
||||||
|
t.Run("specification names count matches values count", func(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
var control int
|
||||||
|
var _ = f()
|
||||||
|
func Main() int {
|
||||||
|
return control
|
||||||
|
}
|
||||||
|
func f() int {
|
||||||
|
control = 1
|
||||||
|
return 5
|
||||||
|
}`
|
||||||
|
eval(t, src, big.NewInt(1))
|
||||||
|
})
|
||||||
|
t.Run("specification names count differs from values count", func(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
var control int
|
||||||
|
var _, _ = f()
|
||||||
|
func Main() int {
|
||||||
|
return control
|
||||||
|
}
|
||||||
|
func f() (int, int) {
|
||||||
|
control = 1
|
||||||
|
return 5, 6
|
||||||
|
}`
|
||||||
|
eval(t, src, big.NewInt(1))
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestChangeGlobal(t *testing.T) {
|
func TestChangeGlobal(t *testing.T) {
|
||||||
|
|
Loading…
Reference in a new issue