neo-go/pkg/compiler/func_scope.go

160 lines
3.6 KiB
Go
Raw Normal View History

package compiler
import (
"go/ast"
"go/token"
"go/types"
)
// A funcScope represents the scope within the function context.
// It holds al the local variables along with the initialized struct positions.
type funcScope struct {
2019-10-22 14:56:03 +00:00
// Identifier of the function.
name string
// Selector of the function if there is any. Only functions imported
// from other packages should have a selector.
selector *ast.Ident
// The declaration of the function in the AST. Nil if this scope is not a function.
decl *ast.FuncDecl
// Package where the function is defined.
pkg *types.Package
// Program label of the scope
label uint16
// Range of opcodes corresponding to the function.
rng DebugRange
// Variables together with it's type in neo-vm.
variables []string
// Local variables
locals map[string]int
arguments map[string]int
// voidCalls are basically functions that return their value
// into nothing. The stack has their return value but there
// is nothing that consumes it. We need to keep track of
// these functions so we can cleanup (drop) the returned
// value from the stack. We also need to add every voidCall
// return value to the stack size.
voidCalls map[*ast.CallExpr]bool
2019-10-22 14:56:03 +00:00
// Local variable counter.
i int
}
func newFuncScope(decl *ast.FuncDecl, label uint16) *funcScope {
var name string
if decl.Name != nil {
name = decl.Name.Name
}
return &funcScope{
name: name,
decl: decl,
label: label,
locals: map[string]int{},
arguments: map[string]int{},
voidCalls: map[*ast.CallExpr]bool{},
variables: []string{},
i: -1,
}
}
2019-10-22 14:56:03 +00:00
// analyzeVoidCalls checks for functions that are not assigned
// and therefore we need to cleanup the return value from the stack.
func (c *funcScope) analyzeVoidCalls(node ast.Node) bool {
switch n := node.(type) {
case *ast.AssignStmt:
for i := 0; i < len(n.Rhs); i++ {
switch n.Rhs[i].(type) {
case *ast.CallExpr:
return false
}
}
case *ast.ReturnStmt:
if len(n.Results) > 0 {
switch n.Results[0].(type) {
case *ast.CallExpr:
return false
}
}
case *ast.BinaryExpr:
return false
case *ast.CallExpr:
c.voidCalls[n] = true
return false
}
return true
}
func (c *funcScope) countLocals() int {
size := 0
ast.Inspect(c.decl, func(n ast.Node) bool {
switch n := n.(type) {
2020-05-06 14:24:32 +00:00
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.Rhs)
}
case *ast.ReturnStmt, *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
})
return size
}
func (c *funcScope) countArgs() int {
n := c.decl.Type.Params.NumFields()
if c.decl.Recv != nil {
n += c.decl.Recv.NumFields()
}
return n
}
func (c *funcScope) stackSize() int64 {
size := c.countLocals()
numArgs := c.countArgs()
return int64(size + numArgs + len(c.voidCalls))
}
// newVariable creates a new local variable or argument in the scope of the function.
func (c *funcScope) newVariable(t varType, name string) int {
var n int
switch t {
case varLocal:
n = len(c.locals)
c.locals[name] = n
case varArgument:
n = len(c.arguments)
c.arguments[name] = n
default:
panic("invalid type")
}
return n
}
// newLocal creates a new local variable into the scope of the function.
func (c *funcScope) newLocal(name string) int {
return c.newVariable(varLocal, name)
}