compiler: support _deploy
method
This commit is contained in:
parent
6701e8cda0
commit
c4a8770215
5 changed files with 110 additions and 18 deletions
|
@ -36,6 +36,10 @@ functionality as Neo .net Framework library.
|
||||||
Compiler provides some helpful builtins in `util` and `convert` packages.
|
Compiler provides some helpful builtins in `util` and `convert` packages.
|
||||||
Refer to them for detailed documentation.
|
Refer to them for detailed documentation.
|
||||||
|
|
||||||
|
`_deploy()` function has a special meaning and is executed when contract is deployed.
|
||||||
|
It should return no value and accept single bool argument which will be true on contract update.
|
||||||
|
`_deploy()` functions are called for every imported package in the same order as `init()`.
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
### Compiling
|
### Compiling
|
||||||
|
|
|
@ -40,13 +40,15 @@ func (c *codegen) getIdentName(pkg string, name string) string {
|
||||||
// and returns number of variables initialized.
|
// and returns number of variables initialized.
|
||||||
// Second return value is -1 if no `init()` functions were encountered
|
// Second return value is -1 if no `init()` functions were encountered
|
||||||
// and number of maximum amount of locals in any of init functions otherwise.
|
// and number of maximum amount of locals in any of init functions otherwise.
|
||||||
func (c *codegen) traverseGlobals() (int, int) {
|
// Same for `_deploy()` functions (see docs/compiler.md).
|
||||||
|
func (c *codegen) traverseGlobals() (int, int, int) {
|
||||||
var hasDefer bool
|
var hasDefer bool
|
||||||
var n int
|
var n int
|
||||||
initLocals := -1
|
initLocals := -1
|
||||||
|
deployLocals := -1
|
||||||
c.ForEachFile(func(f *ast.File, _ *types.Package) {
|
c.ForEachFile(func(f *ast.File, _ *types.Package) {
|
||||||
n += countGlobals(f)
|
n += countGlobals(f)
|
||||||
if initLocals == -1 || !hasDefer {
|
if initLocals == -1 || deployLocals == -1 || !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.FuncDecl:
|
case *ast.FuncDecl:
|
||||||
|
@ -55,6 +57,11 @@ func (c *codegen) traverseGlobals() (int, int) {
|
||||||
if c > initLocals {
|
if c > initLocals {
|
||||||
initLocals = c
|
initLocals = c
|
||||||
}
|
}
|
||||||
|
} else if isDeployFunc(n) {
|
||||||
|
c, _ := countLocals(n)
|
||||||
|
if c > deployLocals {
|
||||||
|
deployLocals = c
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return !hasDefer
|
return !hasDefer
|
||||||
case *ast.DeferStmt:
|
case *ast.DeferStmt:
|
||||||
|
@ -71,7 +78,7 @@ func (c *codegen) traverseGlobals() (int, int) {
|
||||||
if n != 0 || initLocals > -1 {
|
if n != 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
|
return 0, initLocals, deployLocals
|
||||||
}
|
}
|
||||||
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)})
|
||||||
|
@ -104,7 +111,7 @@ func (c *codegen) traverseGlobals() (int, int) {
|
||||||
c.globals["<exception>"] = c.exceptionIndex
|
c.globals["<exception>"] = c.exceptionIndex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return n, initLocals
|
return n, initLocals, deployLocals
|
||||||
}
|
}
|
||||||
|
|
||||||
// countGlobals counts the global variables in the program to add
|
// countGlobals counts the global variables in the program to add
|
||||||
|
|
|
@ -61,6 +61,8 @@ type codegen struct {
|
||||||
|
|
||||||
// initEndOffset specifies the end of the initialization method.
|
// initEndOffset specifies the end of the initialization method.
|
||||||
initEndOffset int
|
initEndOffset int
|
||||||
|
// deployEndOffset specifies the end of the deployment method.
|
||||||
|
deployEndOffset int
|
||||||
|
|
||||||
// importMap contains mapping from package aliases to full package names for the current file.
|
// importMap contains mapping from package aliases to full package names for the current file.
|
||||||
importMap map[string]string
|
importMap map[string]string
|
||||||
|
@ -328,13 +330,46 @@ func (c *codegen) convertInitFuncs(f *ast.File, pkg *types.Package, seenBefore b
|
||||||
return seenBefore
|
return seenBefore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isDeployFunc(decl *ast.FuncDecl) bool {
|
||||||
|
if decl.Name.Name != "_deploy" || decl.Recv != nil ||
|
||||||
|
decl.Type.Params.NumFields() != 1 ||
|
||||||
|
decl.Type.Results.NumFields() != 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
typ, ok := decl.Type.Params.List[0].Type.(*ast.Ident)
|
||||||
|
return ok && typ.Name == "bool"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *codegen) convertDeployFuncs() {
|
||||||
|
seenBefore := false
|
||||||
|
c.ForEachFile(func(f *ast.File, pkg *types.Package) {
|
||||||
|
ast.Inspect(f, func(node ast.Node) bool {
|
||||||
|
switch n := node.(type) {
|
||||||
|
case *ast.FuncDecl:
|
||||||
|
if isDeployFunc(n) {
|
||||||
|
if seenBefore {
|
||||||
|
cnt, _ := countLocals(n)
|
||||||
|
c.clearSlots(cnt)
|
||||||
|
}
|
||||||
|
c.convertFuncDecl(f, n, pkg)
|
||||||
|
seenBefore = true
|
||||||
|
}
|
||||||
|
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)
|
isInit := isInitFunc(decl)
|
||||||
if isInit {
|
isDeploy := isDeployFunc(decl)
|
||||||
|
if isInit || isDeploy {
|
||||||
f = c.newFuncScope(decl, c.newLabel())
|
f = c.newFuncScope(decl, c.newLabel())
|
||||||
} else {
|
} else {
|
||||||
f, ok = c.funcs[c.getFuncNameFromDecl("", decl)]
|
f, ok = c.funcs[c.getFuncNameFromDecl("", decl)]
|
||||||
|
@ -358,7 +393,7 @@ 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 {
|
if !isInit && !isDeploy {
|
||||||
sizeLoc := f.countLocals()
|
sizeLoc := f.countLocals()
|
||||||
if sizeLoc > 255 {
|
if sizeLoc > 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")
|
||||||
|
@ -401,7 +436,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 !isInit && !lastStmtIsReturn(decl) {
|
if !isInit && !isDeploy && !lastStmtIsReturn(decl) {
|
||||||
c.saveSequencePoint(decl.Body)
|
c.saveSequencePoint(decl.Body)
|
||||||
emit.Opcodes(c.prog.BinWriter, opcode.RET)
|
emit.Opcodes(c.prog.BinWriter, opcode.RET)
|
||||||
}
|
}
|
||||||
|
@ -1792,11 +1827,19 @@ 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 := c.traverseGlobals()
|
n, initLocals, deployLocals := c.traverseGlobals()
|
||||||
hasInit := initLocals > -1
|
hasInit := initLocals > -1
|
||||||
if n > 0 || hasInit {
|
if n > 0 || hasInit {
|
||||||
emit.Opcodes(c.prog.BinWriter, opcode.RET)
|
|
||||||
c.initEndOffset = c.prog.Len()
|
c.initEndOffset = c.prog.Len()
|
||||||
|
emit.Opcodes(c.prog.BinWriter, opcode.RET)
|
||||||
|
}
|
||||||
|
|
||||||
|
hasDeploy := deployLocals > -1
|
||||||
|
if hasDeploy {
|
||||||
|
emit.Instruction(c.prog.BinWriter, opcode.INITSLOT, []byte{byte(deployLocals), 1})
|
||||||
|
c.convertDeployFuncs()
|
||||||
|
c.deployEndOffset = c.prog.Len()
|
||||||
|
emit.Opcodes(c.prog.BinWriter, opcode.RET)
|
||||||
}
|
}
|
||||||
|
|
||||||
// sort map keys to generate code deterministically.
|
// sort map keys to generate code deterministically.
|
||||||
|
@ -1814,7 +1857,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 !isInitFunc(n) && funUsage.funcUsed(name) && !isInteropPath(pkg.Path()) {
|
if !isInitFunc(n) && !isDeployFunc(n) && funUsage.funcUsed(name) && !isInteropPath(pkg.Path()) {
|
||||||
c.convertFuncDecl(f, n, pkg)
|
c.convertFuncDecl(f, n, pkg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1837,6 +1880,9 @@ func newCodegen(info *buildInfo, pkg *loader.PackageInfo) *codegen {
|
||||||
constMap: map[string]types.TypeAndValue{},
|
constMap: map[string]types.TypeAndValue{},
|
||||||
docIndex: map[string]int{},
|
docIndex: map[string]int{},
|
||||||
|
|
||||||
|
initEndOffset: -1,
|
||||||
|
deployEndOffset: -1,
|
||||||
|
|
||||||
sequencePoints: make(map[string][]DebugSeqPoint),
|
sequencePoints: make(map[string][]DebugSeqPoint),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,6 +134,27 @@ func (c *codegen) emitDebugInfo(contract []byte) *DebugInfo {
|
||||||
SeqPoints: c.sequencePoints["init"],
|
SeqPoints: c.sequencePoints["init"],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
if c.deployEndOffset >= 0 {
|
||||||
|
d.Methods = append(d.Methods, MethodDebugInfo{
|
||||||
|
ID: manifest.MethodDeploy,
|
||||||
|
Name: DebugMethodName{
|
||||||
|
Name: manifest.MethodDeploy,
|
||||||
|
Namespace: c.mainPkg.Pkg.Name(),
|
||||||
|
},
|
||||||
|
IsExported: true,
|
||||||
|
IsFunction: true,
|
||||||
|
Range: DebugRange{
|
||||||
|
Start: uint16(c.initEndOffset + 1),
|
||||||
|
End: uint16(c.deployEndOffset),
|
||||||
|
},
|
||||||
|
Parameters: []DebugParam{{
|
||||||
|
Name: "isUpdate",
|
||||||
|
Type: "Boolean",
|
||||||
|
}},
|
||||||
|
ReturnType: "Void",
|
||||||
|
SeqPoints: c.sequencePoints[manifest.MethodDeploy],
|
||||||
|
})
|
||||||
|
}
|
||||||
for name, scope := range c.funcs {
|
for name, scope := range c.funcs {
|
||||||
m := c.methodInfoFromScope(name, scope)
|
m := c.methodInfoFromScope(name, scope)
|
||||||
if m.Range.Start == m.Range.End {
|
if m.Range.Start == m.Range.End {
|
||||||
|
|
|
@ -55,6 +55,7 @@ func MethodParams(addr interop.Hash160, h interop.Hash256,
|
||||||
type MyStruct struct {}
|
type MyStruct struct {}
|
||||||
func (ms MyStruct) MethodOnStruct() { }
|
func (ms MyStruct) MethodOnStruct() { }
|
||||||
func (ms *MyStruct) MethodOnPointerToStruct() { }
|
func (ms *MyStruct) MethodOnPointerToStruct() { }
|
||||||
|
func _deploy(isUpdate bool) {}
|
||||||
`
|
`
|
||||||
|
|
||||||
info, err := getBuildInfo("foo.go", src)
|
info, err := getBuildInfo("foo.go", src)
|
||||||
|
@ -83,6 +84,7 @@ func (ms *MyStruct) MethodOnPointerToStruct() { }
|
||||||
"MethodOnStruct": "Void",
|
"MethodOnStruct": "Void",
|
||||||
"MethodOnPointerToStruct": "Void",
|
"MethodOnPointerToStruct": "Void",
|
||||||
"MethodParams": "Boolean",
|
"MethodParams": "Boolean",
|
||||||
|
"_deploy": "Void",
|
||||||
}
|
}
|
||||||
for i := range d.Methods {
|
for i := range d.Methods {
|
||||||
name := d.Methods[i].ID
|
name := d.Methods[i].ID
|
||||||
|
@ -104,6 +106,10 @@ func (ms *MyStruct) MethodOnPointerToStruct() { }
|
||||||
|
|
||||||
t.Run("param types", func(t *testing.T) {
|
t.Run("param types", func(t *testing.T) {
|
||||||
paramTypes := map[string][]DebugParam{
|
paramTypes := map[string][]DebugParam{
|
||||||
|
"_deploy": {{
|
||||||
|
Name: "isUpdate",
|
||||||
|
Type: "Boolean",
|
||||||
|
}},
|
||||||
"MethodInt": {{
|
"MethodInt": {{
|
||||||
Name: "a",
|
Name: "a",
|
||||||
Type: "String",
|
Type: "String",
|
||||||
|
@ -151,8 +157,16 @@ func (ms *MyStruct) MethodOnPointerToStruct() { }
|
||||||
Hash: hash.Hash160(buf),
|
Hash: hash.Hash160(buf),
|
||||||
Methods: []manifest.Method{
|
Methods: []manifest.Method{
|
||||||
{
|
{
|
||||||
Name: "main",
|
Name: "_deploy",
|
||||||
Offset: 0,
|
Offset: 0,
|
||||||
|
Parameters: []manifest.Parameter{
|
||||||
|
manifest.NewParameter("isUpdate", smartcontract.BoolType),
|
||||||
|
},
|
||||||
|
ReturnType: smartcontract.VoidType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "main",
|
||||||
|
Offset: 4,
|
||||||
Parameters: []manifest.Parameter{
|
Parameters: []manifest.Parameter{
|
||||||
manifest.NewParameter("op", smartcontract.StringType),
|
manifest.NewParameter("op", smartcontract.StringType),
|
||||||
},
|
},
|
||||||
|
@ -160,7 +174,7 @@ func (ms *MyStruct) MethodOnPointerToStruct() { }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "methodInt",
|
Name: "methodInt",
|
||||||
Offset: 66,
|
Offset: 70,
|
||||||
Parameters: []manifest.Parameter{
|
Parameters: []manifest.Parameter{
|
||||||
{
|
{
|
||||||
Name: "a",
|
Name: "a",
|
||||||
|
@ -171,31 +185,31 @@ func (ms *MyStruct) MethodOnPointerToStruct() { }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "methodString",
|
Name: "methodString",
|
||||||
Offset: 97,
|
Offset: 101,
|
||||||
Parameters: []manifest.Parameter{},
|
Parameters: []manifest.Parameter{},
|
||||||
ReturnType: smartcontract.StringType,
|
ReturnType: smartcontract.StringType,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "methodByteArray",
|
Name: "methodByteArray",
|
||||||
Offset: 103,
|
Offset: 107,
|
||||||
Parameters: []manifest.Parameter{},
|
Parameters: []manifest.Parameter{},
|
||||||
ReturnType: smartcontract.ByteArrayType,
|
ReturnType: smartcontract.ByteArrayType,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "methodArray",
|
Name: "methodArray",
|
||||||
Offset: 108,
|
Offset: 112,
|
||||||
Parameters: []manifest.Parameter{},
|
Parameters: []manifest.Parameter{},
|
||||||
ReturnType: smartcontract.ArrayType,
|
ReturnType: smartcontract.ArrayType,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "methodStruct",
|
Name: "methodStruct",
|
||||||
Offset: 113,
|
Offset: 117,
|
||||||
Parameters: []manifest.Parameter{},
|
Parameters: []manifest.Parameter{},
|
||||||
ReturnType: smartcontract.ArrayType,
|
ReturnType: smartcontract.ArrayType,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "methodConcat",
|
Name: "methodConcat",
|
||||||
Offset: 88,
|
Offset: 92,
|
||||||
Parameters: []manifest.Parameter{
|
Parameters: []manifest.Parameter{
|
||||||
{
|
{
|
||||||
Name: "a",
|
Name: "a",
|
||||||
|
@ -214,7 +228,7 @@ func (ms *MyStruct) MethodOnPointerToStruct() { }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "methodParams",
|
Name: "methodParams",
|
||||||
Offset: 125,
|
Offset: 129,
|
||||||
Parameters: []manifest.Parameter{
|
Parameters: []manifest.Parameter{
|
||||||
manifest.NewParameter("addr", smartcontract.Hash160Type),
|
manifest.NewParameter("addr", smartcontract.Hash160Type),
|
||||||
manifest.NewParameter("h", smartcontract.Hash256Type),
|
manifest.NewParameter("h", smartcontract.Hash256Type),
|
||||||
|
|
Loading…
Reference in a new issue