From cff8b1c24e8d6ca3bfdabdf6feaf9c1df713cfad Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Tue, 3 Aug 2021 19:37:06 +0300 Subject: [PATCH 1/7] stackitem: use `Bool` item directly It is always copied. Signed-off-by: Evgeniy Stratonikov --- pkg/smartcontract/convertor.go | 2 +- pkg/vm/json_test.go | 2 +- pkg/vm/stackitem/item.go | 56 ++++++++++++++----------------- pkg/vm/stackitem/item_test.go | 12 ++++--- pkg/vm/stackitem/json.go | 8 ++--- pkg/vm/stackitem/serialization.go | 4 +-- 6 files changed, 40 insertions(+), 44 deletions(-) diff --git a/pkg/smartcontract/convertor.go b/pkg/smartcontract/convertor.go index c592807b8..d4d9e45d7 100644 --- a/pkg/smartcontract/convertor.go +++ b/pkg/smartcontract/convertor.go @@ -17,7 +17,7 @@ func ParameterFromStackItem(i stackitem.Item, seen map[stackitem.Item]bool) Para Type: IntegerType, Value: i.Value().(*big.Int).Int64(), } - case *stackitem.Bool: + case stackitem.Bool: return Parameter{ Type: BoolType, Value: i.Value().(bool), diff --git a/pkg/vm/json_test.go b/pkg/vm/json_test.go index fd3f87860..54fe8ff00 100644 --- a/pkg/vm/json_test.go +++ b/pkg/vm/json_test.go @@ -195,7 +195,7 @@ func compareItems(t *testing.T, a, b stackitem.Item) { require.Equal(t, val, ac.Value().(*big.Int).Int64()) case *stackitem.ByteArray: require.Equal(t, val, bigint.FromBytes(ac.Value().([]byte)).Int64()) - case *stackitem.Bool: + case stackitem.Bool: if ac.Value().(bool) { require.Equal(t, val, int64(1)) } else { diff --git a/pkg/vm/stackitem/item.go b/pkg/vm/stackitem/item.go index 2f9e95ac1..f042c0c77 100644 --- a/pkg/vm/stackitem/item.go +++ b/pkg/vm/stackitem/item.go @@ -126,9 +126,7 @@ func Make(v interface{}) Item { value: []byte(val), } case bool: - return &Bool{ - value: val, - } + return Bool(val) case []Item: return &Array{ value: val, @@ -485,76 +483,72 @@ func (i *BigInteger) MarshalJSON() ([]byte, error) { } // Bool represents a boolean Item. -type Bool struct { - value bool -} +type Bool bool // NewBool returns an new Bool object. -func NewBool(val bool) *Bool { - return &Bool{ - value: val, - } +func NewBool(val bool) Bool { + return Bool(val) } // Value implements Item interface. -func (i *Bool) Value() interface{} { - return i.value +func (i Bool) Value() interface{} { + return bool(i) } // MarshalJSON implements the json.Marshaler interface. -func (i *Bool) MarshalJSON() ([]byte, error) { - return json.Marshal(i.value) +func (i Bool) MarshalJSON() ([]byte, error) { + return json.Marshal(bool(i)) } -func (i *Bool) String() string { +func (i Bool) String() string { return "Boolean" } // Dup implements Item interface. -func (i *Bool) Dup() Item { - return &Bool{i.value} +func (i Bool) Dup() Item { + return i } // TryBool implements Item interface. -func (i *Bool) TryBool() (bool, error) { return i.value, nil } +func (i Bool) TryBool() (bool, error) { return bool(i), nil } // Bytes converts Bool to bytes. -func (i *Bool) Bytes() []byte { - if i.value { +func (i Bool) Bytes() []byte { + if i { return []byte{1} } return []byte{0} } // TryBytes implements Item interface. -func (i *Bool) TryBytes() ([]byte, error) { +func (i Bool) TryBytes() ([]byte, error) { return i.Bytes(), nil } // TryInteger implements Item interface. -func (i *Bool) TryInteger() (*big.Int, error) { - if i.value { +func (i Bool) TryInteger() (*big.Int, error) { + if i { return big.NewInt(1), nil } return big.NewInt(0), nil } // Equals implements Item interface. -func (i *Bool) Equals(s Item) bool { +func (i Bool) Equals(s Item) bool { if i == s { return true } else if s == nil { return false } - val, ok := s.(*Bool) - return ok && i.value == val.value + val, ok := s.(Bool) + return ok && i == val } // Type implements Item interface. -func (i *Bool) Type() Type { return BooleanT } +func (i Bool) Type() Type { return BooleanT } // Convert implements Item interface. -func (i *Bool) Convert(typ Type) (Item, error) { +func (i Bool) Convert(typ Type) (Item, error) { return convertPrimitive(i, typ) } @@ -861,7 +855,7 @@ func (i *Map) Drop(index int) { // key. func IsValidMapKey(key Item) error { switch key.(type) { - case *Bool, *BigInteger: + case Bool, *BigInteger: return nil case *ByteArray: size := len(key.Value().([]byte)) @@ -1171,8 +1165,8 @@ func deepCopy(item Item, seen map[Item]Item) Item { return NewByteArray(slice.Copy(it.value)) case *Buffer: return NewBuffer(slice.Copy(it.value)) - case *Bool: - return NewBool(it.value) + case Bool: + return it case *Pointer: return NewPointerWithHash(it.pos, it.script, it.hash) case *Interop: diff --git a/pkg/vm/stackitem/item_test.go b/pkg/vm/stackitem/item_test.go index c114229ce..61d961851 100644 --- a/pkg/vm/stackitem/item_test.go +++ b/pkg/vm/stackitem/item_test.go @@ -63,11 +63,11 @@ var makeStackItemTestCases = []struct { }, { input: true, - result: &Bool{value: true}, + result: Bool(true), }, { input: false, - result: &Bool{value: false}, + result: Bool(false), }, { input: []Item{&BigInteger{value: big.NewInt(3)}, &ByteArray{value: []byte{1, 2, 3}}}, @@ -459,8 +459,8 @@ func TestMarshalJSON(t *testing.T) { switch testCase.input.(type) { case *BigInteger: actual, err = testCase.input.(*BigInteger).MarshalJSON() - case *Bool: - actual, err = testCase.input.(*Bool).MarshalJSON() + case Bool: + actual, err = testCase.input.(Bool).MarshalJSON() case *ByteArray: actual, err = testCase.input.(*ByteArray).MarshalJSON() case *Array: @@ -532,7 +532,9 @@ func TestDeepCopy(t *testing.T) { t.Run(tc.name, func(t *testing.T) { actual := DeepCopy(tc.item) require.Equal(t, tc.item, actual) - require.False(t, actual == tc.item) + if tc.item.Type() != BooleanT { + require.False(t, actual == tc.item) + } }) } diff --git a/pkg/vm/stackitem/json.go b/pkg/vm/stackitem/json.go index 51a89b43f..5b346ada1 100644 --- a/pkg/vm/stackitem/json.go +++ b/pkg/vm/stackitem/json.go @@ -120,8 +120,8 @@ func toJSON(data []byte, seen map[Item]sliceNoPointer, item Item) ([]byte, error return nil, err } data = append(data, raw...) - case *Bool: - if it.value { + case Bool: + if it { data = append(data, "true"...) } else { data = append(data, "false"...) @@ -288,8 +288,8 @@ func toJSONWithTypes(item Item, seen map[Item]bool) (interface{}, error) { } value = arr delete(seen, item) - case *Bool: - value = it.value + case Bool: + value = bool(it) case *Buffer, *ByteArray: value = base64.StdEncoding.EncodeToString(it.Value().([]byte)) case *BigInteger: diff --git a/pkg/vm/stackitem/serialization.go b/pkg/vm/stackitem/serialization.go index 84e01bedc..8db760e37 100644 --- a/pkg/vm/stackitem/serialization.go +++ b/pkg/vm/stackitem/serialization.go @@ -103,9 +103,9 @@ func (w *serContext) serialize(item Item) error { data := t.Value().([]byte) w.appendVarUint(uint64(len(data))) w.data = append(w.data, data...) - case *Bool: + case Bool: w.data = append(w.data, byte(BooleanT)) - if t.Value().(bool) { + if t { w.data = append(w.data, 1) } else { w.data = append(w.data, 0) From a5516e8c96a9203547f17c5d4a9186ae4dc8ab2b Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Wed, 4 Aug 2021 16:41:13 +0300 Subject: [PATCH 2/7] stackitem: make `BigInteger` alias to `big.Int` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove one indirection step. `` name old time/op new time/op delta MakeInt-8 79.7ns ± 8% 56.2ns ± 8% -29.44% (p=0.000 n=10+10) name old alloc/op new alloc/op delta MakeInt-8 48.0B ± 0% 40.0B ± 0% -16.67% (p=0.000 n=10+10) name old allocs/op new allocs/op delta MakeInt-8 3.00 ± 0% 2.00 ± 0% -33.33% (p=0.000 n=10+10) ``` Signed-off-by: Evgeniy Stratonikov --- pkg/vm/stackitem/item.go | 63 ++++++++++++----------------------- pkg/vm/stackitem/item_test.go | 34 +++++++++---------- pkg/vm/stackitem/json.go | 6 ++-- 3 files changed, 42 insertions(+), 61 deletions(-) diff --git a/pkg/vm/stackitem/item.go b/pkg/vm/stackitem/item.go index f042c0c77..a57dfb30d 100644 --- a/pkg/vm/stackitem/item.go +++ b/pkg/vm/stackitem/item.go @@ -2,7 +2,6 @@ package stackitem import ( "bytes" - "encoding/binary" "encoding/hex" "encoding/json" "errors" @@ -90,33 +89,17 @@ func mkInvConversion(from Item, to Type) error { func Make(v interface{}) Item { switch val := v.(type) { case int: - return &BigInteger{ - value: big.NewInt(int64(val)), - } + return (*BigInteger)(big.NewInt(int64(val))) case int64: - return &BigInteger{ - value: big.NewInt(val), - } + return (*BigInteger)(big.NewInt(val)) case uint8: - return &BigInteger{ - value: big.NewInt(int64(val)), - } + return (*BigInteger)(big.NewInt(int64(val))) case uint16: - return &BigInteger{ - value: big.NewInt(int64(val)), - } + return (*BigInteger)(big.NewInt(int64(val))) case uint32: - return &BigInteger{ - value: big.NewInt(int64(val)), - } + return (*BigInteger)(big.NewInt(int64(val))) case uint64: - b := make([]byte, 8) - binary.BigEndian.PutUint64(b, val) - bigInt := big.NewInt(0) - bigInt.SetBytes(b) - return &BigInteger{ - value: bigInt, - } + return (*BigInteger)(new(big.Int).SetUint64(val)) case []byte: return &ByteArray{ value: val, @@ -400,9 +383,7 @@ func (i Null) Convert(typ Type) (Item, error) { } // BigInteger represents a big integer on the stack. -type BigInteger struct { - value *big.Int -} +type BigInteger big.Int // NewBigInteger returns an new BigInteger object. func NewBigInteger(value *big.Int) *BigInteger { @@ -418,19 +399,22 @@ func NewBigInteger(value *big.Int) *BigInteger { panic(errTooBigInteger) } } - return &BigInteger{ - value: value, - } + return (*BigInteger)(value) +} + +// Big casts i to the big.Int type. +func (i *BigInteger) Big() *big.Int { + return (*big.Int)(i) } // Bytes converts i to a slice of bytes. func (i *BigInteger) Bytes() []byte { - return bigint.ToBytes(i.value) + return bigint.ToBytes(i.Big()) } // TryBool implements Item interface. func (i *BigInteger) TryBool() (bool, error) { - return i.value.Sign() != 0, nil + return i.Big().Sign() != 0, nil } // TryBytes implements Item interface. @@ -440,7 +424,7 @@ func (i *BigInteger) TryBytes() ([]byte, error) { // TryInteger implements Item interface. func (i *BigInteger) TryInteger() (*big.Int, error) { - return i.value, nil + return i.Big(), nil } // Equals implements Item interface. @@ -451,12 +435,12 @@ func (i *BigInteger) Equals(s Item) bool { return false } val, ok := s.(*BigInteger) - return ok && i.value.Cmp(val.value) == 0 + return ok && i.Big().Cmp(val.Big()) == 0 } // Value implements Item interface. func (i *BigInteger) Value() interface{} { - return i.value + return i.Big() } func (i *BigInteger) String() string { @@ -466,7 +450,7 @@ func (i *BigInteger) String() string { // Dup implements Item interface. func (i *BigInteger) Dup() Item { n := new(big.Int) - return &BigInteger{n.Set(i.value)} + return (*BigInteger)(n.Set(i.Big())) } // Type implements Item interface. @@ -479,7 +463,7 @@ func (i *BigInteger) Convert(typ Type) (Item, error) { // MarshalJSON implements the json.Marshaler interface. func (i *BigInteger) MarshalJSON() ([]byte, error) { - return json.Marshal(i.value) + return json.Marshal(i.Big()) } // Bool represents a boolean Item. @@ -1156,11 +1140,8 @@ func deepCopy(item Item, seen map[Item]Item) Item { } return m case *BigInteger: - bi := new(big.Int).SetBytes(it.value.Bytes()) - if it.value.Sign() == -1 { - bi.Neg(bi) - } - return NewBigInteger(bi) + bi := new(big.Int).Set(it.Big()) + return (*BigInteger)(bi) case *ByteArray: return NewByteArray(slice.Copy(it.value)) case *Buffer: diff --git a/pkg/vm/stackitem/item_test.go b/pkg/vm/stackitem/item_test.go index 61d961851..b6b877aec 100644 --- a/pkg/vm/stackitem/item_test.go +++ b/pkg/vm/stackitem/item_test.go @@ -15,35 +15,35 @@ var makeStackItemTestCases = []struct { }{ { input: int64(3), - result: &BigInteger{value: big.NewInt(3)}, + result: (*BigInteger)(big.NewInt(3)), }, { input: int16(3), - result: &BigInteger{value: big.NewInt(3)}, + result: (*BigInteger)(big.NewInt(3)), }, { input: 3, - result: &BigInteger{value: big.NewInt(3)}, + result: (*BigInteger)(big.NewInt(3)), }, { input: uint8(3), - result: &BigInteger{value: big.NewInt(3)}, + result: (*BigInteger)(big.NewInt(3)), }, { input: uint16(3), - result: &BigInteger{value: big.NewInt(3)}, + result: (*BigInteger)(big.NewInt(3)), }, { input: uint32(3), - result: &BigInteger{value: big.NewInt(3)}, + result: (*BigInteger)(big.NewInt(3)), }, { input: uint64(3), - result: &BigInteger{value: big.NewInt(3)}, + result: (*BigInteger)(big.NewInt(3)), }, { input: big.NewInt(3), - result: &BigInteger{value: big.NewInt(3)}, + result: (*BigInteger)(big.NewInt(3)), }, { input: []byte{1, 2, 3, 4}, @@ -70,12 +70,12 @@ var makeStackItemTestCases = []struct { result: Bool(false), }, { - input: []Item{&BigInteger{value: big.NewInt(3)}, &ByteArray{value: []byte{1, 2, 3}}}, - result: &Array{value: []Item{&BigInteger{value: big.NewInt(3)}, &ByteArray{value: []byte{1, 2, 3}}}}, + input: []Item{(*BigInteger)(big.NewInt(3)), &ByteArray{value: []byte{1, 2, 3}}}, + result: &Array{value: []Item{(*BigInteger)(big.NewInt(3)), &ByteArray{value: []byte{1, 2, 3}}}}, }, { input: []int{1, 2, 3}, - result: &Array{value: []Item{&BigInteger{value: big.NewInt(1)}, &BigInteger{value: big.NewInt(2)}, &BigInteger{value: big.NewInt(3)}}}, + result: &Array{value: []Item{(*BigInteger)(big.NewInt(1)), (*BigInteger)(big.NewInt(2)), (*BigInteger)(big.NewInt(3))}}, }, } @@ -281,18 +281,18 @@ var equalsTestCases = map[string][]struct { result: false, }, { - item1: NewArray([]Item{&BigInteger{big.NewInt(1)}, &BigInteger{big.NewInt(2)}, &BigInteger{big.NewInt(3)}}), - item2: NewArray([]Item{&BigInteger{big.NewInt(1)}, &BigInteger{big.NewInt(2)}, &BigInteger{big.NewInt(3)}}), + item1: NewArray([]Item{(*BigInteger)(big.NewInt(1)), (*BigInteger)(big.NewInt(2)), (*BigInteger)(big.NewInt(3))}), + item2: NewArray([]Item{(*BigInteger)(big.NewInt(1)), (*BigInteger)(big.NewInt(2)), (*BigInteger)(big.NewInt(3))}), result: false, }, { - item1: NewArray([]Item{&BigInteger{big.NewInt(1)}}), + item1: NewArray([]Item{(*BigInteger)(big.NewInt(1))}), item2: NewBigInteger(big.NewInt(1)), result: false, }, { - item1: NewArray([]Item{&BigInteger{big.NewInt(1)}, &BigInteger{big.NewInt(2)}, &BigInteger{big.NewInt(3)}}), - item2: NewArray([]Item{&BigInteger{big.NewInt(1)}, &BigInteger{big.NewInt(2)}, &BigInteger{big.NewInt(4)}}), + item1: NewArray([]Item{(*BigInteger)(big.NewInt(1)), (*BigInteger)(big.NewInt(2)), (*BigInteger)(big.NewInt(3))}), + item2: NewArray([]Item{(*BigInteger)(big.NewInt(1)), (*BigInteger)(big.NewInt(2)), (*BigInteger)(big.NewInt(4))}), result: false, }, }, @@ -441,7 +441,7 @@ var marshalJSONTestCases = []struct { result: []byte(`"010203"`), }, { - input: &Array{value: []Item{&BigInteger{value: big.NewInt(3)}, &ByteArray{value: []byte{1, 2, 3}}}}, + input: &Array{value: []Item{(*BigInteger)(big.NewInt(3)), &ByteArray{value: []byte{1, 2, 3}}}}, result: []byte(`[3,"010203"]`), }, { diff --git a/pkg/vm/stackitem/json.go b/pkg/vm/stackitem/json.go index 5b346ada1..bb01076b0 100644 --- a/pkg/vm/stackitem/json.go +++ b/pkg/vm/stackitem/json.go @@ -110,10 +110,10 @@ func toJSON(data []byte, seen map[Item]sliceNoPointer, item Item) ([]byte, error data = append(data, '}') seen[item] = sliceNoPointer{start, len(data)} case *BigInteger: - if it.value.CmpAbs(big.NewInt(MaxAllowedInteger)) == 1 { + if it.Big().CmpAbs(big.NewInt(MaxAllowedInteger)) == 1 { return nil, fmt.Errorf("%w (MaxAllowedInteger)", ErrInvalidValue) } - data = append(data, it.value.String()...) + data = append(data, it.Big().String()...) case *ByteArray, *Buffer: raw, err := itemToJSONString(it) if err != nil { @@ -293,7 +293,7 @@ func toJSONWithTypes(item Item, seen map[Item]bool) (interface{}, error) { case *Buffer, *ByteArray: value = base64.StdEncoding.EncodeToString(it.Value().([]byte)) case *BigInteger: - value = it.value.String() + value = it.Big().String() case *Map: if seen[item] { return "", ErrRecursive From f5d1277bfd0983970296a4fa8dbf035ba902b008 Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Tue, 10 Aug 2021 15:31:16 +0300 Subject: [PATCH 3/7] vm: do not copy parameter Signed-off-by: Evgeniy Stratonikov --- pkg/vm/context.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pkg/vm/context.go b/pkg/vm/context.go index af20090cc..e682cda1b 100644 --- a/pkg/vm/context.go +++ b/pkg/vm/context.go @@ -97,9 +97,9 @@ func (c *Context) NextIP() int { return c.nextip } -// Next returns the next instruction to execute with its parameter if any. After -// its invocation the instruction pointer points to the instruction being -// returned. +// Next returns the next instruction to execute with its parameter if any. +// The parameter is not copied and shouldn't be written to. After its invocation +// the instruction pointer points to the instruction being returned. func (c *Context) Next() (opcode.Opcode, []byte, error) { var err error @@ -171,8 +171,7 @@ func (c *Context) Next() (opcode.Opcode, []byte, error) { if err != nil { return instr, nil, err } - parameter := make([]byte, numtoread) - copy(parameter, c.prog[c.nextip:c.nextip+numtoread]) + parameter := c.prog[c.nextip : c.nextip+numtoread] c.nextip += numtoread return instr, parameter, nil } From dc9287bf5c32f71c41ecbdbe90637a6fb2829845 Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Fri, 13 Aug 2021 11:58:21 +0300 Subject: [PATCH 4/7] compiler: use parameter directly in `writeJumps` `Next` doesn't longer copy parameter. Signed-off-by: Evgeniy Stratonikov --- pkg/compiler/codegen.go | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index 25871e69c..8a2bd1673 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -2097,20 +2097,17 @@ func (c *codegen) resolveFuncDecls(f *ast.File, pkg *types.Package) { func (c *codegen) writeJumps(b []byte) ([]byte, error) { ctx := vm.NewContext(b) var offsets []int - for op, _, err := ctx.Next(); err == nil && ctx.IP() < len(b); op, _, err = ctx.Next() { + for op, param, err := ctx.Next(); err == nil && ctx.IP() < len(b); op, param, err = ctx.Next() { switch op { case opcode.JMP, opcode.JMPIFNOT, opcode.JMPIF, opcode.CALL, opcode.JMPEQ, opcode.JMPNE, opcode.JMPGT, opcode.JMPGE, opcode.JMPLE, opcode.JMPLT: case opcode.TRYL: - nextIP := ctx.NextIP() - catchArg := b[nextIP-8:] - _, err := c.replaceLabelWithOffset(ctx.IP(), catchArg) + _, err := c.replaceLabelWithOffset(ctx.IP(), param) if err != nil { return nil, err } - finallyArg := b[nextIP-4:] - _, err = c.replaceLabelWithOffset(ctx.IP(), finallyArg) + _, err = c.replaceLabelWithOffset(ctx.IP(), param[4:]) if err != nil { return nil, err } @@ -2118,10 +2115,7 @@ func (c *codegen) writeJumps(b []byte) ([]byte, error) { opcode.JMPEQL, opcode.JMPNEL, opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLEL, opcode.JMPLTL, opcode.CALLL, opcode.PUSHA, opcode.ENDTRYL: - // we can't use arg returned by ctx.Next() because it is copied - nextIP := ctx.NextIP() - arg := b[nextIP-4:] - offset, err := c.replaceLabelWithOffset(ctx.IP(), arg) + offset, err := c.replaceLabelWithOffset(ctx.IP(), param) if err != nil { return nil, err } From 4f98ec2f534ece0c72e3ef31775e15eb69da4e65 Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Wed, 11 Aug 2021 15:16:29 +0300 Subject: [PATCH 5/7] vm: embed reference counter in compound items MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ``` name old time/op new time/op delta RefCounter_Add-8 44.8ns ± 4% 11.7ns ± 3% -73.94% (p=0.000 n=10+10) ``` Signed-off-by: Evgeniy Stratonikov --- pkg/vm/json_test.go | 22 ++++++++++ pkg/vm/ref_counter.go | 75 ++++++++++++++++------------------- pkg/vm/ref_counter_test.go | 24 +++++++---- pkg/vm/stackitem/item.go | 5 ++- pkg/vm/stackitem/reference.go | 15 +++++++ pkg/vm/vm.go | 3 +- pkg/vm/vm_test.go | 10 ++--- 7 files changed, 99 insertions(+), 55 deletions(-) create mode 100644 pkg/vm/stackitem/reference.go diff --git a/pkg/vm/json_test.go b/pkg/vm/json_test.go index 54fe8ff00..5d7f6acf5 100644 --- a/pkg/vm/json_test.go +++ b/pkg/vm/json_test.go @@ -208,6 +208,28 @@ func compareItems(t *testing.T, a, b stackitem.Item) { p, ok := b.(*stackitem.Pointer) require.True(t, ok) require.Equal(t, si.Position(), p.Position()) // there no script in test files + case *stackitem.Array, *stackitem.Struct: + require.Equal(t, a.Type(), b.Type()) + + as := a.Value().([]stackitem.Item) + bs := a.Value().([]stackitem.Item) + require.Equal(t, len(as), len(bs)) + + for i := range as { + compareItems(t, as[i], bs[i]) + } + + case *stackitem.Map: + require.Equal(t, a.Type(), b.Type()) + + as := a.Value().([]stackitem.MapElement) + bs := a.Value().([]stackitem.MapElement) + require.Equal(t, len(as), len(bs)) + + for i := range as { + compareItems(t, as[i].Key, bs[i].Key) + compareItems(t, as[i].Value, bs[i].Value) + } default: require.Equal(t, a, b) } diff --git a/pkg/vm/ref_counter.go b/pkg/vm/ref_counter.go index 182177f6c..e3d787a0a 100644 --- a/pkg/vm/ref_counter.go +++ b/pkg/vm/ref_counter.go @@ -5,15 +5,19 @@ import ( ) // refCounter represents reference counter for the VM. -type refCounter struct { - items map[stackitem.Item]int - size int -} +type refCounter int + +type ( + rcInc interface { + IncRC() int + } + rcDec interface { + DecRC() int + } +) func newRefCounter() *refCounter { - return &refCounter{ - items: make(map[stackitem.Item]int), - } + return new(refCounter) } // Add adds an item to the reference counter. @@ -21,23 +25,20 @@ func (r *refCounter) Add(item stackitem.Item) { if r == nil { return } - r.size++ + *r++ - switch item.(type) { - case *stackitem.Array, *stackitem.Struct, *stackitem.Map: - if r.items[item]++; r.items[item] > 1 { - return + irc, ok := item.(rcInc) + if !ok || irc.IncRC() > 1 { + return + } + switch t := item.(type) { + case *stackitem.Array, *stackitem.Struct: + for _, it := range item.Value().([]stackitem.Item) { + r.Add(it) } - - switch t := item.(type) { - case *stackitem.Array, *stackitem.Struct: - for _, it := range item.Value().([]stackitem.Item) { - r.Add(it) - } - case *stackitem.Map: - for i := range t.Value().([]stackitem.MapElement) { - r.Add(t.Value().([]stackitem.MapElement)[i].Value) - } + case *stackitem.Map: + for i := range t.Value().([]stackitem.MapElement) { + r.Add(t.Value().([]stackitem.MapElement)[i].Value) } } } @@ -47,26 +48,20 @@ func (r *refCounter) Remove(item stackitem.Item) { if r == nil { return } - r.size-- + *r-- - switch item.(type) { - case *stackitem.Array, *stackitem.Struct, *stackitem.Map: - if r.items[item] > 1 { - r.items[item]-- - return + irc, ok := item.(rcDec) + if !ok || irc.DecRC() > 0 { + return + } + switch t := item.(type) { + case *stackitem.Array, *stackitem.Struct: + for _, it := range item.Value().([]stackitem.Item) { + r.Remove(it) } - - delete(r.items, item) - - switch t := item.(type) { - case *stackitem.Array, *stackitem.Struct: - for _, it := range item.Value().([]stackitem.Item) { - r.Remove(it) - } - case *stackitem.Map: - for i := range t.Value().([]stackitem.MapElement) { - r.Remove(t.Value().([]stackitem.MapElement)[i].Value) - } + case *stackitem.Map: + for i := range t.Value().([]stackitem.MapElement) { + r.Remove(t.Value().([]stackitem.MapElement)[i].Value) } } } diff --git a/pkg/vm/ref_counter_test.go b/pkg/vm/ref_counter_test.go index b50390609..9e0a82d99 100644 --- a/pkg/vm/ref_counter_test.go +++ b/pkg/vm/ref_counter_test.go @@ -10,24 +10,34 @@ import ( func TestRefCounter_Add(t *testing.T) { r := newRefCounter() - require.Equal(t, 0, r.size) + require.Equal(t, 0, int(*r)) r.Add(stackitem.Null{}) - require.Equal(t, 1, r.size) + require.Equal(t, 1, int(*r)) r.Add(stackitem.Null{}) - require.Equal(t, 2, r.size) // count scalar items twice + require.Equal(t, 2, int(*r)) // count scalar items twice arr := stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte{1}), stackitem.NewBool(false)}) r.Add(arr) - require.Equal(t, 5, r.size) // array + 2 elements + require.Equal(t, 5, int(*r)) // array + 2 elements r.Add(arr) - require.Equal(t, 6, r.size) // count only array + require.Equal(t, 6, int(*r)) // count only array r.Remove(arr) - require.Equal(t, 5, r.size) + require.Equal(t, 5, int(*r)) r.Remove(arr) - require.Equal(t, 2, r.size) + require.Equal(t, 2, int(*r)) +} + +func BenchmarkRefCounter_Add(b *testing.B) { + a := stackitem.NewArray(nil) + rc := newRefCounter() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + rc.Add(a) + } } diff --git a/pkg/vm/stackitem/item.go b/pkg/vm/stackitem/item.go index a57dfb30d..8d9b7d63c 100644 --- a/pkg/vm/stackitem/item.go +++ b/pkg/vm/stackitem/item.go @@ -188,6 +188,7 @@ func convertPrimitive(item Item, typ Type) (Item, error) { // Struct represents a struct on the stack. type Struct struct { value []Item + rc } // NewStruct returns an new Struct object. @@ -311,7 +312,7 @@ func (i *Struct) Clone() (*Struct, error) { } func (i *Struct) clone(limit *int) (*Struct, error) { - ret := &Struct{make([]Item, len(i.value))} + ret := &Struct{value: make([]Item, len(i.value))} for j := range i.value { *limit-- if *limit < 0 { @@ -624,6 +625,7 @@ func (i *ByteArray) Convert(typ Type) (Item, error) { // Array represents a new Array object. type Array struct { value []Item + rc } // NewArray returns a new Array object. @@ -724,6 +726,7 @@ type MapElement struct { // if need be. type Map struct { value []MapElement + rc } // NewMap returns new Map object. diff --git a/pkg/vm/stackitem/reference.go b/pkg/vm/stackitem/reference.go new file mode 100644 index 000000000..0102d6b0e --- /dev/null +++ b/pkg/vm/stackitem/reference.go @@ -0,0 +1,15 @@ +package stackitem + +type rc struct { + count int +} + +func (r *rc) IncRC() int { + r.count++ + return r.count +} + +func (r *rc) DecRC() int { + r.count-- + return r.count +} diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 4856270e4..2f973ed25 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -105,7 +105,6 @@ func NewWithTrigger(t trigger.Type) *VM { Invocations: make(map[util.Uint160]int), } - vm.refs.items = make(map[stackitem.Item]int) initStack(&vm.istack, "invocation", nil) vm.estack = newStack("evaluation", &vm.refs) return vm @@ -520,7 +519,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro if errRecover := recover(); errRecover != nil { v.state = FaultState err = newError(ctx.ip, op, errRecover) - } else if v.refs.size > MaxStackSize { + } else if v.refs > MaxStackSize { v.state = FaultState err = newError(ctx.ip, op, "stack is too big") } diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index 8fb07646f..3391ddeca 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -415,7 +415,7 @@ func TestStackLimit(t *testing.T) { require.NoError(t, vm.Step(), "failed to initialize static slot") for i := range expected { require.NoError(t, vm.Step()) - require.Equal(t, expected[i].size, vm.refs.size, "i: %d", i) + require.Equal(t, expected[i].size, int(vm.refs), "i: %d", i) } } @@ -829,7 +829,7 @@ func getTestFuncForVM(prog []byte, result interface{}, args ...interface{}) func if result != nil { f = func(t *testing.T, v *VM) { require.Equal(t, 1, v.estack.Len()) - require.Equal(t, stackitem.Make(result), v.estack.Pop().value) + require.Equal(t, stackitem.Make(result).Value(), v.estack.Pop().Value()) } } return getCustomTestFuncForVM(prog, f, args...) @@ -1761,7 +1761,7 @@ func TestPACK_UNPACK_MaxSize(t *testing.T) { vm.estack.PushVal(len(elements)) runVM(t, vm) // check reference counter = 1+1+1024 - assert.Equal(t, 1+1+len(elements), vm.refs.size) + assert.Equal(t, 1+1+len(elements), int(vm.refs)) assert.Equal(t, 1+1+len(elements), vm.estack.Len()) // canary + length + elements assert.Equal(t, int64(len(elements)), vm.estack.Peek(0).Value().(*big.Int).Int64()) for i := 0; i < len(elements); i++ { @@ -1784,7 +1784,7 @@ func TestPACK_UNPACK_PACK_MaxSize(t *testing.T) { vm.estack.PushVal(len(elements)) runVM(t, vm) // check reference counter = 1+1+1024 - assert.Equal(t, 1+1+len(elements), vm.refs.size) + assert.Equal(t, 1+1+len(elements), int(vm.refs)) assert.Equal(t, 2, vm.estack.Len()) a := vm.estack.Peek(0).Array() assert.Equal(t, len(elements), len(a)) @@ -1959,7 +1959,7 @@ func testCLEARITEMS(t *testing.T, item stackitem.Item) { v.estack.PushVal(item) runVM(t, v) require.Equal(t, 2, v.estack.Len()) - require.EqualValues(t, 2, v.refs.size) // empty collection + it's size + require.EqualValues(t, 2, int(v.refs)) // empty collection + it's size require.EqualValues(t, 0, v.estack.Pop().BigInt().Int64()) } From 1dfef4ba26cf9c61f7f81ddd1c373549b738116f Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Fri, 13 Aug 2021 12:13:10 +0300 Subject: [PATCH 6/7] stackitem: make `ByteArray` an alias to `[]byte` Signed-off-by: Evgeniy Stratonikov --- pkg/vm/stackitem/item.go | 45 +++++++++++++++-------------------- pkg/vm/stackitem/item_test.go | 14 +++++------ 2 files changed, 26 insertions(+), 33 deletions(-) diff --git a/pkg/vm/stackitem/item.go b/pkg/vm/stackitem/item.go index 8d9b7d63c..61d6186aa 100644 --- a/pkg/vm/stackitem/item.go +++ b/pkg/vm/stackitem/item.go @@ -101,13 +101,9 @@ func Make(v interface{}) Item { case uint64: return (*BigInteger)(new(big.Int).SetUint64(val)) case []byte: - return &ByteArray{ - value: val, - } + return NewByteArray(val) case string: - return &ByteArray{ - value: []byte(val), - } + return NewByteArray([]byte(val)) case bool: return Bool(val) case []Item: @@ -538,25 +534,21 @@ func (i Bool) Convert(typ Type) (Item, error) { } // ByteArray represents a byte array on the stack. -type ByteArray struct { - value []byte -} +type ByteArray []byte // NewByteArray returns an new ByteArray object. func NewByteArray(b []byte) *ByteArray { - return &ByteArray{ - value: b, - } + return (*ByteArray)(&b) } // Value implements Item interface. func (i *ByteArray) Value() interface{} { - return i.value + return []byte(*i) } // MarshalJSON implements the json.Marshaler interface. func (i *ByteArray) MarshalJSON() ([]byte, error) { - return json.Marshal(hex.EncodeToString(i.value)) + return json.Marshal(hex.EncodeToString(*i)) } func (i *ByteArray) String() string { @@ -565,10 +557,10 @@ func (i *ByteArray) String() string { // TryBool implements Item interface. func (i *ByteArray) TryBool() (bool, error) { - if len(i.value) > MaxBigIntegerSizeBits/8 { + if len(*i) > MaxBigIntegerSizeBits/8 { return false, errTooBigInteger } - for _, b := range i.value { + for _, b := range *i { if b != 0 { return true, nil } @@ -577,21 +569,21 @@ func (i *ByteArray) TryBool() (bool, error) { } // TryBytes implements Item interface. -func (i *ByteArray) TryBytes() ([]byte, error) { - return i.value, nil +func (i ByteArray) TryBytes() ([]byte, error) { + return i, nil } // TryInteger implements Item interface. -func (i *ByteArray) TryInteger() (*big.Int, error) { - if len(i.value) > MaxBigIntegerSizeBits/8 { +func (i ByteArray) TryInteger() (*big.Int, error) { + if len(i) > MaxBigIntegerSizeBits/8 { return nil, errTooBigInteger } - return bigint.FromBytes(i.value), nil + return bigint.FromBytes(i), nil } // Equals implements Item interface. func (i *ByteArray) Equals(s Item) bool { - if len(i.value) > MaxByteArrayComparableSize { + if len(*i) > MaxByteArrayComparableSize { panic(errTooBigComparable) } if i == s { @@ -603,15 +595,16 @@ func (i *ByteArray) Equals(s Item) bool { if !ok { return false } - if len(val.value) > MaxByteArrayComparableSize { + if len(*val) > MaxByteArrayComparableSize { panic(errTooBigComparable) } - return bytes.Equal(i.value, val.value) + return bytes.Equal(*i, *val) } // Dup implements Item interface. func (i *ByteArray) Dup() Item { - return &ByteArray{slice.Copy(i.value)} + ba := slice.Copy(*i) + return (*ByteArray)(&ba) } // Type implements Item interface. @@ -1146,7 +1139,7 @@ func deepCopy(item Item, seen map[Item]Item) Item { bi := new(big.Int).Set(it.Big()) return (*BigInteger)(bi) case *ByteArray: - return NewByteArray(slice.Copy(it.value)) + return NewByteArray(slice.Copy(*it)) case *Buffer: return NewBuffer(slice.Copy(it.value)) case Bool: diff --git a/pkg/vm/stackitem/item_test.go b/pkg/vm/stackitem/item_test.go index b6b877aec..72920c8e2 100644 --- a/pkg/vm/stackitem/item_test.go +++ b/pkg/vm/stackitem/item_test.go @@ -47,19 +47,19 @@ var makeStackItemTestCases = []struct { }, { input: []byte{1, 2, 3, 4}, - result: &ByteArray{value: []byte{1, 2, 3, 4}}, + result: NewByteArray([]byte{1, 2, 3, 4}), }, { input: []byte{}, - result: &ByteArray{value: []byte{}}, + result: NewByteArray([]byte{}), }, { input: "bla", - result: &ByteArray{value: []byte("bla")}, + result: NewByteArray([]byte("bla")), }, { input: "", - result: &ByteArray{value: []byte{}}, + result: NewByteArray([]byte{}), }, { input: true, @@ -70,8 +70,8 @@ var makeStackItemTestCases = []struct { result: Bool(false), }, { - input: []Item{(*BigInteger)(big.NewInt(3)), &ByteArray{value: []byte{1, 2, 3}}}, - result: &Array{value: []Item{(*BigInteger)(big.NewInt(3)), &ByteArray{value: []byte{1, 2, 3}}}}, + input: []Item{(*BigInteger)(big.NewInt(3)), NewByteArray([]byte{1, 2, 3})}, + result: &Array{value: []Item{(*BigInteger)(big.NewInt(3)), NewByteArray([]byte{1, 2, 3})}}, }, { input: []int{1, 2, 3}, @@ -441,7 +441,7 @@ var marshalJSONTestCases = []struct { result: []byte(`"010203"`), }, { - input: &Array{value: []Item{(*BigInteger)(big.NewInt(3)), &ByteArray{value: []byte{1, 2, 3}}}}, + input: &Array{value: []Item{(*BigInteger)(big.NewInt(3)), NewByteArray([]byte{1, 2, 3})}}, result: []byte(`[3,"010203"]`), }, { From 6879f76a13826aee3de6d062187c5a4d97003859 Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Fri, 13 Aug 2021 12:15:57 +0300 Subject: [PATCH 7/7] stackitem: make `Buffer` an alias to `[]byte` Signed-off-by: Evgeniy Stratonikov --- pkg/vm/stackitem/item.go | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/pkg/vm/stackitem/item.go b/pkg/vm/stackitem/item.go index 61d6186aa..14ed782e8 100644 --- a/pkg/vm/stackitem/item.go +++ b/pkg/vm/stackitem/item.go @@ -1019,20 +1019,16 @@ func (p *Pointer) Position() int { } // Buffer represents represents Buffer stack item. -type Buffer struct { - value []byte -} +type Buffer []byte // NewBuffer returns a new Buffer object. func NewBuffer(b []byte) *Buffer { - return &Buffer{ - value: b, - } + return (*Buffer)(&b) } // Value implements Item interface. func (i *Buffer) Value() interface{} { - return i.value + return []byte(*i) } // String implements fmt.Stringer interface. @@ -1047,7 +1043,7 @@ func (i *Buffer) TryBool() (bool, error) { // TryBytes implements Item interface. func (i *Buffer) TryBytes() ([]byte, error) { - return i.value, nil + return *i, nil } // TryInteger implements Item interface. @@ -1067,7 +1063,7 @@ func (i *Buffer) Dup() Item { // MarshalJSON implements the json.Marshaler interface. func (i *Buffer) MarshalJSON() ([]byte, error) { - return json.Marshal(hex.EncodeToString(i.value)) + return json.Marshal(hex.EncodeToString(*i)) } // Type implements Item interface. @@ -1081,12 +1077,12 @@ func (i *Buffer) Convert(typ Type) (Item, error) { case BufferT: return i, nil case ByteArrayT: - return NewByteArray(slice.Copy(i.value)), nil + return NewByteArray(slice.Copy(*i)), nil case IntegerT: - if len(i.value) > MaxBigIntegerSizeBits/8 { + if len(*i) > MaxBigIntegerSizeBits/8 { return nil, errTooBigInteger } - return NewBigInteger(bigint.FromBytes(i.value)), nil + return NewBigInteger(bigint.FromBytes(*i)), nil default: return nil, mkInvConversion(i, typ) } @@ -1094,7 +1090,7 @@ func (i *Buffer) Convert(typ Type) (Item, error) { // Len returns length of Buffer value. func (i *Buffer) Len() int { - return len(i.value) + return len(*i) } // DeepCopy returns new deep copy of the provided item. @@ -1141,7 +1137,7 @@ func deepCopy(item Item, seen map[Item]Item) Item { case *ByteArray: return NewByteArray(slice.Copy(*it)) case *Buffer: - return NewBuffer(slice.Copy(it.value)) + return NewBuffer(slice.Copy(*it)) case Bool: return it case *Pointer: