Kagome
Polkadot Runtime Engine in C++17
memory_allocator.cpp
Go to the documentation of this file.
1 
7 
8 #include "runtime/memory.hpp"
9 
10 namespace kagome::runtime {
11 
13  "Heap base must be aligned");
14 
15  static_assert(kDefaultHeapBase < kInitialMemorySize,
16  "Heap base must be in memory");
17 
19  size_t size,
20  WasmPointer heap_base)
21  : memory_{std::move(memory)},
22  offset_{heap_base},
23  size_{size},
24  logger_{log::createLogger("Allocator", "runtime")} {
25  // Heap base (and offset in according) must be non-zero to prohibit
26  // allocating memory at 0 in the future, as returning 0 from allocate method
27  // means that wasm memory was exhausted
28  BOOST_ASSERT(offset_ > 0);
29  BOOST_ASSERT(memory_.getSize);
30  BOOST_ASSERT(memory_.resize);
31 
32  size_ = std::max(size_, offset_);
33  BOOST_ASSERT(offset_ <= Memory::kMaxMemorySize - size_);
34  }
35 
37  if (size == 0) {
38  return 0;
39  }
40  const auto ptr = offset_;
41  const auto new_offset = roundUpAlign(ptr + size); // align
42 
43  // Round up allocating chunk of memory
44  size = new_offset - ptr;
45 
46  BOOST_ASSERT(allocated_.find(ptr) == allocated_.end());
47  if (Memory::kMaxMemorySize - offset_ < size) { // overflow
48  logger_->error(
49  "overflow occurred while trying to allocate {} bytes at offset "
50  "0x{:x}",
51  size,
52  offset_);
53  return 0;
54  }
55  if (new_offset <= size_) {
56  offset_ = new_offset;
57  allocated_[ptr] = size;
58  SL_TRACE_FUNC_CALL(logger_, ptr, this, size);
59  return ptr;
60  }
61 
62  auto res = freealloc(size);
63  SL_TRACE_FUNC_CALL(logger_, res, this, size);
64  return res;
65  }
66 
67  std::optional<WasmSize> MemoryAllocator::deallocate(WasmPointer ptr) {
68  auto a_it = allocated_.find(ptr);
69  if (a_it == allocated_.end()) {
70  return std::nullopt;
71  }
72 
73  auto a_node = allocated_.extract(a_it);
74  auto size = a_node.mapped();
75  auto [d_it, is_emplaced] = deallocated_.emplace(ptr, size);
76  BOOST_ASSERT(is_emplaced);
77 
78  // Combine with next chunk if it adjacent
79  while (true) {
80  auto node = deallocated_.extract(ptr + size);
81  if (not node) break;
82  d_it->second += node.mapped();
83  }
84 
85  // Combine with previous chunk if it adjacent
86  while (deallocated_.begin() != d_it) {
87  auto d_it_prev = std::prev(d_it);
88  if (d_it_prev->first + d_it_prev->second != d_it->first) {
89  break;
90  }
91  d_it_prev->second += d_it->second;
92  deallocated_.erase(d_it);
93  d_it = d_it_prev;
94  }
95 
96  auto d_it_next = std::next(d_it);
97  if (d_it_next == deallocated_.end()) {
98  if (d_it->first + d_it->second == offset_) {
99  offset_ = d_it->first;
100  deallocated_.erase(d_it);
101  }
102  }
103 
104  SL_TRACE_FUNC_CALL(logger_, size, this, ptr);
105  return size;
106  }
107 
109  if (size == 0) {
110  return 0;
111  }
112 
113  // Round up size of allocating memory chunk
114  size = roundUpAlign(size);
115 
116  auto min_chunk_size = std::numeric_limits<WasmPointer>::max();
117  WasmPointer ptr = 0;
118  for (const auto &[chunk_ptr, chunk_size] : deallocated_) {
119  BOOST_ASSERT(chunk_size > 0);
120  if (chunk_size >= size and chunk_size < min_chunk_size) {
121  min_chunk_size = chunk_size;
122  ptr = chunk_ptr;
123  if (min_chunk_size == size) {
124  break;
125  }
126  }
127  }
128  if (ptr == 0) {
129  // if did not find available space among deallocated memory chunks,
130  // then grow memory and allocate in new space
131  return growAlloc(size);
132  }
133 
134  const auto node = deallocated_.extract(ptr);
135  BOOST_ASSERT_MSG(!node.empty(),
136  "pointer to the node was received by searching list of "
137  "deallocated nodes, must not be none");
138 
139  auto old_size = node.mapped();
140  if (old_size > size) {
141  auto new_ptr = ptr + size;
142  auto new_size = old_size - size;
143  BOOST_ASSERT(new_size > 0);
144 
145  deallocated_[new_ptr] = new_size;
146  }
147 
148  allocated_[ptr] = size;
149 
150  return ptr;
151  }
152 
154  // check that we do not exceed max memory size
155  if (Memory::kMaxMemorySize - offset_ < size) {
156  logger_->error(
157  "Memory size exceeded when growing it on {} bytes, offset was 0x{:x}",
158  size,
159  offset_);
160  return 0;
161  }
162  // try to increase memory size up to offset + size * 4 (we multiply by 4
163  // to have more memory than currently needed to avoid resizing every time
164  // when we exceed current memory)
165  if ((Memory::kMaxMemorySize - offset_) / 4 > size) {
166  resize(offset_ + size * 4);
167  } else {
168  // if we can't increase by size * 4 then increase memory size by
169  // provided size
170  resize(offset_ + size);
171  }
172  return allocate(size);
173  }
174 
179  BOOST_ASSERT(offset_ <= Memory::kMaxMemorySize - new_size);
180  if (new_size >= size_) {
181  size_ = new_size;
182  memory_.resize(new_size);
183  }
184  }
185 
187  WasmPointer ptr) const {
188  auto it = deallocated_.find(ptr);
189  return it != deallocated_.cend() ? std::make_optional(it->second)
190  : std::nullopt;
191  }
192 
193  std::optional<WasmSize> MemoryAllocator::getAllocatedChunkSize(
194  WasmPointer ptr) const {
195  auto it = allocated_.find(ptr);
196  return it != allocated_.cend() ? std::make_optional(it->second)
197  : std::nullopt;
198  }
199 
201  return allocated_.size();
202  }
203 
205  return deallocated_.size();
206  }
207 
208 } // namespace kagome::runtime
uint32_t WasmSize
Size type is uint32_t because we are working in 32 bit address space.
Definition: types.hpp:35
#define SL_TRACE_FUNC_CALL(logger, ret,...)
Definition: logger.hpp:142
std::unordered_map< WasmPointer, WasmSize > allocated_
constexpr size_t kDefaultHeapBase
static constexpr T roundUpAlign(T t)
constexpr size_t kInitialMemorySize
Definition: memory.hpp:20
std::map< WasmPointer, WasmSize > deallocated_
std::optional< WasmSize > deallocate(WasmPointer ptr)
static constexpr uint32_t kMaxMemorySize
Definition: memory.hpp:43
uint32_t WasmPointer
type of wasm memory is 32 bit integer
Definition: types.hpp:26
WasmPointer growAlloc(WasmSize size)
WasmPointer freealloc(WasmSize size)
Logger createLogger(const std::string &tag)
Definition: logger.cpp:112
std::optional< WasmSize > getAllocatedChunkSize(WasmPointer ptr) const
MemoryAllocator(MemoryHandle memory, size_t size, WasmPointer heap_base)
WasmPointer allocate(WasmSize size)
std::optional< WasmSize > getDeallocatedChunkSize(WasmPointer ptr) const
following methods are needed mostly for testing purposes