compiler: drop stack after inline
Some control-flow statements drop stack items, for example `return` when it is used inside of `range` loop. For inlined calls this `return` should drop only portion of stack which belongs to inlined call.
This commit is contained in:
parent
347212c0c5
commit
b66b853285
4 changed files with 74 additions and 1 deletions
|
@ -49,6 +49,9 @@ type codegen struct {
|
||||||
labels map[labelWithType]uint16
|
labels map[labelWithType]uint16
|
||||||
// A list of nested label names together with evaluation stack depth.
|
// A list of nested label names together with evaluation stack depth.
|
||||||
labelList []labelWithStackSize
|
labelList []labelWithStackSize
|
||||||
|
// inlineLabelOffsets contains size of labelList at the start of inline call processing.
|
||||||
|
// For such calls we need to drop only newly created part of stack.
|
||||||
|
inlineLabelOffsets []int
|
||||||
|
|
||||||
// A label for the for-loop being currently visited.
|
// A label for the for-loop being currently visited.
|
||||||
currentFor string
|
currentFor string
|
||||||
|
@ -607,7 +610,11 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
c.setLabel(l)
|
c.setLabel(l)
|
||||||
|
|
||||||
cnt := 0
|
cnt := 0
|
||||||
for i := range c.labelList {
|
start := 0
|
||||||
|
if len(c.inlineLabelOffsets) > 0 {
|
||||||
|
start = c.inlineLabelOffsets[len(c.inlineLabelOffsets)-1]
|
||||||
|
}
|
||||||
|
for i := start; i < len(c.labelList); i++ {
|
||||||
cnt += c.labelList[i].sz
|
cnt += c.labelList[i].sz
|
||||||
}
|
}
|
||||||
c.dropItems(cnt)
|
c.dropItems(cnt)
|
||||||
|
|
|
@ -16,6 +16,14 @@ import (
|
||||||
// <inline body of f directly>
|
// <inline body of f directly>
|
||||||
// }
|
// }
|
||||||
func (c *codegen) inlineCall(f *funcScope, n *ast.CallExpr) {
|
func (c *codegen) inlineCall(f *funcScope, n *ast.CallExpr) {
|
||||||
|
labelSz := len(c.labelList)
|
||||||
|
offSz := len(c.inlineLabelOffsets)
|
||||||
|
c.inlineLabelOffsets = append(c.inlineLabelOffsets, labelSz)
|
||||||
|
defer func() {
|
||||||
|
c.inlineLabelOffsets = c.inlineLabelOffsets[:offSz]
|
||||||
|
c.labelList = c.labelList[:labelSz]
|
||||||
|
}()
|
||||||
|
|
||||||
pkg := c.buildInfo.program.Package(f.pkg.Path())
|
pkg := c.buildInfo.program.Package(f.pkg.Path())
|
||||||
sig := c.typeOf(n.Fun).(*types.Signature)
|
sig := c.typeOf(n.Fun).(*types.Signature)
|
||||||
|
|
||||||
|
|
|
@ -115,6 +115,56 @@ func TestInline(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInlineInLoop(t *testing.T) {
|
||||||
|
t.Run("simple", func(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
import "github.com/nspcc-dev/neo-go/pkg/interop/binary"
|
||||||
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/inline"
|
||||||
|
func Main() int {
|
||||||
|
sum := 0
|
||||||
|
values := []int{10, 11}
|
||||||
|
for _, v := range values {
|
||||||
|
binary.Itoa(v, 10)
|
||||||
|
sum += inline.VarSum(1, 2, 3, 4)
|
||||||
|
}
|
||||||
|
return sum
|
||||||
|
}`
|
||||||
|
eval(t, src, big.NewInt(20))
|
||||||
|
})
|
||||||
|
t.Run("check clean stack on return", func(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
import "github.com/nspcc-dev/neo-go/pkg/interop/binary"
|
||||||
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/inline"
|
||||||
|
func Main() int {
|
||||||
|
values := []int{10, 11, 12}
|
||||||
|
for _, v := range values {
|
||||||
|
binary.Itoa(v, 10)
|
||||||
|
if v == 11 {
|
||||||
|
return inline.VarSum(2, 20, 200)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}`
|
||||||
|
eval(t, src, big.NewInt(222))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInlineInSwitch(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/inline"
|
||||||
|
func Main() int {
|
||||||
|
switch inline.VarSum(1, 2) {
|
||||||
|
case inline.VarSum(3, 1):
|
||||||
|
return 10
|
||||||
|
case inline.VarSum(4, -1):
|
||||||
|
return 11
|
||||||
|
default:
|
||||||
|
return 12
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
eval(t, src, big.NewInt(11))
|
||||||
|
}
|
||||||
|
|
||||||
func TestInlineGlobalVariable(t *testing.T) {
|
func TestInlineGlobalVariable(t *testing.T) {
|
||||||
t.Run("simple", func(t *testing.T) {
|
t.Run("simple", func(t *testing.T) {
|
||||||
src := `package foo
|
src := `package foo
|
||||||
|
|
|
@ -107,6 +107,7 @@ func newStoragePlugin() *storagePlugin {
|
||||||
s.interops[interopnames.ToID([]byte(interopnames.SystemStoragePut))] = s.Put
|
s.interops[interopnames.ToID([]byte(interopnames.SystemStoragePut))] = s.Put
|
||||||
s.interops[interopnames.ToID([]byte(interopnames.SystemStorageGetContext))] = s.GetContext
|
s.interops[interopnames.ToID([]byte(interopnames.SystemStorageGetContext))] = s.GetContext
|
||||||
s.interops[interopnames.ToID([]byte(interopnames.SystemRuntimeNotify))] = s.Notify
|
s.interops[interopnames.ToID([]byte(interopnames.SystemRuntimeNotify))] = s.Notify
|
||||||
|
s.interops[interopnames.ToID([]byte(interopnames.SystemBinaryItoa))] = s.Itoa
|
||||||
return s
|
return s
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -122,6 +123,13 @@ func (s *storagePlugin) syscallHandler(v *vm.VM, id uint32) error {
|
||||||
return errors.New("syscall not found")
|
return errors.New("syscall not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *storagePlugin) Itoa(v *vm.VM) error {
|
||||||
|
n := v.Estack().Pop().BigInt()
|
||||||
|
base := v.Estack().Pop().BigInt()
|
||||||
|
v.Estack().PushVal(n.Text(int(base.Int64())))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *storagePlugin) Notify(v *vm.VM) error {
|
func (s *storagePlugin) Notify(v *vm.VM) error {
|
||||||
name := v.Estack().Pop().String()
|
name := v.Estack().Pop().String()
|
||||||
item := stackitem.NewArray(v.Estack().Pop().Array())
|
item := stackitem.NewArray(v.Estack().Pop().Array())
|
||||||
|
|
Loading…
Reference in a new issue