forked from TrueCloudLab/neoneo-go
Merge pull request #1921 from nspcc-dev/compiler/locals
compiler: drop `countLocals` logic
This commit is contained in:
commit
399584504e
10 changed files with 209 additions and 238 deletions
|
@ -86,7 +86,7 @@ func TestNEP17Balance(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
e.checkNextLine(t, "^\\s*$")
|
e.checkNextLine(t, "^\\s*$")
|
||||||
addr4, err := address.StringToUint160("NQKpygA5oG8KRivZeYjXVU2T1ZPmUmaqQF") // deployed verify.go contract
|
addr4, err := address.StringToUint160("NU4CTk9H2fgNCuC3ZPqX4LjUX3MHt3Rh6p") // deployed verify.go contract
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
e.checkNextLine(t, "^Account "+address.Uint160ToString(addr4))
|
e.checkNextLine(t, "^Account "+address.Uint160ToString(addr4))
|
||||||
e.checkEOF(t)
|
e.checkEOF(t)
|
||||||
|
|
4
cli/testdata/wallet1_solo.json
vendored
4
cli/testdata/wallet1_solo.json
vendored
|
@ -63,9 +63,9 @@
|
||||||
"key" : "6PYM8VdX2BSm7BSXKzV4Fz6S3R9cDLLWNrD9nMjxW352jEv3fsC8N3wNLY"
|
"key" : "6PYM8VdX2BSm7BSXKzV4Fz6S3R9cDLLWNrD9nMjxW352jEv3fsC8N3wNLY"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"address" : "NQKpygA5oG8KRivZeYjXVU2T1ZPmUmaqQF",
|
"address" : "NU4CTk9H2fgNCuC3ZPqX4LjUX3MHt3Rh6p",
|
||||||
"contract" : {
|
"contract" : {
|
||||||
"script" : "VwEAEdsgQFcAA0BXAQR4eXp7VBTAcAwOT25ORVAxMVBheW1lbnRoUEGVAW9hIUA=",
|
"script" : "EdsgQFcAA0BXAQR4eXp7VBTAcAwOT25ORVAxMVBheW1lbnRoUEGVAW9hIUA=",
|
||||||
"parameters" : [],
|
"parameters" : [],
|
||||||
"deployed" : true
|
"deployed" : true
|
||||||
},
|
},
|
||||||
|
|
|
@ -37,46 +37,20 @@ 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.
|
// It returns `true` if contract has `_deploy` function.
|
||||||
// Second return value is -1 if no `init()` functions were encountered
|
func (c *codegen) traverseGlobals() bool {
|
||||||
// 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) {
|
|
||||||
var hasDefer bool
|
var hasDefer bool
|
||||||
var n, nConst int
|
var n, nConst int
|
||||||
initLocals := -1
|
var hasDeploy bool
|
||||||
deployLocals := -1
|
|
||||||
c.ForEachFile(func(f *ast.File, pkg *types.Package) {
|
c.ForEachFile(func(f *ast.File, pkg *types.Package) {
|
||||||
nv, nc := countGlobals(f)
|
nv, nc := countGlobals(f)
|
||||||
n += nv
|
n += nv
|
||||||
nConst += nc
|
nConst += nc
|
||||||
if initLocals == -1 || deployLocals == -1 || !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) {
|
||||||
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:
|
case *ast.FuncDecl:
|
||||||
if isInitFunc(n) {
|
hasDeploy = hasDeploy || isDeployFunc(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
|
|
||||||
case *ast.DeferStmt:
|
case *ast.DeferStmt:
|
||||||
hasDefer = true
|
hasDefer = true
|
||||||
return false
|
return false
|
||||||
|
@ -88,18 +62,20 @@ func (c *codegen) traverseGlobals() (int, int, int) {
|
||||||
if hasDefer {
|
if hasDefer {
|
||||||
n++
|
n++
|
||||||
}
|
}
|
||||||
if n+nConst != 0 || initLocals > -1 {
|
|
||||||
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, initLocals, deployLocals
|
return hasDeploy
|
||||||
}
|
}
|
||||||
|
|
||||||
if n != 0 {
|
if n != 0 {
|
||||||
emit.Instruction(c.prog.BinWriter, opcode.INITSSLOT, []byte{byte(n)})
|
emit.Instruction(c.prog.BinWriter, opcode.INITSSLOT, []byte{byte(n)})
|
||||||
}
|
}
|
||||||
if initLocals > 0 {
|
|
||||||
emit.Instruction(c.prog.BinWriter, opcode.INITSLOT, []byte{byte(initLocals), 0})
|
initOffset := c.prog.Len()
|
||||||
}
|
emit.Instruction(c.prog.BinWriter, opcode.INITSLOT, []byte{0, 0})
|
||||||
seenBefore := false
|
|
||||||
|
lastCnt, maxCnt := -1, -1
|
||||||
c.ForEachPackage(func(pkg *loader.PackageInfo) {
|
c.ForEachPackage(func(pkg *loader.PackageInfo) {
|
||||||
if n+nConst > 0 {
|
if n+nConst > 0 {
|
||||||
for _, f := range pkg.Files {
|
for _, f := range pkg.Files {
|
||||||
|
@ -107,10 +83,13 @@ func (c *codegen) traverseGlobals() (int, int, int) {
|
||||||
c.convertGlobals(f, pkg.Pkg)
|
c.convertGlobals(f, pkg.Pkg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if initLocals > -1 {
|
|
||||||
for _, f := range pkg.Files {
|
for _, f := range pkg.Files {
|
||||||
c.fillImportMap(f, pkg.Pkg)
|
c.fillImportMap(f, pkg.Pkg)
|
||||||
seenBefore = c.convertInitFuncs(f, pkg.Pkg, seenBefore) || seenBefore
|
|
||||||
|
var currMax int
|
||||||
|
lastCnt, currMax = c.convertInitFuncs(f, pkg.Pkg, lastCnt)
|
||||||
|
if currMax > maxCnt {
|
||||||
|
maxCnt = currMax
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// because we reuse `convertFuncDecl` for init funcs,
|
// because we reuse `convertFuncDecl` for init funcs,
|
||||||
|
@ -118,13 +97,39 @@ func (c *codegen) traverseGlobals() (int, int, int) {
|
||||||
// encountered after will be recognized as globals.
|
// encountered after will be recognized as globals.
|
||||||
c.scope = nil
|
c.scope = nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if c.globalInlineCount > maxCnt {
|
||||||
|
maxCnt = c.globalInlineCount
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// store auxiliary variables after all others.
|
// store auxiliary variables after all others.
|
||||||
if hasDefer {
|
if hasDefer {
|
||||||
c.exceptionIndex = len(c.globals)
|
c.exceptionIndex = len(c.globals)
|
||||||
c.globals["<exception>"] = c.exceptionIndex
|
c.globals[exceptionVarName] = c.exceptionIndex
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return n, initLocals, deployLocals
|
return hasDeploy
|
||||||
}
|
}
|
||||||
|
|
||||||
// countGlobals counts the global variables in the program to add
|
// countGlobals counts the global variables in the program to add
|
||||||
|
|
|
@ -40,6 +40,9 @@ type codegen struct {
|
||||||
// A mapping of lambda functions into their scope.
|
// A mapping of lambda functions into their scope.
|
||||||
lambda map[string]*funcScope
|
lambda map[string]*funcScope
|
||||||
|
|
||||||
|
// reverseOffsetMap maps function offsets to a local variable count.
|
||||||
|
reverseOffsetMap map[int]nameWithLocals
|
||||||
|
|
||||||
// Current funcScope being converted.
|
// Current funcScope being converted.
|
||||||
scope *funcScope
|
scope *funcScope
|
||||||
|
|
||||||
|
@ -58,6 +61,9 @@ type codegen struct {
|
||||||
// inlineLabelOffsets contains size of labelList at the start of inline call processing.
|
// inlineLabelOffsets contains size of labelList at the start of inline call processing.
|
||||||
// For such calls we need to drop only newly created part of stack.
|
// For such calls we need to drop only newly created part of stack.
|
||||||
inlineLabelOffsets []int
|
inlineLabelOffsets []int
|
||||||
|
// globalInlineCount contains amount of auxiliary variables introduced by
|
||||||
|
// function inlining during global variables initialization.
|
||||||
|
globalInlineCount int
|
||||||
|
|
||||||
// A label for the for-loop being currently visited.
|
// A label for the for-loop being currently visited.
|
||||||
currentFor string
|
currentFor string
|
||||||
|
@ -124,6 +130,11 @@ type labelWithStackSize struct {
|
||||||
sz int
|
sz int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type nameWithLocals struct {
|
||||||
|
name string
|
||||||
|
count int
|
||||||
|
}
|
||||||
|
|
||||||
type varType int
|
type varType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -333,24 +344,30 @@ func (c *codegen) clearSlots(n int) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *codegen) convertInitFuncs(f *ast.File, pkg *types.Package, seenBefore bool) bool {
|
// convertInitFuncs converts `init()` functions in file f and returns
|
||||||
|
// number of locals in last processed definition as well as maximum locals number encountered.
|
||||||
|
func (c *codegen) convertInitFuncs(f *ast.File, pkg *types.Package, lastCount int) (int, int) {
|
||||||
|
maxCount := -1
|
||||||
ast.Inspect(f, func(node ast.Node) bool {
|
ast.Inspect(f, func(node ast.Node) bool {
|
||||||
switch n := node.(type) {
|
switch n := node.(type) {
|
||||||
case *ast.FuncDecl:
|
case *ast.FuncDecl:
|
||||||
if isInitFunc(n) {
|
if isInitFunc(n) {
|
||||||
if seenBefore {
|
if lastCount != -1 {
|
||||||
cnt, _ := c.countLocals(n)
|
c.clearSlots(lastCount)
|
||||||
c.clearSlots(cnt)
|
}
|
||||||
seenBefore = true
|
|
||||||
|
f := c.convertFuncDecl(f, n, pkg)
|
||||||
|
lastCount = f.vars.localsCnt
|
||||||
|
if lastCount > maxCount {
|
||||||
|
maxCount = lastCount
|
||||||
}
|
}
|
||||||
c.convertFuncDecl(f, n, pkg)
|
|
||||||
}
|
}
|
||||||
case *ast.GenDecl:
|
case *ast.GenDecl:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
return seenBefore
|
return lastCount, maxCount
|
||||||
}
|
}
|
||||||
|
|
||||||
func isDeployFunc(decl *ast.FuncDecl) bool {
|
func isDeployFunc(decl *ast.FuncDecl) bool {
|
||||||
|
@ -363,19 +380,22 @@ func isDeployFunc(decl *ast.FuncDecl) bool {
|
||||||
return ok && typ.Name == "bool"
|
return ok && typ.Name == "bool"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *codegen) convertDeployFuncs() {
|
func (c *codegen) convertDeployFuncs() int {
|
||||||
seenBefore := false
|
maxCount, lastCount := 0, -1
|
||||||
c.ForEachFile(func(f *ast.File, pkg *types.Package) {
|
c.ForEachFile(func(f *ast.File, pkg *types.Package) {
|
||||||
ast.Inspect(f, func(node ast.Node) bool {
|
ast.Inspect(f, func(node ast.Node) bool {
|
||||||
switch n := node.(type) {
|
switch n := node.(type) {
|
||||||
case *ast.FuncDecl:
|
case *ast.FuncDecl:
|
||||||
if isDeployFunc(n) {
|
if isDeployFunc(n) {
|
||||||
if seenBefore {
|
if lastCount != -1 {
|
||||||
cnt, _ := c.countLocals(n)
|
c.clearSlots(lastCount)
|
||||||
c.clearSlots(cnt)
|
}
|
||||||
|
|
||||||
|
f := c.convertFuncDecl(f, n, pkg)
|
||||||
|
lastCount = f.vars.localsCnt
|
||||||
|
if lastCount > maxCount {
|
||||||
|
maxCount = lastCount
|
||||||
}
|
}
|
||||||
c.convertFuncDecl(f, n, pkg)
|
|
||||||
seenBefore = true
|
|
||||||
}
|
}
|
||||||
case *ast.GenDecl:
|
case *ast.GenDecl:
|
||||||
return false
|
return false
|
||||||
|
@ -383,9 +403,10 @@ func (c *codegen) convertDeployFuncs() {
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
return maxCount
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl, pkg *types.Package) {
|
func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl, pkg *types.Package) *funcScope {
|
||||||
var (
|
var (
|
||||||
f *funcScope
|
f *funcScope
|
||||||
ok, isLambda bool
|
ok, isLambda bool
|
||||||
|
@ -399,7 +420,7 @@ func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl, pkg *types.
|
||||||
if ok {
|
if ok {
|
||||||
// If this function is a syscall or builtin we will not convert it to bytecode.
|
// If this function is a syscall or builtin we will not convert it to bytecode.
|
||||||
if isSyscall(f) || isCustomBuiltin(f) {
|
if isSyscall(f) || isCustomBuiltin(f) {
|
||||||
return
|
return f
|
||||||
}
|
}
|
||||||
c.setLabel(f.label)
|
c.setLabel(f.label)
|
||||||
} else if f, ok = c.lambda[c.getIdentName("", decl.Name.Name)]; ok {
|
} else if f, ok = c.lambda[c.getIdentName("", decl.Name.Name)]; ok {
|
||||||
|
@ -417,17 +438,11 @@ func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl, pkg *types.
|
||||||
// All globals copied into the scope of the function need to be added
|
// All globals copied into the scope of the function need to be added
|
||||||
// to the stack size of the function.
|
// to the stack size of the function.
|
||||||
if !isInit && !isDeploy {
|
if !isInit && !isDeploy {
|
||||||
sizeLoc := c.countLocalsWithDefer(f)
|
|
||||||
if sizeLoc > 255 {
|
|
||||||
c.prog.Err = errors.New("maximum of 255 local variables is allowed")
|
|
||||||
}
|
|
||||||
sizeArg := f.countArgs()
|
sizeArg := f.countArgs()
|
||||||
if sizeArg > 255 {
|
if sizeArg > 255 {
|
||||||
c.prog.Err = errors.New("maximum of 255 local variables is allowed")
|
c.prog.Err = errors.New("maximum of 255 local variables is allowed")
|
||||||
}
|
}
|
||||||
if sizeLoc != 0 || sizeArg != 0 {
|
emit.Instruction(c.prog.BinWriter, opcode.INITSLOT, []byte{byte(0), byte(sizeArg)})
|
||||||
emit.Instruction(c.prog.BinWriter, opcode.INITSLOT, []byte{byte(sizeLoc), byte(sizeArg)})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
f.vars.newScope()
|
f.vars.newScope()
|
||||||
|
@ -478,6 +493,14 @@ func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl, pkg *types.
|
||||||
}
|
}
|
||||||
c.lambda = make(map[string]*funcScope)
|
c.lambda = make(map[string]*funcScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !isInit && !isDeploy {
|
||||||
|
c.reverseOffsetMap[int(f.rng.Start)] = nameWithLocals{
|
||||||
|
name: f.name,
|
||||||
|
count: f.vars.localsCnt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
|
@ -1296,7 +1319,9 @@ func (c *codegen) processDefers() {
|
||||||
c.setLabel(stmt.catchLabel)
|
c.setLabel(stmt.catchLabel)
|
||||||
c.emitStoreByIndex(varGlobal, c.exceptionIndex)
|
c.emitStoreByIndex(varGlobal, c.exceptionIndex)
|
||||||
emit.Int(c.prog.BinWriter, 1)
|
emit.Int(c.prog.BinWriter, 1)
|
||||||
c.emitStoreByIndex(varLocal, c.scope.finallyProcessedIndex)
|
|
||||||
|
finalIndex := c.getVarIndex("", finallyVarName).index
|
||||||
|
c.emitStoreByIndex(varLocal, finalIndex)
|
||||||
ast.Walk(c, stmt.expr)
|
ast.Walk(c, stmt.expr)
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
// After panic, default values must be returns, except for named returns,
|
// After panic, default values must be returns, except for named returns,
|
||||||
|
@ -1309,12 +1334,12 @@ func (c *codegen) processDefers() {
|
||||||
|
|
||||||
c.setLabel(stmt.finallyLabel)
|
c.setLabel(stmt.finallyLabel)
|
||||||
before := c.newLabel()
|
before := c.newLabel()
|
||||||
c.emitLoadByIndex(varLocal, c.scope.finallyProcessedIndex)
|
c.emitLoadByIndex(varLocal, finalIndex)
|
||||||
emit.Jmp(c.prog.BinWriter, opcode.JMPIFL, before)
|
emit.Jmp(c.prog.BinWriter, opcode.JMPIFL, before)
|
||||||
ast.Walk(c, stmt.expr)
|
ast.Walk(c, stmt.expr)
|
||||||
c.setLabel(before)
|
c.setLabel(before)
|
||||||
emit.Int(c.prog.BinWriter, 0)
|
emit.Int(c.prog.BinWriter, 0)
|
||||||
c.emitStoreByIndex(varLocal, c.scope.finallyProcessedIndex)
|
c.emitStoreByIndex(varLocal, finalIndex)
|
||||||
emit.Opcodes(c.prog.BinWriter, opcode.ENDFINALLY)
|
emit.Opcodes(c.prog.BinWriter, opcode.ENDFINALLY)
|
||||||
c.setLabel(after)
|
c.setLabel(after)
|
||||||
}
|
}
|
||||||
|
@ -1959,17 +1984,16 @@ 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, initLocals, deployLocals := c.traverseGlobals()
|
hasDeploy := c.traverseGlobals()
|
||||||
hasInit := initLocals > -1
|
|
||||||
if n > 0 || hasInit {
|
|
||||||
c.initEndOffset = c.prog.Len()
|
|
||||||
emit.Opcodes(c.prog.BinWriter, opcode.RET)
|
|
||||||
}
|
|
||||||
|
|
||||||
hasDeploy := deployLocals > -1
|
|
||||||
if hasDeploy {
|
if hasDeploy {
|
||||||
emit.Instruction(c.prog.BinWriter, opcode.INITSLOT, []byte{byte(deployLocals), 2})
|
deployOffset := c.prog.Len()
|
||||||
c.convertDeployFuncs()
|
emit.Instruction(c.prog.BinWriter, opcode.INITSLOT, []byte{0, 2})
|
||||||
|
locCount := c.convertDeployFuncs()
|
||||||
|
c.reverseOffsetMap[deployOffset] = nameWithLocals{
|
||||||
|
name: "_deploy",
|
||||||
|
count: locCount,
|
||||||
|
}
|
||||||
c.deployEndOffset = c.prog.Len()
|
c.deployEndOffset = c.prog.Len()
|
||||||
emit.Opcodes(c.prog.BinWriter, opcode.RET)
|
emit.Opcodes(c.prog.BinWriter, opcode.RET)
|
||||||
}
|
}
|
||||||
|
@ -2011,6 +2035,7 @@ func newCodegen(info *buildInfo, pkg *loader.PackageInfo) *codegen {
|
||||||
l: []int{},
|
l: []int{},
|
||||||
funcs: map[string]*funcScope{},
|
funcs: map[string]*funcScope{},
|
||||||
lambda: map[string]*funcScope{},
|
lambda: map[string]*funcScope{},
|
||||||
|
reverseOffsetMap: map[int]nameWithLocals{},
|
||||||
globals: map[string]int{},
|
globals: map[string]int{},
|
||||||
labels: map[labelWithType]uint16{},
|
labels: map[labelWithType]uint16{},
|
||||||
typeInfo: &pkg.Info,
|
typeInfo: &pkg.Info,
|
||||||
|
@ -2085,6 +2110,18 @@ func (c *codegen) writeJumps(b []byte) ([]byte, error) {
|
||||||
if op != opcode.PUSHA && math.MinInt8 <= offset && offset <= math.MaxInt8 {
|
if op != opcode.PUSHA && math.MinInt8 <= offset && offset <= math.MaxInt8 {
|
||||||
offsets = append(offsets, ctx.IP())
|
offsets = append(offsets, ctx.IP())
|
||||||
}
|
}
|
||||||
|
case opcode.INITSLOT:
|
||||||
|
nextIP := ctx.NextIP()
|
||||||
|
info := c.reverseOffsetMap[ctx.IP()]
|
||||||
|
if argCount := b[nextIP-1]; info.count == 0 && argCount == 0 {
|
||||||
|
offsets = append(offsets, ctx.IP())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.count > 255 {
|
||||||
|
return nil, fmt.Errorf("func '%s' has %d local variables (maximum is 255)", info.name, info.count)
|
||||||
|
}
|
||||||
|
b[nextIP-2] = byte(info.count)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2137,6 +2174,7 @@ func (c *codegen) replaceLabelWithOffset(ip int, arg []byte) (int, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// longToShortRemoveCount is a difference between short and long instruction sizes in bytes.
|
// longToShortRemoveCount is a difference between short and long instruction sizes in bytes.
|
||||||
|
// By pure coincidence, this is also the size of `INITSLOT` instruction.
|
||||||
const longToShortRemoveCount = 3
|
const longToShortRemoveCount = 3
|
||||||
|
|
||||||
// shortenJumps returns converts b to a program where all long JMP*/CALL* specified by absolute offsets,
|
// shortenJumps returns converts b to a program where all long JMP*/CALL* specified by absolute offsets,
|
||||||
|
@ -2194,13 +2232,22 @@ func shortenJumps(b []byte, offsets []int) []byte {
|
||||||
// 2. Convert instructions.
|
// 2. Convert instructions.
|
||||||
copyOffset := 0
|
copyOffset := 0
|
||||||
l := len(offsets)
|
l := len(offsets)
|
||||||
b[offsets[0]] = byte(toShortForm(opcode.Opcode(b[offsets[0]])))
|
if op := opcode.Opcode(b[offsets[0]]); op != opcode.INITSLOT {
|
||||||
|
b[offsets[0]] = byte(toShortForm(op))
|
||||||
|
}
|
||||||
for i := 0; i < l; i++ {
|
for i := 0; i < l; i++ {
|
||||||
start := offsets[i] + 2
|
start := offsets[i] + 2
|
||||||
|
if b[offsets[i]] == byte(opcode.INITSLOT) {
|
||||||
|
start = offsets[i]
|
||||||
|
}
|
||||||
|
|
||||||
end := len(b)
|
end := len(b)
|
||||||
if i != l-1 {
|
if i != l-1 {
|
||||||
end = offsets[i+1] + 2
|
end = offsets[i+1]
|
||||||
b[offsets[i+1]] = byte(toShortForm(opcode.Opcode(b[offsets[i+1]])))
|
if op := opcode.Opcode(b[offsets[i+1]]); op != opcode.INITSLOT {
|
||||||
|
end += 2
|
||||||
|
b[offsets[i+1]] = byte(toShortForm(op))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
copy(b[start-copyOffset:], b[start+3:end])
|
copy(b[start-copyOffset:], b[start+3:end])
|
||||||
copyOffset += longToShortRemoveCount
|
copyOffset += longToShortRemoveCount
|
||||||
|
|
|
@ -73,7 +73,9 @@ func _deploy(data interface{}, isUpdate bool) { x := 1; _ = x }
|
||||||
c := newCodegen(info, pkg)
|
c := newCodegen(info, pkg)
|
||||||
require.NoError(t, c.compile(info, pkg))
|
require.NoError(t, c.compile(info, pkg))
|
||||||
|
|
||||||
buf := c.prog.Bytes()
|
buf, err := c.writeJumps(c.prog.Bytes())
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
d := c.emitDebugInfo(buf)
|
d := c.emitDebugInfo(buf)
|
||||||
require.NotNil(t, d)
|
require.NotNil(t, d)
|
||||||
|
|
||||||
|
|
|
@ -137,3 +137,14 @@ func TestRecover(t *testing.T) {
|
||||||
eval(t, src, big.NewInt(5))
|
eval(t, src, big.NewInt(5))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDeferNoGlobals(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
func Main() int {
|
||||||
|
a := 1
|
||||||
|
defer func() { recover() }()
|
||||||
|
panic("msg")
|
||||||
|
return a
|
||||||
|
}`
|
||||||
|
eval(t, src, big.NewInt(0))
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ package compiler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/token"
|
|
||||||
"go/types"
|
"go/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -34,9 +33,6 @@ type funcScope struct {
|
||||||
|
|
||||||
// deferStack is a stack containing encountered `defer` statements.
|
// deferStack is a stack containing encountered `defer` statements.
|
||||||
deferStack []deferInfo
|
deferStack []deferInfo
|
||||||
// finallyProcessed is a index of static slot with boolean flag determining
|
|
||||||
// if `defer` statement was already processed.
|
|
||||||
finallyProcessedIndex int
|
|
||||||
|
|
||||||
// Local variables
|
// Local variables
|
||||||
vars varScope
|
vars varScope
|
||||||
|
@ -59,6 +55,11 @@ type deferInfo struct {
|
||||||
expr *ast.CallExpr
|
expr *ast.CallExpr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
finallyVarName = "<finally>"
|
||||||
|
exceptionVarName = "<exception>"
|
||||||
|
)
|
||||||
|
|
||||||
func (c *codegen) newFuncScope(decl *ast.FuncDecl, label uint16) *funcScope {
|
func (c *codegen) newFuncScope(decl *ast.FuncDecl, label uint16) *funcScope {
|
||||||
var name string
|
var name string
|
||||||
if decl.Name != nil {
|
if decl.Name != nil {
|
||||||
|
@ -102,114 +103,6 @@ func (c *funcScope) analyzeVoidCalls(node ast.Node) bool {
|
||||||
return true
|
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *codegen) countLocalsInline(decl *ast.FuncDecl, pkg *types.Package, f *funcScope) (int, bool) {
|
|
||||||
oldMap := c.importMap
|
|
||||||
if pkg != nil {
|
|
||||||
c.fillImportMap(f.file, pkg)
|
|
||||||
}
|
|
||||||
|
|
||||||
size := 0
|
|
||||||
hasDefer := false
|
|
||||||
ast.Inspect(decl, func(n ast.Node) bool {
|
|
||||||
switch n := n.(type) {
|
|
||||||
case *ast.CallExpr:
|
|
||||||
size += c.countLocalsCall(n, pkg)
|
|
||||||
case *ast.FuncType:
|
|
||||||
num := n.Results.NumFields()
|
|
||||||
if num != 0 && len(n.Results.List[0].Names) != 0 {
|
|
||||||
size += num
|
|
||||||
}
|
|
||||||
case *ast.AssignStmt:
|
|
||||||
if n.Tok == token.DEFINE {
|
|
||||||
size += len(n.Lhs)
|
|
||||||
}
|
|
||||||
case *ast.DeferStmt:
|
|
||||||
hasDefer = true
|
|
||||||
return false
|
|
||||||
case *ast.ReturnStmt:
|
|
||||||
if pkg == nil {
|
|
||||||
size++
|
|
||||||
}
|
|
||||||
case *ast.IfStmt:
|
|
||||||
size++
|
|
||||||
// This handles the inline GenDecl like "var x = 2"
|
|
||||||
case *ast.ValueSpec:
|
|
||||||
size += len(n.Names)
|
|
||||||
case *ast.RangeStmt:
|
|
||||||
if n.Tok == token.DEFINE {
|
|
||||||
if n.Key != nil {
|
|
||||||
size++
|
|
||||||
}
|
|
||||||
if n.Value != nil {
|
|
||||||
size++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
if pkg != nil {
|
|
||||||
c.importMap = oldMap
|
|
||||||
}
|
|
||||||
return size, hasDefer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *codegen) countLocalsWithDefer(f *funcScope) int {
|
|
||||||
size, hasDefer := c.countLocals(f.decl)
|
|
||||||
if hasDefer {
|
|
||||||
f.finallyProcessedIndex = size
|
|
||||||
size++
|
|
||||||
}
|
|
||||||
return size
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *funcScope) countArgs() int {
|
func (c *funcScope) countArgs() int {
|
||||||
n := c.decl.Type.Params.NumFields()
|
n := c.decl.Type.Params.NumFields()
|
||||||
if c.decl.Recv != nil {
|
if c.decl.Recv != nil {
|
||||||
|
|
|
@ -306,7 +306,10 @@ func TestJumpOptimize(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 6, len(di.Methods))
|
require.Equal(t, 6, len(di.Methods))
|
||||||
for _, mi := range di.Methods {
|
for _, mi := range di.Methods {
|
||||||
|
// only _deploy and init have locals here
|
||||||
|
if mi.Name.Name == "_deploy" || mi.Name.Name == "init" {
|
||||||
require.Equal(t, b[mi.Range.Start], byte(opcode.INITSLOT))
|
require.Equal(t, b[mi.Range.Start], byte(opcode.INITSLOT))
|
||||||
|
}
|
||||||
require.Equal(t, b[mi.Range.End], byte(opcode.RET))
|
require.Equal(t, b[mi.Range.End], byte(opcode.RET))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,12 @@ func (c *codegen) inlineCall(f *funcScope, n *ast.CallExpr) {
|
||||||
if c.scope == nil {
|
if c.scope == nil {
|
||||||
c.scope = &funcScope{}
|
c.scope = &funcScope{}
|
||||||
c.scope.vars.newScope()
|
c.scope.vars.newScope()
|
||||||
defer func() { c.scope = nil }()
|
defer func() {
|
||||||
|
if cnt := c.scope.vars.localsCnt; cnt > c.globalInlineCount {
|
||||||
|
c.globalInlineCount = cnt
|
||||||
|
}
|
||||||
|
c.scope = nil
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Arguments need to be walked with the current scope,
|
// Arguments need to be walked with the current scope,
|
||||||
|
|
|
@ -30,7 +30,7 @@ func checkCallCount(t *testing.T, src string, expectedCall, expectedInitSlot int
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
require.Equal(t, expectedCall, actualCall)
|
require.Equal(t, expectedCall, actualCall)
|
||||||
require.Equal(t, expectedInitSlot, actualInitSlot)
|
require.True(t, expectedInitSlot == actualInitSlot)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInline(t *testing.T) {
|
func TestInline(t *testing.T) {
|
||||||
|
@ -47,34 +47,34 @@ func TestInline(t *testing.T) {
|
||||||
t.Run("no return", func(t *testing.T) {
|
t.Run("no return", func(t *testing.T) {
|
||||||
src := fmt.Sprintf(srcTmpl, `inline.NoArgsNoReturn()
|
src := fmt.Sprintf(srcTmpl, `inline.NoArgsNoReturn()
|
||||||
return 1`)
|
return 1`)
|
||||||
checkCallCount(t, src, 0, 1)
|
checkCallCount(t, src, 0, 0)
|
||||||
eval(t, src, big.NewInt(1))
|
eval(t, src, big.NewInt(1))
|
||||||
})
|
})
|
||||||
t.Run("has return, dropped", func(t *testing.T) {
|
t.Run("has return, dropped", func(t *testing.T) {
|
||||||
src := fmt.Sprintf(srcTmpl, `inline.NoArgsReturn1()
|
src := fmt.Sprintf(srcTmpl, `inline.NoArgsReturn1()
|
||||||
return 2`)
|
return 2`)
|
||||||
checkCallCount(t, src, 0, 1)
|
checkCallCount(t, src, 0, 0)
|
||||||
eval(t, src, big.NewInt(2))
|
eval(t, src, big.NewInt(2))
|
||||||
})
|
})
|
||||||
t.Run("drop twice", func(t *testing.T) {
|
t.Run("drop twice", func(t *testing.T) {
|
||||||
src := fmt.Sprintf(srcTmpl, `inline.DropInsideInline()
|
src := fmt.Sprintf(srcTmpl, `inline.DropInsideInline()
|
||||||
return 42`)
|
return 42`)
|
||||||
checkCallCount(t, src, 0, 1)
|
checkCallCount(t, src, 0, 0)
|
||||||
eval(t, src, big.NewInt(42))
|
eval(t, src, big.NewInt(42))
|
||||||
})
|
})
|
||||||
t.Run("no args return 1", func(t *testing.T) {
|
t.Run("no args return 1", func(t *testing.T) {
|
||||||
src := fmt.Sprintf(srcTmpl, `return inline.NoArgsReturn1()`)
|
src := fmt.Sprintf(srcTmpl, `return inline.NoArgsReturn1()`)
|
||||||
checkCallCount(t, src, 0, 1)
|
checkCallCount(t, src, 0, 0)
|
||||||
eval(t, src, big.NewInt(1))
|
eval(t, src, big.NewInt(1))
|
||||||
})
|
})
|
||||||
t.Run("sum", func(t *testing.T) {
|
t.Run("sum", func(t *testing.T) {
|
||||||
src := fmt.Sprintf(srcTmpl, `return inline.Sum(1, 2)`)
|
src := fmt.Sprintf(srcTmpl, `return inline.Sum(1, 2)`)
|
||||||
checkCallCount(t, src, 0, 1)
|
checkCallCount(t, src, 0, 0)
|
||||||
eval(t, src, big.NewInt(3))
|
eval(t, src, big.NewInt(3))
|
||||||
})
|
})
|
||||||
t.Run("sum squared (nested inline)", func(t *testing.T) {
|
t.Run("sum squared (nested inline)", func(t *testing.T) {
|
||||||
src := fmt.Sprintf(srcTmpl, `return inline.SumSquared(1, 2)`)
|
src := fmt.Sprintf(srcTmpl, `return inline.SumSquared(1, 2)`)
|
||||||
checkCallCount(t, src, 0, 1)
|
checkCallCount(t, src, 0, 0)
|
||||||
eval(t, src, big.NewInt(9))
|
eval(t, src, big.NewInt(9))
|
||||||
})
|
})
|
||||||
t.Run("inline function in inline function parameter", func(t *testing.T) {
|
t.Run("inline function in inline function parameter", func(t *testing.T) {
|
||||||
|
@ -84,7 +84,7 @@ func TestInline(t *testing.T) {
|
||||||
})
|
})
|
||||||
t.Run("global name clash", func(t *testing.T) {
|
t.Run("global name clash", func(t *testing.T) {
|
||||||
src := fmt.Sprintf(srcTmpl, `return inline.GetSumSameName()`)
|
src := fmt.Sprintf(srcTmpl, `return inline.GetSumSameName()`)
|
||||||
checkCallCount(t, src, 0, 1)
|
checkCallCount(t, src, 0, 0)
|
||||||
eval(t, src, big.NewInt(42))
|
eval(t, src, big.NewInt(42))
|
||||||
})
|
})
|
||||||
t.Run("local name clash", func(t *testing.T) {
|
t.Run("local name clash", func(t *testing.T) {
|
||||||
|
@ -110,6 +110,11 @@ func TestInline(t *testing.T) {
|
||||||
})
|
})
|
||||||
t.Run("globals", func(t *testing.T) {
|
t.Run("globals", func(t *testing.T) {
|
||||||
src := fmt.Sprintf(srcTmpl, `return inline.Concat(Num)`)
|
src := fmt.Sprintf(srcTmpl, `return inline.Concat(Num)`)
|
||||||
|
checkCallCount(t, src, 0, 0)
|
||||||
|
eval(t, src, big.NewInt(221))
|
||||||
|
})
|
||||||
|
t.Run("locals, alias", func(t *testing.T) {
|
||||||
|
src := fmt.Sprintf(srcTmpl, `num := 1; return inline.Concat(num)`)
|
||||||
checkCallCount(t, src, 0, 1)
|
checkCallCount(t, src, 0, 1)
|
||||||
eval(t, src, big.NewInt(221))
|
eval(t, src, big.NewInt(221))
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue