report name when Hunk_Alloc fails
[xonotic/darkplaces.git] / zone.c
1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19 */
20 // Z_zone.c
21
22 #include "quakedef.h"
23
24 // LordHavoc: everyone used a -zone 512, but 128k is sufficient usually, I think...
25 #define DYNAMIC_SIZE    0x20000
26 //#define       DYNAMIC_SIZE    0xc000
27
28 #define ZONEID  0x1d4a11
29 #define MINFRAGMENT     64
30
31 typedef struct memblock_s
32 {
33         int             size;           // including the header and possibly tiny fragments
34         int     tag;            // a tag of 0 is a free block
35         int     id;                     // should be ZONEID
36         struct memblock_s       *next, *prev;
37         int             pad;                    // pad to 64 bit boundary
38 } memblock_t;
39
40 typedef struct
41 {
42         int             size;           // total bytes malloced, including header
43         memblock_t      blocklist;              // start / end cap for linked list
44         memblock_t      *rover;
45 } memzone_t;
46
47 void Cache_FreeLow (int new_low_hunk);
48 void Cache_FreeHigh (int new_high_hunk);
49
50
51 /*
52 ==============================================================================
53
54                                                 ZONE MEMORY ALLOCATION
55
56 There is never any space between memblocks, and there will never be two
57 contiguous free memblocks.
58
59 The rover can be left pointing at a non-empty block
60
61 The zone calls are pretty much only used for small strings and structures,
62 all big things are allocated on the hunk.
63 ==============================================================================
64 */
65
66 memzone_t       *mainzone;
67
68 void Z_ClearZone (memzone_t *zone, int size);
69
70
71 /*
72 ========================
73 Z_ClearZone
74 ========================
75 */
76 void Z_ClearZone (memzone_t *zone, int size)
77 {
78         memblock_t      *block;
79         
80 // set the entire zone to one free block
81
82         zone->blocklist.next = zone->blocklist.prev = block =
83                 (memblock_t *)( (byte *)zone + sizeof(memzone_t) );
84         zone->blocklist.tag = 1;        // in use block
85         zone->blocklist.id = 0;
86         zone->blocklist.size = 0;
87         zone->rover = block;
88         
89         block->prev = block->next = &zone->blocklist;
90         block->tag = 0;                 // free block
91         block->id = ZONEID;
92         block->size = size - sizeof(memzone_t);
93 }
94
95
96 /*
97 ========================
98 Z_Free
99 ========================
100 */
101 void Z_Free (void *ptr)
102 {
103         memblock_t      *block, *other;
104         
105         if (!ptr)
106                 Sys_Error ("Z_Free: NULL pointer");
107
108         block = (memblock_t *) ( (byte *)ptr - sizeof(memblock_t));
109         if (block->id != ZONEID)
110                 Sys_Error ("Z_Free: freed a pointer without ZONEID");
111         if (block->tag == 0)
112                 Sys_Error ("Z_Free: freed a freed pointer");
113
114         block->tag = 0;         // mark as free
115         
116         other = block->prev;
117         if (!other->tag)
118         {       // merge with previous free block
119                 other->size += block->size;
120                 other->next = block->next;
121                 other->next->prev = other;
122                 if (block == mainzone->rover)
123                         mainzone->rover = other;
124                 block = other;
125         }
126         
127         other = block->next;
128         if (!other->tag)
129         {       // merge the next free block onto the end
130                 block->size += other->size;
131                 block->next = other->next;
132                 block->next->prev = block;
133                 if (other == mainzone->rover)
134                         mainzone->rover = block;
135         }
136 }
137
138
139 /*
140 ========================
141 Z_Malloc
142 ========================
143 */
144 void *Z_Malloc (int size)
145 {
146         void    *buf;
147         
148 Z_CheckHeap (); // DEBUG
149         buf = Z_TagMalloc (size, 1);
150         if (!buf)
151                 Sys_Error ("Z_Malloc: failed on allocation of %i bytes",size);
152         memset (buf, 0, size);
153
154         return buf;
155 }
156
157 void *Z_TagMalloc (int size, int tag)
158 {
159         int             extra;
160         memblock_t      *start, *rover, *new, *base;
161
162         if (!tag)
163                 Sys_Error ("Z_TagMalloc: tried to use a 0 tag");
164
165 //
166 // scan through the block list looking for the first free block
167 // of sufficient size
168 //
169         size += sizeof(memblock_t);     // account for size of block header
170         size += 4;                                      // space for memory trash tester
171         size = (size + 7) & ~7;         // align to 8-byte boundary
172         
173         base = rover = mainzone->rover;
174         start = base->prev;
175         
176         do
177         {
178                 if (rover == start)     // scaned all the way around the list
179                         return NULL;
180                 if (rover->tag)
181                         base = rover = rover->next;
182                 else
183                         rover = rover->next;
184         } while (base->tag || base->size < size);
185         
186 //
187 // found a block big enough
188 //
189         extra = base->size - size;
190         if (extra >  MINFRAGMENT)
191         {       // there will be a free fragment after the allocated block
192                 new = (memblock_t *) ((byte *)base + size );
193                 new->size = extra;
194                 new->tag = 0;                   // free block
195                 new->prev = base;
196                 new->id = ZONEID;
197                 new->next = base->next;
198                 new->next->prev = new;
199                 base->next = new;
200                 base->size = size;
201         }
202         
203         base->tag = tag;                                // no longer a free block
204         
205         mainzone->rover = base->next;   // next allocation will start looking here
206         
207         base->id = ZONEID;
208
209 // marker for memory trash testing
210         *(int *)((byte *)base + base->size - 4) = ZONEID;
211
212         return (void *) ((byte *)base + sizeof(memblock_t));
213 }
214
215
216 /*
217 ========================
218 Z_Print
219 ========================
220 */
221 void Z_Print (memzone_t *zone)
222 {
223         memblock_t      *block;
224         
225         Con_Printf ("zone size: %i  location: %p\n",mainzone->size,mainzone);
226         
227         for (block = zone->blocklist.next ; ; block = block->next)
228         {
229                 Con_Printf ("block:%p    size:%7i    tag:%3i\n",
230                         block, block->size, block->tag);
231                 
232                 if (block->next == &zone->blocklist)
233                         break;                  // all blocks have been hit     
234                 if ( (byte *)block + block->size != (byte *)block->next)
235                         Con_Printf ("ERROR: block size does not touch the next block\n");
236                 if ( block->next->prev != block)
237                         Con_Printf ("ERROR: next block doesn't have proper back link\n");
238                 if (!block->tag && !block->next->tag)
239                         Con_Printf ("ERROR: two consecutive free blocks\n");
240         }
241 }
242
243
244 /*
245 ========================
246 Z_CheckHeap
247 ========================
248 */
249 void Z_CheckHeap (void)
250 {
251         memblock_t      *block;
252         
253         for (block = mainzone->blocklist.next ; ; block = block->next)
254         {
255                 if (block->next == &mainzone->blocklist)
256                         break;                  // all blocks have been hit     
257                 if ( (byte *)block + block->size != (byte *)block->next)
258                         Sys_Error ("Z_CheckHeap: block size does not touch the next block\n");
259                 if ( block->next->prev != block)
260                         Sys_Error ("Z_CheckHeap: next block doesn't have proper back link\n");
261                 if (!block->tag && !block->next->tag)
262                         Sys_Error ("Z_CheckHeap: two consecutive free blocks\n");
263         }
264 }
265
266 //============================================================================
267
268 #define HUNK_SENTINAL   0x1df001ed
269
270 typedef struct
271 {
272         int             sentinal;
273         int             size;           // including sizeof(hunk_t), -1 = not allocated
274         char    name[24];
275 } hunk_t;
276
277 byte    *hunk_base;
278 int             hunk_size;
279
280 int             hunk_low_used;
281 int             hunk_high_used;
282
283 void R_FreeTextures (void);
284
285 /*
286 ==============
287 Hunk_Check
288
289 Run consistancy and sentinal trashing checks
290 ==============
291 */
292 void Hunk_Check (void)
293 {
294         hunk_t  *h;
295         
296         for (h = (hunk_t *)hunk_base ; (byte *)h != hunk_base + hunk_low_used ; )
297         {
298                 if (h->sentinal != HUNK_SENTINAL)
299                         Sys_Error ("Hunk_Check: trashed sentinal");
300                 if (h->size < sizeof(hunk_t) || h->size + (byte *)h - hunk_base > hunk_size)
301                         Sys_Error ("Hunk_Check: bad size");
302                 h = (hunk_t *)((byte *)h+h->size);
303         }
304 }
305
306 /*
307 ==============
308 Hunk_Print
309
310 If "all" is specified, every single allocation is printed.
311 Otherwise, allocations with the same name will be totaled up before printing.
312 ==============
313 */
314 void Hunk_Print (qboolean all)
315 {
316         hunk_t  *h, *next, *endlow, *starthigh, *endhigh;
317         int             count, sum, i;
318         int             totalblocks;
319         char    name[25];
320
321         name[24] = 0;
322         count = 0;
323         sum = 0;
324         totalblocks = 0;
325         
326         h = (hunk_t *)hunk_base;
327         endlow = (hunk_t *)(hunk_base + hunk_low_used);
328         starthigh = (hunk_t *)(hunk_base + hunk_size - hunk_high_used);
329         endhigh = (hunk_t *)(hunk_base + hunk_size);
330
331         Con_Printf ("          :%8i total hunk                 size\n", hunk_size);
332         Con_Printf ("-------------------------\n");
333
334         while (1)
335         {
336         //
337         // skip to the high hunk if done with low hunk
338         //
339                 if ( h == endlow )
340                 {
341                         Con_Printf ("-------------------------\n");
342                         Con_Printf ("          :%8i REMAINING\n", hunk_size - hunk_low_used - hunk_high_used);
343                         Con_Printf ("-------------------------\n");
344                         h = starthigh;
345                 }
346                 
347         //
348         // if totally done, break
349         //
350                 if ( h == endhigh )
351                         break;
352
353         //
354         // run consistancy checks
355         //
356                 if (h->sentinal != HUNK_SENTINAL)
357                         Sys_Error ("Hunk_Check: trashed sentinal");
358                 if (h->size < sizeof(hunk_t) || h->size + (byte *)h - hunk_base > hunk_size)
359                         Sys_Error ("Hunk_Check: bad size");
360                         
361                 next = (hunk_t *)((byte *)h+h->size);
362                 count++;
363                 totalblocks++;
364                 sum += h->size;
365
366         //
367         // print the single block
368         //
369                 // LordHavoc: pad name to full length
370                 for (i = 0;i < 24;i++)
371                 {
372                         if (!h->name[i])
373                                 break;
374                         name[i] = h->name[i];
375                 }
376                 for (;i < 24;i++)
377                         name[i] = ' ';
378                 //memcpy (name, h->name, 24);
379                 if (all)
380                         Con_Printf ("%8p :%8i %s\n",h, h->size, name);
381                         
382         //
383         // print the total
384         //
385                 if (next == endlow || next == endhigh || strncmp(h->name, next->name, 24))
386                 {
387                         if (!all)
388                                 Con_Printf ("          :%8i %s (TOTAL)\n",sum, name);
389                         count = 0;
390                         sum = 0;
391                 }
392
393                 h = next;
394         }
395
396 //      Con_Printf ("-------------------------\n");
397         Con_Printf ("%8i total blocks\n", totalblocks);
398         
399 }
400
401 /*
402 ===================
403 Hunk_AllocName
404 ===================
405 */
406 void *Hunk_AllocName (int size, char *name)
407 {
408         hunk_t  *h;
409         
410 #ifdef PARANOID
411         Hunk_Check ();
412 #endif
413
414         if (size < 0)
415                 Sys_Error ("Hunk_Alloc: bad size: %i", size);
416                 
417         size = sizeof(hunk_t) + ((size+15)&~15);
418         
419         if (hunk_size - hunk_low_used - hunk_high_used < size)
420                 Sys_Error ("Hunk_Alloc: failed on %i bytes (name = %s)",size, name);
421         
422         h = (hunk_t *)(hunk_base + hunk_low_used);
423         hunk_low_used += size;
424
425         Cache_FreeLow (hunk_low_used);
426
427         memset (h, 0, size);
428         
429         h->size = size;
430         h->sentinal = HUNK_SENTINAL;
431         strncpy (h->name, name, 24);
432         
433         return (void *)(h+1);
434 }
435
436 int     Hunk_LowMark (void)
437 {
438         return hunk_low_used;
439 }
440
441 void Hunk_FreeToLowMark (int mark)
442 {
443         if (mark < 0 || mark > hunk_low_used)
444                 Sys_Error ("Hunk_FreeToLowMark: bad mark %i", mark);
445         memset (hunk_base + mark, 0, hunk_low_used - mark);
446         hunk_low_used = mark;
447 }
448
449 int     Hunk_HighMark (void)
450 {
451         return hunk_high_used;
452 }
453
454 void Hunk_FreeToHighMark (int mark)
455 {
456         if (mark < 0 || mark > hunk_high_used)
457                 Sys_Error ("Hunk_FreeToHighMark: bad mark %i", mark);
458         memset (hunk_base + hunk_size - hunk_high_used, 0, hunk_high_used - mark);
459         hunk_high_used = mark;
460 }
461
462
463 /*
464 ===================
465 Hunk_HighAllocName
466 ===================
467 */
468 void *Hunk_HighAllocName (int size, char *name)
469 {
470         hunk_t  *h;
471
472         if (size < 0)
473                 Sys_Error ("Hunk_HighAllocName: bad size: %i", size);
474
475 #ifdef PARANOID
476         Hunk_Check ();
477 #endif
478
479         size = sizeof(hunk_t) + ((size+15)&~15);
480
481         if (hunk_size - hunk_low_used - hunk_high_used < size)
482         {
483                 Con_Printf ("Hunk_HighAlloc: failed on %i bytes\n",size);
484                 return NULL;
485         }
486
487         hunk_high_used += size;
488         Cache_FreeHigh (hunk_high_used);
489
490         h = (hunk_t *)(hunk_base + hunk_size - hunk_high_used);
491
492         memset (h, 0, size);
493         h->size = size;
494         h->sentinal = HUNK_SENTINAL;
495         strncpy (h->name, name, 8);
496
497         return (void *)(h+1);
498 }
499
500 /*
501 ===============================================================================
502
503 CACHE MEMORY
504
505 ===============================================================================
506 */
507
508 typedef struct cache_system_s
509 {
510         int                                             size;           // including this header
511         cache_user_t                    *user;
512         char                                    name[16];
513         struct cache_system_s   *prev, *next;
514         struct cache_system_s   *lru_prev, *lru_next;   // for LRU flushing     
515 } cache_system_t;
516
517 cache_system_t *Cache_TryAlloc (int size, qboolean nobottom);
518
519 cache_system_t  cache_head;
520
521 /*
522 ===========
523 Cache_Move
524 ===========
525 */
526 void Cache_Move ( cache_system_t *c)
527 {
528         cache_system_t          *new;
529
530 // we are clearing up space at the bottom, so only allocate it late
531         new = Cache_TryAlloc (c->size, true);
532         if (new)
533         {
534 //              Con_Printf ("cache_move ok\n");
535
536                 memcpy ( new+1, c+1, c->size - sizeof(cache_system_t) );
537                 new->user = c->user;
538                 memcpy (new->name, c->name, sizeof(new->name));
539                 Cache_Free (c->user);
540                 new->user->data = (void *)(new+1);
541         }
542         else
543         {
544 //              Con_Printf ("cache_move failed\n");
545
546                 Cache_Free (c->user);           // tough luck...
547         }
548 }
549
550 /*
551 ============
552 Cache_FreeLow
553
554 Throw things out until the hunk can be expanded to the given point
555 ============
556 */
557 void Cache_FreeLow (int new_low_hunk)
558 {
559         cache_system_t  *c;
560         
561         while (1)
562         {
563                 c = cache_head.next;
564                 if (c == &cache_head)
565                         return;         // nothing in cache at all
566                 if ((byte *)c >= hunk_base + new_low_hunk)
567                         return;         // there is space to grow the hunk
568                 Cache_Move ( c );       // reclaim the space
569         }
570 }
571
572 /*
573 ============
574 Cache_FreeHigh
575
576 Throw things out until the hunk can be expanded to the given point
577 ============
578 */
579 void Cache_FreeHigh (int new_high_hunk)
580 {
581         cache_system_t  *c, *prev;
582         
583         prev = NULL;
584         while (1)
585         {
586                 c = cache_head.prev;
587                 if (c == &cache_head)
588                         return;         // nothing in cache at all
589                 if ( (byte *)c + c->size <= hunk_base + hunk_size - new_high_hunk)
590                         return;         // there is space to grow the hunk
591                 if (c == prev)
592                         Cache_Free (c->user);   // didn't move out of the way
593                 else
594                 {
595                         Cache_Move (c); // try to move it
596                         prev = c;
597                 }
598         }
599 }
600
601 void Cache_UnlinkLRU (cache_system_t *cs)
602 {
603         if (!cs->lru_next || !cs->lru_prev)
604                 Sys_Error ("Cache_UnlinkLRU: NULL link");
605
606         cs->lru_next->lru_prev = cs->lru_prev;
607         cs->lru_prev->lru_next = cs->lru_next;
608         
609         cs->lru_prev = cs->lru_next = NULL;
610 }
611
612 void Cache_MakeLRU (cache_system_t *cs)
613 {
614         if (cs->lru_next || cs->lru_prev)
615                 Sys_Error ("Cache_MakeLRU: active link");
616
617         cache_head.lru_next->lru_prev = cs;
618         cs->lru_next = cache_head.lru_next;
619         cs->lru_prev = &cache_head;
620         cache_head.lru_next = cs;
621 }
622
623 /*
624 ============
625 Cache_TryAlloc
626
627 Looks for a free block of memory between the high and low hunk marks
628 Size should already include the header and padding
629 ============
630 */
631 cache_system_t *Cache_TryAlloc (int size, qboolean nobottom)
632 {
633         cache_system_t  *cs, *new;
634         
635 // is the cache completely empty?
636
637         if (!nobottom && cache_head.prev == &cache_head)
638         {
639                 if (hunk_size - hunk_high_used - hunk_low_used < size)
640                         Sys_Error ("Cache_TryAlloc: %i is greater then free hunk", size);
641
642                 new = (cache_system_t *) (hunk_base + hunk_low_used);
643                 memset (new, 0, sizeof(*new));
644                 new->size = size;
645
646                 cache_head.prev = cache_head.next = new;
647                 new->prev = new->next = &cache_head;
648                 
649                 Cache_MakeLRU (new);
650                 return new;
651         }
652         
653 // search from the bottom up for space
654
655         new = (cache_system_t *) (hunk_base + hunk_low_used);
656         cs = cache_head.next;
657         
658         do
659         {
660                 if (!nobottom || cs != cache_head.next)
661                 {
662                         if ( (byte *)cs - (byte *)new >= size)
663                         {       // found space
664                                 memset (new, 0, sizeof(*new));
665                                 new->size = size;
666                                 
667                                 new->next = cs;
668                                 new->prev = cs->prev;
669                                 cs->prev->next = new;
670                                 cs->prev = new;
671                                 
672                                 Cache_MakeLRU (new);
673         
674                                 return new;
675                         }
676                 }
677
678         // continue looking             
679                 new = (cache_system_t *)((byte *)cs + cs->size);
680                 cs = cs->next;
681
682         } while (cs != &cache_head);
683         
684 // try to allocate one at the very end
685         if ( hunk_base + hunk_size - hunk_high_used - (byte *)new >= size)
686         {
687                 memset (new, 0, sizeof(*new));
688                 new->size = size;
689                 
690                 new->next = &cache_head;
691                 new->prev = cache_head.prev;
692                 cache_head.prev->next = new;
693                 cache_head.prev = new;
694                 
695                 Cache_MakeLRU (new);
696
697                 return new;
698         }
699         
700         return NULL;            // couldn't allocate
701 }
702
703 /*
704 ============
705 Cache_Flush
706
707 Throw everything out, so new data will be demand cached
708 ============
709 */
710 void Cache_Flush (void)
711 {
712         while (cache_head.next != &cache_head)
713                 Cache_Free ( cache_head.next->user );   // reclaim the space
714 }
715
716
717 /*
718 ============
719 Cache_Print
720
721 ============
722 */
723 void Cache_Print (void)
724 {
725         cache_system_t  *cd;
726
727         for (cd = cache_head.next ; cd != &cache_head ; cd = cd->next)
728         {
729                 Con_Printf ("%8i : %s\n", cd->size, cd->name);
730         }
731 }
732
733 /*
734 ============
735 Cache_Report
736
737 ============
738 */
739 void Cache_Report (void)
740 {
741         Con_DPrintf ("%4.1f megabyte data cache\n", (hunk_size - hunk_high_used - hunk_low_used) / (float)(1024*1024) );
742 }
743
744 /*
745 ============
746 Cache_Compact
747
748 ============
749 */
750 void Cache_Compact (void)
751 {
752 }
753
754 /*
755 ============
756 Cache_Init
757
758 ============
759 */
760 void Cache_Init (void)
761 {
762         cache_head.next = cache_head.prev = &cache_head;
763         cache_head.lru_next = cache_head.lru_prev = &cache_head;
764
765         Cmd_AddCommand ("flush", Cache_Flush);
766 }
767
768 /*
769 ==============
770 Cache_Free
771
772 Frees the memory and removes it from the LRU list
773 ==============
774 */
775 void Cache_Free (cache_user_t *c)
776 {
777         cache_system_t  *cs;
778
779         if (!c->data)
780                 Sys_Error ("Cache_Free: not allocated");
781
782         cs = ((cache_system_t *)c->data) - 1;
783
784         cs->prev->next = cs->next;
785         cs->next->prev = cs->prev;
786         cs->next = cs->prev = NULL;
787
788         c->data = NULL;
789
790         Cache_UnlinkLRU (cs);
791 }
792
793
794
795 /*
796 ==============
797 Cache_Check
798 ==============
799 */
800 void *Cache_Check (cache_user_t *c)
801 {
802         cache_system_t  *cs;
803
804         if (!c->data)
805                 return NULL;
806
807         cs = ((cache_system_t *)c->data) - 1;
808
809 // move to head of LRU
810         Cache_UnlinkLRU (cs);
811         Cache_MakeLRU (cs);
812         
813         return c->data;
814 }
815
816
817 /*
818 ==============
819 Cache_Alloc
820 ==============
821 */
822 void *Cache_Alloc (cache_user_t *c, int size, char *name)
823 {
824         cache_system_t  *cs;
825
826         if (c->data)
827                 Sys_Error ("Cache_Alloc: allready allocated");
828         
829         if (size <= 0)
830                 Sys_Error ("Cache_Alloc: size %i", size);
831
832         size = (size + sizeof(cache_system_t) + 15) & ~15;
833
834 // find memory for it   
835         while (1)
836         {
837                 cs = Cache_TryAlloc (size, false);
838                 if (cs)
839                 {
840                         strncpy (cs->name, name, sizeof(cs->name)-1);
841                         c->data = (void *)(cs+1);
842                         cs->user = c;
843                         break;
844                 }
845         
846         // free the least recently used cahedat
847                 if (cache_head.lru_prev == &cache_head)
848                         Sys_Error ("Cache_Alloc: out of memory");
849                                                                                                         // not enough memory at all
850                 Cache_Free ( cache_head.lru_prev->user );
851         } 
852         
853         return Cache_Check (c);
854 }
855
856 //============================================================================
857
858
859 void HunkList_f(void)
860 {
861         if (Cmd_Argc() == 2)
862                 if (strcmp(Cmd_Argv(1), "all"))
863                         Con_Printf("usage: hunklist [all]\n");
864                 else
865                         Hunk_Print(true);
866         else
867                 Hunk_Print(false);
868 }
869
870 /*
871 ========================
872 Memory_Init
873 ========================
874 */
875 void Memory_Init (void *buf, int size)
876 {
877         int p;
878         int zonesize = DYNAMIC_SIZE;
879
880         hunk_base = buf;
881         hunk_size = size;
882         hunk_low_used = 0;
883         hunk_high_used = 0;
884         
885         Cache_Init ();
886         p = COM_CheckParm ("-zone");
887         if (p)
888         {
889                 if (p < com_argc-1)
890                         zonesize = atoi (com_argv[p+1]) * 1024;
891                 else
892                         Sys_Error ("Memory_Init: you must specify a size in KB after -zone");
893         }
894         mainzone = Hunk_AllocName (zonesize, "zone" );
895         Z_ClearZone (mainzone, zonesize);
896         Cmd_AddCommand ("hunklist", HunkList_f);
897 }
898