| 29 #include "cx/mempool.h" |
29 #include "cx/mempool.h" |
| 30 |
30 |
| 31 #include <string.h> |
31 #include <string.h> |
| 32 #include <errno.h> |
32 #include <errno.h> |
| 33 |
33 |
| 34 struct cx_mempool_memory_s { |
|
| 35 /** The destructor. */ |
|
| 36 cx_destructor_func destructor; |
|
| 37 /** The actual memory. */ |
|
| 38 char c[]; |
|
| 39 }; |
|
| 40 |
|
| 41 static int cx_mempool_ensure_capacity( |
34 static int cx_mempool_ensure_capacity( |
| 42 struct cx_mempool_s *pool, |
35 struct cx_mempool_s *pool, |
| 43 size_t needed_capacity |
36 size_t needed_capacity |
| 44 ) { |
37 ) { |
| 45 if (needed_capacity <= pool->capacity) return 0; |
38 if (needed_capacity <= pool->capacity) return 0; |
| 46 size_t newcap = pool->capacity >= 1000 ? |
39 size_t newcap = pool->capacity >= 1000 ? |
| 47 pool->capacity + 1000 : pool->capacity * 2; |
40 pool->capacity + 1000 : pool->capacity * 2; |
| 48 size_t newmsize; |
41 size_t newmsize; |
| 49 if (pool->capacity > newcap || cx_szmul(newcap, |
42 if (pool->capacity > newcap |
| 50 sizeof(struct cx_mempool_memory_s*), &newmsize)) { |
43 || cx_szmul(newcap, sizeof(void*), &newmsize)) { |
| 51 errno = EOVERFLOW; |
44 errno = EOVERFLOW; |
| 52 return 1; |
45 return 1; |
| 53 } |
46 } |
| 54 struct cx_mempool_memory_s **newdata = cxRealloc( |
47 void **newdata = cxReallocDefault(pool->data, newmsize); |
| 55 cxDefaultAllocator, pool->data, newmsize); |
|
| 56 if (newdata == NULL) return 1; |
48 if (newdata == NULL) return 1; |
| 57 pool->data = newdata; |
49 pool->data = newdata; |
| 58 pool->capacity = newcap; |
50 pool->capacity = newcap; |
| 59 return 0; |
51 return 0; |
| 60 } |
52 } |
| 61 |
53 |
| 62 static void *cx_mempool_malloc( |
54 static int cx_mempool_ensure_registered_capacity( |
| |
55 struct cx_mempool_s *pool, |
| |
56 size_t needed_capacity |
| |
57 ) { |
| |
58 if (needed_capacity <= pool->registered_capacity) return 0; |
| |
59 // we do not expect so many registrations |
| |
60 size_t newcap = pool->registered_capacity + 8; |
| |
61 size_t newmsize; |
| |
62 if (pool->registered_capacity > newcap || cx_szmul(newcap, |
| |
63 sizeof(struct cx_mempool_foreign_memory_s), &newmsize)) { |
| |
64 errno = EOVERFLOW; |
| |
65 return 1; |
| |
66 } |
| |
67 void *newdata = cxReallocDefault(pool->registered, newmsize); |
| |
68 if (newdata == NULL) return 1; |
| |
69 pool->registered = newdata; |
| |
70 pool->registered_capacity = newcap; |
| |
71 return 0; |
| |
72 } |
| |
73 |
| |
74 static void *cx_mempool_malloc_simple( |
| 63 void *p, |
75 void *p, |
| 64 size_t n |
76 size_t n |
| 65 ) { |
77 ) { |
| 66 struct cx_mempool_s *pool = p; |
78 struct cx_mempool_s *pool = p; |
| 67 |
79 |
| 68 if (cx_mempool_ensure_capacity(pool, pool->size + 1)) { |
80 if (cx_mempool_ensure_capacity(pool, pool->size + 1)) { |
| 69 return NULL; |
81 return NULL; |
| 70 } |
82 } |
| 71 |
83 |
| 72 struct cx_mempool_memory_s *mem = cxMalloc( |
84 struct cx_mempool_memory_s *mem = |
| 73 cxDefaultAllocator, sizeof(cx_destructor_func) + n); |
85 cxMallocDefault(sizeof(struct cx_mempool_memory_s) + n); |
| 74 if (mem == NULL) return NULL; |
86 if (mem == NULL) return NULL; |
| 75 |
87 mem->destructor = NULL; |
| 76 mem->destructor = pool->auto_destr; |
|
| 77 pool->data[pool->size] = mem; |
88 pool->data[pool->size] = mem; |
| 78 pool->size++; |
89 pool->size++; |
| 79 |
90 |
| 80 return mem->c; |
91 return mem->c; |
| 81 } |
92 } |
| 82 |
93 |
| 83 static void *cx_mempool_calloc( |
94 static void *cx_mempool_calloc_simple( |
| 84 void *p, |
95 void *p, |
| 85 size_t nelem, |
96 size_t nelem, |
| 86 size_t elsize |
97 size_t elsize |
| 87 ) { |
98 ) { |
| 88 size_t msz; |
99 size_t msz; |
| 89 if (cx_szmul(nelem, elsize, &msz)) { |
100 if (cx_szmul(nelem, elsize, &msz)) { |
| 90 errno = EOVERFLOW; |
101 errno = EOVERFLOW; |
| 91 return NULL; |
102 return NULL; |
| 92 } |
103 } |
| 93 void *ptr = cx_mempool_malloc(p, msz); |
104 void *ptr = cx_mempool_malloc_simple(p, msz); |
| 94 if (ptr == NULL) return NULL; |
105 if (ptr == NULL) return NULL; |
| 95 memset(ptr, 0, nelem * elsize); |
106 memset(ptr, 0, nelem * elsize); |
| 96 return ptr; |
107 return ptr; |
| 97 } |
108 } |
| 98 |
109 |
| 99 static void *cx_mempool_realloc( |
110 static void *cx_mempool_realloc_simple( |
| 100 void *p, |
111 void *p, |
| 101 void *ptr, |
112 void *ptr, |
| 102 size_t n |
113 size_t n |
| 103 ) { |
114 ) { |
| 104 struct cx_mempool_s *pool = p; |
115 struct cx_mempool_s *pool = p; |
| 105 |
116 |
| 106 struct cx_mempool_memory_s *mem, *newm; |
117 const unsigned overhead = sizeof(struct cx_mempool_memory_s); |
| 107 mem = (struct cx_mempool_memory_s*)(((char *) ptr) - sizeof(cx_destructor_func)); |
118 struct cx_mempool_memory_s *mem = |
| 108 newm = cxReallocDefault(mem, n + sizeof(cx_destructor_func)); |
119 (void *) (((char *) ptr) - overhead); |
| |
120 struct cx_mempool_memory_s *newm = |
| |
121 cxReallocDefault(mem, n + overhead); |
| 109 |
122 |
| 110 if (newm == NULL) return NULL; |
123 if (newm == NULL) return NULL; |
| 111 if (mem != newm) { |
124 if (mem != newm) { |
| 112 for (size_t i = 0; i < pool->size; i++) { |
125 for (size_t i = 0; i < pool->size; i++) { |
| 113 if (pool->data[i] == mem) { |
126 if (pool->data[i] == mem) { |
| 114 pool->data[i] = newm; |
127 pool->data[i] = newm; |
| 115 return ((char*)newm) + sizeof(cx_destructor_func); |
128 return ((char*)newm) + overhead; |
| 116 } |
129 } |
| 117 } |
130 } |
| 118 abort(); // LCOV_EXCL_LINE |
131 abort(); // LCOV_EXCL_LINE |
| 119 } else { |
132 } else { |
| 120 return ptr; |
133 return ptr; |
| 121 } |
134 } |
| 122 } |
135 } |
| 123 |
136 |
| 124 static void cx_mempool_free( |
137 static void cx_mempool_free_simple( |
| 125 void *p, |
138 void *p, |
| 126 void *ptr |
139 void *ptr |
| 127 ) { |
140 ) { |
| 128 if (!ptr) return; |
141 if (!ptr) return; |
| 129 struct cx_mempool_s *pool = p; |
142 struct cx_mempool_s *pool = p; |
| 130 |
143 |
| 131 struct cx_mempool_memory_s *mem = (struct cx_mempool_memory_s *) |
144 struct cx_mempool_memory_s *mem = |
| 132 ((char *) ptr - sizeof(cx_destructor_func)); |
145 (void*) ((char *) ptr - sizeof(struct cx_mempool_memory_s)); |
| 133 |
146 |
| 134 for (size_t i = 0; i < pool->size; i++) { |
147 for (size_t i = 0; i < pool->size; i++) { |
| 135 if (mem == pool->data[i]) { |
148 if (mem == pool->data[i]) { |
| 136 if (mem->destructor) { |
149 if (mem->destructor) { |
| 137 mem->destructor(mem->c); |
150 mem->destructor(mem->c); |
| |
151 } |
| |
152 if (pool->destr) { |
| |
153 pool->destr(mem->c); |
| |
154 } |
| |
155 if (pool->destr2) { |
| |
156 pool->destr2(pool->destr2_data, mem->c); |
| 138 } |
157 } |
| 139 cxFreeDefault(mem); |
158 cxFreeDefault(mem); |
| 140 size_t last_index = pool->size - 1; |
159 size_t last_index = pool->size - 1; |
| 141 if (i != last_index) { |
160 if (i != last_index) { |
| 142 pool->data[i] = pool->data[last_index]; |
161 pool->data[i] = pool->data[last_index]; |
| 147 } |
166 } |
| 148 } |
167 } |
| 149 abort(); // LCOV_EXCL_LINE |
168 abort(); // LCOV_EXCL_LINE |
| 150 } |
169 } |
| 151 |
170 |
| |
171 static void cx_mempool_free_all_simple(const struct cx_mempool_s *pool) { |
| |
172 const bool has_destr = pool->destr; |
| |
173 const bool has_destr2 = pool->destr2; |
| |
174 for (size_t i = 0; i < pool->size; i++) { |
| |
175 struct cx_mempool_memory_s *mem = pool->data[i]; |
| |
176 if (mem->destructor) { |
| |
177 mem->destructor(mem->c); |
| |
178 } |
| |
179 if (has_destr) { |
| |
180 pool->destr(mem->c); |
| |
181 } |
| |
182 if (has_destr2) { |
| |
183 pool->destr2(pool->destr2_data, mem->c); |
| |
184 } |
| |
185 cxFreeDefault(mem); |
| |
186 } |
| |
187 } |
| |
188 |
| |
189 static void cx_mempool_free_foreign(const struct cx_mempool_s *pool) { |
| |
190 for (size_t i = 0; i < pool->registered_size; i++) { |
| |
191 struct cx_mempool_foreign_memory_s info = pool->registered[i]; |
| |
192 if (info.destr2_data == NULL) { |
| |
193 if (info.destr) { |
| |
194 info.destr(info.mem); |
| |
195 } |
| |
196 } else { |
| |
197 info.destr2(info.destr2_data, info.mem); |
| |
198 } |
| |
199 } |
| |
200 } |
| |
201 |
| |
202 static cx_allocator_class cx_mempool_simple_allocator_class = { |
| |
203 cx_mempool_malloc_simple, |
| |
204 cx_mempool_realloc_simple, |
| |
205 cx_mempool_calloc_simple, |
| |
206 cx_mempool_free_simple |
| |
207 }; |
| |
208 |
| 152 void cxMempoolFree(CxMempool *pool) { |
209 void cxMempoolFree(CxMempool *pool) { |
| 153 if (pool == NULL) return; |
210 if (pool == NULL) return; |
| 154 struct cx_mempool_memory_s *mem; |
211 if (pool->allocator->cl == &cx_mempool_simple_allocator_class) { |
| 155 for (size_t i = 0; i < pool->size; i++) { |
212 cx_mempool_free_all_simple(pool); |
| 156 mem = pool->data[i]; |
213 } else { |
| 157 if (mem->destructor) { |
214 // TODO: implement |
| 158 mem->destructor(mem->c); |
215 } |
| 159 } |
216 cx_mempool_free_foreign(pool); |
| 160 cxFreeDefault(mem); |
|
| 161 } |
|
| 162 cxFreeDefault(pool->data); |
217 cxFreeDefault(pool->data); |
| |
218 cxFreeDefault(pool->registered); |
| 163 cxFreeDefault((void*) pool->allocator); |
219 cxFreeDefault((void*) pool->allocator); |
| 164 cxFreeDefault(pool); |
220 cxFreeDefault(pool); |
| 165 } |
221 } |
| 166 |
222 |
| 167 void cxMempoolSetDestructor( |
223 void cxMempoolSetDestructor( |
| 171 *(cx_destructor_func *) ((char *) ptr - sizeof(cx_destructor_func)) = func; |
227 *(cx_destructor_func *) ((char *) ptr - sizeof(cx_destructor_func)) = func; |
| 172 } |
228 } |
| 173 |
229 |
| 174 void cxMempoolRemoveDestructor(void *ptr) { |
230 void cxMempoolRemoveDestructor(void *ptr) { |
| 175 *(cx_destructor_func *) ((char *) ptr - sizeof(cx_destructor_func)) = NULL; |
231 *(cx_destructor_func *) ((char *) ptr - sizeof(cx_destructor_func)) = NULL; |
| 176 } |
|
| 177 |
|
| 178 struct cx_mempool_foreign_mem_s { |
|
| 179 cx_destructor_func destr; |
|
| 180 void* mem; |
|
| 181 }; |
|
| 182 |
|
| 183 static void cx_mempool_destr_foreign_mem(void* ptr) { |
|
| 184 struct cx_mempool_foreign_mem_s *fm = ptr; |
|
| 185 fm->destr(fm->mem); |
|
| 186 } |
232 } |
| 187 |
233 |
| 188 int cxMempoolRegister( |
234 int cxMempoolRegister( |
| 189 CxMempool *pool, |
235 CxMempool *pool, |
| 190 void *memory, |
236 void *memory, |
| 191 cx_destructor_func destr |
237 cx_destructor_func destr |
| 192 ) { |
238 ) { |
| 193 struct cx_mempool_foreign_mem_s *fm = cx_mempool_malloc( |
239 if (cx_mempool_ensure_registered_capacity(pool, pool->registered_size + 1)) { |
| 194 pool, |
240 return 1; // LCOV_EXCL_LINE |
| 195 sizeof(struct cx_mempool_foreign_mem_s) |
241 } |
| 196 ); |
242 |
| 197 if (fm == NULL) return 1; |
243 pool->registered[pool->registered_size++] = |
| 198 |
244 (struct cx_mempool_foreign_memory_s) { |
| 199 fm->mem = memory; |
245 .mem = memory, |
| 200 fm->destr = destr; |
246 .destr = destr, |
| 201 *(cx_destructor_func *) ((char *) fm - sizeof(cx_destructor_func)) = cx_mempool_destr_foreign_mem; |
247 .destr2_data = NULL |
| |
248 }; |
| 202 |
249 |
| 203 return 0; |
250 return 0; |
| 204 } |
251 } |
| 205 |
|
| 206 static cx_allocator_class cx_mempool_allocator_class = { |
|
| 207 cx_mempool_malloc, |
|
| 208 cx_mempool_realloc, |
|
| 209 cx_mempool_calloc, |
|
| 210 cx_mempool_free |
|
| 211 }; |
|
| 212 |
252 |
| 213 CxMempool *cxMempoolCreate( |
253 CxMempool *cxMempoolCreate( |
| 214 size_t capacity, |
254 size_t capacity, |
| 215 cx_destructor_func destr |
255 enum cx_mempool_type type |
| 216 ) { |
256 ) { |
| |
257 if (capacity == 0) capacity = 16; |
| 217 size_t poolsize; |
258 size_t poolsize; |
| 218 if (cx_szmul(capacity, sizeof(struct cx_mempool_memory_s*), &poolsize)) { |
259 if (cx_szmul(capacity, sizeof(void*), &poolsize)) { |
| 219 errno = EOVERFLOW; |
260 errno = EOVERFLOW; |
| 220 return NULL; |
261 return NULL; |
| 221 } |
262 } |
| 222 |
|
| 223 struct cx_mempool_s *pool = |
|
| 224 cxMallocDefault(sizeof(struct cx_mempool_s)); |
|
| 225 if (pool == NULL) return NULL; |
|
| 226 |
263 |
| 227 CxAllocator *provided_allocator = cxMallocDefault(sizeof(CxAllocator)); |
264 CxAllocator *provided_allocator = cxMallocDefault(sizeof(CxAllocator)); |
| 228 if (provided_allocator == NULL) { // LCOV_EXCL_START |
265 if (provided_allocator == NULL) { // LCOV_EXCL_START |
| 229 cxFreeDefault(pool); |
|
| 230 return NULL; |
266 return NULL; |
| 231 } // LCOV_EXCL_STOP |
267 } // LCOV_EXCL_STOP |
| 232 provided_allocator->cl = &cx_mempool_allocator_class; |
268 |
| |
269 CxMempool *pool = cxCallocDefault(1, sizeof(CxMempool)); |
| |
270 if (pool == NULL) { |
| |
271 cxFreeDefault(provided_allocator); |
| |
272 return NULL; |
| |
273 } |
| |
274 |
| 233 provided_allocator->data = pool; |
275 provided_allocator->data = pool; |
| 234 |
|
| 235 pool->allocator = provided_allocator; |
276 pool->allocator = provided_allocator; |
| |
277 if (type == CX_MEMPOOL_TYPE_SIMPLE) { |
| |
278 provided_allocator->cl = &cx_mempool_simple_allocator_class; |
| |
279 } else if (type == CX_MEMPOOL_TYPE_ADVANCED) { |
| |
280 // TODO: implement |
| |
281 } else { |
| |
282 // TODO: implement |
| |
283 } |
| 236 |
284 |
| 237 pool->data = cxMallocDefault(poolsize); |
285 pool->data = cxMallocDefault(poolsize); |
| 238 if (pool->data == NULL) { // LCOV_EXCL_START |
286 if (pool->data == NULL) { // LCOV_EXCL_START |
| 239 cxFreeDefault(provided_allocator); |
287 cxFreeDefault(provided_allocator); |
| 240 cxFreeDefault(pool); |
288 cxFreeDefault(pool); |
| 241 return NULL; |
289 return NULL; |
| 242 } // LCOV_EXCL_STOP |
290 } // LCOV_EXCL_STOP |
| 243 |
291 |
| 244 pool->size = 0; |
292 pool->size = 0; |
| 245 pool->capacity = capacity; |
293 pool->capacity = capacity; |
| 246 pool->auto_destr = destr; |
|
| 247 |
294 |
| 248 return pool; |
295 return pool; |
| 249 } |
296 } |
| 250 |
297 |
| 251 static void cx_mempool_free_transferred_allocator(void *al) { |
298 static void cx_mempool_free_transferred_allocator(void *al) { |
| 254 |
301 |
| 255 int cxMempoolTransfer( |
302 int cxMempoolTransfer( |
| 256 CxMempool *source, |
303 CxMempool *source, |
| 257 CxMempool *dest |
304 CxMempool *dest |
| 258 ) { |
305 ) { |
| 259 // safety check |
306 // safety checks |
| 260 if (source == dest) return 1; |
307 if (source == dest) return 1; |
| |
308 if (source->allocator->cl != dest->allocator->cl) return 1; |
| 261 |
309 |
| 262 // ensure enough capacity in the destination pool |
310 // ensure enough capacity in the destination pool |
| 263 if (cx_mempool_ensure_capacity(dest, dest->size + source->size + 1)) { |
311 if (cx_mempool_ensure_capacity(dest, dest->size + source->size)) { |
| |
312 return 1; // LCOV_EXCL_LINE |
| |
313 } |
| |
314 if (cx_mempool_ensure_registered_capacity(dest, |
| |
315 dest->registered_size + source->registered_size)) { |
| 264 return 1; // LCOV_EXCL_LINE |
316 return 1; // LCOV_EXCL_LINE |
| 265 } |
317 } |
| 266 |
318 |
| 267 // allocate a replacement allocator for the source pool |
319 // allocate a replacement allocator for the source pool |
| 268 CxAllocator *new_source_allocator = cxMallocDefault(sizeof(CxAllocator)); |
320 CxAllocator *new_source_allocator = cxMallocDefault(sizeof(CxAllocator)); |
| 269 if (new_source_allocator == NULL) { // LCOV_EXCL_START |
321 if (new_source_allocator == NULL) { // LCOV_EXCL_START |
| 270 return 1; |
322 return 1; |
| 271 } // LCOV_EXCL_STOP |
323 } // LCOV_EXCL_STOP |
| 272 new_source_allocator->cl = &cx_mempool_allocator_class; |
324 new_source_allocator->cl = source->allocator->cl; |
| 273 new_source_allocator->data = source; |
325 new_source_allocator->data = source; |
| 274 |
326 |
| 275 // transfer all the data |
327 // transfer all the data |
| 276 memcpy(&dest->data[dest->size], source->data, sizeof(source->data[0])*source->size); |
328 memcpy(&dest->data[dest->size], source->data, sizeof(void*)*source->size); |
| 277 dest->size += source->size; |
329 dest->size += source->size; |
| |
330 |
| |
331 // transfer all registered memory |
| |
332 memcpy(&dest->registered[dest->registered_size], source->registered, |
| |
333 sizeof(struct cx_mempool_foreign_memory_s) * source->size); |
| |
334 dest->registered_size += source->registered_size; |
| 278 |
335 |
| 279 // register the old allocator with the new pool |
336 // register the old allocator with the new pool |
| 280 // we have to remove const-ness for this, but that's okay here |
337 // we have to remove const-ness for this, but that's okay here |
| 281 CxAllocator *transferred_allocator = (CxAllocator*) source->allocator; |
338 CxAllocator *transferred_allocator = (CxAllocator*) source->allocator; |
| 282 transferred_allocator->data = dest; |
339 transferred_allocator->data = dest; |
| 283 cxMempoolRegister(dest, transferred_allocator, cx_mempool_free_transferred_allocator); |
340 cxMempoolRegister(dest, transferred_allocator, |
| |
341 cx_mempool_free_transferred_allocator); |
| 284 |
342 |
| 285 // prepare the source pool for re-use |
343 // prepare the source pool for re-use |
| 286 source->allocator = new_source_allocator; |
344 source->allocator = new_source_allocator; |
| 287 memset(source->data, 0, source->size * sizeof(source->data[0])); |
345 memset(source->data, 0, source->size * sizeof(void*)); |
| |
346 memset(source->registered, 0, |
| |
347 source->registered_size * sizeof(struct cx_mempool_foreign_memory_s)); |
| 288 source->size = 0; |
348 source->size = 0; |
| |
349 source->registered_size = 0; |
| 289 |
350 |
| 290 return 0; |
351 return 0; |
| 291 } |
352 } |
| 292 |
353 |
| 293 int cxMempoolTransferObject( |
354 int cxMempoolTransferObject( |