compiler: count local variables on the go

Create local variables as they are needed and remove `INITSLOT`
if there were no locals. This helps to eliminate a whole class
of bugs when calculated and real amount mismatched.
This commit is contained in:
Evgeniy Stratonikov 2021-04-28 16:10:44 +03:00
parent bfa2bafb04
commit b693d54282
9 changed files with 181 additions and 230 deletions

View file

@ -37,46 +37,20 @@ func (c *codegen) getIdentName(pkg string, name string) string {
}
// traverseGlobals visits and initializes global variables.
// and returns number of variables initialized.
// Second return value is -1 if no `init()` functions were encountered
// and number of maximum amount of locals in any of init functions otherwise.
// Same for `_deploy()` functions (see docs/compiler.md).
func (c *codegen) traverseGlobals() (int, int, int) {
// It returns `true` if contract has `_deploy` function.
func (c *codegen) traverseGlobals() bool {
var hasDefer bool
var n, nConst int
initLocals := -1
deployLocals := -1
var hasDeploy bool
c.ForEachFile(func(f *ast.File, pkg *types.Package) {
nv, nc := countGlobals(f)
n += nv
nConst += nc
if initLocals == -1 || deployLocals == -1 || !hasDefer {
if !hasDeploy || !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)
if num > initLocals {
initLocals = num
}
} else if isDeployFunc(n) {
num, _ := c.countLocals(n)
if num > deployLocals {
deployLocals = num
}
}
return !hasDefer
hasDeploy = hasDeploy || isDeployFunc(n)
case *ast.DeferStmt:
hasDefer = true
return false
@ -88,43 +62,70 @@ func (c *codegen) traverseGlobals() (int, int, int) {
if hasDefer {
n++
}
if n+nConst != 0 || initLocals > -1 {
if n > 255 {
c.prog.BinWriter.Err = errors.New("too many global variables")
return 0, initLocals, deployLocals
}
if n != 0 {
emit.Instruction(c.prog.BinWriter, opcode.INITSSLOT, []byte{byte(n)})
}
if initLocals > 0 {
emit.Instruction(c.prog.BinWriter, opcode.INITSLOT, []byte{byte(initLocals), 0})
}
seenBefore := false
c.ForEachPackage(func(pkg *loader.PackageInfo) {
if n+nConst > 0 {
for _, f := range pkg.Files {
c.fillImportMap(f, pkg.Pkg)
c.convertGlobals(f, pkg.Pkg)
}
if n > 255 {
c.prog.BinWriter.Err = errors.New("too many global variables")
return hasDeploy
}
if n != 0 {
emit.Instruction(c.prog.BinWriter, opcode.INITSSLOT, []byte{byte(n)})
}
initOffset := c.prog.Len()
emit.Instruction(c.prog.BinWriter, opcode.INITSLOT, []byte{0, 0})
lastCnt, maxCnt := -1, -1
c.ForEachPackage(func(pkg *loader.PackageInfo) {
if n+nConst > 0 {
for _, f := range pkg.Files {
c.fillImportMap(f, pkg.Pkg)
c.convertGlobals(f, pkg.Pkg)
}
if initLocals > -1 {
for _, f := range pkg.Files {
c.fillImportMap(f, pkg.Pkg)
seenBefore = c.convertInitFuncs(f, pkg.Pkg, seenBefore) || seenBefore
}
}
for _, f := range pkg.Files {
c.fillImportMap(f, pkg.Pkg)
var currMax int
lastCnt, currMax = c.convertInitFuncs(f, pkg.Pkg, lastCnt)
if currMax > maxCnt {
maxCnt = currMax
}
}
// because we reuse `convertFuncDecl` for init funcs,
// we need to cleare scope, so that global variables
// encountered after will be recognized as globals.
c.scope = nil
})
// Here we remove `INITSLOT` if no code was emitted for `init` function.
// Note that the `INITSSLOT` must stay in place.
hasNoInit := initOffset+3 == c.prog.Len()
if hasNoInit {
buf := c.prog.Bytes()
c.prog.Reset()
c.prog.WriteBytes(buf[:initOffset])
}
if initOffset != 0 || !hasNoInit { // if there are some globals or `init()`.
c.initEndOffset = c.prog.Len()
emit.Opcodes(c.prog.BinWriter, opcode.RET)
if maxCnt >= 0 {
c.reverseOffsetMap[initOffset] = nameWithLocals{
name: "init",
count: maxCnt,
}
// because we reuse `convertFuncDecl` for init funcs,
// we need to cleare scope, so that global variables
// encountered after will be recognized as globals.
c.scope = nil
})
// store auxiliary variables after all others.
if hasDefer {
c.exceptionIndex = len(c.globals)
c.globals[exceptionVarName] = c.exceptionIndex
}
}
return n, initLocals, deployLocals
// store auxiliary variables after all others.
if hasDefer {
c.exceptionIndex = len(c.globals)
c.globals[exceptionVarName] = c.exceptionIndex
}
return hasDeploy
}
// countGlobals counts the global variables in the program to add