1 /**
2 * @file memoryzone.c
3 * Memory zone implementation.
4 *
5 * The zone is composed of multiple memory volumes. New volumes get created on
6 * the fly when needed. This guarantees that all allocation requests will
7 * succeed.
8 *
9 * There is never any space between memblocks, and there will never be two
10 * contiguous free memblocks.
11 *
12 * Each volume employs two rovers that are used to locate free blocks for new
13 * allocations. When an allocation succeeds, the rover is left pointing to the
14 * block after the new allocation. The rover can be left pointing at a
15 * non-empty block. One of the rovers is for the STATIC purgelevels while the
16 * other is for all other purgelevels. The purpose of this is to prevent memory
17 * fragmentation: when longer-lifespan allocations get mixed with
18 * short-lifespan ones, the end result is more longer-lifespan allocations with
19 * small amounts of potentially unusable free space between them. The static
20 * rover attempts to place all the longer-lifespan allocation near the start of
21 * the volume.
22 *
23 * It is not necessary to explicitly call Z_Free() on >= PU_PURGELEVEL blocks
24 * because they will be automatically freed when the rover encounters them.
25 *
26 * @par Block Sequences
27 * The PU_MAPSTATIC purge tag has a special purpose. It works like PU_MAP so
28 * that it is purged on a per map basis, but blocks allocated as PU_MAPSTATIC
29 * should not be freed at any time when the map is being used. Internally, the
30 * map-static blocks are linked into sequences so that Z_Malloc knows to skip
31 * all of them efficiently. This is possible because no block inside the
32 * sequence could be purged by Z_Malloc() anyway.
33 *
34 * @author Copyright © 1999-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
35 * @author Copyright © 2006-2013 Daniel Swanson <danij@dengine.net>
36 * @author Copyright © 2006 Jamie Jones <jamie_jones_au@yahoo.com.au>
37 * @author Copyright © 1993-1996 by id Software, Inc.
38 *
39 * @par License
40 * GPL: http://www.gnu.org/licenses/gpl.html
41 *
42 * <small>This program is free software; you can redistribute it and/or modify
43 * it under the terms of the GNU General Public License as published by the
44 * Free Software Foundation; either version 2 of the License, or (at your
45 * option) any later version. This program is distributed in the hope that it
46 * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
47 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
48 * Public License for more details. You should have received a copy of the GNU
49 * General Public License along with this program; if not, write to the Free
50 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
51 * 02110-1301 USA</small>
52 */
53
54 #include <stdlib.h>
55 #include <string.h>
56 #include <de/Garbage>
57 #include "de/memory.h"
58 #include "de/concurrency.h"
59 #include "de/c_wrapper.h"
60 #include "memoryzone_private.h"
61
62 // Size of one memory zone volume.
63 #define MEMORY_VOLUME_SIZE 0x2000000 // 32 Mb
64
65 #define MINFRAGMENT (sizeof(memblock_t)+32)
66
67 #define ALIGNED(x) (((x) + sizeof(void *) - 1)&(~(sizeof(void *) - 1)))
68
69 /// Special user pointer for blocks that are in use but have no single owner.
70 #define MEMBLOCK_USER_ANONYMOUS ((void *) 2)
71
72 // Used for block allocation of memory from the zone.
73 typedef struct zblockset_block_s {
74 /// Maximum number of elements.
75 unsigned int max;
76
77 /// Number of used elements.
78 unsigned int count;
79
80 /// Size of a single element.
81 size_t elementSize;
82
83 /// Block of memory where elements are.
84 void *elements;
85 } zblockset_block_t;
86
87 static memvolume_t *volumeRoot;
88 static memvolume_t *volumeLast;
89
90 static mutex_t zoneMutex = 0;
91
92 static size_t Z_AllocatedMemory(void);
93 static size_t allocatedMemoryInVolume(memvolume_t *volume);
94
lockZone(void)95 static __inline void lockZone(void)
96 {
97 assert(zoneMutex != 0);
98 Sys_Lock(zoneMutex);
99 }
100
unlockZone(void)101 static __inline void unlockZone(void)
102 {
103 Sys_Unlock(zoneMutex);
104 }
105
106 /**
107 * Conversion from string to long, with the "k" and "m" suffixes.
108 */
superatol(char * s)109 long superatol(char *s)
110 {
111 char *endptr;
112 long val = strtol(s, &endptr, 0);
113
114 if (*endptr == 'k' || *endptr == 'K')
115 val *= 1024;
116 else if (*endptr == 'm' || *endptr == 'M')
117 val *= 1048576;
118 return val;
119 }
120
121 /**
122 * Create a new memory volume. The new volume is added to the list of
123 * memory volumes.
124 */
createVolume(size_t volumeSize)125 static memvolume_t *createVolume(size_t volumeSize)
126 {
127 memblock_t *block;
128 memvolume_t *vol = M_Calloc(sizeof(memvolume_t));
129
130 lockZone();
131
132 // Append to the end of the volume list.
133 if (volumeLast)
134 volumeLast->next = vol;
135 volumeLast = vol;
136 vol->next = 0;
137 if (!volumeRoot)
138 volumeRoot = vol;
139
140 // Allocate memory for the zone volume.
141 vol->size = volumeSize;
142 vol->zone = M_Malloc(vol->size);
143 vol->allocatedBytes = 0;
144
145 // Clear the start of the zone.
146 memset(vol->zone, 0, sizeof(memzone_t) + sizeof(memblock_t));
147 vol->zone->size = vol->size;
148
149 // Set the entire zone to one free block.
150 vol->zone->blockList.next
151 = vol->zone->blockList.prev
152 = block
153 = (memblock_t *) ((byte *) vol->zone + sizeof(memzone_t));
154
155 vol->zone->blockList.user = (void *) vol->zone;
156 vol->zone->blockList.volume = vol;
157 vol->zone->blockList.tag = PU_APPSTATIC;
158 vol->zone->rover = vol->zone->staticRover = block;
159
160 block->prev = block->next = &vol->zone->blockList;
161 block->user = NULL; // free block
162 block->seqFirst = block->seqLast = NULL;
163 block->size = vol->zone->size - sizeof(memzone_t);
164
165 unlockZone();
166
167 App_Log(DE2_LOG_MESSAGE,
168 "Created a new %.1f MB memory volume.", vol->size / 1024.0 / 1024.0);
169
170 Z_CheckHeap();
171
172 return vol;
173 }
174
Z_IsInited(void)175 dd_bool Z_IsInited(void)
176 {
177 return zoneMutex != 0;
178 }
179
Z_Init(void)180 int Z_Init(void)
181 {
182 zoneMutex = Sys_CreateMutex("ZONE_MUTEX");
183
184 // Create the first volume.
185 createVolume(MEMORY_VOLUME_SIZE);
186 return true;
187 }
188
Z_Shutdown(void)189 void Z_Shutdown(void)
190 {
191 int numVolumes = 0;
192 size_t totalMemory = 0;
193
194 // Get rid of possible zone-allocated memory in the garbage.
195 Garbage_RecycleAllWithDestructor(Z_Free);
196
197 // Destroy all the memory volumes.
198 while (volumeRoot)
199 {
200 memvolume_t *vol = volumeRoot;
201 volumeRoot = vol->next;
202
203 // Calculate stats.
204 numVolumes++;
205 totalMemory += vol->size;
206
207 #ifdef LIBDENG_FAKE_MEMORY_ZONE
208 Z_FreeTags(0, DDMAXINT);
209 #endif
210
211 M_Free(vol->zone);
212 M_Free(vol);
213 }
214
215 App_Log(DE2_LOG_NOTE,
216 "Z_Shutdown: Used %i volumes, total %u bytes.", numVolumes, totalMemory);
217
218 Sys_DestroyMutex(zoneMutex);
219 zoneMutex = 0;
220 }
221
222 #ifdef LIBDENG_FAKE_MEMORY_ZONE
Z_GetBlock(void * ptr)223 memblock_t *Z_GetBlock(void *ptr)
224 {
225 memvolume_t *volume;
226 memblock_t *block;
227
228 for (volume = volumeRoot; volume; volume = volume->next)
229 {
230 for (block = volume->zone->blockList.next;
231 block != &volume->zone->blockList;
232 block = block->next)
233 {
234 if (block->area == ptr)
235 {
236 return block;
237 }
238 }
239 }
240 DENG_ASSERT(false); // There is no memory block for this.
241 return NULL;
242 }
243 #endif
244
245 /**
246 * Frees a block of memory allocated with Z_Malloc.
247 *
248 * @param ptr Memory area to free.
249 * @param tracked Pointer to a tracked memory block. Will be updated
250 * if affected by the operation.
251 */
freeBlock(void * ptr,memblock_t ** tracked)252 static void freeBlock(void *ptr, memblock_t **tracked)
253 {
254 memblock_t *block, *other;
255 memvolume_t *volume;
256
257 if (!ptr) return;
258
259 lockZone();
260
261 block = Z_GetBlock(ptr);
262 if (block->id != LIBDENG_ZONEID)
263 {
264 unlockZone();
265 DENG_ASSERT(block->id == LIBDENG_ZONEID);
266 App_Log(DE2_LOG_WARNING,
267 "Attempted to free pointer without ZONEID.");
268 return;
269 }
270
271 // The block was allocated from this volume.
272 volume = block->volume;
273
274 if (block->user > (void **) 0x100) // Smaller values are not pointers.
275 *block->user = 0; // Clear the user's mark.
276 block->user = NULL; // Mark as free.
277 block->tag = 0;
278 block->volume = NULL;
279 block->id = 0;
280
281 #ifdef LIBDENG_FAKE_MEMORY_ZONE
282 M_Free(block->area);
283 block->area = NULL;
284 block->areaSize = 0;
285 #endif
286
287 /**
288 * Erase the entire sequence, if there is one.
289 * It would also be possible to carefully break the sequence in two
290 * parts, but since PU_LEVELSTATICs aren't supposed to be freed one by
291 * one, this this sufficient.
292 */
293 if (block->seqFirst)
294 {
295 memblock_t *first = block->seqFirst;
296 memblock_t *iter = first;
297 while (iter->seqFirst == first)
298 {
299 iter->seqFirst = iter->seqLast = NULL;
300 iter = iter->next;
301 }
302 }
303
304 // Keep tabs on how much memory is used.
305 volume->allocatedBytes -= block->size;
306
307 other = block->prev;
308 if (!other->user)
309 {
310 // Merge with previous free block.
311 other->size += block->size;
312 other->next = block->next;
313 other->next->prev = other;
314 if (block == volume->zone->rover)
315 volume->zone->rover = other;
316 if (block == volume->zone->staticRover)
317 volume->zone->staticRover = other;
318 block = other;
319
320 // Keep track of what happens to the referenced block.
321 if (tracked && *tracked == block)
322 {
323 *tracked = other;
324 }
325 }
326
327 other = block->next;
328 if (!other->user)
329 {
330 // Merge the next free block onto the end.
331 block->size += other->size;
332 block->next = other->next;
333 block->next->prev = block;
334 if (other == volume->zone->rover)
335 volume->zone->rover = block;
336 if (other == volume->zone->staticRover)
337 volume->zone->staticRover = block;
338
339 // Keep track of what happens to the referenced block.
340 if (tracked && *tracked == other)
341 {
342 *tracked = block;
343 }
344 }
345
346 unlockZone();
347 }
348
Z_Free(void * ptr)349 void Z_Free(void *ptr)
350 {
351 freeBlock(ptr, 0);
352 }
353
isFreeBlock(memblock_t * block)354 static __inline dd_bool isFreeBlock(memblock_t *block)
355 {
356 return !block->user;
357 }
358
isRootBlock(memvolume_t * vol,memblock_t * block)359 static __inline dd_bool isRootBlock(memvolume_t *vol, memblock_t *block)
360 {
361 return block == &vol->zone->blockList;
362 }
363
advanceBlock(memvolume_t * vol,memblock_t * block)364 static __inline memblock_t *advanceBlock(memvolume_t *vol, memblock_t *block)
365 {
366 block = block->next;
367 if (isRootBlock(vol, block))
368 {
369 // Continue from the beginning.
370 block = vol->zone->blockList.next;
371 }
372 return block;
373 }
374
rewindRover(memvolume_t * vol,memblock_t * rover,int maxSteps,size_t optimal)375 static __inline memblock_t *rewindRover(memvolume_t *vol, memblock_t *rover, int maxSteps, size_t optimal)
376 {
377 memblock_t *base = rover;
378 size_t prevBest = 0;
379 int i;
380
381 rover = rover->prev;
382 for (i = 0; i < maxSteps && !isRootBlock(vol, rover); ++i)
383 {
384 // Looking for the smallest suitable free block.
385 if (isFreeBlock(rover) && rover->size >= optimal && (!prevBest || rover->size < prevBest))
386 {
387 // Let's use this one.
388 prevBest = rover->size;
389 base = rover;
390 }
391 rover = rover->prev;
392 }
393 return base;
394 }
395
isVolumeTooFull(memvolume_t * vol)396 static dd_bool isVolumeTooFull(memvolume_t *vol)
397 {
398 return vol->allocatedBytes > vol->size * .95f;
399 }
400
401 /**
402 * The static rovers should be rewound back near the beginning of the volume
403 * periodically in order for them to be effective. Currently this is done
404 * whenever tag ranges are purged (e.g., before map changes).
405 */
rewindStaticRovers(void)406 static void rewindStaticRovers(void)
407 {
408 memvolume_t *volume;
409 for (volume = volumeRoot; volume; volume = volume->next)
410 {
411 memblock_t *block;
412 for (block = volume->zone->blockList.next;
413 !isRootBlock(volume, block); block = block->next)
414 {
415 // Let's find the first free block at the beginning of the volume.
416 if (isFreeBlock(block))
417 {
418 volume->zone->staticRover = block;
419 break;
420 }
421 }
422 }
423 }
424
splitFreeBlock(memblock_t * block,size_t size)425 static void splitFreeBlock(memblock_t *block, size_t size)
426 {
427 // There will be a new free fragment after the block.
428 memblock_t *newBlock = (memblock_t *) ((byte *) block + size);
429 newBlock->size = block->size - size;
430 newBlock->user = NULL; // free block
431 newBlock->tag = 0;
432 newBlock->volume = NULL;
433 newBlock->prev = block;
434 newBlock->next = block->next;
435 newBlock->next->prev = newBlock;
436 newBlock->seqFirst = newBlock->seqLast = NULL;
437 #ifdef LIBDENG_FAKE_MEMORY_ZONE
438 newBlock->area = 0;
439 newBlock->areaSize = 0;
440 #endif
441 block->next = newBlock;
442 block->size = size;
443 }
444
Z_Malloc(size_t size,int tag,void * user)445 void *Z_Malloc(size_t size, int tag, void *user)
446 {
447 memblock_t *start, *iter;
448 memvolume_t *volume;
449
450 if (tag < PU_APPSTATIC || tag > PU_PURGELEVEL)
451 {
452 App_Log(DE2_LOG_WARNING, "Z_Malloc: Invalid purgelevel %i, cannot allocate memory.", tag);
453 return NULL;
454 }
455 if (!size)
456 {
457 // You can't allocate "nothing."
458 return NULL;
459 }
460
461 lockZone();
462
463 // Align to pointer size.
464 size = ALIGNED(size);
465
466 // Account for size of block header.
467 size += sizeof(memblock_t);
468
469 // Iterate through memory volumes until we can find one with enough free
470 // memory. (Note: we *will *find one that's large enough.)
471 for (volume = volumeRoot; ; volume = volume->next)
472 {
473 uint numChecked = 0;
474 dd_bool gotoNextVolume = false;
475
476 if (volume == NULL)
477 {
478 // We've run out of volumes. Let's allocate a new one
479 // with enough memory.
480 size_t newVolumeSize = MEMORY_VOLUME_SIZE;
481
482 if (newVolumeSize < size + 0x1000)
483 newVolumeSize = size + 0x1000; // with some spare memory
484
485 volume = createVolume(newVolumeSize);
486 }
487
488 if (isVolumeTooFull(volume))
489 {
490 // We should skip this one.
491 continue;
492 }
493
494 DENG_ASSERT(volume->zone);
495
496 // Scan through the block list looking for the first free block of
497 // sufficient size, throwing out any purgable blocks along the
498 // way.
499
500 if (tag == PU_APPSTATIC || tag == PU_GAMESTATIC)
501 {
502 // Appstatic allocations may be around for a long time so make sure
503 // they don't litter the volume. Their own rover will keep them as
504 // tightly packed as possible.
505 iter = volume->zone->staticRover;
506 }
507 else
508 {
509 // Everything else is allocated using the rover.
510 iter = volume->zone->rover;
511 }
512 assert(iter->prev);
513
514 // Back up a little to see if we have some space available nearby.
515 start = iter = rewindRover(volume, iter, 3, size);
516 numChecked = 0;
517
518 // If the start is in a sequence, move it to the beginning of the
519 // entire sequence. Sequences are handled as a single unpurgable entity,
520 // so we can stop checking at its start.
521 if (start->seqFirst)
522 {
523 start = start->seqFirst;
524 }
525
526 // We will scan ahead until we find something big enough.
527 for ( ; !(isFreeBlock(iter) && iter->size >= size); numChecked++)
528 {
529 // Check for purgable blocks we can dispose of.
530 if (!isFreeBlock(iter))
531 {
532 if (iter->tag >= PU_PURGELEVEL)
533 {
534 memblock_t *old = iter;
535 iter = iter->prev; // Step back.
536 #ifdef LIBDENG_FAKE_MEMORY_ZONE
537 freeBlock(old->area, &start);
538 #else
539 freeBlock((byte *) old + sizeof(memblock_t), &start);
540 #endif
541 }
542 else
543 {
544 if (iter->seqFirst)
545 {
546 // This block is part of a sequence of blocks, none of
547 // which can be purged. Skip the entire sequence.
548 iter = iter->seqFirst->seqLast;
549 }
550 }
551 }
552
553 // Move to the next block.
554 iter = advanceBlock(volume, iter);
555
556 // Ensure that iter will eventually touch start.
557 assert(!start->seqFirst || start->seqFirst == start ||
558 !start->seqFirst->prev->seqFirst ||
559 start->seqFirst->prev->seqFirst == start->seqFirst->prev->seqLast);
560
561 if (iter == start && numChecked > 0)
562 {
563 // Scanned all the way through, no suitable space found.
564 gotoNextVolume = true;
565 App_Log(DE2_LOG_DEBUG,
566 "Z_Malloc: gave up on volume after %i checks", numChecked);
567 break;
568 }
569 }
570
571 // At this point we've found/created a big enough block or we are
572 // skipping this volume entirely.
573
574 if (gotoNextVolume) continue;
575
576 // Found a block big enough.
577 if (iter->size - size > MINFRAGMENT)
578 {
579 splitFreeBlock(iter, size);
580 }
581
582 #ifdef LIBDENG_FAKE_MEMORY_ZONE
583 iter->areaSize = size - sizeof(memblock_t);
584 iter->area = M_Malloc(iter->areaSize);
585 #endif
586
587 if (user)
588 {
589 iter->user = user; // mark as an in use block
590 #ifdef LIBDENG_FAKE_MEMORY_ZONE
591 *(void **) user = iter->area;
592 #else
593 *(void **) user = (void *) ((byte *) iter + sizeof(memblock_t));
594 #endif
595 }
596 else
597 {
598 // An owner is required for purgable blocks.
599 DENG_ASSERT(tag < PU_PURGELEVEL);
600
601 iter->user = MEMBLOCK_USER_ANONYMOUS; // mark as in use, but unowned
602 }
603 iter->tag = tag;
604
605 if (tag == PU_MAPSTATIC)
606 {
607 // Level-statics are linked into unpurgable sequences so they can
608 // be skipped en masse.
609 iter->seqFirst = iter;
610 iter->seqLast = iter;
611 if (iter->prev->seqFirst)
612 {
613 iter->seqFirst = iter->prev->seqFirst;
614 iter->seqFirst->seqLast = iter;
615 }
616 }
617 else
618 {
619 // Not part of a sequence.
620 iter->seqLast = iter->seqFirst = NULL;
621 }
622
623 // Next allocation will start looking here, at the rover.
624 if (tag == PU_APPSTATIC || tag == PU_GAMESTATIC)
625 {
626 volume->zone->staticRover = advanceBlock(volume, iter);
627 }
628 else
629 {
630 volume->zone->rover = advanceBlock(volume, iter);
631 }
632
633 // Keep tabs on how much memory is used.
634 volume->allocatedBytes += iter->size;
635
636 iter->volume = volume;
637 iter->id = LIBDENG_ZONEID;
638
639 unlockZone();
640
641 #ifdef LIBDENG_FAKE_MEMORY_ZONE
642 return iter->area;
643 #else
644 return (void *) ((byte *) iter + sizeof(memblock_t));
645 #endif
646 }
647 }
648
Z_Realloc(void * ptr,size_t n,int mallocTag)649 void *Z_Realloc(void *ptr, size_t n, int mallocTag)
650 {
651 int tag = ptr ? Z_GetTag(ptr) : mallocTag;
652 void *p;
653
654 lockZone();
655
656 n = ALIGNED(n);
657 p = Z_Malloc(n, tag, 0); // User always 0;
658
659 if (ptr)
660 {
661 size_t bsize;
662
663 // Has old data; copy it.
664 memblock_t *block = Z_GetBlock(ptr);
665 #ifdef LIBDENG_FAKE_MEMORY_ZONE
666 bsize = block->areaSize;
667 #else
668 bsize = block->size - sizeof(memblock_t);
669 #endif
670 memcpy(p, ptr, MIN_OF(n, bsize));
671 Z_Free(ptr);
672 }
673
674 unlockZone();
675 return p;
676 }
677
Z_FreeTags(int lowTag,int highTag)678 void Z_FreeTags(int lowTag, int highTag)
679 {
680 memvolume_t *volume;
681 memblock_t *block, *next;
682
683 App_Log(DE2_LOG_DEBUG,
684 "MemoryZone: Freeing all blocks in tag range:[%i, %i)",
685 lowTag, highTag+1);
686
687 for (volume = volumeRoot; volume; volume = volume->next)
688 {
689 for (block = volume->zone->blockList.next;
690 block != &volume->zone->blockList;
691 block = next)
692 {
693 next = block->next;
694
695 if (block->user) // An allocated block?
696 {
697 if (block->tag >= lowTag && block->tag <= highTag)
698 #ifdef LIBDENG_FAKE_MEMORY_ZONE
699 Z_Free(block->area);
700 #else
701 Z_Free((byte *) block + sizeof(memblock_t));
702 #endif
703 }
704 }
705 }
706
707 // Now that there's plenty of new free space, let's keep the static
708 // rover near the beginning of the volume.
709 rewindStaticRovers();
710 }
711
Z_CheckHeap(void)712 void Z_CheckHeap(void)
713 {
714 memvolume_t *volume;
715 memblock_t *block;
716 dd_bool isDone;
717
718 App_Log(DE2_LOG_TRACE, "Z_CheckHeap");
719
720 lockZone();
721
722 for (volume = volumeRoot; volume; volume = volume->next)
723 {
724 size_t total = 0;
725
726 // Validate the counter.
727 if (allocatedMemoryInVolume(volume) != volume->allocatedBytes)
728 {
729 App_Log(DE2_LOG_CRITICAL,
730 "Z_CheckHeap: allocated bytes counter is off (counter:%u != actual:%u)",
731 volume->allocatedBytes, allocatedMemoryInVolume(volume));
732 App_FatalError("Z_CheckHeap: zone book-keeping is wrong");
733 }
734
735 // Does the memory in the blocks sum up to the total volume size?
736 for (block = volume->zone->blockList.next;
737 block != &volume->zone->blockList; block = block->next)
738 {
739 total += block->size;
740 }
741 if (total != volume->size - sizeof(memzone_t))
742 {
743 App_Log(DE2_LOG_CRITICAL,
744 "Z_CheckHeap: invalid total size of blocks (%u != %u)",
745 total, volume->size - sizeof(memzone_t));
746 App_FatalError("Z_CheckHeap: zone book-keeping is wrong");
747 }
748
749 // Does the last block extend all the way to the end?
750 block = volume->zone->blockList.prev;
751 if ((byte *)block - ((byte *)volume->zone + sizeof(memzone_t)) + block->size != volume->size - sizeof(memzone_t))
752 {
753 App_Log(DE2_LOG_CRITICAL,
754 "Z_CheckHeap: last block does not cover the end (%u != %u)",
755 (byte *)block - ((byte *)volume->zone + sizeof(memzone_t)) + block->size,
756 volume->size - sizeof(memzone_t));
757 App_FatalError("Z_CheckHeap: zone is corrupted");
758 }
759
760 block = volume->zone->blockList.next;
761 isDone = false;
762
763 while (!isDone)
764 {
765 if (block->next != &volume->zone->blockList)
766 {
767 if (block->size == 0)
768 App_FatalError("Z_CheckHeap: zero-size block");
769 if ((byte *) block + block->size != (byte *) block->next)
770 App_FatalError("Z_CheckHeap: block size does not touch the "
771 "next block");
772 if (block->next->prev != block)
773 App_FatalError("Z_CheckHeap: next block doesn't have proper "
774 "back link");
775 if (!block->user && !block->next->user)
776 App_FatalError("Z_CheckHeap: two consecutive free blocks");
777 if (block->user == (void **) -1)
778 {
779 DENG_ASSERT(block->user != (void **) -1);
780 App_FatalError("Z_CheckHeap: bad user pointer");
781 }
782
783 /*
784 if (block->seqFirst == block)
785 {
786 // This is the first.
787 printf("sequence begins at (%p): start=%p, end=%p\n", block,
788 block->seqFirst, block->seqLast);
789 }
790 */
791 if (block->seqFirst)
792 {
793 //printf(" seq member (%p): start=%p\n", block, block->seqFirst);
794 if (block->seqFirst->seqLast == block)
795 {
796 //printf(" -=- last member of seq %p -=-\n", block->seqFirst);
797 }
798 else
799 {
800 if (block->next->seqFirst != block->seqFirst)
801 {
802 App_FatalError("Z_CheckHeap: disconnected sequence");
803 }
804 }
805 }
806
807 block = block->next;
808 }
809 else
810 isDone = true; // all blocks have been hit
811 }
812 }
813
814 unlockZone();
815 }
816
Z_ChangeTag2(void * ptr,int tag)817 void Z_ChangeTag2(void *ptr, int tag)
818 {
819 lockZone();
820 {
821 memblock_t *block = Z_GetBlock(ptr);
822
823 DENG_ASSERT(block->id == LIBDENG_ZONEID);
824
825 if (tag >= PU_PURGELEVEL && PTR2INT(block->user) < 0x100)
826 {
827 App_Log(DE2_LOG_ERROR,
828 "Z_ChangeTag: An owner is required for purgable blocks.");
829 }
830 else
831 {
832 block->tag = tag;
833 }
834 }
835 unlockZone();
836 }
837
Z_ChangeUser(void * ptr,void * newUser)838 void Z_ChangeUser(void *ptr, void *newUser)
839 {
840 lockZone();
841 {
842 memblock_t *block = Z_GetBlock(ptr);
843 DENG_ASSERT(block->id == LIBDENG_ZONEID);
844 block->user = newUser;
845 }
846 unlockZone();
847 }
848
Z_GetId(void * ptr)849 uint Z_GetId(void *ptr)
850 {
851 return ((memblock_t *) ((byte *)(ptr) - sizeof(memblock_t)))->id;
852 }
853
Z_GetUser(void * ptr)854 void *Z_GetUser(void *ptr)
855 {
856 memblock_t *block = Z_GetBlock(ptr);
857
858 DENG_ASSERT(block->id == LIBDENG_ZONEID);
859 return block->user;
860 }
861
862 /**
863 * Get the tag of a memory block.
864 */
Z_GetTag(void * ptr)865 int Z_GetTag(void *ptr)
866 {
867 memblock_t *block = Z_GetBlock(ptr);
868
869 DENG_ASSERT(block->id == LIBDENG_ZONEID);
870 return block->tag;
871 }
872
Z_Contains(void * ptr)873 dd_bool Z_Contains(void *ptr)
874 {
875 memvolume_t *volume;
876 memblock_t *block = Z_GetBlock(ptr);
877 DENG_ASSERT(Z_IsInited());
878 if (block->id != LIBDENG_ZONEID)
879 {
880 // Could be in the zone, but does not look like an allocated block.
881 return false;
882 }
883 // Check which volume is it.
884 for (volume = volumeRoot; volume; volume = volume->next)
885 {
886 if ((char *)ptr > (char *)volume->zone && (char *)ptr < (char *)volume->zone + volume->size)
887 {
888 // There it is.
889 return true;
890 }
891 }
892 return false;
893 }
894
Z_Calloc(size_t size,int tag,void * user)895 void *Z_Calloc(size_t size, int tag, void *user)
896 {
897 void *ptr = Z_Malloc(size, tag, user);
898
899 memset(ptr, 0, ALIGNED(size));
900 return ptr;
901 }
902
Z_Recalloc(void * ptr,size_t n,int callocTag)903 void *Z_Recalloc(void *ptr, size_t n, int callocTag)
904 {
905 memblock_t *block;
906 void *p;
907 size_t bsize;
908
909 lockZone();
910
911 n = ALIGNED(n);
912
913 if (ptr) // Has old data.
914 {
915 p = Z_Malloc(n, Z_GetTag(ptr), NULL);
916 block = Z_GetBlock(ptr);
917 #ifdef LIBDENG_FAKE_MEMORY_ZONE
918 bsize = block->areaSize;
919 #else
920 bsize = block->size - sizeof(memblock_t);
921 #endif
922 if (bsize <= n)
923 {
924 memcpy(p, ptr, bsize);
925 memset((char *) p + bsize, 0, n - bsize);
926 }
927 else
928 {
929 // New block is smaller.
930 memcpy(p, ptr, n);
931 }
932 Z_Free(ptr);
933 }
934 else
935 { // Totally new allocation.
936 p = Z_Calloc(n, callocTag, NULL);
937 }
938
939 unlockZone();
940
941 return p;
942 }
943
Z_StrDup(char const * text)944 char *Z_StrDup(char const *text)
945 {
946 if (!text) return 0;
947 {
948 size_t len = strlen(text);
949 char *buf = Z_Malloc(len + 1, PU_APPSTATIC, 0);
950 strcpy(buf, text);
951 return buf;
952 }
953 }
954
Z_MemDup(void const * ptr,size_t size)955 void *Z_MemDup(void const *ptr, size_t size)
956 {
957 void *copy = Z_Malloc(size, PU_APPSTATIC, 0);
958 memcpy(copy, ptr, size);
959 return copy;
960 }
961
Z_VolumeCount(void)962 uint Z_VolumeCount(void)
963 {
964 memvolume_t *volume;
965 size_t count = 0;
966
967 lockZone();
968 for (volume = volumeRoot; volume; volume = volume->next)
969 {
970 count++;
971 }
972 unlockZone();
973
974 return count;
975 }
976
allocatedMemoryInVolume(memvolume_t * volume)977 static size_t allocatedMemoryInVolume(memvolume_t *volume)
978 {
979 memblock_t *block;
980 size_t total = 0;
981
982 for (block = volume->zone->blockList.next; !isRootBlock(volume, block);
983 block = block->next)
984 {
985 if (!isFreeBlock(block))
986 {
987 total += block->size;
988 }
989 }
990 return total;
991 }
992
993 /**
994 * Calculate the size of allocated memory blocks in all volumes combined.
995 */
Z_AllocatedMemory(void)996 static size_t Z_AllocatedMemory(void)
997 {
998 memvolume_t *volume;
999 size_t total = 0;
1000
1001 lockZone();
1002
1003 for (volume = volumeRoot; volume; volume = volume->next)
1004 {
1005 total += allocatedMemoryInVolume(volume);
1006 }
1007
1008 unlockZone();
1009 return total;
1010 }
1011
1012 /**
1013 * Calculate the amount of unused memory in all volumes combined.
1014 */
Z_FreeMemory(void)1015 size_t Z_FreeMemory(void)
1016 {
1017 memvolume_t *volume;
1018 memblock_t *block;
1019 size_t free = 0;
1020
1021 lockZone();
1022
1023 Z_CheckHeap();
1024 for (volume = volumeRoot; volume; volume = volume->next)
1025 {
1026 for (block = volume->zone->blockList.next;
1027 block != &volume->zone->blockList;
1028 block = block->next)
1029 {
1030 if (!block->user)
1031 {
1032 free += block->size;
1033 }
1034 }
1035 }
1036
1037 unlockZone();
1038 return free;
1039 }
1040
Z_PrintStatus(void)1041 void Z_PrintStatus(void)
1042 {
1043 size_t allocated = Z_AllocatedMemory();
1044 size_t wasted = Z_FreeMemory();
1045
1046 App_Log(DE2_LOG_DEBUG,
1047 "Memory zone status: %u volumes, %u bytes allocated, %u bytes free (%f%% in use)",
1048 Z_VolumeCount(), (uint)allocated, (uint)wasted, (float)allocated/(float)(allocated+wasted)*100.f);
1049 }
1050
Garbage_Trash(void * ptr)1051 void Garbage_Trash(void *ptr)
1052 {
1053 Garbage_TrashInstance(ptr, Z_Contains(ptr)? Z_Free : free);
1054 }
1055
1056 /**
1057 * Allocate a new block of memory to be used for linear object allocations.
1058 * A "zblock" (its from the zone).
1059 *
1060 * @param set Block set into which the new block is added.
1061 */
addBlockToSet(zblockset_t * set)1062 static void addBlockToSet(zblockset_t *set)
1063 {
1064 assert(set);
1065 {
1066 zblockset_block_t *block = 0;
1067
1068 // Get a new block by resizing the blocks array. This is done relatively
1069 // seldom, since there is a large number of elements per each block.
1070 set->_blockCount++;
1071 set->_blocks = Z_Recalloc(set->_blocks, sizeof(zblockset_block_t) * set->_blockCount, set->_tag);
1072
1073 App_Log(DE2_LOG_DEBUG,
1074 "addBlockToSet: set=%p blockCount=%u elemSize=%u elemCount=%u (total=%u)",
1075 set, set->_blockCount, (uint)set->_elementSize, set->_elementsPerBlock,
1076 (uint)(set->_blockCount * set->_elementSize * set->_elementsPerBlock));
1077
1078 // Initialize the block's data.
1079 block = &set->_blocks[set->_blockCount - 1];
1080 block->max = set->_elementsPerBlock;
1081 block->elementSize = set->_elementSize;
1082 block->elements = Z_Malloc(block->elementSize * block->max, set->_tag, NULL);
1083 block->count = 0;
1084 }
1085 }
1086
ZBlockSet_Allocate(zblockset_t * set)1087 void *ZBlockSet_Allocate(zblockset_t *set)
1088 {
1089 zblockset_block_t *block = 0;
1090 void *element = 0;
1091
1092 assert(set);
1093 lockZone();
1094
1095 block = &set->_blocks[set->_blockCount - 1];
1096
1097 // When this is called, there is always an available element in the topmost
1098 // block. We will return it.
1099 element = ((byte *)block->elements) + (block->elementSize * block->count);
1100
1101 // Reserve the element.
1102 block->count++;
1103
1104 // If we run out of space in the topmost block, add a new one.
1105 if (block->count == block->max)
1106 {
1107 // Just being cautious: adding a new block invalidates existing
1108 // pointers to the blocks.
1109 block = 0;
1110
1111 addBlockToSet(set);
1112 }
1113
1114 unlockZone();
1115 return element;
1116 }
1117
ZBlockSet_New(size_t sizeOfElement,uint32_t batchSize,int tag)1118 zblockset_t *ZBlockSet_New(size_t sizeOfElement, uint32_t batchSize, int tag)
1119 {
1120 zblockset_t *set;
1121
1122 DENG_ASSERT(sizeOfElement > 0);
1123 DENG_ASSERT(batchSize > 0);
1124
1125 // Allocate the blockset.
1126 set = Z_Calloc(sizeof(*set), tag, NULL);
1127 set->_elementsPerBlock = batchSize;
1128 set->_elementSize = sizeOfElement;
1129 set->_tag = tag;
1130
1131 // Allocate the first block right away.
1132 addBlockToSet(set);
1133
1134 return set;
1135 }
1136
ZBlockSet_Delete(zblockset_t * set)1137 void ZBlockSet_Delete(zblockset_t *set)
1138 {
1139 lockZone();
1140 assert(set);
1141
1142 // Free the elements from each block.
1143 { uint i;
1144 for (i = 0; i < set->_blockCount; ++i)
1145 Z_Free(set->_blocks[i].elements);
1146 }
1147
1148 Z_Free(set->_blocks);
1149 Z_Free(set);
1150 unlockZone();
1151 }
1152
1153 #ifdef DENG_DEBUG
Z_GetPrivateData(MemoryZonePrivateData * pd)1154 void Z_GetPrivateData(MemoryZonePrivateData *pd)
1155 {
1156 pd->volumeCount = Z_VolumeCount();
1157 pd->volumeRoot = volumeRoot;
1158 pd->lock = lockZone;
1159 pd->unlock = unlockZone;
1160 pd->isVolumeTooFull = isVolumeTooFull;
1161 }
1162 #endif
1163