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 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
214void *MEMiExpHeapHead::allocFromHead(size_t size, s32 alignment) {
215 MEMiExpBlockHead *found = nullptr;
216 u32 blockSize = -1;
217 void *bestAddress = nullptr;
218
219 for (MEMiExpBlockHead *block = m_freeBlocks.m_head; block; block = block->m_link.m_next) {
220 void *const memptr = block->getMemoryStart();
221 void *const address = RoundUp(memptr, alignment);
222 size_t offset = GetAddrNum(address) - GetAddrNum(memptr);
223
224 if (block->m_size < size + offset) {
225 continue;
226 }
227
228 if (blockSize <= block->m_size) {
229 continue;
230 }
231
232 found = block;
233 blockSize = block->m_size;
234 bestAddress = address;
235
236 // This is a valid block to allocate in, but is it the best one?
237 if (m_attribute.offBit(eAttribute::BestFitAlloc) || blockSize == size) {
238 break;
239 }
240 }
241
242 if (!found) {
243 return nullptr;
244 }
245
246 return allocUsedBlockFromFreeBlock(found, bestAddress, size, 0);
247}
248
250void *MEMiExpHeapHead::allocFromTail(size_t size, s32 alignment) {
251 MEMiExpBlockHead *found = nullptr;
252 u32 blockSize = -1;
253 void *bestAddress = nullptr;
254
255 for (MEMiExpBlockHead *block = m_freeBlocks.m_tail; block; block = block->m_link.m_prev) {
256 void *const start = block->getMemoryStart();
257 void *const endAddr = AddOffset(start, block->m_size);
258 void *const end = RoundDown(SubOffset(endAddr, size), alignment);
259
260 if (static_cast<intptr_t>(GetAddrNum(end) - GetAddrNum(start)) < 0) {
261 continue;
262 }
263
264 if (blockSize <= block->m_size) {
265 continue;
266 }
267
268 found = block;
269 blockSize = block->m_size;
270 bestAddress = end;
271
272 // This is a valid block to allocate in, but is it the best one?
273 if (m_attribute.offBit(eAttribute::BestFitAlloc) || blockSize == size) {
274 break;
275 }
276 }
277
278 if (!found) {
279 return nullptr;
280 }
281
282 return allocUsedBlockFromFreeBlock(found, bestAddress, size, 1);
283}
284
286void *MEMiExpHeapHead::allocUsedBlockFromFreeBlock(MEMiExpBlockHead *block, void *address, u32 size,
287 s32 direction) {
288 // The left region represents the free block created to the left of the new memory block
289 // The right region represents the free block created to the right of the new memory block
290 // address -> address + size exists entirely between leftRegion.end and rightRegion.start
291 Region leftRegion = block->getRegion();
292 Region rightRegion = Region(AddOffset(address, size), leftRegion.end);
293 leftRegion.end = SubOffset(address, sizeof(MEMiExpBlockHead));
294
295 MEMiExpBlockHead *prev = m_freeBlocks.remove(block);
296
297 if (leftRegion.getRange() < sizeof(MEMiExpBlockHead) + 4) {
298 // Not enough room to insert a free memory block
299 leftRegion.end = leftRegion.start;
300 } else {
301 prev = m_freeBlocks.insert(MEMiExpBlockHead::createFree(leftRegion), prev);
302 }
303
304 if (rightRegion.getRange() < sizeof(MEMiExpBlockHead) + 4) {
305 // Not enough room to insert a free memory block
306 rightRegion.end = rightRegion.start;
307 } else {
308 m_freeBlocks.insert(MEMiExpBlockHead::createFree(rightRegion), prev);
309 }
310
311 fillAllocMemory(leftRegion.end, GetAddrNum(rightRegion.start) - GetAddrNum(leftRegion.end));
312
313 // Now that the free blocks are cleared away, create a new used block
314 Region region = Region(SubOffset(address, sizeof(MEMiExpBlockHead)), rightRegion.start);
315 MEMiExpBlockHead *head = MEMiExpBlockHead::createUsed(region);
316
317 head->m_attribute.fields.direction = direction;
318 head->m_attribute.fields.alignment = GetAddrNum(head) - GetAddrNum(leftRegion.end);
319 head->m_attribute.fields.groupId = m_groupId;
320#ifdef BUILD_DEBUG
321 head->m_tag = ++m_tag;
322#endif // BUILD_DEBUG
323
324 m_usedBlocks.append(head);
325
326 return address;
327}
328
329// @addr{0x80198B40}
330bool MEMiExpHeapHead::recycleRegion(const Region &initialRegion) {
331 MEMiExpBlockHead *block = nullptr;
332 Region region = initialRegion;
333
334 for (MEMiExpBlockHead *search = m_freeBlocks.m_head; search; search = search->m_link.m_next) {
335 if (search < initialRegion.start) {
336 block = search;
337 continue;
338 }
339
340 if (search == initialRegion.end) {
341 region.end = search->getMemoryEnd();
342 m_freeBlocks.remove(search);
343 fillNoUseMemory(search, sizeof(MEMiExpBlockHead));
344 }
345
346 break;
347 }
348
349 if (block && block->getMemoryEnd() == initialRegion.start) {
350 region.start = block;
351 block = m_freeBlocks.remove(block);
352 }
353
354 if (region.getRange() < sizeof(MEMiExpBlockHead)) {
355 return false;
356 }
357
358 fillFreeMemory(initialRegion.start, initialRegion.getRange());
359 m_freeBlocks.insert(MEMiExpBlockHead::createFree(region), block);
360 return true;
361}
362
363} // namespace Abstract::Memory