mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2025-05-03 01:41:48 +00:00
vm: implement slot-related opcodes
1. Slot is a new mechanism for storing variables during execution which is more convenient than alt.stack. This commit implements support for slot opcodes in both vm and compiler. 2. Remove old alt.stack opcodes. 3. Do not process globals at the start of every function, but instead load them single time at main.
This commit is contained in:
parent
a6271f6bf2
commit
0cb6dc47e4
12 changed files with 552 additions and 192 deletions
|
@ -1,11 +1,14 @@
|
|||
package compiler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/constant"
|
||||
"go/types"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||
"golang.org/x/tools/go/loader"
|
||||
)
|
||||
|
||||
|
@ -47,6 +50,24 @@ func typeAndValueForField(fld *types.Var) (types.TypeAndValue, error) {
|
|||
return types.TypeAndValue{}, nil
|
||||
}
|
||||
|
||||
// newGlobal creates new global variable.
|
||||
func (c *codegen) newGlobal(name string) {
|
||||
c.globals[name] = len(c.globals)
|
||||
}
|
||||
|
||||
// traverseGlobals visits and initializes global variables.
|
||||
func (c *codegen) traverseGlobals(f ast.Node) {
|
||||
n := countGlobals(f)
|
||||
if n != 0 {
|
||||
if n > 255 {
|
||||
c.prog.BinWriter.Err = errors.New("too many global variables")
|
||||
return
|
||||
}
|
||||
emit.Instruction(c.prog.BinWriter, opcode.INITSSLOT, []byte{byte(n)})
|
||||
}
|
||||
c.convertGlobals(f)
|
||||
}
|
||||
|
||||
// countGlobals counts the global variables in the program to add
|
||||
// them with the stack size of the function.
|
||||
func countGlobals(f ast.Node) (i int64) {
|
||||
|
|
|
@ -43,6 +43,8 @@ type codegen struct {
|
|||
// Current funcScope being converted.
|
||||
scope *funcScope
|
||||
|
||||
globals map[string]int
|
||||
|
||||
// A mapping from label's names to their ids.
|
||||
labels map[labelWithType]uint16
|
||||
// A list of nested label names together with evaluation stack depth.
|
||||
|
@ -82,6 +84,14 @@ type labelWithStackSize struct {
|
|||
sz int
|
||||
}
|
||||
|
||||
type varType int
|
||||
|
||||
const (
|
||||
varGlobal varType = iota
|
||||
varLocal
|
||||
varArgument
|
||||
)
|
||||
|
||||
// newLabel creates a new label to jump to
|
||||
func (c *codegen) newLabel() (l uint16) {
|
||||
li := len(c.l)
|
||||
|
@ -147,34 +157,6 @@ func (c *codegen) convertBasicType(t types.TypeAndValue, typ *types.Basic) {
|
|||
}
|
||||
}
|
||||
|
||||
func (c *codegen) emitLoadLocal(name string) {
|
||||
pos := c.scope.loadLocal(name)
|
||||
if pos < 0 {
|
||||
c.prog.Err = fmt.Errorf("cannot load local variable with position: %d", pos)
|
||||
return
|
||||
}
|
||||
c.emitLoadLocalPos(pos)
|
||||
}
|
||||
|
||||
func (c *codegen) emitLoadLocalPos(pos int) {
|
||||
emit.Opcode(c.prog.BinWriter, opcode.DUPFROMALTSTACK)
|
||||
emit.Int(c.prog.BinWriter, int64(pos))
|
||||
emit.Opcode(c.prog.BinWriter, opcode.PICKITEM)
|
||||
}
|
||||
|
||||
func (c *codegen) emitStoreLocal(pos int) {
|
||||
emit.Opcode(c.prog.BinWriter, opcode.DUPFROMALTSTACK)
|
||||
|
||||
if pos < 0 {
|
||||
c.prog.Err = fmt.Errorf("invalid position to store local: %d", pos)
|
||||
return
|
||||
}
|
||||
|
||||
emit.Int(c.prog.BinWriter, int64(pos))
|
||||
emit.Opcode(c.prog.BinWriter, opcode.ROT)
|
||||
emit.Opcode(c.prog.BinWriter, opcode.SETITEM)
|
||||
}
|
||||
|
||||
func (c *codegen) emitLoadField(i int) {
|
||||
emit.Int(c.prog.BinWriter, int64(i))
|
||||
emit.Opcode(c.prog.BinWriter, opcode.PICKITEM)
|
||||
|
@ -186,6 +168,81 @@ func (c *codegen) emitStoreStructField(i int) {
|
|||
emit.Opcode(c.prog.BinWriter, opcode.SETITEM)
|
||||
}
|
||||
|
||||
// getVarIndex returns variable type and position in corresponding slot,
|
||||
// according to current scope.
|
||||
func (c *codegen) getVarIndex(name string) (varType, int) {
|
||||
if c.scope != nil {
|
||||
if i, ok := c.scope.arguments[name]; ok {
|
||||
return varArgument, i
|
||||
} else if i, ok := c.scope.locals[name]; ok {
|
||||
return varLocal, i
|
||||
}
|
||||
}
|
||||
if i, ok := c.globals[name]; ok {
|
||||
return varGlobal, i
|
||||
}
|
||||
|
||||
return varLocal, c.scope.newVariable(varLocal, name)
|
||||
}
|
||||
|
||||
func getBaseOpcode(t varType) (opcode.Opcode, opcode.Opcode) {
|
||||
switch t {
|
||||
case varGlobal:
|
||||
return opcode.LDSFLD0, opcode.STSFLD0
|
||||
case varLocal:
|
||||
return opcode.LDLOC0, opcode.STLOC0
|
||||
case varArgument:
|
||||
return opcode.LDARG0, opcode.STARG0
|
||||
default:
|
||||
panic("invalid type")
|
||||
}
|
||||
}
|
||||
|
||||
// emitLoadVar loads specified variable to the evaluation stack.
|
||||
func (c *codegen) emitLoadVar(name string) {
|
||||
t, i := c.getVarIndex(name)
|
||||
base, _ := getBaseOpcode(t)
|
||||
if i < 7 {
|
||||
emit.Opcode(c.prog.BinWriter, base+opcode.Opcode(i))
|
||||
} else {
|
||||
emit.Instruction(c.prog.BinWriter, base+7, []byte{byte(i)})
|
||||
}
|
||||
}
|
||||
|
||||
// emitStoreVar stores top value from the evaluation stack in the specified variable.
|
||||
func (c *codegen) emitStoreVar(name string) {
|
||||
t, i := c.getVarIndex(name)
|
||||
_, base := getBaseOpcode(t)
|
||||
if i < 7 {
|
||||
emit.Opcode(c.prog.BinWriter, base+opcode.Opcode(i))
|
||||
} else {
|
||||
emit.Instruction(c.prog.BinWriter, base+7, []byte{byte(i)})
|
||||
}
|
||||
}
|
||||
|
||||
func (c *codegen) emitDefault(n ast.Expr) {
|
||||
tv, ok := c.typeInfo.Types[n]
|
||||
if !ok {
|
||||
c.prog.Err = errors.New("invalid type")
|
||||
return
|
||||
}
|
||||
if t, ok := tv.Type.(*types.Basic); ok {
|
||||
info := t.Info()
|
||||
switch {
|
||||
case info&types.IsInteger != 0:
|
||||
emit.Int(c.prog.BinWriter, 0)
|
||||
case info&types.IsString != 0:
|
||||
emit.Bytes(c.prog.BinWriter, []byte{})
|
||||
case info&types.IsBoolean != 0:
|
||||
emit.Bool(c.prog.BinWriter, false)
|
||||
default:
|
||||
emit.Opcode(c.prog.BinWriter, opcode.PUSHNULL)
|
||||
}
|
||||
return
|
||||
}
|
||||
emit.Opcode(c.prog.BinWriter, opcode.PUSHNULL)
|
||||
}
|
||||
|
||||
// convertGlobals traverses the AST and only converts global declarations.
|
||||
// If we call this in convertFuncDecl then it will load all global variables
|
||||
// into the scope of the function.
|
||||
|
@ -231,9 +288,17 @@ func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl) {
|
|||
|
||||
// All globals copied into the scope of the function need to be added
|
||||
// to the stack size of the function.
|
||||
emit.Int(c.prog.BinWriter, f.stackSize()+countGlobals(file))
|
||||
emit.Opcode(c.prog.BinWriter, opcode.NEWARRAY)
|
||||
emit.Opcode(c.prog.BinWriter, opcode.TOALTSTACK)
|
||||
sizeLoc := f.countLocals()
|
||||
if sizeLoc > 255 {
|
||||
c.prog.Err = errors.New("maximum of 255 local variables is allowed")
|
||||
}
|
||||
sizeArg := f.countArgs()
|
||||
if sizeArg > 255 {
|
||||
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(sizeLoc), byte(sizeArg)})
|
||||
}
|
||||
|
||||
// We need to handle methods, which in Go, is just syntactic sugar.
|
||||
// The method receiver will be passed in as first argument.
|
||||
|
@ -250,23 +315,18 @@ func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl) {
|
|||
c.prog.Err = fmt.Errorf("method receives for non-struct types is not yet supported")
|
||||
return
|
||||
}
|
||||
l := c.scope.newLocal(ident.Name)
|
||||
c.emitStoreLocal(l)
|
||||
// only create an argument here, it will be stored via INITSLOT
|
||||
c.scope.newVariable(varArgument, ident.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// Load the arguments in scope.
|
||||
for _, arg := range decl.Type.Params.List {
|
||||
for _, id := range arg.Names {
|
||||
l := c.scope.newLocal(id.Name)
|
||||
c.emitStoreLocal(l)
|
||||
// only create an argument here, it will be stored via INITSLOT
|
||||
c.scope.newVariable(varArgument, id.Name)
|
||||
}
|
||||
}
|
||||
// Load in all the global variables in to the scope of the function.
|
||||
// This is not necessary for syscalls.
|
||||
if !isSyscall(f) {
|
||||
c.convertGlobals(file)
|
||||
}
|
||||
|
||||
ast.Walk(c, decl.Body)
|
||||
|
||||
|
@ -275,8 +335,6 @@ func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl) {
|
|||
// This can be the case with void and named-return functions.
|
||||
if !lastStmtIsReturn(decl) {
|
||||
c.saveSequencePoint(decl.Body)
|
||||
emit.Opcode(c.prog.BinWriter, opcode.FROMALTSTACK)
|
||||
emit.Opcode(c.prog.BinWriter, opcode.DROP)
|
||||
emit.Opcode(c.prog.BinWriter, opcode.RET)
|
||||
}
|
||||
|
||||
|
@ -305,25 +363,32 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
|||
switch t := spec.(type) {
|
||||
case *ast.ValueSpec:
|
||||
for _, id := range t.Names {
|
||||
c.scope.newLocal(id.Name)
|
||||
if c.scope == nil {
|
||||
// it is a global declaration
|
||||
c.newGlobal(id.Name)
|
||||
} else {
|
||||
c.scope.newLocal(id.Name)
|
||||
}
|
||||
c.registerDebugVariable(id.Name, t.Type)
|
||||
}
|
||||
if len(t.Values) != 0 {
|
||||
for i, val := range t.Values {
|
||||
ast.Walk(c, val)
|
||||
l := c.scope.loadLocal(t.Names[i].Name)
|
||||
c.emitStoreLocal(l)
|
||||
c.emitStoreVar(t.Names[i].Name)
|
||||
}
|
||||
} else if c.isCompoundArrayType(t.Type) {
|
||||
emit.Opcode(c.prog.BinWriter, opcode.PUSH0)
|
||||
emit.Opcode(c.prog.BinWriter, opcode.NEWARRAY)
|
||||
l := c.scope.loadLocal(t.Names[0].Name)
|
||||
c.emitStoreLocal(l)
|
||||
c.emitStoreVar(t.Names[0].Name)
|
||||
} else if n, ok := c.isStructType(t.Type); ok {
|
||||
emit.Int(c.prog.BinWriter, int64(n))
|
||||
emit.Opcode(c.prog.BinWriter, opcode.NEWSTRUCT)
|
||||
l := c.scope.loadLocal(t.Names[0].Name)
|
||||
c.emitStoreLocal(l)
|
||||
c.emitStoreVar(t.Names[0].Name)
|
||||
} else {
|
||||
for _, id := range t.Names {
|
||||
c.emitDefault(t.Type)
|
||||
c.emitStoreVar(id.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -337,11 +402,10 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
|||
case *ast.Ident:
|
||||
switch n.Tok {
|
||||
case token.ADD_ASSIGN, token.SUB_ASSIGN, token.MUL_ASSIGN, token.QUO_ASSIGN, token.REM_ASSIGN:
|
||||
c.emitLoadLocal(t.Name)
|
||||
c.emitLoadVar(t.Name)
|
||||
ast.Walk(c, n.Rhs[0]) // can only add assign to 1 expr on the RHS
|
||||
c.convertToken(n.Tok)
|
||||
l := c.scope.loadLocal(t.Name)
|
||||
c.emitStoreLocal(l)
|
||||
c.emitStoreVar(t.Name)
|
||||
case token.DEFINE:
|
||||
if !multiRet {
|
||||
c.registerDebugVariable(t.Name, n.Rhs[i])
|
||||
|
@ -355,8 +419,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
|||
if t.Name == "_" {
|
||||
emit.Opcode(c.prog.BinWriter, opcode.DROP)
|
||||
} else {
|
||||
l := c.scope.loadLocal(t.Name)
|
||||
c.emitStoreLocal(l)
|
||||
c.emitStoreVar(t.Name)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -366,7 +429,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
|||
ast.Walk(c, n.Rhs[i])
|
||||
typ := c.typeInfo.ObjectOf(expr).Type().Underlying()
|
||||
if strct, ok := typ.(*types.Struct); ok {
|
||||
c.emitLoadLocal(expr.Name) // load the struct
|
||||
c.emitLoadVar(expr.Name) // load the struct
|
||||
i := indexOfStruct(strct, t.Sel.Name) // get the index of the field
|
||||
c.emitStoreStructField(i) // store the field
|
||||
}
|
||||
|
@ -380,7 +443,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
|||
case *ast.IndexExpr:
|
||||
ast.Walk(c, n.Rhs[i])
|
||||
name := t.X.(*ast.Ident).Name
|
||||
c.emitLoadLocal(name)
|
||||
c.emitLoadVar(name)
|
||||
switch ind := t.Index.(type) {
|
||||
case *ast.BasicLit:
|
||||
indexStr := ind.Value
|
||||
|
@ -391,7 +454,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
|||
}
|
||||
c.emitStoreStructField(index)
|
||||
case *ast.Ident:
|
||||
c.emitLoadLocal(ind.Name)
|
||||
c.emitLoadVar(ind.Name)
|
||||
emit.Opcode(c.prog.BinWriter, opcode.ROT)
|
||||
emit.Opcode(c.prog.BinWriter, opcode.SETITEM)
|
||||
default:
|
||||
|
@ -404,7 +467,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
|||
|
||||
case *ast.SliceExpr:
|
||||
name := n.X.(*ast.Ident).Name
|
||||
c.emitLoadLocal(name)
|
||||
c.emitLoadVar(name)
|
||||
|
||||
if n.Low != nil {
|
||||
ast.Walk(c, n.Low)
|
||||
|
@ -442,7 +505,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
|||
for i := len(results.List) - 1; i >= 0; i-- {
|
||||
names := results.List[i].Names
|
||||
for j := len(names) - 1; j >= 0; j-- {
|
||||
c.emitLoadLocal(names[j].Name)
|
||||
c.emitLoadVar(names[j].Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -454,8 +517,6 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
|||
}
|
||||
|
||||
c.saveSequencePoint(n)
|
||||
emit.Opcode(c.prog.BinWriter, opcode.FROMALTSTACK)
|
||||
emit.Opcode(c.prog.BinWriter, opcode.DROP) // Cleanup the stack.
|
||||
emit.Opcode(c.prog.BinWriter, opcode.RET)
|
||||
return nil
|
||||
|
||||
|
@ -557,7 +618,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
|||
} else if tv := c.typeInfo.Types[n]; tv.Value != nil {
|
||||
c.emitLoadConst(tv)
|
||||
} else {
|
||||
c.emitLoadLocal(n.Name)
|
||||
c.emitLoadVar(n.Name)
|
||||
}
|
||||
return nil
|
||||
|
||||
|
@ -723,7 +784,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
|||
c.convertBuiltin(n)
|
||||
case name != "":
|
||||
// Function was not found thus is can be only an invocation of func-typed variable.
|
||||
c.emitLoadLocal(name)
|
||||
c.emitLoadVar(name)
|
||||
emit.Opcode(c.prog.BinWriter, opcode.CALLA)
|
||||
case isSyscall(f):
|
||||
c.convertSyscall(n, f.selector.Name, f.name)
|
||||
|
@ -738,7 +799,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
|||
case *ast.Ident:
|
||||
typ := c.typeInfo.ObjectOf(t).Type().Underlying()
|
||||
if strct, ok := typ.(*types.Struct); ok {
|
||||
c.emitLoadLocal(t.Name) // load the struct
|
||||
c.emitLoadVar(t.Name) // load the struct
|
||||
i := indexOfStruct(strct, n.Sel.Name)
|
||||
c.emitLoadField(i) // load the field
|
||||
}
|
||||
|
@ -777,8 +838,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
|||
// for i := 0; i < 10; i++ {}
|
||||
// Where the post stmt is ( i++ )
|
||||
if ident, ok := n.X.(*ast.Ident); ok {
|
||||
pos := c.scope.loadLocal(ident.Name)
|
||||
c.emitStoreLocal(pos)
|
||||
c.emitStoreVar(ident.Name)
|
||||
}
|
||||
return nil
|
||||
|
||||
|
@ -913,9 +973,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
|||
|
||||
if n.Key != nil {
|
||||
emit.Opcode(c.prog.BinWriter, opcode.DUP)
|
||||
|
||||
pos := c.scope.loadLocal(n.Key.(*ast.Ident).Name)
|
||||
c.emitStoreLocal(pos)
|
||||
c.emitStoreVar(n.Key.(*ast.Ident).Name)
|
||||
}
|
||||
|
||||
ast.Walk(c, n.Body)
|
||||
|
@ -1315,6 +1373,8 @@ func (c *codegen) compile(info *buildInfo, pkg *loader.PackageInfo) error {
|
|||
}
|
||||
}
|
||||
|
||||
c.traverseGlobals(mainFile)
|
||||
|
||||
// convert the entry point first.
|
||||
c.convertFuncDecl(mainFile, main)
|
||||
|
||||
|
@ -1354,6 +1414,7 @@ func newCodegen(info *buildInfo, pkg *loader.PackageInfo) *codegen {
|
|||
l: []int{},
|
||||
funcs: map[string]*funcScope{},
|
||||
lambda: map[string]*funcScope{},
|
||||
globals: map[string]int{},
|
||||
labels: map[labelWithType]uint16{},
|
||||
typeInfo: &pkg.Info,
|
||||
|
||||
|
|
|
@ -115,6 +115,10 @@ type Event struct {
|
|||
}
|
||||
|
||||
func (c *codegen) saveSequencePoint(n ast.Node) {
|
||||
if c.scope == nil {
|
||||
// do not save globals for now
|
||||
return
|
||||
}
|
||||
fset := c.buildInfo.program.Fset
|
||||
start := fset.Position(n.Pos())
|
||||
end := fset.Position(n.End())
|
||||
|
@ -143,6 +147,10 @@ func (c *codegen) emitDebugInfo() *DebugInfo {
|
|||
}
|
||||
|
||||
func (c *codegen) registerDebugVariable(name string, expr ast.Expr) {
|
||||
if c.scope == nil {
|
||||
// do not save globals for now
|
||||
return
|
||||
}
|
||||
typ := c.scTypeFromExpr(expr)
|
||||
c.scope.variables = append(c.scope.variables, name+","+typ)
|
||||
}
|
||||
|
|
|
@ -27,7 +27,8 @@ type funcScope struct {
|
|||
variables []string
|
||||
|
||||
// Local variables
|
||||
locals map[string]int
|
||||
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
|
||||
|
@ -51,6 +52,7 @@ func newFuncScope(decl *ast.FuncDecl, label uint16) *funcScope {
|
|||
decl: decl,
|
||||
label: label,
|
||||
locals: map[string]int{},
|
||||
arguments: map[string]int{},
|
||||
voidCalls: map[*ast.CallExpr]bool{},
|
||||
variables: []string{},
|
||||
i: -1,
|
||||
|
@ -84,7 +86,7 @@ func (c *funcScope) analyzeVoidCalls(node ast.Node) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func (c *funcScope) stackSize() int64 {
|
||||
func (c *funcScope) countLocals() int {
|
||||
size := 0
|
||||
ast.Inspect(c.decl, func(n ast.Node) bool {
|
||||
switch n := n.(type) {
|
||||
|
@ -110,28 +112,38 @@ func (c *funcScope) stackSize() int64 {
|
|||
}
|
||||
return true
|
||||
})
|
||||
return size
|
||||
}
|
||||
|
||||
numArgs := len(c.decl.Type.Params.List)
|
||||
// Also take care of struct methods recv: e.g. (t Token).Foo().
|
||||
func (c *funcScope) countArgs() int {
|
||||
n := c.decl.Type.Params.NumFields()
|
||||
if c.decl.Recv != nil {
|
||||
numArgs += len(c.decl.Recv.List)
|
||||
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 {
|
||||
c.i++
|
||||
switch t {
|
||||
case varLocal:
|
||||
c.locals[name] = c.i
|
||||
case varArgument:
|
||||
c.arguments[name] = c.i
|
||||
default:
|
||||
panic("invalid type")
|
||||
}
|
||||
return c.i
|
||||
}
|
||||
|
||||
// newLocal creates a new local variable into the scope of the function.
|
||||
func (c *funcScope) newLocal(name string) int {
|
||||
c.i++
|
||||
c.locals[name] = c.i
|
||||
return c.i
|
||||
}
|
||||
|
||||
// loadLocal loads the position of a local variable inside the scope of the function.
|
||||
func (c *funcScope) loadLocal(name string) int {
|
||||
i, ok := c.locals[name]
|
||||
if !ok {
|
||||
// should emit a compiler warning.
|
||||
return c.newLocal(name)
|
||||
}
|
||||
return i
|
||||
return c.newVariable(varLocal, name)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue