A reimplementation of Mario Kart Wii's physics engine in C++
Loading...
Searching...
No Matches
ExpHeap.cc
1#include "ExpHeap.hh"
2
3#include <limits>
4#include <new> // placement new
5
6namespace Kinoko::Abstract::Memory {
7
8Region::Region(void *start, void *end) : start(start), end(end) {}
9
10uintptr_t Region::getRange() const {
11 return GetAddrNum(end) - GetAddrNum(start);
12}
13
14MEMiExpBlockHead *MEMiExpBlockList::insert(MEMiExpBlockHead *block, MEMiExpBlockHead *prev) {
15 MEMiExpBlockHead *next;
16
17 block->m_link.m_prev = prev;
18 if (prev) {
19 next = prev->m_link.m_next;
20 prev->m_link.m_next = block;
21 } else {
22 next = m_head;
23 m_head = block;
24 }
25
26 block->m_link.m_next = next;
27 if (next) {
28 next->m_link.m_prev = block;
29 } else {
30 m_tail = block;
31 }
32
33 return block;
34}
35
36MEMiExpBlockHead *MEMiExpBlockList::append(MEMiExpBlockHead *block) {
37 return insert(block, m_tail);
38}
39
40MEMiExpBlockHead *MEMiExpBlockList::remove(MEMiExpBlockHead *block) {
41 MEMiExpBlockHead *prev = block->m_link.m_prev;
42 MEMiExpBlockHead *next = block->m_link.m_next;
43
44 if (prev) {
45 prev->m_link.m_next = next;
46 } else {
47 m_head = next;
48 }
49
50 if (next) {
51 next->m_link.m_prev = prev;
52 } else {
53 m_tail = prev;
54 }
55
56 return prev;
57}
58
59MEMiExpBlockHead::MEMiExpBlockHead(const Region &region, u16 signature) {
60 m_signature = signature;
61 m_attribute.val = 0;
62
63 m_size = region.getRange() - sizeof(MEMiExpBlockHead);
64
65 m_link.m_prev = nullptr;
66 m_link.m_next = nullptr;
67}
68
69MEMiExpBlockHead *MEMiExpBlockHead::createFree(const Region &region) {
70 constexpr u16 FREE_BLOCK_SIGNATURE = 0x4652; // FR
71 return new (region.start) MEMiExpBlockHead(region, FREE_BLOCK_SIGNATURE);
72}
73
74MEMiExpBlockHead *MEMiExpBlockHead::createUsed(const Region &region) {
75 constexpr u16 USED_BLOCK_SIGNATURE = 0x5544; // UD
76 return new (region.start) MEMiExpBlockHead(region, USED_BLOCK_SIGNATURE);
77}
78
79Region MEMiExpBlockHead::getRegion() const {
80 return Region(SubOffset(this, m_attribute.fields.alignment), getMemoryEnd());
81}
82
83void *MEMiExpBlockHead::getMemoryStart() const {
84 return AddOffset(this, sizeof(MEMiExpBlockHead));
85}
86
87void *MEMiExpBlockHead::getMemoryEnd() const {
88 return AddOffset(getMemoryStart(), m_size);
89}
90
91MEMiExpHeapHead::MEMiExpHeapHead(void *end, u16 opt)
92 : MEMiHeapHead(EXP_HEAP_SIGNATURE, AddOffset(this, sizeof(MEMiExpHeapHead)), end, opt) {
93 m_groupId = 0;
94#ifdef BUILD_DEBUG
95 m_tag = 0;
96#endif // BUILD_DEBUG
97 m_attribute.makeAllZero();
98
99 Region region = Region(getHeapStart(), getHeapEnd());
100 MEMiExpBlockHead *block = MEMiExpBlockHead::createFree(region);
101
102 m_freeBlocks.m_head = block;
103 m_freeBlocks.m_tail = block;
104 m_usedBlocks.m_head = nullptr;
105 m_usedBlocks.m_tail = nullptr;
106}
107
109MEMiExpHeapHead::~MEMiExpHeapHead() = default;
110
112MEMiExpHeapHead *MEMiExpHeapHead::create(void *startAddress, size_t size, u16 flag) {
113 void *endAddress = AddOffset(startAddress, size);
114
115 startAddress = RoundUp(startAddress, 4);
116 endAddress = RoundDown(endAddress, 4);
117
118 uintptr_t startAddrNum = GetAddrNum(startAddress);
119 uintptr_t endAddrNum = GetAddrNum(endAddress);
120
121 if (startAddrNum > endAddrNum) {
122 return nullptr;
123 }
124
125 if (endAddrNum - startAddrNum < sizeof(MEMiExpHeapHead) + sizeof(MEMiExpBlockHead) + 4) {
126 return nullptr;
127 }
128
129 return new (startAddress) MEMiExpHeapHead(endAddress, flag);
130}
131
132void MEMiExpHeapHead::destroy() {
133 this->~MEMiExpHeapHead();
134}
135
137void *MEMiExpHeapHead::alloc(size_t size, s32 align) {
138 if (size == 0) {
139 size = 1;
140 }
141 size = RoundUp(size, 4);
142
143 void *block = nullptr;
144 if (align >= 0) {
145 block = allocFromHead(size, align);
146 } else {
147 block = allocFromTail(size, -align);
148 }
149
150 return block;
151}
152
154void MEMiExpHeapHead::free(void *block) {
155 if (!block) {
156 return;
157 }
158
159 MEMiExpBlockHead *head =
160 reinterpret_cast<MEMiExpBlockHead *>(SubOffset(block, sizeof(MEMiExpBlockHead)));
161
162 Region region = head->getRegion();
163 m_usedBlocks.remove(head);
164 recycleRegion(region);
165}
166
168u32 MEMiExpHeapHead::getAllocatableSize(s32 align) const {
169 // Doesn't matter which direction it can be allocated from, take absolute value
170 align = std::abs(align);
171
172 u32 maxSize = 0;
173 u32 x = std::numeric_limits<u32>::max();
174
175 for (MEMiExpBlockHead *block = m_freeBlocks.m_head; block; block = block->m_link.m_next) {
176 void *memptr = block->getMemoryStart();
177 void *start = RoundUp(memptr, align);
178 void *end = block->getMemoryEnd();
179
180 if (GetAddrNum(start) < GetAddrNum(end)) {
181 u32 size = GetAddrNum(end) - GetAddrNum(start);
182 u32 offset = GetAddrNum(start) - GetAddrNum(memptr);
183
184 if (maxSize < size || (maxSize == size && x > offset)) {
185 maxSize = size;
186 x = offset;
187 }
188 }
189 }
190
191 return maxSize;
192}
193
195void MEMiExpHeapHead::visitAllocated(Visitor visitor, uintptr_t param) {
196 for (MEMiExpBlockHead *block = m_usedBlocks.m_head; block;) {
197 MEMiExpBlockHead *next = block->m_link.m_next;
198 visitor(block->getMemoryStart(), this, param);
199 block = next;
200 }
201}
202
204u16 MEMiExpHeapHead::getGroupID() const {
205 return m_groupId;
206}
207
209void MEMiExpHeapHead::setGroupID(u16 groupID) {
210 m_groupId = groupID;
211}
212
219 MEMiExpBlockHead *tail = m_freeBlocks.m_tail;
220 if (tail && tail->getMemoryEnd() == getHeapEnd()) {
221 return AddOffset(tail, sizeof(MEMiExpBlockHead));
222 }
223
224 return getHeapEnd();
225}
226
228void *MEMiExpHeapHead::allocFromHead(size_t size, s32 alignment) {
229 MEMiExpBlockHead *found = nullptr;
230 u32 blockSize = -1;
231 void *bestAddress = nullptr;
232
233 for (MEMiExpBlockHead *block = m_freeBlocks.m_head; block; block = block->m_link.m_next) {
234 void *const memptr = block->getMemoryStart();
235 void *const address = RoundUp(memptr, alignment);
236 size_t offset = GetAddrNum(address) - GetAddrNum(memptr);
237
238 if (block->m_size < size + offset) {
239 continue;
240 }
241
242 if (blockSize <= block->m_size) {
243 continue;
244 }
245
246 found = block;
247 blockSize = block->m_size;
248 bestAddress = address;
249
250 // This is a valid block to allocate in, but is it the best one?
251 if (m_attribute.offBit(eAttribute::BestFitAlloc) || blockSize == size) {
252 break;
253 }
254 }
255
256 if (!found) {
257 return nullptr;
258 }
259
260 return allocUsedBlockFromFreeBlock(found, bestAddress, size, 0);
261}
262
264void *MEMiExpHeapHead::allocFromTail(size_t size, s32 alignment) {
265 MEMiExpBlockHead *found = nullptr;
266 u32 blockSize = -1;
267 void *bestAddress = nullptr;
268
269 for (MEMiExpBlockHead *block = m_freeBlocks.m_tail; block; block = block->m_link.m_prev) {
270 void *const start = block->getMemoryStart();
271 void *const endAddr = AddOffset(start, block->m_size);
272 void *const end = RoundDown(SubOffset(endAddr, size), alignment);
273
274 if (static_cast<intptr_t>(GetAddrNum(end) - GetAddrNum(start)) < 0) {
275 continue;
276 }
277
278 if (blockSize <= block->m_size) {
279 continue;
280 }
281
282 found = block;
283 blockSize = block->m_size;
284 bestAddress = end;
285
286 // This is a valid block to allocate in, but is it the best one?
287 if (m_attribute.offBit(eAttribute::BestFitAlloc) || blockSize == size) {
288 break;
289 }
290 }
291
292 if (!found) {
293 return nullptr;
294 }
295
296 return allocUsedBlockFromFreeBlock(found, bestAddress, size, 1);
297}
298
300void *MEMiExpHeapHead::allocUsedBlockFromFreeBlock(MEMiExpBlockHead *block, void *address, u32 size,
301 s32 direction) {
302 // The left region represents the free block created to the left of the new memory block
303 // The right region represents the free block created to the right of the new memory block
304 // address -> address + size exists entirely between leftRegion.end and rightRegion.start
305 Region leftRegion = block->getRegion();
306 Region rightRegion = Region(AddOffset(address, size), leftRegion.end);
307 leftRegion.end = SubOffset(address, sizeof(MEMiExpBlockHead));
308
309 MEMiExpBlockHead *prev = m_freeBlocks.remove(block);
310
311 if (leftRegion.getRange() < sizeof(MEMiExpBlockHead) + 4) {
312 // Not enough room to insert a free memory block
313 leftRegion.end = leftRegion.start;
314 } else {
315 prev = m_freeBlocks.insert(MEMiExpBlockHead::createFree(leftRegion), prev);
316 }
317
318 if (rightRegion.getRange() < sizeof(MEMiExpBlockHead) + 4) {
319 // Not enough room to insert a free memory block
320 rightRegion.start = rightRegion.end;
321 } else {
322 m_freeBlocks.insert(MEMiExpBlockHead::createFree(rightRegion), prev);
323 }
324
325 fillAllocMemory(leftRegion.end, GetAddrNum(rightRegion.start) - GetAddrNum(leftRegion.end));
326
327 // Now that the free blocks are cleared away, create a new used block
328 Region region = Region(SubOffset(address, sizeof(MEMiExpBlockHead)), rightRegion.start);
329 MEMiExpBlockHead *head = MEMiExpBlockHead::createUsed(region);
330
331 head->m_attribute.fields.direction = direction;
332 head->m_attribute.fields.alignment = GetAddrNum(head) - GetAddrNum(leftRegion.end);
333 head->m_attribute.fields.groupId = m_groupId;
334#ifdef BUILD_DEBUG
335 head->m_tag = ++m_tag;
336#endif // BUILD_DEBUG
337
338 m_usedBlocks.append(head);
339
340 return address;
341}
342
343// @addr{0x80198B40}
344bool MEMiExpHeapHead::recycleRegion(const Region &initialRegion) {
345 MEMiExpBlockHead *block = nullptr;
346 Region region = initialRegion;
347
348 for (MEMiExpBlockHead *search = m_freeBlocks.m_head; search; search = search->m_link.m_next) {
349 if (search < initialRegion.start) {
350 block = search;
351 continue;
352 }
353
354 if (search == initialRegion.end) {
355 region.end = search->getMemoryEnd();
356 m_freeBlocks.remove(search);
357 fillNoUseMemory(search, sizeof(MEMiExpBlockHead));
358 }
359
360 break;
361 }
362
363 if (block && block->getMemoryEnd() == initialRegion.start) {
364 region.start = block;
365 block = m_freeBlocks.remove(block);
366 }
367
368 if (region.getRange() < sizeof(MEMiExpBlockHead)) {
369 return false;
370 }
371
372 fillFreeMemory(initialRegion.start, initialRegion.getRange());
373 m_freeBlocks.insert(MEMiExpBlockHead::createFree(region), block);
374 return true;
375}
376
377} // namespace Kinoko::Abstract::Memory
void * calcUsedEnd()
Helper function which finds the end of the used heap region (including the tail head).
Definition ExpHeap.cc:218
Head of the memory block. Contains information about the block and a link in the corresponding used/f...
Definition ExpHeap.hh:38