compiler: support _deploy method

This commit is contained in:
Evgenii Stratonikov 2020-10-02 13:16:02 +03:00
parent 6701e8cda0
commit c4a8770215
5 changed files with 110 additions and 18 deletions

View file

@ -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

View file

@ -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

View file

@ -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),
} }
} }

View file

@ -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 {

View file

@ -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),