1 /***********************************************************************************************************************************
2 Memory Context Manager
3 
4 Memory is allocated inside contexts and all allocations (and child memory contexts) are freed when the context is freed.  The goal
5 is to make memory management both easier and more performant.
6 
7 Memory context management is encapsulated in macros so there is rarely any need to call the functions directly.  Memory allocations
8 are mostly performed in the constructors of objects and reallocated as needed.
9 
10 See the sections on memory context management and memory allocations below for more details.
11 ***********************************************************************************************************************************/
12 #ifndef COMMON_MEMCONTEXT_H
13 #define COMMON_MEMCONTEXT_H
14 
15 #include <stddef.h>
16 
17 /***********************************************************************************************************************************
18 Memory context object
19 ***********************************************************************************************************************************/
20 typedef struct MemContext MemContext;
21 
22 #include "common/error.h"
23 
24 /***********************************************************************************************************************************
25 Define initial number of memory contexts
26 
27 No space is reserved for child contexts when a new context is created because most contexts will be leaves.  When a child context is
28 requested then space will be reserved for this many child contexts initially.  When more space is needed the size will be doubled.
29 ***********************************************************************************************************************************/
30 #define MEM_CONTEXT_INITIAL_SIZE                                    4
31 
32 /***********************************************************************************************************************************
33 Define initial number of memory allocations
34 
35 Space is reserved for this many allocations when a context is created.  When more space is needed the size will be doubled.
36 ***********************************************************************************************************************************/
37 #define MEM_CONTEXT_ALLOC_INITIAL_SIZE                              4
38 
39 /***********************************************************************************************************************************
40 Memory management functions
41 
42 All these functions operate in the current memory context, including memResize() and memFree().
43 ***********************************************************************************************************************************/
44 // Allocate memory in the current memory context
45 void *memNew(size_t size);
46 
47 // Allocate requested number of pointers and initialize them to NULL
48 void *memNewPtrArray(size_t size);
49 
50 // Reallocate to the new size.  Original buffer pointer is undefined on return.
51 void *memResize(const void *buffer, size_t size);
52 
53 // Free memory allocation
54 void memFree(void *buffer);
55 
56 /***********************************************************************************************************************************
57 Ensure that the prior memory context is restored after the block executes (even on error)
58 
59 MEM_CONTEXT_BEGIN(memContext)
60 {
61     <The mem context specified is now the current context>
62     <Prior context can be accessed with the memContextPrior() function>
63 }
64 MEM_CONTEXT_END();
65 
66 <Prior memory context is restored>
67 ***********************************************************************************************************************************/
68 #define MEM_CONTEXT_BEGIN(memContext)                                                                                              \
69     do                                                                                                                             \
70     {                                                                                                                              \
71         /* Switch to the new memory context */                                                                                     \
72         memContextSwitch(memContext);
73 
74 #define MEM_CONTEXT_END()                                                                                                          \
75         /* Switch back to the prior context */                                                                                     \
76         memContextSwitchBack();                                                                                                    \
77     }                                                                                                                              \
78     while (0)
79 
80 /***********************************************************************************************************************************
81 Switch to prior context and ensure that the previous prior memory context is restored after the block executes (even on error)
82 
83 MEM_CONTEXT_PRIOR_BEGIN(memContext)
84 {
85     <The mem context specified is now the prior context>
86 }
87 MEM_CONTEXT_PRIOR_END();
88 
89 <Previous prior memory context is restored>
90 ***********************************************************************************************************************************/
91 #define MEM_CONTEXT_PRIOR_BEGIN()                                                                                                  \
92     MEM_CONTEXT_BEGIN(memContextPrior())
93 
94 #define MEM_CONTEXT_PRIOR_END()                                                                                                    \
95     MEM_CONTEXT_END()
96 
97 /***********************************************************************************************************************************
98 Create a new context and make sure it is freed on error and prior context is restored in all cases
99 
100 MEM_CONTEXT_NEW_BEGIN(memContextName)
101 {
102     <The mem context created is now the current context and can be accessed with the MEM_CONTEXT_NEW() macro>
103 
104     ObjectType *object = memNew(sizeof(ObjectType));
105     object->memContext = MEM_CONTEXT_NEW();
106 
107     <Prior context can be accessed with the memContextPrior() function>
108     <On error the newly created context will be freed and the error rethrown>
109 }
110 MEM_CONTEXT_NEW_END();
111 
112 <Prior memory context is restored>
113 
114 Note that memory context names are expected to live for the lifetime of the context -- no copy is made.
115 ***********************************************************************************************************************************/
116 #define MEM_CONTEXT_NEW()                                                                                                          \
117     MEM_CONTEXT_NEW_memContext
118 
119 #define MEM_CONTEXT_NEW_BEGIN(memContextName)                                                                                      \
120     do                                                                                                                             \
121     {                                                                                                                              \
122         MemContext *MEM_CONTEXT_NEW() = memContextNew(memContextName);                                                             \
123         memContextSwitch(MEM_CONTEXT_NEW());
124 
125 #define MEM_CONTEXT_NEW_END()                                                                                                      \
126         memContextSwitchBack();                                                                                                    \
127         memContextKeep();                                                                                                          \
128     }                                                                                                                              \
129     while (0)
130 
131 /***********************************************************************************************************************************
132 Create a temporary memory context and make sure it is freed when done (even on error)
133 
134 MEM_CONTEXT_TEMP_BEGIN()
135 {
136     <A temp memory context is now the current context>
137     <Temp context can be accessed with the MEM_CONTEXT_TEMP() macro>
138     <Prior context can be accessed with the memContextPrior() function>
139 }
140 MEM_CONTEXT_TEMP_END();
141 
142 <Prior memory context is restored>
143 <Temp memory context is freed>
144 ***********************************************************************************************************************************/
145 #define MEM_CONTEXT_TEMP()                                                                                                         \
146     MEM_CONTEXT_TEMP_memContext
147 
148 #define MEM_CONTEXT_TEMP_BEGIN()                                                                                                   \
149     do                                                                                                                             \
150     {                                                                                                                              \
151         MemContext *MEM_CONTEXT_TEMP() = memContextNew("temporary");                                                               \
152         memContextSwitch(MEM_CONTEXT_TEMP());
153 
154 #define MEM_CONTEXT_TEMP_RESET_BEGIN()                                                                                             \
155     MEM_CONTEXT_TEMP_BEGIN()                                                                                                       \
156     unsigned int MEM_CONTEXT_TEMP_loopTotal = 0;
157 
158 #define MEM_CONTEXT_TEMP_RESET(resetTotal)                                                                                         \
159     do                                                                                                                             \
160     {                                                                                                                              \
161         MEM_CONTEXT_TEMP_loopTotal++;                                                                                              \
162                                                                                                                                    \
163         if (MEM_CONTEXT_TEMP_loopTotal >= resetTotal)                                                                              \
164         {                                                                                                                          \
165             memContextSwitchBack();                                                                                                \
166             memContextDiscard();                                                                                                   \
167             MEM_CONTEXT_TEMP() = memContextNew("temporary");                                                                       \
168             memContextSwitch(MEM_CONTEXT_TEMP());                                                                                  \
169             MEM_CONTEXT_TEMP_loopTotal = 0;                                                                                        \
170         }                                                                                                                          \
171     }                                                                                                                              \
172     while (0)
173 
174 #define MEM_CONTEXT_TEMP_END()                                                                                                     \
175         memContextSwitchBack();                                                                                                    \
176         memContextDiscard();                                                                                                       \
177     }                                                                                                                              \
178     while (0)
179 
180 /***********************************************************************************************************************************
181 Memory context management functions
182 
183 memContextSwitch(memContextNew());
184 
185 <Do something with the memory context, e.g. allocation memory with memNew()>
186 <Current memory context can be accessed with memContextCurrent()>
187 <Prior memory context can be accessed with memContextPrior()>
188 
189 memContextSwitchBack();
190 
191 <The memory context must now be kept or discarded>
192 memContextKeep()/memContextDiscard();
193 
194 There is no need to implement any error handling.  The mem context system will automatically clean up any mem contexts that were
195 created but not marked as keep when an error occurs and reset the current mem context to whatever it was at the beginning of the
196 nearest try block.
197 
198 Use the MEM_CONTEXT*() macros when possible rather than reimplement the boilerplate for every memory context block.
199 ***********************************************************************************************************************************/
200 // Create a new mem context in the current mem context. The new context must be either kept with memContextKeep() or discarded with
201 // memContextDisard() before switching back from the parent context.
202 MemContext *memContextNew(const char *name);
203 
204 // Switch to a context making it the current mem context
205 void memContextSwitch(MemContext *this);
206 
207 // Switch back to the context that was current before the last switch. If the last function called was memContextNew(), then
208 // memContextKeep()/memContextDiscard() must be called before calling memContextSwitchBack(), otherwise an error will occur in
209 // debug builds and the behavior is undefined in production builds.
210 void memContextSwitchBack(void);
211 
212 // Keep a context created by memContextNew() so that it will not be automatically freed if an error occurs. If the context was
213 // switched after the call to memContextNew(), then it must be switched back before calling memContextKeep() or an error will occur
214 // in debug builds and the behavior is undefined in production builds.
215 void memContextKeep(void);
216 
217 // Discard a context created by memContextNew(). If the context was switched after the call to memContextNew(), then it must be
218 // switched back before calling memContextDiscard() or an error will occur in debug builds and the behavior is undefined in
219 // production builds.
220 void memContextDiscard(void);
221 
222 // Move mem context to a new parent. This is generally used to move objects to a new context once they have been successfully
223 // created.
224 void memContextMove(MemContext *this, MemContext *parentNew);
225 
226 // Set a function that will be called when this mem context is freed
227 void memContextCallbackSet(MemContext *this, void (*callbackFunction)(void *), void *);
228 
229 // Clear the callback function so it won't be called when the mem context is freed.  This is usually done in the object free method
230 // after resources have been freed but before memContextFree() is called.  The goal is to prevent the object free method from being
231 // called more than once.
232 void memContextCallbackClear(MemContext *this);
233 
234 // Free a memory context
235 void memContextFree(MemContext *this);
236 
237 /***********************************************************************************************************************************
238 Memory context getters
239 ***********************************************************************************************************************************/
240 // Current memory context
241 MemContext *memContextCurrent(void);
242 
243 // Is the mem context currently being freed?
244 bool memContextFreeing(MemContext *this);
245 
246 // Prior context, i.e. the context that was current before the last memContextSwitch()
247 MemContext *memContextPrior(void);
248 
249 // "top" context.  This context is created at initialization and is always present, i.e. it is never freed.  The top context is a
250 // good place to put long-lived mem contexts since they won't be automatically freed until the program exits.
251 MemContext *memContextTop(void);
252 
253 // Mem context name
254 const char *memContextName(MemContext *this);
255 
256 // Get total size of mem context and all children
257 size_t memContextSize(const MemContext *this);
258 
259 /***********************************************************************************************************************************
260 Macros for function logging
261 ***********************************************************************************************************************************/
262 #define FUNCTION_LOG_MEM_CONTEXT_TYPE                                                                                              \
263     MemContext *
264 #define FUNCTION_LOG_MEM_CONTEXT_FORMAT(value, buffer, bufferSize)                                                                 \
265     objToLog(value, "MemContext", buffer, bufferSize)
266 
267 /***********************************************************************************************************************************
268 Internal functions
269 ***********************************************************************************************************************************/
270 // Clean up mem contexts after an error.  Should only be called from error handling routines.
271 void memContextClean(unsigned int tryDepth);
272 
273 #endif
274