1 /***********************************************************************************************************************************
2 Memory Context Manager
3 ***********************************************************************************************************************************/
4 #include "build.auto.h"
5
6 #include <stdlib.h>
7 #include <string.h>
8
9 #include "common/debug.h"
10 #include "common/error.h"
11 #include "common/memContext.h"
12
13 /***********************************************************************************************************************************
14 Memory context states
15 ***********************************************************************************************************************************/
16 typedef enum
17 {
18 memContextStateFree = 0,
19 memContextStateFreeing,
20 memContextStateActive
21 } MemContextState;
22
23 /***********************************************************************************************************************************
24 Contains information about a memory allocation. This header is placed at the beginning of every memory allocation returned to the
25 user by memNew(), etc. The advantage is that when an allocation is passed back by the user we know the location of the allocation
26 header by doing some pointer arithmetic. This is much faster than searching through a list.
27 ***********************************************************************************************************************************/
28 typedef struct MemContextAlloc
29 {
30 unsigned int allocIdx:32; // Index in the allocation list
31 unsigned int size:32; // Allocation size (4GB max)
32 } MemContextAlloc;
33
34 // Get the allocation buffer pointer given the allocation header pointer
35 #define MEM_CONTEXT_ALLOC_BUFFER(header) ((MemContextAlloc *)header + 1)
36
37 // Get the allocation header pointer given the allocation buffer pointer
38 #define MEM_CONTEXT_ALLOC_HEADER(buffer) ((MemContextAlloc *)buffer - 1)
39
40 // Make sure the allocation is valid for the current memory context. This check only works correctly if the allocation is valid but
41 // belongs to another context. Otherwise, there is likely to be a segfault.
42 #define ASSERT_ALLOC_VALID(alloc) \
43 ASSERT( \
44 alloc != NULL && (uintptr_t)alloc != (uintptr_t)-sizeof(MemContextAlloc) && \
45 alloc->allocIdx < memContextStack[memContextCurrentStackIdx].memContext->allocListSize && \
46 memContextStack[memContextCurrentStackIdx].memContext->allocList[alloc->allocIdx]);
47
48 /***********************************************************************************************************************************
49 Contains information about the memory context
50 ***********************************************************************************************************************************/
51 struct MemContext
52 {
53 const char *name; // Indicates what the context is being used for
54 MemContextState state; // Current state of the context
55
56 unsigned int contextParentIdx; // Index in the parent context list
57 MemContext *contextParent; // All contexts have a parent except top
58
59 MemContext **contextChildList; // List of contexts created in this context
60 unsigned int contextChildListSize; // Size of child context list (not the actual count of contexts)
61 unsigned int contextChildFreeIdx; // Index of first free space in the context list
62
63 MemContextAlloc **allocList; // List of memory allocations created in this context
64 unsigned int allocListSize; // Size of alloc list (not the actual count of allocations)
65 unsigned int allocFreeIdx; // Index of first free space in the alloc list
66
67 void (*callbackFunction)(void *); // Function to call before the context is freed
68 void *callbackArgument; // Argument to pass to callback function
69 };
70
71 /***********************************************************************************************************************************
72 Top context
73
74 The top context always exists and can never be freed. All other contexts are children of the top context. The top context is
75 generally used to allocate memory that exists for the life of the program.
76 ***********************************************************************************************************************************/
77 static MemContext contextTop = {.state = memContextStateActive, .name = "TOP"};
78
79 /***********************************************************************************************************************************
80 Memory context stack types
81 ***********************************************************************************************************************************/
82 typedef enum
83 {
84 memContextStackTypeSwitch = 0, // Context can be switched to allocate mem for new variables
85 memContextStackTypeNew, // Context to be tracked for error handling - cannot switch to
86 } MemContextStackType;
87
88 /***********************************************************************************************************************************
89 Mem context stack used to pop mem contexts and cleanup after an error
90 ***********************************************************************************************************************************/
91 #define MEM_CONTEXT_STACK_MAX 128
92
93 static struct MemContextStack
94 {
95 MemContext *memContext;
96 MemContextStackType type;
97 unsigned int tryDepth;
98 } memContextStack[MEM_CONTEXT_STACK_MAX] = {{.memContext = &contextTop}};
99
100 static unsigned int memContextCurrentStackIdx = 0;
101 static unsigned int memContextMaxStackIdx = 0;
102
103 /***********************************************************************************************************************************
104 Wrapper around malloc() with error handling
105 ***********************************************************************************************************************************/
106 static void *
memAllocInternal(size_t size)107 memAllocInternal(size_t size)
108 {
109 FUNCTION_TEST_BEGIN();
110 FUNCTION_TEST_PARAM(SIZE, size);
111 FUNCTION_TEST_END();
112
113 // Allocate memory
114 void *buffer = malloc(size);
115
116 // Error when malloc fails
117 if (buffer == NULL)
118 THROW_FMT(MemoryError, "unable to allocate %zu bytes", size);
119
120 // Return the buffer
121 FUNCTION_TEST_RETURN(buffer);
122 }
123
124 /***********************************************************************************************************************************
125 Allocate an array of pointers and set all entries to NULL
126 ***********************************************************************************************************************************/
127 static void *
memAllocPtrArrayInternal(size_t size)128 memAllocPtrArrayInternal(size_t size)
129 {
130 FUNCTION_TEST_BEGIN();
131 FUNCTION_TEST_PARAM(SIZE, size);
132 FUNCTION_TEST_END();
133
134 // Allocate memory
135 void **buffer = memAllocInternal(size * sizeof(void *));
136
137 // Set all pointers to NULL
138 for (size_t ptrIdx = 0; ptrIdx < size; ptrIdx++)
139 buffer[ptrIdx] = NULL;
140
141 // Return the buffer
142 FUNCTION_TEST_RETURN(buffer);
143 }
144
145 /***********************************************************************************************************************************
146 Wrapper around realloc() with error handling
147 ***********************************************************************************************************************************/
148 static void *
memReAllocInternal(void * bufferOld,size_t sizeNew)149 memReAllocInternal(void *bufferOld, size_t sizeNew)
150 {
151 FUNCTION_TEST_BEGIN();
152 FUNCTION_TEST_PARAM_P(VOID, bufferOld);
153 FUNCTION_TEST_PARAM(SIZE, sizeNew);
154 FUNCTION_TEST_END();
155
156 ASSERT(bufferOld != NULL);
157
158 // Allocate memory
159 void *bufferNew = realloc(bufferOld, sizeNew);
160
161 // Error when realloc fails
162 if (bufferNew == NULL)
163 THROW_FMT(MemoryError, "unable to reallocate %zu bytes", sizeNew);
164
165 // Return the buffer
166 FUNCTION_TEST_RETURN(bufferNew);
167 }
168
169 /***********************************************************************************************************************************
170 Wrapper around realloc() with error handling
171 ***********************************************************************************************************************************/
172 static void *
memReAllocPtrArrayInternal(void * bufferOld,size_t sizeOld,size_t sizeNew)173 memReAllocPtrArrayInternal(void *bufferOld, size_t sizeOld, size_t sizeNew)
174 {
175 FUNCTION_TEST_BEGIN();
176 FUNCTION_TEST_PARAM_P(VOID, bufferOld);
177 FUNCTION_TEST_PARAM(SIZE, sizeOld);
178 FUNCTION_TEST_PARAM(SIZE, sizeNew);
179 FUNCTION_TEST_END();
180
181 // Allocate memory
182 void **bufferNew = memReAllocInternal(bufferOld, sizeNew * sizeof(void *));
183
184 // Set all new pointers to NULL
185 for (size_t ptrIdx = sizeOld; ptrIdx < sizeNew; ptrIdx++)
186 bufferNew[ptrIdx] = NULL;
187
188 // Return the buffer
189 FUNCTION_TEST_RETURN(bufferNew);
190 }
191
192 /***********************************************************************************************************************************
193 Wrapper around free()
194 ***********************************************************************************************************************************/
195 static void
memFreeInternal(void * buffer)196 memFreeInternal(void *buffer)
197 {
198 FUNCTION_TEST_BEGIN();
199 FUNCTION_TEST_PARAM_P(VOID, buffer);
200 FUNCTION_TEST_END();
201
202 ASSERT(buffer != NULL);
203
204 free(buffer);
205
206 FUNCTION_TEST_RETURN_VOID();
207 }
208
209 /***********************************************************************************************************************************
210 Find space for a new mem context
211 ***********************************************************************************************************************************/
212 static unsigned int
memContextNewIndex(MemContext * memContext,bool allowFree)213 memContextNewIndex(MemContext *memContext, bool allowFree)
214 {
215 FUNCTION_TEST_BEGIN();
216 FUNCTION_TEST_PARAM(MEM_CONTEXT, memContext);
217 FUNCTION_TEST_PARAM(BOOL, allowFree);
218 FUNCTION_TEST_END();
219
220 ASSERT(memContext != NULL);
221
222 // Try to find space for the new context
223 for (; memContext->contextChildFreeIdx < memContext->contextChildListSize; memContext->contextChildFreeIdx++)
224 {
225 if (memContext->contextChildList[memContext->contextChildFreeIdx] == NULL ||
226 (allowFree && memContext->contextChildList[memContext->contextChildFreeIdx]->state == memContextStateFree))
227 {
228 break;
229 }
230 }
231
232 // If no space was found then allocate more
233 if (memContext->contextChildFreeIdx == memContext->contextChildListSize)
234 {
235 // If no space has been allocated to the list
236 if (memContext->contextChildListSize == 0)
237 {
238 // Allocate memory before modifying anything else in case there is an error
239 memContext->contextChildList = memAllocPtrArrayInternal(MEM_CONTEXT_INITIAL_SIZE);
240
241 // Set new list size
242 memContext->contextChildListSize = MEM_CONTEXT_INITIAL_SIZE;
243 }
244 // Else grow the list
245 else
246 {
247 // Calculate new list size
248 unsigned int contextChildListSizeNew = memContext->contextChildListSize * 2;
249
250 // ReAllocate memory before modifying anything else in case there is an error
251 memContext->contextChildList = memReAllocPtrArrayInternal(
252 memContext->contextChildList, memContext->contextChildListSize, contextChildListSizeNew);
253
254 // Set new list size
255 memContext->contextChildListSize = contextChildListSizeNew;
256 }
257 }
258
259 FUNCTION_TEST_RETURN(memContext->contextChildFreeIdx);
260 }
261
262 /**********************************************************************************************************************************/
263 MemContext *
memContextNew(const char * name)264 memContextNew(const char *name)
265 {
266 FUNCTION_TEST_BEGIN();
267 FUNCTION_TEST_PARAM(STRINGZ, name);
268 FUNCTION_TEST_END();
269
270 ASSERT(name != NULL);
271
272 // Check context name length
273 ASSERT(name[0] != '\0');
274
275 // Find space for the new context
276 MemContext *contextCurrent = memContextStack[memContextCurrentStackIdx].memContext;
277 unsigned int contextIdx = memContextNewIndex(contextCurrent, true);
278
279 // If the context has not been allocated yet
280 if (contextCurrent->contextChildList[contextIdx] == NULL)
281 contextCurrent->contextChildList[contextIdx] = memAllocInternal(sizeof(MemContext));
282
283 // Get the context
284 MemContext *this = contextCurrent->contextChildList[contextIdx];
285
286 *this = (MemContext)
287 {
288 // Create initial space for allocations
289 .allocList = memAllocPtrArrayInternal(MEM_CONTEXT_ALLOC_INITIAL_SIZE),
290 .allocListSize = MEM_CONTEXT_ALLOC_INITIAL_SIZE,
291
292 // Set the context name
293 .name = name,
294
295 // Set new context active
296 .state = memContextStateActive,
297
298 // Set current context as the parent
299 .contextParent = contextCurrent,
300 .contextParentIdx = contextIdx,
301 };
302
303 // Possible free context must be in the next position
304 contextCurrent->contextChildFreeIdx++;
305
306 // Add to the mem context stack so it will be automatically freed on error if memContextKeep() has not been called
307 memContextMaxStackIdx++;
308
309 memContextStack[memContextMaxStackIdx] = (struct MemContextStack)
310 {
311 .memContext = this,
312 .type = memContextStackTypeNew,
313 .tryDepth = errorTryDepth(),
314 };
315
316 // Return context
317 FUNCTION_TEST_RETURN(this);
318 }
319
320 /**********************************************************************************************************************************/
321 void
memContextCallbackSet(MemContext * this,void (* callbackFunction)(void *),void * callbackArgument)322 memContextCallbackSet(MemContext *this, void (*callbackFunction)(void *), void *callbackArgument)
323 {
324 FUNCTION_TEST_BEGIN();
325 FUNCTION_TEST_PARAM(MEM_CONTEXT, this);
326 FUNCTION_TEST_PARAM(FUNCTIONP, callbackFunction);
327 FUNCTION_TEST_PARAM_P(VOID, callbackArgument);
328 FUNCTION_TEST_END();
329
330 ASSERT(this != NULL);
331 ASSERT(callbackFunction != NULL);
332
333 // Error if context is not active
334 if (this->state != memContextStateActive)
335 THROW(AssertError, "cannot assign callback to inactive context");
336
337 // Top context cannot have a callback
338 if (this == &contextTop)
339 THROW(AssertError, "top context may not have a callback");
340
341 // Error if callback has already been set - there may be valid use cases for this but error until one is found
342 if (this->callbackFunction)
343 THROW_FMT(AssertError, "callback is already set for context '%s'", this->name);
344
345 // Set callback function and argument
346 this->callbackFunction = callbackFunction;
347 this->callbackArgument = callbackArgument;
348
349 FUNCTION_TEST_RETURN_VOID();
350 }
351
352 /**********************************************************************************************************************************/
353 void
memContextCallbackClear(MemContext * this)354 memContextCallbackClear(MemContext *this)
355 {
356 FUNCTION_TEST_BEGIN();
357 FUNCTION_TEST_PARAM(MEM_CONTEXT, this);
358 FUNCTION_TEST_END();
359
360 ASSERT(this != NULL);
361
362 // Error if context is not active or freeing
363 ASSERT(this->state == memContextStateActive || this->state == memContextStateFreeing);
364
365 // Top context cannot have a callback
366 ASSERT(this != &contextTop);
367
368 // Clear callback function and argument
369 this->callbackFunction = NULL;
370 this->callbackArgument = NULL;
371
372 FUNCTION_TEST_RETURN_VOID();
373 }
374
375 /***********************************************************************************************************************************
376 Find an available slot in the memory context's allocation list and allocate memory
377 ***********************************************************************************************************************************/
378 static MemContextAlloc *
memContextAllocNew(size_t size)379 memContextAllocNew(size_t size)
380 {
381 FUNCTION_TEST_BEGIN();
382 FUNCTION_TEST_PARAM(SIZE, size);
383 FUNCTION_TEST_END();
384
385 // Find space for the new allocation
386 MemContext *contextCurrent = memContextStack[memContextCurrentStackIdx].memContext;
387
388 for (; contextCurrent->allocFreeIdx < contextCurrent->allocListSize; contextCurrent->allocFreeIdx++)
389 if (contextCurrent->allocList[contextCurrent->allocFreeIdx] == NULL)
390 break;
391
392 // If no space was found then allocate more
393 if (contextCurrent->allocFreeIdx == contextCurrent->allocListSize)
394 {
395 // Only the top context will not have initial space for allocations
396 if (contextCurrent->allocListSize == 0)
397 {
398 // Allocate memory before modifying anything else in case there is an error
399 contextCurrent->allocList = memAllocPtrArrayInternal(MEM_CONTEXT_ALLOC_INITIAL_SIZE);
400
401 // Set new size
402 contextCurrent->allocListSize = MEM_CONTEXT_ALLOC_INITIAL_SIZE;
403 }
404 // Else grow the list
405 else
406 {
407 // Calculate new list size
408 unsigned int allocListSizeNew = contextCurrent->allocListSize * 2;
409
410 // Reallocate memory before modifying anything else in case there is an error
411 contextCurrent->allocList = memReAllocPtrArrayInternal(
412 contextCurrent->allocList, contextCurrent->allocListSize, allocListSizeNew);
413
414 // Set new size
415 contextCurrent->allocListSize = allocListSizeNew;
416 }
417 }
418
419 // Create new allocation
420 MemContextAlloc *result = memAllocInternal(sizeof(MemContextAlloc) + size);
421
422 *result = (MemContextAlloc)
423 {
424 .allocIdx = contextCurrent->allocFreeIdx,
425 .size = (unsigned int)(sizeof(MemContextAlloc) + size),
426 };
427
428 // Set pointer in allocation list
429 contextCurrent->allocList[contextCurrent->allocFreeIdx] = result;
430
431 // Update free index to next location. This location may not actually be free but it is where the search should start next time.
432 contextCurrent->allocFreeIdx++;
433
434 FUNCTION_TEST_RETURN(result);
435 }
436
437 /***********************************************************************************************************************************
438 Resize memory that has already been allocated
439 ***********************************************************************************************************************************/
440 static MemContextAlloc *
memContextAllocResize(MemContextAlloc * alloc,size_t size)441 memContextAllocResize(MemContextAlloc *alloc, size_t size)
442 {
443 FUNCTION_TEST_BEGIN();
444 FUNCTION_TEST_PARAM_P(VOID, alloc);
445 FUNCTION_TEST_PARAM(SIZE, size);
446 FUNCTION_TEST_END();
447
448 ASSERT_ALLOC_VALID(alloc);
449
450 // Resize the allocation
451 alloc = memReAllocInternal(alloc, sizeof(MemContextAlloc) + size);
452 alloc->size = (unsigned int)(sizeof(MemContextAlloc) + size);
453
454 // Update pointer in allocation list in case the realloc moved the allocation
455 memContextStack[memContextCurrentStackIdx].memContext->allocList[alloc->allocIdx] = alloc;
456
457 FUNCTION_TEST_RETURN(alloc);
458 }
459
460 /**********************************************************************************************************************************/
461 void *
memNew(size_t size)462 memNew(size_t size)
463 {
464 FUNCTION_TEST_BEGIN();
465 FUNCTION_TEST_PARAM(SIZE, size);
466 FUNCTION_TEST_END();
467
468 FUNCTION_TEST_RETURN(MEM_CONTEXT_ALLOC_BUFFER(memContextAllocNew(size)));
469 }
470
471 /**********************************************************************************************************************************/
472 void *
memNewPtrArray(size_t size)473 memNewPtrArray(size_t size)
474 {
475 FUNCTION_TEST_BEGIN();
476 FUNCTION_TEST_PARAM(SIZE, size);
477 FUNCTION_TEST_END();
478
479 // Allocate pointer array
480 void **buffer = (void **)MEM_CONTEXT_ALLOC_BUFFER(memContextAllocNew(size * sizeof(void *)));
481
482 // Set pointers to NULL
483 for (size_t ptrIdx = 0; ptrIdx < size; ptrIdx++)
484 buffer[ptrIdx] = NULL;
485
486 FUNCTION_TEST_RETURN(buffer);
487 }
488
489 /**********************************************************************************************************************************/
490 void *
memResize(const void * buffer,size_t size)491 memResize(const void *buffer, size_t size)
492 {
493 FUNCTION_TEST_BEGIN();
494 FUNCTION_TEST_PARAM_P(VOID, buffer);
495 FUNCTION_TEST_PARAM(SIZE, size);
496 FUNCTION_TEST_END();
497
498 FUNCTION_TEST_RETURN(MEM_CONTEXT_ALLOC_BUFFER(memContextAllocResize(MEM_CONTEXT_ALLOC_HEADER(buffer), size)));
499 }
500
501 /**********************************************************************************************************************************/
502 void
memFree(void * buffer)503 memFree(void *buffer)
504 {
505 FUNCTION_TEST_BEGIN();
506 FUNCTION_TEST_PARAM_P(VOID, buffer);
507 FUNCTION_TEST_END();
508
509 ASSERT_ALLOC_VALID(MEM_CONTEXT_ALLOC_HEADER(buffer));
510
511 // Get the allocation
512 MemContext *contextCurrent = memContextStack[memContextCurrentStackIdx].memContext;
513 MemContextAlloc *alloc = MEM_CONTEXT_ALLOC_HEADER(buffer);
514
515 // If this allocation is before the current free allocation then make it the current free allocation
516 if (alloc->allocIdx < contextCurrent->allocFreeIdx)
517 contextCurrent->allocFreeIdx = alloc->allocIdx;
518
519 // Free the allocation
520 contextCurrent->allocList[alloc->allocIdx] = NULL;
521 memFreeInternal(alloc);
522
523 FUNCTION_TEST_RETURN_VOID();
524 }
525
526 /**********************************************************************************************************************************/
527 void
memContextMove(MemContext * this,MemContext * parentNew)528 memContextMove(MemContext *this, MemContext *parentNew)
529 {
530 FUNCTION_TEST_BEGIN();
531 FUNCTION_TEST_PARAM(MEM_CONTEXT, this);
532 FUNCTION_TEST_PARAM(MEM_CONTEXT, parentNew);
533 FUNCTION_TEST_END();
534
535 ASSERT(parentNew != NULL);
536
537 // Only move if a valid mem context is provided and the old and new parents are not the same
538 if (this != NULL && this->contextParent != parentNew)
539 {
540 // Find context in the old parent and NULL it out
541 MemContext *parentOld = this->contextParent;
542 unsigned int contextIdx;
543
544 for (contextIdx = 0; contextIdx < parentOld->contextChildListSize; contextIdx++)
545 {
546 if (parentOld->contextChildList[contextIdx] == this)
547 {
548 parentOld->contextChildList[contextIdx] = NULL;
549 break;
550 }
551 }
552
553 // The memory must be found
554 if (contextIdx == parentOld->contextChildListSize)
555 THROW(AssertError, "unable to find mem context in old parent");
556
557 // Find a place in the new parent context and assign it. The child list may be moved while finding a new index so store the
558 // index and use it with (what might be) the new pointer.
559 contextIdx = memContextNewIndex(parentNew, false);
560 ASSERT(parentNew->contextChildList[contextIdx] == NULL);
561 parentNew->contextChildList[contextIdx] = this;
562
563 // Assign new parent
564 this->contextParent = parentNew;
565 }
566
567 FUNCTION_TEST_RETURN_VOID();
568 }
569
570 /**********************************************************************************************************************************/
571 void
memContextSwitch(MemContext * this)572 memContextSwitch(MemContext *this)
573 {
574 FUNCTION_TEST_BEGIN();
575 FUNCTION_TEST_PARAM(MEM_CONTEXT, this);
576 FUNCTION_TEST_END();
577
578 ASSERT(this != NULL);
579 ASSERT(memContextCurrentStackIdx < MEM_CONTEXT_STACK_MAX - 1);
580
581 // Error if context is not active
582 if (this->state != memContextStateActive)
583 THROW(AssertError, "cannot switch to inactive context");
584
585 memContextMaxStackIdx++;
586 memContextCurrentStackIdx = memContextMaxStackIdx;
587
588 // Add memContext to the stack as a context that can be used for memory allocation
589 memContextStack[memContextCurrentStackIdx] = (struct MemContextStack)
590 {
591 .memContext = this,
592 .type = memContextStackTypeSwitch,
593 .tryDepth = errorTryDepth(),
594 };
595
596 FUNCTION_TEST_RETURN_VOID();
597 }
598
599 /**********************************************************************************************************************************/
600 void
memContextSwitchBack(void)601 memContextSwitchBack(void)
602 {
603 FUNCTION_TEST_VOID();
604
605 ASSERT(memContextCurrentStackIdx > 0);
606
607 // Generate a detailed error to help with debugging
608 #ifdef DEBUG
609 if (memContextStack[memContextMaxStackIdx].type == memContextStackTypeNew)
610 {
611 THROW_FMT(
612 AssertError, "current context expected but new context '%s' found",
613 memContextName(memContextStack[memContextMaxStackIdx].memContext));
614 }
615 #endif
616
617 ASSERT(memContextCurrentStackIdx == memContextMaxStackIdx);
618
619 memContextMaxStackIdx--;
620 memContextCurrentStackIdx--;
621
622 // memContext of type New cannot be the current context so keep going until we find a memContext we can switch to as the current
623 // context
624 while (memContextStack[memContextCurrentStackIdx].type == memContextStackTypeNew)
625 memContextCurrentStackIdx--;
626
627 FUNCTION_TEST_RETURN_VOID();
628 }
629
630 /**********************************************************************************************************************************/
631 void
memContextKeep(void)632 memContextKeep(void)
633 {
634 FUNCTION_TEST_VOID();
635
636 // Generate a detailed error to help with debugging
637 #ifdef DEBUG
638 if (memContextStack[memContextMaxStackIdx].type != memContextStackTypeNew)
639 {
640 THROW_FMT(
641 AssertError, "new context expected but current context '%s' found",
642 memContextName(memContextStack[memContextMaxStackIdx].memContext));
643 }
644 #endif
645
646 memContextMaxStackIdx--;
647
648 FUNCTION_TEST_RETURN_VOID();
649 }
650
651 /**********************************************************************************************************************************/
652 void
memContextDiscard(void)653 memContextDiscard(void)
654 {
655 FUNCTION_TEST_VOID();
656
657 // Generate a detailed error to help with debugging
658 #ifdef DEBUG
659 if (memContextStack[memContextMaxStackIdx].type != memContextStackTypeNew)
660 {
661 THROW_FMT(
662 AssertError, "new context expected but current context '%s' found",
663 memContextName(memContextStack[memContextMaxStackIdx].memContext));
664 }
665 #endif
666
667 memContextFree(memContextStack[memContextMaxStackIdx].memContext);
668 memContextMaxStackIdx--;
669
670 FUNCTION_TEST_RETURN_VOID();
671 }
672
673 /**********************************************************************************************************************************/
674 MemContext *
memContextTop(void)675 memContextTop(void)
676 {
677 FUNCTION_TEST_VOID();
678 FUNCTION_TEST_RETURN(&contextTop);
679 }
680
681 /**********************************************************************************************************************************/
682 MemContext *
memContextCurrent(void)683 memContextCurrent(void)
684 {
685 FUNCTION_TEST_VOID();
686 FUNCTION_TEST_RETURN(memContextStack[memContextCurrentStackIdx].memContext);
687 }
688
689 /**********************************************************************************************************************************/
690 bool
memContextFreeing(MemContext * this)691 memContextFreeing(MemContext *this)
692 {
693 FUNCTION_TEST_BEGIN();
694 FUNCTION_TEST_PARAM(MEM_CONTEXT, this);
695 FUNCTION_TEST_END();
696
697 ASSERT(this != NULL);
698
699 FUNCTION_TEST_RETURN(this->state == memContextStateFreeing);
700 }
701
702 /**********************************************************************************************************************************/
703 const char *
memContextName(MemContext * this)704 memContextName(MemContext *this)
705 {
706 FUNCTION_TEST_BEGIN();
707 FUNCTION_TEST_PARAM(MEM_CONTEXT, this);
708 FUNCTION_TEST_END();
709
710 ASSERT(this != NULL);
711
712 // Error if context is not active
713 if (this->state != memContextStateActive)
714 THROW(AssertError, "cannot get name for inactive context");
715
716 FUNCTION_TEST_RETURN(this->name);
717 }
718
719 /**********************************************************************************************************************************/
720 MemContext *
memContextPrior(void)721 memContextPrior(void)
722 {
723 FUNCTION_TEST_VOID();
724
725 ASSERT(memContextCurrentStackIdx > 0);
726
727 unsigned int priorIdx = 1;
728
729 while (memContextStack[memContextCurrentStackIdx - priorIdx].type == memContextStackTypeNew)
730 priorIdx++;
731
732 FUNCTION_TEST_RETURN(memContextStack[memContextCurrentStackIdx - priorIdx].memContext);
733 }
734
735 /**********************************************************************************************************************************/
736 size_t
memContextSize(const MemContext * this)737 memContextSize(const MemContext *this)
738 {
739 FUNCTION_TEST_BEGIN();
740 FUNCTION_TEST_PARAM(MEM_CONTEXT, this);
741 FUNCTION_TEST_END();
742
743 // Size of struct and child context/alloc arrays
744 size_t result =
745 sizeof(MemContext) + (this->contextChildListSize * sizeof(MemContext *)) +
746 (this->allocListSize * sizeof(MemContextAlloc *));
747
748 // Add child contexts
749 for (unsigned int contextIdx = 0; contextIdx < this->contextChildListSize; contextIdx++)
750 {
751 if (this->contextChildList[contextIdx])
752 result += memContextSize(this->contextChildList[contextIdx]);
753 }
754
755 // Add allocations
756 for (unsigned int allocIdx = 0; allocIdx < this->allocListSize; allocIdx++)
757 {
758 if (this->allocList[allocIdx] != NULL)
759 result += this->allocList[allocIdx]->size;
760 }
761
762 FUNCTION_TEST_RETURN(result);
763 }
764
765 /**********************************************************************************************************************************/
766 void
memContextClean(unsigned int tryDepth)767 memContextClean(unsigned int tryDepth)
768 {
769 FUNCTION_TEST_BEGIN();
770 FUNCTION_TEST_PARAM(UINT, tryDepth);
771 FUNCTION_TEST_END();
772
773 ASSERT(tryDepth > 0);
774
775 // Iterate through everything pushed to the stack since the last try
776 while (memContextStack[memContextMaxStackIdx].tryDepth >= tryDepth)
777 {
778 // Free memory contexts that were not kept
779 if (memContextStack[memContextMaxStackIdx].type == memContextStackTypeNew)
780 {
781 memContextFree(memContextStack[memContextMaxStackIdx].memContext);
782 }
783 // Else find the prior context and make it the current context
784 else
785 {
786 memContextCurrentStackIdx--;
787
788 while (memContextStack[memContextCurrentStackIdx].type == memContextStackTypeNew)
789 memContextCurrentStackIdx--;
790 }
791
792 memContextMaxStackIdx--;
793 }
794
795 FUNCTION_TEST_RETURN_VOID();
796 }
797
798 /**********************************************************************************************************************************/
799 void
memContextFree(MemContext * this)800 memContextFree(MemContext *this)
801 {
802 FUNCTION_TEST_BEGIN();
803 FUNCTION_TEST_PARAM(MEM_CONTEXT, this);
804 FUNCTION_TEST_END();
805
806 ASSERT(this != NULL);
807
808 // If context is already freeing then return if memContextFree() is called again - this can happen in callbacks
809 if (this->state != memContextStateFreeing)
810 {
811 // Current context cannot be freed unless it is top (top is never really freed, just the stuff under it)
812 if (this == memContextStack[memContextCurrentStackIdx].memContext && this != &contextTop)
813 THROW_FMT(AssertError, "cannot free current context '%s'", this->name);
814
815 // Error if context is not active
816 if (this->state != memContextStateActive)
817 THROW(AssertError, "cannot free inactive context");
818
819 // Free child contexts
820 if (this->contextChildListSize > 0)
821 for (unsigned int contextIdx = 0; contextIdx < this->contextChildListSize; contextIdx++)
822 if (this->contextChildList[contextIdx] && this->contextChildList[contextIdx]->state == memContextStateActive)
823 memContextFree(this->contextChildList[contextIdx]);
824
825 // Set state to freeing now that there are no child contexts. Child contexts might need to interact with their parent while
826 // freeing so the parent needs to remain active until they are all gone.
827 this->state = memContextStateFreeing;
828
829 // Execute callback if defined
830 TRY_BEGIN()
831 {
832 if (this->callbackFunction)
833 this->callbackFunction(this->callbackArgument);
834 }
835 // Finish cleanup even if the callback fails
836 FINALLY()
837 {
838 // Free child context allocations
839 if (this->contextChildListSize > 0)
840 {
841 for (unsigned int contextIdx = 0; contextIdx < this->contextChildListSize; contextIdx++)
842 if (this->contextChildList[contextIdx])
843 memFreeInternal(this->contextChildList[contextIdx]);
844
845 memFreeInternal(this->contextChildList);
846 this->contextChildListSize = 0;
847 }
848
849 // Free memory allocations
850 if (this->allocListSize > 0)
851 {
852 for (unsigned int allocIdx = 0; allocIdx < this->allocListSize; allocIdx++)
853 if (this->allocList[allocIdx] != NULL)
854 memFreeInternal(this->allocList[allocIdx]);
855
856 memFreeInternal(this->allocList);
857 this->allocListSize = 0;
858 }
859
860 // If the context index is lower than the current free index in the parent then replace it
861 if (this->contextParent != NULL && this->contextParentIdx < this->contextParent->contextChildFreeIdx)
862 this->contextParent->contextChildFreeIdx = this->contextParentIdx;
863
864 // Make top context active again
865 if (this == &contextTop)
866 this->state = memContextStateActive;
867 // Else reset the memory context so it can be reused
868 else
869 *this = (MemContext){.state = memContextStateFree};
870 }
871 TRY_END();
872 }
873
874 FUNCTION_TEST_RETURN_VOID();
875 }
876