forked from TrueCloudLab/neoneo-go
compiler: support defer
statement
Compile `defer` statement to a TRY/ENDFINALLY opcode pair.
This commit is contained in:
parent
b18a7f200c
commit
fa1d1a8b00
4 changed files with 156 additions and 14 deletions
|
@ -553,6 +553,8 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.processDefers()
|
||||||
|
|
||||||
c.saveSequencePoint(n)
|
c.saveSequencePoint(n)
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.RET)
|
emit.Opcode(c.prog.BinWriter, opcode.RET)
|
||||||
return nil
|
return nil
|
||||||
|
@ -829,6 +831,17 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
|
case *ast.DeferStmt:
|
||||||
|
finally := c.newLabel()
|
||||||
|
param := make([]byte, 8)
|
||||||
|
binary.LittleEndian.PutUint16(param[4:], finally)
|
||||||
|
emit.Instruction(c.prog.BinWriter, opcode.TRYL, param)
|
||||||
|
c.scope.deferStack = append(c.scope.deferStack, deferInfo{
|
||||||
|
finallyLabel: finally,
|
||||||
|
expr: n.Call,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
|
||||||
case *ast.SelectorExpr:
|
case *ast.SelectorExpr:
|
||||||
typ := c.typeOf(n.X)
|
typ := c.typeOf(n.X)
|
||||||
if typ == nil {
|
if typ == nil {
|
||||||
|
@ -1090,6 +1103,20 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// processDefers emits code for `defer` statements.
|
||||||
|
func (c *codegen) processDefers() {
|
||||||
|
for i := len(c.scope.deferStack) - 1; i >= 0; i-- {
|
||||||
|
stmt := c.scope.deferStack[i]
|
||||||
|
after := c.newLabel()
|
||||||
|
emit.Jmp(c.prog.BinWriter, opcode.ENDTRYL, after)
|
||||||
|
c.setLabel(stmt.finallyLabel)
|
||||||
|
// Execute body.
|
||||||
|
ast.Walk(c, stmt.expr)
|
||||||
|
emit.Opcode(c.prog.BinWriter, opcode.ENDFINALLY)
|
||||||
|
c.setLabel(after)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *codegen) rangeLoadKey() {
|
func (c *codegen) rangeLoadKey() {
|
||||||
emit.Int(c.prog.BinWriter, 2)
|
emit.Int(c.prog.BinWriter, 2)
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.PICK) // load keys
|
emit.Opcode(c.prog.BinWriter, opcode.PICK) // load keys
|
||||||
|
@ -1787,27 +1814,27 @@ func (c *codegen) writeJumps(b []byte) ([]byte, error) {
|
||||||
case opcode.JMP, opcode.JMPIFNOT, opcode.JMPIF, opcode.CALL,
|
case opcode.JMP, opcode.JMPIFNOT, opcode.JMPIF, opcode.CALL,
|
||||||
opcode.JMPEQ, opcode.JMPNE,
|
opcode.JMPEQ, opcode.JMPNE,
|
||||||
opcode.JMPGT, opcode.JMPGE, opcode.JMPLE, opcode.JMPLT:
|
opcode.JMPGT, opcode.JMPGE, opcode.JMPLE, opcode.JMPLT:
|
||||||
|
case opcode.TRYL:
|
||||||
|
nextIP := ctx.NextIP()
|
||||||
|
finallyArg := b[nextIP-4:]
|
||||||
|
_, err := c.replaceLabelWithOffset(ctx.IP(), finallyArg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
case opcode.JMPL, opcode.JMPIFL, opcode.JMPIFNOTL,
|
case opcode.JMPL, opcode.JMPIFL, opcode.JMPIFNOTL,
|
||||||
opcode.JMPEQL, opcode.JMPNEL,
|
opcode.JMPEQL, opcode.JMPNEL,
|
||||||
opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLEL, opcode.JMPLTL,
|
opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLEL, opcode.JMPLTL,
|
||||||
opcode.CALLL, opcode.PUSHA:
|
opcode.CALLL, opcode.PUSHA, opcode.ENDTRYL:
|
||||||
// we can't use arg returned by ctx.Next() because it is copied
|
// we can't use arg returned by ctx.Next() because it is copied
|
||||||
nextIP := ctx.NextIP()
|
nextIP := ctx.NextIP()
|
||||||
arg := b[nextIP-4:]
|
arg := b[nextIP-4:]
|
||||||
|
offset, err := c.replaceLabelWithOffset(ctx.IP(), arg)
|
||||||
index := binary.LittleEndian.Uint16(arg)
|
if err != nil {
|
||||||
if int(index) > len(c.l) {
|
return nil, err
|
||||||
return nil, fmt.Errorf("unexpected label number: %d (max %d)", index, len(c.l))
|
|
||||||
}
|
|
||||||
offset := c.l[index] - nextIP + 5
|
|
||||||
if offset > math.MaxInt32 || offset < math.MinInt32 {
|
|
||||||
return nil, fmt.Errorf("label offset is too big at the instruction %d: %d (max %d, min %d)",
|
|
||||||
nextIP-5, offset, math.MaxInt32, math.MinInt32)
|
|
||||||
}
|
}
|
||||||
if op != opcode.PUSHA && math.MinInt8 <= offset && offset <= math.MaxInt8 {
|
if op != opcode.PUSHA && math.MinInt8 <= offset && offset <= math.MaxInt8 {
|
||||||
offsets = append(offsets, ctx.IP())
|
offsets = append(offsets, ctx.IP())
|
||||||
}
|
}
|
||||||
binary.LittleEndian.PutUint32(arg, uint32(offset))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Correct function ip range.
|
// Correct function ip range.
|
||||||
|
@ -1829,6 +1856,20 @@ func (c *codegen) writeJumps(b []byte) ([]byte, error) {
|
||||||
return shortenJumps(b, offsets), nil
|
return shortenJumps(b, offsets), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *codegen) replaceLabelWithOffset(ip int, arg []byte) (int, error) {
|
||||||
|
index := binary.LittleEndian.Uint16(arg)
|
||||||
|
if int(index) > len(c.l) {
|
||||||
|
return 0, fmt.Errorf("unexpected label number: %d (max %d)", index, len(c.l))
|
||||||
|
}
|
||||||
|
offset := c.l[index] - ip
|
||||||
|
if offset > math.MaxInt32 || offset < math.MinInt32 {
|
||||||
|
return 0, fmt.Errorf("label offset is too big at the instruction %d: %d (max %d, min %d)",
|
||||||
|
ip, offset, math.MaxInt32, math.MinInt32)
|
||||||
|
}
|
||||||
|
binary.LittleEndian.PutUint32(arg, uint32(offset))
|
||||||
|
return offset, nil
|
||||||
|
}
|
||||||
|
|
||||||
// longToShortRemoveCount is a difference between short and long instruction sizes in bytes.
|
// longToShortRemoveCount is a difference between short and long instruction sizes in bytes.
|
||||||
const longToShortRemoveCount = 3
|
const longToShortRemoveCount = 3
|
||||||
|
|
||||||
|
@ -1853,18 +1894,27 @@ func shortenJumps(b []byte, offsets []int) []byte {
|
||||||
switch op {
|
switch op {
|
||||||
case opcode.JMP, opcode.JMPIFNOT, opcode.JMPIF, opcode.CALL,
|
case opcode.JMP, opcode.JMPIFNOT, opcode.JMPIF, opcode.CALL,
|
||||||
opcode.JMPEQ, opcode.JMPNE,
|
opcode.JMPEQ, opcode.JMPNE,
|
||||||
opcode.JMPGT, opcode.JMPGE, opcode.JMPLE, opcode.JMPLT:
|
opcode.JMPGT, opcode.JMPGE, opcode.JMPLE, opcode.JMPLT, opcode.ENDTRY:
|
||||||
offset := int(int8(b[nextIP-1]))
|
offset := int(int8(b[nextIP-1]))
|
||||||
offset += calcOffsetCorrection(ip, ip+offset, offsets)
|
offset += calcOffsetCorrection(ip, ip+offset, offsets)
|
||||||
b[nextIP-1] = byte(offset)
|
b[nextIP-1] = byte(offset)
|
||||||
|
case opcode.TRY:
|
||||||
|
finallyOffset := int(int8(b[nextIP-1]))
|
||||||
|
finallyOffset += calcOffsetCorrection(ip, ip+finallyOffset, offsets)
|
||||||
|
b[nextIP-1] = byte(finallyOffset)
|
||||||
case opcode.JMPL, opcode.JMPIFL, opcode.JMPIFNOTL,
|
case opcode.JMPL, opcode.JMPIFL, opcode.JMPIFNOTL,
|
||||||
opcode.JMPEQL, opcode.JMPNEL,
|
opcode.JMPEQL, opcode.JMPNEL,
|
||||||
opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLEL, opcode.JMPLTL,
|
opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLEL, opcode.JMPLTL,
|
||||||
opcode.CALLL, opcode.PUSHA:
|
opcode.CALLL, opcode.PUSHA, opcode.ENDTRYL:
|
||||||
arg := b[nextIP-4:]
|
arg := b[nextIP-4:]
|
||||||
offset := int(int32(binary.LittleEndian.Uint32(arg)))
|
offset := int(int32(binary.LittleEndian.Uint32(arg)))
|
||||||
offset += calcOffsetCorrection(ip, ip+offset, offsets)
|
offset += calcOffsetCorrection(ip, ip+offset, offsets)
|
||||||
binary.LittleEndian.PutUint32(arg, uint32(offset))
|
binary.LittleEndian.PutUint32(arg, uint32(offset))
|
||||||
|
case opcode.TRYL:
|
||||||
|
arg := b[nextIP-4:]
|
||||||
|
finallyOffset := int(int32(binary.LittleEndian.Uint32(arg)))
|
||||||
|
finallyOffset += calcOffsetCorrection(ip, ip+finallyOffset, offsets)
|
||||||
|
binary.LittleEndian.PutUint32(arg, uint32(finallyOffset))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1948,6 +1998,8 @@ func toShortForm(op opcode.Opcode) opcode.Opcode {
|
||||||
return opcode.JMPLT
|
return opcode.JMPLT
|
||||||
case opcode.CALLL:
|
case opcode.CALLL:
|
||||||
return opcode.CALL
|
return opcode.CALL
|
||||||
|
case opcode.ENDTRYL:
|
||||||
|
return opcode.ENDTRY
|
||||||
default:
|
default:
|
||||||
panic(fmt.Errorf("invalid opcode: %s", op))
|
panic(fmt.Errorf("invalid opcode: %s", op))
|
||||||
}
|
}
|
||||||
|
|
82
pkg/compiler/defer_test.go
Normal file
82
pkg/compiler/defer_test.go
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
package compiler_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDefer(t *testing.T) {
|
||||||
|
t.Run("Simple", func(t *testing.T) {
|
||||||
|
src := `package main
|
||||||
|
var a int
|
||||||
|
func Main() int {
|
||||||
|
return h() + a
|
||||||
|
}
|
||||||
|
func h() int {
|
||||||
|
defer f()
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
func f() { a += 2 }`
|
||||||
|
eval(t, src, big.NewInt(3))
|
||||||
|
})
|
||||||
|
t.Run("ValueUnchanged", func(t *testing.T) {
|
||||||
|
src := `package main
|
||||||
|
var a int
|
||||||
|
func Main() int {
|
||||||
|
defer f()
|
||||||
|
a = 3
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
func f() { a += 2 }`
|
||||||
|
eval(t, src, big.NewInt(3))
|
||||||
|
})
|
||||||
|
t.Run("Function", func(t *testing.T) {
|
||||||
|
src := `package main
|
||||||
|
var a int
|
||||||
|
func Main() int {
|
||||||
|
return h() + a
|
||||||
|
}
|
||||||
|
func h() int {
|
||||||
|
defer f()
|
||||||
|
a = 3
|
||||||
|
return g()
|
||||||
|
}
|
||||||
|
func g() int {
|
||||||
|
a++
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
func f() { a += 2 }`
|
||||||
|
eval(t, src, big.NewInt(10))
|
||||||
|
})
|
||||||
|
t.Run("MultipleDefers", func(t *testing.T) {
|
||||||
|
src := `package main
|
||||||
|
var a int
|
||||||
|
func Main() int {
|
||||||
|
return h() + a
|
||||||
|
}
|
||||||
|
func h() int {
|
||||||
|
defer f()
|
||||||
|
defer g()
|
||||||
|
a = 3
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
func g() { a *= 2 }
|
||||||
|
func f() { a += 2 }`
|
||||||
|
eval(t, src, big.NewInt(11))
|
||||||
|
})
|
||||||
|
t.Run("FunctionLiteral", func(t *testing.T) {
|
||||||
|
src := `package main
|
||||||
|
var a int
|
||||||
|
func Main() int {
|
||||||
|
return h() + a
|
||||||
|
}
|
||||||
|
func h() int {
|
||||||
|
defer func() {
|
||||||
|
a = 10
|
||||||
|
}()
|
||||||
|
a = 3
|
||||||
|
return a
|
||||||
|
}`
|
||||||
|
eval(t, src, big.NewInt(13))
|
||||||
|
})
|
||||||
|
}
|
|
@ -30,6 +30,9 @@ type funcScope struct {
|
||||||
// Variables together with it's type in neo-vm.
|
// Variables together with it's type in neo-vm.
|
||||||
variables []string
|
variables []string
|
||||||
|
|
||||||
|
// deferStack is a stack containing encountered `defer` statements.
|
||||||
|
deferStack []deferInfo
|
||||||
|
|
||||||
// Local variables
|
// Local variables
|
||||||
vars varScope
|
vars varScope
|
||||||
|
|
||||||
|
@ -45,6 +48,11 @@ type funcScope struct {
|
||||||
i int
|
i int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type deferInfo struct {
|
||||||
|
finallyLabel uint16
|
||||||
|
expr *ast.CallExpr
|
||||||
|
}
|
||||||
|
|
||||||
func (c *codegen) newFuncScope(decl *ast.FuncDecl, label uint16) *funcScope {
|
func (c *codegen) newFuncScope(decl *ast.FuncDecl, label uint16) *funcScope {
|
||||||
var name string
|
var name string
|
||||||
if decl.Name != nil {
|
if decl.Name != nil {
|
||||||
|
|
|
@ -160,5 +160,5 @@ func AppCallWithOperationAndArgs(w *io.BinWriter, scriptHash util.Uint160, opera
|
||||||
}
|
}
|
||||||
|
|
||||||
func isInstructionJmp(op opcode.Opcode) bool {
|
func isInstructionJmp(op opcode.Opcode) bool {
|
||||||
return opcode.JMP <= op && op <= opcode.CALLL
|
return opcode.JMP <= op && op <= opcode.CALLL || op == opcode.ENDTRYL
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue