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 &copy; 1999-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
35  * @author Copyright &copy; 2006-2013 Daniel Swanson <danij@dengine.net>
36  * @author Copyright &copy; 2006 Jamie Jones <jamie_jones_au@yahoo.com.au>
37  * @author Copyright &copy; 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