improved error reporting on memory functions
[divverent/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 mempool_t *poolchain = NULL;
25
26 void *_Mem_Alloc(mempool_t *pool, int size, char *filename, int fileline)
27 {
28         int i, j, k, needed, endbit, largest;
29         memclump_t *clump, **clumpchainpointer;
30         memheader_t *mem;
31         if (size <= 0)
32                 return NULL;
33         if (pool == NULL)
34                 Sys_Error("Mem_Alloc: pool == NULL (alloc at %s:%i)", filename, fileline);
35         pool->totalsize += size;
36         if (size < 4096)
37         {
38                 // clumping
39                 needed = (sizeof(memheader_t) + size + sizeof(int) + (MEMUNIT - 1)) / MEMUNIT;
40                 endbit = MEMBITS - needed;
41                 for (clumpchainpointer = &pool->clumpchain;*clumpchainpointer;clumpchainpointer = &(*clumpchainpointer)->chain)
42                 {
43                         clump = *clumpchainpointer;
44                         if (clump->sentinel1 != MEMCLUMP_SENTINEL)
45                                 Sys_Error("Mem_Alloc: trashed clump sentinel 1 (alloc at %s:%d)", filename, fileline);
46                         if (clump->sentinel2 != MEMCLUMP_SENTINEL)
47                                 Sys_Error("Mem_Alloc: trashed clump sentinel 2 (alloc at %s:%d)", filename, fileline);
48                         if (clump->largestavailable >= needed)
49                         {
50                                 largest = 0;
51                                 for (i = 0;i < endbit;i++)
52                                 {
53                                         if (clump->bits[i >> 5] & (1 << (i & 31)))
54                                                 continue;
55                                         k = i + needed;
56                                         for (j = i;i < k;i++)
57                                                 if (clump->bits[i >> 5] & (1 << (i & 31)))
58                                                         goto loopcontinue;
59                                         goto choseclump;
60         loopcontinue:;
61                                         if (largest < j - i)
62                                                 largest = j - i;
63                                 }
64                                 // since clump falsely advertised enough space (nothing wrong
65                                 // with that), update largest count to avoid wasting time in
66                                 // later allocations
67                                 clump->largestavailable = largest;
68                         }
69                 }
70                 pool->realsize += sizeof(memclump_t);
71                 clump = malloc(sizeof(memclump_t));
72                 if (clump == NULL)
73                         Sys_Error("Mem_Alloc: out of memory (alloc at %s:%i)", filename, fileline);
74                 memset(clump, 0, sizeof(memclump_t));
75                 *clumpchainpointer = clump;
76                 clump->sentinel1 = MEMCLUMP_SENTINEL;
77                 clump->sentinel2 = MEMCLUMP_SENTINEL;
78                 clump->chain = NULL;
79                 clump->blocksinuse = 0;
80                 clump->largestavailable = MEMBITS - needed;
81                 j = 0;
82 choseclump:
83                 mem = (memheader_t *)((long) clump->block + j * MEMUNIT);
84                 mem->clump = clump;
85                 clump->blocksinuse += needed;
86                 for (i = j + needed;j < i;j++)
87                         clump->bits[j >> 5] |= (1 << (j & 31));
88         }
89         else
90         {
91                 // big allocations are not clumped
92                 pool->realsize += sizeof(memheader_t) + size + sizeof(int);
93                 mem = malloc(sizeof(memheader_t) + size + sizeof(int));
94                 if (mem == NULL)
95                         Sys_Error("Mem_Alloc: out of memory (alloc at %s:%i)", filename, fileline);
96                 mem->clump = NULL;
97         }
98         mem->filename = filename;
99         mem->fileline = fileline;
100         mem->size = size;
101         mem->pool = pool;
102         mem->sentinel1 = MEMHEADER_SENTINEL;
103         *((int *)((long) mem + sizeof(memheader_t) + mem->size)) = MEMHEADER_SENTINEL;
104         // append to head of list
105         mem->chain = pool->chain;
106         pool->chain = mem;
107         memset((void *)((long) mem + sizeof(memheader_t)), 0, mem->size);
108         return (void *)((long) mem + sizeof(memheader_t));
109 }
110
111 void _Mem_Free(void *data, char *filename, int fileline)
112 {
113         int i, firstblock, endblock;
114         memclump_t *clump, **clumpchainpointer;
115         memheader_t *mem, **memchainpointer;
116         mempool_t *pool;
117         if (data == NULL)
118                 Sys_Error("Mem_Free: data == NULL (called at %s:%i)", filename, fileline);
119
120
121         mem = (memheader_t *)((long) data - sizeof(memheader_t));
122         if (mem->sentinel1 != MEMHEADER_SENTINEL)
123                 Sys_Error("Mem_Free: trashed header sentinel 1 (alloc at %s:%i, free at %s:%i)", mem->filename, mem->fileline, filename, fileline);
124         if (*((int *)((long) mem + sizeof(memheader_t) + mem->size)) != MEMHEADER_SENTINEL)
125                 Sys_Error("Mem_Free: trashed header sentinel 2 (alloc at %s:%i, free at %s:%i)", mem->filename, mem->fileline, filename, fileline);
126         pool = mem->pool;
127         for (memchainpointer = &pool->chain;*memchainpointer;memchainpointer = &(*memchainpointer)->chain)
128         {
129                 if (*memchainpointer == mem)
130                 {
131                         *memchainpointer = mem->chain;
132                         pool->totalsize -= mem->size;
133                         if ((clump = mem->clump))
134                         {
135                                 if (clump->sentinel1 != MEMCLUMP_SENTINEL)
136                                         Sys_Error("Mem_Free: trashed clump sentinel 1 (free at %s:%i)", filename, fileline);
137                                 if (clump->sentinel2 != MEMCLUMP_SENTINEL)
138                                         Sys_Error("Mem_Free: trashed clump sentinel 2 (free at %s:%i)", filename, fileline);
139                                 firstblock = ((long) mem - (long) clump->block);
140                                 if (firstblock & (MEMUNIT - 1))
141                                         Sys_Error("Mem_Free: address not valid in clump (free at %s:%i)", filename, fileline);
142                                 firstblock /= MEMUNIT;
143                                 endblock = firstblock + ((sizeof(memheader_t) + mem->size + sizeof(int) + (MEMUNIT - 1)) / MEMUNIT);
144                                 clump->blocksinuse -= endblock - firstblock;
145                                 // could use &, but we know the bit is set
146                                 for (i = firstblock;i < endblock;i++)
147                                         clump->bits[i >> 5] -= (1 << (i & 31));
148                                 if (clump->blocksinuse <= 0)
149                                 {
150                                         // unlink from chain
151                                         for (clumpchainpointer = &pool->clumpchain;*clumpchainpointer;clumpchainpointer = &(*clumpchainpointer)->chain)
152                                         {
153                                                 if (*clumpchainpointer == clump)
154                                                 {
155                                                         *clumpchainpointer = clump->chain;
156                                                         break;
157                                                 }
158                                         }
159                                         pool->realsize -= sizeof(memclump_t);
160                                         memset(clump, 0xBF, sizeof(memclump_t));
161                                         free(clump);
162                                 }
163                                 else
164                                 {
165                                         // clump still has some allocations
166                                         // force re-check of largest available space on next alloc
167                                         clump->largestavailable = MEMBITS - clump->blocksinuse;
168                                 }
169                         }
170                         else
171                         {
172                                 pool->realsize -= sizeof(memheader_t) + mem->size + sizeof(int);
173                                 memset(mem, 0xBF, sizeof(memheader_t) + mem->size + sizeof(int));
174                                 free(mem);
175                         }
176                         return;
177                 }
178         }
179         Sys_Error("Mem_Free: not allocated (free at %s:%i)", filename, fileline);
180 }
181
182 mempool_t *_Mem_AllocPool(char *name, char *filename, int fileline)
183 {
184 //      int i;
185         mempool_t *pool;
186         pool = malloc(sizeof(mempool_t));
187         if (pool == NULL)
188                 Sys_Error("Mem_AllocPool: out of memory (allocpool at %s:%i)", filename, fileline);
189         memset(pool, 0, sizeof(mempool_t));
190         pool->chain = NULL;
191         pool->totalsize = 0;
192         pool->realsize = sizeof(mempool_t);
193         strcpy(pool->name, name);
194 //      for (i = 0;i < (POOLNAMESIZE - 1) && name[i];i++)
195 //              pool->name[i] = name[i];
196 //      for (i = 0;i < POOLNAMESIZE;i++)
197 //              pool->name[i] = 0;
198         pool->next = poolchain;
199         poolchain = pool;
200         return pool;
201 }
202
203 void _Mem_FreePool(mempool_t **pool, char *filename, int fileline)
204 {
205         mempool_t **chainaddress;
206         if (*pool)
207         {
208                 // unlink pool from chain
209                 for (chainaddress = &poolchain;*chainaddress && *chainaddress != *pool;chainaddress = &((*chainaddress)->next));
210                 if (*chainaddress != *pool)
211                         Sys_Error("Mem_FreePool: pool already free (freepool at %s:%i)", filename, fileline);
212                 *chainaddress = (*pool)->next;
213
214                 // free memory owned by the pool
215                 while ((*pool)->chain)
216                         Mem_Free((void *)((long) (*pool)->chain + sizeof(memheader_t)));
217
218                 // free the pool itself
219                 memset(*pool, 0xBF, sizeof(mempool_t));
220                 free(*pool);
221                 *pool = NULL;
222         }
223 }
224
225 void _Mem_EmptyPool(mempool_t *pool, char *filename, int fileline)
226 {
227         if (pool == NULL)
228                 Sys_Error("Mem_EmptyPool: pool == NULL (emptypool at %s:%i)", filename, fileline);
229
230         // free memory owned by the pool
231         while (pool->chain)
232                 Mem_Free((void *)((long) pool->chain + sizeof(memheader_t)));
233 }
234
235 void _Mem_CheckSentinels(void *data, char *filename, int fileline)
236 {
237         memheader_t *mem;
238
239         if (data == NULL)
240                 Sys_Error("Mem_CheckSentinels: data == NULL (sentinel check at %s:%i)", filename, fileline);
241
242         mem = (memheader_t *)((long) data - sizeof(memheader_t));
243         if (mem->sentinel1 != MEMHEADER_SENTINEL)
244                 Sys_Error("Mem_CheckSentinels: trashed header sentinel 1 (block allocated at %s:%i, sentinel check at %s:%i)", mem->filename, mem->fileline, filename, fileline);
245         if (*((int *)((long) mem + sizeof(memheader_t) + mem->size)) != MEMHEADER_SENTINEL)
246                 Sys_Error("Mem_CheckSentinels: trashed header sentinel 2 (block allocated at %s:%i, sentinel check at %s:%i)", mem->filename, mem->fileline, filename, fileline);
247 }
248
249 static void _Mem_CheckClumpSentinels(memclump_t *clump, char *filename, int fileline)
250 {
251         // this isn't really very useful
252         if (clump->sentinel1 != MEMCLUMP_SENTINEL)
253                 Sys_Error("Mem_CheckClumpSentinels: trashed sentinel 1 (sentinel check at %s:%i)", filename, fileline);
254         if (clump->sentinel2 != MEMCLUMP_SENTINEL)
255                 Sys_Error("Mem_CheckClumpSentinels: trashed sentinel 2 (sentinel check at %s:%i)", filename, fileline);
256 }
257
258 void _Mem_CheckSentinelsGlobal(char *filename, int fileline)
259 {
260         memheader_t *mem;
261         memclump_t *clump;
262         mempool_t *pool;
263         for (pool = poolchain;pool;pool = pool->next)
264         {
265                 for (mem = pool->chain;mem;mem = mem->chain)
266                         _Mem_CheckSentinels((void *)((long) mem + sizeof(memheader_t)), filename, fileline);
267                 for (clump = pool->clumpchain;clump;clump = clump->chain)
268                         _Mem_CheckClumpSentinels(clump, filename, fileline);
269         }
270 }
271
272 // used for temporary memory allocations around the engine, not for longterm
273 // storage, if anything in this pool stays allocated during gameplay, it is
274 // considered a leak
275 mempool_t *tempmempool;
276 // only for zone
277 mempool_t *zonemempool;
278
279 void Mem_PrintStats(void)
280 {
281         int count = 0, size = 0;
282         mempool_t *pool;
283         memheader_t *mem;
284         Mem_CheckSentinelsGlobal();
285         for (pool = poolchain;pool;pool = pool->next)
286         {
287                 count++;
288                 size += pool->totalsize;
289         }
290         Con_Printf("%i memory pools, totalling %i bytes (%.3fMB)\n", count, size, size / 1048576.0);
291         if (tempmempool == NULL)
292                 Con_Printf("Error: no tempmempool allocated\n");
293         else if (tempmempool->chain)
294         {
295                 Con_Printf("%i bytes (%.3fMB) of temporary memory still allocated (Leak!)\n", tempmempool->totalsize, tempmempool->totalsize / 1048576.0);
296                 Con_Printf("listing temporary memory allocations:\n");
297                 for (mem = tempmempool->chain;mem;mem = mem->chain)
298                         Con_Printf("%10i bytes allocated at %s:%i\n", mem->size, mem->filename, mem->fileline);
299         }
300 }
301
302 void Mem_PrintList(int listallocations)
303 {
304         mempool_t *pool;
305         memheader_t *mem;
306         Mem_CheckSentinelsGlobal();
307         Con_Printf("memory pool list:\n"
308                    "size    name\n");
309         for (pool = poolchain;pool;pool = pool->next)
310         {
311                 if (pool->lastchecksize != 0 && pool->totalsize != pool->lastchecksize)
312                         Con_Printf("%6ik (%6ik actual) %s (%i byte change)\n", (pool->totalsize + 1023) / 1024, (pool->realsize + 1023) / 1024, pool->name, pool->totalsize - pool->lastchecksize);
313                 else
314                         Con_Printf("%6ik (%6ik actual) %s\n", (pool->totalsize + 1023) / 1024, (pool->realsize + 1023) / 1024, pool->name);
315                 pool->lastchecksize = pool->totalsize;
316                 if (listallocations)
317                         for (mem = pool->chain;mem;mem = mem->chain)
318                                 Con_Printf("%10i bytes allocated at %s:%i\n", mem->size, mem->filename, mem->fileline);
319         }
320 }
321
322 void MemList_f(void)
323 {
324         switch(Cmd_Argc())
325         {
326         case 1:
327                 Mem_PrintList(false);
328                 Mem_PrintStats();
329                 break;
330         case 2:
331                 if (!strcmp(Cmd_Argv(1), "all"))
332                 {
333                         Mem_PrintList(true);
334                         Mem_PrintStats();
335                         break;
336                 }
337                 // drop through
338         default:
339                 Con_Printf("MemList_f: unrecognized options\nusage: memlist [all]\n");
340                 break;
341         }
342 }
343
344 extern void R_TextureStats_PrintTotal(void);
345 void MemStats_f(void)
346 {
347         Mem_CheckSentinelsGlobal();
348         R_TextureStats_PrintTotal();
349         Mem_PrintStats();
350 }
351
352
353 /*
354 ========================
355 Memory_Init
356 ========================
357 */
358 void Memory_Init (void)
359 {
360         tempmempool = Mem_AllocPool("Temporary Memory");
361         zonemempool = Mem_AllocPool("Zone");
362 }
363
364 void Memory_Init_Commands (void)
365 {
366         Cmd_AddCommand ("memstats", MemStats_f);
367         Cmd_AddCommand ("memlist", MemList_f);
368 }
369