aboutsummaryrefslogtreecommitdiff
path: root/src/z_zone.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/z_zone.c')
-rw-r--r--src/z_zone.c705
1 files changed, 705 insertions, 0 deletions
diff --git a/src/z_zone.c b/src/z_zone.c
new file mode 100644
index 0000000..9b972fe
--- /dev/null
+++ b/src/z_zone.c
@@ -0,0 +1,705 @@
1/* Emacs style mode select -*- C++ -*-
2 *-----------------------------------------------------------------------------
3 *
4 *
5 * PrBoom: a Doom port merged with LxDoom and LSDLDoom
6 * based on BOOM, a modified and improved DOOM engine
7 * Copyright (C) 1999 by
8 * id Software, Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman
9 * Copyright (C) 1999-2000 by
10 * Jess Haas, Nicolas Kalkhof, Colin Phipps, Florian Schulze
11 * Copyright 2005, 2006 by
12 * Florian Schulze, Colin Phipps, Neil Stevens, Andrey Budko
13 *
14 * This program is free software; you can redistribute it and/or
15 * modify it under the terms of the GNU General Public License
16 * as published by the Free Software Foundation; either version 2
17 * of the License, or (at your option) any later version.
18 *
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with this program; if not, write to the Free Software
26 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
27 * 02111-1307, USA.
28 *
29 * DESCRIPTION:
30 * Zone Memory Allocation. Neat.
31 *
32 * Neat enough to be rewritten by Lee Killough...
33 *
34 * Must not have been real neat :)
35 *
36 * Made faster and more general, and added wrappers for all of Doom's
37 * memory allocation functions, including malloc() and similar functions.
38 * Added line and file numbers, in case of error. Added performance
39 * statistics and tunables.
40 *-----------------------------------------------------------------------------
41 */
42
43
44// use config.h if autoconf made one -- josh
45#ifdef HAVE_CONFIG_H
46#include "config.h"
47#endif
48
49#include <stdlib.h>
50#include <stdio.h>
51
52#include "z_zone.h"
53#include "doomstat.h"
54#include "m_argv.h"
55#include "v_video.h"
56#include "g_game.h"
57#include "lprintf.h"
58
59#ifdef DJGPP
60#include <dpmi.h>
61#endif
62
63// Tunables
64
65// Alignment of zone memory (benefit may be negated by HEADER_SIZE, CHUNK_SIZE)
66#define CACHE_ALIGN 32
67
68// Minimum chunk size at which blocks are allocated
69#define CHUNK_SIZE 32
70
71// Minimum size a block must be to become part of a split
72#define MIN_BLOCK_SPLIT (1024)
73
74// How much RAM to leave aside for other libraries
75#define LEAVE_ASIDE (128*1024)
76
77// Amount to subtract when retrying failed attempts to allocate initial pool
78#define RETRY_AMOUNT (256*1024)
79
80// signature for block header
81#define ZONEID 0x931d4a11
82
83// Number of mallocs & frees kept in history buffer (must be a power of 2)
84#define ZONE_HISTORY 4
85
86// End Tunables
87
88typedef struct memblock {
89
90#ifdef ZONEIDCHECK
91 unsigned id;
92#endif
93
94 struct memblock *next,*prev;
95 size_t size;
96 void **user;
97 unsigned char tag;
98
99#ifdef INSTRUMENTED
100 const char *file;
101 int line;
102#endif
103
104} memblock_t;
105
106/* size of block header
107 * cph - base on sizeof(memblock_t), which can be larger than CHUNK_SIZE on
108 * 64bit architectures */
109static const size_t HEADER_SIZE = (sizeof(memblock_t)+CHUNK_SIZE-1) & ~(CHUNK_SIZE-1);
110
111static memblock_t *blockbytag[PU_MAX];
112
113// 0 means unlimited, any other value is a hard limit
114//static int memory_size = 8192*1024;
115static int memory_size = 0;
116static int free_memory = 0;
117
118#ifdef INSTRUMENTED
119
120// statistics for evaluating performance
121static int active_memory = 0;
122static int purgable_memory = 0;
123
124static void Z_DrawStats(void) // Print allocation statistics
125{
126 if (gamestate != GS_LEVEL)
127 return;
128
129 if (memory_size > 0) {
130 unsigned long total_memory = free_memory + memory_size + active_memory + purgable_memory;
131 double s = 100.0 / total_memory;
132
133 doom_printf("%-5i\t%6.01f%%\tstatic\n"
134 "%-5i\t%6.01f%%\tpurgable\n"
135 "%-5i\t%6.01f%%\tfree\n"
136 "%-5li\t\ttotal\n",
137 active_memory,
138 active_memory*s,
139 purgable_memory,
140 purgable_memory*s,
141 (free_memory + memory_size),
142 (free_memory + memory_size)*s,
143 total_memory
144 );
145 } else {
146 unsigned long total_memory = active_memory + purgable_memory;
147 double s = 100.0 / total_memory;
148
149 doom_printf("%-5i\t%6.01f%%\tstatic\n"
150 "%-5i\t%6.01f%%\tpurgable\n"
151 "%-5li\t\ttotal\n",
152 active_memory,
153 active_memory*s,
154 purgable_memory,
155 purgable_memory*s,
156 total_memory
157 );
158 }
159}
160
161#ifdef HEAPDUMP
162
163#ifndef HEAPDUMP_DIR
164#define HEAPDUMP_DIR "."
165#endif
166
167void W_PrintLump(FILE* fp, void* p);
168
169void Z_DumpMemory(void)
170{
171 static int dump;
172 char buf[PATH_MAX + 1];
173 FILE* fp;
174 size_t total_cache = 0, total_free = 0, total_malloc = 0;
175 int tag;
176
177 sprintf(buf, "%s/memdump.%d", HEAPDUMP_DIR, dump++);
178 fp = fopen(buf, "w");
179 for (tag = PU_FREE; tag < PU_MAX; tag++)
180 {
181 memblock_t* end_block, *block;
182 block = blockbytag[tag];
183 if (!block)
184 continue;
185 end_block = block->prev;
186 while (1)
187 {
188 switch (block->tag) {
189 case PU_FREE:
190 fprintf(fp, "free %d\n", block->size);
191 total_free += block->size;
192 break;
193 case PU_CACHE:
194 fprintf(fp, "cache %s:%d:%d\n", block->file, block->line, block->size);
195 total_cache += block->size;
196 break;
197 case PU_LEVEL:
198 fprintf(fp, "level %s:%d:%d\n", block->file, block->line, block->size);
199 total_malloc += block->size;
200 break;
201 default:
202 fprintf(fp, "malloc %s:%d:%d", block->file, block->line, block->size);
203 total_malloc += block->size;
204 if (block->file)
205 if (strstr(block->file,"w_memcache.c"))
206 W_PrintLump(fp, (char*)block + HEADER_SIZE);
207 fputc('\n', fp);
208 break;
209 }
210 if (block == end_block)
211 break;
212 block=block->next;
213 }
214 }
215 fprintf(fp, "malloc %d, cache %d, free %d, total %d\n",
216 total_malloc, total_cache, total_free,
217 total_malloc + total_cache + total_free);
218 fclose(fp);
219}
220#endif
221#endif
222
223#ifdef INSTRUMENTED
224
225// killough 4/26/98: Add history information
226
227enum {malloc_history, free_history, NUM_HISTORY_TYPES};
228
229static const char *file_history[NUM_HISTORY_TYPES][ZONE_HISTORY];
230static int line_history[NUM_HISTORY_TYPES][ZONE_HISTORY];
231static int history_index[NUM_HISTORY_TYPES];
232static const char *const desc[NUM_HISTORY_TYPES] = {"malloc()'s", "free()'s"};
233
234void Z_DumpHistory(char *buf)
235{
236 int i,j;
237 char s[1024];
238 strcat(buf,"\n");
239 for (i=0;i<NUM_HISTORY_TYPES;i++)
240 {
241 sprintf(s,"\nLast several %s:\n\n", desc[i]);
242 strcat(buf,s);
243 for (j=0; j<ZONE_HISTORY; j++)
244 {
245 int k = (history_index[i]-j-1) & (ZONE_HISTORY-1);
246 if (file_history[i][k])
247 {
248 sprintf(s, "File: %s, Line: %d\n", file_history[i][k],
249 line_history[i][k]);
250 strcat(buf,s);
251 }
252 }
253 }
254}
255#else
256
257void Z_DumpHistory(char *buf)
258{
259}
260
261#endif
262
263void Z_Close(void)
264{
265#if 0
266 (free)(zonebase);
267 zone = rover = zonebase = NULL;
268#endif
269}
270
271void Z_Init(void)
272{
273#if 0
274 size_t size = zone_size*1000;
275
276#ifdef HAVE_MMAP
277 return; /* cphipps - if we have mmap, we don't need our own heap */
278#endif
279
280#ifdef INSTRUMENTED
281 if (!(HEADER_SIZE >= sizeof(memblock_t) && size > HEADER_SIZE))
282 I_Error("Z_Init: Sanity check failed");
283#endif
284
285 size = (size+CHUNK_SIZE-1) & ~(CHUNK_SIZE-1); // round to chunk size
286 size += HEADER_SIZE + CACHE_ALIGN;
287
288 // Allocate the memory
289
290 zonebase=(malloc)(size);
291 if (!zonebase)
292 I_Error("Z_Init: Failed on allocation of %lu bytes", (unsigned long)size);
293
294 lprintf(LO_INFO,"Z_Init : Allocated %lukb zone memory\n",
295 (long unsigned)size / 1000);
296
297 // Align on cache boundary
298
299 zone = (memblock_t *) ((char *) zonebase + CACHE_ALIGN -
300 ((unsigned) zonebase & (CACHE_ALIGN-1)));
301
302 rover = zone; // Rover points to base of zone mem
303 zone->next = zone->prev = zone; // Single node
304 zone->size = size; // All memory in one block
305 zone->tag = PU_FREE; // A free block
306 zone->vm = 0;
307
308#ifdef ZONEIDCHECK
309 zone->id = 0;
310#endif
311
312#ifdef INSTRUMENTED
313 free_memory = size;
314 /* cph - remove unnecessary initialisations to 0 */
315#endif
316#ifdef HEAPDUMP
317 atexit(Z_DumpMemory);
318#endif
319#endif
320}
321
322/* Z_Malloc
323 * You can pass a NULL user if the tag is < PU_PURGELEVEL.
324 *
325 * cph - the algorithm here was a very simple first-fit round-robin
326 * one - just keep looping around, freeing everything we can until
327 * we get a large enough space
328 *
329 * This has been changed now; we still do the round-robin first-fit,
330 * but we only free the blocks we actually end up using; we don't
331 * free all the stuff we just pass on the way.
332 */
333
334void *(Z_Malloc)(size_t size, int tag, void **user
335#ifdef INSTRUMENTED
336 , const char *file, int line
337#endif
338 )
339{
340 memblock_t *block = NULL;
341
342#ifdef INSTRUMENTED
343#ifdef CHECKHEAP
344 Z_CheckHeap();
345#endif
346
347 file_history[malloc_history][history_index[malloc_history]] = file;
348 line_history[malloc_history][history_index[malloc_history]++] = line;
349 history_index[malloc_history] &= ZONE_HISTORY-1;
350#endif
351
352#ifdef ZONEIDCHECK
353 if (tag >= PU_PURGELEVEL && !user)
354 I_Error ("Z_Malloc: An owner is required for purgable blocks"
355#ifdef INSTRUMENTED
356 "Source: %s:%d", file, line
357#endif
358 );
359#endif
360
361 if (!size)
362 return user ? *user = NULL : NULL; // malloc(0) returns NULL
363
364 size = (size+CHUNK_SIZE-1) & ~(CHUNK_SIZE-1); // round to chunk size
365
366 if (memory_size > 0 && ((free_memory + memory_size) < (int)(size + HEADER_SIZE)))
367 {
368 memblock_t *end_block;
369 block = blockbytag[PU_CACHE];
370 if (block)
371 {
372 end_block = block->prev;
373 while (1)
374 {
375 memblock_t *next = block->next;
376#ifdef INSTRUMENTED
377 (Z_Free)((char *) block + HEADER_SIZE, file, line);
378#else
379 (Z_Free)((char *) block + HEADER_SIZE);
380#endif
381 if (((free_memory + memory_size) >= (int)(size + HEADER_SIZE)) || (block == end_block))
382 break;
383 block = next; // Advance to next block
384 }
385 }
386 block = NULL;
387 }
388
389#ifdef HAVE_LIBDMALLOC
390 while (!(block = dmalloc_malloc(file,line,size + HEADER_SIZE,DMALLOC_FUNC_MALLOC,0,0))) {
391#else
392 while (!(block = (malloc)(size + HEADER_SIZE))) {
393#endif
394 if (!blockbytag[PU_CACHE])
395 I_Error ("Z_Malloc: Failure trying to allocate %lu bytes"
396#ifdef INSTRUMENTED
397 "\nSource: %s:%d"
398#endif
399 ,(unsigned long) size
400#ifdef INSTRUMENTED
401 , file, line
402#endif
403 );
404 Z_FreeTags(PU_CACHE,PU_CACHE);
405 }
406
407 if (!blockbytag[tag])
408 {
409 blockbytag[tag] = block;
410 block->next = block->prev = block;
411 }
412 else
413 {
414 blockbytag[tag]->prev->next = block;
415 block->prev = blockbytag[tag]->prev;
416 block->next = blockbytag[tag];
417 blockbytag[tag]->prev = block;
418 }
419
420 block->size = size;
421
422#ifdef INSTRUMENTED
423 if (tag >= PU_PURGELEVEL)
424 purgable_memory += block->size;
425 else
426 active_memory += block->size;
427#endif
428 free_memory -= block->size;
429
430#ifdef INSTRUMENTED
431 block->file = file;
432 block->line = line;
433#endif
434
435#ifdef ZONEIDCHECK
436 block->id = ZONEID; // signature required in block header
437#endif
438 block->tag = tag; // tag
439 block->user = user; // user
440 block = (memblock_t *)((char *) block + HEADER_SIZE);
441 if (user) // if there is a user
442 *user = block; // set user to point to new block
443
444#ifdef INSTRUMENTED
445 Z_DrawStats(); // print memory allocation stats
446 // scramble memory -- weed out any bugs
447 memset(block, gametic & 0xff, size);
448#endif
449
450 return block;
451}
452
453void (Z_Free)(void *p
454#ifdef INSTRUMENTED
455 , const char *file, int line
456#endif
457 )
458{
459 memblock_t *block = (memblock_t *)((char *) p - HEADER_SIZE);
460
461#ifdef INSTRUMENTED
462#ifdef CHECKHEAP
463 Z_CheckHeap();
464#endif
465 file_history[free_history][history_index[free_history]] = file;
466 line_history[free_history][history_index[free_history]++] = line;
467 history_index[free_history] &= ZONE_HISTORY-1;
468#endif
469
470 if (!p)
471 return;
472
473
474#ifdef ZONEIDCHECK
475 if (block->id != ZONEID)
476 I_Error("Z_Free: freed a pointer without ZONEID"
477#ifdef INSTRUMENTED
478 "\nSource: %s:%d"
479 "\nSource of malloc: %s:%d"
480 , file, line, block->file, block->line
481#endif
482 );
483 block->id = 0; // Nullify id so another free fails
484#endif
485
486 if (block->user) // Nullify user if one exists
487 *block->user = NULL;
488
489 if (block == block->next)
490 blockbytag[block->tag] = NULL;
491 else
492 if (blockbytag[block->tag] == block)
493 blockbytag[block->tag] = block->next;
494 block->prev->next = block->next;
495 block->next->prev = block->prev;
496
497 free_memory += block->size;
498#ifdef INSTRUMENTED
499 if (block->tag >= PU_PURGELEVEL)
500 purgable_memory -= block->size;
501 else
502 active_memory -= block->size;
503
504 /* scramble memory -- weed out any bugs */
505 memset(block, gametic & 0xff, block->size + HEADER_SIZE);
506#endif
507
508#ifdef HAVE_LIBDMALLOC
509 dmalloc_free(file,line,block,DMALLOC_FUNC_MALLOC);
510#else
511 (free)(block);
512#endif
513#ifdef INSTRUMENTED
514 Z_DrawStats(); // print memory allocation stats
515#endif
516}
517
518void (Z_FreeTags)(int lowtag, int hightag
519#ifdef INSTRUMENTED
520 , const char *file, int line
521#endif
522 )
523{
524#ifdef HEAPDUMP
525 Z_DumpMemory();
526#endif
527
528 if (lowtag <= PU_FREE)
529 lowtag = PU_FREE+1;
530
531 if (hightag > PU_CACHE)
532 hightag = PU_CACHE;
533
534 for (;lowtag <= hightag; lowtag++)
535 {
536 memblock_t *block, *end_block;
537 block = blockbytag[lowtag];
538 if (!block)
539 continue;
540 end_block = block->prev;
541 while (1)
542 {
543 memblock_t *next = block->next;
544#ifdef INSTRUMENTED
545 (Z_Free)((char *) block + HEADER_SIZE, file, line);
546#else
547 (Z_Free)((char *) block + HEADER_SIZE);
548#endif
549 if (block == end_block)
550 break;
551 block = next; // Advance to next block
552 }
553 }
554}
555
556void (Z_ChangeTag)(void *ptr, int tag
557#ifdef INSTRUMENTED
558 , const char *file, int line
559#endif
560 )
561{
562 memblock_t *block = (memblock_t *)((char *) ptr - HEADER_SIZE);
563
564 // proff - added sanity check, this can happen when an empty lump is locked
565 if (!ptr)
566 return;
567
568 // proff - do nothing if tag doesn't differ
569 if (tag == block->tag)
570 return;
571
572#ifdef INSTRUMENTED
573#ifdef CHECKHEAP
574 Z_CheckHeap();
575#endif
576#endif
577
578#ifdef ZONEIDCHECK
579 if (block->id != ZONEID)
580 I_Error ("Z_ChangeTag: freed a pointer without ZONEID"
581#ifdef INSTRUMENTED
582 "\nSource: %s:%d"
583 "\nSource of malloc: %s:%d"
584 , file, line, block->file, block->line
585#endif
586 );
587
588 if (tag >= PU_PURGELEVEL && !block->user)
589 I_Error ("Z_ChangeTag: an owner is required for purgable blocks\n"
590#ifdef INSTRUMENTED
591 "Source: %s:%d"
592 "\nSource of malloc: %s:%d"
593 , file, line, block->file, block->line
594#endif
595 );
596
597#endif // ZONEIDCHECK
598
599 if (block == block->next)
600 blockbytag[block->tag] = NULL;
601 else
602 if (blockbytag[block->tag] == block)
603 blockbytag[block->tag] = block->next;
604 block->prev->next = block->next;
605 block->next->prev = block->prev;
606
607 if (!blockbytag[tag])
608 {
609 blockbytag[tag] = block;
610 block->next = block->prev = block;
611 }
612 else
613 {
614 blockbytag[tag]->prev->next = block;
615 block->prev = blockbytag[tag]->prev;
616 block->next = blockbytag[tag];
617 blockbytag[tag]->prev = block;
618 }
619
620#ifdef INSTRUMENTED
621 if (block->tag < PU_PURGELEVEL && tag >= PU_PURGELEVEL)
622 {
623 active_memory -= block->size;
624 purgable_memory += block->size;
625 }
626 else
627 if (block->tag >= PU_PURGELEVEL && tag < PU_PURGELEVEL)
628 {
629 active_memory += block->size;
630 purgable_memory -= block->size;
631 }
632#endif
633
634 block->tag = tag;
635}
636
637void *(Z_Realloc)(void *ptr, size_t n, int tag, void **user
638#ifdef INSTRUMENTED
639 , const char *file, int line
640#endif
641 )
642{
643 void *p = (Z_Malloc)(n, tag, user DA(file, line));
644 if (ptr)
645 {
646 memblock_t *block = (memblock_t *)((char *) ptr - HEADER_SIZE);
647 memcpy(p, ptr, n <= block->size ? n : block->size);
648 (Z_Free)(ptr DA(file, line));
649 if (user) // in case Z_Free nullified same user
650 *user=p;
651 }
652 return p;
653}
654
655void *(Z_Calloc)(size_t n1, size_t n2, int tag, void **user
656#ifdef INSTRUMENTED
657 , const char *file, int line
658#endif
659 )
660{
661 return
662 (n1*=n2) ? memset((Z_Malloc)(n1, tag, user DA(file, line)), 0, n1) : NULL;
663}
664
665char *(Z_Strdup)(const char *s, int tag, void **user
666#ifdef INSTRUMENTED
667 , const char *file, int line
668#endif
669 )
670{
671 return strcpy((Z_Malloc)(strlen(s)+1, tag, user DA(file, line)), s);
672}
673
674void (Z_CheckHeap)(
675#ifdef INSTRUMENTED
676 const char *file, int line
677#else
678 void
679#endif
680 )
681{
682#if 0
683 memblock_t *block; // Start at base of zone mem
684 if (block)
685 do { // Consistency check (last node treated special)
686 if ((block->next != zone &&
687 (memblock_t *)((char *) block+HEADER_SIZE+block->size) != block->next)
688 || block->next->prev != block || block->prev->next != block)
689 I_Error("Z_CheckHeap: Block size does not touch the next block\n"
690#ifdef INSTRUMENTED
691 "Source: %s:%d"
692 "\nSource of offending block: %s:%d"
693 , file, line, block->file, block->line
694#endif
695 );
696//#ifdef INSTRUMENTED
697// shouldn't be needed anymore, was just for testing
698#if 0
699 if (((int)block->file < 0x00001000) && (block->file != NULL) && (block->tag != 0)) {
700 block->file = NULL;
701 }
702#endif
703 } while ((block=block->next) != zone);
704#endif
705}