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