vm: optimize ROLL/ROT, refactor common code

Add `Roll` method to Stack that doesn't pop and push values and use it for
ROLL and ROT.

1.4M->1.5M 100K block import test before:
real    3m44,292s
user    5m43,494s
sys     0m34,741s

After:
real    3m40,449s
user    5m42,701s
sys     0m35,500s
This commit is contained in:
Roman Khimov 2019-12-16 19:53:21 +03:00
parent 2627628387
commit 587cfc7c66
4 changed files with 108 additions and 13 deletions

View file

@ -389,6 +389,34 @@ func (s *Stack) Swap(n1, n2 int) error {
return nil
}
// Roll brings an item with the given index to the top of the stack, moving all
// the other elements down accordingly. It does all of that without popping and
// pushing elements.
func (s *Stack) Roll(n int) error {
if n < 0 {
return errors.New("negative index")
}
if n >= s.len {
return errors.New("too big index")
}
if n == 0 {
return nil
}
top := s.Peek(0)
e := s.Peek(n)
e.prev.next = e.next
e.next.prev = e.prev
top.prev = e
e.next = top
e.prev = &s.top
s.top.next = e
return nil
}
// popSigElements pops keys or signatures from the stack as needed for
// CHECKMULTISIG.
func (s *Stack) popSigElements() ([][]byte, error) {

View file

@ -258,6 +258,46 @@ func TestSwapElemValues(t *testing.T) {
assert.Equal(t, int64(1), s.Pop().BigInt().Int64())
}
func TestRoll(t *testing.T) {
s := NewStack("test")
s.PushVal(1)
s.PushVal(2)
s.PushVal(3)
s.PushVal(4)
assert.NoError(t, s.Roll(2))
assert.Equal(t, int64(2), s.Pop().BigInt().Int64())
assert.Equal(t, int64(4), s.Pop().BigInt().Int64())
assert.Equal(t, int64(3), s.Pop().BigInt().Int64())
assert.Equal(t, int64(1), s.Pop().BigInt().Int64())
s.PushVal(1)
s.PushVal(2)
s.PushVal(3)
s.PushVal(4)
assert.NoError(t, s.Roll(3))
assert.Equal(t, int64(1), s.Pop().BigInt().Int64())
assert.Equal(t, int64(4), s.Pop().BigInt().Int64())
assert.Equal(t, int64(3), s.Pop().BigInt().Int64())
assert.Equal(t, int64(2), s.Pop().BigInt().Int64())
s.PushVal(1)
s.PushVal(2)
s.PushVal(3)
s.PushVal(4)
assert.Error(t, s.Roll(-1))
assert.Error(t, s.Roll(4))
assert.NoError(t, s.Roll(0))
assert.Equal(t, int64(4), s.Pop().BigInt().Int64())
assert.Equal(t, int64(3), s.Pop().BigInt().Int64())
assert.Equal(t, int64(2), s.Pop().BigInt().Int64())
assert.Equal(t, int64(1), s.Pop().BigInt().Int64())
}
func TestPopSigElements(t *testing.T) {
s := NewStack("test")

View file

@ -607,11 +607,10 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
v.estack.InsertAt(a, n)
case opcode.ROT:
e := v.estack.RemoveAt(2)
if e == nil {
panic("no top-level element found")
err := v.estack.Roll(2)
if err != nil {
panic(err.Error())
}
v.estack.Push(e)
case opcode.DEPTH:
v.estack.PushVal(v.estack.Len())
@ -642,15 +641,9 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
case opcode.ROLL:
n := int(v.estack.Pop().BigInt().Int64())
if n < 0 {
panic("negative stack item returned")
}
if n > 0 {
e := v.estack.RemoveAt(n)
if e == nil {
panic("bad index")
}
v.estack.Push(e)
err := v.estack.Roll(n)
if err != nil {
panic(err.Error())
}
case opcode.DROP:

View file

@ -1518,6 +1518,40 @@ func TestROTGood(t *testing.T) {
assert.Equal(t, makeStackItem(2), vm.estack.Pop().value)
}
func TestROLLBad1(t *testing.T) {
prog := makeProgram(opcode.ROLL)
vm := load(prog)
vm.estack.PushVal(1)
vm.estack.PushVal(-1)
checkVMFailed(t, vm)
}
func TestROLLBad2(t *testing.T) {
prog := makeProgram(opcode.ROLL)
vm := load(prog)
vm.estack.PushVal(1)
vm.estack.PushVal(2)
vm.estack.PushVal(3)
vm.estack.PushVal(3)
checkVMFailed(t, vm)
}
func TestROLLGood(t *testing.T) {
prog := makeProgram(opcode.ROLL)
vm := load(prog)
vm.estack.PushVal(1)
vm.estack.PushVal(2)
vm.estack.PushVal(3)
vm.estack.PushVal(4)
vm.estack.PushVal(1)
runVM(t, vm)
assert.Equal(t, 4, vm.estack.Len())
assert.Equal(t, makeStackItem(3), vm.estack.Pop().value)
assert.Equal(t, makeStackItem(4), vm.estack.Pop().value)
assert.Equal(t, makeStackItem(2), vm.estack.Pop().value)
assert.Equal(t, makeStackItem(1), vm.estack.Pop().value)
}
func TestXTUCKbadNoitem(t *testing.T) {
prog := makeProgram(opcode.XTUCK)
vm := load(prog)