1 /*
2 zone.c
3
4 (description)
5
6 Copyright (C) 1996-1997 Id Software, Inc.
7
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License
10 as published by the Free Software Foundation; either version 2
11 of the License, or (at your option) any later version.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16
17 See the GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to:
21
22 Free Software Foundation, Inc.
23 59 Temple Place - Suite 330
24 Boston, MA 02111-1307, USA
25
26 */
27 #ifdef HAVE_CONFIG_H
28 # include "config.h"
29 #endif
30
31 #ifdef HAVE_STRING_H
32 # include <string.h>
33 #endif
34 #ifdef HAVE_STRINGS_H
35 # include <strings.h>
36 #endif
37
38 #include <stdarg.h>
39 #include <stdlib.h>
40
41 #include "QF/cmd.h"
42 #include "QF/cvar.h"
43 #include "QF/mathlib.h"
44 #include "QF/qargs.h"
45 #include "QF/sys.h"
46 #include "QF/va.h"
47 #include "QF/zone.h"
48
49 #include "compat.h"
50
51 static void Cache_FreeLow (int new_low_hunk);
52 static void Cache_Profile (void);
53 static qboolean Cache_FreeLRU (void);
54
55 #define ZONEID 0x1d4a11
56 #define HUNK_SENTINAL 0x1df001ed
57
58 #define MINFRAGMENT 64
59
60 /*
61 ZONE MEMORY ALLOCATION
62
63 There is never any space between memblocks, and there will never be two
64 contiguous free memblocks.
65
66 The rover can be left pointing at a non-empty block
67
68 The zone calls are pretty much used only for small strings and structures,
69 all big things are allocated on the hunk.
70 */
71
72 typedef struct memblock_s
73 {
74 int block_size; // including the header and possibly tiny fragments
75 int tag; // a tag of 0 is a free block
76 struct memblock_s *next, *prev;
77 int size; // requested size
78 int id; // should be ZONEID
79 //int id2; // pad to 64 bit boundary
80 } memblock_t;
81
82 struct memzone_s
83 {
84 int size; // total bytes malloced, including header
85 int used; // ammount used, including header
86 int offset;
87 int ele_size;
88 void (*error) (void *, const char *);
89 void *data;
90 memblock_t blocklist; // start / end cap for linked list
91 memblock_t *rover;
92 };
93
94
95 static int
z_block_size(memblock_t * block)96 z_block_size (memblock_t *block)
97 {
98 return block->block_size - sizeof (memblock_t) - 4;
99 }
100
101 static int
z_offset(memzone_t * zone,memblock_t * block)102 z_offset (memzone_t *zone, memblock_t *block)
103 {
104 int offset = ((byte *) (block + 1) - (byte *) zone);
105
106 return offset / zone->ele_size + zone->offset;
107 }
108
109 VISIBLE void
Z_ClearZone(memzone_t * zone,int size,int zone_offset,int ele_size)110 Z_ClearZone (memzone_t *zone, int size, int zone_offset, int ele_size)
111 {
112 memblock_t *block;
113
114 // set the entire zone to one free block
115
116 block = (memblock_t *) (zone + 1);
117 zone->blocklist.next = block;
118 zone->blocklist.prev = block;
119 zone->blocklist.tag = 1; // in use block
120 zone->blocklist.id = 0;
121 zone->blocklist.block_size = 0;
122 zone->blocklist.size = 0;
123 zone->offset = zone_offset;
124 zone->ele_size = ele_size;
125 zone->rover = block;
126 zone->size = size;
127 zone->used = sizeof (memzone_t);
128 zone->error = 0;
129 zone->data = 0;
130
131 block->prev = block->next = &zone->blocklist;
132 block->tag = 0; // free block
133 block->id = ZONEID;
134 //block->id2 = ZONEID;
135 block->block_size = size - sizeof (memzone_t);
136 block->size = 0;
137 }
138
139 VISIBLE void
Z_Free(memzone_t * zone,void * ptr)140 Z_Free (memzone_t *zone, void *ptr)
141 {
142 memblock_t *block, *other;
143
144 if (!ptr) {
145 if (zone->error)
146 zone->error (zone->data, "Z_Free: NULL pointer");
147 Sys_Error ("Z_Free: NULL pointer");
148 }
149
150 block = (memblock_t *) ((byte *) ptr - sizeof (memblock_t));
151 if (((byte *) block < (byte *) zone)
152 || (((byte *) block) >= (byte *) zone + zone->size)) {
153 const char *msg;
154 msg = nva ("Z_Free: freed a pointer outside of the zone: %x",
155 z_offset (zone, block));
156 if (zone->error)
157 zone->error (zone->data, msg);
158 Sys_Error ("%s", msg);
159 }
160 if (block->id != ZONEID/* || block->id2 != ZONEID*/) {
161 const char *msg;
162 msg = nva ("bad pointer %x", z_offset (zone, block));
163 Sys_Printf ("%s\n", msg);
164 Z_Print (zone);
165 fflush (stdout);
166 if (zone->error)
167 zone->error (zone->data, msg);
168 Sys_Error ("Z_Free: freed a pointer without ZONEID");
169 }
170 if (block->tag == 0) {
171 if (zone->error)
172 zone->error (zone->data, "Z_Free: freed a freed pointer");
173 Sys_Error ("Z_Free: freed a freed pointer");
174 }
175
176 block->tag = 0; // mark as free
177 block->size = 0;
178 zone->used -= block->block_size;
179
180 other = block->prev;
181 if (!other->tag) {
182 // merge with previous free block
183 other->block_size += block->block_size;
184 other->next = block->next;
185 other->next->prev = other;
186 if (block == zone->rover)
187 zone->rover = other;
188 block = other;
189 }
190
191 other = block->next;
192 if (!other->tag) {
193 // merge the next free block onto the end
194 block->block_size += other->block_size;
195 block->next = other->next;
196 block->next->prev = block;
197 if (other == zone->rover)
198 zone->rover = block;
199 }
200 }
201
202 VISIBLE void *
Z_Malloc(memzone_t * zone,int size)203 Z_Malloc (memzone_t *zone, int size)
204 {
205 void *buf;
206
207 if (!developer || developer->int_val & SYS_DEV)
208 Z_CheckHeap (zone); // DEBUG
209 buf = Z_TagMalloc (zone, size, 1);
210 if (!buf) {
211 const char *msg;
212 msg = nva ("Z_Malloc: failed on allocation of %i bytes",size);
213 if (zone->error)
214 zone->error (zone->data, msg);
215 Sys_Error ("%s", msg);
216 }
217 memset (buf, 0, size);
218
219 return buf;
220 }
221
222 void *
Z_TagMalloc(memzone_t * zone,int size,int tag)223 Z_TagMalloc (memzone_t *zone, int size, int tag)
224 {
225 int extra;
226 int requested_size = size;
227 memblock_t *start, *rover, *new, *base;
228
229 if (!tag) {
230 if (zone->error)
231 zone->error (zone->data, "Z_TagMalloc: tried to use a 0 tag");
232 Sys_Error ("Z_TagMalloc: tried to use a 0 tag");
233 }
234
235 // scan through the block list looking for the first free block
236 // of sufficient size
237 size += sizeof (memblock_t); // account for size of block header
238 size += 4; // space for memory trash tester
239 size = (size + 7) & ~7; // align to 8-byte boundary
240
241 base = rover = zone->rover;
242 start = base->prev;
243
244 do {
245 if (rover == start) // scaned all the way around the list
246 return NULL;
247 if (rover->tag)
248 base = rover = rover->next;
249 else
250 rover = rover->next;
251 } while (base->tag || base->block_size < size);
252
253 // found a block big enough
254 extra = base->block_size - size;
255 if (extra > MINFRAGMENT) {
256 // there will be a free fragment after the allocated block
257 new = (memblock_t *) ((byte *) base + size);
258 new->block_size = extra;
259 new->tag = 0; // free block
260 new->prev = base;
261 new->id = ZONEID;
262 //new->id2 = ZONEID;
263 new->next = base->next;
264 new->next->prev = new;
265 base->next = new;
266 base->block_size = size;
267 }
268
269 base->tag = tag; // no longer a free block
270 base->size = requested_size;
271
272 zone->rover = base->next; // next allocation will start looking here
273
274 base->id = ZONEID;
275 //base->id2 = ZONEID;
276
277 zone->used += base->block_size;
278
279 // marker for memory trash testing
280 *(int *) ((byte *) base + base->block_size - 4) = ZONEID;
281
282 return (void *) (base + 1);
283 }
284
285 VISIBLE void *
Z_Realloc(memzone_t * zone,void * ptr,int size)286 Z_Realloc (memzone_t *zone, void *ptr, int size)
287 {
288 int old_size;
289 memblock_t *block;
290 void *old_ptr;
291
292 if (!ptr)
293 return Z_Malloc (zone, size);
294
295 block = (memblock_t *) ((byte *) ptr - sizeof (memblock_t));
296 if (block->id != ZONEID/* || block->id2 != ZONEID*/) {
297 if (zone->error)
298 zone->error (zone->data,
299 "Z_Realloc: realloced a pointer without ZONEID");
300 Sys_Error ("Z_Realloc: realloced a pointer without ZONEID");
301 }
302 if (block->tag == 0) {
303 if (zone->error)
304 zone->error (zone->data, "Z_Realloc: realloced a freed pointer");
305 Sys_Error ("Z_Realloc: realloced a freed pointer");
306 }
307
308 old_size = block->block_size;
309 old_size -= sizeof (memblock_t); // account for size of block header
310 old_size -= 4; // space for memory trash tester
311 old_ptr = ptr;
312
313 Z_Free (zone, ptr);
314 ptr = Z_TagMalloc (zone, size, 1);
315 if (!ptr) {
316 const char *msg;
317 msg = nva ("Z_Realloc: failed on allocation of %i bytes", size);
318 if (zone->error)
319 zone->error (zone->data, msg);
320 Sys_Error ("%s", msg);
321 }
322
323 if (ptr != old_ptr)
324 memmove (ptr, old_ptr, min (old_size, size));
325 if (old_size < size)
326 memset ((byte *)ptr + old_size, 0, size - old_size);
327
328 return ptr;
329 }
330
331 void
Z_Print(memzone_t * zone)332 Z_Print (memzone_t *zone)
333 {
334 memblock_t *block;
335
336 Sys_Printf ("zone size: %i location: %p used: %i\n",
337 zone->size, zone, zone->used);
338
339 for (block = zone->blocklist.next ; ; block = block->next) {
340 Sys_Printf ("block:%p size:%7i tag:%3i ofs:%x\n",
341 block, z_block_size (block),
342 block->tag, z_offset (zone, block));
343
344 if (block->next == &zone->blocklist)
345 break; // all blocks have been hit
346 if (block->id != ZONEID/* || block->id2 != ZONEID*/)
347 Sys_Printf ("ERROR: block ids incorrect\n");
348 if ((byte *) block + block->block_size != (byte *) block->next)
349 Sys_Printf ("ERROR: block size does not touch the next block\n");
350 if (block->next->prev != block)
351 Sys_Printf ("ERROR: next block doesn't have proper back link\n");
352 if (!block->tag && !block->next->tag)
353 Sys_Printf ("ERROR: two consecutive free blocks\n");
354 if (block->tag
355 && (*(int *) ((byte *) block + block->block_size - 4) != ZONEID))
356 Sys_Printf ("ERROR: memory trashed in block\n");
357 fflush (stdout);
358 }
359 }
360
361 void
Z_CheckHeap(memzone_t * zone)362 Z_CheckHeap (memzone_t *zone)
363 {
364 memblock_t *block;
365
366 for (block = zone->blocklist.next ; ; block = block->next) {
367 if (block->next == &zone->blocklist)
368 break; // all blocks have been hit
369 if ((byte *) block + block->block_size != (byte *) block->next)
370 Sys_Error ("Z_CheckHeap: block size does not touch the next "
371 "block\n");
372 if (block->next->prev != block)
373 Sys_Error ("Z_CheckHeap: next block doesn't have proper back "
374 "link\n");
375 if (!block->tag && !block->next->tag)
376 Sys_Error ("Z_CheckHeap: two consecutive free blocks\n");
377 }
378 }
379
380 VISIBLE void
Z_SetError(memzone_t * zone,void (* err)(void *,const char *),void * data)381 Z_SetError (memzone_t *zone, void (*err) (void *, const char *), void *data)
382 {
383 zone->error = err;
384 zone->data = data;
385 }
386
387 void
Z_CheckPointer(const memzone_t * zone,const void * ptr,int size)388 Z_CheckPointer (const memzone_t *zone, const void *ptr, int size)
389 {
390 const memblock_t *block;
391 const char *block_mem;
392 const char *check = (char *) ptr;
393
394 for (block = zone->blocklist.next ; ; block = block->next) {
395 if (block->next == &zone->blocklist)
396 break; // all blocks have been hit
397 if (check < (const char *) block
398 || check >= (const char *) block + block->block_size)
399 continue;
400 // a block that overlaps with the memory region has been found
401 if (!block->tag)
402 zone->error (zone->data, "invalid access to unallocated memory");
403 block_mem = (char *) &block[1];
404 if (check < block_mem || check + size > block_mem + block->size)
405 zone->error (zone->data, "invalid access to allocated memory");
406 return; // access ok
407 }
408 }
409
410 //============================================================================
411
412 typedef struct {
413 int sentinal;
414 int size; // including sizeof(hunk_t), -1 = not allocated
415 char name[8];
416 } hunk_t;
417
418 byte *hunk_base;
419 int hunk_size;
420 int hunk_low_used;
421 int hunk_high_used;
422 int hunk_tempmark;
423
424 qboolean hunk_tempactive;
425
426 /*
427 Hunk_Check
428
429 Run consistancy and sentinal trahing checks
430 */
431 VISIBLE void
Hunk_Check(void)432 Hunk_Check (void)
433 {
434 hunk_t *h;
435
436 for (h = (hunk_t *) hunk_base; (byte *) h != hunk_base + hunk_low_used;) {
437 if (h->sentinal != HUNK_SENTINAL)
438 Sys_Error ("Hunk_Check: trashed sentinal");
439 if (h->size < 16 || h->size + (byte *) h - hunk_base > hunk_size)
440 Sys_Error ("Hunk_Check: bad size");
441 h = (hunk_t *) ((byte *) h + h->size);
442 }
443 }
444
445 /*
446 Hunk_Print
447
448 If "all" is specified, every single allocation is printed.
449 Otherwise, allocations with the same name will be totaled up before
450 printing.
451 */
452 /*
453 static void
454 Hunk_Print (qboolean all)
455 {
456 char name[9];
457 hunk_t *h, *next, *endlow, *starthigh, *endhigh;
458 int count, sum, totalblocks;
459
460 name[8] = 0;
461 count = 0;
462 sum = 0;
463 totalblocks = 0;
464
465 h = (hunk_t *) hunk_base;
466 endlow = (hunk_t *) (hunk_base + hunk_low_used);
467 starthigh = (hunk_t *) (hunk_base + hunk_size - hunk_high_used);
468 endhigh = (hunk_t *) (hunk_base + hunk_size);
469
470 Sys_Printf (" :%8i total hunk size\n", hunk_size);
471 Sys_Printf ("-------------------------\n");
472
473 while (1) {
474 // skip to the high hunk if done with low hunk
475 if (h == endlow) {
476 Sys_Printf ("-------------------------\n");
477 Sys_Printf (" :%8i REMAINING\n",
478 hunk_size - hunk_low_used - hunk_high_used);
479 Sys_Printf ("-------------------------\n");
480 h = starthigh;
481 }
482 // if totally done, break
483 if (h == endhigh)
484 break;
485
486 // run consistancy checks
487 if (h->sentinal != HUNK_SENTINAL)
488 Sys_Error ("Hunk_Check: trahsed sentinal");
489 if (h->size < 16 || h->size + (byte *) h - hunk_base > hunk_size)
490 Sys_Error ("Hunk_Check: bad size");
491
492 next = (hunk_t *) ((byte *) h + h->size);
493 count++;
494 totalblocks++;
495 sum += h->size;
496
497 // print the single block
498 memcpy (name, h->name, 8);
499 if (all)
500 Sys_Printf ("%8p :%8i %8s\n", h, h->size, name);
501
502 // print the total
503 if (next == endlow || next == endhigh ||
504 strncmp (h->name, next->name, 8)) {
505 if (!all)
506 Sys_Printf (" :%8i %8s (TOTAL)\n", sum, name);
507 count = 0;
508 sum = 0;
509 }
510
511 h = next;
512 }
513
514 Sys_Printf ("-------------------------\n");
515 Sys_Printf ("%8i total blocks\n", totalblocks);
516 }
517 */
518 static void
Hunk_FreeToHighMark(int mark)519 Hunk_FreeToHighMark (int mark)
520 {
521 if (hunk_tempactive) {
522 hunk_tempactive = false;
523 Hunk_FreeToHighMark (hunk_tempmark);
524 }
525 if (mark < 0 || mark > hunk_high_used)
526 Sys_Error ("Hunk_FreeToHighMark: bad mark %i", mark);
527 memset (hunk_base + hunk_size - hunk_high_used, 0, hunk_high_used - mark);
528 hunk_high_used = mark;
529 }
530
531 static int
Hunk_HighMark(void)532 Hunk_HighMark (void)
533 {
534 if (hunk_tempactive) {
535 hunk_tempactive = false;
536 Hunk_FreeToHighMark (hunk_tempmark);
537 }
538
539 return hunk_high_used;
540 }
541
542 VISIBLE void *
Hunk_AllocName(int size,const char * name)543 Hunk_AllocName (int size, const char *name)
544 {
545 hunk_t *h;
546
547 #ifdef PARANOID
548 Hunk_Check ();
549 #endif
550
551 if (size < 0)
552 Sys_Error ("Hunk_Alloc: bad size: %i", size);
553
554 size = sizeof (hunk_t) + ((size + 15) & ~15);
555
556 if (hunk_size - hunk_low_used - hunk_high_used < size) {
557 Hunk_HighMark();
558 Cache_FreeLRU ();
559 }
560 if (hunk_size - hunk_low_used - hunk_high_used < size) {
561 int mem = hunk_size / (1024 * 1024);
562 mem += 8;
563 mem &= ~7;
564 Cache_Profile ();
565 Sys_Error
566 ("Not enough RAM allocated. Try starting using \"-mem %d\" on "
567 "the %s command line. (%d - %d - %d < %d)", mem,
568 PACKAGE_NAME, hunk_size, hunk_low_used, hunk_high_used, size);
569 }
570
571 h = (hunk_t *) (hunk_base + hunk_low_used);
572 hunk_low_used += size;
573
574 Cache_FreeLow (hunk_low_used);
575
576 memset (h, 0, size);
577
578 h->size = size;
579 h->sentinal = HUNK_SENTINAL;
580 strncpy (h->name, name, 8);
581
582 return (void *) (h + 1);
583 }
584
585 VISIBLE void *
Hunk_Alloc(int size)586 Hunk_Alloc (int size)
587 {
588 return Hunk_AllocName (size, "unknown");
589 }
590
591 VISIBLE int
Hunk_LowMark(void)592 Hunk_LowMark (void)
593 {
594 return hunk_low_used;
595 }
596
597 VISIBLE void
Hunk_FreeToLowMark(int mark)598 Hunk_FreeToLowMark (int mark)
599 {
600 if (mark < 0 || mark > hunk_low_used)
601 Sys_Error ("Hunk_FreeToLowMark: bad mark %i", mark);
602 memset (hunk_base + mark, 0, hunk_low_used - mark);
603 hunk_low_used = mark;
604 }
605
606 static void *
Hunk_HighAlloc(int size)607 Hunk_HighAlloc (int size)
608 {
609 if (size < 0)
610 Sys_Error ("Hunk_HighAlloc: bad size: %i", size);
611
612 if (hunk_tempactive) {
613 Hunk_FreeToHighMark (hunk_tempmark);
614 hunk_tempactive = false;
615 }
616 #ifdef PARANOID
617 Hunk_Check ();
618 #endif
619
620 size = ((size + 15) & ~15);
621
622 if (hunk_size - hunk_low_used - hunk_high_used < size) {
623 Sys_Printf ("Hunk_HighAlloc: failed on %i bytes\n", size);
624 return NULL;
625 }
626
627 hunk_high_used += size;
628
629 return (void *) (hunk_base + hunk_size - hunk_high_used);
630 }
631
632 /*
633 Hunk_TempAlloc
634
635 Return space from the top of the hunk
636 */
637 VISIBLE void *
Hunk_TempAlloc(int size)638 Hunk_TempAlloc (int size)
639 {
640 void *buf;
641
642 size = (size + 15) & ~15;
643
644 if (hunk_tempactive) {
645 if (hunk_high_used - hunk_tempmark >= size + (int) sizeof (hunk_t)) {
646 return (hunk_t *) (hunk_base + hunk_size - hunk_high_used) + 1;
647 }
648 Hunk_FreeToHighMark (hunk_tempmark);
649 hunk_tempactive = false;
650 }
651
652 hunk_tempmark = Hunk_HighMark ();
653
654 buf = Hunk_HighAlloc (size);
655
656 hunk_tempactive = true;
657
658 return buf;
659 }
660
661 /* CACHE MEMORY */
662
663 typedef struct cache_system_s cache_system_t;
664 struct cache_system_s {
665 cache_system_t *prev, *next;
666 cache_system_t *lru_prev, *lru_next; // for LRU flushing
667 char name[16];
668 size_t size; // including this header
669 int readlock;
670 cache_user_t *user;
671 };
672
673 static cache_system_t cache_head;
674
675 static cache_system_t *Cache_TryAlloc (size_t size, qboolean nobottom);
676 #if 0
677 static void
678 check_cache (void)
679 {
680 cache_system_t *cs;
681 int used = hunk_tempactive ? hunk_tempmark : hunk_high_used;
682
683 for (cs = cache_head.prev; cs != &cache_head; cs = cs->prev)
684 if (cs->prev != &cache_head) {
685 if ((byte *) cs + cs->size != (byte *) cs->prev)
686 Sys_Error ("inconsistent cache %p %p %d %d", cs, cs->prev,
687 (int)cs->size,
688 (int) ((char *)cs->prev - (char *)cs));
689 if (hunk_size - ((byte*)cs - hunk_base) > used)
690 Sys_Error ("cache block out of high hunk");
691 }
692 if (cache_head.prev != &cache_head &&
693 hunk_size - ((byte*) cache_head.prev - hunk_base) != used)
694 Sys_Error ("cache bottom not at bottom of high hunk");
695 }
696 #endif
697 static void
Cache_Move(cache_system_t * c)698 Cache_Move (cache_system_t * c)
699 {
700 cache_system_t *new;
701
702 // we are clearing up space at the bottom, so allocate it late
703 new = Cache_TryAlloc (c->size, true);
704 if (new) {
705 Sys_MaskPrintf (SYS_DEV, "cache_move ok\n");
706
707 memcpy (new + 1, c + 1, c->size - sizeof (cache_system_t));
708 new->user = c->user;
709 memcpy (new->name, c->name, sizeof (new->name));
710 Cache_Free (c->user);
711 new->user->data = (void *) (new + 1);
712 } else {
713 Sys_MaskPrintf (SYS_DEV, "cache_move failed\n");
714
715 Cache_Free (c->user); // tough luck...
716 }
717 }
718
719 /*
720 Cache_FreeLow
721
722 Throw things out until the hunk can be expanded to the given point
723 */
724 static void
Cache_FreeLow(int new_low_hunk)725 Cache_FreeLow (int new_low_hunk)
726 {
727 cache_system_t *c;
728
729 while (1) {
730 c = cache_head.prev;
731 if (c == &cache_head)
732 return; // nothing in cache at all
733 if ((byte *) c >= hunk_base + new_low_hunk)
734 return; // there is space to grow the hunk
735 Sys_Error ("FIXME: Cache_FreeLow: not enough memory");
736 Cache_Move (c); // reclaim the space
737 }
738 }
739
740 static inline void
Cache_UnlinkLRU(cache_system_t * cs)741 Cache_UnlinkLRU (cache_system_t * cs)
742 {
743 if (!cs->lru_next || !cs->lru_prev)
744 Sys_Error ("Cache_UnlinkLRU: NULL link: %s %p %p",
745 cs->name, cs->lru_next, cs->lru_prev);
746
747 cs->lru_next->lru_prev = cs->lru_prev;
748 cs->lru_prev->lru_next = cs->lru_next;
749
750 cs->lru_prev = cs->lru_next = NULL;
751 }
752
753 static void
Cache_MakeLRU(cache_system_t * cs)754 Cache_MakeLRU (cache_system_t * cs)
755 {
756 if (cs->lru_next || cs->lru_prev)
757 Sys_Error ("Cache_MakeLRU: active link: %s %p %p",
758 cs->name, cs->lru_next, cs->lru_prev);
759
760 cache_head.lru_next->lru_prev = cs;
761 cs->lru_next = cache_head.lru_next;
762 cs->lru_prev = &cache_head;
763 cache_head.lru_next = cs;
764 }
765
766 static qboolean
Cache_FreeLRU(void)767 Cache_FreeLRU (void)
768 {
769 cache_system_t *cs;
770
771 //check_cache ();
772 for (cs = cache_head.lru_prev;
773 cs != &cache_head && cs->readlock; cs = cs->lru_prev)
774 ;
775 if (cs == &cache_head)
776 return 0;
777 Cache_Free (cs->user);
778 return 1;
779 }
780
781 static void
link_cache_system(cache_system_t * new,cache_system_t * cs)782 link_cache_system (cache_system_t *new, cache_system_t *cs)
783 {
784 new->next = cs;
785 new->prev = cs->prev;
786 cs->prev->next = new;
787 cs->prev = new;
788
789 }
790
791 /*
792 Cache_TryAlloc
793
794 Looks for a free block of memory between the high and low hunk marks
795 Size should already include the header and padding
796 */
797 static cache_system_t *
Cache_TryAlloc(size_t size,qboolean nobottom)798 Cache_TryAlloc (size_t size, qboolean nobottom)
799 {
800 cache_system_t *cs, *new;
801
802 //check_cache ();
803 // is the cache completely empty?
804 if (!nobottom && cache_head.prev == &cache_head) {
805 new = (cache_system_t *) Hunk_HighAlloc (size);
806 if (!new)
807 return 0;
808 memset (new, 0, size);
809 new->size = size;
810 cache_head.prev = cache_head.next = new;
811 new->prev = new->next = &cache_head;
812 Cache_MakeLRU (new);
813 //check_cache ();
814 return new;
815 }
816
817 // search for space in existing cache
818 for (cs = cache_head.next; cs != &cache_head; cs = cs->next) {
819 if (cs->user)
820 continue; // block isn't free
821 if (cs->size >= size) {
822 // found a big enough free block. If possible, carve it up for
823 // later reuse, using the upper portion of the block for the
824 // newly allocated block.
825 new = cs;
826 if (size - cs->size >= sizeof (cache_system_t)) {
827 new = (cache_system_t *) ((char *) cs + cs->size - size);
828 memset (new, 0, size);
829 new->size = size;
830 cs->size -= size;
831 link_cache_system (new, cs);
832 //check_cache ();
833 }
834 Cache_MakeLRU (new);
835 return new;
836 }
837 }
838
839 if (nobottom)
840 return 0;
841
842 // didn't find a free block, so make a new one.
843 new = Hunk_HighAlloc (size);
844 if (new) {
845 memset (new, 0, size);
846 new->size = size;
847 link_cache_system (new, &cache_head);
848 Cache_MakeLRU (new);
849 //check_cache ();
850 return new;
851 }
852
853 return 0; // couldn't allocate
854 }
855
856 static void
Cache_Profile(void)857 Cache_Profile (void)
858 {
859 cache_system_t *cs;
860 unsigned int i;
861 unsigned int items[31] = {0}, sizes[31] = {0};
862 int count = 0, total = 0;
863
864 cs = cache_head.next;
865 while (cs != &cache_head) {
866 for (i = 0; (cs->size >> (i + 1)) && i < 30; i++)
867 ;
868 items[i]++;
869 sizes[i] += cs->size;
870 total += cs->size;
871 count++;
872 cs = cs->next;
873 }
874 Sys_Printf ("Cache Profile:\n");
875 Sys_Printf ("%8s %8s %8s %8s %8s\n",
876 "count", "min", "max", "average", "percent");
877 for (i = 0; i < 31; i++) {
878 if (!items[i])
879 continue;
880 Sys_Printf ("%8d %8d %8d %8d %7d%%\n",
881 items[i], 1 << i, (1 << (i + 1)) - 1,
882 sizes[i] / items[i],
883 (sizes[i] * 100) / total);
884 }
885 Sys_Printf ("Total allocations: %d in %d allocations, average of"
886 " %d per allocation\n", total, count,
887 count ? total / count : -1);
888 }
889
890 static void
Cache_Print(void)891 Cache_Print (void)
892 {
893 cache_system_t *cd;
894
895 for (cd = cache_head.next; cd != &cache_head; cd = cd->next) {
896 Sys_Printf ("%8d : %s\n", (int) cd->size, cd->name);
897 }
898 }
899
900 static void
Cache_Init(void)901 Cache_Init (void)
902 {
903 cache_head.next = cache_head.prev = &cache_head;
904 cache_head.lru_next = cache_head.lru_prev = &cache_head;
905 cache_head.user = (cache_user_t *) 1; // make it look allocated
906 cache_head.readlock = 1; // don't try to free or move it
907
908 Cmd_AddCommand ("cache_flush", Cache_Flush, "Clears the current game "
909 "cache");
910 Cmd_AddCommand ("cache_profile", Cache_Profile, "Prints a profile of "
911 "the current cache");
912 Cmd_AddCommand ("cache_print", Cache_Print, "Prints out items in the "
913 "cache");
914 }
915
916 /*
917 Cache_Flush
918
919 Throw everything out, so new data will be demand cached
920 */
921 void
Cache_Flush(void)922 Cache_Flush (void)
923 {
924 // cache_head.prev is guaranteed to not be free because it's the bottom
925 // one and Cache_Free actually properly releases it
926 while (cache_head.prev != &cache_head) {
927 if (!cache_head.prev->user->data)
928 Sys_Error ("Cache_Flush: user/system out of sync for "
929 "'%s' with %d size",
930 cache_head.prev->name, (int) cache_head.prev->size);
931 Cache_Free (cache_head.prev->user); // reclaim the space
932 }
933 }
934
935 VISIBLE void *
Cache_Check(cache_user_t * c)936 Cache_Check (cache_user_t *c)
937 {
938 cache_system_t *cs;
939
940 if (!c->data)
941 return NULL;
942
943 cs = ((cache_system_t *) c->data) - 1;
944
945 // move to head of LRU
946 Cache_UnlinkLRU (cs);
947 Cache_MakeLRU (cs);
948
949 return c->data;
950 }
951
952 /*
953 Cache_Free
954
955 Frees the memory and removes it from the LRU list
956 */
957 VISIBLE void
Cache_Free(cache_user_t * c)958 Cache_Free (cache_user_t *c)
959 {
960 cache_system_t *cs;
961
962 if (!c->data)
963 Sys_Error ("Cache_Free: not allocated");
964
965 cs = ((cache_system_t *) c->data) - 1;
966
967 if (cs->readlock)
968 Sys_Error ("Cache_Free: attempt to free locked block");
969
970 Sys_MaskPrintf (SYS_DEV, "Cache_Free: freeing '%s' %p\n", cs->name, cs);
971
972 Cache_UnlinkLRU (cs);
973
974 //check_cache ();
975 cs->user = 0;
976 if (!cs->prev->user) {
977 cs->size += cs->prev->size;
978 cs->prev->prev->next = cs;
979 cs->prev = cs->prev->prev;
980 }
981 if (!cs->next->user) {
982 cs = cs->next;
983 cs->size += cs->prev->size;
984 cs->prev->prev->next = cs;
985 cs->prev = cs->prev->prev;
986 }
987 if (cs->next == &cache_head) {
988 cs->next->prev = cs->prev;
989 cs->prev->next = cs->next;
990 if (cs->prev != &cache_head)
991 Hunk_FreeToHighMark (hunk_size - ((byte*)cs->prev - hunk_base));
992 else
993 Hunk_FreeToHighMark (0);
994 }
995 //check_cache ();
996
997 c->data = NULL;
998 }
999
1000 VISIBLE void *
Cache_Alloc(cache_user_t * c,int size,const char * name)1001 Cache_Alloc (cache_user_t *c, int size, const char *name)
1002 {
1003 cache_system_t *cs;
1004
1005 if (c->data)
1006 Sys_Error ("Cache_Alloc: already allocated");
1007
1008 if (size <= 0)
1009 Sys_Error ("Cache_Alloc: size %i", size);
1010
1011 size = (size + sizeof (cache_system_t) + 15) & ~15;
1012
1013 // find memory for it
1014 while (1) {
1015 cs = Cache_TryAlloc (size, false);
1016 if (cs) {
1017 strncpy (cs->name, name, sizeof (cs->name) - 1);
1018 c->data = (void *) (cs + 1);
1019 cs->user = c;
1020 break;
1021 }
1022 // free the least recently used cachedat
1023 if (!Cache_FreeLRU())
1024 Sys_Error ("Cache_Alloc: out of memory");
1025 }
1026
1027 return Cache_Check (c);
1028 }
1029
1030 VISIBLE void
Cache_Report(void)1031 Cache_Report (void)
1032 {
1033 Sys_MaskPrintf (SYS_DEV, "%4.1f megabyte data cache\n",
1034 (hunk_size - hunk_high_used -
1035 hunk_low_used) / (float) (1024 * 1024));
1036 }
1037
1038 VISIBLE void
Cache_Add(cache_user_t * c,void * object,cache_loader_t loader)1039 Cache_Add (cache_user_t *c, void *object, cache_loader_t loader)
1040 {
1041 if (c->data || c->object || c->loader)
1042 Sys_Error ("Cache_Add: cache item already exists!");
1043
1044 c->object = object;
1045 c->loader = loader;
1046
1047 // c->loader (c, Cache_Alloc); // for debugging
1048 }
1049
1050 VISIBLE void
Cache_Remove(cache_user_t * c)1051 Cache_Remove (cache_user_t *c)
1052 {
1053 if (!c->object || !c->loader)
1054 Sys_Error ("Cache_Remove: already removed!");
1055
1056 if (Cache_Check (c))
1057 Cache_Free (c);
1058
1059 c->object = 0;
1060 c->loader = 0;
1061 }
1062
1063 VISIBLE void *
Cache_TryGet(cache_user_t * c)1064 Cache_TryGet (cache_user_t *c)
1065 {
1066 void *mem;
1067
1068 mem = Cache_Check (c);
1069 if (!mem) {
1070 c->loader (c->object, Cache_Alloc);
1071 mem = Cache_Check (c);
1072 }
1073 if (mem)
1074 (((cache_system_t *)c->data) - 1)->readlock++;
1075
1076 return mem;
1077 }
1078
1079 VISIBLE void *
Cache_Get(cache_user_t * c)1080 Cache_Get (cache_user_t *c)
1081 {
1082 void *mem = Cache_TryGet (c);
1083
1084 if (!mem)
1085 Sys_Error ("Cache_Get: couldn't get cache!");
1086 return mem;
1087 }
1088
1089 VISIBLE void
Cache_Release(cache_user_t * c)1090 Cache_Release (cache_user_t *c)
1091 {
1092 int *readlock;
1093
1094 readlock = &(((cache_system_t *)c->data) - 1)->readlock;
1095
1096 if (!*readlock)
1097 Sys_Error ("Cache_Release: already released!");
1098
1099 (*readlock)--;
1100
1101 // if (!*readlock)
1102 // Cache_Free (c); // for debugging
1103 }
1104
1105 VISIBLE int
Cache_ReadLock(cache_user_t * c)1106 Cache_ReadLock (cache_user_t *c)
1107 {
1108 return (((cache_system_t *)c->data) - 1)->readlock;
1109 }
1110
1111
1112 //============================================================================
1113
1114 VISIBLE void
Memory_Init(void * buf,int size)1115 Memory_Init (void *buf, int size)
1116 {
1117 hunk_base = buf;
1118 hunk_size = size;
1119 hunk_low_used = 0;
1120 hunk_high_used = 0;
1121
1122 Cache_Init ();
1123 }
1124