compiler: allow to use init
function
Process `init()` functions after global variables has been processed. It's body is saved into the `_initialize` method where all initialization is performed.
This commit is contained in:
parent
ef53a45e7a
commit
b771d2d024
3 changed files with 101 additions and 14 deletions
|
@ -48,6 +48,7 @@ func (c *codegen) traverseGlobals() int {
|
||||||
}
|
}
|
||||||
emit.Instruction(c.prog.BinWriter, opcode.INITSSLOT, []byte{byte(n)})
|
emit.Instruction(c.prog.BinWriter, opcode.INITSSLOT, []byte{byte(n)})
|
||||||
c.ForEachFile(c.convertGlobals)
|
c.ForEachFile(c.convertGlobals)
|
||||||
|
c.ForEachFile(c.convertInitFuncs)
|
||||||
}
|
}
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
|
@ -275,24 +275,48 @@ func (c *codegen) convertGlobals(f *ast.File, _ *types.Package) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isInitFunc(decl *ast.FuncDecl) bool {
|
||||||
|
return decl.Name.Name == "init" && decl.Recv == nil &&
|
||||||
|
decl.Type.Params.NumFields() == 0 &&
|
||||||
|
decl.Type.Results.NumFields() == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *codegen) convertInitFuncs(f *ast.File, pkg *types.Package) {
|
||||||
|
ast.Inspect(f, func(node ast.Node) bool {
|
||||||
|
switch n := node.(type) {
|
||||||
|
case *ast.FuncDecl:
|
||||||
|
if isInitFunc(n) {
|
||||||
|
c.convertFuncDecl(f, n, pkg)
|
||||||
|
}
|
||||||
|
case *ast.GenDecl:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
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) {
|
||||||
var (
|
var (
|
||||||
f *funcScope
|
f *funcScope
|
||||||
ok, isLambda bool
|
ok, isLambda bool
|
||||||
)
|
)
|
||||||
|
isInit := isInitFunc(decl)
|
||||||
f, ok = c.funcs[c.getFuncNameFromDecl("", decl)]
|
if isInit {
|
||||||
if ok {
|
f = c.newFuncScope(decl, c.newLabel())
|
||||||
// If this function is a syscall or builtin we will not convert it to bytecode.
|
|
||||||
if isSyscall(f) || isCustomBuiltin(f) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.setLabel(f.label)
|
|
||||||
} else if f, ok = c.lambda[c.getIdentName("", decl.Name.Name)]; ok {
|
|
||||||
isLambda = ok
|
|
||||||
c.setLabel(f.label)
|
|
||||||
} else {
|
} else {
|
||||||
f = c.newFunc(decl)
|
f, ok = c.funcs[c.getFuncNameFromDecl("", decl)]
|
||||||
|
if ok {
|
||||||
|
// If this function is a syscall or builtin we will not convert it to bytecode.
|
||||||
|
if isSyscall(f) || isCustomBuiltin(f) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.setLabel(f.label)
|
||||||
|
} else if f, ok = c.lambda[c.getIdentName("", decl.Name.Name)]; ok {
|
||||||
|
isLambda = ok
|
||||||
|
c.setLabel(f.label)
|
||||||
|
} else {
|
||||||
|
f = c.newFunc(decl)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
f.rng.Start = uint16(c.prog.Len())
|
f.rng.Start = uint16(c.prog.Len())
|
||||||
|
@ -342,7 +366,7 @@ func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl, pkg *types.
|
||||||
// If we have reached the end of the function without encountering `return` statement,
|
// If we have reached the end of the function without encountering `return` statement,
|
||||||
// we should clean alt.stack manually.
|
// we should clean alt.stack manually.
|
||||||
// This can be the case with void and named-return functions.
|
// This can be the case with void and named-return functions.
|
||||||
if !lastStmtIsReturn(decl) {
|
if !isInit && !lastStmtIsReturn(decl) {
|
||||||
c.saveSequencePoint(decl.Body)
|
c.saveSequencePoint(decl.Body)
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.RET)
|
emit.Opcode(c.prog.BinWriter, opcode.RET)
|
||||||
}
|
}
|
||||||
|
@ -1488,7 +1512,7 @@ func (c *codegen) compile(info *buildInfo, pkg *loader.PackageInfo) error {
|
||||||
// Don't convert the function if it's not used. This will save a lot
|
// Don't convert the function if it's not used. This will save a lot
|
||||||
// of bytecode space.
|
// of bytecode space.
|
||||||
name := c.getFuncNameFromDecl(pkg.Path(), n)
|
name := c.getFuncNameFromDecl(pkg.Path(), n)
|
||||||
if funUsage.funcUsed(name) && !isInteropPath(pkg.Path()) {
|
if !isInitFunc(n) && funUsage.funcUsed(name) && !isInteropPath(pkg.Path()) {
|
||||||
c.convertFuncDecl(f, n, pkg)
|
c.convertFuncDecl(f, n, pkg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
62
pkg/compiler/init_test.go
Normal file
62
pkg/compiler/init_test.go
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
package compiler_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInit(t *testing.T) {
|
||||||
|
t.Run("Simple", func(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
var a int
|
||||||
|
func init() {
|
||||||
|
a = 42
|
||||||
|
}
|
||||||
|
func Main() int {
|
||||||
|
return a
|
||||||
|
}`
|
||||||
|
eval(t, src, big.NewInt(42))
|
||||||
|
})
|
||||||
|
t.Run("Multi", func(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
var m = map[int]int{}
|
||||||
|
var a = 2
|
||||||
|
func init() {
|
||||||
|
m[1] = 11
|
||||||
|
}
|
||||||
|
func init() {
|
||||||
|
a = 1
|
||||||
|
m[3] = 30
|
||||||
|
}
|
||||||
|
func Main() int {
|
||||||
|
return m[1] + m[3] + a
|
||||||
|
}`
|
||||||
|
eval(t, src, big.NewInt(42))
|
||||||
|
})
|
||||||
|
t.Run("WithCall", func(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
var m = map[int]int{}
|
||||||
|
func init() {
|
||||||
|
initMap(m)
|
||||||
|
}
|
||||||
|
func initMap(m map[int]int) {
|
||||||
|
m[11] = 42
|
||||||
|
}
|
||||||
|
func Main() int {
|
||||||
|
return m[11]
|
||||||
|
}`
|
||||||
|
eval(t, src, big.NewInt(42))
|
||||||
|
})
|
||||||
|
t.Run("InvalidSignature", func(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
type Foo int
|
||||||
|
var a int
|
||||||
|
func (f Foo) init() {
|
||||||
|
a = 2
|
||||||
|
}
|
||||||
|
func Main() int {
|
||||||
|
return a
|
||||||
|
}`
|
||||||
|
eval(t, src, big.NewInt(0))
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in a new issue