polar-objc 5.44
An Objective-C runtime library
Loading...
Searching...
No Matches
polar-runtime-memory-pool.c
1/* Internal API: internal memory pool for polar-objc.
2 Copyright (C) 2022-2025 Michael Malicoat <[email protected]>
3
4 This file is part of polar-objc.
5
6 polar-objc is free software; you can redistribute it and/or modify it under the terms of the GNU General Public
7 License as published by the Free Software Foundation; either version 3, or (at your option) any later version.
8
9 polar-objc is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
10 warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
11 details.
12
13 You should have received a copy of the GNU General Public License along with this program; see the file LICENSE. If
14 not, see <http://www.gnu.org/licenses/>.
15*/
16// polar_runtime_memory_pool *******************************************************************************************
17// Private =============================================================================================================
18
19// This variable is initialized in polar_runtime_memory_pool_get_type()
20static polar_runtime_memory_pool_class class_polar_runtime_memory_pool;
21
22// This variable is initialized in _polar_runtime_memory_pool_class_init()
23static polar_runtime_object_class *superclass_polar_runtime_memory_pool = NULL;
24
25// ---------------------------------------------------------------------------------------------------------------------
26
27// This works out to 32K on 64-bit systems and 16K on 32-bit systems
28#define POLAR_SIZE_RUNTIME_MEMORY_POOL_PAGE ( OBJC_SIZEOF_POINTER << 12 )
29#define POLAR_COUNT_RUNTIME_MEMORY_TUNE_MISSES_START 1
30#define POLAR_COUNT_BUCKETS_TABLE_STRINGS_INTERNED 4
31
32// Assumes the pool lock is held by the caller!
33static inline void *
34polar_runtime_memory_pool_block_idle_pop( polar_runtime_memory_pool *self, intptr_t idx_idle_list )
35{
36 void *result;
37
38 assert( POLAR_IS_RUNTIME_MEMORY_POOL(self) );
39 assert( idx_idle_list > 0 );
40 assert( idx_idle_list <= POLAR_LENGTH_ARRAY_MEMBLOCK_IDLE );
41
42 result = self->array_idle_blocks[idx_idle_list].block_last;
43 if( result != NULL )
44 {
45 self->array_idle_blocks[idx_idle_list].block_last = *( (void **)result );
46 *( (void **)result ) = NULL;
47
48 #ifdef POLAR_DEBUG_RUNTIME_MEMORY
49 self->array_idle_blocks[idx_idle_list].count_blocks_idle--;
50 #endif // POLAR_DEBUG_RUNTIME_MEMORY
51 }
52
53 return result;
54}
55
56// Assumes the pool lock is held by the caller!
57static inline void
58polar_runtime_memory_pool_block_idle_push( polar_runtime_memory_pool *self, intptr_t idx_idle_list,
59 void *block_new )
60{
61 assert( POLAR_IS_RUNTIME_MEMORY_POOL(self) );
62 assert( idx_idle_list > 0 );
63 assert( idx_idle_list <= POLAR_LENGTH_ARRAY_MEMBLOCK_IDLE );
64 assert( block_new != NULL );
65 assert( *((void **)block_new) == NULL ); // The memory should be zeroed before it is discarded
66
67 *( (void **)block_new ) = self->array_idle_blocks[idx_idle_list].block_last;
68 self->array_idle_blocks[idx_idle_list].block_last = block_new;
69
70#ifdef POLAR_DEBUG_RUNTIME_MEMORY
71 self->array_idle_blocks[idx_idle_list].count_blocks_idle++;
72#endif // POLAR_DEBUG_RUNTIME_MEMORY
73}
74
75// Assumes the pool lock is held by the caller!
76POLAR_FUNCTION_INTERNAL uintptr_t
77polar_runtime_memory_pool_block_idle_push_list( polar_runtime_memory_pool *self, intptr_t idx_idle_list,
78 void **block_first )
79{
80 uintptr_t result;
81 void *block_current, *block_next;
82
83 assert( POLAR_IS_RUNTIME_MEMORY_POOL(self) );
84 assert( idx_idle_list > 0 );
85 assert( idx_idle_list <= POLAR_LENGTH_ARRAY_MEMBLOCK_IDLE );
86 assert( block_first != NULL );
87
88 result = 0;
89
90 // Find the end of the linked list
91 block_next = block_first;
92
93 do
94 {
95 block_current = block_next;
96 result++;
97
98 block_next = *( (void **)block_current );
99 } while( block_next != NULL );
100
101 // Push the entire chain of nodes at once
102 *( (void **)block_current ) = self->array_idle_blocks[idx_idle_list].block_last;
103 self->array_idle_blocks[idx_idle_list].block_last = block_first;
104
105#ifdef POLAR_DEBUG_RUNTIME_MEMORY
106 self->array_idle_blocks[idx_idle_list].count_blocks_idle += result;
107#endif // POLAR_DEBUG_RUNTIME_MEMORY
108
109 return result;
110}
111
112// Called by _polar_thread_data_finalize()
113POLAR_FUNCTION_INTERNAL void
114polar_runtime_memory_pool_return_thread_memory( polar_runtime_memory_pool *self, void **array_idle_blocks )
115{
116 intptr_t i;
117 uintptr_t count_blocks_returned;
118
119 assert( POLAR_IS_RUNTIME_MEMORY_POOL(self) );
120 assert( array_idle_blocks != NULL );
121
122 for( i = 1; i <= POLAR_LENGTH_ARRAY_MEMBLOCK_IDLE; i++ )
123 {
124 if( array_idle_blocks[i] != NULL )
125 {
126 mtx_lock( &self->pool_lock );
127 count_blocks_returned = polar_runtime_memory_pool_block_idle_push_list( self, i, array_idle_blocks[i] );
128
129 // Reset the count of misses for this block size
130 objc_atomic_subtract_uintptr( &self->array_idle_blocks[i].count_misses, count_blocks_returned );
131 mtx_unlock( &self->pool_lock );
132 }
133 }
134}
135
136// This is called in the context of polar_memory_disposal_thread()
137POLAR_FUNCTION_INTERNAL void
138polar_runtime_memory_pool_tune( polar_runtime_memory_pool *self )
139{
140 polar_memory_pool_class *superclass;
141 intptr_t i;
142 uintptr_t count_misses_max, count_misses_current;
143 size_t size_block, size_chain;
144 char *block_first, *block_last, *block_current;
145
146 assert( POLAR_IS_RUNTIME_MEMORY_POOL(self) );
147
148 superclass = POLAR_MEMORY_POOL_CLASS(superclass_polar_runtime_memory_pool);
149 count_misses_max = POLAR_COUNT_RUNTIME_MEMORY_TUNE_MISSES_START;
150
151 /* Here we preallocate additional structures based on how often an idle structure of a given size was sought by the
152 runtime when none were available. The idea is to speed up allocation from the pool by having preallocated
153 structures ready to go.
154 */
155 for( i = POLAR_LENGTH_ARRAY_MEMBLOCK_IDLE; i > 0; i-- )
156 {
157 count_misses_current = objc_atomic_exchange_uintptr( &self->array_idle_blocks[i].count_misses, 0 );
158
159 if( count_misses_current >= count_misses_max )
160 {
161 // We preallocate half as many blocks as were most recently requested and not available
162 count_misses_current = MAX(count_misses_current >> 1, 1);
163
164 if( count_misses_current > 1 )
165 {
166 /* We preallocate an entire chain of blocks at once and then fix up the resulting linked list so it can all be
167 pushed at once onto the appropriate idle list
168 */
169
170 size_block = ( i << X2_SIZE_SHIFT_POINTER );
171 size_chain = ( size_block * count_misses_current );
172
173 mtx_lock( &self->pool_lock );
174 block_first = (char *)
175 superclass->allocate_elements( (polar_memory_pool *)self, (i << 1) * count_misses_current );
176 mtx_unlock( &self->pool_lock );
177
178 block_last = ( (block_first + size_chain) - size_block );
179 block_current = block_first;
180
181 while( block_current != block_last )
182 {
183 // Point this block at the one that immediately follows it
184 *( (void **)block_current ) = ( block_current + size_block );
185 block_current += size_block;
186 }
187
188 // Push the entire chain of newly-allocated blocks onto the appropriate idle list
189 mtx_lock( &self->pool_lock );
190 *( (void **)block_last ) = self->array_idle_blocks[i].block_last;
191 self->array_idle_blocks[i].block_last = block_first;
192
193 #ifdef POLAR_DEBUG_RUNTIME_MEMORY
194 self->array_idle_blocks[i].count_blocks_idle += count_misses_current;
195 self->array_idle_blocks[i].count_blocks_allocated += count_misses_current;
196 #endif // POLAR_DEBUG_RUNTIME_MEMORY
197
198 mtx_unlock( &self->pool_lock );
199 }
200
201 else
202 {
203 mtx_lock( &self->pool_lock );
204 block_current = (char *)superclass->allocate_elements( (polar_memory_pool *)self, i << 1 );
205 polar_runtime_memory_pool_block_idle_push(self, i, block_current);
206 mtx_unlock( &self->pool_lock );
207 }
208 }
209
210 /* The idea here is to start with a low threshold when preallocating larger block sizes, and then raise the
211 threshold as we get into smaller block sizes. We do this because we fragment larger blocks to make smaller
212 blocks, as needed; but we do not recombine smaller blocks to make larger blocks. There must therefore be a
213 strong demand for smaller blocks, indicated by a number of misses, before we preallocate any.
214 */
215 if( (i & (i - 1)) == 0 )
216 count_misses_max <<= 1;
217 }
218}
219
220// ---------------------------------------------------------------------------------------------------------------------
221
222POLAR_FUNCTION_INTERNAL polar_runtime_memory_pool *
223_polar_runtime_memory_pool_init( polar_runtime_memory_pool *self )
224{
225 size_t size_array_idle_blocks;
226
227 // Chain up first
229 POLAR_RUNTIME_OBJECT_CLASS(superclass_polar_runtime_memory_pool)->init( (polar_runtime_object *)self );
230
231 if( self != NULL )
232 {
233 if( mtx_init(&self->pool_lock, mtx_recursive) == 0 )
234 ;
235
236 else
237 {
238 polar_runtime_object_free( (polar_runtime_object *)self );
239 return NULL;
240 }
241
242 self->data_inherited.size_pool_elements = OBJC_SIZEOF_POINTER;
243
244 /* Allocate the array of idle blocks from the memory pool itself. Note that we will never use array index 0, so
245 we save some space by ignoring it in our allocation request, and then adjusting our pointer afterward.
246 */
247
248 size_array_idle_blocks = ( sizeof(struct polar_list_memory_block_idle) * POLAR_LENGTH_ARRAY_MEMBLOCK_IDLE );
249
250 self->array_idle_blocks = (struct polar_list_memory_block_idle *)
251 polar_memory_pool_page_sbrk( (polar_memory_pool_page *)self, size_array_idle_blocks );
252
253 self->array_idle_blocks = &self->array_idle_blocks[-1];
254 }
255
256 return self;
257}
258
259POLAR_FUNCTION_INTERNAL polar_runtime_object *
260_polar_runtime_memory_pool_finalize( polar_runtime_memory_pool *self )
261{
262 if( self->array_idle_blocks != NULL )
263 {
264 mtx_destroy( &self->pool_lock );
265 objc_memzero( &self->pool_lock, sizeof(self->pool_lock) );
266
267 self->array_idle_blocks = NULL;
268 }
269
270 // Chain up
271 return POLAR_RUNTIME_OBJECT_CLASS(superclass_polar_runtime_memory_pool)->finalize( (polar_runtime_object *)self );
272}
273
274POLAR_FUNCTION_INTERNAL OBJC_FUNCTION_HOTSPOT void *
275_polar_runtime_memory_pool_allocate_elements( polar_runtime_memory_pool *self, size_t count_elements )
276{
277 void *result;
278 polar_memory_pool_class *superclass;
279 intptr_t idx_idle_list, i;
280 char *block_remainder;
281
282 assert( count_elements > 0 );
283
284 superclass = POLAR_MEMORY_POOL_CLASS(superclass_polar_runtime_memory_pool);
285
286 if( count_elements <= POLAR_LENGTH_RUNTIME_STRUCT_MAX )
287 ;
288
289 else
290 {
291 mtx_lock( &self->pool_lock );
292 result = superclass->allocate_elements( (polar_memory_pool *)self, count_elements );
293 mtx_unlock( &self->pool_lock );
294
295 return result;
296 }
297
298 // First, attempt to recycle an idle block
299 idx_idle_list = ( count_elements >> 1 );
300
301 mtx_lock( &self->pool_lock );
302
303 result = polar_runtime_memory_pool_block_idle_pop(self, idx_idle_list);
304 if( result != NULL )
305 {
306 mtx_unlock( &self->pool_lock );
307
308 // The memory is already zeroed
309 return result;
310 }
311
312 // There were no idle blocks of the desired size available
313 self->array_idle_blocks[idx_idle_list].count_misses++;
314
315 // Attempt to fragment and recycle a larger idle block
316 if( idx_idle_list < POLAR_LENGTH_ARRAY_MEMBLOCK_IDLE )
317 {
318 for( i = idx_idle_list + 1; i <= POLAR_LENGTH_ARRAY_MEMBLOCK_IDLE; i++ )
319 {
320 result = polar_runtime_memory_pool_block_idle_pop(self, i);
321
322 if( result != NULL )
323 {
324 // Fragment the larger block and push the upper part back onto the appropriate idle list
325 block_remainder = ( (char *)result + (count_elements << OBJC_SIZE_SHIFT_POINTER) );
326 polar_runtime_memory_pool_block_idle_push(self, i - idx_idle_list, block_remainder);
327
328 mtx_unlock( &self->pool_lock );
329
330 // The memory has already been zeroed
331 return result;
332 }
333 }
334 }
335
336 // There were no idle blocks available; allocate a new one
337 result = superclass->allocate_elements( (polar_memory_pool *)self, count_elements );
338
339#ifdef POLAR_DEBUG_RUNTIME_MEMORY
340 self->array_blocks_idle[idx_idle_list].count_blocks_allocated++;
341#endif // POLAR_DEBUG_RUNTIME_MEMORY
342
343 mtx_unlock( &self->pool_lock );
344
345 // The memory is already zeroed
346 return result;
347}
348
349/* Note that the pool lock is held for the entire time this method executes, owing to the point at which this method is
350 called:
351 _polar_runtime_memory_pool_allocate_elements() <lock acquired> =>
352 _polar_memory_pool_allocate_elements() =>
353 polar_memory_pool_grow() =>
354 _polar_runtime_memory_pool_memory_salvage() <virtual method call>
355*/
356POLAR_FUNCTION_INTERNAL void
357_polar_runtime_memory_pool_memory_salvage( polar_runtime_memory_pool *self, char *byte_next,
358 size_t count_bytes_remaining )
359{
360 size_t size_block_current;
361 intptr_t idx_idle_list;
362
363 while( count_bytes_remaining >= X2_SIZEOF_POINTER )
364 {
365 size_block_current = MIN(count_bytes_remaining, POLAR_SIZE_RUNTIME_STRUCT_MAX);
366
367 idx_idle_list = ( size_block_current >> X2_SIZE_SHIFT_POINTER );
368 size_block_current = ( idx_idle_list << X2_SIZE_SHIFT_POINTER );
369
370 polar_runtime_memory_pool_block_idle_push(self, idx_idle_list, byte_next);
371 byte_next += size_block_current;
372 count_bytes_remaining -= size_block_current;
373
374 #ifdef POLAR_DEBUG_RUNTIME_MEMORY
375 self->count_bytes_salvage += size_block_current;
376 #endif // POLAR_DEBUG_RUNTIME_MEMORY
377 }
378}
379
380POLAR_FUNCTION_INTERNAL void
381_polar_runtime_memory_pool_class_init( polar_runtime_memory_pool_class *self )
382{
383 // Acquire our superclass
384 superclass_polar_runtime_memory_pool = POLAR_RUNTIME_OBJECT_CLASS(self)->class_superclass;
385
386 // Establish our virtual methods
387 ( (polar_runtime_object_class *)self )->init = (polar_func_runtime_object_lifecycle)_polar_runtime_memory_pool_init;
388
389 ( (polar_runtime_object_class *)self )->finalize =
390 (polar_func_runtime_object_lifecycle)_polar_runtime_memory_pool_finalize;
391
392 ( (polar_memory_pool_class *)self )->allocate_elements = (void *)_polar_runtime_memory_pool_allocate_elements;
393 ( (polar_memory_pool_class *)self )->memory_salvage = (void *)_polar_runtime_memory_pool_memory_salvage;
394}
395
396static struct polar_internal_type_info pti_polar_runtime_memory_pool =
397{
398 .name_type = "polar_runtime_memory_pool",
399 .size_type_instance = sizeof(polar_runtime_memory_pool),
400 .size_type_class = sizeof(polar_runtime_memory_pool_class),
401 .type_class_init = (polar_func_runtime_object_class_lifecycle)_polar_runtime_memory_pool_class_init
402};
403
404// Public ==============================================================================================================
405
406POLAR_FUNCTION_INTERNAL OBJC_FUNCTION_CONSTANT polar_internal_type
407polar_runtime_memory_pool_get_type( void )
408{
409 static polar_internal_type pt_polar_runtime_memory_pool = POLAR_INTERNAL_TYPE_INVALID;
410
411 if( pt_polar_runtime_memory_pool != POLAR_INTERNAL_TYPE_INVALID )
412 return pt_polar_runtime_memory_pool;
413
414 pt_polar_runtime_memory_pool = polar_internal_type_init( POLAR_TYPE_MEMORY_POOL, &class_polar_runtime_memory_pool,
415 &pti_polar_runtime_memory_pool );
416
417 return pt_polar_runtime_memory_pool;
418}
419
420POLAR_FUNCTION_INTERNAL void
421polar_runtime_memory_pool_free_elements( polar_runtime_memory_pool *self, size_t count_elements,
422 void *array_elements )
423{
424 assert( POLAR_IS_RUNTIME_MEMORY_POOL(self) );
425 assert( count_elements <= POLAR_LENGTH_RUNTIME_STRUCT_MAX );
426 assert( count_elements & 1 == 0 );
427
428 if( (count_elements > 0) && (array_elements != NULL) )
429 {
430 // Zero the memory before it is pushed onto the appropriate idle list
431 objc_memzero(array_elements, count_elements << OBJC_SIZE_SHIFT_POINTER);
432
433 mtx_lock( &self->pool_lock );
434 polar_runtime_memory_pool_block_idle_push(self, (count_elements >> 1), array_elements);
435 mtx_unlock( &self->pool_lock );
436 }
437}
438
439POLAR_FUNCTION_INTERNAL void *
440polar_runtime_memory_pool_intern_struct( polar_runtime_memory_pool *self, void *p_struct_source,
441 size_t size_struct_source )
442{
443 void *result;
444 size_t length_struct_source;
445
446 assert( POLAR_IS_RUNTIME_MEMORY_POOL(self) );
447 assert( size_struct_source > 0 );
448
449 length_struct_source = POLAR_LENGTH_STRUCT_IN_POINTERS(size_struct_source);
450 result = polar_memory_pool_allocate_elements( (polar_memory_pool *)self, length_struct_source );
451
452 if( p_struct_source != NULL )
453 return objc_memcpy(result, p_struct_source, size_struct_source);
454
455 return result;
456}
457
458POLAR_FUNCTION_INTERNAL const char *
459polar_runtime_memory_pool_intern_string( polar_runtime_memory_pool *self, const char *p_string_source,
460 intptr_t len_string_source )
461{
462 char *result;
463 polar_hash_table_node_string_key *node_string_interned;
464 size_t len_string, len_string_in_pointers;
465
466 assert( POLAR_IS_RUNTIME_MEMORY_POOL(self) );
467
468 if( (p_string_source != NULL) && (*p_string_source != 0) && (len_string_source != 0) )
469 ;
470
471 else
472 return NULL;
473
474 if( len_string_source > 0 )
475 len_string = objc_strnlen(p_string_source, (size_t)len_string_source);
476
477 else
478 len_string = objc_strlen(p_string_source);
479
480 mtx_lock( &self->pool_lock );
481
482 if( self->table_strings_interned != NULL )
483 {
484 node_string_interned = (polar_hash_table_node_string_key *)
485 polar_hash_table_get_node(self->table_strings_interned, (void *)p_string_source);
486
487 if( node_string_interned != NULL )
488 {
489 result = (char *)polar_hash_table_node_string_key_get_key(node_string_interned);
490 mtx_unlock( &self->pool_lock );
491
492 return (const char *)result;
493 }
494 }
495
496 else
497 {
498 self->table_strings_interned = polar_hash_table_new( POLAR_COUNT_BUCKETS_TABLE_STRINGS_INTERNED,
499 POLAR_TYPE_HASH_TABLE_NODE_STRING_KEY );
500 }
501
502 len_string_in_pointers = POLAR_LENGTH_STRUCT_IN_POINTERS(len_string + 1);
503
504 /* This causes a recursive lock on `self`, but potentially allows us to recycle an idle structure for strings that
505 will fit inside of one of them.
506 */
507 result = (char *)polar_memory_pool_allocate_elements( (polar_memory_pool *)self, len_string_in_pointers );
508
509 objc_memcpy(result, (void *)p_string_source, len_string);
510 polar_hash_table_add_node(self->table_strings_interned, result);
511
512 mtx_unlock( &self->pool_lock );
513 return (const char *)result;
514}
515
516POLAR_FUNCTION_INTERNAL void
517polar_runtime_memory_pool_recycle_struct( polar_runtime_memory_pool *self, void *p_struct_source,
518 size_t size_struct_source )
519{
520 assert( POLAR_IS_RUNTIME_MEMORY_POOL(self) );
521
522 if( (size_struct_source >= X2_SIZEOF_POINTER) && (p_struct_source != NULL) )
523 {
524 // Zero the memory first
525 objc_memzero(p_struct_source, size_struct_source);
526
527 mtx_lock( &self->pool_lock );
528 polar_memory_pool_memory_salvage( (polar_memory_pool *)self, (char *)p_struct_source, size_struct_source );
529 mtx_unlock( &self->pool_lock );
530 }
531}
static void mtx_destroy(mtx_t *p_mutex)
Definition polar-windows-platform-threading.h:49
static intptr_t mtx_init(mtx_t *p_mutex, intptr_t tp_mutex)
Definition polar-windows-platform-threading.h:39
static intptr_t mtx_lock(mtx_t *p_mutex)
Definition polar-windows-platform-threading.h:56
static intptr_t mtx_unlock(mtx_t *p_mutex)
Definition polar-windows-platform-threading.h:77
@ mtx_recursive
Definition polar-windows-platform-threading.h:35
Definition polar-hash-table-node-string-key.h:25
Definition polar-internal-type.h:34
Definition polar-runtime-memory-pool.h:35
Definition polar-memory-pool.h:31
Definition polar-memory-pool-page.h:24
Definition polar-memory-pool.h:24
Definition polar-runtime-memory-pool.h:58
Definition polar-runtime-memory-pool.h:46
Definition polar-runtime-object.h:36
Definition polar-runtime-object.h:31