forked from hyperledger-archives/burrow
-
Notifications
You must be signed in to change notification settings - Fork 0
/
memory.go
115 lines (104 loc) · 3.89 KB
/
memory.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
package vm
import (
"fmt"
"math"
)
const (
defaultInitialMemoryCapacity = 0x100000 // 1 MiB
defaultMaximumMemoryCapacity = 0x1000000 // 16 MiB
)
// Change the length of this zero array to tweak the size of the block of zeros
// written to the backing slice at a time when it is grown. A larger number may
// lead to less calls to append to achieve the desired capacity although it is
// unlikely to make a lot of difference.
var zeroBlock []byte = make([]byte, 32)
// Interface for a bounded linear memory indexed by a single int64 parameter
// for each byte in the memory.
type Memory interface {
// Read a value from the memory store starting at offset
// (index of first byte will equal offset). The value will be returned as a
// length-bytes byte slice. Returns an error if the memory cannot be read or
// is not allocated.
//
// The value returned should be copy of any underlying memory, not a reference
// to the underlying store.
Read(offset, length int64) ([]byte, error)
// Write a value to the memory starting at offset (the index of the first byte
// written will equal offset). The value is provided as bytes to be written
// consecutively to the memory store. Return an error if the memory cannot be
// written or allocated.
Write(offset int64, value []byte) error
// Returns the current capacity of the memory. For dynamically allocating
// memory this capacity can be used as a write offset that is guaranteed to be
// unused. Solidity in particular makes this assumption when using MSIZE to
// get the current allocated memory.
Capacity() int64
}
func NewDynamicMemory(initialCapacity, maximumCapacity int64) Memory {
return &dynamicMemory{
slice: make([]byte, initialCapacity),
maximumCapacity: maximumCapacity,
}
}
func DefaultDynamicMemoryProvider() Memory {
return NewDynamicMemory(defaultInitialMemoryCapacity, defaultMaximumMemoryCapacity)
}
// Implements a bounded dynamic memory that relies on Go's (pretty good) dynamic
// array allocation via a backing slice
type dynamicMemory struct {
slice []byte
maximumCapacity int64
}
func (mem *dynamicMemory) Read(offset, length int64) ([]byte, error) {
capacity := offset + length
err := mem.ensureCapacity(capacity)
if err != nil {
return nil, err
}
value := make([]byte, length)
copy(value, mem.slice[offset:capacity])
return value, nil
}
func (mem *dynamicMemory) Write(offset int64, value []byte) error {
capacity := offset + int64(len(value))
err := mem.ensureCapacity(capacity)
if err != nil {
return err
}
copy(mem.slice[offset:capacity], value)
return nil
}
func (mem *dynamicMemory) Capacity() int64 {
return int64(len(mem.slice))
}
// Ensures the current memory store can hold newCapacity. Will only grow the
// memory (will not shrink).
func (mem *dynamicMemory) ensureCapacity(newCapacity int64) error {
if newCapacity > math.MaxInt32 {
// If we ever did want to then we would need to maintain multiple pages
// of memory
return fmt.Errorf("Cannot address memory beyond a maximum index "+
"of Int32 type (%v bytes)", math.MaxInt32)
}
newCapacityInt := int(newCapacity)
// We're already big enough so return
if newCapacityInt <= len(mem.slice) {
return nil
}
if newCapacity > mem.maximumCapacity {
return fmt.Errorf("Cannot grow memory because it would exceed the "+
"current maximum limit of %v bytes", mem.maximumCapacity)
}
// Ensure the backing array of slice is big enough
// Grow the memory one word at time using the pre-allocated zeroBlock to avoid
// unnecessary allocations. Use append to make use of any spare capacity in
// the slice's backing array.
for newCapacityInt > cap(mem.slice) {
// We'll trust Go exponentially grow our arrays (at first).
mem.slice = append(mem.slice, zeroBlock...)
}
// Now we've ensured the backing array of the slice is big enough we can
// just re-slice (even if len(mem.slice) < newCapacity)
mem.slice = mem.slice[:newCapacity]
return nil
}